diff --git a/assets/translations/de.json b/assets/translations/de.json index 7499014..f68da68 100644 --- a/assets/translations/de.json +++ b/assets/translations/de.json @@ -233,6 +233,7 @@ "about": "Über", "requiresCredentialsInSettings": "Benötigt zusätzliche Anmeldedaten (in den Einstellungen)", "checkOnStart": "Überprüfe einmalig beim Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "App entfernen?", "other": "Apps entfernen?" diff --git a/assets/translations/en.json b/assets/translations/en.json index 1f543ac..e9b4503 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -233,6 +233,7 @@ "about": "About", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "checkOnStart": "Check Once on Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "Remove App?", "other": "Remove Apps?" diff --git a/assets/translations/es.json b/assets/translations/es.json index d3b6500..6c7cc05 100644 --- a/assets/translations/es.json +++ b/assets/translations/es.json @@ -233,6 +233,7 @@ "about": "About", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "checkOnStart": "Check Once on Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "¿Eliminar Aplicación?", "other": "¿Eliminar Aplicaciones?" diff --git a/assets/translations/fa.json b/assets/translations/fa.json index d7eb778..0b4030a 100644 --- a/assets/translations/fa.json +++ b/assets/translations/fa.json @@ -233,6 +233,7 @@ "about": "About", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "checkOnStart": "Check Once on Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "برنامه حذف شود؟", "other": "برنامه ها حذف شوند؟" diff --git a/assets/translations/fr.json b/assets/translations/fr.json index dfc8876..ea60088 100644 --- a/assets/translations/fr.json +++ b/assets/translations/fr.json @@ -233,6 +233,7 @@ "about": "About", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "checkOnStart": "Check Once on Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "Supprimer l'application ?", "other": "Supprimer les applications ?" diff --git a/assets/translations/hu.json b/assets/translations/hu.json index 6d311b6..c3771af 100644 --- a/assets/translations/hu.json +++ b/assets/translations/hu.json @@ -232,6 +232,7 @@ "about": "Rólunk", "requiresCredentialsInSettings": "Ehhez további hitelesítő adatokra van szükség (a Beállításokban)", "checkOnStart": "Egyszer az indításkor", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "Eltávolítja az alkalmazást?", "other": "Eltávolítja az alkalmazást?" diff --git a/assets/translations/it.json b/assets/translations/it.json index 53b20c6..93c56e1 100644 --- a/assets/translations/it.json +++ b/assets/translations/it.json @@ -233,6 +233,7 @@ "about": "About", "requiresCredentialsInSettings": "This needs additional credentials (in Settings)", "checkOnStart": "Check Once on Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "Rimuovere l'App?", "other": "Rimuovere le App?" diff --git a/assets/translations/ja.json b/assets/translations/ja.json index 135a8ab..c0d9c22 100644 --- a/assets/translations/ja.json +++ b/assets/translations/ja.json @@ -233,6 +233,7 @@ "about": "概要", "requiresCredentialsInSettings": "これには追加の認証が必要です (設定にて)", "checkOnStart": "Check Once on Start", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "アプリを削除しますか?", "other": "アプリを削除しますか?" diff --git a/assets/translations/zh.json b/assets/translations/zh.json index 83c08b2..0b3837c 100644 --- a/assets/translations/zh.json +++ b/assets/translations/zh.json @@ -233,6 +233,7 @@ "about": "相关文档", "requiresCredentialsInSettings": "此功能需要额外的凭据(在“设置”中添加)", "checkOnStart": "启动时进行一次检查", + "tryInferAppIdFromCode": "Try inferring App ID from source code", "removeAppQuestion": { "one": "是否删除应用?", "other": "是否删除应用?" diff --git a/lib/app_sources/apkcombo.dart b/lib/app_sources/apkcombo.dart index 16e21c5..c90b0cb 100644 --- a/lib/app_sources/apkcombo.dart +++ b/lib/app_sources/apkcombo.dart @@ -19,8 +19,8 @@ class APKCombo extends AppSource { } @override - String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { return Uri.parse(standardUrl).pathSegments.last; } @@ -83,7 +83,7 @@ class APKCombo extends AppSource { String standardUrl, Map additionalSettings, ) async { - String appId = tryInferringAppId(standardUrl)!; + String appId = (await tryInferringAppId(standardUrl))!; var preres = await sourceRequest(standardUrl); if (preres.statusCode != 200) { throw getObtainiumHttpError(preres); diff --git a/lib/app_sources/apkpure.dart b/lib/app_sources/apkpure.dart index 20028a4..f3cef4e 100644 --- a/lib/app_sources/apkpure.dart +++ b/lib/app_sources/apkpure.dart @@ -24,8 +24,8 @@ class APKPure extends AppSource { } @override - String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { return Uri.parse(standardUrl).pathSegments.last; } @@ -34,7 +34,7 @@ class APKPure extends AppSource { String standardUrl, Map additionalSettings, ) async { - String appId = tryInferringAppId(standardUrl)!; + String appId = (await tryInferringAppId(standardUrl))!; String host = Uri.parse(standardUrl).host; var res = await sourceRequest('$standardUrl/download'); if (res.statusCode == 200) { diff --git a/lib/app_sources/fdroid.dart b/lib/app_sources/fdroid.dart index 28d45cc..0602f6d 100644 --- a/lib/app_sources/fdroid.dart +++ b/lib/app_sources/fdroid.dart @@ -31,8 +31,8 @@ class FDroid extends AppSource { } @override - String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { return Uri.parse(standardUrl).pathSegments.last; } @@ -63,7 +63,7 @@ class FDroid extends AppSource { String standardUrl, Map additionalSettings, ) async { - String? appId = tryInferringAppId(standardUrl); + String? appId = await tryInferringAppId(standardUrl); String host = Uri.parse(standardUrl).host; return getAPKUrlsFromFDroidPackagesAPIResponse( await sourceRequest('https://$host/api/v1/packages/$appId'), diff --git a/lib/app_sources/github.dart b/lib/app_sources/github.dart index a34bda2..d75beb2 100644 --- a/lib/app_sources/github.dart +++ b/lib/app_sources/github.dart @@ -6,6 +6,7 @@ import 'package:obtainium/app_sources/html.dart'; import 'package:obtainium/components/generated_form.dart'; import 'package:obtainium/custom_errors.dart'; import 'package:obtainium/providers/apps_provider.dart'; +import 'package:obtainium/providers/logs_provider.dart'; import 'package:obtainium/providers/settings_provider.dart'; import 'package:obtainium/providers/source_provider.dart'; import 'package:url_launcher/url_launcher_string.dart'; @@ -13,6 +14,7 @@ import 'package:url_launcher/url_launcher_string.dart'; class GitHub extends AppSource { GitHub() { host = 'github.com'; + appIdInferIsOptional = true; additionalSourceSpecificSettingFormItems = [ GeneratedFormTextField('github-creds', @@ -79,6 +81,44 @@ class GitHub extends AppSource { canSearch = true; } + @override + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { + const possibleBuildGradleLocations = [ + '/app/build.gradle', + 'android/app/build.gradle', + 'src/app/build.gradle' + ]; + for (var path in possibleBuildGradleLocations) { + try { + var res = await sourceRequest( + '${await convertStandardUrlToAPIUrl(standardUrl)}/contents/$path'); + if (res.statusCode == 200) { + try { + var body = jsonDecode(res.body); + var appId = utf8 + .decode(base64 + .decode(body['content'].toString().split('\n').join(''))) + .split('\n') + .map((e) => e.trim()) + .where((l) => l.startsWith('applicationId "')) + .first + .split('"')[1]; + if (appId.isNotEmpty) { + return appId; + } + } catch (err) { + LogsProvider().add( + 'Error parsing build.gradle from ${res.request!.url.toString()}: ${err.toString()}'); + } + } + } catch (err) { + // Ignore - ID will be extracted from the APK + } + } + return null; + } + @override String sourceSpecificStandardizeURL(String url) { RegExp standardUrlRegEx = RegExp('^https?://$host/[^/]+/[^/]+'); @@ -97,6 +137,12 @@ class GitHub extends AppSource { return creds != null && creds.isNotEmpty ? '$creds@' : ''; } + Future getAPIHost() async => + 'https://${await getCredentialPrefixIfAny()}api.$host'; + + Future convertStandardUrlToAPIUrl(String standardUrl) async => + '${await getAPIHost()}/repos${standardUrl.substring('https://$host'.length)}'; + @override String? changeLogPageFromStandardUrl(String standardUrl) => '$standardUrl/releases'; @@ -239,7 +285,7 @@ class GitHub extends AppSource { ) async { return await getLatestAPKDetailsCommon2(standardUrl, additionalSettings, (bool useTagUrl) async { - return 'https://${await getCredentialPrefixIfAny()}api.$host/repos${standardUrl.substring('https://$host'.length)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100'; + return '${await convertStandardUrlToAPIUrl(standardUrl)}/${useTagUrl ? 'tags' : 'releases'}?per_page=100'; }, (Response res) { rateLimitErrorCheck(res); }); @@ -281,7 +327,7 @@ class GitHub extends AppSource { Future>> search(String query) async { return searchCommon( query, - 'https://${await getCredentialPrefixIfAny()}api.$host/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', + '${await getAPIHost()}/search/repositories?q=${Uri.encodeQueryComponent(query)}&per_page=100', 'items', onHttpErrorCode: (Response res) { rateLimitErrorCheck(res); }); diff --git a/lib/app_sources/izzyondroid.dart b/lib/app_sources/izzyondroid.dart index 1773558..d0fcfe8 100644 --- a/lib/app_sources/izzyondroid.dart +++ b/lib/app_sources/izzyondroid.dart @@ -18,8 +18,8 @@ class IzzyOnDroid extends AppSource { } @override - String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { return FDroid().tryInferringAppId(standardUrl); } @@ -28,7 +28,7 @@ class IzzyOnDroid extends AppSource { String standardUrl, Map additionalSettings, ) async { - String? appId = tryInferringAppId(standardUrl); + String? appId = await tryInferringAppId(standardUrl); return FDroid().getAPKUrlsFromFDroidPackagesAPIResponse( await sourceRequest( 'https://apt.izzysoft.de/fdroid/api/v1/packages/$appId'), diff --git a/lib/main.dart b/lib/main.dart index a1494bc..98d6d1f 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -21,7 +21,7 @@ import 'package:easy_localization/src/easy_localization_controller.dart'; // ignore: implementation_imports import 'package:easy_localization/src/localization.dart'; -const String currentVersion = '0.13.5'; +const String currentVersion = '0.13.6'; const String currentReleaseTag = 'v$currentVersion-beta'; // KEEP THIS IN SYNC WITH GITHUB RELEASES diff --git a/lib/pages/add_app.dart b/lib/pages/add_app.dart index c439f30..b31dc4d 100644 --- a/lib/pages/add_app.dart +++ b/lib/pages/add_app.dart @@ -33,6 +33,7 @@ class _AddAppPageState extends State { AppSource? pickedSource; Map additionalSettings = {}; bool additionalSettingsValid = true; + bool inferAppIdIfOptional = true; List pickedCategories = []; int searchnum = 0; SourceProvider sourceProvider = SourceProvider(); @@ -78,6 +79,7 @@ class _AddAppPageState extends State { additionalSettingsValid = source != null ? !sourceProvider.ifRequiredAppSpecificSettingsExist(source) : true; + inferAppIdIfOptional = true; } }); } @@ -147,7 +149,8 @@ class _AddAppPageState extends State { app = await sourceProvider.getApp( pickedSource!, userInput, additionalSettings, trackOnlyOverride: trackOnly, - overrideSource: pickedSourceOverride); + overrideSource: pickedSourceOverride, + inferAppIdIfOptional: inferAppIdIfOptional); // Only download the APK here if you need to for the package ID if (sourceProvider.isTempId(app) && app.additionalSettings['trackOnly'] != true) { @@ -428,6 +431,23 @@ class _AddAppPageState extends State { }), ], ), + if (pickedSource != null && pickedSource!.appIdInferIsOptional) + GeneratedForm( + key: const Key('inferAppIdIfOptional'), + items: [ + [ + GeneratedFormSwitch('inferAppIdIfOptional', + label: tr('tryInferAppIdFromCode'), + defaultValue: inferAppIdIfOptional) + ] + ], + onValueChanges: (values, valid, isBuilding) { + if (!isBuilding) { + setState(() { + inferAppIdIfOptional = values['inferAppIdIfOptional']; + }); + } + }), ], ); diff --git a/lib/providers/source_provider.dart b/lib/providers/source_provider.dart index d980e3c..41caa43 100644 --- a/lib/providers/source_provider.dart +++ b/lib/providers/source_provider.dart @@ -317,6 +317,7 @@ abstract class AppSource { late String name; bool enforceTrackOnly = false; bool changeLogIfAnyIsMarkDown = true; + bool appIdInferIsOptional = false; AppSource() { name = runtimeType.toString(); @@ -434,8 +435,8 @@ abstract class AppSource { throw NotImplementedError(); } - String? tryInferringAppId(String standardUrl, - {Map additionalSettings = const {}}) { + Future tryInferringAppId(String standardUrl, + {Map additionalSettings = const {}}) async { return null; } } @@ -552,7 +553,8 @@ class SourceProvider { AppSource source, String url, Map additionalSettings, {App? currentApp, bool trackOnlyOverride = false, - String? overrideSource}) async { + String? overrideSource, + bool inferAppIdIfOptional = false}) async { if (trackOnlyOverride || source.enforceTrackOnly) { additionalSettings['trackOnly'] = true; } @@ -592,8 +594,11 @@ class SourceProvider { : apk.names.name[0].toUpperCase() + apk.names.name.substring(1); return App( currentApp?.id ?? - source.tryInferringAppId(standardUrl, - additionalSettings: additionalSettings) ?? + ((!source.appIdInferIsOptional || + (source.appIdInferIsOptional && inferAppIdIfOptional)) + ? await source.tryInferringAppId(standardUrl, + additionalSettings: additionalSettings) + : null) ?? generateTempID(standardUrl, additionalSettings), standardUrl, apk.names.author[0].toUpperCase() + apk.names.author.substring(1), diff --git a/pubspec.yaml b/pubspec.yaml index 54acc82..8d57bd9 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -17,7 +17,7 @@ publish_to: 'none' # Remove this line if you wish to publish to pub.dev # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html # In Windows, build-name is used as the major, minor, and patch parts # of the product and file versions while build-number is used as the build suffix. -version: 0.13.5+169 # When changing this, update the tag in main() accordingly +version: 0.13.6+170 # When changing this, update the tag in main() accordingly environment: sdk: '>=2.18.2 <3.0.0'