mirror of
https://github.com/ImranR98/Obtainium.git
synced 2025-07-13 13:26:43 +02:00
Add Aptoide as a Source (#843)
This commit is contained in:
@ -16,6 +16,7 @@ Currently supported App sources:
|
|||||||
- [Signal](https://signal.org/)
|
- [Signal](https://signal.org/)
|
||||||
- [SourceForge](https://sourceforge.net/)
|
- [SourceForge](https://sourceforge.net/)
|
||||||
- [SourceHut](https://git.sr.ht/)
|
- [SourceHut](https://git.sr.ht/)
|
||||||
|
- [Aptoide](https://aptoide.com/)
|
||||||
- [APKMirror](https://apkmirror.com/) (Track-Only)
|
- [APKMirror](https://apkmirror.com/) (Track-Only)
|
||||||
- [APKPure](https://apkpure.com/)
|
- [APKPure](https://apkpure.com/)
|
||||||
- [Huawei AppGallery](https://appgallery.huawei.com/)
|
- [Huawei AppGallery](https://appgallery.huawei.com/)
|
||||||
|
104
lib/app_sources/aptoide.dart
Normal file
104
lib/app_sources/aptoide.dart
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
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 Aptoide extends AppSource {
|
||||||
|
Aptoide() {
|
||||||
|
host = 'aptoide.com';
|
||||||
|
name = tr('Aptoide');
|
||||||
|
allowSubDomains = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String sourceSpecificStandardizeURL(String url) {
|
||||||
|
RegExp standardUrlRegEx = RegExp('^https?://([^\\.]+\\.){2,}$host');
|
||||||
|
RegExpMatch? match = standardUrlRegEx.firstMatch(url.toLowerCase());
|
||||||
|
if (match == null) {
|
||||||
|
throw InvalidURLError(name);
|
||||||
|
}
|
||||||
|
return url.substring(0, match.end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<String?> tryInferringAppId(String standardUrl,
|
||||||
|
{Map<String, dynamic> additionalSettings = const {}}) async {
|
||||||
|
return (await getLatestAPKDetails(standardUrl, additionalSettings)).version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<APKDetails> getLatestAPKDetails(
|
||||||
|
String standardUrl,
|
||||||
|
Map<String, dynamic> additionalSettings,
|
||||||
|
) async {
|
||||||
|
var res = await sourceRequest(standardUrl);
|
||||||
|
if (res.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var idMatch = RegExp('"app":{"id":[0-9]+').firstMatch(res.body);
|
||||||
|
String? id;
|
||||||
|
if (idMatch != null) {
|
||||||
|
id = res.body.substring(idMatch.start + 12, idMatch.end);
|
||||||
|
} else {
|
||||||
|
throw NoReleasesError();
|
||||||
|
}
|
||||||
|
var res2 =
|
||||||
|
await sourceRequest('https://ws2.aptoide.com/api/7/getApp/app_id/$id');
|
||||||
|
if (res2.statusCode != 200) {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
var appDetails = jsonDecode(res2.body)?['nodes']?['meta']?['data'];
|
||||||
|
String appName = appDetails?['name'] ?? tr('app');
|
||||||
|
String author = appDetails?['developer']?['name'] ?? name;
|
||||||
|
String? dateStr = appDetails?['updated'];
|
||||||
|
String? version = appDetails?['file']?['vername'];
|
||||||
|
String? apkUrl = appDetails?['file']?['path'];
|
||||||
|
if (version == null) {
|
||||||
|
throw NoVersionError();
|
||||||
|
}
|
||||||
|
if (apkUrl == null) {
|
||||||
|
throw NoAPKError();
|
||||||
|
}
|
||||||
|
DateTime? relDate;
|
||||||
|
if (dateStr != null) {
|
||||||
|
relDate = DateTime.parse(dateStr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return APKDetails(
|
||||||
|
version, getApkUrlsFromUrls([apkUrl]), AppNames(author, appName),
|
||||||
|
releaseDate: relDate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Map<String, List<String>>> search(String query,
|
||||||
|
{Map<String, dynamic> querySettings = const {}}) async {
|
||||||
|
Response res = await sourceRequest(
|
||||||
|
'https://search.$host/?q=${Uri.encodeQueryComponent(query)}');
|
||||||
|
if (res.statusCode == 200) {
|
||||||
|
Map<String, List<String>> urlsWithDescriptions = {};
|
||||||
|
parse(res.body).querySelectorAll('.package-header').forEach((e) {
|
||||||
|
String? url = e.attributes['href'];
|
||||||
|
if (url != null) {
|
||||||
|
try {
|
||||||
|
standardizeUrl(url);
|
||||||
|
} catch (e) {
|
||||||
|
url = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (url != null) {
|
||||||
|
urlsWithDescriptions[url] = [
|
||||||
|
e.querySelector('.package-name')?.text.trim() ?? '',
|
||||||
|
e.querySelector('.package-summary')?.text.trim() ??
|
||||||
|
tr('noDescription')
|
||||||
|
];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return urlsWithDescriptions;
|
||||||
|
} else {
|
||||||
|
throw getObtainiumHttpError(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,7 @@ import 'package:html/dom.dart';
|
|||||||
import 'package:http/http.dart';
|
import 'package:http/http.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/apkpure.dart';
|
||||||
|
import 'package:obtainium/app_sources/aptoide.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';
|
||||||
@ -325,6 +326,7 @@ abstract class AppSource {
|
|||||||
bool enforceTrackOnly = false;
|
bool enforceTrackOnly = false;
|
||||||
bool changeLogIfAnyIsMarkDown = true;
|
bool changeLogIfAnyIsMarkDown = true;
|
||||||
bool appIdInferIsOptional = false;
|
bool appIdInferIsOptional = false;
|
||||||
|
bool allowSubDomains = false;
|
||||||
|
|
||||||
AppSource() {
|
AppSource() {
|
||||||
name = runtimeType.toString();
|
name = runtimeType.toString();
|
||||||
@ -522,13 +524,14 @@ class SourceProvider {
|
|||||||
Jenkins(),
|
Jenkins(),
|
||||||
SourceForge(),
|
SourceForge(),
|
||||||
SourceHut(),
|
SourceHut(),
|
||||||
|
Aptoide(),
|
||||||
APKMirror(),
|
APKMirror(),
|
||||||
APKPure(),
|
APKPure(),
|
||||||
HuaweiAppGallery(),
|
HuaweiAppGallery(),
|
||||||
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
|
// APKCombo(), // Can't get past their scraping blocking yet (get 403 Forbidden)
|
||||||
Mullvad(),
|
Mullvad(),
|
||||||
Signal(),
|
Signal(),
|
||||||
VLC(), // As of 2023-08-26 this site randomly messes up the 'latest' version (one minute it's 3.5.4, next minute back to 3.5.3)
|
VLC(),
|
||||||
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
|
// WhatsApp(), // As of 2023-03-20 this is unusable as the version on the webpage is months out of date
|
||||||
TelegramApp(),
|
TelegramApp(),
|
||||||
SteamMobile(),
|
SteamMobile(),
|
||||||
@ -554,7 +557,9 @@ class SourceProvider {
|
|||||||
}
|
}
|
||||||
AppSource? source;
|
AppSource? source;
|
||||||
for (var s in sources.where((element) => element.host != null)) {
|
for (var s in sources.where((element) => element.host != null)) {
|
||||||
if (RegExp('://${s.host}(/|\\z)?').hasMatch(url)) {
|
if (RegExp(
|
||||||
|
'://${s.allowSubDomains ? '([^\\.]+\\.)*' : ''}${s.host}(/|\\z)?')
|
||||||
|
.hasMatch(url)) {
|
||||||
source = s;
|
source = s;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user