mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Merge remote-tracking branch 'origin/main' into dev
This commit is contained in:
@ -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)
|
||||||
|
@ -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",
|
||||||
|
@ -321,6 +321,7 @@
|
|||||||
"refreshBeforeDownload": "下载前刷新应用程序详细信息",
|
"refreshBeforeDownload": "下载前刷新应用程序详细信息",
|
||||||
"tencentAppStore": "腾讯应用宝",
|
"tencentAppStore": "腾讯应用宝",
|
||||||
"coolApk": "酷安",
|
"coolApk": "酷安",
|
||||||
|
"vivoAppStore": "vivo 应用商店(中国)",
|
||||||
"name": "名称",
|
"name": "名称",
|
||||||
"smartname": "姓名(智能)",
|
"smartname": "姓名(智能)",
|
||||||
"sortMethod": "排序方法",
|
"sortMethod": "排序方法",
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
||||||
|
@ -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;
|
||||||
|
@ -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};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
97
lib/app_sources/vivoappstore.dart
Normal file
97
lib/app_sources/vivoappstore.dart
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
@ -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'))
|
||||||
]
|
]
|
||||||
|
@ -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(),
|
||||||
|
Reference in New Issue
Block a user