From cbc840378c992c1db1a6186853c918fb7b004d66 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 9 Sep 2023 05:31:04 -0400 Subject: [PATCH] Add Uptodown (#853) --- README.md | 47 +++++++++-------- lib/app_sources/apkpure.dart | 37 ++++++++------ lib/app_sources/aptoide.dart | 32 ------------ lib/app_sources/uptodown.dart | 82 ++++++++++++++++++++++++++++++ lib/providers/source_provider.dart | 10 ++-- pubspec.yaml | 2 +- 6 files changed, 137 insertions(+), 73 deletions(-) create mode 100644 lib/app_sources/uptodown.dart 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/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/uptodown.dart b/lib/app_sources/uptodown.dart new file mode 100644 index 0000000..4075ee1 --- /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; + String? author = html.querySelector('#author-link')?.innerHtml; + var detailElements = html.querySelectorAll('#technical-information td'); + String? appId = (detailElements.elementAtOrNull(2))?.innerHtml; + String? dateStr = (detailElements.elementAtOrNull(29))?.innerHtml; + 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/providers/source_provider.dart b/lib/providers/source_provider.dart index df21e57..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'; @@ -527,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.yaml b/pubspec.yaml index f643b70..8ed048c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 0.14.10+202 # 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