Skip to main content

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

InjectionStarts atEnds atInputOutputCan modify propsCan modify return valueCalls original method
beforeStart of function callStart of function callFunction propsFunction propsYesNoYes
insteadStart of function callEnd of function callFunction propsFunction return valueYesYesNo (unless you call it yourself)
afterEnd of function callEnd of function callFunction return value (includes props too)Function return valueNoYesYes

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:

info

The props of this component extend the attributes of the div element.

NameTypeDefaultDescription
ariaLabelstringButton aria-label
channelChannelChannel of the message with the button popover; automatically added with the injection
dangerousbooleanfalseWhether the icon color is red
disabledbooleanfalseWhether the button is disabled
icon* ComponentType<unknown>Icon component
iconPropsRecord<string, unknown>Props for the icon component
keystringButton popover key; automatically added with the injection
label* stringTooltip text
messageMessageMessage with the button popover; automatically added with the injection
onClick* (channel: Channel, message: Message, event: MouseEvent<HTMLDivElement>) => voidFunction ran on button click
selectedbooleanfalseWhether the button is selected
separatorbooleanfalseWhether the button shows a divider among others
sparklebooleanfalseWhether the button shows a sparkle effect
tooltipColorTooltip.ColorsTooltip.Colors.PRIMARYTooltip color
tooltipTextstringTooltip 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.

info

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();
}