Sort GitHub releases by date, remove codeberg redundancy

This commit is contained in:
Imran Remtulla
2023-04-22 22:42:59 -04:00
parent 9f2db4e4e7
commit 3e732a4317
2 changed files with 79 additions and 112 deletions

View File

@ -1,6 +1,7 @@
import 'dart:convert'; import 'dart:convert';
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart'; import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.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/providers/source_provider.dart'; import 'package:obtainium/providers/source_provider.dart';
@ -35,6 +36,8 @@ class Codeberg extends AppSource {
canSearch = true; canSearch = true;
} }
var gh = GitHub();
@override @override
String standardizeURL(String url) { String standardizeURL(String url) {
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
@ -54,80 +57,10 @@ class Codeberg extends AppSource {
String standardUrl, String standardUrl,
Map<String, dynamic> additionalSettings, Map<String, dynamic> additionalSettings,
) async { ) async {
bool includePrereleases = additionalSettings['includePrereleases'] == true; return gh.getLatestAPKDetailsCommon(
bool fallbackToOlderReleases = 'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100',
additionalSettings['fallbackToOlderReleases'] == true; standardUrl,
String? regexFilter = additionalSettings);
(additionalSettings['filterReleaseTitlesByRegEx'] as String?)
?.isNotEmpty ==
true
? additionalSettings['filterReleaseTitlesByRegEx']
: null;
Response res = await get(Uri.parse(
'https://$host/api/v1/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?)
?.map((e) {
return e['name'] != null && e['browser_download_url'] != null
? MapEntry(e['name'] as String,
e['browser_download_url'] as String)
: const MapEntry('', '');
})
.where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList() ??
[];
dynamic targetRelease;
var prerrelsSkipped = 0;
for (int i = 0; i < releases.length; i++) {
if (!fallbackToOlderReleases && i > prerrelsSkipped) break;
if (!includePrereleases && releases[i]['prerelease'] == true) {
prerrelsSkipped++;
continue;
}
if (releases[i]['draft'] == true) {
// Draft releases not supported
}
var nameToFilter = releases[i]['name'] as String?;
if (nameToFilter == null || nameToFilter.trim().isEmpty) {
// Some leave titles empty so tag is used
nameToFilter = releases[i]['tag_name'] as String;
}
if (regexFilter != null &&
!RegExp(regexFilter).hasMatch(nameToFilter.trim())) {
continue;
}
var apkUrls = getReleaseAPKUrls(releases[i]);
if (apkUrls.isEmpty && additionalSettings['trackOnly'] != true) {
continue;
}
targetRelease = releases[i];
targetRelease['apkUrls'] = apkUrls;
break;
}
if (targetRelease == null) {
throw NoReleasesError();
}
String? version = targetRelease['tag_name'];
DateTime? releaseDate = targetRelease['published_at'] != null
? DateTime.parse(targetRelease['published_at'])
: null;
if (version == null) {
throw NoVersionError();
}
var changeLog = targetRelease['body'].toString();
return APKDetails(
version,
targetRelease['apkUrls'] as List<MapEntry<String, String>>,
getAppNames(standardUrl),
releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog);
} else {
throw getObtainiumHttpError(res);
}
} }
AppNames getAppNames(String standardUrl) { AppNames getAppNames(String standardUrl) {
@ -138,20 +71,9 @@ class Codeberg extends AppSource {
@override @override
Future<Map<String, String>> search(String query) async { Future<Map<String, String>> search(String query) async {
Response res = await get(Uri.parse( return gh.searchCommon(
'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100')); query,
if (res.statusCode == 200) { 'https://$host/api/v1/repos/search?q=${Uri.encodeQueryComponent(query)}&limit=100',
Map<String, String> urlsWithDescriptions = {}; 'data');
for (var e in (jsonDecode(res.body)['data'] as List<dynamic>)) {
urlsWithDescriptions.addAll({
e['html_url'] as String: e['description'] != null
? e['description'] as String
: tr('noDescription')
});
}
return urlsWithDescriptions;
} else {
throw getObtainiumHttpError(res);
}
} }
} }

View File

@ -96,11 +96,9 @@ class GitHub extends AppSource {
String? changeLogPageFromStandardUrl(String standardUrl) => String? changeLogPageFromStandardUrl(String standardUrl) =>
'$standardUrl/releases'; '$standardUrl/releases';
@override Future<APKDetails> getLatestAPKDetailsCommon(String requestUrl,
Future<APKDetails> getLatestAPKDetails( String standardUrl, Map<String, dynamic> additionalSettings,
String standardUrl, {Function(Response)? onHttpErrorCode}) async {
Map<String, dynamic> additionalSettings,
) async {
bool includePrereleases = additionalSettings['includePrereleases'] == true; bool includePrereleases = additionalSettings['includePrereleases'] == true;
bool fallbackToOlderReleases = bool fallbackToOlderReleases =
additionalSettings['fallbackToOlderReleases'] == true; additionalSettings['fallbackToOlderReleases'] == true;
@ -110,22 +108,40 @@ class GitHub extends AppSource {
true true
? additionalSettings['filterReleaseTitlesByRegEx'] ? additionalSettings['filterReleaseTitlesByRegEx']
: null; : null;
Response res = await get(Uri.parse( Response res = await get(Uri.parse(requestUrl));
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100'));
if (res.statusCode == 200) { if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>; var releases = jsonDecode(res.body) as List<dynamic>;
List<String> getReleaseAPKUrls(dynamic release) => List<MapEntry<String, String>> getReleaseAPKUrls(dynamic release) =>
(release['assets'] as List<dynamic>?) (release['assets'] as List<dynamic>?)
?.map((e) { ?.map((e) {
return e['browser_download_url'] != null return e['name'] != null && e['browser_download_url'] != null
? e['browser_download_url'] as String ? MapEntry(e['name'] as String,
: ''; e['browser_download_url'] as String)
: const MapEntry('', '');
}) })
.where((element) => element.toLowerCase().endsWith('.apk')) .where((element) => element.key.toLowerCase().endsWith('.apk'))
.toList() ?? .toList() ??
[]; [];
DateTime? getReleaseDateFromRelease(dynamic rel) =>
rel?['published_at'] != null
? DateTime.parse(rel['published_at'])
: null;
releases.sort((a, b) {
// See #478
if (a == b) {
return 0;
} else if (a == null) {
return -1;
} else if (b == null) {
return 1;
} else {
return getReleaseDateFromRelease(a)!
.compareTo(getReleaseDateFromRelease(b)!);
}
});
releases = releases.reversed.toList();
dynamic targetRelease; dynamic targetRelease;
var prerrelsSkipped = 0; var prerrelsSkipped = 0;
for (int i = 0; i < releases.length; i++) { for (int i = 0; i < releases.length; i++) {
@ -134,6 +150,10 @@ class GitHub extends AppSource {
prerrelsSkipped++; prerrelsSkipped++;
continue; continue;
} }
if (releases[i]['draft'] == true) {
// Draft releases not supported
continue;
}
var nameToFilter = releases[i]['name'] as String?; var nameToFilter = releases[i]['name'] as String?;
if (nameToFilter == null || nameToFilter.trim().isEmpty) { if (nameToFilter == null || nameToFilter.trim().isEmpty) {
// Some leave titles empty so tag is used // Some leave titles empty so tag is used
@ -155,38 +175,51 @@ class GitHub extends AppSource {
throw NoReleasesError(); throw NoReleasesError();
} }
String? version = targetRelease['tag_name']; String? version = targetRelease['tag_name'];
DateTime? releaseDate = targetRelease['published_at'] != null DateTime? releaseDate = getReleaseDateFromRelease(targetRelease);
? DateTime.parse(targetRelease['published_at'])
: null;
if (version == null) { if (version == null) {
throw NoVersionError(); throw NoVersionError();
} }
var changeLog = targetRelease['body'].toString(); var changeLog = targetRelease['body'].toString();
return APKDetails( return APKDetails(
version, version,
getApkUrlsFromUrls(targetRelease['apkUrls'] as List<String>), targetRelease['apkUrls'] as List<MapEntry<String, String>>,
getAppNames(standardUrl), getAppNames(standardUrl),
releaseDate: releaseDate, releaseDate: releaseDate,
changeLog: changeLog.isEmpty ? null : changeLog); changeLog: changeLog.isEmpty ? null : changeLog);
} else { } else {
rateLimitErrorCheck(res); if (onHttpErrorCode != null) {
onHttpErrorCode(res);
}
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }
} }
@override
Future<APKDetails> getLatestAPKDetails(
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
return getLatestAPKDetailsCommon(
'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/releases?per_page=100',
standardUrl,
additionalSettings, onHttpErrorCode: (Response res) {
rateLimitErrorCheck(res);
});
}
AppNames getAppNames(String standardUrl) { AppNames getAppNames(String standardUrl) {
String temp = standardUrl.substring(standardUrl.indexOf('://') + 3); String temp = standardUrl.substring(standardUrl.indexOf('://') + 3);
List<String> names = temp.substring(temp.indexOf('/') + 1).split('/'); List<String> names = temp.substring(temp.indexOf('/') + 1).split('/');
return AppNames(names[0], names[1]); return AppNames(names[0], names[1]);
} }
@override Future<Map<String, String>> searchCommon(
Future<Map<String, String>> search(String query) async { String query, String requestUrl, String rootProp,
Response res = await get(Uri.parse( {Function(Response)? onHttpErrorCode}) async {
'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100')); Response res = await get(Uri.parse(requestUrl));
if (res.statusCode == 200) { if (res.statusCode == 200) {
Map<String, String> urlsWithDescriptions = {}; Map<String, String> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)['items'] as List<dynamic>)) { for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
urlsWithDescriptions.addAll({ urlsWithDescriptions.addAll({
e['html_url'] as String: e['html_url'] as String:
((e['archived'] == true ? '[ARCHIVED] ' : '') + ((e['archived'] == true ? '[ARCHIVED] ' : '') +
@ -197,11 +230,23 @@ class GitHub extends AppSource {
} }
return urlsWithDescriptions; return urlsWithDescriptions;
} else { } else {
rateLimitErrorCheck(res); if (onHttpErrorCode != null) {
onHttpErrorCode(res);
}
throw getObtainiumHttpError(res); throw getObtainiumHttpError(res);
} }
} }
@override
Future<Map<String, String>> search(String query) async {
return searchCommon(
query,
'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100',
'items', onHttpErrorCode: (Response res) {
rateLimitErrorCheck(res);
});
}
rateLimitErrorCheck(Response res) { rateLimitErrorCheck(Response res) {
if (res.headers['x-ratelimit-remaining'] == '0') { if (res.headers['x-ratelimit-remaining'] == '0') {
throw RateLimitError( throw RateLimitError(