Skip to content

Commit a833d24

Browse files
committed
Stuff for API level v2
1 parent 9c142c1 commit a833d24

10 files changed

+196
-14
lines changed

blog/2023-12-06-reintroducing-moonlight.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ image: /static/img/wordmark.png
77

88
A few days ago, we released a new Discord client mod called [moonlight](/). moonlight is the end result of our ideas and experiences from Discord client modding, influenced by several years of working on other client modding projects. We invite developers to try out moonlight and report feedback on what we can improve.
99

10+
<!-- truncate -->
11+
1012
## Yet another Discord mod
1113

1214
Tens if not hundreds of Discord desktop client mods have come and gone, so why make another? Simple; the freedom to innovate and experiment. With moonlight, we're free to do what we want and experiment with the ideas that we've had for years without any limitations. Creating moonlight has offered a fresh slate to build our own systems and play with what we like most, and allows us to try out new things with zero consequence.
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
---
2+
title: moonlight API v2, mappings, and more
3+
description: New libraries and utilities for a new version of moonlight
4+
authors: [notnite, cynosphere, adryd]
5+
---
6+
7+
Been a while, eh? The last post we wrote for moonlight was [when we first introduced it](/blog/2023-12-06-reintroducing-moonlight.md). Sounds like it's time to change that!
8+
9+
<!-- truncate -->
10+
11+
moonlight development was stalled for a while this year, particularly due to lack of motivation. Less people than expected tinkering with moonlight gave us less reason to update it, and so it fell behind with Discord updates, eventually breaking catastrophically for several months. Thanks to [redstonekasi](https://github.com/redstonekasi) for submitting pull requests for fixing a *lot* of things while none of us had the energy to.
12+
13+
About a week ago, [Ari](https://github.com/adryd325) had the idea of making a centralized repository for Discord client mappings. This short conversation would lead to one of the greatest nerdsnipes in moonlight history.
14+
15+
With the new features we're rolling out, this means new concepts of an "API level". Extensions that do not meet the provided API level will not load. If you don't care about the shiny new toys, jump [here](#what-a-version-bump-means-for-you) for more info on that.
16+
17+
## LunAST: AST-based mapping and patching
18+
19+
The concept of centralized mappings started with [LunAST](https://github.com/moonlight-mod/lunast) (Lunar + AST), a library for manipulating Webpack modules with various ESTree-related tools. LunAST enables you to patch and traverse modules using an AST, which is much more flexible than the current text-based patching with RegEx/strings.
20+
21+
Here's the first LunAST patch we ever wrote, as an idea for how it works. This makes it say "balls" when you click on an image preview (very professional, I know):
22+
23+
```ts
24+
import type { AST } from "@moonlight-mod/types";
25+
26+
moonlight.lunast.register({
27+
name: "ImagePreview",
28+
find: ".Messages.OPEN_IN_BROWSER",
29+
process({ id, ast, lunast, markDirty }) {
30+
const getters = lunast.utils.getPropertyGetters(ast);
31+
const replacement = lunast.utils
32+
.magicAST(`return require("common_react").createElement(
33+
"div",
34+
{
35+
style: {
36+
color: "white",
37+
},
38+
},
39+
"balls"
40+
)`)!;
41+
for (const data of Object.values(getters)) {
42+
if (!lunast.utils.is.identifier(data.expression)) continue;
43+
44+
const node = data.scope.getOwnBinding(data.expression.name);
45+
if (!node) continue;
46+
47+
const body = node.path.get<AST.BlockStatement>("body");
48+
body.replaceWith(replacement);
49+
}
50+
markDirty();
51+
52+
return true;
53+
}
54+
});
55+
```
56+
57+
This is a very powerful tool, but we aren't sure how well it'll work for extension developers just yet. If you have a particularly complicated patch in your extension codebase, see if LunAST can help you. Some notes, though:
58+
59+
- AST parsing is expensive! Use `find` when possible to filter for modules to parse.
60+
- AST patching might break other text-based patches. If you encounter any weirdness, let us know.
61+
- Text-based patching is not going away, and you should not use AST patching everywhere. This is purely a tool for the harder stuff.
62+
63+
## moonmap: dynamic remapping of Webpack modules
64+
65+
[moonmap](https://github.com/moonlight-mod/moonmap) allows you to find a module, create a proper name for it, and create named exports from minified variable names. This feature was originally in LunAST, but we decided to expand on it and bring it into its own library. Here's a snippet from the mappings project (which we'll get into later):
66+
67+
```ts
68+
const name = "discord/utils/HTTPUtils";
69+
moonlight.moonmap.register({
70+
name,
71+
find: '.set("X-Audit-Log-Reason",',
72+
process({ id, moonmap }) {
73+
moonmap.addModule(id, name);
74+
75+
moonmap.addExport(name, "HTTP", {
76+
type: ModuleExportType.Key,
77+
find: "patch"
78+
});
79+
80+
return true;
81+
}
82+
});
83+
```
84+
85+
You can then just `spacepack.require("discord/utils/HTTPUtils").HTTP`, and it just works! Magic:tm:.
86+
87+
## mappings: client mod agnostic Discord mappings
88+
89+
[mappings](https://github.com/moonlight-mod/mappings) combines moonmap and LunAST into one project to map out the Discord client. This is an idea that was tested in [HH3](/blog/2023/12/06/reintroducing-moonlight#whats-with-hh3), and we believe that it's stable by now.
90+
91+
The biggest feature about the mappings is that they aren't locked into the moonlight patcher system. Any client mod that can implement moonmap and LunAST into their patching system can use the mappings repository with no extra effort. We hope this can save some duplicated effort across the client modding community.
92+
93+
```ts
94+
import load from "@moonlight-mod/mappings";
95+
96+
load(moonmap, lunast);
97+
98+
// later, after the modules finish initializing
99+
const Dispatcher = require("discord/Dispatcher").default;
100+
```
101+
102+
There's still a lot of work to be done for typing the untyped modules, and for adding new modules. We migrated a majority of types in the Common extension into this library, and most of the things in Common have been removed.
103+
104+
## What a version bump means for you
105+
106+
### As a user
107+
108+
All extensions you are currently using (minus the extensions built into moonlight) will stop working. The developers of those extensions will need to update them, and if those extensions are on [the official repository][official-repo], they will need to be resubmitted and reviewed.
109+
110+
### As an extension developer
111+
112+
Your extensions will need to be updated. See [this new page in the documentation](/docs/ext-dev/migrating-api-levels).
113+
114+
### As another client mod developer
115+
116+
All of the libraries mentioned above can be used by your own code now. Have fun!
117+
118+
## The future of moonlight
119+
120+
moonlight, like the [PlayStation 5](https://en.wikipedia.org/wiki/Category:PlayStation_5-only_games), is pointless when there's nothing to install onto it. As with [last time](/blog/2023-12-06-reintroducing-moonlight.md#whats-next-for-moonlight), we encourage developers to try making extensions. Let us know if there's anything we can improve, and submit your extension to [the official repository][official-repo] if you'd like.
121+
122+
We don't consider this a "moonlight 2.0" as much as "moonlight API version 2". There's no groundbreaking rewrite going on here, just some new libraries to play with.
123+
124+
[official-repo]: <https://github.com/moonlight-mod/extensions>

docs/02-ext-dev/02-helpful-exts.md

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,16 +11,6 @@ These aren't extensions, but rather options in the moonlight config:
1111
- `loggerLevel`: The level to log at. If not set, defaults to `info`.
1212
- `patchAll`: Set to `true` to wrap every Webpack module in a function. This slows down client starts significantly, but can make debugging easier.
1313

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-
2414
## Disable Sentry & No Track
2515

2616
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).

docs/02-ext-dev/04-esm-webpack-modules.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,3 +24,5 @@ declare module "@moonlight-mod/wp/sampleExtension_someLibrary" {
2424
## CJS and ESM interop
2525

2626
When using `export default` or `export something` in a ESM Webpack module like this, you will need to do `require("ext_id").default` or `require("ext_id").something` to access it. You can also use `module.exports`, but there may be issues that arise from it.
27+
28+
Importing the default export of a mapped Webpack module may have issues.

docs/02-ext-dev/05-pitfalls.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,9 +70,9 @@ const myElement = <span>Hi!</span>;
7070
const myElement = React.createElement("span", null, "Hi!");
7171
```
7272

73-
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:
73+
Because of this, you need to make sure the React variable is in scope. `react` is automatically populated as a Webpack module name with [mappings](/docs/02-ext-dev/07-mappings.md):
7474

7575
```tsx
76-
const React = require("common_react");
76+
const React = require("react");
7777
const myElement = <span>Hi!</span>;
7878
```

docs/02-ext-dev/06-webpack.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export const patches: Patch[] = [
9292

9393
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).
9494

95-
To create a Webpack module, export them from your extension's web entrypoint:
95+
To create a Webpack module, export them from your extension's web entrypoint, or [use an ESM Webpack module](/docs/02-ext-dev/04-esm-webpack-modules.md):
9696

9797
```ts
9898
export const webpackModules: Record<string, ExtensionWebpackModule> = {
@@ -124,4 +124,4 @@ export const webpackModules: Record<string, ExtensionWebpackModule> = {
124124
};
125125
```
126126

127-
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`).
127+
You can then require this module using the format `${ext.id}_${webpackModule.name}` (e.g. the `stores` Webpack module in the `common` extension has the ID `common_stores`).

docs/02-ext-dev/07-mappings.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# Mappings
2+
3+
moonlight uses [mappings](https://github.com/moonlight-mod/mappings) that automatically rename Webpack modules for you.
4+
5+
## Using import statements with mappings
6+
7+
For mapped modules with types, you can `import` from them like normal in a Webpack module:
8+
9+
```ts
10+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
11+
```
12+
13+
## Using require with mappings
14+
15+
For mapped modules without types, you will need to require them manually. Your build system might not understand your `require`, so you may need to use `spacepack.require`:
16+
17+
```ts
18+
// React has a type, so this is not required, but this is for demonstration
19+
const React = spacepack.require("react");
20+
```
21+
22+
## Adding a mapped module to dependencies
23+
24+
You can add a mapped module to your Webpack module dependencies like normal - just remove the `ext` field.
25+
26+
```ts
27+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
28+
something: {
29+
dependencies: [
30+
{
31+
id: "discord/Dispatcher"
32+
},
33+
{
34+
id: "react"
35+
}
36+
]
37+
}
38+
};
39+
```
File renamed without changes.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Migrating API levels
2+
3+
When a moonlight API level increases, all extensions with a different API level will refuse to load. The current API level is 2.
4+
5+
As with every update to moonlight, the following apply:
6+
7+
- The types package must be updated: `pnpm add @moonlight-mod/types@latest`
8+
- The `version` field in your manifest must be updated.
9+
- The `apiLevel` field in your manifest must be set to the latest API level.
10+
- If your extension is on [the official repository](/docs/02-ext-dev/03-official-repository.md), it must be resubmitted and reviewed.
11+
12+
## 1 -> 2
13+
14+
The concept of an API level was introduced with API level 2. No API level specified is implicitly assumed to be 1.
15+
16+
With the introduction of [mappings](/docs/02-ext-dev/07-mappings.md), a lot of helper Webpack modules from the Common extension are gone, and moved into the mappings repository. Some notable examples:
17+
18+
- `common_flux`: `discord/packages/flux`
19+
- `common_fluxDispatcher`: `discord/Dispatcher`
20+
- `common_react`: `react`
21+
- `common_components`: `discord/components/common/index`

docs/03-dev/01-setup.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ moonlight is split into a [pnpm workspace](https://pnpm.io/workspaces). The proj
3535
## Build system
3636

3737
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.
38+
39+
## Other dependencies
40+
41+
moonlight uses some other packages that are not in the monorepo, like [LunAST](https://github.com/moonlight-mod/lunast), [moonmap](https://github.com/moonlight-mod/moonmap), and [mappings](https://github.com/moonlight-mod/mappings). When testing local forks, try [pnpm link](https://pnpm.io/cli/link).

0 commit comments

Comments
 (0)