mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-16 06:36:44 +02:00
APKPure, SourceHut, Bugfixes
This commit is contained in:
116
lib/app_sources/apkcombo.dart
Normal file
116
lib/app_sources/apkcombo.dart
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
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 APKCombo extends AppSource {
|
||||||
|
APKCombo() {
|
||||||
|
host = 'apkcombo.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://$host/+[^/]+/+[^/]+');
|
||||||
|
var match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? tryInferringAppId(String standardUrl,
|
||||||
|
{Map<String, dynamic> additionalSettings = const {}}) {
|
||||||
|
return Uri.parse(standardUrl).pathSegments.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Map<String, String> get requestHeaders => {
|
||||||
|
"User-Agent": "curl/8.0.1",
|
||||||
|
"Accept": "*/*",
|
||||||
|
"Connection": "keep-alive",
|
||||||
|
"Host": "$host"
|
||||||
|
};
|
||||||
|
|
||||||
|
Future<List<MapEntry<String, String>>> getApkUrls(String standardUrl) async {
|
||||||
|
var res = await sourceRequest('$standardUrl/download/apk');
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var html = parse(res.body);
|
||||||
|
return html
|
||||||
|
.querySelectorAll('#variants-tab > div > ul > li')
|
||||||
|
.map((e) {
|
||||||
|
String? arch = e
|
||||||
|
.querySelector('code')
|
||||||
|
?.text
|
||||||
|
.trim()
|
||||||
|
.replaceAll(',', '')
|
||||||
|
.replaceAll(':', '-')
|
||||||
|
.replaceAll(' ', '-');
|
||||||
|
return e.querySelectorAll('a').map((e) {
|
||||||
|
String? url = e.attributes['href'];
|
||||||
|
if (url != null &&
|
||||||
|
!Uri.parse(url).path.toLowerCase().endsWith('.apk')) {
|
||||||
|
url = null;
|
||||||
|
}
|
||||||
|
String verCode =
|
||||||
|
e.querySelector('.info .header .vercode')?.text.trim() ?? '';
|
||||||
|
return MapEntry<String, String>(
|
||||||
|
arch != null ? '$arch-$verCode.apk' : '', url ?? '');
|
||||||
|
}).toList();
|
||||||
|
})
|
||||||
|
.reduce((value, element) => [...value, ...element])
|
||||||
|
.where((element) => element.value.isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String> apkUrlPrefetchModifier(
|
||||||
|
String apkUrl, String standardUrl) async {
|
||||||
|
var freshURLs = await getApkUrls(standardUrl);
|
||||||
|
var path2Match = Uri.parse(apkUrl).path;
|
||||||
|
for (var url in freshURLs) {
|
||||||
|
if (Uri.parse(url.value).path == path2Match) {
|
||||||
|
return url.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw NoAPKError();
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
String appId = tryInferringAppId(standardUrl)!;
|
||||||
|
String host = Uri.parse(standardUrl).host;
|
||||||
|
var preres = await sourceRequest(standardUrl);
|
||||||
|
if (preres.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(preres);
|
||||||
|
}
|
||||||
|
var res = parse(preres.body);
|
||||||
|
String? version = res.querySelector('div.version')?.text.trim();
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
String appName = res.querySelector('div.app_name')?.text.trim() ?? appId;
|
||||||
|
String author = res.querySelector('div.author')?.text.trim() ?? appName;
|
||||||
|
List<String> infoArray = res
|
||||||
|
.querySelectorAll('div.information-table > .item > div.value')
|
||||||
|
.map((e) => e.text.trim())
|
||||||
|
.toList();
|
||||||
|
DateTime? releaseDate;
|
||||||
|
if (infoArray.length >= 2) {
|
||||||
|
try {
|
||||||
|
releaseDate = DateFormat('MMMM d, yyyy').parse(infoArray[1]);
|
||||||
|
} catch (e) {
|
||||||
|
// ignore
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return APKDetails(
|
||||||
|
version, await getApkUrls(standardUrl), AppNames(author, appName),
|
||||||
|
releaseDate: releaseDate);
|
||||||
|
}
|
||||||
|
}
|
@ -57,7 +57,7 @@ class APKMirror extends AppSource {
|
|||||||
true
|
true
|
||||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||||
: null;
|
: null;
|
||||||
Response res = await get(Uri.parse('$standardUrl/feed'));
|
Response res = await sourceRequest('$standardUrl/feed');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var items = parse(res.body).querySelectorAll('item');
|
var items = parse(res.body).querySelectorAll('item');
|
||||||
dynamic targetRelease;
|
dynamic targetRelease;
|
||||||
|
69
lib/app_sources/apkpure.dart
Normal file
69
lib/app_sources/apkpure.dart
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
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 APKPure extends AppSource {
|
||||||
|
APKPure() {
|
||||||
|
host = 'apkpure.com';
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegExB = RegExp('^https?://m.$host/+[^/]+/+[^/]+');
|
||||||
|
RegExpMatch? match = standardUrlRegExB.firstMatch(url.toLowerCase());
|
||||||
|
if (match != null) {
|
||||||
|
url = 'https://$host/${Uri.parse(url).path}';
|
||||||
|
}
|
||||||
|
RegExp standardUrlRegExA = RegExp('^https?://$host/+[^/]+/+[^/]+');
|
||||||
|
match = standardUrlRegExA.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? tryInferringAppId(String standardUrl,
|
||||||
|
{Map<String, dynamic> additionalSettings = const {}}) {
|
||||||
|
return Uri.parse(standardUrl).pathSegments.last;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
String appId = tryInferringAppId(standardUrl)!;
|
||||||
|
String host = Uri.parse(standardUrl).host;
|
||||||
|
var res = await sourceRequest('$standardUrl/download');
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
var html = parse(res.body);
|
||||||
|
String? version = html.querySelector('span.info-sdk span')?.text.trim();
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
String? dateString =
|
||||||
|
html.querySelector('span.info-other span.date')?.text.trim();
|
||||||
|
DateTime? releaseDate = dateString != null
|
||||||
|
? DateFormat('MMMM d, yyyy').parse(dateString)
|
||||||
|
: null;
|
||||||
|
List<MapEntry<String, String>> apkUrls = [
|
||||||
|
MapEntry('$appId.apk', 'https://d.$host/b/APK/$appId?version=latest')
|
||||||
|
];
|
||||||
|
String author = html
|
||||||
|
.querySelector('span.info-sdk')
|
||||||
|
?.text
|
||||||
|
.trim()
|
||||||
|
.substring(version.length + 4) ??
|
||||||
|
Uri.parse(standardUrl).pathSegments.reversed.last;
|
||||||
|
String appName =
|
||||||
|
html.querySelector('h1.info-title')?.text.trim() ?? appId;
|
||||||
|
return APKDetails(version, apkUrls, AppNames(author, appName),
|
||||||
|
releaseDate: releaseDate);
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class Codeberg extends AppSource {
|
class Codeberg extends AppSource {
|
||||||
Codeberg() {
|
Codeberg() {
|
||||||
host = 'codeberg.org';
|
host = 'codeberg.org';
|
||||||
|
overrideEligible = true;
|
||||||
|
|
||||||
additionalSourceSpecificSettingFormItems = [];
|
additionalSourceSpecificSettingFormItems = [];
|
||||||
|
|
||||||
|
@ -66,14 +66,14 @@ class FDroid extends AppSource {
|
|||||||
String? appId = tryInferringAppId(standardUrl);
|
String? appId = tryInferringAppId(standardUrl);
|
||||||
String host = Uri.parse(standardUrl).host;
|
String host = Uri.parse(standardUrl).host;
|
||||||
return getAPKUrlsFromFDroidPackagesAPIResponse(
|
return getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await get(Uri.parse('https://$host/api/v1/packages/$appId')),
|
await sourceRequest('https://$host/api/v1/packages/$appId'),
|
||||||
'https://$host/repo/$appId',
|
'https://$host/repo/$appId',
|
||||||
standardUrl);
|
standardUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<Map<String, List<String>>> search(String query) async {
|
Future<Map<String, List<String>>> search(String query) async {
|
||||||
Response res = await get(Uri.parse('https://search.$host/?q=$query'));
|
Response res = await sourceRequest('https://search.$host/?q=$query');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
Map<String, List<String>> urlsWithDescriptions = {};
|
Map<String, List<String>> urlsWithDescriptions = {};
|
||||||
parse(res.body).querySelectorAll('.package-header').forEach((e) {
|
parse(res.body).querySelectorAll('.package-header').forEach((e) {
|
||||||
|
@ -8,6 +8,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class FDroidRepo extends AppSource {
|
class FDroidRepo extends AppSource {
|
||||||
FDroidRepo() {
|
FDroidRepo() {
|
||||||
name = tr('fdroidThirdPartyRepo');
|
name = tr('fdroidThirdPartyRepo');
|
||||||
|
overrideEligible = true;
|
||||||
|
|
||||||
additionalSourceAppSpecificSettingFormItems = [
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
[
|
[
|
||||||
@ -28,7 +29,7 @@ class FDroidRepo extends AppSource {
|
|||||||
if (appIdOrName == null) {
|
if (appIdOrName == null) {
|
||||||
throw NoReleasesError();
|
throw NoReleasesError();
|
||||||
}
|
}
|
||||||
var res = await get(Uri.parse('$standardUrl/index.xml'));
|
var res = await sourceRequest('$standardUrl/index.xml');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var body = parse(res.body);
|
var body = parse(res.body);
|
||||||
var foundApps = body.querySelectorAll('application').where((element) {
|
var foundApps = body.querySelectorAll('application').where((element) {
|
||||||
|
@ -11,6 +11,7 @@ import 'package:url_launcher/url_launcher_string.dart';
|
|||||||
class GitHub extends AppSource {
|
class GitHub extends AppSource {
|
||||||
GitHub() {
|
GitHub() {
|
||||||
host = 'github.com';
|
host = 'github.com';
|
||||||
|
overrideEligible = true;
|
||||||
|
|
||||||
additionalSourceSpecificSettingFormItems = [
|
additionalSourceSpecificSettingFormItems = [
|
||||||
GeneratedFormTextField('github-creds',
|
GeneratedFormTextField('github-creds',
|
||||||
@ -108,7 +109,7 @@ class GitHub extends AppSource {
|
|||||||
true
|
true
|
||||||
? additionalSettings['filterReleaseTitlesByRegEx']
|
? additionalSettings['filterReleaseTitlesByRegEx']
|
||||||
: null;
|
: null;
|
||||||
Response res = await get(Uri.parse(requestUrl));
|
Response res = await sourceRequest(requestUrl);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var releases = jsonDecode(res.body) as List<dynamic>;
|
var releases = jsonDecode(res.body) as List<dynamic>;
|
||||||
|
|
||||||
@ -216,7 +217,7 @@ class GitHub extends AppSource {
|
|||||||
Future<Map<String, List<String>>> searchCommon(
|
Future<Map<String, List<String>>> searchCommon(
|
||||||
String query, String requestUrl, String rootProp,
|
String query, String requestUrl, String rootProp,
|
||||||
{Function(Response)? onHttpErrorCode}) async {
|
{Function(Response)? onHttpErrorCode}) async {
|
||||||
Response res = await get(Uri.parse(requestUrl));
|
Response res = await sourceRequest(requestUrl);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
Map<String, List<String>> urlsWithDescriptions = {};
|
Map<String, List<String>> urlsWithDescriptions = {};
|
||||||
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
|
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
|
||||||
|
@ -9,6 +9,7 @@ import 'package:easy_localization/easy_localization.dart';
|
|||||||
class GitLab extends AppSource {
|
class GitLab extends AppSource {
|
||||||
GitLab() {
|
GitLab() {
|
||||||
host = 'gitlab.com';
|
host = 'gitlab.com';
|
||||||
|
overrideEligible = true;
|
||||||
|
|
||||||
additionalSourceAppSpecificSettingFormItems = [
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
[
|
[
|
||||||
@ -39,7 +40,7 @@ class GitLab extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
bool fallbackToOlderReleases =
|
bool fallbackToOlderReleases =
|
||||||
additionalSettings['fallbackToOlderReleases'] == true;
|
additionalSettings['fallbackToOlderReleases'] == true;
|
||||||
Response res = await get(Uri.parse('$standardUrl/-/tags?format=atom'));
|
Response res = await sourceRequest('$standardUrl/-/tags?format=atom');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var standardUri = Uri.parse(standardUrl);
|
var standardUri = Uri.parse(standardUrl);
|
||||||
var parsedHtml = parse(res.body);
|
var parsedHtml = parse(res.body);
|
||||||
|
@ -4,84 +4,109 @@ import 'package:http/http.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';
|
||||||
|
|
||||||
|
String ensureAbsoluteUrl(String ambiguousUrl, Uri referenceAbsoluteUrl) {
|
||||||
|
try {
|
||||||
|
Uri.parse(ambiguousUrl).origin;
|
||||||
|
return ambiguousUrl;
|
||||||
|
} catch (err) {
|
||||||
|
// is relative
|
||||||
|
}
|
||||||
|
var currPathSegments = referenceAbsoluteUrl.path
|
||||||
|
.split('/')
|
||||||
|
.where((element) => element.trim().isNotEmpty)
|
||||||
|
.toList();
|
||||||
|
if (ambiguousUrl.startsWith('/') || currPathSegments.isEmpty) {
|
||||||
|
return '${referenceAbsoluteUrl.origin}/$ambiguousUrl';
|
||||||
|
} else if (ambiguousUrl.split('/').length == 1) {
|
||||||
|
return '${referenceAbsoluteUrl.origin}/${currPathSegments.join('/')}/$ambiguousUrl';
|
||||||
|
} else {
|
||||||
|
return '${referenceAbsoluteUrl.origin}/${currPathSegments.sublist(0, currPathSegments.length - 1).join('/')}/$ambiguousUrl';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int compareAlphaNumeric(String a, String b) {
|
||||||
|
List<String> aParts = _splitAlphaNumeric(a);
|
||||||
|
List<String> bParts = _splitAlphaNumeric(b);
|
||||||
|
|
||||||
|
for (int i = 0; i < aParts.length && i < bParts.length; i++) {
|
||||||
|
String aPart = aParts[i];
|
||||||
|
String bPart = bParts[i];
|
||||||
|
|
||||||
|
bool aIsNumber = _isNumeric(aPart);
|
||||||
|
bool bIsNumber = _isNumeric(bPart);
|
||||||
|
|
||||||
|
if (aIsNumber && bIsNumber) {
|
||||||
|
int aNumber = int.parse(aPart);
|
||||||
|
int bNumber = int.parse(bPart);
|
||||||
|
int cmp = aNumber.compareTo(bNumber);
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
} else if (!aIsNumber && !bIsNumber) {
|
||||||
|
int cmp = aPart.compareTo(bPart);
|
||||||
|
if (cmp != 0) {
|
||||||
|
return cmp;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Alphanumeric strings come before numeric strings
|
||||||
|
return aIsNumber ? 1 : -1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return aParts.length.compareTo(bParts.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> _splitAlphaNumeric(String s) {
|
||||||
|
List<String> parts = [];
|
||||||
|
StringBuffer sb = StringBuffer();
|
||||||
|
|
||||||
|
bool isNumeric = _isNumeric(s[0]);
|
||||||
|
sb.write(s[0]);
|
||||||
|
|
||||||
|
for (int i = 1; i < s.length; i++) {
|
||||||
|
bool currentIsNumeric = _isNumeric(s[i]);
|
||||||
|
if (currentIsNumeric == isNumeric) {
|
||||||
|
sb.write(s[i]);
|
||||||
|
} else {
|
||||||
|
parts.add(sb.toString());
|
||||||
|
sb.clear();
|
||||||
|
sb.write(s[i]);
|
||||||
|
isNumeric = currentIsNumeric;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
parts.add(sb.toString());
|
||||||
|
|
||||||
|
return parts;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool _isNumeric(String s) {
|
||||||
|
return s.codeUnitAt(0) >= 48 && s.codeUnitAt(0) <= 57;
|
||||||
|
}
|
||||||
|
|
||||||
class HTML extends AppSource {
|
class HTML extends AppSource {
|
||||||
|
HTML() {
|
||||||
|
overrideEligible = true;
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
int compareAlphaNumeric(String a, String b) {
|
|
||||||
List<String> aParts = _splitAlphaNumeric(a);
|
|
||||||
List<String> bParts = _splitAlphaNumeric(b);
|
|
||||||
|
|
||||||
for (int i = 0; i < aParts.length && i < bParts.length; i++) {
|
|
||||||
String aPart = aParts[i];
|
|
||||||
String bPart = bParts[i];
|
|
||||||
|
|
||||||
bool aIsNumber = _isNumeric(aPart);
|
|
||||||
bool bIsNumber = _isNumeric(bPart);
|
|
||||||
|
|
||||||
if (aIsNumber && bIsNumber) {
|
|
||||||
int aNumber = int.parse(aPart);
|
|
||||||
int bNumber = int.parse(bPart);
|
|
||||||
int cmp = aNumber.compareTo(bNumber);
|
|
||||||
if (cmp != 0) {
|
|
||||||
return cmp;
|
|
||||||
}
|
|
||||||
} else if (!aIsNumber && !bIsNumber) {
|
|
||||||
int cmp = aPart.compareTo(bPart);
|
|
||||||
if (cmp != 0) {
|
|
||||||
return cmp;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Alphanumeric strings come before numeric strings
|
|
||||||
return aIsNumber ? 1 : -1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return aParts.length.compareTo(bParts.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> _splitAlphaNumeric(String s) {
|
|
||||||
List<String> parts = [];
|
|
||||||
StringBuffer sb = StringBuffer();
|
|
||||||
|
|
||||||
bool isNumeric = _isNumeric(s[0]);
|
|
||||||
sb.write(s[0]);
|
|
||||||
|
|
||||||
for (int i = 1; i < s.length; i++) {
|
|
||||||
bool currentIsNumeric = _isNumeric(s[i]);
|
|
||||||
if (currentIsNumeric == isNumeric) {
|
|
||||||
sb.write(s[i]);
|
|
||||||
} else {
|
|
||||||
parts.add(sb.toString());
|
|
||||||
sb.clear();
|
|
||||||
sb.write(s[i]);
|
|
||||||
isNumeric = currentIsNumeric;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
parts.add(sb.toString());
|
|
||||||
|
|
||||||
return parts;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool _isNumeric(String s) {
|
|
||||||
return s.codeUnitAt(0) >= 48 && s.codeUnitAt(0) <= 57;
|
|
||||||
}
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<APKDetails> getLatestAPKDetails(
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
var uri = Uri.parse(standardUrl);
|
var uri = Uri.parse(standardUrl);
|
||||||
Response res = await get(uri);
|
Response res = await sourceRequest(standardUrl);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
List<String> links = parse(res.body)
|
List<String> links = parse(res.body)
|
||||||
.querySelectorAll('a')
|
.querySelectorAll('a')
|
||||||
.map((element) => element.attributes['href'] ?? '')
|
.map((element) => element.attributes['href'] ?? '')
|
||||||
.where((element) => element.toLowerCase().endsWith('.apk'))
|
.where((element) =>
|
||||||
|
Uri.parse(element).path.toLowerCase().endsWith('.apk'))
|
||||||
.toList();
|
.toList();
|
||||||
links.sort(
|
links.sort(
|
||||||
(a, b) => compareAlphaNumeric(a.split('/').last, b.split('/').last));
|
(a, b) => compareAlphaNumeric(a.split('/').last, b.split('/').last));
|
||||||
@ -95,25 +120,8 @@ class HTML extends AppSource {
|
|||||||
var rel = links.last;
|
var rel = links.last;
|
||||||
var apkName = rel.split('/').last;
|
var apkName = rel.split('/').last;
|
||||||
var version = apkName.substring(0, apkName.length - 4);
|
var version = apkName.substring(0, apkName.length - 4);
|
||||||
List<String> apkUrls = [rel].map((e) {
|
List<String> apkUrls =
|
||||||
try {
|
[rel].map((e) => ensureAbsoluteUrl(e, uri)).toList();
|
||||||
Uri.parse(e).origin;
|
|
||||||
return e;
|
|
||||||
} catch (err) {
|
|
||||||
// is relative
|
|
||||||
}
|
|
||||||
var currPathSegments = uri.path
|
|
||||||
.split('/')
|
|
||||||
.where((element) => element.trim().isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
if (e.startsWith('/') || currPathSegments.isEmpty) {
|
|
||||||
return '${uri.origin}/$e';
|
|
||||||
} else if (e.split('/').length == 1) {
|
|
||||||
return '${uri.origin}/${currPathSegments.join('/')}/$e';
|
|
||||||
} else {
|
|
||||||
return '${uri.origin}/${currPathSegments.sublist(0, currPathSegments.length - 1).join('/')}/$e';
|
|
||||||
}
|
|
||||||
}).toList();
|
|
||||||
return APKDetails(
|
return APKDetails(
|
||||||
version, getApkUrlsFromUrls(apkUrls), AppNames(uri.host, tr('app')));
|
version, getApkUrlsFromUrls(apkUrls), AppNames(uri.host, tr('app')));
|
||||||
} else {
|
} else {
|
||||||
|
@ -31,8 +31,8 @@ class IzzyOnDroid extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
String? appId = tryInferringAppId(standardUrl);
|
String? appId = tryInferringAppId(standardUrl);
|
||||||
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
|
return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse(
|
||||||
await get(
|
await sourceRequest(
|
||||||
Uri.parse('https://apt.izzysoft.de/fdroid/api/v1/packages/$appId')),
|
'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'),
|
||||||
'https://android.izzysoft.de/frepo/$appId',
|
'https://android.izzysoft.de/frepo/$appId',
|
||||||
standardUrl);
|
standardUrl);
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
|
|
||||||
class Jenkins extends AppSource {
|
class Jenkins extends AppSource {
|
||||||
Jenkins() {
|
Jenkins() {
|
||||||
|
overrideEligible = true;
|
||||||
overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
|
overrideVersionDetectionFormDefault('releaseDateAsVersion', true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -30,7 +31,7 @@ class Jenkins extends AppSource {
|
|||||||
) async {
|
) async {
|
||||||
standardUrl = trimJobUrl(standardUrl);
|
standardUrl = trimJobUrl(standardUrl);
|
||||||
Response res =
|
Response res =
|
||||||
await get(Uri.parse('$standardUrl/lastSuccessfulBuild/api/json'));
|
await sourceRequest('$standardUrl/lastSuccessfulBuild/api/json');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var json = jsonDecode(res.body);
|
var json = jsonDecode(res.body);
|
||||||
var releaseDate = json['timestamp'] == null
|
var releaseDate = json['timestamp'] == null
|
||||||
@ -55,9 +56,6 @@ class Jenkins extends AppSource {
|
|||||||
.where((url) =>
|
.where((url) =>
|
||||||
url.value.isNotEmpty && url.key.toLowerCase().endsWith('.apk'))
|
url.value.isNotEmpty && url.key.toLowerCase().endsWith('.apk'))
|
||||||
.toList();
|
.toList();
|
||||||
if (apkUrls.isEmpty) {
|
|
||||||
throw NoAPKError();
|
|
||||||
}
|
|
||||||
return APKDetails(
|
return APKDetails(
|
||||||
version,
|
version,
|
||||||
apkUrls,
|
apkUrls,
|
||||||
|
@ -28,7 +28,7 @@ class Mullvad extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(Uri.parse('$standardUrl/en/download/android'));
|
Response res = await sourceRequest('$standardUrl/en/download/android');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var versions = parse(res.body)
|
var versions = parse(res.body)
|
||||||
.querySelectorAll('p')
|
.querySelectorAll('p')
|
||||||
|
@ -78,7 +78,7 @@ class NeutronCode extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(Uri.parse(standardUrl));
|
Response res = await sourceRequest(standardUrl);
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var http = parse(res.body);
|
var http = parse(res.body);
|
||||||
var name = http.querySelector('.pd-title')?.innerHtml;
|
var name = http.querySelector('.pd-title')?.innerHtml;
|
||||||
|
@ -19,7 +19,7 @@ class Signal extends AppSource {
|
|||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res =
|
Response res =
|
||||||
await get(Uri.parse('https://updates.$host/android/latest.json'));
|
await sourceRequest('https://updates.$host/android/latest.json');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var json = jsonDecode(res.body);
|
var json = jsonDecode(res.body);
|
||||||
String? apkUrl = json['url'];
|
String? apkUrl = json['url'];
|
||||||
|
@ -6,6 +6,7 @@ import 'package:obtainium/providers/source_provider.dart';
|
|||||||
class SourceForge extends AppSource {
|
class SourceForge extends AppSource {
|
||||||
SourceForge() {
|
SourceForge() {
|
||||||
host = 'sourceforge.net';
|
host = 'sourceforge.net';
|
||||||
|
overrideEligible = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -29,7 +30,7 @@ class SourceForge extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(Uri.parse('$standardUrl/rss?path=/'));
|
Response res = await sourceRequest('$standardUrl/rss?path=/');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var parsedHtml = parse(res.body);
|
var parsedHtml = parse(res.body);
|
||||||
var allDownloadLinks =
|
var allDownloadLinks =
|
||||||
|
101
lib/app_sources/sourcehut.dart
Normal file
101
lib/app_sources/sourcehut.dart
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
import 'package:html/parser.dart';
|
||||||
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/app_sources/github.dart';
|
||||||
|
import 'package:obtainium/app_sources/html.dart';
|
||||||
|
import 'package:obtainium/custom_errors.dart';
|
||||||
|
import 'package:obtainium/providers/source_provider.dart';
|
||||||
|
import 'package:obtainium/components/generated_form.dart';
|
||||||
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
|
|
||||||
|
class SourceHut extends AppSource {
|
||||||
|
SourceHut() {
|
||||||
|
host = 'git.sr.ht';
|
||||||
|
overrideEligible = true;
|
||||||
|
|
||||||
|
additionalSourceAppSpecificSettingFormItems = [
|
||||||
|
[
|
||||||
|
GeneratedFormSwitch('fallbackToOlderReleases',
|
||||||
|
label: tr('fallbackToOlderReleases'), defaultValue: true)
|
||||||
|
]
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String? changeLogPageFromStandardUrl(String standardUrl) => standardUrl;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
Uri standardUri = Uri.parse(standardUrl);
|
||||||
|
String appName = standardUri.pathSegments.last;
|
||||||
|
bool fallbackToOlderReleases =
|
||||||
|
additionalSettings['fallbackToOlderReleases'] == true;
|
||||||
|
Response res = await sourceRequest('$standardUrl/refs/rss.xml');
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
var parsedHtml = parse(res.body);
|
||||||
|
List<APKDetails> apkDetailsList = [];
|
||||||
|
int ind = 0;
|
||||||
|
|
||||||
|
for (var entry in parsedHtml.querySelectorAll('item').sublist(0, 6)) {
|
||||||
|
// Limit 5 for speed
|
||||||
|
if (!fallbackToOlderReleases && ind > 0) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
String? version = entry.querySelector('title')?.text.trim();
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
String? releaseDateString = entry.querySelector('pubDate')?.innerHtml;
|
||||||
|
var link = entry.querySelector('link');
|
||||||
|
String releasePage = '$standardUrl/refs/$version';
|
||||||
|
DateTime? releaseDate = releaseDateString != null
|
||||||
|
? DateFormat('EEE, dd MMM yyyy HH:mm:ss Z').parse(releaseDateString)
|
||||||
|
: null;
|
||||||
|
var res2 = await sourceRequest(releasePage);
|
||||||
|
List<MapEntry<String, String>> apkUrls = [];
|
||||||
|
if (res2.statusCode == 200) {
|
||||||
|
apkUrls = getApkUrlsFromUrls(parse(res2.body)
|
||||||
|
.querySelectorAll('a')
|
||||||
|
.map((e) => e.attributes['href'] ?? '')
|
||||||
|
.where((e) => e.toLowerCase().endsWith('.apk'))
|
||||||
|
.map((e) => ensureAbsoluteUrl(e, standardUri))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
apkDetailsList.add(APKDetails(
|
||||||
|
version,
|
||||||
|
apkUrls,
|
||||||
|
AppNames(entry.querySelector('author')?.innerHtml.trim() ?? appName,
|
||||||
|
appName),
|
||||||
|
releaseDate: releaseDate));
|
||||||
|
ind++;
|
||||||
|
}
|
||||||
|
if (apkDetailsList.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
if (fallbackToOlderReleases) {
|
||||||
|
if (additionalSettings['trackOnly'] != true) {
|
||||||
|
apkDetailsList =
|
||||||
|
apkDetailsList.where((e) => e.apkUrls.isNotEmpty).toList();
|
||||||
|
}
|
||||||
|
if (apkDetailsList.isEmpty) {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return apkDetailsList.first;
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -29,7 +29,7 @@ class SteamMobile extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(Uri.parse('https://$host/mobile'));
|
Response res = await sourceRequest('https://$host/mobile');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var apkNamePrefix = additionalSettings['app'] as String?;
|
var apkNamePrefix = additionalSettings['app'] as String?;
|
||||||
if (apkNamePrefix == null) {
|
if (apkNamePrefix == null) {
|
||||||
|
@ -20,7 +20,7 @@ class TelegramApp extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(Uri.parse('https://t.me/s/TAndroidAPK'));
|
Response res = await sourceRequest('https://t.me/s/TAndroidAPK');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var http = parse(res.body);
|
var http = parse(res.body);
|
||||||
var messages =
|
var messages =
|
||||||
|
@ -19,8 +19,8 @@ class VLC extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(
|
Response res = await sourceRequest(
|
||||||
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
|
'https://www.videolan.org/vlc/download-android.html');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var dwUrlBase = 'get.videolan.org/vlc-android';
|
var dwUrlBase = 'get.videolan.org/vlc-android';
|
||||||
var dwLinks = parse(res.body)
|
var dwLinks = parse(res.body)
|
||||||
@ -38,7 +38,7 @@ class VLC extends AppSource {
|
|||||||
throw NoVersionError();
|
throw NoVersionError();
|
||||||
}
|
}
|
||||||
String? targetUrl = 'https://$dwUrlBase/$version/';
|
String? targetUrl = 'https://$dwUrlBase/$version/';
|
||||||
Response res2 = await get(Uri.parse(targetUrl));
|
Response res2 = await sourceRequest(targetUrl);
|
||||||
String mirrorDwBase =
|
String mirrorDwBase =
|
||||||
'https://plug-mirror.rcac.purdue.edu/vlc/vlc-android/$version/';
|
'https://plug-mirror.rcac.purdue.edu/vlc/vlc-android/$version/';
|
||||||
List<String> apkUrls = [];
|
List<String> apkUrls = [];
|
||||||
|
@ -14,21 +14,21 @@ class WhatsApp extends AppSource {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<String> apkUrlPrefetchModifier(String apkUrl) async {
|
Future<String> apkUrlPrefetchModifier(
|
||||||
Response res = await get(Uri.parse('https://www.whatsapp.com/android'));
|
String apkUrl, String standardUrl) async {
|
||||||
|
Response res = await sourceRequest('https://www.whatsapp.com/android');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var targetLinks = parse(res.body)
|
var targetLinks = parse(res.body)
|
||||||
.querySelectorAll('a')
|
.querySelectorAll('a')
|
||||||
.map((e) => e.attributes['href'])
|
.map((e) => e.attributes['href'] ?? '')
|
||||||
.where((e) => e != null)
|
.where((e) => e.isNotEmpty)
|
||||||
.where((e) =>
|
.where((e) =>
|
||||||
e!.contains('scontent.whatsapp.net') &&
|
e.contains('content.whatsapp.net') && e.contains('WhatsApp.apk'))
|
||||||
e.contains('WhatsApp.apk'))
|
|
||||||
.toList();
|
.toList();
|
||||||
if (targetLinks.isEmpty) {
|
if (targetLinks.isEmpty) {
|
||||||
throw NoAPKError();
|
throw NoAPKError();
|
||||||
}
|
}
|
||||||
return targetLinks[0]!;
|
return targetLinks[0];
|
||||||
} else {
|
} else {
|
||||||
throw getObtainiumHttpError(res);
|
throw getObtainiumHttpError(res);
|
||||||
}
|
}
|
||||||
@ -39,7 +39,7 @@ class WhatsApp extends AppSource {
|
|||||||
String standardUrl,
|
String standardUrl,
|
||||||
Map<String, dynamic> additionalSettings,
|
Map<String, dynamic> additionalSettings,
|
||||||
) async {
|
) async {
|
||||||
Response res = await get(Uri.parse('https://www.whatsapp.com/android'));
|
Response res = await sourceRequest('https://www.whatsapp.com/android');
|
||||||
if (res.statusCode == 200) {
|
if (res.statusCode == 200) {
|
||||||
var targetElements = parse(res.body)
|
var targetElements = parse(res.body)
|
||||||
.querySelectorAll('p')
|
.querySelectorAll('p')
|
||||||
|
@ -302,8 +302,10 @@ class _AddAppPageState extends State<AddAppPage> {
|
|||||||
'overrideSource',
|
'overrideSource',
|
||||||
defaultValue: HTML().runtimeType.toString(),
|
defaultValue: HTML().runtimeType.toString(),
|
||||||
[
|
[
|
||||||
...sourceProvider.sources.map(
|
...sourceProvider.sources
|
||||||
(s) => MapEntry(s.runtimeType.toString(), s.name))
|
.where((s) => s.overrideEligible)
|
||||||
|
.map((s) =>
|
||||||
|
MapEntry(s.runtimeType.toString(), s.name))
|
||||||
],
|
],
|
||||||
label: tr('overrideSource'))
|
label: tr('overrideSource'))
|
||||||
]
|
]
|
||||||
|
@ -125,10 +125,13 @@ class AppsProvider with ChangeNotifier {
|
|||||||
}
|
}
|
||||||
|
|
||||||
downloadFile(String url, String fileName, Function? onProgress,
|
downloadFile(String url, String fileName, Function? onProgress,
|
||||||
{bool useExisting = true}) async {
|
{bool useExisting = true, Map<String, String>? headers}) async {
|
||||||
var destDir = (await getExternalCacheDirectories())!.first.path;
|
var destDir = (await getExternalCacheDirectories())!.first.path;
|
||||||
StreamedResponse response =
|
var req = Request('GET', Uri.parse(url));
|
||||||
await Client().send(Request('GET', Uri.parse(url)));
|
if (headers != null) {
|
||||||
|
req.headers.addAll(headers);
|
||||||
|
}
|
||||||
|
StreamedResponse response = await Client().send(req);
|
||||||
File downloadedFile = File('$destDir/$fileName');
|
File downloadedFile = File('$destDir/$fileName');
|
||||||
if (!(downloadedFile.existsSync() && useExisting)) {
|
if (!(downloadedFile.existsSync() && useExisting)) {
|
||||||
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
File tempDownloadedFile = File('${downloadedFile.path}.part');
|
||||||
@ -170,15 +173,16 @@ class AppsProvider with ChangeNotifier {
|
|||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
String downloadUrl = await SourceProvider()
|
AppSource source = SourceProvider()
|
||||||
.getSource(app.url, overrideSource: app.overrideSource)
|
.getSource(app.url, overrideSource: app.overrideSource);
|
||||||
.apkUrlPrefetchModifier(app.apkUrls[app.preferredApkIndex].value);
|
String downloadUrl = await source.apkUrlPrefetchModifier(
|
||||||
|
app.apkUrls[app.preferredApkIndex].value, app.url);
|
||||||
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
|
var fileName = '${app.id}-${downloadUrl.hashCode}.apk';
|
||||||
var notif = DownloadNotification(app.finalName, 100);
|
var notif = DownloadNotification(app.finalName, 100);
|
||||||
notificationsProvider?.cancel(notif.id);
|
notificationsProvider?.cancel(notif.id);
|
||||||
int? prevProg;
|
int? prevProg;
|
||||||
File downloadedFile =
|
File downloadedFile = await downloadFile(downloadUrl, fileName,
|
||||||
await downloadFile(downloadUrl, fileName, (double? progress) {
|
headers: source.requestHeaders, (double? progress) {
|
||||||
int? prog = progress?.ceil();
|
int? prog = progress?.ceil();
|
||||||
if (apps[app.id] != null) {
|
if (apps[app.id] != null) {
|
||||||
apps[app.id]!.downloadProgress = progress;
|
apps[app.id]!.downloadProgress = progress;
|
||||||
|
@ -7,7 +7,9 @@ import 'package:device_info_plus/device_info_plus.dart';
|
|||||||
import 'package:easy_localization/easy_localization.dart';
|
import 'package:easy_localization/easy_localization.dart';
|
||||||
import 'package:html/dom.dart';
|
import 'package:html/dom.dart';
|
||||||
import 'package:http/http.dart';
|
import 'package:http/http.dart';
|
||||||
|
import 'package:obtainium/app_sources/apkcombo.dart';
|
||||||
import 'package:obtainium/app_sources/apkmirror.dart';
|
import 'package:obtainium/app_sources/apkmirror.dart';
|
||||||
|
import 'package:obtainium/app_sources/apkpure.dart';
|
||||||
import 'package:obtainium/app_sources/codeberg.dart';
|
import 'package:obtainium/app_sources/codeberg.dart';
|
||||||
import 'package:obtainium/app_sources/fdroid.dart';
|
import 'package:obtainium/app_sources/fdroid.dart';
|
||||||
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
import 'package:obtainium/app_sources/fdroidrepo.dart';
|
||||||
@ -20,6 +22,7 @@ import 'package:obtainium/app_sources/mullvad.dart';
|
|||||||
import 'package:obtainium/app_sources/neutroncode.dart';
|
import 'package:obtainium/app_sources/neutroncode.dart';
|
||||||
import 'package:obtainium/app_sources/signal.dart';
|
import 'package:obtainium/app_sources/signal.dart';
|
||||||
import 'package:obtainium/app_sources/sourceforge.dart';
|
import 'package:obtainium/app_sources/sourceforge.dart';
|
||||||
|
import 'package:obtainium/app_sources/sourcehut.dart';
|
||||||
import 'package:obtainium/app_sources/steammobile.dart';
|
import 'package:obtainium/app_sources/steammobile.dart';
|
||||||
import 'package:obtainium/app_sources/telegramapp.dart';
|
import 'package:obtainium/app_sources/telegramapp.dart';
|
||||||
import 'package:obtainium/app_sources/vlc.dart';
|
import 'package:obtainium/app_sources/vlc.dart';
|
||||||
@ -315,6 +318,7 @@ abstract class AppSource {
|
|||||||
late String name;
|
late String name;
|
||||||
bool enforceTrackOnly = false;
|
bool enforceTrackOnly = false;
|
||||||
bool changeLogIfAnyIsMarkDown = true;
|
bool changeLogIfAnyIsMarkDown = true;
|
||||||
|
bool overrideEligible = false;
|
||||||
|
|
||||||
AppSource() {
|
AppSource() {
|
||||||
name = runtimeType.toString();
|
name = runtimeType.toString();
|
||||||
@ -344,6 +348,18 @@ abstract class AppSource {
|
|||||||
return url;
|
return url;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Map<String, String>? get requestHeaders => null;
|
||||||
|
|
||||||
|
Future<Response> sourceRequest(String url) async {
|
||||||
|
if (requestHeaders != null) {
|
||||||
|
var req = Request('GET', Uri.parse(url));
|
||||||
|
req.headers.addAll(requestHeaders!);
|
||||||
|
return Response.fromStream(await Client().send(req));
|
||||||
|
} else {
|
||||||
|
return get(Uri.parse(url));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
String sourceSpecificStandardizeURL(String url) {
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
throw NotImplementedError();
|
throw NotImplementedError();
|
||||||
}
|
}
|
||||||
@ -410,7 +426,8 @@ abstract class AppSource {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<String> apkUrlPrefetchModifier(String apkUrl) async {
|
Future<String> apkUrlPrefetchModifier(
|
||||||
|
String apkUrl, String standardUrl) async {
|
||||||
return apkUrl;
|
return apkUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -459,7 +476,10 @@ class SourceProvider {
|
|||||||
FDroidRepo(),
|
FDroidRepo(),
|
||||||
Jenkins(),
|
Jenkins(),
|
||||||
SourceForge(),
|
SourceForge(),
|
||||||
|
SourceHut(),
|
||||||
APKMirror(),
|
APKMirror(),
|
||||||
|
APKPure(),
|
||||||
|
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
|
||||||
Mullvad(),
|
Mullvad(),
|
||||||
Signal(),
|
Signal(),
|
||||||
VLC(),
|
VLC(),
|
||||||
|
Reference in New Issue
Block a user