Patching Functions
Run code before, after or instead of Discord's functions.
Wrap one of Discord's functions so every call to it also runs your callback. Pass the object, the function's name on it, and your callback.
Quick Start
import { metro, patcher } from '@unbound-app/api';
export function startMessageLog() {
const Messages = metro.findByProps('sendMessage', 'receiveMessage');
if (!Messages) {
logger.warn('Message log skipped: Discord moved sendMessage.');
return;
}
patcher.before(Messages, 'sendMessage', ({ args }) => {
logger.log('Sending to channel', args[0]);
});
}Reference
Every function is on patcher, from @unbound-app/api. Each returns a function that removes the patch.
| Function | Runs | Callback gets | Callback returns |
|---|---|---|---|
patcher.before(parent, key, callback, options?) | Before the original | { args, this } | New args array, or nothing |
patcher.instead(parent, key, callback, options?) | In place of the original | { args, original, this } | The result |
patcher.after(parent, key, callback, options?) | After the original | { args, result, this } | New result, or nothing |
patcher.react.before|instead|after(...) | The same, for a render | See Patching Components | |
patcher.unpatchAll() | Removes every patch your plugin made |
this is the object the function was called on. The only option is once: true, which removes the patch after one run. Full signatures: patcher in the SDK reference.
Patch Kinds
Return a new array to replace the arguments.
import { metro, patcher } from '@unbound-app/api';
export function patchSendMessage() {
const Messages = metro.findByProps('sendMessage', 'receiveMessage');
if (!Messages) {
logger.warn('Trimming skipped: Discord moved sendMessage.');
return;
}
patcher.before(Messages, 'sendMessage', ({ args }) => {
const [channelId, message] = args;
return [channelId, { ...message, content: message.content.trim() }];
});
}Return a value to replace the result. The usual choice for changing a render.
import { flux, patcher } from '@unbound-app/api';
const UserStore = flux.getStore('User');
patcher.after(UserStore, 'getCurrentUser', ({ result }) => {
logger.log('Current user is', result?.username);
});Call original for Discord's behavior, or skip it to replace the function.
import { metro, patcher } from '@unbound-app/api';
const quietChannels = new Set(['1234567890']);
export function patchSendMessage() {
const Messages = metro.findByProps('sendMessage', 'receiveMessage');
if (!Messages) {
logger.warn('Quiet channels skipped: Discord moved sendMessage.');
return;
}
patcher.instead(Messages, 'sendMessage', ({ args, original }) => {
if (quietChannels.has(args[0])) return;
return original(...args);
});
}Undoing a Patch
The Addon scope removes your patches after stop returns. To remove one sooner, call the function the patch returns.
import { flux, patcher } from '@unbound-app/api';
const UserStore = flux.getStore('User');
const unpatch = patcher.after(UserStore, 'getCurrentUser', ({ result }) => {
logger.log('Current user is', result?.username);
});
// later
unpatch();Unloaded Modules
You can patch a lazy lookup before its module loads. The patch applies when Discord loads it, never earlier.
Shared Functions
Other plugins may patch the same function. Every patch runs, chained.
- Change only what you need; return the rest unchanged.
- Expect
resultalready changed by anotherafterpatch. - In
instead, calloriginalunless you mean to replace Discord's behavior.