mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-12 21:06:43 +02:00
Merge pull request #2305 from bernikr/apkpure-api
Rewrite APKPure Source to use the mobile API instead of web scraping
This commit is contained in:
@ -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",
|
||||
|
@ -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ó",
|
||||
|
@ -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í",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "ソート方法",
|
||||
|
@ -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": "정렬 방법",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "Метод сортировки",
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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": "Метод сортування",
|
||||
|
@ -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",
|
||||
|
@ -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": "排序方式",
|
||||
|
@ -318,6 +318,7 @@
|
||||
"crowdsourcedConfigsShort": "众包应用程序配置",
|
||||
"allowInsecure": "允许不安全的 HTTP 请求",
|
||||
"stayOneVersionBehind": "比最新版本晚一个版本",
|
||||
"useFirstApkOfVersion": "Auto-select first of multiple APKs",
|
||||
"refreshBeforeDownload": "下载前刷新应用程序详细信息",
|
||||
"tencentAppStore": "腾讯应用宝",
|
||||
"coolApk": "酷安",
|
||||
|
@ -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<E, Id> on List<E> {
|
||||
List<E> unique([Id Function(E element)? id, bool inplace = true]) {
|
||||
final ids = Set();
|
||||
var list = inplace ? this : List<E>.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('useFirstApkOfVersion',
|
||||
label: tr('useFirstApkOfVersion'), defaultValue: true)
|
||||
]
|
||||
];
|
||||
}
|
||||
@ -65,109 +63,73 @@ class APKPure extends AppSource {
|
||||
return Uri.parse(standardUrl).pathSegments.last;
|
||||
}
|
||||
|
||||
getDetailsForVersionLink(
|
||||
String standardUrl,
|
||||
String appId,
|
||||
String host,
|
||||
getDetailsForVersion(
|
||||
List<Map<String, dynamic>> versionVariants,
|
||||
List<String> supportedArchs,
|
||||
String link,
|
||||
Map<String, dynamic> additionalSettings) async {
|
||||
var res = await sourceRequest(link, additionalSettings);
|
||||
if (res.statusCode == 200) {
|
||||
var html = parse(res.body);
|
||||
var apksDiv =
|
||||
html.querySelector('#version-list div div.show-more-content');
|
||||
DateTime? topReleaseDate;
|
||||
var apkUrls = apksDiv
|
||||
?.querySelectorAll('div.group-title')
|
||||
.map((e) {
|
||||
String architectureString = e.text.trim();
|
||||
if (architectureString.toLowerCase() == 'unlimited' ||
|
||||
architectureString.toLowerCase() == 'universal') {
|
||||
architectureString = '';
|
||||
}
|
||||
List<String> architectures = architectureString
|
||||
.split(',')
|
||||
.map((e) => e.trim())
|
||||
.where((e) => e.isNotEmpty)
|
||||
.toList();
|
||||
// Only take the first APK for each architecture, ignore others for now, for simplicity
|
||||
// Unclear why there can even be multiple APKs for the same version and arch
|
||||
var apkInfo = e.nextElementSibling?.querySelector('div.info');
|
||||
String? versionCode = RegExp('[0-9]+')
|
||||
.firstMatch(
|
||||
apkInfo?.querySelector('div.info-top .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("<br>", " \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<String> architectures = e['native_code']?.cast<String>();
|
||||
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) {
|
||||
return null;
|
||||
}
|
||||
|
||||
String type = e['asset']['type'];
|
||||
String downloadUri = e['asset']['url'];
|
||||
|
||||
return MapEntry(
|
||||
'$appId-$versionCode-$architectureString.${type.toLowerCase()}',
|
||||
downloadUri);
|
||||
})
|
||||
.nonNulls
|
||||
.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'];
|
||||
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['useFirstApkOfVersion'] == true) {
|
||||
apkUrls = [apkUrls.first];
|
||||
}
|
||||
|
||||
return APKDetails(version, apkUrls, AppNames(author, appName),
|
||||
releaseDate: releaseDate, changeLog: changeLog);
|
||||
}
|
||||
|
||||
@override
|
||||
Future<Map<String, String>?> getRequestHeaders(
|
||||
Map<String, dynamic> 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 +139,50 @@ class APKPure extends AppSource {
|
||||
Map<String, dynamic> 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<String> 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<Map<String, dynamic>> apks =
|
||||
jsonDecode(res.body)['version_list'].cast<Map<String, dynamic>>();
|
||||
|
||||
var supportedArchs = (await DeviceInfoPlugin().androidInfo).supportedAbis;
|
||||
// group by version
|
||||
List<List<Map<String, dynamic>>> versions = apks
|
||||
.fold<Map<String, List<Map<String, dynamic>>>>({},
|
||||
(Map<String, List<Map<String, dynamic>>> val,
|
||||
Map<String, dynamic> 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) {
|
||||
if (versions.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;
|
||||
}
|
||||
}
|
||||
|
@ -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'];
|
||||
|
Reference in New Issue
Block a user