/* * Vencord, a modification for Discord's desktop app * Copyright (c) 2022 Vendicated and Megumin * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import { Settings } from "@api/Settings"; import { BackupAndRestoreTab, CloudTab, PatchHelperTab, PluginsTab, ThemesTab, UpdaterTab, VencordTab } from "@components/settings/tabs"; import { Devs } from "@utils/constants"; import { getIntlMessage } from "@utils/discord"; import definePlugin, { OptionType } from "@utils/types"; import { React } from "@webpack/common"; import gitHash from "~git-hash"; 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: /\[\(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: "#{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: "Vencord", className: "vc-settings-header" }, { section: "settings/tabs", label: "Vencord", element: VencordTab, className: "vc-settings" }, { section: "VencordPlugins", label: "Plugins", element: PluginsTab, className: "vc-plugins" }, { section: "VencordThemes", label: "Themes", element: ThemesTab, className: "vc-themes" }, !IS_UPDATER_DISABLED && { section: "VencordUpdater", label: "Updater", element: UpdaterTab, className: "vc-updater" }, { section: "VencordCloud", label: "Cloud", element: CloudTab, className: "vc-cloud" }, { section: "settings/tabsSync", label: "Backup & Restore", element: BackupAndRestoreTab, className: "vc-backup-restore" }, IS_DEV && { section: "VencordPatchHelper", label: "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 Vencord 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; } }, get additionalInfo() { if (IS_DEV) return " (Dev)"; if (IS_WEB) return " (Web)"; if (IS_VESKTOP) return ` (Vesktop v${VesktopNative.app.getVersion()})`; if (IS_STANDALONE) return " (Standalone)"; return ""; }, getInfoRows() { const { electronVersion, chromiumVersion, additionalInfo } = this; const rows = [`Vencord ${gitHash}${additionalInfo}`]; 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} ); } });