feat(composer_hooks): opera download button

This commit is contained in:
rhunk 2024-04-25 15:21:42 +02:00
parent 69e5ca74ee
commit f830ce13c6
2 changed files with 126 additions and 54 deletions

View File

@ -9,51 +9,130 @@ if (config.composerLogs) {
console.info("loader.js loaded");
}
if (config.bypassCameraRollLimit) {
(module => {
module.MultiSelectClickHandler = new Proxy(module.MultiSelectClickHandler, {
construct: function(target, args, newTarget) {
args[1].selectionLimit = 9999999;
return Reflect.construct(target, args, newTarget);
},
});
})(require('memories_ui/src/clickhandlers/MultiSelectClickHandler'))
// Composer imports
const jsx = require('composer_core/src/JSX').jsx;
const assetCatalog = require("composer_core/src/AssetCatalog")
const style = require("composer_core/src/Style");
const colors = require("coreui/src/styles/semanticColors");
function dumpObject(obj, indent = 0) {
if (typeof obj !== "object") return console.log(obj);
let prefix = ""
for (let i = 0; i < indent; i++) {
prefix += " ";
}
for (let key of Object.keys(obj)) {
try {
console.log(prefix, key, typeof obj[key], obj[key]);
if (key == "renderer") continue
if (typeof obj[key] === "object" && indent < 10) dumpObject(obj[key], indent + 1);
} catch (e) {
}
}
}
(module => {
function onComponentPreRender(component, viewModel) {
const componentName = component.constructor.name;
function proxyProperty(module, functionName, handler) {
if (!module || !module[functionName]) {
console.warn("Function not found", functionName);
return;
}
module[functionName] = new Proxy(module[functionName], {
apply: (a, b, c) => handler(a, b, c),
construct: (a, b, c) => handler(a, b, c)
});
}
if (componentName == "ProfileIdentityView" && config.showFirstCreatedUsername) {
let userInfo = callExport("getFriendInfoByUsername", viewModel.username);
function interceptComponent(moduleName, className, functions) {
proxyProperty(require(moduleName), className, (target, args, newTarget) => {
let initProxy = functions["<init>"]
let component;
if (userInfo) {
let userInfoJson = JSON.parse(userInfo);
let firstCreatedUsername = userInfoJson.username.split("|")[0];
if (firstCreatedUsername != viewModel.username) {
viewModel.username += " (" + firstCreatedUsername + ")";
if (initProxy) {
initProxy(args, (newArgs) => {
component = Reflect.construct(target, newArgs || args, newTarget);
});
} else {
component = Reflect.construct(target, args, newTarget);
}
for (let funcName of Object.keys(functions)) {
if (funcName == "<init>" || !component[funcName]) continue
proxyProperty(component, funcName, (target, thisArg, argumentsList) => {
let result;
try {
functions[funcName](component, argumentsList, (newArgs) => {
result = Reflect.apply(target, thisArg, newArgs || argumentsList);
});
} catch (e) {
console.error("Error in", funcName, e);
}
return result;
});
}
return component;
})
}
if (config.bypassCameraRollLimit) {
interceptComponent(
'memories_ui/src/clickhandlers/MultiSelectClickHandler',
'MultiSelectClickHandler',
{
"<init>": (args, superCall) => {
args[1].selectionLimit = 9999999;
superCall();
}
}
)
}
return false
}
function onComponentPostRender(component, viewModel) {
}
if (config.operaDownloadButton) {
const downloadIcon = assetCatalog.loadCatalog("share_sheet/res").downloadIcon
module.Component = new Proxy(module.Component, {
construct: function(target, args, newTarget) {
let component = Reflect.construct(target, args, newTarget);
component.onRender = new Proxy(component.onRender, {
apply: function(target, thisArg, argumentsList) {
if (onComponentPreRender(component, thisArg.viewModel || {})) return;
let result = Reflect.apply(target, thisArg, argumentsList);
onComponentPostRender(component, thisArg.viewModel || {});
return result;
}
});
return component;
interceptComponent(
'context_chrome_header/src/ChromeHeaderRenderer',
'ChromeHeaderRenderer',
{
onRenderBaseHeader: (component, args, render) => {
render()
jsx.beginRender(jsx.makeNodePrototype("image"))
jsx.setAttributeStyle("style", new style.Style({
height: 32,
marginTop: 4,
marginLeft: 8,
marginRight: 12,
objectFit: "contain",
tint: colors.SemanticColor.Icon.PRIMARY
}))
jsx.setAttribute("src", downloadIcon)
jsx.setAttributeFunction("onTap", () => callExport("downloadLastOperaMedia", false))
jsx.setAttributeFunction("onLongPress", () => callExport("downloadLastOperaMedia", true))
jsx.endRender()
}
}
})
})(require('composer_core/src/Component'))
)
}
if (config.showFirstCreatedUsername) {
interceptComponent(
'common_profile/src/identity/ProfileIdentityView',
'ProfileIdentityView',
{
onRender: (component, _, render) => {
if (component.viewModel) {
let userInfo = callExport("getFriendInfoByUsername", component.viewModel.username);
if (userInfo) {
let userInfoJson = JSON.parse(userInfo);
let firstCreatedUsername = userInfoJson.username.split("|")[0];
if (firstCreatedUsername != component.viewModel.username) {
component.viewModel.username += " (" + firstCreatedUsername + ")";
}
}
}
render();
}
}
)
}

View File

@ -30,6 +30,7 @@ import me.rhunk.snapenhance.common.ui.createComposeAlertDialog
import me.rhunk.snapenhance.common.ui.createComposeView
import me.rhunk.snapenhance.core.features.Feature
import me.rhunk.snapenhance.core.features.FeatureLoadParams
import me.rhunk.snapenhance.core.features.impl.downloader.MediaDownloader
import me.rhunk.snapenhance.core.util.hook.HookStage
import me.rhunk.snapenhance.core.util.hook.Hooker
import me.rhunk.snapenhance.core.util.hook.hook
@ -131,12 +132,13 @@ class ComposerHooks: Feature("ComposerHooks", loadParams = FeatureLoadParams.ACT
}
private fun getConfig(): Map<String, Any> {
return HashMap<String, Any>().apply {
put("bypassCameraRollLimit", config.bypassCameraRollLimit.get())
put("showFirstCreatedUsername", config.showFirstCreatedUsername.get())
put("composerConsole", config.composerConsole.get())
put("composerLogs", config.composerLogs.get())
}
return mapOf<String, Any>(
"operaDownloadButton" to context.config.downloader.operaDownloadButton.get(),
"bypassCameraRollLimit" to config.bypassCameraRollLimit.get(),
"showFirstCreatedUsername" to config.showFirstCreatedUsername.get(),
"composerConsole" to config.composerConsole.get(),
"composerLogs" to config.composerLogs.get()
)
}
private fun handleExportCall(composerMarshaller: ComposerMarshaller): Boolean {
@ -150,6 +152,7 @@ class ComposerHooks: Feature("ComposerHooks", loadParams = FeatureLoadParams.ACT
if (argc < 2) return false
context.shortToast(composerMarshaller.getUntyped(1) as? String ?: return false)
}
"downloadLastOperaMedia" -> context.feature(MediaDownloader::class).downloadLastOperaMediaAsync(composerMarshaller.getUntyped(1) == true)
"getFriendInfoByUsername" -> {
if (argc < 2) return false
val username = composerMarshaller.getUntyped(1) as? String ?: return false
@ -176,16 +179,6 @@ class ComposerHooks: Feature("ComposerHooks", loadParams = FeatureLoadParams.ACT
"error" -> context.log.error(message, tag)
}
}
"eval" -> {
if (argc < 2) return false
runCatching {
composerMarshaller.pushUntyped(context.native.composerEval(
composerMarshaller.getUntyped(1) as? String ?: return false
))
}.onFailure {
composerMarshaller.pushUntyped(it.toString())
}
}
else -> context.log.warn("Unknown action: $action", "Composer")
}