From 849a5651773014c5f2d33645a777fec6e000f088 Mon Sep 17 00:00:00 2001 From: iKirby <6316115+iKirby@users.noreply.github.com> Date: Tue, 29 Apr 2025 17:01:11 +0800 Subject: [PATCH 1/5] Add support for vivo App Store --- README.md | 1 + assets/translations/en.json | 1 + assets/translations/zh.json | 1 + .../android/en-US/full_description.txt | 1 + .../metadata/android/ru/full_description.txt | 1 + lib/app_sources/vivoappstore.dart | 105 ++++++++++++++++++ lib/providers/source_provider.dart | 2 + 7 files changed, 112 insertions(+) create mode 100644 lib/app_sources/vivoappstore.dart diff --git a/README.md b/README.md index 6518041..686b646 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,7 @@ Currently supported App sources: - [Huawei AppGallery](https://appgallery.huawei.com/) - [Tencent App Store](https://sj.qq.com/) - [CoolApk](https://coolapk.com/) + - [vivo App Store](https://h5.appstore.vivo.com.cn/) - [RuStore](https://rustore.ru/) - Jenkins Jobs - [APKMirror](https://apkmirror.com/) (Track-Only) diff --git a/assets/translations/en.json b/assets/translations/en.json index acc31b7..d5c431e 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -321,6 +321,7 @@ "refreshBeforeDownload": "Refresh app details before download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", + "vivoAppStore": "vivo App Store", "name": "Name", "smartname": "Name (smart)", "sortMethod": "Sort method", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 4e617a0..bb36a17 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -321,6 +321,7 @@ "refreshBeforeDownload": "下载前刷新应用程序详细信息", "tencentAppStore": "腾讯应用宝", "coolApk": "酷安", + "vivoAppStore": "vivo 应用商店", "name": "名称", "smartname": "姓名(智能)", "sortMethod": "排序方法", diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index d620444..7fed3cf 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -26,6 +26,7 @@
  • Huawei AppGallery
  • Tencent App Store
  • CoolApk
  • +
  • vivo App Store
  • Jenkins Jobs
  • RuStore
  • diff --git a/fastlane/metadata/android/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt index 7249abd..d67a272 100644 --- a/fastlane/metadata/android/ru/full_description.txt +++ b/fastlane/metadata/android/ru/full_description.txt @@ -26,6 +26,7 @@
  • Huawei AppGallery
  • Tencent App Store
  • CoolApk
  • +
  • vivo App Store
  • Jenkins Jobs
  • RuStore
  • diff --git a/lib/app_sources/vivoappstore.dart b/lib/app_sources/vivoappstore.dart new file mode 100644 index 0000000..6006746 --- /dev/null +++ b/lib/app_sources/vivoappstore.dart @@ -0,0 +1,105 @@ +import 'dart:convert'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/providers/source_provider.dart'; + +class VivoAppStore extends AppSource { + static const appDetailUrl = + 'https://h5coml.vivo.com.cn/h5coml/appdetail_h5/browser_v2/index.html?appId='; + + VivoAppStore() { + name = tr('vivoAppStore'); + hosts = ['h5.appstore.vivo.com.cn', 'h5coml.vivo.com.cn']; + naiveStandardVersionDetection = true; + canSearch = true; + } + + @override + String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { + var vivoAppId = + Uri.parse(url.replaceAll('/#', '')).queryParameters['appId']; + if (vivoAppId == null) { + throw InvalidURLError(name); + } + return '$appDetailUrl$vivoAppId'; + } + + @override + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { + var json = await getDetailJson(standardUrl, additionalSettings); + return json['package_name']; + } + + @override + Future getLatestAPKDetails( + String standardUrl, Map additionalSettings) async { + var json = await getDetailJson(standardUrl, additionalSettings); + var appName = json['title_zh'].toString(); + var packageName = json['package_name'].toString(); + var versionName = json['version_name'].toString(); + var versionCode = json['version_code'].toString(); + var developer = json['developer'].toString(); + var uploadTime = json['upload_time'].toString(); + var apkUrl = json['download_url'].toString(); + var apkName = '${packageName}_$versionCode.apk'; + return APKDetails( + versionName, [MapEntry(apkName, apkUrl)], AppNames(developer, appName), + releaseDate: DateTime.parse(uploadTime)); + } + + @override + Future?> getRequestHeaders( + Map additionalSettings, + {bool forAPKDownload = false}) async { + return { + 'User-Agent': + 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' + }; + } + + @override + Future>> search(String query, + {Map querySettings = const {}}) async { + var apiBaseUrl = + 'https://h5-api.appstore.vivo.com.cn/h5appstore/search/result-list?app_version=2100&page_index=1&apps_per_page=20&target=local&cfrom=2&key='; + var searchUrl = '$apiBaseUrl${Uri.encodeQueryComponent(query)}'; + var response = await sourceRequest(searchUrl, {}); + if (response.statusCode != 200) { + throw getObtainiumHttpError(response); + } + var json = jsonDecode(response.body); + if (json['code'] != 0 || !json['data']['appSearchResponse']['result']) { + throw NoReleasesError(); + } + Map> results = {}; + var resultsJson = json['data']['appSearchResponse']['value']; + for (var item in (resultsJson as List)) { + results['$appDetailUrl${item['id']}'] = [ + item['title_zh'].toString(), + item['developer'].toString() + ]; + } + return results; + } + + Future> getDetailJson( + String standardUrl, Map additionalSettings) async { + var vivoAppId = Uri.parse(standardUrl).queryParameters['appId']; + var apiBaseUrl = 'https://h5-api.appstore.vivo.com.cn/detail/'; + var params = '?frompage=messageh5&app_version=2100'; + + var detailUrl = '$apiBaseUrl$vivoAppId$params'; + var response = await sourceRequest(detailUrl, additionalSettings); + if (response.statusCode != 200) { + throw getObtainiumHttpError(response); + } + + var json = jsonDecode(response.body); + if (json['id'] == null) { + throw NoReleasesError(); + } + return json; + } +} diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 72bc931..dfa423d 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -31,6 +31,7 @@ import 'package:obtainium/app_sources/sourcehut.dart'; import 'package:obtainium/app_sources/telegramapp.dart'; import 'package:obtainium/app_sources/tencent.dart'; import 'package:obtainium/app_sources/uptodown.dart'; +import 'package:obtainium/app_sources/vivoappstore.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/mass_app_sources/githubstars.dart'; @@ -954,6 +955,7 @@ class SourceProvider { HuaweiAppGallery(), Tencent(), CoolApk(), + VivoAppStore(), Jenkins(), APKMirror(), RuStore(), From 8d062f029605c4288358619e0fbd1233e07e5ff9 Mon Sep 17 00:00:00 2001 From: iKirby <6316115+iKirby@users.noreply.github.com> Date: Tue, 6 May 2025 15:52:14 +0800 Subject: [PATCH 2/5] Remove unnecessary custom header --- lib/app_sources/vivoappstore.dart | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/lib/app_sources/vivoappstore.dart b/lib/app_sources/vivoappstore.dart index 6006746..b68b694 100644 --- a/lib/app_sources/vivoappstore.dart +++ b/lib/app_sources/vivoappstore.dart @@ -49,16 +49,6 @@ class VivoAppStore extends AppSource { releaseDate: DateTime.parse(uploadTime)); } - @override - Future?> getRequestHeaders( - Map additionalSettings, - {bool forAPKDownload = false}) async { - return { - 'User-Agent': - 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36' - }; - } - @override Future>> search(String query, {Map querySettings = const {}}) async { From 7a7a6f78e98da9faf304d5433b3ea2aea302c869 Mon Sep 17 00:00:00 2001 From: iKirby <6316115+iKirby@users.noreply.github.com> Date: Tue, 6 May 2025 16:04:03 +0800 Subject: [PATCH 3/5] Indicate vivo App Store is for CN site only --- README.md | 2 +- assets/translations/en.json | 2 +- assets/translations/zh.json | 2 +- fastlane/metadata/android/en-US/full_description.txt | 2 +- fastlane/metadata/android/ru/full_description.txt | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 686b646..b0b3bd9 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ Currently supported App sources: - [Huawei AppGallery](https://appgallery.huawei.com/) - [Tencent App Store](https://sj.qq.com/) - [CoolApk](https://coolapk.com/) - - [vivo App Store](https://h5.appstore.vivo.com.cn/) + - [vivo App Store (CN)](https://h5.appstore.vivo.com.cn/) - [RuStore](https://rustore.ru/) - Jenkins Jobs - [APKMirror](https://apkmirror.com/) (Track-Only) diff --git a/assets/translations/en.json b/assets/translations/en.json index d5c431e..9ea7e44 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -321,7 +321,7 @@ "refreshBeforeDownload": "Refresh app details before download", "tencentAppStore": "Tencent App Store", "coolApk": "CoolApk", - "vivoAppStore": "vivo App Store", + "vivoAppStore": "vivo App Store (CN)", "name": "Name", "smartname": "Name (smart)", "sortMethod": "Sort method", diff --git a/assets/translations/zh.json b/assets/translations/zh.json index bb36a17..33e3782 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -321,7 +321,7 @@ "refreshBeforeDownload": "下载前刷新应用程序详细信息", "tencentAppStore": "腾讯应用宝", "coolApk": "酷安", - "vivoAppStore": "vivo 应用商店", + "vivoAppStore": "vivo 应用商店(中国)", "name": "名称", "smartname": "姓名(智能)", "sortMethod": "排序方法", diff --git a/fastlane/metadata/android/en-US/full_description.txt b/fastlane/metadata/android/en-US/full_description.txt index 7fed3cf..ecdd345 100644 --- a/fastlane/metadata/android/en-US/full_description.txt +++ b/fastlane/metadata/android/en-US/full_description.txt @@ -26,7 +26,7 @@
  • Huawei AppGallery
  • Tencent App Store
  • CoolApk
  • -
  • vivo App Store
  • +
  • vivo App Store (CN)
  • Jenkins Jobs
  • RuStore
  • diff --git a/fastlane/metadata/android/ru/full_description.txt b/fastlane/metadata/android/ru/full_description.txt index d67a272..61e9331 100644 --- a/fastlane/metadata/android/ru/full_description.txt +++ b/fastlane/metadata/android/ru/full_description.txt @@ -26,7 +26,7 @@
  • Huawei AppGallery
  • Tencent App Store
  • CoolApk
  • -
  • vivo App Store
  • +
  • vivo App Store (CN)
  • Jenkins Jobs
  • RuStore
  • From 26bca6df64cc686e968880e578c9fbc3b2adc45c Mon Sep 17 00:00:00 2001 From: iKirby <6316115+iKirby@users.noreply.github.com> Date: Tue, 6 May 2025 16:18:31 +0800 Subject: [PATCH 4/5] Fix parsing vivo app id from URL when using source override --- lib/app_sources/vivoappstore.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/app_sources/vivoappstore.dart b/lib/app_sources/vivoappstore.dart index b68b694..fbd512e 100644 --- a/lib/app_sources/vivoappstore.dart +++ b/lib/app_sources/vivoappstore.dart @@ -17,11 +17,7 @@ class VivoAppStore extends AppSource { @override String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { - var vivoAppId = - Uri.parse(url.replaceAll('/#', '')).queryParameters['appId']; - if (vivoAppId == null) { - throw InvalidURLError(name); - } + var vivoAppId = parseVivoAppId(url); return '$appDetailUrl$vivoAppId'; } @@ -76,20 +72,26 @@ class VivoAppStore extends AppSource { Future> getDetailJson( String standardUrl, Map additionalSettings) async { - var vivoAppId = Uri.parse(standardUrl).queryParameters['appId']; + var vivoAppId = parseVivoAppId(standardUrl); var apiBaseUrl = 'https://h5-api.appstore.vivo.com.cn/detail/'; var params = '?frompage=messageh5&app_version=2100'; - var detailUrl = '$apiBaseUrl$vivoAppId$params'; var response = await sourceRequest(detailUrl, additionalSettings); if (response.statusCode != 200) { throw getObtainiumHttpError(response); } - var json = jsonDecode(response.body); if (json['id'] == null) { throw NoReleasesError(); } return json; } + + String parseVivoAppId(String url) { + var appId = Uri.parse(url.replaceAll('/#', '')).queryParameters['appId']; + if (appId == null) { + throw InvalidURLError(name); + } + return appId; + } } From 9ce74beda51181e4400f658612e96f21d2f1df10 Mon Sep 17 00:00:00 2001 From: iKirby <6316115+iKirby@users.noreply.github.com> Date: Tue, 6 May 2025 16:56:13 +0800 Subject: [PATCH 5/5] Add appId empty check --- lib/app_sources/vivoappstore.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/app_sources/vivoappstore.dart b/lib/app_sources/vivoappstore.dart index fbd512e..9b55ce2 100644 --- a/lib/app_sources/vivoappstore.dart +++ b/lib/app_sources/vivoappstore.dart @@ -89,7 +89,7 @@ class VivoAppStore extends AppSource { String parseVivoAppId(String url) { var appId = Uri.parse(url.replaceAll('/#', '')).queryParameters['appId']; - if (appId == null) { + if (appId == null || appId.isEmpty) { throw InvalidURLError(name); } return appId;