commit f830ce13c697b3f377afb64ba81436fbd454e0d6
parent 69e5ca74eead5e531cccbd6917f0387358fd6d30
Author: rhunk <101876869+rhunk@users.noreply.github.com>
Date:   Thu, 25 Apr 2024 15:21:42 +0200

feat(composer_hooks): opera download button

Diffstat:
Mcore/src/main/assets/composer/loader.js | 153++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-------------------
Mcore/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/ComposerHooks.kt | 25+++++++++----------------
2 files changed, 125 insertions(+), 53 deletions(-)

diff --git a/core/src/main/assets/composer/loader.js b/core/src/main/assets/composer/loader.js @@ -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) { + } + } +} + +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) + }); } -(module => { - function onComponentPreRender(component, viewModel) { - const componentName = component.constructor.name; +function interceptComponent(moduleName, className, functions) { + proxyProperty(require(moduleName), className, (target, args, newTarget) => { + let initProxy = functions["<init>"] + let component; - if (componentName == "ProfileIdentityView" && config.showFirstCreatedUsername) { - let userInfo = callExport("getFriendInfoByUsername", viewModel.username); + if (initProxy) { + initProxy(args, (newArgs) => { + component = Reflect.construct(target, newArgs || args, newTarget); + }); + } else { + component = Reflect.construct(target, args, newTarget); + } - if (userInfo) { - let userInfoJson = JSON.parse(userInfo); - let firstCreatedUsername = userInfoJson.username.split("|")[0]; - if (firstCreatedUsername != viewModel.username) { - viewModel.username += " (" + firstCreatedUsername + ")"; + 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; + 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() + } + } + ) +} + +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 + ")"; + } + } } - }); - return component; + render(); + } } - }) -})(require('composer_core/src/Component')) + ) +} diff --git a/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/ComposerHooks.kt b/core/src/main/kotlin/me/rhunk/snapenhance/core/features/impl/experiments/ComposerHooks.kt @@ -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") }