this repo has no description

Compare changes

Choose any two refs to compare.

Changed files
+10675 -4107
.github
.vscode
img
nix
packages
browser
core
core-extensions
injector
node-preload
types
web-preload
scripts
-57
.eslintrc.json
··· 1 - { 2 - "extends": [ 3 - "eslint:recommended", 4 - "plugin:@typescript-eslint/recommended", 5 - "plugin:prettier/recommended", 6 - "plugin:react/recommended" 7 - ], 8 - "plugins": ["@typescript-eslint", "prettier", "react"], 9 - "parser": "@typescript-eslint/parser", 10 - "env": { 11 - "browser": true, 12 - "node": true 13 - }, 14 - "parserOptions": { 15 - "ecmaFeatures": { 16 - "jsx": true 17 - }, 18 - "ecmaVersion": "latest", 19 - "sourceType": "module" 20 - }, 21 - "rules": { 22 - "indent": "off", 23 - "eqeqeq": [ 24 - "error", 25 - "always", 26 - { 27 - "null": "ignore" 28 - } 29 - ], 30 - "quotes": [ 31 - "error", 32 - "double", 33 - { "avoidEscape": true, "allowTemplateLiterals": true } 34 - ], 35 - "@typescript-eslint/no-unused-vars": [ 36 - "error", 37 - { "args": "none", "varsIgnorePattern": "^_" } 38 - ], 39 - // Mostly so we don't forget to leave these in when committing 40 - "no-console": "error", 41 - "no-debugger": "error", 42 - 43 - // Quite honestly we're interacting with so much unknown within Discord that 44 - // this being enabled is a hinderance 45 - "@typescript-eslint/no-explicit-any": "off", 46 - 47 - "@typescript-eslint/no-var-requires": "off", 48 - 49 - // https://canary.discord.com/channels/1154257010532032512/1154275441788583996/1181760413231230976 50 - "no-unused-labels": "off" 51 - }, 52 - "settings": { 53 - "react": { 54 - "version": "18.2" 55 - } 56 - } 57 - }
···
+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
+4 -8
.github/workflows/lint.yml
··· 9 name: Lint commits 10 runs-on: ubuntu-latest 11 steps: 12 - - uses: actions/checkout@v3 13 - 14 - - uses: pnpm/action-setup@v2 15 - with: 16 - version: 9 17 - run_install: false 18 - - uses: actions/setup-node@v3 19 with: 20 - node-version: 18 21 cache: pnpm 22 23 - name: Install dependencies
··· 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
+9 -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: 9 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 ··· 45 echo "$(date +%s)" >> ./dist/ref 46 47 - name: Setup GitHub Pages 48 - uses: actions/configure-pages@v3 49 - name: Upload artifact 50 - uses: actions/upload-pages-artifact@v1 51 with: 52 path: ./dist 53 - name: Deploy to GitHub Pages 54 - 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 ··· 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: 9 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 }}
+5 -1
.gitignore
··· 3 dist.tar.gz 4 .DS_Store 5 eslint_report.json 6 - 7 # Nix 8 /result 9 *.drv
··· 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/
+1 -1
.prettierignore
··· 1 - pnpm-lock.yml
··· 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 }
-14
.vscode/tasks.json
··· 1 - { 2 - "version": "2.0.0", 3 - "tasks": [ 4 - { 5 - "label": "build", 6 - "type": "shell", 7 - "command": "pnpm run build", 8 - "group": { 9 - "kind": "build", 10 - "isDefault": true 11 - } 12 - } 13 - ] 14 - }
···
+4 -1
CHANGELOG.md
··· 1 - - core-extensions/contextMenu: Fix patches
··· 1 + ## Core 2 + 3 + - Updated mappings 4 + - Fixed using remapped paths as patch finds not working
+15 -4
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 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.
··· 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.
+127 -41
build.mjs
··· 13 14 const prod = process.env.NODE_ENV === "production"; 15 const watch = process.argv.includes("--watch"); 16 17 const external = [ 18 "electron", 19 "fs", 20 "path", 21 "module", 22 - "events", 23 - "original-fs", // wtf asar? 24 25 // Silence an esbuild warning 26 "./node-preload.js" ··· 65 name: "build-log", 66 setup(build) { 67 build.onEnd((result) => { 68 - console.log( 69 - `[${timeFormatter.format(new Date())}] [${tag}] build finished` 70 - ); 71 }); 72 } 73 }); 74 75 async function build(name, entry) { 76 - const outfile = path.join("./dist", name + ".js"); 77 78 const dropLabels = []; 79 - if (name !== "injector") dropLabels.push("injector"); 80 - if (name !== "node-preload") dropLabels.push("nodePreload"); 81 - if (name !== "web-preload") dropLabels.push("webPreload"); 82 83 const define = { 84 MOONLIGHT_ENV: `"${name}"`, 85 - MOONLIGHT_PROD: prod.toString() 86 }; 87 88 - for (const iterName of Object.keys(config)) { 89 const snake = iterName.replace(/-/g, "_").toUpperCase(); 90 define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); 91 } ··· 93 const nodeDependencies = ["glob"]; 94 const ignoredExternal = name === "web-preload" ? nodeDependencies : []; 95 96 /** @type {import("esbuild").BuildOptions} */ 97 const esbuildConfig = { 98 entryPoints: [entry], 99 outfile, 100 101 - format: "cjs", 102 - platform: name === "web-preload" ? "browser" : "node", 103 104 treeShaking: true, 105 bundle: true, ··· 112 dropLabels, 113 114 logLevel: "silent", 115 - plugins: [deduplicatedLogging, taggedBuildLog(name)] 116 }; 117 118 if (watch) { 119 const ctx = await esbuild.context(esbuildConfig); 120 await ctx.watch(); ··· 123 } 124 } 125 126 - async function buildExt(ext, side, copyManifest, fileExt) { 127 const outdir = path.join("./dist", "core-extensions", ext); 128 if (!fs.existsSync(outdir)) { 129 fs.mkdirSync(outdir, { recursive: true }); 130 } 131 132 - const entryPoints = [ 133 - `packages/core-extensions/src/${ext}/${side}.${fileExt}` 134 - ]; 135 136 const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`; 137 if (fs.existsSync(wpModulesDir) && side === "index") { 138 const wpModules = fs.opendirSync(wpModulesDir); 139 for await (const wpModule of wpModules) { 140 if (wpModule.isFile()) { 141 - entryPoints.push( 142 - `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}` 143 - ); 144 } else { 145 for (const fileExt of ["ts", "tsx"]) { 146 const path = `packages/core-extensions/src/${ext}/webpackModules/${wpModule.name}/index.${fileExt}`; ··· 168 } 169 }; 170 171 const esbuildConfig = { 172 entryPoints, 173 outdir, 174 175 - format: "cjs", 176 platform: "node", 177 178 treeShaking: true, ··· 186 }, 187 logLevel: "silent", 188 plugins: [ 189 - ...(copyManifest 190 ? [ 191 copyStaticFiles({ 192 - src: `./packages/core-extensions/src/${ext}/manifest.json`, 193 - dest: `./dist/core-extensions/${ext}/manifest.json` 194 }) 195 ] 196 : []), ··· 210 211 const promises = []; 212 213 - for (const [name, entry] of Object.entries(config)) { 214 - promises.push(build(name, entry)); 215 - } 216 - 217 - const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); 218 - for (const ext of coreExtensions) { 219 - let copiedManifest = false; 220 221 - for (const fileExt of ["ts", "tsx"]) { 222 - for (const type of ["index", "node", "host"]) { 223 - if ( 224 - fs.existsSync( 225 - `./packages/core-extensions/src/${ext}/${type}.${fileExt}` 226 - ) 227 - ) { 228 - promises.push(buildExt(ext, type, !copiedManifest, fileExt)); 229 - copiedManifest = true; 230 } 231 } 232 }
··· 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" ··· 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, ··· 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}`; ··· 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, ··· 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 : []), ··· 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 }
+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 + ];
+4 -73
flake.lock
··· 18 "type": "github" 19 } 20 }, 21 - "flake-utils_2": { 22 - "inputs": { 23 - "systems": "systems_2" 24 - }, 25 - "locked": { 26 - "lastModified": 1701680307, 27 - "narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=", 28 - "owner": "numtide", 29 - "repo": "flake-utils", 30 - "rev": "4022d587cbbfd70fe950c1e2083a02621806a725", 31 - "type": "github" 32 - }, 33 - "original": { 34 - "owner": "numtide", 35 - "repo": "flake-utils", 36 - "type": "github" 37 - } 38 - }, 39 "nixpkgs": { 40 "locked": { 41 - "lastModified": 1704295289, 42 - "narHash": "sha256-9WZDRfpMqCYL6g/HNWVvXF0hxdaAgwgIGeLYiOhmes8=", 43 "owner": "NixOS", 44 "repo": "nixpkgs", 45 - "rev": "b0b2c5445c64191fd8d0b31f2b1a34e45a64547d", 46 "type": "github" 47 }, 48 "original": { 49 "owner": "NixOS", 50 - "ref": "nixos-23.11", 51 - "repo": "nixpkgs", 52 - "type": "github" 53 - } 54 - }, 55 - "nixpkgs_2": { 56 - "locked": { 57 - "lastModified": 1702151865, 58 - "narHash": "sha256-9VAt19t6yQa7pHZLDbil/QctAgVsA66DLnzdRGqDisg=", 59 - "owner": "nixos", 60 - "repo": "nixpkgs", 61 - "rev": "666fc80e7b2afb570462423cb0e1cf1a3a34fedd", 62 - "type": "github" 63 - }, 64 - "original": { 65 - "owner": "nixos", 66 "ref": "nixos-unstable", 67 "repo": "nixpkgs", 68 "type": "github" 69 } 70 }, 71 - "pnpm2nix": { 72 - "inputs": { 73 - "flake-utils": "flake-utils_2", 74 - "nixpkgs": "nixpkgs_2" 75 - }, 76 - "locked": { 77 - "lastModified": 1709572248, 78 - "narHash": "sha256-WhaKD4cIvZLbwI2vZTkpH/oEeqGiyMvdW3bLi24P0eU=", 79 - "owner": "mojotech", 80 - "repo": "pnpm2nix-nzbr", 81 - "rev": "c3cfff81ea297cfb9dc18928652f375314dc287d", 82 - "type": "github" 83 - }, 84 - "original": { 85 - "owner": "mojotech", 86 - "repo": "pnpm2nix-nzbr", 87 - "type": "github" 88 - } 89 - }, 90 "root": { 91 "inputs": { 92 "flake-utils": "flake-utils", 93 - "nixpkgs": "nixpkgs", 94 - "pnpm2nix": "pnpm2nix" 95 } 96 }, 97 "systems": { 98 - "locked": { 99 - "lastModified": 1681028828, 100 - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 101 - "owner": "nix-systems", 102 - "repo": "default", 103 - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 104 - "type": "github" 105 - }, 106 - "original": { 107 - "owner": "nix-systems", 108 - "repo": "default", 109 - "type": "github" 110 - } 111 - }, 112 - "systems_2": { 113 "locked": { 114 "lastModified": 1681028828, 115 "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
··· 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=",
+5 -89
flake.nix
··· 2 description = "Yet another Discord mod"; 3 4 inputs = { 5 - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; 6 flake-utils.url = "github:numtide/flake-utils"; 7 - pnpm2nix.url = "github:mojotech/pnpm2nix-nzbr"; 8 }; 9 10 - outputs = { self, nixpkgs, flake-utils, pnpm2nix }: 11 - let 12 - mkMoonlight = { pkgs, mkPnpmPackage }: 13 - mkPnpmPackage rec { 14 - workspace = ./.; 15 - src = ./.; 16 - components = [ 17 - "packages/core" 18 - "packages/core-extensions" 19 - "packages/injector" 20 - "packages/node-preload" 21 - "packages/types" 22 - "packages/web-preload" 23 - ]; 24 - distDirs = [ "dist" ]; 25 - 26 - copyNodeModules = true; 27 - buildPhase = "pnpm run build"; 28 - installPhase = "cp -r dist $out"; 29 - 30 - meta = with pkgs.lib; { 31 - description = "Yet another Discord mod"; 32 - homepage = "https://moonlight-mod.github.io/"; 33 - license = licenses.lgpl3; 34 - maintainers = with maintainers; [ notnite ]; 35 - }; 36 - }; 37 - 38 - nameTable = { 39 - discord = "Discord"; 40 - discord-ptb = "DiscordPTB"; 41 - discord-canary = "DiscordCanary"; 42 - discord-development = "DiscordDevelopment"; 43 - }; 44 - 45 - darwinNameTable = { 46 - discord = "Discord"; 47 - discord-ptb = "Discord PTB"; 48 - discord-canary = "Discord Canary"; 49 - discord-development = "Discord Development"; 50 - }; 51 - 52 - mkOverride = prev: moonlight: name: 53 - let discord = prev.${name}; 54 - in discord.overrideAttrs (old: { 55 - installPhase = let 56 - folderName = nameTable.${name}; 57 - darwinFolderName = darwinNameTable.${name}; 58 - 59 - injected = '' 60 - require("${moonlight}/injector").inject( 61 - require("path").join(__dirname, "../_app.asar") 62 - ); 63 - ''; 64 - 65 - packageJson = '' 66 - {"name":"discord","main":"./injector.js","private":true} 67 - ''; 68 - 69 - in old.installPhase + "\n" + '' 70 - resources="$out/opt/${folderName}/resources" 71 - if [ ! -d "$resources" ]; then 72 - resources="$out/Applications/${darwinFolderName}.app/Contents/Resources" 73 - fi 74 - 75 - mv "$resources/app.asar" "$resources/_app.asar" 76 - mkdir -p "$resources/app" 77 - 78 - cat > "$resources/app/injector.js" <<EOF 79 - ${injected} 80 - EOF 81 - 82 - echo '${packageJson}' > "$resources/app/package.json" 83 - ''; 84 - }); 85 - 86 - overlay = final: prev: rec { 87 - moonlight-mod = mkMoonlight { 88 - pkgs = final; 89 - mkPnpmPackage = pnpm2nix.packages.${final.system}.mkPnpmPackage; 90 - }; 91 - discord = mkOverride prev moonlight-mod "discord"; 92 - discord-ptb = mkOverride prev moonlight-mod "discord-ptb"; 93 - discord-canary = mkOverride prev moonlight-mod "discord-canary"; 94 - discord-development = 95 - mkOverride prev moonlight-mod "discord-development"; 96 - }; 97 in flake-utils.lib.eachDefaultSystem (system: 98 let 99 pkgs = import nixpkgs { ··· 102 overlays = [ overlay ]; 103 }; 104 in { 105 packages.default = pkgs.moonlight-mod; 106 packages.moonlight-mod = pkgs.moonlight-mod; 107 ··· 111 packages.discord-development = pkgs.discord-development; 112 }) // { 113 overlays.default = overlay; 114 }; 115 }
··· 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 { ··· 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 ··· 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.

+51
nix/default.nix
···
··· 1 + { 2 + lib, 3 + stdenv, 4 + nodejs_22, 5 + pnpm_10, 6 + }: 7 + 8 + stdenv.mkDerivation (finalAttrs: { 9 + pname = "moonlight"; 10 + version = (builtins.fromJSON (builtins.readFile ./../package.json)).version; 11 + 12 + src = ./..; 13 + 14 + nativeBuildInputs = [ 15 + nodejs_22 16 + pnpm_10.configHook 17 + ]; 18 + 19 + pnpmDeps = pnpm_10.fetchDeps { 20 + inherit (finalAttrs) pname version src; 21 + hash = "sha256-I+zRCUqJabpGJRFBGW0NrM9xzyzeCjioF54zlCpynBU="; 22 + }; 23 + 24 + env = { 25 + NODE_ENV = "production"; 26 + MOONLIGHT_VERSION = "v${finalAttrs.version}"; 27 + }; 28 + 29 + buildPhase = '' 30 + runHook preBuild 31 + 32 + pnpm run build 33 + 34 + runHook postBuild 35 + ''; 36 + 37 + installPhase = '' 38 + runHook preInstall 39 + 40 + cp -r dist $out 41 + 42 + runHook postInstall 43 + ''; 44 + 45 + meta = with lib; { 46 + description = "Yet another Discord mod"; 47 + homepage = "https://moonlight-mod.github.io/"; 48 + license = licenses.lgpl3; 49 + maintainers = with maintainers; [ notnite ]; 50 + }; 51 + })
+56
nix/home-manager.nix
···
··· 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 + }
+26 -16
package.json
··· 1 { 2 "name": "moonlight", 3 - "version": "1.0.10", 4 "description": "Yet another Discord mod", 5 - "homepage": "https://moonlight-mod.github.io/", 6 "license": "LGPL-3.0-or-later", 7 "repository": { 8 "type": "git", 9 "url": "git+https://github.com/moonlight-mod/moonlight.git" ··· 11 "bugs": { 12 "url": "https://github.com/moonlight-mod/moonlight/issues" 13 }, 14 "scripts": { 15 "build": "node build.mjs", 16 "dev": "node build.mjs --watch", 17 "lint": "eslint packages", 18 - "lint:fix": "eslint packages", 19 - "lint:report": "eslint --output-file eslint_report.json --format json packages", 20 "typecheck": "tsc --noEmit", 21 "check": "pnpm run lint && pnpm run typecheck", 22 - "prepare": "husky install" 23 }, 24 "devDependencies": { 25 - "@typescript-eslint/eslint-plugin": "^6.13.2", 26 - "@typescript-eslint/parser": "^6.13.2", 27 - "esbuild": "^0.19.3", 28 - "esbuild-copy-static-files": "^0.1.0", 29 - "eslint": "^8.55.0", 30 - "eslint-config-prettier": "^9.1.0", 31 - "eslint-plugin-prettier": "^5.0.1", 32 - "eslint-plugin-react": "^7.33.2", 33 - "husky": "^8.0.3", 34 - "prettier": "^3.1.0", 35 - "typescript": "^5.3.2" 36 } 37 }
··· 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", 10 "url": "git+https://github.com/moonlight-mod/moonlight.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 + }
+28
packages/browser/manifestv2.json
···
··· 1 + { 2 + "$schema": "https://json.schemastore.org/chrome-manifest", 3 + "manifest_version": 2, 4 + "name": "moonlight", 5 + "description": "Yet another Discord mod", 6 + "version": "1.3.14", 7 + "permissions": [ 8 + "webRequest", 9 + "webRequestBlocking", 10 + "scripting", 11 + "webNavigation", 12 + "https://*.discord.com/*", 13 + "https://*.discordapp.com/*", 14 + "https://moonlight-mod.github.io/*", 15 + "https://api.github.com/*" 16 + ], 17 + "background": { 18 + "scripts": ["background.js"] 19 + }, 20 + "content_scripts": [ 21 + { 22 + "js": ["index.js"], 23 + "matches": ["https://*.discord.com/*", "https://*.discordapp.com/*"], 24 + "run_at": "document_start", 25 + "world": "MAIN" 26 + } 27 + ] 28 + }
+19
packages/browser/modifyResponseHeaders.json
···
··· 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 + );
+157
packages/browser/src/index.ts
···
··· 1 + import "@moonlight-mod/web-preload"; 2 + import { readConfig, writeConfig } from "@moonlight-mod/core/config"; 3 + import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; 4 + import { getExtensions } from "@moonlight-mod/core/extension"; 5 + import { loadExtensions } from "@moonlight-mod/core/extension/loader"; 6 + import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types"; 7 + import { getConfig, getConfigOption, getManifest, setConfigOption } from "@moonlight-mod/core/util/config"; 8 + import { IndexedDB } from "@zenfs/dom"; 9 + import { configureSingle } from "@zenfs/core"; 10 + import * as fs from "@zenfs/core/promises"; 11 + import { NodeEventPayloads, NodeEventType } from "@moonlight-mod/types/core/event"; 12 + import { createEventEmitter } from "@moonlight-mod/core/util/event"; 13 + 14 + function getParts(path: string) { 15 + if (path.startsWith("/")) path = path.substring(1); 16 + return path.split("/"); 17 + } 18 + 19 + window._moonlightBrowserInit = async () => { 20 + delete window._moonlightBrowserInit; 21 + 22 + // Set up a virtual filesystem with IndexedDB 23 + await configureSingle({ 24 + backend: IndexedDB, 25 + storeName: "moonlight-fs" 26 + }); 27 + 28 + window.moonlightNodeSandboxed = { 29 + fs: { 30 + async readFile(path) { 31 + return new Uint8Array(await fs.readFile(path)); 32 + }, 33 + async readFileString(path) { 34 + const file = await this.readFile(path); 35 + return new TextDecoder().decode(file); 36 + }, 37 + async writeFile(path, data) { 38 + await fs.writeFile(path, data); 39 + }, 40 + async writeFileString(path, data) { 41 + const file = new TextEncoder().encode(data); 42 + await this.writeFile(path, file); 43 + }, 44 + async unlink(path) { 45 + await fs.unlink(path); 46 + }, 47 + 48 + async readdir(path) { 49 + return await fs.readdir(path); 50 + }, 51 + async mkdir(path) { 52 + const parts = getParts(path); 53 + for (let i = 0; i < parts.length; i++) { 54 + const path = this.join(...parts.slice(0, i + 1)); 55 + if (!(await this.exists(path))) await fs.mkdir(path); 56 + } 57 + }, 58 + 59 + async rmdir(path) { 60 + const entries = await this.readdir(path); 61 + 62 + for (const entry of entries) { 63 + const fullPath = this.join(path, entry); 64 + const isFile = await this.isFile(fullPath); 65 + if (isFile) { 66 + await this.unlink(fullPath); 67 + } else { 68 + await this.rmdir(fullPath); 69 + } 70 + } 71 + 72 + await fs.rmdir(path); 73 + }, 74 + 75 + async exists(path) { 76 + return await fs.exists(path); 77 + }, 78 + async isFile(path) { 79 + return (await fs.stat(path)).isFile(); 80 + }, 81 + async isDir(path) { 82 + return (await fs.stat(path)).isDirectory(); 83 + }, 84 + 85 + join(...parts) { 86 + let str = parts.join("/"); 87 + if (!str.startsWith("/")) str = "/" + str; 88 + return str; 89 + }, 90 + dirname(path) { 91 + const parts = getParts(path); 92 + return "/" + parts.slice(0, parts.length - 1).join("/"); 93 + } 94 + }, 95 + // TODO 96 + addCors(url) {}, 97 + addBlocked(url) {} 98 + }; 99 + 100 + // Actual loading begins here 101 + let config = await readConfig(); 102 + initLogger(config); 103 + 104 + const extensions = await getExtensions(); 105 + const processedExtensions = await loadExtensions(extensions); 106 + 107 + const moonlightNode: MoonlightNode = { 108 + get config() { 109 + return config; 110 + }, 111 + extensions, 112 + processedExtensions, 113 + nativesCache: {}, 114 + isBrowser: true, 115 + events: createEventEmitter<NodeEventType, NodeEventPayloads>(), 116 + 117 + version: MOONLIGHT_VERSION, 118 + branch: MOONLIGHT_BRANCH as MoonlightBranch, 119 + 120 + getConfig(ext) { 121 + return getConfig(ext, config); 122 + }, 123 + getConfigOption(ext, name) { 124 + const manifest = getManifest(extensions, ext); 125 + return getConfigOption(ext, name, config, manifest?.settings); 126 + }, 127 + async setConfigOption(ext, name, value) { 128 + setConfigOption(config, ext, name, value); 129 + await this.writeConfig(config); 130 + }, 131 + 132 + getNatives: () => {}, 133 + getLogger: (id: string) => { 134 + return new Logger(id); 135 + }, 136 + 137 + getMoonlightDir() { 138 + return "/"; 139 + }, 140 + getExtensionDir: (ext: string) => { 141 + return `/extensions/${ext}`; 142 + }, 143 + 144 + async writeConfig(newConfig) { 145 + await writeConfig(newConfig); 146 + config = newConfig; 147 + this.events.dispatchEvent(NodeEventType.ConfigSaved, newConfig); 148 + } 149 + }; 150 + 151 + Object.assign(window, { 152 + moonlightNode 153 + }); 154 + 155 + // This is set by web-preload for us 156 + await window._moonlightWebLoad!(); 157 + };
+7
packages/browser/tsconfig.json
···
··· 1 + { 2 + "extends": "../../tsconfig.json", 3 + "compilerOptions": { 4 + "lib": ["DOM", "ESNext", "ESNext.AsyncIterable"], 5 + "module": "ES2022" 6 + } 7 + }
+7
packages/core/package.json
··· 4 "exports": { 5 "./*": "./src/*.ts" 6 }, 7 "dependencies": { 8 "@moonlight-mod/types": "workspace:*" 9 }
··· 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 }
+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 + }
+31 -31
packages/core/src/config.ts
··· 1 import { Config } from "@moonlight-mod/types"; 2 - import requireImport from "./util/import"; 3 import { getConfigPath } from "./util/data"; 4 5 const defaultConfig: Config = { 6 extensions: { 7 moonbase: true, 8 disableSentry: true, 9 noTrack: true, 10 noHideToken: true 11 }, 12 - repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"] 13 }; 14 15 - export function writeConfig(config: Config) { 16 - const fs = requireImport("fs"); 17 - const configPath = getConfigPath(); 18 - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); 19 - } 20 - 21 - function readConfigNode(): Config { 22 - const fs = requireImport("fs"); 23 - const configPath = getConfigPath(); 24 - 25 - if (!fs.existsSync(configPath)) { 26 - writeConfig(defaultConfig); 27 - return defaultConfig; 28 } 29 - 30 - let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); 31 - 32 - // Assign the default values if they don't exist (newly added) 33 - config = { ...defaultConfig, ...config }; 34 - writeConfig(config); 35 - 36 - return config; 37 } 38 39 - export function readConfig(): Config { 40 webPreload: { 41 return moonlightNode.config; 42 } 43 44 - nodePreload: { 45 - return readConfigNode(); 46 - } 47 48 - injector: { 49 - return readConfigNode(); 50 } 51 - 52 - throw new Error("Called readConfig() in an impossible environment"); 53 }
··· 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 + }
+111 -70
packages/core/src/extension/loader.ts
··· 2 ExtensionWebExports, 3 DetectedExtension, 4 ProcessedExtensions, 5 - WebpackModuleFunc 6 } from "@moonlight-mod/types"; 7 import { readConfig } from "../config"; 8 import Logger from "../util/logger"; ··· 10 import calculateDependencies from "../util/dependency"; 11 import { createEventEmitter } from "../util/event"; 12 import { registerStyles } from "../styles"; 13 14 const logger = new Logger("core/extension/loader"); 15 16 - async function loadExt(ext: DetectedExtension) { 17 - webPreload: { 18 - if (ext.scripts.web != null) { 19 - const source = 20 - ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; 21 - const fn = new Function("require", "module", "exports", source); 22 23 - const module = { id: ext.id, exports: {} }; 24 - fn.apply(window, [ 25 - () => { 26 - logger.warn("Attempted to require() from web"); 27 - }, 28 - module, 29 - module.exports 30 - ]); 31 32 - const exports: ExtensionWebExports = module.exports; 33 - if (exports.patches != null) { 34 - let idx = 0; 35 - for (const patch of exports.patches) { 36 - if (Array.isArray(patch.replace)) { 37 - for (const replacement of patch.replace) { 38 - const newPatch = Object.assign({}, patch, { 39 - replace: replacement 40 - }); 41 42 - registerPatch({ ...newPatch, ext: ext.id, id: idx }); 43 - idx++; 44 - } 45 - } else { 46 - registerPatch({ ...patch, ext: ext.id, id: idx }); 47 - idx++; 48 - } 49 } 50 } 51 52 - if (exports.webpackModules != null) { 53 - for (const [name, wp] of Object.entries(exports.webpackModules)) { 54 - if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { 55 - const func = new Function( 56 - "module", 57 - "exports", 58 - "require", 59 - ext.scripts.webpackModules[name]! 60 - ) as WebpackModuleFunc; 61 - registerWebpackModule({ 62 - ...wp, 63 - ext: ext.id, 64 - id: name, 65 - run: func 66 - }); 67 - } else { 68 - registerWebpackModule({ ...wp, ext: ext.id, id: name }); 69 - } 70 } 71 } 72 73 - if (exports.styles != null) { 74 - registerStyles( 75 - exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) 76 - ); 77 - } 78 } 79 } 80 ··· 100 } 101 } 102 103 /* 104 This function resolves extensions and loads them, split into a few stages: 105 ··· 114 extensions fires an event on completion, which allows us to await the loading 115 of another extension, resolving dependencies & load order effectively. 116 */ 117 - export async function loadExtensions( 118 - exts: DetectedExtension[] 119 - ): Promise<ProcessedExtensions> { 120 - const config = readConfig(); 121 const items = exts 122 .map((ext) => { 123 return { ··· 155 }; 156 } 157 158 - export async function loadProcessedExtensions({ 159 - extensions, 160 - dependencyGraph 161 - }: ProcessedExtensions) { 162 - const eventEmitter = createEventEmitter(); 163 const finished: Set<string> = new Set(); 164 165 logger.trace( ··· 181 } 182 183 function done() { 184 - eventEmitter.removeEventListener("ext-ready", cb); 185 r(); 186 } 187 188 - eventEmitter.addEventListener("ext-ready", cb); 189 if (finished.has(dep)) done(); 190 }) 191 ); 192 193 if (waitPromises.length > 0) { 194 - logger.debug( 195 - `Waiting on ${waitPromises.length} dependencies for "${ext.id}"` 196 - ); 197 await Promise.all(waitPromises); 198 } 199 ··· 201 await loadExt(ext); 202 203 finished.add(ext.id); 204 - eventEmitter.dispatchEvent("ext-ready", ext.id); 205 logger.debug(`Loaded "${ext.id}"`); 206 } 207 208 - webPreload: { 209 for (const ext of extensions) { 210 moonlight.enabledExtensions.add(ext.id); 211 }
··· 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"; ··· 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 ··· 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 { ··· 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( ··· 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 }
+128 -85
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 findManifests(dir: string): string[] { 12 - const fs = requireImport("fs"); 13 - const path = requireImport("path"); 14 const ret = []; 15 16 - if (fs.existsSync(dir)) { 17 - for (const file of fs.readdirSync(dir)) { 18 if (file === "manifest.json") { 19 - ret.push(path.join(dir, file)); 20 } 21 22 - if (fs.statSync(path.join(dir, file)).isDirectory()) { 23 - ret.push(...findManifests(path.join(dir, file))); 24 } 25 } 26 } ··· 28 return ret; 29 } 30 31 - function loadDetectedExtensions( 32 dir: string, 33 - type: ExtensionLoadSource 34 - ): DetectedExtension[] { 35 - const fs = requireImport("fs"); 36 - const path = requireImport("path"); 37 const ret: DetectedExtension[] = []; 38 39 - const manifests = findManifests(dir); 40 for (const manifestPath of manifests) { 41 - if (!fs.existsSync(manifestPath)) continue; 42 - const dir = path.dirname(manifestPath); 43 44 - const manifest: ExtensionManifest = JSON.parse( 45 - fs.readFileSync(manifestPath, "utf8") 46 - ); 47 48 - const webPath = path.join(dir, "index.js"); 49 - const nodePath = path.join(dir, "node.js"); 50 - const hostPath = path.join(dir, "host.js"); 51 52 - // if none exist (empty manifest) don't give a shit 53 - if ( 54 - !fs.existsSync(webPath) && 55 - !fs.existsSync(nodePath) && 56 - !fs.existsSync(hostPath) 57 - ) { 58 - continue; 59 } 60 61 - const web = fs.existsSync(webPath) 62 - ? fs.readFileSync(webPath, "utf8") 63 - : undefined; 64 65 - let url: string | undefined = undefined; 66 - const urlPath = path.join(dir, constants.repoUrlFile); 67 - if (type === ExtensionLoadSource.Normal && fs.existsSync(urlPath)) { 68 - url = fs.readFileSync(urlPath, "utf8"); 69 - } 70 71 - const wpModules: Record<string, string> = {}; 72 - const wpModulesPath = path.join(dir, "webpackModules"); 73 - if (fs.existsSync(wpModulesPath)) { 74 - const wpModulesFile = fs.readdirSync(wpModulesPath); 75 76 - for (const wpModuleFile of wpModulesFile) { 77 - if (wpModuleFile.endsWith(".js")) { 78 - wpModules[wpModuleFile.replace(".js", "")] = fs.readFileSync( 79 - path.join(wpModulesPath, wpModuleFile), 80 - "utf8" 81 - ); 82 - } 83 } 84 } 85 ··· 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: fs.existsSync(nodePath) ? nodePath : undefined, 98 - hostPath: fs.existsSync(hostPath) ? hostPath : undefined 99 } 100 }); 101 } 102 103 - return ret; 104 - } 105 - 106 - function getExtensionsNative(): DetectedExtension[] { 107 - const config = readConfig(); 108 - const res = []; 109 - 110 - res.push( 111 - ...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core) 112 - ); 113 - 114 - res.push( 115 - ...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal) 116 - ); 117 - 118 - for (const devSearchPath of config.devSearchPaths ?? []) { 119 - res.push( 120 - ...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer) 121 - ); 122 } 123 124 - return res; 125 } 126 127 - export function getExtensions(): DetectedExtension[] { 128 webPreload: { 129 return moonlightNode.extensions; 130 } 131 132 - nodePreload: { 133 - return getExtensionsNative(); 134 } 135 136 - injector: { 137 - return getExtensionsNative(); 138 } 139 140 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 } ··· 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 ··· 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");
+53
packages/core/src/fs.ts
···
··· 1 + import type { MoonlightFS } from "@moonlight-mod/types"; 2 + import requireImport from "./util/import"; 3 + 4 + export default function createFS(): MoonlightFS { 5 + const fs = requireImport("fs"); 6 + const path = requireImport("path"); 7 + 8 + return { 9 + async readFile(path) { 10 + const file = fs.readFileSync(path); 11 + return new Uint8Array(file); 12 + }, 13 + async readFileString(path) { 14 + return fs.readFileSync(path, "utf8"); 15 + }, 16 + async writeFile(path, data) { 17 + fs.writeFileSync(path, Buffer.from(data)); 18 + }, 19 + async writeFileString(path, data) { 20 + fs.writeFileSync(path, data, "utf8"); 21 + }, 22 + async unlink(path) { 23 + fs.unlinkSync(path); 24 + }, 25 + 26 + async readdir(path) { 27 + return fs.readdirSync(path); 28 + }, 29 + async mkdir(path) { 30 + fs.mkdirSync(path, { recursive: true }); 31 + }, 32 + async rmdir(path) { 33 + fs.rmSync(path, { recursive: true }); 34 + }, 35 + 36 + async exists(path) { 37 + return fs.existsSync(path); 38 + }, 39 + async isFile(path) { 40 + return fs.statSync(path).isFile(); 41 + }, 42 + async isDir(path) { 43 + return fs.statSync(path).isDirectory(); 44 + }, 45 + 46 + join(...parts) { 47 + return path.join(...parts); 48 + }, 49 + dirname(dir) { 50 + return path.dirname(dir); 51 + } 52 + }; 53 + }
+238 -106
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 const patches: IdentifiedPatch[] = []; 19 let webpackModules: Set<IdentifiedWebpackModule> = new Set(); 20 21 export function registerPatch(patch: IdentifiedPatch) { 22 patches.push(patch); 23 moonlight.unpatched.add(patch); 24 } ··· 30 } 31 } 32 33 /* 34 The patching system functions by matching a string or regex against the 35 .toString()'d copy of a Webpack module. When a patch happens, we reconstruct ··· 42 const moduleCache: Record<string, string> = {}; 43 const patched: Record<string, Array<string>> = {}; 44 45 function patchModules(entry: WebpackJsonpEntry[1]) { 46 for (const [id, func] of Object.entries(entry)) { 47 - let moduleString = Object.prototype.hasOwnProperty.call(moduleCache, id) 48 - ? moduleCache[id] 49 - : func.toString().replace(/\n/g, ""); 50 51 for (let i = 0; i < patches.length; i++) { 52 const patch = patches[i]; 53 if (patch.prerequisite != null && !patch.prerequisite()) { 54 continue; 55 } 56 ··· 59 patch.find.lastIndex = 0; 60 } 61 62 - // indexOf is faster than includes by 0.25% lmao 63 - const match = 64 - typeof patch.find === "string" 65 - ? moduleString.indexOf(patch.find) !== -1 66 - : patch.find.test(moduleString); 67 68 // Global regexes apply to all modules 69 - const shouldRemove = 70 - typeof patch.find === "string" ? true : !patch.find.global; 71 72 if (match) { 73 - moonlight.unpatched.delete(patch); 74 75 - // We ensured all arrays get turned into normal PatchReplace objects on register 76 - const replace = patch.replace as PatchReplace; 77 78 - if ( 79 - replace.type === undefined || 80 - replace.type === PatchReplaceType.Normal 81 - ) { 82 - // Add support for \i to match rspack's minified names 83 - if (typeof replace.match !== "string") { 84 - replace.match = new RegExp( 85 - replace.match.source.replace(/\\i/g, "[A-Za-z_$][\\w$]*"), 86 - replace.match.flags 87 - ); 88 - } 89 - // tsc fails to detect the overloads for this, so I'll just do this 90 - // Verbose, but it works 91 - let replaced; 92 - if (typeof replace.replacement === "string") { 93 - replaced = moduleString.replace(replace.match, replace.replacement); 94 - } else { 95 - replaced = moduleString.replace(replace.match, replace.replacement); 96 - } 97 98 - if (replaced === moduleString) { 99 - logger.warn("Patch replacement failed", id, patch); 100 - continue; 101 } 102 103 - // Store what extensions patched what modules for easier debugging 104 - patched[id] = patched[id] || []; 105 - patched[id].push(`${patch.ext}#${patch.id}`); 106 107 - // Webpack module arguments are minified, so we replace them with consistent names 108 - // We have to wrap it so things don't break, though 109 - const patchedStr = patched[id].sort().join(", "); 110 111 - const wrapped = 112 - `(${replaced}).apply(this, arguments)\n` + 113 - `// Patched by moonlight: ${patchedStr}\n` + 114 - `//# sourceURL=Webpack-Module-${id}`; 115 116 - try { 117 - const func = new Function( 118 - "module", 119 - "exports", 120 - "require", 121 - wrapped 122 - ) as WebpackModuleFunc; 123 - entry[id] = func; 124 - entry[id].__moonlight = true; 125 - moduleString = replaced; 126 - } catch (e) { 127 - logger.warn("Error constructing function for patch", patch, e); 128 - patched[id].pop(); 129 } 130 - } else if (replace.type === PatchReplaceType.Module) { 131 - // Directly replace the module with a new one 132 - const newModule = replace.replacement(moduleString); 133 - entry[id] = newModule; 134 - entry[id].__moonlight = true; 135 - moduleString = 136 - newModule.toString().replace(/\n/g, "") + 137 - `//# sourceURL=Webpack-Module-${id}`; 138 - } 139 - 140 - if (shouldRemove) { 141 - patches.splice(i--, 1); 142 } 143 } 144 } 145 146 if (moonlightNode.config.patchAll === true) { 147 - if ( 148 - (typeof id !== "string" || !id.includes("_")) && 149 - !entry[id].__moonlight 150 - ) { 151 - const wrapped = 152 - `(${moduleString}).apply(this, arguments)\n` + 153 - `//# sourceURL=Webpack-Module-${id}`; 154 - entry[id] = new Function( 155 - "module", 156 - "exports", 157 - "require", 158 - wrapped 159 - ) as WebpackModuleFunc; 160 entry[id].__moonlight = true; 161 } 162 } 163 164 moduleCache[id] = moduleString; 165 } 166 } ··· 172 */ 173 let chunkId = Number.MAX_SAFE_INTEGER; 174 175 function handleModuleDependencies() { 176 const modules = Array.from(webpackModules.values()); 177 178 - const dependencies: Dependency<string, IdentifiedWebpackModule>[] = 179 - modules.map((wp) => { 180 - return { 181 - id: `${wp.ext}_${wp.id}`, 182 - data: wp 183 - }; 184 - }); 185 186 const [sorted, _] = calculateDependencies(dependencies, { 187 fetchDep: (id) => { 188 - return modules.find((x) => id === `${x.ext}_${x.id}`) ?? null; 189 }, 190 191 getDeps: (item) => { 192 const deps = item.data?.dependencies ?? []; 193 return ( 194 deps.filter( 195 - (dep) => !(dep instanceof RegExp || typeof dep === "string") 196 ) as ExplicitExtensionDependency[] 197 - ).map((x) => `${x.ext}_${x.id}`); 198 } 199 }); 200 ··· 210 for (const [_modId, mod] of Object.entries(entry)) { 211 const modStr = mod.toString(); 212 for (const wpModule of webpackModules) { 213 - const id = wpModule.ext + "_" + wpModule.id; 214 if (wpModule.dependencies) { 215 const deps = new Set(wpModule.dependencies); 216 ··· 223 } else if (dep instanceof RegExp) { 224 if (dep.test(modStr)) deps.delete(dep); 225 } else if ( 226 - injectedWpModules.find( 227 - (x) => x.ext === dep.ext && x.id === dep.id 228 - ) 229 ) { 230 deps.delete(dep); 231 } 232 } 233 234 if (deps.size !== 0) { 235 - wpModule.dependencies = Array.from(deps); 236 continue; 237 } 238 - 239 - wpModule.dependencies = Array.from(deps); 240 } 241 } 242 ··· 249 if (wpModule.run) { 250 modules[id] = wpModule.run; 251 wpModule.run.__moonlight = true; 252 } 253 - if (wpModule.entrypoint) entrypoints.push(id); 254 } 255 if (!webpackModules.size) break; 256 } 257 258 if (inject) { ··· 260 window.webpackChunkdiscord_app.push([ 261 [--chunkId], 262 modules, 263 - (require: typeof WebpackRequire) => entrypoints.map(require) 264 ]); 265 } 266 } ··· 269 interface Window { 270 webpackChunkdiscord_app: WebpackJsonp; 271 } 272 } 273 274 /* ··· 282 export async function installWebpackPatcher() { 283 await handleModuleDependencies(); 284 285 let realWebpackJsonp: WebpackJsonp | null = null; 286 Object.defineProperty(window, "webpackChunkdiscord_app", { 287 set: (jsonp: WebpackJsonp) => { ··· 293 const realPush = jsonp.push; 294 if (jsonp.push.__moonlight !== true) { 295 jsonp.push = (items) => { 296 patchModules(items[1]); 297 298 try { ··· 335 set(modules: any) { 336 const { stack } = new Error(); 337 if (stack!.includes("/assets/") && !Array.isArray(modules)) { 338 patchModules(modules); 339 - if (!window.webpackChunkdiscord_app) 340 - window.webpackChunkdiscord_app = []; 341 injectModules(modules); 342 } 343
··· 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 } ··· 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 /* 58 The patching system functions by matching a string or regex against the 59 .toString()'d copy of a Webpack module. When a patch happens, we reconstruct ··· 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; 237 } 238 } ··· 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 ··· 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 ··· 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 ··· 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) { ··· 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 } ··· 375 interface Window { 376 webpackChunkdiscord_app: WebpackJsonp; 377 } 378 + } 379 + 380 + function moduleSourceGetter(id: string) { 381 + return moduleCache[id] ?? null; 382 } 383 384 /* ··· 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 { ··· 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
+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 + }
+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 + }
+25 -28
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 electron = require("electron"); 6 - const fs = requireImport("fs"); 7 - const path = requireImport("path"); 8 9 let appData = ""; 10 injector: { ··· 15 appData = electron.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 let configPath = ""; 35 36 - const buildInfoPath = path.join(process.resourcesPath, "build_info.json"); 37 - if (!fs.existsSync(buildInfoPath)) { 38 - configPath = path.join(dir, "desktop.json"); 39 } else { 40 - const buildInfo: BuildInfo = JSON.parse( 41 - fs.readFileSync(buildInfoPath, "utf8") 42 - ); 43 - configPath = path.join(dir, buildInfo.releaseChannel + ".json"); 44 } 45 46 return configPath; 47 } 48 49 - function getPathFromMoonlight(...names: string[]): string { 50 - const dir = getMoonlightDir(); 51 - const fs = requireImport("fs"); 52 - const path = requireImport("path"); 53 54 - const target = path.join(dir, ...names); 55 - if (!fs.existsSync(target)) fs.mkdirSync(target); 56 57 return target; 58 } 59 60 - export function getExtensionsPath(): string { 61 - return getPathFromMoonlight(constants.extensionsDir); 62 } 63 64 export function getCoreExtensionsPath(): string { 65 - const path = requireImport("path"); 66 - const a = path.join(__dirname, constants.coreExtensionsDir); 67 - return a; 68 }
··· 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: { ··· 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 }
+4 -14
packages/core/src/util/dependency.ts
··· 35 const fullDeps: Set<T> = new Set(); 36 let failed = false; 37 38 - // eslint-disable-next-line no-inner-declarations 39 function resolveDeps(id: T, root: boolean) { 40 if (id === item.id && !root) { 41 logger.warn(`Circular dependency detected: "${item.id}"`); ··· 113 logger.trace("Enabled stage", itemsOrig); 114 const implicitlyEnabled: T[] = []; 115 116 - // eslint-disable-next-line no-inner-declarations 117 function validateDeps(dep: Dependency<T, D>) { 118 if (getEnabled!(dep)) { 119 const deps = dependencyGraphOrig.get(dep.id)!; ··· 122 validateDeps({ id, data }); 123 } 124 } else { 125 - const dependsOnMe = Array.from(dependencyGraphOrig.entries()).filter( 126 - ([, v]) => v?.has(dep.id) 127 - ); 128 129 if (dependsOnMe.length > 0) { 130 logger.debug("Implicitly enabling dependency", dep.id); ··· 134 } 135 136 for (const dep of itemsOrig) validateDeps(dep); 137 - itemsOrig = itemsOrig.filter( 138 - (x) => getEnabled(x) || implicitlyEnabled.includes(x.id) 139 - ); 140 } 141 142 if (getIncompatible != null) { ··· 176 dependencyGraph.set(item.id, new Set(dependencyGraph.get(item.id))); 177 } 178 179 - while ( 180 - Array.from(dependencyGraph.values()).filter((x) => x != null).length > 0 181 - ) { 182 - const noDependents = items.filter( 183 - (e) => dependencyGraph.get(e.id)?.size === 0 184 - ); 185 186 if (noDependents.length === 0) { 187 logger.warn("Stuck dependency graph detected", dependencyGraph);
··· 35 const fullDeps: Set<T> = new Set(); 36 let failed = false; 37 38 function resolveDeps(id: T, root: boolean) { 39 if (id === item.id && !root) { 40 logger.warn(`Circular dependency detected: "${item.id}"`); ··· 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)!; ··· 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); ··· 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) { ··· 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 -5
packages/core/src/util/import.ts
··· 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>( 21 - type: T 22 - ): Awaited<ImportTypes[T]> { 23 return require(type); 24 }
··· 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 }
+12 -16
packages/core/src/util/logger.ts
··· 1 /* eslint-disable no-console */ 2 import { LogLevel } from "@moonlight-mod/types/logger"; 3 - import { readConfig } from "../config"; 4 5 const colors = { 6 [LogLevel.SILLY]: "#EDD3E9", ··· 11 [LogLevel.ERROR]: "#FF0000" 12 }; 13 14 - const config = readConfig(); 15 let maxLevel = LogLevel.INFO; 16 - if (config.loggerLevel != null) { 17 - const enumValue = 18 - LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; 19 - if (enumValue != null) { 20 - maxLevel = enumValue; 21 - } 22 - } 23 24 export default class Logger { 25 private name: string; ··· 57 const logLevel = LogLevel[level].toUpperCase(); 58 if (maxLevel > level) return; 59 60 - if (MOONLIGHT_WEB_PRELOAD) { 61 - args = [ 62 - `%c[${logLevel}]`, 63 - `background-color: ${colors[level]}; color: #FFFFFF;`, 64 - `[${this.name}]`, 65 - ...obj 66 - ]; 67 } else { 68 args = [`[${logLevel}]`, `[${this.name}]`, ...obj]; 69 } ··· 92 } 93 } 94 }
··· 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;
+6 -29
packages/core-extensions/src/common/index.ts
··· 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 2 3 export const webpackModules: ExtensionWebExports["webpackModules"] = { 4 - components: { 5 - dependencies: [ 6 - { ext: "spacepack", id: "spacepack" }, 7 - "MasonryList:", 8 - ".flexGutterSmall," 9 - ] 10 }, 11 - 12 - flux: { 13 - dependencies: [{ ext: "spacepack", id: "spacepack" }, "connectStores:"] 14 }, 15 - 16 - fluxDispatcher: { 17 - dependencies: [ 18 - { ext: "spacepack", id: "spacepack" }, 19 - "isDispatching", 20 - "dispatch" 21 - ] 22 - }, 23 - 24 - react: { 25 - dependencies: [ 26 - { ext: "spacepack", id: "spacepack" }, 27 - "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", 28 - /\.?version(?:=|:)/, 29 - /\.?createElement(?:=|:)/ 30 - ] 31 - }, 32 - 33 - stores: { 34 - dependencies: [{ ext: "common", id: "flux" }] 35 } 36 };
··· 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 },
+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;
-41
packages/core-extensions/src/common/webpackModules/components.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - const Components = spacepack.findByCode("MasonryList:function")[0].exports; 4 - const MarkdownParser = spacepack.findByCode( 5 - "parseAutoModerationSystemMessage:" 6 - )[0].exports.Z; 7 - const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0] 8 - .exports.default; 9 - const Flex = Object.values( 10 - spacepack.findByCode(".flex" + "GutterSmall,")[0].exports 11 - )[0]; 12 - 13 - const CardClasses = {}; 14 - spacepack 15 - .lazyLoad( 16 - "renderArtisanalHack", 17 - /\[(?:.\.e\("\d+?"\),?)+\][^}]+?webpackId:\d+,name:"ChannelSettings"/, 18 - /webpackId:(\d+),name:"ChannelSettings"/ 19 - ) 20 - .then(() => 21 - Object.assign( 22 - CardClasses, 23 - spacepack.findByExports("card", "cardHeader", "inModal")[0].exports 24 - ) 25 - ); 26 - 27 - const ControlClasses = spacepack.findByCode( 28 - "title", 29 - "titleDefault", 30 - "dividerDefault" 31 - )[0].exports; 32 - 33 - // We use CJS export here because merging the exports from Components is annoying as shit 34 - module.exports = { 35 - ...Components, 36 - MarkdownParser, 37 - LegacyText, 38 - Flex, 39 - CardClasses, 40 - ControlClasses 41 - };
···
-28
packages/core-extensions/src/common/webpackModules/flux.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - const mod = spacepack.findByCode("connectStores:")[0].exports; 4 - 5 - const useStateFromStores = spacepack.findFunctionByStrings( 6 - mod, 7 - '"useStateFromStores"' 8 - )!; 9 - 10 - module.exports = { 11 - BatchedStoreListener: spacepack.findFunctionByStrings( 12 - mod, 13 - " tried to load a non-existent store." 14 - ), 15 - Dispatcher: spacepack.findFunctionByStrings(mod, "_dispatchWithDevtools("), 16 - Store: spacepack.findFunctionByStrings(mod, "registerActionHandlers("), 17 - default: mod.ZP, 18 - statesWillNeverBeEqual: spacepack.findFunctionByStrings(mod, "return!1"), 19 - useStateFromStores, 20 - useStateFromStoresArray: spacepack.findFunctionByStrings( 21 - mod, 22 - new RegExp(`return ${useStateFromStores.name}\\(.+?\\.[^Z]\\)`) 23 - ), 24 - useStateFromStoresObject: spacepack.findFunctionByStrings( 25 - mod, 26 - new RegExp(`return ${useStateFromStores.name}\\(.+?\\.Z\\)`) 27 - ) 28 - };
···
-6
packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - module.exports = spacepack.findByExports( 4 - "isDispatching", 5 - "dispatch" 6 - )[0].exports.Z;
···
+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;
-7
packages/core-extensions/src/common/webpackModules/react.ts
··· 1 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 - 3 - module.exports = spacepack.findByCode( 4 - "__SECRET_INTERNALS_DO_NOT_USE" + "_OR_YOU_WILL_BE_FIRED", 5 - /\.?version(?:=|:)/, 6 - /\.?createElement(?:=|:)/ 7 - )[0].exports;
···
+2 -2
packages/core-extensions/src/common/webpackModules/stores.ts
··· 1 - import Flux from "@moonlight-mod/wp/common_flux"; 2 3 module.exports = new Proxy( 4 {}, 5 { 6 get: function (target, key, receiver) { 7 - const allStores = Flux.Store.getAll(); 8 9 let targetStore; 10 for (const store of allStores) {
··· 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) {
+83
packages/core-extensions/src/componentEditor/index.ts
···
··· 1 + import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; 2 + 3 + export const patches: Patch[] = [ 4 + // dm list 5 + { 6 + find: ".interactiveSystemDM]:", 7 + replace: [ 8 + { 9 + match: /decorators:(\i\.isSystemDM\(\)\?\(0,\i\.jsx\)\(.+?verified:!0}\):null)/, 10 + replacement: (_, decorators) => 11 + `decorators:require("componentEditor_dmList").default._patchDecorators([${decorators}],arguments[0])` 12 + }, 13 + { 14 + match: /(?<=selected:\i,)children:\[/, 15 + replacement: 'children:require("componentEditor_dmList").default._patchItems([' 16 + }, 17 + { 18 + match: /(?<=(onMouseDown|nameplate):\i}\))]/, 19 + replacement: "],arguments[0])" 20 + } 21 + ], 22 + hardFail: true 23 + }, 24 + 25 + // member list 26 + { 27 + find: ".lostPermission", 28 + replace: [ 29 + { 30 + match: /(?<=\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[\i\(\),.+?\i\(\)])/, 31 + replacement: (_, decorators) => 32 + `children:require("componentEditor_memberList").default._patchDecorators(${decorators},arguments[0])` 33 + }, 34 + { 35 + match: /name:null==\i\?\(0,\i\.jsx\)\("span"/, 36 + replacement: (orig: string) => 37 + `children:require("componentEditor_memberList").default._patchItems([],arguments[0]),${orig}` 38 + } 39 + ] 40 + }, 41 + 42 + // messages 43 + { 44 + find: '},"new-member")),', 45 + replace: [ 46 + { 47 + match: /(?<=\.BADGES](=|:))(\i)(;|})/, 48 + replacement: (_, leading, badges, trailing) => 49 + `require("componentEditor_messages").default._patchUsernameBadges(${badges},arguments[0])${trailing}` 50 + }, 51 + { 52 + match: /(?<=className:\i,)badges:(\i)/, 53 + replacement: (_, badges) => 54 + `badges:require("componentEditor_messages").default._patchBadges(${badges},arguments[0])` 55 + }, 56 + { 57 + match: /(?<=username:\(0,\i\.jsxs\)\(\i\.Fragment,{)children:(\[.+?])}\),usernameSpanId:/, 58 + replacement: (_, elements) => 59 + `children:require("componentEditor_messages").default._patchUsername(${elements},arguments[0])}),usernameSpanId:` 60 + } 61 + ] 62 + }, 63 + { 64 + find: '.provider&&"Discord"===', 65 + replace: { 66 + match: /(?<=\.container\),)children:(\[.+?this\.renderSuppressConfirmModal\(\),.+?\])}\)/, 67 + replacement: (_, elements) => 68 + `children:require("componentEditor_messages").default._patchAccessories(${elements},this.props)})` 69 + } 70 + } 71 + ]; 72 + 73 + export const webpackModules: Record<string, ExtensionWebpackModule> = { 74 + dmList: { 75 + dependencies: [{ id: "react" }] 76 + }, 77 + memberList: { 78 + dependencies: [{ id: "react" }] 79 + }, 80 + messages: { 81 + dependencies: [{ id: "react" }] 82 + } 83 + };
+11
packages/core-extensions/src/componentEditor/manifest.json
···
··· 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;
+5 -13
packages/core-extensions/src/contextMenu/index.tsx
··· 5 find: "Menu API only allows Items and groups of Items as children.", 6 replace: [ 7 { 8 - match: /(?<=let{navId[^}]+?}=(.),(.)=.\(.\))/, 9 - replacement: (_, props, items) => 10 - `,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})` 11 } 12 ] 13 }, ··· 16 replace: [ 17 { 18 match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/, 19 - replacement: (render) => 20 - `require("contextMenu_contextMenu")._saveProps(this,${render})` 21 } 22 ] 23 } ··· 25 26 export const webpackModules: Record<string, ExtensionWebpackModule> = { 27 contextMenu: { 28 - dependencies: [ 29 - { ext: "spacepack", id: "spacepack" }, 30 - "Menu API only allows Items and groups of Items as children." 31 - ] 32 }, 33 evilMenu: { 34 - dependencies: [ 35 - { ext: "spacepack", id: "spacepack" }, 36 - "Menu API only allows Items and groups of Items as children." 37 - ] 38 } 39 };
··· 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 }, ··· 15 replace: [ 16 { 17 match: /(?<=let\{[^}]+?\}=.;return ).\({[^}]+?}\)/, 18 + replacement: (render) => `require("contextMenu_contextMenu")._saveProps(this,${render})` 19 } 20 ] 21 } ··· 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 };
+2
packages/core-extensions/src/contextMenu/manifest.json
··· 1 { 2 "id": "contextMenu", 3 "meta": { 4 "name": "Context Menu", 5 "tagline": "A library for patching and creating context menus",
··· 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",
+25 -30
packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts
··· 1 - import { 2 - InternalItem, 3 - MenuElement, 4 - MenuProps 5 - } from "@moonlight-mod/types/coreExtensions/contextMenu"; 6 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 7 import parser from "@moonlight-mod/wp/contextMenu_evilMenu"; 8 9 type Patch = { 10 navId: string; 11 - item: ( 12 - props: any 13 - ) => 14 - | React.ReactComponentElement<MenuElement> 15 - | React.ReactComponentElement<MenuElement>[]; 16 - anchorId: string; 17 before: boolean; 18 }; 19 20 - export function addItem<T>( 21 - navId: string, 22 - item: ( 23 - props: T 24 - ) => 25 - | React.ReactComponentElement<MenuElement> 26 - | React.ReactComponentElement<MenuElement>[], 27 - anchorId: string, 28 - before = false 29 - ) { 30 - patches.push({ navId, item, anchorId, before }); 31 } 32 33 - export const patches: Patch[] = []; 34 - function _patchMenu(props: MenuProps, items: InternalItem[]) { 35 const matches = patches.filter((p) => p.navId === props.navId); 36 - if (!matches.length) return; 37 38 for (const patch of matches) { 39 - const idx = items.findIndex((i) => i.key === patch.anchorId); 40 if (idx === -1) continue; 41 - items.splice(idx + 1 - +patch.before, 0, ...parser(patch.item(menuProps))); 42 } 43 } 44 45 let menuProps: any; ··· 56 } 57 58 module.exports = { 59 addItem, 60 _patchMenu, 61 _saveProps 62 }; 63 64 // Unmangle Menu elements 65 const code = 66 spacepack.require.m[ 67 - spacepack.findByCode( 68 - "Menu API only allows Items and groups of Items as children." 69 - )[0].id 70 ].toString(); 71 72 let MangledMenu;
··· 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; ··· 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;
+11 -20
packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts
··· 1 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 2 3 let code = 4 spacepack.require.m[ 5 - spacepack.findByCode( 6 - "Menu API only allows Items and groups of Items as children." 7 - )[0].id 8 ].toString(); 9 - code = code.replace( 10 - /onSelect:(.)}=(.),.=(.\(.\)),/, 11 - `onSelect:$1}=$2;return $3;let ` 12 - ); 13 - const mod = new Function( 14 - "module", 15 - "exports", 16 - "require", 17 - `(${code}).apply(this, arguments)` 18 - ); 19 const exp: any = {}; 20 mod({}, exp, require); 21 - const Menu = spacepack.findFunctionByStrings(exp, "isUsingKeyboardNavigation")!; 22 - module.exports = (el: any) => { 23 - return Menu({ 24 - children: el, 25 - __contextMenu_evilMenu: true 26 - }); 27 - };
··· 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 + }
+4 -25
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 if (moonlightHost.asarPath !== "moonlightDesktop") { 8 try { 9 - const hostSentryPath = require.resolve( 10 - join(moonlightHost.asarPath, "node_modules", "@sentry", "electron") 11 - ); 12 - require.cache[hostSentryPath] = new Module( 13 - hostSentryPath, 14 - require.cache[require.resolve(moonlightHost.asarPath)] 15 - ); 16 require.cache[hostSentryPath]!.exports = { 17 init: () => {}, 18 captureException: () => {}, 19 setTag: () => {}, 20 - setUser: () => {} 21 }; 22 logger.debug("Stubbed Sentry host side!"); 23 } catch (err) { 24 logger.error("Failed to stub Sentry host side:", err); 25 } 26 } 27 - 28 - moonlightHost.events.on("window-created", (window: BrowserWindow) => { 29 - window.webContents.session.webRequest.onBeforeRequest( 30 - { 31 - urls: [ 32 - "https://*.sentry.io/*", 33 - "https://*.discord.com/error-reporting-proxy/*", 34 - "https://discord.com/assets/sentry.*.js", 35 - "https://*.discord.com/assets/sentry.*.js" 36 - ] 37 - }, 38 - function (details, callback) { 39 - callback({ cancel: true }); 40 - } 41 - ); 42 - });
··· 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 }
+5 -6
packages/core-extensions/src/disableSentry/index.ts
··· 6 find: "profiledRootComponent:", 7 replace: { 8 type: PatchReplaceType.Normal, 9 - match: /(?<=\.Z=){.+?}}/, 10 - replacement: 'require("disableSentry_stub").proxy()' 11 } 12 }, 13 { 14 - find: "window.DiscordSentry.addBreadcrumb", 15 replace: { 16 type: PatchReplaceType.Normal, 17 - match: /Z:function\(\){return .}/, 18 - replacement: 19 - 'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}' 20 } 21 }, 22 {
··· 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 {
+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 }
+13 -19
packages/core-extensions/src/disableSentry/node.ts
··· 5 6 const logger = moonlightNode.getLogger("disableSentry"); 7 8 - if (!ipcRenderer.sendSync(constants.ipcGetIsMoonlightDesktop)) { 9 - const preloadPath = ipcRenderer.sendSync(constants.ipcGetOldPreloadPath); 10 - try { 11 - const sentryPath = require.resolve( 12 - resolve(preloadPath, "..", "node_modules", "@sentry", "electron") 13 - ); 14 - require.cache[sentryPath] = new Module( 15 - sentryPath, 16 - require.cache[require.resolve(preloadPath)] 17 - ); 18 - require.cache[sentryPath]!.exports = { 19 - init: () => {}, 20 - setTag: () => {}, 21 - setUser: () => {} 22 - }; 23 - logger.debug("Stubbed Sentry node side!"); 24 - } catch (err) { 25 - logger.error("Failed to stub Sentry:", err); 26 - } 27 }
··· 5 6 const logger = moonlightNode.getLogger("disableSentry"); 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) { 20 + logger.error("Failed to stub Sentry:", err); 21 }
+1 -2
packages/core-extensions/src/disableSentry/webpackModules/stub.ts
··· 23 throw Error("crash"); 24 }; 25 } else if (keys.includes(prop.toString())) { 26 - return (...args: any[]) => 27 - logger.debug(`Sentry calling "${prop.toString()}":`, ...args); 28 } else { 29 return undefined; 30 }
··· 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 }
+39 -2
packages/core-extensions/src/experiments/index.ts
··· 11 { 12 find: '"scientist:triggered"', // Scientist? Triggered. 13 replace: { 14 - match: /(?<=personal_connection_id\|\|)!1/, 15 - replacement: "!0" 16 } 17 } 18 ];
··· 11 { 12 find: '"scientist:triggered"', // Scientist? Triggered. 13 replace: { 14 + match: ".personal_connection_id", 15 + replacement: ".personal_connection_id || true" 16 + } 17 + }, 18 + 19 + // Enable staff help menu 20 + { 21 + find: ".HEADER_BAR)", 22 + replace: { 23 + match: /&&\((.)\?\(0,/, 24 + replacement: (_, isStaff) => 25 + `&&(((moonlight.getConfigOption("experiments","devtools")??false)?true:${isStaff})?(0,` 26 + } 27 + }, 28 + // staff help menu - visual refresh 29 + { 30 + find: '("AppTitleBar")', 31 + replace: { 32 + match: /{hasBugReporterAccess:(\i)}=\i\.\i\.useExperiment\({location:"HeaderBar"},{autoTrackExposure:!1}\);/, 33 + replacement: (orig, isStaff) => 34 + `${orig}if(moonlight.getConfigOption("experiments","devtools")??false)${isStaff}=true;` 35 + } 36 + }, 37 + { 38 + find: 'navId:"staff-help-popout",', 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:(.),/, 51 + replacement: (_, isStaff) => 52 + `.useReducedMotion,isStaff:(moonlight.getConfigOption("experiments","staffSettings")??false)?true:${isStaff},` 53 } 54 } 55 ];
+12 -2
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", ··· 7 "tags": ["dangerZone"] 8 }, 9 "settings": { 10 - "sections": { 11 "displayName": "Allow access to other staff settings elsewhere", 12 - "type": "boolean" 13 } 14 } 15 }
··· 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", ··· 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 }
+7 -19
packages/core-extensions/src/markdown/index.ts
··· 6 replace: [ 7 { 8 match: /={newline:(.+?)},(.{1,2})=\(0,/, 9 - replacement: (_, rules, RULES) => 10 - `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,` 11 }, 12 { 13 - match: /(?<=var (.{1,2})={RULES:.+?})/, 14 - replacement: (_, rulesets) => 15 - `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});` 16 } 17 ] 18 }, ··· 25 `__slateRules,${rulesDef}=__slateRules=require("markdown_markdown")._addSlateRules({link:{${rules}}),${syntaxBefore}=new Set` 26 }, 27 { 28 - match: 29 - /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"link":{(.+?)}default:/, 30 - replacement: ( 31 - _, 32 - start, 33 - rule, 34 - body, 35 - plaintextReturn, 36 - otherRules, 37 - inlineStyleBody 38 - ) => 39 - `${start}if(${rule}.type.startsWith("__moonlight_")){if(__slateRules[${rule}.type].type=="inlineStyle"){${inlineStyleBody}}else{${plaintextReturn}}}${body}case"emoticon":${plaintextReturn}${otherRules}case"link":{${inlineStyleBody}}default:` 40 } 41 ] 42 }, ··· 44 find: '"Slate: Unknown decoration attribute: "', 45 replace: { 46 match: /=({strong:.+?});/, 47 - replacement: (_, rules) => 48 - `=require("markdown_markdown")._addSlateDecorators(${rules});` 49 } 50 } 51 ];
··· 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 }, ··· 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 }, ··· 33 find: '"Slate: Unknown decoration attribute: "', 34 replace: { 35 match: /=({strong:.+?});/, 36 + replacement: (_, rules) => `=require("markdown_markdown")._addSlateDecorators(${rules});` 37 } 38 } 39 ];
+2
packages/core-extensions/src/markdown/manifest.json
··· 1 { 2 "id": "markdown", 3 "meta": { 4 "name": "Markdown", 5 "tagline": "A library for adding new markdown rules",
··· 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",
+4 -17
packages/core-extensions/src/markdown/webpackModules/markdown.ts
··· 1 - /* eslint-disable no-console */ 2 - import { 3 - MarkdownRule, 4 - Ruleset, 5 - SlateRule 6 - } from "@moonlight-mod/types/coreExtensions/markdown"; 7 8 - export const rules: Record< 9 - string, 10 - (rules: Record<string, MarkdownRule>) => MarkdownRule 11 - > = {}; 12 - export const slateRules: Record< 13 - string, 14 - (rules: Record<string, SlateRule>) => SlateRule 15 - > = {}; 16 export const slateDecorators: Record<string, string> = {}; 17 export const ruleBlacklists: Record<Ruleset, Record<string, boolean>> = { 18 RULES: {}, ··· 67 return originalRules; 68 } 69 70 - export function _applyRulesetBlacklist( 71 - rulesets: Record<Ruleset, Record<string, MarkdownRule>> 72 - ) { 73 for (const ruleset of Object.keys(rulesets) as Ruleset[]) { 74 if (ruleset === "RULES") continue; 75
··· 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: {}, ··· 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
+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 + });
+80 -17
packages/core-extensions/src/moonbase/index.tsx
··· 1 - import { ExtensionWebExports } from "@moonlight-mod/types"; 2 3 - export const webpackModules: ExtensionWebExports["webpackModules"] = { 4 stores: { 5 - dependencies: [ 6 - { ext: "common", id: "flux" }, 7 - { ext: "common", id: "fluxDispatcher" } 8 - ] 9 }, 10 11 ui: { 12 dependencies: [ 13 { ext: "spacepack", id: "spacepack" }, 14 - { ext: "common", id: "react" }, 15 - { ext: "common", id: "components" }, 16 { ext: "moonbase", id: "stores" }, 17 "Masks.PANEL_BUTTON", 18 - "renderArtisanalHack(){", 19 '"Missing channel in Channel.openChannelContextMenu"', 20 ".forumOrHome]:" 21 ] 22 }, 23 24 - moonbase: { 25 dependencies: [ 26 { ext: "spacepack", id: "spacepack" }, 27 { ext: "settings", id: "settings" }, 28 - { ext: "common", id: "react" }, 29 - { ext: "moonbase", id: "ui" } 30 ], 31 entrypoint: true 32 } 33 }; 34 - 35 - export const styles = [ 36 - ".moonbase-settings > :first-child { margin-top: 0px; }", 37 - "textarea.moonbase-resizeable { resize: vertical }" 38 - ];
··· 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 };
+35 -5
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 "settings": { 10 "sections": { 11 "displayName": "Split into sections", 12 "description": "Show the Moonbase tabs as separate sections", 13 - "type": "boolean" 14 }, 15 "saveFilter": { 16 "displayName": "Persist filter", 17 "description": "Save extension filter in config", 18 - "type": "boolean" 19 } 20 - } 21 }
··· 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 -69
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 - const logger = moonlightNode.getLogger("moonbase"); 9 - 10 - async function fetchRepositories(repos: string[]) { 11 - const ret: Record<string, RepositoryManifest[]> = {}; 12 - 13 - for (const repo of repos) { 14 - try { 15 - const req = await fetch(repo); 16 - const json = await req.json(); 17 - ret[repo] = json; 18 - } catch (e) { 19 - logger.error(`Error fetching repository ${repo}`, e); 20 - } 21 - } 22 - 23 - return ret; 24 - } 25 - 26 - async function installExtension( 27 - manifest: RepositoryManifest, 28 - url: string, 29 - repo: string 30 - ) { 31 - const req = await fetch(url); 32 - 33 - const dir = moonlightNode.getExtensionDir(manifest.id); 34 - // remake it in case of updates 35 - if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true }); 36 - fs.mkdirSync(dir, { recursive: true }); 37 - 38 - // for some reason i just can't .writeFileSync() a file that ends in .asar??? 39 - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "moonlight-")); 40 - const tempFile = path.join(tempDir, "extension"); 41 - const buffer = await req.arrayBuffer(); 42 - fs.writeFileSync(tempFile, Buffer.from(buffer)); 43 - 44 - asar.extractAll(tempFile, dir); 45 - fs.writeFileSync(path.join(dir, repoUrlFile), repo); 46 - } 47 - 48 - async function deleteExtension(id: string) { 49 - const dir = moonlightNode.getExtensionDir(id); 50 - fs.rmdirSync(dir, { recursive: true }); 51 - } 52 - 53 - function getExtensionConfig(id: string, key: string): any { 54 - const config = moonlightNode.config.extensions[id]; 55 - if (typeof config === "object") { 56 - return config.config?.[key]; 57 - } 58 - 59 - return undefined; 60 - } 61 - 62 - const exports: MoonbaseNatives = { 63 - fetchRepositories, 64 - installExtension, 65 - deleteExtension, 66 - getExtensionConfig 67 - }; 68 - 69 - module.exports = exports;
··· 1 + import getNatives from "./native"; 2 + module.exports = getNatives();
+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 + }
+26 -10
packages/core-extensions/src/moonbase/types.ts
··· 1 - import { DetectedExtension, ExtensionManifest } from "types/src"; 2 3 export type MoonbaseNatives = { 4 - fetchRepositories( 5 - repos: string[] 6 - ): Promise<Record<string, RepositoryManifest[]>>; 7 - installExtension( 8 - manifest: RepositoryManifest, 9 - url: string, 10 - repo: string 11 - ): Promise<void>; 12 deleteExtension(id: string): Promise<void>; 13 - getExtensionConfig(id: string, key: string): any; 14 }; 15 16 export type RepositoryManifest = ExtensionManifest & { ··· 29 manifest: ExtensionManifest | RepositoryManifest; 30 source: DetectedExtension["source"]; 31 state: ExtensionState; 32 };
··· 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 & { ··· 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 + }
+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 + function disableWithDependents() { 88 + const disable = new Set<string>(); 89 + disable.add(ext.id); 90 + for (const [id, dependencies] of moonlightNode.processedExtensions.dependencyGraph) { 91 + if (dependencies?.has(ext.id)) disable.add(id); 92 + } 93 + 94 + const config = structuredClone(moonlightNode.config); 95 + for (const id in config.extensions) { 96 + if (!disable.has(id)) continue; 97 + if (typeof config.extensions[id] === "boolean") config.extensions[id] = false; 98 + else (config.extensions[id] as ConfigExtension).enabled = false; 99 + } 100 + 101 + let msg = `Are you sure you want to disable "${ext.manifest.meta?.name ?? ext.id}"`; 102 + if (disable.size > 1) { 103 + msg += ` and its ${disable.size - 1} dependent${disable.size - 1 === 1 ? "" : "s"}`; 104 + } 105 + msg += "?"; 106 + 107 + if (confirm(msg)) { 108 + moonlightNode.writeConfig(config); 109 + window.location.reload(); 110 + } 111 + } 112 + 113 + return ( 114 + <div className="moonbase-crash-extensionCard"> 115 + <div className="moonbase-crash-extensionCard-meta"> 116 + <div className="moonbase-crash-extensionCard-title">{ext.manifest.meta?.name ?? ext.id}</div> 117 + <div className="moonbase-crash-extensionCard-version">{`v${ext.manifest.version ?? "???"}`}</div> 118 + </div> 119 + <div className="moonbase-crash-extensionCard-button"> 120 + <Button color={Button.Colors.RED} onClick={disableWithDependents}> 121 + Disable 122 + </Button> 123 + </div> 124 + </div> 125 + ); 126 + } 127 + 128 + export function wrapAction({ action, state }: WrapperProps) { 129 + const [tab, setTab] = React.useState("crash"); 130 + 131 + const { updates, updateCount } = useStateFromStoresObject([MoonbaseSettingsStore], () => { 132 + const { updates } = MoonbaseSettingsStore; 133 + return { 134 + updates: Object.entries(updates), 135 + updateCount: Object.keys(updates).length 136 + }; 137 + }); 138 + 139 + const causes = React.useMemo(() => { 140 + const causes = new Set<string>(); 141 + if (state.error.stack) { 142 + for (const [, , id] of state.error.stack.matchAll(MODULE_REGEX)) 143 + for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 144 + } 145 + for (const [, , id] of state.info.componentStack.matchAll(MODULE_REGEX)) 146 + for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 147 + 148 + for (const [path, id] of Object.entries(moonlight.moonmap.modules)) { 149 + const MAPPING_REGEX = new RegExp( 150 + // @ts-expect-error Only Firefox has RegExp.escape 151 + `(${RegExp.escape ? RegExp.escape(path) : path.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")})`, 152 + "g" 153 + ); 154 + 155 + if (state.error.stack) { 156 + for (const match of state.error.stack.matchAll(MAPPING_REGEX)) 157 + if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 158 + } 159 + for (const match of state.info.componentStack.matchAll(MAPPING_REGEX)) 160 + if (match) for (const ext of moonlight.patched.get(id) ?? []) causes.add(ext); 161 + } 162 + 163 + return [...causes]; 164 + }, []); 165 + 166 + return ( 167 + <div className="moonbase-crash-wrapper"> 168 + {action} 169 + <TabBar 170 + className={`${DiscoveryClasses.tabBar} moonbase-crash-tabs`} 171 + type="top" 172 + selectedItem={tab} 173 + onItemSelect={(v) => setTab(v)} 174 + > 175 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id="crash"> 176 + Crash details 177 + </TabBar.Item> 178 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id="extensions" disabled={updateCount === 0}> 179 + {`Extension updates (${updateCount})`} 180 + </TabBar.Item> 181 + <TabBar.Item className={DiscoveryClasses.tabBarItem} id="causes" disabled={causes.length === 0}> 182 + {`Possible causes (${causes.length})`} 183 + </TabBar.Item> 184 + </TabBar> 185 + {tab === "crash" ? ( 186 + <div className="moonbase-crash-details-wrapper"> 187 + <pre className="moonbase-crash-details"> 188 + <code> 189 + {state.error.stack} 190 + {"\n\nComponent stack:"} 191 + {state.info.componentStack} 192 + </code> 193 + </pre> 194 + </div> 195 + ) : null} 196 + {tab === "extensions" ? ( 197 + <div className="moonbase-crash-extensions"> 198 + {updates.map(([id, ext]) => ( 199 + <ExtensionUpdateCard id={Number(id)} ext={ext} /> 200 + ))} 201 + </div> 202 + ) : null} 203 + {tab === "causes" ? ( 204 + <div className="moonbase-crash-extensions"> 205 + {causes 206 + .map((ext) => moonlightNode.extensions.find((e) => e.id === ext)!) 207 + .map((ext) => ( 208 + <ExtensionDisableCard ext={ext} /> 209 + ))} 210 + </div> 211 + ) : null} 212 + </div> 213 + ); 214 + } 215 + 216 + export function UpdateText({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) { 217 + if (!state.__moonlight_update) { 218 + setState({ 219 + ...state, 220 + __moonlight_update: UpdateState.Ready 221 + }); 222 + } 223 + const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion); 224 + 225 + return newVersion == null ? null : ( 226 + <p>{state.__moonlight_update !== undefined ? updateStrings[state.__moonlight_update] : ""}</p> 227 + ); 228 + } 229 + 230 + export function UpdateButton({ state, setState }: { state: ErrorState; setState: (state: ErrorState) => void }) { 231 + const newVersion = useStateFromStores([MoonbaseSettingsStore], () => MoonbaseSettingsStore.newVersion); 232 + return newVersion == null || 233 + state.__moonlight_update === UpdateState.Installed || 234 + state.__moonlight_update === undefined ? null : ( 235 + <Button 236 + size={Button.Sizes.LARGE} 237 + disabled={state.__moonlight_update !== UpdateState.Ready} 238 + onClick={() => { 239 + setState({ 240 + ...state, 241 + __moonlight_update: UpdateState.Working 242 + }); 243 + 244 + MoonbaseSettingsStore.updateMoonlight() 245 + .then(() => { 246 + setState({ 247 + ...state, 248 + __moonlight_update: UpdateState.Installed 249 + }); 250 + }) 251 + .catch((e) => { 252 + logger.error(e); 253 + setState({ 254 + ...state, 255 + __moonlight_update: UpdateState.Failed 256 + }); 257 + }); 258 + }} 259 + > 260 + {state.__moonlight_update !== undefined ? buttonStrings[state.__moonlight_update] : ""} 261 + </Button> 262 + ); 263 + }
+10
packages/core-extensions/src/moonbase/webpackModules/moonbase.ts
···
··· 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;
-44
packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx
··· 1 - import settings from "@moonlight-mod/wp/settings_settings"; 2 - import React from "@moonlight-mod/wp/common_react"; 3 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 - import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui"; 5 - 6 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 7 - import { MenuItem } from "@moonlight-mod/wp/common_components"; 8 - 9 - const { open } = spacepack.findByExports("setSection", "clearSubsection")[0] 10 - .exports.Z; 11 - 12 - settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, { 13 - stores: [MoonbaseSettingsStore], 14 - element: () => { 15 - // Require it here because lazy loading SUX 16 - const SettingsNotice = spacepack.findByCode( 17 - "onSaveButtonColor", 18 - "FocusRingScope" 19 - )[0].exports.Z; 20 - return ( 21 - <SettingsNotice 22 - submitting={MoonbaseSettingsStore.submitting} 23 - onReset={() => { 24 - MoonbaseSettingsStore.reset(); 25 - }} 26 - onSave={() => { 27 - MoonbaseSettingsStore.writeConfig(); 28 - }} 29 - /> 30 - ); 31 - } 32 - }); 33 - 34 - settings.addSectionMenuItems( 35 - "moonbase", 36 - ...pages.map((page, i) => ( 37 - <MenuItem 38 - key={page.id} 39 - id={`moonbase-${page.id}`} 40 - label={page.name} 41 - action={() => open("moonbase", i)} 42 - /> 43 - )) 44 - );
···
+100
packages/core-extensions/src/moonbase/webpackModules/settings.tsx
···
··· 1 + import settings from "@moonlight-mod/wp/settings_settings"; 2 + import React from "@moonlight-mod/wp/react"; 3 + import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 + import { Moonbase, pages, RestartAdviceMessage, Update } from "@moonlight-mod/wp/moonbase_ui"; 5 + import UserSettingsModalActionCreators from "@moonlight-mod/wp/discord/actions/UserSettingsModalActionCreators"; 6 + import Margins from "@moonlight-mod/wp/discord/styles/shared/Margins.css"; 7 + import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 8 + import { Text, Breadcrumbs } from "@moonlight-mod/wp/discord/components/common/index"; 9 + import { MenuItem } from "@moonlight-mod/wp/contextMenu_contextMenu"; 10 + 11 + const notice = { 12 + stores: [MoonbaseSettingsStore], 13 + element: () => { 14 + // Require it here because lazy loading SUX 15 + const SettingsNotice = spacepack.require("discord/components/common/SettingsNotice").default; 16 + return ( 17 + <SettingsNotice 18 + submitting={MoonbaseSettingsStore.submitting} 19 + onReset={() => { 20 + MoonbaseSettingsStore.reset(); 21 + }} 22 + onSave={() => { 23 + MoonbaseSettingsStore.writeConfig(); 24 + }} 25 + /> 26 + ); 27 + } 28 + }; 29 + 30 + const oldLocation = MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "oldLocation", false); 31 + const position = oldLocation ? -2 : -9999; 32 + 33 + function addSection(id: string, name: string, element: React.FunctionComponent) { 34 + settings.addSection(`moonbase-${id}`, name, element, null, position, notice); 35 + } 36 + 37 + // FIXME: move to component types 38 + type Breadcrumb = { 39 + id: string; 40 + label: string; 41 + }; 42 + 43 + function renderBreadcrumb(crumb: Breadcrumb, last: boolean) { 44 + return ( 45 + <Text variant="heading-lg/semibold" tag="h2" color={last ? "header-primary" : "header-secondary"}> 46 + {crumb.label} 47 + </Text> 48 + ); 49 + } 50 + 51 + if (!oldLocation) { 52 + settings.addDivider(position); 53 + } 54 + 55 + if (MoonbaseSettingsStore.getExtensionConfigRaw<boolean>("moonbase", "sections", false)) { 56 + if (oldLocation) settings.addHeader("Moonbase", position); 57 + 58 + const _pages = oldLocation ? pages : pages.reverse(); 59 + for (const page of _pages) { 60 + addSection(page.id, page.name, () => { 61 + const breadcrumbs = [ 62 + { id: "moonbase", label: "Moonbase" }, 63 + { id: page.id, label: page.name } 64 + ]; 65 + return ( 66 + <> 67 + <Breadcrumbs 68 + className={Margins.marginBottom20} 69 + renderCustomBreadcrumb={renderBreadcrumb} 70 + breadcrumbs={breadcrumbs} 71 + activeId={page.id} 72 + > 73 + {page.name} 74 + </Breadcrumbs> 75 + 76 + <RestartAdviceMessage /> 77 + <Update /> 78 + 79 + <page.element /> 80 + </> 81 + ); 82 + }); 83 + } 84 + 85 + if (!oldLocation) settings.addHeader("Moonbase", position); 86 + } else { 87 + settings.addSection("moonbase", "Moonbase", Moonbase, null, position, notice); 88 + 89 + settings.addSectionMenuItems( 90 + "moonbase", 91 + ...pages.map((page, i) => ( 92 + <MenuItem 93 + key={page.id} 94 + id={`moonbase-${page.id}`} 95 + label={page.name} 96 + action={() => UserSettingsModalActionCreators.open("moonbase", i.toString())} 97 + /> 98 + )) 99 + ); 100 + }
+340 -89
packages/core-extensions/src/moonbase/webpackModules/stores.ts
··· 1 - import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; 2 - import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; 3 - import Flux from "@moonlight-mod/wp/common_flux"; 4 - import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher"; 5 6 - const natives: MoonbaseNatives = moonlight.getNatives("moonbase"); 7 const logger = moonlight.getLogger("moonbase"); 8 9 - class MoonbaseSettingsStore extends Flux.Store<any> { 10 - private origConfig: Config; 11 private config: Config; 12 private extensionIndex: number; 13 14 modified: boolean; 15 submitting: boolean; 16 installing: boolean; 17 18 extensions: { [id: number]: MoonbaseExtension }; 19 - updates: { [id: number]: { version: string; download: string } }; 20 21 constructor() { 22 super(Dispatcher); 23 24 - this.origConfig = moonlightNode.config; 25 - this.config = this.clone(this.origConfig); 26 this.extensionIndex = 0; 27 28 this.modified = false; 29 this.submitting = false; 30 this.installing = false; 31 32 this.extensions = {}; 33 this.updates = {}; 34 for (const ext of moonlightNode.extensions) { ··· 36 this.extensions[uniqueId] = { 37 ...ext, 38 uniqueId, 39 - state: moonlight.enabledExtensions.has(ext.id) 40 - ? ExtensionState.Enabled 41 - : ExtensionState.Disabled 42 }; 43 } 44 45 - natives.fetchRepositories(this.config.repositories).then((ret) => { 46 - for (const [repo, exts] of Object.entries(ret)) { 47 - try { 48 - for (const ext of exts) { 49 - const uniqueId = this.extensionIndex++; 50 - const extensionData = { 51 - id: ext.id, 52 - uniqueId, 53 - manifest: ext, 54 - source: { type: ExtensionLoadSource.Normal, url: repo }, 55 - state: ExtensionState.NotDownloaded 56 - }; 57 58 - if (this.alreadyExists(extensionData)) { 59 - if (this.hasUpdate(extensionData)) { 60 - this.updates[uniqueId] = { 61 - version: ext.version!, 62 - download: ext.download 63 - }; 64 - } 65 66 - continue; 67 - } 68 69 - this.extensions[uniqueId] = extensionData; 70 } 71 - } catch (e) { 72 - logger.error(`Error processing repository ${repo}`, e); 73 } 74 } 75 76 - this.emitChange(); 77 - }); 78 } 79 80 - private alreadyExists(ext: MoonbaseExtension) { 81 - return Object.values(this.extensions).some( 82 - (e) => e.id === ext.id && e.source.url === ext.source.url 83 - ); 84 } 85 86 private hasUpdate(ext: MoonbaseExtension) { 87 - const existing = Object.values(this.extensions).find( 88 - (e) => e.id === ext.id && e.source.url === ext.source.url 89 - ); 90 if (existing == null) return false; 91 92 - return ( 93 - existing.manifest.version !== ext.manifest.version && 94 - existing.state !== ExtensionState.NotDownloaded 95 - ); 96 } 97 98 // Jank 99 private isModified() { 100 - const orig = JSON.stringify(this.origConfig); 101 const curr = JSON.stringify(this.config); 102 return orig !== curr; 103 } ··· 106 return this.submitting || this.installing; 107 } 108 109 showNotice() { 110 return this.modified; 111 } ··· 115 } 116 117 getExtensionUniqueId(id: string) { 118 - return Object.values(this.extensions).find((ext) => ext.id === id) 119 - ?.uniqueId; 120 } 121 122 getExtensionConflicting(uniqueId: number) { 123 const ext = this.getExtension(uniqueId); 124 if (ext.state !== ExtensionState.NotDownloaded) return false; 125 return Object.values(this.extensions).some( 126 - (e) => 127 - e.id === ext.id && 128 - e.uniqueId !== uniqueId && 129 - e.state !== ExtensionState.NotDownloaded 130 ); 131 } 132 ··· 149 150 getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 151 const ext = this.getExtension(uniqueId); 152 - const defaultValue = ext.manifest.settings?.[key]?.default; 153 - const clonedDefaultValue = this.clone(defaultValue); 154 - const cfg = this.config.extensions[ext.id]; 155 156 - if (cfg == null || typeof cfg === "boolean") return clonedDefaultValue; 157 - return cfg.config?.[key] ?? clonedDefaultValue; 158 } 159 160 getExtensionConfigName(uniqueId: number, key: string) { 161 const ext = this.getExtension(uniqueId); 162 - return ext.manifest.settings?.[key]?.displayName ?? key; 163 } 164 165 getExtensionConfigDescription(uniqueId: number, key: string) { 166 const ext = this.getExtension(uniqueId); 167 - return ext.manifest.settings?.[key]?.description; 168 } 169 170 - setExtensionConfig(uniqueId: number, key: string, value: any) { 171 - const ext = this.getExtension(uniqueId); 172 - const oldConfig = this.config.extensions[ext.id]; 173 - const newConfig = 174 - typeof oldConfig === "boolean" 175 - ? { 176 - enabled: oldConfig, 177 - config: { [key]: value } 178 - } 179 - : { 180 - ...oldConfig, 181 - config: { ...(oldConfig?.config ?? {}), [key]: value } 182 - }; 183 - 184 - this.config.extensions[ext.id] = newConfig; 185 this.modified = this.isModified(); 186 this.emitChange(); 187 } ··· 208 this.emitChange(); 209 } 210 211 async installExtension(uniqueId: number) { 212 const ext = this.getExtension(uniqueId); 213 if (!("download" in ext.manifest)) { ··· 216 217 this.installing = true; 218 try { 219 - const url = this.updates[uniqueId]?.download ?? ext.manifest.download; 220 - await natives.installExtension(ext.manifest, url, ext.source.url!); 221 if (ext.state === ExtensionState.NotDownloaded) { 222 this.extensions[uniqueId].state = ExtensionState.Disabled; 223 } 224 225 delete this.updates[uniqueId]; 226 } catch (e) { 227 logger.error("Error installing extension:", e); 228 } 229 230 this.installing = false; 231 this.emitChange(); 232 } 233 234 async deleteExtension(uniqueId: number) { 235 const ext = this.getExtension(uniqueId); 236 if (ext == null) return; 237 238 this.installing = true; 239 try { 240 - await natives.deleteExtension(ext.id); 241 this.extensions[uniqueId].state = ExtensionState.NotDownloaded; 242 } catch (e) { 243 logger.error("Error deleting extension:", e); 244 } 245 246 this.installing = false; 247 this.emitChange(); 248 } 249 ··· 257 this.emitChange(); 258 } 259 260 writeConfig() { 261 this.submitting = true; 262 263 - try { 264 - moonlightNode.writeConfig(this.config); 265 - this.origConfig = this.clone(this.config); 266 - } catch (e) { 267 - logger.error("Error writing config", e); 268 - } 269 270 this.submitting = false; 271 this.modified = false; 272 this.emitChange(); 273 } 274 275 reset() { 276 this.submitting = false; 277 this.modified = false; 278 - this.config = this.clone(this.origConfig); 279 this.emitChange(); 280 } 281 282 // Required because electron likes to make it immutable sometimes.
··· 1 + import { Config, ExtensionEnvironment, ExtensionLoadSource, ExtensionSettingsAdvice } from "@moonlight-mod/types"; 2 + import { 3 + ExtensionState, 4 + MoonbaseExtension, 5 + MoonbaseNatives, 6 + RepositoryManifest, 7 + RestartAdvice, 8 + UpdateState 9 + } from "../types"; 10 + import { Store } from "@moonlight-mod/wp/discord/packages/flux"; 11 + import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; 12 + import getNatives from "../native"; 13 + import { mainRepo } from "@moonlight-mod/types/constants"; 14 + import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader"; 15 + import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase"; 16 + import { getConfigOption, setConfigOption } from "@moonlight-mod/core/util/config"; 17 + import diff from "microdiff"; 18 19 const logger = moonlight.getLogger("moonbase"); 20 21 + let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); 22 + if (moonlightNode.isBrowser) natives = getNatives(); 23 + 24 + class MoonbaseSettingsStore extends Store<any> { 25 + private initialConfig: Config; 26 + private savedConfig: Config; 27 private config: Config; 28 private extensionIndex: number; 29 + private configComponents: Record<string, Record<string, CustomComponent>> = {}; 30 31 modified: boolean; 32 submitting: boolean; 33 installing: boolean; 34 35 + #updateState = UpdateState.Ready; 36 + get updateState() { 37 + return this.#updateState; 38 + } 39 + newVersion: string | null; 40 + shouldShowNotice: boolean; 41 + 42 + restartAdvice = RestartAdvice.NotNeeded; 43 + 44 extensions: { [id: number]: MoonbaseExtension }; 45 + updates: { 46 + [id: number]: { 47 + version: string; 48 + download: string; 49 + updateManifest: RepositoryManifest; 50 + }; 51 + }; 52 53 constructor() { 54 super(Dispatcher); 55 56 + this.initialConfig = moonlightNode.config; 57 + this.savedConfig = moonlightNode.config; 58 + this.config = this.clone(this.savedConfig); 59 this.extensionIndex = 0; 60 61 this.modified = false; 62 this.submitting = false; 63 this.installing = false; 64 65 + this.newVersion = null; 66 + this.shouldShowNotice = false; 67 + 68 this.extensions = {}; 69 this.updates = {}; 70 for (const ext of moonlightNode.extensions) { ··· 72 this.extensions[uniqueId] = { 73 ...ext, 74 uniqueId, 75 + state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled, 76 + compat: checkExtensionCompat(ext.manifest), 77 + hasUpdate: false 78 }; 79 } 80 81 + this.checkUpdates(); 82 + } 83 84 + async checkUpdates() { 85 + await Promise.all([this.checkExtensionUpdates(), this.checkMoonlightUpdates()]); 86 + this.shouldShowNotice = this.newVersion != null || Object.keys(this.updates).length > 0; 87 + this.emitChange(); 88 + } 89 + 90 + private async checkExtensionUpdates() { 91 + const repositories = await natives!.fetchRepositories(this.savedConfig.repositories); 92 + 93 + // Reset update state 94 + for (const id in this.extensions) { 95 + const ext = this.extensions[id]; 96 + ext.hasUpdate = false; 97 + ext.changelog = undefined; 98 + } 99 + this.updates = {}; 100 + 101 + for (const [repo, exts] of Object.entries(repositories)) { 102 + for (const ext of exts) { 103 + const uniqueId = this.extensionIndex++; 104 + const extensionData = { 105 + id: ext.id, 106 + uniqueId, 107 + manifest: ext, 108 + source: { type: ExtensionLoadSource.Normal, url: repo }, 109 + state: ExtensionState.NotDownloaded, 110 + compat: ExtensionCompat.Compatible, 111 + hasUpdate: false 112 + }; 113 + 114 + // Don't present incompatible updates 115 + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) continue; 116 117 + const existing = this.getExisting(extensionData); 118 + if (existing != null) { 119 + // Make sure the download URL is properly updated 120 + existing.manifest = { 121 + ...existing.manifest, 122 + download: ext.download 123 + }; 124 125 + if (this.hasUpdate(extensionData)) { 126 + this.updates[existing.uniqueId] = { 127 + version: ext.version!, 128 + download: ext.download, 129 + updateManifest: ext 130 + }; 131 + existing.hasUpdate = true; 132 + existing.changelog = ext.meta?.changelog; 133 } 134 + } else { 135 + this.extensions[uniqueId] = extensionData; 136 } 137 } 138 + } 139 + } 140 141 + private async checkMoonlightUpdates() { 142 + this.newVersion = this.getExtensionConfigRaw("moonbase", "updateChecking", true) 143 + ? await natives!.checkForMoonlightUpdate() 144 + : null; 145 } 146 147 + private getExisting(ext: MoonbaseExtension) { 148 + return Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 149 } 150 151 private hasUpdate(ext: MoonbaseExtension) { 152 + const existing = Object.values(this.extensions).find((e) => e.id === ext.id && e.source.url === ext.source.url); 153 if (existing == null) return false; 154 155 + return existing.manifest.version !== ext.manifest.version && existing.state !== ExtensionState.NotDownloaded; 156 } 157 158 // Jank 159 private isModified() { 160 + const orig = JSON.stringify(this.savedConfig); 161 const curr = JSON.stringify(this.config); 162 return orig !== curr; 163 } ··· 166 return this.submitting || this.installing; 167 } 168 169 + // Required for the settings store contract 170 showNotice() { 171 return this.modified; 172 } ··· 176 } 177 178 getExtensionUniqueId(id: string) { 179 + return Object.values(this.extensions).find((ext) => ext.id === id)?.uniqueId; 180 } 181 182 getExtensionConflicting(uniqueId: number) { 183 const ext = this.getExtension(uniqueId); 184 if (ext.state !== ExtensionState.NotDownloaded) return false; 185 return Object.values(this.extensions).some( 186 + (e) => e.id === ext.id && e.uniqueId !== uniqueId && e.state !== ExtensionState.NotDownloaded 187 ); 188 } 189 ··· 206 207 getExtensionConfig<T>(uniqueId: number, key: string): T | undefined { 208 const ext = this.getExtension(uniqueId); 209 + const settings = ext.settingsOverride ?? ext.manifest.settings; 210 + return getConfigOption(ext.id, key, this.config, settings); 211 + } 212 213 + getExtensionConfigRaw<T>(id: string, key: string, defaultValue: T | undefined): T | undefined { 214 + const cfg = this.config.extensions[id]; 215 + if (cfg == null || typeof cfg === "boolean") return defaultValue; 216 + return cfg.config?.[key] ?? defaultValue; 217 } 218 219 getExtensionConfigName(uniqueId: number, key: string) { 220 const ext = this.getExtension(uniqueId); 221 + const settings = ext.settingsOverride ?? ext.manifest.settings; 222 + return settings?.[key]?.displayName ?? key; 223 } 224 225 getExtensionConfigDescription(uniqueId: number, key: string) { 226 const ext = this.getExtension(uniqueId); 227 + const settings = ext.settingsOverride ?? ext.manifest.settings; 228 + return settings?.[key]?.description; 229 } 230 231 + setExtensionConfig(id: string, key: string, value: any) { 232 + setConfigOption(this.config, id, key, value); 233 this.modified = this.isModified(); 234 this.emitChange(); 235 } ··· 256 this.emitChange(); 257 } 258 259 + dismissAllExtensionUpdates() { 260 + for (const id in this.extensions) { 261 + this.extensions[id].hasUpdate = false; 262 + } 263 + this.emitChange(); 264 + } 265 + 266 + async updateAllExtensions() { 267 + for (const id of Object.keys(this.updates)) { 268 + try { 269 + await this.installExtension(parseInt(id)); 270 + } catch (e) { 271 + logger.error("Error bulk updating extension", id, e); 272 + } 273 + } 274 + } 275 + 276 async installExtension(uniqueId: number) { 277 const ext = this.getExtension(uniqueId); 278 if (!("download" in ext.manifest)) { ··· 281 282 this.installing = true; 283 try { 284 + const update = this.updates[uniqueId]; 285 + const url = update?.download ?? ext.manifest.download; 286 + await natives!.installExtension(ext.manifest, url, ext.source.url!); 287 if (ext.state === ExtensionState.NotDownloaded) { 288 this.extensions[uniqueId].state = ExtensionState.Disabled; 289 } 290 291 + if (update != null) { 292 + const existing = this.extensions[uniqueId]; 293 + existing.settingsOverride = update.updateManifest.settings; 294 + existing.compat = checkExtensionCompat(update.updateManifest); 295 + existing.manifest = update.updateManifest; 296 + existing.changelog = update.updateManifest.meta?.changelog; 297 + } 298 + 299 delete this.updates[uniqueId]; 300 } catch (e) { 301 logger.error("Error installing extension:", e); 302 } 303 304 this.installing = false; 305 + this.restartAdvice = this.#computeRestartAdvice(); 306 this.emitChange(); 307 } 308 309 + private getRank(ext: MoonbaseExtension) { 310 + if (ext.source.type === ExtensionLoadSource.Developer) return 3; 311 + if (ext.source.type === ExtensionLoadSource.Core) return 2; 312 + if (ext.source.url === mainRepo) return 1; 313 + return 0; 314 + } 315 + 316 + async getDependencies(uniqueId: number) { 317 + const ext = this.getExtension(uniqueId); 318 + 319 + const missingDeps = []; 320 + for (const dep of ext.manifest.dependencies ?? []) { 321 + const anyInstalled = Object.values(this.extensions).some( 322 + (e) => e.id === dep && e.state !== ExtensionState.NotDownloaded 323 + ); 324 + if (!anyInstalled) missingDeps.push(dep); 325 + } 326 + 327 + if (missingDeps.length === 0) return null; 328 + 329 + const deps: Record<string, MoonbaseExtension[]> = {}; 330 + for (const dep of missingDeps) { 331 + const candidates = Object.values(this.extensions).filter((e) => e.id === dep); 332 + 333 + deps[dep] = candidates.sort((a, b) => { 334 + const aRank = this.getRank(a); 335 + const bRank = this.getRank(b); 336 + if (aRank === bRank) { 337 + const repoIndex = this.savedConfig.repositories.indexOf(a.source.url!); 338 + const otherRepoIndex = this.savedConfig.repositories.indexOf(b.source.url!); 339 + return repoIndex - otherRepoIndex; 340 + } else { 341 + return bRank - aRank; 342 + } 343 + }); 344 + } 345 + 346 + return deps; 347 + } 348 + 349 async deleteExtension(uniqueId: number) { 350 const ext = this.getExtension(uniqueId); 351 if (ext == null) return; 352 353 this.installing = true; 354 try { 355 + await natives!.deleteExtension(ext.id); 356 this.extensions[uniqueId].state = ExtensionState.NotDownloaded; 357 } catch (e) { 358 logger.error("Error deleting extension:", e); 359 } 360 361 this.installing = false; 362 + this.restartAdvice = this.#computeRestartAdvice(); 363 + this.emitChange(); 364 + } 365 + 366 + async updateMoonlight() { 367 + this.#updateState = UpdateState.Working; 368 + this.emitChange(); 369 + 370 + await natives 371 + .updateMoonlight() 372 + .then(() => (this.#updateState = UpdateState.Installed)) 373 + .catch((e) => { 374 + logger.error(e); 375 + this.#updateState = UpdateState.Failed; 376 + }); 377 + 378 this.emitChange(); 379 } 380 ··· 388 this.emitChange(); 389 } 390 391 + tryGetExtensionName(id: string) { 392 + const uniqueId = this.getExtensionUniqueId(id); 393 + return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; 394 + } 395 + 396 + registerConfigComponent(ext: string, name: string, component: CustomComponent) { 397 + if (!(ext in this.configComponents)) this.configComponents[ext] = {}; 398 + this.configComponents[ext][name] = component; 399 + } 400 + 401 + getExtensionConfigComponent(ext: string, name: string) { 402 + return this.configComponents[ext]?.[name]; 403 + } 404 + 405 + #computeRestartAdvice() { 406 + // If moonlight update needs a restart, always hide advice. 407 + if (this.#updateState === UpdateState.Installed) return RestartAdvice.NotNeeded; 408 + 409 + const i = this.initialConfig; // Initial config, from startup 410 + const n = this.config; // New config about to be saved 411 + 412 + let returnedAdvice = RestartAdvice.NotNeeded; 413 + const updateAdvice = (r: RestartAdvice) => (returnedAdvice < r ? (returnedAdvice = r) : returnedAdvice); 414 + 415 + // Top-level keys, repositories is not needed here because Moonbase handles it. 416 + if (i.patchAll !== n.patchAll) updateAdvice(RestartAdvice.ReloadNeeded); 417 + if (i.loggerLevel !== n.loggerLevel) updateAdvice(RestartAdvice.ReloadNeeded); 418 + if (diff(i.devSearchPaths ?? [], n.devSearchPaths ?? [], { cyclesFix: false }).length !== 0) 419 + return updateAdvice(RestartAdvice.RestartNeeded); 420 + 421 + // Extension specific logic 422 + for (const id in n.extensions) { 423 + // Installed extension (might not be detected yet) 424 + const ext = Object.values(this.extensions).find((e) => e.id === id && e.state !== ExtensionState.NotDownloaded); 425 + // Installed and detected extension 426 + const detected = moonlightNode.extensions.find((e) => e.id === id); 427 + 428 + // If it's not installed at all, we don't care 429 + if (!ext) continue; 430 + 431 + const initState = i.extensions[id]; 432 + const newState = n.extensions[id]; 433 + 434 + const newEnabled = typeof newState === "boolean" ? newState : newState.enabled; 435 + // If it's enabled but not detected yet, restart. 436 + if (newEnabled && !detected) { 437 + return updateAdvice(RestartAdvice.RestartNeeded); 438 + } 439 + 440 + // Toggling extensions specifically wants to rely on the initial state, 441 + // that's what was considered when loading extensions. 442 + const initEnabled = initState && (typeof initState === "boolean" ? initState : initState.enabled); 443 + if (initEnabled !== newEnabled || detected?.manifest.version !== ext.manifest.version) { 444 + // If we have the extension locally, we confidently know if it has host/preload scripts. 445 + // If not, we have to respect the environment specified in the manifest. 446 + // If that is the default, we can't know what's needed. 447 + 448 + if (detected?.scripts.hostPath || detected?.scripts.nodePath) { 449 + return updateAdvice(RestartAdvice.RestartNeeded); 450 + } 451 + 452 + switch (ext.manifest.environment) { 453 + case ExtensionEnvironment.Both: 454 + case ExtensionEnvironment.Web: 455 + updateAdvice(RestartAdvice.ReloadNeeded); 456 + continue; 457 + case ExtensionEnvironment.Desktop: 458 + return updateAdvice(RestartAdvice.RestartNeeded); 459 + default: 460 + updateAdvice(RestartAdvice.ReloadNeeded); 461 + continue; 462 + } 463 + } 464 + 465 + const initConfig = typeof initState === "boolean" ? {} : { ...initState?.config }; 466 + const newConfig = typeof newState === "boolean" ? {} : { ...newState?.config }; 467 + 468 + const def = ext.manifest.settings; 469 + if (!def) continue; 470 + 471 + for (const key in def) { 472 + const defaultValue = def[key].default; 473 + 474 + initConfig[key] ??= defaultValue; 475 + newConfig[key] ??= defaultValue; 476 + } 477 + 478 + const changedKeys = diff(initConfig, newConfig, { cyclesFix: false }).map((c) => c.path[0]); 479 + for (const key in def) { 480 + if (!changedKeys.includes(key)) continue; 481 + 482 + const advice = def[key].advice; 483 + switch (advice) { 484 + case ExtensionSettingsAdvice.None: 485 + updateAdvice(RestartAdvice.NotNeeded); 486 + continue; 487 + case ExtensionSettingsAdvice.Reload: 488 + updateAdvice(RestartAdvice.ReloadNeeded); 489 + continue; 490 + case ExtensionSettingsAdvice.Restart: 491 + updateAdvice(RestartAdvice.RestartNeeded); 492 + continue; 493 + default: 494 + updateAdvice(RestartAdvice.ReloadSuggested); 495 + } 496 + } 497 + } 498 + 499 + return returnedAdvice; 500 + } 501 + 502 writeConfig() { 503 this.submitting = true; 504 + this.restartAdvice = this.#computeRestartAdvice(); 505 + const modifiedRepos = diff(this.savedConfig.repositories, this.config.repositories); 506 507 + moonlightNode.writeConfig(this.config); 508 + this.savedConfig = this.clone(this.config); 509 510 this.submitting = false; 511 this.modified = false; 512 this.emitChange(); 513 + 514 + if (modifiedRepos.length !== 0) this.checkUpdates(); 515 } 516 517 reset() { 518 this.submitting = false; 519 this.modified = false; 520 + this.config = this.clone(this.savedConfig); 521 this.emitChange(); 522 + } 523 + 524 + restartDiscord() { 525 + if (moonlightNode.isBrowser) { 526 + window.location.reload(); 527 + } else { 528 + // @ts-expect-error TODO: DiscordNative 529 + window.DiscordNative.app.relaunch(); 530 + } 531 } 532 533 // Required because electron likes to make it immutable sometimes.
+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 + }
+29 -34
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( 4 - (v) => typeof v === "string" 5 - ) as string[]; 6 7 - import React from "@moonlight-mod/wp/common_react"; 8 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 9 import { 10 FormDivider, ··· 12 FormText, 13 FormSwitch, 14 TextInput, 15 - Flex, 16 Button, 17 SingleSelect, 18 Tooltip, 19 Clickable 20 - } from "@moonlight-mod/wp/common_components"; 21 - import CommonComponents from "@moonlight-mod/wp/common_components"; 22 23 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 24 25 - const FormClasses = spacepack.findByCode("dividerDefault:")[0].exports; 26 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 27 - 28 - let RemoveButtonClasses: any; 29 spacepack 30 .lazyLoad( 31 "renderArtisanalHack", ··· 34 ) 35 .then( 36 () => 37 - (RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 38 - .exports) 39 ); 40 41 - const { CircleXIcon } = CommonComponents; 42 function RemoveEntryButton({ onClick }: { onClick: () => void }) { 43 return ( 44 - <div className={RemoveButtonClasses.removeButtonContainer}> 45 <Tooltip text="Remove entry" position="top"> 46 {(props: any) => ( 47 - <Clickable 48 - {...props} 49 - className={RemoveButtonClasses.removeButton} 50 - onClick={onClick} 51 - > 52 <CircleXIcon width={24} height={24} /> 53 </Clickable> 54 )} ··· 57 ); 58 } 59 60 - function ArrayFormItem({ 61 - config 62 - }: { 63 - config: "repositories" | "devSearchPaths"; 64 - }) { 65 const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 66 return ( 67 <Flex ··· 119 export default function ConfigPage() { 120 return ( 121 <> 122 <FormItem title="Repositories"> 123 - <FormText className={Margins.marginBottom4}> 124 - A list of remote repositories to display extensions from 125 - </FormText> 126 <ArrayFormItem config="repositories" /> 127 </FormItem> 128 - <FormDivider className={FormClasses.dividerDefault} /> 129 <FormItem title="Extension search paths" className={Margins.marginTop20}> 130 <FormText className={Margins.marginBottom4}> 131 A list of local directories to search for built extensions 132 </FormText> 133 <ArrayFormItem config="devSearchPaths" /> 134 </FormItem> 135 - <FormDivider className={FormClasses.dividerDefault} /> 136 <FormSwitch 137 className={Margins.marginTop20} 138 - value={MoonbaseSettingsStore.getConfigOption("patchAll")} 139 onChange={(value: boolean) => { 140 MoonbaseSettingsStore.setConfigOption("patchAll", value); 141 }} ··· 152 value: o.toLowerCase(), 153 label: o[0] + o.slice(1).toLowerCase() 154 }))} 155 - onChange={(v) => 156 - MoonbaseSettingsStore.setConfigOption("loggerLevel", v) 157 - } 158 /> 159 </FormItem> 160 </>
··· 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, ··· 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", ··· 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 )} ··· 50 ); 51 } 52 53 + function ArrayFormItem({ config }: { config: "repositories" | "devSearchPaths" }) { 54 const items = MoonbaseSettingsStore.getConfigOption(config) ?? []; 55 return ( 56 <Flex ··· 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 }} ··· 149 value: o.toLowerCase(), 150 label: o[0] + o.slice(1).toLowerCase() 151 }))} 152 + onChange={(v) => MoonbaseSettingsStore.setConfigOption("loggerLevel", v)} 153 /> 154 </FormItem> 155 </>
+285 -156
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx
··· 1 import { ExtensionState } from "../../../types"; 2 - import { ExtensionLoadSource } from "@moonlight-mod/types"; 3 - 4 - import React from "@moonlight-mod/wp/common_react"; 5 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 6 - import CommonComponents from "@moonlight-mod/wp/common_components"; 7 - import * as Flux from "@moonlight-mod/wp/common_flux"; 8 9 import ExtensionInfo from "./info"; 10 import Settings from "./settings"; 11 12 export enum ExtensionPage { 13 Info, 14 Description, 15 Settings 16 } 17 18 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 19 20 - const { DownloadIcon, TrashIcon, CircleWarningIcon } = CommonComponents; 21 22 - const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z; 23 - const TabBarClasses = spacepack.findByExports( 24 - "tabBar", 25 - "tabBarItem", 26 - "headerContentWrapper" 27 - )[0].exports; 28 29 - export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { 30 - const [tab, setTab] = React.useState(ExtensionPage.Info); 31 - const [restartNeeded, setRestartNeeded] = React.useState(false); 32 33 - const { ext, enabled, busy, update, conflicting } = Flux.useStateFromStores( 34 - [MoonbaseSettingsStore], 35 - () => { 36 - return { 37 - ext: MoonbaseSettingsStore.getExtension(uniqueId), 38 - enabled: MoonbaseSettingsStore.getExtensionEnabled(uniqueId), 39 - busy: MoonbaseSettingsStore.busy, 40 - update: MoonbaseSettingsStore.getExtensionUpdate(uniqueId), 41 - conflicting: MoonbaseSettingsStore.getExtensionConflicting(uniqueId) 42 - }; 43 - } 44 ); 45 46 - // Why it work like that :sob: 47 - if (ext == null) return <></>; 48 49 - const { 50 - Card, 51 - CardClasses, 52 - Flex, 53 - Text, 54 - MarkdownParser, 55 - Switch, 56 - TabBar, 57 - Button 58 - } = CommonComponents; 59 60 - const tagline = ext.manifest?.meta?.tagline; 61 - const settings = ext.manifest?.settings; 62 - const description = ext.manifest?.meta?.description; 63 64 - return ( 65 - <Card editable={true} className={CardClasses.card}> 66 - <div className={CardClasses.cardHeader}> 67 - <Flex direction={Flex.Direction.VERTICAL}> 68 - <Flex direction={Flex.Direction.HORIZONTAL}> 69 - <Text variant="text-md/semibold"> 70 - {ext.manifest?.meta?.name ?? ext.id} 71 - </Text> 72 </Flex> 73 74 - {tagline != null && ( 75 - <Text variant="text-sm/normal"> 76 - {MarkdownParser.parse(tagline)} 77 - </Text> 78 - )} 79 </Flex> 80 81 - <Flex 82 - direction={Flex.Direction.HORIZONTAL} 83 - align={Flex.Align.END} 84 - justify={Flex.Justify.END} 85 - > 86 - {ext.state === ExtensionState.NotDownloaded ? ( 87 - <Button 88 - color={Button.Colors.BRAND} 89 - submitting={busy} 90 - disabled={conflicting} 91 - onClick={() => { 92 - MoonbaseSettingsStore.installExtension(uniqueId); 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 - <PanelButton 108 - icon={TrashIcon} 109 - tooltipText="Delete" 110 - onClick={() => { 111 - MoonbaseSettingsStore.deleteExtension(uniqueId); 112 - }} 113 - /> 114 - )} 115 116 - {update != null && ( 117 - <PanelButton 118 - icon={DownloadIcon} 119 - tooltipText="Update" 120 - onClick={() => { 121 - MoonbaseSettingsStore.installExtension(uniqueId); 122 - }} 123 - /> 124 )} 125 126 - {restartNeeded && ( 127 - <PanelButton 128 - icon={() => ( 129 - <CircleWarningIcon 130 - color={CommonComponents.tokens.colors.STATUS_DANGER} 131 - /> 132 - )} 133 - onClick={() => window.location.reload()} 134 - tooltipText="You will need to reload/restart your client for this extension to work properly." 135 - /> 136 )} 137 138 - <Switch 139 - checked={enabled} 140 - onChange={() => { 141 - setRestartNeeded(true); 142 - MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); 143 - }} 144 - /> 145 - </div> 146 - )} 147 - </Flex> 148 - </div> 149 150 - <div> 151 - {(description != null || settings != null) && ( 152 - <TabBar 153 - selectedItem={tab} 154 - type="top" 155 - onItemSelect={setTab} 156 - className={TabBarClasses.tabBar} 157 - style={{ 158 - padding: "0 20px" 159 - }} 160 - > 161 - <TabBar.Item 162 - className={TabBarClasses.tabBarItem} 163 - id={ExtensionPage.Info} 164 > 165 - Info 166 - </TabBar.Item> 167 - 168 - {description != null && ( 169 - <TabBar.Item 170 - className={TabBarClasses.tabBarItem} 171 - id={ExtensionPage.Description} 172 - > 173 - Description 174 - </TabBar.Item> 175 - )} 176 - 177 - {settings != null && ( 178 - <TabBar.Item 179 - className={TabBarClasses.tabBarItem} 180 - id={ExtensionPage.Settings} 181 - > 182 - Settings 183 - </TabBar.Item> 184 - )} 185 - </TabBar> 186 )} 187 188 <Flex 189 justify={Flex.Justify.START} 190 wrap={Flex.Wrap.WRAP} 191 style={{ 192 - padding: "16px 16px" 193 }} 194 > 195 - {tab === ExtensionPage.Info && <ExtensionInfo ext={ext} />} 196 {tab === ExtensionPage.Description && ( 197 - <Text variant="text-md/normal"> 198 - {MarkdownParser.parse(description ?? "*No description*")} 199 </Text> 200 )} 201 - {tab === ExtensionPage.Settings && <Settings ext={ext} />} 202 </Flex> 203 </div> 204 </Card>
··· 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>
+119 -119
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx
··· 1 import { tagNames } from "./info"; 2 - 3 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 4 - import React from "@moonlight-mod/wp/common_react"; 5 - import * as Flux from "@moonlight-mod/wp/common_flux"; 6 import { WindowStore } from "@moonlight-mod/wp/common_stores"; 7 import { 8 Button, ··· 11 Popout, 12 Dialog, 13 Menu, 14 - MenuGroup, 15 - MenuCheckboxItem, 16 - MenuItem 17 - } from "@moonlight-mod/wp/common_components"; 18 - import CommonComponents from "@moonlight-mod/wp/common_components"; 19 20 export enum Filter { 21 Core = 1 << 0, ··· 24 Enabled = 1 << 3, 25 Disabled = 1 << 4, 26 Installed = 1 << 5, 27 - Repository = 1 << 6 28 } 29 - export const defaultFilter = ~(~0 << 7); 30 - 31 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 32 - const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0] 33 - .exports; 34 35 - let FilterDialogClasses: any; 36 - let FilterBarClasses: any; 37 spacepack 38 - .lazyLoad( 39 - '"Missing channel in Channel.openChannelContextMenu"', 40 - /e\("(\d+)"\)/g, 41 - /webpackId:(\d+?),/ 42 - ) 43 .then(() => { 44 - FilterBarClasses = spacepack.findByCode("tagsButtonWithCount:")[0].exports; 45 - FilterDialogClasses = spacepack.findByCode( 46 - "countContainer:", 47 - "tagContainer:" 48 - )[0].exports; 49 }); 50 51 - const TagItem = spacepack.findByCode(".FORUM_TAG_A11Y_FILTER_BY_TAG")[0].exports 52 - .Z; 53 - 54 - const { ChevronSmallDownIcon, ChevronSmallUpIcon, ArrowsUpDownIcon } = 55 - CommonComponents; 56 - 57 - function toggleTag( 58 - selectedTags: Set<string>, 59 - setSelectedTags: (tags: Set<string>) => void, 60 - tag: string 61 - ) { 62 const newState = new Set(selectedTags); 63 if (newState.has(tag)) newState.delete(tag); 64 else newState.add(tag); ··· 74 setFilter: (filter: Filter) => void; 75 closePopout: () => void; 76 }) { 77 - const toggleFilter = (set: Filter) => 78 - setFilter(filter & set ? filter & ~set : filter | set); 79 80 return ( 81 <div className={SortMenuClasses.container}> 82 - <Menu navId="sort-filter" hideScrollbar={true} onClose={closePopout}> 83 <MenuGroup label="Type"> 84 <MenuCheckboxItem 85 id="t-core" 86 label="Core" 87 - checked={filter & Filter.Core} 88 action={() => toggleFilter(Filter.Core)} 89 /> 90 <MenuCheckboxItem 91 id="t-normal" 92 label="Normal" 93 - checked={filter & Filter.Normal} 94 action={() => toggleFilter(Filter.Normal)} 95 /> 96 <MenuCheckboxItem 97 id="t-developer" 98 label="Developer" 99 - checked={filter & Filter.Developer} 100 action={() => toggleFilter(Filter.Developer)} 101 /> 102 </MenuGroup> ··· 104 <MenuCheckboxItem 105 id="s-enabled" 106 label="Enabled" 107 - checked={filter & Filter.Enabled} 108 action={() => toggleFilter(Filter.Enabled)} 109 /> 110 <MenuCheckboxItem 111 id="s-disabled" 112 label="Disabled" 113 - checked={filter & Filter.Disabled} 114 action={() => toggleFilter(Filter.Disabled)} 115 /> 116 </MenuGroup> ··· 118 <MenuCheckboxItem 119 id="l-installed" 120 label="Installed" 121 - checked={filter & Filter.Installed} 122 action={() => toggleFilter(Filter.Installed)} 123 /> 124 <MenuCheckboxItem 125 id="l-repository" 126 label="Repository" 127 - checked={filter & Filter.Repository} 128 action={() => toggleFilter(Filter.Repository)} 129 /> 130 </MenuGroup> 131 <MenuGroup> 132 <MenuItem 133 id="reset-all" 134 className={SortMenuClasses.clearText} 135 - label={ 136 - <Text variant="text-sm/medium" color="none"> 137 - Reset to default 138 - </Text> 139 - } 140 action={() => { 141 setFilter(defaultFilter); 142 closePopout(); ··· 148 ); 149 } 150 151 - function TagButtonPopout({ 152 - selectedTags, 153 - setSelectedTags, 154 - setPopoutRef, 155 - closePopout 156 - }: any) { 157 return ( 158 - <Dialog ref={setPopoutRef} className={FilterDialogClasses.container}> 159 - <div className={FilterDialogClasses.header}> 160 - <div className={FilterDialogClasses.headerLeft}> 161 - <Heading 162 - color="interactive-normal" 163 - variant="text-xs/bold" 164 - className={FilterDialogClasses.headerText} 165 - > 166 Select tags 167 </Heading> 168 - <div className={FilterDialogClasses.countContainer}> 169 - <Text 170 - className={FilterDialogClasses.countText} 171 - color="none" 172 - variant="text-xs/medium" 173 - > 174 {selectedTags.size} 175 </Text> 176 </div> 177 </div> 178 </div> 179 - <div className={FilterDialogClasses.tagContainer}> 180 {Object.keys(tagNames).map((tag) => ( 181 <TagItem 182 key={tag} 183 - className={FilterDialogClasses.tag} 184 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 185 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 186 selected={selectedTags.has(tag)} 187 /> 188 ))} 189 </div> 190 - <div className={FilterDialogClasses.separator} /> 191 <Button 192 look={Button.Looks.LINK} 193 size={Button.Sizes.MIN} 194 color={Button.Colors.CUSTOM} 195 - className={FilterDialogClasses.clear} 196 onClick={() => { 197 setSelectedTags(new Set()); 198 closePopout(); ··· 217 selectedTags: Set<string>; 218 setSelectedTags: (tags: Set<string>) => void; 219 }) { 220 - const windowSize = Flux.useStateFromStores([WindowStore], () => 221 - WindowStore.windowSize() 222 - ); 223 224 const tagsContainer = React.useRef<HTMLDivElement>(null); 225 const tagListInner = React.useRef<HTMLDivElement>(null); 226 const [tagsButtonOffset, setTagsButtonOffset] = React.useState(0); 227 React.useLayoutEffect(() => { 228 if (tagsContainer.current === null || tagListInner.current === null) return; 229 - const { left: containerX, top: containerY } = 230 - tagsContainer.current.getBoundingClientRect(); 231 let offset = 0; 232 for (const child of tagListInner.current.children) { 233 - const { 234 - right: childX, 235 - top: childY, 236 - height 237 - } = child.getBoundingClientRect(); 238 if (childY - containerY > height) break; 239 const newOffset = childX - containerX; 240 if (newOffset > offset) { ··· 242 } 243 } 244 setTagsButtonOffset(offset); 245 - }, [windowSize]); 246 247 return ( 248 <div ··· 250 style={{ 251 paddingTop: "12px" 252 }} 253 - className={`${FilterBarClasses.tagsContainer} ${Margins.marginBottom8}`} 254 > 255 <Popout 256 renderPopout={({ closePopout }: any) => ( 257 - <FilterButtonPopout 258 - filter={filter} 259 - setFilter={setFilter} 260 - closePopout={closePopout} 261 - /> 262 )} 263 position="bottom" 264 align="left" ··· 268 {...props} 269 size={Button.Sizes.MIN} 270 color={Button.Colors.CUSTOM} 271 - className={FilterBarClasses.sortDropdown} 272 - innerClassName={FilterBarClasses.sortDropdownInner} 273 > 274 <ArrowsUpDownIcon size="xs" /> 275 - <Text 276 - className={FilterBarClasses.sortDropdownText} 277 - variant="text-sm/medium" 278 - color="interactive-normal" 279 - > 280 Sort & filter 281 </Text> 282 {isShown ? ( ··· 287 </Button> 288 )} 289 </Popout> 290 - <div className={FilterBarClasses.divider} /> 291 - <div className={FilterBarClasses.tagList}> 292 - <div ref={tagListInner} className={FilterBarClasses.tagListInner}> 293 {Object.keys(tagNames).map((tag) => ( 294 <TagItem 295 key={tag} 296 - className={FilterBarClasses.tag} 297 - tag={{ name: tagNames[tag as keyof typeof tagNames] }} 298 onClick={() => toggleTag(selectedTags, setSelectedTags, tag)} 299 selected={selectedTags.has(tag)} 300 /> ··· 322 left: tagsButtonOffset 323 }} 324 // TODO: Use Discord's class name utility 325 - className={`${FilterBarClasses.tagsButton} ${ 326 - selectedTags.size > 0 ? FilterBarClasses.tagsButtonWithCount : "" 327 - }`} 328 - innerClassName={FilterBarClasses.tagsButtonInner} 329 > 330 {selectedTags.size > 0 ? ( 331 - <div 332 - style={{ boxSizing: "content-box" }} 333 - className={FilterBarClasses.countContainer} 334 - > 335 - <Text 336 - className={FilterBarClasses.countText} 337 - color="none" 338 - variant="text-xs/medium" 339 - > 340 {selectedTags.size} 341 </Text> 342 </div> ··· 351 </Button> 352 )} 353 </Popout> 354 </div> 355 ); 356 }
··· 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, ··· 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, ··· 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); ··· 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> ··· 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> ··· 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(); ··· 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(); ··· 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) { ··· 218 } 219 } 220 setTagsButtonOffset(offset); 221 + }, [windowSize, tagsContainer.current, tagListInner.current, tagListInner.current?.getBoundingClientRect()?.width]); 222 223 return ( 224 <div ··· 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" ··· 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 ? ( ··· 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 /> ··· 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> ··· 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 }
+110 -51
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx
··· 3 import FilterBar, { Filter, defaultFilter } from "./filterBar"; 4 import ExtensionCard from "./card"; 5 6 - import React from "@moonlight-mod/wp/common_react"; 7 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 8 - import * as Flux from "@moonlight-mod/wp/common_flux"; 9 10 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 11 12 - const SearchBar: any = Object.values( 13 - spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0].exports 14 - )[0]; 15 16 export default function ExtensionsPage() { 17 - const moonbaseId = MoonbaseSettingsStore.getExtensionUniqueId("moonbase")!; 18 - const { extensions, savedFilter } = Flux.useStateFromStoresObject( 19 - [MoonbaseSettingsStore], 20 - () => { 21 - return { 22 - extensions: MoonbaseSettingsStore.extensions, 23 - savedFilter: MoonbaseSettingsStore.getExtensionConfig( 24 - moonbaseId, 25 - "filter" 26 - ) 27 - }; 28 - } 29 - ); 30 31 const [query, setQuery] = React.useState(""); 32 33 let filter: Filter, setFilter: (filter: Filter) => void; 34 - if (moonlight.getConfigOption<boolean>("moonbase", "saveFilter")) { 35 filter = savedFilter ?? defaultFilter; 36 - setFilter = (filter) => 37 - MoonbaseSettingsStore.setExtensionConfig(moonbaseId, "filter", filter); 38 } else { 39 - const state = React.useState(defaultFilter); 40 - filter = state[0]; 41 - setFilter = state[1]; 42 } 43 const [selectedTags, setSelectedTags] = React.useState(new Set<string>()); 44 const sorted = Object.values(extensions).sort((a, b) => { 45 const aName = a.manifest.meta?.name ?? a.id; 46 const bName = b.manifest.meta?.name ?? b.id; ··· 49 50 const filtered = sorted.filter( 51 (ext) => 52 - (ext.manifest.meta?.name?.toLowerCase().includes(query) || 53 ext.manifest.meta?.tagline?.toLowerCase().includes(query) || 54 ext.manifest.meta?.description?.toLowerCase().includes(query)) && 55 - [...selectedTags.values()].every( 56 - (tag) => ext.manifest.meta?.tags?.includes(tag as ExtensionTag) 57 - ) && 58 // This seems very bad, sorry 59 !( 60 - (!(filter & Filter.Core) && 61 - ext.source.type === ExtensionLoadSource.Core) || 62 - (!(filter & Filter.Normal) && 63 - ext.source.type === ExtensionLoadSource.Normal) || 64 - (!(filter & Filter.Developer) && 65 - ext.source.type === ExtensionLoadSource.Developer) || 66 - (!(filter & Filter.Enabled) && 67 - MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 68 - (!(filter & Filter.Disabled) && 69 - !MoonbaseSettingsStore.getExtensionEnabled(ext.uniqueId)) || 70 - (!(filter & Filter.Installed) && 71 - ext.state !== ExtensionState.NotDownloaded) || 72 - (!(filter & Filter.Repository) && 73 - ext.state === ExtensionState.NotDownloaded) 74 - ) 75 ); 76 77 return ( 78 <> ··· 89 spellCheck: "false" 90 }} 91 /> 92 - <FilterBar 93 - filter={filter} 94 - setFilter={setFilter} 95 - selectedTags={selectedTags} 96 - setSelectedTags={setSelectedTags} 97 - /> 98 - {filtered.map((ext) => ( 99 - <ExtensionCard uniqueId={ext.uniqueId} key={ext.id} /> 100 ))} 101 </> 102 );
··· 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; ··· 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 <> ··· 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 );
+55 -54
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/common_react"; 5 - import CommonComponents from "@moonlight-mod/wp/common_components"; 6 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 7 8 type Dependency = { 9 id: string; ··· 34 [ExtensionTag.Library]: "Library" 35 }; 36 37 - const UserInfoClasses = spacepack.findByCode( 38 - "infoScroller", 39 - "userInfoSection", 40 - "userInfoSectionHeader" 41 - )[0].exports; 42 - 43 import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 44 45 - function InfoSection({ 46 - title, 47 - children 48 - }: { 49 - title: string; 50 - children: React.ReactNode; 51 - }) { 52 return ( 53 <div 54 style={{ 55 marginRight: "1em" 56 }} 57 > 58 - <CommonComponents.Text 59 - variant="eyebrow" 60 - className={UserInfoClasses.userInfoSectionHeader} 61 - > 62 {title} 63 - </CommonComponents.Text> 64 65 - <CommonComponents.Text variant="text-sm/normal"> 66 - {children} 67 - </CommonComponents.Text> 68 </div> 69 ); 70 } 71 72 function Badge({ 73 color, 74 - children 75 }: { 76 color: string; 77 children: React.ReactNode; 78 }) { 79 return ( 80 <span 81 - style={{ 82 - borderRadius: ".1875rem", 83 - padding: "0 0.275rem", 84 - marginRight: "0.4em", 85 - backgroundColor: color, 86 - color: "#fff" 87 - }} 88 > 89 {children} 90 </span> 91 ); 92 } 93 94 - export default function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) { 95 const authors = ext.manifest?.meta?.authors; 96 const tags = ext.manifest?.meta?.tags; 97 const version = ext.manifest?.version; 98 99 const dependencies: Dependency[] = []; 100 if (ext.manifest.dependencies != null) { 101 dependencies.push( 102 ...ext.manifest.dependencies.map((dep) => ({ ··· 116 } 117 118 if (ext.manifest.incompatible != null) { 119 - dependencies.push( 120 ...ext.manifest.incompatible.map((dep) => ({ 121 id: dep, 122 type: DependencyType.Incompatible ··· 154 <InfoSection title="Tags"> 155 {tags.map((tag, i) => { 156 const name = tagNames[tag]; 157 158 return ( 159 - <Badge 160 - key={i} 161 - color={ 162 - tag === ExtensionTag.DangerZone 163 - ? "var(--red-400)" 164 - : "var(--brand-500)" 165 - } 166 - > 167 {name} 168 </Badge> 169 ); ··· 174 {dependencies.length > 0 && ( 175 <InfoSection title="Dependencies"> 176 {dependencies.map((dep) => { 177 - const colors = { 178 - [DependencyType.Dependency]: "var(--brand-500)", 179 - [DependencyType.Optional]: "var(--orange-400)", 180 - [DependencyType.Incompatible]: "var(--red-400)" 181 - }; 182 - const color = colors[dep.type]; 183 - const id = MoonbaseSettingsStore.getExtensionUniqueId(dep.id); 184 - const name = 185 - (id !== null 186 - ? MoonbaseSettingsStore.getExtensionName(id!) 187 - : null) ?? dep.id; 188 return ( 189 - <Badge color={color} key={dep.id}> 190 {name} 191 </Badge> 192 );
··· 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; ··· 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) => ({ ··· 112 } 113 114 if (ext.manifest.incompatible != null) { 115 + incompatible.push( 116 ...ext.manifest.incompatible.map((dep) => ({ 117 id: dep, 118 type: DependencyType.Incompatible ··· 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 ); ··· 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 );
+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 + }
+133 -138
packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx
··· 9 10 import { ExtensionState, MoonbaseExtension } from "../../../types"; 11 12 - import React from "@moonlight-mod/wp/common_react"; 13 - import CommonComponents from "@moonlight-mod/wp/common_components"; 14 - import * as Flux from "@moonlight-mod/wp/common_flux"; 15 import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 16 17 type SettingsProps = { 18 ext: MoonbaseExtension; ··· 20 setting: ExtensionSettingsManifest; 21 disabled: boolean; 22 }; 23 - 24 type SettingsComponent = React.ComponentType<SettingsProps>; 25 26 - import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; 27 28 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 29 30 function useConfigEntry<T>(uniqueId: number, name: string) { 31 - return Flux.useStateFromStores( 32 [MoonbaseSettingsStore], 33 () => { 34 return { 35 value: MoonbaseSettingsStore.getExtensionConfig<T>(uniqueId, name), 36 - displayName: MoonbaseSettingsStore.getExtensionConfigName( 37 - uniqueId, 38 - name 39 - ), 40 - description: MoonbaseSettingsStore.getExtensionConfigDescription( 41 - uniqueId, 42 - name 43 - ) 44 }; 45 }, 46 [uniqueId, name] ··· 48 } 49 50 function Boolean({ ext, name, setting, disabled }: SettingsProps) { 51 - const { FormSwitch } = CommonComponents; 52 - const { value, displayName, description } = useConfigEntry<boolean>( 53 - ext.uniqueId, 54 - name 55 - ); 56 57 return ( 58 <FormSwitch ··· 60 hideBorder={true} 61 disabled={disabled} 62 onChange={(value: boolean) => { 63 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 64 }} 65 - note={description} 66 className={`${Margins.marginReset} ${Margins.marginTop20}`} 67 > 68 {displayName} ··· 71 } 72 73 function Number({ ext, name, setting, disabled }: SettingsProps) { 74 - const { FormItem, FormText, Slider } = CommonComponents; 75 - const { value, displayName, description } = useConfigEntry<number>( 76 - ext.uniqueId, 77 - name 78 - ); 79 80 const castedSetting = setting as NumberSettingType; 81 - const min = castedSetting.min ?? 0; 82 - const max = castedSetting.max ?? 100; 83 84 return ( 85 <FormItem className={Margins.marginTop20} title={displayName}> 86 - {description && <FormText>{description}</FormText>} 87 - <Slider 88 - initialValue={value ?? 0} 89 - disabled={disabled} 90 - minValue={castedSetting.min ?? 0} 91 - maxValue={castedSetting.max ?? 100} 92 - onValueChange={(value: number) => { 93 - const rounded = Math.max(min, Math.min(max, Math.round(value))); 94 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, rounded); 95 - }} 96 - /> 97 </FormItem> 98 ); 99 } 100 101 function String({ ext, name, setting, disabled }: SettingsProps) { 102 - const { FormItem, FormText, TextInput } = CommonComponents; 103 - const { value, displayName, description } = useConfigEntry<string>( 104 - ext.uniqueId, 105 - name 106 - ); 107 108 return ( 109 <FormItem className={Margins.marginTop20} title={displayName}> 110 - {description && ( 111 - <FormText className={Margins.marginBottom8}>{description}</FormText> 112 - )} 113 <TextInput 114 value={value ?? ""} 115 - onChange={(value: string) => { 116 - if (disabled) return; 117 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 118 - }} 119 /> 120 </FormItem> 121 ); 122 } 123 124 function MultilineString({ ext, name, setting, disabled }: SettingsProps) { 125 - const { FormItem, FormText, TextArea } = CommonComponents; 126 - const { value, displayName, description } = useConfigEntry<string>( 127 - ext.uniqueId, 128 - name 129 - ); 130 131 return ( 132 <FormItem className={Margins.marginTop20} title={displayName}> 133 - {description && ( 134 - <FormText className={Margins.marginBottom8}>{description}</FormText> 135 - )} 136 <TextArea 137 rows={5} 138 value={value ?? ""} 139 className={"moonbase-resizeable"} 140 - onChange={(value: string) => { 141 - if (disabled) return; 142 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 143 - }} 144 /> 145 </FormItem> 146 ); 147 } 148 149 function Select({ ext, name, setting, disabled }: SettingsProps) { 150 - const { FormItem, FormText, SingleSelect } = CommonComponents; 151 - const { value, displayName, description } = useConfigEntry<string>( 152 - ext.uniqueId, 153 - name 154 - ); 155 156 const castedSetting = setting as SelectSettingType; 157 const options = castedSetting.options; 158 159 return ( 160 <FormItem className={Margins.marginTop20} title={displayName}> 161 - {description && ( 162 - <FormText className={Margins.marginBottom8}>{description}</FormText> 163 - )} 164 <SingleSelect 165 autofocus={false} 166 clearable={false} 167 value={value ?? ""} 168 - options={options.map((o: SelectOption) => 169 - typeof o === "string" ? { value: o, label: o } : o 170 - )} 171 onChange={(value: string) => { 172 if (disabled) return; 173 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); 174 }} 175 /> 176 </FormItem> ··· 178 } 179 180 function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { 181 - const { FormItem, FormText, Select, useVariableSelect, multiSelect } = 182 - CommonComponents; 183 - const { value, displayName, description } = useConfigEntry<string | string[]>( 184 - ext.uniqueId, 185 - name 186 - ); 187 188 const castedSetting = setting as MultiSelectSettingType; 189 const options = castedSetting.options; 190 191 return ( 192 <FormItem className={Margins.marginTop20} title={displayName}> 193 - {description && ( 194 - <FormText className={Margins.marginBottom8}>{description}</FormText> 195 - )} 196 - <Select 197 autofocus={false} 198 clearable={false} 199 closeOnSelect={false} 200 - options={options.map((o: SelectOption) => 201 - typeof o === "string" ? { value: o, label: o } : o 202 - )} 203 {...useVariableSelect({ 204 onSelectInteraction: multiSelect, 205 - value: new Set(Array.isArray(value) ? value : [value]), 206 onChange: (value: string) => { 207 if (disabled) return; 208 - MoonbaseSettingsStore.setExtensionConfig( 209 - ext.uniqueId, 210 - name, 211 - Array.from(value) 212 - ); 213 } 214 })} 215 /> ··· 217 ); 218 } 219 220 - const RemoveButtonClasses = spacepack.findByCode("removeButtonContainer")[0] 221 - .exports; 222 - const CircleXIcon = CommonComponents.CircleXIcon; 223 - function RemoveEntryButton({ 224 - onClick, 225 - disabled 226 - }: { 227 - onClick: () => void; 228 - disabled: boolean; 229 - }) { 230 - const { Tooltip, Clickable } = CommonComponents; 231 return ( 232 - <div className={RemoveButtonClasses.removeButtonContainer}> 233 <Tooltip text="Remove entry" position="top"> 234 {(props: any) => ( 235 - <Clickable 236 - {...props} 237 - className={RemoveButtonClasses.removeButton} 238 - onClick={onClick} 239 - > 240 <CircleXIcon width={16} height={16} /> 241 </Clickable> 242 )} ··· 246 } 247 248 function List({ ext, name, setting, disabled }: SettingsProps) { 249 - const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents; 250 - const { value, displayName, description } = useConfigEntry<string[]>( 251 - ext.uniqueId, 252 - name 253 - ); 254 255 const entries = value ?? []; 256 - const updateConfig = () => 257 - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, entries); 258 259 return ( 260 <FormItem className={Margins.marginTop20} title={displayName}> 261 - {description && ( 262 - <FormText className={Margins.marginBottom4}>{description}</FormText> 263 - )} 264 <Flex direction={Flex.Direction.VERTICAL}> 265 {entries.map((val, i) => ( 266 // FIXME: stylesheets ··· 312 } 313 314 function Dictionary({ ext, name, setting, disabled }: SettingsProps) { 315 - const { FormItem, FormText, TextInput, Button, Flex } = CommonComponents; 316 - const { value, displayName, description } = useConfigEntry< 317 - Record<string, string> 318 - >(ext.uniqueId, name); 319 320 const entries = Object.entries(value ?? {}); 321 - const updateConfig = () => 322 - MoonbaseSettingsStore.setExtensionConfig( 323 - ext.uniqueId, 324 - name, 325 - Object.fromEntries(entries) 326 - ); 327 328 return ( 329 <FormItem className={Margins.marginTop20} title={displayName}> 330 - {description && ( 331 - <FormText className={Margins.marginBottom4}>{description}</FormText> 332 - )} 333 <Flex direction={Flex.Direction.VERTICAL}> 334 {entries.map(([key, val], i) => ( 335 // FIXME: stylesheets ··· 389 ); 390 } 391 392 function Setting({ ext, name, setting, disabled }: SettingsProps) { 393 const elements: Partial<Record<ExtensionSettingType, SettingsComponent>> = { 394 [ExtensionSettingType.Boolean]: Boolean, ··· 398 [ExtensionSettingType.Select]: Select, 399 [ExtensionSettingType.MultiSelect]: MultiSelect, 400 [ExtensionSettingType.List]: List, 401 - [ExtensionSettingType.Dictionary]: Dictionary 402 }; 403 const element = elements[setting.type]; 404 if (element == null) return <></>; ··· 406 } 407 408 export default function Settings({ ext }: { ext: MoonbaseExtension }) { 409 - const { Flex } = CommonComponents; 410 return ( 411 <Flex className="moonbase-settings" direction={Flex.Direction.VERTICAL}> 412 - {Object.entries(ext.manifest.settings!).map(([name, setting]) => ( 413 <Setting 414 ext={ext} 415 key={name}
··· 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; ··· 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] ··· 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 ··· 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} ··· 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> ··· 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 /> ··· 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 )} ··· 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 ··· 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 ··· 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, ··· 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 <></>; ··· 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}
+28 -29
packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx
··· 1 - import React from "@moonlight-mod/wp/common_react"; 2 - import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; 3 - import { Text, TabBar } from "@moonlight-mod/wp/common_components"; 4 - import * as Flux from "@moonlight-mod/wp/common_flux"; 5 import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores"; 6 7 import ExtensionsPage from "./extensions"; 8 import ConfigPage from "./config"; 9 - 10 - const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; 11 - 12 - const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z; 13 - const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0] 14 - .exports; 15 - const TabBarClasses = spacepack.findByCode("nowPlayingColumn:")[0].exports; 16 - const { setSection, clearSubsection } = spacepack.findByExports( 17 - "setSection", 18 - "clearSubsection" 19 - )[0].exports.Z; 20 21 export const pages: { 22 id: string; ··· 32 id: "config", 33 name: "Config", 34 element: ConfigPage 35 } 36 ]; 37 38 export function Moonbase(props: { initialTab?: number } = {}) { 39 - const subsection = Flux.useStateFromStores( 40 - [UserSettingsModalStore], 41 - () => UserSettingsModalStore.getSubsection() ?? 0 42 - ); 43 const setSubsection = React.useCallback( 44 (to: string) => { 45 - if (subsection !== to) setSection("moonbase", to); 46 }, 47 [subsection] 48 ); ··· 50 React.useEffect( 51 () => () => { 52 // 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 53 - clearSubsection("moonbase"); 54 }, 55 [] 56 ); 57 58 return ( 59 <> 60 - <div className={`${TitleBarClasses.children} ${Margins.marginBottom20}`}> 61 - <Text 62 - className={TitleBarClasses.titleWrapper} 63 - variant="heading-lg/semibold" 64 - tag="h2" 65 - > 66 Moonbase 67 </Text> 68 <Divider /> ··· 70 selectedItem={subsection} 71 onItemSelect={setSubsection} 72 type="top-pill" 73 - className={TabBarClasses.tabBar} 74 > 75 {pages.map((page, i) => ( 76 - <TabBar.Item key={page.id} id={i} className={TabBarClasses.item}> 77 {page.name} 78 </TabBar.Item> 79 ))} 80 </TabBar> 81 </div> 82 83 {React.createElement(pages[subsection].element)} 84 </> 85 ); 86 }
··· 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; ··· 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 ); ··· 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 /> ··· 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);
+5
packages/core-extensions/src/moonbase/wp.d.ts
··· 5 declare module "@moonlight-mod/wp/moonbase_stores" { 6 export * from "core-extensions/src/moonbase/webpackModules/stores"; 7 }
··· 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:function", 6 replace: { 7 - match: /(?<=hideToken:function\(\){)/, 8 - replacement: `return()=>{};` 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 - });
···
+3 -3
packages/core-extensions/src/noTrack/index.ts
··· 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 {
··· 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;
+34 -44
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: /throw .+?,./, 14 - replacement: "return{}" 15 } 16 }, 17 { ··· 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 - ['="RunningGameStore"', /.\.info\("games",{.+?}\),/], 37 - [ 38 - '"[BUILD INFO] Release Channel: "', 39 - /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?"\)\),/ 40 - ], 41 - [ 42 - '.APP_NATIVE_CRASH,"Storage"', 43 - /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/ 44 - ], 45 - [ 46 - '.APP_NATIVE_CRASH,"Storage"', 47 - 'console.log("AppCrashedFatalReport: getLastCrash not supported.");' 48 - ], 49 ['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/], 50 ['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 51 - [ 52 - 'throw Error("Messages are still loading.");', 53 - /console\.warn\("Unsupported Locale",.\),/ 54 - ], 55 - ["}_dispatchWithDevtools(", /.\.totalTime>100&&.\.verbose\(.+?\);/], 56 - [ 57 - '"NativeDispatchUtils"', 58 - /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/ 59 - ], 60 ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 61 [ 62 '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 63 /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 64 ], 65 - [ 66 - 'console.warn("Window state not initialized"', 67 - /console\.warn\("Window state not initialized",.\),/ 68 - ] 69 ]; 70 71 const simplePatches = [ 72 // Moment.js deprecation warnings 73 - ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"], 74 - 75 - // Zustand related 76 - [ 77 - /console\.warn\("\[DEPRECATED\] Please use `subscribeWithSelector` middleware"\)/g, 78 - "/*$&*/" 79 - ], 80 - ["this.getDebugLogging()", "false"] 81 ] as { [0]: string | RegExp; [1]: string }[]; 82 83 export const patches: Patch[] = [ 84 { 85 - find: ".Messages.XSSDefenses", 86 replace: { 87 match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 88 replacement: "(true)" 89 } 90 }, 91 ...loggerFixes, 92 ...stubPatches.map((patch) => ({
··· 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 { ··· 30 // Patches to simply remove a logger call 31 const stubPatches = [ 32 // "sh" is not a valid locale. 33 + ["is not a valid locale", /void (.)\.error\(""\.concat\((.)," is not a valid locale\."\)\)/g], 34 + ['"[BUILD INFO] Release Channel: "', /new .{1,2}\.Z\(\)\.log\("\[BUILD INFO\] Release Channel: ".+?\)\),/], 35 + ['.APP_NATIVE_CRASH,"Storage"', /console\.log\("AppCrashedFatalReport lastCrash:",.,.\);/], 36 + ['.APP_NATIVE_CRASH,"Storage"', 'void console.log("AppCrashedFatalReport: getLastCrash not supported.")'], 37 ['"[NATIVE INFO] ', /new .{1,2}\.Z\(\)\.log\("\[NATIVE INFO] .+?\)\);/], 38 ['"Spellchecker"', /.\.info\("Switching to ".+?"\(unavailable\)"\);?/g], 39 + ['throw Error("Messages are still loading.");', /console\.warn\("Unsupported Locale",.\),/], 40 + ["}_dispatchWithDevtools(", /.\.totalTime>.{1,2}&&.\.verbose\(.+?\);/], 41 + ['"NativeDispatchUtils"', /null==.&&.\.warn\("Tried getting Dispatch instance before instantiated"\),/], 42 ['("DatabaseManager")', /.\.log\("removing database \(user: ".+?\)\),/], 43 [ 44 '"Dispatch.dispatch(...): Cannot dispatch in the middle of a dispatch. Action: "', 45 /.\.has\(.\.type\)&&.\.log\(.+?\.type\)\),/ 46 ], 47 + ['console.warn("Window state not initialized"', /console\.warn\("Window state not initialized",.\),/] 48 ]; 49 50 const simplePatches = [ 51 // Moment.js deprecation warnings 52 + ["suppressDeprecationWarnings=!1", "suppressDeprecationWarnings=!0"] 53 ] as { [0]: string | RegExp; [1]: string }[]; 54 55 export const patches: Patch[] = [ 56 { 57 + find: ".Messages.SELF_XSS_HEADER", 58 replace: { 59 match: /\(null!=.{1,2}&&"0\.0\.0"===.{1,2}\.remoteApp\.getVersion\(\)\)/, 60 replacement: "(true)" 61 } 62 + }, 63 + // Highlight.js deprecation warnings 64 + { 65 + find: "Deprecated as of", 66 + replace: { 67 + match: /console\./g, 68 + replacement: "false&&console." 69 + }, 70 + prerequisite: notXssDefensesOnly 71 + }, 72 + // Discord's logger 73 + { 74 + find: "ฮฃ:", 75 + replace: { 76 + match: "for", 77 + replacement: "return;for" 78 + }, 79 + prerequisite: () => silenceDiscordLogger && notXssDefensesOnly() 80 }, 81 ...loggerFixes, 82 ...stubPatches.map((patch) => ({
+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 + }
+4 -6
packages/core-extensions/src/settings/index.ts
··· 5 { 6 find: '"useGenerateUserSettingsSections"', 7 replace: { 8 - match: /(?<=\.push\(.+?\)}\)\)}\),)./, 9 - replacement: (sections: string) => 10 - `require("settings_settings").Settings._mutateSections(${sections})` 11 } 12 }, 13 { 14 find: 'navId:"user-settings-cog",', 15 replace: { 16 - match: /children:\[(.)\.map\(.+?\),children:.\((.)\)/, 17 replacement: (orig, sections, section) => 18 `${orig.replace( 19 /Object\.values\(.\..+?\)/, 20 - (orig) => 21 - `[...require("settings_settings").Settings.sectionNames,...${orig}]` 22 )}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` 23 } 24 }
··· 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 }
+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",
+11 -13
packages/core-extensions/src/settings/webpackModules/settings.ts
··· 1 - import { 2 - SettingsSection, 3 - Settings as SettingsType 4 - } from "@moonlight-mod/types/coreExtensions"; 5 6 export const Settings: SettingsType = { 7 ourSections: [], 8 sectionNames: [], 9 sectionMenuItems: {}, 10 11 - addSection: (section, label, element, color = null, pos, notice) => { 12 const data: SettingsSection = { 13 section, 14 label, 15 color, 16 element, 17 pos: pos ?? -4, 18 - notice: notice 19 }; 20 21 Settings.ourSections.push(data); ··· 24 }, 25 addSectionMenuItems(section, ...newItems) { 26 const data = Settings.ourSections.find((x) => x.section === section); 27 - if (!data || !("element" in data)) 28 - throw new Error(`Could not find section "${section}"`); 29 (Settings.sectionMenuItems[section] ??= []).push(...newItems); 30 data._moonlight_submenu ??= () => Settings.sectionMenuItems[section]; 31 }, ··· 47 48 _mutateSections: (sections) => { 49 for (const section of Settings.ourSections) { 50 - sections.splice( 51 - section.pos < 0 ? sections.length + section.pos : section.pos, 52 - 0, 53 - section 54 - ); 55 } 56 57 return sections;
··· 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); ··· 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 }, ··· 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;
+1 -1
packages/core-extensions/src/spacepack/index.ts
··· 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 2 - import { Spacepack } from "@moonlight-mod/types/coreExtensions"; 3 4 declare global { 5 interface Window {
··· 1 import { ExtensionWebExports } from "@moonlight-mod/types"; 2 + import { Spacepack } from "@moonlight-mod/types/coreExtensions/spacepack"; 3 4 declare global { 5 interface Window {
+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",
+89 -68
packages/core-extensions/src/spacepack/webpackModules/spacepack.ts
··· 1 - import { 2 - WebpackModule, 3 - WebpackModuleFunc, 4 - WebpackRequireType 5 - } from "@moonlight-mod/types"; 6 - import { Spacepack } from "@moonlight-mod/types/coreExtensions"; 7 8 const webpackRequire = require as unknown as WebpackRequireType; 9 const cache = webpackRequire.c; ··· 21 module = module.toString(); 22 } 23 24 if (!(module in modules)) { 25 return null; 26 } ··· 36 "module", 37 "exports", 38 "require", 39 - `(${funcStr}).apply(this, arguments)\n` + 40 - `//# sourceURL=Webpack-Module-${module}` 41 ) as WebpackModuleFunc; 42 }, 43 44 findByCode: (...args: (string | RegExp)[]) => { 45 - return Object.entries(modules) 46 - .filter( 47 - ([id, mod]) => 48 - !args.some( 49 - (item) => 50 - !(item instanceof RegExp 51 - ? item.test(mod.toString()) 52 - : mod.toString().indexOf(item) !== -1) 53 - ) 54 - ) 55 .map(([id]) => { 56 //if (!(id in cache)) require(id); 57 //return cache[id]; ··· 60 try { 61 exports = require(id); 62 } catch (e) { 63 - logger.error(`Error requiring module "${id}": `, e); 64 } 65 66 return { ··· 69 }; 70 }) 71 .filter((item) => item !== null); 72 }, 73 74 findByExports: (...args: string[]) => { ··· 80 !( 81 exports !== undefined && 82 exports !== window && 83 - (exports?.[item] || 84 - exports?.default?.[item] || 85 - exports?.Z?.[item] || 86 - exports?.ZP?.[item]) 87 ) 88 ) 89 ) ··· 95 }, 96 97 findObjectFromKey: (exports: Record<string, any>, key: string) => { 98 let subKey; 99 if (key.indexOf(".") > -1) { 100 const splitKey = key.split("."); ··· 105 const obj = exports[exportKey]; 106 if (obj && obj[key] !== undefined) { 107 if (subKey) { 108 - if (obj[key][subKey]) return obj; 109 } else { 110 - return obj; 111 } 112 } 113 } 114 - return null; 115 }, 116 117 findObjectFromValue: (exports: Record<string, any>, value: any) => { 118 for (const exportKey in exports) { 119 const obj = exports[exportKey]; 120 // eslint-disable-next-line eqeqeq 121 - if (obj == value) return obj; 122 for (const subKey in obj) { 123 // eslint-disable-next-line eqeqeq 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 // eslint-disable-next-line eqeqeq 140 if (obj && obj[key] == value) { 141 - return obj; 142 } 143 } 144 return null; 145 }, 146 147 - findFunctionByStrings: ( 148 - exports: Record<string, any>, 149 - ...strings: (string | RegExp)[] 150 - ) => { 151 - return ( 152 Object.entries(exports).filter( 153 ([index, func]) => 154 - typeof func === "function" && 155 - !strings.some( 156 - (query) => 157 - !(query instanceof RegExp 158 - ? func.toString().match(query) 159 - : func.toString().includes(query)) 160 - ) 161 - )?.[0]?.[1] ?? null 162 - ); 163 }, 164 165 - lazyLoad: ( 166 - find: string | RegExp | (string | RegExp)[], 167 - chunk: RegExp, 168 - module: RegExp 169 - ) => { 170 - const mod = Array.isArray(find) 171 - ? spacepack.findByCode(...find) 172 - : spacepack.findByCode(find); 173 - if (mod.length < 1) return Promise.reject("Module find failed"); 174 175 const findId = mod[0].id; 176 const findCode = webpackRequire.m[findId].toString().replace(/\n/g, ""); ··· 180 chunkIds = [...findCode.matchAll(chunk)].map(([, id]) => id); 181 } else { 182 const match = findCode.match(chunk); 183 - if (match) 184 - chunkIds = [...match[0].matchAll(/"(\d+)"/g)].map(([, id]) => id); 185 } 186 187 - if (!chunkIds || chunkIds.length === 0) 188 return Promise.reject("Chunk ID match failed"); 189 190 const moduleId = findCode.match(module)?.[1]; 191 - if (!moduleId) return Promise.reject("Module ID match failed"); 192 193 - return Promise.all(chunkIds.map((c) => webpackRequire.e(c))).then(() => 194 - webpackRequire(moduleId) 195 - ); 196 }, 197 198 filterReal: (modules: WebpackModule[]) => { ··· 200 } 201 }; 202 203 - if ( 204 - moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true 205 - ) { 206 window.spacepack = spacepack; 207 } 208
··· 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; ··· 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 } ··· 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]; ··· 52 try { 53 exports = require(id); 54 } catch (e) { 55 + logger.error(`findByCode: Error requiring module "${id}": `, args, e); 56 } 57 58 return { ··· 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[]) => { ··· 78 !( 79 exports !== undefined && 80 exports !== window && 81 + (exports?.[item] || exports?.default?.[item] || exports?.Z?.[item] || exports?.ZP?.[item]) 82 ) 83 ) 84 ) ··· 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("."); ··· 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, ""); ··· 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[]) => { ··· 223 } 224 }; 225 226 + if (moonlight.getConfigOption<boolean>("spacepack", "addToGlobalScope") === true) { 227 window.spacepack = spacepack; 228 } 229
+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 }
+173 -113
packages/injector/src/index.ts
··· 5 app 6 } from "electron"; 7 import Module from "node:module"; 8 - import { constants } from "@moonlight-mod/types"; 9 - import { readConfig } from "@moonlight-mod/core/config"; 10 import { getExtensions } from "@moonlight-mod/core/extension"; 11 - import Logger from "@moonlight-mod/core/util/logger"; 12 - import { 13 - loadExtensions, 14 - loadProcessedExtensions 15 - } from "@moonlight-mod/core/extension/loader"; 16 import EventEmitter from "node:events"; 17 - import { join, resolve } from "node:path"; 18 19 const logger = new Logger("injector"); 20 21 let oldPreloadPath: string | undefined; 22 let corsAllow: string[] = []; 23 - let isMoonlightDesktop = false; 24 - let hasOpenAsar = false; 25 - let openAsarConfigPreload: string | undefined; 26 27 ipcMain.on(constants.ipcGetOldPreloadPath, (e) => { 28 e.returnValue = oldPreloadPath; 29 }); 30 ipcMain.on(constants.ipcGetAppData, (e) => { 31 e.returnValue = app.getPath("appData"); 32 }); 33 - ipcMain.on(constants.ipcGetIsMoonlightDesktop, (e) => { 34 - e.returnValue = isMoonlightDesktop; 35 }); 36 ipcMain.handle(constants.ipcMessageBox, (_, opts) => { 37 electron.dialog.showMessageBoxSync(opts); ··· 40 corsAllow = list; 41 }); 42 43 - function patchCsp(headers: Record<string, string[]>) { 44 - const directives = [ 45 - "style-src", 46 - "connect-src", 47 - "img-src", 48 - "font-src", 49 - "media-src", 50 - "worker-src", 51 - "prefetch-src" 52 - ]; 53 - const values = ["*", "blob:", "data:", "'unsafe-inline'", "disclip:"]; 54 55 const csp = "content-security-policy"; 56 if (headers[csp] == null) return; ··· 67 68 for (const directive of directives) { 69 parts[directive] = values; 70 } 71 72 const stringified = Object.entries<string[]>(parts) ··· 77 headers[csp] = [stringified]; 78 } 79 80 - function removeOpenAsarEventIfPresent(eventHandler: (...args: any[]) => void) { 81 - const code = eventHandler.toString(); 82 - if (code.indexOf("bw.webContents.on('dom-ready'") > -1) { 83 - electron.app.off("browser-window-created", eventHandler); 84 - } 85 - } 86 - 87 class BrowserWindow extends ElectronBrowserWindow { 88 constructor(opts: BrowserWindowConstructorOptions) { 89 - oldPreloadPath = opts.webPreferences!.preload; 90 91 - // Only overwrite preload if its the actual main client window 92 - if (opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1) { 93 opts.webPreferences!.preload = require.resolve("./node-preload.js"); 94 } 95 96 // Event for modifying window options 97 - moonlightHost.events.emit("window-options", opts); 98 99 super(opts); 100 101 // Event for when a window is created 102 - moonlightHost.events.emit("window-created", this); 103 104 this.webContents.session.webRequest.onHeadersReceived((details, cb) => { 105 if (details.responseHeaders != null) { 106 // Patch CSP so things can use externally hosted assets 107 if (details.resourceType === "mainFrame") { 108 - patchCsp(details.responseHeaders); 109 } 110 111 // Allow plugins to bypass CORS for specific URLs 112 if (corsAllow.some((x) => details.url.startsWith(x))) { 113 - details.responseHeaders["access-control-allow-origin"] = ["*"]; 114 } 115 116 cb({ cancel: false, responseHeaders: details.responseHeaders }); 117 } 118 }); 119 120 - if (hasOpenAsar) { 121 - // Remove DOM injections 122 - // Settings can still be opened via: 123 - // `DiscordNative.ipc.send("DISCORD_UPDATED_QUOTES","o")` 124 - // @ts-expect-error Electron internals 125 - const events = electron.app._events["browser-window-created"]; 126 - if (Array.isArray(events)) { 127 - for (const event of events) { 128 - removeOpenAsarEventIfPresent(event); 129 } 130 - } else if (events != null) { 131 - removeOpenAsarEventIfPresent(events); 132 } 133 134 - // Config screen fails to context bridge properly 135 - // Less than ideal, but better than disabling it everywhere 136 - if (opts.webPreferences!.preload === openAsarConfigPreload) { 137 - opts.webPreferences!.sandbox = false; 138 - } 139 - } 140 } 141 } 142 ··· 157 writable: false 158 }); 159 160 - export async function inject(asarPath: string) { 161 - isMoonlightDesktop = asarPath === "moonlightDesktop"; 162 try { 163 - const config = readConfig(); 164 - const extensions = getExtensions(); 165 166 // Duplicated in node-preload... oops 167 - // eslint-disable-next-line no-inner-declarations 168 function getConfig(ext: string) { 169 const val = config.extensions[ext]; 170 if (val == null || typeof val === "boolean") return undefined; 171 return val.config; 172 } 173 - 174 global.moonlightHost = { 175 asarPath, 176 - config, 177 events: new EventEmitter(), 178 - extensions, 179 - processedExtensions: { 180 - extensions: [], 181 - dependencyGraph: new Map() 182 - }, 183 184 getConfig, 185 - getConfigOption: <T>(ext: string, name: string) => { 186 - const config = getConfig(ext); 187 - if (config == null) return undefined; 188 - const option = config[name]; 189 - if (option == null) return undefined; 190 - return option as T; 191 }, 192 - getLogger: (id: string) => { 193 return new Logger(id); 194 } 195 }; 196 197 - // Check if we're running with OpenAsar 198 - try { 199 - require.resolve(join(asarPath, "updater", "updater.js")); 200 - hasOpenAsar = true; 201 - openAsarConfigPreload = resolve(asarPath, "config", "preload.js"); 202 - // eslint-disable-next-line no-empty 203 - } catch {} 204 - 205 - if (hasOpenAsar) { 206 - // Disable command line switch injection 207 - // I personally think that the command line switches should be vetted by 208 - // the user and not just "trust that these are sane defaults that work 209 - // always". I'm not hating on Ducko or anything, I'm just opinionated. 210 - // Someone can always make a command line modifier plugin, thats the point 211 - // of having host modules. 212 - try { 213 - const cmdSwitchesPath = require.resolve( 214 - join(asarPath, "cmdSwitches.js") 215 - ); 216 - require.cache[cmdSwitchesPath] = new Module( 217 - cmdSwitchesPath, 218 - require.cache[require.resolve(asarPath)] 219 - ); 220 - require.cache[cmdSwitchesPath]!.exports = () => {}; 221 - } catch (error) { 222 - logger.error("Failed to disable OpenAsar's command line flags:", error); 223 - } 224 - } 225 - 226 patchElectron(); 227 228 - global.moonlightHost.processedExtensions = await loadExtensions(extensions); 229 await loadProcessedExtensions(global.moonlightHost.processedExtensions); 230 } catch (error) { 231 logger.error("Failed to inject:", error); 232 } 233 234 - if (isMoonlightDesktop) return; 235 236 - // Need to do this instead of require() or it breaks require.main 237 - // @ts-expect-error Module internals 238 - Module._load(asarPath, Module, true); 239 } 240 241 function patchElectron() { ··· 249 configurable: false 250 }); 251 } else { 252 - Object.defineProperty( 253 - electronClone, 254 - property, 255 - Object.getOwnPropertyDescriptor(electron, property)! 256 - ); 257 } 258 } 259
··· 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); ··· 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; ··· 95 96 for (const directive of directives) { 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) ··· 110 headers[csp] = [stringified]; 111 } 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 ··· 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) { 245 const val = config.extensions[ext]; 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
+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 }
+144 -46
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 { 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 "@moonlight-mod/core/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 - getConfig, 32 - getConfigOption: <T>(ext: string, name: string) => { 33 - const config = getConfig(ext); 34 - if (config == null) return undefined; 35 - const option = config[name]; 36 - if (option == null) return undefined; 37 - return option as T; 38 }, 39 getNatives: (ext: string) => global.moonlightNode.nativesCache[ext], 40 getLogger: (id: string) => { 41 return new Logger(id); 42 }, 43 - 44 - getExtensionDir: (ext: string) => { 45 - const extPath = getExtensionsPath(); 46 - return path.join(extPath, ext); 47 }, 48 - writeConfig 49 }; 50 51 - await loadProcessedExtensions(processed); 52 contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); 53 54 - const extCors = moonlightNode.processedExtensions.extensions 55 - .map((x) => x.manifest.cors ?? []) 56 - .flat(); 57 58 for (const repo of moonlightNode.config.repositories) { 59 const url = new URL(repo); 60 url.pathname = "/"; 61 - extCors.push(url.toString()); 62 } 63 64 - ipcRenderer.invoke(constants.ipcSetCorsList, extCors); 65 } 66 67 async function loadPreload() { 68 const webPreloadPath = path.join(__dirname, "web-preload.js"); 69 const webPreload = fs.readFileSync(webPreloadPath, "utf8"); 70 await webFrame.executeJavaScript(webPreload); 71 } 72 73 - async function init(oldPreloadPath: string) { 74 try { 75 await injectGlobals(); 76 await loadPreload(); ··· 81 message: message 82 }); 83 } 84 85 - // Let Discord start even if we fail 86 - if (oldPreloadPath) require(oldPreloadPath); 87 } 88 - 89 - const oldPreloadPath: string = ipcRenderer.sendSync( 90 - constants.ipcGetOldPreloadPath 91 - ); 92 - 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 }
+15 -6
packages/types/package.json
··· 1 { 2 "name": "@moonlight-mod/types", 3 - "version": "1.1.8", 4 - "main": "./src/index.ts", 5 - "types": "./src/index.ts", 6 "exports": { 7 ".": "./src/index.ts", 8 "./import": "./src/import.d.ts", 9 "./*": "./src/*.ts" 10 }, 11 "dependencies": { 12 - "@types/flux": "^3.1.12", 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 }
+49 -3
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; ··· 35 }; 36 37 export type BooleanSettingType = { 38 type: ExtensionSettingType.Boolean; 39 default?: boolean; 40 }; 41 42 export type NumberSettingType = { 43 type: ExtensionSettingType.Number; 44 default?: number; 45 min?: number; ··· 47 }; 48 49 export type StringSettingType = { 50 type: ExtensionSettingType.String; 51 default?: string; 52 }; 53 54 export type MultilineTextInputSettingType = { 55 type: ExtensionSettingType.MultilineString; 56 default?: string; 57 }; 58 59 export type SelectSettingType = { 60 type: ExtensionSettingType.Select; 61 options: SelectOption[]; 62 default?: string; 63 }; 64 65 export type MultiSelectSettingType = { 66 type: ExtensionSettingType.MultiSelect; 67 options: string[]; 68 default?: string[]; 69 }; 70 71 export type ListSettingType = { 72 type: ExtensionSettingType.List; 73 default?: string[]; 74 }; 75 76 export type DictionarySettingType = { 77 type: ExtensionSettingType.Dictionary; 78 default?: Record<string, string>; 79 }; 80 81 export type CustomSettingType = { 82 type: ExtensionSettingType.Custom; 83 default?: any; 84 }; 85 86 export type ExtensionSettingsManifest = { 87 displayName?: string; 88 description?: string; 89 } & ( 90 | BooleanSettingType 91 | NumberSettingType
··· 6 patchAll?: boolean; 7 }; 8 9 + export type ConfigExtensions = { [key: string]: boolean } | { [key: string]: ConfigExtension }; 10 11 export type ConfigExtension = { 12 enabled: boolean; ··· 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
+11 -1
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 ipcGetIsMoonlightDesktop = "_moonlight_getIsMoonlightDesktop"; 9 export const ipcMessageBox = "_moonlight_messageBox"; 10 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
-409
packages/types/src/coreExtensions/components.ts
··· 1 - import type { 2 - Component, 3 - Ref, 4 - PropsWithChildren, 5 - PropsWithoutRef, 6 - CSSProperties, 7 - ReactNode, 8 - ReactElement, 9 - ComponentClass, 10 - ComponentType, 11 - MouseEventHandler, 12 - KeyboardEventHandler 13 - } from "react"; 14 - import * as CSS from "csstype"; 15 - 16 - export enum TextInputSizes { 17 - DEFAULT = "inputDefault", 18 - MINI = "inputMini" 19 - } 20 - 21 - interface TextInput 22 - extends ComponentClass< 23 - PropsWithoutRef<{ 24 - value?: string; 25 - name?: string; 26 - className?: string; 27 - inputClassName?: string; 28 - inputPrefix?: string; 29 - disabled?: boolean; 30 - size?: TextInputSizes; 31 - editable?: boolean; 32 - inputRef?: Ref<any>; 33 - prefixElement?: Component; 34 - focusProps?: PropsWithoutRef<any>; 35 - error?: string; 36 - minLength?: number; 37 - maxLength?: number; 38 - onChange?: (value: string, name: string) => void; 39 - onFocus?: (event: any, name: string) => void; 40 - onBlur?: (event: any, name: string) => void; 41 - }> 42 - > { 43 - Sizes: typeof TextInputSizes; 44 - } 45 - 46 - export enum TextAreaAutoComplete { 47 - ON = "on", 48 - OFF = "off" 49 - } 50 - 51 - export enum TextAreaWrap { 52 - HARD = "hard", 53 - SOFT = "soft", 54 - OFF = "off" 55 - } 56 - 57 - interface TextArea 58 - extends ComponentClass< 59 - PropsWithoutRef<{ 60 - value?: string; 61 - defaultValue?: string; 62 - autoComplete?: TextAreaAutoComplete; 63 - autoFocus?: boolean; 64 - cols?: number; 65 - disabled?: boolean; 66 - form?: string; 67 - maxLength?: number; 68 - minLength?: number; 69 - name?: string; 70 - onChange?: (value: string, name: string) => void; 71 - onChangeCapture?: (value: string, name: string) => void; 72 - onInput?: (value: string, name: string) => void; 73 - onInputCapture?: (value: string, name: string) => void; 74 - onInvalid?: (value: string, name: string) => void; 75 - onInvalidCapture?: (value: string, name: string) => void; 76 - onSelect?: (value: string, name: string) => void; 77 - onSelectCapture?: (value: string, name: string) => void; 78 - placeholder?: string; 79 - readOnly?: boolean; 80 - required?: boolean; 81 - rows?: number; 82 - wrap?: TextAreaWrap; 83 - className?: string; 84 - }> 85 - > { 86 - AutoCompletes: typeof TextAreaAutoComplete; 87 - Wraps: typeof TextAreaWrap; 88 - } 89 - 90 - export enum FormTextTypes { 91 - DEFAULT = "default", 92 - DESCRIPTION = "description", 93 - ERROR = "error", 94 - INPUT_PLACEHOLDER = "placeholder", 95 - LABEL_BOLD = "labelBold", 96 - LABEL_DESCRIPTOR = "labelDescriptor", 97 - LABEL_SELECTED = "labelSelected", 98 - SUCCESS = "success" 99 - } 100 - 101 - interface FormText 102 - extends ComponentClass< 103 - PropsWithChildren<{ 104 - type?: FormTextTypes; 105 - className?: string; 106 - disabled?: boolean; 107 - selectable?: boolean; 108 - style?: CSSProperties; 109 - }> 110 - > { 111 - Types: FormTextTypes; 112 - } 113 - 114 - declare enum SliderMarkerPosition { 115 - ABOVE, 116 - BELOW 117 - } 118 - 119 - declare enum ButtonLooks { 120 - FILLED = "lookFilled", 121 - INVERTED = "lookInverted", 122 - OUTLINED = "lookOutlined", 123 - LINK = "lookLink", 124 - BLANK = "lookBlank" 125 - } 126 - declare enum ButtonColors { 127 - BRAND = "colorBrand", 128 - RED = "colorRed", 129 - GREEN = "colorGreen", 130 - YELLOW = "colorYellow", 131 - PRIMARY = "colorPrimary", 132 - LINK = "colorLink", 133 - WHITE = "colorWhite", 134 - BLACK = "colorBlack", 135 - TRANSPARENT = "colorTransparent", 136 - BRAND_NEW = "colorBrandNew", 137 - CUSTOM = "" 138 - } 139 - declare enum ButtonBorderColors { 140 - BRAND = "borderBrand", 141 - RED = "borderRed", 142 - GREEN = "borderGreen", 143 - YELLOW = "borderYellow", 144 - PRIMARY = "borderPrimary", 145 - LINK = "borderLink", 146 - WHITE = "borderWhite", 147 - BLACK = "borderBlack", 148 - TRANSPARENT = "borderTransparent", 149 - BRAND_NEW = "borderBrandNew" 150 - } 151 - declare enum ButtonHovers { 152 - DEFAULT = "", 153 - BRAND = "hoverBrand", 154 - RED = "hoverRed", 155 - GREEN = "hoverGreen", 156 - YELLOW = "hoverYellow", 157 - PRIMARY = "hoverPrimary", 158 - LINK = "hoverLink", 159 - WHITE = "hoverWhite", 160 - BLACK = "hoverBlack", 161 - TRANSPARENT = "hoverTransparent" 162 - } 163 - declare enum ButtonSizes { 164 - NONE = "", 165 - TINY = "sizeTiny", 166 - SMALL = "sizeSmall", 167 - MEDIUM = "sizeMedium", 168 - LARGE = "sizeLarge", 169 - XLARGE = "sizeXlarge", 170 - MIN = "sizeMin", 171 - MAX = "sizeMax", 172 - ICON = "sizeIcon" 173 - } 174 - 175 - type Button = ComponentType< 176 - PropsWithChildren<{ 177 - look?: ButtonLooks; 178 - color?: ButtonColors; 179 - borderColor?: ButtonBorderColors; 180 - hover?: ButtonHovers; 181 - size?: ButtonSizes; 182 - fullWidth?: boolean; 183 - grow?: boolean; 184 - disabled?: boolean; 185 - submitting?: boolean; 186 - type?: string; 187 - style?: CSSProperties; 188 - wrapperClassName?: string; 189 - className?: string; 190 - innerClassName?: string; 191 - onClick?: MouseEventHandler; 192 - onDoubleClick?: MouseEventHandler; 193 - onMouseDown?: MouseEventHandler; 194 - onMouseUp?: MouseEventHandler; 195 - onMouseEnter?: MouseEventHandler; 196 - onMouseLeave?: MouseEventHandler; 197 - onKeyDown?: KeyboardEventHandler; 198 - rel?: any; 199 - buttonRef?: Ref<any>; 200 - focusProps?: PropsWithChildren<any>; 201 - "aria-label"?: string; 202 - submittingStartedLabel?: string; 203 - submittingFinishedLabel?: string; 204 - }> 205 - > & { 206 - Looks: typeof ButtonLooks; 207 - Colors: typeof ButtonColors; 208 - BorderColors: typeof ButtonBorderColors; 209 - Hovers: typeof ButtonHovers; 210 - Sizes: typeof ButtonSizes; 211 - }; 212 - 213 - export enum FlexDirection { 214 - VERTICAL = "vertical", 215 - HORIZONTAL = "horizontal", 216 - HORIZONTAL_REVERSE = "horizontalReverse" 217 - } 218 - 219 - declare enum FlexAlign { 220 - START = "alignStart", 221 - END = "alignEnd", 222 - CENTER = "alignCenter", 223 - STRETCH = "alignStretch", 224 - BASELINE = "alignBaseline" 225 - } 226 - declare enum FlexJustify { 227 - START = "justifyStart", 228 - END = "justifyEnd", 229 - CENTER = "justifyCenter", 230 - BETWEEN = "justifyBetween", 231 - AROUND = "justifyAround" 232 - } 233 - declare enum FlexWrap { 234 - NO_WRAP = "noWrap", 235 - WRAP = "wrap", 236 - WRAP_REVERSE = "wrapReverse" 237 - } 238 - interface Flex 239 - extends ComponentClass< 240 - PropsWithChildren<{ 241 - className?: string; 242 - direction?: FlexDirection; 243 - justify?: FlexJustify; 244 - align?: FlexAlign; 245 - wrap?: FlexWrap; 246 - shrink?: CSS.Property.FlexShrink; 247 - grow?: CSS.Property.FlexGrow; 248 - basis?: CSS.Property.FlexBasis; 249 - style?: CSSProperties; 250 - }> 251 - > { 252 - Direction: typeof FlexDirection; 253 - Align: typeof FlexAlign; 254 - Justify: typeof FlexJustify; 255 - Wrap: typeof FlexWrap; 256 - Child: Component< 257 - PropsWithChildren<{ 258 - className?: string; 259 - shrink?: CSS.Property.FlexShrink; 260 - grow?: CSS.Property.FlexGrow; 261 - basis?: CSS.Property.FlexBasis; 262 - style?: CSSProperties; 263 - wrap?: boolean; 264 - }> 265 - >; 266 - } 267 - 268 - // TODO: wtaf is up with react types not working in jsx 269 - export type CommonComponents = { 270 - [index: string]: any; 271 - Clickable: ComponentClass< 272 - PropsWithChildren<{ 273 - onClick?: () => void; 274 - href?: any; 275 - onKeyPress?: () => void; 276 - ignoreKeyPress?: boolean; 277 - innerRef?: Ref<any>; 278 - focusProps?: any; 279 - tag?: string | Component; 280 - role?: any; 281 - tabIndex?: any; 282 - className?: string; 283 - }> 284 - >; 285 - TextInput: TextInput; 286 - TextArea: TextArea; 287 - FormDivider: ComponentClass<any>; 288 - FormSection: ComponentClass< 289 - PropsWithChildren<{ 290 - className?: string; 291 - titleClassName?: string; 292 - title?: ReactNode; 293 - icon?: ReactNode; 294 - disabled?: boolean; 295 - htmlFor?: any; 296 - tag?: string; 297 - }> 298 - >; 299 - FormText: FormText; 300 - FormTitle: ComponentClass< 301 - PropsWithChildren<{ 302 - tag?: string; 303 - className?: string; 304 - faded?: boolean; 305 - disabled?: boolean; 306 - required?: boolean; 307 - error?: string; 308 - }> 309 - >; 310 - FormSwitch: ComponentClass<PropsWithChildren<any>>; 311 - FormItem: ComponentClass<PropsWithChildren<any>>; 312 - Slider: ComponentClass< 313 - PropsWithChildren<{ 314 - disabled?: boolean; 315 - stickToMarkers?: boolean; 316 - className?: string; 317 - barStyles?: CSSProperties; 318 - fillStyles?: CSSProperties; 319 - mini?: boolean; 320 - hideBubble?: boolean; 321 - initialValue?: number; 322 - orientation?: "horizontal" | "vertical"; 323 - onValueRender?: (value: number) => string; 324 - renderMarker?: (marker: number) => ReactNode; 325 - getAriaValueText?: (value: number) => string; 326 - barClassName?: string; 327 - grabberClassName?: string; 328 - grabberStyles?: CSSProperties; 329 - markerPosition?: SliderMarkerPosition; 330 - "aria-hidden"?: "true" | "false"; 331 - "aria-label"?: string; 332 - "aria-labelledby"?: string; 333 - "aria-describedby"?: string; 334 - minValue?: number; 335 - maxValue?: number; 336 - asValueChanges?: (value: number) => void; 337 - onValueChange?: (value: number) => void; 338 - keyboardStep?: number; 339 - }> 340 - >; 341 - Switch: ComponentClass<PropsWithChildren<any>>; 342 - Button: Button; 343 - Tooltip: ComponentClass<PropsWithChildren<any>>; 344 - SmallSlider: Component; 345 - Avatar: Component; 346 - Scroller: Component; 347 - Text: ComponentClass<PropsWithChildren<any>>; 348 - Heading: ComponentClass<PropsWithChildren<any>>; 349 - LegacyText: Component; 350 - Flex: Flex; 351 - Card: ComponentClass<PropsWithChildren<any>>; 352 - Popout: ComponentClass<PropsWithChildren<any>>; 353 - Dialog: ComponentClass<PropsWithChildren<any>>; 354 - Menu: ComponentClass<PropsWithChildren<any>>; 355 - MenuItem: ComponentClass<PropsWithChildren<any>>; 356 - MenuGroup: ComponentClass<PropsWithChildren<any>>; 357 - MenuCheckboxItem: ComponentClass<PropsWithChildren<any>>; 358 - CardClasses: { 359 - card: string; 360 - cardHeader: string; 361 - }; 362 - ControlClasses: { 363 - container: string; 364 - control: string; 365 - disabled: string; 366 - dividerDefault: string; 367 - labelRow: string; 368 - note: string; 369 - title: string; 370 - titleDefault: string; 371 - titleMini: string; 372 - }; 373 - MarkdownParser: { 374 - parse: (text: string) => ReactElement; 375 - }; 376 - SettingsNotice: React.ComponentType<{ 377 - submitting: boolean; 378 - onReset: () => void; 379 - onSave: () => void; 380 - }>; 381 - TabBar: React.ComponentType<any> & { 382 - Item: React.ComponentType<any>; 383 - }; 384 - SingleSelect: React.ComponentType<{ 385 - autofocus?: boolean; 386 - clearable?: boolean; 387 - value?: string; 388 - options?: { 389 - value: string; 390 - label: string; 391 - }[]; 392 - onChange?: (value: string) => void; 393 - }>; 394 - Select: React.ComponentType<{ 395 - autofocus?: boolean; 396 - clearable?: boolean; 397 - value?: string[]; 398 - options?: { 399 - value: string; 400 - label: string; 401 - }[]; 402 - onChange?: (value: string[]) => void; 403 - }>; 404 - 405 - // TODO 406 - useVariableSelect: any; 407 - multiSelect: any; 408 - tokens: any; 409 - };
···
+21 -121
packages/types/src/coreExtensions/contextMenu.ts
··· 1 - // TODO: Deduplicate common props 2 - 3 - export type Menu = React.FunctionComponent<{ 4 - navId: string; 5 - variant?: string; 6 - hideScrollbar?: boolean; 7 - className?: string; 8 - children: React.ReactComponentElement<MenuElement>[]; 9 - onClose?: () => void; 10 - onSelect?: () => void; 11 - }>; 12 - export type MenuProps = React.ComponentProps<Menu>; 13 - 14 - export type MenuElement = 15 - | MenuSeparator 16 - | MenuGroup 17 - | MenuItem 18 - | MenuCheckboxItem 19 - | MenuRadioItem 20 - | MenuControlItem; 21 - 22 - /* eslint-disable prettier/prettier */ 23 - export type MenuSeparator = React.FunctionComponent; 24 - export type MenuGroup = React.FunctionComponent<{ 25 - label?: string; 26 - className?: string; 27 - color?: string; 28 - children: React.ReactComponentElement<MenuElement>[]; 29 - }>; 30 - export type MenuItem = React.FunctionComponent< 31 - { 32 - id: any; 33 - dontCloseOnActionIfHoldingShiftKey?: boolean; 34 - } & ( 35 - | { 36 - label: string; 37 - subtext?: string; 38 - color?: string; 39 - hint?: string; 40 - disabled?: boolean; 41 - icon?: any; 42 - showIconFirst?: boolean; 43 - imageUrl?: string; 44 - 45 - className?: string; 46 - focusedClassName?: string; 47 - subMenuIconClassName?: string; 48 - 49 - action?: () => void; 50 - onFocus?: () => void; 51 - 52 - iconProps?: any; 53 - sparkle?: any; 54 - 55 - children?: React.ReactComponentElement<MenuElement>[]; 56 - onChildrenScroll?: any; 57 - childRowHeight?: any; 58 - listClassName?: string; 59 - subMenuClassName?: string; 60 - } 61 - | { 62 - color?: string; 63 - disabled?: boolean; 64 - keepItemStyles?: boolean; 65 - 66 - action?: () => void; 67 - 68 - render: any; 69 - navigable?: boolean; 70 - } 71 - ) 72 - >; 73 - export type MenuCheckboxItem = React.FunctionComponent<{ 74 - id: any; 75 - label: string; 76 - subtext?: string; 77 - color?: string; 78 - className?: string; 79 - focusedClassName?: string; 80 - disabled?: boolean; 81 - checked: boolean; 82 - action?: () => void; 83 - }>; 84 - export type MenuRadioItem = React.FunctionComponent<{ 85 - id: any; 86 - label: string; 87 - subtext?: string; 88 - color?: string; 89 - disabled?: boolean; 90 - action?: () => void; 91 - }>; 92 - export type MenuControlItem = React.FunctionComponent< 93 - { 94 - id: any; 95 - label: string; 96 - color?: string; 97 - disabled?: boolean; 98 - showDefaultFocus?: boolean; 99 - } & ( 100 - | { 101 - control: any; 102 - } 103 - | { 104 - control?: undefined; 105 - interactive?: boolean; 106 - children?: React.ReactComponentElement<MenuElement>[]; 107 - } 108 - ) 109 - >; 110 - /* eslint-disable prettier/prettier */ 111 112 export type ContextMenu = { 113 - addItem: ( 114 - navId: string, 115 - item: (props: any) => React.ReactComponentElement<MenuElement>, 116 - anchorId: string, 117 - before?: boolean 118 - ) => void; 119 120 MenuCheckboxItem: MenuCheckboxItem; 121 MenuControlItem: MenuControlItem; ··· 157 label: string; 158 }; 159 160 - export type EvilItemParser = ( 161 - el: 162 - | React.ReactComponentElement<MenuElement> 163 - | React.ReactComponentElement<MenuElement>[] 164 - ) => InternalItem[];
··· 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; ··· 59 label: string; 60 }; 61 62 + export type EvilItemParser = (el: MenuElement | MenuElement[]) => InternalItem[]; 63 + 64 + export type { Menu, MenuElement };
+20 -23
packages/types/src/coreExtensions/markdown.ts
··· 11 12 export type ASTNode = SingleASTNode | Array<SingleASTNode>; 13 14 - export type Parser = ( 15 - source: string, 16 - state?: State | null | undefined 17 - ) => Array<SingleASTNode>; 18 19 - export type ParseFunction = ( 20 - capture: Capture, 21 - nestedParse: Parser, 22 - state: State 23 - ) => UntypedASTNode | ASTNode; 24 25 export type Capture = 26 | (Array<string> & { ··· 38 39 export type MatchFunction = { 40 regex?: RegExp; 41 - } & (( 42 - source: string, 43 - state: State, 44 - prevCapture: string 45 - ) => Capture | null | undefined); 46 47 - export type Output<Result> = ( 48 - node: ASTNode, 49 - state?: State | null | undefined 50 - ) => Result; 51 52 - export type SingleNodeOutput<Result> = ( 53 - node: SingleASTNode, 54 - nestedOutput: Output<Result>, 55 - state: State 56 - ) => Result; 57 58 // }}} 59 ··· 100 slateDecorators: Record<string, string>; 101 ruleBlacklists: Record<Ruleset, Record<string, boolean>>; 102 103 addRule: ( 104 name: string, 105 markdown: (rules: Record<string, MarkdownRule>) => MarkdownRule, 106 slate: (rules: Record<string, SlateRule>) => SlateRule, 107 decorator?: string | undefined 108 ) => void; 109 blacklistFromRuleset: (ruleset: Ruleset, name: string) => void; 110 };
··· 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> & { ··· 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 ··· 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 + };
+8 -80
packages/types/src/coreExtensions.ts
··· 1 - import { FluxDefault, Store } from "./discord/common/Flux"; 2 - import { CommonComponents as CommonComponents_ } from "./coreExtensions/components"; 3 - import { Dispatcher } from "flux"; 4 - import React, { ReactElement } from "react"; 5 - import { 6 - WebpackModule, 7 - WebpackModuleFunc, 8 - WebpackRequireType 9 - } from "./discord"; 10 - 11 - export type Spacepack = { 12 - inspect: (module: number | string) => WebpackModuleFunc | null; 13 - findByCode: (...args: (string | RegExp)[]) => any[]; 14 - findByExports: (...args: string[]) => any[]; 15 - require: WebpackRequireType; 16 - modules: Record<string, WebpackModuleFunc>; 17 - cache: Record<string, any>; 18 - findObjectFromKey: (exports: Record<string, any>, key: string) => any | null; 19 - findObjectFromValue: (exports: Record<string, any>, value: any) => any | null; 20 - findObjectFromKeyValuePair: ( 21 - exports: Record<string, any>, 22 - key: string, 23 - value: any 24 - ) => any | null; 25 - findFunctionByStrings: ( 26 - exports: Record<string, any>, 27 - ...strings: (string | RegExp)[] 28 - // eslint-disable-next-line @typescript-eslint/ban-types 29 - ) => Function | null; 30 - lazyLoad: ( 31 - find: string | RegExp | (string | RegExp)[], 32 - chunk: RegExp, 33 - module: RegExp 34 - ) => Promise<any>; 35 - filterReal: (modules: WebpackModule[]) => WebpackModule[]; 36 - }; 37 - 38 - export type NoticeProps = { 39 - stores: Store<any>[]; 40 - element: React.FunctionComponent; 41 - }; 42 - 43 - export type SettingsSection = 44 - | { section: "DIVIDER"; pos: number } 45 - | { section: "HEADER"; label: string; pos: number } 46 - | { 47 - section: string; 48 - label: string; 49 - color: string | null; 50 - element: React.FunctionComponent; 51 - pos: number; 52 - notice?: NoticeProps; 53 - _moonlight_submenu?: () => ReactElement | ReactElement[]; 54 - }; 55 - 56 - export type Settings = { 57 - ourSections: SettingsSection[]; 58 - sectionNames: string[]; 59 - sectionMenuItems: Record<string, ReactElement[]>; 60 - 61 - addSection: ( 62 - section: string, 63 - label: string, 64 - element: React.FunctionComponent, 65 - color?: string | null, 66 - pos?: number, 67 - notice?: NoticeProps 68 - ) => void; 69 - addSectionMenuItems: (section: string, ...items: ReactElement[]) => void; 70 - 71 - addDivider: (pos: number | null) => void; 72 - addHeader: (label: string, pos: number | null) => void; 73 - _mutateSections: (sections: SettingsSection[]) => SettingsSection[]; 74 - }; 75 - 76 - export type CommonReact = typeof import("react"); 77 - export type CommonFlux = FluxDefault; 78 - export type CommonComponents = CommonComponents_; // lol 79 - export type CommonFluxDispatcher = Dispatcher<any>; 80 - 81 export * as Markdown from "./coreExtensions/markdown"; 82 export * as ContextMenu from "./coreExtensions/contextMenu";
··· 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 - }
···
+31 -22
packages/types/src/discord/require.ts
··· 1 - import { 2 - Spacepack, 3 - CommonReact, 4 - CommonFlux, 5 - Settings, 6 - CommonComponents, 7 - CommonFluxDispatcher 8 - } from "../coreExtensions"; 9 import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu"; 10 import { Markdown } from "../coreExtensions/markdown"; 11 12 declare function WebpackRequire(id: string): any; 13 - declare function WebpackRequire(id: "spacepack_spacepack"): { 14 - default: Spacepack; 15 - spacepack: Spacepack; 16 - }; 17 18 - declare function WebpackRequire(id: "common_components"): CommonComponents; 19 - declare function WebpackRequire(id: "common_flux"): CommonFlux; 20 - declare function WebpackRequire( 21 - id: "common_fluxDispatcher" 22 - ): CommonFluxDispatcher; 23 - declare function WebpackRequire(id: "common_react"): CommonReact; 24 25 declare function WebpackRequire(id: "settings_settings"): { 26 Settings: Settings; 27 default: Settings; 28 }; 29 30 - declare function WebpackRequire(id: "markdown_markdown"): Markdown; 31 - 32 - declare function WebpackRequire(id: "contextMenu_evilMenu"): EvilItemParser; 33 - declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu; 34 35 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 -16
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 - e: (module: number | string) => Promise<void>; 7 - }; 8 9 export type WebpackModule = { 10 id: string | number; 11 - loaded: boolean; 12 exports: any; 13 }; 14 15 - export type WebpackModuleFunc = (( 16 - module: any, 17 - exports: any, 18 - require: WebpackRequireType 19 - ) => void) & { 20 __moonlight?: boolean; 21 }; 22 23 - export type WebpackJsonpEntry = [ 24 - number[], 25 - { [id: string]: WebpackModuleFunc }, 26 - (require: WebpackRequireType) => any 27 - ]; 28 29 export type WebpackJsonp = WebpackJsonpEntry[] & { 30 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: {
+116 -4
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[]; 47 48 settings?: Record<string, ExtensionSettingsManifest>; 49 cors?: string[]; 50 }; 51 52 export enum ExtensionLoadSource { 53 Developer, 54 Core, ··· 65 webpackModules?: Record<string, string>; 66 nodePath?: string; 67 hostPath?: string; 68 }; 69 }; 70 ··· 96 export type Patch = { 97 find: PatchMatch; 98 replace: PatchReplace | PatchReplace[]; 99 prerequisite?: () => boolean; 100 }; 101 102 export type ExplicitExtensionDependency = { 103 - ext: string; 104 id: string; 105 }; 106 ··· 123 id: number; 124 }; 125 126 - export type IdentifiedWebpackModule = ExtensionWebpackModule & 127 - 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, ··· 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 ··· 236 id: number; 237 }; 238 239 + export type IdentifiedWebpackModule = ExtensionWebpackModule & ExplicitExtensionDependency;
+18
packages/types/src/fs.ts
···
··· 1 + export type MoonlightFS = { 2 + readFile: (path: string) => Promise<Uint8Array>; 3 + readFileString: (path: string) => Promise<string>; 4 + writeFile: (path: string, data: Uint8Array) => Promise<void>; 5 + writeFileString: (path: string, data: string) => Promise<void>; 6 + unlink: (path: string) => Promise<void>; 7 + 8 + readdir: (path: string) => Promise<string[]>; 9 + mkdir: (path: string) => Promise<void>; 10 + rmdir: (path: string) => Promise<void>; 11 + 12 + exists: (path: string) => Promise<boolean>; 13 + isFile: (path: string) => Promise<boolean>; 14 + isDir: (path: string) => Promise<boolean>; 15 + 16 + join: (...parts: string[]) => string; 17 + dirname: (path: string) => string; 18 + };
+67 -15
packages/types/src/globals.ts
··· 1 - import { Logger } from "./logger"; 2 - import { Config, ConfigExtension } from "./config"; 3 - import { 4 - DetectedExtension, 5 - IdentifiedPatch, 6 - IdentifiedWebpackModule, 7 - ProcessedExtensions 8 - } from "./extension"; 9 - import EventEmitter from "events"; 10 11 export type MoonlightHost = { 12 - asarPath: string; 13 config: Config; 14 - events: EventEmitter; 15 extensions: DetectedExtension[]; 16 processedExtensions: ProcessedExtensions; 17 18 getConfig: (ext: string) => ConfigExtension["config"]; 19 getConfigOption: <T>(ext: string, name: string) => T | undefined; 20 getLogger: (id: string) => Logger; 21 }; 22 23 export type MoonlightNode = { ··· 25 extensions: DetectedExtension[]; 26 processedExtensions: ProcessedExtensions; 27 nativesCache: Record<string, any>; 28 29 getConfig: (ext: string) => ConfigExtension["config"]; 30 getConfigOption: <T>(ext: string, name: string) => T | undefined; 31 getNatives: (ext: string) => any | undefined; 32 getLogger: (id: string) => Logger; 33 34 - getExtensionDir: (ext: string) => string; 35 - writeConfig: (config: Config) => void; 36 }; 37 38 export type MoonlightWeb = { 39 unpatched: Set<IdentifiedPatch>; 40 pendingModules: Set<IdentifiedWebpackModule>; 41 enabledExtensions: Set<string>; 42 43 - getConfig: (ext: string) => ConfigExtension["config"]; 44 - getConfigOption: <T>(ext: string, name: string) => T | undefined; 45 getNatives: (ext: string) => any | undefined; 46 getLogger: (id: string) => Logger; 47 }; 48 49 export enum MoonlightEnv { ··· 51 NodePreload = "node-preload", 52 WebPreload = "web-preload" 53 }
··· 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 + }
+52 -27
packages/types/src/import.d.ts
··· 1 - declare module "@moonlight-mod/wp/spacepack_spacepack" { 2 import { CoreExtensions } from "@moonlight-mod/types"; 3 - export const spacepack: CoreExtensions.Spacepack; 4 - export default spacepack; 5 } 6 7 - declare module "@moonlight-mod/wp/common_components" { 8 import { CoreExtensions } from "@moonlight-mod/types"; 9 - const CommonComponent: CoreExtensions.CommonComponents; 10 - export = CommonComponent; 11 } 12 13 - declare module "@moonlight-mod/wp/common_flux" { 14 import { CoreExtensions } from "@moonlight-mod/types"; 15 - const Flux: CoreExtensions.CommonFlux; 16 - // FIXME: This is wrong, the default export differs from the named exports. 17 - export = Flux; 18 } 19 - 20 - declare module "@moonlight-mod/wp/common_fluxDispatcher" { 21 import { CoreExtensions } from "@moonlight-mod/types"; 22 - const Dispatcher: CoreExtensions.CommonFluxDispatcher; 23 - export default Dispatcher; 24 } 25 - 26 declare module "@moonlight-mod/wp/common_stores"; 27 28 - declare module "@moonlight-mod/wp/common_react" { 29 - import React from "react"; 30 - export = React; 31 } 32 33 - declare module "@moonlight-mod/wp/settings_settings" { 34 import { CoreExtensions } from "@moonlight-mod/types"; 35 - export const Settings: CoreExtensions.Settings; 36 - export default Settings; 37 } 38 39 declare module "@moonlight-mod/wp/markdown_markdown" { ··· 42 export = Markdown; 43 } 44 45 - declare module "@moonlight-mod/wp/contextMenu_evilMenu" { 46 import { CoreExtensions } from "@moonlight-mod/types"; 47 - const EvilParser: CoreExtensions.ContextMenu.EvilItemParser; 48 - export = EvilParser; 49 } 50 51 - declare module "@moonlight-mod/wp/contextMenu_contextMenu" { 52 import { CoreExtensions } from "@moonlight-mod/types"; 53 - const ContextMenu: CoreExtensions.ContextMenu.ContextMenu; 54 - export = ContextMenu; 55 }
··· 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" { ··· 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 }
+14 -7
packages/types/src/index.ts
··· 1 /// <reference types="standalone-electron-types" /> 2 /// <reference types="react" /> 3 - /// <reference types="flux" /> 4 /// <reference types="./import" /> 5 /* eslint-disable no-var */ 6 7 - import { 8 - MoonlightEnv, 9 - MoonlightHost, 10 - MoonlightNode, 11 - MoonlightWeb 12 - } from "./globals"; 13 14 export * from "./discord"; 15 export * from "./config"; ··· 18 export * from "./globals"; 19 export * from "./logger"; 20 export * as constants from "./constants"; 21 22 declare global { 23 const MOONLIGHT_ENV: MoonlightEnv; ··· 25 const MOONLIGHT_INJECTOR: boolean; 26 const MOONLIGHT_NODE_PRELOAD: boolean; 27 const MOONLIGHT_WEB_PRELOAD: boolean; 28 29 var moonlightHost: MoonlightHost; 30 var moonlightNode: MoonlightNode; 31 var moonlight: MoonlightWeb; 32 }
··· 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"; ··· 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" {}
+7 -7
packages/types/tsconfig.json
··· 1 { 2 "compilerOptions": { 3 - "target": "es2016", 4 - "module": "es6", 5 - "esModuleInterop": true, 6 - "forceConsistentCasingInFileNames": true, 7 - "strict": true, 8 - "moduleResolution": "bundler", 9 "jsx": "react", 10 - "declaration": true 11 }, 12 "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] 13 }
··· 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 }
+40 -8
packages/web-preload/src/index.ts
··· 1 import { loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; 2 - import { installWebpackPatcher } from "@moonlight-mod/core/patch"; 3 import { installStyles } from "@moonlight-mod/core/styles"; 4 - import Logger from "@moonlight-mod/core/util/logger"; 5 6 - (async () => { 7 const logger = new Logger("web-preload"); 8 9 window.moonlight = { 10 unpatched: new Set(), 11 pendingModules: new Set(), 12 enabledExtensions: new Set(), 13 14 getConfig: moonlightNode.getConfig.bind(moonlightNode), 15 getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), 16 getNatives: moonlightNode.getNatives.bind(moonlightNode), 17 - getLogger: (id: string) => { 18 return new Logger(id); 19 - } 20 }; 21 22 try { 23 await loadProcessedExtensions(moonlightNode.processedExtensions); 24 await installWebpackPatcher(); 25 } catch (e) { 26 logger.error("Error setting up web-preload", e); 27 } 28 29 - window.addEventListener("DOMContentLoaded", () => { 30 installStyles(); 31 - }); 32 - })();
··· 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 }
+1683 -1139
pnpm-lock.yaml
··· 4 autoInstallPeers: true 5 excludeLinksFromLockfile: false 6 7 importers: 8 9 .: 10 devDependencies: 11 - '@typescript-eslint/eslint-plugin': 12 - specifier: ^6.13.2 13 - version: 6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2) 14 - '@typescript-eslint/parser': 15 - specifier: ^6.13.2 16 - version: 6.13.2(eslint@8.55.0)(typescript@5.3.2) 17 esbuild: 18 - specifier: ^0.19.3 19 version: 0.19.3 20 esbuild-copy-static-files: 21 - specifier: ^0.1.0 22 version: 0.1.0 23 eslint: 24 - specifier: ^8.55.0 25 - version: 8.55.0 26 - eslint-config-prettier: 27 - specifier: ^9.1.0 28 - version: 9.1.0(eslint@8.55.0) 29 - eslint-plugin-prettier: 30 - specifier: ^5.0.1 31 - version: 5.0.1(eslint-config-prettier@9.1.0)(eslint@8.55.0)(prettier@3.1.0) 32 - eslint-plugin-react: 33 - specifier: ^7.33.2 34 - version: 7.33.2(eslint@8.55.0) 35 husky: 36 - specifier: ^8.0.3 37 version: 8.0.3 38 prettier: 39 - specifier: ^3.1.0 40 version: 3.1.0 41 typescript: 42 - specifier: ^5.3.2 43 - version: 5.3.2 44 45 packages/core: 46 dependencies: ··· 50 51 packages/core-extensions: 52 dependencies: 53 - '@electron/asar': 54 - specifier: ^3.2.5 55 - version: 3.2.5 56 '@moonlight-mod/types': 57 specifier: workspace:* 58 version: link:../types 59 60 packages/injector: 61 dependencies: ··· 77 78 packages/types: 79 dependencies: 80 - '@types/flux': 81 - specifier: ^3.1.12 82 - version: 3.1.12 83 '@types/react': 84 - specifier: ^18.2.22 85 - version: 18.2.22 86 csstype: 87 - specifier: ^3.1.2 88 - version: 3.1.2 89 standalone-electron-types: 90 specifier: ^1.0.0 91 version: 1.0.0 ··· 95 '@moonlight-mod/core': 96 specifier: workspace:* 97 version: link:../core 98 99 packages: 100 ··· 102 resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} 103 engines: {node: '>=0.10.0'} 104 105 - '@electron/asar@3.2.5': 106 - resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==} 107 - engines: {node: '>=10.12.0'} 108 hasBin: true 109 110 '@esbuild/android-arm64@0.19.3': ··· 239 cpu: [x64] 240 os: [win32] 241 242 - '@eslint-community/eslint-utils@4.4.0': 243 - resolution: {integrity: sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==} 244 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 245 peerDependencies: 246 eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 247 248 - '@eslint-community/regexpp@4.10.0': 249 - resolution: {integrity: sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==} 250 engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} 251 252 - '@eslint/eslintrc@2.1.4': 253 - resolution: {integrity: sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==} 254 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 255 256 - '@eslint/js@8.55.0': 257 - resolution: {integrity: sha512-qQfo2mxH5yVom1kacMtZZJFVdW+E70mqHMJvVg6WTLo+VBuQJ4TojZlfWBjK0ve5BdEeNAVxOsl/nvNMpJOaJA==} 258 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 259 260 - '@humanwhocodes/config-array@0.11.13': 261 - resolution: {integrity: sha512-JSBDMiDKSzQVngfRjOdFXgFfklaXI4K9nLF49Auh21lmBWRLIK3+xTErTWD4KU54pb6coM6ESE7Awz/FNU3zgQ==} 262 - engines: {node: '>=10.10.0'} 263 264 '@humanwhocodes/module-importer@1.0.1': 265 resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} 266 engines: {node: '>=12.22'} 267 268 - '@humanwhocodes/object-schema@2.0.1': 269 - resolution: {integrity: sha512-dvuCeX5fC9dXgJn9t+X5atfmgQAzUOWqS1254Gh0m6i8wKd10ebXkfNKiRK+1GWi/yTvvLDHpoxLr0xxxeslWw==} 270 271 '@nodelib/fs.scandir@2.1.5': 272 resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} ··· 280 resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} 281 engines: {node: '>= 8'} 282 283 - '@pkgr/utils@2.4.2': 284 - resolution: {integrity: sha512-POgTXhjrTfbTV63DiFXav4lBHiICLKKwDeaKn9Nphwj7WH6m0hMMCaJkMyRWjgtPFyRKRVoMXXjczsTQRDEhYw==} 285 engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} 286 287 - '@types/fbemitter@2.0.33': 288 - resolution: {integrity: sha512-KcSilwdl0D8YgXGL6l9d+rTBm2W7pDyTZrDEw0+IzqQ724676KJtMeO+xHodJewKFWZT+GFWaJubA5mpMxSkcg==} 289 290 - '@types/flux@3.1.12': 291 - resolution: {integrity: sha512-HZ8o/DTVNgcgnXoDyn0ZnjqEZMT4Chr4w5ktMQSbQAnqVDklasmRqNGd2agZDsk5i0jYHQLgQQuM782bWG7fUA==} 292 293 '@types/json-schema@7.0.15': 294 resolution: {integrity: sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==} 295 296 '@types/node@18.17.17': 297 resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} 298 299 - '@types/prop-types@15.7.6': 300 - resolution: {integrity: sha512-RK/kBbYOQQHLYj9Z95eh7S6t7gq4Ojt/NT8HTk8bWVhA5DaF+5SMnxHKkP4gPNN3wAZkKP+VjAf0ebtYzf+fxg==} 301 302 - '@types/react@18.2.22': 303 - resolution: {integrity: sha512-60fLTOLqzarLED2O3UQImc/lsNRgG0jE/a1mPW9KjMemY0LMITWEsbS4VvZ4p6rorEHd5YKxxmMKSDK505GHpA==} 304 305 - '@types/scheduler@0.16.3': 306 - resolution: {integrity: sha512-5cJ8CB4yAx7BH1oMvdU0Jh9lrEXyPkar6F9G/ERswkCuvP4KQZfZkSjcMbAICCpQTN4OuZn8tz0HiKv9TGZgrQ==} 307 308 - '@types/semver@7.5.6': 309 - resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} 310 311 - '@typescript-eslint/eslint-plugin@6.13.2': 312 - resolution: {integrity: sha512-3+9OGAWHhk4O1LlcwLBONbdXsAhLjyCFogJY/cWy2lxdVJ2JrcTF2pTGMaLl2AE7U1l31n8Py4a8bx5DLf/0dQ==} 313 - engines: {node: ^16.0.0 || >=18.0.0} 314 peerDependencies: 315 - '@typescript-eslint/parser': ^6.0.0 || ^6.0.0-alpha 316 - eslint: ^7.0.0 || ^8.0.0 317 - typescript: '*' 318 - peerDependenciesMeta: 319 - typescript: 320 - optional: true 321 322 - '@typescript-eslint/parser@6.13.2': 323 - resolution: {integrity: sha512-MUkcC+7Wt/QOGeVlM8aGGJZy1XV5YKjTpq9jK6r6/iLsGXhBVaGP5N0UYvFsu9BFlSpwY9kMretzdBH01rkRXg==} 324 - engines: {node: ^16.0.0 || >=18.0.0} 325 peerDependencies: 326 - eslint: ^7.0.0 || ^8.0.0 327 - typescript: '*' 328 - peerDependenciesMeta: 329 - typescript: 330 - optional: true 331 332 - '@typescript-eslint/scope-manager@6.13.2': 333 - resolution: {integrity: sha512-CXQA0xo7z6x13FeDYCgBkjWzNqzBn8RXaE3QVQVIUm74fWJLkJkaHmHdKStrxQllGh6Q4eUGyNpMe0b1hMkXFA==} 334 - engines: {node: ^16.0.0 || >=18.0.0} 335 336 - '@typescript-eslint/type-utils@6.13.2': 337 - resolution: {integrity: sha512-Qr6ssS1GFongzH2qfnWKkAQmMUyZSyOr0W54nZNU1MDfo+U4Mv3XveeLZzadc/yq8iYhQZHYT+eoXJqnACM1tw==} 338 - engines: {node: ^16.0.0 || >=18.0.0} 339 peerDependencies: 340 - eslint: ^7.0.0 || ^8.0.0 341 - typescript: '*' 342 - peerDependenciesMeta: 343 - typescript: 344 - optional: true 345 346 - '@typescript-eslint/types@6.13.2': 347 - resolution: {integrity: sha512-7sxbQ+EMRubQc3wTfTsycgYpSujyVbI1xw+3UMRUcrhSy+pN09y/lWzeKDbvhoqcRbHdc+APLs/PWYi/cisLPg==} 348 - engines: {node: ^16.0.0 || >=18.0.0} 349 350 - '@typescript-eslint/typescript-estree@6.13.2': 351 - resolution: {integrity: sha512-SuD8YLQv6WHnOEtKv8D6HZUzOub855cfPnPMKvdM/Bh1plv1f7Q/0iFUDLKKlxHcEstQnaUU4QZskgQq74t+3w==} 352 - engines: {node: ^16.0.0 || >=18.0.0} 353 peerDependencies: 354 - typescript: '*' 355 - peerDependenciesMeta: 356 - typescript: 357 - optional: true 358 359 - '@typescript-eslint/utils@6.13.2': 360 - resolution: {integrity: sha512-b9Ptq4eAZUym4idijCRzl61oPCwwREcfDI8xGk751Vhzig5fFZR9CyzDz4Sp/nxSLBYxUPyh4QdIDqWykFhNmQ==} 361 - engines: {node: ^16.0.0 || >=18.0.0} 362 peerDependencies: 363 - eslint: ^7.0.0 || ^8.0.0 364 365 - '@typescript-eslint/visitor-keys@6.13.2': 366 - resolution: {integrity: sha512-OGznFs0eAQXJsp+xSd6k/O1UbFi/K/L7WjqeRoFE7vadjAF9y0uppXhYNQNEqygjou782maGClOoZwPqF0Drlw==} 367 - engines: {node: ^16.0.0 || >=18.0.0} 368 369 - '@ungap/structured-clone@1.2.0': 370 - resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} 371 372 acorn-jsx@5.3.2: 373 resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} 374 peerDependencies: 375 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 376 377 - acorn@8.11.2: 378 - resolution: {integrity: sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==} 379 engines: {node: '>=0.4.0'} 380 hasBin: true 381 382 ajv@6.12.6: 383 resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} 384 385 - ansi-regex@5.0.1: 386 - resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} 387 - engines: {node: '>=8'} 388 - 389 ansi-styles@4.3.0: 390 resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} 391 engines: {node: '>=8'} 392 393 argparse@2.0.1: 394 resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} 395 396 - array-buffer-byte-length@1.0.0: 397 - resolution: {integrity: sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==} 398 399 - array-includes@3.1.7: 400 - resolution: {integrity: sha512-dlcsNBIiWhPkHdOEEKnehA+RNUWDc4UqFtnIXU4uuYDPtA4LDkr7qip2p0VvFAEXNDr0yWZ9PJyIRiGjRLQzwQ==} 401 engines: {node: '>= 0.4'} 402 403 - array-union@2.1.0: 404 - resolution: {integrity: sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==} 405 - engines: {node: '>=8'} 406 407 - array.prototype.flat@1.3.2: 408 - resolution: {integrity: sha512-djYB+Zx2vLewY8RWlNCUdHjDXs2XOgm602S9E7P/UpHgfeHL00cRiIF+IN/G/aUJ7kGPb6yO/ErDI5V2s8iycA==} 409 engines: {node: '>= 0.4'} 410 411 - array.prototype.flatmap@1.3.2: 412 - resolution: {integrity: sha512-Ewyx0c9PmpcsByhSW4r+9zDU7sGjFc86qf/kKtuSCRdhfbk0SNLLkaT5qvcHnRGgc5NP/ly/y+qkXkqONX54CQ==} 413 engines: {node: '>= 0.4'} 414 415 - array.prototype.tosorted@1.1.2: 416 - resolution: {integrity: sha512-HuQCHOlk1Weat5jzStICBCd83NxiIMwqDg/dHEsoefabn/hJRj5pVdWcPUSpRrwhwxZOsQassMpgN/xRYFBMIg==} 417 418 - arraybuffer.prototype.slice@1.0.2: 419 - resolution: {integrity: sha512-yMBKppFur/fbHu9/6USUe03bZ4knMYiwFBcyiaXB8Go0qNehwX6inYPzK9U0NeQvGxKthcmHcaR8P5MStSRBAw==} 420 engines: {node: '>= 0.4'} 421 422 - asynciterator.prototype@1.0.0: 423 - resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} 424 425 - available-typed-arrays@1.0.5: 426 - resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} 427 engines: {node: '>= 0.4'} 428 429 balanced-match@1.0.2: 430 resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} 431 432 - big-integer@1.6.52: 433 - resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} 434 - engines: {node: '>=0.6'} 435 - 436 - bplist-parser@0.2.0: 437 - resolution: {integrity: sha512-z0M+byMThzQmD9NILRniCUXYsYpjwnlO8N5uCFaCqIOpqRsJCrQL9NK3JsD67CN5a08nF5oIL2bD6loTdHOuKw==} 438 - engines: {node: '>= 5.10.0'} 439 440 brace-expansion@1.1.11: 441 resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} 442 443 - braces@3.0.2: 444 - resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} 445 engines: {node: '>=8'} 446 447 - bundle-name@3.0.0: 448 - resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} 449 - engines: {node: '>=12'} 450 451 - call-bind@1.0.5: 452 - resolution: {integrity: sha512-C3nQxfFZxFRVoJoGKKI8y3MOEo129NQ+FgQ08iye+Mk4zNZZGdjfs06bVTr+DBSlA66Q2VEcMki/cUCP4SercQ==} 453 454 callsites@3.1.0: 455 resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==} ··· 466 color-name@1.1.4: 467 resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} 468 469 - commander@5.1.0: 470 - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} 471 - engines: {node: '>= 6'} 472 - 473 concat-map@0.0.1: 474 - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} 475 476 - cross-spawn@7.0.3: 477 - resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} 478 engines: {node: '>= 8'} 479 480 - csstype@3.1.2: 481 - resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} 482 483 - debug@4.3.4: 484 - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} 485 engines: {node: '>=6.0'} 486 peerDependencies: 487 supports-color: '*' ··· 492 deep-is@0.1.4: 493 resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} 494 495 - default-browser-id@3.0.0: 496 - resolution: {integrity: sha512-OZ1y3y0SqSICtE8DE4S8YOE9UZOJ8wO16fKWVP5J1Qz42kV9jcnMVFrEE/noXb/ss3Q4pZIH79kxofzyNNtUNA==} 497 - engines: {node: '>=12'} 498 - 499 - default-browser@4.0.0: 500 - resolution: {integrity: sha512-wX5pXO1+BrhMkSbROFsyxUm0i/cJEScyNhA4PPxc41ICuv05ZZB/MX28s8aZx6xjmatvebIapF6hLEKEcpneUA==} 501 - engines: {node: '>=14.16'} 502 - 503 - define-data-property@1.1.1: 504 - resolution: {integrity: sha512-E7uGkTzkk1d0ByLeSc6ZsFS79Axg+m1P/VsgYsxHgiuc3tFSj+MjMIwe90FC4lOAZzNBdY7kkO2P2wKdsQ1vgQ==} 505 engines: {node: '>= 0.4'} 506 - 507 - define-lazy-prop@3.0.0: 508 - resolution: {integrity: sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg==} 509 - engines: {node: '>=12'} 510 511 define-properties@1.2.1: 512 resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} 513 engines: {node: '>= 0.4'} 514 515 - dir-glob@3.0.1: 516 - resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} 517 - engines: {node: '>=8'} 518 519 doctrine@2.1.0: 520 resolution: {integrity: sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==} 521 engines: {node: '>=0.10.0'} 522 523 - doctrine@3.0.0: 524 - resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} 525 - engines: {node: '>=6.0.0'} 526 527 - es-abstract@1.22.3: 528 - resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} 529 engines: {node: '>= 0.4'} 530 531 - es-iterator-helpers@1.0.15: 532 - resolution: {integrity: sha512-GhoY8uYqd6iwUl2kgjTm4CZAf6oo5mHK7BPqx3rKgx893YSsy0LGHV6gfqqQvZt/8xM8xeOnfXBCfqclMKkJ5g==} 533 534 - es-set-tostringtag@2.0.2: 535 - resolution: {integrity: sha512-BuDyupZt65P9D2D2vA/zqcI3G5xRsklm5N3xCwuiy+/vKy8i0ifdsQP1sLgO4tZDSCaQUSnmC48khknGMV3D2Q==} 536 engines: {node: '>= 0.4'} 537 538 - es-shim-unscopables@1.0.2: 539 - resolution: {integrity: sha512-J3yBRXCzDu4ULnQwxyToo/OjdMx6akgVC7K6few0a7F/0wLtmKKN7I73AH5T2836UuXRqN7Qg+IIUw/+YJksRw==} 540 541 - es-to-primitive@1.2.1: 542 - resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} 543 engines: {node: '>= 0.4'} 544 545 esbuild-copy-static-files@0.1.0: ··· 560 peerDependencies: 561 eslint: '>=7.0.0' 562 563 - eslint-plugin-prettier@5.0.1: 564 - resolution: {integrity: sha512-m3u5RnR56asrwV/lDC4GHorlW75DsFfmUcjfCYylTUs85dBRnB7VM6xG8eCMJdeDRnppzmxZVf1GEPJvl1JmNg==} 565 engines: {node: ^14.18.0 || >=16.0.0} 566 peerDependencies: 567 '@types/eslint': '>=8.0.0' 568 eslint: '>=8.0.0' 569 - eslint-config-prettier: '*' 570 prettier: '>=3.0.0' 571 peerDependenciesMeta: 572 '@types/eslint': ··· 574 eslint-config-prettier: 575 optional: true 576 577 - eslint-plugin-react@7.33.2: 578 - resolution: {integrity: sha512-73QQMKALArI8/7xGLNI/3LylrEYrlKZSb5C9+q3OtOewTnMQi5cT+aE9E41sLCmli3I9PGGmD1yiZydyo4FEPw==} 579 engines: {node: '>=4'} 580 peerDependencies: 581 - eslint: ^3 || ^4 || ^5 || ^6 || ^7 || ^8 582 583 - eslint-scope@7.2.2: 584 - resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} 585 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 586 587 eslint-visitor-keys@3.4.3: 588 resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} 589 engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 590 591 - eslint@8.55.0: 592 - resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} 593 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 594 hasBin: true 595 596 - espree@9.6.1: 597 - resolution: {integrity: sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==} 598 - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 599 600 - esquery@1.5.0: 601 - resolution: {integrity: sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==} 602 engines: {node: '>=0.10'} 603 604 esrecurse@4.3.0: ··· 608 estraverse@5.3.0: 609 resolution: {integrity: sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==} 610 engines: {node: '>=4.0'} 611 612 esutils@2.0.3: 613 resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} 614 engines: {node: '>=0.10.0'} 615 616 - execa@5.1.1: 617 - resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} 618 - engines: {node: '>=10'} 619 620 - execa@7.2.0: 621 - resolution: {integrity: sha512-UduyVP7TLB5IcAQl+OzLyLcS/l32W/GLg+AhHJ+ow40FOk2U3SAllPwR44v4vmdFwIWqpdwxxpQbF1n5ta9seA==} 622 - engines: {node: ^14.18.0 || ^16.14.0 || >=18.0.0} 623 624 fast-deep-equal@3.1.3: 625 resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} ··· 637 fast-levenshtein@2.0.6: 638 resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} 639 640 - fastq@1.15.0: 641 - resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} 642 643 - file-entry-cache@6.0.1: 644 - resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} 645 - engines: {node: ^10.12.0 || >=12.0.0} 646 647 - fill-range@7.0.1: 648 - resolution: {integrity: sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==} 649 engines: {node: '>=8'} 650 651 find-up@5.0.0: 652 resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} 653 engines: {node: '>=10'} 654 655 - flat-cache@3.2.0: 656 - resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==} 657 - engines: {node: ^10.12.0 || >=12.0.0} 658 659 flatted@3.2.9: 660 resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} 661 662 - for-each@0.3.3: 663 - resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} 664 - 665 - fs.realpath@1.0.0: 666 - resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} 667 668 function-bind@1.1.2: 669 resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==} 670 671 - function.prototype.name@1.1.6: 672 - resolution: {integrity: sha512-Z5kx79swU5P27WEayXM1tBi5Ze/lbIyiNgU3qyXUOf9b2rgXYyF9Dy9Cx+IQv/Lc8WCG6L82zwUPpSS9hGehIg==} 673 engines: {node: '>= 0.4'} 674 675 functions-have-names@1.2.3: 676 resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} 677 678 - get-intrinsic@1.2.2: 679 - resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} 680 681 - get-stream@6.0.1: 682 - resolution: {integrity: sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==} 683 - engines: {node: '>=10'} 684 685 - get-symbol-description@1.0.0: 686 - resolution: {integrity: sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==} 687 engines: {node: '>= 0.4'} 688 689 glob-parent@5.1.2: ··· 694 resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} 695 engines: {node: '>=10.13.0'} 696 697 - glob@7.2.3: 698 - resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} 699 700 - globals@13.23.0: 701 - resolution: {integrity: sha512-XAmF0RjlrjY23MA51q3HltdlGxUpXPvg0GioKiD9X6HD28iMjo2dKC8Vqwm7lne4GNr78+RHTfliktR6ZH09wA==} 702 - engines: {node: '>=8'} 703 - 704 - globalthis@1.0.3: 705 - resolution: {integrity: sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==} 706 engines: {node: '>= 0.4'} 707 708 - globby@11.1.0: 709 - resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==} 710 - engines: {node: '>=10'} 711 - 712 - gopd@1.0.1: 713 - resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} 714 715 graphemer@1.4.0: 716 resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} 717 718 - has-bigints@1.0.2: 719 - resolution: {integrity: sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==} 720 721 has-flag@4.0.0: 722 resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} 723 engines: {node: '>=8'} 724 725 - has-property-descriptors@1.0.1: 726 - resolution: {integrity: sha512-VsX8eaIewvas0xnvinAe9bw4WfIeODpGYikiWYLH+dma0Jw6KHYqWiWfhQlgOVK8D6PvjubK5Uc4P0iIhIcNVg==} 727 728 - has-proto@1.0.1: 729 - resolution: {integrity: sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==} 730 engines: {node: '>= 0.4'} 731 732 - has-symbols@1.0.3: 733 - resolution: {integrity: sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==} 734 engines: {node: '>= 0.4'} 735 736 - has-tostringtag@1.0.0: 737 - resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} 738 engines: {node: '>= 0.4'} 739 740 - hasown@2.0.0: 741 - resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} 742 engines: {node: '>= 0.4'} 743 744 - human-signals@2.1.0: 745 - resolution: {integrity: sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==} 746 - engines: {node: '>=10.17.0'} 747 - 748 - human-signals@4.3.1: 749 - resolution: {integrity: sha512-nZXjEF2nbo7lIw3mgYjItAfgQXog3OjJogSbKa2CQIIvSGWcKgeJnQlNXip6NglNzYH45nSRiEVimMvYL8DDqQ==} 750 - engines: {node: '>=14.18.0'} 751 - 752 husky@8.0.3: 753 resolution: {integrity: sha512-+dQSyqPh4x1hlO1swXBiNb2HzTDN1I2IGLQx1GrBuiqFJfoMrnZWwVmatvSiO+Iz8fBUnf+lekwNo4c2LlXItg==} 754 engines: {node: '>=14'} 755 hasBin: true 756 757 - ignore@5.3.0: 758 - resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} 759 engines: {node: '>= 4'} 760 761 import-fresh@3.3.0: ··· 766 resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} 767 engines: {node: '>=0.8.19'} 768 769 - inflight@1.0.6: 770 - resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} 771 772 - inherits@2.0.4: 773 - resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} 774 - 775 - internal-slot@1.0.6: 776 - resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} 777 engines: {node: '>= 0.4'} 778 779 - is-array-buffer@3.0.2: 780 - resolution: {integrity: sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==} 781 - 782 - is-async-function@2.0.0: 783 - resolution: {integrity: sha512-Y1JXKrfykRJGdlDwdKlLpLyMIiWqWvuSd17TvZk68PLAOGOoF4Xyav1z0Xhoi+gCYjZVeC5SI+hYFOfvXmGRCA==} 784 engines: {node: '>= 0.4'} 785 786 - is-bigint@1.0.4: 787 - resolution: {integrity: sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==} 788 789 - is-boolean-object@1.1.2: 790 - resolution: {integrity: sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==} 791 engines: {node: '>= 0.4'} 792 793 is-callable@1.2.7: 794 resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} 795 engines: {node: '>= 0.4'} 796 797 - is-core-module@2.13.1: 798 - resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} 799 - 800 - is-date-object@1.0.5: 801 - resolution: {integrity: sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==} 802 engines: {node: '>= 0.4'} 803 804 - is-docker@2.2.1: 805 - resolution: {integrity: sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ==} 806 - engines: {node: '>=8'} 807 - hasBin: true 808 809 - is-docker@3.0.0: 810 - resolution: {integrity: sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ==} 811 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 812 - hasBin: true 813 814 is-extglob@2.1.1: 815 resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} 816 engines: {node: '>=0.10.0'} 817 818 - is-finalizationregistry@1.0.2: 819 - resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} 820 821 - is-generator-function@1.0.10: 822 - resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} 823 engines: {node: '>= 0.4'} 824 825 is-glob@4.0.3: 826 resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} 827 engines: {node: '>=0.10.0'} 828 829 - is-inside-container@1.0.0: 830 - resolution: {integrity: sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA==} 831 - engines: {node: '>=14.16'} 832 - hasBin: true 833 - 834 - is-map@2.0.2: 835 - resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} 836 - 837 - is-negative-zero@2.0.2: 838 - resolution: {integrity: sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==} 839 engines: {node: '>= 0.4'} 840 841 - is-number-object@1.0.7: 842 - resolution: {integrity: sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==} 843 engines: {node: '>= 0.4'} 844 845 is-number@7.0.0: 846 resolution: {integrity: sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==} 847 engines: {node: '>=0.12.0'} 848 849 - is-path-inside@3.0.3: 850 - resolution: {integrity: sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==} 851 - engines: {node: '>=8'} 852 853 - is-regex@1.1.4: 854 - resolution: {integrity: sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==} 855 engines: {node: '>= 0.4'} 856 857 - is-set@2.0.2: 858 - resolution: {integrity: sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==} 859 860 - is-shared-array-buffer@1.0.2: 861 - resolution: {integrity: sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==} 862 863 - is-stream@2.0.1: 864 - resolution: {integrity: sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==} 865 - engines: {node: '>=8'} 866 - 867 - is-stream@3.0.0: 868 - resolution: {integrity: sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==} 869 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 870 - 871 - is-string@1.0.7: 872 - resolution: {integrity: sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==} 873 engines: {node: '>= 0.4'} 874 875 - is-symbol@1.0.4: 876 - resolution: {integrity: sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==} 877 engines: {node: '>= 0.4'} 878 879 - is-typed-array@1.1.12: 880 - resolution: {integrity: sha512-Z14TF2JNG8Lss5/HMqt0//T9JeHXttXy5pH/DBU4vi98ozO2btxzq9MwYDZYnKwU8nRsz/+GVFVRDq3DkVuSPg==} 881 engines: {node: '>= 0.4'} 882 883 - is-weakmap@2.0.1: 884 - resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} 885 886 - is-weakref@1.0.2: 887 - resolution: {integrity: sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==} 888 - 889 - is-weakset@2.0.2: 890 - resolution: {integrity: sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==} 891 - 892 - is-wsl@2.2.0: 893 - resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} 894 - engines: {node: '>=8'} 895 896 isarray@2.0.5: 897 resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} ··· 899 isexe@2.0.0: 900 resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} 901 902 - iterator.prototype@1.1.2: 903 - resolution: {integrity: sha512-DR33HMMr8EzwuRL8Y9D3u2BMj8+RqSE850jfGu59kS7tbmPLzGkZmVSfyCFSDxuZiEY6Rzt3T2NA/qU+NwVj1w==} 904 905 js-tokens@4.0.0: 906 resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} ··· 940 resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} 941 hasBin: true 942 943 - lru-cache@6.0.0: 944 - resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} 945 - engines: {node: '>=10'} 946 - 947 - merge-stream@2.0.0: 948 - resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} 949 950 merge2@1.4.1: 951 resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} 952 engines: {node: '>= 8'} 953 954 - micromatch@4.0.5: 955 - resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} 956 engines: {node: '>=8.6'} 957 958 - mimic-fn@2.1.0: 959 - resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} 960 - engines: {node: '>=6'} 961 - 962 - mimic-fn@4.0.0: 963 - resolution: {integrity: sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==} 964 - engines: {node: '>=12'} 965 966 minimatch@3.1.2: 967 resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} 968 969 - ms@2.1.2: 970 - resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} 971 972 natural-compare@1.4.0: 973 resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} 974 975 - npm-run-path@4.0.1: 976 - resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} 977 - engines: {node: '>=8'} 978 - 979 - npm-run-path@5.1.0: 980 - resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} 981 - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} 982 983 object-assign@4.1.1: 984 resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} 985 engines: {node: '>=0.10.0'} 986 987 - object-inspect@1.13.1: 988 - resolution: {integrity: sha512-5qoj1RUiKOMsCCNLV1CBiPYE10sziTsnmNxkAI/rZhiD63CF7IqdFGC/XzjWjpSgLf0LxXX3bDFIh0E18f6UhQ==} 989 990 object-keys@1.1.1: 991 resolution: {integrity: sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==} 992 engines: {node: '>= 0.4'} 993 994 - object.assign@4.1.5: 995 - resolution: {integrity: sha512-byy+U7gp+FVwmyzKPYhW2h5l3crpmGsxl7X2s8y43IgxvG4g3QZ6CffDtsNQy1WsmZpQbO+ybo0AlW7TY6DcBQ==} 996 engines: {node: '>= 0.4'} 997 998 - object.entries@1.1.7: 999 - resolution: {integrity: sha512-jCBs/0plmPsOnrKAfFQXRG2NFjlhZgjjcBLSmTnEhU8U6vVTsVe8ANeQJCHTl3gSsI4J+0emOoCgoKlmQPMgmA==} 1000 engines: {node: '>= 0.4'} 1001 1002 - object.fromentries@2.0.7: 1003 - resolution: {integrity: sha512-UPbPHML6sL8PI/mOqPwsH4G6iyXcCGzLin8KvEPenOZN5lpCNBZZQ+V62vdjB1mQHrmqGQt5/OJzemUA+KJmEA==} 1004 engines: {node: '>= 0.4'} 1005 1006 - object.hasown@1.1.3: 1007 - resolution: {integrity: sha512-fFI4VcYpRHvSLXxP7yiZOMAd331cPfd2p7PFDVbgUsYOfCT3tICVqXWngbjr4m49OvsBwUBQ6O2uQoJvy3RexA==} 1008 - 1009 - object.values@1.1.7: 1010 - resolution: {integrity: sha512-aU6xnDFYT3x17e/f0IiiwlGPTy2jzMySGfUB4fq6z7CV8l85CWHDk5ErhyhpfDHhrOMwGFhSQkhMGHaIotA6Ng==} 1011 engines: {node: '>= 0.4'} 1012 1013 - once@1.4.0: 1014 - resolution: {integrity: sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==} 1015 - 1016 - onetime@5.1.2: 1017 - resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==} 1018 - engines: {node: '>=6'} 1019 - 1020 - onetime@6.0.0: 1021 - resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} 1022 - engines: {node: '>=12'} 1023 1024 - open@9.1.0: 1025 - resolution: {integrity: sha512-OS+QTnw1/4vrf+9hh1jc1jnYjzSG4ttTBB8UxOwAnInG3Uo4ssetzC1ihqaIHjLJnA5GGlRl6QlZXOTQhRBUvg==} 1026 - engines: {node: '>=14.16'} 1027 1028 optionator@0.9.3: 1029 resolution: {integrity: sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==} 1030 engines: {node: '>= 0.8.0'} 1031 1032 p-limit@3.1.0: 1033 resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} ··· 1037 resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} 1038 engines: {node: '>=10'} 1039 1040 parent-module@1.0.1: 1041 resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} 1042 engines: {node: '>=6'} ··· 1045 resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} 1046 engines: {node: '>=8'} 1047 1048 - path-is-absolute@1.0.1: 1049 - resolution: {integrity: sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==} 1050 - engines: {node: '>=0.10.0'} 1051 - 1052 path-key@3.1.1: 1053 resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==} 1054 engines: {node: '>=8'} 1055 - 1056 - path-key@4.0.0: 1057 - resolution: {integrity: sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==} 1058 - engines: {node: '>=12'} 1059 1060 path-parse@1.0.7: 1061 resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} 1062 1063 - path-type@4.0.0: 1064 - resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} 1065 - engines: {node: '>=8'} 1066 - 1067 - picocolors@1.0.0: 1068 - resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} 1069 1070 picomatch@2.3.1: 1071 resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} 1072 engines: {node: '>=8.6'} 1073 1074 prelude-ls@1.2.1: 1075 resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} 1076 engines: {node: '>= 0.8.0'} ··· 1084 engines: {node: '>=14'} 1085 hasBin: true 1086 1087 prop-types@15.8.1: 1088 resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} 1089 ··· 1091 resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} 1092 engines: {node: '>=6'} 1093 1094 queue-microtask@1.2.3: 1095 resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} 1096 1097 react-is@16.13.1: 1098 resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} 1099 1100 - reflect.getprototypeof@1.0.4: 1101 - resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} 1102 engines: {node: '>= 0.4'} 1103 1104 - regexp.prototype.flags@1.5.1: 1105 - resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} 1106 engines: {node: '>= 0.4'} 1107 1108 resolve-from@4.0.0: ··· 1113 resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} 1114 hasBin: true 1115 1116 reusify@1.0.4: 1117 resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} 1118 engines: {iojs: '>=1.0.0', node: '>=0.10.0'} 1119 1120 - rimraf@3.0.2: 1121 - resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} 1122 - hasBin: true 1123 - 1124 - run-applescript@5.0.0: 1125 - resolution: {integrity: sha512-XcT5rBksx1QdIhlFOCtgZkB99ZEouFZ1E2Kc2LHqNW13U3/74YGdkQRmThTwxy4QIyookibDKYZOPqX//6BlAg==} 1126 - engines: {node: '>=12'} 1127 - 1128 run-parallel@1.2.0: 1129 resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} 1130 1131 - safe-array-concat@1.0.1: 1132 - resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} 1133 engines: {node: '>=0.4'} 1134 1135 - safe-regex-test@1.0.0: 1136 - resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} 1137 1138 semver@6.3.1: 1139 resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} 1140 hasBin: true 1141 1142 - semver@7.5.4: 1143 - resolution: {integrity: sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==} 1144 engines: {node: '>=10'} 1145 hasBin: true 1146 1147 - set-function-length@1.1.1: 1148 - resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} 1149 engines: {node: '>= 0.4'} 1150 1151 - set-function-name@2.0.1: 1152 - resolution: {integrity: sha512-tMNCiqYVkXIZgc2Hnoy2IvC/f8ezc5koaRFkCjrpWzGpCd3qbZXPzVy9MAZzK1ch/X0jvSkojys3oqJN0qCmdA==} 1153 engines: {node: '>= 0.4'} 1154 1155 shebang-command@2.0.0: ··· 1160 resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==} 1161 engines: {node: '>=8'} 1162 1163 - side-channel@1.0.4: 1164 - resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} 1165 1166 - signal-exit@3.0.7: 1167 - resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} 1168 1169 - slash@3.0.0: 1170 - resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} 1171 - engines: {node: '>=8'} 1172 1173 standalone-electron-types@1.0.0: 1174 resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} 1175 1176 - string.prototype.matchall@4.0.10: 1177 - resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} 1178 - 1179 - string.prototype.trim@1.2.8: 1180 - resolution: {integrity: sha512-lfjY4HcixfQXOfaqCvcBuOIapyaroTXhbkfJN3gcB1OtyupngWK4sEET9Knd0cXd28kTUqu/kHoV4HKSJdnjiQ==} 1181 engines: {node: '>= 0.4'} 1182 1183 - string.prototype.trimend@1.0.7: 1184 - resolution: {integrity: sha512-Ni79DqeB72ZFq1uH/L6zJ+DKZTkOtPIHovb3YZHQViE+HDouuU4mBrLOLDn5Dde3RF8qw5qVETEjhu9locMLvA==} 1185 1186 - string.prototype.trimstart@1.0.7: 1187 - resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} 1188 1189 - strip-ansi@6.0.1: 1190 - resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} 1191 - engines: {node: '>=8'} 1192 1193 - strip-final-newline@2.0.0: 1194 - resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==} 1195 - engines: {node: '>=6'} 1196 1197 - strip-final-newline@3.0.0: 1198 - resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} 1199 - engines: {node: '>=12'} 1200 1201 strip-json-comments@3.1.1: 1202 resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} ··· 1210 resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} 1211 engines: {node: '>= 0.4'} 1212 1213 - synckit@0.8.6: 1214 - resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==} 1215 engines: {node: ^14.18.0 || >=16.0.0} 1216 1217 - text-table@0.2.0: 1218 - resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} 1219 1220 - titleize@3.0.0: 1221 - resolution: {integrity: sha512-KxVu8EYHDPBdUYdKZdKtU2aj2XfEx9AfjXxE/Aj0vT06w2icA09Vus1rh6eSu1y01akYg6BjIK/hxyLJINoMLQ==} 1222 - engines: {node: '>=12'} 1223 1224 to-regex-range@5.0.1: 1225 resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} 1226 engines: {node: '>=8.0'} 1227 1228 - ts-api-utils@1.0.3: 1229 - resolution: {integrity: sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==} 1230 - engines: {node: '>=16.13.0'} 1231 peerDependencies: 1232 - typescript: '>=4.2.0' 1233 1234 - tslib@2.6.2: 1235 - resolution: {integrity: sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==} 1236 1237 type-check@0.4.0: 1238 resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==} 1239 engines: {node: '>= 0.8.0'} 1240 1241 - type-fest@0.20.2: 1242 - resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} 1243 - engines: {node: '>=10'} 1244 1245 - typed-array-buffer@1.0.0: 1246 - resolution: {integrity: sha512-Y8KTSIglk9OZEr8zywiIHG/kmQ7KWyjseXs1CbSo8vC42w7hg2HgYTxSWwP0+is7bWDc1H+Fo026CpHFwm8tkw==} 1247 engines: {node: '>= 0.4'} 1248 1249 - typed-array-byte-length@1.0.0: 1250 - resolution: {integrity: sha512-Or/+kvLxNpeQ9DtSydonMxCx+9ZXOswtwJn17SNLvhptaXYDJvkFFP5zbfU/uLmvnBJlI4yrnXRxpdWH/M5tNA==} 1251 engines: {node: '>= 0.4'} 1252 1253 - typed-array-byte-offset@1.0.0: 1254 - resolution: {integrity: sha512-RD97prjEt9EL8YgAgpOkf3O4IF9lhJFr9g0htQkm0rchFp/Vx7LW5Q8fSXXub7BXAODyUQohRMyOc3faCPd0hg==} 1255 engines: {node: '>= 0.4'} 1256 1257 - typed-array-length@1.0.4: 1258 - resolution: {integrity: sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==} 1259 1260 - typescript@5.3.2: 1261 - resolution: {integrity: sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==} 1262 engines: {node: '>=14.17'} 1263 hasBin: true 1264 1265 - unbox-primitive@1.0.2: 1266 - resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} 1267 1268 - untildify@4.0.0: 1269 - resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} 1270 - engines: {node: '>=8'} 1271 1272 uri-js@4.4.1: 1273 resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} 1274 1275 - which-boxed-primitive@1.0.2: 1276 - resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} 1277 1278 - which-builtin-type@1.1.3: 1279 - resolution: {integrity: sha512-YmjsSMDBYsM1CaFiayOVT06+KJeXf0o5M/CAd4o1lTadFAtacTUM49zoYxr/oroopFDfhvN6iEcBxUyc3gvKmw==} 1280 engines: {node: '>= 0.4'} 1281 1282 - which-collection@1.0.1: 1283 - resolution: {integrity: sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==} 1284 1285 - which-typed-array@1.1.13: 1286 - resolution: {integrity: sha512-P5Nra0qjSncduVPEAr7xhoF5guty49ArDTwzJ/yNuPIbZppyRxFQsRCWrocxIY+CnMVG+qfbU2FmDKyvSGClow==} 1287 engines: {node: '>= 0.4'} 1288 1289 which@2.0.2: ··· 1291 engines: {node: '>= 8'} 1292 hasBin: true 1293 1294 - wrappy@1.0.2: 1295 - resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} 1296 - 1297 - yallist@4.0.0: 1298 - resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} 1299 1300 yocto-queue@0.1.0: 1301 resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} 1302 engines: {node: '>=10'} 1303 1304 snapshots: 1305 1306 '@aashutoshrathi/word-wrap@1.2.6': {} 1307 1308 - '@electron/asar@3.2.5': 1309 dependencies: 1310 - commander: 5.1.0 1311 - glob: 7.2.3 1312 - minimatch: 3.1.2 1313 1314 '@esbuild/android-arm64@0.19.3': 1315 optional: true ··· 1377 '@esbuild/win32-x64@0.19.3': 1378 optional: true 1379 1380 - '@eslint-community/eslint-utils@4.4.0(eslint@8.55.0)': 1381 dependencies: 1382 - eslint: 8.55.0 1383 eslint-visitor-keys: 3.4.3 1384 1385 - '@eslint-community/regexpp@4.10.0': {} 1386 1387 - '@eslint/eslintrc@2.1.4': 1388 dependencies: 1389 ajv: 6.12.6 1390 - debug: 4.3.4 1391 - espree: 9.6.1 1392 - globals: 13.23.0 1393 - ignore: 5.3.0 1394 import-fresh: 3.3.0 1395 js-yaml: 4.1.0 1396 minimatch: 3.1.2 ··· 1398 transitivePeerDependencies: 1399 - supports-color 1400 1401 - '@eslint/js@8.55.0': {} 1402 1403 - '@humanwhocodes/config-array@0.11.13': 1404 dependencies: 1405 - '@humanwhocodes/object-schema': 2.0.1 1406 - debug: 4.3.4 1407 - minimatch: 3.1.2 1408 transitivePeerDependencies: 1409 - supports-color 1410 1411 - '@humanwhocodes/module-importer@1.0.1': {} 1412 1413 - '@humanwhocodes/object-schema@2.0.1': {} 1414 1415 '@nodelib/fs.scandir@2.1.5': 1416 dependencies: ··· 1422 '@nodelib/fs.walk@1.2.8': 1423 dependencies: 1424 '@nodelib/fs.scandir': 2.1.5 1425 - fastq: 1.15.0 1426 1427 - '@pkgr/utils@2.4.2': 1428 dependencies: 1429 - cross-spawn: 7.0.3 1430 - fast-glob: 3.3.2 1431 - is-glob: 4.0.3 1432 - open: 9.1.0 1433 - picocolors: 1.0.0 1434 - tslib: 2.6.2 1435 1436 - '@types/fbemitter@2.0.33': {} 1437 1438 - '@types/flux@3.1.12': 1439 dependencies: 1440 - '@types/fbemitter': 2.0.33 1441 - '@types/react': 18.2.22 1442 1443 '@types/json-schema@7.0.15': {} 1444 1445 '@types/node@18.17.17': {} 1446 1447 - '@types/prop-types@15.7.6': {} 1448 1449 - '@types/react@18.2.22': 1450 dependencies: 1451 - '@types/prop-types': 15.7.6 1452 - '@types/scheduler': 0.16.3 1453 - csstype: 3.1.2 1454 1455 - '@types/scheduler@0.16.3': {} 1456 1457 - '@types/semver@7.5.6': {} 1458 1459 - '@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2)(eslint@8.55.0)(typescript@5.3.2)': 1460 dependencies: 1461 - '@eslint-community/regexpp': 4.10.0 1462 - '@typescript-eslint/parser': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1463 - '@typescript-eslint/scope-manager': 6.13.2 1464 - '@typescript-eslint/type-utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1465 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1466 - '@typescript-eslint/visitor-keys': 6.13.2 1467 - debug: 4.3.4 1468 - eslint: 8.55.0 1469 graphemer: 1.4.0 1470 - ignore: 5.3.0 1471 natural-compare: 1.4.0 1472 - semver: 7.5.4 1473 - ts-api-utils: 1.0.3(typescript@5.3.2) 1474 - typescript: 5.3.2 1475 transitivePeerDependencies: 1476 - supports-color 1477 1478 - '@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1479 dependencies: 1480 - '@typescript-eslint/scope-manager': 6.13.2 1481 - '@typescript-eslint/types': 6.13.2 1482 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1483 - '@typescript-eslint/visitor-keys': 6.13.2 1484 - debug: 4.3.4 1485 - eslint: 8.55.0 1486 - typescript: 5.3.2 1487 transitivePeerDependencies: 1488 - supports-color 1489 1490 - '@typescript-eslint/scope-manager@6.13.2': 1491 dependencies: 1492 - '@typescript-eslint/types': 6.13.2 1493 - '@typescript-eslint/visitor-keys': 6.13.2 1494 1495 - '@typescript-eslint/type-utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1496 dependencies: 1497 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1498 - '@typescript-eslint/utils': 6.13.2(eslint@8.55.0)(typescript@5.3.2) 1499 - debug: 4.3.4 1500 - eslint: 8.55.0 1501 - ts-api-utils: 1.0.3(typescript@5.3.2) 1502 - typescript: 5.3.2 1503 transitivePeerDependencies: 1504 - supports-color 1505 1506 - '@typescript-eslint/types@6.13.2': {} 1507 1508 - '@typescript-eslint/typescript-estree@6.13.2(typescript@5.3.2)': 1509 dependencies: 1510 - '@typescript-eslint/types': 6.13.2 1511 - '@typescript-eslint/visitor-keys': 6.13.2 1512 - debug: 4.3.4 1513 - globby: 11.1.0 1514 is-glob: 4.0.3 1515 - semver: 7.5.4 1516 - ts-api-utils: 1.0.3(typescript@5.3.2) 1517 - typescript: 5.3.2 1518 transitivePeerDependencies: 1519 - supports-color 1520 1521 - '@typescript-eslint/utils@6.13.2(eslint@8.55.0)(typescript@5.3.2)': 1522 dependencies: 1523 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 1524 - '@types/json-schema': 7.0.15 1525 - '@types/semver': 7.5.6 1526 - '@typescript-eslint/scope-manager': 6.13.2 1527 - '@typescript-eslint/types': 6.13.2 1528 - '@typescript-eslint/typescript-estree': 6.13.2(typescript@5.3.2) 1529 - eslint: 8.55.0 1530 - semver: 7.5.4 1531 transitivePeerDependencies: 1532 - supports-color 1533 - - typescript 1534 1535 - '@typescript-eslint/visitor-keys@6.13.2': 1536 dependencies: 1537 - '@typescript-eslint/types': 6.13.2 1538 - eslint-visitor-keys: 3.4.3 1539 1540 - '@ungap/structured-clone@1.2.0': {} 1541 1542 - acorn-jsx@5.3.2(acorn@8.11.2): 1543 dependencies: 1544 - acorn: 8.11.2 1545 1546 - acorn@8.11.2: {} 1547 1548 ajv@6.12.6: 1549 dependencies: ··· 1552 json-schema-traverse: 0.4.1 1553 uri-js: 4.4.1 1554 1555 - ansi-regex@5.0.1: {} 1556 - 1557 ansi-styles@4.3.0: 1558 dependencies: 1559 color-convert: 2.0.1 1560 1561 argparse@2.0.1: {} 1562 1563 - array-buffer-byte-length@1.0.0: 1564 dependencies: 1565 - call-bind: 1.0.5 1566 - is-array-buffer: 3.0.2 1567 1568 - array-includes@3.1.7: 1569 dependencies: 1570 - call-bind: 1.0.5 1571 define-properties: 1.2.1 1572 - es-abstract: 1.22.3 1573 - get-intrinsic: 1.2.2 1574 - is-string: 1.0.7 1575 - 1576 - array-union@2.1.0: {} 1577 1578 - array.prototype.flat@1.3.2: 1579 dependencies: 1580 - call-bind: 1.0.5 1581 define-properties: 1.2.1 1582 - es-abstract: 1.22.3 1583 - es-shim-unscopables: 1.0.2 1584 1585 - array.prototype.flatmap@1.3.2: 1586 dependencies: 1587 - call-bind: 1.0.5 1588 define-properties: 1.2.1 1589 - es-abstract: 1.22.3 1590 - es-shim-unscopables: 1.0.2 1591 1592 - array.prototype.tosorted@1.1.2: 1593 dependencies: 1594 - call-bind: 1.0.5 1595 define-properties: 1.2.1 1596 - es-abstract: 1.22.3 1597 - es-shim-unscopables: 1.0.2 1598 - get-intrinsic: 1.2.2 1599 1600 - arraybuffer.prototype.slice@1.0.2: 1601 dependencies: 1602 - array-buffer-byte-length: 1.0.0 1603 - call-bind: 1.0.5 1604 define-properties: 1.2.1 1605 - es-abstract: 1.22.3 1606 - get-intrinsic: 1.2.2 1607 - is-array-buffer: 3.0.2 1608 - is-shared-array-buffer: 1.0.2 1609 1610 - asynciterator.prototype@1.0.0: 1611 dependencies: 1612 - has-symbols: 1.0.3 1613 - 1614 - available-typed-arrays@1.0.5: {} 1615 1616 - balanced-match@1.0.2: {} 1617 1618 - big-integer@1.6.52: {} 1619 1620 - bplist-parser@0.2.0: 1621 dependencies: 1622 - big-integer: 1.6.52 1623 1624 brace-expansion@1.1.11: 1625 dependencies: 1626 balanced-match: 1.0.2 1627 concat-map: 0.0.1 1628 1629 - braces@3.0.2: 1630 dependencies: 1631 - fill-range: 7.0.1 1632 1633 - bundle-name@3.0.0: 1634 dependencies: 1635 - run-applescript: 5.0.0 1636 1637 - call-bind@1.0.5: 1638 dependencies: 1639 function-bind: 1.1.2 1640 - get-intrinsic: 1.2.2 1641 - set-function-length: 1.1.1 1642 1643 callsites@3.1.0: {} 1644 ··· 1653 1654 color-name@1.1.4: {} 1655 1656 - commander@5.1.0: {} 1657 - 1658 concat-map@0.0.1: {} 1659 1660 - cross-spawn@7.0.3: 1661 dependencies: 1662 path-key: 3.1.1 1663 shebang-command: 2.0.0 1664 which: 2.0.2 1665 1666 - csstype@3.1.2: {} 1667 1668 - debug@4.3.4: 1669 dependencies: 1670 - ms: 2.1.2 1671 1672 - deep-is@0.1.4: {} 1673 - 1674 - default-browser-id@3.0.0: 1675 dependencies: 1676 - bplist-parser: 0.2.0 1677 - untildify: 4.0.0 1678 1679 - default-browser@4.0.0: 1680 dependencies: 1681 - bundle-name: 3.0.0 1682 - default-browser-id: 3.0.0 1683 - execa: 7.2.0 1684 - titleize: 3.0.0 1685 1686 - define-data-property@1.1.1: 1687 dependencies: 1688 - get-intrinsic: 1.2.2 1689 - gopd: 1.0.1 1690 - has-property-descriptors: 1.0.1 1691 1692 - define-lazy-prop@3.0.0: {} 1693 1694 define-properties@1.2.1: 1695 dependencies: 1696 - define-data-property: 1.1.1 1697 - has-property-descriptors: 1.0.1 1698 object-keys: 1.1.1 1699 1700 - dir-glob@3.0.1: 1701 - dependencies: 1702 - path-type: 4.0.0 1703 1704 doctrine@2.1.0: 1705 dependencies: 1706 esutils: 2.0.3 1707 1708 - doctrine@3.0.0: 1709 dependencies: 1710 - esutils: 2.0.3 1711 1712 - es-abstract@1.22.3: 1713 dependencies: 1714 - array-buffer-byte-length: 1.0.0 1715 - arraybuffer.prototype.slice: 1.0.2 1716 - available-typed-arrays: 1.0.5 1717 - call-bind: 1.0.5 1718 - es-set-tostringtag: 2.0.2 1719 - es-to-primitive: 1.2.1 1720 - function.prototype.name: 1.1.6 1721 - get-intrinsic: 1.2.2 1722 - get-symbol-description: 1.0.0 1723 - globalthis: 1.0.3 1724 - gopd: 1.0.1 1725 - has-property-descriptors: 1.0.1 1726 - has-proto: 1.0.1 1727 - has-symbols: 1.0.3 1728 - hasown: 2.0.0 1729 - internal-slot: 1.0.6 1730 - is-array-buffer: 3.0.2 1731 is-callable: 1.2.7 1732 - is-negative-zero: 2.0.2 1733 - is-regex: 1.1.4 1734 - is-shared-array-buffer: 1.0.2 1735 - is-string: 1.0.7 1736 - is-typed-array: 1.1.12 1737 - is-weakref: 1.0.2 1738 - object-inspect: 1.13.1 1739 object-keys: 1.1.1 1740 - object.assign: 4.1.5 1741 - regexp.prototype.flags: 1.5.1 1742 - safe-array-concat: 1.0.1 1743 - safe-regex-test: 1.0.0 1744 - string.prototype.trim: 1.2.8 1745 - string.prototype.trimend: 1.0.7 1746 - string.prototype.trimstart: 1.0.7 1747 - typed-array-buffer: 1.0.0 1748 - typed-array-byte-length: 1.0.0 1749 - typed-array-byte-offset: 1.0.0 1750 - typed-array-length: 1.0.4 1751 - unbox-primitive: 1.0.2 1752 - which-typed-array: 1.1.13 1753 1754 - es-iterator-helpers@1.0.15: 1755 dependencies: 1756 - asynciterator.prototype: 1.0.0 1757 - call-bind: 1.0.5 1758 define-properties: 1.2.1 1759 - es-abstract: 1.22.3 1760 - es-set-tostringtag: 2.0.2 1761 function-bind: 1.1.2 1762 - get-intrinsic: 1.2.2 1763 - globalthis: 1.0.3 1764 - has-property-descriptors: 1.0.1 1765 - has-proto: 1.0.1 1766 - has-symbols: 1.0.3 1767 - internal-slot: 1.0.6 1768 - iterator.prototype: 1.1.2 1769 - safe-array-concat: 1.0.1 1770 1771 - es-set-tostringtag@2.0.2: 1772 dependencies: 1773 - get-intrinsic: 1.2.2 1774 - has-tostringtag: 1.0.0 1775 - hasown: 2.0.0 1776 1777 - es-shim-unscopables@1.0.2: 1778 dependencies: 1779 - hasown: 2.0.0 1780 1781 - es-to-primitive@1.2.1: 1782 dependencies: 1783 is-callable: 1.2.7 1784 - is-date-object: 1.0.5 1785 - is-symbol: 1.0.4 1786 1787 esbuild-copy-static-files@0.1.0: {} 1788 ··· 1813 1814 escape-string-regexp@4.0.0: {} 1815 1816 - eslint-config-prettier@9.1.0(eslint@8.55.0): 1817 dependencies: 1818 - eslint: 8.55.0 1819 1820 - eslint-plugin-prettier@5.0.1(eslint-config-prettier@9.1.0)(eslint@8.55.0)(prettier@3.1.0): 1821 dependencies: 1822 - eslint: 8.55.0 1823 - eslint-config-prettier: 9.1.0(eslint@8.55.0) 1824 prettier: 3.1.0 1825 prettier-linter-helpers: 1.0.0 1826 - synckit: 0.8.6 1827 1828 - eslint-plugin-react@7.33.2(eslint@8.55.0): 1829 dependencies: 1830 - array-includes: 3.1.7 1831 - array.prototype.flatmap: 1.3.2 1832 - array.prototype.tosorted: 1.1.2 1833 doctrine: 2.1.0 1834 - es-iterator-helpers: 1.0.15 1835 - eslint: 8.55.0 1836 estraverse: 5.3.0 1837 jsx-ast-utils: 3.3.5 1838 minimatch: 3.1.2 1839 - object.entries: 1.1.7 1840 - object.fromentries: 2.0.7 1841 - object.hasown: 1.1.3 1842 - object.values: 1.1.7 1843 prop-types: 15.8.1 1844 resolve: 2.0.0-next.5 1845 semver: 6.3.1 1846 - string.prototype.matchall: 4.0.10 1847 1848 - eslint-scope@7.2.2: 1849 dependencies: 1850 esrecurse: 4.3.0 1851 estraverse: 5.3.0 1852 1853 eslint-visitor-keys@3.4.3: {} 1854 1855 - eslint@8.55.0: 1856 dependencies: 1857 - '@eslint-community/eslint-utils': 4.4.0(eslint@8.55.0) 1858 - '@eslint-community/regexpp': 4.10.0 1859 - '@eslint/eslintrc': 2.1.4 1860 - '@eslint/js': 8.55.0 1861 - '@humanwhocodes/config-array': 0.11.13 1862 '@humanwhocodes/module-importer': 1.0.1 1863 - '@nodelib/fs.walk': 1.2.8 1864 - '@ungap/structured-clone': 1.2.0 1865 ajv: 6.12.6 1866 chalk: 4.1.2 1867 - cross-spawn: 7.0.3 1868 - debug: 4.3.4 1869 - doctrine: 3.0.0 1870 escape-string-regexp: 4.0.0 1871 - eslint-scope: 7.2.2 1872 - eslint-visitor-keys: 3.4.3 1873 - espree: 9.6.1 1874 - esquery: 1.5.0 1875 esutils: 2.0.3 1876 fast-deep-equal: 3.1.3 1877 - file-entry-cache: 6.0.1 1878 find-up: 5.0.0 1879 glob-parent: 6.0.2 1880 - globals: 13.23.0 1881 - graphemer: 1.4.0 1882 - ignore: 5.3.0 1883 imurmurhash: 0.1.4 1884 is-glob: 4.0.3 1885 - is-path-inside: 3.0.3 1886 - js-yaml: 4.1.0 1887 json-stable-stringify-without-jsonify: 1.0.1 1888 - levn: 0.4.1 1889 lodash.merge: 4.6.2 1890 minimatch: 3.1.2 1891 natural-compare: 1.4.0 1892 optionator: 0.9.3 1893 - strip-ansi: 6.0.1 1894 - text-table: 0.2.0 1895 transitivePeerDependencies: 1896 - supports-color 1897 1898 - espree@9.6.1: 1899 dependencies: 1900 - acorn: 8.11.2 1901 - acorn-jsx: 5.3.2(acorn@8.11.2) 1902 - eslint-visitor-keys: 3.4.3 1903 1904 - esquery@1.5.0: 1905 dependencies: 1906 estraverse: 5.3.0 1907 ··· 1911 1912 estraverse@5.3.0: {} 1913 1914 esutils@2.0.3: {} 1915 1916 - execa@5.1.1: 1917 - dependencies: 1918 - cross-spawn: 7.0.3 1919 - get-stream: 6.0.1 1920 - human-signals: 2.1.0 1921 - is-stream: 2.0.1 1922 - merge-stream: 2.0.0 1923 - npm-run-path: 4.0.1 1924 - onetime: 5.1.2 1925 - signal-exit: 3.0.7 1926 - strip-final-newline: 2.0.0 1927 1928 - execa@7.2.0: 1929 - dependencies: 1930 - cross-spawn: 7.0.3 1931 - get-stream: 6.0.1 1932 - human-signals: 4.3.1 1933 - is-stream: 3.0.0 1934 - merge-stream: 2.0.0 1935 - npm-run-path: 5.1.0 1936 - onetime: 6.0.0 1937 - signal-exit: 3.0.7 1938 - strip-final-newline: 3.0.0 1939 1940 fast-deep-equal@3.1.3: {} 1941 ··· 1947 '@nodelib/fs.walk': 1.2.8 1948 glob-parent: 5.1.2 1949 merge2: 1.4.1 1950 - micromatch: 4.0.5 1951 1952 fast-json-stable-stringify@2.1.0: {} 1953 1954 fast-levenshtein@2.0.6: {} 1955 1956 - fastq@1.15.0: 1957 dependencies: 1958 reusify: 1.0.4 1959 1960 - file-entry-cache@6.0.1: 1961 dependencies: 1962 - flat-cache: 3.2.0 1963 1964 - fill-range@7.0.1: 1965 dependencies: 1966 to-regex-range: 5.0.1 1967 1968 find-up@5.0.0: 1969 dependencies: 1970 locate-path: 6.0.0 1971 path-exists: 4.0.0 1972 1973 - flat-cache@3.2.0: 1974 dependencies: 1975 flatted: 3.2.9 1976 keyv: 4.5.4 1977 - rimraf: 3.0.2 1978 1979 flatted@3.2.9: {} 1980 1981 - for-each@0.3.3: 1982 dependencies: 1983 is-callable: 1.2.7 1984 1985 - fs.realpath@1.0.0: {} 1986 - 1987 function-bind@1.1.2: {} 1988 1989 - function.prototype.name@1.1.6: 1990 dependencies: 1991 - call-bind: 1.0.5 1992 define-properties: 1.2.1 1993 - es-abstract: 1.22.3 1994 functions-have-names: 1.2.3 1995 1996 functions-have-names@1.2.3: {} 1997 1998 - get-intrinsic@1.2.2: 1999 dependencies: 2000 function-bind: 1.1.2 2001 - has-proto: 1.0.1 2002 - has-symbols: 1.0.3 2003 - hasown: 2.0.0 2004 2005 - get-stream@6.0.1: {} 2006 2007 - get-symbol-description@1.0.0: 2008 dependencies: 2009 - call-bind: 1.0.5 2010 - get-intrinsic: 1.2.2 2011 2012 glob-parent@5.1.2: 2013 dependencies: ··· 2017 dependencies: 2018 is-glob: 4.0.3 2019 2020 - glob@7.2.3: 2021 - dependencies: 2022 - fs.realpath: 1.0.0 2023 - inflight: 1.0.6 2024 - inherits: 2.0.4 2025 - minimatch: 3.1.2 2026 - once: 1.4.0 2027 - path-is-absolute: 1.0.1 2028 2029 - globals@13.23.0: 2030 - dependencies: 2031 - type-fest: 0.20.2 2032 - 2033 - globalthis@1.0.3: 2034 dependencies: 2035 define-properties: 1.2.1 2036 2037 - globby@11.1.0: 2038 - dependencies: 2039 - array-union: 2.1.0 2040 - dir-glob: 3.0.1 2041 - fast-glob: 3.3.2 2042 - ignore: 5.3.0 2043 - merge2: 1.4.1 2044 - slash: 3.0.0 2045 - 2046 - gopd@1.0.1: 2047 - dependencies: 2048 - get-intrinsic: 1.2.2 2049 2050 graphemer@1.4.0: {} 2051 2052 - has-bigints@1.0.2: {} 2053 2054 has-flag@4.0.0: {} 2055 2056 - has-property-descriptors@1.0.1: 2057 dependencies: 2058 - get-intrinsic: 1.2.2 2059 2060 - has-proto@1.0.1: {} 2061 2062 - has-symbols@1.0.3: {} 2063 2064 - has-tostringtag@1.0.0: 2065 dependencies: 2066 - has-symbols: 1.0.3 2067 2068 - hasown@2.0.0: 2069 dependencies: 2070 function-bind: 1.1.2 2071 2072 - human-signals@2.1.0: {} 2073 - 2074 - human-signals@4.3.1: {} 2075 - 2076 husky@8.0.3: {} 2077 2078 - ignore@5.3.0: {} 2079 2080 import-fresh@3.3.0: 2081 dependencies: ··· 2084 2085 imurmurhash@0.1.4: {} 2086 2087 - inflight@1.0.6: 2088 dependencies: 2089 - once: 1.4.0 2090 - wrappy: 1.0.2 2091 2092 - inherits@2.0.4: {} 2093 - 2094 - internal-slot@1.0.6: 2095 dependencies: 2096 - get-intrinsic: 1.2.2 2097 - hasown: 2.0.0 2098 - side-channel: 1.0.4 2099 2100 - is-array-buffer@3.0.2: 2101 dependencies: 2102 - call-bind: 1.0.5 2103 - get-intrinsic: 1.2.2 2104 - is-typed-array: 1.1.12 2105 2106 - is-async-function@2.0.0: 2107 dependencies: 2108 - has-tostringtag: 1.0.0 2109 2110 - is-bigint@1.0.4: 2111 dependencies: 2112 - has-bigints: 1.0.2 2113 - 2114 - is-boolean-object@1.1.2: 2115 - dependencies: 2116 - call-bind: 1.0.5 2117 - has-tostringtag: 1.0.0 2118 2119 is-callable@1.2.7: {} 2120 2121 - is-core-module@2.13.1: 2122 dependencies: 2123 - hasown: 2.0.0 2124 2125 - is-date-object@1.0.5: 2126 dependencies: 2127 - has-tostringtag: 1.0.0 2128 2129 - is-docker@2.2.1: {} 2130 - 2131 - is-docker@3.0.0: {} 2132 2133 is-extglob@2.1.1: {} 2134 2135 - is-finalizationregistry@1.0.2: 2136 dependencies: 2137 - call-bind: 1.0.5 2138 2139 - is-generator-function@1.0.10: 2140 dependencies: 2141 - has-tostringtag: 1.0.0 2142 2143 is-glob@4.0.3: 2144 dependencies: 2145 is-extglob: 2.1.1 2146 2147 - is-inside-container@1.0.0: 2148 - dependencies: 2149 - is-docker: 3.0.0 2150 - 2151 - is-map@2.0.2: {} 2152 - 2153 - is-negative-zero@2.0.2: {} 2154 2155 - is-number-object@1.0.7: 2156 dependencies: 2157 - has-tostringtag: 1.0.0 2158 2159 is-number@7.0.0: {} 2160 2161 - is-path-inside@3.0.3: {} 2162 - 2163 - is-regex@1.1.4: 2164 dependencies: 2165 - call-bind: 1.0.5 2166 - has-tostringtag: 1.0.0 2167 2168 - is-set@2.0.2: {} 2169 2170 - is-shared-array-buffer@1.0.2: 2171 dependencies: 2172 - call-bind: 1.0.5 2173 - 2174 - is-stream@2.0.1: {} 2175 - 2176 - is-stream@3.0.0: {} 2177 2178 - is-string@1.0.7: 2179 dependencies: 2180 - has-tostringtag: 1.0.0 2181 2182 - is-symbol@1.0.4: 2183 dependencies: 2184 - has-symbols: 1.0.3 2185 2186 - is-typed-array@1.1.12: 2187 dependencies: 2188 - which-typed-array: 1.1.13 2189 2190 - is-weakmap@2.0.1: {} 2191 2192 - is-weakref@1.0.2: 2193 dependencies: 2194 - call-bind: 1.0.5 2195 2196 - is-weakset@2.0.2: 2197 dependencies: 2198 - call-bind: 1.0.5 2199 - get-intrinsic: 1.2.2 2200 - 2201 - is-wsl@2.2.0: 2202 - dependencies: 2203 - is-docker: 2.2.1 2204 2205 isarray@2.0.5: {} 2206 2207 isexe@2.0.0: {} 2208 2209 - iterator.prototype@1.1.2: 2210 dependencies: 2211 - define-properties: 1.2.1 2212 - get-intrinsic: 1.2.2 2213 - has-symbols: 1.0.3 2214 - reflect.getprototypeof: 1.0.4 2215 - set-function-name: 2.0.1 2216 2217 js-tokens@4.0.0: {} 2218 ··· 2228 2229 jsx-ast-utils@3.3.5: 2230 dependencies: 2231 - array-includes: 3.1.7 2232 - array.prototype.flat: 1.3.2 2233 - object.assign: 4.1.5 2234 - object.values: 1.1.7 2235 2236 keyv@4.5.4: 2237 dependencies: ··· 2252 dependencies: 2253 js-tokens: 4.0.0 2254 2255 - lru-cache@6.0.0: 2256 - dependencies: 2257 - yallist: 4.0.0 2258 - 2259 - merge-stream@2.0.0: {} 2260 2261 merge2@1.4.1: {} 2262 2263 - micromatch@4.0.5: 2264 dependencies: 2265 - braces: 3.0.2 2266 picomatch: 2.3.1 2267 2268 - mimic-fn@2.1.0: {} 2269 - 2270 - mimic-fn@4.0.0: {} 2271 2272 minimatch@3.1.2: 2273 dependencies: 2274 brace-expansion: 1.1.11 2275 2276 - ms@2.1.2: {} 2277 2278 - natural-compare@1.4.0: {} 2279 2280 - npm-run-path@4.0.1: 2281 - dependencies: 2282 - path-key: 3.1.1 2283 2284 - npm-run-path@5.1.0: 2285 - dependencies: 2286 - path-key: 4.0.0 2287 2288 object-assign@4.1.1: {} 2289 2290 - object-inspect@1.13.1: {} 2291 2292 object-keys@1.1.1: {} 2293 2294 - object.assign@4.1.5: 2295 dependencies: 2296 - call-bind: 1.0.5 2297 define-properties: 1.2.1 2298 - has-symbols: 1.0.3 2299 object-keys: 1.1.1 2300 2301 - object.entries@1.1.7: 2302 - dependencies: 2303 - call-bind: 1.0.5 2304 - define-properties: 1.2.1 2305 - es-abstract: 1.22.3 2306 - 2307 - object.fromentries@2.0.7: 2308 dependencies: 2309 - call-bind: 1.0.5 2310 define-properties: 1.2.1 2311 - es-abstract: 1.22.3 2312 2313 - object.hasown@1.1.3: 2314 dependencies: 2315 define-properties: 1.2.1 2316 - es-abstract: 1.22.3 2317 2318 - object.values@1.1.7: 2319 dependencies: 2320 - call-bind: 1.0.5 2321 define-properties: 1.2.1 2322 - es-abstract: 1.22.3 2323 - 2324 - once@1.4.0: 2325 - dependencies: 2326 - wrappy: 1.0.2 2327 - 2328 - onetime@5.1.2: 2329 - dependencies: 2330 - mimic-fn: 2.1.0 2331 2332 - onetime@6.0.0: 2333 dependencies: 2334 - mimic-fn: 4.0.0 2335 2336 - open@9.1.0: 2337 dependencies: 2338 - default-browser: 4.0.0 2339 - define-lazy-prop: 3.0.0 2340 - is-inside-container: 1.0.0 2341 - is-wsl: 2.2.0 2342 2343 optionator@0.9.3: 2344 dependencies: ··· 2349 prelude-ls: 1.2.1 2350 type-check: 0.4.0 2351 2352 p-limit@3.1.0: 2353 dependencies: 2354 yocto-queue: 0.1.0 ··· 2356 p-locate@5.0.0: 2357 dependencies: 2358 p-limit: 3.1.0 2359 2360 parent-module@1.0.1: 2361 dependencies: ··· 2363 2364 path-exists@4.0.0: {} 2365 2366 - path-is-absolute@1.0.1: {} 2367 - 2368 path-key@3.1.1: {} 2369 2370 - path-key@4.0.0: {} 2371 - 2372 path-parse@1.0.7: {} 2373 2374 - path-type@4.0.0: {} 2375 - 2376 - picocolors@1.0.0: {} 2377 2378 picomatch@2.3.1: {} 2379 2380 prelude-ls@1.2.1: {} 2381 2382 prettier-linter-helpers@1.0.0: ··· 2385 2386 prettier@3.1.0: {} 2387 2388 prop-types@15.8.1: 2389 dependencies: 2390 loose-envify: 1.4.0 ··· 2393 2394 punycode@2.3.1: {} 2395 2396 queue-microtask@1.2.3: {} 2397 2398 react-is@16.13.1: {} 2399 2400 - reflect.getprototypeof@1.0.4: 2401 dependencies: 2402 - call-bind: 1.0.5 2403 define-properties: 1.2.1 2404 - es-abstract: 1.22.3 2405 - get-intrinsic: 1.2.2 2406 - globalthis: 1.0.3 2407 - which-builtin-type: 1.1.3 2408 2409 - regexp.prototype.flags@1.5.1: 2410 dependencies: 2411 - call-bind: 1.0.5 2412 define-properties: 1.2.1 2413 - set-function-name: 2.0.1 2414 2415 resolve-from@4.0.0: {} 2416 2417 resolve@2.0.0-next.5: 2418 dependencies: 2419 - is-core-module: 2.13.1 2420 path-parse: 1.0.7 2421 supports-preserve-symlinks-flag: 1.0.0 2422 2423 - reusify@1.0.4: {} 2424 - 2425 - rimraf@3.0.2: 2426 dependencies: 2427 - glob: 7.2.3 2428 2429 - run-applescript@5.0.0: 2430 - dependencies: 2431 - execa: 5.1.1 2432 2433 run-parallel@1.2.0: 2434 dependencies: 2435 queue-microtask: 1.2.3 2436 2437 - safe-array-concat@1.0.1: 2438 dependencies: 2439 - call-bind: 1.0.5 2440 - get-intrinsic: 1.2.2 2441 - has-symbols: 1.0.3 2442 isarray: 2.0.5 2443 2444 - safe-regex-test@1.0.0: 2445 dependencies: 2446 - call-bind: 1.0.5 2447 - get-intrinsic: 1.2.2 2448 - is-regex: 1.1.4 2449 2450 semver@6.3.1: {} 2451 2452 - semver@7.5.4: 2453 - dependencies: 2454 - lru-cache: 6.0.0 2455 2456 - set-function-length@1.1.1: 2457 dependencies: 2458 - define-data-property: 1.1.1 2459 - get-intrinsic: 1.2.2 2460 - gopd: 1.0.1 2461 - has-property-descriptors: 1.0.1 2462 2463 - set-function-name@2.0.1: 2464 dependencies: 2465 - define-data-property: 1.1.1 2466 functions-have-names: 1.2.3 2467 - has-property-descriptors: 1.0.1 2468 2469 shebang-command@2.0.0: 2470 dependencies: ··· 2472 2473 shebang-regex@3.0.0: {} 2474 2475 - side-channel@1.0.4: 2476 dependencies: 2477 - call-bind: 1.0.5 2478 - get-intrinsic: 1.2.2 2479 - object-inspect: 1.13.1 2480 2481 - signal-exit@3.0.7: {} 2482 2483 - slash@3.0.0: {} 2484 2485 standalone-electron-types@1.0.0: 2486 dependencies: 2487 '@types/node': 18.17.17 2488 2489 - string.prototype.matchall@4.0.10: 2490 dependencies: 2491 - call-bind: 1.0.5 2492 define-properties: 1.2.1 2493 - es-abstract: 1.22.3 2494 - get-intrinsic: 1.2.2 2495 - has-symbols: 1.0.3 2496 - internal-slot: 1.0.6 2497 - regexp.prototype.flags: 1.5.1 2498 - set-function-name: 2.0.1 2499 - side-channel: 1.0.4 2500 2501 - string.prototype.trim@1.2.8: 2502 dependencies: 2503 - call-bind: 1.0.5 2504 define-properties: 1.2.1 2505 - es-abstract: 1.22.3 2506 2507 - string.prototype.trimend@1.0.7: 2508 dependencies: 2509 - call-bind: 1.0.5 2510 define-properties: 1.2.1 2511 - es-abstract: 1.22.3 2512 2513 - string.prototype.trimstart@1.0.7: 2514 dependencies: 2515 - call-bind: 1.0.5 2516 define-properties: 1.2.1 2517 - es-abstract: 1.22.3 2518 2519 - strip-ansi@6.0.1: 2520 dependencies: 2521 - ansi-regex: 5.0.1 2522 - 2523 - strip-final-newline@2.0.0: {} 2524 2525 - strip-final-newline@3.0.0: {} 2526 2527 strip-json-comments@3.1.1: {} 2528 ··· 2532 2533 supports-preserve-symlinks-flag@1.0.0: {} 2534 2535 - synckit@0.8.6: 2536 dependencies: 2537 - '@pkgr/utils': 2.4.2 2538 - tslib: 2.6.2 2539 2540 - text-table@0.2.0: {} 2541 2542 - titleize@3.0.0: {} 2543 2544 to-regex-range@5.0.1: 2545 dependencies: 2546 is-number: 7.0.0 2547 2548 - ts-api-utils@1.0.3(typescript@5.3.2): 2549 dependencies: 2550 - typescript: 5.3.2 2551 2552 - tslib@2.6.2: {} 2553 2554 type-check@0.4.0: 2555 dependencies: 2556 prelude-ls: 1.2.1 2557 2558 - type-fest@0.20.2: {} 2559 2560 - typed-array-buffer@1.0.0: 2561 dependencies: 2562 - call-bind: 1.0.5 2563 - get-intrinsic: 1.2.2 2564 - is-typed-array: 1.1.12 2565 2566 - typed-array-byte-length@1.0.0: 2567 dependencies: 2568 - call-bind: 1.0.5 2569 - for-each: 0.3.3 2570 - has-proto: 1.0.1 2571 - is-typed-array: 1.1.12 2572 2573 - typed-array-byte-offset@1.0.0: 2574 dependencies: 2575 - available-typed-arrays: 1.0.5 2576 - call-bind: 1.0.5 2577 - for-each: 0.3.3 2578 - has-proto: 1.0.1 2579 - is-typed-array: 1.1.12 2580 2581 - typed-array-length@1.0.4: 2582 dependencies: 2583 - call-bind: 1.0.5 2584 - for-each: 0.3.3 2585 - is-typed-array: 1.1.12 2586 2587 - typescript@5.3.2: {} 2588 2589 - unbox-primitive@1.0.2: 2590 dependencies: 2591 - call-bind: 1.0.5 2592 - has-bigints: 1.0.2 2593 - has-symbols: 1.0.3 2594 - which-boxed-primitive: 1.0.2 2595 2596 - untildify@4.0.0: {} 2597 2598 uri-js@4.4.1: 2599 dependencies: 2600 punycode: 2.3.1 2601 2602 - which-boxed-primitive@1.0.2: 2603 dependencies: 2604 - is-bigint: 1.0.4 2605 - is-boolean-object: 1.1.2 2606 - is-number-object: 1.0.7 2607 - is-string: 1.0.7 2608 - is-symbol: 1.0.4 2609 2610 - which-builtin-type@1.1.3: 2611 dependencies: 2612 - function.prototype.name: 1.1.6 2613 - has-tostringtag: 1.0.0 2614 - is-async-function: 2.0.0 2615 - is-date-object: 1.0.5 2616 - is-finalizationregistry: 1.0.2 2617 - is-generator-function: 1.0.10 2618 - is-regex: 1.1.4 2619 - is-weakref: 1.0.2 2620 isarray: 2.0.5 2621 - which-boxed-primitive: 1.0.2 2622 - which-collection: 1.0.1 2623 - which-typed-array: 1.1.13 2624 2625 - which-collection@1.0.1: 2626 dependencies: 2627 - is-map: 2.0.2 2628 - is-set: 2.0.2 2629 - is-weakmap: 2.0.1 2630 - is-weakset: 2.0.2 2631 2632 - which-typed-array@1.1.13: 2633 dependencies: 2634 - available-typed-arrays: 1.0.5 2635 - call-bind: 1.0.5 2636 - for-each: 0.3.3 2637 - gopd: 1.0.1 2638 - has-tostringtag: 1.0.0 2639 2640 which@2.0.2: 2641 dependencies: 2642 isexe: 2.0.0 2643 2644 - wrappy@1.0.2: {} 2645 2646 - yallist@4.0.0: {} 2647 2648 - yocto-queue@0.1.0: {}
··· 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: ··· 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 ··· 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': ··· 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==} ··· 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==} ··· 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: '*' ··· 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: ··· 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': ··· 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: ··· 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==} ··· 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: ··· 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: ··· 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==} ··· 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==} ··· 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==} ··· 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'} ··· 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'} ··· 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 ··· 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: ··· 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: ··· 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==} ··· 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: ··· 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 ··· 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 ··· 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: ··· 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: ··· 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 ··· 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 ··· 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 ··· 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 ··· 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: ··· 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: ··· 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 ··· 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: ··· 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: ··· 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 ··· 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: ··· 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: ··· 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 ··· 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: ··· 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 ··· 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 - "moduleResolution": "bundler", 9 "baseUrl": "./packages/", 10 - "jsx": "react", 11 - "noEmit": true, 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 }