diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 90d0737..fa1fcba 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 13bf6d7..18b0a67 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Správce", "shizukuBinderNotFound": "Shizuku neběží", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Odstranit Apku?", "other": "Odstranit Apky?" diff --git a/assets/translations/de.json b/assets/translations/de.json index 28ef8fb..3c924b4 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku läuft nicht", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index ed5a898..7f2daf1 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -289,6 +289,8 @@ "shizukuBinderNotFound": "Сompatible Shizuku service wasn't found", "useSystemFont": "Use the system font", "systemFontError": "Error loading the system font: {}", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index 82c00a4..abe6417 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku no está operativo", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 9cf7264..feea14f 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 70aac00..0a64673 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 61ebca0..df48201 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index 55cecc8..91dff86 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku non è in esecuzione", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 281693b..43101c1 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -289,6 +289,8 @@ "shizukuBinderNotFound": "Shizukuが起動していません", "useSystemFont": "システムフォントを使用する", "systemFontError": "システムフォントの読み込みエラー: {}", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/nl.json b/assets/translations/nl.json index cb5f434..88ae1b4 100644 --- a/assets/translations/nl.json +++ b/assets/translations/nl.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "App verwijderen?", "other": "Apps verwijderen?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 23b1c1a..c602475 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/pt.json b/assets/translations/pt.json index 809a850..2fe1cb2 100644 --- a/assets/translations/pt.json +++ b/assets/translations/pt.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku não está rodando", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Remover aplicativo?", "other": "Remover aplicativos?" diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 67c424d..ab25718 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -289,6 +289,8 @@ "shizukuBinderNotFound": "Совместимый сервис Shizuku не найден", "useSystemFont": "Использовать системный шрифт", "systemFontError": "Ошибка загрузки системного шрифта: {}", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/sv.json b/assets/translations/sv.json index 73fd8ea..6886fdc 100644 --- a/assets/translations/sv.json +++ b/assets/translations/sv.json @@ -273,6 +273,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Ta Bort App?", "other": "Ta Bort Appar?" diff --git a/assets/translations/tr.json b/assets/translations/tr.json index c3a96f1..35f8c16 100644 --- a/assets/translations/tr.json +++ b/assets/translations/tr.json @@ -287,6 +287,8 @@ "shizuku": "Shizuku", "root": "Root", "shizukuBinderNotFound": "Shizuku is not running", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "Uygulamayı Kaldır?", "other": "Uygulamaları Kaldır?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 3a92d78..094c032 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -289,6 +289,8 @@ "shizukuBinderNotFound": "未发现兼容的 Shizuku 服务", "useSystemFont": "使用系统字体", "systemFontError": "加载系统字体出错:{}", + "useVersionCodeAsOSVersion": "Use app versionCode as OS-detected version", + "requestHeader": "Request header", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/apkcombo.dart b/lib/app_sources/apkcombo.dart index afcaf7f..1a58847 100644 --- a/lib/app_sources/apkcombo.dart +++ b/lib/app_sources/apkcombo.dart @@ -10,9 +10,10 @@ class APKCombo extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+'); - var match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+', + caseSensitive: false); + var match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -27,8 +28,8 @@ class APKCombo extends AppSource { @override Future?> getRequestHeaders( - {Map additionalSettings = const {}, - bool forAPKDownload = false}) async { + Map additionalSettings, + {bool forAPKDownload = false}) async { return { "User-Agent": "curl/8.0.1", "Accept": "*/*", @@ -37,8 +38,9 @@ class APKCombo extends AppSource { }; } - Future>> getApkUrls(String standardUrl) async { - var res = await sourceRequest('$standardUrl/download/apk'); + Future>> getApkUrls( + String standardUrl, Map additionalSettings) async { + var res = await sourceRequest('$standardUrl/download/apk', {}); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } @@ -71,9 +73,9 @@ class APKCombo extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, String standardUrl) async { - var freshURLs = await getApkUrls(standardUrl); + Future apkUrlPrefetchModifier(String apkUrl, String standardUrl, + Map additionalSettings) async { + var freshURLs = await getApkUrls(standardUrl, additionalSettings); var path2Match = Uri.parse(apkUrl).path; for (var url in freshURLs) { if (Uri.parse(url.value).path == path2Match) { @@ -89,7 +91,7 @@ class APKCombo extends AppSource { Map additionalSettings, ) async { String appId = (await tryInferringAppId(standardUrl))!; - var preres = await sourceRequest(standardUrl); + var preres = await sourceRequest(standardUrl, additionalSettings); if (preres.statusCode != 200) { throw getObtainiumHttpError(preres); } @@ -113,7 +115,9 @@ class APKCombo extends AppSource { } } return APKDetails( - version, await getApkUrls(standardUrl), AppNames(author, appName), + version, + await getApkUrls(standardUrl, additionalSettings), + AppNames(author, appName), releaseDate: releaseDate); } } diff --git a/lib/app_sources/apkmirror.dart b/lib/app_sources/apkmirror.dart index 1bfa8d0..4e3cd1f 100644 --- a/lib/app_sources/apkmirror.dart +++ b/lib/app_sources/apkmirror.dart @@ -32,9 +32,10 @@ class APKMirror extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -58,7 +59,7 @@ class APKMirror extends AppSource { true ? additionalSettings['filterReleaseTitlesByRegEx'] : null; - Response res = await sourceRequest('$standardUrl/feed'); + Response res = await sourceRequest('$standardUrl/feed', additionalSettings); if (res.statusCode == 200) { var items = parse(res.body).querySelectorAll('item'); dynamic targetRelease; diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index e145b28..f4c0026 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -27,15 +27,17 @@ class APKPure extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegExB = - RegExp('^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?'); - RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); + RegExp standardUrlRegExB = RegExp( + '^https?://m.${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?', + caseSensitive: false); + RegExpMatch? match = standardUrlRegExB.firstMatch(url); if (match != null) { url = 'https://${getSourceRegex(hosts)}${Uri.parse(url).path}'; } RegExp standardUrlRegExA = RegExp( - '^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?'); - match = standardUrlRegExA.firstMatch(url.toLowerCase()); + '^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+(/+[^/]+)?', + caseSensitive: false); + match = standardUrlRegExA.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -55,8 +57,8 @@ class APKPure extends AppSource { ) async { String appId = (await tryInferringAppId(standardUrl))!; String host = Uri.parse(standardUrl).host; - var res = await sourceRequest('$standardUrl/download'); - var resChangelog = await sourceRequest(standardUrl); + var res = await sourceRequest('$standardUrl/download', additionalSettings); + var resChangelog = await sourceRequest(standardUrl, additionalSettings); if (res.statusCode == 200 && resChangelog.statusCode == 200) { var html = parse(res.body); var htmlChangelog = parse(resChangelog.body); @@ -69,7 +71,8 @@ class APKPure extends AppSource { DateTime? releaseDate = parseDateTimeMMMddCommayyyy(dateString); String type = html.querySelector('a.info-tag')?.text.trim() ?? 'APK'; List> apkUrls = [ - MapEntry('$appId.apk', 'https://d.$host/b/$type/$appId?version=latest') + MapEntry('$appId.apk', + 'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?version=latest') ]; String author = html .querySelector('span.info-sdk') diff --git a/lib/app_sources/aptoide.dart b/lib/app_sources/aptoide.dart index 58470d5..a2795d6 100644 --- a/lib/app_sources/aptoide.dart +++ b/lib/app_sources/aptoide.dart @@ -14,9 +14,10 @@ class Aptoide extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -26,11 +27,13 @@ class Aptoide extends AppSource { @override Future tryInferringAppId(String standardUrl, {Map additionalSettings = const {}}) async { - return (await getAppDetailsJSON(standardUrl))['package']; + return (await getAppDetailsJSON( + standardUrl, additionalSettings))['package']; } - Future> getAppDetailsJSON(String standardUrl) async { - var res = await sourceRequest(standardUrl); + Future> getAppDetailsJSON( + String standardUrl, Map additionalSettings) async { + var res = await sourceRequest(standardUrl, additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } @@ -41,8 +44,8 @@ class Aptoide extends AppSource { } else { throw NoReleasesError(); } - var res2 = - await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id'); + var res2 = await sourceRequest( + 'https://ws2.aptoide.com/api/7/getApp/app_id/$id', additionalSettings); if (res2.statusCode != 200) { throw getObtainiumHttpError(res); } @@ -54,7 +57,7 @@ class Aptoide extends AppSource { String standardUrl, Map additionalSettings, ) async { - var appDetails = await getAppDetailsJSON(standardUrl); + var appDetails = await getAppDetailsJSON(standardUrl, additionalSettings); String appName = appDetails['name'] ?? tr('app'); String author = appDetails['developer']?['name'] ?? name; String? dateStr = appDetails['updated']; diff --git a/lib/app_sources/codeberg.dart b/lib/app_sources/codeberg.dart index 9e9b7b6..bf01701 100644 --- a/lib/app_sources/codeberg.dart +++ b/lib/app_sources/codeberg.dart @@ -16,9 +16,10 @@ class Codeberg extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } diff --git a/lib/app_sources/fdroid.dart b/lib/app_sources/fdroid.dart index f3a38bd..9129f3c 100644 --- a/lib/app_sources/fdroid.dart +++ b/lib/app_sources/fdroid.dart @@ -38,15 +38,17 @@ class FDroid extends AppSource { @override String sourceSpecificStandardizeURL(String url) { RegExp standardUrlRegExB = RegExp( - '^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+'); - RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); + '^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegExB.firstMatch(url); if (match != null) { url = 'https://${Uri.parse(match.group(0)!).host}/packages/${Uri.parse(url).pathSegments.last}'; } - RegExp standardUrlRegExA = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+'); - match = standardUrlRegExA.firstMatch(url.toLowerCase()); + RegExp standardUrlRegExA = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/+packages/+[^/]+', + caseSensitive: false); + match = standardUrlRegExA.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -67,7 +69,8 @@ class FDroid extends AppSource { String? appId = await tryInferringAppId(standardUrl); String host = Uri.parse(standardUrl).host; var details = getAPKUrlsFromFDroidPackagesAPIResponse( - await sourceRequest('https://$host/api/v1/packages/$appId'), + await sourceRequest( + 'https://$host/api/v1/packages/$appId', additionalSettings), 'https://$host/repo/$appId', standardUrl, name, @@ -84,29 +87,30 @@ class FDroid extends AppSource { if (!hostChanged) { try { var res = await sourceRequest( - 'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml'); + 'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml', + additionalSettings); var lines = res.body.split('\n'); - String author = lines - .where((l) => l.startsWith('AuthorName: ')) - .first - .split(': ') - .sublist(1) - .join(': '); - details.names.author = author; + var authorLines = lines.where((l) => l.startsWith('AuthorName: ')); + if (authorLines.isNotEmpty) { + details.names.author = + authorLines.first.split(': ').sublist(1).join(': '); + } var changelogUrls = lines.where((l) => l.startsWith('Changelog: ')); if (changelogUrls.isNotEmpty) { details.changeLog = changelogUrls.first; - details.changeLog = (await sourceRequest(details.changeLog! - .split(': ') - .sublist(1) - .join(': ') - .replaceFirst('/blob/', '/raw/'))) + details.changeLog = (await sourceRequest( + details.changeLog! + .split(': ') + .sublist(1) + .join(': ') + .replaceFirst('/blob/', '/raw/'), + additionalSettings)) .body; } } catch (e) { // Fail silently } - if ((details.changeLog?.length ?? 0) > 1000) { + if ((details.changeLog?.length ?? 0) > 2048) { details.changeLog = '${details.changeLog!.substring(0, 2048)}...'; } } @@ -117,7 +121,7 @@ class FDroid extends AppSource { Future>> search(String query, {Map querySettings = const {}}) async { Response res = await sourceRequest( - 'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}'); + 'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {}); if (res.statusCode == 200) { Map> urlsWithDescriptions = {}; parse(res.body).querySelectorAll('.package-header').forEach((e) { diff --git a/lib/app_sources/fdroidrepo.dart b/lib/app_sources/fdroidrepo.dart index f988a4f..a1c571b 100644 --- a/lib/app_sources/fdroidrepo.dart +++ b/lib/app_sources/fdroidrepo.dart @@ -59,7 +59,7 @@ class FDroidRepo extends AppSource { throw NoReleasesError(); } url = removeQueryParamsFromUrl(standardizeUrl(url)); - var res = await sourceRequest('$url/index.xml'); + var res = await sourceRequest('$url/index.xml', {}); if (res.statusCode == 200) { var body = parse(res.body); Map> results = {}; @@ -117,7 +117,8 @@ class FDroidRepo extends AppSource { throw NoReleasesError(); } var res = await sourceRequest( - '$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}'); + '$standardUrl${standardUrl.endsWith('/index.xml') ? '' : '/index.xml'}', + additionalSettings); if (res.statusCode == 200) { var body = parse(res.body); var foundApps = body.querySelectorAll('application').where((element) { diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index 26d71a2..7c0e19d 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -108,7 +108,8 @@ class GitHub extends AppSource { for (var path in possibleBuildGradleLocations) { try { var res = await sourceRequest( - '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path'); + '${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path', + additionalSettings); if (res.statusCode == 200) { try { var body = jsonDecode(res.body); @@ -149,9 +150,10 @@ class GitHub extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -160,8 +162,8 @@ class GitHub extends AppSource { @override Future?> getRequestHeaders( - {Map additionalSettings = const {}, - bool forAPKDownload = false}) async { + Map additionalSettings, + {bool forAPKDownload = false}) async { var token = await getTokenIfAny(additionalSettings); var headers = {}; if (token != null) { @@ -239,7 +241,8 @@ class GitHub extends AppSource { if (verifyLatestTag) { var temp = requestUrl.split('?'); Response res = await sourceRequest( - '${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}'); + '${temp[0]}/latest${temp.length > 1 ? '?${temp.sublist(1).join('?')}' : ''}', + additionalSettings); if (res.statusCode != 200) { if (onHttpErrorCode != null) { onHttpErrorCode(res); @@ -248,7 +251,7 @@ class GitHub extends AppSource { } latestRelease = jsonDecode(res.body); } - Response res = await sourceRequest(requestUrl); + Response res = await sourceRequest(requestUrl, additionalSettings); if (res.statusCode == 200) { var releases = jsonDecode(res.body) as List; if (latestRelease != null) { @@ -425,7 +428,7 @@ class GitHub extends AppSource { String query, String requestUrl, String rootProp, {Function(Response)? onHttpErrorCode, Map querySettings = const {}}) async { - Response res = await sourceRequest(requestUrl); + Response res = await sourceRequest(requestUrl, {}); if (res.statusCode == 200) { int minStarCount = querySettings['minStarCount'] != null ? int.parse(querySettings['minStarCount']) diff --git a/lib/app_sources/gitlab.dart b/lib/app_sources/gitlab.dart index a9ecac2..0b7776f 100644 --- a/lib/app_sources/gitlab.dart +++ b/lib/app_sources/gitlab.dart @@ -52,9 +52,10 @@ class GitLab extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -83,7 +84,7 @@ class GitLab extends AppSource { {Map querySettings = const {}}) async { var url = 'https://${hosts[0]}/api/v4/projects?search=${Uri.encodeQueryComponent(query)}'; - var res = await sourceRequest(url); + var res = await sourceRequest(url, {}); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } @@ -114,7 +115,8 @@ class GitLab extends AppSource { if (PAT != null) { var names = GitHub().getAppNames(standardUrl); Response res = await sourceRequest( - 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT'); + 'https://${hosts[0]}/api/v4/projects/${names.author}%2F${names.name}/releases?private_token=$PAT', + additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } @@ -149,7 +151,8 @@ class GitLab extends AppSource { releaseDate: releaseDate); }); } else { - Response res = await sourceRequest('$standardUrl/-/tags?format=atom'); + Response res = await sourceRequest( + '$standardUrl/-/tags?format=atom', additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index cd48bbc..1e8b1e9 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -141,7 +141,37 @@ class HTML extends AppSource { ], finalStepFormitems[0], ...commonFormItems, - ...finalStepFormitems.sublist(1) + ...finalStepFormitems.sublist(1), + [ + GeneratedFormSubForm( + 'requestHeader', + [ + [ + GeneratedFormTextField('requestHeader', + label: tr('requestHeader'), + additionalValidators: [ + (value) { + if ((value ?? 'empty:valid') + .split(':') + .map((e) => e.trim()) + .where((e) => e.isNotEmpty) + .length < + 2) { + return tr('invalidInput'); + } + return null; + } + ]) + ] + ], + label: tr('requestHeader'), + defaultValue: [ + { + 'requestHeader': + 'User-Agent: Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36' + } + ]) + ] ]; overrideVersionDetectionFormDefault('noVersionDetection', disableStandard: false, disableRelDate: true); @@ -149,12 +179,25 @@ class HTML extends AppSource { @override Future?> getRequestHeaders( - {Map additionalSettings = const {}, - bool forAPKDownload = false}) async { - return { - "User-Agent": - "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" - }; + Map additionalSettings, + {bool forAPKDownload = false}) async { + if (additionalSettings.isNotEmpty) { + if (additionalSettings['requestHeader']?.isNotEmpty != true) { + additionalSettings['requestHeader'] = []; + } + additionalSettings['requestHeader'] = additionalSettings['requestHeader'] + .where((l) => l['requestHeader'].isNotEmpty == true) + .toList(); + Map requestHeaders = {}; + for (int i = 0; i < (additionalSettings['requestHeader'].length); i++) { + var temp = + (additionalSettings['requestHeader'][i]['requestHeader'] as String) + .split(':'); + requestHeaders[temp[0].trim()] = temp.sublist(1).join(':').trim(); + } + return requestHeaders; + } + return null; } @override @@ -235,7 +278,8 @@ class HTML extends AppSource { .where((l) => l['customLinkFilterRegex'].isNotEmpty == true) .toList(); for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) { - var intLinks = await grabLinksCommon(await sourceRequest(currentUrl), + var intLinks = await grabLinksCommon( + await sourceRequest(currentUrl, additionalSettings), additionalSettings['intermediateLink'][i]); if (intLinks.isEmpty) { throw NoReleasesError(); @@ -245,7 +289,7 @@ class HTML extends AppSource { } var uri = Uri.parse(currentUrl); - Response res = await sourceRequest(currentUrl); + Response res = await sourceRequest(currentUrl, additionalSettings); var links = await grabLinksCommon(res, additionalSettings); if ((additionalSettings['apkFilterRegEx'] as String?)?.isNotEmpty == true) { diff --git a/lib/app_sources/huaweiappgallery.dart b/lib/app_sources/huaweiappgallery.dart index b4f7605..54ab2ec 100644 --- a/lib/app_sources/huaweiappgallery.dart +++ b/lib/app_sources/huaweiappgallery.dart @@ -13,9 +13,10 @@ class HuaweiAppGallery extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/app/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -25,8 +26,10 @@ class HuaweiAppGallery extends AppSource { getDlUrl(String standardUrl) => 'https://${hosts[0].replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}'; - requestAppdlRedirect(String dlUrl) async { - Response res = await sourceRequest(dlUrl, followRedirects: false); + requestAppdlRedirect( + String dlUrl, Map additionalSettings) async { + Response res = + await sourceRequest(dlUrl, additionalSettings, followRedirects: false); if (res.statusCode == 200 || res.statusCode == 302 || res.statusCode == 304) { @@ -53,7 +56,7 @@ class HuaweiAppGallery extends AppSource { Future tryInferringAppId(String standardUrl, {Map additionalSettings = const {}}) async { String dlUrl = getDlUrl(standardUrl); - Response res = await requestAppdlRedirect(dlUrl); + Response res = await requestAppdlRedirect(dlUrl, additionalSettings); return res.headers['location'] != null ? appIdFromRedirectDlUrl(res.headers['location']!) : null; @@ -65,7 +68,7 @@ class HuaweiAppGallery extends AppSource { Map additionalSettings, ) async { String dlUrl = getDlUrl(standardUrl); - Response res = await requestAppdlRedirect(dlUrl); + Response res = await requestAppdlRedirect(dlUrl, additionalSettings); if (res.headers['location'] == null) { throw NoReleasesError(); } diff --git a/lib/app_sources/izzyondroid.dart b/lib/app_sources/izzyondroid.dart index 75a9d59..ace8ed1 100644 --- a/lib/app_sources/izzyondroid.dart +++ b/lib/app_sources/izzyondroid.dart @@ -15,13 +15,15 @@ class IzzyOnDroid extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegExA = - RegExp('^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+'); - RegExpMatch? match = standardUrlRegExA.firstMatch(url.toLowerCase()); + RegExp standardUrlRegExA = RegExp( + '^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegExA.firstMatch(url); if (match == null) { RegExp standardUrlRegExB = RegExp( - '^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+'); - match = standardUrlRegExB.firstMatch(url.toLowerCase()); + '^https?://apt.${getSourceRegex(hosts)}/fdroid/index/apk/[^/]+', + caseSensitive: false); + match = standardUrlRegExB.firstMatch(url); } if (match == null) { throw InvalidURLError(name); @@ -43,7 +45,8 @@ class IzzyOnDroid extends AppSource { String? appId = await tryInferringAppId(standardUrl); return fd.getAPKUrlsFromFDroidPackagesAPIResponse( await sourceRequest( - 'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'), + 'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId', + additionalSettings), 'https://android.izzysoft.de/frepo/$appId', standardUrl, name, diff --git a/lib/app_sources/jenkins.dart b/lib/app_sources/jenkins.dart index d816cfc..8e817db 100644 --- a/lib/app_sources/jenkins.dart +++ b/lib/app_sources/jenkins.dart @@ -8,6 +8,7 @@ class Jenkins extends AppSource { Jenkins() { overrideVersionDetectionFormDefault('releaseDateAsVersion', disableStandard: true); + neverAutoSelect = true; } String trimJobUrl(String url) { @@ -29,8 +30,8 @@ class Jenkins extends AppSource { Map additionalSettings, ) async { standardUrl = trimJobUrl(standardUrl); - Response res = - await sourceRequest('$standardUrl/lastSuccessfulBuild/api/json'); + Response res = await sourceRequest( + '$standardUrl/lastSuccessfulBuild/api/json', additionalSettings); if (res.statusCode == 200) { var json = jsonDecode(res.body); var releaseDate = json['timestamp'] == null diff --git a/lib/app_sources/mullvad.dart b/lib/app_sources/mullvad.dart index ddba4f1..3b0439d 100644 --- a/lib/app_sources/mullvad.dart +++ b/lib/app_sources/mullvad.dart @@ -11,9 +11,10 @@ class Mullvad extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -29,7 +30,8 @@ class Mullvad extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = await sourceRequest('$standardUrl/en/download/android'); + Response res = await sourceRequest( + '$standardUrl/en/download/android', additionalSettings); if (res.statusCode == 200) { var versions = parse(res.body) .querySelectorAll('p') diff --git a/lib/app_sources/neutroncode.dart b/lib/app_sources/neutroncode.dart index 29c1d64..b0f2d93 100644 --- a/lib/app_sources/neutroncode.dart +++ b/lib/app_sources/neutroncode.dart @@ -11,8 +11,9 @@ class NeutronCode extends AppSource { @override String sourceSpecificStandardizeURL(String url) { RegExp standardUrlRegEx = RegExp( - '^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + '^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -79,7 +80,7 @@ class NeutronCode extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = await sourceRequest(standardUrl); + Response res = await sourceRequest(standardUrl, additionalSettings); if (res.statusCode == 200) { var http = parse(res.body); var name = http.querySelector('.pd-title')?.innerHtml; diff --git a/lib/app_sources/signal.dart b/lib/app_sources/signal.dart index 2e79f2d..8e91939 100644 --- a/lib/app_sources/signal.dart +++ b/lib/app_sources/signal.dart @@ -18,8 +18,8 @@ class Signal extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = - await sourceRequest('https://updates.${hosts[0]}/android/latest.json'); + Response res = await sourceRequest( + 'https://updates.${hosts[0]}/android/latest.json', additionalSettings); if (res.statusCode == 200) { var json = jsonDecode(res.body); String? apkUrl = json['url']; diff --git a/lib/app_sources/sourceforge.dart b/lib/app_sources/sourceforge.dart index af64065..52f6aca 100644 --- a/lib/app_sources/sourceforge.dart +++ b/lib/app_sources/sourceforge.dart @@ -10,16 +10,18 @@ class SourceForge extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegExB = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+'); - RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase()); + RegExp standardUrlRegExB = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/p/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegExB.firstMatch(url); if (match != null) { url = 'https://${Uri.parse(match.group(0)!).host}/projects/${url.substring(Uri.parse(match.group(0)!).host.length + '/projects/'.length + 1)}'; } - RegExp standardUrlRegExA = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/projects/[^/]+'); - match = standardUrlRegExA.firstMatch(url.toLowerCase()); + RegExp standardUrlRegExA = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/projects/[^/]+', + caseSensitive: false); + match = standardUrlRegExA.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -31,7 +33,8 @@ class SourceForge extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = await sourceRequest('$standardUrl/rss?path=/'); + Response res = + await sourceRequest('$standardUrl/rss?path=/', additionalSettings); if (res.statusCode == 200) { var parsedHtml = parse(res.body); var allDownloadLinks = diff --git a/lib/app_sources/sourcehut.dart b/lib/app_sources/sourcehut.dart index 3bae7db..bb19b96 100644 --- a/lib/app_sources/sourcehut.dart +++ b/lib/app_sources/sourcehut.dart @@ -20,9 +20,10 @@ class SourceHut extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -41,7 +42,8 @@ class SourceHut extends AppSource { String appName = standardUri.pathSegments.last; bool fallbackToOlderReleases = additionalSettings['fallbackToOlderReleases'] == true; - Response res = await sourceRequest('$standardUrl/refs/rss.xml'); + Response res = + await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings); if (res.statusCode == 200) { var parsedHtml = parse(res.body); List apkDetailsList = []; @@ -70,7 +72,7 @@ class SourceHut extends AppSource { } catch (e) { // ignore } - var res2 = await sourceRequest(releasePage); + var res2 = await sourceRequest(releasePage, additionalSettings); List> apkUrls = []; if (res2.statusCode == 200) { apkUrls = getApkUrlsFromUrls(parse(res2.body) diff --git a/lib/app_sources/steammobile.dart b/lib/app_sources/steammobile.dart index 9185518..c70a6f7 100644 --- a/lib/app_sources/steammobile.dart +++ b/lib/app_sources/steammobile.dart @@ -29,7 +29,8 @@ class SteamMobile extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = await sourceRequest('https://${hosts[0]}/mobile'); + Response res = + await sourceRequest('https://${hosts[0]}/mobile', additionalSettings); if (res.statusCode == 200) { var apkNamePrefix = additionalSettings['app'] as String?; if (apkNamePrefix == null) { diff --git a/lib/app_sources/telegramapp.dart b/lib/app_sources/telegramapp.dart index b514af4..651d00f 100644 --- a/lib/app_sources/telegramapp.dart +++ b/lib/app_sources/telegramapp.dart @@ -20,7 +20,8 @@ class TelegramApp extends AppSource { String standardUrl, Map additionalSettings, ) async { - Response res = await sourceRequest('https://t.me/s/TAndroidAPK'); + Response res = + await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings); if (res.statusCode == 200) { var http = parse(res.body); var messages = diff --git a/lib/app_sources/uptodown.dart b/lib/app_sources/uptodown.dart index 0cc6089..fe3d589 100644 --- a/lib/app_sources/uptodown.dart +++ b/lib/app_sources/uptodown.dart @@ -13,9 +13,10 @@ class Uptodown extends AppSource { @override String sourceSpecificStandardizeURL(String url) { - RegExp standardUrlRegEx = - RegExp('^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}'); - RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + RegExp standardUrlRegEx = RegExp( + '^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}', + caseSensitive: false); + RegExpMatch? match = standardUrlRegEx.firstMatch(url); if (match == null) { throw InvalidURLError(name); } @@ -25,11 +26,13 @@ class Uptodown extends AppSource { @override Future tryInferringAppId(String standardUrl, {Map additionalSettings = const {}}) async { - return (await getAppDetailsFromPage(standardUrl))['appId']; + return (await getAppDetailsFromPage( + standardUrl, additionalSettings))['appId']; } - Future> getAppDetailsFromPage(String standardUrl) async { - var res = await sourceRequest(standardUrl); + Future> getAppDetailsFromPage( + String standardUrl, Map additionalSettings) async { + var res = await sourceRequest(standardUrl, additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } @@ -57,7 +60,8 @@ class Uptodown extends AppSource { String standardUrl, Map additionalSettings, ) async { - var appDetails = await getAppDetailsFromPage(standardUrl); + var appDetails = + await getAppDetailsFromPage(standardUrl, additionalSettings); var version = appDetails['version']; var apkUrl = appDetails['apkUrl']; var appId = appDetails['appId']; @@ -83,9 +87,9 @@ class Uptodown extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, String standardUrl) async { - var res = await sourceRequest(apkUrl); + Future apkUrlPrefetchModifier(String apkUrl, String standardUrl, + Map additionalSettings) async { + var res = await sourceRequest(apkUrl, additionalSettings); if (res.statusCode != 200) { throw getObtainiumHttpError(res); } diff --git a/lib/app_sources/vlc.dart b/lib/app_sources/vlc.dart index 40a42ec..7d16870 100644 --- a/lib/app_sources/vlc.dart +++ b/lib/app_sources/vlc.dart @@ -1,7 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; import 'package:http/http.dart'; -import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -13,19 +12,22 @@ class VLC extends AppSource { @override Future?> getRequestHeaders( - {Map additionalSettings = const {}, - bool forAPKDownload = false}) => - HTML().getRequestHeaders( - additionalSettings: additionalSettings, - forAPKDownload: forAPKDownload); + Map additionalSettings, + {bool forAPKDownload = false}) async { + return { + "User-Agent": + "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" + }; + } @override String sourceSpecificStandardizeURL(String url) { return 'https://${hosts[0]}'; } - Future getLatestVersion(String standardUrl) async { - Response res = await sourceRequest(dwUrlBase); + Future getLatestVersion( + String standardUrl, Map additionalSettings) async { + Response res = await sourceRequest(dwUrlBase, additionalSettings); if (res.statusCode == 200) { var dwLinks = parse(res.body) .querySelectorAll('a') @@ -77,9 +79,9 @@ class VLC extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, String standardUrl) async { - Response res = await sourceRequest(apkUrl); + Future apkUrlPrefetchModifier(String apkUrl, String standardUrl, + Map additionalSettings) async { + Response res = await sourceRequest(apkUrl, additionalSettings); if (res.statusCode == 200) { String? apkUrl = parse(res.body).querySelector('#alt_link')?.attributes['href']; diff --git a/lib/app_sources/whatsapp.dart b/lib/app_sources/whatsapp.dart index ed0fdb1..e9ad7c5 100644 --- a/lib/app_sources/whatsapp.dart +++ b/lib/app_sources/whatsapp.dart @@ -16,9 +16,10 @@ class WhatsApp extends AppSource { } @override - Future apkUrlPrefetchModifier( - String apkUrl, String standardUrl) async { - Response res = await sourceRequest('$standardUrl/android'); + Future apkUrlPrefetchModifier(String apkUrl, String standardUrl, + Map additionalSettings) async { + Response res = + await sourceRequest('$standardUrl/android', additionalSettings); if (res.statusCode == 200) { var targetLinks = parse(res.body) .querySelectorAll('a') @@ -42,8 +43,8 @@ class WhatsApp extends AppSource { ) async { // This is a CDN link that is consistent per version // But it has query params that change constantly - Uri apkUri = - Uri.parse(await apkUrlPrefetchModifier(standardUrl, standardUrl)); + Uri apkUri = Uri.parse(await apkUrlPrefetchModifier( + standardUrl, standardUrl, additionalSettings)); var unusableApkUrl = '${apkUri.origin}/${apkUri.path}'; // So we use the param-less URL is a pseudo-version to add the app and check for updates // See #357 for why we can't scrape the version number directly diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 02ecc29..5ac21f8 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -510,9 +510,10 @@ class _GeneratedFormState extends State { ]); } else if (widget.items[r][e] is GeneratedFormSubForm) { List subformColumn = []; + var formItems = (widget.items[r][e] as GeneratedFormSubForm).items; + var compact = formItems.length == 1 && formItems[0].length == 1; for (int i = 0; i < values[fieldKey].length; i++) { - var items = (widget.items[r][e] as GeneratedFormSubForm) - .items + var items = formItems .map((x) => x.map((y) { y.defaultValue = values[fieldKey]?[i]?[y.key]; return y; @@ -525,14 +526,15 @@ class _GeneratedFormState extends State { subformColumn.add(Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - const Divider(), - const SizedBox( - height: 16, - ), - Text( - '${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})', - style: const TextStyle(fontWeight: FontWeight.bold), - ), + if (!compact) + const SizedBox( + height: 16, + ), + if (!compact) + Text( + '${(widget.items[r][e] as GeneratedFormSubForm).label} (${i + 1})', + style: const TextStyle(fontWeight: FontWeight.bold), + ), GeneratedForm( key: internalFormKey, items: items, @@ -567,13 +569,12 @@ class _GeneratedFormState extends State { Icons.delete_outline_rounded, )) ], - ), + ) ], )); } subformColumn.add(Padding( - padding: EdgeInsets.only( - bottom: values[fieldKey].length > 0 ? 24 : 0, top: 8), + padding: const EdgeInsets.only(bottom: 0, top: 8), child: Row( children: [ Expanded( @@ -591,9 +592,6 @@ class _GeneratedFormState extends State { ], ), )); - if (values[fieldKey].length > 0) { - subformColumn.add(const Divider()); - } formInputs[r][e] = Column(children: subformColumn); } } diff --git a/lib/main.dart b/lib/main.dart index c0d38f6..df577ff 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,12 +19,10 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.15.8'; +const String currentVersion = '0.15.9'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES -const int bgUpdateCheckAlarmId = 666; - List> supportedLocales = const [ MapEntry(Locale('en'), 'English'), MapEntry(Locale('zh'), '简体中文'), diff --git a/lib/mass_app_sources/githubstars.dart b/lib/mass_app_sources/githubstars.dart index b32cc77..4a7ac61 100644 --- a/lib/mass_app_sources/githubstars.dart +++ b/lib/mass_app_sources/githubstars.dart @@ -18,7 +18,7 @@ class GitHubStars implements MassAppUrlSource { Response res = await get( Uri.parse( 'https://api.github.com/users/$username/starred?per_page=100&page=$page'), - headers: await GitHub().getRequestHeaders()); + headers: await GitHub().getRequestHeaders({})); if (res.statusCode == 200) { Map> urlsWithDescriptions = {}; for (var e in (jsonDecode(res.body) as List)) { diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 3a53819..071947d 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -326,13 +326,15 @@ class AppsProvider with ChangeNotifier { AppSource source = SourceProvider() .getSource(app.url, overrideSource: app.overrideSource); String downloadUrl = await source.apkUrlPrefetchModifier( - app.apkUrls[app.preferredApkIndex].value, app.url); + app.apkUrls[app.preferredApkIndex].value, + app.url, + app.additionalSettings); var notif = DownloadNotification(app.finalName, 100); notificationsProvider?.cancel(notif.id); int? prevProg; var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}'; - var headers = await source.getRequestHeaders( - additionalSettings: app.additionalSettings, forAPKDownload: true); + var headers = await source.getRequestHeaders(app.additionalSettings, + forAPKDownload: true); var downloadedFile = await downloadFileWithRetry( downloadUrl, fileNameNoExt, headers: headers, (double? progress) { @@ -796,13 +798,17 @@ class AppsProvider with ChangeNotifier { SourceProvider() .getSource(app.app.url, overrideSource: app.app.overrideSource) .naiveStandardVersionDetection; + String? realInstalledVersion = + app.app.additionalSettings['useVersionCodeAsOSVersion'] == true + ? app.installedInfo?.versionCode.toString() + : app.installedInfo?.versionName; return app.app.additionalSettings['trackOnly'] != true && app.app.additionalSettings['versionDetection'] != 'releaseDateAsVersion' && - app.installedInfo?.versionName != null && + realInstalledVersion != null && app.app.installedVersion != null && - (reconcileVersionDifferences(app.installedInfo!.versionName!, - app.app.installedVersion!) != + (reconcileVersionDifferences( + realInstalledVersion, app.app.installedVersion!) != null || naiveStandardVersionDetection); } @@ -821,30 +827,33 @@ class AppsProvider with ChangeNotifier { SourceProvider() .getSource(app.url, overrideSource: app.overrideSource) .naiveStandardVersionDetection; + String? realInstalledVersion = + app.additionalSettings['useVersionCodeAsOSVersion'] == true + ? installedInfo?.versionCode.toString() + : installedInfo?.versionName; // FIRST, COMPARE THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE ONE IS NULL if (installedInfo == null && app.installedVersion != null && !trackOnly) { // App says it's installed but isn't really (and isn't track only) - set to not installed app.installedVersion = null; modded = true; - } else if (installedInfo?.versionName != null && - app.installedVersion == null) { - // App says it's not installed but really is - set to installed and use real package versionName - app.installedVersion = installedInfo!.versionName; + } else if (realInstalledVersion != null && app.installedVersion == null) { + // App says it's not installed but really is - set to installed and use real package versionName (or versionCode if chosen) + app.installedVersion = realInstalledVersion; modded = true; } // SECOND, RECONCILE DIFFERENCES BETWEEN THE APP'S REPORTED AND REAL INSTALLED VERSIONS, WHERE NEITHER IS NULL - if (installedInfo?.versionName != null && - installedInfo!.versionName != app.installedVersion && + if (realInstalledVersion != null && + realInstalledVersion != app.installedVersion && versionDetectionIsStandard) { // App's reported version and real version don't match (and it uses standard version detection) // If they share a standard format (and are still different under it), update the reported version accordingly var correctedInstalledVersion = reconcileVersionDifferences( - installedInfo.versionName!, app.installedVersion!); + realInstalledVersion, app.installedVersion!); if (correctedInstalledVersion?.key == false) { app.installedVersion = correctedInstalledVersion!.value; modded = true; } else if (naiveStandardVersionDetection) { - app.installedVersion = installedInfo.versionName; + app.installedVersion = realInstalledVersion; modded = true; } } @@ -1289,8 +1298,11 @@ class AppsProvider with ChangeNotifier { await Future.delayed(const Duration(microseconds: 1)); } for (App a in importedApps) { + var installedInfo = await getInstalledInfo(a.id, printErr: false); a.installedVersion = - (await getInstalledInfo(a.id, printErr: false))?.versionName; + a.additionalSettings['useVersionCodeAsOSVersion'] == true + ? installedInfo?.versionCode.toString() + : installedInfo?.versionName; } await saveApps(importedApps, onlyIfExists: false); notifyListeners(); diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index f60da70..57ea4dd 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -19,7 +19,6 @@ import 'package:obtainium/app_sources/huaweiappgallery.dart'; import 'package:obtainium/app_sources/izzyondroid.dart'; import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/jenkins.dart'; -import 'package:obtainium/app_sources/mullvad.dart'; import 'package:obtainium/app_sources/neutroncode.dart'; import 'package:obtainium/app_sources/signal.dart'; import 'package:obtainium/app_sources/sourceforge.dart'; @@ -417,8 +416,8 @@ abstract class AppSource { } Future?> getRequestHeaders( - {Map additionalSettings = const {}, - bool forAPKDownload = false}) async { + Map additionalSettings, + {bool forAPKDownload = false}) async { return null; } @@ -426,12 +425,10 @@ abstract class AppSource { return app; } - Future sourceRequest(String url, - {bool followRedirects = true, - Map additionalSettings = - const {}}) async { - var requestHeaders = - await getRequestHeaders(additionalSettings: additionalSettings); + Future sourceRequest( + String url, Map additionalSettings, + {bool followRedirects = true}) async { + var requestHeaders = await getRequestHeaders(additionalSettings); if (requestHeaders != null || followRedirects == false) { var req = Request('GET', Uri.parse(url)); req.followRedirects = followRedirects; @@ -488,6 +485,10 @@ abstract class AppSource { label: tr('versionDetection'), defaultValue: 'standardVersionDetection') ], + [ + GeneratedFormSwitch('useVersionCodeAsOSVersion', + label: tr('useVersionCodeAsOSVersion'), defaultValue: false) + ], [ GeneratedFormTextField('apkFilterRegEx', label: tr('filterAPKsByRegEx'), @@ -548,8 +549,8 @@ abstract class AppSource { return null; } - Future apkUrlPrefetchModifier( - String apkUrl, String standardUrl) async { + Future apkUrlPrefetchModifier(String apkUrl, String standardUrl, + Map additionalSettings) async { return apkUrl; } @@ -680,7 +681,6 @@ class SourceProvider { APKMirror(), HuaweiAppGallery(), Jenkins(), - Mullvad(), Signal(), VLC(), WhatsApp(), diff --git a/pubspec.lock b/pubspec.lock index 894a4a9..8743138 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -22,10 +22,10 @@ packages: dependency: "direct main" description: name: android_package_manager - sha256: b873fe5856f7c442aca9751dac05d117285be9e4de08eb15d1ffb811fd1b688d + sha256: e52ca607b9f19f95d5dae4211ed8fa93e67093f22ac570db47489c5bca512940 url: "https://pub.dev" source: hosted - version: "0.6.0" + version: "0.7.0" animations: dependency: "direct main" description: @@ -70,10 +70,10 @@ packages: dependency: "direct main" description: name: background_fetch - sha256: f70b28a0f7a3156195e9742229696f004ea3bf10f74039b7bf4c78a74fbda8a4 + sha256: "34550cf9b383e5a1844e7d22119aa500508c7df9421fa967c9fb4430d6cb2878" url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" boolean_selector: dependency: transitive description: @@ -258,6 +258,14 @@ packages: url: "https://pub.dev" source: hosted version: "6.1.1" + fixnum: + dependency: transitive + description: + name: fixnum + sha256: "25517a4deb0c03aa0f32fd12db525856438902d9c16536311e76cdc57b31d7d1" + url: "https://pub.dev" + source: hosted + version: "1.1.0" flutter: dependency: "direct main" description: flutter @@ -506,10 +514,10 @@ packages: dependency: "direct main" description: name: path_provider - sha256: a1aa8aaa2542a6bc57e381f132af822420216c80d4781f7aa085ca3229208aaa + sha256: b27217933eeeba8ff24845c34003b003b2b22151de3c908d0e679e8fe1aa078b url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_android: dependency: transitive description: @@ -538,10 +546,10 @@ packages: dependency: transitive description: name: path_provider_platform_interface - sha256: "94b1e0dd80970c1ce43d5d4e050a9918fce4f4a775e6142424c30a29a363265c" + sha256: "88f5779f72ba699763fa3a3b06aa4bf6de76c8e5de842cf6f29e2e06476c2334" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" path_provider_windows: dependency: transitive description: @@ -690,10 +698,10 @@ packages: dependency: transitive description: name: shared_preferences_platform_interface - sha256: d4ec5fc9ebb2f2e056c617112aa75dcf92fc2e4faaf2ae999caa297473f75d8a + sha256: "22e2ecac9419b4246d7c22bfbbda589e3acf5c0351137d87dd2939d984d37c3b" url: "https://pub.dev" source: hosted - version: "2.3.1" + version: "2.3.2" shared_preferences_web: dependency: transitive description: @@ -823,26 +831,26 @@ packages: dependency: "direct main" description: name: url_launcher - sha256: e9aa5ea75c84cf46b3db4eea212523591211c3cf2e13099ee4ec147f54201c86 + sha256: d25bb0ca00432a5e1ee40e69c36c85863addf7cc45e433769d61bed3fe81fd96 url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.3" url_launcher_android: dependency: transitive description: name: url_launcher_android - sha256: c0766a55ab42cefaa728cabc951e82919ab41a3a4fee0aaa96176ca82da8cc51 + sha256: "507dc655b1d9cb5ebc756032eb785f114e415f91557b73bf60b7e201dfedeb2f" url: "https://pub.dev" source: hosted - version: "6.2.1" + version: "6.2.2" url_launcher_ios: dependency: transitive description: name: url_launcher_ios - sha256: "46b81e3109cbb2d6b81702ad3077540789a3e74e22795eb9f0b7d494dbaa72ea" + sha256: cdb7b6da34483f9b2c9f8b2b29bc468fa7271d92e2021607ca0c4d3bcb04cdd4 url: "https://pub.dev" source: hosted - version: "6.2.2" + version: "6.2.3" url_launcher_linux: dependency: transitive description: @@ -863,10 +871,10 @@ packages: dependency: transitive description: name: url_launcher_platform_interface - sha256: "4aca1e060978e19b2998ee28503f40b5ba6226819c2b5e3e4d1821e8ccd92198" + sha256: a932c3a8082e118f80a475ce692fde89dc20fddb24c57360b96bc56f7035de1f url: "https://pub.dev" source: hosted - version: "2.3.0" + version: "2.3.1" url_launcher_web: dependency: transitive description: @@ -887,10 +895,10 @@ packages: dependency: transitive description: name: uuid - sha256: "22c94e5ad1e75f9934b766b53c742572ee2677c56bc871d850a57dad0f82127f" + sha256: cd210a09f7c18cbe5a02511718e0334de6559871052c90a90c0cca46a4aa81c8 url: "https://pub.dev" source: hosted - version: "4.2.2" + version: "4.3.3" vector_math: dependency: transitive description: @@ -911,10 +919,10 @@ packages: dependency: "direct main" description: name: webview_flutter - sha256: "60e23976834e995c404c0b21d3b9db37ecd77d3303ef74f8b8d7a7b19947fc04" + sha256: "71e1bfaef41016c8d5954291df5e9f8c6172f1f6ff3af01b5656456ddb11f94c" url: "https://pub.dev" source: hosted - version: "4.4.3" + version: "4.4.4" webview_flutter_android: dependency: transitive description: @@ -927,10 +935,10 @@ packages: dependency: transitive description: name: webview_flutter_platform_interface - sha256: dbe745ee459a16b6fec296f7565a8ef430d0d681001d8ae521898b9361854943 + sha256: "80b40ae4fb959957eef9fa8970b6c9accda9f49fc45c2b75154696a8e8996cfe" url: "https://pub.dev" source: hosted - version: "2.9.0" + version: "2.9.1" webview_flutter_wkwebview: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 31fd9c3..287afec 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.15.8+244 # When changing this, update the tag in main() accordingly +version: 0.15.9+245 # When changing this, update the tag in main() accordingly environment: sdk: '>=3.0.0 <4.0.0' @@ -55,7 +55,7 @@ dependencies: git: url: https://github.com/ImranR98/android_package_installer ref: main - android_package_manager: ^0.6.0 + android_package_manager: ^0.7.0 share_plus: ^7.0.0 sqflite: ^2.2.0+3 easy_localization: ^3.0.1