mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-16 22:56:44 +02:00
Merge pull request #1826 from ImranR98/dev
- Improved APKPure compatibility by prioritizing APK when available (over XAPK) - Auto-select direct APK source for URLs ending in '.apk' (#1820) - Handle install failures more gracefully on apps page (#1782) - Enabled ZH-TW and EO languages - Added "allow insecure request" option (#1825)
This commit is contained in:
2
.flutter
2
.flutter
Submodule .flutter updated: 80c2e84975...4cf269e36d
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Dijeli sa AppVerifier-om, zatim se vratite kada ste spremni.",
|
||||
"wiki": "Pomoć/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Konfiguracije aplikacije obezbeđene pomoću velikog broja ljudi (crowdsourcing) (koristite na svoju odgovornost)",
|
||||
"allowInsecure": "Allow insecure HTTP requests",
|
||||
"removeAppQuestion": {
|
||||
"one": "Želite li ukloniti aplikaciju?",
|
||||
"other": "Želite li ukloniti aplikacije?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Sdílejte do aplikace AppVerifier a po dokončení se sem vraťte.",
|
||||
"wiki": "Nápověda/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Konfigurace aplikací s využitím crowdsourcingu (použití na vlastní nebezpečí)",
|
||||
"allowInsecure": "Povolení nezabezpečených požadavků HTTP",
|
||||
"removeAppQuestion": {
|
||||
"one": "Odstranit Apku?",
|
||||
"other": "Odstranit Apky?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Del til AppVerifier, og vend tilbage hertil, når du er klar.",
|
||||
"wiki": "Hjælp/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourcede app-konfigurationer (brug på egen risiko)",
|
||||
"allowInsecure": "Tillad usikre HTTP-anmodninger",
|
||||
"removeAppQuestion": {
|
||||
"one": "Fjern app?",
|
||||
"other": "Fjern apps?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Geben Sie die Daten an AppVerifier weiter und kehren Sie dann hierher zurück, wenn Sie fertig sind.",
|
||||
"wiki": "Hilfe/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourced App-Konfigurationen (Verwendung auf eigene Gefahr)",
|
||||
"allowInsecure": "Unsichere HTTP-Anfragen zulassen",
|
||||
"removeAppQuestion": {
|
||||
"one": "App entfernen?",
|
||||
"other": "Apps entfernen?"
|
||||
|
@ -102,7 +102,7 @@
|
||||
"searchX": "Serĉi {}",
|
||||
"noResults": "Neniu rezulto",
|
||||
"importX": "Importi {}",
|
||||
"importedAppsIdDisclaimer": "La importitaj apoj povas montriĝi malĝuste kiel \"Neinstalitaj\".\nPor solvi tion, reinstalu ilin per Obtainium.\Tiu ne afekcios la apodatumoj.\n\nAkefcias nur la URL-ajn lak triajn importmetodojn.",
|
||||
"importedAppsIdDisclaimer": "La importitaj apoj povas montriĝi malĝuste kiel \"Neinstalitaj\".\nPor solvi tion, reinstalu ilin per Obtainium.\nTiu ne afekcios la apodatumoj.\n\nAkefcias nur la URL-ajn lak triajn importmetodojn.",
|
||||
"importErrors": "Eraroj de importado",
|
||||
"importedXOfYApps": "{} apoj el {} importitaj.",
|
||||
"followingURLsHadErrors": "La sekvantaj URLj havis erarojn:",
|
||||
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Diskonigu kun AppVerifier, poste revenu ĉi tie kiam preta.",
|
||||
"wiki": "Helpo/Vikio",
|
||||
"crowdsourcedConfigsLabel": "Komunumaj apo-agordoj (uzu kun singardo)",
|
||||
"allowInsecure": "Allow insecure HTTP requests",
|
||||
"removeAppQuestion": {
|
||||
"one": "Forigi la aplikaĵon?",
|
||||
"other": "Forigi la aplikaĵojn?"
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Share to AppVerifier, then return here when ready.",
|
||||
"wiki": "Help/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
|
||||
"allowInsecure": "Allow insecure HTTP requests",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remove App?",
|
||||
"other": "Remove Apps?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Comparta con AppVerifier y vuelva aquí cuando esté listo.",
|
||||
"wiki": "Ayuda/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (uso bajo su propia responsabilidad)",
|
||||
"allowInsecure": "Permitir peticiones HTTP inseguras",
|
||||
"removeAppQuestion": {
|
||||
"one": "¿Eliminar aplicación?",
|
||||
"other": "¿Eliminar aplicaciones?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "در AppVerifier به اشتراک بگذارید، سپس پس از آماده شدن به اینجا برگردید.",
|
||||
"wiki": "راهنما/ویکی",
|
||||
"crowdsourcedConfigsLabel": "تنظیمات برنامه Crowdsourced (با مسئولیت خود استفاده کنید)",
|
||||
"allowInsecure": "Allow insecure HTTP requests",
|
||||
"removeAppQuestion": {
|
||||
"one": "برنامه حذف شود؟",
|
||||
"other": "برنامه ها حذف شوند؟"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Partagez avec AppVerifier, puis revenez ici lorsque tout est prêt.",
|
||||
"wiki": "Aide/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Configurations d'applications par la communauté (à utiliser à vos risques et périls)",
|
||||
"allowInsecure": "Autoriser les requêtes HTTP non sécurisées",
|
||||
"removeAppQuestion": {
|
||||
"one": "Supprimer l'application ?",
|
||||
"other": "Supprimer les applications ?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Ossza meg az AppVerifierrel, majd térjen vissza ide, ha kész.",
|
||||
"wiki": "Súgó/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (használat saját felelősségre)",
|
||||
"allowInsecure": "Bizonytalan HTTP-kérések engedélyezése",
|
||||
"removeAppQuestion": {
|
||||
"one": "Eltávolítja az alkalmazást?",
|
||||
"other": "Eltávolítja az alkalmazásokat?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Condividete con AppVerifier, quindi tornate qui quando siete pronti.",
|
||||
"wiki": "Aiuto/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Configurazioni di app in crowdsourcing (uso a proprio rischio)",
|
||||
"allowInsecure": "Consentire le richieste HTTP non sicure",
|
||||
"removeAppQuestion": {
|
||||
"one": "Rimuovere l'app?",
|
||||
"other": "Rimuovere le app?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "AppVerifierに共有し、準備ができたらここに戻ってください。",
|
||||
"wiki": "ヘルプ/ウィキ",
|
||||
"crowdsourcedConfigsLabel": "クラウドソーシングによるアプリの設定(利用は自己責任で)",
|
||||
"allowInsecure": "安全でないHTTPリクエストを許可する",
|
||||
"removeAppQuestion": {
|
||||
"one": "アプリを削除しますか?",
|
||||
"other": "アプリを削除しますか?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Deel het met AppVerifier en keer daarna hier terug.",
|
||||
"wiki": "Help/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourced App-configuraties (gebruik op eigen risico)",
|
||||
"allowInsecure": "Onveilige HTTP-verzoeken toestaan",
|
||||
"removeAppQuestion": {
|
||||
"one": "App verwijderen?",
|
||||
"other": "Apps verwijderen?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Udostępnij w AppVerifier, a następnie wróć tutaj, gdy będziesz gotowy.",
|
||||
"wiki": "Pomoc/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Konfiguracje aplikacji pochodzące z crowdsourcingu (korzystanie na własne ryzyko)",
|
||||
"allowInsecure": "Zezwalaj na niezabezpieczone żądania HTTP",
|
||||
"removeAppQuestion": {
|
||||
"one": "Usunąć aplikację?",
|
||||
"few": "Usunąć aplikacje?",
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Partilhe com o AppVerifier e, em seguida, regresse aqui quando estiver pronto.",
|
||||
"wiki": "Ajuda/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Configurações de aplicações de crowdsourcing (utilização por sua conta e risco)",
|
||||
"allowInsecure": "Permitir pedidos HTTP inseguros",
|
||||
"removeAppQuestion": {
|
||||
"one": "Remover aplicativo?",
|
||||
"other": "Remover aplicativos?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Поделитесь с AppVerifier, а затем вернитесь сюда, когда будете готовы.",
|
||||
"wiki": "Помощь/Вики",
|
||||
"crowdsourcedConfigsLabel": "Конфигурации приложений на основе краудсорсинга (используйте на свой страх и риск)",
|
||||
"allowInsecure": "Разрешить небезопасные HTTP-запросы",
|
||||
"removeAppQuestion": {
|
||||
"one": "Удалить приложение?",
|
||||
"other": "Удалить приложения?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Dela till AppVerifier och återvänd sedan hit när du är klar.",
|
||||
"wiki": "Hjälp/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourcade appkonfigurationer (använd på egen risk)",
|
||||
"allowInsecure": "Tillåt osäkra HTTP-förfrågningar",
|
||||
"removeAppQuestion": {
|
||||
"one": "Ta Bort App?",
|
||||
"other": "Ta Bort Appar?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "AppVerifier ile paylaşın, hazır olduğunuzda buraya dönün.",
|
||||
"wiki": "Yardım/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Kitle Kaynaklı Uygulama Yapılandırmaları (riski size ait olmak üzere kullanın)",
|
||||
"allowInsecure": "Güvensiz HTTP isteklerine izin ver",
|
||||
"removeAppQuestion": {
|
||||
"one": "Uygulamayı Kaldır?",
|
||||
"other": "Uygulamaları Kaldır?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Надішліть на AppVerifier, а потім поверніться сюди, коли будете готові.",
|
||||
"wiki": "Довідка/Вікі",
|
||||
"crowdsourcedConfigsLabel": "Краудсорсингові конфігурації додатків (використовуйте на свій страх і ризик)",
|
||||
"allowInsecure": "Дозволити незахищені HTTP-запити",
|
||||
"removeAppQuestion": {
|
||||
"one": "Видалити застосунок?",
|
||||
"other": "Видалити застосунки?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "Chia sẻ lên AppVerifier, sau đó quay lại đây khi sẵn sàng.",
|
||||
"wiki": "Trợ giúp/Wiki",
|
||||
"crowdsourcedConfigsLabel": "Crowdsourced App Configurations (use at your own risk)",
|
||||
"allowInsecure": "Allow insecure HTTP requests",
|
||||
"removeAppQuestion": {
|
||||
"one": "Gỡ ứng dụng?",
|
||||
"other": "Gỡ ứng dụng?"
|
||||
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "分享至 AppVerifier,然後準備好時回到此處。",
|
||||
"wiki": "幫助/維基",
|
||||
"crowdsourcedConfigsLabel": "群眾外包的應用程式設定(使用風險自負)",
|
||||
"allowInsecure": "Allow insecure HTTP requests",
|
||||
"removeAppQuestion": {
|
||||
"one": "移除應用程式?",
|
||||
"other": "移除應用程式?"
|
@ -314,6 +314,7 @@
|
||||
"appVerifierInstructionToast": "分享至 AppVerifier,完成后返回此处。",
|
||||
"wiki": "帮助/Wiki",
|
||||
"crowdsourcedConfigsLabel": "众包应用程序配置(使用风险自负)",
|
||||
"allowInsecure": "允许不安全的 HTTP 请求",
|
||||
"removeAppQuestion": {
|
||||
"one": "是否删除应用?",
|
||||
"other": "是否删除应用?"
|
||||
|
@ -10,7 +10,7 @@ class APKCombo extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -32,7 +32,7 @@ class APKMirror extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/apk/[^/]+/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -29,7 +29,7 @@ class APKPure extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegExB = RegExp(
|
||||
'^https?://m.${getSourceRegex(hosts)}(/+[^/]{2})?/+[^/]+/+[^/]+',
|
||||
caseSensitive: false);
|
||||
@ -109,11 +109,16 @@ class APKPure extends AppSource {
|
||||
'')
|
||||
?.group(0)
|
||||
?.trim();
|
||||
String? type = apkInfo
|
||||
?.querySelector('div.info-top span.tag')
|
||||
?.text
|
||||
.trim() ??
|
||||
'APK';
|
||||
var types = apkInfo
|
||||
?.querySelectorAll('div.info-top span.tag')
|
||||
.map((e) => e.text.trim())
|
||||
.map((t) => t == 'APKs' ? 'APK' : t) ??
|
||||
[];
|
||||
String type = types.isEmpty
|
||||
? 'APK'
|
||||
: types.length == 1
|
||||
? types.first
|
||||
: types.last;
|
||||
String? dateString = apkInfo
|
||||
?.querySelector('div.info-bottom span.time')
|
||||
?.text
|
||||
|
@ -14,7 +14,7 @@ class Aptoide extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
|
||||
caseSensitive: false);
|
||||
|
@ -16,7 +16,7 @@ class Codeberg extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:obtainium/app_sources/html.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
import 'package:obtainium/providers/source_provider.dart';
|
||||
|
||||
class DirectAPKLink extends AppSource {
|
||||
HTML html = HTML();
|
||||
|
||||
DirectAPKLink() {
|
||||
neverAutoSelect = true;
|
||||
name = tr('directAPKLink');
|
||||
additionalSourceAppSpecificSettingFormItems = html
|
||||
.additionalSourceAppSpecificSettingFormItems
|
||||
@ -24,6 +24,19 @@ class DirectAPKLink extends AppSource {
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
if (!forSelection) {
|
||||
return url;
|
||||
}
|
||||
RegExp standardUrlRegExA = RegExp('.+\\.apk\$', caseSensitive: false);
|
||||
var match = standardUrlRegExA.firstMatch(url);
|
||||
if (match == null) {
|
||||
throw InvalidURLError(name);
|
||||
}
|
||||
return match.group(0)!;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>?> getRequestHeaders(
|
||||
Map<String, dynamic> additionalSettings,
|
||||
|
@ -38,7 +38,7 @@ class FDroid extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegExB = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/+[^/]+/+packages/+[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -43,7 +43,7 @@ class FDroidRepo extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
var standardUri = Uri.parse(url);
|
||||
var pathSegments = standardUri.pathSegments;
|
||||
if (pathSegments.isNotEmpty && pathSegments.last == 'index.xml') {
|
||||
|
@ -154,7 +154,7 @@ class GitHub extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -52,7 +52,7 @@ class GitLab extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -288,7 +288,7 @@ class HTML extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
return url;
|
||||
}
|
||||
|
||||
@ -350,7 +350,8 @@ class HTML extends AppSource {
|
||||
? rel.hashCode.toString()
|
||||
: (await checkPartialDownloadHashDynamic(rel,
|
||||
headers: await getRequestHeaders(additionalSettings,
|
||||
forAPKDownload: true)))
|
||||
forAPKDownload: true),
|
||||
allowInsecure: additionalSettings['allowInsecure'] == true))
|
||||
.toString();
|
||||
return APKDetails(version, [rel].map((e) => MapEntry(e, e)).toList(),
|
||||
AppNames(uri.host, tr('app')));
|
||||
|
@ -12,7 +12,7 @@ class HuaweiAppGallery extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}(/#)?/(app|appdl)/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -14,7 +14,7 @@ class IzzyOnDroid extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegExA = RegExp(
|
||||
'^https?://android.${getSourceRegex(hosts)}/repo/apk/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -10,7 +10,7 @@ class Mullvad extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}',
|
||||
caseSensitive: false);
|
||||
|
@ -10,7 +10,7 @@ class NeutronCode extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/downloads/file/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -9,7 +9,7 @@ class Signal extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
return 'https://${hosts[0]}';
|
||||
}
|
||||
|
||||
|
@ -9,7 +9,7 @@ class SourceForge extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
var sourceRegex = getSourceRegex(hosts);
|
||||
RegExp standardUrlRegExC =
|
||||
RegExp('^https?://(www\\.)?$sourceRegex/p/.+', caseSensitive: false);
|
||||
|
@ -20,7 +20,7 @@ class SourceHut extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://(www\\.)?${getSourceRegex(hosts)}/[^/]+/[^/]+',
|
||||
caseSensitive: false);
|
||||
|
@ -20,7 +20,7 @@ class SteamMobile extends AppSource {
|
||||
final apks = {'steam': tr('steamMobile'), 'steam-chat-app': tr('steamChat')};
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
return 'https://${hosts[0]}';
|
||||
}
|
||||
|
||||
|
@ -11,7 +11,7 @@ class TelegramApp extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
return 'https://${hosts[0]}';
|
||||
}
|
||||
|
||||
|
@ -14,7 +14,7 @@ class Uptodown extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
RegExp standardUrlRegEx = RegExp(
|
||||
'^https?://([^\\.]+\\.){2,}${getSourceRegex(hosts)}',
|
||||
caseSensitive: false);
|
||||
|
@ -21,7 +21,7 @@ class VLC extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
return 'https://${hosts[0]}';
|
||||
}
|
||||
|
||||
|
@ -10,7 +10,7 @@ class WhatsApp extends AppSource {
|
||||
}
|
||||
|
||||
@override
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
return 'https://${hosts[0]}';
|
||||
}
|
||||
|
||||
|
@ -23,6 +23,7 @@ import 'package:easy_localization/src/localization.dart';
|
||||
List<MapEntry<Locale, String>> supportedLocales = const [
|
||||
MapEntry(Locale('en'), 'English'),
|
||||
MapEntry(Locale('zh'), '简体中文'),
|
||||
MapEntry(Locale('zh_Hant_TW'), '臺灣話'),
|
||||
MapEntry(Locale('it'), 'Italiano'),
|
||||
MapEntry(Locale('ja'), '日本語'),
|
||||
MapEntry(Locale('hu'), 'Magyar'),
|
||||
@ -41,6 +42,8 @@ List<MapEntry<Locale, String>> supportedLocales = const [
|
||||
MapEntry(Locale('tr'), 'Türkçe'),
|
||||
MapEntry(Locale('uk'), 'Українська'),
|
||||
MapEntry(Locale('da'), 'Dansk'),
|
||||
MapEntry(Locale('en', 'EO'),
|
||||
'Esperanto'), // https://github.com/aissat/easy_localization/issues/220#issuecomment-846035493
|
||||
];
|
||||
const fallbackLocale = Locale('en');
|
||||
const localeDir = 'assets/translations';
|
||||
@ -245,15 +248,17 @@ class _ObtainiumState extends State<Obtainium> {
|
||||
colorScheme: settingsProvider.theme == ThemeSettings.dark
|
||||
? darkColorScheme
|
||||
: lightColorScheme,
|
||||
fontFamily:
|
||||
settingsProvider.useSystemFont ? 'SystemFont' : 'Wix-Madefor-Display'),
|
||||
fontFamily: settingsProvider.useSystemFont
|
||||
? 'SystemFont'
|
||||
: 'Wix-Madefor-Display'),
|
||||
darkTheme: ThemeData(
|
||||
useMaterial3: true,
|
||||
colorScheme: settingsProvider.theme == ThemeSettings.light
|
||||
? lightColorScheme
|
||||
: darkColorScheme,
|
||||
fontFamily:
|
||||
settingsProvider.useSystemFont ? 'SystemFont' : 'Wix-Madefor-Display'),
|
||||
fontFamily: settingsProvider.useSystemFont
|
||||
? 'SystemFont'
|
||||
: 'Wix-Madefor-Display'),
|
||||
home: Shortcuts(shortcuts: <LogicalKeySet, Intent>{
|
||||
LogicalKeySet(LogicalKeyboardKey.select): const ActivateIntent(),
|
||||
}, child: const HomePage()));
|
||||
|
@ -17,6 +17,7 @@ import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:obtainium/components/generated_form.dart';
|
||||
import 'package:obtainium/components/generated_form_modal.dart';
|
||||
import 'package:obtainium/custom_errors.dart';
|
||||
@ -146,17 +147,23 @@ Future<File> downloadFileWithRetry(String url, String fileName,
|
||||
bool fileNameHasExt, Function? onProgress, String destDir,
|
||||
{bool useExisting = true,
|
||||
Map<String, String>? headers,
|
||||
int retries = 3}) async {
|
||||
int retries = 3,
|
||||
bool allowInsecure = false}) async {
|
||||
try {
|
||||
return await downloadFile(
|
||||
url, fileName, fileNameHasExt, onProgress, destDir,
|
||||
useExisting: useExisting, headers: headers);
|
||||
useExisting: useExisting,
|
||||
headers: headers,
|
||||
allowInsecure: allowInsecure);
|
||||
} catch (e) {
|
||||
if (retries > 0 && e is ClientException) {
|
||||
await Future.delayed(const Duration(seconds: 5));
|
||||
return await downloadFileWithRetry(
|
||||
url, fileName, fileNameHasExt, onProgress, destDir,
|
||||
useExisting: useExisting, headers: headers, retries: (retries - 1));
|
||||
useExisting: useExisting,
|
||||
headers: headers,
|
||||
retries: (retries - 1),
|
||||
allowInsecure: allowInsecure);
|
||||
} else {
|
||||
rethrow;
|
||||
}
|
||||
@ -173,11 +180,14 @@ String hashListOfLists(List<List<int>> data) {
|
||||
Future<String> checkPartialDownloadHashDynamic(String url,
|
||||
{int startingSize = 1024,
|
||||
int lowerLimit = 128,
|
||||
Map<String, String>? headers}) async {
|
||||
Map<String, String>? headers,
|
||||
bool allowInsecure = false}) async {
|
||||
for (int i = startingSize; i >= lowerLimit; i -= 256) {
|
||||
List<String> ab = await Future.wait([
|
||||
checkPartialDownloadHash(url, i, headers: headers),
|
||||
checkPartialDownloadHash(url, i, headers: headers)
|
||||
checkPartialDownloadHash(url, i,
|
||||
headers: headers, allowInsecure: allowInsecure),
|
||||
checkPartialDownloadHash(url, i,
|
||||
headers: headers, allowInsecure: allowInsecure)
|
||||
]);
|
||||
if (ab[0] == ab[1]) {
|
||||
return ab[0];
|
||||
@ -187,13 +197,13 @@ Future<String> checkPartialDownloadHashDynamic(String url,
|
||||
}
|
||||
|
||||
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
|
||||
{Map<String, String>? headers}) async {
|
||||
{Map<String, String>? headers, bool allowInsecure = false}) async {
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
if (headers != null) {
|
||||
req.headers.addAll(headers);
|
||||
}
|
||||
req.headers[HttpHeaders.rangeHeader] = 'bytes=0-$bytesToGrab';
|
||||
var client = http.Client();
|
||||
var client = IOClient(createHttpClient(allowInsecure));
|
||||
var response = await client.send(req);
|
||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
|
||||
@ -204,12 +214,14 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
|
||||
|
||||
Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
|
||||
Function? onProgress, String destDir,
|
||||
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||
{bool useExisting = true,
|
||||
Map<String, String>? headers,
|
||||
bool allowInsecure = false}) async {
|
||||
// Send the initial request but cancel it as soon as you have the headers
|
||||
var reqHeaders = headers ?? {};
|
||||
var req = Request('GET', Uri.parse(url));
|
||||
req.headers.addAll(reqHeaders);
|
||||
var client = http.Client();
|
||||
var client = IOClient(createHttpClient(allowInsecure));
|
||||
StreamedResponse response = await client.send(req);
|
||||
var resHeaders = response.headers;
|
||||
|
||||
@ -275,7 +287,7 @@ Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
|
||||
IOSink? sink;
|
||||
if (rangeFeatureEnabled && fullContentLength != null && rangeStart > 0) {
|
||||
client.close();
|
||||
client = http.Client();
|
||||
client = IOClient(createHttpClient(allowInsecure));
|
||||
req = Request('GET', Uri.parse(url));
|
||||
req.headers.addAll(reqHeaders);
|
||||
req.headers.addAll({'range': 'bytes=$rangeStart-${fullContentLength - 1}'});
|
||||
@ -318,12 +330,12 @@ Future<File> downloadFile(String url, String fileName, bool fileNameHasExt,
|
||||
}
|
||||
|
||||
Future<Map<String, String>> getHeaders(String url,
|
||||
{Map<String, String>? headers}) async {
|
||||
{Map<String, String>? headers, bool allowInsecure = false}) async {
|
||||
var req = http.Request('GET', Uri.parse(url));
|
||||
if (headers != null) {
|
||||
req.headers.addAll(headers);
|
||||
}
|
||||
var client = http.Client();
|
||||
var client = IOClient(createHttpClient(allowInsecure));
|
||||
var response = await client.send(req);
|
||||
if (response.statusCode < 200 || response.statusCode > 299) {
|
||||
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
|
||||
@ -468,7 +480,9 @@ class AppsProvider with ChangeNotifier {
|
||||
notificationsProvider?.notify(notif);
|
||||
}
|
||||
prevProg = prog;
|
||||
}, APKDir.path, useExisting: useExisting);
|
||||
}, APKDir.path,
|
||||
useExisting: useExisting,
|
||||
allowInsecure: app.additionalSettings['allowInsecure'] == true);
|
||||
// Set to 90 for remaining steps, will make null in 'finally'
|
||||
if (apps[app.id] != null) {
|
||||
apps[app.id]!.downloadProgress = -1;
|
||||
@ -967,11 +981,16 @@ class AppsProvider with ChangeNotifier {
|
||||
}
|
||||
for (var res in downloadResults) {
|
||||
if (!errors.appIdNames.containsKey(res['id'])) {
|
||||
await installFn(
|
||||
res['id'] as String,
|
||||
res['willBeSilent'] as bool,
|
||||
res['downloadedFile'] as DownloadedApk?,
|
||||
res['downloadedDir'] as DownloadedXApkDir?);
|
||||
try {
|
||||
await installFn(
|
||||
res['id'] as String,
|
||||
res['willBeSilent'] as bool,
|
||||
res['downloadedFile'] as DownloadedApk?,
|
||||
res['downloadedDir'] as DownloadedXApkDir?);
|
||||
} catch (e) {
|
||||
var id = res['id'] as String;
|
||||
errors.add(id, e, appName: apps[id]?.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1031,7 +1050,8 @@ class AppsProvider with ChangeNotifier {
|
||||
.getRequestHeaders(app.additionalSettings,
|
||||
forAPKDownload:
|
||||
fileUrl.key.endsWith('.apk') ? true : false),
|
||||
useExisting: false);
|
||||
useExisting: false,
|
||||
allowInsecure: app.additionalSettings['allowInsecure'] == true);
|
||||
notificationsProvider
|
||||
.notify(DownloadedNotification(fileUrl.key, fileUrl.value));
|
||||
} catch (e) {
|
||||
|
@ -2,11 +2,13 @@
|
||||
// AppSource is an abstract class with a concrete implementation for each source
|
||||
|
||||
import 'dart:convert';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:device_info_plus/device_info_plus.dart';
|
||||
import 'package:easy_localization/easy_localization.dart';
|
||||
import 'package:html/dom.dart';
|
||||
import 'package:http/http.dart';
|
||||
import 'package:http/io_client.dart';
|
||||
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||
import 'package:obtainium/app_sources/apkpure.dart';
|
||||
import 'package:obtainium/app_sources/aptoide.dart';
|
||||
@ -399,6 +401,15 @@ getSourceRegex(List<String> hosts) {
|
||||
return '(${hosts.join('|').replaceAll('.', '\\.')})';
|
||||
}
|
||||
|
||||
HttpClient createHttpClient(bool insecure) {
|
||||
final client = HttpClient();
|
||||
if (insecure) {
|
||||
client.badCertificateCallback =
|
||||
(X509Certificate cert, String host, int port) => true;
|
||||
}
|
||||
return client;
|
||||
}
|
||||
|
||||
abstract class AppSource {
|
||||
List<String> hosts = [];
|
||||
bool hostChanged = false;
|
||||
@ -462,7 +473,9 @@ abstract class AppSource {
|
||||
if (requestHeaders != null) {
|
||||
req.headers.addAll(requestHeaders);
|
||||
}
|
||||
return Response.fromStream(await Client().send(req));
|
||||
return Response.fromStream(await IOClient(
|
||||
createHttpClient(additionalSettings['allowInsecure'] == true))
|
||||
.send(req));
|
||||
} else {
|
||||
return get(Uri.parse(url));
|
||||
}
|
||||
@ -472,7 +485,7 @@ abstract class AppSource {
|
||||
//
|
||||
}
|
||||
|
||||
String sourceSpecificStandardizeURL(String url) {
|
||||
String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) {
|
||||
throw NotImplementedError();
|
||||
}
|
||||
|
||||
@ -538,6 +551,10 @@ abstract class AppSource {
|
||||
GeneratedFormSwitch('shizukuPretendToBeGooglePlay',
|
||||
label: tr('shizukuPretendToBeGooglePlay'), defaultValue: false)
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('allowInsecure',
|
||||
label: tr('allowInsecure'), defaultValue: false)
|
||||
],
|
||||
[
|
||||
GeneratedFormSwitch('exemptFromBackgroundUpdates',
|
||||
label: tr('exemptFromBackgroundUpdates'))
|
||||
@ -809,7 +826,7 @@ class SourceProvider {
|
||||
for (var s in sources.where(
|
||||
(element) => element.hosts.isEmpty && !element.neverAutoSelect)) {
|
||||
try {
|
||||
s.sourceSpecificStandardizeURL(url);
|
||||
s.sourceSpecificStandardizeURL(url, forSelection: true);
|
||||
source = s;
|
||||
break;
|
||||
} catch (e) {
|
||||
|
48
pubspec.lock
48
pubspec.lock
@ -47,10 +47,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: app_links
|
||||
sha256: "4acba851087b25136e8f6e32a53bd4536eb3bec69947ddb66e7b9a5792ceb0c7"
|
||||
sha256: ad1a6d598e7e39b46a34f746f9a8b011ee147e4c275d407fa457e7a62f84dd99
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.2.0"
|
||||
version: "6.3.2"
|
||||
app_links_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -103,10 +103,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: background_fetch
|
||||
sha256: b5c298c911bc2ce41152668bc72eb0488f0665d75bc6d1e69e7d8367763eddcd
|
||||
sha256: f910c1c7c67a55f242daf78e9e9835d26eb01d39fc7f5d77f57dd84d009a6bab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.3.5"
|
||||
version: "1.3.6"
|
||||
boolean_selector:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -311,10 +311,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flex_seed_scheme
|
||||
sha256: cc08c81879ecfd2ab840664ce4770980da0b8a319e35f51bcf763849b7f7596b
|
||||
sha256: "7d97ba5c20f0e5cb1e3e2c17c865e1f797d129de31fc1f75d2dcce9470d6373c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "3.3.0"
|
||||
flutter:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@ -441,10 +441,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_plugin_android_lifecycle
|
||||
sha256: "9d98bd47ef9d34e803d438f17fd32b116d31009f534a6fa5ce3a1167f189a6de"
|
||||
sha256: "9ee02950848f61c4129af3d6ec84a1cfc0e47931abc746b03e7a3bc3e8ff6eda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.0.21"
|
||||
version: "2.0.22"
|
||||
flutter_test:
|
||||
dependency: "direct dev"
|
||||
description: flutter
|
||||
@ -611,10 +611,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: mime
|
||||
sha256: "2e123074287cc9fd6c09de8336dae606d1ddb88d9ac47358826db698c176a1f2"
|
||||
sha256: "801fd0b26f14a4a58ccb09d5892c3fbdeff209594300a542492cf13fba9d247a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.5"
|
||||
version: "1.0.6"
|
||||
nested:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -715,10 +715,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: permission_handler_html
|
||||
sha256: d220eb8476b466d58b161e10b3001d93999010a26228a3fb89c4280db1249546
|
||||
sha256: af26edbbb1f2674af65a8f4b56e1a6f526156bc273d0e65dd8075fab51c78851
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.1.3+1"
|
||||
version: "0.1.3+2"
|
||||
permission_handler_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -827,10 +827,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: shared_preferences_android
|
||||
sha256: a7e8467e9181cef109f601e3f65765685786c1a738a83d7fbbde377589c0d974
|
||||
sha256: "480ba4345773f56acda9abf5f50bd966f581dac5d514e5fc4a18c62976bbba7e"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.3.1"
|
||||
version: "2.3.2"
|
||||
shared_preferences_foundation:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1001,10 +1001,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: url_launcher_android
|
||||
sha256: f0c73347dfcfa5b3db8bc06e1502668265d39c08f310c29bff4e28eea9699f79
|
||||
sha256: e35a698ac302dd68e41f73250bd9517fe3ab5fa4f18fe4647a0872db61bacbab
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.3.9"
|
||||
version: "6.3.10"
|
||||
url_launcher_ios:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1057,10 +1057,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: uuid
|
||||
sha256: "83d37c7ad7aaf9aa8e275490669535c8080377cfa7a7004c24dfac53afffaa90"
|
||||
sha256: f33d6bb662f0e4f79dcd7ada2e6170f3b3a2530c28fc41f49a411ddedd576a77
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.4.2"
|
||||
version: "4.5.0"
|
||||
vector_math:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1073,10 +1073,10 @@ packages:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: vm_service
|
||||
sha256: f652077d0bdf60abe4c1f6377448e8655008eef28f128bc023f7b5e8dfeb48fc
|
||||
sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "14.2.4"
|
||||
version: "14.2.5"
|
||||
web:
|
||||
dependency: transitive
|
||||
description:
|
||||
@ -1089,18 +1089,18 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: webview_flutter
|
||||
sha256: "6869c8786d179f929144b4a1f86e09ac0eddfe475984951ea6c634774c16b522"
|
||||
sha256: ec81f57aa1611f8ebecf1d2259da4ef052281cb5ad624131c93546c79ccc7736
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "4.8.0"
|
||||
version: "4.9.0"
|
||||
webview_flutter_android:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: webview_flutter_android
|
||||
sha256: c66651fba15f9d7ddd31daec42da8d6bce46c85610a7127e3ebcb39a4395c3c9
|
||||
sha256: "6e64fcb1c19d92024da8f33503aaeeda35825d77142c01d0ea2aa32edc79fdc8"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.16.6"
|
||||
version: "3.16.7"
|
||||
webview_flutter_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
@ -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: 1.1.20+2277
|
||||
version: 1.1.21+2278
|
||||
|
||||
environment:
|
||||
sdk: '>=3.0.0 <4.0.0'
|
||||
|
Reference in New Issue
Block a user