Cookbook
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
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
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
Discord internally maintains a heavily-modified fork of Flux. Various state and events in the client are managed with Flux.
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
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
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 }});
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 }; }})