Compare commits

...

1 Commits

27 changed files with 321 additions and 170 deletions

View File

@ -45,7 +45,7 @@ class APKCombo extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
return html
.querySelectorAll('#variants-tab > div > ul > li')
.map((e) {
@ -96,7 +96,7 @@ class APKCombo extends AppSource {
if (preres.statusCode != 200) {
throw getObtainiumHttpError(preres);
}
var res = parse(preres.body);
var res = parse(preres.data);
String? version = res.querySelector('div.version')?.text.trim();
if (version == null) {
throw NoVersionError();

View File

@ -1,8 +1,8 @@
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -62,7 +62,7 @@ class APKMirror extends AppSource {
: null;
Response res = await sourceRequest('$standardUrl/feed', additionalSettings);
if (res.statusCode == 200) {
var items = parse(res.body).querySelectorAll('item');
var items = parse(res.data).querySelectorAll('item');
dynamic targetRelease;
for (int i = 0; i < items.length; i++) {
if (!fallbackToOlderReleases && i > 0) break;

View File

@ -61,8 +61,8 @@ class APKPure extends AppSource {
var res = await sourceRequest('$standardUrl/download', additionalSettings);
var resChangelog = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200 && resChangelog.statusCode == 200) {
var html = parse(res.body);
var htmlChangelog = parse(resChangelog.body);
var html = parse(res.data);
var htmlChangelog = parse(resChangelog.data);
String? version = html.querySelector('span.info-sdk span')?.text.trim();
if (version == null) {
throw NoVersionError();

View File

@ -38,10 +38,10 @@ class Aptoide extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body);
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.data);
String? id;
if (idMatch != null) {
id = res.body.substring(idMatch.start + 12, idMatch.end);
id = res.data.substring(idMatch.start + 12, idMatch.end);
} else {
throw NoReleasesError();
}
@ -50,7 +50,7 @@ class Aptoide extends AppSource {
if (res2.statusCode != 200) {
throw getObtainiumHttpError(res);
}
return jsonDecode(res2.body)?['nodes']?['meta']?['data'];
return jsonDecode(res2.data)?['nodes']?['meta']?['data'];
}
@override

View File

@ -1,8 +1,8 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/app_sources/gitlab.dart';
import 'package:obtainium/components/generated_form.dart';
@ -82,7 +82,7 @@ class FDroid extends AppSource {
var res = await sourceRequest(
'https://gitlab.com/fdroid/fdroiddata/-/raw/master/metadata/$appId.yml',
additionalSettings);
var lines = res.body.split('\n');
var lines = res.data.split('\n');
var authorLines = lines.where((l) => l.startsWith('AuthorName: '));
if (authorLines.isNotEmpty) {
details.names.author =
@ -112,7 +112,7 @@ class FDroid extends AppSource {
details.changeLog = (await sourceRequest(
details.changeLog!.replaceFirst('/blob/', '/raw/'),
additionalSettings))
.body;
.data;
}
}
} catch (e) {
@ -132,7 +132,7 @@ class FDroid extends AppSource {
'https://search.${hosts[0]}/?q=${Uri.encodeQueryComponent(query)}', {});
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
parse(res.body).querySelectorAll('.package-header').forEach((e) {
parse(res.data).querySelectorAll('.package-header').forEach((e) {
String? url = e.attributes['href'];
if (url != null) {
try {
@ -172,7 +172,7 @@ class FDroid extends AppSource {
? additionalSettings['apkFilterRegEx']
: null;
if (res.statusCode == 200) {
var response = jsonDecode(res.body);
var response = jsonDecode(res.data);
List<dynamic> releases = response['packages'] ?? [];
if (apkFilterRegEx != null) {
releases = releases.where((rel) {

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -61,9 +61,10 @@ class FDroidRepo extends AppSource {
throw NoReleasesError();
}
url = removeQueryParamsFromUrl(standardizeUrl(url));
var res = await sourceRequestWithURLVariants(url, {});
var ress = await sourceRequestWithURLVariants(url, {});
var res = ress.value;
if (res.statusCode == 200) {
var body = parse(res.body);
var body = parse(res.data);
Map<String, List<String>> results = {};
body.querySelectorAll('application').toList().forEach((app) {
String appId = app.attributes['id']!;
@ -74,7 +75,7 @@ class FDroidRepo extends AppSource {
appName.contains(query) ||
appDesc.contains(query)) {
results[
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
'${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}?appId=$appId'] = [
appName,
appDesc
];
@ -107,24 +108,24 @@ class FDroidRepo extends AppSource {
return app;
}
Future<Response> sourceRequestWithURLVariants(
Future<MapEntry<String, Response>> sourceRequestWithURLVariants(
String url,
Map<String, dynamic> additionalSettings,
) async {
var res = await sourceRequest(
'$url${url.endsWith('/index.xml') ? '' : '/index.xml'}',
additionalSettings);
var finalUrl = '$url${url.endsWith('/index.xml') ? '' : '/index.xml'}';
var res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode != 200) {
var base = url.endsWith('/index.xml')
? url.split('/').reversed.toList().sublist(1).reversed.join('/')
: url;
res = await sourceRequest('$base/repo/index.xml', additionalSettings);
finalUrl = '$base/repo/index.xml';
res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode != 200) {
res = await sourceRequest(
'$base/fdroid/repo/index.xml', additionalSettings);
finalUrl = '$base/fdroid/repo/index.xml';
res = await sourceRequest(finalUrl, additionalSettings);
}
}
return res;
return MapEntry(finalUrl, res);
}
@override
@ -142,10 +143,11 @@ class FDroidRepo extends AppSource {
if (appIdOrName == null) {
throw NoReleasesError();
}
var res =
var ress =
await sourceRequestWithURLVariants(standardUrl, additionalSettings);
var res = ress.value;
if (res.statusCode == 200) {
var body = parse(res.body);
var body = parse(res.data);
var foundApps = body.querySelectorAll('application').where((element) {
return element.attributes['id'] == appIdOrName;
}).toList();
@ -193,7 +195,7 @@ class FDroidRepo extends AppSource {
}
List<String> apkUrls = latestVersionReleases
.map((e) =>
'${res.request!.url.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
'${ress.value.toString().split('/').reversed.toList().sublist(1).reversed.join('/')}/${e.querySelector('apkname')!.innerHtml}')
.toList();
return APKDetails(latestVersion, getApkUrlsFromUrls(apkUrls),
AppNames(authorName, appName),

View File

@ -1,8 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
@ -112,12 +112,12 @@ class GitHub extends AppSource {
];
for (var path in possibleBuildGradleLocations) {
try {
var res = await sourceRequest(
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path',
additionalSettings);
var finalUrl =
'${await convertStandardUrlToAPIUrl(standardUrl, additionalSettings)}/contents/$path';
var res = await sourceRequest(finalUrl, additionalSettings);
if (res.statusCode == 200) {
try {
var body = jsonDecode(res.body);
var body = jsonDecode(res.data);
var trimmedLines = utf8
.decode(base64
.decode(body['content'].toString().split('\n').join('')))
@ -143,7 +143,7 @@ class GitHub extends AppSource {
}
} catch (err) {
LogsProvider().add(
'Error parsing build.gradle from ${res.request!.url.toString()}: ${err.toString()}');
'Error parsing build.gradle from $finalUrl: ${err.toString()}');
}
}
} catch (err) {
@ -256,11 +256,11 @@ class GitHub extends AppSource {
}
throw getObtainiumHttpError(res);
}
latestRelease = jsonDecode(res.body);
latestRelease = jsonDecode(res.data);
}
Response res = await sourceRequest(requestUrl, additionalSettings);
if (res.statusCode == 200) {
var releases = jsonDecode(res.body) as List<dynamic>;
var releases = jsonDecode(res.data) as List<dynamic>;
if (latestRelease != null) {
var latestTag = latestRelease['tag_name'] ?? latestRelease['name'];
if (releases
@ -466,7 +466,7 @@ class GitHub extends AppSource {
? int.parse(querySettings['minStarCount'])
: 0;
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body)[rootProp] as List<dynamic>)) {
for (var e in (jsonDecode(res.data)[rootProp] as List<dynamic>)) {
if ((e['stargazers_count'] ?? e['stars_count'] ?? 0) >= minStarCount) {
urlsWithDescriptions.addAll({
e['html_url'] as String: [
@ -500,11 +500,13 @@ class GitHub extends AppSource {
}
rateLimitErrorCheck(Response res) {
if (res.headers['x-ratelimit-remaining'] == '0') {
String? rateLimitHeader;
if (res.headers.map['x-ratelimit-remaining']?.isNotEmpty == true) {
rateLimitHeader = res.headers.map['x-ratelimit-remaining']![0];
}
if (rateLimitHeader == '0') {
throw RateLimitError(
(int.parse(res.headers['x-ratelimit-reset'] ?? '1800000000') /
60000000)
.round());
(int.parse(rateLimitHeader ?? '1800000000') / 60000000).round());
}
}
}

View File

@ -1,8 +1,8 @@
import 'dart:convert';
import 'dart:io';
import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/settings_provider.dart';
@ -81,7 +81,7 @@ class GitLab extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var json = jsonDecode(res.body) as List<dynamic>;
var json = jsonDecode(res.data) as List<dynamic>;
Map<String, List<String>> results = {};
for (var element in json) {
results['https://${hosts[0]}/${element['path_with_namespace']}'] = [
@ -131,7 +131,7 @@ class GitLab extends AppSource {
// Extract .apk details from received data
Iterable<APKDetails> apkDetailsList = [];
var json = jsonDecode(res.body) as List<dynamic>;
var json = jsonDecode(res.data) as List<dynamic>;
apkDetailsList = json.map((e) {
var apkUrlsFromAssets = (e['assets']?['links'] as List<dynamic>? ?? [])
.map((e) {
@ -152,9 +152,8 @@ class GitLab extends AppSource {
var apkUrlsSet = apkUrlsFromAssets.toSet();
apkUrlsSet.addAll(uploadedAPKsFromDescription);
var releaseDateString = e['released_at'] ?? e['created_at'];
DateTime? releaseDate = releaseDateString != null
? DateTime.parse(releaseDateString)
: null;
DateTime? releaseDate =
releaseDateString != null ? DateTime.parse(releaseDateString) : null;
return APKDetails(
e['tag_name'] ?? e['name'],
getApkUrlsFromUrls(apkUrlsSet.toList()),

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/apps_provider.dart';
@ -213,11 +213,11 @@ class HTML extends AppSource {
// Given an HTTP response, grab some links according to the common additional settings
// (those that apply to intermediate and final steps)
Future<List<MapEntry<String, String>>> grabLinksCommon(
Response res, Map<String, dynamic> additionalSettings) async {
Response res, Uri url, Map<String, dynamic> additionalSettings) async {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
List<MapEntry<String, String>> allLinks = html
.querySelectorAll('a')
.map((element) => MapEntry(
@ -226,13 +226,12 @@ class HTML extends AppSource {
? element.text
: (element.attributes['href'] ?? '').split('/').last))
.where((element) => element.key.isNotEmpty)
.map((e) =>
MapEntry(ensureAbsoluteUrl(e.key, res.request!.url), e.value))
.map((e) => MapEntry(ensureAbsoluteUrl(e.key, url), e.value))
.toList();
if (allLinks.isEmpty) {
allLinks = RegExp(
r'(http|ftp|https)://([\w_-]+(?:(?:\.[\w_-]+)+))([\w.,@?^=%&:/~+#-]*[\w@?^=%&/~+#-])?')
.allMatches(res.body)
.allMatches(res.data)
.map((match) =>
MapEntry(match.group(0)!, match.group(0)?.split('/').last ?? ''))
.toList();
@ -285,6 +284,7 @@ class HTML extends AppSource {
for (int i = 0; i < (additionalSettings['intermediateLink'].length); i++) {
var intLinks = await grabLinksCommon(
await sourceRequest(currentUrl, additionalSettings),
Uri.parse(currentUrl),
additionalSettings['intermediateLink'][i]);
if (intLinks.isEmpty) {
throw NoReleasesError();
@ -298,8 +298,9 @@ class HTML extends AppSource {
if (additionalSettings['directAPKLink'] != true) {
Response res = await sourceRequest(currentUrl, additionalSettings);
versionExtractionWholePageString =
res.body.split('\r\n').join('\n').split('\n').join('\\n');
links = await grabLinksCommon(res, additionalSettings);
res.data.split('\r\n').join('\n').split('\n').join('\\n');
links =
await grabLinksCommon(res, Uri.parse(currentUrl), additionalSettings);
links = filterApks(links, additionalSettings['apkFilterRegEx'],
additionalSettings['invertAPKFilter']);
if (links.isEmpty) {

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -57,8 +57,8 @@ class HuaweiAppGallery extends AppSource {
{Map<String, dynamic> additionalSettings = const {}}) async {
String dlUrl = getDlUrl(standardUrl);
Response res = await requestAppdlRedirect(dlUrl, additionalSettings);
return res.headers['location'] != null
? appIdFromRedirectDlUrl(res.headers['location']!)
return res.headers.map['location']?.isNotEmpty == true
? appIdFromRedirectDlUrl(res.headers.map['location']![0])
: null;
}
@ -72,9 +72,12 @@ class HuaweiAppGallery extends AppSource {
if (res.headers['location'] == null) {
throw NoReleasesError();
}
String appId = appIdFromRedirectDlUrl(res.headers['location']!);
var relDateStr =
res.headers['location']?.split('?')[0].split('.').reversed.toList()[1];
String appId = appIdFromRedirectDlUrl(res.headers.map['location']![0]);
var relDateStr = res.headers.map['location']?[0]
.split('?')[0]
.split('.')
.reversed
.toList()[1];
var relDateStrAdj = relDateStr?.split('');
var tempLen = relDateStrAdj?.length ?? 0;
var i = 2;

View File

@ -1,6 +1,6 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:dio/dio.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -33,7 +33,7 @@ class Jenkins extends AppSource {
Response res = await sourceRequest(
'$standardUrl/lastSuccessfulBuild/api/json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var json = jsonDecode(res.data);
var releaseDate = json['timestamp'] == null
? null
: DateTime.fromMillisecondsSinceEpoch(json['timestamp'] as int);

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -33,7 +33,7 @@ class Mullvad extends AppSource {
Response res = await sourceRequest(
'$standardUrl/en/download/android', additionalSettings);
if (res.statusCode == 200) {
var versions = parse(res.body)
var versions = parse(res.data)
.querySelectorAll('p')
.map((e) => e.innerHtml)
.where((p) => p.contains('Latest version: '))

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -83,7 +83,7 @@ class NeutronCode extends AppSource {
) async {
Response res = await sourceRequest(standardUrl, additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var http = parse(res.data);
var name = http.querySelector('.pd-title')?.innerHtml;
var filename = http.querySelector('.pd-filename .pd-float')?.innerHtml;
if (filename == null) {

View File

@ -1,5 +1,5 @@
import 'dart:convert';
import 'package:http/http.dart';
import 'package:dio/dio.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -21,7 +21,7 @@ class Signal extends AppSource {
Response res = await sourceRequest(
'https://updates.${hosts[0]}/android/latest.json', additionalSettings);
if (res.statusCode == 200) {
var json = jsonDecode(res.body);
var json = jsonDecode(res.data);
String? apkUrl = json['url'];
List<String> apkUrls = apkUrl == null ? [] : [apkUrl];
String? version = json['versionName'];

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -49,7 +49,7 @@ class SourceForge extends AppSource {
'${standardUri.origin}/${standardUri.pathSegments.sublist(0, 2).join('/')}/rss?path=/',
additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var parsedHtml = parse(res.data);
var allDownloadLinks = parsedHtml
.querySelectorAll('guid')
.map((e) => e.innerHtml)

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/html.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -55,7 +55,7 @@ class SourceHut extends AppSource {
Response res =
await sourceRequest('$standardUrl/refs/rss.xml', additionalSettings);
if (res.statusCode == 200) {
var parsedHtml = parse(res.body);
var parsedHtml = parse(res.data);
List<APKDetails> apkDetailsList = [];
int ind = 0;
@ -85,7 +85,7 @@ class SourceHut extends AppSource {
var res2 = await sourceRequest(releasePage, additionalSettings);
List<MapEntry<String, String>> apkUrls = [];
if (res2.statusCode == 200) {
apkUrls = getApkUrlsFromUrls(parse(res2.body)
apkUrls = getApkUrlsFromUrls(parse(res2.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.toLowerCase().endsWith('.apk'))

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -38,7 +38,7 @@ class SteamMobile extends AppSource {
}
String apkInURLRegexPattern =
'/$apkNamePrefix-([0-9]+\\.)*[0-9]+\\.apk\$';
var links = parse(res.body)
var links = parse(res.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => RegExp('https://.*$apkInURLRegexPattern').hasMatch(e))

View File

@ -1,6 +1,6 @@
import 'package:dio/dio.dart';
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';
@ -23,7 +23,7 @@ class TelegramApp extends AppSource {
Response res =
await sourceRequest('https://t.me/s/TAndroidAPK', additionalSettings);
if (res.statusCode == 200) {
var http = parse(res.body);
var http = parse(res.data);
var messages =
http.querySelectorAll('.tgme_widget_message_text.js-message_text');
var version = messages.isNotEmpty

View File

@ -37,7 +37,7 @@ class Uptodown extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
String? version = html.querySelector('div.version')?.innerHtml;
String? apkUrl =
'${standardUrl.split('/').reversed.toList().sublist(1).reversed.join('/')}/post-download';
@ -94,7 +94,7 @@ class Uptodown extends AppSource {
if (res.statusCode != 200) {
throw getObtainiumHttpError(res);
}
var html = parse(res.body);
var html = parse(res.data);
var finalUrlKey =
html.querySelector('.post-download')?.attributes['data-url'];
if (finalUrlKey == null) {

View File

@ -1,7 +1,8 @@
import 'package:dio/dio.dart';
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/main.dart';
import 'package:obtainium/providers/source_provider.dart';
class VLC extends AppSource {
@ -29,7 +30,7 @@ class VLC extends AppSource {
String standardUrl, Map<String, dynamic> additionalSettings) async {
Response res = await sourceRequest(dwUrlBase, additionalSettings);
if (res.statusCode == 200) {
var dwLinks = parse(res.body)
var dwLinks = parse(res.data)
.querySelectorAll('a')
.where((element) => element.attributes['href'] != 'last/')
.map((e) => e.attributes['href']?.split('/')[0])
@ -49,11 +50,11 @@ class VLC extends AppSource {
String standardUrl,
Map<String, dynamic> additionalSettings,
) async {
Response res = await get(
Uri.parse('https://www.videolan.org/vlc/download-android.html'));
Response res =
await dio.get('https://www.videolan.org/vlc/download-android.html');
if (res.statusCode == 200) {
var dwUrlBase = 'get.videolan.org/vlc-android';
var dwLinks = parse(res.body)
var dwLinks = parse(res.data)
.querySelectorAll('a')
.where((element) =>
element.attributes['href']?.contains(dwUrlBase) ?? false)
@ -84,14 +85,14 @@ class VLC extends AppSource {
Response res = await sourceRequest(apkUrl, additionalSettings);
if (res.statusCode == 200) {
String? apkUrl =
parse(res.body).querySelector('#alt_link')?.attributes['href'];
parse(res.data).querySelector('#alt_link')?.attributes['href'];
if (apkUrl == null) {
throw NoAPKError();
}
return apkUrl;
} else if (res.statusCode == 500 &&
res.body.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.body);
res.data.toLowerCase().indexOf('mirror') > 0) {
var html = parse(res.data);
var err = '';
html.body?.nodes.forEach((element) {
if (element.text != null) {

View File

@ -1,5 +1,5 @@
import 'package:dio/dio.dart';
import 'package:html/parser.dart';
import 'package:http/http.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/providers/source_provider.dart';
@ -20,7 +20,7 @@ class WhatsApp extends AppSource {
Response res =
await sourceRequest('$standardUrl/android', additionalSettings);
if (res.statusCode == 200) {
var targetLinks = parse(res.body)
var targetLinks = parse(res.data)
.querySelectorAll('a')
.map((e) => e.attributes['href'] ?? '')
.where((e) => e.isNotEmpty)

View File

@ -18,6 +18,7 @@ import 'package:easy_localization/easy_localization.dart';
import 'package:easy_localization/src/easy_localization_controller.dart';
// ignore: implementation_imports
import 'package:easy_localization/src/localization.dart';
import 'package:dio/dio.dart';
List<MapEntry<Locale, String>> supportedLocales = const [
MapEntry(Locale('en'), 'English'),
@ -46,6 +47,9 @@ var fdroid = false;
final globalNavigatorKey = GlobalKey<NavigatorState>();
final dio = Dio(BaseOptions(
responseType: ResponseType.plain, receiveDataWhenStatusError: true));
Future<void> loadTranslations() async {
// See easy_localization/issues/210
await EasyLocalizationController.initEasyLocation();

View File

@ -1,9 +1,10 @@
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/github.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/providers/source_provider.dart';
class GitHubStars implements MassAppUrlSource {
@ -15,13 +16,12 @@ class GitHubStars implements MassAppUrlSource {
Future<Map<String, List<String>>> getOnePageOfUserStarredUrlsWithDescriptions(
String username, int page) async {
Response res = await get(
Uri.parse(
'https://api.github.com/users/$username/starred?per_page=100&page=$page'),
headers: await GitHub().getRequestHeaders({}));
Response res = await dio.get(
'https://api.github.com/users/$username/starred?per_page=100&page=$page',
options: Options(headers: await GitHub().getRequestHeaders({})));
if (res.statusCode == 200) {
Map<String, List<String>> urlsWithDescriptions = {};
for (var e in (jsonDecode(res.body) as List<dynamic>)) {
for (var e in (jsonDecode(res.data) as List<dynamic>)) {
urlsWithDescriptions.addAll({
e['html_url'] as String: [
e['full_name'] as String,

View File

@ -5,14 +5,15 @@ import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';
import 'package:http/http.dart' as http;
import 'package:crypto/crypto.dart';
import 'package:http/http.dart' as http;
import 'package:android_intent_plus/flag.dart';
import 'package:android_package_installer/android_package_installer.dart';
import 'package:android_package_manager/android_package_manager.dart';
import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
@ -28,7 +29,6 @@ import 'package:provider/provider.dart';
import 'package:path_provider/path_provider.dart';
import 'package:flutter_fgbg/flutter_fgbg.dart';
import 'package:obtainium/providers/source_provider.dart';
import 'package:http/http.dart';
import 'package:android_intent_plus/android_intent.dart';
import 'package:flutter_archive/flutter_archive.dart';
import 'package:shared_storage/shared_storage.dart' as saf;
@ -146,10 +146,10 @@ Future<File> downloadFileWithRetry(
Map<String, String>? headers,
int retries = 3}) async {
try {
return await downloadFile(url, fileNameNoExt, onProgress, destDir,
return await downloadApk(url, fileNameNoExt, onProgress, destDir,
useExisting: useExisting, headers: headers);
} catch (e) {
if (retries > 0 && e is ClientException) {
if (retries > 0 && e is DioException) {
await Future.delayed(const Duration(seconds: 5));
return await downloadFileWithRetry(
url, fileNameNoExt, onProgress, destDir,
@ -183,9 +183,162 @@ Future<String> checkPartialDownloadHashDynamic(String url,
throw NoVersionError();
}
Future<File> downloadApk(
String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async {
var resHeaders = await getHeaders(url, headers: headers);
String ext = resHeaders['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
}
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
ext = 'apk';
}
File file = File('$destDir/$fileNameNoExt.$ext');
final contentLength = await getContentLengthIfRangeSupported(resHeaders);
if (useExisting && file.existsSync()) {
var length = file.lengthSync();
if (contentLength == null) {
return file;
} else {
if (length == contentLength) {
return file;
}
if (length > contentLength) {
useExisting = false;
}
}
}
double progress = -1;
try {
if (contentLength == null) {
Response response = await dio.download(
url,
file.path,
options: Options(headers: headers),
onReceiveProgress: (count, total) {
progress = (total > 0 ? count / total * 100 : 30);
if (onProgress != null) {
onProgress(progress);
}
},
);
if ((response.statusCode ?? 200) < 200 ||
(response.statusCode ?? 200) > 299) {
throw response.statusMessage ?? tr('unexpectedError');
}
} else {
var targetFileLength =
useExisting && file.existsSync() ? file.lengthSync() : null;
int bufferSize = 1024 * 1024; // 1 Megabyte
final sink = file.openWrite(
mode: useExisting ? FileMode.writeOnlyAppend : FileMode.writeOnly,
);
int rangeStart = targetFileLength ?? 0;
int rangeEnd = min(
rangeStart + bufferSize - 1,
contentLength - 1,
);
if (onProgress != null) {
progress = ((rangeStart / contentLength) * 100);
onProgress(progress);
}
while (true) {
var headersCurrent = headers ?? {};
headersCurrent['range'] = 'bytes=$rangeStart-$rangeEnd';
Response response = await dio.get(
url,
onReceiveProgress: (count, total) {
if (onProgress != null) {
final newProgress =
(((rangeStart + count) / contentLength) * 100);
if (newProgress != progress) {
progress = newProgress;
onProgress(progress);
}
}
},
options: Options(
headers: headersCurrent,
responseType: ResponseType.bytes,
),
);
if ((response.statusCode ?? 200) < 200 ||
(response.statusCode ?? 200) > 299) {
throw response.statusMessage ?? tr('unexpectedError');
}
final Uint8List data = response.data;
sink.add(data);
if (rangeEnd == contentLength - 1) {
break;
}
rangeStart = rangeEnd + 1;
rangeEnd = min(
rangeStart + bufferSize - 1,
contentLength - 1,
);
}
await sink.flush();
await sink.close();
}
} finally {
if (onProgress != null) {
onProgress(null);
}
}
return file;
}
Future<int?> getContentLengthIfRangeSupported(
Map<String, String> headers) async {
try {
int? contentLength;
{
var contentLengthHeaderValue = headers['content-length'];
if (contentLengthHeaderValue?.isNotEmpty == true) {
contentLength = int.tryParse(contentLengthHeaderValue!);
}
}
bool rangeFeatureEnabled = false;
if (headers['accept-ranges']?.isNotEmpty == true) {
rangeFeatureEnabled =
headers['accept-ranges']!.trim().toLowerCase() == 'bytes';
}
if (!rangeFeatureEnabled) {
contentLength = null;
}
return contentLength;
} catch (e) {
return null;
}
}
Future<Map<String, String>> getHeaders(String url,
{Map<String, String>? headers}) async {
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
var client = http.Client();
var response = await client.send(req);
if (response.statusCode < 200 || response.statusCode > 299) {
throw ObtainiumError(response.reasonPhrase ?? tr('unexpectedError'));
}
var returnHeaders = response.headers;
client.close();
return returnHeaders;
}
Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
{Map<String, String>? headers}) async {
var req = Request('GET', Uri.parse(url));
var req = http.Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
@ -199,58 +352,41 @@ Future<String> checkPartialDownloadHash(String url, int bytesToGrab,
return hashListOfLists(bytes);
}
Future<File> downloadFile(
String url, String fileNameNoExt, Function? onProgress, String destDir,
{bool useExisting = true, Map<String, String>? headers}) async {
var req = Request('GET', Uri.parse(url));
if (headers != null) {
req.headers.addAll(headers);
}
var client = http.Client();
StreamedResponse response = await client.send(req);
String ext =
response.headers['content-disposition']?.split('.').last ?? 'apk';
if (ext.endsWith('"') || ext.endsWith("other")) {
ext = ext.substring(0, ext.length - 1);
}
if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
ext = 'apk';
}
File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
if (!(downloadedFile.existsSync() && useExisting)) {
File tempDownloadedFile = File('${downloadedFile.path}.part');
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.deleteSync(recursive: true);
}
var length = response.contentLength;
var received = 0;
double? progress;
var sink = tempDownloadedFile.openWrite();
await response.stream.map((s) {
received += s.length;
progress = (length != null ? received / length * 100 : 30);
if (onProgress != null) {
onProgress(progress);
}
return s;
}).pipe(sink);
await sink.close();
progress = null;
if (onProgress != null) {
onProgress(progress);
}
if (response.statusCode != 200) {
tempDownloadedFile.deleteSync(recursive: true);
throw response.reasonPhrase ?? tr('unexpectedError');
}
if (tempDownloadedFile.existsSync()) {
tempDownloadedFile.renameSync(downloadedFile.path);
}
} else {
client.close();
}
return downloadedFile;
}
// Future<File> downloadFile(
// String url, String fileNameNoExt, Function? onProgress, String destDir,
// {bool useExisting = true, Map<String, String>? headers}) async {
// var resHead = await dio.head(url);
// String ext =
// resHead.headers.map['content-disposition']?[0].split('.').last ?? 'apk';
// if (ext.endsWith('"') || ext.endsWith("other")) {
// ext = ext.substring(0, ext.length - 1);
// }
// if (url.toLowerCase().endsWith('.apk') && ext != 'apk') {
// ext = 'apk';
// }
// File downloadedFile = File('$destDir/$fileNameNoExt.$ext');
// if (!(downloadedFile.existsSync() && useExisting)) {
// double? progress;
// var response = await dio.download(
// url,
// downloadedFile.path,
// options: Options(headers: headers),
// onReceiveProgress: (count, total) {
// progress = (total > 0 ? count / total * 100 : 30);
// if (onProgress != null) {
// onProgress(progress);
// }
// },
// );
// if (onProgress != null) {
// onProgress(null);
// }
// if (response.statusCode != 200) {
// throw response.statusMessage ?? tr('unexpectedError');
// }
// }
// return downloadedFile;
// }
Future<PackageInfo?> getInstalledInfo(String? packageName,
{bool printErr = true}) async {
@ -1621,7 +1757,7 @@ Future<void> bgUpdateCheck(String taskId, Map<String, dynamic>? params) async {
// Next task interval is based on the error with the longest retry time
int minRetryIntervalForThisApp = err is RateLimitError
? (err.remainingMinutes * 60)
: e is ClientException
: e is DioException
? (15 * 60)
: (toCheckApp.value + 1);
if (minRetryIntervalForThisApp > maxRetryWaitSeconds) {

View File

@ -4,9 +4,9 @@
import 'dart:convert';
import 'package:device_info_plus/device_info_plus.dart';
import 'package:dio/dio.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:html/dom.dart';
import 'package:http/http.dart';
import 'package:obtainium/app_sources/apkmirror.dart';
import 'package:obtainium/app_sources/apkpure.dart';
import 'package:obtainium/app_sources/aptoide.dart';
@ -31,6 +31,7 @@ import 'package:obtainium/app_sources/vlc.dart';
import 'package:obtainium/app_sources/whatsapp.dart';
import 'package:obtainium/components/generated_form.dart';
import 'package:obtainium/custom_errors.dart';
import 'package:obtainium/main.dart';
import 'package:obtainium/mass_app_sources/githubstars.dart';
import 'package:obtainium/providers/settings_provider.dart';
@ -442,16 +443,9 @@ abstract class AppSource {
String url, Map<String, dynamic> additionalSettings,
{bool followRedirects = true}) async {
var requestHeaders = await getRequestHeaders(additionalSettings);
if (requestHeaders != null || followRedirects == false) {
var req = Request('GET', Uri.parse(url));
req.followRedirects = followRedirects;
if (requestHeaders != null) {
req.headers.addAll(requestHeaders);
}
return Response.fromStream(await Client().send(req));
} else {
return get(Uri.parse(url));
}
return await dio.get(url,
options:
Options(headers: requestHeaders, followRedirects: followRedirects));
}
String sourceSpecificStandardizeURL(String url) {
@ -618,10 +612,10 @@ abstract class AppSource {
}
ObtainiumError getObtainiumHttpError(Response res) {
return ObtainiumError((res.reasonPhrase != null &&
res.reasonPhrase != null &&
res.reasonPhrase!.isNotEmpty)
? res.reasonPhrase!
return ObtainiumError((res.statusMessage != null &&
res.statusMessage != null &&
res.statusMessage!.isNotEmpty)
? res.statusMessage!
: tr('errorWithHttpStatusCode', args: [res.statusCode.toString()]));
}

View File

@ -202,6 +202,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "7.0.0"
dio:
dependency: "direct main"
description:
name: dio
sha256: "49af28382aefc53562459104f64d16b9dfd1e8ef68c862d5af436cc8356ce5a8"
url: "https://pub.dev"
source: hosted
version: "5.4.1"
dynamic_color:
dependency: "direct main"
description:

View File

@ -68,6 +68,7 @@ dependencies:
crypto: ^3.0.3
app_links: ^3.5.0
background_fetch: ^1.2.1
dio: ^5.4.1
dev_dependencies:
flutter_test: