From 3345b26fa9d580bb47b3e2f52d605c63326171cf Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 18:17:32 +0200 Subject: [PATCH 01/10] rewrite apkpure source to use api instead of web scraping --- lib/app_sources/apkpure.dart | 239 ++++++++++++++-------------------- lib/app_sources/uptodown.dart | 16 ++- 2 files changed, 114 insertions(+), 141 deletions(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 37742a2..db6c929 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -1,24 +1,18 @@ +import 'dart:convert'; + 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'; -parseDateTimeMMMddCommayyyy(String? dateString) { - DateTime? releaseDate; - try { - releaseDate = dateString != null - ? DateFormat('MMM dd, yyyy').parse(dateString) - : null; - releaseDate = dateString != null && releaseDate == null - ? DateFormat('MMMM dd, yyyy').parse(dateString) - : releaseDate; - } catch (err) { - // ignore +extension Unique on List { + List unique([Id Function(E element)? id, bool inplace = true]) { + final ids = Set(); + var list = inplace ? this : List.from(this); + list.retainWhere((x) => ids.add(id != null ? id(x) : x as Id)); + return list; } - return releaseDate; } class APKPure extends AppSource { @@ -35,6 +29,10 @@ class APKPure extends AppSource { [ GeneratedFormSwitch('stayOneVersionBehind', label: tr('stayOneVersionBehind'), defaultValue: false) + ], + [ + GeneratedFormSwitch('selectNewestApk', + label: tr('selectNewestApk'), defaultValue: true) ] ]; } @@ -65,109 +63,65 @@ class APKPure extends AppSource { return Uri.parse(standardUrl).pathSegments.last; } - getDetailsForVersionLink( - String standardUrl, - String appId, - String host, - List supportedArchs, - String link, + getDetailsForVersion(List versionVariants, List supportedArchs, Map 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 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 .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.first; - 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) { - var link = - html.querySelector("a.download-start-btn")?.attributes['href']; - RegExp downloadLinkRegEx = RegExp( - r'^https:\/\/d\.[^/]+\/b\/([^/]+)\/[^/?]+\?versionCode=([0-9]+)$', - caseSensitive: false); - RegExpMatch? match = downloadLinkRegEx.firstMatch(link ?? ''); - if (match == null) { - throw NoAPKError(); - } - String type = match.group(1)!; - String versionCode = match.group(2)!; - apkUrls = [ - MapEntry('$appId-$versionCode-.${type.toLowerCase()}', - 'https://d.${hosts.contains(host) ? 'cdnpure.com' : host}/b/$type/$appId?versionCode=$versionCode') - ]; - } - String version = Uri.parse(link).pathSegments.last; - String? author; - try { - author = html - .querySelector('span.info-sdk') - ?.text - .trim() - .substring(version.length + 4) ?? - Uri.parse(standardUrl).pathSegments.reversed.last; - } catch (e) { - author = html.querySelector('span.info-sdk')?.text.trim() ?? - 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("
", " \n"); - return APKDetails(version, apkUrls, AppNames(author, appName), - releaseDate: topReleaseDate, changeLog: changeLog); + var apkUrls = versionVariants + .map((e) { + String appId = e['package_name']; + String versionCode = e['version_code']; + + List architectures = e['native_code']?.cast(); + String architectureString = architectures.join(','); + if (additionalSettings['autoApkFilterByArch'] == true && + architectures.isNotEmpty && + architectures.where((a) => supportedArchs.contains(a)).isEmpty) { + return null; + } + + String type = e['asset']['type']; + if (e['is_a_p_ks'] == true) { + type = 'APK'; + } + + return MapEntry( + '$appId-$versionCode-$architectureString.${type.toLowerCase()}', + 'https://d.cdnpure.com/b/$type/$appId?versionCode=$versionCode&nc=$architectureString'); + }) + .nonNulls + .toList() + .unique((e) => e.key); + + // 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['selectNewestApk'] == true) { + apkUrls = [apkUrls.first]; + } + + return APKDetails(version, apkUrls, AppNames(author, appName), + releaseDate: releaseDate, changeLog: changeLog); + } + + @override + Future?> getRequestHeaders( + Map additionalSettings, + {bool forAPKDownload = false}) async { + if (forAPKDownload) { + return null; } else { - throw getObtainiumHttpError(res); + return { + "Ual-Access-Businessid": "projecta", + "Ual-Access-ProjectA": + '{"device_info":{"os_ver":"${((await DeviceInfoPlugin().androidInfo).version.sdkInt)}"}}', + }; } } @@ -177,41 +131,46 @@ class APKPure extends AppSource { Map additionalSettings, ) async { String appId = (await tryInferringAppId(standardUrl))!; - String host = Uri.parse(standardUrl).host; - var res0 = await sourceRequest('$standardUrl/versions', additionalSettings); - var decodedStandardUrl = standardUrl; - try { - decodedStandardUrl = Uri.decodeFull(decodedStandardUrl); - } catch (e) { - // + List 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); } - var versionLinks = await grabLinksCommon(res0, { - 'skipSort': true, - 'customLinkFilterRegex': '$decodedStandardUrl/download/[^/]+\$' - }); + List> apks = + jsonDecode(res.body)['version_list'].cast>(); - var supportedArchs = (await DeviceInfoPlugin().androidInfo).supportedAbis; + // group by version + List versions = apks + .fold>>>({}, + (Map>> val, + Map element) { + String v = element['version_name']; + if (!val.containsKey(v)) { + val[v] = []; + } + val[v]?.add(element); + return val; + }) + .values + .toList(); - if (additionalSettings['autoApkFilterByArch'] != true) { - // No need to request multiple versions when we're not going to filter them (always pick the top one) - versionLinks = versionLinks.sublist(0, 1); - } - if (versionLinks.isEmpty) { - throw NoReleasesError(); - } - - for (var i = 0; i < versionLinks.length; i++) { - var link = versionLinks[i]; + for (var i = 0; i < versions.length; i++) { + var v = versions[i]; try { if (i == 0 && additionalSettings['stayOneVersionBehind'] == true) { throw NoReleasesError(); } - return await getDetailsForVersionLink(standardUrl, appId, host, - supportedArchs, link.key, additionalSettings); + return await getDetailsForVersion( + v, supportedArchs, additionalSettings); } catch (e) { if (additionalSettings['fallbackToOlderReleases'] != true || - i == versionLinks.length - 1) { + i == versions.length - 1) { rethrow; } } diff --git a/lib/app_sources/uptodown.dart b/lib/app_sources/uptodown.dart index fa447b8..134b64c 100644 --- a/lib/app_sources/uptodown.dart +++ b/lib/app_sources/uptodown.dart @@ -1,9 +1,23 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:html/parser.dart'; -import 'package:obtainium/app_sources/apkpure.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; +parseDateTimeMMMddCommayyyy(String? dateString) { + DateTime? releaseDate; + try { + releaseDate = dateString != null + ? DateFormat('MMM dd, yyyy').parse(dateString) + : null; + releaseDate = dateString != null && releaseDate == null + ? DateFormat('MMMM dd, yyyy').parse(dateString) + : releaseDate; + } catch (err) { + // ignore + } + return releaseDate; +} + class Uptodown extends AppSource { Uptodown() { hosts = ['uptodown.com']; From ae2dad01ff938864e650fa4c8b356ac3d85b6d58 Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 18:29:45 +0200 Subject: [PATCH 02/10] rename newst apk option --- lib/app_sources/apkpure.dart | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index db6c929..cab50d7 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -31,8 +31,8 @@ class APKPure extends AppSource { label: tr('stayOneVersionBehind'), defaultValue: false) ], [ - GeneratedFormSwitch('selectNewestApk', - label: tr('selectNewestApk'), defaultValue: true) + GeneratedFormSwitch('useFirstApkOfVersion', + label: tr('useFirstApkOfVersion'), defaultValue: true) ] ]; } @@ -102,7 +102,7 @@ class APKPure extends AppSource { changeLog = null; } - if (additionalSettings['selectNewestApk'] == true) { + if (additionalSettings['useFirstApkOfVersion'] == true) { apkUrls = [apkUrls.first]; } From d8c9cd65791b27f3bc3593d56a132c730c880cff Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 18:34:15 +0200 Subject: [PATCH 03/10] apkpure: detect 'universal' and 'unlimited' architectures --- lib/app_sources/apkpure.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index cab50d7..1c66fb0 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -72,6 +72,10 @@ class APKPure extends AppSource { List architectures = e['native_code']?.cast(); 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) { From 6e0819b0a7b664cab191ecc8957c7bf56c17916a Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 18:39:18 +0200 Subject: [PATCH 04/10] apkpure: only use nc query param if not empty --- lib/app_sources/apkpure.dart | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 1c66fb0..4a57552 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -87,9 +87,18 @@ class APKPure extends AppSource { type = 'APK'; } + var downloadUri = Uri.parse("https://d.cdnpure.com/b/$type/$appId"); + var queryParameters = { + "versionCode": versionCode, + }; + if (architectureString.isNotEmpty) { + queryParameters["nc"] = architectureString; + } + downloadUri = downloadUri.replace(queryParameters: queryParameters); + return MapEntry( '$appId-$versionCode-$architectureString.${type.toLowerCase()}', - 'https://d.cdnpure.com/b/$type/$appId?versionCode=$versionCode&nc=$architectureString'); + downloadUri.toString()); }) .nonNulls .toList() From ee9b0e710ca8e8b985e0628a03c174291f9954bb Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 18:42:55 +0200 Subject: [PATCH 05/10] apkpure: properly throw NoAPKError if none found --- lib/app_sources/apkpure.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 4a57552..8c1bb7a 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -104,6 +104,10 @@ class APKPure extends AppSource { .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']; From 369127806f226e754cb6ba13943697db947d0c4e Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 19:00:54 +0200 Subject: [PATCH 06/10] apkpure: throw NoReleasesError when version list is empty --- lib/app_sources/apkpure.dart | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 8c1bb7a..e4b0bc9 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -177,6 +177,10 @@ class APKPure extends AppSource { .values .toList(); + if (versions.isEmpty) { + throw NoReleasesError(); + } + for (var i = 0; i < versions.length; i++) { var v = versions[i]; try { From 5b6299496ff5eecba1cc440a7cab226033e2f03c Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 19:14:11 +0200 Subject: [PATCH 07/10] apkpure: determine APK/XAPK only by asset type, ignore APKs apks install resulted in "incompatible" error with some apps on testing --- lib/app_sources/apkpure.dart | 3 --- 1 file changed, 3 deletions(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index e4b0bc9..9ac3171 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -83,9 +83,6 @@ class APKPure extends AppSource { } String type = e['asset']['type']; - if (e['is_a_p_ks'] == true) { - type = 'APK'; - } var downloadUri = Uri.parse("https://d.cdnpure.com/b/$type/$appId"); var queryParameters = { From 47a6e0dc7c613a8de459e777efcbd08141cddf20 Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 19:24:33 +0200 Subject: [PATCH 08/10] apkpure: improve typing --- lib/app_sources/apkpure.dart | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 9ac3171..836c8c2 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -63,7 +63,9 @@ class APKPure extends AppSource { return Uri.parse(standardUrl).pathSegments.last; } - getDetailsForVersion(List versionVariants, List supportedArchs, + getDetailsForVersion( + List> versionVariants, + List supportedArchs, Map additionalSettings) async { var apkUrls = versionVariants .map((e) { @@ -160,7 +162,7 @@ class APKPure extends AppSource { jsonDecode(res.body)['version_list'].cast>(); // group by version - List versions = apks + List>> versions = apks .fold>>>({}, (Map>> val, Map element) { From b2d6752b809d0d10e4521eabf30360b0c47267a1 Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 21:10:18 +0200 Subject: [PATCH 09/10] apkpure: use provided asset download links --- lib/app_sources/apkpure.dart | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 836c8c2..e2c5a9c 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -85,19 +85,11 @@ class APKPure extends AppSource { } String type = e['asset']['type']; - - var downloadUri = Uri.parse("https://d.cdnpure.com/b/$type/$appId"); - var queryParameters = { - "versionCode": versionCode, - }; - if (architectureString.isNotEmpty) { - queryParameters["nc"] = architectureString; - } - downloadUri = downloadUri.replace(queryParameters: queryParameters); + String downloadUri = e['asset']['url']; return MapEntry( '$appId-$versionCode-$architectureString.${type.toLowerCase()}', - downloadUri.toString()); + downloadUri); }) .nonNulls .toList() From 6d28d15d4f3415105d9ba5bf984b5cc23d423fcb Mon Sep 17 00:00:00 2001 From: bernikr Date: Mon, 19 May 2025 21:47:04 +0200 Subject: [PATCH 10/10] add translations --- assets/translations/bs.json | 2 ++ assets/translations/ca.json | 2 ++ assets/translations/cs.json | 2 ++ assets/translations/da.json | 2 ++ assets/translations/de.json | 2 ++ assets/translations/en-EO.json | 2 ++ assets/translations/en.json | 1 + assets/translations/es.json | 2 ++ assets/translations/fa.json | 2 ++ assets/translations/fr.json | 2 ++ assets/translations/hu.json | 2 ++ assets/translations/id.json | 2 ++ assets/translations/it.json | 2 ++ assets/translations/ja.json | 2 ++ assets/translations/ko.json | 2 ++ assets/translations/nl.json | 2 ++ assets/translations/pl.json | 2 ++ assets/translations/pt-BR.json | 2 ++ assets/translations/pt.json | 2 ++ assets/translations/ru.json | 2 ++ assets/translations/sv.json | 2 ++ assets/translations/tr.json | 2 ++ assets/translations/uk.json | 2 ++ assets/translations/vi.json | 2 ++ assets/translations/zh-Hant-TW.json | 2 ++ assets/translations/zh.json | 1 + 26 files changed, 50 insertions(+) diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 93115fe..9ccd50c 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Crowdsourced App Configurations", "allowInsecure": "Allow insecure HTTP requests", "stayOneVersionBehind": "Stay one version behind latest", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Refresh app details before download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Name", "smartname": "Name (Smart)", "sortMethod": "Sort Method", diff --git a/assets/translations/ca.json b/assets/translations/ca.json index bc54638..5b91004 100644 --- a/assets/translations/ca.json +++ b/assets/translations/ca.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Configuració de les aplicacions crowdsourcing", "allowInsecure": "Permet les sol·licituds HTTP insegures", "stayOneVersionBehind": "Roman a la versió anterior a l'última", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Actualitza les dades de l'aplicació abans de descarregar-la", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nom", "smartname": "Nom (smart)", "sortMethod": "Mètode d'ordenació", diff --git a/assets/translations/cs.json b/assets/translations/cs.json index 13acf9b..3da39f2 100644 --- a/assets/translations/cs.json +++ b/assets/translations/cs.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Konfigurace aplikací s využitím crowdsourcingu", "allowInsecure": "Povolení nezabezpečených požadavků HTTP", "stayOneVersionBehind": "Zůstaňte o jednu verzi pozadu za nejnovější", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Obnovení údajů o aplikaci před stažením", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Název", "smartname": "Název (Smart)", "sortMethod": "Metoda třídění", diff --git a/assets/translations/da.json b/assets/translations/da.json index fb5e92e..1b788a7 100644 --- a/assets/translations/da.json +++ b/assets/translations/da.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Crowdsourcede app-konfigurationer", "allowInsecure": "Tillad usikre HTTP-anmodninger", "stayOneVersionBehind": "Forbliv én version bagud den seneste", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Opdater app-detaljer før download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Navn", "smartname": "Navn (Smart)", "sortMethod": "Sorteringsmetode", diff --git a/assets/translations/de.json b/assets/translations/de.json index 1013106..9f1165d 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Crowdsourced App-Konfigurationen", "allowInsecure": "Unsichere HTTP-Anfragen zulassen", "stayOneVersionBehind": "Eine Version hinter der neuesten Version bleiben", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "App-Details vor dem Download aktualisieren", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Name", "smartname": "Name (Smart)", "sortMethod": "Sortierverfahren", diff --git a/assets/translations/en-EO.json b/assets/translations/en-EO.json index 2f1826c..a298994 100644 --- a/assets/translations/en-EO.json +++ b/assets/translations/en-EO.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Crowdsourced App Configurations", "allowInsecure": "Allow insecure HTTP requests", "stayOneVersionBehind": "Stay one version behind latest", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Refresh app details before download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Name", "smartname": "Name (Smart)", "sortMethod": "Sort Method", diff --git a/assets/translations/en.json b/assets/translations/en.json index 3565d0a..5473a27 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -318,6 +318,7 @@ "crowdsourcedConfigsShort": "Crowdsourced app configurations", "allowInsecure": "Allow insecure HTTP requests", "stayOneVersionBehind": "Stay one version behind latest", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Refresh app details before download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", diff --git a/assets/translations/es.json b/assets/translations/es.json index 0e9d905..fe50f2b 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Configuración de aplicaciones por crowdsourcing", "allowInsecure": "Permitir peticiones HTTP inseguras", "stayOneVersionBehind": "Mantenerse una versión por detrás de la última", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Actualiza los datos de la aplicación antes de descargarla", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nombre", "smartname": "Nombre (Smart)", "sortMethod": "Método de clasificación", diff --git a/assets/translations/fa.json b/assets/translations/fa.json index e91875e..b4a7c85 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "تنظیمات برنامه های مشارکت جمعی", "allowInsecure": "درخواست های HTTP ناامن را مجاز کنید", "stayOneVersionBehind": "یک نسخه از آخرین نسخه پشت سر بگذارید", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "قبل از دانلود، جزئیات برنامه را بازخوانی کنید", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Name", "smartname": "Name (Smart)", "sortMethod": "Sort Method", diff --git a/assets/translations/fr.json b/assets/translations/fr.json index 0f4e313..c249bb9 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Applications communautaires", "allowInsecure": "Autoriser les requêtes HTTP non sécurisées", "stayOneVersionBehind": "Rester une version en arrière de la dernière", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Actualiser les détails de l'application avant de la télécharger", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nom", "smartname": "Nom (Smart)", "sortMethod": "Méthode de tri", diff --git a/assets/translations/hu.json b/assets/translations/hu.json index b140dee..0112a62 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Alkalmazáslista", "allowInsecure": "Nem biztonságos HTTP-kérések engedélyezése", "stayOneVersionBehind": "Maradjon egy verzióval a legújabb mögött", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Az alkalmazás adatainak frissítése a letöltés előtt", "tencentAppStore": "Tencent Appstore", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Név", "smartname": "Név (Okos)", "sortMethod": "Rendezési eljárás", diff --git a/assets/translations/id.json b/assets/translations/id.json index 17f3313..2cbe350 100644 --- a/assets/translations/id.json +++ b/assets/translations/id.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Konfigurasi Aplikasi Crowdsourced", "allowInsecure": "Izinkan permintaan HTTP yang tidak aman", "stayOneVersionBehind": "Tetap satu versi di belakang versi terbaru", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Segarkan detail aplikasi sebelum mengunduh", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nama", "smartname": "Nama (Cerdas)", "sortMethod": "Metode Penyortiran", diff --git a/assets/translations/it.json b/assets/translations/it.json index e0ede2f..34f28f4 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Configurazioni di app in crowdsourcing", "allowInsecure": "Consentire le richieste HTTP non sicure", "stayOneVersionBehind": "Rimanere una versione indietro rispetto alla più recente", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Aggiornare i dettagli dell'app prima del download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nome", "smartname": "Nome (intelligente)", "sortMethod": "Metodo di ordinamento", diff --git a/assets/translations/ja.json b/assets/translations/ja.json index f1c7419..8ef458d 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "クラウドソーシングによるアプリの設定", "allowInsecure": "安全でないHTTPリクエストを許可する", "stayOneVersionBehind": "最新のバージョンから1つ前のものを使用する", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "ダウンロード前にアプリの詳細を更新する", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "名称", "smartname": "名前(スマート)", "sortMethod": "ソート方法", diff --git a/assets/translations/ko.json b/assets/translations/ko.json index ad34de9..4ce75a3 100644 --- a/assets/translations/ko.json +++ b/assets/translations/ko.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "크라우드소싱 앱 구성", "allowInsecure": "안전하지 않은 HTTP 요청 허용", "stayOneVersionBehind": "최신 버전보다 한 버전 뒤에 머무르기", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "다운로드 전에 앱 세부 정보 새로 고침", "tencentAppStore": "텐센트 앱 스토어", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "이름", "smartname": "이름(스마트)", "sortMethod": "정렬 방법", diff --git a/assets/translations/nl.json b/assets/translations/nl.json index a069052..edc53ae 100644 --- a/assets/translations/nl.json +++ b/assets/translations/nl.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "App-configuraties door menigte", "allowInsecure": "Onveilige HTTP-verzoeken toestaan", "stayOneVersionBehind": "Blijf een versie achter op de nieuwste", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Vernieuw app details voor download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Naam", "smartname": "Naam (Slim)", "sortMethod": "Sorteermethode", diff --git a/assets/translations/pl.json b/assets/translations/pl.json index 7cb65cf..ad13b6b 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Baza konfiguracji", "allowInsecure": "Zezwalaj na niezabezpieczone żądania HTTP", "stayOneVersionBehind": "Pozostań jedną wersję w tyle za najnowszą", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Odśwież szczegóły aplikacji przed pobraniem", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nazwa", "smartname": "Nazwa (Smart)", "sortMethod": "Metoda sortowania", diff --git a/assets/translations/pt-BR.json b/assets/translations/pt-BR.json index 8578f5c..bd85a05 100644 --- a/assets/translations/pt-BR.json +++ b/assets/translations/pt-BR.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Configurações de app da comunidade", "allowInsecure": "Permitir solicitações de HTTP inseguras", "stayOneVersionBehind": "Ficar uma versão antes da mais recente", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Atualizar detalhes do app antes de baixar", "tencentAppStore": "Loja de Apps da Tencent", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nome", "smartname": "Nome (inteligente)", "sortMethod": "Método de ordenação", diff --git a/assets/translations/pt.json b/assets/translations/pt.json index 21125d9..ec08032 100644 --- a/assets/translations/pt.json +++ b/assets/translations/pt.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Configurações de aplicações com base em crowdsourcing", "allowInsecure": "Permitir pedidos HTTP inseguros", "stayOneVersionBehind": "Manter-se uma versão atrás da mais recente", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Atualizar os detalhes da aplicação antes da transferência", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Nome", "smartname": "Nome (Smart)", "sortMethod": "Método de ordenação", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index 9a8af88..43c80db 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Конфиги приложений с помощью краудсорсинга", "allowInsecure": "Разрешить небезопасные HTTP-запросы", "stayOneVersionBehind": "Не отставайте от последней версии", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Обновляйте информацию о приложении перед загрузкой", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Имя", "smartname": "Имя (умное)", "sortMethod": "Метод сортировки", diff --git a/assets/translations/sv.json b/assets/translations/sv.json index 11117a2..733b5b2 100644 --- a/assets/translations/sv.json +++ b/assets/translations/sv.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Appkonfigurationer med hjälp av crowdsourcing", "allowInsecure": "Tillåt osäkra HTTP-förfrågningar", "stayOneVersionBehind": "Håll dig en version bakom den senaste", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Uppdatera appdetaljerna före nedladdning", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Namn", "smartname": "Namn (Smart)", "sortMethod": "Sorteringsmetod", diff --git a/assets/translations/tr.json b/assets/translations/tr.json index 9b1e6c8..d1eb71e 100644 --- a/assets/translations/tr.json +++ b/assets/translations/tr.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Kitle Kaynaklı Uygulama Yapılandırmaları", "allowInsecure": "Güvensiz HTTP isteklerine izin ver", "stayOneVersionBehind": "En son sürümün bir sürüm gerisinde kalın", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "İndirmeden önce uygulama ayrıntılarını yenileyin", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "İsim", "smartname": "İsim (Akıllı)", "sortMethod": "Sıralama Yöntemi", diff --git a/assets/translations/uk.json b/assets/translations/uk.json index cd20fc6..892bf2d 100644 --- a/assets/translations/uk.json +++ b/assets/translations/uk.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Налаштування краудсорсингових додатків", "allowInsecure": "Дозволити незахищені HTTP-запити", "stayOneVersionBehind": "Залишайтеся на одну версію актуальнішою", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Оновіть інформацію про програму перед завантаженням", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Ім'я", "smartname": "Ім'я (Smart)", "sortMethod": "Метод сортування", diff --git a/assets/translations/vi.json b/assets/translations/vi.json index b63d108..7beaf56 100644 --- a/assets/translations/vi.json +++ b/assets/translations/vi.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "Crowdsourced App Configurations", "allowInsecure": "Allow insecure HTTP requests", "stayOneVersionBehind": "Stay one version behind latest", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "Refresh app details before download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "Name", "smartname": "Name (Smart)", "sortMethod": "Sort Method", diff --git a/assets/translations/zh-Hant-TW.json b/assets/translations/zh-Hant-TW.json index 92d6527..832855e 100644 --- a/assets/translations/zh-Hant-TW.json +++ b/assets/translations/zh-Hant-TW.json @@ -318,9 +318,11 @@ "crowdsourcedConfigsShort": "群眾外包的應用程式設定", "allowInsecure": "允許不安全的 HTTP 請求", "stayOneVersionBehind": "保持比最新版本落後一個版本", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "下載前刷新應用程式詳細資訊", "tencentAppStore": "騰訊應用寶", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store (CN)", "name": "名稱", "smartname": "名稱(智慧)", "sortMethod": "排序方式", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index eb07843..51af4b4 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -318,6 +318,7 @@ "crowdsourcedConfigsShort": "众包应用程序配置", "allowInsecure": "允许不安全的 HTTP 请求", "stayOneVersionBehind": "比最新版本晚一个版本", + "useFirstApkOfVersion": "Auto-select first of multiple APKs", "refreshBeforeDownload": "下载前刷新应用程序详细信息", "tencentAppStore": "腾讯应用宝", "coolApk": "酷安",