mirror of
https://github.com/revanced/revanced-integrations.git
synced 2025-04-30 06:34:27 +02:00
feat(Discord): Add Plugin loader
patch
This commit is contained in:
parent
5ffff1bd40
commit
9b083e6d84
@ -0,0 +1,207 @@
|
|||||||
|
|
||||||
|
package app.revanced.integrations.discord.plugin;
|
||||||
|
|
||||||
|
import android.annotation.SuppressLint;
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.res.Resources;
|
||||||
|
import android.graphics.Color;
|
||||||
|
import android.os.Build;
|
||||||
|
import app.revanced.integrations.shared.react.BaseRemoteReactPreloadScriptBootstrapper;
|
||||||
|
import com.facebook.react.bridge.CatalystInstanceImpl;
|
||||||
|
import org.json.JSONArray;
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
|
|
||||||
|
import java.lang.ref.WeakReference;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
|
||||||
|
public class BunnyBootstrapper extends BaseRemoteReactPreloadScriptBootstrapper {
|
||||||
|
private WeakReference<Context> context;
|
||||||
|
|
||||||
|
private JSONObject theme;
|
||||||
|
private final HashMap<String, Integer> RESOURCE_COLORS = new HashMap<>();
|
||||||
|
private final HashMap<String, int[]> COMPONENT_COLORS = new HashMap<>();
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void initialize(Context context) {
|
||||||
|
this.context = new WeakReference<>(context);
|
||||||
|
|
||||||
|
download(
|
||||||
|
"https://raw.githubusercontent.com/pyoncord/detta-builds/main/bunny.js",
|
||||||
|
getWorkingDirectoryFile("bunny.bundle"),
|
||||||
|
1024
|
||||||
|
);
|
||||||
|
|
||||||
|
readThemeFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void loadPreloadScripts(CatalystInstanceImpl instance) {
|
||||||
|
var config = new JSONObject();
|
||||||
|
try {
|
||||||
|
config.put("loaderName", "ReVanced");
|
||||||
|
config.put("loaderVersion", "1.0.0");
|
||||||
|
config.put("hasThemeSupport", true);
|
||||||
|
buildThemeConfig(config);
|
||||||
|
buildSysColorsConfig(config);
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
instance.setGlobalVariable("__PYON_LOADER__", config.toString());
|
||||||
|
super.loadPreloadScripts(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildThemeConfig(JSONObject config) throws JSONException {
|
||||||
|
config.put("storedTheme", theme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void buildSysColorsConfig(JSONObject config) throws JSONException {
|
||||||
|
boolean isSystemColorsSupported = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S;
|
||||||
|
|
||||||
|
config.put("isSysColorsSupported", isSystemColorsSupported);
|
||||||
|
|
||||||
|
if (isSystemColorsSupported) {
|
||||||
|
var context = this.context.get();
|
||||||
|
var resources = context.getResources();
|
||||||
|
var packageName = context.getPackageName();
|
||||||
|
|
||||||
|
String[] accents = {"accent1", "accent2", "accent3", "neutral1", "neutral2"};
|
||||||
|
int[] shades = {0, 10, 50, 100, 200, 300, 400, 500, 600, 700, 800, 900, 1000};
|
||||||
|
|
||||||
|
var colors = new JSONObject() {{
|
||||||
|
for (String accent : accents) {
|
||||||
|
var accentColors = new JSONArray() {{
|
||||||
|
for (int shade : shades) {
|
||||||
|
@SuppressLint("DiscouragedApi")
|
||||||
|
var colorResourceId = resources.getIdentifier(
|
||||||
|
"system_" + accent + "_" + shade,
|
||||||
|
"color",
|
||||||
|
packageName
|
||||||
|
);
|
||||||
|
var color = colorResourceId == 0 ? 0 : context.getColor(colorResourceId);
|
||||||
|
var hexColor = String.format("#%06X", (0xFFFFFF & color));
|
||||||
|
|
||||||
|
put(hexColor);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
put(accent, accentColors);
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
|
||||||
|
config.put("sysColors", colors);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hookColorDark(String themeKey, int originalColor) {
|
||||||
|
return getColor(themeKey, originalColor, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hookColorLight(String themeKey, int originalColor) {
|
||||||
|
return getColor(themeKey, originalColor, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int hookRawColor(Object contextOrResource, int id, int originalColor) {
|
||||||
|
return readRawColor(contextOrResource, id, originalColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getColor(String colorName, int originalColor, boolean isDark) {
|
||||||
|
waitUntilInitialized();
|
||||||
|
|
||||||
|
var colors = COMPONENT_COLORS.get(colorName);
|
||||||
|
if (colors == null) {
|
||||||
|
return originalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isDark) {
|
||||||
|
return colors[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only if there are two colors in the array we return the light color
|
||||||
|
if (colors.length == 2) {
|
||||||
|
return colors[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
return originalColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readThemeFile() {
|
||||||
|
var themeFile = getWorkingDirectoryFile("pyoncord/theme.json");
|
||||||
|
var legacyThemeFile = getWorkingDirectoryFile("vendetta_theme.json");
|
||||||
|
|
||||||
|
if (legacyThemeFile.exists() && !legacyThemeFile.renameTo(themeFile)) {
|
||||||
|
throw new RuntimeException("Failed to rename theme file");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!themeFile.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
theme = new JSONObject(read(themeFile, 256));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
readThemeColors();
|
||||||
|
}
|
||||||
|
|
||||||
|
private int hexStringToColorInt(String hexString) {
|
||||||
|
var parsed = Color.parseColor(hexString);
|
||||||
|
return (hexString.length() == 7) ? parsed : parsed & 0xFFFFFF | (parsed >>> 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void readThemeColors() {
|
||||||
|
try {
|
||||||
|
var data = theme.getJSONObject("data");
|
||||||
|
|
||||||
|
var jsonRawColors = data.getJSONObject("rawColors");
|
||||||
|
var jsonSemanticColors = data.getJSONObject("semanticColors");
|
||||||
|
|
||||||
|
for (var colors = jsonRawColors.keys(); colors.hasNext(); ) {
|
||||||
|
var colorKey = colors.next();
|
||||||
|
int color = hexStringToColorInt(jsonRawColors.getString(colorKey));
|
||||||
|
RESOURCE_COLORS.put(colorKey.toLowerCase(), color);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var colors = jsonSemanticColors.keys(); colors.hasNext(); ) {
|
||||||
|
var componentName = colors.next();
|
||||||
|
var componentColors = jsonSemanticColors.getJSONArray(componentName);
|
||||||
|
|
||||||
|
int[] value;
|
||||||
|
if (componentColors.length() == 1) {
|
||||||
|
value = new int[]{hexStringToColorInt(componentColors.getString(0))};
|
||||||
|
} else {
|
||||||
|
value = new int[]{
|
||||||
|
hexStringToColorInt(componentColors.getString(0)),
|
||||||
|
hexStringToColorInt(componentColors.getString(1))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
COMPONENT_COLORS.put(componentName, value);
|
||||||
|
}
|
||||||
|
} catch (JSONException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public int readRawColor(Object contextOrResource, int id, int originalColor) {
|
||||||
|
waitUntilInitialized();
|
||||||
|
|
||||||
|
Resources resources;
|
||||||
|
if (contextOrResource instanceof Context) {
|
||||||
|
resources = ((Context) contextOrResource).getResources();
|
||||||
|
} else {
|
||||||
|
resources = (Resources) contextOrResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
var name = resources.getResourceEntryName(id);
|
||||||
|
var color = RESOURCE_COLORS.get(name);
|
||||||
|
|
||||||
|
return Objects.requireNonNullElse(color, originalColor);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
package app.revanced.integrations.discord.plugin;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import com.facebook.react.bridge.CatalystInstanceImpl;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public final class BunnyBootstrapperPatch {
|
||||||
|
private final static BunnyBootstrapper INSTANCE = new BunnyBootstrapper();
|
||||||
|
|
||||||
|
public static void hookOnCreate(Activity mainActivity) {
|
||||||
|
INSTANCE.hookOnCreate(mainActivity);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void hookLoadScriptFromFile(CatalystInstanceImpl instance) {
|
||||||
|
INSTANCE.hookLoadScriptFromFile(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int hookColorDark(String themeKey, int originalColor) {
|
||||||
|
return INSTANCE.hookColorDark(themeKey, originalColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int hookColorLight(String themeKey, int originalColor) {
|
||||||
|
return INSTANCE.hookColorLight(themeKey, originalColor);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static int hookRawColor(Object contextOrResource, int id, int originalColor) {
|
||||||
|
return INSTANCE.hookRawColor(contextOrResource, id, originalColor);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,81 @@
|
|||||||
|
package app.revanced.integrations.shared.react;
|
||||||
|
|
||||||
|
import android.app.Activity;
|
||||||
|
import android.content.Context;
|
||||||
|
import com.facebook.react.bridge.CatalystInstanceImpl;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
public abstract class BaseReactPreloadScriptBootstrapper {
|
||||||
|
private Thread initializeThread;
|
||||||
|
private File workingDirectory;
|
||||||
|
|
||||||
|
protected abstract void initialize(Context context);
|
||||||
|
|
||||||
|
public final void hookOnCreate(Activity mainActivity) {
|
||||||
|
workingDirectory = mainActivity.getFilesDir();
|
||||||
|
if (!workingDirectory.exists() && !workingDirectory.mkdirs()) {
|
||||||
|
throw new RuntimeException("Failed to create working directory");
|
||||||
|
}
|
||||||
|
|
||||||
|
initializeThread = new Thread(() -> initialize(mainActivity));
|
||||||
|
initializeThread.start();
|
||||||
|
}
|
||||||
|
|
||||||
|
public final void hookLoadScriptFromFile(CatalystInstanceImpl instance) {
|
||||||
|
waitUntilInitialized();
|
||||||
|
loadPreloadScripts(instance);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void loadPreloadScripts(CatalystInstanceImpl instance) {
|
||||||
|
final var preloadScripts = workingDirectory.listFiles(pathname ->
|
||||||
|
pathname.isFile() && pathname.getName().endsWith(".bundle"));
|
||||||
|
assert preloadScripts != null;
|
||||||
|
|
||||||
|
for (final var preloadScript : preloadScripts) {
|
||||||
|
final var path = preloadScript.getAbsolutePath();
|
||||||
|
instance.loadPreloadScriptFromFile(path, path, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected void waitUntilInitialized() {
|
||||||
|
try {
|
||||||
|
initializeThread.join();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final File getWorkingDirectoryFile(String name) {
|
||||||
|
return new File(workingDirectory, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final void write(InputStream inputStream, File file, int bufferSize) {
|
||||||
|
try (final var fileOutputStream = new FileOutputStream(file)) {
|
||||||
|
final var buffer = new byte[bufferSize];
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = inputStream.read(buffer)) != -1) {
|
||||||
|
fileOutputStream.write(buffer, 0, bytesRead);
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected final String read(File file, int bufferSize) {
|
||||||
|
try (final var fileInputStream = new FileInputStream(file)) {
|
||||||
|
final var buffer = new byte[bufferSize];
|
||||||
|
final var stringBuilder = new StringBuilder();
|
||||||
|
|
||||||
|
int bytesRead;
|
||||||
|
while ((bytesRead = fileInputStream.read(buffer)) != -1) {
|
||||||
|
stringBuilder.append(new String(buffer, 0, bytesRead));
|
||||||
|
}
|
||||||
|
|
||||||
|
return stringBuilder.toString();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,38 @@
|
|||||||
|
package app.revanced.integrations.shared.react;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public abstract class BaseRemoteReactPreloadScriptBootstrapper extends BaseReactPreloadScriptBootstrapper {
|
||||||
|
protected final void download(String url, File preloadScriptFile, int bufferSize) {
|
||||||
|
final var eTagFile = getWorkingDirectoryFile(preloadScriptFile.getName() + ".etag");
|
||||||
|
|
||||||
|
try {
|
||||||
|
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
if (eTagFile.exists() && preloadScriptFile.exists()) {
|
||||||
|
connection.setRequestProperty("If-None-Match", read(eTagFile, 256));
|
||||||
|
}
|
||||||
|
connection.connect();
|
||||||
|
|
||||||
|
if (connection.getResponseCode() == 304) {
|
||||||
|
connection.disconnect();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connection.getResponseCode() != 200) {
|
||||||
|
throw new RuntimeException("Failed to download the preload script: " + connection.getResponseCode());
|
||||||
|
}
|
||||||
|
|
||||||
|
final var eTagHeader = connection.getHeaderField("ETag");
|
||||||
|
if (eTagHeader != null) {
|
||||||
|
write(new ByteArrayInputStream(eTagHeader.getBytes()), eTagFile, 256);
|
||||||
|
}
|
||||||
|
|
||||||
|
write(connection.getInputStream(), preloadScriptFile, bufferSize);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.facebook.react.bridge;
|
||||||
|
|
||||||
|
import android.content.res.AssetManager;
|
||||||
|
|
||||||
|
public class CatalystInstanceImpl {
|
||||||
|
public native void setGlobalVariable(String propName, String jsonValue);
|
||||||
|
|
||||||
|
public void loadScriptFromAssets(AssetManager assetManager, String assetURL, boolean loadSynchronously) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public void loadPreloadScriptFromFile(String fileName, String sourceURL, boolean loadSynchronously) {
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user