Skip to content

Commit f978d9a

Browse files
committed
Initial commit
0 parents  commit f978d9a

23 files changed

+9026
-0
lines changed

Diff for: .github/workflows_/deploy.yml

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
name: Deploy to GitHub Pages
2+
3+
on:
4+
push:
5+
branches:
6+
- main
7+
8+
jobs:
9+
deploy:
10+
name: Deploy to GitHub Pages
11+
runs-on: ubuntu-latest
12+
steps:
13+
- uses: actions/checkout@v3
14+
- uses: actions/setup-node@v3
15+
with:
16+
node-version: 18
17+
cache: pnpm
18+
19+
- name: Install dependencies
20+
run: pnpm install --frozen-lockfile
21+
- name: Build website
22+
run: pnpm run build
23+
24+
- name: Deploy to GitHub Pages
25+
uses: peaceiris/actions-gh-pages@v3
26+
with:
27+
github_token: ${{ secrets.GITHUB_TOKEN }}
28+
publish_dir: ./build
29+
user_name: github-actions[bot]
30+
user_email: 41898282+github-actions[bot]@users.noreply.github.com

Diff for: .gitignore

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Dependencies
2+
/node_modules
3+
4+
# Production
5+
/build
6+
7+
# Generated files
8+
.docusaurus
9+
.cache-loader
10+
11+
# Misc
12+
.DS_Store
13+
.env.local
14+
.env.development.local
15+
.env.test.local
16+
.env.production.local
17+
18+
npm-debug.log*
19+
yarn-debug.log*
20+
yarn-error.log*

Diff for: .prettierrc

+6
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"printWidth": 80,
3+
"trailingComma": "none",
4+
"tabWidth": 2,
5+
"singleQuote": false
6+
}

Diff for: README.md

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# moonlight-mod.github.io
2+
3+
The official website and documentation for [moonlight](https://github.com/moonlight-mod/moonlight).

Diff for: babel.config.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
module.exports = {
2+
presets: [require.resolve("@docusaurus/core/lib/babel/preset")]
3+
};

Diff for: docs/dev/_category_.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label": "moonlight developers",
3+
"position": 3
4+
}

Diff for: docs/dev/setup.md

