mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-11-04 07:13:28 +01: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,8 +128,22 @@ class APKPure extends AppSource {
 | 
				
			|||||||
              .toList() ??
 | 
					              .toList() ??
 | 
				
			||||||
          [];
 | 
					          [];
 | 
				
			||||||
      if (apkUrls.isEmpty) {
 | 
					      if (apkUrls.isEmpty) {
 | 
				
			||||||
 | 
					        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();
 | 
					          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;
 | 
				
			||||||
      try {
 | 
					      try {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -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
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										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