From a68d49c71c0194280ced1d08a5ab91d4678632e0 Mon Sep 17 00:00:00 2001 From: Imran Remtulla Date: Fri, 16 Dec 2022 19:26:07 -0500 Subject: [PATCH] Added Steam as a Source (#159) + Bugfixes --- README.md | 1 + assets/translations/en.json | 3 + assets/translations/it.json | 3 + assets/translations/zh.json | 3 + .../{fdroidRepo.dart => fdroidrepo.dart} | 0 lib/app_sources/steammobile.dart | 69 +++++++++++++++++++ lib/components/generated_form.dart | 11 +-- lib/providers/apps_provider.dart | 7 +- lib/providers/source_provider.dart | 8 ++- 9 files changed, 96 insertions(+), 9 deletions(-) rename lib/app_sources/{fdroidRepo.dart => fdroidrepo.dart} (100%) create mode 100644 lib/app_sources/steammobile.dart diff --git a/README.md b/README.md index 42a63b9..3d21466 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,7 @@ Currently supported App sources: - [SourceForge](https://sourceforge.net/) - [APKMirror](https://apkmirror.com/) (Track-Only) - Third Party F-Droid Repos (URLs ending with `/fdroid/repo`) +- [Steam](https://store.steampowered.com/mobile) ## Limitations - App installs happen asynchronously and the success/failure of an install cannot be determined directly. This results in install statuses and versions sometimes being out of sync with the OS until the next launch or until the problem is manually corrected. diff --git a/assets/translations/en.json b/assets/translations/en.json index 0c28603..8aa0a06 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -185,6 +185,9 @@ "appWithIdOrNameNotFound": "No App was found with that ID or Name", "reposHaveMultipleApps": "Repos may contain multiple Apps", "fdroidThirdPartyRepo": "F-Droid Third-Party Repo", + "steam": "Steam", + "steamMobile": "Steam Mobile", + "steamChat": "Steam Chat", "tooManyRequestsTryAgainInMinutes": { "one": "Too many requests (rate limited) - try again in {} minute", "other": "Too many requests (rate limited) - try again in {} minutes" diff --git a/assets/translations/it.json b/assets/translations/it.json index 8f39b48..46d2824 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -185,6 +185,9 @@ "appWithIdOrNameNotFound": "Non è stata trovata alcuna App con quell'ID o nome", "reposHaveMultipleApps": "I repository possono contenere più App", "fdroidThirdPartyRepo": "Repository di terze parti di F-Droid", + "steam": "Steam", + "steamMobile": "Steam Mobile", + "steamChat": "Steam Chat", "tooManyRequestsTryAgainInMinutes": { "one": "Troppe richieste (traffico limitato) - riprova tra {} minuto", "other": "Troppe richieste (traffico limitato) - riprova tra {} minuti" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 9b16fa1..50e8040 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -185,6 +185,9 @@ "appWithIdOrNameNotFound": "没有发现具有此 ID 或名称的应用", "reposHaveMultipleApps": "来源可能包含多个应用", "fdroidThirdPartyRepo": "F-Droid 第三方源", + "steam": "Steam", + "steamMobile": "Steam Mobile", + "steamChat": "Steam Chat", "tooManyRequestsTryAgainInMinutes": { "one": "请求过多 (API 限制) - 在 {} 分钟后重试", "other": "请求过多 (API 限制) - 在 {} 分钟后重试" diff --git a/lib/app_sources/fdroidRepo.dart b/lib/app_sources/fdroidrepo.dart similarity index 100% rename from lib/app_sources/fdroidRepo.dart rename to lib/app_sources/fdroidrepo.dart diff --git a/lib/app_sources/steammobile.dart b/lib/app_sources/steammobile.dart new file mode 100644 index 0000000..f202bd8 --- /dev/null +++ b/lib/app_sources/steammobile.dart @@ -0,0 +1,69 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:html/parser.dart'; +import 'package:http/http.dart'; +import 'package:obtainium/components/generated_form.dart'; +import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/providers/source_provider.dart'; + +class SteamMobile extends AppSource { + SteamMobile() { + host = 'store.steampowered.com'; + name = tr('steam'); + additionalSourceAppSpecificFormItems = [ + [ + GeneratedFormItem( + label: tr('app'), + key: 'app', + required: true, + opts: apks.entries.toList()) + ] + ]; + } + + final apks = {'steam': tr('steamMobile'), 'steam-chat-app': tr('steamChat')}; + + @override + String standardizeURL(String url) { + return 'https://$host'; + } + + @override + String? changeLogPageFromStandardUrl(String standardUrl) => null; + + @override + Future getLatestAPKDetails( + String standardUrl, List additionalData, + {bool trackOnly = false}) async { + Response res = await get(Uri.parse('https://$host/mobile')); + if (res.statusCode == 200) { + var apkNamePrefix = findGeneratedFormValueByKey( + additionalSourceAppSpecificFormItems + .reduce((value, element) => [...value, ...element]), + additionalData, + 'app'); + if (apkNamePrefix == null) { + throw NoReleasesError(); + } + String apkInURLRegexPattern = '/$apkNamePrefix-[^/]+\\.apk\$'; + var links = parse(res.body) + .querySelectorAll('a') + .map((e) => e.attributes['href'] ?? '') + .where((e) => RegExp('https://.*$apkInURLRegexPattern').hasMatch(e)) + .toList(); + + if (links.isEmpty) { + throw NoReleasesError(); + } + var versionMatch = RegExp(apkInURLRegexPattern).firstMatch(links[0]); + if (versionMatch == null) { + throw NoVersionError(); + } + var version = links[0].substring( + versionMatch.start + apkNamePrefix.length + 2, versionMatch.end - 4); + var apkUrls = [links[0]]; + return APKDetails(version, apkUrls, AppNames(name, apks[apkNamePrefix]!)); + } else { + throw NoReleasesError(); + } + } +} diff --git a/lib/components/generated_form.dart b/lib/components/generated_form.dart index 9ca009e..c0ae67f 100644 --- a/lib/components/generated_form.dart +++ b/lib/components/generated_form.dart @@ -16,7 +16,7 @@ class GeneratedFormItem { late String id; late List belowWidgets; late String? hint; - late List? opts; + late List>? opts; GeneratedFormItem( {this.label = 'Input', @@ -86,7 +86,7 @@ class _GeneratedFormState extends State { return j < widget.defaultValues.length ? widget.defaultValues[j++] : e.opts != null - ? e.opts!.first + ? e.opts!.first.key : ''; }).toList()) .toList(); @@ -130,14 +130,15 @@ class _GeneratedFormState extends State { return Text(tr('dropdownNoOptsError')); } return DropdownButtonFormField( - decoration: InputDecoration(labelText: tr('colour')), + decoration: InputDecoration(labelText: e.value.label), value: values[row.key][e.key], items: e.value.opts! - .map((e) => DropdownMenuItem(value: e, child: Text(e))) + .map((e) => + DropdownMenuItem(value: e.key, child: Text(e.value))) .toList(), onChanged: (value) { setState(() { - values[row.key][e.key] = value ?? e.value.opts!.first; + values[row.key][e.key] = value ?? e.value.opts!.first.key; someValueChanged(); }); }); diff --git a/lib/providers/apps_provider.dart b/lib/providers/apps_provider.dart index 278794d..ec605e2 100644 --- a/lib/providers/apps_provider.dart +++ b/lib/providers/apps_provider.dart @@ -274,9 +274,14 @@ class AppsProvider with ChangeNotifier { ); }); } + getHost(String url) { + var temp = Uri.parse(url).host.split('.'); + return temp.sublist(temp.length - 2).join('.'); + } + // If the picked APK comes from an origin different from the source, get user confirmation (if context provided) if (apkUrl != null && - Uri.parse(apkUrl).origin != Uri.parse(app.url).origin && + getHost(apkUrl) != getHost(app.url) && context != null) { if (await showDialog( context: context, diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 7af0664..cea22a5 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -8,13 +8,14 @@ import 'package:html/dom.dart'; import 'package:http/http.dart'; import 'package:obtainium/app_sources/apkmirror.dart'; import 'package:obtainium/app_sources/fdroid.dart'; -import 'package:obtainium/app_sources/fdroidRepo.dart'; +import 'package:obtainium/app_sources/fdroidrepo.dart'; import 'package:obtainium/app_sources/github.dart'; import 'package:obtainium/app_sources/gitlab.dart'; import 'package:obtainium/app_sources/izzyondroid.dart'; import 'package:obtainium/app_sources/mullvad.dart'; import 'package:obtainium/app_sources/signal.dart'; import 'package:obtainium/app_sources/sourceforge.dart'; +import 'package:obtainium/app_sources/steammobile.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/mass_app_sources/githubstars.dart'; @@ -212,7 +213,8 @@ class SourceProvider { Signal(), SourceForge(), APKMirror(), - FDroidRepo() + FDroidRepo(), + SteamMobile() ]; // Add more mass url source classes here so they are available via the service @@ -247,7 +249,7 @@ class SourceProvider { bool ifSourceAppsRequireAdditionalData(AppSource source) { for (var row in source.additionalSourceAppSpecificFormItems) { for (var element in row) { - if (element.required) { + if (element.required && element.opts == null) { return true; } }