Merge remote-tracking branch 'origin/main' into dev

This commit is contained in:
Imran Remtulla
2025-05-17 01:09:37 -04:00
10 changed files with 130 additions and 4 deletions

View File

@ -31,6 +31,7 @@ Currently supported App sources:
- [Huawei AppGallery](https://appgallery.huawei.com/) - [Huawei AppGallery](https://appgallery.huawei.com/)
- [Tencent App Store](https://sj.qq.com/) - [Tencent App Store](https://sj.qq.com/)
- [CoolApk](https://coolapk.com/) - [CoolApk](https://coolapk.com/)
- [vivo App Store (CN)](https://h5.appstore.vivo.com.cn/)
- [RuStore](https://rustore.ru/) - [RuStore](https://rustore.ru/)
- Jenkins Jobs - Jenkins Jobs
- [APKMirror](https://apkmirror.com/) (Track-Only) - [APKMirror](https://apkmirror.com/) (Track-Only)

View File

@ -321,6 +321,7 @@
"refreshBeforeDownload": "Refresh app details before download", "refreshBeforeDownload": "Refresh app details before download",
"tencentAppStore": "Tencent App Store", "tencentAppStore": "Tencent App Store",
"coolApk": "CoolApk", "coolApk": "CoolApk",
"vivoAppStore": "vivo App Store (CN)",
"name": "Name", "name": "Name",
"smartname": "Name (smart)", "smartname": "Name (smart)",
"sortMethod": "Sort method", "sortMethod": "Sort method",

View File

@ -321,6 +321,7 @@
"refreshBeforeDownload": "下载前刷新应用程序详细信息", "refreshBeforeDownload": "下载前刷新应用程序详细信息",
"tencentAppStore": "腾讯应用宝", "tencentAppStore": "腾讯应用宝",
"coolApk": "酷安", "coolApk": "酷安",
"vivoAppStore": "vivo 应用商店(中国)",
"name": "名称", "name": "名称",
"smartname": "姓名(智能)", "smartname": "姓名(智能)",
"sortMethod": "排序方法", "sortMethod": "排序方法",

View File

@ -26,6 +26,7 @@
<li>Huawei AppGallery</li> <li>Huawei AppGallery</li>
<li>Tencent App Store</li> <li>Tencent App Store</li>
<li>CoolApk</li> <li>CoolApk</li>
<li>vivo App Store (CN)</li>
<li>Jenkins Jobs</li> <li>Jenkins Jobs</li>
<li>RuStore</li> <li>RuStore</li>
</ul> </ul>

View File

@ -26,6 +26,7 @@
<li>Huawei AppGallery</li> <li>Huawei AppGallery</li>
<li>Tencent App Store</li> <li>Tencent App Store</li>
<li>CoolApk</li> <li>CoolApk</li>
<li>vivo App Store (CN)</li>
<li>Jenkins Jobs</li> <li>Jenkins Jobs</li>
<li>RuStore</li> <li>RuStore</li>
</ul> </ul>

View File

@ -128,7 +128,21 @@ class APKPure extends AppSource {
.toList() ?? .toList() ??
[]; [];
if (apkUrls.isEmpty) { if (apkUrls.isEmpty) {
throw NoAPKError(); 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 version = Uri.parse(link).pathSegments.last;
String? author; String? author;

View File

@ -13,6 +13,7 @@ class CoolApk extends AppSource {
hosts = ['www.coolapk.com', 'api2.coolapk.com']; hosts = ['www.coolapk.com', 'api2.coolapk.com'];
allowSubDomains = true; allowSubDomains = true;
naiveStandardVersionDetection = true; naiveStandardVersionDetection = true;
allowOverride = false;
} }
@override @override
@ -170,4 +171,4 @@ class CoolApk extends AppSource {
return {'deviceCode': deviceCode, 'token': finalToken}; return {'deviceCode': deviceCode, 'token': finalToken};
} }
} }

View File

@ -0,0 +1,97 @@
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 = parseVivoAppId(url);
return '$appDetailUrl$vivoAppId';
}
@override
Future<String?> tryInferringAppId(String standardUrl,
{Map<String, dynamic> additionalSettings = const {}}) async {
var json = await getDetailJson(standardUrl, additionalSettings);
return json['package_name'];
}
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl, Map<String, dynamic> 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<Map<String, List<String>>> search(String query,
{Map<String, dynamic> 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<String, List<String>> results = {};
var resultsJson = json['data']['appSearchResponse']['value'];
for (var item in (resultsJson as List<dynamic>)) {
results['$appDetailUrl${item['id']}'] = [
item['title_zh'].toString(),
item['developer'].toString()
];
}
return results;
}
Future<Map<String, dynamic>> getDetailJson(
String standardUrl, Map<String, dynamic> additionalSettings) async {
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 || appId.isEmpty) {
throw InvalidURLError(name);
}
return appId;
}
}

View File

@ -407,8 +407,14 @@ class AddAppPageState extends State<AddAppPage> {
defaultValue: pickedSourceOverride ?? '', defaultValue: pickedSourceOverride ?? '',
[ [
MapEntry('', tr('none')), MapEntry('', tr('none')),
...sourceProvider.sources.map( ...sourceProvider.sources
(s) => MapEntry(s.runtimeType.toString(), s.name)) .where((s) =>
s.allowOverride ||
(pickedSource != null &&
pickedSource.runtimeType ==
s.runtimeType))
.map((s) =>
MapEntry(s.runtimeType.toString(), s.name))
], ],
label: tr('overrideSource')) label: tr('overrideSource'))
] ]

View File

@ -31,6 +31,7 @@ import 'package:obtainium/app_sources/sourcehut.dart';
import 'package:obtainium/app_sources/telegramapp.dart'; import 'package:obtainium/app_sources/telegramapp.dart';
import 'package:obtainium/app_sources/tencent.dart'; import 'package:obtainium/app_sources/tencent.dart';
import 'package:obtainium/app_sources/uptodown.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/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart'; import 'package:obtainium/mass_app_sources/githubstars.dart';
@ -586,6 +587,7 @@ abstract class AppSource {
bool appIdInferIsOptional = false; bool appIdInferIsOptional = false;
bool allowSubDomains = false; bool allowSubDomains = false;
bool naiveStandardVersionDetection = false; bool naiveStandardVersionDetection = false;
bool allowOverride = true;
bool neverAutoSelect = false; bool neverAutoSelect = false;
bool showReleaseDateAsVersionToggle = false; bool showReleaseDateAsVersionToggle = false;
bool versionDetectionDisallowed = false; bool versionDetectionDisallowed = false;
@ -954,6 +956,7 @@ class SourceProvider {
HuaweiAppGallery(), HuaweiAppGallery(),
Tencent(), Tencent(),
CoolApk(), CoolApk(),
VivoAppStore(),
Jenkins(), Jenkins(),
APKMirror(), APKMirror(),
RuStore(), RuStore(),