mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-08-01 05:10:15 +02: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