aliucord-plugins/guide/04-patching.md
2026-09-27 14:34:59 +02:00

3.1 KiB

Patching

Patches let you run code before, after, or instead of Discord methods. Under the hood Aliucord uses Aliuhook / LSPlant.

Upstream: docs/documentation/plugin-dev/3_patching.md
Template patches: docs/plugins-template/plugins/MyFirstKotlinPlugin/.../MyFirstKotlinPlugin.kt
Tiny real patch: examples/Juby210-Aliucord-plugins/NoAutoReplyMention/

Basics

Every plugin has patcher. Prefer the Kotlin helpers when you can:

patcher.before<SomeClass>("methodName", ArgType::class.java) { param ->
    // runs before the original; set param.result to skip it
}

patcher.after<SomeClass>("methodName", ArgType::class.java) { param ->
    // runs after; param.result is the return value
}

patcher.instead<SomeClass>("methodName", ArgType::class.java) {
    // replaces the method; return the substitute result
    null
}

Java equivalents use PreHook, Hook, and InsteadHook. Prefer those wrappers over raw XC_MethodHook so Aliucord can catch and log failures.

You must pass parameter types so overloads resolve. Primitives use Int::class.javaPrimitiveType / int.class, not the boxed type, when the method takes a primitive.

What param gives you

Field / method Before After
thisObject / typed this in Kotlin helpers Instance (or null if static) same
args Mutable. Changes feed the original call Original args
result / setResult Setting it skips the original Changes what callers see
throwable — Exception from the original, if any

Patterns that show up everywhere

Replace a return value

patcher.instead<CoreUser>("getUsername") { "Clyde" }

Tweak after the fact

patcher.after<CoreUser>("getUsername") {
    val name = it.result as String?
    if (name.equals("Clyde", ignoreCase = true)) it.result = "Evil Clyde"
}

Mutate arguments before the call

NoAutoReplyMention forces Discord not to auto-mention on reply:

patcher.patch(
    "com.discord.stores.StorePendingReplies", "onCreatePendingReply",
    new Class<?>[]{ Channel.class, Message.class, boolean.class, boolean.class },
    new PreHook(param -> {
        param.args[2] = false; // mention
        param.args[3] = true;  // showMentionToggle
    })
);

No-op a method

patcher.instead<StoreUserTyping>("setUserTyping", Long::class.java) { null }

The template does this to hide typing. Same idea as Vendicated's quieter privacy-style hooks.

Patch UI configure methods

The template's message stats embed patches WidgetChatListAdapterItemMessage.onConfigure. Guard against loading messages and against running twice on edits. That class of bug is easy to miss.

Cleanup

override fun stop(context: Context) {
    patcher.unpatchAll()
}

Individual patches also return a Runnable you can call to remove just that hook.

Finding what to patch

You cannot invent class names. Decompile Discord and search. See Discord code and reflection.

Next: Settings