mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-30 21:13:28 +01:00 
			
		
		
		
	Added "skip latest" option to APKPure (also fallback toggle)
This commit is contained in:
		| @@ -2,6 +2,7 @@ import 'package:device_info_plus/device_info_plus.dart'; | ||||
| import 'package:easy_localization/easy_localization.dart'; | ||||
| import 'package:html/parser.dart'; | ||||
| import 'package:obtainium/app_sources/html.dart'; | ||||
| import 'package:obtainium/components/generated_form.dart'; | ||||
| import 'package:obtainium/custom_errors.dart'; | ||||
| import 'package:obtainium/providers/source_provider.dart'; | ||||
|  | ||||
| @@ -26,6 +27,16 @@ class APKPure extends AppSource { | ||||
|     allowSubDomains = true; | ||||
|     naiveStandardVersionDetection = true; | ||||
|     showReleaseDateAsVersionToggle = true; | ||||
|     additionalSourceAppSpecificSettingFormItems = [ | ||||
|       [ | ||||
|         GeneratedFormSwitch('fallbackToOlderReleases', | ||||
|             label: tr('fallbackToOlderReleases'), defaultValue: true) | ||||
|       ], | ||||
|       [ | ||||
|         GeneratedFormSwitch('stayOneVersionBehind', | ||||
|             label: tr('stayOneVersionBehind'), defaultValue: false) | ||||
|       ] | ||||
|     ]; | ||||
|   } | ||||
|  | ||||
|   @override | ||||
| @@ -54,6 +65,97 @@ class APKPure extends AppSource { | ||||
|     return Uri.parse(standardUrl).pathSegments.last; | ||||
|   } | ||||
|  | ||||
|   getDetailsForVersionLink( | ||||
|       String standardUrl, | ||||
|       String appId, | ||||
|       String host, | ||||
|       List<String> supportedArchs, | ||||
|       String link, | ||||
|       Map<String, dynamic> additionalSettings) async { | ||||
|     var res = await sourceRequest(link, additionalSettings); | ||||
|     if (res.statusCode == 200) { | ||||
|       var html = parse(res.body); | ||||
|       var apksDiv = | ||||
|           html.querySelector('#version-list div div.show-more-content'); | ||||
|       DateTime? topReleaseDate; | ||||
|       var apkUrls = apksDiv | ||||
|               ?.querySelectorAll('div.group-title') | ||||
|               .map((e) { | ||||
|                 String architectureString = e.text.trim(); | ||||
|                 if (architectureString.toLowerCase() == 'unlimited' || | ||||
|                     architectureString.toLowerCase() == 'universal') { | ||||
|                   architectureString = ''; | ||||
|                 } | ||||
|                 List<String> architectures = architectureString | ||||
|                     .split(',') | ||||
|                     .map((e) => e.trim()) | ||||
|                     .where((e) => e.isNotEmpty) | ||||
|                     .toList(); | ||||
|                 // Only take the first APK for each architecture, ignore others for now, for simplicity | ||||
|                 // Unclear why there can even be multiple APKs for the same version and arch | ||||
|                 var apkInfo = e.nextElementSibling?.querySelector('div.info'); | ||||
|                 String? versionCode = RegExp('[0-9]+') | ||||
|                     .firstMatch(apkInfo | ||||
|                             ?.querySelector('div.info-top span.code') | ||||
|                             ?.text ?? | ||||
|                         '') | ||||
|                     ?.group(0) | ||||
|                     ?.trim(); | ||||
|                 var types = apkInfo | ||||
|                         ?.querySelectorAll('div.info-top span.tag') | ||||
|                         .map((e) => e.text.trim()) | ||||
|                         .map((t) => t == 'APKs' ? 'APK' : t) ?? | ||||
|                     []; | ||||
|                 String type = types.isEmpty | ||||
|                     ? 'APK' | ||||
|                     : types.length == 1 | ||||
|                         ? types.first | ||||
|                         : types.last; | ||||
|                 String? dateString = apkInfo | ||||
|                     ?.querySelector('div.info-bottom span.time') | ||||
|                     ?.text | ||||
|                     .trim(); | ||||
|                 DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString); | ||||
|                 if (additionalSettings['autoApkFilterByArch'] == true && | ||||
|                     architectures.isNotEmpty && | ||||
|                     architectures | ||||
|                         .where((a) => supportedArchs.contains(a)) | ||||
|                         .isEmpty) { | ||||
|                   return const MapEntry('', ''); | ||||
|                 } | ||||
|                 topReleaseDate ??= | ||||
|                     releaseDate; // Just use the release date of the first APK in the list as the release date for this version | ||||
|                 return MapEntry( | ||||
|                     '$appId-$versionCode-$architectureString.${type.toLowerCase()}', | ||||
|                     'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?versionCode=$versionCode'); | ||||
|               }) | ||||
|               .where((e) => e.key.isNotEmpty) | ||||
|               .toList() ?? | ||||
|           []; | ||||
|       if (apkUrls.isEmpty) { | ||||
|         throw NoAPKError(); | ||||
|       } | ||||
|       String version = Uri.parse(link).pathSegments.last; | ||||
|       String author = html | ||||
|               .querySelector('span.info-sdk') | ||||
|               ?.text | ||||
|               .trim() | ||||
|               .substring(version.length + 4) ?? | ||||
|           Uri.parse(standardUrl).pathSegments.reversed.last; | ||||
|       String appName = | ||||
|           html.querySelector('h1.info-title')?.text.trim() ?? appId; | ||||
|       String? changeLog = html | ||||
|           .querySelector('div.module.change-log') | ||||
|           ?.innerHtml | ||||
|           .trim() | ||||
|           .replaceAll("<br>", "  \n"); | ||||
|       return APKDetails(version, apkUrls, AppNames(author, appName), | ||||
|           releaseDate: topReleaseDate, changeLog: changeLog); | ||||
|     } else { | ||||
|       throw getObtainiumHttpError(res); | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   @override | ||||
|   Future<APKDetails> getLatestAPKDetails( | ||||
|     String standardUrl, | ||||
| @@ -80,88 +182,17 @@ class APKPure extends AppSource { | ||||
|  | ||||
|     for (var i = 0; i < versionLinks.length; i++) { | ||||
|       var link = versionLinks[i]; | ||||
|       var res = await sourceRequest(link.key, additionalSettings); | ||||
|       if (res.statusCode == 200) { | ||||
|         var html = parse(res.body); | ||||
|         var apksDiv = | ||||
|             html.querySelector('#version-list div div.show-more-content'); | ||||
|         DateTime? topReleaseDate; | ||||
|         var apkUrls = apksDiv | ||||
|                 ?.querySelectorAll('div.group-title') | ||||
|                 .map((e) { | ||||
|                   String architectureString = e.text.trim(); | ||||
|                   if (architectureString.toLowerCase() == 'unlimited' || | ||||
|                       architectureString.toLowerCase() == 'universal') { | ||||
|                     architectureString = ''; | ||||
|                   } | ||||
|                   List<String> architectures = architectureString | ||||
|                       .split(',') | ||||
|                       .map((e) => e.trim()) | ||||
|                       .where((e) => e.isNotEmpty) | ||||
|                       .toList(); | ||||
|                   // Only take the first APK for each architecture, ignore others for now, for simplicity | ||||
|                   // Unclear why there can even be multiple APKs for the same version and arch | ||||
|                   var apkInfo = e.nextElementSibling?.querySelector('div.info'); | ||||
|                   String? versionCode = RegExp('[0-9]+') | ||||
|                       .firstMatch(apkInfo | ||||
|                               ?.querySelector('div.info-top span.code') | ||||
|                               ?.text ?? | ||||
|                           '') | ||||
|                       ?.group(0) | ||||
|                       ?.trim(); | ||||
|                   var types = apkInfo | ||||
|                           ?.querySelectorAll('div.info-top span.tag') | ||||
|                           .map((e) => e.text.trim()) | ||||
|                           .map((t) => t == 'APKs' ? 'APK' : t) ?? | ||||
|                       []; | ||||
|                   String type = types.isEmpty | ||||
|                       ? 'APK' | ||||
|                       : types.length == 1 | ||||
|                           ? types.first | ||||
|                           : types.last; | ||||
|                   String? dateString = apkInfo | ||||
|                       ?.querySelector('div.info-bottom span.time') | ||||
|                       ?.text | ||||
|                       .trim(); | ||||
|                   DateTime? releaseDate = | ||||
|                       parseDateTimeMMMddCommayyyy(dateString); | ||||
|                   if (additionalSettings['autoApkFilterByArch'] == true && | ||||
|                       architectures.isNotEmpty && | ||||
|                       architectures | ||||
|                           .where((a) => supportedArchs.contains(a)) | ||||
|                           .isEmpty) { | ||||
|                     return const MapEntry('', ''); | ||||
|                   } | ||||
|                   topReleaseDate ??= | ||||
|                       releaseDate; // Just use the release date of the first APK in the list as the release date for this version | ||||
|                   return MapEntry( | ||||
|                       '$appId-$versionCode-$architectureString.${type.toLowerCase()}', | ||||
|                       'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?versionCode=$versionCode'); | ||||
|                 }) | ||||
|                 .where((e) => e.key.isNotEmpty) | ||||
|                 .toList() ?? | ||||
|             []; | ||||
|         if (apkUrls.isEmpty) { | ||||
|           continue; | ||||
|       try { | ||||
|         if (i == 0 && additionalSettings['stayOneVersionBehind'] == true) { | ||||
|           throw NoReleasesError(); | ||||
|         } | ||||
|         return await getDetailsForVersionLink(standardUrl, appId, host, | ||||
|             supportedArchs, link.key, additionalSettings); | ||||
|       } catch (e) { | ||||
|         if (additionalSettings['fallbackToOlderReleases'] != true || | ||||
|             i == versionLinks.length - 1) { | ||||
|           rethrow; | ||||
|         } | ||||
|         String version = Uri.parse(link.key).pathSegments.last; | ||||
|         String author = html | ||||
|                 .querySelector('span.info-sdk') | ||||
|                 ?.text | ||||
|                 .trim() | ||||
|                 .substring(version.length + 4) ?? | ||||
|             Uri.parse(standardUrl).pathSegments.reversed.last; | ||||
|         String appName = | ||||
|             html.querySelector('h1.info-title')?.text.trim() ?? appId; | ||||
|         String? changeLog = html | ||||
|             .querySelector('div.module.change-log') | ||||
|             ?.innerHtml | ||||
|             .trim() | ||||
|             .replaceAll("<br>", "  \n"); | ||||
|         return APKDetails(version, apkUrls, AppNames(author, appName), | ||||
|             releaseDate: topReleaseDate, changeLog: changeLog); | ||||
|       } else { | ||||
|         throw getObtainiumHttpError(res); | ||||
|       } | ||||
|     } | ||||
|     throw NoAPKError(); | ||||
|   | ||||
		Reference in New Issue
	
	Block a user