Injecting into Modules
Injection is one of the most important parts of a plugin. It allows you to change the behavior of a module.
Plugin Lifecycle
The index file of the plugin (usually src/index.ts
, can be configured in your manifest) should
export 2 functions: start
and stop
. Your plugin should run injection code in start
and remove
all injections in stop
.
Creating an Injector
import { Injector } from "replugged";
const injector = new Injector();
Injecting into a Module
First, you will need to find the module you want to inject into and the property name on that object. See the modules page for more information on how to find modules.
There are 3 types of injections which determine how it behaves:
before
This injection will run on the module before the original method is called and will receive the props of the original method. This is useful for modifying the arguments of the original method or running other code when the original method is called.
injector.before(mod, "method", (props) => {
// props is an array of the arguments passed to the original method
// the props returned will be passed to the original method
// you can return undefined if you want to use the original props
return props;
});
instead
This injection will completely replace the original method with your own. This is useful for completely changing the behavior of a method, or if you want to prevent it from running at all.
injector.instead(mod, "method", (props, fn) => {
// props is an array of the arguments passed to the original method
// any value returned from this function will be returned to the original caller
// if you want to run the original method, you can call fn with the original props (or modified props)
return fn(...props);
});
after
This injection will run on the module after the original method is called and will receive the props of the original method and the return value of the original method. This is useful for modifying the return value of the original method or running other code based on the return value of the original method.
injector.after(mod, "method", (props, res) => {
// props is an array of the arguments passed to the original method (similar to `before`)
// res is the return value of the original method
// you can return a modified version of the return value if you want to modify it
// or you can return undefined if you want to use the original return value
return res;
});
Comparison
Injection | Starts at | Ends at | Input | Output | Can modify props | Can modify return value | Calls original method |
---|---|---|---|---|---|---|---|
before | Start of function call | Start of function call | Function props | Function props | Yes | No | Yes |
instead | Start of function call | End of function call | Function props | Function return value | Yes | Yes | No (unless you call it yourself) |
after | End of function call | End of function call | Function return value (includes props too) | Function return value | No | Yes | Yes |
Removing an Injection
Each injection returns a function which can be called to remove the injection.
const removeTheInjection = injector.before(mod, "method", (props) => {
// ...
});
// later on, you can call remove to remove the injection
removeTheInjection();
The injector class has an uninjectAll
method which will remove all injections for the plugin.
import { Injector } from "replugged";
const injector = new Injector();
export function start() {
// do your injections here
}
export function stop() {
injector.uninjectAll();
}
Utilities
Replugged includes some utilities to inject into some common things. These can be uninjected the
same way as regular injections and will also be removed when uninjectAll
is called. These
utilities are available on injector.utils
.
Message Popover
You can use the addPopoverButton
util to add a button to the message popover. These are the
buttons that are shown in the top right corner when you hover over a message, like react, edit,
reply, etc. The button order cannot be controlled.
import { Injector } from "replugged";
const injector = new Injector();
export function start() {
injector.utils.addPopoverButton((msg: Message, channel: Channel) => {
return {
label: "Click the button!",
icon: myVeryCoolIcon(), // Cool icon
onClick: () => {
// do stuff here when someone left clicks the button
},
onContextMenu: () => {
// do other stuff here when someone right clicks the button
},
};
});
}
export function stop() {
injector.uninjectAll();
}
Props:
The props of this component extend the attributes of the div
element.
Name | Type | Default | Description |
---|---|---|---|
ariaLabel | string | Button aria-label | |
channel | Channel | Channel of the message with the button popover; automatically added with the injection | |
dangerous | boolean | false | Whether the icon color is red |
disabled | boolean | false | Whether the button is disabled |
icon | * ComponentType<unknown> | Icon component | |
iconProps | Record<string, unknown> | Props for the icon component | |
key | string | Button popover key; automatically added with the injection | |
label | * string | Tooltip text | |
message | Message | Message with the button popover; automatically added with the injection | |
onClick | * (channel: Channel, message: Message, event: MouseEvent<HTMLDivElement>) => void | Function ran on button click | |
selected | boolean | false | Whether the button is selected |
separator | boolean | false | Whether the button shows a divider among others |
sparkle | boolean | false | Whether the button shows a sparkle effect |
tooltipColor | Tooltip.Colors | Tooltip.Colors.PRIMARY | Tooltip color |
tooltipText | string | Tooltip text; alternative of label |
Context Menus
You can use the addMenuItem
util to add an item into a context menu. Context menus appear when
right-clicking almost anything, and left-clicking special objects such as guild headers. Items can
be inserted into any part of a menu, but default to a special group for plugins.
import { Injector, components, types } from "replugged";
const {
ContextMenu: { MenuItem },
} = components;
const { ContextMenuTypes } = types;
const injector = new Injector();
export function start(): void {
injector.utils.addMenuItem(
ContextMenuTypes.UserContext, // Right-clicking a user
(data) => {
return <MenuItem id="my-item" label="An Item!" action={() => console.log(data)} />;
},
);
}
export function stop(): void {
injector.uninjectAll();
}
More examples can be found here.
Slash Commands
You can use the registerSlashCommand
util to register a custom slash command under the Replugged
command section. In the following example we are registering a slash command which sends a normal
message or an Embed, based on the value of the option.
The slash commands API is supported by TypeScript. An error will occur if you try to get the value of a nonexistent option in the registered slash command.
import { Injector, types } from "replugged";
const { ApplicationCommandOptionType } = types;
const injector = new Injector();
export function start(): void {
injector.utils.registerSlashCommand({
name: "my-command",
description: "Helpful command description",
options: [
{
name: "my-option",
description: "Very cool option description",
type: ApplicationCommandOptionType.Boolean,
required: true,
},
],
executor: (interaction) => {
// You can access the guild and channel of where the command
// was executed with interaction.guild and interaction.channel
// Get the value of the option
const sendEmbed = interaction.getValue("my-option");
if (sendEmbed) {
return {
// Whether the message should be sent in the channel;
// By setting it to false, the message will be hidden
// from anyone except the executor of the slash command
send: false,
result: "This is a message",
};
} else {
return {
send: false,
embeds: [
{
color: 0x5865f2,
title: "Embed title",
description: "Embed description",
},
],
};
}
},
});
}
export function stop(): void {
injector.uninjectAll();
}