From b77a467de57ddc81cd0092d62f4717a174ada41d Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 6 Dec 2023 10:54:59 -0500 Subject: [PATCH 1/3] Allow for ESM webpack modules --- build.mjs | 24 ++- env.d.ts | 1 - .../core-extensions/src/common/components.ts | 2 +- packages/core-extensions/src/common/flux.ts | 2 +- .../src/common/fluxDispatcher.ts | 2 +- packages/core-extensions/src/common/http.ts | 2 +- packages/core-extensions/src/common/react.ts | 2 +- .../core-extensions/src/moonbase/index.tsx | 2 +- .../core-extensions/src/moonbase/ui/index.tsx | 2 +- .../core-extensions/src/moonbase/ui/info.tsx | 2 +- .../core-extensions/src/spacepack/index.ts | 14 +- .../src/spacepack/webpackModule.ts | 176 ------------------ .../src/spacepack/webpackModules/spacepack.ts | 172 +++++++++++++++++ packages/core/src/extension.ts | 16 ++ packages/core/src/extension/loader.ts | 20 +- packages/types/src/discord/require.ts | 5 +- packages/types/src/extension.ts | 3 +- 17 files changed, 247 insertions(+), 200 deletions(-) delete mode 100644 env.d.ts delete mode 100644 packages/core-extensions/src/spacepack/webpackModule.ts create mode 100644 packages/core-extensions/src/spacepack/webpackModules/spacepack.ts diff --git a/build.mjs b/build.mjs index 21e498e..55d2859 100644 --- a/build.mjs +++ b/build.mjs @@ -73,16 +73,28 @@ async function build(name, entry) { } async function buildExt(ext, side, copyManifest, fileExt) { - const outDir = path.join("./dist", "core-extensions", ext); - if (!fs.existsSync(outDir)) { - fs.mkdirSync(outDir, { recursive: true }); + const outdir = path.join("./dist", "core-extensions", ext); + if (!fs.existsSync(outdir)) { + fs.mkdirSync(outdir, { recursive: true }); } - const entryPoint = `packages/core-extensions/src/${ext}/${side}.${fileExt}`; + const entryPoints = [ + `packages/core-extensions/src/${ext}/${side}.${fileExt}` + ]; + + const wpModulesDir = `packages/core-extensions/src/${ext}/webpackModules`; + if (fs.existsSync(wpModulesDir)) { + const wpModules = fs.readdirSync(wpModulesDir); + for (const wpModule of wpModules) { + entryPoints.push( + `packages/core-extensions/src/${ext}/webpackModules/${wpModule}` + ); + } + } const esbuildConfig = { - entryPoints: [entryPoint], - outfile: path.join(outDir, side + ".js"), + entryPoints, + outdir, format: "cjs", platform: "node", diff --git a/env.d.ts b/env.d.ts deleted file mode 100644 index df6d5da..0000000 --- a/env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/packages/core-extensions/src/common/components.ts b/packages/core-extensions/src/common/components.ts index e4f08b4..f3dc60d 100644 --- a/packages/core-extensions/src/common/components.ts +++ b/packages/core-extensions/src/common/components.ts @@ -9,7 +9,7 @@ export const components: ExtensionWebpackModule = { //".Messages.SWITCH_ACCOUNTS_TOAST_LOGIN_SUCCESS.format" ], run: function (module, exports, require) { - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; const Components = spacepack.findByCode("MasonryList:function")[0].exports; const MarkdownParser = spacepack.findByCode( diff --git a/packages/core-extensions/src/common/flux.ts b/packages/core-extensions/src/common/flux.ts index b1adf32..c05a970 100644 --- a/packages/core-extensions/src/common/flux.ts +++ b/packages/core-extensions/src/common/flux.ts @@ -6,7 +6,7 @@ const findFlux = ["useStateFromStores:function"]; export const flux: ExtensionWebpackModule = { dependencies: [{ ext: "spacepack", id: "spacepack" }, ...findFlux], run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; const Flux = spacepack.findByCode(...findFlux)[0].exports; module.exports = Flux as CommonFlux; } diff --git a/packages/core-extensions/src/common/fluxDispatcher.ts b/packages/core-extensions/src/common/fluxDispatcher.ts index d519eac..01800a3 100644 --- a/packages/core-extensions/src/common/fluxDispatcher.ts +++ b/packages/core-extensions/src/common/fluxDispatcher.ts @@ -7,7 +7,7 @@ export const fluxDispatcher: ExtensionWebpackModule = { "dispatch" ], run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; module.exports = spacepack.findByExports( "isDispatching", "dispatch" diff --git a/packages/core-extensions/src/common/http.ts b/packages/core-extensions/src/common/http.ts index 8722b6f..a1bca3a 100644 --- a/packages/core-extensions/src/common/http.ts +++ b/packages/core-extensions/src/common/http.ts @@ -5,7 +5,7 @@ const findHTTP = ["get", "put", "V8APIError"]; export const http: ExtensionWebpackModule = { dependencies: [{ ext: "spacepack", id: "spacepack" }], run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; const HTTP = spacepack.findByExports(...findHTTP)[0].exports; module.exports = HTTP.ZP ?? HTTP.Z; } diff --git a/packages/core-extensions/src/common/react.ts b/packages/core-extensions/src/common/react.ts index 71a91a3..16ba509 100644 --- a/packages/core-extensions/src/common/react.ts +++ b/packages/core-extensions/src/common/react.ts @@ -9,7 +9,7 @@ const findReact = [ export const react: ExtensionWebpackModule = { dependencies: [...findReact, { ext: "spacepack", id: "spacepack" }], run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; module.exports = spacepack.findByCode(...findReact)[0].exports; } }; diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 151882c..1a3fcac 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -28,7 +28,7 @@ export const webpackModules: ExtensionWebExports["webpackModules"] = { run: (module, exports, require) => { const settings = require("settings_settings"); const React = require("common_react"); - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType< (typeof import("./stores"))["stores"] diff --git a/packages/core-extensions/src/moonbase/ui/index.tsx b/packages/core-extensions/src/moonbase/ui/index.tsx index 83bc092..4e4acf3 100644 --- a/packages/core-extensions/src/moonbase/ui/index.tsx +++ b/packages/core-extensions/src/moonbase/ui/index.tsx @@ -12,7 +12,7 @@ export enum ExtensionPage { export default (require: typeof WebpackRequire) => { const React = require("common_react"); - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; const CommonComponents = require("common_components"); const Flux = require("common_flux"); diff --git a/packages/core-extensions/src/moonbase/ui/info.tsx b/packages/core-extensions/src/moonbase/ui/info.tsx index 1813964..2a03b11 100644 --- a/packages/core-extensions/src/moonbase/ui/info.tsx +++ b/packages/core-extensions/src/moonbase/ui/info.tsx @@ -15,7 +15,7 @@ enum DependencyType { export default (require: typeof WebpackRequire) => { const React = require("common_react"); - const spacepack = require("spacepack_spacepack"); + const spacepack = require("spacepack_spacepack").spacepack; const CommonComponents = require("common_components"); const UserInfoClasses = spacepack.findByCode( diff --git a/packages/core-extensions/src/spacepack/index.ts b/packages/core-extensions/src/spacepack/index.ts index c406cce..3fd3b96 100644 --- a/packages/core-extensions/src/spacepack/index.ts +++ b/packages/core-extensions/src/spacepack/index.ts @@ -1,10 +1,14 @@ -import { ExtensionWebExports, WebpackModuleFunc } from "@moonlight-mod/types"; -import webpackModule from "./webpackModule"; +import { ExtensionWebExports } from "@moonlight-mod/types"; +import { Spacepack } from "@moonlight-mod/types/coreExtensions"; + +declare global { + interface Window { + spacepack: Spacepack; + } +} export const webpackModules: ExtensionWebExports["webpackModules"] = { spacepack: { - entrypoint: true, - // Assert the type because we're adding extra fields to require - run: webpackModule as WebpackModuleFunc + entrypoint: true } }; diff --git a/packages/core-extensions/src/spacepack/webpackModule.ts b/packages/core-extensions/src/spacepack/webpackModule.ts deleted file mode 100644 index 50bef29..0000000 --- a/packages/core-extensions/src/spacepack/webpackModule.ts +++ /dev/null @@ -1,176 +0,0 @@ -import { WebpackModuleFunc, WebpackModule } from "@moonlight-mod/types"; -import { Spacepack } from "@moonlight-mod/types/coreExtensions"; -import { WebpackRequireType } from "@moonlight-mod/types/discord/webpack"; - -declare global { - interface Window { - spacepack: Spacepack; - } -} - -export default (module: any, exports: any, require: WebpackRequireType) => { - const cache = require.c; - const modules = require.m; - - const logger = moonlight.getLogger("spacepack"); - - const spacepack: Spacepack = { - require, - modules, - cache, - - inspect: (module: number | string) => { - if (typeof module === "number") { - module = module.toString(); - } - - if (!(module in modules)) { - return null; - } - - const func = modules[module]; - if (func.__moonlight === true) { - return func; - } - - const funcStr = func.toString(); - - return new Function( - "module", - "exports", - "require", - `(${funcStr}).apply(this, arguments)\n` + - `//# sourceURL=Webpack-Module-${module}` - ) as WebpackModuleFunc; - }, - - findByCode: (...args: (string | RegExp)[]) => { - return Object.entries(modules) - .filter( - ([id, mod]) => - !args.some( - (item) => - !(item instanceof RegExp - ? item.test(mod.toString()) - : mod.toString().indexOf(item) !== -1) - ) - ) - .map(([id]) => { - //if (!(id in cache)) require(id); - //return cache[id]; - - let exports; - try { - exports = require(id); - } catch (e) { - logger.error(`Error requiring module "${id}": `, e); - } - - return { - id, - exports - }; - }) - .filter((item) => item !== null); - }, - - findByExports: (...args: string[]) => { - return Object.entries(cache) - .filter( - ([id, { exports }]) => - !args.some( - (item) => - !( - exports !== undefined && - exports !== window && - (exports?.[item] || - exports?.default?.[item] || - exports?.Z?.[item] || - exports?.ZP?.[item]) - ) - ) - ) - .map((item) => item[1]) - .reduce((prev, curr) => { - if (!prev.includes(curr)) prev.push(curr); - return prev; - }, []); - }, - - findObjectFromKey: (exports: Record, key: string) => { - let subKey; - if (key.indexOf(".") > -1) { - const splitKey = key.split("."); - key = splitKey[0]; - subKey = splitKey[1]; - } - for (const exportKey in exports) { - const obj = exports[exportKey]; - if (obj && obj[key] !== undefined) { - if (subKey) { - if (obj[key][subKey]) return obj; - } else { - return obj; - } - } - } - return null; - }, - - findObjectFromValue: (exports: Record, value: any) => { - for (const exportKey in exports) { - const obj = exports[exportKey]; - // eslint-disable-next-line eqeqeq - if (obj == value) return obj; - for (const subKey in obj) { - // eslint-disable-next-line eqeqeq - if (obj && obj[subKey] == value) { - return obj; - } - } - } - return null; - }, - - findObjectFromKeyValuePair: ( - exports: Record, - key: string, - value: any - ) => { - for (const exportKey in exports) { - const obj = exports[exportKey]; - // eslint-disable-next-line eqeqeq - if (obj && obj[key] == value) { - return obj; - } - } - return null; - }, - - findFunctionByStrings: ( - exports: Record, - ...strings: (string | RegExp)[] - ) => { - return ( - Object.entries(exports).filter( - ([index, func]) => - typeof func === "function" && - !strings.some( - (query) => - !(query instanceof RegExp - ? func.toString().match(query) - : func.toString().includes(query)) - ) - )?.[0]?.[1] ?? null - ); - } - }; - - module.exports = spacepack; - - if ( - moonlight.getConfigOption("spacepack", "addToGlobalScope") === true - ) { - window.spacepack = spacepack; - } -}; diff --git a/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts b/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts new file mode 100644 index 0000000..a916963 --- /dev/null +++ b/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts @@ -0,0 +1,172 @@ +import { + WebpackModule, + WebpackModuleFunc, + WebpackRequireType +} from "@moonlight-mod/types"; +import { Spacepack } from "@moonlight-mod/types/coreExtensions"; + +const webpackRequire = require as unknown as WebpackRequireType; +const cache = webpackRequire.c; +const modules = webpackRequire.m; + +const logger = moonlight.getLogger("spacepack"); + +export const spacepack: Spacepack = { + require, + modules, + cache, + + inspect: (module: number | string) => { + if (typeof module === "number") { + module = module.toString(); + } + + if (!(module in modules)) { + return null; + } + + const func = modules[module]; + if (func.__moonlight === true) { + return func; + } + + const funcStr = func.toString(); + + return new Function( + "module", + "exports", + "require", + `(${funcStr}).apply(this, arguments)\n` + + `//# sourceURL=Webpack-Module-${module}` + ) as WebpackModuleFunc; + }, + + findByCode: (...args: (string | RegExp)[]) => { + return Object.entries(modules) + .filter( + ([id, mod]) => + !args.some( + (item) => + !(item instanceof RegExp + ? item.test(mod.toString()) + : mod.toString().indexOf(item) !== -1) + ) + ) + .map(([id]) => { + //if (!(id in cache)) require(id); + //return cache[id]; + + let exports; + try { + exports = require(id); + } catch (e) { + logger.error(`Error requiring module "${id}": `, e); + } + + return { + id, + exports + }; + }) + .filter((item) => item !== null); + }, + + findByExports: (...args: string[]) => { + return Object.entries(cache) + .filter( + ([id, { exports }]) => + !args.some( + (item) => + !( + exports !== undefined && + exports !== window && + (exports?.[item] || + exports?.default?.[item] || + exports?.Z?.[item] || + exports?.ZP?.[item]) + ) + ) + ) + .map((item) => item[1]) + .reduce((prev, curr) => { + if (!prev.includes(curr)) prev.push(curr); + return prev; + }, []); + }, + + findObjectFromKey: (exports: Record, key: string) => { + let subKey; + if (key.indexOf(".") > -1) { + const splitKey = key.split("."); + key = splitKey[0]; + subKey = splitKey[1]; + } + for (const exportKey in exports) { + const obj = exports[exportKey]; + if (obj && obj[key] !== undefined) { + if (subKey) { + if (obj[key][subKey]) return obj; + } else { + return obj; + } + } + } + return null; + }, + + findObjectFromValue: (exports: Record, value: any) => { + for (const exportKey in exports) { + const obj = exports[exportKey]; + // eslint-disable-next-line eqeqeq + if (obj == value) return obj; + for (const subKey in obj) { + // eslint-disable-next-line eqeqeq + if (obj && obj[subKey] == value) { + return obj; + } + } + } + return null; + }, + + findObjectFromKeyValuePair: ( + exports: Record, + key: string, + value: any + ) => { + for (const exportKey in exports) { + const obj = exports[exportKey]; + // eslint-disable-next-line eqeqeq + if (obj && obj[key] == value) { + return obj; + } + } + return null; + }, + + findFunctionByStrings: ( + exports: Record, + ...strings: (string | RegExp)[] + ) => { + return ( + Object.entries(exports).filter( + ([index, func]) => + typeof func === "function" && + !strings.some( + (query) => + !(query instanceof RegExp + ? func.toString().match(query) + : func.toString().includes(query)) + ) + )?.[0]?.[1] ?? null + ); + } +}; + +if ( + moonlight.getConfigOption("spacepack", "addToGlobalScope") === true +) { + window.spacepack = spacepack; +} + +export default spacepack; diff --git a/packages/core/src/extension.ts b/packages/core/src/extension.ts index 320f161..3a7553b 100644 --- a/packages/core/src/extension.ts +++ b/packages/core/src/extension.ts @@ -66,6 +66,21 @@ function loadDetectedExtensions( url = fs.readFileSync(urlPath, "utf8"); } + 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" + ); + } + } + } + ret.push({ id: manifest.id, manifest, @@ -76,6 +91,7 @@ function loadDetectedExtensions( scripts: { web, webPath: web != null ? webPath : undefined, + webpackModules: wpModules, nodePath: fs.existsSync(nodePath) ? nodePath : undefined, hostPath: fs.existsSync(hostPath) ? hostPath : undefined } diff --git a/packages/core/src/extension/loader.ts b/packages/core/src/extension/loader.ts index 495b500..8c41f36 100644 --- a/packages/core/src/extension/loader.ts +++ b/packages/core/src/extension/loader.ts @@ -1,7 +1,8 @@ import { ExtensionWebExports, DetectedExtension, - ProcessedExtensions + ProcessedExtensions, + WebpackModuleFunc } from "@moonlight-mod/types"; import { readConfig } from "../config"; import Logger from "../util/logger"; @@ -49,7 +50,22 @@ async function loadExt(ext: DetectedExtension) { if (exports.webpackModules != null) { for (const [name, wp] of Object.entries(exports.webpackModules)) { - registerWebpackModule({ ...wp, ext: ext.id, id: name }); + 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 }); + } } } } diff --git a/packages/types/src/discord/require.ts b/packages/types/src/discord/require.ts index 056dc96..a949353 100644 --- a/packages/types/src/discord/require.ts +++ b/packages/types/src/discord/require.ts @@ -8,7 +8,10 @@ import { } from "../coreExtensions"; declare function WebpackRequire(id: string): any; -declare function WebpackRequire(id: "spacepack_spacepack"): Spacepack; +declare function WebpackRequire(id: "spacepack_spacepack"): { + default: Spacepack; + spacepack: Spacepack; +}; declare function WebpackRequire(id: "common_react"): CommonReact; declare function WebpackRequire(id: "common_flux"): CommonFlux; declare function WebpackRequire( diff --git a/packages/types/src/extension.ts b/packages/types/src/extension.ts index 1c7560f..7fc1e95 100644 --- a/packages/types/src/extension.ts +++ b/packages/types/src/extension.ts @@ -62,6 +62,7 @@ export type DetectedExtension = { scripts: { web?: string; webPath?: string; + webpackModules?: Record; nodePath?: string; hostPath?: string; }; @@ -108,7 +109,7 @@ export type ExtensionDependency = string | RegExp | ExplicitExtensionDependency; export type ExtensionWebpackModule = { entrypoint?: boolean; dependencies?: ExtensionDependency[]; - run: WebpackModuleFunc; + run?: WebpackModuleFunc; }; export type ExtensionWebExports = { From c234e2cdb41b164c9b2b67ac7396f4807bc0c41f Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 6 Dec 2023 11:35:20 -0500 Subject: [PATCH 2/3] Begin work on typing imports in Webpack modules --- build.mjs | 22 ++++++++++- env.d.ts | 1 + .../core-extensions/src/common/components.ts | 39 ------------------- packages/core-extensions/src/common/index.ts | 10 ++++- .../src/common/webpackModules/components.ts | 26 +++++++++++++ .../src/spacepack/webpackModules/spacepack.ts | 2 +- packages/types/package.json | 1 + packages/types/src/coreExtensions.ts | 4 +- packages/types/src/import.d.ts | 5 +++ packages/types/src/index.ts | 1 + packages/types/tsconfig.json | 2 +- 11 files changed, 66 insertions(+), 47 deletions(-) create mode 100644 env.d.ts delete mode 100644 packages/core-extensions/src/common/components.ts create mode 100644 packages/core-extensions/src/common/webpackModules/components.ts create mode 100644 packages/types/src/import.d.ts diff --git a/build.mjs b/build.mjs index 55d2859..4434039 100644 --- a/build.mjs +++ b/build.mjs @@ -92,6 +92,19 @@ async function buildExt(ext, side, copyManifest, fileExt) { } } + const wpImportPlugin = { + name: "webpackImports", + setup(build) { + build.onResolve({ filter: /^@moonlight-mod\/wp\// }, (args) => { + const wpModule = args.path.replace(/^@moonlight-mod\/wp\//, ""); + return { + path: wpModule, + external: true + }; + }); + } + }; + const esbuildConfig = { entryPoints, outdir, @@ -110,9 +123,14 @@ async function buildExt(ext, side, copyManifest, fileExt) { copyStaticFiles({ src: `./packages/core-extensions/src/${ext}/manifest.json`, dest: `./dist/core-extensions/${ext}/manifest.json` - }) + }), + wpImportPlugin ] - : [] + : [wpImportPlugin], + + logOverride: { + "commonjs-variable-in-esm": "verbose" + } }; if (watch) { diff --git a/env.d.ts b/env.d.ts new file mode 100644 index 0000000..55c16fb --- /dev/null +++ b/env.d.ts @@ -0,0 +1 @@ +/// diff --git a/packages/core-extensions/src/common/components.ts b/packages/core-extensions/src/common/components.ts deleted file mode 100644 index f3dc60d..0000000 --- a/packages/core-extensions/src/common/components.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { ExtensionWebpackModule } from "@moonlight-mod/types"; - -export const components: ExtensionWebpackModule = { - dependencies: [ - { ext: "spacepack", id: "spacepack" }, - "MasonryList:", - ".flexGutterSmall," - //"ALWAYS_WHITE:", - //".Messages.SWITCH_ACCOUNTS_TOAST_LOGIN_SUCCESS.format" - ], - run: function (module, exports, require) { - const spacepack = require("spacepack_spacepack").spacepack; - - const Components = spacepack.findByCode("MasonryList:function")[0].exports; - const MarkdownParser = spacepack.findByCode( - "parseAutoModerationSystemMessage:" - )[0].exports.default; - const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0] - .exports.default; - const Flex = spacepack.findByCode(".flex" + "GutterSmall,")[0].exports - .default; - const CardClasses = spacepack.findByCode("card", "cardHeader", "inModal")[0] - .exports; - const ControlClasses = spacepack.findByCode( - "title", - "titleDefault", - "dividerDefault" - )[0].exports; - - module.exports = { - ...Components, - MarkdownParser, - LegacyText, - Flex, - CardClasses, - ControlClasses - }; - } -}; diff --git a/packages/core-extensions/src/common/index.ts b/packages/core-extensions/src/common/index.ts index 2aefe58..c88250a 100644 --- a/packages/core-extensions/src/common/index.ts +++ b/packages/core-extensions/src/common/index.ts @@ -4,14 +4,20 @@ import { react } from "./react"; import { flux } from "./flux"; import { stores } from "./stores"; import { http } from "./http"; -import { components } from "./components"; import { fluxDispatcher } from "./fluxDispatcher"; export const webpackModules: ExtensionWebExports["webpackModules"] = { + components: { + dependencies: [ + { ext: "spacepack", id: "spacepack" }, + "MasonryList:", + ".flexGutterSmall," + ] + }, + react, flux, stores, http, - components, fluxDispatcher }; diff --git a/packages/core-extensions/src/common/webpackModules/components.ts b/packages/core-extensions/src/common/webpackModules/components.ts new file mode 100644 index 0000000..e8a0a55 --- /dev/null +++ b/packages/core-extensions/src/common/webpackModules/components.ts @@ -0,0 +1,26 @@ +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; + +const Components = spacepack.findByCode("MasonryList:function")[0].exports; +const MarkdownParser = spacepack.findByCode( + "parseAutoModerationSystemMessage:" +)[0].exports.default; +const LegacyText = spacepack.findByCode(".selectable", ".colorStandard")[0] + .exports.default; +const Flex = spacepack.findByCode(".flex" + "GutterSmall,")[0].exports.default; +const CardClasses = spacepack.findByCode("card", "cardHeader", "inModal")[0] + .exports; +const ControlClasses = spacepack.findByCode( + "title", + "titleDefault", + "dividerDefault" +)[0].exports; + +// We use CJS export here because merging the exports from Components is annoying as shit +module.exports = { + ...Components, + MarkdownParser, + LegacyText, + Flex, + CardClasses, + ControlClasses +}; diff --git a/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts b/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts index a916963..f6369cd 100644 --- a/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts +++ b/packages/core-extensions/src/spacepack/webpackModules/spacepack.ts @@ -12,7 +12,7 @@ const modules = webpackRequire.m; const logger = moonlight.getLogger("spacepack"); export const spacepack: Spacepack = { - require, + require: webpackRequire, modules, cache, diff --git a/packages/types/package.json b/packages/types/package.json index 30d164f..7ee5fd2 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -5,6 +5,7 @@ "types": "./src/index.ts", "exports": { ".": "./src/index.ts", + "./import": "./src/import.d.ts", "./*": "./src/*.ts" }, "dependencies": { diff --git a/packages/types/src/coreExtensions.ts b/packages/types/src/coreExtensions.ts index 8c65de2..8ac4c47 100644 --- a/packages/types/src/coreExtensions.ts +++ b/packages/types/src/coreExtensions.ts @@ -1,9 +1,9 @@ import { FluxDefault, Store } from "./discord/common/Flux"; -import WebpackRequire from "./discord/require"; -import { WebpackModuleFunc } from "./discord/webpack"; import { CommonComponents as CommonComponents_ } from "./coreExtensions/components"; import { Dispatcher } from "flux"; import React from "react"; +import { WebpackModuleFunc } from "./discord"; +import WebpackRequire from "./discord/require"; export type Spacepack = { inspect: (module: number | string) => WebpackModuleFunc | null; diff --git a/packages/types/src/import.d.ts b/packages/types/src/import.d.ts new file mode 100644 index 0000000..daf0b7d --- /dev/null +++ b/packages/types/src/import.d.ts @@ -0,0 +1,5 @@ +declare module "@moonlight-mod/wp/spacepack_spacepack" { + import { Spacepack } from "@moonlight-mod/types/coreExtensions"; + export const spacepack: Spacepack; + export default spacepack; +} diff --git a/packages/types/src/index.ts b/packages/types/src/index.ts index 15477be..ba2f7d7 100644 --- a/packages/types/src/index.ts +++ b/packages/types/src/index.ts @@ -2,6 +2,7 @@ /// /// /// +/// /* eslint-disable no-var */ import { diff --git a/packages/types/tsconfig.json b/packages/types/tsconfig.json index 80ec4b5..40dd733 100644 --- a/packages/types/tsconfig.json +++ b/packages/types/tsconfig.json @@ -10,5 +10,5 @@ "jsx": "react", "declaration": true }, - "include": ["./src/**/*", "src/index.ts"] + "include": ["./src/**/*", "src/index.ts", "./src/import.d.ts"] } From d2506d767b24cc4b2b14811660f1b39685a5f71b Mon Sep 17 00:00:00 2001 From: NotNite Date: Wed, 6 Dec 2023 12:03:08 -0500 Subject: [PATCH 3/3] Convert mostly everything to new webpack modules structure --- packages/core-extensions/src/common/flux.ts | 13 - .../src/common/fluxDispatcher.ts | 16 -- packages/core-extensions/src/common/http.ts | 12 - packages/core-extensions/src/common/index.ts | 38 ++- packages/core-extensions/src/common/react.ts | 15 - packages/core-extensions/src/common/stores.ts | 30 -- .../src/common/webpackModules/flux.ts | 5 + .../common/webpackModules/fluxDispatcher.ts | 6 + .../src/common/webpackModules/react.ts | 7 + .../src/common/webpackModules/stores.ts | 23 ++ .../src/disableSentry/index.ts | 41 +-- .../src/disableSentry/webpackModules/stub.ts | 33 +++ .../core-extensions/src/moonbase/index.tsx | 12 +- .../core-extensions/src/moonbase/stores.ts | 257 ------------------ .../core-extensions/src/moonbase/ui/index.tsx | 5 +- .../core-extensions/src/moonbase/ui/info.tsx | 5 +- .../src/moonbase/ui/settings.tsx | 5 +- .../src/moonbase/webpackModules/stores.ts | 251 +++++++++++++++++ .../core-extensions/src/settings/index.ts | 62 +---- .../src/settings/webpackModules/settings.ts | 53 ++++ packages/types/src/discord/require.ts | 11 +- packages/types/src/import.d.ts | 18 ++ 22 files changed, 447 insertions(+), 471 deletions(-) delete mode 100644 packages/core-extensions/src/common/flux.ts delete mode 100644 packages/core-extensions/src/common/fluxDispatcher.ts delete mode 100644 packages/core-extensions/src/common/http.ts delete mode 100644 packages/core-extensions/src/common/react.ts delete mode 100644 packages/core-extensions/src/common/stores.ts create mode 100644 packages/core-extensions/src/common/webpackModules/flux.ts create mode 100644 packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts create mode 100644 packages/core-extensions/src/common/webpackModules/react.ts create mode 100644 packages/core-extensions/src/common/webpackModules/stores.ts create mode 100644 packages/core-extensions/src/disableSentry/webpackModules/stub.ts delete mode 100644 packages/core-extensions/src/moonbase/stores.ts create mode 100644 packages/core-extensions/src/moonbase/webpackModules/stores.ts create mode 100644 packages/core-extensions/src/settings/webpackModules/settings.ts diff --git a/packages/core-extensions/src/common/flux.ts b/packages/core-extensions/src/common/flux.ts deleted file mode 100644 index c05a970..0000000 --- a/packages/core-extensions/src/common/flux.ts +++ /dev/null @@ -1,13 +0,0 @@ -import { ExtensionWebpackModule } from "@moonlight-mod/types"; -import { CommonFlux } from "@moonlight-mod/types/coreExtensions"; - -const findFlux = ["useStateFromStores:function"]; - -export const flux: ExtensionWebpackModule = { - dependencies: [{ ext: "spacepack", id: "spacepack" }, ...findFlux], - run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack").spacepack; - const Flux = spacepack.findByCode(...findFlux)[0].exports; - module.exports = Flux as CommonFlux; - } -}; diff --git a/packages/core-extensions/src/common/fluxDispatcher.ts b/packages/core-extensions/src/common/fluxDispatcher.ts deleted file mode 100644 index 01800a3..0000000 --- a/packages/core-extensions/src/common/fluxDispatcher.ts +++ /dev/null @@ -1,16 +0,0 @@ -import { ExtensionWebpackModule } from "@moonlight-mod/types"; - -export const fluxDispatcher: ExtensionWebpackModule = { - dependencies: [ - { ext: "spacepack", id: "spacepack" }, - "isDispatching", - "dispatch" - ], - run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack").spacepack; - module.exports = spacepack.findByExports( - "isDispatching", - "dispatch" - )[0].exports.default; - } -}; diff --git a/packages/core-extensions/src/common/http.ts b/packages/core-extensions/src/common/http.ts deleted file mode 100644 index a1bca3a..0000000 --- a/packages/core-extensions/src/common/http.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ExtensionWebpackModule } from "@moonlight-mod/types"; - -const findHTTP = ["get", "put", "V8APIError"]; - -export const http: ExtensionWebpackModule = { - dependencies: [{ ext: "spacepack", id: "spacepack" }], - run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack").spacepack; - const HTTP = spacepack.findByExports(...findHTTP)[0].exports; - module.exports = HTTP.ZP ?? HTTP.Z; - } -}; diff --git a/packages/core-extensions/src/common/index.ts b/packages/core-extensions/src/common/index.ts index c88250a..95209d6 100644 --- a/packages/core-extensions/src/common/index.ts +++ b/packages/core-extensions/src/common/index.ts @@ -1,11 +1,5 @@ import { ExtensionWebExports } from "@moonlight-mod/types"; -import { react } from "./react"; -import { flux } from "./flux"; -import { stores } from "./stores"; -import { http } from "./http"; -import { fluxDispatcher } from "./fluxDispatcher"; - export const webpackModules: ExtensionWebExports["webpackModules"] = { components: { dependencies: [ @@ -15,9 +9,31 @@ export const webpackModules: ExtensionWebExports["webpackModules"] = { ] }, - react, - flux, - stores, - http, - fluxDispatcher + flux: { + dependencies: [ + { ext: "spacepack", id: "spacepack" }, + "useStateFromStores:function" + ] + }, + + fluxDispatcher: { + dependencies: [ + { ext: "spacepack", id: "spacepack" }, + "isDispatching", + "dispatch" + ] + }, + + react: { + dependencies: [ + { ext: "spacepack", id: "spacepack" }, + "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", + /\.?version(?:=|:)/, + /\.?createElement(?:=|:)/ + ] + }, + + stores: { + dependencies: [{ ext: "common", id: "flux" }] + } }; diff --git a/packages/core-extensions/src/common/react.ts b/packages/core-extensions/src/common/react.ts deleted file mode 100644 index 16ba509..0000000 --- a/packages/core-extensions/src/common/react.ts +++ /dev/null @@ -1,15 +0,0 @@ -import { ExtensionWebpackModule } from "@moonlight-mod/types"; - -const findReact = [ - "__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED", - /\.?version(?:=|:)/, - /\.?createElement(?:=|:)/ -]; - -export const react: ExtensionWebpackModule = { - dependencies: [...findReact, { ext: "spacepack", id: "spacepack" }], - run: (module, exports, require) => { - const spacepack = require("spacepack_spacepack").spacepack; - module.exports = spacepack.findByCode(...findReact)[0].exports; - } -}; diff --git a/packages/core-extensions/src/common/stores.ts b/packages/core-extensions/src/common/stores.ts deleted file mode 100644 index 1647502..0000000 --- a/packages/core-extensions/src/common/stores.ts +++ /dev/null @@ -1,30 +0,0 @@ -import { ExtensionWebpackModule } from "@moonlight-mod/types"; - -export const stores: ExtensionWebpackModule = { - dependencies: [{ ext: "common", id: "flux" }], - run: (module, exports, require) => { - const Flux = require("common_flux"); - - module.exports = new Proxy( - {}, - { - get: function (target, key, receiver) { - const allStores = Flux.Store.getAll(); - - let targetStore; - for (const store of allStores) { - const name = store.getName(); - if (name.length === 1) continue; // filter out unnamed stores - - if (name === key) { - targetStore = store; - break; - } - } - - return targetStore; - } - } - ); - } -}; diff --git a/packages/core-extensions/src/common/webpackModules/flux.ts b/packages/core-extensions/src/common/webpackModules/flux.ts new file mode 100644 index 0000000..1e21d25 --- /dev/null +++ b/packages/core-extensions/src/common/webpackModules/flux.ts @@ -0,0 +1,5 @@ +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; + +module.exports = spacepack.findByCode( + ["useStateFromStores", ":function"].join("") +)[0].exports; diff --git a/packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts b/packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts new file mode 100644 index 0000000..964f7eb --- /dev/null +++ b/packages/core-extensions/src/common/webpackModules/fluxDispatcher.ts @@ -0,0 +1,6 @@ +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; + +module.exports = spacepack.findByExports( + "isDispatching", + "dispatch" +)[0].exports.default; diff --git a/packages/core-extensions/src/common/webpackModules/react.ts b/packages/core-extensions/src/common/webpackModules/react.ts new file mode 100644 index 0000000..a89715b --- /dev/null +++ b/packages/core-extensions/src/common/webpackModules/react.ts @@ -0,0 +1,7 @@ +import spacepack from "@moonlight-mod/wp/spacepack_spacepack"; + +module.exports = spacepack.findByCode( + "__SECRET_INTERNALS_DO_NOT_USE" + "_OR_YOU_WILL_BE_FIRED", + /\.?version(?:=|:)/, + /\.?createElement(?:=|:)/ +)[0].exports; diff --git a/packages/core-extensions/src/common/webpackModules/stores.ts b/packages/core-extensions/src/common/webpackModules/stores.ts new file mode 100644 index 0000000..36bf7b9 --- /dev/null +++ b/packages/core-extensions/src/common/webpackModules/stores.ts @@ -0,0 +1,23 @@ +import Flux from "@moonlight-mod/wp/common_flux"; + +module.exports = new Proxy( + {}, + { + get: function (target, key, receiver) { + const allStores = Flux.Store.getAll(); + + let targetStore; + for (const store of allStores) { + const name = store.getName(); + if (name.length === 1) continue; // filter out unnamed stores + + if (name === key) { + targetStore = store; + break; + } + } + + return targetStore; + } + } +); diff --git a/packages/core-extensions/src/disableSentry/index.ts b/packages/core-extensions/src/disableSentry/index.ts index 6d43135..9d90988 100644 --- a/packages/core-extensions/src/disableSentry/index.ts +++ b/packages/core-extensions/src/disableSentry/index.ts @@ -7,7 +7,8 @@ export const patches: Patch[] = [ replace: { type: PatchReplaceType.Normal, match: /default:function\(\){return .}/, - replacement: 'default:function(){return require("disableSentry_stub")()}' + replacement: + 'default:function(){return require("disableSentry_stub").proxy()}' } }, { @@ -38,41 +39,5 @@ export const patches: Patch[] = [ ]; export const webpackModules: ExtensionWebExports["webpackModules"] = { - stub: { - run: function (module, exports, require) { - const logger = moonlight.getLogger("disableSentry"); - - const keys = [ - "setUser", - "clearUser", - "setTags", - "setExtra", - "captureException", - "captureCrash", - "captureMessage", - "addBreadcrumb" - ]; - - module.exports = () => - new Proxy( - {}, - { - get(target, prop, receiver) { - if (prop === "profiledRootComponent") { - return (arg: any) => arg; - } else if (prop === "crash") { - return () => { - throw Error("crash"); - }; - } else if (keys.includes(prop.toString())) { - return (...args: any[]) => - logger.debug(`Sentry calling "${prop.toString()}":`, ...args); - } else { - return undefined; - } - } - } - ); - } - } + stub: {} }; diff --git a/packages/core-extensions/src/disableSentry/webpackModules/stub.ts b/packages/core-extensions/src/disableSentry/webpackModules/stub.ts new file mode 100644 index 0000000..f766777 --- /dev/null +++ b/packages/core-extensions/src/disableSentry/webpackModules/stub.ts @@ -0,0 +1,33 @@ +const logger = moonlight.getLogger("disableSentry"); + +const keys = [ + "setUser", + "clearUser", + "setTags", + "setExtra", + "captureException", + "captureCrash", + "captureMessage", + "addBreadcrumb" +]; + +export const proxy = () => + new Proxy( + {}, + { + get(target, prop, receiver) { + if (prop === "profiledRootComponent") { + return (arg: any) => arg; + } else if (prop === "crash") { + return () => { + throw Error("crash"); + }; + } else if (keys.includes(prop.toString())) { + return (...args: any[]) => + logger.debug(`Sentry calling "${prop.toString()}":`, ...args); + } else { + return undefined; + } + } + } + ); diff --git a/packages/core-extensions/src/moonbase/index.tsx b/packages/core-extensions/src/moonbase/index.tsx index 1a3fcac..a00ad26 100644 --- a/packages/core-extensions/src/moonbase/index.tsx +++ b/packages/core-extensions/src/moonbase/index.tsx @@ -1,6 +1,5 @@ import { ExtensionWebExports } from "@moonlight-mod/types"; import ui from "./ui"; -import { stores } from "./stores"; import { DownloadIconSVG, TrashIconSVG } from "./types"; export const webpackModules: ExtensionWebExports["webpackModules"] = { @@ -8,10 +7,7 @@ export const webpackModules: ExtensionWebExports["webpackModules"] = { dependencies: [ { ext: "common", id: "flux" }, { ext: "common", id: "fluxDispatcher" } - ], - run: (module, exports, require) => { - module.exports = stores(require); - } + ] }, moonbase: { @@ -26,13 +22,11 @@ export const webpackModules: ExtensionWebExports["webpackModules"] = { ], entrypoint: true, run: (module, exports, require) => { - const settings = require("settings_settings"); + const settings = require("settings_settings").Settings; const React = require("common_react"); const spacepack = require("spacepack_spacepack").spacepack; const { MoonbaseSettingsStore } = - require("moonbase_stores") as ReturnType< - (typeof import("./stores"))["stores"] - >; + require("moonbase_stores") as typeof import("./webpackModules/stores"); settings.addSection("Moonbase", "Moonbase", ui(require), null, -2, { stores: [MoonbaseSettingsStore], diff --git a/packages/core-extensions/src/moonbase/stores.ts b/packages/core-extensions/src/moonbase/stores.ts deleted file mode 100644 index aa7c95b..0000000 --- a/packages/core-extensions/src/moonbase/stores.ts +++ /dev/null @@ -1,257 +0,0 @@ -import WebpackRequire from "@moonlight-mod/types/discord/require"; -import { Config, ExtensionLoadSource } from "@moonlight-mod/types"; -import { - ExtensionState, - MoonbaseExtension, - MoonbaseNatives, - RepositoryManifest -} from "./types"; - -export const stores = (require: typeof WebpackRequire) => { - const Flux = require("common_flux"); - const Dispatcher = require("common_fluxDispatcher"); - const natives: MoonbaseNatives = moonlight.getNatives("moonbase"); - - const logger = moonlight.getLogger("moonbase"); - - class MoonbaseSettingsStore extends Flux.Store { - private origConfig: Config; - private config: Config; - - modified: boolean; - submitting: boolean; - installing: boolean; - - extensions: { [id: string]: MoonbaseExtension }; - updates: { [id: string]: { version: string; download: string } }; - - constructor() { - super(Dispatcher); - - // Fucking Electron making it immutable - this.origConfig = moonlightNode.config; - this.config = JSON.parse(JSON.stringify(this.origConfig)); - - this.modified = false; - this.submitting = false; - this.installing = false; - - this.extensions = {}; - this.updates = {}; - for (const ext of moonlightNode.extensions) { - const existingExtension = this.extensions[ext.id]; - if (existingExtension != null) continue; - - this.extensions[ext.id] = { - ...ext, - state: moonlight.enabledExtensions.has(ext.id) - ? ExtensionState.Enabled - : ExtensionState.Disabled - }; - } - - natives.fetchRepositories(this.config.repositories).then((ret) => { - for (const [repo, exts] of Object.entries(ret)) { - try { - for (const ext of exts) { - try { - const existingExtension = this.extensions[ext.id]; - if (existingExtension !== null) { - if (this.hasUpdate(repo, ext, existingExtension)) { - this.updates[ext.id] = { - version: ext.version!, - download: ext.download - }; - } - continue; - } - - this.extensions[ext.id] = { - id: ext.id, - manifest: ext, - source: { type: ExtensionLoadSource.Normal, url: repo }, - state: ExtensionState.NotDownloaded - }; - } catch (e) { - logger.error(`Error processing extension ${ext.id}`, e); - } - } - } catch (e) { - logger.error(`Error processing repository ${repo}`, e); - } - } - - this.emitChange(); - }); - } - - // this logic sucks so bad lol - private hasUpdate( - repo: string, - repoExt: RepositoryManifest, - existing: MoonbaseExtension - ) { - return ( - existing.source.type === ExtensionLoadSource.Normal && - existing.source.url != null && - existing.source.url === repo && - repoExt.version != null && - existing.manifest.version !== repoExt.version - ); - } - - // Jank - private isModified() { - const orig = JSON.stringify(this.origConfig); - const curr = JSON.stringify(this.config); - return orig !== curr; - } - - get busy() { - return this.submitting || this.installing; - } - - showNotice() { - return this.modified; - } - - getExtension(id: string) { - return this.extensions[id]; - } - - getExtensionName(id: string) { - return Object.prototype.hasOwnProperty.call(this.extensions, id) - ? this.extensions[id].manifest.meta?.name ?? id - : id; - } - - getExtensionUpdate(id: string) { - return Object.prototype.hasOwnProperty.call(this.updates, id) - ? this.updates[id] - : null; - } - - getExtensionEnabled(id: string) { - const val = this.config.extensions[id]; - if (val == null) return false; - return typeof val === "boolean" ? val : val.enabled; - } - - getExtensionConfig(id: string, key: string): T | undefined { - const defaultValue = - this.extensions[id].manifest.settings?.[key]?.default; - const cfg = this.config.extensions[id]; - - if (cfg == null || typeof cfg === "boolean") return defaultValue; - return cfg.config?.[key] ?? defaultValue; - } - - getExtensionConfigName(id: string, key: string) { - return this.extensions[id].manifest.settings?.[key]?.displayName ?? key; - } - - setExtensionConfig(id: string, key: string, value: any) { - const oldConfig = this.config.extensions[id]; - const newConfig = - typeof oldConfig === "boolean" - ? { - enabled: oldConfig, - config: { [key]: value } - } - : { - ...oldConfig, - config: { ...(oldConfig?.config ?? {}), [key]: value } - }; - - this.config.extensions[id] = newConfig; - this.modified = this.isModified(); - this.emitChange(); - } - - setExtensionEnabled(id: string, enabled: boolean) { - let val = this.config.extensions[id]; - - if (val == null) { - this.config.extensions[id] = { enabled }; - this.emitChange(); - return; - } - - if (typeof val === "boolean") { - val = enabled; - } else { - val.enabled = enabled; - } - - this.config.extensions[id] = val; - this.modified = this.isModified(); - this.emitChange(); - } - - async installExtension(id: string) { - const ext = this.getExtension(id); - if (!("download" in ext.manifest)) { - throw new Error("Extension has no download URL"); - } - - this.installing = true; - try { - const url = this.updates[id]?.download ?? ext.manifest.download; - await natives.installExtension(ext.manifest, url, ext.source.url!); - if (ext.state === ExtensionState.NotDownloaded) { - this.extensions[id].state = ExtensionState.Disabled; - } - - delete this.updates[id]; - } catch (e) { - logger.error("Error installing extension:", e); - } - - this.installing = false; - this.emitChange(); - } - - async deleteExtension(id: string) { - const ext = this.getExtension(id); - if (ext == null) return; - - this.installing = true; - try { - await natives.deleteExtension(ext.id); - this.extensions[id].state = ExtensionState.NotDownloaded; - } catch (e) { - logger.error("Error deleting extension:", e); - } - - this.installing = false; - this.emitChange(); - } - - writeConfig() { - this.submitting = true; - - try { - moonlightNode.writeConfig(this.config); - // I love jank cloning - this.origConfig = JSON.parse(JSON.stringify(this.config)); - } catch (e) { - logger.error("Error writing config", e); - } - - this.submitting = false; - this.modified = false; - this.emitChange(); - } - - reset() { - this.submitting = false; - this.modified = false; - this.config = JSON.parse(JSON.stringify(this.origConfig)); - this.emitChange(); - } - } - - return { - MoonbaseSettingsStore: new MoonbaseSettingsStore() - }; -}; diff --git a/packages/core-extensions/src/moonbase/ui/index.tsx b/packages/core-extensions/src/moonbase/ui/index.tsx index 4e4acf3..b12f7cc 100644 --- a/packages/core-extensions/src/moonbase/ui/index.tsx +++ b/packages/core-extensions/src/moonbase/ui/index.tsx @@ -18,9 +18,8 @@ export default (require: typeof WebpackRequire) => { const { ExtensionInfo } = info(require); const { Settings } = settings(require); - const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType< - (typeof import("../stores"))["stores"] - >; + const { MoonbaseSettingsStore } = + require("moonbase_stores") as typeof import("../webpackModules/stores"); const UserProfileClasses = spacepack.findByCode( "tabBarContainer", diff --git a/packages/core-extensions/src/moonbase/ui/info.tsx b/packages/core-extensions/src/moonbase/ui/info.tsx index 2a03b11..dfd5a2c 100644 --- a/packages/core-extensions/src/moonbase/ui/info.tsx +++ b/packages/core-extensions/src/moonbase/ui/info.tsx @@ -24,9 +24,8 @@ export default (require: typeof WebpackRequire) => { "userInfoSectionHeader" )[0].exports; - const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType< - (typeof import("../stores"))["stores"] - >; + const { MoonbaseSettingsStore } = + require("moonbase_stores") as typeof import("../webpackModules/stores"); function InfoSection({ title, diff --git a/packages/core-extensions/src/moonbase/ui/settings.tsx b/packages/core-extensions/src/moonbase/ui/settings.tsx index 5dba53c..c624c99 100644 --- a/packages/core-extensions/src/moonbase/ui/settings.tsx +++ b/packages/core-extensions/src/moonbase/ui/settings.tsx @@ -20,9 +20,8 @@ export default (require: typeof WebpackRequire) => { const CommonComponents = require("common_components"); const Flux = require("common_flux"); - const { MoonbaseSettingsStore } = require("moonbase_stores") as ReturnType< - (typeof import("../stores"))["stores"] - >; + const { MoonbaseSettingsStore } = + require("moonbase_stores") as typeof import("../webpackModules/stores"); function Boolean({ ext, name, setting }: SettingsProps) { const { FormSwitch } = CommonComponents; diff --git a/packages/core-extensions/src/moonbase/webpackModules/stores.ts b/packages/core-extensions/src/moonbase/webpackModules/stores.ts new file mode 100644 index 0000000..b1c729a --- /dev/null +++ b/packages/core-extensions/src/moonbase/webpackModules/stores.ts @@ -0,0 +1,251 @@ +import { Config, ExtensionLoadSource } from "@moonlight-mod/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"); + +class MoonbaseSettingsStore extends Flux.Store { + private origConfig: Config; + private config: Config; + + modified: boolean; + submitting: boolean; + installing: boolean; + + extensions: { [id: string]: MoonbaseExtension }; + updates: { [id: string]: { version: string; download: string } }; + + constructor() { + super(Dispatcher); + + // Fucking Electron making it immutable + this.origConfig = moonlightNode.config; + this.config = JSON.parse(JSON.stringify(this.origConfig)); + + this.modified = false; + this.submitting = false; + this.installing = false; + + this.extensions = {}; + this.updates = {}; + for (const ext of moonlightNode.extensions) { + const existingExtension = this.extensions[ext.id]; + if (existingExtension != null) continue; + + this.extensions[ext.id] = { + ...ext, + state: moonlight.enabledExtensions.has(ext.id) + ? ExtensionState.Enabled + : ExtensionState.Disabled + }; + } + + natives.fetchRepositories(this.config.repositories).then((ret) => { + for (const [repo, exts] of Object.entries(ret)) { + try { + for (const ext of exts) { + try { + const existingExtension = this.extensions[ext.id]; + if (existingExtension !== null) { + if (this.hasUpdate(repo, ext, existingExtension)) { + this.updates[ext.id] = { + version: ext.version!, + download: ext.download + }; + } + continue; + } + + this.extensions[ext.id] = { + id: ext.id, + manifest: ext, + source: { type: ExtensionLoadSource.Normal, url: repo }, + state: ExtensionState.NotDownloaded + }; + } catch (e) { + logger.error(`Error processing extension ${ext.id}`, e); + } + } + } catch (e) { + logger.error(`Error processing repository ${repo}`, e); + } + } + + this.emitChange(); + }); + } + + // this logic sucks so bad lol + private hasUpdate( + repo: string, + repoExt: RepositoryManifest, + existing: MoonbaseExtension + ) { + return ( + existing.source.type === ExtensionLoadSource.Normal && + existing.source.url != null && + existing.source.url === repo && + repoExt.version != null && + existing.manifest.version !== repoExt.version + ); + } + + // Jank + private isModified() { + const orig = JSON.stringify(this.origConfig); + const curr = JSON.stringify(this.config); + return orig !== curr; + } + + get busy() { + return this.submitting || this.installing; + } + + showNotice() { + return this.modified; + } + + getExtension(id: string) { + return this.extensions[id]; + } + + getExtensionName(id: string) { + return Object.prototype.hasOwnProperty.call(this.extensions, id) + ? this.extensions[id].manifest.meta?.name ?? id + : id; + } + + getExtensionUpdate(id: string) { + return Object.prototype.hasOwnProperty.call(this.updates, id) + ? this.updates[id] + : null; + } + + getExtensionEnabled(id: string) { + const val = this.config.extensions[id]; + if (val == null) return false; + return typeof val === "boolean" ? val : val.enabled; + } + + getExtensionConfig(id: string, key: string): T | undefined { + const defaultValue = this.extensions[id].manifest.settings?.[key]?.default; + const cfg = this.config.extensions[id]; + + if (cfg == null || typeof cfg === "boolean") return defaultValue; + return cfg.config?.[key] ?? defaultValue; + } + + getExtensionConfigName(id: string, key: string) { + return this.extensions[id].manifest.settings?.[key]?.displayName ?? key; + } + + setExtensionConfig(id: string, key: string, value: any) { + const oldConfig = this.config.extensions[id]; + const newConfig = + typeof oldConfig === "boolean" + ? { + enabled: oldConfig, + config: { [key]: value } + } + : { + ...oldConfig, + config: { ...(oldConfig?.config ?? {}), [key]: value } + }; + + this.config.extensions[id] = newConfig; + this.modified = this.isModified(); + this.emitChange(); + } + + setExtensionEnabled(id: string, enabled: boolean) { + let val = this.config.extensions[id]; + + if (val == null) { + this.config.extensions[id] = { enabled }; + this.emitChange(); + return; + } + + if (typeof val === "boolean") { + val = enabled; + } else { + val.enabled = enabled; + } + + this.config.extensions[id] = val; + this.modified = this.isModified(); + this.emitChange(); + } + + async installExtension(id: string) { + const ext = this.getExtension(id); + if (!("download" in ext.manifest)) { + throw new Error("Extension has no download URL"); + } + + this.installing = true; + try { + const url = this.updates[id]?.download ?? ext.manifest.download; + await natives.installExtension(ext.manifest, url, ext.source.url!); + if (ext.state === ExtensionState.NotDownloaded) { + this.extensions[id].state = ExtensionState.Disabled; + } + + delete this.updates[id]; + } catch (e) { + logger.error("Error installing extension:", e); + } + + this.installing = false; + this.emitChange(); + } + + async deleteExtension(id: string) { + const ext = this.getExtension(id); + if (ext == null) return; + + this.installing = true; + try { + await natives.deleteExtension(ext.id); + this.extensions[id].state = ExtensionState.NotDownloaded; + } catch (e) { + logger.error("Error deleting extension:", e); + } + + this.installing = false; + this.emitChange(); + } + + writeConfig() { + this.submitting = true; + + try { + moonlightNode.writeConfig(this.config); + // I love jank cloning + this.origConfig = JSON.parse(JSON.stringify(this.config)); + } catch (e) { + logger.error("Error writing config", e); + } + + this.submitting = false; + this.modified = false; + this.emitChange(); + } + + reset() { + this.submitting = false; + this.modified = false; + this.config = JSON.parse(JSON.stringify(this.origConfig)); + this.emitChange(); + } +} + +const settingsStore = new MoonbaseSettingsStore(); +export { settingsStore as MoonbaseSettingsStore }; diff --git a/packages/core-extensions/src/settings/index.ts b/packages/core-extensions/src/settings/index.ts index f3fe5b5..c98f56a 100644 --- a/packages/core-extensions/src/settings/index.ts +++ b/packages/core-extensions/src/settings/index.ts @@ -1,8 +1,4 @@ import { Patch } from "@moonlight-mod/types"; -import { - SettingsSection, - Settings as SettingsType -} from "@moonlight-mod/types/coreExtensions"; import { ExtensionWebExports } from "@moonlight-mod/types"; export const patches: Patch[] = [ @@ -11,7 +7,7 @@ export const patches: Patch[] = [ replace: { match: /\.CUSTOM,element:(.+?)}\];return (.{1,2})/, replacement: (_, lastElement, sections) => - `.CUSTOM,element:${lastElement}}];return require("settings_settings")._mutateSections(${sections})` + `.CUSTOM,element:${lastElement}}];return require("settings_settings").Settings._mutateSections(${sections})` } }, { @@ -21,63 +17,13 @@ export const patches: Patch[] = [ replacement: (orig, sections, section) => `${orig.replace( /Object\.values\(.\.UserSettingsSections\)/, - (orig) => `[...require("settings_settings").sectionNames,...${orig}]` + (orig) => + `[...require("settings_settings").Settings.sectionNames,...${orig}]` )}??${sections}.find(x=>x.section==${section})?._moonlight_submenu?.()` } } ]; export const webpackModules: ExtensionWebExports["webpackModules"] = { - settings: { - run: (module, exports, require) => { - const Settings: SettingsType = { - ourSections: [], - sectionNames: [], - - addSection: (section, label, element, color = null, pos, notice) => { - const data: SettingsSection = { - section, - label, - color, - element, - pos: pos ?? -4, - notice: notice - }; - - Settings.ourSections.push(data); - Settings.sectionNames.push(label); - return data; - }, - - addDivider: (pos = null) => { - Settings.ourSections.push({ - section: "DIVIDER", - pos: pos === null ? -4 : pos - }); - }, - - addHeader: function (label, pos = null) { - Settings.ourSections.push({ - section: "HEADER", - label: label, - pos: pos === null ? -4 : pos - }); - }, - - _mutateSections: (sections) => { - for (const section of Settings.ourSections) { - sections.splice( - section.pos < 0 ? sections.length + section.pos : section.pos, - 0, - section - ); - } - - return sections; - } - }; - - module.exports = Settings; - } - } + settings: {} }; diff --git a/packages/core-extensions/src/settings/webpackModules/settings.ts b/packages/core-extensions/src/settings/webpackModules/settings.ts new file mode 100644 index 0000000..09ef6fd --- /dev/null +++ b/packages/core-extensions/src/settings/webpackModules/settings.ts @@ -0,0 +1,53 @@ +import { + SettingsSection, + Settings as SettingsType +} from "@moonlight-mod/types/coreExtensions"; + +export const Settings: SettingsType = { + ourSections: [], + sectionNames: [], + + addSection: (section, label, element, color = null, pos, notice) => { + const data: SettingsSection = { + section, + label, + color, + element, + pos: pos ?? -4, + notice: notice + }; + + Settings.ourSections.push(data); + Settings.sectionNames.push(label); + return data; + }, + + addDivider: (pos = null) => { + Settings.ourSections.push({ + section: "DIVIDER", + pos: pos === null ? -4 : pos + }); + }, + + addHeader: function (label, pos = null) { + Settings.ourSections.push({ + section: "HEADER", + label: label, + pos: pos === null ? -4 : pos + }); + }, + + _mutateSections: (sections) => { + for (const section of Settings.ourSections) { + sections.splice( + section.pos < 0 ? sections.length + section.pos : section.pos, + 0, + section + ); + } + + return sections; + } +}; + +export default Settings; diff --git a/packages/types/src/discord/require.ts b/packages/types/src/discord/require.ts index a949353..4b918e1 100644 --- a/packages/types/src/discord/require.ts +++ b/packages/types/src/discord/require.ts @@ -12,12 +12,17 @@ declare function WebpackRequire(id: "spacepack_spacepack"): { default: Spacepack; spacepack: Spacepack; }; -declare function WebpackRequire(id: "common_react"): CommonReact; + +declare function WebpackRequire(id: "common_components"): CommonComponents; declare function WebpackRequire(id: "common_flux"): CommonFlux; declare function WebpackRequire( id: "common_fluxDispatcher" ): CommonFluxDispatcher; -declare function WebpackRequire(id: "settings_settings"): Settings; -declare function WebpackRequire(id: "common_components"): CommonComponents; +declare function WebpackRequire(id: "common_react"): CommonReact; + +declare function WebpackRequire(id: "settings_settings"): { + Settings: Settings; + default: Settings; +}; export default WebpackRequire; diff --git a/packages/types/src/import.d.ts b/packages/types/src/import.d.ts index daf0b7d..b87e1da 100644 --- a/packages/types/src/import.d.ts +++ b/packages/types/src/import.d.ts @@ -3,3 +3,21 @@ declare module "@moonlight-mod/wp/spacepack_spacepack" { export const spacepack: Spacepack; export default spacepack; } + +declare module "@moonlight-mod/wp/common_components" { + import { CommonComponents } from "@moonlight-mod/types/coreExtensions"; + const components: CommonComponents; + export default components; +} + +declare module "@moonlight-mod/wp/common_flux" { + import { CommonFlux } from "@moonlight-mod/types/coreExtensions"; + const Flux: CommonFlux; + export default Flux; +} + +declare module "@moonlight-mod/wp/common_fluxDispatcher" { + import { CommonFluxDispatcher } from "@moonlight-mod/types/coreExtensions"; + const Dispatcher: CommonFluxDispatcher; + export default Dispatcher; +}