From f61824ff0d67c0685b2f390087bda30b3f354b91 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Sat, 19 Aug 2023 00:19:52 -0400 Subject: [PATCH] Add Huawei AppGallery (#756) --- README.md | 1 + lib/app_sources/huaweiappgallery.dart | 90 +++++++++++++++++++++++++++ lib/providers/source_provider.dart | 12 +++- 3 files changed, 100 insertions(+), 3 deletions(-) create mode 100644 lib/app_sources/huaweiappgallery.dart diff --git a/README.md b/README.md index 47ece7c..d0c3f1a 100644 --- a/README.md +++ b/README.md @@ -18,6 +18,7 @@ Currently supported App sources: - [SourceHut](https://git.sr.ht/) - [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) diff --git a/lib/app_sources/huaweiappgallery.dart b/lib/app_sources/huaweiappgallery.dart new file mode 100644 index 0000000..1a97774 --- /dev/null +++ b/lib/app_sources/huaweiappgallery.dart @@ -0,0 +1,90 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:http/http.dart'; +import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/providers/source_provider.dart'; + +class HuaweiAppGallery extends AppSource { + HuaweiAppGallery() { + name = 'Huawei AppGallery'; + host = 'appgallery.huawei.com'; + overrideVersionDetectionFormDefault('releaseDateAsVersion', true); + } + + @override + String sourceSpecificStandardizeURL(String url) { + RegExp standardUrlRegEx = RegExp('^https?://$host/app/[^/]+'); + RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); + if (match == null) { + throw InvalidURLError(name); + } + return url.substring(0, match.end); + } + + getDlUrl(String standardUrl) => + 'https://${host!.replaceAll('appgallery.', 'appgallery.cloud.')}/appdl/${standardUrl.split('/').last}'; + + requestAppdlRedirect(String dlUrl) async { + Response res = await sourceRequest(dlUrl, followRedirects: false); + if (res.statusCode == 200 || + res.statusCode == 302 || + res.statusCode == 304) { + return res; + } else { + throw getObtainiumHttpError(res); + } + } + + appIdFromRedirectDlUrl(String redirectDlUrl) { + var parts = redirectDlUrl + .split('?')[0] + .split('/') + .last + .split('.') + .reversed + .toList(); + parts.removeAt(0); + parts.removeAt(0); + return parts.reversed.join('.'); + } + + @override + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { + String dlUrl = getDlUrl(standardUrl); + Response res = await requestAppdlRedirect(dlUrl); + return res.headers['location'] != null + ? appIdFromRedirectDlUrl(res.headers['location']!) + : null; + } + + @override + Future getLatestAPKDetails( + String standardUrl, + Map additionalSettings, + ) async { + String dlUrl = getDlUrl(standardUrl); + Response res = await requestAppdlRedirect(dlUrl); + if (res.headers['location'] == null) { + throw NoReleasesError(); + } + String appId = appIdFromRedirectDlUrl(res.headers['location']!); + var relDateStr = + res.headers['location']?.split('?')[0].split('.').reversed.toList()[1]; + var relDateStrAdj = relDateStr?.split(''); + var tempLen = relDateStrAdj?.length ?? 0; + var i = 2; + while (i < tempLen) { + relDateStrAdj?.insert((i + i ~/ 2 - 1), '-'); + i += 2; + } + var relDate = relDateStrAdj == null + ? null + : DateFormat('yy-MM-dd-HH-mm').parse(relDateStrAdj.join('')); + if (relDateStr == null) { + throw NoVersionError(); + } + return APKDetails( + relDateStr, [MapEntry('$appId.apk', dlUrl)], AppNames(name, appId), + releaseDate: relDate); + } +} diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index da88640..1fff134 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -14,6 +14,7 @@ import 'package:obtainium/app_sources/fdroid.dart'; import 'package:obtainium/app_sources/fdroidrepo.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/gitlab.dart'; +import 'package:obtainium/app_sources/huaweiappgallery.dart'; import 'package:obtainium/app_sources/izzyondroid.dart'; import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/app_sources/jenkins.dart'; @@ -355,10 +356,14 @@ abstract class AppSource { Map? get requestHeaders => null; - Future sourceRequest(String url) async { - if (requestHeaders != null) { + Future sourceRequest(String url, + {bool followRedirects = true}) async { + if (requestHeaders != null || followRedirects == false) { var req = Request('GET', Uri.parse(url)); - req.headers.addAll(requestHeaders!); + req.followRedirects = followRedirects; + if (requestHeaders != null) { + req.headers.addAll(requestHeaders!); + } return Response.fromStream(await Client().send(req)); } else { return get(Uri.parse(url)); @@ -508,6 +513,7 @@ class SourceProvider { SourceHut(), APKMirror(), APKPure(), + HuaweiAppGallery(), // APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden) Mullvad(), Signal(),