mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-04 07:13:28 +01:00 
			
		
		
		
	
		
			
				
	
	
		
			220 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
			
		
		
	
	
			220 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			Dart
		
	
	
	
	
	
import 'dart:convert';
 | 
						|
 | 
						|
import 'package:device_info_plus/device_info_plus.dart';
 | 
						|
import 'package:easy_localization/easy_localization.dart';
 | 
						|
import 'package:obtainium/components/generated_form.dart';
 | 
						|
import 'package:obtainium/custom_errors.dart';
 | 
						|
import 'package:obtainium/providers/source_provider.dart';
 | 
						|
 | 
						|
extension Unique<E, Id> on List<E> {
 | 
						|
  List<E> unique([Id Function(E element)? id, bool inplace = true]) {
 | 
						|
    final ids = <dynamic>{};
 | 
						|
    var list = inplace ? this : List<E>.from(this);
 | 
						|
    list.retainWhere((x) => ids.add(id != null ? id(x) : x as Id));
 | 
						|
    return list;
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class APKPure extends AppSource {
 | 
						|
  APKPure() {
 | 
						|
    hosts = ['apkpure.net', 'apkpure.com'];
 | 
						|
    allowSubDomains = true;
 | 
						|
    naiveStandardVersionDetection = true;
 | 
						|
    showReleaseDateAsVersionToggle = true;
 | 
						|
    additionalSourceAppSpecificSettingFormItems = [
 | 
						|
      [
 | 
						|
        GeneratedFormSwitch(
 | 
						|
          'fallbackToOlderReleases',
 | 
						|
          label: tr('fallbackToOlderReleases'),
 | 
						|
          defaultValue: true,
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
      [
 | 
						|
        GeneratedFormSwitch(
 | 
						|
          'stayOneVersionBehind',
 | 
						|
          label: tr('stayOneVersionBehind'),
 | 
						|
          defaultValue: false,
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
      [
 | 
						|
        GeneratedFormSwitch(
 | 
						|
          'useFirstApkOfVersion',
 | 
						|
          label: tr('useFirstApkOfVersion'),
 | 
						|
          defaultValue: true,
 | 
						|
        ),
 | 
						|
      ],
 | 
						|
    ];
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
 | 
						|
    RegExp standardUrlRegExB = RegExp(
 | 
						|
      '^https?://m.${getSourceRegex(hosts)}(/+[^/]{2})?/+[^/]+/+[^/]+',
 | 
						|
      caseSensitive: false,
 | 
						|
    );
 | 
						|
    RegExpMatch? match = standardUrlRegExB.firstMatch(url);
 | 
						|
    if (match != null) {
 | 
						|
      var uri = Uri.parse(url);
 | 
						|
      url = 'https://${uri.host.substring(2)}${uri.path}';
 | 
						|
    }
 | 
						|
    RegExp standardUrlRegExA = RegExp(
 | 
						|
      '^https?://(www\\.)?${getSourceRegex(hosts)}(/+[^/]{2})?/+[^/]+/+[^/]+',
 | 
						|
      caseSensitive: false,
 | 
						|
    );
 | 
						|
    match = standardUrlRegExA.firstMatch(url);
 | 
						|
    if (match == null) {
 | 
						|
      throw InvalidURLError(name);
 | 
						|
    }
 | 
						|
    return match.group(0)!;
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<String?> tryInferringAppId(
 | 
						|
    String standardUrl, {
 | 
						|
    Map<String, dynamic> additionalSettings = const {},
 | 
						|
  }) async {
 | 
						|
    return Uri.parse(standardUrl).pathSegments.last;
 | 
						|
  }
 | 
						|
 | 
						|
  Future<APKDetails> getDetailsForVersion(
 | 
						|
    List<Map<String, dynamic>> versionVariants,
 | 
						|
    List<String> supportedArchs,
 | 
						|
    Map<String, dynamic> additionalSettings,
 | 
						|
  ) async {
 | 
						|
    var apkUrls = versionVariants
 | 
						|
        .map((e) {
 | 
						|
          String appId = e['package_name'];
 | 
						|
          String versionCode = e['version_code'];
 | 
						|
 | 
						|
          List<String> architectures = e['native_code']?.cast<String>();
 | 
						|
          String architectureString = architectures.join(',');
 | 
						|
          if (architectures.contains("universal") ||
 | 
						|
              architectures.contains("unlimited")) {
 | 
						|
            architectures = [];
 | 
						|
          }
 | 
						|
          if (additionalSettings['autoApkFilterByArch'] == true &&
 | 
						|
              architectures.isNotEmpty &&
 | 
						|
              architectures.where((a) => supportedArchs.contains(a)).isEmpty) {
 | 
						|
            return null;
 | 
						|
          }
 | 
						|
 | 
						|
          String type = e['asset']['type'];
 | 
						|
          String downloadUri = e['asset']['url'];
 | 
						|
 | 
						|
          return MapEntry(
 | 
						|
            '$appId-$versionCode-$architectureString.${type.toLowerCase()}',
 | 
						|
            downloadUri,
 | 
						|
          );
 | 
						|
        })
 | 
						|
        .nonNulls
 | 
						|
        .toList()
 | 
						|
        .unique((e) => e.key);
 | 
						|
 | 
						|
    if (apkUrls.isEmpty) {
 | 
						|
      throw NoAPKError();
 | 
						|
    }
 | 
						|
 | 
						|
    // get version details from first variant
 | 
						|
    var v = versionVariants.first;
 | 
						|
    String version = v['version_name'];
 | 
						|
    String author = v['developer'];
 | 
						|
    String appName = v['title'];
 | 
						|
    DateTime releaseDate = DateTime.parse(v['update_date']);
 | 
						|
    String? changeLog = v['whatsnew'];
 | 
						|
    if (changeLog != null && changeLog.isEmpty) {
 | 
						|
      changeLog = null;
 | 
						|
    }
 | 
						|
 | 
						|
    if (additionalSettings['useFirstApkOfVersion'] == true) {
 | 
						|
      apkUrls = [apkUrls.first];
 | 
						|
    }
 | 
						|
 | 
						|
    return APKDetails(
 | 
						|
      version,
 | 
						|
      apkUrls,
 | 
						|
      AppNames(author, appName),
 | 
						|
      releaseDate: releaseDate,
 | 
						|
      changeLog: changeLog,
 | 
						|
    );
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<Map<String, String>?> getRequestHeaders(
 | 
						|
    Map<String, dynamic> additionalSettings, {
 | 
						|
    bool forAPKDownload = false,
 | 
						|
  }) async {
 | 
						|
    if (forAPKDownload) {
 | 
						|
      return null;
 | 
						|
    } else {
 | 
						|
      return {
 | 
						|
        "Ual-Access-Businessid": "projecta",
 | 
						|
        "Ual-Access-ProjectA":
 | 
						|
            '{"device_info":{"os_ver":"${((await DeviceInfoPlugin().androidInfo).version.sdkInt)}"}}',
 | 
						|
      };
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  @override
 | 
						|
  Future<APKDetails> getLatestAPKDetails(
 | 
						|
    String standardUrl,
 | 
						|
    Map<String, dynamic> additionalSettings,
 | 
						|
  ) async {
 | 
						|
    String appId = (await tryInferringAppId(standardUrl))!;
 | 
						|
 | 
						|
    List<String> supportedArchs =
 | 
						|
        (await DeviceInfoPlugin().androidInfo).supportedAbis;
 | 
						|
 | 
						|
    // request versions from API
 | 
						|
    var res = await sourceRequest(
 | 
						|
      "https://tapi.pureapk.com/v3/get_app_his_version?package_name=$appId&hl=en",
 | 
						|
      additionalSettings,
 | 
						|
    );
 | 
						|
    if (res.statusCode != 200) {
 | 
						|
      throw getObtainiumHttpError(res);
 | 
						|
    }
 | 
						|
    List<Map<String, dynamic>> apks = jsonDecode(
 | 
						|
      res.body,
 | 
						|
    )['version_list'].cast<Map<String, dynamic>>();
 | 
						|
 | 
						|
    // group by version
 | 
						|
    List<List<Map<String, dynamic>>> versions = apks
 | 
						|
        .fold<Map<String, List<Map<String, dynamic>>>>({}, (
 | 
						|
          Map<String, List<Map<String, dynamic>>> val,
 | 
						|
          Map<String, dynamic> element,
 | 
						|
        ) {
 | 
						|
          String v = element['version_name'];
 | 
						|
          if (!val.containsKey(v)) {
 | 
						|
            val[v] = [];
 | 
						|
          }
 | 
						|
          val[v]?.add(element);
 | 
						|
          return val;
 | 
						|
        })
 | 
						|
        .values
 | 
						|
        .toList();
 | 
						|
 | 
						|
    if (versions.isEmpty) {
 | 
						|
      throw NoReleasesError();
 | 
						|
    }
 | 
						|
 | 
						|
    for (var i = 0; i < versions.length; i++) {
 | 
						|
      var v = versions[i];
 | 
						|
      try {
 | 
						|
        if (i == 0 && additionalSettings['stayOneVersionBehind'] == true) {
 | 
						|
          throw NoReleasesError();
 | 
						|
        }
 | 
						|
        return await getDetailsForVersion(
 | 
						|
          v,
 | 
						|
          supportedArchs,
 | 
						|
          additionalSettings,
 | 
						|
        );
 | 
						|
      } catch (e) {
 | 
						|
        if (additionalSettings['fallbackToOlderReleases'] != true ||
 | 
						|
            i == versions.length - 1) {
 | 
						|
          rethrow;
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    throw NoAPKError();
 | 
						|
  }
 | 
						|
}
 |