this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+14500 -3293
.github
.husky
img
nix
packages
browser
core
core-extensions
src
injector
node-preload
types
web-preload
scripts
+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
···
··· 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
··· 15 name: Nightly builds on GitHub Pages 16 runs-on: ubuntu-latest 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 25 with: 26 - node-version: 18 27 cache: pnpm 28 29 - name: Install dependencies ··· 31 - name: Build moonlight 32 env: 33 NODE_ENV: production 34 run: pnpm run build 35 36 - name: Write ref/commit to file 37 run: | 38 find ./dist -type f -not -path "./dist/files" > ./dist/files 39 echo "${{ github.sha }}" > ./dist/ref 40 echo "${{ github.ref }}" >> ./dist/ref 41 echo "$(date +%s)" >> ./dist/ref 42 43 - name: Setup GitHub Pages 44 - uses: actions/configure-pages@v3 45 - name: Upload artifact 46 - uses: actions/upload-pages-artifact@v1 47 with: 48 path: ./dist 49 - name: Deploy to GitHub Pages 50 - uses: actions/deploy-pages@v2
··· 15 name: Nightly builds on GitHub Pages 16 runs-on: ubuntu-latest 17 steps: 18 + - uses: actions/checkout@v4 19 + - uses: pnpm/action-setup@v4 20 + - uses: actions/setup-node@v4 21 with: 22 + node-version: 22 23 cache: pnpm 24 25 - name: Install dependencies ··· 27 - name: Build moonlight 28 env: 29 NODE_ENV: production 30 + MOONLIGHT_BRANCH: nightly 31 + MOONLIGHT_VERSION: ${{ github.sha }} 32 run: pnpm run build 33 34 - name: Write ref/commit to file 35 run: | 36 + cd ./dist 37 + tar -czf ../dist.tar.gz * 38 + cd .. 39 + mv ./dist.tar.gz ./dist/dist.tar.gz 40 find ./dist -type f -not -path "./dist/files" > ./dist/files 41 echo "${{ github.sha }}" > ./dist/ref 42 echo "${{ github.ref }}" >> ./dist/ref 43 echo "$(date +%s)" >> ./dist/ref 44 45 - name: Setup GitHub Pages 46 + uses: actions/configure-pages@v5 47 - name: Upload artifact 48 + uses: actions/upload-pages-artifact@v3 49 with: 50 path: ./dist 51 - name: Deploy to GitHub Pages 52 + uses: actions/deploy-pages@v4
+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
··· 13 name: Release builds to GitHub Releases 14 runs-on: ubuntu-latest 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 23 with: 24 - node-version: 18 25 cache: pnpm 26 27 - name: Install dependencies ··· 29 - name: Build moonlight 30 env: 31 NODE_ENV: production 32 run: pnpm run build 33 - name: Create archive 34 run: |
··· 13 name: Release builds to GitHub Releases 14 runs-on: ubuntu-latest 15 steps: 16 + - uses: actions/checkout@v4 17 + - uses: pnpm/action-setup@v4 18 + - uses: actions/setup-node@v4 19 with: 20 + node-version: 22 21 cache: pnpm 22 23 - name: Install dependencies ··· 25 - name: Build moonlight 26 env: 27 NODE_ENV: production 28 + MOONLIGHT_BRANCH: stable 29 + MOONLIGHT_VERSION: ${{ github.ref_name }} 30 run: pnpm run build 31 - name: Create archive 32 run: |
+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
··· 1 node_modules/ 2 dist/ 3 dist.tar.gz
··· 1 node_modules/ 2 dist/ 3 dist.tar.gz 4 + .DS_Store 5 + eslint_report.json 6 + .eslintcache 7 + # Nix 8 + /result 9 + *.drv 10 + 11 + # IDEs 12 + .vscode/ 13 + .idea/
+4
.husky/pre-commit
···
··· 1 + #!/usr/bin/env sh 2 + . "$(dirname -- "$0")/_/husky.sh" 3 + 4 + pnpm run check
+1
.prettierignore
···
··· 1 + pnpm-lock.yaml
+4 -4
.prettierrc
··· 1 { 2 - "printWidth": 80, 3 - "trailingComma": "none", 4 - "tabWidth": 2, 5 - "singleQuote": false 6 }
··· 1 { 2 + "printWidth": 120, 3 + "trailingComma": "none", 4 + "tabWidth": 2, 5 + "singleQuote": false 6 }
+4 -1
CHANGELOG.md
··· 1 - - Fix release builds
··· 1 + ## Core 2 + 3 + - Updated mappings 4 + - Fixed using remapped paths as patch finds not working
+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
··· 1 <h3 align="center"> 2 - <img src="./img/wordmark.png" alt="moonlight" /> 3 4 - <a href="https://discord.gg/FdZBTFCP6F">Discord server</a> 5 \- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a> 6 - \- <a href="https://moonlight-mod.github.io/">Docs</a> 7 8 <hr /> 9 </h3> 10 11 **moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience. 12 13 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 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. 16 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.
··· 1 <h3 align="center"> 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> 7 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> 11 \- <a href="https://github.com/moonlight-mod/moonlight">GitHub</a> 12 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> 20 </h3> 21 22 **moonlight** is yet another Discord client mod, focused on providing a decent user and developer experience. 23 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. 25 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. 27 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
··· 1 import * as esbuild from "esbuild"; 2 import copyStaticFiles from "esbuild-copy-static-files"; 3 ··· 12 13 const prod = process.env.NODE_ENV === "production"; 14 const watch = process.argv.includes("--watch"); 15 16 const external = [ 17 "electron", 18 "fs", 19 "path", 20 "module", 21 - "events", 22 - "original-fs", // wtf asar? 23 24 // Silence an esbuild warning 25 "./node-preload.js" 26 ]; 27 28 async function build(name, entry) { 29 - const outfile = path.join("./dist", name + ".js"); 30 31 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"); 35 36 const define = { 37 MOONLIGHT_ENV: `"${name}"`, 38 - MOONLIGHT_PROD: prod.toString() 39 }; 40 41 - for (const iterName of Object.keys(config)) { 42 const snake = iterName.replace(/-/g, "_").toUpperCase(); 43 define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); 44 } ··· 46 const nodeDependencies = ["glob"]; 47 const ignoredExternal = name === "web-preload" ? nodeDependencies : []; 48 49 const esbuildConfig = { 50 entryPoints: [entry], 51 outfile, 52 53 - format: "cjs", 54 - platform: name === "web-preload" ? "browser" : "node", 55 56 treeShaking: true, 57 bundle: true, ··· 61 external: [...ignoredExternal, ...external], 62 63 define, 64 - dropLabels 65 }; 66 67 if (watch) { 68 const ctx = await esbuild.context(esbuildConfig); 69 await ctx.watch(); ··· 72 } 73 } 74 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 }); 79 } 80 81 - const entryPoint = `packages/core-extensions/src/${ext}/${side}.${fileExt}`; 82 83 const esbuildConfig = { 84 - entryPoints: [entryPoint], 85 - outfile: path.join(outDir, side + ".js"), 86 87 - format: "cjs", 88 platform: "node", 89 90 treeShaking: true, ··· 93 94 external, 95 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 - : [] 104 }; 105 106 if (watch) { ··· 113 114 const promises = []; 115 116 - for (const [name, entry] of Object.entries(config)) { 117 - promises.push(build(name, entry)); 118 - } 119 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; 133 } 134 } 135 }
··· 1 + /* eslint-disable no-console */ 2 import * as esbuild from "esbuild"; 3 import copyStaticFiles from "esbuild-copy-static-files"; 4 ··· 13 14 const prod = process.env.NODE_ENV === "production"; 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"; 22 23 const external = [ 24 "electron", 25 "fs", 26 "path", 27 "module", 28 + "discord", // mappings 29 30 // Silence an esbuild warning 31 "./node-preload.js" 32 ]; 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 + 78 async function build(name, entry) { 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"); 82 83 const dropLabels = []; 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 + } 98 99 const define = { 100 MOONLIGHT_ENV: `"${name}"`, 101 + MOONLIGHT_PROD: prod.toString(), 102 + MOONLIGHT_BRANCH: `"${buildBranch}"`, 103 + MOONLIGHT_VERSION: `"${buildVersion}"` 104 }; 105 106 + for (const iterName of ["injector", "node-preload", "web-preload", "browser"]) { 107 const snake = iterName.replace(/-/g, "_").toUpperCase(); 108 define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); 109 } ··· 111 const nodeDependencies = ["glob"]; 112 const ignoredExternal = name === "web-preload" ? nodeDependencies : []; 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} */ 147 const esbuildConfig = { 148 entryPoints: [entry], 149 outfile, 150 151 + format: "iife", 152 + globalName: "module.exports", 153 + 154 + platform: ["web-preload", "browser"].includes(name) ? "browser" : "node", 155 156 treeShaking: true, 157 bundle: true, ··· 161 external: [...ignoredExternal, ...external], 162 163 define, 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 176 }; 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 + 201 if (watch) { 202 const ctx = await esbuild.context(esbuildConfig); 203 await ctx.watch(); ··· 206 } 207 } 208 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 + } 235 } 236 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`; 252 253 const esbuildConfig = { 254 + entryPoints, 255 + outdir, 256 257 + format: "iife", 258 + globalName: "module.exports", 259 platform: "node", 260 261 treeShaking: true, ··· 264 265 external, 266 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 + ] 288 }; 289 290 if (watch) { ··· 297 298 const promises = []; 299 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 + } 308 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 + } 316 } 317 } 318 }
-1
env.d.ts
··· 1 - /// <reference types="./packages/types/src/index.d.ts" />
···
+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
···
··· 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
···
··· 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

This is a binary file and will not be displayed.

+57
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 + outputs = [ "out" "firefox" ]; 15 + 16 + nativeBuildInputs = [ 17 + nodejs_22 18 + pnpm_10.configHook 19 + ]; 20 + 21 + pnpmDeps = pnpm_10.fetchDeps { 22 + inherit (finalAttrs) pname version src; 23 + hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU="; 24 + }; 25 + 26 + env = { 27 + NODE_ENV = "production"; 28 + MOONLIGHT_VERSION = "v${finalAttrs.version}"; 29 + }; 30 + 31 + buildPhase = '' 32 + runHook preBuild 33 + 34 + pnpm run build 35 + pnpm run browser-mv2 36 + 37 + runHook postBuild 38 + ''; 39 + 40 + installPhase = '' 41 + runHook preInstall 42 + 43 + cp -r dist $out 44 + 45 + mkdir -p $firefox/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/ 46 + mv $out/browser-mv2 $firefox/share/mozilla/extensions/{ec8030f7-c20a-464f-9b0e-13a3a9e97384}/{0fb6d66f-f22d-4555-a87b-34ef4bea5e2a} 47 + 48 + runHook postInstall 49 + ''; 50 + 51 + meta = with lib; { 52 + description = "Yet another Discord mod"; 53 + homepage = "https://moonlight-mod.github.io/"; 54 + license = licenses.lgpl3; 55 + maintainers = with maintainers; [ notnite ]; 56 + }; 57 + })
+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
···
··· 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
··· 1 { 2 "name": "moonlight", 3 - "version": "1.0.1", 4 "description": "Yet another Discord mod", 5 "homepage": "https://moonlight-mod.github.io/", 6 "repository": { 7 "type": "git", ··· 10 "bugs": { 11 "url": "https://github.com/moonlight-mod/moonlight/issues" 12 }, 13 "scripts": { 14 "build": "node build.mjs", 15 - "dev": "node build.mjs --watch" 16 }, 17 "devDependencies": { 18 - "esbuild": "^0.19.3", 19 - "esbuild-copy-static-files": "^0.1.0" 20 } 21 }
··· 1 { 2 "name": "moonlight", 3 + "version": "1.3.14", 4 + "packageManager": "pnpm@10.7.1", 5 "description": "Yet another Discord mod", 6 + "license": "LGPL-3.0-or-later", 7 "homepage": "https://moonlight-mod.github.io/", 8 "repository": { 9 "type": "git", ··· 12 "bugs": { 13 "url": "https://github.com/moonlight-mod/moonlight/issues" 14 }, 15 + "engineStrict": true, 16 + "engines": { 17 + "node": ">=22", 18 + "pnpm": ">=10", 19 + "npm": "pnpm", 20 + "yarn": "pnpm" 21 + }, 22 "scripts": { 23 "build": "node build.mjs", 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" 35 }, 36 "devDependencies": { 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" 46 } 47 }
+14
packages/browser/blockLoading.json
···
··· 1 + [ 2 + { 3 + "id": 2, 4 + "priority": 1, 5 + "action": { 6 + "type": "block" 7 + }, 8 + "condition": { 9 + "requestDomains": ["discord.com", "discordapp.com"], 10 + "urlFilter": "*/assets/*.js", 11 + "resourceTypes": ["script"] 12 + } 13 + } 14 + ]
+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 + }
+33
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 + "browser_specific_settings": { 29 + "gecko": { 30 + "id": "{0fb6d66f-f22d-4555-a87b-34ef4bea5e2a}" 31 + } 32 + } 33 + }
+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
···
··· 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
···
··· 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
···
··· 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 + );
+161
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 + basename(path) { 95 + const parts = getParts(path); 96 + return parts[parts.length - 1]; 97 + } 98 + }, 99 + // TODO 100 + addCors(url) {}, 101 + addBlocked(url) {} 102 + }; 103 + 104 + // Actual loading begins here 105 + let config = await readConfig(); 106 + initLogger(config); 107 + 108 + const extensions = await getExtensions(); 109 + const processedExtensions = await loadExtensions(extensions); 110 + 111 + const moonlightNode: MoonlightNode = { 112 + get config() { 113 + return config; 114 + }, 115 + extensions, 116 + processedExtensions, 117 + nativesCache: {}, 118 + isBrowser: true, 119 + events: createEventEmitter<NodeEventType, NodeEventPayloads>(), 120 + 121 + version: MOONLIGHT_VERSION, 122 + branch: MOONLIGHT_BRANCH as MoonlightBranch, 123 + 124 + getConfig(ext) { 125 + return getConfig(ext, config); 126 + }, 127 + getConfigOption(ext, name) { 128 + const manifest = getManifest(extensions, ext); 129 + return getConfigOption(ext, name, config, manifest?.settings); 130 + }, 131 + async setConfigOption(ext, name, value) { 132 + setConfigOption(config, ext, name, value); 133 + await this.writeConfig(config); 134 + }, 135 + 136 + getNatives: () => {}, 137 + getLogger: (id: string) => { 138 + return new Logger(id); 139 + }, 140 + 141 + getMoonlightDir() { 142 + return "/"; 143 + }, 144 + getExtensionDir: (ext: string) => { 145 + return `/extensions/${ext}`; 146 + }, 147 + 148 + async writeConfig(newConfig) { 149 + await writeConfig(newConfig); 150 + config = newConfig; 151 + this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig); 152 + } 153 + }; 154 + 155 + Object.assign(window, { 156 + moonlightNode 157 + }); 158 + 159 + // This is set by web-preload for us 160 + await window._moonlightWebLoad!(); 161 + };
+7
packages/browser/tsconfig.json
···
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "ESNext.AsyncIterable"], 5 + "module": "ES2022" 6 + } 7 + }
+7 -1
packages/core/package.json
··· 4 "exports": { 5 "./*": "./src/*.ts" 6 }, 7 "dependencies": { 8 - "glob": "^10.3.4", 9 "@moonlight-mod/types": "workspace:*" 10 } 11 }
··· 4 "exports": { 5 "./*": "./src/*.ts" 6 }, 7 + "engineStrict": true, 8 + "engines": { 9 + "node": ">=22", 10 + "pnpm": ">=10", 11 + "npm": "pnpm", 12 + "yarn": "pnpm" 13 + }, 14 "dependencies": { 15 "@moonlight-mod/types": "workspace:*" 16 } 17 }
+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
··· 1 - import { Config, constants } from "@moonlight-mod/types"; 2 - import requireImport from "./util/import"; 3 import { getConfigPath } from "./util/data"; 4 5 const defaultConfig: Config = { 6 - extensions: {}, 7 - repositories: ["https://moonlight-mod.github.io/extensions/repo.json"] 8 }; 9 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; 23 } 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 } 33 34 - export function readConfig(): Config { 35 webPreload: { 36 return moonlightNode.config; 37 } 38 39 - nodePreload: { 40 - return readConfigNode(); 41 - } 42 43 - injector: { 44 - return readConfigNode(); 45 } 46 - 47 - throw new Error("Called readConfig() in an impossible environment"); 48 }
··· 1 + import { Config } from "@moonlight-mod/types"; 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"); 7 8 const defaultConfig: Config = { 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] 17 }; 18 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); 25 } 26 } 27 28 + export async function readConfig(): Promise<Config> { 29 webPreload: { 30 return moonlightNode.config; 31 } 32 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); 43 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 + } 52 } 53 }
+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
··· 1 import { 2 ExtensionWebExports, 3 DetectedExtension, 4 - ProcessedExtensions 5 } from "@moonlight-mod/types"; 6 import { readConfig } from "../config"; 7 import Logger from "../util/logger"; 8 - import { getExtensions } from "../extension"; 9 import { registerPatch, registerWebpackModule } from "../patch"; 10 import calculateDependencies from "../util/dependency"; 11 import { createEventEmitter } from "../util/event"; 12 13 const logger = new Logger("core/extension/loader"); 14 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); 21 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 - ]); 30 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 - }); 40 41 - registerPatch({ ...newPatch, ext: ext.id, id: idx }); 42 - idx++; 43 - } 44 - } else { 45 - registerPatch({ ...patch, ext: ext.id, id: idx }); 46 - idx++; 47 - } 48 } 49 } 50 51 - if (exports.webpackModules != null) { 52 - for (const [name, wp] of Object.entries(exports.webpackModules)) { 53 registerWebpackModule({ ...wp, ext: ext.id, id: name }); 54 } 55 } 56 } 57 } 58 59 nodePreload: { ··· 78 } 79 } 80 81 /* 82 This function resolves extensions and loads them, split into a few stages: 83 ··· 92 extensions fires an event on completion, which allows us to await the loading 93 of another extension, resolving dependencies & load order effectively. 94 */ 95 - export async function loadExtensions( 96 - exts: DetectedExtension[] 97 - ): Promise<ProcessedExtensions> { 98 const items = exts 99 .map((ext) => { 100 return { ··· 104 }) 105 .sort((a, b) => a.id.localeCompare(b.id)); 106 107 - const [sorted, dependencyGraph] = calculateDependencies( 108 - items, 109 - 110 - function fetchDep(id) { 111 return exts.find((x) => x.id === id) ?? null; 112 }, 113 114 - function getDeps(item) { 115 return item.data.manifest.dependencies ?? []; 116 }, 117 118 - function getIncompatible(item) { 119 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 - ); 153 154 - if (dependsOnMe.length > 0) { 155 - logger.debug("Implicitly enabling extension", ext.id); 156 - implicitlyEnabled.push(ext.id); 157 - } 158 } 159 - } 160 - 161 - for (const ext of exts) validateDeps(ext); 162 - exts = exts.filter((e) => isEnabledInConfig(e)); 163 164 return { 165 - extensions: exts, 166 dependencyGraph 167 }; 168 } 169 170 - export async function loadProcessedExtensions({ 171 - extensions, 172 - dependencyGraph 173 - }: ProcessedExtensions) { 174 - const eventEmitter = createEventEmitter(); 175 const finished: Set<string> = new Set(); 176 177 - logger.debug( 178 "Load stage - extension list:", 179 extensions.map((x) => x.id) 180 ); ··· 193 } 194 195 function done() { 196 - eventEmitter.removeEventListener("ext-ready", cb); 197 r(); 198 } 199 200 - eventEmitter.addEventListener("ext-ready", cb); 201 if (finished.has(dep)) done(); 202 }) 203 ); 204 205 if (waitPromises.length > 0) { 206 - logger.debug( 207 - `Waiting on ${waitPromises.length} dependencies for "${ext.id}"` 208 - ); 209 await Promise.all(waitPromises); 210 } 211 ··· 213 await loadExt(ext); 214 215 finished.add(ext.id); 216 - eventEmitter.dispatchEvent("ext-ready", ext.id); 217 logger.debug(`Loaded "${ext.id}"`); 218 } 219 220 - webPreload: { 221 for (const ext of extensions) { 222 moonlight.enabledExtensions.add(ext.id); 223 }
··· 1 import { 2 ExtensionWebExports, 3 DetectedExtension, 4 + ProcessedExtensions, 5 + WebpackModuleFunc, 6 + constants, 7 + ExtensionManifest, 8 + ExtensionEnvironment 9 } from "@moonlight-mod/types"; 10 import { readConfig } from "../config"; 11 import Logger from "../util/logger"; 12 import { registerPatch, registerWebpackModule } from "../patch"; 13 import calculateDependencies from "../util/dependency"; 14 import { createEventEmitter } from "../util/event"; 15 + import { registerStyles } from "../styles"; 16 + import { WebEventPayloads, WebEventType } from "@moonlight-mod/types/core/event"; 17 18 const logger = new Logger("core/extension/loader"); 19 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); 40 41 + URL.revokeObjectURL(url); 42 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 + } 58 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 }); 66 } 67 + idx++; 68 } 69 + } 70 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 { 83 registerWebpackModule({ ...wp, ext: ext.id, id: name }); 84 } 85 } 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 + } 104 } 105 106 nodePreload: { ··· 125 } 126 } 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 + 149 /* 150 This function resolves extensions and loads them, split into a few stages: 151 ··· 160 extensions fires an event on completion, which allows us to await the loading 161 of another extension, resolving dependencies & load order effectively. 162 */ 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(); 167 const items = exts 168 .map((ext) => { 169 return { ··· 173 }) 174 .sort((a, b) => a.id.localeCompare(b.id)); 175 176 + const [sorted, dependencyGraph] = calculateDependencies(items, { 177 + fetchDep: (id) => { 178 return exts.find((x) => x.id === id) ?? null; 179 }, 180 181 + getDeps: (item) => { 182 return item.data.manifest.dependencies ?? []; 183 }, 184 185 + getIncompatible: (item) => { 186 return item.data.manifest.incompatible ?? []; 187 + }, 188 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; 195 } 196 + }); 197 198 return { 199 + extensions: sorted.map((x) => x.data), 200 dependencyGraph 201 }; 202 } 203 204 + export async function loadProcessedExtensions({ extensions, dependencyGraph }: ProcessedExtensions) { 205 + const eventEmitter = createEventEmitter<WebEventType, WebEventPayloads>(); 206 const finished: Set<string> = new Set(); 207 208 + logger.trace( 209 "Load stage - extension list:", 210 extensions.map((x) => x.id) 211 ); ··· 224 } 225 226 function done() { 227 + eventEmitter.removeEventListener(WebEventType.ExtensionLoad, cb); 228 r(); 229 } 230 231 + eventEmitter.addEventListener(WebEventType.ExtensionLoad, cb); 232 if (finished.has(dep)) done(); 233 }) 234 ); 235 236 if (waitPromises.length > 0) { 237 + logger.debug(`Waiting on ${waitPromises.length} dependencies for "${ext.id}"`); 238 await Promise.all(waitPromises); 239 } 240 ··· 242 await loadExt(ext); 243 244 finished.add(ext.id); 245 + eventEmitter.dispatchEvent(WebEventType.ExtensionLoad, ext.id); 246 logger.debug(`Loaded "${ext.id}"`); 247 } 248 249 + webTarget: { 250 for (const ext of extensions) { 251 moonlight.enabledExtensions.add(ext.id); 252 }
+144 -67
packages/core/src/extension.ts
··· 1 - import { 2 - ExtensionManifest, 3 - DetectedExtension, 4 - ExtensionLoadSource, 5 - constants 6 - } from "@moonlight-mod/types"; 7 import { readConfig } from "./config"; 8 - import requireImport from "./util/import"; 9 import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; 10 11 - function loadDetectedExtensions( 12 dir: string, 13 - type: ExtensionLoadSource 14 - ): DetectedExtension[] { 15 - const fs = requireImport("fs"); 16 - const path = requireImport("path"); 17 const ret: DetectedExtension[] = []; 18 19 - const glob = require("glob"); 20 - const manifests = glob.sync(dir + "/**/manifest.json"); 21 - 22 for (const manifestPath of manifests) { 23 - if (!fs.existsSync(manifestPath)) continue; 24 - const dir = path.dirname(manifestPath); 25 26 - const manifest: ExtensionManifest = JSON.parse( 27 - fs.readFileSync(manifestPath, "utf8") 28 - ); 29 30 - const webPath = path.join(dir, "index.js"); 31 - const nodePath = path.join(dir, "node.js"); 32 - const hostPath = path.join(dir, "host.js"); 33 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; 41 } 42 43 - const web = fs.existsSync(webPath) 44 - ? fs.readFileSync(webPath, "utf8") 45 - : undefined; 46 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"); 51 } 52 53 ret.push({ 54 id: manifest.id, 55 manifest, 56 source: { 57 - type, 58 - url 59 }, 60 scripts: { 61 web, 62 - webPath: web != null ? webPath : undefined, 63 - nodePath: fs.existsSync(nodePath) ? nodePath : undefined, 64 - hostPath: fs.existsSync(hostPath) ? hostPath : undefined 65 } 66 }); 67 } 68 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 - ); 88 } 89 90 - return res; 91 } 92 93 - export function getExtensions(): DetectedExtension[] { 94 webPreload: { 95 return moonlightNode.extensions; 96 } 97 98 - nodePreload: { 99 - return getExtensionsNative(); 100 } 101 102 - injector: { 103 - return getExtensionsNative(); 104 } 105 106 throw new Error("Called getExtensions() outside of node-preload/web-preload");
··· 1 + import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, constants } from "@moonlight-mod/types"; 2 import { readConfig } from "./config"; 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 + } 23 24 + return ret; 25 + } 26 + 27 + async function loadDetectedExtensions( 28 dir: string, 29 + type: ExtensionLoadSource, 30 + seen: Set<string> 31 + ): Promise<DetectedExtension[]> { 32 const ret: DetectedExtension[] = []; 33 34 + const manifests = await findManifests(dir); 35 for (const manifestPath of manifests) { 36 + try { 37 + if (!(await moonlightNodeSandboxed.fs.exists(manifestPath))) continue; 38 + const dir = moonlightNodeSandboxed.fs.dirname(manifestPath); 39 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 + } 69 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); 74 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); 106 } 107 + } 108 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`]; 139 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 + } 146 } 147 148 ret.push({ 149 id: manifest.id, 150 manifest, 151 source: { 152 + type: ExtensionLoadSource.Core 153 }, 154 scripts: { 155 web, 156 + webpackModules: wpModules, 157 + style: coreExtensionsFs[`${ext}/style.css`] 158 } 159 }); 160 + seen.add(manifest.id); 161 } 162 163 + if (await moonlightNodeSandboxed.fs.exists("/extensions")) { 164 + ret.push(...(await loadDetectedExtensions("/extensions", ExtensionLoadSource.Normal, seen))); 165 } 166 167 + return ret; 168 } 169 170 + export async function getExtensions(): Promise<DetectedExtension[]> { 171 webPreload: { 172 return moonlightNode.extensions; 173 } 174 175 + browser: { 176 + return await getExtensionsBrowser(); 177 } 178 179 + nodeTarget: { 180 + return await getExtensionsNative(); 181 } 182 183 throw new Error("Called getExtensions() outside of node-preload/web-preload");
+56
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 + basename(dir) { 53 + return path.basename(dir); 54 + } 55 + }; 56 + }
+272 -121
packages/core/src/patch.ts
··· 6 IdentifiedWebpackModule, 7 WebpackJsonp, 8 WebpackJsonpEntry, 9 - WebpackModuleFunc 10 } from "@moonlight-mod/types"; 11 import Logger from "./util/logger"; 12 import calculateDependencies, { Dependency } from "./util/dependency"; 13 - import WebpackRequire from "@moonlight-mod/types/discord/require"; 14 15 const logger = new Logger("core/patch"); 16 17 // Can't be Set because we need splice 18 - let patches: IdentifiedPatch[] = []; 19 let webpackModules: Set<IdentifiedWebpackModule> = new Set(); 20 21 export function registerPatch(patch: IdentifiedPatch) { 22 patches.push(patch); 23 } 24 25 export function registerWebpackModule(wp: IdentifiedWebpackModule) { 26 webpackModules.add(wp); 27 } 28 29 /* ··· 38 const moduleCache: Record<string, string> = {}; 39 const patched: Record<string, Array<string>> = {}; 40 41 function patchModules(entry: WebpackJsonpEntry[1]) { 42 for (const [id, func] of Object.entries(entry)) { 43 - let moduleString = moduleCache.hasOwnProperty(id) 44 - ? moduleCache[id] 45 - : func.toString().replace(/\n/g, ""); 46 47 - for (const patch of patches) { 48 if (patch.prerequisite != null && !patch.prerequisite()) { 49 continue; 50 } 51 ··· 54 patch.find.lastIndex = 0; 55 } 56 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); 62 63 // Global regexes apply to all modules 64 - const shouldRemove = 65 - typeof patch.find === "string" ? true : !patch.find.global; 66 67 if (match) { 68 - moonlight.unpatched.delete(patch); 69 70 - // We ensured all arrays get turned into normal PatchReplace objects on register 71 - const replace = patch.replace as PatchReplace; 72 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 - } 85 86 - if (replaced === moduleString) { 87 - logger.warn("Patch replacement failed", id, patch); 88 - continue; 89 } 90 91 - // Store what extensions patched what modules for easier debugging 92 - patched[id] = patched[id] || []; 93 - patched[id].push(`${patch.ext}#${patch.id}`); 94 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(", "); 98 99 - const wrapped = 100 - `(${replaced}).apply(this, arguments)\n` + 101 - `// Patched by moonlight: ${patchedStr}\n` + 102 - `//# sourceURL=Webpack-Module-${id}`; 103 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); 116 } 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 } 133 } 134 } 135 136 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; 150 entry[id].__moonlight = true; 151 } 152 } 153 154 moduleCache[id] = moduleString; ··· 162 */ 163 let chunkId = Number.MAX_SAFE_INTEGER; 164 165 function handleModuleDependencies() { 166 const modules = Array.from(webpackModules.values()); 167 168 - const dependencies: Dependency<string, IdentifiedWebpackModule>[] = 169 - modules.map((wp) => { 170 - return { 171 - id: `${wp.ext}_${wp.id}`, 172 - data: wp 173 - }; 174 - }); 175 176 - const [sorted, _] = calculateDependencies( 177 - dependencies, 178 - 179 - function fetchDep(id) { 180 - return modules.find((x) => id === `${x.ext}_${x.id}`) ?? null; 181 }, 182 183 - function getDeps(item) { 184 const deps = item.data?.dependencies ?? []; 185 return ( 186 deps.filter( 187 - (dep) => !(dep instanceof RegExp || typeof dep === "string") 188 ) as ExplicitExtensionDependency[] 189 - ).map((x) => `${x.ext}_${x.id}`); 190 } 191 - ); 192 193 webpackModules = new Set(sorted.map((x) => x.data)); 194 } ··· 199 const entrypoints: string[] = []; 200 let inject = false; 201 202 - for (const [modId, mod] of Object.entries(entry)) { 203 const modStr = mod.toString(); 204 - const wpModules = Array.from(webpackModules.values()); 205 - for (const wpModule of wpModules) { 206 - const id = wpModule.ext + "_" + wpModule.id; 207 if (wpModule.dependencies) { 208 const deps = new Set(wpModule.dependencies); 209 210 // FIXME: This dependency resolution might fail if the things we want 211 // got injected earlier. If weird dependencies fail, this is likely why. 212 if (deps.size) { 213 - for (const dep of deps.values()) { 214 if (typeof dep === "string") { 215 if (modStr.includes(dep)) deps.delete(dep); 216 } else if (dep instanceof RegExp) { 217 if (dep.test(modStr)) deps.delete(dep); 218 } else if ( 219 - injectedWpModules.find( 220 - (x) => x.ext === dep.ext && x.id === dep.id 221 - ) 222 ) { 223 deps.delete(dep); 224 } 225 } 226 227 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 continue; 233 } 234 - 235 - wpModule.dependencies = Array.from(deps); 236 } 237 } 238 239 webpackModules.delete(wpModule); 240 injectedWpModules.push(wpModule); 241 242 inject = true; 243 244 - modules[id] = wpModule.run; 245 - if (wpModule.entrypoint) entrypoints.push(id); 246 } 247 if (!webpackModules.size) break; 248 } 249 250 if (inject) { 251 logger.debug("Injecting modules:", modules, entrypoints); 252 window.webpackChunkdiscord_app.push([ 253 [--chunkId], 254 modules, 255 - (require: typeof WebpackRequire) => entrypoints.map(require) 256 ]); 257 } 258 } ··· 263 } 264 } 265 266 /* 267 Webpack modules are bundled into an array of arrays that hold each function. 268 Since we run code before Discord, we can create our own Webpack array and ··· 274 export async function installWebpackPatcher() { 275 await handleModuleDependencies(); 276 277 let realWebpackJsonp: WebpackJsonp | null = null; 278 Object.defineProperty(window, "webpackChunkdiscord_app", { 279 set: (jsonp: WebpackJsonp) => { ··· 285 const realPush = jsonp.push; 286 if (jsonp.push.__moonlight !== true) { 287 jsonp.push = (items) => { 288 patchModules(items[1]); 289 290 try { ··· 322 } 323 }); 324 325 - registerWebpackModule({ 326 - ext: "moonlight", 327 - id: "fix_rspack_init_modules", 328 - entrypoint: true, 329 - run: function (module, exports, require) { 330 - patchModules(require.m); 331 } 332 }); 333 }
··· 6 IdentifiedWebpackModule, 7 WebpackJsonp, 8 WebpackJsonpEntry, 9 + WebpackModuleFunc, 10 + WebpackRequireType 11 } from "@moonlight-mod/types"; 12 import Logger from "./util/logger"; 13 import calculateDependencies, { Dependency } from "./util/dependency"; 14 + import { WebEventType } from "@moonlight-mod/types/core/event"; 15 + import { processFind, processReplace, testFind } from "./util/patch"; 16 17 const logger = new Logger("core/patch"); 18 19 // Can't be Set because we need splice 20 + const patches: IdentifiedPatch[] = []; 21 let webpackModules: Set<IdentifiedWebpackModule> = new Set(); 22 + let webpackRequire: WebpackRequireType | null = null; 23 + 24 + const moduleLoadSubscriptions: Map<string, ((moduleId: string) => void)[]> = new Map(); 25 26 export function registerPatch(patch: IdentifiedPatch) { 27 + patch.find = processFind(patch.find); 28 + processReplace(patch.replace); 29 + 30 patches.push(patch); 31 + moonlight.unpatched.add(patch); 32 } 33 34 export function registerWebpackModule(wp: IdentifiedWebpackModule) { 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 + } 55 } 56 57 /* ··· 66 const moduleCache: Record<string, string> = {}; 67 const patched: Record<string, Array<string>> = {}; 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 + 103 function patchModules(entry: WebpackJsonpEntry[1]) { 104 + // Populate the module cache 105 for (const [id, func] of Object.entries(entry)) { 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; 114 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]; 127 if (patch.prerequisite != null && !patch.prerequisite()) { 128 + moonlight.unpatched.delete(patch); 129 continue; 130 } 131 ··· 134 patch.find.lastIndex = 0; 135 } 136 137 + const match = testFind(origModuleString, patch.find) || patch.find === mappedName; 138 139 // Global regexes apply to all modules 140 + const shouldRemove = typeof patch.find === "string" ? true : !patch.find.global; 141 142 + let replaced = moduleString; 143 + let hardFailed = false; 144 if (match) { 145 + // We ensured normal PatchReplace objects get turned into arrays on register 146 + const replaces = patch.replace as PatchReplace[]; 147 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); 154 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 + } 163 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; 181 } 182 + } 183 184 + if (!hardFailed) { 185 + moduleString = replaced; 186 + modified = true; 187 + exts.add(patch.ext); 188 + } 189 190 + if (isPatched) moonlight.unpatched.delete(patch); 191 + if (shouldRemove) patches.splice(i--, 1); 192 + } 193 + } 194 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 + } 201 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; 208 } 209 } 210 } 211 + } catch (e) { 212 + logger.error("Failed to parse script for LunAST", id, e); 213 } 214 215 if (moonlightNode.config.patchAll === true) { 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; 219 entry[id].__moonlight = true; 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); 234 } 235 236 moduleCache[id] = moduleString; ··· 244 */ 245 let chunkId = Number.MAX_SAFE_INTEGER; 246 247 + function depToString(x: ExplicitExtensionDependency) { 248 + return x.ext != null ? `${x.ext}_${x.id}` : x.id; 249 + } 250 + 251 function handleModuleDependencies() { 252 const modules = Array.from(webpackModules.values()); 253 254 + const dependencies: Dependency<string, IdentifiedWebpackModule>[] = modules.map((wp) => { 255 + return { 256 + id: depToString(wp), 257 + data: wp 258 + }; 259 + }); 260 261 + const [sorted, _] = calculateDependencies(dependencies, { 262 + fetchDep: (id) => { 263 + return modules.find((x) => id === depToString(x)) ?? null; 264 }, 265 266 + getDeps: (item) => { 267 const deps = item.data?.dependencies ?? []; 268 return ( 269 deps.filter( 270 + (dep) => !(dep instanceof RegExp || typeof dep === "string") && dep.ext != null 271 ) as ExplicitExtensionDependency[] 272 + ).map(depToString); 273 } 274 + }); 275 276 webpackModules = new Set(sorted.map((x) => x.data)); 277 } ··· 282 const entrypoints: string[] = []; 283 let inject = false; 284 285 + for (const [_modId, mod] of Object.entries(entry)) { 286 const modStr = mod.toString(); 287 + for (const wpModule of webpackModules) { 288 + const id = depToString(wpModule); 289 if (wpModule.dependencies) { 290 const deps = new Set(wpModule.dependencies); 291 292 // FIXME: This dependency resolution might fail if the things we want 293 // got injected earlier. If weird dependencies fail, this is likely why. 294 if (deps.size) { 295 + for (const dep of deps) { 296 if (typeof dep === "string") { 297 if (modStr.includes(dep)) deps.delete(dep); 298 } else if (dep instanceof RegExp) { 299 if (dep.test(modStr)) deps.delete(dep); 300 } else if ( 301 + dep.ext != null 302 + ? injectedWpModules.find((x) => x.ext === dep.ext && x.id === dep.id) 303 + : injectedWpModules.find((x) => x.id === dep.id) 304 ) { 305 deps.delete(dep); 306 } 307 } 308 309 + wpModule.dependencies = Array.from(deps); 310 if (deps.size !== 0) { 311 continue; 312 } 313 } 314 } 315 316 webpackModules.delete(wpModule); 317 + moonlight.pendingModules.delete(wpModule); 318 injectedWpModules.push(wpModule); 319 320 inject = true; 321 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 + } 335 } 336 if (!webpackModules.size) break; 337 } 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 + 353 if (inject) { 354 logger.debug("Injecting modules:", modules, entrypoints); 355 window.webpackChunkdiscord_app.push([ 356 [--chunkId], 357 modules, 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 + }) 370 ]); 371 } 372 } ··· 377 } 378 } 379 380 + function moduleSourceGetter(id: string) { 381 + return moduleCache[id] ?? null; 382 + } 383 + 384 /* 385 Webpack modules are bundled into an array of arrays that hold each function. 386 Since we run code before Discord, we can create our own Webpack array and ··· 392 export async function installWebpackPatcher() { 393 await handleModuleDependencies(); 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 + 408 let realWebpackJsonp: WebpackJsonp | null = null; 409 Object.defineProperty(window, "webpackChunkdiscord_app", { 410 set: (jsonp: WebpackJsonp) => { ··· 416 const realPush = jsonp.push; 417 if (jsonp.push.__moonlight !== true) { 418 jsonp.push = (items) => { 419 + moonlight.events.dispatchEvent(WebEventType.ChunkLoad, { 420 + chunkId: items[0], 421 + modules: items[1], 422 + require: items[2] 423 + }); 424 + 425 patchModules(items[1]); 426 427 try { ··· 459 } 460 }); 461 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 + }); 482 } 483 }); 484 }
+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
···
··· 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
···
··· 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