+33
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
# Project setup
2+
3+
## Building from source
4+
5+
You will need [pnpm](https://pnpm.io), as moonlight uses it for dependency and workspace management.
6+
7+
- Clone the repository.
8+
- Install dependencies with `pnpm i`.
9+
- Build the project with `pnpm run build`.
10+
- For working on moonlight, a watch mode is available with `pnpm run dev`.
11+
12+
## Project structure
13+
14+
moonlight is split into a [pnpm workspace](https://pnpm.io/workspaces). The project is comprised of multiple packages:
15+
16+
- `core`: core code and utilities responsible for loading extensions
17+
- `injector`: the entrypoint for moonlight loaded by the patched Discord client
18+
- loads the config and detected extensions from disk and forwards them to the other stages
19+
- loads host modules
20+
- sets up `node-preload` to be ran when the browser window is created
21+
- `node-preload`: ran before Discord's code on the Node side
22+
- receives the config/detected extensions from `injector` and forwards it to `web-preload`
23+
- loads node modules
24+
- loads `web-preload` and forwards some inforation to it
25+
- `web-preload`: ran before Discord's code on the web side
26+
- receives the loaded config and detected extensions from `node-preload`
27+
- loads extensions and installs their Webpack modules and patches
28+
- `core-extensions`: built-in extensions that come with every moonlight install, mostly libraries
29+
- `types`: types for moonlight's core, core extensions, and plugin manifests/exports
30+
31+
## Build system
32+
33+
moonlight uses [esbuild](https://esbuild.github.io) as its build system (`build.mjs`). This script is responsible for outputting `injector`, `node-preload`, and `web-preload` into `dist`, along with all core extensions.

Diff for: docs/ext-dev/_category_.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label": "Extension developers",
3+
"position": 2
4+
}

Diff for: docs/ext-dev/getting-started.md

+12
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# Getting started
2+
3+
## Sample extension
4+
5+
A sample extension is provided to start development quickly:
6+
7+
- Pre-configured build system (esbuild)
8+
- TypeScript support
9+
- Both web and Node.js entrypoints
10+
- Example patch & Webpack modules
11+
12+
You can view it [here](https://github.com/moonlight-mod/sample-extension).

Diff for: docs/ext-dev/helpful-exts.md

+36
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Helpful extensions
2+
3+
This is a list of helpful extensions that can be used to aid extension development.
4+
5+
## Config options
6+
7+
These aren't extensions, but rather options in the moonlight config:
8+
9+
- `devSearchPaths`: An array of directories to recursively search for built extensions (`manifest.json`).
10+
- When developing extensions, use the `dist` folder instead of the root folder. This is because the `manifest.json` resides in the root of the source code, but does not have the built files, which wastes time resolving files that will never exist.
11+
- `loggerLevel`: The level to log at. If not set, defaults to `info`.
12+
- `patchAll`: Set to `true` to wrap every Webpack module in a function. This slows down client starts significantly, but can make debugging easier.
13+
14+
## Common
15+
16+
Common exposes multiple Webpack modules useful for development:
17+
18+
- `common_react`: Discord's version of React (required to be in scope for JSX)
19+
- `common_flux`: Discord's version of Flux
20+
- `common_stores`: A proxy around Discord's Flux stores
21+
- Access stores as properties, e.g. `require("common_stores").UserStore`
22+
- `common_http`: A small HTTP client
23+
24+
## Disable Sentry & No Track
25+
26+
Both of these extensions do not provide any utilities, but prevent your client from sending metrics to Discord, which lessens the risk of moonlight related errors being reported. These should always be enabled. These are not magical tools to prevent account suspension, and you should always consider safety when writing an extension (especially one that makes requests automatically).
27+
28+
## Spacepack
29+
30+
Spacepack allows you to find and inspect Webpack modules. The "Add to global scope" setting is suggested, so you can use Spacepack from within DevTools.
31+
32+
- `require`: Standard Webpack require.
33+
- `inspect`: Fetch a Webpack module by its ID and return an isolated copy of it, which can be double-clicked in DevTools to inspect the source code.
34+
- `findByCode`, `findByExports`: Returns an array of Webpack modules that has code/exports that match the arguments.
35+
- `findObjectFromKey`, `findObjectFromValue`, `findObjectFromKeyValuePair`, `findFunctionByStrings`: Search the exports of a Webpack module to find an object or function using the given arguments.
36+
- `modules`, `cache`: The Webpack modules and cache objects.

Diff for: docs/ext-dev/pitfalls.md

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Pitfalls
2+
3+
## Web vs Node.js
4+
5+
Because of the Electron process model, extensions have two entrypoints: one on the web side (where the actual Discord app runs) and one on the Node.js side (where DiscordNative and such live).
6+
7+
Native Node.js modules *can* be used in an extension, but they cannot be imported from the web side. Instead, write a wrapper around them in your Node.js entrypoint, and call them from the web side:
8+
9+
```ts title="node.ts"
10+
module.exports.doSomething = () => {
11+
console.log("Doing something...");
12+
};
13+
```
14+
15+
```ts title="index.ts"
16+
const natives = moonlight.getNatives("your extension ID");
17+
natives.doSomething();
18+
```
19+
20+
## Webpack require is not Node.js require
21+
22+
The `require` function used in Webpack modules and patches is not the same as the function in Node.js. Instead, it lets you require other Webpack modules.
23+
24+
If you have a Webpack module you want to load, you can `require` it by its ID. Extension Webpack modules take the form of `${ext.id}_${webpackModule.name}` (e.g. `spacepack_spacepack` or `common_react`).
25+
26+
## Spacepack findByCode matching itself
27+
28+
When using the `findByCode` function in Spacepack while inside of a Webpack module, you can sometimes accidentally match yourself. This can be solved in two ways.
29+
30+
The first option is to bring it out of the Webpack module source, but still in code:
31+
32+
```ts
33+
const findReact = [
34+
"__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED",
35+
/\.?version(?:=|:)/,
36+
/\.?createElement(?:=|:)/
37+
];
38+
39+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
40+
myWebpackModule: {
41+
dependencies: [{ ext: "spacepack", id: "spacepack" }],
42+
run: (module, exports, require) => {
43+
const spacepack = require("spacepack_spacepack");
44+
const React = spacepack.findByCode(...findReact)[0].exports;
45+
}
46+
}
47+
};
48+
```
49+
50+
The second option is to split a string in half and concatenante it, such that the string is fragmented in source but will evaluate to the same string:
51+
52+
```ts
53+
const spacepack = require("spacepack_spacepack");
54+
const React = spacepack.findByCode([
55+
"__SECRET_INTERNALS_DO_NOT" + "_USE_OR_YOU_WILL_BE_FIRED",
56+
/\.?version(?:=|:)/,
57+
/\.?createElement(?:=|:)/
58+
])[0].exports;
59+
```
60+
61+
## Using JSX
62+
63+
[JSX](https://react.dev/learn/writing-markup-with-jsx) (and its TypeScript version, TSX) is an extension of JavaScript that allows you to write HTML-like syntax in your code. The default configuration of the sample extension is to convert the JSX to `React.createElement` calls:
64+
65+
```tsx
66+
const myElement = <span>Hi!</span>;
67+
// becomes
68+
const myElement = React.createElement("span", null, "Hi!");
69+
```
70+
71+
Because of this, you need to make sure the React variable is in scope. The easiest way to do this is to use the Common core extension:
72+
73+
```tsx
74+
const React = require("common_react");
75+
const myElement = <span>Hi!</span>;
76+
```

Diff for: docs/ext-dev/webpack.md

+125
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
# Webpack modules & patching
2+
3+
[Webpack](https://webpack.js.org) is a library used by Discord to turn their codebase into a bundled JavaScript file. Code gets converted into Webpack "modules", which are individual functions that can require and load each other. You can think of them as individual files, each with their own code and exports, but converted into single functions inside of the client.
4+
5+
## Patching
6+
7+
Patching is the process of altering the code of Webpack modules. Each module is [minified](https://en.wikipedia.org/wiki/Minification_(programming)), which means that its source has no whitespace and variable names are often short. Patching allows extensions to find and replace snippets of minified code.
8+
9+
To create a patch, export them from your extension's web entrypoint:
10+
11+
```ts
12+
import { Patch } from "@moonlight-mod/types";
13+
14+
export const patches: Patch[] = [
15+
{
16+
find: "...",
17+
replace: {
18+
match: "...",
19+
replacement: "..."
20+
}
21+
}
22+
];
23+
```
24+
25+
A patch consists of three parts:
26+
27+
- `find` dictates what Webpack module we want to patch. It is matched against the code of all Webpack modules, and once the match is found, it patches that module. Because of this, the match must be specific to a single module.
28+
- `match` is used to find the piece of code we want to patch inside of the target Webpack module. The area of code that is matched is replaced with the `replacement`.
29+
- `replacement` is the code that replaces the matched code.
30+
31+
`find` and `match` can both be regular expressions, and `replacement` can either be a string or a function that returns a string.
32+
33+
You can also set the `type` field in `replace` to `PatchReplaceType.Module`, in which case the `replacement` will be used as the entire module's code. This completely overrides it, breaking other extensions that patch the same module, so use it wisely.
34+
35+
When `match` is a regex and `replacement` is a function, it will be passed the groups matched in the regex. This is useful for capturing and reusing the minified variable names.
36+
37+
### Examples
38+
39+
#### Using a regex for a match
40+
41+
```ts
42+
// From Disable Sentry
43+
export const patches: Patch[] = [
44+
{
45+
find: "window.DiscordErrors=",
46+
replace: {
47+
type: PatchReplaceType.Normal,
48+
match: /uses_client_mods:\(0,.\..\)\(\)/,
49+
replacement: "uses_client_mods:false"
50+
}
51+
}
52+
];
53+
```
54+
55+
### Using a function as a replacement
56+
57+
```ts
58+
// From Settings
59+
export const patches: Patch[] = [
60+
{
61+
find: 'navId:"user-settings-cog",',
62+
replace: {
63+
match: /children:\[(.)\.map\(.+?\),{children:.\((.)\)/,
64+
// `orig` is the entire string, and the following args are each group
65+
replacement: (orig, sections, section) =>
66+
`${orig}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()`
67+
}
68+
}
69+
];
70+
```
71+
72+
#### Replacing an entire module
73+
74+
```ts
75+
// From Disable Sentry
76+
export const patches: Patch[] = [
77+
{
78+
find: "window.DiscordSentry=",
79+
replace: {
80+
type: PatchReplaceType.Module,
81+
replacement: () => () => {
82+
// no-op
83+
}
84+
}
85+
}
86+
];
87+
```
88+
89+
## Webpack module insertion
90+
91+
Similar to patching, extensions can also insert their own Webpack modules. These can be required like normal modules (which means they can be used inside of patches and other extensions).
92+
93+
To create a Webpack module, export them from your extension's web entrypoint:
94+
95+
```ts
96+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
97+
moduleName: {
98+
entrypoint: true,
99+
run: (module, exports, require) => {
100+
console.log("Hello, world!");
101+
102+
// You can export your own data from a Webpack module for use in other places.
103+
module.exports = "Hello from someLibrary's exports!";
104+
}
105+
}
106+
};
107+
```
108+
109+
Specify `entrypoint` to run on startup. If you need to run after a certain Webpack module, or use another module as a library, specify `dependencies`:
110+
111+
```ts
112+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
113+
moduleName: {
114+
dependencies: [
115+
{ ext: "common", id: "stores" }
116+
],
117+
entrypoint: true,
118+
run: (module, exports, require) => {
119+
const UserStore = require("common_stores").UserStore;
120+
}
121+
}
122+
};
123+
```
124+
125+
You can then require this module using the format `${ext.id}_${webpackModule.name}` (e.g. the `react` Webpack module in the `common` extension has the ID `common_react`).

Diff for: docs/using/_category_.json

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"label": "Using moonlight",
3+
"position": 1
4+
}

Diff for: docs/using/install.md

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
# Installation
2+
3+
moonlight in its current stage does not have any installer, but you can install it manually.
4+
5+
- [Build moonlight](/docs/dev/setup).
6+
- Go to your Discord install's `resources` folder.
7+
- Rename the `app.asar` to `_app.asar`, and create an `app` folder. Discord will load the folder instead of the `.asar` now that it has been renamed.
8+
- Create the following files:
9+
10+
> `resources/app/package.json`:
11+
>
12+
> ```json
13+
> {
14+
> "name": "discord",
15+
> "main": "./injector.js",
16+
> "private": true
17+
> }
18+
> ```
19+
>
20+
> `resources/app/injector.js`:
21+
>
22+
> ```js
23+
> require("/path/to/moonlight/dist/injector").inject(
24+
> require.resolve("../_app.asar")
25+
> );
26+
> ```
27+
28+
Adjust the `/path/to/moonlight/dist` to point to the `dist` folder in your locally cloned moonlight install.
29+
30+
After first launch, moonlight will create a config directory in the Electron `appData` path named `moonlight-mod`. Each Discord branch has its own `.json` file for configuration.

0 commit comments

Comments
 (0)