Compare commits

...

1 Commits

Author SHA1 Message Date
294327bde4 FIXED GITHUB ISSUE 2022-09-13 21:42:06 -04:00
4 changed files with 60 additions and 45 deletions

View File

@ -16,7 +16,7 @@ Currently supported App sources:
## Limitations ## Limitations
- App installs are assumed to have succeeded; failures and cancelled installs cannot be detected. - App installs are assumed to have succeeded; failures and cancelled installs cannot be detected.
- Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin. - Auto (unattended) updates are unsupported due to a lack of any capable Flutter plugin.
- For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods are either unavailable (e.g. Mullvad), insufficient (e.g. GitHub RSS) or subject to rate limits (e.g. GitHub API). - For some sources, data is gathered using Web scraping and can easily break due to changes in website design. In such cases, more reliable methods may be unavailable.
## Screenshots ## Screenshots

View File

@ -12,7 +12,7 @@ import 'package:dynamic_color/dynamic_color.dart';
import 'package:device_info_plus/device_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart';
const String currentReleaseTag = const String currentReleaseTag =
'v0.1.8-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES 'v0.1.9-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES
@pragma('vm:entry-point') @pragma('vm:entry-point')
void bgTaskCallback() { void bgTaskCallback() {

View File

@ -71,6 +71,12 @@ escapeRegEx(String s) {
}); });
} }
const String couldNotFindReleases = 'Unable to fetch release info';
const String couldNotFindLatestVersion =
'Could not determine latest release version';
const String notValidURL = 'Not a valid URL';
const String noAPKFound = 'No APK found';
List<String> getLinksFromParsedHTML( List<String> getLinksFromParsedHTML(
Document dom, RegExp hrefPattern, String prependToLinks) => Document dom, RegExp hrefPattern, String prependToLinks) =>
dom dom
@ -98,44 +104,53 @@ class GitHub implements AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw 'Not a valid URL'; throw notValidURL;
} }
return url.substring(0, match.end); return url.substring(0, match.end);
} }
@override @override
Future<APKDetails> getLatestAPKDetails(String standardUrl) async { Future<APKDetails> getLatestAPKDetails(String standardUrl) async {
Response res = await get(Uri.parse('$standardUrl/releases/latest')); Response res = await get(Uri.parse(
'https://api.$host/repos${standardUrl.substring('https://$host'.length)}/releases'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var standardUri = Uri.parse(standardUrl); var releases = jsonDecode(res.body) as List<dynamic>;
var parsedHtml = parse(res.body); // Right now, the latest non-prerelease version is picked
var apkUrlList = getLinksFromParsedHTML( // If none exists, the latest prerelease version is picked
parsedHtml, // In the future, the user could be given a choice
RegExp( var nonPrereleaseReleases =
'^${escapeRegEx(standardUri.path)}/releases/download/[^/]+/[^/]+\\.apk\$', releases.where((element) => element['prerelease'] != true).toList();
caseSensitive: false), var latestRelease = nonPrereleaseReleases.isNotEmpty
standardUri.origin); ? nonPrereleaseReleases[0]
if (apkUrlList.isEmpty) { : releases.isNotEmpty
throw 'No APK found'; ? releases[0]
: null;
if (latestRelease == null) {
throw couldNotFindReleases;
} }
String getTag(String url) { List<dynamic>? assets = latestRelease['assets'];
List<String> parts = url.split('/'); List<String>? apkUrlList = assets
return parts[parts.length - 2]; ?.map((e) {
return e['browser_download_url'] != null
? e['browser_download_url'] as String
: '';
})
.where((element) => element.toLowerCase().endsWith('.apk'))
.toList();
if (apkUrlList == null || apkUrlList.isEmpty) {
throw noAPKFound;
}
String? version = latestRelease['tag_name'];
if (version == null) {
throw couldNotFindLatestVersion;
}
return APKDetails(version, apkUrlList);
} else {
if (res.headers['x-ratelimit-remaining'] == '0') {
throw 'Rate limit reached - try again in ${(int.parse(res.headers['x-ratelimit-reset'] ?? '1800000000') / 60000000).toString()} minutes';
} }
String latestTag = getTag(apkUrlList[0]); throw couldNotFindReleases;
String? version = parsedHtml
.querySelector('.octicon-tag')
?.nextElementSibling
?.innerHtml
.trim();
if (version == null) {
throw 'Could not determine latest release version';
}
return APKDetails(version,
apkUrlList.where((element) => getTag(element) == latestTag).toList());
} else {
throw 'Unable to fetch release info';
} }
} }
@ -156,7 +171,7 @@ class GitLab implements AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw 'Not a valid URL'; throw notValidURL;
} }
return url.substring(0, match.end); return url.substring(0, match.end);
} }
@ -184,18 +199,18 @@ class GitLab implements AppSource {
.toList() .toList()
]; ];
if (apkUrlList.isEmpty) { if (apkUrlList.isEmpty) {
throw 'No APK found'; throw noAPKFound;
} }
var entryId = entry?.querySelector('id')?.innerHtml; var entryId = entry?.querySelector('id')?.innerHtml;
var version = var version =
entryId == null ? null : Uri.parse(entryId).pathSegments.last; entryId == null ? null : Uri.parse(entryId).pathSegments.last;
if (version == null) { if (version == null) {
throw 'Could not determine latest release version'; throw couldNotFindLatestVersion;
} }
return APKDetails(version, apkUrlList); return APKDetails(version, apkUrlList);
} else { } else {
throw 'Unable to fetch release info'; throw couldNotFindReleases;
} }
} }
@ -223,15 +238,15 @@ class Signal implements AppSource {
var json = jsonDecode(res.body); var json = jsonDecode(res.body);
String? apkUrl = json['url']; String? apkUrl = json['url'];
if (apkUrl == null) { if (apkUrl == null) {
throw 'No APK found'; throw noAPKFound;
} }
String? version = json['versionName']; String? version = json['versionName'];
if (version == null) { if (version == null) {
throw 'Could not determine latest release version'; throw couldNotFindLatestVersion;
} }
return APKDetails(version, [apkUrl]); return APKDetails(version, [apkUrl]);
} else { } else {
throw 'Unable to fetch release info'; throw couldNotFindReleases;
} }
} }
@ -248,7 +263,7 @@ class FDroid implements AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/packages/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/packages/[^/]+');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw 'Not a valid URL'; throw notValidURL;
} }
return url.substring(0, match.end); return url.substring(0, match.end);
} }
@ -263,7 +278,7 @@ class FDroid implements AppSource {
?.querySelector('.package-version-download a') ?.querySelector('.package-version-download a')
?.attributes['href']; ?.attributes['href'];
if (apkUrl == null) { if (apkUrl == null) {
throw 'No APK found'; throw noAPKFound;
} }
var version = latestReleaseDiv var version = latestReleaseDiv
?.querySelector('.package-version-header b') ?.querySelector('.package-version-header b')
@ -271,11 +286,11 @@ class FDroid implements AppSource {
.split(' ') .split(' ')
.last; .last;
if (version == null) { if (version == null) {
throw 'Could not determine latest release version'; throw couldNotFindLatestVersion;
} }
return APKDetails(version, [apkUrl]); return APKDetails(version, [apkUrl]);
} else { } else {
throw 'Unable to fetch release info'; throw couldNotFindReleases;
} }
} }
@ -295,7 +310,7 @@ class Mullvad implements AppSource {
RegExp standardUrlRegEx = RegExp('^https?://$host'); RegExp standardUrlRegEx = RegExp('^https?://$host');
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase()); RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
if (match == null) { if (match == null) {
throw 'Not a valid URL'; throw notValidURL;
} }
return url.substring(0, match.end); return url.substring(0, match.end);
} }
@ -311,12 +326,12 @@ class Mullvad implements AppSource {
?.split('/') ?.split('/')
.last; .last;
if (version == null) { if (version == null) {
throw 'Could not determine the latest release version'; throw couldNotFindLatestVersion;
} }
return APKDetails( return APKDetails(
version, ['https://mullvad.net/download/app/apk/latest']); version, ['https://mullvad.net/download/app/apk/latest']);
} else { } else {
throw 'Unable to fetch release info'; throw couldNotFindReleases;
} }
} }

View File

@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev
# https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
# In Windows, build-name is used as the major, minor, and patch parts # In Windows, build-name is used as the major, minor, and patch parts
# of the product and file versions while build-number is used as the build suffix. # of the product and file versions while build-number is used as the build suffix.
version: 0.1.8+9 # When changing this, update the tag in main() accordingly version: 0.1.9+10 # When changing this, update the tag in main() accordingly
environment: environment:
sdk: '>=2.19.0-79.0.dev <3.0.0' sdk: '>=2.19.0-79.0.dev <3.0.0'