This is a binary file and will not be displayed.

+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
··· 1 import { constants } from "@moonlight-mod/types"; 2 - import requireImport from "./import"; 3 4 - export function getMoonlightDir(): string { 5 - const { app, ipcRenderer } = require("electron"); 6 - const fs = requireImport("fs"); 7 - const path = requireImport("path"); 8 9 let appData = ""; 10 injector: { 11 - appData = app.getPath("appData"); 12 } 13 14 nodePreload: { 15 - appData = ipcRenderer.sendSync(constants.ipcGetAppData); 16 } 17 18 - const dir = path.join(appData, "moonlight-mod"); 19 - if (!fs.existsSync(dir)) fs.mkdirSync(dir); 20 21 return dir; 22 } ··· 26 version: string; 27 }; 28 29 - export function getConfigPath(): string { 30 - const dir = getMoonlightDir(); 31 - const fs = requireImport("fs"); 32 - const path = requireImport("path"); 33 34 - const buildInfoPath = path.join(process.resourcesPath, "build_info.json"); 35 - const buildInfo: BuildInfo = JSON.parse( 36 - fs.readFileSync(buildInfoPath, "utf8") 37 - ); 38 39 - const configPath = path.join(dir, buildInfo.releaseChannel + ".json"); 40 return configPath; 41 } 42 43 - function getPathFromMoonlight(...names: string[]): string { 44 - const dir = getMoonlightDir(); 45 - const fs = requireImport("fs"); 46 - const path = requireImport("path"); 47 48 - const target = path.join(dir, ...names); 49 - if (!fs.existsSync(target)) fs.mkdirSync(target); 50 51 return target; 52 } 53 54 - export function getExtensionsPath(): string { 55 - return getPathFromMoonlight(constants.extensionsDir); 56 } 57 58 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 - } 65 }
··· 1 import { constants } from "@moonlight-mod/types"; 2 3 + export async function getMoonlightDir() { 4 + browser: { 5 + return "/"; 6 + } 7 + 8 + const electron = require("electron"); 9 10 let appData = ""; 11 injector: { 12 + appData = electron.app.getPath("appData"); 13 } 14 15 nodePreload: { 16 + appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData); 17 } 18 19 + const dir = moonlightNodeSandboxed.fs.join(appData, "moonlight-mod"); 20 + if (!(await moonlightNodeSandboxed.fs.exists(dir))) await moonlightNodeSandboxed.fs.mkdir(dir); 21 22 return dir; 23 } ··· 27 version: string; 28 }; 29 30 + export async function getConfigPath() { 31 + browser: { 32 + return "/config.json"; 33 + } 34 35 + const dir = await getMoonlightDir(); 36 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 + 47 return configPath; 48 } 49 50 + async function getPathFromMoonlight(...names: string[]) { 51 + const dir = await getMoonlightDir(); 52 53 + const target = moonlightNodeSandboxed.fs.join(dir, ...names); 54 + if (!(await moonlightNodeSandboxed.fs.exists(target))) await moonlightNodeSandboxed.fs.mkdir(target); 55 56 return target; 57 } 58 59 + export async function getExtensionsPath() { 60 + return await getPathFromMoonlight(constants.extensionsDir); 61 } 62 63 export function getCoreExtensionsPath(): string { 64 + return moonlightNodeSandboxed.fs.join(__dirname, constants.coreExtensionsDir); 65 }
+104 -30
packages/core/src/util/dependency.ts
··· 1 import Logger from "./logger"; 2 3 export type Dependency<T, D> = { 4 id: T; 5 data: D; 6 }; 7 8 - const logger = new Logger("core/util/dependency"); 9 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 let items = [...origItems]; 18 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 for (const item of items) { 36 const fullDeps: Set<T> = new Set(); 37 let failed = false; ··· 83 items = items.filter((item) => !failed.includes(item)); 84 } 85 86 logger.trace("Sorting stage", items); 87 const sorted: Dependency<T, D>[] = []; 88 ··· 92 dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id))); 93 } 94 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 - ); 101 102 if (noDependents.length === 0) { 103 logger.warn("Stuck dependency graph detected", dependencyGraph);
··· 1 import Logger from "./logger"; 2 3 + const logger = new Logger("core/util/dependency"); 4 + 5 export type Dependency<T, D> = { 6 id: T; 7 data: D; 8 }; 9 + type Dependencies<T, D> = Dependency<T, D>[]; 10 + type DependencyGraph<T> = Map<T, Set<T> | null>; 11 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; 16 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>] { 31 let items = [...origItems]; 32 + const dependencyGraph: DependencyGraph<T> = new Map(); 33 34 for (const item of items) { 35 const fullDeps: Set<T> = new Set(); 36 let failed = false; ··· 82 items = items.filter((item) => !failed.includes(item)); 83 } 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 + 164 logger.trace("Sorting stage", items); 165 const sorted: Dependency<T, D>[] = []; 166 ··· 170 dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id))); 171 } 172 173 + while (Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0) { 174 + const noDependents = items.filter((e) => dependencyGraph.get(e.id)?.size === 0); 175 176 if (noDependents.length === 0) { 177 logger.warn("Stuck dependency graph detected", dependencyGraph);
+48 -54
packages/core/src/util/event.ts
··· 1 - export type MoonlightEventCallback = (data: string) => void; 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 - } 8 9 - function nodeMethod(): MoonlightEventEmitter { 10 - const EventEmitter = require("events"); 11 - const eventEmitter = new EventEmitter(); 12 - const listeners = new Map<MoonlightEventCallback, (...args: any[]) => void>(); 13 14 - return { 15 - dispatchEvent: (id: string, data: string) => { 16 - eventEmitter.emit(id, data); 17 - }, 18 19 - addEventListener: (id: string, cb: (data: string) => void) => { 20 - if (listeners.has(cb)) return; 21 22 - function listener(data: string) { 23 - cb(data); 24 - } 25 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 - } 38 39 - export function createEventEmitter(): MoonlightEventEmitter { 40 - webPreload: { 41 - const eventEmitter = new EventTarget(); 42 - const listeners = new Map<MoonlightEventCallback, (e: Event) => void>(); 43 44 return { 45 - dispatchEvent: (id: string, data: string) => { 46 - eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data })); 47 }, 48 49 - addEventListener: (id: string, cb: (data: string) => void) => { 50 - if (listeners.has(cb)) return; 51 52 function listener(e: Event) { 53 const event = e as CustomEvent<string>; 54 - cb(event.detail); 55 } 56 57 - listeners.set(cb, listener); 58 - eventEmitter.addEventListener(id, listener); 59 }, 60 61 - removeEventListener: (id: string, cb: (data: string) => void) => { 62 - const listener = listeners.get(cb); 63 if (listener == null) return; 64 - listeners.delete(cb); 65 - eventEmitter.removeEventListener(id, listener); 66 } 67 }; 68 - } 69 - 70 - nodePreload: { 71 - return nodeMethod(); 72 - } 73 - 74 - injector: { 75 - return nodeMethod(); 76 } 77 78 throw new Error("Called createEventEmitter() in an impossible environment");
··· 1 + import { MoonlightEventEmitter } from "@moonlight-mod/types/core/event"; 2 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>(); 10 11 + return { 12 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => { 13 + eventEmitter.dispatchEvent(new CustomEvent(id as string, { detail: data })); 14 + }, 15 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; 19 20 + function listener(e: Event) { 21 + const event = e as CustomEvent<string>; 22 + cb(event.detail as EventData[Id]); 23 + } 24 25 + listeners.set(untyped, listener); 26 + eventEmitter.addEventListener(id as string, listener); 27 + }, 28 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 39 + nodeTarget: { 40 + const EventEmitter = require("events"); 41 + const eventEmitter = new EventEmitter(); 42 + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); 43 44 return { 45 + dispatchEvent: <Id extends keyof EventData>(id: Id, data: EventData[Id]) => { 46 + eventEmitter.emit(id as string, data); 47 }, 48 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; 52 53 function listener(e: Event) { 54 const event = e as CustomEvent<string>; 55 + cb(event as EventData[Id]); 56 } 57 58 + listeners.set(untyped, listener); 59 + eventEmitter.on(id as string, listener); 60 }, 61 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); 65 if (listener == null) return; 66 + listeners.delete(untyped); 67 + eventEmitter.off(id as string, listener); 68 } 69 }; 70 } 71 72 throw new Error("Called createEventEmitter() in an impossible environment");
+3 -6
packages/core/src/util/import.ts
··· 9 cemented if import is passed a string literal. 10 */ 11 12 - const canRequire = ["path", "fs", "glob"] as const; 13 - type CanRequire = (typeof canRequire)[number]; 14 15 type ImportTypes = { 16 path: typeof import("path"); 17 fs: typeof import("fs"); 18 - glob: typeof import("glob"); 19 }; 20 21 - export default function requireImport<T extends CanRequire>( 22 - type: T 23 - ): Awaited<ImportTypes[T]> { 24 return require(type); 25 }
··· 9 cemented if import is passed a string literal. 10 */ 11 12 + const _canRequire = ["path", "fs"] as const; 13 + type CanRequire = (typeof _canRequire)[number]; 14 15 type ImportTypes = { 16 path: typeof import("path"); 17 fs: typeof import("fs"); 18 }; 19 20 + export default function requireImport<T extends CanRequire>(type: T): Awaited<ImportTypes[T]> { 21 return require(type); 22 }
+13 -16
packages/core/src/util/logger.ts
··· 1 import { LogLevel } from "@moonlight-mod/types/logger"; 2 - import { readConfig } from "../config"; 3 4 const colors = { 5 [LogLevel.SILLY]: "#EDD3E9", ··· 10 [LogLevel.ERROR]: "#FF0000" 11 }; 12 13 - const config = readConfig(); 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 23 export default class Logger { 24 private name: string; ··· 56 const logLevel = LogLevel[level].toUpperCase(); 57 if (maxLevel > level) return; 58 59 - if (MOONLIGHT_WEB_PRELOAD) { 60 - args = [ 61 - `%c[${logLevel}]`, 62 - `background-color: ${colors[level]}; color: #FFFFFF;`, 63 - `[${this.name}]`, 64 - ...obj 65 - ]; 66 } else { 67 args = [`[${logLevel}]`, `[${this.name}]`, ...obj]; 68 } ··· 91 } 92 } 93 }
··· 1 + /* eslint-disable no-console */ 2 import { LogLevel } from "@moonlight-mod/types/logger"; 3 + import { Config } from "@moonlight-mod/types"; 4 5 const colors = { 6 [LogLevel.SILLY]: "#EDD3E9", ··· 11 [LogLevel.ERROR]: "#FF0000" 12 }; 13 14 let maxLevel = LogLevel.INFO; 15 16 export default class Logger { 17 private name: string; ··· 49 const logLevel = LogLevel[level].toUpperCase(); 50 if (maxLevel > level) return; 51 52 + if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) { 53 + args = [`%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, `[${this.name}]`, ...obj]; 54 } else { 55 args = [`[${logLevel}]`, `[${this.name}]`, ...obj]; 56 } ··· 79 } 80 } 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
···
··· 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
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM"] 5 + } 6 }
+11 -2
packages/core-extensions/package.json
··· 1 { 2 "name": "@moonlight-mod/core-extensions", 3 "private": true, 4 "dependencies": { 5 - "@electron/asar": "^3.2.5", 6 - "@moonlight-mod/types": "workspace:*" 7 } 8 }
··· 1 { 2 "name": "@moonlight-mod/core-extensions", 3 "private": true, 4 + "engineStrict": true, 5 + "engines": { 6 + "node": ">=22", 7 + "pnpm": ">=10", 8 + "npm": "pnpm", 9 + "yarn": "pnpm" 10 + }, 11 "dependencies": { 12 + "@moonlight-mod/core": "workspace:*", 13 + "@moonlight-mod/types": "workspace:*", 14 + "microdiff": "catalog:prod", 15 + "nanotar": "catalog:prod" 16 } 17 }
+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
···
··· 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
···
··· 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
···
··· 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
···
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "commands", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "Commands", 7 + "tagline": "A library to add commands", 8 + "authors": ["Cynosphere", "NotNite"], 9 + "tags": ["library"] 10 + } 11 + }
+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
··· 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
··· 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
··· 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
··· 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
··· 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 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 export const webpackModules: ExtensionWebExports["webpackModules"] = { 11 - react, 12 - flux, 13 - stores, 14 - http, 15 - components, 16 - fluxDispatcher 17 };
··· 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 2 3 export const webpackModules: ExtensionWebExports["webpackModules"] = { 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 + } 13 };
+3 -1
packages/core-extensions/src/common/manifest.json
··· 1 { 2 "id": "common", 3 "meta": { 4 "name": "Common", 5 - "tagline": "A *lot* of common clientmodding utilities from the Discord client", 6 "authors": ["Cynosphere", "NotNite"], 7 "tags": ["library"] 8 },
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "common", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Common", 7 + "tagline": "Common client modding utilities for the Discord client", 8 "authors": ["Cynosphere", "NotNite"], 9 "tags": ["library"] 10 },
-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
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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 + );
+84
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: 31 + /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\(0,\i\.jsx\)\(\i,{user:\i}\),.+?onClickPremiumGuildIcon:\i}\)])/, 32 + replacement: (_, decorators) => 33 + `children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])` 34 + }, 35 + { 36 + match: /name:null==\i\?\(0,\i\.jsx\)\("span"/, 37 + replacement: (orig: string) => 38 + `children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}` 39 + } 40 + ] 41 + }, 42 + 43 + // messages 44 + { 45 + find: '},"new-member")),', 46 + replace: [ 47 + { 48 + match: /(?<=\.BADGES](=|:))(\i)(;|})/, 49 + replacement: (_, leading, badges, trailing) => 50 + `require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}` 51 + }, 52 + { 53 + match: /(?<=className:\i,)badges:(\i)/, 54 + replacement: (_, badges) => 55 + `badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])` 56 + }, 57 + { 58 + match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/, 59 + replacement: (_, elements) => 60 + `children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:` 61 + } 62 + ] 63 + }, 64 + { 65 + find: '.provider&&"Discord"===', 66 + replace: { 67 + match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/, 68 + replacement: (_, elements) => 69 + `children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})` 70 + } 71 + } 72 + ]; 73 + 74 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 75 + dmList: { 76 + dependencies: [{ id: "react" }] 77 + }, 78 + memberList: { 79 + dependencies: [{ id: "react" }] 80 + }, 81 + messages: { 82 + dependencies: [{ id: "react" }] 83 + } 84 + };
+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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
··· 1 import { join } from "node:path"; 2 import { Module } from "node:module"; 3 - import { BrowserWindow } from "electron"; 4 5 const logger = moonlightHost.getLogger("disableSentry"); 6 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); 24 } 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 - });
··· 1 import { join } from "node:path"; 2 import { Module } from "node:module"; 3 4 const logger = moonlightHost.getLogger("disableSentry"); 5 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 + } 21 }
+9 -54
packages/core-extensions/src/disableSentry/index.ts
··· 3 4 export const patches: Patch[] = [ 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", 15 replace: { 16 type: PatchReplaceType.Normal, 17 - match: /default:function\(\){return .}/, 18 - replacement: 19 - 'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}' 20 } 21 }, 22 { 23 - find: "initSentry:function", 24 replace: { 25 type: PatchReplaceType.Normal, 26 - match: /initSentry:function\(\){return .}/, 27 - replacement: "default:function(){return ()=>{}}" 28 } 29 }, 30 { 31 find: "window.DiscordErrors=", 32 replace: { 33 type: PatchReplaceType.Normal, 34 - match: /uses_client_mods:\(0,.\.usesClientMods\)\(\)/, 35 - replacement: "uses_client_mods:false" 36 } 37 } 38 ]; 39 40 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 - } 78 };
··· 3 4 export const patches: Patch[] = [ 5 { 6 + find: "profiledRootComponent:", 7 replace: { 8 type: PatchReplaceType.Normal, 9 + match: /Z:\(\)=>\i/, 10 + replacement: 'Z:()=>require("disableSentry_stub").proxy()' 11 } 12 }, 13 { 14 + find: "this._sentryUtils=", 15 replace: { 16 type: PatchReplaceType.Normal, 17 + match: /(?<=this._sentryUtils=)./, 18 + replacement: "undefined" 19 } 20 }, 21 { 22 find: "window.DiscordErrors=", 23 replace: { 24 type: PatchReplaceType.Normal, 25 + match: /(?<=uses_client_mods:)./, 26 + replacement: "false" 27 } 28 } 29 ]; 30 31 export const webpackModules: ExtensionWebExports["webpackModules"] = { 32 + stub: {} 33 };
+12 -1
packages/core-extensions/src/disableSentry/manifest.json
··· 1 { 2 "id": "disableSentry", 3 "meta": { 4 "name": "Disable Sentry", 5 "tagline": "Turns off Discord's error reporting systems", 6 "authors": ["Cynosphere", "NotNite"], 7 "tags": ["privacy"] 8 - } 9 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "disableSentry", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Disable Sentry", 7 "tagline": "Turns off Discord's error reporting systems", 8 "authors": ["Cynosphere", "NotNite"], 9 "tags": ["privacy"] 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 + ] 20 }
+4 -8
packages/core-extensions/src/disableSentry/node.ts
··· 7 8 const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 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 - ); 17 require.cache[sentryPath]!.exports = { 18 init: () => {}, 19 setTag: () => {}, 20 - setUser: () => {} 21 }; 22 logger.debug("Stubbed Sentry node side!"); 23 } catch (err) {
··· 7 8 const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 9 try { 10 + const sentryPath = require.resolve(resolve(preloadPath, "..", "node_modules", "@sentry", "electron")); 11 + require.cache[sentryPath] = new Module(sentryPath, require.cache[require.resolve(preloadPath)]); 12 require.cache[sentryPath]!.exports = { 13 init: () => {}, 14 setTag: () => {}, 15 + setUser: () => {}, 16 + captureMessage: () => {} 17 }; 18 logger.debug("Stubbed Sentry node side!"); 19 } catch (err) {
+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
··· 2 3 export const patches: Patch[] = [ 4 { 5 - find: /\.displayName="(Developer)?ExperimentStore"/, 6 replace: { 7 - match: "window.GLOBAL_ENV.RELEASE_CHANNEL", 8 - replacement: '"staging"' 9 } 10 }, 11 { 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 - ] 27 } 28 ];
··· 2 3 export const patches: Patch[] = [ 4 { 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: /&&\((\i)\?\(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",', 39 replace: { 40 + match: /isDiscordDeveloper:(\i)}\),/, 41 + replacement: (_, isStaff) => 42 + `isDiscordDeveloper:(moonlight.getConfigOption("experiments","devtools")??false)||${isStaff}}),` 43 } 44 }, 45 + 46 + // Enable further staff-locked options 47 { 48 + find: "shouldShowLurkerModeUpsellPopout:", 49 + replace: { 50 + match: /\.useReducedMotion,isStaff:(\i)(,|})/, 51 + replacement: (_, isStaff, trail) => 52 + `.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff}${trail}` 53 + } 54 } 55 ];
+17 -1
packages/core-extensions/src/experiments/manifest.json
··· 1 { 2 "id": "experiments", 3 "meta": { 4 "name": "Experiments", 5 "tagline": "Allows you to configure Discord's internal A/B testing features", 6 - "authors": ["NotNite"], 7 "tags": ["dangerZone"] 8 } 9 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "experiments", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Experiments", 7 "tagline": "Allows you to configure Discord's internal A/B testing features", 8 + "authors": ["NotNite", "Cynosphere"], 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 + } 24 } 25 }
+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
···
··· 1 + { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 + "id": "markdown", 4 + "apiLevel": 2, 5 + "meta": { 6 + "name": "Markdown", 7 + "tagline": "A library for adding new markdown rules", 8 + "authors": ["Cynosphere"], 9 + "tags": ["library"] 10 + } 11 + }
+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
···
··· 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
··· 1 - import { ExtensionWebExports } from "@moonlight-mod/types"; 2 - import ui from "./ui"; 3 - import { stores } from "./stores"; 4 - import { DownloadIconSVG, TrashIconSVG } from "./types"; 5 6 - export const webpackModules: ExtensionWebExports["webpackModules"] = { 7 stores: { 8 dependencies: [ 9 - { ext: "common", id: "flux" }, 10 - { ext: "common", id: "fluxDispatcher" } 11 - ], 12 - run: (module, exports, require) => { 13 - module.exports = stores(require); 14 - } 15 }, 16 17 - moonbase: { 18 dependencies: [ 19 { ext: "spacepack", id: "spacepack" }, 20 { ext: "settings", id: "settings" }, 21 - { ext: "common", id: "react" }, 22 - { ext: "common", id: "components" }, 23 { ext: "moonbase", id: "stores" }, 24 - DownloadIconSVG, 25 - TrashIconSVG 26 ], 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 - >; 36 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 - } 57 } 58 };
··· 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 + ]; 33 34 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 35 stores: { 36 + dependencies: [{ id: "discord/packages/flux" }, { id: "discord/Dispatcher" }] 37 + }, 38 + 39 + ui: { 40 dependencies: [ 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 + ] 53 }, 54 55 + ThemeDarkIcon: { 56 + dependencies: [{ ext: "common", id: "icons" }, { id: "react" }] 57 + }, 58 + 59 + settings: { 60 dependencies: [ 61 { ext: "spacepack", id: "spacepack" }, 62 { ext: "settings", id: "settings" }, 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" }, 74 { ext: "moonbase", id: "stores" }, 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" } 83 ], 84 + entrypoint: true 85 + }, 86 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 + ] 100 } 101 };
+44 -2
packages/core-extensions/src/moonbase/manifest.json
··· 1 { 2 "id": "moonbase", 3 "meta": { 4 "name": "Moonbase", 5 "tagline": "The official settings UI for moonlight", 6 - "authors": ["Cynosphere", "NotNite"] 7 }, 8 - "dependencies": ["spacepack", "settings", "common"] 9 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "moonbase", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Moonbase", 7 "tagline": "The official settings UI for moonlight", 8 + "authors": ["Cynosphere", "NotNite", "redstonekasi"] 9 }, 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 + ] 51 }
+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
··· 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
··· 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
···
··· 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
··· 1 - import { DetectedExtension, ExtensionManifest } from "types/src"; 2 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"; 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>; 17 deleteExtension(id: string): Promise<void>; 18 - getExtensionConfig(id: string, key: string): any; 19 }; 20 21 export type RepositoryManifest = ExtensionManifest & { ··· 30 31 export type MoonbaseExtension = { 32 id: string; 33 manifest: ExtensionManifest | RepositoryManifest; 34 source: DetectedExtension["source"]; 35 state: ExtensionState; 36 };
··· 1 + import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 2 + import { DetectedExtension, ExtensionManifest, MoonlightBranch } from "@moonlight-mod/types"; 3 4 + export type MoonbaseNatives = { 5 + checkForMoonlightUpdate(): Promise<string | null>; 6 + updateMoonlight(overrideBranch?: MoonlightBranch): Promise<void>; 7 8 + fetchRepositories(repos: string[]): Promise<Record<string, RepositoryManifest[]>>; 9 + installExtension(manifest: RepositoryManifest, url: string, repo: string): Promise<void>; 10 deleteExtension(id: string): Promise<void>; 11 }; 12 13 export type RepositoryManifest = ExtensionManifest & { ··· 22 23 export type MoonbaseExtension = { 24 id: string; 25 + uniqueId: number; 26 manifest: ExtensionManifest | RepositoryManifest; 27 source: DetectedExtension["source"]; 28 state: ExtensionState; 29 + compat: ExtensionCompat; 30 + hasUpdate: boolean; 31 + changelog?: string; 32 + settingsOverride?: ExtensionManifest["settings"]; 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
··· 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
··· 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
··· 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
···
··· 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
···
··· 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 + async 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 + await 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
···
··· 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
···
··· 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={async () => { 23 + await 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 + }
+561
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 { NodeEventType } from "@moonlight-mod/types/core/event"; 17 + import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config"; 18 + import diff from "microdiff"; 19 + 20 + const logger = moonlight.getLogger("moonbase"); 21 + 22 + let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); 23 + if (moonlightNode.isBrowser) natives = getNatives(); 24 + 25 + class MoonbaseSettingsStore extends Store<any> { 26 + private initialConfig: Config; 27 + private savedConfig: Config; 28 + private config: Config; 29 + private extensionIndex: number; 30 + private configComponents: Record<string, Record<string, CustomComponent>> = {}; 31 + 32 + modified: boolean; 33 + submitting: boolean; 34 + installing: boolean; 35 + 36 + #updateState = UpdateState.Ready; 37 + get updateState() { 38 + return this.#updateState; 39 + } 40 + newVersion: string | null; 41 + shouldShowNotice: boolean; 42 + 43 + restartAdvice = RestartAdvice.NotNeeded; 44 + 45 + extensions: { [id: number]: MoonbaseExtension }; 46 + updates: { 47 + [id: number]: { 48 + version: string; 49 + download: string; 50 + updateManifest: RepositoryManifest; 51 + }; 52 + }; 53 + 54 + constructor() { 55 + super(Dispatcher); 56 + 57 + this.initialConfig = moonlightNode.config; 58 + this.savedConfig = moonlightNode.config; 59 + this.config = this.clone(this.savedConfig); 60 + this.extensionIndex = 0; 61 + 62 + this.modified = false; 63 + this.submitting = false; 64 + this.installing = false; 65 + 66 + this.newVersion = null; 67 + this.shouldShowNotice = false; 68 + 69 + this.extensions = {}; 70 + this.updates = {}; 71 + for (const ext of moonlightNode.extensions) { 72 + const uniqueId = this.extensionIndex++; 73 + this.extensions[uniqueId] = { 74 + ...ext, 75 + uniqueId, 76 + state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled, 77 + compat: checkExtensionCompat(ext.manifest), 78 + hasUpdate: false 79 + }; 80 + } 81 + 82 + // This is async but we're calling it without 83 + this.checkUpdates(); 84 + 85 + // Update our state if another extension edited the config programatically 86 + moonlightNode.events.addEventListener(NodeEventType.ConfigSaved, (config) => { 87 + if (!this.submitting) { 88 + this.config = this.clone(config); 89 + // NOTE: This is also async but we're calling it without 90 + this.processConfigChanged(); 91 + } 92 + }); 93 + } 94 + 95 + async checkUpdates() { 96 + await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]); 97 + this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0; 98 + this.emitChange(); 99 + } 100 + 101 + private async checkExtensionUpdates() { 102 + const repositories = await natives!.fetchRepositories(this.savedConfig.repositories); 103 + 104 + // Reset update state 105 + for (const id in this.extensions) { 106 + const ext = this.extensions[id]; 107 + ext.hasUpdate = false; 108 + ext.changelog = undefined; 109 + } 110 + this.updates = {}; 111 + 112 + for (const [repo, exts] of Object.entries(repositories)) { 113 + for (const ext of exts) { 114 + const uniqueId = this.extensionIndex++; 115 + const extensionData = { 116 + id: ext.id, 117 + uniqueId, 118 + manifest: ext, 119 + source: { type: ExtensionLoadSource.Normal, url: repo }, 120 + state: ExtensionState.NotDownloaded, 121 + compat: ExtensionCompat.Compatible, 122 + hasUpdate: false 123 + }; 124 + 125 + // Don't present incompatible updates 126 + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 127 + 128 + const existing = this.getExisting(extensionData); 129 + if (existing != null) { 130 + // Make sure the download URL is properly updated 131 + existing.manifest = { 132 + ...existing.manifest, 133 + download: ext.download 134 + }; 135 + 136 + if (this.hasUpdate(extensionData)) { 137 + this.updates[existing.uniqueId] = { 138 + version: ext.version!, 139 + download: ext.download, 140 + updateManifest: ext 141 + }; 142 + existing.hasUpdate = true; 143 + existing.changelog = ext.meta?.changelog; 144 + } 145 + } else { 146 + this.extensions[uniqueId] = extensionData; 147 + } 148 + } 149 + } 150 + } 151 + 152 + private async checkMoonlightUpdates() { 153 + this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true) 154 + ? await natives!.checkForMoonlightUpdate() 155 + : null; 156 + } 157 + 158 + private getExisting(ext: MoonbaseExtension) { 159 + return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 160 + } 161 + 162 + private hasUpdate(ext: MoonbaseExtension) { 163 + const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 164 + if (existing == null) return false; 165 + 166 + return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded; 167 + } 168 + 169 + // Jank 170 + private isModified() { 171 + const orig = JSON.stringify(this.savedConfig); 172 + const curr = JSON.stringify(this.config); 173 + return orig !== curr; 174 + } 175 + 176 + get busy() { 177 + return this.submitting || this.installing; 178 + } 179 + 180 + // Required for the settings store contract 181 + showNotice() { 182 + return this.modified; 183 + } 184 + 185 + getExtension(uniqueId: number) { 186 + return this.extensions[uniqueId]; 187 + } 188 + 189 + getExtensionUniqueId(id: string) { 190 + return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId; 191 + } 192 + 193 + getExtensionConflicting(uniqueId: number) { 194 + const ext = this.getExtension(uniqueId); 195 + if (ext.state !== ExtensionState.NotDownloaded) return false; 196 + return Object.values(this.extensions).some( 197 + (e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded 198 + ); 199 + } 200 + 201 + getExtensionName(uniqueId: number) { 202 + const ext = this.getExtension(uniqueId); 203 + return ext.manifest.meta?.name ?? ext.id; 204 + } 205 + 206 + getExtensionUpdate(uniqueId: number) { 207 + return this.updates[uniqueId]?.version; 208 + } 209 + 210 + getExtensionEnabled(uniqueId: number) { 211 + const ext = this.getExtension(uniqueId); 212 + if (ext.state === ExtensionState.NotDownloaded) return false; 213 + const val = this.config.extensions[ext.id]; 214 + if (val == null) return false; 215 + return typeof val === "boolean" ? val : val.enabled; 216 + } 217 + 218 + getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 219 + const ext = this.getExtension(uniqueId); 220 + const settings = ext.settingsOverride ?? ext.manifest.settings; 221 + return getConfigOption(ext.id, key, this.config, settings); 222 + } 223 + 224 + getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined { 225 + const cfg = this.config.extensions[id]; 226 + if (cfg == null || typeof cfg === "boolean") return defaultValue; 227 + return cfg.config?.[key] ?? defaultValue; 228 + } 229 + 230 + getExtensionConfigName(uniqueId: number, key: string) { 231 + const ext = this.getExtension(uniqueId); 232 + const settings = ext.settingsOverride ?? ext.manifest.settings; 233 + return settings?.[key]?.displayName ?? key; 234 + } 235 + 236 + getExtensionConfigDescription(uniqueId: number, key: string) { 237 + const ext = this.getExtension(uniqueId); 238 + const settings = ext.settingsOverride ?? ext.manifest.settings; 239 + return settings?.[key]?.description; 240 + } 241 + 242 + setExtensionConfig(id: string, key: string, value: any) { 243 + setConfigOption(this.config, id, key, value); 244 + this.modified = this.isModified(); 245 + this.emitChange(); 246 + } 247 + 248 + setExtensionEnabled(uniqueId: number, enabled: boolean) { 249 + const ext = this.getExtension(uniqueId); 250 + let val = this.config.extensions[ext.id]; 251 + 252 + if (val == null) { 253 + this.config.extensions[ext.id] = enabled; 254 + this.modified = this.isModified(); 255 + this.emitChange(); 256 + return; 257 + } 258 + 259 + if (typeof val === "boolean") { 260 + val = enabled; 261 + } else { 262 + val.enabled = enabled; 263 + } 264 + 265 + this.config.extensions[ext.id] = val; 266 + this.modified = this.isModified(); 267 + this.emitChange(); 268 + } 269 + 270 + dismissAllExtensionUpdates() { 271 + for (const id in this.extensions) { 272 + this.extensions[id].hasUpdate = false; 273 + } 274 + this.emitChange(); 275 + } 276 + 277 + async updateAllExtensions() { 278 + for (const id of Object.keys(this.updates)) { 279 + try { 280 + await this.installExtension(parseInt(id)); 281 + } catch (e) { 282 + logger.error("Error bulk updating extension", id, e); 283 + } 284 + } 285 + } 286 + 287 + async installExtension(uniqueId: number) { 288 + const ext = this.getExtension(uniqueId); 289 + if (!("download" in ext.manifest)) { 290 + throw new Error("Extension has no download URL"); 291 + } 292 + 293 + this.installing = true; 294 + try { 295 + const update = this.updates[uniqueId]; 296 + const url = update?.download ?? ext.manifest.download; 297 + await natives!.installExtension(ext.manifest, url, ext.source.url!); 298 + if (ext.state === ExtensionState.NotDownloaded) { 299 + this.extensions[uniqueId].state = ExtensionState.Disabled; 300 + } 301 + 302 + if (update != null) { 303 + const existing = this.extensions[uniqueId]; 304 + existing.settingsOverride = update.updateManifest.settings; 305 + existing.compat = checkExtensionCompat(update.updateManifest); 306 + existing.manifest = update.updateManifest; 307 + existing.changelog = update.updateManifest.meta?.changelog; 308 + } 309 + 310 + delete this.updates[uniqueId]; 311 + } catch (e) { 312 + logger.error("Error installing extension:", e); 313 + } 314 + 315 + this.installing = false; 316 + this.restartAdvice = this.#computeRestartAdvice(); 317 + this.emitChange(); 318 + } 319 + 320 + private getRank(ext: MoonbaseExtension) { 321 + if (ext.source.type === ExtensionLoadSource.Developer) return 3; 322 + if (ext.source.type === ExtensionLoadSource.Core) return 2; 323 + if (ext.source.url === mainRepo) return 1; 324 + return 0; 325 + } 326 + 327 + async getDependencies(uniqueId: number) { 328 + const ext = this.getExtension(uniqueId); 329 + 330 + const missingDeps = []; 331 + for (const dep of ext.manifest.dependencies ?? []) { 332 + const anyInstalled = Object.values(this.extensions).some( 333 + (e) => e.id === dep && e.state !== ExtensionState.NotDownloaded 334 + ); 335 + if (!anyInstalled) missingDeps.push(dep); 336 + } 337 + 338 + if (missingDeps.length === 0) return null; 339 + 340 + const deps: Record<string, MoonbaseExtension[]> = {}; 341 + for (const dep of missingDeps) { 342 + const candidates = Object.values(this.extensions).filter((e) => e.id === dep); 343 + 344 + deps[dep] = candidates.sort((a, b) => { 345 + const aRank = this.getRank(a); 346 + const bRank = this.getRank(b); 347 + if (aRank === bRank) { 348 + const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!); 349 + const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!); 350 + return repoIndex - otherRepoIndex; 351 + } else { 352 + return bRank - aRank; 353 + } 354 + }); 355 + } 356 + 357 + return deps; 358 + } 359 + 360 + async deleteExtension(uniqueId: number) { 361 + const ext = this.getExtension(uniqueId); 362 + if (ext == null) return; 363 + 364 + this.installing = true; 365 + try { 366 + await natives!.deleteExtension(ext.id); 367 + this.extensions[uniqueId].state = ExtensionState.NotDownloaded; 368 + } catch (e) { 369 + logger.error("Error deleting extension:", e); 370 + } 371 + 372 + this.installing = false; 373 + this.restartAdvice = this.#computeRestartAdvice(); 374 + this.emitChange(); 375 + } 376 + 377 + async updateMoonlight() { 378 + this.#updateState = UpdateState.Working; 379 + this.emitChange(); 380 + 381 + await natives 382 + .updateMoonlight() 383 + .then(() => (this.#updateState = UpdateState.Installed)) 384 + .catch((e) => { 385 + logger.error(e); 386 + this.#updateState = UpdateState.Failed; 387 + }); 388 + 389 + this.emitChange(); 390 + } 391 + 392 + getConfigOption<K extends keyof Config>(key: K): Config[K] { 393 + return this.config[key]; 394 + } 395 + 396 + setConfigOption<K extends keyof Config>(key: K, value: Config[K]) { 397 + this.config[key] = value; 398 + this.modified = this.isModified(); 399 + this.emitChange(); 400 + } 401 + 402 + tryGetExtensionName(id: string) { 403 + const uniqueId = this.getExtensionUniqueId(id); 404 + return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; 405 + } 406 + 407 + registerConfigComponent(ext: string, name: string, component: CustomComponent) { 408 + if (!(ext in this.configComponents)) this.configComponents[ext] = {}; 409 + this.configComponents[ext][name] = component; 410 + } 411 + 412 + getExtensionConfigComponent(ext: string, name: string) { 413 + return this.configComponents[ext]?.[name]; 414 + } 415 + 416 + #computeRestartAdvice() { 417 + // If moonlight update needs a restart, always hide advice. 418 + if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded; 419 + 420 + const i = this.initialConfig; // Initial config, from startup 421 + const n = this.config; // New config about to be saved 422 + 423 + let returnedAdvice = RestartAdvice.NotNeeded; 424 + const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice); 425 + 426 + // Top-level keys, repositories is not needed here because Moonbase handles it. 427 + if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded); 428 + if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded); 429 + if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0) 430 + return updateAdvice(RestartAdvice.RestartNeeded); 431 + 432 + // Extension specific logic 433 + for (const id in n.extensions) { 434 + // Installed extension (might not be detected yet) 435 + const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded); 436 + // Installed and detected extension 437 + const detected = moonlightNode.extensions.find((e) => e.id === id); 438 + 439 + // If it's not installed at all, we don't care 440 + if (!ext) continue; 441 + 442 + const initState = i.extensions[id]; 443 + const newState = n.extensions[id]; 444 + 445 + const newEnabled = typeof newState === "boolean" ? newState : newState.enabled; 446 + // If it's enabled but not detected yet, restart. 447 + if (newEnabled && !detected) { 448 + return updateAdvice(RestartAdvice.RestartNeeded); 449 + } 450 + 451 + // Toggling extensions specifically wants to rely on the initial state, 452 + // that's what was considered when loading extensions. 453 + const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled); 454 + if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) { 455 + // If we have the extension locally, we confidently know if it has host/preload scripts. 456 + // If not, we have to respect the environment specified in the manifest. 457 + // If that is the default, we can't know what's needed. 458 + 459 + if (detected?.scripts.hostPath || detected?.scripts.nodePath) { 460 + return updateAdvice(RestartAdvice.RestartNeeded); 461 + } 462 + 463 + switch (ext.manifest.environment) { 464 + case ExtensionEnvironment.Both: 465 + case ExtensionEnvironment.Web: 466 + updateAdvice(RestartAdvice.ReloadNeeded); 467 + continue; 468 + case ExtensionEnvironment.Desktop: 469 + return updateAdvice(RestartAdvice.RestartNeeded); 470 + default: 471 + updateAdvice(RestartAdvice.ReloadNeeded); 472 + continue; 473 + } 474 + } 475 + 476 + const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config }; 477 + const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config }; 478 + 479 + const def = ext.manifest.settings; 480 + if (!def) continue; 481 + 482 + for (const key in def) { 483 + const defaultValue = def[key].default; 484 + 485 + initConfig[key] ??= defaultValue; 486 + newConfig[key] ??= defaultValue; 487 + } 488 + 489 + const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]); 490 + for (const key in def) { 491 + if (!changedKeys.includes(key)) continue; 492 + 493 + const advice = def[key].advice; 494 + switch (advice) { 495 + case ExtensionSettingsAdvice.None: 496 + updateAdvice(RestartAdvice.NotNeeded); 497 + continue; 498 + case ExtensionSettingsAdvice.Reload: 499 + updateAdvice(RestartAdvice.ReloadNeeded); 500 + continue; 501 + case ExtensionSettingsAdvice.Restart: 502 + updateAdvice(RestartAdvice.RestartNeeded); 503 + continue; 504 + default: 505 + updateAdvice(RestartAdvice.ReloadSuggested); 506 + } 507 + } 508 + } 509 + 510 + return returnedAdvice; 511 + } 512 + 513 + async writeConfig() { 514 + try { 515 + this.submitting = true; 516 + this.emitChange(); 517 + 518 + await moonlightNode.writeConfig(this.config); 519 + await this.processConfigChanged(); 520 + } finally { 521 + this.submitting = false; 522 + this.emitChange(); 523 + } 524 + } 525 + 526 + private async processConfigChanged() { 527 + this.savedConfig = this.clone(this.config); 528 + this.restartAdvice = this.#computeRestartAdvice(); 529 + this.modified = false; 530 + 531 + const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories); 532 + if (modifiedRepos.length !== 0) await this.checkUpdates(); 533 + 534 + this.emitChange(); 535 + } 536 + 537 + reset() { 538 + this.submitting = false; 539 + this.modified = false; 540 + this.config = this.clone(this.savedConfig); 541 + this.emitChange(); 542 + } 543 + 544 + restartDiscord() { 545 + if (moonlightNode.isBrowser) { 546 + window.location.reload(); 547 + } else { 548 + // @ts-expect-error TODO: DiscordNative 549 + window.DiscordNative.app.relaunch(); 550 + } 551 + } 552 + 553 + // Required because electron likes to make it immutable sometimes. 554 + // This sucks. 555 + private clone<T>(obj: T): T { 556 + return structuredClone(obj); 557 + } 558 + } 559 + 560 + const settingsStore = new MoonbaseSettingsStore(); 561 + export { settingsStore as MoonbaseSettingsStore };
+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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
··· 1 - import { Patch } from "types/src"; 2 3 export const patches: Patch[] = [ 4 { 5 - find: "hideToken(){", 6 replace: { 7 - match: /hideToken\(\)\{.+?},/, 8 - replacement: `hideToken(){},` 9 } 10 } 11 ];
··· 1 + import { Patch } from "@moonlight-mod/types"; 2 3 export const patches: Patch[] = [ 4 { 5 + find: "hideToken:()=>", 6 replace: { 7 + match: /hideToken:\(\)=>.+?,/, 8 + replacement: `hideToken:()=>{},` 9 } 10 } 11 ];
+4 -1
packages/core-extensions/src/noHideToken/manifest.json
··· 1 { 2 "id": "noHideToken", 3 "meta": { 4 "name": "No Hide Token", 5 - "tagline": "Disables removal of token from localStorage when opening dev tools", 6 "authors": ["adryd"], 7 "tags": ["dangerZone", "development"] 8 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "noHideToken", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "No Hide Token", 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", 9 "authors": ["adryd"], 10 "tags": ["dangerZone", "development"] 11 }
-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
··· 1 - import { Patch, PatchReplaceType } from "@moonlight-mod/types"; 2 3 export const patches: Patch[] = [ 4 { 5 - find: "analyticsTrackingStoreMaker:function", 6 replace: { 7 - match: /analyticsTrackingStoreMaker:function\(\){return .}/, 8 - replacement: "analyticsTrackingStoreMaker:function(){return ()=>{}}" 9 } 10 }, 11 {
··· 1 + import { Patch } from "@moonlight-mod/types"; 2 3 export const patches: Patch[] = [ 4 { 5 + find: "analyticsTrackingStoreMaker:()=>", 6 replace: { 7 + match: /analyticsTrackingStoreMaker:\(\)=>.+?,/, 8 + replacement: "analyticsTrackingStoreMaker:()=>()=>{}," 9 } 10 }, 11 {
+9 -1
packages/core-extensions/src/noTrack/manifest.json
··· 1 { 2 "id": "noTrack", 3 "meta": { 4 "name": "No Track", 5 "tagline": "Disables /api/science and analytics", 6 "authors": ["Cynosphere", "NotNite"], 7 "tags": ["privacy"] 8 - } 9 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "noTrack", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "No Track", 7 "tagline": "Disables /api/science and analytics", 8 "authors": ["Cynosphere", "NotNite"], 9 "tags": ["privacy"] 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 + ] 17 }
+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
···
··· 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
···
··· 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
···
··· 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;
+98 -43
packages/core-extensions/src/quietLoggers/index.ts
··· 1 import { Patch } from "@moonlight-mod/types"; 2 3 const notXssDefensesOnly = () => 4 - (moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? 5 - false) === false; 6 7 // These patches MUST run before the simple patches, these are to remove loggers 8 // that end up causing syntax errors by the normal patch 9 const loggerFixes: Patch[] = [ 10 { 11 - find: '"./ggsans-800-extrabolditalic.woff2":', 12 replace: { 13 - match: /\.then\(function\(\){var.+?"MODULE_NOT_FOUND",.\}\)/, 14 - replacement: ".then(()=>(()=>{}))" 15 } 16 }, 17 { 18 find: '("GatewaySocket")', 19 replace: { 20 - match: /.\.(info|log)(\(.+?\))(;|,)/g, 21 - replacement: (_, type, body, trail) => `(()=>{})${body}${trail}` 22 } 23 } 24 ]; ··· 29 // Patches to simply remove a logger call 30 const stubPatches = [ 31 // "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 ], 53 - ['"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 - ], 68 - ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 69 - [ 70 - '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 71 - /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 72 ] 73 ]; 74 75 const simplePatches = [ 76 // 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 - ] 84 ] as { [0]: string | RegExp; [1]: string }[]; 85 86 export const patches: Patch[] = [ 87 { 88 - find: ".Messages.XSSDefenses", 89 replace: { 90 - match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 91 replacement: "(true)" 92 } 93 }, 94 ...loggerFixes, 95 ...stubPatches.map((patch) => ({ 96 find: patch[0], ··· 105 replace: { 106 match: patch[0], 107 replacement: patch[1] 108 }, 109 prerequisite: notXssDefensesOnly 110 }))
··· 1 import { Patch } from "@moonlight-mod/types"; 2 3 const notXssDefensesOnly = () => 4 + (moonlight.getConfigOption<boolean>("quietLoggers", "xssDefensesOnly") ?? false) === false; 5 + 6 + const silenceDiscordLogger = moonlight.getConfigOption<boolean>("quietLoggers", "silenceDiscordLogger") ?? false; 7 8 // These patches MUST run before the simple patches, these are to remove loggers 9 // that end up causing syntax errors by the normal patch 10 const loggerFixes: Patch[] = [ 11 { 12 + find: '"./gg-sans/ggsans-800-extrabolditalic.woff2":', 13 replace: { 14 + match: /var .=Error.+?;throw .+?,./, 15 + replacement: "" 16 } 17 }, 18 { 19 find: '("GatewaySocket")', 20 replace: { 21 + match: /\i\.(log|info)\(/g, 22 + replacement: "(()=>{})(" 23 + } 24 + }, 25 + { 26 + find: '"_connect called with already existing websocket"', 27 + replace: { 28 + match: /\i\.(log|info|verbose)\(/g, 29 + replacement: "(()=>{})(" 30 } 31 } 32 ]; ··· 37 // Patches to simply remove a logger call 38 const stubPatches = [ 39 // "sh" is not a valid locale. 40 + ["is not a valid locale", /void \i\.error\(""\.concat\(\i," is not a valid locale\."\)\)/g], 41 + ['"[BUILD INFO] Release Channel: "', /new \i\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/], 42 + ['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",\i,\i\);/], 43 + ['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'], 44 + ['"[NATIVE INFO] ', /new \i\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/], 45 + ['"Spellchecker"', /\i\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 46 + ['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",\i\),/], 47 + ["}_dispatchWithDevtools(", /\i\.totalTime>\i&&\i\.verbose\(.+?\);/], 48 + ['"NativeDispatchUtils"', /null==\i&&\i\.warn\("Tried getting Dispatch instance before instantiated"\),/], 49 [ 50 + '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 51 + /\i\.has\(\i\.type\)&&\i\.log\(.+?\.type\)\),/ 52 ], 53 + ['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",\i\),/], 54 + ['.name="MaxListenersExceededWarning",', /(?<=\.length),\i\(\i\)/], 55 [ 56 + '"The answer for life the universe and everything is:"', 57 + /\i\.info\("The answer for life the universe and everything is:",\i\),/ 58 ], 59 [ 60 + '"isLibdiscoreBlockedDomainsEnabled called but libdiscore is not loaded"', 61 + /,\i\.verbose\("isLibdiscoreBlockedDomainsEnabledThisSession: ".concat\(\i\)\)/ 62 ], 63 [ 64 + '"Unable to determine render window for element"', 65 + /console\.warn\("Unable to determine render window for element",\i\),/ 66 ], 67 [ 68 + '"Unable to determine render window for element"', 69 + /console\.warn\('Unable to find element constructor "'\.concat\(\i,'" in'\),\i\),/ 70 ], 71 [ 72 + '"[PostMessageTransport] Protocol error: event data should be an Array!"', 73 + /void console\.warn\("\[PostMessageTransport] Protocol error: event data should be an Array!"\)/ 74 ], 75 [ 76 + '("ComponentDispatchUtils")', 77 + /new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.resubscribe: Resubscribe without existing subscription",\i\),/ 78 ] 79 ]; 80 81 + const stripLoggers = [ 82 + '("OverlayRenderStore")', 83 + '("FetchBlockedDomain")', 84 + '="UserSettingsProtoLastWriteTimes",', 85 + '("MessageActionCreators")', 86 + '("Routing/Utils")', 87 + '("DatabaseManager")', 88 + '("KeyboardLayoutMapUtils")', 89 + '("ChannelMessages")', 90 + '("MessageQueue")', 91 + '("RTCLatencyTestManager")', 92 + '("OverlayStoreV3")', 93 + '("OverlayBridgeStore")', 94 + '("AuthenticationStore")', 95 + '("ConnectionStore")', 96 + '"Dispatched INITIAL_GUILD "', 97 + '"handleIdentify called"', 98 + '("Spotify")' 99 + ]; 100 + 101 const simplePatches = [ 102 // Moment.js deprecation warnings 103 + ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"] 104 ] as { [0]: string | RegExp; [1]: string }[]; 105 106 export const patches: Patch[] = [ 107 { 108 + find: ".Messages.SELF_XSS_HEADER", 109 replace: { 110 + match: /\(null!=\i&&"0\.0\.0"===\i\.remoteApp\.getVersion\(\)\)/, 111 replacement: "(true)" 112 } 113 }, 114 + { 115 + find: '("ComponentDispatchUtils")', 116 + replace: { 117 + match: 118 + /new \i\.Z\("ComponentDispatchUtils"\)\.warn\("ComponentDispatch\.subscribe: Attempting to add a duplicate listener",\i\)/, 119 + replacement: "void 0" 120 + }, 121 + prerequisite: notXssDefensesOnly 122 + }, 123 + // Highlight.js deprecation warnings 124 + { 125 + find: "Deprecated as of", 126 + replace: { 127 + match: /console\./g, 128 + replacement: "false&&console." 129 + }, 130 + prerequisite: notXssDefensesOnly 131 + }, 132 + // Discord's logger 133 + { 134 + find: "ฮฃ:", 135 + replace: { 136 + match: "for", 137 + replacement: "return;for" 138 + }, 139 + prerequisite: () => silenceDiscordLogger && notXssDefensesOnly() 140 + }, 141 ...loggerFixes, 142 ...stubPatches.map((patch) => ({ 143 find: patch[0], ··· 152 replace: { 153 match: patch[0], 154 replacement: patch[1] 155 + }, 156 + prerequisite: notXssDefensesOnly 157 + })), 158 + ...stripLoggers.map((find) => ({ 159 + find, 160 + replace: { 161 + match: /(\i|this\.logger)\.(log|warn|error|info|verbose)\(/g, 162 + replacement: "(()=>{})(" 163 }, 164 prerequisite: notXssDefensesOnly 165 }))
+10
packages/core-extensions/src/quietLoggers/manifest.json
··· 1 { 2 "id": "quietLoggers", 3 "meta": { 4 "name": "Quiet Loggers", 5 "tagline": "Quiet errors on startup, and disable unnecesary loggers", ··· 8 }, 9 "settings": { 10 "xssDefensesOnly": { 11 "displayName": "Only hide self-XSS", 12 "description": "Only disable self XSS prevention log", 13 "type": "boolean", 14 "default": false 15 }
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "quietLoggers", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Quiet Loggers", 7 "tagline": "Quiet errors on startup, and disable unnecesary loggers", ··· 10 }, 11 "settings": { 12 "xssDefensesOnly": { 13 + "advice": "reload", 14 "displayName": "Only hide self-XSS", 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)", 23 "type": "boolean", 24 "default": false 25 }
+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
···
··· 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
···
··· 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
···
··· 1 + import "./host/permissions"; 2 + import "./host/venmic";
+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
···
··· 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
··· 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"; 7 8 export const patches: Patch[] = [ 9 { 10 - find: ".UserSettingsSections.EXPERIMENTS", 11 replace: { 12 - match: /\.CUSTOM,element:(.+?)}\];return (.{1,2})/, 13 - replacement: (_, lastElement, sections) => 14 - `.CUSTOM,element:${lastElement}}];return require("settings_settings")._mutateSections(${sections})` 15 } 16 }, 17 { 18 find: 'navId:"user-settings-cog",', 19 replace: { 20 - match: /children:\[(.)\.map\(.+?\),{children:.\((.)\)/, 21 replacement: (orig, sections, section) => 22 - `${orig}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` 23 } 24 } 25 ]; 26 27 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 - } 78 };
··· 1 + import { Patch } from "@moonlight-mod/types"; 2 + import { ExtensionWebExports } from "@moonlight-mod/types"; 3 4 export const patches: Patch[] = [ 5 { 6 + find: '"useGenerateUserSettingsSections"', 7 replace: { 8 + match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/, 9 + replacement: (_, sections: string) => `require("settings_settings").Settings._mutateSections(${sections})}` 10 } 11 }, 12 { 13 find: 'navId:"user-settings-cog",', 14 replace: { 15 + match: /children:\[(\i)\.map\(.+?\),.*?children:\i\((\i)\)/, 16 replacement: (orig, sections, section) => 17 + `${orig.replace( 18 + /Object\.values\(.\..+?\)/, 19 + (orig) => `[...require("settings_settings").Settings.sectionNames,...${orig}]` 20 + )}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` 21 } 22 } 23 ]; 24 25 export const webpackModules: ExtensionWebExports["webpackModules"] = { 26 + settings: {} 27 };
+2
packages/core-extensions/src/settings/manifest.json
··· 1 { 2 "id": "settings", 3 "meta": { 4 "name": "Settings", 5 "tagline": "An API for adding to Discord's settings menu",
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "settings", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Settings", 7 "tagline": "An API for adding to Discord's settings menu",
+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
··· 1 - import { ExtensionWebExports, WebpackModuleFunc } from "@moonlight-mod/types"; 2 - import webpackModule from "./webpackModule"; 3 4 export const webpackModules: ExtensionWebExports["webpackModules"] = { 5 spacepack: { 6 - entrypoint: true, 7 - // Assert the type because we're adding extra fields to require 8 - run: webpackModule as WebpackModuleFunc 9 } 10 };
··· 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 + } 9 10 export const webpackModules: ExtensionWebExports["webpackModules"] = { 11 spacepack: { 12 + entrypoint: true 13 } 14 };
+3
packages/core-extensions/src/spacepack/manifest.json
··· 1 { 2 "id": "spacepack", 3 "meta": { 4 "name": "Spacepack", 5 "tagline": "Search utilities across all Webpack modules", ··· 8 }, 9 "settings": { 10 "addToGlobalScope": { 11 "displayName": "Add to global scope", 12 "description": "Populates window.spacepack for easier usage in DevTools", 13 "type": "boolean",
··· 1 { 2 + "$schema": "https://moonlight-mod.github.io/manifest.schema.json", 3 "id": "spacepack", 4 + "apiLevel": 2, 5 "meta": { 6 "name": "Spacepack", 7 "tagline": "Search utilities across all Webpack modules", ··· 10 }, 11 "settings": { 12 "addToGlobalScope": { 13 + "advice": "reload", 14 "displayName": "Add to global scope", 15 "description": "Populates window.spacepack for easier usage in DevTools", 16 "type": "boolean",
-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
···
··· 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
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM", "DOM.Iterable"] 5 + } 6 }
+10 -3
packages/injector/package.json
··· 1 { 2 "name": "@moonlight-mod/injector", 3 "private": true, 4 "dependencies": { 5 - "@moonlight-mod/types": "workspace:*", 6 - "@moonlight-mod/core": "workspace:*" 7 - } 8 }
··· 1 { 2 "name": "@moonlight-mod/injector", 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 + }, 14 + "engineStrict": true 15 }
+216 -55
packages/injector/src/index.ts
··· 2 BrowserWindowConstructorOptions, 3 BrowserWindow as ElectronBrowserWindow, 4 ipcMain, 5 - app, 6 - ipcRenderer 7 } from "electron"; 8 - import Module from "module"; 9 - import { constants } from "@moonlight-mod/types"; 10 - import { readConfig } from "@moonlight-mod/core/config"; 11 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"; 18 19 - let oldPreloadPath = ""; 20 let corsAllow: string[] = []; 21 22 ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 23 e.returnValue = oldPreloadPath; 24 }); 25 ipcMain.on(constants.ipcGetAppData, (e) => { 26 e.returnValue = app.getPath("appData"); 27 }); 28 ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 29 electron.dialog.showMessageBoxSync(opts); 30 }); ··· 32 corsAllow = list; 33 }); 34 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:"]; 46 47 const csp = "content-security-policy"; 48 if (headers[csp] == null) return; ··· 61 parts[directive] = values; 62 } 63 64 const stringified = Object.entries<string[]>(parts) 65 .map(([key, value]) => { 66 return `${key} ${value.join(" ")}`; ··· 71 72 class BrowserWindow extends ElectronBrowserWindow { 73 constructor(opts: BrowserWindowConstructorOptions) { 74 - oldPreloadPath = opts.webPreferences!.preload!; 75 - opts.webPreferences!.preload = require.resolve("./node-preload.js"); 76 77 super(opts); 78 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 79 if (details.responseHeaders != null) { 80 - if (details.resourceType == "mainFrame") { 81 - patchCsp(details.responseHeaders); 82 } 83 84 if (corsAllow.some((x) => details.url.startsWith(x))) { 85 - details.responseHeaders["access-control-allow-origin"] = ["*"]; 86 } 87 88 cb({ cancel: false, responseHeaders: details.responseHeaders }); 89 } 90 }); 91 } 92 } 93 94 - export async function inject(asarPath: string) { 95 try { 96 - const config = readConfig(); 97 - const extensions = getExtensions(); 98 99 // Duplicated in node-preload... oops 100 function getConfig(ext: string) { ··· 102 if (val == null || typeof val === "boolean") return undefined; 103 return val.config; 104 } 105 - 106 global.moonlightHost = { 107 asarPath, 108 - config, 109 events: new EventEmitter(), 110 - extensions, 111 - processedExtensions: { 112 - extensions: [], 113 - dependencyGraph: new Map() 114 - }, 115 116 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; 123 }, 124 - getLogger: (id: string) => { 125 return new Logger(id); 126 } 127 }; 128 129 patchElectron(); 130 131 - global.moonlightHost.processedExtensions = await loadExtensions(extensions); 132 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 133 - } catch (e) { 134 - console.error("Failed to inject", e); 135 } 136 137 - require(asarPath); 138 } 139 140 function patchElectron() { ··· 148 configurable: false 149 }); 150 } else { 151 - Object.defineProperty( 152 - electronClone, 153 - property, 154 - Object.getOwnPropertyDescriptor(electron, property)! 155 - ); 156 } 157 } 158 159 - // exports is a getter only on Windows, let's do some cursed shit instead 160 const electronPath = require.resolve("electron"); 161 const cachedElectron = require.cache[electronPath]!; 162 require.cache[electronPath] = new Module(cachedElectron.id, require.main);
··· 2 BrowserWindowConstructorOptions, 3 BrowserWindow as ElectronBrowserWindow, 4 ipcMain, 5 + app 6 } from "electron"; 7 + import Module from "node:module"; 8 + import { constants, MoonlightBranch } from "@moonlight-mod/types"; 9 + import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 10 import { getExtensions } from "@moonlight-mod/core/extension"; 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"; 19 20 + const logger = new Logger("injector"); 21 + 22 + let oldPreloadPath: string | undefined; 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>(); 29 30 ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 31 e.returnValue = oldPreloadPath; 32 }); 33 + 34 ipcMain.on(constants.ipcGetAppData, (e) => { 35 e.returnValue = app.getPath("appData"); 36 }); 37 + ipcMain.on(constants.ipcGetInjectorConfig, (e) => { 38 + e.returnValue = injectorConfig; 39 + }); 40 ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 41 electron.dialog.showMessageBoxSync(opts); 42 }); ··· 44 corsAllow = list; 45 }); 46 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:"]; 82 83 const csp = "content-security-policy"; 84 if (headers[csp] == null) return; ··· 97 parts[directive] = values; 98 } 99 100 + for (const [directive, urls] of Object.entries(extensionCspOverrides)) { 101 + parts[directive] ??= []; 102 + parts[directive].push(...urls); 103 + } 104 + 105 const stringified = Object.entries<string[]>(parts) 106 .map(([key, value]) => { 107 return `${key} ${value.join(" ")}`; ··· 112 113 class BrowserWindow extends ElectronBrowserWindow { 114 constructor(opts: BrowserWindowConstructorOptions) { 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); 124 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 + 142 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 143 if (details.responseHeaders != null) { 144 + // Patch CSP so things can use externally hosted assets 145 + if (details.resourceType === "mainFrame") { 146 + patchCsp(details.responseHeaders, extensionCspOverrides); 147 } 148 149 + // Allow plugins to bypass CORS for specific URLs 150 if (corsAllow.some((x) => details.url.startsWith(x))) { 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] = ["*"]; 158 } 159 160 + moonlightHost.events.emit("headers-received", details, isMainWindow); 161 + 162 cb({ cancel: false, responseHeaders: details.responseHeaders }); 163 } 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 + }); 204 } 205 } 206 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 + 235 try { 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(); 242 243 // Duplicated in node-preload... oops 244 function getConfig(ext: string) { ··· 246 if (val == null || typeof val === "boolean") return undefined; 247 return val.config; 248 } 249 global.moonlightHost = { 250 + get config() { 251 + return config; 252 + }, 253 + extensions, 254 + processedExtensions, 255 asarPath, 256 events: new EventEmitter(), 257 + 258 + version: MOONLIGHT_VERSION, 259 + branch: MOONLIGHT_BRANCH as MoonlightBranch, 260 261 getConfig, 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; 274 }, 275 + 276 + getLogger(id) { 277 return new Logger(id); 278 + }, 279 + getMoonlightDir() { 280 + return moonlightDir; 281 + }, 282 + getExtensionDir: (ext: string) => { 283 + return path.join(extensionsPath, ext); 284 } 285 }; 286 287 patchElectron(); 288 289 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 290 + } catch (error) { 291 + logger.error("Failed to inject:", error); 292 + } 293 + 294 + if (injectorConfig?.disablePersist !== true) { 295 + persist(asarPath); 296 } 297 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 + } 303 } 304 305 function patchElectron() { ··· 313 configurable: false 314 }); 315 } else { 316 + Object.defineProperty(electronClone, property, Object.getOwnPropertyDescriptor(electron, property)!); 317 } 318 } 319 320 + // exports is a getter only on Windows, recreate export cache instead 321 const electronPath = require.resolve("electron"); 322 const cachedElectron = require.cache[electronPath]!; 323 require.cache[electronPath] = new Module(cachedElectron.id, require.main);
+8 -1
packages/node-preload/package.json
··· 1 { 2 "name": "@moonlight-mod/node-preload", 3 "private": true, 4 "dependencies": { 5 "@moonlight-mod/core": "workspace:*", 6 "@moonlight-mod/types": "workspace:*" 7 - } 8 }
··· 1 { 2 "name": "@moonlight-mod/node-preload", 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 + }, 14 + "engineStrict": true 15 }
+142 -45
packages/node-preload/src/index.ts
··· 1 import { webFrame, ipcRenderer, contextBridge } from "electron"; 2 - import fs from "fs"; 3 - import path from "path"; 4 5 import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 6 - import { ProcessedExtensions, constants } from "@moonlight-mod/types"; 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"; 14 15 async function injectGlobals() { 16 - const config = readConfig(); 17 - const extensions = getExtensions(); 18 - const processed = await loadExtensions(extensions); 19 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 - } 25 26 global.moonlightNode = { 27 - config, 28 - extensions: getExtensions(), 29 - processedExtensions: processed, 30 nativesCache: {}, 31 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; 39 }, 40 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext], 41 getLogger: (id: string) => { 42 return new Logger(id); 43 }, 44 - 45 - getExtensionDir: (ext: string) => { 46 - const extPath = getExtensionsPath(); 47 - return path.join(extPath, ext); 48 }, 49 - writeConfig 50 }; 51 52 - await loadProcessedExtensions(processed); 53 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); 54 55 - const extCors = moonlightNode.processedExtensions.extensions 56 - .map((x) => x.manifest.cors ?? []) 57 - .flat(); 58 59 for (const repo of moonlightNode.config.repositories) { 60 const url = new URL(repo); 61 url.pathname = "/"; 62 - extCors.push(url.toString()); 63 } 64 65 - ipcRenderer.invoke(constants.ipcSetCorsList, extCors); 66 } 67 68 async function loadPreload() { 69 const webPreloadPath = path.join(__dirname, "web-preload.js"); 70 const webPreload = fs.readFileSync(webPreloadPath, "utf8"); 71 await webFrame.executeJavaScript(webPreload); 72 } 73 74 - async function init(oldPreloadPath: string) { 75 try { 76 await injectGlobals(); 77 await loadPreload(); ··· 82 message: message 83 }); 84 } 85 86 - // Let Discord start even if we fail 87 require(oldPreloadPath); 88 - } 89 90 - const oldPreloadPath: string = ipcRenderer.sendSync( 91 - constants.ipcGetOldPreloadPath 92 - ); 93 - init(oldPreloadPath);
··· 1 import { webFrame, ipcRenderer, contextBridge } from "electron"; 2 + import fs from "node:fs"; 3 + import path from "node:path"; 4 5 import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 6 + import { constants, MoonlightBranch } from "@moonlight-mod/types"; 7 import { getExtensions } from "@moonlight-mod/core/extension"; 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 + } 25 26 async function injectGlobals() { 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 + }; 38 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(); 47 48 global.moonlightNode = { 49 + get config() { 50 + return config; 51 + }, 52 + extensions, 53 + processedExtensions, 54 nativesCache: {}, 55 + isBrowser: false, 56 + events: createEventEmitter<NodeEventType, NodeEventPayloads>(), 57 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); 76 }, 77 + 78 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext], 79 getLogger: (id: string) => { 80 return new Logger(id); 81 }, 82 + getMoonlightDir() { 83 + return moonlightDir; 84 }, 85 + getExtensionDir: (ext: string) => { 86 + return path.join(extensionsPath, ext); 87 + } 88 }; 89 90 + await loadProcessedExtensions(processedExtensions); 91 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); 92 93 + const extCors = moonlightNode.processedExtensions.extensions.flatMap((x) => x.manifest.cors ?? []); 94 + for (const cors of extCors) { 95 + registerCors(cors); 96 + } 97 98 for (const repo of moonlightNode.config.repositories) { 99 const url = new URL(repo); 100 url.pathname = "/"; 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); 107 } 108 109 + setCors(); 110 + 111 + initialized = true; 112 } 113 114 async function loadPreload() { 115 const webPreloadPath = path.join(__dirname, "web-preload.js"); 116 const webPreload = fs.readFileSync(webPreloadPath, "utf8"); 117 await webFrame.executeJavaScript(webPreload); 118 + 119 + const func = await webFrame.executeJavaScript("async () => { await window._moonlightWebLoad(); }"); 120 + await func(); 121 } 122 123 + async function init() { 124 try { 125 await injectGlobals(); 126 await loadPreload(); ··· 131 message: message 132 }); 133 } 134 + } 135 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 144 require(oldPreloadPath); 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 + ); 164 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
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "DOM.Iterable"] 5 + } 6 }
+16 -7
packages/types/package.json
··· 1 { 2 "name": "@moonlight-mod/types", 3 - "version": "1.0.0", 4 - "main": "./src/index.ts", 5 - "types": "./src/index.ts", 6 "exports": { 7 ".": "./src/index.ts", 8 "./*": "./src/*.ts" 9 }, 10 "dependencies": { 11 - "@types/flux": "^3.1.12", 12 - "@types/node": "^20.6.2", 13 - "@types/react": "^18.2.22", 14 - "csstype": "^3.1.2", 15 "standalone-electron-types": "^1.0.0" 16 } 17 }
··· 1 { 2 "name": "@moonlight-mod/types", 3 + "version": "1.3.17", 4 "exports": { 5 ".": "./src/index.ts", 6 + "./import": "./src/import.d.ts", 7 "./*": "./src/*.ts" 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 + }, 18 "dependencies": { 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", 24 "standalone-electron-types": "^1.0.0" 25 } 26 }
+72 -5
packages/types/src/config.ts
··· 6 patchAll?: boolean; 7 }; 8 9 - export type ConfigExtensions = 10 - | { [key: string]: boolean } 11 - | { [key: string]: ConfigExtension }; 12 13 export type ConfigExtension = { 14 enabled: boolean; ··· 19 Boolean = "boolean", 20 Number = "number", 21 String = "string", 22 Select = "select", 23 List = "list", 24 Dictionary = "dictionary", 25 Custom = "custom" 26 } 27 28 export type BooleanSettingType = { 29 type: ExtensionSettingType.Boolean; 30 default?: boolean; 31 }; 32 33 export type NumberSettingType = { 34 type: ExtensionSettingType.Number; 35 default?: number; 36 min?: number; ··· 38 }; 39 40 export type StringSettingType = { 41 type: ExtensionSettingType.String; 42 default?: string; 43 }; 44 45 export type SelectSettingType = { 46 type: ExtensionSettingType.Select; 47 options: string[]; 48 - default?: string; 49 }; 50 51 export type ListSettingType = { 52 type: ExtensionSettingType.List; 53 - options?: string[]; 54 default?: string[]; 55 }; 56 57 export type DictionarySettingType = { 58 type: ExtensionSettingType.Dictionary; 59 default?: Record<string, string>; 60 }; 61 62 export type CustomSettingType = { 63 type: ExtensionSettingType.Custom; 64 default?: any; 65 }; 66 67 export type ExtensionSettingsManifest = { 68 displayName?: string; 69 description?: string; 70 } & ( 71 | BooleanSettingType 72 | NumberSettingType 73 | StringSettingType 74 | SelectSettingType 75 | ListSettingType 76 | DictionarySettingType 77 | CustomSettingType
··· 6 patchAll?: boolean; 7 }; 8 9 + export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension }; 10 11 export type ConfigExtension = { 12 enabled: boolean; ··· 17 Boolean = "boolean", 18 Number = "number", 19 String = "string", 20 + MultilineString = "multilinestring", 21 Select = "select", 22 + MultiSelect = "multiselect", 23 List = "list", 24 Dictionary = "dictionary", 25 Custom = "custom" 26 } 27 28 + export type SelectOption = 29 + | string 30 + | { 31 + value: string; 32 + label: string; 33 + }; 34 + 35 export type BooleanSettingType = { 36 + /** 37 + * Displays as a simple switch. 38 + */ 39 type: ExtensionSettingType.Boolean; 40 default?: boolean; 41 }; 42 43 export type NumberSettingType = { 44 + /** 45 + * Displays as a simple slider. 46 + */ 47 type: ExtensionSettingType.Number; 48 default?: number; 49 min?: number; ··· 51 }; 52 53 export type StringSettingType = { 54 + /** 55 + * Displays as a single line string input. 56 + */ 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; 66 default?: string; 67 }; 68 69 export type SelectSettingType = { 70 + /** 71 + * A dropdown to pick between one of many values. 72 + */ 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; 83 options: string[]; 84 + default?: string[]; 85 }; 86 87 export type ListSettingType = { 88 + /** 89 + * A list of strings that the user can add or remove from. 90 + */ 91 type: ExtensionSettingType.List; 92 default?: string[]; 93 }; 94 95 export type DictionarySettingType = { 96 + /** 97 + * A dictionary (key-value pair) that the user can add or remove from. 98 + */ 99 type: ExtensionSettingType.Dictionary; 100 default?: Record<string, string>; 101 }; 102 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 + */ 108 type: ExtensionSettingType.Custom; 109 default?: any; 110 }; 111 112 + export enum ExtensionSettingsAdvice { 113 + None = "none", 114 + Reload = "reload", 115 + Restart = "restart" 116 + } 117 + 118 export type ExtensionSettingsManifest = { 119 + /** 120 + * A human friendly name for the setting. 121 + */ 122 displayName?: string; 123 + 124 + /** 125 + * A longer description for the setting. 126 + * Markdown is not supported. 127 + */ 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; 135 } & ( 136 | BooleanSettingType 137 | NumberSettingType 138 | StringSettingType 139 + | MultilineTextInputSettingType 140 | SelectSettingType 141 + | MultiSelectSettingType 142 | ListSettingType 143 | DictionarySettingType 144 | CustomSettingType
+11
packages/types/src/constants.ts
··· 2 export const distDir = "dist"; 3 export const coreExtensionsDir = "core-extensions"; 4 export const repoUrlFile = ".moonlight-repo-url"; 5 6 export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath"; 7 export const ipcGetAppData = "_moonlight_getAppData"; 8 export const ipcMessageBox = "_moonlight_messageBox"; 9 export const ipcSetCorsList = "_moonlight_setCorsList";
··· 2 export const distDir = "dist"; 3 export const coreExtensionsDir = "core-extensions"; 4 export const repoUrlFile = ".moonlight-repo-url"; 5 + export const installedVersionFile = ".moonlight-installed-version"; 6 7 + export const ipcNodePreloadKickoff = "_moonlight_nodePreloadKickoff"; 8 export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath"; 9 + 10 export const ipcGetAppData = "_moonlight_getAppData"; 11 + export const ipcGetInjectorConfig = "_moonlight_getInjectorConfig"; 12 export const ipcMessageBox = "_moonlight_messageBox"; 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
···
··· 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
··· 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
··· 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
··· 1 - import { 2 - Spacepack, 3 - CommonReact, 4 - CommonFlux, 5 - Settings, 6 - CommonComponents, 7 - CommonFluxDispatcher 8 - } from "../coreExtensions"; 9 10 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; 19 20 export default WebpackRequire;
··· 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"; 11 12 declare function WebpackRequire(id: string): any; 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 + }; 43 44 export default WebpackRequire;
+10 -15
packages/types/src/discord/webpack.ts
··· 1 import WebpackRequire from "./require"; 2 3 - export type WebpackRequireType = typeof WebpackRequire & { 4 - c: Record<string, WebpackModule>; 5 - m: Record<string, WebpackModuleFunc>; 6 - }; 7 8 export type WebpackModule = { 9 id: string | number; 10 - loaded: boolean; 11 exports: any; 12 }; 13 14 - export type WebpackModuleFunc = (( 15 - module: any, 16 - exports: any, 17 - require: WebpackRequireType 18 - ) => void) & { 19 __moonlight?: boolean; 20 }; 21 22 - export type WebpackJsonpEntry = [ 23 - number[], 24 - { [id: string]: WebpackModuleFunc }, 25 - (require: WebpackRequireType) => any 26 - ]; 27 28 export type WebpackJsonp = WebpackJsonpEntry[] & { 29 push: {
··· 1 import WebpackRequire from "./require"; 2 + import { WebpackRequire as MappingsWebpackRequire } from "@moonlight-mod/mappings"; 3 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 + }; 10 11 export type WebpackModule = { 12 id: string | number; 13 + loaded?: boolean; 14 exports: any; 15 }; 16 17 + export type WebpackModuleFunc = ((module: any, exports: any, require: WebpackRequireType) => void) & { 18 __moonlight?: boolean; 19 }; 20 21 + export type WebpackJsonpEntry = [number[], { [id: string]: WebpackModuleFunc }, (require: WebpackRequireType) => any]; 22 23 export type WebpackJsonp = WebpackJsonpEntry[] & { 24 push: {
+120 -6
packages/types/src/extension.ts
··· 28 }; 29 30 export type ExtensionManifest = { 31 id: string; 32 version?: string; 33 34 meta?: { 35 name?: string; 36 tagline?: string; 37 description?: string; 38 authors?: ExtensionAuthor[]; 39 - deprecated?: boolean; 40 tags?: ExtensionTag[]; 41 source?: string; 42 }; 43 44 dependencies?: string[]; 45 suggested?: string[]; 46 - incompatible?: string[]; // TODO: implement 47 48 settings?: Record<string, ExtensionSettingsManifest>; 49 cors?: string[]; 50 }; 51 52 export enum ExtensionLoadSource { 53 Developer, 54 Core, ··· 62 scripts: { 63 web?: string; 64 webPath?: string; 65 nodePath?: string; 66 hostPath?: string; 67 }; 68 }; 69 ··· 95 export type Patch = { 96 find: PatchMatch; 97 replace: PatchReplace | PatchReplace[]; 98 prerequisite?: () => boolean; 99 }; 100 101 export type ExplicitExtensionDependency = { 102 - ext: string; 103 id: string; 104 }; 105 ··· 108 export type ExtensionWebpackModule = { 109 entrypoint?: boolean; 110 dependencies?: ExtensionDependency[]; 111 - run: WebpackModuleFunc; 112 }; 113 114 export type ExtensionWebExports = { 115 patches?: Patch[]; 116 webpackModules?: Record<string, ExtensionWebpackModule>; 117 }; 118 119 export type IdentifiedPatch = Patch & { ··· 121 id: number; 122 }; 123 124 - export type IdentifiedWebpackModule = ExtensionWebpackModule & 125 - ExplicitExtensionDependency;
··· 28 }; 29 30 export type ExtensionManifest = { 31 + $schema?: string; 32 + 33 + /** 34 + * A unique identifier for your extension. 35 + */ 36 id: string; 37 + 38 + /** 39 + * A version string for your extension - doesn't need to follow a specific format. Required for publishing. 40 + */ 41 version?: string; 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 + */ 56 meta?: { 57 + /** 58 + * A human friendly name for your extension as a proper noun. 59 + */ 60 name?: string; 61 + 62 + /** 63 + * A short tagline that appears below the name. 64 + */ 65 tagline?: string; 66 + 67 + /** 68 + * A longer description that can use Markdown. 69 + */ 70 description?: string; 71 + 72 + /** 73 + * List of authors that worked on this extension - accepts string or object with ID. 74 + */ 75 authors?: ExtensionAuthor[]; 76 + 77 + /** 78 + * A list of tags that are relevant to the extension. 79 + */ 80 tags?: ExtensionTag[]; 81 + 82 + /** 83 + * The URL to the source repository. 84 + */ 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; 102 }; 103 104 + /** 105 + * A list of extension IDs that are required for the extension to load. 106 + */ 107 dependencies?: string[]; 108 + 109 + /** 110 + * A list of extension IDs that the user may want to install. 111 + */ 112 suggested?: string[]; 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 + */ 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 + */ 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[]>; 144 }; 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 + 163 export enum ExtensionLoadSource { 164 Developer, 165 Core, ··· 173 scripts: { 174 web?: string; 175 webPath?: string; 176 + webpackModules?: Record<string, string>; 177 nodePath?: string; 178 hostPath?: string; 179 + style?: string; 180 }; 181 }; 182 ··· 208 export type Patch = { 209 find: PatchMatch; 210 replace: PatchReplace | PatchReplace[]; 211 + hardFail?: boolean; // if any patches fail, all fail 212 prerequisite?: () => boolean; 213 }; 214 215 export type ExplicitExtensionDependency = { 216 + ext?: string; 217 id: string; 218 }; 219 ··· 222 export type ExtensionWebpackModule = { 223 entrypoint?: boolean; 224 dependencies?: ExtensionDependency[]; 225 + run?: WebpackModuleFunc; 226 }; 227 228 export type ExtensionWebExports = { 229 patches?: Patch[]; 230 webpackModules?: Record<string, ExtensionWebpackModule>; 231 + styles?: string[]; 232 }; 233 234 export type IdentifiedPatch = Patch & { ··· 236 id: number; 237 }; 238 239 + export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+19
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 + basename: (path: string) => string; 19 + };
+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"; 9 10 export type MoonlightHost = { 11 - asarPath: string; 12 config: Config; 13 - events: EventEmitter; 14 extensions: DetectedExtension[]; 15 processedExtensions: ProcessedExtensions; 16 17 getConfig: (ext: string) => ConfigExtension["config"]; 18 getConfigOption: <T>(ext: string, name: string) => T | undefined; 19 getLogger: (id: string) => Logger; 20 }; 21 22 export type MoonlightNode = { ··· 24 extensions: DetectedExtension[]; 25 processedExtensions: ProcessedExtensions; 26 nativesCache: Record<string, any>; 27 28 getConfig: (ext: string) => ConfigExtension["config"]; 29 getConfigOption: <T>(ext: string, name: string) => T | undefined; 30 getNatives: (ext: string) => any | undefined; 31 getLogger: (id: string) => Logger; 32 33 - getExtensionDir: (ext: string) => string; 34 - writeConfig: (config: Config) => void; 35 }; 36 37 export type MoonlightWeb = { 38 unpatched: Set<IdentifiedPatch>; 39 enabledExtensions: Set<string>; 40 41 - getConfig: (ext: string) => ConfigExtension["config"]; 42 - getConfigOption: <T>(ext: string, name: string) => T | undefined; 43 getNatives: (ext: string) => any | undefined; 44 getLogger: (id: string) => Logger; 45 }; 46 47 export enum MoonlightEnv { ··· 49 NodePreload = "node-preload", 50 WebPreload = "web-preload" 51 }
··· 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"; 15 16 export type MoonlightHost = { 17 config: Config; 18 extensions: DetectedExtension[]; 19 processedExtensions: ProcessedExtensions; 20 + asarPath: string; 21 + events: EventEmitter; 22 + 23 + version: string; 24 + branch: MoonlightBranch; 25 26 getConfig: (ext: string) => ConfigExtension["config"]; 27 + getConfigPath: () => Promise<string>; 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 + 32 getLogger: (id: string) => Logger; 33 + getMoonlightDir: () => string; 34 + getExtensionDir: (ext: string) => string; 35 }; 36 37 export type MoonlightNode = { ··· 39 extensions: DetectedExtension[]; 40 processedExtensions: ProcessedExtensions; 41 nativesCache: Record<string, any>; 42 + isBrowser: boolean; 43 + events: MoonlightEventEmitter<NodeEventType, NodeEventPayloads>; 44 + 45 + version: string; 46 + branch: MoonlightBranch; 47 48 getConfig: (ext: string) => ConfigExtension["config"]; 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 + 53 getNatives: (ext: string) => any | undefined; 54 getLogger: (id: string) => Logger; 55 + getMoonlightDir: () => string; 56 + getExtensionDir: (ext: string) => string; 57 + }; 58 59 + export type MoonlightNodeSandboxed = { 60 + fs: MoonlightFS; 61 + addCors: (url: string) => void; 62 + addBlocked: (url: string) => void; 63 }; 64 65 export type MoonlightWeb = { 66 + patched: Map<string, Set<string>>; 67 unpatched: Set<IdentifiedPatch>; 68 + pendingModules: Set<IdentifiedWebpackModule>; 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; 77 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 + 88 getNatives: (ext: string) => any | undefined; 89 getLogger: (id: string) => Logger; 90 + 91 + lunast: LunAST; 92 + moonmap: Moonmap; 93 }; 94 95 export enum MoonlightEnv { ··· 97 NodePreload = "node-preload", 98 WebPreload = "web-preload" 99 } 100 + 101 + export enum MoonlightBranch { 102 + STABLE = "stable", 103 + NIGHTLY = "nightly", 104 + DEV = "dev" 105 + }
+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
··· 1 - /// <reference types="node" /> 2 /// <reference types="standalone-electron-types" /> 3 /// <reference types="react" /> 4 - /// <reference types="flux" /> 5 6 - import { 7 - MoonlightEnv, 8 - MoonlightHost, 9 - MoonlightNode, 10 - MoonlightWeb 11 - } from "./globals"; 12 13 export * from "./discord"; 14 export * from "./config"; 15 export * from "./extension"; 16 export * from "./globals"; 17 export * from "./logger"; 18 export * as constants from "./constants"; 19 20 declare global { 21 const MOONLIGHT_ENV: MoonlightEnv; ··· 23 const MOONLIGHT_INJECTOR: boolean; 24 const MOONLIGHT_NODE_PRELOAD: boolean; 25 const MOONLIGHT_WEB_PRELOAD: boolean; 26 27 var moonlightHost: MoonlightHost; 28 var moonlightNode: MoonlightNode; 29 var moonlight: MoonlightWeb; 30 }
··· 1 /// <reference types="standalone-electron-types" /> 2 /// <reference types="react" /> 3 + /// <reference types="./import" /> 4 + /// <reference types="./mappings" /> 5 + /* eslint-disable no-var */ 6 7 + import { MoonlightEnv, MoonlightHost, MoonlightNode, MoonlightNodeSandboxed, MoonlightWeb } from "./globals"; 8 9 export * from "./discord"; 10 export * from "./config"; 11 export * from "./extension"; 12 + export * as CoreExtensions from "./coreExtensions"; 13 export * from "./globals"; 14 export * from "./logger"; 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"; 20 21 declare global { 22 const MOONLIGHT_ENV: MoonlightEnv; ··· 24 const MOONLIGHT_INJECTOR: boolean; 25 const MOONLIGHT_NODE_PRELOAD: boolean; 26 const MOONLIGHT_WEB_PRELOAD: boolean; 27 + const MOONLIGHT_BROWSER: boolean; 28 + const MOONLIGHT_BRANCH: string; 29 + const MOONLIGHT_VERSION: string; 30 31 var moonlightHost: MoonlightHost; 32 var moonlightNode: MoonlightNode; 33 + var moonlightNodeSandboxed: MoonlightNodeSandboxed; 34 var moonlight: MoonlightWeb; 35 + var _moonlight_coreExtensionsStr: string; 36 + 37 + var _moonlightBrowserInit: undefined | (() => Promise<void>); 38 + var _moonlightWebLoad: undefined | (() => Promise<void>); 39 }
+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
··· 1 { 2 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 5 "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "skipLibCheck": true, 9 - "moduleResolution": "bundler", 10 - "jsx": "react", 11 - "declaration": true 12 }, 13 - "include": ["./src/**/*", "src/index.ts"] 14 }
··· 1 { 2 "compilerOptions": { 3 + "target": "ES2016", 4 + "jsx": "react", 5 + "module": "ES6", 6 + "moduleResolution": "bundler", 7 + "strict": true, 8 + "declaration": true, 9 "esModuleInterop": true, 10 + "forceConsistentCasingInFileNames": true 11 }, 12 + "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] 13 }
+13 -1
packages/web-preload/package.json
··· 1 { 2 "name": "@moonlight-mod/web-preload", 3 "private": true, 4 "dependencies": { 5 - "@moonlight-mod/core": "workspace:*" 6 } 7 }
··· 1 { 2 "name": "@moonlight-mod/web-preload", 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 + }, 12 "dependencies": { 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:*" 18 } 19 }
+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"; 7 8 - (async () => { 9 const logger = new Logger("web-preload"); 10 11 window.moonlight = { 12 unpatched: new Set(), 13 enabledExtensions: new Set(), 14 15 getConfig: moonlightNode.getConfig.bind(moonlightNode), 16 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 17 getNatives: moonlightNode.getNatives.bind(moonlightNode), 18 - getLogger: (id: string) => { 19 return new Logger(id); 20 - } 21 }; 22 23 try { 24 await loadProcessedExtensions(moonlightNode.processedExtensions); 25 await installWebpackPatcher(); 26 } catch (e) { 27 logger.error("Error setting up web-preload", e); 28 } 29 - })();
··· 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"; 11 12 + async function load() { 13 + delete window._moonlightWebLoad; 14 + initLogger(moonlightNode.config); 15 const logger = new Logger("web-preload"); 16 17 window.moonlight = { 18 + patched: new Map(), 19 unpatched: new Set(), 20 + pendingModules: new Set(), 21 enabledExtensions: new Set(), 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 + 35 getConfig: moonlightNode.getConfig.bind(moonlightNode), 36 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 37 + setConfigOption: moonlightNode.setConfigOption.bind(moonlightNode), 38 + writeConfig: moonlightNode.writeConfig.bind(moonlightNode), 39 + 40 getNatives: moonlightNode.getNatives.bind(moonlightNode), 41 + getLogger(id) { 42 return new Logger(id); 43 + }, 44 + 45 + lunast: new LunAST(), 46 + moonmap: new Moonmap() 47 }; 48 49 try { 50 + loadMappings(window.moonlight.moonmap, window.moonlight.lunast); 51 await loadProcessedExtensions(moonlightNode.processedExtensions); 52 await installWebpackPatcher(); 53 } catch (e) { 54 logger.error("Error setting up web-preload", e); 55 } 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
··· 1 { 2 - "extends": "../../tsconfig.json" 3 }
··· 1 { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["ESNext", "DOM"] 5 + } 6 }
+2901 -363
pnpm-lock.yaml
··· 1 - lockfileVersion: '6.0' 2 3 settings: 4 autoInstallPeers: true 5 excludeLinksFromLockfile: false 6 7 importers: 8 9 .: 10 devDependencies: 11 esbuild: 12 - specifier: ^0.19.3 13 version: 0.19.3 14 esbuild-copy-static-files: 15 - specifier: ^0.1.0 16 version: 0.1.0 17 18 packages/core: 19 dependencies: 20 '@moonlight-mod/types': 21 specifier: workspace:* 22 version: link:../types 23 - glob: 24 - specifier: ^10.3.4 25 - version: 10.3.4 26 27 packages/core-extensions: 28 dependencies: 29 - '@electron/asar': 30 - specifier: ^3.2.5 31 - version: 3.2.5 32 '@moonlight-mod/types': 33 specifier: workspace:* 34 version: link:../types 35 36 packages/injector: 37 dependencies: ··· 53 54 packages/types: 55 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 62 '@types/react': 63 - specifier: ^18.2.22 64 - version: 18.2.22 65 csstype: 66 - specifier: ^3.1.2 67 - version: 3.1.2 68 standalone-electron-types: 69 specifier: ^1.0.0 70 version: 1.0.0 ··· 74 '@moonlight-mod/core': 75 specifier: workspace:* 76 version: link:../core 77 78 packages: 79 80 - /@electron/asar@3.2.5: 81 - resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==} 82 - engines: {node: '>=10.12.0'} 83 hasBin: true 84 - dependencies: 85 - commander: 5.1.0 86 - glob: 7.2.3 87 - minimatch: 3.1.2 88 - dev: false 89 90 - /@esbuild/android-arm64@0.19.3: 91 resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} 92 engines: {node: '>=12'} 93 cpu: [arm64] 94 os: [android] 95 - requiresBuild: true 96 - dev: true 97 - optional: true 98 99 - /@esbuild/android-arm@0.19.3: 100 resolution: {integrity: sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==} 101 engines: {node: '>=12'} 102 cpu: [arm] 103 os: [android] 104 - requiresBuild: true 105 - dev: true 106 - optional: true 107 108 - /@esbuild/android-x64@0.19.3: 109 resolution: {integrity: sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==} 110 engines: {node: '>=12'} 111 cpu: [x64] 112 os: [android] 113 - requiresBuild: true 114 - dev: true 115 - optional: true 116 117 - /@esbuild/darwin-arm64@0.19.3: 118 resolution: {integrity: sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==} 119 engines: {node: '>=12'} 120 cpu: [arm64] 121 os: [darwin] 122 - requiresBuild: true 123 - dev: true 124 - optional: true 125 126 - /@esbuild/darwin-x64@0.19.3: 127 resolution: {integrity: sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==} 128 engines: {node: '>=12'} 129 cpu: [x64] 130 os: [darwin] 131 - requiresBuild: true 132 - dev: true 133 - optional: true 134 135 - /@esbuild/freebsd-arm64@0.19.3: 136 resolution: {integrity: sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==} 137 engines: {node: '>=12'} 138 cpu: [arm64] 139 os: [freebsd] 140 - requiresBuild: true 141 - dev: true 142 - optional: true 143 144 - /@esbuild/freebsd-x64@0.19.3: 145 resolution: {integrity: sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==} 146 engines: {node: '>=12'} 147 cpu: [x64] 148 os: [freebsd] 149 - requiresBuild: true 150 - dev: true 151 - optional: true 152 153 - /@esbuild/linux-arm64@0.19.3: 154 resolution: {integrity: sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==} 155 engines: {node: '>=12'} 156 cpu: [arm64] 157 os: [linux] 158 - requiresBuild: true 159 - dev: true 160 - optional: true 161 162 - /@esbuild/linux-arm@0.19.3: 163 resolution: {integrity: sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==} 164 engines: {node: '>=12'} 165 cpu: [arm] 166 os: [linux] 167 - requiresBuild: true 168 - dev: true 169 - optional: true 170 171 - /@esbuild/linux-ia32@0.19.3: 172 resolution: {integrity: sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==} 173 engines: {node: '>=12'} 174 cpu: [ia32] 175 os: [linux] 176 - requiresBuild: true 177 - dev: true 178 - optional: true 179 180 - /@esbuild/linux-loong64@0.19.3: 181 resolution: {integrity: sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==} 182 engines: {node: '>=12'} 183 cpu: [loong64] 184 os: [linux] 185 - requiresBuild: true 186 - dev: true 187 - optional: true 188 189 - /@esbuild/linux-mips64el@0.19.3: 190 resolution: {integrity: sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==} 191 engines: {node: '>=12'} 192 cpu: [mips64el] 193 os: [linux] 194 - requiresBuild: true 195 - dev: true 196 - optional: true 197 198 - /@esbuild/linux-ppc64@0.19.3: 199 resolution: {integrity: sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==} 200 engines: {node: '>=12'} 201 cpu: [ppc64] 202 os: [linux] 203 - requiresBuild: true 204 - dev: true 205 - optional: true 206 207 - /@esbuild/linux-riscv64@0.19.3: 208 resolution: {integrity: sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==} 209 engines: {node: '>=12'} 210 cpu: [riscv64] 211 os: [linux] 212 - requiresBuild: true 213 - dev: true 214 - optional: true 215 216 - /@esbuild/linux-s390x@0.19.3: 217 resolution: {integrity: sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==} 218 engines: {node: '>=12'} 219 cpu: [s390x] 220 os: [linux] 221 - requiresBuild: true 222 - dev: true 223 - optional: true 224 225 - /@esbuild/linux-x64@0.19.3: 226 resolution: {integrity: sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==} 227 engines: {node: '>=12'} 228 cpu: [x64] 229 os: [linux] 230 - requiresBuild: true 231 - dev: true 232 - optional: true 233 234 - /@esbuild/netbsd-x64@0.19.3: 235 resolution: {integrity: sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==} 236 engines: {node: '>=12'} 237 cpu: [x64] 238 os: [netbsd] 239 - requiresBuild: true 240 - dev: true 241 - optional: true 242 243 - /@esbuild/openbsd-x64@0.19.3: 244 resolution: {integrity: sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==} 245 engines: {node: '>=12'} 246 cpu: [x64] 247 os: [openbsd] 248 - requiresBuild: true 249 - dev: true 250 - optional: true 251 252 - /@esbuild/sunos-x64@0.19.3: 253 resolution: {integrity: sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==} 254 engines: {node: '>=12'} 255 cpu: [x64] 256 os: [sunos] 257 - requiresBuild: true 258 - dev: true 259 - optional: true 260 261 - /@esbuild/win32-arm64@0.19.3: 262 resolution: {integrity: sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==} 263 engines: {node: '>=12'} 264 cpu: [arm64] 265 os: [win32] 266 - requiresBuild: true 267 - dev: true 268 - optional: true 269 270 - /@esbuild/win32-ia32@0.19.3: 271 resolution: {integrity: sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==} 272 engines: {node: '>=12'} 273 cpu: [ia32] 274 os: [win32] 275 - requiresBuild: true 276 - dev: true 277 - optional: true 278 279 - /@esbuild/win32-x64@0.19.3: 280 resolution: {integrity: sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==} 281 engines: {node: '>=12'} 282 cpu: [x64] 283 os: [win32] 284 - requiresBuild: true 285 - dev: true 286 - optional: true 287 288 - /@isaacs/cliui@8.0.2: 289 - resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} 290 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 299 300 - /@pkgjs/parseargs@0.11.0: 301 - resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} 302 engines: {node: '>=14'} 303 - requiresBuild: true 304 - dev: false 305 - optional: true 306 307 - /@types/fbemitter@2.0.33: 308 - resolution: {integrity: sha512-KcSilwdl0D8YgXGL6l9d+rTBm2W7pDyTZrDEw0+IzqQ724676KJtMeO+xHodJewKFWZT+GFWaJubA5mpMxSkcg==} 309 - dev: false 310 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 317 318 - /@types/node@18.17.17: 319 - resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 320 - dev: false 321 322 - /@types/node@20.6.2: 323 - resolution: {integrity: sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==} 324 - dev: false 325 326 - /@types/prop-types@15.7.6: 327 - resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==} 328 - dev: false 329 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 337 338 - /@types/scheduler@0.16.3: 339 - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} 340 - dev: false 341 342 - /ansi-regex@5.0.1: 343 - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 344 engines: {node: '>=8'} 345 - dev: false 346 347 - /ansi-regex@6.0.1: 348 - resolution: {integrity: sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==} 349 engines: {node: '>=12'} 350 - dev: false 351 352 - /ansi-styles@4.3.0: 353 - resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 354 engines: {node: '>=8'} 355 dependencies: 356 color-convert: 2.0.1 357 - dev: false 358 359 - /ansi-styles@6.2.1: 360 - resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} 361 - engines: {node: '>=12'} 362 - dev: false 363 364 - /balanced-match@1.0.2: 365 - resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 366 - dev: false 367 368 - /brace-expansion@1.1.11: 369 - resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 370 dependencies: 371 balanced-match: 1.0.2 372 concat-map: 0.0.1 373 - dev: false 374 375 - /brace-expansion@2.0.1: 376 - resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} 377 dependencies: 378 balanced-match: 1.0.2 379 - dev: false 380 381 - /color-convert@2.0.1: 382 - resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} 383 - engines: {node: '>=7.0.0'} 384 dependencies: 385 - color-name: 1.1.4 386 - dev: false 387 388 - /color-name@1.1.4: 389 - resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 390 - dev: false 391 392 - /commander@5.1.0: 393 - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} 394 - engines: {node: '>= 6'} 395 - dev: false 396 397 - /concat-map@0.0.1: 398 - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 399 - dev: false 400 401 - /cross-spawn@7.0.3: 402 - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 403 - engines: {node: '>= 8'} 404 dependencies: 405 path-key: 3.1.1 406 shebang-command: 2.0.0 407 which: 2.0.2 408 - dev: false 409 410 - /csstype@3.1.2: 411 - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} 412 - dev: false 413 414 - /eastasianwidth@0.2.0: 415 - resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} 416 - dev: false 417 418 - /emoji-regex@8.0.0: 419 - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} 420 - dev: false 421 422 - /emoji-regex@9.2.2: 423 - resolution: {integrity: sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==} 424 - dev: false 425 426 - /esbuild-copy-static-files@0.1.0: 427 - resolution: {integrity: sha512-KlpmYqANA1t2nZavEdItfcOjJC6wbHA21v35HJWN32DddGTWKNNGDKljUzbCPojmpD+wAw8/DXr5abJ4jFCE0w==} 428 - dev: true 429 430 - /esbuild@0.19.3: 431 - resolution: {integrity: sha512-UlJ1qUUA2jL2nNib1JTSkifQTcYTroFqRjwCFW4QYEKEsixXD5Tik9xML7zh2gTxkYTBKGHNH9y7txMwVyPbjw==} 432 - engines: {node: '>=12'} 433 - hasBin: true 434 - requiresBuild: true 435 optionalDependencies: 436 '@esbuild/android-arm': 0.19.3 437 '@esbuild/android-arm64': 0.19.3 ··· 455 '@esbuild/win32-arm64': 0.19.3 456 '@esbuild/win32-ia32': 0.19.3 457 '@esbuild/win32-x64': 0.19.3 458 - dev: true 459 460 - /foreground-child@3.1.1: 461 - resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} 462 - engines: {node: '>=14'} 463 dependencies: 464 - cross-spawn: 7.0.3 465 - signal-exit: 4.1.0 466 - dev: false 467 468 - /fs.realpath@1.0.0: 469 - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 470 - dev: false 471 472 - /glob@10.3.4: 473 - resolution: {integrity: sha512-6LFElP3A+i/Q8XQKEvZjkEWEOTgAIALR9AO2rwT8bgPhDd1anmqDJDZ6lLddI4ehxxxR1S5RIqKe1uapMQfYaQ==} 474 - engines: {node: '>=16 || 14 >=14.17'} 475 - hasBin: true 476 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 483 484 - /glob@7.2.3: 485 - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 486 dependencies: 487 - fs.realpath: 1.0.0 488 - inflight: 1.0.6 489 - inherits: 2.0.4 490 minimatch: 3.1.2 491 - once: 1.4.0 492 - path-is-absolute: 1.0.1 493 - dev: false 494 495 - /inflight@1.0.6: 496 - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 497 dependencies: 498 - once: 1.4.0 499 - wrappy: 1.0.2 500 - dev: false 501 502 - /inherits@2.0.4: 503 - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 504 - dev: false 505 506 - /is-fullwidth-code-point@3.0.0: 507 - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} 508 - engines: {node: '>=8'} 509 - dev: false 510 511 - /isexe@2.0.0: 512 - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 513 - dev: false 514 515 - /jackspeak@2.3.3: 516 - resolution: {integrity: sha512-R2bUw+kVZFS/h1AZqBKrSgDmdmjApzgY0AlCPumopFiAlbUxE2gf+SCuBzQ0cP5hHmUmFYF5yw55T97Th5Kstg==} 517 - engines: {node: '>=14'} 518 dependencies: 519 - '@isaacs/cliui': 8.0.2 520 optionalDependencies: 521 - '@pkgjs/parseargs': 0.11.0 522 - dev: false 523 524 - /lru-cache@10.0.1: 525 - resolution: {integrity: sha512-IJ4uwUTi2qCccrioU6g9g/5rvvVl13bsdczUUcqbciD9iLr095yj8DQKdObriEvuNSx325N1rV1O0sJFszx75g==} 526 - engines: {node: 14 || >=16.14} 527 - dev: false 528 529 - /minimatch@3.1.2: 530 - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 531 dependencies: 532 brace-expansion: 1.1.11 533 - dev: false 534 535 - /minimatch@9.0.3: 536 - resolution: {integrity: sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==} 537 - engines: {node: '>=16 || 14 >=14.17'} 538 dependencies: 539 brace-expansion: 2.0.1 540 - dev: false 541 542 - /minipass@7.0.3: 543 - resolution: {integrity: sha512-LhbbwCfz3vsb12j/WkWQPZfKTsgqIe1Nf/ti1pKjYESGLHIVjWU96G9/ljLH4F9mWNVhlQOm0VySdAWzf05dpg==} 544 - engines: {node: '>=16 || 14 >=14.17'} 545 - dev: false 546 547 - /once@1.4.0: 548 - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 549 dependencies: 550 - wrappy: 1.0.2 551 - dev: false 552 553 - /path-is-absolute@1.0.1: 554 - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 555 - engines: {node: '>=0.10.0'} 556 - dev: false 557 558 - /path-key@3.1.1: 559 - resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 560 - engines: {node: '>=8'} 561 - dev: false 562 563 - /path-scurry@1.10.1: 564 - resolution: {integrity: sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==} 565 - engines: {node: '>=16 || 14 >=14.17'} 566 dependencies: 567 - lru-cache: 10.0.1 568 - minipass: 7.0.3 569 - dev: false 570 571 - /shebang-command@2.0.0: 572 - resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} 573 - engines: {node: '>=8'} 574 dependencies: 575 shebang-regex: 3.0.0 576 - dev: false 577 578 - /shebang-regex@3.0.0: 579 - resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 580 - engines: {node: '>=8'} 581 - dev: false 582 583 - /signal-exit@4.1.0: 584 - resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} 585 - engines: {node: '>=14'} 586 - dev: false 587 588 - /standalone-electron-types@1.0.0: 589 - resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 590 dependencies: 591 '@types/node': 18.17.17 592 - dev: false 593 594 - /string-width@4.2.3: 595 - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} 596 - engines: {node: '>=8'} 597 dependencies: 598 - emoji-regex: 8.0.0 599 - is-fullwidth-code-point: 3.0.0 600 - strip-ansi: 6.0.1 601 - dev: false 602 603 - /string-width@5.1.2: 604 - resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} 605 - engines: {node: '>=12'} 606 dependencies: 607 - eastasianwidth: 0.2.0 608 - emoji-regex: 9.2.2 609 - strip-ansi: 7.1.0 610 - dev: false 611 612 - /strip-ansi@6.0.1: 613 - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 614 - engines: {node: '>=8'} 615 dependencies: 616 - ansi-regex: 5.0.1 617 - dev: false 618 619 - /strip-ansi@7.1.0: 620 - resolution: {integrity: sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==} 621 - engines: {node: '>=12'} 622 dependencies: 623 - ansi-regex: 6.0.1 624 - dev: false 625 626 - /which@2.0.2: 627 - resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} 628 - engines: {node: '>= 8'} 629 - hasBin: true 630 dependencies: 631 - isexe: 2.0.0 632 - dev: false 633 634 - /wrap-ansi@7.0.0: 635 - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} 636 - engines: {node: '>=10'} 637 dependencies: 638 - ansi-styles: 4.3.0 639 - string-width: 4.2.3 640 - strip-ansi: 6.0.1 641 - dev: false 642 643 - /wrap-ansi@8.1.0: 644 - resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} 645 - engines: {node: '>=12'} 646 dependencies: 647 - ansi-styles: 6.2.1 648 - string-width: 5.1.2 649 - strip-ansi: 7.1.0 650 - dev: false 651 652 - /wrappy@1.0.2: 653 - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 654 - dev: false
··· 1 + lockfileVersion: '9.0' 2 3 settings: 4 autoInstallPeers: true 5 excludeLinksFromLockfile: false 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 + 62 importers: 63 64 .: 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 72 esbuild: 73 + specifier: catalog:dev 74 version: 0.19.3 75 esbuild-copy-static-files: 76 + specifier: catalog:dev 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 115 116 packages/core: 117 dependencies: 118 '@moonlight-mod/types': 119 specifier: workspace:* 120 version: link:../types 121 122 packages/core-extensions: 123 dependencies: 124 + '@moonlight-mod/core': 125 + specifier: workspace:* 126 + version: link:../core 127 '@moonlight-mod/types': 128 specifier: workspace:* 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 136 137 packages/injector: 138 dependencies: ··· 154 155 packages/types: 156 dependencies: 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 166 '@types/react': 167 + specifier: ^18.3.10 168 + version: 18.3.20 169 csstype: 170 + specifier: ^3.1.3 171 + version: 3.1.3 172 standalone-electron-types: 173 specifier: ^1.0.0 174 version: 1.0.0 ··· 178 '@moonlight-mod/core': 179 specifier: workspace:* 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 193 194 packages: 195 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==} 202 hasBin: true 203 204 + '@esbuild/android-arm64@0.19.3': 205 resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} 206 engines: {node: '>=12'} 207 cpu: [arm64] 208 os: [android] 209 210 + '@esbuild/android-arm@0.19.3': 211 resolution: {integrity: sha512-Lemgw4io4VZl9GHJmjiBGzQ7ONXRfRPHcUEerndjwiSkbxzrpq0Uggku5MxxrXdwJ+pTj1qyw4jwTu7hkPsgIA==} 212 engines: {node: '>=12'} 213 cpu: [arm] 214 os: [android] 215 216 + '@esbuild/android-x64@0.19.3': 217 resolution: {integrity: sha512-FKQJKkK5MXcBHoNZMDNUAg1+WcZlV/cuXrWCoGF/TvdRiYS4znA0m5Il5idUwfxrE20bG/vU1Cr5e1AD6IEIjQ==} 218 engines: {node: '>=12'} 219 cpu: [x64] 220 os: [android] 221 222 + '@esbuild/darwin-arm64@0.19.3': 223 resolution: {integrity: sha512-kw7e3FXU+VsJSSSl2nMKvACYlwtvZB8RUIeVShIEY6PVnuZ3c9+L9lWB2nWeeKWNNYDdtL19foCQ0ZyUL7nqGw==} 224 engines: {node: '>=12'} 225 cpu: [arm64] 226 os: [darwin] 227 228 + '@esbuild/darwin-x64@0.19.3': 229 resolution: {integrity: sha512-tPfZiwF9rO0jW6Jh9ipi58N5ZLoSjdxXeSrAYypy4psA2Yl1dAMhM71KxVfmjZhJmxRjSnb29YlRXXhh3GqzYw==} 230 engines: {node: '>=12'} 231 cpu: [x64] 232 os: [darwin] 233 234 + '@esbuild/freebsd-arm64@0.19.3': 235 resolution: {integrity: sha512-ERDyjOgYeKe0Vrlr1iLrqTByB026YLPzTytDTz1DRCYM+JI92Dw2dbpRHYmdqn6VBnQ9Bor6J8ZlNwdZdxjlSg==} 236 engines: {node: '>=12'} 237 cpu: [arm64] 238 os: [freebsd] 239 240 + '@esbuild/freebsd-x64@0.19.3': 241 resolution: {integrity: sha512-nXesBZ2Ad1qL+Rm3crN7NmEVJ5uvfLFPLJev3x1j3feCQXfAhoYrojC681RhpdOph8NsvKBBwpYZHR7W0ifTTA==} 242 engines: {node: '>=12'} 243 cpu: [x64] 244 os: [freebsd] 245 246 + '@esbuild/linux-arm64@0.19.3': 247 resolution: {integrity: sha512-qXvYKmXj8GcJgWq3aGvxL/JG1ZM3UR272SdPU4QSTzD0eymrM7leiZH77pvY3UetCy0k1xuXZ+VPvoJNdtrsWQ==} 248 engines: {node: '>=12'} 249 cpu: [arm64] 250 os: [linux] 251 252 + '@esbuild/linux-arm@0.19.3': 253 resolution: {integrity: sha512-zr48Cg/8zkzZCzDHNxXO/89bf9e+r4HtzNUPoz4GmgAkF1gFAFmfgOdCbR8zMbzFDGb1FqBBhdXUpcTQRYS1cQ==} 254 engines: {node: '>=12'} 255 cpu: [arm] 256 os: [linux] 257 258 + '@esbuild/linux-ia32@0.19.3': 259 resolution: {integrity: sha512-7XlCKCA0nWcbvYpusARWkFjRQNWNGlt45S+Q18UeS///K6Aw8bB2FKYe9mhVWy/XLShvCweOLZPrnMswIaDXQA==} 260 engines: {node: '>=12'} 261 cpu: [ia32] 262 os: [linux] 263 264 + '@esbuild/linux-loong64@0.19.3': 265 resolution: {integrity: sha512-qGTgjweER5xqweiWtUIDl9OKz338EQqCwbS9c2Bh5jgEH19xQ1yhgGPNesugmDFq+UUSDtWgZ264st26b3de8A==} 266 engines: {node: '>=12'} 267 cpu: [loong64] 268 os: [linux] 269 270 + '@esbuild/linux-mips64el@0.19.3': 271 resolution: {integrity: sha512-gy1bFskwEyxVMFRNYSvBauDIWNggD6pyxUksc0MV9UOBD138dKTzr8XnM2R4mBsHwVzeuIH8X5JhmNs2Pzrx+A==} 272 engines: {node: '>=12'} 273 cpu: [mips64el] 274 os: [linux] 275 276 + '@esbuild/linux-ppc64@0.19.3': 277 resolution: {integrity: sha512-UrYLFu62x1MmmIe85rpR3qou92wB9lEXluwMB/STDzPF9k8mi/9UvNsG07Tt9AqwPQXluMQ6bZbTzYt01+Ue5g==} 278 engines: {node: '>=12'} 279 cpu: [ppc64] 280 os: [linux] 281 282 + '@esbuild/linux-riscv64@0.19.3': 283 resolution: {integrity: sha512-9E73TfyMCbE+1AwFOg3glnzZ5fBAFK4aawssvuMgCRqCYzE0ylVxxzjEfut8xjmKkR320BEoMui4o/t9KA96gA==} 284 engines: {node: '>=12'} 285 cpu: [riscv64] 286 os: [linux] 287 288 + '@esbuild/linux-s390x@0.19.3': 289 resolution: {integrity: sha512-LlmsbuBdm1/D66TJ3HW6URY8wO6IlYHf+ChOUz8SUAjVTuaisfuwCOAgcxo3Zsu3BZGxmI7yt//yGOxV+lHcEA==} 290 engines: {node: '>=12'} 291 cpu: [s390x] 292 os: [linux] 293 294 + '@esbuild/linux-x64@0.19.3': 295 resolution: {integrity: sha512-ogV0+GwEmvwg/8ZbsyfkYGaLACBQWDvO0Kkh8LKBGKj9Ru8VM39zssrnu9Sxn1wbapA2qNS6BiLdwJZGouyCwQ==} 296 engines: {node: '>=12'} 297 cpu: [x64] 298 os: [linux] 299 300 + '@esbuild/netbsd-x64@0.19.3': 301 resolution: {integrity: sha512-o1jLNe4uzQv2DKXMlmEzf66Wd8MoIhLNO2nlQBHLtWyh2MitDG7sMpfCO3NTcoTMuqHjfufgUQDFRI5C+xsXQw==} 302 engines: {node: '>=12'} 303 cpu: [x64] 304 os: [netbsd] 305 306 + '@esbuild/openbsd-x64@0.19.3': 307 resolution: {integrity: sha512-AZJCnr5CZgZOdhouLcfRdnk9Zv6HbaBxjcyhq0StNcvAdVZJSKIdOiPB9az2zc06ywl0ePYJz60CjdKsQacp5Q==} 308 engines: {node: '>=12'} 309 cpu: [x64] 310 os: [openbsd] 311 312 + '@esbuild/sunos-x64@0.19.3': 313 resolution: {integrity: sha512-Acsujgeqg9InR4glTRvLKGZ+1HMtDm94ehTIHKhJjFpgVzZG9/pIcWW/HA/DoMfEyXmANLDuDZ2sNrWcjq1lxw==} 314 engines: {node: '>=12'} 315 cpu: [x64] 316 os: [sunos] 317 318 + '@esbuild/win32-arm64@0.19.3': 319 resolution: {integrity: sha512-FSrAfjVVy7TifFgYgliiJOyYynhQmqgPj15pzLyJk8BUsnlWNwP/IAy6GAiB1LqtoivowRgidZsfpoYLZH586A==} 320 engines: {node: '>=12'} 321 cpu: [arm64] 322 os: [win32] 323 324 + '@esbuild/win32-ia32@0.19.3': 325 resolution: {integrity: sha512-xTScXYi12xLOWZ/sc5RBmMN99BcXp/eEf7scUC0oeiRoiT5Vvo9AycuqCp+xdpDyAU+LkrCqEpUS9fCSZF8J3Q==} 326 engines: {node: '>=12'} 327 cpu: [ia32] 328 os: [win32] 329 330 + '@esbuild/win32-x64@0.19.3': 331 resolution: {integrity: sha512-FbUN+0ZRXsypPyWE2IwIkVjDkDnJoMJARWOcFZn4KPPli+QnKqF0z1anvfaYe3ev5HFCpRDLLBDHyOALLppWHw==} 332 engines: {node: '>=12'} 333 cpu: [x64] 334 os: [win32] 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} 357 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==} 771 engines: {node: '>=12'} 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} 811 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==} 991 engines: {node: '>=14'} 992 + hasBin: true 993 994 + ieee754@1.2.1: 995 + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} 996 997 + ignore@5.3.2: 998 + resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} 999 + engines: {node: '>= 4'} 1000 1001 + import-fresh@3.3.0: 1002 + resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} 1003 + engines: {node: '>=6'} 1004 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'} 1012 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'} 1032 1033 + is-core-module@2.16.1: 1034 + resolution: {integrity: sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==} 1035 + engines: {node: '>= 0.4'} 1036 1037 + is-data-view@1.0.2: 1038 + resolution: {integrity: sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==} 1039 + engines: {node: '>= 0.4'} 1040 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==} 1263 engines: {node: '>=8'} 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'} 1278 1279 + picomatch@4.0.2: 1280 + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} 1281 engines: {node: '>=12'} 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'} 1289 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==} 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: 1953 dependencies: 1954 color-convert: 2.0.1 1955 1956 + ansis@3.17.0: {} 1957 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 2004 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: 2028 dependencies: 2029 balanced-match: 1.0.2 2030 concat-map: 0.0.1 2031 2032 + brace-expansion@2.0.1: 2033 dependencies: 2034 balanced-match: 1.0.2 2035 2036 + braces@3.0.3: 2037 dependencies: 2038 + fill-range: 7.1.1 2039 2040 + buffer@6.0.3: 2041 + dependencies: 2042 + base64-js: 1.5.1 2043 + ieee754: 1.2.1 2044 2045 + cac@6.7.14: {} 2046 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: {} 2076 2077 + concat-map@0.0.1: {} 2078 + 2079 + cross-spawn@7.0.6: 2080 dependencies: 2081 path-key: 3.1.1 2082 shebang-command: 2.0.0 2083 which: 2.0.2 2084 2085 + csstype@3.1.3: {} 2086 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 2092 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 2122 2123 + defu@6.1.4: {} 2124 + 2125 + destr@2.0.4: {} 2126 + 2127 + doctrine@2.1.0: 2128 + dependencies: 2129 + esutils: 2.0.3 2130 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 2136 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: 2238 optionalDependencies: 2239 '@esbuild/android-arm': 0.19.3 2240 '@esbuild/android-arm64': 0.19.3 ··· 2258 '@esbuild/win32-arm64': 0.19.3 2259 '@esbuild/win32-ia32': 0.19.3 2260 '@esbuild/win32-x64': 0.19.3 2261 2262 + escape-string-regexp@4.0.0: {} 2263 + 2264 + eslint-config-prettier@9.1.0(eslint@9.23.0(jiti@2.4.2)): 2265 dependencies: 2266 + eslint: 9.23.0(jiti@2.4.2) 2267 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)) 2277 2278 + eslint-plugin-react@7.37.5(eslint@9.23.0(jiti@2.4.2)): 2279 dependencies: 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 2299 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): 2310 dependencies: 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 2343 minimatch: 3.1.2 2344 + natural-compare: 1.4.0 2345 + optionator: 0.9.3 2346 + optionalDependencies: 2347 + jiti: 2.4.2 2348 + transitivePeerDependencies: 2349 + - supports-color 2350 2351 + espree@10.3.0: 2352 dependencies: 2353 + acorn: 8.14.1 2354 + acorn-jsx: 5.3.2(acorn@8.14.1) 2355 + eslint-visitor-keys: 4.2.0 2356 2357 + esquery@1.6.0: 2358 + dependencies: 2359 + estraverse: 5.3.0 2360 2361 + esrecurse@4.3.0: 2362 + dependencies: 2363 + estraverse: 5.3.0 2364 2365 + estraverse@5.3.0: {} 2366 2367 + estree-toolkit@1.7.8: 2368 dependencies: 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): 2401 optionalDependencies: 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 2423 2424 + flatted@3.2.9: {} 2425 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: 2703 dependencies: 2704 brace-expansion: 1.1.11 2705 2706 + minimatch@9.0.5: 2707 dependencies: 2708 brace-expansion: 2.0.1 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: {} 2721 2722 + object-keys@1.1.1: {} 2723 2724 + object.assign@4.1.7: 2725 dependencies: 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 2732 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 2739 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 2759 2760 + onetime@7.0.0: 2761 dependencies: 2762 + mimic-function: 5.0.1 2763 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: 2930 dependencies: 2931 shebang-regex: 3.0.0 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 2954 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 2962 2963 + signal-exit@4.1.0: {} 2964 2965 + standalone-electron-types@1.0.0: 2966 dependencies: 2967 '@types/node': 18.17.17 2968 2969 + string.prototype.matchall@4.0.12: 2970 dependencies: 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 2984 2985 + string.prototype.repeat@1.0.0: 2986 dependencies: 2987 + define-properties: 1.2.1 2988 + es-abstract: 1.23.9 2989 2990 + string.prototype.trim@1.2.10: 2991 dependencies: 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 2999 3000 + string.prototype.trimend@1.0.9: 3001 dependencies: 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 3006 3007 + string.prototype.trimstart@1.0.8: 3008 dependencies: 3009 + call-bind: 1.0.8 3010 + define-properties: 1.2.1 3011 + es-object-atoms: 1.1.1 3012 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: 3020 dependencies: 3021 + has-flag: 4.0.0 3022 + 3023 + supports-preserve-symlinks-flag@1.0.0: {} 3024 3025 + synckit@0.11.1: 3026 dependencies: 3027 + '@pkgr/core': 0.2.0 3028 + tslib: 2.8.1 3029 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
··· 1 packages: 2 - - "packages/*"
··· 1 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
···
··· 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
···
··· 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
··· 1 { 2 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 5 - "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "skipLibCheck": true, 9 - "moduleResolution": "bundler", 10 "baseUrl": "./packages/", 11 - "jsx": "react", 12 - 13 - // disable unreachable code detection because it breaks with esbuild labels 14 - "allowUnreachableCode": true 15 }, 16 - "include": ["./packages/**/*", "./env.d.ts"], 17 - "exclude": ["node_modules"] 18 }
··· 1 { 2 + "extends": ["./tsconfig.base.json"], 3 "compilerOptions": { 4 "baseUrl": "./packages/", 5 + "noEmit": true 6 }, 7 + "exclude": [ 8 + "**/node_modules/**", 9 + "**/dist/**", 10 + "**/build/**" 11 + ] 12 }