2022-01-30 21:14:36 +00:00
|
|
|
package com.fox2code.mmm.androidacy;
|
|
|
|
|
2022-12-24 21:07:14 +00:00
|
|
|
import android.annotation.SuppressLint;
|
|
|
|
import android.content.Intent;
|
2022-01-30 21:14:36 +00:00
|
|
|
import android.content.SharedPreferences;
|
2022-12-24 21:07:14 +00:00
|
|
|
import android.net.Uri;
|
2022-01-30 21:14:36 +00:00
|
|
|
import android.util.Log;
|
2022-10-12 01:15:13 +00:00
|
|
|
import android.widget.Toast;
|
2022-01-30 21:14:36 +00:00
|
|
|
|
2022-07-25 17:36:45 +00:00
|
|
|
import androidx.annotation.NonNull;
|
|
|
|
|
2022-11-26 02:00:30 +00:00
|
|
|
import com.fox2code.mmm.BuildConfig;
|
2022-10-12 01:15:13 +00:00
|
|
|
import com.fox2code.mmm.MainApplication;
|
2022-01-30 21:14:36 +00:00
|
|
|
import com.fox2code.mmm.R;
|
|
|
|
import com.fox2code.mmm.manager.ModuleInfo;
|
|
|
|
import com.fox2code.mmm.repo.RepoData;
|
2022-07-16 19:29:25 +00:00
|
|
|
import com.fox2code.mmm.repo.RepoManager;
|
2022-01-30 21:14:36 +00:00
|
|
|
import com.fox2code.mmm.repo.RepoModule;
|
|
|
|
import com.fox2code.mmm.utils.Http;
|
2022-10-19 14:25:56 +00:00
|
|
|
import com.fox2code.mmm.utils.HttpException;
|
2022-01-30 21:14:36 +00:00
|
|
|
import com.fox2code.mmm.utils.PropUtils;
|
2022-12-02 16:16:42 +00:00
|
|
|
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
|
2022-12-06 22:29:28 +00:00
|
|
|
import com.topjohnwu.superuser.Shell;
|
2022-01-30 21:14:36 +00:00
|
|
|
|
|
|
|
import org.json.JSONArray;
|
|
|
|
import org.json.JSONException;
|
|
|
|
import org.json.JSONObject;
|
|
|
|
|
|
|
|
import java.io.File;
|
2022-10-19 14:25:56 +00:00
|
|
|
import java.io.IOException;
|
2022-12-24 21:07:14 +00:00
|
|
|
import java.net.HttpURLConnection;
|
|
|
|
import java.net.URL;
|
2022-12-06 22:29:28 +00:00
|
|
|
import java.security.MessageDigest;
|
|
|
|
import java.security.NoSuchAlgorithmException;
|
2022-01-30 21:14:36 +00:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.Iterator;
|
|
|
|
import java.util.List;
|
2022-12-07 20:38:55 +00:00
|
|
|
import java.util.Objects;
|
2022-01-30 21:14:36 +00:00
|
|
|
|
2022-04-12 16:01:47 +00:00
|
|
|
import okhttp3.HttpUrl;
|
|
|
|
|
|
|
|
@SuppressWarnings("KotlinInternalInJava")
|
2022-07-28 11:31:25 +00:00
|
|
|
public final class AndroidacyRepoData extends RepoData {
|
2022-01-30 21:14:36 +00:00
|
|
|
private static final String TAG = "AndroidacyRepoData";
|
2022-08-24 15:39:00 +00:00
|
|
|
|
2022-04-12 16:01:47 +00:00
|
|
|
static {
|
2022-10-12 01:15:13 +00:00
|
|
|
HttpUrl.Builder OK_HTTP_URL_BUILDER = new HttpUrl.Builder().scheme("https");
|
2022-04-12 16:01:47 +00:00
|
|
|
// Using HttpUrl.Builder.host(String) crash the app
|
|
|
|
OK_HTTP_URL_BUILDER.setHost$okhttp(".androidacy.com");
|
2022-08-24 15:39:00 +00:00
|
|
|
OK_HTTP_URL_BUILDER.build();
|
2022-04-12 16:01:47 +00:00
|
|
|
}
|
2022-08-24 15:39:00 +00:00
|
|
|
|
2022-12-06 22:29:28 +00:00
|
|
|
@SuppressWarnings("unused")
|
|
|
|
public final String ClientID = BuildConfig.ANDROIDACY_CLIENT_ID;
|
|
|
|
|
2022-07-25 17:36:45 +00:00
|
|
|
private final boolean testMode;
|
|
|
|
private final String host;
|
2022-11-29 21:43:33 +00:00
|
|
|
public String token = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).getString("pref_androidacy_api_token", null);
|
2022-10-12 01:15:13 +00:00
|
|
|
// Avoid spamming requests to Androidacy
|
|
|
|
private long androidacyBlockade = 0;
|
2022-01-30 21:14:36 +00:00
|
|
|
|
2022-10-12 01:15:13 +00:00
|
|
|
public AndroidacyRepoData(File cacheRoot, SharedPreferences cachedPreferences, boolean testMode) {
|
|
|
|
super(testMode ? RepoManager.ANDROIDACY_TEST_MAGISK_REPO_ENDPOINT : RepoManager.ANDROIDACY_MAGISK_REPO_ENDPOINT, cacheRoot, cachedPreferences);
|
2022-12-24 16:51:19 +00:00
|
|
|
// make sure the modules.json exists
|
|
|
|
File modulesJson = new File(cacheRoot, "modules.json");
|
|
|
|
if (!modulesJson.exists()) {
|
|
|
|
try {
|
|
|
|
if (!modulesJson.createNewFile()) {
|
|
|
|
throw new IOException("Failed to create modules.json");
|
|
|
|
}
|
2022-12-24 21:07:14 +00:00
|
|
|
} catch (
|
|
|
|
IOException e) {
|
2022-12-24 16:51:19 +00:00
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
2022-07-16 19:29:25 +00:00
|
|
|
this.defaultName = "Androidacy Modules Repo";
|
|
|
|
this.defaultWebsite = RepoManager.ANDROIDACY_MAGISK_REPO_HOMEPAGE;
|
|
|
|
this.defaultSupport = "https://t.me/androidacy_discussions";
|
2022-07-25 19:57:18 +00:00
|
|
|
this.defaultDonate = "https://www.androidacy.com/membership-join/?utm_source=foxmmm&utm-medium=app&utm_campaign=fox-inapp";
|
2022-07-16 19:29:25 +00:00
|
|
|
this.defaultSubmitModule = "https://www.androidacy.com/module-repository-applications/";
|
2022-08-24 15:39:00 +00:00
|
|
|
this.host = testMode ? "staging-api.androidacy.com" : "production-api.androidacy.com";
|
2022-07-25 17:36:45 +00:00
|
|
|
this.testMode = testMode;
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
|
|
|
|
2022-08-01 11:36:55 +00:00
|
|
|
public static AndroidacyRepoData getInstance() {
|
|
|
|
return RepoManager.getINSTANCE().getAndroidacyRepoData();
|
|
|
|
}
|
|
|
|
|
2022-10-12 01:15:13 +00:00
|
|
|
private static String filterURL(String url) {
|
|
|
|
if (url == null || url.isEmpty() || PropUtils.isInvalidURL(url)) {
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
return url;
|
|
|
|
}
|
|
|
|
|
2022-11-29 21:43:33 +00:00
|
|
|
// Generates a unique device ID. This is used to identify the device in the API for rate
|
|
|
|
// limiting and fraud detection.
|
2022-12-06 22:29:28 +00:00
|
|
|
public static String generateDeviceId() throws NoSuchAlgorithmException {
|
2022-11-29 21:43:33 +00:00
|
|
|
// Try to get the device ID from the shared preferences
|
|
|
|
SharedPreferences sharedPreferences = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0);
|
|
|
|
String deviceIdPref = sharedPreferences.getString("device_id", null);
|
|
|
|
if (deviceIdPref != null) {
|
|
|
|
return deviceIdPref;
|
|
|
|
} else {
|
2022-12-06 22:29:28 +00:00
|
|
|
// AAAA we're fingerprintiiiiing
|
|
|
|
// Really not that scary - just hashes some device info. We can't even get the info
|
|
|
|
// we originally hashed, so it's not like we can use it to track you.
|
|
|
|
String deviceId = null;
|
|
|
|
// Get ro.serialno if it exists
|
|
|
|
// First, we need to get an su shell
|
|
|
|
Shell.Result result = Shell.cmd("getprop ro.serialno").exec();
|
|
|
|
// Check if the command was successful
|
|
|
|
if (result.isSuccess()) {
|
|
|
|
// Get the output
|
|
|
|
String output = result.getOut().get(0);
|
|
|
|
// Check if the output is valid
|
|
|
|
if (output != null && !output.isEmpty()) {
|
|
|
|
deviceId = output;
|
|
|
|
}
|
|
|
|
}
|
2022-12-14 21:24:54 +00:00
|
|
|
// Now, get device model, manufacturer, and Android version originally from
|
2022-12-06 22:29:28 +00:00
|
|
|
String deviceModel = android.os.Build.MODEL;
|
|
|
|
String deviceManufacturer = android.os.Build.MANUFACTURER;
|
|
|
|
String androidVersion = android.os.Build.VERSION.RELEASE;
|
|
|
|
// Append it all together
|
|
|
|
deviceId += deviceModel + deviceManufacturer + androidVersion;
|
|
|
|
// Hash it
|
|
|
|
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
|
|
|
byte[] hash = digest.digest(deviceId.getBytes());
|
|
|
|
// Convert it to a hex string
|
|
|
|
StringBuilder hexString = new StringBuilder();
|
|
|
|
for (byte b : hash) {
|
|
|
|
String hex = Integer.toHexString(0xff & b);
|
|
|
|
if (hex.length() == 1) {
|
|
|
|
hexString.append('0');
|
|
|
|
}
|
|
|
|
hexString.append(hex);
|
|
|
|
}
|
|
|
|
// Save it to shared preferences
|
|
|
|
SharedPreferences.Editor editor = sharedPreferences.edit();
|
|
|
|
editor.putString("device_id", hexString.toString());
|
|
|
|
editor.apply();
|
|
|
|
// Return it
|
|
|
|
return hexString.toString();
|
2022-11-29 21:43:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-12-06 22:29:28 +00:00
|
|
|
public boolean isValidToken(String token) throws IOException, NoSuchAlgorithmException {
|
2022-11-29 21:43:33 +00:00
|
|
|
String deviceId = generateDeviceId();
|
2022-08-25 15:38:03 +00:00
|
|
|
try {
|
2022-11-29 21:43:33 +00:00
|
|
|
Http.doHttpGet("https://" + this.host + "/auth/me?token=" + token + "&device_id=" + deviceId, false);
|
2022-12-24 21:07:14 +00:00
|
|
|
} catch (
|
|
|
|
HttpException e) {
|
2022-10-19 14:25:56 +00:00
|
|
|
if (e.getErrorCode() == 401) {
|
|
|
|
Log.w(TAG, "Invalid token, resetting...");
|
|
|
|
// Remove saved preference
|
2022-11-29 21:43:33 +00:00
|
|
|
SharedPreferences.Editor editor = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).edit();
|
2022-11-26 02:00:30 +00:00
|
|
|
editor.remove("pref_androidacy_api_token");
|
2022-10-19 14:25:56 +00:00
|
|
|
editor.apply();
|
2022-08-25 15:38:03 +00:00
|
|
|
return false;
|
|
|
|
}
|
2022-10-19 14:25:56 +00:00
|
|
|
throw e;
|
2022-08-25 15:38:03 +00:00
|
|
|
}
|
|
|
|
// If status code is 200, we are good
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2022-12-24 21:07:14 +00:00
|
|
|
@SuppressLint("RestrictedApi")
|
2022-01-30 21:14:36 +00:00
|
|
|
@Override
|
2022-12-06 22:29:28 +00:00
|
|
|
protected boolean prepare() throws NoSuchAlgorithmException {
|
2022-12-07 20:38:55 +00:00
|
|
|
// If ANDROIDACY_CLIENT_ID is not set or is empty, disable this repo and return
|
|
|
|
if (Objects.equals(BuildConfig.ANDROIDACY_CLIENT_ID, "")) {
|
|
|
|
SharedPreferences.Editor editor = this.cachedPreferences.edit();
|
|
|
|
editor.putBoolean("pref_androidacy_repo_enabled", false);
|
|
|
|
editor.apply();
|
|
|
|
return false;
|
|
|
|
}
|
2022-12-24 21:07:14 +00:00
|
|
|
if (Http.needCaptchaAndroidacy())
|
|
|
|
return false;
|
2022-01-30 21:14:36 +00:00
|
|
|
// Implementation details discussed on telegram
|
2022-10-12 01:15:13 +00:00
|
|
|
// First, ping the server to check if it's alive
|
|
|
|
try {
|
2022-12-24 21:07:14 +00:00
|
|
|
HttpURLConnection connection = (HttpURLConnection) new URL("https://" + this.host + "/ping").openConnection();
|
|
|
|
connection.setRequestMethod("GET");
|
|
|
|
connection.setReadTimeout(5000);
|
|
|
|
connection.connect();
|
|
|
|
if (connection.getResponseCode() != 200 && connection.getResponseCode() != 204) {
|
|
|
|
// If it's a 400, the app is probably outdated. Show a snackbar suggesting user update app and webview
|
|
|
|
if (connection.getResponseCode() == 400) {
|
|
|
|
// Show a dialog using androidacy_update_needed string
|
2022-12-27 19:13:31 +00:00
|
|
|
new MaterialAlertDialogBuilder(MainApplication.getINSTANCE()).setTitle(R.string.androidacy_update_needed).setMessage(R.string.androidacy_update_needed_message).setPositiveButton(R.string.update, (dialog, which) -> {
|
|
|
|
// Open the app's page on the Play Store
|
|
|
|
Intent intent = new Intent(Intent.ACTION_VIEW);
|
|
|
|
intent.setData(Uri.parse("https://github.com/Fox2Code/FoxMagiskModuleManager/releases/latest"));
|
|
|
|
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
|
|
|
|
MainApplication.getINSTANCE().startActivity(intent);
|
|
|
|
}).setNegativeButton(R.string.cancel, null).show();
|
2022-12-24 21:07:14 +00:00
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
} catch (
|
|
|
|
Exception e) {
|
2022-10-12 01:15:13 +00:00
|
|
|
Log.e(TAG, "Failed to ping server", e);
|
|
|
|
return false;
|
|
|
|
}
|
2022-11-29 21:43:33 +00:00
|
|
|
String deviceId = generateDeviceId();
|
2022-01-30 21:14:36 +00:00
|
|
|
long time = System.currentTimeMillis();
|
2022-12-24 21:07:14 +00:00
|
|
|
if (this.androidacyBlockade > time)
|
|
|
|
return true; // fake it till you make it. Basically,
|
2022-12-09 19:42:18 +00:00
|
|
|
// don'e fail just becaue we're rate limited. API and web rate limits are different.
|
2022-05-14 21:47:43 +00:00
|
|
|
this.androidacyBlockade = time + 30_000L;
|
2022-10-19 14:25:56 +00:00
|
|
|
try {
|
|
|
|
if (this.token == null) {
|
|
|
|
this.token = this.cachedPreferences.getString("pref_androidacy_api_token", null);
|
|
|
|
if (this.token != null && !this.isValidToken(this.token)) {
|
|
|
|
this.token = null;
|
2022-11-26 02:00:30 +00:00
|
|
|
} else {
|
|
|
|
Log.i(TAG, "Using cached token");
|
2022-10-19 14:25:56 +00:00
|
|
|
}
|
|
|
|
} else if (!this.isValidToken(this.token)) {
|
2022-11-26 02:00:30 +00:00
|
|
|
if (BuildConfig.DEBUG) {
|
|
|
|
throw new IllegalStateException("Invalid token: " + this.token);
|
|
|
|
}
|
2022-10-12 01:15:13 +00:00
|
|
|
this.token = null;
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
2022-12-24 21:07:14 +00:00
|
|
|
} catch (
|
|
|
|
IOException e) {
|
2022-10-19 14:25:56 +00:00
|
|
|
if (HttpException.shouldTimeout(e)) {
|
|
|
|
Log.e(TAG, "We are being rate limited!", e);
|
|
|
|
this.androidacyBlockade = time + 3_600_000L;
|
|
|
|
}
|
|
|
|
return false;
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
|
|
|
if (token == null) {
|
|
|
|
try {
|
2022-11-26 02:00:30 +00:00
|
|
|
Log.i(TAG, "Requesting new token...");
|
2022-11-29 21:43:33 +00:00
|
|
|
// POST json request to https://production-api.androidacy.com/auth/register
|
|
|
|
token = new String(Http.doHttpPost("https://" + this.host + "/auth/register", "{\"device_id\":\"" + deviceId + "\"}", false));
|
2022-08-25 01:21:06 +00:00
|
|
|
// Parse token
|
2022-10-12 01:15:13 +00:00
|
|
|
try {
|
|
|
|
JSONObject jsonObject = new JSONObject(token);
|
|
|
|
token = jsonObject.getString("token");
|
2022-12-24 21:07:14 +00:00
|
|
|
} catch (
|
|
|
|
JSONException e) {
|
2022-10-12 01:15:13 +00:00
|
|
|
Log.e(TAG, "Failed to parse token", e);
|
|
|
|
// Show a toast
|
|
|
|
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_parse_token, Toast.LENGTH_LONG).show();
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
// Ensure token is valid
|
|
|
|
if (!isValidToken(token)) {
|
|
|
|
Log.e(TAG, "Failed to validate token");
|
|
|
|
// Show a toast
|
|
|
|
Toast.makeText(MainApplication.getINSTANCE(), R.string.androidacy_failed_to_validate_token, Toast.LENGTH_LONG).show();
|
|
|
|
return false;
|
|
|
|
}
|
2022-08-24 15:39:00 +00:00
|
|
|
// Save token to shared preference
|
2022-11-29 21:43:33 +00:00
|
|
|
SharedPreferences.Editor editor = MainApplication.getINSTANCE().getSharedPreferences("androidacy", 0).edit();
|
2022-11-26 02:00:30 +00:00
|
|
|
editor.putString("pref_androidacy_api_token", token);
|
|
|
|
editor.apply();
|
2022-12-24 21:07:14 +00:00
|
|
|
} catch (
|
|
|
|
Exception e) {
|
2022-10-19 14:25:56 +00:00
|
|
|
if (HttpException.shouldTimeout(e)) {
|
2022-01-30 21:14:36 +00:00
|
|
|
Log.e(TAG, "We are being rate limited!", e);
|
|
|
|
this.androidacyBlockade = time + 3_600_000L;
|
|
|
|
}
|
|
|
|
Log.e(TAG, "Failed to get a new token", e);
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
2022-10-12 01:15:13 +00:00
|
|
|
//noinspection SillyAssignment // who are you calling silly?
|
2022-06-03 19:15:38 +00:00
|
|
|
this.token = token;
|
2022-01-30 21:14:36 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-12-06 22:29:28 +00:00
|
|
|
protected List<RepoModule> populate(JSONObject jsonObject) throws JSONException, NoSuchAlgorithmException {
|
2022-12-09 19:42:18 +00:00
|
|
|
if (BuildConfig.DEBUG) {
|
2022-12-27 19:13:31 +00:00
|
|
|
Log.d(TAG, "AndroidacyRepoData populate start");
|
2022-12-09 19:42:18 +00:00
|
|
|
}
|
2022-01-30 21:14:36 +00:00
|
|
|
if (!jsonObject.getString("status").equals("success"))
|
|
|
|
throw new JSONException("Response is not a success!");
|
2022-10-12 01:15:13 +00:00
|
|
|
String name = jsonObject.optString("name", "Androidacy Modules Repo");
|
|
|
|
String nameForModules = name.endsWith(" (Official)") ? name.substring(0, name.length() - 11) : name;
|
2022-01-30 21:14:36 +00:00
|
|
|
JSONArray jsonArray = jsonObject.getJSONArray("data");
|
|
|
|
for (RepoModule repoModule : this.moduleHashMap.values()) {
|
|
|
|
repoModule.processed = false;
|
|
|
|
}
|
|
|
|
ArrayList<RepoModule> newModules = new ArrayList<>();
|
|
|
|
int len = jsonArray.length();
|
|
|
|
long lastLastUpdate = 0;
|
|
|
|
for (int i = 0; i < len; i++) {
|
|
|
|
jsonObject = jsonArray.getJSONObject(i);
|
|
|
|
String moduleId = jsonObject.getString("codename");
|
2023-01-03 18:23:35 +00:00
|
|
|
// Normally, we'd validate the module id here, but we don't need to because the server does it for us
|
2022-01-30 21:14:36 +00:00
|
|
|
long lastUpdate = jsonObject.getLong("updated_at") * 1000;
|
|
|
|
lastLastUpdate = Math.max(lastLastUpdate, lastUpdate);
|
|
|
|
RepoModule repoModule = this.moduleHashMap.get(moduleId);
|
|
|
|
if (repoModule == null) {
|
2022-02-01 20:23:00 +00:00
|
|
|
repoModule = new RepoModule(this, moduleId);
|
2022-01-30 21:14:36 +00:00
|
|
|
repoModule.moduleInfo.flags = 0;
|
|
|
|
this.moduleHashMap.put(moduleId, repoModule);
|
|
|
|
newModules.add(repoModule);
|
|
|
|
} else {
|
|
|
|
if (repoModule.lastUpdated < lastUpdate) {
|
|
|
|
newModules.add(repoModule);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
repoModule.processed = true;
|
|
|
|
repoModule.lastUpdated = lastUpdate;
|
|
|
|
repoModule.repoName = nameForModules;
|
2022-10-12 01:15:13 +00:00
|
|
|
repoModule.zipUrl = filterURL(jsonObject.optString("zipUrl", ""));
|
|
|
|
repoModule.notesUrl = filterURL(jsonObject.optString("notesUrl", ""));
|
2022-08-24 15:39:00 +00:00
|
|
|
if (repoModule.zipUrl == null) {
|
2022-02-06 01:04:39 +00:00
|
|
|
repoModule.zipUrl = // Fallback url in case the API doesn't have zipUrl
|
2022-07-25 19:55:06 +00:00
|
|
|
"https://" + this.host + "/magisk/info/" + moduleId;
|
2022-02-06 01:04:39 +00:00
|
|
|
}
|
2022-01-30 21:14:36 +00:00
|
|
|
if (repoModule.notesUrl == null) {
|
2022-02-06 01:04:39 +00:00
|
|
|
repoModule.notesUrl = // Fallback url in case the API doesn't have notesUrl
|
2022-07-25 19:55:06 +00:00
|
|
|
"https://" + this.host + "/magisk/readme/" + moduleId;
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
2022-06-07 15:50:37 +00:00
|
|
|
repoModule.zipUrl = this.injectToken(repoModule.zipUrl);
|
|
|
|
repoModule.notesUrl = this.injectToken(repoModule.notesUrl);
|
2022-01-30 21:14:36 +00:00
|
|
|
repoModule.qualityText = R.string.module_downloads;
|
|
|
|
repoModule.qualityValue = jsonObject.optInt("downloads", 0);
|
|
|
|
String checksum = jsonObject.optString("checksum", "");
|
|
|
|
repoModule.checksum = checksum.isEmpty() ? null : checksum;
|
|
|
|
ModuleInfo moduleInfo = repoModule.moduleInfo;
|
|
|
|
moduleInfo.name = jsonObject.getString("name");
|
|
|
|
moduleInfo.versionCode = jsonObject.getLong("versionCode");
|
2022-10-12 01:15:13 +00:00
|
|
|
moduleInfo.version = jsonObject.optString("version", "v" + moduleInfo.versionCode);
|
2022-01-30 21:14:36 +00:00
|
|
|
moduleInfo.author = jsonObject.optString("author", "Unknown");
|
2022-01-30 22:32:10 +00:00
|
|
|
moduleInfo.description = jsonObject.optString("description", "");
|
2022-01-30 21:14:36 +00:00
|
|
|
moduleInfo.minApi = jsonObject.getInt("minApi");
|
|
|
|
moduleInfo.maxApi = jsonObject.getInt("maxApi");
|
|
|
|
String minMagisk = jsonObject.getString("minMagisk");
|
|
|
|
try {
|
|
|
|
int c = minMagisk.indexOf('.');
|
|
|
|
if (c == -1) {
|
|
|
|
moduleInfo.minMagisk = Integer.parseInt(minMagisk);
|
|
|
|
} else {
|
|
|
|
moduleInfo.minMagisk = // Allow 24.1 to mean 24100
|
2022-10-12 01:15:13 +00:00
|
|
|
(Integer.parseInt(minMagisk.substring(0, c)) * 1000) + (Integer.parseInt(minMagisk.substring(c + 1)) * 100);
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
2022-12-24 21:07:14 +00:00
|
|
|
} catch (
|
|
|
|
Exception e) {
|
2022-01-30 21:14:36 +00:00
|
|
|
moduleInfo.minMagisk = 0;
|
|
|
|
}
|
2022-05-10 16:55:26 +00:00
|
|
|
moduleInfo.needRamdisk = jsonObject.optBoolean("needRamdisk", false);
|
2022-06-07 15:50:37 +00:00
|
|
|
moduleInfo.changeBoot = jsonObject.optBoolean("changeBoot", false);
|
2022-09-04 18:46:34 +00:00
|
|
|
moduleInfo.mmtReborn = jsonObject.optBoolean("mmtReborn", false);
|
2022-01-30 21:14:36 +00:00
|
|
|
moduleInfo.support = filterURL(jsonObject.optString("support"));
|
|
|
|
moduleInfo.donate = filterURL(jsonObject.optString("donate"));
|
|
|
|
String config = jsonObject.optString("config", "");
|
|
|
|
moduleInfo.config = config.isEmpty() ? null : config;
|
|
|
|
PropUtils.applyFallbacks(moduleInfo); // Apply fallbacks
|
|
|
|
}
|
|
|
|
Iterator<RepoModule> moduleInfoIterator = this.moduleHashMap.values().iterator();
|
|
|
|
while (moduleInfoIterator.hasNext()) {
|
|
|
|
RepoModule repoModule = moduleInfoIterator.next();
|
|
|
|
if (!repoModule.processed) {
|
|
|
|
moduleInfoIterator.remove();
|
2022-09-04 18:46:34 +00:00
|
|
|
} else {
|
|
|
|
repoModule.moduleInfo.verify();
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
this.lastUpdate = lastLastUpdate;
|
|
|
|
this.name = name;
|
2022-07-16 19:29:25 +00:00
|
|
|
this.website = jsonObject.optString("website");
|
|
|
|
this.support = jsonObject.optString("support");
|
|
|
|
this.donate = jsonObject.optString("donate");
|
|
|
|
this.submitModule = jsonObject.optString("submitModule");
|
2022-01-30 21:14:36 +00:00
|
|
|
return newModules;
|
|
|
|
}
|
|
|
|
|
|
|
|
@Override
|
2022-08-24 15:39:00 +00:00
|
|
|
public void storeMetadata(RepoModule repoModule, byte[] data) {
|
|
|
|
}
|
2022-01-30 21:14:36 +00:00
|
|
|
|
|
|
|
@Override
|
|
|
|
public boolean tryLoadMetadata(RepoModule repoModule) {
|
2022-02-01 17:22:58 +00:00
|
|
|
if (this.moduleHashMap.containsKey(repoModule.id)) {
|
2022-10-12 01:15:13 +00:00
|
|
|
repoModule.moduleInfo.flags &= ~ModuleInfo.FLAG_METADATA_INVALID;
|
2022-02-01 17:22:58 +00:00
|
|
|
return true;
|
|
|
|
}
|
2022-10-12 01:15:13 +00:00
|
|
|
repoModule.moduleInfo.flags |= ModuleInfo.FLAG_METADATA_INVALID;
|
2022-02-01 17:22:58 +00:00
|
|
|
return false;
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|
2022-06-03 19:15:38 +00:00
|
|
|
|
|
|
|
@Override
|
2022-12-06 22:29:28 +00:00
|
|
|
public String getUrl() throws NoSuchAlgorithmException {
|
2022-12-24 21:07:14 +00:00
|
|
|
return this.token == null ? this.url : this.url + "?token=" + this.token + "&v=" + BuildConfig.VERSION_CODE + "&c=" + BuildConfig.VERSION_NAME + "&device_id=" + generateDeviceId();
|
2022-06-03 19:15:38 +00:00
|
|
|
}
|
2022-06-07 15:50:37 +00:00
|
|
|
|
2022-12-06 22:29:28 +00:00
|
|
|
private String injectToken(String url) throws NoSuchAlgorithmException {
|
2022-06-07 15:50:37 +00:00
|
|
|
// Do not inject token for non Androidacy urls
|
2022-12-24 21:07:14 +00:00
|
|
|
if (!AndroidacyUtil.isAndroidacyLink(url))
|
|
|
|
return url;
|
2022-08-06 16:16:14 +00:00
|
|
|
if (this.testMode) {
|
2022-10-12 01:15:13 +00:00
|
|
|
if (url.startsWith("https://production-api.androidacy.com/")) {
|
2022-08-06 16:16:14 +00:00
|
|
|
Log.e(TAG, "Got non test mode url: " + AndroidacyUtil.hideToken(url));
|
2022-10-13 12:16:31 +00:00
|
|
|
url = "https://staging-api.androidacy.com/" + url.substring(38);
|
2022-08-06 16:16:14 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
if (url.startsWith("https://staging-api.androidacy.com/")) {
|
|
|
|
Log.e(TAG, "Got test mode url: " + AndroidacyUtil.hideToken(url));
|
2022-10-12 01:15:13 +00:00
|
|
|
url = "https://production-api.androidacy.com/" + url.substring(35);
|
2022-08-06 16:16:14 +00:00
|
|
|
}
|
2022-07-28 11:31:25 +00:00
|
|
|
}
|
2022-06-07 15:50:37 +00:00
|
|
|
String token = "token=" + this.token;
|
2022-11-29 21:43:33 +00:00
|
|
|
String deviceId = "device_id=" + generateDeviceId();
|
2022-06-07 15:50:37 +00:00
|
|
|
if (!url.contains(token)) {
|
|
|
|
if (url.lastIndexOf('/') < url.lastIndexOf('?')) {
|
|
|
|
return url + '&' + token;
|
|
|
|
} else {
|
|
|
|
return url + '?' + token;
|
|
|
|
}
|
|
|
|
}
|
2022-11-29 21:43:33 +00:00
|
|
|
if (!url.contains(deviceId)) {
|
|
|
|
if (url.lastIndexOf('/') < url.lastIndexOf('?')) {
|
|
|
|
return url + '&' + deviceId;
|
|
|
|
} else {
|
|
|
|
return url + '?' + deviceId;
|
|
|
|
}
|
|
|
|
}
|
2022-06-07 15:50:37 +00:00
|
|
|
return url;
|
|
|
|
}
|
2022-07-25 17:36:45 +00:00
|
|
|
|
|
|
|
@NonNull
|
|
|
|
@Override
|
|
|
|
public String getName() {
|
|
|
|
return this.testMode ? super.getName() + " (Test Mode)" : super.getName();
|
|
|
|
}
|
2022-07-28 11:31:25 +00:00
|
|
|
|
2022-10-19 14:25:56 +00:00
|
|
|
public void setToken(String token) {
|
2022-08-01 11:36:55 +00:00
|
|
|
if (Http.hasWebView()) {
|
|
|
|
this.token = token;
|
|
|
|
}
|
|
|
|
}
|
2022-01-30 21:14:36 +00:00
|
|
|
}
|