Cookbook
Extension entrypoints
Section titled “Extension entrypoints”Some extensions need to go beyond the scope of the Discord web application and interact with the native parts of the client. To help with this, extensions can load in one or more environments:
- The “web” environment (the browser where the web app runs):
index.ts
& Webpack modules - The “Node” environment (where DiscordNative runs, with access to Node.js APIs):
node.ts
- The “host” environment (with little sandboxing and access to Electron APIs):
host.ts
These map to the renderer, preload script, and main process in Electron terminology. In moonlight internals, the term “browser” is used to refer to the moonlight browser extension, while “web” refers to the web application that runs on the desktop app and browser site.
Most extensions only run in the web environment, where the majority of the Discord client code executes in. However, extensions can run in the other environments for access to extra APIs when needed. Use system APIs as little as possible, and be mindful about security concerns.
Extensions can export APIs from the Node environment and import them from the web environment using moonlight.getNatives
:
export function doSomething() { // insert some Node.js-specific code here}
const natives = moonlight.getNatives("your extension ID");natives.doSomething();
Remember to restart the dev server after creating node.ts
, and that you cannot directly import node.ts.
Exporting from your extension
Section titled “Exporting from your extension”On the web target (index.ts
), you can export patches, Webpack modules, and CSS styles:
import type { ExtensionWebExports } from "@moonlight-mod/types";
export const patches: ExtensionWebExports["patches"] = [];export const webpackModules: ExtensionWebExports["webpackModules"] = {};export const styles: ExtensionWebExports["styles"] = [];
All exports are optional. If you aren’t exporting anything from this file (e.g. your extension works entirely on the Node or host environments), you can delete index.ts
.
Using React
Section titled “Using React”Create a Webpack module with a .tsx
file extension, then mark React as a module dependency:
export const webpackModules: ExtensionWebExports["webpackModules"] = { element: { dependencies: [ { id: "react" } ] }};
In the module, import React from mappings:
import React from "@moonlight-mod/wp/react";
export default function MyElement() { return <span>Hello, world!</span>;}
You can use this React component in a patch or through an extension library. React must be imported into the scope when using JSX.
Using Flux
Section titled “Using Flux”Discord internally maintains a heavily-modified fork of Flux. Various state and events in the client are managed with Flux.
Interacting with Flux events
Section titled “Interacting with Flux events”Flux events contain a type (e.g. MESSAGE_CREATE
) and their associated data. They function similarly to the Discord gateway (and some gateway events have Flux equivalents), but they are two separate concepts, and there are client-specific Flux events.
To interact with Flux events, mark discord/Dispatcher
as a module dependency, then import it from mappings in your Webpack module:
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
// Listen for MESSAGE_CREATE eventsDispatcher.subscribe("MESSAGE_CREATE", (event: any) => { console.log(event);});
// Block all events (don't actually do this)Dispatcher.addInterceptor((event) => { console.log(event.type); return true; // return `true` to block, `false` to pass through});
Interacting with Flux stores
Section titled “Interacting with Flux stores”Flux stores contain application state and dispatch/receive events. Most Flux stores use the same central Flux dispatcher (discord/Dispatcher
).
To interact with Flux stores, use the Common extension library by adding it as a dependency to your extension and module:
import { UserStore } from "@moonlight-mod/wp/common_stores";
console.log(UserStore.getCurrentUser());
Flux stores can be used in React components with the useStateFromStores
hook.
Using slash commands
Section titled “Using slash commands”Slash commands can be registered with the Commands extension library. Remember to add the Commands extension as a extension and module dependency.
import Commands from "@moonlight-mod/wp/commands_commands";import { InputType, CommandType } from "@moonlight-mod/types/coreExtensions/commands";
Commands.registerCommand({ id: "myCoolCommand", description: "(insert witty example description here)", inputType: InputType.BUILT_IN, type: CommandType.CHAT, options: [], execute: () => { // do something here }});
Adding options to a slash command
Section titled “Adding options to a slash command”Specify the option in the options
array, then use a type predicate to get the value with the correct type:
import Commands from "@moonlight-mod/wp/commands_commands";import { CommandType, InputType, OptionType, StringCommandOption } from "@moonlight-mod/types/coreExtensions/commands";
const logger = moonlight.getLogger("your extension");
Commands.registerCommand({ type: CommandType.CHAT, inputType: InputType.BUILT_IN, id: "print", description: "Print a message to the logger", options: [ { name: "text", description: "What to print", type: OptionType.STRING } ], execute: (options) => { const textOption = options.find((o): o is StringCommandOption => o.name === "text")!; logger.info(textOption.value); }});
Sending a message with a slash command
Section titled “Sending a message with a slash command”Use InputType.BUILT_IN_TEXT
to send the command in chat, then return an object with content
to override the message:
import Commands from "@moonlight-mod/wp/commands_commands";import { CommandType, InputType } from "@moonlight-mod/types/coreExtensions/commands";
Commands.registerCommand({ type: CommandType.CHAT, inputType: InputType.BUILT_IN_TEXT, id: "colonThree", description: "Post :3 in chat", options: [], execute: () => { return { content: ":3" }; }});
Note that this sends an actual message into chat - it is not clientside or ephemeral.
Modifying message content before it is sent
Section titled “Modifying message content before it is sent”The legacy command system can also be used to replace text in messages before they are sent.
import Commands from "@moonlight-mod/wp/commands_commands";
Commands.registerLegacyCommand("unique-id", { // This can be a more specific regex, but this only tells it to run if it // finds anything that matches this regex within the message. // You will have to do your own extraction and processing if you want to do // something based on a specific string. match: /.*/, action: (content, context) => { // modify the content here return { content }; }})