+41
.github/workflows/browser.yml
+41
.github/workflows/browser.yml
···
1
+
name: Browser extension builds
2
+
3
+
on:
4
+
push:
5
+
branches:
6
+
- develop
7
+
8
+
jobs:
9
+
browser:
10
+
name: Browser extension builds
11
+
runs-on: ubuntu-latest
12
+
steps:
13
+
- uses: actions/checkout@v4
14
+
- uses: pnpm/action-setup@v4
15
+
- uses: actions/setup-node@v4
16
+
with:
17
+
node-version: 22
18
+
cache: pnpm
19
+
20
+
- name: Install dependencies
21
+
run: pnpm install --frozen-lockfile
22
+
- name: Build moonlight
23
+
env:
24
+
NODE_ENV: production
25
+
run: pnpm run build
26
+
27
+
- name: Build MV3
28
+
run: pnpm run browser
29
+
- name: Build MV2
30
+
run: pnpm run browser-mv2
31
+
32
+
- name: Upload MV3
33
+
uses: actions/upload-artifact@v4
34
+
with:
35
+
name: browser
36
+
path: ./dist/browser
37
+
- name: Upload MV2
38
+
uses: actions/upload-artifact@v4
39
+
with:
40
+
name: browser-mv2
41
+
path: ./dist/browser-mv2
+24
.github/workflows/lint.yml
+24
.github/workflows/lint.yml
···
1
+
name: Lint commits
2
+
on: [push, pull_request]
3
+
4
+
permissions:
5
+
checks: write
6
+
7
+
jobs:
8
+
lint:
9
+
name: Lint commits
10
+
runs-on: ubuntu-latest
11
+
steps:
12
+
- uses: actions/checkout@v4
13
+
- uses: pnpm/action-setup@v4
14
+
- uses: actions/setup-node@v4
15
+
with:
16
+
node-version: 22
17
+
cache: pnpm
18
+
19
+
- name: Install dependencies
20
+
run: pnpm install --frozen-lockfile
21
+
- name: Run tsc
22
+
run: pnpm run typecheck
23
+
- name: Run ESLint
24
+
run: pnpm run lint
+13
-11
.github/workflows/nightly.yml
+13
-11
.github/workflows/nightly.yml
···
15
15
name: Nightly builds on GitHub Pages
16
16
runs-on: ubuntu-latest
17
17
steps:
18
-
- uses: actions/checkout@v3
19
-
20
-
- uses: pnpm/action-setup@v2
21
-
with:
22
-
version: 8
23
-
run_install: false
24
-
- uses: actions/setup-node@v3
18
+
- uses: actions/checkout@v4
19
+
- uses: pnpm/action-setup@v4
20
+
- uses: actions/setup-node@v4
25
21
with:
26
-
node-version: 18
22
+
node-version: 22
27
23
cache: pnpm
28
24
29
25
- name: Install dependencies
···
31
27
- name: Build moonlight
32
28
env:
33
29
NODE_ENV: production
30
+
MOONLIGHT_BRANCH: nightly
31
+
MOONLIGHT_VERSION: ${{ github.sha }}
34
32
run: pnpm run build
35
33
36
34
- name: Write ref/commit to file
37
35
run: |
36
+
cd ./dist
37
+
tar -czf ../dist.tar.gz *
38
+
cd ..
39
+
mv ./dist.tar.gz ./dist/dist.tar.gz
38
40
find ./dist -type f -not -path "./dist/files" > ./dist/files
39
41
echo "${{ github.sha }}" > ./dist/ref
40
42
echo "${{ github.ref }}" >> ./dist/ref
41
43
echo "$(date +%s)" >> ./dist/ref
42
44
43
45
- name: Setup GitHub Pages
44
-
uses: actions/configure-pages@v3
46
+
uses: actions/configure-pages@v5
45
47
- name: Upload artifact
46
-
uses: actions/upload-pages-artifact@v1
48
+
uses: actions/upload-pages-artifact@v3
47
49
with:
48
50
path: ./dist
49
51
- name: Deploy to GitHub Pages
50
-
uses: actions/deploy-pages@v2
52
+
uses: actions/deploy-pages@v4
+16
.github/workflows/nix.yml
+16
.github/workflows/nix.yml
···
1
+
name: Check Nix flake
2
+
on: [push, pull_request]
3
+
4
+
permissions:
5
+
checks: write
6
+
7
+
jobs:
8
+
nix:
9
+
name: Check Nix flake
10
+
runs-on: ubuntu-latest
11
+
steps:
12
+
- uses: actions/checkout@v4
13
+
- uses: DeterminateSystems/nix-installer-action@main
14
+
15
+
- name: Build default flake output
16
+
run: nix build
+6
-8
.github/workflows/release.yml
+6
-8
.github/workflows/release.yml
···
13
13
name: Release builds to GitHub Releases
14
14
runs-on: ubuntu-latest
15
15
steps:
16
-
- uses: actions/checkout@v3
17
-
18
-
- uses: pnpm/action-setup@v2
19
-
with:
20
-
version: 8
21
-
run_install: false
22
-
- uses: actions/setup-node@v3
16
+
- uses: actions/checkout@v4
17
+
- uses: pnpm/action-setup@v4
18
+
- uses: actions/setup-node@v4
23
19
with:
24
-
node-version: 18
20
+
node-version: 22
25
21
cache: pnpm
26
22
27
23
- name: Install dependencies
···
29
25
- name: Build moonlight
30
26
env:
31
27
NODE_ENV: production
28
+
MOONLIGHT_BRANCH: stable
29
+
MOONLIGHT_VERSION: ${{ github.ref_name }}
32
30
run: pnpm run build
33
31
- name: Create archive
34
32
run: |
+32
.github/workflows/types.yml
+32
.github/workflows/types.yml
···
1
+
name: Publish types on npm
2
+
on: workflow_dispatch
3
+
4
+
permissions:
5
+
contents: read
6
+
pages: write
7
+
id-token: write
8
+
9
+
jobs:
10
+
types:
11
+
name: Publish types on npm
12
+
runs-on: ubuntu-latest
13
+
steps:
14
+
- uses: actions/checkout@v4
15
+
- uses: pnpm/action-setup@v4
16
+
- uses: actions/setup-node@v4
17
+
with:
18
+
node-version: 22
19
+
cache: pnpm
20
+
registry-url: https://registry.npmjs.org
21
+
22
+
- name: Install dependencies
23
+
run: pnpm install --frozen-lockfile
24
+
- name: Build moonlight
25
+
env:
26
+
NODE_ENV: production
27
+
run: pnpm run build
28
+
29
+
- name: Publish types
30
+
run: pnpm publish --filter=./packages/types --access public --no-git-checks
31
+
env:
32
+
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
+10
.gitignore
+10
.gitignore
+4
.husky/pre-commit
+4
.husky/pre-commit
+1
.prettierignore
+1
.prettierignore
···
1
+
pnpm-lock.yaml
+4
-4
.prettierrc
+4
-4
.prettierrc
+4
-1
CHANGELOG.md
+4
-1
CHANGELOG.md
+165
LICENSE
+165
LICENSE
···
1
+
GNU LESSER GENERAL PUBLIC LICENSE
2
+
Version 3, 29 June 2007
3
+
4
+
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
5
+
Everyone is permitted to copy and distribute verbatim copies
6
+
of this license document, but changing it is not allowed.
7
+
8
+
9
+
This version of the GNU Lesser General Public License incorporates
10
+
the terms and conditions of version 3 of the GNU General Public
11
+
License, supplemented by the additional permissions listed below.
12
+
13
+
0. Additional Definitions.
14
+
15
+
As used herein, "this License" refers to version 3 of the GNU Lesser
16
+
General Public License, and the "GNU GPL" refers to version 3 of the GNU
17
+
General Public License.
18
+
19
+
"The Library" refers to a covered work governed by this License,
20
+
other than an Application or a Combined Work as defined below.
21
+
22
+
An "Application" is any work that makes use of an interface provided
23
+
by the Library, but which is not otherwise based on the Library.
24
+
Defining a subclass of a class defined by the Library is deemed a mode
25
+
of using an interface provided by the Library.
26
+
27
+
A "Combined Work" is a work produced by combining or linking an
28
+
Application with the Library. The particular version of the Library
29
+
with which the Combined Work was made is also called the "Linked
30
+
Version".
31
+
32
+
The "Minimal Corresponding Source" for a Combined Work means the
33
+
Corresponding Source for the Combined Work, excluding any source code
34
+
for portions of the Combined Work that, considered in isolation, are
35
+
based on the Application, and not on the Linked Version.
36
+
37
+
The "Corresponding Application Code" for a Combined Work means the
38
+
object code and/or source code for the Application, including any data
39
+
and utility programs needed for reproducing the Combined Work from the
40
+
Application, but excluding the System Libraries of the Combined Work.
41
+
42
+
1. Exception to Section 3 of the GNU GPL.
43
+
44
+
You may convey a covered work under sections 3 and 4 of this License
45
+
without being bound by section 3 of the GNU GPL.
46
+
47
+
2. Conveying Modified Versions.
48
+
49
+
If you modify a copy of the Library, and, in your modifications, a
50
+
facility refers to a function or data to be supplied by an Application
51
+
that uses the facility (other than as an argument passed when the
52
+
facility is invoked), then you may convey a copy of the modified
53
+
version:
54
+
55
+
a) under this License, provided that you make a good faith effort to
56
+
ensure that, in the event an Application does not supply the
57
+
function or data, the facility still operates, and performs
58
+
whatever part of its purpose remains meaningful, or
59
+
60
+
b) under the GNU GPL, with none of the additional permissions of
61
+
this License applicable to that copy.
62
+
63
+
3. Object Code Incorporating Material from Library Header Files.
64
+
65
+
The object code form of an Application may incorporate material from
66
+
a header file that is part of the Library. You may convey such object
67
+
code under terms of your choice, provided that, if the incorporated
68
+
material is not limited to numerical parameters, data structure
69
+
layouts and accessors, or small macros, inline functions and templates
70
+
(ten or fewer lines in length), you do both of the following:
71
+
72
+
a) Give prominent notice with each copy of the object code that the
73
+
Library is used in it and that the Library and its use are
74
+
covered by this License.
75
+
76
+
b) Accompany the object code with a copy of the GNU GPL and this license
77
+
document.
78
+
79
+
4. Combined Works.
80
+
81
+
You may convey a Combined Work under terms of your choice that,
82
+
taken together, effectively do not restrict modification of the
83
+
portions of the Library contained in the Combined Work and reverse
84
+
engineering for debugging such modifications, if you also do each of
85
+
the following:
86
+
87
+
a) Give prominent notice with each copy of the Combined Work that
88
+
the Library is used in it and that the Library and its use are
89
+
covered by this License.
90
+
91
+
b) Accompany the Combined Work with a copy of the GNU GPL and this license
92
+
document.
93
+
94
+
c) For a Combined Work that displays copyright notices during
95
+
execution, include the copyright notice for the Library among
96
+
these notices, as well as a reference directing the user to the
97
+
copies of the GNU GPL and this license document.
98
+
99
+
d) Do one of the following:
100
+
101
+
0) Convey the Minimal Corresponding Source under the terms of this
102
+
License, and the Corresponding Application Code in a form
103
+
suitable for, and under terms that permit, the user to
104
+
recombine or relink the Application with a modified version of
105
+
the Linked Version to produce a modified Combined Work, in the
106
+
manner specified by section 6 of the GNU GPL for conveying
107
+
Corresponding Source.
108
+
109
+
1) Use a suitable shared library mechanism for linking with the
110
+
Library. A suitable mechanism is one that (a) uses at run time
111
+
a copy of the Library already present on the user's computer
112
+
system, and (b) will operate properly with a modified version
113
+
of the Library that is interface-compatible with the Linked
114
+
Version.
115
+
116
+
e) Provide Installation Information, but only if you would otherwise
117
+
be required to provide such information under section 6 of the
118
+
GNU GPL, and only to the extent that such information is
119
+
necessary to install and execute a modified version of the
120
+
Combined Work produced by recombining or relinking the
121
+
Application with a modified version of the Linked Version. (If
122
+
you use option 4d0, the Installation Information must accompany
123
+
the Minimal Corresponding Source and Corresponding Application
124
+
Code. If you use option 4d1, you must provide the Installation
125
+
Information in the manner specified by section 6 of the GNU GPL
126
+
for conveying Corresponding Source.)
127
+
128
+
5. Combined Libraries.
129
+
130
+
You may place library facilities that are a work based on the
131
+
Library side by side in a single library together with other library
132
+
facilities that are not Applications and are not covered by this
133
+
License, and convey such a combined library under terms of your
134
+
choice, if you do both of the following:
135
+
136
+
a) Accompany the combined library with a copy of the same work based
137
+
on the Library, uncombined with any other library facilities,
138
+
conveyed under the terms of this License.
139
+
140
+
b) Give prominent notice with the combined library that part of it
141
+
is a work based on the Library, and explaining where to find the
142
+
accompanying uncombined form of the same work.
143
+
144
+
6. Revised Versions of the GNU Lesser General Public License.
145
+
146
+
The Free Software Foundation may publish revised and/or new versions
147
+
of the GNU Lesser General Public License from time to time. Such new
148
+
versions will be similar in spirit to the present version, but may
149
+
differ in detail to address new problems or concerns.
150
+
151
+
Each version is given a distinguishing version number. If the
152
+
Library as you received it specifies that a certain numbered version
153
+
of the GNU Lesser General Public License "or any later version"
154
+
applies to it, you have the option of following the terms and
155
+
conditions either of that published version or of any later version
156
+
published by the Free Software Foundation. If the Library as you
157
+
received it does not specify a version number of the GNU Lesser
158
+
General Public License, you may choose any version of the GNU Lesser
159
+
General Public License ever published by the Free Software Foundation.
160
+
161
+
If the Library as you received it specifies that a proxy can decide
162
+
whether future versions of the GNU Lesser General Public License shall
163
+
apply, that proxy's public statement of acceptance of any version is
164
+
permanent authorization for you to choose that version for the
165
+
Library.
+16
-5
README.md
+16
-5
README.md
···
1
1
<h3 align="center">
2
-
<img src="./img/wordmark.png" alt="moonlight" />
2
+
<picture>
3
+
<source media="(prefers-color-scheme: dark)" srcset="./img/wordmark-light.png">
4
+
<source media="(prefers-color-scheme: light)" srcset="./img/wordmark.png">
5
+
<img src="./img/wordmark.png" alt="moonlight" />
6
+
</picture>
3
7
4
-
<a href="https://discord.gg/FdZBTFCP6F">Discord server</a>
8
+
<a href="https://moonlight-mod.github.io/using/install">Install</a>
9
+
\- <a href="https://moonlight-mod.github.io/ext-dev/getting-started">Docs</a>
10
+
\- <a href="https://discord.gg/FdZBTFCP6F">Discord server</a>
5
11
\- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a>
6
-
\- <a href="https://moonlight-mod.github.io/">Docs</a>
7
12
8
13
<hr />
14
+
15
+
<picture>
16
+
<source media="(prefers-color-scheme: dark)" srcset="https://moonlight-mod.github.io/moonbase.png">
17
+
<source media="(prefers-color-scheme: light)" srcset="https://moonlight-mod.github.io/moonbase-light.png">
18
+
<img src="https://moonlight-mod.github.io/moonbase.png" alt="A screenshot of Moonbase, the moonlight UI" />
19
+
</picture>
9
20
</h3>
10
21
11
22
**moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience.
12
23
13
24
moonlight is heavily inspired by hh3 (a private client mod) and the projects before it that it is inspired by, namely EndPwn. All core code is original or used with permission from their respective authors where not copyleft.
14
25
15
-
**_This is an experimental passion project._** moonlight was not created out of malicious intent nor intended to seriously compete with other mods. Anything and everything is subject to change.
26
+
moonlight is a **_passion project_** - things may break from time to time, but we try our best to keep things working in a timely manner.
16
27
17
-
moonlight is licensed under the [GNU Affero General Public License](https://www.gnu.org/licenses/agpl-3.0.html) (`AGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information.
28
+
moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`). See [the documentation](https://moonlight-mod.github.io/) for more information.
+226
-43
build.mjs
+226
-43
build.mjs
···
1
+
/* eslint-disable no-console */
1
2
import * as esbuild from "esbuild";
2
3
import copyStaticFiles from "esbuild-copy-static-files";
3
4
···
12
13
13
14
const prod = process.env.NODE_ENV === "production";
14
15
const watch = process.argv.includes("--watch");
16
+
const browser = process.argv.includes("--browser");
17
+
const mv2 = process.argv.includes("--mv2");
18
+
const clean = process.argv.includes("--clean");
19
+
20
+
const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev";
21
+
const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev";
15
22
16
23
const external = [
17
24
"electron",
18
25
"fs",
19
26
"path",
20
27
"module",
21
-
"events",
22
-
"original-fs", // wtf asar?
28
+
"discord", // mappings
23
29
24
30
// Silence an esbuild warning
25
31
"./node-preload.js"
26
32
];
27
33
34
+
let lastMessages = new Set();
35
+
/** @type {import("esbuild").Plugin} */
36
+
const deduplicatedLogging = {
37
+
name: "deduplicated-logging",
38
+
setup(build) {
39
+
build.onStart(() => {
40
+
lastMessages.clear();
41
+
});
42
+
43
+
build.onEnd(async (result) => {
44
+
const formatted = await Promise.all([
45
+
esbuild.formatMessages(result.warnings, {
46
+
kind: "warning",
47
+
color: true
48
+
}),
49
+
esbuild.formatMessages(result.errors, { kind: "error", color: true })
50
+
]).then((a) => a.flat());
51
+
52
+
// console.log(formatted);
53
+
for (const message of formatted) {
54
+
if (lastMessages.has(message)) continue;
55
+
lastMessages.add(message);
56
+
console.log(message.trim());
57
+
}
58
+
});
59
+
}
60
+
};
61
+
62
+
const timeFormatter = new Intl.DateTimeFormat(undefined, {
63
+
hour: "numeric",
64
+
minute: "numeric",
65
+
second: "numeric",
66
+
hour12: false
67
+
});
68
+
/** @type {import("esbuild").Plugin} */
69
+
const taggedBuildLog = (tag) => ({
70
+
name: "build-log",
71
+
setup(build) {
72
+
build.onEnd((result) => {
73
+
console.log(`[${timeFormatter.format(new Date())}] [${tag}] build finished`);
74
+
});
75
+
}
76
+
});
77
+
28
78
async function build(name, entry) {
29
-
const outfile = path.join("./dist", name + ".js");
79
+
let outfile = path.join("./dist", name + ".js");
80
+
const browserDir = mv2 ? "browser-mv2" : "browser";
81
+
if (name === "browser") outfile = path.join("./dist", browserDir, "index.js");
30
82
31
83
const dropLabels = [];
32
-
if (name !== "injector") dropLabels.push("injector");
33
-
if (name !== "node-preload") dropLabels.push("nodePreload");
34
-
if (name !== "web-preload") dropLabels.push("webPreload");
84
+
const labels = {
85
+
injector: ["injector"],
86
+
nodePreload: ["node-preload"],
87
+
webPreload: ["web-preload"],
88
+
browser: ["browser"],
89
+
90
+
webTarget: ["web-preload", "browser"],
91
+
nodeTarget: ["node-preload", "injector"]
92
+
};
93
+
for (const [label, targets] of Object.entries(labels)) {
94
+
if (!targets.includes(name)) {
95
+
dropLabels.push(label);
96
+
}
97
+
}
35
98
36
99
const define = {
37
100
MOONLIGHT_ENV: `"${name}"`,
38
-
MOONLIGHT_PROD: prod.toString()
101
+
MOONLIGHT_PROD: prod.toString(),
102
+
MOONLIGHT_BRANCH: `"${buildBranch}"`,
103
+
MOONLIGHT_VERSION: `"${buildVersion}"`
39
104
};
40
105
41
-
for (const iterName of Object.keys(config)) {
106
+
for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) {
42
107
const snake = iterName.replace(/-/g, "_").toUpperCase();
43
108
define[`MOONLIGHT_${snake}`] = (name === iterName).toString();
44
109
}
···
46
111
const nodeDependencies = ["glob"];
47
112
const ignoredExternal = name === "web-preload" ? nodeDependencies : [];
48
113
114
+
const plugins = [deduplicatedLogging, taggedBuildLog(name)];
115
+
if (name === "browser") {
116
+
plugins.push(
117
+
copyStaticFiles({
118
+
src: mv2 ? "./packages/browser/manifestv2.json" : "./packages/browser/manifest.json",
119
+
dest: `./dist/${browserDir}/manifest.json`
120
+
})
121
+
);
122
+
123
+
if (!mv2) {
124
+
plugins.push(
125
+
copyStaticFiles({
126
+
src: "./packages/browser/modifyResponseHeaders.json",
127
+
dest: `./dist/${browserDir}/modifyResponseHeaders.json`
128
+
})
129
+
);
130
+
plugins.push(
131
+
copyStaticFiles({
132
+
src: "./packages/browser/blockLoading.json",
133
+
dest: `./dist/${browserDir}/blockLoading.json`
134
+
})
135
+
);
136
+
}
137
+
138
+
plugins.push(
139
+
copyStaticFiles({
140
+
src: mv2 ? "./packages/browser/src/background-mv2.js" : "./packages/browser/src/background.js",
141
+
dest: `./dist/${browserDir}/background.js`
142
+
})
143
+
);
144
+
}
145
+
146
+
/** @type {import("esbuild").BuildOptions} */
49
147
const esbuildConfig = {
50
148
entryPoints: [entry],
51
149
outfile,
52
150
53
-
format: "cjs",
54
-
platform: name === "web-preload" ? "browser" : "node",
151
+
format: "iife",
152
+
globalName: "module.exports",
153
+
154
+
platform: ["web-preload", "browser"].includes(name) ? "browser" : "node",
55
155
56
156
treeShaking: true,
57
157
bundle: true,
···
61
161
external: [...ignoredExternal, ...external],
62
162
63
163
define,
64
-
dropLabels
164
+
dropLabels,
165
+
166
+
logLevel: "silent",
167
+
plugins,
168
+
169
+
// https://github.com/evanw/esbuild/issues/3944
170
+
footer:
171
+
name === "web-preload"
172
+
? {
173
+
js: `\n//# sourceURL=${name}.js`
174
+
}
175
+
: undefined
65
176
};
66
177
178
+
if (name === "browser") {
179
+
const coreExtensionsJson = {};
180
+
181
+
function readDir(dir) {
182
+
const files = fs.readdirSync(dir);
183
+
for (const file of files) {
184
+
const filePath = dir + "/" + file;
185
+
const normalizedPath = filePath.replace("./dist/core-extensions/", "");
186
+
if (fs.statSync(filePath).isDirectory()) {
187
+
readDir(filePath);
188
+
} else {
189
+
coreExtensionsJson[normalizedPath] = fs.readFileSync(filePath, "utf8");
190
+
}
191
+
}
192
+
}
193
+
194
+
readDir("./dist/core-extensions");
195
+
196
+
esbuildConfig.banner = {
197
+
js: `window._moonlight_coreExtensionsStr = ${JSON.stringify(JSON.stringify(coreExtensionsJson))};`
198
+
};
199
+
}
200
+
67
201
if (watch) {
68
202
const ctx = await esbuild.context(esbuildConfig);
69
203
await ctx.watch();
···
72
206
}
73
207
}
74
208
75
-
async function buildExt(ext, side, copyManifest, fileExt) {
76
-
const outDir = path.join("./dist", "core-extensions", ext);
77
-
if (!fs.existsSync(outDir)) {
78
-
fs.mkdirSync(outDir, { recursive: true });
209
+
async function buildExt(ext, side, fileExt) {
210
+
const outdir = path.join("./dist", "core-extensions", ext);
211
+
if (!fs.existsSync(outdir)) {
212
+
fs.mkdirSync(outdir, { recursive: true });
213
+
}
214
+
215
+
const entryPoints = [`packages/core-extensions/src/${ext}/${side}.${fileExt}`];
216
+
217
+
const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`;
218
+
if (fs.existsSync(wpModulesDir) && side === "index") {
219
+
const wpModules = fs.opendirSync(wpModulesDir);
220
+
for await (const wpModule of wpModules) {
221
+
if (wpModule.isFile()) {
222
+
entryPoints.push(`packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}`);
223
+
} else {
224
+
for (const fileExt of ["ts", "tsx"]) {
225
+
const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`;
226
+
if (fs.existsSync(path)) {
227
+
entryPoints.push({
228
+
in: path,
229
+
out: `webpackModules/${wpModule.name}`
230
+
});
231
+
}
232
+
}
233
+
}
234
+
}
79
235
}
80
236
81
-
const entryPoint = `packages/core-extensions/src/${ext}/${side}.${fileExt}`;
237
+
const wpImportPlugin = {
238
+
name: "webpackImports",
239
+
setup(build) {
240
+
build.onResolve({ filter: /^@moonlight-mod\/wp\// }, (args) => {
241
+
const wpModule = args.path.replace(/^@moonlight-mod\/wp\//, "");
242
+
return {
243
+
path: wpModule,
244
+
external: true
245
+
};
246
+
});
247
+
}
248
+
};
249
+
250
+
const styleInput = `packages/core-extensions/src/${ext}/style.css`;
251
+
const styleOutput = `dist/core-extensions/${ext}/style.css`;
82
252
83
253
const esbuildConfig = {
84
-
entryPoints: [entryPoint],
85
-
outfile: path.join(outDir, side + ".js"),
254
+
entryPoints,
255
+
outdir,
86
256
87
-
format: "cjs",
257
+
format: "iife",
258
+
globalName: "module.exports",
88
259
platform: "node",
89
260
90
261
treeShaking: true,
···
93
264
94
265
external,
95
266
96
-
plugins: copyManifest
97
-
? [
98
-
copyStaticFiles({
99
-
src: `./packages/core-extensions/src/${ext}/manifest.json`,
100
-
dest: `./dist/core-extensions/${ext}/manifest.json`
101
-
})
102
-
]
103
-
: []
267
+
logOverride: {
268
+
"commonjs-variable-in-esm": "verbose"
269
+
},
270
+
logLevel: "silent",
271
+
plugins: [
272
+
copyStaticFiles({
273
+
src: `./packages/core-extensions/src/${ext}/manifest.json`,
274
+
dest: `./dist/core-extensions/${ext}/manifest.json`
275
+
}),
276
+
...(fs.existsSync(styleInput)
277
+
? [
278
+
copyStaticFiles({
279
+
src: styleInput,
280
+
dest: styleOutput
281
+
})
282
+
]
283
+
: []),
284
+
wpImportPlugin,
285
+
deduplicatedLogging,
286
+
taggedBuildLog(`ext/${ext}`)
287
+
]
104
288
};
105
289
106
290
if (watch) {
···
113
297
114
298
const promises = [];
115
299
116
-
for (const [name, entry] of Object.entries(config)) {
117
-
promises.push(build(name, entry));
118
-
}
300
+
if (clean) {
301
+
fs.rmSync("./dist", { recursive: true, force: true });
302
+
} else if (browser) {
303
+
build("browser", "packages/browser/src/index.ts");
304
+
} else {
305
+
for (const [name, entry] of Object.entries(config)) {
306
+
promises.push(build(name, entry));
307
+
}
119
308
120
-
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
121
-
for (const ext of coreExtensions) {
122
-
let copiedManifest = false;
123
-
124
-
for (const fileExt of ["ts", "tsx"]) {
125
-
for (const type of ["index", "node", "host"]) {
126
-
if (
127
-
fs.existsSync(
128
-
`./packages/core-extensions/src/${ext}/${type}.${fileExt}`
129
-
)
130
-
) {
131
-
promises.push(buildExt(ext, type, !copiedManifest, fileExt));
132
-
copiedManifest = true;
309
+
const coreExtensions = fs.readdirSync("./packages/core-extensions/src");
310
+
for (const ext of coreExtensions) {
311
+
for (const fileExt of ["ts", "tsx"]) {
312
+
for (const type of ["index", "node", "host"]) {
313
+
if (fs.existsSync(`./packages/core-extensions/src/${ext}/${type}.${fileExt}`)) {
314
+
promises.push(buildExt(ext, type, fileExt));
315
+
}
133
316
}
134
317
}
135
318
}
-1
env.d.ts
-1
env.d.ts
···
1
-
/// <reference types="./packages/types/src/index.d.ts" />
+25
eslint.config.mjs
+25
eslint.config.mjs
···
1
+
import config from "@moonlight-mod/eslint-config";
2
+
3
+
export default [
4
+
...config,
5
+
{
6
+
rules: {
7
+
// baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types"
8
+
"no-restricted-imports": [
9
+
"error",
10
+
{
11
+
patterns: [
12
+
{
13
+
group: ["types/*"],
14
+
message: "Use @moonlight-mod/types instead"
15
+
},
16
+
{
17
+
group: ["core/*"],
18
+
message: "Use @moonlight-mod/core instead"
19
+
}
20
+
]
21
+
}
22
+
]
23
+
}
24
+
}
25
+
];
+61
flake.lock
+61
flake.lock
···
1
+
{
2
+
"nodes": {
3
+
"flake-utils": {
4
+
"inputs": {
5
+
"systems": "systems"
6
+
},
7
+
"locked": {
8
+
"lastModified": 1701680307,
9
+
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
10
+
"owner": "numtide",
11
+
"repo": "flake-utils",
12
+
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
13
+
"type": "github"
14
+
},
15
+
"original": {
16
+
"owner": "numtide",
17
+
"repo": "flake-utils",
18
+
"type": "github"
19
+
}
20
+
},
21
+
"nixpkgs": {
22
+
"locked": {
23
+
"lastModified": 1744232761,
24
+
"narHash": "sha256-gbl9hE39nQRpZaLjhWKmEu5ejtQsgI5TWYrIVVJn30U=",
25
+
"owner": "NixOS",
26
+
"repo": "nixpkgs",
27
+
"rev": "f675531bc7e6657c10a18b565cfebd8aa9e24c14",
28
+
"type": "github"
29
+
},
30
+
"original": {
31
+
"owner": "NixOS",
32
+
"ref": "nixos-unstable",
33
+
"repo": "nixpkgs",
34
+
"type": "github"
35
+
}
36
+
},
37
+
"root": {
38
+
"inputs": {
39
+
"flake-utils": "flake-utils",
40
+
"nixpkgs": "nixpkgs"
41
+
}
42
+
},
43
+
"systems": {
44
+
"locked": {
45
+
"lastModified": 1681028828,
46
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
47
+
"owner": "nix-systems",
48
+
"repo": "default",
49
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
50
+
"type": "github"
51
+
},
52
+
"original": {
53
+
"owner": "nix-systems",
54
+
"repo": "default",
55
+
"type": "github"
56
+
}
57
+
}
58
+
},
59
+
"root": "root",
60
+
"version": 7
61
+
}
+31
flake.nix
+31
flake.nix
···
1
+
{
2
+
description = "Yet another Discord mod";
3
+
4
+
inputs = {
5
+
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
6
+
flake-utils.url = "github:numtide/flake-utils";
7
+
};
8
+
9
+
outputs = { self, nixpkgs, flake-utils }:
10
+
let overlay = import ./nix/overlay.nix { };
11
+
in flake-utils.lib.eachDefaultSystem (system:
12
+
let
13
+
pkgs = import nixpkgs {
14
+
inherit system;
15
+
config.allowUnfree = true;
16
+
overlays = [ overlay ];
17
+
};
18
+
in {
19
+
# Don't use these unless you're testing things
20
+
packages.default = pkgs.moonlight-mod;
21
+
packages.moonlight-mod = pkgs.moonlight-mod;
22
+
23
+
packages.discord = pkgs.discord;
24
+
packages.discord-ptb = pkgs.discord-ptb;
25
+
packages.discord-canary = pkgs.discord-canary;
26
+
packages.discord-development = pkgs.discord-development;
27
+
}) // {
28
+
overlays.default = overlay;
29
+
homeModules.default = ./nix/home-manager.nix;
30
+
};
31
+
}
img/wordmark-light.png
img/wordmark-light.png
This is a binary file and will not be displayed.
+51
nix/default.nix
+51
nix/default.nix
···
1
+
{
2
+
lib,
3
+
stdenv,
4
+
nodejs_22,
5
+
pnpm_10,
6
+
}:
7
+
8
+
stdenv.mkDerivation (finalAttrs: {
9
+
pname = "moonlight";
10
+
version = (builtins.fromJSON (builtins.readFile ./../package.json)).version;
11
+
12
+
src = ./..;
13
+
14
+
nativeBuildInputs = [
15
+
nodejs_22
16
+
pnpm_10.configHook
17
+
];
18
+
19
+
pnpmDeps = pnpm_10.fetchDeps {
20
+
inherit (finalAttrs) pname version src;
21
+
hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU=";
22
+
};
23
+
24
+
env = {
25
+
NODE_ENV = "production";
26
+
MOONLIGHT_VERSION = "v${finalAttrs.version}";
27
+
};
28
+
29
+
buildPhase = ''
30
+
runHook preBuild
31
+
32
+
pnpm run build
33
+
34
+
runHook postBuild
35
+
'';
36
+
37
+
installPhase = ''
38
+
runHook preInstall
39
+
40
+
cp -r dist $out
41
+
42
+
runHook postInstall
43
+
'';
44
+
45
+
meta = with lib; {
46
+
description = "Yet another Discord mod";
47
+
homepage = "https://moonlight-mod.github.io/";
48
+
license = licenses.lgpl3;
49
+
maintainers = with maintainers; [ notnite ];
50
+
};
51
+
})
+56
nix/home-manager.nix
+56
nix/home-manager.nix
···
1
+
{ config, lib, pkgs, ... }:
2
+
3
+
let cfg = config.programs.moonlight-mod;
4
+
in {
5
+
options.programs.moonlight-mod = {
6
+
enable = lib.mkEnableOption "Yet another Discord mod";
7
+
8
+
configs = let
9
+
# TODO: type this
10
+
type = lib.types.nullOr (lib.types.attrs);
11
+
default = null;
12
+
in {
13
+
stable = lib.mkOption {
14
+
inherit type default;
15
+
description = "Configuration for Discord Stable";
16
+
};
17
+
18
+
ptb = lib.mkOption {
19
+
inherit type default;
20
+
description = "Configuration for Discord PTB";
21
+
};
22
+
23
+
canary = lib.mkOption {
24
+
inherit type default;
25
+
description = "Configuration for Discord Canary";
26
+
};
27
+
28
+
development = lib.mkOption {
29
+
inherit type default;
30
+
description = "Configuration for Discord Development";
31
+
};
32
+
};
33
+
};
34
+
35
+
config = lib.mkIf cfg.enable {
36
+
xdg.configFile."moonlight-mod/stable.json" =
37
+
lib.mkIf (cfg.configs.stable != null) {
38
+
text = builtins.toJSON cfg.configs.stable;
39
+
};
40
+
41
+
xdg.configFile."moonlight-mod/ptb.json" =
42
+
lib.mkIf (cfg.configs.ptb != null) {
43
+
text = builtins.toJSON cfg.configs.ptb;
44
+
};
45
+
46
+
xdg.configFile."moonlight-mod/canary.json" =
47
+
lib.mkIf (cfg.configs.canary != null) {
48
+
text = builtins.toJSON cfg.configs.canary;
49
+
};
50
+
51
+
xdg.configFile."moonlight-mod/development.json" =
52
+
lib.mkIf (cfg.configs.development != null) {
53
+
text = builtins.toJSON cfg.configs.development;
54
+
};
55
+
};
56
+
}
+57
nix/overlay.nix
+57
nix/overlay.nix
···
1
+
{ ... }:
2
+
3
+
let
4
+
nameTable = {
5
+
discord = "Discord";
6
+
discord-ptb = "DiscordPTB";
7
+
discord-canary = "DiscordCanary";
8
+
discord-development = "DiscordDevelopment";
9
+
};
10
+
11
+
darwinNameTable = {
12
+
discord = "Discord";
13
+
discord-ptb = "Discord PTB";
14
+
discord-canary = "Discord Canary";
15
+
discord-development = "Discord Development";
16
+
};
17
+
18
+
mkOverride = prev: moonlight: name:
19
+
let discord = prev.${name};
20
+
in discord.overrideAttrs (old: {
21
+
installPhase = let
22
+
folderName = nameTable.${name};
23
+
darwinFolderName = darwinNameTable.${name};
24
+
25
+
injected = ''
26
+
require("${moonlight}/injector").inject(
27
+
require("path").join(__dirname, "../_app.asar")
28
+
);
29
+
'';
30
+
31
+
packageJson = ''
32
+
{"name":"${name}","main":"./injector.js","private":true}
33
+
'';
34
+
35
+
in old.installPhase + "\n" + ''
36
+
resources="$out/opt/${folderName}/resources"
37
+
if [ ! -d "$resources" ]; then
38
+
resources="$out/Applications/${darwinFolderName}.app/Contents/Resources"
39
+
fi
40
+
41
+
mv "$resources/app.asar" "$resources/_app.asar"
42
+
mkdir -p "$resources/app"
43
+
44
+
cat > "$resources/app/injector.js" <<EOF
45
+
${injected}
46
+
EOF
47
+
48
+
echo '${packageJson}' > "$resources/app/package.json"
49
+
'';
50
+
});
51
+
in final: prev: rec {
52
+
moonlight-mod = final.callPackage ./default.nix { };
53
+
discord = mkOverride prev moonlight-mod "discord";
54
+
discord-ptb = mkOverride prev moonlight-mod "discord-ptb";
55
+
discord-canary = mkOverride prev moonlight-mod "discord-canary";
56
+
discord-development = mkOverride prev moonlight-mod "discord-development";
57
+
}
+30
-4
package.json
+30
-4
package.json
···
1
1
{
2
2
"name": "moonlight",
3
-
"version": "1.0.1",
3
+
"version": "1.3.14",
4
+
"packageManager": "pnpm@10.7.1",
4
5
"description": "Yet another Discord mod",
6
+
"license": "LGPL-3.0-or-later",
5
7
"homepage": "https://moonlight-mod.github.io/",
6
8
"repository": {
7
9
"type": "git",
···
10
12
"bugs": {
11
13
"url": "https://github.com/moonlight-mod/moonlight/issues"
12
14
},
15
+
"engineStrict": true,
16
+
"engines": {
17
+
"node": ">=22",
18
+
"pnpm": ">=10",
19
+
"npm": "pnpm",
20
+
"yarn": "pnpm"
21
+
},
13
22
"scripts": {
14
23
"build": "node build.mjs",
15
-
"dev": "node build.mjs --watch"
24
+
"dev": "node build.mjs --watch",
25
+
"clean": "node build.mjs --clean",
26
+
"browser": "node build.mjs --browser",
27
+
"browser-mv2": "node build.mjs --browser --mv2",
28
+
"lint": "eslint packages",
29
+
"lint:fix": "pnpm lint --fix",
30
+
"lint:report": "pnpm lint --output-file eslint_report.json --format json",
31
+
"typecheck": "tsc --noEmit",
32
+
"check": "pnpm run lint && pnpm run typecheck",
33
+
"prepare": "husky install",
34
+
"updates": "pnpm taze -r"
16
35
},
17
36
"devDependencies": {
18
-
"esbuild": "^0.19.3",
19
-
"esbuild-copy-static-files": "^0.1.0"
37
+
"@moonlight-mod/eslint-config": "catalog:dev",
38
+
"@types/node": "catalog:dev",
39
+
"esbuild": "catalog:dev",
40
+
"esbuild-copy-static-files": "catalog:dev",
41
+
"eslint": "catalog:dev",
42
+
"husky": "catalog:dev",
43
+
"prettier": "catalog:dev",
44
+
"taze": "catalog:dev",
45
+
"typescript": "catalog:dev"
20
46
}
21
47
}
+14
packages/browser/blockLoading.json
+14
packages/browser/blockLoading.json
+46
packages/browser/manifest.json
+46
packages/browser/manifest.json
···
1
+
{
2
+
"$schema": "https://json.schemastore.org/chrome-manifest",
3
+
"manifest_version": 3,
4
+
"name": "moonlight",
5
+
"description": "Yet another Discord mod",
6
+
"version": "1.3.14",
7
+
"permissions": ["declarativeNetRequestWithHostAccess", "webRequest", "scripting", "webNavigation"],
8
+
"host_permissions": [
9
+
"https://moonlight-mod.github.io/*",
10
+
"https://api.github.com/*",
11
+
"https://*.discord.com/*",
12
+
"https://*.discordapp.com/*"
13
+
],
14
+
"content_scripts": [
15
+
{
16
+
"js": ["index.js"],
17
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"],
18
+
"run_at": "document_start",
19
+
"world": "MAIN"
20
+
}
21
+
],
22
+
"declarative_net_request": {
23
+
"rule_resources": [
24
+
{
25
+
"id": "modifyResponseHeaders",
26
+
"enabled": true,
27
+
"path": "modifyResponseHeaders.json"
28
+
},
29
+
{
30
+
"id": "blockLoading",
31
+
"enabled": true,
32
+
"path": "blockLoading.json"
33
+
}
34
+
]
35
+
},
36
+
"background": {
37
+
"service_worker": "background.js",
38
+
"type": "module"
39
+
},
40
+
"web_accessible_resources": [
41
+
{
42
+
"resources": ["index.js"],
43
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"]
44
+
}
45
+
]
46
+
}
+28
packages/browser/manifestv2.json
+28
packages/browser/manifestv2.json
···
1
+
{
2
+
"$schema": "https://json.schemastore.org/chrome-manifest",
3
+
"manifest_version": 2,
4
+
"name": "moonlight",
5
+
"description": "Yet another Discord mod",
6
+
"version": "1.3.14",
7
+
"permissions": [
8
+
"webRequest",
9
+
"webRequestBlocking",
10
+
"scripting",
11
+
"webNavigation",
12
+
"https://*.discord.com/*",
13
+
"https://*.discordapp.com/*",
14
+
"https://moonlight-mod.github.io/*",
15
+
"https://api.github.com/*"
16
+
],
17
+
"background": {
18
+
"scripts": ["background.js"]
19
+
},
20
+
"content_scripts": [
21
+
{
22
+
"js": ["index.js"],
23
+
"matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"],
24
+
"run_at": "document_start",
25
+
"world": "MAIN"
26
+
}
27
+
]
28
+
}
+19
packages/browser/modifyResponseHeaders.json
+19
packages/browser/modifyResponseHeaders.json
···
1
+
[
2
+
{
3
+
"id": 1,
4
+
"priority": 2,
5
+
"action": {
6
+
"type": "modifyHeaders",
7
+
"responseHeaders": [
8
+
{
9
+
"header": "Content-Security-Policy",
10
+
"operation": "remove"
11
+
}
12
+
]
13
+
},
14
+
"condition": {
15
+
"resourceTypes": ["main_frame"],
16
+
"requestDomains": ["discord.com"]
17
+
}
18
+
}
19
+
]
+21
packages/browser/package.json
+21
packages/browser/package.json
···
1
+
{
2
+
"name": "@moonlight-mod/browser",
3
+
"private": true,
4
+
"engines": {
5
+
"node": ">=22",
6
+
"pnpm": ">=10",
7
+
"npm": "pnpm",
8
+
"yarn": "pnpm"
9
+
},
10
+
"dependencies": {
11
+
"@moonlight-mod/core": "workspace:*",
12
+
"@moonlight-mod/types": "workspace:*",
13
+
"@moonlight-mod/web-preload": "workspace:*",
14
+
"@zenfs/core": "catalog:prod",
15
+
"@zenfs/dom": "catalog:prod"
16
+
},
17
+
"engineStrict": true,
18
+
"devDependencies": {
19
+
"@types/chrome": "catalog:dev"
20
+
}
21
+
}
+84
packages/browser/src/background-mv2.js
+84
packages/browser/src/background-mv2.js
···
1
+
/* eslint-disable no-console */
2
+
/* eslint-disable no-undef */
3
+
4
+
const scriptUrls = ["web.", "sentry."];
5
+
let blockedScripts = new Set();
6
+
7
+
chrome.webRequest.onBeforeRequest.addListener(
8
+
async (details) => {
9
+
if (details.tabId === -1) return;
10
+
11
+
const url = new URL(details.url);
12
+
const hasUrl = scriptUrls.some((scriptUrl) => {
13
+
return (
14
+
details.url.includes(scriptUrl) &&
15
+
!url.searchParams.has("inj") &&
16
+
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"))
17
+
);
18
+
});
19
+
if (hasUrl) blockedScripts.add(details.url);
20
+
21
+
if (blockedScripts.size === scriptUrls.length) {
22
+
const blockedScriptsCopy = Array.from(blockedScripts);
23
+
blockedScripts.clear();
24
+
25
+
setTimeout(async () => {
26
+
console.log("Starting moonlight");
27
+
await chrome.scripting.executeScript({
28
+
target: { tabId: details.tabId },
29
+
world: "MAIN",
30
+
args: [blockedScriptsCopy],
31
+
func: async (blockedScripts) => {
32
+
console.log("Initializing moonlight");
33
+
try {
34
+
await window._moonlightBrowserInit();
35
+
} catch (e) {
36
+
console.error(e);
37
+
}
38
+
39
+
console.log("Readding scripts");
40
+
try {
41
+
const scripts = [...document.querySelectorAll("script")].filter(
42
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
43
+
);
44
+
45
+
blockedScripts.reverse();
46
+
for (const url of blockedScripts) {
47
+
if (url.includes("/sentry.")) continue;
48
+
49
+
const script = scripts.find((script) => url.includes(script.src));
50
+
const newScript = document.createElement("script");
51
+
for (const attr of script.attributes) {
52
+
if (attr.name === "src") attr.value += "?inj";
53
+
newScript.setAttribute(attr.name, attr.value);
54
+
}
55
+
script.remove();
56
+
document.documentElement.appendChild(newScript);
57
+
}
58
+
} catch (e) {
59
+
console.error(e);
60
+
}
61
+
}
62
+
});
63
+
}, 0);
64
+
}
65
+
66
+
if (hasUrl) return { cancel: true };
67
+
},
68
+
{
69
+
urls: ["https://*.discord.com/assets/*.js", "https://*.discordapp.com/assets/*.js"]
70
+
},
71
+
["blocking"]
72
+
);
73
+
74
+
chrome.webRequest.onHeadersReceived.addListener(
75
+
(details) => {
76
+
return {
77
+
responseHeaders: details.responseHeaders.filter(
78
+
(header) => header.name.toLowerCase() !== "content-security-policy"
79
+
)
80
+
};
81
+
},
82
+
{ urls: ["https://*.discord.com/*", "https://*.discordapp.com/*"] },
83
+
["blocking", "responseHeaders"]
84
+
);
+111
packages/browser/src/background.js
+111
packages/browser/src/background.js
···
1
+
/* eslint-disable no-console */
2
+
/* eslint-disable no-undef */
3
+
4
+
const scriptUrls = ["web.", "sentry."];
5
+
let blockedScripts = new Set();
6
+
7
+
chrome.webNavigation.onBeforeNavigate.addListener(async (details) => {
8
+
const url = new URL(details.url);
9
+
if (
10
+
!url.searchParams.has("inj") &&
11
+
(url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com"))
12
+
) {
13
+
console.log("Enabling block ruleset");
14
+
await chrome.declarativeNetRequest.updateEnabledRulesets({
15
+
enableRulesetIds: ["modifyResponseHeaders", "blockLoading"]
16
+
});
17
+
}
18
+
});
19
+
20
+
chrome.webRequest.onBeforeRequest.addListener(
21
+
async (details) => {
22
+
if (details.tabId === -1) return;
23
+
24
+
const url = new URL(details.url);
25
+
const hasUrl = scriptUrls.some((scriptUrl) => {
26
+
return (
27
+
details.url.includes(scriptUrl) &&
28
+
!url.searchParams.has("inj") &&
29
+
(url.hostname.endsWith("discord.com") || url.hostname.endsWith("discordapp.com"))
30
+
);
31
+
});
32
+
33
+
if (hasUrl) blockedScripts.add(details.url);
34
+
35
+
if (blockedScripts.size === scriptUrls.length) {
36
+
const blockedScriptsCopy = Array.from(blockedScripts);
37
+
blockedScripts.clear();
38
+
39
+
console.log("Running moonlight script");
40
+
try {
41
+
await chrome.scripting.executeScript({
42
+
target: { tabId: details.tabId },
43
+
world: "MAIN",
44
+
files: ["index.js"]
45
+
});
46
+
} catch (e) {
47
+
console.error(e);
48
+
}
49
+
50
+
console.log("Initializing moonlight");
51
+
try {
52
+
await chrome.scripting.executeScript({
53
+
target: { tabId: details.tabId },
54
+
world: "MAIN",
55
+
func: async () => {
56
+
try {
57
+
await window._moonlightBrowserInit();
58
+
} catch (e) {
59
+
console.error(e);
60
+
}
61
+
}
62
+
});
63
+
} catch (e) {
64
+
console.log(e);
65
+
}
66
+
67
+
console.log("Disabling block ruleset");
68
+
try {
69
+
await chrome.declarativeNetRequest.updateEnabledRulesets({
70
+
disableRulesetIds: ["blockLoading"],
71
+
enableRulesetIds: ["modifyResponseHeaders"]
72
+
});
73
+
} catch (e) {
74
+
console.error(e);
75
+
}
76
+
77
+
console.log("Readding scripts");
78
+
try {
79
+
await chrome.scripting.executeScript({
80
+
target: { tabId: details.tabId },
81
+
world: "MAIN",
82
+
args: [blockedScriptsCopy],
83
+
func: async (blockedScripts) => {
84
+
const scripts = [...document.querySelectorAll("script")].filter(
85
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
86
+
);
87
+
88
+
blockedScripts.reverse();
89
+
for (const url of blockedScripts) {
90
+
if (url.includes("/sentry.")) continue;
91
+
92
+
const script = scripts.find((script) => url.includes(script.src));
93
+
const newScript = document.createElement("script");
94
+
for (const attr of script.attributes) {
95
+
if (attr.name === "src") attr.value += "?inj";
96
+
newScript.setAttribute(attr.name, attr.value);
97
+
}
98
+
script.remove();
99
+
document.documentElement.appendChild(newScript);
100
+
}
101
+
}
102
+
});
103
+
} catch (e) {
104
+
console.error(e);
105
+
}
106
+
}
107
+
},
108
+
{
109
+
urls: ["*://*.discord.com/assets/*.js", "*://*.discordapp.com/assets/*.js"]
110
+
}
111
+
);
+157
packages/browser/src/index.ts
+157
packages/browser/src/index.ts
···
1
+
import "@moonlight-mod/web-preload";
2
+
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
3
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
4
+
import { getExtensions } from "@moonlight-mod/core/extension";
5
+
import { loadExtensions } from "@moonlight-mod/core/extension/loader";
6
+
import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types";
7
+
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
8
+
import { IndexedDB } from "@zenfs/dom";
9
+
import { configureSingle } from "@zenfs/core";
10
+
import * as fs from "@zenfs/core/promises";
11
+
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
12
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
13
+
14
+
function getParts(path: string) {
15
+
if (path.startsWith("/")) path = path.substring(1);
16
+
return path.split("/");
17
+
}
18
+
19
+
window._moonlightBrowserInit = async () => {
20
+
delete window._moonlightBrowserInit;
21
+
22
+
// Set up a virtual filesystem with IndexedDB
23
+
await configureSingle({
24
+
backend: IndexedDB,
25
+
storeName: "moonlight-fs"
26
+
});
27
+
28
+
window.moonlightNodeSandboxed = {
29
+
fs: {
30
+
async readFile(path) {
31
+
return new Uint8Array(await fs.readFile(path));
32
+
},
33
+
async readFileString(path) {
34
+
const file = await this.readFile(path);
35
+
return new TextDecoder().decode(file);
36
+
},
37
+
async writeFile(path, data) {
38
+
await fs.writeFile(path, data);
39
+
},
40
+
async writeFileString(path, data) {
41
+
const file = new TextEncoder().encode(data);
42
+
await this.writeFile(path, file);
43
+
},
44
+
async unlink(path) {
45
+
await fs.unlink(path);
46
+
},
47
+
48
+
async readdir(path) {
49
+
return await fs.readdir(path);
50
+
},
51
+
async mkdir(path) {
52
+
const parts = getParts(path);
53
+
for (let i = 0; i < parts.length; i++) {
54
+
const path = this.join(...parts.slice(0, i + 1));
55
+
if (!(await this.exists(path))) await fs.mkdir(path);
56
+
}
57
+
},
58
+
59
+
async rmdir(path) {
60
+
const entries = await this.readdir(path);
61
+
62
+
for (const entry of entries) {
63
+
const fullPath = this.join(path, entry);
64
+
const isFile = await this.isFile(fullPath);
65
+
if (isFile) {
66
+
await this.unlink(fullPath);
67
+
} else {
68
+
await this.rmdir(fullPath);
69
+
}
70
+
}
71
+
72
+
await fs.rmdir(path);
73
+
},
74
+
75
+
async exists(path) {
76
+
return await fs.exists(path);
77
+
},
78
+
async isFile(path) {
79
+
return (await fs.stat(path)).isFile();
80
+
},
81
+
async isDir(path) {
82
+
return (await fs.stat(path)).isDirectory();
83
+
},
84
+
85
+
join(...parts) {
86
+
let str = parts.join("/");
87
+
if (!str.startsWith("/")) str = "/" + str;
88
+
return str;
89
+
},
90
+
dirname(path) {
91
+
const parts = getParts(path);
92
+
return "/" + parts.slice(0, parts.length - 1).join("/");
93
+
}
94
+
},
95
+
// TODO
96
+
addCors(url) {},
97
+
addBlocked(url) {}
98
+
};
99
+
100
+
// Actual loading begins here
101
+
let config = await readConfig();
102
+
initLogger(config);
103
+
104
+
const extensions = await getExtensions();
105
+
const processedExtensions = await loadExtensions(extensions);
106
+
107
+
const moonlightNode: MoonlightNode = {
108
+
get config() {
109
+
return config;
110
+
},
111
+
extensions,
112
+
processedExtensions,
113
+
nativesCache: {},
114
+
isBrowser: true,
115
+
events: createEventEmitter<NodeEventType, NodeEventPayloads>(),
116
+
117
+
version: MOONLIGHT_VERSION,
118
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
119
+
120
+
getConfig(ext) {
121
+
return getConfig(ext, config);
122
+
},
123
+
getConfigOption(ext, name) {
124
+
const manifest = getManifest(extensions, ext);
125
+
return getConfigOption(ext, name, config, manifest?.settings);
126
+
},
127
+
async setConfigOption(ext, name, value) {
128
+
setConfigOption(config, ext, name, value);
129
+
await this.writeConfig(config);
130
+
},
131
+
132
+
getNatives: () => {},
133
+
getLogger: (id: string) => {
134
+
return new Logger(id);
135
+
},
136
+
137
+
getMoonlightDir() {
138
+
return "/";
139
+
},
140
+
getExtensionDir: (ext: string) => {
141
+
return `/extensions/${ext}`;
142
+
},
143
+
144
+
async writeConfig(newConfig) {
145
+
await writeConfig(newConfig);
146
+
config = newConfig;
147
+
this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig);
148
+
}
149
+
};
150
+
151
+
Object.assign(window, {
152
+
moonlightNode
153
+
});
154
+
155
+
// This is set by web-preload for us
156
+
await window._moonlightWebLoad!();
157
+
};
+7
packages/browser/tsconfig.json
+7
packages/browser/tsconfig.json
+7
-1
packages/core/package.json
+7
-1
packages/core/package.json
+57
packages/core/src/asar.ts
+57
packages/core/src/asar.ts
···
1
+
// https://github.com/electron/asar
2
+
// http://formats.kaitai.io/python_pickle/
3
+
import { BinaryReader } from "./util/binary";
4
+
5
+
/*
6
+
The asar format is kinda bad, especially because it uses multiple pickle
7
+
entries. It spams sizes, expecting us to read small buffers and parse those,
8
+
but we can just take it all through at once without having to create multiple
9
+
BinaryReaders. This implementation might be wrong, though.
10
+
11
+
This either has size/offset or files but I can't get the type to cooperate,
12
+
so pretend this is a union.
13
+
*/
14
+
15
+
type AsarEntry = {
16
+
size: number;
17
+
offset: `${number}`; // who designed this
18
+
19
+
files?: Record<string, AsarEntry>;
20
+
};
21
+
22
+
export default function extractAsar(file: ArrayBuffer) {
23
+
const array = new Uint8Array(file);
24
+
const br = new BinaryReader(array);
25
+
26
+
// two uints, one containing the number '4', to signify that the other uint takes up 4 bytes
27
+
// bravo, electron, bravo
28
+
const _payloadSize = br.readUInt32();
29
+
const _headerSize = br.readInt32();
30
+
31
+
const headerStringStart = br.position;
32
+
const headerStringSize = br.readUInt32(); // How big the block is
33
+
const actualStringSize = br.readUInt32(); // How big the string in that block is
34
+
35
+
const base = headerStringStart + headerStringSize + 4;
36
+
37
+
const string = br.readString(actualStringSize);
38
+
const header: AsarEntry = JSON.parse(string);
39
+
40
+
const ret: Record<string, Uint8Array> = {};
41
+
function addDirectory(dir: AsarEntry, path: string) {
42
+
for (const [name, data] of Object.entries(dir.files!)) {
43
+
const fullName = path + "/" + name;
44
+
if (data.files != null) {
45
+
addDirectory(data, fullName);
46
+
} else {
47
+
br.position = base + parseInt(data.offset);
48
+
const file = br.read(data.size);
49
+
ret[fullName] = file;
50
+
}
51
+
}
52
+
}
53
+
54
+
addDirectory(header, "");
55
+
56
+
return ret;
57
+
}
+38
-33
packages/core/src/config.ts
+38
-33
packages/core/src/config.ts
···
1
-
import { Config, constants } from "@moonlight-mod/types";
2
-
import requireImport from "./util/import";
1
+
import { Config } from "@moonlight-mod/types";
3
2
import { getConfigPath } from "./util/data";
3
+
import * as constants from "@moonlight-mod/types/constants";
4
+
import Logger from "./util/logger";
5
+
6
+
const logger = new Logger("core/config");
4
7
5
8
const defaultConfig: Config = {
6
-
extensions: {},
7
-
repositories: ["https://moonlight-mod.github.io/extensions/repo.json"]
9
+
// If you're updating this, update `builtinExtensions` in constants as well
10
+
extensions: {
11
+
moonbase: true,
12
+
disableSentry: true,
13
+
noTrack: true,
14
+
noHideToken: true
15
+
},
16
+
repositories: [constants.mainRepo]
8
17
};
9
18
10
-
export function writeConfig(config: Config) {
11
-
const fs = requireImport("fs");
12
-
const configPath = getConfigPath();
13
-
fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
14
-
}
15
-
16
-
function readConfigNode(): Config {
17
-
const fs = requireImport("fs");
18
-
const configPath = getConfigPath();
19
-
20
-
if (!fs.existsSync(configPath)) {
21
-
writeConfig(defaultConfig);
22
-
return defaultConfig;
19
+
export async function writeConfig(config: Config) {
20
+
try {
21
+
const configPath = await getConfigPath();
22
+
await moonlightNodeSandboxed.fs.writeFileString(configPath, JSON.stringify(config, null, 2));
23
+
} catch (e) {
24
+
logger.error("Failed to write config", e);
23
25
}
24
-
25
-
let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8"));
26
-
27
-
// Assign the default values if they don't exist (newly added)
28
-
config = { ...defaultConfig, ...config };
29
-
writeConfig(config);
30
-
31
-
return config;
32
26
}
33
27
34
-
export function readConfig(): Config {
28
+
export async function readConfig(): Promise<Config> {
35
29
webPreload: {
36
30
return moonlightNode.config;
37
31
}
38
32
39
-
nodePreload: {
40
-
return readConfigNode();
41
-
}
33
+
const configPath = await getConfigPath();
34
+
if (!(await moonlightNodeSandboxed.fs.exists(configPath))) {
35
+
await writeConfig(defaultConfig);
36
+
return defaultConfig;
37
+
} else {
38
+
try {
39
+
let config: Config = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(configPath));
40
+
// Assign the default values if they don't exist (newly added)
41
+
config = { ...defaultConfig, ...config };
42
+
await writeConfig(config);
42
43
43
-
injector: {
44
-
return readConfigNode();
44
+
return config;
45
+
} catch (e) {
46
+
logger.error("Failed to read config, falling back to defaults", e);
47
+
// We don't want to write the default config here - if a user is manually
48
+
// editing their config and messes it up, we'll delete it all instead of
49
+
// letting them fix it
50
+
return defaultConfig;
51
+
}
45
52
}
46
-
47
-
throw new Error("Called readConfig() in an impossible environment");
48
53
}
+17
packages/core/src/cors.ts
+17
packages/core/src/cors.ts
···
1
+
const cors: string[] = [];
2
+
const blocked: string[] = [];
3
+
4
+
export function registerCors(url: string) {
5
+
cors.push(url);
6
+
}
7
+
8
+
export function registerBlocked(url: string) {
9
+
blocked.push(url);
10
+
}
11
+
12
+
export function getDynamicCors() {
13
+
return {
14
+
cors,
15
+
blocked
16
+
};
17
+
}
+127
-98
packages/core/src/extension/loader.ts
+127
-98
packages/core/src/extension/loader.ts
···
1
1
import {
2
2
ExtensionWebExports,
3
3
DetectedExtension,
4
-
ProcessedExtensions
4
+
ProcessedExtensions,
5
+
WebpackModuleFunc,
6
+
constants,
7
+
ExtensionManifest,
8
+
ExtensionEnvironment
5
9
} from "@moonlight-mod/types";
6
10
import { readConfig } from "../config";
7
11
import Logger from "../util/logger";
8
-
import { getExtensions } from "../extension";
9
12
import { registerPatch, registerWebpackModule } from "../patch";
10
13
import calculateDependencies from "../util/dependency";
11
14
import { createEventEmitter } from "../util/event";
15
+
import { registerStyles } from "../styles";
16
+
import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event";
12
17
13
18
const logger = new Logger("core/extension/loader");
14
19
15
-
async function loadExt(ext: DetectedExtension) {
16
-
webPreload: {
17
-
if (ext.scripts.web != null) {
18
-
const source =
19
-
ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath;
20
-
const fn = new Function("require", "module", "exports", source);
20
+
function evalIIFE(id: string, source: string): ExtensionWebExports {
21
+
const fn = new Function("require", "module", "exports", source);
22
+
23
+
const module = { id, exports: {} };
24
+
fn.apply(window, [
25
+
() => {
26
+
logger.warn("Attempted to require() from web");
27
+
},
28
+
module,
29
+
module.exports
30
+
]);
31
+
32
+
return module.exports;
33
+
}
34
+
35
+
async function evalEsm(source: string): Promise<ExtensionWebExports> {
36
+
// Data URLs (`data:`) don't seem to work under the CSP, but object URLs do
37
+
const url = URL.createObjectURL(new Blob([source], { type: "text/javascript" }));
38
+
39
+
const module = await import(url);
21
40
22
-
const module = { id: ext.id, exports: {} };
23
-
fn.apply(window, [
24
-
() => {
25
-
logger.warn("Attempted to require() from web");
26
-
},
27
-
module,
28
-
module.exports
29
-
]);
41
+
URL.revokeObjectURL(url);
30
42
31
-
const exports: ExtensionWebExports = module.exports;
32
-
if (exports.patches != null) {
33
-
let idx = 0;
34
-
for (const patch of exports.patches) {
35
-
if (Array.isArray(patch.replace)) {
36
-
for (const replacement of patch.replace) {
37
-
const newPatch = Object.assign({}, patch, {
38
-
replace: replacement
39
-
});
43
+
return module;
44
+
}
45
+
46
+
async function loadExtWeb(ext: DetectedExtension) {
47
+
if (ext.scripts.web != null) {
48
+
const source = ext.scripts.web + `\n//# sourceURL=${ext.id}/web.js`;
49
+
50
+
let exports: ExtensionWebExports;
51
+
52
+
try {
53
+
exports = evalIIFE(ext.id, source);
54
+
} catch {
55
+
logger.trace(`Failed to load IIFE for extension ${ext.id}, trying ESM loading`);
56
+
exports = await evalEsm(source);
57
+
}
40
58
41
-
registerPatch({ ...newPatch, ext: ext.id, id: idx });
42
-
idx++;
43
-
}
44
-
} else {
45
-
registerPatch({ ...patch, ext: ext.id, id: idx });
46
-
idx++;
47
-
}
59
+
if (exports.patches != null) {
60
+
let idx = 0;
61
+
for (const patch of exports.patches) {
62
+
if (Array.isArray(patch.replace)) {
63
+
registerPatch({ ...patch, ext: ext.id, id: idx });
64
+
} else {
65
+
registerPatch({ ...patch, replace: [patch.replace], ext: ext.id, id: idx });
48
66
}
67
+
idx++;
49
68
}
69
+
}
50
70
51
-
if (exports.webpackModules != null) {
52
-
for (const [name, wp] of Object.entries(exports.webpackModules)) {
71
+
if (exports.webpackModules != null) {
72
+
for (const [name, wp] of Object.entries(exports.webpackModules)) {
73
+
if (wp.run == null && ext.scripts.webpackModules?.[name] != null) {
74
+
const source = ext.scripts.webpackModules[name]! + `\n//# sourceURL=${ext.id}/webpackModules/${name}.js`;
75
+
const func = new Function("module", "exports", "require", source) as WebpackModuleFunc;
76
+
registerWebpackModule({
77
+
...wp,
78
+
ext: ext.id,
79
+
id: name,
80
+
run: func
81
+
});
82
+
} else {
53
83
registerWebpackModule({ ...wp, ext: ext.id, id: name });
54
84
}
55
85
}
56
86
}
87
+
88
+
if (exports.styles != null) {
89
+
registerStyles(exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`));
90
+
}
91
+
if (ext.scripts.style != null) {
92
+
registerStyles([`/* ${ext.id}#style.css */ ${ext.scripts.style}`]);
93
+
}
94
+
}
95
+
}
96
+
97
+
async function loadExt(ext: DetectedExtension) {
98
+
webTarget: {
99
+
try {
100
+
await loadExtWeb(ext);
101
+
} catch (e) {
102
+
logger.error(`Failed to load extension "${ext.id}"`, e);
103
+
}
57
104
}
58
105
59
106
nodePreload: {
···
78
125
}
79
126
}
80
127
128
+
export enum ExtensionCompat {
129
+
Compatible,
130
+
InvalidApiLevel,
131
+
InvalidEnvironment
132
+
}
133
+
134
+
export function checkExtensionCompat(manifest: ExtensionManifest): ExtensionCompat {
135
+
let environment;
136
+
webTarget: {
137
+
environment = ExtensionEnvironment.Web;
138
+
}
139
+
nodeTarget: {
140
+
environment = ExtensionEnvironment.Desktop;
141
+
}
142
+
143
+
if (manifest.apiLevel !== constants.apiLevel) return ExtensionCompat.InvalidApiLevel;
144
+
if ((manifest.environment ?? "both") !== "both" && manifest.environment !== environment)
145
+
return ExtensionCompat.InvalidEnvironment;
146
+
return ExtensionCompat.Compatible;
147
+
}
148
+
81
149
/*
82
150
This function resolves extensions and loads them, split into a few stages:
83
151
···
92
160
extensions fires an event on completion, which allows us to await the loading
93
161
of another extension, resolving dependencies & load order effectively.
94
162
*/
95
-
export async function loadExtensions(
96
-
exts: DetectedExtension[]
97
-
): Promise<ProcessedExtensions> {
163
+
export async function loadExtensions(exts: DetectedExtension[]): Promise<ProcessedExtensions> {
164
+
exts = exts.filter((ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible);
165
+
166
+
const config = await readConfig();
98
167
const items = exts
99
168
.map((ext) => {
100
169
return {
···
104
173
})
105
174
.sort((a, b) => a.id.localeCompare(b.id));
106
175
107
-
const [sorted, dependencyGraph] = calculateDependencies(
108
-
items,
109
-
110
-
function fetchDep(id) {
176
+
const [sorted, dependencyGraph] = calculateDependencies(items, {
177
+
fetchDep: (id) => {
111
178
return exts.find((x) => x.id === id) ?? null;
112
179
},
113
180
114
-
function getDeps(item) {
181
+
getDeps: (item) => {
115
182
return item.data.manifest.dependencies ?? [];
116
183
},
117
184
118
-
function getIncompatible(item) {
185
+
getIncompatible: (item) => {
119
186
return item.data.manifest.incompatible ?? [];
120
-
}
121
-
);
122
-
exts = sorted.map((x) => x.data);
123
-
124
-
logger.debug(
125
-
"Implicit dependency stage - extension list:",
126
-
exts.map((x) => x.id)
127
-
);
128
-
const config = readConfig();
129
-
const implicitlyEnabled: string[] = [];
130
-
131
-
function isEnabledInConfig(ext: DetectedExtension) {
132
-
if (implicitlyEnabled.includes(ext.id)) return true;
133
-
134
-
const entry = config.extensions[ext.id];
135
-
if (entry == null) return false;
136
-
137
-
if (entry === true) return true;
138
-
if (typeof entry === "object" && entry.enabled === true) return true;
139
-
140
-
return false;
141
-
}
142
-
143
-
function validateDeps(ext: DetectedExtension) {
144
-
if (isEnabledInConfig(ext)) {
145
-
const deps = dependencyGraph.get(ext.id)!;
146
-
for (const dep of deps.values()) {
147
-
validateDeps(exts.find((e) => e.id === dep)!);
148
-
}
149
-
} else {
150
-
const dependsOnMe = Array.from(dependencyGraph.entries()).filter(
151
-
([, v]) => v?.has(ext.id)
152
-
);
187
+
},
153
188
154
-
if (dependsOnMe.length > 0) {
155
-
logger.debug("Implicitly enabling extension", ext.id);
156
-
implicitlyEnabled.push(ext.id);
157
-
}
189
+
getEnabled: (item) => {
190
+
const entry = config.extensions[item.id];
191
+
if (entry == null) return false;
192
+
if (entry === true) return true;
193
+
if (typeof entry === "object" && entry.enabled === true) return true;
194
+
return false;
158
195
}
159
-
}
160
-
161
-
for (const ext of exts) validateDeps(ext);
162
-
exts = exts.filter((e) => isEnabledInConfig(e));
196
+
});
163
197
164
198
return {
165
-
extensions: exts,
199
+
extensions: sorted.map((x) => x.data),
166
200
dependencyGraph
167
201
};
168
202
}
169
203
170
-
export async function loadProcessedExtensions({
171
-
extensions,
172
-
dependencyGraph
173
-
}: ProcessedExtensions) {
174
-
const eventEmitter = createEventEmitter();
204
+
export async function loadProcessedExtensions({ extensions, dependencyGraph }: ProcessedExtensions) {
205
+
const eventEmitter = createEventEmitter<WebEventType, WebEventPayloads>();
175
206
const finished: Set<string> = new Set();
176
207
177
-
logger.debug(
208
+
logger.trace(
178
209
"Load stage - extension list:",
179
210
extensions.map((x) => x.id)
180
211
);
···
193
224
}
194
225
195
226
function done() {
196
-
eventEmitter.removeEventListener("ext-ready", cb);
227
+
eventEmitter.removeEventListener(WebEventType.ExtensionLoad, cb);
197
228
r();
198
229
}
199
230
200
-
eventEmitter.addEventListener("ext-ready", cb);
231
+
eventEmitter.addEventListener(WebEventType.ExtensionLoad, cb);
201
232
if (finished.has(dep)) done();
202
233
})
203
234
);
204
235
205
236
if (waitPromises.length > 0) {
206
-
logger.debug(
207
-
`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`
208
-
);
237
+
logger.debug(`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`);
209
238
await Promise.all(waitPromises);
210
239
}
211
240
···
213
242
await loadExt(ext);
214
243
215
244
finished.add(ext.id);
216
-
eventEmitter.dispatchEvent("ext-ready", ext.id);
245
+
eventEmitter.dispatchEvent(WebEventType.ExtensionLoad, ext.id);
217
246
logger.debug(`Loaded "${ext.id}"`);
218
247
}
219
248
220
-
webPreload: {
249
+
webTarget: {
221
250
for (const ext of extensions) {
222
251
moonlight.enabledExtensions.add(ext.id);
223
252
}
+144
-67
packages/core/src/extension.ts
+144
-67
packages/core/src/extension.ts
···
1
-
import {
2
-
ExtensionManifest,
3
-
DetectedExtension,
4
-
ExtensionLoadSource,
5
-
constants
6
-
} from "@moonlight-mod/types";
1
+
import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types";
7
2
import { readConfig } from "./config";
8
-
import requireImport from "./util/import";
9
3
import { getCoreExtensionsPath, getExtensionsPath } from "./util/data";
4
+
import Logger from "./util/logger";
5
+
6
+
const logger = new Logger("core/extension");
7
+
8
+
async function findManifests(dir: string): Promise<string[]> {
9
+
const ret = [];
10
+
11
+
if (await moonlightNodeSandboxed.fs.exists(dir)) {
12
+
for (const file of await moonlightNodeSandboxed.fs.readdir(dir)) {
13
+
const path = moonlightNodeSandboxed.fs.join(dir, file);
14
+
if (file === "manifest.json") {
15
+
ret.push(path);
16
+
}
17
+
18
+
if (!(await moonlightNodeSandboxed.fs.isFile(path))) {
19
+
ret.push(...(await findManifests(path)));
20
+
}
21
+
}
22
+
}
10
23
11
-
function loadDetectedExtensions(
24
+
return ret;
25
+
}
26
+
27
+
async function loadDetectedExtensions(
12
28
dir: string,
13
-
type: ExtensionLoadSource
14
-
): DetectedExtension[] {
15
-
const fs = requireImport("fs");
16
-
const path = requireImport("path");
29
+
type: ExtensionLoadSource,
30
+
seen: Set<string>
31
+
): Promise<DetectedExtension[]> {
17
32
const ret: DetectedExtension[] = [];
18
33
19
-
const glob = require("glob");
20
-
const manifests = glob.sync(dir + "/**/manifest.json");
21
-
34
+
const manifests = await findManifests(dir);
22
35
for (const manifestPath of manifests) {
23
-
if (!fs.existsSync(manifestPath)) continue;
24
-
const dir = path.dirname(manifestPath);
36
+
try {
37
+
if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue;
38
+
const dir = moonlightNodeSandboxed.fs.dirname(manifestPath);
25
39
26
-
const manifest: ExtensionManifest = JSON.parse(
27
-
fs.readFileSync(manifestPath, "utf8")
28
-
);
40
+
const manifest: ExtensionManifest = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(manifestPath));
41
+
if (seen.has(manifest.id)) {
42
+
logger.warn(`Duplicate extension found, skipping: ${manifest.id}`);
43
+
continue;
44
+
}
45
+
seen.add(manifest.id);
46
+
47
+
const webPath = moonlightNodeSandboxed.fs.join(dir, "index.js");
48
+
const nodePath = moonlightNodeSandboxed.fs.join(dir, "node.js");
49
+
const hostPath = moonlightNodeSandboxed.fs.join(dir, "host.js");
50
+
51
+
// if none exist (empty manifest) don't give a shit
52
+
if (
53
+
!moonlightNodeSandboxed.fs.exists(webPath) &&
54
+
!moonlightNodeSandboxed.fs.exists(nodePath) &&
55
+
!moonlightNodeSandboxed.fs.exists(hostPath)
56
+
) {
57
+
continue;
58
+
}
59
+
60
+
const web = (await moonlightNodeSandboxed.fs.exists(webPath))
61
+
? await moonlightNodeSandboxed.fs.readFileString(webPath)
62
+
: undefined;
63
+
64
+
let url: string | undefined = undefined;
65
+
const urlPath = moonlightNodeSandboxed.fs.join(dir, constants.repoUrlFile);
66
+
if (type === ExtensionLoadSource.Normal && (await moonlightNodeSandboxed.fs.exists(urlPath))) {
67
+
url = await moonlightNodeSandboxed.fs.readFileString(urlPath);
68
+
}
29
69
30
-
const webPath = path.join(dir, "index.js");
31
-
const nodePath = path.join(dir, "node.js");
32
-
const hostPath = path.join(dir, "host.js");
70
+
const wpModules: Record<string, string> = {};
71
+
const wpModulesPath = moonlightNodeSandboxed.fs.join(dir, "webpackModules");
72
+
if (await moonlightNodeSandboxed.fs.exists(wpModulesPath)) {
73
+
const wpModulesFile = await moonlightNodeSandboxed.fs.readdir(wpModulesPath);
33
74
34
-
// if none exist (empty manifest) don't give a shit
35
-
if (
36
-
!fs.existsSync(webPath) &&
37
-
!fs.existsSync(nodePath) &&
38
-
!fs.existsSync(hostPath)
39
-
) {
40
-
continue;
75
+
for (const wpModuleFile of wpModulesFile) {
76
+
if (wpModuleFile.endsWith(".js")) {
77
+
wpModules[wpModuleFile.replace(".js", "")] = await moonlightNodeSandboxed.fs.readFileString(
78
+
moonlightNodeSandboxed.fs.join(wpModulesPath, wpModuleFile)
79
+
);
80
+
}
81
+
}
82
+
}
83
+
84
+
const stylePath = moonlightNodeSandboxed.fs.join(dir, "style.css");
85
+
86
+
ret.push({
87
+
id: manifest.id,
88
+
manifest,
89
+
source: {
90
+
type,
91
+
url
92
+
},
93
+
scripts: {
94
+
web,
95
+
webPath: web != null ? webPath : undefined,
96
+
webpackModules: wpModules,
97
+
nodePath: (await moonlightNodeSandboxed.fs.exists(nodePath)) ? nodePath : undefined,
98
+
hostPath: (await moonlightNodeSandboxed.fs.exists(hostPath)) ? hostPath : undefined,
99
+
style: (await moonlightNodeSandboxed.fs.exists(stylePath))
100
+
? await moonlightNodeSandboxed.fs.readFileString(stylePath)
101
+
: undefined
102
+
}
103
+
});
104
+
} catch (err) {
105
+
logger.error(`Failed to load extension from "${manifestPath}":`, err);
41
106
}
107
+
}
42
108
43
-
const web = fs.existsSync(webPath)
44
-
? fs.readFileSync(webPath, "utf8")
45
-
: undefined;
109
+
return ret;
110
+
}
111
+
112
+
async function getExtensionsNative(): Promise<DetectedExtension[]> {
113
+
const config = await readConfig();
114
+
const res = [];
115
+
const seen = new Set<string>();
116
+
117
+
res.push(...(await loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core, seen)));
118
+
119
+
for (const devSearchPath of config.devSearchPaths ?? []) {
120
+
res.push(...(await loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer, seen)));
121
+
}
122
+
123
+
res.push(...(await loadDetectedExtensions(await getExtensionsPath(), ExtensionLoadSource.Normal, seen)));
124
+
125
+
return res;
126
+
}
127
+
128
+
async function getExtensionsBrowser(): Promise<DetectedExtension[]> {
129
+
const ret: DetectedExtension[] = [];
130
+
const seen = new Set<string>();
131
+
132
+
const coreExtensionsFs: Record<string, string> = JSON.parse(_moonlight_coreExtensionsStr);
133
+
const coreExtensions = Array.from(new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])));
134
+
135
+
for (const ext of coreExtensions) {
136
+
if (!coreExtensionsFs[`${ext}/index.js`]) continue;
137
+
const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]);
138
+
const web = coreExtensionsFs[`${ext}/index.js`];
46
139
47
-
let url: string | undefined = undefined;
48
-
const urlPath = path.join(dir, constants.repoUrlFile);
49
-
if (type == ExtensionLoadSource.Normal && fs.existsSync(urlPath)) {
50
-
url = fs.readFileSync(urlPath, "utf8");
140
+
const wpModules: Record<string, string> = {};
141
+
const wpModulesPath = `${ext}/webpackModules`;
142
+
for (const wpModuleFile of Object.keys(coreExtensionsFs)) {
143
+
if (wpModuleFile.startsWith(wpModulesPath)) {
144
+
wpModules[wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "")] = coreExtensionsFs[wpModuleFile];
145
+
}
51
146
}
52
147
53
148
ret.push({
54
149
id: manifest.id,
55
150
manifest,
56
151
source: {
57
-
type,
58
-
url
152
+
type: ExtensionLoadSource.Core
59
153
},
60
154
scripts: {
61
155
web,
62
-
webPath: web != null ? webPath : undefined,
63
-
nodePath: fs.existsSync(nodePath) ? nodePath : undefined,
64
-
hostPath: fs.existsSync(hostPath) ? hostPath : undefined
156
+
webpackModules: wpModules,
157
+
style: coreExtensionsFs[`${ext}/style.css`]
65
158
}
66
159
});
160
+
seen.add(manifest.id);
67
161
}
68
162
69
-
return ret;
70
-
}
71
-
72
-
function getExtensionsNative(): DetectedExtension[] {
73
-
const config = readConfig();
74
-
const res = [];
75
-
76
-
res.push(
77
-
...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core)
78
-
);
79
-
80
-
res.push(
81
-
...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal)
82
-
);
83
-
84
-
for (const devSearchPath of config.devSearchPaths ?? []) {
85
-
res.push(
86
-
...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer)
87
-
);
163
+
if (await moonlightNodeSandboxed.fs.exists("/extensions")) {
164
+
ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen)));
88
165
}
89
166
90
-
return res;
167
+
return ret;
91
168
}
92
169
93
-
export function getExtensions(): DetectedExtension[] {
170
+
export async function getExtensions(): Promise<DetectedExtension[]> {
94
171
webPreload: {
95
172
return moonlightNode.extensions;
96
173
}
97
174
98
-
nodePreload: {
99
-
return getExtensionsNative();
175
+
browser: {
176
+
return await getExtensionsBrowser();
100
177
}
101
178
102
-
injector: {
103
-
return getExtensionsNative();
179
+
nodeTarget: {
180
+
return await getExtensionsNative();
104
181
}
105
182
106
183
throw new Error("Called getExtensions() outside of node-preload/web-preload");
+53
packages/core/src/fs.ts
+53
packages/core/src/fs.ts
···
1
+
import type { MoonlightFS } from "@moonlight-mod/types";
2
+
import requireImport from "./util/import";
3
+
4
+
export default function createFS(): MoonlightFS {
5
+
const fs = requireImport("fs");
6
+
const path = requireImport("path");
7
+
8
+
return {
9
+
async readFile(path) {
10
+
const file = fs.readFileSync(path);
11
+
return new Uint8Array(file);
12
+
},
13
+
async readFileString(path) {
14
+
return fs.readFileSync(path, "utf8");
15
+
},
16
+
async writeFile(path, data) {
17
+
fs.writeFileSync(path, Buffer.from(data));
18
+
},
19
+
async writeFileString(path, data) {
20
+
fs.writeFileSync(path, data, "utf8");
21
+
},
22
+
async unlink(path) {
23
+
fs.unlinkSync(path);
24
+
},
25
+
26
+
async readdir(path) {
27
+
return fs.readdirSync(path);
28
+
},
29
+
async mkdir(path) {
30
+
fs.mkdirSync(path, { recursive: true });
31
+
},
32
+
async rmdir(path) {
33
+
fs.rmSync(path, { recursive: true });
34
+
},
35
+
36
+
async exists(path) {
37
+
return fs.existsSync(path);
38
+
},
39
+
async isFile(path) {
40
+
return fs.statSync(path).isFile();
41
+
},
42
+
async isDir(path) {
43
+
return fs.statSync(path).isDirectory();
44
+
},
45
+
46
+
join(...parts) {
47
+
return path.join(...parts);
48
+
},
49
+
dirname(dir) {
50
+
return path.dirname(dir);
51
+
}
52
+
};
53
+
}
+272
-121
packages/core/src/patch.ts
+272
-121
packages/core/src/patch.ts
···
6
6
IdentifiedWebpackModule,
7
7
WebpackJsonp,
8
8
WebpackJsonpEntry,
9
-
WebpackModuleFunc
9
+
WebpackModuleFunc,
10
+
WebpackRequireType
10
11
} from "@moonlight-mod/types";
11
12
import Logger from "./util/logger";
12
13
import calculateDependencies, { Dependency } from "./util/dependency";
13
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
14
+
import { WebEventType } from "@moonlight-mod/types/core/event";
15
+
import { processFind, processReplace, testFind } from "./util/patch";
14
16
15
17
const logger = new Logger("core/patch");
16
18
17
19
// Can't be Set because we need splice
18
-
let patches: IdentifiedPatch[] = [];
20
+
const patches: IdentifiedPatch[] = [];
19
21
let webpackModules: Set<IdentifiedWebpackModule> = new Set();
22
+
let webpackRequire: WebpackRequireType | null = null;
23
+
24
+
const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = new Map();
20
25
21
26
export function registerPatch(patch: IdentifiedPatch) {
27
+
patch.find = processFind(patch.find);
28
+
processReplace(patch.replace);
29
+
22
30
patches.push(patch);
31
+
moonlight.unpatched.add(patch);
23
32
}
24
33
25
34
export function registerWebpackModule(wp: IdentifiedWebpackModule) {
26
35
webpackModules.add(wp);
36
+
if (wp.dependencies?.length) {
37
+
moonlight.pendingModules.add(wp);
38
+
}
39
+
}
40
+
41
+
export function onModuleLoad(module: string | string[], callback: (moduleId: string) => void): void {
42
+
let moduleIds = module;
43
+
44
+
if (typeof module === "string") {
45
+
moduleIds = [module];
46
+
}
47
+
48
+
for (const moduleId of moduleIds) {
49
+
if (moduleLoadSubscriptions.has(moduleId)) {
50
+
moduleLoadSubscriptions.get(moduleId)?.push(callback);
51
+
} else {
52
+
moduleLoadSubscriptions.set(moduleId, [callback]);
53
+
}
54
+
}
27
55
}
28
56
29
57
/*
···
38
66
const moduleCache: Record<string, string> = {};
39
67
const patched: Record<string, Array<string>> = {};
40
68
69
+
function createSourceURL(id: string) {
70
+
const remapped = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0];
71
+
72
+
if (remapped) {
73
+
return `// Webpack Module: ${id}\n//# sourceURL=${remapped}`;
74
+
}
75
+
76
+
return `//# sourceURL=Webpack-Module/${id.slice(0, 3)}/${id}`;
77
+
}
78
+
79
+
function patchModule(id: string, patchId: string, replaced: string, entry: WebpackJsonpEntry[1]) {
80
+
// Store what extensions patched what modules for easier debugging
81
+
patched[id] = patched[id] ?? [];
82
+
patched[id].push(patchId);
83
+
84
+
// Webpack module arguments are minified, so we replace them with consistent names
85
+
// We have to wrap it so things don't break, though
86
+
const patchedStr = patched[id].sort().join(", ");
87
+
88
+
const wrapped =
89
+
`(${replaced}).apply(this, arguments)\n` + `// Patched by moonlight: ${patchedStr}\n` + createSourceURL(id);
90
+
91
+
try {
92
+
const func = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc;
93
+
entry[id] = func;
94
+
entry[id].__moonlight = true;
95
+
return true;
96
+
} catch (e) {
97
+
logger.warn("Error constructing function for patch", patchId, e);
98
+
patched[id].pop();
99
+
return false;
100
+
}
101
+
}
102
+
41
103
function patchModules(entry: WebpackJsonpEntry[1]) {
104
+
// Populate the module cache
42
105
for (const [id, func] of Object.entries(entry)) {
43
-
let moduleString = moduleCache.hasOwnProperty(id)
44
-
? moduleCache[id]
45
-
: func.toString().replace(/\n/g, "");
106
+
if (!Object.hasOwn(moduleCache, id) && func.__moonlight !== true) {
107
+
moduleCache[id] = func.toString().replace(/\n/g, "");
108
+
moonlight.moonmap.parseScript(id, moduleCache[id]);
109
+
}
110
+
}
111
+
112
+
for (const [id, func] of Object.entries(entry)) {
113
+
if (func.__moonlight === true) continue;
46
114
47
-
for (const patch of patches) {
115
+
// Clone the module string so finds don't get messed up by other extensions
116
+
const origModuleString = moduleCache[id];
117
+
let moduleString = origModuleString;
118
+
const patchedStr = [];
119
+
const mappedName = Object.entries(moonlight.moonmap.modules).find((m) => m[1] === id)?.[0];
120
+
let modified = false;
121
+
let swappedModule = false;
122
+
123
+
const exts = new Set<string>();
124
+
125
+
for (let i = 0; i < patches.length; i++) {
126
+
const patch = patches[i];
48
127
if (patch.prerequisite != null && !patch.prerequisite()) {
128
+
moonlight.unpatched.delete(patch);
49
129
continue;
50
130
}
51
131
···
54
134
patch.find.lastIndex = 0;
55
135
}
56
136
57
-
// indexOf is faster than includes by 0.25% lmao
58
-
const match =
59
-
typeof patch.find === "string"
60
-
? moduleString.indexOf(patch.find) !== -1
61
-
: patch.find.test(moduleString);
137
+
const match = testFind(origModuleString, patch.find) || patch.find === mappedName;
62
138
63
139
// Global regexes apply to all modules
64
-
const shouldRemove =
65
-
typeof patch.find === "string" ? true : !patch.find.global;
140
+
const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global;
66
141
142
+
let replaced = moduleString;
143
+
let hardFailed = false;
67
144
if (match) {
68
-
moonlight.unpatched.delete(patch);
145
+
// We ensured normal PatchReplace objects get turned into arrays on register
146
+
const replaces = patch.replace as PatchReplace[];
69
147
70
-
// We ensured all arrays get turned into normal PatchReplace objects on register
71
-
const replace = patch.replace as PatchReplace;
148
+
let isPatched = true;
149
+
for (let i = 0; i < replaces.length; i++) {
150
+
const replace = replaces[i];
151
+
let patchId = `${patch.ext}#${patch.id}`;
152
+
if (replaces.length > 1) patchId += `#${i}`;
153
+
patchedStr.push(patchId);
72
154
73
-
if (
74
-
replace.type === undefined ||
75
-
replace.type == PatchReplaceType.Normal
76
-
) {
77
-
// tsc fails to detect the overloads for this, so I'll just do this
78
-
// Verbose, but it works
79
-
let replaced;
80
-
if (typeof replace.replacement === "string") {
81
-
replaced = moduleString.replace(replace.match, replace.replacement);
82
-
} else {
83
-
replaced = moduleString.replace(replace.match, replace.replacement);
84
-
}
155
+
if (replace.type === undefined || replace.type === PatchReplaceType.Normal) {
156
+
// tsc fails to detect the overloads for this, so I'll just do this
157
+
// Verbose, but it works
158
+
if (typeof replace.replacement === "string") {
159
+
replaced = replaced.replace(replace.match, replace.replacement);
160
+
} else {
161
+
replaced = replaced.replace(replace.match, replace.replacement);
162
+
}
85
163
86
-
if (replaced === moduleString) {
87
-
logger.warn("Patch replacement failed", id, patch);
88
-
continue;
164
+
if (replaced === moduleString) {
165
+
logger.warn("Patch replacement failed", id, patchId, patch);
166
+
isPatched = false;
167
+
if (patch.hardFail) {
168
+
hardFailed = true;
169
+
break;
170
+
} else {
171
+
continue;
172
+
}
173
+
}
174
+
} else if (replace.type === PatchReplaceType.Module) {
175
+
// Directly replace the module with a new one
176
+
const newModule = replace.replacement(replaced);
177
+
entry[id] = newModule;
178
+
entry[id].__moonlight = true;
179
+
replaced = newModule.toString().replace(/\n/g, "");
180
+
swappedModule = true;
89
181
}
182
+
}
90
183
91
-
// Store what extensions patched what modules for easier debugging
92
-
patched[id] = patched[id] || [];
93
-
patched[id].push(`${patch.ext}#${patch.id}`);
184
+
if (!hardFailed) {
185
+
moduleString = replaced;
186
+
modified = true;
187
+
exts.add(patch.ext);
188
+
}
94
189
95
-
// Webpack module arguments are minified, so we replace them with consistent names
96
-
// We have to wrap it so things don't break, though
97
-
const patchedStr = patched[id].sort().join(", ");
190
+
if (isPatched) moonlight.unpatched.delete(patch);
191
+
if (shouldRemove) patches.splice(i--, 1);
192
+
}
193
+
}
98
194
99
-
const wrapped =
100
-
`(${replaced}).apply(this, arguments)\n` +
101
-
`// Patched by moonlight: ${patchedStr}\n` +
102
-
`//# sourceURL=Webpack-Module-${id}`;
195
+
if (modified) {
196
+
let shouldCache = true;
197
+
if (!swappedModule) shouldCache = patchModule(id, patchedStr.join(", "), moduleString, entry);
198
+
if (shouldCache) moduleCache[id] = moduleString;
199
+
moonlight.patched.set(id, exts);
200
+
}
103
201
104
-
try {
105
-
const func = new Function(
106
-
"module",
107
-
"exports",
108
-
"require",
109
-
wrapped
110
-
) as WebpackModuleFunc;
111
-
entry[id] = func;
112
-
entry[id].__moonlight = true;
113
-
moduleString = replaced;
114
-
} catch (e) {
115
-
logger.warn("Error constructing function for patch", e);
202
+
try {
203
+
const parsed = moonlight.lunast.parseScript(id, moduleString);
204
+
if (parsed != null) {
205
+
for (const [parsedId, parsedScript] of Object.entries(parsed)) {
206
+
if (patchModule(parsedId, "lunast", parsedScript, entry)) {
207
+
moduleCache[parsedId] = parsedScript;
116
208
}
117
-
} else if (replace.type == PatchReplaceType.Module) {
118
-
// Directly replace the module with a new one
119
-
const newModule = replace.replacement(moduleString);
120
-
entry[id] = newModule;
121
-
entry[id].__moonlight = true;
122
-
moduleString =
123
-
newModule.toString().replace(/\n/g, "") +
124
-
`//# sourceURL=Webpack-Module-${id}`;
125
-
}
126
-
127
-
if (shouldRemove) {
128
-
patches.splice(
129
-
patches.findIndex((p) => p.ext === patch.ext && p.id === patch.id),
130
-
1
131
-
);
132
209
}
133
210
}
211
+
} catch (e) {
212
+
logger.error("Failed to parse script for LunAST", id, e);
134
213
}
135
214
136
215
if (moonlightNode.config.patchAll === true) {
137
-
if (
138
-
(typeof id !== "string" || !id.includes("_")) &&
139
-
!entry[id].__moonlight
140
-
) {
141
-
const wrapped =
142
-
`(${moduleString}).apply(this, arguments)\n` +
143
-
`//# sourceURL=Webpack-Module-${id}`;
144
-
entry[id] = new Function(
145
-
"module",
146
-
"exports",
147
-
"require",
148
-
wrapped
149
-
) as WebpackModuleFunc;
216
+
if ((typeof id !== "string" || !id.includes("_")) && !entry[id].__moonlight) {
217
+
const wrapped = `(${moduleCache[id]}).apply(this, arguments)\n` + createSourceURL(id);
218
+
entry[id] = new Function("module", "exports", "require", wrapped) as WebpackModuleFunc;
150
219
entry[id].__moonlight = true;
151
220
}
221
+
}
222
+
223
+
// Dispatch module load event subscription
224
+
if (moduleLoadSubscriptions.has(id)) {
225
+
const loadCallbacks = moduleLoadSubscriptions.get(id)!;
226
+
for (const callback of loadCallbacks) {
227
+
try {
228
+
callback(id);
229
+
} catch (e) {
230
+
logger.error("Error in module load subscription: " + e);
231
+
}
232
+
}
233
+
moduleLoadSubscriptions.delete(id);
152
234
}
153
235
154
236
moduleCache[id] = moduleString;
···
162
244
*/
163
245
let chunkId = Number.MAX_SAFE_INTEGER;
164
246
247
+
function depToString(x: ExplicitExtensionDependency) {
248
+
return x.ext != null ? `${x.ext}_${x.id}` : x.id;
249
+
}
250
+
165
251
function handleModuleDependencies() {
166
252
const modules = Array.from(webpackModules.values());
167
253
168
-
const dependencies: Dependency<string, IdentifiedWebpackModule>[] =
169
-
modules.map((wp) => {
170
-
return {
171
-
id: `${wp.ext}_${wp.id}`,
172
-
data: wp
173
-
};
174
-
});
254
+
const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => {
255
+
return {
256
+
id: depToString(wp),
257
+
data: wp
258
+
};
259
+
});
175
260
176
-
const [sorted, _] = calculateDependencies(
177
-
dependencies,
178
-
179
-
function fetchDep(id) {
180
-
return modules.find((x) => id === `${x.ext}_${x.id}`) ?? null;
261
+
const [sorted, _] = calculateDependencies(dependencies, {
262
+
fetchDep: (id) => {
263
+
return modules.find((x) => id === depToString(x)) ?? null;
181
264
},
182
265
183
-
function getDeps(item) {
266
+
getDeps: (item) => {
184
267
const deps = item.data?.dependencies ?? [];
185
268
return (
186
269
deps.filter(
187
-
(dep) => !(dep instanceof RegExp || typeof dep === "string")
270
+
(dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null
188
271
) as ExplicitExtensionDependency[]
189
-
).map((x) => `${x.ext}_${x.id}`);
272
+
).map(depToString);
190
273
}
191
-
);
274
+
});
192
275
193
276
webpackModules = new Set(sorted.map((x) => x.data));
194
277
}
···
199
282
const entrypoints: string[] = [];
200
283
let inject = false;
201
284
202
-
for (const [modId, mod] of Object.entries(entry)) {
285
+
for (const [_modId, mod] of Object.entries(entry)) {
203
286
const modStr = mod.toString();
204
-
const wpModules = Array.from(webpackModules.values());
205
-
for (const wpModule of wpModules) {
206
-
const id = wpModule.ext + "_" + wpModule.id;
287
+
for (const wpModule of webpackModules) {
288
+
const id = depToString(wpModule);
207
289
if (wpModule.dependencies) {
208
290
const deps = new Set(wpModule.dependencies);
209
291
210
292
// FIXME: This dependency resolution might fail if the things we want
211
293
// got injected earlier. If weird dependencies fail, this is likely why.
212
294
if (deps.size) {
213
-
for (const dep of deps.values()) {
295
+
for (const dep of deps) {
214
296
if (typeof dep === "string") {
215
297
if (modStr.includes(dep)) deps.delete(dep);
216
298
} else if (dep instanceof RegExp) {
217
299
if (dep.test(modStr)) deps.delete(dep);
218
300
} else if (
219
-
injectedWpModules.find(
220
-
(x) => x.ext === dep.ext && x.id === dep.id
221
-
)
301
+
dep.ext != null
302
+
? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id)
303
+
: injectedWpModules.find((x) => x.id === dep.id)
222
304
) {
223
305
deps.delete(dep);
224
306
}
225
307
}
226
308
309
+
wpModule.dependencies = Array.from(deps);
227
310
if (deps.size !== 0) {
228
-
// Update the deps that have passed
229
-
webpackModules.delete(wpModule);
230
-
wpModule.dependencies = Array.from(deps);
231
-
webpackModules.add(wpModule);
232
311
continue;
233
312
}
234
-
235
-
wpModule.dependencies = Array.from(deps);
236
313
}
237
314
}
238
315
239
316
webpackModules.delete(wpModule);
317
+
moonlight.pendingModules.delete(wpModule);
240
318
injectedWpModules.push(wpModule);
241
319
242
320
inject = true;
243
321
244
-
modules[id] = wpModule.run;
245
-
if (wpModule.entrypoint) entrypoints.push(id);
322
+
if (wpModule.run) {
323
+
modules[id] = wpModule.run;
324
+
wpModule.run.__moonlight = true;
325
+
// @ts-expect-error hacks
326
+
wpModule.run.call = function (self, module, exports, require) {
327
+
try {
328
+
wpModule.run!.apply(self, [module, exports, require]);
329
+
} catch (err) {
330
+
logger.error(`Failed to run module "${id}":`, err);
331
+
}
332
+
};
333
+
if (wpModule.entrypoint) entrypoints.push(id);
334
+
}
246
335
}
247
336
if (!webpackModules.size) break;
248
337
}
249
338
339
+
for (const [name, func] of Object.entries(moonlight.moonmap.getWebpackModules("window.moonlight.moonmap"))) {
340
+
// @ts-expect-error probably should fix the type on this idk
341
+
func.__moonlight = true;
342
+
injectedWpModules.push({ id: name, run: func });
343
+
modules[name] = func;
344
+
inject = true;
345
+
}
346
+
347
+
if (webpackRequire != null) {
348
+
for (const id of moonlight.moonmap.getLazyModules()) {
349
+
webpackRequire.e(id);
350
+
}
351
+
}
352
+
250
353
if (inject) {
251
354
logger.debug("Injecting modules:", modules, entrypoints);
252
355
window.webpackChunkdiscord_app.push([
253
356
[--chunkId],
254
357
modules,
255
-
(require: typeof WebpackRequire) => entrypoints.map(require)
358
+
(require: WebpackRequireType) =>
359
+
entrypoints.map((id) => {
360
+
try {
361
+
if (require.m[id] == null) {
362
+
logger.error(`Failing to load entrypoint module "${id}" because it's not found in Webpack.`);
363
+
} else {
364
+
require(id);
365
+
}
366
+
} catch (err) {
367
+
logger.error(`Failed to load entrypoint module "${id}":`, err);
368
+
}
369
+
})
256
370
]);
257
371
}
258
372
}
···
263
377
}
264
378
}
265
379
380
+
function moduleSourceGetter(id: string) {
381
+
return moduleCache[id] ?? null;
382
+
}
383
+
266
384
/*
267
385
Webpack modules are bundled into an array of arrays that hold each function.
268
386
Since we run code before Discord, we can create our own Webpack array and
···
274
392
export async function installWebpackPatcher() {
275
393
await handleModuleDependencies();
276
394
395
+
moonlight.lunast.setModuleSourceGetter(moduleSourceGetter);
396
+
moonlight.moonmap.setModuleSourceGetter(moduleSourceGetter);
397
+
398
+
const wpRequireFetcher: WebpackModuleFunc = (module, exports, require) => {
399
+
webpackRequire = require;
400
+
};
401
+
wpRequireFetcher.__moonlight = true;
402
+
webpackModules.add({
403
+
id: "moonlight",
404
+
entrypoint: true,
405
+
run: wpRequireFetcher
406
+
});
407
+
277
408
let realWebpackJsonp: WebpackJsonp | null = null;
278
409
Object.defineProperty(window, "webpackChunkdiscord_app", {
279
410
set: (jsonp: WebpackJsonp) => {
···
285
416
const realPush = jsonp.push;
286
417
if (jsonp.push.__moonlight !== true) {
287
418
jsonp.push = (items) => {
419
+
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
420
+
chunkId: items[0],
421
+
modules: items[1],
422
+
require: items[2]
423
+
});
424
+
288
425
patchModules(items[1]);
289
426
290
427
try {
···
322
459
}
323
460
});
324
461
325
-
registerWebpackModule({
326
-
ext: "moonlight",
327
-
id: "fix_rspack_init_modules",
328
-
entrypoint: true,
329
-
run: function (module, exports, require) {
330
-
patchModules(require.m);
462
+
Object.defineProperty(Function.prototype, "m", {
463
+
configurable: true,
464
+
set(modules: any) {
465
+
const { stack } = new Error();
466
+
if (stack!.includes("/assets/") && !Array.isArray(modules)) {
467
+
moonlight.events.dispatchEvent(WebEventType.ChunkLoad, {
468
+
modules: modules
469
+
});
470
+
patchModules(modules);
471
+
472
+
if (!window.webpackChunkdiscord_app) window.webpackChunkdiscord_app = [];
473
+
injectModules(modules);
474
+
}
475
+
476
+
Object.defineProperty(this, "m", {
477
+
value: modules,
478
+
configurable: true,
479
+
enumerable: true,
480
+
writable: true
481
+
});
331
482
}
332
483
});
333
484
}
+49
packages/core/src/persist.ts
+49
packages/core/src/persist.ts
···
1
+
import { join, dirname } from "node:path";
2
+
import { mkdirSync, renameSync, existsSync, copyFileSync, readdirSync } from "node:fs";
3
+
import Logger from "./util/logger";
4
+
5
+
const logger = new Logger("core/persist");
6
+
7
+
export default function persist(asarPath: string) {
8
+
try {
9
+
if (process.platform === "win32") {
10
+
persistWin32(asarPath);
11
+
}
12
+
} catch (e) {
13
+
logger.error(`Failed to persist moonlight: ${e}`);
14
+
}
15
+
}
16
+
17
+
function persistWin32(asarPath: string) {
18
+
const updaterModule = require(join(asarPath, "common", "updater"));
19
+
const updater = updaterModule.Updater;
20
+
21
+
const currentAppDir = join(dirname(asarPath), "app");
22
+
23
+
const realEmit = updater.prototype.emit;
24
+
updater.prototype.emit = function (event: string, ...args: any[]) {
25
+
if (event === "host-updated") {
26
+
const versions = this.queryCurrentVersionsSync();
27
+
28
+
const newRootDir = join(this.rootPath, "app-" + versions.current_host.map((v: number) => v.toString()).join("."));
29
+
logger.info(`Persisting moonlight - new root dir: ${newRootDir}`);
30
+
31
+
const newResources = join(newRootDir, "resources");
32
+
33
+
// app.asar -> _app.asar
34
+
const newAsar = join(newResources, "app.asar");
35
+
const newRenamedAsar = join(newResources, "_app.asar");
36
+
if (!existsSync(newRenamedAsar)) renameSync(newAsar, newRenamedAsar);
37
+
38
+
// copy the already existing app dir so we don't have to figure out the moonlight dir
39
+
const newAppDir = join(newResources, "app");
40
+
if (!existsSync(newAppDir)) mkdirSync(newAppDir);
41
+
42
+
for (const file of readdirSync(currentAppDir)) {
43
+
copyFileSync(join(currentAppDir, file), join(newAppDir, file));
44
+
}
45
+
}
46
+
47
+
return realEmit.call(this, event, ...args);
48
+
};
49
+
}
+13
packages/core/src/styles.ts
+13
packages/core/src/styles.ts
···
1
+
const styles: string[] = [];
2
+
3
+
export function registerStyles(style: string[]) {
4
+
styles.push(...style);
5
+
}
6
+
7
+
export function installStyles() {
8
+
for (const style of styles) {
9
+
const el = document.createElement("style");
10
+
el.textContent = style;
11
+
document.documentElement.appendChild(el);
12
+
}
13
+
}
+63
packages/core/src/util/binary.ts
+63
packages/core/src/util/binary.ts
···
1
+
// https://github.com/NotNite/brc-save-editor/blob/main/src/lib/binary.ts
2
+
export interface BinaryInterface {
3
+
data: Uint8Array;
4
+
view: DataView;
5
+
length: number;
6
+
position: number;
7
+
}
8
+
9
+
export class BinaryReader implements BinaryInterface {
10
+
data: Uint8Array;
11
+
view: DataView;
12
+
length: number;
13
+
position: number;
14
+
15
+
constructor(data: Uint8Array) {
16
+
this.data = data;
17
+
this.view = new DataView(data.buffer);
18
+
19
+
this.length = data.length;
20
+
this.position = 0;
21
+
}
22
+
23
+
readByte() {
24
+
return this._read(this.view.getInt8, 1);
25
+
}
26
+
27
+
readBoolean() {
28
+
return this.readByte() !== 0;
29
+
}
30
+
31
+
readInt32() {
32
+
return this._read(this.view.getInt32, 4);
33
+
}
34
+
35
+
readUInt32() {
36
+
return this._read(this.view.getUint32, 4);
37
+
}
38
+
39
+
readSingle() {
40
+
return this._read(this.view.getFloat32, 4);
41
+
}
42
+
43
+
readInt64() {
44
+
return this._read(this.view.getBigInt64, 8);
45
+
}
46
+
47
+
readString(length: number) {
48
+
const result = this.read(length);
49
+
return new TextDecoder().decode(result);
50
+
}
51
+
52
+
read(length: number) {
53
+
const data = this.data.subarray(this.position, this.position + length);
54
+
this.position += length;
55
+
return data;
56
+
}
57
+
58
+
private _read<T>(func: (position: number, littleEndian?: boolean) => T, length: number): T {
59
+
const result = func.call(this.view, this.position, true);
60
+
this.position += length;
61
+
return result;
62
+
}
63
+
}
packages/core/src/util/clone.ts
packages/core/src/util/clone.ts
This is a binary file and will not be displayed.
+39
packages/core/src/util/config.ts
+39
packages/core/src/util/config.ts
···
1
+
import type { Config, DetectedExtension, ExtensionManifest } from "@moonlight-mod/types";
2
+
3
+
export function getManifest(extensions: DetectedExtension[], ext: string) {
4
+
return extensions.find((x) => x.id === ext)?.manifest;
5
+
}
6
+
7
+
export function getConfig(ext: string, config: Config) {
8
+
const val = config.extensions[ext];
9
+
if (val == null || typeof val === "boolean") return undefined;
10
+
return val.config;
11
+
}
12
+
13
+
export function getConfigOption<T>(
14
+
ext: string,
15
+
key: string,
16
+
config: Config,
17
+
settings?: ExtensionManifest["settings"]
18
+
): T | undefined {
19
+
const defaultValue: T | undefined = structuredClone(settings?.[key]?.default);
20
+
const cfg = getConfig(ext, config);
21
+
if (cfg == null || typeof cfg === "boolean") return defaultValue;
22
+
return cfg?.[key] ?? defaultValue;
23
+
}
24
+
25
+
export function setConfigOption<T>(config: Config, ext: string, key: string, value: T) {
26
+
const oldConfig = config.extensions[ext];
27
+
const newConfig =
28
+
typeof oldConfig === "boolean"
29
+
? {
30
+
enabled: oldConfig,
31
+
config: { [key]: value }
32
+
}
33
+
: {
34
+
...oldConfig,
35
+
config: { ...(oldConfig?.config ?? {}), [key]: value }
36
+
};
37
+
38
+
config.extensions[ext] = newConfig;
39
+
}
+32
-32
packages/core/src/util/data.ts
+32
-32
packages/core/src/util/data.ts
···
1
1
import { constants } from "@moonlight-mod/types";
2
-
import requireImport from "./import";
3
2
4
-
export function getMoonlightDir(): string {
5
-
const { app, ipcRenderer } = require("electron");
6
-
const fs = requireImport("fs");
7
-
const path = requireImport("path");
3
+
export async function getMoonlightDir() {
4
+
browser: {
5
+
return "/";
6
+
}
7
+
8
+
const electron = require("electron");
8
9
9
10
let appData = "";
10
11
injector: {
11
-
appData = app.getPath("appData");
12
+
appData = electron.app.getPath("appData");
12
13
}
13
14
14
15
nodePreload: {
15
-
appData = ipcRenderer.sendSync(constants.ipcGetAppData);
16
+
appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData);
16
17
}
17
18
18
-
const dir = path.join(appData, "moonlight-mod");
19
-
if (!fs.existsSync(dir)) fs.mkdirSync(dir);
19
+
const dir = moonlightNodeSandboxed.fs.join(appData, "moonlight-mod");
20
+
if (!(await moonlightNodeSandboxed.fs.exists(dir))) await moonlightNodeSandboxed.fs.mkdir(dir);
20
21
21
22
return dir;
22
23
}
···
26
27
version: string;
27
28
};
28
29
29
-
export function getConfigPath(): string {
30
-
const dir = getMoonlightDir();
31
-
const fs = requireImport("fs");
32
-
const path = requireImport("path");
30
+
export async function getConfigPath() {
31
+
browser: {
32
+
return "/config.json";
33
+
}
33
34
34
-
const buildInfoPath = path.join(process.resourcesPath, "build_info.json");
35
-
const buildInfo: BuildInfo = JSON.parse(
36
-
fs.readFileSync(buildInfoPath, "utf8")
37
-
);
35
+
const dir = await getMoonlightDir();
38
36
39
-
const configPath = path.join(dir, buildInfo.releaseChannel + ".json");
37
+
let configPath = "";
38
+
39
+
const buildInfoPath = moonlightNodeSandboxed.fs.join(process.resourcesPath, "build_info.json");
40
+
if (!(await moonlightNodeSandboxed.fs.exists(buildInfoPath))) {
41
+
configPath = moonlightNodeSandboxed.fs.join(dir, "desktop.json");
42
+
} else {
43
+
const buildInfo: BuildInfo = JSON.parse(await moonlightNodeSandboxed.fs.readFileString(buildInfoPath));
44
+
configPath = moonlightNodeSandboxed.fs.join(dir, buildInfo.releaseChannel + ".json");
45
+
}
46
+
40
47
return configPath;
41
48
}
42
49
43
-
function getPathFromMoonlight(...names: string[]): string {
44
-
const dir = getMoonlightDir();
45
-
const fs = requireImport("fs");
46
-
const path = requireImport("path");
50
+
async function getPathFromMoonlight(...names: string[]) {
51
+
const dir = await getMoonlightDir();
47
52
48
-
const target = path.join(dir, ...names);
49
-
if (!fs.existsSync(target)) fs.mkdirSync(target);
53
+
const target = moonlightNodeSandboxed.fs.join(dir, ...names);
54
+
if (!(await moonlightNodeSandboxed.fs.exists(target))) await moonlightNodeSandboxed.fs.mkdir(target);
50
55
51
56
return target;
52
57
}
53
58
54
-
export function getExtensionsPath(): string {
55
-
return getPathFromMoonlight(constants.extensionsDir);
59
+
export async function getExtensionsPath() {
60
+
return await getPathFromMoonlight(constants.extensionsDir);
56
61
}
57
62
58
63
export function getCoreExtensionsPath(): string {
59
-
if (MOONLIGHT_PROD) {
60
-
return getPathFromMoonlight(constants.distDir, constants.coreExtensionsDir);
61
-
} else {
62
-
const path = requireImport("path");
63
-
return path.join(__dirname, constants.coreExtensionsDir);
64
-
}
64
+
return moonlightNodeSandboxed.fs.join(__dirname, constants.coreExtensionsDir);
65
65
}
+104
-30
packages/core/src/util/dependency.ts
+104
-30
packages/core/src/util/dependency.ts
···
1
1
import Logger from "./logger";
2
2
3
+
const logger = new Logger("core/util/dependency");
4
+
3
5
export type Dependency<T, D> = {
4
6
id: T;
5
7
data: D;
6
8
};
9
+
type Dependencies<T, D> = Dependency<T, D>[];
10
+
type DependencyGraph<T> = Map<T, Set<T> | null>;
7
11
8
-
const logger = new Logger("core/util/dependency");
12
+
type FetchDep<T, D> = (id: T) => D | null;
13
+
type GetDeps<T, D> = (item: Dependency<T, D>) => T[];
14
+
type GetIncompatible<T, D> = (item: Dependency<T, D>) => T[];
15
+
type GetEnabled<T, D> = (item: Dependency<T, D>) => boolean;
9
16
10
-
export default function calculateDependencies<T, D>(
11
-
origItems: Dependency<T, D>[],
12
-
fetchDep: (id: T) => D | null,
13
-
getDeps: (item: Dependency<T, D>) => T[],
14
-
getIncompatible?: (item: Dependency<T, D>) => T[]
15
-
): [Dependency<T, D>[], Map<T, Set<T> | null>] {
16
-
logger.trace("sortDependencies begin", origItems);
17
+
function buildDependencyGraph<T, D>(
18
+
origItems: Dependencies<T, D>,
19
+
{
20
+
fetchDep,
21
+
getDeps,
22
+
getIncompatible,
23
+
getEnabled
24
+
}: {
25
+
fetchDep: FetchDep<T, D>;
26
+
getDeps: GetDeps<T, D>;
27
+
getIncompatible?: GetIncompatible<T, D>;
28
+
getEnabled?: GetEnabled<T, D>;
29
+
}
30
+
): [Dependencies<T, D>, DependencyGraph<T>] {
17
31
let items = [...origItems];
32
+
const dependencyGraph: DependencyGraph<T> = new Map();
18
33
19
-
if (getIncompatible != null) {
20
-
for (const item of items) {
21
-
const incompatibleItems = getIncompatible(item);
22
-
for (const incompatibleItem of incompatibleItems) {
23
-
if (items.find((x) => x.id === incompatibleItem) != null) {
24
-
logger.warn(
25
-
`Incompatible dependency detected: "${item.id}" and "${incompatibleItem}" - removing "${incompatibleItem}"`
26
-
);
27
-
28
-
items = items.filter((x) => x.id !== incompatibleItem);
29
-
}
30
-
}
31
-
}
32
-
}
33
-
34
-
const dependencyGraph = new Map<T, Set<T> | null>();
35
34
for (const item of items) {
36
35
const fullDeps: Set<T> = new Set();
37
36
let failed = false;
···
83
82
items = items.filter((item) => !failed.includes(item));
84
83
}
85
84
85
+
return [items, dependencyGraph];
86
+
}
87
+
88
+
export default function calculateDependencies<T, D>(
89
+
origItems: Dependencies<T, D>,
90
+
{
91
+
fetchDep,
92
+
getDeps,
93
+
getIncompatible,
94
+
getEnabled
95
+
}: {
96
+
fetchDep: FetchDep<T, D>;
97
+
getDeps: GetDeps<T, D>;
98
+
getIncompatible?: GetIncompatible<T, D>;
99
+
getEnabled?: GetEnabled<T, D>;
100
+
}
101
+
): [Dependencies<T, D>, DependencyGraph<T>] {
102
+
logger.trace("sortDependencies begin", origItems);
103
+
// eslint-disable-next-line prefer-const
104
+
let [itemsOrig, dependencyGraphOrig] = buildDependencyGraph(origItems, {
105
+
fetchDep,
106
+
getDeps,
107
+
getIncompatible,
108
+
getEnabled
109
+
});
110
+
111
+
if (getEnabled != null) {
112
+
logger.trace("Enabled stage", itemsOrig);
113
+
const implicitlyEnabled: T[] = [];
114
+
115
+
function validateDeps(dep: Dependency<T, D>) {
116
+
if (getEnabled!(dep)) {
117
+
const deps = dependencyGraphOrig.get(dep.id)!;
118
+
for (const id of deps.values()) {
119
+
const data = fetchDep(id)!;
120
+
validateDeps({ id, data });
121
+
}
122
+
} else {
123
+
const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter(([, v]) => v?.has(dep.id));
124
+
125
+
if (dependsOnMe.length > 0) {
126
+
logger.debug("Implicitly enabling dependency", dep.id);
127
+
implicitlyEnabled.push(dep.id);
128
+
}
129
+
}
130
+
}
131
+
132
+
for (const dep of itemsOrig) validateDeps(dep);
133
+
itemsOrig = itemsOrig.filter((x) => getEnabled(x) || implicitlyEnabled.includes(x.id));
134
+
}
135
+
136
+
if (getIncompatible != null) {
137
+
logger.trace("Incompatible stage", itemsOrig);
138
+
139
+
for (const item of itemsOrig) {
140
+
// JavaScript iterator moment
141
+
if (!itemsOrig.includes(item)) continue;
142
+
143
+
const incompatibleItems = getIncompatible(item);
144
+
for (const incompatibleItem of incompatibleItems) {
145
+
if (itemsOrig.find((x) => x.id === incompatibleItem) != null) {
146
+
logger.warn(
147
+
`Incompatible dependency detected: "${item.id}" and "${incompatibleItem}" - removing "${incompatibleItem}"`
148
+
);
149
+
150
+
itemsOrig = itemsOrig.filter((x) => x.id !== incompatibleItem);
151
+
}
152
+
}
153
+
}
154
+
}
155
+
156
+
logger.trace("Verification stage", itemsOrig);
157
+
const [items, dependencyGraph] = buildDependencyGraph(itemsOrig, {
158
+
fetchDep,
159
+
getDeps,
160
+
getIncompatible,
161
+
getEnabled
162
+
});
163
+
86
164
logger.trace("Sorting stage", items);
87
165
const sorted: Dependency<T, D>[] = [];
88
166
···
92
170
dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id)));
93
171
}
94
172
95
-
while (
96
-
Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0
97
-
) {
98
-
const noDependents = items.filter(
99
-
(e) => dependencyGraph.get(e.id)?.size === 0
100
-
);
173
+
while (Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0) {
174
+
const noDependents = items.filter((e) => dependencyGraph.get(e.id)?.size === 0);
101
175
102
176
if (noDependents.length === 0) {
103
177
logger.warn("Stuck dependency graph detected", dependencyGraph);
+48
-54
packages/core/src/util/event.ts
+48
-54
packages/core/src/util/event.ts
···
1
-
export type MoonlightEventCallback = (data: string) => void;
1
+
import { MoonlightEventEmitter } from "@moonlight-mod/types/core/event";
2
2
3
-
export interface MoonlightEventEmitter {
4
-
dispatchEvent: (id: string, data: string) => void;
5
-
addEventListener: (id: string, cb: MoonlightEventCallback) => void;
6
-
removeEventListener: (id: string, cb: MoonlightEventCallback) => void;
7
-
}
3
+
export function createEventEmitter<
4
+
EventId extends string = string,
5
+
EventData = Record<EventId, any>
6
+
>(): MoonlightEventEmitter<EventId, EventData> {
7
+
webTarget: {
8
+
const eventEmitter = new EventTarget();
9
+
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
8
10
9
-
function nodeMethod(): MoonlightEventEmitter {
10
-
const EventEmitter = require("events");
11
-
const eventEmitter = new EventEmitter();
12
-
const listeners = new Map<MoonlightEventCallback, (...args: any[]) => void>();
11
+
return {
12
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => {
13
+
eventEmitter.dispatchEvent(new CustomEvent(id as string, { detail: data }));
14
+
},
13
15
14
-
return {
15
-
dispatchEvent: (id: string, data: string) => {
16
-
eventEmitter.emit(id, data);
17
-
},
16
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
17
+
const untyped = cb as (data: EventData) => void;
18
+
if (listeners.has(untyped)) return;
18
19
19
-
addEventListener: (id: string, cb: (data: string) => void) => {
20
-
if (listeners.has(cb)) return;
20
+
function listener(e: Event) {
21
+
const event = e as CustomEvent<string>;
22
+
cb(event.detail as EventData[Id]);
23
+
}
21
24
22
-
function listener(data: string) {
23
-
cb(data);
24
-
}
25
+
listeners.set(untyped, listener);
26
+
eventEmitter.addEventListener(id as string, listener);
27
+
},
25
28
26
-
listeners.set(cb, listener);
27
-
eventEmitter.on(id, listener);
28
-
},
29
-
30
-
removeEventListener: (id: string, cb: (data: string) => void) => {
31
-
const listener = listeners.get(cb);
32
-
if (listener == null) return;
33
-
listeners.delete(cb);
34
-
eventEmitter.off(id, listener);
35
-
}
36
-
};
37
-
}
29
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
30
+
const untyped = cb as (data: EventData) => void;
31
+
const listener = listeners.get(untyped);
32
+
if (listener == null) return;
33
+
listeners.delete(untyped);
34
+
eventEmitter.removeEventListener(id as string, listener);
35
+
}
36
+
};
37
+
}
38
38
39
-
export function createEventEmitter(): MoonlightEventEmitter {
40
-
webPreload: {
41
-
const eventEmitter = new EventTarget();
42
-
const listeners = new Map<MoonlightEventCallback, (e: Event) => void>();
39
+
nodeTarget: {
40
+
const EventEmitter = require("events");
41
+
const eventEmitter = new EventEmitter();
42
+
const listeners = new Map<(data: EventData) => void, (e: Event) => void>();
43
43
44
44
return {
45
-
dispatchEvent: (id: string, data: string) => {
46
-
eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data }));
45
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => {
46
+
eventEmitter.emit(id as string, data);
47
47
},
48
48
49
-
addEventListener: (id: string, cb: (data: string) => void) => {
50
-
if (listeners.has(cb)) return;
49
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
50
+
const untyped = cb as (data: EventData) => void;
51
+
if (listeners.has(untyped)) return;
51
52
52
53
function listener(e: Event) {
53
54
const event = e as CustomEvent<string>;
54
-
cb(event.detail);
55
+
cb(event as EventData[Id]);
55
56
}
56
57
57
-
listeners.set(cb, listener);
58
-
eventEmitter.addEventListener(id, listener);
58
+
listeners.set(untyped, listener);
59
+
eventEmitter.on(id as string, listener);
59
60
},
60
61
61
-
removeEventListener: (id: string, cb: (data: string) => void) => {
62
-
const listener = listeners.get(cb);
62
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => {
63
+
const untyped = cb as (data: EventData) => void;
64
+
const listener = listeners.get(untyped);
63
65
if (listener == null) return;
64
-
listeners.delete(cb);
65
-
eventEmitter.removeEventListener(id, listener);
66
+
listeners.delete(untyped);
67
+
eventEmitter.off(id as string, listener);
66
68
}
67
69
};
68
-
}
69
-
70
-
nodePreload: {
71
-
return nodeMethod();
72
-
}
73
-
74
-
injector: {
75
-
return nodeMethod();
76
70
}
77
71
78
72
throw new Error("Called createEventEmitter() in an impossible environment");
+3
-6
packages/core/src/util/import.ts
+3
-6
packages/core/src/util/import.ts
···
9
9
cemented if import is passed a string literal.
10
10
*/
11
11
12
-
const canRequire = ["path", "fs", "glob"] as const;
13
-
type CanRequire = (typeof canRequire)[number];
12
+
const _canRequire = ["path", "fs"] as const;
13
+
type CanRequire = (typeof _canRequire)[number];
14
14
15
15
type ImportTypes = {
16
16
path: typeof import("path");
17
17
fs: typeof import("fs");
18
-
glob: typeof import("glob");
19
18
};
20
19
21
-
export default function requireImport<T extends CanRequire>(
22
-
type: T
23
-
): Awaited<ImportTypes[T]> {
20
+
export default function requireImport<T extends CanRequire>(type: T): Awaited<ImportTypes[T]> {
24
21
return require(type);
25
22
}
+13
-16
packages/core/src/util/logger.ts
+13
-16
packages/core/src/util/logger.ts
···
1
+
/* eslint-disable no-console */
1
2
import { LogLevel } from "@moonlight-mod/types/logger";
2
-
import { readConfig } from "../config";
3
+
import { Config } from "@moonlight-mod/types";
3
4
4
5
const colors = {
5
6
[LogLevel.SILLY]: "#EDD3E9",
···
10
11
[LogLevel.ERROR]: "#FF0000"
11
12
};
12
13
13
-
const config = readConfig();
14
14
let maxLevel = LogLevel.INFO;
15
-
if (config.loggerLevel != null) {
16
-
const enumValue =
17
-
LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel];
18
-
if (enumValue != null) {
19
-
maxLevel = enumValue;
20
-
}
21
-
}
22
15
23
16
export default class Logger {
24
17
private name: string;
···
56
49
const logLevel = LogLevel[level].toUpperCase();
57
50
if (maxLevel > level) return;
58
51
59
-
if (MOONLIGHT_WEB_PRELOAD) {
60
-
args = [
61
-
`%c[${logLevel}]`,
62
-
`background-color: ${colors[level]}; color: #FFFFFF;`,
63
-
`[${this.name}]`,
64
-
...obj
65
-
];
52
+
if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) {
53
+
args = [`%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, `[${this.name}]`, ...obj];
66
54
} else {
67
55
args = [`[${logLevel}]`, `[${this.name}]`, ...obj];
68
56
}
···
91
79
}
92
80
}
93
81
}
82
+
83
+
export function initLogger(config: Config) {
84
+
if (config.loggerLevel != null) {
85
+
const enumValue = LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel];
86
+
if (enumValue != null) {
87
+
maxLevel = enumValue;
88
+
}
89
+
}
90
+
}
+30
packages/core/src/util/patch.ts
+30
packages/core/src/util/patch.ts
···
1
+
import { PatchReplace, PatchReplaceType } from "@moonlight-mod/types";
2
+
3
+
type SingleFind = string | RegExp;
4
+
type Find = SingleFind | SingleFind[];
5
+
6
+
export function processFind<T extends Find>(find: T): T {
7
+
if (Array.isArray(find)) {
8
+
return find.map(processFind) as T;
9
+
} else if (find instanceof RegExp) {
10
+
// Add support for \i to match rspack's minified names
11
+
return new RegExp(find.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), find.flags) as T;
12
+
} else {
13
+
return find;
14
+
}
15
+
}
16
+
17
+
export function processReplace(replace: PatchReplace | PatchReplace[]) {
18
+
if (Array.isArray(replace)) {
19
+
replace.forEach(processReplace);
20
+
} else {
21
+
if (replace.type === undefined || replace.type === PatchReplaceType.Normal) {
22
+
replace.match = processFind(replace.match);
23
+
}
24
+
}
25
+
}
26
+
27
+
export function testFind(src: string, find: SingleFind) {
28
+
// indexOf is faster than includes by 0.25% lmao
29
+
return typeof find === "string" ? src.indexOf(find) !== -1 : find.test(src);
30
+
}
+4
-1
packages/core/tsconfig.json
+4
-1
packages/core/tsconfig.json
+11
-2
packages/core-extensions/package.json
+11
-2
packages/core-extensions/package.json
···
1
1
{
2
2
"name": "@moonlight-mod/core-extensions",
3
3
"private": true,
4
+
"engineStrict": true,
5
+
"engines": {
6
+
"node": ">=22",
7
+
"pnpm": ">=10",
8
+
"npm": "pnpm",
9
+
"yarn": "pnpm"
10
+
},
4
11
"dependencies": {
5
-
"@electron/asar": "^3.2.5",
6
-
"@moonlight-mod/types": "workspace:*"
12
+
"@moonlight-mod/core": "workspace:*",
13
+
"@moonlight-mod/types": "workspace:*",
14
+
"microdiff": "catalog:prod",
15
+
"nanotar": "catalog:prod"
7
16
}
8
17
}
+19
packages/core-extensions/src/appPanels/index.ts
+19
packages/core-extensions/src/appPanels/index.ts
···
1
+
import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: 'setProperty("--custom-app-panels-height"',
6
+
replace: [
7
+
{
8
+
match: /\(0,.\.jsx\)\((.\..),{section:/,
9
+
replacement: (prev, el) => `...require("appPanels_appPanels").default.getPanels(${el}),${prev}`
10
+
}
11
+
]
12
+
}
13
+
];
14
+
15
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
16
+
appPanels: {
17
+
dependencies: [{ id: "react" }]
18
+
}
19
+
};
+11
packages/core-extensions/src/appPanels/manifest.json
+11
packages/core-extensions/src/appPanels/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "appPanels",
4
+
"apiLevel": 2,
5
+
"meta": {
6
+
"name": "App Panels",
7
+
"tagline": "An API for adding panels around the user/voice controls",
8
+
"authors": ["NotNite"],
9
+
"tags": ["library"]
10
+
}
11
+
}
+23
packages/core-extensions/src/appPanels/webpackModules/appPanels.ts
+23
packages/core-extensions/src/appPanels/webpackModules/appPanels.ts
···
1
+
import type { AppPanels as AppPanelsType } from "@moonlight-mod/types/coreExtensions/appPanels";
2
+
import React from "@moonlight-mod/wp/react";
3
+
4
+
const panels: Record<string, React.FC<any>> = {};
5
+
6
+
export const AppPanels: AppPanelsType = {
7
+
addPanel(section, element) {
8
+
panels[section] = element;
9
+
},
10
+
getPanels(panel) {
11
+
return Object.entries(panels).map(([section, element]) =>
12
+
React.createElement(
13
+
panel,
14
+
{
15
+
section
16
+
},
17
+
React.createElement(element)
18
+
)
19
+
);
20
+
}
21
+
};
22
+
23
+
export default AppPanels;
+85
packages/core-extensions/src/commands/index.ts
+85
packages/core-extensions/src/commands/index.ts
···
1
+
import { Patch, ExtensionWebpackModule } from "@moonlight-mod/types";
2
+
import { APPLICATION_ID } from "@moonlight-mod/types/coreExtensions/commands";
3
+
4
+
export const patches: Patch[] = [
5
+
{
6
+
find: ".fI5MTU)", // COMMAND_SECTION_BUILT_IN_NAME
7
+
replace: [
8
+
// inject commands
9
+
{
10
+
match: /return (\i)\.filter/,
11
+
replacement: (orig, commands) =>
12
+
`return [...${commands},...require("commands_commands").default._getCommands()].filter`
13
+
},
14
+
15
+
// section
16
+
{
17
+
match: /(?<=\i={)(?=\[\i\.\i\.BUILT_IN]:{id:\i\.\i\.BUILT_IN,type:(\i.\i\.BUILT_IN))/,
18
+
replacement: (_, type) =>
19
+
`"${APPLICATION_ID}":{id:"${APPLICATION_ID}",type:${type},get name(){return "moonlight"}},`
20
+
}
21
+
]
22
+
},
23
+
24
+
// index our section
25
+
{
26
+
find: '"ApplicationCommandIndexStore"',
27
+
replace: {
28
+
match: /(?<=let \i=(\i)\((\i\.\i)\[\i\.\i\.BUILT_IN\],(\i),!0,!0,(\i)\);)null!=(\i)&&(\i)\.push\(\i\)/,
29
+
replacement: (_, createSection, sections, deny, props, section, commands) =>
30
+
`null!=${section}&&(${section}.data=${section}.data.filter(c=>c.applicationId=="-1"));
31
+
null!=${section}&&${commands}.push(${section});
32
+
const moonlightCommands=${createSection}(${sections}["${APPLICATION_ID}"],${deny},!0,!0,${props});
33
+
null!=moonlightCommands&&(moonlightCommands.data=moonlightCommands.data.filter(c=>c.applicationId=="${APPLICATION_ID}"));
34
+
null!=moonlightCommands&&${commands}.push(moonlightCommands)`
35
+
}
36
+
},
37
+
38
+
// grab legacy commands (needed for adding actions that act like sed/plus reacting)
39
+
{
40
+
find: "={tts:{action:",
41
+
replace: {
42
+
match: /Object\.setPrototypeOf\((\i),null\)/,
43
+
replacement: (_, legacyCommands) => `require("commands_commands")._getLegacyCommands(${legacyCommands})`
44
+
}
45
+
},
46
+
47
+
// add icon
48
+
{
49
+
find: ",hasSpaceTerminator:",
50
+
replace: {
51
+
match: /(\i)\.type===/,
52
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
53
+
}
54
+
},
55
+
{
56
+
find: ".icon,bot:null==",
57
+
replace: {
58
+
match: /(\.useMemo\(\(\)=>{(var \i;)?)((return |if\()(\i)\.type)/,
59
+
replacement: (_, before, beforeVar, after, afterIf, section) => `${before}
60
+
if (${section}.id==="${APPLICATION_ID}") return "https://moonlight-mod.github.io/favicon.png";
61
+
${after}`
62
+
}
63
+
},
64
+
// fix icon sizing because they expect built in to be 24 and others to be 32
65
+
{
66
+
find: ".builtInSeparator}):null]",
67
+
replace: {
68
+
match: /(\i)\.type===\i\.\i\.BUILT_IN/,
69
+
replacement: (orig, section) => `${section}.id!=="${APPLICATION_ID}"&&${orig}`
70
+
}
71
+
},
72
+
73
+
// tell it this app id is authorized
74
+
{
75
+
find: /let{customInstallUrl:\i,installParams:\i,integrationTypesConfig:\i}/,
76
+
replace: {
77
+
match: /\|\|(\i)===\i\.\i\.BUILT_IN/,
78
+
replacement: (orig, id) => `${orig}||${id}==="${APPLICATION_ID}"`
79
+
}
80
+
}
81
+
];
82
+
83
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
84
+
commands: {}
85
+
};
+11
packages/core-extensions/src/commands/manifest.json
+11
packages/core-extensions/src/commands/manifest.json
+71
packages/core-extensions/src/commands/webpackModules/commands.ts
+71
packages/core-extensions/src/commands/webpackModules/commands.ts
···
1
+
import {
2
+
APPLICATION_ID,
3
+
Commands,
4
+
LegacyCommand,
5
+
RegisteredCommand
6
+
} from "@moonlight-mod/types/coreExtensions/commands";
7
+
8
+
type LegacyCommands = Record<string, LegacyCommand>;
9
+
let legacyCommands: LegacyCommands | undefined;
10
+
let queuedLegacyCommands: Record<string, LegacyCommand> | null = {};
11
+
12
+
const registeredCommands: RegisteredCommand[] = [];
13
+
14
+
export function _getLegacyCommands(commands: LegacyCommands) {
15
+
legacyCommands = commands;
16
+
if (queuedLegacyCommands != null) {
17
+
for (const [key, value] of Object.entries(queuedLegacyCommands)) {
18
+
legacyCommands[key] = value;
19
+
}
20
+
queuedLegacyCommands = null;
21
+
}
22
+
}
23
+
24
+
export const commands: Commands = {
25
+
registerCommand(command) {
26
+
const registered: RegisteredCommand = {
27
+
...command,
28
+
untranslatedName: command.id,
29
+
displayName: command.id,
30
+
applicationId: APPLICATION_ID,
31
+
untranslatedDescription: command.description,
32
+
displayDescription: command.description,
33
+
options: command.options?.map((o) => ({
34
+
...o,
35
+
displayName: o.name,
36
+
displayDescription: o.description
37
+
}))
38
+
};
39
+
registeredCommands.push(registered);
40
+
},
41
+
42
+
registerLegacyCommand(id, command) {
43
+
if (command.match) {
44
+
if (command.match instanceof RegExp) {
45
+
command.match = this.anyScopeRegex(command.match);
46
+
} else if (command.match.regex && typeof command.match !== "function") {
47
+
command.match = this.anyScopeRegex(command.match.regex);
48
+
}
49
+
}
50
+
51
+
if (!legacyCommands) {
52
+
queuedLegacyCommands![id] = command;
53
+
} else {
54
+
legacyCommands[id] = command;
55
+
}
56
+
},
57
+
58
+
anyScopeRegex(regex) {
59
+
const out = function (str: string) {
60
+
return regex.exec(str);
61
+
};
62
+
out.regex = regex;
63
+
return out;
64
+
},
65
+
66
+
_getCommands() {
67
+
return [...registeredCommands];
68
+
}
69
+
};
70
+
71
+
export default commands;
-41
packages/core-extensions/src/common/components.ts
-41
packages/core-extensions/src/common/components.ts
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
-
import { CommonComponents } from "@moonlight-mod/types/coreExtensions";
3
-
4
-
export const components: ExtensionWebpackModule = {
5
-
dependencies: [
6
-
{ ext: "spacepack", id: "spacepack" },
7
-
"MasonryList:",
8
-
".flexGutterSmall,"
9
-
//"ALWAYS_WHITE:",
10
-
//".Messages.SWITCH_ACCOUNTS_TOAST_LOGIN_SUCCESS.format"
11
-
],
12
-
run: function (module, exports, require) {
13
-
const spacepack = require("spacepack_spacepack");
14
-
15
-
const Components = spacepack.findByCode("MasonryList:function")[0].exports;
16
-
const MarkdownParser = spacepack.findByCode(
17
-
"parseAutoModerationSystemMessage:"
18
-
)[0].exports.default;
19
-
const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0]
20
-
.exports.default;
21
-
const Flex = spacepack.findByCode(".flex" + "GutterSmall,")[0].exports
22
-
.default;
23
-
const CardClasses = spacepack.findByCode("card", "cardHeader", "inModal")[0]
24
-
.exports;
25
-
const ControlClasses = spacepack.findByCode(
26
-
"title",
27
-
"titleDefault",
28
-
"dividerDefault"
29
-
)[0].exports;
30
-
31
-
let cache: Partial<CommonComponents> = {};
32
-
module.exports = {
33
-
...Components,
34
-
MarkdownParser,
35
-
LegacyText,
36
-
Flex,
37
-
CardClasses,
38
-
ControlClasses
39
-
};
40
-
}
41
-
};
-13
packages/core-extensions/src/common/flux.ts
-13
packages/core-extensions/src/common/flux.ts
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
-
import { CommonFlux } from "@moonlight-mod/types/coreExtensions";
3
-
4
-
const findFlux = ["useStateFromStores:function"];
5
-
6
-
export const flux: ExtensionWebpackModule = {
7
-
dependencies: [{ ext: "spacepack", id: "spacepack" }, ...findFlux],
8
-
run: (module, exports, require) => {
9
-
const spacepack = require("spacepack_spacepack");
10
-
const Flux = spacepack.findByCode(...findFlux)[0].exports;
11
-
module.exports = Flux as CommonFlux;
12
-
}
13
-
};
-16
packages/core-extensions/src/common/fluxDispatcher.ts
-16
packages/core-extensions/src/common/fluxDispatcher.ts
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
-
3
-
export const fluxDispatcher: ExtensionWebpackModule = {
4
-
dependencies: [
5
-
{ ext: "spacepack", id: "spacepack" },
6
-
"isDispatching",
7
-
"dispatch"
8
-
],
9
-
run: (module, exports, require) => {
10
-
const spacepack = require("spacepack_spacepack");
11
-
module.exports = spacepack.findByExports(
12
-
"isDispatching",
13
-
"dispatch"
14
-
)[0].exports.default;
15
-
}
16
-
};
-12
packages/core-extensions/src/common/http.ts
-12
packages/core-extensions/src/common/http.ts
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
-
3
-
const findHTTP = ["get", "put", "V8APIError"];
4
-
5
-
export const http: ExtensionWebpackModule = {
6
-
dependencies: [{ ext: "spacepack", id: "spacepack" }],
7
-
run: (module, exports, require) => {
8
-
const spacepack = require("spacepack_spacepack");
9
-
const HTTP = spacepack.findByExports(...findHTTP)[0].exports;
10
-
module.exports = HTTP.ZP ?? HTTP.Z;
11
-
}
12
-
};
+9
-13
packages/core-extensions/src/common/index.ts
+9
-13
packages/core-extensions/src/common/index.ts
···
1
1
import { ExtensionWebExports } from "@moonlight-mod/types";
2
2
3
-
import { react } from "./react";
4
-
import { flux } from "./flux";
5
-
import { stores } from "./stores";
6
-
import { http } from "./http";
7
-
import { components } from "./components";
8
-
import { fluxDispatcher } from "./fluxDispatcher";
9
-
10
3
export const webpackModules: ExtensionWebExports["webpackModules"] = {
11
-
react,
12
-
flux,
13
-
stores,
14
-
http,
15
-
components,
16
-
fluxDispatcher
4
+
stores: {
5
+
dependencies: [{ id: "discord/packages/flux" }]
6
+
},
7
+
ErrorBoundary: {
8
+
dependencies: [{ id: "react" }]
9
+
},
10
+
icons: {
11
+
dependencies: [{ id: "react" }, { id: "discord/components/common/index" }]
12
+
}
17
13
};
+3
-1
packages/core-extensions/src/common/manifest.json
+3
-1
packages/core-extensions/src/common/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "common",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "Common",
5
-
"tagline": "A *lot* of common clientmodding utilities from the Discord client",
7
+
"tagline": "Common client modding utilities for the Discord client",
6
8
"authors": ["Cynosphere", "NotNite"],
7
9
"tags": ["library"]
8
10
},
-15
packages/core-extensions/src/common/react.ts
-15
packages/core-extensions/src/common/react.ts
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
-
3
-
const findReact = [
4
-
"__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED",
5
-
/\.?version(?:=|:)/,
6
-
/\.?createElement(?:=|:)/
7
-
];
8
-
9
-
export const react: ExtensionWebpackModule = {
10
-
dependencies: [...findReact, { ext: "spacepack", id: "spacepack" }],
11
-
run: (module, exports, require) => {
12
-
const spacepack = require("spacepack_spacepack");
13
-
module.exports = spacepack.findByCode(...findReact)[0].exports;
14
-
}
15
-
};
-30
packages/core-extensions/src/common/stores.ts
-30
packages/core-extensions/src/common/stores.ts
···
1
-
import { ExtensionWebpackModule } from "@moonlight-mod/types";
2
-
3
-
export const stores: ExtensionWebpackModule = {
4
-
dependencies: [{ ext: "common", id: "flux" }],
5
-
run: (module, exports, require) => {
6
-
const Flux = require("common_flux");
7
-
8
-
module.exports = new Proxy(
9
-
{},
10
-
{
11
-
get: function (target, key, receiver) {
12
-
const allStores = Flux.Store.getAll();
13
-
14
-
let targetStore;
15
-
for (const store of allStores) {
16
-
const name = store.getName();
17
-
if (name.length == 1) continue; // filter out unnamed stores
18
-
19
-
if (name == key) {
20
-
targetStore = store;
21
-
break;
22
-
}
23
-
}
24
-
25
-
return targetStore;
26
-
}
27
-
}
28
-
);
29
-
}
30
-
};
+27
packages/core-extensions/src/common/style.css
+27
packages/core-extensions/src/common/style.css
···
1
+
.moonlight-error-boundary {
2
+
margin: 0 0 15px;
3
+
padding: 10px;
4
+
border-radius: 5px;
5
+
font-size: 1rem;
6
+
font-weight: 300;
7
+
line-height: 22px;
8
+
color: var(--text-normal, white);
9
+
background: hsl(var(--red-400-hsl) / 0.1);
10
+
border: 2px solid hsl(var(--red-400-hsl) / 0.5);
11
+
12
+
.theme-light & {
13
+
color: var(--text-normal, black) !important;
14
+
}
15
+
16
+
& > h3 {
17
+
margin-bottom: 0.25rem;
18
+
}
19
+
20
+
& > .hljs {
21
+
background: var(--background-secondary);
22
+
border: 1px solid var(--background-tertiary);
23
+
white-space: pre-wrap;
24
+
font-family: var(--font-code);
25
+
user-select: text;
26
+
}
27
+
}
+47
packages/core-extensions/src/common/webpackModules/ErrorBoundary.tsx
+47
packages/core-extensions/src/common/webpackModules/ErrorBoundary.tsx
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import { ErrorBoundaryProps, ErrorBoundaryState } from "@moonlight-mod/types/coreExtensions/common";
3
+
4
+
const logger = moonlight.getLogger("ErrorBoundary");
5
+
6
+
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
7
+
constructor(props: ErrorBoundaryProps) {
8
+
super(props);
9
+
this.state = {
10
+
errored: false,
11
+
error: undefined,
12
+
componentStack: undefined
13
+
};
14
+
}
15
+
16
+
static getDerivedStateFromError(error: Error) {
17
+
return {
18
+
errored: true,
19
+
error
20
+
};
21
+
}
22
+
23
+
componentDidCatch(error: Error, { componentStack }: { componentStack: string }) {
24
+
logger.error(`${error}\n\nComponent stack:\n${componentStack}`);
25
+
this.setState({ error, componentStack });
26
+
}
27
+
28
+
render() {
29
+
const { noop, fallback: FallbackComponent, children, message } = this.props;
30
+
const { errored, error, componentStack } = this.state;
31
+
32
+
if (FallbackComponent) return <FallbackComponent children={children} {...this.state} />;
33
+
34
+
if (errored) {
35
+
return noop ? null : (
36
+
<div className={`moonlight-error-boundary`}>
37
+
<h3>{message ?? "An error occurred rendering this component:"}</h3>
38
+
<code className="hljs">{`${error}\n\nComponent stack:\n${componentStack}`}</code>
39
+
</div>
40
+
);
41
+
}
42
+
43
+
return children;
44
+
}
45
+
}
46
+
47
+
export default ErrorBoundary;
+31
packages/core-extensions/src/common/webpackModules/icons.ts
+31
packages/core-extensions/src/common/webpackModules/icons.ts
···
1
+
import { Icons, IconSize } from "@moonlight-mod/types/coreExtensions/common";
2
+
import { tokens } from "@moonlight-mod/wp/discord/components/common/index";
3
+
4
+
// This is defined in a Webpack module but we copy it here to be less breakage-prone
5
+
const sizes: Partial<Record<IconSize, number>> = {
6
+
xxs: 12,
7
+
xs: 16,
8
+
sm: 18,
9
+
md: 24,
10
+
lg: 32,
11
+
refresh_sm: 20
12
+
};
13
+
14
+
export const icons: Icons = {
15
+
parseProps(props) {
16
+
// NOTE: var() fallback is non-standard behavior, just for safety reasons
17
+
const color = props?.color ?? tokens?.colors?.["INTERACTIVE_NORMAL"] ?? "var(--interactive-normal)";
18
+
19
+
const size = sizes[props?.size ?? "md"];
20
+
21
+
return {
22
+
// note: this default size is also non-standard behavior, just for safety
23
+
width: size ?? props?.width ?? sizes.md!,
24
+
height: size ?? props?.width ?? sizes.md!,
25
+
26
+
fill: typeof color === "string" ? color : color.css,
27
+
className: props?.colorClass ?? ""
28
+
};
29
+
}
30
+
};
31
+
export default icons;
+23
packages/core-extensions/src/common/webpackModules/stores.ts
+23
packages/core-extensions/src/common/webpackModules/stores.ts
···
1
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
2
+
3
+
module.exports = new Proxy(
4
+
{},
5
+
{
6
+
get: function (target, key, receiver) {
7
+
const allStores = Store.getAll();
8
+
9
+
let targetStore;
10
+
for (const store of allStores) {
11
+
const name = store.getName();
12
+
if (name.length === 1) continue; // filter out unnamed stores
13
+
14
+
if (name === key) {
15
+
targetStore = store;
16
+
break;
17
+
}
18
+
}
19
+
20
+
return targetStore;
21
+
}
22
+
}
23
+
);
+83
packages/core-extensions/src/componentEditor/index.ts
+83
packages/core-extensions/src/componentEditor/index.ts
···
1
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
// dm list
5
+
{
6
+
find: ".interactiveSystemDM]:",
7
+
replace: [
8
+
{
9
+
match: /decorators:(\i\.isSystemDM\(\)\?\(0,\i\.jsx\)\(.+?verified:!0}\):null)/,
10
+
replacement: (_, decorators) =>
11
+
`decorators:require("componentEditor_dmList").default._patchDecorators([${decorators}],arguments[0])`
12
+
},
13
+
{
14
+
match: /(?<=selected:\i,)children:\[/,
15
+
replacement: 'children:require("componentEditor_dmList").default._patchItems(['
16
+
},
17
+
{
18
+
match: /(?<=(onMouseDown|nameplate):\i}\))]/,
19
+
replacement: "],arguments[0])"
20
+
}
21
+
],
22
+
hardFail: true
23
+
},
24
+
25
+
// member list
26
+
{
27
+
find: ".lostPermission",
28
+
replace: [
29
+
{
30
+
match: /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\i\(\),.+?\i\(\)])/,
31
+
replacement: (_, decorators) =>
32
+
`children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])`
33
+
},
34
+
{
35
+
match: /name:null==\i\?\(0,\i\.jsx\)\("span"/,
36
+
replacement: (orig: string) =>
37
+
`children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}`
38
+
}
39
+
]
40
+
},
41
+
42
+
// messages
43
+
{
44
+
find: '},"new-member")),',
45
+
replace: [
46
+
{
47
+
match: /(?<=\.BADGES](=|:))(\i)(;|})/,
48
+
replacement: (_, leading, badges, trailing) =>
49
+
`require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}`
50
+
},
51
+
{
52
+
match: /(?<=className:\i,)badges:(\i)/,
53
+
replacement: (_, badges) =>
54
+
`badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])`
55
+
},
56
+
{
57
+
match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/,
58
+
replacement: (_, elements) =>
59
+
`children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:`
60
+
}
61
+
]
62
+
},
63
+
{
64
+
find: '.provider&&"Discord"===',
65
+
replace: {
66
+
match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/,
67
+
replacement: (_, elements) =>
68
+
`children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})`
69
+
}
70
+
}
71
+
];
72
+
73
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
74
+
dmList: {
75
+
dependencies: [{ id: "react" }]
76
+
},
77
+
memberList: {
78
+
dependencies: [{ id: "react" }]
79
+
},
80
+
messages: {
81
+
dependencies: [{ id: "react" }]
82
+
}
83
+
};
+11
packages/core-extensions/src/componentEditor/manifest.json
+11
packages/core-extensions/src/componentEditor/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "componentEditor",
4
+
"apiLevel": 2,
5
+
"meta": {
6
+
"name": "Component Editor",
7
+
"tagline": "A library to add to commonly patched components",
8
+
"authors": ["Cynosphere"],
9
+
"tags": ["library"]
10
+
}
11
+
}
+61
packages/core-extensions/src/componentEditor/webpackModules/dmList.tsx
+61
packages/core-extensions/src/componentEditor/webpackModules/dmList.tsx
···
1
+
import {
2
+
DMList,
3
+
DMListItem,
4
+
DMListDecorator,
5
+
DMListAnchorIndicies,
6
+
DMListDecoratorAnchorIndicies
7
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
8
+
import React from "@moonlight-mod/wp/react";
9
+
10
+
const items: Record<string, DMListItem> = {};
11
+
const decorators: Record<string, DMListDecorator> = {};
12
+
13
+
function addEntries(
14
+
elements: React.ReactNode[],
15
+
entries: Record<string, DMListItem | DMListDecorator>,
16
+
indicies: Partial<Record<keyof typeof DMListAnchorIndicies | keyof typeof DMListDecoratorAnchorIndicies, number>>,
17
+
props: any
18
+
) {
19
+
const originalElements = [...elements];
20
+
for (const [id, entry] of Object.entries(entries)) {
21
+
const component = <entry.component {...props} key={id} />;
22
+
23
+
if (entry.anchor === undefined) {
24
+
if (entry.before) {
25
+
elements.splice(0, 0, component);
26
+
} else {
27
+
elements.push(component);
28
+
}
29
+
} else {
30
+
const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);
31
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
32
+
}
33
+
}
34
+
}
35
+
36
+
export const dmList: DMList = {
37
+
addItem(id, component, anchor, before = false) {
38
+
items[id] = {
39
+
component,
40
+
anchor,
41
+
before
42
+
};
43
+
},
44
+
addDecorator(id, component, anchor, before = false) {
45
+
decorators[id] = {
46
+
component,
47
+
anchor,
48
+
before
49
+
};
50
+
},
51
+
_patchItems(elements, props) {
52
+
addEntries(elements, items, DMListAnchorIndicies, props);
53
+
return elements;
54
+
},
55
+
_patchDecorators(elements, props) {
56
+
addEntries(elements, decorators, DMListDecoratorAnchorIndicies, props);
57
+
return elements;
58
+
}
59
+
};
60
+
61
+
export default dmList;
+50
packages/core-extensions/src/componentEditor/webpackModules/memberList.tsx
+50
packages/core-extensions/src/componentEditor/webpackModules/memberList.tsx
···
1
+
import {
2
+
MemberList,
3
+
MemberListDecorator,
4
+
MemberListDecoratorAnchorIndicies
5
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
6
+
import React from "@moonlight-mod/wp/react";
7
+
8
+
const items: Record<string, React.FC<any>> = {};
9
+
const decorators: Record<string, MemberListDecorator> = {};
10
+
11
+
export const memberList: MemberList = {
12
+
addItem(id, component) {
13
+
items[id] = component;
14
+
},
15
+
addDecorator(id, component, anchor, before = false) {
16
+
decorators[id] = {
17
+
component,
18
+
anchor,
19
+
before
20
+
};
21
+
},
22
+
_patchItems(elements, props) {
23
+
for (const [id, Component] of Object.entries(items)) {
24
+
elements.push(<Component {...props} key={id} />);
25
+
}
26
+
27
+
return elements;
28
+
},
29
+
_patchDecorators(elements, props) {
30
+
const originalElements = [...elements];
31
+
for (const [id, entry] of Object.entries(decorators)) {
32
+
const component = <entry.component {...props} key={id} />;
33
+
34
+
if (entry.anchor === undefined) {
35
+
if (entry.before) {
36
+
elements.splice(0, 0, component);
37
+
} else {
38
+
elements.push(component);
39
+
}
40
+
} else {
41
+
const index = elements.indexOf(originalElements[MemberListDecoratorAnchorIndicies[entry.anchor]!]);
42
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
43
+
}
44
+
}
45
+
46
+
return elements;
47
+
}
48
+
};
49
+
50
+
export default memberList;
+97
packages/core-extensions/src/componentEditor/webpackModules/messages.tsx
+97
packages/core-extensions/src/componentEditor/webpackModules/messages.tsx
···
1
+
import {
2
+
MessageBadge,
3
+
MessageBadgeIndicies,
4
+
Messages,
5
+
MessageUsername,
6
+
MessageUsernameBadge,
7
+
MessageUsernameBadgeIndicies,
8
+
MessageUsernameIndicies
9
+
} from "@moonlight-mod/types/coreExtensions/componentEditor";
10
+
import React from "@moonlight-mod/wp/react";
11
+
12
+
const username: Record<string, MessageUsername> = {};
13
+
const usernameBadges: Record<string, MessageUsernameBadge> = {};
14
+
const badges: Record<string, MessageBadge> = {};
15
+
const accessories: Record<string, React.FC<any>> = {};
16
+
17
+
function addEntries(
18
+
elements: React.ReactNode[],
19
+
entries: Record<string, MessageUsername | MessageUsernameBadge | MessageBadge>,
20
+
indicies: Partial<
21
+
Record<
22
+
| keyof typeof MessageUsernameIndicies
23
+
| keyof typeof MessageUsernameBadgeIndicies
24
+
| keyof typeof MessageBadgeIndicies,
25
+
number
26
+
>
27
+
>,
28
+
props: any
29
+
) {
30
+
const originalElements = [...elements];
31
+
for (const [id, entry] of Object.entries(entries)) {
32
+
const component = <entry.component {...props} key={id} />;
33
+
34
+
if (entry.anchor === undefined) {
35
+
if (entry.before) {
36
+
elements.splice(0, 0, component);
37
+
} else {
38
+
elements.push(component);
39
+
}
40
+
} else {
41
+
const index = elements.indexOf(originalElements[indicies[entry.anchor]!]);
42
+
elements.splice(index! + (entry.before ? 0 : 1), 0, component);
43
+
}
44
+
}
45
+
}
46
+
47
+
function addComponents(elements: React.ReactNode[], components: Record<string, React.FC<any>>, props: any) {
48
+
for (const [id, Component] of Object.entries(components)) {
49
+
const component = <Component {...props} key={id} />;
50
+
elements.push(component);
51
+
}
52
+
}
53
+
54
+
export const messages: Messages = {
55
+
addToUsername(id, component, anchor, before = false) {
56
+
username[id] = {
57
+
component,
58
+
anchor,
59
+
before
60
+
};
61
+
},
62
+
addUsernameBadge(id, component, anchor, before = false) {
63
+
usernameBadges[id] = {
64
+
component,
65
+
anchor,
66
+
before
67
+
};
68
+
},
69
+
addBadge(id, component, anchor, before = false) {
70
+
badges[id] = {
71
+
component,
72
+
anchor,
73
+
before
74
+
};
75
+
},
76
+
addAccessory(id, component) {
77
+
accessories[id] = component;
78
+
},
79
+
_patchUsername(elements, props) {
80
+
addEntries(elements, username, MessageUsernameIndicies, props);
81
+
return elements;
82
+
},
83
+
_patchUsernameBadges(elements, props) {
84
+
addEntries(elements, usernameBadges, MessageUsernameBadgeIndicies, props);
85
+
return elements;
86
+
},
87
+
_patchBadges(elements, props) {
88
+
addEntries(elements, badges, MessageBadgeIndicies, props);
89
+
return elements;
90
+
},
91
+
_patchAccessories(elements, props) {
92
+
addComponents(elements, accessories, props);
93
+
return elements;
94
+
}
95
+
};
96
+
97
+
export default messages;
+31
packages/core-extensions/src/contextMenu/index.tsx
+31
packages/core-extensions/src/contextMenu/index.tsx
···
1
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: "Menu API only allows Items and groups of Items as children.",
6
+
replace: [
7
+
{
8
+
match: /(?<=let{navId[^}]+?}=(.),.=).+?(?=,)/,
9
+
replacement: (items, props) => `require("contextMenu_contextMenu")._patchMenu(${props},${items})`
10
+
}
11
+
]
12
+
},
13
+
{
14
+
find: ".getContextMenu(",
15
+
replace: [
16
+
{
17
+
match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/,
18
+
replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})`
19
+
}
20
+
]
21
+
}
22
+
];
23
+
24
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
25
+
contextMenu: {
26
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."]
27
+
},
28
+
evilMenu: {
29
+
dependencies: [{ ext: "spacepack", id: "spacepack" }, "Menu API only allows Items and groups of Items as children."]
30
+
}
31
+
};
+11
packages/core-extensions/src/contextMenu/manifest.json
+11
packages/core-extensions/src/contextMenu/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "contextMenu",
4
+
"apiLevel": 2,
5
+
"meta": {
6
+
"name": "Context Menu",
7
+
"tagline": "A library for patching and creating context menus",
8
+
"authors": ["redstonekasi"],
9
+
"tags": ["library"]
10
+
}
11
+
}
+88
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
+88
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
···
1
+
import { InternalItem, Menu, MenuElement } from "@moonlight-mod/types/coreExtensions/contextMenu";
2
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
3
+
import parser from "@moonlight-mod/wp/contextMenu_evilMenu";
4
+
5
+
// NOTE: We originally had item as a function that returned this, but it didn't
6
+
// quite know how to work out the type and thought it was a JSX element (it
7
+
// *technically* was). This has less type safety, but a @ts-expect-error has
8
+
// zero, so it's better than nothing.
9
+
type ReturnType = MenuElement | MenuElement[];
10
+
11
+
type Patch = {
12
+
navId: string;
13
+
item: React.FC<any>;
14
+
anchor: string | RegExp;
15
+
before: boolean;
16
+
};
17
+
18
+
function addItem<T = any>(navId: string, item: React.FC<T>, anchor: string | RegExp, before = false) {
19
+
if (anchor instanceof RegExp && anchor.flags.includes("g"))
20
+
throw new Error("anchor regular expression should not be global");
21
+
patches.push({ navId, item, anchor, before });
22
+
}
23
+
24
+
const patches: Patch[] = [];
25
+
function _patchMenu(props: React.ComponentProps<Menu>, items: InternalItem[]) {
26
+
const matches = patches.filter((p) => p.navId === props.navId);
27
+
if (!matches.length) return items;
28
+
29
+
for (const patch of matches) {
30
+
const idx = items.findIndex((i) =>
31
+
typeof patch.anchor === "string" ? i.key === patch.anchor : patch.anchor.test(i.key!)
32
+
);
33
+
if (idx === -1) continue;
34
+
items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps) as ReturnType));
35
+
}
36
+
37
+
return items;
38
+
}
39
+
40
+
let menuProps: any;
41
+
function _saveProps(self: any, el: any) {
42
+
menuProps = el.props;
43
+
44
+
const original = self.props.closeContextMenu;
45
+
self.props.closeContextMenu = function (...args: any[]) {
46
+
menuProps = undefined;
47
+
return original?.apply(this, args);
48
+
};
49
+
50
+
return el;
51
+
}
52
+
53
+
module.exports = {
54
+
patches,
55
+
addItem,
56
+
_patchMenu,
57
+
_saveProps
58
+
};
59
+
60
+
// Unmangle Menu elements
61
+
// spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString();
62
+
const code =
63
+
spacepack.require.m[
64
+
spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id
65
+
].toString();
66
+
67
+
let MangledMenu;
68
+
69
+
const typeRegex = /if\(.\.type===(.)\.(.+?)\).+?type:"(.+?)"/g;
70
+
const typeMap: Record<string, string | undefined> = {
71
+
checkbox: "MenuCheckboxItem",
72
+
control: "MenuControlItem",
73
+
groupstart: "MenuGroup",
74
+
customitem: "MenuItem",
75
+
radio: "MenuRadioItem",
76
+
separator: "MenuSeparator"
77
+
};
78
+
79
+
for (const [, modIdent, mangled, type] of code.matchAll(typeRegex)) {
80
+
if (!MangledMenu) {
81
+
const modId = code.match(new RegExp(`${modIdent}=.\\((\\d+?)\\)`))![1];
82
+
MangledMenu = spacepack.require(modId);
83
+
}
84
+
85
+
const prop = typeMap[type];
86
+
if (!prop) continue;
87
+
module.exports[prop] = MangledMenu[mangled];
88
+
}
+18
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
+18
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
···
1
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
2
+
3
+
// spacepack.require.m[moonlight.moonmap.modules["discord/modules/menus/web/Menu"]].toString();
4
+
let code =
5
+
spacepack.require.m[
6
+
spacepack.findByCode("Menu API only allows Items and groups of Items as children.")[0].id
7
+
].toString();
8
+
9
+
const parserSym = code.match(/(?<=_patchMenu\(.,).+?(?=\()/)![0];
10
+
11
+
code = code.replace(/{(.):\(\)=>./, (orig, e) => `{${e}:()=>${parserSym}`);
12
+
const mod = new Function("module", "exports", "require", `(${code}).apply(this, arguments)`);
13
+
14
+
const exp: any = {};
15
+
mod({}, exp, require);
16
+
17
+
const parser = spacepack.findFunctionByStrings(exp, "Menu API only allows Items and groups of Items as children.")!;
18
+
module.exports = parser;
+19
packages/core-extensions/src/devToolsExtensions/host.ts
+19
packages/core-extensions/src/devToolsExtensions/host.ts
···
1
+
import { app, session } from "electron";
2
+
import { resolve } from "node:path";
3
+
import Logger from "@moonlight-mod/core/util/logger";
4
+
5
+
const logger = new Logger("DevTools Extensions");
6
+
7
+
app.whenReady().then(async () => {
8
+
const paths = moonlightHost.getConfigOption<string[]>("devToolsExtensions", "paths") ?? [];
9
+
10
+
for (const path of paths) {
11
+
const resolved = resolve(path);
12
+
13
+
try {
14
+
await session.defaultSession.loadExtension(resolved);
15
+
} catch (err) {
16
+
logger.error(`Failed to load an extension in "${resolved}":`, err);
17
+
}
18
+
}
19
+
});
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
+22
packages/core-extensions/src/devToolsExtensions/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "devToolsExtensions",
4
+
"meta": {
5
+
"name": "DevTools Extensions",
6
+
"tagline": "Loads Chrome extensions into Electron DevTools",
7
+
"authors": [
8
+
"Cynosphere"
9
+
],
10
+
"tags": [
11
+
"development"
12
+
]
13
+
},
14
+
"settings": {
15
+
"paths": {
16
+
"advice": "restart",
17
+
"displayName": "Extension Paths",
18
+
"type": "list"
19
+
}
20
+
},
21
+
"apiLevel": 2
22
+
}
+15
-32
packages/core-extensions/src/disableSentry/host.ts
+15
-32
packages/core-extensions/src/disableSentry/host.ts
···
1
1
import { join } from "node:path";
2
2
import { Module } from "node:module";
3
-
import { BrowserWindow } from "electron";
4
3
5
4
const logger = moonlightHost.getLogger("disableSentry");
6
5
7
-
try {
8
-
const hostSentryPath = require.resolve(
9
-
join(moonlightHost.asarPath, "node_modules", "@sentry", "electron")
10
-
);
11
-
require.cache[hostSentryPath] = new Module(
12
-
hostSentryPath,
13
-
require.cache[require.resolve(moonlightHost.asarPath)]
14
-
);
15
-
require.cache[hostSentryPath]!.exports = {
16
-
init: () => {},
17
-
captureException: () => {},
18
-
setTag: () => {},
19
-
setUser: () => {}
20
-
};
21
-
logger.debug("Stubbed Sentry host side!");
22
-
} catch (err) {
23
-
logger.error("Failed to stub Sentry host side:", err);
6
+
if (moonlightHost.asarPath !== "moonlightDesktop") {
7
+
try {
8
+
const hostSentryPath = require.resolve(join(moonlightHost.asarPath, "node_modules", "@sentry", "electron"));
9
+
require.cache[hostSentryPath] = new Module(hostSentryPath, require.cache[require.resolve(moonlightHost.asarPath)]);
10
+
require.cache[hostSentryPath]!.exports = {
11
+
init: () => {},
12
+
captureException: () => {},
13
+
setTag: () => {},
14
+
setUser: () => {},
15
+
captureMessage: () => {}
16
+
};
17
+
logger.debug("Stubbed Sentry host side!");
18
+
} catch (err) {
19
+
logger.error("Failed to stub Sentry host side:", err);
20
+
}
24
21
}
25
-
26
-
moonlightHost.events.on("window-created", (window: BrowserWindow) => {
27
-
window.webContents.session.webRequest.onBeforeRequest(
28
-
{
29
-
urls: [
30
-
"https://*.sentry.io/*",
31
-
"https://*.discord.com/error-reporting-proxy/*"
32
-
]
33
-
},
34
-
function (details, callback) {
35
-
callback({ cancel: true });
36
-
}
37
-
);
38
-
});
+9
-54
packages/core-extensions/src/disableSentry/index.ts
+9
-54
packages/core-extensions/src/disableSentry/index.ts
···
3
3
4
4
export const patches: Patch[] = [
5
5
{
6
-
find: "DSN:function",
7
-
replace: {
8
-
type: PatchReplaceType.Normal,
9
-
match: /default:function\(\){return .}/,
10
-
replacement: 'default:function(){return require("disableSentry_stub")()}'
11
-
}
12
-
},
13
-
{
14
-
find: "window.DiscordSentry.addBreadcrumb",
6
+
find: "profiledRootComponent:",
15
7
replace: {
16
8
type: PatchReplaceType.Normal,
17
-
match: /default:function\(\){return .}/,
18
-
replacement:
19
-
'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}'
9
+
match: /Z:\(\)=>\i/,
10
+
replacement: 'Z:()=>require("disableSentry_stub").proxy()'
20
11
}
21
12
},
22
13
{
23
-
find: "initSentry:function",
14
+
find: "this._sentryUtils=",
24
15
replace: {
25
16
type: PatchReplaceType.Normal,
26
-
match: /initSentry:function\(\){return .}/,
27
-
replacement: "default:function(){return ()=>{}}"
17
+
match: /(?<=this._sentryUtils=)./,
18
+
replacement: "undefined"
28
19
}
29
20
},
30
21
{
31
22
find: "window.DiscordErrors=",
32
23
replace: {
33
24
type: PatchReplaceType.Normal,
34
-
match: /uses_client_mods:\(0,.\.usesClientMods\)\(\)/,
35
-
replacement: "uses_client_mods:false"
25
+
match: /(?<=uses_client_mods:)./,
26
+
replacement: "false"
36
27
}
37
28
}
38
29
];
39
30
40
31
export const webpackModules: ExtensionWebExports["webpackModules"] = {
41
-
stub: {
42
-
run: function (module, exports, require) {
43
-
const logger = moonlight.getLogger("disableSentry");
44
-
45
-
const keys = [
46
-
"setUser",
47
-
"clearUser",
48
-
"setTags",
49
-
"setExtra",
50
-
"captureException",
51
-
"captureCrash",
52
-
"captureMessage",
53
-
"addBreadcrumb"
54
-
];
55
-
56
-
module.exports = () =>
57
-
new Proxy(
58
-
{},
59
-
{
60
-
get(target, prop, receiver) {
61
-
if (prop === "profiledRootComponent") {
62
-
return (arg: any) => arg;
63
-
} else if (prop === "crash") {
64
-
return () => {
65
-
throw Error("crash");
66
-
};
67
-
} else if (keys.includes(prop.toString())) {
68
-
return (...args: any[]) =>
69
-
logger.debug(`Sentry calling "${prop.toString()}":`, ...args);
70
-
} else {
71
-
return undefined;
72
-
}
73
-
}
74
-
}
75
-
);
76
-
}
77
-
}
32
+
stub: {}
78
33
};
+12
-1
packages/core-extensions/src/disableSentry/manifest.json
+12
-1
packages/core-extensions/src/disableSentry/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "disableSentry",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "Disable Sentry",
5
7
"tagline": "Turns off Discord's error reporting systems",
6
8
"authors": ["Cynosphere", "NotNite"],
7
9
"tags": ["privacy"]
8
-
}
10
+
},
11
+
"blocked": [
12
+
"https://*.sentry.io/*",
13
+
"https://*.discord.com/error-reporting-proxy/*",
14
+
"https://discord.com/assets/sentry.*.js",
15
+
"https://*.discord.com/assets/sentry.*.js",
16
+
"https://*.discordapp.com/error-reporting-proxy/*",
17
+
"https://discordapp.com/assets/sentry.*.js",
18
+
"https://*.discordapp.com/assets/sentry.*.js"
19
+
]
9
20
}
+4
-8
packages/core-extensions/src/disableSentry/node.ts
+4
-8
packages/core-extensions/src/disableSentry/node.ts
···
7
7
8
8
const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
9
9
try {
10
-
const sentryPath = require.resolve(
11
-
resolve(preloadPath, "..", "node_modules", "@sentry", "electron")
12
-
);
13
-
require.cache[sentryPath] = new Module(
14
-
sentryPath,
15
-
require.cache[require.resolve(preloadPath)]
16
-
);
10
+
const sentryPath = require.resolve(resolve(preloadPath, "..", "node_modules", "@sentry", "electron"));
11
+
require.cache[sentryPath] = new Module(sentryPath, require.cache[require.resolve(preloadPath)]);
17
12
require.cache[sentryPath]!.exports = {
18
13
init: () => {},
19
14
setTag: () => {},
20
-
setUser: () => {}
15
+
setUser: () => {},
16
+
captureMessage: () => {}
21
17
};
22
18
logger.debug("Stubbed Sentry node side!");
23
19
} catch (err) {
+32
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
+32
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
···
1
+
const logger = moonlight.getLogger("disableSentry");
2
+
3
+
const keys = [
4
+
"setUser",
5
+
"clearUser",
6
+
"setTags",
7
+
"setExtra",
8
+
"captureException",
9
+
"captureCrash",
10
+
"captureMessage",
11
+
"addBreadcrumb"
12
+
];
13
+
14
+
export const proxy = () =>
15
+
new Proxy(
16
+
{},
17
+
{
18
+
get(target, prop, receiver) {
19
+
if (prop === "profiledRootComponent") {
20
+
return (arg: any) => arg;
21
+
} else if (prop === "crash") {
22
+
return () => {
23
+
throw Error("crash");
24
+
};
25
+
} else if (keys.includes(prop.toString())) {
26
+
return (...args: any[]) => logger.debug(`Sentry calling "${prop.toString()}":`, ...args);
27
+
} else {
28
+
return undefined;
29
+
}
30
+
}
31
+
}
32
+
);
+45
-18
packages/core-extensions/src/experiments/index.ts
+45
-18
packages/core-extensions/src/experiments/index.ts
···
2
2
3
3
export const patches: Patch[] = [
4
4
{
5
-
find: /\.displayName="(Developer)?ExperimentStore"/,
5
+
find: "isStaffPersonal:",
6
+
replace: {
7
+
match: /&&null!=this\.personalConnectionId/,
8
+
replacement: "||!0"
9
+
}
10
+
},
11
+
{
12
+
find: '"scientist:triggered"', // Scientist? Triggered.
13
+
replace: {
14
+
match: ".personal_connection_id",
15
+
replacement: ".personal_connection_id || true"
16
+
}
17
+
},
18
+
19
+
// Enable staff help menu
20
+
{
21
+
find: ".HEADER_BAR)",
22
+
replace: {
23
+
match: /&&\((.)\?\(0,/,
24
+
replacement: (_, isStaff) =>
25
+
`&&(((moonlight.getConfigOption("experiments","devtools")??false)?true:${isStaff})?(0,`
26
+
}
27
+
},
28
+
// staff help menu - visual refresh
29
+
{
30
+
find: '("AppTitleBar")',
31
+
replace: {
32
+
match: /{hasBugReporterAccess:(\i)}=\i\.\i\.useExperiment\({location:"HeaderBar"},{autoTrackExposure:!1}\);/,
33
+
replacement: (orig, isStaff) =>
34
+
`${orig}if(moonlight.getConfigOption("experiments","devtools")??false)${isStaff}=true;`
35
+
}
36
+
},
37
+
{
38
+
find: 'navId:"staff-help-popout",',
6
39
replace: {
7
-
match: "window.GLOBAL_ENV.RELEASE_CHANNEL",
8
-
replacement: '"staging"'
40
+
match: /isDiscordDeveloper:(\i)}\),/,
41
+
replacement: (_, isStaff) =>
42
+
`isDiscordDeveloper:(moonlight.getConfigOption("experiments","devtools")??false)||${isStaff}}),`
9
43
}
10
44
},
45
+
46
+
// Enable further staff-locked options
11
47
{
12
-
find: '.displayName="DeveloperExperimentStore"',
13
-
replace: [
14
-
{
15
-
match: /CONNECTION_OPEN:.,OVERLAY_INITIALIZE:.,CURRENT_USER_UPDATE:./,
16
-
replacement: ""
17
-
},
18
-
{
19
-
match: '"production"',
20
-
replacement: '"development"'
21
-
},
22
-
{
23
-
match: /(get:function\(\){return .}}}\);).\(\);/,
24
-
replacement: "$1"
25
-
}
26
-
]
48
+
find: "shouldShowLurkerModeUpsellPopout:",
49
+
replace: {
50
+
match: /\.useReducedMotion,isStaff:(.),/,
51
+
replacement: (_, isStaff) =>
52
+
`.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff},`
53
+
}
27
54
}
28
55
];
+17
-1
packages/core-extensions/src/experiments/manifest.json
+17
-1
packages/core-extensions/src/experiments/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "experiments",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "Experiments",
5
7
"tagline": "Allows you to configure Discord's internal A/B testing features",
6
-
"authors": ["NotNite"],
8
+
"authors": ["NotNite", "Cynosphere"],
7
9
"tags": ["dangerZone"]
10
+
},
11
+
"settings": {
12
+
"devtools": {
13
+
"advice": "reload",
14
+
"displayName": "Enable staff help menu (DevTools)",
15
+
"type": "boolean",
16
+
"default": false
17
+
},
18
+
"staffSettings": {
19
+
"advice": "reload",
20
+
"displayName": "Allow access to other staff settings elsewhere",
21
+
"type": "boolean",
22
+
"default": false
23
+
}
8
24
}
9
25
}
+43
packages/core-extensions/src/markdown/index.ts
+43
packages/core-extensions/src/markdown/index.ts
···
1
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: "/^(ยฏ\\\\_\\(ใ\\)_\\/ยฏ)/.exec",
6
+
replace: [
7
+
{
8
+
match: /={newline:(.+?)},(.{1,2})=\(0,/,
9
+
replacement: (_, rules, RULES) => `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,`
10
+
},
11
+
{
12
+
match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/,
13
+
replacement: (_, rulesets) => `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});`
14
+
}
15
+
]
16
+
},
17
+
{
18
+
find: "then you probably need to add it to this file so that the rich chat box understands it.",
19
+
replace: [
20
+
{
21
+
match: /(.)={link:{(.+?)},(.)=new Set/,
22
+
replacement: (_, rulesDef, rules, syntaxBefore) =>
23
+
`__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set`
24
+
},
25
+
{
26
+
match: /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/,
27
+
replacement: (_, start, rule, body, plaintextReturn, otherRules, inlineStyleBody) =>
28
+
`${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"subtext":{${inlineStyleBody}}default:`
29
+
}
30
+
]
31
+
},
32
+
{
33
+
find: '"Slate: Unknown decoration attribute: "',
34
+
replace: {
35
+
match: /=({strong:.+?});/,
36
+
replacement: (_, rules) => `=require("markdown_markdown")._addSlateDecorators(${rules});`
37
+
}
38
+
}
39
+
];
40
+
41
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
42
+
markdown: {}
43
+
};
+11
packages/core-extensions/src/markdown/manifest.json
+11
packages/core-extensions/src/markdown/manifest.json
+68
packages/core-extensions/src/markdown/webpackModules/markdown.ts
+68
packages/core-extensions/src/markdown/webpackModules/markdown.ts
···
1
+
import { MarkdownRule, Ruleset, SlateRule } from "@moonlight-mod/types/coreExtensions/markdown";
2
+
3
+
export const rules: Record<string, (rules: Record<string, MarkdownRule>) => MarkdownRule> = {};
4
+
export const slateRules: Record<string, (rules: Record<string, SlateRule>) => SlateRule> = {};
5
+
export const slateDecorators: Record<string, string> = {};
6
+
export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = {
7
+
RULES: {},
8
+
CHANNEL_TOPIC_RULES: {},
9
+
VOICE_CHANNEL_STATUS_RULES: {},
10
+
EMBED_TITLE_RULES: {},
11
+
INLINE_REPLY_RULES: {},
12
+
GUILD_VERIFICATION_FORM_RULES: {},
13
+
GUILD_EVENT_RULES: {},
14
+
PROFILE_BIO_RULES: {},
15
+
AUTO_MODERATION_SYSTEM_MESSAGE_RULES: {},
16
+
NATIVE_SEARCH_RESULT_LINK_RULES: {}
17
+
};
18
+
19
+
export function addRule(
20
+
name: string,
21
+
markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule,
22
+
slate: (rules: Record<string, SlateRule>) => SlateRule,
23
+
decorator?: string
24
+
) {
25
+
rules[name] = markdown;
26
+
slateRules[name] = slate;
27
+
if (decorator != null) slateDecorators[name] = decorator;
28
+
}
29
+
30
+
export function blacklistFromRuleset(ruleset: Ruleset, name: string) {
31
+
if (ruleBlacklists[ruleset] == null) ruleBlacklists[ruleset] = {};
32
+
ruleBlacklists[ruleset][name] = true;
33
+
}
34
+
35
+
export function _addRules(originalRules: Record<string, MarkdownRule>) {
36
+
for (const name in rules) {
37
+
originalRules["__moonlight_" + name] = rules[name](originalRules);
38
+
}
39
+
40
+
return originalRules;
41
+
}
42
+
43
+
export function _addSlateRules(originalRules: Record<string, SlateRule>) {
44
+
for (const name in slateRules) {
45
+
originalRules["__moonlight_" + name] = slateRules[name](originalRules);
46
+
}
47
+
48
+
return originalRules;
49
+
}
50
+
51
+
export function _addSlateDecorators(originalRules: Record<string, string>) {
52
+
for (const name in slateDecorators) {
53
+
originalRules["__moonlight_" + name] = slateDecorators[name];
54
+
}
55
+
56
+
return originalRules;
57
+
}
58
+
59
+
export function _applyRulesetBlacklist(rulesets: Record<Ruleset, Record<string, MarkdownRule>>) {
60
+
for (const ruleset of Object.keys(rulesets) as Ruleset[]) {
61
+
if (ruleset === "RULES") continue;
62
+
63
+
const rules = rulesets[ruleset];
64
+
for (const rule in ruleBlacklists[ruleset] || {}) {
65
+
delete rules["__moonlight_" + rule];
66
+
}
67
+
}
68
+
}
+108
packages/core-extensions/src/moonbase/host.ts
+108
packages/core-extensions/src/moonbase/host.ts
···
1
+
import * as electron from "electron";
2
+
import * as fs from "node:fs/promises";
3
+
import * as path from "node:path";
4
+
import getNatives from "./native";
5
+
import { MoonlightBranch } from "@moonlight-mod/types";
6
+
7
+
const natives = getNatives();
8
+
9
+
const confirm = (action: string) =>
10
+
electron.dialog
11
+
.showMessageBox({
12
+
title: "Are you sure?",
13
+
message: `Are you sure? This will ${action} and restart Discord.`,
14
+
type: "warning",
15
+
buttons: ["OK", "Cancel"]
16
+
})
17
+
.then((r) => r.response === 0);
18
+
19
+
async function updateAndRestart() {
20
+
if (!(await confirm("update moonlight"))) return;
21
+
const newVersion = await natives.checkForMoonlightUpdate();
22
+
23
+
if (newVersion === null) {
24
+
electron.dialog.showMessageBox({ message: "You are already on the latest version of moonlight." });
25
+
return;
26
+
}
27
+
28
+
try {
29
+
await natives.updateMoonlight();
30
+
await electron.dialog.showMessageBox({ message: "Update successful, restarting Discord." });
31
+
electron.app.relaunch();
32
+
electron.app.exit(0);
33
+
} catch {
34
+
await electron.dialog.showMessageBox({
35
+
message: "Failed to update moonlight. Please use the installer instead.",
36
+
type: "error"
37
+
});
38
+
}
39
+
}
40
+
41
+
async function resetConfig() {
42
+
if (!(await confirm("reset your configuration"))) return;
43
+
44
+
const config = await moonlightHost.getConfigPath();
45
+
const dir = path.dirname(config);
46
+
const branch = path.basename(config, ".json");
47
+
await fs.rename(config, path.join(dir, `${branch}-backup-${Math.floor(Date.now() / 1000)}.json`));
48
+
49
+
await electron.dialog.showMessageBox({ message: "Configuration reset, restarting Discord." });
50
+
electron.app.relaunch();
51
+
electron.app.exit(0);
52
+
}
53
+
54
+
async function changeBranch(branch: MoonlightBranch) {
55
+
if (moonlightHost.branch === branch) return;
56
+
if (!(await confirm("switch branches"))) return;
57
+
try {
58
+
await natives.updateMoonlight(branch);
59
+
await electron.dialog.showMessageBox({ message: "Branch switch successful, restarting Discord." });
60
+
electron.app.relaunch();
61
+
electron.app.exit(0);
62
+
} catch (e) {
63
+
await electron.dialog.showMessageBox({ message: "Failed to switch branches:\n" + e, type: "error" });
64
+
}
65
+
}
66
+
67
+
function showAbout() {
68
+
electron.dialog.showMessageBox({
69
+
title: "About moonlight",
70
+
message: `moonlight ${moonlightHost.branch} ${moonlightHost.version}`
71
+
});
72
+
}
73
+
74
+
electron.app.whenReady().then(() => {
75
+
const original = electron.Menu.buildFromTemplate;
76
+
electron.Menu.buildFromTemplate = function (entries) {
77
+
const i = entries.findIndex((e) => e.label === "Check for Updates...");
78
+
if (i === -1) return original.call(this, entries);
79
+
80
+
if (!entries.find((e) => e.label === "moonlight")) {
81
+
const options: Electron.MenuItemConstructorOptions[] = [
82
+
{ label: "Update and restart", click: updateAndRestart },
83
+
{ label: "Reset config", click: resetConfig }
84
+
];
85
+
86
+
if (moonlightHost.branch !== MoonlightBranch.DEV) {
87
+
options.push({
88
+
label: "Switch branch",
89
+
submenu: [MoonlightBranch.STABLE, MoonlightBranch.NIGHTLY].map((branch) => ({
90
+
label: branch,
91
+
type: "radio",
92
+
checked: moonlightHost.branch === branch,
93
+
click: () => changeBranch(branch)
94
+
}))
95
+
});
96
+
}
97
+
98
+
options.push({ label: "About", click: showAbout });
99
+
100
+
entries.splice(i + 1, 0, {
101
+
label: "moonlight",
102
+
submenu: options
103
+
});
104
+
}
105
+
106
+
return original.call(this, entries);
107
+
};
108
+
});
+88
-45
packages/core-extensions/src/moonbase/index.tsx
+88
-45
packages/core-extensions/src/moonbase/index.tsx
···
1
-
import { ExtensionWebExports } from "@moonlight-mod/types";
2
-
import ui from "./ui";
3
-
import { stores } from "./stores";
4
-
import { DownloadIconSVG, TrashIconSVG } from "./types";
1
+
import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: "window.DiscordErrors=",
6
+
replace: [
7
+
// replace reporting line with update status
8
+
{
9
+
// CvQlAA mapped to ERRORS_ACTION_TO_TAKE
10
+
// FIXME: Better patch find?
11
+
match: /,(\(0,(\i)\.jsx\))\("p",{children:\i\.\i\.string\(\i\.\i\.CvQlAA\)}\)/,
12
+
replacement: (_, createElement, ReactJSX) =>
13
+
`,${createElement}(require("moonbase_crashScreen")?.UpdateText??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})`
14
+
},
15
+
16
+
// wrap actions field to display error details
17
+
{
18
+
match: /(?<=return(\(0,(\i)\.jsx\))\(.+?,)action:(\i),className:/,
19
+
replacement: (_, createElement, ReactJSX, action) =>
20
+
`action:require("moonbase_crashScreen")?.wrapAction?${createElement}(require("moonbase_crashScreen").wrapAction,{action:${action},state:this.state}):${action},className:`
21
+
},
22
+
23
+
// add update button
24
+
// +hivLS -> ERRORS_RELOAD
25
+
{
26
+
match: /(?<=\["\+hivLS"\]\)}\),(\(0,(\i)\.jsx\))\(\i,{}\))/,
27
+
replacement: (_, createElement, ReactJSX) =>
28
+
`,${createElement}(require("moonbase_crashScreen")?.UpdateButton??${ReactJSX}.Fragment,{state:this.state,setState:this.setState.bind(this)})`
29
+
}
30
+
]
31
+
}
32
+
];
5
33
6
-
export const webpackModules: ExtensionWebExports["webpackModules"] = {
34
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
7
35
stores: {
36
+
dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }]
37
+
},
38
+
39
+
ui: {
8
40
dependencies: [
9
-
{ ext: "common", id: "flux" },
10
-
{ ext: "common", id: "fluxDispatcher" }
11
-
],
12
-
run: (module, exports, require) => {
13
-
module.exports = stores(require);
14
-
}
41
+
{ ext: "spacepack", id: "spacepack" },
42
+
{ id: "react" },
43
+
{ id: "discord/components/common/index" },
44
+
{ ext: "moonbase", id: "stores" },
45
+
{ ext: "moonbase", id: "ThemeDarkIcon" },
46
+
{ id: "discord/modules/guild_settings/web/AppCard.css" },
47
+
{ ext: "contextMenu", id: "contextMenu" },
48
+
{ id: "discord/modules/modals/Modals" },
49
+
"Masks.PANEL_BUTTON",
50
+
'"Missing channel in Channel.openChannelContextMenu"',
51
+
".forumOrHome]:"
52
+
]
15
53
},
16
54
17
-
moonbase: {
55
+
ThemeDarkIcon: {
56
+
dependencies: [{ ext: "common", id: "icons" }, { id: "react" }]
57
+
},
58
+
59
+
settings: {
18
60
dependencies: [
19
61
{ ext: "spacepack", id: "spacepack" },
20
62
{ ext: "settings", id: "settings" },
21
-
{ ext: "common", id: "react" },
22
-
{ ext: "common", id: "components" },
63
+
{ id: "react" },
64
+
{ ext: "moonbase", id: "ui" },
65
+
{ ext: "contextMenu", id: "contextMenu" },
66
+
':"USER_SETTINGS_MODAL_SET_SECTION"'
67
+
],
68
+
entrypoint: true
69
+
},
70
+
71
+
updates: {
72
+
dependencies: [
73
+
{ id: "react" },
23
74
{ ext: "moonbase", id: "stores" },
24
-
DownloadIconSVG,
25
-
TrashIconSVG
75
+
{ ext: "moonbase", id: "ThemeDarkIcon" },
76
+
{ ext: "notices", id: "notices" },
77
+
{
78
+
ext: "spacepack",
79
+
id: "spacepack"
80
+
},
81
+
{ id: "discord/Constants" },
82
+
{ id: "discord/components/common/index" }
26
83
],
27
-
entrypoint: true,
28
-
run: (module, exports, require) => {
29
-
const settings = require("settings_settings");
30
-
const React = require("common_react");
31
-
const spacepack = require("spacepack_spacepack");
32
-
const { MoonbaseSettingsStore } =
33
-
require("moonbase_stores") as ReturnType<
34
-
typeof import("./stores")["stores"]
35
-
>;
84
+
entrypoint: true
85
+
},
36
86
37
-
settings.addSection("Moonbase", "Moonbase", ui(require), null, -2, {
38
-
stores: [MoonbaseSettingsStore],
39
-
element: () => {
40
-
// Require it here because lazy loading SUX
41
-
const SettingsNotice =
42
-
spacepack.findByCode("onSaveButtonColor")[0].exports.default;
43
-
return (
44
-
<SettingsNotice
45
-
submitting={MoonbaseSettingsStore.submitting}
46
-
onReset={() => {
47
-
MoonbaseSettingsStore.reset();
48
-
}}
49
-
onSave={() => {
50
-
MoonbaseSettingsStore.writeConfig();
51
-
}}
52
-
/>
53
-
);
54
-
}
55
-
});
56
-
}
87
+
moonbase: {
88
+
dependencies: [{ ext: "moonbase", id: "stores" }]
89
+
},
90
+
91
+
crashScreen: {
92
+
dependencies: [
93
+
{ ext: "spacepack", id: "spacepack" },
94
+
{ id: "react" },
95
+
{ ext: "moonbase", id: "stores" },
96
+
{ id: "discord/packages/flux" },
97
+
{ id: "discord/components/common/index" },
98
+
/tabBar:"tabBar_[a-z0-9]+",tabBarItem:"tabBarItem_[a-z0-9]+"/
99
+
]
57
100
}
58
101
};
+44
-2
packages/core-extensions/src/moonbase/manifest.json
+44
-2
packages/core-extensions/src/moonbase/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "moonbase",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "Moonbase",
5
7
"tagline": "The official settings UI for moonlight",
6
-
"authors": ["Cynosphere", "NotNite"]
8
+
"authors": ["Cynosphere", "NotNite", "redstonekasi"]
7
9
},
8
-
"dependencies": ["spacepack", "settings", "common"]
10
+
"dependencies": ["spacepack", "settings", "common", "notices", "contextMenu"],
11
+
"settings": {
12
+
"sections": {
13
+
"advice": "reload",
14
+
"displayName": "Split into sections",
15
+
"description": "Show the Moonbase tabs as separate sections",
16
+
"type": "boolean",
17
+
"default": false
18
+
},
19
+
"oldLocation": {
20
+
"advice": "reload",
21
+
"displayName": "Put Moonbase back at the bottom",
22
+
"type": "boolean",
23
+
"default": false
24
+
},
25
+
"saveFilter": {
26
+
"advice": "none",
27
+
"displayName": "Persist filter",
28
+
"description": "Save extension filter in config",
29
+
"type": "boolean",
30
+
"default": false
31
+
},
32
+
"updateChecking": {
33
+
"advice": "none",
34
+
"displayName": "Automatic update checking",
35
+
"description": "Checks for updates to moonlight",
36
+
"type": "boolean",
37
+
"default": true
38
+
},
39
+
"updateBanner": {
40
+
"advice": "none",
41
+
"displayName": "Show update banner",
42
+
"description": "Shows a banner for moonlight and extension updates",
43
+
"type": "boolean",
44
+
"default": true
45
+
}
46
+
},
47
+
"cors": [
48
+
"https://github.com/moonlight-mod/moonlight/releases/download/",
49
+
"https://objects.githubusercontent.com/github-production-release-asset-"
50
+
]
9
51
}
+178
packages/core-extensions/src/moonbase/native.ts
+178
packages/core-extensions/src/moonbase/native.ts
···
1
+
import { MoonlightBranch } from "@moonlight-mod/types";
2
+
import type { MoonbaseNatives, RepositoryManifest } from "./types";
3
+
import extractAsar from "@moonlight-mod/core/asar";
4
+
import { distDir, repoUrlFile, installedVersionFile } from "@moonlight-mod/types/constants";
5
+
import { parseTarGzip } from "nanotar";
6
+
7
+
const moonlightGlobal = globalThis.moonlightHost ?? globalThis.moonlightNode;
8
+
9
+
const githubRepo = "moonlight-mod/moonlight";
10
+
const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`;
11
+
const artifactName = "dist.tar.gz";
12
+
13
+
const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref";
14
+
const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz";
15
+
16
+
export const userAgent = `moonlight/${moonlightGlobal.version} (https://github.com/moonlight-mod/moonlight)`;
17
+
18
+
// User-Agent header causes trouble on Firefox
19
+
const isBrowser = globalThis.moonlightNode != null && globalThis.moonlightNode.isBrowser;
20
+
const sharedHeaders: Record<string, string> = {};
21
+
if (!isBrowser) sharedHeaders["User-Agent"] = userAgent;
22
+
23
+
async function getStableRelease(): Promise<{
24
+
name: string;
25
+
assets: {
26
+
name: string;
27
+
browser_download_url: string;
28
+
}[];
29
+
}> {
30
+
const req = await fetch(githubApiUrl, {
31
+
cache: "no-store",
32
+
headers: sharedHeaders
33
+
});
34
+
return await req.json();
35
+
}
36
+
37
+
export default function getNatives(): MoonbaseNatives {
38
+
const logger = moonlightGlobal.getLogger("moonbase/natives");
39
+
40
+
return {
41
+
async checkForMoonlightUpdate() {
42
+
try {
43
+
if (moonlightGlobal.branch === MoonlightBranch.STABLE) {
44
+
const json = await getStableRelease();
45
+
return json.name !== moonlightGlobal.version ? json.name : null;
46
+
} else if (moonlightGlobal.branch === MoonlightBranch.NIGHTLY) {
47
+
const req = await fetch(nightlyRefUrl, {
48
+
cache: "no-store",
49
+
headers: sharedHeaders
50
+
});
51
+
const ref = (await req.text()).split("\n")[0];
52
+
return ref !== moonlightGlobal.version ? ref : null;
53
+
}
54
+
55
+
return null;
56
+
} catch (e) {
57
+
logger.error("Error checking for moonlight update", e);
58
+
return null;
59
+
}
60
+
},
61
+
62
+
async updateMoonlight(overrideBranch?: MoonlightBranch) {
63
+
const branch = overrideBranch ?? moonlightGlobal.branch;
64
+
65
+
// Note: this won't do anything on browser, we should probably disable it
66
+
// entirely when running in browser.
67
+
async function downloadStable(): Promise<[ArrayBuffer, string]> {
68
+
const json = await getStableRelease();
69
+
const asset = json.assets.find((a) => a.name === artifactName);
70
+
if (!asset) throw new Error("Artifact not found");
71
+
72
+
logger.debug(`Downloading ${asset.browser_download_url}`);
73
+
const req = await fetch(asset.browser_download_url, {
74
+
cache: "no-store",
75
+
headers: sharedHeaders
76
+
});
77
+
78
+
return [await req.arrayBuffer(), json.name];
79
+
}
80
+
81
+
async function downloadNightly(): Promise<[ArrayBuffer, string]> {
82
+
logger.debug(`Downloading ${nightlyZipUrl}`);
83
+
const zipReq = await fetch(nightlyZipUrl, {
84
+
cache: "no-store",
85
+
headers: sharedHeaders
86
+
});
87
+
88
+
const refReq = await fetch(nightlyRefUrl, {
89
+
cache: "no-store",
90
+
headers: sharedHeaders
91
+
});
92
+
const ref = (await refReq.text()).split("\n")[0];
93
+
94
+
return [await zipReq.arrayBuffer(), ref];
95
+
}
96
+
97
+
const [tar, ref] =
98
+
branch === MoonlightBranch.STABLE
99
+
? await downloadStable()
100
+
: branch === MoonlightBranch.NIGHTLY
101
+
? await downloadNightly()
102
+
: [null, null];
103
+
104
+
if (!tar || !ref) return;
105
+
106
+
const dist = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), distDir);
107
+
if (await moonlightNodeSandboxed.fs.exists(dist)) await moonlightNodeSandboxed.fs.rmdir(dist);
108
+
await moonlightNodeSandboxed.fs.mkdir(dist);
109
+
110
+
logger.debug("Extracting update");
111
+
const files = await parseTarGzip(tar);
112
+
for (const file of files) {
113
+
if (!file.data) continue;
114
+
// @ts-expect-error What do you mean their own types are wrong
115
+
if (file.type !== "file") continue;
116
+
117
+
const fullFile = moonlightNodeSandboxed.fs.join(dist, file.name);
118
+
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
119
+
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
120
+
await moonlightNodeSandboxed.fs.writeFile(fullFile, file.data);
121
+
}
122
+
123
+
logger.debug("Writing version file:", ref);
124
+
const versionFile = moonlightNodeSandboxed.fs.join(moonlightGlobal.getMoonlightDir(), installedVersionFile);
125
+
await moonlightNodeSandboxed.fs.writeFileString(versionFile, ref.trim());
126
+
127
+
logger.debug("Update extracted");
128
+
},
129
+
130
+
async fetchRepositories(repos) {
131
+
const ret: Record<string, RepositoryManifest[]> = {};
132
+
133
+
for (const repo of repos) {
134
+
try {
135
+
const req = await fetch(repo, {
136
+
cache: "no-store",
137
+
headers: sharedHeaders
138
+
});
139
+
const json = await req.json();
140
+
ret[repo] = json;
141
+
} catch (e) {
142
+
logger.error(`Error fetching repository ${repo}`, e);
143
+
}
144
+
}
145
+
146
+
return ret;
147
+
},
148
+
149
+
async installExtension(manifest, url, repo) {
150
+
const req = await fetch(url, {
151
+
cache: "no-store",
152
+
headers: sharedHeaders
153
+
});
154
+
155
+
const dir = moonlightGlobal.getExtensionDir(manifest.id);
156
+
// remake it in case of updates
157
+
if (await moonlightNodeSandboxed.fs.exists(dir)) await moonlightNodeSandboxed.fs.rmdir(dir);
158
+
await moonlightNodeSandboxed.fs.mkdir(dir);
159
+
160
+
const buffer = await req.arrayBuffer();
161
+
const files = extractAsar(buffer);
162
+
for (const [file, buf] of Object.entries(files)) {
163
+
const fullFile = moonlightNodeSandboxed.fs.join(dir, file);
164
+
const fullDir = moonlightNodeSandboxed.fs.dirname(fullFile);
165
+
166
+
if (!(await moonlightNodeSandboxed.fs.exists(fullDir))) await moonlightNodeSandboxed.fs.mkdir(fullDir);
167
+
await moonlightNodeSandboxed.fs.writeFile(moonlightNodeSandboxed.fs.join(dir, file), buf);
168
+
}
169
+
170
+
await moonlightNodeSandboxed.fs.writeFileString(moonlightNodeSandboxed.fs.join(dir, repoUrlFile), repo);
171
+
},
172
+
173
+
async deleteExtension(id) {
174
+
const dir = moonlightGlobal.getExtensionDir(id);
175
+
await moonlightNodeSandboxed.fs.rmdir(dir);
176
+
}
177
+
};
178
+
}
+2
-67
packages/core-extensions/src/moonbase/node.ts
+2
-67
packages/core-extensions/src/moonbase/node.ts
···
1
-
import { MoonbaseNatives, RepositoryManifest } from "./types";
2
-
import asar from "@electron/asar";
3
-
import fs from "fs";
4
-
import path from "path";
5
-
import os from "os";
6
-
import { repoUrlFile } from "types/src/constants";
7
-
8
-
async function fetchRepositories(repos: string[]) {
9
-
const ret: Record<string, RepositoryManifest[]> = {};
10
-
11
-
for (const repo of repos) {
12
-
try {
13
-
const req = await fetch(repo);
14
-
const json = await req.json();
15
-
ret[repo] = json;
16
-
} catch (e) {
17
-
console.error(`Error fetching repository ${repo}`, e);
18
-
}
19
-
}
20
-
21
-
return ret;
22
-
}
23
-
24
-
async function installExtension(
25
-
manifest: RepositoryManifest,
26
-
url: string,
27
-
repo: string
28
-
) {
29
-
const req = await fetch(url);
30
-
31
-
const dir = moonlightNode.getExtensionDir(manifest.id);
32
-
// remake it in case of updates
33
-
if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true });
34
-
fs.mkdirSync(dir, { recursive: true });
35
-
36
-
// for some reason i just can't .writeFileSync() a file that ends in .asar???
37
-
const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "moonlight-"));
38
-
const tempFile = path.join(tempDir, "extension");
39
-
const buffer = await req.arrayBuffer();
40
-
fs.writeFileSync(tempFile, Buffer.from(buffer));
41
-
42
-
asar.extractAll(tempFile, dir);
43
-
fs.writeFileSync(path.join(dir, repoUrlFile), repo);
44
-
}
45
-
46
-
async function deleteExtension(id: string) {
47
-
const dir = moonlightNode.getExtensionDir(id);
48
-
fs.rmdirSync(dir, { recursive: true });
49
-
}
50
-
51
-
function getExtensionConfig(id: string, key: string): any {
52
-
const config = moonlightNode.config.extensions[id];
53
-
if (typeof config === "object") {
54
-
return config.config?.[key];
55
-
}
56
-
57
-
return undefined;
58
-
}
59
-
60
-
const exports: MoonbaseNatives = {
61
-
fetchRepositories,
62
-
installExtension,
63
-
deleteExtension,
64
-
getExtensionConfig
65
-
};
66
-
67
-
module.exports = exports;
1
+
import getNatives from "./native";
2
+
module.exports = getNatives();
-257
packages/core-extensions/src/moonbase/stores.ts
-257
packages/core-extensions/src/moonbase/stores.ts
···
1
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
2
-
import {
3
-
Config,
4
-
DetectedExtension,
5
-
ExtensionLoadSource
6
-
} from "@moonlight-mod/types";
7
-
import {
8
-
ExtensionState,
9
-
MoonbaseExtension,
10
-
MoonbaseNatives,
11
-
RepositoryManifest
12
-
} from "./types";
13
-
14
-
export const stores = (require: typeof WebpackRequire) => {
15
-
const Flux = require("common_flux");
16
-
const Dispatcher = require("common_fluxDispatcher");
17
-
const natives: MoonbaseNatives = moonlight.getNatives("moonbase");
18
-
19
-
class MoonbaseSettingsStore extends Flux.Store<any> {
20
-
private origConfig: Config;
21
-
private config: Config;
22
-
23
-
modified: boolean;
24
-
submitting: boolean;
25
-
installing: boolean;
26
-
27
-
extensions: { [id: string]: MoonbaseExtension };
28
-
updates: { [id: string]: { version: string; download: string } };
29
-
30
-
constructor() {
31
-
super(Dispatcher);
32
-
33
-
// Fucking Electron making it immutable
34
-
this.origConfig = moonlightNode.config;
35
-
this.config = JSON.parse(JSON.stringify(this.origConfig));
36
-
37
-
this.modified = false;
38
-
this.submitting = false;
39
-
this.installing = false;
40
-
41
-
this.extensions = {};
42
-
this.updates = {};
43
-
for (const ext of moonlightNode.extensions) {
44
-
const existingExtension = this.extensions[ext.id];
45
-
if (existingExtension != null) continue;
46
-
47
-
this.extensions[ext.id] = {
48
-
...ext,
49
-
state: moonlight.enabledExtensions.has(ext.id)
50
-
? ExtensionState.Enabled
51
-
: ExtensionState.Disabled
52
-
};
53
-
}
54
-
55
-
natives.fetchRepositories(this.config.repositories).then((ret) => {
56
-
for (const [repo, exts] of Object.entries(ret)) {
57
-
try {
58
-
for (const ext of exts) {
59
-
try {
60
-
const existingExtension = this.extensions[ext.id];
61
-
if (existingExtension != null) {
62
-
if (this.hasUpdate(repo, ext, existingExtension)) {
63
-
this.updates[ext.id] = {
64
-
version: ext.version!,
65
-
download: ext.download
66
-
};
67
-
}
68
-
continue;
69
-
}
70
-
71
-
this.extensions[ext.id] = {
72
-
id: ext.id,
73
-
manifest: ext,
74
-
source: { type: ExtensionLoadSource.Normal, url: repo },
75
-
state: ExtensionState.NotDownloaded
76
-
};
77
-
} catch (e) {
78
-
console.error(`Error processing extension ${ext.id}`, e);
79
-
}
80
-
}
81
-
} catch (e) {
82
-
console.error(`Error processing repository ${repo}`, e);
83
-
}
84
-
}
85
-
86
-
this.emitChange();
87
-
});
88
-
}
89
-
90
-
// this logic sucks so bad lol
91
-
private hasUpdate(
92
-
repo: string,
93
-
repoExt: RepositoryManifest,
94
-
existing: MoonbaseExtension
95
-
) {
96
-
return (
97
-
existing.source.type === ExtensionLoadSource.Normal &&
98
-
existing.source.url != null &&
99
-
existing.source.url === repo &&
100
-
repoExt.version != null &&
101
-
existing.manifest.version != repoExt.version
102
-
);
103
-
}
104
-
105
-
// Jank
106
-
private isModified() {
107
-
const orig = JSON.stringify(this.origConfig);
108
-
const curr = JSON.stringify(this.config);
109
-
return orig !== curr;
110
-
}
111
-
112
-
get busy() {
113
-
return this.submitting || this.installing;
114
-
}
115
-
116
-
showNotice() {
117
-
return this.modified;
118
-
}
119
-
120
-
getExtension(id: string) {
121
-
return this.extensions[id];
122
-
}
123
-
124
-
getExtensionName(id: string) {
125
-
return this.extensions.hasOwnProperty(id)
126
-
? this.extensions[id].manifest.meta?.name ?? id
127
-
: id;
128
-
}
129
-
130
-
getExtensionUpdate(id: string) {
131
-
return this.updates.hasOwnProperty(id) ? this.updates[id] : null;
132
-
}
133
-
134
-
getExtensionEnabled(id: string) {
135
-
const val = this.config.extensions[id];
136
-
if (val == null) return false;
137
-
return typeof val === "boolean" ? val : val.enabled;
138
-
}
139
-
140
-
getExtensionConfig<T>(id: string, key: string): T | undefined {
141
-
const defaultValue =
142
-
this.extensions[id].manifest.settings?.[key]?.default;
143
-
const cfg = this.config.extensions[id];
144
-
145
-
if (cfg == null || typeof cfg === "boolean") return defaultValue;
146
-
return cfg.config?.[key] ?? defaultValue;
147
-
}
148
-
149
-
getExtensionConfigName(id: string, key: string) {
150
-
return this.extensions[id].manifest.settings?.[key]?.displayName ?? key;
151
-
}
152
-
153
-
setExtensionConfig(id: string, key: string, value: any) {
154
-
const oldConfig = this.config.extensions[id];
155
-
const newConfig =
156
-
typeof oldConfig === "boolean"
157
-
? {
158
-
enabled: oldConfig,
159
-
config: { [key]: value }
160
-
}
161
-
: {
162
-
...oldConfig,
163
-
config: { ...(oldConfig?.config ?? {}), [key]: value }
164
-
};
165
-
166
-
this.config.extensions[id] = newConfig;
167
-
this.modified = this.isModified();
168
-
this.emitChange();
169
-
}
170
-
171
-
setExtensionEnabled(id: string, enabled: boolean) {
172
-
let val = this.config.extensions[id];
173
-
174
-
if (val == null) {
175
-
this.config.extensions[id] = { enabled };
176
-
this.emitChange();
177
-
return;
178
-
}
179
-
180
-
if (typeof val === "boolean") {
181
-
val = enabled;
182
-
} else {
183
-
val.enabled = enabled;
184
-
}
185
-
186
-
this.config.extensions[id] = val;
187
-
this.modified = this.isModified();
188
-
this.emitChange();
189
-
}
190
-
191
-
async installExtension(id: string) {
192
-
const ext = this.getExtension(id);
193
-
if (!("download" in ext.manifest)) {
194
-
throw new Error("Extension has no download URL");
195
-
}
196
-
197
-
this.installing = true;
198
-
try {
199
-
const url = this.updates[id]?.download ?? ext.manifest.download;
200
-
await natives.installExtension(ext.manifest, url, ext.source.url!);
201
-
if (ext.state === ExtensionState.NotDownloaded) {
202
-
this.extensions[id].state = ExtensionState.Disabled;
203
-
}
204
-
205
-
delete this.updates[id];
206
-
} catch (e) {
207
-
console.error("Error installing extension:", e);
208
-
}
209
-
210
-
this.installing = false;
211
-
this.emitChange();
212
-
}
213
-
214
-
async deleteExtension(id: string) {
215
-
const ext = this.getExtension(id);
216
-
if (ext == null) return;
217
-
218
-
this.installing = true;
219
-
try {
220
-
await natives.deleteExtension(ext.id);
221
-
this.extensions[id].state = ExtensionState.NotDownloaded;
222
-
} catch (e) {
223
-
console.error("Error deleting extension:", e);
224
-
}
225
-
226
-
this.installing = false;
227
-
this.emitChange();
228
-
}
229
-
230
-
writeConfig() {
231
-
this.submitting = true;
232
-
233
-
try {
234
-
moonlightNode.writeConfig(this.config);
235
-
// I love jank cloning
236
-
this.origConfig = JSON.parse(JSON.stringify(this.config));
237
-
} catch (e) {
238
-
console.error("Error writing config", e);
239
-
}
240
-
241
-
this.submitting = false;
242
-
this.modified = false;
243
-
this.emitChange();
244
-
}
245
-
246
-
reset() {
247
-
this.submitting = false;
248
-
this.modified = false;
249
-
this.config = JSON.parse(JSON.stringify(this.origConfig));
250
-
this.emitChange();
251
-
}
252
-
}
253
-
254
-
return {
255
-
MoonbaseSettingsStore: new MoonbaseSettingsStore()
256
-
};
257
-
};
+269
packages/core-extensions/src/moonbase/style.css
+269
packages/core-extensions/src/moonbase/style.css
···
1
+
:root {
2
+
--moonbase-bg: #222034;
3
+
--moonbase-fg: #fffba6;
4
+
}
5
+
6
+
.moonbase-settings > :first-child {
7
+
margin-top: 0px;
8
+
}
9
+
10
+
.moonbase-retry-button {
11
+
padding: 8px;
12
+
margin-right: 8px;
13
+
}
14
+
15
+
textarea.moonbase-resizeable {
16
+
resize: vertical;
17
+
}
18
+
19
+
.moonbase-link-buttons {
20
+
border-bottom: 2px solid var(--background-modifier-accent);
21
+
margin-bottom: -2px;
22
+
margin-left: 0 !important;
23
+
padding-right: 20px;
24
+
gap: 1rem;
25
+
}
26
+
27
+
.moonbase-speen {
28
+
animation: moonbase-speen-animation 0.25s linear infinite;
29
+
}
30
+
31
+
@keyframes moonbase-speen-animation {
32
+
from {
33
+
transform: rotate(0deg);
34
+
}
35
+
to {
36
+
transform: rotate(360deg);
37
+
}
38
+
}
39
+
40
+
/* Update notice at the top of the client */
41
+
.moonbase-updates-notice {
42
+
background-color: var(--moonbase-bg);
43
+
color: var(--moonbase-fg);
44
+
--custom-notice-text: var(--moonbase-fg);
45
+
line-height: unset;
46
+
height: 36px;
47
+
}
48
+
49
+
.moonbase-updates-notice button {
50
+
color: var(--moonbase-fg);
51
+
border-color: var(--moonbase-fg);
52
+
}
53
+
54
+
.moonbase-updates-notice_text-wrapper {
55
+
display: inline-flex;
56
+
align-items: center;
57
+
line-height: 36px;
58
+
gap: 2px;
59
+
}
60
+
61
+
/* Help messages in Moonbase UI */
62
+
.moonbase-help-message {
63
+
display: flex;
64
+
flex-direction: row;
65
+
justify-content: space-between;
66
+
}
67
+
68
+
.moonbase-help-message-sticky {
69
+
position: sticky;
70
+
top: 24px;
71
+
z-index: 10;
72
+
background-color: var(--background-primary);
73
+
}
74
+
75
+
.moonbase-extension-update-section {
76
+
margin-top: 15px;
77
+
}
78
+
79
+
.moonbase-update-section {
80
+
background-color: var(--moonbase-bg);
81
+
--info-help-foreground: var(--moonbase-fg);
82
+
border: none !important;
83
+
color: var(--moonbase-fg);
84
+
}
85
+
86
+
.moonbase-update-section button {
87
+
--info-help-foreground: var(--moonbase-fg);
88
+
color: var(--moonbase-fg);
89
+
background-color: transparent;
90
+
border-color: var(--moonbase-fg);
91
+
}
92
+
93
+
.moonbase-help-message-buttons {
94
+
display: flex;
95
+
flex-direction: row;
96
+
gap: 8px;
97
+
align-items: center;
98
+
}
99
+
100
+
.moonbase-update-divider {
101
+
margin: 32px 0;
102
+
}
103
+
104
+
.moonlight-card-info-header {
105
+
margin-bottom: 0.25rem;
106
+
}
107
+
108
+
.moonlight-card-badge {
109
+
border-radius: 0.1875rem;
110
+
padding: 0 0.275rem;
111
+
margin-right: 0.4em;
112
+
background-color: var(--badge-color, var(--bg-mod-strong));
113
+
}
114
+
115
+
/* Crash screen */
116
+
.moonbase-crash-wrapper > [class^="buttons_"] {
117
+
gap: 1rem;
118
+
}
119
+
120
+
.moonbase-crash-wrapper {
121
+
display: flex;
122
+
flex-direction: column;
123
+
align-items: center;
124
+
gap: 1rem;
125
+
height: 50%;
126
+
width: 50vw;
127
+
max-height: 50%;
128
+
max-width: 50vw;
129
+
}
130
+
131
+
.moonbase-crash-tabs {
132
+
width: 100%;
133
+
}
134
+
135
+
.moonbase-crash-details-wrapper {
136
+
overflow-y: scroll;
137
+
color: var(--text-normal);
138
+
background: var(--background-secondary);
139
+
border: 1px solid var(--background-tertiary);
140
+
border-radius: 4px;
141
+
padding: 0.5em;
142
+
143
+
&::-webkit-scrollbar {
144
+
width: 8px;
145
+
height: 8px;
146
+
}
147
+
148
+
&::-webkit-scrollbar-thumb {
149
+
background-clip: padding-box;
150
+
border: 2px solid transparent;
151
+
border-radius: 4px;
152
+
background-color: var(--scrollbar-thin-thumb);
153
+
min-height: 40px;
154
+
}
155
+
156
+
&::-webkit-scrollbar-track {
157
+
border: 2px solid var(--scrollbar-thin-track);
158
+
background-color: var(--scrollbar-thin-track);
159
+
border-color: var(--scrollbar-thin-track);
160
+
}
161
+
}
162
+
163
+
.moonbase-crash-details {
164
+
box-sizing: border-box;
165
+
padding: 0;
166
+
font-family: var(--font-code);
167
+
font-size: 0.75rem;
168
+
line-height: 1rem;
169
+
margin: 6px;
170
+
white-space: pre-wrap;
171
+
background-clip: border-box;
172
+
173
+
& > code {
174
+
font-size: 0.875rem;
175
+
line-height: 1.125rem;
176
+
text-indent: 0;
177
+
white-space: pre-wrap;
178
+
text-size-adjust: none;
179
+
display: block;
180
+
user-select: text;
181
+
}
182
+
}
183
+
184
+
.moonbase-crash-extensions {
185
+
overflow-y: scroll;
186
+
display: grid;
187
+
grid-auto-columns: 25vw;
188
+
gap: 8px;
189
+
190
+
&::-webkit-scrollbar {
191
+
width: 8px;
192
+
height: 8px;
193
+
}
194
+
195
+
&::-webkit-scrollbar-thumb {
196
+
background-clip: padding-box;
197
+
border: 2px solid transparent;
198
+
border-radius: 4px;
199
+
background-color: var(--scrollbar-thin-thumb);
200
+
min-height: 40px;
201
+
}
202
+
203
+
&::-webkit-scrollbar-track {
204
+
border: 2px solid var(--scrollbar-thin-track);
205
+
background-color: var(--scrollbar-thin-track);
206
+
border-color: var(--scrollbar-thin-track);
207
+
}
208
+
}
209
+
210
+
.moonbase-crash-extensionCard {
211
+
color: var(--text-normal);
212
+
background: var(--background-secondary);
213
+
border: 1px solid var(--background-tertiary);
214
+
border-radius: 4px;
215
+
padding: 0.5em;
216
+
display: flex;
217
+
}
218
+
219
+
.moonbase-crash-extensionCard-meta {
220
+
display: flex;
221
+
flex-direction: column;
222
+
flex-grow: 1;
223
+
}
224
+
225
+
.moonbase-crash-extensionCard-title {
226
+
color: var(--text-normal);
227
+
font-family: var(--font-primary);
228
+
font-size: 16px;
229
+
line-height: 1.25;
230
+
font-weight: 600;
231
+
}
232
+
233
+
.moonbase-crash-extensionCard-version {
234
+
color: var(--text-muted);
235
+
font-family: var(--font-primary);
236
+
font-size: 14px;
237
+
line-height: 1.286;
238
+
font-weight: 400;
239
+
}
240
+
241
+
/* About page */
242
+
.moonbase-wordmark {
243
+
width: 100%;
244
+
}
245
+
246
+
.moonbase-devs {
247
+
width: 100%;
248
+
display: flex;
249
+
justify-content: center;
250
+
gap: 0rem 0.5rem;
251
+
padding-top: 0.5rem;
252
+
}
253
+
254
+
.moonbase-dev {
255
+
height: 4rem;
256
+
}
257
+
258
+
.moonbase-dev-avatar {
259
+
width: 2rem;
260
+
border-radius: 50%;
261
+
}
262
+
263
+
.moonbase-gap {
264
+
gap: 0.5rem;
265
+
}
266
+
267
+
.moonbase-about-page {
268
+
gap: 1rem;
269
+
}
+27
-15
packages/core-extensions/src/moonbase/types.ts
+27
-15
packages/core-extensions/src/moonbase/types.ts
···
1
-
import { DetectedExtension, ExtensionManifest } from "types/src";
1
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
2
+
import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types";
2
3
3
-
export const DownloadIconSVG =
4
-
"M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z";
5
-
export const TrashIconSVG =
6
-
"M5 6.99902V18.999C5 20.101 5.897 20.999 7 20.999H17C18.103 20.999 19 20.101 19 18.999V6.99902H5ZM11 17H9V11H11V17ZM15 17H13V11H15V17Z";
4
+
export type MoonbaseNatives = {
5
+
checkForMoonlightUpdate(): Promise<string | null>;
6
+
updateMoonlight(overrideBranch?: MoonlightBranch): Promise<void>;
7
7
8
-
export type MoonbaseNatives = {
9
-
fetchRepositories(
10
-
repos: string[]
11
-
): Promise<Record<string, RepositoryManifest[]>>;
12
-
installExtension(
13
-
manifest: RepositoryManifest,
14
-
url: string,
15
-
repo: string
16
-
): Promise<void>;
8
+
fetchRepositories(repos: string[]): Promise<Record<string, RepositoryManifest[]>>;
9
+
installExtension(manifest: RepositoryManifest, url: string, repo: string): Promise<void>;
17
10
deleteExtension(id: string): Promise<void>;
18
-
getExtensionConfig(id: string, key: string): any;
19
11
};
20
12
21
13
export type RepositoryManifest = ExtensionManifest & {
···
30
22
31
23
export type MoonbaseExtension = {
32
24
id: string;
25
+
uniqueId: number;
33
26
manifest: ExtensionManifest | RepositoryManifest;
34
27
source: DetectedExtension["source"];
35
28
state: ExtensionState;
29
+
compat: ExtensionCompat;
30
+
hasUpdate: boolean;
31
+
changelog?: string;
32
+
settingsOverride?: ExtensionManifest["settings"];
36
33
};
34
+
35
+
export enum UpdateState {
36
+
Ready,
37
+
Working,
38
+
Installed,
39
+
Failed
40
+
}
41
+
42
+
// Ordered in terms of priority
43
+
export enum RestartAdvice {
44
+
NotNeeded, // No action is needed
45
+
ReloadSuggested, // A reload might be needed
46
+
ReloadNeeded, // A reload is needed
47
+
RestartNeeded // A restart is needed
48
+
}
-228
packages/core-extensions/src/moonbase/ui/index.tsx
-228
packages/core-extensions/src/moonbase/ui/index.tsx
···
1
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
2
-
import { DownloadIconSVG, ExtensionState, TrashIconSVG } from "../types";
3
-
import { ExtensionLoadSource } from "types/src";
4
-
import info from "./info";
5
-
import settings from "./settings";
6
-
7
-
export enum ExtensionPage {
8
-
Info,
9
-
Description,
10
-
Settings
11
-
}
12
-
13
-
export default (require: typeof WebpackRequire) => {
14
-
const React = require("common_react");
15
-
const spacepack = require("spacepack_spacepack");
16
-
const CommonComponents = require("common_components");
17
-
const Flux = require("common_flux");
18
-
19
-
const { ExtensionInfo } = info(require);
20
-
const { Settings } = settings(require);
21
-
const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType<
22
-
typeof import("../stores")["stores"]
23
-
>;
24
-
25
-
const UserProfileClasses = spacepack.findByCode(
26
-
"tabBarContainer",
27
-
"topSection"
28
-
)[0].exports;
29
-
30
-
const DownloadIcon = spacepack.findByCode(DownloadIconSVG)[0].exports.default;
31
-
const TrashIcon = spacepack.findByCode(TrashIconSVG)[0].exports.default;
32
-
33
-
function ExtensionCard({ id }: { id: string }) {
34
-
const [tab, setTab] = React.useState(ExtensionPage.Info);
35
-
const { ext, enabled, busy, update } = Flux.useStateFromStores(
36
-
[MoonbaseSettingsStore],
37
-
() => {
38
-
return {
39
-
ext: MoonbaseSettingsStore.getExtension(id),
40
-
enabled: MoonbaseSettingsStore.getExtensionEnabled(id),
41
-
busy: MoonbaseSettingsStore.busy,
42
-
update: MoonbaseSettingsStore.getExtensionUpdate(id)
43
-
};
44
-
}
45
-
);
46
-
47
-
// Why it work like that :sob:
48
-
if (ext == null) return <></>;
49
-
50
-
const {
51
-
Card,
52
-
CardClasses,
53
-
Flex,
54
-
Text,
55
-
MarkdownParser,
56
-
Switch,
57
-
TabBar,
58
-
Button
59
-
} = CommonComponents;
60
-
61
-
const tagline = ext.manifest?.meta?.tagline;
62
-
const settings = ext.manifest?.settings;
63
-
const description = ext.manifest?.meta?.description;
64
-
65
-
return (
66
-
<Card editable={true} className={CardClasses.card}>
67
-
<div className={CardClasses.cardHeader}>
68
-
<Flex direction={Flex.Direction.VERTICAL}>
69
-
<Flex direction={Flex.Direction.HORIZONTAL}>
70
-
<Text variant="text-md/semibold">
71
-
{ext.manifest?.meta?.name ?? ext.id}
72
-
</Text>
73
-
</Flex>
74
-
75
-
{tagline != null && (
76
-
<Text variant="text-sm/normal">
77
-
{MarkdownParser.parse(tagline)}
78
-
</Text>
79
-
)}
80
-
</Flex>
81
-
82
-
<Flex
83
-
direction={Flex.Direction.HORIZONTAL}
84
-
align={Flex.Align.END}
85
-
justify={Flex.Justify.END}
86
-
>
87
-
{ext.state === ExtensionState.NotDownloaded ? (
88
-
<Button
89
-
color={Button.Colors.BRAND}
90
-
submitting={busy}
91
-
onClick={() => {
92
-
MoonbaseSettingsStore.installExtension(id);
93
-
}}
94
-
>
95
-
Install
96
-
</Button>
97
-
) : (
98
-
<div
99
-
// too lazy to learn how <Flex /> works lmao
100
-
style={{
101
-
display: "flex",
102
-
alignItems: "center",
103
-
gap: "1rem"
104
-
}}
105
-
>
106
-
{ext.source.type == ExtensionLoadSource.Normal && (
107
-
// TODO: this needs centering
108
-
<Button
109
-
color={Button.Colors.RED}
110
-
size={Button.Sizes.ICON}
111
-
submitting={busy}
112
-
onClick={() => {
113
-
MoonbaseSettingsStore.deleteExtension(id);
114
-
}}
115
-
>
116
-
<TrashIcon width={27} />
117
-
</Button>
118
-
)}
119
-
120
-
{update != null && (
121
-
<Button
122
-
color={Button.Colors.BRAND}
123
-
size={Button.Sizes.ICON}
124
-
submitting={busy}
125
-
onClick={() => {
126
-
MoonbaseSettingsStore.installExtension(id);
127
-
}}
128
-
>
129
-
<DownloadIcon width={27} />
130
-
</Button>
131
-
)}
132
-
133
-
<Switch
134
-
checked={enabled}
135
-
onChange={() => {
136
-
MoonbaseSettingsStore.setExtensionEnabled(id, !enabled);
137
-
}}
138
-
/>
139
-
</div>
140
-
)}
141
-
</Flex>
142
-
</div>
143
-
144
-
<div className={UserProfileClasses.body}>
145
-
{(description != null || settings != null) && (
146
-
<div
147
-
className={UserProfileClasses.tabBarContainer}
148
-
style={{
149
-
padding: "0 10px"
150
-
}}
151
-
>
152
-
<TabBar
153
-
selectedItem={tab}
154
-
type="top"
155
-
onItemSelect={setTab}
156
-
className={UserProfileClasses.tabBar}
157
-
>
158
-
<TabBar.Item
159
-
className={UserProfileClasses.tabBarItem}
160
-
id={ExtensionPage.Info}
161
-
>
162
-
Info
163
-
</TabBar.Item>
164
-
165
-
{description != null && (
166
-
<TabBar.Item
167
-
className={UserProfileClasses.tabBarItem}
168
-
id={ExtensionPage.Description}
169
-
>
170
-
Description
171
-
</TabBar.Item>
172
-
)}
173
-
174
-
{settings != null && (
175
-
<TabBar.Item
176
-
className={UserProfileClasses.tabBarItem}
177
-
id={ExtensionPage.Settings}
178
-
>
179
-
Settings
180
-
</TabBar.Item>
181
-
)}
182
-
</TabBar>
183
-
</div>
184
-
)}
185
-
186
-
<Flex
187
-
justify={Flex.Justify.START}
188
-
wrap={Flex.Wrap.WRAP}
189
-
style={{
190
-
padding: "16px 16px"
191
-
}}
192
-
>
193
-
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />}
194
-
{tab === ExtensionPage.Description && (
195
-
<Text variant="text-md/normal">
196
-
{MarkdownParser.parse(description ?? "*No description*")}
197
-
</Text>
198
-
)}
199
-
{tab === ExtensionPage.Settings && <Settings ext={ext} />}
200
-
</Flex>
201
-
</div>
202
-
</Card>
203
-
);
204
-
}
205
-
206
-
return function Moonbase() {
207
-
const { extensions } = Flux.useStateFromStoresObject(
208
-
[MoonbaseSettingsStore],
209
-
() => {
210
-
return { extensions: MoonbaseSettingsStore.extensions };
211
-
}
212
-
);
213
-
214
-
const sorted = Object.values(extensions).sort((a, b) => {
215
-
const aName = a.manifest.meta?.name ?? a.id;
216
-
const bName = b.manifest.meta?.name ?? b.id;
217
-
return aName.localeCompare(bName);
218
-
});
219
-
220
-
return (
221
-
<>
222
-
{sorted.map((ext) => (
223
-
<ExtensionCard id={ext.id} key={ext.id} />
224
-
))}
225
-
</>
226
-
);
227
-
};
228
-
};
-198
packages/core-extensions/src/moonbase/ui/info.tsx
-198
packages/core-extensions/src/moonbase/ui/info.tsx
···
1
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
2
-
import { DetectedExtension, ExtensionTag } from "@moonlight-mod/types";
3
-
import { MoonbaseExtension } from "../types";
4
-
5
-
type Dependency = {
6
-
id: string;
7
-
type: DependencyType;
8
-
};
9
-
10
-
enum DependencyType {
11
-
Dependency = "dependency",
12
-
Optional = "optional",
13
-
Incompatible = "incompatible"
14
-
}
15
-
16
-
export default (require: typeof WebpackRequire) => {
17
-
const React = require("common_react");
18
-
const spacepack = require("spacepack_spacepack");
19
-
20
-
const CommonComponents = require("common_components");
21
-
const UserInfoClasses = spacepack.findByCode(
22
-
"infoScroller",
23
-
"userInfoSection",
24
-
"userInfoSectionHeader"
25
-
)[0].exports;
26
-
27
-
const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType<
28
-
typeof import("../stores")["stores"]
29
-
>;
30
-
31
-
function InfoSection({
32
-
title,
33
-
children
34
-
}: {
35
-
title: string;
36
-
children: React.ReactNode;
37
-
}) {
38
-
return (
39
-
<div
40
-
style={{
41
-
marginRight: "1em"
42
-
}}
43
-
>
44
-
<CommonComponents.Text
45
-
variant="eyebrow"
46
-
className={UserInfoClasses.userInfoSectionHeader}
47
-
>
48
-
{title}
49
-
</CommonComponents.Text>
50
-
51
-
<CommonComponents.Text variant="text-sm/normal">
52
-
{children}
53
-
</CommonComponents.Text>
54
-
</div>
55
-
);
56
-
}
57
-
58
-
function Badge({
59
-
color,
60
-
children
61
-
}: {
62
-
color: string;
63
-
children: React.ReactNode;
64
-
}) {
65
-
return (
66
-
<span
67
-
style={{
68
-
borderRadius: ".1875rem",
69
-
padding: "0 0.275rem",
70
-
marginRight: "0.4em",
71
-
backgroundColor: color,
72
-
color: "#fff"
73
-
}}
74
-
>
75
-
{children}
76
-
</span>
77
-
);
78
-
}
79
-
80
-
function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) {
81
-
const { Flex, Text } = CommonComponents;
82
-
const authors = ext.manifest?.meta?.authors;
83
-
const tags = ext.manifest?.meta?.tags;
84
-
85
-
const dependencies: Dependency[] = [];
86
-
if (ext.manifest.dependencies != null) {
87
-
dependencies.push(
88
-
...ext.manifest.dependencies.map((dep) => ({
89
-
id: dep,
90
-
type: DependencyType.Dependency
91
-
}))
92
-
);
93
-
}
94
-
95
-
if (ext.manifest.suggested != null) {
96
-
dependencies.push(
97
-
...ext.manifest.suggested.map((dep) => ({
98
-
id: dep,
99
-
type: DependencyType.Optional
100
-
}))
101
-
);
102
-
}
103
-
104
-
if (ext.manifest.incompatible != null) {
105
-
dependencies.push(
106
-
...ext.manifest.incompatible.map((dep) => ({
107
-
id: dep,
108
-
type: DependencyType.Incompatible
109
-
}))
110
-
);
111
-
}
112
-
113
-
return (
114
-
<>
115
-
{authors != null && (
116
-
<InfoSection title="Authors">
117
-
{authors.map((author, i) => {
118
-
const comma = i !== authors.length - 1 ? ", " : "";
119
-
if (typeof author === "string") {
120
-
return (
121
-
<span>
122
-
{author}
123
-
{comma}
124
-
</span>
125
-
);
126
-
} else {
127
-
// TODO: resolve IDs
128
-
return (
129
-
<span>
130
-
{author.name}
131
-
{comma}
132
-
</span>
133
-
);
134
-
}
135
-
})}
136
-
</InfoSection>
137
-
)}
138
-
139
-
{tags != null && (
140
-
<InfoSection title="Tags">
141
-
{tags.map((tag, i) => {
142
-
const names: Record<ExtensionTag, string> = {
143
-
[ExtensionTag.Accessibility]: "Accessibility",
144
-
[ExtensionTag.Appearance]: "Appearance",
145
-
[ExtensionTag.Chat]: "Chat",
146
-
[ExtensionTag.Commands]: "Commands",
147
-
[ExtensionTag.ContextMenu]: "Context Menu",
148
-
[ExtensionTag.DangerZone]: "Danger Zone",
149
-
[ExtensionTag.Development]: "Development",
150
-
[ExtensionTag.Fixes]: "Fixes",
151
-
[ExtensionTag.Fun]: "Fun",
152
-
[ExtensionTag.Markdown]: "Markdown",
153
-
[ExtensionTag.Voice]: "Voice",
154
-
[ExtensionTag.Privacy]: "Privacy",
155
-
[ExtensionTag.Profiles]: "Profiles",
156
-
[ExtensionTag.QualityOfLife]: "Quality of Life",
157
-
[ExtensionTag.Library]: "Library"
158
-
};
159
-
const name = names[tag];
160
-
161
-
return (
162
-
<Badge
163
-
color={
164
-
tag == ExtensionTag.DangerZone
165
-
? "var(--red-400)"
166
-
: "var(--brand-500)"
167
-
}
168
-
>
169
-
{name}
170
-
</Badge>
171
-
);
172
-
})}
173
-
</InfoSection>
174
-
)}
175
-
176
-
{dependencies.length > 0 && (
177
-
<InfoSection title="Dependencies">
178
-
{dependencies.map((dep) => {
179
-
const colors = {
180
-
[DependencyType.Dependency]: "var(--brand-500)",
181
-
[DependencyType.Optional]: "var(--orange-400)",
182
-
[DependencyType.Incompatible]: "var(--red-400)"
183
-
};
184
-
const color = colors[dep.type];
185
-
const name = MoonbaseSettingsStore.getExtensionName(dep.id);
186
-
return <Badge color={color}>{name}</Badge>;
187
-
})}
188
-
</InfoSection>
189
-
)}
190
-
</>
191
-
);
192
-
}
193
-
194
-
return {
195
-
InfoSection,
196
-
ExtensionInfo
197
-
};
198
-
};
-327
packages/core-extensions/src/moonbase/ui/settings.tsx
-327
packages/core-extensions/src/moonbase/ui/settings.tsx
···
1
-
import {
2
-
DictionarySettingType,
3
-
ExtensionSettingType,
4
-
ExtensionSettingsManifest,
5
-
NumberSettingType,
6
-
SelectSettingType
7
-
} from "@moonlight-mod/types/config";
8
-
import WebpackRequire from "@moonlight-mod/types/discord/require";
9
-
import { MoonbaseExtension } from "../types";
10
-
11
-
type SettingsProps = {
12
-
ext: MoonbaseExtension;
13
-
name: string;
14
-
setting: ExtensionSettingsManifest;
15
-
};
16
-
17
-
type SettingsComponent = React.ComponentType<SettingsProps>;
18
-
19
-
export default (require: typeof WebpackRequire) => {
20
-
const React = require("common_react");
21
-
const spacepack = require("spacepack_spacepack");
22
-
const CommonComponents = require("common_components");
23
-
const Flux = require("common_flux");
24
-
25
-
const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType<
26
-
typeof import("../stores")["stores"]
27
-
>;
28
-
29
-
function Boolean({ ext, name, setting }: SettingsProps) {
30
-
const { FormSwitch } = CommonComponents;
31
-
const { value, displayName } = Flux.useStateFromStores(
32
-
[MoonbaseSettingsStore],
33
-
() => {
34
-
return {
35
-
value: MoonbaseSettingsStore.getExtensionConfig<boolean>(
36
-
ext.id,
37
-
name
38
-
),
39
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
40
-
ext.id,
41
-
name
42
-
)
43
-
};
44
-
},
45
-
[ext.id, name]
46
-
);
47
-
48
-
return (
49
-
<FormSwitch
50
-
value={value ?? false}
51
-
hideBorder={true}
52
-
onChange={(value: boolean) => {
53
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
54
-
}}
55
-
>
56
-
{displayName}
57
-
</FormSwitch>
58
-
);
59
-
}
60
-
61
-
function Number({ ext, name, setting }: SettingsProps) {
62
-
const { Slider, ControlClasses } = CommonComponents;
63
-
const { value, displayName } = Flux.useStateFromStores(
64
-
[MoonbaseSettingsStore],
65
-
() => {
66
-
return {
67
-
value: MoonbaseSettingsStore.getExtensionConfig<number>(ext.id, name),
68
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
69
-
ext.id,
70
-
name
71
-
)
72
-
};
73
-
},
74
-
[ext.id, name]
75
-
);
76
-
77
-
const castedSetting = setting as NumberSettingType;
78
-
const min = castedSetting.min ?? 0;
79
-
const max = castedSetting.max ?? 100;
80
-
81
-
return (
82
-
<div>
83
-
<label className={ControlClasses.title}>{displayName}</label>
84
-
<Slider
85
-
initialValue={value ?? 0}
86
-
minValue={castedSetting.min ?? 0}
87
-
maxValue={castedSetting.max ?? 100}
88
-
onValueChange={(value: number) => {
89
-
const rounded = Math.max(min, Math.min(max, Math.round(value)));
90
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
91
-
}}
92
-
/>
93
-
</div>
94
-
);
95
-
}
96
-
97
-
function String({ ext, name, setting }: SettingsProps) {
98
-
const { TextInput, ControlClasses } = CommonComponents;
99
-
const { value, displayName } = Flux.useStateFromStores(
100
-
[MoonbaseSettingsStore],
101
-
() => {
102
-
return {
103
-
value: MoonbaseSettingsStore.getExtensionConfig<string>(ext.id, name),
104
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
105
-
ext.id,
106
-
name
107
-
)
108
-
};
109
-
},
110
-
[ext.id, name]
111
-
);
112
-
113
-
return (
114
-
<div>
115
-
<label className={ControlClasses.title}>{displayName}</label>
116
-
<TextInput
117
-
value={value ?? ""}
118
-
onChange={(value: string) => {
119
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
120
-
}}
121
-
/>
122
-
</div>
123
-
);
124
-
}
125
-
126
-
function Select({ ext, name, setting }: SettingsProps) {
127
-
const { ControlClasses, SingleSelect } = CommonComponents;
128
-
const { value, displayName } = Flux.useStateFromStores(
129
-
[MoonbaseSettingsStore],
130
-
() => {
131
-
return {
132
-
value: MoonbaseSettingsStore.getExtensionConfig<string>(ext.id, name),
133
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
134
-
ext.id,
135
-
name
136
-
)
137
-
};
138
-
},
139
-
[ext.id, name]
140
-
);
141
-
142
-
const castedSetting = setting as SelectSettingType;
143
-
const options = castedSetting.options;
144
-
145
-
return (
146
-
<div>
147
-
<label className={ControlClasses.title}>{displayName}</label>
148
-
<SingleSelect
149
-
autofocus={false}
150
-
clearable={false}
151
-
value={value ?? ""}
152
-
options={options.map((o) => ({ value: o, label: o }))}
153
-
onChange={(value: string) => {
154
-
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
155
-
}}
156
-
/>
157
-
</div>
158
-
);
159
-
}
160
-
161
-
function List({ ext, name, setting }: SettingsProps) {
162
-
const { ControlClasses, Select, useVariableSelect, multiSelect } =
163
-
CommonComponents;
164
-
const { value, displayName } = Flux.useStateFromStores(
165
-
[MoonbaseSettingsStore],
166
-
() => {
167
-
return {
168
-
value:
169
-
MoonbaseSettingsStore.getExtensionConfig<string>(ext.id, name) ??
170
-
[],
171
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
172
-
ext.id,
173
-
name
174
-
)
175
-
};
176
-
},
177
-
[ext.id, name]
178
-
);
179
-
180
-
const castedSetting = setting as SelectSettingType;
181
-
const options = castedSetting.options;
182
-
183
-
return (
184
-
<div>
185
-
<label className={ControlClasses.title}>{displayName}</label>
186
-
<Select
187
-
autofocus={false}
188
-
clearable={false}
189
-
options={options.map((o) => ({ value: o, label: o }))}
190
-
{...useVariableSelect({
191
-
onSelectInteraction: multiSelect,
192
-
value: new Set(Array.isArray(value) ? value : [value]),
193
-
onChange: (value: string) => {
194
-
MoonbaseSettingsStore.setExtensionConfig(
195
-
ext.id,
196
-
name,
197
-
Array.from(value)
198
-
);
199
-
}
200
-
})}
201
-
/>
202
-
</div>
203
-
);
204
-
}
205
-
206
-
function Dictionary({ ext, name, setting }: SettingsProps) {
207
-
const { TextInput, ControlClasses, Button, Flex } = CommonComponents;
208
-
const { value, displayName } = Flux.useStateFromStores(
209
-
[MoonbaseSettingsStore],
210
-
() => {
211
-
return {
212
-
value: MoonbaseSettingsStore.getExtensionConfig<
213
-
Record<string, string>
214
-
>(ext.id, name),
215
-
displayName: MoonbaseSettingsStore.getExtensionConfigName(
216
-
ext.id,
217
-
name
218
-
)
219
-
};
220
-
},
221
-
[ext.id, name]
222
-
);
223
-
224
-
const castedSetting = setting as DictionarySettingType;
225
-
const entries = Object.entries(value ?? {});
226
-
227
-
return (
228
-
<Flex direction={Flex.Direction.VERTICAL}>
229
-
<label className={ControlClasses.title}>{displayName}</label>
230
-
{entries.map(([key, val], i) => (
231
-
// FIXME: stylesheets
232
-
<div
233
-
key={i}
234
-
style={{
235
-
display: "grid",
236
-
height: "40px",
237
-
gap: "10px",
238
-
gridTemplateColumns: "1fr 1fr 40px"
239
-
}}
240
-
>
241
-
<TextInput
242
-
value={key}
243
-
onChange={(newKey: string) => {
244
-
entries[i][0] = newKey;
245
-
MoonbaseSettingsStore.setExtensionConfig(
246
-
ext.id,
247
-
name,
248
-
Object.fromEntries(entries)
249
-
);
250
-
}}
251
-
/>
252
-
<TextInput
253
-
value={val}
254
-
onChange={(newValue: string) => {
255
-
entries[i][1] = newValue;
256
-
MoonbaseSettingsStore.setExtensionConfig(
257
-
ext.id,
258
-
name,
259
-
Object.fromEntries(entries)
260
-
);
261
-
}}
262
-
/>
263
-
<Button
264
-
color={Button.Colors.RED}
265
-
size={Button.Sizes.ICON}
266
-
onClick={() => {
267
-
entries.splice(i, 1);
268
-
MoonbaseSettingsStore.setExtensionConfig(
269
-
ext.id,
270
-
name,
271
-
Object.fromEntries(entries)
272
-
);
273
-
}}
274
-
>
275
-
X
276
-
</Button>
277
-
</div>
278
-
))}
279
-
280
-
<Button
281
-
look={Button.Looks.FILLED}
282
-
color={Button.Colors.GREEN}
283
-
onClick={() => {
284
-
entries.push([`entry-${entries.length}`, ""]);
285
-
MoonbaseSettingsStore.setExtensionConfig(
286
-
ext.id,
287
-
name,
288
-
Object.fromEntries(entries)
289
-
);
290
-
}}
291
-
>
292
-
Add new entry
293
-
</Button>
294
-
</Flex>
295
-
);
296
-
}
297
-
298
-
function Setting({ ext, name, setting }: SettingsProps) {
299
-
const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
300
-
[ExtensionSettingType.Boolean]: Boolean,
301
-
[ExtensionSettingType.Number]: Number,
302
-
[ExtensionSettingType.String]: String,
303
-
[ExtensionSettingType.Select]: Select,
304
-
[ExtensionSettingType.List]: List,
305
-
[ExtensionSettingType.Dictionary]: Dictionary
306
-
};
307
-
const element = elements[setting.type];
308
-
if (element == null) return <></>;
309
-
return React.createElement(element, { ext, name, setting });
310
-
}
311
-
312
-
function Settings({ ext }: { ext: MoonbaseExtension }) {
313
-
const { Flex } = CommonComponents;
314
-
return (
315
-
<Flex direction={Flex.Direction.VERTICAL}>
316
-
{Object.entries(ext.manifest.settings!).map(([name, setting]) => (
317
-
<Setting ext={ext} key={name} name={name} setting={setting} />
318
-
))}
319
-
</Flex>
320
-
);
321
-
}
322
-
323
-
return {
324
-
Boolean,
325
-
Settings
326
-
};
327
-
};
+36
packages/core-extensions/src/moonbase/webpackModules/ThemeDarkIcon.tsx
+36
packages/core-extensions/src/moonbase/webpackModules/ThemeDarkIcon.tsx
···
1
+
// RIP to ThemeDarkIcon ????-2025
2
+
// <Cynthia> Failed to remap "ThemeDarkIcon" in "discord/components/common/index"
3
+
// <NotNite> bro are you fucking kidding me
4
+
// <NotNite> that's literally the icon we use for the update banner
5
+
6
+
import React from "@moonlight-mod/wp/react";
7
+
import icons from "@moonlight-mod/wp/common_icons";
8
+
import type { IconProps } from "@moonlight-mod/types/coreExtensions/common";
9
+
10
+
export default function ThemeDarkIcon(props?: IconProps) {
11
+
const parsed = icons.parseProps(props);
12
+
13
+
return (
14
+
<svg
15
+
aria-hidden="true"
16
+
role="img"
17
+
xmlns="http://www.w3.org/2000/svg"
18
+
width={parsed.width}
19
+
height={parsed.height}
20
+
fill="none"
21
+
viewBox="0 0 24 24"
22
+
>
23
+
<path
24
+
fill={parsed.fill}
25
+
className={parsed.className}
26
+
d="M20.52 18.96c.32-.4-.01-.96-.52-.96A11 11 0 0 1 9.77 2.94c.31-.78-.3-1.68-1.1-1.43a11 11 0 1 0 11.85 17.45Z"
27
+
/>
28
+
29
+
<path
30
+
fill={parsed.fill}
31
+
className={parsed.className}
32
+
d="m17.73 9.27-.76-2.02a.5.5 0 0 0-.94 0l-.76 2.02-2.02.76a.5.5 0 0 0 0 .94l2.02.76.76 2.02a.5.5 0 0 0 .94 0l.76-2.02 2.02-.76a.5.5 0 0 0 0-.94l-2.02-.76ZM19.73 2.62l.45 1.2 1.2.45c.21.08.21.38 0 .46l-1.2.45-.45 1.2a.25.25 0 0 1-.46 0l-.45-1.2-1.2-.45a.25.25 0 0 1 0-.46l1.2-.45.45-1.2a.25.25 0 0 1 .46 0Z"
33
+
/>
34
+
</svg>
35
+
);
36
+
}
+263
packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
+263
packages/core-extensions/src/moonbase/webpackModules/crashScreen.tsx
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import { Button, TabBar } from "@moonlight-mod/wp/discord/components/common/index";
3
+
import { useStateFromStores, useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
4
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
5
+
import { RepositoryManifest, UpdateState } from "../types";
6
+
import { ConfigExtension, DetectedExtension } from "@moonlight-mod/types";
7
+
import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css";
8
+
9
+
const MODULE_REGEX = /Webpack-Module\/(\d+)\/(\d+)/g;
10
+
11
+
const logger = moonlight.getLogger("moonbase/crashScreen");
12
+
13
+
type ErrorState = {
14
+
error: Error;
15
+
info: {
16
+
componentStack: string;
17
+
};
18
+
__moonlight_update?: UpdateState;
19
+
};
20
+
21
+
type WrapperProps = {
22
+
action: React.ReactNode;
23
+
state: ErrorState;
24
+
};
25
+
26
+
type UpdateCardProps = {
27
+
id: number;
28
+
ext: {
29
+
version: string;
30
+
download: string;
31
+
updateManifest: RepositoryManifest;
32
+
};
33
+
};
34
+
35
+
const updateStrings: Record<UpdateState, string> = {
36
+
[UpdateState.Ready]: "A new version of moonlight is available.",
37
+
[UpdateState.Working]: "Updating moonlight...",
38
+
[UpdateState.Installed]: "Updated moonlight. Click Reload to apply changes.",
39
+
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer."
40
+
};
41
+
const buttonStrings: Record<UpdateState, string> = {
42
+
[UpdateState.Ready]: "Update moonlight",
43
+
[UpdateState.Working]: "Updating moonlight...",
44
+
[UpdateState.Installed]: "",
45
+
[UpdateState.Failed]: "Update failed"
46
+
};
47
+
const extensionButtonStrings: Record<UpdateState, string> = {
48
+
[UpdateState.Ready]: "Update",
49
+
[UpdateState.Working]: "Updating...",
50
+
[UpdateState.Installed]: "Updated",
51
+
[UpdateState.Failed]: "Update failed"
52
+
};
53
+
54
+
function ExtensionUpdateCard({ id, ext }: UpdateCardProps) {
55
+
const [state, setState] = React.useState(UpdateState.Ready);
56
+
const installed = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.getExtension(id), [id]);
57
+
58
+
return (
59
+
<div className="moonbase-crash-extensionCard">
60
+
<div className="moonbase-crash-extensionCard-meta">
61
+
<div className="moonbase-crash-extensionCard-title">
62
+
{ext.updateManifest.meta?.name ?? ext.updateManifest.id}
63
+
</div>
64
+
<div className="moonbase-crash-extensionCard-version">{`v${installed?.manifest?.version ?? "???"} -> v${
65
+
ext.version
66
+
}`}</div>
67
+
</div>
68
+
<div className="moonbase-crash-extensionCard-button">
69
+
<Button
70
+
color={Button.Colors.GREEN}
71
+
disabled={state !== UpdateState.Ready}
72
+
onClick={() => {
73
+
setState(UpdateState.Working);
74
+
MoonbaseSettingsStore.installExtension(id)
75
+
.then(() => setState(UpdateState.Installed))
76
+
.catch(() => setState(UpdateState.Failed));
77
+
}}
78
+
>
79
+
{extensionButtonStrings[state]}
80
+
</Button>
81
+
</div>
82
+
</div>
83
+
);
84
+
}
85
+
86
+
function ExtensionDisableCard({ ext }: { ext: DetectedExtension }) {
87
+
function disableWithDependents() {
88
+
const disable = new Set<string>();
89
+
disable.add(ext.id);
90
+
for (const [id, dependencies] of moonlightNode.processedExtensions.dependencyGraph) {
91
+
if (dependencies?.has(ext.id)) disable.add(id);
92
+
}
93
+
94
+
const config = structuredClone(moonlightNode.config);
95
+
for (const id in config.extensions) {
96
+
if (!disable.has(id)) continue;
97
+
if (typeof config.extensions[id] === "boolean") config.extensions[id] = false;
98
+
else (config.extensions[id] as ConfigExtension).enabled = false;
99
+
}
100
+
101
+
let msg = `Are you sure you want to disable "${ext.manifest.meta?.name ?? ext.id}"`;
102
+
if (disable.size > 1) {
103
+
msg += ` and its ${disable.size - 1} dependent${disable.size - 1 === 1 ? "" : "s"}`;
104
+
}
105
+
msg += "?";
106
+
107
+
if (confirm(msg)) {
108
+
moonlightNode.writeConfig(config);
109
+
window.location.reload();
110
+
}
111
+
}
112
+
113
+
return (
114
+
<div className="moonbase-crash-extensionCard">
115
+
<div className="moonbase-crash-extensionCard-meta">
116
+
<div className="moonbase-crash-extensionCard-title">{ext.manifest.meta?.name ?? ext.id}</div>
117
+
<div className="moonbase-crash-extensionCard-version">{`v${ext.manifest.version ?? "???"}`}</div>
118
+
</div>
119
+
<div className="moonbase-crash-extensionCard-button">
120
+
<Button color={Button.Colors.RED} onClick={disableWithDependents}>
121
+
Disable
122
+
</Button>
123
+
</div>
124
+
</div>
125
+
);
126
+
}
127
+
128
+
export function wrapAction({ action, state }: WrapperProps) {
129
+
const [tab, setTab] = React.useState("crash");
130
+
131
+
const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
132
+
const { updates } = MoonbaseSettingsStore;
133
+
return {
134
+
updates: Object.entries(updates),
135
+
updateCount: Object.keys(updates).length
136
+
};
137
+
});
138
+
139
+
const causes = React.useMemo(() => {
140
+
const causes = new Set<string>();
141
+
if (state.error.stack) {
142
+
for (const [, , id] of state.error.stack.matchAll(MODULE_REGEX))
143
+
for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
144
+
}
145
+
for (const [, , id] of state.info.componentStack.matchAll(MODULE_REGEX))
146
+
for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
147
+
148
+
for (const [path, id] of Object.entries(moonlight.moonmap.modules)) {
149
+
const MAPPING_REGEX = new RegExp(
150
+
// @ts-expect-error Only Firefox has RegExp.escape
151
+
`(${RegExp.escape ? RegExp.escape(path) : path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`,
152
+
"g"
153
+
);
154
+
155
+
if (state.error.stack) {
156
+
for (const match of state.error.stack.matchAll(MAPPING_REGEX))
157
+
if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
158
+
}
159
+
for (const match of state.info.componentStack.matchAll(MAPPING_REGEX))
160
+
if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext);
161
+
}
162
+
163
+
return [...causes];
164
+
}, []);
165
+
166
+
return (
167
+
<div className="moonbase-crash-wrapper">
168
+
{action}
169
+
<TabBar
170
+
className={`${DiscoveryClasses.tabBar} moonbase-crash-tabs`}
171
+
type="top"
172
+
selectedItem={tab}
173
+
onItemSelect={(v) => setTab(v)}
174
+
>
175
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="crash">
176
+
Crash details
177
+
</TabBar.Item>
178
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="extensions" disabled={updateCount === 0}>
179
+
{`Extension updates (${updateCount})`}
180
+
</TabBar.Item>
181
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id="causes" disabled={causes.length === 0}>
182
+
{`Possible causes (${causes.length})`}
183
+
</TabBar.Item>
184
+
</TabBar>
185
+
{tab === "crash" ? (
186
+
<div className="moonbase-crash-details-wrapper">
187
+
<pre className="moonbase-crash-details">
188
+
<code>
189
+
{state.error.stack}
190
+
{"\n\nComponent stack:"}
191
+
{state.info.componentStack}
192
+
</code>
193
+
</pre>
194
+
</div>
195
+
) : null}
196
+
{tab === "extensions" ? (
197
+
<div className="moonbase-crash-extensions">
198
+
{updates.map(([id, ext]) => (
199
+
<ExtensionUpdateCard id={Number(id)} ext={ext} />
200
+
))}
201
+
</div>
202
+
) : null}
203
+
{tab === "causes" ? (
204
+
<div className="moonbase-crash-extensions">
205
+
{causes
206
+
.map((ext) => moonlightNode.extensions.find((e) => e.id === ext)!)
207
+
.map((ext) => (
208
+
<ExtensionDisableCard ext={ext} />
209
+
))}
210
+
</div>
211
+
) : null}
212
+
</div>
213
+
);
214
+
}
215
+
216
+
export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
217
+
if (!state.__moonlight_update) {
218
+
setState({
219
+
...state,
220
+
__moonlight_update: UpdateState.Ready
221
+
});
222
+
}
223
+
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
224
+
225
+
return newVersion == null ? null : (
226
+
<p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p>
227
+
);
228
+
}
229
+
230
+
export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) {
231
+
const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion);
232
+
return newVersion == null ||
233
+
state.__moonlight_update === UpdateState.Installed ||
234
+
state.__moonlight_update === undefined ? null : (
235
+
<Button
236
+
size={Button.Sizes.LARGE}
237
+
disabled={state.__moonlight_update !== UpdateState.Ready}
238
+
onClick={() => {
239
+
setState({
240
+
...state,
241
+
__moonlight_update: UpdateState.Working
242
+
});
243
+
244
+
MoonbaseSettingsStore.updateMoonlight()
245
+
.then(() => {
246
+
setState({
247
+
...state,
248
+
__moonlight_update: UpdateState.Installed
249
+
});
250
+
})
251
+
.catch((e) => {
252
+
logger.error(e);
253
+
setState({
254
+
...state,
255
+
__moonlight_update: UpdateState.Failed
256
+
});
257
+
});
258
+
}}
259
+
>
260
+
{state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""}
261
+
</Button>
262
+
);
263
+
}
+10
packages/core-extensions/src/moonbase/webpackModules/moonbase.ts
+10
packages/core-extensions/src/moonbase/webpackModules/moonbase.ts
···
1
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
2
+
import type { Moonbase } from "@moonlight-mod/types/coreExtensions/moonbase";
3
+
4
+
export const moonbase: Moonbase = {
5
+
registerConfigComponent(ext, option, component) {
6
+
MoonbaseSettingsStore.registerConfigComponent(ext, option, component);
7
+
}
8
+
};
9
+
10
+
export default moonbase;
+100
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
+100
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
···
1
+
import settings from "@moonlight-mod/wp/settings_settings";
2
+
import React from "@moonlight-mod/wp/react";
3
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
4
+
import { Moonbase, pages, RestartAdviceMessage, Update } from "@moonlight-mod/wp/moonbase_ui";
5
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
6
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
7
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
8
+
import { Text, Breadcrumbs } from "@moonlight-mod/wp/discord/components/common/index";
9
+
import { MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu";
10
+
11
+
const notice = {
12
+
stores: [MoonbaseSettingsStore],
13
+
element: () => {
14
+
// Require it here because lazy loading SUX
15
+
const SettingsNotice = spacepack.require("discord/components/common/SettingsNotice").default;
16
+
return (
17
+
<SettingsNotice
18
+
submitting={MoonbaseSettingsStore.submitting}
19
+
onReset={() => {
20
+
MoonbaseSettingsStore.reset();
21
+
}}
22
+
onSave={() => {
23
+
MoonbaseSettingsStore.writeConfig();
24
+
}}
25
+
/>
26
+
);
27
+
}
28
+
};
29
+
30
+
const oldLocation = MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "oldLocation", false);
31
+
const position = oldLocation ? -2 : -9999;
32
+
33
+
function addSection(id: string, name: string, element: React.FunctionComponent) {
34
+
settings.addSection(`moonbase-${id}`, name, element, null, position, notice);
35
+
}
36
+
37
+
// FIXME: move to component types
38
+
type Breadcrumb = {
39
+
id: string;
40
+
label: string;
41
+
};
42
+
43
+
function renderBreadcrumb(crumb: Breadcrumb, last: boolean) {
44
+
return (
45
+
<Text variant="heading-lg/semibold" tag="h2" color={last ? "header-primary" : "header-secondary"}>
46
+
{crumb.label}
47
+
</Text>
48
+
);
49
+
}
50
+
51
+
if (!oldLocation) {
52
+
settings.addDivider(position);
53
+
}
54
+
55
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) {
56
+
if (oldLocation) settings.addHeader("Moonbase", position);
57
+
58
+
const _pages = oldLocation ? pages : pages.reverse();
59
+
for (const page of _pages) {
60
+
addSection(page.id, page.name, () => {
61
+
const breadcrumbs = [
62
+
{ id: "moonbase", label: "Moonbase" },
63
+
{ id: page.id, label: page.name }
64
+
];
65
+
return (
66
+
<>
67
+
<Breadcrumbs
68
+
className={Margins.marginBottom20}
69
+
renderCustomBreadcrumb={renderBreadcrumb}
70
+
breadcrumbs={breadcrumbs}
71
+
activeId={page.id}
72
+
>
73
+
{page.name}
74
+
</Breadcrumbs>
75
+
76
+
<RestartAdviceMessage />
77
+
<Update />
78
+
79
+
<page.element />
80
+
</>
81
+
);
82
+
});
83
+
}
84
+
85
+
if (!oldLocation) settings.addHeader("Moonbase", position);
86
+
} else {
87
+
settings.addSection("moonbase", "Moonbase", Moonbase, null, position, notice);
88
+
89
+
settings.addSectionMenuItems(
90
+
"moonbase",
91
+
...pages.map((page, i) => (
92
+
<MenuItem
93
+
key={page.id}
94
+
id={`moonbase-${page.id}`}
95
+
label={page.name}
96
+
action={() => UserSettingsModalActionCreators.open("moonbase", i.toString())}
97
+
/>
98
+
))
99
+
);
100
+
}
+541
packages/core-extensions/src/moonbase/webpackModules/stores.ts
+541
packages/core-extensions/src/moonbase/webpackModules/stores.ts
···
1
+
import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types";
2
+
import {
3
+
ExtensionState,
4
+
MoonbaseExtension,
5
+
MoonbaseNatives,
6
+
RepositoryManifest,
7
+
RestartAdvice,
8
+
UpdateState
9
+
} from "../types";
10
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
11
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
12
+
import getNatives from "../native";
13
+
import { mainRepo } from "@moonlight-mod/types/constants";
14
+
import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader";
15
+
import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase";
16
+
import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config";
17
+
import diff from "microdiff";
18
+
19
+
const logger = moonlight.getLogger("moonbase");
20
+
21
+
let natives: MoonbaseNatives = moonlight.getNatives("moonbase");
22
+
if (moonlightNode.isBrowser) natives = getNatives();
23
+
24
+
class MoonbaseSettingsStore extends Store<any> {
25
+
private initialConfig: Config;
26
+
private savedConfig: Config;
27
+
private config: Config;
28
+
private extensionIndex: number;
29
+
private configComponents: Record<string, Record<string, CustomComponent>> = {};
30
+
31
+
modified: boolean;
32
+
submitting: boolean;
33
+
installing: boolean;
34
+
35
+
#updateState = UpdateState.Ready;
36
+
get updateState() {
37
+
return this.#updateState;
38
+
}
39
+
newVersion: string | null;
40
+
shouldShowNotice: boolean;
41
+
42
+
restartAdvice = RestartAdvice.NotNeeded;
43
+
44
+
extensions: { [id: number]: MoonbaseExtension };
45
+
updates: {
46
+
[id: number]: {
47
+
version: string;
48
+
download: string;
49
+
updateManifest: RepositoryManifest;
50
+
};
51
+
};
52
+
53
+
constructor() {
54
+
super(Dispatcher);
55
+
56
+
this.initialConfig = moonlightNode.config;
57
+
this.savedConfig = moonlightNode.config;
58
+
this.config = this.clone(this.savedConfig);
59
+
this.extensionIndex = 0;
60
+
61
+
this.modified = false;
62
+
this.submitting = false;
63
+
this.installing = false;
64
+
65
+
this.newVersion = null;
66
+
this.shouldShowNotice = false;
67
+
68
+
this.extensions = {};
69
+
this.updates = {};
70
+
for (const ext of moonlightNode.extensions) {
71
+
const uniqueId = this.extensionIndex++;
72
+
this.extensions[uniqueId] = {
73
+
...ext,
74
+
uniqueId,
75
+
state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled,
76
+
compat: checkExtensionCompat(ext.manifest),
77
+
hasUpdate: false
78
+
};
79
+
}
80
+
81
+
this.checkUpdates();
82
+
}
83
+
84
+
async checkUpdates() {
85
+
await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]);
86
+
this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0;
87
+
this.emitChange();
88
+
}
89
+
90
+
private async checkExtensionUpdates() {
91
+
const repositories = await natives!.fetchRepositories(this.savedConfig.repositories);
92
+
93
+
// Reset update state
94
+
for (const id in this.extensions) {
95
+
const ext = this.extensions[id];
96
+
ext.hasUpdate = false;
97
+
ext.changelog = undefined;
98
+
}
99
+
this.updates = {};
100
+
101
+
for (const [repo, exts] of Object.entries(repositories)) {
102
+
for (const ext of exts) {
103
+
const uniqueId = this.extensionIndex++;
104
+
const extensionData = {
105
+
id: ext.id,
106
+
uniqueId,
107
+
manifest: ext,
108
+
source: { type: ExtensionLoadSource.Normal, url: repo },
109
+
state: ExtensionState.NotDownloaded,
110
+
compat: ExtensionCompat.Compatible,
111
+
hasUpdate: false
112
+
};
113
+
114
+
// Don't present incompatible updates
115
+
if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue;
116
+
117
+
const existing = this.getExisting(extensionData);
118
+
if (existing != null) {
119
+
// Make sure the download URL is properly updated
120
+
existing.manifest = {
121
+
...existing.manifest,
122
+
download: ext.download
123
+
};
124
+
125
+
if (this.hasUpdate(extensionData)) {
126
+
this.updates[existing.uniqueId] = {
127
+
version: ext.version!,
128
+
download: ext.download,
129
+
updateManifest: ext
130
+
};
131
+
existing.hasUpdate = true;
132
+
existing.changelog = ext.meta?.changelog;
133
+
}
134
+
} else {
135
+
this.extensions[uniqueId] = extensionData;
136
+
}
137
+
}
138
+
}
139
+
}
140
+
141
+
private async checkMoonlightUpdates() {
142
+
this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true)
143
+
? await natives!.checkForMoonlightUpdate()
144
+
: null;
145
+
}
146
+
147
+
private getExisting(ext: MoonbaseExtension) {
148
+
return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url);
149
+
}
150
+
151
+
private hasUpdate(ext: MoonbaseExtension) {
152
+
const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url);
153
+
if (existing == null) return false;
154
+
155
+
return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded;
156
+
}
157
+
158
+
// Jank
159
+
private isModified() {
160
+
const orig = JSON.stringify(this.savedConfig);
161
+
const curr = JSON.stringify(this.config);
162
+
return orig !== curr;
163
+
}
164
+
165
+
get busy() {
166
+
return this.submitting || this.installing;
167
+
}
168
+
169
+
// Required for the settings store contract
170
+
showNotice() {
171
+
return this.modified;
172
+
}
173
+
174
+
getExtension(uniqueId: number) {
175
+
return this.extensions[uniqueId];
176
+
}
177
+
178
+
getExtensionUniqueId(id: string) {
179
+
return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId;
180
+
}
181
+
182
+
getExtensionConflicting(uniqueId: number) {
183
+
const ext = this.getExtension(uniqueId);
184
+
if (ext.state !== ExtensionState.NotDownloaded) return false;
185
+
return Object.values(this.extensions).some(
186
+
(e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded
187
+
);
188
+
}
189
+
190
+
getExtensionName(uniqueId: number) {
191
+
const ext = this.getExtension(uniqueId);
192
+
return ext.manifest.meta?.name ?? ext.id;
193
+
}
194
+
195
+
getExtensionUpdate(uniqueId: number) {
196
+
return this.updates[uniqueId]?.version;
197
+
}
198
+
199
+
getExtensionEnabled(uniqueId: number) {
200
+
const ext = this.getExtension(uniqueId);
201
+
if (ext.state === ExtensionState.NotDownloaded) return false;
202
+
const val = this.config.extensions[ext.id];
203
+
if (val == null) return false;
204
+
return typeof val === "boolean" ? val : val.enabled;
205
+
}
206
+
207
+
getExtensionConfig<T>(uniqueId: number, key: string): T | undefined {
208
+
const ext = this.getExtension(uniqueId);
209
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
210
+
return getConfigOption(ext.id, key, this.config, settings);
211
+
}
212
+
213
+
getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined {
214
+
const cfg = this.config.extensions[id];
215
+
if (cfg == null || typeof cfg === "boolean") return defaultValue;
216
+
return cfg.config?.[key] ?? defaultValue;
217
+
}
218
+
219
+
getExtensionConfigName(uniqueId: number, key: string) {
220
+
const ext = this.getExtension(uniqueId);
221
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
222
+
return settings?.[key]?.displayName ?? key;
223
+
}
224
+
225
+
getExtensionConfigDescription(uniqueId: number, key: string) {
226
+
const ext = this.getExtension(uniqueId);
227
+
const settings = ext.settingsOverride ?? ext.manifest.settings;
228
+
return settings?.[key]?.description;
229
+
}
230
+
231
+
setExtensionConfig(id: string, key: string, value: any) {
232
+
setConfigOption(this.config, id, key, value);
233
+
this.modified = this.isModified();
234
+
this.emitChange();
235
+
}
236
+
237
+
setExtensionEnabled(uniqueId: number, enabled: boolean) {
238
+
const ext = this.getExtension(uniqueId);
239
+
let val = this.config.extensions[ext.id];
240
+
241
+
if (val == null) {
242
+
this.config.extensions[ext.id] = { enabled };
243
+
this.modified = this.isModified();
244
+
this.emitChange();
245
+
return;
246
+
}
247
+
248
+
if (typeof val === "boolean") {
249
+
val = enabled;
250
+
} else {
251
+
val.enabled = enabled;
252
+
}
253
+
254
+
this.config.extensions[ext.id] = val;
255
+
this.modified = this.isModified();
256
+
this.emitChange();
257
+
}
258
+
259
+
dismissAllExtensionUpdates() {
260
+
for (const id in this.extensions) {
261
+
this.extensions[id].hasUpdate = false;
262
+
}
263
+
this.emitChange();
264
+
}
265
+
266
+
async updateAllExtensions() {
267
+
for (const id of Object.keys(this.updates)) {
268
+
try {
269
+
await this.installExtension(parseInt(id));
270
+
} catch (e) {
271
+
logger.error("Error bulk updating extension", id, e);
272
+
}
273
+
}
274
+
}
275
+
276
+
async installExtension(uniqueId: number) {
277
+
const ext = this.getExtension(uniqueId);
278
+
if (!("download" in ext.manifest)) {
279
+
throw new Error("Extension has no download URL");
280
+
}
281
+
282
+
this.installing = true;
283
+
try {
284
+
const update = this.updates[uniqueId];
285
+
const url = update?.download ?? ext.manifest.download;
286
+
await natives!.installExtension(ext.manifest, url, ext.source.url!);
287
+
if (ext.state === ExtensionState.NotDownloaded) {
288
+
this.extensions[uniqueId].state = ExtensionState.Disabled;
289
+
}
290
+
291
+
if (update != null) {
292
+
const existing = this.extensions[uniqueId];
293
+
existing.settingsOverride = update.updateManifest.settings;
294
+
existing.compat = checkExtensionCompat(update.updateManifest);
295
+
existing.manifest = update.updateManifest;
296
+
existing.changelog = update.updateManifest.meta?.changelog;
297
+
}
298
+
299
+
delete this.updates[uniqueId];
300
+
} catch (e) {
301
+
logger.error("Error installing extension:", e);
302
+
}
303
+
304
+
this.installing = false;
305
+
this.restartAdvice = this.#computeRestartAdvice();
306
+
this.emitChange();
307
+
}
308
+
309
+
private getRank(ext: MoonbaseExtension) {
310
+
if (ext.source.type === ExtensionLoadSource.Developer) return 3;
311
+
if (ext.source.type === ExtensionLoadSource.Core) return 2;
312
+
if (ext.source.url === mainRepo) return 1;
313
+
return 0;
314
+
}
315
+
316
+
async getDependencies(uniqueId: number) {
317
+
const ext = this.getExtension(uniqueId);
318
+
319
+
const missingDeps = [];
320
+
for (const dep of ext.manifest.dependencies ?? []) {
321
+
const anyInstalled = Object.values(this.extensions).some(
322
+
(e) => e.id === dep && e.state !== ExtensionState.NotDownloaded
323
+
);
324
+
if (!anyInstalled) missingDeps.push(dep);
325
+
}
326
+
327
+
if (missingDeps.length === 0) return null;
328
+
329
+
const deps: Record<string, MoonbaseExtension[]> = {};
330
+
for (const dep of missingDeps) {
331
+
const candidates = Object.values(this.extensions).filter((e) => e.id === dep);
332
+
333
+
deps[dep] = candidates.sort((a, b) => {
334
+
const aRank = this.getRank(a);
335
+
const bRank = this.getRank(b);
336
+
if (aRank === bRank) {
337
+
const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!);
338
+
const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!);
339
+
return repoIndex - otherRepoIndex;
340
+
} else {
341
+
return bRank - aRank;
342
+
}
343
+
});
344
+
}
345
+
346
+
return deps;
347
+
}
348
+
349
+
async deleteExtension(uniqueId: number) {
350
+
const ext = this.getExtension(uniqueId);
351
+
if (ext == null) return;
352
+
353
+
this.installing = true;
354
+
try {
355
+
await natives!.deleteExtension(ext.id);
356
+
this.extensions[uniqueId].state = ExtensionState.NotDownloaded;
357
+
} catch (e) {
358
+
logger.error("Error deleting extension:", e);
359
+
}
360
+
361
+
this.installing = false;
362
+
this.restartAdvice = this.#computeRestartAdvice();
363
+
this.emitChange();
364
+
}
365
+
366
+
async updateMoonlight() {
367
+
this.#updateState = UpdateState.Working;
368
+
this.emitChange();
369
+
370
+
await natives
371
+
.updateMoonlight()
372
+
.then(() => (this.#updateState = UpdateState.Installed))
373
+
.catch((e) => {
374
+
logger.error(e);
375
+
this.#updateState = UpdateState.Failed;
376
+
});
377
+
378
+
this.emitChange();
379
+
}
380
+
381
+
getConfigOption<K extends keyof Config>(key: K): Config[K] {
382
+
return this.config[key];
383
+
}
384
+
385
+
setConfigOption<K extends keyof Config>(key: K, value: Config[K]) {
386
+
this.config[key] = value;
387
+
this.modified = this.isModified();
388
+
this.emitChange();
389
+
}
390
+
391
+
tryGetExtensionName(id: string) {
392
+
const uniqueId = this.getExtensionUniqueId(id);
393
+
return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id;
394
+
}
395
+
396
+
registerConfigComponent(ext: string, name: string, component: CustomComponent) {
397
+
if (!(ext in this.configComponents)) this.configComponents[ext] = {};
398
+
this.configComponents[ext][name] = component;
399
+
}
400
+
401
+
getExtensionConfigComponent(ext: string, name: string) {
402
+
return this.configComponents[ext]?.[name];
403
+
}
404
+
405
+
#computeRestartAdvice() {
406
+
// If moonlight update needs a restart, always hide advice.
407
+
if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded;
408
+
409
+
const i = this.initialConfig; // Initial config, from startup
410
+
const n = this.config; // New config about to be saved
411
+
412
+
let returnedAdvice = RestartAdvice.NotNeeded;
413
+
const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice);
414
+
415
+
// Top-level keys, repositories is not needed here because Moonbase handles it.
416
+
if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded);
417
+
if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded);
418
+
if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0)
419
+
return updateAdvice(RestartAdvice.RestartNeeded);
420
+
421
+
// Extension specific logic
422
+
for (const id in n.extensions) {
423
+
// Installed extension (might not be detected yet)
424
+
const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded);
425
+
// Installed and detected extension
426
+
const detected = moonlightNode.extensions.find((e) => e.id === id);
427
+
428
+
// If it's not installed at all, we don't care
429
+
if (!ext) continue;
430
+
431
+
const initState = i.extensions[id];
432
+
const newState = n.extensions[id];
433
+
434
+
const newEnabled = typeof newState === "boolean" ? newState : newState.enabled;
435
+
// If it's enabled but not detected yet, restart.
436
+
if (newEnabled && !detected) {
437
+
return updateAdvice(RestartAdvice.RestartNeeded);
438
+
}
439
+
440
+
// Toggling extensions specifically wants to rely on the initial state,
441
+
// that's what was considered when loading extensions.
442
+
const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled);
443
+
if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) {
444
+
// If we have the extension locally, we confidently know if it has host/preload scripts.
445
+
// If not, we have to respect the environment specified in the manifest.
446
+
// If that is the default, we can't know what's needed.
447
+
448
+
if (detected?.scripts.hostPath || detected?.scripts.nodePath) {
449
+
return updateAdvice(RestartAdvice.RestartNeeded);
450
+
}
451
+
452
+
switch (ext.manifest.environment) {
453
+
case ExtensionEnvironment.Both:
454
+
case ExtensionEnvironment.Web:
455
+
updateAdvice(RestartAdvice.ReloadNeeded);
456
+
continue;
457
+
case ExtensionEnvironment.Desktop:
458
+
return updateAdvice(RestartAdvice.RestartNeeded);
459
+
default:
460
+
updateAdvice(RestartAdvice.ReloadNeeded);
461
+
continue;
462
+
}
463
+
}
464
+
465
+
const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config };
466
+
const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config };
467
+
468
+
const def = ext.manifest.settings;
469
+
if (!def) continue;
470
+
471
+
for (const key in def) {
472
+
const defaultValue = def[key].default;
473
+
474
+
initConfig[key] ??= defaultValue;
475
+
newConfig[key] ??= defaultValue;
476
+
}
477
+
478
+
const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]);
479
+
for (const key in def) {
480
+
if (!changedKeys.includes(key)) continue;
481
+
482
+
const advice = def[key].advice;
483
+
switch (advice) {
484
+
case ExtensionSettingsAdvice.None:
485
+
updateAdvice(RestartAdvice.NotNeeded);
486
+
continue;
487
+
case ExtensionSettingsAdvice.Reload:
488
+
updateAdvice(RestartAdvice.ReloadNeeded);
489
+
continue;
490
+
case ExtensionSettingsAdvice.Restart:
491
+
updateAdvice(RestartAdvice.RestartNeeded);
492
+
continue;
493
+
default:
494
+
updateAdvice(RestartAdvice.ReloadSuggested);
495
+
}
496
+
}
497
+
}
498
+
499
+
return returnedAdvice;
500
+
}
501
+
502
+
writeConfig() {
503
+
this.submitting = true;
504
+
this.restartAdvice = this.#computeRestartAdvice();
505
+
const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories);
506
+
507
+
moonlightNode.writeConfig(this.config);
508
+
this.savedConfig = this.clone(this.config);
509
+
510
+
this.submitting = false;
511
+
this.modified = false;
512
+
this.emitChange();
513
+
514
+
if (modifiedRepos.length !== 0) this.checkUpdates();
515
+
}
516
+
517
+
reset() {
518
+
this.submitting = false;
519
+
this.modified = false;
520
+
this.config = this.clone(this.savedConfig);
521
+
this.emitChange();
522
+
}
523
+
524
+
restartDiscord() {
525
+
if (moonlightNode.isBrowser) {
526
+
window.location.reload();
527
+
} else {
528
+
// @ts-expect-error TODO: DiscordNative
529
+
window.DiscordNative.app.relaunch();
530
+
}
531
+
}
532
+
533
+
// Required because electron likes to make it immutable sometimes.
534
+
// This sucks.
535
+
private clone<T>(obj: T): T {
536
+
return structuredClone(obj);
537
+
}
538
+
}
539
+
540
+
const settingsStore = new MoonbaseSettingsStore();
541
+
export { settingsStore as MoonbaseSettingsStore };
+47
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
+47
packages/core-extensions/src/moonbase/webpackModules/ui/HelpMessage.tsx
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
3
+
import { Text } from "@moonlight-mod/wp/discord/components/common/index";
4
+
import HelpMessageClasses from "@moonlight-mod/wp/discord/components/common/HelpMessage.css";
5
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
6
+
7
+
// reimpl of HelpMessage but with a custom icon
8
+
export default function HelpMessage({
9
+
className,
10
+
text,
11
+
icon,
12
+
children,
13
+
type = "info"
14
+
}: {
15
+
className?: string;
16
+
text: string;
17
+
icon: React.ComponentType<any>;
18
+
type?: "warning" | "positive" | "error" | "info";
19
+
children?: React.ReactNode;
20
+
}) {
21
+
return (
22
+
<div
23
+
className={`${Margins.marginBottom20} ${HelpMessageClasses[type]} ${HelpMessageClasses.container} moonbase-help-message ${className}`}
24
+
>
25
+
<Flex direction={Flex.Direction.HORIZONTAL}>
26
+
<div
27
+
className={HelpMessageClasses.iconDiv}
28
+
style={{
29
+
alignItems: "center"
30
+
}}
31
+
>
32
+
{React.createElement(icon, {
33
+
size: "sm",
34
+
color: "currentColor",
35
+
className: HelpMessageClasses.icon
36
+
})}
37
+
</div>
38
+
39
+
<Text variant="text-sm/medium" color="currentColor" className={HelpMessageClasses.text}>
40
+
{text}
41
+
</Text>
42
+
43
+
{children}
44
+
</Flex>
45
+
</div>
46
+
);
47
+
}
+43
packages/core-extensions/src/moonbase/webpackModules/ui/RestartAdvice.tsx
+43
packages/core-extensions/src/moonbase/webpackModules/ui/RestartAdvice.tsx
···
1
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
2
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
3
+
import { Button, CircleWarningIcon } from "@moonlight-mod/wp/discord/components/common/index";
4
+
import React from "@moonlight-mod/wp/react";
5
+
import { RestartAdvice } from "../../types";
6
+
import HelpMessage from "./HelpMessage";
7
+
8
+
const strings: Record<RestartAdvice, string> = {
9
+
[RestartAdvice.NotNeeded]: "how did you even",
10
+
[RestartAdvice.ReloadSuggested]: "A reload might be needed to apply some of the changed options.",
11
+
[RestartAdvice.ReloadNeeded]: "A reload is needed to apply some of the changed options.",
12
+
[RestartAdvice.RestartNeeded]: "A restart is needed to apply some of the changed options."
13
+
};
14
+
15
+
const buttonStrings: Record<RestartAdvice, string> = {
16
+
[RestartAdvice.NotNeeded]: "huh?",
17
+
[RestartAdvice.ReloadSuggested]: "Reload",
18
+
[RestartAdvice.ReloadNeeded]: "Reload",
19
+
[RestartAdvice.RestartNeeded]: "Restart"
20
+
};
21
+
22
+
const actions: Record<RestartAdvice, () => void> = {
23
+
[RestartAdvice.NotNeeded]: () => {},
24
+
[RestartAdvice.ReloadSuggested]: () => window.location.reload(),
25
+
[RestartAdvice.ReloadNeeded]: () => window.location.reload(),
26
+
[RestartAdvice.RestartNeeded]: () => MoonbaseSettingsStore.restartDiscord()
27
+
};
28
+
29
+
export default function RestartAdviceMessage() {
30
+
const restartAdvice = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.restartAdvice);
31
+
32
+
if (restartAdvice === RestartAdvice.NotNeeded) return null;
33
+
34
+
return (
35
+
<div className="moonbase-help-message-sticky">
36
+
<HelpMessage text={strings[restartAdvice]} icon={CircleWarningIcon} type="warning">
37
+
<Button color={Button.Colors.YELLOW} size={Button.Sizes.TINY} onClick={actions[restartAdvice]}>
38
+
{buttonStrings[restartAdvice]}
39
+
</Button>
40
+
</HelpMessage>
41
+
</div>
42
+
);
43
+
}
+110
packages/core-extensions/src/moonbase/webpackModules/ui/about.tsx
+110
packages/core-extensions/src/moonbase/webpackModules/ui/about.tsx
···
1
+
import {
2
+
Text,
3
+
useThemeContext,
4
+
Button,
5
+
AngleBracketsIcon,
6
+
BookCheckIcon,
7
+
ClydeIcon
8
+
} from "@moonlight-mod/wp/discord/components/common/index";
9
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
10
+
import React from "@moonlight-mod/wp/react";
11
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
12
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
13
+
14
+
const wordmark = "https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark.png";
15
+
const wordmarkLight =
16
+
"https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/heads/main/img/wordmark-light.png";
17
+
18
+
function parse(str: string) {
19
+
return MarkupUtils.parse(str, true, {
20
+
allowHeading: true,
21
+
allowLinks: true,
22
+
allowList: true
23
+
});
24
+
}
25
+
26
+
function Dev({ name, picture, link }: { name: string; picture: string; link: string }) {
27
+
return (
28
+
<Button onClick={() => window.open(link)} color={Button.Colors.PRIMARY} className="moonbase-dev">
29
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
30
+
<img src={picture} alt={name} className="moonbase-dev-avatar" />
31
+
32
+
<Text variant="text-md/semibold">{name}</Text>
33
+
</Flex>
34
+
</Button>
35
+
);
36
+
}
37
+
38
+
function IconButton({
39
+
text,
40
+
link,
41
+
icon,
42
+
openInClient
43
+
}: {
44
+
text: string;
45
+
link: string;
46
+
icon: React.FC<any>;
47
+
openInClient?: boolean;
48
+
}) {
49
+
return (
50
+
<Button
51
+
onClick={() => {
52
+
if (openInClient) {
53
+
try {
54
+
const { handleClick } = spacepack.require("discord/utils/MaskedLinkUtils");
55
+
handleClick({ href: link });
56
+
} catch {
57
+
window.open(link);
58
+
}
59
+
} else {
60
+
// Will open externally in the user's browser
61
+
window.open(link);
62
+
}
63
+
}}
64
+
>
65
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
66
+
{React.createElement(icon, {
67
+
size: "sm",
68
+
color: "currentColor"
69
+
})}
70
+
{text}
71
+
</Flex>
72
+
</Button>
73
+
);
74
+
}
75
+
76
+
export default function AboutPage() {
77
+
const darkTheme = useThemeContext()?.theme !== "light";
78
+
79
+
return (
80
+
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.CENTER} className="moonbase-about-page">
81
+
<img src={darkTheme ? wordmarkLight : wordmark} alt="moonlight wordmark" className="moonbase-wordmark" />
82
+
83
+
<Text variant="heading-lg/medium">created by:</Text>
84
+
<div className="moonbase-devs">
85
+
<Dev name="Cynosphere" picture="https://github.com/Cynosphere.png" link="https://github.com/Cynosphere" />
86
+
<Dev name="NotNite" picture="https://github.com/NotNite.png" link="https://github.com/NotNite" />
87
+
<Dev name="adryd" picture="https://github.com/adryd325.png" link="https://github.com/adryd325" />
88
+
<Dev name="redstonekasi" picture="https://github.com/redstonekasi.png" link="https://github.com/redstonekasi" />
89
+
</div>
90
+
91
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER} className="moonbase-gap">
92
+
<IconButton text="View source" icon={AngleBracketsIcon} link="https://github.com/moonlight-mod/moonlight" />
93
+
<IconButton text="Open the docs" icon={BookCheckIcon} link="https://moonlight-mod.github.io/" />
94
+
<IconButton text="Join the server" icon={ClydeIcon} link="https://discord.gg/FdZBTFCP6F" openInClient={true} />
95
+
</Flex>
96
+
97
+
<Flex direction={Flex.Direction.VERTICAL} align={Flex.Align.START}>
98
+
<Text variant="text-sm/normal">
99
+
{parse(`moonlight \`${window.moonlight.version}\` on \`${window.moonlight.branch}\``)}
100
+
</Text>
101
+
102
+
<Text variant="text-sm/normal">
103
+
{parse(
104
+
"moonlight is licensed under the [GNU Lesser General Public License](https://www.gnu.org/licenses/lgpl-3.0.html) (`LGPL-3.0-or-later`)."
105
+
)}
106
+
</Text>
107
+
</Flex>
108
+
</Flex>
109
+
);
110
+
}
+157
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
+157
packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx
···
1
+
import { LogLevel } from "@moonlight-mod/types";
2
+
3
+
const logLevels = Object.values(LogLevel).filter((v) => typeof v === "string") as string[];
4
+
5
+
import React from "@moonlight-mod/wp/react";
6
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
7
+
import {
8
+
FormDivider,
9
+
FormItem,
10
+
FormText,
11
+
FormSwitch,
12
+
TextInput,
13
+
Button,
14
+
SingleSelect,
15
+
Tooltip,
16
+
Clickable
17
+
} from "@moonlight-mod/wp/discord/components/common/index";
18
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
19
+
import { CircleXIcon } from "@moonlight-mod/wp/discord/components/common/index";
20
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
21
+
import FormSwitchClasses from "@moonlight-mod/wp/discord/components/common/FormSwitch.css";
22
+
23
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
24
+
25
+
let GuildSettingsRoleEditClasses: any;
26
+
spacepack
27
+
.lazyLoad(
28
+
"renderArtisanalHack",
29
+
/\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/,
30
+
/webpackId:(\d+),name:"GuildSettings"/
31
+
)
32
+
.then(
33
+
() =>
34
+
(GuildSettingsRoleEditClasses = spacepack.require(
35
+
"discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"
36
+
))
37
+
);
38
+
39
+
function RemoveEntryButton({ onClick }: { onClick: () => void }) {
40
+
return (
41
+
<div className={GuildSettingsRoleEditClasses.removeButtonContainer}>
42
+
<Tooltip text="Remove entry" position="top">
43
+
{(props: any) => (
44
+
<Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}>
45
+
<CircleXIcon width={24} height={24} />
46
+
</Clickable>
47
+
)}
48
+
</Tooltip>
49
+
</div>
50
+
);
51
+
}
52
+
53
+
function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) {
54
+
const items = MoonbaseSettingsStore.getConfigOption(config) ?? [];
55
+
return (
56
+
<Flex
57
+
style={{
58
+
gap: "20px"
59
+
}}
60
+
direction={Flex.Direction.VERTICAL}
61
+
>
62
+
{items.map((val, i) => (
63
+
<div
64
+
key={i}
65
+
style={{
66
+
display: "grid",
67
+
height: "32px",
68
+
gap: "8px",
69
+
gridTemplateColumns: "1fr 32px",
70
+
alignItems: "center"
71
+
}}
72
+
>
73
+
<TextInput
74
+
size={TextInput.Sizes.DEFAULT}
75
+
value={val}
76
+
onChange={(newVal: string) => {
77
+
items[i] = newVal;
78
+
MoonbaseSettingsStore.setConfigOption(config, items);
79
+
}}
80
+
/>
81
+
<RemoveEntryButton
82
+
onClick={() => {
83
+
items.splice(i, 1);
84
+
MoonbaseSettingsStore.setConfigOption(config, items);
85
+
}}
86
+
/>
87
+
</div>
88
+
))}
89
+
90
+
<Button
91
+
look={Button.Looks.FILLED}
92
+
color={Button.Colors.GREEN}
93
+
size={Button.Sizes.SMALL}
94
+
style={{
95
+
marginTop: "10px"
96
+
}}
97
+
onClick={() => {
98
+
items.push("");
99
+
MoonbaseSettingsStore.setConfigOption(config, items);
100
+
}}
101
+
>
102
+
Add new entry
103
+
</Button>
104
+
</Flex>
105
+
);
106
+
}
107
+
108
+
export default function ConfigPage() {
109
+
return (
110
+
<>
111
+
<FormSwitch
112
+
className={Margins.marginTop20}
113
+
value={MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "updateChecking", true) ?? true}
114
+
onChange={(value: boolean) => {
115
+
MoonbaseSettingsStore.setExtensionConfig("moonbase", "updateChecking", value);
116
+
}}
117
+
note="Checks for updates to moonlight"
118
+
>
119
+
Automatic update checking
120
+
</FormSwitch>
121
+
<FormItem title="Repositories">
122
+
<FormText className={Margins.marginBottom4}>A list of remote repositories to display extensions from</FormText>
123
+
<ArrayFormItem config="repositories" />
124
+
</FormItem>
125
+
<FormDivider className={FormSwitchClasses.dividerDefault} />
126
+
<FormItem title="Extension search paths" className={Margins.marginTop20}>
127
+
<FormText className={Margins.marginBottom4}>
128
+
A list of local directories to search for built extensions
129
+
</FormText>
130
+
<ArrayFormItem config="devSearchPaths" />
131
+
</FormItem>
132
+
<FormDivider className={FormSwitchClasses.dividerDefault} />
133
+
<FormSwitch
134
+
className={Margins.marginTop20}
135
+
value={MoonbaseSettingsStore.getConfigOption("patchAll") ?? false}
136
+
onChange={(value: boolean) => {
137
+
MoonbaseSettingsStore.setConfigOption("patchAll", value);
138
+
}}
139
+
note="Wraps every webpack module in a function, separating them in DevTools"
140
+
>
141
+
Patch all
142
+
</FormSwitch>
143
+
<FormItem title="Log level">
144
+
<SingleSelect
145
+
autofocus={false}
146
+
clearable={false}
147
+
value={MoonbaseSettingsStore.getConfigOption("loggerLevel")}
148
+
options={logLevels.map((o) => ({
149
+
value: o.toLowerCase(),
150
+
label: o[0] + o.slice(1).toLowerCase()
151
+
}))}
152
+
onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)}
153
+
/>
154
+
</FormItem>
155
+
</>
156
+
);
157
+
}
+335
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
+335
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
···
1
+
import { ExtensionState } from "../../../types";
2
+
import { constants, ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types";
3
+
4
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
5
+
import {
6
+
ScienceIcon,
7
+
DownloadIcon,
8
+
TrashIcon,
9
+
AngleBracketsIcon,
10
+
Tooltip,
11
+
Card,
12
+
Text,
13
+
FormSwitch,
14
+
TabBar,
15
+
Button,
16
+
ChannelListIcon,
17
+
HeartIcon,
18
+
WindowTopOutlineIcon,
19
+
WarningIcon
20
+
} from "@moonlight-mod/wp/discord/components/common/index";
21
+
import React from "@moonlight-mod/wp/react";
22
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
23
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
24
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
25
+
import AppCardClasses from "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css";
26
+
import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton";
27
+
import DiscoveryClasses from "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css";
28
+
import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css";
29
+
import BuildOverrideClasses from "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css";
30
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
31
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
32
+
import ExtensionInfo from "./info";
33
+
import Settings from "./settings";
34
+
import { doGenericExtensionPopup, doMissingExtensionPopup } from "./popup";
35
+
36
+
export enum ExtensionPage {
37
+
Info,
38
+
Description,
39
+
Changelog,
40
+
Settings
41
+
}
42
+
43
+
const COMPAT_TEXT_MAP: Record<ExtensionCompat, string> = {
44
+
[ExtensionCompat.Compatible]: "huh?",
45
+
[ExtensionCompat.InvalidApiLevel]: "Incompatible API level",
46
+
[ExtensionCompat.InvalidEnvironment]: "Incompatible platform"
47
+
};
48
+
const CONFLICTING_TEXT = "This extension is already installed from another source.";
49
+
50
+
function PanelLinkButton({ icon, tooltip, link }: { icon: React.ReactNode; tooltip: string; link: string }) {
51
+
return (
52
+
<PanelButton
53
+
icon={icon}
54
+
tooltipText={tooltip}
55
+
onClick={() => {
56
+
window.open(link);
57
+
}}
58
+
/>
59
+
);
60
+
}
61
+
62
+
export default function ExtensionCard({ uniqueId, selectTag }: { uniqueId: number; selectTag: (tag: string) => void }) {
63
+
const { ext, enabled, busy, update, conflicting } = useStateFromStores([MoonbaseSettingsStore], () => {
64
+
return {
65
+
ext: MoonbaseSettingsStore.getExtension(uniqueId),
66
+
enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId),
67
+
busy: MoonbaseSettingsStore.busy,
68
+
update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId),
69
+
conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId)
70
+
};
71
+
});
72
+
73
+
const [tab, setTab] = React.useState(
74
+
update != null && ext?.changelog != null ? ExtensionPage.Changelog : ExtensionPage.Info
75
+
);
76
+
77
+
const tagline = ext.manifest?.meta?.tagline;
78
+
const settings = ext.settingsOverride ?? ext.manifest?.settings;
79
+
const description = ext.manifest?.meta?.description;
80
+
const changelog = ext.changelog;
81
+
const linkButtons = [
82
+
ext?.manifest?.meta?.source && (
83
+
<PanelLinkButton icon={<AngleBracketsIcon />} tooltip="View source" link={ext.manifest.meta.source} />
84
+
),
85
+
ext?.source?.url && <PanelLinkButton icon={<ChannelListIcon />} tooltip="View repository" link={ext.source.url} />,
86
+
ext?.manifest?.meta?.donate && (
87
+
<PanelLinkButton icon={<HeartIcon />} tooltip="Donate" link={ext.manifest.meta.donate} />
88
+
)
89
+
].filter((x) => x != null);
90
+
91
+
const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () =>
92
+
Object.keys(MoonbaseSettingsStore.extensions)
93
+
.filter((uniqueId) => {
94
+
const potentialDependant = MoonbaseSettingsStore.getExtension(parseInt(uniqueId));
95
+
96
+
return (
97
+
potentialDependant.manifest.dependencies?.includes(ext?.id) &&
98
+
MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId))
99
+
);
100
+
})
101
+
.map((a) => MoonbaseSettingsStore.getExtension(parseInt(a)))
102
+
);
103
+
const implicitlyEnabled = enabledDependants.length > 0;
104
+
105
+
const hasDuplicateEntry = useStateFromStores([MoonbaseSettingsStore], () =>
106
+
Object.entries(MoonbaseSettingsStore.extensions).some(
107
+
([otherUniqueId, otherExt]) =>
108
+
otherExt != null && otherExt?.id === ext?.id && parseInt(otherUniqueId) !== uniqueId
109
+
)
110
+
);
111
+
112
+
return ext == null ? (
113
+
<></>
114
+
) : (
115
+
<Card editable={true} className={AppCardClasses.card}>
116
+
<div className={AppCardClasses.cardHeader}>
117
+
<Flex direction={Flex.Direction.VERTICAL}>
118
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.CENTER}>
119
+
<Text variant="text-md/semibold">{ext.manifest?.meta?.name ?? ext.id}</Text>
120
+
{ext.source.type === ExtensionLoadSource.Developer && (
121
+
<Tooltip text="This is a local extension" position="top">
122
+
{(props: any) => <ScienceIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
123
+
</Tooltip>
124
+
)}
125
+
126
+
{hasDuplicateEntry && ext?.source?.url && (
127
+
<Tooltip text={`This extension is from the following repository: ${ext.source.url}`} position="top">
128
+
{(props: any) => <WindowTopOutlineIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
129
+
</Tooltip>
130
+
)}
131
+
132
+
{ext.manifest?.meta?.deprecated && (
133
+
<Tooltip text="This extension is deprecated" position="top">
134
+
{(props: any) => <WarningIcon {...props} class={BuildOverrideClasses.infoIcon} size="xs" />}
135
+
</Tooltip>
136
+
)}
137
+
</Flex>
138
+
139
+
{tagline != null && <Text variant="text-sm/normal">{MarkupUtils.parse(tagline)}</Text>}
140
+
</Flex>
141
+
142
+
<Flex direction={Flex.Direction.HORIZONTAL} align={Flex.Align.END} justify={Flex.Justify.END}>
143
+
<div
144
+
// too lazy to learn how <Flex /> works lmao
145
+
style={{
146
+
display: "flex",
147
+
alignItems: "center",
148
+
gap: "1rem"
149
+
}}
150
+
>
151
+
{ext.state === ExtensionState.NotDownloaded ? (
152
+
<Tooltip
153
+
text={conflicting ? CONFLICTING_TEXT : COMPAT_TEXT_MAP[ext.compat]}
154
+
shouldShow={conflicting || ext.compat !== ExtensionCompat.Compatible}
155
+
>
156
+
{(props: any) => (
157
+
<Button
158
+
{...props}
159
+
color={Button.Colors.BRAND}
160
+
submitting={busy}
161
+
disabled={ext.compat !== ExtensionCompat.Compatible || conflicting}
162
+
onClick={async () => {
163
+
await MoonbaseSettingsStore.installExtension(uniqueId);
164
+
const deps = await MoonbaseSettingsStore.getDependencies(uniqueId);
165
+
if (deps != null) {
166
+
await doMissingExtensionPopup(deps);
167
+
}
168
+
169
+
// Don't auto enable dangerous extensions
170
+
if (!ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) {
171
+
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, true);
172
+
}
173
+
}}
174
+
>
175
+
Install
176
+
</Button>
177
+
)}
178
+
</Tooltip>
179
+
) : (
180
+
<>
181
+
{ext.source.type === ExtensionLoadSource.Normal && (
182
+
<PanelButton
183
+
icon={TrashIcon}
184
+
tooltipText="Delete"
185
+
onClick={() => {
186
+
MoonbaseSettingsStore.deleteExtension(uniqueId);
187
+
}}
188
+
/>
189
+
)}
190
+
191
+
{update != null && (
192
+
<PanelButton
193
+
icon={DownloadIcon}
194
+
tooltipText="Update"
195
+
onClick={() => {
196
+
MoonbaseSettingsStore.installExtension(uniqueId);
197
+
}}
198
+
/>
199
+
)}
200
+
201
+
<FormSwitch
202
+
value={ext.compat === ExtensionCompat.Compatible && (enabled || implicitlyEnabled)}
203
+
disabled={implicitlyEnabled || ext.compat !== ExtensionCompat.Compatible}
204
+
hideBorder={true}
205
+
style={{ marginBottom: "0px" }}
206
+
// @ts-expect-error fix type later
207
+
tooltipNote={
208
+
ext.compat !== ExtensionCompat.Compatible ? (
209
+
COMPAT_TEXT_MAP[ext.compat]
210
+
) : implicitlyEnabled ? (
211
+
<div style={{ display: "flex", flexDirection: "column" }}>
212
+
<div>{`This extension is a dependency of the following enabled extension${
213
+
enabledDependants.length > 1 ? "s" : ""
214
+
}:`}</div>
215
+
{enabledDependants.map((dep) => (
216
+
<div>{"โข " + (dep.manifest.meta?.name ?? dep.id)}</div>
217
+
))}
218
+
</div>
219
+
) : undefined
220
+
}
221
+
onChange={() => {
222
+
const toggle = () => {
223
+
MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled);
224
+
};
225
+
226
+
if (enabled && constants.builtinExtensions.includes(ext.id)) {
227
+
doGenericExtensionPopup(
228
+
"Built in extension",
229
+
"This extension is enabled by default. Disabling it might have consequences. Are you sure you want to disable it?",
230
+
uniqueId,
231
+
toggle
232
+
);
233
+
} else if (!enabled && ext.manifest?.meta?.tags?.includes(ExtensionTag.DangerZone)) {
234
+
doGenericExtensionPopup(
235
+
"Dangerous extension",
236
+
"This extension is marked as dangerous. Enabling it might have consequences. Are you sure you want to enable it?",
237
+
uniqueId,
238
+
toggle
239
+
);
240
+
} else {
241
+
toggle();
242
+
}
243
+
}}
244
+
/>
245
+
</>
246
+
)}
247
+
</div>
248
+
</Flex>
249
+
</div>
250
+
251
+
<div>
252
+
{(description != null || changelog != null || settings != null || linkButtons.length > 0) && (
253
+
<Flex>
254
+
<TabBar
255
+
selectedItem={tab}
256
+
type="top"
257
+
onItemSelect={setTab}
258
+
className={DiscoveryClasses.tabBar}
259
+
style={{
260
+
padding: "0 20px"
261
+
}}
262
+
>
263
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Info}>
264
+
Info
265
+
</TabBar.Item>
266
+
267
+
{description != null && (
268
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Description}>
269
+
Description
270
+
</TabBar.Item>
271
+
)}
272
+
273
+
{changelog != null && (
274
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Changelog}>
275
+
Changelog
276
+
</TabBar.Item>
277
+
)}
278
+
279
+
{settings != null && (
280
+
<TabBar.Item className={DiscoveryClasses.tabBarItem} id={ExtensionPage.Settings}>
281
+
Settings
282
+
</TabBar.Item>
283
+
)}
284
+
</TabBar>
285
+
286
+
<Flex
287
+
align={Flex.Align.CENTER}
288
+
justify={Flex.Justify.END}
289
+
direction={Flex.Direction.HORIZONTAL}
290
+
grow={1}
291
+
className="moonbase-link-buttons"
292
+
>
293
+
{linkButtons.length > 0 && linkButtons}
294
+
</Flex>
295
+
</Flex>
296
+
)}
297
+
298
+
<Flex
299
+
justify={Flex.Justify.START}
300
+
wrap={Flex.Wrap.WRAP}
301
+
style={{
302
+
padding: "16px 16px",
303
+
// This looks wonky in the settings tab
304
+
rowGap: tab === ExtensionPage.Info ? "16px" : undefined
305
+
}}
306
+
>
307
+
{tab === ExtensionPage.Info && <ExtensionInfo ext={ext} selectTag={selectTag} />}
308
+
{tab === ExtensionPage.Description && (
309
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}>
310
+
{MarkupUtils.parse(description ?? "*No description*", true, {
311
+
allowHeading: true,
312
+
allowLinks: true,
313
+
allowList: true
314
+
})}
315
+
</Text>
316
+
)}
317
+
{tab === ExtensionPage.Changelog && (
318
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ width: "100%" }}>
319
+
{MarkupUtils.parse(changelog ?? "*No changelog*", true, {
320
+
allowHeading: true,
321
+
allowLinks: true,
322
+
allowList: true
323
+
})}
324
+
</Text>
325
+
)}
326
+
{tab === ExtensionPage.Settings && (
327
+
<ErrorBoundary>
328
+
<Settings ext={ext} />
329
+
</ErrorBoundary>
330
+
)}
331
+
</Flex>
332
+
</div>
333
+
</Card>
334
+
);
335
+
}
+356
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
+356
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
···
1
+
import { tagNames } from "./info";
2
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
3
+
import * as React from "@moonlight-mod/wp/react";
4
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
5
+
import { WindowStore } from "@moonlight-mod/wp/common_stores";
6
+
import {
7
+
Button,
8
+
Text,
9
+
Heading,
10
+
Popout,
11
+
Dialog,
12
+
Menu,
13
+
ChevronSmallDownIcon,
14
+
ChevronSmallUpIcon,
15
+
ArrowsUpDownIcon,
16
+
RetryIcon,
17
+
Tooltip
18
+
} from "@moonlight-mod/wp/discord/components/common/index";
19
+
import { MenuGroup, MenuCheckboxItem, MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu";
20
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
21
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
22
+
import TagItem from "@moonlight-mod/wp/discord/modules/forums/web/Tag";
23
+
24
+
export enum Filter {
25
+
Core = 1 << 0,
26
+
Normal = 1 << 1,
27
+
Developer = 1 << 2,
28
+
Enabled = 1 << 3,
29
+
Disabled = 1 << 4,
30
+
Installed = 1 << 5,
31
+
Repository = 1 << 6,
32
+
Incompatible = 1 << 7,
33
+
Deprecated = 1 << 8
34
+
}
35
+
export const defaultFilter = 127 as Filter;
36
+
37
+
let HeaderClasses: any;
38
+
let ForumsClasses: any;
39
+
let SortMenuClasses: any;
40
+
spacepack
41
+
.lazyLoad('"Missing channel in Channel.openChannelContextMenu"', /e\("(\d+)"\)/g, /webpackId:(\d+?),/)
42
+
.then(() => {
43
+
ForumsClasses = spacepack.require("discord/modules/forums/web/Forums.css");
44
+
HeaderClasses = spacepack.require("discord/modules/forums/web/Header.css");
45
+
SortMenuClasses = spacepack.require("discord/modules/forums/web/SortMenu.css");
46
+
});
47
+
48
+
function toggleTag(selectedTags: Set<string>, setSelectedTags: (tags: Set<string>) => void, tag: string) {
49
+
const newState = new Set(selectedTags);
50
+
if (newState.has(tag)) newState.delete(tag);
51
+
else newState.add(tag);
52
+
setSelectedTags(newState);
53
+
}
54
+
55
+
function FilterButtonPopout({
56
+
filter,
57
+
setFilter,
58
+
closePopout
59
+
}: {
60
+
filter: Filter;
61
+
setFilter: (filter: Filter) => void;
62
+
closePopout: () => void;
63
+
}) {
64
+
const toggleFilter = (set: Filter) => setFilter(filter & set ? filter & ~set : filter | set);
65
+
66
+
return (
67
+
<div className={SortMenuClasses.container}>
68
+
<Menu navId="sort-filter" hideScroller={true} onClose={closePopout}>
69
+
<MenuGroup label="Type">
70
+
<MenuCheckboxItem
71
+
id="t-core"
72
+
label="Core"
73
+
checked={(filter & Filter.Core) === Filter.Core}
74
+
action={() => toggleFilter(Filter.Core)}
75
+
/>
76
+
<MenuCheckboxItem
77
+
id="t-normal"
78
+
label="Normal"
79
+
checked={(filter & Filter.Normal) === Filter.Normal}
80
+
action={() => toggleFilter(Filter.Normal)}
81
+
/>
82
+
<MenuCheckboxItem
83
+
id="t-developer"
84
+
label="Developer"
85
+
checked={(filter & Filter.Developer) === Filter.Developer}
86
+
action={() => toggleFilter(Filter.Developer)}
87
+
/>
88
+
</MenuGroup>
89
+
<MenuGroup label="State">
90
+
<MenuCheckboxItem
91
+
id="s-enabled"
92
+
label="Enabled"
93
+
checked={(filter & Filter.Enabled) === Filter.Enabled}
94
+
action={() => toggleFilter(Filter.Enabled)}
95
+
/>
96
+
<MenuCheckboxItem
97
+
id="s-disabled"
98
+
label="Disabled"
99
+
checked={(filter & Filter.Disabled) === Filter.Disabled}
100
+
action={() => toggleFilter(Filter.Disabled)}
101
+
/>
102
+
</MenuGroup>
103
+
<MenuGroup label="Location">
104
+
<MenuCheckboxItem
105
+
id="l-installed"
106
+
label="Installed"
107
+
checked={(filter & Filter.Installed) === Filter.Installed}
108
+
action={() => toggleFilter(Filter.Installed)}
109
+
/>
110
+
<MenuCheckboxItem
111
+
id="l-repository"
112
+
label="Repository"
113
+
checked={(filter & Filter.Repository) === Filter.Repository}
114
+
action={() => toggleFilter(Filter.Repository)}
115
+
/>
116
+
</MenuGroup>
117
+
<MenuGroup>
118
+
<MenuCheckboxItem
119
+
id="l-incompatible"
120
+
label="Show incompatible"
121
+
checked={(filter & Filter.Incompatible) === Filter.Incompatible}
122
+
action={() => toggleFilter(Filter.Incompatible)}
123
+
/>
124
+
<MenuCheckboxItem
125
+
id="l-deprecated"
126
+
label="Show deprecated"
127
+
checked={(filter & Filter.Deprecated) === Filter.Deprecated}
128
+
action={() => toggleFilter(Filter.Deprecated)}
129
+
/>
130
+
<MenuItem
131
+
id="reset-all"
132
+
className={SortMenuClasses.clearText}
133
+
label="Reset to default"
134
+
action={() => {
135
+
setFilter(defaultFilter);
136
+
closePopout();
137
+
}}
138
+
/>
139
+
</MenuGroup>
140
+
</Menu>
141
+
</div>
142
+
);
143
+
}
144
+
145
+
function TagButtonPopout({ selectedTags, setSelectedTags, setPopoutRef, closePopout }: any) {
146
+
return (
147
+
<Dialog ref={setPopoutRef} className={HeaderClasses.container}>
148
+
<div className={HeaderClasses.header}>
149
+
<div className={HeaderClasses.headerLeft}>
150
+
<Heading color="interactive-normal" variant="text-xs/bold" className={HeaderClasses.headerText}>
151
+
Select tags
152
+
</Heading>
153
+
<div className={HeaderClasses.countContainer}>
154
+
<Text className={HeaderClasses.countText} color="none" variant="text-xs/medium">
155
+
{selectedTags.size}
156
+
</Text>
157
+
</div>
158
+
</div>
159
+
</div>
160
+
<div className={HeaderClasses.tagContainer}>
161
+
{Object.keys(tagNames).map((tag) => (
162
+
<TagItem
163
+
key={tag}
164
+
className={HeaderClasses.tag}
165
+
tag={{ name: tagNames[tag as keyof typeof tagNames], id: tagNames[tag as keyof typeof tagNames] }}
166
+
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
167
+
selected={selectedTags.has(tag)}
168
+
/>
169
+
))}
170
+
</div>
171
+
<div className={HeaderClasses.separator} />
172
+
<Button
173
+
look={Button.Looks.LINK}
174
+
size={Button.Sizes.MIN}
175
+
color={Button.Colors.CUSTOM}
176
+
className={HeaderClasses.clear}
177
+
onClick={() => {
178
+
setSelectedTags(new Set());
179
+
closePopout();
180
+
}}
181
+
>
182
+
<Text variant="text-sm/medium" color="text-link">
183
+
Clear all
184
+
</Text>
185
+
</Button>
186
+
</Dialog>
187
+
);
188
+
}
189
+
190
+
export default function FilterBar({
191
+
filter,
192
+
setFilter,
193
+
selectedTags,
194
+
setSelectedTags
195
+
}: {
196
+
filter: Filter;
197
+
setFilter: (filter: Filter) => void;
198
+
selectedTags: Set<string>;
199
+
setSelectedTags: (tags: Set<string>) => void;
200
+
}) {
201
+
const windowSize = useStateFromStores([WindowStore], () => WindowStore.windowSize());
202
+
203
+
const tagsContainer = React.useRef<HTMLDivElement>(null);
204
+
const tagListInner = React.useRef<HTMLDivElement>(null);
205
+
const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0);
206
+
const [checkingUpdates, setCheckingUpdates] = React.useState(false);
207
+
208
+
React.useLayoutEffect(() => {
209
+
if (tagsContainer.current === null || tagListInner.current === null) return;
210
+
const { left: containerX, top: containerY } = tagsContainer.current.getBoundingClientRect();
211
+
let offset = 0;
212
+
for (const child of tagListInner.current.children) {
213
+
const { right: childX, top: childY, height } = child.getBoundingClientRect();
214
+
if (childY - containerY > height) break;
215
+
const newOffset = childX - containerX;
216
+
if (newOffset > offset) {
217
+
offset = newOffset;
218
+
}
219
+
}
220
+
setTagsButtonOffset(offset);
221
+
}, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]);
222
+
223
+
return (
224
+
<div
225
+
ref={tagsContainer}
226
+
style={{
227
+
paddingTop: "12px"
228
+
}}
229
+
className={`${ForumsClasses.tagsContainer} ${Margins.marginBottom8}`}
230
+
>
231
+
<Tooltip text="Refresh updates" position="top">
232
+
{(props: any) => (
233
+
<Button
234
+
{...props}
235
+
size={Button.Sizes.MIN}
236
+
color={Button.Colors.CUSTOM}
237
+
className={`${ForumsClasses.sortDropdown} moonbase-retry-button`}
238
+
innerClassName={ForumsClasses.sortDropdownInner}
239
+
onClick={() => {
240
+
(async () => {
241
+
try {
242
+
setCheckingUpdates(true);
243
+
await MoonbaseSettingsStore.checkUpdates();
244
+
} finally {
245
+
// artificial delay because the spin is fun
246
+
await new Promise((r) => setTimeout(r, 500));
247
+
setCheckingUpdates(false);
248
+
}
249
+
})();
250
+
}}
251
+
>
252
+
<RetryIcon size={"custom"} width={16} className={checkingUpdates ? "moonbase-speen" : ""} />
253
+
</Button>
254
+
)}
255
+
</Tooltip>
256
+
<Popout
257
+
renderPopout={({ closePopout }: any) => (
258
+
<FilterButtonPopout filter={filter} setFilter={setFilter} closePopout={closePopout} />
259
+
)}
260
+
position="bottom"
261
+
align="left"
262
+
>
263
+
{(props: any, { isShown }: { isShown: boolean }) => (
264
+
<Button
265
+
{...props}
266
+
size={Button.Sizes.MIN}
267
+
color={Button.Colors.CUSTOM}
268
+
className={ForumsClasses.sortDropdown}
269
+
innerClassName={ForumsClasses.sortDropdownInner}
270
+
>
271
+
<ArrowsUpDownIcon size="xs" />
272
+
<Text className={ForumsClasses.sortDropdownText} variant="text-sm/medium" color="interactive-normal">
273
+
Sort & filter
274
+
</Text>
275
+
{isShown ? (
276
+
<ChevronSmallUpIcon size={"custom"} width={20} />
277
+
) : (
278
+
<ChevronSmallDownIcon size={"custom"} width={20} />
279
+
)}
280
+
</Button>
281
+
)}
282
+
</Popout>
283
+
<div className={ForumsClasses.divider} />
284
+
<div className={ForumsClasses.tagList}>
285
+
<div ref={tagListInner} className={ForumsClasses.tagListInner}>
286
+
{Object.keys(tagNames).map((tag) => (
287
+
<TagItem
288
+
key={tag}
289
+
className={ForumsClasses.tag}
290
+
tag={{ name: tagNames[tag as keyof typeof tagNames], id: tag }}
291
+
onClick={() => toggleTag(selectedTags, setSelectedTags, tag)}
292
+
selected={selectedTags.has(tag)}
293
+
/>
294
+
))}
295
+
</div>
296
+
</div>
297
+
<Popout
298
+
renderPopout={({ setPopoutRef, closePopout }: any) => (
299
+
<TagButtonPopout
300
+
selectedTags={selectedTags}
301
+
setSelectedTags={setSelectedTags}
302
+
setPopoutRef={setPopoutRef}
303
+
closePopout={closePopout}
304
+
/>
305
+
)}
306
+
position="bottom"
307
+
align="right"
308
+
>
309
+
{(props: any, { isShown }: { isShown: boolean }) => (
310
+
<Button
311
+
{...props}
312
+
size={Button.Sizes.MIN}
313
+
color={Button.Colors.CUSTOM}
314
+
style={{
315
+
left: tagsButtonOffset
316
+
}}
317
+
// TODO: Use Discord's class name utility
318
+
className={`${ForumsClasses.tagsButton} ${selectedTags.size > 0 ? ForumsClasses.tagsButtonWithCount : ""}`}
319
+
innerClassName={ForumsClasses.tagsButtonInner}
320
+
>
321
+
{selectedTags.size > 0 ? (
322
+
<div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}>
323
+
<Text className={ForumsClasses.countText} color="none" variant="text-xs/medium">
324
+
{selectedTags.size}
325
+
</Text>
326
+
</div>
327
+
) : (
328
+
<>All</>
329
+
)}
330
+
{isShown ? (
331
+
<ChevronSmallUpIcon size={"custom"} width={20} />
332
+
) : (
333
+
<ChevronSmallDownIcon size={"custom"} width={20} />
334
+
)}
335
+
</Button>
336
+
)}
337
+
</Popout>
338
+
<Button
339
+
size={Button.Sizes.MIN}
340
+
color={Button.Colors.CUSTOM}
341
+
className={`${ForumsClasses.tagsButton} ${ForumsClasses.tagsButtonPlaceholder}`}
342
+
innerClassName={ForumsClasses.tagsButtonInner}
343
+
>
344
+
{selectedTags.size > 0 ? (
345
+
<div style={{ boxSizing: "content-box" }} className={ForumsClasses.countContainer}>
346
+
<Text className={ForumsClasses.countText} color="none" variant="text-xs/medium">
347
+
{selectedTags.size}
348
+
</Text>
349
+
</div>
350
+
) : null}
351
+
352
+
<ChevronSmallUpIcon size={"custom"} width={20} />
353
+
</Button>
354
+
</div>
355
+
);
356
+
}
+162
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
+162
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
···
1
+
import { ExtensionLoadSource, ExtensionTag } from "@moonlight-mod/types";
2
+
import { ExtensionState } from "../../../types";
3
+
import FilterBar, { Filter, defaultFilter } from "./filterBar";
4
+
import ExtensionCard from "./card";
5
+
6
+
import React from "@moonlight-mod/wp/react";
7
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
8
+
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
9
+
import {
10
+
FormDivider,
11
+
CircleInformationIcon,
12
+
XSmallIcon,
13
+
Button
14
+
} from "@moonlight-mod/wp/discord/components/common/index";
15
+
import PanelButton from "@moonlight-mod/wp/discord/components/common/PanelButton";
16
+
17
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
18
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
19
+
import { ExtensionCompat } from "@moonlight-mod/core/extension/loader";
20
+
import HelpMessage from "../HelpMessage";
21
+
22
+
const SearchBar = spacepack.require("discord/uikit/search/SearchBar").default;
23
+
24
+
const validTags: string[] = Object.values(ExtensionTag);
25
+
26
+
export default function ExtensionsPage() {
27
+
const { extensions, savedFilter } = useStateFromStoresObject([MoonbaseSettingsStore], () => {
28
+
return {
29
+
extensions: MoonbaseSettingsStore.extensions,
30
+
savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw<number>("moonbase", "filter", defaultFilter)
31
+
};
32
+
});
33
+
34
+
const [query, setQuery] = React.useState("");
35
+
const [hitUpdateAll, setHitUpdateAll] = React.useState(false);
36
+
37
+
const filterState = React.useState(defaultFilter);
38
+
39
+
let filter: Filter, setFilter: (filter: Filter) => void;
40
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "saveFilter", false)) {
41
+
filter = savedFilter ?? defaultFilter;
42
+
setFilter = (filter) => MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter);
43
+
} else {
44
+
filter = filterState[0];
45
+
setFilter = filterState[1];
46
+
}
47
+
48
+
const [selectedTags, setSelectedTags] = React.useState(new Set<string>());
49
+
const selectTag = React.useCallback(
50
+
(tag: string) => {
51
+
const newState = new Set(selectedTags);
52
+
if (validTags.includes(tag)) newState.add(tag);
53
+
setSelectedTags(newState);
54
+
},
55
+
[selectedTags]
56
+
);
57
+
58
+
const sorted = Object.values(extensions).sort((a, b) => {
59
+
const aName = a.manifest.meta?.name ?? a.id;
60
+
const bName = b.manifest.meta?.name ?? b.id;
61
+
return aName.localeCompare(bName);
62
+
});
63
+
64
+
const filtered = sorted.filter(
65
+
(ext) =>
66
+
(query === "" ||
67
+
ext.manifest.id?.toLowerCase().includes(query) ||
68
+
ext.manifest.meta?.name?.toLowerCase().includes(query) ||
69
+
ext.manifest.meta?.tagline?.toLowerCase().includes(query) ||
70
+
(ext.manifest?.settings != null &&
71
+
Object.entries(ext.manifest.settings).some(([key, setting]) =>
72
+
(setting.displayName ?? key).toLowerCase().includes(query)
73
+
)) ||
74
+
(ext.manifest?.meta?.authors != null &&
75
+
ext.manifest.meta.authors.some((author) =>
76
+
(typeof author === "string" ? author : author.name).toLowerCase().includes(query)
77
+
)) ||
78
+
ext.manifest.meta?.description?.toLowerCase().includes(query)) &&
79
+
[...selectedTags.values()].every((tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag)) &&
80
+
// This seems very bad, sorry
81
+
!(
82
+
(!(filter & Filter.Core) && ext.source.type === ExtensionLoadSource.Core) ||
83
+
(!(filter & Filter.Normal) && ext.source.type === ExtensionLoadSource.Normal) ||
84
+
(!(filter & Filter.Developer) && ext.source.type === ExtensionLoadSource.Developer) ||
85
+
(!(filter & Filter.Enabled) && MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
86
+
(!(filter & Filter.Disabled) && !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) ||
87
+
(!(filter & Filter.Installed) && ext.state !== ExtensionState.NotDownloaded) ||
88
+
(!(filter & Filter.Repository) && ext.state === ExtensionState.NotDownloaded)
89
+
) &&
90
+
(filter & Filter.Incompatible ||
91
+
ext.compat === ExtensionCompat.Compatible ||
92
+
(ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) &&
93
+
(filter & Filter.Deprecated ||
94
+
ext.manifest?.meta?.deprecated !== true ||
95
+
ext.state !== ExtensionState.NotDownloaded)
96
+
);
97
+
98
+
// Prioritize extensions with updates
99
+
const filteredWithUpdates = filtered.filter((ext) => ext!.hasUpdate);
100
+
const filteredWithoutUpdates = filtered.filter((ext) => !ext!.hasUpdate);
101
+
102
+
return (
103
+
<>
104
+
<SearchBar
105
+
size={SearchBar.Sizes.MEDIUM}
106
+
query={query}
107
+
onChange={(v: string) => setQuery(v.toLowerCase())}
108
+
onClear={() => setQuery("")}
109
+
autoFocus={true}
110
+
autoComplete="off"
111
+
inputProps={{
112
+
autoCapitalize: "none",
113
+
autoCorrect: "off",
114
+
spellCheck: "false"
115
+
}}
116
+
/>
117
+
<FilterBar filter={filter} setFilter={setFilter} selectedTags={selectedTags} setSelectedTags={setSelectedTags} />
118
+
119
+
{filteredWithUpdates.length > 0 && (
120
+
<HelpMessage
121
+
icon={CircleInformationIcon}
122
+
text="Extension updates are available"
123
+
className="moonbase-extension-update-section"
124
+
>
125
+
<div className="moonbase-help-message-buttons">
126
+
<Button
127
+
color={Button.Colors.BRAND}
128
+
size={Button.Sizes.TINY}
129
+
disabled={hitUpdateAll}
130
+
onClick={() => {
131
+
setHitUpdateAll(true);
132
+
MoonbaseSettingsStore.updateAllExtensions();
133
+
}}
134
+
>
135
+
Update all
136
+
</Button>
137
+
<PanelButton
138
+
icon={XSmallIcon}
139
+
onClick={() => {
140
+
MoonbaseSettingsStore.dismissAllExtensionUpdates();
141
+
}}
142
+
/>
143
+
</div>
144
+
</HelpMessage>
145
+
)}
146
+
147
+
{filteredWithUpdates.map((ext) => (
148
+
<ErrorBoundary>
149
+
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} />
150
+
</ErrorBoundary>
151
+
))}
152
+
{filteredWithUpdates.length > 0 && filteredWithoutUpdates.length > 0 && (
153
+
<FormDivider className="moonbase-update-divider" />
154
+
)}
155
+
{filteredWithoutUpdates.map((ext) => (
156
+
<ErrorBoundary>
157
+
<ExtensionCard uniqueId={ext.uniqueId} key={ext.uniqueId} selectTag={selectTag} />
158
+
</ErrorBoundary>
159
+
))}
160
+
</>
161
+
);
162
+
}
+205
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
+205
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx
···
1
+
import { ExtensionTag } from "@moonlight-mod/types";
2
+
import { MoonbaseExtension } from "../../../types";
3
+
4
+
import React from "@moonlight-mod/wp/react";
5
+
import { Text } from "@moonlight-mod/wp/discord/components/common/index";
6
+
7
+
type Dependency = {
8
+
id: string;
9
+
type: DependencyType;
10
+
};
11
+
12
+
enum DependencyType {
13
+
Dependency = "dependency",
14
+
Optional = "optional",
15
+
Incompatible = "incompatible"
16
+
}
17
+
18
+
export const tagNames: Record<ExtensionTag, string> = {
19
+
[ExtensionTag.Accessibility]: "Accessibility",
20
+
[ExtensionTag.Appearance]: "Appearance",
21
+
[ExtensionTag.Chat]: "Chat",
22
+
[ExtensionTag.Commands]: "Commands",
23
+
[ExtensionTag.ContextMenu]: "Context Menu",
24
+
[ExtensionTag.DangerZone]: "Danger Zone",
25
+
[ExtensionTag.Development]: "Development",
26
+
[ExtensionTag.Fixes]: "Fixes",
27
+
[ExtensionTag.Fun]: "Fun",
28
+
[ExtensionTag.Markdown]: "Markdown",
29
+
[ExtensionTag.Voice]: "Voice",
30
+
[ExtensionTag.Privacy]: "Privacy",
31
+
[ExtensionTag.Profiles]: "Profiles",
32
+
[ExtensionTag.QualityOfLife]: "Quality of Life",
33
+
[ExtensionTag.Library]: "Library"
34
+
};
35
+
36
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
37
+
38
+
function InfoSection({ title, children }: { title: string; children: React.ReactNode }) {
39
+
return (
40
+
<div
41
+
style={{
42
+
marginRight: "1em"
43
+
}}
44
+
>
45
+
<Text variant="eyebrow" className="moonlight-card-info-header">
46
+
{title}
47
+
</Text>
48
+
49
+
<Text variant="text-sm/normal">{children}</Text>
50
+
</div>
51
+
);
52
+
}
53
+
54
+
function Badge({
55
+
color,
56
+
children,
57
+
style = {},
58
+
onClick
59
+
}: {
60
+
color: string;
61
+
children: React.ReactNode;
62
+
style?: React.CSSProperties;
63
+
onClick?: () => void;
64
+
}) {
65
+
if (onClick) style.cursor ??= "pointer";
66
+
return (
67
+
<span
68
+
className="moonlight-card-badge"
69
+
style={
70
+
{
71
+
"--badge-color": color,
72
+
...style
73
+
} as React.CSSProperties
74
+
}
75
+
onClick={onClick}
76
+
>
77
+
{children}
78
+
</span>
79
+
);
80
+
}
81
+
82
+
export default function ExtensionInfo({
83
+
ext,
84
+
selectTag
85
+
}: {
86
+
ext: MoonbaseExtension;
87
+
selectTag: (tag: string) => void;
88
+
}) {
89
+
const authors = ext.manifest?.meta?.authors;
90
+
const tags = ext.manifest?.meta?.tags;
91
+
const version = ext.manifest?.version;
92
+
93
+
const dependencies: Dependency[] = [];
94
+
const incompatible: Dependency[] = [];
95
+
96
+
if (ext.manifest.dependencies != null) {
97
+
dependencies.push(
98
+
...ext.manifest.dependencies.map((dep) => ({
99
+
id: dep,
100
+
type: DependencyType.Dependency
101
+
}))
102
+
);
103
+
}
104
+
105
+
if (ext.manifest.suggested != null) {
106
+
dependencies.push(
107
+
...ext.manifest.suggested.map((dep) => ({
108
+
id: dep,
109
+
type: DependencyType.Optional
110
+
}))
111
+
);
112
+
}
113
+
114
+
if (ext.manifest.incompatible != null) {
115
+
incompatible.push(
116
+
...ext.manifest.incompatible.map((dep) => ({
117
+
id: dep,
118
+
type: DependencyType.Incompatible
119
+
}))
120
+
);
121
+
}
122
+
123
+
return (
124
+
<>
125
+
{authors != null && (
126
+
<InfoSection title="Authors">
127
+
{authors.map((author, i) => {
128
+
const comma = i !== authors.length - 1 ? ", " : "";
129
+
if (typeof author === "string") {
130
+
return (
131
+
<span key={i}>
132
+
{author}
133
+
{comma}
134
+
</span>
135
+
);
136
+
} else {
137
+
// TODO: resolve IDs
138
+
return (
139
+
<span key={i}>
140
+
{author.name}
141
+
{comma}
142
+
</span>
143
+
);
144
+
}
145
+
})}
146
+
</InfoSection>
147
+
)}
148
+
149
+
{tags != null && (
150
+
<InfoSection title="Tags">
151
+
{tags.map((tag, i) => {
152
+
const name = tagNames[tag];
153
+
let color = "var(--bg-mod-strong)";
154
+
let style;
155
+
if (tag === ExtensionTag.DangerZone) {
156
+
color = "var(--red-460)";
157
+
style = { color: "var(--primary-230)" };
158
+
}
159
+
160
+
return (
161
+
<Badge key={i} color={color} style={style} onClick={() => selectTag(tag)}>
162
+
{name}
163
+
</Badge>
164
+
);
165
+
})}
166
+
</InfoSection>
167
+
)}
168
+
169
+
{dependencies.length > 0 && (
170
+
<InfoSection title="Dependencies">
171
+
{dependencies.map((dep) => {
172
+
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
173
+
174
+
// TODO: figure out a decent way to distinguish suggested
175
+
return (
176
+
<Badge color="var(--bg-mod-strong)" key={dep.id}>
177
+
{name}
178
+
</Badge>
179
+
);
180
+
})}
181
+
</InfoSection>
182
+
)}
183
+
184
+
{incompatible.length > 0 && (
185
+
<InfoSection title="Incompatible">
186
+
{incompatible.map((dep) => {
187
+
const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id);
188
+
189
+
return (
190
+
<Badge color="var(--bg-mod-strong)" key={dep.id}>
191
+
{name}
192
+
</Badge>
193
+
);
194
+
})}
195
+
</InfoSection>
196
+
)}
197
+
198
+
{version != null && (
199
+
<InfoSection title="Version">
200
+
<span>{version}</span>
201
+
</InfoSection>
202
+
)}
203
+
</>
204
+
);
205
+
}
+211
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
+211
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx
···
1
+
// TODO: clean up the styling here
2
+
import React from "@moonlight-mod/wp/react";
3
+
import { MoonbaseExtension } from "core-extensions/src/moonbase/types";
4
+
import { openModalLazy, useModalsStore, closeModal } from "@moonlight-mod/wp/discord/modules/modals/Modals";
5
+
import { SingleSelect, Text } from "@moonlight-mod/wp/discord/components/common/index";
6
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
7
+
import { ExtensionLoadSource } from "@moonlight-mod/types";
8
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
9
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
10
+
11
+
let ConfirmModal: typeof import("@moonlight-mod/wp/discord/components/modals/ConfirmModal").default;
12
+
13
+
function close() {
14
+
const ModalStore = useModalsStore.getState();
15
+
closeModal(ModalStore.default[0].key);
16
+
}
17
+
18
+
// do this to avoid a hard dependency
19
+
function lazyLoad() {
20
+
if (!ConfirmModal) {
21
+
ConfirmModal = spacepack.require("discord/components/modals/ConfirmModal").default;
22
+
}
23
+
}
24
+
25
+
const presentableLoadSources: Record<ExtensionLoadSource, string> = {
26
+
[ExtensionLoadSource.Developer]: "Local extension", // should never show up
27
+
[ExtensionLoadSource.Core]: "Core extension",
28
+
[ExtensionLoadSource.Normal]: "Extension repository"
29
+
};
30
+
31
+
function ExtensionSelect({
32
+
id,
33
+
candidates,
34
+
option,
35
+
setOption
36
+
}: {
37
+
id: string;
38
+
candidates: MoonbaseExtension[];
39
+
option: string | undefined;
40
+
setOption: (pick: string | undefined) => void;
41
+
}) {
42
+
return (
43
+
<SingleSelect
44
+
key={id}
45
+
autofocus={false}
46
+
value={option}
47
+
options={candidates.map((candidate) => {
48
+
return {
49
+
value: candidate.uniqueId.toString(),
50
+
label:
51
+
candidate.source.url ?? presentableLoadSources[candidate.source.type] ?? candidate.manifest.version ?? ""
52
+
};
53
+
})}
54
+
onChange={(value: string) => {
55
+
setOption(value);
56
+
}}
57
+
placeholder="Missing extension"
58
+
/>
59
+
);
60
+
}
61
+
62
+
function MissingExtensionPopup({
63
+
deps,
64
+
transitionState
65
+
}: {
66
+
deps: Record<string, MoonbaseExtension[]>;
67
+
transitionState: number | null;
68
+
}) {
69
+
lazyLoad();
70
+
const amountNotAvailable = Object.values(deps).filter((candidates) => candidates.length === 0).length;
71
+
72
+
const [options, setOptions] = React.useState<Record<string, string | undefined>>(
73
+
Object.fromEntries(
74
+
Object.entries(deps).map(([id, candidates]) => [
75
+
id,
76
+
candidates.length > 0 ? candidates[0].uniqueId.toString() : undefined
77
+
])
78
+
)
79
+
);
80
+
81
+
return (
82
+
<ConfirmModal
83
+
body={
84
+
<Flex
85
+
style={{
86
+
gap: "20px"
87
+
}}
88
+
direction={Flex.Direction.VERTICAL}
89
+
>
90
+
<Text variant="text-md/normal">
91
+
This extension depends on other extensions which are not downloaded. Choose which extensions to download.
92
+
</Text>
93
+
94
+
{amountNotAvailable > 0 && (
95
+
<Text variant="text-md/normal">
96
+
{amountNotAvailable} extension
97
+
{amountNotAvailable > 1 ? "s" : ""} could not be found, and must be installed manually.
98
+
</Text>
99
+
)}
100
+
101
+
<div
102
+
style={{
103
+
display: "grid",
104
+
gridTemplateColumns: "1fr 2fr",
105
+
gap: "10px"
106
+
}}
107
+
>
108
+
{Object.entries(deps).map(([id, candidates], i) => (
109
+
<>
110
+
<Text
111
+
variant="text-md/normal"
112
+
style={{
113
+
alignSelf: "center",
114
+
wordBreak: "break-word"
115
+
}}
116
+
>
117
+
{MoonbaseSettingsStore.tryGetExtensionName(id)}
118
+
</Text>
119
+
120
+
<ExtensionSelect
121
+
id={id}
122
+
candidates={candidates}
123
+
option={options[id]}
124
+
setOption={(pick) =>
125
+
setOptions((prev) => ({
126
+
...prev,
127
+
[id]: pick
128
+
}))
129
+
}
130
+
/>
131
+
</>
132
+
))}
133
+
</div>
134
+
</Flex>
135
+
}
136
+
cancelText="Cancel"
137
+
confirmText="Install"
138
+
onCancel={close}
139
+
onConfirm={() => {
140
+
close();
141
+
142
+
for (const pick of Object.values(options)) {
143
+
if (pick != null) {
144
+
MoonbaseSettingsStore.installExtension(parseInt(pick));
145
+
}
146
+
}
147
+
}}
148
+
title="Extension dependencies"
149
+
transitionState={transitionState}
150
+
/>
151
+
);
152
+
}
153
+
154
+
export async function doMissingExtensionPopup(deps: Record<string, MoonbaseExtension[]>) {
155
+
await openModalLazy(async () => {
156
+
return ({ transitionState }: { transitionState: number | null }) => {
157
+
return <MissingExtensionPopup transitionState={transitionState} deps={deps} />;
158
+
};
159
+
});
160
+
}
161
+
162
+
function GenericExtensionPopup({
163
+
title,
164
+
content,
165
+
transitionState,
166
+
uniqueId,
167
+
cb
168
+
}: {
169
+
title: string;
170
+
content: string;
171
+
transitionState: number | null;
172
+
uniqueId: number;
173
+
cb: () => void;
174
+
}) {
175
+
lazyLoad();
176
+
177
+
return (
178
+
<ConfirmModal
179
+
title={title}
180
+
body={
181
+
<Flex>
182
+
<Text variant="text-md/normal">{content}</Text>
183
+
</Flex>
184
+
}
185
+
confirmText="Yes"
186
+
cancelText="No"
187
+
onCancel={close}
188
+
onConfirm={() => {
189
+
close();
190
+
cb();
191
+
}}
192
+
transitionState={transitionState}
193
+
/>
194
+
);
195
+
}
196
+
197
+
export async function doGenericExtensionPopup(title: string, content: string, uniqueId: number, cb: () => void) {
198
+
await openModalLazy(async () => {
199
+
return ({ transitionState }: { transitionState: number | null }) => {
200
+
return (
201
+
<GenericExtensionPopup
202
+
title={title}
203
+
content={content}
204
+
transitionState={transitionState}
205
+
uniqueId={uniqueId}
206
+
cb={cb}
207
+
/>
208
+
);
209
+
};
210
+
});
211
+
}
+418
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
+418
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
···
1
+
import {
2
+
ExtensionSettingType,
3
+
ExtensionSettingsManifest,
4
+
MultiSelectSettingType,
5
+
NumberSettingType,
6
+
SelectOption,
7
+
SelectSettingType
8
+
} from "@moonlight-mod/types/config";
9
+
10
+
import { ExtensionState, MoonbaseExtension } from "../../../types";
11
+
12
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
13
+
import React from "@moonlight-mod/wp/react";
14
+
import {
15
+
FormSwitch,
16
+
FormItem,
17
+
FormText,
18
+
TextInput,
19
+
Slider,
20
+
TextArea,
21
+
Tooltip,
22
+
Clickable,
23
+
CircleXIcon,
24
+
Text,
25
+
SingleSelect,
26
+
Button,
27
+
useVariableSelect,
28
+
multiSelect,
29
+
Select as DiscordSelect,
30
+
NumberInputStepper
31
+
} from "@moonlight-mod/wp/discord/components/common/index";
32
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
33
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
34
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
35
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
36
+
import ErrorBoundary from "@moonlight-mod/wp/common_ErrorBoundary";
37
+
38
+
let GuildSettingsRoleEditClasses: any;
39
+
spacepack
40
+
.lazyLoad(
41
+
"renderArtisanalHack",
42
+
/\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"GuildSettings"/,
43
+
/webpackId:(\d+),name:"GuildSettings"/
44
+
)
45
+
.then(
46
+
() =>
47
+
(GuildSettingsRoleEditClasses = spacepack.require(
48
+
"discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"
49
+
))
50
+
);
51
+
52
+
type SettingsProps = {
53
+
ext: MoonbaseExtension;
54
+
name: string;
55
+
setting: ExtensionSettingsManifest;
56
+
disabled: boolean;
57
+
};
58
+
type SettingsComponent = React.ComponentType<SettingsProps>;
59
+
60
+
const Margins = spacepack.require("discord/styles/shared/Margins.css");
61
+
62
+
function markdownify(str: string) {
63
+
return MarkupUtils.parse(str, true, {
64
+
hideSimpleEmbedContent: true,
65
+
allowLinks: true
66
+
});
67
+
}
68
+
69
+
function useConfigEntry<T>(uniqueId: number, name: string) {
70
+
return useStateFromStores(
71
+
[MoonbaseSettingsStore],
72
+
() => {
73
+
return {
74
+
value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name),
75
+
displayName: MoonbaseSettingsStore.getExtensionConfigName(uniqueId, name),
76
+
description: MoonbaseSettingsStore.getExtensionConfigDescription(uniqueId, name)
77
+
};
78
+
},
79
+
[uniqueId, name]
80
+
);
81
+
}
82
+
83
+
function Boolean({ ext, name, setting, disabled }: SettingsProps) {
84
+
const { value, displayName, description } = useConfigEntry<boolean>(ext.uniqueId, name);
85
+
86
+
return (
87
+
<FormSwitch
88
+
value={value ?? false}
89
+
hideBorder={true}
90
+
disabled={disabled}
91
+
onChange={(value: boolean) => {
92
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
93
+
}}
94
+
note={description != null ? markdownify(description) : undefined}
95
+
className={`${Margins.marginReset} ${Margins.marginTop20}`}
96
+
>
97
+
{displayName}
98
+
</FormSwitch>
99
+
);
100
+
}
101
+
102
+
function Number({ ext, name, setting, disabled }: SettingsProps) {
103
+
const { value, displayName, description } = useConfigEntry<number>(ext.uniqueId, name);
104
+
105
+
const castedSetting = setting as NumberSettingType;
106
+
const min = castedSetting.min;
107
+
const max = castedSetting.max;
108
+
109
+
const onChange = (value: number) => {
110
+
const rounded = min == null || max == null ? Math.round(value) : Math.max(min, Math.min(max, Math.round(value)));
111
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded);
112
+
};
113
+
114
+
return (
115
+
<FormItem className={Margins.marginTop20} title={displayName}>
116
+
{min == null || max == null ? (
117
+
<Flex justify={Flex.Justify.BETWEEN} direction={Flex.Direction.HORIZONTAL}>
118
+
{description && <FormText>{markdownify(description)}</FormText>}
119
+
<NumberInputStepper value={value ?? 0} onChange={onChange} />
120
+
</Flex>
121
+
) : (
122
+
<>
123
+
{description && <FormText>{markdownify(description)}</FormText>}
124
+
<Slider
125
+
initialValue={value ?? 0}
126
+
disabled={disabled}
127
+
minValue={min}
128
+
maxValue={max}
129
+
onValueChange={onChange}
130
+
onValueRender={(value: number) => `${Math.round(value)}`}
131
+
/>
132
+
</>
133
+
)}
134
+
</FormItem>
135
+
);
136
+
}
137
+
138
+
function String({ ext, name, setting, disabled }: SettingsProps) {
139
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
140
+
141
+
return (
142
+
<FormItem className={Margins.marginTop20} title={displayName}>
143
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
144
+
<TextInput
145
+
value={value ?? ""}
146
+
disabled={disabled}
147
+
onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)}
148
+
/>
149
+
</FormItem>
150
+
);
151
+
}
152
+
153
+
function MultilineString({ ext, name, setting, disabled }: SettingsProps) {
154
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
155
+
156
+
return (
157
+
<FormItem className={Margins.marginTop20} title={displayName}>
158
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
159
+
<TextArea
160
+
rows={5}
161
+
value={value ?? ""}
162
+
disabled={disabled}
163
+
className={"moonbase-resizeable"}
164
+
onChange={(value: string) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)}
165
+
/>
166
+
</FormItem>
167
+
);
168
+
}
169
+
170
+
function Select({ ext, name, setting, disabled }: SettingsProps) {
171
+
const { value, displayName, description } = useConfigEntry<string>(ext.uniqueId, name);
172
+
173
+
const castedSetting = setting as SelectSettingType;
174
+
const options = castedSetting.options;
175
+
176
+
return (
177
+
<FormItem className={Margins.marginTop20} title={displayName}>
178
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
179
+
<SingleSelect
180
+
autofocus={false}
181
+
clearable={false}
182
+
value={value ?? ""}
183
+
options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))}
184
+
onChange={(value: string) => {
185
+
if (disabled) return;
186
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value);
187
+
}}
188
+
/>
189
+
</FormItem>
190
+
);
191
+
}
192
+
193
+
function MultiSelect({ ext, name, setting, disabled }: SettingsProps) {
194
+
const { value, displayName, description } = useConfigEntry<string | string[]>(ext.uniqueId, name);
195
+
196
+
const castedSetting = setting as MultiSelectSettingType;
197
+
const options = castedSetting.options;
198
+
199
+
return (
200
+
<FormItem className={Margins.marginTop20} title={displayName}>
201
+
{description && <FormText className={Margins.marginBottom8}>{markdownify(description)}</FormText>}
202
+
<DiscordSelect
203
+
autofocus={false}
204
+
clearable={false}
205
+
closeOnSelect={false}
206
+
options={options.map((o: SelectOption) => (typeof o === "string" ? { value: o, label: o } : o))}
207
+
{...useVariableSelect({
208
+
onSelectInteraction: multiSelect,
209
+
value: value == null ? new Set() : new Set(Array.isArray(value) ? value : [value]),
210
+
onChange: (value: string) => {
211
+
if (disabled) return;
212
+
MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Array.from(value));
213
+
}
214
+
})}
215
+
/>
216
+
</FormItem>
217
+
);
218
+
}
219
+
220
+
function RemoveEntryButton({ onClick, disabled }: { onClick: () => void; disabled: boolean }) {
221
+
return (
222
+
<div className={GuildSettingsRoleEditClasses.removeButtonContainer}>
223
+
<Tooltip text="Remove entry" position="top">
224
+
{(props: any) => (
225
+
<Clickable {...props} className={GuildSettingsRoleEditClasses.removeButton} onClick={onClick}>
226
+
<CircleXIcon width={16} height={16} />
227
+
</Clickable>
228
+
)}
229
+
</Tooltip>
230
+
</div>
231
+
);
232
+
}
233
+
234
+
function List({ ext, name, setting, disabled }: SettingsProps) {
235
+
const { value, displayName, description } = useConfigEntry<string[]>(ext.uniqueId, name);
236
+
237
+
const entries = value ?? [];
238
+
const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries);
239
+
240
+
return (
241
+
<FormItem className={Margins.marginTop20} title={displayName}>
242
+
{description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>}
243
+
<Flex direction={Flex.Direction.VERTICAL}>
244
+
{entries.map((val, i) => (
245
+
// FIXME: stylesheets
246
+
<div
247
+
key={i}
248
+
style={{
249
+
display: "grid",
250
+
height: "32px",
251
+
gap: "8px",
252
+
gridTemplateColumns: "1fr 32px",
253
+
alignItems: "center"
254
+
}}
255
+
>
256
+
<TextInput
257
+
size={TextInput.Sizes.MINI}
258
+
value={val}
259
+
disabled={disabled}
260
+
onChange={(newVal: string) => {
261
+
entries[i] = newVal;
262
+
updateConfig();
263
+
}}
264
+
/>
265
+
<RemoveEntryButton
266
+
disabled={disabled}
267
+
onClick={() => {
268
+
entries.splice(i, 1);
269
+
updateConfig();
270
+
}}
271
+
/>
272
+
</div>
273
+
))}
274
+
275
+
<Button
276
+
look={Button.Looks.FILLED}
277
+
color={Button.Colors.GREEN}
278
+
size={Button.Sizes.SMALL}
279
+
disabled={disabled}
280
+
className={Margins.marginTop8}
281
+
onClick={() => {
282
+
entries.push("");
283
+
updateConfig();
284
+
}}
285
+
>
286
+
Add new entry
287
+
</Button>
288
+
</Flex>
289
+
</FormItem>
290
+
);
291
+
}
292
+
293
+
function Dictionary({ ext, name, setting, disabled }: SettingsProps) {
294
+
const { value, displayName, description } = useConfigEntry<Record<string, string>>(ext.uniqueId, name);
295
+
296
+
const entries = Object.entries(value ?? {});
297
+
const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, Object.fromEntries(entries));
298
+
299
+
return (
300
+
<FormItem className={Margins.marginTop20} title={displayName}>
301
+
{description && <FormText className={Margins.marginBottom4}>{markdownify(description)}</FormText>}
302
+
<Flex direction={Flex.Direction.VERTICAL}>
303
+
{entries.map(([key, val], i) => (
304
+
// FIXME: stylesheets
305
+
<div
306
+
key={i}
307
+
style={{
308
+
display: "grid",
309
+
height: "32px",
310
+
gap: "8px",
311
+
gridTemplateColumns: "1fr 1fr 32px",
312
+
alignItems: "center"
313
+
}}
314
+
>
315
+
<TextInput
316
+
size={TextInput.Sizes.MINI}
317
+
value={key}
318
+
disabled={disabled}
319
+
onChange={(newKey: string) => {
320
+
entries[i][0] = newKey;
321
+
updateConfig();
322
+
}}
323
+
/>
324
+
<TextInput
325
+
size={TextInput.Sizes.MINI}
326
+
value={val}
327
+
disabled={disabled}
328
+
onChange={(newValue: string) => {
329
+
entries[i][1] = newValue;
330
+
updateConfig();
331
+
}}
332
+
/>
333
+
<RemoveEntryButton
334
+
disabled={disabled}
335
+
onClick={() => {
336
+
entries.splice(i, 1);
337
+
updateConfig();
338
+
}}
339
+
/>
340
+
</div>
341
+
))}
342
+
343
+
<Button
344
+
look={Button.Looks.FILLED}
345
+
color={Button.Colors.GREEN}
346
+
size={Button.Sizes.SMALL}
347
+
className={Margins.marginTop8}
348
+
disabled={disabled}
349
+
onClick={() => {
350
+
entries.push([`entry-${entries.length}`, ""]);
351
+
updateConfig();
352
+
}}
353
+
>
354
+
Add new entry
355
+
</Button>
356
+
</Flex>
357
+
</FormItem>
358
+
);
359
+
}
360
+
361
+
function Custom({ ext, name, setting, disabled }: SettingsProps) {
362
+
const { value, displayName } = useConfigEntry<any>(ext.uniqueId, name);
363
+
364
+
const { component: Component } = useStateFromStores(
365
+
[MoonbaseSettingsStore],
366
+
() => {
367
+
return {
368
+
component: MoonbaseSettingsStore.getExtensionConfigComponent(ext.id, name)
369
+
};
370
+
},
371
+
[ext.uniqueId, name]
372
+
);
373
+
374
+
if (Component == null) {
375
+
return (
376
+
<Text variant="text-md/normal">{`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`}</Text>
377
+
);
378
+
}
379
+
380
+
return (
381
+
<ErrorBoundary>
382
+
<Component value={value} setValue={(value) => MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value)} />
383
+
</ErrorBoundary>
384
+
);
385
+
}
386
+
387
+
function Setting({ ext, name, setting, disabled }: SettingsProps) {
388
+
const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = {
389
+
[ExtensionSettingType.Boolean]: Boolean,
390
+
[ExtensionSettingType.Number]: Number,
391
+
[ExtensionSettingType.String]: String,
392
+
[ExtensionSettingType.MultilineString]: MultilineString,
393
+
[ExtensionSettingType.Select]: Select,
394
+
[ExtensionSettingType.MultiSelect]: MultiSelect,
395
+
[ExtensionSettingType.List]: List,
396
+
[ExtensionSettingType.Dictionary]: Dictionary,
397
+
[ExtensionSettingType.Custom]: Custom
398
+
};
399
+
const element = elements[setting.type];
400
+
if (element == null) return <></>;
401
+
return React.createElement(element, { ext, name, setting, disabled });
402
+
}
403
+
404
+
export default function Settings({ ext }: { ext: MoonbaseExtension }) {
405
+
return (
406
+
<Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}>
407
+
{Object.entries(ext.settingsOverride ?? ext.manifest.settings!).map(([name, setting]) => (
408
+
<Setting
409
+
ext={ext}
410
+
key={name}
411
+
name={name}
412
+
setting={setting}
413
+
disabled={ext.state === ExtensionState.NotDownloaded}
414
+
/>
415
+
))}
416
+
</Flex>
417
+
);
418
+
}
+85
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
+85
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import { Text, TabBar } from "@moonlight-mod/wp/discord/components/common/index";
3
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
4
+
import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores";
5
+
6
+
import ExtensionsPage from "./extensions";
7
+
import ConfigPage from "./config";
8
+
import AboutPage from "./about";
9
+
import Update from "./update";
10
+
import RestartAdviceMessage from "./RestartAdvice";
11
+
import { Divider } from "@moonlight-mod/wp/discord/components/common/BaseHeaderBar";
12
+
import HeaderBarClasses from "@moonlight-mod/wp/discord/components/common/HeaderBar.css";
13
+
import PeoplePageClasses from "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css";
14
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
15
+
import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css";
16
+
17
+
export const pages: {
18
+
id: string;
19
+
name: string;
20
+
element: React.FunctionComponent;
21
+
}[] = [
22
+
{
23
+
id: "extensions",
24
+
name: "Extensions",
25
+
element: ExtensionsPage
26
+
},
27
+
{
28
+
id: "config",
29
+
name: "Config",
30
+
element: ConfigPage
31
+
},
32
+
{
33
+
id: "about",
34
+
name: "About",
35
+
element: AboutPage
36
+
}
37
+
];
38
+
39
+
export function Moonbase(props: { initialTab?: number } = {}) {
40
+
const subsection = useStateFromStores([UserSettingsModalStore], () => UserSettingsModalStore.getSubsection() ?? 0);
41
+
const setSubsection = React.useCallback(
42
+
(to: string) => {
43
+
if (subsection !== to) UserSettingsModalActionCreators.setSection("moonbase", to);
44
+
},
45
+
[subsection]
46
+
);
47
+
48
+
React.useEffect(
49
+
() => () => {
50
+
// Normally there's an onSettingsClose prop you can set but we don't expose it and I don't care enough to add support for it right now
51
+
UserSettingsModalActionCreators.clearSubsection("moonbase");
52
+
},
53
+
[]
54
+
);
55
+
56
+
return (
57
+
<>
58
+
<div className={`${HeaderBarClasses.children} ${Margins.marginBottom20}`}>
59
+
<Text className={HeaderBarClasses.titleWrapper} variant="heading-lg/semibold" tag="h2">
60
+
Moonbase
61
+
</Text>
62
+
<Divider />
63
+
<TabBar
64
+
selectedItem={subsection}
65
+
onItemSelect={setSubsection}
66
+
type="top-pill"
67
+
className={PeoplePageClasses.tabBar}
68
+
>
69
+
{pages.map((page, i) => (
70
+
<TabBar.Item key={page.id} id={i} className={PeoplePageClasses.item}>
71
+
{page.name}
72
+
</TabBar.Item>
73
+
))}
74
+
</TabBar>
75
+
</div>
76
+
77
+
<RestartAdviceMessage />
78
+
<Update />
79
+
80
+
{React.createElement(pages[subsection].element)}
81
+
</>
82
+
);
83
+
}
84
+
85
+
export { RestartAdviceMessage, Update };
+124
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
+124
packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx
···
1
+
import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux";
2
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
3
+
import React from "@moonlight-mod/wp/react";
4
+
import { UpdateState } from "../../types";
5
+
import HelpMessage from "./HelpMessage";
6
+
import { MoonlightBranch } from "@moonlight-mod/types";
7
+
import MarkupUtils from "@moonlight-mod/wp/discord/modules/markup/MarkupUtils";
8
+
import Flex from "@moonlight-mod/wp/discord/uikit/Flex";
9
+
import {
10
+
Button,
11
+
Text,
12
+
ModalRoot,
13
+
ModalSize,
14
+
ModalContent,
15
+
ModalHeader,
16
+
Heading,
17
+
ModalCloseButton,
18
+
openModal
19
+
} from "@moonlight-mod/wp/discord/components/common/index";
20
+
import MarkupClasses from "@moonlight-mod/wp/discord/modules/messages/web/Markup.css";
21
+
import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon";
22
+
23
+
const strings: Record<UpdateState, string> = {
24
+
[UpdateState.Ready]: "A new version of moonlight is available.",
25
+
[UpdateState.Working]: "Updating moonlight...",
26
+
[UpdateState.Installed]: "Updated. Restart Discord to apply changes.",
27
+
[UpdateState.Failed]: "Failed to update moonlight. Please use the installer instead."
28
+
};
29
+
30
+
function MoonlightChangelog({
31
+
changelog,
32
+
version,
33
+
transitionState,
34
+
onClose
35
+
}: {
36
+
changelog: string;
37
+
version: string;
38
+
transitionState: number | null;
39
+
onClose: () => void;
40
+
}) {
41
+
return (
42
+
<ModalRoot transitionState={transitionState} size={ModalSize.DYNAMIC}>
43
+
<ModalHeader>
44
+
<Flex.Child grow={1} shrink={1}>
45
+
<Heading variant="heading-lg/semibold">moonlight</Heading>
46
+
<Text variant="text-xs/normal">{version}</Text>
47
+
</Flex.Child>
48
+
49
+
<Flex.Child grow={0}>
50
+
<ModalCloseButton onClick={onClose} />
51
+
</Flex.Child>
52
+
</ModalHeader>
53
+
54
+
<ModalContent>
55
+
<Text variant="text-md/normal" className={MarkupClasses.markup} style={{ padding: "1rem" }}>
56
+
{MarkupUtils.parse(changelog, true, {
57
+
allowHeading: true,
58
+
allowList: true,
59
+
allowLinks: true
60
+
})}
61
+
</Text>
62
+
</ModalContent>
63
+
</ModalRoot>
64
+
);
65
+
}
66
+
67
+
export default function Update() {
68
+
const [newVersion, state] = useStateFromStores([MoonbaseSettingsStore], () => [
69
+
MoonbaseSettingsStore.newVersion,
70
+
MoonbaseSettingsStore.updateState
71
+
]);
72
+
73
+
if (newVersion == null) return null;
74
+
75
+
return (
76
+
<HelpMessage text={strings[state]} className="moonbase-update-section" icon={ThemeDarkIcon}>
77
+
<div className="moonbase-help-message-buttons">
78
+
{moonlight.branch === MoonlightBranch.STABLE && (
79
+
<Button
80
+
look={Button.Looks.OUTLINED}
81
+
color={Button.Colors.CUSTOM}
82
+
size={Button.Sizes.TINY}
83
+
onClick={() => {
84
+
fetch(`https://raw.githubusercontent.com/moonlight-mod/moonlight/refs/tags/${newVersion}/CHANGELOG.md`)
85
+
.then((r) => r.text())
86
+
.then((changelog) =>
87
+
openModal((modalProps) => {
88
+
return <MoonlightChangelog {...modalProps} changelog={changelog} version={newVersion} />;
89
+
})
90
+
);
91
+
}}
92
+
>
93
+
View changelog
94
+
</Button>
95
+
)}
96
+
97
+
{state === UpdateState.Installed && (
98
+
<Button
99
+
look={Button.Looks.OUTLINED}
100
+
color={Button.Colors.CUSTOM}
101
+
size={Button.Sizes.TINY}
102
+
onClick={() => {
103
+
MoonbaseSettingsStore.restartDiscord();
104
+
}}
105
+
>
106
+
Restart Discord
107
+
</Button>
108
+
)}
109
+
110
+
<Button
111
+
look={Button.Looks.OUTLINED}
112
+
color={Button.Colors.CUSTOM}
113
+
size={Button.Sizes.TINY}
114
+
disabled={state !== UpdateState.Ready}
115
+
onClick={() => {
116
+
MoonbaseSettingsStore.updateMoonlight();
117
+
}}
118
+
>
119
+
Update
120
+
</Button>
121
+
</div>
122
+
</HelpMessage>
123
+
);
124
+
}
+74
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
+74
packages/core-extensions/src/moonbase/webpackModules/updates.tsx
···
1
+
import spacepack from "@moonlight-mod/wp/spacepack_spacepack";
2
+
import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores";
3
+
import Notices from "@moonlight-mod/wp/notices_notices";
4
+
import { MoonlightBranch } from "@moonlight-mod/types";
5
+
import React from "@moonlight-mod/wp/react";
6
+
import ThemeDarkIcon from "@moonlight-mod/wp/moonbase_ThemeDarkIcon";
7
+
8
+
function plural(str: string, num: number) {
9
+
return `${str}${num > 1 ? "s" : ""}`;
10
+
}
11
+
12
+
function listener() {
13
+
if (
14
+
MoonbaseSettingsStore.shouldShowNotice &&
15
+
MoonbaseSettingsStore.getExtensionConfigRaw("moonbase", "updateBanner", true)
16
+
) {
17
+
MoonbaseSettingsStore.removeChangeListener(listener);
18
+
19
+
const version = MoonbaseSettingsStore.newVersion;
20
+
const extensionUpdateCount = Object.keys(MoonbaseSettingsStore.updates).length;
21
+
const hasExtensionUpdates = extensionUpdateCount > 0;
22
+
23
+
let message;
24
+
25
+
if (version != null) {
26
+
message =
27
+
moonlightNode.branch === MoonlightBranch.NIGHTLY
28
+
? `A new version of moonlight is available`
29
+
: `moonlight ${version} is available`;
30
+
}
31
+
32
+
if (hasExtensionUpdates) {
33
+
let concat = false;
34
+
if (message == null) {
35
+
message = "";
36
+
} else {
37
+
concat = true;
38
+
message += ", and ";
39
+
}
40
+
message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural(
41
+
"extension",
42
+
extensionUpdateCount
43
+
)} can be updated`;
44
+
}
45
+
46
+
if (message != null) message += ".";
47
+
48
+
Notices.addNotice({
49
+
element: (
50
+
<div className="moonbase-updates-notice_text-wrapper">
51
+
<ThemeDarkIcon size="sm" color="currentColor" />
52
+
{message}
53
+
</div>
54
+
),
55
+
color: "moonbase-updates-notice",
56
+
buttons: [
57
+
{
58
+
name: "Open Moonbase",
59
+
onClick: () => {
60
+
const { open } = spacepack.require("discord/actions/UserSettingsModalActionCreators").default;
61
+
if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) {
62
+
open("moonbase-extensions");
63
+
} else {
64
+
open("moonbase", "0");
65
+
}
66
+
return true;
67
+
}
68
+
}
69
+
]
70
+
});
71
+
}
72
+
}
73
+
74
+
MoonbaseSettingsStore.addChangeListener(listener);
+12
packages/core-extensions/src/moonbase/wp.d.ts
+12
packages/core-extensions/src/moonbase/wp.d.ts
···
1
+
declare module "@moonlight-mod/wp/moonbase_ui" {
2
+
export * from "core-extensions/src/moonbase/webpackModules/ui";
3
+
}
4
+
5
+
declare module "@moonlight-mod/wp/moonbase_stores" {
6
+
export * from "core-extensions/src/moonbase/webpackModules/stores";
7
+
}
8
+
9
+
declare module "@moonlight-mod/wp/moonbase_ThemeDarkIcon" {
10
+
import ThemeDarkIcon from "core-extensions/src/moonbase/webpackModules/ThemeDarkIcon";
11
+
export = ThemeDarkIcon;
12
+
}
+186
packages/core-extensions/src/nativeFixes/host.ts
+186
packages/core-extensions/src/nativeFixes/host.ts
···
1
+
import { app, nativeTheme } from "electron";
2
+
import * as path from "node:path";
3
+
import * as fs from "node:fs/promises";
4
+
import * as fsSync from "node:fs";
5
+
import { parseTarGzip } from "nanotar";
6
+
7
+
const logger = moonlightHost.getLogger("nativeFixes/host");
8
+
const enabledFeatures = app.commandLine.getSwitchValue("enable-features").split(",");
9
+
10
+
moonlightHost.events.on("window-created", function (browserWindow) {
11
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "devtoolsThemeFix") ?? true) {
12
+
browserWindow.webContents.on("devtools-opened", () => {
13
+
if (!nativeTheme.shouldUseDarkColors) return;
14
+
nativeTheme.themeSource = "light";
15
+
setTimeout(() => {
16
+
nativeTheme.themeSource = "dark";
17
+
}, 100);
18
+
});
19
+
}
20
+
});
21
+
22
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "disableRendererBackgrounding") ?? true) {
23
+
// Discord already disables UseEcoQoSForBackgroundProcess and some other
24
+
// related features
25
+
app.commandLine.appendSwitch("disable-renderer-backgrounding");
26
+
app.commandLine.appendSwitch("disable-backgrounding-occluded-windows");
27
+
28
+
// already added on Windows, but not on other operating systems
29
+
app.commandLine.appendSwitch("disable-background-timer-throttling");
30
+
}
31
+
32
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vulkan") ?? false) {
33
+
enabledFeatures.push("Vulkan", "DefaultANGLEVulkan", "VulkanFromANGLE");
34
+
}
35
+
36
+
if (process.platform === "linux") {
37
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxAutoscroll") ?? false) {
38
+
app.commandLine.appendSwitch("enable-blink-features", "MiddleClickAutoscroll");
39
+
}
40
+
41
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxSpeechDispatcher") ?? true) {
42
+
app.commandLine.appendSwitch("enable-speech-dispatcher");
43
+
}
44
+
45
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxHevcSupport") ?? true) {
46
+
enabledFeatures.push("PlatformHEVCDecoderSupport");
47
+
}
48
+
}
49
+
50
+
// NOTE: Only tested if this appears on Windows, it should appear on all when
51
+
// hardware acceleration is disabled
52
+
const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing");
53
+
if ((moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapi") ?? true) && !noAccel) {
54
+
if (process.platform === "linux") {
55
+
// These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4
56
+
enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder", "VaapiVideoDecodeLinuxGL");
57
+
58
+
if (moonlightHost.getConfigOption<boolean>("nativeFixes", "vaapiIgnoreDriverChecks") ?? false)
59
+
enabledFeatures.push("VaapiIgnoreDriverChecks");
60
+
}
61
+
}
62
+
63
+
app.commandLine.appendSwitch("enable-features", [...new Set(enabledFeatures)].join(","));
64
+
65
+
if (process.platform === "linux" && moonlightHost.getConfigOption<boolean>("nativeFixes", "linuxUpdater")) {
66
+
const exePath = app.getPath("exe");
67
+
const appName = path.basename(exePath);
68
+
const targetDir = path.dirname(exePath);
69
+
const { releaseChannel }: { releaseChannel: string } = JSON.parse(
70
+
fsSync.readFileSync(path.join(targetDir, "resources", "build_info.json"), "utf8")
71
+
);
72
+
73
+
const updaterModule = require(path.join(moonlightHost.asarPath, "app_bootstrap", "hostUpdater.js"));
74
+
const updater = updaterModule.constructor;
75
+
76
+
async function doUpdate(cb: (percent: number) => void) {
77
+
logger.debug("Extracting to", targetDir);
78
+
79
+
const exists = (path: string) =>
80
+
fs
81
+
.stat(path)
82
+
.then(() => true)
83
+
.catch(() => false);
84
+
85
+
const url = `https://discord.com/api/download/${releaseChannel}?platform=linux&format=tar.gz`;
86
+
const resp = await fetch(url, {
87
+
cache: "no-store"
88
+
});
89
+
90
+
const reader = resp.body!.getReader();
91
+
const contentLength = parseInt(resp.headers.get("Content-Length") ?? "0");
92
+
logger.info(`Expecting ${contentLength} bytes for the update`);
93
+
const bytes = new Uint8Array(contentLength);
94
+
let pos = 0;
95
+
let lastPercent = 0;
96
+
97
+
while (true) {
98
+
const { done, value } = await reader.read();
99
+
if (done) {
100
+
break;
101
+
} else {
102
+
bytes.set(value, pos);
103
+
pos += value.length;
104
+
105
+
const newPercent = Math.floor((pos / contentLength) * 100);
106
+
if (lastPercent !== newPercent) {
107
+
lastPercent = newPercent;
108
+
cb(newPercent);
109
+
}
110
+
}
111
+
}
112
+
113
+
const files = await parseTarGzip(bytes);
114
+
115
+
for (const file of files) {
116
+
if (!file.data) continue;
117
+
// @ts-expect-error What do you mean their own types are wrong
118
+
if (file.type !== "file") continue;
119
+
120
+
// Discord update files are inside of a main "Discord(PTB|Canary)" folder
121
+
const filePath = file.name.replace(`${appName}/`, "");
122
+
logger.info("Extracting", filePath);
123
+
124
+
let targetFilePath = path.join(targetDir, filePath);
125
+
if (filePath === "resources/app.asar") {
126
+
// You tried
127
+
targetFilePath = path.join(targetDir, "resources", "_app.asar");
128
+
} else if (filePath === appName || filePath === "chrome_crashpad_handler") {
129
+
// Can't write over the executable? Just move it! 4head
130
+
if (await exists(targetFilePath)) {
131
+
await fs.rename(targetFilePath, targetFilePath + ".bak");
132
+
await fs.unlink(targetFilePath + ".bak");
133
+
}
134
+
}
135
+
const targetFileDir = path.dirname(targetFilePath);
136
+
137
+
if (!(await exists(targetFileDir))) await fs.mkdir(targetFileDir, { recursive: true });
138
+
await fs.writeFile(targetFilePath, file.data);
139
+
140
+
const mode = file.attrs?.mode;
141
+
if (mode != null) {
142
+
// Not sure why this slice is needed
143
+
await fs.chmod(targetFilePath, mode.slice(-3));
144
+
}
145
+
}
146
+
147
+
logger.debug("Done updating");
148
+
}
149
+
150
+
const realEmit = updater.prototype.emit;
151
+
updater.prototype.emit = function (event: string, ...args: any[]) {
152
+
// Arrow functions don't bind `this` :D
153
+
const call = (event: string, ...args: any[]) => realEmit.call(this, event, ...args);
154
+
155
+
if (event === "update-manually") {
156
+
const latestVerStr: string = args[0];
157
+
logger.debug("update-manually called, intercepting", latestVerStr);
158
+
call("update-available");
159
+
160
+
(async () => {
161
+
try {
162
+
await doUpdate((progress) => {
163
+
call("update-progress", progress);
164
+
});
165
+
// Copied from the win32 updater
166
+
this.updateVersion = latestVerStr;
167
+
call(
168
+
"update-downloaded",
169
+
{},
170
+
releaseChannel,
171
+
latestVerStr,
172
+
new Date(),
173
+
this.updateUrl,
174
+
this.quitAndInstall.bind(this)
175
+
);
176
+
} catch (e) {
177
+
logger.error("Error updating", e);
178
+
}
179
+
})();
180
+
181
+
return this;
182
+
} else {
183
+
return realEmit.call(this, event, ...args);
184
+
}
185
+
};
186
+
}
+77
packages/core-extensions/src/nativeFixes/manifest.json
+77
packages/core-extensions/src/nativeFixes/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "nativeFixes",
4
+
"meta": {
5
+
"name": "Native Fixes",
6
+
"tagline": "Various configurable fixes for Discord and Electron",
7
+
"authors": ["Cynosphere", "adryd", "NotNite"],
8
+
"tags": ["fixes"]
9
+
},
10
+
"environment": "desktop",
11
+
"settings": {
12
+
"devtoolsThemeFix": {
13
+
"advice": "restart",
14
+
"displayName": "Devtools Theme Fix",
15
+
"description": "Temporary workaround for devtools defaulting to light theme on Electron 32",
16
+
"type": "boolean",
17
+
"default": true
18
+
},
19
+
"disableRendererBackgrounding": {
20
+
"advice": "restart",
21
+
"displayName": "Disable Renderer Backgrounding",
22
+
"description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often",
23
+
"type": "boolean",
24
+
"default": true
25
+
},
26
+
"vulkan": {
27
+
"advice": "restart",
28
+
"displayName": "Enable Vulkan renderer",
29
+
"description": "Uses the Vulkan backend for rendering",
30
+
"type": "boolean",
31
+
"default": false
32
+
},
33
+
"linuxAutoscroll": {
34
+
"advice": "restart",
35
+
"displayName": "Enable middle click autoscroll on Linux",
36
+
"description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems",
37
+
"type": "boolean",
38
+
"default": false
39
+
},
40
+
"linuxSpeechDispatcher": {
41
+
"advice": "restart",
42
+
"displayName": "Enable speech-dispatcher for TTS on Linux",
43
+
"description": "Fixes text-to-speech. Has no effect on other operating systems",
44
+
"type": "boolean",
45
+
"default": true
46
+
},
47
+
"vaapi": {
48
+
"advice": "restart",
49
+
"displayName": "Enable VAAPI features on Linux",
50
+
"description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems",
51
+
"type": "boolean",
52
+
"default": true
53
+
},
54
+
"vaapiIgnoreDriverChecks": {
55
+
"advice": "restart",
56
+
"displayName": "Ignore VAAPI driver checks on Linux",
57
+
"description": "Forces hardware video acceleration on some graphics drivers at the cost of stability. Has no effect on other operating systems",
58
+
"type": "boolean",
59
+
"default": false
60
+
},
61
+
"linuxUpdater": {
62
+
"advice": "restart",
63
+
"displayName": "Linux Updater",
64
+
"description": "Actually implements updating Discord on Linux. Has no effect on other operating systems",
65
+
"type": "boolean",
66
+
"default": false
67
+
},
68
+
"linuxHevcSupport": {
69
+
"advice": "restart",
70
+
"displayName": "HEVC support on Linux",
71
+
"description": "You might also need to enable Vulkan renderer. Has no effect on other operating systems",
72
+
"type": "boolean",
73
+
"default": true
74
+
}
75
+
},
76
+
"apiLevel": 2
77
+
}
+4
-4
packages/core-extensions/src/noHideToken/index.ts
+4
-4
packages/core-extensions/src/noHideToken/index.ts
···
1
-
import { Patch } from "types/src";
1
+
import { Patch } from "@moonlight-mod/types";
2
2
3
3
export const patches: Patch[] = [
4
4
{
5
-
find: "hideToken(){",
5
+
find: "hideToken:()=>",
6
6
replace: {
7
-
match: /hideToken\(\)\{.+?},/,
8
-
replacement: `hideToken(){},`
7
+
match: /hideToken:\(\)=>.+?,/,
8
+
replacement: `hideToken:()=>{},`
9
9
}
10
10
}
11
11
];
+4
-1
packages/core-extensions/src/noHideToken/manifest.json
+4
-1
packages/core-extensions/src/noHideToken/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "noHideToken",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "No Hide Token",
5
-
"tagline": "Disables removal of token from localStorage when opening dev tools",
7
+
"tagline": "Prevents you from being logged-out on hard-crash",
8
+
"description": "Prevents you from being logged-out on hard-crash by disabling removal of token from localStorage when opening dev tools",
6
9
"authors": ["adryd"],
7
10
"tags": ["dangerZone", "development"]
8
11
}
-15
packages/core-extensions/src/noTrack/host.ts
-15
packages/core-extensions/src/noTrack/host.ts
···
1
-
import { BrowserWindow } from "electron";
2
-
3
-
moonlightHost.events.on("window-created", (window: BrowserWindow) => {
4
-
window.webContents.session.webRequest.onBeforeRequest(
5
-
{
6
-
urls: [
7
-
"https://*.discord.com/api/v*/science",
8
-
"https://*.discord.com/api/v*/metrics"
9
-
]
10
-
},
11
-
function (details, callback) {
12
-
callback({ cancel: true });
13
-
}
14
-
);
15
-
});
+4
-4
packages/core-extensions/src/noTrack/index.ts
+4
-4
packages/core-extensions/src/noTrack/index.ts
···
1
-
import { Patch, PatchReplaceType } from "@moonlight-mod/types";
1
+
import { Patch } from "@moonlight-mod/types";
2
2
3
3
export const patches: Patch[] = [
4
4
{
5
-
find: "analyticsTrackingStoreMaker:function",
5
+
find: "analyticsTrackingStoreMaker:()=>",
6
6
replace: {
7
-
match: /analyticsTrackingStoreMaker:function\(\){return .}/,
8
-
replacement: "analyticsTrackingStoreMaker:function(){return ()=>{}}"
7
+
match: /analyticsTrackingStoreMaker:\(\)=>.+?,/,
8
+
replacement: "analyticsTrackingStoreMaker:()=>()=>{},"
9
9
}
10
10
},
11
11
{
+9
-1
packages/core-extensions/src/noTrack/manifest.json
+9
-1
packages/core-extensions/src/noTrack/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "noTrack",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "No Track",
5
7
"tagline": "Disables /api/science and analytics",
6
8
"authors": ["Cynosphere", "NotNite"],
7
9
"tags": ["privacy"]
8
-
}
10
+
},
11
+
"blocked": [
12
+
"https://*.discord.com/api/v*/science",
13
+
"https://*.discord.com/api/v*/metrics",
14
+
"https://*.discordapp.com/api/v*/science",
15
+
"https://*.discordapp.com/api/v*/metrics"
16
+
]
9
17
}
+42
packages/core-extensions/src/notices/index.ts
+42
packages/core-extensions/src/notices/index.ts
···
1
+
import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types";
2
+
3
+
export const patches: Patch[] = [
4
+
{
5
+
find: ".GUILD_RAID_NOTIFICATION:",
6
+
replace: {
7
+
match: /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/,
8
+
replacement: (orig, createElement) =>
9
+
`case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}`
10
+
}
11
+
},
12
+
{
13
+
find: '"NoticeStore"',
14
+
replace: [
15
+
{
16
+
match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/,
17
+
replacement: (orig: string) =>
18
+
`__moonlight_notice:{predicate:()=>require("notices_notices").default.shouldShowNotice()},${orig}`
19
+
},
20
+
{
21
+
match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g,
22
+
replacement: (_, orig) => `=["__moonlight_notice",${orig}`
23
+
}
24
+
]
25
+
}
26
+
];
27
+
28
+
export const webpackModules: Record<string, ExtensionWebpackModule> = {
29
+
notices: {
30
+
dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }]
31
+
},
32
+
33
+
component: {
34
+
dependencies: [
35
+
{ id: "react" },
36
+
{ id: "discord/Dispatcher" },
37
+
{ id: "discord/components/common/index" },
38
+
{ id: "discord/packages/flux" },
39
+
{ ext: "notices", id: "notices" }
40
+
]
41
+
}
42
+
};
+11
packages/core-extensions/src/notices/manifest.json
+11
packages/core-extensions/src/notices/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "notices",
4
+
"apiLevel": 2,
5
+
"meta": {
6
+
"name": "Notices",
7
+
"tagline": "An API for adding notices at the top of the page",
8
+
"authors": ["Cynosphere", "NotNite"],
9
+
"tags": ["library"]
10
+
}
11
+
}
+50
packages/core-extensions/src/notices/webpackModules/component.tsx
+50
packages/core-extensions/src/notices/webpackModules/component.tsx
···
1
+
import React from "@moonlight-mod/wp/react";
2
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
3
+
import { Notice, NoticeCloseButton, PrimaryCTANoticeButton } from "@moonlight-mod/wp/discord/components/common/index";
4
+
import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux";
5
+
import NoticesStore from "@moonlight-mod/wp/notices_notices";
6
+
import type { Notice as NoticeType } from "@moonlight-mod/types/coreExtensions/notices";
7
+
8
+
function popAndDismiss(notice: NoticeType) {
9
+
NoticesStore.popNotice();
10
+
if (notice?.onDismiss) {
11
+
notice.onDismiss();
12
+
}
13
+
if (!NoticesStore.shouldShowNotice()) {
14
+
Dispatcher.dispatch({
15
+
type: "NOTICE_DISMISS"
16
+
});
17
+
}
18
+
}
19
+
20
+
export default function UpdateNotice() {
21
+
const { notice } = useStateFromStoresObject([NoticesStore], () => ({
22
+
notice: NoticesStore.getCurrentNotice()
23
+
}));
24
+
25
+
if (notice == null) return <></>;
26
+
27
+
return (
28
+
<Notice color={notice.color}>
29
+
{notice.element}
30
+
31
+
{(notice.showClose ?? true) && (
32
+
<NoticeCloseButton onClick={() => popAndDismiss(notice)} noticeType="__moonlight_notice" />
33
+
)}
34
+
35
+
{(notice.buttons ?? []).map((button) => (
36
+
<PrimaryCTANoticeButton
37
+
key={button.name}
38
+
onClick={() => {
39
+
if (button.onClick()) {
40
+
popAndDismiss(notice);
41
+
}
42
+
}}
43
+
noticeType="__moonlight_notice"
44
+
>
45
+
{button.name}
46
+
</PrimaryCTANoticeButton>
47
+
))}
48
+
</Notice>
49
+
);
50
+
}
+55
packages/core-extensions/src/notices/webpackModules/notices.ts
+55
packages/core-extensions/src/notices/webpackModules/notices.ts
···
1
+
import { Store } from "@moonlight-mod/wp/discord/packages/flux";
2
+
import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher";
3
+
import type { Notice, Notices } from "@moonlight-mod/types/coreExtensions/notices";
4
+
5
+
// very lazy way of doing this, FIXME
6
+
let open = false;
7
+
8
+
class NoticesStore extends Store<any> {
9
+
private notices: Notice[] = [];
10
+
11
+
constructor() {
12
+
super(Dispatcher);
13
+
}
14
+
15
+
addNotice(notice: Notice) {
16
+
this.notices.push(notice);
17
+
if (open && this.notices.length !== 0) {
18
+
Dispatcher.dispatch({
19
+
type: "NOTICE_SHOW",
20
+
notice: { type: "__moonlight_notice" }
21
+
});
22
+
}
23
+
this.emitChange();
24
+
}
25
+
26
+
popNotice() {
27
+
this.notices.shift();
28
+
this.emitChange();
29
+
}
30
+
31
+
getCurrentNotice() {
32
+
return this.notices.length > 0 ? this.notices[0] : null;
33
+
}
34
+
35
+
shouldShowNotice() {
36
+
return this.notices.length > 0;
37
+
}
38
+
}
39
+
40
+
const store: Notices = new NoticesStore();
41
+
42
+
function showNotice() {
43
+
open = true;
44
+
if (store.shouldShowNotice()) {
45
+
Dispatcher.dispatch({
46
+
type: "NOTICE_SHOW",
47
+
notice: { type: "__moonlight_notice" }
48
+
});
49
+
}
50
+
}
51
+
52
+
Dispatcher.subscribe("CONNECTION_OPEN", showNotice);
53
+
Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", showNotice);
54
+
55
+
export default store;
+36
-49
packages/core-extensions/src/quietLoggers/index.ts
+36
-49
packages/core-extensions/src/quietLoggers/index.ts
···
1
1
import { Patch } from "@moonlight-mod/types";
2
2
3
3
const notXssDefensesOnly = () =>
4
-
(moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ??
5
-
false) === false;
4
+
(moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? false) === false;
5
+
6
+
const silenceDiscordLogger = moonlight.getConfigOption<boolean>("quietLoggers", "silenceDiscordLogger") ?? false;
6
7
7
8
// These patches MUST run before the simple patches, these are to remove loggers
8
9
// that end up causing syntax errors by the normal patch
9
10
const loggerFixes: Patch[] = [
10
11
{
11
-
find: '"./ggsans-800-extrabolditalic.woff2":',
12
+
find: '"./gg-sans/ggsans-800-extrabolditalic.woff2":',
12
13
replace: {
13
-
match: /\.then\(function\(\){var.+?"MODULE_NOT_FOUND",.\}\)/,
14
-
replacement: ".then(()=>(()=>{}))"
14
+
match: /var .=Error.+?;throw .+?,./,
15
+
replacement: ""
15
16
}
16
17
},
17
18
{
···
29
30
// Patches to simply remove a logger call
30
31
const stubPatches = [
31
32
// "sh" is not a valid locale.
32
-
[
33
-
"is not a valid locale",
34
-
/(.)\.error\(""\.concat\((.)\," is not a valid locale\."\)\)/g
35
-
],
36
-
['.displayName="RunningGameStore"', /.\.info\("games",{.+?}\),/],
37
-
[
38
-
'"[BUILD INFO] Release Channel: "',
39
-
/new\(0,.{1,2}\.default\)\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?"\)\),/
40
-
],
41
-
[
42
-
'.AnalyticEvents.APP_NATIVE_CRASH,"Storage"',
43
-
/console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/
44
-
],
45
-
[
46
-
'.AnalyticEvents.APP_NATIVE_CRASH,"Storage"',
47
-
'console.log("AppCrashedFatalReport: getLastCrash not supported.");'
48
-
],
49
-
[
50
-
'"[NATIVE INFO] ',
51
-
/new\(0,.{1,2}\.default\)\(\)\.log\("\[NATIVE INFO] .+?\)\),/
52
-
],
33
+
["is not a valid locale", /void (.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g],
34
+
['"[BUILD INFO] Release Channel: "', /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/],
35
+
['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/],
36
+
['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'],
37
+
['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/],
53
38
['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g],
54
-
[
55
-
'throw new Error("Messages are still loading.");',
56
-
/console\.warn\("Unsupported Locale",.\);/
57
-
],
58
-
["_dispatchWithDevtools=", /.\.has\(.\.type\)&&.\.log\(.+?\);/],
59
-
["_dispatchWithDevtools=", /.\.totalTime>100&&.\.log\(.+?\);0;/],
60
-
[
61
-
'"NativeDispatchUtils"',
62
-
/null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/
63
-
],
64
-
[
65
-
'Error("Messages are still loading.")',
66
-
/console\.warn\("Unsupported Locale",.\),/
67
-
],
39
+
['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",.\),/],
40
+
["}_dispatchWithDevtools(", /.\.totalTime>.{1,2}&&.\.verbose\(.+?\);/],
41
+
['"NativeDispatchUtils"', /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/],
68
42
['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/],
69
43
[
70
44
'"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "',
71
45
/.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/
72
-
]
46
+
],
47
+
['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",.\),/]
73
48
];
74
49
75
50
const simplePatches = [
76
51
// Moment.js deprecation warnings
77
-
["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"],
78
-
79
-
// Zustand related
80
-
[
81
-
/console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\)/g,
82
-
"/*$&*/"
83
-
]
52
+
["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"]
84
53
] as { [0]: string | RegExp; [1]: string }[];
85
54
86
55
export const patches: Patch[] = [
87
56
{
88
-
find: ".Messages.XSSDefenses",
57
+
find: ".Messages.SELF_XSS_HEADER",
89
58
replace: {
90
59
match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/,
91
60
replacement: "(true)"
92
61
}
62
+
},
63
+
// Highlight.js deprecation warnings
64
+
{
65
+
find: "Deprecated as of",
66
+
replace: {
67
+
match: /console\./g,
68
+
replacement: "false&&console."
69
+
},
70
+
prerequisite: notXssDefensesOnly
71
+
},
72
+
// Discord's logger
73
+
{
74
+
find: "ฮฃ:",
75
+
replace: {
76
+
match: "for",
77
+
replacement: "return;for"
78
+
},
79
+
prerequisite: () => silenceDiscordLogger && notXssDefensesOnly()
93
80
},
94
81
...loggerFixes,
95
82
...stubPatches.map((patch) => ({
+10
packages/core-extensions/src/quietLoggers/manifest.json
+10
packages/core-extensions/src/quietLoggers/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "quietLoggers",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "Quiet Loggers",
5
7
"tagline": "Quiet errors on startup, and disable unnecesary loggers",
···
8
10
},
9
11
"settings": {
10
12
"xssDefensesOnly": {
13
+
"advice": "reload",
11
14
"displayName": "Only hide self-XSS",
12
15
"description": "Only disable self XSS prevention log",
16
+
"type": "boolean",
17
+
"default": false
18
+
},
19
+
"silenceDiscordLogger": {
20
+
"advice": "reload",
21
+
"displayName": "Silence Discord logger",
22
+
"description": "Hides all messages from Discord's logger (the logs that start with purple text in brackets)",
13
23
"type": "boolean",
14
24
"default": false
15
25
}
+75
packages/core-extensions/src/rocketship/host/permissions.ts
+75
packages/core-extensions/src/rocketship/host/permissions.ts
···
1
+
import type { BrowserWindow } from "electron";
2
+
3
+
type PermissionRequestHandler = (
4
+
webContents: Electron.WebContents,
5
+
permission: string,
6
+
callback: (permissionGranted: boolean) => void,
7
+
details: Electron.PermissionRequestHandlerHandlerDetails
8
+
) => void;
9
+
10
+
type PermissionCheckHandler = (
11
+
webContents: Electron.WebContents | null,
12
+
permission: string,
13
+
requestingOrigin: string,
14
+
details: Electron.PermissionCheckHandlerHandlerDetails
15
+
) => boolean;
16
+
17
+
moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => {
18
+
if (!isMainWindow) return;
19
+
const windowSession = window.webContents.session;
20
+
21
+
// setPermissionRequestHandler
22
+
windowSession.setPermissionRequestHandler((webcontents, permission, callback, details) => {
23
+
let cbResult = false;
24
+
function fakeCallback(result: boolean) {
25
+
cbResult = result;
26
+
}
27
+
28
+
if (caughtPermissionRequestHandler) {
29
+
caughtPermissionRequestHandler(webcontents, permission, fakeCallback, details);
30
+
}
31
+
32
+
if (permission === "media" || permission === "display-capture") {
33
+
cbResult = true;
34
+
}
35
+
36
+
callback(cbResult);
37
+
});
38
+
39
+
let caughtPermissionRequestHandler: PermissionRequestHandler | undefined;
40
+
41
+
windowSession.setPermissionRequestHandler = function catchSetPermissionRequestHandler(
42
+
handler: (
43
+
webcontents: Electron.WebContents,
44
+
permission: string,
45
+
callback: (permissionGranted: boolean) => void
46
+
) => void
47
+
) {
48
+
caughtPermissionRequestHandler = handler;
49
+
};
50
+
51
+
// setPermissionCheckHandler
52
+
windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => {
53
+
return false;
54
+
});
55
+
56
+
let caughtPermissionCheckHandler: PermissionCheckHandler | undefined;
57
+
58
+
windowSession.setPermissionCheckHandler((webcontents, permission, requestingOrigin, details) => {
59
+
let result = false;
60
+
61
+
if (caughtPermissionCheckHandler) {
62
+
result = caughtPermissionCheckHandler(webcontents, permission, requestingOrigin, details);
63
+
}
64
+
65
+
if (permission === "media" || permission === "display-capture") {
66
+
result = true;
67
+
}
68
+
69
+
return result;
70
+
});
71
+
72
+
windowSession.setPermissionCheckHandler = function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) {
73
+
caughtPermissionCheckHandler = handler;
74
+
};
75
+
});
+27
packages/core-extensions/src/rocketship/host/types.ts
+27
packages/core-extensions/src/rocketship/host/types.ts
···
1
+
// https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts
2
+
type DefaultProps = "node.name" | "application.name";
3
+
4
+
type LiteralUnion<LiteralType, BaseType extends string> = LiteralType | (BaseType & Record<never, never>);
5
+
6
+
type Optional<Type, Key extends keyof Type> = Partial<Pick<Type, Key>> & Omit<Type, Key>;
7
+
8
+
export type Node<T extends string = never> = Record<LiteralUnion<T, string>, string>;
9
+
10
+
export interface LinkData {
11
+
include: Node[];
12
+
exclude: Node[];
13
+
14
+
ignore_devices?: boolean;
15
+
16
+
only_speakers?: boolean;
17
+
only_default_speakers?: boolean;
18
+
19
+
workaround?: Node[];
20
+
}
21
+
22
+
export interface PatchBay {
23
+
unlink(): void;
24
+
25
+
list<T extends string = DefaultProps>(props?: T[]): Node<T>[];
26
+
link(data: Optional<LinkData, "exclude"> | Optional<LinkData, "include">): boolean;
27
+
}
+69
packages/core-extensions/src/rocketship/host/venmic.ts
+69
packages/core-extensions/src/rocketship/host/venmic.ts
···
1
+
import type { BrowserWindow } from "electron";
2
+
import { app, desktopCapturer } from "electron";
3
+
import path from "node:path";
4
+
import { type PatchBay } from "./types";
5
+
6
+
const logger = moonlightHost.getLogger("rocketship");
7
+
8
+
function getPatchbay() {
9
+
try {
10
+
const venmic = require(path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node")) as {
11
+
PatchBay: new () => PatchBay;
12
+
};
13
+
const patchbay = new venmic.PatchBay();
14
+
return patchbay;
15
+
} catch (error) {
16
+
logger.error("Failed to load venmic.node:", error);
17
+
return null;
18
+
}
19
+
}
20
+
21
+
const patchbay = getPatchbay();
22
+
23
+
// TODO: figure out how to map source to window with venmic
24
+
function linkVenmic() {
25
+
if (patchbay == null) return false;
26
+
27
+
try {
28
+
const pid =
29
+
app
30
+
.getAppMetrics()
31
+
.find((proc) => proc.name === "Audio Service")
32
+
?.pid?.toString() ?? "";
33
+
34
+
logger.info("Audio Service PID:", pid);
35
+
36
+
patchbay.unlink();
37
+
return patchbay.link({
38
+
exclude: [{ "application.process.id": pid }, { "media.class": "Stream/Input/Audio" }],
39
+
ignore_devices: true,
40
+
only_speakers: true,
41
+
only_default_speakers: true
42
+
});
43
+
} catch (error) {
44
+
logger.error("Failed to link venmic:", error);
45
+
return false;
46
+
}
47
+
}
48
+
49
+
moonlightHost.events.on("window-created", (window: BrowserWindow, isMainWindow: boolean) => {
50
+
if (!isMainWindow) return;
51
+
const windowSession = window.webContents.session;
52
+
53
+
// @ts-expect-error these types ancient
54
+
windowSession.setDisplayMediaRequestHandler(
55
+
(request: any, callback: any) => {
56
+
const linked = linkVenmic();
57
+
desktopCapturer.getSources({ types: ["screen", "window"] }).then((sources) => {
58
+
//logger.debug("desktopCapturer.getSources", sources);
59
+
logger.debug("Linked to venmic:", linked);
60
+
61
+
callback({
62
+
video: sources[0],
63
+
audio: "loopback"
64
+
});
65
+
});
66
+
},
67
+
{ useSystemPicker: true }
68
+
);
69
+
});
+2
packages/core-extensions/src/rocketship/host.ts
+2
packages/core-extensions/src/rocketship/host.ts
+124
packages/core-extensions/src/rocketship/index.ts
+124
packages/core-extensions/src/rocketship/index.ts
···
1
+
import { Patch } from "@moonlight-mod/types";
2
+
3
+
const logger = moonlight.getLogger("rocketship");
4
+
const getDisplayMediaOrig = navigator.mediaDevices.getDisplayMedia;
5
+
6
+
async function getVenmicStream() {
7
+
try {
8
+
const devices = await navigator.mediaDevices.enumerateDevices();
9
+
logger.debug("Devices:", devices);
10
+
11
+
// This isn't vencord :(
12
+
const id = devices.find((device) => device.label === "vencord-screen-share")?.deviceId;
13
+
if (!id) return null;
14
+
logger.debug("Got venmic device ID:", id);
15
+
16
+
const stream = await navigator.mediaDevices.getUserMedia({
17
+
audio: {
18
+
deviceId: {
19
+
exact: id
20
+
},
21
+
autoGainControl: false,
22
+
echoCancellation: false,
23
+
noiseSuppression: false
24
+
}
25
+
});
26
+
27
+
return stream.getAudioTracks();
28
+
} catch (error) {
29
+
logger.warn("Failed to get venmic stream:", error);
30
+
return null;
31
+
}
32
+
}
33
+
34
+
navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect(options) {
35
+
const orig = await getDisplayMediaOrig.call(this, options);
36
+
37
+
const venmic = await getVenmicStream();
38
+
logger.debug("venmic", venmic);
39
+
if (venmic != null) {
40
+
// venmic will be proxying all audio, so we need to remove the original
41
+
// tracks to not cause overlap
42
+
for (const track of orig.getAudioTracks()) {
43
+
orig.removeTrack(track);
44
+
}
45
+
46
+
for (const track of venmic) {
47
+
orig.addTrack(track);
48
+
}
49
+
}
50
+
51
+
return orig;
52
+
};
53
+
54
+
export const patches: Patch[] = [
55
+
// "Ensure discord_voice is happy"
56
+
{
57
+
find: "RustAudioDeviceModule",
58
+
replace: [
59
+
{
60
+
match: /static supported\(\)\{.+?\}/,
61
+
replacement: "static supported(){return true}"
62
+
},
63
+
{
64
+
match: "supported(){return!0}",
65
+
replacement: "supported(){return true}"
66
+
}
67
+
]
68
+
},
69
+
// Remove Native media engine from list of choices
70
+
{
71
+
find: '.CAMERA_BACKGROUND_LIVE="cameraBackgroundLive"',
72
+
replace: {
73
+
match: /.\..{1,2}\.NATIVE,/,
74
+
replacement: ""
75
+
}
76
+
},
77
+
// Stub out browser checks to allow us to use WebRTC voice on Embedded
78
+
{
79
+
find: "Using Unified Plan (",
80
+
replace: {
81
+
match: /return .\..{1,2}\?\((.)\.info/,
82
+
replacement: (_, logger) => `return true?(${logger}.info`
83
+
}
84
+
},
85
+
{
86
+
find: '"UnifiedConnection("',
87
+
replace: {
88
+
match: /this\.videoSupported=.\..{1,2};/,
89
+
replacement: "this.videoSupported=true;"
90
+
}
91
+
},
92
+
{
93
+
find: "OculusBrowser",
94
+
replace: [
95
+
{
96
+
match: /"Firefox"===(.)\(\)\.name/g,
97
+
replacement: (orig, info) => `true||${orig}`
98
+
}
99
+
]
100
+
},
101
+
{
102
+
find: ".getMediaEngine().getDesktopSource",
103
+
replace: {
104
+
match: /.\.isPlatformEmbedded/,
105
+
replacement: "false"
106
+
}
107
+
},
108
+
{
109
+
// Matching MediaEngineStore
110
+
find: '"displayName","MediaEngineStore")',
111
+
replace: [
112
+
// Prevent loading of krisp native module by stubbing out desktop checks
113
+
{
114
+
match: /\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/,
115
+
replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__`
116
+
},
117
+
// Enable loading of web krisp equivelant by replacing isWeb with true
118
+
{
119
+
match: /\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/,
120
+
replacement: (orig, supportsNoiseCancellation) => `true&&${supportsNoiseCancellation}`
121
+
}
122
+
]
123
+
}
124
+
];
+13
packages/core-extensions/src/rocketship/manifest.json
+13
packages/core-extensions/src/rocketship/manifest.json
···
1
+
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
3
+
"id": "rocketship",
4
+
"apiLevel": 2,
5
+
"environment": "desktop",
6
+
"meta": {
7
+
"name": "Rocketship",
8
+
"tagline": "Adds new features when using rocketship",
9
+
"description": "**This extension only works on Linux when using rocketship:**\nhttps://github.com/moonlight-mod/rocketship\n\nAdds new features to the Discord Linux client with rocketship, such as a better screensharing experience.",
10
+
"authors": ["NotNite", "Cynosphere", "adryd"],
11
+
"deprecated": true
12
+
}
13
+
}
+11
-62
packages/core-extensions/src/settings/index.ts
+11
-62
packages/core-extensions/src/settings/index.ts
···
1
-
import { Patch, PatchReplaceType } from "@moonlight-mod/types";
2
-
import {
3
-
SettingsSection,
4
-
Settings as SettingsType
5
-
} from "@moonlight-mod/types/coreExtensions";
6
-
import { ExtensionWebExports, WebpackModuleFunc } from "@moonlight-mod/types";
1
+
import { Patch } from "@moonlight-mod/types";
2
+
import { ExtensionWebExports } from "@moonlight-mod/types";
7
3
8
4
export const patches: Patch[] = [
9
5
{
10
-
find: ".UserSettingsSections.EXPERIMENTS",
6
+
find: '"useGenerateUserSettingsSections"',
11
7
replace: {
12
-
match: /\.CUSTOM,element:(.+?)}\];return (.{1,2})/,
13
-
replacement: (_, lastElement, sections) =>
14
-
`.CUSTOM,element:${lastElement}}];return require("settings_settings")._mutateSections(${sections})`
8
+
match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/,
9
+
replacement: (_, sections: string) => `require("settings_settings").Settings._mutateSections(${sections})}`
15
10
}
16
11
},
17
12
{
18
13
find: 'navId:"user-settings-cog",',
19
14
replace: {
20
-
match: /children:\[(.)\.map\(.+?\),{children:.\((.)\)/,
15
+
match: /children:\[(\i)\.map\(.+?\),.*?children:\i\((\i)\)/,
21
16
replacement: (orig, sections, section) =>
22
-
`${orig}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()`
17
+
`${orig.replace(
18
+
/Object\.values\(.\..+?\)/,
19
+
(orig) => `[...require("settings_settings").Settings.sectionNames,...${orig}]`
20
+
)}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()`
23
21
}
24
22
}
25
23
];
26
24
27
25
export const webpackModules: ExtensionWebExports["webpackModules"] = {
28
-
settings: {
29
-
run: (module, exports, require) => {
30
-
const Settings: SettingsType = {
31
-
ourSections: [],
32
-
33
-
addSection: (section, label, element, color = null, pos, notice) => {
34
-
const data: SettingsSection = {
35
-
section,
36
-
label,
37
-
color,
38
-
element,
39
-
pos: pos ?? -4,
40
-
notice: notice
41
-
};
42
-
43
-
Settings.ourSections.push(data);
44
-
return data;
45
-
},
46
-
47
-
addDivider: (pos = null) => {
48
-
Settings.ourSections.push({
49
-
section: "DIVIDER",
50
-
pos: pos === null ? -4 : pos
51
-
});
52
-
},
53
-
54
-
addHeader: function (label, pos = null) {
55
-
Settings.ourSections.push({
56
-
section: "HEADER",
57
-
label: label,
58
-
pos: pos === null ? -4 : pos
59
-
});
60
-
},
61
-
62
-
_mutateSections: (sections) => {
63
-
for (const section of Settings.ourSections) {
64
-
sections.splice(
65
-
section.pos < 0 ? sections.length + section.pos : section.pos,
66
-
0,
67
-
section
68
-
);
69
-
}
70
-
71
-
return sections;
72
-
}
73
-
};
74
-
75
-
module.exports = Settings;
76
-
}
77
-
}
26
+
settings: {}
78
27
};
+2
packages/core-extensions/src/settings/manifest.json
+2
packages/core-extensions/src/settings/manifest.json
+59
packages/core-extensions/src/settings/webpackModules/settings.ts
+59
packages/core-extensions/src/settings/webpackModules/settings.ts
···
1
+
import { SettingsSection, Settings as SettingsType } from "@moonlight-mod/types/coreExtensions/settings";
2
+
import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators";
3
+
4
+
export const Settings: SettingsType = {
5
+
ourSections: [],
6
+
sectionNames: [],
7
+
sectionMenuItems: {},
8
+
9
+
addSection: (section, label, element, color = null, pos, notice, onClick) => {
10
+
const data: SettingsSection = {
11
+
section,
12
+
label,
13
+
color,
14
+
element,
15
+
pos: pos ?? -4,
16
+
notice: notice,
17
+
onClick: onClick ?? (() => UserSettingsModalActionCreators.open(section))
18
+
};
19
+
20
+
Settings.ourSections.push(data);
21
+
Settings.sectionNames.push(section);
22
+
return data;
23
+
},
24
+
addSectionMenuItems(section, ...newItems) {
25
+
const data = Settings.ourSections.find((x) => x.section === section);
26
+
if (!data || !("element" in data)) throw new Error(`Could not find section "${section}"`);
27
+
(Settings.sectionMenuItems[section] ??= []).push(...newItems);
28
+
data._moonlight_submenu ??= () => Settings.sectionMenuItems[section];
29
+
},
30
+
31
+
addDivider: (pos = null) => {
32
+
Settings.ourSections.push({
33
+
section: "DIVIDER",
34
+
pos: pos === null ? -4 : pos
35
+
});
36
+
},
37
+
38
+
addHeader: function (label, pos = null) {
39
+
Settings.ourSections.push({
40
+
section: "HEADER",
41
+
label: label,
42
+
pos: pos === null ? -4 : pos
43
+
});
44
+
},
45
+
46
+
_mutateSections: (sections) => {
47
+
for (const section of Settings.ourSections) {
48
+
// Discord's `pos` only supports numbers, so lets call the function to get the position.
49
+
if (typeof section.pos === "function") {
50
+
section.pos = section.pos(sections);
51
+
}
52
+
sections.splice(section.pos < 0 ? sections.length + section.pos : section.pos, 0, section);
53
+
}
54
+
55
+
return sections;
56
+
}
57
+
};
58
+
59
+
export default Settings;
+9
-5
packages/core-extensions/src/spacepack/index.ts
+9
-5
packages/core-extensions/src/spacepack/index.ts
···
1
-
import { ExtensionWebExports, WebpackModuleFunc } from "@moonlight-mod/types";
2
-
import webpackModule from "./webpackModule";
1
+
import { ExtensionWebExports } from "@moonlight-mod/types";
2
+
import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack";
3
+
4
+
declare global {
5
+
interface Window {
6
+
spacepack: Spacepack;
7
+
}
8
+
}
3
9
4
10
export const webpackModules: ExtensionWebExports["webpackModules"] = {
5
11
spacepack: {
6
-
entrypoint: true,
7
-
// Assert the type because we're adding extra fields to require
8
-
run: webpackModule as WebpackModuleFunc
12
+
entrypoint: true
9
13
}
10
14
};
+3
packages/core-extensions/src/spacepack/manifest.json
+3
packages/core-extensions/src/spacepack/manifest.json
···
1
1
{
2
+
"$schema": "https://moonlight-mod.github.io/manifest.schema.json",
2
3
"id": "spacepack",
4
+
"apiLevel": 2,
3
5
"meta": {
4
6
"name": "Spacepack",
5
7
"tagline": "Search utilities across all Webpack modules",
···
8
10
},
9
11
"settings": {
10
12
"addToGlobalScope": {
13
+
"advice": "reload",
11
14
"displayName": "Add to global scope",
12
15
"description": "Populates window.spacepack for easier usage in DevTools",
13
16
"type": "boolean",
-172
packages/core-extensions/src/spacepack/webpackModule.ts
-172
packages/core-extensions/src/spacepack/webpackModule.ts
···
1
-
import { WebpackModuleFunc, WebpackModule } from "@moonlight-mod/types";
2
-
import { Spacepack } from "@moonlight-mod/types/coreExtensions";
3
-
import { WebpackRequireType } from "@moonlight-mod/types/discord/webpack";
4
-
5
-
declare global {
6
-
interface Window {
7
-
spacepack: Spacepack;
8
-
}
9
-
}
10
-
11
-
export default (module: any, exports: any, require: WebpackRequireType) => {
12
-
const cache = require.c;
13
-
const modules = require.m;
14
-
15
-
const spacepack: Spacepack = {
16
-
require,
17
-
modules,
18
-
cache,
19
-
20
-
inspect: (module: number | string) => {
21
-
if (typeof module === "number") {
22
-
module = module.toString();
23
-
}
24
-
25
-
if (!(module in modules)) {
26
-
return null;
27
-
}
28
-
29
-
const func = modules[module];
30
-
if (func.__moonlight === true) {
31
-
return func;
32
-
}
33
-
34
-
const funcStr = func.toString();
35
-
36
-
return new Function(
37
-
"module",
38
-
"exports",
39
-
"require",
40
-
`(${funcStr}).apply(this, arguments)\n` +
41
-
`//# sourceURL=Webpack-Module-${module}`
42
-
) as WebpackModuleFunc;
43
-
},
44
-
45
-
findByCode: (...args: (string | RegExp)[]) => {
46
-
return Object.entries(modules)
47
-
.filter(
48
-
([id, mod]) =>
49
-
!args.some(
50
-
(item) =>
51
-
!(item instanceof RegExp
52
-
? item.test(mod.toString())
53
-
: mod.toString().indexOf(item) !== -1)
54
-
)
55
-
)
56
-
.map(([id]) => {
57
-
//if (!(id in cache)) require(id);
58
-
//return cache[id];
59
-
60
-
let exports;
61
-
try {
62
-
exports = require(id);
63
-
} catch (e) {
64
-
console.error(e);
65
-
debugger;
66
-
}
67
-
68
-
return {
69
-
id,
70
-
exports
71
-
};
72
-
})
73
-
.filter((item) => item != null);
74
-
},
75
-
76
-
findByExports: (...args: string[]) => {
77
-
return Object.entries(cache)
78
-
.filter(
79
-
([id, { exports }]) =>
80
-
!args.some(
81
-
(item) =>
82
-
!(
83
-
exports != undefined &&
84
-
exports != window &&
85
-
(exports?.[item] ||
86
-
exports?.default?.[item] ||
87
-
exports?.Z?.[item] ||
88
-
exports?.ZP?.[item])
89
-
)
90
-
)
91
-
)
92
-
.map((item) => item[1])
93
-
.reduce<WebpackModule[]>((prev, curr) => {
94
-
if (!prev.includes(curr)) prev.push(curr);
95
-
return prev;
96
-
}, []);
97
-
},
98
-
99
-
findObjectFromKey: (exports: Record<string, any>, key: string) => {
100
-
let subKey;
101
-
if (key.indexOf(".") > -1) {
102
-
const splitKey = key.split(".");
103
-
key = splitKey[0];
104
-
subKey = splitKey[1];
105
-
}
106
-
for (const exportKey in exports) {
107
-
const obj = exports[exportKey];
108
-
if (obj && obj[key] !== undefined) {
109
-
if (subKey) {
110
-
if (obj[key][subKey]) return obj;
111
-
} else {
112
-
return obj;
113
-
}
114
-
}
115
-
}
116
-
return null;
117
-
},
118
-
119
-
findObjectFromValue: (exports: Record<string, any>, value: any) => {
120
-
for (const exportKey in exports) {
121
-
const obj = exports[exportKey];
122
-
if (obj == value) return obj;
123
-
for (const subKey in obj) {
124
-
if (obj && obj[subKey] == value) {
125
-
return obj;
126
-
}
127
-
}
128
-
}
129
-
return null;
130
-
},
131
-
132
-
findObjectFromKeyValuePair: (
133
-
exports: Record<string, any>,
134
-
key: string,
135
-
value: any
136
-
) => {
137
-
for (const exportKey in exports) {
138
-
const obj = exports[exportKey];
139
-
if (obj && obj[key] == value) {
140
-
return obj;
141
-
}
142
-
}
143
-
return null;
144
-
},
145
-
146
-
findFunctionByStrings: (
147
-
exports: Record<string, any>,
148
-
...strings: (string | RegExp)[]
149
-
) => {
150
-
return (
151
-
Object.entries(exports).filter(
152
-
([index, func]) =>
153
-
typeof func === "function" &&
154
-
!strings.some(
155
-
(query) =>
156
-
!(query instanceof RegExp
157
-
? func.toString().match(query)
158
-
: func.toString().includes(query))
159
-
)
160
-
)?.[0]?.[1] ?? null
161
-
);
162
-
}
163
-
};
164
-
165
-
module.exports = spacepack;
166
-
167
-
if (
168
-
moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true
169
-
) {
170
-
window.spacepack = spacepack;
171
-
}
172
-
};
+230
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
+230
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
···
1
+
import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "@moonlight-mod/types";
2
+
import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack";
3
+
import { processFind, testFind } from "@moonlight-mod/core/util/patch";
4
+
5
+
const webpackRequire = require as unknown as WebpackRequireType;
6
+
const cache = webpackRequire.c;
7
+
const modules = webpackRequire.m;
8
+
9
+
const logger = moonlight.getLogger("spacepack");
10
+
11
+
export const spacepack: Spacepack = {
12
+
require: webpackRequire,
13
+
modules,
14
+
cache,
15
+
16
+
inspect: (module: number | string) => {
17
+
if (typeof module === "number") {
18
+
module = module.toString();
19
+
}
20
+
21
+
if (module in moonlight.moonmap.modules) {
22
+
module = moonlight.moonmap.modules[module];
23
+
}
24
+
25
+
if (!(module in modules)) {
26
+
return null;
27
+
}
28
+
29
+
const func = modules[module];
30
+
if (func.__moonlight === true) {
31
+
return func;
32
+
}
33
+
34
+
const funcStr = func.toString();
35
+
36
+
return new Function(
37
+
"module",
38
+
"exports",
39
+
"require",
40
+
`(${funcStr}).apply(this, arguments)\n` + `//# sourceURL=Webpack-Module/${module.slice(0, 3)}/${module}`
41
+
) as WebpackModuleFunc;
42
+
},
43
+
44
+
findByCode: (...args: (string | RegExp)[]) => {
45
+
const ret = Object.entries(modules)
46
+
.filter(([id, mod]) => !args.some((item) => !testFind(mod.toString(), processFind(item))))
47
+
.map(([id]) => {
48
+
//if (!(id in cache)) require(id);
49
+
//return cache[id];
50
+
51
+
let exports;
52
+
try {
53
+
exports = require(id);
54
+
} catch (e) {
55
+
logger.error(`findByCode: Error requiring module "${id}": `, args, e);
56
+
}
57
+
58
+
return {
59
+
id,
60
+
exports
61
+
};
62
+
})
63
+
.filter((item) => item !== null);
64
+
65
+
if (ret.length === 0) {
66
+
logger.warn("findByCode: Got zero results for", args, new Error().stack!.substring(5));
67
+
}
68
+
69
+
return ret;
70
+
},
71
+
72
+
findByExports: (...args: string[]) => {
73
+
return Object.entries(cache)
74
+
.filter(
75
+
([id, { exports }]) =>
76
+
!args.some(
77
+
(item) =>
78
+
!(
79
+
exports !== undefined &&
80
+
exports !== window &&
81
+
(exports?.[item] || exports?.default?.[item] || exports?.Z?.[item] || exports?.ZP?.[item])
82
+
)
83
+
)
84
+
)
85
+
.map((item) => item[1])
86
+
.reduce<WebpackModule[]>((prev, curr) => {
87
+
if (!prev.includes(curr)) prev.push(curr);
88
+
return prev;
89
+
}, []);
90
+
},
91
+
92
+
findObjectFromKey: (exports: Record<string, any>, key: string) => {
93
+
let ret = null;
94
+
let subKey;
95
+
if (key.indexOf(".") > -1) {
96
+
const splitKey = key.split(".");
97
+
key = splitKey[0];
98
+
subKey = splitKey[1];
99
+
}
100
+
for (const exportKey in exports) {
101
+
const obj = exports[exportKey];
102
+
if (obj && obj[key] !== undefined) {
103
+
if (subKey) {
104
+
if (obj[key][subKey]) {
105
+
ret = obj;
106
+
break;
107
+
}
108
+
} else {
109
+
ret = obj;
110
+
break;
111
+
}
112
+
}
113
+
}
114
+
115
+
if (ret == null) {
116
+
logger.warn("Failed to find object by key", key, "in", exports, new Error().stack!.substring(5));
117
+
}
118
+
119
+
return ret;
120
+
},
121
+
122
+
findObjectFromValue: (exports: Record<string, any>, value: any) => {
123
+
let ret = null;
124
+
for (const exportKey in exports) {
125
+
const obj = exports[exportKey];
126
+
// eslint-disable-next-line eqeqeq
127
+
if (obj == value) {
128
+
ret = obj;
129
+
break;
130
+
}
131
+
for (const subKey in obj) {
132
+
// eslint-disable-next-line eqeqeq
133
+
if (obj && obj[subKey] == value) {
134
+
ret = obj;
135
+
break;
136
+
}
137
+
}
138
+
}
139
+
140
+
if (ret == null) {
141
+
logger.warn("Failed to find object by value", value, "in", exports, new Error().stack!.substring(5));
142
+
}
143
+
144
+
return ret;
145
+
},
146
+
147
+
findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => {
148
+
let ret = null;
149
+
for (const exportKey in exports) {
150
+
const obj = exports[exportKey];
151
+
// eslint-disable-next-line eqeqeq
152
+
if (obj && obj[key] == value) {
153
+
ret = obj;
154
+
break;
155
+
}
156
+
}
157
+
158
+
if (ret == null) {
159
+
logger.warn(
160
+
"Failed to find object by key value pair",
161
+
key,
162
+
value,
163
+
"in",
164
+
exports,
165
+
new Error().stack!.substring(5)
166
+
);
167
+
}
168
+
169
+
return null;
170
+
},
171
+
172
+
findFunctionByStrings: (exports: Record<string, any>, ...strings: (string | RegExp)[]) => {
173
+
const ret =
174
+
Object.entries(exports).filter(
175
+
([index, func]) =>
176
+
typeof func === "function" && !strings.some((query) => !testFind(func.toString(), processFind(query)))
177
+
)?.[0]?.[1] ?? null;
178
+
179
+
if (ret == null) {
180
+
logger.warn("Failed to find function by strings", strings, "in", exports, new Error().stack!.substring(5));
181
+
}
182
+
183
+
return ret;
184
+
},
185
+
186
+
lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => {
187
+
chunk = processFind(chunk);
188
+
module = processFind(module);
189
+
190
+
const mod = Array.isArray(find) ? spacepack.findByCode(...find) : spacepack.findByCode(find);
191
+
if (mod.length < 1) {
192
+
logger.warn("lazyLoad: Module find failed", find, chunk, module, new Error().stack!.substring(5));
193
+
return Promise.reject("Module find failed");
194
+
}
195
+
196
+
const findId = mod[0].id;
197
+
const findCode = webpackRequire.m[findId].toString().replace(/\n/g, "");
198
+
199
+
let chunkIds;
200
+
if (chunk.flags.includes("g")) {
201
+
chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id);
202
+
} else {
203
+
const match = findCode.match(chunk);
204
+
if (match) chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id);
205
+
}
206
+
207
+
if (!chunkIds || chunkIds.length === 0) {
208
+
logger.warn("lazyLoad: Chunk ID match failed", find, chunk, module, new Error().stack!.substring(5));
209
+
return Promise.reject("Chunk ID match failed");
210
+
}
211
+
212
+
const moduleId = findCode.match(module)?.[1];
213
+
if (!moduleId) {
214
+
logger.warn("lazyLoad: Module ID match failed", find, chunk, module, new Error().stack!.substring(5));
215
+
return Promise.reject("Module ID match failed");
216
+
}
217
+
218
+
return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => webpackRequire(moduleId));
219
+
},
220
+
221
+
filterReal: (modules: WebpackModule[]) => {
222
+
return modules.filter((module) => module.id.toString().match(/^\d+$/));
223
+
}
224
+
};
225
+
226
+
if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) {
227
+
window.spacepack = spacepack;
228
+
}
229
+
230
+
export default spacepack;
+4
-1
packages/core-extensions/tsconfig.json
+4
-1
packages/core-extensions/tsconfig.json
+10
-3
packages/injector/package.json
+10
-3
packages/injector/package.json
···
1
1
{
2
2
"name": "@moonlight-mod/injector",
3
3
"private": true,
4
+
"engines": {
5
+
"node": ">=22",
6
+
"pnpm": ">=10",
7
+
"npm": "pnpm",
8
+
"yarn": "pnpm"
9
+
},
4
10
"dependencies": {
5
-
"@moonlight-mod/types": "workspace:*",
6
-
"@moonlight-mod/core": "workspace:*"
7
-
}
11
+
"@moonlight-mod/core": "workspace:*",
12
+
"@moonlight-mod/types": "workspace:*"
13
+
},
14
+
"engineStrict": true
8
15
}
+216
-55
packages/injector/src/index.ts
+216
-55
packages/injector/src/index.ts
···
2
2
BrowserWindowConstructorOptions,
3
3
BrowserWindow as ElectronBrowserWindow,
4
4
ipcMain,
5
-
app,
6
-
ipcRenderer
5
+
app
7
6
} from "electron";
8
-
import Module from "module";
9
-
import { constants } from "@moonlight-mod/types";
10
-
import { readConfig } from "@moonlight-mod/core/config";
7
+
import Module from "node:module";
8
+
import { constants, MoonlightBranch } from "@moonlight-mod/types";
9
+
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
11
10
import { getExtensions } from "@moonlight-mod/core/extension";
12
-
import Logger from "@moonlight-mod/core/util/logger";
13
-
import {
14
-
loadExtensions,
15
-
loadProcessedExtensions
16
-
} from "core/src/extension/loader";
17
-
import EventEmitter from "events";
11
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
12
+
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
13
+
import EventEmitter from "node:events";
14
+
import path from "node:path";
15
+
import persist from "@moonlight-mod/core/persist";
16
+
import createFS from "@moonlight-mod/core/fs";
17
+
import { getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
18
+
import { getConfigPath, getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
18
19
19
-
let oldPreloadPath = "";
20
+
const logger = new Logger("injector");
21
+
22
+
let oldPreloadPath: string | undefined;
20
23
let corsAllow: string[] = [];
24
+
let blockedUrls: RegExp[] = [];
25
+
let injectorConfig: InjectorConfig | undefined;
26
+
27
+
const scriptUrls = ["web.", "sentry."];
28
+
const blockedScripts = new Set<string>();
21
29
22
30
ipcMain.on(constants.ipcGetOldPreloadPath, (e) => {
23
31
e.returnValue = oldPreloadPath;
24
32
});
33
+
25
34
ipcMain.on(constants.ipcGetAppData, (e) => {
26
35
e.returnValue = app.getPath("appData");
27
36
});
37
+
ipcMain.on(constants.ipcGetInjectorConfig, (e) => {
38
+
e.returnValue = injectorConfig;
39
+
});
28
40
ipcMain.handle(constants.ipcMessageBox, (_, opts) => {
29
41
electron.dialog.showMessageBoxSync(opts);
30
42
});
···
32
44
corsAllow = list;
33
45
});
34
46
35
-
function patchCsp(headers: Record<string, string[]>) {
36
-
const directives = [
37
-
"style-src",
38
-
"connect-src",
39
-
"img-src",
40
-
"font-src",
41
-
"media-src",
42
-
"worker-src",
43
-
"prefetch-src"
44
-
];
45
-
const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"];
47
+
const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g;
48
+
const reMatchPattern = /^(?<scheme>\*|[a-z][a-z0-9+.-]*):\/\/(?<host>.+?)\/(?<path>.+)?$/;
49
+
50
+
const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&");
51
+
ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => {
52
+
// We compile the patterns into a RegExp based on a janky match pattern-like syntax
53
+
const compiled = list
54
+
.map((pattern) => {
55
+
const match = pattern.match(reMatchPattern);
56
+
if (!match?.groups) return;
57
+
58
+
let regex = "";
59
+
if (match.groups.scheme === "*") regex += ".+?";
60
+
else regex += escapeRegExp(match.groups.scheme);
61
+
regex += ":\\/\\/";
62
+
63
+
const parts = match.groups.host.split(".");
64
+
if (parts[0] === "*") {
65
+
parts.shift();
66
+
regex += "(?:.+?\\.)?";
67
+
}
68
+
regex += escapeRegExp(parts.join("."));
69
+
70
+
regex += "\\/" + escapeRegExp(match.groups.path).replace("\\*", ".*?");
71
+
72
+
return new RegExp("^" + regex + "$");
73
+
})
74
+
.filter(Boolean) as RegExp[];
75
+
76
+
blockedUrls = compiled;
77
+
});
78
+
79
+
function patchCsp(headers: Record<string, string[]>, extensionCspOverrides: Record<string, string[]>) {
80
+
const directives = ["script-src", "style-src", "connect-src", "img-src", "font-src", "media-src", "worker-src"];
81
+
const values = ["*", "blob:", "data:", "'unsafe-inline'", "'unsafe-eval'", "disclip:"];
46
82
47
83
const csp = "content-security-policy";
48
84
if (headers[csp] == null) return;
···
61
97
parts[directive] = values;
62
98
}
63
99
100
+
for (const [directive, urls] of Object.entries(extensionCspOverrides)) {
101
+
parts[directive] ??= [];
102
+
parts[directive].push(...urls);
103
+
}
104
+
64
105
const stringified = Object.entries<string[]>(parts)
65
106
.map(([key, value]) => {
66
107
return `${key} ${value.join(" ")}`;
···
71
112
72
113
class BrowserWindow extends ElectronBrowserWindow {
73
114
constructor(opts: BrowserWindowConstructorOptions) {
74
-
oldPreloadPath = opts.webPreferences!.preload!;
75
-
opts.webPreferences!.preload = require.resolve("./node-preload.js");
115
+
const isMainWindow = opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1;
116
+
117
+
if (isMainWindow) {
118
+
if (!oldPreloadPath) oldPreloadPath = opts.webPreferences!.preload;
119
+
opts.webPreferences!.preload = require.resolve("./node-preload.js");
120
+
}
121
+
122
+
// Event for modifying window options
123
+
moonlightHost.events.emit("window-options", opts, isMainWindow);
76
124
77
125
super(opts);
126
+
127
+
// Event for when a window is created
128
+
moonlightHost.events.emit("window-created", this, isMainWindow);
129
+
130
+
const extensionCspOverrides: Record<string, string[]> = {};
131
+
132
+
{
133
+
const extCsps = moonlightHost.processedExtensions.extensions.map((x) => x.manifest.csp ?? {});
134
+
for (const csp of extCsps) {
135
+
for (const [directive, urls] of Object.entries(csp)) {
136
+
extensionCspOverrides[directive] ??= [];
137
+
extensionCspOverrides[directive].push(...urls);
138
+
}
139
+
}
140
+
}
141
+
78
142
this.webContents.session.webRequest.onHeadersReceived((details, cb) => {
79
143
if (details.responseHeaders != null) {
80
-
if (details.resourceType == "mainFrame") {
81
-
patchCsp(details.responseHeaders);
144
+
// Patch CSP so things can use externally hosted assets
145
+
if (details.resourceType === "mainFrame") {
146
+
patchCsp(details.responseHeaders, extensionCspOverrides);
82
147
}
83
148
149
+
// Allow plugins to bypass CORS for specific URLs
84
150
if (corsAllow.some((x) => details.url.startsWith(x))) {
85
-
details.responseHeaders["access-control-allow-origin"] = ["*"];
151
+
if (!details.responseHeaders) details.responseHeaders = {};
152
+
153
+
// Work around HTTP header case sensitivity by reusing the header name if it exists
154
+
// https://github.com/moonlight-mod/moonlight/issues/201
155
+
const fallback = "access-control-allow-origin";
156
+
const key = Object.keys(details.responseHeaders).find((h) => h.toLowerCase() === fallback) ?? fallback;
157
+
details.responseHeaders[key] = ["*"];
86
158
}
87
159
160
+
moonlightHost.events.emit("headers-received", details, isMainWindow);
161
+
88
162
cb({ cancel: false, responseHeaders: details.responseHeaders });
89
163
}
90
164
});
165
+
166
+
this.webContents.session.webRequest.onBeforeRequest((details, cb) => {
167
+
/*
168
+
In order to get moonlight loading to be truly async, we prevent Discord
169
+
from loading their scripts immediately. We block the requests, keep note
170
+
of their URLs, and then send them off to node-preload when we get all of
171
+
them. node-preload then loads node side, web side, and then recreates
172
+
the script elements to cause them to re-fetch.
173
+
174
+
The browser extension also does this, but in a background script (see
175
+
packages/browser/src/background.js - we should probably get this working
176
+
with esbuild someday).
177
+
*/
178
+
if (details.resourceType === "script" && isMainWindow) {
179
+
const url = new URL(details.url);
180
+
const hasUrl = scriptUrls.some((scriptUrl) => {
181
+
return (
182
+
details.url.includes(scriptUrl) &&
183
+
!url.searchParams.has("inj") &&
184
+
(url.host.endsWith("discord.com") || url.host.endsWith("discordapp.com"))
185
+
);
186
+
});
187
+
if (hasUrl) blockedScripts.add(details.url);
188
+
189
+
if (blockedScripts.size === scriptUrls.length) {
190
+
setTimeout(() => {
191
+
logger.debug("Kicking off node-preload");
192
+
this.webContents.send(constants.ipcNodePreloadKickoff, Array.from(blockedScripts));
193
+
blockedScripts.clear();
194
+
}, 0);
195
+
}
196
+
197
+
if (hasUrl) return cb({ cancel: true });
198
+
}
199
+
200
+
// Allow plugins to block some URLs,
201
+
// this is needed because multiple webRequest handlers cannot be registered at once
202
+
cb({ cancel: blockedUrls.some((u) => u.test(details.url)) });
203
+
});
91
204
}
92
205
}
93
206
94
-
export async function inject(asarPath: string) {
207
+
/*
208
+
Fun fact: esbuild transforms that BrowserWindow class statement into this:
209
+
210
+
var variableName = class extends electronImport.BrowserWindow {
211
+
...
212
+
}
213
+
214
+
This means that in production builds, variableName is minified, and for some
215
+
ungodly reason this breaks electron (because it needs to be named BrowserWindow).
216
+
Without it, random things fail and crash (like opening DevTools). There is no
217
+
esbuild option to preserve only a single name, so you get the next best thing:
218
+
*/
219
+
Object.defineProperty(BrowserWindow, "name", {
220
+
value: "BrowserWindow",
221
+
writable: false
222
+
});
223
+
224
+
type InjectorConfig = { disablePersist?: boolean; disableLoad?: boolean };
225
+
export async function inject(asarPath: string, _injectorConfig?: InjectorConfig) {
226
+
injectorConfig = _injectorConfig;
227
+
228
+
global.moonlightNodeSandboxed = {
229
+
fs: createFS(),
230
+
// These aren't supposed to be used from host
231
+
addCors() {},
232
+
addBlocked() {}
233
+
};
234
+
95
235
try {
96
-
const config = readConfig();
97
-
const extensions = getExtensions();
236
+
let config = await readConfig();
237
+
initLogger(config);
238
+
const extensions = await getExtensions();
239
+
const processedExtensions = await loadExtensions(extensions);
240
+
const moonlightDir = await getMoonlightDir();
241
+
const extensionsPath = await getExtensionsPath();
98
242
99
243
// Duplicated in node-preload... oops
100
244
function getConfig(ext: string) {
···
102
246
if (val == null || typeof val === "boolean") return undefined;
103
247
return val.config;
104
248
}
105
-
106
249
global.moonlightHost = {
250
+
get config() {
251
+
return config;
252
+
},
253
+
extensions,
254
+
processedExtensions,
107
255
asarPath,
108
-
config,
109
256
events: new EventEmitter(),
110
-
extensions,
111
-
processedExtensions: {
112
-
extensions: [],
113
-
dependencyGraph: new Map()
114
-
},
257
+
258
+
version: MOONLIGHT_VERSION,
259
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
115
260
116
261
getConfig,
117
-
getConfigOption: <T>(ext: string, name: string) => {
118
-
const config = getConfig(ext);
119
-
if (config == null) return undefined;
120
-
const option = config[name];
121
-
if (option == null) return undefined;
122
-
return option as T;
262
+
getConfigPath,
263
+
getConfigOption(ext, name) {
264
+
const manifest = getManifest(extensions, ext);
265
+
return getConfigOption(ext, name, config, manifest?.settings);
266
+
},
267
+
setConfigOption(ext, name, value) {
268
+
setConfigOption(config, ext, name, value);
269
+
this.writeConfig(config);
270
+
},
271
+
async writeConfig(newConfig) {
272
+
await writeConfig(newConfig);
273
+
config = newConfig;
123
274
},
124
-
getLogger: (id: string) => {
275
+
276
+
getLogger(id) {
125
277
return new Logger(id);
278
+
},
279
+
getMoonlightDir() {
280
+
return moonlightDir;
281
+
},
282
+
getExtensionDir: (ext: string) => {
283
+
return path.join(extensionsPath, ext);
126
284
}
127
285
};
128
286
129
287
patchElectron();
130
288
131
-
global.moonlightHost.processedExtensions = await loadExtensions(extensions);
132
289
await loadProcessedExtensions(global.moonlightHost.processedExtensions);
133
-
} catch (e) {
134
-
console.error("Failed to inject", e);
290
+
} catch (error) {
291
+
logger.error("Failed to inject:", error);
292
+
}
293
+
294
+
if (injectorConfig?.disablePersist !== true) {
295
+
persist(asarPath);
135
296
}
136
297
137
-
require(asarPath);
298
+
if (injectorConfig?.disableLoad !== true) {
299
+
// Need to do this instead of require() or it breaks require.main
300
+
// @ts-expect-error Module internals
301
+
Module._load(asarPath, Module, true);
302
+
}
138
303
}
139
304
140
305
function patchElectron() {
···
148
313
configurable: false
149
314
});
150
315
} else {
151
-
Object.defineProperty(
152
-
electronClone,
153
-
property,
154
-
Object.getOwnPropertyDescriptor(electron, property)!
155
-
);
316
+
Object.defineProperty(electronClone, property, Object.getOwnPropertyDescriptor(electron, property)!);
156
317
}
157
318
}
158
319
159
-
// exports is a getter only on Windows, let's do some cursed shit instead
320
+
// exports is a getter only on Windows, recreate export cache instead
160
321
const electronPath = require.resolve("electron");
161
322
const cachedElectron = require.cache[electronPath]!;
162
323
require.cache[electronPath] = new Module(cachedElectron.id, require.main);
+8
-1
packages/node-preload/package.json
+8
-1
packages/node-preload/package.json
···
1
1
{
2
2
"name": "@moonlight-mod/node-preload",
3
3
"private": true,
4
+
"engines": {
5
+
"node": ">=22",
6
+
"pnpm": ">=10",
7
+
"npm": "pnpm",
8
+
"yarn": "pnpm"
9
+
},
4
10
"dependencies": {
5
11
"@moonlight-mod/core": "workspace:*",
6
12
"@moonlight-mod/types": "workspace:*"
7
-
}
13
+
},
14
+
"engineStrict": true
8
15
}
+142
-45
packages/node-preload/src/index.ts
+142
-45
packages/node-preload/src/index.ts
···
1
1
import { webFrame, ipcRenderer, contextBridge } from "electron";
2
-
import fs from "fs";
3
-
import path from "path";
2
+
import fs from "node:fs";
3
+
import path from "node:path";
4
4
5
5
import { readConfig, writeConfig } from "@moonlight-mod/core/config";
6
-
import { ProcessedExtensions, constants } from "@moonlight-mod/types";
6
+
import { constants, MoonlightBranch } from "@moonlight-mod/types";
7
7
import { getExtensions } from "@moonlight-mod/core/extension";
8
-
import { getExtensionsPath } from "@moonlight-mod/core/util/data";
9
-
import Logger from "@moonlight-mod/core/util/logger";
10
-
import {
11
-
loadExtensions,
12
-
loadProcessedExtensions
13
-
} from "core/src/extension/loader";
8
+
import { getExtensionsPath, getMoonlightDir } from "@moonlight-mod/core/util/data";
9
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
10
+
import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
11
+
import createFS from "@moonlight-mod/core/fs";
12
+
import { registerCors, registerBlocked, getDynamicCors } from "@moonlight-mod/core/cors";
13
+
import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config";
14
+
import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event";
15
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
16
+
17
+
let initialized = false;
18
+
let logger: Logger;
19
+
20
+
function setCors() {
21
+
const data = getDynamicCors();
22
+
ipcRenderer.invoke(constants.ipcSetCorsList, data.cors);
23
+
ipcRenderer.invoke(constants.ipcSetBlockedList, data.blocked);
24
+
}
14
25
15
26
async function injectGlobals() {
16
-
const config = readConfig();
17
-
const extensions = getExtensions();
18
-
const processed = await loadExtensions(extensions);
27
+
global.moonlightNodeSandboxed = {
28
+
fs: createFS(),
29
+
addCors(url) {
30
+
registerCors(url);
31
+
if (initialized) setCors();
32
+
},
33
+
addBlocked(url) {
34
+
registerBlocked(url);
35
+
if (initialized) setCors();
36
+
}
37
+
};
19
38
20
-
function getConfig(ext: string) {
21
-
const val = config.extensions[ext];
22
-
if (val == null || typeof val === "boolean") return undefined;
23
-
return val.config;
24
-
}
39
+
let config = await readConfig();
40
+
initLogger(config);
41
+
logger = new Logger("node-preload");
42
+
43
+
const extensions = await getExtensions();
44
+
const processedExtensions = await loadExtensions(extensions);
45
+
const moonlightDir = await getMoonlightDir();
46
+
const extensionsPath = await getExtensionsPath();
25
47
26
48
global.moonlightNode = {
27
-
config,
28
-
extensions: getExtensions(),
29
-
processedExtensions: processed,
49
+
get config() {
50
+
return config;
51
+
},
52
+
extensions,
53
+
processedExtensions,
30
54
nativesCache: {},
55
+
isBrowser: false,
56
+
events: createEventEmitter<NodeEventType, NodeEventPayloads>(),
31
57
32
-
getConfig,
33
-
getConfigOption: <T>(ext: string, name: string) => {
34
-
const config = getConfig(ext);
35
-
if (config == null) return undefined;
36
-
const option = config[name];
37
-
if (option == null) return undefined;
38
-
return option as T;
58
+
version: MOONLIGHT_VERSION,
59
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
60
+
61
+
getConfig(ext) {
62
+
return getConfig(ext, config);
63
+
},
64
+
getConfigOption(ext, name) {
65
+
const manifest = getManifest(extensions, ext);
66
+
return getConfigOption(ext, name, config, manifest?.settings);
67
+
},
68
+
async setConfigOption(ext, name, value) {
69
+
setConfigOption(config, ext, name, value);
70
+
await this.writeConfig(config);
71
+
},
72
+
async writeConfig(newConfig) {
73
+
await writeConfig(newConfig);
74
+
config = newConfig;
75
+
this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig);
39
76
},
77
+
40
78
getNatives: (ext: string) => global.moonlightNode.nativesCache[ext],
41
79
getLogger: (id: string) => {
42
80
return new Logger(id);
43
81
},
44
-
45
-
getExtensionDir: (ext: string) => {
46
-
const extPath = getExtensionsPath();
47
-
return path.join(extPath, ext);
82
+
getMoonlightDir() {
83
+
return moonlightDir;
48
84
},
49
-
writeConfig
85
+
getExtensionDir: (ext: string) => {
86
+
return path.join(extensionsPath, ext);
87
+
}
50
88
};
51
89
52
-
await loadProcessedExtensions(processed);
90
+
await loadProcessedExtensions(processedExtensions);
53
91
contextBridge.exposeInMainWorld("moonlightNode", moonlightNode);
54
92
55
-
const extCors = moonlightNode.processedExtensions.extensions
56
-
.map((x) => x.manifest.cors ?? [])
57
-
.flat();
93
+
const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []);
94
+
for (const cors of extCors) {
95
+
registerCors(cors);
96
+
}
58
97
59
98
for (const repo of moonlightNode.config.repositories) {
60
99
const url = new URL(repo);
61
100
url.pathname = "/";
62
-
extCors.push(url.toString());
101
+
registerCors(url.toString());
102
+
}
103
+
104
+
const extBlocked = moonlightNode.processedExtensions.extensions.flatMap((e) => e.manifest.blocked ?? []);
105
+
for (const blocked of extBlocked) {
106
+
registerBlocked(blocked);
63
107
}
64
108
65
-
ipcRenderer.invoke(constants.ipcSetCorsList, extCors);
109
+
setCors();
110
+
111
+
initialized = true;
66
112
}
67
113
68
114
async function loadPreload() {
69
115
const webPreloadPath = path.join(__dirname, "web-preload.js");
70
116
const webPreload = fs.readFileSync(webPreloadPath, "utf8");
71
117
await webFrame.executeJavaScript(webPreload);
118
+
119
+
const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }");
120
+
await func();
72
121
}
73
122
74
-
async function init(oldPreloadPath: string) {
123
+
async function init() {
75
124
try {
76
125
await injectGlobals();
77
126
await loadPreload();
···
82
131
message: message
83
132
});
84
133
}
134
+
}
85
135
86
-
// Let Discord start even if we fail
136
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
137
+
const isOverlay = window.location.href.indexOf("discord_overlay") > -1;
138
+
139
+
if (isOverlay) {
140
+
// The overlay has an inline script tag to call to DiscordNative, so we'll
141
+
// just load it immediately. Somehow moonlight still loads in this env, I
142
+
// have no idea why - so I suspect it's just forwarding render calls or
143
+
// something from the original process
87
144
require(oldPreloadPath);
88
-
}
145
+
} else {
146
+
ipcRenderer.on(constants.ipcNodePreloadKickoff, (_, blockedScripts: string[]) => {
147
+
(async () => {
148
+
try {
149
+
await init();
150
+
logger.debug("Blocked scripts:", blockedScripts);
151
+
152
+
const oldPreloadPath: string = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath);
153
+
logger.debug("Old preload path:", oldPreloadPath);
154
+
if (oldPreloadPath) require(oldPreloadPath);
155
+
156
+
// Do this to get global.DiscordNative assigned
157
+
// @ts-expect-error Lying to discord_desktop_core
158
+
process.emit("loaded");
159
+
160
+
function replayScripts() {
161
+
const scripts = [...document.querySelectorAll("script")].filter(
162
+
(script) => script.src && blockedScripts.some((url) => url.includes(script.src))
163
+
);
89
164
90
-
const oldPreloadPath: string = ipcRenderer.sendSync(
91
-
constants.ipcGetOldPreloadPath
92
-
);
93
-
init(oldPreloadPath);
165
+
blockedScripts.reverse();
166
+
for (const url of blockedScripts) {
167
+
if (url.includes("/sentry.")) continue;
168
+
169
+
const script = scripts.find((script) => url.includes(script.src))!;
170
+
const newScript = document.createElement("script");
171
+
for (const attr of script.attributes) {
172
+
if (attr.name === "src") attr.value += "?inj";
173
+
newScript.setAttribute(attr.name, attr.value);
174
+
}
175
+
script.remove();
176
+
document.documentElement.appendChild(newScript);
177
+
}
178
+
}
179
+
180
+
if (document.readyState === "complete") {
181
+
replayScripts();
182
+
} else {
183
+
window.addEventListener("load", replayScripts);
184
+
}
185
+
} catch (e) {
186
+
logger.error("Error restoring original scripts:", e);
187
+
}
188
+
})();
189
+
});
190
+
}
+4
-1
packages/node-preload/tsconfig.json
+4
-1
packages/node-preload/tsconfig.json
+16
-7
packages/types/package.json
+16
-7
packages/types/package.json
···
1
1
{
2
2
"name": "@moonlight-mod/types",
3
-
"version": "1.0.0",
4
-
"main": "./src/index.ts",
5
-
"types": "./src/index.ts",
3
+
"version": "1.3.17",
6
4
"exports": {
7
5
".": "./src/index.ts",
6
+
"./import": "./src/import.d.ts",
8
7
"./*": "./src/*.ts"
9
8
},
9
+
"main": "./src/index.ts",
10
+
"types": "./src/index.ts",
11
+
"engineStrict": false,
12
+
"engines": {
13
+
"node": ">=22",
14
+
"pnpm": ">=10",
15
+
"npm": "pnpm",
16
+
"yarn": "pnpm"
17
+
},
10
18
"dependencies": {
11
-
"@types/flux": "^3.1.12",
12
-
"@types/node": "^20.6.2",
13
-
"@types/react": "^18.2.22",
14
-
"csstype": "^3.1.2",
19
+
"@moonlight-mod/lunast": "^1.0.1",
20
+
"@moonlight-mod/mappings": "^1.1.25",
21
+
"@moonlight-mod/moonmap": "^1.0.5",
22
+
"@types/react": "^18.3.10",
23
+
"csstype": "^3.1.3",
15
24
"standalone-electron-types": "^1.0.0"
16
25
}
17
26
}
+72
-5
packages/types/src/config.ts
+72
-5
packages/types/src/config.ts
···
6
6
patchAll?: boolean;
7
7
};
8
8
9
-
export type ConfigExtensions =
10
-
| { [key: string]: boolean }
11
-
| { [key: string]: ConfigExtension };
9
+
export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension };
12
10
13
11
export type ConfigExtension = {
14
12
enabled: boolean;
···
19
17
Boolean = "boolean",
20
18
Number = "number",
21
19
String = "string",
20
+
MultilineString = "multilinestring",
22
21
Select = "select",
22
+
MultiSelect = "multiselect",
23
23
List = "list",
24
24
Dictionary = "dictionary",
25
25
Custom = "custom"
26
26
}
27
27
28
+
export type SelectOption =
29
+
| string
30
+
| {
31
+
value: string;
32
+
label: string;
33
+
};
34
+
28
35
export type BooleanSettingType = {
36
+
/**
37
+
* Displays as a simple switch.
38
+
*/
29
39
type: ExtensionSettingType.Boolean;
30
40
default?: boolean;
31
41
};
32
42
33
43
export type NumberSettingType = {
44
+
/**
45
+
* Displays as a simple slider.
46
+
*/
34
47
type: ExtensionSettingType.Number;
35
48
default?: number;
36
49
min?: number;
···
38
51
};
39
52
40
53
export type StringSettingType = {
54
+
/**
55
+
* Displays as a single line string input.
56
+
*/
41
57
type: ExtensionSettingType.String;
58
+
default?: string;
59
+
};
60
+
61
+
export type MultilineTextInputSettingType = {
62
+
/**
63
+
* Displays as a multiple line string input.
64
+
*/
65
+
type: ExtensionSettingType.MultilineString;
42
66
default?: string;
43
67
};
44
68
45
69
export type SelectSettingType = {
70
+
/**
71
+
* A dropdown to pick between one of many values.
72
+
*/
46
73
type: ExtensionSettingType.Select;
74
+
options: SelectOption[];
75
+
default?: string;
76
+
};
77
+
78
+
export type MultiSelectSettingType = {
79
+
/**
80
+
* A dropdown to pick multiple values.
81
+
*/
82
+
type: ExtensionSettingType.MultiSelect;
47
83
options: string[];
48
-
default?: string;
84
+
default?: string[];
49
85
};
50
86
51
87
export type ListSettingType = {
88
+
/**
89
+
* A list of strings that the user can add or remove from.
90
+
*/
52
91
type: ExtensionSettingType.List;
53
-
options?: string[];
54
92
default?: string[];
55
93
};
56
94
57
95
export type DictionarySettingType = {
96
+
/**
97
+
* A dictionary (key-value pair) that the user can add or remove from.
98
+
*/
58
99
type: ExtensionSettingType.Dictionary;
59
100
default?: Record<string, string>;
60
101
};
61
102
62
103
export type CustomSettingType = {
104
+
/**
105
+
* A custom component.
106
+
* You can use the registerConfigComponent function in the Moonbase API to register a React component to render here.
107
+
*/
63
108
type: ExtensionSettingType.Custom;
64
109
default?: any;
65
110
};
66
111
112
+
export enum ExtensionSettingsAdvice {
113
+
None = "none",
114
+
Reload = "reload",
115
+
Restart = "restart"
116
+
}
117
+
67
118
export type ExtensionSettingsManifest = {
119
+
/**
120
+
* A human friendly name for the setting.
121
+
*/
68
122
displayName?: string;
123
+
124
+
/**
125
+
* A longer description for the setting.
126
+
* Markdown is not supported.
127
+
*/
69
128
description?: string;
129
+
130
+
/**
131
+
* The "advice" to give upon changing this setting.
132
+
* Can be configured to reload the client, restart the client, or do nothing.
133
+
*/
134
+
advice?: ExtensionSettingsAdvice;
70
135
} & (
71
136
| BooleanSettingType
72
137
| NumberSettingType
73
138
| StringSettingType
139
+
| MultilineTextInputSettingType
74
140
| SelectSettingType
141
+
| MultiSelectSettingType
75
142
| ListSettingType
76
143
| DictionarySettingType
77
144
| CustomSettingType
+11
packages/types/src/constants.ts
+11
packages/types/src/constants.ts
···
2
2
export const distDir = "dist";
3
3
export const coreExtensionsDir = "core-extensions";
4
4
export const repoUrlFile = ".moonlight-repo-url";
5
+
export const installedVersionFile = ".moonlight-installed-version";
5
6
7
+
export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff";
6
8
export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath";
9
+
7
10
export const ipcGetAppData = "_moonlight_getAppData";
11
+
export const ipcGetInjectorConfig = "_moonlight_getInjectorConfig";
8
12
export const ipcMessageBox = "_moonlight_messageBox";
9
13
export const ipcSetCorsList = "_moonlight_setCorsList";
14
+
export const ipcSetBlockedList = "_moonlight_setBlockedList";
15
+
16
+
export const apiLevel = 2;
17
+
18
+
export const mainRepo = "https://moonlight-mod.github.io/extensions-dist/repo.json";
19
+
// If you're updating this, update `defaultConfig` in core as well
20
+
export const builtinExtensions = ["moonbase", "disableSentry", "noTrack", "noHideToken"];
+30
packages/types/src/core/event.ts
+30
packages/types/src/core/event.ts
···
1
+
import { Config } from "../config";
2
+
import { WebpackModuleFunc, WebpackRequireType } from "../discord";
3
+
4
+
export interface MoonlightEventEmitter<EventId extends string = string, EventData = Record<EventId, any>> {
5
+
dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => void;
6
+
addEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void;
7
+
removeEventListener: <Id extends keyof EventData>(id: Id, cb: (data: EventData[Id]) => void) => void;
8
+
}
9
+
10
+
export enum WebEventType {
11
+
ChunkLoad = "chunkLoad",
12
+
ExtensionLoad = "extensionLoad"
13
+
}
14
+
15
+
export type WebEventPayloads = {
16
+
[WebEventType.ChunkLoad]: {
17
+
chunkId?: number[];
18
+
modules: { [id: string]: WebpackModuleFunc };
19
+
require?: (require: WebpackRequireType) => any;
20
+
};
21
+
[WebEventType.ExtensionLoad]: string;
22
+
};
23
+
24
+
export enum NodeEventType {
25
+
ConfigSaved = "configSaved"
26
+
}
27
+
28
+
export type NodeEventPayloads = {
29
+
[NodeEventType.ConfigSaved]: Config;
30
+
};
+13
packages/types/src/coreExtensions/appPanels.ts
+13
packages/types/src/coreExtensions/appPanels.ts
···
1
+
export type AppPanels = {
2
+
/**
3
+
* Registers a new panel to be displayed around the user/voice controls.
4
+
* @param section A unique name for your section
5
+
* @param element A React component
6
+
*/
7
+
addPanel: (section: string, element: React.FC<any>) => void;
8
+
9
+
/**
10
+
* @private
11
+
*/
12
+
getPanels: (el: React.FC<any>) => React.ReactNode;
13
+
};
+204
packages/types/src/coreExtensions/commands.ts
+204
packages/types/src/coreExtensions/commands.ts
···
1
+
export const APPLICATION_ID = "-3";
2
+
3
+
export enum CommandType {
4
+
CHAT = 1,
5
+
MESSAGE = 3,
6
+
PRIMARY_ENTRY_POINT = 4,
7
+
USER = 2
8
+
}
9
+
10
+
export enum InputType {
11
+
BOT = 3,
12
+
BUILT_IN = 0,
13
+
BUILT_IN_INTEGRATION = 2,
14
+
BUILT_IN_TEXT = 1,
15
+
PLACEHOLDER = 4
16
+
}
17
+
18
+
export enum OptionType {
19
+
SUB_COMMAND = 1,
20
+
SUB_COMMAND_GROUP = 2,
21
+
STRING = 3,
22
+
INTEGER = 4,
23
+
BOOLEAN = 5,
24
+
USER = 6,
25
+
CHANNEL = 7,
26
+
ROLE = 8,
27
+
MENTIONABLE = 9,
28
+
NUMBER = 10,
29
+
ATTACHMENT = 11
30
+
}
31
+
32
+
export enum ChannelType {
33
+
GUILD_TEXT = 0,
34
+
DM = 1,
35
+
GUILD_VOICE = 2,
36
+
GROUP_DM = 3,
37
+
GUILD_CATEGORY = 4,
38
+
GUILD_ANNOUNCEMENT = 5,
39
+
GUILD_STORE = 6,
40
+
ANNOUNCEMENT_THREAD = 10,
41
+
PUBLIC_THREAD = 11,
42
+
PRIVATE_THREAD = 12,
43
+
GUILD_STAGE_VOICE = 13,
44
+
GUILD_DIRECTORY = 14,
45
+
GUILD_FORUM = 15,
46
+
GUILD_MEDIA = 16,
47
+
LOBBY = 17,
48
+
DM_SDK = 18
49
+
}
50
+
51
+
export type RegisteredCommandOption = MoonlightCommandOption & {
52
+
displayName: string;
53
+
displayDescription: string;
54
+
};
55
+
56
+
export type CommandOptionChoice<T> = {
57
+
name: string;
58
+
value: T;
59
+
};
60
+
61
+
type CommandOptionBase<T> = {
62
+
type: T;
63
+
name: string;
64
+
description: string;
65
+
required?: T extends OptionType.SUB_COMMAND
66
+
? never
67
+
: T extends OptionType.SUB_COMMAND_GROUP
68
+
? never
69
+
: boolean | undefined;
70
+
choices?: T extends OptionType.STRING
71
+
? CommandOptionChoice<string>[]
72
+
: T extends OptionType.INTEGER
73
+
? CommandOptionChoice<number>[]
74
+
: T extends OptionType.NUMBER
75
+
? CommandOptionChoice<number>[]
76
+
: never;
77
+
options?: T extends OptionType.SUB_COMMAND
78
+
? MoonlightCommandOption[]
79
+
: T extends OptionType.SUB_COMMAND_GROUP
80
+
? MoonlightCommandOption[]
81
+
: never;
82
+
channelTypes?: T extends OptionType.CHANNEL ? ChannelType[] : never;
83
+
minValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never;
84
+
maxValue?: T extends OptionType.INTEGER ? number : T extends OptionType.NUMBER ? number : never;
85
+
minLength?: T extends OptionType.STRING ? number : never;
86
+
maxLength?: T extends OptionType.STRING ? number : never;
87
+
};
88
+
89
+
// This is bad lol
90
+
export type MoonlightCommandOption =
91
+
| CommandOptionBase<OptionType.SUB_COMMAND>
92
+
| CommandOptionBase<OptionType.SUB_COMMAND_GROUP>
93
+
| CommandOptionBase<OptionType.STRING>
94
+
| CommandOptionBase<OptionType.INTEGER>
95
+
| CommandOptionBase<OptionType.BOOLEAN>
96
+
| CommandOptionBase<OptionType.USER>
97
+
| CommandOptionBase<OptionType.CHANNEL>
98
+
| CommandOptionBase<OptionType.ROLE>
99
+
| CommandOptionBase<OptionType.MENTIONABLE>
100
+
| CommandOptionBase<OptionType.NUMBER>
101
+
| CommandOptionBase<OptionType.ATTACHMENT>;
102
+
103
+
// TODO: types
104
+
export type CommandPredicateState = {
105
+
channel: any;
106
+
guild: any;
107
+
};
108
+
109
+
export type RegisteredCommand = {
110
+
id: string;
111
+
untranslatedName: string;
112
+
displayName: string;
113
+
type: CommandType;
114
+
inputType: InputType;
115
+
applicationId: string; // set to -3!
116
+
untranslatedDescription: string;
117
+
displayDescription: string;
118
+
options?: RegisteredCommandOption[];
119
+
predicate?: (state: CommandPredicateState) => boolean;
120
+
execute: (options: CommandOption[]) => void;
121
+
};
122
+
123
+
export type MoonlightCommand = {
124
+
id: string;
125
+
description: string;
126
+
127
+
/**
128
+
* You likely want CHAT
129
+
*/
130
+
type: CommandType;
131
+
132
+
/**
133
+
* You likely want BUILT_IN (or BUILT_IN_TEXT if usable with replies)
134
+
*/
135
+
inputType: InputType;
136
+
options?: MoonlightCommandOption[];
137
+
predicate?: (state: CommandPredicateState) => boolean;
138
+
execute: (options: CommandOption[]) => void;
139
+
};
140
+
141
+
export type CommandOption = {
142
+
name: string;
143
+
} & ( // TODO: more of these
144
+
| {
145
+
type: Exclude<OptionType, OptionType.STRING>;
146
+
value: any;
147
+
}
148
+
| {
149
+
type: OptionType.STRING;
150
+
value: string;
151
+
}
152
+
| {
153
+
type: OptionType.NUMBER | OptionType.INTEGER;
154
+
value: number;
155
+
}
156
+
| {
157
+
type: OptionType.BOOLEAN;
158
+
value: boolean;
159
+
}
160
+
| {
161
+
type: OptionType.SUB_COMMAND | OptionType.SUB_COMMAND_GROUP;
162
+
options: CommandOption[];
163
+
}
164
+
);
165
+
166
+
export type AnyScopeRegex = RegExp["exec"] & {
167
+
regex: RegExp;
168
+
};
169
+
170
+
export type Commands = {
171
+
/**
172
+
* Register a command in the internal slash command system
173
+
*/
174
+
registerCommand: (command: MoonlightCommand) => void;
175
+
176
+
/**
177
+
* Register a legacy command that works via regex
178
+
*/
179
+
registerLegacyCommand: (id: string, command: LegacyCommand) => void;
180
+
181
+
/**
182
+
* Creates a regular expression that legacy commands can understand
183
+
*/
184
+
anyScopeRegex: (regex: RegExp) => AnyScopeRegex;
185
+
186
+
/**
187
+
* @private
188
+
*/
189
+
_getCommands: () => RegisteredCommand[];
190
+
};
191
+
192
+
export type LegacyContext = {
193
+
channel: any;
194
+
isEdit: boolean;
195
+
};
196
+
197
+
export type LegacyReturn = {
198
+
content: string;
199
+
};
200
+
201
+
export type LegacyCommand = {
202
+
match?: RegExp | { regex: RegExp } | AnyScopeRegex;
203
+
action: (content: string, context: LegacyContext) => LegacyReturn;
204
+
};
+33
packages/types/src/coreExtensions/common.ts
+33
packages/types/src/coreExtensions/common.ts
···
1
+
import type { IconProps, IconSize } from "@moonlight-mod/mappings/discord/components/common/index";
2
+
3
+
export type ErrorBoundaryProps = React.PropsWithChildren<{
4
+
noop?: boolean;
5
+
fallback?: React.FC<any>;
6
+
message?: string;
7
+
}>;
8
+
9
+
export type ErrorBoundaryState = {
10
+
errored: boolean;
11
+
error?: Error;
12
+
componentStack?: string;
13
+
};
14
+
15
+
export type ErrorBoundary = React.ComponentClass<ErrorBoundaryProps, ErrorBoundaryState>;
16
+
17
+
export type ParsedIconProps = {
18
+
width: number;
19
+
height: number;
20
+
fill: string;
21
+
className: string;
22
+
};
23
+
24
+
export interface Icons {
25
+
/**
26
+
* Parse icon props into their actual width/height.
27
+
* @param props The icon props
28
+
*/
29
+
parseProps(props?: IconProps): ParsedIconProps;
30
+
}
31
+
32
+
// Re-export so extension developers don't need to depend on mappings
33
+
export type { IconProps, IconSize };
+162
packages/types/src/coreExtensions/componentEditor.ts
+162
packages/types/src/coreExtensions/componentEditor.ts
···
1
+
type Patcher<T> = (elements: React.ReactNode[], props: T) => React.ReactNode[];
2
+
3
+
//#region DM List
4
+
export type DMListAnchors =
5
+
| "content"
6
+
| "favorite-server-indicator"
7
+
| "ignored-indicator"
8
+
| "blocked-indicator"
9
+
| "close-button"
10
+
| undefined;
11
+
export type DMListDecoratorAnchors = "system-tag" | undefined;
12
+
13
+
export enum DMListAnchorIndicies {
14
+
content = 0,
15
+
"favorite-server-indicator",
16
+
"ignored-indicator",
17
+
"blocked-indicator",
18
+
"close-button"
19
+
}
20
+
export enum DMListDecoratorAnchorIndicies {
21
+
"system-tag" = 0
22
+
}
23
+
24
+
export type DMListItem = {
25
+
component: React.FC<any>;
26
+
anchor: DMListAnchors;
27
+
before: boolean;
28
+
};
29
+
export type DMListDecorator = {
30
+
component: React.FC<any>;
31
+
anchor: DMListDecoratorAnchors;
32
+
before: boolean;
33
+
};
34
+
35
+
export type DMList = {
36
+
addItem: (id: string, component: React.FC<any>, anchor?: DMListAnchors, before?: boolean) => void;
37
+
addDecorator: (id: string, component: React.FC<any>, anchor?: DMListDecoratorAnchors, before?: boolean) => void;
38
+
//TODO: fix props type
39
+
/**
40
+
* @private
41
+
*/
42
+
_patchItems: Patcher<any>;
43
+
/**
44
+
* @private
45
+
*/
46
+
_patchDecorators: Patcher<any>;
47
+
};
48
+
//#endregion
49
+
50
+
//#region Member List
51
+
export type MemberListDecoratorAnchors = "bot-tag" | "owner-crown" | "boost-icon" | undefined;
52
+
53
+
export enum MemberListDecoratorAnchorIndicies {
54
+
"bot-tag" = 0,
55
+
"owner-crown",
56
+
"boost-icon"
57
+
}
58
+
59
+
export type MemberListDecorator = {
60
+
component: React.FC<any>;
61
+
anchor: MemberListDecoratorAnchors;
62
+
before: boolean;
63
+
};
64
+
65
+
export type MemberList = {
66
+
addItem: (id: string, component: React.FC<any>) => void;
67
+
addDecorator: (id: string, component: React.FC<any>, anchor?: MemberListDecoratorAnchors, before?: boolean) => void;
68
+
//TODO: fix props type
69
+
/**
70
+
* @private
71
+
*/
72
+
_patchItems: Patcher<any>;
73
+
/**
74
+
* @private
75
+
*/
76
+
_patchDecorators: Patcher<any>;
77
+
};
78
+
//#endregion
79
+
80
+
//#region Messages
81
+
export type MessageUsernameAnchors = "communication-disabled" | "username" | undefined;
82
+
export type MessageUsernameBadgeAnchors =
83
+
| "nitro-author"
84
+
| "role-icon"
85
+
| "new-member"
86
+
| "leaderboard-champion"
87
+
| "connections"
88
+
| undefined;
89
+
export type MessageBadgeAnchors = "silent" | "potion" | undefined;
90
+
91
+
export type MessageUsername = {
92
+
component: React.FC<any>;
93
+
anchor: MessageUsernameAnchors;
94
+
before: boolean;
95
+
};
96
+
export type MessageUsernameBadge = {
97
+
component: React.FC<any>;
98
+
anchor: MessageUsernameBadgeAnchors;
99
+
before: boolean;
100
+
};
101
+
export type MessageBadge = {
102
+
component: React.FC<any>;
103
+
anchor: MessageBadgeAnchors;
104
+
before: boolean;
105
+
};
106
+
107
+
export enum MessageUsernameIndicies {
108
+
"communication-disabled" = 0,
109
+
username
110
+
}
111
+
export enum MessageUsernameBadgeIndicies {
112
+
"nitro-author" = 0,
113
+
"role-icon",
114
+
"new-member",
115
+
"leaderboard-champion",
116
+
connections
117
+
}
118
+
export enum MessageBadgeIndicies {
119
+
silent = 0,
120
+
potion
121
+
}
122
+
123
+
export type Messages = {
124
+
/**
125
+
* Adds a component to the username of a message
126
+
*/
127
+
addToUsername: (id: string, component: React.FC<any>, anchor?: MessageUsernameAnchors, before?: boolean) => void;
128
+
/**
129
+
* Adds a component to the username badge area of a message (e.g. where role icons/new member badge is)
130
+
*/
131
+
addUsernameBadge: (
132
+
id: string,
133
+
component: React.FC<any>,
134
+
anchor?: MessageUsernameBadgeAnchors,
135
+
before?: boolean
136
+
) => void;
137
+
/**
138
+
* Adds a component to the end of a message header (e.g. silent indicator)
139
+
*/
140
+
addBadge: (id: string, component: React.FC<any>, anchor?: MessageBadgeAnchors, before?: boolean) => void;
141
+
/**
142
+
* Adds a component to message accessories (e.g. embeds)
143
+
*/
144
+
addAccessory: (id: string, component: React.FC<any>) => void;
145
+
/**
146
+
* @private
147
+
*/
148
+
_patchUsername: Patcher<any>;
149
+
/**
150
+
* @private
151
+
*/
152
+
_patchUsernameBadges: Patcher<any>;
153
+
/**
154
+
* @private
155
+
*/
156
+
_patchBadges: Patcher<any>;
157
+
/**
158
+
* @private
159
+
*/
160
+
_patchAccessories: Patcher<any>;
161
+
};
162
+
//#endregion
-356
packages/types/src/coreExtensions/components.ts
-356
packages/types/src/coreExtensions/components.ts
···
1
-
import type {
2
-
Component,
3
-
Ref,
4
-
PropsWithChildren,
5
-
PropsWithoutRef,
6
-
CSSProperties,
7
-
ReactNode,
8
-
MouseEvent,
9
-
KeyboardEvent,
10
-
ReactElement,
11
-
ComponentClass,
12
-
ComponentType,
13
-
MouseEventHandler,
14
-
KeyboardEventHandler
15
-
} from "react";
16
-
import * as CSS from "csstype";
17
-
18
-
export enum TextInputSizes {
19
-
DEFAULT = "inputDefault",
20
-
MINI = "inputMini"
21
-
}
22
-
23
-
interface TextInput
24
-
extends ComponentClass<
25
-
PropsWithoutRef<{
26
-
value?: string;
27
-
name?: string;
28
-
className?: string;
29
-
inputClassName?: string;
30
-
inputPrefix?: string;
31
-
disabled?: boolean;
32
-
size?: TextInputSizes;
33
-
editable?: boolean;
34
-
inputRef?: Ref<any>;
35
-
prefixElement?: Component;
36
-
focusProps?: PropsWithoutRef<any>;
37
-
error?: string;
38
-
minLength?: number;
39
-
maxLength?: number;
40
-
onChange?: (value: string, name: string) => void;
41
-
onFocus?: (event: any, name: string) => void;
42
-
onBlur?: (event: any, name: string) => void;
43
-
}>
44
-
> {
45
-
Sizes: TextInputSizes;
46
-
}
47
-
48
-
export enum FormTextTypes {
49
-
DEFAULT = "default",
50
-
DESCRIPTION = "description",
51
-
ERROR = "error",
52
-
INPUT_PLACEHOLDER = "placeholder",
53
-
LABEL_BOLD = "labelBold",
54
-
LABEL_DESCRIPTOR = "labelDescriptor",
55
-
LABEL_SELECTED = "labelSelected",
56
-
SUCCESS = "success"
57
-
}
58
-
59
-
interface FormText
60
-
extends Component<
61
-
PropsWithChildren<{
62
-
type?: FormTextTypes;
63
-
className?: string;
64
-
disabled?: boolean;
65
-
selectable?: boolean;
66
-
style?: CSSProperties;
67
-
}>
68
-
> {
69
-
Types: FormTextTypes;
70
-
}
71
-
72
-
declare enum SliderMarkerPosition {
73
-
ABOVE,
74
-
BELOW
75
-
}
76
-
77
-
declare enum ButtonLooks {
78
-
FILLED = "lookFilled",
79
-
INVERTED = "lookInverted",
80
-
OUTLINED = "lookOutlined",
81
-
LINK = "lookLink",
82
-
BLANK = "lookBlank"
83
-
}
84
-
declare enum ButtonColors {
85
-
BRAND = "colorBrand",
86
-
RED = "colorRed",
87
-
GREEN = "colorGreen",
88
-
YELLOW = "colorYellow",
89
-
PRIMARY = "colorPrimary",
90
-
LINK = "colorLink",
91
-
WHITE = "colorWhite",
92
-
BLACK = "colorBlack",
93
-
TRANSPARENT = "colorTransparent",
94
-
BRAND_NEW = "colorBrandNew",
95
-
CUSTOM = ""
96
-
}
97
-
declare enum ButtonBorderColors {
98
-
BRAND = "borderBrand",
99
-
RED = "borderRed",
100
-
GREEN = "borderGreen",
101
-
YELLOW = "borderYellow",
102
-
PRIMARY = "borderPrimary",
103
-
LINK = "borderLink",
104
-
WHITE = "borderWhite",
105
-
BLACK = "borderBlack",
106
-
TRANSPARENT = "borderTransparent",
107
-
BRAND_NEW = "borderBrandNew"
108
-
}
109
-
declare enum ButtonHovers {
110
-
DEFAULT = "",
111
-
BRAND = "hoverBrand",
112
-
RED = "hoverRed",
113
-
GREEN = "hoverGreen",
114
-
YELLOW = "hoverYellow",
115
-
PRIMARY = "hoverPrimary",
116
-
LINK = "hoverLink",
117
-
WHITE = "hoverWhite",
118
-
BLACK = "hoverBlack",
119
-
TRANSPARENT = "hoverTransparent"
120
-
}
121
-
declare enum ButtonSizes {
122
-
NONE = "",
123
-
TINY = "sizeTiny",
124
-
SMALL = "sizeSmall",
125
-
MEDIUM = "sizeMedium",
126
-
LARGE = "sizeLarge",
127
-
XLARGE = "sizeXlarge",
128
-
MIN = "sizeMin",
129
-
MAX = "sizeMax",
130
-
ICON = "sizeIcon"
131
-
}
132
-
133
-
type Button = ComponentType<
134
-
PropsWithChildren<{
135
-
look?: ButtonLooks;
136
-
color?: ButtonColors;
137
-
borderColor?: ButtonBorderColors;
138
-
hover?: ButtonHovers;
139
-
size?: ButtonSizes;
140
-
fullWidth?: boolean;
141
-
grow?: boolean;
142
-
disabled?: boolean;
143
-
submitting?: boolean;
144
-
type?: string;
145
-
style?: CSSProperties;
146
-
wrapperClassName?: string;
147
-
className?: string;
148
-
innerClassName?: string;
149
-
onClick?: MouseEventHandler;
150
-
onDoubleClick?: MouseEventHandler;
151
-
onMouseDown?: MouseEventHandler;
152
-
onMouseUp?: MouseEventHandler;
153
-
onMouseEnter?: MouseEventHandler;
154
-
onMouseLeave?: MouseEventHandler;
155
-
onKeyDown?: KeyboardEventHandler;
156
-
rel?: any;
157
-
buttonRef?: Ref<any>;
158
-
focusProps?: PropsWithChildren<any>;
159
-
"aria-label"?: string;
160
-
submittingStartedLabel?: string;
161
-
submittingFinishedLabel?: string;
162
-
}>
163
-
> & {
164
-
Looks: typeof ButtonLooks;
165
-
Colors: typeof ButtonColors;
166
-
BorderColors: typeof ButtonBorderColors;
167
-
Hovers: typeof ButtonHovers;
168
-
Sizes: typeof ButtonSizes;
169
-
};
170
-
171
-
export enum FlexDirection {
172
-
VERTICAL = "vertical",
173
-
HORIZONTAL = "horizontal",
174
-
HORIZONTAL_REVERSE = "horizontalReverse"
175
-
}
176
-
177
-
declare enum FlexAlign {
178
-
START = "alignStart",
179
-
END = "alignEnd",
180
-
CENTER = "alignCenter",
181
-
STRETCH = "alignStretch",
182
-
BASELINE = "alignBaseline"
183
-
}
184
-
declare enum FlexJustify {
185
-
START = "justifyStart",
186
-
END = "justifyEnd",
187
-
CENTER = "justifyCenter",
188
-
BETWEEN = "justifyBetween",
189
-
AROUND = "justifyAround"
190
-
}
191
-
declare enum FlexWrap {
192
-
NO_WRAP = "noWrap",
193
-
WRAP = "wrap",
194
-
WRAP_REVERSE = "wrapReverse"
195
-
}
196
-
interface Flex
197
-
extends ComponentClass<
198
-
PropsWithChildren<{
199
-
className?: string;
200
-
direction?: FlexDirection;
201
-
justify?: FlexJustify;
202
-
align?: FlexAlign;
203
-
wrap?: FlexWrap;
204
-
shrink?: CSS.Property.FlexShrink;
205
-
grow?: CSS.Property.FlexGrow;
206
-
basis?: CSS.Property.FlexBasis;
207
-
style?: CSSProperties;
208
-
}>
209
-
> {
210
-
Direction: typeof FlexDirection;
211
-
Align: typeof FlexAlign;
212
-
Justify: typeof FlexJustify;
213
-
Wrap: typeof FlexWrap;
214
-
Child: Component<
215
-
PropsWithChildren<{
216
-
className?: string;
217
-
shrink?: CSS.Property.FlexShrink;
218
-
grow?: CSS.Property.FlexGrow;
219
-
basis?: CSS.Property.FlexBasis;
220
-
style?: CSSProperties;
221
-
wrap?: boolean;
222
-
}>
223
-
>;
224
-
}
225
-
226
-
// TODO: wtaf is up with react types not working in jsx
227
-
export type CommonComponents = {
228
-
Clickable: Component<
229
-
PropsWithChildren<{
230
-
onClick?: () => void;
231
-
href?: any;
232
-
onKeyPress?: () => void;
233
-
ignoreKeyPress?: boolean;
234
-
innerRef?: Ref<any>;
235
-
focusProps?: any;
236
-
tag?: string | Component;
237
-
role?: any;
238
-
tabIndex?: any;
239
-
className?: string;
240
-
}>
241
-
>;
242
-
TextInput: TextInput;
243
-
Form: {
244
-
Section: Component<
245
-
PropsWithChildren<{
246
-
className?: string;
247
-
titleClassName?: string;
248
-
title?: ReactNode;
249
-
icon?: ReactNode;
250
-
disabled?: boolean;
251
-
htmlFor?: any;
252
-
tag?: string;
253
-
}>
254
-
>;
255
-
Text: FormText;
256
-
Title: Component<
257
-
PropsWithChildren<{
258
-
tag?: string;
259
-
className?: string;
260
-
faded?: boolean;
261
-
disabled?: boolean;
262
-
required?: boolean;
263
-
error?: string;
264
-
}>
265
-
>;
266
-
};
267
-
Slider: ComponentClass<
268
-
PropsWithChildren<{
269
-
disabled?: boolean;
270
-
stickToMarkers?: boolean;
271
-
className?: string;
272
-
barStyles?: CSSProperties;
273
-
fillStyles?: CSSProperties;
274
-
mini?: boolean;
275
-
hideBubble?: boolean;
276
-
initialValue?: number;
277
-
orientation?: "horizontal" | "vertical";
278
-
onValueRender?: (value: number) => string;
279
-
renderMarker?: (marker: number) => ReactNode;
280
-
getAriaValueText?: (value: number) => string;
281
-
barClassName?: string;
282
-
grabberClassName?: string;
283
-
grabberStyles?: CSSProperties;
284
-
markerPosition?: SliderMarkerPosition;
285
-
"aria-hidden"?: "true" | "false";
286
-
"aria-label"?: string;
287
-
"aria-labelledby"?: string;
288
-
"aria-describedby"?: string;
289
-
minValue?: number;
290
-
maxValue?: number;
291
-
asValueChanges?: (value: number) => void;
292
-
onValueChange?: (value: number) => void;
293
-
keyboardStep?: number;
294
-
}>
295
-
>;
296
-
FormSwitch: ComponentClass<PropsWithChildren<any>>;
297
-
Switch: ComponentClass<PropsWithChildren<any>>;
298
-
Button: Button;
299
-
SmallSlider: Component;
300
-
Avatar: Component;
301
-
Scroller: Component;
302
-
Text: ComponentClass<PropsWithChildren<any>>;
303
-
LegacyText: Component;
304
-
Flex: Flex;
305
-
Card: ComponentClass<PropsWithChildren<any>>;
306
-
CardClasses: {
307
-
card: string;
308
-
cardHeader: string;
309
-
};
310
-
ControlClasses: {
311
-
container: string;
312
-
control: string;
313
-
disabled: string;
314
-
dividerDefault: string;
315
-
labelRow: string;
316
-
note: string;
317
-
title: string;
318
-
titleDefault: string;
319
-
titleMini: string;
320
-
};
321
-
MarkdownParser: {
322
-
parse: (text: string) => ReactElement;
323
-
};
324
-
SettingsNotice: React.ComponentType<{
325
-
submitting: boolean;
326
-
onReset: () => void;
327
-
onSave: () => void;
328
-
}>;
329
-
TabBar: React.ComponentType<any> & {
330
-
Item: React.ComponentType<any>;
331
-
};
332
-
SingleSelect: React.ComponentType<{
333
-
autofocus?: boolean;
334
-
clearable?: boolean;
335
-
value?: string;
336
-
options?: {
337
-
value: string;
338
-
label: string;
339
-
}[];
340
-
onChange?: (value: string) => void;
341
-
}>;
342
-
Select: React.ComponentType<{
343
-
autofocus?: boolean;
344
-
clearable?: boolean;
345
-
value?: string[];
346
-
options?: {
347
-
value: string;
348
-
label: string;
349
-
}[];
350
-
onChange?: (value: string[]) => void;
351
-
}>;
352
-
353
-
// TODO
354
-
useVariableSelect: any;
355
-
multiSelect: any;
356
-
};
+64
packages/types/src/coreExtensions/contextMenu.ts
+64
packages/types/src/coreExtensions/contextMenu.ts
···
1
+
import {
2
+
Menu,
3
+
MenuCheckboxItem,
4
+
MenuControlItem,
5
+
MenuGroup,
6
+
MenuRadioItem,
7
+
MenuSeparator,
8
+
MenuItem,
9
+
MenuElement
10
+
} from "@moonlight-mod/mappings/discord/components/common/index";
11
+
12
+
export type ContextMenu = {
13
+
/**
14
+
* Registers a new context menu item for a given context menu type.
15
+
* @param navId The navigation ID for the target context menu (e.g. "user-context", "message")
16
+
* @param item A React component
17
+
* @param anchor An existing item's ID to anchor the new item to
18
+
* @param before Whether to insert the new item before the anchor item
19
+
*/
20
+
addItem: (navId: string, item: React.FC<any>, anchor: string | RegExp, before?: boolean) => void;
21
+
22
+
MenuCheckboxItem: MenuCheckboxItem;
23
+
MenuControlItem: MenuControlItem;
24
+
MenuGroup: MenuGroup;
25
+
MenuItem: MenuItem;
26
+
MenuRadioItem: MenuRadioItem;
27
+
MenuSeparator: MenuSeparator;
28
+
};
29
+
30
+
export type InternalItem = {
31
+
type: string;
32
+
key?: string;
33
+
};
34
+
35
+
export type InternalSeparator = {
36
+
type: "separator";
37
+
navigable: false;
38
+
};
39
+
export type InternalGroupStart = {
40
+
type: "groupstart";
41
+
length: number;
42
+
navigable: false;
43
+
props: React.ComponentProps<MenuGroup>;
44
+
};
45
+
export type InternalGroupEnd = {
46
+
type: "groupend";
47
+
} & Omit<InternalGroupStart, "type">;
48
+
export type InternalCustomItem = {
49
+
type: "customitem";
50
+
key: any;
51
+
navigable?: boolean;
52
+
render: any;
53
+
props: Extract<React.ComponentProps<MenuItem>, { render: any }>;
54
+
};
55
+
export type InternalItem_ = {
56
+
type: "item";
57
+
key: any;
58
+
navigable: true;
59
+
label: string;
60
+
};
61
+
62
+
export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[];
63
+
64
+
export type { Menu, MenuElement };
+107
packages/types/src/coreExtensions/markdown.ts
+107
packages/types/src/coreExtensions/markdown.ts
···
1
+
// {{{ simple-markdown
2
+
3
+
export type SingleASTNode = {
4
+
type: string;
5
+
[key: string]: any;
6
+
};
7
+
8
+
export type UntypedASTNode = {
9
+
[key: string]: any;
10
+
};
11
+
12
+
export type ASTNode = SingleASTNode | Array<SingleASTNode>;
13
+
14
+
export type Parser = (source: string, state?: State | null | undefined) => Array<SingleASTNode>;
15
+
16
+
export type ParseFunction = (capture: Capture, nestedParse: Parser, state: State) => UntypedASTNode | ASTNode;
17
+
18
+
export type Capture =
19
+
| (Array<string> & {
20
+
index: number;
21
+
})
22
+
| (Array<string> & {
23
+
index?: number;
24
+
});
25
+
26
+
export type State = {
27
+
key?: string | number | undefined;
28
+
inline?: boolean | null | undefined;
29
+
[key: string]: any;
30
+
};
31
+
32
+
export type MatchFunction = {
33
+
regex?: RegExp;
34
+
} & ((source: string, state: State, prevCapture: string) => Capture | null | undefined);
35
+
36
+
export type Output<Result> = (node: ASTNode, state?: State | null | undefined) => Result;
37
+
38
+
export type SingleNodeOutput<Result> = (node: SingleASTNode, nestedOutput: Output<Result>, state: State) => Result;
39
+
40
+
// }}}
41
+
42
+
export type ValidFlags = "g" | "i" | "m" | "s" | "u" | "y" | undefined;
43
+
44
+
export type MarkdownRule = {
45
+
order: number;
46
+
match: MatchFunction;
47
+
parse: ParseFunction;
48
+
react?: SingleNodeOutput<React.ReactNode>;
49
+
};
50
+
51
+
export type SlateRule =
52
+
| {
53
+
type: "skip";
54
+
}
55
+
| {
56
+
type: "verbatim";
57
+
}
58
+
| {
59
+
type: "inlineObject";
60
+
}
61
+
| {
62
+
type: "inlineStyle";
63
+
before: string;
64
+
after: string;
65
+
};
66
+
67
+
export type Ruleset =
68
+
| "RULES"
69
+
| "CHANNEL_TOPIC_RULES"
70
+
| "VOICE_CHANNEL_STATUS_RULES"
71
+
| "EMBED_TITLE_RULES"
72
+
| "INLINE_REPLY_RULES"
73
+
| "GUILD_VERIFICATION_FORM_RULES"
74
+
| "GUILD_EVENT_RULES"
75
+
| "PROFILE_BIO_RULES"
76
+
| "AUTO_MODERATION_SYSTEM_MESSAGE_RULES"
77
+
| "NATIVE_SEARCH_RESULT_LINK_RULES";
78
+
79
+
export type Markdown = {
80
+
rules: Record<string, MarkdownRule>;
81
+
slateRules: Record<string, SlateRule>;
82
+
slateDecorators: Record<string, string>;
83
+
ruleBlacklists: Record<Ruleset, Record<string, boolean>>;
84
+
85
+
/**
86
+
* Registers a new Markdown rule with simple-markdown.
87
+
* @param name The name of the rule
88
+
* @param markdown A function that returns simple-markdown rules
89
+
* @param slate A function that returns Slate rules
90
+
* @param decorator A decorator name for Slate
91
+
* @see https://www.npmjs.com/package/simple-markdown#adding-a-simple-extension
92
+
* @see https://docs.slatejs.org/
93
+
*/
94
+
addRule: (
95
+
name: string,
96
+
markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule,
97
+
slate: (rules: Record<string, SlateRule>) => SlateRule,
98
+
decorator?: string | undefined
99
+
) => void;
100
+
101
+
/**
102
+
* Blacklist a rule from a ruleset.
103
+
* @param ruleset The ruleset name
104
+
* @param name The rule name
105
+
*/
106
+
blacklistFromRuleset: (ruleset: Ruleset, name: string) => void;
107
+
};
+17
packages/types/src/coreExtensions/moonbase.ts
+17
packages/types/src/coreExtensions/moonbase.ts
···
1
+
export type CustomComponentProps = {
2
+
value: any;
3
+
setValue: (value: any) => void;
4
+
};
5
+
6
+
export type CustomComponent = React.FC<CustomComponentProps>;
7
+
8
+
export type Moonbase = {
9
+
/**
10
+
* Registers a custom component for an extension setting.
11
+
* The extension setting must be of type "custom".
12
+
* @param ext The extension ID
13
+
* @param option The setting ID
14
+
* @param component A React component
15
+
*/
16
+
registerConfigComponent: (ext: string, option: string, component: CustomComponent) => void;
17
+
};
+36
packages/types/src/coreExtensions/notices.ts
+36
packages/types/src/coreExtensions/notices.ts
···
1
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store";
2
+
3
+
export type NoticeButton = {
4
+
name: string;
5
+
onClick: () => boolean; // return true to dismiss the notice after the button is clicked
6
+
};
7
+
8
+
export type Notice = {
9
+
element: React.ReactNode;
10
+
color?: string;
11
+
showClose?: boolean;
12
+
buttons?: NoticeButton[];
13
+
onDismiss?: () => void;
14
+
};
15
+
16
+
export type Notices = Store<any> & {
17
+
/**
18
+
* Adds a custom notice to the top of the screen.
19
+
*/
20
+
addNotice: (notice: Notice) => void;
21
+
22
+
/**
23
+
* Removes the current notice from the top of the screen.
24
+
*/
25
+
popNotice: () => void;
26
+
27
+
/**
28
+
* @private
29
+
*/
30
+
getCurrentNotice: () => Notice | null;
31
+
32
+
/**
33
+
* @private
34
+
*/
35
+
shouldShowNotice: () => boolean;
36
+
};
+71
packages/types/src/coreExtensions/settings.ts
+71
packages/types/src/coreExtensions/settings.ts
···
1
+
import React, { ReactElement } from "react";
2
+
import type { Store } from "@moonlight-mod/mappings/discord/packages/flux/Store";
3
+
4
+
export type NoticeProps = {
5
+
stores: Store<any>[];
6
+
element: React.FunctionComponent;
7
+
};
8
+
9
+
export type SettingsSection =
10
+
| { section: "DIVIDER"; pos: number | ((sections: SettingsSection[]) => number) }
11
+
| { section: "HEADER"; label: string; pos: number | ((sections: SettingsSection[]) => number) }
12
+
| {
13
+
section: string;
14
+
label: string;
15
+
color: string | null;
16
+
element: React.FunctionComponent;
17
+
pos: number | ((sections: SettingsSection[]) => number);
18
+
notice?: NoticeProps;
19
+
onClick?: () => void;
20
+
_moonlight_submenu?: () => ReactElement | ReactElement[];
21
+
};
22
+
23
+
export type Settings = {
24
+
ourSections: SettingsSection[];
25
+
sectionNames: string[];
26
+
sectionMenuItems: Record<string, ReactElement[]>;
27
+
28
+
/**
29
+
* Registers a new section in the settings menu.
30
+
* @param section The section ID
31
+
* @param label The label for the section
32
+
* @param element The React component to render
33
+
* @param color A color to use for the section
34
+
* @param pos The position in the settings menu to place the section
35
+
* @param notice A notice to display when in the section
36
+
* @param onClick A custom action to execute when clicked from the context menu
37
+
*/
38
+
addSection: (
39
+
section: string,
40
+
label: string,
41
+
element: React.FunctionComponent,
42
+
color?: string | null,
43
+
pos?: number | ((sections: SettingsSection[]) => number),
44
+
notice?: NoticeProps,
45
+
onClick?: () => void
46
+
) => void;
47
+
48
+
/**
49
+
* Adds new items to a section in the settings menu.
50
+
* @param section The section ID
51
+
* @param items The React components to render
52
+
*/
53
+
addSectionMenuItems: (section: string, ...items: ReactElement[]) => void;
54
+
55
+
/**
56
+
* Places a divider in the settings menu.
57
+
* @param pos The position in the settings menu to place the divider
58
+
*/
59
+
addDivider: (pos: number | ((sections: SettingsSection[]) => number) | null) => void;
60
+
61
+
/**
62
+
* Places a header in the settings menu.
63
+
* @param pos The position in the settings menu to place the header
64
+
*/
65
+
addHeader: (label: string, pos: number | ((sections: SettingsSection[]) => number) | null) => void;
66
+
67
+
/**
68
+
* @private
69
+
*/
70
+
_mutateSections: (sections: SettingsSection[]) => SettingsSection[];
71
+
};
+97
packages/types/src/coreExtensions/spacepack.ts
+97
packages/types/src/coreExtensions/spacepack.ts
···
1
+
import { WebpackModule, WebpackModuleFunc, WebpackRequireType } from "../discord";
2
+
3
+
export type Spacepack = {
4
+
/**
5
+
* Given a Webpack module ID, returns the function for the Webpack module.
6
+
* Can be double clicked to inspect in DevTools.
7
+
* @param module The module ID
8
+
* @returns The Webpack module, if found
9
+
*/
10
+
inspect: (module: number | string) => WebpackModuleFunc | null;
11
+
12
+
/**
13
+
* Find Webpack modules based on matches in code.
14
+
* @param args A list of finds to match against
15
+
* @returns The Webpack modules, if found
16
+
*/
17
+
findByCode: (...args: (string | RegExp)[]) => WebpackModule[];
18
+
19
+
/**
20
+
* Find Webpack modules based on their exports.
21
+
* @deprecated This has race conditions. Consider using findByCode instead.
22
+
* @param args A list of finds to match exports against
23
+
* @returns The Webpack modules, if found
24
+
*/
25
+
findByExports: (...args: string[]) => WebpackModule[];
26
+
27
+
/**
28
+
* The Webpack require function.
29
+
*/
30
+
require: WebpackRequireType;
31
+
32
+
/**
33
+
* The Webpack module list.
34
+
* Re-export of require.m.
35
+
*/
36
+
modules: Record<string, WebpackModuleFunc>;
37
+
38
+
/**
39
+
* The Webpack module cache.
40
+
* Re-export of require.c.
41
+
*/
42
+
cache: Record<string, any>;
43
+
44
+
/**
45
+
* Finds an object from a module's exports using the given key.
46
+
* @param exports Exports from a Webpack module
47
+
* @param key The key to find with
48
+
* @returns The object, if found
49
+
*/
50
+
findObjectFromKey: (exports: Record<string, any>, key: string) => any | null;
51
+
52
+
/**
53
+
* Finds an object from a module's exports using the given value.
54
+
* @param exports Exports from a Webpack module
55
+
* @param value The value to find with
56
+
* @returns The object, if found
57
+
*/
58
+
findObjectFromValue: (exports: Record<string, any>, value: any) => any | null;
59
+
60
+
/**
61
+
* Finds an object from a module's exports using the given key-value pair.
62
+
* @param exports Exports from a Webpack module
63
+
* @param key The key to find with
64
+
* @param value The value to find with
65
+
* @returns The object, if found
66
+
*/
67
+
findObjectFromKeyValuePair: (exports: Record<string, any>, key: string, value: any) => any | null;
68
+
69
+
/**
70
+
* Finds a function from a module's exports using the given source find.
71
+
* This behaves like findByCode but localized to the exported function.
72
+
* @param exports A module's exports
73
+
* @param strings A list of finds to use
74
+
* @returns The function, if found
75
+
*/
76
+
findFunctionByStrings: (
77
+
exports: Record<string, any>,
78
+
...strings: (string | RegExp)[]
79
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-function-type
80
+
) => Function | null;
81
+
82
+
/**
83
+
* Lazy load a Webpack module.
84
+
* @param find A list of finds to discover a target module with
85
+
* @param chunk A RegExp to match chunks to load
86
+
* @param module A RegExp to match the target Webpack module
87
+
* @returns The target Webpack module
88
+
*/
89
+
lazyLoad: (find: string | RegExp | (string | RegExp)[], chunk: RegExp, module: RegExp) => Promise<any>;
90
+
91
+
/**
92
+
* Filter a list of Webpack modules to "real" ones from the Discord client.
93
+
* @param modules A list of Webpack modules
94
+
* @returns A filtered list of Webpack modules
95
+
*/
96
+
filterReal: (modules: WebpackModule[]) => WebpackModule[];
97
+
};
+10
-66
packages/types/src/coreExtensions.ts
+10
-66
packages/types/src/coreExtensions.ts
···
1
-
import { FluxDefault, Store } from "./discord/common/Flux";
2
-
import WebpackRequire from "./discord/require";
3
-
import { WebpackModuleFunc } from "./discord/webpack";
4
-
import { CommonComponents as CommonComponents_ } from "./coreExtensions/components";
5
-
import { Dispatcher } from "flux";
6
-
import React from "react";
7
-
8
-
export type Spacepack = {
9
-
inspect: (module: number | string) => WebpackModuleFunc | null;
10
-
findByCode: (...args: (string | RegExp)[]) => any[];
11
-
findByExports: (...args: string[]) => any[];
12
-
require: typeof WebpackRequire;
13
-
modules: Record<string, WebpackModuleFunc>;
14
-
cache: Record<string, any>;
15
-
findObjectFromKey: (exports: Record<string, any>, key: string) => any | null;
16
-
findObjectFromValue: (exports: Record<string, any>, value: any) => any | null;
17
-
findObjectFromKeyValuePair: (
18
-
exports: Record<string, any>,
19
-
key: string,
20
-
value: any
21
-
) => any | null;
22
-
findFunctionByStrings: (
23
-
exports: Record<string, any>,
24
-
...strings: (string | RegExp)[]
25
-
) => Function | null;
26
-
};
27
-
28
-
export type NoticeProps = {
29
-
stores: Store<any>[];
30
-
element: React.FunctionComponent;
31
-
};
32
-
33
-
export type SettingsSection =
34
-
| { section: "DIVIDER"; pos: number }
35
-
| { section: "HEADER"; label: string; pos: number }
36
-
| {
37
-
section: string;
38
-
label: string;
39
-
color: string | null;
40
-
element: React.FunctionComponent;
41
-
pos: number;
42
-
notice?: NoticeProps;
43
-
_moonlight_submenu?: () => any;
44
-
};
45
-
46
-
export type Settings = {
47
-
ourSections: SettingsSection[];
48
-
49
-
addSection: (
50
-
section: string,
51
-
label: string,
52
-
element: React.FunctionComponent,
53
-
color?: string | null,
54
-
pos?: number,
55
-
notice?: NoticeProps
56
-
) => void;
57
-
58
-
addDivider: (pos: number | null) => void;
59
-
addHeader: (label: string, pos: number | null) => void;
60
-
_mutateSections: (sections: SettingsSection[]) => SettingsSection[];
61
-
};
62
-
63
-
export type CommonReact = typeof import("react");
64
-
export type CommonFlux = FluxDefault;
65
-
export type CommonComponents = CommonComponents_; // lol
66
-
export type CommonFluxDispatcher = Dispatcher<any>;
1
+
export * as Spacepack from "./coreExtensions/spacepack";
2
+
export * as Settings from "./coreExtensions/settings";
3
+
export * as Markdown from "./coreExtensions/markdown";
4
+
export * as ContextMenu from "./coreExtensions/contextMenu";
5
+
export * as Notices from "./coreExtensions/notices";
6
+
export * as Moonbase from "./coreExtensions/moonbase";
7
+
export * as AppPanels from "./coreExtensions/appPanels";
8
+
export * as Commands from "./coreExtensions/commands";
9
+
export * as ComponentEditor from "./coreExtensions/componentEditor";
10
+
export * as Common from "./coreExtensions/common";
-57
packages/types/src/discord/common/Flux.ts
-57
packages/types/src/discord/common/Flux.ts
···
1
-
/*
2
-
It seems like Discord maintains their own version of Flux that doesn't match
3
-
the types on NPM. This is a heavy work in progress - if you encounter rough
4
-
edges, please contribute!
5
-
*/
6
-
7
-
import { DependencyList } from "react";
8
-
import { Store as FluxStore } from "flux/utils";
9
-
import { Dispatcher as FluxDispatcher } from "flux";
10
-
import { ComponentConstructor } from "flux/lib/FluxContainer";
11
-
12
-
export declare abstract class Store<T> extends FluxStore<T> {
13
-
static getAll: () => Store<any>[];
14
-
getName: () => string;
15
-
emitChange: () => void;
16
-
}
17
-
18
-
interface ConnectStores {
19
-
<T>(
20
-
stores: Store<any>[],
21
-
callback: T,
22
-
context?: any
23
-
): ComponentConstructor<T>;
24
-
}
25
-
26
-
export type FluxDefault = {
27
-
DeviceSettingsStore: any; // TODO
28
-
Emitter: any; // @types/fbemitter
29
-
OfflineCacheStore: any; // TODO
30
-
PersistedStore: any; // TODO
31
-
Store: typeof Store;
32
-
Dispatcher: typeof FluxDispatcher;
33
-
connectStores: ConnectStores;
34
-
initialize: () => void;
35
-
initialized: Promise<boolean>;
36
-
destroy: () => void;
37
-
useStateFromStores: UseStateFromStores;
38
-
useStateFromStoresArray: UseStateFromStoresArray;
39
-
useStateFromStoresObject: UseStateFromStoresObject;
40
-
};
41
-
42
-
interface UseStateFromStores {
43
-
<T>(
44
-
stores: Store<any>[],
45
-
callback: () => T,
46
-
deps?: DependencyList,
47
-
shouldUpdate?: (oldState: T, newState: T) => boolean
48
-
): T;
49
-
}
50
-
51
-
interface UseStateFromStoresArray {
52
-
<T>(stores: Store<any>[], callback: () => T, deps?: DependencyList): T;
53
-
}
54
-
55
-
interface UseStateFromStoresObject {
56
-
<T>(stores: Store<any>[], callback: () => T, deps?: DependencyList): T;
57
-
}
+40
-16
packages/types/src/discord/require.ts
+40
-16
packages/types/src/discord/require.ts
···
1
-
import {
2
-
Spacepack,
3
-
CommonReact,
4
-
CommonFlux,
5
-
Settings,
6
-
CommonComponents,
7
-
CommonFluxDispatcher
8
-
} from "../coreExtensions";
1
+
import { AppPanels } from "../coreExtensions/appPanels";
2
+
import { Commands } from "../coreExtensions/commands";
3
+
import { ErrorBoundary, Icons } from "../coreExtensions/common";
4
+
import { DMList, MemberList, Messages } from "../coreExtensions/componentEditor";
5
+
import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu";
6
+
import { Markdown } from "../coreExtensions/markdown";
7
+
import { Moonbase } from "../coreExtensions/moonbase";
8
+
import { Notices } from "../coreExtensions/notices";
9
+
import { Settings } from "../coreExtensions/settings";
10
+
import { Spacepack } from "../coreExtensions/spacepack";
9
11
10
12
declare function WebpackRequire(id: string): any;
11
-
declare function WebpackRequire(id: "spacepack_spacepack"): Spacepack;
12
-
declare function WebpackRequire(id: "common_react"): CommonReact;
13
-
declare function WebpackRequire(id: "common_flux"): CommonFlux;
14
-
declare function WebpackRequire(
15
-
id: "common_fluxDispatcher"
16
-
): CommonFluxDispatcher;
17
-
declare function WebpackRequire(id: "settings_settings"): Settings;
18
-
declare function WebpackRequire(id: "common_components"): CommonComponents;
13
+
14
+
declare function WebpackRequire(id: "appPanels_appPanels"): AppPanels;
15
+
16
+
declare function WebpackRequire(id: "commands_commands"): Commands;
17
+
18
+
declare function WebpackRequire(id: "common_ErrorBoundary"): ErrorBoundary;
19
+
declare function WebpackRequire(id: "common_icons"): Icons;
20
+
21
+
declare function WebpackRequire(id: "componentEditor_dmList"): DMList;
22
+
declare function WebpackRequire(id: "componentEditor_memberList"): MemberList;
23
+
declare function WebpackRequire(id: "componentEditor_messages"): Messages;
24
+
25
+
declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser;
26
+
declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu;
27
+
28
+
declare function WebpackRequire(id: "markdown_markdown"): Markdown;
29
+
30
+
declare function WebpackRequire(id: "moonbase_moonbase"): Moonbase;
31
+
32
+
declare function WebpackRequire(id: "notices_notices"): Notices;
33
+
34
+
declare function WebpackRequire(id: "settings_settings"): {
35
+
Settings: Settings;
36
+
default: Settings;
37
+
};
38
+
39
+
declare function WebpackRequire(id: "spacepack_spacepack"): {
40
+
default: Spacepack;
41
+
spacepack: Spacepack;
42
+
};
19
43
20
44
export default WebpackRequire;
+10
-15
packages/types/src/discord/webpack.ts
+10
-15
packages/types/src/discord/webpack.ts
···
1
1
import WebpackRequire from "./require";
2
+
import { WebpackRequire as MappingsWebpackRequire } from "@moonlight-mod/mappings";
2
3
3
-
export type WebpackRequireType = typeof WebpackRequire & {
4
-
c: Record<string, WebpackModule>;
5
-
m: Record<string, WebpackModuleFunc>;
6
-
};
4
+
export type WebpackRequireType = typeof MappingsWebpackRequire &
5
+
typeof WebpackRequire & {
6
+
c: Record<string, WebpackModule>;
7
+
m: Record<string, WebpackModuleFunc>;
8
+
e: (module: number | string) => Promise<void>;
9
+
};
7
10
8
11
export type WebpackModule = {
9
12
id: string | number;
10
-
loaded: boolean;
13
+
loaded?: boolean;
11
14
exports: any;
12
15
};
13
16
14
-
export type WebpackModuleFunc = ((
15
-
module: any,
16
-
exports: any,
17
-
require: WebpackRequireType
18
-
) => void) & {
17
+
export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackRequireType) => void) & {
19
18
__moonlight?: boolean;
20
19
};
21
20
22
-
export type WebpackJsonpEntry = [
23
-
number[],
24
-
{ [id: string]: WebpackModuleFunc },
25
-
(require: WebpackRequireType) => any
26
-
];
21
+
export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any];
27
22
28
23
export type WebpackJsonp = WebpackJsonpEntry[] & {
29
24
push: {
+120
-6
packages/types/src/extension.ts
+120
-6
packages/types/src/extension.ts
···
28
28
};
29
29
30
30
export type ExtensionManifest = {
31
+
$schema?: string;
32
+
33
+
/**
34
+
* A unique identifier for your extension.
35
+
*/
31
36
id: string;
37
+
38
+
/**
39
+
* A version string for your extension - doesn't need to follow a specific format. Required for publishing.
40
+
*/
32
41
version?: string;
33
42
43
+
/**
44
+
* The API level this extension targets. If it does not match the current version, the extension will not be loaded.
45
+
*/
46
+
apiLevel?: number;
47
+
48
+
/**
49
+
* Which environment this extension is capable of running in.
50
+
*/
51
+
environment?: ExtensionEnvironment;
52
+
53
+
/**
54
+
* Metadata about your extension for use in Moonbase.
55
+
*/
34
56
meta?: {
57
+
/**
58
+
* A human friendly name for your extension as a proper noun.
59
+
*/
35
60
name?: string;
61
+
62
+
/**
63
+
* A short tagline that appears below the name.
64
+
*/
36
65
tagline?: string;
66
+
67
+
/**
68
+
* A longer description that can use Markdown.
69
+
*/
37
70
description?: string;
71
+
72
+
/**
73
+
* List of authors that worked on this extension - accepts string or object with ID.
74
+
*/
38
75
authors?: ExtensionAuthor[];
39
-
deprecated?: boolean;
76
+
77
+
/**
78
+
* A list of tags that are relevant to the extension.
79
+
*/
40
80
tags?: ExtensionTag[];
81
+
82
+
/**
83
+
* The URL to the source repository.
84
+
*/
41
85
source?: string;
86
+
87
+
/**
88
+
* A donation link (or other method of support). If you don't want financial contributions, consider putting your favorite charity here!
89
+
*/
90
+
donate?: string;
91
+
92
+
/**
93
+
* A changelog to show in Moonbase.
94
+
* Moonbase will show the changelog for the latest version, even if it is not installed.
95
+
*/
96
+
changelog?: string;
97
+
98
+
/**
99
+
* Whether the extension is deprecated and no longer receiving updates.
100
+
*/
101
+
deprecated?: boolean;
42
102
};
43
103
104
+
/**
105
+
* A list of extension IDs that are required for the extension to load.
106
+
*/
44
107
dependencies?: string[];
108
+
109
+
/**
110
+
* A list of extension IDs that the user may want to install.
111
+
*/
45
112
suggested?: string[];
46
-
incompatible?: string[]; // TODO: implement
47
113
114
+
/**
115
+
* A list of extension IDs that the extension is incompatible with.
116
+
* If two incompatible extensions are enabled, one of them will not load.
117
+
*/
118
+
incompatible?: string[];
119
+
120
+
/**
121
+
* A list of settings for your extension, where the key is the settings ID.
122
+
*/
48
123
settings?: Record<string, ExtensionSettingsManifest>;
124
+
125
+
/**
126
+
* A list of URLs to bypass CORS for.
127
+
* This is implemented by checking if the start of the URL matches.
128
+
* @example https://moonlight-mod.github.io/
129
+
*/
49
130
cors?: string[];
131
+
132
+
/**
133
+
* A list of URLs to block all requests to.
134
+
* This is implemented by checking if the start of the URL matches.
135
+
* @example https://moonlight-mod.github.io/
136
+
*/
137
+
blocked?: string[];
138
+
139
+
/**
140
+
* A mapping from CSP directives to URLs to allow.
141
+
* @example { "script-src": ["https://example.com"] }
142
+
*/
143
+
csp?: Record<string, string[]>;
50
144
};
51
145
146
+
export enum ExtensionEnvironment {
147
+
/**
148
+
* The extension will run on both platforms, the host/native modules MAY be loaded
149
+
*/
150
+
Both = "both",
151
+
152
+
/**
153
+
* Extension will run on desktop only, the host/native modules are guaranteed to load
154
+
*/
155
+
Desktop = "desktop",
156
+
157
+
/**
158
+
* Currently equivalent to Both
159
+
*/
160
+
Web = "web"
161
+
}
162
+
52
163
export enum ExtensionLoadSource {
53
164
Developer,
54
165
Core,
···
62
173
scripts: {
63
174
web?: string;
64
175
webPath?: string;
176
+
webpackModules?: Record<string, string>;
65
177
nodePath?: string;
66
178
hostPath?: string;
179
+
style?: string;
67
180
};
68
181
};
69
182
···
95
208
export type Patch = {
96
209
find: PatchMatch;
97
210
replace: PatchReplace | PatchReplace[];
211
+
hardFail?: boolean; // if any patches fail, all fail
98
212
prerequisite?: () => boolean;
99
213
};
100
214
101
215
export type ExplicitExtensionDependency = {
102
-
ext: string;
216
+
ext?: string;
103
217
id: string;
104
218
};
105
219
···
108
222
export type ExtensionWebpackModule = {
109
223
entrypoint?: boolean;
110
224
dependencies?: ExtensionDependency[];
111
-
run: WebpackModuleFunc;
225
+
run?: WebpackModuleFunc;
112
226
};
113
227
114
228
export type ExtensionWebExports = {
115
229
patches?: Patch[];
116
230
webpackModules?: Record<string, ExtensionWebpackModule>;
231
+
styles?: string[];
117
232
};
118
233
119
234
export type IdentifiedPatch = Patch & {
···
121
236
id: number;
122
237
};
123
238
124
-
export type IdentifiedWebpackModule = ExtensionWebpackModule &
125
-
ExplicitExtensionDependency;
239
+
export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+18
packages/types/src/fs.ts
+18
packages/types/src/fs.ts
···
1
+
export type MoonlightFS = {
2
+
readFile: (path: string) => Promise<Uint8Array>;
3
+
readFileString: (path: string) => Promise<string>;
4
+
writeFile: (path: string, data: Uint8Array) => Promise<void>;
5
+
writeFileString: (path: string, data: string) => Promise<void>;
6
+
unlink: (path: string) => Promise<void>;
7
+
8
+
readdir: (path: string) => Promise<string[]>;
9
+
mkdir: (path: string) => Promise<void>;
10
+
rmdir: (path: string) => Promise<void>;
11
+
12
+
exists: (path: string) => Promise<boolean>;
13
+
isFile: (path: string) => Promise<boolean>;
14
+
isDir: (path: string) => Promise<boolean>;
15
+
16
+
join: (...parts: string[]) => string;
17
+
dirname: (path: string) => string;
18
+
};
+68
-14
packages/types/src/globals.ts
+68
-14
packages/types/src/globals.ts
···
1
-
import { Logger } from "./logger";
2
-
import { Config, ConfigExtension } from "./config";
3
-
import {
4
-
DetectedExtension,
5
-
IdentifiedPatch,
6
-
ProcessedExtensions
7
-
} from "./extension";
8
-
import EventEmitter from "events";
1
+
import type { Logger } from "./logger";
2
+
import type { Config, ConfigExtension } from "./config";
3
+
import type { DetectedExtension, IdentifiedPatch, IdentifiedWebpackModule, ProcessedExtensions } from "./extension";
4
+
import type EventEmitter from "events";
5
+
import type LunAST from "@moonlight-mod/lunast";
6
+
import type Moonmap from "@moonlight-mod/moonmap";
7
+
import type {
8
+
WebEventPayloads,
9
+
WebEventType,
10
+
MoonlightEventEmitter,
11
+
NodeEventType,
12
+
NodeEventPayloads
13
+
} from "./core/event";
14
+
import type { MoonlightFS } from "./fs";
9
15
10
16
export type MoonlightHost = {
11
-
asarPath: string;
12
17
config: Config;
13
-
events: EventEmitter;
14
18
extensions: DetectedExtension[];
15
19
processedExtensions: ProcessedExtensions;
20
+
asarPath: string;
21
+
events: EventEmitter;
22
+
23
+
version: string;
24
+
branch: MoonlightBranch;
16
25
17
26
getConfig: (ext: string) => ConfigExtension["config"];
27
+
getConfigPath: () => Promise<string>;
18
28
getConfigOption: <T>(ext: string, name: string) => T | undefined;
29
+
setConfigOption: <T>(ext: string, name: string, value: T) => void;
30
+
writeConfig: (config: Config) => Promise<void>;
31
+
19
32
getLogger: (id: string) => Logger;
33
+
getMoonlightDir: () => string;
34
+
getExtensionDir: (ext: string) => string;
20
35
};
21
36
22
37
export type MoonlightNode = {
···
24
39
extensions: DetectedExtension[];
25
40
processedExtensions: ProcessedExtensions;
26
41
nativesCache: Record<string, any>;
42
+
isBrowser: boolean;
43
+
events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>;
44
+
45
+
version: string;
46
+
branch: MoonlightBranch;
27
47
28
48
getConfig: (ext: string) => ConfigExtension["config"];
29
49
getConfigOption: <T>(ext: string, name: string) => T | undefined;
50
+
setConfigOption: <T>(ext: string, name: string, value: T) => Promise<void>;
51
+
writeConfig: (config: Config) => Promise<void>;
52
+
30
53
getNatives: (ext: string) => any | undefined;
31
54
getLogger: (id: string) => Logger;
55
+
getMoonlightDir: () => string;
56
+
getExtensionDir: (ext: string) => string;
57
+
};
32
58
33
-
getExtensionDir: (ext: string) => string;
34
-
writeConfig: (config: Config) => void;
59
+
export type MoonlightNodeSandboxed = {
60
+
fs: MoonlightFS;
61
+
addCors: (url: string) => void;
62
+
addBlocked: (url: string) => void;
35
63
};
36
64
37
65
export type MoonlightWeb = {
66
+
patched: Map<string, Set<string>>;
38
67
unpatched: Set<IdentifiedPatch>;
68
+
pendingModules: Set<IdentifiedWebpackModule>;
39
69
enabledExtensions: Set<string>;
70
+
events: MoonlightEventEmitter<WebEventType, WebEventPayloads>;
71
+
patchingInternals: {
72
+
onModuleLoad: (moduleId: string | string[], callback: (moduleId: string) => void) => void;
73
+
registerPatch: (patch: IdentifiedPatch) => void;
74
+
registerWebpackModule: (module: IdentifiedWebpackModule) => void;
75
+
};
76
+
localStorage: Storage;
40
77
41
-
getConfig: (ext: string) => ConfigExtension["config"];
42
-
getConfigOption: <T>(ext: string, name: string) => T | undefined;
78
+
version: string;
79
+
branch: MoonlightBranch;
80
+
apiLevel: number;
81
+
82
+
// Re-exports for ease of use
83
+
getConfig: MoonlightNode["getConfig"];
84
+
getConfigOption: MoonlightNode["getConfigOption"];
85
+
setConfigOption: MoonlightNode["setConfigOption"];
86
+
writeConfig: MoonlightNode["writeConfig"];
87
+
43
88
getNatives: (ext: string) => any | undefined;
44
89
getLogger: (id: string) => Logger;
90
+
91
+
lunast: LunAST;
92
+
moonmap: Moonmap;
45
93
};
46
94
47
95
export enum MoonlightEnv {
···
49
97
NodePreload = "node-preload",
50
98
WebPreload = "web-preload"
51
99
}
100
+
101
+
export enum MoonlightBranch {
102
+
STABLE = "stable",
103
+
NIGHTLY = "nightly",
104
+
DEV = "dev"
105
+
}
+80
packages/types/src/import.d.ts
+80
packages/types/src/import.d.ts
···
1
+
declare module "@moonlight-mod/wp/appPanels_appPanels" {
2
+
import { CoreExtensions } from "@moonlight-mod/types";
3
+
const AppPanels: CoreExtensions.AppPanels.AppPanels;
4
+
export = AppPanels;
5
+
}
6
+
7
+
declare module "@moonlight-mod/wp/commands_commands" {
8
+
import { CoreExtensions } from "@moonlight-mod/types";
9
+
export const commands: CoreExtensions.Commands.Commands;
10
+
export default commands;
11
+
}
12
+
13
+
declare module "@moonlight-mod/wp/common_ErrorBoundary" {
14
+
import { CoreExtensions } from "@moonlight-mod/types";
15
+
const ErrorBoundary: CoreExtensions.Common.ErrorBoundary;
16
+
export = ErrorBoundary;
17
+
}
18
+
declare module "@moonlight-mod/wp/common_icons" {
19
+
import { CoreExtensions } from "@moonlight-mod/types";
20
+
export const icons: CoreExtensions.Common.Icons;
21
+
export default icons;
22
+
}
23
+
declare module "@moonlight-mod/wp/common_stores";
24
+
25
+
declare module "@moonlight-mod/wp/componentEditor_dmList" {
26
+
import { CoreExtensions } from "@moonlight-mod/types";
27
+
export const dmList: CoreExtensions.ComponentEditor.DMList;
28
+
export default dmList;
29
+
}
30
+
declare module "@moonlight-mod/wp/componentEditor_memberList" {
31
+
import { CoreExtensions } from "@moonlight-mod/types";
32
+
export const memberList: CoreExtensions.ComponentEditor.MemberList;
33
+
export default memberList;
34
+
}
35
+
declare module "@moonlight-mod/wp/componentEditor_messages" {
36
+
import { CoreExtensions } from "@moonlight-mod/types";
37
+
export const message: CoreExtensions.ComponentEditor.Messages;
38
+
export default message;
39
+
}
40
+
41
+
declare module "@moonlight-mod/wp/contextMenu_evilMenu" {
42
+
import { CoreExtensions } from "@moonlight-mod/types";
43
+
const EvilParser: CoreExtensions.ContextMenu.EvilItemParser;
44
+
export = EvilParser;
45
+
}
46
+
declare module "@moonlight-mod/wp/contextMenu_contextMenu" {
47
+
import { CoreExtensions } from "@moonlight-mod/types";
48
+
const ContextMenu: CoreExtensions.ContextMenu.ContextMenu;
49
+
export = ContextMenu;
50
+
}
51
+
52
+
declare module "@moonlight-mod/wp/markdown_markdown" {
53
+
import { CoreExtensions } from "@moonlight-mod/types";
54
+
const Markdown: CoreExtensions.Markdown.Markdown;
55
+
export = Markdown;
56
+
}
57
+
58
+
declare module "@moonlight-mod/wp/moonbase_moonbase" {
59
+
import { CoreExtensions } from "@moonlight-mod/types";
60
+
const Moonbase: CoreExtensions.Moonbase.Moonbase;
61
+
export = Moonbase;
62
+
}
63
+
64
+
declare module "@moonlight-mod/wp/notices_notices" {
65
+
import { CoreExtensions } from "@moonlight-mod/types";
66
+
const Notices: CoreExtensions.Notices.Notices;
67
+
export = Notices;
68
+
}
69
+
70
+
declare module "@moonlight-mod/wp/settings_settings" {
71
+
import { CoreExtensions } from "@moonlight-mod/types";
72
+
export const Settings: CoreExtensions.Settings.Settings;
73
+
export default Settings;
74
+
}
75
+
76
+
declare module "@moonlight-mod/wp/spacepack_spacepack" {
77
+
import { CoreExtensions } from "@moonlight-mod/types";
78
+
export const spacepack: CoreExtensions.Spacepack.Spacepack;
79
+
export default spacepack;
80
+
}
+17
-8
packages/types/src/index.ts
+17
-8
packages/types/src/index.ts
···
1
-
/// <reference types="node" />
2
1
/// <reference types="standalone-electron-types" />
3
2
/// <reference types="react" />
4
-
/// <reference types="flux" />
3
+
/// <reference types="./import" />
4
+
/// <reference types="./mappings" />
5
+
/* eslint-disable no-var */
5
6
6
-
import {
7
-
MoonlightEnv,
8
-
MoonlightHost,
9
-
MoonlightNode,
10
-
MoonlightWeb
11
-
} from "./globals";
7
+
import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals";
12
8
13
9
export * from "./discord";
14
10
export * from "./config";
15
11
export * from "./extension";
12
+
export * as CoreExtensions from "./coreExtensions";
16
13
export * from "./globals";
17
14
export * from "./logger";
18
15
export * as constants from "./constants";
16
+
export * from "./fs";
17
+
18
+
export type { AST } from "@moonlight-mod/lunast";
19
+
export { ModuleExport, ModuleExportType } from "@moonlight-mod/moonmap";
19
20
20
21
declare global {
21
22
const MOONLIGHT_ENV: MoonlightEnv;
···
23
24
const MOONLIGHT_INJECTOR: boolean;
24
25
const MOONLIGHT_NODE_PRELOAD: boolean;
25
26
const MOONLIGHT_WEB_PRELOAD: boolean;
27
+
const MOONLIGHT_BROWSER: boolean;
28
+
const MOONLIGHT_BRANCH: string;
29
+
const MOONLIGHT_VERSION: string;
26
30
27
31
var moonlightHost: MoonlightHost;
28
32
var moonlightNode: MoonlightNode;
33
+
var moonlightNodeSandboxed: MoonlightNodeSandboxed;
29
34
var moonlight: MoonlightWeb;
35
+
var _moonlight_coreExtensionsStr: string;
36
+
37
+
var _moonlightBrowserInit: undefined | (() => Promise<void>);
38
+
var _moonlightWebLoad: undefined | (() => Promise<void>);
30
39
}
+888
packages/types/src/mappings.d.ts
+888
packages/types/src/mappings.d.ts
···
1
+
// auto-generated
2
+
declare module "@moonlight-mod/wp/chroma-js" {}
3
+
4
+
declare module "@moonlight-mod/wp/classnames" {
5
+
import { MappedModules } from "@moonlight-mod/mappings";
6
+
const _default: MappedModules["classnames"]["default"];
7
+
export default _default;
8
+
}
9
+
10
+
declare module "@moonlight-mod/wp/dependency-graph" {
11
+
import { MappedModules } from "@moonlight-mod/mappings";
12
+
export const DepGraph: MappedModules["dependency-graph"]["DepGraph"];
13
+
}
14
+
15
+
declare module "@moonlight-mod/wp/discord/Constants" {
16
+
import { MappedModules } from "@moonlight-mod/mappings";
17
+
export const ActivityFlags: MappedModules["discord/Constants"]["ActivityFlags"];
18
+
export const ActivityTypes: MappedModules["discord/Constants"]["ActivityTypes"];
19
+
export const AnalyticsLocations: MappedModules["discord/Constants"]["AnalyticsLocations"];
20
+
export const ChannelLayouts: MappedModules["discord/Constants"]["ChannelLayouts"];
21
+
export const ChannelModes: MappedModules["discord/Constants"]["ChannelModes"];
22
+
export const ChannelTypes: MappedModules["discord/Constants"]["ChannelTypes"];
23
+
export const ChannelStreamTypes: MappedModules["discord/Constants"]["ChannelStreamTypes"];
24
+
export const ComponentActions: MappedModules["discord/Constants"]["ComponentActions"];
25
+
export const DEFAULT_ROLE_COLOR: MappedModules["discord/Constants"]["DEFAULT_ROLE_COLOR"];
26
+
export const Endpoints: MappedModules["discord/Constants"]["Endpoints"];
27
+
export const MessageFlags: MappedModules["discord/Constants"]["MessageFlags"];
28
+
export const MessageTypes: MappedModules["discord/Constants"]["MessageTypes"];
29
+
export const Permissions: MappedModules["discord/Constants"]["Permissions"];
30
+
export const PlatformTypes: MappedModules["discord/Constants"]["PlatformTypes"];
31
+
export const RelationshipTypes: MappedModules["discord/Constants"]["RelationshipTypes"];
32
+
export const Routes: MappedModules["discord/Constants"]["Routes"];
33
+
export const StatusTypes: MappedModules["discord/Constants"]["StatusTypes"];
34
+
export const Themes: MappedModules["discord/Constants"]["Themes"];
35
+
export const UserSettingsSections: MappedModules["discord/Constants"]["UserSettingsSections"];
36
+
export const UserFlags: MappedModules["discord/Constants"]["UserFlags"];
37
+
}
38
+
39
+
declare module "@moonlight-mod/wp/discord/Dispatcher" {
40
+
import { MappedModules } from "@moonlight-mod/mappings";
41
+
const _default: MappedModules["discord/Dispatcher"]["default"];
42
+
export default _default;
43
+
}
44
+
45
+
declare module "@moonlight-mod/wp/discord/actions/ContextMenuActionCreators" {
46
+
import { MappedModules } from "@moonlight-mod/mappings";
47
+
export const closeContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["closeContextMenu"];
48
+
export const openContextMenu: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenu"];
49
+
export const openContextMenuLazy: MappedModules["discord/actions/ContextMenuActionCreators"]["openContextMenuLazy"];
50
+
}
51
+
52
+
declare module "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators" {
53
+
import { MappedModules } from "@moonlight-mod/mappings";
54
+
const _default: MappedModules["discord/actions/UserSettingsModalActionCreators"]["default"];
55
+
export default _default;
56
+
}
57
+
58
+
declare module "@moonlight-mod/wp/discord/common/AppStartPerformance" {
59
+
import { MappedModules } from "@moonlight-mod/mappings";
60
+
const _default: MappedModules["discord/common/AppStartPerformance"]["default"];
61
+
export default _default;
62
+
}
63
+
64
+
declare module "@moonlight-mod/wp/discord/components/common/Alerts" {
65
+
import { MappedModules } from "@moonlight-mod/mappings";
66
+
const _default: MappedModules["discord/components/common/Alerts"]["default"];
67
+
export default _default;
68
+
}
69
+
70
+
declare module "@moonlight-mod/wp/discord/components/common/BaseHeaderBar" {
71
+
import { MappedModules } from "@moonlight-mod/mappings";
72
+
export const Icon: MappedModules["discord/components/common/BaseHeaderBar"]["Icon"];
73
+
export const Divider: MappedModules["discord/components/common/BaseHeaderBar"]["Divider"];
74
+
const _default: MappedModules["discord/components/common/BaseHeaderBar"]["default"];
75
+
export default _default;
76
+
}
77
+
78
+
declare module "@moonlight-mod/wp/discord/components/common/Card" {
79
+
import { MappedModules } from "@moonlight-mod/mappings";
80
+
const _default: MappedModules["discord/components/common/Card"]["default"];
81
+
export default _default;
82
+
export const Types: MappedModules["discord/components/common/Card"]["Types"];
83
+
}
84
+
85
+
declare module "@moonlight-mod/wp/discord/components/common/FileUpload" {
86
+
import { MappedModules } from "@moonlight-mod/mappings";
87
+
const _default: MappedModules["discord/components/common/FileUpload"]["default"];
88
+
export default _default;
89
+
}
90
+
91
+
declare module "@moonlight-mod/wp/discord/components/common/FormSwitch.css" {
92
+
import { MappedModules } from "@moonlight-mod/mappings";
93
+
export const container: MappedModules["discord/components/common/FormSwitch.css"]["container"];
94
+
export const labelRow: MappedModules["discord/components/common/FormSwitch.css"]["labelRow"];
95
+
export const control: MappedModules["discord/components/common/FormSwitch.css"]["control"];
96
+
export const disabled: MappedModules["discord/components/common/FormSwitch.css"]["disabled"];
97
+
export const title: MappedModules["discord/components/common/FormSwitch.css"]["title"];
98
+
export const note: MappedModules["discord/components/common/FormSwitch.css"]["note"];
99
+
export const disabledText: MappedModules["discord/components/common/FormSwitch.css"]["disabledText"];
100
+
export const dividerDefault: MappedModules["discord/components/common/FormSwitch.css"]["dividerDefault"];
101
+
}
102
+
103
+
declare module "@moonlight-mod/wp/discord/components/common/HeaderBar.css" {
104
+
import { MappedModules } from "@moonlight-mod/mappings";
105
+
export const caret: MappedModules["discord/components/common/HeaderBar.css"]["caret"];
106
+
export const children: MappedModules["discord/components/common/HeaderBar.css"]["children"];
107
+
export const clickable: MappedModules["discord/components/common/HeaderBar.css"]["clickable"];
108
+
export const container: MappedModules["discord/components/common/HeaderBar.css"]["container"];
109
+
export const divider: MappedModules["discord/components/common/HeaderBar.css"]["divider"];
110
+
export const dot: MappedModules["discord/components/common/HeaderBar.css"]["dot"];
111
+
export const hamburger: MappedModules["discord/components/common/HeaderBar.css"]["hamburger"];
112
+
export const icon: MappedModules["discord/components/common/HeaderBar.css"]["icon"];
113
+
export const iconBadge: MappedModules["discord/components/common/HeaderBar.css"]["iconBadge"];
114
+
export const iconBadgeBottom: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeBottom"];
115
+
export const iconBadgeTop: MappedModules["discord/components/common/HeaderBar.css"]["iconBadgeTop"];
116
+
export const iconWrapper: MappedModules["discord/components/common/HeaderBar.css"]["iconWrapper"];
117
+
export const scrollable: MappedModules["discord/components/common/HeaderBar.css"]["scrollable"];
118
+
export const selected: MappedModules["discord/components/common/HeaderBar.css"]["selected"];
119
+
export const themed: MappedModules["discord/components/common/HeaderBar.css"]["themed"];
120
+
export const themedMobile: MappedModules["discord/components/common/HeaderBar.css"]["themedMobile"];
121
+
export const title: MappedModules["discord/components/common/HeaderBar.css"]["title"];
122
+
export const titleWrapper: MappedModules["discord/components/common/HeaderBar.css"]["titleWrapper"];
123
+
export const toolbar: MappedModules["discord/components/common/HeaderBar.css"]["toolbar"];
124
+
export const transparent: MappedModules["discord/components/common/HeaderBar.css"]["transparent"];
125
+
export const upperContainer: MappedModules["discord/components/common/HeaderBar.css"]["upperContainer"];
126
+
}
127
+
128
+
declare module "@moonlight-mod/wp/discord/components/common/HelpMessage.css" {
129
+
import { MappedModules } from "@moonlight-mod/mappings";
130
+
export const container: MappedModules["discord/components/common/HelpMessage.css"]["container"];
131
+
export const icon: MappedModules["discord/components/common/HelpMessage.css"]["icon"];
132
+
export const iconDiv: MappedModules["discord/components/common/HelpMessage.css"]["iconDiv"];
133
+
export const text: MappedModules["discord/components/common/HelpMessage.css"]["text"];
134
+
export const positive: MappedModules["discord/components/common/HelpMessage.css"]["positive"];
135
+
export const warning: MappedModules["discord/components/common/HelpMessage.css"]["warning"];
136
+
export const info: MappedModules["discord/components/common/HelpMessage.css"]["info"];
137
+
export const error: MappedModules["discord/components/common/HelpMessage.css"]["error"];
138
+
}
139
+
140
+
declare module "@moonlight-mod/wp/discord/components/common/Image" {}
141
+
142
+
declare module "@moonlight-mod/wp/discord/components/common/PanelButton" {
143
+
import { MappedModules } from "@moonlight-mod/mappings";
144
+
const _default: MappedModules["discord/components/common/PanelButton"]["default"];
145
+
export default _default;
146
+
}
147
+
148
+
declare module "@moonlight-mod/wp/discord/components/common/Scroller.css" {
149
+
import { MappedModules } from "@moonlight-mod/mappings";
150
+
export const auto: MappedModules["discord/components/common/Scroller.css"]["auto"];
151
+
export const content: MappedModules["discord/components/common/Scroller.css"]["content"];
152
+
export const customTheme: MappedModules["discord/components/common/Scroller.css"]["customTheme"];
153
+
export const disableScrollAnchor: MappedModules["discord/components/common/Scroller.css"]["disableScrollAnchor"];
154
+
export const fade: MappedModules["discord/components/common/Scroller.css"]["fade"];
155
+
export const managedReactiveScroller: MappedModules["discord/components/common/Scroller.css"]["managedReactiveScroller"];
156
+
export const none: MappedModules["discord/components/common/Scroller.css"]["none"];
157
+
export const pointerCover: MappedModules["discord/components/common/Scroller.css"]["pointerCover"];
158
+
export const scrolling: MappedModules["discord/components/common/Scroller.css"]["scrolling"];
159
+
export const thin: MappedModules["discord/components/common/Scroller.css"]["thin"];
160
+
}
161
+
162
+
declare module "@moonlight-mod/wp/discord/components/common/index" {
163
+
import { MappedModules } from "@moonlight-mod/mappings";
164
+
export const Clickable: MappedModules["discord/components/common/index"]["Clickable"];
165
+
export const TextInput: MappedModules["discord/components/common/index"]["TextInput"];
166
+
export const TextArea: MappedModules["discord/components/common/index"]["TextArea"];
167
+
export const FormDivider: MappedModules["discord/components/common/index"]["FormDivider"];
168
+
export const FormSection: MappedModules["discord/components/common/index"]["FormSection"];
169
+
export const FormText: MappedModules["discord/components/common/index"]["FormText"];
170
+
export const FormTitle: MappedModules["discord/components/common/index"]["FormTitle"];
171
+
export const FormSwitch: MappedModules["discord/components/common/index"]["FormSwitch"];
172
+
export const FormItem: MappedModules["discord/components/common/index"]["FormItem"];
173
+
export const Slider: MappedModules["discord/components/common/index"]["Slider"];
174
+
export const Switch: MappedModules["discord/components/common/index"]["Switch"];
175
+
export const Button: MappedModules["discord/components/common/index"]["Button"];
176
+
export const Tooltip: MappedModules["discord/components/common/index"]["Tooltip"];
177
+
export const Avatar: MappedModules["discord/components/common/index"]["Avatar"];
178
+
export const AvatarSizes: MappedModules["discord/components/common/index"]["AvatarSizes"];
179
+
export const AvatarSizeSpecs: MappedModules["discord/components/common/index"]["AvatarSizeSpecs"];
180
+
export const Scroller: MappedModules["discord/components/common/index"]["Scroller"];
181
+
export const Text: MappedModules["discord/components/common/index"]["Text"];
182
+
export const Heading: MappedModules["discord/components/common/index"]["Heading"];
183
+
export const Card: MappedModules["discord/components/common/index"]["Card"];
184
+
export const Popout: MappedModules["discord/components/common/index"]["Popout"];
185
+
export const Dialog: MappedModules["discord/components/common/index"]["Dialog"];
186
+
export const Menu: MappedModules["discord/components/common/index"]["Menu"];
187
+
export const TabBar: MappedModules["discord/components/common/index"]["TabBar"];
188
+
export const SingleSelect: MappedModules["discord/components/common/index"]["SingleSelect"];
189
+
export const Select: MappedModules["discord/components/common/index"]["Select"];
190
+
export const NoticeColors: MappedModules["discord/components/common/index"]["NoticeColors"];
191
+
export const Notice: MappedModules["discord/components/common/index"]["Notice"];
192
+
export const NoticeCloseButton: MappedModules["discord/components/common/index"]["NoticeCloseButton"];
193
+
export const PrimaryCTANoticeButton: MappedModules["discord/components/common/index"]["PrimaryCTANoticeButton"];
194
+
export const Breadcrumbs: MappedModules["discord/components/common/index"]["Breadcrumbs"];
195
+
export const Image: MappedModules["discord/components/common/index"]["Image"];
196
+
export const tokens: MappedModules["discord/components/common/index"]["tokens"];
197
+
export const useVariableSelect: MappedModules["discord/components/common/index"]["useVariableSelect"];
198
+
export const useMultiSelect: MappedModules["discord/components/common/index"]["useMultiSelect"];
199
+
export const multiSelect: MappedModules["discord/components/common/index"]["multiSelect"];
200
+
export const openModal: MappedModules["discord/components/common/index"]["openModal"];
201
+
export const openModalLazy: MappedModules["discord/components/common/index"]["openModalLazy"];
202
+
export const closeModal: MappedModules["discord/components/common/index"]["closeModal"];
203
+
export const AngleBracketsIcon: MappedModules["discord/components/common/index"]["AngleBracketsIcon"];
204
+
export const ArrowAngleLeftUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleLeftUpIcon"];
205
+
export const ArrowAngleRightUpIcon: MappedModules["discord/components/common/index"]["ArrowAngleRightUpIcon"];
206
+
export const ArrowsUpDownIcon: MappedModules["discord/components/common/index"]["ArrowsUpDownIcon"];
207
+
export const BookCheckIcon: MappedModules["discord/components/common/index"]["BookCheckIcon"];
208
+
export const ChannelListIcon: MappedModules["discord/components/common/index"]["ChannelListIcon"];
209
+
export const ChevronSmallDownIcon: MappedModules["discord/components/common/index"]["ChevronSmallDownIcon"];
210
+
export const ChevronSmallUpIcon: MappedModules["discord/components/common/index"]["ChevronSmallUpIcon"];
211
+
export const CircleInformationIcon: MappedModules["discord/components/common/index"]["CircleInformationIcon"];
212
+
export const CircleWarningIcon: MappedModules["discord/components/common/index"]["CircleWarningIcon"];
213
+
export const CircleXIcon: MappedModules["discord/components/common/index"]["CircleXIcon"];
214
+
export const ClydeIcon: MappedModules["discord/components/common/index"]["ClydeIcon"];
215
+
export const CopyIcon: MappedModules["discord/components/common/index"]["CopyIcon"];
216
+
export const DownloadIcon: MappedModules["discord/components/common/index"]["DownloadIcon"];
217
+
export const FullscreenEnterIcon: MappedModules["discord/components/common/index"]["FullscreenEnterIcon"];
218
+
export const GameControllerIcon: MappedModules["discord/components/common/index"]["GameControllerIcon"];
219
+
export const GlobeEarthIcon: MappedModules["discord/components/common/index"]["GlobeEarthIcon"];
220
+
export const HeartIcon: MappedModules["discord/components/common/index"]["HeartIcon"];
221
+
export const LinkIcon: MappedModules["discord/components/common/index"]["LinkIcon"];
222
+
export const MaximizeIcon: MappedModules["discord/components/common/index"]["MaximizeIcon"];
223
+
export const MinusIcon: MappedModules["discord/components/common/index"]["MinusIcon"];
224
+
export const MobilePhoneIcon: MappedModules["discord/components/common/index"]["MobilePhoneIcon"];
225
+
export const PauseIcon: MappedModules["discord/components/common/index"]["PauseIcon"];
226
+
export const PlayIcon: MappedModules["discord/components/common/index"]["PlayIcon"];
227
+
export const PlusLargeIcon: MappedModules["discord/components/common/index"]["PlusLargeIcon"];
228
+
export const RetryIcon: MappedModules["discord/components/common/index"]["RetryIcon"];
229
+
export const ScienceIcon: MappedModules["discord/components/common/index"]["ScienceIcon"];
230
+
export const ScreenIcon: MappedModules["discord/components/common/index"]["ScreenIcon"];
231
+
export const StarIcon: MappedModules["discord/components/common/index"]["StarIcon"];
232
+
export const TrashIcon: MappedModules["discord/components/common/index"]["TrashIcon"];
233
+
export const WarningIcon: MappedModules["discord/components/common/index"]["WarningIcon"];
234
+
export const WindowLaunchIcon: MappedModules["discord/components/common/index"]["WindowLaunchIcon"];
235
+
export const WindowTopOutlineIcon: MappedModules["discord/components/common/index"]["WindowTopOutlineIcon"];
236
+
export const XLargeIcon: MappedModules["discord/components/common/index"]["XLargeIcon"];
237
+
export const XSmallIcon: MappedModules["discord/components/common/index"]["XSmallIcon"];
238
+
export const ConfirmModal: MappedModules["discord/components/common/index"]["ConfirmModal"];
239
+
export const H: MappedModules["discord/components/common/index"]["H"];
240
+
export const HelpMessage: MappedModules["discord/components/common/index"]["HelpMessage"];
241
+
export const ModalCloseButton: MappedModules["discord/components/common/index"]["ModalCloseButton"];
242
+
export const ModalContent: MappedModules["discord/components/common/index"]["ModalContent"];
243
+
export const ModalFooter: MappedModules["discord/components/common/index"]["ModalFooter"];
244
+
export const ModalHeader: MappedModules["discord/components/common/index"]["ModalHeader"];
245
+
export const ModalRoot: MappedModules["discord/components/common/index"]["ModalRoot"];
246
+
export const NumberInputStepper: MappedModules["discord/components/common/index"]["NumberInputStepper"];
247
+
export const SearchableSelect: MappedModules["discord/components/common/index"]["SearchableSelect"];
248
+
export const createToast: MappedModules["discord/components/common/index"]["createToast"];
249
+
export const popToast: MappedModules["discord/components/common/index"]["popToast"];
250
+
export const showToast: MappedModules["discord/components/common/index"]["showToast"];
251
+
export const useThemeContext: MappedModules["discord/components/common/index"]["useThemeContext"];
252
+
export const AccessibilityAnnouncer: MappedModules["discord/components/common/index"]["AccessibilityAnnouncer"];
253
+
export const BackdropStyles: MappedModules["discord/components/common/index"]["BackdropStyles"];
254
+
export const BadgeShapes: MappedModules["discord/components/common/index"]["BadgeShapes"];
255
+
export const CardTypes: MappedModules["discord/components/common/index"]["CardTypes"];
256
+
export const CircleIconButtonColors: MappedModules["discord/components/common/index"]["CircleIconButtonColors"];
257
+
export const CircleIconButtonSizes: MappedModules["discord/components/common/index"]["CircleIconButtonSizes"];
258
+
export const FormErrorBlockColors: MappedModules["discord/components/common/index"]["FormErrorBlockColors"];
259
+
export const FormNoticeImagePositions: MappedModules["discord/components/common/index"]["FormNoticeImagePositions"];
260
+
export const FormTitleTags: MappedModules["discord/components/common/index"]["FormTitleTags"];
261
+
export const HelpMessageTypes: MappedModules["discord/components/common/index"]["HelpMessageTypes"];
262
+
export const ModalSize: MappedModules["discord/components/common/index"]["ModalSize"];
263
+
export const ModalTransitionState: MappedModules["discord/components/common/index"]["ModalTransitionState"];
264
+
export const PRETTY_KEYS: MappedModules["discord/components/common/index"]["PRETTY_KEYS"];
265
+
export const SelectLooks: MappedModules["discord/components/common/index"]["SelectLooks"];
266
+
export const SpinnerTypes: MappedModules["discord/components/common/index"]["SpinnerTypes"];
267
+
export const StatusTypes: MappedModules["discord/components/common/index"]["StatusTypes"];
268
+
export const ToastPosition: MappedModules["discord/components/common/index"]["ToastPosition"];
269
+
export const ToastType: MappedModules["discord/components/common/index"]["ToastType"];
270
+
export const TransitionStates: MappedModules["discord/components/common/index"]["TransitionStates"];
271
+
export const DEFAULT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["DEFAULT_MODAL_CONTEXT"];
272
+
export const LOW_SATURATION_THRESHOLD: MappedModules["discord/components/common/index"]["LOW_SATURATION_THRESHOLD"];
273
+
export const LayerClassName: MappedModules["discord/components/common/index"]["LayerClassName"];
274
+
export const POPOUT_MODAL_CONTEXT: MappedModules["discord/components/common/index"]["POPOUT_MODAL_CONTEXT"];
275
+
}
276
+
277
+
declare module "@moonlight-mod/wp/discord/components/modals/ConfirmModal" {
278
+
import { MappedModules } from "@moonlight-mod/mappings";
279
+
const _default: MappedModules["discord/components/modals/ConfirmModal"]["default"];
280
+
export default _default;
281
+
}
282
+
283
+
declare module "@moonlight-mod/wp/discord/lib/BaseRecord" {
284
+
import { MappedModules } from "@moonlight-mod/mappings";
285
+
const _default: MappedModules["discord/lib/BaseRecord"]["default"];
286
+
export default _default;
287
+
}
288
+
289
+
declare module "@moonlight-mod/wp/discord/lib/web/Storage" {
290
+
import { MappedModules } from "@moonlight-mod/mappings";
291
+
export const ObjectStorage: MappedModules["discord/lib/web/Storage"]["ObjectStorage"];
292
+
export const impl: MappedModules["discord/lib/web/Storage"]["impl"];
293
+
}
294
+
295
+
declare module "@moonlight-mod/wp/discord/modules/build_overrides/web/BuildOverride.css" {
296
+
import { MappedModules } from "@moonlight-mod/mappings";
297
+
export const wrapper: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["wrapper"];
298
+
export const titleRegion: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["titleRegion"];
299
+
export const title: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["title"];
300
+
export const infoIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoIcon"];
301
+
export const copyLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLink"];
302
+
export const copied: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copied"];
303
+
export const copyLinkIcon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["copyLinkIcon"];
304
+
export const content: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["content"];
305
+
export const infoLink: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["infoLink"];
306
+
export const buildInfo: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildInfo"];
307
+
export const button: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["button"];
308
+
export const buttonSize: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonSize"];
309
+
export const subHead: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["subHead"];
310
+
export const icon: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["icon"];
311
+
export const buildDetails: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buildDetails"];
312
+
export const barLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barLoader"];
313
+
export const barTitle: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["barTitle"];
314
+
export const buttonLoader: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["buttonLoader"];
315
+
export const disabledButtonOverride: MappedModules["discord/modules/build_overrides/web/BuildOverride.css"]["disabledButtonOverride"];
316
+
}
317
+
318
+
declare module "@moonlight-mod/wp/discord/modules/discovery/web/Discovery.css" {
319
+
import { MappedModules } from "@moonlight-mod/mappings";
320
+
export const header: MappedModules["discord/modules/discovery/web/Discovery.css"]["header"];
321
+
export const headerImage: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImage"];
322
+
export const headerImageSimple: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageSimple"];
323
+
export const headerImageBG: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerImageBG"];
324
+
export const searchTitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchTitle"];
325
+
export const searchSubtitle: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchSubtitle"];
326
+
export const headerContentWrapper: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentWrapper"];
327
+
export const headerContent: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContent"];
328
+
export const headerContentSmall: MappedModules["discord/modules/discovery/web/Discovery.css"]["headerContentSmall"];
329
+
export const searchBox: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBox"];
330
+
export const searchBoxInput: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchBoxInput"];
331
+
export const closeIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["closeIcon"];
332
+
export const searchIcon: MappedModules["discord/modules/discovery/web/Discovery.css"]["searchIcon"];
333
+
export const tabBar: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBar"];
334
+
export const tabBarItem: MappedModules["discord/modules/discovery/web/Discovery.css"]["tabBarItem"];
335
+
export const sectionHeader: MappedModules["discord/modules/discovery/web/Discovery.css"]["sectionHeader"];
336
+
}
337
+
338
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Forums.css" {
339
+
import { MappedModules } from "@moonlight-mod/mappings";
340
+
export const container: MappedModules["discord/modules/forums/web/Forums.css"]["container"];
341
+
export const uploadArea: MappedModules["discord/modules/forums/web/Forums.css"]["uploadArea"];
342
+
export const label: MappedModules["discord/modules/forums/web/Forums.css"]["label"];
343
+
export const content: MappedModules["discord/modules/forums/web/Forums.css"]["content"];
344
+
export const noListContainer: MappedModules["discord/modules/forums/web/Forums.css"]["noListContainer"];
345
+
export const list: MappedModules["discord/modules/forums/web/Forums.css"]["list"];
346
+
export const grid: MappedModules["discord/modules/forums/web/Forums.css"]["grid"];
347
+
export const headerRow: MappedModules["discord/modules/forums/web/Forums.css"]["headerRow"];
348
+
export const card: MappedModules["discord/modules/forums/web/Forums.css"]["card"];
349
+
export const columnsSpan: MappedModules["discord/modules/forums/web/Forums.css"]["columnsSpan"];
350
+
export const emptyStateRow: MappedModules["discord/modules/forums/web/Forums.css"]["emptyStateRow"];
351
+
export const newMemberBanner: MappedModules["discord/modules/forums/web/Forums.css"]["newMemberBanner"];
352
+
export const gridViewBanner: MappedModules["discord/modules/forums/web/Forums.css"]["gridViewBanner"];
353
+
export const placeholder: MappedModules["discord/modules/forums/web/Forums.css"]["placeholder"];
354
+
export const mainCard: MappedModules["discord/modules/forums/web/Forums.css"]["mainCard"];
355
+
export const emptyMainCard: MappedModules["discord/modules/forums/web/Forums.css"]["emptyMainCard"];
356
+
export const outOfDate: MappedModules["discord/modules/forums/web/Forums.css"]["outOfDate"];
357
+
export const header: MappedModules["discord/modules/forums/web/Forums.css"]["header"];
358
+
export const matchingPostsRow: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPostsRow"];
359
+
export const headerWithMatchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["headerWithMatchingPosts"];
360
+
export const noForm: MappedModules["discord/modules/forums/web/Forums.css"]["noForm"];
361
+
export const sortContainer: MappedModules["discord/modules/forums/web/Forums.css"]["sortContainer"];
362
+
export const sort: MappedModules["discord/modules/forums/web/Forums.css"]["sort"];
363
+
export const sortPopout: MappedModules["discord/modules/forums/web/Forums.css"]["sortPopout"];
364
+
export const archivedDividerRow: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDividerRow"];
365
+
export const archivedDivider: MappedModules["discord/modules/forums/web/Forums.css"]["archivedDivider"];
366
+
export const newPostsButton: MappedModules["discord/modules/forums/web/Forums.css"]["newPostsButton"];
367
+
export const loadingCard: MappedModules["discord/modules/forums/web/Forums.css"]["loadingCard"];
368
+
export const enterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["enterIcon"];
369
+
export const warnIcon: MappedModules["discord/modules/forums/web/Forums.css"]["warnIcon"];
370
+
export const searchIcon: MappedModules["discord/modules/forums/web/Forums.css"]["searchIcon"];
371
+
export const missingReadHistoryPermission: MappedModules["discord/modules/forums/web/Forums.css"]["missingReadHistoryPermission"];
372
+
export const divider: MappedModules["discord/modules/forums/web/Forums.css"]["divider"];
373
+
export const tagsContainer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsContainer"];
374
+
export const filterIcon: MappedModules["discord/modules/forums/web/Forums.css"]["filterIcon"];
375
+
export const tagList: MappedModules["discord/modules/forums/web/Forums.css"]["tagList"];
376
+
export const tagListInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagListInner"];
377
+
export const tag: MappedModules["discord/modules/forums/web/Forums.css"]["tag"];
378
+
export const tagsButton: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButton"];
379
+
export const tagsButtonInner: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonInner"];
380
+
export const tagsButtonPlaceholder: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonPlaceholder"];
381
+
export const tagsButtonWithCount: MappedModules["discord/modules/forums/web/Forums.css"]["tagsButtonWithCount"];
382
+
export const sortDropdown: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdown"];
383
+
export const sortDropdownInner: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownInner"];
384
+
export const sortDropdownText: MappedModules["discord/modules/forums/web/Forums.css"]["sortDropdownText"];
385
+
export const clear: MappedModules["discord/modules/forums/web/Forums.css"]["clear"];
386
+
export const matchingPosts: MappedModules["discord/modules/forums/web/Forums.css"]["matchingPosts"];
387
+
export const startPostHelp: MappedModules["discord/modules/forums/web/Forums.css"]["startPostHelp"];
388
+
export const tagsSpacer: MappedModules["discord/modules/forums/web/Forums.css"]["tagsSpacer"];
389
+
export const keyboardShortcut: MappedModules["discord/modules/forums/web/Forums.css"]["keyboardShortcut"];
390
+
export const key: MappedModules["discord/modules/forums/web/Forums.css"]["key"];
391
+
export const countContainer: MappedModules["discord/modules/forums/web/Forums.css"]["countContainer"];
392
+
export const countText: MappedModules["discord/modules/forums/web/Forums.css"]["countText"];
393
+
export const optInNotice: MappedModules["discord/modules/forums/web/Forums.css"]["optInNotice"];
394
+
}
395
+
396
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Header.css" {
397
+
import { MappedModules } from "@moonlight-mod/mappings";
398
+
export const container: MappedModules["discord/modules/forums/web/Header.css"]["container"];
399
+
export const header: MappedModules["discord/modules/forums/web/Header.css"]["header"];
400
+
export const headerLeft: MappedModules["discord/modules/forums/web/Header.css"]["headerLeft"];
401
+
export const headerText: MappedModules["discord/modules/forums/web/Header.css"]["headerText"];
402
+
export const countContainer: MappedModules["discord/modules/forums/web/Header.css"]["countContainer"];
403
+
export const countText: MappedModules["discord/modules/forums/web/Header.css"]["countText"];
404
+
export const tagContainer: MappedModules["discord/modules/forums/web/Header.css"]["tagContainer"];
405
+
export const tag: MappedModules["discord/modules/forums/web/Header.css"]["tag"];
406
+
export const clear: MappedModules["discord/modules/forums/web/Header.css"]["clear"];
407
+
export const row: MappedModules["discord/modules/forums/web/Header.css"]["row"];
408
+
export const separator: MappedModules["discord/modules/forums/web/Header.css"]["separator"];
409
+
}
410
+
411
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/SortMenu.css" {
412
+
import { MappedModules } from "@moonlight-mod/mappings";
413
+
export const container: MappedModules["discord/modules/forums/web/SortMenu.css"]["container"];
414
+
export const clearText: MappedModules["discord/modules/forums/web/SortMenu.css"]["clearText"];
415
+
}
416
+
417
+
declare module "@moonlight-mod/wp/discord/modules/forums/web/Tag" {
418
+
import { MappedModules } from "@moonlight-mod/mappings";
419
+
const _default: MappedModules["discord/modules/forums/web/Tag"]["default"];
420
+
export default _default;
421
+
export const TagBar: MappedModules["discord/modules/forums/web/Tag"]["TagBar"];
422
+
}
423
+
424
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css" {
425
+
import { MappedModules } from "@moonlight-mod/mappings";
426
+
export const addButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["addButton"];
427
+
export const container: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["container"];
428
+
export const emptyRowContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowContainer"];
429
+
export const emptyRowText: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["emptyRowText"];
430
+
export const headerContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["headerContainer"];
431
+
export const list: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["list"];
432
+
export const memberDetails: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberDetails"];
433
+
export const memberRow: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["memberRow"];
434
+
export const removeButton: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButton"];
435
+
export const removeButtonContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonContainer"];
436
+
export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeButtonDisabled"];
437
+
export const removeTip: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["removeTip"];
438
+
export const searchContainer: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchContainer"];
439
+
export const searchWarning: MappedModules["discord/modules/guild_settings/roles/web/GuildSettingsRoleEdit.css"]["searchWarning"];
440
+
}
441
+
442
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCard.css" {
443
+
import { MappedModules } from "@moonlight-mod/mappings";
444
+
export const card: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["card"];
445
+
export const inModal: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["inModal"];
446
+
export const cardHeader: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["cardHeader"];
447
+
export const title: MappedModules["discord/modules/guild_settings/web/AppCard.css"]["title"];
448
+
}
449
+
450
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/AppCardItem.css" {
451
+
import { MappedModules } from "@moonlight-mod/mappings";
452
+
export const icon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["icon"];
453
+
export const identifier: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["identifier"];
454
+
export const item: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["item"];
455
+
export const statusContainer: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusContainer"];
456
+
export const statusLine: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusLine"];
457
+
export const statusIcon: MappedModules["discord/modules/guild_settings/web/AppCardItem.css"]["statusIcon"];
458
+
}
459
+
460
+
declare module "@moonlight-mod/wp/discord/modules/guild_settings/web/SearchSection.css" {
461
+
import { MappedModules } from "@moonlight-mod/mappings";
462
+
export const container: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["container"];
463
+
export const headerContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["headerContainer"];
464
+
export const searchContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchContainer"];
465
+
export const searchWarning: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["searchWarning"];
466
+
export const addButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["addButton"];
467
+
export const memberRow: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberRow"];
468
+
export const emptyRowContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowContainer"];
469
+
export const emptyRowText: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["emptyRowText"];
470
+
export const memberDetails: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["memberDetails"];
471
+
export const list: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["list"];
472
+
export const removeButtonContainer: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonContainer"];
473
+
export const removeButton: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButton"];
474
+
export const removeButtonDisabled: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeButtonDisabled"];
475
+
export const removeTip: MappedModules["discord/modules/guild_settings/web/SearchSection.css"]["removeTip"];
476
+
}
477
+
478
+
declare module "@moonlight-mod/wp/discord/modules/guild_sidebar/web/CategoryChannel.css" {
479
+
import { MappedModules } from "@moonlight-mod/mappings";
480
+
export const containerDefault: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDefault"];
481
+
export const containerDragBefore: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragBefore"];
482
+
export const containerDragAfter: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["containerDragAfter"];
483
+
export const addButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButton"];
484
+
export const forceVisible: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["forceVisible"];
485
+
export const iconVisibility: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["iconVisibility"];
486
+
export const addButtonIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["addButtonIcon"];
487
+
export const wrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapper"];
488
+
export const wrapperStatic: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["wrapperStatic"];
489
+
export const clickable: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["clickable"];
490
+
export const children: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["children"];
491
+
export const mainContent: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["mainContent"];
492
+
export const icon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["icon"];
493
+
export const collapsed: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["collapsed"];
494
+
export const muted: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["muted"];
495
+
export const name: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["name"];
496
+
export const dismissWrapper: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissWrapper"];
497
+
export const dismissButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismissButton"];
498
+
export const dismiss: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["dismiss"];
499
+
export const voiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsButton"];
500
+
export const voiceChannelsToggleIcon: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["voiceChannelsToggleIcon"];
501
+
export const refreshVoiceChannelsButton: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButton"];
502
+
export const refreshVoiceChannelsButtonInner: MappedModules["discord/modules/guild_sidebar/web/CategoryChannel.css"]["refreshVoiceChannelsButtonInner"];
503
+
}
504
+
505
+
declare module "@moonlight-mod/wp/discord/modules/markup/MarkupUtils" {
506
+
import { MappedModules } from "@moonlight-mod/mappings";
507
+
const _default: MappedModules["discord/modules/markup/MarkupUtils"]["default"];
508
+
export default _default;
509
+
}
510
+
511
+
declare module "@moonlight-mod/wp/discord/modules/menus/web/Menu" {
512
+
import { MappedModules } from "@moonlight-mod/mappings";
513
+
export const MenuSpinner: MappedModules["discord/modules/menus/web/Menu"]["MenuSpinner"];
514
+
export const Menu: MappedModules["discord/modules/menus/web/Menu"]["Menu"];
515
+
}
516
+
517
+
declare module "@moonlight-mod/wp/discord/modules/messages/web/Markup.css" {
518
+
import { MappedModules } from "@moonlight-mod/mappings";
519
+
export const markup: MappedModules["discord/modules/messages/web/Markup.css"]["markup"];
520
+
export const inlineFormat: MappedModules["discord/modules/messages/web/Markup.css"]["inlineFormat"];
521
+
export const codeContainer: MappedModules["discord/modules/messages/web/Markup.css"]["codeContainer"];
522
+
export const codeActions: MappedModules["discord/modules/messages/web/Markup.css"]["codeActions"];
523
+
export const blockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteContainer"];
524
+
export const blockquoteDivider: MappedModules["discord/modules/messages/web/Markup.css"]["blockquoteDivider"];
525
+
export const slateBlockquoteContainer: MappedModules["discord/modules/messages/web/Markup.css"]["slateBlockquoteContainer"];
526
+
export const roleMention: MappedModules["discord/modules/messages/web/Markup.css"]["roleMention"];
527
+
export const rolePopout: MappedModules["discord/modules/messages/web/Markup.css"]["rolePopout"];
528
+
export const roleHeader: MappedModules["discord/modules/messages/web/Markup.css"]["roleHeader"];
529
+
export const roleScroller: MappedModules["discord/modules/messages/web/Markup.css"]["roleScroller"];
530
+
export const timestamp: MappedModules["discord/modules/messages/web/Markup.css"]["timestamp"];
531
+
export const timestampTooltip: MappedModules["discord/modules/messages/web/Markup.css"]["timestampTooltip"];
532
+
}
533
+
534
+
declare module "@moonlight-mod/wp/discord/modules/messages/web/Message.css" {
535
+
import { MappedModules } from "@moonlight-mod/mappings";
536
+
export const wrapper: MappedModules["discord/modules/messages/web/Message.css"]["wrapper"];
537
+
export const compact: MappedModules["discord/modules/messages/web/Message.css"]["compact"];
538
+
export const cozy: MappedModules["discord/modules/messages/web/Message.css"]["cozy"];
539
+
export const contentOnly: MappedModules["discord/modules/messages/web/Message.css"]["contentOnly"];
540
+
export const repliedMessage: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessage"];
541
+
export const threadMessageAccessory: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessory"];
542
+
export const executedCommand: MappedModules["discord/modules/messages/web/Message.css"]["executedCommand"];
543
+
export const latin12CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin12CompactTimeStamp"];
544
+
export const latin24CompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["latin24CompactTimeStamp"];
545
+
export const asianCompactTimeStamp: MappedModules["discord/modules/messages/web/Message.css"]["asianCompactTimeStamp"];
546
+
export const contextCommandMessage: MappedModules["discord/modules/messages/web/Message.css"]["contextCommandMessage"];
547
+
export const messageSpine: MappedModules["discord/modules/messages/web/Message.css"]["messageSpine"];
548
+
export const repliedMessageClickableSpine: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpine"];
549
+
export const repliedMessageContentHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageContentHovered"];
550
+
export const threadMessageAccessoryAvatar: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryAvatar"];
551
+
export const replyAvatar: MappedModules["discord/modules/messages/web/Message.css"]["replyAvatar"];
552
+
export const replyBadge: MappedModules["discord/modules/messages/web/Message.css"]["replyBadge"];
553
+
export const executedCommandAvatar: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandAvatar"];
554
+
export const replyChatIconContainer: MappedModules["discord/modules/messages/web/Message.css"]["replyChatIconContainer"];
555
+
export const replyIcon: MappedModules["discord/modules/messages/web/Message.css"]["replyIcon"];
556
+
export const clanTagChiplet: MappedModules["discord/modules/messages/web/Message.css"]["clanTagChiplet"];
557
+
export const userJoinSystemMessageIcon: MappedModules["discord/modules/messages/web/Message.css"]["userJoinSystemMessageIcon"];
558
+
export const ticketIcon: MappedModules["discord/modules/messages/web/Message.css"]["ticketIcon"];
559
+
export const commandIcon: MappedModules["discord/modules/messages/web/Message.css"]["commandIcon"];
560
+
export const username: MappedModules["discord/modules/messages/web/Message.css"]["username"];
561
+
export const roleDot: MappedModules["discord/modules/messages/web/Message.css"]["roleDot"];
562
+
export const commandName: MappedModules["discord/modules/messages/web/Message.css"]["commandName"];
563
+
export const appsIcon: MappedModules["discord/modules/messages/web/Message.css"]["appsIcon"];
564
+
export const appLauncherOnboardingCommandName: MappedModules["discord/modules/messages/web/Message.css"]["appLauncherOnboardingCommandName"];
565
+
export const targetUsername: MappedModules["discord/modules/messages/web/Message.css"]["targetUsername"];
566
+
export const executedCommandSeparator: MappedModules["discord/modules/messages/web/Message.css"]["executedCommandSeparator"];
567
+
export const repliedTextPreview: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPreview"];
568
+
export const threadMessageAccessoryPreview: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPreview"];
569
+
export const repliedTextContent: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContent"];
570
+
export const clickable: MappedModules["discord/modules/messages/web/Message.css"]["clickable"];
571
+
export const repliedMessageClickableSpineHovered: MappedModules["discord/modules/messages/web/Message.css"]["repliedMessageClickableSpineHovered"];
572
+
export const threadMessageAccessoryContent: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContent"];
573
+
export const repliedTextPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextPlaceholder"];
574
+
export const threadMessageAccessoryPlaceholder: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryPlaceholder"];
575
+
export const repliedTextContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentTrailingIcon"];
576
+
export const threadMessageAccessoryContentTrailingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentTrailingIcon"];
577
+
export const repliedTextContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["repliedTextContentLeadingIcon"];
578
+
export const threadMessageAccessoryContentLeadingIcon: MappedModules["discord/modules/messages/web/Message.css"]["threadMessageAccessoryContentLeadingIcon"];
579
+
export const contents: MappedModules["discord/modules/messages/web/Message.css"]["contents"];
580
+
export const zalgo: MappedModules["discord/modules/messages/web/Message.css"]["zalgo"];
581
+
export const messageContent: MappedModules["discord/modules/messages/web/Message.css"]["messageContent"];
582
+
export const header: MappedModules["discord/modules/messages/web/Message.css"]["header"];
583
+
export const buttonContainer: MappedModules["discord/modules/messages/web/Message.css"]["buttonContainer"];
584
+
export const avatar: MappedModules["discord/modules/messages/web/Message.css"]["avatar"];
585
+
export const avatarDecoration: MappedModules["discord/modules/messages/web/Message.css"]["avatarDecoration"];
586
+
export const roleIcon: MappedModules["discord/modules/messages/web/Message.css"]["roleIcon"];
587
+
export const timestamp: MappedModules["discord/modules/messages/web/Message.css"]["timestamp"];
588
+
export const timestampInline: MappedModules["discord/modules/messages/web/Message.css"]["timestampInline"];
589
+
export const alt: MappedModules["discord/modules/messages/web/Message.css"]["alt"];
590
+
export const timestampTooltip: MappedModules["discord/modules/messages/web/Message.css"]["timestampTooltip"];
591
+
export const timestampVisibleOnHover: MappedModules["discord/modules/messages/web/Message.css"]["timestampVisibleOnHover"];
592
+
export const nitroAuthorBadgeTootip: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeTootip"];
593
+
export const headerText: MappedModules["discord/modules/messages/web/Message.css"]["headerText"];
594
+
export const hasRoleIcon: MappedModules["discord/modules/messages/web/Message.css"]["hasRoleIcon"];
595
+
export const hasBadges: MappedModules["discord/modules/messages/web/Message.css"]["hasBadges"];
596
+
export const botTagCompact: MappedModules["discord/modules/messages/web/Message.css"]["botTagCompact"];
597
+
export const botTagCozy: MappedModules["discord/modules/messages/web/Message.css"]["botTagCozy"];
598
+
export const nitroBadgeSvg: MappedModules["discord/modules/messages/web/Message.css"]["nitroBadgeSvg"];
599
+
export const nitroAuthorBadgeContainer: MappedModules["discord/modules/messages/web/Message.css"]["nitroAuthorBadgeContainer"];
600
+
export const separator: MappedModules["discord/modules/messages/web/Message.css"]["separator"];
601
+
export const hasThread: MappedModules["discord/modules/messages/web/Message.css"]["hasThread"];
602
+
export const isSystemMessage: MappedModules["discord/modules/messages/web/Message.css"]["isSystemMessage"];
603
+
export const hasReply: MappedModules["discord/modules/messages/web/Message.css"]["hasReply"];
604
+
export const markupRtl: MappedModules["discord/modules/messages/web/Message.css"]["markupRtl"];
605
+
export const isSending: MappedModules["discord/modules/messages/web/Message.css"]["isSending"];
606
+
export const isFailed: MappedModules["discord/modules/messages/web/Message.css"]["isFailed"];
607
+
export const isUnsupported: MappedModules["discord/modules/messages/web/Message.css"]["isUnsupported"];
608
+
export const edited: MappedModules["discord/modules/messages/web/Message.css"]["edited"];
609
+
export const communicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabled"];
610
+
export const compactCommunicationDisabled: MappedModules["discord/modules/messages/web/Message.css"]["compactCommunicationDisabled"];
611
+
export const communicationDisabledOpacity: MappedModules["discord/modules/messages/web/Message.css"]["communicationDisabledOpacity"];
612
+
export const badgesContainer: MappedModules["discord/modules/messages/web/Message.css"]["badgesContainer"];
613
+
}
614
+
615
+
declare module "@moonlight-mod/wp/discord/modules/modals/Modals" {
616
+
import { MappedModules } from "@moonlight-mod/mappings";
617
+
export const closeAllModals: MappedModules["discord/modules/modals/Modals"]["closeAllModals"];
618
+
export const closeAllModalsForContext: MappedModules["discord/modules/modals/Modals"]["closeAllModalsForContext"];
619
+
export const closeModal: MappedModules["discord/modules/modals/Modals"]["closeModal"];
620
+
export const getInteractingModalContext: MappedModules["discord/modules/modals/Modals"]["getInteractingModalContext"];
621
+
export const hasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpen"];
622
+
export const hasAnyModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasAnyModalOpenSelector"];
623
+
export const hasModalOpen: MappedModules["discord/modules/modals/Modals"]["hasModalOpen"];
624
+
export const hasModalOpenSelector: MappedModules["discord/modules/modals/Modals"]["hasModalOpenSelector"];
625
+
export const openModal: MappedModules["discord/modules/modals/Modals"]["openModal"];
626
+
export const openModalLazy: MappedModules["discord/modules/modals/Modals"]["openModalLazy"];
627
+
export const updateModal: MappedModules["discord/modules/modals/Modals"]["updateModal"];
628
+
export const useHasAnyModalOpen: MappedModules["discord/modules/modals/Modals"]["useHasAnyModalOpen"];
629
+
export const useIsModalAtTop: MappedModules["discord/modules/modals/Modals"]["useIsModalAtTop"];
630
+
export const useModalsStore: MappedModules["discord/modules/modals/Modals"]["useModalsStore"];
631
+
}
632
+
633
+
declare module "@moonlight-mod/wp/discord/modules/oauth2/index" {
634
+
import { MappedModules } from "@moonlight-mod/mappings";
635
+
export const OAuth2AuthorizeModal: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizeModal"];
636
+
export const OAuth2AuthorizePage: MappedModules["discord/modules/oauth2/index"]["OAuth2AuthorizePage"];
637
+
export const getOAuth2AuthorizeProps: MappedModules["discord/modules/oauth2/index"]["getOAuth2AuthorizeProps"];
638
+
export const openOAuth2Modal: MappedModules["discord/modules/oauth2/index"]["openOAuth2Modal"];
639
+
export const openOAuth2ModalWithCreateGuildModal: MappedModules["discord/modules/oauth2/index"]["openOAuth2ModalWithCreateGuildModal"];
640
+
export const useOAuth2AuthorizeForm: MappedModules["discord/modules/oauth2/index"]["useOAuth2AuthorizeForm"];
641
+
}
642
+
643
+
declare module "@moonlight-mod/wp/discord/modules/people/web/PeoplePage.css" {
644
+
import { MappedModules } from "@moonlight-mod/mappings";
645
+
export const addFriend: MappedModules["discord/modules/people/web/PeoplePage.css"]["addFriend"];
646
+
export const badge: MappedModules["discord/modules/people/web/PeoplePage.css"]["badge"];
647
+
export const container: MappedModules["discord/modules/people/web/PeoplePage.css"]["container"];
648
+
export const inviteToolbar: MappedModules["discord/modules/people/web/PeoplePage.css"]["inviteToolbar"];
649
+
export const item: MappedModules["discord/modules/people/web/PeoplePage.css"]["item"];
650
+
export const nowPlayingColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["nowPlayingColumn"];
651
+
export const peopleColumn: MappedModules["discord/modules/people/web/PeoplePage.css"]["peopleColumn"];
652
+
export const tabBar: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBar"];
653
+
export const tabBody: MappedModules["discord/modules/people/web/PeoplePage.css"]["tabBody"];
654
+
}
655
+
656
+
declare module "@moonlight-mod/wp/discord/modules/user_profile/web/BiteSizeActivity.css" {
657
+
import { MappedModules } from "@moonlight-mod/mappings";
658
+
export const header: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["header"];
659
+
export const headerTag: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["headerTag"];
660
+
export const body: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["body"];
661
+
export const footer: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["footer"];
662
+
export const backdrop: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["backdrop"];
663
+
export const toast: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["toast"];
664
+
export const activity: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["activity"];
665
+
export const upsell: MappedModules["discord/modules/user_profile/web/BiteSizeActivity.css"]["upsell"];
666
+
}
667
+
668
+
declare module "@moonlight-mod/wp/discord/packages/flux" {
669
+
import { MappedModules } from "@moonlight-mod/mappings";
670
+
export const BatchedStoreListener: MappedModules["discord/packages/flux"]["BatchedStoreListener"];
671
+
export const Dispatcher: MappedModules["discord/packages/flux"]["Dispatcher"];
672
+
export const Store: MappedModules["discord/packages/flux"]["Store"];
673
+
const _default: MappedModules["discord/packages/flux"]["default"];
674
+
export default _default;
675
+
export const statesWillNeverBeEqual: MappedModules["discord/packages/flux"]["statesWillNeverBeEqual"];
676
+
export const useStateFromStores: MappedModules["discord/packages/flux"]["useStateFromStores"];
677
+
export const useStateFromStoresArray: MappedModules["discord/packages/flux"]["useStateFromStoresArray"];
678
+
export const useStateFromStoresObject: MappedModules["discord/packages/flux"]["useStateFromStoresObject"];
679
+
}
680
+
681
+
declare module "@moonlight-mod/wp/discord/packages/flux/BatchedStoreListener" {
682
+
import { MappedModules } from "@moonlight-mod/mappings";
683
+
const _default: MappedModules["discord/packages/flux/BatchedStoreListener"]["default"];
684
+
export default _default;
685
+
}
686
+
687
+
declare module "@moonlight-mod/wp/discord/packages/flux/ChangeListeners" {
688
+
import { MappedModules } from "@moonlight-mod/mappings";
689
+
const _default: MappedModules["discord/packages/flux/ChangeListeners"]["default"];
690
+
export default _default;
691
+
}
692
+
693
+
declare module "@moonlight-mod/wp/discord/packages/flux/Dispatcher" {
694
+
import { MappedModules } from "@moonlight-mod/mappings";
695
+
export const Dispatcher: MappedModules["discord/packages/flux/Dispatcher"]["Dispatcher"];
696
+
}
697
+
698
+
declare module "@moonlight-mod/wp/discord/packages/flux/Emitter" {
699
+
import { MappedModules } from "@moonlight-mod/mappings";
700
+
const _default: MappedModules["discord/packages/flux/Emitter"]["default"];
701
+
export default _default;
702
+
}
703
+
704
+
declare module "@moonlight-mod/wp/discord/packages/flux/LoggingUtils" {
705
+
import { MappedModules } from "@moonlight-mod/mappings";
706
+
const _default: MappedModules["discord/packages/flux/LoggingUtils"]["default"];
707
+
export default _default;
708
+
}
709
+
710
+
declare module "@moonlight-mod/wp/discord/packages/flux/PersistedStore" {
711
+
import { MappedModules } from "@moonlight-mod/mappings";
712
+
export const PersistedStore: MappedModules["discord/packages/flux/PersistedStore"]["PersistedStore"];
713
+
}
714
+
715
+
declare module "@moonlight-mod/wp/discord/packages/flux/Store" {
716
+
import { MappedModules } from "@moonlight-mod/mappings";
717
+
export const Store: MappedModules["discord/packages/flux/Store"]["Store"];
718
+
}
719
+
720
+
declare module "@moonlight-mod/wp/discord/packages/flux/connectStores" {
721
+
import { MappedModules } from "@moonlight-mod/mappings";
722
+
const _default: MappedModules["discord/packages/flux/connectStores"]["default"];
723
+
export default _default;
724
+
}
725
+
726
+
declare module "@moonlight-mod/wp/discord/records/UserRecord" {
727
+
import { MappedModules } from "@moonlight-mod/mappings";
728
+
const _default: MappedModules["discord/records/UserRecord"]["default"];
729
+
export default _default;
730
+
}
731
+
732
+
declare module "@moonlight-mod/wp/discord/styles/shared/Margins.css" {
733
+
import { MappedModules } from "@moonlight-mod/mappings";
734
+
export const marginReset: MappedModules["discord/styles/shared/Margins.css"]["marginReset"];
735
+
export const marginTop4: MappedModules["discord/styles/shared/Margins.css"]["marginTop4"];
736
+
export const marginBottom4: MappedModules["discord/styles/shared/Margins.css"]["marginBottom4"];
737
+
export const marginTop8: MappedModules["discord/styles/shared/Margins.css"]["marginTop8"];
738
+
export const marginBottom8: MappedModules["discord/styles/shared/Margins.css"]["marginBottom8"];
739
+
export const marginTop20: MappedModules["discord/styles/shared/Margins.css"]["marginTop20"];
740
+
export const marginBottom20: MappedModules["discord/styles/shared/Margins.css"]["marginBottom20"];
741
+
export const marginTop40: MappedModules["discord/styles/shared/Margins.css"]["marginTop40"];
742
+
export const marginBottom40: MappedModules["discord/styles/shared/Margins.css"]["marginBottom40"];
743
+
export const marginTop60: MappedModules["discord/styles/shared/Margins.css"]["marginTop60"];
744
+
export const marginBottom60: MappedModules["discord/styles/shared/Margins.css"]["marginBottom60"];
745
+
export const marginCenterHorz: MappedModules["discord/styles/shared/Margins.css"]["marginCenterHorz"];
746
+
export const marginLeft8: MappedModules["discord/styles/shared/Margins.css"]["marginLeft8"];
747
+
}
748
+
749
+
declare module "@moonlight-mod/wp/discord/uikit/Flex" {
750
+
import { MappedModules } from "@moonlight-mod/mappings";
751
+
const _default: MappedModules["discord/uikit/Flex"]["default"];
752
+
export default _default;
753
+
}
754
+
755
+
declare module "@moonlight-mod/wp/discord/utils/ClipboardUtils" {
756
+
import { MappedModules } from "@moonlight-mod/mappings";
757
+
export const SUPPORTS_COPY: MappedModules["discord/utils/ClipboardUtils"]["SUPPORTS_COPY"];
758
+
export const copy: MappedModules["discord/utils/ClipboardUtils"]["copy"];
759
+
}
760
+
761
+
declare module "@moonlight-mod/wp/discord/utils/ComponentDispatchUtils" {
762
+
import { MappedModules } from "@moonlight-mod/mappings";
763
+
export const ComponentDispatcher: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatcher"];
764
+
export const ComponentDispatch: MappedModules["discord/utils/ComponentDispatchUtils"]["ComponentDispatch"];
765
+
}
766
+
767
+
declare module "@moonlight-mod/wp/discord/utils/HTTPUtils" {
768
+
import { MappedModules } from "@moonlight-mod/mappings";
769
+
export const HTTP: MappedModules["discord/utils/HTTPUtils"]["HTTP"];
770
+
}
771
+
772
+
declare module "@moonlight-mod/wp/discord/utils/MaskedLinkUtils" {
773
+
import { MappedModules } from "@moonlight-mod/mappings";
774
+
export const isLinkTrusted: MappedModules["discord/utils/MaskedLinkUtils"]["isLinkTrusted"];
775
+
export const handleClick: MappedModules["discord/utils/MaskedLinkUtils"]["handleClick"];
776
+
}
777
+
778
+
declare module "@moonlight-mod/wp/discord/utils/NativeUtils" {
779
+
import { MappedModules } from "@moonlight-mod/mappings";
780
+
const _default: MappedModules["discord/utils/NativeUtils"]["default"];
781
+
export default _default;
782
+
}
783
+
784
+
declare module "@moonlight-mod/wp/highlight.js" {
785
+
import { MappedModules } from "@moonlight-mod/mappings";
786
+
export const highlight: MappedModules["highlight.js"]["highlight"];
787
+
export const highlightAuto: MappedModules["highlight.js"]["highlightAuto"];
788
+
export const fixMarkup: MappedModules["highlight.js"]["fixMarkup"];
789
+
export const highlightBlock: MappedModules["highlight.js"]["highlightBlock"];
790
+
export const configure: MappedModules["highlight.js"]["configure"];
791
+
export const initHighlighting: MappedModules["highlight.js"]["initHighlighting"];
792
+
export const initHighlightingOnLoad: MappedModules["highlight.js"]["initHighlightingOnLoad"];
793
+
export const registerLanguage: MappedModules["highlight.js"]["registerLanguage"];
794
+
export const listLanguages: MappedModules["highlight.js"]["listLanguages"];
795
+
export const getLanguage: MappedModules["highlight.js"]["getLanguage"];
796
+
export const inherit: MappedModules["highlight.js"]["inherit"];
797
+
export const COMMENT: MappedModules["highlight.js"]["COMMENT"];
798
+
export const IDENT_RE: MappedModules["highlight.js"]["IDENT_RE"];
799
+
export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js"]["UNDERSCORE_IDENT_RE"];
800
+
export const NUMBER_RE: MappedModules["highlight.js"]["NUMBER_RE"];
801
+
export const C_NUMBER_RE: MappedModules["highlight.js"]["C_NUMBER_RE"];
802
+
export const BINARY_NUMBER_RE: MappedModules["highlight.js"]["BINARY_NUMBER_RE"];
803
+
export const RE_STARTERS_RE: MappedModules["highlight.js"]["RE_STARTERS_RE"];
804
+
export const BACKSLASH_ESCAPE: MappedModules["highlight.js"]["BACKSLASH_ESCAPE"];
805
+
export const APOS_STRING_MODE: MappedModules["highlight.js"]["APOS_STRING_MODE"];
806
+
export const QUOTE_STRING_MODE: MappedModules["highlight.js"]["QUOTE_STRING_MODE"];
807
+
export const PHRASAL_WORDS_MODE: MappedModules["highlight.js"]["PHRASAL_WORDS_MODE"];
808
+
export const C_LINE_COMMENT_MODE: MappedModules["highlight.js"]["C_LINE_COMMENT_MODE"];
809
+
export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js"]["C_BLOCK_COMMENT_MODE"];
810
+
export const HASH_COMMENT_MODE: MappedModules["highlight.js"]["HASH_COMMENT_MODE"];
811
+
export const NUMBER_MODE: MappedModules["highlight.js"]["NUMBER_MODE"];
812
+
export const C_NUMBER_MODE: MappedModules["highlight.js"]["C_NUMBER_MODE"];
813
+
export const BINARY_NUMBER_MODE: MappedModules["highlight.js"]["BINARY_NUMBER_MODE"];
814
+
export const CSS_NUMBER_MODE: MappedModules["highlight.js"]["CSS_NUMBER_MODE"];
815
+
export const REGEX_MODE: MappedModules["highlight.js"]["REGEX_MODE"];
816
+
export const TITLE_MODE: MappedModules["highlight.js"]["TITLE_MODE"];
817
+
export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js"]["UNDERSCORE_TITLE_MODE"];
818
+
const _default: MappedModules["highlight.js"]["default"];
819
+
export default _default;
820
+
export const HighlightJS: MappedModules["highlight.js"]["HighlightJS"];
821
+
}
822
+
823
+
declare module "@moonlight-mod/wp/highlight.js/lib/core" {
824
+
import { MappedModules } from "@moonlight-mod/mappings";
825
+
export const highlight: MappedModules["highlight.js/lib/core"]["highlight"];
826
+
export const highlightAuto: MappedModules["highlight.js/lib/core"]["highlightAuto"];
827
+
export const fixMarkup: MappedModules["highlight.js/lib/core"]["fixMarkup"];
828
+
export const highlightBlock: MappedModules["highlight.js/lib/core"]["highlightBlock"];
829
+
export const configure: MappedModules["highlight.js/lib/core"]["configure"];
830
+
export const initHighlighting: MappedModules["highlight.js/lib/core"]["initHighlighting"];
831
+
export const initHighlightingOnLoad: MappedModules["highlight.js/lib/core"]["initHighlightingOnLoad"];
832
+
export const registerLanguage: MappedModules["highlight.js/lib/core"]["registerLanguage"];
833
+
export const listLanguages: MappedModules["highlight.js/lib/core"]["listLanguages"];
834
+
export const getLanguage: MappedModules["highlight.js/lib/core"]["getLanguage"];
835
+
export const inherit: MappedModules["highlight.js/lib/core"]["inherit"];
836
+
export const COMMENT: MappedModules["highlight.js/lib/core"]["COMMENT"];
837
+
export const IDENT_RE: MappedModules["highlight.js/lib/core"]["IDENT_RE"];
838
+
export const UNDERSCORE_IDENT_RE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_IDENT_RE"];
839
+
export const NUMBER_RE: MappedModules["highlight.js/lib/core"]["NUMBER_RE"];
840
+
export const C_NUMBER_RE: MappedModules["highlight.js/lib/core"]["C_NUMBER_RE"];
841
+
export const BINARY_NUMBER_RE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_RE"];
842
+
export const RE_STARTERS_RE: MappedModules["highlight.js/lib/core"]["RE_STARTERS_RE"];
843
+
export const BACKSLASH_ESCAPE: MappedModules["highlight.js/lib/core"]["BACKSLASH_ESCAPE"];
844
+
export const APOS_STRING_MODE: MappedModules["highlight.js/lib/core"]["APOS_STRING_MODE"];
845
+
export const QUOTE_STRING_MODE: MappedModules["highlight.js/lib/core"]["QUOTE_STRING_MODE"];
846
+
export const PHRASAL_WORDS_MODE: MappedModules["highlight.js/lib/core"]["PHRASAL_WORDS_MODE"];
847
+
export const C_LINE_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_LINE_COMMENT_MODE"];
848
+
export const C_BLOCK_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["C_BLOCK_COMMENT_MODE"];
849
+
export const HASH_COMMENT_MODE: MappedModules["highlight.js/lib/core"]["HASH_COMMENT_MODE"];
850
+
export const NUMBER_MODE: MappedModules["highlight.js/lib/core"]["NUMBER_MODE"];
851
+
export const C_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["C_NUMBER_MODE"];
852
+
export const BINARY_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["BINARY_NUMBER_MODE"];
853
+
export const CSS_NUMBER_MODE: MappedModules["highlight.js/lib/core"]["CSS_NUMBER_MODE"];
854
+
export const REGEX_MODE: MappedModules["highlight.js/lib/core"]["REGEX_MODE"];
855
+
export const TITLE_MODE: MappedModules["highlight.js/lib/core"]["TITLE_MODE"];
856
+
export const UNDERSCORE_TITLE_MODE: MappedModules["highlight.js/lib/core"]["UNDERSCORE_TITLE_MODE"];
857
+
}
858
+
859
+
declare module "@moonlight-mod/wp/lodash" {}
860
+
861
+
declare module "@moonlight-mod/wp/murmurhash" {
862
+
import { MappedModules } from "@moonlight-mod/mappings";
863
+
export const v2: MappedModules["murmurhash"]["v2"];
864
+
export const v3: MappedModules["murmurhash"]["v3"];
865
+
}
866
+
867
+
declare module "@moonlight-mod/wp/platform.js" {
868
+
import { MappedModules } from "@moonlight-mod/mappings";
869
+
export const description: MappedModules["platform.js"]["description"];
870
+
export const layout: MappedModules["platform.js"]["layout"];
871
+
export const manufacturer: MappedModules["platform.js"]["manufacturer"];
872
+
export const name: MappedModules["platform.js"]["name"];
873
+
export const prerelease: MappedModules["platform.js"]["prerelease"];
874
+
export const product: MappedModules["platform.js"]["product"];
875
+
export const ua: MappedModules["platform.js"]["ua"];
876
+
export const version: MappedModules["platform.js"]["version"];
877
+
export const os: MappedModules["platform.js"]["os"];
878
+
export const parse: MappedModules["platform.js"]["parse"];
879
+
export const toString: MappedModules["platform.js"]["toString"];
880
+
}
881
+
882
+
declare module "@moonlight-mod/wp/react" {
883
+
import { MappedModules } from "@moonlight-mod/mappings";
884
+
const _: Omit<MappedModules["react"], "__mappings_exportEquals">;
885
+
export = _;
886
+
}
887
+
888
+
declare module "@moonlight-mod/wp/uuid/v4" {}
+8
-9
packages/types/tsconfig.json
+8
-9
packages/types/tsconfig.json
···
1
1
{
2
2
"compilerOptions": {
3
-
"target": "es2016",
4
-
"module": "es6",
3
+
"target": "ES2016",
4
+
"jsx": "react",
5
+
"module": "ES6",
6
+
"moduleResolution": "bundler",
7
+
"strict": true,
8
+
"declaration": true,
5
9
"esModuleInterop": true,
6
-
"forceConsistentCasingInFileNames": true,
7
-
"strict": true,
8
-
"skipLibCheck": true,
9
-
"moduleResolution": "bundler",
10
-
"jsx": "react",
11
-
"declaration": true
10
+
"forceConsistentCasingInFileNames": true
12
11
},
13
-
"include": ["./src/**/*", "src/index.ts"]
12
+
"include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"]
14
13
}
+13
-1
packages/web-preload/package.json
+13
-1
packages/web-preload/package.json
···
1
1
{
2
2
"name": "@moonlight-mod/web-preload",
3
3
"private": true,
4
+
"main": "src/index.ts",
5
+
"engineStrict": true,
6
+
"engines": {
7
+
"node": ">=22",
8
+
"pnpm": ">=10",
9
+
"npm": "pnpm",
10
+
"yarn": "pnpm"
11
+
},
4
12
"dependencies": {
5
-
"@moonlight-mod/core": "workspace:*"
13
+
"@moonlight-mod/core": "workspace:*",
14
+
"@moonlight-mod/lunast": "catalog:prod",
15
+
"@moonlight-mod/mappings": "catalog:prod",
16
+
"@moonlight-mod/moonmap": "catalog:prod",
17
+
"@moonlight-mod/types": "workspace:*"
6
18
}
7
19
}
+45
-10
packages/web-preload/src/index.ts
+45
-10
packages/web-preload/src/index.ts
···
1
-
import {
2
-
loadExtensions,
3
-
loadProcessedExtensions
4
-
} from "@moonlight-mod/core/extension/loader";
5
-
import { installWebpackPatcher } from "@moonlight-mod/core/patch";
6
-
import Logger from "@moonlight-mod/core/util/logger";
1
+
import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader";
2
+
import { installWebpackPatcher, onModuleLoad, registerPatch, registerWebpackModule } from "@moonlight-mod/core/patch";
3
+
import { constants, MoonlightBranch } from "@moonlight-mod/types";
4
+
import { installStyles } from "@moonlight-mod/core/styles";
5
+
import Logger, { initLogger } from "@moonlight-mod/core/util/logger";
6
+
import LunAST from "@moonlight-mod/lunast";
7
+
import Moonmap from "@moonlight-mod/moonmap";
8
+
import loadMappings from "@moonlight-mod/mappings";
9
+
import { createEventEmitter } from "@moonlight-mod/core/util/event";
10
+
import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event";
7
11
8
-
(async () => {
12
+
async function load() {
13
+
delete window._moonlightWebLoad;
14
+
initLogger(moonlightNode.config);
9
15
const logger = new Logger("web-preload");
10
16
11
17
window.moonlight = {
18
+
patched: new Map(),
12
19
unpatched: new Set(),
20
+
pendingModules: new Set(),
13
21
enabledExtensions: new Set(),
14
22
23
+
events: createEventEmitter<WebEventType, WebEventPayloads>(),
24
+
patchingInternals: {
25
+
onModuleLoad,
26
+
registerPatch,
27
+
registerWebpackModule
28
+
},
29
+
localStorage: window.localStorage,
30
+
31
+
version: MOONLIGHT_VERSION,
32
+
branch: MOONLIGHT_BRANCH as MoonlightBranch,
33
+
apiLevel: constants.apiLevel,
34
+
15
35
getConfig: moonlightNode.getConfig.bind(moonlightNode),
16
36
getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode),
37
+
setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode),
38
+
writeConfig: moonlightNode.writeConfig.bind(moonlightNode),
39
+
17
40
getNatives: moonlightNode.getNatives.bind(moonlightNode),
18
-
getLogger: (id: string) => {
41
+
getLogger(id) {
19
42
return new Logger(id);
20
-
}
43
+
},
44
+
45
+
lunast: new LunAST(),
46
+
moonmap: new Moonmap()
21
47
};
22
48
23
49
try {
50
+
loadMappings(window.moonlight.moonmap, window.moonlight.lunast);
24
51
await loadProcessedExtensions(moonlightNode.processedExtensions);
25
52
await installWebpackPatcher();
26
53
} catch (e) {
27
54
logger.error("Error setting up web-preload", e);
28
55
}
29
-
})();
56
+
57
+
if (document.readyState === "complete") {
58
+
installStyles();
59
+
} else {
60
+
window.addEventListener("load", installStyles);
61
+
}
62
+
}
63
+
64
+
window._moonlightWebLoad = load;
+4
-1
packages/web-preload/tsconfig.json
+4
-1
packages/web-preload/tsconfig.json
+2901
-363
pnpm-lock.yaml
+2901
-363
pnpm-lock.yaml
···
1
-
lockfileVersion: '6.0'
1
+
lockfileVersion: '9.0'
2
2
3
3
settings:
4
4
autoInstallPeers: true
5
5
excludeLinksFromLockfile: false
6
6
7
+
catalogs:
8
+
dev:
9
+
'@moonlight-mod/eslint-config':
10
+
specifier: github:moonlight-mod/eslint-config
11
+
version: 1.0.1
12
+
'@types/chrome':
13
+
specifier: ^0.0.313
14
+
version: 0.0.313
15
+
'@types/node':
16
+
specifier: ^22.14.0
17
+
version: 22.14.0
18
+
esbuild:
19
+
specifier: ^0.19.3
20
+
version: 0.19.3
21
+
esbuild-copy-static-files:
22
+
specifier: ^0.1.0
23
+
version: 0.1.0
24
+
eslint:
25
+
specifier: ^9.12.0
26
+
version: 9.23.0
27
+
husky:
28
+
specifier: ^8.0.3
29
+
version: 8.0.3
30
+
prettier:
31
+
specifier: ^3.1.0
32
+
version: 3.1.0
33
+
taze:
34
+
specifier: ^19.0.4
35
+
version: 19.0.4
36
+
typescript:
37
+
specifier: ^5.3.3
38
+
version: 5.8.2
39
+
prod:
40
+
'@moonlight-mod/lunast':
41
+
specifier: ^1.0.1
42
+
version: 1.0.1
43
+
'@moonlight-mod/mappings':
44
+
specifier: ^1.1.25
45
+
version: 1.1.25
46
+
'@moonlight-mod/moonmap':
47
+
specifier: ^1.0.5
48
+
version: 1.0.5
49
+
'@zenfs/core':
50
+
specifier: ^2.0.0
51
+
version: 2.0.0
52
+
'@zenfs/dom':
53
+
specifier: ^1.1.3
54
+
version: 1.1.6
55
+
microdiff:
56
+
specifier: ^1.5.0
57
+
version: 1.5.0
58
+
nanotar:
59
+
specifier: ^0.1.1
60
+
version: 0.1.1
61
+
7
62
importers:
8
63
9
64
.:
10
65
devDependencies:
66
+
'@moonlight-mod/eslint-config':
67
+
specifier: catalog:dev
68
+
version: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(@types/eslint@9.6.1)(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)(typescript@5.8.2)
69
+
'@types/node':
70
+
specifier: catalog:dev
71
+
version: 22.14.0
11
72
esbuild:
12
-
specifier: ^0.19.3
73
+
specifier: catalog:dev
13
74
version: 0.19.3
14
75
esbuild-copy-static-files:
15
-
specifier: ^0.1.0
76
+
specifier: catalog:dev
16
77
version: 0.1.0
78
+
eslint:
79
+
specifier: catalog:dev
80
+
version: 9.23.0(jiti@2.4.2)
81
+
husky:
82
+
specifier: catalog:dev
83
+
version: 8.0.3
84
+
prettier:
85
+
specifier: catalog:dev
86
+
version: 3.1.0
87
+
taze:
88
+
specifier: catalog:dev
89
+
version: 19.0.4
90
+
typescript:
91
+
specifier: catalog:dev
92
+
version: 5.8.2
93
+
94
+
packages/browser:
95
+
dependencies:
96
+
'@moonlight-mod/core':
97
+
specifier: workspace:*
98
+
version: link:../core
99
+
'@moonlight-mod/types':
100
+
specifier: workspace:*
101
+
version: link:../types
102
+
'@moonlight-mod/web-preload':
103
+
specifier: workspace:*
104
+
version: link:../web-preload
105
+
'@zenfs/core':
106
+
specifier: catalog:prod
107
+
version: 2.0.0
108
+
'@zenfs/dom':
109
+
specifier: catalog:prod
110
+
version: 1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)
111
+
devDependencies:
112
+
'@types/chrome':
113
+
specifier: catalog:dev
114
+
version: 0.0.313
17
115
18
116
packages/core:
19
117
dependencies:
20
118
'@moonlight-mod/types':
21
119
specifier: workspace:*
22
120
version: link:../types
23
-
glob:
24
-
specifier: ^10.3.4
25
-
version: 10.3.4
26
121
27
122
packages/core-extensions:
28
123
dependencies:
29
-
'@electron/asar':
30
-
specifier: ^3.2.5
31
-
version: 3.2.5
124
+
'@moonlight-mod/core':
125
+
specifier: workspace:*
126
+
version: link:../core
32
127
'@moonlight-mod/types':
33
128
specifier: workspace:*
34
129
version: link:../types
130
+
microdiff:
131
+
specifier: catalog:prod
132
+
version: 1.5.0
133
+
nanotar:
134
+
specifier: catalog:prod
135
+
version: 0.1.1
35
136
36
137
packages/injector:
37
138
dependencies:
···
53
154
54
155
packages/types:
55
156
dependencies:
56
-
'@types/flux':
57
-
specifier: ^3.1.12
58
-
version: 3.1.12
59
-
'@types/node':
60
-
specifier: ^20.6.2
61
-
version: 20.6.2
157
+
'@moonlight-mod/lunast':
158
+
specifier: ^1.0.1
159
+
version: 1.0.1
160
+
'@moonlight-mod/mappings':
161
+
specifier: ^1.1.25
162
+
version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)
163
+
'@moonlight-mod/moonmap':
164
+
specifier: ^1.0.5
165
+
version: 1.0.5
62
166
'@types/react':
63
-
specifier: ^18.2.22
64
-
version: 18.2.22
167
+
specifier: ^18.3.10
168
+
version: 18.3.20
65
169
csstype:
66
-
specifier: ^3.1.2
67
-
version: 3.1.2
170
+
specifier: ^3.1.3
171
+
version: 3.1.3
68
172
standalone-electron-types:
69
173
specifier: ^1.0.0
70
174
version: 1.0.0
···
74
178
'@moonlight-mod/core':
75
179
specifier: workspace:*
76
180
version: link:../core
181
+
'@moonlight-mod/lunast':
182
+
specifier: catalog:prod
183
+
version: 1.0.1
184
+
'@moonlight-mod/mappings':
185
+
specifier: catalog:prod
186
+
version: 1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)
187
+
'@moonlight-mod/moonmap':
188
+
specifier: catalog:prod
189
+
version: 1.0.5
190
+
'@moonlight-mod/types':
191
+
specifier: workspace:*
192
+
version: link:../types
77
193
78
194
packages:
79
195
80
-
/@electron/asar@3.2.5:
81
-
resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==}
82
-
engines: {node: '>=10.12.0'}
196
+
'@aashutoshrathi/word-wrap@1.2.6':
197
+
resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==}
198
+
engines: {node: '>=0.10.0'}
199
+
200
+
'@antfu/ni@24.3.0':
201
+
resolution: {integrity: sha512-wBSav4mBxvHEW9RbdSo1SWLQ6MAlT0Dc423weC58yOWqW4OcMvtnNDdDrxOZeJ88fEIyPK93gDUWIelBxzSf8g==}
83
202
hasBin: true
84
-
dependencies:
85
-
commander: 5.1.0
86
-
glob: 7.2.3
87
-
minimatch: 3.1.2
88
-
dev: false
89
203
90
-
/@esbuild/android-arm64@0.19.3:
204
+
'@esbuild/android-arm64@0.19.3':
91
205
resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==}
92
206
engines: {node: '>=12'}
93
207
cpu: [arm64]
94
208
os: [android]
95
-
requiresBuild: true
96
-
dev: true
97
-
optional: true
98
209
99
-
/@esbuild/android-arm@0.19.3:
210
+
'@esbuild/android-arm@0.19.3':
100
211
resolution: {integrity: sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==}
101
212
engines: {node: '>=12'}
102
213
cpu: [arm]
103
214
os: [android]
104
-
requiresBuild: true
105
-
dev: true
106
-
optional: true
107
215
108
-
/@esbuild/android-x64@0.19.3:
216
+
'@esbuild/android-x64@0.19.3':
109
217
resolution: {integrity: sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==}
110
218
engines: {node: '>=12'}
111
219
cpu: [x64]
112
220
os: [android]
113
-
requiresBuild: true
114
-
dev: true
115
-
optional: true
116
221
117
-
/@esbuild/darwin-arm64@0.19.3:
222
+
'@esbuild/darwin-arm64@0.19.3':
118
223
resolution: {integrity: sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==}
119
224
engines: {node: '>=12'}
120
225
cpu: [arm64]
121
226
os: [darwin]
122
-
requiresBuild: true
123
-
dev: true
124
-
optional: true
125
227
126
-
/@esbuild/darwin-x64@0.19.3:
228
+
'@esbuild/darwin-x64@0.19.3':
127
229
resolution: {integrity: sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==}
128
230
engines: {node: '>=12'}
129
231
cpu: [x64]
130
232
os: [darwin]
131
-
requiresBuild: true
132
-
dev: true
133
-
optional: true
134
233
135
-
/@esbuild/freebsd-arm64@0.19.3:
234
+
'@esbuild/freebsd-arm64@0.19.3':
136
235
resolution: {integrity: sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==}
137
236
engines: {node: '>=12'}
138
237
cpu: [arm64]
139
238
os: [freebsd]
140
-
requiresBuild: true
141
-
dev: true
142
-
optional: true
143
239
144
-
/@esbuild/freebsd-x64@0.19.3:
240
+
'@esbuild/freebsd-x64@0.19.3':
145
241
resolution: {integrity: sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==}
146
242
engines: {node: '>=12'}
147
243
cpu: [x64]
148
244
os: [freebsd]
149
-
requiresBuild: true
150
-
dev: true
151
-
optional: true
152
245
153
-
/@esbuild/linux-arm64@0.19.3:
246
+
'@esbuild/linux-arm64@0.19.3':
154
247
resolution: {integrity: sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==}
155
248
engines: {node: '>=12'}
156
249
cpu: [arm64]
157
250
os: [linux]
158
-
requiresBuild: true
159
-
dev: true
160
-
optional: true
161
251
162
-
/@esbuild/linux-arm@0.19.3:
252
+
'@esbuild/linux-arm@0.19.3':
163
253
resolution: {integrity: sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==}
164
254
engines: {node: '>=12'}
165
255
cpu: [arm]
166
256
os: [linux]
167
-
requiresBuild: true
168
-
dev: true
169
-
optional: true
170
257
171
-
/@esbuild/linux-ia32@0.19.3:
258
+
'@esbuild/linux-ia32@0.19.3':
172
259
resolution: {integrity: sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==}
173
260
engines: {node: '>=12'}
174
261
cpu: [ia32]
175
262
os: [linux]
176
-
requiresBuild: true
177
-
dev: true
178
-
optional: true
179
263
180
-
/@esbuild/linux-loong64@0.19.3:
264
+
'@esbuild/linux-loong64@0.19.3':
181
265
resolution: {integrity: sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==}
182
266
engines: {node: '>=12'}
183
267
cpu: [loong64]
184
268
os: [linux]
185
-
requiresBuild: true
186
-
dev: true
187
-
optional: true
188
269
189
-
/@esbuild/linux-mips64el@0.19.3:
270
+
'@esbuild/linux-mips64el@0.19.3':
190
271
resolution: {integrity: sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==}
191
272
engines: {node: '>=12'}
192
273
cpu: [mips64el]
193
274
os: [linux]
194
-
requiresBuild: true
195
-
dev: true
196
-
optional: true
197
275
198
-
/@esbuild/linux-ppc64@0.19.3:
276
+
'@esbuild/linux-ppc64@0.19.3':
199
277
resolution: {integrity: sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==}
200
278
engines: {node: '>=12'}
201
279
cpu: [ppc64]
202
280
os: [linux]
203
-
requiresBuild: true
204
-
dev: true
205
-
optional: true
206
281
207
-
/@esbuild/linux-riscv64@0.19.3:
282
+
'@esbuild/linux-riscv64@0.19.3':
208
283
resolution: {integrity: sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==}
209
284
engines: {node: '>=12'}
210
285
cpu: [riscv64]
211
286
os: [linux]
212
-
requiresBuild: true
213
-
dev: true
214
-
optional: true
215
287
216
-
/@esbuild/linux-s390x@0.19.3:
288
+
'@esbuild/linux-s390x@0.19.3':
217
289
resolution: {integrity: sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==}
218
290
engines: {node: '>=12'}
219
291
cpu: [s390x]
220
292
os: [linux]
221
-
requiresBuild: true
222
-
dev: true
223
-
optional: true
224
293
225
-
/@esbuild/linux-x64@0.19.3:
294
+
'@esbuild/linux-x64@0.19.3':
226
295
resolution: {integrity: sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==}
227
296
engines: {node: '>=12'}
228
297
cpu: [x64]
229
298
os: [linux]
230
-
requiresBuild: true
231
-
dev: true
232
-
optional: true
233
299
234
-
/@esbuild/netbsd-x64@0.19.3:
300
+
'@esbuild/netbsd-x64@0.19.3':
235
301
resolution: {integrity: sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==}
236
302
engines: {node: '>=12'}
237
303
cpu: [x64]
238
304
os: [netbsd]
239
-
requiresBuild: true
240
-
dev: true
241
-
optional: true
242
305
243
-
/@esbuild/openbsd-x64@0.19.3:
306
+
'@esbuild/openbsd-x64@0.19.3':
244
307
resolution: {integrity: sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==}
245
308
engines: {node: '>=12'}
246
309
cpu: [x64]
247
310
os: [openbsd]
248
-
requiresBuild: true
249
-
dev: true
250
-
optional: true
251
311
252
-
/@esbuild/sunos-x64@0.19.3:
312
+
'@esbuild/sunos-x64@0.19.3':
253
313
resolution: {integrity: sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==}
254
314
engines: {node: '>=12'}
255
315
cpu: [x64]
256
316
os: [sunos]
257
-
requiresBuild: true
258
-
dev: true
259
-
optional: true
260
317
261
-
/@esbuild/win32-arm64@0.19.3:
318
+
'@esbuild/win32-arm64@0.19.3':
262
319
resolution: {integrity: sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==}
263
320
engines: {node: '>=12'}
264
321
cpu: [arm64]
265
322
os: [win32]
266
-
requiresBuild: true
267
-
dev: true
268
-
optional: true
269
323
270
-
/@esbuild/win32-ia32@0.19.3:
324
+
'@esbuild/win32-ia32@0.19.3':
271
325
resolution: {integrity: sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==}
272
326
engines: {node: '>=12'}
273
327
cpu: [ia32]
274
328
os: [win32]
275
-
requiresBuild: true
276
-
dev: true
277
-
optional: true
278
329
279
-
/@esbuild/win32-x64@0.19.3:
330
+
'@esbuild/win32-x64@0.19.3':
280
331
resolution: {integrity: sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==}
281
332
engines: {node: '>=12'}
282
333
cpu: [x64]
283
334
os: [win32]
284
-
requiresBuild: true
285
-
dev: true
286
-
optional: true
335
+
336
+
'@eslint-community/eslint-utils@4.5.1':
337
+
resolution: {integrity: sha512-soEIOALTfTK6EjmKMMoLugwaP0rzkad90iIWd1hMO9ARkSAyjfMfkRRhLvD5qH7vvM0Cg72pieUfR6yh6XxC4w==}
338
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
339
+
peerDependencies:
340
+
eslint: ^6.0.0 || ^7.0.0 || >=8.0.0
341
+
342
+
'@eslint-community/regexpp@4.12.1':
343
+
resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==}
344
+
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
345
+
346
+
'@eslint/config-array@0.19.2':
347
+
resolution: {integrity: sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==}
348
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
349
+
350
+
'@eslint/config-helpers@0.2.1':
351
+
resolution: {integrity: sha512-RI17tsD2frtDu/3dmI7QRrD4bedNKPM08ziRYaC5AhkGrzIAJelm9kJU1TznK+apx6V+cqRz8tfpEeG3oIyjxw==}
352
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
353
+
354
+
'@eslint/core@0.12.0':
355
+
resolution: {integrity: sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==}
356
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
287
357
288
-
/@isaacs/cliui@8.0.2:
289
-
resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==}
358
+
'@eslint/core@0.13.0':
359
+
resolution: {integrity: sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==}
360
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
361
+
362
+
'@eslint/eslintrc@3.3.1':
363
+
resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==}
364
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
365
+
366
+
'@eslint/js@9.23.0':
367
+
resolution: {integrity: sha512-35MJ8vCPU0ZMxo7zfev2pypqTwWTofFZO6m4KAtdoFhRpLJUpHTZZ+KB3C7Hb1d7bULYwO4lJXGCi5Se+8OMbw==}
368
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
369
+
370
+
'@eslint/object-schema@2.1.6':
371
+
resolution: {integrity: sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==}
372
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
373
+
374
+
'@eslint/plugin-kit@0.2.8':
375
+
resolution: {integrity: sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==}
376
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
377
+
378
+
'@humanfs/core@0.19.1':
379
+
resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==}
380
+
engines: {node: '>=18.18.0'}
381
+
382
+
'@humanfs/node@0.16.6':
383
+
resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==}
384
+
engines: {node: '>=18.18.0'}
385
+
386
+
'@humanwhocodes/module-importer@1.0.1':
387
+
resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==}
388
+
engines: {node: '>=12.22'}
389
+
390
+
'@humanwhocodes/retry@0.3.1':
391
+
resolution: {integrity: sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==}
392
+
engines: {node: '>=18.18'}
393
+
394
+
'@humanwhocodes/retry@0.4.2':
395
+
resolution: {integrity: sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==}
396
+
engines: {node: '>=18.18'}
397
+
398
+
'@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9':
399
+
resolution: {tarball: https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9}
400
+
version: 1.0.1
401
+
peerDependencies:
402
+
eslint: '>= 9'
403
+
typescript: '>= 5.3'
404
+
405
+
'@moonlight-mod/lunast@1.0.1':
406
+
resolution: {integrity: sha512-K3vxzDlfFuYKjciIW2FMlcZ1qrrkAGDGpSBlNqYGtJ0sMt9bRCd2lpSpg6AX/giSljDtmAUXa/5mOfUoDQxjBA==}
407
+
408
+
'@moonlight-mod/mappings@1.1.25':
409
+
resolution: {integrity: sha512-bgnSN9H/IBdMGxGev6RQKXuzhQxwo1090NhIDHnflguZnjiu2pg/usPfh76bqyhxRuX4SS7tiZSNTwBoSflCLg==}
410
+
engines: {node: '>=22', npm: pnpm, pnpm: '>=10', yarn: pnpm}
411
+
peerDependencies:
412
+
'@moonlight-mod/lunast': ^1.0.1
413
+
'@moonlight-mod/moonmap': ^1.0.5
414
+
415
+
'@moonlight-mod/moonmap@1.0.5':
416
+
resolution: {integrity: sha512-Fdpxj8ghdulKB6TlTnchlCPey2YUKgEf1chuO1ofOIcvlqnVPBcQwSf2S80naOUQpXCDo4dQ+LWSE2fmhdDiiw==}
417
+
418
+
'@nodelib/fs.scandir@2.1.5':
419
+
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
420
+
engines: {node: '>= 8'}
421
+
422
+
'@nodelib/fs.stat@2.0.5':
423
+
resolution: {integrity: sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==}
424
+
engines: {node: '>= 8'}
425
+
426
+
'@nodelib/fs.walk@1.2.8':
427
+
resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==}
428
+
engines: {node: '>= 8'}
429
+
430
+
'@pkgr/core@0.2.0':
431
+
resolution: {integrity: sha512-vsJDAkYR6qCPu+ioGScGiMYR7LvZYIXh/dlQeviqoTWNCVfKTLYD/LkNWH4Mxsv2a5vpIRc77FN5DnmK1eBggQ==}
432
+
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
433
+
434
+
'@quansync/fs@0.1.2':
435
+
resolution: {integrity: sha512-ezIadUb1aFhwJLd++WVqVpi9rnlX8vnd4ju7saPhwLHJN1mJgOv0puePTGV+FbtSnWtwoHDT8lAm4kagDZmpCg==}
436
+
engines: {node: '>=20.0.0'}
437
+
438
+
'@types/chroma-js@3.1.0':
439
+
resolution: {integrity: sha512-Uwl3SOtUkbQ6Ye6ZYu4q4xdLGBzmY839sEHYtOT7i691neeyd+7fXWT5VIkcUSfNwIFrIjQutNYQn9h4q5HFvg==}
440
+
441
+
'@types/chrome@0.0.313':
442
+
resolution: {integrity: sha512-9R5T7gTaYZhkxlu+Ho4wk9FL+y/werWQY2yjGWSqCuiTsqS7nL/BE5UMTP6rU7J+oIG2FRKqrEycHhJATeltVA==}
443
+
444
+
'@types/eslint@9.6.1':
445
+
resolution: {integrity: sha512-FXx2pKgId/WyYo2jXw63kk7/+TY7u7AziEJxJAnSFzHlqTAS3Ync6SvgYAN/k4/PQpnnVuzoMuVnByKK2qp0ag==}
446
+
447
+
'@types/estree-jsx@1.0.5':
448
+
resolution: {integrity: sha512-52CcUVNFyfb1A2ALocQw/Dd1BQFNmSdkuC3BkZ6iqhdMfQz7JWOFRuJFloOzjk+6WijU56m9oKXFAXc7o3Towg==}
449
+
450
+
'@types/estree@1.0.6':
451
+
resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
452
+
453
+
'@types/estree@1.0.7':
454
+
resolution: {integrity: sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==}
455
+
456
+
'@types/fbemitter@2.0.35':
457
+
resolution: {integrity: sha512-Xem6d7qUfmouCHntCrRYgDBwbf+WWRd6G+7WEFlEZFZ67LZXiYRvT2LV8wcZa6mIaAil95+ABQdKgB6hPIsnng==}
458
+
459
+
'@types/filesystem@0.0.36':
460
+
resolution: {integrity: sha512-vPDXOZuannb9FZdxgHnqSwAG/jvdGM8Wq+6N4D/d80z+D4HWH+bItqsZaVRQykAn6WEVeEkLm2oQigyHtgb0RA==}
461
+
462
+
'@types/filewriter@0.0.33':
463
+
resolution: {integrity: sha512-xFU8ZXTw4gd358lb2jw25nxY9QAgqn2+bKKjKOYfNCzN4DKCFetK7sPtrlpg66Ywe3vWY9FNxprZawAh9wfJ3g==}
464
+
465
+
'@types/flux@3.1.14':
466
+
resolution: {integrity: sha512-WRXN0kQPCnqxN0/PgNgc7WBF6c8rbSHsEep3/qBLpsQ824RONdOmTs0TV7XhIW2GDNRAHO2CqCgAFLR5PChosw==}
467
+
468
+
'@types/har-format@1.2.16':
469
+
resolution: {integrity: sha512-fluxdy7ryD3MV6h8pTfTYpy/xQzCFC7m89nOH9y94cNqJ1mDIDPut7MnRHI3F6qRmh/cT2fUjG1MLdCNb4hE9A==}
470
+
471
+
'@types/highlightjs@9.12.6':
472
+
resolution: {integrity: sha512-Qfd1DUrwE851Hc3tExADJY4qY8yeZMt06Xw9AJm/UtpneepJS3MZY29c33BY0wP899veaaHD4gZzYiSuQm84Fg==}
473
+
474
+
'@types/json-schema@7.0.15':
475
+
resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==}
476
+
477
+
'@types/lodash@4.17.14':
478
+
resolution: {integrity: sha512-jsxagdikDiDBeIRaPYtArcT8my4tN1og7MtMRquFT3XNA6axxyHDRUemqDz/taRDdOUn0GnGHRCuff4q48sW9A==}
479
+
480
+
'@types/node@18.17.17':
481
+
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
482
+
483
+
'@types/node@22.13.6':
484
+
resolution: {integrity: sha512-GYmF65GI7417CpZXsEXMjT8goQQDnpRnJnDw6jIYa+le3V/lMazPZ4vZmK1B/9R17fh2VLr2zuy9d/h5xgrLAg==}
485
+
486
+
'@types/node@22.14.0':
487
+
resolution: {integrity: sha512-Kmpl+z84ILoG+3T/zQFyAJsU6EPTmOCj8/2+83fSN6djd6I4o7uOuGIH6vq3PrjY5BGitSbFuMN18j3iknubbA==}
488
+
489
+
'@types/platform@1.3.6':
490
+
resolution: {integrity: sha512-ZmSaqHuvzv+jC232cFoz2QqPUkaj6EvMmCrWcx3WRr7xTPVFCMUOTcOq8m2d+Zw1iKRc1kDiaA+jtNrV0hkVew==}
491
+
492
+
'@types/prop-types@15.7.13':
493
+
resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==}
494
+
495
+
'@types/react@18.3.20':
496
+
resolution: {integrity: sha512-IPaCZN7PShZK/3t6Q87pfTkRm6oLTd4vztyoj+cbHUF1g3FfVb2tFIL79uCRKEfv16AhqDMBywP2VW3KIZUvcg==}
497
+
498
+
'@typescript-eslint/eslint-plugin@8.29.0':
499
+
resolution: {integrity: sha512-PAIpk/U7NIS6H7TEtN45SPGLQaHNgB7wSjsQV/8+KYokAb2T/gloOA/Bee2yd4/yKVhPKe5LlaUGhAZk5zmSaQ==}
500
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
501
+
peerDependencies:
502
+
'@typescript-eslint/parser': ^8.0.0 || ^8.0.0-alpha.0
503
+
eslint: ^8.57.0 || ^9.0.0
504
+
typescript: '>=4.8.4 <5.9.0'
505
+
506
+
'@typescript-eslint/parser@8.29.0':
507
+
resolution: {integrity: sha512-8C0+jlNJOwQso2GapCVWWfW/rzaq7Lbme+vGUFKE31djwNncIpgXD7Cd4weEsDdkoZDjH0lwwr3QDQFuyrMg9g==}
508
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
509
+
peerDependencies:
510
+
eslint: ^8.57.0 || ^9.0.0
511
+
typescript: '>=4.8.4 <5.9.0'
512
+
513
+
'@typescript-eslint/scope-manager@8.29.0':
514
+
resolution: {integrity: sha512-aO1PVsq7Gm+tcghabUpzEnVSFMCU4/nYIgC2GOatJcllvWfnhrgW0ZEbnTxm36QsikmCN1K/6ZgM7fok2I7xNw==}
515
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
516
+
517
+
'@typescript-eslint/type-utils@8.29.0':
518
+
resolution: {integrity: sha512-ahaWQ42JAOx+NKEf5++WC/ua17q5l+j1GFrbbpVKzFL/tKVc0aYY8rVSYUpUvt2hUP1YBr7mwXzx+E/DfUWI9Q==}
519
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
520
+
peerDependencies:
521
+
eslint: ^8.57.0 || ^9.0.0
522
+
typescript: '>=4.8.4 <5.9.0'
523
+
524
+
'@typescript-eslint/types@8.29.0':
525
+
resolution: {integrity: sha512-wcJL/+cOXV+RE3gjCyl/V2G877+2faqvlgtso/ZRbTCnZazh0gXhe+7gbAnfubzN2bNsBtZjDvlh7ero8uIbzg==}
526
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
527
+
528
+
'@typescript-eslint/typescript-estree@8.29.0':
529
+
resolution: {integrity: sha512-yOfen3jE9ISZR/hHpU/bmNvTtBW1NjRbkSFdZOksL1N+ybPEE7UVGMwqvS6CP022Rp00Sb0tdiIkhSCe6NI8ow==}
530
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
531
+
peerDependencies:
532
+
typescript: '>=4.8.4 <5.9.0'
533
+
534
+
'@typescript-eslint/utils@8.29.0':
535
+
resolution: {integrity: sha512-gX/A0Mz9Bskm8avSWFcK0gP7cZpbY4AIo6B0hWYFCaIsz750oaiWR4Jr2CI+PQhfW1CpcQr9OlfPS+kMFegjXA==}
536
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
537
+
peerDependencies:
538
+
eslint: ^8.57.0 || ^9.0.0
539
+
typescript: '>=4.8.4 <5.9.0'
540
+
541
+
'@typescript-eslint/visitor-keys@8.29.0':
542
+
resolution: {integrity: sha512-Sne/pVz8ryR03NFK21VpN88dZ2FdQXOlq3VIklbrTYEt8yXtRFr9tvUhqvCeKjqYk5FSim37sHbooT6vzBTZcg==}
543
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
544
+
545
+
'@xterm/xterm@5.5.0':
546
+
resolution: {integrity: sha512-hqJHYaQb5OptNunnyAnkHyM8aCjZ1MEIDTQu1iIbbTD/xops91NB5yq1ZK/dC2JDbVWtF23zUtl9JE2NqwT87A==}
547
+
548
+
'@zenfs/core@2.0.0':
549
+
resolution: {integrity: sha512-wOKNFTY1DJ1vdLqKdU7M8cRh0nVYZcDVu7WHuk/3u49hrSwTZVm4PzGxJUjFd8O9Wi3U5nYTbZoN7RX5mS2ldA==}
550
+
engines: {node: '>= 18'}
551
+
hasBin: true
552
+
553
+
'@zenfs/dom@1.1.6':
554
+
resolution: {integrity: sha512-7SBTWgA0esuEv/TE+N/xk6W/XJf8uBF+LhlPNHQdXds0H7aOy/UYsWv/8glvARe+meDMMidoeWFLzUWoMXfjlA==}
555
+
engines: {node: '>= 18'}
556
+
peerDependencies:
557
+
'@zenfs/core': ^2.0.0
558
+
utilium: ^1.9.0
559
+
560
+
abort-controller@3.0.0:
561
+
resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==}
562
+
engines: {node: '>=6.5'}
563
+
564
+
acorn-jsx@5.3.2:
565
+
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
566
+
peerDependencies:
567
+
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
568
+
569
+
acorn@8.14.1:
570
+
resolution: {integrity: sha512-OvQ/2pUDKmgfCg++xsTX1wGxfTaszcHVcTctW4UJB4hibJx2HXxxO5UmVgyjMa+ZDsiaf5wWLXYpRWMmBI0QHg==}
571
+
engines: {node: '>=0.4.0'}
572
+
hasBin: true
573
+
574
+
ajv@6.12.6:
575
+
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
576
+
577
+
ansi-styles@4.3.0:
578
+
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
579
+
engines: {node: '>=8'}
580
+
581
+
ansis@3.17.0:
582
+
resolution: {integrity: sha512-0qWUglt9JEqLFr3w1I1pbrChn1grhaiAR2ocX1PP/flRmxgtwTzPFFFnfIlD6aMOLQZgSuCRlidD70lvx8yhzg==}
583
+
engines: {node: '>=14'}
584
+
585
+
argparse@2.0.1:
586
+
resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==}
587
+
588
+
array-buffer-byte-length@1.0.2:
589
+
resolution: {integrity: sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==}
590
+
engines: {node: '>= 0.4'}
591
+
592
+
array-includes@3.1.8:
593
+
resolution: {integrity: sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==}
594
+
engines: {node: '>= 0.4'}
595
+
596
+
array.prototype.findlast@1.2.5:
597
+
resolution: {integrity: sha512-CVvd6FHg1Z3POpBLxO6E6zr+rSKEQ9L6rZHAaY7lLfhKsWYUBBOuMs0e9o24oopj6H+geRCX0YJ+TJLBK2eHyQ==}
598
+
engines: {node: '>= 0.4'}
599
+
600
+
array.prototype.flat@1.3.3:
601
+
resolution: {integrity: sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==}
602
+
engines: {node: '>= 0.4'}
603
+
604
+
array.prototype.flatmap@1.3.3:
605
+
resolution: {integrity: sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==}
606
+
engines: {node: '>= 0.4'}
607
+
608
+
array.prototype.tosorted@1.1.4:
609
+
resolution: {integrity: sha512-p6Fx8B7b7ZhL/gmUsAy0D15WhvDccw3mnGNbZpi3pmeJdxtWsj2jEaI4Y6oo3XiHfzuSgPwKc04MYt6KgvC/wA==}
610
+
engines: {node: '>= 0.4'}
611
+
612
+
arraybuffer.prototype.slice@1.0.4:
613
+
resolution: {integrity: sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==}
614
+
engines: {node: '>= 0.4'}
615
+
616
+
astring@1.9.0:
617
+
resolution: {integrity: sha512-LElXdjswlqjWrPpJFg1Fx4wpkOCxj1TDHlSV4PlaRxHGWko024xICaa97ZkMfs6DRKlCguiAI+rbXv5GWwXIkg==}
618
+
hasBin: true
619
+
620
+
async-function@1.0.0:
621
+
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
622
+
engines: {node: '>= 0.4'}
623
+
624
+
available-typed-arrays@1.0.7:
625
+
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
626
+
engines: {node: '>= 0.4'}
627
+
628
+
balanced-match@1.0.2:
629
+
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
630
+
631
+
base64-js@1.5.1:
632
+
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
633
+
634
+
brace-expansion@1.1.11:
635
+
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
636
+
637
+
brace-expansion@2.0.1:
638
+
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
639
+
640
+
braces@3.0.3:
641
+
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
642
+
engines: {node: '>=8'}
643
+
644
+
buffer@6.0.3:
645
+
resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
646
+
647
+
cac@6.7.14:
648
+
resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==}
649
+
engines: {node: '>=8'}
650
+
651
+
call-bind-apply-helpers@1.0.2:
652
+
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
653
+
engines: {node: '>= 0.4'}
654
+
655
+
call-bind@1.0.8:
656
+
resolution: {integrity: sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==}
657
+
engines: {node: '>= 0.4'}
658
+
659
+
call-bound@1.0.4:
660
+
resolution: {integrity: sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==}
661
+
engines: {node: '>= 0.4'}
662
+
663
+
callsites@3.1.0:
664
+
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
665
+
engines: {node: '>=6'}
666
+
667
+
chalk@4.1.2:
668
+
resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==}
669
+
engines: {node: '>=10'}
670
+
671
+
color-convert@2.0.1:
672
+
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
673
+
engines: {node: '>=7.0.0'}
674
+
675
+
color-name@1.1.4:
676
+
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
677
+
678
+
concat-map@0.0.1:
679
+
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
680
+
681
+
cross-spawn@7.0.6:
682
+
resolution: {integrity: sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==}
683
+
engines: {node: '>= 8'}
684
+
685
+
csstype@3.1.3:
686
+
resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
687
+
688
+
data-view-buffer@1.0.2:
689
+
resolution: {integrity: sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==}
690
+
engines: {node: '>= 0.4'}
691
+
692
+
data-view-byte-length@1.0.2:
693
+
resolution: {integrity: sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==}
694
+
engines: {node: '>= 0.4'}
695
+
696
+
data-view-byte-offset@1.0.1:
697
+
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
698
+
engines: {node: '>= 0.4'}
699
+
700
+
debug@4.4.0:
701
+
resolution: {integrity: sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==}
702
+
engines: {node: '>=6.0'}
703
+
peerDependencies:
704
+
supports-color: '*'
705
+
peerDependenciesMeta:
706
+
supports-color:
707
+
optional: true
708
+
709
+
deep-is@0.1.4:
710
+
resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==}
711
+
712
+
define-data-property@1.1.4:
713
+
resolution: {integrity: sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==}
714
+
engines: {node: '>= 0.4'}
715
+
716
+
define-properties@1.2.1:
717
+
resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==}
718
+
engines: {node: '>= 0.4'}
719
+
720
+
defu@6.1.4:
721
+
resolution: {integrity: sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==}
722
+
723
+
destr@2.0.4:
724
+
resolution: {integrity: sha512-FCAorltMy7QwX0QU38jOkhrv20LBpsHA8ogzvMhhPHCCKVCaN6GxrB0GGaWEWBUYI4eEjjfJ95RdP6dk9IdMQA==}
725
+
726
+
doctrine@2.1.0:
727
+
resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==}
728
+
engines: {node: '>=0.10.0'}
729
+
730
+
dunder-proto@1.0.1:
731
+
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
732
+
engines: {node: '>= 0.4'}
733
+
734
+
es-abstract@1.23.9:
735
+
resolution: {integrity: sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==}
736
+
engines: {node: '>= 0.4'}
737
+
738
+
es-define-property@1.0.1:
739
+
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
740
+
engines: {node: '>= 0.4'}
741
+
742
+
es-errors@1.3.0:
743
+
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
744
+
engines: {node: '>= 0.4'}
745
+
746
+
es-iterator-helpers@1.2.1:
747
+
resolution: {integrity: sha512-uDn+FE1yrDzyC0pCo961B2IHbdM8y/ACZsKD4dG6WqrjV53BADjwa7D+1aom2rsNVfLyDgU/eigvlJGJ08OQ4w==}
748
+
engines: {node: '>= 0.4'}
749
+
750
+
es-object-atoms@1.1.1:
751
+
resolution: {integrity: sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==}
752
+
engines: {node: '>= 0.4'}
753
+
754
+
es-set-tostringtag@2.1.0:
755
+
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
756
+
engines: {node: '>= 0.4'}
757
+
758
+
es-shim-unscopables@1.1.0:
759
+
resolution: {integrity: sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==}
760
+
engines: {node: '>= 0.4'}
761
+
762
+
es-to-primitive@1.3.0:
763
+
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
764
+
engines: {node: '>= 0.4'}
765
+
766
+
esbuild-copy-static-files@0.1.0:
767
+
resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==}
768
+
769
+
esbuild@0.19.3:
770
+
resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==}
290
771
engines: {node: '>=12'}
291
-
dependencies:
292
-
string-width: 5.1.2
293
-
string-width-cjs: /string-width@4.2.3
294
-
strip-ansi: 7.1.0
295
-
strip-ansi-cjs: /strip-ansi@6.0.1
296
-
wrap-ansi: 8.1.0
297
-
wrap-ansi-cjs: /wrap-ansi@7.0.0
298
-
dev: false
772
+
hasBin: true
773
+
774
+
escape-string-regexp@4.0.0:
775
+
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
776
+
engines: {node: '>=10'}
777
+
778
+
eslint-config-prettier@9.1.0:
779
+
resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==}
780
+
hasBin: true
781
+
peerDependencies:
782
+
eslint: '>=7.0.0'
783
+
784
+
eslint-plugin-prettier@5.2.6:
785
+
resolution: {integrity: sha512-mUcf7QG2Tjk7H055Jk0lGBjbgDnfrvqjhXh9t2xLMSCjZVcw9Rb1V6sVNXO0th3jgeO7zllWPTNRil3JW94TnQ==}
786
+
engines: {node: ^14.18.0 || >=16.0.0}
787
+
peerDependencies:
788
+
'@types/eslint': '>=8.0.0'
789
+
eslint: '>=8.0.0'
790
+
eslint-config-prettier: '>= 7.0.0 <10.0.0 || >=10.1.0'
791
+
prettier: '>=3.0.0'
792
+
peerDependenciesMeta:
793
+
'@types/eslint':
794
+
optional: true
795
+
eslint-config-prettier:
796
+
optional: true
797
+
798
+
eslint-plugin-react@7.37.5:
799
+
resolution: {integrity: sha512-Qteup0SqU15kdocexFNAJMvCJEfa2xUKNV4CC1xsVMrIIqEy3SQ/rqyxCWNzfrd3/ldy6HMlD2e0JDVpDg2qIA==}
800
+
engines: {node: '>=4'}
801
+
peerDependencies:
802
+
eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 || ^9.7
803
+
804
+
eslint-scope@8.3.0:
805
+
resolution: {integrity: sha512-pUNxi75F8MJ/GdeKtVLSbYg4ZI34J6C0C7sbL4YOp2exGwen7ZsuBqKzUhXd0qMQ362yET3z+uPwKeg/0C2XCQ==}
806
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
807
+
808
+
eslint-visitor-keys@3.4.3:
809
+
resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==}
810
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
299
811
300
-
/@pkgjs/parseargs@0.11.0:
301
-
resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==}
812
+
eslint-visitor-keys@4.2.0:
813
+
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
814
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
815
+
816
+
eslint@9.23.0:
817
+
resolution: {integrity: sha512-jV7AbNoFPAY1EkFYpLq5bslU9NLNO8xnEeQXwErNibVryjk67wHVmddTBilc5srIttJDBrB0eMHKZBFbSIABCw==}
818
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
819
+
hasBin: true
820
+
peerDependencies:
821
+
jiti: '*'
822
+
peerDependenciesMeta:
823
+
jiti:
824
+
optional: true
825
+
826
+
espree@10.3.0:
827
+
resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==}
828
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
829
+
830
+
esquery@1.6.0:
831
+
resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==}
832
+
engines: {node: '>=0.10'}
833
+
834
+
esrecurse@4.3.0:
835
+
resolution: {integrity: sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==}
836
+
engines: {node: '>=4.0'}
837
+
838
+
estraverse@5.3.0:
839
+
resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==}
840
+
engines: {node: '>=4.0'}
841
+
842
+
estree-toolkit@1.7.8:
843
+
resolution: {integrity: sha512-v0Q0L+0agSDFe3x9Sj7aAzrI9afvsfr5r7AM2SNk/8bKYRQ3tUf4PQEUWe99LkWysmT1PsuSpW+W1w/xZmCKeg==}
844
+
845
+
esutils@2.0.3:
846
+
resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==}
847
+
engines: {node: '>=0.10.0'}
848
+
849
+
event-target-shim@5.0.1:
850
+
resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==}
851
+
engines: {node: '>=6'}
852
+
853
+
eventemitter3@5.0.1:
854
+
resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==}
855
+
856
+
events@3.3.0:
857
+
resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==}
858
+
engines: {node: '>=0.8.x'}
859
+
860
+
fast-deep-equal@3.1.3:
861
+
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
862
+
863
+
fast-diff@1.3.0:
864
+
resolution: {integrity: sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==}
865
+
866
+
fast-glob@3.3.2:
867
+
resolution: {integrity: sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==}
868
+
engines: {node: '>=8.6.0'}
869
+
870
+
fast-json-stable-stringify@2.1.0:
871
+
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
872
+
873
+
fast-levenshtein@2.0.6:
874
+
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
875
+
876
+
fastq@1.17.1:
877
+
resolution: {integrity: sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==}
878
+
879
+
fdir@6.4.3:
880
+
resolution: {integrity: sha512-PMXmW2y1hDDfTSRc9gaXIuCCRpuoz3Kaz8cUelp3smouvfT632ozg2vrT6lJsHKKOF59YLbOGfAWGUcKEfRMQw==}
881
+
peerDependencies:
882
+
picomatch: ^3 || ^4
883
+
peerDependenciesMeta:
884
+
picomatch:
885
+
optional: true
886
+
887
+
file-entry-cache@8.0.0:
888
+
resolution: {integrity: sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==}
889
+
engines: {node: '>=16.0.0'}
890
+
891
+
fill-range@7.1.1:
892
+
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
893
+
engines: {node: '>=8'}
894
+
895
+
find-up-simple@1.0.1:
896
+
resolution: {integrity: sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==}
897
+
engines: {node: '>=18'}
898
+
899
+
find-up@5.0.0:
900
+
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
901
+
engines: {node: '>=10'}
902
+
903
+
flat-cache@4.0.1:
904
+
resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==}
905
+
engines: {node: '>=16'}
906
+
907
+
flatted@3.2.9:
908
+
resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==}
909
+
910
+
for-each@0.3.5:
911
+
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
912
+
engines: {node: '>= 0.4'}
913
+
914
+
function-bind@1.1.2:
915
+
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
916
+
917
+
function.prototype.name@1.1.8:
918
+
resolution: {integrity: sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==}
919
+
engines: {node: '>= 0.4'}
920
+
921
+
functions-have-names@1.2.3:
922
+
resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==}
923
+
924
+
fzf@0.5.2:
925
+
resolution: {integrity: sha512-Tt4kuxLXFKHy8KT40zwsUPUkg1CrsgY25FxA2U/j/0WgEDCk3ddc/zLTCCcbSHX9FcKtLuVaDGtGE/STWC+j3Q==}
926
+
927
+
get-intrinsic@1.3.0:
928
+
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
929
+
engines: {node: '>= 0.4'}
930
+
931
+
get-proto@1.0.1:
932
+
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
933
+
engines: {node: '>= 0.4'}
934
+
935
+
get-symbol-description@1.1.0:
936
+
resolution: {integrity: sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==}
937
+
engines: {node: '>= 0.4'}
938
+
939
+
glob-parent@5.1.2:
940
+
resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==}
941
+
engines: {node: '>= 6'}
942
+
943
+
glob-parent@6.0.2:
944
+
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
945
+
engines: {node: '>=10.13.0'}
946
+
947
+
globals@14.0.0:
948
+
resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==}
949
+
engines: {node: '>=18'}
950
+
951
+
globalthis@1.0.4:
952
+
resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
953
+
engines: {node: '>= 0.4'}
954
+
955
+
gopd@1.2.0:
956
+
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
957
+
engines: {node: '>= 0.4'}
958
+
959
+
graphemer@1.4.0:
960
+
resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==}
961
+
962
+
has-bigints@1.1.0:
963
+
resolution: {integrity: sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==}
964
+
engines: {node: '>= 0.4'}
965
+
966
+
has-flag@4.0.0:
967
+
resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==}
968
+
engines: {node: '>=8'}
969
+
970
+
has-property-descriptors@1.0.2:
971
+
resolution: {integrity: sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==}
972
+
973
+
has-proto@1.2.0:
974
+
resolution: {integrity: sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==}
975
+
engines: {node: '>= 0.4'}
976
+
977
+
has-symbols@1.1.0:
978
+
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
979
+
engines: {node: '>= 0.4'}
980
+
981
+
has-tostringtag@1.0.2:
982
+
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
983
+
engines: {node: '>= 0.4'}
984
+
985
+
hasown@2.0.2:
986
+
resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==}
987
+
engines: {node: '>= 0.4'}
988
+
989
+
husky@8.0.3:
990
+
resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==}
302
991
engines: {node: '>=14'}
303
-
requiresBuild: true
304
-
dev: false
305
-
optional: true
992
+
hasBin: true
306
993
307
-
/@types/fbemitter@2.0.33:
308
-
resolution: {integrity: sha512-KcSilwdl0D8YgXGL6l9d+rTBm2W7pDyTZrDEw0+IzqQ724676KJtMeO+xHodJewKFWZT+GFWaJubA5mpMxSkcg==}
309
-
dev: false
994
+
ieee754@1.2.1:
995
+
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
310
996
311
-
/@types/flux@3.1.12:
312
-
resolution: {integrity: sha512-HZ8o/DTVNgcgnXoDyn0ZnjqEZMT4Chr4w5ktMQSbQAnqVDklasmRqNGd2agZDsk5i0jYHQLgQQuM782bWG7fUA==}
313
-
dependencies:
314
-
'@types/fbemitter': 2.0.33
315
-
'@types/react': 18.2.22
316
-
dev: false
997
+
ignore@5.3.2:
998
+
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
999
+
engines: {node: '>= 4'}
317
1000
318
-
/@types/node@18.17.17:
319
-
resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==}
320
-
dev: false
1001
+
import-fresh@3.3.0:
1002
+
resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==}
1003
+
engines: {node: '>=6'}
321
1004
322
-
/@types/node@20.6.2:
323
-
resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==}
324
-
dev: false
1005
+
imurmurhash@0.1.4:
1006
+
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
1007
+
engines: {node: '>=0.8.19'}
1008
+
1009
+
internal-slot@1.1.0:
1010
+
resolution: {integrity: sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==}
1011
+
engines: {node: '>= 0.4'}
325
1012
326
-
/@types/prop-types@15.7.6:
327
-
resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==}
328
-
dev: false
1013
+
is-array-buffer@3.0.5:
1014
+
resolution: {integrity: sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==}
1015
+
engines: {node: '>= 0.4'}
1016
+
1017
+
is-async-function@2.1.1:
1018
+
resolution: {integrity: sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==}
1019
+
engines: {node: '>= 0.4'}
1020
+
1021
+
is-bigint@1.1.0:
1022
+
resolution: {integrity: sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==}
1023
+
engines: {node: '>= 0.4'}
1024
+
1025
+
is-boolean-object@1.2.2:
1026
+
resolution: {integrity: sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==}
1027
+
engines: {node: '>= 0.4'}
1028
+
1029
+
is-callable@1.2.7:
1030
+
resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==}
1031
+
engines: {node: '>= 0.4'}
329
1032
330
-
/@types/react@18.2.22:
331
-
resolution: {integrity: sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==}
332
-
dependencies:
333
-
'@types/prop-types': 15.7.6
334
-
'@types/scheduler': 0.16.3
335
-
csstype: 3.1.2
336
-
dev: false
1033
+
is-core-module@2.16.1:
1034
+
resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==}
1035
+
engines: {node: '>= 0.4'}
337
1036
338
-
/@types/scheduler@0.16.3:
339
-
resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==}
340
-
dev: false
1037
+
is-data-view@1.0.2:
1038
+
resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==}
1039
+
engines: {node: '>= 0.4'}
341
1040
342
-
/ansi-regex@5.0.1:
343
-
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
1041
+
is-date-object@1.1.0:
1042
+
resolution: {integrity: sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==}
1043
+
engines: {node: '>= 0.4'}
1044
+
1045
+
is-extglob@2.1.1:
1046
+
resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==}
1047
+
engines: {node: '>=0.10.0'}
1048
+
1049
+
is-finalizationregistry@1.1.1:
1050
+
resolution: {integrity: sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==}
1051
+
engines: {node: '>= 0.4'}
1052
+
1053
+
is-generator-function@1.1.0:
1054
+
resolution: {integrity: sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==}
1055
+
engines: {node: '>= 0.4'}
1056
+
1057
+
is-glob@4.0.3:
1058
+
resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==}
1059
+
engines: {node: '>=0.10.0'}
1060
+
1061
+
is-map@2.0.3:
1062
+
resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==}
1063
+
engines: {node: '>= 0.4'}
1064
+
1065
+
is-number-object@1.1.1:
1066
+
resolution: {integrity: sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==}
1067
+
engines: {node: '>= 0.4'}
1068
+
1069
+
is-number@7.0.0:
1070
+
resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==}
1071
+
engines: {node: '>=0.12.0'}
1072
+
1073
+
is-regex@1.2.1:
1074
+
resolution: {integrity: sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==}
1075
+
engines: {node: '>= 0.4'}
1076
+
1077
+
is-set@2.0.3:
1078
+
resolution: {integrity: sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==}
1079
+
engines: {node: '>= 0.4'}
1080
+
1081
+
is-shared-array-buffer@1.0.4:
1082
+
resolution: {integrity: sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==}
1083
+
engines: {node: '>= 0.4'}
1084
+
1085
+
is-string@1.1.1:
1086
+
resolution: {integrity: sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==}
1087
+
engines: {node: '>= 0.4'}
1088
+
1089
+
is-symbol@1.1.1:
1090
+
resolution: {integrity: sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==}
1091
+
engines: {node: '>= 0.4'}
1092
+
1093
+
is-typed-array@1.1.15:
1094
+
resolution: {integrity: sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==}
1095
+
engines: {node: '>= 0.4'}
1096
+
1097
+
is-weakmap@2.0.2:
1098
+
resolution: {integrity: sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==}
1099
+
engines: {node: '>= 0.4'}
1100
+
1101
+
is-weakref@1.1.1:
1102
+
resolution: {integrity: sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==}
1103
+
engines: {node: '>= 0.4'}
1104
+
1105
+
is-weakset@2.0.4:
1106
+
resolution: {integrity: sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==}
1107
+
engines: {node: '>= 0.4'}
1108
+
1109
+
isarray@2.0.5:
1110
+
resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==}
1111
+
1112
+
isexe@2.0.0:
1113
+
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
1114
+
1115
+
iterator.prototype@1.1.5:
1116
+
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
1117
+
engines: {node: '>= 0.4'}
1118
+
1119
+
jiti@2.4.2:
1120
+
resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
1121
+
hasBin: true
1122
+
1123
+
js-tokens@4.0.0:
1124
+
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
1125
+
1126
+
js-yaml@4.1.0:
1127
+
resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==}
1128
+
hasBin: true
1129
+
1130
+
json-buffer@3.0.1:
1131
+
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
1132
+
1133
+
json-schema-traverse@0.4.1:
1134
+
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
1135
+
1136
+
json-stable-stringify-without-jsonify@1.0.1:
1137
+
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
1138
+
1139
+
jsx-ast-utils@3.3.5:
1140
+
resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==}
1141
+
engines: {node: '>=4.0'}
1142
+
1143
+
keyv@4.5.4:
1144
+
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
1145
+
1146
+
levn@0.4.1:
1147
+
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
1148
+
engines: {node: '>= 0.8.0'}
1149
+
1150
+
locate-path@6.0.0:
1151
+
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
1152
+
engines: {node: '>=10'}
1153
+
1154
+
lodash.merge@4.6.2:
1155
+
resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==}
1156
+
1157
+
loose-envify@1.4.0:
1158
+
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
1159
+
hasBin: true
1160
+
1161
+
math-intrinsics@1.1.0:
1162
+
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
1163
+
engines: {node: '>= 0.4'}
1164
+
1165
+
merge2@1.4.1:
1166
+
resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==}
1167
+
engines: {node: '>= 8'}
1168
+
1169
+
meriyah@6.0.1:
1170
+
resolution: {integrity: sha512-OyvYIOgpzXREySYJ1cqEb2pOKdeQMTfF9M8dRU6nC4hi/GXMmNpe9ssZCrSoTHazu05BSAoRBN/uYeco+ymfOg==}
1171
+
engines: {node: '>=18.0.0'}
1172
+
1173
+
microdiff@1.5.0:
1174
+
resolution: {integrity: sha512-Drq+/THMvDdzRYrK0oxJmOKiC24ayUV8ahrt8l3oRK51PWt6gdtrIGrlIH3pT/lFh1z93FbAcidtsHcWbnRz8Q==}
1175
+
1176
+
micromatch@4.0.8:
1177
+
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
1178
+
engines: {node: '>=8.6'}
1179
+
1180
+
mimic-function@5.0.1:
1181
+
resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
1182
+
engines: {node: '>=18'}
1183
+
1184
+
minimatch@3.1.2:
1185
+
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
1186
+
1187
+
minimatch@9.0.5:
1188
+
resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==}
1189
+
engines: {node: '>=16 || 14 >=14.17'}
1190
+
1191
+
ms@2.1.3:
1192
+
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
1193
+
1194
+
nanotar@0.1.1:
1195
+
resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==}
1196
+
1197
+
natural-compare@1.4.0:
1198
+
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
1199
+
1200
+
node-fetch-native@1.6.6:
1201
+
resolution: {integrity: sha512-8Mc2HhqPdlIfedsuZoc3yioPuzp6b+L5jRCRY1QzuWZh2EGJVQrGppC6V6cF0bLdbW0+O2YpqCA25aF/1lvipQ==}
1202
+
1203
+
object-assign@4.1.1:
1204
+
resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==}
1205
+
engines: {node: '>=0.10.0'}
1206
+
1207
+
object-inspect@1.13.4:
1208
+
resolution: {integrity: sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==}
1209
+
engines: {node: '>= 0.4'}
1210
+
1211
+
object-keys@1.1.1:
1212
+
resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==}
1213
+
engines: {node: '>= 0.4'}
1214
+
1215
+
object.assign@4.1.7:
1216
+
resolution: {integrity: sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==}
1217
+
engines: {node: '>= 0.4'}
1218
+
1219
+
object.entries@1.1.9:
1220
+
resolution: {integrity: sha512-8u/hfXFRBD1O0hPUjioLhoWFHRmt6tKA4/vZPyckBr18l1KE9uHrFaFaUi8MDRTpi4uak2goyPTSNJLXX2k2Hw==}
1221
+
engines: {node: '>= 0.4'}
1222
+
1223
+
object.fromentries@2.0.8:
1224
+
resolution: {integrity: sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==}
1225
+
engines: {node: '>= 0.4'}
1226
+
1227
+
object.values@1.2.1:
1228
+
resolution: {integrity: sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==}
1229
+
engines: {node: '>= 0.4'}
1230
+
1231
+
ofetch@1.4.1:
1232
+
resolution: {integrity: sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==}
1233
+
1234
+
onetime@7.0.0:
1235
+
resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
1236
+
engines: {node: '>=18'}
1237
+
1238
+
optionator@0.9.3:
1239
+
resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==}
1240
+
engines: {node: '>= 0.8.0'}
1241
+
1242
+
own-keys@1.0.1:
1243
+
resolution: {integrity: sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==}
1244
+
engines: {node: '>= 0.4'}
1245
+
1246
+
p-limit@3.1.0:
1247
+
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
1248
+
engines: {node: '>=10'}
1249
+
1250
+
p-locate@5.0.0:
1251
+
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
1252
+
engines: {node: '>=10'}
1253
+
1254
+
package-manager-detector@1.1.0:
1255
+
resolution: {integrity: sha512-Y8f9qUlBzW8qauJjd/eu6jlpJZsuPJm2ZAV0cDVd420o4EdpH5RPdoCv+60/TdJflGatr4sDfpAL6ArWZbM5tA==}
1256
+
1257
+
parent-module@1.0.1:
1258
+
resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==}
1259
+
engines: {node: '>=6'}
1260
+
1261
+
path-exists@4.0.0:
1262
+
resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==}
344
1263
engines: {node: '>=8'}
345
-
dev: false
1264
+
1265
+
path-key@3.1.1:
1266
+
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
1267
+
engines: {node: '>=8'}
1268
+
1269
+
path-parse@1.0.7:
1270
+
resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==}
1271
+
1272
+
pathe@2.0.3:
1273
+
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
1274
+
1275
+
picomatch@2.3.1:
1276
+
resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==}
1277
+
engines: {node: '>=8.6'}
346
1278
347
-
/ansi-regex@6.0.1:
348
-
resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==}
1279
+
picomatch@4.0.2:
1280
+
resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==}
349
1281
engines: {node: '>=12'}
350
-
dev: false
1282
+
1283
+
pnpm-workspace-yaml@0.3.1:
1284
+
resolution: {integrity: sha512-3nW5RLmREmZ8Pm8MbPsO2RM+99RRjYd25ynj3NV0cFsN7CcEl4sDFzgoFmSyduFwxFQ2Qbu3y2UdCh6HlyUOeA==}
1285
+
1286
+
possible-typed-array-names@1.1.0:
1287
+
resolution: {integrity: sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==}
1288
+
engines: {node: '>= 0.4'}
351
1289
352
-
/ansi-styles@4.3.0:
353
-
resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==}
1290
+
prelude-ls@1.2.1:
1291
+
resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==}
1292
+
engines: {node: '>= 0.8.0'}
1293
+
1294
+
prettier-linter-helpers@1.0.0:
1295
+
resolution: {integrity: sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==}
1296
+
engines: {node: '>=6.0.0'}
1297
+
1298
+
prettier@3.1.0:
1299
+
resolution: {integrity: sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==}
1300
+
engines: {node: '>=14'}
1301
+
hasBin: true
1302
+
1303
+
process@0.11.10:
1304
+
resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==}
1305
+
engines: {node: '>= 0.6.0'}
1306
+
1307
+
prop-types@15.8.1:
1308
+
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
1309
+
1310
+
punycode@2.3.1:
1311
+
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
1312
+
engines: {node: '>=6'}
1313
+
1314
+
quansync@0.2.10:
1315
+
resolution: {integrity: sha512-t41VRkMYbkHyCYmOvx/6URnN80H7k4X0lLdBMGsz+maAwrJQYB1djpV6vHrQIBE0WBSGqhtEHrK9U3DWWH8v7A==}
1316
+
1317
+
queue-microtask@1.2.3:
1318
+
resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==}
1319
+
1320
+
react-is@16.13.1:
1321
+
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
1322
+
1323
+
readable-stream@4.5.2:
1324
+
resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==}
1325
+
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
1326
+
1327
+
reflect.getprototypeof@1.0.10:
1328
+
resolution: {integrity: sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==}
1329
+
engines: {node: '>= 0.4'}
1330
+
1331
+
regexp.prototype.flags@1.5.4:
1332
+
resolution: {integrity: sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==}
1333
+
engines: {node: '>= 0.4'}
1334
+
1335
+
resolve-from@4.0.0:
1336
+
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
1337
+
engines: {node: '>=4'}
1338
+
1339
+
resolve@2.0.0-next.5:
1340
+
resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==}
1341
+
hasBin: true
1342
+
1343
+
restore-cursor@5.1.0:
1344
+
resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
1345
+
engines: {node: '>=18'}
1346
+
1347
+
reusify@1.0.4:
1348
+
resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==}
1349
+
engines: {iojs: '>=1.0.0', node: '>=0.10.0'}
1350
+
1351
+
run-parallel@1.2.0:
1352
+
resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==}
1353
+
1354
+
safe-array-concat@1.1.3:
1355
+
resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==}
1356
+
engines: {node: '>=0.4'}
1357
+
1358
+
safe-buffer@5.2.1:
1359
+
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
1360
+
1361
+
safe-push-apply@1.0.0:
1362
+
resolution: {integrity: sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==}
1363
+
engines: {node: '>= 0.4'}
1364
+
1365
+
safe-regex-test@1.1.0:
1366
+
resolution: {integrity: sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==}
1367
+
engines: {node: '>= 0.4'}
1368
+
1369
+
semver@6.3.1:
1370
+
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
1371
+
hasBin: true
1372
+
1373
+
semver@7.7.1:
1374
+
resolution: {integrity: sha512-hlq8tAfn0m/61p4BVRcPzIGr6LKiMwo4VM6dGi6pt4qcRkmNzTcWq6eCEjEh+qXjkMDvPlOFFSGwQjoEa6gyMA==}
1375
+
engines: {node: '>=10'}
1376
+
hasBin: true
1377
+
1378
+
set-function-length@1.2.2:
1379
+
resolution: {integrity: sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==}
1380
+
engines: {node: '>= 0.4'}
1381
+
1382
+
set-function-name@2.0.2:
1383
+
resolution: {integrity: sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==}
1384
+
engines: {node: '>= 0.4'}
1385
+
1386
+
set-proto@1.0.0:
1387
+
resolution: {integrity: sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==}
1388
+
engines: {node: '>= 0.4'}
1389
+
1390
+
shebang-command@2.0.0:
1391
+
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
354
1392
engines: {node: '>=8'}
1393
+
1394
+
shebang-regex@3.0.0:
1395
+
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
1396
+
engines: {node: '>=8'}
1397
+
1398
+
side-channel-list@1.0.0:
1399
+
resolution: {integrity: sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==}
1400
+
engines: {node: '>= 0.4'}
1401
+
1402
+
side-channel-map@1.0.1:
1403
+
resolution: {integrity: sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==}
1404
+
engines: {node: '>= 0.4'}
1405
+
1406
+
side-channel-weakmap@1.0.2:
1407
+
resolution: {integrity: sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==}
1408
+
engines: {node: '>= 0.4'}
1409
+
1410
+
side-channel@1.1.0:
1411
+
resolution: {integrity: sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==}
1412
+
engines: {node: '>= 0.4'}
1413
+
1414
+
signal-exit@4.1.0:
1415
+
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
1416
+
engines: {node: '>=14'}
1417
+
1418
+
standalone-electron-types@1.0.0:
1419
+
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
1420
+
1421
+
string.prototype.matchall@4.0.12:
1422
+
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
1423
+
engines: {node: '>= 0.4'}
1424
+
1425
+
string.prototype.repeat@1.0.0:
1426
+
resolution: {integrity: sha512-0u/TldDbKD8bFCQ/4f5+mNRrXwZ8hg2w7ZR8wa16e8z9XpePWl3eGEcUD0OXpEH/VJH/2G3gjUtR3ZOiBe2S/w==}
1427
+
1428
+
string.prototype.trim@1.2.10:
1429
+
resolution: {integrity: sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==}
1430
+
engines: {node: '>= 0.4'}
1431
+
1432
+
string.prototype.trimend@1.0.9:
1433
+
resolution: {integrity: sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==}
1434
+
engines: {node: '>= 0.4'}
1435
+
1436
+
string.prototype.trimstart@1.0.8:
1437
+
resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==}
1438
+
engines: {node: '>= 0.4'}
1439
+
1440
+
string_decoder@1.3.0:
1441
+
resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==}
1442
+
1443
+
strip-json-comments@3.1.1:
1444
+
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
1445
+
engines: {node: '>=8'}
1446
+
1447
+
supports-color@7.2.0:
1448
+
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
1449
+
engines: {node: '>=8'}
1450
+
1451
+
supports-preserve-symlinks-flag@1.0.0:
1452
+
resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==}
1453
+
engines: {node: '>= 0.4'}
1454
+
1455
+
synckit@0.11.1:
1456
+
resolution: {integrity: sha512-fWZqNBZNNFp/7mTUy1fSsydhKsAKJ+u90Nk7kOK5Gcq9vObaqLBLjWFDBkyVU9Vvc6Y71VbOevMuGhqv02bT+Q==}
1457
+
engines: {node: ^14.18.0 || >=16.0.0}
1458
+
1459
+
taze@19.0.4:
1460
+
resolution: {integrity: sha512-bviyNotzqcIWpVBCC4QYVb2yupzKyUDGQi2m/8GERdiPaudVMtgAqaE98+x0cDDaByYRMJCyhQWM04ikUL6+kQ==}
1461
+
hasBin: true
1462
+
1463
+
tinyexec@1.0.1:
1464
+
resolution: {integrity: sha512-5uC6DDlmeqiOwCPmK9jMSdOuZTh8bU39Ys6yidB+UTt5hfZUPGAypSgFRiEp+jbi9qH40BLDvy85jIU88wKSqw==}
1465
+
1466
+
tinyglobby@0.2.12:
1467
+
resolution: {integrity: sha512-qkf4trmKSIiMTs/E63cxH+ojC2unam7rJ0WrauAzpT3ECNTxGRMlaXxVbfxMUC/w0LaYk6jQ4y/nGR9uBO3tww==}
1468
+
engines: {node: '>=12.0.0'}
1469
+
1470
+
to-regex-range@5.0.1:
1471
+
resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==}
1472
+
engines: {node: '>=8.0'}
1473
+
1474
+
ts-api-utils@2.1.0:
1475
+
resolution: {integrity: sha512-CUgTZL1irw8u29bzrOD/nH85jqyc74D6SshFgujOIA7osm2Rz7dYH77agkx7H4FBNxDq7Cjf+IjaX/8zwFW+ZQ==}
1476
+
engines: {node: '>=18.12'}
1477
+
peerDependencies:
1478
+
typescript: '>=4.8.4'
1479
+
1480
+
tslib@2.8.1:
1481
+
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
1482
+
1483
+
type-check@0.4.0:
1484
+
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
1485
+
engines: {node: '>= 0.8.0'}
1486
+
1487
+
typed-array-buffer@1.0.3:
1488
+
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
1489
+
engines: {node: '>= 0.4'}
1490
+
1491
+
typed-array-byte-length@1.0.3:
1492
+
resolution: {integrity: sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==}
1493
+
engines: {node: '>= 0.4'}
1494
+
1495
+
typed-array-byte-offset@1.0.4:
1496
+
resolution: {integrity: sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==}
1497
+
engines: {node: '>= 0.4'}
1498
+
1499
+
typed-array-length@1.0.7:
1500
+
resolution: {integrity: sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==}
1501
+
engines: {node: '>= 0.4'}
1502
+
1503
+
typescript-eslint@8.29.0:
1504
+
resolution: {integrity: sha512-ep9rVd9B4kQsZ7ZnWCVxUE/xDLUUUsRzE0poAeNu+4CkFErLfuvPt/qtm2EpnSyfvsR0S6QzDFSrPCFBwf64fg==}
1505
+
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
1506
+
peerDependencies:
1507
+
eslint: ^8.57.0 || ^9.0.0
1508
+
typescript: '>=4.8.4 <5.9.0'
1509
+
1510
+
typescript@5.8.2:
1511
+
resolution: {integrity: sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ==}
1512
+
engines: {node: '>=14.17'}
1513
+
hasBin: true
1514
+
1515
+
ufo@1.5.4:
1516
+
resolution: {integrity: sha512-UsUk3byDzKd04EyoZ7U4DOlxQaD14JUKQl6/P7wiX4FNvUfm3XL246n9W5AmqwW5RSFJ27NAuM0iLscAOYUiGQ==}
1517
+
1518
+
unbox-primitive@1.1.0:
1519
+
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
1520
+
engines: {node: '>= 0.4'}
1521
+
1522
+
unconfig@7.3.1:
1523
+
resolution: {integrity: sha512-LH5WL+un92tGAzWS87k7LkAfwpMdm7V0IXG2FxEjZz/QxiIW5J5LkcrKQThj0aRz6+h/lFmKI9EUXmK/T0bcrw==}
1524
+
1525
+
undici-types@6.20.0:
1526
+
resolution: {integrity: sha512-Ny6QZ2Nju20vw1SRHe3d9jVu6gJ+4e3+MMpqu7pqE5HT6WsTSlce++GQmK5UXS8mzV8DSYHrQH+Xrf2jVcuKNg==}
1527
+
1528
+
undici-types@6.21.0:
1529
+
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
1530
+
1531
+
uri-js@4.4.1:
1532
+
resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==}
1533
+
1534
+
utilium@1.10.1:
1535
+
resolution: {integrity: sha512-GQINDTb/ocyz4acQj3GXAe0wipYxws6L+9ouqaq10KlInTk9DGvW9TJd0pYa/Xu3cppNnZuB4T/sBuSXpcN2ng==}
1536
+
1537
+
which-boxed-primitive@1.1.1:
1538
+
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
1539
+
engines: {node: '>= 0.4'}
1540
+
1541
+
which-builtin-type@1.2.1:
1542
+
resolution: {integrity: sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==}
1543
+
engines: {node: '>= 0.4'}
1544
+
1545
+
which-collection@1.0.2:
1546
+
resolution: {integrity: sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==}
1547
+
engines: {node: '>= 0.4'}
1548
+
1549
+
which-typed-array@1.1.19:
1550
+
resolution: {integrity: sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==}
1551
+
engines: {node: '>= 0.4'}
1552
+
1553
+
which@2.0.2:
1554
+
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
1555
+
engines: {node: '>= 8'}
1556
+
hasBin: true
1557
+
1558
+
yaml@2.7.1:
1559
+
resolution: {integrity: sha512-10ULxpnOCQXxJvBgxsn9ptjq6uviG/htZKk9veJGhlqn3w/DxQ631zFF+nlQXLwmImeS5amR2dl2U8sg6U9jsQ==}
1560
+
engines: {node: '>= 14'}
1561
+
hasBin: true
1562
+
1563
+
yocto-queue@0.1.0:
1564
+
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
1565
+
engines: {node: '>=10'}
1566
+
1567
+
zustand@5.0.3:
1568
+
resolution: {integrity: sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==}
1569
+
engines: {node: '>=12.20.0'}
1570
+
peerDependencies:
1571
+
'@types/react': '>=18.0.0'
1572
+
immer: '>=9.0.6'
1573
+
react: '>=18.0.0'
1574
+
use-sync-external-store: '>=1.2.0'
1575
+
peerDependenciesMeta:
1576
+
'@types/react':
1577
+
optional: true
1578
+
immer:
1579
+
optional: true
1580
+
react:
1581
+
optional: true
1582
+
use-sync-external-store:
1583
+
optional: true
1584
+
1585
+
snapshots:
1586
+
1587
+
'@aashutoshrathi/word-wrap@1.2.6': {}
1588
+
1589
+
'@antfu/ni@24.3.0':
1590
+
dependencies:
1591
+
ansis: 3.17.0
1592
+
fzf: 0.5.2
1593
+
package-manager-detector: 1.1.0
1594
+
tinyexec: 1.0.1
1595
+
1596
+
'@esbuild/android-arm64@0.19.3':
1597
+
optional: true
1598
+
1599
+
'@esbuild/android-arm@0.19.3':
1600
+
optional: true
1601
+
1602
+
'@esbuild/android-x64@0.19.3':
1603
+
optional: true
1604
+
1605
+
'@esbuild/darwin-arm64@0.19.3':
1606
+
optional: true
1607
+
1608
+
'@esbuild/darwin-x64@0.19.3':
1609
+
optional: true
1610
+
1611
+
'@esbuild/freebsd-arm64@0.19.3':
1612
+
optional: true
1613
+
1614
+
'@esbuild/freebsd-x64@0.19.3':
1615
+
optional: true
1616
+
1617
+
'@esbuild/linux-arm64@0.19.3':
1618
+
optional: true
1619
+
1620
+
'@esbuild/linux-arm@0.19.3':
1621
+
optional: true
1622
+
1623
+
'@esbuild/linux-ia32@0.19.3':
1624
+
optional: true
1625
+
1626
+
'@esbuild/linux-loong64@0.19.3':
1627
+
optional: true
1628
+
1629
+
'@esbuild/linux-mips64el@0.19.3':
1630
+
optional: true
1631
+
1632
+
'@esbuild/linux-ppc64@0.19.3':
1633
+
optional: true
1634
+
1635
+
'@esbuild/linux-riscv64@0.19.3':
1636
+
optional: true
1637
+
1638
+
'@esbuild/linux-s390x@0.19.3':
1639
+
optional: true
1640
+
1641
+
'@esbuild/linux-x64@0.19.3':
1642
+
optional: true
1643
+
1644
+
'@esbuild/netbsd-x64@0.19.3':
1645
+
optional: true
1646
+
1647
+
'@esbuild/openbsd-x64@0.19.3':
1648
+
optional: true
1649
+
1650
+
'@esbuild/sunos-x64@0.19.3':
1651
+
optional: true
1652
+
1653
+
'@esbuild/win32-arm64@0.19.3':
1654
+
optional: true
1655
+
1656
+
'@esbuild/win32-ia32@0.19.3':
1657
+
optional: true
1658
+
1659
+
'@esbuild/win32-x64@0.19.3':
1660
+
optional: true
1661
+
1662
+
'@eslint-community/eslint-utils@4.5.1(eslint@9.23.0(jiti@2.4.2))':
1663
+
dependencies:
1664
+
eslint: 9.23.0(jiti@2.4.2)
1665
+
eslint-visitor-keys: 3.4.3
1666
+
1667
+
'@eslint-community/regexpp@4.12.1': {}
1668
+
1669
+
'@eslint/config-array@0.19.2':
1670
+
dependencies:
1671
+
'@eslint/object-schema': 2.1.6
1672
+
debug: 4.4.0
1673
+
minimatch: 3.1.2
1674
+
transitivePeerDependencies:
1675
+
- supports-color
1676
+
1677
+
'@eslint/config-helpers@0.2.1': {}
1678
+
1679
+
'@eslint/core@0.12.0':
1680
+
dependencies:
1681
+
'@types/json-schema': 7.0.15
1682
+
1683
+
'@eslint/core@0.13.0':
1684
+
dependencies:
1685
+
'@types/json-schema': 7.0.15
1686
+
1687
+
'@eslint/eslintrc@3.3.1':
1688
+
dependencies:
1689
+
ajv: 6.12.6
1690
+
debug: 4.4.0
1691
+
espree: 10.3.0
1692
+
globals: 14.0.0
1693
+
ignore: 5.3.2
1694
+
import-fresh: 3.3.0
1695
+
js-yaml: 4.1.0
1696
+
minimatch: 3.1.2
1697
+
strip-json-comments: 3.1.1
1698
+
transitivePeerDependencies:
1699
+
- supports-color
1700
+
1701
+
'@eslint/js@9.23.0': {}
1702
+
1703
+
'@eslint/object-schema@2.1.6': {}
1704
+
1705
+
'@eslint/plugin-kit@0.2.8':
1706
+
dependencies:
1707
+
'@eslint/core': 0.13.0
1708
+
levn: 0.4.1
1709
+
1710
+
'@humanfs/core@0.19.1': {}
1711
+
1712
+
'@humanfs/node@0.16.6':
1713
+
dependencies:
1714
+
'@humanfs/core': 0.19.1
1715
+
'@humanwhocodes/retry': 0.3.1
1716
+
1717
+
'@humanwhocodes/module-importer@1.0.1': {}
1718
+
1719
+
'@humanwhocodes/retry@0.3.1': {}
1720
+
1721
+
'@humanwhocodes/retry@0.4.2': {}
1722
+
1723
+
'@moonlight-mod/eslint-config@https://codeload.github.com/moonlight-mod/eslint-config/tar.gz/e262ac24e1a0955a9b3e0d66da247a0a8c0446c9(@types/eslint@9.6.1)(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)(typescript@5.8.2)':
1724
+
dependencies:
1725
+
'@eslint/js': 9.23.0
1726
+
eslint: 9.23.0(jiti@2.4.2)
1727
+
eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2))
1728
+
eslint-plugin-prettier: 5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)))(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0)
1729
+
eslint-plugin-react: 7.37.5(eslint@9.23.0(jiti@2.4.2))
1730
+
typescript: 5.8.2
1731
+
typescript-eslint: 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1732
+
transitivePeerDependencies:
1733
+
- '@types/eslint'
1734
+
- prettier
1735
+
- supports-color
1736
+
1737
+
'@moonlight-mod/lunast@1.0.1':
1738
+
dependencies:
1739
+
astring: 1.9.0
1740
+
estree-toolkit: 1.7.8
1741
+
meriyah: 6.0.1
1742
+
1743
+
'@moonlight-mod/mappings@1.1.25(@moonlight-mod/lunast@1.0.1)(@moonlight-mod/moonmap@1.0.5)':
1744
+
dependencies:
1745
+
'@moonlight-mod/lunast': 1.0.1
1746
+
'@moonlight-mod/moonmap': 1.0.5
1747
+
'@types/chroma-js': 3.1.0
1748
+
'@types/flux': 3.1.14
1749
+
'@types/highlightjs': 9.12.6
1750
+
'@types/lodash': 4.17.14
1751
+
'@types/platform': 1.3.6
1752
+
'@types/react': 18.3.20
1753
+
csstype: 3.1.3
1754
+
zustand: 5.0.3(@types/react@18.3.20)
1755
+
transitivePeerDependencies:
1756
+
- immer
1757
+
- react
1758
+
- use-sync-external-store
1759
+
1760
+
'@moonlight-mod/moonmap@1.0.5': {}
1761
+
1762
+
'@nodelib/fs.scandir@2.1.5':
1763
+
dependencies:
1764
+
'@nodelib/fs.stat': 2.0.5
1765
+
run-parallel: 1.2.0
1766
+
1767
+
'@nodelib/fs.stat@2.0.5': {}
1768
+
1769
+
'@nodelib/fs.walk@1.2.8':
1770
+
dependencies:
1771
+
'@nodelib/fs.scandir': 2.1.5
1772
+
fastq: 1.17.1
1773
+
1774
+
'@pkgr/core@0.2.0': {}
1775
+
1776
+
'@quansync/fs@0.1.2':
1777
+
dependencies:
1778
+
quansync: 0.2.10
1779
+
1780
+
'@types/chroma-js@3.1.0': {}
1781
+
1782
+
'@types/chrome@0.0.313':
1783
+
dependencies:
1784
+
'@types/filesystem': 0.0.36
1785
+
'@types/har-format': 1.2.16
1786
+
1787
+
'@types/eslint@9.6.1':
1788
+
dependencies:
1789
+
'@types/estree': 1.0.7
1790
+
'@types/json-schema': 7.0.15
1791
+
optional: true
1792
+
1793
+
'@types/estree-jsx@1.0.5':
1794
+
dependencies:
1795
+
'@types/estree': 1.0.6
1796
+
1797
+
'@types/estree@1.0.6': {}
1798
+
1799
+
'@types/estree@1.0.7':
1800
+
optional: true
1801
+
1802
+
'@types/fbemitter@2.0.35': {}
1803
+
1804
+
'@types/filesystem@0.0.36':
1805
+
dependencies:
1806
+
'@types/filewriter': 0.0.33
1807
+
1808
+
'@types/filewriter@0.0.33': {}
1809
+
1810
+
'@types/flux@3.1.14':
1811
+
dependencies:
1812
+
'@types/fbemitter': 2.0.35
1813
+
'@types/react': 18.3.20
1814
+
1815
+
'@types/har-format@1.2.16': {}
1816
+
1817
+
'@types/highlightjs@9.12.6': {}
1818
+
1819
+
'@types/json-schema@7.0.15': {}
1820
+
1821
+
'@types/lodash@4.17.14': {}
1822
+
1823
+
'@types/node@18.17.17': {}
1824
+
1825
+
'@types/node@22.13.6':
1826
+
dependencies:
1827
+
undici-types: 6.20.0
1828
+
1829
+
'@types/node@22.14.0':
1830
+
dependencies:
1831
+
undici-types: 6.21.0
1832
+
1833
+
'@types/platform@1.3.6': {}
1834
+
1835
+
'@types/prop-types@15.7.13': {}
1836
+
1837
+
'@types/react@18.3.20':
1838
+
dependencies:
1839
+
'@types/prop-types': 15.7.13
1840
+
csstype: 3.1.3
1841
+
1842
+
'@typescript-eslint/eslint-plugin@8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1843
+
dependencies:
1844
+
'@eslint-community/regexpp': 4.12.1
1845
+
'@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1846
+
'@typescript-eslint/scope-manager': 8.29.0
1847
+
'@typescript-eslint/type-utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1848
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1849
+
'@typescript-eslint/visitor-keys': 8.29.0
1850
+
eslint: 9.23.0(jiti@2.4.2)
1851
+
graphemer: 1.4.0
1852
+
ignore: 5.3.2
1853
+
natural-compare: 1.4.0
1854
+
ts-api-utils: 2.1.0(typescript@5.8.2)
1855
+
typescript: 5.8.2
1856
+
transitivePeerDependencies:
1857
+
- supports-color
1858
+
1859
+
'@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1860
+
dependencies:
1861
+
'@typescript-eslint/scope-manager': 8.29.0
1862
+
'@typescript-eslint/types': 8.29.0
1863
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
1864
+
'@typescript-eslint/visitor-keys': 8.29.0
1865
+
debug: 4.4.0
1866
+
eslint: 9.23.0(jiti@2.4.2)
1867
+
typescript: 5.8.2
1868
+
transitivePeerDependencies:
1869
+
- supports-color
1870
+
1871
+
'@typescript-eslint/scope-manager@8.29.0':
1872
+
dependencies:
1873
+
'@typescript-eslint/types': 8.29.0
1874
+
'@typescript-eslint/visitor-keys': 8.29.0
1875
+
1876
+
'@typescript-eslint/type-utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1877
+
dependencies:
1878
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
1879
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
1880
+
debug: 4.4.0
1881
+
eslint: 9.23.0(jiti@2.4.2)
1882
+
ts-api-utils: 2.1.0(typescript@5.8.2)
1883
+
typescript: 5.8.2
1884
+
transitivePeerDependencies:
1885
+
- supports-color
1886
+
1887
+
'@typescript-eslint/types@8.29.0': {}
1888
+
1889
+
'@typescript-eslint/typescript-estree@8.29.0(typescript@5.8.2)':
1890
+
dependencies:
1891
+
'@typescript-eslint/types': 8.29.0
1892
+
'@typescript-eslint/visitor-keys': 8.29.0
1893
+
debug: 4.4.0
1894
+
fast-glob: 3.3.2
1895
+
is-glob: 4.0.3
1896
+
minimatch: 9.0.5
1897
+
semver: 7.7.1
1898
+
ts-api-utils: 2.1.0(typescript@5.8.2)
1899
+
typescript: 5.8.2
1900
+
transitivePeerDependencies:
1901
+
- supports-color
1902
+
1903
+
'@typescript-eslint/utils@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)':
1904
+
dependencies:
1905
+
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2))
1906
+
'@typescript-eslint/scope-manager': 8.29.0
1907
+
'@typescript-eslint/types': 8.29.0
1908
+
'@typescript-eslint/typescript-estree': 8.29.0(typescript@5.8.2)
1909
+
eslint: 9.23.0(jiti@2.4.2)
1910
+
typescript: 5.8.2
1911
+
transitivePeerDependencies:
1912
+
- supports-color
1913
+
1914
+
'@typescript-eslint/visitor-keys@8.29.0':
1915
+
dependencies:
1916
+
'@typescript-eslint/types': 8.29.0
1917
+
eslint-visitor-keys: 4.2.0
1918
+
1919
+
'@xterm/xterm@5.5.0':
1920
+
optional: true
1921
+
1922
+
'@zenfs/core@2.0.0':
1923
+
dependencies:
1924
+
'@types/node': 22.13.6
1925
+
buffer: 6.0.3
1926
+
eventemitter3: 5.0.1
1927
+
readable-stream: 4.5.2
1928
+
utilium: 1.10.1
1929
+
1930
+
'@zenfs/dom@1.1.6(@zenfs/core@2.0.0)(utilium@1.10.1)':
1931
+
dependencies:
1932
+
'@zenfs/core': 2.0.0
1933
+
utilium: 1.10.1
1934
+
1935
+
abort-controller@3.0.0:
1936
+
dependencies:
1937
+
event-target-shim: 5.0.1
1938
+
1939
+
acorn-jsx@5.3.2(acorn@8.14.1):
1940
+
dependencies:
1941
+
acorn: 8.14.1
1942
+
1943
+
acorn@8.14.1: {}
1944
+
1945
+
ajv@6.12.6:
1946
+
dependencies:
1947
+
fast-deep-equal: 3.1.3
1948
+
fast-json-stable-stringify: 2.1.0
1949
+
json-schema-traverse: 0.4.1
1950
+
uri-js: 4.4.1
1951
+
1952
+
ansi-styles@4.3.0:
355
1953
dependencies:
356
1954
color-convert: 2.0.1
357
-
dev: false
358
1955
359
-
/ansi-styles@6.2.1:
360
-
resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==}
361
-
engines: {node: '>=12'}
362
-
dev: false
1956
+
ansis@3.17.0: {}
363
1957
364
-
/balanced-match@1.0.2:
365
-
resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==}
366
-
dev: false
1958
+
argparse@2.0.1: {}
1959
+
1960
+
array-buffer-byte-length@1.0.2:
1961
+
dependencies:
1962
+
call-bound: 1.0.4
1963
+
is-array-buffer: 3.0.5
1964
+
1965
+
array-includes@3.1.8:
1966
+
dependencies:
1967
+
call-bind: 1.0.8
1968
+
define-properties: 1.2.1
1969
+
es-abstract: 1.23.9
1970
+
es-object-atoms: 1.1.1
1971
+
get-intrinsic: 1.3.0
1972
+
is-string: 1.1.1
1973
+
1974
+
array.prototype.findlast@1.2.5:
1975
+
dependencies:
1976
+
call-bind: 1.0.8
1977
+
define-properties: 1.2.1
1978
+
es-abstract: 1.23.9
1979
+
es-errors: 1.3.0
1980
+
es-object-atoms: 1.1.1
1981
+
es-shim-unscopables: 1.1.0
1982
+
1983
+
array.prototype.flat@1.3.3:
1984
+
dependencies:
1985
+
call-bind: 1.0.8
1986
+
define-properties: 1.2.1
1987
+
es-abstract: 1.23.9
1988
+
es-shim-unscopables: 1.1.0
1989
+
1990
+
array.prototype.flatmap@1.3.3:
1991
+
dependencies:
1992
+
call-bind: 1.0.8
1993
+
define-properties: 1.2.1
1994
+
es-abstract: 1.23.9
1995
+
es-shim-unscopables: 1.1.0
1996
+
1997
+
array.prototype.tosorted@1.1.4:
1998
+
dependencies:
1999
+
call-bind: 1.0.8
2000
+
define-properties: 1.2.1
2001
+
es-abstract: 1.23.9
2002
+
es-errors: 1.3.0
2003
+
es-shim-unscopables: 1.1.0
367
2004
368
-
/brace-expansion@1.1.11:
369
-
resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==}
2005
+
arraybuffer.prototype.slice@1.0.4:
2006
+
dependencies:
2007
+
array-buffer-byte-length: 1.0.2
2008
+
call-bind: 1.0.8
2009
+
define-properties: 1.2.1
2010
+
es-abstract: 1.23.9
2011
+
es-errors: 1.3.0
2012
+
get-intrinsic: 1.3.0
2013
+
is-array-buffer: 3.0.5
2014
+
2015
+
astring@1.9.0: {}
2016
+
2017
+
async-function@1.0.0: {}
2018
+
2019
+
available-typed-arrays@1.0.7:
2020
+
dependencies:
2021
+
possible-typed-array-names: 1.1.0
2022
+
2023
+
balanced-match@1.0.2: {}
2024
+
2025
+
base64-js@1.5.1: {}
2026
+
2027
+
brace-expansion@1.1.11:
370
2028
dependencies:
371
2029
balanced-match: 1.0.2
372
2030
concat-map: 0.0.1
373
-
dev: false
374
2031
375
-
/brace-expansion@2.0.1:
376
-
resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==}
2032
+
brace-expansion@2.0.1:
377
2033
dependencies:
378
2034
balanced-match: 1.0.2
379
-
dev: false
380
2035
381
-
/color-convert@2.0.1:
382
-
resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==}
383
-
engines: {node: '>=7.0.0'}
2036
+
braces@3.0.3:
384
2037
dependencies:
385
-
color-name: 1.1.4
386
-
dev: false
2038
+
fill-range: 7.1.1
387
2039
388
-
/color-name@1.1.4:
389
-
resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==}
390
-
dev: false
2040
+
buffer@6.0.3:
2041
+
dependencies:
2042
+
base64-js: 1.5.1
2043
+
ieee754: 1.2.1
391
2044
392
-
/commander@5.1.0:
393
-
resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==}
394
-
engines: {node: '>= 6'}
395
-
dev: false
2045
+
cac@6.7.14: {}
396
2046
397
-
/concat-map@0.0.1:
398
-
resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
399
-
dev: false
2047
+
call-bind-apply-helpers@1.0.2:
2048
+
dependencies:
2049
+
es-errors: 1.3.0
2050
+
function-bind: 1.1.2
2051
+
2052
+
call-bind@1.0.8:
2053
+
dependencies:
2054
+
call-bind-apply-helpers: 1.0.2
2055
+
es-define-property: 1.0.1
2056
+
get-intrinsic: 1.3.0
2057
+
set-function-length: 1.2.2
2058
+
2059
+
call-bound@1.0.4:
2060
+
dependencies:
2061
+
call-bind-apply-helpers: 1.0.2
2062
+
get-intrinsic: 1.3.0
2063
+
2064
+
callsites@3.1.0: {}
2065
+
2066
+
chalk@4.1.2:
2067
+
dependencies:
2068
+
ansi-styles: 4.3.0
2069
+
supports-color: 7.2.0
2070
+
2071
+
color-convert@2.0.1:
2072
+
dependencies:
2073
+
color-name: 1.1.4
2074
+
2075
+
color-name@1.1.4: {}
400
2076
401
-
/cross-spawn@7.0.3:
402
-
resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==}
403
-
engines: {node: '>= 8'}
2077
+
concat-map@0.0.1: {}
2078
+
2079
+
cross-spawn@7.0.6:
404
2080
dependencies:
405
2081
path-key: 3.1.1
406
2082
shebang-command: 2.0.0
407
2083
which: 2.0.2
408
-
dev: false
409
2084
410
-
/csstype@3.1.2:
411
-
resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==}
412
-
dev: false
2085
+
csstype@3.1.3: {}
413
2086
414
-
/eastasianwidth@0.2.0:
415
-
resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==}
416
-
dev: false
2087
+
data-view-buffer@1.0.2:
2088
+
dependencies:
2089
+
call-bound: 1.0.4
2090
+
es-errors: 1.3.0
2091
+
is-data-view: 1.0.2
417
2092
418
-
/emoji-regex@8.0.0:
419
-
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
420
-
dev: false
2093
+
data-view-byte-length@1.0.2:
2094
+
dependencies:
2095
+
call-bound: 1.0.4
2096
+
es-errors: 1.3.0
2097
+
is-data-view: 1.0.2
2098
+
2099
+
data-view-byte-offset@1.0.1:
2100
+
dependencies:
2101
+
call-bound: 1.0.4
2102
+
es-errors: 1.3.0
2103
+
is-data-view: 1.0.2
2104
+
2105
+
debug@4.4.0:
2106
+
dependencies:
2107
+
ms: 2.1.3
2108
+
2109
+
deep-is@0.1.4: {}
2110
+
2111
+
define-data-property@1.1.4:
2112
+
dependencies:
2113
+
es-define-property: 1.0.1
2114
+
es-errors: 1.3.0
2115
+
gopd: 1.2.0
2116
+
2117
+
define-properties@1.2.1:
2118
+
dependencies:
2119
+
define-data-property: 1.1.4
2120
+
has-property-descriptors: 1.0.2
2121
+
object-keys: 1.1.1
421
2122
422
-
/emoji-regex@9.2.2:
423
-
resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==}
424
-
dev: false
2123
+
defu@6.1.4: {}
2124
+
2125
+
destr@2.0.4: {}
2126
+
2127
+
doctrine@2.1.0:
2128
+
dependencies:
2129
+
esutils: 2.0.3
425
2130
426
-
/esbuild-copy-static-files@0.1.0:
427
-
resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==}
428
-
dev: true
2131
+
dunder-proto@1.0.1:
2132
+
dependencies:
2133
+
call-bind-apply-helpers: 1.0.2
2134
+
es-errors: 1.3.0
2135
+
gopd: 1.2.0
429
2136
430
-
/esbuild@0.19.3:
431
-
resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==}
432
-
engines: {node: '>=12'}
433
-
hasBin: true
434
-
requiresBuild: true
2137
+
es-abstract@1.23.9:
2138
+
dependencies:
2139
+
array-buffer-byte-length: 1.0.2
2140
+
arraybuffer.prototype.slice: 1.0.4
2141
+
available-typed-arrays: 1.0.7
2142
+
call-bind: 1.0.8
2143
+
call-bound: 1.0.4
2144
+
data-view-buffer: 1.0.2
2145
+
data-view-byte-length: 1.0.2
2146
+
data-view-byte-offset: 1.0.1
2147
+
es-define-property: 1.0.1
2148
+
es-errors: 1.3.0
2149
+
es-object-atoms: 1.1.1
2150
+
es-set-tostringtag: 2.1.0
2151
+
es-to-primitive: 1.3.0
2152
+
function.prototype.name: 1.1.8
2153
+
get-intrinsic: 1.3.0
2154
+
get-proto: 1.0.1
2155
+
get-symbol-description: 1.1.0
2156
+
globalthis: 1.0.4
2157
+
gopd: 1.2.0
2158
+
has-property-descriptors: 1.0.2
2159
+
has-proto: 1.2.0
2160
+
has-symbols: 1.1.0
2161
+
hasown: 2.0.2
2162
+
internal-slot: 1.1.0
2163
+
is-array-buffer: 3.0.5
2164
+
is-callable: 1.2.7
2165
+
is-data-view: 1.0.2
2166
+
is-regex: 1.2.1
2167
+
is-shared-array-buffer: 1.0.4
2168
+
is-string: 1.1.1
2169
+
is-typed-array: 1.1.15
2170
+
is-weakref: 1.1.1
2171
+
math-intrinsics: 1.1.0
2172
+
object-inspect: 1.13.4
2173
+
object-keys: 1.1.1
2174
+
object.assign: 4.1.7
2175
+
own-keys: 1.0.1
2176
+
regexp.prototype.flags: 1.5.4
2177
+
safe-array-concat: 1.1.3
2178
+
safe-push-apply: 1.0.0
2179
+
safe-regex-test: 1.1.0
2180
+
set-proto: 1.0.0
2181
+
string.prototype.trim: 1.2.10
2182
+
string.prototype.trimend: 1.0.9
2183
+
string.prototype.trimstart: 1.0.8
2184
+
typed-array-buffer: 1.0.3
2185
+
typed-array-byte-length: 1.0.3
2186
+
typed-array-byte-offset: 1.0.4
2187
+
typed-array-length: 1.0.7
2188
+
unbox-primitive: 1.1.0
2189
+
which-typed-array: 1.1.19
2190
+
2191
+
es-define-property@1.0.1: {}
2192
+
2193
+
es-errors@1.3.0: {}
2194
+
2195
+
es-iterator-helpers@1.2.1:
2196
+
dependencies:
2197
+
call-bind: 1.0.8
2198
+
call-bound: 1.0.4
2199
+
define-properties: 1.2.1
2200
+
es-abstract: 1.23.9
2201
+
es-errors: 1.3.0
2202
+
es-set-tostringtag: 2.1.0
2203
+
function-bind: 1.1.2
2204
+
get-intrinsic: 1.3.0
2205
+
globalthis: 1.0.4
2206
+
gopd: 1.2.0
2207
+
has-property-descriptors: 1.0.2
2208
+
has-proto: 1.2.0
2209
+
has-symbols: 1.1.0
2210
+
internal-slot: 1.1.0
2211
+
iterator.prototype: 1.1.5
2212
+
safe-array-concat: 1.1.3
2213
+
2214
+
es-object-atoms@1.1.1:
2215
+
dependencies:
2216
+
es-errors: 1.3.0
2217
+
2218
+
es-set-tostringtag@2.1.0:
2219
+
dependencies:
2220
+
es-errors: 1.3.0
2221
+
get-intrinsic: 1.3.0
2222
+
has-tostringtag: 1.0.2
2223
+
hasown: 2.0.2
2224
+
2225
+
es-shim-unscopables@1.1.0:
2226
+
dependencies:
2227
+
hasown: 2.0.2
2228
+
2229
+
es-to-primitive@1.3.0:
2230
+
dependencies:
2231
+
is-callable: 1.2.7
2232
+
is-date-object: 1.1.0
2233
+
is-symbol: 1.1.1
2234
+
2235
+
esbuild-copy-static-files@0.1.0: {}
2236
+
2237
+
esbuild@0.19.3:
435
2238
optionalDependencies:
436
2239
'@esbuild/android-arm': 0.19.3
437
2240
'@esbuild/android-arm64': 0.19.3
···
455
2258
'@esbuild/win32-arm64': 0.19.3
456
2259
'@esbuild/win32-ia32': 0.19.3
457
2260
'@esbuild/win32-x64': 0.19.3
458
-
dev: true
459
2261
460
-
/foreground-child@3.1.1:
461
-
resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==}
462
-
engines: {node: '>=14'}
2262
+
escape-string-regexp@4.0.0: {}
2263
+
2264
+
eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)):
463
2265
dependencies:
464
-
cross-spawn: 7.0.3
465
-
signal-exit: 4.1.0
466
-
dev: false
2266
+
eslint: 9.23.0(jiti@2.4.2)
467
2267
468
-
/fs.realpath@1.0.0:
469
-
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
470
-
dev: false
2268
+
eslint-plugin-prettier@5.2.6(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)))(eslint@9.23.0(jiti@2.4.2))(prettier@3.1.0):
2269
+
dependencies:
2270
+
eslint: 9.23.0(jiti@2.4.2)
2271
+
prettier: 3.1.0
2272
+
prettier-linter-helpers: 1.0.0
2273
+
synckit: 0.11.1
2274
+
optionalDependencies:
2275
+
'@types/eslint': 9.6.1
2276
+
eslint-config-prettier: 9.1.0(eslint@9.23.0(jiti@2.4.2))
471
2277
472
-
/glob@10.3.4:
473
-
resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==}
474
-
engines: {node: '>=16 || 14 >=14.17'}
475
-
hasBin: true
2278
+
eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)):
476
2279
dependencies:
477
-
foreground-child: 3.1.1
478
-
jackspeak: 2.3.3
479
-
minimatch: 9.0.3
480
-
minipass: 7.0.3
481
-
path-scurry: 1.10.1
482
-
dev: false
2280
+
array-includes: 3.1.8
2281
+
array.prototype.findlast: 1.2.5
2282
+
array.prototype.flatmap: 1.3.3
2283
+
array.prototype.tosorted: 1.1.4
2284
+
doctrine: 2.1.0
2285
+
es-iterator-helpers: 1.2.1
2286
+
eslint: 9.23.0(jiti@2.4.2)
2287
+
estraverse: 5.3.0
2288
+
hasown: 2.0.2
2289
+
jsx-ast-utils: 3.3.5
2290
+
minimatch: 3.1.2
2291
+
object.entries: 1.1.9
2292
+
object.fromentries: 2.0.8
2293
+
object.values: 1.2.1
2294
+
prop-types: 15.8.1
2295
+
resolve: 2.0.0-next.5
2296
+
semver: 6.3.1
2297
+
string.prototype.matchall: 4.0.12
2298
+
string.prototype.repeat: 1.0.0
483
2299
484
-
/glob@7.2.3:
485
-
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
2300
+
eslint-scope@8.3.0:
2301
+
dependencies:
2302
+
esrecurse: 4.3.0
2303
+
estraverse: 5.3.0
2304
+
2305
+
eslint-visitor-keys@3.4.3: {}
2306
+
2307
+
eslint-visitor-keys@4.2.0: {}
2308
+
2309
+
eslint@9.23.0(jiti@2.4.2):
486
2310
dependencies:
487
-
fs.realpath: 1.0.0
488
-
inflight: 1.0.6
489
-
inherits: 2.0.4
2311
+
'@eslint-community/eslint-utils': 4.5.1(eslint@9.23.0(jiti@2.4.2))
2312
+
'@eslint-community/regexpp': 4.12.1
2313
+
'@eslint/config-array': 0.19.2
2314
+
'@eslint/config-helpers': 0.2.1
2315
+
'@eslint/core': 0.12.0
2316
+
'@eslint/eslintrc': 3.3.1
2317
+
'@eslint/js': 9.23.0
2318
+
'@eslint/plugin-kit': 0.2.8
2319
+
'@humanfs/node': 0.16.6
2320
+
'@humanwhocodes/module-importer': 1.0.1
2321
+
'@humanwhocodes/retry': 0.4.2
2322
+
'@types/estree': 1.0.6
2323
+
'@types/json-schema': 7.0.15
2324
+
ajv: 6.12.6
2325
+
chalk: 4.1.2
2326
+
cross-spawn: 7.0.6
2327
+
debug: 4.4.0
2328
+
escape-string-regexp: 4.0.0
2329
+
eslint-scope: 8.3.0
2330
+
eslint-visitor-keys: 4.2.0
2331
+
espree: 10.3.0
2332
+
esquery: 1.6.0
2333
+
esutils: 2.0.3
2334
+
fast-deep-equal: 3.1.3
2335
+
file-entry-cache: 8.0.0
2336
+
find-up: 5.0.0
2337
+
glob-parent: 6.0.2
2338
+
ignore: 5.3.2
2339
+
imurmurhash: 0.1.4
2340
+
is-glob: 4.0.3
2341
+
json-stable-stringify-without-jsonify: 1.0.1
2342
+
lodash.merge: 4.6.2
490
2343
minimatch: 3.1.2
491
-
once: 1.4.0
492
-
path-is-absolute: 1.0.1
493
-
dev: false
2344
+
natural-compare: 1.4.0
2345
+
optionator: 0.9.3
2346
+
optionalDependencies:
2347
+
jiti: 2.4.2
2348
+
transitivePeerDependencies:
2349
+
- supports-color
494
2350
495
-
/inflight@1.0.6:
496
-
resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==}
2351
+
espree@10.3.0:
497
2352
dependencies:
498
-
once: 1.4.0
499
-
wrappy: 1.0.2
500
-
dev: false
2353
+
acorn: 8.14.1
2354
+
acorn-jsx: 5.3.2(acorn@8.14.1)
2355
+
eslint-visitor-keys: 4.2.0
501
2356
502
-
/inherits@2.0.4:
503
-
resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==}
504
-
dev: false
2357
+
esquery@1.6.0:
2358
+
dependencies:
2359
+
estraverse: 5.3.0
505
2360
506
-
/is-fullwidth-code-point@3.0.0:
507
-
resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==}
508
-
engines: {node: '>=8'}
509
-
dev: false
2361
+
esrecurse@4.3.0:
2362
+
dependencies:
2363
+
estraverse: 5.3.0
510
2364
511
-
/isexe@2.0.0:
512
-
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
513
-
dev: false
2365
+
estraverse@5.3.0: {}
514
2366
515
-
/jackspeak@2.3.3:
516
-
resolution: {integrity: sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg==}
517
-
engines: {node: '>=14'}
2367
+
estree-toolkit@1.7.8:
518
2368
dependencies:
519
-
'@isaacs/cliui': 8.0.2
2369
+
'@types/estree': 1.0.6
2370
+
'@types/estree-jsx': 1.0.5
2371
+
2372
+
esutils@2.0.3: {}
2373
+
2374
+
event-target-shim@5.0.1: {}
2375
+
2376
+
eventemitter3@5.0.1: {}
2377
+
2378
+
events@3.3.0: {}
2379
+
2380
+
fast-deep-equal@3.1.3: {}
2381
+
2382
+
fast-diff@1.3.0: {}
2383
+
2384
+
fast-glob@3.3.2:
2385
+
dependencies:
2386
+
'@nodelib/fs.stat': 2.0.5
2387
+
'@nodelib/fs.walk': 1.2.8
2388
+
glob-parent: 5.1.2
2389
+
merge2: 1.4.1
2390
+
micromatch: 4.0.8
2391
+
2392
+
fast-json-stable-stringify@2.1.0: {}
2393
+
2394
+
fast-levenshtein@2.0.6: {}
2395
+
2396
+
fastq@1.17.1:
2397
+
dependencies:
2398
+
reusify: 1.0.4
2399
+
2400
+
fdir@6.4.3(picomatch@4.0.2):
520
2401
optionalDependencies:
521
-
'@pkgjs/parseargs': 0.11.0
522
-
dev: false
2402
+
picomatch: 4.0.2
2403
+
2404
+
file-entry-cache@8.0.0:
2405
+
dependencies:
2406
+
flat-cache: 4.0.1
2407
+
2408
+
fill-range@7.1.1:
2409
+
dependencies:
2410
+
to-regex-range: 5.0.1
2411
+
2412
+
find-up-simple@1.0.1: {}
2413
+
2414
+
find-up@5.0.0:
2415
+
dependencies:
2416
+
locate-path: 6.0.0
2417
+
path-exists: 4.0.0
2418
+
2419
+
flat-cache@4.0.1:
2420
+
dependencies:
2421
+
flatted: 3.2.9
2422
+
keyv: 4.5.4
523
2423
524
-
/lru-cache@10.0.1:
525
-
resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==}
526
-
engines: {node: 14 || >=16.14}
527
-
dev: false
2424
+
flatted@3.2.9: {}
528
2425
529
-
/minimatch@3.1.2:
530
-
resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==}
2426
+
for-each@0.3.5:
2427
+
dependencies:
2428
+
is-callable: 1.2.7
2429
+
2430
+
function-bind@1.1.2: {}
2431
+
2432
+
function.prototype.name@1.1.8:
2433
+
dependencies:
2434
+
call-bind: 1.0.8
2435
+
call-bound: 1.0.4
2436
+
define-properties: 1.2.1
2437
+
functions-have-names: 1.2.3
2438
+
hasown: 2.0.2
2439
+
is-callable: 1.2.7
2440
+
2441
+
functions-have-names@1.2.3: {}
2442
+
2443
+
fzf@0.5.2: {}
2444
+
2445
+
get-intrinsic@1.3.0:
2446
+
dependencies:
2447
+
call-bind-apply-helpers: 1.0.2
2448
+
es-define-property: 1.0.1
2449
+
es-errors: 1.3.0
2450
+
es-object-atoms: 1.1.1
2451
+
function-bind: 1.1.2
2452
+
get-proto: 1.0.1
2453
+
gopd: 1.2.0
2454
+
has-symbols: 1.1.0
2455
+
hasown: 2.0.2
2456
+
math-intrinsics: 1.1.0
2457
+
2458
+
get-proto@1.0.1:
2459
+
dependencies:
2460
+
dunder-proto: 1.0.1
2461
+
es-object-atoms: 1.1.1
2462
+
2463
+
get-symbol-description@1.1.0:
2464
+
dependencies:
2465
+
call-bound: 1.0.4
2466
+
es-errors: 1.3.0
2467
+
get-intrinsic: 1.3.0
2468
+
2469
+
glob-parent@5.1.2:
2470
+
dependencies:
2471
+
is-glob: 4.0.3
2472
+
2473
+
glob-parent@6.0.2:
2474
+
dependencies:
2475
+
is-glob: 4.0.3
2476
+
2477
+
globals@14.0.0: {}
2478
+
2479
+
globalthis@1.0.4:
2480
+
dependencies:
2481
+
define-properties: 1.2.1
2482
+
gopd: 1.2.0
2483
+
2484
+
gopd@1.2.0: {}
2485
+
2486
+
graphemer@1.4.0: {}
2487
+
2488
+
has-bigints@1.1.0: {}
2489
+
2490
+
has-flag@4.0.0: {}
2491
+
2492
+
has-property-descriptors@1.0.2:
2493
+
dependencies:
2494
+
es-define-property: 1.0.1
2495
+
2496
+
has-proto@1.2.0:
2497
+
dependencies:
2498
+
dunder-proto: 1.0.1
2499
+
2500
+
has-symbols@1.1.0: {}
2501
+
2502
+
has-tostringtag@1.0.2:
2503
+
dependencies:
2504
+
has-symbols: 1.1.0
2505
+
2506
+
hasown@2.0.2:
2507
+
dependencies:
2508
+
function-bind: 1.1.2
2509
+
2510
+
husky@8.0.3: {}
2511
+
2512
+
ieee754@1.2.1: {}
2513
+
2514
+
ignore@5.3.2: {}
2515
+
2516
+
import-fresh@3.3.0:
2517
+
dependencies:
2518
+
parent-module: 1.0.1
2519
+
resolve-from: 4.0.0
2520
+
2521
+
imurmurhash@0.1.4: {}
2522
+
2523
+
internal-slot@1.1.0:
2524
+
dependencies:
2525
+
es-errors: 1.3.0
2526
+
hasown: 2.0.2
2527
+
side-channel: 1.1.0
2528
+
2529
+
is-array-buffer@3.0.5:
2530
+
dependencies:
2531
+
call-bind: 1.0.8
2532
+
call-bound: 1.0.4
2533
+
get-intrinsic: 1.3.0
2534
+
2535
+
is-async-function@2.1.1:
2536
+
dependencies:
2537
+
async-function: 1.0.0
2538
+
call-bound: 1.0.4
2539
+
get-proto: 1.0.1
2540
+
has-tostringtag: 1.0.2
2541
+
safe-regex-test: 1.1.0
2542
+
2543
+
is-bigint@1.1.0:
2544
+
dependencies:
2545
+
has-bigints: 1.1.0
2546
+
2547
+
is-boolean-object@1.2.2:
2548
+
dependencies:
2549
+
call-bound: 1.0.4
2550
+
has-tostringtag: 1.0.2
2551
+
2552
+
is-callable@1.2.7: {}
2553
+
2554
+
is-core-module@2.16.1:
2555
+
dependencies:
2556
+
hasown: 2.0.2
2557
+
2558
+
is-data-view@1.0.2:
2559
+
dependencies:
2560
+
call-bound: 1.0.4
2561
+
get-intrinsic: 1.3.0
2562
+
is-typed-array: 1.1.15
2563
+
2564
+
is-date-object@1.1.0:
2565
+
dependencies:
2566
+
call-bound: 1.0.4
2567
+
has-tostringtag: 1.0.2
2568
+
2569
+
is-extglob@2.1.1: {}
2570
+
2571
+
is-finalizationregistry@1.1.1:
2572
+
dependencies:
2573
+
call-bound: 1.0.4
2574
+
2575
+
is-generator-function@1.1.0:
2576
+
dependencies:
2577
+
call-bound: 1.0.4
2578
+
get-proto: 1.0.1
2579
+
has-tostringtag: 1.0.2
2580
+
safe-regex-test: 1.1.0
2581
+
2582
+
is-glob@4.0.3:
2583
+
dependencies:
2584
+
is-extglob: 2.1.1
2585
+
2586
+
is-map@2.0.3: {}
2587
+
2588
+
is-number-object@1.1.1:
2589
+
dependencies:
2590
+
call-bound: 1.0.4
2591
+
has-tostringtag: 1.0.2
2592
+
2593
+
is-number@7.0.0: {}
2594
+
2595
+
is-regex@1.2.1:
2596
+
dependencies:
2597
+
call-bound: 1.0.4
2598
+
gopd: 1.2.0
2599
+
has-tostringtag: 1.0.2
2600
+
hasown: 2.0.2
2601
+
2602
+
is-set@2.0.3: {}
2603
+
2604
+
is-shared-array-buffer@1.0.4:
2605
+
dependencies:
2606
+
call-bound: 1.0.4
2607
+
2608
+
is-string@1.1.1:
2609
+
dependencies:
2610
+
call-bound: 1.0.4
2611
+
has-tostringtag: 1.0.2
2612
+
2613
+
is-symbol@1.1.1:
2614
+
dependencies:
2615
+
call-bound: 1.0.4
2616
+
has-symbols: 1.1.0
2617
+
safe-regex-test: 1.1.0
2618
+
2619
+
is-typed-array@1.1.15:
2620
+
dependencies:
2621
+
which-typed-array: 1.1.19
2622
+
2623
+
is-weakmap@2.0.2: {}
2624
+
2625
+
is-weakref@1.1.1:
2626
+
dependencies:
2627
+
call-bound: 1.0.4
2628
+
2629
+
is-weakset@2.0.4:
2630
+
dependencies:
2631
+
call-bound: 1.0.4
2632
+
get-intrinsic: 1.3.0
2633
+
2634
+
isarray@2.0.5: {}
2635
+
2636
+
isexe@2.0.0: {}
2637
+
2638
+
iterator.prototype@1.1.5:
2639
+
dependencies:
2640
+
define-data-property: 1.1.4
2641
+
es-object-atoms: 1.1.1
2642
+
get-intrinsic: 1.3.0
2643
+
get-proto: 1.0.1
2644
+
has-symbols: 1.1.0
2645
+
set-function-name: 2.0.2
2646
+
2647
+
jiti@2.4.2: {}
2648
+
2649
+
js-tokens@4.0.0: {}
2650
+
2651
+
js-yaml@4.1.0:
2652
+
dependencies:
2653
+
argparse: 2.0.1
2654
+
2655
+
json-buffer@3.0.1: {}
2656
+
2657
+
json-schema-traverse@0.4.1: {}
2658
+
2659
+
json-stable-stringify-without-jsonify@1.0.1: {}
2660
+
2661
+
jsx-ast-utils@3.3.5:
2662
+
dependencies:
2663
+
array-includes: 3.1.8
2664
+
array.prototype.flat: 1.3.3
2665
+
object.assign: 4.1.7
2666
+
object.values: 1.2.1
2667
+
2668
+
keyv@4.5.4:
2669
+
dependencies:
2670
+
json-buffer: 3.0.1
2671
+
2672
+
levn@0.4.1:
2673
+
dependencies:
2674
+
prelude-ls: 1.2.1
2675
+
type-check: 0.4.0
2676
+
2677
+
locate-path@6.0.0:
2678
+
dependencies:
2679
+
p-locate: 5.0.0
2680
+
2681
+
lodash.merge@4.6.2: {}
2682
+
2683
+
loose-envify@1.4.0:
2684
+
dependencies:
2685
+
js-tokens: 4.0.0
2686
+
2687
+
math-intrinsics@1.1.0: {}
2688
+
2689
+
merge2@1.4.1: {}
2690
+
2691
+
meriyah@6.0.1: {}
2692
+
2693
+
microdiff@1.5.0: {}
2694
+
2695
+
micromatch@4.0.8:
2696
+
dependencies:
2697
+
braces: 3.0.3
2698
+
picomatch: 2.3.1
2699
+
2700
+
mimic-function@5.0.1: {}
2701
+
2702
+
minimatch@3.1.2:
531
2703
dependencies:
532
2704
brace-expansion: 1.1.11
533
-
dev: false
534
2705
535
-
/minimatch@9.0.3:
536
-
resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==}
537
-
engines: {node: '>=16 || 14 >=14.17'}
2706
+
minimatch@9.0.5:
538
2707
dependencies:
539
2708
brace-expansion: 2.0.1
540
-
dev: false
2709
+
2710
+
ms@2.1.3: {}
2711
+
2712
+
nanotar@0.1.1: {}
2713
+
2714
+
natural-compare@1.4.0: {}
2715
+
2716
+
node-fetch-native@1.6.6: {}
2717
+
2718
+
object-assign@4.1.1: {}
2719
+
2720
+
object-inspect@1.13.4: {}
541
2721
542
-
/minipass@7.0.3:
543
-
resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==}
544
-
engines: {node: '>=16 || 14 >=14.17'}
545
-
dev: false
2722
+
object-keys@1.1.1: {}
546
2723
547
-
/once@1.4.0:
548
-
resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==}
2724
+
object.assign@4.1.7:
549
2725
dependencies:
550
-
wrappy: 1.0.2
551
-
dev: false
2726
+
call-bind: 1.0.8
2727
+
call-bound: 1.0.4
2728
+
define-properties: 1.2.1
2729
+
es-object-atoms: 1.1.1
2730
+
has-symbols: 1.1.0
2731
+
object-keys: 1.1.1
552
2732
553
-
/path-is-absolute@1.0.1:
554
-
resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==}
555
-
engines: {node: '>=0.10.0'}
556
-
dev: false
2733
+
object.entries@1.1.9:
2734
+
dependencies:
2735
+
call-bind: 1.0.8
2736
+
call-bound: 1.0.4
2737
+
define-properties: 1.2.1
2738
+
es-object-atoms: 1.1.1
557
2739
558
-
/path-key@3.1.1:
559
-
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
560
-
engines: {node: '>=8'}
561
-
dev: false
2740
+
object.fromentries@2.0.8:
2741
+
dependencies:
2742
+
call-bind: 1.0.8
2743
+
define-properties: 1.2.1
2744
+
es-abstract: 1.23.9
2745
+
es-object-atoms: 1.1.1
2746
+
2747
+
object.values@1.2.1:
2748
+
dependencies:
2749
+
call-bind: 1.0.8
2750
+
call-bound: 1.0.4
2751
+
define-properties: 1.2.1
2752
+
es-object-atoms: 1.1.1
2753
+
2754
+
ofetch@1.4.1:
2755
+
dependencies:
2756
+
destr: 2.0.4
2757
+
node-fetch-native: 1.6.6
2758
+
ufo: 1.5.4
562
2759
563
-
/path-scurry@1.10.1:
564
-
resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==}
565
-
engines: {node: '>=16 || 14 >=14.17'}
2760
+
onetime@7.0.0:
566
2761
dependencies:
567
-
lru-cache: 10.0.1
568
-
minipass: 7.0.3
569
-
dev: false
2762
+
mimic-function: 5.0.1
570
2763
571
-
/shebang-command@2.0.0:
572
-
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
573
-
engines: {node: '>=8'}
2764
+
optionator@0.9.3:
2765
+
dependencies:
2766
+
'@aashutoshrathi/word-wrap': 1.2.6
2767
+
deep-is: 0.1.4
2768
+
fast-levenshtein: 2.0.6
2769
+
levn: 0.4.1
2770
+
prelude-ls: 1.2.1
2771
+
type-check: 0.4.0
2772
+
2773
+
own-keys@1.0.1:
2774
+
dependencies:
2775
+
get-intrinsic: 1.3.0
2776
+
object-keys: 1.1.1
2777
+
safe-push-apply: 1.0.0
2778
+
2779
+
p-limit@3.1.0:
2780
+
dependencies:
2781
+
yocto-queue: 0.1.0
2782
+
2783
+
p-locate@5.0.0:
2784
+
dependencies:
2785
+
p-limit: 3.1.0
2786
+
2787
+
package-manager-detector@1.1.0: {}
2788
+
2789
+
parent-module@1.0.1:
2790
+
dependencies:
2791
+
callsites: 3.1.0
2792
+
2793
+
path-exists@4.0.0: {}
2794
+
2795
+
path-key@3.1.1: {}
2796
+
2797
+
path-parse@1.0.7: {}
2798
+
2799
+
pathe@2.0.3: {}
2800
+
2801
+
picomatch@2.3.1: {}
2802
+
2803
+
picomatch@4.0.2: {}
2804
+
2805
+
pnpm-workspace-yaml@0.3.1:
2806
+
dependencies:
2807
+
yaml: 2.7.1
2808
+
2809
+
possible-typed-array-names@1.1.0: {}
2810
+
2811
+
prelude-ls@1.2.1: {}
2812
+
2813
+
prettier-linter-helpers@1.0.0:
2814
+
dependencies:
2815
+
fast-diff: 1.3.0
2816
+
2817
+
prettier@3.1.0: {}
2818
+
2819
+
process@0.11.10: {}
2820
+
2821
+
prop-types@15.8.1:
2822
+
dependencies:
2823
+
loose-envify: 1.4.0
2824
+
object-assign: 4.1.1
2825
+
react-is: 16.13.1
2826
+
2827
+
punycode@2.3.1: {}
2828
+
2829
+
quansync@0.2.10: {}
2830
+
2831
+
queue-microtask@1.2.3: {}
2832
+
2833
+
react-is@16.13.1: {}
2834
+
2835
+
readable-stream@4.5.2:
2836
+
dependencies:
2837
+
abort-controller: 3.0.0
2838
+
buffer: 6.0.3
2839
+
events: 3.3.0
2840
+
process: 0.11.10
2841
+
string_decoder: 1.3.0
2842
+
2843
+
reflect.getprototypeof@1.0.10:
2844
+
dependencies:
2845
+
call-bind: 1.0.8
2846
+
define-properties: 1.2.1
2847
+
es-abstract: 1.23.9
2848
+
es-errors: 1.3.0
2849
+
es-object-atoms: 1.1.1
2850
+
get-intrinsic: 1.3.0
2851
+
get-proto: 1.0.1
2852
+
which-builtin-type: 1.2.1
2853
+
2854
+
regexp.prototype.flags@1.5.4:
2855
+
dependencies:
2856
+
call-bind: 1.0.8
2857
+
define-properties: 1.2.1
2858
+
es-errors: 1.3.0
2859
+
get-proto: 1.0.1
2860
+
gopd: 1.2.0
2861
+
set-function-name: 2.0.2
2862
+
2863
+
resolve-from@4.0.0: {}
2864
+
2865
+
resolve@2.0.0-next.5:
2866
+
dependencies:
2867
+
is-core-module: 2.16.1
2868
+
path-parse: 1.0.7
2869
+
supports-preserve-symlinks-flag: 1.0.0
2870
+
2871
+
restore-cursor@5.1.0:
2872
+
dependencies:
2873
+
onetime: 7.0.0
2874
+
signal-exit: 4.1.0
2875
+
2876
+
reusify@1.0.4: {}
2877
+
2878
+
run-parallel@1.2.0:
2879
+
dependencies:
2880
+
queue-microtask: 1.2.3
2881
+
2882
+
safe-array-concat@1.1.3:
2883
+
dependencies:
2884
+
call-bind: 1.0.8
2885
+
call-bound: 1.0.4
2886
+
get-intrinsic: 1.3.0
2887
+
has-symbols: 1.1.0
2888
+
isarray: 2.0.5
2889
+
2890
+
safe-buffer@5.2.1: {}
2891
+
2892
+
safe-push-apply@1.0.0:
2893
+
dependencies:
2894
+
es-errors: 1.3.0
2895
+
isarray: 2.0.5
2896
+
2897
+
safe-regex-test@1.1.0:
2898
+
dependencies:
2899
+
call-bound: 1.0.4
2900
+
es-errors: 1.3.0
2901
+
is-regex: 1.2.1
2902
+
2903
+
semver@6.3.1: {}
2904
+
2905
+
semver@7.7.1: {}
2906
+
2907
+
set-function-length@1.2.2:
2908
+
dependencies:
2909
+
define-data-property: 1.1.4
2910
+
es-errors: 1.3.0
2911
+
function-bind: 1.1.2
2912
+
get-intrinsic: 1.3.0
2913
+
gopd: 1.2.0
2914
+
has-property-descriptors: 1.0.2
2915
+
2916
+
set-function-name@2.0.2:
2917
+
dependencies:
2918
+
define-data-property: 1.1.4
2919
+
es-errors: 1.3.0
2920
+
functions-have-names: 1.2.3
2921
+
has-property-descriptors: 1.0.2
2922
+
2923
+
set-proto@1.0.0:
2924
+
dependencies:
2925
+
dunder-proto: 1.0.1
2926
+
es-errors: 1.3.0
2927
+
es-object-atoms: 1.1.1
2928
+
2929
+
shebang-command@2.0.0:
574
2930
dependencies:
575
2931
shebang-regex: 3.0.0
576
-
dev: false
2932
+
2933
+
shebang-regex@3.0.0: {}
2934
+
2935
+
side-channel-list@1.0.0:
2936
+
dependencies:
2937
+
es-errors: 1.3.0
2938
+
object-inspect: 1.13.4
2939
+
2940
+
side-channel-map@1.0.1:
2941
+
dependencies:
2942
+
call-bound: 1.0.4
2943
+
es-errors: 1.3.0
2944
+
get-intrinsic: 1.3.0
2945
+
object-inspect: 1.13.4
2946
+
2947
+
side-channel-weakmap@1.0.2:
2948
+
dependencies:
2949
+
call-bound: 1.0.4
2950
+
es-errors: 1.3.0
2951
+
get-intrinsic: 1.3.0
2952
+
object-inspect: 1.13.4
2953
+
side-channel-map: 1.0.1
577
2954
578
-
/shebang-regex@3.0.0:
579
-
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
580
-
engines: {node: '>=8'}
581
-
dev: false
2955
+
side-channel@1.1.0:
2956
+
dependencies:
2957
+
es-errors: 1.3.0
2958
+
object-inspect: 1.13.4
2959
+
side-channel-list: 1.0.0
2960
+
side-channel-map: 1.0.1
2961
+
side-channel-weakmap: 1.0.2
582
2962
583
-
/signal-exit@4.1.0:
584
-
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
585
-
engines: {node: '>=14'}
586
-
dev: false
2963
+
signal-exit@4.1.0: {}
587
2964
588
-
/standalone-electron-types@1.0.0:
589
-
resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==}
2965
+
standalone-electron-types@1.0.0:
590
2966
dependencies:
591
2967
'@types/node': 18.17.17
592
-
dev: false
593
2968
594
-
/string-width@4.2.3:
595
-
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
596
-
engines: {node: '>=8'}
2969
+
string.prototype.matchall@4.0.12:
597
2970
dependencies:
598
-
emoji-regex: 8.0.0
599
-
is-fullwidth-code-point: 3.0.0
600
-
strip-ansi: 6.0.1
601
-
dev: false
2971
+
call-bind: 1.0.8
2972
+
call-bound: 1.0.4
2973
+
define-properties: 1.2.1
2974
+
es-abstract: 1.23.9
2975
+
es-errors: 1.3.0
2976
+
es-object-atoms: 1.1.1
2977
+
get-intrinsic: 1.3.0
2978
+
gopd: 1.2.0
2979
+
has-symbols: 1.1.0
2980
+
internal-slot: 1.1.0
2981
+
regexp.prototype.flags: 1.5.4
2982
+
set-function-name: 2.0.2
2983
+
side-channel: 1.1.0
602
2984
603
-
/string-width@5.1.2:
604
-
resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==}
605
-
engines: {node: '>=12'}
2985
+
string.prototype.repeat@1.0.0:
606
2986
dependencies:
607
-
eastasianwidth: 0.2.0
608
-
emoji-regex: 9.2.2
609
-
strip-ansi: 7.1.0
610
-
dev: false
2987
+
define-properties: 1.2.1
2988
+
es-abstract: 1.23.9
611
2989
612
-
/strip-ansi@6.0.1:
613
-
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
614
-
engines: {node: '>=8'}
2990
+
string.prototype.trim@1.2.10:
615
2991
dependencies:
616
-
ansi-regex: 5.0.1
617
-
dev: false
2992
+
call-bind: 1.0.8
2993
+
call-bound: 1.0.4
2994
+
define-data-property: 1.1.4
2995
+
define-properties: 1.2.1
2996
+
es-abstract: 1.23.9
2997
+
es-object-atoms: 1.1.1
2998
+
has-property-descriptors: 1.0.2
618
2999
619
-
/strip-ansi@7.1.0:
620
-
resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==}
621
-
engines: {node: '>=12'}
3000
+
string.prototype.trimend@1.0.9:
622
3001
dependencies:
623
-
ansi-regex: 6.0.1
624
-
dev: false
3002
+
call-bind: 1.0.8
3003
+
call-bound: 1.0.4
3004
+
define-properties: 1.2.1
3005
+
es-object-atoms: 1.1.1
625
3006
626
-
/which@2.0.2:
627
-
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
628
-
engines: {node: '>= 8'}
629
-
hasBin: true
3007
+
string.prototype.trimstart@1.0.8:
630
3008
dependencies:
631
-
isexe: 2.0.0
632
-
dev: false
3009
+
call-bind: 1.0.8
3010
+
define-properties: 1.2.1
3011
+
es-object-atoms: 1.1.1
633
3012
634
-
/wrap-ansi@7.0.0:
635
-
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
636
-
engines: {node: '>=10'}
3013
+
string_decoder@1.3.0:
3014
+
dependencies:
3015
+
safe-buffer: 5.2.1
3016
+
3017
+
strip-json-comments@3.1.1: {}
3018
+
3019
+
supports-color@7.2.0:
637
3020
dependencies:
638
-
ansi-styles: 4.3.0
639
-
string-width: 4.2.3
640
-
strip-ansi: 6.0.1
641
-
dev: false
3021
+
has-flag: 4.0.0
3022
+
3023
+
supports-preserve-symlinks-flag@1.0.0: {}
642
3024
643
-
/wrap-ansi@8.1.0:
644
-
resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==}
645
-
engines: {node: '>=12'}
3025
+
synckit@0.11.1:
646
3026
dependencies:
647
-
ansi-styles: 6.2.1
648
-
string-width: 5.1.2
649
-
strip-ansi: 7.1.0
650
-
dev: false
3027
+
'@pkgr/core': 0.2.0
3028
+
tslib: 2.8.1
651
3029
652
-
/wrappy@1.0.2:
653
-
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
654
-
dev: false
3030
+
taze@19.0.4:
3031
+
dependencies:
3032
+
'@antfu/ni': 24.3.0
3033
+
cac: 6.7.14
3034
+
find-up-simple: 1.0.1
3035
+
ofetch: 1.4.1
3036
+
package-manager-detector: 1.1.0
3037
+
pathe: 2.0.3
3038
+
pnpm-workspace-yaml: 0.3.1
3039
+
restore-cursor: 5.1.0
3040
+
tinyexec: 1.0.1
3041
+
tinyglobby: 0.2.12
3042
+
unconfig: 7.3.1
3043
+
yaml: 2.7.1
3044
+
3045
+
tinyexec@1.0.1: {}
3046
+
3047
+
tinyglobby@0.2.12:
3048
+
dependencies:
3049
+
fdir: 6.4.3(picomatch@4.0.2)
3050
+
picomatch: 4.0.2
3051
+
3052
+
to-regex-range@5.0.1:
3053
+
dependencies:
3054
+
is-number: 7.0.0
3055
+
3056
+
ts-api-utils@2.1.0(typescript@5.8.2):
3057
+
dependencies:
3058
+
typescript: 5.8.2
3059
+
3060
+
tslib@2.8.1: {}
3061
+
3062
+
type-check@0.4.0:
3063
+
dependencies:
3064
+
prelude-ls: 1.2.1
3065
+
3066
+
typed-array-buffer@1.0.3:
3067
+
dependencies:
3068
+
call-bound: 1.0.4
3069
+
es-errors: 1.3.0
3070
+
is-typed-array: 1.1.15
3071
+
3072
+
typed-array-byte-length@1.0.3:
3073
+
dependencies:
3074
+
call-bind: 1.0.8
3075
+
for-each: 0.3.5
3076
+
gopd: 1.2.0
3077
+
has-proto: 1.2.0
3078
+
is-typed-array: 1.1.15
3079
+
3080
+
typed-array-byte-offset@1.0.4:
3081
+
dependencies:
3082
+
available-typed-arrays: 1.0.7
3083
+
call-bind: 1.0.8
3084
+
for-each: 0.3.5
3085
+
gopd: 1.2.0
3086
+
has-proto: 1.2.0
3087
+
is-typed-array: 1.1.15
3088
+
reflect.getprototypeof: 1.0.10
3089
+
3090
+
typed-array-length@1.0.7:
3091
+
dependencies:
3092
+
call-bind: 1.0.8
3093
+
for-each: 0.3.5
3094
+
gopd: 1.2.0
3095
+
is-typed-array: 1.1.15
3096
+
possible-typed-array-names: 1.1.0
3097
+
reflect.getprototypeof: 1.0.10
3098
+
3099
+
typescript-eslint@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2):
3100
+
dependencies:
3101
+
'@typescript-eslint/eslint-plugin': 8.29.0(@typescript-eslint/parser@8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2))(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
3102
+
'@typescript-eslint/parser': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
3103
+
'@typescript-eslint/utils': 8.29.0(eslint@9.23.0(jiti@2.4.2))(typescript@5.8.2)
3104
+
eslint: 9.23.0(jiti@2.4.2)
3105
+
typescript: 5.8.2
3106
+
transitivePeerDependencies:
3107
+
- supports-color
3108
+
3109
+
typescript@5.8.2: {}
3110
+
3111
+
ufo@1.5.4: {}
3112
+
3113
+
unbox-primitive@1.1.0:
3114
+
dependencies:
3115
+
call-bound: 1.0.4
3116
+
has-bigints: 1.1.0
3117
+
has-symbols: 1.1.0
3118
+
which-boxed-primitive: 1.1.1
3119
+
3120
+
unconfig@7.3.1:
3121
+
dependencies:
3122
+
'@quansync/fs': 0.1.2
3123
+
defu: 6.1.4
3124
+
jiti: 2.4.2
3125
+
quansync: 0.2.10
3126
+
3127
+
undici-types@6.20.0: {}
3128
+
3129
+
undici-types@6.21.0: {}
3130
+
3131
+
uri-js@4.4.1:
3132
+
dependencies:
3133
+
punycode: 2.3.1
3134
+
3135
+
utilium@1.10.1:
3136
+
dependencies:
3137
+
eventemitter3: 5.0.1
3138
+
optionalDependencies:
3139
+
'@xterm/xterm': 5.5.0
3140
+
3141
+
which-boxed-primitive@1.1.1:
3142
+
dependencies:
3143
+
is-bigint: 1.1.0
3144
+
is-boolean-object: 1.2.2
3145
+
is-number-object: 1.1.1
3146
+
is-string: 1.1.1
3147
+
is-symbol: 1.1.1
3148
+
3149
+
which-builtin-type@1.2.1:
3150
+
dependencies:
3151
+
call-bound: 1.0.4
3152
+
function.prototype.name: 1.1.8
3153
+
has-tostringtag: 1.0.2
3154
+
is-async-function: 2.1.1
3155
+
is-date-object: 1.1.0
3156
+
is-finalizationregistry: 1.1.1
3157
+
is-generator-function: 1.1.0
3158
+
is-regex: 1.2.1
3159
+
is-weakref: 1.1.1
3160
+
isarray: 2.0.5
3161
+
which-boxed-primitive: 1.1.1
3162
+
which-collection: 1.0.2
3163
+
which-typed-array: 1.1.19
3164
+
3165
+
which-collection@1.0.2:
3166
+
dependencies:
3167
+
is-map: 2.0.3
3168
+
is-set: 2.0.3
3169
+
is-weakmap: 2.0.2
3170
+
is-weakset: 2.0.4
3171
+
3172
+
which-typed-array@1.1.19:
3173
+
dependencies:
3174
+
available-typed-arrays: 1.0.7
3175
+
call-bind: 1.0.8
3176
+
call-bound: 1.0.4
3177
+
for-each: 0.3.5
3178
+
get-proto: 1.0.1
3179
+
gopd: 1.2.0
3180
+
has-tostringtag: 1.0.2
3181
+
3182
+
which@2.0.2:
3183
+
dependencies:
3184
+
isexe: 2.0.0
3185
+
3186
+
yaml@2.7.1: {}
3187
+
3188
+
yocto-queue@0.1.0: {}
3189
+
3190
+
zustand@5.0.3(@types/react@18.3.20):
3191
+
optionalDependencies:
3192
+
'@types/react': 18.3.20
+31
-1
pnpm-workspace.yaml
+31
-1
pnpm-workspace.yaml
···
1
1
packages:
2
-
- "packages/*"
2
+
- packages/*
3
+
4
+
catalogs:
5
+
dev:
6
+
esbuild: ^0.19.3
7
+
esbuild-copy-static-files: ^0.1.0
8
+
"@types/node": ^22.14.0
9
+
"@moonlight-mod/eslint-config": "github:moonlight-mod/eslint-config"
10
+
eslint: ^9.12.0
11
+
"@types/chrome": ^0.0.313
12
+
husky: ^8.0.3
13
+
prettier: ^3.1.0
14
+
typescript: ^5.3.3
15
+
taze: ^19.0.4
16
+
prod:
17
+
"@moonlight-mod/lunast": ^1.0.1
18
+
"@moonlight-mod/mappings": ^1.1.25
19
+
"@moonlight-mod/moonmap": ^1.0.5
20
+
microdiff: ^1.5.0
21
+
nanotar: ^0.1.1
22
+
"@zenfs/core": ^2.0.0
23
+
"@zenfs/dom": ^1.1.3
24
+
25
+
onlyBuiltDependencies:
26
+
- esbuild
27
+
28
+
engineStrict: true
29
+
strictSsl: true
30
+
strictDepBuilds: true
31
+
packageManagerStrict: true
32
+
registry: https://registry.npmjs.org/
+78
scripts/link.mjs
+78
scripts/link.mjs
···
1
+
// Janky script to get around pnpm link issues
2
+
// Probably don't use this. Probably
3
+
/* eslint-disable no-console */
4
+
const fs = require("fs");
5
+
const path = require("path");
6
+
const child_process = require("child_process");
7
+
8
+
const cwd = process.cwd();
9
+
const onDisk = {
10
+
//"@moonlight-mod/lunast": "../lunast",
11
+
//"@moonlight-mod/moonmap": "../moonmap",
12
+
"@moonlight-mod/mappings": "../mappings"
13
+
};
14
+
15
+
function exec(cmd, dir) {
16
+
child_process.execSync(cmd, { cwd: dir, stdio: "inherit" });
17
+
}
18
+
19
+
function getDeps(packageJSON) {
20
+
const ret = {};
21
+
Object.assign(ret, packageJSON.dependencies || {});
22
+
Object.assign(ret, packageJSON.devDependencies || {});
23
+
Object.assign(ret, packageJSON.peerDependencies || {});
24
+
return ret;
25
+
}
26
+
27
+
function link(dir) {
28
+
const packageJSONPath = path.join(dir, "package.json");
29
+
if (!fs.existsSync(packageJSONPath)) return;
30
+
const packageJSON = JSON.parse(fs.readFileSync(packageJSONPath, "utf8"));
31
+
const deps = getDeps(packageJSON);
32
+
33
+
for (const [dep, relativePath] of Object.entries(onDisk)) {
34
+
const fullPath = path.join(cwd, relativePath);
35
+
if (deps[dep]) {
36
+
exec(`pnpm link ${fullPath}`, dir);
37
+
}
38
+
}
39
+
}
40
+
41
+
function undo(dir) {
42
+
exec("pnpm unlink", dir);
43
+
try {
44
+
if (fs.existsSync(path.join(dir, "pnpm-lock.yaml"))) {
45
+
exec("git restore pnpm-lock.yaml", dir);
46
+
}
47
+
} catch {
48
+
// ignored
49
+
}
50
+
}
51
+
52
+
const shouldUndo = process.argv.includes("--undo");
53
+
const packages = fs.readdirSync("./packages");
54
+
55
+
for (const path of Object.values(onDisk)) {
56
+
console.log(path);
57
+
if (shouldUndo) {
58
+
undo(path);
59
+
} else {
60
+
link(path);
61
+
}
62
+
}
63
+
64
+
if (shouldUndo) {
65
+
console.log(cwd);
66
+
undo(cwd);
67
+
for (const pkg of packages) {
68
+
const dir = path.join(cwd, "packages", pkg);
69
+
console.log(dir);
70
+
undo(dir);
71
+
}
72
+
} else {
73
+
for (const pkg of packages) {
74
+
const dir = path.join(cwd, "packages", pkg);
75
+
console.log(dir);
76
+
link(dir);
77
+
}
78
+
}
+35
tsconfig.base.json
+35
tsconfig.base.json
···
1
+
{
2
+
"$schema": "https://json.schemastore.org/tsconfig.json",
3
+
"display": "Base",
4
+
"_version": "1.0.0",
5
+
"compilerOptions": {
6
+
"incremental": true,
7
+
"target": "ES2022",
8
+
"jsx": "react",
9
+
"lib": ["ESNext", "ESNext.Disposable", "DOM", "DOM.Iterable"],
10
+
"module": "ES2020",
11
+
"moduleResolution": "Bundler",
12
+
"resolveJsonModule": true,
13
+
"allowArbitraryExtensions": false,
14
+
"allowImportingTsExtensions": true,
15
+
"allowJs": true,
16
+
"strict": true,
17
+
"strictNullChecks": true,
18
+
19
+
// disable unreachable code detection because it breaks with esbuild labels
20
+
"allowUnreachableCode": true,
21
+
"noFallthroughCasesInSwitch": true,
22
+
"noImplicitReturns": true,
23
+
"declaration": true,
24
+
"declarationMap": true,
25
+
"outDir": "dist",
26
+
"sourceMap": true,
27
+
"stripInternal": true,
28
+
"esModuleInterop": true,
29
+
"forceConsistentCasingInFileNames": true,
30
+
"noErrorTruncation": true,
31
+
"verbatimModuleSyntax": false,
32
+
// meriyah has a broken import lol
33
+
"skipLibCheck": true
34
+
}
35
+
}
+7
-13
tsconfig.json
+7
-13
tsconfig.json
···
1
1
{
2
+
"extends": ["./tsconfig.base.json"],
2
3
"compilerOptions": {
3
-
"target": "es2016",
4
-
"module": "es6",
5
-
"esModuleInterop": true,
6
-
"forceConsistentCasingInFileNames": true,
7
-
"strict": true,
8
-
"skipLibCheck": true,
9
-
"moduleResolution": "bundler",
10
4
"baseUrl": "./packages/",
11
-
"jsx": "react",
12
-
13
-
// disable unreachable code detection because it breaks with esbuild labels
14
-
"allowUnreachableCode": true
5
+
"noEmit": true
15
6
},
16
-
"include": ["./packages/**/*", "./env.d.ts"],
17
-
"exclude": ["node_modules"]
7
+
"exclude": [
8
+
"**/node_modules/**",
9
+
"**/dist/**",
10
+
"**/build/**"
11
+
]
18
12
}