From 433c19b20a094295498b625f0e6c2538c834c4ac Mon Sep 17 00:00:00 2001 From: NotNite Date: Sun, 5 May 2024 16:47:41 -0400 Subject: [PATCH 01/73] Initial work on browser support --- build.mjs | 97 +++++++++++--- package.json | 2 + packages/browser/manifest.json | 34 +++++ packages/browser/modifyResponseHeaders.json | 19 +++ packages/browser/package.json | 8 ++ packages/browser/src/index.ts | 49 +++++++ packages/browser/tsconfig.json | 3 + .../src/moonbase/webpackModules/stores.ts | 49 ++++++- packages/core/src/config.ts | 34 ++++- packages/core/src/extension.ts | 49 ++++++- packages/core/src/extension/loader.ts | 122 ++++++++++-------- packages/core/src/util/event.ts | 62 +++++---- packages/core/src/util/logger.ts | 2 +- packages/injector/src/index.ts | 2 +- packages/node-preload/src/index.ts | 10 +- packages/types/src/index.ts | 1 + packages/web-preload/package.json | 1 + packages/web-preload/src/index.ts | 6 +- pnpm-lock.yaml | 9 ++ 19 files changed, 440 insertions(+), 119 deletions(-) create mode 100644 packages/browser/manifest.json create mode 100644 packages/browser/modifyResponseHeaders.json create mode 100644 packages/browser/package.json create mode 100644 packages/browser/src/index.ts create mode 100644 packages/browser/tsconfig.json diff --git a/build.mjs b/build.mjs index 2a51741..490f494 100644 --- a/build.mjs +++ b/build.mjs @@ -13,6 +13,7 @@ const config = { const prod = process.env.NODE_ENV === "production"; const watch = process.argv.includes("--watch"); +const browser = process.argv.includes("--browser"); const external = [ "electron", @@ -73,19 +74,26 @@ const taggedBuildLog = (tag) => ({ }); async function build(name, entry) { - const outfile = path.join("./dist", name + ".js"); + let outfile = path.join("./dist", name + ".js"); + if (name === "browser") outfile = path.join("./dist", "browser", "index.js"); const dropLabels = []; if (name !== "injector") dropLabels.push("injector"); if (name !== "node-preload") dropLabels.push("nodePreload"); if (name !== "web-preload") dropLabels.push("webPreload"); + if (name !== "browser") dropLabels.push("browser"); const define = { MOONLIGHT_ENV: `"${name}"`, MOONLIGHT_PROD: prod.toString() }; - for (const iterName of Object.keys(config)) { + for (const iterName of [ + "injector", + "node-preload", + "web-preload", + "browser" + ]) { const snake = iterName.replace(/-/g, "_").toUpperCase(); define[`MOONLIGHT_${snake}`] = (name === iterName).toString(); } @@ -93,13 +101,29 @@ async function build(name, entry) { const nodeDependencies = ["glob"]; const ignoredExternal = name === "web-preload" ? nodeDependencies : []; + const plugins = [deduplicatedLogging, taggedBuildLog(name)]; + if (name === "browser") { + plugins.push( + copyStaticFiles({ + src: "./packages/browser/manifest.json", + dest: "./dist/browser/manifest.json" + }) + ); + plugins.push( + copyStaticFiles({ + src: "./packages/browser/modifyResponseHeaders.json", + dest: "./dist/browser/modifyResponseHeaders.json" + }) + ); + } + /** @type {import("esbuild").BuildOptions} */ const esbuildConfig = { entryPoints: [entry], outfile, format: "cjs", - platform: name === "web-preload" ? "browser" : "node", + platform: ["web-preload", "browser"].includes(name) ? "browser" : "node", treeShaking: true, bundle: true, @@ -112,9 +136,38 @@ async function build(name, entry) { dropLabels, logLevel: "silent", - plugins: [deduplicatedLogging, taggedBuildLog(name)] + plugins }; + if (name === "browser") { + const coreExtensionsJson = {}; + + // eslint-disable-next-line no-inner-declarations + function readDir(dir) { + const files = fs.readdirSync(dir); + for (const file of files) { + const filePath = dir + "/" + file; + const normalizedPath = filePath.replace("./dist/core-extensions/", ""); + if (fs.statSync(filePath).isDirectory()) { + readDir(filePath); + } else { + coreExtensionsJson[normalizedPath] = fs.readFileSync( + filePath, + "utf8" + ); + } + } + } + + readDir("./dist/core-extensions"); + + esbuildConfig.banner = { + js: `window._moonlight_coreExtensionsStr = ${JSON.stringify( + JSON.stringify(coreExtensionsJson) + )};` + }; + } + if (watch) { const ctx = await esbuild.context(esbuildConfig); await ctx.watch(); @@ -210,23 +263,27 @@ async function buildExt(ext, side, copyManifest, fileExt) { const promises = []; -for (const [name, entry] of Object.entries(config)) { - promises.push(build(name, entry)); -} +if (browser) { + build("browser", "packages/browser/src/index.ts"); +} else { + for (const [name, entry] of Object.entries(config)) { + promises.push(build(name, entry)); + } -const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); -for (const ext of coreExtensions) { - let copiedManifest = false; - - for (const fileExt of ["ts", "tsx"]) { - for (const type of ["index", "node", "host"]) { - if ( - fs.existsSync( - `./packages/core-extensions/src/${ext}/${type}.${fileExt}` - ) - ) { - promises.push(buildExt(ext, type, !copiedManifest, fileExt)); - copiedManifest = true; + const coreExtensions = fs.readdirSync("./packages/core-extensions/src"); + for (const ext of coreExtensions) { + let copiedManifest = false; + + for (const fileExt of ["ts", "tsx"]) { + for (const type of ["index", "node", "host"]) { + if ( + fs.existsSync( + `./packages/core-extensions/src/${ext}/${type}.${fileExt}` + ) + ) { + promises.push(buildExt(ext, type, !copiedManifest, fileExt)); + copiedManifest = true; + } } } } diff --git a/package.json b/package.json index 32f99c7..c2ba342 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,8 @@ "scripts": { "build": "node build.mjs", "dev": "node build.mjs --watch", + "browser": "node build.mjs --browser", + "browser-dev": "node build.mjs --browser --watch", "lint": "eslint packages", "lint:fix": "eslint packages", "lint:report": "eslint --output-file eslint_report.json --format json packages", diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json new file mode 100644 index 0000000..8600cab --- /dev/null +++ b/packages/browser/manifest.json @@ -0,0 +1,34 @@ +{ + "manifest_version": 3, + "name": "moonlight", + "description": "Yet another Discord mod", + "version": "1.0.8", + "permissions": ["declarativeNetRequestWithHostAccess"], + "content_scripts": [ + { + "js": ["index.js"], + "matches": ["https://*.discord.com/*"], + "run_at": "document_start", + "world": "MAIN" + } + ], + "web_accessible_resources": [ + { + "resources": ["core-extensions/*"], + "matches": ["https://*.discord.com/*"] + } + ], + "host_permissions": [ + "https://moonlight-mod.github.io/*", + "https://*.discord.com/*" + ], + "declarative_net_request": { + "rule_resources": [ + { + "id": "modifyResponseHeaders", + "enabled": true, + "path": "modifyResponseHeaders.json" + } + ] + } +} diff --git a/packages/browser/modifyResponseHeaders.json b/packages/browser/modifyResponseHeaders.json new file mode 100644 index 0000000..51ce30d --- /dev/null +++ b/packages/browser/modifyResponseHeaders.json @@ -0,0 +1,19 @@ +[ + { + "id": 1, + "priority": 1, + "action": { + "type": "modifyHeaders", + "responseHeaders": [ + { + "header": "Content-Security-Policy", + "operation": "remove" + } + ] + }, + "condition": { + "resourceTypes": ["main_frame"], + "initiatorDomains": ["discord.com"] + } + } +] diff --git a/packages/browser/package.json b/packages/browser/package.json new file mode 100644 index 0000000..3fa3e4b --- /dev/null +++ b/packages/browser/package.json @@ -0,0 +1,8 @@ +{ + "name": "@moonlight-mod/browser", + "private": true, + "dependencies": { + "@moonlight-mod/core": "workspace:*", + "@moonlight-mod/web-preload": "workspace:*" + } +} diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts new file mode 100644 index 0000000..90a12b0 --- /dev/null +++ b/packages/browser/src/index.ts @@ -0,0 +1,49 @@ +import load from "@moonlight-mod/web-preload"; +import { readConfig, writeConfig } from "@moonlight-mod/core/config"; +import Logger from "@moonlight-mod/core/util/logger"; +import { getExtensions } from "@moonlight-mod/core/extension"; +import { loadExtensions } from "@moonlight-mod/core/extension/loader"; + +// Mostly copy pasted from node-preload, FIXME +// TODO: is this safe an in IIFE? +(async () => { + const config = readConfig(); + const extensions = await getExtensions(); + const processedExtensions = await loadExtensions(extensions); + + function getConfig(ext: string) { + const val = config.extensions[ext]; + if (val == null || typeof val === "boolean") return undefined; + return val.config; + } + + Object.assign(window, { + moonlightNode: { + config, + extensions, + processedExtensions, + nativesCache: {}, + + getConfig, + getConfigOption: (ext: string, name: string) => { + const config = getConfig(ext); + if (config == null) return undefined; + const option = config[name]; + if (option == null) return undefined; + return option as T; + }, + getNatives: () => {}, + getLogger: (id: string) => { + return new Logger(id); + }, + + getExtensionDir: (ext: string) => { + return `/extensions/${ext}`; + }, + + writeConfig + } + }); + + await load(); +})(); diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json new file mode 100644 index 0000000..4082f16 --- /dev/null +++ b/packages/browser/tsconfig.json @@ -0,0 +1,3 @@ +{ + "extends": "../../tsconfig.json" +} diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 6f920bd..7f480d2 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,11 +1,50 @@ import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; -import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; +import { + ExtensionState, + MoonbaseExtension, + MoonbaseNatives, + RepositoryManifest +} from "../types"; import Flux from "@moonlight-mod/wp/common_flux"; import Dispatcher from "@moonlight-mod/wp/common_fluxDispatcher"; -const natives: MoonbaseNatives = moonlight.getNatives("moonbase"); const logger = moonlight.getLogger("moonbase"); +let natives: MoonbaseNatives | undefined = moonlight.getNatives("moonbase"); +if (!natives) { + natives = { + fetchRepositories: async (repos) => { + const ret: Record = {}; + + for (const repo of repos) { + try { + const req = await fetch(repo); + const json = await req.json(); + ret[repo] = json; + } catch (e) { + logger.error(`Error fetching repository ${repo}`, e); + } + } + + return ret; + }, + installExtension: async (manifest, url, repo) => { + // TODO + }, + deleteExtension: async (id) => { + // TODO + }, + getExtensionConfig: (id, key) => { + const config = moonlightNode.config.extensions[id]; + if (typeof config === "object") { + return config.config?.[key]; + } + + return undefined; + } + }; +} + class MoonbaseSettingsStore extends Flux.Store { private origConfig: Config; private config: Config; @@ -42,7 +81,7 @@ class MoonbaseSettingsStore extends Flux.Store { }; } - natives.fetchRepositories(this.config.repositories).then((ret) => { + natives!.fetchRepositories(this.config.repositories).then((ret) => { for (const [repo, exts] of Object.entries(ret)) { try { for (const ext of exts) { @@ -217,7 +256,7 @@ class MoonbaseSettingsStore extends Flux.Store { this.installing = true; try { const url = this.updates[uniqueId]?.download ?? ext.manifest.download; - await natives.installExtension(ext.manifest, url, ext.source.url!); + await natives!.installExtension(ext.manifest, url, ext.source.url!); if (ext.state === ExtensionState.NotDownloaded) { this.extensions[uniqueId].state = ExtensionState.Disabled; } @@ -237,7 +276,7 @@ class MoonbaseSettingsStore extends Flux.Store { this.installing = true; try { - await natives.deleteExtension(ext.id); + await natives!.deleteExtension(ext.id); this.extensions[uniqueId].state = ExtensionState.NotDownloaded; } catch (e) { logger.error("Error deleting extension:", e); diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index eefa38a..16e48b5 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -13,9 +13,19 @@ const defaultConfig: Config = { }; export function writeConfig(config: Config) { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + nodePreload: { + const fs = requireImport("fs"); + const configPath = getConfigPath(); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); + return; + } + + browser: { + localStorage.setItem("moonlight-config", JSON.stringify(config)); + return; + } + + throw new Error("Called writeConfig() in an impossible environment"); } function readConfigNode(): Config { @@ -36,6 +46,20 @@ function readConfigNode(): Config { return config; } +function readConfigBrowser(): Config { + const configStr = localStorage.getItem("moonlight-config"); + if (!configStr) { + writeConfig(defaultConfig); + return defaultConfig; + } + + let config: Config = JSON.parse(configStr); + config = { ...defaultConfig, ...config }; + writeConfig(config); + + return config; +} + export function readConfig(): Config { webPreload: { return moonlightNode.config; @@ -49,5 +73,9 @@ export function readConfig(): Config { return readConfigNode(); } + browser: { + return readConfigBrowser(); + } + throw new Error("Called readConfig() in an impossible environment"); } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index f9d1be5..0c30ece 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -124,7 +124,50 @@ function getExtensionsNative(): DetectedExtension[] { return res; } -export function getExtensions(): DetectedExtension[] { +async function getExtensionsBrowser(): Promise { + const res: DetectedExtension[] = []; + + const coreExtensionsFs: Record = JSON.parse( + // @ts-expect-error shut up + _moonlight_coreExtensionsStr + ); + const coreExtensions = Array.from( + new Set(Object.keys(coreExtensionsFs).map((x) => x.split("/")[0])) + ); + + for (const ext of coreExtensions) { + if (!coreExtensionsFs[`${ext}/index.js`]) continue; + const manifest = JSON.parse(coreExtensionsFs[`${ext}/manifest.json`]); + const web = coreExtensionsFs[`${ext}/index.js`]; + + const wpModules: Record = {}; + const wpModulesPath = `${ext}/webpackModules`; + for (const wpModuleFile of Object.keys(coreExtensionsFs)) { + if (wpModuleFile.startsWith(wpModulesPath)) { + wpModules[ + wpModuleFile.replace(wpModulesPath + "/", "").replace(".js", "") + ] = coreExtensionsFs[wpModuleFile]; + } + } + + res.push({ + id: manifest.id, + manifest, + source: { + type: ExtensionLoadSource.Core + }, + scripts: { + web, + webpackModules: wpModules + } + }); + } + + // TODO: normal extensions + return res; +} + +export async function getExtensions(): Promise { webPreload: { return moonlightNode.extensions; } @@ -137,5 +180,9 @@ export function getExtensions(): DetectedExtension[] { return getExtensionsNative(); } + browser: { + return getExtensionsBrowser(); + } + throw new Error("Called getExtensions() outside of node-preload/web-preload"); } diff --git a/packages/core/src/extension/loader.ts b/packages/core/src/extension/loader.ts index b7d696a..1cdcc4e 100644 --- a/packages/core/src/extension/loader.ts +++ b/packages/core/src/extension/loader.ts @@ -13,70 +13,78 @@ import { registerStyles } from "../styles"; const logger = new Logger("core/extension/loader"); -async function loadExt(ext: DetectedExtension) { - webPreload: { - if (ext.scripts.web != null) { - const source = - ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; - const fn = new Function("require", "module", "exports", source); - - const module = { id: ext.id, exports: {} }; - fn.apply(window, [ - () => { - logger.warn("Attempted to require() from web"); - }, - module, - module.exports - ]); - - const exports: ExtensionWebExports = module.exports; - if (exports.patches != null) { - let idx = 0; - for (const patch of exports.patches) { - if (Array.isArray(patch.replace)) { - for (const replacement of patch.replace) { - const newPatch = Object.assign({}, patch, { - replace: replacement - }); - - registerPatch({ ...newPatch, ext: ext.id, id: idx }); - idx++; - } - } else { - registerPatch({ ...patch, ext: ext.id, id: idx }); +function loadExtWeb(ext: DetectedExtension) { + if (ext.scripts.web != null) { + const source = + ext.scripts.web + "\n//# sourceURL=file:///" + ext.scripts.webPath; + const fn = new Function("require", "module", "exports", source); + + const module = { id: ext.id, exports: {} }; + fn.apply(window, [ + () => { + logger.warn("Attempted to require() from web"); + }, + module, + module.exports + ]); + + const exports: ExtensionWebExports = module.exports; + if (exports.patches != null) { + let idx = 0; + for (const patch of exports.patches) { + if (Array.isArray(patch.replace)) { + for (const replacement of patch.replace) { + const newPatch = Object.assign({}, patch, { + replace: replacement + }); + + registerPatch({ ...newPatch, ext: ext.id, id: idx }); idx++; } + } else { + registerPatch({ ...patch, ext: ext.id, id: idx }); + idx++; } } + } - if (exports.webpackModules != null) { - for (const [name, wp] of Object.entries(exports.webpackModules)) { - if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { - const func = new Function( - "module", - "exports", - "require", - ext.scripts.webpackModules[name]! - ) as WebpackModuleFunc; - registerWebpackModule({ - ...wp, - ext: ext.id, - id: name, - run: func - }); - } else { - registerWebpackModule({ ...wp, ext: ext.id, id: name }); - } + if (exports.webpackModules != null) { + for (const [name, wp] of Object.entries(exports.webpackModules)) { + if (wp.run == null && ext.scripts.webpackModules?.[name] != null) { + const func = new Function( + "module", + "exports", + "require", + ext.scripts.webpackModules[name]! + ) as WebpackModuleFunc; + registerWebpackModule({ + ...wp, + ext: ext.id, + id: name, + run: func + }); + } else { + registerWebpackModule({ ...wp, ext: ext.id, id: name }); } } + } - if (exports.styles != null) { - registerStyles( - exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) - ); - } + if (exports.styles != null) { + registerStyles( + exports.styles.map((style, i) => `/* ${ext.id}#${i} */ ${style}`) + ); } } +} + +async function loadExt(ext: DetectedExtension) { + webPreload: { + loadExtWeb(ext); + } + + browser: { + loadExtWeb(ext); + } nodePreload: { if (ext.scripts.nodePath != null) { @@ -211,6 +219,12 @@ export async function loadProcessedExtensions({ } } + browser: { + for (const ext of extensions) { + moonlight.enabledExtensions.add(ext.id); + } + } + logger.debug("Loading all extensions"); await Promise.all(extensions.map(loadExtWithDependencies)); logger.info(`Loaded ${extensions.length} extensions`); diff --git a/packages/core/src/util/event.ts b/packages/core/src/util/event.ts index dd606e1..7eda9a9 100644 --- a/packages/core/src/util/event.ts +++ b/packages/core/src/util/event.ts @@ -6,6 +6,36 @@ export interface MoonlightEventEmitter { removeEventListener: (id: string, cb: MoonlightEventCallback) => void; } +function webMethod(): MoonlightEventEmitter { + const eventEmitter = new EventTarget(); + const listeners = new Map void>(); + + return { + dispatchEvent: (id: string, data: string) => { + eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data })); + }, + + addEventListener: (id: string, cb: (data: string) => void) => { + if (listeners.has(cb)) return; + + function listener(e: Event) { + const event = e as CustomEvent; + cb(event.detail); + } + + listeners.set(cb, listener); + eventEmitter.addEventListener(id, listener); + }, + + removeEventListener: (id: string, cb: (data: string) => void) => { + const listener = listeners.get(cb); + if (listener == null) return; + listeners.delete(cb); + eventEmitter.removeEventListener(id, listener); + } + }; +} + function nodeMethod(): MoonlightEventEmitter { const EventEmitter = require("events"); const eventEmitter = new EventEmitter(); @@ -38,33 +68,7 @@ function nodeMethod(): MoonlightEventEmitter { export function createEventEmitter(): MoonlightEventEmitter { webPreload: { - const eventEmitter = new EventTarget(); - const listeners = new Map void>(); - - return { - dispatchEvent: (id: string, data: string) => { - eventEmitter.dispatchEvent(new CustomEvent(id, { detail: data })); - }, - - addEventListener: (id: string, cb: (data: string) => void) => { - if (listeners.has(cb)) return; - - function listener(e: Event) { - const event = e as CustomEvent; - cb(event.detail); - } - - listeners.set(cb, listener); - eventEmitter.addEventListener(id, listener); - }, - - removeEventListener: (id: string, cb: (data: string) => void) => { - const listener = listeners.get(cb); - if (listener == null) return; - listeners.delete(cb); - eventEmitter.removeEventListener(id, listener); - } - }; + return webMethod(); } nodePreload: { @@ -75,5 +79,9 @@ export function createEventEmitter(): MoonlightEventEmitter { return nodeMethod(); } + browser: { + return webMethod(); + } + throw new Error("Called createEventEmitter() in an impossible environment"); } diff --git a/packages/core/src/util/logger.ts b/packages/core/src/util/logger.ts index 96488a1..55f5a5b 100644 --- a/packages/core/src/util/logger.ts +++ b/packages/core/src/util/logger.ts @@ -57,7 +57,7 @@ export default class Logger { const logLevel = LogLevel[level].toUpperCase(); if (maxLevel > level) return; - if (MOONLIGHT_WEB_PRELOAD) { + if (MOONLIGHT_WEB_PRELOAD || MOONLIGHT_BROWSER) { args = [ `%c[${logLevel}]`, `background-color: ${colors[level]}; color: #FFFFFF;`, diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index a235b45..ceb5b2e 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -161,7 +161,7 @@ export async function inject(asarPath: string) { isMoonlightDesktop = asarPath === "moonlightDesktop"; try { const config = readConfig(); - const extensions = getExtensions(); + const extensions = await getExtensions(); // Duplicated in node-preload... oops // eslint-disable-next-line no-inner-declarations diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index 179fef2..ea914f7 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -14,8 +14,8 @@ import { async function injectGlobals() { const config = readConfig(); - const extensions = getExtensions(); - const processed = await loadExtensions(extensions); + const extensions = await getExtensions(); + const processedExtensions = await loadExtensions(extensions); function getConfig(ext: string) { const val = config.extensions[ext]; @@ -25,8 +25,8 @@ async function injectGlobals() { global.moonlightNode = { config, - extensions: getExtensions(), - processedExtensions: processed, + extensions, + processedExtensions, nativesCache: {}, getConfig, getConfigOption: (ext: string, name: string) => { @@ -48,7 +48,7 @@ async function injectGlobals() { writeConfig }; - await loadProcessedExtensions(processed); + await loadProcessedExtensions(processedExtensions); contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); const extCors = moonlightNode.processedExtensions.extensions diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 2aef490..03e2688 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -25,6 +25,7 @@ declare global { const MOONLIGHT_INJECTOR: boolean; const MOONLIGHT_NODE_PRELOAD: boolean; const MOONLIGHT_WEB_PRELOAD: boolean; + const MOONLIGHT_BROWSER: boolean; var moonlightHost: MoonlightHost; var moonlightNode: MoonlightNode; diff --git a/packages/web-preload/package.json b/packages/web-preload/package.json index 419ef88..be672b2 100644 --- a/packages/web-preload/package.json +++ b/packages/web-preload/package.json @@ -1,6 +1,7 @@ { "name": "@moonlight-mod/web-preload", "private": true, + "main": "src/index.ts", "dependencies": { "@moonlight-mod/core": "workspace:*" } diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index eaebeaa..a039b9f 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -3,7 +3,7 @@ import { installWebpackPatcher } from "@moonlight-mod/core/patch"; import { installStyles } from "@moonlight-mod/core/styles"; import Logger from "@moonlight-mod/core/util/logger"; -(async () => { +export default async function load() { const logger = new Logger("web-preload"); window.moonlight = { @@ -29,4 +29,6 @@ import Logger from "@moonlight-mod/core/util/logger"; window.addEventListener("DOMContentLoaded", () => { installStyles(); }); -})(); +} + +if (MOONLIGHT_ENV === "web-preload") load(); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index c853f61..c71a023 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -42,6 +42,15 @@ importers: specifier: ^5.3.2 version: 5.3.2 + packages/browser: + dependencies: + '@moonlight-mod/core': + specifier: workspace:* + version: link:../core + '@moonlight-mod/web-preload': + specifier: workspace:* + version: link:../web-preload + packages/core: dependencies: '@moonlight-mod/types': From 2dae229e73f7ba2b5e3775d0dda6d76bda9fe7d1 Mon Sep 17 00:00:00 2001 From: xyzeva Date: Fri, 4 Oct 2024 17:11:07 +0300 Subject: [PATCH 02/73] feat(moonbase): add implicitly enabled indicator closes #60 --- .../webpackModules/ui/extensions/card.tsx | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index 972463e..0354f93 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -49,11 +49,26 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { // Why it work like that :sob: if (ext == null) return <>; - const { Card, Text, Switch, TabBar, Button } = Components; + const { Card, Text, FormSwitch, TabBar, Button } = Components; const tagline = ext.manifest?.meta?.tagline; const settings = ext.manifest?.settings; const description = ext.manifest?.meta?.description; + const enabledDependants = useStateFromStores([MoonbaseSettingsStore], () => + Object.keys(MoonbaseSettingsStore.extensions) + .filter((uniqueId) => { + const potentialDependant = MoonbaseSettingsStore.getExtension( + parseInt(uniqueId) + ); + + return ( + potentialDependant.manifest.dependencies?.includes(ext.id) && + MoonbaseSettingsStore.getExtensionEnabled(parseInt(uniqueId)) + ); + }) + .map((a) => MoonbaseSettingsStore.getExtension(parseInt(a))) + ); + const implicitlyEnabled = enabledDependants.length > 0; return ( @@ -127,8 +142,18 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { /> )} - 1 ? "s" : "" + }: ${enabledDependants + .map((a) => a.manifest.meta!.name) + .join(", ")}` + : undefined + } onChange={() => { setRestartNeeded(true); MoonbaseSettingsStore.setExtensionEnabled(uniqueId, !enabled); From b021404649f6acbf0915ed054055249c8498c57e Mon Sep 17 00:00:00 2001 From: xyzeva Date: Fri, 4 Oct 2024 17:17:47 +0300 Subject: [PATCH 03/73] fix(moonbase): loose divider on switch --- .../src/moonbase/webpackModules/ui/extensions/card.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index 0354f93..a2ad731 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -145,6 +145,8 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { Date: Fri, 4 Oct 2024 13:52:20 -0400 Subject: [PATCH 04/73] Rest of the fucking owl --- build.mjs | 34 ++- packages/browser/manifest.json | 2 +- packages/browser/package.json | 5 +- packages/browser/src/index.ts | 140 ++++++++-- packages/core-extensions/package.json | 4 +- packages/core-extensions/src/moonbase/node.ts | 15 +- .../src/moonbase/webpackModules/stores.ts | 25 +- packages/core/src/asar.ts | 57 ++++ packages/core/src/config.ts | 91 +++---- packages/core/src/extension.ts | 250 ++++++++++++------ packages/core/src/extension/loader.ts | 16 +- packages/core/src/util/binary.ts | 66 +++++ packages/core/src/util/event.ts | 184 ++++++------- packages/core/src/util/logger.ts | 20 +- packages/injector/src/index.ts | 5 +- packages/node-preload/src/index.ts | 5 +- packages/types/src/globals.ts | 17 ++ packages/types/src/index.ts | 2 + packages/web-preload/src/index.ts | 3 +- pnpm-lock.yaml | 177 +++++++++++-- 20 files changed, 779 insertions(+), 339 deletions(-) create mode 100644 packages/core/src/asar.ts create mode 100644 packages/core/src/util/binary.ts diff --git a/build.mjs b/build.mjs index 40fc76b..3144738 100644 --- a/build.mjs +++ b/build.mjs @@ -20,8 +20,6 @@ const external = [ "fs", "path", "module", - "events", - "original-fs", // wtf asar? "discord", // mappings // Silence an esbuild warning @@ -79,10 +77,20 @@ async function build(name, entry) { if (name === "browser") outfile = path.join("./dist", "browser", "index.js"); const dropLabels = []; - if (name !== "injector") dropLabels.push("injector"); - if (name !== "node-preload") dropLabels.push("nodePreload"); - if (name !== "web-preload") dropLabels.push("webPreload"); - if (name !== "browser") dropLabels.push("browser"); + const labels = { + injector: ["injector"], + nodePreload: ["node-preload"], + webPreload: ["web-preload"], + browser: ["browser"], + + webTarget: ["web-preload", "browser"], + nodeTarget: ["node-preload", "injector"] + }; + for (const [label, targets] of Object.entries(labels)) { + if (!targets.includes(name)) { + dropLabels.push(label); + } + } const define = { MOONLIGHT_ENV: `"${name}"`, @@ -116,6 +124,20 @@ async function build(name, entry) { dest: "./dist/browser/modifyResponseHeaders.json" }) ); + + // This sucks lmfao + plugins.push({ + name: "browserPath", + setup(build) { + build.onResolve({ filter: /^path$/ }, () => { + const index = + "./packages/browser/node_modules/path-browserify/index.js"; + return { + path: path.resolve(index) + }; + }); + } + }); } /** @type {import("esbuild").BuildOptions} */ diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index 8600cab..1f51179 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -2,7 +2,7 @@ "manifest_version": 3, "name": "moonlight", "description": "Yet another Discord mod", - "version": "1.0.8", + "version": "1.1.0", "permissions": ["declarativeNetRequestWithHostAccess"], "content_scripts": [ { diff --git a/packages/browser/package.json b/packages/browser/package.json index 3fa3e4b..fe8a4f8 100644 --- a/packages/browser/package.json +++ b/packages/browser/package.json @@ -3,6 +3,9 @@ "private": true, "dependencies": { "@moonlight-mod/core": "workspace:*", - "@moonlight-mod/web-preload": "workspace:*" + "@moonlight-mod/types": "workspace:*", + "@moonlight-mod/web-preload": "workspace:*", + "@zenfs/core": "^1.0.2", + "@zenfs/dom": "^0.2.16" } } diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 0ff4635..9bf9f1c 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -1,13 +1,96 @@ import "@moonlight-mod/web-preload"; import { readConfig, writeConfig } from "@moonlight-mod/core/config"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { getExtensions } from "@moonlight-mod/core/extension"; import { loadExtensions } from "@moonlight-mod/core/extension/loader"; +import { MoonlightBrowserFS, MoonlightNode } from "@moonlight-mod/types"; + +import { IndexedDB } from "@zenfs/dom"; +import { configure } from "@zenfs/core"; +import * as fs from "@zenfs/core/promises"; // Mostly copy pasted from node-preload, FIXME // TODO: is this safe an in IIFE? (async () => { - const config = readConfig(); + // Set up a virtual filesystem with IndexedDB + await configure({ + mounts: { + "/": { + backend: IndexedDB, + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore tsc tweaking + storeName: "moonlight-fs" + } + } + }); + + const browserFS: MoonlightBrowserFS = { + async readFile(path) { + return new Uint8Array(await fs.readFile(path)); + }, + async writeFile(path, data) { + await fs.writeFile(path, data); + }, + async unlink(path) { + await fs.unlink(path); + }, + + async readdir(path) { + return await fs.readdir(path); + }, + async mkdir(path) { + const parts = this.parts(path); + for (let i = 0; i < parts.length; i++) { + const path = this.join(...parts.slice(0, i + 1)); + if (!(await this.exists(path))) await fs.mkdir(path); + } + }, + + async rmdir(path) { + const entries = await this.readdir(path); + + for (const entry of entries) { + const fullPath = this.join(path, entry); + const isFile = await this.isFile(fullPath); + if (isFile) { + await this.unlink(fullPath); + } else { + await this.rmdir(fullPath); + } + } + + await fs.rmdir(path); + }, + + async exists(path) { + return await fs.exists(path); + }, + async isFile(path) { + return (await fs.stat(path)).isFile(); + }, + + join(...parts) { + let str = parts.join("/"); + if (!str.startsWith("/")) str = "/" + str; + return str; + }, + dirname(path) { + const parts = this.parts(path); + return "/" + parts.slice(0, parts.length - 1).join("/"); + }, + parts(path) { + if (path.startsWith("/")) path = path.substring(1); + return path.split("/"); + } + }; + Object.assign(window, { + _moonlightBrowserFS: browserFS + }); + + // Actual loading begins here + const config = await readConfig(); + initLogger(config); + const extensions = await getExtensions(); const processedExtensions = await loadExtensions(extensions); @@ -17,33 +100,36 @@ import { loadExtensions } from "@moonlight-mod/core/extension/loader"; return val.config; } + const moonlightNode: MoonlightNode = { + config, + extensions, + processedExtensions, + nativesCache: {}, + + getConfig, + getConfigOption: (ext: string, name: string) => { + const config = getConfig(ext); + if (config == null) return undefined; + const option = config[name]; + if (option == null) return undefined; + return option as T; + }, + getNatives: () => {}, + getLogger: (id: string) => { + return new Logger(id); + }, + + getExtensionDir: (ext: string) => { + return `/extensions/${ext}`; + }, + + writeConfig + }; + Object.assign(window, { - moonlightNode: { - config, - extensions, - processedExtensions, - nativesCache: {}, - - getConfig, - getConfigOption: (ext: string, name: string) => { - const config = getConfig(ext); - if (config == null) return undefined; - const option = config[name]; - if (option == null) return undefined; - return option as T; - }, - getNatives: () => {}, - getLogger: (id: string) => { - return new Logger(id); - }, - - getExtensionDir: (ext: string) => { - return `/extensions/${ext}`; - }, - - writeConfig - } + moonlightNode }); + // This is set by web-preload for us await window._moonlightLoad(); })(); diff --git a/packages/core-extensions/package.json b/packages/core-extensions/package.json index 11d57f6..d90313f 100644 --- a/packages/core-extensions/package.json +++ b/packages/core-extensions/package.json @@ -2,7 +2,7 @@ "name": "@moonlight-mod/core-extensions", "private": true, "dependencies": { - "@electron/asar": "^3.2.5", - "@moonlight-mod/types": "workspace:*" + "@moonlight-mod/types": "workspace:*", + "@moonlight-mod/core": "workspace:*" } } diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index ce44165..69c063c 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -1,9 +1,7 @@ import { MoonbaseNatives, RepositoryManifest } from "./types"; -import asar from "@electron/asar"; import fs from "fs"; import path from "path"; -import os from "os"; -import { repoUrlFile } from "types/src/constants"; +import extractAsar from "@moonlight-mod/core/asar"; const logger = moonlightNode.getLogger("moonbase"); @@ -36,13 +34,12 @@ async function installExtension( fs.mkdirSync(dir, { recursive: true }); // for some reason i just can't .writeFileSync() a file that ends in .asar??? - const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "moonlight-")); - const tempFile = path.join(tempDir, "extension"); const buffer = await req.arrayBuffer(); - fs.writeFileSync(tempFile, Buffer.from(buffer)); - - asar.extractAll(tempFile, dir); - fs.writeFileSync(path.join(dir, repoUrlFile), repo); + const files = extractAsar(buffer); + for (const [file, buf] of Object.entries(files)) { + const nodeBuf = Buffer.from(buf); + fs.writeFileSync(path.join(dir, file), nodeBuf); + } } async function deleteExtension(id: string) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 46ddcc3..5dbc653 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -7,11 +7,13 @@ import { } from "../types"; import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import extractAsar from "@moonlight-mod/core/asar"; const logger = moonlight.getLogger("moonbase"); -let natives: MoonbaseNatives | undefined = moonlight.getNatives("moonbase"); -if (!natives) { +let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); +if (window._moonlightBrowserFS != null) { + const browserFS = window._moonlightBrowserFS!; natives = { fetchRepositories: async (repos) => { const ret: Record = {}; @@ -29,10 +31,25 @@ if (!natives) { return ret; }, installExtension: async (manifest, url, repo) => { - // TODO + const req = await fetch(url); + const buffer = await req.arrayBuffer(); + + if (await browserFS.exists("/extensions/" + manifest.id)) { + await browserFS.rmdir("/extensions/" + manifest.id); + } + + const files = extractAsar(buffer); + for (const [file, data] of Object.entries(files)) { + const path = + "/extensions/" + + manifest.id + + (file.startsWith("/") ? file : `/${file}`); + await browserFS.mkdir(browserFS.dirname(path)); + await browserFS.writeFile(path, data); + } }, deleteExtension: async (id) => { - // TODO + browserFS.rmdir("/extensions/" + id); }, getExtensionConfig: (id, key) => { const config = moonlightNode.config.extensions[id]; diff --git a/packages/core/src/asar.ts b/packages/core/src/asar.ts new file mode 100644 index 0000000..1f2f166 --- /dev/null +++ b/packages/core/src/asar.ts @@ -0,0 +1,57 @@ +// https://github.com/electron/asar +// http://formats.kaitai.io/python_pickle/ +import { BinaryReader } from "./util/binary"; + +/* + The asar format is kinda bad, especially because it uses multiple pickle + entries. It spams sizes, expecting us to read small buffers and parse those, + but we can just take it all through at once without having to create multiple + BinaryReaders. This implementation might be wrong, though. + + This either has size/offset or files but I can't get the type to cooperate, + so pretend this is a union. +*/ + +type AsarEntry = { + size: number; + offset: `${number}`; // who designed this + + files?: Record; +}; + +export default function extractAsar(file: ArrayBuffer) { + const array = new Uint8Array(file); + const br = new BinaryReader(array); + + // two uints, one containing the number '4', to signify that the other uint takes up 4 bytes + // bravo, electron, bravo + const _payloadSize = br.readUInt32(); + const _headerSize = br.readInt32(); + + const headerStringStart = br.position; + const headerStringSize = br.readUInt32(); // How big the block is + const actualStringSize = br.readUInt32(); // How big the string in that block is + + const base = headerStringStart + headerStringSize + 4; + + const string = br.readString(actualStringSize); + const header: AsarEntry = JSON.parse(string); + + const ret: Record = {}; + function addDirectory(dir: AsarEntry, path: string) { + for (const [name, data] of Object.entries(dir.files!)) { + const fullName = path + "/" + name; + if (data.files != null) { + addDirectory(data, fullName); + } else { + br.position = base + parseInt(data.offset); + const file = br.read(data.size); + ret[fullName] = file; + } + } + } + + addDirectory(header, ""); + + return ret; +} diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index 94207de..db941e8 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -12,79 +12,60 @@ const defaultConfig: Config = { repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"] }; -function writeConfigNode(config: Config) { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - return; -} - export function writeConfig(config: Config) { - nodePreload: { - writeConfigNode(config); - return; - } - - injector: { - writeConfigNode(config); + browser: { + const enc = new TextEncoder().encode(JSON.stringify(config, null, 2)); + window._moonlightBrowserFS!.writeFile("/config.json", enc); return; } - browser: { - localStorage.setItem("moonlight-config", JSON.stringify(config)); + nodeTarget: { + const fs = requireImport("fs"); + const configPath = getConfigPath(); + fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); return; } throw new Error("Called writeConfig() in an impossible environment"); } -function readConfigNode(): Config { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - - if (!fs.existsSync(configPath)) { - writeConfig(defaultConfig); - return defaultConfig; +export async function readConfig(): Promise { + webPreload: { + return moonlightNode.config; } - let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); - - // Assign the default values if they don't exist (newly added) - config = { ...defaultConfig, ...config }; - writeConfig(config); - - return config; -} - -function readConfigBrowser(): Config { - const configStr = localStorage.getItem("moonlight-config"); - if (!configStr) { - writeConfig(defaultConfig); - return defaultConfig; + browser: { + if (await window._moonlightBrowserFS!.exists("/config.json")) { + const file = await window._moonlightBrowserFS!.readFile("/config.json"); + const configStr = new TextDecoder().decode(file); + let config: Config = JSON.parse(configStr); + + config = { ...defaultConfig, ...config }; + writeConfig(config); + + return config; + } else { + writeConfig(defaultConfig); + return defaultConfig; + } } - let config: Config = JSON.parse(configStr); - config = { ...defaultConfig, ...config }; - writeConfig(config); + nodeTarget: { + const fs = requireImport("fs"); + const configPath = getConfigPath(); - return config; -} + if (!fs.existsSync(configPath)) { + writeConfig(defaultConfig); + return defaultConfig; + } -export function readConfig(): Config { - webPreload: { - return moonlightNode.config; - } + let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); - nodePreload: { - return readConfigNode(); - } + // Assign the default values if they don't exist (newly added) + config = { ...defaultConfig, ...config }; + writeConfig(config); - injector: { - return readConfigNode(); - } - - browser: { - return readConfigBrowser(); + return config; } throw new Error("Called readConfig() in an impossible environment"); diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index eb73890..c072ca6 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -7,20 +7,87 @@ import { import { readConfig } from "./config"; import requireImport from "./util/import"; import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; +import Logger from "./util/logger"; + +const logger = new Logger("core/extension"); + +// This is kinda duplicated from the browser FS type but idc +interface MoonlightFSWrapper { + readdir(path: string): Promise; + exists(path: string): Promise; + isFile(path: string): Promise; + readFile(path: string): Promise; + join(...parts: string[]): string; + dirname(path: string): string; +} + +function getFS(): MoonlightFSWrapper { + browser: { + const fs = window._moonlightBrowserFS!; + return { + async readdir(path) { + return await fs.readdir(path); + }, + async exists(path) { + return await fs.exists(path); + }, + async isFile(path) { + return await fs.isFile(path); + }, + async readFile(path) { + const buf = await fs.readFile(path); + const text = new TextDecoder().decode(buf); + return text; + }, + join(...parts) { + return fs.join(...parts); + }, + dirname(path) { + return fs.dirname(path); + } + }; + } -function findManifests(dir: string): string[] { const fs = requireImport("fs"); const path = requireImport("path"); + + return { + async readdir(path) { + return fs.readdirSync(path); + }, + async exists(path) { + return fs.existsSync(path); + }, + async isFile(path) { + return fs.statSync(path).isFile(); + }, + async readFile(path) { + return fs.readFileSync(path, "utf8"); + }, + join(...parts) { + return path.join(...parts); + }, + dirname(dir) { + return path.dirname(dir); + } + }; +} + +async function findManifests( + fs: MoonlightFSWrapper, + dir: string +): Promise { const ret = []; - if (fs.existsSync(dir)) { - for (const file of fs.readdirSync(dir)) { + if (await fs.exists(dir)) { + for (const file of await fs.readdir(dir)) { + const path = fs.join(dir, file); if (file === "manifest.json") { - ret.push(path.join(dir, file)); + ret.push(path); } - if (fs.statSync(path.join(dir, file)).isDirectory()) { - ret.push(...findManifests(path.join(dir, file))); + if (!(await fs.isFile(path))) { + ret.push(...(await findManifests(fs, path))); } } } @@ -28,100 +95,111 @@ function findManifests(dir: string): string[] { return ret; } -function loadDetectedExtensions( +async function loadDetectedExtensions( + fs: MoonlightFSWrapper, dir: string, type: ExtensionLoadSource -): DetectedExtension[] { - const fs = requireImport("fs"); - const path = requireImport("path"); +): Promise { const ret: DetectedExtension[] = []; - const manifests = findManifests(dir); + const manifests = await findManifests(fs, dir); for (const manifestPath of manifests) { - if (!fs.existsSync(manifestPath)) continue; - const dir = path.dirname(manifestPath); + try { + if (!(await fs.exists(manifestPath))) continue; + const dir = fs.dirname(manifestPath); - const manifest: ExtensionManifest = JSON.parse( - fs.readFileSync(manifestPath, "utf8") - ); - const level = manifest.apiLevel ?? 1; - if (level !== constants.apiLevel) { - continue; - } + const manifest: ExtensionManifest = JSON.parse( + await fs.readFile(manifestPath) + ); + const level = manifest.apiLevel ?? 1; + if (level !== constants.apiLevel) { + continue; + } - const webPath = path.join(dir, "index.js"); - const nodePath = path.join(dir, "node.js"); - const hostPath = path.join(dir, "host.js"); - - // if none exist (empty manifest) don't give a shit - if ( - !fs.existsSync(webPath) && - !fs.existsSync(nodePath) && - !fs.existsSync(hostPath) - ) { - continue; - } + const webPath = fs.join(dir, "index.js"); + const nodePath = fs.join(dir, "node.js"); + const hostPath = fs.join(dir, "host.js"); - const web = fs.existsSync(webPath) - ? fs.readFileSync(webPath, "utf8") - : undefined; + // if none exist (empty manifest) don't give a shit + if (!fs.exists(webPath) && !fs.exists(nodePath) && !fs.exists(hostPath)) { + continue; + } - let url: string | undefined = undefined; - const urlPath = path.join(dir, constants.repoUrlFile); - if (type === ExtensionLoadSource.Normal && fs.existsSync(urlPath)) { - url = fs.readFileSync(urlPath, "utf8"); - } + const web = (await fs.exists(webPath)) + ? await fs.readFile(webPath) + : undefined; - const wpModules: Record = {}; - const wpModulesPath = path.join(dir, "webpackModules"); - if (fs.existsSync(wpModulesPath)) { - const wpModulesFile = fs.readdirSync(wpModulesPath); - - for (const wpModuleFile of wpModulesFile) { - if (wpModuleFile.endsWith(".js")) { - wpModules[wpModuleFile.replace(".js", "")] = fs.readFileSync( - path.join(wpModulesPath, wpModuleFile), - "utf8" - ); - } + let url: string | undefined = undefined; + const urlPath = fs.join(dir, constants.repoUrlFile); + if (type === ExtensionLoadSource.Normal && (await fs.exists(urlPath))) { + url = await fs.readFile(urlPath); } - } - ret.push({ - id: manifest.id, - manifest, - source: { - type, - url - }, - scripts: { - web, - webPath: web != null ? webPath : undefined, - webpackModules: wpModules, - nodePath: fs.existsSync(nodePath) ? nodePath : undefined, - hostPath: fs.existsSync(hostPath) ? hostPath : undefined + const wpModules: Record = {}; + const wpModulesPath = fs.join(dir, "webpackModules"); + if (await fs.exists(wpModulesPath)) { + const wpModulesFile = await fs.readdir(wpModulesPath); + + for (const wpModuleFile of wpModulesFile) { + if (wpModuleFile.endsWith(".js")) { + wpModules[wpModuleFile.replace(".js", "")] = await fs.readFile( + fs.join(wpModulesPath, wpModuleFile) + ); + } + } } - }); + + ret.push({ + id: manifest.id, + manifest, + source: { + type, + url + }, + scripts: { + web, + webPath: web != null ? webPath : undefined, + webpackModules: wpModules, + nodePath: (await fs.exists(nodePath)) ? nodePath : undefined, + hostPath: (await fs.exists(hostPath)) ? hostPath : undefined + } + }); + } catch (e) { + logger.error(e, "Failed to load extension"); + } } return ret; } -function getExtensionsNative(): DetectedExtension[] { - const config = readConfig(); +async function getExtensionsNative(): Promise { + const config = await readConfig(); const res = []; + const fs = getFS(); res.push( - ...loadDetectedExtensions(getCoreExtensionsPath(), ExtensionLoadSource.Core) + ...(await loadDetectedExtensions( + fs, + getCoreExtensionsPath(), + ExtensionLoadSource.Core + )) ); res.push( - ...loadDetectedExtensions(getExtensionsPath(), ExtensionLoadSource.Normal) + ...(await loadDetectedExtensions( + fs, + getExtensionsPath(), + ExtensionLoadSource.Normal + )) ); for (const devSearchPath of config.devSearchPaths ?? []) { res.push( - ...loadDetectedExtensions(devSearchPath, ExtensionLoadSource.Developer) + ...(await loadDetectedExtensions( + fs, + devSearchPath, + ExtensionLoadSource.Developer + )) ); } @@ -129,7 +207,7 @@ function getExtensionsNative(): DetectedExtension[] { } async function getExtensionsBrowser(): Promise { - const res: DetectedExtension[] = []; + const ret: DetectedExtension[] = []; const coreExtensionsFs: Record = JSON.parse( // @ts-expect-error shut up @@ -154,7 +232,7 @@ async function getExtensionsBrowser(): Promise { } } - res.push({ + ret.push({ id: manifest.id, manifest, source: { @@ -167,8 +245,16 @@ async function getExtensionsBrowser(): Promise { }); } - // TODO: normal extensions - return res; + const fs = getFS(); + ret.push( + ...(await loadDetectedExtensions( + fs, + "/extensions", + ExtensionLoadSource.Normal + )) + ); + + return ret; } export async function getExtensions(): Promise { @@ -176,16 +262,12 @@ export async function getExtensions(): Promise { return moonlightNode.extensions; } - nodePreload: { - return getExtensionsNative(); - } - - injector: { - return getExtensionsNative(); + browser: { + return await getExtensionsBrowser(); } - browser: { - return getExtensionsBrowser(); + nodeTarget: { + return await getExtensionsNative(); } throw new Error("Called getExtensions() outside of node-preload/web-preload"); diff --git a/packages/core/src/extension/loader.ts b/packages/core/src/extension/loader.ts index cd65472..db1c414 100644 --- a/packages/core/src/extension/loader.ts +++ b/packages/core/src/extension/loader.ts @@ -78,11 +78,7 @@ function loadExtWeb(ext: DetectedExtension) { } async function loadExt(ext: DetectedExtension) { - webPreload: { - loadExtWeb(ext); - } - - browser: { + webTarget: { loadExtWeb(ext); } @@ -125,7 +121,7 @@ async function loadExt(ext: DetectedExtension) { export async function loadExtensions( exts: DetectedExtension[] ): Promise { - const config = readConfig(); + const config = await readConfig(); const items = exts .map((ext) => { return { @@ -213,13 +209,7 @@ export async function loadProcessedExtensions({ logger.debug(`Loaded "${ext.id}"`); } - webPreload: { - for (const ext of extensions) { - moonlight.enabledExtensions.add(ext.id); - } - } - - browser: { + webTarget: { for (const ext of extensions) { moonlight.enabledExtensions.add(ext.id); } diff --git a/packages/core/src/util/binary.ts b/packages/core/src/util/binary.ts new file mode 100644 index 0000000..421f8c9 --- /dev/null +++ b/packages/core/src/util/binary.ts @@ -0,0 +1,66 @@ +// https://github.com/NotNite/brc-save-editor/blob/main/src/lib/binary.ts +export interface BinaryInterface { + data: Uint8Array; + view: DataView; + length: number; + position: number; +} + +export class BinaryReader implements BinaryInterface { + data: Uint8Array; + view: DataView; + length: number; + position: number; + + constructor(data: Uint8Array) { + this.data = data; + this.view = new DataView(data.buffer); + + this.length = data.length; + this.position = 0; + } + + readByte() { + return this._read(this.view.getInt8, 1); + } + + readBoolean() { + return this.readByte() !== 0; + } + + readInt32() { + return this._read(this.view.getInt32, 4); + } + + readUInt32() { + return this._read(this.view.getUint32, 4); + } + + readSingle() { + return this._read(this.view.getFloat32, 4); + } + + readInt64() { + return this._read(this.view.getBigInt64, 8); + } + + readString(length: number) { + const result = this.read(length); + return new TextDecoder().decode(result); + } + + read(length: number) { + const data = this.data.subarray(this.position, this.position + length); + this.position += length; + return data; + } + + private _read( + func: (position: number, littleEndian?: boolean) => T, + length: number + ): T { + const result = func.call(this.view, this.position, true); + this.position += length; + return result; + } +} diff --git a/packages/core/src/util/event.ts b/packages/core/src/util/event.ts index 49bf6dc..8458c6f 100644 --- a/packages/core/src/util/event.ts +++ b/packages/core/src/util/event.ts @@ -1,114 +1,92 @@ import { MoonlightEventEmitter } from "@moonlight-mod/types/core/event"; -function nodeMethod< - EventId extends string = string, - EventData = Record ->(): MoonlightEventEmitter { - const EventEmitter = require("events"); - const eventEmitter = new EventEmitter(); - const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); - - return { - dispatchEvent: ( - id: Id, - data: EventData[Id] - ) => { - eventEmitter.emit(id as string, data); - }, - - addEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - if (listeners.has(untyped)) return; - - function listener(e: Event) { - const event = e as CustomEvent; - cb(event as EventData[Id]); - } - - listeners.set(untyped, listener); - eventEmitter.on(id as string, listener); - }, - - removeEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - const listener = listeners.get(untyped); - if (listener == null) return; - listeners.delete(untyped); - eventEmitter.off(id as string, listener); - } - }; -} - -function webMethod< - EventId extends string = string, - EventData = Record ->(): MoonlightEventEmitter { - const eventEmitter = new EventTarget(); - const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); - - return { - dispatchEvent: ( - id: Id, - data: EventData[Id] - ) => { - eventEmitter.dispatchEvent( - new CustomEvent(id as string, { detail: data }) - ); - }, - - addEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - if (listeners.has(untyped)) return; - - function listener(e: Event) { - const event = e as CustomEvent; - cb(event.detail as EventData[Id]); - } - - listeners.set(untyped, listener); - eventEmitter.addEventListener(id as string, listener); - }, - - removeEventListener: ( - id: Id, - cb: (data: EventData[Id]) => void - ) => { - const untyped = cb as (data: EventData) => void; - const listener = listeners.get(untyped); - if (listener == null) return; - listeners.delete(untyped); - eventEmitter.removeEventListener(id as string, listener); - } - }; -} - export function createEventEmitter< EventId extends string = string, EventData = Record >(): MoonlightEventEmitter { - webPreload: { - return webMethod(); - } - - nodePreload: { - return nodeMethod(); - } - - injector: { - return nodeMethod(); + webTarget: { + const eventEmitter = new EventTarget(); + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); + + return { + dispatchEvent: ( + id: Id, + data: EventData[Id] + ) => { + eventEmitter.dispatchEvent( + new CustomEvent(id as string, { detail: data }) + ); + }, + + addEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + if (listeners.has(untyped)) return; + + function listener(e: Event) { + const event = e as CustomEvent; + cb(event.detail as EventData[Id]); + } + + listeners.set(untyped, listener); + eventEmitter.addEventListener(id as string, listener); + }, + + removeEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + const listener = listeners.get(untyped); + if (listener == null) return; + listeners.delete(untyped); + eventEmitter.removeEventListener(id as string, listener); + } + }; } - browser: { - return webMethod(); + nodeTarget: { + const EventEmitter = require("events"); + const eventEmitter = new EventEmitter(); + const listeners = new Map<(data: EventData) => void, (e: Event) => void>(); + + return { + dispatchEvent: ( + id: Id, + data: EventData[Id] + ) => { + eventEmitter.emit(id as string, data); + }, + + addEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + if (listeners.has(untyped)) return; + + function listener(e: Event) { + const event = e as CustomEvent; + cb(event as EventData[Id]); + } + + listeners.set(untyped, listener); + eventEmitter.on(id as string, listener); + }, + + removeEventListener: ( + id: Id, + cb: (data: EventData[Id]) => void + ) => { + const untyped = cb as (data: EventData) => void; + const listener = listeners.get(untyped); + if (listener == null) return; + listeners.delete(untyped); + eventEmitter.off(id as string, listener); + } + }; } throw new Error("Called createEventEmitter() in an impossible environment"); diff --git a/packages/core/src/util/logger.ts b/packages/core/src/util/logger.ts index 55f5a5b..080a8c3 100644 --- a/packages/core/src/util/logger.ts +++ b/packages/core/src/util/logger.ts @@ -1,6 +1,6 @@ /* eslint-disable no-console */ import { LogLevel } from "@moonlight-mod/types/logger"; -import { readConfig } from "../config"; +import { Config } from "@moonlight-mod/types"; const colors = { [LogLevel.SILLY]: "#EDD3E9", @@ -11,15 +11,7 @@ const colors = { [LogLevel.ERROR]: "#FF0000" }; -const config = readConfig(); let maxLevel = LogLevel.INFO; -if (config.loggerLevel != null) { - const enumValue = - LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; - if (enumValue != null) { - maxLevel = enumValue; - } -} export default class Logger { private name: string; @@ -92,3 +84,13 @@ export default class Logger { } } } + +export function initLogger(config: Config) { + if (config.loggerLevel != null) { + const enumValue = + LogLevel[config.loggerLevel.toUpperCase() as keyof typeof LogLevel]; + if (enumValue != null) { + maxLevel = enumValue; + } + } +} diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index 4f57d02..562a5ec 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -8,7 +8,7 @@ import Module from "node:module"; import { constants } from "@moonlight-mod/types"; import { readConfig } from "@moonlight-mod/core/config"; import { getExtensions } from "@moonlight-mod/core/extension"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { loadExtensions, loadProcessedExtensions @@ -161,7 +161,8 @@ Object.defineProperty(BrowserWindow, "name", { export async function inject(asarPath: string) { isMoonlightDesktop = asarPath === "moonlightDesktop"; try { - const config = readConfig(); + const config = await readConfig(); + initLogger(config); const extensions = await getExtensions(); // Duplicated in node-preload... oops diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index ea914f7..85817b7 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -6,14 +6,15 @@ import { readConfig, writeConfig } from "@moonlight-mod/core/config"; import { constants } from "@moonlight-mod/types"; import { getExtensions } from "@moonlight-mod/core/extension"; import { getExtensionsPath } from "@moonlight-mod/core/util/data"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; async function injectGlobals() { - const config = readConfig(); + const config = await readConfig(); + initLogger(config); const extensions = await getExtensions(); const processedExtensions = await loadExtensions(extensions); diff --git a/packages/types/src/globals.ts b/packages/types/src/globals.ts index adc6c7f..08a438d 100644 --- a/packages/types/src/globals.ts +++ b/packages/types/src/globals.ts @@ -38,6 +38,23 @@ export type MoonlightNode = { writeConfig: (config: Config) => void; }; +export type MoonlightBrowserFS = { + readFile: (path: string) => Promise; + writeFile: (path: string, data: Uint8Array) => Promise; + unlink: (path: string) => Promise; + + readdir: (path: string) => Promise; + mkdir: (path: string) => Promise; + rmdir: (path: string) => Promise; + + exists: (path: string) => Promise; + isFile: (path: string) => Promise; + + join: (...parts: string[]) => string; + dirname: (path: string) => string; + parts: (path: string) => string[]; +}; + export type MoonlightWeb = { unpatched: Set; pendingModules: Set; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5bf11aa..fa87414 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -5,6 +5,7 @@ /* eslint-disable no-var */ import { + MoonlightBrowserFS, MoonlightEnv, MoonlightHost, MoonlightNode, @@ -35,4 +36,5 @@ declare global { var moonlight: MoonlightWeb; var _moonlightLoad: () => Promise; + var _moonlightBrowserFS: MoonlightBrowserFS | undefined; } diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index b4e5f18..d958a7b 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -7,7 +7,7 @@ import { } from "@moonlight-mod/core/patch"; import { constants } from "@moonlight-mod/types"; import { installStyles } from "@moonlight-mod/core/styles"; -import Logger from "@moonlight-mod/core/util/logger"; +import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import LunAST from "@moonlight-mod/lunast"; import Moonmap from "@moonlight-mod/moonmap"; import loadMappings from "@moonlight-mod/mappings"; @@ -15,6 +15,7 @@ import { createEventEmitter } from "@moonlight-mod/core/util/event"; import { EventPayloads, EventType } from "@moonlight-mod/types/core/event"; async function load() { + initLogger(moonlightNode.config); const logger = new Logger("web-preload"); window.moonlight = { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9d7170b..8f02261 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -47,9 +47,18 @@ importers: '@moonlight-mod/core': specifier: workspace:* version: link:../core + '@moonlight-mod/types': + specifier: workspace:* + version: link:../types '@moonlight-mod/web-preload': specifier: workspace:* version: link:../web-preload + '@zenfs/core': + specifier: ^1.0.2 + version: 1.0.2 + '@zenfs/dom': + specifier: ^0.2.16 + version: 0.2.16(@zenfs/core@1.0.2) packages/core: dependencies: @@ -59,9 +68,9 @@ importers: packages/core-extensions: dependencies: - '@electron/asar': - specifier: ^3.2.5 - version: 3.2.5 + '@moonlight-mod/core': + specifier: workspace:* + version: link:../core '@moonlight-mod/types': specifier: workspace:* version: link:../types @@ -129,11 +138,6 @@ packages: resolution: {integrity: sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==} engines: {node: '>=0.10.0'} - '@electron/asar@3.2.5': - resolution: {integrity: sha512-Ypahc2ElTj9YOrFvUHuoXv5Z/V1nPA5enlhmQapc578m/HZBHKTbqhoL5JZQjje2+/6Ti5AHh7Gj1/haeJa63Q==} - engines: {node: '>=10.12.0'} - hasBin: true - '@esbuild/android-arm64@0.19.3': resolution: {integrity: sha512-w+Akc0vv5leog550kjJV9Ru+MXMR2VuMrui3C61mnysim0gkFCPOUTAfzTP0qX+HpN9Syu3YA3p1hf3EPqObRw==} engines: {node: '>=12'} @@ -343,12 +347,18 @@ packages: '@types/node@18.17.17': resolution: {integrity: sha512-cOxcXsQ2sxiwkykdJqvyFS+MLQPLvIdwh5l6gNg8qF6s+C7XSkEWOZjK+XhUZd+mYvHV/180g2cnCcIl4l06Pw==} + '@types/node@20.16.10': + resolution: {integrity: sha512-vQUKgWTjEIRFCvK6CyriPH3MZYiYlNy0fKiEYHWbcoWLEgs4opurGGKlebrTLqdSMIbXImH6XExNiIyNUv3WpA==} + '@types/prop-types@15.7.13': resolution: {integrity: sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==} '@types/react@18.3.10': resolution: {integrity: sha512-02sAAlBnP39JgXwkAq3PeU9DVaaGpZyF3MGcC0MKgQVkZor5IiiDAipVaxQHtDJAmO4GIy/rVBy/LzVj76Cyqg==} + '@types/readable-stream@4.0.15': + resolution: {integrity: sha512-oAZ3kw+kJFkEqyh7xORZOku1YAKvsFTogRY8kVl4vHpEKiDkfnSA/My8haRE7fvmix5Zyy+1pwzOi7yycGLBJw==} + '@types/semver@7.5.6': resolution: {integrity: sha512-dn1l8LaMea/IjDoHNd9J52uBbInB796CDffS6VdIxvqYCPSG0V0DzHp76GpaWnlhg88uYyPbXCDIowa86ybd5A==} @@ -413,6 +423,21 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@zenfs/core@1.0.2': + resolution: {integrity: sha512-LMTD4ntn6Ag1y+IeOSVykDDvYC12dsGFtsX8M/54OQrLs7v+YnX4bpo0o2osbm8XFmU2MTNMX/G3PLsvzgWzrg==} + engines: {node: '>= 16'} + hasBin: true + + '@zenfs/dom@0.2.16': + resolution: {integrity: sha512-6Ev+ol9hZIgQECNZR+xxjQ/a99EhhrWeiQttm/+U7YJK3HdTjiKfU39DsfGeH64vSqhpa5Vj+LWRx75SHkjw0Q==} + engines: {node: '>= 18'} + peerDependencies: + '@zenfs/core': ^1.0.0 + + abort-controller@3.0.0: + resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} + engines: {node: '>=6.5'} + acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -477,6 +502,9 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} + base64-js@1.5.1: + resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} + big-integer@1.6.52: resolution: {integrity: sha512-QxD8cf2eVqJOOz63z6JIN9BzvVs/dlySa5HGSBH5xtR8dPteIRQnBxxKqkNTiT6jbDTF6jAfrd4oMcND9RGbQg==} engines: {node: '>=0.6'} @@ -488,10 +516,16 @@ packages: brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} + brace-expansion@2.0.1: + resolution: {integrity: sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==} + braces@3.0.2: resolution: {integrity: sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==} engines: {node: '>=8'} + buffer@6.0.3: + resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==} + bundle-name@3.0.0: resolution: {integrity: sha512-PKA4BeSvBpQKQ8iPOGCSiell+N8P+Tf1DlwqmYhpe2gAhKPHn8EYOxVT+ShuGmhg8lN8XiSlS80yiExKXrURlw==} engines: {node: '>=12'} @@ -514,10 +548,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - commander@5.1.0: - resolution: {integrity: sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==} - engines: {node: '>= 6'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -667,6 +697,17 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + event-target-shim@5.0.1: + resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} + engines: {node: '>=6'} + + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + + events@3.3.0: + resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} + engines: {node: '>=0.8.x'} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -809,6 +850,9 @@ packages: engines: {node: '>=14'} hasBin: true + ieee754@1.2.1: + resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} + ignore@5.3.0: resolution: {integrity: sha512-g7dmpshy+gD7mh88OC9NwSGTKoc3kyLAZQRU1mt53Aw/vnvfXnbC+F/7F7QoYVKbV+KNvJx8wArewKy1vXMtlg==} engines: {node: '>= 4'} @@ -1026,6 +1070,10 @@ packages: minimatch@3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@9.0.5: + resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + engines: {node: '>=16 || 14 >=14.17'} + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} @@ -1144,6 +1192,10 @@ packages: engines: {node: '>=14'} hasBin: true + process@0.11.10: + resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} + engines: {node: '>= 0.6.0'} + prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} @@ -1157,6 +1209,10 @@ packages: react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + readable-stream@4.5.2: + resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + reflect.getprototypeof@1.0.4: resolution: {integrity: sha512-ECkTw8TmJwW60lOTR+ZkODISW6RQ8+2CL3COqtiJKLd6MmB45hN51HprHFziKLGkAuTGQhBb91V8cy+KHlaCjw==} engines: {node: '>= 0.4'} @@ -1193,6 +1249,12 @@ packages: resolution: {integrity: sha512-6XbUAseYE2KtOuGueyeobCySj9L4+66Tn6KQMOPQJrAJEowYKW/YR/MGJZl7FdydUdaFu4LYyDZjxf4/Nmo23Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + safe-regex-test@1.0.0: resolution: {integrity: sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==} @@ -1247,6 +1309,9 @@ packages: string.prototype.trimstart@1.0.7: resolution: {integrity: sha512-NGhtDFu3jCEm7B4Fy0DpLewdJQOZcQ0rGbwQ/+stjnrp2i+rlKeCvos9hOIeCmqwratM47OBxY7uFZzjxHXmrg==} + string_decoder@1.3.0: + resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} @@ -1326,6 +1391,9 @@ packages: unbox-primitive@1.0.2: resolution: {integrity: sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==} + undici-types@6.19.8: + resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -1333,6 +1401,9 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + utilium@0.7.1: + resolution: {integrity: sha512-2ocvTkI7U8LERmwxL0LhFUvEfN66UqcjF6tMiURvUwSyU7U1QC9gST+3iSUSiGccFfnP3f2EXwHNXOnOzx+lAg==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -1366,12 +1437,6 @@ snapshots: '@aashutoshrathi/word-wrap@1.2.6': {} - '@electron/asar@3.2.5': - dependencies: - commander: 5.1.0 - glob: 7.2.3 - minimatch: 3.1.2 - '@esbuild/android-arm64@0.19.3': optional: true @@ -1527,6 +1592,10 @@ snapshots: '@types/node@18.17.17': {} + '@types/node@20.16.10': + dependencies: + undici-types: 6.19.8 + '@types/prop-types@15.7.13': {} '@types/react@18.3.10': @@ -1534,6 +1603,11 @@ snapshots: '@types/prop-types': 15.7.13 csstype: 3.1.3 + '@types/readable-stream@4.0.15': + dependencies: + '@types/node': 20.16.10 + safe-buffer: 5.1.2 + '@types/semver@7.5.6': {} '@typescript-eslint/eslint-plugin@6.13.2(@typescript-eslint/parser@6.13.2(eslint@8.55.0)(typescript@5.3.2))(eslint@8.55.0)(typescript@5.3.2)': @@ -1623,6 +1697,24 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@zenfs/core@1.0.2': + dependencies: + '@types/node': 20.16.10 + '@types/readable-stream': 4.0.15 + buffer: 6.0.3 + eventemitter3: 5.0.1 + minimatch: 9.0.5 + readable-stream: 4.5.2 + utilium: 0.7.1 + + '@zenfs/dom@0.2.16(@zenfs/core@1.0.2)': + dependencies: + '@zenfs/core': 1.0.2 + + abort-controller@3.0.0: + dependencies: + event-target-shim: 5.0.1 + acorn-jsx@5.3.2(acorn@8.12.1): dependencies: acorn: 8.12.1 @@ -1701,6 +1793,8 @@ snapshots: balanced-match@1.0.2: {} + base64-js@1.5.1: {} + big-integer@1.6.52: {} bplist-parser@0.2.0: @@ -1712,10 +1806,19 @@ snapshots: balanced-match: 1.0.2 concat-map: 0.0.1 + brace-expansion@2.0.1: + dependencies: + balanced-match: 1.0.2 + braces@3.0.2: dependencies: fill-range: 7.0.1 + buffer@6.0.3: + dependencies: + base64-js: 1.5.1 + ieee754: 1.2.1 + bundle-name@3.0.0: dependencies: run-applescript: 5.0.0 @@ -1739,8 +1842,6 @@ snapshots: color-name@1.1.4: {} - commander@5.1.0: {} - concat-map@0.0.1: {} cross-spawn@7.0.3: @@ -2007,6 +2108,12 @@ snapshots: esutils@2.0.3: {} + event-target-shim@5.0.1: {} + + eventemitter3@5.0.1: {} + + events@3.3.0: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -2169,6 +2276,8 @@ snapshots: husky@8.0.3: {} + ieee754@1.2.1: {} + ignore@5.3.0: {} import-fresh@3.3.0: @@ -2369,6 +2478,10 @@ snapshots: dependencies: brace-expansion: 1.1.11 + minimatch@9.0.5: + dependencies: + brace-expansion: 2.0.1 + ms@2.1.2: {} natural-compare@1.4.0: {} @@ -2481,6 +2594,8 @@ snapshots: prettier@3.1.0: {} + process@0.11.10: {} + prop-types@15.8.1: dependencies: loose-envify: 1.4.0 @@ -2493,6 +2608,14 @@ snapshots: react-is@16.13.1: {} + readable-stream@4.5.2: + dependencies: + abort-controller: 3.0.0 + buffer: 6.0.3 + events: 3.3.0 + process: 0.11.10 + string_decoder: 1.3.0 + reflect.getprototypeof@1.0.4: dependencies: call-bind: 1.0.5 @@ -2537,6 +2660,10 @@ snapshots: has-symbols: 1.0.3 isarray: 2.0.5 + safe-buffer@5.1.2: {} + + safe-buffer@5.2.1: {} + safe-regex-test@1.0.0: dependencies: call-bind: 1.0.5 @@ -2612,6 +2739,10 @@ snapshots: define-properties: 1.2.1 es-abstract: 1.22.3 + string_decoder@1.3.0: + dependencies: + safe-buffer: 5.2.1 + strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 @@ -2689,12 +2820,18 @@ snapshots: has-symbols: 1.0.3 which-boxed-primitive: 1.0.2 + undici-types@6.19.8: {} + untildify@4.0.0: {} uri-js@4.4.1: dependencies: punycode: 2.3.1 + utilium@0.7.1: + dependencies: + eventemitter3: 5.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 From c9d0ba9b464841da27137fcf246ef7966273a7d8 Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 14:12:26 -0400 Subject: [PATCH 05/73] Use mkdir when installing extension --- packages/core-extensions/src/moonbase/node.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 69c063c..5d34e56 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -38,6 +38,10 @@ async function installExtension( const files = extractAsar(buffer); for (const [file, buf] of Object.entries(files)) { const nodeBuf = Buffer.from(buf); + const fullFile = path.join(dir, file); + const fullDir = path.dirname(fullFile); + + if (!fs.existsSync(fullDir)) fs.mkdirSync(fullDir, { recursive: true }); fs.writeFileSync(path.join(dir, file), nodeBuf); } } From e765c5e37b9813d9eed7ce3e431087b2ec00f4e8 Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 14:17:11 -0400 Subject: [PATCH 06/73] Write repo URL file --- packages/core-extensions/src/moonbase/node.ts | 4 +++- .../core-extensions/src/moonbase/webpackModules/stores.ts | 6 ++++++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 5d34e56..22b30f7 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -2,6 +2,7 @@ import { MoonbaseNatives, RepositoryManifest } from "./types"; import fs from "fs"; import path from "path"; import extractAsar from "@moonlight-mod/core/asar"; +import { repoUrlFile } from "@moonlight-mod/types/constants"; const logger = moonlightNode.getLogger("moonbase"); @@ -33,7 +34,6 @@ async function installExtension( if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true }); fs.mkdirSync(dir, { recursive: true }); - // for some reason i just can't .writeFileSync() a file that ends in .asar??? const buffer = await req.arrayBuffer(); const files = extractAsar(buffer); for (const [file, buf] of Object.entries(files)) { @@ -44,6 +44,8 @@ async function installExtension( if (!fs.existsSync(fullDir)) fs.mkdirSync(fullDir, { recursive: true }); fs.writeFileSync(path.join(dir, file), nodeBuf); } + + fs.writeFileSync(path.join(dir, repoUrlFile), repo); } async function deleteExtension(id: string) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 5dbc653..36e7360 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -8,6 +8,7 @@ import { import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; import extractAsar from "@moonlight-mod/core/asar"; +import { repoUrlFile } from "@moonlight-mod/types/constants"; const logger = moonlight.getLogger("moonbase"); @@ -47,6 +48,11 @@ if (window._moonlightBrowserFS != null) { await browserFS.mkdir(browserFS.dirname(path)); await browserFS.writeFile(path, data); } + + await browserFS.writeFile( + `/extensions/${manifest.id}/` + repoUrlFile, + new TextEncoder().encode(repo) + ); }, deleteExtension: async (id) => { browserFS.rmdir("/extensions/" + id); From d1b7f4d6e2ac3ad84cbc134877b6439eda5e1a5b Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 14:33:14 -0400 Subject: [PATCH 07/73] Add mv2 support --- build.mjs | 27 ++++++++++++--------------- package.json | 2 +- packages/browser/manifest.json | 6 ------ packages/browser/manifestv2.json | 23 +++++++++++++++++++++++ packages/browser/src/background.js | 12 ++++++++++++ 5 files changed, 48 insertions(+), 22 deletions(-) create mode 100644 packages/browser/manifestv2.json create mode 100644 packages/browser/src/background.js diff --git a/build.mjs b/build.mjs index 3144738..b825584 100644 --- a/build.mjs +++ b/build.mjs @@ -14,6 +14,7 @@ const config = { const prod = process.env.NODE_ENV === "production"; const watch = process.argv.includes("--watch"); const browser = process.argv.includes("--browser"); +const mv2 = process.argv.includes("--mv2"); const external = [ "electron", @@ -114,7 +115,9 @@ async function build(name, entry) { if (name === "browser") { plugins.push( copyStaticFiles({ - src: "./packages/browser/manifest.json", + src: mv2 + ? "./packages/browser/manifestv2.json" + : "./packages/browser/manifest.json", dest: "./dist/browser/manifest.json" }) ); @@ -124,20 +127,14 @@ async function build(name, entry) { dest: "./dist/browser/modifyResponseHeaders.json" }) ); - - // This sucks lmfao - plugins.push({ - name: "browserPath", - setup(build) { - build.onResolve({ filter: /^path$/ }, () => { - const index = - "./packages/browser/node_modules/path-browserify/index.js"; - return { - path: path.resolve(index) - }; - }); - } - }); + if (mv2) { + plugins.push( + copyStaticFiles({ + src: "./packages/browser/src/background.js", + dest: "./dist/browser/background.js" + }) + ); + } } /** @type {import("esbuild").BuildOptions} */ diff --git a/package.json b/package.json index 902ed49..4f1ad30 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "build": "node build.mjs", "dev": "node build.mjs --watch", "browser": "node build.mjs --browser", - "browser-dev": "node build.mjs --browser --watch", + "browser-mv2": "node build.mjs --browser --mv2", "lint": "eslint packages", "lint:fix": "eslint packages", "lint:report": "eslint --output-file eslint_report.json --format json packages", diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index 1f51179..7477ce4 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -12,12 +12,6 @@ "world": "MAIN" } ], - "web_accessible_resources": [ - { - "resources": ["core-extensions/*"], - "matches": ["https://*.discord.com/*"] - } - ], "host_permissions": [ "https://moonlight-mod.github.io/*", "https://*.discord.com/*" diff --git a/packages/browser/manifestv2.json b/packages/browser/manifestv2.json new file mode 100644 index 0000000..e120c24 --- /dev/null +++ b/packages/browser/manifestv2.json @@ -0,0 +1,23 @@ +{ + "manifest_version": 2, + "name": "moonlight", + "description": "Yet another Discord mod", + "version": "1.1.0", + "content_scripts": [ + { + "js": ["index.js"], + "matches": ["https://*.discord.com/*"], + "run_at": "document_start", + "world": "MAIN" + } + ], + "permissions": [ + "webRequest", + "webRequestBlocking", + "https://moonlight-mod.github.io/*", + "https://*.discord.com/*" + ], + "background": { + "scripts": ["background.js"] + } +} diff --git a/packages/browser/src/background.js b/packages/browser/src/background.js new file mode 100644 index 0000000..dca4d6f --- /dev/null +++ b/packages/browser/src/background.js @@ -0,0 +1,12 @@ +// This is so tiny that I don't need to esbuild it + +// eslint-disable-next-line no-undef +chrome.webRequest.onHeadersReceived.addListener( + (details) => ({ + responseHeaders: details.responseHeaders.filter( + (header) => header.name.toLowerCase() !== "content-security-policy" + ) + }), + { urls: ["https://*.discord.com/*"] }, + ["blocking", "responseHeaders"] +); From 36cf7589107befb3f788eaefbd25f643c22c1a85 Mon Sep 17 00:00:00 2001 From: NotNite Date: Fri, 4 Oct 2024 19:34:06 -0400 Subject: [PATCH 08/73] Delay Discord loading in the worst way possible --- build.mjs | 28 +++-- packages/browser/blockLoading.json | 13 ++ packages/browser/manifest.json | 32 ++++- packages/browser/manifestv2.json | 22 ++-- packages/browser/modifyResponseHeaders.json | 2 +- packages/browser/src/background-mv2.js | 99 ++++++++++++++++ packages/browser/src/background.js | 124 ++++++++++++++++++-- packages/browser/src/index.ts | 9 +- packages/browser/tsconfig.json | 5 +- packages/core/src/extension.ts | 16 +-- packages/types/src/index.ts | 3 +- packages/web-preload/src/index.ts | 2 +- 12 files changed, 302 insertions(+), 53 deletions(-) create mode 100644 packages/browser/blockLoading.json create mode 100644 packages/browser/src/background-mv2.js diff --git a/build.mjs b/build.mjs index b825584..c898b99 100644 --- a/build.mjs +++ b/build.mjs @@ -121,20 +121,30 @@ async function build(name, entry) { dest: "./dist/browser/manifest.json" }) ); - plugins.push( - copyStaticFiles({ - src: "./packages/browser/modifyResponseHeaders.json", - dest: "./dist/browser/modifyResponseHeaders.json" - }) - ); - if (mv2) { + + if (!mv2) { plugins.push( copyStaticFiles({ - src: "./packages/browser/src/background.js", - dest: "./dist/browser/background.js" + src: "./packages/browser/modifyResponseHeaders.json", + dest: "./dist/browser/modifyResponseHeaders.json" + }) + ); + plugins.push( + copyStaticFiles({ + src: "./packages/browser/blockLoading.json", + dest: "./dist/browser/blockLoading.json" }) ); } + + plugins.push( + copyStaticFiles({ + src: mv2 + ? "./packages/browser/src/background-mv2.js" + : "./packages/browser/src/background.js", + dest: "./dist/browser/background.js" + }) + ); } /** @type {import("esbuild").BuildOptions} */ diff --git a/packages/browser/blockLoading.json b/packages/browser/blockLoading.json new file mode 100644 index 0000000..252bbd9 --- /dev/null +++ b/packages/browser/blockLoading.json @@ -0,0 +1,13 @@ +[ + { + "id": 2, + "priority": 1, + "action": { + "type": "block" + }, + "condition": { + "urlFilter": "*://discord.com/assets/*.js", + "resourceTypes": ["script"] + } + } +] diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index 7477ce4..f0bea16 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -3,7 +3,16 @@ "name": "moonlight", "description": "Yet another Discord mod", "version": "1.1.0", - "permissions": ["declarativeNetRequestWithHostAccess"], + "permissions": [ + "declarativeNetRequestWithHostAccess", + "webRequest", + "scripting", + "webNavigation" + ], + "host_permissions": [ + "https://moonlight-mod.github.io/*", + "https://*.discord.com/*" + ], "content_scripts": [ { "js": ["index.js"], @@ -12,17 +21,28 @@ "world": "MAIN" } ], - "host_permissions": [ - "https://moonlight-mod.github.io/*", - "https://*.discord.com/*" - ], "declarative_net_request": { "rule_resources": [ { "id": "modifyResponseHeaders", "enabled": true, "path": "modifyResponseHeaders.json" + }, + { + "id": "blockLoading", + "enabled": true, + "path": "blockLoading.json" } ] - } + }, + "background": { + "service_worker": "background.js", + "type": "module" + }, + "web_accessible_resources": [ + { + "resources": ["index.js"], + "matches": ["https://*.discord.com/*"] + } + ] } diff --git a/packages/browser/manifestv2.json b/packages/browser/manifestv2.json index e120c24..1b97688 100644 --- a/packages/browser/manifestv2.json +++ b/packages/browser/manifestv2.json @@ -3,21 +3,23 @@ "name": "moonlight", "description": "Yet another Discord mod", "version": "1.1.0", - "content_scripts": [ - { - "js": ["index.js"], - "matches": ["https://*.discord.com/*"], - "run_at": "document_start", - "world": "MAIN" - } - ], "permissions": [ "webRequest", "webRequestBlocking", - "https://moonlight-mod.github.io/*", + "scripting", + "webNavigation", + "https://*.discord.com/assets/*.js", "https://*.discord.com/*" ], "background": { "scripts": ["background.js"] - } + }, + "content_scripts": [ + { + "js": ["index.js"], + "matches": ["https://*.discord.com/*"], + "run_at": "document_start", + "world": "MAIN" + } + ] } diff --git a/packages/browser/modifyResponseHeaders.json b/packages/browser/modifyResponseHeaders.json index 51ce30d..b8fed08 100644 --- a/packages/browser/modifyResponseHeaders.json +++ b/packages/browser/modifyResponseHeaders.json @@ -1,7 +1,7 @@ [ { "id": 1, - "priority": 1, + "priority": 2, "action": { "type": "modifyHeaders", "responseHeaders": [ diff --git a/packages/browser/src/background-mv2.js b/packages/browser/src/background-mv2.js new file mode 100644 index 0000000..33b1669 --- /dev/null +++ b/packages/browser/src/background-mv2.js @@ -0,0 +1,99 @@ +/* eslint-disable no-console */ +/* eslint-disable no-undef */ + +const starterUrls = ["web.", "sentry."]; +let blockLoading = true; +let doing = false; +let collectedUrls = new Set(); + +chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { + const url = new URL(details.url); + if (!blockLoading && url.hostname.endsWith("discord.com")) { + console.log("Blocking", details.url); + blockLoading = true; + collectedUrls.clear(); + } +}); + +async function doTheThing(urls, tabId) { + console.log("Doing", urls, tabId); + + blockLoading = false; + + try { + await chrome.scripting.executeScript({ + target: { tabId }, + world: "MAIN", + args: [urls], + func: async (urls) => { + try { + await window._moonlightBrowserInit(); + } catch (e) { + console.log(e); + } + + const scripts = [...document.querySelectorAll("script")].filter( + (script) => script.src && urls.some((url) => url.includes(script.src)) + ); + + // backwards + urls.reverse(); + for (const url of urls) { + const script = scripts.find((script) => url.includes(script.src)); + console.log("adding new script", script); + + const newScript = document.createElement("script"); + for (const { name, value } of script.attributes) { + newScript.setAttribute(name, value); + } + + script.remove(); + document.documentElement.appendChild(newScript); + } + } + }); + } catch (e) { + console.log(e); + } + + doing = false; + collectedUrls.clear(); +} + +chrome.webRequest.onBeforeRequest.addListener( + async (details) => { + if (starterUrls.some((url) => details.url.includes(url))) { + console.log("Adding", details.url); + collectedUrls.add(details.url); + } + + if (collectedUrls.size === starterUrls.length) { + if (doing) return; + if (!blockLoading) return; + doing = true; + const urls = [...collectedUrls]; + const tabId = details.tabId; + + // yes this is a load-bearing sleep + setTimeout(() => doTheThing(urls, tabId), 0); + } + + if (blockLoading) return { cancel: true }; + }, + { + urls: ["https://*.discord.com/assets/*.js"] + }, + ["blocking"] +); + +chrome.webRequest.onHeadersReceived.addListener( + (details) => { + return { + responseHeaders: details.responseHeaders.filter( + (header) => header.name.toLowerCase() !== "content-security-policy" + ) + }; + }, + { urls: ["https://*.discord.com/*"] }, + ["blocking", "responseHeaders"] +); diff --git a/packages/browser/src/background.js b/packages/browser/src/background.js index dca4d6f..46d0464 100644 --- a/packages/browser/src/background.js +++ b/packages/browser/src/background.js @@ -1,12 +1,114 @@ -// This is so tiny that I don't need to esbuild it - -// eslint-disable-next-line no-undef -chrome.webRequest.onHeadersReceived.addListener( - (details) => ({ - responseHeaders: details.responseHeaders.filter( - (header) => header.name.toLowerCase() !== "content-security-policy" - ) - }), - { urls: ["https://*.discord.com/*"] }, - ["blocking", "responseHeaders"] +/* eslint-disable no-console */ +/* eslint-disable no-undef */ + +const starterUrls = ["web.", "sentry."]; +let blockLoading = true; +let doing = false; +let collectedUrls = new Set(); + +chrome.webNavigation.onBeforeNavigate.addListener(async (details) => { + const url = new URL(details.url); + if (!blockLoading && url.hostname.endsWith("discord.com")) { + await chrome.declarativeNetRequest.updateEnabledRulesets({ + enableRulesetIds: ["modifyResponseHeaders", "blockLoading"] + }); + blockLoading = true; + collectedUrls.clear(); + } +}); + +chrome.webRequest.onBeforeRequest.addListener( + async (details) => { + if (details.tabId === -1) return; + if (starterUrls.some((url) => details.url.includes(url))) { + console.log("Adding", details.url); + collectedUrls.add(details.url); + } + + if (collectedUrls.size === starterUrls.length) { + if (doing) return; + if (!blockLoading) return; + doing = true; + const urls = [...collectedUrls]; + console.log("Doing", urls); + + console.log("Running moonlight script"); + try { + await chrome.scripting.executeScript({ + target: { tabId: details.tabId }, + world: "MAIN", + files: ["index.js"] + }); + } catch (e) { + console.log(e); + } + + console.log("Initializing moonlight"); + try { + await chrome.scripting.executeScript({ + target: { tabId: details.tabId }, + world: "MAIN", + func: async () => { + try { + await window._moonlightBrowserInit(); + } catch (e) { + console.log(e); + } + } + }); + } catch (e) { + console.log(e); + } + + console.log("Updating rulesets"); + try { + blockLoading = false; + await chrome.declarativeNetRequest.updateEnabledRulesets({ + disableRulesetIds: ["blockLoading"], + enableRulesetIds: ["modifyResponseHeaders"] + }); + } catch (e) { + console.log(e); + } + + console.log("Readding scripts"); + try { + await chrome.scripting.executeScript({ + target: { tabId: details.tabId }, + world: "MAIN", + args: [urls], + func: async (urls) => { + const scripts = [...document.querySelectorAll("script")].filter( + (script) => + script.src && urls.some((url) => url.includes(script.src)) + ); + + // backwards + urls.reverse(); + for (const url of urls) { + const script = scripts.find((script) => url.includes(script.src)); + console.log("adding new script", script); + + const newScript = document.createElement("script"); + for (const { name, value } of script.attributes) { + newScript.setAttribute(name, value); + } + + script.remove(); + document.documentElement.appendChild(newScript); + } + } + }); + } catch (e) { + console.log(e); + } + + console.log("Done"); + doing = false; + collectedUrls.clear(); + } + }, + { + urls: ["*://*.discord.com/assets/*.js"] + } ); diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 9bf9f1c..75795c2 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -4,14 +4,11 @@ import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { getExtensions } from "@moonlight-mod/core/extension"; import { loadExtensions } from "@moonlight-mod/core/extension/loader"; import { MoonlightBrowserFS, MoonlightNode } from "@moonlight-mod/types"; - import { IndexedDB } from "@zenfs/dom"; import { configure } from "@zenfs/core"; import * as fs from "@zenfs/core/promises"; -// Mostly copy pasted from node-preload, FIXME -// TODO: is this safe an in IIFE? -(async () => { +window._moonlightBrowserInit = async () => { // Set up a virtual filesystem with IndexedDB await configure({ mounts: { @@ -131,5 +128,5 @@ import * as fs from "@zenfs/core/promises"; }); // This is set by web-preload for us - await window._moonlightLoad(); -})(); + await window._moonlightBrowserLoad(); +}; diff --git a/packages/browser/tsconfig.json b/packages/browser/tsconfig.json index 4082f16..691d95d 100644 --- a/packages/browser/tsconfig.json +++ b/packages/browser/tsconfig.json @@ -1,3 +1,6 @@ { - "extends": "../../tsconfig.json" + "extends": "../../tsconfig.json", + "compilerOptions": { + "module": "ES2022" + } } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index c072ca6..d20cc28 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -246,13 +246,15 @@ async function getExtensionsBrowser(): Promise { } const fs = getFS(); - ret.push( - ...(await loadDetectedExtensions( - fs, - "/extensions", - ExtensionLoadSource.Normal - )) - ); + if (await fs.exists("/extensions")) { + ret.push( + ...(await loadDetectedExtensions( + fs, + "/extensions", + ExtensionLoadSource.Normal + )) + ); + } return ret; } diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index fa87414..5930b8f 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -35,6 +35,7 @@ declare global { var moonlightNode: MoonlightNode; var moonlight: MoonlightWeb; - var _moonlightLoad: () => Promise; + var _moonlightBrowserInit: () => Promise; + var _moonlightBrowserLoad: () => Promise; var _moonlightBrowserFS: MoonlightBrowserFS | undefined; } diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index d958a7b..aa0b063 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -56,5 +56,5 @@ async function load() { if (MOONLIGHT_ENV === "web-preload") { load(); } else { - window._moonlightLoad = load; + window._moonlightBrowserLoad = load; } From 7c107ca9d345757edd0182915c5cf9e925884215 Mon Sep 17 00:00:00 2001 From: NotNite Date: Sat, 5 Oct 2024 00:09:39 -0400 Subject: [PATCH 09/73] CI for browser --- .github/workflows/browser.yml | 45 +++++++++++++++++++++++++++++++++++ build.mjs | 11 +++++---- 2 files changed, 51 insertions(+), 5 deletions(-) create mode 100644 .github/workflows/browser.yml diff --git a/.github/workflows/browser.yml b/.github/workflows/browser.yml new file mode 100644 index 0000000..2f74daa --- /dev/null +++ b/.github/workflows/browser.yml @@ -0,0 +1,45 @@ +name: Browser extension builds + +on: + push: + branches: + - develop + +jobs: + browser: + name: Browser extension builds + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + + - uses: pnpm/action-setup@v2 + with: + version: 9 + run_install: false + - uses: actions/setup-node@v3 + with: + node-version: 18 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + - name: Build moonlight + env: + NODE_ENV: production + run: pnpm run build + + - name: Build MV3 + run: pnpm run browser + - name: Build MV2 + run: pnpm run browser-mv2 + + - name: Upload MV3 + uses: actions/upload-artifact@v4 + with: + name: browser + path: ./dist/browser + - name: Upload MV2 + uses: actions/upload-artifact@v4 + with: + name: browser-mv2 + path: ./dist/browser-mv2 diff --git a/build.mjs b/build.mjs index c898b99..0b3cda5 100644 --- a/build.mjs +++ b/build.mjs @@ -75,7 +75,8 @@ const taggedBuildLog = (tag) => ({ async function build(name, entry) { let outfile = path.join("./dist", name + ".js"); - if (name === "browser") outfile = path.join("./dist", "browser", "index.js"); + const browserDir = mv2 ? "browser-mv2" : "browser"; + if (name === "browser") outfile = path.join("./dist", browserDir, "index.js"); const dropLabels = []; const labels = { @@ -118,7 +119,7 @@ async function build(name, entry) { src: mv2 ? "./packages/browser/manifestv2.json" : "./packages/browser/manifest.json", - dest: "./dist/browser/manifest.json" + dest: `./dist/${browserDir}/manifest.json` }) ); @@ -126,13 +127,13 @@ async function build(name, entry) { plugins.push( copyStaticFiles({ src: "./packages/browser/modifyResponseHeaders.json", - dest: "./dist/browser/modifyResponseHeaders.json" + dest: `./dist/${browserDir}/modifyResponseHeaders.json` }) ); plugins.push( copyStaticFiles({ src: "./packages/browser/blockLoading.json", - dest: "./dist/browser/blockLoading.json" + dest: `./dist/${browserDir}/blockLoading.json` }) ); } @@ -142,7 +143,7 @@ async function build(name, entry) { src: mv2 ? "./packages/browser/src/background-mv2.js" : "./packages/browser/src/background.js", - dest: "./dist/browser/background.js" + dest: `./dist/${browserDir}/background.js` }) ); } From 167912e5005d53ff37e2286c89326306f87d34a4 Mon Sep 17 00:00:00 2001 From: NotNite Date: Sat, 5 Oct 2024 10:18:58 -0400 Subject: [PATCH 10/73] Nix flake: Fix for pnpm 9 --- flake.lock | 24 ++++++++++++------------ flake.nix | 4 ++-- 2 files changed, 14 insertions(+), 14 deletions(-) diff --git a/flake.lock b/flake.lock index 4b80cec..4846425 100644 --- a/flake.lock +++ b/flake.lock @@ -38,27 +38,27 @@ }, "nixpkgs": { "locked": { - "lastModified": 1704295289, - "narHash": "sha256-9WZDRfpMqCYL6g/HNWVvXF0hxdaAgwgIGeLYiOhmes8=", + "lastModified": 1728067476, + "narHash": "sha256-/uJcVXuBt+VFCPQIX+4YnYrHaubJSx4HoNsJVNRgANM=", "owner": "NixOS", "repo": "nixpkgs", - "rev": "b0b2c5445c64191fd8d0b31f2b1a34e45a64547d", + "rev": "6e6b3dd395c3b1eb9be9f2d096383a8d05add030", "type": "github" }, "original": { "owner": "NixOS", - "ref": "nixos-23.11", + "ref": "nixos-24.05", "repo": "nixpkgs", "type": "github" } }, "nixpkgs_2": { "locked": { - "lastModified": 1702151865, - "narHash": "sha256-9VAt19t6yQa7pHZLDbil/QctAgVsA66DLnzdRGqDisg=", + "lastModified": 1727802920, + "narHash": "sha256-HP89HZOT0ReIbI7IJZJQoJgxvB2Tn28V6XS3MNKnfLs=", "owner": "nixos", "repo": "nixpkgs", - "rev": "666fc80e7b2afb570462423cb0e1cf1a3a34fedd", + "rev": "27e30d177e57d912d614c88c622dcfdb2e6e6515", "type": "github" }, "original": { @@ -74,15 +74,15 @@ "nixpkgs": "nixpkgs_2" }, "locked": { - "lastModified": 1709572248, - "narHash": "sha256-WhaKD4cIvZLbwI2vZTkpH/oEeqGiyMvdW3bLi24P0eU=", - "owner": "mojotech", + "lastModified": 1728137762, + "narHash": "sha256-iEFvPR3BopGyI5KjQ1DK+gEZ1dKDugq838tKdet2moQ=", + "owner": "NotNite", "repo": "pnpm2nix-nzbr", - "rev": "c3cfff81ea297cfb9dc18928652f375314dc287d", + "rev": "b7a60d3c7d106b601665e3f05dba6cdc6f59f959", "type": "github" }, "original": { - "owner": "mojotech", + "owner": "NotNite", "repo": "pnpm2nix-nzbr", "type": "github" } diff --git a/flake.nix b/flake.nix index 24bd0bd..41ebfee 100644 --- a/flake.nix +++ b/flake.nix @@ -2,9 +2,9 @@ description = "Yet another Discord mod"; inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixos-23.11"; + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.05"; flake-utils.url = "github:numtide/flake-utils"; - pnpm2nix.url = "github:mojotech/pnpm2nix-nzbr"; + pnpm2nix.url = "github:NotNite/pnpm2nix-nzbr"; }; outputs = { self, nixpkgs, flake-utils, pnpm2nix }: From d4cd2fac408a0eeea22f3fddf35479837ac1e6d9 Mon Sep 17 00:00:00 2001 From: NotNite Date: Sat, 5 Oct 2024 10:26:20 -0400 Subject: [PATCH 11/73] Add light mode wordmark for the README --- README.md | 6 +++++- img/wordmark-light.png | Bin 0 -> 5360 bytes 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 img/wordmark-light.png diff --git a/README.md b/README.md index 03dac1a..ce17efc 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@

- moonlight + + + + moonlight + Discord server \- GitHub diff --git a/img/wordmark-light.png b/img/wordmark-light.png new file mode 100644 index 0000000000000000000000000000000000000000..1a1de5d381cfb091fe4f6ade7ad5e177cd157ebf GIT binary patch literal 5360 zcmeHLe^8V68Gli8v}O`}t$-EC>Bg?xFwxX8P*|EOvli#&{DHO-0u9#PGy;ne zIxg?~CCT@FettgB^L+D;aL3kVOJ7_{QPi?+uWi{$QA^<82SMejR=o6jB}KiEv2BZBcS+7f zfA`&0t-oI)EKB3h%o-;T-`6mITX-_#qDS{m%chL)QT18FR{~S#&Tq_$dF9UaHtKxT zU4~G8kyClTS{|`G{sL3$$Q4n0BF7f#HtzejZ7Dk(;a#k}I&sb|*^{pQ&!MO&kMPDA z0{pilykk{*Yn+&u#Z-Kt^-J7`7%p2@3}reZE5EgGt-HR;Q#aVO!lK+SqGH>-rxGQX z2M#WE@N0}&!hlpBkvpDj)~N1Oo%?|<5bJ+a;A_6asQm8=bmzd^e-jCghc8#hRZK_Stu}53A3e~}?()}^l zxZmK1tn9y1<)hPs0hYg|gPm8hCO>Gwzj4j$% zn4}YrkW4=k$E1{K3s-g_+PhtlepBE}>0!7eY0q1%ZB%p|)1gTvh)EdID$a~ndDfqO z)*%&9Of|#R8TTQBze~s}67p}$1isjwC9W$Ij7g*0FNPG34k~-g-Nv0gYutlvnQGgJ zd*-1h{n7oLN~XHtT$643-|TG}_=Y_&>t5u}V`X!y*oM)4OZ+Kp!<039S~KlErOz~p zNBR=&nWiewSMd^9N*l(2R{&SNZIIL3ae+CIcUH_hcFWxv*Pai26N(Lz!>|;{`lV_N z;LN5khTAmqh_tThOcQ6NvRFAa>r~VXj*yk+NjOE;AOs?zUu58cu9#D&ThWsQQ*S7u&EX}R=lg$XW z+4%dv>hEl$5=I^-f<{12rb(k>xP~{G-h(B%w|~S9Fp?N=h#Je@h`Dwn&6p1wHL2jr zDZSQEZtjES-bkA@jtu}AFK+3SbH^5y{BcyH67l>zGiRlk7j4lvWB0>i%`R@RBa(el z#jW=bSeXNX$$pkC(8(OAb(Tl}ns4w81`7Mdx2FXa4G_&EvNSN2qpJqMZq(UEPOk<{ zsErQPw|kS6Td9<=y4FnkSY-n^VM!SxxDKeIml(O@!-4OrjO9-7FH(+P z;(h9resvBnc`kl}cnd@^(^OM}oWlZ4Ni;IPN;%H1**J$zP@+*S!{=z*qa0fJ2W23i zSy(e;+t1AAc_o2$77z$Q!28ww!}uH>-ca4gZk1rwzScMV1j$k3gNkIox(K)=+_NM5tZU)goAdV)6z@xep3O=%kylAYU zxxu8N`xtRLUvqaClvhH}`Q#IGgRw;5(3nHC(ZM7h6W$~HX`|6x?i>n9J_!^SU`RYB zOhHi5Mx(jnIdBjr00Dx&(ilOH!x9~d<{C)O#utAS`8H7OAmk1zRBp?CyY~o&^^$l! zNPCJ{3MxKtiUp&eB4t{`_+BIx9pr^je?2n5)tMhXg?}&*Lqum+AfuivC$U02 zdA48#P<--qpkTNT7~gtw>{%tye$$t~JFs^~ko21sJ%%n|7n$~#k$Ml6G>li+7Q$I2 zRU@acEaMf*CoJ>fY{DZ*@_elikSX$R0e#^v!o80>mJ1 z=8X!n28J4_)4}!@#uscr#Hb)!$Wds%Uxx7n4I@ScS#k`A7Sende3|11w#g87KnuDU z`POsZi>w+azc2E}<-kmrRMDI$L6?(s1?Y@a4ZvNfXIe$Wp~uAM|C@s~_lZL{VaIt> z4#$*{zQPGBmL-Co&^dmgR>S;EM(@xzf`H#2I?gYo2~!9Ycxb^T5DJ^>K^9%0JCJD5 zM?!lCL4D|OztC$mDtaCdR@vaOd~N8AzfhG=!TB7vb+b*H9gHyTjv@>02%!zp= zzWYIfMcl;Hsz=ILj;3CQaO-qOq^lQx?0Ns;y=u>xJi_u>xl`bCwNZ{=7#?W-rOMA2 z={(PW&6grdzQI=Z#5$UWX8T9lP_6ir2kqT*w8U`IS==yY0OKlF*qq6|6=1LDhX$Lp zakf-Mx%QMBVc11;K6EQ!Hbx8gPWg_=`q5|*SkZ7u{TT`@_9pkny-x(XrH!dSBLd+Z zDN_-F_U6`+SR&9|AnRUF1j5nzwB#rXj5l`w*!&L^xXI8xWhbAd!@;_U>^oZ^^QMqz zdzw=fXNbH}YaD+E(J?Jmv;R!=K1?>({f#(R-e5O>`9Jjwn%UE%^s_sD_tDP8yy(^N QD Date: Sun, 6 Oct 2024 00:41:21 -0400 Subject: [PATCH 12/73] Update README --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ce17efc..242e4f8 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,6 @@ 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. -**_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. +**_This is an experimental passion project._** Anything and everything is subject to change, but it is stable enough for developers to experiment with. 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. From 1863ee061ac8e122c578fe6dcdaf08d5bb3947e6 Mon Sep 17 00:00:00 2001 From: NotNite Date: Sun, 6 Oct 2024 18:28:15 -0400 Subject: [PATCH 13/73] rocketship init --- .../core-extensions/src/rocketship/host.ts | 36 +++++++++++++ .../core-extensions/src/rocketship/index.ts | 54 +++++++++++++++++++ .../src/rocketship/manifest.json | 10 ++++ packages/injector/src/index.ts | 11 ++-- 4 files changed, 106 insertions(+), 5 deletions(-) create mode 100644 packages/core-extensions/src/rocketship/host.ts create mode 100644 packages/core-extensions/src/rocketship/index.ts create mode 100644 packages/core-extensions/src/rocketship/manifest.json diff --git a/packages/core-extensions/src/rocketship/host.ts b/packages/core-extensions/src/rocketship/host.ts new file mode 100644 index 0000000..33b4086 --- /dev/null +++ b/packages/core-extensions/src/rocketship/host.ts @@ -0,0 +1,36 @@ +import type { BrowserWindow } from "electron"; +import { desktopCapturer } from "electron"; + +const logger = moonlightHost.getLogger("rocketship"); + +moonlightHost.events.on( + "window-created", + (window: BrowserWindow, isMainWindow: boolean) => { + if (!isMainWindow) return; + + const windowSession = window.webContents.session; + + window.webContents.on("did-finish-load", () => { + windowSession.setPermissionRequestHandler( + (webContents, permission, callback) => { + logger.debug("windowSession.setPermissionRequestHandler", permission); + if (permission === "media") callback(true); + } + ); + + // @ts-expect-error these types ancient + windowSession.setDisplayMediaRequestHandler( + (request: any, callback: any) => { + logger.debug("windowSession.setDisplayMediaRequestHandler", request); + desktopCapturer + .getSources({ types: ["screen", "window"] }) + .then((sources) => { + logger.debug("desktopCapturer.getSources", sources); + callback({ video: sources[0], audio: "loopback" }); + }); + }, + { useSystemPicker: true } + ); + }); + } +); diff --git a/packages/core-extensions/src/rocketship/index.ts b/packages/core-extensions/src/rocketship/index.ts new file mode 100644 index 0000000..d392425 --- /dev/null +++ b/packages/core-extensions/src/rocketship/index.ts @@ -0,0 +1,54 @@ +import { Patch } from "@moonlight-mod/types"; + +export const patches: Patch[] = [ + { + find: "RustAudioDeviceModule", + replace: [ + { + match: /static supported\(\)\{.+?\}/, + replacement: "static supported(){return true}" + }, + { + match: "supported(){return!0}", + replacement: "supported(){return true}" + } + ] + }, + { + find: '.CAMERA_BACKGROUND_LIVE="cameraBackgroundLive"', + replace: { + match: /.\..{1,2}\.NATIVE,/, + replacement: "" + } + }, + { + find: "Using Unified Plan (", + replace: { + match: /return .\..{1,2}\?\((.)\.info/, + replacement: (_, logger) => `return true?(${logger}.info` + } + }, + { + find: '"UnifiedConnection("', + replace: { + match: /this\.videoSupported=.\..{1,2};/, + replacement: "this.videoSupported=true;" + } + }, + { + find: "OculusBrowser", + replace: [ + { + match: /"Firefox"===(.)\(\)\.name/g, + replacement: (orig, info) => `true||${orig}` + } + ] + }, + { + find: ".getMediaEngine().getDesktopSource", + replace: { + match: /.\.isPlatformEmbedded/, + replacement: "false" + } + } +]; diff --git a/packages/core-extensions/src/rocketship/manifest.json b/packages/core-extensions/src/rocketship/manifest.json new file mode 100644 index 0000000..3f3b07b --- /dev/null +++ b/packages/core-extensions/src/rocketship/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "rocketship", + "apiLevel": 2, + "meta": { + "name": "Rocketship", + "tagline": "Adds new features when using rocketship", + "description": "**This extension only works on Linux when using rocketship: https://github.com/moonlight-mod/rocketship**. Adds new features to the Discord Linux client with rocketship, like better screensharing.", + "authors": ["NotNite", "Cynosphere", "adryd"] + } +} diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index 562a5ec..e9afe0f 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -89,18 +89,19 @@ class BrowserWindow extends ElectronBrowserWindow { constructor(opts: BrowserWindowConstructorOptions) { oldPreloadPath = opts.webPreferences!.preload; - // Only overwrite preload if its the actual main client window - if (opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1) { + const isMainWindow = + opts.webPreferences!.preload!.indexOf("discord_desktop_core") > -1; + + if (isMainWindow) opts.webPreferences!.preload = require.resolve("./node-preload.js"); - } // Event for modifying window options - moonlightHost.events.emit("window-options", opts); + moonlightHost.events.emit("window-options", opts, isMainWindow); super(opts); // Event for when a window is created - moonlightHost.events.emit("window-created", this); + moonlightHost.events.emit("window-created", this, isMainWindow); this.webContents.session.webRequest.onHeadersReceived((details, cb) => { if (details.responseHeaders != null) { From 8f5deb45f237256422edd8d0bf4daac0e3385c7b Mon Sep 17 00:00:00 2001 From: NotNite Date: Sun, 6 Oct 2024 19:44:47 -0400 Subject: [PATCH 14/73] rocketship: venmic integration --- packages/core-extensions/package.json | 3 +- .../core-extensions/src/rocketship/host.ts | 63 ++- .../core-extensions/src/rocketship/index.ts | 54 +++ pnpm-lock.yaml | 445 ++++++++++++++++++ 4 files changed, 559 insertions(+), 6 deletions(-) diff --git a/packages/core-extensions/package.json b/packages/core-extensions/package.json index d90313f..4f6d61a 100644 --- a/packages/core-extensions/package.json +++ b/packages/core-extensions/package.json @@ -2,7 +2,8 @@ "name": "@moonlight-mod/core-extensions", "private": true, "dependencies": { + "@moonlight-mod/core": "workspace:*", "@moonlight-mod/types": "workspace:*", - "@moonlight-mod/core": "workspace:*" + "@vencord/venmic": "^6.1.0" } } diff --git a/packages/core-extensions/src/rocketship/host.ts b/packages/core-extensions/src/rocketship/host.ts index 33b4086..d995ee6 100644 --- a/packages/core-extensions/src/rocketship/host.ts +++ b/packages/core-extensions/src/rocketship/host.ts @@ -1,8 +1,53 @@ import type { BrowserWindow } from "electron"; -import { desktopCapturer } from "electron"; +import { app, desktopCapturer } from "electron"; +import path from "node:path"; const logger = moonlightHost.getLogger("rocketship"); +function getPatchbay() { + try { + const venmic = require( + path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node") + ) as typeof import("@vencord/venmic"); + const patchbay = new venmic.PatchBay(); + return patchbay; + } catch (error) { + logger.error("Failed to load venmic.node:", error); + return null; + } +} + +const patchbay = getPatchbay(); + +// TODO: figure out how to map source to window with venmic +function linkVenmic() { + if (patchbay == null) return false; + + try { + const pid = + app + .getAppMetrics() + .find((proc) => proc.name === "Audio Service") + ?.pid?.toString() ?? ""; + + logger.info("Audio Service PID:", pid); + + patchbay.unlink(); + return patchbay.link({ + exclude: [ + { "application.process.id": pid }, + { "media.class": "Stream/Input/Audio" } + ], + ignore_devices: true, + only_speakers: true, + only_default_speakers: true + }); + } catch (error) { + logger.error("Failed to link venmic:", error); + return false; + } +} + moonlightHost.events.on( "window-created", (window: BrowserWindow, isMainWindow: boolean) => { @@ -13,20 +58,28 @@ moonlightHost.events.on( window.webContents.on("did-finish-load", () => { windowSession.setPermissionRequestHandler( (webContents, permission, callback) => { - logger.debug("windowSession.setPermissionRequestHandler", permission); if (permission === "media") callback(true); } ); + windowSession.setPermissionCheckHandler((webContents, permission) => { + if (permission === "media") return true; + return false; + }); // @ts-expect-error these types ancient windowSession.setDisplayMediaRequestHandler( (request: any, callback: any) => { - logger.debug("windowSession.setDisplayMediaRequestHandler", request); + const linked = linkVenmic(); desktopCapturer .getSources({ types: ["screen", "window"] }) .then((sources) => { - logger.debug("desktopCapturer.getSources", sources); - callback({ video: sources[0], audio: "loopback" }); + //logger.debug("desktopCapturer.getSources", sources); + logger.debug("Linked to venmic:", linked); + + callback({ + video: sources[0], + audio: "loopback" + }); }); }, { useSystemPicker: true } diff --git a/packages/core-extensions/src/rocketship/index.ts b/packages/core-extensions/src/rocketship/index.ts index d392425..1fa11fb 100644 --- a/packages/core-extensions/src/rocketship/index.ts +++ b/packages/core-extensions/src/rocketship/index.ts @@ -1,5 +1,59 @@ import { Patch } from "@moonlight-mod/types"; +const logger = moonlight.getLogger("rocketship"); +const getDisplayMediaOrig = navigator.mediaDevices.getDisplayMedia; + +async function getVenmicStream() { + try { + const devices = await navigator.mediaDevices.enumerateDevices(); + logger.debug("Devices:", devices); + + // This isn't vencord :( + const id = devices.find((device) => device.label === "vencord-screen-share") + ?.deviceId; + if (!id) return null; + logger.debug("Got venmic device ID:", id); + + const stream = await navigator.mediaDevices.getUserMedia({ + audio: { + deviceId: { + exact: id + }, + autoGainControl: false, + echoCancellation: false, + noiseSuppression: false + } + }); + + return stream.getAudioTracks(); + } catch (error) { + logger.warn("Failed to get venmic stream:", error); + return null; + } +} + +navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect( + options +) { + const orig = await getDisplayMediaOrig.call(this, options); + + const venmic = await getVenmicStream(); + logger.debug("venmic", venmic); + if (venmic != null) { + // venmic will be proxying all audio, so we need to remove the original + // tracks to not cause overlap + for (const track of orig.getAudioTracks()) { + orig.removeTrack(track); + } + + for (const track of venmic) { + orig.addTrack(track); + } + } + + return orig; +}; + export const patches: Patch[] = [ { find: "RustAudioDeviceModule", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f02261..6ccf3ab 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: '@moonlight-mod/types': specifier: workspace:* version: link:../types + '@vencord/venmic': + specifier: ^6.1.0 + version: 6.1.0 packages/injector: dependencies: @@ -423,6 +426,11 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} + '@vencord/venmic@6.1.0': + resolution: {integrity: sha512-YiCtzml/W8tYbGhu3jm5jfbbEnl2slKKARNK0jO+8qV979k9eFnfIRTxvhMN/SWq1h8ZNJdXVwvXpffQwq0RuA==} + engines: {node: '>=14.15'} + os: [linux] + '@zenfs/core@1.0.2': resolution: {integrity: sha512-LMTD4ntn6Ag1y+IeOSVykDDvYC12dsGFtsX8M/54OQrLs7v+YnX4bpo0o2osbm8XFmU2MTNMX/G3PLsvzgWzrg==} engines: {node: '>= 16'} @@ -459,6 +467,14 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} + aproba@2.0.0: + resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} + + are-we-there-yet@3.0.1: + resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -495,10 +511,16 @@ packages: asynciterator.prototype@1.0.0: resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} + axios@1.7.7: + resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -541,6 +563,19 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} + chownr@2.0.0: + resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} + engines: {node: '>=10'} + + cliui@8.0.1: + resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} + engines: {node: '>=12'} + + cmake-js@7.3.0: + resolution: {integrity: sha512-dXs2zq9WxrV87bpJ+WbnGKv8WUBXDw8blNiwNHoRe/it+ptscxhQHKB1SJXa1w+kocLMeP28Tk4/eTCezg4o+w==} + engines: {node: '>= 14.15.0'} + hasBin: true + color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -548,9 +583,20 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + color-support@1.1.3: + resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} + hasBin: true + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + console-control-strings@1.1.0: + resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} + cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -570,6 +616,10 @@ packages: supports-color: optional: true + deep-extend@0.6.0: + resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} + engines: {node: '>=4.0.0'} + deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -593,6 +643,13 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + + delegates@1.0.0: + resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} + dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -605,6 +662,9 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} + emoji-regex@8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + es-abstract@1.22.3: resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} engines: {node: '>= 0.4'} @@ -631,6 +691,10 @@ packages: engines: {node: '>=12'} hasBin: true + escalade@3.2.0: + resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} + engines: {node: '>=6'} + escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -672,6 +736,7 @@ packages: eslint@8.55.0: resolution: {integrity: sha512-iyUUAM0PCKj5QpwGfmCAG9XXbZCWsqP/eWAWrG/W0umvjuLRBECwSFdt+rCntju0xEH7teIABPwXpahftIaTdA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -754,9 +819,30 @@ packages: flatted@3.2.9: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} + follow-redirects@1.15.9: + resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + + fs-extra@11.2.0: + resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} + engines: {node: '>=14.14'} + + fs-minipass@2.1.0: + resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} + engines: {node: '>= 8'} + fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -770,6 +856,15 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} + gauge@4.0.4: + resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + + get-caller-file@2.0.5: + resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} + engines: {node: 6.* || 8.* || >= 10.*} + get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} @@ -808,6 +903,9 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} + graceful-fs@4.2.11: + resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} + graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -833,6 +931,9 @@ packages: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} + has-unicode@2.0.1: + resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} + hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} @@ -872,6 +973,9 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} + ini@1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + internal-slot@1.0.6: resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} engines: {node: '>= 0.4'} @@ -918,6 +1022,10 @@ packages: is-finalizationregistry@1.0.2: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} + is-fullwidth-code-point@3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -1018,6 +1126,9 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} + jsonfile@6.1.0: + resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} + jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -1033,6 +1144,9 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1044,6 +1158,9 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} + memory-stream@1.0.0: + resolution: {integrity: sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww==} + merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -1059,6 +1176,14 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} + mime-db@1.52.0: + resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} + engines: {node: '>= 0.6'} + + mime-types@2.1.35: + resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} + engines: {node: '>= 0.6'} + mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1074,12 +1199,39 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} + minimist@1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + + minipass@3.3.6: + resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} + engines: {node: '>=8'} + + minipass@5.0.0: + resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} + engines: {node: '>=8'} + + minizlib@2.1.2: + resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} + engines: {node: '>= 8'} + + mkdirp@1.0.4: + resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} + engines: {node: '>=10'} + hasBin: true + ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} + node-addon-api@8.1.0: + resolution: {integrity: sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==} + engines: {node: ^18 || ^20 || >= 21} + + node-api-headers@1.3.0: + resolution: {integrity: sha512-8Bviwtw4jNhv0B2qDjj4M5e6GyAuGtxsmZTrFJu3S3Z0+oHwIgSUdIKkKJmZd+EbMo7g3v4PLBbrjxwmZOqMBg==} + npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -1088,6 +1240,11 @@ packages: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + npmlog@6.0.2: + resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + deprecated: This package is no longer supported. + object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1179,6 +1336,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pkg-prebuilds@0.2.1: + resolution: {integrity: sha512-FdOlDiRqRL7i9aYzQflhGWCoiJf/8u6Qgzq48gKsRDYejtfjvGb1U5QGSzllcqpNg2a8Swx/9fMgtuVefwU+zw==} + engines: {node: '>= 14.15.0'} + hasBin: true + prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1199,6 +1361,9 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1206,9 +1371,17 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} + rc@1.2.8: + resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} + hasBin: true + react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + readable-stream@4.5.2: resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1221,6 +1394,10 @@ packages: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} engines: {node: '>= 0.4'} + require-directory@2.1.1: + resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} + engines: {node: '>=0.10.0'} + resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1267,6 +1444,9 @@ packages: engines: {node: '>=10'} hasBin: true + set-blocking@2.0.0: + resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} + set-function-length@1.1.1: resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} engines: {node: '>= 0.4'} @@ -1296,6 +1476,10 @@ packages: standalone-electron-types@1.0.0: resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} + string-width@4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} @@ -1324,6 +1508,10 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} + strip-json-comments@2.0.1: + resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} + engines: {node: '>=0.10.0'} + strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1340,6 +1528,10 @@ packages: resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==} engines: {node: ^14.18.0 || >=16.0.0} + tar@6.2.1: + resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} + engines: {node: '>=10'} + text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1394,6 +1586,10 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} + universalify@2.0.1: + resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} + engines: {node: '>= 10.0.0'} + untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -1401,6 +1597,12 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} + url-join@4.0.1: + resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} + + util-deprecate@1.0.2: + resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} + utilium@0.7.1: resolution: {integrity: sha512-2ocvTkI7U8LERmwxL0LhFUvEfN66UqcjF6tMiURvUwSyU7U1QC9gST+3iSUSiGccFfnP3f2EXwHNXOnOzx+lAg==} @@ -1423,12 +1625,31 @@ packages: engines: {node: '>= 8'} hasBin: true + wide-align@1.1.5: + resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} + + wrap-ansi@7.0.0: + resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} + engines: {node: '>=10'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} + y18n@5.0.8: + resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} + engines: {node: '>=10'} + yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} + yargs-parser@21.1.1: + resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} + engines: {node: '>=12'} + + yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1697,6 +1918,14 @@ snapshots: '@ungap/structured-clone@1.2.0': {} + '@vencord/venmic@6.1.0': + dependencies: + cmake-js: 7.3.0 + node-addon-api: 8.1.0 + pkg-prebuilds: 0.2.1 + transitivePeerDependencies: + - supports-color + '@zenfs/core@1.0.2': dependencies: '@types/node': 20.16.10 @@ -1734,6 +1963,13 @@ snapshots: dependencies: color-convert: 2.0.1 + aproba@2.0.0: {} + + are-we-there-yet@3.0.1: + dependencies: + delegates: 1.0.0 + readable-stream: 3.6.2 + argparse@2.0.1: {} array-buffer-byte-length@1.0.0: @@ -1789,8 +2025,18 @@ snapshots: dependencies: has-symbols: 1.0.3 + asynckit@0.4.0: {} + available-typed-arrays@1.0.5: {} + axios@1.7.7(debug@4.3.4): + dependencies: + follow-redirects: 1.15.9(debug@4.3.4) + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -1836,14 +2082,48 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 + chownr@2.0.0: {} + + cliui@8.0.1: + dependencies: + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrap-ansi: 7.0.0 + + cmake-js@7.3.0: + dependencies: + axios: 1.7.7(debug@4.3.4) + debug: 4.3.4 + fs-extra: 11.2.0 + lodash.isplainobject: 4.0.6 + memory-stream: 1.0.0 + node-api-headers: 1.3.0 + npmlog: 6.0.2 + rc: 1.2.8 + semver: 7.5.4 + tar: 6.2.1 + url-join: 4.0.1 + which: 2.0.2 + yargs: 17.7.2 + transitivePeerDependencies: + - supports-color + color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} + color-support@1.1.3: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + concat-map@0.0.1: {} + console-control-strings@1.1.0: {} + cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -1858,6 +2138,8 @@ snapshots: dependencies: ms: 2.1.2 + deep-extend@0.6.0: {} + deep-is@0.1.4: {} default-browser-id@3.0.0: @@ -1886,6 +2168,10 @@ snapshots: has-property-descriptors: 1.0.1 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + + delegates@1.0.0: {} + dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -1898,6 +2184,8 @@ snapshots: dependencies: esutils: 2.0.3 + emoji-regex@8.0.0: {} + es-abstract@1.22.3: dependencies: array-buffer-byte-length: 1.0.0 @@ -2000,6 +2288,8 @@ snapshots: '@esbuild/win32-ia32': 0.19.3 '@esbuild/win32-x64': 0.19.3 + escalade@3.2.0: {} + escape-string-regexp@4.0.0: {} eslint-config-prettier@9.1.0(eslint@8.55.0): @@ -2179,10 +2469,30 @@ snapshots: flatted@3.2.9: {} + follow-redirects@1.15.9(debug@4.3.4): + optionalDependencies: + debug: 4.3.4 + for-each@0.3.3: dependencies: is-callable: 1.2.7 + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + + fs-extra@11.2.0: + dependencies: + graceful-fs: 4.2.11 + jsonfile: 6.1.0 + universalify: 2.0.1 + + fs-minipass@2.1.0: + dependencies: + minipass: 3.3.6 + fs.realpath@1.0.0: {} function-bind@1.1.2: {} @@ -2196,6 +2506,19 @@ snapshots: functions-have-names@1.2.3: {} + gauge@4.0.4: + dependencies: + aproba: 2.0.0 + color-support: 1.1.3 + console-control-strings: 1.1.0 + has-unicode: 2.0.1 + signal-exit: 3.0.7 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wide-align: 1.1.5 + + get-caller-file@2.0.5: {} + get-intrinsic@1.2.2: dependencies: function-bind: 1.1.2 @@ -2248,6 +2571,8 @@ snapshots: dependencies: get-intrinsic: 1.2.2 + graceful-fs@4.2.11: {} + graphemer@1.4.0: {} has-bigints@1.0.2: {} @@ -2266,6 +2591,8 @@ snapshots: dependencies: has-symbols: 1.0.3 + has-unicode@2.0.1: {} + hasown@2.0.0: dependencies: function-bind: 1.1.2 @@ -2294,6 +2621,8 @@ snapshots: inherits@2.0.4: {} + ini@1.3.8: {} + internal-slot@1.0.6: dependencies: get-intrinsic: 1.2.2 @@ -2339,6 +2668,8 @@ snapshots: dependencies: call-bind: 1.0.5 + is-fullwidth-code-point@3.0.0: {} + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.0 @@ -2429,6 +2760,12 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} + jsonfile@6.1.0: + dependencies: + universalify: 2.0.1 + optionalDependencies: + graceful-fs: 4.2.11 + jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.7 @@ -2449,6 +2786,8 @@ snapshots: dependencies: p-locate: 5.0.0 + lodash.isplainobject@4.0.6: {} + lodash.merge@4.6.2: {} loose-envify@1.4.0: @@ -2459,6 +2798,10 @@ snapshots: dependencies: yallist: 4.0.0 + memory-stream@1.0.0: + dependencies: + readable-stream: 3.6.2 + merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -2470,6 +2813,12 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 + mime-db@1.52.0: {} + + mime-types@2.1.35: + dependencies: + mime-db: 1.52.0 + mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -2482,10 +2831,29 @@ snapshots: dependencies: brace-expansion: 2.0.1 + minimist@1.2.8: {} + + minipass@3.3.6: + dependencies: + yallist: 4.0.0 + + minipass@5.0.0: {} + + minizlib@2.1.2: + dependencies: + minipass: 3.3.6 + yallist: 4.0.0 + + mkdirp@1.0.4: {} + ms@2.1.2: {} natural-compare@1.4.0: {} + node-addon-api@8.1.0: {} + + node-api-headers@1.3.0: {} + npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -2494,6 +2862,13 @@ snapshots: dependencies: path-key: 4.0.0 + npmlog@6.0.2: + dependencies: + are-we-there-yet: 3.0.1 + console-control-strings: 1.1.0 + gauge: 4.0.4 + set-blocking: 2.0.0 + object-assign@4.1.1: {} object-inspect@1.13.1: {} @@ -2586,6 +2961,10 @@ snapshots: picomatch@2.3.1: {} + pkg-prebuilds@0.2.1: + dependencies: + yargs: 17.7.2 + prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -2602,12 +2981,27 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} queue-microtask@1.2.3: {} + rc@1.2.8: + dependencies: + deep-extend: 0.6.0 + ini: 1.3.8 + minimist: 1.2.8 + strip-json-comments: 2.0.1 + react-is@16.13.1: {} + readable-stream@3.6.2: + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + readable-stream@4.5.2: dependencies: abort-controller: 3.0.0 @@ -2631,6 +3025,8 @@ snapshots: define-properties: 1.2.1 set-function-name: 2.0.1 + require-directory@2.1.1: {} + resolve-from@4.0.0: {} resolve@2.0.0-next.5: @@ -2676,6 +3072,8 @@ snapshots: dependencies: lru-cache: 6.0.0 + set-blocking@2.0.0: {} + set-function-length@1.1.1: dependencies: define-data-property: 1.1.1 @@ -2709,6 +3107,12 @@ snapshots: dependencies: '@types/node': 18.17.17 + string-width@4.2.3: + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + string.prototype.matchall@4.0.10: dependencies: call-bind: 1.0.5 @@ -2751,6 +3155,8 @@ snapshots: strip-final-newline@3.0.0: {} + strip-json-comments@2.0.1: {} + strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -2764,6 +3170,15 @@ snapshots: '@pkgr/utils': 2.4.2 tslib: 2.6.2 + tar@6.2.1: + dependencies: + chownr: 2.0.0 + fs-minipass: 2.1.0 + minipass: 5.0.0 + minizlib: 2.1.2 + mkdirp: 1.0.4 + yallist: 4.0.0 + text-table@0.2.0: {} titleize@3.0.0: {} @@ -2822,12 +3237,18 @@ snapshots: undici-types@6.19.8: {} + universalify@2.0.1: {} + untildify@4.0.0: {} uri-js@4.4.1: dependencies: punycode: 2.3.1 + url-join@4.0.1: {} + + util-deprecate@1.0.2: {} + utilium@0.7.1: dependencies: eventemitter3: 5.0.1 @@ -2874,8 +3295,32 @@ snapshots: dependencies: isexe: 2.0.0 + wide-align@1.1.5: + dependencies: + string-width: 4.2.3 + + wrap-ansi@7.0.0: + dependencies: + ansi-styles: 4.3.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + wrappy@1.0.2: {} + y18n@5.0.8: {} + yallist@4.0.0: {} + yargs-parser@21.1.1: {} + + yargs@17.7.2: + dependencies: + cliui: 8.0.1 + escalade: 3.2.0 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + yocto-queue@0.1.0: {} From a5cae1745bbf149fce1b8813e94d13559feffa9a Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 6 Oct 2024 20:43:43 -0600 Subject: [PATCH 15/73] markdown: fix patches --- packages/core-extensions/src/markdown/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-extensions/src/markdown/index.ts b/packages/core-extensions/src/markdown/index.ts index fb73a6b..bc5a4bb 100644 --- a/packages/core-extensions/src/markdown/index.ts +++ b/packages/core-extensions/src/markdown/index.ts @@ -10,7 +10,7 @@ export const patches: Patch[] = [ `=require("markdown_markdown")._addRules({newline:${rules}}),${RULES}=(0,` }, { - match: /(?<=var (.{1,2})={RULES:.+?})/, + match: /(?<=;(.{1,2}\.Z)={RULES:.+?})/, replacement: (_, rulesets) => `;require("markdown_markdown")._applyRulesetBlacklist(${rulesets});` } @@ -26,7 +26,7 @@ export const patches: Patch[] = [ }, { match: - /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"link":{(.+?)}default:/, + /(originalMatch:.}=(.);)(.+?)case"emoticon":(return .+?;)(.+?)case"subtext":{(.+?)}default:/, replacement: ( _, start, From d69492ad888d47d75f647a6afe915b26175538b8 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Sun, 6 Oct 2024 20:45:45 -0600 Subject: [PATCH 16/73] fix a niche deprecation warning with eslint caused by working dir being in home --- .eslintrc.json | 1 + 1 file changed, 1 insertion(+) diff --git a/.eslintrc.json b/.eslintrc.json index caa830e..66afb63 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -1,4 +1,5 @@ { + "root": true, "extends": [ "eslint:recommended", "plugin:@typescript-eslint/recommended", From 122d5618b9f5f92b25e6230f38f4e40217866242 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 7 Oct 2024 13:52:02 +0200 Subject: [PATCH 17/73] core-ext/contextMenu: revert fixed patches This reverts the patches from commit b73b2d7ceb97bc9cce88e5395fefa2608635b08f, whatever warranted that commit has been reverted and is now broken on both stable and canary. --- packages/core-extensions/src/contextMenu/index.tsx | 3 ++- .../src/contextMenu/webpackModules/evilMenu.ts | 11 ++++++----- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/core-extensions/src/contextMenu/index.tsx b/packages/core-extensions/src/contextMenu/index.tsx index 89461f8..ce6ffe0 100644 --- a/packages/core-extensions/src/contextMenu/index.tsx +++ b/packages/core-extensions/src/contextMenu/index.tsx @@ -5,7 +5,8 @@ export const patches: Patch[] = [ find: "Menu API only allows Items and groups of Items as children.", replace: [ { - match: /(?<=let{navId[^}]+?}=(.),(.)=.\(.\))/, + match: + /(?<=let{navId[^}]+?}=(.),(.)=function .\(.\){.+(?=,.=function))/, replacement: (_, props, items) => `,__contextMenu=!${props}.__contextMenu_evilMenu&&require("contextMenu_contextMenu")._patchMenu(${props}, ${items})` } diff --git a/packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts b/packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts index 2a7f531..4b009f3 100644 --- a/packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts +++ b/packages/core-extensions/src/contextMenu/webpackModules/evilMenu.ts @@ -6,10 +6,8 @@ let code = "Menu API only allows Items and groups of Items as children." )[0].id ].toString(); -code = code.replace( - /onSelect:(.)}=(.),.=(.\(.\)),/, - `onSelect:$1}=$2;return $3;let ` -); +code = code.replace(/,.=(?=function .\(.\){.+?,.=function)/, ";return "); +code = code.replace(/,(?=__contextMenu)/, ";let "); const mod = new Function( "module", "exports", @@ -18,7 +16,10 @@ const mod = new Function( ); const exp: any = {}; mod({}, exp, require); -const Menu = spacepack.findFunctionByStrings(exp, "isUsingKeyboardNavigation")!; +const Menu = spacepack.findFunctionByStrings( + exp, + "Menu API only allows Items and groups of Items as children." +)!; module.exports = (el: any) => { return Menu({ children: el, From 0e921cd584818815607606efff4e422e5dd94c85 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 7 Oct 2024 14:07:25 +0200 Subject: [PATCH 18/73] core-ext/quietLoggers: fix remaining patch --- packages/core-extensions/src/quietLoggers/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/quietLoggers/index.ts b/packages/core-extensions/src/quietLoggers/index.ts index b0f77d4..a97d991 100644 --- a/packages/core-extensions/src/quietLoggers/index.ts +++ b/packages/core-extensions/src/quietLoggers/index.ts @@ -8,10 +8,10 @@ const notXssDefensesOnly = () => // that end up causing syntax errors by the normal patch const loggerFixes: Patch[] = [ { - find: '"./ggsans-800-extrabolditalic.woff2":', + find: '"./gg-sans/ggsans-800-extrabolditalic.woff2":', replace: { - match: /throw .+?,./, - replacement: "return{}" + match: /var .=Error.+?;throw .+?,./, + replacement: "" } }, { From b2e49f170e607f0b5a9711f21ed662c82871c2e1 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 7 Oct 2024 14:15:01 +0200 Subject: [PATCH 19/73] core-ext/disableSentry: patch out call of addBreadcrumbs that bypasses the stub --- packages/core-extensions/src/disableSentry/index.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/core-extensions/src/disableSentry/index.ts b/packages/core-extensions/src/disableSentry/index.ts index 4cb63ae..93809a2 100644 --- a/packages/core-extensions/src/disableSentry/index.ts +++ b/packages/core-extensions/src/disableSentry/index.ts @@ -11,12 +11,11 @@ export const patches: Patch[] = [ } }, { - find: "window.DiscordSentry.addBreadcrumb", + find: "this._sentryUtils=", replace: { type: PatchReplaceType.Normal, - match: /Z:function\(\){return .}/, - replacement: - 'default:function(){return (...args)=>{moonlight.getLogger("disableSentry").debug("Sentry calling addBreadcrumb passthrough:", ...args);}}' + match: /(?<=this._sentryUtils=)./, + replacement: "undefined" } }, { From d13cb7e34700f999c54df442cea6da71dfb122f9 Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 09:07:38 -0400 Subject: [PATCH 20/73] Implement update checking, no UI for now --- .github/workflows/nightly.yml | 2 + .github/workflows/release.yml | 2 + build.mjs | 7 ++- packages/browser/src/index.ts | 6 ++ packages/core-extensions/src/moonbase/node.ts | 59 ++++++++++++++++++- .../core-extensions/src/moonbase/types.ts | 1 + .../src/moonbase/webpackModules/stores.ts | 5 ++ packages/injector/src/index.ts | 3 + packages/node-preload/src/index.ts | 12 +++- packages/types/src/globals.ts | 10 ++++ packages/types/src/index.ts | 2 + packages/web-preload/src/index.ts | 3 + 12 files changed, 108 insertions(+), 4 deletions(-) diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 83d2270..9eb6b04 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -31,6 +31,8 @@ jobs: - name: Build moonlight env: NODE_ENV: production + MOONLIGHT_BRANCH: nightly + MOONLIGHT_VERSION: ${{ github.sha }} run: pnpm run build - name: Write ref/commit to file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 849626f..828d69d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -29,6 +29,8 @@ jobs: - name: Build moonlight env: NODE_ENV: production + MOONLIGHT_BRANCH: stable + MOONLIGHT_VERSION: ${{ github.ref_name }} run: pnpm run build - name: Create archive run: | diff --git a/build.mjs b/build.mjs index 0b3cda5..5cf069e 100644 --- a/build.mjs +++ b/build.mjs @@ -16,6 +16,9 @@ const watch = process.argv.includes("--watch"); const browser = process.argv.includes("--browser"); const mv2 = process.argv.includes("--mv2"); +const buildBranch = process.env.MOONLIGHT_BRANCH ?? "dev"; +const buildVersion = process.env.MOONLIGHT_VERSION ?? "dev"; + const external = [ "electron", "fs", @@ -96,7 +99,9 @@ async function build(name, entry) { const define = { MOONLIGHT_ENV: `"${name}"`, - MOONLIGHT_PROD: prod.toString() + MOONLIGHT_PROD: prod.toString(), + MOONLIGHT_BRANCH: `"${buildBranch}"`, + MOONLIGHT_VERSION: `"${buildVersion}"` }; for (const iterName of [ diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 75795c2..1ff50c5 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -103,6 +103,9 @@ window._moonlightBrowserInit = async () => { processedExtensions, nativesCache: {}, + version: MOONLIGHT_VERSION, + branch: MOONLIGHT_BRANCH, + getConfig, getConfigOption: (ext: string, name: string) => { const config = getConfig(ext); @@ -116,6 +119,9 @@ window._moonlightBrowserInit = async () => { return new Logger(id); }, + getMoonlightDir() { + return "/"; + }, getExtensionDir: (ext: string) => { return `/extensions/${ext}`; }, diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 22b30f7..5c1c146 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -6,12 +6,62 @@ import { repoUrlFile } from "@moonlight-mod/types/constants"; const logger = moonlightNode.getLogger("moonbase"); +const updateTimerFile = path.join( + moonlightNode.getMoonlightDir(), + ".moonbase-update-timer" +); +const updateTimerInterval = 12 * 60 * 60 * 1000; + +const githubRepo = "moonlight-mod/moonlight"; +const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; +const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; + +async function checkForMoonlightUpdate() { + const updateTimerStr = fs.existsSync(updateTimerFile) + ? fs.readFileSync(updateTimerFile, "utf-8") + : "0"; + let updateTimer = parseInt(updateTimerStr); + if (isNaN(updateTimer)) updateTimer = 0; + + const shouldCheck = Date.now() - updateTimer > updateTimerInterval; + if (!shouldCheck) return null; + + fs.writeFileSync(updateTimerFile, Date.now().toString()); + + if (moonlightNode.branch === "stable") { + const req = await fetch( + `https://api.github.com/repos/${githubRepo}/releases/latest`, + { + headers: { + "User-Agent": userAgent + } + } + ); + const json: { name: string } = await req.json(); + return json.name !== moonlightNode.version ? json.name : null; + } else if (moonlightNode.branch === "nightly") { + const req = await fetch(nightlyRefUrl, { + headers: { + "User-Agent": userAgent + } + }); + const ref = (await req.text()).split("\n")[0]; + return ref !== moonlightNode.version ? ref : null; + } + + return null; +} + async function fetchRepositories(repos: string[]) { const ret: Record = {}; for (const repo of repos) { try { - const req = await fetch(repo); + const req = await fetch(repo, { + headers: { + "User-Agent": userAgent + } + }); const json = await req.json(); ret[repo] = json; } catch (e) { @@ -27,7 +77,11 @@ async function installExtension( url: string, repo: string ) { - const req = await fetch(url); + const req = await fetch(url, { + headers: { + "User-Agent": userAgent + } + }); const dir = moonlightNode.getExtensionDir(manifest.id); // remake it in case of updates @@ -63,6 +117,7 @@ function getExtensionConfig(id: string, key: string): any { } const exports: MoonbaseNatives = { + checkForMoonlightUpdate, fetchRepositories, installExtension, deleteExtension, diff --git a/packages/core-extensions/src/moonbase/types.ts b/packages/core-extensions/src/moonbase/types.ts index 55a0553..2b14937 100644 --- a/packages/core-extensions/src/moonbase/types.ts +++ b/packages/core-extensions/src/moonbase/types.ts @@ -1,6 +1,7 @@ import { DetectedExtension, ExtensionManifest } from "types/src"; export type MoonbaseNatives = { + checkForMoonlightUpdate(): Promise; fetchRepositories( repos: string[] ): Promise>; diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 36e7360..356ab55 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -16,6 +16,11 @@ let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); if (window._moonlightBrowserFS != null) { const browserFS = window._moonlightBrowserFS!; natives = { + checkForMoonlightUpdate: async () => { + // TODO + return null; + }, + fetchRepositories: async (repos) => { const ret: Record = {}; diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index 562a5ec..eee0f45 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -183,6 +183,9 @@ export async function inject(asarPath: string) { dependencyGraph: new Map() }, + version: MOONLIGHT_VERSION, + branch: MOONLIGHT_BRANCH, + getConfig, getConfigOption: (ext: string, name: string) => { const config = getConfig(ext); diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index 85817b7..f7a2030 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -5,7 +5,10 @@ import path from "path"; import { readConfig, writeConfig } from "@moonlight-mod/core/config"; import { constants } from "@moonlight-mod/types"; import { getExtensions } from "@moonlight-mod/core/extension"; -import { getExtensionsPath } from "@moonlight-mod/core/util/data"; +import { + getExtensionsPath, + getMoonlightDir +} from "@moonlight-mod/core/util/data"; import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { loadExtensions, @@ -29,6 +32,10 @@ async function injectGlobals() { extensions, processedExtensions, nativesCache: {}, + + version: MOONLIGHT_VERSION, + branch: MOONLIGHT_BRANCH, + getConfig, getConfigOption: (ext: string, name: string) => { const config = getConfig(ext); @@ -42,6 +49,9 @@ async function injectGlobals() { return new Logger(id); }, + getMoonlightDir() { + return getMoonlightDir(); + }, getExtensionDir: (ext: string) => { const extPath = getExtensionsPath(); return path.join(extPath, ext); diff --git a/packages/types/src/globals.ts b/packages/types/src/globals.ts index 08a438d..b639481 100644 --- a/packages/types/src/globals.ts +++ b/packages/types/src/globals.ts @@ -18,6 +18,9 @@ export type MoonlightHost = { extensions: DetectedExtension[]; processedExtensions: ProcessedExtensions; + version: string; + branch: string; + getConfig: (ext: string) => ConfigExtension["config"]; getConfigOption: (ext: string, name: string) => T | undefined; getLogger: (id: string) => Logger; @@ -29,11 +32,15 @@ export type MoonlightNode = { processedExtensions: ProcessedExtensions; nativesCache: Record; + version: string; + branch: string; + getConfig: (ext: string) => ConfigExtension["config"]; getConfigOption: (ext: string, name: string) => T | undefined; getNatives: (ext: string) => any | undefined; getLogger: (id: string) => Logger; + getMoonlightDir: () => string; getExtensionDir: (ext: string) => string; writeConfig: (config: Config) => void; }; @@ -70,6 +77,9 @@ export type MoonlightWeb = { registerWebpackModule: (module: IdentifiedWebpackModule) => void; }; + version: string; + branch: string; + getConfig: (ext: string) => ConfigExtension["config"]; getConfigOption: (ext: string, name: string) => T | undefined; getNatives: (ext: string) => any | undefined; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 5930b8f..aa4cf2b 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -30,6 +30,8 @@ declare global { const MOONLIGHT_NODE_PRELOAD: boolean; const MOONLIGHT_WEB_PRELOAD: boolean; const MOONLIGHT_BROWSER: boolean; + const MOONLIGHT_BRANCH: string; + const MOONLIGHT_VERSION: string; var moonlightHost: MoonlightHost; var moonlightNode: MoonlightNode; diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index aa0b063..f2b4a9e 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -30,6 +30,9 @@ async function load() { registerWebpackModule }, + version: MOONLIGHT_VERSION, + branch: MOONLIGHT_BRANCH, + getConfig: moonlightNode.getConfig.bind(moonlightNode), getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), getNatives: moonlightNode.getNatives.bind(moonlightNode), From ad6a0e80863088115de15d67f6e06f4df44cdde5 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 7 Oct 2024 15:44:15 +0200 Subject: [PATCH 21/73] injector: declarative url blocking --- .../core-extensions/src/disableSentry/host.ts | 17 -------- .../src/disableSentry/manifest.json | 8 +++- packages/core-extensions/src/noTrack/host.ts | 15 ------- .../core-extensions/src/noTrack/manifest.json | 6 ++- packages/injector/src/index.ts | 40 +++++++++++++++++++ packages/node-preload/src/index.ts | 11 +++-- packages/types/src/constants.ts | 1 + packages/types/src/extension.ts | 2 + 8 files changed, 63 insertions(+), 37 deletions(-) delete mode 100644 packages/core-extensions/src/noTrack/host.ts diff --git a/packages/core-extensions/src/disableSentry/host.ts b/packages/core-extensions/src/disableSentry/host.ts index 909b247..54548de 100644 --- a/packages/core-extensions/src/disableSentry/host.ts +++ b/packages/core-extensions/src/disableSentry/host.ts @@ -1,6 +1,5 @@ import { join } from "node:path"; import { Module } from "node:module"; -import { BrowserWindow } from "electron"; const logger = moonlightHost.getLogger("disableSentry"); @@ -24,19 +23,3 @@ if (moonlightHost.asarPath !== "moonlightDesktop") { logger.error("Failed to stub Sentry host side:", err); } } - -moonlightHost.events.on("window-created", (window: BrowserWindow) => { - window.webContents.session.webRequest.onBeforeRequest( - { - urls: [ - "https://*.sentry.io/*", - "https://*.discord.com/error-reporting-proxy/*", - "https://discord.com/assets/sentry.*.js", - "https://*.discord.com/assets/sentry.*.js" - ] - }, - function (details, callback) { - callback({ cancel: true }); - } - ); -}); diff --git a/packages/core-extensions/src/disableSentry/manifest.json b/packages/core-extensions/src/disableSentry/manifest.json index 3e91eda..0f8c815 100644 --- a/packages/core-extensions/src/disableSentry/manifest.json +++ b/packages/core-extensions/src/disableSentry/manifest.json @@ -6,5 +6,11 @@ "tagline": "Turns off Discord's error reporting systems", "authors": ["Cynosphere", "NotNite"], "tags": ["privacy"] - } + }, + "blocked": [ + "https://*.sentry.io/*", + "https://*.discord.com/error-reporting-proxy/*", + "https://discord.com/assets/sentry.*.js", + "https://*.discord.com/assets/sentry.*.js" + ] } diff --git a/packages/core-extensions/src/noTrack/host.ts b/packages/core-extensions/src/noTrack/host.ts deleted file mode 100644 index f24909f..0000000 --- a/packages/core-extensions/src/noTrack/host.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { BrowserWindow } from "electron"; - -moonlightHost.events.on("window-created", (window: BrowserWindow) => { - window.webContents.session.webRequest.onBeforeRequest( - { - urls: [ - "https://*.discord.com/api/v*/science", - "https://*.discord.com/api/v*/metrics" - ] - }, - function (details, callback) { - callback({ cancel: true }); - } - ); -}); diff --git a/packages/core-extensions/src/noTrack/manifest.json b/packages/core-extensions/src/noTrack/manifest.json index bff28f4..f9fc268 100644 --- a/packages/core-extensions/src/noTrack/manifest.json +++ b/packages/core-extensions/src/noTrack/manifest.json @@ -6,5 +6,9 @@ "tagline": "Disables /api/science and analytics", "authors": ["Cynosphere", "NotNite"], "tags": ["privacy"] - } + }, + "blocked": [ + "https://*.discord.com/api/v*/science", + "https://*.discord.com/api/v*/metrics" + ] } diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index 562a5ec..6563836 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -21,6 +21,7 @@ const logger = new Logger("injector"); let oldPreloadPath: string | undefined; let corsAllow: string[] = []; +let blockedUrls: RegExp[] = []; let isMoonlightDesktop = false; let hasOpenAsar = false; let openAsarConfigPreload: string | undefined; @@ -41,6 +42,39 @@ ipcMain.handle(constants.ipcSetCorsList, (_, list) => { corsAllow = list; }); +const reEscapeRegExp = /[\\^$.*+?()[\]{}|]/g; +const reMatchPattern = + /^(?\*|[a-z][a-z0-9+.-]*):\/\/(?.+?)\/(?.+)?$/; + +const escapeRegExp = (s: string) => s.replace(reEscapeRegExp, "\\$&"); +ipcMain.handle(constants.ipcSetBlockedList, (_, list: string[]) => { + // We compile the patterns into a RegExp based on a janky match pattern-like syntax + const compiled = list + .map((pattern) => { + const match = pattern.match(reMatchPattern); + if (!match?.groups) return; + + let regex = ""; + if (match.groups.scheme === "*") regex += ".+?"; + else regex += escapeRegExp(match.groups.scheme); + regex += ":\\/\\/"; + + const parts = match.groups.host.split("."); + if (parts[0] === "*") { + parts.shift(); + regex += "(?:.+?\\.)?"; + } + regex += escapeRegExp(parts.join(".")); + + regex += "\\/" + escapeRegExp(match.groups.path).replace("\\*", ".*?"); + + return new RegExp("^" + regex + "$"); + }) + .filter(Boolean) as RegExp[]; + + blockedUrls = compiled; +}); + function patchCsp(headers: Record) { const directives = [ "style-src", @@ -118,6 +152,12 @@ class BrowserWindow extends ElectronBrowserWindow { } }); + // Allow plugins to block some URLs, + // this is needed because multiple webRequest handlers cannot be registered at once + this.webContents.session.webRequest.onBeforeRequest((details, cb) => { + cb({ cancel: blockedUrls.some((u) => u.test(details.url)) }); + }); + if (hasOpenAsar) { // Remove DOM injections // Settings can still be opened via: diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index 85817b7..6e5d3e6 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -52,9 +52,9 @@ async function injectGlobals() { await loadProcessedExtensions(processedExtensions); contextBridge.exposeInMainWorld("moonlightNode", moonlightNode); - const extCors = moonlightNode.processedExtensions.extensions - .map((x) => x.manifest.cors ?? []) - .flat(); + const extCors = moonlightNode.processedExtensions.extensions.flatMap( + (x) => x.manifest.cors ?? [] + ); for (const repo of moonlightNode.config.repositories) { const url = new URL(repo); @@ -63,6 +63,11 @@ async function injectGlobals() { } ipcRenderer.invoke(constants.ipcSetCorsList, extCors); + + const extBlocked = moonlightNode.processedExtensions.extensions.flatMap( + (e) => e.manifest.blocked ?? [] + ); + ipcRenderer.invoke(constants.ipcSetBlockedList, extBlocked); } async function loadPreload() { diff --git a/packages/types/src/constants.ts b/packages/types/src/constants.ts index 99b7c06..978f4c8 100644 --- a/packages/types/src/constants.ts +++ b/packages/types/src/constants.ts @@ -8,5 +8,6 @@ export const ipcGetAppData = "_moonlight_getAppData"; export const ipcGetIsMoonlightDesktop = "_moonlight_getIsMoonlightDesktop"; export const ipcMessageBox = "_moonlight_messageBox"; export const ipcSetCorsList = "_moonlight_setCorsList"; +export const ipcSetBlockedList = "_moonlight_setBlockedList"; export const apiLevel = 2; diff --git a/packages/types/src/extension.ts b/packages/types/src/extension.ts index 744af71..8eaa777 100644 --- a/packages/types/src/extension.ts +++ b/packages/types/src/extension.ts @@ -47,7 +47,9 @@ export type ExtensionManifest = { incompatible?: string[]; settings?: Record; + cors?: string[]; + blocked?: string[]; }; export enum ExtensionLoadSource { From 253be05c3f0ee8ee6030c740c1b79550b2380ec5 Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 13:29:52 -0400 Subject: [PATCH 22/73] moonbase: Enable extra Markdown features in description --- .../webpackModules/ui/extensions/card.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index a2ad731..cd81394 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -28,6 +28,8 @@ const TabBarClasses = spacepack.findByExports( "tabBarItem", "headerContentWrapper" )[0].exports; +const MarkupClasses = spacepack.findByExports("markup", "inlineFormat")[0] + .exports; export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { const [tab, setTab] = React.useState(ExtensionPage.Info); @@ -213,8 +215,17 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { > {tab === ExtensionPage.Info && } {tab === ExtensionPage.Description && ( - - {MarkupUtils.parse(description ?? "*No description*")} + + {/* @ts-expect-error This type needs to be updated! */} + {MarkupUtils.parse(description ?? "*No description*", true, { + allowHeading: true, + allowLinks: true, + allowList: true + })} )} {tab === ExtensionPage.Settings && } From e940d51d7511bf0f218b31c63c306ff2530438f0 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 7 Oct 2024 20:54:47 +0200 Subject: [PATCH 23/73] ext/moonbase: use unique id as list key --- .../src/moonbase/webpackModules/ui/extensions/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx index 8e887a5..697d3ed 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx @@ -96,7 +96,7 @@ export default function ExtensionsPage() { setSelectedTags={setSelectedTags} /> {filtered.map((ext) => ( - + ))} ); From 9201a8bdaa3884b31aa8c1d01c1ef96952ce3cbb Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Mon, 7 Oct 2024 13:16:01 -0600 Subject: [PATCH 24/73] Add Native Fixes core extension --- .../core-extensions/src/nativeFixes/host.ts | 56 +++++++++++++++++++ .../src/nativeFixes/manifest.json | 47 ++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 packages/core-extensions/src/nativeFixes/host.ts create mode 100644 packages/core-extensions/src/nativeFixes/manifest.json diff --git a/packages/core-extensions/src/nativeFixes/host.ts b/packages/core-extensions/src/nativeFixes/host.ts new file mode 100644 index 0000000..70dabe2 --- /dev/null +++ b/packages/core-extensions/src/nativeFixes/host.ts @@ -0,0 +1,56 @@ +import { app, nativeTheme } from "electron"; + +const config = moonlightHost.getConfig("nativeFixes") ?? {}; + +const enabledFeatures = app.commandLine + .getSwitchValue("enable-features") + .split(","); + +moonlightHost.events.on("window-created", function (browserWindow) { + if (config.devtoolsThemeFix ?? true) { + browserWindow.webContents.on("devtools-opened", () => { + if (!nativeTheme.shouldUseDarkColors) return; + nativeTheme.themeSource = "light"; + setTimeout(() => { + nativeTheme.themeSource = "dark"; + }, 100); + }); + } +}); + +if (config.disableRendererBackgrounding ?? true) { + // Discord already disables UseEcoQoSForBackgroundProcess and some other + // related features + app.commandLine.appendSwitch("disable-renderer-backgrounding"); + app.commandLine.appendSwitch("disable-backgrounding-occluded-windows"); + + // already added on Windows, but not on other operating systems + app.commandLine.appendSwitch("disable-background-timer-throttling"); +} + +if (process.platform === "linux") { + if (config.linuxAutoscroll ?? false) { + app.commandLine.appendSwitch( + "enable-blink-features", + "MiddleClickAutoscroll" + ); + } + + if (config.linuxSpeechDispatcher ?? true) { + app.commandLine.appendSwitch("enable-speech-dispatcher"); + } +} + +// NOTE: Only tested if this appears on Windows, it should appear on all when +// hardware acceleration is disabled +const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing"); +if ((config.vaapi ?? true) && !noAccel) { + enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder"); + if (process.platform === "linux") + enabledFeatures.push("VaapiVideoDecodeLinuxGL"); +} + +app.commandLine.appendSwitch( + "enable-features", + [...new Set(enabledFeatures)].join(",") +); diff --git a/packages/core-extensions/src/nativeFixes/manifest.json b/packages/core-extensions/src/nativeFixes/manifest.json new file mode 100644 index 0000000..2e0e810 --- /dev/null +++ b/packages/core-extensions/src/nativeFixes/manifest.json @@ -0,0 +1,47 @@ +{ + "id": "nativeFixes", + "meta": { + "name": "Native Fixes", + "tagline": "Various configurable fixes for Discord and Electron", + "authors": [ + "Cynosphere", + "adryd" + ], + "tags": [ + "fixes" + ] + }, + "settings": { + "devtoolsThemeFix": { + "displayName": "Devtools Theme Fix", + "description": "Temporary workaround for devtools defaulting to light theme on Electron 32", + "type": "boolean", + "default": true + }, + "disableRendererBackgrounding": { + "displayName": "Disable Renderer Backgrounding", + "description": "This is enabled by default as a power saving measure, but it breaks screensharing and websocket connections fairly often", + "type": "boolean", + "default": true + }, + "linuxAutoscroll": { + "displayName": "Enable middle click autoscroll on Linux", + "description": "Requires manual configuration of your system to disable middle click paste, has no effect on other operating systems", + "type": "boolean", + "default": false + }, + "linuxSpeechDispatcher": { + "displayName": "Enable speech-dispatcher for TTS on Linux", + "description": "Has no effect on other operating systems", + "type": "boolean", + "default": true + }, + "vaapi": { + "displayName": "Enable VAAPI features", + "description": "Provides additional hardware acceleration", + "type": "boolean", + "default": true + } + }, + "apiLevel": 2 +} From 32758ae7d82743c57f7e2ebbabe66c26e5868411 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Mon, 7 Oct 2024 13:22:31 -0600 Subject: [PATCH 25/73] nativeFixes: Use getConfigOption instead --- .../core-extensions/src/nativeFixes/host.ts | 33 +++++++++++++++---- 1 file changed, 26 insertions(+), 7 deletions(-) diff --git a/packages/core-extensions/src/nativeFixes/host.ts b/packages/core-extensions/src/nativeFixes/host.ts index 70dabe2..b0ec298 100644 --- a/packages/core-extensions/src/nativeFixes/host.ts +++ b/packages/core-extensions/src/nativeFixes/host.ts @@ -1,13 +1,14 @@ import { app, nativeTheme } from "electron"; -const config = moonlightHost.getConfig("nativeFixes") ?? {}; - const enabledFeatures = app.commandLine .getSwitchValue("enable-features") .split(","); moonlightHost.events.on("window-created", function (browserWindow) { - if (config.devtoolsThemeFix ?? true) { + if ( + moonlightHost.getConfigOption("nativeFixes", "devtoolsThemeFix") ?? + true + ) { browserWindow.webContents.on("devtools-opened", () => { if (!nativeTheme.shouldUseDarkColors) return; nativeTheme.themeSource = "light"; @@ -18,7 +19,13 @@ moonlightHost.events.on("window-created", function (browserWindow) { } }); -if (config.disableRendererBackgrounding ?? true) { +if ( + moonlightHost.getConfigOption( + "nativeFixes", + "disableRendererBackgrounding" + ) ?? + true +) { // Discord already disables UseEcoQoSForBackgroundProcess and some other // related features app.commandLine.appendSwitch("disable-renderer-backgrounding"); @@ -29,14 +36,23 @@ if (config.disableRendererBackgrounding ?? true) { } if (process.platform === "linux") { - if (config.linuxAutoscroll ?? false) { + if ( + moonlightHost.getConfigOption("nativeFixes", "linuxAutoscroll") ?? + false + ) { app.commandLine.appendSwitch( "enable-blink-features", "MiddleClickAutoscroll" ); } - if (config.linuxSpeechDispatcher ?? true) { + if ( + moonlightHost.getConfigOption( + "nativeFixes", + "linuxSpeechDispatcher" + ) ?? + true + ) { app.commandLine.appendSwitch("enable-speech-dispatcher"); } } @@ -44,7 +60,10 @@ if (process.platform === "linux") { // NOTE: Only tested if this appears on Windows, it should appear on all when // hardware acceleration is disabled const noAccel = app.commandLine.hasSwitch("disable-gpu-compositing"); -if ((config.vaapi ?? true) && !noAccel) { +if ( + (moonlightHost.getConfigOption("nativeFixes", "vaapi") ?? true) && + !noAccel +) { enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder"); if (process.platform === "linux") enabledFeatures.push("VaapiVideoDecodeLinuxGL"); From 0d0c773dd2620eec9baa5682f9be01602f531d00 Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Mon, 7 Oct 2024 21:51:06 +0200 Subject: [PATCH 26/73] ext/moonbase: mark local extensions as such --- .../webpackModules/ui/extensions/card.tsx | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index cd81394..666a951 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -20,7 +20,8 @@ export enum ExtensionPage { import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; -const { DownloadIcon, TrashIcon, CircleWarningIcon } = Components; +const { BeakerIcon, DownloadIcon, TrashIcon, CircleWarningIcon, Tooltip } = + Components; const PanelButton = spacepack.findByCode("Masks.PANEL_BUTTON")[0].exports.Z; const TabBarClasses = spacepack.findByExports( @@ -31,6 +32,10 @@ const TabBarClasses = spacepack.findByExports( const MarkupClasses = spacepack.findByExports("markup", "inlineFormat")[0] .exports; +const BuildOverrideClasses = spacepack.findByExports( + "disabledButtonOverride" +)[0].exports; + export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { const [tab, setTab] = React.useState(ExtensionPage.Info); const [restartNeeded, setRestartNeeded] = React.useState(false); @@ -76,10 +81,21 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) {
- + {ext.manifest?.meta?.name ?? ext.id} + {ext.source.type === ExtensionLoadSource.Developer && ( + + {(props: any) => ( + + )} + + )} {tagline != null && ( From c64f0ad8e72507e774fed94b567a4d2f3d70b13e Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 16:04:28 -0400 Subject: [PATCH 27/73] Update mappings, commit my scripts for local dev work --- .../webpackModules/ui/extensions/card.tsx | 1 - packages/types/package.json | 2 +- packages/web-preload/package.json | 2 +- pnpm-lock.yaml | 14 ++-- scripts/link.js | 70 +++++++++++++++++++ scripts/update.js | 31 ++++++++ 6 files changed, 110 insertions(+), 10 deletions(-) create mode 100644 scripts/link.js create mode 100644 scripts/update.js diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index cd81394..25ecb65 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -220,7 +220,6 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { class={MarkupClasses.markup} style={{ width: "100%" }} > - {/* @ts-expect-error This type needs to be updated! */} {MarkupUtils.parse(description ?? "*No description*", true, { allowHeading: true, allowLinks: true, diff --git a/packages/types/package.json b/packages/types/package.json index 7e56bea..9343f31 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -10,7 +10,7 @@ }, "dependencies": { "@moonlight-mod/lunast": "^1.0.0", - "@moonlight-mod/mappings": "^1.0.0", + "@moonlight-mod/mappings": "^1.0.2", "@moonlight-mod/moonmap": "^1.0.2", "@types/react": "^18.3.10", "csstype": "^3.1.2", diff --git a/packages/web-preload/package.json b/packages/web-preload/package.json index 29be379..1a58408 100644 --- a/packages/web-preload/package.json +++ b/packages/web-preload/package.json @@ -5,7 +5,7 @@ "dependencies": { "@moonlight-mod/core": "workspace:*", "@moonlight-mod/lunast": "^1.0.0", - "@moonlight-mod/mappings": "^1.0.0", + "@moonlight-mod/mappings": "^1.0.2", "@moonlight-mod/moonmap": "^1.0.2", "@moonlight-mod/types": "workspace:*" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f02261..d139ba9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -99,8 +99,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 '@moonlight-mod/mappings': - specifier: ^1.0.0 - version: 1.0.0(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2) + specifier: ^1.0.2 + version: 1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2) '@moonlight-mod/moonmap': specifier: ^1.0.2 version: 1.0.2 @@ -123,8 +123,8 @@ importers: specifier: ^1.0.0 version: 1.0.0 '@moonlight-mod/mappings': - specifier: ^1.0.0 - version: 1.0.0(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2) + specifier: ^1.0.2 + version: 1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2) '@moonlight-mod/moonmap': specifier: ^1.0.2 version: 1.0.2 @@ -304,8 +304,8 @@ packages: '@moonlight-mod/lunast@1.0.0': resolution: {integrity: sha512-kJgf41K12i6/2LbXK97CNO+pNO7ADGh9N4bCQcOPwosocKMcwKHDEZUgPqeihNshY3c3AEW1LiyXjlsl24PdDw==} - '@moonlight-mod/mappings@1.0.0': - resolution: {integrity: sha512-n6ybbTqFxXVKvTHcIGEtCS9208an6BFnTojK56giWE/bjuqq6DvFrk/0+C3ZRDEjgohGf/DuUKc9f0qe9UH6Rg==} + '@moonlight-mod/mappings@1.0.2': + resolution: {integrity: sha512-PjIv4LFyt3j4LyGiokUmJ6a0L5JljoLXjUkixCynLLpNLd660qTcLe8f9tbhOovvD8joqejq+f5oqSo2V4/Vfg==} peerDependencies: '@moonlight-mod/lunast': ^1.0.0 '@moonlight-mod/moonmap': ^1.0.0 @@ -1544,7 +1544,7 @@ snapshots: estree-toolkit: 1.7.8 meriyah: 6.0.1 - '@moonlight-mod/mappings@1.0.0(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2)': + '@moonlight-mod/mappings@1.0.2(@moonlight-mod/lunast@1.0.0)(@moonlight-mod/moonmap@1.0.2)': dependencies: '@moonlight-mod/lunast': 1.0.0 '@moonlight-mod/moonmap': 1.0.2 diff --git a/scripts/link.js b/scripts/link.js new file mode 100644 index 0000000..293e97c --- /dev/null +++ b/scripts/link.js @@ -0,0 +1,70 @@ +// Janky script to get around pnpm link issues +// Probably don't use this. Probably +/* eslint-disable no-console */ +const fs = require("fs"); +const path = require("path"); +const child_process = require("child_process"); + +const onDisk = { + "@moonlight-mod/lunast": "../lunast", + "@moonlight-mod/moonmap": "../moonmap", + "@moonlight-mod/mappings": "../mappings" +}; + +function exec(cmd, dir) { + child_process.execSync(cmd, { cwd: dir, stdio: "inherit" }); +} + +function getDeps(packageJSON) { + const ret = {}; + Object.assign(ret, packageJSON.dependencies || {}); + Object.assign(ret, packageJSON.devDependencies || {}); + Object.assign(ret, packageJSON.peerDependencies || {}); + return ret; +} + +function link(dir) { + const packageJSON = JSON.parse( + fs.readFileSync(path.join(dir, "package.json"), "utf8") + ); + const deps = getDeps(packageJSON); + + for (const [dep, path] of Object.entries(onDisk)) { + if (deps[dep]) { + exec(`pnpm link ${path}`, dir); + } + } +} + +function undo(dir) { + exec("pnpm unlink", dir); + try { + exec("git restore pnpm-lock.yaml", dir); + } catch { + // ignored + } +} + +const shouldUndo = process.argv.includes("--undo"); +const packages = fs.readdirSync("./packages"); + +for (const path of Object.values(onDisk)) { + console.log(path); + if (shouldUndo) { + undo(path); + } else { + link(path); + } +} + +if (shouldUndo) { + const dir = __dirname; + console.log(dir); + undo(dir); +} else { + for (const pkg of packages) { + const dir = path.join(__dirname, "packages", pkg); + console.log(dir); + link(dir); + } +} diff --git a/scripts/update.js b/scripts/update.js new file mode 100644 index 0000000..4c17327 --- /dev/null +++ b/scripts/update.js @@ -0,0 +1,31 @@ +// Update dependencies in all packages +/* eslint-disable no-console */ +const fs = require("fs"); +const path = require("path"); +const child_process = require("child_process"); + +const packageToUpdate = process.argv[2]; + +function getDeps(packageJSON) { + const ret = {}; + Object.assign(ret, packageJSON.dependencies || {}); + Object.assign(ret, packageJSON.devDependencies || {}); + Object.assign(ret, packageJSON.peerDependencies || {}); + return ret; +} + +function exec(cmd, dir) { + child_process.execSync(cmd, { cwd: dir, stdio: "inherit" }); +} + +for (const package of fs.readdirSync("./packages")) { + const packageJSON = JSON.parse( + fs.readFileSync(path.join("./packages", package, "package.json"), "utf8") + ); + + const deps = getDeps(packageJSON); + if (Object.keys(deps).includes(packageToUpdate)) { + console.log(`Updating ${packageToUpdate} in ${package}`); + exec(`pnpm update ${packageToUpdate}`, path.join("./packages", package)); + } +} From aaa591c2c7f55f5ad9e79eb509ea9335e00950a4 Mon Sep 17 00:00:00 2001 From: adryd Date: Mon, 7 Oct 2024 19:26:29 -0400 Subject: [PATCH 28/73] rocketship: don't override Discord's permission handler --- .../core-extensions/src/rocketship/host.ts | 44 ++++++++++++++++--- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/packages/core-extensions/src/rocketship/host.ts b/packages/core-extensions/src/rocketship/host.ts index d995ee6..8141d80 100644 --- a/packages/core-extensions/src/rocketship/host.ts +++ b/packages/core-extensions/src/rocketship/host.ts @@ -52,15 +52,47 @@ moonlightHost.events.on( "window-created", (window: BrowserWindow, isMainWindow: boolean) => { if (!isMainWindow) return; - const windowSession = window.webContents.session; - window.webContents.on("did-finish-load", () => { - windowSession.setPermissionRequestHandler( - (webContents, permission, callback) => { - if (permission === "media") callback(true); + windowSession.setPermissionRequestHandler( + (webcontents, permission, callback) => { + let cbResult = false; + function fakeCallback(result: boolean) { + cbResult = result; } - ); + + if (caughtPermissionRequestHandler) { + caughtPermissionRequestHandler(webcontents, permission, fakeCallback); + } + + if (permission === "media" || permission === "display-capture") { + cbResult = true; + } + + callback(cbResult); + } + ); + + let caughtPermissionRequestHandler: + | (( + webcontents: Electron.WebContents, + permission: string, + callback: (permissionGranted: boolean) => void + ) => void) + | undefined; + + windowSession.setPermissionRequestHandler = + function catchSetPermissionRequestHandler( + handler: ( + webcontents: Electron.WebContents, + permission: string, + callback: (permissionGranteda: boolean) => void + ) => void + ) { + caughtPermissionRequestHandler = handler; + }; + + window.webContents.on("did-finish-load", () => { windowSession.setPermissionCheckHandler((webContents, permission) => { if (permission === "media") return true; return false; From d0911bf54d3d98c0663eea7da8d8520fb2a41897 Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 19:29:04 -0400 Subject: [PATCH 29/73] Initial work on the popup modal --- .../src/moonbase/webpackModules/stores.ts | 24 +++++++++++++ .../webpackModules/ui/extensions/card.tsx | 10 ++++-- .../webpackModules/ui/extensions/popup.tsx | 34 +++++++++++++++++++ 3 files changed, 66 insertions(+), 2 deletions(-) create mode 100644 packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 36e7360..db3b4ce 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -296,6 +296,30 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); } + async getDependencies(uniqueId: number) { + const ext = this.getExtension(uniqueId); + + let allDepsSatisfied = true; + for (const dep of ext.manifest.dependencies ?? []) { + const id = this.getExtensionUniqueId(dep); + const state = + id != null ? this.getExtension(id).state : ExtensionState.NotDownloaded; + if (id == null || state === ExtensionState.NotDownloaded) { + allDepsSatisfied = false; + break; + } + } + + if (allDepsSatisfied) return null; + + const deps: Record = {}; + for (const dep of ext.manifest.dependencies ?? []) { + deps[dep] = Object.values(this.extensions).filter((e) => e.id === dep); + } + + return deps; + } + async deleteExtension(uniqueId: number) { const ext = this.getExtension(uniqueId); if (ext == null) return; diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index 9ff2546..e888fe8 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -19,6 +19,7 @@ export enum ExtensionPage { } import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; +import doPopup from "./popup"; const { BeakerIcon, DownloadIcon, TrashIcon, CircleWarningIcon, Tooltip } = Components; @@ -113,8 +114,13 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { color={Button.Colors.BRAND} submitting={busy} disabled={conflicting} - onClick={() => { - MoonbaseSettingsStore.installExtension(uniqueId); + onClick={async () => { + await MoonbaseSettingsStore.installExtension(uniqueId); + const deps = + await MoonbaseSettingsStore.getDependencies(uniqueId); + if (deps != null) { + await doPopup(); + } }} > Install diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx new file mode 100644 index 0000000..6e24e6a --- /dev/null +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx @@ -0,0 +1,34 @@ +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; +import React from "@moonlight-mod/wp/react"; + +const { + openModalLazy +} = require("@moonlight-mod/wp/discord/components/common/index"); +const Popup = spacepack.findByCode(".minorContainer", "secondaryAction")[0] + .exports.default; + +const logger = moonlight.getLogger("moonbase"); + +function OurPopup({ transitionState }: { transitionState: any }) { + return ( + body} + cancelText="cancel" + confirmText="confirm" + secondaryConfirmText="secondaryConfirm" + onCancel={() => logger.info("cancel")} + onClose={() => logger.info("close")} + onConfirm={() => logger.info("confirm")} + onConfirmSecondary={() => logger.info("confirmSecondary")} + title="title" + header="header" + transitionState={transitionState} + /> + ); +} + +export default async function doPopup() { + await openModalLazy(async () => { + return OurPopup; + }); +} From 147b146ea7ebfe2612366369c58178fa71c70d12 Mon Sep 17 00:00:00 2001 From: adryd Date: Mon, 7 Oct 2024 19:34:01 -0400 Subject: [PATCH 30/73] Document patch purposes Co-authored-by: Cynosphere --- packages/core-extensions/src/rocketship/index.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/core-extensions/src/rocketship/index.ts b/packages/core-extensions/src/rocketship/index.ts index 1fa11fb..9caad9c 100644 --- a/packages/core-extensions/src/rocketship/index.ts +++ b/packages/core-extensions/src/rocketship/index.ts @@ -55,6 +55,7 @@ navigator.mediaDevices.getDisplayMedia = async function getDisplayMediaRedirect( }; export const patches: Patch[] = [ + // "Ensure discord_voice is happy" { find: "RustAudioDeviceModule", replace: [ @@ -68,6 +69,7 @@ export const patches: Patch[] = [ } ] }, + // Remove Native media engine from list of choices { find: '.CAMERA_BACKGROUND_LIVE="cameraBackgroundLive"', replace: { @@ -75,6 +77,7 @@ export const patches: Patch[] = [ replacement: "" } }, + // Stub out browser checks to allow us to use WebRTC voice on Embedded { find: "Using Unified Plan (", replace: { From 9e2f70685b3596b3246488f5d78d8944805057b2 Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 19:52:03 -0400 Subject: [PATCH 31/73] moonbase: Properly set download URL on exising extensions --- .../src/moonbase/webpackModules/stores.ts | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 36e7360..b783880 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -121,6 +121,17 @@ class MoonbaseSettingsStore extends Store { }; if (this.alreadyExists(extensionData)) { + // Make sure the download URL is properly updated + for (const [id, e] of Object.entries(this.extensions)) { + if (e.id === ext.id && e.source.url === repo) { + this.extensions[parseInt(id)].manifest = { + ...e.manifest, + download: ext.download + }; + break; + } + } + if (this.hasUpdate(extensionData)) { this.updates[uniqueId] = { version: ext.version!, From 0b3b93ce66b9669a00029debf0be8daa3b664183 Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 21:16:02 -0400 Subject: [PATCH 32/73] Very WIP dep picker --- .../src/moonbase/webpackModules/stores.ts | 49 ++++-- .../webpackModules/ui/extensions/card.tsx | 11 +- .../webpackModules/ui/extensions/info.tsx | 7 +- .../webpackModules/ui/extensions/popup.tsx | 149 ++++++++++++++++-- packages/core/src/config.ts | 3 +- packages/types/src/constants.ts | 3 + 6 files changed, 180 insertions(+), 42 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index db3b4ce..a96f971 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,4 +1,4 @@ -import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; +import { Config, constants, ExtensionLoadSource } from "@moonlight-mod/types"; import { ExtensionState, MoonbaseExtension, @@ -296,25 +296,45 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); } + private getRank(ext: MoonbaseExtension) { + if (ext.source.type === ExtensionLoadSource.Developer) return 3; + if (ext.source.type === ExtensionLoadSource.Core) return 2; + if (ext.source.url === constants.mainRepo) return 1; + return 0; + } + async getDependencies(uniqueId: number) { const ext = this.getExtension(uniqueId); - let allDepsSatisfied = true; + const missingDeps = []; for (const dep of ext.manifest.dependencies ?? []) { - const id = this.getExtensionUniqueId(dep); - const state = - id != null ? this.getExtension(id).state : ExtensionState.NotDownloaded; - if (id == null || state === ExtensionState.NotDownloaded) { - allDepsSatisfied = false; - break; - } + const anyInstalled = Object.values(this.extensions).some( + (e) => e.id === dep && e.state !== ExtensionState.NotDownloaded + ); + if (!anyInstalled) missingDeps.push(dep); } - if (allDepsSatisfied) return null; + if (missingDeps.length === 0) return null; const deps: Record = {}; - for (const dep of ext.manifest.dependencies ?? []) { - deps[dep] = Object.values(this.extensions).filter((e) => e.id === dep); + for (const dep of missingDeps) { + const candidates = Object.values(this.extensions).filter( + (e) => e.id === dep + ); + + deps[dep] = candidates.sort((a, b) => { + const aRank = this.getRank(a); + const bRank = this.getRank(b); + if (aRank === bRank) { + const repoIndex = this.config.repositories.indexOf(a.source.url!); + const otherRepoIndex = this.config.repositories.indexOf( + b.source.url! + ); + return repoIndex - otherRepoIndex; + } else { + return bRank - aRank; + } + }); } return deps; @@ -346,6 +366,11 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); } + tryGetExtensionName(id: string) { + const uniqueId = this.getExtensionUniqueId(id); + return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; + } + writeConfig() { this.submitting = true; diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index e888fe8..045bd6f 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -11,6 +11,7 @@ import IntegrationCard from "@moonlight-mod/wp/discord/modules/guild_settings/In import ExtensionInfo from "./info"; import Settings from "./settings"; +import installWithDependencyPopup from "./popup"; export enum ExtensionPage { Info, @@ -19,7 +20,6 @@ export enum ExtensionPage { } import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; -import doPopup from "./popup"; const { BeakerIcon, DownloadIcon, TrashIcon, CircleWarningIcon, Tooltip } = Components; @@ -115,12 +115,7 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { submitting={busy} disabled={conflicting} onClick={async () => { - await MoonbaseSettingsStore.installExtension(uniqueId); - const deps = - await MoonbaseSettingsStore.getDependencies(uniqueId); - if (deps != null) { - await doPopup(); - } + await installWithDependencyPopup(uniqueId); }} > Install @@ -176,7 +171,7 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { ? `This extension is a dependency of the following enabled extension${ enabledDependants.length > 1 ? "s" : "" }: ${enabledDependants - .map((a) => a.manifest.meta!.name) + .map((a) => a.manifest.meta?.name ?? a.id) .join(", ")}` : undefined } diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx index 8e119a1..b40d540 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/info.tsx @@ -178,11 +178,8 @@ export default function ExtensionInfo({ ext }: { ext: MoonbaseExtension }) { [DependencyType.Incompatible]: "var(--red-400)" }; const color = colors[dep.type]; - const id = MoonbaseSettingsStore.getExtensionUniqueId(dep.id); - const name = - (id !== null - ? MoonbaseSettingsStore.getExtensionName(id!) - : null) ?? dep.id; + const name = MoonbaseSettingsStore.tryGetExtensionName(dep.id); + return ( {name} diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx index 6e24e6a..1508a67 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx @@ -1,34 +1,151 @@ +// TODO: clean up the styling here import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import React from "@moonlight-mod/wp/react"; +import { MoonbaseExtension } from "core-extensions/src/moonbase/types"; +import * as Components from "@moonlight-mod/wp/discord/components/common/index"; +import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; +import { ExtensionLoadSource } from "types/src"; const { - openModalLazy + openModalLazy, + closeModal } = require("@moonlight-mod/wp/discord/components/common/index"); const Popup = spacepack.findByCode(".minorContainer", "secondaryAction")[0] .exports.default; -const logger = moonlight.getLogger("moonbase"); +const presentableLoadSources: Record = { + [ExtensionLoadSource.Developer]: "Local extension", // should never show up + [ExtensionLoadSource.Core]: "Core extension", + [ExtensionLoadSource.Normal]: "Extension repository" +}; + +function ExtensionSelect({ + id, + candidates, + option, + setOption +}: { + id: string; + candidates: MoonbaseExtension[]; + option: string | undefined; + setOption: (pick: string | undefined) => void; +}) { + const { SingleSelect } = Components; + + return ( + { + return { + value: candidate.uniqueId.toString(), + label: + candidate.source.url ?? + presentableLoadSources[candidate.source.type] ?? + candidate.manifest.version ?? + "" + }; + })} + onChange={(value: string) => { + setOption(value); + }} + /> + ); +} + +function OurPopup({ + deps, + transitionState, + id +}: { + deps: Record; + transitionState: number | null; + id: string; +}) { + const { Text } = Components; + + const amountNotAvailable = Object.values(deps).filter( + (candidates) => candidates.length === 0 + ).length; + + const [options, setOptions] = React.useState< + Record + >( + Object.fromEntries( + Object.entries(deps).map(([id, candidates]) => [ + id, + candidates.length > 0 ? candidates[0].uniqueId.toString() : undefined + ]) + ) + ); -function OurPopup({ transitionState }: { transitionState: any }) { return ( body} - cancelText="cancel" - confirmText="confirm" - secondaryConfirmText="secondaryConfirm" - onCancel={() => logger.info("cancel")} - onClose={() => logger.info("close")} - onConfirm={() => logger.info("confirm")} - onConfirmSecondary={() => logger.info("confirmSecondary")} - title="title" - header="header" + body={ + <> + + This extension depends on other extensions which are not downloaded. + Choose which extensions to download. + + + {amountNotAvailable > 0 && ( + + {amountNotAvailable} extension + {amountNotAvailable > 1 ? "s" : ""} could not be found, and must + be installed manually. + + )} + + {Object.entries(deps).map(([id, candidates], i) => ( + <> + {MoonbaseSettingsStore.tryGetExtensionName(id)} + + setOptions((prev) => ({ + ...prev, + [id]: pick + })) + } + /> + + ))} + + } + cancelText="Cancel" + confirmText="Install" + onConfirm={() => { + closeModal(id); + + for (const pick of Object.values(options)) { + if (pick != null) { + MoonbaseSettingsStore.installExtension(parseInt(pick)); + } + } + }} + title="Extension dependencies" transitionState={transitionState} /> ); } -export default async function doPopup() { - await openModalLazy(async () => { - return OurPopup; +export async function doPopup(deps: Record) { + const id: string = await openModalLazy(async () => { + // eslint-disable-next-line react/display-name + return ({ transitionState }: { transitionState: number | null }) => { + return ; + }; }); } + +export default async function installWithDependencyPopup(uniqueId: number) { + await MoonbaseSettingsStore.installExtension(uniqueId); + const deps = await MoonbaseSettingsStore.getDependencies(uniqueId); + if (deps != null) { + await doPopup(deps); + } +} diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index db941e8..09738f5 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,6 +1,7 @@ import { Config } from "@moonlight-mod/types"; import requireImport from "./util/import"; import { getConfigPath } from "./util/data"; +import * as constants from "@moonlight-mod/types/constants"; const defaultConfig: Config = { extensions: { @@ -9,7 +10,7 @@ const defaultConfig: Config = { noTrack: true, noHideToken: true }, - repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"] + repositories: [constants.mainRepo] }; export function writeConfig(config: Config) { diff --git a/packages/types/src/constants.ts b/packages/types/src/constants.ts index 978f4c8..1dea746 100644 --- a/packages/types/src/constants.ts +++ b/packages/types/src/constants.ts @@ -11,3 +11,6 @@ export const ipcSetCorsList = "_moonlight_setCorsList"; export const ipcSetBlockedList = "_moonlight_setBlockedList"; export const apiLevel = 2; + +export const mainRepo = + "https://moonlight-mod.github.io/extensions-dist/repo.json"; From cde6fa30baebfcd7b6c7edfffa294166e86663e5 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Mon, 7 Oct 2024 19:52:11 -0600 Subject: [PATCH 33/73] update banner, untested if automatic detection works --- .../core-extensions/src/moonbase/index.tsx | 52 +++++++++- .../src/moonbase/webpackModules/stores.ts | 26 +++++ .../src/moonbase/webpackModules/updates.ts | 21 ++++ .../moonbase/webpackModules/updatesNotice.tsx | 95 +++++++++++++++++++ 4 files changed, 191 insertions(+), 3 deletions(-) create mode 100644 packages/core-extensions/src/moonbase/webpackModules/updates.ts create mode 100644 packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 4716758..f28166c 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -1,6 +1,6 @@ -import { ExtensionWebExports } from "@moonlight-mod/types"; +import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; -export const webpackModules: ExtensionWebExports["webpackModules"] = { +export const webpackModules: Record = { stores: { dependencies: [ { id: "discord/packages/flux" }, @@ -29,10 +29,56 @@ export const webpackModules: ExtensionWebExports["webpackModules"] = { { ext: "moonbase", id: "ui" } ], entrypoint: true + }, + + updates: { + dependencies: [ + { id: "discord/Dispatcher" }, + { ext: "moonbase", id: "stores" } + ], + entrypoint: true + }, + + updatesNotice: { + dependencies: [ + { id: "react" }, + { id: "discord/Dispatcher" }, + { id: "discord/components/common/index" }, + { id: "discord/packages/flux" }, + { ext: "spacepack", id: "spacepack" }, + { ext: "moonbase", id: "stores" } + ] } }; +export const patches: Patch[] = [ + { + find: ".GUILD_RAID_NOTIFICATION:", + replace: { + match: + /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/, + replacement: (orig, createElement) => + `case "__moonlight_updates":return${createElement}(require("moonbase_updatesNotice").default,{});${orig}` + } + }, + { + find: '"NoticeStore"', + replace: [ + { + match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/, + replacement: (orig: string) => + `__moonlight_updates:{predicate:()=>require("moonbase_stores").MoonbaseSettingsStore.shouldShowUpdateNotice()},${orig}` + }, + { + match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g, + replacement: (_, orig) => `=["__moonlight_updates",${orig}` + } + ] + } +]; + export const styles = [ ".moonbase-settings > :first-child { margin-top: 0px; }", - "textarea.moonbase-resizeable { resize: vertical }" + "textarea.moonbase-resizeable { resize: vertical }", + ".moonbase-updates-notice { background-color: #222034; color: #FFFBA6; }" ]; diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 356ab55..e6c5dbd 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -82,6 +82,9 @@ class MoonbaseSettingsStore extends Store { submitting: boolean; installing: boolean; + newVersion: string | null; + private updateNotice: boolean; + extensions: { [id: number]: MoonbaseExtension }; updates: { [id: number]: { version: string; download: string } }; @@ -96,6 +99,9 @@ class MoonbaseSettingsStore extends Store { this.submitting = false; this.installing = false; + this.newVersion = null; + this.updateNotice = false; + this.extensions = {}; this.updates = {}; for (const ext of moonlightNode.extensions) { @@ -145,6 +151,12 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); }); + + natives!.checkForMoonlightUpdate().then((version) => { + this.newVersion = version; + + this.emitChange(); + }); } private alreadyExists(ext: MoonbaseExtension) { @@ -349,6 +361,20 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); } + showUpdateNotice() { + this.updateNotice = true; + //this.emitChange(); + } + + dismissUpdateNotice() { + this.updateNotice = false; + //this.emitChange(); + } + + shouldShowUpdateNotice() { + return this.updateNotice; + } + // Required because electron likes to make it immutable sometimes. // This sucks. private clone(obj: T): T { diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.ts b/packages/core-extensions/src/moonbase/webpackModules/updates.ts new file mode 100644 index 0000000..20fe343 --- /dev/null +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.ts @@ -0,0 +1,21 @@ +import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; + +function checkForUpdates() { + if ( + MoonbaseSettingsStore.newVersion != null || + Object.keys(MoonbaseSettingsStore.updates).length > 0 + ) { + MoonbaseSettingsStore.showUpdateNotice(); + Dispatcher.dispatch({ + type: "NOTICE_SHOW", + notice: { type: "__moonlight_updates" } + }); + } + + Dispatcher.unsubscribe("CONNECTION_OPEN", checkForUpdates); + Dispatcher.unsubscribe("CONNECTION_OPEN_SUPPLEMENTAL", checkForUpdates); +} + +Dispatcher.subscribe("CONNECTION_OPEN", checkForUpdates); +Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", checkForUpdates); diff --git a/packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx b/packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx new file mode 100644 index 0000000..eb831df --- /dev/null +++ b/packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx @@ -0,0 +1,95 @@ +import React from "@moonlight-mod/wp/react"; +import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import * as Components from "@moonlight-mod/wp/discord/components/common/index"; +import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; +import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; + +// FIXME: not indexed as importable +const Constants = spacepack.require("discord/Constants"); +const UserSettingsSections = spacepack.findObjectFromKey( + Constants, + "APPEARANCE_THEME_PICKER" +); + +// FIXME: types +const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components; + +const { open } = spacepack.findByExports("setSection", "clearSubsection")[0] + .exports.Z; + +function dismiss() { + MoonbaseSettingsStore.dismissUpdateNotice(); + Dispatcher.dispatch({ + type: "NOTICE_DISMISS" + }); +} + +function openMoonbase() { + dismiss(); + // settings is lazy loaded thus lazily patched + // FIXME: figure out a way to detect if settings has been opened already + // just so the transition isnt as jarring + open(UserSettingsSections.ACCOUNT); + setTimeout(() => open("moonbase", 0), 0); +} + +function plural(str: string, num: number) { + return `${str}${num > 1 ? "s" : ""}`; +} + +export default function MoonbaseUpdatesNotice() { + const { version, extensionUpdateCount } = useStateFromStoresObject( + [MoonbaseSettingsStore], + () => ({ + version: MoonbaseSettingsStore.newVersion, + extensionUpdateCount: Object.keys(MoonbaseSettingsStore.updates).length + }) + ); + const hasExtensionUpdates = extensionUpdateCount > 0; + + let message; + + if (version != null) { + message = + moonlightNode.branch === "nightly" + ? `A new version of moonlight is available` + : `moonlight ${version} is available`; + } + + if (hasExtensionUpdates) { + let concat = false; + if (message == null) { + message = ""; + } else { + concat = true; + message += ", and "; + } + message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural( + "extension", + extensionUpdateCount + )} can be updated`; + } + + if (message != null) message += "."; + + let openButton; + + if (hasExtensionUpdates) + openButton = ( + + Open Moonbase + + ); + + return ( + + + {message} + {openButton} + + ); +} From 977a95e93d6fe619d815f8e31fd112b09b150afb Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Mon, 7 Oct 2024 19:57:53 -0600 Subject: [PATCH 34/73] remove caching logic --- packages/core-extensions/src/moonbase/node.ts | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 5c1c146..56ef32e 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -6,28 +6,11 @@ import { repoUrlFile } from "@moonlight-mod/types/constants"; const logger = moonlightNode.getLogger("moonbase"); -const updateTimerFile = path.join( - moonlightNode.getMoonlightDir(), - ".moonbase-update-timer" -); -const updateTimerInterval = 12 * 60 * 60 * 1000; - const githubRepo = "moonlight-mod/moonlight"; const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; async function checkForMoonlightUpdate() { - const updateTimerStr = fs.existsSync(updateTimerFile) - ? fs.readFileSync(updateTimerFile, "utf-8") - : "0"; - let updateTimer = parseInt(updateTimerStr); - if (isNaN(updateTimer)) updateTimer = 0; - - const shouldCheck = Date.now() - updateTimer > updateTimerInterval; - if (!shouldCheck) return null; - - fs.writeFileSync(updateTimerFile, Date.now().toString()); - if (moonlightNode.branch === "stable") { const req = await fetch( `https://api.github.com/repos/${githubRepo}/releases/latest`, From 8e336954a9720cd89ffa577b8c3cb7c08800a343 Mon Sep 17 00:00:00 2001 From: NotNite Date: Mon, 7 Oct 2024 22:05:42 -0400 Subject: [PATCH 35/73] moonbase: Implement update checking in browser --- packages/browser/manifest.json | 1 + packages/browser/manifestv2.json | 2 ++ .../core-extensions/src/moonbase/consts.ts | 3 +++ packages/core-extensions/src/moonbase/node.ts | 5 +---- .../src/moonbase/webpackModules/stores.ts | 22 +++++++++++++++++++ packages/web-preload/src/index.ts | 8 +++++-- 6 files changed, 35 insertions(+), 6 deletions(-) create mode 100644 packages/core-extensions/src/moonbase/consts.ts diff --git a/packages/browser/manifest.json b/packages/browser/manifest.json index f0bea16..3839a01 100644 --- a/packages/browser/manifest.json +++ b/packages/browser/manifest.json @@ -11,6 +11,7 @@ ], "host_permissions": [ "https://moonlight-mod.github.io/*", + "https://api.github.com/*", "https://*.discord.com/*" ], "content_scripts": [ diff --git a/packages/browser/manifestv2.json b/packages/browser/manifestv2.json index 1b97688..49d0699 100644 --- a/packages/browser/manifestv2.json +++ b/packages/browser/manifestv2.json @@ -9,6 +9,8 @@ "scripting", "webNavigation", "https://*.discord.com/assets/*.js", + "https://moonlight-mod.github.io/*", + "https://api.github.com/*", "https://*.discord.com/*" ], "background": { diff --git a/packages/core-extensions/src/moonbase/consts.ts b/packages/core-extensions/src/moonbase/consts.ts new file mode 100644 index 0000000..6b0a150 --- /dev/null +++ b/packages/core-extensions/src/moonbase/consts.ts @@ -0,0 +1,3 @@ +export const githubRepo = "moonlight-mod/moonlight"; +export const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; +export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 56ef32e..d1e2bdc 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -3,13 +3,10 @@ import fs from "fs"; import path from "path"; import extractAsar from "@moonlight-mod/core/asar"; import { repoUrlFile } from "@moonlight-mod/types/constants"; +import { githubRepo, userAgent, nightlyRefUrl } from "./consts"; const logger = moonlightNode.getLogger("moonbase"); -const githubRepo = "moonlight-mod/moonlight"; -const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; -const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; - async function checkForMoonlightUpdate() { if (moonlightNode.branch === "stable") { const req = await fetch( diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index e6c5dbd..b5afd27 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -9,6 +9,7 @@ import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; import extractAsar from "@moonlight-mod/core/asar"; import { repoUrlFile } from "@moonlight-mod/types/constants"; +import { githubRepo, userAgent, nightlyRefUrl } from "../consts"; const logger = moonlight.getLogger("moonbase"); @@ -18,6 +19,27 @@ if (window._moonlightBrowserFS != null) { natives = { checkForMoonlightUpdate: async () => { // TODO + if (moonlight.branch === "stable") { + const req = await fetch( + `https://api.github.com/repos/${githubRepo}/releases/latest`, + { + headers: { + "User-Agent": userAgent + } + } + ); + const json: { name: string } = await req.json(); + return json.name !== moonlight.version ? json.name : null; + } else if (moonlight.branch === "nightly") { + const req = await fetch(nightlyRefUrl, { + headers: { + "User-Agent": userAgent + } + }); + const ref = (await req.text()).split("\n")[0]; + return ref !== moonlight.version ? ref : null; + } + return null; }, diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index f2b4a9e..666f39c 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -51,9 +51,13 @@ async function load() { logger.error("Error setting up web-preload", e); } - window.addEventListener("DOMContentLoaded", () => { + if (MOONLIGHT_ENV === "web-preload") { + window.addEventListener("DOMContentLoaded", () => { + installStyles(); + }); + } else { installStyles(); - }); + } } if (MOONLIGHT_ENV === "web-preload") { From 65207db84f8922f815bb92d2b09b5be743bee557 Mon Sep 17 00:00:00 2001 From: adryd Date: Mon, 7 Oct 2024 23:36:35 -0400 Subject: [PATCH 36/73] settings: Adjust patch so that it will wrap patches from other client mods --- packages/core-extensions/src/settings/index.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/settings/index.ts b/packages/core-extensions/src/settings/index.ts index a85d71a..9b2f27f 100644 --- a/packages/core-extensions/src/settings/index.ts +++ b/packages/core-extensions/src/settings/index.ts @@ -5,9 +5,9 @@ export const patches: Patch[] = [ { find: '"useGenerateUserSettingsSections"', replace: { - match: /(?<=\.push\(.+?\)}\)\)}\),)./, - replacement: (sections: string) => - `require("settings_settings").Settings._mutateSections(${sections})` + match: /(?<=\.push\(.+?\)}\)\)}\),)(.+?)}/, + replacement: (_, sections: string) => + `require("settings_settings").Settings._mutateSections(${sections})}` } }, { From e8654ec4432fbd6a7565bdc02710431ee0c85174 Mon Sep 17 00:00:00 2001 From: adryd Date: Tue, 8 Oct 2024 01:03:26 -0400 Subject: [PATCH 37/73] rocketship: add wrapper for setPermissionCheckHandler to fix notifications --- .../core-extensions/src/rocketship/host.ts | 123 +----------------- .../src/rocketship/host/permissions.ts | 96 ++++++++++++++ .../src/rocketship/host/venmic.ts | 76 +++++++++++ 3 files changed, 174 insertions(+), 121 deletions(-) create mode 100644 packages/core-extensions/src/rocketship/host/permissions.ts create mode 100644 packages/core-extensions/src/rocketship/host/venmic.ts diff --git a/packages/core-extensions/src/rocketship/host.ts b/packages/core-extensions/src/rocketship/host.ts index 8141d80..dbd2a58 100644 --- a/packages/core-extensions/src/rocketship/host.ts +++ b/packages/core-extensions/src/rocketship/host.ts @@ -1,121 +1,2 @@ -import type { BrowserWindow } from "electron"; -import { app, desktopCapturer } from "electron"; -import path from "node:path"; - -const logger = moonlightHost.getLogger("rocketship"); - -function getPatchbay() { - try { - const venmic = require( - path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node") - ) as typeof import("@vencord/venmic"); - const patchbay = new venmic.PatchBay(); - return patchbay; - } catch (error) { - logger.error("Failed to load venmic.node:", error); - return null; - } -} - -const patchbay = getPatchbay(); - -// TODO: figure out how to map source to window with venmic -function linkVenmic() { - if (patchbay == null) return false; - - try { - const pid = - app - .getAppMetrics() - .find((proc) => proc.name === "Audio Service") - ?.pid?.toString() ?? ""; - - logger.info("Audio Service PID:", pid); - - patchbay.unlink(); - return patchbay.link({ - exclude: [ - { "application.process.id": pid }, - { "media.class": "Stream/Input/Audio" } - ], - ignore_devices: true, - only_speakers: true, - only_default_speakers: true - }); - } catch (error) { - logger.error("Failed to link venmic:", error); - return false; - } -} - -moonlightHost.events.on( - "window-created", - (window: BrowserWindow, isMainWindow: boolean) => { - if (!isMainWindow) return; - const windowSession = window.webContents.session; - - windowSession.setPermissionRequestHandler( - (webcontents, permission, callback) => { - let cbResult = false; - function fakeCallback(result: boolean) { - cbResult = result; - } - - if (caughtPermissionRequestHandler) { - caughtPermissionRequestHandler(webcontents, permission, fakeCallback); - } - - if (permission === "media" || permission === "display-capture") { - cbResult = true; - } - - callback(cbResult); - } - ); - - let caughtPermissionRequestHandler: - | (( - webcontents: Electron.WebContents, - permission: string, - callback: (permissionGranted: boolean) => void - ) => void) - | undefined; - - windowSession.setPermissionRequestHandler = - function catchSetPermissionRequestHandler( - handler: ( - webcontents: Electron.WebContents, - permission: string, - callback: (permissionGranteda: boolean) => void - ) => void - ) { - caughtPermissionRequestHandler = handler; - }; - - window.webContents.on("did-finish-load", () => { - windowSession.setPermissionCheckHandler((webContents, permission) => { - if (permission === "media") return true; - return false; - }); - - // @ts-expect-error these types ancient - windowSession.setDisplayMediaRequestHandler( - (request: any, callback: any) => { - const linked = linkVenmic(); - desktopCapturer - .getSources({ types: ["screen", "window"] }) - .then((sources) => { - //logger.debug("desktopCapturer.getSources", sources); - logger.debug("Linked to venmic:", linked); - - callback({ - video: sources[0], - audio: "loopback" - }); - }); - }, - { useSystemPicker: true } - ); - }); - } -); +import "./host/permissions.js"; +import "./host/venmic.js"; diff --git a/packages/core-extensions/src/rocketship/host/permissions.ts b/packages/core-extensions/src/rocketship/host/permissions.ts new file mode 100644 index 0000000..3f3eecb --- /dev/null +++ b/packages/core-extensions/src/rocketship/host/permissions.ts @@ -0,0 +1,96 @@ +import type { BrowserWindow } from "electron"; + +type PermissionRequestHandler = ( + webContents: Electron.WebContents, + permission: string, + callback: (permissionGranted: boolean) => void, + details: Electron.PermissionRequestHandlerHandlerDetails +) => void; + +type PermissionCheckHandler = ( + webContents: Electron.WebContents | null, + permission: string, + requestingOrigin: string, + details: Electron.PermissionCheckHandlerHandlerDetails +) => boolean; + +moonlightHost.events.on( + "window-created", + (window: BrowserWindow, isMainWindow: boolean) => { + if (!isMainWindow) return; + const windowSession = window.webContents.session; + + // setPermissionRequestHandler + windowSession.setPermissionRequestHandler( + (webcontents, permission, callback, details) => { + let cbResult = false; + function fakeCallback(result: boolean) { + cbResult = result; + } + + if (caughtPermissionRequestHandler) { + caughtPermissionRequestHandler( + webcontents, + permission, + fakeCallback, + details + ); + } + + if (permission === "media" || permission === "display-capture") { + cbResult = true; + } + + callback(cbResult); + } + ); + + let caughtPermissionRequestHandler: PermissionRequestHandler | undefined; + + windowSession.setPermissionRequestHandler = + function catchSetPermissionRequestHandler( + handler: ( + webcontents: Electron.WebContents, + permission: string, + callback: (permissionGranted: boolean) => void + ) => void + ) { + caughtPermissionRequestHandler = handler; + }; + + // setPermissionCheckHandler + windowSession.setPermissionCheckHandler( + (webcontents, permission, requestingOrigin, details) => { + return false; + } + ); + + let caughtPermissionCheckHandler: PermissionCheckHandler | undefined; + + windowSession.setPermissionCheckHandler( + (webcontents, permission, requestingOrigin, details) => { + let result = false; + + if (caughtPermissionCheckHandler) { + result = caughtPermissionCheckHandler( + webcontents, + permission, + requestingOrigin, + details + ); + } + + if (permission === "media" || permission === "display-capture") { + result = true; + } + + return result; + } + ); + + windowSession.setPermissionCheckHandler = + function catchSetPermissionCheckHandler(handler: PermissionCheckHandler) { + caughtPermissionCheckHandler = handler; + }; + } +); diff --git a/packages/core-extensions/src/rocketship/host/venmic.ts b/packages/core-extensions/src/rocketship/host/venmic.ts new file mode 100644 index 0000000..66abba1 --- /dev/null +++ b/packages/core-extensions/src/rocketship/host/venmic.ts @@ -0,0 +1,76 @@ +import type { BrowserWindow } from "electron"; +import { app, desktopCapturer } from "electron"; +import path from "node:path"; + +const logger = moonlightHost.getLogger("rocketship"); + +function getPatchbay() { + try { + const venmic = require( + path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node") + ) as typeof import("@vencord/venmic"); + const patchbay = new venmic.PatchBay(); + return patchbay; + } catch (error) { + logger.error("Failed to load venmic.node:", error); + return null; + } +} + +const patchbay = getPatchbay(); + +// TODO: figure out how to map source to window with venmic +function linkVenmic() { + if (patchbay == null) return false; + + try { + const pid = + app + .getAppMetrics() + .find((proc) => proc.name === "Audio Service") + ?.pid?.toString() ?? ""; + + logger.info("Audio Service PID:", pid); + + patchbay.unlink(); + return patchbay.link({ + exclude: [ + { "application.process.id": pid }, + { "media.class": "Stream/Input/Audio" } + ], + ignore_devices: true, + only_speakers: true, + only_default_speakers: true + }); + } catch (error) { + logger.error("Failed to link venmic:", error); + return false; + } +} + +moonlightHost.events.on( + "window-created", + (window: BrowserWindow, isMainWindow: boolean) => { + if (!isMainWindow) return; + const windowSession = window.webContents.session; + + // @ts-expect-error these types ancient + windowSession.setDisplayMediaRequestHandler( + (request: any, callback: any) => { + const linked = linkVenmic(); + desktopCapturer + .getSources({ types: ["screen", "window"] }) + .then((sources) => { + //logger.debug("desktopCapturer.getSources", sources); + logger.debug("Linked to venmic:", linked); + + callback({ + video: sources[0], + audio: "loopback" + }); + }); + }, + { useSystemPicker: true } + ); + } +); From c9f2316c8d61075c631f2edf28f2b00d26590288 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 09:23:44 -0400 Subject: [PATCH 38/73] build: Use an IIFE where possible --- build.mjs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.mjs b/build.mjs index 0b3cda5..7bfe811 100644 --- a/build.mjs +++ b/build.mjs @@ -153,7 +153,9 @@ async function build(name, entry) { entryPoints: [entry], outfile, - format: "cjs", + format: "iife", + globalName: "module.exports", + platform: ["web-preload", "browser"].includes(name) ? "browser" : "node", treeShaking: true, @@ -256,7 +258,8 @@ async function buildExt(ext, side, copyManifest, fileExt) { entryPoints, outdir, - format: "cjs", + format: "iife", + globalName: "module.exports", platform: "node", treeShaking: true, From 0df5611a08a63d8779e6fe218366a9ca5e831d82 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 09:30:20 -0400 Subject: [PATCH 39/73] Remove hard dependency on venmic We were only using it for types. By having it as a dependency, it tries to build, which requires CMake. --- packages/core-extensions/package.json | 3 +- .../core-extensions/src/rocketship/host.ts | 4 +- .../src/rocketship/host/types.ts | 35 ++ .../src/rocketship/host/venmic.ts | 3 +- pnpm-lock.yaml | 444 ------------------ 5 files changed, 40 insertions(+), 449 deletions(-) create mode 100644 packages/core-extensions/src/rocketship/host/types.ts diff --git a/packages/core-extensions/package.json b/packages/core-extensions/package.json index 4f6d61a..a0bf3c5 100644 --- a/packages/core-extensions/package.json +++ b/packages/core-extensions/package.json @@ -3,7 +3,6 @@ "private": true, "dependencies": { "@moonlight-mod/core": "workspace:*", - "@moonlight-mod/types": "workspace:*", - "@vencord/venmic": "^6.1.0" + "@moonlight-mod/types": "workspace:*" } } diff --git a/packages/core-extensions/src/rocketship/host.ts b/packages/core-extensions/src/rocketship/host.ts index dbd2a58..d2fd25f 100644 --- a/packages/core-extensions/src/rocketship/host.ts +++ b/packages/core-extensions/src/rocketship/host.ts @@ -1,2 +1,2 @@ -import "./host/permissions.js"; -import "./host/venmic.js"; +import "./host/permissions"; +import "./host/venmic"; diff --git a/packages/core-extensions/src/rocketship/host/types.ts b/packages/core-extensions/src/rocketship/host/types.ts new file mode 100644 index 0000000..8e709ab --- /dev/null +++ b/packages/core-extensions/src/rocketship/host/types.ts @@ -0,0 +1,35 @@ +// https://github.com/Vencord/venmic/blob/d737ef33eaae7a73d03ec02673e008cf0243434d/lib/module.d.ts +type DefaultProps = "node.name" | "application.name"; + +type LiteralUnion = + | LiteralType + | (BaseType & Record); + +type Optional = Partial> & + Omit; + +export type Node = Record< + LiteralUnion, + string +>; + +export interface LinkData { + include: Node[]; + exclude: Node[]; + + ignore_devices?: boolean; + + only_speakers?: boolean; + only_default_speakers?: boolean; + + workaround?: Node[]; +} + +export interface PatchBay { + unlink(): void; + + list(props?: T[]): Node[]; + link( + data: Optional | Optional + ): boolean; +} diff --git a/packages/core-extensions/src/rocketship/host/venmic.ts b/packages/core-extensions/src/rocketship/host/venmic.ts index 66abba1..75b9ef5 100644 --- a/packages/core-extensions/src/rocketship/host/venmic.ts +++ b/packages/core-extensions/src/rocketship/host/venmic.ts @@ -1,6 +1,7 @@ import type { BrowserWindow } from "electron"; import { app, desktopCapturer } from "electron"; import path from "node:path"; +import { type PatchBay } from "./types"; const logger = moonlightHost.getLogger("rocketship"); @@ -8,7 +9,7 @@ function getPatchbay() { try { const venmic = require( path.join(path.dirname(moonlightHost.asarPath), "..", "venmic.node") - ) as typeof import("@vencord/venmic"); + ) as { PatchBay: new () => PatchBay }; const patchbay = new venmic.PatchBay(); return patchbay; } catch (error) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a1b97c6..62ac295 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,9 +74,6 @@ importers: '@moonlight-mod/types': specifier: workspace:* version: link:../types - '@vencord/venmic': - specifier: ^6.1.0 - version: 6.1.0 packages/injector: dependencies: @@ -426,11 +423,6 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vencord/venmic@6.1.0': - resolution: {integrity: sha512-YiCtzml/W8tYbGhu3jm5jfbbEnl2slKKARNK0jO+8qV979k9eFnfIRTxvhMN/SWq1h8ZNJdXVwvXpffQwq0RuA==} - engines: {node: '>=14.15'} - os: [linux] - '@zenfs/core@1.0.2': resolution: {integrity: sha512-LMTD4ntn6Ag1y+IeOSVykDDvYC12dsGFtsX8M/54OQrLs7v+YnX4bpo0o2osbm8XFmU2MTNMX/G3PLsvzgWzrg==} engines: {node: '>= 16'} @@ -467,14 +459,6 @@ packages: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} - aproba@2.0.0: - resolution: {integrity: sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==} - - are-we-there-yet@3.0.1: - resolution: {integrity: sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - argparse@2.0.1: resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} @@ -511,16 +495,10 @@ packages: asynciterator.prototype@1.0.0: resolution: {integrity: sha512-wwHYEIS0Q80f5mosx3L/dfG5t5rjEa9Ft51GTaNt862EnpyGHpgz2RkZvLPp1oF5TnAiTohkEKVEu8pQPJI7Vg==} - asynckit@0.4.0: - resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} - available-typed-arrays@1.0.5: resolution: {integrity: sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==} engines: {node: '>= 0.4'} - axios@1.7.7: - resolution: {integrity: sha512-S4kL7XrjgBmvdGut0sN3yJxqYzrDOnivkBiN0OFs6hLiUam3UPvswUo0kqGyhqUZGEOytHyumEdXsAkgCOUf3Q==} - balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -563,19 +541,6 @@ packages: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} - chownr@2.0.0: - resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} - engines: {node: '>=10'} - - cliui@8.0.1: - resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} - engines: {node: '>=12'} - - cmake-js@7.3.0: - resolution: {integrity: sha512-dXs2zq9WxrV87bpJ+WbnGKv8WUBXDw8blNiwNHoRe/it+ptscxhQHKB1SJXa1w+kocLMeP28Tk4/eTCezg4o+w==} - engines: {node: '>= 14.15.0'} - hasBin: true - color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -583,20 +548,9 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - color-support@1.1.3: - resolution: {integrity: sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==} - hasBin: true - - combined-stream@1.0.8: - resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} - engines: {node: '>= 0.8'} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} - console-control-strings@1.1.0: - resolution: {integrity: sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==} - cross-spawn@7.0.3: resolution: {integrity: sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==} engines: {node: '>= 8'} @@ -616,10 +570,6 @@ packages: supports-color: optional: true - deep-extend@0.6.0: - resolution: {integrity: sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==} - engines: {node: '>=4.0.0'} - deep-is@0.1.4: resolution: {integrity: sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==} @@ -643,13 +593,6 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} - delayed-stream@1.0.0: - resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} - engines: {node: '>=0.4.0'} - - delegates@1.0.0: - resolution: {integrity: sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==} - dir-glob@3.0.1: resolution: {integrity: sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==} engines: {node: '>=8'} @@ -662,9 +605,6 @@ packages: resolution: {integrity: sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==} engines: {node: '>=6.0.0'} - emoji-regex@8.0.0: - resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} - es-abstract@1.22.3: resolution: {integrity: sha512-eiiY8HQeYfYH2Con2berK+To6GrK2RxbPawDkGq4UiCQQfZHb6wX9qQqkbpPqaxQFcl8d9QzZqo0tGE0VcrdwA==} engines: {node: '>= 0.4'} @@ -691,10 +631,6 @@ packages: engines: {node: '>=12'} hasBin: true - escalade@3.2.0: - resolution: {integrity: sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==} - engines: {node: '>=6'} - escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -819,30 +755,9 @@ packages: flatted@3.2.9: resolution: {integrity: sha512-36yxDn5H7OFZQla0/jFJmbIKTdZAQHngCedGxiMmpNfEZM0sdEeT+WczLQrjK6D7o2aiyLYDnkw0R3JK0Qv1RQ==} - follow-redirects@1.15.9: - resolution: {integrity: sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==} - engines: {node: '>=4.0'} - peerDependencies: - debug: '*' - peerDependenciesMeta: - debug: - optional: true - for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} - form-data@4.0.0: - resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} - engines: {node: '>= 6'} - - fs-extra@11.2.0: - resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==} - engines: {node: '>=14.14'} - - fs-minipass@2.1.0: - resolution: {integrity: sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==} - engines: {node: '>= 8'} - fs.realpath@1.0.0: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} @@ -856,15 +771,6 @@ packages: functions-have-names@1.2.3: resolution: {integrity: sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==} - gauge@4.0.4: - resolution: {integrity: sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - - get-caller-file@2.0.5: - resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} - engines: {node: 6.* || 8.* || >= 10.*} - get-intrinsic@1.2.2: resolution: {integrity: sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==} @@ -903,9 +809,6 @@ packages: gopd@1.0.1: resolution: {integrity: sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==} - graceful-fs@4.2.11: - resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} - graphemer@1.4.0: resolution: {integrity: sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==} @@ -931,9 +834,6 @@ packages: resolution: {integrity: sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==} engines: {node: '>= 0.4'} - has-unicode@2.0.1: - resolution: {integrity: sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==} - hasown@2.0.0: resolution: {integrity: sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==} engines: {node: '>= 0.4'} @@ -973,9 +873,6 @@ packages: inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} - ini@1.3.8: - resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} - internal-slot@1.0.6: resolution: {integrity: sha512-Xj6dv+PsbtwyPpEflsejS+oIZxmMlV44zAhG479uYu89MsjcYOhCFnNyKrkJrihbsiasQyY0afoCl/9BLR65bg==} engines: {node: '>= 0.4'} @@ -1022,10 +919,6 @@ packages: is-finalizationregistry@1.0.2: resolution: {integrity: sha512-0by5vtUJs8iFQb5TYUHHPudOR+qXYIMKtiUzvLIZITZUjknFmziyBJuLhVRc+Ds0dREFlskDNJKYIdIzu/9pfw==} - is-fullwidth-code-point@3.0.0: - resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} - engines: {node: '>=8'} - is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -1126,9 +1019,6 @@ packages: json-stable-stringify-without-jsonify@1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} - jsonfile@6.1.0: - resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} - jsx-ast-utils@3.3.5: resolution: {integrity: sha512-ZZow9HBI5O6EPgSJLUb8n2NKgmVWTwCvHGwFuJlMjvLFqlGG6pjirPhtdsseaLZjSibD8eegzmYpUZwoIlj2cQ==} engines: {node: '>=4.0'} @@ -1144,9 +1034,6 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.isplainobject@4.0.6: - resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} - lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} @@ -1158,9 +1045,6 @@ packages: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} - memory-stream@1.0.0: - resolution: {integrity: sha512-Wm13VcsPIMdG96dzILfij09PvuS3APtcKNh7M28FsCA/w6+1mjR7hhPmfFNoilX9xU7wTdhsH5lJAm6XNzdtww==} - merge-stream@2.0.0: resolution: {integrity: sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==} @@ -1176,14 +1060,6 @@ packages: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} - mime-db@1.52.0: - resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==} - engines: {node: '>= 0.6'} - - mime-types@2.1.35: - resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==} - engines: {node: '>= 0.6'} - mimic-fn@2.1.0: resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==} engines: {node: '>=6'} @@ -1199,39 +1075,12 @@ packages: resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} engines: {node: '>=16 || 14 >=14.17'} - minimist@1.2.8: - resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} - - minipass@3.3.6: - resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} - engines: {node: '>=8'} - - minipass@5.0.0: - resolution: {integrity: sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==} - engines: {node: '>=8'} - - minizlib@2.1.2: - resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} - engines: {node: '>= 8'} - - mkdirp@1.0.4: - resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} - engines: {node: '>=10'} - hasBin: true - ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} - node-addon-api@8.1.0: - resolution: {integrity: sha512-yBY+qqWSv3dWKGODD6OGE6GnTX7Q2r+4+DfpqxHSHh8x0B4EKP9+wVGLS6U/AM1vxSNNmUEuIV5EGhYwPpfOwQ==} - engines: {node: ^18 || ^20 || >= 21} - - node-api-headers@1.3.0: - resolution: {integrity: sha512-8Bviwtw4jNhv0B2qDjj4M5e6GyAuGtxsmZTrFJu3S3Z0+oHwIgSUdIKkKJmZd+EbMo7g3v4PLBbrjxwmZOqMBg==} - npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -1240,11 +1089,6 @@ packages: resolution: {integrity: sha512-sJOdmRGrY2sjNTRMbSvluQqg+8X7ZK61yvzBEIDhz4f8z1TZFYABsqjjCBd/0PUNE9M6QDgHJXQkGUEm7Q+l9Q==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npmlog@6.0.2: - resolution: {integrity: sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==} - engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} - deprecated: This package is no longer supported. - object-assign@4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} @@ -1336,11 +1180,6 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} - pkg-prebuilds@0.2.1: - resolution: {integrity: sha512-FdOlDiRqRL7i9aYzQflhGWCoiJf/8u6Qgzq48gKsRDYejtfjvGb1U5QGSzllcqpNg2a8Swx/9fMgtuVefwU+zw==} - engines: {node: '>= 14.15.0'} - hasBin: true - prelude-ls@1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1361,9 +1200,6 @@ packages: prop-types@15.8.1: resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} - proxy-from-env@1.1.0: - resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} - punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -1371,17 +1207,9 @@ packages: queue-microtask@1.2.3: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} - rc@1.2.8: - resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==} - hasBin: true - react-is@16.13.1: resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - readable-stream@3.6.2: - resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} - engines: {node: '>= 6'} - readable-stream@4.5.2: resolution: {integrity: sha512-yjavECdqeZ3GLXNgRXgeQEdz9fvDDkNKyHnbHRFtOr7/LcfgBcmct7t/ET+HaCTqfh06OzoAxrkN/IfjJBVe+g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -1394,10 +1222,6 @@ packages: resolution: {integrity: sha512-sy6TXMN+hnP/wMy+ISxg3krXx7BAtWVO4UouuCN/ziM9UEne0euamVNafDfvC83bRNr95y0V5iijeDQFUNpvrg==} engines: {node: '>= 0.4'} - require-directory@2.1.1: - resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} - engines: {node: '>=0.10.0'} - resolve-from@4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} @@ -1444,9 +1268,6 @@ packages: engines: {node: '>=10'} hasBin: true - set-blocking@2.0.0: - resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==} - set-function-length@1.1.1: resolution: {integrity: sha512-VoaqjbBJKiWtg4yRcKBQ7g7wnGnLV3M8oLvVWwOk2PdYY6PEFegR1vezXR0tw6fZGF9csVakIRjrJiy2veSBFQ==} engines: {node: '>= 0.4'} @@ -1476,10 +1297,6 @@ packages: standalone-electron-types@1.0.0: resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} - string-width@4.2.3: - resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} - engines: {node: '>=8'} - string.prototype.matchall@4.0.10: resolution: {integrity: sha512-rGXbGmOEosIQi6Qva94HUjgPs9vKW+dkG7Y8Q5O2OYkWL6wFaTRZO8zM4mhP94uX55wgyrXzfS2aGtGzUL7EJQ==} @@ -1508,10 +1325,6 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - strip-json-comments@2.0.1: - resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} - engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -1528,10 +1341,6 @@ packages: resolution: {integrity: sha512-laHF2savN6sMeHCjLRkheIU4wo3Zg9Ln5YOjOo7sZ5dVQW8yF5pPE5SIw1dsPhq3TRp1jisKRCdPhfs/1WMqDA==} engines: {node: ^14.18.0 || >=16.0.0} - tar@6.2.1: - resolution: {integrity: sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==} - engines: {node: '>=10'} - text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} @@ -1586,10 +1395,6 @@ packages: undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} - universalify@2.0.1: - resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==} - engines: {node: '>= 10.0.0'} - untildify@4.0.0: resolution: {integrity: sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==} engines: {node: '>=8'} @@ -1597,12 +1402,6 @@ packages: uri-js@4.4.1: resolution: {integrity: sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==} - url-join@4.0.1: - resolution: {integrity: sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==} - - util-deprecate@1.0.2: - resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - utilium@0.7.1: resolution: {integrity: sha512-2ocvTkI7U8LERmwxL0LhFUvEfN66UqcjF6tMiURvUwSyU7U1QC9gST+3iSUSiGccFfnP3f2EXwHNXOnOzx+lAg==} @@ -1625,31 +1424,12 @@ packages: engines: {node: '>= 8'} hasBin: true - wide-align@1.1.5: - resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} - - wrap-ansi@7.0.0: - resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} - engines: {node: '>=10'} - wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} - y18n@5.0.8: - resolution: {integrity: sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==} - engines: {node: '>=10'} - yallist@4.0.0: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} - yargs-parser@21.1.1: - resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} - engines: {node: '>=12'} - - yargs@17.7.2: - resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} - engines: {node: '>=12'} - yocto-queue@0.1.0: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} @@ -1918,14 +1698,6 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vencord/venmic@6.1.0': - dependencies: - cmake-js: 7.3.0 - node-addon-api: 8.1.0 - pkg-prebuilds: 0.2.1 - transitivePeerDependencies: - - supports-color - '@zenfs/core@1.0.2': dependencies: '@types/node': 20.16.10 @@ -1963,13 +1735,6 @@ snapshots: dependencies: color-convert: 2.0.1 - aproba@2.0.0: {} - - are-we-there-yet@3.0.1: - dependencies: - delegates: 1.0.0 - readable-stream: 3.6.2 - argparse@2.0.1: {} array-buffer-byte-length@1.0.0: @@ -2025,18 +1790,8 @@ snapshots: dependencies: has-symbols: 1.0.3 - asynckit@0.4.0: {} - available-typed-arrays@1.0.5: {} - axios@1.7.7(debug@4.3.4): - dependencies: - follow-redirects: 1.15.9(debug@4.3.4) - form-data: 4.0.0 - proxy-from-env: 1.1.0 - transitivePeerDependencies: - - debug - balanced-match@1.0.2: {} base64-js@1.5.1: {} @@ -2082,48 +1837,14 @@ snapshots: ansi-styles: 4.3.0 supports-color: 7.2.0 - chownr@2.0.0: {} - - cliui@8.0.1: - dependencies: - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrap-ansi: 7.0.0 - - cmake-js@7.3.0: - dependencies: - axios: 1.7.7(debug@4.3.4) - debug: 4.3.4 - fs-extra: 11.2.0 - lodash.isplainobject: 4.0.6 - memory-stream: 1.0.0 - node-api-headers: 1.3.0 - npmlog: 6.0.2 - rc: 1.2.8 - semver: 7.5.4 - tar: 6.2.1 - url-join: 4.0.1 - which: 2.0.2 - yargs: 17.7.2 - transitivePeerDependencies: - - supports-color - color-convert@2.0.1: dependencies: color-name: 1.1.4 color-name@1.1.4: {} - color-support@1.1.3: {} - - combined-stream@1.0.8: - dependencies: - delayed-stream: 1.0.0 - concat-map@0.0.1: {} - console-control-strings@1.1.0: {} - cross-spawn@7.0.3: dependencies: path-key: 3.1.1 @@ -2138,8 +1859,6 @@ snapshots: dependencies: ms: 2.1.2 - deep-extend@0.6.0: {} - deep-is@0.1.4: {} default-browser-id@3.0.0: @@ -2168,10 +1887,6 @@ snapshots: has-property-descriptors: 1.0.1 object-keys: 1.1.1 - delayed-stream@1.0.0: {} - - delegates@1.0.0: {} - dir-glob@3.0.1: dependencies: path-type: 4.0.0 @@ -2184,8 +1899,6 @@ snapshots: dependencies: esutils: 2.0.3 - emoji-regex@8.0.0: {} - es-abstract@1.22.3: dependencies: array-buffer-byte-length: 1.0.0 @@ -2288,8 +2001,6 @@ snapshots: '@esbuild/win32-ia32': 0.19.3 '@esbuild/win32-x64': 0.19.3 - escalade@3.2.0: {} - escape-string-regexp@4.0.0: {} eslint-config-prettier@9.1.0(eslint@8.55.0): @@ -2469,30 +2180,10 @@ snapshots: flatted@3.2.9: {} - follow-redirects@1.15.9(debug@4.3.4): - optionalDependencies: - debug: 4.3.4 - for-each@0.3.3: dependencies: is-callable: 1.2.7 - form-data@4.0.0: - dependencies: - asynckit: 0.4.0 - combined-stream: 1.0.8 - mime-types: 2.1.35 - - fs-extra@11.2.0: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.1 - - fs-minipass@2.1.0: - dependencies: - minipass: 3.3.6 - fs.realpath@1.0.0: {} function-bind@1.1.2: {} @@ -2506,19 +2197,6 @@ snapshots: functions-have-names@1.2.3: {} - gauge@4.0.4: - dependencies: - aproba: 2.0.0 - color-support: 1.1.3 - console-control-strings: 1.1.0 - has-unicode: 2.0.1 - signal-exit: 3.0.7 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wide-align: 1.1.5 - - get-caller-file@2.0.5: {} - get-intrinsic@1.2.2: dependencies: function-bind: 1.1.2 @@ -2571,8 +2249,6 @@ snapshots: dependencies: get-intrinsic: 1.2.2 - graceful-fs@4.2.11: {} - graphemer@1.4.0: {} has-bigints@1.0.2: {} @@ -2591,8 +2267,6 @@ snapshots: dependencies: has-symbols: 1.0.3 - has-unicode@2.0.1: {} - hasown@2.0.0: dependencies: function-bind: 1.1.2 @@ -2621,8 +2295,6 @@ snapshots: inherits@2.0.4: {} - ini@1.3.8: {} - internal-slot@1.0.6: dependencies: get-intrinsic: 1.2.2 @@ -2668,8 +2340,6 @@ snapshots: dependencies: call-bind: 1.0.5 - is-fullwidth-code-point@3.0.0: {} - is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.0 @@ -2760,12 +2430,6 @@ snapshots: json-stable-stringify-without-jsonify@1.0.1: {} - jsonfile@6.1.0: - dependencies: - universalify: 2.0.1 - optionalDependencies: - graceful-fs: 4.2.11 - jsx-ast-utils@3.3.5: dependencies: array-includes: 3.1.7 @@ -2786,8 +2450,6 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.isplainobject@4.0.6: {} - lodash.merge@4.6.2: {} loose-envify@1.4.0: @@ -2798,10 +2460,6 @@ snapshots: dependencies: yallist: 4.0.0 - memory-stream@1.0.0: - dependencies: - readable-stream: 3.6.2 - merge-stream@2.0.0: {} merge2@1.4.1: {} @@ -2813,12 +2471,6 @@ snapshots: braces: 3.0.2 picomatch: 2.3.1 - mime-db@1.52.0: {} - - mime-types@2.1.35: - dependencies: - mime-db: 1.52.0 - mimic-fn@2.1.0: {} mimic-fn@4.0.0: {} @@ -2831,29 +2483,10 @@ snapshots: dependencies: brace-expansion: 2.0.1 - minimist@1.2.8: {} - - minipass@3.3.6: - dependencies: - yallist: 4.0.0 - - minipass@5.0.0: {} - - minizlib@2.1.2: - dependencies: - minipass: 3.3.6 - yallist: 4.0.0 - - mkdirp@1.0.4: {} - ms@2.1.2: {} natural-compare@1.4.0: {} - node-addon-api@8.1.0: {} - - node-api-headers@1.3.0: {} - npm-run-path@4.0.1: dependencies: path-key: 3.1.1 @@ -2862,13 +2495,6 @@ snapshots: dependencies: path-key: 4.0.0 - npmlog@6.0.2: - dependencies: - are-we-there-yet: 3.0.1 - console-control-strings: 1.1.0 - gauge: 4.0.4 - set-blocking: 2.0.0 - object-assign@4.1.1: {} object-inspect@1.13.1: {} @@ -2961,10 +2587,6 @@ snapshots: picomatch@2.3.1: {} - pkg-prebuilds@0.2.1: - dependencies: - yargs: 17.7.2 - prelude-ls@1.2.1: {} prettier-linter-helpers@1.0.0: @@ -2981,27 +2603,12 @@ snapshots: object-assign: 4.1.1 react-is: 16.13.1 - proxy-from-env@1.1.0: {} - punycode@2.3.1: {} queue-microtask@1.2.3: {} - rc@1.2.8: - dependencies: - deep-extend: 0.6.0 - ini: 1.3.8 - minimist: 1.2.8 - strip-json-comments: 2.0.1 - react-is@16.13.1: {} - readable-stream@3.6.2: - dependencies: - inherits: 2.0.4 - string_decoder: 1.3.0 - util-deprecate: 1.0.2 - readable-stream@4.5.2: dependencies: abort-controller: 3.0.0 @@ -3025,8 +2632,6 @@ snapshots: define-properties: 1.2.1 set-function-name: 2.0.1 - require-directory@2.1.1: {} - resolve-from@4.0.0: {} resolve@2.0.0-next.5: @@ -3072,8 +2677,6 @@ snapshots: dependencies: lru-cache: 6.0.0 - set-blocking@2.0.0: {} - set-function-length@1.1.1: dependencies: define-data-property: 1.1.1 @@ -3107,12 +2710,6 @@ snapshots: dependencies: '@types/node': 18.17.17 - string-width@4.2.3: - dependencies: - emoji-regex: 8.0.0 - is-fullwidth-code-point: 3.0.0 - strip-ansi: 6.0.1 - string.prototype.matchall@4.0.10: dependencies: call-bind: 1.0.5 @@ -3155,8 +2752,6 @@ snapshots: strip-final-newline@3.0.0: {} - strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} supports-color@7.2.0: @@ -3170,15 +2765,6 @@ snapshots: '@pkgr/utils': 2.4.2 tslib: 2.6.2 - tar@6.2.1: - dependencies: - chownr: 2.0.0 - fs-minipass: 2.1.0 - minipass: 5.0.0 - minizlib: 2.1.2 - mkdirp: 1.0.4 - yallist: 4.0.0 - text-table@0.2.0: {} titleize@3.0.0: {} @@ -3237,18 +2823,12 @@ snapshots: undici-types@6.19.8: {} - universalify@2.0.1: {} - untildify@4.0.0: {} uri-js@4.4.1: dependencies: punycode: 2.3.1 - url-join@4.0.1: {} - - util-deprecate@1.0.2: {} - utilium@0.7.1: dependencies: eventemitter3: 5.0.1 @@ -3295,32 +2875,8 @@ snapshots: dependencies: isexe: 2.0.0 - wide-align@1.1.5: - dependencies: - string-width: 4.2.3 - - wrap-ansi@7.0.0: - dependencies: - ansi-styles: 4.3.0 - string-width: 4.2.3 - strip-ansi: 6.0.1 - wrappy@1.0.2: {} - y18n@5.0.8: {} - yallist@4.0.0: {} - yargs-parser@21.1.1: {} - - yargs@17.7.2: - dependencies: - cliui: 8.0.1 - escalade: 3.2.0 - get-caller-file: 2.0.5 - require-directory: 2.1.1 - string-width: 4.2.3 - y18n: 5.0.8 - yargs-parser: 21.1.1 - yocto-queue@0.1.0: {} From 21e3ff130cf025fae64ef7419df8b44a1433135e Mon Sep 17 00:00:00 2001 From: adryd Date: Tue, 8 Oct 2024 12:19:22 -0400 Subject: [PATCH 40/73] nativeFixes: update descriptions in manifest Also prettier on host.ts noHideToken: clarify the extension's purpose --- packages/core-extensions/src/nativeFixes/host.ts | 8 ++++++-- .../core-extensions/src/nativeFixes/manifest.json | 15 +++++---------- .../core-extensions/src/noHideToken/manifest.json | 3 ++- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/core-extensions/src/nativeFixes/host.ts b/packages/core-extensions/src/nativeFixes/host.ts index b0ec298..f341b5d 100644 --- a/packages/core-extensions/src/nativeFixes/host.ts +++ b/packages/core-extensions/src/nativeFixes/host.ts @@ -64,9 +64,13 @@ if ( (moonlightHost.getConfigOption("nativeFixes", "vaapi") ?? true) && !noAccel ) { - enabledFeatures.push("VaapiVideoEncoder", "VaapiVideoDecoder"); if (process.platform === "linux") - enabledFeatures.push("VaapiVideoDecodeLinuxGL"); + // These will eventually be renamed https://source.chromium.org/chromium/chromium/src/+/5482210941a94d70406b8da962426e4faca7fce4 + enabledFeatures.push( + "VaapiVideoEncoder", + "VaapiVideoDecoder", + "VaapiVideoDecodeLinuxGL" + ); } app.commandLine.appendSwitch( diff --git a/packages/core-extensions/src/nativeFixes/manifest.json b/packages/core-extensions/src/nativeFixes/manifest.json index 2e0e810..29368b5 100644 --- a/packages/core-extensions/src/nativeFixes/manifest.json +++ b/packages/core-extensions/src/nativeFixes/manifest.json @@ -3,13 +3,8 @@ "meta": { "name": "Native Fixes", "tagline": "Various configurable fixes for Discord and Electron", - "authors": [ - "Cynosphere", - "adryd" - ], - "tags": [ - "fixes" - ] + "authors": ["Cynosphere", "adryd"], + "tags": ["fixes"] }, "settings": { "devtoolsThemeFix": { @@ -32,13 +27,13 @@ }, "linuxSpeechDispatcher": { "displayName": "Enable speech-dispatcher for TTS on Linux", - "description": "Has no effect on other operating systems", + "description": "Fixes text-to-speech. Has no effect on other operating systems", "type": "boolean", "default": true }, "vaapi": { - "displayName": "Enable VAAPI features", - "description": "Provides additional hardware acceleration", + "displayName": "Enable VAAPI features on Linux", + "description": "Provides hardware accelerated video encode and decode. Has no effect on other operating systems", "type": "boolean", "default": true } diff --git a/packages/core-extensions/src/noHideToken/manifest.json b/packages/core-extensions/src/noHideToken/manifest.json index 550f21c..36d15fc 100644 --- a/packages/core-extensions/src/noHideToken/manifest.json +++ b/packages/core-extensions/src/noHideToken/manifest.json @@ -3,7 +3,8 @@ "apiLevel": 2, "meta": { "name": "No Hide Token", - "tagline": "Disables removal of token from localStorage when opening dev tools", + "tagline": "Prevents you from being logged-out on hard-crash", + "description": "Prevents you from being logged-out on hard-crash by disabling removal of token from localStorage when opening dev tools", "authors": ["adryd"], "tags": ["dangerZone", "development"] } From 94bef0821407282ae826defef16b1470517ccc95 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 13:07:18 -0400 Subject: [PATCH 41/73] Use mappings require type --- packages/types/src/discord/webpack.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/packages/types/src/discord/webpack.ts b/packages/types/src/discord/webpack.ts index 2469c96..fc3878d 100644 --- a/packages/types/src/discord/webpack.ts +++ b/packages/types/src/discord/webpack.ts @@ -1,10 +1,12 @@ import WebpackRequire from "./require"; +import { WebpackRequire as MappingsWebpackRequire } from "@moonlight-mod/mappings"; -export type WebpackRequireType = typeof WebpackRequire & { - c: Record; - m: Record; - e: (module: number | string) => Promise; -}; +export type WebpackRequireType = typeof MappingsWebpackRequire & + typeof WebpackRequire & { + c: Record; + m: Record; + e: (module: number | string) => Promise; + }; export type WebpackModule = { id: string | number; From be88233d1787918070145bba1e86825806e4243a Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 14:23:08 -0400 Subject: [PATCH 42/73] Clean up Nix a bit, expose home-manager module --- flake.nix | 89 ++------------------------------------------ nix/default.nix | 28 ++++++++++++++ nix/home-manager.nix | 56 ++++++++++++++++++++++++++++ nix/overlay.nix | 60 +++++++++++++++++++++++++++++ 4 files changed, 147 insertions(+), 86 deletions(-) create mode 100644 nix/default.nix create mode 100644 nix/home-manager.nix create mode 100644 nix/overlay.nix diff --git a/flake.nix b/flake.nix index 41ebfee..ef9d25b 100644 --- a/flake.nix +++ b/flake.nix @@ -8,92 +8,7 @@ }; outputs = { self, nixpkgs, flake-utils, pnpm2nix }: - let - mkMoonlight = { pkgs, mkPnpmPackage }: - mkPnpmPackage rec { - workspace = ./.; - src = ./.; - components = [ - "packages/core" - "packages/core-extensions" - "packages/injector" - "packages/node-preload" - "packages/types" - "packages/web-preload" - ]; - distDirs = [ "dist" ]; - - copyNodeModules = true; - buildPhase = "pnpm run build"; - installPhase = "cp -r dist $out"; - - meta = with pkgs.lib; { - description = "Yet another Discord mod"; - homepage = "https://moonlight-mod.github.io/"; - license = licenses.lgpl3; - maintainers = with maintainers; [ notnite ]; - }; - }; - - nameTable = { - discord = "Discord"; - discord-ptb = "DiscordPTB"; - discord-canary = "DiscordCanary"; - discord-development = "DiscordDevelopment"; - }; - - darwinNameTable = { - discord = "Discord"; - discord-ptb = "Discord PTB"; - discord-canary = "Discord Canary"; - discord-development = "Discord Development"; - }; - - mkOverride = prev: moonlight: name: - let discord = prev.${name}; - in discord.overrideAttrs (old: { - installPhase = let - folderName = nameTable.${name}; - darwinFolderName = darwinNameTable.${name}; - - injected = '' - require("${moonlight}/injector").inject( - require("path").join(__dirname, "../_app.asar") - ); - ''; - - packageJson = '' - {"name":"discord","main":"./injector.js","private":true} - ''; - - in old.installPhase + "\n" + '' - resources="$out/opt/${folderName}/resources" - if [ ! -d "$resources" ]; then - resources="$out/Applications/${darwinFolderName}.app/Contents/Resources" - fi - - mv "$resources/app.asar" "$resources/_app.asar" - mkdir -p "$resources/app" - - cat > "$resources/app/injector.js" < "$resources/app/package.json" - ''; - }); - - overlay = final: prev: rec { - moonlight-mod = mkMoonlight { - pkgs = final; - mkPnpmPackage = pnpm2nix.packages.${final.system}.mkPnpmPackage; - }; - discord = mkOverride prev moonlight-mod "discord"; - discord-ptb = mkOverride prev moonlight-mod "discord-ptb"; - discord-canary = mkOverride prev moonlight-mod "discord-canary"; - discord-development = - mkOverride prev moonlight-mod "discord-development"; - }; + let overlay = import ./nix/overlay.nix { inherit pnpm2nix; }; in flake-utils.lib.eachDefaultSystem (system: let pkgs = import nixpkgs { @@ -102,6 +17,7 @@ overlays = [ overlay ]; }; in { + # Don't use these unless you're testing things packages.default = pkgs.moonlight-mod; packages.moonlight-mod = pkgs.moonlight-mod; @@ -111,5 +27,6 @@ packages.discord-development = pkgs.discord-development; }) // { overlays.default = overlay; + homeModules.default = ./nix/home-manager.nix; }; } diff --git a/nix/default.nix b/nix/default.nix new file mode 100644 index 0000000..8ef726b --- /dev/null +++ b/nix/default.nix @@ -0,0 +1,28 @@ +{ pkgs, mkPnpmPackage }: + +mkPnpmPackage rec { + workspace = ./..; + src = ./..; + + # Work around a bug with how it expects dist + components = [ + "packages/core" + "packages/core-extensions" + "packages/injector" + "packages/node-preload" + "packages/types" + "packages/web-preload" + ]; + distDirs = [ "dist" ]; + + copyNodeModules = true; + buildPhase = "pnpm run build"; + installPhase = "cp -r dist $out"; + + meta = with pkgs.lib; { + description = "Yet another Discord mod"; + homepage = "https://moonlight-mod.github.io/"; + license = licenses.lgpl3; + maintainers = with maintainers; [ notnite ]; + }; +} diff --git a/nix/home-manager.nix b/nix/home-manager.nix new file mode 100644 index 0000000..a014d46 --- /dev/null +++ b/nix/home-manager.nix @@ -0,0 +1,56 @@ +{ config, lib, pkgs, ... }: + +let cfg = config.programs.moonlight-mod; +in { + options.programs.moonlight-mod = { + enable = lib.mkEnableOption "Yet another Discord mod"; + + configs = let + # TODO: type this + type = lib.types.nullOr (lib.types.attrs); + default = null; + in { + stable = lib.mkOption { + inherit type default; + description = "Configuration for Discord Stable"; + }; + + ptb = lib.mkOption { + inherit type default; + description = "Configuration for Discord PTB"; + }; + + canary = lib.mkOption { + inherit type default; + description = "Configuration for Discord Canary"; + }; + + development = lib.mkOption { + inherit type default; + description = "Configuration for Discord Development"; + }; + }; + }; + + config = lib.mkIf cfg.enable { + xdg.configFile."moonlight-mod/stable.json" = + lib.mkIf (cfg.configs.stable != null) { + text = builtins.toJSON cfg.configs.stable; + }; + + xdg.configFile."moonlight-mod/ptb.json" = + lib.mkIf (cfg.configs.ptb != null) { + text = builtins.toJSON cfg.configs.ptb; + }; + + xdg.configFile."moonlight-mod/canary.json" = + lib.mkIf (cfg.configs.canary != null) { + text = builtins.toJSON cfg.configs.canary; + }; + + xdg.configFile."moonlight-mod/development.json" = + lib.mkIf (cfg.configs.development != null) { + text = builtins.toJSON cfg.configs.development; + }; + }; +} diff --git a/nix/overlay.nix b/nix/overlay.nix new file mode 100644 index 0000000..122aa52 --- /dev/null +++ b/nix/overlay.nix @@ -0,0 +1,60 @@ +{ pnpm2nix }: + +let + nameTable = { + discord = "Discord"; + discord-ptb = "DiscordPTB"; + discord-canary = "DiscordCanary"; + discord-development = "DiscordDevelopment"; + }; + + darwinNameTable = { + discord = "Discord"; + discord-ptb = "Discord PTB"; + discord-canary = "Discord Canary"; + discord-development = "Discord Development"; + }; + + mkOverride = prev: moonlight: name: + let discord = prev.${name}; + in discord.overrideAttrs (old: { + installPhase = let + folderName = nameTable.${name}; + darwinFolderName = darwinNameTable.${name}; + + injected = '' + require("${moonlight}/injector").inject( + require("path").join(__dirname, "../_app.asar") + ); + ''; + + packageJson = '' + {"name":"discord","main":"./injector.js","private":true} + ''; + + in old.installPhase + "\n" + '' + resources="$out/opt/${folderName}/resources" + if [ ! -d "$resources" ]; then + resources="$out/Applications/${darwinFolderName}.app/Contents/Resources" + fi + + mv "$resources/app.asar" "$resources/_app.asar" + mkdir -p "$resources/app" + + cat > "$resources/app/injector.js" < "$resources/app/package.json" + ''; + }); +in final: prev: rec { + moonlight-mod = final.callPackage ./default.nix { + pkgs = final; + mkPnpmPackage = pnpm2nix.packages.${final.system}.mkPnpmPackage; + }; + discord = mkOverride prev moonlight-mod "discord"; + discord-ptb = mkOverride prev moonlight-mod "discord-ptb"; + discord-canary = mkOverride prev moonlight-mod "discord-canary"; + discord-development = mkOverride prev moonlight-mod "discord-development"; +} From fc419b4a9821de73c9076d721a56e40f19251e67 Mon Sep 17 00:00:00 2001 From: adryd Date: Tue, 8 Oct 2024 15:30:37 -0400 Subject: [PATCH 43/73] rocketship: Forcibly use web krisp equivelant --- .../core-extensions/src/rocketship/index.ts | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/packages/core-extensions/src/rocketship/index.ts b/packages/core-extensions/src/rocketship/index.ts index 9caad9c..1169ac2 100644 --- a/packages/core-extensions/src/rocketship/index.ts +++ b/packages/core-extensions/src/rocketship/index.ts @@ -107,5 +107,24 @@ export const patches: Patch[] = [ match: /.\.isPlatformEmbedded/, replacement: "false" } + }, + { + // Matching MediaEngineStore + find: '"displayName","MediaEngineStore")', + replace: [ + // Prevent loading of krisp native module by stubbing out desktop checks + { + match: + /\(\(0,.\.isWindows\)\(\)\|\|\(0,.\.isLinux\)\(\)\|\|.+?&&!__OVERLAY__/, + replacement: (orig, macosPlatformCheck) => `false&&!__OVERLAY__` + }, + // Enable loading of web krisp equivelant by replacing isWeb with true + { + match: + /\(0,.\.isWeb\)\(\)&&(.{1,2}\.supports\(.{1,2}\..{1,2}.NOISE_CANCELLATION)/, + replacement: (orig, supportsNoiseCancellation) => + `true&&${supportsNoiseCancellation}` + } + ] } ]; From 045cb1af6cd4109193671b9280326e9767fc5b84 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 15:34:22 -0400 Subject: [PATCH 44/73] Extract notice code into separate library --- .../core-extensions/src/moonbase/index.tsx | 48 +----- .../src/moonbase/manifest.json | 2 +- packages/core-extensions/src/moonbase/node.ts | 41 +++--- .../src/moonbase/webpackModules/stores.ts | 138 +++++++++--------- .../src/moonbase/webpackModules/updates.ts | 89 +++++++++-- .../moonbase/webpackModules/updatesNotice.tsx | 95 ------------ packages/core-extensions/src/notices/index.ts | 46 ++++++ .../core-extensions/src/notices/manifest.json | 10 ++ .../src/notices/webpackModules/component.tsx | 56 +++++++ .../src/notices/webpackModules/notices.ts | 58 ++++++++ packages/types/src/coreExtensions.ts | 1 + packages/types/src/coreExtensions/notices.ts | 21 +++ packages/types/src/discord/require.ts | 3 + packages/types/src/import.d.ts | 6 + 14 files changed, 374 insertions(+), 240 deletions(-) delete mode 100644 packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx create mode 100644 packages/core-extensions/src/notices/index.ts create mode 100644 packages/core-extensions/src/notices/manifest.json create mode 100644 packages/core-extensions/src/notices/webpackModules/component.tsx create mode 100644 packages/core-extensions/src/notices/webpackModules/notices.ts create mode 100644 packages/types/src/coreExtensions/notices.ts diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index f28166c..5e10d35 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -1,4 +1,4 @@ -import { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; +import { ExtensionWebpackModule } from "@moonlight-mod/types"; export const webpackModules: Record = { stores: { @@ -32,50 +32,18 @@ export const webpackModules: Record = { }, updates: { - dependencies: [ - { id: "discord/Dispatcher" }, - { ext: "moonbase", id: "stores" } - ], - entrypoint: true - }, - - updatesNotice: { dependencies: [ { id: "react" }, - { id: "discord/Dispatcher" }, - { id: "discord/components/common/index" }, - { id: "discord/packages/flux" }, - { ext: "spacepack", id: "spacepack" }, - { ext: "moonbase", id: "stores" } - ] - } -}; - -export const patches: Patch[] = [ - { - find: ".GUILD_RAID_NOTIFICATION:", - replace: { - match: - /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/, - replacement: (orig, createElement) => - `case "__moonlight_updates":return${createElement}(require("moonbase_updatesNotice").default,{});${orig}` - } - }, - { - find: '"NoticeStore"', - replace: [ - { - match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/, - replacement: (orig: string) => - `__moonlight_updates:{predicate:()=>require("moonbase_stores").MoonbaseSettingsStore.shouldShowUpdateNotice()},${orig}` - }, + { ext: "moonbase", id: "stores" }, + { ext: "notices", id: "notices" }, { - match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g, - replacement: (_, orig) => `=["__moonlight_updates",${orig}` + ext: "spacepack", + id: "spacepack" } - ] + ], + entrypoint: true } -]; +}; export const styles = [ ".moonbase-settings > :first-child { margin-top: 0px; }", diff --git a/packages/core-extensions/src/moonbase/manifest.json b/packages/core-extensions/src/moonbase/manifest.json index 8fb4179..159acde 100644 --- a/packages/core-extensions/src/moonbase/manifest.json +++ b/packages/core-extensions/src/moonbase/manifest.json @@ -6,7 +6,7 @@ "tagline": "The official settings UI for moonlight", "authors": ["Cynosphere", "NotNite"] }, - "dependencies": ["spacepack", "settings", "common"], + "dependencies": ["spacepack", "settings", "common", "notices"], "settings": { "sections": { "displayName": "Split into sections", diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index d1e2bdc..3e1ffb2 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -8,28 +8,33 @@ import { githubRepo, userAgent, nightlyRefUrl } from "./consts"; const logger = moonlightNode.getLogger("moonbase"); async function checkForMoonlightUpdate() { - if (moonlightNode.branch === "stable") { - const req = await fetch( - `https://api.github.com/repos/${githubRepo}/releases/latest`, - { + try { + if (moonlightNode.branch === "stable") { + const req = await fetch( + `https://api.github.com/repos/${githubRepo}/releases/latest`, + { + headers: { + "User-Agent": userAgent + } + } + ); + const json: { name: string } = await req.json(); + return json.name !== moonlightNode.version ? json.name : null; + } else if (moonlightNode.branch === "nightly") { + const req = await fetch(nightlyRefUrl, { headers: { "User-Agent": userAgent } - } - ); - const json: { name: string } = await req.json(); - return json.name !== moonlightNode.version ? json.name : null; - } else if (moonlightNode.branch === "nightly") { - const req = await fetch(nightlyRefUrl, { - headers: { - "User-Agent": userAgent - } - }); - const ref = (await req.text()).split("\n")[0]; - return ref !== moonlightNode.version ? ref : null; - } + }); + const ref = (await req.text()).split("\n")[0]; + return ref !== moonlightNode.version ? ref : null; + } - return null; + return null; + } catch (e) { + logger.error("Error checking for moonlight update", e); + return null; + } } async function fetchRepositories(repos: string[]) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index b5afd27..dc78599 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -18,29 +18,33 @@ if (window._moonlightBrowserFS != null) { const browserFS = window._moonlightBrowserFS!; natives = { checkForMoonlightUpdate: async () => { - // TODO - if (moonlight.branch === "stable") { - const req = await fetch( - `https://api.github.com/repos/${githubRepo}/releases/latest`, - { + try { + if (moonlight.branch === "stable") { + const req = await fetch( + `https://api.github.com/repos/${githubRepo}/releases/latest`, + { + headers: { + "User-Agent": userAgent + } + } + ); + const json: { name: string } = await req.json(); + return json.name !== moonlight.version ? json.name : null; + } else if (moonlight.branch === "nightly") { + const req = await fetch(nightlyRefUrl, { headers: { "User-Agent": userAgent } - } - ); - const json: { name: string } = await req.json(); - return json.name !== moonlight.version ? json.name : null; - } else if (moonlight.branch === "nightly") { - const req = await fetch(nightlyRefUrl, { - headers: { - "User-Agent": userAgent - } - }); - const ref = (await req.text()).split("\n")[0]; - return ref !== moonlight.version ? ref : null; - } + }); + const ref = (await req.text()).split("\n")[0]; + return ref !== moonlight.version ? ref : null; + } - return null; + return null; + } catch (e) { + logger.error("Error checking for moonlight update", e); + return null; + } }, fetchRepositories: async (repos) => { @@ -105,7 +109,7 @@ class MoonbaseSettingsStore extends Store { installing: boolean; newVersion: string | null; - private updateNotice: boolean; + shouldShowNotice: boolean; extensions: { [id: number]: MoonbaseExtension }; updates: { [id: number]: { version: string; download: string } }; @@ -122,7 +126,7 @@ class MoonbaseSettingsStore extends Store { this.installing = false; this.newVersion = null; - this.updateNotice = false; + this.shouldShowNotice = false; this.extensions = {}; this.updates = {}; @@ -137,48 +141,54 @@ class MoonbaseSettingsStore extends Store { }; } - natives!.fetchRepositories(this.config.repositories).then((ret) => { - for (const [repo, exts] of Object.entries(ret)) { - try { - for (const ext of exts) { - const level = ext.apiLevel ?? 1; - if (level !== window.moonlight.apiLevel) continue; - - const uniqueId = this.extensionIndex++; - const extensionData = { - id: ext.id, - uniqueId, - manifest: ext, - source: { type: ExtensionLoadSource.Normal, url: repo }, - state: ExtensionState.NotDownloaded - }; - - if (this.alreadyExists(extensionData)) { - if (this.hasUpdate(extensionData)) { - this.updates[uniqueId] = { - version: ext.version!, - download: ext.download - }; + natives! + .fetchRepositories(this.config.repositories) + .then((ret) => { + for (const [repo, exts] of Object.entries(ret)) { + try { + for (const ext of exts) { + const level = ext.apiLevel ?? 1; + if (level !== window.moonlight.apiLevel) continue; + + const uniqueId = this.extensionIndex++; + const extensionData = { + id: ext.id, + uniqueId, + manifest: ext, + source: { type: ExtensionLoadSource.Normal, url: repo }, + state: ExtensionState.NotDownloaded + }; + + if (this.alreadyExists(extensionData)) { + if (this.hasUpdate(extensionData)) { + this.updates[uniqueId] = { + version: ext.version!, + download: ext.download + }; + } + + continue; } - continue; + this.extensions[uniqueId] = extensionData; } - - this.extensions[uniqueId] = extensionData; + } catch (e) { + logger.error(`Error processing repository ${repo}`, e); } - } catch (e) { - logger.error(`Error processing repository ${repo}`, e); } - } - this.emitChange(); - }); - - natives!.checkForMoonlightUpdate().then((version) => { - this.newVersion = version; - - this.emitChange(); - }); + this.emitChange(); + }) + .then(natives!.checkForMoonlightUpdate) + .then((version) => { + this.newVersion = version; + this.emitChange(); + }) + .then(() => { + this.shouldShowNotice = + this.newVersion != null || Object.keys(this.updates).length > 0; + this.emitChange(); + }); } private alreadyExists(ext: MoonbaseExtension) { @@ -383,20 +393,6 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); } - showUpdateNotice() { - this.updateNotice = true; - //this.emitChange(); - } - - dismissUpdateNotice() { - this.updateNotice = false; - //this.emitChange(); - } - - shouldShowUpdateNotice() { - return this.updateNotice; - } - // Required because electron likes to make it immutable sometimes. // This sucks. private clone(obj: T): T { diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.ts b/packages/core-extensions/src/moonbase/webpackModules/updates.ts index 20fe343..232cbc9 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.ts @@ -1,21 +1,80 @@ -import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; +import Notices from "@moonlight-mod/wp/notices_notices"; -function checkForUpdates() { - if ( - MoonbaseSettingsStore.newVersion != null || - Object.keys(MoonbaseSettingsStore.updates).length > 0 - ) { - MoonbaseSettingsStore.showUpdateNotice(); - Dispatcher.dispatch({ - type: "NOTICE_SHOW", - notice: { type: "__moonlight_updates" } +// FIXME: not indexed as importable +const Constants = spacepack.require("discord/Constants"); +const UserSettingsSections = spacepack.findObjectFromKey( + Constants, + "APPEARANCE_THEME_PICKER" +); + +function plural(str: string, num: number) { + return `${str}${num > 1 ? "s" : ""}`; +} + +function listener() { + if (MoonbaseSettingsStore.shouldShowNotice) { + // @ts-expect-error epic type fail + MoonbaseSettingsStore.removeChangeListener(listener); + + const version = MoonbaseSettingsStore.newVersion; + const extensionUpdateCount = Object.keys( + MoonbaseSettingsStore.updates + ).length; + const hasExtensionUpdates = extensionUpdateCount > 0; + + let message; + + if (version != null) { + message = + moonlightNode.branch === "nightly" + ? `A new version of moonlight is available` + : `moonlight ${version} is available`; + } + + if (hasExtensionUpdates) { + let concat = false; + if (message == null) { + message = ""; + } else { + concat = true; + message += ", and "; + } + message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural( + "extension", + extensionUpdateCount + )} can be updated`; + } + + if (message != null) message += "."; + + Notices.addNotice({ + element: message, + color: "moonbase-updates-notice", + buttons: hasExtensionUpdates + ? [ + { + name: "Open Moonbase", + onClick: () => { + const { open } = spacepack.findByExports( + "setSection", + "clearSubsection" + )[0].exports.Z; + + // settings is lazy loaded thus lazily patched + // FIXME: figure out a way to detect if settings has been opened + // alreadyjust so the transition isnt as jarring + open(UserSettingsSections.ACCOUNT); + setTimeout(() => open("moonbase", 0), 0); + return true; + } + } + ] + : [] }); } - - Dispatcher.unsubscribe("CONNECTION_OPEN", checkForUpdates); - Dispatcher.unsubscribe("CONNECTION_OPEN_SUPPLEMENTAL", checkForUpdates); } -Dispatcher.subscribe("CONNECTION_OPEN", checkForUpdates); -Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", checkForUpdates); +// @ts-expect-error epic type fail +MoonbaseSettingsStore.addChangeListener(listener); diff --git a/packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx b/packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx deleted file mode 100644 index eb831df..0000000 --- a/packages/core-extensions/src/moonbase/webpackModules/updatesNotice.tsx +++ /dev/null @@ -1,95 +0,0 @@ -import React from "@moonlight-mod/wp/react"; -import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; -import * as Components from "@moonlight-mod/wp/discord/components/common/index"; -import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; -import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; -import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; - -// FIXME: not indexed as importable -const Constants = spacepack.require("discord/Constants"); -const UserSettingsSections = spacepack.findObjectFromKey( - Constants, - "APPEARANCE_THEME_PICKER" -); - -// FIXME: types -const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components; - -const { open } = spacepack.findByExports("setSection", "clearSubsection")[0] - .exports.Z; - -function dismiss() { - MoonbaseSettingsStore.dismissUpdateNotice(); - Dispatcher.dispatch({ - type: "NOTICE_DISMISS" - }); -} - -function openMoonbase() { - dismiss(); - // settings is lazy loaded thus lazily patched - // FIXME: figure out a way to detect if settings has been opened already - // just so the transition isnt as jarring - open(UserSettingsSections.ACCOUNT); - setTimeout(() => open("moonbase", 0), 0); -} - -function plural(str: string, num: number) { - return `${str}${num > 1 ? "s" : ""}`; -} - -export default function MoonbaseUpdatesNotice() { - const { version, extensionUpdateCount } = useStateFromStoresObject( - [MoonbaseSettingsStore], - () => ({ - version: MoonbaseSettingsStore.newVersion, - extensionUpdateCount: Object.keys(MoonbaseSettingsStore.updates).length - }) - ); - const hasExtensionUpdates = extensionUpdateCount > 0; - - let message; - - if (version != null) { - message = - moonlightNode.branch === "nightly" - ? `A new version of moonlight is available` - : `moonlight ${version} is available`; - } - - if (hasExtensionUpdates) { - let concat = false; - if (message == null) { - message = ""; - } else { - concat = true; - message += ", and "; - } - message += `${extensionUpdateCount} ${concat ? "" : "moonlight "}${plural( - "extension", - extensionUpdateCount - )} can be updated`; - } - - if (message != null) message += "."; - - let openButton; - - if (hasExtensionUpdates) - openButton = ( - - Open Moonbase - - ); - - return ( - - - {message} - {openButton} - - ); -} diff --git a/packages/core-extensions/src/notices/index.ts b/packages/core-extensions/src/notices/index.ts new file mode 100644 index 0000000..5773170 --- /dev/null +++ b/packages/core-extensions/src/notices/index.ts @@ -0,0 +1,46 @@ +import type { ExtensionWebpackModule, Patch } from "@moonlight-mod/types"; + +export const patches: Patch[] = [ + { + find: ".GUILD_RAID_NOTIFICATION:", + replace: { + match: + /(?<=return(\(0,.\.jsx\))\(.+?\);)case .{1,2}\..{1,3}\.GUILD_RAID_NOTIFICATION:/, + replacement: (orig, createElement) => + `case "__moonlight_notice":return${createElement}(require("notices_component").default,{});${orig}` + } + }, + { + find: '"NoticeStore"', + replace: [ + { + match: /\[.{1,2}\..{1,3}\.CONNECT_SPOTIFY\]:{/, + replacement: (orig: string) => + `__moonlight_notice:{predicate:()=>require("notices_notices").default.shouldShowNotice()},${orig}` + }, + { + match: /=\[(.{1,2}\..{1,3}\.QUARANTINED,)/g, + replacement: (_, orig) => `=["__moonlight_notice",${orig}` + } + ] + } +]; + +export const webpackModules: Record = { + notices: { + dependencies: [ + { id: "discord/packages/flux" }, + { id: "discord/Dispatcher" } + ] + }, + + component: { + dependencies: [ + { id: "react" }, + { id: "discord/Dispatcher" }, + { id: "discord/components/common/index" }, + { id: "discord/packages/flux" }, + { ext: "notices", id: "notices" } + ] + } +}; diff --git a/packages/core-extensions/src/notices/manifest.json b/packages/core-extensions/src/notices/manifest.json new file mode 100644 index 0000000..3864a21 --- /dev/null +++ b/packages/core-extensions/src/notices/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "notices", + "apiLevel": 2, + "meta": { + "name": "Notices", + "tagline": "An API for adding notices at the top of the page", + "authors": ["Cynosphere", "NotNite"], + "tags": ["library"] + } +} diff --git a/packages/core-extensions/src/notices/webpackModules/component.tsx b/packages/core-extensions/src/notices/webpackModules/component.tsx new file mode 100644 index 0000000..aa4634b --- /dev/null +++ b/packages/core-extensions/src/notices/webpackModules/component.tsx @@ -0,0 +1,56 @@ +import React from "@moonlight-mod/wp/react"; +import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import * as Components from "@moonlight-mod/wp/discord/components/common/index"; +import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; +import NoticesStore from "@moonlight-mod/wp/notices_notices"; +import type { Notice } from "@moonlight-mod/types/coreExtensions/notices"; + +// FIXME: types +const { Notice, NoticeCloseButton, PrimaryCTANoticeButton } = Components; + +function popAndDismiss(notice: Notice) { + NoticesStore.popNotice(); + if (notice?.onDismiss) { + notice.onDismiss(); + } + if (!NoticesStore.shouldShowNotice()) { + Dispatcher.dispatch({ + type: "NOTICE_DISMISS" + }); + } +} + +export default function UpdateNotice() { + const { notice } = useStateFromStoresObject([NoticesStore], () => ({ + notice: NoticesStore.getCurrentNotice() + })); + + if (notice == null) return <>; + + return ( + + {notice.element} + + {(notice.showClose ?? true) && ( + popAndDismiss(notice)} + noticeType="__moonlight_notice" + /> + )} + + {(notice.buttons ?? []).map((button) => ( + { + if (button.onClick()) { + popAndDismiss(notice); + } + }} + noticeType="__moonlight_notice" + > + {button.name} + + ))} + + ); +} diff --git a/packages/core-extensions/src/notices/webpackModules/notices.ts b/packages/core-extensions/src/notices/webpackModules/notices.ts new file mode 100644 index 0000000..2755bd3 --- /dev/null +++ b/packages/core-extensions/src/notices/webpackModules/notices.ts @@ -0,0 +1,58 @@ +import { Store } from "@moonlight-mod/wp/discord/packages/flux"; +import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; +import type { + Notice, + Notices +} from "@moonlight-mod/types/coreExtensions/notices"; + +// very lazy way of doing this, FIXME +let open = false; + +class NoticesStore extends Store { + private notices: Notice[] = []; + + constructor() { + super(Dispatcher); + } + + addNotice(notice: Notice) { + this.notices.push(notice); + if (open && this.notices.length !== 0) { + Dispatcher.dispatch({ + type: "NOTICE_SHOW", + notice: { type: "__moonlight_notice" } + }); + } + this.emitChange(); + } + + popNotice() { + this.notices.shift(); + this.emitChange(); + } + + getCurrentNotice() { + return this.notices.length > 0 ? this.notices[0] : null; + } + + shouldShowNotice() { + return this.notices.length > 0; + } +} + +const store: Notices = new NoticesStore(); + +function showNotice() { + open = true; + if (store.shouldShowNotice()) { + Dispatcher.dispatch({ + type: "NOTICE_SHOW", + notice: { type: "__moonlight_notice" } + }); + } +} + +Dispatcher.subscribe("CONNECTION_OPEN", showNotice); +Dispatcher.subscribe("CONNECTION_OPEN_SUPPLEMENTAL", showNotice); + +export default store; diff --git a/packages/types/src/coreExtensions.ts b/packages/types/src/coreExtensions.ts index 7b05f87..e49cda1 100644 --- a/packages/types/src/coreExtensions.ts +++ b/packages/types/src/coreExtensions.ts @@ -2,3 +2,4 @@ export * as Spacepack from "./coreExtensions/spacepack"; export * as Settings from "./coreExtensions/settings"; export * as Markdown from "./coreExtensions/markdown"; export * as ContextMenu from "./coreExtensions/contextMenu"; +export * as Notices from "./coreExtensions/notices"; diff --git a/packages/types/src/coreExtensions/notices.ts b/packages/types/src/coreExtensions/notices.ts new file mode 100644 index 0000000..55d9212 --- /dev/null +++ b/packages/types/src/coreExtensions/notices.ts @@ -0,0 +1,21 @@ +import type { Store } from "@moonlight-mod/mappings/discord/packages/flux"; + +export type NoticeButton = { + name: string; + onClick: () => boolean; // return true to dismiss the notice after the button is clicked +}; + +export type Notice = { + element: React.ReactNode; + color?: string; + showClose?: boolean; + buttons?: NoticeButton[]; + onDismiss?: () => void; +}; + +export type Notices = Store & { + addNotice: (notice: Notice) => void; + popNotice: () => void; + getCurrentNotice: () => Notice | null; + shouldShowNotice: () => boolean; +}; diff --git a/packages/types/src/discord/require.ts b/packages/types/src/discord/require.ts index b1f46c6..a5e4f7f 100644 --- a/packages/types/src/discord/require.ts +++ b/packages/types/src/discord/require.ts @@ -2,6 +2,7 @@ import { ContextMenu, EvilItemParser } from "../coreExtensions/contextMenu"; import { Markdown } from "../coreExtensions/markdown"; import { Settings } from "../coreExtensions/settings"; import { Spacepack } from "../coreExtensions/spacepack"; +import { Notices } from "../coreExtensions/notices"; declare function WebpackRequire(id: string): any; @@ -10,6 +11,8 @@ declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu; declare function WebpackRequire(id: "markdown_markdown"): Markdown; +declare function WebpackRequire(id: "notices_notices"): Notices; + declare function WebpackRequire(id: "settings_settings"): { Settings: Settings; default: Settings; diff --git a/packages/types/src/import.d.ts b/packages/types/src/import.d.ts index fb9c6f5..08365bc 100644 --- a/packages/types/src/import.d.ts +++ b/packages/types/src/import.d.ts @@ -17,6 +17,12 @@ declare module "@moonlight-mod/wp/markdown_markdown" { export = Markdown; } +declare module "@moonlight-mod/wp/notices_notices" { + import { CoreExtensions } from "@moonlight-mod/types"; + const Notices: CoreExtensions.Notices.Notices; + export = Notices; +} + declare module "@moonlight-mod/wp/settings_settings" { import { CoreExtensions } from "@moonlight-mod/types"; export const Settings: CoreExtensions.Settings.Settings; From bd6b9bfb52376ce33782b5973fa068ea72c4b3eb Mon Sep 17 00:00:00 2001 From: adryd Date: Tue, 8 Oct 2024 15:52:45 -0400 Subject: [PATCH 45/73] create MoonlightBranch enum --- packages/browser/src/index.ts | 8 ++++++-- packages/core-extensions/src/moonbase/node.ts | 5 +++-- .../src/moonbase/webpackModules/stores.ts | 10 +++++++--- .../src/moonbase/webpackModules/updates.ts | 3 ++- packages/injector/src/index.ts | 4 ++-- packages/node-preload/src/index.ts | 4 ++-- packages/types/src/globals.ts | 12 +++++++++--- packages/web-preload/src/index.ts | 4 ++-- 8 files changed, 33 insertions(+), 17 deletions(-) diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 1ff50c5..8743d06 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -3,7 +3,11 @@ import { readConfig, writeConfig } from "@moonlight-mod/core/config"; import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { getExtensions } from "@moonlight-mod/core/extension"; import { loadExtensions } from "@moonlight-mod/core/extension/loader"; -import { MoonlightBrowserFS, MoonlightNode } from "@moonlight-mod/types"; +import { + MoonlightBranch, + MoonlightBrowserFS, + MoonlightNode +} from "@moonlight-mod/types"; import { IndexedDB } from "@zenfs/dom"; import { configure } from "@zenfs/core"; import * as fs from "@zenfs/core/promises"; @@ -104,7 +108,7 @@ window._moonlightBrowserInit = async () => { nativesCache: {}, version: MOONLIGHT_VERSION, - branch: MOONLIGHT_BRANCH, + branch: MOONLIGHT_BRANCH as MoonlightBranch, getConfig, getConfigOption: (ext: string, name: string) => { diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 3e1ffb2..183b040 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -4,12 +4,13 @@ import path from "path"; import extractAsar from "@moonlight-mod/core/asar"; import { repoUrlFile } from "@moonlight-mod/types/constants"; import { githubRepo, userAgent, nightlyRefUrl } from "./consts"; +import { MoonlightBranch } from "types/src"; const logger = moonlightNode.getLogger("moonbase"); async function checkForMoonlightUpdate() { try { - if (moonlightNode.branch === "stable") { + if (moonlightNode.branch === MoonlightBranch.STABLE) { const req = await fetch( `https://api.github.com/repos/${githubRepo}/releases/latest`, { @@ -20,7 +21,7 @@ async function checkForMoonlightUpdate() { ); const json: { name: string } = await req.json(); return json.name !== moonlightNode.version ? json.name : null; - } else if (moonlightNode.branch === "nightly") { + } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { const req = await fetch(nightlyRefUrl, { headers: { "User-Agent": userAgent diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index a9767ae..9e45e7f 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,4 +1,8 @@ -import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; +import { + Config, + ExtensionLoadSource, + MoonlightBranch +} from "@moonlight-mod/types"; import { ExtensionState, MoonbaseExtension, @@ -19,7 +23,7 @@ if (window._moonlightBrowserFS != null) { natives = { checkForMoonlightUpdate: async () => { try { - if (moonlight.branch === "stable") { + if (moonlight.branch === MoonlightBranch.STABLE) { const req = await fetch( `https://api.github.com/repos/${githubRepo}/releases/latest`, { @@ -30,7 +34,7 @@ if (window._moonlightBrowserFS != null) { ); const json: { name: string } = await req.json(); return json.name !== moonlight.version ? json.name : null; - } else if (moonlight.branch === "nightly") { + } else if (moonlight.branch === MoonlightBranch.NIGHTLY) { const req = await fetch(nightlyRefUrl, { headers: { "User-Agent": userAgent diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.ts b/packages/core-extensions/src/moonbase/webpackModules/updates.ts index 232cbc9..f6270d4 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.ts @@ -1,6 +1,7 @@ import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; import Notices from "@moonlight-mod/wp/notices_notices"; +import { MoonlightBranch } from "types/src"; // FIXME: not indexed as importable const Constants = spacepack.require("discord/Constants"); @@ -28,7 +29,7 @@ function listener() { if (version != null) { message = - moonlightNode.branch === "nightly" + moonlightNode.branch === MoonlightBranch.NIGHTLY ? `A new version of moonlight is available` : `moonlight ${version} is available`; } diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index 49c1399..a7eea23 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -5,7 +5,7 @@ import electron, { app } from "electron"; import Module from "node:module"; -import { constants } from "@moonlight-mod/types"; +import { constants, MoonlightBranch } from "@moonlight-mod/types"; import { readConfig } from "@moonlight-mod/core/config"; import { getExtensions } from "@moonlight-mod/core/extension"; import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; @@ -225,7 +225,7 @@ export async function inject(asarPath: string) { }, version: MOONLIGHT_VERSION, - branch: MOONLIGHT_BRANCH, + branch: MOONLIGHT_BRANCH as MoonlightBranch, getConfig, getConfigOption: (ext: string, name: string) => { diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index e1c76c2..c5c3103 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -3,7 +3,7 @@ import fs from "fs"; import path from "path"; import { readConfig, writeConfig } from "@moonlight-mod/core/config"; -import { constants } from "@moonlight-mod/types"; +import { constants, MoonlightBranch } from "@moonlight-mod/types"; import { getExtensions } from "@moonlight-mod/core/extension"; import { getExtensionsPath, @@ -34,7 +34,7 @@ async function injectGlobals() { nativesCache: {}, version: MOONLIGHT_VERSION, - branch: MOONLIGHT_BRANCH, + branch: MOONLIGHT_BRANCH as MoonlightBranch, getConfig, getConfigOption: (ext: string, name: string) => { diff --git a/packages/types/src/globals.ts b/packages/types/src/globals.ts index b639481..f1a9b07 100644 --- a/packages/types/src/globals.ts +++ b/packages/types/src/globals.ts @@ -19,7 +19,7 @@ export type MoonlightHost = { processedExtensions: ProcessedExtensions; version: string; - branch: string; + branch: MoonlightBranch; getConfig: (ext: string) => ConfigExtension["config"]; getConfigOption: (ext: string, name: string) => T | undefined; @@ -33,7 +33,7 @@ export type MoonlightNode = { nativesCache: Record; version: string; - branch: string; + branch: MoonlightBranch; getConfig: (ext: string) => ConfigExtension["config"]; getConfigOption: (ext: string, name: string) => T | undefined; @@ -78,7 +78,7 @@ export type MoonlightWeb = { }; version: string; - branch: string; + branch: MoonlightBranch; getConfig: (ext: string) => ConfigExtension["config"]; getConfigOption: (ext: string, name: string) => T | undefined; @@ -93,3 +93,9 @@ export enum MoonlightEnv { NodePreload = "node-preload", WebPreload = "web-preload" } + +export enum MoonlightBranch { + STABLE = "stable", + NIGHTLY = "nightly", + DEV = "dev" +} diff --git a/packages/web-preload/src/index.ts b/packages/web-preload/src/index.ts index 666f39c..dcd4f96 100644 --- a/packages/web-preload/src/index.ts +++ b/packages/web-preload/src/index.ts @@ -5,7 +5,7 @@ import { registerPatch, registerWebpackModule } from "@moonlight-mod/core/patch"; -import { constants } from "@moonlight-mod/types"; +import { constants, MoonlightBranch } from "@moonlight-mod/types"; import { installStyles } from "@moonlight-mod/core/styles"; import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import LunAST from "@moonlight-mod/lunast"; @@ -31,7 +31,7 @@ async function load() { }, version: MOONLIGHT_VERSION, - branch: MOONLIGHT_BRANCH, + branch: MOONLIGHT_BRANCH as MoonlightBranch, getConfig: moonlightNode.getConfig.bind(moonlightNode), getConfigOption: moonlightNode.getConfigOption.bind(moonlightNode), From cddfdb383cb1d20e17083fc21a0757847105c67e Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Tue, 8 Oct 2024 14:09:16 -0600 Subject: [PATCH 46/73] contextMenu: fix exports --- .../src/contextMenu/webpackModules/contextMenu.ts | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts b/packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts index 280c35c..068ee92 100644 --- a/packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts +++ b/packages/core-extensions/src/contextMenu/webpackModules/contextMenu.ts @@ -31,7 +31,7 @@ export function addItem( } export const patches: Patch[] = []; -function _patchMenu(props: MenuProps, items: InternalItem[]) { +export function _patchMenu(props: MenuProps, items: InternalItem[]) { const matches = patches.filter((p) => p.navId === props.navId); if (!matches.length) return; @@ -43,7 +43,7 @@ function _patchMenu(props: MenuProps, items: InternalItem[]) { } let menuProps: any; -function _saveProps(self: any, el: any) { +export function _saveProps(self: any, el: any) { menuProps = el.props; const original = self.props.closeContextMenu; @@ -55,12 +55,6 @@ function _saveProps(self: any, el: any) { return el; } -module.exports = { - addItem, - _patchMenu, - _saveProps -}; - // Unmangle Menu elements const code = spacepack.require.m[ From 863e788db263d660921e7f363652b746ec63748a Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Tue, 8 Oct 2024 14:10:30 -0600 Subject: [PATCH 47/73] moonbase: add cache buster to update check --- packages/core-extensions/src/moonbase/node.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 183b040..9249030 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -12,7 +12,7 @@ async function checkForMoonlightUpdate() { try { if (moonlightNode.branch === MoonlightBranch.STABLE) { const req = await fetch( - `https://api.github.com/repos/${githubRepo}/releases/latest`, + `https://api.github.com/repos/${githubRepo}/releases/latest?_=${Date.now()}`, { headers: { "User-Agent": userAgent @@ -22,7 +22,7 @@ async function checkForMoonlightUpdate() { const json: { name: string } = await req.json(); return json.name !== moonlightNode.version ? json.name : null; } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { - const req = await fetch(nightlyRefUrl, { + const req = await fetch(`${nightlyRefUrl}?_=${Date.now()}`, { headers: { "User-Agent": userAgent } From 194f084ee2a43be5233b5c29299308cc15fc6178 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 17:33:11 -0400 Subject: [PATCH 48/73] Abstract FS for browser --- packages/browser/src/index.ts | 33 ++--- .../core-extensions/src/moonbase/consts.ts | 3 - .../core-extensions/src/moonbase/native.ts | 104 ++++++++++++++++ packages/core-extensions/src/moonbase/node.ts | 115 +----------------- .../src/moonbase/webpackModules/stores.ts | 110 +---------------- packages/core/src/config.ts | 69 ++++------- packages/core/src/extension.ts | 87 ++----------- packages/core/src/fs.ts | 56 +++++++++ packages/core/src/util/data.ts | 56 +++++---- packages/node-preload/src/index.ts | 13 +- packages/types/src/fs.ts | 17 +++ packages/types/src/globals.ts | 27 ++-- packages/types/src/index.ts | 5 +- 13 files changed, 289 insertions(+), 406 deletions(-) delete mode 100644 packages/core-extensions/src/moonbase/consts.ts create mode 100644 packages/core-extensions/src/moonbase/native.ts create mode 100644 packages/core/src/fs.ts create mode 100644 packages/types/src/fs.ts diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 8743d06..4b98643 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -3,15 +3,16 @@ import { readConfig, writeConfig } from "@moonlight-mod/core/config"; import Logger, { initLogger } from "@moonlight-mod/core/util/logger"; import { getExtensions } from "@moonlight-mod/core/extension"; import { loadExtensions } from "@moonlight-mod/core/extension/loader"; -import { - MoonlightBranch, - MoonlightBrowserFS, - MoonlightNode -} from "@moonlight-mod/types"; +import { MoonlightBranch, MoonlightNode } from "@moonlight-mod/types"; import { IndexedDB } from "@zenfs/dom"; import { configure } from "@zenfs/core"; import * as fs from "@zenfs/core/promises"; +function getParts(path: string) { + if (path.startsWith("/")) path = path.substring(1); + return path.split("/"); +} + window._moonlightBrowserInit = async () => { // Set up a virtual filesystem with IndexedDB await configure({ @@ -25,13 +26,21 @@ window._moonlightBrowserInit = async () => { } }); - const browserFS: MoonlightBrowserFS = { + window._moonlightBrowserFS = { async readFile(path) { return new Uint8Array(await fs.readFile(path)); }, + async readFileString(path) { + const file = await this.readFile(path); + return new TextDecoder().decode(file); + }, async writeFile(path, data) { await fs.writeFile(path, data); }, + async writeFileString(path, data) { + const file = new TextEncoder().encode(data); + await this.writeFile(path, file); + }, async unlink(path) { await fs.unlink(path); }, @@ -40,7 +49,7 @@ window._moonlightBrowserInit = async () => { return await fs.readdir(path); }, async mkdir(path) { - const parts = this.parts(path); + const parts = getParts(path); for (let i = 0; i < parts.length; i++) { const path = this.join(...parts.slice(0, i + 1)); if (!(await this.exists(path))) await fs.mkdir(path); @@ -76,17 +85,10 @@ window._moonlightBrowserInit = async () => { return str; }, dirname(path) { - const parts = this.parts(path); + const parts = getParts(path); return "/" + parts.slice(0, parts.length - 1).join("/"); - }, - parts(path) { - if (path.startsWith("/")) path = path.substring(1); - return path.split("/"); } }; - Object.assign(window, { - _moonlightBrowserFS: browserFS - }); // Actual loading begins here const config = await readConfig(); @@ -106,6 +108,7 @@ window._moonlightBrowserInit = async () => { extensions, processedExtensions, nativesCache: {}, + fs: window._moonlightBrowserFS, version: MOONLIGHT_VERSION, branch: MOONLIGHT_BRANCH as MoonlightBranch, diff --git a/packages/core-extensions/src/moonbase/consts.ts b/packages/core-extensions/src/moonbase/consts.ts deleted file mode 100644 index 6b0a150..0000000 --- a/packages/core-extensions/src/moonbase/consts.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const githubRepo = "moonlight-mod/moonlight"; -export const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; -export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; diff --git a/packages/core-extensions/src/moonbase/native.ts b/packages/core-extensions/src/moonbase/native.ts new file mode 100644 index 0000000..6ac49aa --- /dev/null +++ b/packages/core-extensions/src/moonbase/native.ts @@ -0,0 +1,104 @@ +import { MoonlightBranch } from "@moonlight-mod/types"; +import type { MoonbaseNatives, RepositoryManifest } from "./types"; +import extractAsar from "@moonlight-mod/core/asar"; +import { repoUrlFile } from "@moonlight-mod/types/constants"; + +export const githubRepo = "moonlight-mod/moonlight"; +export const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; +export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; + +export default function getNatives(): MoonbaseNatives { + const fs = moonlightNode.fs; + const logger = moonlightNode.getLogger("moonbase/natives"); + + return { + async checkForMoonlightUpdate() { + try { + if (moonlightNode.branch === MoonlightBranch.STABLE) { + const req = await fetch( + `https://api.github.com/repos/${githubRepo}/releases/latest?_=${Date.now()}`, + { + headers: { + "User-Agent": userAgent + } + } + ); + const json: { name: string } = await req.json(); + return json.name !== moonlightNode.version ? json.name : null; + } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { + const req = await fetch(`${nightlyRefUrl}?_=${Date.now()}`, { + headers: { + "User-Agent": userAgent + } + }); + const ref = (await req.text()).split("\n")[0]; + return ref !== moonlightNode.version ? ref : null; + } + + return null; + } catch (e) { + logger.error("Error checking for moonlight update", e); + return null; + } + }, + + async fetchRepositories(repos) { + const ret: Record = {}; + + for (const repo of repos) { + try { + const req = await fetch(repo, { + headers: { + "User-Agent": userAgent + } + }); + const json = await req.json(); + ret[repo] = json; + } catch (e) { + logger.error(`Error fetching repository ${repo}`, e); + } + } + + return ret; + }, + + async installExtension(manifest, url, repo) { + const req = await fetch(url, { + headers: { + "User-Agent": userAgent + } + }); + + const dir = moonlightNode.getExtensionDir(manifest.id); + // remake it in case of updates + if (await fs.exists(dir)) await fs.rmdir(dir); + await fs.mkdir(dir); + + const buffer = await req.arrayBuffer(); + const files = extractAsar(buffer); + for (const [file, buf] of Object.entries(files)) { + const fullFile = fs.join(dir, file); + const fullDir = fs.dirname(fullFile); + + if (!(await fs.exists(fullDir))) await fs.mkdir(fullDir); + await fs.writeFile(fs.join(dir, file), buf); + } + + await fs.writeFileString(fs.join(dir, repoUrlFile), repo); + }, + + async deleteExtension(id) { + const dir = moonlightNode.getExtensionDir(id); + await fs.rmdir(dir); + }, + + getExtensionConfig(id, key) { + const config = moonlightNode.config.extensions[id]; + if (typeof config === "object") { + return config.config?.[key]; + } + + return undefined; + } + }; +} diff --git a/packages/core-extensions/src/moonbase/node.ts b/packages/core-extensions/src/moonbase/node.ts index 9249030..35c8a0e 100644 --- a/packages/core-extensions/src/moonbase/node.ts +++ b/packages/core-extensions/src/moonbase/node.ts @@ -1,113 +1,2 @@ -import { MoonbaseNatives, RepositoryManifest } from "./types"; -import fs from "fs"; -import path from "path"; -import extractAsar from "@moonlight-mod/core/asar"; -import { repoUrlFile } from "@moonlight-mod/types/constants"; -import { githubRepo, userAgent, nightlyRefUrl } from "./consts"; -import { MoonlightBranch } from "types/src"; - -const logger = moonlightNode.getLogger("moonbase"); - -async function checkForMoonlightUpdate() { - try { - if (moonlightNode.branch === MoonlightBranch.STABLE) { - const req = await fetch( - `https://api.github.com/repos/${githubRepo}/releases/latest?_=${Date.now()}`, - { - headers: { - "User-Agent": userAgent - } - } - ); - const json: { name: string } = await req.json(); - return json.name !== moonlightNode.version ? json.name : null; - } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { - const req = await fetch(`${nightlyRefUrl}?_=${Date.now()}`, { - headers: { - "User-Agent": userAgent - } - }); - const ref = (await req.text()).split("\n")[0]; - return ref !== moonlightNode.version ? ref : null; - } - - return null; - } catch (e) { - logger.error("Error checking for moonlight update", e); - return null; - } -} - -async function fetchRepositories(repos: string[]) { - const ret: Record = {}; - - for (const repo of repos) { - try { - const req = await fetch(repo, { - headers: { - "User-Agent": userAgent - } - }); - const json = await req.json(); - ret[repo] = json; - } catch (e) { - logger.error(`Error fetching repository ${repo}`, e); - } - } - - return ret; -} - -async function installExtension( - manifest: RepositoryManifest, - url: string, - repo: string -) { - const req = await fetch(url, { - headers: { - "User-Agent": userAgent - } - }); - - const dir = moonlightNode.getExtensionDir(manifest.id); - // remake it in case of updates - if (fs.existsSync(dir)) fs.rmdirSync(dir, { recursive: true }); - fs.mkdirSync(dir, { recursive: true }); - - const buffer = await req.arrayBuffer(); - const files = extractAsar(buffer); - for (const [file, buf] of Object.entries(files)) { - const nodeBuf = Buffer.from(buf); - const fullFile = path.join(dir, file); - const fullDir = path.dirname(fullFile); - - if (!fs.existsSync(fullDir)) fs.mkdirSync(fullDir, { recursive: true }); - fs.writeFileSync(path.join(dir, file), nodeBuf); - } - - fs.writeFileSync(path.join(dir, repoUrlFile), repo); -} - -async function deleteExtension(id: string) { - const dir = moonlightNode.getExtensionDir(id); - fs.rmdirSync(dir, { recursive: true }); -} - -function getExtensionConfig(id: string, key: string): any { - const config = moonlightNode.config.extensions[id]; - if (typeof config === "object") { - return config.config?.[key]; - } - - return undefined; -} - -const exports: MoonbaseNatives = { - checkForMoonlightUpdate, - fetchRepositories, - installExtension, - deleteExtension, - getExtensionConfig -}; - -module.exports = exports; +import getNatives from "./native"; +module.exports = getNatives(); diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 9e45e7f..eec2582 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,107 +1,13 @@ -import { - Config, - ExtensionLoadSource, - MoonlightBranch -} from "@moonlight-mod/types"; -import { - ExtensionState, - MoonbaseExtension, - MoonbaseNatives, - RepositoryManifest -} from "../types"; +import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; +import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; -import extractAsar from "@moonlight-mod/core/asar"; -import { repoUrlFile } from "@moonlight-mod/types/constants"; -import { githubRepo, userAgent, nightlyRefUrl } from "../consts"; +import getNatives from "../native"; const logger = moonlight.getLogger("moonbase"); let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); -if (window._moonlightBrowserFS != null) { - const browserFS = window._moonlightBrowserFS!; - natives = { - checkForMoonlightUpdate: async () => { - try { - if (moonlight.branch === MoonlightBranch.STABLE) { - const req = await fetch( - `https://api.github.com/repos/${githubRepo}/releases/latest`, - { - headers: { - "User-Agent": userAgent - } - } - ); - const json: { name: string } = await req.json(); - return json.name !== moonlight.version ? json.name : null; - } else if (moonlight.branch === MoonlightBranch.NIGHTLY) { - const req = await fetch(nightlyRefUrl, { - headers: { - "User-Agent": userAgent - } - }); - const ref = (await req.text()).split("\n")[0]; - return ref !== moonlight.version ? ref : null; - } - - return null; - } catch (e) { - logger.error("Error checking for moonlight update", e); - return null; - } - }, - - fetchRepositories: async (repos) => { - const ret: Record = {}; - - for (const repo of repos) { - try { - const req = await fetch(repo); - const json = await req.json(); - ret[repo] = json; - } catch (e) { - logger.error(`Error fetching repository ${repo}`, e); - } - } - - return ret; - }, - installExtension: async (manifest, url, repo) => { - const req = await fetch(url); - const buffer = await req.arrayBuffer(); - - if (await browserFS.exists("/extensions/" + manifest.id)) { - await browserFS.rmdir("/extensions/" + manifest.id); - } - - const files = extractAsar(buffer); - for (const [file, data] of Object.entries(files)) { - const path = - "/extensions/" + - manifest.id + - (file.startsWith("/") ? file : `/${file}`); - await browserFS.mkdir(browserFS.dirname(path)); - await browserFS.writeFile(path, data); - } - - await browserFS.writeFile( - `/extensions/${manifest.id}/` + repoUrlFile, - new TextEncoder().encode(repo) - ); - }, - deleteExtension: async (id) => { - browserFS.rmdir("/extensions/" + id); - }, - getExtensionConfig: (id, key) => { - const config = moonlightNode.config.extensions[id]; - if (typeof config === "object") { - return config.config?.[key]; - } - - return undefined; - } - }; -} +if (!natives) natives = getNatives(); class MoonbaseSettingsStore extends Store { private origConfig: Config; @@ -389,12 +295,8 @@ class MoonbaseSettingsStore extends Store { writeConfig() { this.submitting = true; - try { - moonlightNode.writeConfig(this.config); - this.origConfig = this.clone(this.config); - } catch (e) { - logger.error("Error writing config", e); - } + moonlightNode.writeConfig(this.config); + this.origConfig = this.clone(this.config); this.submitting = false; this.modified = false; diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index db941e8..1337f88 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,6 +1,9 @@ import { Config } from "@moonlight-mod/types"; -import requireImport from "./util/import"; import { getConfigPath } from "./util/data"; +import getFS from "./fs"; +import Logger from "./util/logger"; + +const logger = new Logger("core/config"); const defaultConfig: Config = { extensions: { @@ -12,21 +15,14 @@ const defaultConfig: Config = { repositories: ["https://moonlight-mod.github.io/extensions-dist/repo.json"] }; -export function writeConfig(config: Config) { - browser: { - const enc = new TextEncoder().encode(JSON.stringify(config, null, 2)); - window._moonlightBrowserFS!.writeFile("/config.json", enc); - return; - } - - nodeTarget: { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - fs.writeFileSync(configPath, JSON.stringify(config, null, 2)); - return; +export async function writeConfig(config: Config) { + try { + const fs = getFS(); + const configPath = await getConfigPath(); + await fs.writeFileString(configPath, JSON.stringify(config, null, 2)); + } catch (e) { + logger.error("Failed to write config", e); } - - throw new Error("Called writeConfig() in an impossible environment"); } export async function readConfig(): Promise { @@ -34,39 +30,26 @@ export async function readConfig(): Promise { return moonlightNode.config; } - browser: { - if (await window._moonlightBrowserFS!.exists("/config.json")) { - const file = await window._moonlightBrowserFS!.readFile("/config.json"); - const configStr = new TextDecoder().decode(file); - let config: Config = JSON.parse(configStr); + const fs = getFS(); + const configPath = await getConfigPath(); + if (!(await fs.exists(configPath))) { + await writeConfig(defaultConfig); + return defaultConfig; + } else { + try { + let config: Config = JSON.parse(await fs.readFileString(configPath)); + // Assign the default values if they don't exist (newly added) config = { ...defaultConfig, ...config }; - writeConfig(config); + await writeConfig(config); return config; - } else { - writeConfig(defaultConfig); + } catch (e) { + logger.error("Failed to read config, falling back to defaults", e); + // We don't want to write the default config here - if a user is manually + // editing their config and messes it up, we'll delete it all instead of + // letting them fix it return defaultConfig; } } - - nodeTarget: { - const fs = requireImport("fs"); - const configPath = getConfigPath(); - - if (!fs.existsSync(configPath)) { - writeConfig(defaultConfig); - return defaultConfig; - } - - let config: Config = JSON.parse(fs.readFileSync(configPath, "utf8")); - - // Assign the default values if they don't exist (newly added) - config = { ...defaultConfig, ...config }; - writeConfig(config); - - return config; - } - - throw new Error("Called readConfig() in an impossible environment"); } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index d20cc28..c551192 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -2,81 +2,17 @@ import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, - constants + constants, + MoonlightFS } from "@moonlight-mod/types"; import { readConfig } from "./config"; -import requireImport from "./util/import"; import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; import Logger from "./util/logger"; +import getFS from "./fs"; const logger = new Logger("core/extension"); -// This is kinda duplicated from the browser FS type but idc -interface MoonlightFSWrapper { - readdir(path: string): Promise; - exists(path: string): Promise; - isFile(path: string): Promise; - readFile(path: string): Promise; - join(...parts: string[]): string; - dirname(path: string): string; -} - -function getFS(): MoonlightFSWrapper { - browser: { - const fs = window._moonlightBrowserFS!; - return { - async readdir(path) { - return await fs.readdir(path); - }, - async exists(path) { - return await fs.exists(path); - }, - async isFile(path) { - return await fs.isFile(path); - }, - async readFile(path) { - const buf = await fs.readFile(path); - const text = new TextDecoder().decode(buf); - return text; - }, - join(...parts) { - return fs.join(...parts); - }, - dirname(path) { - return fs.dirname(path); - } - }; - } - - const fs = requireImport("fs"); - const path = requireImport("path"); - - return { - async readdir(path) { - return fs.readdirSync(path); - }, - async exists(path) { - return fs.existsSync(path); - }, - async isFile(path) { - return fs.statSync(path).isFile(); - }, - async readFile(path) { - return fs.readFileSync(path, "utf8"); - }, - join(...parts) { - return path.join(...parts); - }, - dirname(dir) { - return path.dirname(dir); - } - }; -} - -async function findManifests( - fs: MoonlightFSWrapper, - dir: string -): Promise { +async function findManifests(fs: MoonlightFS, dir: string): Promise { const ret = []; if (await fs.exists(dir)) { @@ -96,7 +32,7 @@ async function findManifests( } async function loadDetectedExtensions( - fs: MoonlightFSWrapper, + fs: MoonlightFS, dir: string, type: ExtensionLoadSource ): Promise { @@ -109,7 +45,7 @@ async function loadDetectedExtensions( const dir = fs.dirname(manifestPath); const manifest: ExtensionManifest = JSON.parse( - await fs.readFile(manifestPath) + await fs.readFileString(manifestPath) ); const level = manifest.apiLevel ?? 1; if (level !== constants.apiLevel) { @@ -126,13 +62,13 @@ async function loadDetectedExtensions( } const web = (await fs.exists(webPath)) - ? await fs.readFile(webPath) + ? await fs.readFileString(webPath) : undefined; let url: string | undefined = undefined; const urlPath = fs.join(dir, constants.repoUrlFile); if (type === ExtensionLoadSource.Normal && (await fs.exists(urlPath))) { - url = await fs.readFile(urlPath); + url = await fs.readFileString(urlPath); } const wpModules: Record = {}; @@ -142,9 +78,8 @@ async function loadDetectedExtensions( for (const wpModuleFile of wpModulesFile) { if (wpModuleFile.endsWith(".js")) { - wpModules[wpModuleFile.replace(".js", "")] = await fs.readFile( - fs.join(wpModulesPath, wpModuleFile) - ); + wpModules[wpModuleFile.replace(".js", "")] = + await fs.readFileString(fs.join(wpModulesPath, wpModuleFile)); } } } @@ -188,7 +123,7 @@ async function getExtensionsNative(): Promise { res.push( ...(await loadDetectedExtensions( fs, - getExtensionsPath(), + await getExtensionsPath(), ExtensionLoadSource.Normal )) ); diff --git a/packages/core/src/fs.ts b/packages/core/src/fs.ts new file mode 100644 index 0000000..0b9be09 --- /dev/null +++ b/packages/core/src/fs.ts @@ -0,0 +1,56 @@ +import { MoonlightFS } from "types/src"; +import requireImport from "./util/import"; + +export default function getFS(): MoonlightFS { + browser: { + return window._moonlightBrowserFS!; + } + + nodeTarget: { + const fs = requireImport("fs"); + const path = requireImport("path"); + + return { + async readFile(path) { + const file = fs.readFileSync(path); + return new Uint8Array(file); + }, + async readFileString(path) { + return fs.readFileSync(path, "utf8"); + }, + async writeFile(path, data) { + fs.writeFileSync(path, Buffer.from(data)); + }, + async writeFileString(path, data) { + fs.writeFileSync(path, data, "utf8"); + }, + async unlink(path) { + fs.unlinkSync(path); + }, + + async readdir(path) { + return fs.readdirSync(path); + }, + async mkdir(path) { + fs.mkdirSync(path, { recursive: true }); + }, + async rmdir(path) { + fs.rmSync(path, { recursive: true }); + }, + + async exists(path) { + return fs.existsSync(path); + }, + async isFile(path) { + return fs.statSync(path).isFile(); + }, + + join(...parts) { + return path.join(...parts); + }, + dirname(dir) { + return path.dirname(dir); + } + }; + } +} diff --git a/packages/core/src/util/data.ts b/packages/core/src/util/data.ts index 2d6acd0..aa922f8 100644 --- a/packages/core/src/util/data.ts +++ b/packages/core/src/util/data.ts @@ -1,10 +1,13 @@ import { constants } from "@moonlight-mod/types"; -import requireImport from "./import"; +import getFS from "../fs"; + +export async function getMoonlightDir() { + browser: { + return "/"; + } -export function getMoonlightDir(): string { const electron = require("electron"); - const fs = requireImport("fs"); - const path = requireImport("path"); + const fs = getFS(); let appData = ""; injector: { @@ -15,8 +18,8 @@ export function getMoonlightDir(): string { appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData); } - const dir = path.join(appData, "moonlight-mod"); - if (!fs.existsSync(dir)) fs.mkdirSync(dir); + const dir = fs.join(appData, "moonlight-mod"); + if (!(await fs.exists(dir))) await fs.mkdir(dir); return dir; } @@ -26,43 +29,44 @@ type BuildInfo = { version: string; }; -export function getConfigPath(): string { - const dir = getMoonlightDir(); - const fs = requireImport("fs"); - const path = requireImport("path"); +export async function getConfigPath() { + browser: { + return "/config.json"; + } + + const fs = getFS(); + const dir = await getMoonlightDir(); let configPath = ""; - const buildInfoPath = path.join(process.resourcesPath, "build_info.json"); - if (!fs.existsSync(buildInfoPath)) { - configPath = path.join(dir, "desktop.json"); + const buildInfoPath = fs.join(process.resourcesPath, "build_info.json"); + if (!(await fs.exists(buildInfoPath))) { + configPath = fs.join(dir, "desktop.json"); } else { const buildInfo: BuildInfo = JSON.parse( - fs.readFileSync(buildInfoPath, "utf8") + await fs.readFileString(buildInfoPath) ); - configPath = path.join(dir, buildInfo.releaseChannel + ".json"); + configPath = fs.join(dir, buildInfo.releaseChannel + ".json"); } return configPath; } -function getPathFromMoonlight(...names: string[]): string { - const dir = getMoonlightDir(); - const fs = requireImport("fs"); - const path = requireImport("path"); +async function getPathFromMoonlight(...names: string[]) { + const dir = await getMoonlightDir(); + const fs = getFS(); - const target = path.join(dir, ...names); - if (!fs.existsSync(target)) fs.mkdirSync(target); + const target = fs.join(dir, ...names); + if (!(await fs.exists(target))) await fs.mkdir(target); return target; } -export function getExtensionsPath(): string { - return getPathFromMoonlight(constants.extensionsDir); +export async function getExtensionsPath() { + return await getPathFromMoonlight(constants.extensionsDir); } export function getCoreExtensionsPath(): string { - const path = requireImport("path"); - const a = path.join(__dirname, constants.coreExtensionsDir); - return a; + const fs = getFS(); + return fs.join(__dirname, constants.coreExtensionsDir); } diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index c5c3103..4828734 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -1,6 +1,6 @@ import { webFrame, ipcRenderer, contextBridge } from "electron"; -import fs from "fs"; -import path from "path"; +import fs from "node:fs"; +import path from "node:path"; import { readConfig, writeConfig } from "@moonlight-mod/core/config"; import { constants, MoonlightBranch } from "@moonlight-mod/types"; @@ -14,12 +14,15 @@ import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; +import getFS from "@moonlight-mod/core/fs"; async function injectGlobals() { const config = await readConfig(); initLogger(config); const extensions = await getExtensions(); const processedExtensions = await loadExtensions(extensions); + const moonlightDir = await getMoonlightDir(); + const extensionsPath = await getExtensionsPath(); function getConfig(ext: string) { const val = config.extensions[ext]; @@ -32,6 +35,7 @@ async function injectGlobals() { extensions, processedExtensions, nativesCache: {}, + fs: getFS(), version: MOONLIGHT_VERSION, branch: MOONLIGHT_BRANCH as MoonlightBranch, @@ -50,11 +54,10 @@ async function injectGlobals() { }, getMoonlightDir() { - return getMoonlightDir(); + return moonlightDir; }, getExtensionDir: (ext: string) => { - const extPath = getExtensionsPath(); - return path.join(extPath, ext); + return path.join(extensionsPath, ext); }, writeConfig }; diff --git a/packages/types/src/fs.ts b/packages/types/src/fs.ts new file mode 100644 index 0000000..0df76eb --- /dev/null +++ b/packages/types/src/fs.ts @@ -0,0 +1,17 @@ +export type MoonlightFS = { + readFile: (path: string) => Promise; + readFileString: (path: string) => Promise; + writeFile: (path: string, data: Uint8Array) => Promise; + writeFileString: (path: string, data: string) => Promise; + unlink: (path: string) => Promise; + + readdir: (path: string) => Promise; + mkdir: (path: string) => Promise; + rmdir: (path: string) => Promise; + + exists: (path: string) => Promise; + isFile: (path: string) => Promise; + + join: (...parts: string[]) => string; + dirname: (path: string) => string; +}; diff --git a/packages/types/src/globals.ts b/packages/types/src/globals.ts index f1a9b07..076dc41 100644 --- a/packages/types/src/globals.ts +++ b/packages/types/src/globals.ts @@ -9,7 +9,12 @@ import type { import type EventEmitter from "events"; import type LunAST from "@moonlight-mod/lunast"; import type Moonmap from "@moonlight-mod/moonmap"; -import { EventPayloads, EventType, MoonlightEventEmitter } from "./core/event"; +import type { + EventPayloads, + EventType, + MoonlightEventEmitter +} from "./core/event"; +import type { MoonlightFS } from "./fs"; export type MoonlightHost = { asarPath: string; @@ -31,6 +36,7 @@ export type MoonlightNode = { extensions: DetectedExtension[]; processedExtensions: ProcessedExtensions; nativesCache: Record; + fs: MoonlightFS; version: string; branch: MoonlightBranch; @@ -42,24 +48,7 @@ export type MoonlightNode = { getMoonlightDir: () => string; getExtensionDir: (ext: string) => string; - writeConfig: (config: Config) => void; -}; - -export type MoonlightBrowserFS = { - readFile: (path: string) => Promise; - writeFile: (path: string, data: Uint8Array) => Promise; - unlink: (path: string) => Promise; - - readdir: (path: string) => Promise; - mkdir: (path: string) => Promise; - rmdir: (path: string) => Promise; - - exists: (path: string) => Promise; - isFile: (path: string) => Promise; - - join: (...parts: string[]) => string; - dirname: (path: string) => string; - parts: (path: string) => string[]; + writeConfig: (config: Config) => Promise; }; export type MoonlightWeb = { diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index aa4cf2b..7414426 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -4,8 +4,8 @@ /// /* eslint-disable no-var */ +import { MoonlightFS } from "./fs"; import { - MoonlightBrowserFS, MoonlightEnv, MoonlightHost, MoonlightNode, @@ -19,6 +19,7 @@ export * as CoreExtensions from "./coreExtensions"; export * from "./globals"; export * from "./logger"; export * as constants from "./constants"; +export * from "./fs"; export type { AST } from "@moonlight-mod/lunast"; export { ModuleExport, ModuleExportType } from "@moonlight-mod/moonmap"; @@ -39,5 +40,5 @@ declare global { var _moonlightBrowserInit: () => Promise; var _moonlightBrowserLoad: () => Promise; - var _moonlightBrowserFS: MoonlightBrowserFS | undefined; + var _moonlightBrowserFS: MoonlightFS; } From 8ae8d9fcd16c87449ce38fe1da0b6c9915f45278 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 19:28:49 -0400 Subject: [PATCH 49/73] Fix API levels skipping an extension completely --- .../src/moonbase/webpackModules/stores.ts | 16 +++++++++------- packages/core/src/extension.ts | 4 ---- packages/core/src/extension/loader.ts | 5 ++++- 3 files changed, 13 insertions(+), 12 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 9e45e7f..89b13ac 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,5 +1,6 @@ import { Config, + constants, ExtensionLoadSource, MoonlightBranch } from "@moonlight-mod/types"; @@ -151,9 +152,6 @@ class MoonbaseSettingsStore extends Store { for (const [repo, exts] of Object.entries(ret)) { try { for (const ext of exts) { - const level = ext.apiLevel ?? 1; - if (level !== window.moonlight.apiLevel) continue; - const uniqueId = this.extensionIndex++; const extensionData = { id: ext.id, @@ -163,7 +161,11 @@ class MoonbaseSettingsStore extends Store { state: ExtensionState.NotDownloaded }; - if (this.alreadyExists(extensionData)) { + const apiLevel = ext.apiLevel ?? 1; + if (apiLevel !== constants.apiLevel) continue; + + const existing = this.getExisting(extensionData); + if (existing != null) { // Make sure the download URL is properly updated for (const [id, e] of Object.entries(this.extensions)) { if (e.id === ext.id && e.source.url === repo) { @@ -176,7 +178,7 @@ class MoonbaseSettingsStore extends Store { } if (this.hasUpdate(extensionData)) { - this.updates[uniqueId] = { + this.updates[existing.uniqueId] = { version: ext.version!, download: ext.download }; @@ -206,8 +208,8 @@ class MoonbaseSettingsStore extends Store { }); } - private alreadyExists(ext: MoonbaseExtension) { - return Object.values(this.extensions).some( + private getExisting(ext: MoonbaseExtension) { + return Object.values(this.extensions).find( (e) => e.id === ext.id && e.source.url === ext.source.url ); } diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index d20cc28..e167645 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -111,10 +111,6 @@ async function loadDetectedExtensions( const manifest: ExtensionManifest = JSON.parse( await fs.readFile(manifestPath) ); - const level = manifest.apiLevel ?? 1; - if (level !== constants.apiLevel) { - continue; - } const webPath = fs.join(dir, "index.js"); const nodePath = fs.join(dir, "node.js"); diff --git a/packages/core/src/extension/loader.ts b/packages/core/src/extension/loader.ts index db1c414..7c2ff97 100644 --- a/packages/core/src/extension/loader.ts +++ b/packages/core/src/extension/loader.ts @@ -2,7 +2,8 @@ import { ExtensionWebExports, DetectedExtension, ProcessedExtensions, - WebpackModuleFunc + WebpackModuleFunc, + constants } from "@moonlight-mod/types"; import { readConfig } from "../config"; import Logger from "../util/logger"; @@ -121,6 +122,8 @@ async function loadExt(ext: DetectedExtension) { export async function loadExtensions( exts: DetectedExtension[] ): Promise { + exts = exts.filter((x) => x.manifest.apiLevel === constants.apiLevel); + const config = await readConfig(); const items = exts .map((ext) => { From f6e2ba38ee4e261019e88fd1cdedd86ff7c009c1 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 20:57:19 -0400 Subject: [PATCH 50/73] moonbase: Clean up dependency prompt --- .../webpackModules/ui/extensions/popup.tsx | 64 +++++++++++++------ 1 file changed, 45 insertions(+), 19 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx index 1508a67..4874879 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx @@ -5,6 +5,7 @@ import { MoonbaseExtension } from "core-extensions/src/moonbase/types"; import * as Components from "@moonlight-mod/wp/discord/components/common/index"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; import { ExtensionLoadSource } from "types/src"; +import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; const { openModalLazy, @@ -50,6 +51,8 @@ function ExtensionSelect({ onChange={(value: string) => { setOption(value); }} + // @ts-expect-error no thanks + placeholder="Missing extension" /> ); } @@ -83,7 +86,12 @@ function OurPopup({ return ( + This extension depends on other extensions which are not downloaded. Choose which extensions to download. @@ -97,27 +105,45 @@ function OurPopup({ )} - {Object.entries(deps).map(([id, candidates], i) => ( - <> - {MoonbaseSettingsStore.tryGetExtensionName(id)} - - setOptions((prev) => ({ - ...prev, - [id]: pick - })) - } - /> - - ))} - +
+ {Object.entries(deps).map(([id, candidates], i) => ( + <> + + {MoonbaseSettingsStore.tryGetExtensionName(id)} + + + + setOptions((prev) => ({ + ...prev, + [id]: pick + })) + } + /> + + ))} +
+
} cancelText="Cancel" confirmText="Install" + onCancel={() => { + closeModal(id); + }} onConfirm={() => { closeModal(id); From e78e92072c620371a1f2cc8aabea2c2623ebd835 Mon Sep 17 00:00:00 2001 From: NotNite Date: Tue, 8 Oct 2024 21:15:06 -0400 Subject: [PATCH 51/73] moonbase: Fix manifests without meta --- .../src/moonbase/webpackModules/ui/extensions/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx index 697d3ed..13ffb7d 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx @@ -49,7 +49,9 @@ export default function ExtensionsPage() { const filtered = sorted.filter( (ext) => - (ext.manifest.meta?.name?.toLowerCase().includes(query) || + (query === "" || + ext.manifest.id?.toLowerCase().includes(query) || + ext.manifest.meta?.name?.toLowerCase().includes(query) || ext.manifest.meta?.tagline?.toLowerCase().includes(query) || ext.manifest.meta?.description?.toLowerCase().includes(query)) && [...selectedTags.values()].every( From dc186f02fb9c776e9e93e1c16affcfaf60919e1d Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 09:37:44 -0400 Subject: [PATCH 52/73] Add word-break to dependency prompt --- .../src/moonbase/webpackModules/ui/extensions/popup.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx index 4874879..5009eeb 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx @@ -109,7 +109,7 @@ function OurPopup({ style={{ display: "grid", gridTemplateColumns: "1fr 2fr", - gap: "10px 0" + gap: "10px" }} > {Object.entries(deps).map(([id, candidates], i) => ( @@ -117,7 +117,8 @@ function OurPopup({ {MoonbaseSettingsStore.tryGetExtensionName(id)} From d8a649e31c5cb8be07f672604e990095d6c0f413 Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 14:04:03 -0400 Subject: [PATCH 53/73] Begin work on Moonbase update installation --- packages/core-extensions/package.json | 3 +- .../src/moonbase/manifest.json | 6 +- .../core-extensions/src/moonbase/native.ts | 99 ++++++++++++++++--- .../core-extensions/src/moonbase/types.ts | 2 + .../src/moonbase/webpackModules/updates.ts | 36 ++++--- pnpm-lock.yaml | 8 ++ 6 files changed, 121 insertions(+), 33 deletions(-) diff --git a/packages/core-extensions/package.json b/packages/core-extensions/package.json index a0bf3c5..3d0dd35 100644 --- a/packages/core-extensions/package.json +++ b/packages/core-extensions/package.json @@ -3,6 +3,7 @@ "private": true, "dependencies": { "@moonlight-mod/core": "workspace:*", - "@moonlight-mod/types": "workspace:*" + "@moonlight-mod/types": "workspace:*", + "nanotar": "^0.1.1" } } diff --git a/packages/core-extensions/src/moonbase/manifest.json b/packages/core-extensions/src/moonbase/manifest.json index 159acde..30a5b04 100644 --- a/packages/core-extensions/src/moonbase/manifest.json +++ b/packages/core-extensions/src/moonbase/manifest.json @@ -18,5 +18,9 @@ "description": "Save extension filter in config", "type": "boolean" } - } + }, + "cors": [ + "https://github.com/moonlight-mod/moonlight/releases/download/", + "https://objects.githubusercontent.com/github-production-release-asset-" + ] } diff --git a/packages/core-extensions/src/moonbase/native.ts b/packages/core-extensions/src/moonbase/native.ts index 6ac49aa..b125c8a 100644 --- a/packages/core-extensions/src/moonbase/native.ts +++ b/packages/core-extensions/src/moonbase/native.ts @@ -2,11 +2,33 @@ import { MoonlightBranch } from "@moonlight-mod/types"; import type { MoonbaseNatives, RepositoryManifest } from "./types"; import extractAsar from "@moonlight-mod/core/asar"; import { repoUrlFile } from "@moonlight-mod/types/constants"; +import { parseTarGzip } from "nanotar"; + +const githubRepo = "moonlight-mod/moonlight"; +const githubApiUrl = `https://api.github.com/repos/${githubRepo}/releases/latest`; +const artifactName = "dist.tar.gz"; + +const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; +const nightlyZipUrl = "https://moonlight-mod.github.io/moonlight/dist.tar.gz"; -export const githubRepo = "moonlight-mod/moonlight"; -export const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; +async function getStableRelease(): Promise<{ + name: string; + assets: { + name: string; + browser_download_url: string; + }[]; +}> { + const req = await fetch(githubApiUrl, { + cache: "no-store", + headers: { + "User-Agent": userAgent + } + }); + return await req.json(); +} + export default function getNatives(): MoonbaseNatives { const fs = moonlightNode.fs; const logger = moonlightNode.getLogger("moonbase/natives"); @@ -15,18 +37,11 @@ export default function getNatives(): MoonbaseNatives { async checkForMoonlightUpdate() { try { if (moonlightNode.branch === MoonlightBranch.STABLE) { - const req = await fetch( - `https://api.github.com/repos/${githubRepo}/releases/latest?_=${Date.now()}`, - { - headers: { - "User-Agent": userAgent - } - } - ); - const json: { name: string } = await req.json(); + const json = await getStableRelease(); return json.name !== moonlightNode.version ? json.name : null; } else if (moonlightNode.branch === MoonlightBranch.NIGHTLY) { - const req = await fetch(`${nightlyRefUrl}?_=${Date.now()}`, { + const req = await fetch(nightlyRefUrl, { + cache: "no-store", headers: { "User-Agent": userAgent } @@ -42,12 +57,72 @@ export default function getNatives(): MoonbaseNatives { } }, + async updateMoonlight() { + // Note: this won't do anything on browser, we should probably disable it + // entirely when running in browser. + async function downloadStable() { + const json = await getStableRelease(); + const asset = json.assets.find((a) => a.name === artifactName); + if (!asset) throw new Error("Artifact not found"); + + logger.debug(`Downloading ${asset.browser_download_url}`); + const req = await fetch(asset.browser_download_url, { + cache: "no-store", + headers: { + "User-Agent": userAgent + } + }); + + return await req.arrayBuffer(); + } + + async function downloadNightly() { + logger.debug(`Downloading ${nightlyZipUrl}`); + const req = await fetch(nightlyZipUrl, { + cache: "no-store", + headers: { + "User-Agent": userAgent + } + }); + + return await req.arrayBuffer(); + } + + const tar = + moonlightNode.branch === MoonlightBranch.STABLE + ? await downloadStable() + : moonlightNode.branch === MoonlightBranch.NIGHTLY + ? await downloadNightly() + : null; + + if (!tar) return; + + const distDir = fs.join(moonlightNode.getMoonlightDir(), "dist"); + if (await fs.exists(distDir)) await fs.rmdir(distDir); + await fs.mkdir(distDir); + + logger.debug("Extracting update"); + const files = await parseTarGzip(tar); + for (const file of files) { + if (!file.data) continue; + // @ts-expect-error What do you mean their own types are wrong + if (file.type !== "file") continue; + + const fullFile = fs.join(distDir, file.name); + const fullDir = fs.dirname(fullFile); + if (!(await fs.exists(fullDir))) await fs.mkdir(fullDir); + await fs.writeFile(fullFile, file.data); + } + logger.debug("Update extracted"); + }, + async fetchRepositories(repos) { const ret: Record = {}; for (const repo of repos) { try { const req = await fetch(repo, { + cache: "no-store", headers: { "User-Agent": userAgent } diff --git a/packages/core-extensions/src/moonbase/types.ts b/packages/core-extensions/src/moonbase/types.ts index 2b14937..54a63ab 100644 --- a/packages/core-extensions/src/moonbase/types.ts +++ b/packages/core-extensions/src/moonbase/types.ts @@ -2,6 +2,8 @@ import { DetectedExtension, ExtensionManifest } from "types/src"; export type MoonbaseNatives = { checkForMoonlightUpdate(): Promise; + updateMoonlight(): Promise; + fetchRepositories( repos: string[] ): Promise>; diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.ts b/packages/core-extensions/src/moonbase/webpackModules/updates.ts index f6270d4..d26a602 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.ts @@ -53,26 +53,24 @@ function listener() { Notices.addNotice({ element: message, color: "moonbase-updates-notice", - buttons: hasExtensionUpdates - ? [ - { - name: "Open Moonbase", - onClick: () => { - const { open } = spacepack.findByExports( - "setSection", - "clearSubsection" - )[0].exports.Z; + buttons: [ + { + name: "Open Moonbase", + onClick: () => { + const { open } = spacepack.findByExports( + "setSection", + "clearSubsection" + )[0].exports.Z; - // settings is lazy loaded thus lazily patched - // FIXME: figure out a way to detect if settings has been opened - // alreadyjust so the transition isnt as jarring - open(UserSettingsSections.ACCOUNT); - setTimeout(() => open("moonbase", 0), 0); - return true; - } - } - ] - : [] + // settings is lazy loaded thus lazily patched + // FIXME: figure out a way to detect if settings has been opened + // alreadyjust so the transition isnt as jarring + open(UserSettingsSections.ACCOUNT); + setTimeout(() => open("moonbase", 0), 0); + return true; + } + } + ] }); } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 62ac295..8b45e5b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -74,6 +74,9 @@ importers: '@moonlight-mod/types': specifier: workspace:* version: link:../types + nanotar: + specifier: ^0.1.1 + version: 0.1.1 packages/injector: dependencies: @@ -1078,6 +1081,9 @@ packages: ms@2.1.2: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} + nanotar@0.1.1: + resolution: {integrity: sha512-AiJsGsSF3O0havL1BydvI4+wR76sKT+okKRwWIaK96cZUnXqH0uNBOsHlbwZq3+m2BR1VKqHDVudl3gO4mYjpQ==} + natural-compare@1.4.0: resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} @@ -2485,6 +2491,8 @@ snapshots: ms@2.1.2: {} + nanotar@0.1.1: {} + natural-compare@1.4.0: {} npm-run-path@4.0.1: From 6898f7f1d207186bedc4ddd335a4d8cb90916092 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 12:15:49 -0600 Subject: [PATCH 54/73] moonbase: Options to disable update checking and update banner Also a slight refactor to getting and setting extension settings within MoonbaseSettingsStore --- .../src/moonbase/manifest.json | 14 ++++++++++- .../src/moonbase/webpackModules/stores.ts | 24 +++++++++++++++---- .../webpackModules/ui/config/index.tsx | 18 ++++++++++++++ .../webpackModules/ui/extensions/index.tsx | 18 +++++++++----- .../webpackModules/ui/extensions/settings.tsx | 16 ++++++------- .../src/moonbase/webpackModules/updates.ts | 9 ++++++- 6 files changed, 78 insertions(+), 21 deletions(-) diff --git a/packages/core-extensions/src/moonbase/manifest.json b/packages/core-extensions/src/moonbase/manifest.json index 30a5b04..f0aa51d 100644 --- a/packages/core-extensions/src/moonbase/manifest.json +++ b/packages/core-extensions/src/moonbase/manifest.json @@ -4,7 +4,7 @@ "meta": { "name": "Moonbase", "tagline": "The official settings UI for moonlight", - "authors": ["Cynosphere", "NotNite"] + "authors": ["Cynosphere", "NotNite", "redstonekasi"] }, "dependencies": ["spacepack", "settings", "common", "notices"], "settings": { @@ -17,6 +17,18 @@ "displayName": "Persist filter", "description": "Save extension filter in config", "type": "boolean" + }, + "updateChecking": { + "displayName": "Automatic update checking", + "description": "Checks for updates to moonlight", + "type": "boolean", + "default": "true" + }, + "updateBanner": { + "displayName": "Show update banner", + "description": "Shows a banner for moonlight and extension updates", + "type": "boolean", + "default": "true" } }, "cors": [ diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 2cfe0e4..108a9ee 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -102,7 +102,11 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); }) - .then(natives!.checkForMoonlightUpdate) + .then(() => + this.getExtensionConfigRaw("moonbase", "updateChecking", true) + ? natives!.checkForMoonlightUpdate() + : new Promise((resolve) => resolve(null)) + ) .then((version) => { this.newVersion = version; this.emitChange(); @@ -194,6 +198,17 @@ class MoonbaseSettingsStore extends Store { return cfg.config?.[key] ?? clonedDefaultValue; } + getExtensionConfigRaw( + id: string, + key: string, + defaultValue: T | undefined + ): T | undefined { + const cfg = this.config.extensions[id]; + + if (cfg == null || typeof cfg === "boolean") return defaultValue; + return cfg.config?.[key] ?? defaultValue; + } + getExtensionConfigName(uniqueId: number, key: string) { const ext = this.getExtension(uniqueId); return ext.manifest.settings?.[key]?.displayName ?? key; @@ -204,9 +219,8 @@ class MoonbaseSettingsStore extends Store { return ext.manifest.settings?.[key]?.description; } - setExtensionConfig(uniqueId: number, key: string, value: any) { - const ext = this.getExtension(uniqueId); - const oldConfig = this.config.extensions[ext.id]; + setExtensionConfig(id: string, key: string, value: any) { + const oldConfig = this.config.extensions[id]; const newConfig = typeof oldConfig === "boolean" ? { @@ -218,7 +232,7 @@ class MoonbaseSettingsStore extends Store { config: { ...(oldConfig?.config ?? {}), [key]: value } }; - this.config.extensions[ext.id] = newConfig; + this.config.extensions[id] = newConfig; this.modified = this.isModified(); this.emitChange(); } diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx index 2bd4d2e..e14b2ce 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/config/index.tsx @@ -121,6 +121,24 @@ function ArrayFormItem({ export default function ConfigPage() { return ( <> + ( + "moonbase", + "updateChecking", + true + )} + onChange={(value: boolean) => { + MoonbaseSettingsStore.setExtensionConfig( + "moonbase", + "updateChecking", + value + ); + }} + note="Checks for updates to moonlight" + > + Automatic update checking + A list of remote repositories to display extensions from diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx index 13ffb7d..e6150c7 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx @@ -14,15 +14,15 @@ const SearchBar: any = Object.values( )[0]; export default function ExtensionsPage() { - const moonbaseId = MoonbaseSettingsStore.getExtensionUniqueId("moonbase")!; const { extensions, savedFilter } = useStateFromStoresObject( [MoonbaseSettingsStore], () => { return { extensions: MoonbaseSettingsStore.extensions, - savedFilter: MoonbaseSettingsStore.getExtensionConfig( - moonbaseId, - "filter" + savedFilter: MoonbaseSettingsStore.getExtensionConfigRaw( + "moonbase", + "filter", + defaultFilter ) }; } @@ -31,10 +31,16 @@ export default function ExtensionsPage() { const [query, setQuery] = React.useState(""); let filter: Filter, setFilter: (filter: Filter) => void; - if (moonlight.getConfigOption("moonbase", "saveFilter")) { + if ( + MoonbaseSettingsStore.getExtensionConfigRaw( + "moonbase", + "saveFilter", + false + ) + ) { filter = savedFilter ?? defaultFilter; setFilter = (filter) => - MoonbaseSettingsStore.setExtensionConfig(moonbaseId, "filter", filter); + MoonbaseSettingsStore.setExtensionConfig("moonbase", "filter", filter); } else { const state = React.useState(defaultFilter); filter = state[0]; diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx index 9b1f2c4..36b57ce 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx @@ -60,7 +60,7 @@ function Boolean({ ext, name, setting, disabled }: SettingsProps) { hideBorder={true} disabled={disabled} onChange={(value: boolean) => { - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); }} note={description} className={`${Margins.marginReset} ${Margins.marginTop20}`} @@ -91,7 +91,7 @@ function Number({ ext, name, setting, disabled }: SettingsProps) { maxValue={castedSetting.max ?? 100} onValueChange={(value: number) => { const rounded = Math.max(min, Math.min(max, Math.round(value))); - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, rounded); + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, rounded); }} /> @@ -114,7 +114,7 @@ function String({ ext, name, setting, disabled }: SettingsProps) { value={value ?? ""} onChange={(value: string) => { if (disabled) return; - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); }} /> @@ -139,7 +139,7 @@ function MultilineString({ ext, name, setting, disabled }: SettingsProps) { className={"moonbase-resizeable"} onChange={(value: string) => { if (disabled) return; - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); }} /> @@ -170,7 +170,7 @@ function Select({ ext, name, setting, disabled }: SettingsProps) { )} onChange={(value: string) => { if (disabled) return; - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value); + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value); }} /> @@ -206,7 +206,7 @@ function MultiSelect({ ext, name, setting, disabled }: SettingsProps) { onChange: (value: string) => { if (disabled) return; MoonbaseSettingsStore.setExtensionConfig( - ext.uniqueId, + ext.id, name, Array.from(value) ); @@ -257,7 +257,7 @@ function List({ ext, name, setting, disabled }: SettingsProps) { const entries = value ?? []; const updateConfig = () => - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, entries); + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, entries); return ( @@ -323,7 +323,7 @@ function Dictionary({ ext, name, setting, disabled }: SettingsProps) { const entries = Object.entries(value ?? {}); const updateConfig = () => MoonbaseSettingsStore.setExtensionConfig( - ext.uniqueId, + ext.id, name, Object.fromEntries(entries) ); diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.ts b/packages/core-extensions/src/moonbase/webpackModules/updates.ts index d26a602..40740db 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.ts @@ -15,7 +15,14 @@ function plural(str: string, num: number) { } function listener() { - if (MoonbaseSettingsStore.shouldShowNotice) { + if ( + MoonbaseSettingsStore.shouldShowNotice && + MoonbaseSettingsStore.getExtensionConfigRaw( + "moonbase", + "updateBanner", + true + ) + ) { // @ts-expect-error epic type fail MoonbaseSettingsStore.removeChangeListener(listener); From fde3b8ef16259be1e5cecdb3c8216e20337c41bc Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 12:41:51 -0600 Subject: [PATCH 55/73] moonbase: Add moon icon to update notice --- packages/core-extensions/src/moonbase/index.tsx | 3 ++- .../webpackModules/{updates.ts => updates.tsx} | 11 ++++++++++- 2 files changed, 12 insertions(+), 2 deletions(-) rename packages/core-extensions/src/moonbase/webpackModules/{updates.ts => updates.tsx} (87%) diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 5e10d35..9ba2fab 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -48,5 +48,6 @@ export const webpackModules: Record = { export const styles = [ ".moonbase-settings > :first-child { margin-top: 0px; }", "textarea.moonbase-resizeable { resize: vertical }", - ".moonbase-updates-notice { background-color: #222034; color: #FFFBA6; }" + ".moonbase-updates-notice { background-color: #222034; color: #FFFBA6; line-height: unset; height: 36px; }", + ".moonbase-updates-notice_text-wrapper { display: inline-flex; align-items: center; line-height: 36px; gap: 2px; }" ]; diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.ts b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx similarity index 87% rename from packages/core-extensions/src/moonbase/webpackModules/updates.ts rename to packages/core-extensions/src/moonbase/webpackModules/updates.tsx index 40740db..7d3dab6 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx @@ -2,6 +2,8 @@ import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; import Notices from "@moonlight-mod/wp/notices_notices"; import { MoonlightBranch } from "types/src"; +import React from "@moonlight-mod/wp/react"; +import * as Components from "@moonlight-mod/wp/discord/components/common/index"; // FIXME: not indexed as importable const Constants = spacepack.require("discord/Constants"); @@ -10,6 +12,8 @@ const UserSettingsSections = spacepack.findObjectFromKey( "APPEARANCE_THEME_PICKER" ); +const { ThemeDarkIcon } = Components; + function plural(str: string, num: number) { return `${str}${num > 1 ? "s" : ""}`; } @@ -58,7 +62,12 @@ function listener() { if (message != null) message += "."; Notices.addNotice({ - element: message, + element: ( +
+ + {message} +
+ ), color: "moonbase-updates-notice", buttons: [ { From 6071ab963125d2edfee1ae9ea00419e36fec0240 Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 14:42:25 -0400 Subject: [PATCH 56/73] Write installed version to sync with installer --- .../core-extensions/src/moonbase/native.ts | 46 +++++++++++++------ packages/types/src/constants.ts | 1 + 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/packages/core-extensions/src/moonbase/native.ts b/packages/core-extensions/src/moonbase/native.ts index b125c8a..85718c9 100644 --- a/packages/core-extensions/src/moonbase/native.ts +++ b/packages/core-extensions/src/moonbase/native.ts @@ -1,7 +1,11 @@ import { MoonlightBranch } from "@moonlight-mod/types"; import type { MoonbaseNatives, RepositoryManifest } from "./types"; import extractAsar from "@moonlight-mod/core/asar"; -import { repoUrlFile } from "@moonlight-mod/types/constants"; +import { + distDir, + repoUrlFile, + installedVersionFile +} from "@moonlight-mod/types/constants"; import { parseTarGzip } from "nanotar"; const githubRepo = "moonlight-mod/moonlight"; @@ -60,7 +64,7 @@ export default function getNatives(): MoonbaseNatives { async updateMoonlight() { // Note: this won't do anything on browser, we should probably disable it // entirely when running in browser. - async function downloadStable() { + async function downloadStable(): Promise<[ArrayBuffer, string]> { const json = await getStableRelease(); const asset = json.assets.find((a) => a.name === artifactName); if (!asset) throw new Error("Artifact not found"); @@ -73,33 +77,41 @@ export default function getNatives(): MoonbaseNatives { } }); - return await req.arrayBuffer(); + return [await req.arrayBuffer(), json.name]; } - async function downloadNightly() { + async function downloadNightly(): Promise<[ArrayBuffer, string]> { logger.debug(`Downloading ${nightlyZipUrl}`); - const req = await fetch(nightlyZipUrl, { + const zipReq = await fetch(nightlyZipUrl, { cache: "no-store", headers: { "User-Agent": userAgent } }); - return await req.arrayBuffer(); + const refReq = await fetch(nightlyRefUrl, { + cache: "no-store", + headers: { + "User-Agent": userAgent + } + }); + const ref = (await refReq.text()).split("\n")[0]; + + return [await zipReq.arrayBuffer(), ref]; } - const tar = + const [tar, ref] = moonlightNode.branch === MoonlightBranch.STABLE ? await downloadStable() : moonlightNode.branch === MoonlightBranch.NIGHTLY ? await downloadNightly() - : null; + : [null, null]; - if (!tar) return; + if (!tar || !ref) return; - const distDir = fs.join(moonlightNode.getMoonlightDir(), "dist"); - if (await fs.exists(distDir)) await fs.rmdir(distDir); - await fs.mkdir(distDir); + const dist = fs.join(moonlightNode.getMoonlightDir(), distDir); + if (await fs.exists(dist)) await fs.rmdir(dist); + await fs.mkdir(dist); logger.debug("Extracting update"); const files = await parseTarGzip(tar); @@ -108,11 +120,19 @@ export default function getNatives(): MoonbaseNatives { // @ts-expect-error What do you mean their own types are wrong if (file.type !== "file") continue; - const fullFile = fs.join(distDir, file.name); + const fullFile = fs.join(dist, file.name); const fullDir = fs.dirname(fullFile); if (!(await fs.exists(fullDir))) await fs.mkdir(fullDir); await fs.writeFile(fullFile, file.data); } + + logger.debug("Writing version file:", ref); + const versionFile = fs.join( + moonlightNode.getMoonlightDir(), + installedVersionFile + ); + await fs.writeFileString(versionFile, ref.trim()); + logger.debug("Update extracted"); }, diff --git a/packages/types/src/constants.ts b/packages/types/src/constants.ts index 1dea746..62d4f32 100644 --- a/packages/types/src/constants.ts +++ b/packages/types/src/constants.ts @@ -2,6 +2,7 @@ export const extensionsDir = "extensions"; export const distDir = "dist"; export const coreExtensionsDir = "core-extensions"; export const repoUrlFile = ".moonlight-repo-url"; +export const installedVersionFile = ".moonlight-installed-version"; export const ipcGetOldPreloadPath = "_moonlight_getOldPreloadPath"; export const ipcGetAppData = "_moonlight_getAppData"; From 749709faaa75df872da0b0a5492636c50947604c Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 16:37:23 -0400 Subject: [PATCH 57/73] Fix wrong baseUrl imports --- packages/core-extensions/src/moonbase/types.ts | 2 +- packages/core-extensions/src/noHideToken/index.ts | 2 +- packages/core/src/fs.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/moonbase/types.ts b/packages/core-extensions/src/moonbase/types.ts index 2b14937..40f106b 100644 --- a/packages/core-extensions/src/moonbase/types.ts +++ b/packages/core-extensions/src/moonbase/types.ts @@ -1,4 +1,4 @@ -import { DetectedExtension, ExtensionManifest } from "types/src"; +import { DetectedExtension, ExtensionManifest } from "@moonlight-mod/types"; export type MoonbaseNatives = { checkForMoonlightUpdate(): Promise; diff --git a/packages/core-extensions/src/noHideToken/index.ts b/packages/core-extensions/src/noHideToken/index.ts index 6e45492..f58cd38 100644 --- a/packages/core-extensions/src/noHideToken/index.ts +++ b/packages/core-extensions/src/noHideToken/index.ts @@ -1,4 +1,4 @@ -import { Patch } from "types/src"; +import { Patch } from "@moonlight-mod/types"; export const patches: Patch[] = [ { diff --git a/packages/core/src/fs.ts b/packages/core/src/fs.ts index 0b9be09..70eb8fe 100644 --- a/packages/core/src/fs.ts +++ b/packages/core/src/fs.ts @@ -1,4 +1,4 @@ -import { MoonlightFS } from "types/src"; +import { MoonlightFS } from "@moonlight-mod/types"; import requireImport from "./util/import"; export default function getFS(): MoonlightFS { From 136c8691b1b2eae73cb0dc13d250d00374e2c38c Mon Sep 17 00:00:00 2001 From: redstonekasi Date: Wed, 9 Oct 2024 22:48:14 +0200 Subject: [PATCH 58/73] Allow extensions to specify their platform compatability --- .../core-extensions/src/moonbase/types.ts | 4 +- .../src/moonbase/webpackModules/stores.ts | 17 ++++-- .../webpackModules/ui/extensions/card.tsx | 60 +++++++++++++------ .../ui/extensions/filterBar.tsx | 11 +++- .../webpackModules/ui/extensions/index.tsx | 5 +- .../src/nativeFixes/manifest.json | 3 +- packages/core/src/extension/loader.ts | 35 ++++++++++- packages/types/src/extension.ts | 7 +++ 8 files changed, 112 insertions(+), 30 deletions(-) diff --git a/packages/core-extensions/src/moonbase/types.ts b/packages/core-extensions/src/moonbase/types.ts index 2b14937..f30a77b 100644 --- a/packages/core-extensions/src/moonbase/types.ts +++ b/packages/core-extensions/src/moonbase/types.ts @@ -1,4 +1,5 @@ -import { DetectedExtension, ExtensionManifest } from "types/src"; +import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; +import { DetectedExtension, ExtensionManifest } from "@moonlight-mod/types"; export type MoonbaseNatives = { checkForMoonlightUpdate(): Promise; @@ -30,4 +31,5 @@ export type MoonbaseExtension = { manifest: ExtensionManifest | RepositoryManifest; source: DetectedExtension["source"]; state: ExtensionState; + compat: ExtensionCompat; }; diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 2cfe0e4..b6a7947 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,9 +1,13 @@ -import { Config, constants, ExtensionLoadSource } from "@moonlight-mod/types"; +import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; import getNatives from "../native"; import { mainRepo } from "@moonlight-mod/types/constants"; +import { + checkExtensionCompat, + ExtensionCompat +} from "@moonlight-mod/core/extension/loader"; const logger = moonlight.getLogger("moonbase"); @@ -48,7 +52,8 @@ class MoonbaseSettingsStore extends Store { uniqueId, state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled - : ExtensionState.Disabled + : ExtensionState.Disabled, + compat: checkExtensionCompat(ext.manifest) }; } @@ -64,11 +69,13 @@ class MoonbaseSettingsStore extends Store { uniqueId, manifest: ext, source: { type: ExtensionLoadSource.Normal, url: repo }, - state: ExtensionState.NotDownloaded + state: ExtensionState.NotDownloaded, + compat: ExtensionCompat.Compatible }; - const apiLevel = ext.apiLevel ?? 1; - if (apiLevel !== constants.apiLevel) continue; + // Don't present incompatible updates + if (checkExtensionCompat(ext) !== ExtensionCompat.Compatible) + continue; const existing = this.getExisting(extensionData); if (existing != null) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx index 045bd6f..8833fc0 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/card.tsx @@ -1,5 +1,6 @@ import { ExtensionState } from "../../../types"; import { ExtensionLoadSource } from "@moonlight-mod/types"; +import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import * as Components from "@moonlight-mod/wp/discord/components/common/index"; @@ -37,6 +38,12 @@ const BuildOverrideClasses = spacepack.findByExports( "disabledButtonOverride" )[0].exports; +const COMPAT_TEXT_MAP: Record = { + [ExtensionCompat.Compatible]: "huh?", + [ExtensionCompat.InvalidApiLevel]: "Incompatible API level", + [ExtensionCompat.InvalidEnvironment]: "Incompatible platform" +}; + export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { const [tab, setTab] = React.useState(ExtensionPage.Info); const [restartNeeded, setRestartNeeded] = React.useState(false); @@ -110,16 +117,26 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { justify={Flex.Justify.END} > {ext.state === ExtensionState.NotDownloaded ? ( - + {(props: any) => ( + + )} + ) : (
works lmao @@ -162,18 +179,25 @@ export default function ExtensionCard({ uniqueId }: { uniqueId: number }) { )} 1 ? "s" : "" - }: ${enabledDependants - .map((a) => a.manifest.meta?.name ?? a.id) - .join(", ")}` - : undefined + ext.compat !== ExtensionCompat.Compatible + ? COMPAT_TEXT_MAP[ext.compat] + : implicitlyEnabled + ? `This extension is a dependency of the following enabled extension${ + enabledDependants.length > 1 ? "s" : "" + }: ${enabledDependants + .map((a) => a.manifest.meta?.name ?? a.id) + .join(", ")}` + : undefined } onChange={() => { setRestartNeeded(true); diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx index 493fcc8..a3049e1 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/filterBar.tsx @@ -24,9 +24,10 @@ export enum Filter { Enabled = 1 << 3, Disabled = 1 << 4, Installed = 1 << 5, - Repository = 1 << 6 + Repository = 1 << 6, + Incompatible = 1 << 7 } -export const defaultFilter = ~(~0 << 7); +export const defaultFilter = 127 as Filter; const Margins = spacepack.findByCode("marginCenterHorz:")[0].exports; const SortMenuClasses = spacepack.findByCode("container:", "clearText:")[0] @@ -130,6 +131,12 @@ function FilterButtonPopout({ /> + toggleFilter(Filter.Incompatible)} + /> { - exts = exts.filter((x) => x.manifest.apiLevel === constants.apiLevel); + exts = exts.filter( + (ext) => checkExtensionCompat(ext.manifest) === ExtensionCompat.Compatible + ); const config = await readConfig(); const items = exts diff --git a/packages/types/src/extension.ts b/packages/types/src/extension.ts index 8eaa777..a8094fe 100644 --- a/packages/types/src/extension.ts +++ b/packages/types/src/extension.ts @@ -31,6 +31,7 @@ export type ExtensionManifest = { id: string; version?: string; apiLevel?: number; + environment?: ExtensionEnvironment; meta?: { name?: string; @@ -52,6 +53,12 @@ export type ExtensionManifest = { blocked?: string[]; }; +export enum ExtensionEnvironment { + Both = "both", + Desktop = "desktop", + Web = "web" +} + export enum ExtensionLoadSource { Developer, Core, From 01d483b3852cf931bf1b47f986ca21f401a01393 Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 16:52:34 -0400 Subject: [PATCH 59/73] Exposing FS to web is bad actually --- packages/browser/src/index.ts | 4 +- .../core-extensions/src/moonbase/native.ts | 21 +++-- .../src/moonbase/webpackModules/stores.ts | 2 +- packages/core/src/config.ts | 15 +-- packages/core/src/extension.ts | 72 +++++++-------- packages/core/src/fs.ts | 92 +++++++++---------- packages/core/src/util/data.ts | 28 +++--- packages/injector/src/index.ts | 3 + packages/node-preload/src/index.ts | 6 +- packages/types/src/globals.ts | 3 +- packages/types/src/index.ts | 2 +- 11 files changed, 124 insertions(+), 124 deletions(-) diff --git a/packages/browser/src/index.ts b/packages/browser/src/index.ts index 4b98643..1d48a61 100644 --- a/packages/browser/src/index.ts +++ b/packages/browser/src/index.ts @@ -26,7 +26,7 @@ window._moonlightBrowserInit = async () => { } }); - window._moonlightBrowserFS = { + window.moonlightFS = { async readFile(path) { return new Uint8Array(await fs.readFile(path)); }, @@ -108,7 +108,7 @@ window._moonlightBrowserInit = async () => { extensions, processedExtensions, nativesCache: {}, - fs: window._moonlightBrowserFS, + isBrowser: true, version: MOONLIGHT_VERSION, branch: MOONLIGHT_BRANCH as MoonlightBranch, diff --git a/packages/core-extensions/src/moonbase/native.ts b/packages/core-extensions/src/moonbase/native.ts index 6ac49aa..24b87bf 100644 --- a/packages/core-extensions/src/moonbase/native.ts +++ b/packages/core-extensions/src/moonbase/native.ts @@ -8,7 +8,6 @@ export const nightlyRefUrl = "https://moonlight-mod.github.io/moonlight/ref"; export const userAgent = `moonlight/${moonlightNode.version} (https://github.com/moonlight-mod/moonlight)`; export default function getNatives(): MoonbaseNatives { - const fs = moonlightNode.fs; const logger = moonlightNode.getLogger("moonbase/natives"); return { @@ -71,25 +70,29 @@ export default function getNatives(): MoonbaseNatives { const dir = moonlightNode.getExtensionDir(manifest.id); // remake it in case of updates - if (await fs.exists(dir)) await fs.rmdir(dir); - await fs.mkdir(dir); + if (await moonlightFS.exists(dir)) await moonlightFS.rmdir(dir); + await moonlightFS.mkdir(dir); const buffer = await req.arrayBuffer(); const files = extractAsar(buffer); for (const [file, buf] of Object.entries(files)) { - const fullFile = fs.join(dir, file); - const fullDir = fs.dirname(fullFile); + const fullFile = moonlightFS.join(dir, file); + const fullDir = moonlightFS.dirname(fullFile); - if (!(await fs.exists(fullDir))) await fs.mkdir(fullDir); - await fs.writeFile(fs.join(dir, file), buf); + if (!(await moonlightFS.exists(fullDir))) + await moonlightFS.mkdir(fullDir); + await moonlightFS.writeFile(moonlightFS.join(dir, file), buf); } - await fs.writeFileString(fs.join(dir, repoUrlFile), repo); + await moonlightFS.writeFileString( + moonlightFS.join(dir, repoUrlFile), + repo + ); }, async deleteExtension(id) { const dir = moonlightNode.getExtensionDir(id); - await fs.rmdir(dir); + await moonlightFS.rmdir(dir); }, getExtensionConfig(id, key) { diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 2cfe0e4..209ece9 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -8,7 +8,7 @@ import { mainRepo } from "@moonlight-mod/types/constants"; const logger = moonlight.getLogger("moonbase"); let natives: MoonbaseNatives = moonlight.getNatives("moonbase"); -if (!natives) natives = getNatives(); +if (moonlightNode.isBrowser) natives = getNatives(); class MoonbaseSettingsStore extends Store { private origConfig: Config; diff --git a/packages/core/src/config.ts b/packages/core/src/config.ts index e05e8c7..b824cc1 100644 --- a/packages/core/src/config.ts +++ b/packages/core/src/config.ts @@ -1,7 +1,6 @@ import { Config } from "@moonlight-mod/types"; import { getConfigPath } from "./util/data"; import * as constants from "@moonlight-mod/types/constants"; -import getFS from "./fs"; import Logger from "./util/logger"; const logger = new Logger("core/config"); @@ -18,9 +17,11 @@ const defaultConfig: Config = { export async function writeConfig(config: Config) { try { - const fs = getFS(); const configPath = await getConfigPath(); - await fs.writeFileString(configPath, JSON.stringify(config, null, 2)); + await moonlightFS.writeFileString( + configPath, + JSON.stringify(config, null, 2) + ); } catch (e) { logger.error("Failed to write config", e); } @@ -31,15 +32,15 @@ export async function readConfig(): Promise { return moonlightNode.config; } - const fs = getFS(); - const configPath = await getConfigPath(); - if (!(await fs.exists(configPath))) { + if (!(await moonlightFS.exists(configPath))) { await writeConfig(defaultConfig); return defaultConfig; } else { try { - let config: Config = JSON.parse(await fs.readFileString(configPath)); + let config: Config = JSON.parse( + await moonlightFS.readFileString(configPath) + ); // Assign the default values if they don't exist (newly added) config = { ...defaultConfig, ...config }; await writeConfig(config); diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index 7e1bb84..19e9744 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -2,28 +2,26 @@ import { ExtensionManifest, DetectedExtension, ExtensionLoadSource, - constants, - MoonlightFS + constants } from "@moonlight-mod/types"; import { readConfig } from "./config"; import { getCoreExtensionsPath, getExtensionsPath } from "./util/data"; import Logger from "./util/logger"; -import getFS from "./fs"; const logger = new Logger("core/extension"); -async function findManifests(fs: MoonlightFS, dir: string): Promise { +async function findManifests(dir: string): Promise { const ret = []; - if (await fs.exists(dir)) { - for (const file of await fs.readdir(dir)) { - const path = fs.join(dir, file); + if (await moonlightFS.exists(dir)) { + for (const file of await moonlightFS.readdir(dir)) { + const path = moonlightFS.join(dir, file); if (file === "manifest.json") { ret.push(path); } - if (!(await fs.isFile(path))) { - ret.push(...(await findManifests(fs, path))); + if (!(await moonlightFS.isFile(path))) { + ret.push(...(await findManifests(path))); } } } @@ -32,50 +30,58 @@ async function findManifests(fs: MoonlightFS, dir: string): Promise { } async function loadDetectedExtensions( - fs: MoonlightFS, dir: string, type: ExtensionLoadSource ): Promise { const ret: DetectedExtension[] = []; - const manifests = await findManifests(fs, dir); + const manifests = await findManifests(dir); for (const manifestPath of manifests) { try { - if (!(await fs.exists(manifestPath))) continue; - const dir = fs.dirname(manifestPath); + if (!(await moonlightFS.exists(manifestPath))) continue; + const dir = moonlightFS.dirname(manifestPath); const manifest: ExtensionManifest = JSON.parse( - await fs.readFileString(manifestPath) + await moonlightFS.readFileString(manifestPath) ); - const webPath = fs.join(dir, "index.js"); - const nodePath = fs.join(dir, "node.js"); - const hostPath = fs.join(dir, "host.js"); + const webPath = moonlightFS.join(dir, "index.js"); + const nodePath = moonlightFS.join(dir, "node.js"); + const hostPath = moonlightFS.join(dir, "host.js"); // if none exist (empty manifest) don't give a shit - if (!fs.exists(webPath) && !fs.exists(nodePath) && !fs.exists(hostPath)) { + if ( + !moonlightFS.exists(webPath) && + !moonlightFS.exists(nodePath) && + !moonlightFS.exists(hostPath) + ) { continue; } - const web = (await fs.exists(webPath)) - ? await fs.readFileString(webPath) + const web = (await moonlightFS.exists(webPath)) + ? await moonlightFS.readFileString(webPath) : undefined; let url: string | undefined = undefined; - const urlPath = fs.join(dir, constants.repoUrlFile); - if (type === ExtensionLoadSource.Normal && (await fs.exists(urlPath))) { - url = await fs.readFileString(urlPath); + const urlPath = moonlightFS.join(dir, constants.repoUrlFile); + if ( + type === ExtensionLoadSource.Normal && + (await moonlightFS.exists(urlPath)) + ) { + url = await moonlightFS.readFileString(urlPath); } const wpModules: Record = {}; - const wpModulesPath = fs.join(dir, "webpackModules"); - if (await fs.exists(wpModulesPath)) { - const wpModulesFile = await fs.readdir(wpModulesPath); + const wpModulesPath = moonlightFS.join(dir, "webpackModules"); + if (await moonlightFS.exists(wpModulesPath)) { + const wpModulesFile = await moonlightFS.readdir(wpModulesPath); for (const wpModuleFile of wpModulesFile) { if (wpModuleFile.endsWith(".js")) { wpModules[wpModuleFile.replace(".js", "")] = - await fs.readFileString(fs.join(wpModulesPath, wpModuleFile)); + await moonlightFS.readFileString( + moonlightFS.join(wpModulesPath, wpModuleFile) + ); } } } @@ -91,8 +97,8 @@ async function loadDetectedExtensions( web, webPath: web != null ? webPath : undefined, webpackModules: wpModules, - nodePath: (await fs.exists(nodePath)) ? nodePath : undefined, - hostPath: (await fs.exists(hostPath)) ? hostPath : undefined + nodePath: (await moonlightFS.exists(nodePath)) ? nodePath : undefined, + hostPath: (await moonlightFS.exists(hostPath)) ? hostPath : undefined } }); } catch (e) { @@ -106,11 +112,9 @@ async function loadDetectedExtensions( async function getExtensionsNative(): Promise { const config = await readConfig(); const res = []; - const fs = getFS(); res.push( ...(await loadDetectedExtensions( - fs, getCoreExtensionsPath(), ExtensionLoadSource.Core )) @@ -118,7 +122,6 @@ async function getExtensionsNative(): Promise { res.push( ...(await loadDetectedExtensions( - fs, await getExtensionsPath(), ExtensionLoadSource.Normal )) @@ -127,7 +130,6 @@ async function getExtensionsNative(): Promise { for (const devSearchPath of config.devSearchPaths ?? []) { res.push( ...(await loadDetectedExtensions( - fs, devSearchPath, ExtensionLoadSource.Developer )) @@ -176,11 +178,9 @@ async function getExtensionsBrowser(): Promise { }); } - const fs = getFS(); - if (await fs.exists("/extensions")) { + if (await moonlightFS.exists("/extensions")) { ret.push( ...(await loadDetectedExtensions( - fs, "/extensions", ExtensionLoadSource.Normal )) diff --git a/packages/core/src/fs.ts b/packages/core/src/fs.ts index 70eb8fe..123b4e5 100644 --- a/packages/core/src/fs.ts +++ b/packages/core/src/fs.ts @@ -1,56 +1,50 @@ -import { MoonlightFS } from "@moonlight-mod/types"; +import type { MoonlightFS } from "@moonlight-mod/types"; import requireImport from "./util/import"; -export default function getFS(): MoonlightFS { - browser: { - return window._moonlightBrowserFS!; - } +export default function createFS(): MoonlightFS { + const fs = requireImport("fs"); + const path = requireImport("path"); - nodeTarget: { - const fs = requireImport("fs"); - const path = requireImport("path"); + return { + async readFile(path) { + const file = fs.readFileSync(path); + return new Uint8Array(file); + }, + async readFileString(path) { + return fs.readFileSync(path, "utf8"); + }, + async writeFile(path, data) { + fs.writeFileSync(path, Buffer.from(data)); + }, + async writeFileString(path, data) { + fs.writeFileSync(path, data, "utf8"); + }, + async unlink(path) { + fs.unlinkSync(path); + }, - return { - async readFile(path) { - const file = fs.readFileSync(path); - return new Uint8Array(file); - }, - async readFileString(path) { - return fs.readFileSync(path, "utf8"); - }, - async writeFile(path, data) { - fs.writeFileSync(path, Buffer.from(data)); - }, - async writeFileString(path, data) { - fs.writeFileSync(path, data, "utf8"); - }, - async unlink(path) { - fs.unlinkSync(path); - }, + async readdir(path) { + return fs.readdirSync(path); + }, + async mkdir(path) { + fs.mkdirSync(path, { recursive: true }); + }, + async rmdir(path) { + fs.rmSync(path, { recursive: true }); + }, - async readdir(path) { - return fs.readdirSync(path); - }, - async mkdir(path) { - fs.mkdirSync(path, { recursive: true }); - }, - async rmdir(path) { - fs.rmSync(path, { recursive: true }); - }, + async exists(path) { + return fs.existsSync(path); + }, + async isFile(path) { + return fs.statSync(path).isFile(); + }, - async exists(path) { - return fs.existsSync(path); - }, - async isFile(path) { - return fs.statSync(path).isFile(); - }, - - join(...parts) { - return path.join(...parts); - }, - dirname(dir) { - return path.dirname(dir); - } - }; - } + join(...parts) { + return path.join(...parts); + }, + dirname(dir) { + return path.dirname(dir); + } + }; } diff --git a/packages/core/src/util/data.ts b/packages/core/src/util/data.ts index aa922f8..7f51ef4 100644 --- a/packages/core/src/util/data.ts +++ b/packages/core/src/util/data.ts @@ -1,5 +1,4 @@ import { constants } from "@moonlight-mod/types"; -import getFS from "../fs"; export async function getMoonlightDir() { browser: { @@ -7,7 +6,6 @@ export async function getMoonlightDir() { } const electron = require("electron"); - const fs = getFS(); let appData = ""; injector: { @@ -18,8 +16,8 @@ export async function getMoonlightDir() { appData = electron.ipcRenderer.sendSync(constants.ipcGetAppData); } - const dir = fs.join(appData, "moonlight-mod"); - if (!(await fs.exists(dir))) await fs.mkdir(dir); + const dir = moonlightFS.join(appData, "moonlight-mod"); + if (!(await moonlightFS.exists(dir))) await moonlightFS.mkdir(dir); return dir; } @@ -34,19 +32,21 @@ export async function getConfigPath() { return "/config.json"; } - const fs = getFS(); const dir = await getMoonlightDir(); let configPath = ""; - const buildInfoPath = fs.join(process.resourcesPath, "build_info.json"); - if (!(await fs.exists(buildInfoPath))) { - configPath = fs.join(dir, "desktop.json"); + const buildInfoPath = moonlightFS.join( + process.resourcesPath, + "build_info.json" + ); + if (!(await moonlightFS.exists(buildInfoPath))) { + configPath = moonlightFS.join(dir, "desktop.json"); } else { const buildInfo: BuildInfo = JSON.parse( - await fs.readFileString(buildInfoPath) + await moonlightFS.readFileString(buildInfoPath) ); - configPath = fs.join(dir, buildInfo.releaseChannel + ".json"); + configPath = moonlightFS.join(dir, buildInfo.releaseChannel + ".json"); } return configPath; @@ -54,10 +54,9 @@ export async function getConfigPath() { async function getPathFromMoonlight(...names: string[]) { const dir = await getMoonlightDir(); - const fs = getFS(); - const target = fs.join(dir, ...names); - if (!(await fs.exists(target))) await fs.mkdir(target); + const target = moonlightFS.join(dir, ...names); + if (!(await moonlightFS.exists(target))) await moonlightFS.mkdir(target); return target; } @@ -67,6 +66,5 @@ export async function getExtensionsPath() { } export function getCoreExtensionsPath(): string { - const fs = getFS(); - return fs.join(__dirname, constants.coreExtensionsDir); + return moonlightFS.join(__dirname, constants.coreExtensionsDir); } diff --git a/packages/injector/src/index.ts b/packages/injector/src/index.ts index a7eea23..5212f0a 100644 --- a/packages/injector/src/index.ts +++ b/packages/injector/src/index.ts @@ -16,6 +16,7 @@ import { import EventEmitter from "node:events"; import { join, resolve } from "node:path"; import persist from "@moonlight-mod/core/persist"; +import createFS from "@moonlight-mod/core/fs"; const logger = new Logger("injector"); @@ -201,6 +202,8 @@ Object.defineProperty(BrowserWindow, "name", { export async function inject(asarPath: string) { isMoonlightDesktop = asarPath === "moonlightDesktop"; + global.moonlightFS = createFS(); + try { const config = await readConfig(); initLogger(config); diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index 4828734..6f04d56 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -14,9 +14,11 @@ import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; -import getFS from "@moonlight-mod/core/fs"; +import createFS from "core/src/fs"; async function injectGlobals() { + global.moonlightFS = createFS(); + const config = await readConfig(); initLogger(config); const extensions = await getExtensions(); @@ -35,7 +37,7 @@ async function injectGlobals() { extensions, processedExtensions, nativesCache: {}, - fs: getFS(), + isBrowser: false, version: MOONLIGHT_VERSION, branch: MOONLIGHT_BRANCH as MoonlightBranch, diff --git a/packages/types/src/globals.ts b/packages/types/src/globals.ts index 076dc41..be31d44 100644 --- a/packages/types/src/globals.ts +++ b/packages/types/src/globals.ts @@ -14,7 +14,6 @@ import type { EventType, MoonlightEventEmitter } from "./core/event"; -import type { MoonlightFS } from "./fs"; export type MoonlightHost = { asarPath: string; @@ -36,7 +35,7 @@ export type MoonlightNode = { extensions: DetectedExtension[]; processedExtensions: ProcessedExtensions; nativesCache: Record; - fs: MoonlightFS; + isBrowser: boolean; version: string; branch: MoonlightBranch; diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 7414426..ebce9a4 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -37,8 +37,8 @@ declare global { var moonlightHost: MoonlightHost; var moonlightNode: MoonlightNode; var moonlight: MoonlightWeb; + var moonlightFS: MoonlightFS; var _moonlightBrowserInit: () => Promise; var _moonlightBrowserLoad: () => Promise; - var _moonlightBrowserFS: MoonlightFS; } From df1fc19cca6a21d93b18743ebb660c77430ed483 Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 17:00:14 -0400 Subject: [PATCH 60/73] Remove testing manifest change --- packages/core-extensions/src/nativeFixes/manifest.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/core-extensions/src/nativeFixes/manifest.json b/packages/core-extensions/src/nativeFixes/manifest.json index 6dbf511..29368b5 100644 --- a/packages/core-extensions/src/nativeFixes/manifest.json +++ b/packages/core-extensions/src/nativeFixes/manifest.json @@ -38,6 +38,5 @@ "default": true } }, - "apiLevel": 2, - "environment": "desktosdfp" + "apiLevel": 2 } From 9daa73f7c96cafe9fd0698f92af3e00eab6dddff Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 17:10:32 -0400 Subject: [PATCH 61/73] moonbase: Incompatible filter exception for updates --- packages/core-extensions/src/moonbase/types.ts | 1 + .../core-extensions/src/moonbase/webpackModules/stores.ts | 7 +++++-- .../src/moonbase/webpackModules/ui/extensions/index.tsx | 3 ++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/moonbase/types.ts b/packages/core-extensions/src/moonbase/types.ts index f30a77b..082de3e 100644 --- a/packages/core-extensions/src/moonbase/types.ts +++ b/packages/core-extensions/src/moonbase/types.ts @@ -32,4 +32,5 @@ export type MoonbaseExtension = { source: DetectedExtension["source"]; state: ExtensionState; compat: ExtensionCompat; + hasUpdate: boolean; }; diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index b796df3..44d7e19 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -53,7 +53,8 @@ class MoonbaseSettingsStore extends Store { state: moonlight.enabledExtensions.has(ext.id) ? ExtensionState.Enabled : ExtensionState.Disabled, - compat: checkExtensionCompat(ext.manifest) + compat: checkExtensionCompat(ext.manifest), + hasUpdate: false }; } @@ -70,7 +71,8 @@ class MoonbaseSettingsStore extends Store { manifest: ext, source: { type: ExtensionLoadSource.Normal, url: repo }, state: ExtensionState.NotDownloaded, - compat: ExtensionCompat.Compatible + compat: ExtensionCompat.Compatible, + hasUpdate: false }; // Don't present incompatible updates @@ -95,6 +97,7 @@ class MoonbaseSettingsStore extends Store { version: ext.version!, download: ext.download }; + existing.hasUpdate = true; } continue; diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx index fa6ea0a..2a08606 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx @@ -76,7 +76,8 @@ export default function ExtensionsPage() { ext.state === ExtensionState.NotDownloaded) ) && (filter & Filter.Incompatible || - ext.compat === ExtensionCompat.Compatible) + ext.compat === ExtensionCompat.Compatible || + (ext.compat === ExtensionCompat.InvalidApiLevel && ext.hasUpdate)) ); return ( From 445cb7f535c02221c1484f25938a7e618aea51a3 Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 17:14:36 -0400 Subject: [PATCH 62/73] moonbase: Recalculate compat when updating --- .../src/moonbase/webpackModules/stores.ts | 26 ++++++++++++++++--- 1 file changed, 22 insertions(+), 4 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 44d7e19..4a8af34 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -1,5 +1,10 @@ import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; -import { ExtensionState, MoonbaseExtension, MoonbaseNatives } from "../types"; +import { + ExtensionState, + MoonbaseExtension, + MoonbaseNatives, + RepositoryManifest +} from "../types"; import { Store } from "@moonlight-mod/wp/discord/packages/flux"; import Dispatcher from "@moonlight-mod/wp/discord/Dispatcher"; import getNatives from "../native"; @@ -27,7 +32,13 @@ class MoonbaseSettingsStore extends Store { shouldShowNotice: boolean; extensions: { [id: number]: MoonbaseExtension }; - updates: { [id: number]: { version: string; download: string } }; + updates: { + [id: number]: { + version: string; + download: string; + updateManifest: RepositoryManifest; + }; + }; constructor() { super(Dispatcher); @@ -95,7 +106,8 @@ class MoonbaseSettingsStore extends Store { if (this.hasUpdate(extensionData)) { this.updates[existing.uniqueId] = { version: ext.version!, - download: ext.download + download: ext.download, + updateManifest: ext }; existing.hasUpdate = true; } @@ -263,12 +275,18 @@ class MoonbaseSettingsStore extends Store { this.installing = true; try { - const url = this.updates[uniqueId]?.download ?? ext.manifest.download; + const update = this.updates[uniqueId]; + const url = update?.download ?? ext.manifest.download; await natives!.installExtension(ext.manifest, url, ext.source.url!); if (ext.state === ExtensionState.NotDownloaded) { this.extensions[uniqueId].state = ExtensionState.Disabled; } + if (update != null) + this.extensions[uniqueId].compat = checkExtensionCompat( + update.updateManifest + ); + delete this.updates[uniqueId]; } catch (e) { logger.error("Error installing extension:", e); From c5811f74b67eaeb347a16cafd1486eb86c3f5be1 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 13:39:42 -0600 Subject: [PATCH 63/73] moonlight: Restore split sections and give them breadcrumb titles --- .../src/moonbase/webpackModules/moonbase.tsx | 94 ++++++++++++++++--- .../src/moonbase/webpackModules/updates.tsx | 14 ++- 2 files changed, 92 insertions(+), 16 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx b/packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx index 55080b5..855b17c 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx @@ -4,12 +4,16 @@ import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; -import { MenuItem } from "@moonlight-mod/wp/discord/components/common/index"; +import * as Components from "@moonlight-mod/wp/discord/components/common/index"; + +const { MenuItem, Text, Breadcrumbs } = Components; + +const Margins = spacepack.require("discord/styles/shared/Margins.css"); const { open } = spacepack.findByExports("setSection", "clearSubsection")[0] .exports.Z; -settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, { +const notice = { stores: [MoonbaseSettingsStore], element: () => { // Require it here because lazy loading SUX @@ -29,16 +33,76 @@ settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, { /> ); } -}); - -settings.addSectionMenuItems( - "moonbase", - ...pages.map((page, i) => ( - open("moonbase", i)} - /> - )) -); +}; + +function addSection( + id: string, + name: string, + element: React.FunctionComponent +) { + settings.addSection(`moonbase-${id}`, name, element, null, -2, notice); +} + +// FIXME: move to component types +type Breadcrumb = { + id: string; + label: string; +}; + +function renderBreadcrumb(crumb: Breadcrumb, last: boolean) { + return ( + + {crumb.label} + + ); +} + +if ( + MoonbaseSettingsStore.getExtensionConfigRaw( + "moonbase", + "sections", + false + ) +) { + settings.addHeader("Moonbase", -2); + + for (const page of pages) { + addSection(page.id, page.name, () => { + const breadcrumbs = [ + { id: "moonbase", label: "Moonbase" }, + { id: page.id, label: page.name } + ]; + return ( + <> + + {page.name} + + + + ); + }); + } +} else { + settings.addSection("moonbase", "Moonbase", Moonbase, null, -2, notice); + + settings.addSectionMenuItems( + "moonbase", + ...pages.map((page, i) => ( + open("moonbase", i)} + /> + )) + ); +} diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.tsx b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx index 7d3dab6..240035c 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx @@ -82,7 +82,19 @@ function listener() { // FIXME: figure out a way to detect if settings has been opened // alreadyjust so the transition isnt as jarring open(UserSettingsSections.ACCOUNT); - setTimeout(() => open("moonbase", 0), 0); + setTimeout(() => { + if ( + MoonbaseSettingsStore.getExtensionConfigRaw( + "moonbase", + "sections", + false + ) + ) { + open("moonbase-updates"); + } else { + open("moonbase", 0); + } + }, 0); return true; } } From 9f0cdf5257ba20ede1b0c239c12f494f4be73a4d Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 15:34:26 -0600 Subject: [PATCH 64/73] fix fs --- .../core-extensions/src/moonbase/native.ts | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/packages/core-extensions/src/moonbase/native.ts b/packages/core-extensions/src/moonbase/native.ts index 366c5e7..7821484 100644 --- a/packages/core-extensions/src/moonbase/native.ts +++ b/packages/core-extensions/src/moonbase/native.ts @@ -108,9 +108,9 @@ export default function getNatives(): MoonbaseNatives { if (!tar || !ref) return; - const dist = fs.join(moonlightNode.getMoonlightDir(), distDir); - if (await fs.exists(dist)) await fs.rmdir(dist); - await fs.mkdir(dist); + const dist = moonlightFS.join(moonlightNode.getMoonlightDir(), distDir); + if (await moonlightFS.exists(dist)) await moonlightFS.rmdir(dist); + await moonlightFS.mkdir(dist); logger.debug("Extracting update"); const files = await parseTarGzip(tar); @@ -119,18 +119,19 @@ export default function getNatives(): MoonbaseNatives { // @ts-expect-error What do you mean their own types are wrong if (file.type !== "file") continue; - const fullFile = fs.join(dist, file.name); - const fullDir = fs.dirname(fullFile); - if (!(await fs.exists(fullDir))) await fs.mkdir(fullDir); - await fs.writeFile(fullFile, file.data); + const fullFile = moonlightFS.join(dist, file.name); + const fullDir = moonlightFS.dirname(fullFile); + if (!(await moonlightFS.exists(fullDir))) + await moonlightFS.mkdir(fullDir); + await moonlightFS.writeFile(fullFile, file.data); } logger.debug("Writing version file:", ref); - const versionFile = fs.join( + const versionFile = moonlightFS.join( moonlightNode.getMoonlightDir(), installedVersionFile ); - await fs.writeFileString(versionFile, ref.trim()); + await moonlightFS.writeFileString(versionFile, ref.trim()); logger.debug("Update extracted"); }, From 67ca7f41b3e2f28509b374765aa9baf2bf03193d Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 18:42:50 -0400 Subject: [PATCH 65/73] moonbase: Make custom settings real --- .../core-extensions/src/moonbase/index.tsx | 6 +++- .../src/moonbase/webpackModules/moonbase.ts | 10 ++++++ .../{moonbase.tsx => settings.tsx} | 0 .../src/moonbase/webpackModules/stores.ts | 16 +++++++++ .../webpackModules/ui/extensions/settings.tsx | 36 ++++++++++++++++++- packages/types/src/coreExtensions.ts | 1 + packages/types/src/coreExtensions/moonbase.ts | 12 +++++++ packages/types/src/discord/require.ts | 3 ++ packages/types/src/import.d.ts | 6 ++++ 9 files changed, 88 insertions(+), 2 deletions(-) create mode 100644 packages/core-extensions/src/moonbase/webpackModules/moonbase.ts rename packages/core-extensions/src/moonbase/webpackModules/{moonbase.tsx => settings.tsx} (100%) create mode 100644 packages/types/src/coreExtensions/moonbase.ts diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 5e10d35..5edbacd 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -21,7 +21,7 @@ export const webpackModules: Record = { ] }, - moonbase: { + settings: { dependencies: [ { ext: "spacepack", id: "spacepack" }, { ext: "settings", id: "settings" }, @@ -42,6 +42,10 @@ export const webpackModules: Record = { } ], entrypoint: true + }, + + moonbase: { + dependencies: [{ ext: "moonbase", id: "stores" }] } }; diff --git a/packages/core-extensions/src/moonbase/webpackModules/moonbase.ts b/packages/core-extensions/src/moonbase/webpackModules/moonbase.ts new file mode 100644 index 0000000..c85e7a8 --- /dev/null +++ b/packages/core-extensions/src/moonbase/webpackModules/moonbase.ts @@ -0,0 +1,10 @@ +import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; +import type { Moonbase } from "@moonlight-mod/types/coreExtensions/moonbase"; + +export const moonbase: Moonbase = { + registerConfigComponent(ext, option, component) { + MoonbaseSettingsStore.registerConfigComponent(ext, option, component); + } +}; + +export default moonbase; diff --git a/packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx b/packages/core-extensions/src/moonbase/webpackModules/settings.tsx similarity index 100% rename from packages/core-extensions/src/moonbase/webpackModules/moonbase.tsx rename to packages/core-extensions/src/moonbase/webpackModules/settings.tsx diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 4a8af34..422d1b9 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -13,6 +13,7 @@ import { checkExtensionCompat, ExtensionCompat } from "@moonlight-mod/core/extension/loader"; +import { CustomComponent } from "@moonlight-mod/types/coreExtensions/moonbase"; const logger = moonlight.getLogger("moonbase"); @@ -23,6 +24,8 @@ class MoonbaseSettingsStore extends Store { private origConfig: Config; private config: Config; private extensionIndex: number; + private configComponents: Record> = + {}; modified: boolean; submitting: boolean; @@ -371,6 +374,19 @@ class MoonbaseSettingsStore extends Store { return (uniqueId != null ? this.getExtensionName(uniqueId) : null) ?? id; } + registerConfigComponent( + ext: string, + name: string, + component: CustomComponent + ) { + if (!(ext in this.configComponents)) this.configComponents[ext] = {}; + this.configComponents[ext][name] = component; + } + + getExtensionConfigComponent(ext: string, name: string) { + return this.configComponents[ext]?.[name]; + } + writeConfig() { this.submitting = true; diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx index 9b1f2c4..0dac614 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx @@ -392,6 +392,39 @@ function Dictionary({ ext, name, setting, disabled }: SettingsProps) { ); } +function Custom({ ext, name, setting, disabled }: SettingsProps) { + const { value, displayName } = useConfigEntry(ext.uniqueId, name); + + const { component: Component } = useStateFromStores( + [MoonbaseSettingsStore], + () => { + return { + component: MoonbaseSettingsStore.getExtensionConfigComponent( + ext.id, + name + ) + }; + }, + [ext.uniqueId, name] + ); + + if (Component == null) { + const { Text } = Components; + return ( + {`Custom setting "${displayName}" is missing a component. Perhaps the extension is not installed?`} + ); + } + + return ( + + MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value) + } + /> + ); +} + function Setting({ ext, name, setting, disabled }: SettingsProps) { const elements: Partial> = { [ExtensionSettingType.Boolean]: Boolean, @@ -401,7 +434,8 @@ function Setting({ ext, name, setting, disabled }: SettingsProps) { [ExtensionSettingType.Select]: Select, [ExtensionSettingType.MultiSelect]: MultiSelect, [ExtensionSettingType.List]: List, - [ExtensionSettingType.Dictionary]: Dictionary + [ExtensionSettingType.Dictionary]: Dictionary, + [ExtensionSettingType.Custom]: Custom }; const element = elements[setting.type]; if (element == null) return <>; diff --git a/packages/types/src/coreExtensions.ts b/packages/types/src/coreExtensions.ts index e49cda1..fd461a1 100644 --- a/packages/types/src/coreExtensions.ts +++ b/packages/types/src/coreExtensions.ts @@ -3,3 +3,4 @@ export * as Settings from "./coreExtensions/settings"; export * as Markdown from "./coreExtensions/markdown"; export * as ContextMenu from "./coreExtensions/contextMenu"; export * as Notices from "./coreExtensions/notices"; +export * as Moonbase from "./coreExtensions/moonbase"; diff --git a/packages/types/src/coreExtensions/moonbase.ts b/packages/types/src/coreExtensions/moonbase.ts new file mode 100644 index 0000000..9a5c84b --- /dev/null +++ b/packages/types/src/coreExtensions/moonbase.ts @@ -0,0 +1,12 @@ +export type CustomComponent = React.FC<{ + value: any; + setValue: (value: any) => void; +}>; + +export type Moonbase = { + registerConfigComponent: ( + ext: string, + option: string, + component: CustomComponent + ) => void; +}; diff --git a/packages/types/src/discord/require.ts b/packages/types/src/discord/require.ts index a5e4f7f..fa442d7 100644 --- a/packages/types/src/discord/require.ts +++ b/packages/types/src/discord/require.ts @@ -3,6 +3,7 @@ import { Markdown } from "../coreExtensions/markdown"; import { Settings } from "../coreExtensions/settings"; import { Spacepack } from "../coreExtensions/spacepack"; import { Notices } from "../coreExtensions/notices"; +import { Moonbase } from "../coreExtensions/moonbase"; declare function WebpackRequire(id: string): any; @@ -11,6 +12,8 @@ declare function WebpackRequire(id: "contextMenu_contextMenu"): ContextMenu; declare function WebpackRequire(id: "markdown_markdown"): Markdown; +declare function WebpackRequire(id: "moonbase_moonbase"): Moonbase; + declare function WebpackRequire(id: "notices_notices"): Notices; declare function WebpackRequire(id: "settings_settings"): { diff --git a/packages/types/src/import.d.ts b/packages/types/src/import.d.ts index 08365bc..70ad944 100644 --- a/packages/types/src/import.d.ts +++ b/packages/types/src/import.d.ts @@ -17,6 +17,12 @@ declare module "@moonlight-mod/wp/markdown_markdown" { export = Markdown; } +declare module "@moonlight-mod/wp/moonbase_moonbase" { + import { CoreExtensions } from "@moonlight-mod/types"; + const Moonbase: CoreExtensions.Moonbase.Moonbase; + export = Moonbase; +} + declare module "@moonlight-mod/wp/notices_notices" { import { CoreExtensions } from "@moonlight-mod/types"; const Notices: CoreExtensions.Notices.Notices; From 33c8d6dd686e8cb1fe48a0434c0f15f667007dfd Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 21:58:18 -0400 Subject: [PATCH 66/73] Make the update button --- .../core-extensions/src/moonbase/index.tsx | 47 ++++++++++- .../src/moonbase/webpackModules/stores.ts | 4 + .../src/moonbase/webpackModules/ui/index.tsx | 3 + .../src/moonbase/webpackModules/ui/update.tsx | 83 +++++++++++++++++++ 4 files changed, 133 insertions(+), 4 deletions(-) create mode 100644 packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 9ba2fab..28053c9 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -45,9 +45,48 @@ export const webpackModules: Record = { } }; +const bg = "#222034"; +const fg = "#FFFBA6"; + export const styles = [ - ".moonbase-settings > :first-child { margin-top: 0px; }", - "textarea.moonbase-resizeable { resize: vertical }", - ".moonbase-updates-notice { background-color: #222034; color: #FFFBA6; line-height: unset; height: 36px; }", - ".moonbase-updates-notice_text-wrapper { display: inline-flex; align-items: center; line-height: 36px; gap: 2px; }" + ` +.moonbase-settings > :first-child { + margin-top: 0px; +} + +textarea.moonbase-resizeable { + resize: vertical +} + +.moonbase-updates-notice { + background-color: ${bg}; + color: ${fg}; + line-height: unset; + height: 36px; +} + +.moonbase-updates-notice_text-wrapper { + display: inline-flex; + align-items: center; + line-height: 36px; + gap: 2px; +} + +.moonbase-update-section { + background-color: ${bg}; + --info-help-foreground: ${fg}; + border: none !important; + color: ${fg}; + + display: flex; + flex-direction: row; + justify-content: space-between; +} + +.moonbase-update-section > button { + color: ${fg}; + background-color: transparent; + border-color: ${fg}; +} +`.trim() ]; diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts index 1451d20..30fd3a5 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/stores.ts +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -370,6 +370,10 @@ class MoonbaseSettingsStore extends Store { this.emitChange(); } + async updateMoonlight() { + await natives.updateMoonlight(); + } + getConfigOption(key: K): Config[K] { return this.config[key]; } diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx index 54b540f..caddc4f 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/index.tsx @@ -9,6 +9,7 @@ import { UserSettingsModalStore } from "@moonlight-mod/wp/common_stores"; import ExtensionsPage from "./extensions"; import ConfigPage from "./config"; +import Update from "./update"; const { Divider } = spacepack.findByCode(".forumOrHome]:")[0].exports.Z; const TitleBarClasses = spacepack.findByCode("iconWrapper:", "children:")[0] @@ -82,6 +83,8 @@ export function Moonbase(props: { initialTab?: number } = {}) {
+ + {React.createElement(pages[subsection].element)} ); diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx new file mode 100644 index 0000000..3b808e6 --- /dev/null +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx @@ -0,0 +1,83 @@ +import { useStateFromStores } from "@moonlight-mod/wp/discord/packages/flux"; +import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; +import * as Components from "@moonlight-mod/wp/discord/components/common/index"; +import React from "@moonlight-mod/wp/react"; +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; +import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; + +enum UpdateState { + Ready, + Working, + Installed, + Failed +} + +const { ThemeDarkIcon, Text, Button } = Components; +const Margins = spacepack.require("discord/styles/shared/Margins.css"); +const HelpMessageClasses = spacepack.findByExports("positive", "iconDiv")[0] + .exports; + +const logger = moonlight.getLogger("moonbase/ui/update"); + +const strings: Record = { + [UpdateState.Ready]: "A new version of moonlight is available", + [UpdateState.Working]: "Updating moonlight...", + [UpdateState.Installed]: "Updated, restart Discord to apply changes", + [UpdateState.Failed]: "Failed to update moonlight, use the installer instead" +}; + +export default function Update() { + const [state, setState] = React.useState(UpdateState.Ready); + const { newVersion } = useStateFromStores([MoonbaseSettingsStore], () => ({ + newVersion: MoonbaseSettingsStore.newVersion + })); + + if (newVersion == null) return null; + + // reimpl of HelpMessage but with a custom icon + return ( +
+ +
+ +
+ + + {strings[state]} + +
+ + +
+ ); +} From 064485c5e6ca1f5b4462bcc5cdac10a3827b32aa Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 23:11:57 -0400 Subject: [PATCH 67/73] Make notice button yellow as well --- packages/core-extensions/src/moonbase/index.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 28053c9..d3cc780 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -65,6 +65,11 @@ textarea.moonbase-resizeable { height: 36px; } +.moonbase-updates-notice button { + color: ${fg}; + border-color: ${fg}; +} + .moonbase-updates-notice_text-wrapper { display: inline-flex; align-items: center; From c9a835467019478150f64cd5e88cec817bc2f3a8 Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 9 Oct 2024 23:14:03 -0400 Subject: [PATCH 68/73] Consistent punctuation --- .../src/moonbase/webpackModules/ui/update.tsx | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx index 3b808e6..8fd6499 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx @@ -20,10 +20,11 @@ const HelpMessageClasses = spacepack.findByExports("positive", "iconDiv")[0] const logger = moonlight.getLogger("moonbase/ui/update"); const strings: Record = { - [UpdateState.Ready]: "A new version of moonlight is available", + [UpdateState.Ready]: "A new version of moonlight is available.", [UpdateState.Working]: "Updating moonlight...", - [UpdateState.Installed]: "Updated, restart Discord to apply changes", - [UpdateState.Failed]: "Failed to update moonlight, use the installer instead" + [UpdateState.Installed]: "Updated. Restart Discord to apply changes.", + [UpdateState.Failed]: + "Failed to update moonlight. Please use the installer instead." }; export default function Update() { From 0338b3f8d49b5f68f469c3bb0b7586f4405f0caa Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 21:53:02 -0600 Subject: [PATCH 69/73] Fix settings key to open on update notice when using split sections --- .../core-extensions/src/moonbase/webpackModules/updates.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.tsx b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx index 240035c..72eefeb 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx @@ -90,7 +90,7 @@ function listener() { false ) ) { - open("moonbase-updates"); + open("moonbase-extensions"); } else { open("moonbase", 0); } From 244971f85b033eb7f8cc222cbe79e34c1e99f380 Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 21:58:55 -0600 Subject: [PATCH 70/73] fix save call --- .../src/moonbase/webpackModules/ui/extensions/settings.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx index 3ca2d3a..d341b00 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/settings.tsx @@ -419,7 +419,7 @@ function Custom({ ext, name, setting, disabled }: SettingsProps) { - MoonbaseSettingsStore.setExtensionConfig(ext.uniqueId, name, value) + MoonbaseSettingsStore.setExtensionConfig(ext.id, name, value) } /> ); From 7e2c4ca4fb6605eacbae8e30d9487b227a3b595b Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 22:21:27 -0600 Subject: [PATCH 71/73] moonbase.updater: Cleanup useStateFromStores call, set Working state, fix not showing on split sections --- .../src/moonbase/webpackModules/settings.tsx | 5 +++++ .../src/moonbase/webpackModules/ui/update.tsx | 9 ++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/packages/core-extensions/src/moonbase/webpackModules/settings.tsx b/packages/core-extensions/src/moonbase/webpackModules/settings.tsx index 855b17c..1992ea1 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/settings.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/settings.tsx @@ -6,6 +6,8 @@ import { Moonbase, pages } from "@moonlight-mod/wp/moonbase_ui"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; import * as Components from "@moonlight-mod/wp/discord/components/common/index"; +import Update from "./ui/update"; + const { MenuItem, Text, Breadcrumbs } = Components; const Margins = spacepack.require("discord/styles/shared/Margins.css"); @@ -86,6 +88,9 @@ if ( > {page.name} + + + ); diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx index 8fd6499..ada54a7 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/update.tsx @@ -29,9 +29,10 @@ const strings: Record = { export default function Update() { const [state, setState] = React.useState(UpdateState.Ready); - const { newVersion } = useStateFromStores([MoonbaseSettingsStore], () => ({ - newVersion: MoonbaseSettingsStore.newVersion - })); + const newVersion = useStateFromStores( + [MoonbaseSettingsStore], + () => MoonbaseSettingsStore.newVersion + ); if (newVersion == null) return null; @@ -69,6 +70,8 @@ export default function Update() { size={Button.Sizes.TINY} disabled={state !== UpdateState.Ready} onClick={() => { + setState(UpdateState.Working); + MoonbaseSettingsStore.updateMoonlight() .then(() => setState(UpdateState.Installed)) .catch((e) => { From 83cc782ab397c6f2e718f45cd6029821719be75f Mon Sep 17 00:00:00 2001 From: Cynthia Foxwell Date: Wed, 9 Oct 2024 22:28:46 -0600 Subject: [PATCH 72/73] rocketship: reword description a tiny bit and format it better --- packages/core-extensions/src/rocketship/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core-extensions/src/rocketship/manifest.json b/packages/core-extensions/src/rocketship/manifest.json index 3f3b07b..8e327c4 100644 --- a/packages/core-extensions/src/rocketship/manifest.json +++ b/packages/core-extensions/src/rocketship/manifest.json @@ -4,7 +4,7 @@ "meta": { "name": "Rocketship", "tagline": "Adds new features when using rocketship", - "description": "**This extension only works on Linux when using rocketship: https://github.com/moonlight-mod/rocketship**. Adds new features to the Discord Linux client with rocketship, like better screensharing.", + "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.", "authors": ["NotNite", "Cynosphere", "adryd"] } } From 1e0ab85b4fa19a9f28fef19e9ca90097532fe426 Mon Sep 17 00:00:00 2001 From: NotNite Date: Thu, 10 Oct 2024 09:30:35 -0400 Subject: [PATCH 73/73] Fix wrong baseUrl imports pt2 --- .eslintrc.json | 19 ++++++++++++++++++- .../webpackModules/ui/extensions/index.tsx | 2 +- .../webpackModules/ui/extensions/popup.tsx | 2 +- .../src/moonbase/webpackModules/updates.tsx | 2 +- packages/node-preload/src/index.ts | 2 +- 5 files changed, 22 insertions(+), 5 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 66afb63..151d778 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -48,7 +48,24 @@ "@typescript-eslint/no-var-requires": "off", // https://canary.discord.com/channels/1154257010532032512/1154275441788583996/1181760413231230976 - "no-unused-labels": "off" + "no-unused-labels": "off", + + // baseUrl being set to ./packages/ makes language server suggest "types/src" instead of "@moonlight-mod/types" + "no-restricted-imports": [ + "error", + { + "patterns": [ + { + "group": ["types/*"], + "message": "Use @moonlight-mod/types instead" + }, + { + "group": ["core/*"], + "message": "Use @moonlight-mod/core instead" + } + ] + } + ] }, "settings": { "react": { diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx index 8340a55..a41d30d 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/index.tsx @@ -8,7 +8,7 @@ import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import { useStateFromStoresObject } from "@moonlight-mod/wp/discord/packages/flux"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; -import { ExtensionCompat } from "core/src/extension/loader"; +import { ExtensionCompat } from "@moonlight-mod/core/extension/loader"; const SearchBar: any = Object.values( spacepack.findByCode("Messages.SEARCH", "hideSearchIcon")[0].exports diff --git a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx index 5009eeb..3ce573b 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/ui/extensions/popup.tsx @@ -4,7 +4,7 @@ import React from "@moonlight-mod/wp/react"; import { MoonbaseExtension } from "core-extensions/src/moonbase/types"; import * as Components from "@moonlight-mod/wp/discord/components/common/index"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; -import { ExtensionLoadSource } from "types/src"; +import { ExtensionLoadSource } from "@moonlight-mod/types"; import Flex from "@moonlight-mod/wp/discord/uikit/Flex"; const { diff --git a/packages/core-extensions/src/moonbase/webpackModules/updates.tsx b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx index 72eefeb..c4042e4 100644 --- a/packages/core-extensions/src/moonbase/webpackModules/updates.tsx +++ b/packages/core-extensions/src/moonbase/webpackModules/updates.tsx @@ -1,7 +1,7 @@ import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; import { MoonbaseSettingsStore } from "@moonlight-mod/wp/moonbase_stores"; import Notices from "@moonlight-mod/wp/notices_notices"; -import { MoonlightBranch } from "types/src"; +import { MoonlightBranch } from "@moonlight-mod/types"; import React from "@moonlight-mod/wp/react"; import * as Components from "@moonlight-mod/wp/discord/components/common/index"; diff --git a/packages/node-preload/src/index.ts b/packages/node-preload/src/index.ts index 6f04d56..b9e1833 100644 --- a/packages/node-preload/src/index.ts +++ b/packages/node-preload/src/index.ts @@ -14,7 +14,7 @@ import { loadExtensions, loadProcessedExtensions } from "@moonlight-mod/core/extension/loader"; -import createFS from "core/src/fs"; +import createFS from "@moonlight-mod/core/fs"; async function injectGlobals() { global.moonlightFS = createFS();