mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-17 07:06:43 +02:00
Sort GitHub releases by date, remove codeberg redundancy
This commit is contained in:
@ -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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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(
|
||||||
|
Reference in New Issue
Block a user