diff --git a/README.md b/README.md index 15720dc..471207f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Currently supported App sources: - [Aptoide](https://aptoide.com/) - [Uptodown](https://uptodown.com/) - [Huawei AppGallery](https://appgallery.huawei.com/) + - [Tencent App Store](https://sj.qq.com/) - Jenkins Jobs - [APKMirror](https://apkmirror.com/) (Track-Only) - Open Source - App-Specific: diff --git a/lib/app_sources/tencent.dart b/lib/app_sources/tencent.dart new file mode 100644 index 0000000..6552498 --- /dev/null +++ b/lib/app_sources/tencent.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; + +import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/providers/source_provider.dart'; + +class Tencent extends AppSource { + Tencent() { + name = 'Tencent App Store'; + hosts = ['sj.qq.com']; + naiveStandardVersionDetection = true; + showReleaseDateAsVersionToggle = true; + } + + @override + String sourceSpecificStandardizeURL(String url, {bool forSelection = false}) { + RegExp standardUrlRegEx = RegExp( + '^https?://${getSourceRegex(hosts)}/appdetail/[^/]+', + caseSensitive: false); + var match = standardUrlRegEx.firstMatch(url); + if (match == null) { + throw InvalidURLError(name); + } + return match.group(0)!; + } + + @override + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { + return Uri.parse(standardUrl).pathSegments.last; + } + + @override + Future getLatestAPKDetails( + String standardUrl, + Map additionalSettings, + ) async { + String appId = (await tryInferringAppId(standardUrl))!; + String baseHost = Uri.parse(standardUrl) + .host + .split('.') + .reversed + .toList() + .sublist(0, 2) + .reversed + .join('.'); + + var res = await sourceRequest( + 'https://upage.html5.$baseHost/wechat-apkinfo', additionalSettings, + followRedirects: false, postBody: {"packagename": appId}); + + if (res.statusCode == 200) { + var json = jsonDecode(res.body); + if (json['app_detail_records'][appId] == null) { + throw NoReleasesError(); + } + var version = + json['app_detail_records'][appId]['apk_all_data']['version_name']; + var apkUrl = json['app_detail_records'][appId]['apk_all_data']['url']; + if (apkUrl == null) { + throw NoAPKError(); + } + var appName = json['app_detail_records'][appId]['app_info']['name']; + var author = json['app_detail_records'][appId]['app_info']['author']; + var releaseDate = + json['app_detail_records'][appId]['app_info']['update_time']; + + return APKDetails( + version, + [MapEntry(Uri.parse(apkUrl).queryParameters['fsname']!, apkUrl)], + AppNames(author, appName), + releaseDate: releaseDate != null + ? DateTime.fromMillisecondsSinceEpoch(releaseDate * 1000) + : null); + } else { + throw getObtainiumHttpError(res); + } + } +} diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 43b90ba..3c59297 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -28,6 +28,7 @@ import 'package:obtainium/app_sources/sourceforge.dart'; import 'package:obtainium/app_sources/sourcehut.dart'; import 'package:obtainium/app_sources/steammobile.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/vlc.dart'; import 'package:obtainium/app_sources/whatsapp.dart'; @@ -465,19 +466,25 @@ abstract class AppSource { Future sourceRequest( String url, Map additionalSettings, - {bool followRedirects = true}) async { + {bool followRedirects = true, Object? postBody}) async { var requestHeaders = await getRequestHeaders(additionalSettings); if (requestHeaders != null || followRedirects == false) { - var req = Request('GET', Uri.parse(url)); + var req = Request(postBody == null ? 'GET' : 'POST', Uri.parse(url)); req.followRedirects = followRedirects; if (requestHeaders != null) { req.headers.addAll(requestHeaders); } + if (postBody != null) { + req.headers[HttpHeaders.contentTypeHeader] = 'application/json'; + req.body = jsonEncode(postBody); + } return Response.fromStream(await IOClient( createHttpClient(additionalSettings['allowInsecure'] == true)) .send(req)); } else { - return get(Uri.parse(url)); + return postBody == null + ? get(Uri.parse(url)) + : post(Uri.parse(url), body: jsonEncode(postBody)); } } @@ -782,6 +789,7 @@ class SourceProvider { Aptoide(), Uptodown(), HuaweiAppGallery(), + Tencent(), Jenkins(), APKMirror(), Signal(),