mirror of
https://github.com/fromchat-messenger/app.git
synced 2026-09-22 19:15:05 +03:00
Fix raw hooks: plugin-side hookRawMethod with Advice instrumentation
Remove the planted isContactsTabVisible() hook target and app-side visibility conditionals (that was cheating). Plugins now declare raw JVM targets directly via hookRawMethod on existing methods like MainScreenKt.selectMainPage, ContactsTabKt.ContactsTab, NavigationBarKt.NavigationBarItem, and Logger.d. Replace ByteBuddy MethodDelegation (schema-changing) with Advice-based ClassReloadingStrategy so hooks actually install at runtime. Fix Logger hook signature for Kotlin object instance methods. Add chat banner z-index fix and PluginEngine.hostRevision to recompose MainScreen after plugin load. Co-authored-by: denis0001-dev <denis0001.dev@ya.ru>
This commit is contained in:
@@ -35,10 +35,10 @@ tasks.register("packageFcPlugin") {
|
||||
{
|
||||
"id": "hello_world",
|
||||
"name": "Hello World",
|
||||
"description": "Demonstrates high-level send hooks and raw Logger.d hooks.",
|
||||
"description": "Demonstrates high-level send hooks, raw Logger.d hooks, and raw main-nav hooks.",
|
||||
"author": "FromChat",
|
||||
"version": "1.0.1",
|
||||
"usage": "Send .hello Name in any chat to rewrite the message. Raw hook shows a bulletin when a message is queued for sending.",
|
||||
"version": "1.0.3",
|
||||
"usage": "Raw-hooks existing MainScreen/ContactsTab JVM methods (no app hook IDs). Also hides Devices/Logs settings and emoji; injects settings row, message menu item, and chat banner. Send .hello Name to test send hooks.",
|
||||
"app_version": ">=1.0.0",
|
||||
"sdk_version": ">=1.0.0",
|
||||
"entry_class": "ru.fromchat.plugins.sample.hello.HelloWorldPlugin",
|
||||
|
||||
+146
-1
@@ -3,6 +3,8 @@ package ru.fromchat.plugins.sample.hello
|
||||
import ru.fromchat.plugins.BasePlugin
|
||||
import ru.fromchat.plugins.HookResult
|
||||
import ru.fromchat.plugins.HookStrategy
|
||||
import ru.fromchat.plugins.MenuItemData
|
||||
import ru.fromchat.plugins.MenuItemType
|
||||
import ru.fromchat.plugins.PluginSetting
|
||||
import ru.fromchat.plugins.SendMessageHookContext
|
||||
|
||||
@@ -10,12 +12,15 @@ class HelloWorldPlugin : BasePlugin() {
|
||||
override fun onPluginLoad() {
|
||||
log("Hello World plugin loaded")
|
||||
showBulletin("Hello World plugin is active")
|
||||
registerUiOverrides()
|
||||
registerInjectedUi()
|
||||
registerRawHooks()
|
||||
addOnSendMessageHook { context ->
|
||||
onSendMessage(context)
|
||||
}
|
||||
hookShared(
|
||||
hookId = "logger.debug",
|
||||
after = { args, _ ->
|
||||
before = { args ->
|
||||
val tag = args.getOrNull(0) as? String ?: return@hookShared HookResult()
|
||||
val message = args.getOrNull(1) as? String ?: return@hookShared HookResult()
|
||||
if (tag == "OutgoingMessageCoordinator" && message.contains("enqueue")) {
|
||||
@@ -26,6 +31,13 @@ class HelloWorldPlugin : BasePlugin() {
|
||||
)
|
||||
}
|
||||
|
||||
override fun onMenuItemClick(key: String) {
|
||||
when (key) {
|
||||
MENU_SETTINGS_DEMO -> showBulletin("Injected settings row clicked")
|
||||
MENU_MESSAGE_DEMO -> showBulletin("Injected message menu clicked")
|
||||
}
|
||||
}
|
||||
|
||||
override fun createSettings(): List<PluginSetting> = listOf(
|
||||
PluginSetting.Header("Hello World"),
|
||||
PluginSetting.Input(
|
||||
@@ -40,6 +52,122 @@ class HelloWorldPlugin : BasePlugin() {
|
||||
),
|
||||
)
|
||||
|
||||
private fun registerRawHooks() {
|
||||
hookRawMethod(
|
||||
className = "ru.fromchat.ui.main.MainScreenKt",
|
||||
methodName = "selectMainPage",
|
||||
paramTypeNames = arrayOf(
|
||||
"int",
|
||||
"com.pr0gramm3r101.utils.WindowWidthSizeClass",
|
||||
"kotlinx.coroutines.CoroutineScope",
|
||||
"androidx.compose.foundation.pager.PagerState",
|
||||
"androidx.navigation.NavController",
|
||||
),
|
||||
before = { args ->
|
||||
val page = (args.getOrNull(0) as? Number)?.toInt() ?: return@hookRawMethod HookResult()
|
||||
if (page == MAIN_PAGE_CONTACTS) {
|
||||
if (!contactsNavBlockedNotified) {
|
||||
contactsNavBlockedNotified = true
|
||||
showBulletin("Raw hook: blocked Contacts tab navigation")
|
||||
}
|
||||
HookResult(strategy = HookStrategy.CANCEL)
|
||||
} else {
|
||||
HookResult()
|
||||
}
|
||||
},
|
||||
)
|
||||
hookRawMethod(
|
||||
className = "ru.fromchat.ui.main.ContactsTabKt",
|
||||
methodName = "ContactsTab",
|
||||
paramTypeNames = arrayOf(
|
||||
"androidx.compose.runtime.Composer",
|
||||
"int",
|
||||
),
|
||||
before = { _ ->
|
||||
HookResult(strategy = HookStrategy.CANCEL)
|
||||
},
|
||||
)
|
||||
hookRawMethod(
|
||||
className = "ru.fromchat.ui.main.MainScreenKt",
|
||||
methodName = "MainScreen",
|
||||
paramTypeNames = arrayOf(
|
||||
"androidx.compose.animation.SharedTransitionScope",
|
||||
"androidx.compose.animation.AnimatedVisibilityScope",
|
||||
"androidx.compose.material3.SnackbarHostState",
|
||||
"boolean",
|
||||
"int",
|
||||
"boolean",
|
||||
"kotlin.jvm.functions.Function1",
|
||||
"androidx.compose.runtime.Composer",
|
||||
"int",
|
||||
"int",
|
||||
),
|
||||
before = { _ ->
|
||||
mainScreenNavItemOrdinal = 0
|
||||
HookResult()
|
||||
},
|
||||
)
|
||||
hookRawMethod(
|
||||
className = "androidx.compose.material3.NavigationBarKt",
|
||||
methodName = "NavigationBarItem",
|
||||
paramTypeNames = arrayOf(
|
||||
"androidx.compose.foundation.layout.RowScope",
|
||||
"boolean",
|
||||
"kotlin.jvm.functions.Function0",
|
||||
"kotlin.jvm.functions.Function2",
|
||||
"androidx.compose.ui.Modifier",
|
||||
"boolean",
|
||||
"kotlin.jvm.functions.Function2",
|
||||
"boolean",
|
||||
"androidx.compose.material3.NavigationBarItemColors",
|
||||
"androidx.compose.foundation.interaction.MutableInteractionSource",
|
||||
"androidx.compose.runtime.Composer",
|
||||
"int",
|
||||
"int",
|
||||
),
|
||||
before = { _ ->
|
||||
mainScreenNavItemOrdinal += 1
|
||||
if (mainScreenNavItemOrdinal == CONTACTS_NAV_ITEM_ORDINAL) {
|
||||
HookResult(strategy = HookStrategy.CANCEL)
|
||||
} else {
|
||||
HookResult()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
private fun registerUiOverrides() {
|
||||
registerFeatureOverride(FEATURE_SETTINGS_DEVICES) { false }
|
||||
registerFeatureOverride(FEATURE_SETTINGS_LOGS) { false }
|
||||
registerFeatureOverride(FEATURE_CHAT_EMOJI) { false }
|
||||
}
|
||||
|
||||
private fun registerInjectedUi() {
|
||||
registerUiOverlay(
|
||||
slot = OVERLAY_CHAT_BANNER,
|
||||
title = "Hello World plugin UI",
|
||||
message = "This banner was injected by the plugin engine.",
|
||||
)
|
||||
addMenuItem(
|
||||
MenuItemData(
|
||||
menuType = MenuItemType.SETTINGS_ROW,
|
||||
text = "Hello World demo action",
|
||||
subtext = "Injected settings row from plugin",
|
||||
priority = 10,
|
||||
onClickKey = MENU_SETTINGS_DEMO,
|
||||
),
|
||||
)
|
||||
addMenuItem(
|
||||
MenuItemData(
|
||||
menuType = MenuItemType.MESSAGE_CONTEXT,
|
||||
text = "Hello World message action",
|
||||
subtext = "Injected message menu item",
|
||||
priority = 10,
|
||||
onClickKey = MENU_MESSAGE_DEMO,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
private fun onSendMessage(context: SendMessageHookContext): HookResult<SendMessageHookContext> {
|
||||
val raw = context.text.trim()
|
||||
if (!raw.startsWith(".hello")) return HookResult()
|
||||
@@ -50,4 +178,21 @@ class HelloWorldPlugin : BasePlugin() {
|
||||
showBulletin("High-level hook: rewrote .hello command")
|
||||
return HookResult(strategy = HookStrategy.MODIFY, value = context)
|
||||
}
|
||||
|
||||
private companion object {
|
||||
const val MAIN_PAGE_CONTACTS = 1
|
||||
const val CONTACTS_NAV_ITEM_ORDINAL = 2
|
||||
const val FEATURE_SETTINGS_DEVICES = "settings.devices"
|
||||
const val FEATURE_SETTINGS_LOGS = "settings.logs"
|
||||
const val FEATURE_CHAT_EMOJI = "chat.emoji_picker"
|
||||
const val OVERLAY_CHAT_BANNER = "chat.banner"
|
||||
const val MENU_SETTINGS_DEMO = "hello_world.settings_demo"
|
||||
const val MENU_MESSAGE_DEMO = "hello_world.message_demo"
|
||||
|
||||
@Volatile
|
||||
var mainScreenNavItemOrdinal: Int = 0
|
||||
|
||||
@Volatile
|
||||
var contactsNavBlockedNotified: Boolean = false
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user