2022-08-25 01:51:47 +02:00
|
|
|
import 'dart:convert';
|
2022-08-01 21:05:11 +02:00
|
|
|
import 'dart:io';
|
2022-08-25 01:51:47 +02:00
|
|
|
import 'package:device_apps/device_apps.dart';
|
|
|
|
import 'package:injectable/injectable.dart';
|
2022-08-18 18:32:58 +02:00
|
|
|
import 'package:package_info_plus/package_info_plus.dart';
|
2022-09-12 02:41:53 +02:00
|
|
|
import 'package:revanced_manager/app/app.locator.dart';
|
2022-09-11 03:01:06 +02:00
|
|
|
import 'package:revanced_manager/models/patch.dart';
|
2022-08-25 01:51:47 +02:00
|
|
|
import 'package:revanced_manager/models/patched_application.dart';
|
2022-08-09 01:01:06 +02:00
|
|
|
import 'package:revanced_manager/services/github_api.dart';
|
2022-09-11 03:01:06 +02:00
|
|
|
import 'package:revanced_manager/services/revanced_api.dart';
|
2022-08-25 01:51:47 +02:00
|
|
|
import 'package:revanced_manager/services/root_api.dart';
|
2022-10-14 20:05:33 +02:00
|
|
|
import 'package:sentry_flutter/sentry_flutter.dart';
|
2022-08-25 01:51:47 +02:00
|
|
|
import 'package:shared_preferences/shared_preferences.dart';
|
2022-07-31 21:46:27 +02:00
|
|
|
|
2022-08-25 01:51:47 +02:00
|
|
|
@lazySingleton
|
2022-08-02 10:06:35 +02:00
|
|
|
class ManagerAPI {
|
2022-09-12 02:41:53 +02:00
|
|
|
final RevancedAPI _revancedAPI = locator<RevancedAPI>();
|
|
|
|
final GithubAPI _githubAPI = locator<GithubAPI>();
|
2022-08-25 01:51:47 +02:00
|
|
|
final RootAPI _rootAPI = RootAPI();
|
2022-09-07 03:37:25 +02:00
|
|
|
final String patcherRepo = 'revanced-patcher';
|
|
|
|
final String cliRepo = 'revanced-cli';
|
2022-08-25 01:51:47 +02:00
|
|
|
late SharedPreferences _prefs;
|
2022-10-08 15:33:45 +02:00
|
|
|
String defaultApiUrl = 'https://releases.rvcd.win/';
|
2022-09-07 03:37:25 +02:00
|
|
|
String defaultPatcherRepo = 'revanced/revanced-patcher';
|
|
|
|
String defaultPatchesRepo = 'revanced/revanced-patches';
|
|
|
|
String defaultIntegrationsRepo = 'revanced/revanced-integrations';
|
|
|
|
String defaultCliRepo = 'revanced/revanced-cli';
|
|
|
|
String defaultManagerRepo = 'revanced/revanced-manager';
|
2022-08-25 01:51:47 +02:00
|
|
|
|
|
|
|
Future<void> initialize() async {
|
|
|
|
_prefs = await SharedPreferences.getInstance();
|
|
|
|
}
|
2022-08-02 10:06:35 +02:00
|
|
|
|
2022-09-18 19:42:30 +02:00
|
|
|
String getApiUrl() {
|
|
|
|
return _prefs.getString('apiUrl') ?? defaultApiUrl;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setApiUrl(String url) async {
|
|
|
|
if (url.isEmpty || url == ' ') {
|
|
|
|
url = defaultApiUrl;
|
|
|
|
}
|
2022-09-19 01:28:26 +02:00
|
|
|
await _revancedAPI.initialize(url);
|
|
|
|
await _revancedAPI.clearAllCache();
|
2022-09-18 19:42:30 +02:00
|
|
|
await _prefs.setString('apiUrl', url);
|
|
|
|
}
|
|
|
|
|
2022-09-07 03:37:25 +02:00
|
|
|
String getPatchesRepo() {
|
|
|
|
return _prefs.getString('patchesRepo') ?? defaultPatchesRepo;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setPatchesRepo(String value) async {
|
2022-09-07 12:14:54 +02:00
|
|
|
if (value.isEmpty || value.startsWith('/') || value.endsWith('/')) {
|
2022-09-07 03:37:25 +02:00
|
|
|
value = defaultPatchesRepo;
|
|
|
|
}
|
|
|
|
await _prefs.setString('patchesRepo', value);
|
|
|
|
}
|
|
|
|
|
|
|
|
String getIntegrationsRepo() {
|
|
|
|
return _prefs.getString('integrationsRepo') ?? defaultIntegrationsRepo;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setIntegrationsRepo(String value) async {
|
2022-09-07 12:14:54 +02:00
|
|
|
if (value.isEmpty || value.startsWith('/') || value.endsWith('/')) {
|
2022-09-07 03:37:25 +02:00
|
|
|
value = defaultIntegrationsRepo;
|
|
|
|
}
|
|
|
|
await _prefs.setString('integrationsRepo', value);
|
|
|
|
}
|
|
|
|
|
2022-09-05 04:32:36 +02:00
|
|
|
bool getUseDynamicTheme() {
|
|
|
|
return _prefs.getBool('useDynamicTheme') ?? false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setUseDynamicTheme(bool value) async {
|
|
|
|
await _prefs.setBool('useDynamicTheme', value);
|
|
|
|
}
|
|
|
|
|
|
|
|
bool getUseDarkTheme() {
|
|
|
|
return _prefs.getBool('useDarkTheme') ?? false;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setUseDarkTheme(bool value) async {
|
|
|
|
await _prefs.setBool('useDarkTheme', value);
|
|
|
|
}
|
|
|
|
|
2022-10-14 22:22:10 +02:00
|
|
|
bool isSentryEnabled() {
|
|
|
|
return _prefs.getBool('sentryEnabled') ?? true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setSentryStatus(bool value) async {
|
|
|
|
await _prefs.setBool('sentryEnabled', value);
|
|
|
|
}
|
|
|
|
|
2022-10-15 14:59:50 +02:00
|
|
|
bool isCrashlyticsEnabled() {
|
|
|
|
return _prefs.getBool('crashlyticsEnabled') ?? true;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<void> setCrashlyticsStatus(bool value) async {
|
|
|
|
await _prefs.setBool('crashlyticsEnabled', value);
|
|
|
|
}
|
|
|
|
|
2022-08-25 01:51:47 +02:00
|
|
|
List<PatchedApplication> getPatchedApps() {
|
|
|
|
List<String> apps = _prefs.getStringList('patchedApps') ?? [];
|
2022-09-11 03:01:06 +02:00
|
|
|
return apps.map((a) => PatchedApplication.fromJson(jsonDecode(a))).toList();
|
2022-08-25 01:51:47 +02:00
|
|
|
}
|
|
|
|
|
2022-08-30 03:07:28 +02:00
|
|
|
Future<void> setPatchedApps(List<PatchedApplication> patchedApps) async {
|
2022-09-03 16:28:22 +02:00
|
|
|
if (patchedApps.length > 1) {
|
|
|
|
patchedApps.sort((a, b) => a.name.compareTo(b.name));
|
|
|
|
}
|
2022-08-30 03:07:28 +02:00
|
|
|
await _prefs.setStringList('patchedApps',
|
2022-08-25 01:51:47 +02:00
|
|
|
patchedApps.map((a) => json.encode(a.toJson())).toList());
|
|
|
|
}
|
|
|
|
|
2022-08-30 03:07:28 +02:00
|
|
|
Future<void> savePatchedApp(PatchedApplication app) async {
|
2022-08-25 01:51:47 +02:00
|
|
|
List<PatchedApplication> patchedApps = getPatchedApps();
|
|
|
|
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
2022-09-20 02:07:36 +02:00
|
|
|
ApplicationWithIcon? installed = await DeviceApps.getApp(
|
|
|
|
app.packageName,
|
|
|
|
true,
|
|
|
|
) as ApplicationWithIcon?;
|
2022-09-01 14:52:51 +02:00
|
|
|
if (installed != null) {
|
|
|
|
app.name = installed.appName;
|
|
|
|
app.version = installed.versionName!;
|
|
|
|
app.icon = installed.icon;
|
|
|
|
}
|
2022-08-25 01:51:47 +02:00
|
|
|
patchedApps.add(app);
|
2022-08-30 03:07:28 +02:00
|
|
|
await setPatchedApps(patchedApps);
|
2022-08-25 01:51:47 +02:00
|
|
|
}
|
|
|
|
|
2022-09-05 14:43:13 +02:00
|
|
|
Future<void> deletePatchedApp(PatchedApplication app) async {
|
|
|
|
List<PatchedApplication> patchedApps = getPatchedApps();
|
|
|
|
patchedApps.removeWhere((a) => a.packageName == app.packageName);
|
|
|
|
await setPatchedApps(patchedApps);
|
|
|
|
}
|
|
|
|
|
2022-10-14 20:05:33 +02:00
|
|
|
void clearAllData() async {
|
|
|
|
try {
|
|
|
|
_revancedAPI.clearAllCache();
|
|
|
|
_githubAPI.clearAllCache();
|
|
|
|
} on Exception catch (e, s) {
|
|
|
|
await Sentry.captureException(e, stackTrace: s);
|
|
|
|
}
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<Map<String, List<dynamic>>> getContributors() async {
|
2022-09-19 01:28:26 +02:00
|
|
|
return await _revancedAPI.getContributors();
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<Patch>> getPatches() async {
|
2022-10-14 20:05:33 +02:00
|
|
|
try {
|
|
|
|
String repoName = getPatchesRepo();
|
|
|
|
if (repoName == defaultPatchesRepo) {
|
|
|
|
return await _revancedAPI.getPatches();
|
|
|
|
} else {
|
|
|
|
return await _githubAPI.getPatches(repoName);
|
|
|
|
}
|
|
|
|
} on Exception catch (e, s) {
|
|
|
|
await Sentry.captureException(e, stackTrace: s);
|
|
|
|
return [];
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<File?> downloadPatches() async {
|
2022-10-14 20:05:33 +02:00
|
|
|
try {
|
|
|
|
String repoName = getPatchesRepo();
|
|
|
|
if (repoName == defaultPatchesRepo) {
|
|
|
|
return await _revancedAPI.getLatestReleaseFile(
|
|
|
|
'.jar',
|
|
|
|
defaultPatchesRepo,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return await _githubAPI.getLatestReleaseFile('.jar', repoName);
|
|
|
|
}
|
|
|
|
} on Exception catch (e, s) {
|
|
|
|
await Sentry.captureException(e, stackTrace: s);
|
|
|
|
return null;
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<File?> downloadIntegrations() async {
|
2022-10-14 20:05:33 +02:00
|
|
|
try {
|
|
|
|
String repoName = getIntegrationsRepo();
|
|
|
|
if (repoName == defaultIntegrationsRepo) {
|
|
|
|
return await _revancedAPI.getLatestReleaseFile(
|
|
|
|
'.apk',
|
|
|
|
defaultIntegrationsRepo,
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
return await _githubAPI.getLatestReleaseFile('.apk', repoName);
|
|
|
|
}
|
|
|
|
} on Exception catch (e, s) {
|
|
|
|
await Sentry.captureException(e, stackTrace: s);
|
|
|
|
return null;
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<File?> downloadManager() async {
|
2022-09-19 01:28:26 +02:00
|
|
|
return await _revancedAPI.getLatestReleaseFile('.apk', defaultManagerRepo);
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<String?> getLatestPatcherReleaseTime() async {
|
2022-09-19 01:28:26 +02:00
|
|
|
return await _revancedAPI.getLatestReleaseTime('.gz', defaultPatcherRepo);
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<String?> getLatestManagerReleaseTime() async {
|
2022-09-19 01:28:26 +02:00
|
|
|
return await _revancedAPI.getLatestReleaseTime('.apk', defaultManagerRepo);
|
2022-09-11 03:01:06 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<String?> getLatestManagerVersion() async {
|
|
|
|
return await _revancedAPI.getLatestReleaseVersion(
|
|
|
|
'.apk',
|
|
|
|
defaultManagerRepo,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<String> getCurrentManagerVersion() async {
|
|
|
|
PackageInfo packageInfo = await PackageInfo.fromPlatform();
|
|
|
|
return packageInfo.version;
|
|
|
|
}
|
|
|
|
|
2022-09-17 20:29:46 +02:00
|
|
|
Future<List<PatchedApplication>> getAppsToRemove(
|
|
|
|
List<PatchedApplication> patchedApps,
|
|
|
|
) async {
|
2022-08-26 03:01:53 +02:00
|
|
|
List<PatchedApplication> toRemove = [];
|
2022-08-25 01:51:47 +02:00
|
|
|
for (PatchedApplication app in patchedApps) {
|
2022-09-06 15:40:49 +02:00
|
|
|
bool isRemove = await isAppUninstalled(app);
|
2022-08-29 16:01:51 +02:00
|
|
|
if (isRemove) {
|
2022-08-26 03:01:53 +02:00
|
|
|
toRemove.add(app);
|
2022-09-17 20:29:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
return toRemove;
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<PatchedApplication>> getUnsavedApps(
|
|
|
|
List<PatchedApplication> patchedApps,
|
|
|
|
) async {
|
|
|
|
List<PatchedApplication> unsavedApps = [];
|
2022-09-18 02:54:25 +02:00
|
|
|
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
|
|
|
if (hasRootPermissions) {
|
|
|
|
List<String> installedApps = await _rootAPI.getInstalledApps();
|
|
|
|
for (String packageName in installedApps) {
|
|
|
|
if (!patchedApps.any((app) => app.packageName == packageName)) {
|
2022-09-20 02:07:36 +02:00
|
|
|
ApplicationWithIcon? application = await DeviceApps.getApp(
|
|
|
|
packageName,
|
|
|
|
true,
|
|
|
|
) as ApplicationWithIcon?;
|
2022-09-18 02:54:25 +02:00
|
|
|
if (application != null) {
|
|
|
|
unsavedApps.add(
|
|
|
|
PatchedApplication(
|
|
|
|
name: application.appName,
|
|
|
|
packageName: application.packageName,
|
2022-10-10 15:15:58 +02:00
|
|
|
originalPackageName: application.packageName,
|
2022-09-18 02:54:25 +02:00
|
|
|
version: application.versionName!,
|
|
|
|
apkFilePath: application.apkFilePath,
|
|
|
|
icon: application.icon,
|
|
|
|
patchDate: DateTime.now(),
|
|
|
|
isRooted: true,
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2022-09-17 20:29:46 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
List<Application> userApps = await DeviceApps.getInstalledApplications(
|
|
|
|
includeSystemApps: false,
|
|
|
|
includeAppIcons: false,
|
|
|
|
);
|
|
|
|
for (Application app in userApps) {
|
|
|
|
if (app.packageName.startsWith('app.revanced') &&
|
2022-09-18 05:44:44 +02:00
|
|
|
!app.packageName.startsWith('app.revanced.manager.') &&
|
|
|
|
!patchedApps.any((uapp) => uapp.packageName == app.packageName)) {
|
2022-09-20 02:07:36 +02:00
|
|
|
ApplicationWithIcon? application = await DeviceApps.getApp(
|
|
|
|
app.packageName,
|
|
|
|
true,
|
|
|
|
) as ApplicationWithIcon?;
|
2022-09-17 20:29:46 +02:00
|
|
|
if (application != null) {
|
|
|
|
unsavedApps.add(
|
|
|
|
PatchedApplication(
|
|
|
|
name: application.appName,
|
|
|
|
packageName: application.packageName,
|
2022-10-10 15:15:58 +02:00
|
|
|
originalPackageName: application.packageName,
|
2022-09-17 20:29:46 +02:00
|
|
|
version: application.versionName!,
|
|
|
|
apkFilePath: application.apkFilePath,
|
|
|
|
icon: application.icon,
|
|
|
|
patchDate: DateTime.now(),
|
|
|
|
isRooted: false,
|
|
|
|
),
|
|
|
|
);
|
2022-09-01 14:23:24 +02:00
|
|
|
}
|
2022-08-25 01:51:47 +02:00
|
|
|
}
|
|
|
|
}
|
2022-09-17 20:29:46 +02:00
|
|
|
return unsavedApps;
|
|
|
|
}
|
|
|
|
|
2022-10-11 16:57:29 +02:00
|
|
|
Future<void> reAssessSavedApps() async {
|
|
|
|
List<PatchedApplication> patchedApps = getPatchedApps();
|
|
|
|
List<PatchedApplication> unsavedApps = await getUnsavedApps(patchedApps);
|
|
|
|
patchedApps.addAll(unsavedApps);
|
|
|
|
List<PatchedApplication> toRemove = await getAppsToRemove(patchedApps);
|
|
|
|
patchedApps.removeWhere((a) => toRemove.contains(a));
|
|
|
|
for (PatchedApplication app in patchedApps) {
|
|
|
|
app.hasUpdates =
|
|
|
|
await hasAppUpdates(app.originalPackageName, app.patchDate);
|
|
|
|
app.changelog =
|
|
|
|
await getAppChangelog(app.originalPackageName, app.patchDate);
|
|
|
|
if (!app.hasUpdates) {
|
|
|
|
String? currentInstalledVersion =
|
|
|
|
(await DeviceApps.getApp(app.packageName))?.versionName;
|
|
|
|
if (currentInstalledVersion != null) {
|
|
|
|
String currentSavedVersion = app.version;
|
|
|
|
int currentInstalledVersionInt = int.parse(
|
|
|
|
currentInstalledVersion.replaceAll(RegExp('[^0-9]'), ''));
|
|
|
|
int currentSavedVersionInt =
|
|
|
|
int.parse(currentSavedVersion.replaceAll(RegExp('[^0-9]'), ''));
|
|
|
|
if (currentInstalledVersionInt > currentSavedVersionInt) {
|
|
|
|
app.hasUpdates = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
await setPatchedApps(patchedApps);
|
|
|
|
}
|
|
|
|
|
2022-09-06 15:40:49 +02:00
|
|
|
Future<bool> isAppUninstalled(PatchedApplication app) async {
|
2022-08-29 16:01:51 +02:00
|
|
|
bool existsRoot = false;
|
2022-09-18 02:54:25 +02:00
|
|
|
bool existsNonRoot = await DeviceApps.isAppInstalled(app.packageName);
|
2022-09-06 15:40:49 +02:00
|
|
|
if (app.isRooted) {
|
2022-09-13 17:54:43 +02:00
|
|
|
bool hasRootPermissions = await _rootAPI.hasRootPermissions();
|
|
|
|
if (hasRootPermissions) {
|
|
|
|
existsRoot = await _rootAPI.isAppInstalled(app.packageName);
|
|
|
|
}
|
2022-09-18 02:54:25 +02:00
|
|
|
return !existsRoot || !existsNonRoot;
|
2022-08-29 16:01:51 +02:00
|
|
|
}
|
2022-09-18 02:54:25 +02:00
|
|
|
return !existsNonRoot;
|
2022-08-18 18:32:58 +02:00
|
|
|
}
|
|
|
|
|
2022-08-30 03:07:28 +02:00
|
|
|
Future<bool> hasAppUpdates(String packageName, DateTime patchDate) async {
|
2022-09-11 03:01:06 +02:00
|
|
|
List<String> commits = await _githubAPI.getCommits(
|
2022-09-07 03:37:25 +02:00
|
|
|
packageName,
|
|
|
|
getPatchesRepo(),
|
2022-09-11 03:01:06 +02:00
|
|
|
patchDate,
|
2022-09-07 03:37:25 +02:00
|
|
|
);
|
2022-09-11 03:01:06 +02:00
|
|
|
return commits.isNotEmpty;
|
2022-08-30 03:07:28 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<String>> getAppChangelog(
|
2022-09-11 03:01:06 +02:00
|
|
|
String packageName, DateTime patchDate) async {
|
|
|
|
List<String> newCommits = await _githubAPI.getCommits(
|
2022-09-07 03:37:25 +02:00
|
|
|
packageName,
|
|
|
|
getPatchesRepo(),
|
2022-09-11 03:01:06 +02:00
|
|
|
patchDate,
|
2022-09-07 03:37:25 +02:00
|
|
|
);
|
2022-08-30 03:07:28 +02:00
|
|
|
if (newCommits.isEmpty) {
|
2022-09-11 03:01:06 +02:00
|
|
|
newCommits = await _githubAPI.getCommits(
|
|
|
|
packageName,
|
|
|
|
getPatchesRepo(),
|
2022-10-08 18:36:45 +02:00
|
|
|
patchDate,
|
2022-09-11 03:01:06 +02:00
|
|
|
);
|
2022-08-29 16:01:51 +02:00
|
|
|
}
|
2022-08-30 03:07:28 +02:00
|
|
|
return newCommits;
|
2022-08-02 09:55:01 +02:00
|
|
|
}
|
2022-09-23 16:31:24 +02:00
|
|
|
|
|
|
|
Future<bool> isSplitApk(PatchedApplication patchedApp) async {
|
|
|
|
Application? app;
|
|
|
|
if (patchedApp.isFromStorage) {
|
|
|
|
app = await DeviceApps.getAppFromStorage(patchedApp.apkFilePath);
|
|
|
|
} else {
|
|
|
|
app = await DeviceApps.getApp(patchedApp.packageName);
|
|
|
|
}
|
|
|
|
return app != null && app.isSplit;
|
|
|
|
}
|
2022-08-01 21:05:11 +02:00
|
|
|
}
|