/* * Vencord, a Discord client mod * Copyright (c) 2025 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { Settings } from "@api/Settings"; import { BackupAndRestoreTab, ChangelogTab, CloudTab, PatchHelperTab, PluginsTab, UpdaterTab, VencordTab, } from "@components/settings"; import ThemesTab from "@components/ThemeSettings/ThemesTab"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { shortGitHash } from "@utils/updater"; import { React } from "@webpack/common"; type SectionType = "HEADER" | "DIVIDER" | "CUSTOM"; type SectionTypes = Record; export default definePlugin({ name: "Settings", description: "Adds Settings UI and debug info", authors: [Devs.Ven, Devs.Megu], required: true, patches: [ { find: ".versionHash", replacement: [ { match: /\.info.+?\[\(0,\i\.jsxs?\)\((.{1,10}),(\{[^{}}]+\{.{0,20}.versionHash,.+?\})\)," "/, replace: (m, component, props) => { props = props.replace(/children:\[.+\]/, ""); return `${m},$self.makeInfoElements(${component}, ${props})`; }, }, { match: /copyValue:\i\.join\(" "\)/, replace: "$& + $self.getInfoString()", }, ], }, { find: ".SEARCH_NO_RESULTS&&0===", replacement: [ { match: /(?<=section:(.{0,50})\.DIVIDER\}\))([,;])(?=.{0,200}(\i)\.push.{0,100}label:(\i)\.header)/, replace: ( _, sectionTypes, commaOrSemi, elements, element, ) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`, }, { match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?\(\)=>)\2/, replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`, }, ], }, { find: ".DEVELOPER_SECTION,", replacement: [ { match: /\i\.\i\.isDeveloper/, replace: "true" }, ] }, { find: "#{intl::USER_SETTINGS_ACTIONS_MENU_LABEL}", replacement: { match: /(?<=function\((\i),\i\)\{)(?=let \i=Object.values\(\i.\i\).*?(\i\.\i)\.open\()/, replace: "$2.open($1);return;", }, }, ], customSections: [] as ((SectionTypes: SectionTypes) => any)[], makeSettingsCategories(SectionTypes: SectionTypes) { return [ { section: SectionTypes.HEADER, label: "Equicord", className: "vc-settings-header", }, { section: "EquicordSettings", label: "Equicord", element: VencordTab, className: "vc-settings", }, { section: "EquicordPlugins", label: "Plugins", searchableTitles: ["Plugins"], element: PluginsTab, className: "vc-plugins", }, { section: "EquicordThemes", label: "Themes", searchableTitles: ["Themes"], element: ThemesTab, className: "vc-themes", }, !IS_UPDATER_DISABLED && { section: "EquicordUpdater", label: "Updater", searchableTitles: ["Updater"], element: UpdaterTab, className: "vc-updater", }, { section: "EquicordChangelog", label: "Changelog", searchableTitles: ["Changelog"], element: ChangelogTab, className: "vc-changelog", }, { section: "EquicordCloud", label: "Cloud", searchableTitles: ["Cloud"], element: CloudTab, className: "vc-cloud", }, { section: "settings/tabsSync", label: "Backup & Restore", searchableTitles: ["Backup & Restore"], element: BackupAndRestoreTab, className: "vc-backup-restore", }, IS_DEV && { section: "EquicordPatchHelper", label: "Patch Helper", searchableTitles: ["Patch Helper"], element: PatchHelperTab, className: "vc-patch-helper", }, ...this.customSections.map(func => func(SectionTypes)), { section: SectionTypes.DIVIDER, }, ].filter(Boolean); }, isRightSpot({ header, settings, }: { header?: string; settings?: string[]; }) { const firstChild = settings?.[0]; // lowest two elements... sanity backup if (firstChild === "LOGOUT" || firstChild === "SOCIAL_LINKS") return true; const { settingsLocation } = Settings.plugins.Settings; if (settingsLocation === "bottom") return firstChild === "LOGOUT"; if (settingsLocation === "belowActivity") return firstChild === "CHANGELOG"; if (!header) return; try { const names = { top: getIntlMessage("USER_SETTINGS"), aboveNitro: getIntlMessage("BILLING_SETTINGS"), belowNitro: getIntlMessage("APP_SETTINGS"), aboveActivity: getIntlMessage("ACTIVITY_SETTINGS"), }; if ( !names[settingsLocation] || names[settingsLocation].endsWith("_SETTINGS") ) return firstChild === "PREMIUM"; return header === names[settingsLocation]; } catch { return firstChild === "PREMIUM"; } }, patchedSettings: new WeakSet(), addSettings( elements: any[], element: { header?: string; settings: string[]; }, sectionTypes: SectionTypes, ) { if (this.patchedSettings.has(elements) || !this.isRightSpot(element)) return; this.patchedSettings.add(elements); elements.push(...this.makeSettingsCategories(sectionTypes)); }, wrapSettingsHook( originalHook: (...args: any[]) => Record[], ) { return (...args: any[]) => { const elements = originalHook(...args); if (!this.patchedSettings.has(elements)) elements.unshift( ...this.makeSettingsCategories({ HEADER: "HEADER", DIVIDER: "DIVIDER", CUSTOM: "CUSTOM", }), ); return elements; }; }, options: { settingsLocation: { type: OptionType.SELECT, description: "Where to put the Equicord settings section", options: [ { label: "At the very top", value: "top" }, { label: "Above the Nitro section", value: "aboveNitro", default: true }, { label: "Below the Nitro section", value: "belowNitro" }, { label: "Above Activity Settings", value: "aboveActivity" }, { label: "Below Activity Settings", value: "belowActivity" }, { label: "At the very bottom", value: "bottom" }, ], }, }, get electronVersion() { return ( VencordNative.native.getVersions().electron || window.legcord?.electron || null ); }, get chromiumVersion() { try { return ( VencordNative.native.getVersions().chrome || // @ts-expect-error Typescript will add userAgentData IMMEDIATELY navigator.userAgentData?.brands?.find( b => b.brand === "Chromium" || b.brand === "Google Chrome", )?.version || null ); } catch { // inb4 some stupid browser throws unsupported error for navigator.userAgentData, it's only in chromium return null; } }, getVersionInfo(support = true) { let version = ""; if (IS_DEV) version = "Dev"; if (IS_WEB) version = "Web"; if (IS_VESKTOP) version = `Vesktop v${VesktopNative.app.getVersion()}`; if (IS_EQUIBOP) version = `Equibop v${VesktopNative.app.getVersion()}`; if (IS_STANDALONE) version = "Standalone"; return support && version ? ` (${version})` : version; }, getInfoRows() { const { electronVersion, chromiumVersion, getVersionInfo } = this; const rows = [`Equicord ${shortGitHash()}${getVersionInfo()}`]; if (electronVersion) rows.push(`Electron ${electronVersion}`); if (chromiumVersion) rows.push(`Chromium ${chromiumVersion}`); return rows; }, getInfoString() { return "\n" + this.getInfoRows().join("\n"); }, makeInfoElements( Component: React.ComponentType, props: React.PropsWithChildren, ) { return this.getInfoRows().map((text, i) => ( {text} )); }, });