Added "skip latest" option to APKPure (also fallback toggle)

This commit is contained in:
Imran Remtulla
2024-10-01 15:49:00 -04:00
parent 783e8029b6
commit cbcc8c4eaf
24 changed files with 137 additions and 83 deletions

View File

@@ -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();