diff --git a/README.md b/README.md index cf57007..762d485 100644 --- a/README.md +++ b/README.md @@ -2,31 +2,36 @@ Get Android App Updates Directly From the Source. -Obtainium allows you to install and update Open-Source Apps directly from their releases pages, and receive notifications when new releases are made available. +Obtainium allows you to install and update Apps directly from their releases pages, and receive notifications when new releases are made available. Motivation: [Side Of Burritos - You should use this instead of F-Droid | How to use app RSS feed](https://youtu.be/FFz57zNR_M0) Currently supported App sources: -- [GitHub](https://github.com/) -- [GitLab](https://gitlab.com/) -- [Codeberg](https://codeberg.org/) -- [F-Droid](https://f-droid.org/) -- [IzzyOnDroid](https://android.izzysoft.de/) -- [Mullvad](https://mullvad.net/en/) -- [Signal](https://signal.org/) -- [SourceForge](https://sourceforge.net/) -- [SourceHut](https://git.sr.ht/) -- [Aptoide](https://aptoide.com/) -- [APKMirror](https://apkmirror.com/) (Track-Only) -- [APKPure](https://apkpure.com/) -- [Huawei AppGallery](https://appgallery.huawei.com/) -- Third Party F-Droid Repos -- Jenkins Jobs -- [Steam](https://store.steampowered.com/mobile) -- [Telegram App](https://telegram.org) -- [Neutron Code](https://neutroncode.com) -- "HTML" (Fallback) - - Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked) +- Open Source - General: + - [GitHub](https://github.com/) + - [GitLab](https://gitlab.com/) + - [Codeberg](https://codeberg.org/) + - [F-Droid](https://f-droid.org/) + - Third Party F-Droid Repos + - [IzzyOnDroid](https://android.izzysoft.de/) + - [SourceForge](https://sourceforge.net/) + - [SourceHut](https://git.sr.ht/) +- Other - General: + - [APKPure](https://apkpure.com/) + - [Aptoide](https://aptoide.com/) + - [Uptodown](https://uptodown.com/) + - [APKMirror](https://apkmirror.com/) (Track-Only) + - [Huawei AppGallery](https://appgallery.huawei.com/) + - Jenkins Jobs +- Open Source - App-Specific: + - [Mullvad](https://mullvad.net/en/) + - [Signal](https://signal.org/) + - [VLC](https://videolan.org/) +- Other - App-Specific: + - [Telegram App](https://telegram.org) + - [Steam Mobile Apps](https://store.steampowered.com/mobile) + - [Neutron Code](https://neutroncode.com) +- "HTML" (Fallback): Any other URL that returns an HTML page with links to APK files ## Installation diff --git a/assets/translations/br.json b/assets/translations/br.json index 7a23ea5..9073850 100644 --- a/assets/translations/br.json +++ b/assets/translations/br.json @@ -12,8 +12,6 @@ "ok": "Ok", "and": "e", "githubPATLabel": "Token de Acceso Pessoal do GitHub (Reduz tempos de espera)", - "githubPATHint": "O TAP deve estar nesse formato: usuario:token", - "githubPATFormat": "usuario:token", "includePrereleases": "Incluir pré-lançamentos", "fallbackToOlderReleases": "Retornar para versões anteriores", "filterReleaseTitlesByRegEx": "Filtrar Titulos de Versões por Expressão Regular", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Procurar (Apenas Algumas Fontes)", "search": "Procurar", "additionalOptsFor": "Opções Adicionais para {}", - "supportedSourcesBelow": "Fontes Compatíveis:", + "supportedSources": "Fontes Compatíveis", "trackOnlyInBrackets": "(Apenas Seguir)", "searchableInBrackets": "(Pesquisável)", "appsString": "Apps", @@ -253,6 +251,8 @@ "exemptFromBackgroundUpdates": "Isento de atualizações em segundo plano (se ativadas)", "bgUpdatesOnWiFiOnly": "Desative atualizações em segundo plano quando não estiver em WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Remover App?", "other": "Remover Apps?" diff --git a/assets/translations/bs.json b/assets/translations/bs.json index 9813cd4..7b7d657 100644 --- a/assets/translations/bs.json +++ b/assets/translations/bs.json @@ -12,8 +12,6 @@ "ok": "Dobro", "and": "i", "githubPATLabel": "GitHub token za lični pristup (eng. PAT, povećava ograničenje stope)", - "githubPATHint": "PAT mora biti u ovom formatu: korisničko_ime:token", - "githubPATFormat": "korisničko_ime:token", "includePrereleases": "Uključi preliminarna izdanja", "fallbackToOlderReleases": "Povratak na starija izdanja", "filterReleaseTitlesByRegEx": "Filtrirajte naslove izdanja prema regularnom izrazu", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Pretraživanje (samo neki izvori)", "search": "Pretraživanje", "additionalOptsFor": "Dodatne opcije za {}", - "supportedSourcesBelow": "Podržani izvori:", + "supportedSources": "Podržani izvori", "trackOnlyInBrackets": "(Samo za praćenje)", "searchableInBrackets": "(Može se pretraživati)", "appsString": "Aplikacije", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Želite li ukloniti aplikaciju?", "other": "Želite li ukloniti aplikacije?" diff --git a/assets/translations/de.json b/assets/translations/de.json index 9d0de6a..300844d 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -12,8 +12,6 @@ "ok": "Okay", "and": "und", "githubPATLabel": "GitHub Personal Access Token (Erhöht das Ratenlimit)", - "githubPATHint": "PAT muss in diesem Format sein: Benutzername:Token", - "githubPATFormat": "Benutzername:Token", "includePrereleases": "Vorabversionen einbeziehen", "fallbackToOlderReleases": "Fallback auf ältere Versionen", "filterReleaseTitlesByRegEx": "Release-Titel nach regulärem Ausdruck\nfiltern", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Suche (nur bestimmte Quellen)", "search": "Suchen", "additionalOptsFor": "Zusatzoptionen für {}", - "supportedSourcesBelow": "Unterstützte Quellen:", + "supportedSources": "Unterstützte Quellen", "trackOnlyInBrackets": "(Nur Nachverfolgen)", "searchableInBrackets": "(Durchsuchbar)", "appsString": "Apps", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Ausschluss von Hintergrundaktualisierungen (falls aktiviert)", "bgUpdatesOnWiFiOnly": "Hintergrundaktualisierungen deaktivieren, wenn kein WLAN vorhanden ist", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index c157509..abf4080 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -12,8 +12,6 @@ "ok": "Okay", "and": "and", "githubPATLabel": "GitHub Personal Access Token (Increases Rate Limit)", - "githubPATHint": "PAT must be in this format: username:token", - "githubPATFormat": "username:token", "includePrereleases": "Include prereleases", "fallbackToOlderReleases": "Fallback to older releases", "filterReleaseTitlesByRegEx": "Filter Release Titles by Regular Expression", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Search (Some Sources Only)", "search": "Search", "additionalOptsFor": "Additional Options for {}", - "supportedSourcesBelow": "Supported Sources:", + "supportedSources": "Supported Sources", "trackOnlyInBrackets": "(Track-Only)", "searchableInBrackets": "(Searchable)", "appsString": "Apps", @@ -253,6 +251,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index bf83959..75ea294 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -12,8 +12,6 @@ "ok": "Correcto", "and": "y", "githubPATLabel": "Token de Acceso Personal de GitHub (Reduce tiempos de espera)", - "githubPATHint": "El TAP debe tener este formato: nombre_de_usuario:token", - "githubPATFormat": "nombre_de_usuario:token", "includePrereleases": "Incluir versiones preliminares", "fallbackToOlderReleases": "Retorceder a versiones previas", "filterReleaseTitlesByRegEx": "Filtra Títulos de Versiones mediantes Expresiones Regulares", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Buscar (Solo Algunas Fuentes)", "search": "Buscar", "additionalOptsFor": "Opciones Adicionales para {}", - "supportedSourcesBelow": "Fuentes Soportadas:", + "supportedSources": "Fuentes Soportadas", "trackOnlyInBrackets": "(Solo Seguimiento)", "searchableInBrackets": "(Soporta Búsquedas)", "appsString": "Aplicaciones", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index 1866dd6..5afe7df 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -12,8 +12,6 @@ "ok": "باشه", "and": "و", "githubPATLabel": "توکن دسترسی شخصی گیت هاب(محدودیت نرخ را افزایش میدهد)", - "githubPATHint": "PAT باید در این قالب باشد: username:token", - "githubPATFormat": "username:token", "includePrereleases": "شامل نسخه های اولیه", "fallbackToOlderReleases": "بازگشت به نسخه های قدیمی تر", "filterReleaseTitlesByRegEx": "عناوین انتشار را با بیان منظم فیلتر کنید", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "جستجو (فقط برخی منابع)", "search": "جستجو کردن", "additionalOptsFor": "گزینه های اضافی برای {}", - "supportedSourcesBelow": "منابع پشتیبانی شده:", + "supportedSources": "منابع پشتیبانی شده", "trackOnlyInBrackets": "«فقط ردیابی»", "searchableInBrackets": "(قابل جستجو)", "appsString": "برنامه ها", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index e55ba3b..fbd15c1 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -12,8 +12,6 @@ "ok": "Okay", "and": "et", "githubPATLabel": "Jeton d'Accès Personnel GitHub (Augmente la limite de débit)", - "githubPATHint": "Le JAP doit être dans ce format : username:token", - "githubPATFormat": "username:token", "includePrereleases": "Inclure les avant-premières", "fallbackToOlderReleases": "Retour aux anciennes versions", "filterReleaseTitlesByRegEx": "Filtrer les titres de version par expression régulière", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Rechercher (certaines sources uniquement)", "search": "Rechercher", "additionalOptsFor": "Options supplémentaires pour {}", - "supportedSourcesBelow": "Sources prises en charge :", + "supportedSources": "Sources prises en charge ", "trackOnlyInBrackets": "(Suivi uniquement)", "searchableInBrackets": "(Recherchable)", "appsString": "Applications", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index b7a75d9..0c5d98f 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -12,8 +12,6 @@ "ok": "Oké", "and": "és", "githubPATLabel": "GitHub Personal Access Token (megnöveli a díjkorlátot)", - "githubPATHint": "A PAT-nak a következő formátumban kell lennie: felhasználónév:token", - "githubPATFormat": "felhasználónév:token", "includePrereleases": "Tartalmazza az előzetes kiadásokat", "fallbackToOlderReleases": "Visszatérés a régebbi kiadásokhoz", "filterReleaseTitlesByRegEx": "A kiadás címeinek szűrése reguláris kifejezéssel", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Keresés (csak egyes források)", "search": "Keresés", "additionalOptsFor": "További lehetőségek a következőhöz: {}", - "supportedSourcesBelow": "Támogatott források:", + "supportedSources": "Támogatott források", "trackOnlyInBrackets": "(Csak nyomonkövetés)", "searchableInBrackets": "(Kereshető)", "appsString": "Appok", @@ -249,6 +247,8 @@ "exemptFromBackgroundUpdates": "Mentes a háttérben történő frissítések alól (ha engedélyezett)", "bgUpdatesOnWiFiOnly": "Tiltsa le a háttérben frissítéseket, ha nincs Wi-Fi-n", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "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 5d43a36..d63547d 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -12,8 +12,6 @@ "ok": "Va bene", "and": "e", "githubPATLabel": "GitHub Personal Access Token (diminuisce limite di traffico)", - "githubPATHint": "PAT deve seguire questo formato: nomeutente:token", - "githubPATFormat": "nomeutente:token", "includePrereleases": "Includi prerelease", "fallbackToOlderReleases": "Ripiega su release precedenti", "filterReleaseTitlesByRegEx": "Filtra release con espressioni regolari", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Cerca (solo per alcune fonti)", "search": "Cerca", "additionalOptsFor": "Opzioni aggiuntive per {}", - "supportedSourcesBelow": "Fonti supportate:", + "supportedSources": "Fonti supportate", "trackOnlyInBrackets": "(Solo-Monitoraggio)", "searchableInBrackets": "(ricercabile)", "appsString": "App", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Rimuovere l'app?", "other": "Rimuovere le app?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 0119f5d..81d0e18 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -12,8 +12,6 @@ "ok": "OK", "and": "と", "githubPATLabel": "GitHub パーソナルアクセストークン (レート制限の引き上げ)", - "githubPATHint": "PATは次の形式でなければなりません: ユーザー名:トークン", - "githubPATFormat": "ユーザー名:トークン", "includePrereleases": "プレリリースを含む", "fallbackToOlderReleases": "旧リリースへのフォールバック", "filterReleaseTitlesByRegEx": "正規表現でリリースタイトルをフィルタリングする", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "検索 (一部ソースのみ)", "search": "検索", "additionalOptsFor": "{}の追加オプション", - "supportedSourcesBelow": "対応するソース:", + "supportedSources": "対応するソース", "trackOnlyInBrackets": "(追跡のみ)", "searchableInBrackets": "(検索可能)", "appsString": "アプリ", @@ -251,6 +249,8 @@ "exemptFromBackgroundUpdates": "バックグラウンドアップデートを行わない (有効な場合)", "bgUpdatesOnWiFiOnly": "WiFiを使用していない場合,バックグラウンドアップデートを無効にする", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/pl.json b/assets/translations/pl.json index f2495bf..bda97f1 100644 --- a/assets/translations/pl.json +++ b/assets/translations/pl.json @@ -22,8 +22,6 @@ "ok": "Okej", "and": "i", "githubPATLabel": "Osobisty token dostępu GitHub (zwiększa limit zapytań)", - "githubPATHint": "Wymagany format: użytkownik:token", - "githubPATFormat": "użytkownik:token", "includePrereleases": "Uwzględnij wersje wstępne", "fallbackToOlderReleases": "Powracaj do starszych wersji", "filterReleaseTitlesByRegEx": "Filtruj tytuły wydań wg. wyrażeń regularnych", @@ -52,7 +50,7 @@ "searchSomeSourcesLabel": "Szukaj (tylko niektóre źródła)", "search": "Szukaj", "additionalOptsFor": "Dodatkowe opcje dla {}", - "supportedSourcesBelow": "Obsługiwane źródła:", + "supportedSources": "Obsługiwane źródła", "trackOnlyInBrackets": "(tylko obserwowane)", "searchableInBrackets": "(Wyszukiwalne)", "appsString": "Aplikacje", @@ -256,6 +254,8 @@ "exemptFromBackgroundUpdates": "Wyklucz z uaktualnień w tle (jeśli są włączone)", "bgUpdatesOnWiFiOnly": "Wyłącz aktualizacje w tle, gdy nie ma połączenia z Wi-Fi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Usunąć aplikację?", "few": "Usunąć aplikacje?", diff --git a/assets/translations/ru.json b/assets/translations/ru.json index e53644f..98c167e 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -12,8 +12,6 @@ "ok": "Окей", "and": "и", "githubPATLabel": "Персональный токен доступа GitHub (увеличивает лимит запросов)", - "githubPATHint": "Токен доступа должен быть в формате: имя_пользователя:токен", - "githubPATFormat": "имя_пользователя:токен", "includePrereleases": "Включить предварительные релизы", "fallbackToOlderReleases": "Откатиться к более старым версиям", "filterReleaseTitlesByRegEx": "Фильтровать заголовки релизов\nс помощью регулярного выражения", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "Поиск (только в некоторых источниках)", "search": "Поиск", "additionalOptsFor": "Дополнительные опции для {}", - "supportedSourcesBelow": "Поддерживаемые источники:", + "supportedSources": "Поддерживаемые источники", "trackOnlyInBrackets": "(Только для отслеживания)", "searchableInBrackets": "(Поиск)", "appsString": "Приложения", @@ -250,6 +248,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "Удалить приложение?", "other": "Удалить приложения?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 0837f74..a3024e5 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -12,8 +12,6 @@ "ok": "好的", "and": "和", "githubPATLabel": "GitHub 个人访问令牌(提升 API 请求限额)", - "githubPATHint": "个人访问令牌必须为“username:token”的格式", - "githubPATFormat": "username:token", "includePrereleases": "包含预发行版", "fallbackToOlderReleases": "将旧发行版作为备选", "filterReleaseTitlesByRegEx": "使用正则表达式筛选发行标题", @@ -43,7 +41,7 @@ "searchSomeSourcesLabel": "搜索(仅支持部分来源)", "search": "搜索", "additionalOptsFor": "{} 的更多选项", - "supportedSourcesBelow": "支持的来源:", + "supportedSources": "支持的来源", "trackOnlyInBrackets": "(仅追踪)", "searchableInBrackets": "(可搜索)", "appsString": "应用列表", @@ -251,6 +249,8 @@ "exemptFromBackgroundUpdates": "Exempt from background updates (if enabled)", "bgUpdatesOnWiFiOnly": "Disable background updates when not on WiFi", "autoSelectHighestVersionCode": "Auto-select highest versionCode APK", + "versionExtractionRegEx": "Version Extraction RegEx", + "matchGroupToUse": "Match Group to Use", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/apkcombo.dart b/lib/app_sources/apkcombo.dart index c90b0cb..73c144b 100644 --- a/lib/app_sources/apkcombo.dart +++ b/lib/app_sources/apkcombo.dart @@ -25,12 +25,16 @@ class APKCombo extends AppSource { } @override - Map get requestHeaders => { - "User-Agent": "curl/8.0.1", - "Accept": "*/*", - "Connection": "keep-alive", - "Host": "$host" - }; + Future?> getRequestHeaders( + {Map additionalSettings = const {}, + bool forAPKDownload = false}) async { + return { + "User-Agent": "curl/8.0.1", + "Accept": "*/*", + "Connection": "keep-alive", + "Host": "$host" + }; + } Future>> getApkUrls(String standardUrl) async { var res = await sourceRequest('$standardUrl/download/apk'); diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index ff63d2b..15ca098 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -3,6 +3,21 @@ import 'package:html/parser.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 APKPure extends AppSource { APKPure() { host = 'apkpure.com'; @@ -47,17 +62,7 @@ class APKPure extends AppSource { } String? dateString = html.querySelector('span.info-other span.date')?.text.trim(); - DateTime? releaseDate; - try { - releaseDate = dateString != null - ? DateFormat('MMM dd, yyyy').parse(dateString) - : null; - releaseDate = dateString != null && releaseDate == null - ? DateFormat('MMMM dd, yyyy').parse(dateString) - : null; - } catch (err) { - // ignore - } + 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') @@ -70,11 +75,13 @@ class APKPure extends AppSource { Uri.parse(standardUrl).pathSegments.reversed.last; String appName = html.querySelector('h1.info-title')?.text.trim() ?? appId; - String? changeLog = htmlChangelog.querySelector("div.whats-new-info p:not(.date)")?.innerHtml - .trim().replaceAll("
", " \n"); + String? changeLog = htmlChangelog + .querySelector("div.whats-new-info p:not(.date)") + ?.innerHtml + .trim() + .replaceAll("
", " \n"); return APKDetails(version, apkUrls, AppNames(author, appName), - releaseDate: releaseDate, - changeLog: changeLog); + releaseDate: releaseDate, changeLog: changeLog); } else { throw getObtainiumHttpError(res); } diff --git a/lib/app_sources/aptoide.dart b/lib/app_sources/aptoide.dart index e3c5548..9543464 100644 --- a/lib/app_sources/aptoide.dart +++ b/lib/app_sources/aptoide.dart @@ -1,8 +1,6 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; -import 'package:html/parser.dart'; -import 'package:http/http.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/source_provider.dart'; @@ -75,34 +73,4 @@ class Aptoide extends AppSource { version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName), releaseDate: relDate); } - - @override - Future>> search(String query, - {Map querySettings = const {}}) async { - Response res = await sourceRequest( - 'https://search.$host/?q=${Uri.encodeQueryComponent(query)}'); - if (res.statusCode == 200) { - Map> urlsWithDescriptions = {}; - parse(res.body).querySelectorAll('.package-header').forEach((e) { - String? url = e.attributes['href']; - if (url != null) { - try { - standardizeUrl(url); - } catch (e) { - url = null; - } - } - if (url != null) { - urlsWithDescriptions[url] = [ - e.querySelector('.package-name')?.text.trim() ?? '', - e.querySelector('.package-summary')?.text.trim() ?? - tr('noDescription') - ]; - } - }); - return urlsWithDescriptions; - } else { - throw getObtainiumHttpError(res); - } - } } diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index 0b2f2c9..9c7cb73 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -1,4 +1,5 @@ import 'dart:convert'; +import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:http/http.dart'; @@ -21,20 +22,6 @@ class GitHub extends AppSource { label: tr('githubPATLabel'), password: true, required: false, - additionalValidators: [ - (value) { - if (value != null && value.trim().isNotEmpty) { - if (value - .split(':') - .where((element) => element.trim().isNotEmpty) - .length != - 2) { - return tr('githubPATHint'); - } - } - return null; - } - ], hint: tr('githubPATFormat'), belowWidgets: [ const SizedBox( @@ -169,26 +156,53 @@ class GitHub extends AppSource { return url.substring(0, match.end); } - Future getCredentialPrefixIfAny( - Map additionalSettings) async { + @override + Future?> getRequestHeaders( + {Map additionalSettings = const {}, + bool forAPKDownload = false}) async { + var token = await getTokenIfAny(additionalSettings); + var headers = {}; + if (token != null) { + headers[HttpHeaders.authorizationHeader] = 'Token $token'; + } + if (forAPKDownload == true) { + headers[HttpHeaders.acceptHeader] = 'application/octet-stream'; + } + if (headers.isNotEmpty) { + return headers; + } else { + return null; + } + } + + Future getTokenIfAny(Map additionalSettings) async { SettingsProvider settingsProvider = SettingsProvider(); await settingsProvider.initializeSettings(); var sourceConfig = await getSourceConfigValues(additionalSettings, settingsProvider); String? creds = sourceConfig['github-creds']; - return creds != null && creds.isNotEmpty ? '$creds@' : ''; + if (creds != null) { + var userNameEndIndex = creds.indexOf(':'); + if (userNameEndIndex > 0) { + creds = creds.substring( + userNameEndIndex + 1); // For old username-included token inputs + } + return creds; + } else { + return null; + } } @override Future getSourceNote() async { - if (!hostChanged && (await getCredentialPrefixIfAny({})).isEmpty) { + if (!hostChanged && (await getTokenIfAny({})) == null) { return '${tr('githubSourceNote')} ${hostChanged ? tr('addInfoBelow') : tr('addInfoInSettings')}'; } return null; } Future getAPIHost(Map additionalSettings) async => - 'https://${await getCredentialPrefixIfAny(additionalSettings)}api.$host'; + 'https://api.$host'; Future convertStandardUrlToAPIUrl( String standardUrl, Map additionalSettings) async => @@ -238,9 +252,8 @@ class GitHub extends AppSource { List> getReleaseAPKUrls(dynamic release) => (release['assets'] as List?) ?.map((e) { - return e['name'] != null && e['browser_download_url'] != null - ? MapEntry(e['name'] as String, - e['browser_download_url'] as String) + return e['name'] != null && e['url'] != null + ? MapEntry(e['name'] as String, e['url'] as String) : const MapEntry('', ''); }) .where((element) => element.key.toLowerCase().endsWith('.apk')) diff --git a/lib/app_sources/html.dart b/lib/app_sources/html.dart index 47dc5ff..a2a25cc 100644 --- a/lib/app_sources/html.dart +++ b/lib/app_sources/html.dart @@ -1,4 +1,5 @@ import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; import 'package:html/parser.dart'; import 'package:http/http.dart'; import 'package:obtainium/components/generated_form.dart'; @@ -109,6 +110,26 @@ class HTML extends AppSource { hint: '([0-9]+\.)*[0-9]+/\$', required: false, additionalValidators: [(value) => regExValidator(value)]) + ], + [ + GeneratedFormTextField('versionExtractionRegEx', + label: tr('versionExtractionRegEx'), + required: false, + additionalValidators: [(value) => regExValidator(value)]), + GeneratedFormTextField('matchGroupToUse', + label: tr('matchGroupToUse'), + required: false, + hint: '1', + textInputType: const TextInputType.numberWithOptions(), + additionalValidators: [ + (value) { + value ??= '1'; + if (int.tryParse(value) == null) { + return tr('invalidInput'); + } + return null; + } + ]) ] ]; overrideVersionDetectionFormDefault('noVersionDetection', @@ -116,11 +137,14 @@ class HTML extends AppSource { } @override - // TODO: implement requestHeaders choice, hardcoded for now - Map? get requestHeaders => { - "User-Agent": - "Mozilla/5.0 (Linux; Android 10; K) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Mobile Safari/537.36" - }; + 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" + }; + } @override String sourceSpecificStandardizeURL(String url) { @@ -180,10 +204,23 @@ class HTML extends AppSource { throw NoReleasesError(); } var rel = links.last; - var version = rel.hashCode.toString(); + String? version = rel.hashCode.toString(); + var versionExtractionRegEx = + additionalSettings['versionExtractionRegEx'] as String?; + if (versionExtractionRegEx?.isNotEmpty == true) { + var match = RegExp(versionExtractionRegEx!).allMatches(rel); + if (match.isEmpty) { + throw NoVersionError(); + } + version = match.last + .group(int.parse(additionalSettings['matchGroupToUse'] as String)); + if (version?.isEmpty == true) { + throw NoVersionError(); + } + } List apkUrls = [rel].map((e) => ensureAbsoluteUrl(e, uri)).toList(); - return APKDetails(version, apkUrls.map((e) => MapEntry(e, e)).toList(), + return APKDetails(version!, apkUrls.map((e) => MapEntry(e, e)).toList(), AppNames(uri.host, tr('app'))); } else { throw getObtainiumHttpError(res); diff --git a/lib/app_sources/uptodown.dart b/lib/app_sources/uptodown.dart new file mode 100644 index 0000000..dd9b68a --- /dev/null +++ b/lib/app_sources/uptodown.dart @@ -0,0 +1,82 @@ +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'; + +class Uptodown extends AppSource { + Uptodown() { + host = 'uptodown.com'; + allowSubDomains = true; + } + + @override + String sourceSpecificStandardizeURL(String url) { + RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host'); + RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + if (match == null) { + throw InvalidURLError(name); + } + return '${url.substring(0, match.end)}/android/download'; + } + + @override + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { + return (await getAppDetailsFromPage(standardUrl))['appId']; + } + + Future> getAppDetailsFromPage(String standardUrl) async { + var res = await sourceRequest(standardUrl); + if (res.statusCode != 200) { + throw getObtainiumHttpError(res); + } + var html = parse(res.body); + String? version = html.querySelector('div.version')?.innerHtml; + String? apkUrl = + html.querySelector('#detail-download-button')?.attributes['data-url']; + String? name = html.querySelector('#detail-app-name')?.innerHtml.trim(); + String? author = html.querySelector('#author-link')?.innerHtml.trim(); + var detailElements = html.querySelectorAll('#technical-information td'); + String? appId = (detailElements.elementAtOrNull(2))?.innerHtml.trim(); + String? dateStr = (detailElements.elementAtOrNull(29))?.innerHtml.trim(); + return Map.fromEntries([ + MapEntry('version', version), + MapEntry('apkUrl', apkUrl), + MapEntry('appId', appId), + MapEntry('name', name), + MapEntry('author', author), + MapEntry('dateStr', dateStr) + ]); + } + + @override + Future getLatestAPKDetails( + String standardUrl, + Map additionalSettings, + ) async { + var appDetails = await getAppDetailsFromPage(standardUrl); + var version = appDetails['version']; + var apkUrl = appDetails['apkUrl']; + var appId = appDetails['appId']; + if (version == null) { + throw NoVersionError(); + } + if (apkUrl == null) { + throw NoAPKError(); + } + if (appId == null) { + throw NoReleasesError(); + } + String appName = appDetails['name'] ?? tr('app'); + String author = appDetails['author'] ?? name; + String? dateStr = appDetails['dateStr']; + DateTime? relDate; + if (dateStr != null) { + relDate = parseDateTimeMMMddCommayyyy(dateStr); + } + return APKDetails( + version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName), + releaseDate: relDate); + } +} diff --git a/lib/app_sources/vlc.dart b/lib/app_sources/vlc.dart index 1c1adb8..091411a 100644 --- a/lib/app_sources/vlc.dart +++ b/lib/app_sources/vlc.dart @@ -12,7 +12,12 @@ class VLC extends AppSource { get dwUrlBase => 'https://get.$host/vlc-android/'; @override - Map? get requestHeaders => HTML().requestHeaders; + Future?> getRequestHeaders( + {Map additionalSettings = const {}, + bool forAPKDownload = false}) => + HTML().getRequestHeaders( + additionalSettings: additionalSettings, + forAPKDownload: forAPKDownload); @override String sourceSpecificStandardizeURL(String url) { diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 300b679..41e742f 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -25,6 +25,7 @@ class GeneratedFormTextField extends GeneratedFormItem { late int max; late String? hint; late bool password; + late TextInputType? textInputType; GeneratedFormTextField(String key, {String label = 'Input', @@ -34,7 +35,8 @@ class GeneratedFormTextField extends GeneratedFormItem { this.required = true, this.max = 1, this.hint, - this.password = false}) + this.password = false, + this.textInputType}) : super(key, label: label, belowWidgets: belowWidgets, @@ -144,7 +146,8 @@ Color generateRandomLightColor() { // Map from HPLuv color space to RGB, use constant saturation=100, lightness=70 final List rgbValuesDbl = Hsluv.hpluvToRgb([hue, 100, 70]); // Map RBG values from 0-1 to 0-255: - final List rgbValues = rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList(); + final List rgbValues = + rgbValuesDbl.map((rgb) => (rgb * 255).toInt()).toList(); return Color.fromARGB(255, rgbValues[0], rgbValues[1], rgbValues[2]); } @@ -190,6 +193,7 @@ class _GeneratedFormState extends State { if (formItem is GeneratedFormTextField) { final formFieldKey = GlobalKey(); return TextFormField( + keyboardType: formItem.textInputType, obscureText: formItem.password, autocorrect: !formItem.password, enableSuggestions: !formItem.password, @@ -370,34 +374,37 @@ class _GeneratedFormState extends State { }) ?? [const SizedBox.shrink()], (values[widget.items[r][e].key] - as Map>?) - ?.values - .where((e) => e.value) - .length == 1 + as Map>?) + ?.values + .where((e) => e.value) + .length == + 1 ? Padding( - padding: const EdgeInsets.symmetric(horizontal: 4), - child: IconButton( - onPressed: () { - setState(() { - var temp = values[widget.items[r][e].key] - as Map>; - // get selected category str where bool is true - final oldEntry = temp.entries.firstWhere((entry) => entry.value.value); - // generate new color, ensure it is not the same - int newColor = oldEntry.value.key; - while(oldEntry.value.key == newColor) { - newColor = generateRandomLightColor().value; - } - // Update entry with new color, remain selected - temp.update(oldEntry.key, (old) => MapEntry(newColor, old.value)); - values[widget.items[r][e].key] = temp; - someValueChanged(); - }); - }, - icon: const Icon(Icons.format_color_fill_rounded), - visualDensity: VisualDensity.compact, - tooltip: tr('colour'), - )) + padding: const EdgeInsets.symmetric(horizontal: 4), + child: IconButton( + onPressed: () { + setState(() { + var temp = values[widget.items[r][e].key] + as Map>; + // get selected category str where bool is true + final oldEntry = temp.entries + .firstWhere((entry) => entry.value.value); + // generate new color, ensure it is not the same + int newColor = oldEntry.value.key; + while (oldEntry.value.key == newColor) { + newColor = generateRandomLightColor().value; + } + // Update entry with new color, remain selected + temp.update(oldEntry.key, + (old) => MapEntry(newColor, old.value)); + values[widget.items[r][e].key] = temp; + someValueChanged(); + }); + }, + icon: const Icon(Icons.format_color_fill_rounded), + visualDensity: VisualDensity.compact, + tooltip: tr('colour'), + )) : const SizedBox.shrink(), (values[widget.items[r][e].key] as Map>?) diff --git a/lib/main.dart b/lib/main.dart index 91d86b5..8b21863 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -19,7 +19,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.14.10'; +const String currentVersion = '0.14.11'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/mass_app_sources/githubstars.dart b/lib/mass_app_sources/githubstars.dart index db8495b..b32cc77 100644 --- a/lib/mass_app_sources/githubstars.dart +++ b/lib/mass_app_sources/githubstars.dart @@ -15,8 +15,10 @@ class GitHubStars implements MassAppUrlSource { Future>> getOnePageOfUserStarredUrlsWithDescriptions( String username, int page) async { - Response res = await get(Uri.parse( - 'https://${await GitHub().getCredentialPrefixIfAny({})}api.github.com/users/$username/starred?per_page=100&page=$page')); + Response res = await get( + Uri.parse( + 'https://api.github.com/users/$username/starred?per_page=100&page=$page'), + headers: await GitHub().getRequestHeaders()); if (res.statusCode == 200) { Map> urlsWithDescriptions = {}; for (var e in (jsonDecode(res.body) as List)) { diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index 0f8095c..ee8099e 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -11,6 +11,7 @@ import 'package:obtainium/pages/app.dart'; import 'package:obtainium/pages/import_export.dart'; import 'package:obtainium/pages/settings.dart'; import 'package:obtainium/providers/apps_provider.dart'; +import 'package:obtainium/providers/notifications_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/source_provider.dart'; import 'package:provider/provider.dart'; @@ -42,6 +43,8 @@ class _AddAppPageState extends State { Widget build(BuildContext context) { AppsProvider appsProvider = context.read(); SettingsProvider settingsProvider = context.watch(); + NotificationsProvider notificationsProvider = + context.read(); bool doingSomething = gettingAppInfo || searching; @@ -161,7 +164,8 @@ class _AddAppPageState extends State { app.apkUrls.map((e) => e.value).toList().indexOf(apkUrl.value); // ignore: use_build_context_synchronously var downloadedArtifact = await appsProvider.downloadApp( - app, globalNavigatorKey.currentContext); + app, globalNavigatorKey.currentContext, + notificationsProvider: notificationsProvider); DownloadedApk? downloadedFile; DownloadedXApkDir? downloadedDir; if (downloadedArtifact is DownloadedApk) { @@ -459,14 +463,12 @@ class _AddAppPageState extends State { crossAxisAlignment: CrossAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center, children: [ - const SizedBox( - height: 48, - ), Text( - tr('supportedSourcesBelow'), + tr('supportedSources'), + style: const TextStyle(fontWeight: FontWeight.bold), ), const SizedBox( - height: 8, + height: 16, ), ...sourceProvider.sources .map((e) => GestureDetector( @@ -520,15 +522,17 @@ class _AddAppPageState extends State { : const SizedBox(); }, future: pickedSource?.getSourceNote()), - const SizedBox( - height: 16, + SizedBox( + height: pickedSource != null ? 16 : 96, ), - if (pickedSource != null) - getAdditionalOptsCol() - else - getSourcesListWidget(), - const SizedBox( - height: 8, + if (pickedSource != null) getAdditionalOptsCol(), + if (pickedSource == null) + const Divider( + height: 48, + ), + if (pickedSource == null) getSourcesListWidget(), + SizedBox( + height: pickedSource != null ? 8 : 2, ), ])), ) diff --git a/lib/pages/apps.dart b/lib/pages/apps.dart index 835be74..ebbc272 100644 --- a/lib/pages/apps.dart +++ b/lib/pages/apps.dart @@ -449,33 +449,40 @@ class AppsPageState extends State { : const SizedBox.shrink(), GestureDetector( onTap: showChangesFn, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Row(mainAxisSize: MainAxisSize.min, children: [ - Container( - constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width / 4), - child: Text(getVersionText(index), - overflow: TextOverflow.ellipsis, - textAlign: TextAlign.end)), - ]), - Row( - mainAxisSize: MainAxisSize.min, + child: Container( + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(12), + color: Theme.of(context).primaryColor.withAlpha(20)), + padding: const EdgeInsets.fromLTRB(12, 0, 12, 0), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.end, children: [ - Text( - getChangesButtonString(index, showChangesFn != null), - style: TextStyle( - fontStyle: FontStyle.italic, - decoration: showChangesFn != null - ? TextDecoration.underline - : TextDecoration.none), - ) + Row(mainAxisSize: MainAxisSize.min, children: [ + Container( + constraints: BoxConstraints( + maxWidth: + MediaQuery.of(context).size.width / 4), + child: Text(getVersionText(index), + overflow: TextOverflow.ellipsis, + textAlign: TextAlign.end)), + ]), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + getChangesButtonString( + index, showChangesFn != null), + style: TextStyle( + fontStyle: FontStyle.italic, + decoration: showChangesFn != null + ? TextDecoration.underline + : TextDecoration.none), + ) + ], + ), ], - ), - ], - )) + ))) ], ); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index cad3b2f..208b222 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; import 'dart:math'; +import 'package:http/http.dart' as http; import 'package:android_alarm_manager_plus/android_alarm_manager_plus.dart'; import 'package:android_intent_plus/flag.dart'; @@ -215,7 +216,7 @@ class AppsProvider with ChangeNotifier { if (headers != null) { req.headers.addAll(headers); } - var client = Client(); + var client = http.Client(); StreamedResponse response = await client.send(req); String ext = response.headers['content-disposition']?.split('.').last ?? 'apk'; @@ -298,9 +299,11 @@ class AppsProvider with ChangeNotifier { notificationsProvider?.cancel(notif.id); int? prevProg; var fileNameNoExt = '${app.id}-${downloadUrl.hashCode}'; + var headers = await source.getRequestHeaders( + additionalSettings: app.additionalSettings, forAPKDownload: true); var downloadedFile = await downloadFileWithRetry( - downloadUrl, fileNameNoExt, headers: source.requestHeaders, - (double? progress) { + downloadUrl, fileNameNoExt, + headers: headers, (double? progress) { int? prog = progress?.ceil(); if (apps[app.id] != null) { apps[app.id]!.downloadProgress = progress; diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index c9433b7..3c06be2 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -26,6 +26,7 @@ import 'package:obtainium/app_sources/sourceforge.dart'; import 'package:obtainium/app_sources/sourcehut.dart'; import 'package:obtainium/app_sources/steammobile.dart'; import 'package:obtainium/app_sources/telegramapp.dart'; +import 'package:obtainium/app_sources/uptodown.dart'; import 'package:obtainium/app_sources/vlc.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; @@ -363,15 +364,23 @@ abstract class AppSource { return url; } - Map? get requestHeaders => null; + Future?> getRequestHeaders( + {Map additionalSettings = const {}, + bool forAPKDownload = false}) async { + return null; + } Future sourceRequest(String url, - {bool followRedirects = true}) async { + {bool followRedirects = true, + Map additionalSettings = + const {}}) async { + var requestHeaders = + await getRequestHeaders(additionalSettings: additionalSettings); if (requestHeaders != null || followRedirects == false) { var req = Request('GET', Uri.parse(url)); req.followRedirects = followRedirects; if (requestHeaders != null) { - req.headers.addAll(requestHeaders!); + req.headers.addAll(requestHeaders); } return Response.fromStream(await Client().send(req)); } else { @@ -519,15 +528,16 @@ class SourceProvider { GitLab(), Codeberg(), FDroid(), - IzzyOnDroid(), FDroidRepo(), - Jenkins(), + IzzyOnDroid(), SourceForge(), SourceHut(), - Aptoide(), - APKMirror(), APKPure(), + Aptoide(), + Uptodown(), + APKMirror(), HuaweiAppGallery(), + Jenkins(), // APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden) Mullvad(), Signal(), diff --git a/pubspec.lock b/pubspec.lock index c394bd0..04ca07a 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -538,18 +538,18 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: "63e5216aae014a72fe9579ccd027323395ce7a98271d9defa9d57320d001af81" + sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 url: "https://pub.dev" source: hosted - version: "10.4.3" + version: "10.4.5" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: d74e77a5ecd38649905db0a7d05ef16bed42ff263b9efb73ed794317c5764ec3 + sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" url: "https://pub.dev" source: hosted - version: "10.3.4" + version: "10.3.6" permission_handler_apple: dependency: transitive description: @@ -562,10 +562,10 @@ packages: dependency: transitive description: name: permission_handler_platform_interface - sha256: "7c6b1500385dd1d2ca61bb89e2488ca178e274a69144d26bbd65e33eae7c02a9" + sha256: f2343e9fa9c22ae4fd92d4732755bfe452214e7189afcc097380950cf567b4b2 url: "https://pub.dev" source: hosted - version: "3.11.3" + version: "3.11.5" permission_handler_windows: dependency: transitive description: @@ -879,18 +879,18 @@ packages: dependency: transitive description: name: webview_flutter_android - sha256: "0d8f5ac96a155e672129bf94c7abf625de01241d44d269dbaff083f1b4deb1aa" + sha256: "9427774649fd3c8b7ff53523051395d13aed2ca355822b822e6493d79f5fc05a" url: "https://pub.dev" source: hosted - version: "3.9.5" + version: "3.10.0" webview_flutter_platform_interface: dependency: transitive description: name: webview_flutter_platform_interface - sha256: "9d32a63a5ee111b37482cb3eac3379b9f0992afd27a52ee30279dbf06f41918b" + sha256: "6d9213c65f1060116757a7c473247c60f3f7f332cac33dc417c9e362a9a13e4f" url: "https://pub.dev" source: hosted - version: "2.5.1" + version: "2.6.0" webview_flutter_wkwebview: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index f643b70..d3c80c3 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,10 +17,10 @@ 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.14.10+202 # When changing this, update the tag in main() accordingly +version: 0.14.11+203 # When changing this, update the tag in main() accordingly environment: - sdk: '>=2.18.2 <3.0.0' + sdk: '>=3.0.0 <4.0.0' # Dependencies specify other packages that your package needs in order to work. # To automatically upgrade your package dependencies to the latest versions