mirror of
				https://github.com/ImranR98/Obtainium.git
				synced 2025-10-31 21:43:29 +01:00 
			
		
		
		
	Make Third Party F-Droid Repos Searchable (#995)
This commit is contained in:
		| @@ -7,6 +7,7 @@ import 'package:obtainium/providers/source_provider.dart'; | |||||||
| class FDroidRepo extends AppSource { | class FDroidRepo extends AppSource { | ||||||
|   FDroidRepo() { |   FDroidRepo() { | ||||||
|     name = tr('fdroidThirdPartyRepo'); |     name = tr('fdroidThirdPartyRepo'); | ||||||
|  |     canSearch = true; | ||||||
|  |  | ||||||
|     additionalSourceAppSpecificSettingFormItems = [ |     additionalSourceAppSpecificSettingFormItems = [ | ||||||
|       [ |       [ | ||||||
| @@ -22,12 +23,85 @@ class FDroidRepo extends AppSource { | |||||||
|     ]; |     ]; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   String removeQueryParamsFromUrl(String url, {List<String> keep = const []}) { | ||||||
|  |     var uri = Uri.parse(url); | ||||||
|  |     Map<String, dynamic> resultParams = {}; | ||||||
|  |     uri.queryParameters.forEach((key, value) { | ||||||
|  |       if (keep.contains(key)) { | ||||||
|  |         resultParams[key] = value; | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |     url = uri.replace(queryParameters: resultParams).toString(); | ||||||
|  |     if (url.endsWith('?')) { | ||||||
|  |       url = url.substring(0, url.length - 1); | ||||||
|  |     } | ||||||
|  |     return url; | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   String sourceSpecificStandardizeURL(String url) { | ||||||
|  |     var standardUri = Uri.parse(url); | ||||||
|  |     var pathSegments = standardUri.pathSegments; | ||||||
|  |     if (pathSegments.last == 'index.xml') { | ||||||
|  |       pathSegments.removeLast(); | ||||||
|  |       standardUri = standardUri.replace(path: pathSegments.join('/')); | ||||||
|  |     } | ||||||
|  |     return removeQueryParamsFromUrl(standardUri.toString(), keep: ['appId']); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   Future<Map<String, List<String>>> search(String query, | ||||||
|  |       {Map<String, dynamic> querySettings = const {}}) async { | ||||||
|  |     query = removeQueryParamsFromUrl(standardizeUrl(query)); | ||||||
|  |     var res = await sourceRequest('$query/index.xml'); | ||||||
|  |     if (res.statusCode == 200) { | ||||||
|  |       var body = parse(res.body); | ||||||
|  |       Map<String, List<String>> results = {}; | ||||||
|  |       body.querySelectorAll('application').toList().forEach((app) { | ||||||
|  |         String appId = app.attributes['id']!; | ||||||
|  |         results['$query?appId=$appId'] = [ | ||||||
|  |           app.querySelector('name')?.innerHtml ?? appId, | ||||||
|  |           app.querySelector('desc')?.innerHtml ?? '' | ||||||
|  |         ]; | ||||||
|  |       }); | ||||||
|  |       return results; | ||||||
|  |     } else { | ||||||
|  |       throw getObtainiumHttpError(res); | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   @override | ||||||
|  |   App endOfGetAppChanges(App app) { | ||||||
|  |     var uri = Uri.parse(app.url); | ||||||
|  |     String? appId; | ||||||
|  |     if (!isTempId(app)) { | ||||||
|  |       appId = app.id; | ||||||
|  |     } else if (uri.queryParameters['appId'] != null) { | ||||||
|  |       appId = uri.queryParameters['appId']; | ||||||
|  |     } | ||||||
|  |     if (appId != null) { | ||||||
|  |       app.url = uri | ||||||
|  |           .replace( | ||||||
|  |               queryParameters: Map.fromEntries( | ||||||
|  |                   [...uri.queryParameters.entries, MapEntry('appId', appId)])) | ||||||
|  |           .toString(); | ||||||
|  |       app.additionalSettings['appIdOrName'] = appId; | ||||||
|  |       app.id = appId; | ||||||
|  |     } | ||||||
|  |     return app; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   @override |   @override | ||||||
|   Future<APKDetails> getLatestAPKDetails( |   Future<APKDetails> getLatestAPKDetails( | ||||||
|     String standardUrl, |     String standardUrl, | ||||||
|     Map<String, dynamic> additionalSettings, |     Map<String, dynamic> additionalSettings, | ||||||
|   ) async { |   ) async { | ||||||
|     String? appIdOrName = additionalSettings['appIdOrName']; |     String? appIdOrName = additionalSettings['appIdOrName']; | ||||||
|  |     var standardUri = Uri.parse(standardUrl); | ||||||
|  |     if (standardUri.queryParameters['appId'] != null) { | ||||||
|  |       appIdOrName = standardUri.queryParameters['appId']; | ||||||
|  |     } | ||||||
|  |     standardUrl = removeQueryParamsFromUrl(standardUrl); | ||||||
|     bool pickHighestVersionCode = additionalSettings['pickHighestVersionCode']; |     bool pickHighestVersionCode = additionalSettings['pickHighestVersionCode']; | ||||||
|     if (appIdOrName == null) { |     if (appIdOrName == null) { | ||||||
|       throw NoReleasesError(); |       throw NoReleasesError(); | ||||||
| @@ -41,7 +115,7 @@ class FDroidRepo extends AppSource { | |||||||
|       if (foundApps.isEmpty) { |       if (foundApps.isEmpty) { | ||||||
|         foundApps = body.querySelectorAll('application').where((element) { |         foundApps = body.querySelectorAll('application').where((element) { | ||||||
|           return element.querySelector('name')?.innerHtml.toLowerCase() == |           return element.querySelector('name')?.innerHtml.toLowerCase() == | ||||||
|               appIdOrName.toLowerCase(); |               appIdOrName!.toLowerCase(); | ||||||
|         }).toList(); |         }).toList(); | ||||||
|       } |       } | ||||||
|       if (foundApps.isEmpty) { |       if (foundApps.isEmpty) { | ||||||
| @@ -50,7 +124,7 @@ class FDroidRepo extends AppSource { | |||||||
|                   .querySelector('name') |                   .querySelector('name') | ||||||
|                   ?.innerHtml |                   ?.innerHtml | ||||||
|                   .toLowerCase() |                   .toLowerCase() | ||||||
|                   .contains(appIdOrName.toLowerCase()) ?? |                   .contains(appIdOrName!.toLowerCase()) ?? | ||||||
|               false; |               false; | ||||||
|         }).toList(); |         }).toList(); | ||||||
|       } |       } | ||||||
| @@ -58,8 +132,9 @@ class FDroidRepo extends AppSource { | |||||||
|         throw ObtainiumError(tr('appWithIdOrNameNotFound')); |         throw ObtainiumError(tr('appWithIdOrNameNotFound')); | ||||||
|       } |       } | ||||||
|       var authorName = body.querySelector('repo')?.attributes['name'] ?? name; |       var authorName = body.querySelector('repo')?.attributes['name'] ?? name; | ||||||
|       var appName = |       String appId = foundApps[0].attributes['id']!; | ||||||
|           foundApps[0].querySelector('name')?.innerHtml ?? appIdOrName; |       foundApps[0].querySelector('name')?.innerHtml ?? appId; | ||||||
|  |       var appName = foundApps[0].querySelector('name')?.innerHtml ?? appId; | ||||||
|       var releases = foundApps[0].querySelectorAll('package'); |       var releases = foundApps[0].querySelectorAll('package'); | ||||||
|       String? latestVersion = releases[0].querySelector('version')?.innerHtml; |       String? latestVersion = releases[0].querySelector('version')?.innerHtml; | ||||||
|       String? added = releases[0].querySelector('added')?.innerHtml; |       String? added = releases[0].querySelector('added')?.innerHtml; | ||||||
|   | |||||||
| @@ -153,7 +153,7 @@ class _AddAppPageState extends State<AddAppPage> { | |||||||
|               overrideSource: pickedSourceOverride, |               overrideSource: pickedSourceOverride, | ||||||
|               inferAppIdIfOptional: inferAppIdIfOptional); |               inferAppIdIfOptional: inferAppIdIfOptional); | ||||||
|           // Only download the APK here if you need to for the package ID |           // Only download the APK here if you need to for the package ID | ||||||
|           if (sourceProvider.isTempId(app) && |           if (isTempId(app) && | ||||||
|               app.additionalSettings['trackOnly'] != true) { |               app.additionalSettings['trackOnly'] != true) { | ||||||
|             // ignore: use_build_context_synchronously |             // ignore: use_build_context_synchronously | ||||||
|             var apkUrl = await appsProvider.confirmApkUrl(app, context); |             var apkUrl = await appsProvider.confirmApkUrl(app, context); | ||||||
|   | |||||||
| @@ -267,10 +267,10 @@ class AppsProvider with ChangeNotifier { | |||||||
|       File downloadedFile, String downloadUrl) async { |       File downloadedFile, String downloadUrl) async { | ||||||
|     // If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed |     // If the APK package ID is different from the App ID, it is either new (using a placeholder ID) or the ID has changed | ||||||
|     // The former case should be handled (give the App its real ID), the latter is a security issue |     // The former case should be handled (give the App its real ID), the latter is a security issue | ||||||
|     var isTempId = SourceProvider().isTempId(app); |     var isTempIdBool = isTempId(app); | ||||||
|     if (newInfo != null) { |     if (newInfo != null) { | ||||||
|       if (app.id != newInfo.packageName) { |       if (app.id != newInfo.packageName) { | ||||||
|         if (apps[app.id] != null && !isTempId && !app.allowIdChange) { |         if (apps[app.id] != null && !isTempIdBool && !app.allowIdChange) { | ||||||
|           throw IDChangedError(newInfo.packageName!); |           throw IDChangedError(newInfo.packageName!); | ||||||
|         } |         } | ||||||
|         var idChangeWasAllowed = app.allowIdChange; |         var idChangeWasAllowed = app.allowIdChange; | ||||||
| @@ -281,10 +281,10 @@ class AppsProvider with ChangeNotifier { | |||||||
|             '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}'); |             '${downloadedFile.parent.path}/${app.id}-${downloadUrl.hashCode}.${downloadedFile.path.split('.').last}'); | ||||||
|         if (apps[originalAppId] != null) { |         if (apps[originalAppId] != null) { | ||||||
|           await removeApps([originalAppId]); |           await removeApps([originalAppId]); | ||||||
|           await saveApps([app], onlyIfExists: !isTempId && !idChangeWasAllowed); |           await saveApps([app], onlyIfExists: !isTempIdBool && !idChangeWasAllowed); | ||||||
|         } |         } | ||||||
|       } |       } | ||||||
|     } else if (isTempId) { |     } else if (isTempIdBool) { | ||||||
|       throw ObtainiumError('Could not get ID from APK'); |       throw ObtainiumError('Could not get ID from APK'); | ||||||
|     } |     } | ||||||
|     return downloadedFile; |     return downloadedFile; | ||||||
|   | |||||||
| @@ -372,6 +372,10 @@ abstract class AppSource { | |||||||
|     return null; |     return null; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   App endOfGetAppChanges(App app) { | ||||||
|  |     return app; | ||||||
|  |   } | ||||||
|  |  | ||||||
|   Future<Response> sourceRequest(String url, |   Future<Response> sourceRequest(String url, | ||||||
|       {bool followRedirects = true, |       {bool followRedirects = true, | ||||||
|       Map<String, dynamic> additionalSettings = |       Map<String, dynamic> additionalSettings = | ||||||
| @@ -541,6 +545,11 @@ intValidator(String? value, {bool positive = false}) { | |||||||
|   return null; |   return null; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | bool isTempId(App app) { | ||||||
|  |   // return app.id == generateTempID(app.url, app.additionalSettings); | ||||||
|  |   return RegExp('^[0-9]+\$').hasMatch(app.id); | ||||||
|  | } | ||||||
|  |  | ||||||
| class SourceProvider { | class SourceProvider { | ||||||
|   // Add more source classes here so they are available via the service |   // Add more source classes here so they are available via the service | ||||||
|   List<AppSource> get sources => [ |   List<AppSource> get sources => [ | ||||||
| @@ -626,11 +635,6 @@ class SourceProvider { | |||||||
|           String standardUrl, Map<String, dynamic> additionalSettings) => |           String standardUrl, Map<String, dynamic> additionalSettings) => | ||||||
|       (standardUrl + additionalSettings.toString()).hashCode.toString(); |       (standardUrl + additionalSettings.toString()).hashCode.toString(); | ||||||
|  |  | ||||||
|   bool isTempId(App app) { |  | ||||||
|     // return app.id == generateTempID(app.url, app.additionalSettings); |  | ||||||
|     return RegExp('^[0-9]+\$').hasMatch(app.id); |  | ||||||
|   } |  | ||||||
|  |  | ||||||
|   Future<App> getApp( |   Future<App> getApp( | ||||||
|       AppSource source, String url, Map<String, dynamic> additionalSettings, |       AppSource source, String url, Map<String, dynamic> additionalSettings, | ||||||
|       {App? currentApp, |       {App? currentApp, | ||||||
| @@ -672,7 +676,7 @@ class SourceProvider { | |||||||
|     String apkVersion = apk.version.replaceAll('/', '-'); |     String apkVersion = apk.version.replaceAll('/', '-'); | ||||||
|     var name = currentApp != null ? currentApp.name.trim() : ''; |     var name = currentApp != null ? currentApp.name.trim() : ''; | ||||||
|     name = name.isNotEmpty ? name : apk.names.name; |     name = name.isNotEmpty ? name : apk.names.name; | ||||||
|     return App( |     App finalApp = App( | ||||||
|         currentApp?.id ?? |         currentApp?.id ?? | ||||||
|             ((!source.appIdInferIsOptional || |             ((!source.appIdInferIsOptional || | ||||||
|                     (source.appIdInferIsOptional && inferAppIdIfOptional)) |                     (source.appIdInferIsOptional && inferAppIdIfOptional)) | ||||||
| @@ -698,6 +702,7 @@ class SourceProvider { | |||||||
|             source.appIdInferIsOptional && |             source.appIdInferIsOptional && | ||||||
|                 inferAppIdIfOptional // Optional ID inferring may be incorrect - allow correction on first install |                 inferAppIdIfOptional // Optional ID inferring may be incorrect - allow correction on first install | ||||||
|         ); |         ); | ||||||
|  |     return source.endOfGetAppChanges(finalApp); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // Returns errors in [results, errors] instead of throwing them |   // Returns errors in [results, errors] instead of throwing them | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user