diff --git a/browser/userscript.meta.js b/browser/userscript.meta.js index 5b2a39be6..1d986aaee 100644 --- a/browser/userscript.meta.js +++ b/browser/userscript.meta.js @@ -5,6 +5,7 @@ // @author Vendicated (https://github.com/Vendicated) // @namespace https://github.com/Vendicated/Vencord // @supportURL https://github.com/Vendicated/Vencord +// @icon https://raw.githubusercontent.com/Vendicated/Vencord/refs/heads/main/browser/icon.png // @license GPL-3.0 // @match *://*.discord.com/* // @grant GM_xmlhttpRequest diff --git a/package.json b/package.json index 22b99f8bc..9d5ad3e58 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.9.7", + "version": "1.10.5", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -70,6 +70,7 @@ "stylelint": "^16.8.1", "stylelint-config-standard": "^36.0.1", "ts-patch": "^3.2.1", + "ts-pattern": "^5.3.1", "tsx": "^4.16.5", "type-fest": "^4.23.0", "typescript": "^5.5.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9567475fb..eaa6b537c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -116,6 +116,9 @@ importers: ts-patch: specifier: ^3.2.1 version: 3.2.1 + ts-pattern: + specifier: ^5.3.1 + version: 5.3.1 tsx: specifier: ^4.16.5 version: 4.16.5 @@ -2524,6 +2527,9 @@ packages: resolution: {integrity: sha512-hlR43v+GUIUy8/ZGFP1DquEqPh7PFKQdDMTAmYt671kCCA6AkDQMoeFaFmZ7ObPLYOmpMgyKUqL1C+coFMf30w==} hasBin: true + ts-pattern@5.3.1: + resolution: {integrity: sha512-1RUMKa8jYQdNfmnK4jyzBK3/PS/tnjcZ1CW0v1vWDeYe5RBklc/nquw03MEoB66hVBm4BnlCfmOqDVxHyT1DpA==} + tsconfig-paths@3.15.0: resolution: {integrity: sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==} @@ -5158,6 +5164,8 @@ snapshots: semver: 7.6.3 strip-ansi: 6.0.1 + ts-pattern@5.3.1: {} + tsconfig-paths@3.15.0: dependencies: '@types/json5': 0.0.29 diff --git a/scripts/build/build.mjs b/scripts/build/build.mjs index 817c2cec3..623f9f940 100755 --- a/scripts/build/build.mjs +++ b/scripts/build/build.mjs @@ -21,7 +21,7 @@ import esbuild from "esbuild"; import { readdir } from "fs/promises"; import { join } from "path"; -import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, watch } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, exists, globPlugins, IS_DEV, IS_REPORTER, IS_STANDALONE, IS_UPDATER_DISABLED, resolvePluginName, VERSION, commonRendererPlugins, watch } from "./common.mjs"; const defines = { IS_STANDALONE, @@ -131,7 +131,7 @@ await Promise.all([ sourcemap, plugins: [ globPlugins("discordDesktop"), - ...commonOpts.plugins + ...commonRendererPlugins ], define: { ...defines, @@ -180,7 +180,7 @@ await Promise.all([ sourcemap, plugins: [ globPlugins("vencordDesktop"), - ...commonOpts.plugins + ...commonRendererPlugins ], define: { ...defines, diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index bc15ccced..deab86610 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -23,7 +23,7 @@ import { appendFile, mkdir, readdir, readFile, rm, writeFile } from "fs/promises import { join } from "path"; import Zip from "zip-local"; -import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION } from "./common.mjs"; +import { BUILD_TIMESTAMP, commonOpts, globPlugins, IS_DEV, IS_REPORTER, VERSION, commonRendererPlugins } from "./common.mjs"; /** * @type {esbuild.BuildOptions} @@ -36,7 +36,7 @@ const commonOptions = { external: ["~plugins", "~git-hash", "/assets/*"], plugins: [ globPlugins("web"), - ...commonOpts.plugins, + ...commonRendererPlugins ], target: ["esnext"], define: { @@ -116,7 +116,12 @@ await Promise.all( } }) ] -); +).catch(err => { + console.error("Build failed"); + console.error(err.message); + if (!commonOpts.watch) + process.exit(1); +});; /** * @type {(dir: string) => Promise} diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index c46a559a7..e88f1e2b9 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -28,6 +28,7 @@ import { join, relative } from "path"; import { promisify } from "util"; import { getPluginTarget } from "../utils.mjs"; +import { builtinModules } from "module"; /** @type {import("../../package.json")} */ const PackageJSON = JSON.parse(readFileSync("package.json")); @@ -292,6 +293,18 @@ export const stylePlugin = { } }; +/** + * @type {(filter: RegExp, message: string) => import("esbuild").Plugin} + */ +export const banImportPlugin = (filter, message) => ({ + name: "ban-imports", + setup: build => { + build.onResolve({ filter }, () => { + return { errors: [{ text: message }] }; + }); + } +}); + /** * @type {import("esbuild").BuildOptions} */ @@ -311,3 +324,16 @@ export const commonOpts = { // Work around https://github.com/evanw/esbuild/issues/2460 tsconfig: "./scripts/build/tsconfig.esbuild.json" }; + +const escapedBuiltinModules = builtinModules + .map(m => m.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&")) + .join("|"); +const builtinModuleRegex = new RegExp(`^(node:)?(${escapedBuiltinModules})$`); + +export const commonRendererPlugins = [ + banImportPlugin(builtinModuleRegex, "Cannot import node inbuilt modules in browser code. You need to use a native.ts file"), + banImportPlugin(/^react$/, "Cannot import from react. React and hooks should be imported from @webpack/common"), + banImportPlugin(/^electron(\/.*)?$/, "Cannot import electron in browser code. You need to use a native.ts file"), + banImportPlugin(/^ts-pattern$/, "Cannot import from ts-pattern. match and P should be imported from @webpack/common"), + ...commonOpts.plugins +]; diff --git a/src/api/Commands/index.ts b/src/api/Commands/index.ts index ef4db171c..e5803ba02 100644 --- a/src/api/Commands/index.ts +++ b/src/api/Commands/index.ts @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import { Logger } from "@utils/Logger"; import { makeCodeblock } from "@utils/text"; import { sendBotMessage } from "./commandHelpers"; @@ -46,10 +47,10 @@ export let RequiredMessageOption: Option = ReqPlaceholder; export const _init = function (cmds: Command[]) { try { BUILT_IN = cmds; - OptionalMessageOption = cmds.find(c => c.name === "shrug")!.options![0]; - RequiredMessageOption = cmds.find(c => c.name === "me")!.options![0]; + OptionalMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "shrug")!.options![0]; + RequiredMessageOption = cmds.find(c => (c.untranslatedName || c.displayName) === "me")!.options![0]; } catch (e) { - console.error("Failed to load CommandsApi"); + new Logger("CommandsAPI").error("Failed to load CommandsApi", e, " - cmds is", cmds); } return cmds; } as never; @@ -138,6 +139,8 @@ export function registerCommand(command: C, plugin: string) { throw new Error(`Command '${command.name}' already exists.`); command.isVencordCommand = true; + command.untranslatedName ??= command.name; + command.untranslatedDescription ??= command.description; command.id ??= `-${BUILT_IN.length + 1}`; command.applicationId ??= "-1"; // BUILT_IN; command.type ??= ApplicationCommandType.CHAT_INPUT; diff --git a/src/api/Commands/types.ts b/src/api/Commands/types.ts index bd349e250..70b73775a 100644 --- a/src/api/Commands/types.ts +++ b/src/api/Commands/types.ts @@ -93,8 +93,10 @@ export interface Command { isVencordCommand?: boolean; name: string; + untranslatedName?: string; displayName?: string; description: string; + untranslatedDescription?: string; displayDescription?: string; options?: Option[]; diff --git a/src/api/ContextMenu.ts b/src/api/ContextMenu.ts index fdd4facf4..114942ff6 100644 --- a/src/api/ContextMenu.ts +++ b/src/api/ContextMenu.ts @@ -90,19 +90,20 @@ export function removeGlobalContextMenuPatch(patch: GlobalContextMenuPatchCallba * A helper function for finding the children array of a group nested inside a context menu based on the id(s) of its children * @param id The id of the child. If an array is specified, all ids will be tried * @param children The context menu children + * @param matchSubstring Whether to check if the id is a substring of the child id */ -export function findGroupChildrenByChildId(id: string | string[], children: Array): Array | null { +export function findGroupChildrenByChildId(id: string | string[], children: Array, matchSubstring = false): Array | null { for (const child of children) { if (child == null) continue; if (Array.isArray(child)) { - const found = findGroupChildrenByChildId(id, child); + const found = findGroupChildrenByChildId(id, child, matchSubstring); if (found !== null) return found; } if ( - (Array.isArray(id) && id.some(id => child.props?.id === id)) - || child.props?.id === id + (Array.isArray(id) && id.some(id => matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id)) + || (matchSubstring ? child.props?.id?.includes(id) : child.props?.id === id) ) return children; let nextChildren = child.props?.children; @@ -112,7 +113,7 @@ export function findGroupChildrenByChildId(id: string | string[], children: Arra child.props.children = nextChildren; } - const found = findGroupChildrenByChildId(id, nextChildren); + const found = findGroupChildrenByChildId(id, nextChildren, matchSubstring); if (found !== null) return found; } } diff --git a/src/api/Settings.ts b/src/api/Settings.ts index 88337a917..ac116f547 100644 --- a/src/api/Settings.ts +++ b/src/api/Settings.ts @@ -230,6 +230,10 @@ export function definePluginSettings< if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); return Settings.plugins[definedSettings.pluginName] as any; }, + get plain() { + if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); + return PlainSettings.plugins[definedSettings.pluginName] as any; + }, use: settings => useSettings( settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) as UseSettings[] ).plugins[definedSettings.pluginName] as any, diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index 7ba078d33..fa142a18c 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -65,8 +65,7 @@ export function LinkIcon({ height = 24, width = 24, className }: IconProps) { } /** - * Discord's copy icon, as seen in the user popout right of the username when clicking - * your own username in the bottom left user panel + * Discord's copy icon, as seen in the user panel popout on the right of the username and in large code blocks */ export function CopyIcon(props: IconProps) { return ( @@ -76,8 +75,9 @@ export function CopyIcon(props: IconProps) { viewBox="0 0 24 24" > - - + + + ); diff --git a/src/components/PluginSettings/components/SettingNumericComponent.tsx b/src/components/PluginSettings/components/SettingNumericComponent.tsx index 446d2504b..b724717d7 100644 --- a/src/components/PluginSettings/components/SettingNumericComponent.tsx +++ b/src/components/PluginSettings/components/SettingNumericComponent.tsx @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import { Margins } from "@utils/margins"; +import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { OptionType, PluginOptionNumber } from "@utils/types"; import { Forms, React, TextInput } from "@webpack/common"; @@ -54,7 +56,8 @@ export function SettingNumericComponent({ option, pluginSettings, definedSetting return ( - {option.description} + {wordsToTitle(wordsFromCamel(id))} + {option.description} . */ +import { Margins } from "@utils/margins"; +import { wordsFromCamel, wordsToTitle } from "@utils/text"; import { PluginOptionSelect } from "@utils/types"; import { Forms, React, Select } from "@webpack/common"; @@ -44,7 +46,8 @@ export function SettingSelectComponent({ option, pluginSettings, definedSettings return ( - {option.description} + {wordsToTitle(wordsFromCamel(id))} + {option.description}