diff --git a/README.md b/README.md index b1bbfe3..4337bde 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,8 @@ Currently supported App sources: - Third Party F-Droid Repos - Any URLs ending with `/fdroid/`, where `` can be anything - most often `repo` - [Steam](https://store.steampowered.com/mobile) +- "HTML" (Fallback) + - Any other URL that returns an HTML page with links to APK files (if multiple, the last file alphabetically is picked) ## 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/lib/app_sources/html.dart b/lib/app_sources/html.dart new file mode 100644 index 0000000..984c20e --- /dev/null +++ b/lib/app_sources/html.dart @@ -0,0 +1,47 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:html/parser.dart'; +import 'package:http/http.dart'; +import 'package:obtainium/custom_errors.dart'; +import 'package:obtainium/providers/source_provider.dart'; + +class HTML extends AppSource { + @override + String standardizeURL(String url) { + return url; + } + + @override + String? changeLogPageFromStandardUrl(String standardUrl) => null; + + @override + Future getLatestAPKDetails( + String standardUrl, + Map additionalSettings, + ) async { + var uri = Uri.parse(standardUrl); + Response res = await get(uri); + if (res.statusCode == 200) { + List links = parse(res.body) + .querySelectorAll('a') + .map((element) => element.attributes['href'] ?? '') + .where((element) => element.toLowerCase().endsWith('.apk')) + .toList(); + links.sort((a, b) => a.split('/').last.compareTo(b.split('/').last)); + if (links.isEmpty) { + throw NoReleasesError(); + } + var rel = links.last; + var apkName = rel.split('/').last; + var version = apkName.substring(0, apkName.length - 4); + List apkUrls = [rel] + .map((e) => e.toLowerCase().startsWith('http://') || + e.toLowerCase().startsWith('https://') + ? e + : '${uri.origin}/$e') + .toList(); + return APKDetails(version, apkUrls, AppNames(uri.host, tr('app'))); + } else { + throw getObtainiumHttpError(res); + } + } +} diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index 4d0eebd..d03f36b 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -13,6 +13,7 @@ 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/html.dart'; import 'package:obtainium/app_sources/mullvad.dart'; import 'package:obtainium/app_sources/signal.dart'; import 'package:obtainium/app_sources/sourceforge.dart'; @@ -154,6 +155,10 @@ class App { // Ensure the input is starts with HTTPS and has no WWW preStandardizeUrl(String url) { + var firstDotIndex = url.indexOf('.'); + if (!(firstDotIndex >= 0 && firstDotIndex != url.length - 1)) { + throw UnsupportedURLError(); + } if (url.toLowerCase().indexOf('http://') != 0 && url.toLowerCase().indexOf('https://') != 0) { url = 'https://$url'; @@ -277,7 +282,8 @@ class SourceProvider { SourceForge(), APKMirror(), FDroidRepo(), - SteamMobile() + SteamMobile(), + HTML() // This should ALWAYS be the last option as they are tried in order ]; // Add more mass url source classes here so they are available via the service