From 374531d10e8c12803036867de84edeca10fe2e25 Mon Sep 17 00:00:00 2001 From: A user Date: Mon, 2 Jan 2023 22:30:54 -0300 Subject: [PATCH 001/114] Extract inline styles to css (#370) --- .../components/BadgeComponent.tsx | 7 +-- src/components/PluginSettings/index.tsx | 18 +++--- src/components/PluginSettings/styles.css | 61 +++++++++++++++++++ src/components/PluginSettings/styles.ts | 61 ------------------- .../VencordSettings/BackupRestoreTab.tsx | 9 +-- src/components/VencordSettings/ThemesTab.tsx | 6 +- src/components/VencordSettings/Updater.tsx | 12 ++-- src/components/VencordSettings/VencordTab.tsx | 28 +++------ src/components/VencordSettings/index.tsx | 7 ++- .../VencordSettings/settingsStyles.css | 24 +++++++- src/utils/misc.tsx | 2 +- 11 files changed, 121 insertions(+), 114 deletions(-) create mode 100644 src/components/PluginSettings/styles.css delete mode 100644 src/components/PluginSettings/styles.ts diff --git a/src/components/PluginSettings/components/BadgeComponent.tsx b/src/components/PluginSettings/components/BadgeComponent.tsx index 059376fd5..6acf42a13 100644 --- a/src/components/PluginSettings/components/BadgeComponent.tsx +++ b/src/components/PluginSettings/components/BadgeComponent.tsx @@ -16,15 +16,12 @@ * along with this program. If not, see . */ -import { BadgeStyle } from "@components/PluginSettings/styles"; - export function Badge({ text, color }): JSX.Element { return ( -
{text}
); } diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 981891457..27618d430 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -16,16 +16,18 @@ * along with this program. If not, see . */ +import "./styles.css"; + import * as DataStore from "@api/DataStore"; import { showNotice } from "@api/Notices"; import { Settings, useSettings } from "@api/settings"; +import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { handleComponentFailed } from "@components/handleComponentFailed"; import { Badge } from "@components/PluginSettings/components"; import PluginModal from "@components/PluginSettings/PluginModal"; -import * as styles from "@components/PluginSettings/styles"; import { ChangeList } from "@utils/ChangeList"; import Logger from "@utils/Logger"; import { classes, LazyComponent, useAwaiter } from "@utils/misc"; @@ -36,6 +38,8 @@ import { Alerts, Button, Forms, Margins, Parser, React, Select, Switch, Text, Te import Plugins from "~plugins"; +const cl = classNameFactory("vc-plugins-"); + import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins"; const logger = new Logger("PluginSettings", "#a6d189"); @@ -145,7 +149,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe } return ( - + {plugin.name}{(isNew) && } - + + + + ); +} + +export function buildDecModal(msg: any): any { + openModal((props: any) => ); +} diff --git a/src/plugins/invisiblechat/components/EncryptionModal.tsx b/src/plugins/invisiblechat/components/EncryptionModal.tsx new file mode 100644 index 000000000..f650f28c5 --- /dev/null +++ b/src/plugins/invisiblechat/components/EncryptionModal.tsx @@ -0,0 +1,116 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 { + ModalContent, + ModalFooter, + ModalHeader, + ModalProps, + ModalRoot, + openModal, +} from "@utils/modal"; +import { findLazy } from "@webpack"; +import { Button, Forms, React, Switch, TextInput } from "@webpack/common"; + +import { encrypt } from "../index"; + +const ComponentDispatch = findLazy(m => m.emitter?._events?.INSERT_TEXT); + +function EncModal(props: ModalProps) { + const [secret, setSecret] = React.useState(""); + const [cover, setCover] = React.useState(""); + const [password, setPassword] = React.useState("password"); + const [noCover, setNoCover] = React.useState(false); + + const isValid = secret && (noCover || (cover && /\w \w/.test(cover))); + + return ( + + + Encrypt Message + + + + Secret + { + setSecret(e); + }} + /> + Cover (2 or more Words!!) + { + setCover(e); + }} + /> + Password + { + setPassword(e); + }} + /> + { + setNoCover(e); + }} + > + Don't use a Cover + + + + + + + + + ); +} + +export function buildEncModal(): any { + openModal(props => ); +} diff --git a/src/plugins/invisiblechat/index.tsx b/src/plugins/invisiblechat/index.tsx new file mode 100644 index 000000000..89d2d6164 --- /dev/null +++ b/src/plugins/invisiblechat/index.tsx @@ -0,0 +1,213 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 { addButton, removeButton } from "@api/MessagePopover"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { getStegCloak } from "@utils/dependencies"; +import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Button, ButtonLooks, ChannelStore, FluxDispatcher, Tooltip } from "@webpack/common"; + +import { buildDecModal } from "./components/DecryptionModal"; +import { buildEncModal } from "./components/EncryptionModal"; + +const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent"); + +let steggo: any; + +function PopOverIcon() { + return ( + + + + + ); +} + + +function Indicator() { + return ( + + {({ onMouseEnter, onMouseLeave }) => ( + + )} + + + ); + +} + +function ChatBarIcon() { + return ( + + {({ onMouseEnter, onMouseLeave }) => ( + // size="" = Button.Sizes.NONE + + )} + + ); +} + + +export default definePlugin({ + name: "InvisibleChat", + description: "Encrypt your Messages in a non-suspicious way! This plugin makes requests to >>https://embed.sammcheese.net<< to provide embeds to decrypted links!", + authors: [Devs.SammCheese], + patches: [ + { + // Indicator + find: ".Messages.MESSAGE_EDITED,", + replacement: { + match: /var .,.,.=(.)\.className,.=.\.message,.=.\.children,.=.\.content,.=.\.onUpdate/gm, + replace: "try {$1 && $self.INV_REGEX.test($1.content[0]) ? $1.content.push($self.indicator()) : null } catch {};$&" + } + }, + { + find: ".activeCommandOption", + replacement: { + match: /.=.\.activeCommand,.=.\.activeCommandOption,.{1,133}(.)=\[\];/, + replace: "$&;$1.push($self.chatBarIcon());", + } + }, + ], + + EMBED_API_URL: "https://embed.sammcheese.net", + INV_REGEX: new RegExp(/( \u200c|\u200d |[\u2060-\u2064])[^\u200b]/), + URL_REGEX: new RegExp( + /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/, + ), + + async start() { + const { default: StegCloak } = await getStegCloak(); + steggo = new StegCloak(true, false); + + addButton("invDecrypt", message => { + return this.INV_REGEX.test(message?.content) + ? { + label: "Decrypt Message", + icon: this.popOverIcon, + message: message, + channel: ChannelStore.getChannel(message.channel_id), + onClick: () => buildDecModal({ message }) + } + : null; + }); + }, + + stop() { + removeButton("invDecrypt"); + }, + + // Gets the Embed of a Link + async getEmbed(url: URL): Promise { + const controller = new AbortController(); + const timeout = setTimeout(() => controller.abort(), 5000); + + const options: RequestInit = { + signal: controller.signal, + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + url, + }), + }; + + // AWS hosted url to discord embed object + const rawRes = await fetch(this.EMBED_API_URL, options); + clearTimeout(timeout); + + return await rawRes.json(); + }, + + async buildEmbed(message: any, revealed: string): Promise { + const urlCheck = revealed.match(this.URL_REGEX); + + message.embeds.push({ + type: "rich", + title: "Decrypted Message", + color: "0x45f5f5", + description: revealed, + footer: { + text: "Made with ❤️ by c0dine and Sammy!", + }, + }); + + if (urlCheck?.length) + message.embeds.push(await this.getEmbed(new URL(urlCheck[0]))); + + this.updateMessage(message); + }, + + updateMessage: (message: any) => { + FluxDispatcher.dispatch({ + type: "MESSAGE_UPDATE", + message, + }); + }, + + chatBarIcon: ErrorBoundary.wrap(ChatBarIcon, { noop: true }), + popOverIcon: () => , + indicator: ErrorBoundary.wrap(Indicator, { noop: true }) +}); + +export function encrypt(secret: string, password: string, cover: string): string { + return steggo.hide(secret + "\u200b", password, cover); +} + +export function decrypt(secret: string, password: string): string { + return steggo.reveal(secret, password).replace("\u200b", ""); +} + diff --git a/src/utils/constants.ts b/src/utils/constants.ts index d15615b60..dfb8157f0 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -180,5 +180,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ pointy: { name: "pointy", id: 99914384989519872n + }, + SammCheese: { + name: "Samm-Cheese", + id: 372148345894076416n } }); diff --git a/src/utils/dependencies.ts b/src/utils/dependencies.ts index ed26644ea..a09a87b2f 100644 --- a/src/utils/dependencies.ts +++ b/src/utils/dependencies.ts @@ -78,3 +78,6 @@ export interface ApngFrameData { const shikiWorkerDist = "https://unpkg.com/@vap/shiki-worker@0.0.8/dist"; export const shikiWorkerSrc = `${shikiWorkerDist}/${IS_DEV ? "index.js" : "index.min.js"}`; export const shikiOnigasmSrc = "https://unpkg.com/@vap/shiki@0.10.3/dist/onig.wasm"; + +// @ts-expect-error SHUT UP +export const getStegCloak = makeLazy(() => import("https://unpkg.com/stegcloak-dist@1.0.0/index.js")); diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx index 9024ff928..2a7d587ea 100644 --- a/src/webpack/common.tsx +++ b/src/webpack/common.tsx @@ -72,6 +72,7 @@ export const Forms = {} as { }; export let Card: Components.Card; export let Button: any; +export const ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED") as Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; export let Switch: any; export let Tooltip: Components.Tooltip; export let Router: any; From 15aa2299c3220aa5964981b0958338b6fad1d5b9 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 7 Jan 2023 22:53:41 +0100 Subject: [PATCH 014/114] Add Ctrl+Q Exit shortcut on Windows --- src/patcher.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/patcher.ts b/src/patcher.ts index 0cf7e24ce..6766e0f13 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -17,7 +17,7 @@ */ import { onceDefined } from "@utils/onceDefined"; -import electron, { app, BrowserWindowConstructorOptions } from "electron"; +import electron, { app, BrowserWindowConstructorOptions, globalShortcut } from "electron"; import { readFileSync } from "fs"; import { dirname, join } from "path"; @@ -153,6 +153,13 @@ if (!process.argv.includes("--vanilla")) { responseHeaders["content-type"] = ["text/css"]; } cb({ cancel: false, responseHeaders }); + + + if (process.platform === "win32") { + globalShortcut.register("Control+Q", () => { + app.quit(); + }); + } }); }); } else { From f0240ec3457aff627b56d75876a8ed9fc8a98a5d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 7 Jan 2023 22:15:22 -0300 Subject: [PATCH 015/114] chore(plugins): Fix IgnoreActivities & clean up other plugins (#384) --- ...ableDMCallIdle.ts => disableDMCallIdle.ts} | 0 src/plugins/ignoreActivities.tsx | 16 +++++++-------- src/plugins/volumeBooster.ts | 20 +++++++++---------- 3 files changed, 18 insertions(+), 18 deletions(-) rename src/plugins/{DisableDMCallIdle.ts => disableDMCallIdle.ts} (100%) diff --git a/src/plugins/DisableDMCallIdle.ts b/src/plugins/disableDMCallIdle.ts similarity index 100% rename from src/plugins/DisableDMCallIdle.ts rename to src/plugins/disableDMCallIdle.ts diff --git a/src/plugins/ignoreActivities.tsx b/src/plugins/ignoreActivities.tsx index a85e50189..cfaaafd6a 100644 --- a/src/plugins/ignoreActivities.tsx +++ b/src/plugins/ignoreActivities.tsx @@ -35,7 +35,7 @@ interface IgnoredActivity { } const RegisteredGamesClasses = findByPropsLazy("overlayToggleIconOff", "overlayToggleIconOn"); -const PreviewBadgeClasses = findByPropsLazy("previewBadge", "previewBadgeIcon"); +const TryItOutClasses = findByPropsLazy("tryItOutBadge", "tryItOutBadgeIcon"); const BaseShapeRoundClasses = findByPropsLazy("baseShapeRound", "baseShapeRoundLeft", "baseShapeRoundRight"); const RunningGameStore = findByPropsLazy("getRunningGames", "getGamesSeen"); @@ -116,7 +116,7 @@ function ToggleActivityComponent({ activity }: { activity: IgnoredActivity; }) { function ToggleActivityComponentWithBackground({ activity }: { activity: IgnoredActivity; }) { return (
@@ -145,11 +145,11 @@ export default definePlugin({ patches: [{ find: ".Messages.SETTINGS_GAMES_OVERLAY_ON", replacement: { - match: /(this.renderLastPlayed\(\)]}\),this.renderOverlayToggle\(\))/, - replace: "$1,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(this.props)" + match: /this.renderLastPlayed\(\)]}\),this.renderOverlayToggle\(\)/, + replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(this.props)" } }, { - find: ".Messages.NEW,name", + find: ".overlayBadge", replacement: { match: /.badgeContainer.+?.\?\(0,.\.jsx\)\(.{1,2},{name:(?.)\.name}\):null/, replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleActivityButton($)" @@ -157,8 +157,8 @@ export default definePlugin({ }, { find: '.displayName="LocalActivityStore"', replacement: { - match: /((.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?;)/, - replace: "$1$2=$2.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityEnabled);" + match: /(?.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?\)\)/, + replace: "$&;$=$.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityIgnored);" } }], @@ -207,7 +207,7 @@ export default definePlugin({ ); }, - isActivityEnabled(props: { type: number; application_id?: string; name?: string; }) { + isActivityIgnored(props: { type: number; application_id?: string; name?: string; }) { if (props.type === 0) { if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id); else { diff --git a/src/plugins/volumeBooster.ts b/src/plugins/volumeBooster.ts index dab6b930f..49d9d5433 100644 --- a/src/plugins/volumeBooster.ts +++ b/src/plugins/volumeBooster.ts @@ -29,21 +29,21 @@ export default definePlugin({ { find: ".Messages.USER_VOLUME", replacement: { - match: /maxValue:(.{1,2}\..{1,2})\?(\d+?):(\d+?),/, - replace: (_, defaultMaxVolumePredicate, higherMaxVolume, minorMaxVolume) => "" - + `maxValue:${defaultMaxVolumePredicate}` - + `?${higherMaxVolume}*Vencord.Settings.plugins.VolumeBooster.multiplier` - + `:${minorMaxVolume}*Vencord.Settings.plugins.VolumeBooster.multiplier,` + match: /maxValue:(?.{1,2}\..{1,2})\?(?\d+?):(?\d+?),/, + replace: "" + + "maxValue:$" + + "?$*Vencord.Settings.plugins.VolumeBooster.multiplier" + + ":$*Vencord.Settings.plugins.VolumeBooster.multiplier," } }, { find: "currentVolume:", replacement: { - match: /maxValue:(.{1,2}\..{1,2})\?(\d+?):(\d+?),/, - replace: (_, defaultMaxVolumePredicate, higherMaxVolume, minorMaxVolume) => "" - + `maxValue:${defaultMaxVolumePredicate}` - + `?${higherMaxVolume}*Vencord.Settings.plugins.VolumeBooster.multiplier` - + `:${minorMaxVolume}*Vencord.Settings.plugins.VolumeBooster.multiplier,` + match: /maxValue:(?.{1,2}\..{1,2})\?(?\d+?):(?\d+?),/, + replace: "" + + "maxValue:$" + + "?$*Vencord.Settings.plugins.VolumeBooster.multiplier" + + ":$*Vencord.Settings.plugins.VolumeBooster.multiplier," } } ], From ae9fe7fcfd49b6b178e55f36cc3d12e286a6134c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 8 Jan 2023 19:13:33 +0100 Subject: [PATCH 016/114] Fix Ctrl+Q shortcut working when Discord minimised --- src/patcher.ts | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/src/patcher.ts b/src/patcher.ts index 6766e0f13..96cb23ca2 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -17,7 +17,7 @@ */ import { onceDefined } from "@utils/onceDefined"; -import electron, { app, BrowserWindowConstructorOptions, globalShortcut } from "electron"; +import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron"; import { readFileSync } from "fs"; import { dirname, join } from "path"; @@ -44,9 +44,27 @@ app.setAppPath(asarPath); if (!process.argv.includes("--vanilla")) { // Repatch after host updates on Windows - if (process.platform === "win32") + if (process.platform === "win32") { require("./patchWin32Updater"); + const originalBuild = Menu.buildFromTemplate; + Menu.buildFromTemplate = function (template) { + if (template[0]?.label === "&File") { + const { submenu } = template[0]; + if (Array.isArray(submenu)) { + submenu.push({ + label: "Quit (Hidden)", + visible: false, + acceleratorWorksWhenHidden: true, + accelerator: "Control+Q", + click: () => app.quit() + }); + } + } + return originalBuild.call(this, template); + }; + } + class BrowserWindow extends electron.BrowserWindow { constructor(options: BrowserWindowConstructorOptions) { if (options?.webPreferences?.preload && options.title) { @@ -153,13 +171,6 @@ if (!process.argv.includes("--vanilla")) { responseHeaders["content-type"] = ["text/css"]; } cb({ cancel: false, responseHeaders }); - - - if (process.platform === "win32") { - globalShortcut.register("Control+Q", () => { - app.quit(); - }); - } }); }); } else { From 95df164e44f3bf07824c8197d3a600af2ad63770 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 9 Jan 2023 15:57:02 +0100 Subject: [PATCH 017/114] PluginSettings: Try to improve performance --- src/components/PluginSettings/index.tsx | 119 +++++++++++++----------- 1 file changed, 65 insertions(+), 54 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index b51615f55..4389fe640 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -198,7 +198,13 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe ); } -export default ErrorBoundary.wrap(function Settings() { +enum SearchStatus { + ALL, + ENABLED, + DISABLED +} + +export default ErrorBoundary.wrap(function PluginSettings() { const settings = useSettings(); const changes = React.useMemo(() => new ChangeList(), []); @@ -239,21 +245,19 @@ export default ErrorBoundary.wrap(function Settings() { const sortedPlugins = React.useMemo(() => Object.values(Plugins) .sort((a, b) => a.name.localeCompare(b.name)), []); - const [searchValue, setSearchValue] = React.useState({ value: "", status: "all" }); + const [searchValue, setSearchValue] = React.useState({ value: "", status: SearchStatus.ALL }); const onSearch = (query: string) => setSearchValue(prev => ({ ...prev, value: query })); - const onStatusChange = (status: string) => setSearchValue(prev => ({ ...prev, status })); + const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status })); const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => { - const showEnabled = searchValue.status === "enabled" || searchValue.status === "all"; - const showDisabled = searchValue.status === "disabled" || searchValue.status === "all"; const enabled = settings.plugins[plugin.name]?.enabled || plugin.started; + if (enabled && searchValue.status === SearchStatus.DISABLED) return false; + if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; + if (!searchValue.value.length) return true; return ( - ((showEnabled && enabled) || (showDisabled && !enabled)) && - ( - plugin.name.toLowerCase().includes(searchValue.value.toLowerCase()) || - plugin.description.toLowerCase().includes(searchValue.value.toLowerCase()) - ) + plugin.name.toLowerCase().includes(searchValue.value.toLowerCase()) || + plugin.description.toLowerCase().includes(searchValue.value.toLowerCase()) ); }; @@ -274,6 +278,52 @@ export default ErrorBoundary.wrap(function Settings() { return window._.isEqual(newPlugins, sortedPluginNames) ? [] : newPlugins; })); + type P = JSX.Element | JSX.Element[]; + let plugins: P, requiredPlugins: P; + if (sortedPlugins?.length) { + plugins = []; + requiredPlugins = []; + + for (const p of sortedPlugins) { + if (!pluginFilter(p)) continue; + + const isRequired = p.required || depMap[p.name]?.some(d => Settings.plugins[d].enabled); + + if (isRequired) { + const tooltipText = p.required + ? "This plugin is required for Vencord to function." + : makeDependencyList(depMap[p.name]?.filter(d => settings.plugins[d].enabled)); + + requiredPlugins.push( + + {({ onMouseLeave, onMouseEnter }) => ( + changes.handleChange(name)} + disabled={true} + plugin={p} + /> + )} + + ); + } else { + plugins.push( + changes.handleChange(name)} + disabled={false} + plugin={p} + isNew={newPlugins?.includes(p.name)} + key={p.name} + /> + ); + } + + } + } else { + plugins = requiredPlugins = No plugins meet search criteria.; + } + return ( @@ -288,9 +338,9 @@ export default ErrorBoundary.wrap(function Settings() { This plugin is required by: - {deps.map((dep: string) => {dep})} + {deps.map((dep: string) => {dep})} ); } diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index f9856ee40..c48ce0cf9 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -27,14 +27,13 @@ background-color: var(--background-modifier-selected); color: var(--interactive-active); border-radius: 3px; - cursor: pointer; display: block; height: 100%; padding: 10px; - width: 100% + width: 100%; } -.vc-plugins-card .vc-plugins-info-button { +.vc-plugins-info-button { height: 24px; width: 24px; padding: 0; @@ -58,4 +57,47 @@ font-size: 12px; line-height: 16px; color: var(--white-500); + text-align: center; +} + +.vc-plugins-note { + height: 40px; + overflow: hidden; + text-overflow: ellipsis; + display: -webkit-box; + -webkit-line-clamp: 2; + line-clamp: 2; + -webkit-box-orient: vertical; + box-orient: vertical; +} + +.vc-plugins-name { + display: flex; + width: 100%; + align-items: center; + flex-grow: 1; + gap: 8px; +} + +.vc-plugins-flex { + margin-top: auto; + width: 100%; + height: 100%; + align-items: center; + gap: 8px; +} + +.vc-plugins-dep-name { + margin: 0 auto; +} + +.vc-plugins-reload-card { + padding: 1em; + display: grid; + grid-template-columns: 1fr auto; + gap: 1em; +} + +.vc-plugins-info-button svg:not(:hover):not(:focus) { + color: var(--text-muted); } From 23a461c36d7a2d9dbea4d0fc8f3a62d226aef3de Mon Sep 17 00:00:00 2001 From: "ZerXDE \"Till O" Date: Mon, 9 Jan 2023 16:53:33 +0100 Subject: [PATCH 019/114] oneko: Disable pointer events to not block below buttons (#395) Updated version of oneko which disables pointer Events. Co-authored-by: Ven --- src/plugins/oneko.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/oneko.ts b/src/plugins/oneko.ts index ef2f5d682..d95ba2bc9 100644 --- a/src/plugins/oneko.ts +++ b/src/plugins/oneko.ts @@ -26,7 +26,7 @@ export default definePlugin({ authors: [Devs.Ven, Devs.adryd], start() { - fetch("https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.js") + fetch("https://raw.githubusercontent.com/adryd325/oneko.js/5977144dce83e4d71af1de005d16e38eebeb7b72/oneko.js") .then(x => x.text()) .then(s => s.replace("./oneko.gif", "https://raw.githubusercontent.com/adryd325/oneko.js/14bab15a755d0e35cd4ae19c931d96d306f99f42/oneko.gif")) .then(eval); From a772aa62f55ff16a42ab62f910fb6fcba8d48c32 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 9 Jan 2023 23:18:49 +0100 Subject: [PATCH 020/114] Fix PetPet & CorruptMp4s --- .gitattributes | 1 + src/plugins/corruptMp4s.ts | 2 +- src/plugins/petpet.ts | 4 ++-- 3 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 000000000..6313b56c5 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text=auto eol=lf diff --git a/src/plugins/corruptMp4s.ts b/src/plugins/corruptMp4s.ts index 5ae25eef6..b9c3a1197 100644 --- a/src/plugins/corruptMp4s.ts +++ b/src/plugins/corruptMp4s.ts @@ -99,7 +99,7 @@ export default definePlugin({ const newName = video.name.replace(/\.mp4$/i, ".corrupt.mp4"); const promptToUpload = findByCode("UPLOAD_FILE_LIMIT_ERROR"); const file = new File([buf], newName, { type: "video/mp4" }); - setImmediate(() => promptToUpload([file], ctx.channel, DRAFT_TYPE)); + setTimeout(() => promptToUpload([file], ctx.channel, DRAFT_TYPE), 10); } }] }); diff --git a/src/plugins/petpet.ts b/src/plugins/petpet.ts index a8281d06d..0d9a3d099 100644 --- a/src/plugins/petpet.ts +++ b/src/plugins/petpet.ts @@ -175,8 +175,8 @@ export default definePlugin({ gif.finish(); const file = new File([gif.bytesView()], "petpet.gif", { type: "image/gif" }); // Immediately after the command finishes, Discord clears all input, including pending attachments. - // Thus, setImmediate is needed to make this execute after Discord cleared the input - setImmediate(() => promptToUpload([file], cmdCtx.channel, DRAFT_TYPE)); + // Thus, setTimeout is needed to make this execute after Discord cleared the input + setTimeout(() => promptToUpload([file], cmdCtx.channel, DRAFT_TYPE), 10); }, }, ] From c525672777942179bec8762da6394fa2449536d4 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 11 Jan 2023 01:24:55 +0100 Subject: [PATCH 021/114] Fix BetterRoleDot crash --- src/plugins/betterRoleDot.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/betterRoleDot.ts b/src/plugins/betterRoleDot.ts index 3b2ac39f8..6cadf7915 100644 --- a/src/plugins/betterRoleDot.ts +++ b/src/plugins/betterRoleDot.ts @@ -41,7 +41,7 @@ export default definePlugin({ all: true, predicate: () => Settings.plugins.BetterRoleDot.bothStyles, replacement: { - match: /"(?:username|dot)"===\w\b/g, + match: /"(?:username|dot)"===\w(?!\.\w)/g, replace: "true", }, }, From 19c9a132733dfb67de6d62d6da1348944cbe1146 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 11 Jan 2023 01:50:00 +0100 Subject: [PATCH 022/114] Fix InvisibleChat button getting hidden by themes --- .../components/DecryptionModal.tsx | 0 .../components/EncryptionModal.tsx | 0 .../index.tsx | 61 +++++++++++-------- 3 files changed, 35 insertions(+), 26 deletions(-) rename src/plugins/{invisiblechat => invisibleChat}/components/DecryptionModal.tsx (100%) rename src/plugins/{invisiblechat => invisibleChat}/components/EncryptionModal.tsx (100%) rename src/plugins/{invisiblechat => invisibleChat}/index.tsx (76%) diff --git a/src/plugins/invisiblechat/components/DecryptionModal.tsx b/src/plugins/invisibleChat/components/DecryptionModal.tsx similarity index 100% rename from src/plugins/invisiblechat/components/DecryptionModal.tsx rename to src/plugins/invisibleChat/components/DecryptionModal.tsx diff --git a/src/plugins/invisiblechat/components/EncryptionModal.tsx b/src/plugins/invisibleChat/components/EncryptionModal.tsx similarity index 100% rename from src/plugins/invisiblechat/components/EncryptionModal.tsx rename to src/plugins/invisibleChat/components/EncryptionModal.tsx diff --git a/src/plugins/invisiblechat/index.tsx b/src/plugins/invisibleChat/index.tsx similarity index 76% rename from src/plugins/invisiblechat/index.tsx rename to src/plugins/invisibleChat/index.tsx index 89d2d6164..e2f776957 100644 --- a/src/plugins/invisiblechat/index.tsx +++ b/src/plugins/invisibleChat/index.tsx @@ -70,32 +70,41 @@ function ChatBarIcon() { {({ onMouseEnter, onMouseLeave }) => ( // size="" = Button.Sizes.NONE - - )} - + /* + many themes set "> button" to display: none, as the gift button is + the only directly descending button (all the other elements are divs.) + Thus, wrap in a div here to avoid getting hidden by that. + flex is for some reason necessary as otherwise the button goes flying off + */ +
+ +
+ ) + } + ); } From b39cbcd934f55f3eb9f1f18a04ea084a4b281022 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 11 Jan 2023 22:50:31 -0300 Subject: [PATCH 023/114] fix(IgnoreActivities): Fix for upcoming change (#399) --- src/plugins/ignoreActivities.tsx | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/plugins/ignoreActivities.tsx b/src/plugins/ignoreActivities.tsx index cfaaafd6a..3b1ee98e3 100644 --- a/src/plugins/ignoreActivities.tsx +++ b/src/plugins/ignoreActivities.tsx @@ -143,10 +143,10 @@ export default definePlugin({ authors: [Devs.Nuckyz], description: "Ignore certain activities (like games and actual activities) from showing up on your status. You can configure which ones are ignored from the Registered Games and Activities tabs.", patches: [{ - find: ".Messages.SETTINGS_GAMES_OVERLAY_ON", + find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", replacement: { - match: /this.renderLastPlayed\(\)]}\),this.renderOverlayToggle\(\)/, - replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton(this.props)" + match: /var .=(?.)\.overlay.+?"aria-label":.\..\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}}\)/, + replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton($)" } }, { find: ".overlayBadge", @@ -189,12 +189,10 @@ export default definePlugin({ } }, - renderToggleGameActivityButton(props: { game: { id?: string; exePath: string; } | null; }) { - if (!props.game) return (null); - + renderToggleGameActivityButton(props: { id?: string; exePath: string; }) { return ( - + ); }, From bedb7b212bdb3da26294a04b934c8a04be79eda0 Mon Sep 17 00:00:00 2001 From: Nick Date: Wed, 11 Jan 2023 20:55:02 -0500 Subject: [PATCH 024/114] feat(Plugin): Add AlwaysTrust (#401) Co-authored-by: Ven --- src/plugins/alwaysTrust.ts | 42 ++++++++++++++++++++++++++++++++++++++ src/utils/constants.ts | 4 ++++ 2 files changed, 46 insertions(+) create mode 100644 src/plugins/alwaysTrust.ts diff --git a/src/plugins/alwaysTrust.ts b/src/plugins/alwaysTrust.ts new file mode 100644 index 000000000..8dde07fbf --- /dev/null +++ b/src/plugins/alwaysTrust.ts @@ -0,0 +1,42 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "AlwaysTrust", + description: "Removes the annoying untrusted domain and suspicious file popup", + authors: [Devs.zt], + patches: [ + { + find: ".displayName=\"MaskedLinkStore\"", + replacement: { + match: /\.isTrustedDomain=function\(.\){return.+?};/, + replace: ".isTrustedDomain=function(){return true};" + } + }, + { + find: "\"github.com\":new RegExp(\"\\\\/releases\\\\S*\\\\/download\"),", + replacement: { + match: /const o=JSON.parse\('\[.+?'\)/, + replace: "const o=[]" + } + } + ] +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index dfb8157f0..b5e1eb612 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -184,5 +184,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ SammCheese: { name: "Samm-Cheese", id: 372148345894076416n + }, + zt: { + name: "zt", + id: 289556910426816513n } }); From a8678db78c63d1981274f452045ccabe4cf6079d Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 12 Jan 2023 04:44:00 +0100 Subject: [PATCH 025/114] Fix React DevTools --- src/ipcMain/extensions.ts | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/src/ipcMain/extensions.ts b/src/ipcMain/extensions.ts index 0e26ff1d8..d8f843774 100644 --- a/src/ipcMain/extensions.ts +++ b/src/ipcMain/extensions.ts @@ -67,9 +67,18 @@ export async function installExt(id: string) { try { await access(extDir, fsConstants.F_OK); } catch (err) { - const url = `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`; - const buf = await get(url); - await extract(crxToZip(buf), extDir); + const url = id === "fmkadmapgofadopljbjfkapdkoienihi" + // React Devtools v4.25 + // v4.27 is broken in Electron, see https://github.com/facebook/react/issues/25843 + // Unfortunately, Google does not serve old versions, so this is the only way + ? "https://raw.githubusercontent.com/Vendicated/random-files/f6f550e4c58ac5f2012095a130406c2ab25b984d/fmkadmapgofadopljbjfkapdkoienihi.zip" + : `https://clients2.google.com/service/update2/crx?response=redirect&acceptformat=crx2,crx3&x=id%3D${id}%26uc&prodversion=32`; + const buf = await get(url, { + headers: { + "User-Agent": "Vencord (https://github.com/Vendicated/Vencord)" + } + }); + await extract(crxToZip(buf), extDir).catch(console.error); } session.defaultSession.loadExtension(extDir); From e70abc57b6885e97dff54b89e752ab6df949bd67 Mon Sep 17 00:00:00 2001 From: Ven Date: Thu, 12 Jan 2023 23:15:38 +0100 Subject: [PATCH 026/114] Update Windows Update patcher (#404) --- src/ipcMain/index.ts | 5 +-- src/patchWin32Updater.ts | 74 ++++++++++++++++++---------------------- src/preload.ts | 17 +-------- src/utils/IpcEvents.ts | 1 - 4 files changed, 35 insertions(+), 62 deletions(-) diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts index ae8a96db0..3f22b7253 100644 --- a/src/ipcMain/index.ts +++ b/src/ipcMain/index.ts @@ -22,7 +22,7 @@ import "./updater"; import { debounce } from "@utils/debounce"; import IpcEvents from "@utils/IpcEvents"; import { Queue } from "@utils/Queue"; -import { BrowserWindow, desktopCapturer, ipcMain, shell } from "electron"; +import { BrowserWindow, ipcMain, shell } from "electron"; import { mkdirSync, readFileSync, watch } from "fs"; import { open, readFile, writeFile } from "fs/promises"; import { join } from "path"; @@ -45,9 +45,6 @@ export function readSettings() { } } -// Fix for screensharing in Electron >= 17 -ipcMain.handle(IpcEvents.GET_DESKTOP_CAPTURE_SOURCES, (_, opts) => desktopCapturer.getSources(opts)); - ipcMain.handle(IpcEvents.OPEN_QUICKCSS, () => shell.openPath(QUICKCSS_PATH)); ipcMain.handle(IpcEvents.OPEN_EXTERNAL, (_, url) => { diff --git a/src/patchWin32Updater.ts b/src/patchWin32Updater.ts index e853ebf4d..e08e37dc0 100644 --- a/src/patchWin32Updater.ts +++ b/src/patchWin32Updater.ts @@ -17,7 +17,7 @@ */ import { app, autoUpdater } from "electron"; -import { existsSync, mkdirSync, readdirSync, writeFileSync } from "fs"; +import { existsSync, mkdirSync, readdirSync, renameSync, statSync, writeFileSync } from "fs"; import { basename, dirname, join } from "path"; const { setAppUserModelId } = app; @@ -44,58 +44,50 @@ function isNewer($new: string, old: string) { } function patchLatest() { - const currentAppPath = dirname(process.execPath); - const currentVersion = basename(currentAppPath); - const discordPath = join(currentAppPath, ".."); + try { + const currentAppPath = dirname(process.execPath); + const currentVersion = basename(currentAppPath); + const discordPath = join(currentAppPath, ".."); - const latestVersion = readdirSync(discordPath).reduce((prev, curr) => { - return (curr.startsWith("app-") && isNewer(curr, prev)) - ? curr - : prev; - }, currentVersion as string); + const latestVersion = readdirSync(discordPath).reduce((prev, curr) => { + return (curr.startsWith("app-") && isNewer(curr, prev)) + ? curr + : prev; + }, currentVersion as string); - if (latestVersion === currentVersion) return; + if (latestVersion === currentVersion) return; - const app = join(discordPath, latestVersion, "resources", "app"); - if (existsSync(app)) return; + const resources = join(discordPath, latestVersion, "resources"); + const app = join(resources, "app.asar"); + const _app = join(resources, "_app.asar"); - console.info("[Vencord] Detected Host Update. Repatching..."); + if (!existsSync(app) || statSync(app).isDirectory()) return; - const patcherPath = join(__dirname, "patcher.js"); - mkdirSync(app); - writeFileSync(join(app, "package.json"), JSON.stringify({ - name: "discord", - main: "index.js" - })); - writeFileSync(join(app, "index.js"), `require(${JSON.stringify(patcherPath)});`); + console.info("[Vencord] Detected Host Update. Repatching..."); + + renameSync(app, _app); + mkdirSync(app); + writeFileSync(join(app, "package.json"), JSON.stringify({ + name: "discord", + main: "index.js" + })); + writeFileSync(join(app, "index.js"), `require(${JSON.stringify(join(__dirname, "patcher.js"))});`); + } catch (err) { + console.error("[Vencord] Failed to repatch latest host update", err); + } } // Windows Host Updates install to a new folder app-{HOST_VERSION}, so we // need to reinject function patchUpdater() { - const main = require.main!; - const buildInfo = require(join(process.resourcesPath, "build_info.json")); - try { - if (buildInfo?.newUpdater) { - const autoStartScript = join(main.filename, "..", "autoStart", "win32.js"); - const { update } = require(autoStartScript); + const autoStartScript = join(require.main!.filename, "..", "autoStart", "win32.js"); + const { update } = require(autoStartScript); - // New Updater Injection - require.cache[autoStartScript]!.exports.update = function () { - patchLatest(); - update.apply(this, arguments); - }; - } else { - const hostUpdaterScript = join(main.filename, "..", "hostUpdater.js"); - const { quitAndInstall } = require(hostUpdaterScript); - - // Old Updater Injection - require.cache[hostUpdaterScript]!.exports.quitAndInstall = function () { - patchLatest(); - quitAndInstall.apply(this, arguments); - }; - } + require.cache[autoStartScript]!.exports.update = function () { + update.apply(this, arguments); + patchLatest(); + }; } catch { // OpenAsar uses electrons autoUpdater on Windows const { quitAndInstall } = autoUpdater; diff --git a/src/preload.ts b/src/preload.ts index 746008142..ee2fb80d7 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -18,27 +18,12 @@ import { debounce } from "@utils/debounce"; import IpcEvents from "@utils/IpcEvents"; -import electron, { contextBridge, ipcRenderer, webFrame } from "electron"; +import { contextBridge, ipcRenderer, webFrame } from "electron"; import { readFileSync } from "fs"; import { join } from "path"; import VencordNative from "./VencordNative"; -if (electron.desktopCapturer === void 0) { - // Fix for desktopCapturer being main only in Electron 17+ - // Discord accesses this in discord_desktop_core (DiscordNative.desktopCapture.getDesktopCaptureSources) - // and errors with cannot "read property getSources() of undefined" - // see discord_desktop_core/app/discord_native/renderer/desktopCapture.js - const electronPath = require.resolve("electron"); - delete require.cache[electronPath]!.exports; - require.cache[electronPath]!.exports = { - ...electron, - desktopCapturer: { - getSources: opts => ipcRenderer.invoke(IpcEvents.GET_DESKTOP_CAPTURE_SOURCES, opts) - } - }; -} - contextBridge.exposeInMainWorld("VencordNative", VencordNative); if (location.protocol !== "data:") { diff --git a/src/utils/IpcEvents.ts b/src/utils/IpcEvents.ts index 345146b22..e97e41b0f 100644 --- a/src/utils/IpcEvents.ts +++ b/src/utils/IpcEvents.ts @@ -43,7 +43,6 @@ export default strEnum({ GET_HASHES: "VencordGetHashes", UPDATE: "VencordUpdate", BUILD: "VencordBuild", - GET_DESKTOP_CAPTURE_SOURCES: "VencordGetDesktopCaptureSources", OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor", DOWNLOAD_VENCORD_CSS: "VencordDownloadVencordCss" } as const); From 10fd51071e90c47b6cd0041fb1260ea284864905 Mon Sep 17 00:00:00 2001 From: Nick Date: Thu, 12 Jan 2023 17:48:37 -0500 Subject: [PATCH 027/114] feat: Add option to disable the window frame (#400) Co-authored-by: Ven --- src/api/settings.ts | 2 ++ src/components/VencordSettings/VencordTab.tsx | 6 ++++++ src/patcher.ts | 9 +++++++-- 3 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/api/settings.ts b/src/api/settings.ts index 2617903a5..384647c53 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -30,6 +30,7 @@ export interface Settings { useQuickCss: boolean; enableReactDevtools: boolean; themeLinks: string[]; + frameless: boolean; plugins: { [plugin: string]: { enabled: boolean; @@ -43,6 +44,7 @@ const DefaultSettings: Settings = { useQuickCss: true, themeLinks: [], enableReactDevtools: false, + frameless: false, plugins: {} }; diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index e59c44991..9429cddc5 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -106,6 +106,12 @@ function VencordSettings() { note="Shows a toast on startup"> Get notified about new updates + settings.frameless = v} + note="Requires a full restart"> + Disable the window frame + )} diff --git a/src/patcher.ts b/src/patcher.ts index 96cb23ca2..4369782c2 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -65,12 +65,18 @@ if (!process.argv.includes("--vanilla")) { }; } + let settings = {} as any; + try { + settings = JSON.parse(readSettings()); + } catch { } + class BrowserWindow extends electron.BrowserWindow { constructor(options: BrowserWindowConstructorOptions) { if (options?.webPreferences?.preload && options.title) { const original = options.webPreferences.preload; options.webPreferences.preload = join(__dirname, "preload.js"); options.webPreferences.sandbox = false; + options.frame = settings.frameless; process.env.DISCORD_PRELOAD = original; @@ -118,8 +124,7 @@ if (!process.argv.includes("--vanilla")) { }); try { - const settings = JSON.parse(readSettings()); - if (settings.enableReactDevtools) + if (settings?.enableReactDevtools) installExt("fmkadmapgofadopljbjfkapdkoienihi") .then(() => console.info("[Vencord] Installed React Developer Tools")) .catch(err => console.error("[Vencord] Failed to install React Developer Tools", err)); From 075b0e0970bd0853a3e7c9fc0001da0472928695 Mon Sep 17 00:00:00 2001 From: Ven Date: Fri, 13 Jan 2023 01:34:06 +0100 Subject: [PATCH 028/114] Update patcher.ts --- src/patcher.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/patcher.ts b/src/patcher.ts index 4369782c2..d9eb79474 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -76,7 +76,7 @@ if (!process.argv.includes("--vanilla")) { const original = options.webPreferences.preload; options.webPreferences.preload = join(__dirname, "preload.js"); options.webPreferences.sandbox = false; - options.frame = settings.frameless; + options.frame = !settings.frameless; process.env.DISCORD_PRELOAD = original; From 26f2b51eb95d59ed55d1fc0f81a1579e08bd29f3 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Fri, 13 Jan 2023 01:50:58 +0100 Subject: [PATCH 029/114] Fix Frameless --- src/patcher.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/patcher.ts b/src/patcher.ts index d9eb79474..82fc23336 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -76,7 +76,9 @@ if (!process.argv.includes("--vanilla")) { const original = options.webPreferences.preload; options.webPreferences.preload = join(__dirname, "preload.js"); options.webPreferences.sandbox = false; - options.frame = !settings.frameless; + if (settings.frameless) { + options.frame = false; + } process.env.DISCORD_PRELOAD = original; From 6c5fcc4119d05389bbc71bd3e52090f6fd29b10c Mon Sep 17 00:00:00 2001 From: Ven Date: Fri, 13 Jan 2023 17:52:28 +0100 Subject: [PATCH 030/114] Use GUI installer for pnpm inject/uninject (#407) * Use GUI installer for pnpm inject/uninject * Run Installer in DevMode --- package.json | 4 +- scripts/patcher/common.js | 357 ----------------------------------- scripts/patcher/install.js | 219 --------------------- scripts/patcher/uninstall.js | 116 ------------ scripts/runInstaller.mjs | 97 ++++++++++ 5 files changed, 99 insertions(+), 694 deletions(-) delete mode 100644 scripts/patcher/common.js delete mode 100755 scripts/patcher/install.js delete mode 100755 scripts/patcher/uninstall.js create mode 100644 scripts/runInstaller.mjs diff --git a/package.json b/package.json index 51d384db9..224a480fc 100644 --- a/package.json +++ b/package.json @@ -20,13 +20,13 @@ "scripts": { "build": "node scripts/build/build.mjs", "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", - "inject": "node scripts/patcher/install.js", + "inject": "node scripts/runInstaller.mjs", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", "lint:fix": "pnpm lint --fix", "test": "pnpm lint && pnpm build && pnpm testTsc", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testTsc": "tsc --noEmit", - "uninject": "node scripts/patcher/uninstall.js", + "uninject": "node scripts/runInstaller.mjs", "watch": "node scripts/build/build.mjs --watch" }, "dependencies": { diff --git a/scripts/patcher/common.js b/scripts/patcher/common.js deleted file mode 100644 index 05523e5aa..000000000 --- a/scripts/patcher/common.js +++ /dev/null @@ -1,357 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 . -*/ - -const path = require("path"); -const readline = require("readline"); -const fs = require("fs"); -const menu = require("console-menu"); - -function pathToBranch(dir) { - dir = dir.toLowerCase(); - if (dir.endsWith("development")) { - return "development"; - } - if (dir.endsWith("canary")) { - return "canary"; - } - if (dir.endsWith("ptb")) { - return "ptb"; - } - return "stable"; -} - -const BRANCH_NAMES = [ - "Discord", - "DiscordPTB", - "DiscordCanary", - "DiscordDevelopment", - "discord", - "discordptb", - "discordcanary", - "discorddevelopment", - "discord-ptb", - "discord-canary", - "discord-development", - // Flatpak - "com.discordapp.Discord", - "com.discordapp.DiscordPTB", - "com.discordapp.DiscordCanary", - "com.discordapp.DiscordDevelopment", -]; - -const MACOS_DISCORD_DIRS = [ - "Discord.app", - "Discord PTB.app", - "Discord Canary.app", - "Discord Development.app", -]; - -if (process.platform === "linux" && process.env.SUDO_USER) { - process.env.HOME = fs - .readFileSync("/etc/passwd", "utf-8") - .match(new RegExp(`^${process.env.SUDO_USER}.+$`, "m"))[0] - .split(":")[5]; -} - -const LINUX_DISCORD_DIRS = [ - "/usr/share", - "/usr/lib64", - "/opt", - `${process.env.HOME}/.local/share`, - `${process.env.HOME}/.dvm`, - "/var/lib/flatpak/app", - `${process.env.HOME}/.local/share/flatpak/app`, -]; - -const FLATPAK_NAME_MAPPING = { - DiscordCanary: "discord-canary", - DiscordPTB: "discord-ptb", - DiscordDevelopment: "discord-development", - Discord: "discord", -}; - -const ENTRYPOINT = path - .join(process.cwd(), "dist", "patcher.js") - .replace(/\\/g, "/"); - -function question(question) { - const rl = readline.createInterface({ - input: process.stdin, - output: process.stdout, - terminal: false, - }); - - return new Promise(resolve => { - rl.question(question, answer => { - rl.close(); - resolve(answer); - }); - }); -} - -async function getMenuItem(installations) { - const menuItems = installations.map(info => ({ - title: info.patched ? "[MODIFIED] " + info.location : info.location, - info, - })); - - const result = await menu( - [ - ...menuItems, - { title: "Specify custom path", info: "custom" }, - { title: "Exit without patching", exit: true } - ], - { - header: "Select a Discord installation to patch:", - border: true, - helpMessage: - "Use the up/down arrow keys to select an option. " + - "Press ENTER to confirm.", - } - ); - - if (!result || !result.info || result.exit) { - console.log("No installation selected."); - process.exit(0); - } - - if (result.info === "custom") { - const customPath = await question("Please enter the path: "); - if (!customPath || !fs.existsSync(customPath)) { - console.log("No such Path or not specifed."); - process.exit(); - } - - const resourceDir = path.join(customPath, "resources"); - if (!fs.existsSync(path.join(resourceDir, "app.asar"))) { - console.log("Unsupported Install. resources/app.asar not found"); - process.exit(); - } - - const appDir = path.join(resourceDir, "app"); - result.info = { - branch: "unknown", - patched: fs.existsSync(appDir), - location: customPath, - versions: [{ - path: appDir, - name: null - }], - arch: process.platform === "linux" ? "linux" : "win32", - isFlatpak: false, - }; - } - - if (result.info.patched) { - const answer = await question( - "This installation has already been modified. Overwrite? [Y/n]: " - ); - - if (!["y", "yes", "yeah", ""].includes(answer.toLowerCase())) { - console.log("Not patching."); - process.exit(0); - } - } - - return result.info; -} - -function getWindowsDirs() { - const dirs = []; - for (const dir of fs.readdirSync(process.env.LOCALAPPDATA)) { - if (!BRANCH_NAMES.includes(dir)) continue; - - const location = path.join(process.env.LOCALAPPDATA, dir); - if (!fs.statSync(location).isDirectory()) continue; - - const appDirs = fs - .readdirSync(location, { withFileTypes: true }) - .filter(file => file.isDirectory()) - .filter(file => file.name.startsWith("app-")) - .map(file => path.join(location, file.name)); - - const versions = []; - let patched = false; - - for (const fqAppDir of appDirs) { - const resourceDir = path.join(fqAppDir, "resources"); - if (!fs.existsSync(path.join(resourceDir, "app.asar"))) { - continue; - } - const appDir = path.join(resourceDir, "app"); - if (fs.existsSync(appDir)) { - patched = true; - } - versions.push({ - path: appDir, - name: /app-([0-9.]+)/.exec(fqAppDir)[1], - }); - } - - if (appDirs.length) { - dirs.push({ - branch: dir, - patched, - location, - versions, - arch: "win32", - flatpak: false, - }); - } - } - return dirs; -} - -function getDarwinDirs() { - const dirs = []; - for (const dir of fs.readdirSync("/Applications")) { - if (!MACOS_DISCORD_DIRS.includes(dir)) continue; - - const location = path.join("/Applications", dir, "Contents"); - if (!fs.existsSync(location)) continue; - if (!fs.statSync(location).isDirectory()) continue; - - const appDirs = fs - .readdirSync(location, { withFileTypes: true }) - .filter(file => file.isDirectory()) - .filter(file => file.name.startsWith("Resources")) - .map(file => path.join(location, file.name)); - - const versions = []; - let patched = false; - - for (const resourceDir of appDirs) { - if (!fs.existsSync(path.join(resourceDir, "app.asar"))) { - continue; - } - const appDir = path.join(resourceDir, "app"); - if (fs.existsSync(appDir)) { - patched = true; - } - - versions.push({ - path: appDir, - name: null, // MacOS installs have no version number - }); - } - - if (appDirs.length) { - dirs.push({ - branch: dir, - patched, - location, - versions, - arch: "win32", - }); - } - } - return dirs; -} - -function getLinuxDirs() { - const dirs = []; - for (const dir of LINUX_DISCORD_DIRS) { - if (!fs.existsSync(dir)) continue; - for (const branch of fs.readdirSync(dir)) { - if (!BRANCH_NAMES.includes(branch)) continue; - - const location = path.join(dir, branch); - if (!fs.statSync(location).isDirectory()) continue; - - const isFlatpak = location.includes("/flatpak/"); - - let appDirs = []; - - if (isFlatpak) { - const fqDir = path.join(location, "current", "active", "files"); - if (!/com\.discordapp\.(\w+)\//.test(fqDir)) continue; - const branchName = /com\.discordapp\.(\w+)\//.exec(fqDir)[1]; - if (!Object.keys(FLATPAK_NAME_MAPPING).includes(branchName)) { - continue; - } - const appDir = path.join( - fqDir, - FLATPAK_NAME_MAPPING[branchName] - ); - - if (!fs.existsSync(appDir)) continue; - if (!fs.statSync(appDir).isDirectory()) continue; - - const resourceDir = path.join(appDir, "resources"); - - appDirs.push(resourceDir); - } else { - appDirs = fs - .readdirSync(location, { withFileTypes: true }) - .filter(file => file.isDirectory()) - .filter( - file => - file.name.startsWith("app-") || - file.name === "resources" - ) - .map(file => path.join(location, file.name)); - } - - const versions = []; - let patched = false; - - for (const resourceDir of appDirs) { - if (!fs.existsSync(path.join(resourceDir, "app.asar"))) { - continue; - } - const appDir = path.join(resourceDir, "app"); - if (fs.existsSync(appDir)) { - patched = true; - } - - const version = /app-([0-9.]+)/.exec(resourceDir); - - versions.push({ - path: appDir, - name: version && version.length > 1 ? version[1] : null, - }); - } - - if (appDirs.length) { - dirs.push({ - branch, - patched, - location, - versions, - arch: "linux", - isFlatpak, - }); - } - } - } - return dirs; -} - -module.exports = { - pathToBranch, - BRANCH_NAMES, - MACOS_DISCORD_DIRS, - LINUX_DISCORD_DIRS, - FLATPAK_NAME_MAPPING, - ENTRYPOINT, - question, - getMenuItem, - getWindowsDirs, - getDarwinDirs, - getLinuxDirs, -}; diff --git a/scripts/patcher/install.js b/scripts/patcher/install.js deleted file mode 100755 index 3d744a67f..000000000 --- a/scripts/patcher/install.js +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/node -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 . -*/ - -const path = require("path"); -const fs = require("fs"); -const { execSync } = require("child_process"); - -console.log("\nVencord Installer\n"); - -if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) { - console.log("You need to install dependencies first. Run:", "pnpm install --frozen-lockfile"); - process.exit(1); -} - -if (!fs.existsSync(path.join(process.cwd(), "dist", "patcher.js"))) { - console.log("You need to build the project first. Run:", "pnpm build"); - process.exit(1); -} - -const { - getMenuItem, - getWindowsDirs, - getDarwinDirs, - getLinuxDirs, - ENTRYPOINT, - question, - pathToBranch -} = require("./common"); - -switch (process.platform) { - case "win32": - install(getWindowsDirs()); - break; - case "darwin": - install(getDarwinDirs()); - break; - case "linux": - install(getLinuxDirs()); - break; - default: - console.log("Unknown OS"); - break; -} - -async function install(installations) { - const selected = await getMenuItem(installations); - - // Attempt to give flatpak perms - if (selected.isFlatpak) { - try { - const cwd = process.cwd(); - const globalCmd = `flatpak override ${selected.branch} --filesystem=${cwd}`; - const userCmd = `flatpak override --user ${selected.branch} --filesystem=${cwd}`; - const cmd = selected.location.startsWith("/home") - ? userCmd - : globalCmd; - execSync(cmd); - console.log("Gave write perms to Discord Flatpak."); - } catch (e) { - console.log("Failed to give write perms to Discord Flatpak."); - console.log( - "Try running this script as an administrator:", - "sudo pnpm inject" - ); - process.exit(1); - } - - const answer = await question( - `Would you like to allow ${selected.branch} to talk to org.freedesktop.Flatpak?\n` + - "This is essentially full host access but necessary to spawn git. Without it, the updater will not work\n" + - "Consider using the http based updater (using the gui installer) instead if you want to maintain the sandbox.\n" + - "[y/N]: " - ); - - if (["y", "yes", "yeah"].includes(answer.toLowerCase())) { - try { - const globalCmd = `flatpak override ${selected.branch} --talk-name=org.freedesktop.Flatpak`; - const userCmd = `flatpak override --user ${selected.branch} --talk-name=org.freedesktop.Flatpak`; - const cmd = selected.location.startsWith("/home") - ? userCmd - : globalCmd; - execSync(cmd); - console.log("Sucessfully gave talk permission"); - } catch (err) { - console.error("Failed to give talk permission\n", err); - } - } else { - console.log(`Not giving full host access. If you change your mind later, you can run:\nflatpak override ${selected.branch} --talk-name=org.freedesktop.Flatpak`); - } - } - - const useNewMethod = pathToBranch(selected.branch) !== "stable"; - - for (const version of selected.versions) { - - const dir = useNewMethod ? path.join(version.path, "..") : version.path; - - // Check if we have write perms to the install directory... - try { - fs.accessSync(selected.location, fs.constants.W_OK); - } catch (e) { - console.error("No write access to", selected.location); - console.error( - "Make sure Discord isn't running. If that doesn't work,", - "try running this script as an administrator:", - "sudo pnpm inject" - ); - process.exit(1); - } - if (useNewMethod) { - const appAsar = path.join(dir, "app.asar"); - const _appAsar = path.join(dir, "_app.asar"); - - if (fs.existsSync(_appAsar) && fs.existsSync(appAsar)) { - console.log("This copy of Discord already seems to be patched..."); - console.log("Try running `pnpm uninject` first."); - process.exit(1); - } - - try { - fs.renameSync(appAsar, _appAsar); - } catch (err) { - if (err.code === "EBUSY") { - console.error(selected.branch, "is still running. Make sure you fully close it before running this script."); - process.exit(1); - } - console.error("Failed to rename app.asar to _app.asar"); - throw err; - } - - try { - fs.mkdirSync(appAsar); - } catch (err) { - if (err.code === "EBUSY") { - console.error(selected.branch, "is still running. Make sure you fully close it before running this script."); - process.exit(1); - } - console.error("Failed to create app.asar folder"); - throw err; - } - - fs.writeFileSync( - path.join(appAsar, "index.js"), - `require("${ENTRYPOINT}");` - ); - fs.writeFileSync( - path.join(appAsar, "package.json"), - JSON.stringify({ - name: "discord", - main: "index.js", - }) - ); - - const requiredFiles = ["index.js", "package.json"]; - - if (requiredFiles.every(f => fs.existsSync(path.join(appAsar, f)))) { - console.log( - "Successfully patched", - version.name - ? `${selected.branch} ${version.name}` - : selected.branch - ); - } else { - console.log("Failed to patch", dir); - console.log("Files in directory:", fs.readdirSync(appAsar)); - } - - return; - } - if (fs.existsSync(dir) && fs.lstatSync(dir).isDirectory()) { - fs.rmSync(dir, { recursive: true }); - } - if (!fs.existsSync(dir)) { - fs.mkdirSync(dir, { recursive: true }); - } - - fs.writeFileSync( - path.join(dir, "index.js"), - `require("${ENTRYPOINT}");` - ); - fs.writeFileSync( - path.join(dir, "package.json"), - JSON.stringify({ - name: "discord", - main: "index.js", - }) - ); - - const requiredFiles = ["index.js", "package.json"]; - - if (requiredFiles.every(f => fs.existsSync(path.join(dir, f)))) { - console.log( - "Successfully patched", - version.name - ? `${selected.branch} ${version.name}` - : selected.branch - ); - } else { - console.log("Failed to patch", dir); - console.log("Files in directory:", fs.readdirSync(dir)); - } - } -} diff --git a/scripts/patcher/uninstall.js b/scripts/patcher/uninstall.js deleted file mode 100755 index ded6cf9c5..000000000 --- a/scripts/patcher/uninstall.js +++ /dev/null @@ -1,116 +0,0 @@ -#!/usr/bin/node -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 . -*/ - -const path = require("path"); -const fs = require("fs"); - -console.log("\nVencord Uninstaller\n"); - -if (!fs.existsSync(path.join(process.cwd(), "node_modules"))) { - console.log("You need to install dependencies first. Run:", "pnpm install --frozen-lockfile"); - process.exit(1); -} - -const { - getMenuItem, - getWindowsDirs, - getDarwinDirs, - getLinuxDirs, - pathToBranch, -} = require("./common"); - -switch (process.platform) { - case "win32": - uninstall(getWindowsDirs()); - break; - case "darwin": - uninstall(getDarwinDirs()); - break; - case "linux": - uninstall(getLinuxDirs()); - break; - default: - console.log("Unknown OS"); - break; -} - -async function uninstall(installations) { - const selected = await getMenuItem(installations); - - const useNewMethod = pathToBranch(selected.branch) !== "stable"; - - for (const version of selected.versions) { - const dir = useNewMethod ? path.join(version.path, "..") : version.path; - - // Check if we have write perms to the install directory... - try { - fs.accessSync(selected.location, fs.constants.W_OK); - } catch (e) { - console.error("No write access to", selected.location); - console.error( - "Make sure Discord isn't running. If that doesn't work,", - "try running this script as an administrator:", - "sudo pnpm uninject" - ); - process.exit(1); - } - if (useNewMethod) { - if (!fs.existsSync(path.join(dir, "_app.asar"))) { - console.error( - "Original app.asar (_app.asar) doesn't exist.", - "Is your Discord installation corrupt? Try reinstalling Discord." - ); - process.exit(1); - } - if (fs.existsSync(path.join(dir, "app.asar"))) { - try { - fs.rmSync(path.join(dir, "app.asar"), { force: true, recursive: true }); - } catch (err) { - console.error("Failed to delete app.asar folder"); - throw err; - } - } - try { - fs.renameSync( - path.join(dir, "_app.asar"), - path.join(dir, "app.asar") - ); - } catch (err) { - console.error("Failed to rename _app.asar to app.asar"); - throw err; - } - console.log( - "Successfully unpatched", - version.name - ? `${selected.branch} ${version.name}` - : selected.branch - ); - return; - } - if (fs.existsSync(dir)) { - fs.rmSync(dir, { recursive: true }); - } - console.log( - "Successfully unpatched", - version.name - ? `${selected.branch} ${version.name}` - : selected.branch - ); - } -} diff --git a/scripts/runInstaller.mjs b/scripts/runInstaller.mjs new file mode 100644 index 000000000..a74d01fa8 --- /dev/null +++ b/scripts/runInstaller.mjs @@ -0,0 +1,97 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 { execFileSync } from "child_process"; +import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; +import { dirname, join } from "path"; +import { Readable } from "stream"; +import { finished } from "stream/promises"; +import { fileURLToPath } from "url"; + +const BASE_URL = "https://github.com/Vencord/Installer/releases/latest/download/"; + +const DIST_DIR = join(dirname(fileURLToPath(import.meta.url)), ".."); +const FILE_DIR = join(DIST_DIR, "dist", "Installer"); +const ETAG_FILE = join(FILE_DIR, "etag.txt"); + +function getFilename() { + switch (process.platform) { + case "win32": + return "VencordInstaller.exe"; + case "darwin": + // return "VencordInstaller.MacOS.zip"; + throw new Error("PR Mac support if you want it. Or use a better OS that doesn't suck"); + case "linux": + return "VencordInstaller-" + (process.env.WAYLAND_DISPLAY ? "wayland" : "x11"); + default: + throw new Error("Unsupported platform: " + process.platform); + } +} + +async function ensureBinary() { + const filename = getFilename(); + console.log("Downloading " + filename); + + mkdirSync(FILE_DIR, { recursive: true }); + + const installerFile = join(FILE_DIR, filename); + const etag = existsSync(installerFile) && existsSync(ETAG_FILE) ? readFileSync(ETAG_FILE, "utf-8") : null; + + const res = await fetch(BASE_URL + filename, { + headers: { + "User-Agent": "Vencord (https://github.com/Vendicated/Vencord)", + "If-None-Match": etag + } + }); + if (res.status === 304) { + console.log("Up to date, not redownloading!"); + return installerFile; + } + + if (!res.ok) { + throw new Error(`Failed to download installer: ${res.status} ${res.statusText}`); + } + + const newEtag = res.headers.get("etag"); + writeFileSync(ETAG_FILE, newEtag); + + // WHY DOES NODE FETCH RETURN A WEB STREAM OH MY GOD + const body = Readable.fromWeb(res.body); + await finished(body.pipe(createWriteStream(installerFile, { + mode: 0o755, + autoClose: true + }))); + + console.log("Finished downloading!"); + + return installerFile; +} + + +console.log("Now running Installer..."); + +const installerBin = await ensureBinary(); + +execFileSync(installerBin, { + stdio: "inherit", + env: { + ...process.env, + VENCORD_USER_DATA_DIR: DIST_DIR, + VENCORD_DEV_INSTALL: "1" + } +}); From ea748dfb605386b80a4919183ad6fa9249a82e21 Mon Sep 17 00:00:00 2001 From: Justice Almanzar Date: Fri, 13 Jan 2023 17:15:45 -0500 Subject: [PATCH 031/114] feat: Typesafe Settings Definitions (#403) Co-authored-by: Ven --- src/api/settings.ts | 19 ++- src/components/PluginSettings/PluginModal.tsx | 1 + .../components/SettingBooleanComponent.tsx | 6 +- .../components/SettingNumericComponent.tsx | 6 +- .../components/SettingSelectComponent.tsx | 6 +- .../components/SettingSliderComponent.tsx | 6 +- .../components/SettingTextComponent.tsx | 6 +- .../PluginSettings/components/index.ts | 3 +- src/plugins/index.ts | 12 +- .../shikiCodeblocks/hooks/useShikiSettings.ts | 14 +- src/plugins/shikiCodeblocks/index.ts | 104 +------------ src/plugins/shikiCodeblocks/settings.ts | 123 +++++++++++++++ src/plugins/shikiCodeblocks/types.ts | 14 -- src/plugins/shikiCodeblocks/utils/misc.ts | 7 +- src/utils/types.ts | 141 +++++++++++++----- 15 files changed, 288 insertions(+), 180 deletions(-) create mode 100644 src/plugins/shikiCodeblocks/settings.ts diff --git a/src/api/settings.ts b/src/api/settings.ts index 384647c53..9bae8b73b 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -19,7 +19,7 @@ import IpcEvents from "@utils/IpcEvents"; import Logger from "@utils/Logger"; import { mergeDefaults } from "@utils/misc"; -import { OptionType } from "@utils/types"; +import { DefinedSettings, OptionType, SettingsChecks, SettingsDefinition } from "@utils/types"; import { React } from "@webpack/common"; import plugins from "~plugins"; @@ -146,6 +146,7 @@ export const Settings = makeProxy(settings); * @param paths An optional list of paths to whitelist for rerenders * @returns Settings */ +// TODO: Representing paths as essentially "string[].join('.')" wont allow dots in paths, change to "paths?: string[][]" later export function useSettings(paths?: string[]) { const [, forceUpdate] = React.useReducer(() => ({}), {}); @@ -200,3 +201,19 @@ export function migratePluginSettings(name: string, ...oldNames: string[]) { } } } + +export function definePluginSettings>(def: D, checks?: C) { + const definedSettings: DefinedSettings = { + get store() { + if (!definedSettings.pluginName) throw new Error("Cannot access settings before plugin is initialized"); + return Settings.plugins[definedSettings.pluginName] as any; + }, + use: settings => useSettings( + settings?.map(name => `plugins.${definedSettings.pluginName}.${name}`) + ).plugins[definedSettings.pluginName] as any, + def, + checks: checks ?? {}, + pluginName: "", + }; + return definedSettings; +} diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 46568505c..43e1d31ab 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -144,6 +144,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti onChange={onChange} onError={onError} pluginSettings={pluginSettings} + definedSettings={plugin.settings} /> ); }); diff --git a/src/components/PluginSettings/components/SettingBooleanComponent.tsx b/src/components/PluginSettings/components/SettingBooleanComponent.tsx index 0aaafa049..c90af1684 100644 --- a/src/components/PluginSettings/components/SettingBooleanComponent.tsx +++ b/src/components/PluginSettings/components/SettingBooleanComponent.tsx @@ -21,7 +21,7 @@ import { Forms, React, Select } from "@webpack/common"; import { ISettingElementProps } from "."; -export function SettingBooleanComponent({ option, pluginSettings, id, onChange, onError }: ISettingElementProps) { +export function SettingBooleanComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) { const def = pluginSettings[id] ?? option.default; const [state, setState] = React.useState(def ?? false); @@ -37,7 +37,7 @@ export function SettingBooleanComponent({ option, pluginSettings, id, onChange, ]; function handleChange(newValue: boolean): void { - const isValid = (option.isValid && option.isValid(newValue)) ?? true; + const isValid = option.isValid?.call(definedSettings, newValue) ?? true; if (typeof isValid === "string") setError(isValid); else if (!isValid) setError("Invalid input provided."); else { @@ -51,7 +51,7 @@ export function SettingBooleanComponent({ option, pluginSettings, id, onChange, {option.description} ) { +export function SettingSliderComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) { const def = pluginSettings[id] ?? option.default; const [error, setError] = React.useState(null); @@ -39,7 +39,7 @@ export function SettingSliderComponent({ option, pluginSettings, id, onChange, o }, [error]); function handleChange(newValue: number): void { - const isValid = (option.isValid && option.isValid(newValue)) ?? true; + const isValid = option.isValid?.call(definedSettings, newValue) ?? true; if (typeof isValid === "string") setError(isValid); else if (!isValid) setError("Invalid input provided."); else { @@ -52,7 +52,7 @@ export function SettingSliderComponent({ option, pluginSettings, id, onChange, o {option.description} ) { +export function SettingTextComponent({ option, pluginSettings, definedSettings, id, onChange, onError }: ISettingElementProps) { const [state, setState] = React.useState(pluginSettings[id] ?? option.default ?? null); const [error, setError] = React.useState(null); @@ -30,7 +30,7 @@ export function SettingTextComponent({ option, pluginSettings, id, onChange, onE }, [error]); function handleChange(newValue) { - const isValid = (option.isValid && option.isValid(newValue)) ?? true; + const isValid = option.isValid?.call(definedSettings, newValue) ?? true; if (typeof isValid === "string") setError(isValid); else if (!isValid) setError("Invalid input provided."); else { @@ -47,7 +47,7 @@ export function SettingTextComponent({ option, pluginSettings, id, onChange, onE value={state} onChange={handleChange} placeholder={option.placeholder ?? "Enter a value"} - disabled={option.disabled?.() ?? false} + disabled={option.disabled?.call(definedSettings) ?? false} {...option.componentProps} /> {error && {error}} diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts index d44fb386f..52745ea49 100644 --- a/src/components/PluginSettings/components/index.ts +++ b/src/components/PluginSettings/components/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { PluginOptionBase } from "@utils/types"; +import { DefinedSettings, PluginOptionBase } from "@utils/types"; export interface ISettingElementProps { option: T; @@ -27,6 +27,7 @@ export interface ISettingElementProps { }; id: string; onError(hasError: boolean): void; + definedSettings?: DefinedSettings; } export * from "./BadgeComponent"; diff --git a/src/plugins/index.ts b/src/plugins/index.ts index c0325d41d..6ac221da5 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -60,7 +60,16 @@ for (const p of pluginsValues) { }); } -for (const p of pluginsValues) +for (const p of pluginsValues) { + if (p.settings) { + p.settings.pluginName = p.name; + p.options ??= {}; + for (const [name, def] of Object.entries(p.settings.def)) { + const checks = p.settings.checks?.[name]; + p.options[name] = { ...def, ...checks }; + } + } + if (p.patches && isPluginEnabled(p.name)) { for (const patch of p.patches) { patch.plugin = p.name; @@ -69,6 +78,7 @@ for (const p of pluginsValues) patches.push(patch); } } +} export const startAllPlugins = traceFunction("startAllPlugins", function startAllPlugins() { for (const name in Plugins) diff --git a/src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts b/src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts index 50b0fc978..22954ce1a 100644 --- a/src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts +++ b/src/plugins/shikiCodeblocks/hooks/useShikiSettings.ts @@ -16,25 +16,25 @@ * along with this program. If not, see . */ -import { useSettings } from "@api/settings"; +import { PartialExcept } from "@utils/types"; import { React } from "@webpack/common"; import { shiki } from "../api/shiki"; -import { ShikiSettings } from "../types"; +import { settings as pluginSettings, ShikiSettings } from "../settings"; -export function useShikiSettings(settingKeys: (keyof ShikiSettings)[], overrides?: Record) { - const settings = useSettings(settingKeys.map(key => `plugins.ShikiCodeblocks.${key}`)).plugins.ShikiCodeblocks as ShikiSettings; +export function useShikiSettings(settingKeys: F[], overrides?: Partial) { + const settings: Partial = pluginSettings.use(settingKeys); const [isLoading, setLoading] = React.useState(false); - const withOverrides = { ...settings, ...overrides }; + const withOverrides = { ...settings, ...overrides } as PartialExcept; const themeUrl = withOverrides.customTheme || withOverrides.theme; if (overrides) { - const willChangeTheme = shiki.currentThemeUrl && themeUrl !== shiki.currentThemeUrl; + const willChangeTheme = shiki.currentThemeUrl && themeUrl && themeUrl !== shiki.currentThemeUrl; const noOverrides = Object.keys(overrides).length === 0; if (isLoading && (!willChangeTheme || noOverrides)) setLoading(false); - if ((!isLoading && willChangeTheme)) { + if (!isLoading && willChangeTheme) { setLoading(true); shiki.setTheme(themeUrl); } diff --git a/src/plugins/shikiCodeblocks/index.ts b/src/plugins/shikiCodeblocks/index.ts index 428a2735e..58e55b4e5 100644 --- a/src/plugins/shikiCodeblocks/index.ts +++ b/src/plugins/shikiCodeblocks/index.ts @@ -18,26 +18,19 @@ import "./shiki.css"; -import { disableStyle, enableStyle } from "@api/Styles"; +import { enableStyle } from "@api/Styles"; import { Devs } from "@utils/constants"; -import { parseUrl } from "@utils/misc"; -import { wordsFromPascal, wordsToTitle } from "@utils/text"; -import definePlugin, { OptionType } from "@utils/types"; +import definePlugin from "@utils/types"; import previewExampleText from "~fileContent/previewExample.tsx"; -import { Settings } from "../../Vencord"; import { shiki } from "./api/shiki"; -import { themes } from "./api/themes"; import { createHighlighter } from "./components/Highlighter"; import deviconStyle from "./devicon.css?managed"; -import { DeviconSetting, HljsSetting, ShikiSettings } from "./types"; +import { settings } from "./settings"; +import { DeviconSetting } from "./types"; import { clearStyles } from "./utils/createStyle"; -const themeNames = Object.keys(themes); - -const getSettings = () => Settings.plugins.ShikiCodeblocks as ShikiSettings; - export default definePlugin({ name: "ShikiCodeblocks", description: "Brings vscode-style codeblocks into Discord, powered by Shiki", @@ -52,10 +45,10 @@ export default definePlugin({ }, ], start: async () => { - if (getSettings().useDevIcon !== DeviconSetting.Disabled) + if (settings.store.useDevIcon !== DeviconSetting.Disabled) enableStyle(deviconStyle); - await shiki.init(getSettings().customTheme || getSettings().theme); + await shiki.init(settings.store.customTheme || settings.store.theme); }, stop: () => { shiki.destroy(); @@ -67,90 +60,7 @@ export default definePlugin({ isPreview: true, tempSettings, }), - options: { - theme: { - type: OptionType.SELECT, - description: "Default themes", - options: themeNames.map(themeName => ({ - label: wordsToTitle(wordsFromPascal(themeName)), - value: themes[themeName], - default: themes[themeName] === themes.DarkPlus, - })), - disabled: () => !!getSettings().customTheme, - onChange: shiki.setTheme, - }, - customTheme: { - type: OptionType.STRING, - description: "A link to a custom vscode theme", - placeholder: themes.MaterialCandy, - isValid: value => { - if (!value) return true; - const url = parseUrl(value); - if (!url) return "Must be a valid URL"; - - if (!url.pathname.endsWith(".json")) return "Must be a json file"; - - return true; - }, - onChange: value => shiki.setTheme(value || getSettings().theme), - }, - tryHljs: { - type: OptionType.SELECT, - description: "Use the more lightweight default Discord highlighter and theme.", - options: [ - { - label: "Never", - value: HljsSetting.Never, - }, - { - label: "Prefer Shiki instead of Highlight.js", - value: HljsSetting.Secondary, - default: true, - }, - { - label: "Prefer Highlight.js instead of Shiki", - value: HljsSetting.Primary, - }, - { - label: "Always", - value: HljsSetting.Always, - }, - ], - }, - useDevIcon: { - type: OptionType.SELECT, - description: "How to show language icons on codeblocks", - options: [ - { - label: "Disabled", - value: DeviconSetting.Disabled, - }, - { - label: "Colorless", - value: DeviconSetting.Greyscale, - default: true, - }, - { - label: "Colored", - value: DeviconSetting.Color, - }, - ], - onChange: (newValue: DeviconSetting) => { - if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle); - else enableStyle(deviconStyle); - }, - }, - bgOpacity: { - type: OptionType.SLIDER, - description: "Background opacity", - markers: [0, 20, 40, 60, 80, 100], - default: 100, - componentProps: { - stickToMarkers: false, - onValueRender: null, // Defaults to percentage - }, - }, - }, + settings, // exports shiki, diff --git a/src/plugins/shikiCodeblocks/settings.ts b/src/plugins/shikiCodeblocks/settings.ts new file mode 100644 index 000000000..ff5afc2e7 --- /dev/null +++ b/src/plugins/shikiCodeblocks/settings.ts @@ -0,0 +1,123 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 { definePluginSettings } from "@api/settings"; +import { disableStyle, enableStyle } from "@api/Styles"; +import { parseUrl } from "@utils/misc"; +import { wordsFromPascal, wordsToTitle } from "@utils/text"; +import { OptionType } from "@utils/types"; + +import { shiki } from "./api/shiki"; +import { themes } from "./api/themes"; +import deviconStyle from "./devicon.css?managed"; +import { DeviconSetting, HljsSetting } from "./types"; + +const themeNames = Object.keys(themes) as (keyof typeof themes)[]; + +export type ShikiSettings = typeof settings.store; +export const settings = definePluginSettings({ + theme: { + type: OptionType.SELECT, + description: "Default themes", + options: themeNames.map(themeName => ({ + label: wordsToTitle(wordsFromPascal(themeName)), + value: themes[themeName], + default: themes[themeName] === themes.DarkPlus, + })), + onChange: shiki.setTheme, + }, + customTheme: { + type: OptionType.STRING, + description: "A link to a custom vscode theme", + placeholder: themes.MaterialCandy, + onChange: value => { + shiki.setTheme(value || settings.store.theme); + }, + }, + tryHljs: { + type: OptionType.SELECT, + description: "Use the more lightweight default Discord highlighter and theme.", + options: [ + { + label: "Never", + value: HljsSetting.Never, + }, + { + label: "Prefer Shiki instead of Highlight.js", + value: HljsSetting.Secondary, + default: true, + }, + { + label: "Prefer Highlight.js instead of Shiki", + value: HljsSetting.Primary, + }, + { + label: "Always", + value: HljsSetting.Always, + }, + ], + }, + useDevIcon: { + type: OptionType.SELECT, + description: "How to show language icons on codeblocks", + options: [ + { + label: "Disabled", + value: DeviconSetting.Disabled, + }, + { + label: "Colorless", + value: DeviconSetting.Greyscale, + default: true, + }, + { + label: "Colored", + value: DeviconSetting.Color, + }, + ], + onChange: (newValue: DeviconSetting) => { + if (newValue === DeviconSetting.Disabled) disableStyle(deviconStyle); + else enableStyle(deviconStyle); + }, + }, + bgOpacity: { + type: OptionType.SLIDER, + description: "Background opacity", + markers: [0, 20, 40, 60, 80, 100], + default: 100, + componentProps: { + stickToMarkers: false, + onValueRender: null, // Defaults to percentage + }, + }, +}, { + theme: { + disabled() { return !!this.store.customTheme; }, + }, + customTheme: { + isValid(value) { + if (!value) return true; + const url = parseUrl(value); + if (!url) return "Must be a valid URL"; + + if (!url.pathname.endsWith(".json")) return "Must be a json file"; + + return true; + }, + } +}); diff --git a/src/plugins/shikiCodeblocks/types.ts b/src/plugins/shikiCodeblocks/types.ts index ee5aa9e64..e724ea438 100644 --- a/src/plugins/shikiCodeblocks/types.ts +++ b/src/plugins/shikiCodeblocks/types.ts @@ -23,8 +23,6 @@ import type { IThemeRegistration, } from "@vap/shiki"; -import type { Settings } from "../../Vencord"; - /** This must be atleast a subset of the `@vap/shiki-worker` spec */ export type ShikiSpec = { setOnigasm: ({ wasm }: { wasm: string; }) => Promise; @@ -64,15 +62,3 @@ export enum DeviconSetting { Greyscale = "GREYSCALE", Color = "COLOR" } - -type CommonSettings = { - [K in keyof Settings["plugins"][string]as K extends `${infer V}` ? K : never]: Settings["plugins"][string][K]; -}; - -export interface ShikiSettings extends CommonSettings { - theme: string; - customTheme: string; - tryHljs: HljsSetting; - useDevIcon: DeviconSetting; - bgOpacity: number; -} diff --git a/src/plugins/shikiCodeblocks/utils/misc.ts b/src/plugins/shikiCodeblocks/utils/misc.ts index fefe938fc..e0c526342 100644 --- a/src/plugins/shikiCodeblocks/utils/misc.ts +++ b/src/plugins/shikiCodeblocks/utils/misc.ts @@ -21,7 +21,7 @@ import { hljs } from "@webpack/common"; import { resolveLang } from "../api/languages"; import { HighlighterProps } from "../components/Highlighter"; -import { HljsSetting, ShikiSettings } from "../types"; +import { HljsSetting } from "../types"; export const cl = classNameFactory("shiki-"); @@ -30,7 +30,7 @@ export const shouldUseHljs = ({ tryHljs, }: { lang: HighlighterProps["lang"], - tryHljs: ShikiSettings["tryHljs"], + tryHljs: HljsSetting, }) => { const hljsLang = lang ? hljs?.getLanguage?.(lang) : null; const shikiLang = lang ? resolveLang(lang) : null; @@ -45,7 +45,6 @@ export const shouldUseHljs = ({ return !langName && !!hljsLang; case HljsSetting.Never: return false; + default: return false; } - - return false; }; diff --git a/src/utils/types.ts b/src/utils/types.ts index d3083fcab..5ab685761 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -81,8 +81,14 @@ export interface PluginDef { target?: "WEB" | "DESKTOP" | "BOTH"; /** * Optionally provide settings that the user can configure in the Plugins tab of settings. + * @deprecated Use `settings` instead */ + // TODO: Remove when everything is migrated to `settings` options?: Record; + /** + * Optionally provide settings that the user can configure in the Plugins tab of settings. + */ + settings?: DefinedSettings; /** * Check that this returns true before allowing a save to complete. * If a string is returned, show the error to the user. @@ -107,19 +113,25 @@ export enum OptionType { COMPONENT, } -export type PluginOptionsItem = - | PluginOptionString - | PluginOptionNumber - | PluginOptionBoolean - | PluginOptionSelect - | PluginOptionSlider - | PluginOptionComponent; +export type SettingsDefinition = Record; +export type SettingsChecks = { + [K in keyof D]?: D[K] extends PluginSettingComponentDef ? IsDisabled> : + (IsDisabled> & IsValid, DefinedSettings>); +}; -export interface PluginOptionBase { +export type PluginSettingDef = ( + | PluginSettingStringDef + | PluginSettingNumberDef + | PluginSettingBooleanDef + | PluginSettingSelectDef + | PluginSettingSliderDef + | PluginSettingComponentDef +) & PluginSettingCommon; + +export interface PluginSettingCommon { description: string; placeholder?: string; onChange?(newValue: any): void; - disabled?(): boolean; restartNeeded?: boolean; componentProps?: Record; /** @@ -127,49 +139,47 @@ export interface PluginOptionBase { */ target?: "WEB" | "DESKTOP" | "BOTH"; } - -export interface PluginOptionString extends PluginOptionBase { - type: OptionType.STRING; +interface IsDisabled { + /** + * Checks if this setting should be disabled + */ + disabled?(this: D): boolean; +} +interface IsValid { /** * Prevents the user from saving settings if this is false or a string */ - isValid?(value: string): boolean | string; + isValid?(this: D, value: T): boolean | string; +} + +export interface PluginSettingStringDef { + type: OptionType.STRING; default?: string; } - -export interface PluginOptionNumber extends PluginOptionBase { - type: OptionType.NUMBER | OptionType.BIGINT; - /** - * Prevents the user from saving settings if this is false or a string - */ - isValid?(value: number | BigInt): boolean | string; +export interface PluginSettingNumberDef { + type: OptionType.NUMBER; default?: number; } - -export interface PluginOptionBoolean extends PluginOptionBase { +export interface PluginSettingBigIntDef { + type: OptionType.BIGINT; + default?: BigInt; +} +export interface PluginSettingBooleanDef { type: OptionType.BOOLEAN; - /** - * Prevents the user from saving settings if this is false or a string - */ - isValid?(value: boolean): boolean | string; default?: boolean; } -export interface PluginOptionSelect extends PluginOptionBase { +export interface PluginSettingSelectDef { type: OptionType.SELECT; - /** - * Prevents the user from saving settings if this is false or a string - */ - isValid?(value: PluginOptionSelectOption): boolean | string; - options: PluginOptionSelectOption[]; + options: readonly PluginSettingSelectOption[]; } -export interface PluginOptionSelectOption { +export interface PluginSettingSelectOption { label: string; value: string | number | boolean; default?: boolean; } -export interface PluginOptionSlider extends PluginOptionBase { +export interface PluginSettingSliderDef { type: OptionType.SLIDER; /** * All the possible values in the slider. Needs at least two values. @@ -183,10 +193,6 @@ export interface PluginOptionSlider extends PluginOptionBase { * If false, allow users to select values in-between your markers. */ stickToMarkers?: boolean; - /** - * Prevents the user from saving settings if this is false or a string - */ - isValid?(value: number): boolean | string; } interface IPluginOptionComponentProps { @@ -206,12 +212,67 @@ interface IPluginOptionComponentProps { /** * The options object */ - option: PluginOptionComponent; + option: PluginSettingComponentDef; } -export interface PluginOptionComponent extends PluginOptionBase { +export interface PluginSettingComponentDef { type: OptionType.COMPONENT; component: (props: IPluginOptionComponentProps) => JSX.Element; } +/** Maps a `PluginSettingDef` to its value type */ +type PluginSettingType = O extends PluginSettingStringDef ? string : + O extends PluginSettingNumberDef ? number : + O extends PluginSettingBigIntDef ? BigInt : + O extends PluginSettingBooleanDef ? boolean : + O extends PluginSettingSelectDef ? O["options"][number]["value"] : + O extends PluginSettingSliderDef ? number : + O extends PluginSettingComponentDef ? any : + never; + +type SettingsStore = { + [K in keyof D]: PluginSettingType; +}; + +/** An instance of defined plugin settings */ +export interface DefinedSettings = {}> { + /** Shorthand for `Vencord.Settings.plugins.PluginName`, but with typings */ + store: SettingsStore; + /** + * React hook for getting the settings for this plugin + * @param filter optional filter to avoid rerenders for irrelavent settings + */ + use>(filter?: F[]): Pick, F>; + /** Definitions of each setting */ + def: D; + /** Setting methods with return values that could rely on other settings */ + checks: C; + /** + * Name of the plugin these settings belong to, + * will be an empty string until plugin is initialized + */ + pluginName: string; +} + +export type PartialExcept = Partial & Required>; + export type IpcRes = { ok: true; value: V; } | { ok: false, error: any; }; + +/* -------------------------------------------- */ +/* Legacy Options Types */ +/* -------------------------------------------- */ + +export type PluginOptionBase = PluginSettingCommon & IsDisabled; +export type PluginOptionsItem = + | PluginOptionString + | PluginOptionNumber + | PluginOptionBoolean + | PluginOptionSelect + | PluginOptionSlider + | PluginOptionComponent; +export type PluginOptionString = PluginSettingStringDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionNumber = (PluginSettingNumberDef | PluginSettingBigIntDef) & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionBoolean = PluginSettingBooleanDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionSelect = PluginSettingSelectDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionSlider = PluginSettingSliderDef & PluginSettingCommon & IsDisabled & IsValid; +export type PluginOptionComponent = PluginSettingComponentDef & PluginSettingCommon; From 32cdb6388527c109945c69b4c9edff42033e8a93 Mon Sep 17 00:00:00 2001 From: Nico Date: Fri, 13 Jan 2023 23:18:12 +0100 Subject: [PATCH 032/114] fix(vcDoubleClick): fix functionality (#410) Co-authored-by: Ven --- src/plugins/forceOwnerCrown.ts | 5 +---- src/plugins/vcDoubleClick.ts | 15 +++++---------- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/plugins/forceOwnerCrown.ts b/src/plugins/forceOwnerCrown.ts index 9345ea75b..0c1df4780 100644 --- a/src/plugins/forceOwnerCrown.ts +++ b/src/plugins/forceOwnerCrown.ts @@ -18,10 +18,7 @@ import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; -import { waitFor } from "@webpack"; - -let GuildStore; -waitFor(["getGuild"], m => GuildStore = m); +import { GuildStore } from "@webpack/common"; export default definePlugin({ name: "ForceOwnerCrown", diff --git a/src/plugins/vcDoubleClick.ts b/src/plugins/vcDoubleClick.ts index de573f1c4..1985107c3 100644 --- a/src/plugins/vcDoubleClick.ts +++ b/src/plugins/vcDoubleClick.ts @@ -39,19 +39,15 @@ export default definePlugin({ // e.detail since instead of the event they pass the channel. // do this timer workaround instead replacement: [ - // voice channels + // voice/stage channels { - match: /onClick:(.*)function\(\)\{(e\.handleClick.+?)}/g, - replace: "onClick:$1function(){Vencord.Plugins.plugins.VoiceChatDoubleClick.schedule(()=>{$2}, e)}", + match: /onClick:function\(\)\{(e\.handleClick.+?)}/g, + replace: "onClick:function(){Vencord.Plugins.plugins.VoiceChatDoubleClick.schedule(()=>{$1},e)}", }, - // stage channels - { - match: /onClick:(.{0,15})this\.handleClick,/g, - replace: "onClick:$1(...args)=>Vencord.Plugins.plugins.VoiceChatDoubleClick.schedule(()=>{this.handleClick(...args);}, args[0]),", - } ], }, { + // channel mentions find: 'className:"channelMention",iconType:(', replacement: { match: /onClick:(.{1,3}),/, @@ -61,8 +57,7 @@ export default definePlugin({ ], schedule(cb: () => void, e: any) { - // support from stage and voice channels patch - const id = e?.id ?? e.props.channel.id as string; + const id = e.props.channel.id as string; if (SelectedChannelStore.getVoiceChannelId() === id) { cb(); return; From 6329499b1d589e8c5650565c640ca2a8da41f410 Mon Sep 17 00:00:00 2001 From: Dominik Date: Fri, 13 Jan 2023 23:24:51 +0100 Subject: [PATCH 033/114] git updater: Fix macOS (#391) Co-authored-by: Ven --- .gitignore | 1 + src/ipcMain/updater/git.ts | 8 +++++--- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 7bd751cb9..135673a6d 100644 --- a/.gitignore +++ b/.gitignore @@ -5,6 +5,7 @@ node_modules vencord_installer .idea +.DS_Store yarn.lock package-lock.json diff --git a/src/ipcMain/updater/git.ts b/src/ipcMain/updater/git.ts index 20cc5b1f0..e787b8f46 100644 --- a/src/ipcMain/updater/git.ts +++ b/src/ipcMain/updater/git.ts @@ -30,6 +30,8 @@ const execFile = promisify(cpExecFile); const isFlatpak = Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord")); +if (process.platform === "darwin") process.env.PATH = `/usr/local/bin:${process.env.PATH}`; + function git(...args: string[]) { const opts = { cwd: VENCORD_SRC_DIR }; @@ -66,10 +68,10 @@ async function pull() { async function build() { const opts = { cwd: VENCORD_SRC_DIR }; - let res; + const command = isFlatpak ? "flatpak-spawn" : "node"; + const args = isFlatpak ? ["--host", "node", "scripts/build/build.mjs"] : ["scripts/build/build.mjs"]; - if (isFlatpak) res = await execFile("flatpak-spawn", ["--host", "node", "scripts/build/build.mjs"], opts); - else res = await execFile("node", ["scripts/build/build.mjs"], opts); + const res = await execFile(command, args, opts); return !res.stderr.includes("Build failed"); } From 7582feb60352f19b7f8f6750c5be624bbf6baafa Mon Sep 17 00:00:00 2001 From: Jeroen Claassens Date: Fri, 13 Jan 2023 23:59:31 +0100 Subject: [PATCH 034/114] feat(messageActions): make features toggleable (#373) Co-authored-by: Ven --- src/plugins/messageActions.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/plugins/messageActions.ts b/src/plugins/messageActions.ts index df4d01611..b71a9f162 100644 --- a/src/plugins/messageActions.ts +++ b/src/plugins/messageActions.ts @@ -1,6 +1,6 @@ /* * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors + * Copyright (c) 2023 Vendicated and contributors * * 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 @@ -19,7 +19,7 @@ import { addClickListener, removeClickListener } from "@api/MessageEvents"; import { migratePluginSettings } from "@api/settings"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findLazy } from "@webpack"; import { UserStore } from "@webpack/common"; @@ -35,6 +35,19 @@ export default definePlugin({ authors: [Devs.Ven], dependencies: ["MessageEventsAPI"], + options: { + enableDeleteOnClick: { + type: OptionType.BOOLEAN, + description: "Enable delete on click", + default: true + }, + enableDoubleClickToEdit: { + type: OptionType.BOOLEAN, + description: "Enable double click to edit", + default: true + } + }, + start() { const MessageActions = findByPropsLazy("deleteMessage", "startEditMessage"); const PermissionStore = findByPropsLazy("can", "initialize"); @@ -47,11 +60,11 @@ export default definePlugin({ this.onClick = addClickListener((msg, chan, event) => { const isMe = msg.author.id === UserStore.getCurrentUser().id; if (!isDeletePressed) { - if (isMe && event.detail >= 2 && !EditStore.isEditing(chan.id, msg.id)) { + if (Vencord.Settings.plugins.MessageClickActions.enableDoubleClickToEdit && (isMe && event.detail >= 2 && !EditStore.isEditing(chan.id, msg.id))) { MessageActions.startEditMessage(chan.id, msg.id, msg.content); event.preventDefault(); } - } else if (isMe || PermissionStore.can(Permissions.MANAGE_MESSAGES, chan)) { + } else if (Vencord.Settings.plugins.MessageClickActions.enableDeleteOnClick && (isMe || PermissionStore.can(Permissions.MANAGE_MESSAGES, chan))) { MessageActions.deleteMessage(chan.id, msg.id); event.preventDefault(); } From 0e5b8b07c99f3ab54c576a58ac3f59721d4f9904 Mon Sep 17 00:00:00 2001 From: Swishilicous Date: Fri, 13 Jan 2023 17:25:24 -0600 Subject: [PATCH 035/114] make plugin cards prettier (#389) Co-authored-by: Ven --- src/components/PluginSettings/index.tsx | 7 ++----- src/components/PluginSettings/styles.css | 19 ++++++++++++++++--- 2 files changed, 18 insertions(+), 8 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 687b9ee65..3b283d044 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -152,14 +152,11 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe onChange={toggleEnabled} disabled={disabled} value={isEnabled()} - note={{plugin.description}} + note={{plugin.description}} hideBorder={true} > - + {plugin.name}{isNew && } +
+ )} + + ); +} + +export default definePlugin({ + name: "SilentTyping", + authors: [Devs.Ven, Devs.dzshn], + description: "Hide that you are typing", + patches: [ + { + find: "startTyping:", + replacement: { + match: /startTyping:.+?,stop/, + replace: "startTyping:$self.startTyping,stop" + } + }, + { + find: ".activeCommandOption", + predicate: () => settings.store.showIcon, + replacement: { + match: /\i=\i\.activeCommand,\i=\i\.activeCommandOption,.{1,133}(.)=\[\];/, + replace: "$&;$1.push($self.chatBarIcon());", + } + }, + ], + dependencies: ["CommandsAPI"], + settings, + commands: [{ + name: "silenttype", + description: "Toggle whether you're hiding that you're typing or not.", + inputType: ApplicationCommandInputType.BUILT_IN, + options: [ + { + name: "value", + description: "whether to hide or not that you're typing (default is toggle)", + required: false, + type: ApplicationCommandOptionType.BOOLEAN, + }, + ], + execute: async (args, ctx) => { + settings.store.isEnabled = !!findOption(args, "value", !settings.store.isEnabled); + sendBotMessage(ctx.channel.id, { + content: settings.store.isEnabled ? "Silent typing enabled!" : "Silent typing disabled!", + }); + }, + }], + + async startTyping(channelId: string) { + if (settings.store.isEnabled) return; + FluxDispatcher.dispatch({ type: "TYPING_START_LOCAL", channelId }); + }, + + chatBarIcon: ErrorBoundary.wrap(SilentTypingToggle, { noop: true }), +}); diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx index 2a7d587ea..855b887b5 100644 --- a/src/webpack/common.tsx +++ b/src/webpack/common.tsx @@ -79,6 +79,7 @@ export let Router: any; export let TextInput: any; export let Text: (props: TextProps) => JSX.Element; export const TextArea = findByCodeLazy("handleSetRef", "textArea") as React.ComponentType>; +export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record; export const Select = LazyComponent(() => findByCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); export const Slider = LazyComponent(() => findByCode("closestMarkerIndex", "stickToMarkers")); From be7fa0cb3f7a30c604315d08f6bf3a4dc51c62a5 Mon Sep 17 00:00:00 2001 From: Nico Date: Sat, 14 Jan 2023 20:32:33 +0100 Subject: [PATCH 039/114] fix(showHiddenChannels): remove obsolete icons patch (#416) resolves https://github.com/Vendicated/Vencord/issues/415 --- src/plugins/showHiddenChannels.tsx | 8 -------- 1 file changed, 8 deletions(-) diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index 7f5263532..ea6fcc262 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -52,14 +52,6 @@ export default definePlugin({ replace: "renderLevel:Vencord.Plugins.plugins.ShowHiddenChannels.shouldShow(this.record, this.category, this.isMuted)?$1.Show:$1.CannotShow" } }, - { - // This is where the logic that chooses the icon is, we override it to be a locked voice channel if it's hidden - find: ".rulesChannelId))", - replacement: { - match: /(\w+)\.locked(.*?)switch\((\w+)\.type\)({case \w+\.\w+\.GUILD_ANNOUNCEMENT)/g, - replace: "Vencord.Plugins.plugins.ShowHiddenChannels.isHiddenChannel($3)||$1.locked$2switch($3._isHiddenChannel?2:$3.type)$4" - } - }, { // inside the onMouseClick handler, we check if the channel is hidden and open the modal if it is find: ".handleThreadsPopoutClose();", From 7478e880a8920479f6a7026d90c3a1b93ddd8a70 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 14 Jan 2023 23:00:29 +0100 Subject: [PATCH 040/114] ShowHiddenChannels: Use Lock as ChannelIcon --- .../BadgeComponent.tsx => Badge.tsx} | 4 +- .../PluginSettings/components/index.ts | 2 +- src/plugins/showHiddenChannels.tsx | 40 +++++++++++++------ 3 files changed, 31 insertions(+), 15 deletions(-) rename src/components/{PluginSettings/components/BadgeComponent.tsx => Badge.tsx} (95%) diff --git a/src/components/PluginSettings/components/BadgeComponent.tsx b/src/components/Badge.tsx similarity index 95% rename from src/components/PluginSettings/components/BadgeComponent.tsx rename to src/components/Badge.tsx index 6acf42a13..0ed45ff81 100644 --- a/src/components/PluginSettings/components/BadgeComponent.tsx +++ b/src/components/Badge.tsx @@ -22,6 +22,8 @@ export function Badge({ text, color }): JSX.Element { backgroundColor: color, justifySelf: "flex-end", marginLeft: "auto" - }}>{text} + }}> + {text} + ); } diff --git a/src/components/PluginSettings/components/index.ts b/src/components/PluginSettings/components/index.ts index 52745ea49..d307b4e68 100644 --- a/src/components/PluginSettings/components/index.ts +++ b/src/components/PluginSettings/components/index.ts @@ -30,7 +30,7 @@ export interface ISettingElementProps { definedSettings?: DefinedSettings; } -export * from "./BadgeComponent"; +export * from "../../Badge"; export * from "./SettingBooleanComponent"; export * from "./SettingCustomComponent"; export * from "./SettingNumericComponent"; diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index ea6fcc262..a97259a6a 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -18,23 +18,20 @@ import { Settings } from "@api/settings"; +import { Badge } from "@components/Badge"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { waitFor } from "@webpack"; -import { Button, ChannelStore, SnowflakeUtils, Text } from "@webpack/common"; +import { Button, ChannelStore, PermissionStore, SnowflakeUtils, Text } from "@webpack/common"; const CONNECT = 1048576n; const VIEW_CHANNEL = 1024n; -let can = (permission, channel) => true; -waitFor(m => m.can && m.initialize, m => ({ can } = m)); - export default definePlugin({ name: "ShowHiddenChannels", description: "Show hidden channels", - authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX], + authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven], options: { hideUnreads: { description: "Hide unreads", @@ -84,8 +81,17 @@ export default definePlugin({ match: /((.)\.getGuildId\(\))(&&\(!\(.\.isThread.{1,100}\.hasRelevantUnread\()/, replace: "$1&&!$2._isHiddenChannel$3" } + }, + // Lock Icon + { + find: ".rulesChannelId))", + replacement: { + match: /(\.locked.{0,400})(switch\((\i)\.type\))/, + replace: "$1 if($3._isHiddenChannel)return $self.LockIcon;$2" + } } ], + shouldShow(channel, category, isMuted) { if (!this.isHiddenChannel(channel)) return false; if (!category) return false; @@ -93,6 +99,7 @@ export default definePlugin({ return !category.isCollapsed; }, + isHiddenChannel(channel) { if (!channel) return false; if (channel.channelId) @@ -101,9 +108,10 @@ export default definePlugin({ return false; // check for disallowed voice channels too so that they get hidden when collapsing the category - channel._isHiddenChannel = !can(VIEW_CHANNEL, channel) || (channel.type === 2 && !can(CONNECT, channel)); + channel._isHiddenChannel = !PermissionStore.can(VIEW_CHANNEL, channel) || (channel.type === 2 && !PermissionStore.can(CONNECT, channel)); return channel._isHiddenChannel; }, + channelSelected(channel) { if (!channel) return false; const isHidden = this.isHiddenChannel(channel); @@ -115,11 +123,7 @@ export default definePlugin({ {channel.name} - {(channel.isNSFW() && ( - - NSFW - - ))} + {channel.isNSFW() && } @@ -156,5 +160,15 @@ export default definePlugin({ )); } return isHidden; - } + }, + + LockIcon: () => ( + + + + ) }); From e49151ff3313205026a89ba6cd722312f63db98d Mon Sep 17 00:00:00 2001 From: megumin Date: Sun, 15 Jan 2023 04:59:20 +0000 Subject: [PATCH 041/114] remove power user instructions from readme (#417) normal users will stop asking dumb questions in support!! pog --- README.md | 2 -- 1 file changed, 2 deletions(-) diff --git a/README.md b/README.md index aa09032d0..896ce7120 100644 --- a/README.md +++ b/README.md @@ -15,8 +15,6 @@ A Discord client mod that does things differently If you're just a normal user, use [our simple gui installer!](https://github.com/Vencord/Installer#usage) -If you're a power user who wants to contribute and make plugins or just want to build from source and install manually, read [Megu's Installation Guide!](docs/1_INSTALLING.md) - ## Installing on Browser [![Get the Firefox extension](https://blog.mozilla.org/addons/files/2015/11/get-the-addon-small.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) From 1d287357ca33f8bdd9933a27dc5ea63a8f29e2f8 Mon Sep 17 00:00:00 2001 From: Ven Date: Sun, 15 Jan 2023 22:26:02 +0100 Subject: [PATCH 042/114] Reimplement Discord's Switch to fix performance (#413) --- src/components/PluginSettings/index.tsx | 86 ++++++++++++------------ src/components/PluginSettings/styles.css | 47 +++++++++---- src/components/Switch.css | 3 + src/components/Switch.tsx | 76 +++++++++++++++++++++ src/preload.ts | 6 +- 5 files changed, 161 insertions(+), 57 deletions(-) create mode 100644 src/components/Switch.css create mode 100644 src/components/Switch.tsx diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 3b283d044..58058b14a 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -23,25 +23,25 @@ import { showNotice } from "@api/Notices"; import { useSettings } from "@api/settings"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; -import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; import { handleComponentFailed } from "@components/handleComponentFailed"; import { Badge } from "@components/PluginSettings/components"; import PluginModal from "@components/PluginSettings/PluginModal"; +import { Switch } from "@components/Switch"; import { ChangeList } from "@utils/ChangeList"; import Logger from "@utils/Logger"; import { classes, LazyComponent, useAwaiter } from "@utils/misc"; import { openModalLazy } from "@utils/modal"; import { Plugin } from "@utils/types"; import { findByCode, findByPropsLazy } from "@webpack"; -import { Alerts, Button, Forms, Margins, Parser, React, Select, Switch, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; +import { Alerts, Button, Card, Forms, Margins, Parser, React, Select, Text, TextInput, Toasts, Tooltip } from "@webpack/common"; import Plugins from "~plugins"; -const cl = classNameFactory("vc-plugins-"); - import { startDependenciesRecursive, startPlugin, stopPlugin } from "../../plugins"; + +const cl = classNameFactory("vc-plugins-"); const logger = new Logger("PluginSettings", "#a6d189"); const InputStyles = findByPropsLazy("inputDefault", "inputWrapper"); @@ -60,23 +60,27 @@ function showErrorToast(message: string) { }); } -interface ReloadRequiredCardProps extends React.HTMLProps { - plugins: string[]; -} - -function ReloadRequiredCard({ plugins, ...props }: ReloadRequiredCardProps) { - if (plugins.length === 0) return null; - - const pluginPrefix = plugins.length === 1 ? "The plugin" : "The following plugins require a reload to apply changes:"; - const pluginSuffix = plugins.length === 1 ? " requires a reload to apply changes." : "."; - +function ReloadRequiredCard({ required }: { required: boolean; }) { return ( - - - {pluginPrefix} {plugins.join(", ")}{pluginSuffix} - - - + + {required ? ( + <> + Restart required! + + Restart now to apply new plugins and their settings + + + + ) : ( + <> + Plugin Management + Press the cog wheel or info icon to get more info on a plugin + Plugins with a cog wheel have settings you can modify! + + )} + ); } @@ -147,26 +151,24 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe } return ( - - {plugin.description}} - hideBorder={true} - > - - - {plugin.name}{isNew && } - - - - - + +
+ + {plugin.name}{isNew && } + + + +
+ {plugin.description} +
); } @@ -298,12 +300,12 @@ export default ErrorBoundary.wrap(function PluginSettings() { return ( + + Filters - -
diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index fe8dc940d..1626d7dba 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -29,18 +29,32 @@ border-radius: 8px; display: block; height: 100%; - padding: 10px; + padding: 12px; width: 100%; transition: 0.1s ease-out; transition-property: box-shadow, transform, background, opacity; } +.vc-plugins-card-disabled { + opacity: 0.6; +} + .vc-plugins-card:hover { background-color: var(--background-tertiary); transform: translateY(-1px); box-shadow: var(--elevation-high); } +.vc-plugins-card-header { + margin-top: auto; + display: flex; + width: 100%; + justify-content: flex-end; + height: 1.5rem; + align-items: center; + gap: 8px; +} + .vc-plugins-info-button { height: 24px; width: 24px; @@ -92,23 +106,30 @@ cursor: "default"; } -.vc-plugins-flex { - margin-top: auto; - width: 100%; - height: 100%; - align-items: center; - gap: 8px; -} - .vc-plugins-dep-name { margin: 0 auto; } -.vc-plugins-reload-card { +.vc-plugins-info-card { padding: 1em; - display: grid; - grid-template-columns: 1fr auto; - gap: 1em; + height: 8em; + display: flex; + flex-direction: column; +} + +.vc-plugins-info-card div { + line-height: 32px; +} + +.vc-plugins-restart-card { + padding: 1em; + background: var(--info-warning-background); + border: 1px solid var(--info-warning-foreground); + color: var(--info-warning-text); +} + +.vc-plugins-restart-card button { + margin-top: 0.5em; } .vc-plugins-info-button svg:not(:hover):not(:focus) { diff --git a/src/components/Switch.css b/src/components/Switch.css new file mode 100644 index 000000000..e6dcf5626 --- /dev/null +++ b/src/components/Switch.css @@ -0,0 +1,3 @@ +.vc-switch-slider { + transition: 100ms transform ease-in-out; +} diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx new file mode 100644 index 000000000..a18a7e492 --- /dev/null +++ b/src/components/Switch.tsx @@ -0,0 +1,76 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 "./Switch.css"; + +import { findByPropsLazy } from "@webpack"; + +interface SwitchProps { + checked: boolean; + onChange: (checked: boolean) => void; + disabled?: boolean; +} + +const SWITCH_ON = "var(--status-green-600)"; +const SWITCH_OFF = "var(--primary-dark-400)"; +const SwitchClasses = findByPropsLazy("slider", "input", "container"); + +export function Switch({ checked, onChange, disabled }: SwitchProps) { + return ( +
+
+ + onChange(e.currentTarget.checked)} + /> +
+
+ ); +} diff --git a/src/preload.ts b/src/preload.ts index 33f2410b9..3867f336c 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -49,9 +49,11 @@ if (location.protocol !== "data:") { const css = readFileSync(rendererCss, "utf-8"); insertCss(css); if (IS_DEV) { - watch(rendererCss, debounce(() => { + // persistent means keep process running if watcher is the only thing still running + // which we obviously don't want + watch(rendererCss, { persistent: false }, () => { document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8"); - }, 30)); + }); } } catch (err) { if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") From ebdcbcaf0cd7d49106672d622213951bd5dfc0ae Mon Sep 17 00:00:00 2001 From: Ven Date: Mon, 16 Jan 2023 05:19:04 +0100 Subject: [PATCH 043/114] Update README.md --- README.md | 35 +++++++++-------------------------- 1 file changed, 9 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 896ce7120..82fc5616a 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,20 @@ # Vencord -A Discord client mod that does things differently +The cutest Discord client mod ## Features -- Super easy to install, no git or node or anything else required -- Many plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033) - - Some highlights: SpotifyControls, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, custom slash commands, ShowHiddenChannels -- Browser Support: Run Vencord in your Browser via extension or UserScript +- Super easy to install (one click installer) +- 90+ plugins built in: [See a list](https://gist.github.com/Vendicated/8696cde7b92548064a3ae92ead84d033) + - Some highlights: SpotifyControls, Experiments, NoTrack, MessageLogger, QuickReply, Free Emotes/Stickers, CustomCommands, ShowHiddenChannels, PronounDB +- Excellent Browser Support: Run Vencord in your Browser via extension or UserScript - Custom CSS and Themes: Inbuilt css editor with support to import any css files (including BetterDiscord themes) - Works in all Electron versions (Confirmed working on versions 13-23) +- Maintained very actively, broken plugins are usually fixed within 12 hours ## Installing / Uninstalling -If you're just a normal user, use [our simple gui installer!](https://github.com/Vencord/Installer#usage) +[![Download and run the Installer ](https://img.shields.io/github/v/release/Vencord/Installer?label=Download%20Vencord%20Installer&style=for-the-badge)](https://github.com/Vencord/Installer#usage) ## Installing on Browser @@ -23,27 +24,9 @@ Or install the browser extension for - [![Chrome](https://img.shields.io/badge/chrome-ext-brightgreen)](https://github.com/Vendicated/Vencord/releases/latest/download/Vencord-for-Chrome-and-Edge.zip) - [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that QuickCSS, shiki and other plugins making use of external resources will not work with the UserScript. +## Building from Source -You may also build them from source, to do that do the same steps as in the manual regular install method, -except run `pnpm buildWeb` instead of `pnpm build`, and your outputs will be in the dist folder - -```sh -pnpm buildWeb -``` - -You will find the built extension at dist/extension.zip. Now just install this extension in your Browser - -## Installing Plugins - -> **Note** -> You can only use 3rd party plugins in the manual Vencord install for now. - -Vencord comes with a bunch of plugins out of the box! - -However, if you want to install your own ones, create a `userplugins` folder in the `src` directory and create or clone your plugins in there. -Don't forget to rebuild! - -Want to learn how to create your own plugin, and maybe PR it into Vencord? See the [Contributing](#contributing) section below! +See the docs folder ## Contributing From 5fe0600d6ce8679248d475b6c6251b8eea3a33d4 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 16 Jan 2023 23:33:33 -0300 Subject: [PATCH 044/114] Fix Message Popover API (#425) --- src/plugins/apiMessagePopover.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/apiMessagePopover.ts b/src/plugins/apiMessagePopover.ts index 95814e05f..e9d712a54 100644 --- a/src/plugins/apiMessagePopover.ts +++ b/src/plugins/apiMessagePopover.ts @@ -26,8 +26,8 @@ export default definePlugin({ patches: [{ find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL", replacement: { - match: /(message:(.).{0,100}Fragment,\{children:\[)(.{0,90}renderPopout:.{0,200}message_reaction_emoji_picker.+?return (.{1,3})\(.{0,30}"add-reaction")/, - replace: "$1...Vencord.Api.MessagePopover._buildPopoverElements($2,$4),$3" + match: /\?(?\i)\(.{1,35}\.Messages\.CONFIGURE.+?message:(?\i).+?children:\[/, + replace: "$&...Vencord.Api.MessagePopover._buildPopoverElements($,$)," } }], }); From 08a2030bbc4378cc2f750794cc1fe0d5fcb98d28 Mon Sep 17 00:00:00 2001 From: The Captain <81252038+Captain8771@users.noreply.github.com> Date: Wed, 18 Jan 2023 23:47:40 +0100 Subject: [PATCH 045/114] feat(Plugin): customRPC (#406) Co-authored-by: Ven --- src/plugins/customRPC.tsx | 251 ++++++++++++++++++++++++++++++++++++++ src/utils/constants.ts | 4 + 2 files changed, 255 insertions(+) create mode 100644 src/plugins/customRPC.tsx diff --git a/src/plugins/customRPC.tsx b/src/plugins/customRPC.tsx new file mode 100644 index 000000000..9a0901b76 --- /dev/null +++ b/src/plugins/customRPC.tsx @@ -0,0 +1,251 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 { definePluginSettings } from "@api/settings"; +import { Link } from "@components/Link"; +import { Devs } from "@utils/constants"; +import { useAwaiter } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "@webpack"; +import { + FluxDispatcher, + Forms, + GuildStore, + React, + SelectedChannelStore, + SelectedGuildStore, + UserStore +} from "@webpack/common"; + +const ActivityComponent = findByCodeLazy("onOpenGameProfile"); +const ActivityClassName = findByPropsLazy("activity", "buttonColor"); +const Colors = findByPropsLazy("profileColors"); + +// START yoinked from lastfm.tsx +const assetManager = mapMangledModuleLazy( + "getAssetImage: size must === [number, number] for Twitch", + { + getAsset: filters.byCode("apply("), + } +); + +async function getApplicationAsset(key: string): Promise { + return (await assetManager.getAsset(settings.store.appID, [key, undefined]))[0]; +} + +interface ActivityAssets { + large_image?: string; + large_text?: string; + small_image?: string; + small_text?: string; +} + +interface Activity { + state: string; + details?: string; + timestamps?: { + start?: Number; + end?: Number; + }; + assets?: ActivityAssets; + buttons?: Array; + name: string; + application_id: string; + metadata?: { + button_urls?: Array; + }; + type: ActivityType; + flags: Number; +} + +enum ActivityType { + PLAYING = 0, + LISTENING = 2, + WATCHING = 3, + COMPETING = 5 +} +// END + +const strOpt = (description: string) => ({ + type: OptionType.STRING, + description, + onChange: setRpc +}) as const; + +const numOpt = (description: string) => ({ + type: OptionType.NUMBER, + description, + onChange: setRpc +}) as const; + +const choice = (label: string, value: any, _default?: Boolean) => ({ + label, + value, + default: _default +}) as const; + +const choiceOpt = (description: string, options) => ({ + type: OptionType.SELECT, + description, + onChange: setRpc, + options +}) as const; + + +const settings = definePluginSettings({ + appID: strOpt("The ID of the application for the rich presence."), + appName: strOpt("The name of the presence."), + details: strOpt("Line 1 of rich presence."), + state: strOpt("Line 2 of rich presence."), + type: choiceOpt("Type of presence", [ + choice("Playing", ActivityType.PLAYING, true), + choice("Listening", ActivityType.LISTENING), + choice("Watching", ActivityType.WATCHING), + choice("Competing", ActivityType.COMPETING) + ]), + startTime: numOpt("Unix Timestamp for beginning of activity."), + endTime: numOpt("Unix Timestamp for end of activity."), + imageBig: strOpt("Sets the big image to the specified image."), + imageBigTooltip: strOpt("Sets the tooltip text for the big image."), + imageSmall: strOpt("Sets the small image to the specified image."), + imageSmallTooltip: strOpt("Sets the tooltip text for the small image."), + buttonOneText: strOpt("The text for the first button"), + buttonOneURL: strOpt("The URL for the first button"), + buttonTwoText: strOpt("The text for the second button"), + buttonTwoURL: strOpt("The URL for the second button") +}); + +async function createActivity(): Promise { + const { + appID, + appName, + details, + state, + type, + startTime, + endTime, + imageBig, + imageBigTooltip, + imageSmall, + imageSmallTooltip, + buttonOneText, + buttonOneURL, + buttonTwoText, + buttonTwoURL + } = settings.store; + + if (!appName) return; + + const activity: Activity = { + application_id: appID || "0", + name: appName, + state, + details, + type, + flags: 1 << 0, + }; + + if (startTime) { + activity.timestamps = { + start: startTime, + }; + if (endTime) { + activity.timestamps.end = endTime; + } + } + + if (buttonOneText) { + activity.buttons = [ + buttonOneText, + buttonTwoText + ].filter(Boolean); + + activity.metadata = { + button_urls: [ + buttonOneURL, + buttonTwoURL + ].filter(Boolean) + }; + } + + if (imageBig) { + activity.assets = { + large_image: await getApplicationAsset(imageBig), + large_text: imageBigTooltip + }; + } + + if (imageSmall) { + activity.assets = { + ...activity.assets, + small_image: await getApplicationAsset(imageSmall), + small_text: imageSmallTooltip + }; + } + + + for (const k in activity) { + if (k === "type") continue; // without type, the presence is considered invalid. + const v = activity[k]; + if (!v || v.length === 0) + delete activity[k]; + } + + // WHAT DO YOU WANT FROM ME + // eslint-disable-next-line consistent-return + return activity; +} + +async function setRpc(disable?: Boolean) { + const activity: Activity | undefined = await createActivity(); + + FluxDispatcher.dispatch({ + type: "LOCAL_ACTIVITY_UPDATE", + activity: !disable ? activity : {} + }); +} + +export default definePlugin({ + name: "CustomRPC", + description: "Allows you to set a custom rich presence.", + authors: [Devs.captain], + start: setRpc, + stop: () => setRpc(true), + settings, + + settingsAboutComponent: () => { + const activity = useAwaiter(createActivity); + return ( + <> + NOTE: + + You will need to create an + application and + get its ID to use this plugin. + + +
+ {activity[0] && } +
+ + ); + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index b5e1eb612..1eba71304 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -188,5 +188,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ zt: { name: "zt", id: 289556910426816513n + }, + captain: { + name: "Captain", + id: 347366054806159360n } }); From c4d2b4a8cd39fae7b1f453608d3cdc97a24f739d Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 18 Jan 2023 20:36:31 -0300 Subject: [PATCH 046/114] Fix message logger patch (#431) --- src/plugins/messageLogger/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index e650dbbf6..50a6f5273 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -259,8 +259,8 @@ export default definePlugin({ replace: "$1,deleted=$2.attachment?.deleted," }, { - match: /(hiddenSpoilers:\w,className:)/, - replace: "$1 (deleted ? 'messageLogger-deleted-attachment ' : '') +" + match: /\["className","attachment","inlineMedia".+?className:/, + replace: "$& (deleted ? 'messageLogger-deleted-attachment ' : '') +" } ] }, From 7fe3a2c8059e2f9b51a8ac559a8badcefb607266 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Jan 2023 19:31:14 +0100 Subject: [PATCH 047/114] Add issue templates --- .github/ISSUE_TEMPLATE/blank.yml | 21 ++++++++ .github/ISSUE_TEMPLATE/bug_report.yml | 56 ++++++++++++++++++++++ .github/ISSUE_TEMPLATE/config.yml | 8 ++++ .github/ISSUE_TEMPLATE/feature_request.yml | 22 +++++++++ 4 files changed, 107 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/blank.yml create mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml create mode 100644 .github/ISSUE_TEMPLATE/config.yml create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml new file mode 100644 index 000000000..b003b737d --- /dev/null +++ b/.github/ISSUE_TEMPLATE/blank.yml @@ -0,0 +1,21 @@ +name: Blank Template +description: Use this only if your issue does not fit into another template. **DO NOT ASK FOR SUPPORT OR REQUEST PLUGINS** +labels: [] +body: + - type: textarea + id: info-sec + attributes: + label: Tell us all about it. + description: Go nuts, let us know what you're wanting to bring attention to. + placeholder: ... + validations: + required: true + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + description: DO NOT USE THIS TEMPLATE FOR SUPPORT OR PLUGIN REQUESTS!!! For Support, **join our Discord**. For plugin requests, **use discussions** + options: + - label: This is not a support or plugin request + required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 000000000..2721360f4 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,56 @@ +name: Bug/Crash Report +description: Create a bug or crash report for Vencord +labels: [bug] + +body: + - type: input + id: discord + attributes: + label: Discord Account + description: Who on Discord is making this request? Not required but encouraged for easier follow-up + placeholder: username#0000 + validations: + required: false + + - type: textarea + id: bug-description + attributes: + label: What happens when the bug or crash occurs? + description: Where does this bug or crash occur, when does it occur, etc. + placeholder: The bug/crash happens sometimes when I do ..., causing this to not work/the app to crash. I think it happens because of ... + validations: + required: true + + - type: textarea + id: expected-behaviour + attributes: + label: What is the expected behaviour? + description: Simply detail what the expected behaviour is. + placeholder: I expect Vencord/Discord to open the ... page instead of ..., it prevents me from doing ... + validations: + required: true + + - type: textarea + id: steps-to-take + attributes: + label: How do you recreate this bug or crash? + description: Give us a list of steps in order to recreate the bug or crash. + placeholder: | + 1. Do ... + 2. Then ... + 3. Do this ..., ... and then ... + 4. Observe "the bug" or "the crash" + validations: + required: true + + - type: textarea + id: crash-log + attributes: + label: Errors + description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". + value: | + ``` + Replace this text with your crash-log. + ``` + validations: + required: false diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 000000000..bc5d97662 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,8 @@ +blank_issues_enabled: false +contact_links: + - name: Vencord Support Server + url: https://discord.gg/D9uwnFnqmd + about: If you need help regarding Vencord, please join our support server! + - name: Vencord Installer + url: https://github.com/Vencord/Installer + about: You can find the Vencord Installer here diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..c8aae71e7 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,22 @@ +name: Feature Request +description: Create a feature request for Vencord. To request new plugins, please use the Discussions tab +labels: [enhancement] + +body: + - type: input + id: discord + attributes: + label: Discord Account + description: Who on Discord is making this request? Not required but encouraged for easier follow-up + placeholder: username#0000 + validations: + required: false + + - type: textarea + id: feature-basic-description + attributes: + label: What is it that you'd like to see? + description: Describe the feature you want added as detailed as possible + placeholder: I think ... would be a cool feature to add. This would be awesome, thanks! + validations: + required: true From ef5b3e1818b959e77a2e2f7f4229a712d5983cbe Mon Sep 17 00:00:00 2001 From: Vendicated Date: Thu, 19 Jan 2023 19:35:53 +0100 Subject: [PATCH 048/114] add issue title templates --- .github/ISSUE_TEMPLATE/blank.yml | 1 + .github/ISSUE_TEMPLATE/bug_report.yml | 1 + .github/ISSUE_TEMPLATE/feature_request.yml | 10 ++++++++++ 3 files changed, 12 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml index b003b737d..9887db99f 100644 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ b/.github/ISSUE_TEMPLATE/blank.yml @@ -1,6 +1,7 @@ name: Blank Template description: Use this only if your issue does not fit into another template. **DO NOT ASK FOR SUPPORT OR REQUEST PLUGINS** labels: [] + body: - type: textarea id: info-sec diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index 2721360f4..eb66cee3e 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,6 +1,7 @@ name: Bug/Crash Report description: Create a bug or crash report for Vencord labels: [bug] +title: "[Bug] " body: - type: input diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml index c8aae71e7..115f7f700 100644 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -1,6 +1,7 @@ name: Feature Request description: Create a feature request for Vencord. To request new plugins, please use the Discussions tab labels: [enhancement] +title: "[Feature Request] <title>" body: - type: input @@ -20,3 +21,12 @@ body: placeholder: I think ... would be a cool feature to add. This would be awesome, thanks! validations: required: true + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + description: DO NOT USE THIS TEMPLATE FOR PLUGIN REQUESTS!!! For plugin requests, **use discussions** + options: + - label: This is not a plugin request + required: true From 2641adb29b3660c361f386d721937aab026feaf3 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 19 Jan 2023 19:39:33 +0100 Subject: [PATCH 049/114] canary bad --- .github/ISSUE_TEMPLATE/bug_report.yml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index eb66cee3e..734bbaca8 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -55,3 +55,12 @@ body: ``` validations: required: false + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable + options: + - label: I am using Discord Stable or tried on Stable and this bug happens there as well + required: true From 6e44b8c47ebf6a59caef130d58fb90a102a88479 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 20 Jan 2023 21:47:24 -0300 Subject: [PATCH 050/114] Fix Message Accessories API (#441) --- src/plugins/apiMessageAccessories.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/apiMessageAccessories.ts b/src/plugins/apiMessageAccessories.ts index 11109beea..5bb13cfe1 100644 --- a/src/plugins/apiMessageAccessories.ts +++ b/src/plugins/apiMessageAccessories.ts @@ -25,7 +25,7 @@ export default definePlugin({ authors: [Devs.Cyn], patches: [ { - find: "_messageAttachmentToEmbedMedia", + find: ".Messages.REMOVE_ATTACHMENT_BODY", replacement: { match: /(.container\)?,children:)(\[[^\]]+\])(}\)\};return)/, replace: (_, pre, accessories, post) => From 64180362fda076519c310f74e9245c94543edd39 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 21 Jan 2023 10:37:36 -0300 Subject: [PATCH 051/114] ViewIcons: Fix finding ImageModal and props passing to MaskedLink (#442) * Fix finding ImageModal and props passing to MaskedLink * gonna stick this here --- src/plugins/ignoreActivities.tsx | 4 ++-- src/plugins/viewIcons.tsx | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/plugins/ignoreActivities.tsx b/src/plugins/ignoreActivities.tsx index 3b1ee98e3..300caf7aa 100644 --- a/src/plugins/ignoreActivities.tsx +++ b/src/plugins/ignoreActivities.tsx @@ -158,7 +158,7 @@ export default definePlugin({ find: '.displayName="LocalActivityStore"', replacement: { match: /(?<activities>.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?\)\)/, - replace: "$&;$<activities>=$<activities>.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityIgnored);" + replace: "$&;$<activities>=$<activities>.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityNotIgnored);" } }], @@ -205,7 +205,7 @@ export default definePlugin({ ); }, - isActivityIgnored(props: { type: number; application_id?: string; name?: string; }) { + isActivityNotIgnored(props: { type: number; application_id?: string; name?: string; }) { if (props.type === 0) { if (props.application_id !== undefined) return !ignoredActivitiesCache.has(props.application_id); else { diff --git a/src/plugins/viewIcons.tsx b/src/plugins/viewIcons.tsx index 63f355486..307fd0154 100644 --- a/src/plugins/viewIcons.tsx +++ b/src/plugins/viewIcons.tsx @@ -20,11 +20,11 @@ import { Devs } from "@utils/constants"; import { LazyComponent } from "@utils/misc"; import { ModalRoot, ModalSize, openModal } from "@utils/modal"; import { PluginDef } from "@utils/types"; -import { find, findByPropsLazy } from "@webpack"; +import { find, findByCode, findByPropsLazy } from "@webpack"; import { Menu } from "@webpack/common"; import type { Guild } from "discord-types/general"; -const ImageModal = LazyComponent(() => find(m => m.prototype?.render?.toString().includes("this.renderMobileCloseButton()"))); +const ImageModal = LazyComponent(() => findByCode(".MEDIA_MODAL_CLOSE,")); const MaskedLink = LazyComponent(() => find(m => m.type?.toString().includes("MASKED_LINK)"))); const GuildBannerStore = findByPropsLazy("getGuildBannerURL"); @@ -48,7 +48,7 @@ export default new class ViewIcons implements PluginDef { shouldAnimate={true} original={url} src={url} - renderLinkComponent={() => <MaskedLink />} + renderLinkComponent={MaskedLink} /> </ModalRoot> )); From 9602f527d8e2a8c90856d76c1da5d9a592fc08fc Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sat, 21 Jan 2023 10:41:10 -0300 Subject: [PATCH 052/114] Future proof Volume Booster to work with volume settings syncing (#439) --- src/plugins/volumeBooster.ts | 48 +++++++++++++++++++++++++++--------- 1 file changed, 37 insertions(+), 11 deletions(-) diff --git a/src/plugins/volumeBooster.ts b/src/plugins/volumeBooster.ts index 49d9d5433..6553a5c78 100644 --- a/src/plugins/volumeBooster.ts +++ b/src/plugins/volumeBooster.ts @@ -26,25 +26,51 @@ export default definePlugin({ description: "Allows you to set the user and stream volume above the default maximum.", patches: [ - { - find: ".Messages.USER_VOLUME", + // Change the max volume for sliders to allow for values above 200 + ...[ + ".Messages.USER_VOLUME", + "currentVolume:" + ].map(find => ({ + find, replacement: { - match: /maxValue:(?<defaultMaxVolumePredicate>.{1,2}\..{1,2})\?(?<higherMaxVolume>\d+?):(?<minorMaxVolume>\d+?),/, + match: /maxValue:(?<defaultMaxVolumePredicate>\i\.\i)\?(?<higherMaxVolume>\d+?):(?<minorMaxVolume>\d+?),/, replace: "" + "maxValue:$<defaultMaxVolumePredicate>" + "?$<higherMaxVolume>*Vencord.Settings.plugins.VolumeBooster.multiplier" + ":$<minorMaxVolume>*Vencord.Settings.plugins.VolumeBooster.multiplier," } + })), + // Prevent Audio Context Settings sync from trying to sync with values above 200, changing them to 200 before we send to Discord + { + find: "AudioContextSettingsMigrated", + replacement: [ + { + match: /(?<restOfFunction>updateAsync\("audioContextSettings".{1,50})(?<volumeChangeExpression>return (?<volumeOptions>\i)\.volume=(?<newVolume>\i))/, + replace: "$<restOfFunction>if($<newVolume>>200)return $<volumeOptions>.volume=200;$<volumeChangeExpression>" + }, + { + match: /(?<restOfFunction>Object\.entries\(\i\.localMutes\).+?)volume:(?<volumeExpression>.+?),/, + replace: "$<restOfFunction>volume:$<volumeExpression>>200?200:$<volumeExpression>," + }, + { + match: /(?<restOfFunction>Object\.entries\(\i\.localVolumes\).+?)volume:(?<volumeExpression>.+?)}\)/, + replace: "$<restOfFunction>volume:$<volumeExpression>>200?200:$<volumeExpression>})" + } + ] }, + // Prevent the MediaEngineStore from overwriting our LocalVolumes above 200 with the ones the Discord Audio Context Settings sync sends { - find: "currentVolume:", - replacement: { - match: /maxValue:(?<defaultMaxVolumePredicate>.{1,2}\..{1,2})\?(?<higherMaxVolume>\d+?):(?<minorMaxVolume>\d+?),/, - replace: "" - + "maxValue:$<defaultMaxVolumePredicate>" - + "?$<higherMaxVolume>*Vencord.Settings.plugins.VolumeBooster.multiplier" - + ":$<minorMaxVolume>*Vencord.Settings.plugins.VolumeBooster.multiplier," - } + find: '.displayName="MediaEngineStore"', + replacement: [ + { + match: /(?<restOfFunction>\.settings\.audioContextSettings.+?)(?<localVolume>\i\[\i\])=(?<syncVolume>\i\.volume)(?<secondRestOfFunction>.+?)setLocalVolume\((?<id>.+?),.+?\)/, + replace: "" + + "$<restOfFunction>" + + "($<localVolume>>200?undefined:$<localVolume>=$<syncVolume>)" + + "$<secondRestOfFunction>" + + "setLocalVolume($<id>,$<localVolume>??$<syncVolume>)" + } + ] } ], From b4f98e5066aa41f986865f4b598593afc9161d3b Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 22 Jan 2023 04:26:33 +0100 Subject: [PATCH 053/114] Fix Settings ContextMenu Shortcuts & Settings on canary --- src/plugins/settings.tsx | 75 ++++++++++++++++++++++++++++++++-------- src/webpack/common.tsx | 5 ++- 2 files changed, 65 insertions(+), 15 deletions(-) diff --git a/src/plugins/settings.tsx b/src/plugins/settings.tsx index 183798af4..36bf52576 100644 --- a/src/plugins/settings.tsx +++ b/src/plugins/settings.tsx @@ -17,10 +17,12 @@ */ import { Settings } from "@api/settings"; +import PatchHelper from "@components/PatchHelper"; import { Devs } from "@utils/constants"; import Logger from "@utils/Logger"; import { LazyComponent } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; +import { Router } from "@webpack/common"; import gitHash from "~git-hash"; @@ -62,23 +64,68 @@ export default definePlugin({ } } }, - replace: (m, mod) => { - const updater = !IS_WEB ? '{section:"VencordUpdater",label:"Updater",element:Vencord.Plugins.plugins.Settings.tabs.updater},' : ""; - const patchHelper = IS_DEV ? '{section:"VencordPatchHelper",label:"Patch Helper",element:Vencord.Components.PatchHelper},' : ""; - return ( - `{section:${mod}.ID.HEADER,label:"Vencord"},` + - '{section:"VencordSettings",label:"Vencord",element:Vencord.Plugins.plugins.Settings.tabs.vencord},' + - '{section:"VencordPlugins",label:"Plugins",element:Vencord.Plugins.plugins.Settings.tabs.plugins},' + - '{section:"VencordThemes",label:"Themes",element:Vencord.Plugins.plugins.Settings.tabs.themes},' + - updater + - '{section:"VencordSettingsSync",label:"Backup & Restore",element:Vencord.Plugins.plugins.Settings.tabs.sync},' + - patchHelper + - `{section:${mod}.ID.DIVIDER},${m}` - ); - } + replace: "...$self.makeSettingsCategories($1),$&" } }], + makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) { + const makeOnClick = (tab: string) => () => Router.open(tab); + + const cats = [ + { + section: ID.HEADER, + label: "Vencord" + }, { + section: "VencordSettings", + label: "Vencord", + element: () => <SettingsComponent tab="VencordSettings" />, + onClick: makeOnClick("VencordSettings") + }, { + section: "VencordPlugins", + label: "Plugins", + element: () => <SettingsComponent tab="VencordPlugins" />, + onClick: makeOnClick("VencordPlugins") + }, { + section: "VencordThemes", + label: "Themes", + element: () => <SettingsComponent tab="VencordThemes" />, + onClick: makeOnClick("VencordThemes") + } + ] as Array<{ + section: unknown, + label?: string; + element?: React.ComponentType; + onClick?(): void; + }>; + + if (!IS_WEB) + cats.push({ + section: "VencordUpdater", + label: "Updater", + element: () => <SettingsComponent tab="VencordUpdater" />, + onClick: makeOnClick("VencordUpdater") + }); + + cats.push({ + section: "VencordSettingsSync", + label: "Backup & Restore", + element: () => <SettingsComponent tab="VencordSettingsSync" />, + onClick: makeOnClick("VencordSettingsSync") + }); + + if (IS_DEV) + cats.push({ + section: "VencordPatchHelper", + label: "Patch Helper", + element: PatchHelper!, + onClick: makeOnClick("VencordPatchHelper") + }); + + cats.push({ section: ID.DIVIDER }); + + return cats; + }, + options: { settingsLocation: { type: OptionType.SELECT, diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx index 855b887b5..0be47ff7a 100644 --- a/src/webpack/common.tsx +++ b/src/webpack/common.tsx @@ -182,7 +182,9 @@ waitFor(["getMember", "initialize"], m => GuildMemberStore = m); waitFor("getRelationshipType", m => RelationshipStore = m); waitFor(["Hovers", "Looks", "Sizes"], m => Button = m); -waitFor(filters.byCode("helpdeskArticleId"), m => Switch = m); + +waitFor(filters.byCode("tooltipNote", "ringTarget"), m => Switch = m); + waitFor(["Positions", "Colors"], m => Tooltip = m); waitFor(m => m.Types?.PRIMARY === "cardPrimary", m => Card = m); @@ -305,3 +307,4 @@ export const ContextMenu = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { openUntrustedLink: filters.byCode(".apply(this,arguments)") }); + From 68e80c4d4ce162275f68a9a8099211626f1fe4ad Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 22 Jan 2023 04:29:58 +0100 Subject: [PATCH 054/114] Fix small QuickCss bug --- src/ipcMain/index.ts | 2 +- src/utils/quickCss.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts index 3f22b7253..0946f5af7 100644 --- a/src/ipcMain/index.ts +++ b/src/ipcMain/index.ts @@ -78,7 +78,7 @@ ipcMain.handle(IpcEvents.SET_SETTINGS, (_, s) => { export function initIpc(mainWindow: BrowserWindow) { open(QUICKCSS_PATH, "a+").then(fd => { fd.close(); - watch(QUICKCSS_PATH, debounce(async () => { + watch(QUICKCSS_PATH, { persistent: false }, debounce(async () => { mainWindow.webContents.postMessage(IpcEvents.QUICK_CSS_UPDATE, await readCss()); }, 50)); }); diff --git a/src/utils/quickCss.ts b/src/utils/quickCss.ts index de4eaefbc..4ae102333 100644 --- a/src/utils/quickCss.ts +++ b/src/utils/quickCss.ts @@ -29,10 +29,10 @@ export async function toggle(isEnabled: boolean) { style = document.createElement("style"); style.id = "vencord-custom-css"; document.head.appendChild(style); - VencordNative.ipc.on(IpcEvents.QUICK_CSS_UPDATE, (_, css: string) => style.innerText = css); + VencordNative.ipc.on(IpcEvents.QUICK_CSS_UPDATE, (_, css: string) => style.textContent = css); style.textContent = await VencordNative.ipc.invoke(IpcEvents.GET_QUICK_CSS); } - } else // @ts-ignore yes typescript, property 'disabled' does exist on type 'HTMLStyleElement' u should try reading the docs some time + } else style.disabled = !isEnabled; } From 84cfe531af3fb26fa31bb364c71a23b1e9845735 Mon Sep 17 00:00:00 2001 From: hunter <61890674+hunt-g@users.noreply.github.com> Date: Sun, 22 Jan 2023 16:27:55 -0700 Subject: [PATCH 055/114] Ignore dotfiles in plugin dirs (#447) --- scripts/build/common.mjs | 1 + 1 file changed, 1 insertion(+) diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 2743c700d..9e0023c70 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -68,6 +68,7 @@ export const globPlugins = { if (!existsSync(`./src/${dir}`)) continue; const files = await readdir(`./src/${dir}`); for (const file of files) { + if (file.startsWith(".")) continue; if (file === "index.ts") { continue; } From 8a43e9b25f97cda19a5dc85020152020873c6a8e Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 23 Jan 2023 04:02:09 +0100 Subject: [PATCH 056/114] dev: Better errors when using Node < v18 --- package.json | 3 +++ scripts/checkNodeVersion.js | 20 ++++++++++++++++++++ scripts/runInstaller.mjs | 2 ++ 3 files changed, 25 insertions(+) create mode 100644 scripts/checkNodeVersion.js diff --git a/package.json b/package.json index 224a480fc..459e01b6a 100644 --- a/package.json +++ b/package.json @@ -84,5 +84,8 @@ "overwriteDest": true }, "sourceDir": "./dist/extension-v2-unpacked" + }, + "engines": { + "node": ">=18" } } diff --git a/scripts/checkNodeVersion.js b/scripts/checkNodeVersion.js new file mode 100644 index 000000000..041b958be --- /dev/null +++ b/scripts/checkNodeVersion.js @@ -0,0 +1,20 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +if (Number(process.versions.node.split(".")[0]) < 18) + throw `Your node version (${process.version}) is too old, please update to v18 or higher https://nodejs.org/en/download/`; diff --git a/scripts/runInstaller.mjs b/scripts/runInstaller.mjs index 0d4746499..b35039f8a 100644 --- a/scripts/runInstaller.mjs +++ b/scripts/runInstaller.mjs @@ -16,6 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import "./checkNodeVersion.js"; + import { execFileSync, execSync } from "child_process"; import { createWriteStream, existsSync, mkdirSync, readFileSync, writeFileSync } from "fs"; import { dirname, join } from "path"; From 75050e74ca01856f9b48867d7945e2b7263b42ae Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Mon, 23 Jan 2023 18:04:50 -0300 Subject: [PATCH 057/114] ShowHiddenChannels: better ui, alternative display mode (#446) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/messageLogger/index.tsx | 23 +--- src/plugins/showHiddenChannels.tsx | 197 +++++++++++++++++++--------- src/webpack/common.tsx | 4 +- 3 files changed, 141 insertions(+), 83 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 50a6f5273..8c7fa1149 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -23,8 +23,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import Logger from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; -import { Parser, UserStore } from "@webpack/common"; +import { moment, Parser, Timestamp, UserStore } from "@webpack/common"; function addDeleteStyleClass() { if (Settings.plugins.MessageLogger.deleteStyle === "text") { @@ -41,13 +40,7 @@ export default definePlugin({ description: "Temporarily logs deleted and edited messages.", authors: [Devs.rushii, Devs.Ven], - timestampModule: null as any, - moment: null as Function | null, - start() { - this.moment = findByPropsLazy("relativeTimeRounding", "relativeTimeThreshold"); - this.timestampModule = findByPropsLazy("messageLogger_TimestampComponent"); - addDeleteStyleClass(); }, @@ -59,7 +52,6 @@ export default definePlugin({ }, renderEdit(edit: { timestamp: any, content: string; }) { - const Timestamp = this.timestampModule.messageLogger_TimestampComponent; return ( <ErrorBoundary noop> <div className="messageLogger-edited"> @@ -78,7 +70,7 @@ export default definePlugin({ makeEdit(newMessage: any, oldMessage: any): any { return { - timestamp: this.moment?.call(newMessage.edited_timestamp), + timestamp: moment?.call(newMessage.edited_timestamp), content: oldMessage.content }; }, @@ -312,17 +304,6 @@ export default definePlugin({ ] }, - { - // Message "(edited)" timestamp component - // Module 23552 - find: "Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format", - replacement: { - // Re-export the timestamp component under a findable name - match: /{(\w{1,2}:\(\)=>(\w{1,2}))}/, - replace: "{$1,messageLogger_TimestampComponent:()=>$2}" - } - }, - { // Message context base menu // Module 600300 diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index a97259a6a..1225cf113 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -23,125 +23,177 @@ import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import definePlugin, { OptionType } from "@utils/types"; -import { Button, ChannelStore, PermissionStore, SnowflakeUtils, Text } from "@webpack/common"; +import { findByPropsLazy } from "@webpack"; +import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; + +const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); -const CONNECT = 1048576n; const VIEW_CHANNEL = 1024n; +enum ChannelTypes { + GUILD_TEXT = 0, + GUILD_ANNOUNCEMENT = 5, + GUILD_FORUM = 15 +} + +const ChannelTypesToChannelName = { + [ChannelTypes.GUILD_TEXT]: "TEXT", + [ChannelTypes.GUILD_ANNOUNCEMENT]: "ANNOUNCEMENT", + [ChannelTypes.GUILD_FORUM]: "FORUM" +}; + +enum ShowMode { + LockIcon, + HiddenIconWithMutedStyle +} + export default definePlugin({ name: "ShowHiddenChannels", - description: "Show hidden channels", - authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven], + description: "Show channels that you do not have access to view.", + authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux], options: { hideUnreads: { - description: "Hide unreads", + description: "Hide Unreads", type: OptionType.BOOLEAN, default: true, - restartNeeded: true // Restart is needed to refresh channel list + restartNeeded: true + }, + showMode: { + description: "The mode used to display hidden channels.", + type: OptionType.SELECT, + options: [ + { label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true }, + { label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle }, + ], + restartNeeded: true } }, patches: [ { // RenderLevel defines if a channel is hidden, collapsed in category, visible, etc find: ".CannotShow", - replacement: { - match: /renderLevel:(\w+)\.CannotShow/g, - replace: "renderLevel:Vencord.Plugins.plugins.ShowHiddenChannels.shouldShow(this.record, this.category, this.isMuted)?$1.Show:$1.CannotShow" - } + // These replacements only change the necessary CannotShow's + replacement: [ + { + match: /(?<restOfFunction>renderLevel:(?<renderLevelExpression>\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?,/, + replace: "$<restOfFunction>$<renderLevelExpression>," + }, + { + match: /(?<restOfFunction>activeJoinedRelevantThreads.{1,100}renderLevel:(?<RenderLevels>\i)\.Show.+?renderLevel:).+?,/, + replace: "$<restOfFunction>$<RenderLevels>.Show," + }, + { + match: /(?<restOfFunction>isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(?<RenderLevels>\i)\.CannotShow/, + replace: "$<restOfFunction>this.category.isCollapsed?$<RenderLevels>.WouldShowIfUncollapsed:$<RenderLevels>.Show" + }, + { + match: /(?<restOfFunction>getRenderLevel=function.+?return).+?\?(?<renderLevelExpression>.+?):\i\.CannotShow}/, + replace: "$<restOfFunction> $<renderLevelExpression>}" + } + ] }, { // inside the onMouseClick handler, we check if the channel is hidden and open the modal if it is find: ".handleThreadsPopoutClose();", replacement: { - match: /((\w)\.handleThreadsPopoutClose\(\);)/g, - replace: "if(arguments[0].button===0&&Vencord.Plugins.plugins.ShowHiddenChannels.channelSelected($2?.props?.channel))return;$1" + match: /(?<this>\i)\.handleThreadsPopoutClose\(\);/, + replace: "if(arguments[0].button===0&&$self.channelSelected($<this>?.props?.channel))return;$&" } }, { - // Prevent categories from disappearing when they're collapsed - find: ".prototype.shouldShowEmptyCategory=function(){", - replacement: { - match: /(\.prototype\.shouldShowEmptyCategory=function\(\){)/g, - replace: "$1return true;" - } - }, - { - // Hide unreads - find: "?\"button\":\"link\"", + find: ".UNREAD_HIGHLIGHT", predicate: () => Settings.plugins.ShowHiddenChannels.hideUnreads === true, + replacement: [{ + // Hide unreads + match: /(?<restOfFunction>\i\.connected,)(?<hasUnread>\i)=(?<props>\i).unread/, + replace: "$<restOfFunction>$<hasUnread>=$self.isHiddenChannel($<props>.channel)?false:$<props>.unread" + }] + }, + { + find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY", + predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.LockIcon, replacement: { - match: /(\w)\.connected,(\w)=(\w\.unread),(\w=\w\.canHaveDot)/g, - replace: "$1.connected,$2=Vencord.Plugins.plugins.ShowHiddenChannels.isHiddenChannel($1.channel)?false:$3,$4" + // Lock Icon + match: /switch\((?<channel>\i)\.type\).{1,30}\.GUILD_ANNOUNCEMENT.{1,30}\(0,\i\.\i\)\(\i\)/, + replace: "if($self.isHiddenChannel($<channel>))return $self.LockIcon;$&" } }, + { + find: ".UNREAD_HIGHLIGHT", + predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.HiddenIconWithMutedStyle, + replacement: [ + // Make the channel appear as muted if it's hidden + { + match: /(?<restOfFunction>\i\.name,)(?<isMuted>\i)=(?<props>\i).muted/, + replace: "$<restOfFunction>$<isMuted>=$self.isHiddenChannel($<props>.channel)?true:$<props>.muted" + }, + // Add the hidden eye icon if the channel is hidden + { + match: /channel:(?<channel>\i),.+?\.channelName.+?\.children.+?:null/, + replace: "$&,$self.isHiddenChannel($<channel>)?$self.HiddenChannelIcon():null" + }, + // Make voice channels also appear as muted if they are muted + { + match: /(?<restOfFunction>.wrapper:\i\(\).notInteractive,)(?<secondRestOfFunction>.+?)(?<isMutedClassExpression>(?<isMuted>\i)\?\i\.MUTED:)/, + replace: "$<restOfFunction>$<isMutedClassExpression>\"\",$<secondRestOfFunction>$<isMuted>?\"\":" + } + ] + }, { // Hide New unreads box for hidden channels find: '.displayName="ChannelListUnreadsStore"', replacement: { - match: /((.)\.getGuildId\(\))(&&\(!\(.\.isThread.{1,100}\.hasRelevantUnread\()/, - replace: "$1&&!$2._isHiddenChannel$3" + match: /(?<restOfFunction>return null!=(?<channel>\i))(?<secondRestOfFunction>&&null!=\i\.getGuildId\(\).{1,120}hasRelevantUnread\(\i\)\))/, + replace: "$<restOfFunction>&&!$self.isHiddenChannel($<channel>)$<secondRestOfFunction>" } }, - // Lock Icon - { - find: ".rulesChannelId))", - replacement: { - match: /(\.locked.{0,400})(switch\((\i)\.type\))/, - replace: "$1 if($3._isHiddenChannel)return $self.LockIcon;$2" - } - } ], - shouldShow(channel, category, isMuted) { - if (!this.isHiddenChannel(channel)) return false; - if (!category) return false; - if (channel.type === 0 && category.guild?.hideMutedChannels && isMuted) return false; - - return !category.isCollapsed; - }, - isHiddenChannel(channel) { if (!channel) return false; - if (channel.channelId) - channel = ChannelStore.getChannel(channel.channelId); - if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) - return false; - // check for disallowed voice channels too so that they get hidden when collapsing the category - channel._isHiddenChannel = !PermissionStore.can(VIEW_CHANNEL, channel) || (channel.type === 2 && !PermissionStore.can(CONNECT, channel)); - return channel._isHiddenChannel; + if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); + if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; + + return !PermissionStore.can(VIEW_CHANNEL, channel); }, channelSelected(channel) { if (!channel) return false; + const isHidden = this.isHiddenChannel(channel); - // check for type again, otherwise it would show it for hidden stage channels - if (channel.type === 0 && isHidden) { - const lastMessageDate = channel.lastMessageId ? new Date(SnowflakeUtils.extractTimestamp(channel.lastMessageId)).toLocaleString() : null; + + // Check for type, otherwise it would attempt to show the modal for stage channels + if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type) && isHidden) { openModal(modalProps => ( <ModalRoot size={ModalSize.SMALL} {...modalProps}> <ModalHeader> <Flex> - <Text variant="heading-md/bold">{channel.name}</Text> + <Text variant="heading-md/bold">#{channel.name}</Text> + {<Badge text={ChannelTypesToChannelName[channel.type]} color="var(--brand-experiment)" />} {channel.isNSFW() && <Badge text="NSFW" color="var(--status-danger)" />} </Flex> </ModalHeader> <ModalContent style={{ marginBottom: 10, marginTop: 10, marginRight: 8, marginLeft: 8 }}> - <Text variant="text-md/normal">You don't have the permission to view the messages in this channel.</Text> - {(channel.topic || "").length > 0 && ( + <Text variant="text-md/normal">You don't have permission to view {channel.type === ChannelTypes.GUILD_FORUM ? "posts" : "messages"} in this channel.</Text> + {(channel.topic ?? "").length > 0 && ( <> <Text variant="text-md/bold" style={{ marginTop: 10 }}> - Topic: + {channel.type === ChannelTypes.GUILD_FORUM ? "Guidelines:" : "Topic:"} </Text> - <Text variant="code">{channel.topic}</Text> + <div style={{ color: "var(--text-normal)", marginTop: 10 }}> + {Parser.parseTopic(channel.topic, true, { channelId: channel.id })} + </div> </> )} - {lastMessageDate && ( + {channel.lastMessageId && ( <> <Text variant="text-md/bold" style={{ marginTop: 10 }}> - Last message sent: + {channel.type === ChannelTypes.GUILD_FORUM ? "Last Post Created" : "Last Message Sent:"} </Text> - <Text variant="code">{lastMessageDate}</Text> + <div style={{ color: "var(--text-normal)", marginTop: 10 }}> + <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(channel.lastMessageId))} /> + </div> </> )} </ModalContent> @@ -164,11 +216,34 @@ export default definePlugin({ LockIcon: () => ( <svg + className={ChannelListClasses.icon} height="18" width="20" viewBox="0 0 24 24" + aria-hidden={true} + role="img" > - <path fill="var(--channel-icon)" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> + <path fillRule="evenodd" fill="currentColor" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> </svg> + ), + + HiddenChannelIcon: () => ( + <Tooltip text="Hidden Channel"> + {({ onMouseLeave, onMouseEnter }) => ( + <svg + onMouseLeave={onMouseLeave} + onMouseEnter={onMouseEnter} + className={ChannelListClasses.icon} + width="24" + height="24" + viewBox="0 0 24 24" + aria-hidden={true} + role="img" + style={{ marginLeft: 6, zIndex: 0, cursor: "not-allowed" }} + > + <path fillRule="evenodd" fill="currentColor" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> + </svg> + )} + </Tooltip> ) }); diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx index 0be47ff7a..562332adb 100644 --- a/src/webpack/common.tsx +++ b/src/webpack/common.tsx @@ -75,6 +75,7 @@ export let Button: any; export const ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED") as Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; export let Switch: any; export let Tooltip: Components.Tooltip; +export let Timestamp: any; export let Router: any; export let TextInput: any; export let Text: (props: TextProps) => JSX.Element; @@ -185,6 +186,8 @@ waitFor(["Hovers", "Looks", "Sizes"], m => Button = m); waitFor(filters.byCode("tooltipNote", "ringTarget"), m => Switch = m); +waitFor(filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"), m => Timestamp = m); + waitFor(["Positions", "Colors"], m => Tooltip = m); waitFor(m => m.Types?.PRIMARY === "cardPrimary", m => Card = m); @@ -307,4 +310,3 @@ export const ContextMenu = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { openUntrustedLink: filters.byCode(".apply(this,arguments)") }); - From 83757b19be30811f54a4d31adecb741a343be345 Mon Sep 17 00:00:00 2001 From: megumin <megumin.bakaretsurie@gmail.com> Date: Mon, 23 Jan 2023 21:11:28 +0000 Subject: [PATCH 058/114] fix: emojis with duplicate names failing to clone (#449) --- src/plugins/emoteCloner.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/emoteCloner.tsx b/src/plugins/emoteCloner.tsx index c22eebdc5..bb744060f 100644 --- a/src/plugins/emoteCloner.tsx +++ b/src/plugins/emoteCloner.tsx @@ -57,7 +57,7 @@ async function doClone(guildId: string, id: string, name: string, isAnimated: bo reader.onload = () => { uploadEmoji({ guildId, - name, + name: name.split("~")[0], image: reader.result }).then(() => { Toasts.show({ From cb4c50842f36a1d23a18618741b787dc3dafdc68 Mon Sep 17 00:00:00 2001 From: Dominik <dominik.buettner1711@gmail.com> Date: Mon, 23 Jan 2023 22:25:00 +0100 Subject: [PATCH 059/114] [SpotifyControls] Add option to show Controls on hover (#352) Co-authored-by: Dominik <domi@bambus.me> Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/spotifyControls/index.tsx | 26 ++++++++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index 7ab1e3701..86e187e31 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -16,16 +16,38 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { Settings } from "@api/settings"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { Player } from "./PlayerComponent"; +function toggleHoverControls(value: boolean) { + document.getElementById("vc-spotify-hover-controls")?.remove(); + if (value) { + const style = document.createElement("style"); + style.id = "vc-spotify-hover-controls"; + style.textContent = ` +.vc-spotify-button-row { height: 0; opacity: 0; will-change: height, opacity; transition: height .2s, opacity .05s; } +#vc-spotify-player:hover .vc-spotify-button-row { opacity: 1; height: 32px; } +`; + document.head.appendChild(style); + } +} + export default definePlugin({ name: "SpotifyControls", description: "Spotify Controls", authors: [Devs.Ven, Devs.afn, Devs.KraXen72], dependencies: ["MenuItemDeobfuscatorAPI"], + options: { + hoverControls: { + description: "Show controls on hover", + type: OptionType.BOOLEAN, + default: false, + onChange: v => toggleHoverControls(v) + }, + }, patches: [ { find: "showTaglessAccountPanel:", @@ -53,6 +75,6 @@ export default definePlugin({ } } ], - + start: () => toggleHoverControls(Settings.plugins.SpotifyControls.hoverControls), renderPlayer: () => <Player /> }); From 25d32ce2922f276f1a7f67c8d3be978a8d360042 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 23 Jan 2023 22:43:25 +0100 Subject: [PATCH 060/114] Settings: Fix plugin switch state not updating (fixes #209) --- src/components/PluginSettings/index.tsx | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 58058b14a..4b6abdd92 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -92,12 +92,9 @@ interface PluginCardProps extends React.HTMLProps<HTMLDivElement> { } function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLeave, isNew }: PluginCardProps) { - const settings = useSettings(); - const pluginSettings = settings.plugins[plugin.name]; + const settings = useSettings([`plugins.${plugin.name}`]).plugins[plugin.name]; - function isEnabled() { - return pluginSettings?.enabled || plugin.started; - } + const isEnabled = () => settings.enabled ?? false; function openModal() { openModalLazy(async () => { @@ -119,7 +116,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe return; } else if (restartNeeded) { // If any dependencies have patches, don't start the plugin yet. - pluginSettings.enabled = true; + settings.enabled = true; onRestartNeeded(plugin.name); return; } @@ -127,14 +124,14 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe // if the plugin has patches, dont use stopPlugin/startPlugin. Wait for restart to apply changes. if (plugin.patches) { - pluginSettings.enabled = !wasEnabled; + settings.enabled = !wasEnabled; onRestartNeeded(plugin.name); return; } // If the plugin is enabled, but hasn't been started, then we can just toggle it off. if (wasEnabled && !plugin.started) { - pluginSettings.enabled = !wasEnabled; + settings.enabled = !wasEnabled; return; } @@ -147,7 +144,7 @@ function PluginCard({ plugin, disabled, onRestartNeeded, onMouseEnter, onMouseLe return; } - pluginSettings.enabled = !wasEnabled; + settings.enabled = !wasEnabled; } return ( From b2ecb02335fa51a7c7eadab0acb3beb91c289802 Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Tue, 24 Jan 2023 01:42:57 +0100 Subject: [PATCH 061/114] Make Windows Ctrl+Q feature optional; add opt-in auto update (#451) --- src/Vencord.ts | 26 ++++++++++-- src/api/settings.ts | 4 ++ src/components/VencordSettings/Updater.tsx | 22 +++++++++- src/components/VencordSettings/VencordTab.tsx | 21 ++++++---- src/patcher.ts | 42 ++++++++++--------- src/utils/updater.ts | 2 +- 6 files changed, 84 insertions(+), 33 deletions(-) diff --git a/src/Vencord.ts b/src/Vencord.ts index 48e628fde..82d5af0ad 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -30,7 +30,7 @@ import "./webpack/patchWebpack"; import { popNotice, showNotice } from "./api/Notices"; import { PlainSettings, Settings } from "./api/settings"; import { patches, PMLogger, startAllPlugins } from "./plugins"; -import { checkForUpdates, UpdateLogger } from "./utils/updater"; +import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; import { Router } from "./webpack/common"; @@ -44,7 +44,27 @@ async function init() { if (!IS_WEB) { try { const isOutdated = await checkForUpdates(); - if (isOutdated && Settings.notifyAboutUpdates) + if (!isOutdated) return; + + if (Settings.autoUpdate) { + await update(); + const needsFullRestart = await rebuild(); + setTimeout(() => { + showNotice( + "Vencord has been updated!", + "Restart", + () => { + if (needsFullRestart) + window.DiscordNative.app.relaunch(); + else + location.reload(); + } + ); + }, 10_000); + return; + } + + if (Settings.notifyAboutUpdates) setTimeout(() => { showNotice( "A Vencord update is available!", @@ -54,7 +74,7 @@ async function init() { Router.open("VencordUpdater"); } ); - }, 10000); + }, 10_000); } catch (err) { UpdateLogger.error("Failed to check for updates", err); } diff --git a/src/api/settings.ts b/src/api/settings.ts index 9bae8b73b..4cdb24b67 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -27,10 +27,12 @@ import plugins from "~plugins"; const logger = new Logger("Settings"); export interface Settings { notifyAboutUpdates: boolean; + autoUpdate: boolean; useQuickCss: boolean; enableReactDevtools: boolean; themeLinks: string[]; frameless: boolean; + winCtrlQ: boolean; plugins: { [plugin: string]: { enabled: boolean; @@ -41,10 +43,12 @@ export interface Settings { const DefaultSettings: Settings = { notifyAboutUpdates: true, + autoUpdate: false, useQuickCss: true, themeLinks: [], enableReactDevtools: false, frameless: false, + winCtrlQ: false, plugins: {} }; diff --git a/src/components/VencordSettings/Updater.tsx b/src/components/VencordSettings/Updater.tsx index dea876676..8a126a8de 100644 --- a/src/components/VencordSettings/Updater.tsx +++ b/src/components/VencordSettings/Updater.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { useSettings } from "@api/settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { ErrorCard } from "@components/ErrorCard"; import { Flex } from "@components/Flex"; @@ -23,7 +24,7 @@ import { handleComponentFailed } from "@components/handleComponentFailed"; import { Link } from "@components/Link"; import { classes, useAwaiter } from "@utils/misc"; import { changes, checkForUpdates, getRepo, isNewer, rebuild, update, updateError, UpdateLogger } from "@utils/updater"; -import { Alerts, Button, Card, Forms, Margins, Parser, React, Toasts } from "@webpack/common"; +import { Alerts, Button, Card, Forms, Margins, Parser, React, Switch, Toasts } from "@webpack/common"; import gitHash from "~git-hash"; @@ -183,6 +184,8 @@ function Newer(props: CommonProps) { } function Updater() { + const settings = useSettings(["notifyAboutUpdates", "autoUpdate"]); + const [repo, err, repoPending] = useAwaiter(getRepo, { fallbackValue: "Loading..." }); React.useEffect(() => { @@ -197,6 +200,23 @@ function Updater() { return ( <Forms.FormSection> + <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> + <Switch + value={settings.notifyAboutUpdates} + onChange={(v: boolean) => settings.notifyAboutUpdates = v} + note="Shows a toast on startup" + disabled={settings.autoUpdate} + > + Get notified about new updates + </Switch> + <Switch + value={settings.autoUpdate} + onChange={(v: boolean) => settings.autoUpdate = v} + note="Automatically update Vencord without confirmation prompt" + > + Automatically update + </Switch> + <Forms.FormTitle tag="h5">Repo</Forms.FormTitle> <Forms.FormText>{repoPending ? repo : err ? "Failed to retrieve - check console" : ( diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 9429cddc5..5da444205 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -97,21 +97,26 @@ function VencordSettings() { <Switch value={settings.enableReactDevtools} onChange={(v: boolean) => settings.enableReactDevtools = v} - note="Requires a full restart"> + note="Requires a full restart" + > Enable React Developer Tools </Switch> - <Switch - value={settings.notifyAboutUpdates} - onChange={(v: boolean) => settings.notifyAboutUpdates = v} - note="Shows a toast on startup"> - Get notified about new updates - </Switch> <Switch value={settings.frameless} onChange={(v: boolean) => settings.frameless = v} - note="Requires a full restart"> + note="Requires a full restart" + > Disable the window frame </Switch> + {navigator.platform.toLowerCase().startsWith("win") && ( + <Switch + value={settings.winCtrlQ} + onChange={(v: boolean) => settings.winCtrlQ = v} + note="Requires a full restart" + > + Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4) + </Switch> + )} </React.Fragment> )} diff --git a/src/patcher.ts b/src/patcher.ts index 82fc23336..2da404faa 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -43,33 +43,35 @@ require.main!.filename = join(asarPath, discordPkg.main); app.setAppPath(asarPath); if (!process.argv.includes("--vanilla")) { + let settings: typeof import("@api/settings").Settings = {} as any; + try { + settings = JSON.parse(readSettings()); + } catch { } + // Repatch after host updates on Windows if (process.platform === "win32") { require("./patchWin32Updater"); - const originalBuild = Menu.buildFromTemplate; - Menu.buildFromTemplate = function (template) { - if (template[0]?.label === "&File") { - const { submenu } = template[0]; - if (Array.isArray(submenu)) { - submenu.push({ - label: "Quit (Hidden)", - visible: false, - acceleratorWorksWhenHidden: true, - accelerator: "Control+Q", - click: () => app.quit() - }); + if (settings.winCtrlQ) { + const originalBuild = Menu.buildFromTemplate; + Menu.buildFromTemplate = function (template) { + if (template[0]?.label === "&File") { + const { submenu } = template[0]; + if (Array.isArray(submenu)) { + submenu.push({ + label: "Quit (Hidden)", + visible: false, + acceleratorWorksWhenHidden: true, + accelerator: "Control+Q", + click: () => app.quit() + }); + } } - } - return originalBuild.call(this, template); - }; + return originalBuild.call(this, template); + }; + } } - let settings = {} as any; - try { - settings = JSON.parse(readSettings()); - } catch { } - class BrowserWindow extends electron.BrowserWindow { constructor(options: BrowserWindowConstructorOptions) { if (options?.webPreferences?.preload && options.title) { diff --git a/src/utils/updater.ts b/src/utils/updater.ts index 04205a556..9fdec471e 100644 --- a/src/utils/updater.ts +++ b/src/utils/updater.ts @@ -22,7 +22,7 @@ import IpcEvents from "./IpcEvents"; import Logger from "./Logger"; import { IpcRes } from "./types"; -export const UpdateLogger = new Logger("Updater", "white"); +export const UpdateLogger = /* #__PURE__*/ new Logger("Updater", "white"); export let isOutdated = false; export let isNewer = false; export let updateError: any; From 34276301c31dc1b464d79a07e25a347968e78313 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 24 Jan 2023 13:35:57 +0100 Subject: [PATCH 062/114] Fix Settings UI (Discord removed default margins --- src/components/PluginSettings/index.tsx | 2 +- src/components/VencordSettings/BackupRestoreTab.tsx | 2 +- src/components/VencordSettings/ThemesTab.tsx | 2 +- src/components/VencordSettings/Updater.tsx | 4 ++-- src/components/VencordSettings/VencordTab.tsx | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 4b6abdd92..f439753c1 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -296,7 +296,7 @@ export default ErrorBoundary.wrap(function PluginSettings() { } return ( - <Forms.FormSection> + <Forms.FormSection className={Margins.marginTop16}> <ReloadRequiredCard required={changes.hasChanges} /> <Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}> diff --git a/src/components/VencordSettings/BackupRestoreTab.tsx b/src/components/VencordSettings/BackupRestoreTab.tsx index c7dc7d2fc..546db35f2 100644 --- a/src/components/VencordSettings/BackupRestoreTab.tsx +++ b/src/components/VencordSettings/BackupRestoreTab.tsx @@ -24,7 +24,7 @@ import { Button, Card, Forms, Margins, Text } from "@webpack/common"; function BackupRestoreTab() { return ( - <Forms.FormSection title="Settings Sync"> + <Forms.FormSection title="Settings Sync" className={Margins.marginTop16}> <Card className={classes("vc-settings-card", "vc-backup-restore-card")}> <Flex flexDirection="column"> <strong>Warning</strong> diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 738715f3c..69fcc2942 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -93,7 +93,7 @@ export default ErrorBoundary.wrap(function () { <Forms.FormTitle tag="h5">Paste links to .css / .theme.css files here</Forms.FormTitle> <Forms.FormText>One link per line</Forms.FormText> <Forms.FormText>Make sure to use the raw links or github.io links!</Forms.FormText> - <Forms.FormDivider /> + <Forms.FormDivider className={Margins.marginTop8 + " " + Margins.marginBottom8} /> <Forms.FormTitle tag="h5">Find Themes:</Forms.FormTitle> <div style={{ marginBottom: ".5em" }}> <Link style={{ marginRight: ".5em" }} href="https://betterdiscord.app/themes"> diff --git a/src/components/VencordSettings/Updater.tsx b/src/components/VencordSettings/Updater.tsx index 8a126a8de..b5243f2d5 100644 --- a/src/components/VencordSettings/Updater.tsx +++ b/src/components/VencordSettings/Updater.tsx @@ -199,7 +199,7 @@ function Updater() { }; return ( - <Forms.FormSection> + <Forms.FormSection className={Margins.marginTop16}> <Forms.FormTitle tag="h5">Updater Settings</Forms.FormTitle> <Switch value={settings.notifyAboutUpdates} @@ -225,7 +225,7 @@ function Updater() { </Link> )} (<HashLink hash={gitHash} repo={repo} disabled={repoPending} />)</Forms.FormText> - <Forms.FormDivider /> + <Forms.FormDivider className={Margins.marginTop8 + " " + Margins.marginBottom8} /> <Forms.FormTitle tag="h5">Updates</Forms.FormTitle> diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 5da444205..e1632b197 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -82,7 +82,7 @@ function VencordSettings() { <Forms.FormDivider /> - <Forms.FormSection title="Settings"> + <Forms.FormSection className={Margins.marginTop16} title="Settings"> <Forms.FormText className={Margins.marginBottom20}> Hint: You can change the position of this settings section in the settings of the "Settings" plugin! </Forms.FormText> From a38ac956dfaf53711a4cddea73ae1b8cf617211a Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 24 Jan 2023 13:50:02 +0100 Subject: [PATCH 063/114] chore: Remove legacy workarounds --- build.mjs | 3 --- docs/1_INSTALLING.md | 3 +-- src/ipcMain/index.ts | 1 - src/ipcMain/legacy.ts | 31 ------------------------------- src/ipcMain/updater/http.ts | 5 +---- src/patcher.ts | 20 +------------------- src/preload.ts | 28 +++++++++------------------- src/utils/IpcEvents.ts | 1 - 8 files changed, 12 insertions(+), 80 deletions(-) delete mode 100644 build.mjs delete mode 100644 src/ipcMain/legacy.ts diff --git a/build.mjs b/build.mjs deleted file mode 100644 index 44c18e94a..000000000 --- a/build.mjs +++ /dev/null @@ -1,3 +0,0 @@ -// FIXME: Delete this soon, for now it is needed so people can update - -import("./scripts/build/build.mjs"); diff --git a/docs/1_INSTALLING.md b/docs/1_INSTALLING.md index f27dfc14f..96f1bed56 100644 --- a/docs/1_INSTALLING.md +++ b/docs/1_INSTALLING.md @@ -1,5 +1,5 @@ > **Warning** -> These instructions are only for advanced users. If you're not a Developer, you should use our [graphical installer](https://github.com/Vendicated/VencordInstaller#usage) instead. +> These instructions are only for advanced users. If you're not a Developer, you should use our [graphical installer](https://github.com/Vendicated/VencordInstaller#usage) instead. # Installation Guide @@ -183,7 +183,6 @@ In `index.js`: ```js require("C:/Users/<your user>/path/to/vencord/dist/patcher.js"); -require("../app.asar"); ``` And in `package.json`: diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts index 0946f5af7..bae679330 100644 --- a/src/ipcMain/index.ts +++ b/src/ipcMain/index.ts @@ -16,7 +16,6 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ -import "./legacy"; import "./updater"; import { debounce } from "@utils/debounce"; diff --git a/src/ipcMain/legacy.ts b/src/ipcMain/legacy.ts deleted file mode 100644 index 567ad3d06..000000000 --- a/src/ipcMain/legacy.ts +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 <https://www.gnu.org/licenses/>. -*/ - -import IpcEvents from "@utils/IpcEvents"; -import { ipcMain } from "electron"; -import { writeFile } from "fs/promises"; -import { join } from "path"; - -import { get } from "./simpleGet"; - -ipcMain.handleOnce(IpcEvents.DOWNLOAD_VENCORD_CSS, async () => { - const buf = await get("https://github.com/Vendicated/Vencord/releases/download/devbuild/renderer.css"); - await writeFile(join(__dirname, "renderer.css"), buf); - return buf.toString("utf-8"); -}); - diff --git a/src/ipcMain/updater/http.ts b/src/ipcMain/updater/http.ts index 3b3814477..25dc5bada 100644 --- a/src/ipcMain/updater/http.ts +++ b/src/ipcMain/updater/http.ts @@ -37,10 +37,7 @@ async function githubGet(endpoint: string) { Accept: "application/vnd.github+json", // "All API requests MUST include a valid User-Agent header. // Requests with no User-Agent header will be rejected." - "User-Agent": VENCORD_USER_AGENT, - // todo: perhaps add support for (optional) api token? - // unauthorised rate limit is 60 reqs/h - // https://github.com/settings/tokens/new?description=Vencord%20Updater + "User-Agent": VENCORD_USER_AGENT } }); } diff --git a/src/patcher.ts b/src/patcher.ts index 2da404faa..64bc50266 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -18,7 +18,6 @@ import { onceDefined } from "@utils/onceDefined"; import electron, { app, BrowserWindowConstructorOptions, Menu } from "electron"; -import { readFileSync } from "fs"; import { dirname, join } from "path"; import { initIpc } from "./ipcMain"; @@ -187,21 +186,4 @@ if (!process.argv.includes("--vanilla")) { } console.log("[Vencord] Loading original Discord app.asar"); -// Legacy Vencord Injector requires "../app.asar". However, because we -// restore the require.main above this is messed up, so monkey patch Module._load to -// redirect such requires -// FIXME: remove this eventually -if (readFileSync(injectorPath, "utf-8").includes('require("../app.asar")')) { - console.warn("[Vencord] [--> WARNING <--] You have a legacy Vencord install. Please reinject"); - const Module = require("module"); - const loadModule = Module._load; - Module._load = function (path: string) { - if (path === "../app.asar") { - Module._load = loadModule; - arguments[0] = require.main!.filename; - } - return loadModule.apply(this, arguments); - }; -} else { - require(require.main!.filename); -} +require(require.main!.filename); diff --git a/src/preload.ts b/src/preload.ts index 3867f336c..820b65547 100644 --- a/src/preload.ts +++ b/src/preload.ts @@ -18,7 +18,7 @@ import { debounce } from "@utils/debounce"; import IpcEvents from "@utils/IpcEvents"; -import { contextBridge, ipcRenderer, webFrame } from "electron"; +import { contextBridge, webFrame } from "electron"; import { readFileSync, watch } from "fs"; import { join } from "path"; @@ -45,24 +45,14 @@ if (location.protocol !== "data:") { } } - try { - const css = readFileSync(rendererCss, "utf-8"); - insertCss(css); - if (IS_DEV) { - // persistent means keep process running if watcher is the only thing still running - // which we obviously don't want - watch(rendererCss, { persistent: false }, () => { - document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8"); - }); - } - } catch (err) { - if ((err as NodeJS.ErrnoException)?.code !== "ENOENT") - throw err; - - // hack: the pre update updater does not download this file, so manually download it - // TODO: remove this in a future version - ipcRenderer.invoke(IpcEvents.DOWNLOAD_VENCORD_CSS) - .then(insertCss); + const css = readFileSync(rendererCss, "utf-8"); + insertCss(css); + if (IS_DEV) { + // persistent means keep process running if watcher is the only thing still running + // which we obviously don't want + watch(rendererCss, { persistent: false }, () => { + document.getElementById("vencord-css-core")!.textContent = readFileSync(rendererCss, "utf-8"); + }); } require(process.env.DISCORD_PRELOAD!); } else { diff --git a/src/utils/IpcEvents.ts b/src/utils/IpcEvents.ts index e97e41b0f..dbf3540c3 100644 --- a/src/utils/IpcEvents.ts +++ b/src/utils/IpcEvents.ts @@ -44,5 +44,4 @@ export default strEnum({ UPDATE: "VencordUpdate", BUILD: "VencordBuild", OPEN_MONACO_EDITOR: "VencordOpenMonacoEditor", - DOWNLOAD_VENCORD_CSS: "VencordDownloadVencordCss" } as const); From f19504f8282702bc6945a3d97acbee1a1fbe1b8d Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Wed, 25 Jan 2023 03:25:29 +0100 Subject: [PATCH 064/114] split up webpack commons into categories & type everything (#455) --- src/Vencord.ts | 4 +- src/components/PluginSettings/index.tsx | 4 +- .../VencordSettings/BackupRestoreTab.tsx | 2 +- src/components/VencordSettings/ThemesTab.tsx | 2 +- src/components/VencordSettings/index.tsx | 4 +- src/ipcMain/updater/git.ts | 2 +- src/ipcMain/updater/http.ts | 2 +- src/plugins/settings.tsx | 4 +- src/plugins/spotifyControls/SpotifyStore.ts | 4 - src/plugins/viewRaw.tsx | 2 +- src/utils/misc.tsx | 28 -- src/utils/modal.tsx | 76 ++++- src/webpack/common.tsx | 312 ------------------ src/webpack/common/components.ts | 53 +++ src/webpack/common/index.ts | 27 ++ src/webpack/common/internal.tsx | 36 ++ src/webpack/common/menu.ts | 51 +++ src/webpack/common/react.ts | 33 ++ src/webpack/common/stores.ts | 54 +++ src/webpack/common/types/components.d.ts | 284 ++++++++++++++++ src/webpack/common/types/fluxEvents.d.ts | 40 +++ src/webpack/common/types/menu.d.ts | 68 ++++ src/webpack/common/types/utils.d.ts | 98 ++++++ src/webpack/common/utils.ts | 112 +++++++ 24 files changed, 930 insertions(+), 372 deletions(-) delete mode 100644 src/webpack/common.tsx create mode 100644 src/webpack/common/components.ts create mode 100644 src/webpack/common/index.ts create mode 100644 src/webpack/common/internal.tsx create mode 100644 src/webpack/common/menu.ts create mode 100644 src/webpack/common/react.ts create mode 100644 src/webpack/common/stores.ts create mode 100644 src/webpack/common/types/components.d.ts create mode 100644 src/webpack/common/types/fluxEvents.d.ts create mode 100644 src/webpack/common/types/menu.d.ts create mode 100644 src/webpack/common/types/utils.d.ts create mode 100644 src/webpack/common/utils.ts diff --git a/src/Vencord.ts b/src/Vencord.ts index 82d5af0ad..ac8579bfe 100644 --- a/src/Vencord.ts +++ b/src/Vencord.ts @@ -32,7 +32,7 @@ import { PlainSettings, Settings } from "./api/settings"; import { patches, PMLogger, startAllPlugins } from "./plugins"; import { checkForUpdates, rebuild, update, UpdateLogger } from "./utils/updater"; import { onceReady } from "./webpack"; -import { Router } from "./webpack/common"; +import { SettingsRouter } from "./webpack/common"; export let Components: any; @@ -71,7 +71,7 @@ async function init() { "View Update", () => { popNotice(); - Router.open("VencordUpdater"); + SettingsRouter.open("VencordUpdater"); } ); }, 10_000); diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index f439753c1..34e6828f7 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -326,7 +326,9 @@ export default ErrorBoundary.wrap(function PluginSettings() { <div className={cl("grid")}> {plugins} </div> - <Forms.FormDivider /> + + <Forms.FormDivider className={Margins.marginTop20} /> + <Forms.FormTitle tag="h5" className={classes(Margins.marginTop20, Margins.marginBottom8)}> Required Plugins </Forms.FormTitle> diff --git a/src/components/VencordSettings/BackupRestoreTab.tsx b/src/components/VencordSettings/BackupRestoreTab.tsx index 546db35f2..2ea04527a 100644 --- a/src/components/VencordSettings/BackupRestoreTab.tsx +++ b/src/components/VencordSettings/BackupRestoreTab.tsx @@ -45,7 +45,7 @@ function BackupRestoreTab() { </Text> <Flex> <Button - onClick={uploadSettingsBackup} + onClick={() => uploadSettingsBackup()} size={Button.Sizes.SMALL} > Import Settings diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index 69fcc2942..b59590c46 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -75,7 +75,7 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { export default ErrorBoundary.wrap(function () { const settings = useSettings(); - const ref = React.useRef<HTMLTextAreaElement>(); + const ref = React.useRef<HTMLTextAreaElement>(null); function onBlur() { settings.themeLinks = [...new Set( diff --git a/src/components/VencordSettings/index.tsx b/src/components/VencordSettings/index.tsx index 2ab94070c..acd81c36a 100644 --- a/src/components/VencordSettings/index.tsx +++ b/src/components/VencordSettings/index.tsx @@ -21,7 +21,7 @@ import "./settingsStyles.css"; import { classNameFactory } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { findByCodeLazy } from "@webpack"; -import { Forms, Router, Text } from "@webpack/common"; +import { Forms, SettingsRouter, Text } from "@webpack/common"; import BackupRestoreTab from "./BackupRestoreTab"; import PluginsTab from "./PluginsTab"; @@ -65,7 +65,7 @@ function Settings(props: SettingsProps) { look={TabBar.Looks.BRAND} className={cl("tab-bar")} selectedItem={tab} - onItemSelect={Router.open} + onItemSelect={SettingsRouter.open} > {Object.entries(SettingsTabs).map(([key, { name, component }]) => { if (!component) return null; diff --git a/src/ipcMain/updater/git.ts b/src/ipcMain/updater/git.ts index e787b8f46..89c2d3ccf 100644 --- a/src/ipcMain/updater/git.ts +++ b/src/ipcMain/updater/git.ts @@ -28,7 +28,7 @@ const VENCORD_SRC_DIR = join(__dirname, ".."); const execFile = promisify(cpExecFile); -const isFlatpak = Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord")); +const isFlatpak = process.platform === "linux" && Boolean(process.env.FLATPAK_ID?.includes("discordapp") || process.env.FLATPAK_ID?.includes("Discord")); if (process.platform === "darwin") process.env.PATH = `/usr/local/bin:${process.env.PATH}`; diff --git a/src/ipcMain/updater/http.ts b/src/ipcMain/updater/http.ts index 25dc5bada..cc106316f 100644 --- a/src/ipcMain/updater/http.ts +++ b/src/ipcMain/updater/http.ts @@ -49,7 +49,7 @@ async function calculateGitChanges() { const res = await githubGet(`/compare/${gitHash}...HEAD`); const data = JSON.parse(res.toString("utf-8")); - return data.commits.map(c => ({ + return data.commits.map((c: any) => ({ // github api only sends the long sha hash: c.sha.slice(0, 7), author: c.author.login, diff --git a/src/plugins/settings.tsx b/src/plugins/settings.tsx index 36bf52576..67d1f8de2 100644 --- a/src/plugins/settings.tsx +++ b/src/plugins/settings.tsx @@ -22,7 +22,7 @@ import { Devs } from "@utils/constants"; import Logger from "@utils/Logger"; import { LazyComponent } from "@utils/misc"; import definePlugin, { OptionType } from "@utils/types"; -import { Router } from "@webpack/common"; +import { SettingsRouter } from "@webpack/common"; import gitHash from "~git-hash"; @@ -69,7 +69,7 @@ export default definePlugin({ }], makeSettingsCategories({ ID }: { ID: Record<string, unknown>; }) { - const makeOnClick = (tab: string) => () => Router.open(tab); + const makeOnClick = (tab: string) => () => SettingsRouter.open(tab); const cats = [ { diff --git a/src/plugins/spotifyControls/SpotifyStore.ts b/src/plugins/spotifyControls/SpotifyStore.ts index 641ba1ac2..ceac57707 100644 --- a/src/plugins/spotifyControls/SpotifyStore.ts +++ b/src/plugins/spotifyControls/SpotifyStore.ts @@ -76,10 +76,6 @@ export const SpotifyStore = proxyLazy(() => { const API_BASE = "https://api.spotify.com/v1/me/player"; class SpotifyStore extends Store { - constructor(dispatcher: any, handlers: any) { - super(dispatcher, handlers); - } - public mPosition = 0; private start = 0; diff --git a/src/plugins/viewRaw.tsx b/src/plugins/viewRaw.tsx index fc7a42a00..510520312 100644 --- a/src/plugins/viewRaw.tsx +++ b/src/plugins/viewRaw.tsx @@ -98,7 +98,7 @@ function openViewRawModal(msg: Message) { <> <Forms.FormTitle tag="h5">Content</Forms.FormTitle> <CodeBlock content={msg.content} lang="" /> - <Forms.FormDivider classes={Margins.marginBottom20} /> + <Forms.FormDivider className={Margins.marginBottom20} /> </> )} diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index 0ef7ffb94..c64d9e1f6 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -152,34 +152,6 @@ export function sleep(ms: number): Promise<void> { return new Promise(r => setTimeout(r, ms)); } -/** - * Wraps a Function into a try catch block and logs any errors caught - * Due to the nature of this function, not all paths return a result. - * Thus, for consistency, the returned functions will always return void or Promise<void> - * - * @param name Name identifying the wrapped function. This will appear in the logged errors - * @param func Function (async or sync both work) - * @param thisObject Optional thisObject - * @returns Wrapped Function - */ -export function suppressErrors<F extends Function>(name: string, func: F, thisObject?: any): F { - return (func.constructor.name === "AsyncFunction" - ? async function (this: any) { - try { - await func.apply(thisObject ?? this, arguments); - } catch (e) { - console.error(`Caught an Error in ${name || "anonymous"}\n`, e); - } - } - : function (this: any) { - try { - func.apply(thisObject ?? this, arguments); - } catch (e) { - console.error(`Caught an Error in ${name || "anonymous"}\n`, e); - } - }) as any as F; -} - /** * Wrap the text in ``` with an optional language */ diff --git a/src/utils/modal.tsx b/src/utils/modal.tsx index 73dd009c9..3174cace0 100644 --- a/src/utils/modal.tsx +++ b/src/utils/modal.tsx @@ -17,6 +17,9 @@ */ import { filters, mapMangledModuleLazy } from "@webpack"; +import type { ComponentType, PropsWithChildren, ReactNode, Ref } from "react"; + +import { LazyComponent } from "./misc"; export enum ModalSize { SMALL = "small", @@ -44,16 +47,7 @@ export interface ModalOptions { onCloseCallback?: (() => void); } -interface ModalRootProps { - transitionState: ModalTransitionState; - children: React.ReactNode; - size?: ModalSize; - role?: "alertdialog" | "dialog"; - className?: string; - onAnimationEnd?(): string; -} - -type RenderFunction = (props: ModalProps) => React.ReactNode; +type RenderFunction = (props: ModalProps) => ReactNode; export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", { ModalRoot: filters.byCode(".root"), @@ -61,13 +55,63 @@ export const Modals = mapMangledModuleLazy(".closeWithCircleBackground", { ModalContent: filters.byCode(".content"), ModalFooter: filters.byCode(".footerSeparator"), ModalCloseButton: filters.byCode(".closeWithCircleBackground"), -}); +}) as { + ModalRoot: ComponentType<PropsWithChildren<{ + transitionState: ModalTransitionState; + size?: ModalSize; + role?: "alertdialog" | "dialog"; + className?: string; + fullscreenOnMobile?: boolean; + "aria-label"?: string; + "aria-labelledby"?: string; + onAnimationEnd?(): string; + }>>; + ModalHeader: ComponentType<PropsWithChildren<{ + /** Flex.Justify.START */ + justify?: string; + /** Flex.Direction.HORIZONTAL */ + direction?: string; + /** Flex.Align.CENTER */ + align?: string; + /** Flex.Wrap.NO_WRAP */ + wrap?: string; + separator?: boolean; -export const ModalRoot = (props: ModalRootProps) => <Modals.ModalRoot {...props} />; -export const ModalHeader = (props: any) => <Modals.ModalHeader {...props} />; -export const ModalContent = (props: any) => <Modals.ModalContent {...props} />; -export const ModalFooter = (props: any) => <Modals.ModalFooter {...props} />; -export const ModalCloseButton = (props: any) => <Modals.ModalCloseButton {...props} />; + className?: string; + }>>; + /** This also accepts Scroller props but good luck with that */ + ModalContent: ComponentType<PropsWithChildren<{ + className?: string; + scrollerRef?: Ref<HTMLElement>; + [prop: string]: any; + }>>; + ModalFooter: ComponentType<PropsWithChildren<{ + /** Flex.Justify.START */ + justify?: string; + /** Flex.Direction.HORIZONTAL_REVERSE */ + direction?: string; + /** Flex.Align.STRETCH */ + align?: string; + /** Flex.Wrap.NO_WRAP */ + wrap?: string; + separator?: boolean; + + className?: string; + }>>; + ModalCloseButton: ComponentType<{ + focusProps?: any; + onClick(): void; + withCircleBackground?: boolean; + hideOnFullscreen?: boolean; + className?: string; + }>; +}; + +export const ModalRoot = LazyComponent(() => Modals.ModalRoot); +export const ModalHeader = LazyComponent(() => Modals.ModalHeader); +export const ModalContent = LazyComponent(() => Modals.ModalContent); +export const ModalFooter = LazyComponent(() => Modals.ModalFooter); +export const ModalCloseButton = LazyComponent(() => Modals.ModalCloseButton); const ModalAPI = mapMangledModuleLazy("onCloseRequest:null!=", { openModal: filters.byCode("onCloseRequest:null!="), diff --git a/src/webpack/common.tsx b/src/webpack/common.tsx deleted file mode 100644 index 562332adb..000000000 --- a/src/webpack/common.tsx +++ /dev/null @@ -1,312 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Vendicated and contributors - * - * 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 <https://www.gnu.org/licenses/>. -*/ - -import { LazyComponent } from "@utils/misc"; -import { proxyLazy } from "@utils/proxyLazy"; -import { - _resolveReady, - filters, findByCode, findByCodeLazy, findByPropsLazy, mapMangledModule, mapMangledModuleLazy, waitFor -} from "@webpack"; -import type Components from "discord-types/components"; -import { User } from "discord-types/general"; -import type Other from "discord-types/other"; -import type Stores from "discord-types/stores"; - -export const Margins = findByPropsLazy("marginTop20"); - -export let FluxDispatcher: Other.FluxDispatcher; -export const Flux = findByPropsLazy("connectStores"); - -export let React: typeof import("react"); -export let useState: typeof React.useState; -export let useEffect: typeof React.useEffect; -export let useMemo: typeof React.useMemo; -export let useRef: typeof React.useRef; - -export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render"); - -export const RestAPI = findByPropsLazy("getAPIBaseURL", "get"); -export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); - -export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight"); - -export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & { - getMessages(chanId: string): any; -}; -export const PermissionStore = findByPropsLazy("can", "getGuildPermissions"); -export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel"); -export const GuildChannelStore = findByPropsLazy("getChannels"); -export const ReadStateStore = findByPropsLazy("lastMessageId"); -export const PresenceStore = findByPropsLazy("setCurrentUserOnConnectionOpen"); -export let GuildStore: Stores.GuildStore; -export let UserStore: Stores.UserStore; -export let SelectedChannelStore: Stores.SelectedChannelStore; -export let SelectedGuildStore: any; -export let ChannelStore: Stores.ChannelStore; -export let GuildMemberStore: Stores.GuildMemberStore; -export let RelationshipStore: Stores.RelationshipStore & { - /** Get the date (as a string) that the relationship was created */ - getSince(userId: string): string; -}; - -export const Forms = {} as { - FormTitle: Components.FormTitle; - FormSection: any; - FormDivider: any; - FormText: Components.FormText; -}; -export let Card: Components.Card; -export let Button: any; -export const ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED") as Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; -export let Switch: any; -export let Tooltip: Components.Tooltip; -export let Timestamp: any; -export let Router: any; -export let TextInput: any; -export let Text: (props: TextProps) => JSX.Element; -export const TextArea = findByCodeLazy("handleSetRef", "textArea") as React.ComponentType<React.PropsWithRef<any>>; -export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>; - -export const Select = LazyComponent(() => findByCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); -export const Slider = LazyComponent(() => findByCode("closestMarkerIndex", "stickToMarkers")); - -export let SnowflakeUtils: { fromTimestamp: (timestamp: number) => string, extractTimestamp: (snowflake: string) => number; }; -waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); - -export let Parser: any; -export let Alerts: { - show(alert: { - title: any; - body: React.ReactNode; - className?: string; - confirmColor?: string; - cancelText?: string; - confirmText?: string; - secondaryConfirmText?: string; - onCancel?(): void; - onConfirm?(): void; - onConfirmSecondary?(): void; - }): void; - /** This is a noop, it does nothing. */ - close(): void; -}; -const ToastType = { - MESSAGE: 0, - SUCCESS: 1, - FAILURE: 2, - CUSTOM: 3 -}; -const ToastPosition = { - TOP: 0, - BOTTOM: 1 -}; - -export const Toasts = { - Type: ToastType, - Position: ToastPosition, - // what's less likely than getting 0 from Math.random()? Getting it twice in a row - genId: () => (Math.random() || Math.random()).toString(36).slice(2), - - // hack to merge with the following interface, dunno if there's a better way - ...{} as { - show(data: { - message: string, - id: string, - /** - * Toasts.Type - */ - type: number, - options?: { - /** - * Toasts.Position - */ - position?: number; - component?: React.ReactNode, - duration?: number; - }; - }): void; - pop(): void; - } -}; - -export const UserUtils = { - fetchUser: findByCodeLazy(".USER(", "getUser") as (id: string) => Promise<User>, -}; - -export const Clipboard = mapMangledModuleLazy('document.queryCommandEnabled("copy")||document.queryCommandSupported("copy")', { - copy: filters.byCode(".default.copy("), - SUPPORTS_COPY: x => typeof x === "boolean", -}); - -export const NavigationRouter = mapMangledModuleLazy("Transitioning to external path", { - transitionTo: filters.byCode("Transitioning to external path"), - transitionToGuild: filters.byCode("transitionToGuild"), - goBack: filters.byCode("goBack()"), - goForward: filters.byCode("goForward()"), -}); - -waitFor("useState", m => { - React = m; - ({ useEffect, useState, useMemo, useRef } = React); -}); - -waitFor(["dispatch", "subscribe"], m => { - FluxDispatcher = m; - const cb = () => { - m.unsubscribe("CONNECTION_OPEN", cb); - _resolveReady(); - }; - m.subscribe("CONNECTION_OPEN", cb); -}); - -waitFor(["getCurrentUser", "initialize"], m => UserStore = m); -waitFor("getSortedPrivateChannels", m => ChannelStore = m); -waitFor("getCurrentlySelectedChannelId", m => SelectedChannelStore = m); -waitFor("getLastSelectedGuildId", m => SelectedGuildStore = m); -waitFor("getGuildCount", m => GuildStore = m); -waitFor(["getMember", "initialize"], m => GuildMemberStore = m); -waitFor("getRelationshipType", m => RelationshipStore = m); - -waitFor(["Hovers", "Looks", "Sizes"], m => Button = m); - -waitFor(filters.byCode("tooltipNote", "ringTarget"), m => Switch = m); - -waitFor(filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format"), m => Timestamp = m); - -waitFor(["Positions", "Colors"], m => Tooltip = m); -waitFor(m => m.Types?.PRIMARY === "cardPrimary", m => Card = m); - -waitFor(filters.byCode("errorSeparator"), m => Forms.FormTitle = m); -waitFor(filters.byCode("titleClassName", "sectionTitle"), m => Forms.FormSection = m); -waitFor(m => m.Types?.INPUT_PLACEHOLDER, m => Forms.FormText = m); - -waitFor(m => { - if (typeof m !== "function") return false; - const s = m.toString(); - return s.length < 200 && s.includes(".divider"); -}, m => Forms.FormDivider = m); - -// This is the same module but this is easier -waitFor(filters.byCode("currentToast?"), m => Toasts.show = m); -waitFor(filters.byCode("currentToast:null"), m => Toasts.pop = m); - -waitFor(["show", "close"], m => Alerts = m); -waitFor("parseTopic", m => Parser = m); - -waitFor(["open", "saveAccountChanges"], m => Router = m); -waitFor(["defaultProps", "Sizes", "contextType"], m => TextInput = m); - -waitFor(m => { - if (typeof m !== "function") return false; - const s = m.toString(); - return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white")); -}, m => Text = m); - -export type TextProps = React.PropsWithChildren & { - variant: TextVariant; - style?: React.CSSProperties; - color?: string; - tag?: "div" | "span" | "p" | "strong" | `h${1 | 2 | 3 | 4 | 5 | 6}`; - selectable?: boolean; - lineClamp?: number; - id?: string; - className?: string; -}; - -export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; - -type RC<C> = React.ComponentType<React.PropsWithChildren<C & Record<string, any>>>; -interface Menu { - ContextMenu: RC<{ - navId: string; - onClose(): void; - className?: string; - style?: React.CSSProperties; - hideScroller?: boolean; - onSelect?(): void; - }>; - MenuSeparator: React.ComponentType; - MenuGroup: RC<any>; - MenuItem: RC<{ - id: string; - label: string; - render?: React.ComponentType; - onChildrenScroll?: Function; - childRowHeight?: number; - listClassName?: string; - }>; - MenuCheckboxItem: RC<{ - id: string; - }>; - MenuRadioItem: RC<{ - id: string; - }>; - MenuControlItem: RC<{ - id: string; - interactive?: boolean; - }>; -} - -/** - * Discord's Context menu items. - * To use anything but Menu.ContextMenu, your plugin HAS TO - * depend on MenuItemDeobfuscatorAPI. Otherwise they will throw - */ -export const Menu = proxyLazy(() => { - const hasDeobfuscator = Vencord.Settings.plugins.MenuItemDeobfuscatorAPI.enabled; - const menuItems = ["MenuSeparator", "MenuGroup", "MenuItem", "MenuCheckboxItem", "MenuRadioItem", "MenuControlItem"]; - - const map = mapMangledModule("♫ ⊂(。◕‿‿◕。⊂) ♪", { - ContextMenu: filters.byCode("getContainerProps"), - ...Object.fromEntries((hasDeobfuscator ? menuItems : []).map(s => [s, (m: any) => m.name === s])) - }) as Menu; - - if (!hasDeobfuscator) { - for (const m of menuItems) - Object.defineProperty(map, m, { - get() { - throw new Error("MenuItemDeobfuscator must be enabled to use this."); - } - }); - } - - return map; -}); - -export const ContextMenu = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { - open: filters.byCode("stopPropagation"), - openLazy: m => m.toString().length < 50, - close: filters.byCode("CONTEXT_MENU_CLOSE") -}) as { - close(): void; - open( - event: React.UIEvent, - render?: Menu["ContextMenu"], - options?: { enableSpellCheck?: boolean; }, - renderLazy?: () => Promise<Menu["ContextMenu"]> - ): void; - openLazy( - event: React.UIEvent, - renderLazy?: () => Promise<Menu["ContextMenu"]>, - options?: { enableSpellCheck?: boolean; } - ): void; -}; - -export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { - openUntrustedLink: filters.byCode(".apply(this,arguments)") -}); diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts new file mode 100644 index 000000000..be585c3a4 --- /dev/null +++ b/src/webpack/common/components.ts @@ -0,0 +1,53 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +// eslint-disable-next-line path-alias/no-relative +import { filters, findByPropsLazy } from "../webpack"; +import { waitForComponent } from "./internal"; +import * as t from "./types/components"; + +export const Forms = { + FormTitle: waitForComponent<t.FormTitle>("FormTitle", filters.byCode("errorSeparator")), + FormSection: waitForComponent<t.FormSection>("FormSection", filters.byCode("titleClassName", "sectionTitle")), + FormDivider: waitForComponent<t.FormDivider>("FormDivider", m => { + if (typeof m !== "function") return false; + const s = m.toString(); + return s.length < 200 && s.includes(".divider"); + }), + FormText: waitForComponent<t.FormText>("FormText", m => m.Types?.INPUT_PLACEHOLDER), +}; + +export const Card = waitForComponent<t.Card>("Card", m => m.Types?.PRIMARY === "cardPrimary"); +export const Button = waitForComponent<t.Button>("Button", ["Hovers", "Looks", "Sizes"]); +export const Switch = waitForComponent<t.Switch>("Switch", filters.byCode("tooltipNote", "ringTarget")); +export const Tooltip = waitForComponent<t.Tooltip>("Tooltip", ["Positions", "Colors"]); +export const Timestamp = waitForComponent<t.Timestamp>("Timestamp", filters.byCode(".Messages.MESSAGE_EDITED_TIMESTAMP_A11Y_LABEL.format")); +export const TextInput = waitForComponent<t.TextInput>("TextInput", ["defaultProps", "Sizes", "contextType"]); +export const TextArea = waitForComponent<t.TextArea>("TextArea", filters.byCode("handleSetRef", "textArea")); +export const Text = waitForComponent<t.Text>("Text", m => { + if (typeof m !== "function") return false; + const s = m.toString(); + return (s.length < 1500 && s.includes("data-text-variant") && s.includes("always-white")); +}); +export const Select = waitForComponent<t.Select>("Select", filters.byCode("optionClassName", "popoutPosition", "autoFocus", "maxVisibleItems")); +export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("closestMarkerIndex", "stickToMarkers")); +export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]); + +export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>; +export const Margins: t.Margins = findByPropsLazy("marginTop20"); +export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED"); diff --git a/src/webpack/common/index.ts b/src/webpack/common/index.ts new file mode 100644 index 000000000..dff7826cb --- /dev/null +++ b/src/webpack/common/index.ts @@ -0,0 +1,27 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +export * from "./components"; +export * from "./menu"; +export * from "./react"; +export * from "./stores"; +export * as ComponentTypes from "./types/components.d"; +export * as MenuTypes from "./types/menu.d"; +export * as UtilTypes from "./types/utils.d"; +export * from "./utils"; + diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx new file mode 100644 index 000000000..df768f763 --- /dev/null +++ b/src/webpack/common/internal.tsx @@ -0,0 +1,36 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { LazyComponent } from "@utils/misc"; + +// eslint-disable-next-line path-alias/no-relative +import { FilterFn, waitFor } from "../webpack"; + +export function waitForComponent<T extends React.ComponentType<any> = React.ComponentType<any> & Record<string, any>>(name: string, filter: FilterFn | string | string[]): T { + let myValue: T = function () { + throw new Error(`Vencord could not find the ${name} Component`); + } as any; + + const lazyComponent = LazyComponent(() => myValue) as T; + waitFor(filter, (v: any) => { + myValue = v; + Object.assign(lazyComponent, v); + }); + + return lazyComponent; +} diff --git a/src/webpack/common/menu.ts b/src/webpack/common/menu.ts new file mode 100644 index 000000000..6ecd754ef --- /dev/null +++ b/src/webpack/common/menu.ts @@ -0,0 +1,51 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { proxyLazy } from "@utils/proxyLazy"; + +// eslint-disable-next-line path-alias/no-relative +import { filters, mapMangledModule, mapMangledModuleLazy } from "../webpack"; +import type * as t from "./types/menu"; + +export const Menu: t.Menu = proxyLazy(() => { + const hasDeobfuscator = Vencord.Settings.plugins.MenuItemDeobfuscatorAPI.enabled; + const menuItems = ["MenuSeparator", "MenuGroup", "MenuItem", "MenuCheckboxItem", "MenuRadioItem", "MenuControlItem"]; + + const map = mapMangledModule("♫ ⊂(。◕‿‿◕。⊂) ♪", { + ContextMenu: filters.byCode("getContainerProps"), + ...Object.fromEntries((hasDeobfuscator ? menuItems : []).map(s => [s, (m: any) => m.name === s])) + }) as t.Menu; + + if (!hasDeobfuscator) { + for (const m of menuItems) + Object.defineProperty(map, m, { + get() { + throw new Error("MenuItemDeobfuscator must be enabled to use this."); + } + }); + } + + return map; +}); + +export const ContextMenu: t.ContextMenuApi = mapMangledModuleLazy('type:"CONTEXT_MENU_OPEN"', { + open: filters.byCode("stopPropagation"), + openLazy: m => m.toString().length < 50, + close: filters.byCode("CONTEXT_MENU_CLOSE") +}); + diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts new file mode 100644 index 000000000..455f39bef --- /dev/null +++ b/src/webpack/common/react.ts @@ -0,0 +1,33 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +// eslint-disable-next-line path-alias/no-relative +import { findByPropsLazy, waitFor } from "../webpack"; + +export let React: typeof import("react"); +export let useState: typeof React.useState; +export let useEffect: typeof React.useEffect; +export let useMemo: typeof React.useMemo; +export let useRef: typeof React.useRef; + +export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render"); + +waitFor("useState", m => { + React = m; + ({ useEffect, useState, useMemo, useRef } = React); +}); diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts new file mode 100644 index 000000000..bcd26b1ef --- /dev/null +++ b/src/webpack/common/stores.ts @@ -0,0 +1,54 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import type * as Stores from "discord-types/stores"; + +// eslint-disable-next-line path-alias/no-relative +import { filters, findByPropsLazy, mapMangledModuleLazy, waitFor } from "../webpack"; + +export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & { + getMessages(chanId: string): any; +}; +export const PermissionStore = findByPropsLazy("can", "getGuildPermissions"); +export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel"); +export const GuildChannelStore = findByPropsLazy("getChannels"); +export const ReadStateStore = findByPropsLazy("lastMessageId"); +export const PresenceStore = findByPropsLazy("setCurrentUserOnConnectionOpen"); + +export let GuildStore: Stores.GuildStore; +export let UserStore: Stores.UserStore; +export let SelectedChannelStore: Stores.SelectedChannelStore; +export let SelectedGuildStore: any; +export let ChannelStore: Stores.ChannelStore; +export let GuildMemberStore: Stores.GuildMemberStore; +export let RelationshipStore: Stores.RelationshipStore & { + /** Get the date (as a string) that the relationship was created */ + getSince(userId: string): string; +}; + +export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { + openUntrustedLink: filters.byCode(".apply(this,arguments)") +}); + +waitFor(["getCurrentUser", "initialize"], m => UserStore = m); +waitFor("getSortedPrivateChannels", m => ChannelStore = m); +waitFor("getCurrentlySelectedChannelId", m => SelectedChannelStore = m); +waitFor("getLastSelectedGuildId", m => SelectedGuildStore = m); +waitFor("getGuildCount", m => GuildStore = m); +waitFor(["getMember", "initialize"], m => GuildMemberStore = m); +waitFor("getRelationshipType", m => RelationshipStore = m); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts new file mode 100644 index 000000000..3f76c22ed --- /dev/null +++ b/src/webpack/common/types/components.d.ts @@ -0,0 +1,284 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import type { Moment } from "moment"; +import type { ComponentType, CSSProperties, FunctionComponent, HtmlHTMLAttributes, HTMLProps, PropsWithChildren, PropsWithRef, ReactNode, Ref } from "react"; + +export type TextVariant = "heading-sm/normal" | "heading-sm/medium" | "heading-sm/semibold" | "heading-sm/bold" | "heading-md/normal" | "heading-md/medium" | "heading-md/semibold" | "heading-md/bold" | "heading-lg/normal" | "heading-lg/medium" | "heading-lg/semibold" | "heading-lg/bold" | "heading-xl/normal" | "heading-xl/medium" | "heading-xl/bold" | "heading-xxl/normal" | "heading-xxl/medium" | "heading-xxl/bold" | "eyebrow" | "heading-deprecated-14/normal" | "heading-deprecated-14/medium" | "heading-deprecated-14/bold" | "text-xxs/normal" | "text-xxs/medium" | "text-xxs/semibold" | "text-xxs/bold" | "text-xs/normal" | "text-xs/medium" | "text-xs/semibold" | "text-xs/bold" | "text-sm/normal" | "text-sm/medium" | "text-sm/semibold" | "text-sm/bold" | "text-md/normal" | "text-md/medium" | "text-md/semibold" | "text-md/bold" | "text-lg/normal" | "text-lg/medium" | "text-lg/semibold" | "text-lg/bold" | "display-sm" | "display-md" | "display-lg" | "code"; +export type FormTextTypes = Record<"DEFAULT" | "INPUT_PLACEHOLDER" | "DESCRIPTION" | "LABEL_BOLD" | "LABEL_SELECTED" | "LABEL_DESCRIPTOR" | "ERROR" | "SUCCESS", string>; +export type Heading = `h${1 | 2 | 3 | 4 | 5 | 6}`; + +export type Margins = Record<"marginTop16" | "marginTop8" | "marginBottom8" | "marginTop20" | "marginBottom20", string>; +export type ButtonLooks = Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; + +export type TextProps = PropsWithChildren<HtmlHTMLAttributes<HTMLDivElement> & { + variant?: TextVariant; + tag?: "div" | "span" | "p" | "strong" | Heading; + selectable?: boolean; + lineClamp?: number; +}>; + +export type Text = ComponentType<TextProps>; + +export type FormTitle = ComponentType<HTMLProps<HTMLTitleElement> & PropsWithChildren<{ + /** default is h5 */ + tag?: Heading; + faded?: boolean; + disabled?: boolean; + required?: boolean; + error?: ReactNode; +}>>; + +export type FormSection = ComponentType<PropsWithChildren<{ + /** default is h5 */ + tag?: Heading; + className?: string; + titleClassName?: string; + titleId?: string; + title?: ReactNode; + disabled?: boolean; + htmlFor?: unknown; +}>>; + +export type FormDivider = ComponentType<{ + className?: string; + style?: CSSProperties; +}>; + + +export type FormText = ComponentType<PropsWithChildren<{ + disabled?: boolean; + selectable?: boolean; + /** defaults to FormText.Types.DEFAULT */ + type?: string; +}> & TextProps> & { Types: FormTextTypes; }; + +export type Tooltip = ComponentType<{ + text: ReactNode; + children: FunctionComponent<{ + onClick(): void; + onMouseEnter(): void; + onMouseLeave(): void; + onContextMenu(): void; + onFocus(): void; + onBlur(): void; + "aria-label"?: string; + }>; + "aria-label"?: string; + + allowOverflow?: boolean; + forceOpen?: boolean; + hide?: boolean; + hideOnClick?: boolean; + shouldShow?: boolean; + spacing?: number; + + /** Tooltip.Colors.BLACK */ + color?: string; + /** Tooltip.Positions.TOP */ + position?: string; + + tooltipClassName?: string; + tooltipContentClassName?: string; +}> & { + Positions: Record<"BOTTOM" | "CENTER" | "LEFT" | "RIGHT" | "TOP" | "WINDOW_CENTER", string>; + Colors: Record<"BLACK" | "BRAND" | "CUSTOM" | "GREEN" | "GREY" | "PRIMARY" | "RED" | "YELLOW", string>; +}; + +export type Card = ComponentType<PropsWithChildren<HTMLProps<HTMLDivElement> & { + editable?: boolean; + outline?: boolean; + /** Card.Types.PRIMARY */ + type?: string; +}>> & { + Types: Record<"BRAND" | "CUSTOM" | "DANGER" | "PRIMARY" | "SUCCESS" | "WARNING", string>; +}; + +export type Button = ComponentType<PropsWithChildren<Omit<HTMLProps<HTMLButtonElement>, "size"> & { + /** Button.Looks.FILLED */ + look?: string; + /** Button.Colors.BRAND */ + color?: string; + /** Button.Sizes.MEDIUM */ + size?: string; + /** Button.BorderColors.BLACK */ + borderColor?: string; + + wrapperClassName?: string; + className?: string; + innerClassName?: string; + + buttonRef?: Ref<HTMLButtonElement>; + focusProps?: any; + + submittingStartedLabel?: string; + submittingFinishedLabel?: string; +}>> & { + BorderColors: Record<"BLACK" | "BRAND" | "BRAND_NEW" | "GREEN" | "LINK" | "PRIMARY" | "RED" | "TRANSPARENT" | "WHITE" | "YELLOW", string>; + Colors: Record<"BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT" | "BRAND_NEW" | "CUSTOM", string>; + Hovers: Record<"DEFAULT" | "BRAND" | "RED" | "GREEN" | "YELLOW" | "PRIMARY" | "LINK" | "WHITE" | "BLACK" | "TRANSPARENT", string>; + Looks: Record<"FILLED" | "INVERTED" | "OUTLINED" | "LINK" | "BLANK", string>; + Sizes: Record<"NONE" | "TINY" | "SMALL" | "MEDIUM" | "LARGE" | "XLARGE" | "MIN" | "MAX" | "ICON", string>; + + Link: any; +}; + +export type Switch = ComponentType<PropsWithChildren<{ + value: boolean; + onChange(value: boolean): void; + + disabled?: boolean; + hideBorder?: boolean; + className?: string; + style?: CSSProperties; + + note?: ReactNode; + tooltipNote?: ReactNode; +}>>; + +export type Timestamp = ComponentType<PropsWithChildren<{ + timestamp: Moment; + isEdited?: boolean; + + className?: string; + id?: string; + + cozyAlt?: boolean; + compact?: boolean; + isInline?: boolean; + isVisibleOnlyOnHover?: boolean; +}>>; + +export type TextInput = ComponentType<PropsWithChildren<{ + name?: string; + onChange?(value: string, name?: string): void; + placeholder?: string; + editable?: boolean; + maxLength?: number; + error?: string; + + inputClassName?: string; + inputPrefix?: string; + inputRef?: Ref<HTMLInputElement>; + prefixElement?: ReactNode; + + focusProps?: any; + + /** TextInput.Sizes.DEFAULT */ + size?: string; +} & Omit<HTMLProps<HTMLInputElement>, "onChange">>> & { + Sizes: Record<"DEFAULT" | "MINI", string>; +}; + +export type TextArea = ComponentType<PropsWithRef<HTMLProps<HTMLTextAreaElement>>>; + +interface SelectOption { + disabled?: boolean; + value: any; + label: string; + key?: React.Key; + default?: boolean; +} + +export type Select = ComponentType<PropsWithChildren<{ + placeholder?: string; + options: ReadonlyArray<SelectOption>; // TODO + + /** + * - 0 ~ Filled + * - 1 ~ Custom + */ + look?: 0 | 1; + className?: string; + popoutClassName?: string; + popoutPosition?: "top" | "left" | "right" | "bottom" | "center" | "window_center"; + optionClassName?: string; + + autoFocus?: boolean; + isDisabled?: boolean; + clearable?: boolean; + closeOnSelect?: boolean; + hideIcon?: boolean; + + select?(value: any): void; + isSelected?(value: any): boolean; + serialize?(value: any): string; + clear?(): void; + + maxVisibleItems?: number; + popoutWidth?: number; + + onClose?(): void; + onOpen?(): void; + + renderOptionLabel?(option: SelectOption): ReactNode; + /** discord stupid this gets all options instead of one yeah */ + renderOptionValue?(option: SelectOption[]): ReactNode; + + "aria-label"?: boolean; + "aria-labelledby"?: boolean; +}>>; + +export type Slider = ComponentType<PropsWithChildren<{ + initialValue: number; + defaultValue?: number; + keyboardStep?: number; + maxValue?: number; + minValue?: number; + markers?: number[]; + stickToMarkers?: boolean; + + /** 0 above, 1 below */ + markerPosition?: 0 | 1; + orientation?: "horizontal" | "vertical"; + + getAriaValueText?(currentValue: number): string; + renderMarker?(marker: number): ReactNode; + onMarkerRender?(marker: number): ReactNode; + onValueRender?(value: number): ReactNode; + onValueChange?(value: number): void; + asValueChanges?(value: number): void; + + className?: string; + disabled?: boolean; + handleSize?: number; + mini?: boolean; + hideBubble?: boolean; + + fillStyles?: CSSProperties; + barStyles?: CSSProperties; + grabberStyles?: CSSProperties; + grabberClassName?: string; + barClassName?: string; + + "aria-hidden"?: boolean; + "aria-label"?: string; + "aria-labelledby"?: string; + "aria-describedby"?: string; +}>>; + +// TODO - type maybe idk probably not that useful other than the constants +export type Flex = ComponentType<PropsWithChildren<any>> & { + Align: Record<"START" | "END" | "CENTER" | "STRETCH" | "BASELINE", string>; + Direction: Record<"VERTICAL" | "HORIZONTAL" | "HORIZONTAL_REVERSE", string>; + Justify: Record<"START" | "END" | "CENTER" | "BETWEEN" | "AROUND", string>; + Wrap: Record<"NO_WRAP" | "WRAP" | "WRAP_REVERSE", string>; + + Content: ComponentType<PropsWithChildren<any>>; + Sidebar: ComponentType<PropsWithChildren<any>>; +}; diff --git a/src/webpack/common/types/fluxEvents.d.ts b/src/webpack/common/types/fluxEvents.d.ts new file mode 100644 index 000000000..36bcb2cde --- /dev/null +++ b/src/webpack/common/types/fluxEvents.d.ts @@ -0,0 +1,40 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +/* +function makeFluxEventList() { + // prefill MESSAGE_CREATE so that typescript infers this is a String Set + // without explicitly typing so that this function is also valid javascript + const events = new Set(["MESSAGE_CREATE"]); + + const { nodes } = Vencord.Webpack.Common.FluxDispatcher._actionHandlers._dependencyGraph; + for (const nodeId in nodes) { + for (const event in nodes[nodeId].actionHandler) { + events.add(event); + } + } + for (const event in Vencord.Webpack.Common.FluxDispatcher._subscriptions) { + events.add(event); + } + + return Array.from(events, e => JSON.stringify(e)).sort().join("|"); +} +*/ + +// 46kb worth of events ??????? +export type FluxEvents = "ACCESSIBILITY_COLORBLIND_TOGGLE" | "ACCESSIBILITY_DARK_SIDEBAR_TOGGLE" | "ACCESSIBILITY_DESATURATE_ROLES_TOGGLE" | "ACCESSIBILITY_DETECTION_MODAL_SEEN" | "ACCESSIBILITY_FORCED_COLORS_MODAL_SEEN" | "ACCESSIBILITY_KEYBOARD_MODE_DISABLE" | "ACCESSIBILITY_KEYBOARD_MODE_ENABLE" | "ACCESSIBILITY_LOW_CONTRAST_TOGGLE" | "ACCESSIBILITY_RESET_TO_DEFAULT" | "ACCESSIBILITY_SET_ALWAYS_SHOW_LINK_DECORATIONS" | "ACCESSIBILITY_SET_FONT_SIZE" | "ACCESSIBILITY_SET_MESSAGE_GROUP_SPACING" | "ACCESSIBILITY_SET_PREFERS_REDUCED_MOTION" | "ACCESSIBILITY_SET_ROLE_STYLE" | "ACCESSIBILITY_SET_SATURATION" | "ACCESSIBILITY_SET_SYNC_FORCED_COLORS" | "ACCESSIBILITY_SET_ZOOM" | "ACCESSIBILITY_SUBMIT_BUTTON_TOGGLE" | "ACCESSIBILITY_SUPPORT_CHANGED" | "ACCESSIBILITY_SYNC_PROFILE_THEME_WITH_USER_THEME_TOGGLE" | "ACCESSIBILITY_SYSTEM_COLOR_PREFERENCES_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_CONTRAST_CHANGED" | "ACCESSIBILITY_SYSTEM_PREFERS_REDUCED_MOTION_CHANGED" | "ACK_APPROVED_GUILD_JOIN_REQUEST" | "ACTIVE_CHANNELS_FETCH_FAILURE" | "ACTIVE_CHANNELS_FETCH_START" | "ACTIVE_CHANNELS_FETCH_SUCCESS" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_FAIL" | "ACTIVE_OUTBOUND_PROMOTIONS_FETCH_SUCCESS" | "ACTIVITY_INVITE_EDUCATION_DISMISS" | "ACTIVITY_INVITE_MODAL_CLOSE" | "ACTIVITY_INVITE_MODAL_OPEN" | "ACTIVITY_JOIN" | "ACTIVITY_JOIN_FAILED" | "ACTIVITY_JOIN_LOADING" | "ACTIVITY_LAUNCH_FAIL" | "ACTIVITY_METADATA_UPDATE" | "ACTIVITY_PIP_MODE_UPDATE" | "ACTIVITY_PLAY" | "ACTIVITY_SCREEN_ORIENTATION_UPDATE" | "ACTIVITY_START" | "ACTIVITY_SYNC" | "ACTIVITY_SYNC_STOP" | "ACTIVITY_UPDATE_FAIL" | "ACTIVITY_UPDATE_START" | "ACTIVITY_UPDATE_SUCCESS" | "ADD_STICKER_PREVIEW" | "ADMIN_ONBOARDING_GUIDE_HIDE" | "AFK" | "AGE_GATE_FAILURE_MODAL_OPEN" | "AGE_GATE_LOGOUT_UNDERAGE_NEW_USER" | "AGE_GATE_MODAL_CLOSE" | "AGE_GATE_MODAL_OPEN" | "AGE_GATE_SUCCESS_MODAL_OPEN" | "ALLOW_SPAM_MESSAGES_FOR_USER" | "APPLICATIONS_FETCH" | "APPLICATIONS_FETCH_FAIL" | "APPLICATIONS_FETCH_SUCCESS" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_FAIL" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_START" | "APPLICATION_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "APPLICATION_BRANCHES_FETCH_FAIL" | "APPLICATION_BRANCHES_FETCH_SUCCESS" | "APPLICATION_BUILD_FETCH_START" | "APPLICATION_BUILD_FETCH_SUCCESS" | "APPLICATION_BUILD_NOT_FOUND" | "APPLICATION_BUILD_SIZE_FETCH_FAIL" | "APPLICATION_BUILD_SIZE_FETCH_START" | "APPLICATION_BUILD_SIZE_FETCH_SUCCESS" | "APPLICATION_COMMANDS_FETCH" | "APPLICATION_COMMANDS_FETCH_FOR_APPLICATION" | "APPLICATION_COMMAND_AUTOCOMPLETE_REQUEST" | "APPLICATION_COMMAND_AUTOCOMPLETE_RESPONSE" | "APPLICATION_COMMAND_FETCH" | "APPLICATION_COMMAND_REGISTRY_UPDATE" | "APPLICATION_COMMAND_SEARCH_STORE_QUERY" | "APPLICATION_COMMAND_SEARCH_STORE_UI_UPDATE" | "APPLICATION_COMMAND_SEARCH_STORE_UPDATE" | "APPLICATION_COMMAND_SET_ACTIVE_COMMAND" | "APPLICATION_COMMAND_SET_PREFERRED_COMMAND" | "APPLICATION_COMMAND_UPDATE_CHANNEL_STATE" | "APPLICATION_COMMAND_UPDATE_OPTIONS" | "APPLICATION_COMMAND_USED" | "APPLICATION_FETCH" | "APPLICATION_FETCH_FAIL" | "APPLICATION_FETCH_SUCCESS" | "APPLICATION_STORE_ACCEPT_EULA" | "APPLICATION_STORE_ACCEPT_STORE_TERMS" | "APPLICATION_STORE_CLEAR_DATA" | "APPLICATION_STORE_DIRECTORY_FETCH_SUCCESS" | "APPLICATION_STORE_DIRECTORY_MUTE" | "APPLICATION_STORE_DIRECTORY_UNMUTE" | "APPLICATION_STORE_LOCATION_CHANGE" | "APPLICATION_STORE_MATURE_AGREE" | "APPLICATION_STORE_RESET_NAVIGATION" | "APPLICATION_SUBSCRIPTIONS_CHANNEL_NOTICE_DISMISSED" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_ENTITLEMENTS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "APPLICATION_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "APPLIED_BOOSTS_COOLDOWN_FETCH_SUCCESS" | "APPLIED_GUILD_BOOST_COUNT_UPDATE" | "APP_STATE_UPDATE" | "APP_VIEW_SET_HOME_LINK" | "AUDIO_INPUT_DETECTED" | "AUDIO_RESET" | "AUDIO_SET_ATTENUATION" | "AUDIO_SET_AUTOMATIC_GAIN_CONTROL" | "AUDIO_SET_DEBUG_LOGGING" | "AUDIO_SET_DISPLAY_SILENCE_WARNING" | "AUDIO_SET_ECHO_CANCELLATION" | "AUDIO_SET_INPUT_DEVICE" | "AUDIO_SET_INPUT_VOLUME" | "AUDIO_SET_LOCAL_PAN" | "AUDIO_SET_LOCAL_VIDEO_DISABLED" | "AUDIO_SET_LOCAL_VOLUME" | "AUDIO_SET_LOOPBACK" | "AUDIO_SET_MODE" | "AUDIO_SET_NOISE_CANCELLATION" | "AUDIO_SET_NOISE_SUPPRESSION" | "AUDIO_SET_OUTPUT_DEVICE" | "AUDIO_SET_OUTPUT_VOLUME" | "AUDIO_SET_QOS" | "AUDIO_SET_SUBSYSTEM" | "AUDIO_SET_TEMPORARY_SELF_MUTE" | "AUDIO_TOGGLE_LOCAL_MUTE" | "AUDIO_TOGGLE_SELF_DEAF" | "AUDIO_TOGGLE_SELF_MUTE" | "AUDIO_VOLUME_CHANGE" | "AUDIT_LOG_FETCH_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_FAIL" | "AUDIT_LOG_FETCH_NEXT_PAGE_START" | "AUDIT_LOG_FETCH_NEXT_PAGE_SUCCESS" | "AUDIT_LOG_FETCH_START" | "AUDIT_LOG_FETCH_SUCCESS" | "AUDIT_LOG_FILTER_BY_ACTION" | "AUDIT_LOG_FILTER_BY_USER" | "AUTH_INVITE_UPDATE" | "AUTH_SESSION_CHANGE" | "AUTO_MODERATION_MENTION_RAID_DETECTION" | "AUTO_MODERATION_MENTION_RAID_NOTICE_DISMISS" | "BILLING_IP_COUNTRY_CODE_FAILURE" | "BILLING_IP_COUNTRY_CODE_FETCH_START" | "BILLING_LOCALIZED_PRICING_PROMO_FAILURE" | "BILLING_MOST_RECENT_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_PAYMENTS_FETCH_SUCCESS" | "BILLING_PAYMENT_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCES_FETCH_FAIL" | "BILLING_PAYMENT_SOURCES_FETCH_START" | "BILLING_PAYMENT_SOURCES_FETCH_SUCCESS" | "BILLING_PAYMENT_SOURCE_CREATE_FAIL" | "BILLING_PAYMENT_SOURCE_CREATE_START" | "BILLING_PAYMENT_SOURCE_CREATE_SUCCESS" | "BILLING_PAYMENT_SOURCE_REMOVE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_REMOVE_FAIL" | "BILLING_PAYMENT_SOURCE_REMOVE_START" | "BILLING_PAYMENT_SOURCE_REMOVE_SUCCESS" | "BILLING_PAYMENT_SOURCE_UPDATE_CLEAR_ERROR" | "BILLING_PAYMENT_SOURCE_UPDATE_FAIL" | "BILLING_PAYMENT_SOURCE_UPDATE_START" | "BILLING_PAYMENT_SOURCE_UPDATE_SUCCESS" | "BILLING_POPUP_BRIDGE_CALLBACK" | "BILLING_POPUP_BRIDGE_STATE_UPDATE" | "BILLING_PURCHASE_TOKEN_AUTH_CLEAR_STATE" | "BILLING_SET_IP_COUNTRY_CODE" | "BILLING_SET_LOCALIZED_PRICING_PROMO" | "BILLING_SUBSCRIPTION_CANCEL_FAIL" | "BILLING_SUBSCRIPTION_CANCEL_START" | "BILLING_SUBSCRIPTION_CANCEL_SUCCESS" | "BILLING_SUBSCRIPTION_FETCH_FAIL" | "BILLING_SUBSCRIPTION_FETCH_START" | "BILLING_SUBSCRIPTION_FETCH_SUCCESS" | "BILLING_SUBSCRIPTION_RESET" | "BILLING_SUBSCRIPTION_UPDATE_FAIL" | "BILLING_SUBSCRIPTION_UPDATE_START" | "BILLING_SUBSCRIPTION_UPDATE_SUCCESS" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH_ERROR" | "BILLING_USER_PREMIUM_LIKELIHOOD_FETCH_SUCCESS" | "BILLING_USER_TRIAL_OFFER_ACKNOWLEDGED_SUCCESS" | "BILLING_USER_TRIAL_OFFER_FETCH_SUCCESS" | "BLOCKED_DOMAIN_LIST_FETCHED" | "BOOSTED_GUILD_GRACE_PERIOD_NOTICE_DISMISS" | "BRAINTREE_CREATE_CLIENT_SUCCESS" | "BRAINTREE_CREATE_PAYPAL_CLIENT_SUCCESS" | "BRAINTREE_CREATE_VENMO_CLIENT_SUCCESS" | "BRAINTREE_TEARDOWN_PAYPAL_CLIENT" | "BRAINTREE_TEARDOWN_VENMO_CLIENT" | "BRAINTREE_TOKENIZE_PAYPAL_START" | "BRAINTREE_TOKENIZE_PAYPAL_SUCCESS" | "BRAINTREE_TOKENIZE_VENMO_START" | "BRAINTREE_TOKENIZE_VENMO_SUCCESS" | "BROWSER_HANDOFF_BEGIN" | "BROWSER_HANDOFF_FROM_APP" | "BROWSER_HANDOFF_SET_USER" | "BROWSER_HANDOFF_UNAVAILABLE" | "BUILD_OVERRIDE_RESOLVED" | "BULK_ACK" | "BULK_CLEAR_RECENTS" | "BURST_REACTION_ADD" | "BURST_REACTION_ANIMATION_ADD" | "BURST_REACTION_EFFECT_CLEAR" | "BURST_REACTION_EFFECT_PLAY" | "BURST_REACTION_REMOVE" | "CACHE_LOADED" | "CACHE_LOADED_LAZY" | "CALL_CHAT_TOASTS_SET_ENABLED" | "CALL_CONNECT" | "CALL_CREATE" | "CALL_DELETE" | "CALL_ENQUEUE_RING" | "CALL_UPDATE" | "CANDIDATE_GAMES_CHANGE" | "CATEGORY_COLLAPSE" | "CATEGORY_COLLAPSE_ALL" | "CATEGORY_EXPAND" | "CATEGORY_EXPAND_ALL" | "CERTIFIED_DEVICES_SET" | "CHANGE_LOG_CLOSE" | "CHANGE_LOG_FETCH_SUCCESS" | "CHANGE_LOG_LOCK" | "CHANGE_LOG_OPEN" | "CHANGE_LOG_SET_OVERRIDE" | "CHANGE_LOG_UNLOCK" | "CHANNEL_ACK" | "CHANNEL_CALL_POPOUT_WINDOW_OPEN" | "CHANNEL_COLLAPSE" | "CHANNEL_CREATE" | "CHANNEL_DELETE" | "CHANNEL_FOLLOWER_CREATED" | "CHANNEL_FOLLOWER_STATS_FETCH_FAILURE" | "CHANNEL_FOLLOWER_STATS_FETCH_SUCCESS" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_DISMISSED" | "CHANNEL_FOLLOWING_PUBLISH_BUMP_HIDE_PERMANENTLY" | "CHANNEL_HIGHLIGHTS_FETCH_START" | "CHANNEL_HIGHLIGHTS_FETCH_SUCCESS" | "CHANNEL_LOCAL_ACK" | "CHANNEL_MESSAGE_PREVIEW_LOAD_MESSAGES" | "CHANNEL_PINS_ACK" | "CHANNEL_PINS_UPDATE" | "CHANNEL_PRELOAD" | "CHANNEL_RECIPIENT_ADD" | "CHANNEL_RECIPIENT_REMOVE" | "CHANNEL_RTC_ACTIVE_CHANNELS" | "CHANNEL_RTC_SELECT_PARTICIPANT" | "CHANNEL_RTC_UPDATE_CHAT_OPEN" | "CHANNEL_RTC_UPDATE_LAYOUT" | "CHANNEL_RTC_UPDATE_PARTICIPANTS_OPEN" | "CHANNEL_RTC_UPDATE_STAGE_MUSIC_MUTED" | "CHANNEL_RTC_UPDATE_STAGE_STREAM_SIZE" | "CHANNEL_RTC_UPDATE_STAGE_VIDEO_LIMIT_BOOST_UPSELL_DISMISSED" | "CHANNEL_RTC_UPDATE_VOICE_PARTICIPANTS_HIDDEN" | "CHANNEL_SELECT" | "CHANNEL_SETTINGS_CLOSE" | "CHANNEL_SETTINGS_INIT" | "CHANNEL_SETTINGS_LOADED_INVITES" | "CHANNEL_SETTINGS_OPEN" | "CHANNEL_SETTINGS_OVERWRITE_SELECT" | "CHANNEL_SETTINGS_PERMISSIONS_INIT" | "CHANNEL_SETTINGS_PERMISSIONS_SAVE_SUCCESS" | "CHANNEL_SETTINGS_PERMISSIONS_SELECT_PERMISSION" | "CHANNEL_SETTINGS_PERMISSIONS_SET_ADVANCED_MODE" | "CHANNEL_SETTINGS_PERMISSIONS_SUBMITTING" | "CHANNEL_SETTINGS_PERMISSIONS_UPDATE_PERMISSION" | "CHANNEL_SETTINGS_SET_SECTION" | "CHANNEL_SETTINGS_SUBMIT" | "CHANNEL_SETTINGS_SUBMIT_FAILURE" | "CHANNEL_SETTINGS_SUBMIT_SUCCESS" | "CHANNEL_SETTINGS_UPDATE" | "CHANNEL_TOGGLE_MEMBERS_SECTION" | "CHANNEL_UPDATES" | "CHECKING_FOR_UPDATES" | "CHECK_LAUNCHABLE_GAME" | "CLEAR_AUTHENTICATION_ERRORS" | "CLEAR_CACHES" | "CLEAR_GUILD_CACHE" | "CLEAR_INTERACTION_MODAL_STATE" | "CLEAR_MESSAGES" | "CLEAR_OLDEST_UNREAD_MESSAGE" | "CLEAR_PENDING_CHANNEL_AND_ROLE_UPDATES" | "CLEAR_REMOTE_DISCONNECT_VOICE_CHANNEL_ID" | "CLEAR_STICKER_PREVIEW" | "CLIENT_THEMES_EDITOR_CLOSE" | "CLIENT_THEMES_EDITOR_OPEN" | "CLIPS_CLEAR_CLIPS_SESSION" | "CLIPS_SAVE_CLIP" | "CLIPS_SETTINGS_UPDATE" | "COMMANDS_MIGRATION_NOTICE_DISMISSED" | "COMMANDS_MIGRATION_OVERVIEW_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_TOGGLE_TOOLTIP_DISMISSED" | "COMMANDS_MIGRATION_UPDATE_SUCCESS" | "CONNECTED_DEVICE_IGNORE" | "CONNECTED_DEVICE_NEVER_SHOW_MODAL" | "CONNECTED_DEVICE_SET" | "CONNECTIONS_GRID_MODAL_HIDE" | "CONNECTIONS_GRID_MODAL_SHOW" | "CONNECTION_CLOSED" | "CONNECTION_OPEN" | "CONNECTION_OPEN_SUPPLEMENTAL" | "CONNECTION_RESUMED" | "CONSOLE_COMMAND_UPDATE" | "CONTEXT_MENU_CLOSE" | "CONTEXT_MENU_OPEN" | "CREATE_PENDING_REPLY" | "CURRENT_BUILD_OVERRIDE_RESOLVED" | "CURRENT_USER_UPDATE" | "DECAY_READ_STATES" | "DELETED_ENTITY_IDS" | "DELETE_PENDING_REPLY" | "DETECTED_OFF_PLATFORM_PREMIUM_PERKS_DISMISS" | "DEVELOPER_ACTIVITY_SHELF_FETCH_FAIL" | "DEVELOPER_ACTIVITY_SHELF_FETCH_START" | "DEVELOPER_ACTIVITY_SHELF_FETCH_SUCCESS" | "DEVELOPER_ACTIVITY_SHELF_MARK_ACTIVITY_USED" | "DEVELOPER_ACTIVITY_SHELF_SET_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_ENABLED" | "DEVELOPER_ACTIVITY_SHELF_TOGGLE_USE_ACTIVITY_URL_OVERRIDE" | "DEVELOPER_OPTIONS_UPDATE_SETTINGS" | "DEVELOPER_TEST_MODE_AUTHORIZATION_FAIL" | "DEVELOPER_TEST_MODE_AUTHORIZATION_START" | "DEVELOPER_TEST_MODE_AUTHORIZATION_SUCCESS" | "DEVELOPER_TEST_MODE_RESET" | "DEVELOPER_TEST_MODE_RESET_ERROR" | "DEV_TOOLS_SETTINGS_UPDATE" | "DISABLE_AUTOMATIC_ACK" | "DISCOVER_CHECKLIST_FETCH_FAILURE" | "DISCOVER_CHECKLIST_FETCH_START" | "DISCOVER_CHECKLIST_FETCH_SUCCESS" | "DISCOVER_GUILDS_FETCH_FAILURE" | "DISCOVER_GUILDS_FETCH_START" | "DISCOVER_GUILDS_FETCH_SUCCESS" | "DISMISS_FAVORITE_SUGGESTION" | "DISMISS_NITRODUCTION_TOOLTIP" | "DISPATCH_APPLICATION_ADD_TO_INSTALLATIONS" | "DISPATCH_APPLICATION_CANCEL" | "DISPATCH_APPLICATION_ERROR" | "DISPATCH_APPLICATION_INSTALL" | "DISPATCH_APPLICATION_INSTALL_SCRIPTS_PROGRESS_UPDATE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_COMPLETE" | "DISPATCH_APPLICATION_LAUNCH_SETUP_START" | "DISPATCH_APPLICATION_MOVE_UP" | "DISPATCH_APPLICATION_REMOVE_FINISHED" | "DISPATCH_APPLICATION_REPAIR" | "DISPATCH_APPLICATION_STATE_UPDATE" | "DISPATCH_APPLICATION_UNINSTALL" | "DISPATCH_APPLICATION_UPDATE" | "DISPLAYED_INVITE_SHOW" | "DOMAIN_MIGRATION_FAILURE" | "DOMAIN_MIGRATION_SKIP" | "DOMAIN_MIGRATION_START" | "DRAFT_CHANGE" | "DRAFT_CLEAR" | "DRAFT_SAVE" | "DRAWER_CLOSE" | "DRAWER_OPEN" | "DRAWER_SELECT_TAB" | "DROPS_ELIGIBILITY_FETCH_SUCCESS" | "DROPS_ENROLLED_USER_FETCH_SUCCESS" | "DROPS_HEARTBEAT_FAILURE" | "DROPS_HEARTBEAT_SUCCESS" | "DROPS_PLATFORM_AVAILABILITY_SUCCESS" | "DROPS_USER_STATUS_FETCH_SUCCESS" | "EMAIL_SETTINGS_FETCH_SUCCESS" | "EMAIL_SETTINGS_UPDATE" | "EMAIL_SETTINGS_UPDATE_SUCCESS" | "EMBEDDED_ACTIVITY_CLOSE" | "EMBEDDED_ACTIVITY_DEFERRED_OPEN" | "EMBEDDED_ACTIVITY_DISCONNECT" | "EMBEDDED_ACTIVITY_DISMISS_FREE_INDICATOR" | "EMBEDDED_ACTIVITY_FETCH_SHELF" | "EMBEDDED_ACTIVITY_FETCH_SHELF_FAIL" | "EMBEDDED_ACTIVITY_FETCH_SHELF_SUCCESS" | "EMBEDDED_ACTIVITY_FREE_ACTIVITY_UPDATE" | "EMBEDDED_ACTIVITY_INBOUND_UPDATE" | "EMBEDDED_ACTIVITY_LAUNCH_FAIL" | "EMBEDDED_ACTIVITY_LAUNCH_START" | "EMBEDDED_ACTIVITY_LAUNCH_SUCCESS" | "EMBEDDED_ACTIVITY_OPEN" | "EMBEDDED_ACTIVITY_SET_CONFIG" | "EMBEDDED_ACTIVITY_SET_ORIENTATION_LOCK_STATE" | "EMOJI_DELETE" | "EMOJI_FETCH_FAILURE" | "EMOJI_FETCH_SUCCESS" | "EMOJI_TRACK_USAGE" | "EMOJI_UPLOAD_START" | "EMOJI_UPLOAD_STOP" | "ENABLE_AUTOMATIC_ACK" | "ENTITLEMENTS_FETCH_FOR_USER_SUCCESS" | "ENTITLEMENTS_GIFTABLE_FETCH_SUCCESS" | "ENTITLEMENT_CREATE" | "ENTITLEMENT_DELETE" | "ENTITLEMENT_FETCH_APPLICATION_FAIL" | "ENTITLEMENT_FETCH_APPLICATION_START" | "ENTITLEMENT_FETCH_APPLICATION_SUCCESS" | "ENTITLEMENT_UPDATE" | "EVENT_DIRECTORY_FETCH_FAILURE" | "EVENT_DIRECTORY_FETCH_START" | "EVENT_DIRECTORY_FETCH_SUCCESS" | "EXPERIMENTS_FETCH" | "EXPERIMENTS_FETCH_FAILURE" | "EXPERIMENTS_FETCH_SUCCESS" | "EXPERIMENT_OVERRIDE_BUCKET" | "EXPERIMENT_REGISTER_LEGACY" | "EXPERIMENT_TRIGGER" | "FETCH_AUTH_SESSIONS_SUCCESS" | "FETCH_GUILD_EVENTS_FOR_GUILD" | "FINGERPRINT" | "FORCE_INVISIBLE" | "FORGOT_PASSWORD_REQUEST" | "FORGOT_PASSWORD_SENT" | "FORUM_SEARCH_CLEAR" | "FORUM_SEARCH_FAILURE" | "FORUM_SEARCH_QUERY_UPDATED" | "FORUM_SEARCH_START" | "FORUM_SEARCH_SUCCESS" | "FORUM_UNREADS" | "FRIENDS_SET_INITIAL_SECTION" | "FRIENDS_SET_SECTION" | "FRIEND_INVITES_FETCH_REQUEST" | "FRIEND_INVITES_FETCH_RESPONSE" | "FRIEND_INVITE_CREATE_FAILURE" | "FRIEND_INVITE_CREATE_REQUEST" | "FRIEND_INVITE_CREATE_SUCCESS" | "FRIEND_INVITE_REVOKE_REQUEST" | "FRIEND_INVITE_REVOKE_SUCCESS" | "FRIEND_SUGGESTION_CREATE" | "FRIEND_SUGGESTION_DELETE" | "GAMES_DATABASE_FETCH" | "GAMES_DATABASE_FETCH_FAIL" | "GAMES_DATABASE_UPDATE" | "GAME_CLOUD_SYNC_COMPLETE" | "GAME_CLOUD_SYNC_CONFLICT" | "GAME_CLOUD_SYNC_ERROR" | "GAME_CLOUD_SYNC_START" | "GAME_CLOUD_SYNC_UPDATE" | "GAME_CONSOLE_FETCH_DEVICES_FAIL" | "GAME_CONSOLE_FETCH_DEVICES_START" | "GAME_CONSOLE_FETCH_DEVICES_SUCCESS" | "GAME_ICON_UPDATE" | "GAME_LAUNCHABLE_UPDATE" | "GAME_LAUNCH_FAIL" | "GAME_LAUNCH_START" | "GAME_LAUNCH_SUCCESS" | "GENERIC_PUSH_NOTIFICATION_SENT" | "GIFT_CODES_FETCH" | "GIFT_CODES_FETCH_FAILURE" | "GIFT_CODES_FETCH_SUCCESS" | "GIFT_CODE_CREATE" | "GIFT_CODE_CREATE_SUCCESS" | "GIFT_CODE_REDEEM" | "GIFT_CODE_REDEEM_FAILURE" | "GIFT_CODE_REDEEM_SUCCESS" | "GIFT_CODE_RESOLVE" | "GIFT_CODE_RESOLVE_FAILURE" | "GIFT_CODE_RESOLVE_SUCCESS" | "GIFT_CODE_REVOKE_SUCCESS" | "GIFT_CODE_UPDATE" | "GIF_PICKER_INITIALIZE" | "GIF_PICKER_QUERY" | "GIF_PICKER_QUERY_FAILURE" | "GIF_PICKER_QUERY_SUCCESS" | "GIF_PICKER_SUGGESTIONS_SUCCESS" | "GIF_PICKER_TRENDING_FETCH_SUCCESS" | "GIF_PICKER_TRENDING_SEARCH_TERMS_SUCCESS" | "GUILD_ACK" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_ENGAGEMENT_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_OVERVIEW_FETCH_SUCCESS" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_FAILURE" | "GUILD_ANALYTICS_GROWTH_ACTIVATION_RETENTION_FETCH_SUCCESS" | "GUILD_ANALYTICS_MEMBER_INSIGHTS_FETCH_SUCCESS" | "GUILD_APPLICATIONS_FETCH_SUCCESS" | "GUILD_APPLICATION_COMMAND_INDEX_UPDATE" | "GUILD_APPLIED_BOOSTS_FETCH_SUCCESS" | "GUILD_APPLY_BOOST_FAIL" | "GUILD_APPLY_BOOST_START" | "GUILD_APPLY_BOOST_SUCCESS" | "GUILD_BAN_ADD" | "GUILD_BAN_REMOVE" | "GUILD_BOOST_SLOTS_FETCH_SUCCESS" | "GUILD_BOOST_SLOT_CREATE" | "GUILD_BOOST_SLOT_UPDATE" | "GUILD_BOOST_SLOT_UPDATE_SUCCESS" | "GUILD_CREATE" | "GUILD_DELETE" | "GUILD_DIRECTORY_ADMIN_ENTRIES_FETCH_SUCCESS" | "GUILD_DIRECTORY_CACHED_SEARCH" | "GUILD_DIRECTORY_CATEGORY_SELECT" | "GUILD_DIRECTORY_COUNTS_FETCH_SUCCESS" | "GUILD_DIRECTORY_ENTRY_CREATE" | "GUILD_DIRECTORY_ENTRY_DELETE" | "GUILD_DIRECTORY_ENTRY_UPDATE" | "GUILD_DIRECTORY_FETCH_FAILURE" | "GUILD_DIRECTORY_FETCH_START" | "GUILD_DIRECTORY_FETCH_SUCCESS" | "GUILD_DIRECTORY_SEARCH_CLEAR" | "GUILD_DIRECTORY_SEARCH_FAILURE" | "GUILD_DIRECTORY_SEARCH_START" | "GUILD_DIRECTORY_SEARCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_ADD" | "GUILD_DISCOVERY_CATEGORY_DELETE" | "GUILD_DISCOVERY_CATEGORY_FETCH_SUCCESS" | "GUILD_DISCOVERY_CATEGORY_UPDATE_FAIL" | "GUILD_DISCOVERY_CLEAR_SEARCH" | "GUILD_DISCOVERY_CLEAR_SEEN_GUILDS" | "GUILD_DISCOVERY_FETCH_FAILURE" | "GUILD_DISCOVERY_FETCH_START" | "GUILD_DISCOVERY_FETCH_SUCCESS" | "GUILD_DISCOVERY_GUILD_SEEN" | "GUILD_DISCOVERY_METADATA_FETCH_FAIL" | "GUILD_DISCOVERY_POPULAR_FETCH_FAILURE" | "GUILD_DISCOVERY_POPULAR_FETCH_START" | "GUILD_DISCOVERY_POPULAR_FETCH_SUCCESS" | "GUILD_DISCOVERY_SEARCH_COUNTS_FAIL" | "GUILD_DISCOVERY_SEARCH_FETCH_FAILURE" | "GUILD_DISCOVERY_SEARCH_FETCH_START" | "GUILD_DISCOVERY_SEARCH_FETCH_SUCCESS" | "GUILD_DISCOVERY_SEARCH_INIT" | "GUILD_DISCOVERY_SEARCH_UPDATE_COUNTS" | "GUILD_DISCOVERY_SELECT_CATEGORY" | "GUILD_DISCOVERY_SLUG_FETCH_SUCCESS" | "GUILD_EMOJIS_UPDATE" | "GUILD_FEATURE_ACK" | "GUILD_FEED_FEATURED_ITEMS_FETCH_FAILURE" | "GUILD_FEED_FEATURED_ITEMS_FETCH_SUCCESS" | "GUILD_FEED_FEATURE_ITEM" | "GUILD_FEED_FETCH_FAILURE" | "GUILD_FEED_FETCH_FRESH_START" | "GUILD_FEED_FETCH_PAGE_START" | "GUILD_FEED_FETCH_SUCCESS" | "GUILD_FEED_ITEM_HIDE" | "GUILD_FEED_ITEM_READ_ACK" | "GUILD_FEED_ITEM_REMOVE" | "GUILD_FEED_ITEM_UNHIDE" | "GUILD_FEED_UNFEATURE_ITEM" | "GUILD_FOLDER_COLLAPSE" | "GUILD_HOME_ENSURE_HOME_SESSION" | "GUILD_HOME_SET_SCROLL_POSITION" | "GUILD_HOME_SET_SOURCE" | "GUILD_IDENTITY_SETTINGS_CLEAR_ERRORS" | "GUILD_IDENTITY_SETTINGS_CLOSE" | "GUILD_IDENTITY_SETTINGS_INIT" | "GUILD_IDENTITY_SETTINGS_RESET_ALL_PENDING" | "GUILD_IDENTITY_SETTINGS_RESET_AND_CLOSE_FORM" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_MEMBER_CHANGES" | "GUILD_IDENTITY_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "GUILD_IDENTITY_SETTINGS_SET_DISABLE_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SET_GUILD" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_AVATAR" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BANNER" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_BIO" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_NICKNAME" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_PRONOUNS" | "GUILD_IDENTITY_SETTINGS_SET_PENDING_THEME_COLORS" | "GUILD_IDENTITY_SETTINGS_SUBMIT" | "GUILD_IDENTITY_SETTINGS_SUBMIT_FAILURE" | "GUILD_IDENTITY_SETTINGS_SUBMIT_SUCCESS" | "GUILD_INTEGRATIONS_UPDATE" | "GUILD_JOIN" | "GUILD_JOIN_REQUESTS_BULK_ACTION" | "GUILD_JOIN_REQUESTS_FETCH_FAILURE" | "GUILD_JOIN_REQUESTS_FETCH_START" | "GUILD_JOIN_REQUESTS_FETCH_SUCCESS" | "GUILD_JOIN_REQUESTS_SET_APPLICATION_STATUS" | "GUILD_JOIN_REQUESTS_SET_SELECTED" | "GUILD_JOIN_REQUESTS_SET_SORT_ORDER" | "GUILD_JOIN_REQUEST_CREATE" | "GUILD_JOIN_REQUEST_DELETE" | "GUILD_JOIN_REQUEST_UPDATE" | "GUILD_MEMBERS_CHUNK" | "GUILD_MEMBERS_REQUEST" | "GUILD_MEMBER_ADD" | "GUILD_MEMBER_LIST_UPDATE" | "GUILD_MEMBER_PROFILE_UPDATE" | "GUILD_MEMBER_REMOVE" | "GUILD_MEMBER_UPDATE" | "GUILD_MEMBER_UPDATE_LOCAL" | "GUILD_MOVE" | "GUILD_MOVE_BY_ID" | "GUILD_NSFW_AGREE" | "GUILD_ONBOARDING_COMPLETE" | "GUILD_ONBOARDING_PROMPTS_FETCH_FAILURE" | "GUILD_ONBOARDING_PROMPTS_FETCH_START" | "GUILD_ONBOARDING_PROMPTS_FETCH_SUCCESS" | "GUILD_ONBOARDING_PROMPTS_LOCAL_UPDATE" | "GUILD_ONBOARDING_SELECT_OPTION" | "GUILD_ONBOARDING_SET_STEP" | "GUILD_ONBOARDING_START" | "GUILD_ONBOARDING_UPDATE_RESPONSES_SUCCESS" | "GUILD_POPOUT_FETCH_FAILURE" | "GUILD_POPOUT_FETCH_START" | "GUILD_POPOUT_FETCH_SUCCESS" | "GUILD_PROGRESS_COMPLETED_SEEN" | "GUILD_PROGRESS_DISMISS" | "GUILD_PROGRESS_INITIALIZE" | "GUILD_PROMPT_VIEWED" | "GUILD_RECOMMENDATION_FETCH" | "GUILD_RECOMMENDATION_FETCH_FAILURE" | "GUILD_RECOMMENDATION_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTIONS_CONFIGURATIONS_FETCH_SUCCESS" | "GUILD_ROLE_CONNECTION_ELIGIBILITY_FETCH_SUCCESS" | "GUILD_ROLE_CREATE" | "GUILD_ROLE_DELETE" | "GUILD_ROLE_MEMBER_ADD" | "GUILD_ROLE_MEMBER_BULK_ADD" | "GUILD_ROLE_MEMBER_COUNT_FETCH_SUCCESS" | "GUILD_ROLE_MEMBER_COUNT_UPDATE" | "GUILD_ROLE_MEMBER_REMOVE" | "GUILD_ROLE_SUBSCRIPTIONS_CREATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_DELETE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTINGS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_LISTING_FOR_PLAN_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_ABORTED" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_FAILURE" | "GUILD_ROLE_SUBSCRIPTIONS_FETCH_RESTRICTIONS_SUCCESS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_GROUP_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_LISTING" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTIONS_SETTINGS" | "GUILD_ROLE_SUBSCRIPTIONS_UPDATE_SUBSCRIPTION_TRIAL" | "GUILD_ROLE_UPDATE" | "GUILD_SCHEDULED_EVENT_CREATE" | "GUILD_SCHEDULED_EVENT_DELETE" | "GUILD_SCHEDULED_EVENT_RSVPS_FETCH_SUCESS" | "GUILD_SCHEDULED_EVENT_UPDATE" | "GUILD_SCHEDULED_EVENT_USERS_FETCH_SUCCESS" | "GUILD_SCHEDULED_EVENT_USER_ADD" | "GUILD_SCHEDULED_EVENT_USER_REMOVE" | "GUILD_SETTINGS_CANCEL_CHANGES" | "GUILD_SETTINGS_CLOSE" | "GUILD_SETTINGS_DEFAULT_CHANNELS_RESET" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_FAILED" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SAVE_SUCCESS" | "GUILD_SETTINGS_DEFAULT_CHANNELS_SUBMIT" | "GUILD_SETTINGS_DEFAULT_CHANNELS_TOGGLE" | "GUILD_SETTINGS_INIT" | "GUILD_SETTINGS_LOADED_BANS" | "GUILD_SETTINGS_LOADED_INTEGRATIONS" | "GUILD_SETTINGS_LOADED_INTEGRATIONS_WITH_COMMANDS" | "GUILD_SETTINGS_LOADED_INVITES" | "GUILD_SETTINGS_ONBOARDING_EDUCATION_UPSELL_DISMISSED" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_EDIT" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_ERRORS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_RESET" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_FAILED" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SAVE_SUCCESS" | "GUILD_SETTINGS_ONBOARDING_PROMPTS_SUBMIT" | "GUILD_SETTINGS_ONBOARDING_STEP" | "GUILD_SETTINGS_OPEN" | "GUILD_SETTINGS_ROLES_CLEAR_PERMISSIONS" | "GUILD_SETTINGS_ROLES_EDIT_SECTION_UPDATE" | "GUILD_SETTINGS_ROLES_INIT" | "GUILD_SETTINGS_ROLES_SAVE_FAIL" | "GUILD_SETTINGS_ROLES_SAVE_SUCCESS" | "GUILD_SETTINGS_ROLES_SORT_UPDATE" | "GUILD_SETTINGS_ROLES_SUBMITTING" | "GUILD_SETTINGS_ROLES_UPDATE_COLOR" | "GUILD_SETTINGS_ROLES_UPDATE_DESCRIPTION" | "GUILD_SETTINGS_ROLES_UPDATE_NAME" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSIONS" | "GUILD_SETTINGS_ROLES_UPDATE_PERMISSION_SET" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_CONNECTION_CONFIGURATIONS" | "GUILD_SETTINGS_ROLES_UPDATE_ROLE_ICON" | "GUILD_SETTINGS_ROLES_UPDATE_SETTINGS" | "GUILD_SETTINGS_ROLE_SELECT" | "GUILD_SETTINGS_SAVE_ROUTE_STACK" | "GUILD_SETTINGS_SET_MFA_SUCCESS" | "GUILD_SETTINGS_SET_SEARCH_QUERY" | "GUILD_SETTINGS_SET_SECTION" | "GUILD_SETTINGS_SET_VANITY_URL" | "GUILD_SETTINGS_SET_WIDGET" | "GUILD_SETTINGS_SUBMIT" | "GUILD_SETTINGS_SUBMIT_FAILURE" | "GUILD_SETTINGS_SUBMIT_SUCCESS" | "GUILD_SETTINGS_UPDATE" | "GUILD_SETTINGS_VANITY_URL_CLOSE" | "GUILD_SETTINGS_VANITY_URL_ERROR" | "GUILD_SETTINGS_VANITY_URL_RESET" | "GUILD_SETTINGS_VANITY_URL_SET" | "GUILD_SOUNDBOARD_DELETE_SUCCESS" | "GUILD_SOUNDBOARD_FETCH" | "GUILD_SOUNDBOARD_FETCH_FAILURE" | "GUILD_SOUNDBOARD_FETCH_SUCCESS" | "GUILD_SOUNDBOARD_SOUND_PLAY_END" | "GUILD_SOUNDBOARD_SOUND_PLAY_START" | "GUILD_SOUNDBOARD_UPDATE_SUCCESS" | "GUILD_SOUNDBOARD_UPLOAD_SUCCESS" | "GUILD_STICKERS_CREATE_SUCCESS" | "GUILD_STICKERS_FETCH_SUCCESS" | "GUILD_STICKERS_UPDATE" | "GUILD_STOP_LURKING" | "GUILD_STOP_LURKING_FAILURE" | "GUILD_SUBSCRIPTIONS" | "GUILD_SUBSCRIPTIONS_CHANNEL" | "GUILD_SUBSCRIPTIONS_FLUSH" | "GUILD_SUBSCRIPTIONS_MEMBERS_ADD" | "GUILD_SUBSCRIPTIONS_MEMBERS_REMOVE" | "GUILD_TEMPLATE_ACCEPT" | "GUILD_TEMPLATE_ACCEPT_FAILURE" | "GUILD_TEMPLATE_ACCEPT_SUCCESS" | "GUILD_TEMPLATE_CREATE_SUCCESS" | "GUILD_TEMPLATE_DELETE_SUCCESS" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_HIDE" | "GUILD_TEMPLATE_DIRTY_TOOLTIP_REFRESH" | "GUILD_TEMPLATE_LOAD_FOR_GUILD_SUCCESS" | "GUILD_TEMPLATE_MODAL_HIDE" | "GUILD_TEMPLATE_MODAL_SHOW" | "GUILD_TEMPLATE_PROMOTION_TOOLTIP_HIDE" | "GUILD_TEMPLATE_RESOLVE" | "GUILD_TEMPLATE_RESOLVE_FAILURE" | "GUILD_TEMPLATE_RESOLVE_SUCCESS" | "GUILD_TEMPLATE_SYNC_SUCCESS" | "GUILD_TOGGLE_COLLAPSE_MUTED" | "GUILD_TOP_READ_CHANNELS_FETCH_SUCCESS" | "GUILD_UNAPPLY_BOOST_FAIL" | "GUILD_UNAPPLY_BOOST_START" | "GUILD_UNAPPLY_BOOST_SUCCESS" | "GUILD_UNAVAILABLE" | "GUILD_UNREADS_SET_LAST_CLEARED" | "GUILD_UPDATE" | "GUILD_UPDATE_DISCOVERY_METADATA" | "GUILD_UPDATE_DISCOVERY_METADATA_FAIL" | "GUILD_UPDATE_DISCOVERY_METADATA_FROM_SERVER" | "GUILD_VERIFICATION_CHECK" | "HIDE_ACTION_SHEET" | "HIDE_ACTION_SHEET_QUICK_SWITCHER" | "HIDE_KEYBOARD_SHORTCUTS" | "HIDE_SPAM_MESSAGES_FOR_USER" | "HOTSPOT_HIDE" | "HOTSPOT_OVERRIDE_CLEAR" | "HOTSPOT_OVERRIDE_SET" | "HYPESQUAD_ONLINE_MEMBERSHIP_JOIN_SUCCESS" | "HYPESQUAD_ONLINE_MEMBERSHIP_LEAVE_SUCCESS" | "I18N_LOAD_ERROR" | "I18N_LOAD_START" | "I18N_LOAD_SUCCESS" | "IDLE" | "IMPERSONATE_STOP" | "IMPERSONATE_UPDATE" | "INBOX_OPEN" | "INCOMING_CALL_MOVE" | "INSTALLATION_LOCATION_ADD" | "INSTALLATION_LOCATION_FETCH_METADATA" | "INSTALLATION_LOCATION_REMOVE" | "INSTALLATION_LOCATION_UPDATE" | "INSTANT_INVITE_CREATE" | "INSTANT_INVITE_CREATE_FAILURE" | "INSTANT_INVITE_CREATE_SUCCESS" | "INSTANT_INVITE_REVOKE_SUCCESS" | "INTEGRATION_PERMISSION_SETTINGS_APPLICATION_PERMISSIONS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_CLEAR" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_FAILURE" | "INTEGRATION_PERMISSION_SETTINGS_COMMANDS_FETCH_SUCCESS" | "INTEGRATION_PERMISSION_SETTINGS_COMMAND_UPDATE" | "INTEGRATION_PERMISSION_SETTINGS_EDIT" | "INTEGRATION_PERMISSION_SETTINGS_INIT" | "INTEGRATION_PERMISSION_SETTINGS_RESET" | "INTEGRATION_QUERY" | "INTEGRATION_QUERY_FAILURE" | "INTEGRATION_QUERY_SUCCESS" | "INTEGRATION_SETTINGS_INIT" | "INTEGRATION_SETTINGS_SAVE_FAILURE" | "INTEGRATION_SETTINGS_SAVE_SUCCESS" | "INTEGRATION_SETTINGS_SET_SECTION" | "INTEGRATION_SETTINGS_START_EDITING_COMMAND" | "INTEGRATION_SETTINGS_START_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_START_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_STOP_EDITING_COMMAND" | "INTEGRATION_SETTINGS_STOP_EDITING_INTEGRATION" | "INTEGRATION_SETTINGS_STOP_EDITING_WEBHOOK" | "INTEGRATION_SETTINGS_SUBMITTING" | "INTEGRATION_SETTINGS_UPDATE_INTEGRATION" | "INTEGRATION_SETTINGS_UPDATE_WEBHOOK" | "INTERACTION_CREATE" | "INTERACTION_FAILURE" | "INTERACTION_MODAL_CREATE" | "INTERACTION_QUEUE" | "INTERACTION_SUCCESS" | "INVITE_ACCEPT" | "INVITE_ACCEPT_FAILURE" | "INVITE_ACCEPT_SUCCESS" | "INVITE_APP_NOT_OPENED" | "INVITE_APP_OPENED" | "INVITE_APP_OPENING" | "INVITE_MODAL_CLOSE" | "INVITE_MODAL_ERROR" | "INVITE_MODAL_OPEN" | "INVITE_RESOLVE" | "INVITE_RESOLVE_FAILURE" | "INVITE_RESOLVE_SUCCESS" | "KEYBINDS_ADD_KEYBIND" | "KEYBINDS_DELETE_KEYBIND" | "KEYBINDS_ENABLE_ALL_KEYBINDS" | "KEYBINDS_REGISTER_GLOBAL_KEYBIND_ACTIONS" | "KEYBINDS_SET_KEYBIND" | "KEYBOARD_NAVIGATION_EXPLAINER_MODAL_SEEN" | "LAYER_POP" | "LAYER_POP_ALL" | "LAYER_PUSH" | "LAYOUT_CREATE" | "LAYOUT_CREATE_WIDGETS" | "LAYOUT_DELETE_ALL_WIDGETS" | "LAYOUT_DELETE_WIDGET" | "LAYOUT_SET_PINNED" | "LAYOUT_SET_TOP_WIDGET" | "LAYOUT_UPDATE_WIDGET" | "LIBRARY_APPLICATIONS_TEST_MODE_ENABLED" | "LIBRARY_APPLICATION_ACTIVE_BRANCH_UPDATE" | "LIBRARY_APPLICATION_ACTIVE_LAUNCH_OPTION_UPDATE" | "LIBRARY_APPLICATION_FILTER_UPDATE" | "LIBRARY_APPLICATION_FLAGS_UPDATE_START" | "LIBRARY_APPLICATION_FLAGS_UPDATE_SUCCESS" | "LIBRARY_APPLICATION_UPDATE" | "LIBRARY_FETCH_SUCCESS" | "LIBRARY_TABLE_ACTIVE_ROW_ID_UPDATE" | "LIBRARY_TABLE_SORT_UPDATE" | "LIVE_CHANNEL_NOTICE_HIDE" | "LOAD_ARCHIVED_THREADS" | "LOAD_ARCHIVED_THREADS_FAIL" | "LOAD_ARCHIVED_THREADS_SUCCESS" | "LOAD_FORUM_POSTS" | "LOAD_FRIEND_SUGGESTIONS_FAILURE" | "LOAD_FRIEND_SUGGESTIONS_SUCCESS" | "LOAD_GUILD_AFFINITIES_SUCCESS" | "LOAD_MESSAGES" | "LOAD_MESSAGES_AROUND_SUCCESS" | "LOAD_MESSAGES_FAILURE" | "LOAD_MESSAGES_SUCCESS" | "LOAD_MESSAGES_SUCCESS_CACHED" | "LOAD_MESSAGE_INTERACTION_DATA_SUCCESS" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_ERROR" | "LOAD_MESSAGE_REQUESTS_SUPPLEMENTAL_DATA_SUCCESS" | "LOAD_NOTIFICATION_CENTER_ITEMS" | "LOAD_NOTIFICATION_CENTER_ITEMS_FAILURE" | "LOAD_NOTIFICATION_CENTER_ITEMS_SUCCESS" | "LOAD_PINNED_MESSAGES" | "LOAD_PINNED_MESSAGES_FAILURE" | "LOAD_PINNED_MESSAGES_SUCCESS" | "LOAD_RECENT_MENTIONS" | "LOAD_RECENT_MENTIONS_FAILURE" | "LOAD_RECENT_MENTIONS_SUCCESS" | "LOAD_REGIONS" | "LOAD_RELATIONSHIPS_FAILURE" | "LOAD_RELATIONSHIPS_SUCCESS" | "LOAD_THREADS_SUCCESS" | "LOAD_USER_AFFINITIES" | "LOAD_USER_AFFINITIES_FAILURE" | "LOAD_USER_AFFINITIES_SUCCESS" | "LOBBY_CONNECT" | "LOBBY_CREATE" | "LOBBY_DELETE" | "LOBBY_DISCONNECT" | "LOBBY_MEMBER_CONNECT" | "LOBBY_MEMBER_DISCONNECT" | "LOBBY_MEMBER_UPDATE" | "LOBBY_MESSAGE" | "LOBBY_UPDATE" | "LOBBY_VOICE_CONNECT" | "LOBBY_VOICE_DISCONNECT" | "LOBBY_VOICE_SERVER_UPDATE" | "LOBBY_VOICE_STATE_UPDATE" | "LOCAL_ACTIVITY_UPDATE" | "LOGIN" | "LOGIN_ACCOUNT_DISABLED" | "LOGIN_ACCOUNT_SCHEDULED_FOR_DELETION" | "LOGIN_ATTEMPTED" | "LOGIN_FAILURE" | "LOGIN_MFA" | "LOGIN_MFA_FAILURE" | "LOGIN_MFA_SMS" | "LOGIN_MFA_SMS_FAILURE" | "LOGIN_MFA_SMS_REQUEST_SUCCESS" | "LOGIN_MFA_STEP" | "LOGIN_MFA_WEBAUTHN" | "LOGIN_MFA_WEBAUTHN_TO_TOTP" | "LOGIN_PASSWORD_RECOVERY_PHONE_VERIFICATION" | "LOGIN_PHONE_IP_AUTHORIZATION_REQUIRED" | "LOGIN_RESET" | "LOGIN_STATUS_RESET" | "LOGIN_SUCCESS" | "LOGOUT" | "LOGOUT_AUTH_SESSIONS_SUCCESS" | "MASKED_LINK_ADD_TRUSTED_DOMAIN" | "MASKED_LINK_ADD_TRUSTED_PROTOCOL" | "MAX_MEMBER_COUNT_NOTICE_DISMISS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_ERROR" | "MEDIA_ENGINE_APPLY_MEDIA_FILTER_SETTINGS_START" | "MEDIA_ENGINE_DEVICES" | "MEDIA_ENGINE_ENABLE_SOUNDSHARE" | "MEDIA_ENGINE_INTERACTION_REQUIRED" | "MEDIA_ENGINE_NOISE_CANCELLATION_ERROR_RESET" | "MEDIA_ENGINE_PERMISSION" | "MEDIA_ENGINE_SET_AEC_DUMP" | "MEDIA_ENGINE_SET_AUDIO_ENABLED" | "MEDIA_ENGINE_SET_AV1" | "MEDIA_ENGINE_SET_DESKTOP_SOURCE" | "MEDIA_ENGINE_SET_EXPERIMENTAL_ENCODERS" | "MEDIA_ENGINE_SET_EXPERIMENTAL_SOUNDSHARE" | "MEDIA_ENGINE_SET_HARDWARE_H264" | "MEDIA_ENGINE_SET_OPEN_H264" | "MEDIA_ENGINE_SET_VIDEO_DEVICE" | "MEDIA_ENGINE_SET_VIDEO_ENABLED" | "MEDIA_ENGINE_SET_VIDEO_HOOK" | "MEDIA_ENGINE_SOUNDSHARE_FAILED" | "MEDIA_ENGINE_SOUNDSHARE_TRANSMITTING" | "MEDIA_ENGINE_VIDEO_SOURCE_QUALITY_CHANGED" | "MEDIA_ENGINE_VIDEO_STATE_CHANGED" | "MEMBER_VERIFICATION_FORM_FETCH_FAIL" | "MEMBER_VERIFICATION_FORM_UPDATE" | "MENTION_MODAL_CLOSE" | "MENTION_MODAL_OPEN" | "MESSAGE_ACK" | "MESSAGE_CREATE" | "MESSAGE_DELETE" | "MESSAGE_DELETE_BULK" | "MESSAGE_EDIT_FAILED_AUTOMOD" | "MESSAGE_END_EDIT" | "MESSAGE_LENGTH_UPSELL" | "MESSAGE_REACTION_ADD" | "MESSAGE_REACTION_ADD_USERS" | "MESSAGE_REACTION_REMOVE" | "MESSAGE_REACTION_REMOVE_ALL" | "MESSAGE_REACTION_REMOVE_EMOJI" | "MESSAGE_REQUEST_ACCEPT_OPTIMISTIC" | "MESSAGE_REVEAL" | "MESSAGE_SEND_FAILED" | "MESSAGE_SEND_FAILED_AUTOMOD" | "MESSAGE_START_EDIT" | "MESSAGE_TODO_ADD" | "MESSAGE_TODO_CLEANUP" | "MESSAGE_TODO_COMPLETE" | "MESSAGE_UPDATE" | "MESSAGE_UPDATE_EDIT" | "MFA_CLEAR_BACKUP_CODES" | "MFA_DISABLE_SUCCESS" | "MFA_ENABLE_SUCCESS" | "MFA_SEND_VERIFICATION_KEY" | "MFA_SMS_TOGGLE" | "MFA_SMS_TOGGLE_COMPLETE" | "MFA_VIEW_BACKUP_CODES" | "MFA_WEBAUTHN_CREDENTIALS_LOADED" | "MFA_WEBAUTHN_CREDENTIALS_LOADING" | "MFA_WEBAUTHN_CREDENTIAL_CREATE" | "MFA_WEBAUTHN_CREDENTIAL_DELETE" | "MOBILE_WEB_SIDEBAR_CLOSE" | "MOBILE_WEB_SIDEBAR_OPEN" | "MODAL_POP" | "MODAL_PUSH" | "MULTI_ACCOUNT_INVALIDATE_PUSH_SYNC_TOKENS" | "MULTI_ACCOUNT_MOBILE_EXPERIMENT_UPDATE" | "MULTI_ACCOUNT_MOVE_ACCOUNT" | "MULTI_ACCOUNT_REMOVE_ACCOUNT" | "MULTI_ACCOUNT_UPDATE_PUSH_SYNC_TOKEN" | "MULTI_ACCOUNT_VALIDATE_TOKEN_FAILURE" | "MULTI_ACCOUNT_VALIDATE_TOKEN_REQUEST" | "MULTI_ACCOUNT_VALIDATE_TOKEN_SUCCESS" | "MUTUAL_FRIENDS_FETCH_FAILURE" | "MUTUAL_FRIENDS_FETCH_START" | "MUTUAL_FRIENDS_FETCH_SUCCESS" | "NEWLY_ADDED_EMOJI_SEEN_ACKNOWLEDGED" | "NEWLY_ADDED_EMOJI_SEEN_PENDING" | "NEWLY_ADDED_EMOJI_SEEN_UPDATED" | "NEW_PAYMENT_SOURCE_ADDRESS_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CARD_INFO_UPDATE" | "NEW_PAYMENT_SOURCE_CLEAR_ERROR" | "NEW_PAYMENT_SOURCE_STRIPE_PAYMENT_REQUEST_UPDATE" | "NITRODUCTION_PERSISTENT_ONBOARDING_TOGGLE_COLLAPSE" | "NOTICE_DISABLE" | "NOTICE_DISMISS" | "NOTICE_SHOW" | "NOTIFICATIONS_SET_DESKTOP_TYPE" | "NOTIFICATIONS_SET_DISABLED_SOUNDS" | "NOTIFICATIONS_SET_DISABLE_UNREAD_BADGE" | "NOTIFICATIONS_SET_NOTIFY_MESSAGES_IN_SELECTED_CHANNEL" | "NOTIFICATIONS_SET_PERMISSION_STATE" | "NOTIFICATIONS_SET_TASKBAR_FLASH" | "NOTIFICATIONS_SET_TTS_TYPE" | "NOTIFICATIONS_TOGGLE_ALL_DISABLED" | "NOTIFICATION_CENTER_ITEMS_ACK" | "NOTIFICATION_CENTER_ITEMS_ACK_FAILURE" | "NOTIFICATION_CENTER_ITEMS_LOCAL_ACK" | "NOTIFICATION_CENTER_ITEM_COMPLETED" | "NOTIFICATION_CENTER_ITEM_CREATE" | "NOTIFICATION_CENTER_ITEM_DELETE" | "NOTIFICATION_CENTER_ITEM_DELETE_FAILURE" | "NOTIFICATION_CENTER_SET_ACTIVE" | "NOTIFICATION_CENTER_SET_TAB" | "NOTIFICATION_CLICK" | "NOTIFICATION_CREATE" | "NOW_PLAYING_MOUNTED" | "NOW_PLAYING_UNMOUNTED" | "NUF_COMPLETE" | "NUF_NEW_USER" | "OAUTH2_TOKEN_REVOKE" | "ONLINE_GUILD_MEMBER_COUNT_UPDATE" | "OUTBOUND_PROMOTIONS_SEEN" | "OUTBOUND_PROMOTION_NOTICE_DISMISS" | "OVERLAY_ACTIVATE_REGION" | "OVERLAY_CALL_PRIVATE_CHANNEL" | "OVERLAY_CRASHED" | "OVERLAY_DEACTIVATE_ALL_REGIONS" | "OVERLAY_DISABLE_EXTERNAL_LINK_ALERT" | "OVERLAY_FOCUSED" | "OVERLAY_GAMES_CHANGE" | "OVERLAY_INCOMPATIBLE_APP" | "OVERLAY_INITIALIZE" | "OVERLAY_JOIN_GAME" | "OVERLAY_READY" | "OVERLAY_SELECT_CALL" | "OVERLAY_SELECT_CHANNEL" | "OVERLAY_SET_AVATAR_SIZE_MODE" | "OVERLAY_SET_DISPLAY_NAME_MODE" | "OVERLAY_SET_DISPLAY_USER_MODE" | "OVERLAY_SET_ENABLED" | "OVERLAY_SET_INPUT_LOCKED" | "OVERLAY_SET_NOTIFICATION_POSITION_MODE" | "OVERLAY_SET_NOT_IDLE" | "OVERLAY_SET_PREVIEW_IN_GAME_MODE" | "OVERLAY_SET_TEXT_CHAT_NOTIFICATION_MODE" | "OVERLAY_SET_TEXT_WIDGET_OPACITY" | "OVERLAY_SET_UI_LOCKED" | "OVERLAY_START_SESSION" | "PARTNER_REQUIREMENTS_FETCH_FAILURE" | "PARTNER_REQUIREMENTS_FETCH_START" | "PARTNER_REQUIREMENTS_FETCH_SUCCESS" | "PASSIVE_UPDATE_V1" | "PASSWORD_UPDATED" | "PAYMENT_AUTHENTICATION_CLEAR_ERROR" | "PAYMENT_AUTHENTICATION_ERROR" | "PAYMENT_UPDATE" | "PERMISSION_CLEAR_ELEVATED_PROCESS" | "PERMISSION_CLEAR_PTT_ADMIN_WARNING" | "PERMISSION_CLEAR_SUPPRESS_WARNING" | "PERMISSION_CLEAR_VAD_WARNING" | "PERMISSION_CONTINUE_NONELEVATED_PROCESS" | "PERMISSION_REQUEST_ELEVATED_PROCESS" | "PHONE_SET_COUNTRY_CODE" | "PICTURE_IN_PICTURE_CLOSE" | "PICTURE_IN_PICTURE_HIDE" | "PICTURE_IN_PICTURE_MOVE" | "PICTURE_IN_PICTURE_OPEN" | "PICTURE_IN_PICTURE_SHOW" | "PICTURE_IN_PICTURE_UPDATE_RECT" | "PICTURE_IN_PICTURE_UPDATE_SELECTED_WINDOW" | "POGGERMODE_ACHIEVEMENT_UNLOCK" | "POGGERMODE_SETTINGS_UPDATE" | "POGGERMODE_TEMPORARILY_DISABLED" | "POGGERMODE_UPDATE_COMBO" | "POGGERMODE_UPDATE_MESSAGE_COMBO" | "POPOUT_WINDOW_CLOSE" | "POPOUT_WINDOW_OPEN" | "POPOUT_WINDOW_SET_ALWAYS_ON_TOP" | "POST_CONNECTION_OPEN" | "PREMIUM_PAYMENT_ERROR_CLEAR" | "PREMIUM_PAYMENT_MODAL_CLOSE" | "PREMIUM_PAYMENT_MODAL_OPEN" | "PREMIUM_PAYMENT_SUBSCRIBE_FAIL" | "PREMIUM_PAYMENT_SUBSCRIBE_START" | "PREMIUM_PAYMENT_SUBSCRIBE_SUCCESS" | "PREMIUM_PAYMENT_UPDATE_FAIL" | "PREMIUM_PAYMENT_UPDATE_SUCCESS" | "PREMIUM_REQUIRED_MODAL_CLOSE" | "PREMIUM_REQUIRED_MODAL_OPEN" | "PRESENCES_REPLACE" | "PRESENCE_UPDATES" | "PRIVATE_CHANNEL_RECIPIENTS_ADD_USER" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_CLOSE" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_OPEN" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_QUERY" | "PRIVATE_CHANNEL_RECIPIENTS_INVITE_SELECT" | "PRIVATE_CHANNEL_RECIPIENTS_REMOVE_USER" | "PROFILE_CUSTOMIZATION_OPEN_PREVIEW_MODAL" | "PROFILE_PANEL_TOGGLE_SECTION" | "PUBLIC_UPSELL_NOTICE_DISMISS" | "PURCHASE_CONFIRMATION_MODAL_CLOSE" | "PURCHASE_CONFIRMATION_MODAL_OPEN" | "PUSH_NOTIFICATION_CLICK" | "QUEUE_INTERACTION_COMPONENT_STATE" | "QUICKSWITCHER_HIDE" | "QUICKSWITCHER_SEARCH" | "QUICKSWITCHER_SELECT" | "QUICKSWITCHER_SHOW" | "QUICKSWITCHER_SWITCH_TO" | "RECENT_MENTION_DELETE" | "REGISTER" | "REGISTER_FAILURE" | "REGISTER_SAVE_FORM" | "REGISTER_SUCCESS" | "RELATIONSHIP_ADD" | "RELATIONSHIP_REMOVE" | "RELATIONSHIP_UPDATE" | "REMOTE_COMMAND" | "REMOTE_SESSION_CONNECT" | "REMOTE_SESSION_DISCONNECT" | "REMOVE_AUTOMOD_MESSAGE_NOTICE" | "REQUEST_FORUM_UNREADS" | "RESET_ALL_NITRODUCTION_TOOLTIPS" | "RESET_HAS_COMPLETED_STEP" | "RESET_NOTIFICATION_CENTER" | "RESET_PAYMENT_ID" | "RESORT_THREADS" | "RPC_APP_AUTHENTICATED" | "RPC_APP_CONNECTED" | "RPC_APP_DISCONNECTED" | "RPC_NOTIFICATION_CREATE" | "RPC_SERVER_READY" | "RTC_CONNECTION_LOSS_RATE" | "RTC_CONNECTION_PING" | "RTC_CONNECTION_STATE" | "RTC_CONNECTION_UPDATE_ID" | "RTC_CONNECTION_VIDEO" | "RTC_DEBUG_MODAL_CLOSE" | "RTC_DEBUG_MODAL_OPEN" | "RTC_DEBUG_MODAL_OPEN_REPLAY" | "RTC_DEBUG_MODAL_OPEN_REPLAY_AT_PATH" | "RTC_DEBUG_MODAL_SET_SECTION" | "RTC_DEBUG_MODAL_UPDATE" | "RTC_DEBUG_MODAL_UPDATE_VIDEO_OUTPUT" | "RTC_DEBUG_POPOUT_WINDOW_OPEN" | "RTC_DEBUG_SET_RECORDING_FLAG" | "RTC_LATENCY_TEST_COMPLETE" | "RUNNING_GAMES_CHANGE" | "RUNNING_GAME_ADD_OVERRIDE" | "RUNNING_GAME_DELETE_ENTRY" | "RUNNING_GAME_EDIT_NAME" | "RUNNING_GAME_TOGGLE_OVERLAY" | "RUNNING_STREAMER_TOOLS_CHANGE" | "SAVE_LAST_NON_VOICE_ROUTE" | "SAVE_LAST_ROUTE" | "SEARCH_AUTOCOMPLETE_QUERY_UPDATE" | "SEARCH_CLEAR_HISTORY" | "SEARCH_EDITOR_STATE_CHANGE" | "SEARCH_EDITOR_STATE_CLEAR" | "SEARCH_ENSURE_SEARCH_STATE" | "SEARCH_FINISH" | "SEARCH_INDEXING" | "SEARCH_MODAL_CLOSE" | "SEARCH_MODAL_OPEN" | "SEARCH_SET_SHOW_BLOCKED_RESULTS" | "SEARCH_START" | "SELECTIVELY_SYNCED_USER_SETTINGS_UPDATE" | "SELF_PRESENCE_STORE_UPDATE" | "SESSIONS_REPLACE" | "SET_CHANNEL_BITRATE" | "SET_CHANNEL_VIDEO_QUALITY_MODE" | "SET_CONSENT_REQUIRED" | "SET_GUILD_FOLDER_EXPANDED" | "SET_HAS_COMPLETED_STEP" | "SET_INTERACTION_COMPONENT_STATE" | "SET_LOCATION_METADATA" | "SET_LOGIN_CREDENTIALS" | "SET_NATIVE_PERMISSION" | "SET_PENDING_REPLY_SHOULD_MENTION" | "SET_RECENT_MENTIONS_FILTER" | "SET_RECENT_MENTIONS_STALE" | "SET_SOUNDPACK" | "SET_TTS_SPEECH_RATE" | "SET_VAD_PERMISSION" | "SHOW_ACTION_SHEET" | "SHOW_ACTION_SHEET_QUICK_SWITCHER" | "SHOW_KEYBOARD_SHORTCUTS" | "SIDEBAR_CLOSE" | "SIDEBAR_CREATE_THREAD" | "SIDEBAR_VIEW_CHANNEL" | "SKUS_FETCH_SUCCESS" | "SKU_FETCH_FAIL" | "SKU_FETCH_START" | "SKU_FETCH_SUCCESS" | "SKU_PURCHASE_CLEAR_ERROR" | "SKU_PURCHASE_FAIL" | "SKU_PURCHASE_MODAL_CLOSE" | "SKU_PURCHASE_MODAL_OPEN" | "SKU_PURCHASE_PREVIEW_FETCH_SUCCESS" | "SKU_PURCHASE_SHOW_CONFIRMATION_STEP" | "SKU_PURCHASE_START" | "SKU_PURCHASE_SUCCESS" | "SKU_PURCHASE_UPDATE_IS_GIFT" | "SLOWMODE_RESET_COOLDOWN" | "SLOWMODE_SET_COOLDOWN" | "SOUNDBOARD_ADD_FAVORITE_SOUND" | "SOUNDBOARD_REMOVE_FAVORITE_SOUND" | "SPEAKING" | "SPEAKING_MESSAGE" | "SPEAK_MESSAGE" | "SPEAK_TEXT" | "SPELLCHECK_LEARN_WORD" | "SPELLCHECK_TOGGLE" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN" | "SPOTIFY_ACCOUNT_ACCESS_TOKEN_REVOKE" | "SPOTIFY_PLAYER_PAUSE" | "SPOTIFY_PLAYER_PLAY" | "SPOTIFY_PLAYER_STATE" | "SPOTIFY_PROFILE_UPDATE" | "SPOTIFY_SET_ACTIVE_DEVICE" | "SPOTIFY_SET_DEVICES" | "SPOTIFY_SET_PROTOCOL_REGISTERED" | "STAGE_INSTANCE_CREATE" | "STAGE_INSTANCE_DELETE" | "STAGE_INSTANCE_UPDATE" | "START_SESSION" | "STATUS_PAGE_INCIDENT" | "STATUS_PAGE_SCHEDULED_MAINTENANCE" | "STATUS_PAGE_SCHEDULED_MAINTENANCE_ACK" | "STICKER_FETCH_SUCCESS" | "STICKER_PACKS_FETCH_START" | "STICKER_PACKS_FETCH_SUCCESS" | "STICKER_PACK_FETCH_SUCCESS" | "STICKER_TRACK_USAGE" | "STOP_SPEAKING" | "STORE_APPLICATION_INTERACTION_FAKE_USER" | "STORE_LISTINGS_FETCH_SUCCESS" | "STORE_LISTING_FETCH_SUCCESS" | "STREAMER_MODE_UPDATE" | "STREAMING_UPDATE" | "STREAM_CLOSE" | "STREAM_CREATE" | "STREAM_DELETE" | "STREAM_LAYOUT_UPDATE" | "STREAM_PREVIEW_FETCH_FAIL" | "STREAM_PREVIEW_FETCH_START" | "STREAM_PREVIEW_FETCH_SUCCESS" | "STREAM_SERVER_UPDATE" | "STREAM_SET_PAUSED" | "STREAM_START" | "STREAM_STATS_UPDATE" | "STREAM_STOP" | "STREAM_TIMED_OUT" | "STREAM_UPDATE" | "STREAM_UPDATE_SELF_HIDDEN" | "STREAM_UPDATE_SETTINGS" | "STREAM_WATCH" | "STRIPE_TOKEN_FAILURE" | "SUBSCRIPTION_PLANS_FETCH" | "SUBSCRIPTION_PLANS_FETCH_FAILURE" | "SUBSCRIPTION_PLANS_FETCH_SUCCESS" | "SUBSCRIPTION_PLANS_RESET" | "SURVEY_FETCHED" | "SURVEY_HIDE" | "SURVEY_OVERRIDE" | "SYSTEM_THEME_CHANGE" | "THERMAL_STATE_CHANGE" | "THREAD_CREATE" | "THREAD_CREATE_LOCAL" | "THREAD_DELETE" | "THREAD_LIST_SYNC" | "THREAD_MEMBERS_UPDATE" | "THREAD_MEMBER_LIST_UPDATE" | "THREAD_MEMBER_LOCAL_UPDATE" | "THREAD_MEMBER_UPDATE" | "THREAD_SETTINGS_DRAFT_CHANGE" | "THREAD_UPDATE" | "TOGGLE_GUILD_FOLDER_EXPAND" | "TOP_EMOJIS_FETCH" | "TOP_EMOJIS_FETCH_SUCCESS" | "TRUNCATE_MENTIONS" | "TRUNCATE_MESSAGES" | "TUTORIAL_INDICATOR_DISMISS" | "TUTORIAL_INDICATOR_HIDE" | "TUTORIAL_INDICATOR_SHOW" | "TUTORIAL_INDICATOR_SUPPRESS_ALL" | "TYPING_START" | "TYPING_START_LOCAL" | "TYPING_STOP" | "TYPING_STOP_LOCAL" | "UNSYNCED_USER_SETTINGS_UPDATE" | "UNVERIFIED_GAME_UPDATE" | "UPDATE_APP_COLORS" | "UPDATE_AVAILABLE" | "UPDATE_CHANNEL_DIMENSIONS" | "UPDATE_CHANNEL_LIST_DIMENSIONS" | "UPDATE_CONSENTS" | "UPDATE_DOWNLOADED" | "UPDATE_ERROR" | "UPDATE_GUILD_LIST_DIMENSIONS" | "UPDATE_HAS_FLOW_START_EVENT_BEEN_EMITTED" | "UPDATE_MANUALLY" | "UPDATE_NOT_AVAILABLE" | "UPDATE_TOKEN" | "UPLOAD_ATTACHMENT_ADD_FILES" | "UPLOAD_ATTACHMENT_CLEAR_ALL_FILES" | "UPLOAD_ATTACHMENT_POP_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILE" | "UPLOAD_ATTACHMENT_REMOVE_FILES" | "UPLOAD_ATTACHMENT_SET_FILE" | "UPLOAD_ATTACHMENT_SET_UPLOADS" | "UPLOAD_ATTACHMENT_UPDATE_FILE" | "UPLOAD_CANCEL_REQUEST" | "UPLOAD_COMPLETE" | "UPLOAD_COMPRESSION_PROGRESS" | "UPLOAD_FAIL" | "UPLOAD_PROGRESS" | "UPLOAD_RESTORE_FAILED_UPLOAD" | "UPLOAD_START" | "USER_ACHIEVEMENT_UPDATE" | "USER_ACTIVITY_STATISTICS_FETCH_SUCCESS" | "USER_APPLIED_BOOSTS_FETCH_SUCCESS" | "USER_AUTHORIZED_APPS_UPDATE" | "USER_CONNECTIONS_INTEGRATION_JOINING" | "USER_CONNECTIONS_UPDATE" | "USER_CONNECTION_UPDATE" | "USER_GUILD_JOIN_REQUEST_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE" | "USER_GUILD_SETTINGS_CHANNEL_UPDATE_BULK" | "USER_GUILD_SETTINGS_FULL_UPDATE" | "USER_GUILD_SETTINGS_GUILD_AND_CHANNELS_UPDATE" | "USER_GUILD_SETTINGS_GUILD_UPDATE" | "USER_GUILD_SETTINGS_REMOVE_PENDING_CHANNEL_UPDATES" | "USER_JOIN_REQUEST_GUILDS_FETCH" | "USER_NON_CHANNEL_ACK" | "USER_NOTE_LOADED" | "USER_NOTE_LOAD_START" | "USER_NOTE_UPDATE" | "USER_PAYMENT_CLIENT_ADD" | "USER_PROFILE_ACCESSIBILITY_TOOLTIP_VIEWED" | "USER_PROFILE_FETCH_FAILURE" | "USER_PROFILE_FETCH_START" | "USER_PROFILE_FETCH_SUCCESS" | "USER_PROFILE_MODAL_CLOSE" | "USER_PROFILE_MODAL_OPEN" | "USER_PROFILE_UPDATE_SUCCESS" | "USER_REQUIRED_ACTION_UPDATE" | "USER_SETTINGS_ACCOUNT_CLOSE" | "USER_SETTINGS_ACCOUNT_INIT" | "USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM" | "USER_SETTINGS_ACCOUNT_SET_DISABLE_SUBMIT" | "USER_SETTINGS_ACCOUNT_SET_PENDING_ACCENT_COLOR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_PENDING_AVATAR_DECORATION" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BANNER" | "USER_SETTINGS_ACCOUNT_SET_PENDING_BIO" | "USER_SETTINGS_ACCOUNT_SET_PENDING_PRONOUNS" | "USER_SETTINGS_ACCOUNT_SET_PENDING_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_AVATAR" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_BANNER" | "USER_SETTINGS_ACCOUNT_SET_TRY_IT_OUT_THEME_COLORS" | "USER_SETTINGS_ACCOUNT_SUBMIT" | "USER_SETTINGS_ACCOUNT_SUBMIT_FAILURE" | "USER_SETTINGS_ACCOUNT_SUBMIT_SUCCESS" | "USER_SETTINGS_CLEAR_ERRORS" | "USER_SETTINGS_LOCALE_OVERRIDE" | "USER_SETTINGS_MODAL_CLEAR_SCROLL_POSITION" | "USER_SETTINGS_MODAL_CLEAR_SUBSECTION" | "USER_SETTINGS_MODAL_CLOSE" | "USER_SETTINGS_MODAL_INIT" | "USER_SETTINGS_MODAL_OPEN" | "USER_SETTINGS_MODAL_RESET" | "USER_SETTINGS_MODAL_SET_SECTION" | "USER_SETTINGS_MODAL_SUBMIT" | "USER_SETTINGS_MODAL_SUBMIT_COMPLETE" | "USER_SETTINGS_MODAL_SUBMIT_FAILURE" | "USER_SETTINGS_MODAL_UPDATE_ACCOUNT" | "USER_SETTINGS_OVERRIDE_APPLY" | "USER_SETTINGS_OVERRIDE_CLEAR" | "USER_SETTINGS_PROTO_ENQUEUE_UPDATE" | "USER_SETTINGS_PROTO_LOAD_IF_NECESSARY" | "USER_SETTINGS_PROTO_UPDATE" | "USER_SETTINGS_PROTO_UPDATE_EDIT_INFO" | "USER_SETTINGS_RESET_ALL_PENDING" | "USER_SETTINGS_RESET_ALL_TRY_IT_OUT" | "USER_SETTINGS_RESET_PENDING_ACCOUNT_CHANGES" | "USER_SETTINGS_RESET_PENDING_PROFILE_CHANGES" | "USER_SETTINGS_THEME_OVERRIDE" | "USER_UPDATE" | "VERIFY_FAILURE" | "VERIFY_SUCCESS" | "VIDEO_FILTER_ASSETS_FETCH_SUCCESS" | "VIDEO_FILTER_ASSET_DELETE_SUCCESS" | "VIDEO_FILTER_ASSET_UPLOAD_SUCCESS" | "VIDEO_SAVE_LAST_USED_BACKGROUND_OPTION" | "VIEW_HISTORY_MARK_VIEW" | "VOICE_CATEGORY_COLLAPSE" | "VOICE_CATEGORY_EXPAND" | "VOICE_CHANNEL_EFFECT_CLEAR" | "VOICE_CHANNEL_EFFECT_RECENT_EMOJI" | "VOICE_CHANNEL_EFFECT_SEND" | "VOICE_CHANNEL_EFFECT_SENT_LOCAL" | "VOICE_CHANNEL_EFFECT_TOGGLE_ANIMATION_TYPE" | "VOICE_CHANNEL_EFFECT_UPDATE_TIME_STAMP" | "VOICE_CHANNEL_SELECT" | "VOICE_SERVER_UPDATE" | "VOICE_STATE_UPDATES" | "WAIT_FOR_REMOTE_SESSION" | "WEBHOOKS_FETCHING" | "WEBHOOKS_UPDATE" | "WEBHOOK_CREATE" | "WEBHOOK_DELETE" | "WEBHOOK_UPDATE" | "WELCOME_SCREEN_FETCH_FAIL" | "WELCOME_SCREEN_FETCH_START" | "WELCOME_SCREEN_FETCH_SUCCESS" | "WELCOME_SCREEN_SETTINGS_CLEAR" | "WELCOME_SCREEN_SETTINGS_RESET" | "WELCOME_SCREEN_SETTINGS_UPDATE" | "WELCOME_SCREEN_SUBMIT" | "WELCOME_SCREEN_SUBMIT_FAILURE" | "WELCOME_SCREEN_SUBMIT_SUCCESS" | "WELCOME_SCREEN_UPDATE" | "WELCOME_SCREEN_VIEW" | "WINDOW_FOCUS" | "WINDOW_FULLSCREEN_CHANGE" | "WINDOW_HIDDEN" | "WINDOW_INIT" | "WINDOW_RESIZED" | "WINDOW_UNLOAD"; diff --git a/src/webpack/common/types/menu.d.ts b/src/webpack/common/types/menu.d.ts new file mode 100644 index 000000000..e48d59458 --- /dev/null +++ b/src/webpack/common/types/menu.d.ts @@ -0,0 +1,68 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import type { ComponentType, CSSProperties, PropsWithChildren, UIEvent } from "react"; + +type RC<C> = ComponentType<PropsWithChildren<C & Record<string, any>>>; + +export interface Menu { + ContextMenu: RC<{ + navId: string; + onClose(): void; + className?: string; + style?: CSSProperties; + hideScroller?: boolean; + onSelect?(): void; + }>; + MenuSeparator: ComponentType; + MenuGroup: RC<any>; + MenuItem: RC<{ + id: string; + label: string; + render?: ComponentType; + onChildrenScroll?: Function; + childRowHeight?: number; + listClassName?: string; + }>; + MenuCheckboxItem: RC<{ + id: string; + }>; + MenuRadioItem: RC<{ + id: string; + }>; + MenuControlItem: RC<{ + id: string; + interactive?: boolean; + }>; +} + +export interface ContextMenuApi { + close(): void; + open( + event: UIEvent, + render?: Menu["ContextMenu"], + options?: { enableSpellCheck?: boolean; }, + renderLazy?: () => Promise<Menu["ContextMenu"]> + ): void; + openLazy( + event: UIEvent, + renderLazy?: () => Promise<Menu["ContextMenu"]>, + options?: { enableSpellCheck?: boolean; } + ): void; +} + diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts new file mode 100644 index 000000000..7222be458 --- /dev/null +++ b/src/webpack/common/types/utils.d.ts @@ -0,0 +1,98 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import type { ReactNode } from "react"; + +import type { FluxEvents } from "./fluxEvents"; + +export { FluxEvents }; + +export interface FluxDispatcher { + _actionHandlers: any; + _subscriptions: any; + dispatch(event: { [key: string]: unknown; type: FluxEvents; }): Promise<void>; + isDispatching(): boolean; + subscribe(event: FluxEvents, callback: (data: any) => void): void; + unsubscribe(event: FluxEvents, callback: (data: any) => void): void; +} + +declare class FluxStore { + constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>); + + emitChange(): void; + getDispatchToken(): string; + getName(): string; + initialize(): void; + initializeIfNeeded(): void; +} + +export interface Flux { + Store: typeof FluxStore; +} + +export type Parser = Record< + | "parse" + | "parseTopic" + | "parseEmbedTitle" + | "parseInlineReply" + | "parseGuildVerificationFormRule" + | "parseGuildEventDescription" + | "parseAutoModerationSystemMessage" + | "parseForumPostGuidelines" + | "parseForumPostMostRecentMessage", + (content: string, inline?: boolean, state?: Record<string, any>) => ReactNode[] +> & Record<"defaultRules" | "guildEventRules", Record<string, Record<"react" | "html" | "parse" | "match" | "order", any>>>; + +export interface Alerts { + show(alert: { + title: any; + body: React.ReactNode; + className?: string; + confirmColor?: string; + cancelText?: string; + confirmText?: string; + secondaryConfirmText?: string; + onCancel?(): void; + onConfirm?(): void; + onConfirmSecondary?(): void; + }): void; + /** This is a noop, it does nothing. */ + close(): void; +} + +export interface SnowflakeUtils { + fromTimestamp(timestamp: number): string; + extractTimestamp(snowflake: string): number; + age(snowflake: string): number; + atPreviousMillisecond(snowflake: string): string; + compare(snowflake1: string, snowflake2: string): number; +} + +interface RestRequestData { + url: string; + query?: Record<string, any>; + body?: Record<string, any>; + oldFormErrors?: boolean; + retries?: number; +} + +export type RestAPI = Record<"delete" | "get" | "patch" | "post" | "put", (data: RestRequestData) => Promise<any>> & { + V6OrEarlierAPIError: Error; + V8APIError: Error; + getAPIBaseURL(withVersion?: boolean): string; +}; diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts new file mode 100644 index 000000000..daac207d2 --- /dev/null +++ b/src/webpack/common/utils.ts @@ -0,0 +1,112 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import type { User } from "discord-types/general"; + +// eslint-disable-next-line path-alias/no-relative +import { _resolveReady,filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy, waitFor } from "../webpack"; +import type * as t from "./types/utils"; + +export let FluxDispatcher: t.FluxDispatcher; +export const Flux: t.Flux = findByPropsLazy("connectStores"); + +export const RestAPI: t.RestAPI = findByPropsLazy("getAPIBaseURL", "get"); +export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); + +export const hljs: typeof import("highlight.js") = findByPropsLazy("highlight"); + +export let SnowflakeUtils: t.SnowflakeUtils; +waitFor(["fromTimestamp", "extractTimestamp"], m => SnowflakeUtils = m); + +export let Parser: t.Parser; +export let Alerts: t.Alerts; + +const ToastType = { + MESSAGE: 0, + SUCCESS: 1, + FAILURE: 2, + CUSTOM: 3 +}; +const ToastPosition = { + TOP: 0, + BOTTOM: 1 +}; + +export const Toasts = { + Type: ToastType, + Position: ToastPosition, + // what's less likely than getting 0 from Math.random()? Getting it twice in a row + genId: () => (Math.random() || Math.random()).toString(36).slice(2), + + // hack to merge with the following interface, dunno if there's a better way + ...{} as { + show(data: { + message: string, + id: string, + /** + * Toasts.Type + */ + type: number, + options?: { + /** + * Toasts.Position + */ + position?: number; + component?: React.ReactNode, + duration?: number; + }; + }): void; + pop(): void; + } +}; + +export const UserUtils = { + fetchUser: findByCodeLazy(".USER(", "getUser") as (id: string) => Promise<User>, +}; + +export const Clipboard = mapMangledModuleLazy('document.queryCommandEnabled("copy")||document.queryCommandSupported("copy")', { + copy: filters.byCode(".default.copy("), + SUPPORTS_COPY: x => typeof x === "boolean", +}); + +export const NavigationRouter = mapMangledModuleLazy("transitionToGuild - ", { + transitionTo: filters.byCode("transitionTo -"), + transitionToGuild: filters.byCode("transitionToGuild -"), + goBack: filters.byCode("goBack()"), + goForward: filters.byCode("goForward()"), +}); + +waitFor(["dispatch", "subscribe"], m => { + FluxDispatcher = m; + const cb = () => { + m.unsubscribe("CONNECTION_OPEN", cb); + _resolveReady(); + }; + m.subscribe("CONNECTION_OPEN", cb); +}); + + +// This is the same module but this is easier +waitFor(filters.byCode("currentToast?"), m => Toasts.show = m); +waitFor(filters.byCode("currentToast:null"), m => Toasts.pop = m); + +waitFor(["show", "close"], m => Alerts = m); +waitFor("parseTopic", m => Parser = m); + +export let SettingsRouter: any; +waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m); From d628924b59e577db5ad4e8f0f0bfc32eafd84c99 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Tue, 24 Jan 2023 23:35:34 -0300 Subject: [PATCH 065/114] ShowHiddenChannels: More improvements (#454) - Remove buttons like the invite button when hovering hidden channels (as they do not work correctly) - Make hideUnreads false work with HiddenIconWithMutedStyle - migrate to definePluginSettings - Change hardcoded constants to webpack gathering - Clean up some patches - Other minor things - Make all patches use lookbehind for cleaner replacements (and better performance too lmao) - Handle trying to connect to hidden channels --- src/plugins/showHiddenChannels.tsx | 175 +++++++++++++++++------------ 1 file changed, 101 insertions(+), 74 deletions(-) diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index 1225cf113..91e4c3ba2 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -17,57 +17,56 @@ */ -import { Settings } from "@api/settings"; +import { definePluginSettings } from "@api/settings"; import { Badge } from "@components/Badge"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; +import { proxyLazy } from "@utils/proxyLazy"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy } from "@webpack"; import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; +import { Channel } from "discord-types/general"; const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); +const Permissions = findByPropsLazy("VIEW_CHANNEL", "ADMINISTRATOR"); +const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); -const VIEW_CHANNEL = 1024n; - -enum ChannelTypes { - GUILD_TEXT = 0, - GUILD_ANNOUNCEMENT = 5, - GUILD_FORUM = 15 -} - -const ChannelTypesToChannelName = { +const ChannelTypesToChannelName = proxyLazy(() => ({ [ChannelTypes.GUILD_TEXT]: "TEXT", [ChannelTypes.GUILD_ANNOUNCEMENT]: "ANNOUNCEMENT", [ChannelTypes.GUILD_FORUM]: "FORUM" -}; +})); enum ShowMode { LockIcon, HiddenIconWithMutedStyle } +const settings = definePluginSettings({ + hideUnreads: { + description: "Hide Unreads", + type: OptionType.BOOLEAN, + default: true, + restartNeeded: true + }, + showMode: { + description: "The mode used to display hidden channels.", + type: OptionType.SELECT, + options: [ + { label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true }, + { label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle }, + ], + restartNeeded: true + } +}); + export default definePlugin({ name: "ShowHiddenChannels", description: "Show channels that you do not have access to view.", authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux], - options: { - hideUnreads: { - description: "Hide Unreads", - type: OptionType.BOOLEAN, - default: true, - restartNeeded: true - }, - showMode: { - description: "The mode used to display hidden channels.", - type: OptionType.SELECT, - options: [ - { label: "Plain style with Lock Icon instead", value: ShowMode.LockIcon, default: true }, - { label: "Muted style with hidden eye icon on the right", value: ShowMode.HiddenIconWithMutedStyle }, - ], - restartNeeded: true - } - }, + settings, + patches: [ { // RenderLevel defines if a channel is hidden, collapsed in category, visible, etc @@ -75,96 +74,125 @@ export default definePlugin({ // These replacements only change the necessary CannotShow's replacement: [ { - match: /(?<restOfFunction>renderLevel:(?<renderLevelExpression>\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?,/, - replace: "$<restOfFunction>$<renderLevelExpression>," + match: /(?<=isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(?<RenderLevels>\i)\..+?(?=,)/, + replace: "this.category.isCollapsed?$<RenderLevels>.WouldShowIfUncollapsed:$<RenderLevels>.Show" + }, + // Move isChannelGatedAndVisible renderLevel logic to the bottom to not show hidden channels in case they are muted + { + match: /(?<=(?<permissionCheck>if\(!\i\.\i\.can\(\i\.\i\.VIEW_CHANNEL.+?{)if\(this\.id===\i\).+?};)(?<isChannelGatedAndVisibleCondition>if\(!\i\.\i\.isChannelGatedAndVisible\(.+?})(?<restOfFunction>.+?)(?=return{renderLevel:\i\.Show.{1,40}return \i)/, + replace: "$<restOfFunction>$<permissionCheck>$<isChannelGatedAndVisibleCondition>}" }, { - match: /(?<restOfFunction>activeJoinedRelevantThreads.{1,100}renderLevel:(?<RenderLevels>\i)\.Show.+?renderLevel:).+?,/, - replace: "$<restOfFunction>$<RenderLevels>.Show," + match: /(?<=renderLevel:(?<renderLevelExpression>\i\(this,\i\)\?\i\.Show:\i\.WouldShowIfUncollapsed).+?renderLevel:).+?(?=,)/, + replace: "$<renderLevelExpression>" }, { - match: /(?<restOfFunction>isChannelGatedAndVisible\(this\.record\.guild_id,this\.record\.id\).+?renderLevel:)(?<RenderLevels>\i)\.CannotShow/, - replace: "$<restOfFunction>this.category.isCollapsed?$<RenderLevels>.WouldShowIfUncollapsed:$<RenderLevels>.Show" + match: /(?<=activeJoinedRelevantThreads.+?renderLevel:.+?,threadIds:\i\(this.record.+?renderLevel:)(?<RenderLevels>\i)\..+?(?=,)/, + replace: "$<RenderLevels>.Show" }, { - match: /(?<restOfFunction>getRenderLevel=function.+?return).+?\?(?<renderLevelExpression>.+?):\i\.CannotShow}/, - replace: "$<restOfFunction> $<renderLevelExpression>}" + match: /(?<=getRenderLevel=function.+?return ).+?\?(?<renderLevelExpressionWithoutPermCheck>.+?):\i\.CannotShow(?=})/, + replace: "$<renderLevelExpressionWithoutPermCheck>" } ] }, { - // inside the onMouseClick handler, we check if the channel is hidden and open the modal if it is - find: ".handleThreadsPopoutClose();", - replacement: { - match: /(?<this>\i)\.handleThreadsPopoutClose\(\);/, - replace: "if(arguments[0].button===0&&$self.channelSelected($<this>?.props?.channel))return;$&" - } - }, - { - find: ".UNREAD_HIGHLIGHT", - predicate: () => Settings.plugins.ShowHiddenChannels.hideUnreads === true, - replacement: [{ - // Hide unreads - match: /(?<restOfFunction>\i\.connected,)(?<hasUnread>\i)=(?<props>\i).unread/, - replace: "$<restOfFunction>$<hasUnread>=$self.isHiddenChannel($<props>.channel)?false:$<props>.unread" - }] + // inside the onMouseDown handler, we check if the channel is hidden and open the modal if it is + find: "VoiceChannel.renderPopout: There must always be something to render", + replacement: [ + { + match: /(?=(?<this>\i)\.handleThreadsPopoutClose\(\))/, + replace: "if($self.isHiddenChannel($<this>.props.channel)&&arguments[0].button===0){" + + "$self.onHiddenChannelSelected($<this>.props.channel);" + + "return;" + + "}" + }, + // Do nothing when trying to join a voice channel if the channel is hidden + { + match: /(?<=handleClick=function\(\){)(?=.{1,80}(?<this>\i)\.handleVoiceConnect\(\))/, + replace: "if($self.isHiddenChannel($<this>.props.channel))return;" + }, + // Render null instead of the buttons if the channel is hidden + ...[ + "renderEditButton", + "renderInviteButton", + "renderOpenChatButton" + ].map(func => ({ + match: new RegExp(`(?<=\\i\\.${func}=function\\(\\){)`, "g"), // Global because Discord has multiple declarations of the same functions + replace: "if($self.isHiddenChannel(this.props.channel))return null;" + })) + ] }, { find: ".Messages.CHANNEL_TOOLTIP_DIRECTORY", - predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.LockIcon, + predicate: () => settings.store.showMode === ShowMode.LockIcon, replacement: { // Lock Icon - match: /switch\((?<channel>\i)\.type\).{1,30}\.GUILD_ANNOUNCEMENT.{1,30}\(0,\i\.\i\)\(\i\)/, - replace: "if($self.isHiddenChannel($<channel>))return $self.LockIcon;$&" + match: /(?=switch\((?<channel>\i)\.type\).{1,30}\.GUILD_ANNOUNCEMENT.{1,30}\(0,\i\.\i\))/, + replace: "if($self.isHiddenChannel($<channel>))return $self.LockIcon;" } }, { find: ".UNREAD_HIGHLIGHT", - predicate: () => Settings.plugins.ShowHiddenChannels.showMode === ShowMode.HiddenIconWithMutedStyle, + predicate: () => settings.store.hideUnreads === true, + replacement: [{ + // Hide unreads + match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/, + replace: "$self.isHiddenChannel($<props>.channel)?false:" + }] + }, + { + find: ".UNREAD_HIGHLIGHT", + predicate: () => settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, replacement: [ // Make the channel appear as muted if it's hidden { - match: /(?<restOfFunction>\i\.name,)(?<isMuted>\i)=(?<props>\i).muted/, - replace: "$<restOfFunction>$<isMuted>=$self.isHiddenChannel($<props>.channel)?true:$<props>.muted" + match: /(?<=\i\.name,\i=)(?=(?<props>\i)\.muted)/, + replace: "$self.isHiddenChannel($<props>.channel)?true:" }, // Add the hidden eye icon if the channel is hidden { - match: /channel:(?<channel>\i),.+?\.channelName.+?\.children.+?:null/, - replace: "$&,$self.isHiddenChannel($<channel>)?$self.HiddenChannelIcon():null" + match: /(?<=(?<channel>\i)=\i\.channel,.+?\(\)\.children.+?:null)/, + replace: ",$self.isHiddenChannel($<channel>)?$self.HiddenChannelIcon():null" }, // Make voice channels also appear as muted if they are muted { - match: /(?<restOfFunction>.wrapper:\i\(\).notInteractive,)(?<secondRestOfFunction>.+?)(?<isMutedClassExpression>(?<isMuted>\i)\?\i\.MUTED:)/, - replace: "$<restOfFunction>$<isMutedClassExpression>\"\",$<secondRestOfFunction>$<isMuted>?\"\":" + match: /(?<=\i\(\)\.wrapper:\i\(\)\.notInteractive,)(?<otherClasses>.+?)(?<mutedClassExpression>(?<isMuted>\i)\?\i\.MUTED)/, + replace: "$<mutedClassExpression>:\"\",$<otherClasses>$<isMuted>?\"\"" } ] }, + // Make muted channels also appear as unread if hide unreads is false, using the HiddenIconWithMutedStyle and the channel is hidden + { + find: ".UNREAD_HIGHLIGHT", + predicate: () => settings.store.hideUnreads === false && settings.store.showMode === ShowMode.HiddenIconWithMutedStyle, + replacement: { + match: /(?<=(?<channel>\i)=\i\.channel,.+?\.LOCKED:\i)/, + replace: "&&!($self.settings.store.hideUnreads===false&&$self.isHiddenChannel($<channel>))" + } + }, { // Hide New unreads box for hidden channels find: '.displayName="ChannelListUnreadsStore"', replacement: { - match: /(?<restOfFunction>return null!=(?<channel>\i))(?<secondRestOfFunction>&&null!=\i\.getGuildId\(\).{1,120}hasRelevantUnread\(\i\)\))/, - replace: "$<restOfFunction>&&!$self.isHiddenChannel($<channel>)$<secondRestOfFunction>" + match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/, + replace: "&&!$self.isHiddenChannel($<channel>)" } - }, + } ], - isHiddenChannel(channel) { + isHiddenChannel(channel: Channel & { channelId?: string; }) { if (!channel) return false; if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; - return !PermissionStore.can(VIEW_CHANNEL, channel); + return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel); }, - channelSelected(channel) { - if (!channel) return false; - - const isHidden = this.isHiddenChannel(channel); - + onHiddenChannelSelected(channel: Channel) { // Check for type, otherwise it would attempt to show the modal for stage channels - if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type) && isHidden) { + if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type)) { openModal(modalProps => ( <ModalRoot size={ModalSize.SMALL} {...modalProps}> <ModalHeader> @@ -174,7 +202,7 @@ export default definePlugin({ {channel.isNSFW() && <Badge text="NSFW" color="var(--status-danger)" />} </Flex> </ModalHeader> - <ModalContent style={{ marginBottom: 10, marginTop: 10, marginRight: 8, marginLeft: 8 }}> + <ModalContent style={{ margin: "10px 8px" }}> <Text variant="text-md/normal">You don't have permission to view {channel.type === ChannelTypes.GUILD_FORUM ? "posts" : "messages"} in this channel.</Text> {(channel.topic ?? "").length > 0 && ( <> @@ -211,7 +239,6 @@ export default definePlugin({ </ModalRoot> )); } - return isHidden; }, LockIcon: () => ( From 5d3148cf50f315a303e8e7f8ac2f0861b0279eb1 Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Wed, 25 Jan 2023 03:42:01 +0100 Subject: [PATCH 066/114] New plugin: VcNarrator (#402) Co-authored-by: Nico <nico@d3sox.me> --- scripts/build/common.mjs | 2 +- src/plugins/vcNarrator.tsx | 334 +++++++++++++++++++++++++++++++++++++ 2 files changed, 335 insertions(+), 1 deletion(-) create mode 100644 src/plugins/vcNarrator.tsx diff --git a/scripts/build/common.mjs b/scripts/build/common.mjs index 9e0023c70..c6a082dc9 100644 --- a/scripts/build/common.mjs +++ b/scripts/build/common.mjs @@ -73,7 +73,7 @@ export const globPlugins = { continue; } const mod = `p${i}`; - code += `import ${mod} from "./${dir}/${file.replace(/.tsx?$/, "")}";\n`; + code += `import ${mod} from "./${dir}/${file.replace(/\.tsx?$/, "")}";\n`; plugins += `[${mod}.name]:${mod},\n`; i++; } diff --git a/src/plugins/vcNarrator.tsx b/src/plugins/vcNarrator.tsx new file mode 100644 index 000000000..a8558727d --- /dev/null +++ b/src/plugins/vcNarrator.tsx @@ -0,0 +1,334 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { Settings } from "@api/settings"; +import { ErrorCard } from "@components/ErrorCard"; +import { Devs } from "@utils/constants"; +import Logger from "@utils/Logger"; +import { wordsToTitle } from "@utils/text"; +import definePlugin, { OptionType, PluginOptionsItem } from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Button, ChannelStore, FluxDispatcher, Forms, Margins, SelectedChannelStore, useMemo, UserStore } from "@webpack/common"; + +interface VoiceState { + userId: string; + channelId?: string; + oldChannelId?: string; + deaf: boolean; + mute: boolean; + selfDeaf: boolean; + selfMute: boolean; +} + +const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentClientVoiceChannelId"); + +// Mute/Deaf for other people than you is commented out, because otherwise someone can spam it and it will be annoying +// Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would +// not say the second mute, which would lead you to believe they're unmuted + +function getEnglishVoices() { + const voices = speechSynthesis.getVoices(); + const englishVoices = voices.filter(v => v.lang.startsWith("en")); + return !englishVoices.length ? voices : englishVoices; +} + +function speak(text: string, settings: any = Settings.plugins.VcNarrator) { + if (!text) return; + + const speech = new SpeechSynthesisUtterance(text); + let voice = speechSynthesis.getVoices().find(v => v.voiceURI === settings.voice); + if (!voice) { + new Logger("VcNarrator").error(`Voice "${settings.voice}" not found. Resetting to default.`); + voice = speechSynthesis.getVoices().find(v => v.default); + settings.voice = voice?.voiceURI; + if (!voice) return; // This should never happen + } + speech.voice = voice!; + speech.volume = settings.volume; + speech.rate = settings.rate; + speechSynthesis.speak(speech); +} + +function clean(str: string, fallback: string) { + return str.normalize("NFKC") + .replace(/[^\w ]/g, "") + .trim() + || fallback; +} + +function formatText(str: string, user: string, channel: string) { + return str + .replaceAll("{{USER}}", clean(user, user ? "Someone" : "")) + .replaceAll("{{CHANNEL}}", clean(channel, "channel")); +} + +/* +let StatusMap = {} as Record<string, { + mute: boolean; + deaf: boolean; +}>; +*/ + +// For every user, channelId and oldChannelId will differ when moving channel. +// Only for the local user, channelId and oldChannelId will be the same when moving channel, +// for some ungodly reason +let myLastChannelId: string | undefined; + +function getTypeAndChannelId({ channelId, oldChannelId }: VoiceState, isMe: boolean) { + if (isMe && channelId !== myLastChannelId) { + oldChannelId = myLastChannelId; + myLastChannelId = channelId; + } + + if (channelId !== oldChannelId) { + if (channelId) return [oldChannelId ? "move" : "join", channelId]; + if (oldChannelId) return ["leave", oldChannelId]; + } + /* + if (channelId) { + if (deaf || selfDeaf) return ["deafen", channelId]; + if (mute || selfMute) return ["mute", channelId]; + const oldStatus = StatusMap[userId]; + if (oldStatus.deaf) return ["undeafen", channelId]; + if (oldStatus.mute) return ["unmute", channelId]; + } + */ + return ["", ""]; +} + +/* +function updateStatuses(type: string, { deaf, mute, selfDeaf, selfMute, userId, channelId }: VoiceState, isMe: boolean) { + if (isMe && (type === "join" || type === "move")) { + StatusMap = {}; + const states = VoiceStateStore.getVoiceStatesForChannel(channelId!) as Record<string, VoiceState>; + for (const userId in states) { + const s = states[userId]; + StatusMap[userId] = { + mute: s.mute || s.selfMute, + deaf: s.deaf || s.selfDeaf + }; + } + return; + } + + if (type === "leave" || (type === "move" && channelId !== SelectedChannelStore.getVoiceChannelId())) { + if (isMe) + StatusMap = {}; + else + delete StatusMap[userId]; + + return; + } + + StatusMap[userId] = { + deaf: deaf || selfDeaf, + mute: mute || selfMute + }; +} +*/ + +function handleVoiceStates({ voiceStates }: { voiceStates: VoiceState[]; }) { + const myChanId = SelectedChannelStore.getVoiceChannelId(); + const myId = UserStore.getCurrentUser().id; + + for (const state of voiceStates) { + const { userId, channelId, oldChannelId } = state; + const isMe = userId === myId; + if (!isMe) { + if (!myChanId) continue; + if (channelId !== myChanId && oldChannelId !== myChanId) continue; + } + + const [type, id] = getTypeAndChannelId(state, isMe); + if (!type) continue; + + const template = Settings.plugins.VcNarrator[type + "Message"]; + const user = isMe ? "" : UserStore.getUser(userId).username; + const channel = ChannelStore.getChannel(id).name; + + speak(formatText(template, user, channel)); + + // updateStatuses(type, state, isMe); + } +} + +function handleToggleSelfMute() { + const chanId = SelectedChannelStore.getVoiceChannelId()!; + const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState; + if (!s) return; + + const event = s.mute || s.selfMute ? "unmute" : "mute"; + speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name)); +} + +function handleToggleSelfDeafen() { + const chanId = SelectedChannelStore.getVoiceChannelId()!; + const s = VoiceStateStore.getVoiceStateForChannel(chanId) as VoiceState; + if (!s) return; + + const event = s.deaf || s.selfDeaf ? "undeafen" : "deafen"; + speak(formatText(Settings.plugins.VcNarrator[event + "Message"], "", ChannelStore.getChannel(chanId).name)); +} + +function playSample(tempSettings: any, type: string) { + const settings = Object.assign({}, Settings.plugins.VcNarrator, tempSettings); + + speak(formatText(settings[type + "Message"], UserStore.getCurrentUser().username, "general"), settings); +} + +export default definePlugin({ + name: "VcNarrator", + description: "Announces when users join, leave, or move voice channels via narrator", + authors: [Devs.Ven], + + start() { + if (speechSynthesis.getVoices().length === 0) { + new Logger("VcNarrator").warn("No Narrator voices found. Thus, this plugin will not work. Check my Settings for more info"); + return; + } + FluxDispatcher.subscribe("VOICE_STATE_UPDATES", handleVoiceStates); + FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_MUTE", handleToggleSelfMute); + FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_DEAF", handleToggleSelfDeafen); + }, + + stop() { + FluxDispatcher.unsubscribe("VOICE_STATE_UPDATES", handleVoiceStates); + FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_MUTE", handleToggleSelfMute); + FluxDispatcher.subscribe("AUDIO_TOGGLE_SELF_DEAF", handleToggleSelfDeafen); + }, + + optionsCache: null as Record<string, PluginOptionsItem> | null, + + get options() { + return this.optionsCache ??= { + voice: { + type: OptionType.SELECT, + description: "Narrator Voice", + options: getEnglishVoices().map(v => ({ + label: v.name, + value: v.voiceURI, + default: v.default + })) + }, + volume: { + type: OptionType.SLIDER, + description: "Narrator Volume", + default: 1, + markers: [0, 0.25, 0.5, 0.75, 1], + stickToMarkers: false + }, + rate: { + type: OptionType.SLIDER, + description: "Narrator Speed", + default: 1, + markers: [0.1, 0.5, 1, 2, 5, 10], + stickToMarkers: false + }, + joinMessage: { + type: OptionType.STRING, + description: "Join Message", + default: "{{USER}} joined {{CHANNEL}}" + }, + leaveMessage: { + type: OptionType.STRING, + description: "Leave Message", + default: "{{USER}} left {{CHANNEL}}" + }, + moveMessage: { + type: OptionType.STRING, + description: "Move Message", + default: "{{USER}} moved to {{CHANNEL}}" + }, + muteMessage: { + type: OptionType.STRING, + description: "Mute Message (only self for now)", + default: "{{USER}} Muted" + }, + unmuteMessage: { + type: OptionType.STRING, + description: "Unmute Message (only self for now)", + default: "{{USER}} unmuted" + }, + deafenMessage: { + type: OptionType.STRING, + description: "Deafen Message (only self for now)", + default: "{{USER}} deafened" + }, + undeafenMessage: { + type: OptionType.STRING, + description: "Undeafen Message (only self for now)", + default: "{{USER}} undeafened" + } + }; + }, + + settingsAboutComponent({ tempSettings: s }) { + const [hasVoices, hasEnglishVoices] = useMemo(() => { + const voices = speechSynthesis.getVoices(); + return [voices.length !== 0, voices.some(v => v.lang.startsWith("en"))]; + }, []); + + const types = useMemo( + () => Object.keys(Vencord.Plugins.plugins.VcNarrator.options!).filter(k => k.endsWith("Message")).map(k => k.slice(0, -7)), + [], + ); + + let errorComponent: React.ReactElement | null = null; + if (!hasVoices) { + let error = "No narrator voices found. "; + error += navigator.platform?.toLowerCase().includes("linux") + ? "Install speech-dispatcher or espeak and run Discord with the --enable-speech-dispatcher flag" + : "Try installing some in the Narrator settings of your Operating System"; + errorComponent = <ErrorCard>{error}</ErrorCard>; + } else if (!hasEnglishVoices) { + errorComponent = <ErrorCard>You don't have any English voices installed, so the narrator might sound weird</ErrorCard>; + } + + return ( + <Forms.FormSection> + <Forms.FormText> + You can customise the spoken messages below. You can disable specific messages by setting them to nothing + </Forms.FormText> + <Forms.FormText> + The special placeholders <code>{"{{USER}}"}</code> and <code>{"{{CHANNEL}}"}</code>{" "} + will be replaced with the user's name (nothing if it's yourself) and the channel's name respectively + </Forms.FormText> + {hasEnglishVoices && ( + <> + <Forms.FormTitle className={Margins.marginTop20} tag="h3">Play Example Sounds</Forms.FormTitle> + <div + style={{ + display: "grid", + gridTemplateColumns: "repeat(4, 1fr)", + gap: "1rem", + }} + className={"vc-narrator-buttons"} + > + {types.map(t => ( + <Button key={t} onClick={() => playSample(s, t)}> + {wordsToTitle([t])} + </Button> + ))} + </div> + </> + )} + {errorComponent} + </Forms.FormSection> + ); + } +}); From 41226f0358a7605e2fb668e7b6df30f658c79289 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 25 Jan 2023 04:08:37 +0100 Subject: [PATCH 067/114] Fix ShowHiddenChannels --- src/plugins/showHiddenChannels.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index 91e4c3ba2..c969b305c 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -24,12 +24,12 @@ import { Devs } from "@utils/constants"; import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { proxyLazy } from "@utils/proxyLazy"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy } from "@webpack"; +import { findByPropsLazy, findLazy } from "@webpack"; import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); -const Permissions = findByPropsLazy("VIEW_CHANNEL", "ADMINISTRATOR"); +const Permissions = findLazy(m => typeof m.VIEW_CHANNEL === "bigint"); const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); const ChannelTypesToChannelName = proxyLazy(() => ({ From 103cd143613a9edb2217d1a19cfb34e086b40820 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 25 Jan 2023 17:49:19 +0100 Subject: [PATCH 068/114] Fix Themes Tab --- src/components/VencordSettings/ThemesTab.tsx | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/components/VencordSettings/ThemesTab.tsx b/src/components/VencordSettings/ThemesTab.tsx index b59590c46..b2cf85b6a 100644 --- a/src/components/VencordSettings/ThemesTab.tsx +++ b/src/components/VencordSettings/ThemesTab.tsx @@ -75,11 +75,11 @@ function Validators({ themeLinks }: { themeLinks: string[]; }) { export default ErrorBoundary.wrap(function () { const settings = useSettings(); - const ref = React.useRef<HTMLTextAreaElement>(null); + const [themeText, setThemeText] = React.useState(settings.themeLinks.join("\n")); function onBlur() { settings.themeLinks = [...new Set( - ref.current!.value + themeText .trim() .split(/\n+/) .map(s => s.trim()) @@ -119,8 +119,8 @@ export default ErrorBoundary.wrap(function () { padding: ".5em", border: "1px solid var(--background-modifier-accent)" }} - ref={ref} - defaultValue={settings.themeLinks.join("\n")} + value={themeText} + onChange={e => setThemeText(e.currentTarget.value)} className={TextAreaProps.textarea} placeholder="Theme Links" spellCheck={false} From 6ab4b48b47eebc12c9d74de669e224045030b457 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 25 Jan 2023 21:05:35 +0100 Subject: [PATCH 069/114] chore: bump deps --- package.json | 32 ++-- pnpm-lock.yaml | 385 ++++++++++++++++++++++++++----------------------- 2 files changed, 222 insertions(+), 195 deletions(-) diff --git a/package.json b/package.json index 459e01b6a..fe37ba341 100644 --- a/package.json +++ b/package.json @@ -1,8 +1,8 @@ { "name": "vencord", "private": "true", - "version": "1.0.1", - "description": "A Discord client mod that does things differently", + "version": "1.0.3", + "description": "The cutest Discord client mod", "keywords": [], "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { @@ -30,23 +30,22 @@ "watch": "node scripts/build/build.mjs --watch" }, "dependencies": { + "@vap/core": "0.0.12", + "@vap/shiki": "0.10.3", "fflate": "^0.7.4" }, "devDependencies": { "@types/diff": "^5.0.2", - "@types/lodash": "^4.14.0", - "@types/node": "^18.11.9", - "@types/react": "^18.0.25", - "@types/react-dom": "^18.0.9", + "@types/lodash": "^4.14.191", + "@types/node": "^18.11.18", + "@types/react": "^18.0.27", + "@types/react-dom": "^18.0.10", "@types/yazl": "^2.4.2", - "@typescript-eslint/eslint-plugin": "^5.44.0", - "@typescript-eslint/parser": "^5.44.0", - "@vap/core": "0.0.12", - "@vap/shiki": "0.10.3", - "console-menu": "^0.1.0", + "@typescript-eslint/eslint-plugin": "^5.49.0", + "@typescript-eslint/parser": "^5.49.0", "diff": "^5.1.0", "discord-types": "^1.3.26", - "esbuild": "^0.15.16", + "esbuild": "^0.15.18", "eslint": "^8.28.0", "eslint-import-resolver-alias": "^1.1.2", "eslint-plugin-header": "^3.1.1", @@ -55,10 +54,10 @@ "eslint-plugin-unused-imports": "^2.0.0", "highlight.js": "10.6.0", "moment": "^2.29.4", - "puppeteer-core": "^19.3.0", + "puppeteer-core": "^19.6.0", "standalone-electron-types": "^1.0.0", - "type-fest": "^3.3.0", - "typescript": "^4.9.3" + "type-fest": "^3.5.3", + "typescript": "^4.9.4" }, "packageManager": "pnpm@7.13.4", "pnpm": { @@ -68,7 +67,8 @@ }, "peerDependencyRules": { "ignoreMissing": [ - "eslint-plugin-import" + "eslint-plugin-import", + "eslint" ] }, "allowedDeprecatedVersions": { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 02c2ea6c3..60e1df831 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -10,19 +10,18 @@ patchedDependencies: specifiers: '@types/diff': ^5.0.2 - '@types/lodash': ^4.14.0 - '@types/node': ^18.11.9 - '@types/react': ^18.0.25 - '@types/react-dom': ^18.0.9 + '@types/lodash': ^4.14.191 + '@types/node': ^18.11.18 + '@types/react': ^18.0.27 + '@types/react-dom': ^18.0.10 '@types/yazl': ^2.4.2 - '@typescript-eslint/eslint-plugin': ^5.44.0 - '@typescript-eslint/parser': ^5.44.0 + '@typescript-eslint/eslint-plugin': ^5.49.0 + '@typescript-eslint/parser': ^5.49.0 '@vap/core': 0.0.12 '@vap/shiki': 0.10.3 - console-menu: ^0.1.0 diff: ^5.1.0 discord-types: ^1.3.26 - esbuild: ^0.15.16 + esbuild: ^0.15.18 eslint: ^8.28.0 eslint-import-resolver-alias: ^1.1.2 eslint-plugin-header: ^3.1.1 @@ -32,46 +31,45 @@ specifiers: fflate: ^0.7.4 highlight.js: 10.6.0 moment: ^2.29.4 - puppeteer-core: ^19.3.0 + puppeteer-core: ^19.6.0 standalone-electron-types: ^1.0.0 - type-fest: ^3.3.0 - typescript: ^4.9.3 + type-fest: ^3.5.3 + typescript: ^4.9.4 dependencies: + '@vap/core': 0.0.12 + '@vap/shiki': 0.10.3 fflate: 0.7.4 devDependencies: '@types/diff': 5.0.2 - '@types/lodash': 4.14.189 - '@types/node': 18.11.9 - '@types/react': 18.0.25 - '@types/react-dom': 18.0.9 + '@types/lodash': 4.14.191 + '@types/node': 18.11.18 + '@types/react': 18.0.27 + '@types/react-dom': 18.0.10 '@types/yazl': 2.4.2 - '@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au - '@typescript-eslint/parser': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a - '@vap/core': 0.0.12 - '@vap/shiki': 0.10.3 - console-menu: 0.1.0 + '@typescript-eslint/eslint-plugin': 5.49.0_ffoscbl6fkz64kp3vlggrfqozm + '@typescript-eslint/parser': 5.49.0_wy4udjehnvkneqnogzx5kughki diff: 5.1.0 discord-types: 1.3.26 - esbuild: 0.15.16 + esbuild: 0.15.18 eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-import-resolver-alias: 1.1.2 eslint-plugin-header: 3.1.1_eslint@8.28.0 eslint-plugin-path-alias: 1.0.0_m6sma4g6bh67km3q6igf6uxaja_eslint@8.28.0 eslint-plugin-simple-import-sort: 8.0.0_eslint@8.28.0 - eslint-plugin-unused-imports: 2.0.0_5am2datodjm2qi4eijrjrnoz54 + eslint-plugin-unused-imports: 2.0.0_nzrwdcb3mq4ezaurfymehngbla highlight.js: 10.6.0 moment: 2.29.4 - puppeteer-core: 19.3.0 + puppeteer-core: 19.6.0 standalone-electron-types: 1.0.0 - type-fest: 3.3.0 - typescript: 4.9.3 + type-fest: 3.5.3 + typescript: 4.9.4 packages: - /@esbuild/android-arm/0.15.16: - resolution: {integrity: sha512-nyB6CH++2mSgx3GbnrJsZSxzne5K0HMyNIWafDHqYy7IwxFc4fd/CgHVZXr8Eh+Q3KbIAcAe3vGyqIPhGblvMQ==} + /@esbuild/android-arm/0.15.18: + resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -79,8 +77,8 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.15.16: - resolution: {integrity: sha512-SDLfP1uoB0HZ14CdVYgagllgrG7Mdxhkt4jDJOKl/MldKrkQ6vDJMZKl2+5XsEY/Lzz37fjgLQoJBGuAw/x8kQ==} + /@esbuild/linux-loong64/0.15.18: + resolution: {integrity: sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -154,22 +152,22 @@ packages: resolution: {integrity: sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==} dev: true - /@types/lodash/4.14.189: - resolution: {integrity: sha512-kb9/98N6X8gyME9Cf7YaqIMvYGnBSWqEci6tiettE6iJWH1XdJz/PO8LB0GtLCG7x8dU3KWhZT+lA1a35127tA==} + /@types/lodash/4.14.191: + resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} dev: true - /@types/node/18.11.9: - resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} + /@types/node/18.11.18: + resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} dev: true /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: true - /@types/react-dom/18.0.9: - resolution: {integrity: sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==} + /@types/react-dom/18.0.10: + resolution: {integrity: sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==} dependencies: - '@types/react': 18.0.25 + '@types/react': 18.0.27 dev: true /@types/react/17.0.2: @@ -179,8 +177,8 @@ packages: csstype: 3.1.0 dev: true - /@types/react/18.0.25: - resolution: {integrity: sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==} + /@types/react/18.0.27: + resolution: {integrity: sha512-3vtRKHgVxu3Jp9t718R9BuzoD4NcQ8YJ5XRzsSKxNDiDonD2MXIT1TmSkenxuCycZJoQT5d2vE8LwWJxBC1gmA==} dependencies: '@types/prop-types': 15.7.5 '@types/scheduler': 0.16.2 @@ -199,98 +197,104 @@ packages: resolution: {integrity: sha512-Cn6WYCm0tXv8p6k+A8PvbDG763EDpBoTzHdA+Q/MF6H3sapGjCm9NzoaJncJS9tUKSuCoDs9XHxYYsQDgxR6kw==} requiresBuild: true dependencies: - '@types/node': 18.11.9 + '@types/node': 18.11.18 dev: true optional: true /@types/yazl/2.4.2: resolution: {integrity: sha512-T+9JH8O2guEjXNxqmybzQ92mJUh2oCwDDMSSimZSe1P+pceZiFROZLYmcbqkzV5EUwz6VwcKXCO2S2yUpra6XQ==} dependencies: - '@types/node': 18.11.9 + '@types/node': 18.11.18 dev: true - /@typescript-eslint/eslint-plugin/5.45.0_czs5uoqkd3podpy6vgtsxfc7au: - resolution: {integrity: sha512-CXXHNlf0oL+Yg021cxgOdMHNTXD17rHkq7iW6RFHoybdFgQBjU3yIXhhcPpGwr1CjZlo6ET8C6tzX5juQoXeGA==} + /@typescript-eslint/eslint-plugin/5.49.0_ffoscbl6fkz64kp3vlggrfqozm: + resolution: {integrity: sha512-IhxabIpcf++TBaBa1h7jtOWyon80SXPRLDq0dVz5SLFC/eW6tofkw/O7Ar3lkx5z5U6wzbKDrl2larprp5kk5Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: '@typescript-eslint/parser': ^5.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: + eslint: + optional: true typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a - '@typescript-eslint/scope-manager': 5.45.0 - '@typescript-eslint/type-utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a - '@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/parser': 5.49.0_wy4udjehnvkneqnogzx5kughki + '@typescript-eslint/scope-manager': 5.49.0 + '@typescript-eslint/type-utils': 5.49.0_wy4udjehnvkneqnogzx5kughki + '@typescript-eslint/utils': 5.49.0_wy4udjehnvkneqnogzx5kughki debug: 4.3.4 eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe ignore: 5.2.0 natural-compare-lite: 1.4.0 regexpp: 3.2.0 semver: 7.3.7 - tsutils: 3.21.0_typescript@4.9.3 - typescript: 4.9.3 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/parser/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a: - resolution: {integrity: sha512-brvs/WSM4fKUmF5Ot/gEve6qYiCMjm6w4HkHPfS6ZNmxTS0m0iNN4yOChImaCkqc1hRwFGqUyanMXuGal6oyyQ==} + /@typescript-eslint/parser/5.49.0_wy4udjehnvkneqnogzx5kughki: + resolution: {integrity: sha512-veDlZN9mUhGqU31Qiv2qEp+XrJj5fgZpJ8PW30sHU+j/8/e5ruAhLaVDAeznS7A7i4ucb/s8IozpDtt9NqCkZg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 typescript: '*' peerDependenciesMeta: + eslint: + optional: true typescript: optional: true dependencies: - '@typescript-eslint/scope-manager': 5.45.0 - '@typescript-eslint/types': 5.45.0 - '@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3 + '@typescript-eslint/scope-manager': 5.49.0 + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/typescript-estree': 5.49.0_typescript@4.9.4 debug: 4.3.4 eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe - typescript: 4.9.3 + typescript: 4.9.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/scope-manager/5.45.0: - resolution: {integrity: sha512-noDMjr87Arp/PuVrtvN3dXiJstQR1+XlQ4R1EvzG+NMgXi8CuMCXpb8JqNtFHKceVSQ985BZhfRdowJzbv4yKw==} + /@typescript-eslint/scope-manager/5.49.0: + resolution: {integrity: sha512-clpROBOiMIzpbWNxCe1xDK14uPZh35u4QaZO1GddilEzoCLAEz4szb51rBpdgurs5k2YzPtJeTEN3qVbG+LRUQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.45.0 - '@typescript-eslint/visitor-keys': 5.45.0 + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/visitor-keys': 5.49.0 dev: true - /@typescript-eslint/type-utils/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a: - resolution: {integrity: sha512-DY7BXVFSIGRGFZ574hTEyLPRiQIvI/9oGcN8t1A7f6zIs6ftbrU0nhyV26ZW//6f85avkwrLag424n+fkuoJ1Q==} + /@typescript-eslint/type-utils/5.49.0_wy4udjehnvkneqnogzx5kughki: + resolution: {integrity: sha512-eUgLTYq0tR0FGU5g1YHm4rt5H/+V2IPVkP0cBmbhRyEmyGe4XvJ2YJ6sYTmONfjmdMqyMLad7SB8GvblbeESZA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: '*' typescript: '*' peerDependenciesMeta: + eslint: + optional: true typescript: optional: true dependencies: - '@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3 - '@typescript-eslint/utils': 5.45.0_hsf322ms6xhhd4b5ne6lb74y4a + '@typescript-eslint/typescript-estree': 5.49.0_typescript@4.9.4 + '@typescript-eslint/utils': 5.49.0_wy4udjehnvkneqnogzx5kughki debug: 4.3.4 eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe - tsutils: 3.21.0_typescript@4.9.3 - typescript: 4.9.3 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/types/5.45.0: - resolution: {integrity: sha512-QQij+u/vgskA66azc9dCmx+rev79PzX8uDHpsqSjEFtfF2gBUTRCpvYMh2gw2ghkJabNkPlSUCimsyBEQZd1DA==} + /@typescript-eslint/types/5.49.0: + resolution: {integrity: sha512-7If46kusG+sSnEpu0yOz2xFv5nRz158nzEXnJFCGVEHWnuzolXKwrH5Bsf9zsNlOQkyZuk0BZKKoJQI+1JPBBg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /@typescript-eslint/typescript-estree/5.45.0_typescript@4.9.3: - resolution: {integrity: sha512-maRhLGSzqUpFcZgXxg1qc/+H0bT36lHK4APhp0AEUVrpSwXiRAomm/JGjSG+kNUio5kAa3uekCYu/47cnGn5EQ==} + /@typescript-eslint/typescript-estree/5.49.0_typescript@4.9.4: + resolution: {integrity: sha512-PBdx+V7deZT/3GjNYPVQv1Nc0U46dAHbIuOG8AZ3on3vuEKiPDwFE/lG1snN2eUB9IhF7EyF7K1hmTcLztNIsA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: typescript: '*' @@ -298,29 +302,32 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/types': 5.45.0 - '@typescript-eslint/visitor-keys': 5.45.0 + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/visitor-keys': 5.49.0 debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.3.7 - tsutils: 3.21.0_typescript@4.9.3 - typescript: 4.9.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.4 + typescript: 4.9.4 transitivePeerDependencies: - supports-color dev: true - /@typescript-eslint/utils/5.45.0_hsf322ms6xhhd4b5ne6lb74y4a: - resolution: {integrity: sha512-OUg2JvsVI1oIee/SwiejTot2OxwU8a7UfTFMOdlhD2y+Hl6memUSL4s98bpUTo8EpVEr0lmwlU7JSu/p2QpSvA==} + /@typescript-eslint/utils/5.49.0_wy4udjehnvkneqnogzx5kughki: + resolution: {integrity: sha512-cPJue/4Si25FViIb74sHCLtM4nTSBXtLx1d3/QT6mirQ/c65bV8arBEebBJJizfq8W2YyMoPI/WWPFWitmNqnQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 + peerDependenciesMeta: + eslint: + optional: true dependencies: '@types/json-schema': 7.0.11 '@types/semver': 7.3.13 - '@typescript-eslint/scope-manager': 5.45.0 - '@typescript-eslint/types': 5.45.0 - '@typescript-eslint/typescript-estree': 5.45.0_typescript@4.9.3 + '@typescript-eslint/scope-manager': 5.49.0 + '@typescript-eslint/types': 5.49.0 + '@typescript-eslint/typescript-estree': 5.49.0_typescript@4.9.4 eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.28.0 @@ -330,11 +337,11 @@ packages: - typescript dev: true - /@typescript-eslint/visitor-keys/5.45.0: - resolution: {integrity: sha512-jc6Eccbn2RtQPr1s7th6jJWQHBHI6GBVQkCHoJFQ5UreaKm59Vxw+ynQUPPY2u2Amquc+7tmEoC2G52ApsGNNg==} + /@typescript-eslint/visitor-keys/5.49.0: + resolution: {integrity: sha512-v9jBMjpNWyn8B6k/Mjt6VbUS4J1GvUlR4x3Y+ibnP1z7y7V4n0WRz+50DY6+Myj0UaXVSuUlHohO+eZ8IJEnkg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - '@typescript-eslint/types': 5.45.0 + '@typescript-eslint/types': 5.49.0 eslint-visitor-keys: 3.3.0 dev: true @@ -342,7 +349,7 @@ packages: resolution: {integrity: sha512-3csHpkE1zUSRTZwl7xIf2uXg1cD4IhhtUm0F6K/dWydc95R5Nj+krB4OTNATuqkewIv/ViCbwjPfkafAgvZQSg==} dependencies: eventemitter3: 4.0.7 - dev: true + dev: false /@vap/shiki/0.10.3: resolution: {integrity: sha512-tZPHZxDKEBlorQ2BaprytGfkbo5yKBvdxdAF144p94HCTpjO3ScJk/f319wi7GtV1NE4DV8HBQo/0XpldixWQA==} @@ -350,7 +357,7 @@ packages: jsonc-parser: 3.2.0 vscode-oniguruma: 1.7.0 vscode-textmate: 5.2.0 - dev: true + dev: false /acorn-jsx/5.3.2_acorn@8.8.0: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} @@ -554,12 +561,6 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /console-menu/0.1.0: - resolution: {integrity: sha512-gOGvuhugXvHggnodbEop0Wzh05eondeCdpPZqcwlzJc7KoPrdsHUM8TZug1lN2jN7Qm3XrZcoP5dZDaO2CaYSw==} - dependencies: - keypress: 0.2.1 - dev: true - /copy-descriptor/0.1.1: resolution: {integrity: sha512-XgZ0pFcakEUlbwQEVNg3+QAis1FyTL3Qel9FYy8pSkQqoG3PNoT0bOCQtOXcOkur21r2Eq2kI+IE+gsmAEVlYw==} engines: {node: '>=0.10.0'} @@ -640,8 +641,8 @@ packages: isobject: 3.0.1 dev: true - /devtools-protocol/0.0.1056733: - resolution: {integrity: sha512-CmTu6SQx2g3TbZzDCAV58+LTxVdKplS7xip0g5oDXpZ+isr0rv5dDP8ToyVRywzPHkCCPKgKgScEcwz4uPWDIA==} + /devtools-protocol/0.0.1082910: + resolution: {integrity: sha512-RqoZ2GmqaNxyx+99L/RemY5CkwG9D0WEfOKxekwCRXOGrDCep62ngezEJUVMq6rISYQ+085fJnWDQqGHlxVNww==} dev: true /diff/5.1.0: @@ -676,8 +677,8 @@ packages: once: 1.4.0 dev: true - /esbuild-android-64/0.15.16: - resolution: {integrity: sha512-Vwkv/sT0zMSgPSVO3Jlt1pUbnZuOgtOQJkJkyyJFAlLe7BiT8e9ESzo0zQSx4c3wW4T6kGChmKDPMbWTgtliQA==} + /esbuild-android-64/0.15.18: + resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} engines: {node: '>=12'} cpu: [x64] os: [android] @@ -685,8 +686,8 @@ packages: dev: true optional: true - /esbuild-android-arm64/0.15.16: - resolution: {integrity: sha512-lqfKuofMExL5niNV3gnhMUYacSXfsvzTa/58sDlBET/hCOG99Zmeh+lz6kvdgvGOsImeo6J9SW21rFCogNPLxg==} + /esbuild-android-arm64/0.15.18: + resolution: {integrity: sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==} engines: {node: '>=12'} cpu: [arm64] os: [android] @@ -694,8 +695,8 @@ packages: dev: true optional: true - /esbuild-darwin-64/0.15.16: - resolution: {integrity: sha512-wo2VWk/n/9V2TmqUZ/KpzRjCEcr00n7yahEdmtzlrfQ3lfMCf3Wa+0sqHAbjk3C6CKkR3WKK/whkMq5Gj4Da9g==} + /esbuild-darwin-64/0.15.18: + resolution: {integrity: sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==} engines: {node: '>=12'} cpu: [x64] os: [darwin] @@ -703,8 +704,8 @@ packages: dev: true optional: true - /esbuild-darwin-arm64/0.15.16: - resolution: {integrity: sha512-fMXaUr5ou0M4WnewBKsspMtX++C1yIa3nJ5R2LSbLCfJT3uFdcRoU/NZjoM4kOMKyOD9Sa/2vlgN8G07K3SJnw==} + /esbuild-darwin-arm64/0.15.18: + resolution: {integrity: sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] @@ -712,8 +713,8 @@ packages: dev: true optional: true - /esbuild-freebsd-64/0.15.16: - resolution: {integrity: sha512-UzIc0xlRx5x9kRuMr+E3+hlSOxa/aRqfuMfiYBXu2jJ8Mzej4lGL7+o6F5hzhLqWfWm1GWHNakIdlqg1ayaTNQ==} + /esbuild-freebsd-64/0.15.18: + resolution: {integrity: sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] @@ -721,8 +722,8 @@ packages: dev: true optional: true - /esbuild-freebsd-arm64/0.15.16: - resolution: {integrity: sha512-8xyiYuGc0DLZphFQIiYaLHlfoP+hAN9RHbE+Ibh8EUcDNHAqbQgUrQg7pE7Bo00rXmQ5Ap6KFgcR0b4ALZls1g==} + /esbuild-freebsd-arm64/0.15.18: + resolution: {integrity: sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] @@ -730,8 +731,8 @@ packages: dev: true optional: true - /esbuild-linux-32/0.15.16: - resolution: {integrity: sha512-iGijUTV+0kIMyUVoynK0v+32Oi8yyp0xwMzX69GX+5+AniNy/C/AL1MjFTsozRp/3xQPl7jVux/PLe2ds10/2w==} + /esbuild-linux-32/0.15.18: + resolution: {integrity: sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] @@ -739,8 +740,8 @@ packages: dev: true optional: true - /esbuild-linux-64/0.15.16: - resolution: {integrity: sha512-tuSOjXdLw7VzaUj89fIdAaQT7zFGbKBcz4YxbWrOiXkwscYgE7HtTxUavreBbnRkGxKwr9iT/gmeJWNm4djy/g==} + /esbuild-linux-64/0.15.18: + resolution: {integrity: sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==} engines: {node: '>=12'} cpu: [x64] os: [linux] @@ -748,8 +749,8 @@ packages: dev: true optional: true - /esbuild-linux-arm/0.15.16: - resolution: {integrity: sha512-XKcrxCEXDTOuoRj5l12tJnkvuxXBMKwEC5j0JISw3ziLf0j4zIwXbKbTmUrKFWbo6ZgvNpa7Y5dnbsjVvH39bQ==} + /esbuild-linux-arm/0.15.18: + resolution: {integrity: sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==} engines: {node: '>=12'} cpu: [arm] os: [linux] @@ -757,8 +758,8 @@ packages: dev: true optional: true - /esbuild-linux-arm64/0.15.16: - resolution: {integrity: sha512-mPYksnfHnemNrvjrDhZyixL/AfbJN0Xn9S34ZOHYdh6/jJcNd8iTsv3JwJoEvTJqjMggjMhGUPJAdjnFBHoH8A==} + /esbuild-linux-arm64/0.15.18: + resolution: {integrity: sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==} engines: {node: '>=12'} cpu: [arm64] os: [linux] @@ -766,8 +767,8 @@ packages: dev: true optional: true - /esbuild-linux-mips64le/0.15.16: - resolution: {integrity: sha512-kSJO2PXaxfm0pWY39+YX+QtpFqyyrcp0ZeI8QPTrcFVQoWEPiPVtOfTZeS3ZKedfH+Ga38c4DSzmKMQJocQv6A==} + /esbuild-linux-mips64le/0.15.18: + resolution: {integrity: sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] @@ -775,8 +776,8 @@ packages: dev: true optional: true - /esbuild-linux-ppc64le/0.15.16: - resolution: {integrity: sha512-NimPikwkBY0yGABw6SlhKrtT35sU4O23xkhlrTT/O6lSxv3Pm5iSc6OYaqVAHWkLdVf31bF4UDVFO+D990WpAA==} + /esbuild-linux-ppc64le/0.15.18: + resolution: {integrity: sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] @@ -784,8 +785,8 @@ packages: dev: true optional: true - /esbuild-linux-riscv64/0.15.16: - resolution: {integrity: sha512-ty2YUHZlwFOwp7pR+J87M4CVrXJIf5ZZtU/umpxgVJBXvWjhziSLEQxvl30SYfUPq0nzeWKBGw5i/DieiHeKfw==} + /esbuild-linux-riscv64/0.15.18: + resolution: {integrity: sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] @@ -793,8 +794,8 @@ packages: dev: true optional: true - /esbuild-linux-s390x/0.15.16: - resolution: {integrity: sha512-VkZaGssvPDQtx4fvVdZ9czezmyWyzpQhEbSNsHZZN0BHvxRLOYAQ7sjay8nMQwYswP6O2KlZluRMNPYefFRs+w==} + /esbuild-linux-s390x/0.15.18: + resolution: {integrity: sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==} engines: {node: '>=12'} cpu: [s390x] os: [linux] @@ -802,8 +803,8 @@ packages: dev: true optional: true - /esbuild-netbsd-64/0.15.16: - resolution: {integrity: sha512-ElQ9rhdY51et6MJTWrCPbqOd/YuPowD7Cxx3ee8wlmXQQVW7UvQI6nSprJ9uVFQISqSF5e5EWpwWqXZsECLvXg==} + /esbuild-netbsd-64/0.15.18: + resolution: {integrity: sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] @@ -811,8 +812,8 @@ packages: dev: true optional: true - /esbuild-openbsd-64/0.15.16: - resolution: {integrity: sha512-KgxMHyxMCT+NdLQE1zVJEsLSt2QQBAvJfmUGDmgEq8Fvjrf6vSKB00dVHUEDKcJwMID6CdgCpvYNt999tIYhqA==} + /esbuild-openbsd-64/0.15.18: + resolution: {integrity: sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] @@ -820,8 +821,8 @@ packages: dev: true optional: true - /esbuild-sunos-64/0.15.16: - resolution: {integrity: sha512-exSAx8Phj7QylXHlMfIyEfNrmqnLxFqLxdQF6MBHPdHAjT7fsKaX6XIJn+aQEFiOcE4X8e7VvdMCJ+WDZxjSRQ==} + /esbuild-sunos-64/0.15.18: + resolution: {integrity: sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==} engines: {node: '>=12'} cpu: [x64] os: [sunos] @@ -829,8 +830,8 @@ packages: dev: true optional: true - /esbuild-windows-32/0.15.16: - resolution: {integrity: sha512-zQgWpY5pUCSTOwqKQ6/vOCJfRssTvxFuEkpB4f2VUGPBpdddZfdj8hbZuFRdZRPIVHvN7juGcpgCA/XCF37mAQ==} + /esbuild-windows-32/0.15.18: + resolution: {integrity: sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==} engines: {node: '>=12'} cpu: [ia32] os: [win32] @@ -838,8 +839,8 @@ packages: dev: true optional: true - /esbuild-windows-64/0.15.16: - resolution: {integrity: sha512-HjW1hHRLSncnM3MBCP7iquatHVJq9l0S2xxsHHj4yzf4nm9TU4Z7k4NkeMlD/dHQ4jPlQQhwcMvwbJiOefSuZw==} + /esbuild-windows-64/0.15.18: + resolution: {integrity: sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==} engines: {node: '>=12'} cpu: [x64] os: [win32] @@ -847,8 +848,8 @@ packages: dev: true optional: true - /esbuild-windows-arm64/0.15.16: - resolution: {integrity: sha512-oCcUKrJaMn04Vxy9Ekd8x23O8LoU01+4NOkQ2iBToKgnGj5eo1vU9i27NQZ9qC8NFZgnQQZg5oZWAejmbsppNA==} + /esbuild-windows-arm64/0.15.18: + resolution: {integrity: sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==} engines: {node: '>=12'} cpu: [arm64] os: [win32] @@ -856,34 +857,34 @@ packages: dev: true optional: true - /esbuild/0.15.16: - resolution: {integrity: sha512-o6iS9zxdHrrojjlj6pNGC2NAg86ECZqIETswTM5KmJitq+R1YmahhWtMumeQp9lHqJaROGnsBi2RLawGnfo5ZQ==} + /esbuild/0.15.18: + resolution: {integrity: sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.15.16 - '@esbuild/linux-loong64': 0.15.16 - esbuild-android-64: 0.15.16 - esbuild-android-arm64: 0.15.16 - esbuild-darwin-64: 0.15.16 - esbuild-darwin-arm64: 0.15.16 - esbuild-freebsd-64: 0.15.16 - esbuild-freebsd-arm64: 0.15.16 - esbuild-linux-32: 0.15.16 - esbuild-linux-64: 0.15.16 - esbuild-linux-arm: 0.15.16 - esbuild-linux-arm64: 0.15.16 - esbuild-linux-mips64le: 0.15.16 - esbuild-linux-ppc64le: 0.15.16 - esbuild-linux-riscv64: 0.15.16 - esbuild-linux-s390x: 0.15.16 - esbuild-netbsd-64: 0.15.16 - esbuild-openbsd-64: 0.15.16 - esbuild-sunos-64: 0.15.16 - esbuild-windows-32: 0.15.16 - esbuild-windows-64: 0.15.16 - esbuild-windows-arm64: 0.15.16 + '@esbuild/android-arm': 0.15.18 + '@esbuild/linux-loong64': 0.15.18 + esbuild-android-64: 0.15.18 + esbuild-android-arm64: 0.15.18 + esbuild-darwin-64: 0.15.18 + esbuild-darwin-arm64: 0.15.18 + esbuild-freebsd-64: 0.15.18 + esbuild-freebsd-arm64: 0.15.18 + esbuild-linux-32: 0.15.18 + esbuild-linux-64: 0.15.18 + esbuild-linux-arm: 0.15.18 + esbuild-linux-arm64: 0.15.18 + esbuild-linux-mips64le: 0.15.18 + esbuild-linux-ppc64le: 0.15.18 + esbuild-linux-riscv64: 0.15.18 + esbuild-linux-s390x: 0.15.18 + esbuild-netbsd-64: 0.15.18 + esbuild-openbsd-64: 0.15.18 + esbuild-sunos-64: 0.15.18 + esbuild-windows-32: 0.15.18 + esbuild-windows-64: 0.15.18 + esbuild-windows-arm64: 0.15.18 dev: true /escape-string-regexp/4.0.0: @@ -896,12 +897,18 @@ packages: engines: {node: '>= 4'} peerDependencies: eslint-plugin-import: '>=1.4.0' + peerDependenciesMeta: + eslint-plugin-import: + optional: true dev: true /eslint-plugin-header/3.1.1_eslint@8.28.0: resolution: {integrity: sha512-9vlKxuJ4qf793CmeeSrZUvVClw6amtpghq3CuWcB5cUNnWHQhgcqy5eF8oVKFk1G3Y/CbchGfEaw3wiIJaNmVg==} peerDependencies: eslint: '>=7.7.0' + peerDependenciesMeta: + eslint: + optional: true dependencies: eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe dev: true @@ -910,6 +917,9 @@ packages: resolution: {integrity: sha512-FXus57yC+Zd3sMv46pbloXYwFeNVNHJqlACr9V68FG/IzGFBBokGJpmjDbEjpt8ZCeVSndUubeDWWl2A8sCNVQ==} peerDependencies: eslint: ^7 + peerDependenciesMeta: + eslint: + optional: true dependencies: eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe nanomatch: 1.2.13 @@ -922,11 +932,14 @@ packages: resolution: {integrity: sha512-bXgJQ+lqhtQBCuWY/FUWdB27j4+lqcvXv5rUARkzbeWLwea+S5eBZEQrhnO+WgX3ZoJHVj0cn943iyXwByHHQw==} peerDependencies: eslint: '>=5.0.0' + peerDependenciesMeta: + eslint: + optional: true dependencies: eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe dev: true - /eslint-plugin-unused-imports/2.0.0_5am2datodjm2qi4eijrjrnoz54: + /eslint-plugin-unused-imports/2.0.0_nzrwdcb3mq4ezaurfymehngbla: resolution: {integrity: sha512-3APeS/tQlTrFa167ThtP0Zm0vctjr4M44HMpeg1P4bK6wItarumq0Ma82xorMKdFsWpphQBlRPzw/pxiVELX1A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -935,8 +948,10 @@ packages: peerDependenciesMeta: '@typescript-eslint/eslint-plugin': optional: true + eslint: + optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.45.0_czs5uoqkd3podpy6vgtsxfc7au + '@typescript-eslint/eslint-plugin': 5.49.0_ffoscbl6fkz64kp3vlggrfqozm eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-rule-composer: 0.3.0 dev: true @@ -967,6 +982,9 @@ packages: engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' + peerDependenciesMeta: + eslint: + optional: true dependencies: eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-visitor-keys: 2.1.0 @@ -1071,7 +1089,7 @@ packages: /eventemitter3/4.0.7: resolution: {integrity: sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==} - dev: true + dev: false /extend-shallow/2.0.1: resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} @@ -1246,7 +1264,7 @@ packages: array-union: 2.1.0 dir-glob: 3.0.1 fast-glob: 3.2.12 - ignore: 5.2.0 + ignore: 5.2.4 merge2: 1.4.1 slash: 3.0.0 dev: true @@ -1314,6 +1332,11 @@ packages: engines: {node: '>= 4'} dev: true + /ignore/5.2.4: + resolution: {integrity: sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==} + engines: {node: '>= 4'} + dev: true + /import-fresh/3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -1482,11 +1505,7 @@ packages: /jsonc-parser/3.2.0: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} - dev: true - - /keypress/0.2.1: - resolution: {integrity: sha512-HjorDJFNhnM4SicvaUXac0X77NiskggxJdesG72+O5zBKpSqKFCrqmndKVqpu3pFqkla0St6uGk8Ju0sCurrmg==} - dev: true + dev: false /kind-of/3.2.2: resolution: {integrity: sha512-NOW9QQXMoZGg/oqnVNoNTTIFEIid1627WCffUBJEdMxYApq7mNE7CpzucIPc+ZQg25Phej7IJSmX3hO+oblOtQ==} @@ -1749,20 +1768,20 @@ packages: engines: {node: '>=6'} dev: true - /puppeteer-core/19.3.0: - resolution: {integrity: sha512-P8VAAOBnBJo/7DKJnj1b0K9kZBF2D8lkdL94CjJ+DZKCp182LQqYemPI9omUSZkh4bgykzXjZhaVR1qtddTTQg==} + /puppeteer-core/19.6.0: + resolution: {integrity: sha512-GvqWdHr9eY/MFR5pXf9o0apnrTmG0hhS7/TtCPfeAvCbaUS1bsLMZWxNGvI/QbviRu4xxi6HrR7VW4x/4esq1Q==} engines: {node: '>=14.1.0'} dependencies: cross-fetch: 3.1.5 debug: 4.3.4 - devtools-protocol: 0.0.1056733 + devtools-protocol: 0.0.1082910 extract-zip: 2.0.1 https-proxy-agent: 5.0.1 proxy-from-env: 1.1.0 rimraf: 3.0.2 tar-fs: 2.1.1 unbzip2-stream: 1.4.3 - ws: 8.10.0 + ws: 8.11.0 transitivePeerDependencies: - bufferutil - encoding @@ -1847,6 +1866,14 @@ packages: lru-cache: 6.0.0 dev: true + /semver/7.3.8: + resolution: {integrity: sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==} + engines: {node: '>=10'} + hasBin: true + dependencies: + lru-cache: 6.0.0 + dev: true + /set-value/2.0.1: resolution: {integrity: sha512-JxHc1weCN68wRY0fhCoXpyK55m/XPHafOmK4UWD7m2CI14GMcFypt4w/0+NV5f/ZMby2F6S2wwA7fgynh9gWSw==} engines: {node: '>=0.10.0'} @@ -1921,7 +1948,7 @@ packages: /standalone-electron-types/1.0.0: resolution: {integrity: sha512-0HOi/tlTz3mjWhsAz4uRbpQcHMZ+ifj1JzWW9nugykOHClBBG77ps8QinrzX1eow4Iw2pnC+RFaSYRgufF4BOg==} dependencies: - '@types/node': 18.11.9 + '@types/node': 18.11.18 dev: true /static-extend/0.1.2: @@ -2017,14 +2044,14 @@ packages: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true - /tsutils/3.21.0_typescript@4.9.3: + /tsutils/3.21.0_typescript@4.9.4: resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} engines: {node: '>= 6'} peerDependencies: typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' dependencies: tslib: 1.14.1 - typescript: 4.9.3 + typescript: 4.9.4 dev: true /type-check/0.4.0: @@ -2039,13 +2066,13 @@ packages: engines: {node: '>=10'} dev: true - /type-fest/3.3.0: - resolution: {integrity: sha512-gezeeOIZyQLGW5uuCeEnXF1aXmtt2afKspXz3YqoOcZ3l/YMJq1pujvgT+cz/Nw1O/7q/kSav5fihJHsC/AOUg==} + /type-fest/3.5.3: + resolution: {integrity: sha512-V2+og4j/rWReWvaFrse3s9g2xvUv/K9Azm/xo6CjIuq7oeGqsoimC7+9/A3tfvNcbQf8RPSVj/HV81fB4DJrjA==} engines: {node: '>=14.16'} dev: true - /typescript/4.9.3: - resolution: {integrity: sha512-CIfGzTelbKNEnLpLdGFgdyKhG23CKdKgQPOBc+OUNrkJ2vr+KSzsSV5kq5iWhEQbok+quxgGzrAtGWCyU7tHnA==} + /typescript/4.9.4: + resolution: {integrity: sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==} engines: {node: '>=4.2.0'} hasBin: true dev: true @@ -2097,11 +2124,11 @@ packages: /vscode-oniguruma/1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} - dev: true + dev: false /vscode-textmate/5.2.0: resolution: {integrity: sha512-Uw5ooOQxRASHgu6C7GVvUxisKXfSgW4oFlO+aa+PAkgmH89O3CXxEEzNRNtHSqtXFTl0nAC1uYj0GMSH27uwtQ==} - dev: true + dev: false /webidl-conversions/3.0.1: resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} @@ -2131,8 +2158,8 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true - /ws/8.10.0: - resolution: {integrity: sha512-+s49uSmZpvtAsd2h37vIPy1RBusaLawVe8of+GyEPsaJTCMpj/2v8NpeK1SHXjBlQ95lQTmQofOJnFiLoaN3yw==} + /ws/8.11.0: + resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} engines: {node: '>=10.0.0'} peerDependencies: bufferutil: ^4.0.1 From 49aacccc1955f5e15aadec2242000384b3fe499d Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 25 Jan 2023 21:06:22 +0100 Subject: [PATCH 070/114] shc: Make topic not inline --- src/plugins/showHiddenChannels.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index c969b305c..e1eff86ad 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -210,7 +210,7 @@ export default definePlugin({ {channel.type === ChannelTypes.GUILD_FORUM ? "Guidelines:" : "Topic:"} </Text> <div style={{ color: "var(--text-normal)", marginTop: 10 }}> - {Parser.parseTopic(channel.topic, true, { channelId: channel.id })} + {Parser.parseTopic(channel.topic, false, { channelId: channel.id })} </div> </> )} From 0c030a3a27e4c34409143261975fb3bd0e481fcb Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 25 Jan 2023 21:08:45 +0100 Subject: [PATCH 071/114] VcNarrator: Show all voices, better defaults --- src/plugins/vcNarrator.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/plugins/vcNarrator.tsx b/src/plugins/vcNarrator.tsx index a8558727d..b2904ac83 100644 --- a/src/plugins/vcNarrator.tsx +++ b/src/plugins/vcNarrator.tsx @@ -41,12 +41,6 @@ const VoiceStateStore = findByPropsLazy("getVoiceStatesForChannel", "getCurrentC // Filtering out events is not as simple as just dropping duplicates, as otherwise mute, unmute, mute would // not say the second mute, which would lead you to believe they're unmuted -function getEnglishVoices() { - const voices = speechSynthesis.getVoices(); - const englishVoices = voices.filter(v => v.lang.startsWith("en")); - return !englishVoices.length ? voices : englishVoices; -} - function speak(text: string, settings: any = Settings.plugins.VcNarrator) { if (!text) return; @@ -219,7 +213,7 @@ export default definePlugin({ voice: { type: OptionType.SELECT, description: "Narrator Voice", - options: getEnglishVoices().map(v => ({ + options: speechSynthesis.getVoices().map(v => ({ label: v.name, value: v.voiceURI, default: v.default @@ -242,12 +236,12 @@ export default definePlugin({ joinMessage: { type: OptionType.STRING, description: "Join Message", - default: "{{USER}} joined {{CHANNEL}}" + default: "{{USER}} joined" }, leaveMessage: { type: OptionType.STRING, description: "Leave Message", - default: "{{USER}} left {{CHANNEL}}" + default: "{{USER}} left" }, moveMessage: { type: OptionType.STRING, From 399305fd8a51de8f0e14d1898d9dd904c540f062 Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Thu, 26 Jan 2023 22:38:02 +0100 Subject: [PATCH 072/114] Automatic extension publishing (#453) --- .github/workflows/build.yml | 20 +++---- .github/workflows/publish.yml | 60 +++++++++++++++++++++ README.md | 6 +-- browser/background.js | 48 ----------------- browser/icon.png | Bin 0 -> 21831 bytes browser/{manifestv3.json => manifest.json} | 15 +++++- browser/manifestv2.json | 25 --------- scripts/build/buildWeb.mjs | 33 ++++++++---- 8 files changed, 103 insertions(+), 104 deletions(-) create mode 100644 .github/workflows/publish.yml delete mode 100644 browser/background.js create mode 100644 browser/icon.png rename browser/{manifestv3.json => manifest.json} (74%) delete mode 100644 browser/manifestv2.json diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c9eafee4..1590c795c 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -34,28 +34,19 @@ jobs: - name: Build web run: pnpm buildWeb --standalone - - name: Sign firefox extension - run: | - pnpx web-ext sign --api-key $WEBEXT_USER --api-secret $WEBEXT_SECRET --channel=unlisted - env: - WEBEXT_USER: ${{ secrets.WEBEXT_USER }} - WEBEXT_SECRET: ${{ secrets.WEBEXT_SECRET }} - - name: Build run: pnpm build --standalone - - name: Rename extensions for more user friendliness + - name: Clean up obsolete files run: | - mv dist/*.xpi dist/Vencord-for-Firefox.xpi - mv dist/extension-v3.zip dist/Vencord-for-Chrome-and-Edge.zip - rm -rf dist/extension-v2-unpacked dist/extension-v2.zip + rm -rf dist/extension* Vencord.user.css - name: Get some values needed for the release id: release_values run: | echo "release_tag=$(git rev-parse --short HEAD)" >> $GITHUB_ENV - - name: Upload Devbuild as release + - name: Upload DevBuild as release run: | gh release upload devbuild --clobber dist/* gh release edit devbuild --title "DevBuild $RELEASE_TAG" @@ -63,13 +54,15 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} RELEASE_TAG: ${{ env.release_tag }} - - name: Upload Devbuild to builds repo + - name: Upload DevBuild to builds repo run: | git config --global user.name "$USERNAME" git config --global user.email actions@github.com git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload cd upload + + GLOBIGNORE: .git:.gitignore:README.md:LICENSE rm -rf * cp -r ../dist/* . @@ -78,6 +71,5 @@ jobs: git push --force https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git env: API_TOKEN: ${{ secrets.BUILDS_TOKEN }} - GLOBIGNORE: .git:.gitignore:README.md:LICENSE GH_REPO: Vencord/builds USERNAME: GitHub-Actions diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 000000000..34738b950 --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,60 @@ +name: Release Browser Extension +on: + push: + tags: + - v* + +jobs: + Publish: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v3 + + - name: check that tag matches package.json version + run: | + pkg_version="$(jq -r .version < package.json)" + if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then + echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 + exit 1 + fi + + - uses: pnpm/action-setup@v2 # Install pnpm using packageManager key in package.json + + - name: Use Node.js 19 + uses: actions/setup-node@v3 + with: + node-version: 19 + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Build web + run: pnpm buildWeb --standalone + + - name: Publish extension + run: | + cd dist/extension-unpacked + + # Do not fail so that even if chrome fails, firefox gets a shot. But also store exit code to fail workflow later + EXIT_CODE=0 + + # Chrome + pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$? + + # Firefox + pnpx web-ext-submit@7.4.0 + + exit $EXIT_CODE + env: + # Chrome + EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} + + # Firefox + WEB_EXT_API_KEY: ${{ secrets.WEBEXT_USER }} + WEB_EXT_API_SECRET: ${{ secrets.WEBEXT_SECRET }} + diff --git a/README.md b/README.md index 82fc5616a..048709fd7 100644 --- a/README.md +++ b/README.md @@ -18,11 +18,9 @@ The cutest Discord client mod ## Installing on Browser -[![Get the Firefox extension](https://blog.mozilla.org/addons/files/2015/11/get-the-addon-small.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) +[![Get it on the Firefox Webstore](https://blog.mozilla.org/addons/files/2015/11/get-the-addon.png)](https://addons.mozilla.org/en-GB/firefox/addon/vencord-web/) [![Get it on the Chrome Webstore](https://storage.googleapis.com/web-dev-uploads/image/WlD8wC6g8khYWPJUsQceQkhXSlv1/UV4C4ybeBTsZt43U4xis.png)](https://chrome.google.com/webstore/detail/vencord-web/cbghhgpcnddeihccjmnadmkaejncjndb) -Or install the browser extension for -- [![Chrome](https://img.shields.io/badge/chrome-ext-brightgreen)](https://github.com/Vendicated/Vencord/releases/latest/download/Vencord-for-Chrome-and-Edge.zip) -- [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that QuickCSS, shiki and other plugins making use of external resources will not work with the UserScript. +Or use the [UserScript](https://raw.githubusercontent.com/Vencord/builds/main/Vencord.user.js) - Please note that QuickCSS and plugins making use of external resources will not work with the UserScript. ## Building from Source diff --git a/browser/background.js b/browser/background.js deleted file mode 100644 index 5c99dd8f6..000000000 --- a/browser/background.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Vencord, a modification for Discord's desktop app - * Copyright (c) 2022 Linnea Gräf - * - * 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 <https://www.gnu.org/licenses/>. -*/ - -function setContentTypeOnStylesheets(details) { - if (details.type === "stylesheet") { - details.responseHeaders = details.responseHeaders.filter(it => it.name.toLowerCase() !== 'content-type'); - details.responseHeaders.push({ name: "Content-Type", value: "text/css" }); - } - return { responseHeaders: details.responseHeaders }; -} - -var cspHeaders = [ - "content-security-policy", - "content-security-policy-report-only", -]; - -function removeCSPHeaders(details) { - return { - responseHeaders: details.responseHeaders.filter(header => - !cspHeaders.includes(header.name.toLowerCase())) - }; -} - - - - -browser.webRequest.onHeadersReceived.addListener( - setContentTypeOnStylesheets, { urls: ["https://raw.githubusercontent.com/*"] }, ["blocking", "responseHeaders"] -); - -browser.webRequest.onHeadersReceived.addListener( - removeCSPHeaders, { urls: ["https://raw.githubusercontent.com/*", "*://*.discord.com/*"] }, ["blocking", "responseHeaders"] -); diff --git a/browser/icon.png b/browser/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..57349fad211308d620bc7fab2cacfc56c0a091e4 GIT binary patch literal 21831 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 literal 0 HcmV?d00001 diff --git a/browser/manifestv3.json b/browser/manifest.json similarity index 74% rename from browser/manifestv3.json rename to browser/manifest.json index d15b80a1b..c20770ef1 100644 --- a/browser/manifestv3.json +++ b/browser/manifest.json @@ -1,10 +1,14 @@ { "manifest_version": 3, + "minimum_chrome_version": "91", + "name": "Vencord Web", - "description": "Yeee", - "version": "1.0.0", + "description": "The cutest Discord mod now in your browser", "author": "Vendicated", "homepage_url": "https://github.com/Vendicated/Vencord", + "icons": { + "128": "icon.png" + }, "host_permissions": [ "*://*.discord.com/*", @@ -36,5 +40,12 @@ "path": "modifyResponseHeaders.json" } ] + }, + + "applications": { + "gecko": { + "id": "vencord-firefox@vendicated.dev", + "strict_min_version": "109.0" + } } } diff --git a/browser/manifestv2.json b/browser/manifestv2.json deleted file mode 100644 index b28b73f8d..000000000 --- a/browser/manifestv2.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "manifest_version": 2, - "name": "Vencord Web", - "description": "The Vencord Client Mod for Discord Web.", - "version": "1.0.0", - "author": "Vendicated", - "homepage_url": "https://github.com/Vendicated/Vencord", - "permissions": [ - "webRequest", - "webRequestBlocking", - "*://*.discord.com/*", - "https://raw.githubusercontent.com/*" - ], - "content_scripts": [ - { - "run_at": "document_start", - "matches": ["*://*.discord.com/*"], - "js": ["content.js"] - } - ], - "web_accessible_resources": ["dist/Vencord.js", "dist/Vencord.css"], - "background": { - "scripts": ["background.js"] - } -} diff --git a/scripts/build/buildWeb.mjs b/scripts/build/buildWeb.mjs index ceddbcb68..dd7d32ebc 100644 --- a/scripts/build/buildWeb.mjs +++ b/scripts/build/buildWeb.mjs @@ -82,10 +82,19 @@ async function buildPluginZip(target, files, shouldZip) { const entries = { "dist/Vencord.js": await readFile("dist/browser.js"), "dist/Vencord.css": await readFile("dist/browser.css"), - ...Object.fromEntries(await Promise.all(files.map(async f => [ - (f.startsWith("manifest") ? "manifest.json" : f), - await readFile(join("browser", f)) - ]))), + ...Object.fromEntries(await Promise.all(files.map(async f => { + let content = await readFile(join("browser", f)); + if (f.startsWith("manifest")) { + const json = JSON.parse(content.toString("utf-8")); + json.version = PackageJSON.version; + content = new TextEncoder().encode(JSON.stringify(json)); + } + + return [ + f.startsWith("manifest") ? "manifest.json" : f, + content + ]; + }))), }; if (shouldZip) { @@ -115,20 +124,22 @@ async function buildPluginZip(target, files, shouldZip) { } } -const cssText = "`" + readFileSync("dist/Vencord.user.css", "utf-8").replaceAll("`", "\\`") + "`"; -const cssRuntime = ` +const appendCssRuntime = readFile("dist/Vencord.user.css", "utf-8").then(content => { + const cssRuntime = ` ;document.addEventListener("DOMContentLoaded", () => document.documentElement.appendChild( Object.assign(document.createElement("style"), { - textContent: ${cssText}, + textContent: \`${content.replaceAll("`", "\\`")}\`, id: "vencord-css-core" }) ), { once: true }); `; + return appendFile("dist/Vencord.user.js", cssRuntime); +}); + await Promise.all([ - appendFile("dist/Vencord.user.js", cssRuntime), - buildPluginZip("extension-v3.zip", ["modifyResponseHeaders.json", "content.js", "manifestv3.json"], true), - buildPluginZip("extension-v2.zip", ["background.js", "content.js", "manifestv2.json"], true), - buildPluginZip("extension-v2-unpacked", ["background.js", "content.js", "manifestv2.json"], false), + appendCssRuntime, + buildPluginZip("extension.zip", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], true), + buildPluginZip("extension-unpacked", ["modifyResponseHeaders.json", "content.js", "manifest.json", "icon.png"], false), ]); From bc4c7473e806bf59c380e486dd1da9c37a819625 Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Thu, 26 Jan 2023 22:51:30 +0100 Subject: [PATCH 073/114] ci: fix typo --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 1590c795c..c264821e3 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -62,7 +62,7 @@ jobs: git clone https://$USERNAME:$API_TOKEN@github.com/$GH_REPO.git upload cd upload - GLOBIGNORE: .git:.gitignore:README.md:LICENSE + GLOBIGNORE=.git:.gitignore:README.md:LICENSE rm -rf * cp -r ../dist/* . From 6e22a96d9eed97ecc1991962fe965ea352ad5549 Mon Sep 17 00:00:00 2001 From: Sofia <me@dzshn.xyz> Date: Fri, 27 Jan 2023 21:40:10 -0300 Subject: [PATCH 074/114] feat(ShowHiddenChannels): fix channel switch keybinds jumping to hiddens (#459) Co-authored-by: Nuckyz --- src/plugins/showHiddenChannels.tsx | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels.tsx index e1eff86ad..283eb83a7 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels.tsx @@ -64,7 +64,7 @@ const settings = definePluginSettings({ export default definePlugin({ name: "ShowHiddenChannels", description: "Show channels that you do not have access to view.", - authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux], + authors: [Devs.BigDuck, Devs.AverageReactEnjoyer, Devs.D3SOX, Devs.Ven, Devs.Nuckyz, Devs.Nickyux, Devs.dzshn], settings, patches: [ @@ -178,7 +178,22 @@ export default definePlugin({ match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/, replace: "&&!$self.isHiddenChannel($<channel>)" } - } + }, + // Patch keybind handlers so you can't accidentally jump to hidden channels + { + find: '"alt+shift+down"', + replacement: { + match: /(?<=getChannel\(\i\);return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/, + replace: "&&!$self.isHiddenChannel($<channel>)" + } + }, + { + find: '"alt+down"', + replacement: { + match: /(?<=getState\(\)\.channelId.{1,30}\(0,\i\.\i\)\(\i\))(?=\.map\()/, + replace: ".filter(ch=>!$self.isHiddenChannel(ch))" + } + }, ], isHiddenChannel(channel: Channel & { channelId?: string; }) { From 072ad3d7e67300c1de25858b790670a46fb6c10e Mon Sep 17 00:00:00 2001 From: Kaydax <kaydax@kaydax.xyz> Date: Sat, 28 Jan 2023 17:54:38 -0500 Subject: [PATCH 075/114] feat(settings): Add the ability to make the window transparent (#457) --- src/api/settings.ts | 2 ++ src/components/VencordSettings/VencordTab.tsx | 7 +++++++ src/patcher.ts | 4 ++++ 3 files changed, 13 insertions(+) diff --git a/src/api/settings.ts b/src/api/settings.ts index 4cdb24b67..d20e9642f 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -32,6 +32,7 @@ export interface Settings { enableReactDevtools: boolean; themeLinks: string[]; frameless: boolean; + transparent: boolean; winCtrlQ: boolean; plugins: { [plugin: string]: { @@ -48,6 +49,7 @@ const DefaultSettings: Settings = { themeLinks: [], enableReactDevtools: false, frameless: false, + transparent: false, winCtrlQ: false, plugins: {} }; diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index e1632b197..9f55d57f9 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -108,6 +108,13 @@ function VencordSettings() { > Disable the window frame </Switch> + <Switch + value={settings.transparent} + onChange={(v: boolean) => settings.transparent = v} + note="Requires a full restart" + > + Enable window transparency + </Switch> {navigator.platform.toLowerCase().startsWith("win") && ( <Switch value={settings.winCtrlQ} diff --git a/src/patcher.ts b/src/patcher.ts index 64bc50266..d51405d1b 100644 --- a/src/patcher.ts +++ b/src/patcher.ts @@ -80,6 +80,10 @@ if (!process.argv.includes("--vanilla")) { if (settings.frameless) { options.frame = false; } + if (settings.transparent) { + options.transparent = true; + options.backgroundColor = "#00000000"; + } process.env.DISCORD_PRELOAD = original; From 429ab9d3632b76d9122c8b264b47f9a15c971d6d Mon Sep 17 00:00:00 2001 From: Nick <nwowens32@gmail.com> Date: Sat, 28 Jan 2023 18:06:33 -0500 Subject: [PATCH 076/114] feat(plugin): TypingTweaks (#432) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/typingTweaks.tsx | 111 +++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/plugins/typingTweaks.tsx diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx new file mode 100644 index 000000000..bc1883151 --- /dev/null +++ b/src/plugins/typingTweaks.tsx @@ -0,0 +1,111 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { definePluginSettings } from "@api/settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByCodeLazy } from "@webpack"; +import { GuildMemberStore, React } from "@webpack/common"; + +const Avatar = findByCodeLazy(".Positions.TOP,spacing:"); + +const settings = definePluginSettings({ + showAvatars: { + type: OptionType.BOOLEAN, + default: true, + description: "Show avatars in the typing indicator" + }, + showRoleColors: { + type: OptionType.BOOLEAN, + default: true, + description: "Show role colors in the typing indicator" + }, + alternativeFormatting: { + type: OptionType.BOOLEAN, + default: true, + description: "Show a more useful message when several users are typing" + } +}); + +export default definePlugin({ + name: "TypingTweaks", + description: "Show avatars and role colours in the typing indicator", + authors: [Devs.zt], + patches: [ + // Style the indicator and add function call to modify the children before rendering + { + find: "getCooldownTextStyle", + replacement: { + match: /=(\i)\[2];(.+)"aria-atomic":!0,children:(\i)}\)/, + replace: "=$1[2];$2\"aria-atomic\":!0,style:{display:\"grid\",gridAutoFlow:\"column\",gridGap:\"0.25em\"},children:$self.mutateChildren(this.props,$1,$3)})" + } + }, + // Changes the indicator to keep the user object when creating the list of typing users + { + find: "getCooldownTextStyle", + replacement: { + match: /return \i\.Z\.getName\(.,.\.props\.channel\.id,(.)\)/, + replace: "return $1" + } + }, + // Changes indicator to format message with the typing users + { + find: ',"SEVERAL_USERS_TYPING","', + replacement: { + match: /(\i)\((\i),"SEVERAL_USERS_TYPING",".+?"\)/, + replace: "$1($2,\"SEVERAL_USERS_TYPING\",\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")" + }, + predicate: () => settings.store.alternativeFormatting + }, + // Adds the alternative formatting for several users typing + { + find: "getCooldownTextStyle", + replacement: { + match: /(\i)\.length\?.\..\.Messages\.THREE_USERS_TYPING.format\(\{a:(\i),b:(\i),c:.}\).+?SEVERAL_USERS_TYPING/, + replace: "$&.format({a:$2,b:$3,c:$1.length})" + }, + predicate: () => settings.store.alternativeFormatting + } + ], + settings, + + mutateChildren(props, users, children) { + if (!Array.isArray(children)) return children; + + let element = 0; + + return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]}/> : c); + }, + + TypingUser: ErrorBoundary.wrap(({ user, guildId }) => { + return <strong style={{ + display: "grid", + gridAutoFlow: "column", + gap: "4px", + color: settings.store.showRoleColors ? GuildMemberStore.getMember(guildId, user.id)?.colorString : undefined + }}> + {settings.store.showAvatars && <div style={{ marginTop: "4px" }}> + <Avatar + size={Avatar.Sizes.SIZE_16} + src={user.getAvatarURL(guildId, 128)}/> + </div>} + {user.username} + </strong>; + }, { noop: true }) +}); From 3cdffe444eebab83cb6400a8788871c8e523891a Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 29 Jan 2023 00:09:17 +0100 Subject: [PATCH 077/114] chore: Fix inconsistent file name casing --- src/plugins/{BetterNotes.ts => betterNotes.ts} | 0 src/plugins/{TimeBarAllActivities.ts => timeBarAllActivities.ts} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename src/plugins/{BetterNotes.ts => betterNotes.ts} (100%) rename src/plugins/{TimeBarAllActivities.ts => timeBarAllActivities.ts} (100%) diff --git a/src/plugins/BetterNotes.ts b/src/plugins/betterNotes.ts similarity index 100% rename from src/plugins/BetterNotes.ts rename to src/plugins/betterNotes.ts diff --git a/src/plugins/TimeBarAllActivities.ts b/src/plugins/timeBarAllActivities.ts similarity index 100% rename from src/plugins/TimeBarAllActivities.ts rename to src/plugins/timeBarAllActivities.ts From 823fa2d0c3c1b7cfaae61736ecbe721aca7f5171 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 29 Jan 2023 00:10:17 +0100 Subject: [PATCH 078/114] Bump to v1.0.4 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fe37ba341..44bcc6aeb 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.0.3", + "version": "1.0.4", "description": "The cutest Discord client mod", "keywords": [], "homepage": "https://github.com/Vendicated/Vencord#readme", From e32388e3ac8f2eada8ea2620e163d6e53dada7a0 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 29 Jan 2023 00:12:27 +0100 Subject: [PATCH 079/114] ci: fix version check --- .github/workflows/publish.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 34738b950..0b1217608 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -13,7 +13,7 @@ jobs: - name: check that tag matches package.json version run: | - pkg_version="$(jq -r .version < package.json)" + pkg_version="v$(jq -r .version < package.json)" if [[ "${{ github.ref_name }}" != "$pkg_version" ]]; then echo "Tag ${{ github.ref_name }} does not match package.json version $pkg_version" >&2 exit 1 From d9fb7f45b5320fb6aa691679dd24806abd42db01 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 29 Jan 2023 00:19:25 +0100 Subject: [PATCH 080/114] ci: fix extension publishing --- .github/workflows/publish.yml | 3 ++- pnpm-lock.yaml | 13 +++++++++++-- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 0b1217608..89cc2cb8a 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -44,7 +44,8 @@ jobs: pnpx chrome-webstore-upload-cli@2.1.0 upload --auto-publish || EXIT_CODE=$? # Firefox - pnpx web-ext-submit@7.4.0 + npm i -g web-ext@7.4.0 web-ext-submit@7.4.0 + web-ext-submit || EXIT_CODE=$? exit $EXIT_CODE env: diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 60e1df831..312bbbf38 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -92,7 +92,7 @@ packages: dependencies: ajv: 6.12.6 debug: 4.3.4 - espree: 9.4.0 + espree: 9.4.1 globals: 13.17.0 ignore: 5.2.0 import-fresh: 3.3.0 @@ -331,7 +331,7 @@ packages: eslint: 8.28.0_7wc6icvgtg3uswirb5tpsbjnbe eslint-scope: 5.1.1 eslint-utils: 3.0.0_eslint@8.28.0 - semver: 7.3.7 + semver: 7.3.8 transitivePeerDependencies: - supports-color - typescript @@ -1058,6 +1058,15 @@ packages: eslint-visitor-keys: 3.3.0 dev: true + /espree/9.4.1: + resolution: {integrity: sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + dependencies: + acorn: 8.8.0 + acorn-jsx: 5.3.2_acorn@8.8.0 + eslint-visitor-keys: 3.3.0 + dev: true + /esquery/1.4.0: resolution: {integrity: sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==} engines: {node: '>=0.10'} From 69715070b9835eff44f6caa58d2e97bace9233d9 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 29 Jan 2023 00:22:11 +0100 Subject: [PATCH 081/114] browser ext: change applications to browser_specific_settings --- browser/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/browser/manifest.json b/browser/manifest.json index c20770ef1..293888051 100644 --- a/browser/manifest.json +++ b/browser/manifest.json @@ -42,7 +42,7 @@ ] }, - "applications": { + "browser_specific_settings": { "gecko": { "id": "vencord-firefox@vendicated.dev", "strict_min_version": "109.0" From fce7d6b6814c70fe71baee57f272f4f3717c4379 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 30 Jan 2023 04:53:28 +0100 Subject: [PATCH 082/114] Make webpack types importable from @webpack/types --- .vscode/extensions.json | 3 +-- src/webpack/common/types/index.d.ts | 23 +++++++++++++++++++++++ tsconfig.json | 9 +++++---- 3 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 src/webpack/common/types/index.d.ts diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 8922d157e..54ebaeb92 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,11 +1,10 @@ { "recommendations": [ "EditorConfig.EditorConfig", - "pmneo.tsimporter", "dbaeumer.vscode-eslint", "formulahendry.auto-rename-tag", "GregorBiswanger.json2ts", "eamodio.gitlens", - "kamikillerto.vscode-colorize" + "ExodiusStudios.comment-anchors" ] } diff --git a/src/webpack/common/types/index.d.ts b/src/webpack/common/types/index.d.ts new file mode 100644 index 000000000..9d6c29502 --- /dev/null +++ b/src/webpack/common/types/index.d.ts @@ -0,0 +1,23 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +export * from "./components"; +export * from "./fluxEvents"; +export * from "./menu"; +export * from "./utils"; + diff --git a/tsconfig.json b/tsconfig.json index a55c1fe36..f811c00a9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -18,11 +18,12 @@ "baseUrl": "./src/", "paths": { - "@webpack": ["./webpack"], - "@webpack/common": ["./webpack/common"], - "@utils/*": ["./utils/*"], "@api/*": ["./api/*"], - "@components/*": ["./components/*"] + "@components/*": ["./components/*"], + "@utils/*": ["./utils/*"], + "@webpack/types": ["./webpack/common/types"], + "@webpack/common": ["./webpack/common"], + "@webpack": ["./webpack/webpack"] } }, "include": ["src/**/*"] From 62f7e4d45c0aa7bdbedecd639e51e6f2446fcc2b Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Mon, 30 Jan 2023 05:02:17 +0100 Subject: [PATCH 083/114] Add stylelint --- .stylelintrc.json | 6 + .vscode/extensions.json | 7 +- package.json | 5 +- pnpm-lock.yaml | 727 ++++++++++++++++++ src/components/PluginSettings/styles.css | 3 +- .../VencordSettings/settingsStyles.css | 3 +- src/plugins/messageLogger/index.tsx | 12 +- src/plugins/messageLogger/messageLogger.css | 17 +- src/plugins/shikiCodeblocks/devicon.css | 2 +- src/plugins/shikiCodeblocks/shiki.css | 5 +- src/plugins/spotifyControls/spotifyStyles.css | 18 +- 11 files changed, 773 insertions(+), 32 deletions(-) create mode 100644 .stylelintrc.json diff --git a/.stylelintrc.json b/.stylelintrc.json new file mode 100644 index 000000000..6449c3f29 --- /dev/null +++ b/.stylelintrc.json @@ -0,0 +1,6 @@ +{ + "extends": "stylelint-config-standard", + "rules": { + "indentation": 4 + } +} diff --git a/.vscode/extensions.json b/.vscode/extensions.json index 54ebaeb92..f16f1e273 100644 --- a/.vscode/extensions.json +++ b/.vscode/extensions.json @@ -1,10 +1,11 @@ { "recommendations": [ - "EditorConfig.EditorConfig", "dbaeumer.vscode-eslint", + "eamodio.gitlens", + "EditorConfig.EditorConfig", + "ExodiusStudios.comment-anchors", "formulahendry.auto-rename-tag", "GregorBiswanger.json2ts", - "eamodio.gitlens", - "ExodiusStudios.comment-anchors" + "stylelint.vscode-stylelint" ] } diff --git a/package.json b/package.json index 44bcc6aeb..520f5db0e 100644 --- a/package.json +++ b/package.json @@ -22,8 +22,9 @@ "buildWeb": "node --require=./scripts/suppressExperimentalWarnings.js scripts/build/buildWeb.mjs", "inject": "node scripts/runInstaller.mjs", "lint": "eslint . --ext .js,.jsx,.ts,.tsx", + "lint-styles": "stylelint \"src/**/*.css\"", "lint:fix": "pnpm lint --fix", - "test": "pnpm lint && pnpm build && pnpm testTsc", + "test": "pnpm build && pnpm lint && pnpm lint-styles && pnpm testTsc", "testWeb": "pnpm lint && pnpm buildWeb && pnpm testTsc", "testTsc": "tsc --noEmit", "uninject": "node scripts/runInstaller.mjs", @@ -56,6 +57,8 @@ "moment": "^2.29.4", "puppeteer-core": "^19.6.0", "standalone-electron-types": "^1.0.0", + "stylelint": "^14.16.1", + "stylelint-config-standard": "^29.0.0", "type-fest": "^3.5.3", "typescript": "^4.9.4" }, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 312bbbf38..ac7aca771 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,8 @@ specifiers: moment: ^2.29.4 puppeteer-core: ^19.6.0 standalone-electron-types: ^1.0.0 + stylelint: ^14.16.1 + stylelint-config-standard: ^29.0.0 type-fest: ^3.5.3 typescript: ^4.9.4 @@ -63,11 +65,45 @@ devDependencies: moment: 2.29.4 puppeteer-core: 19.6.0 standalone-electron-types: 1.0.0 + stylelint: 14.16.1 + stylelint-config-standard: 29.0.0_stylelint@14.16.1 type-fest: 3.5.3 typescript: 4.9.4 packages: + /@babel/code-frame/7.18.6: + resolution: {integrity: sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/highlight': 7.18.6 + dev: true + + /@babel/helper-validator-identifier/7.19.1: + resolution: {integrity: sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==} + engines: {node: '>=6.9.0'} + dev: true + + /@babel/highlight/7.18.6: + resolution: {integrity: sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==} + engines: {node: '>=6.9.0'} + dependencies: + '@babel/helper-validator-identifier': 7.19.1 + chalk: 2.4.2 + js-tokens: 4.0.0 + dev: true + + /@csstools/selector-specificity/2.1.1_wajs5nedgkikc5pcuwett7legi: + resolution: {integrity: sha512-jwx+WCqszn53YHOfvFMJJRd/B2GqkCBt+1MJSG6o5/s8+ytHMvDZXsJgUEWLk12UnLd7HYKac4BYU5i/Ron1Cw==} + engines: {node: ^14 || ^16 || >=18} + peerDependencies: + postcss: ^8.4 + postcss-selector-parser: ^6.0.10 + dependencies: + postcss: 8.4.21 + postcss-selector-parser: 6.0.11 + dev: true + /@esbuild/android-arm/0.15.18: resolution: {integrity: sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==} engines: {node: '>=12'} @@ -156,10 +192,22 @@ packages: resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} dev: true + /@types/minimist/1.2.2: + resolution: {integrity: sha512-jhuKLIRrhvCPLqwPcx6INqmKeiA5EWrsCOPhrlFSrbrmU4ZMPjj5Ul/oLCMDO98XRUIwVm78xICz4EPCektzeQ==} + dev: true + /@types/node/18.11.18: resolution: {integrity: sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA==} dev: true + /@types/normalize-package-data/2.4.1: + resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} + dev: true + + /@types/parse-json/4.0.0: + resolution: {integrity: sha512-//oorEZjL6sbPcKUaCdIGlIUeH26mgzimjBB77G6XRgnDl/L5wOnpyBGRe/Mmf5CVW3PwEBE1NjiMZ/ssFh4wA==} + dev: true + /@types/prop-types/15.7.5: resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} dev: true @@ -391,11 +439,27 @@ packages: uri-js: 4.4.1 dev: true + /ajv/8.12.0: + resolution: {integrity: sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==} + dependencies: + fast-deep-equal: 3.1.3 + json-schema-traverse: 1.0.0 + require-from-string: 2.0.2 + uri-js: 4.4.1 + dev: true + /ansi-regex/5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} dev: true + /ansi-styles/3.2.1: + resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==} + engines: {node: '>=4'} + dependencies: + color-convert: 1.9.3 + dev: true + /ansi-styles/4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} engines: {node: '>=8'} @@ -427,11 +491,21 @@ packages: engines: {node: '>=0.10.0'} dev: true + /arrify/1.0.1: + resolution: {integrity: sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==} + engines: {node: '>=0.10.0'} + dev: true + /assign-symbols/1.0.0: resolution: {integrity: sha512-Q+JC7Whu8HhmTdBph/Tq59IoRtoy6KAm5zzPv00WdujX82lbAL8K7WVjne7vdCsAmbF4AYaDOPyO3k0kl8qIrw==} engines: {node: '>=0.10.0'} dev: true + /astral-regex/2.0.0: + resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==} + engines: {node: '>=8'} + dev: true + /atob/2.1.2: resolution: {integrity: sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==} engines: {node: '>= 4.5.0'} @@ -442,6 +516,10 @@ packages: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} dev: true + /balanced-match/2.0.0: + resolution: {integrity: sha512-1ugUSr8BHXRnK23KfuYS+gVMC3LB8QGH9W1iGtDPsNWoQbgtXSExkBu2aDR4epiGWZOjZsj6lDl/N/AqqTC3UA==} + dev: true + /base/0.11.2: resolution: {integrity: sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==} engines: {node: '>=0.10.0'} @@ -512,6 +590,29 @@ packages: engines: {node: '>=6'} dev: true + /camelcase-keys/6.2.2: + resolution: {integrity: sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==} + engines: {node: '>=8'} + dependencies: + camelcase: 5.3.1 + map-obj: 4.3.0 + quick-lru: 4.0.1 + dev: true + + /camelcase/5.3.1: + resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} + engines: {node: '>=6'} + dev: true + + /chalk/2.4.2: + resolution: {integrity: sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==} + engines: {node: '>=4'} + dependencies: + ansi-styles: 3.2.1 + escape-string-regexp: 1.0.5 + supports-color: 5.5.0 + dev: true + /chalk/4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} engines: {node: '>=10'} @@ -542,6 +643,12 @@ packages: object-visit: 1.0.1 dev: true + /color-convert/1.9.3: + resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} + dependencies: + color-name: 1.1.3 + dev: true + /color-convert/2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} engines: {node: '>=7.0.0'} @@ -549,10 +656,18 @@ packages: color-name: 1.1.4 dev: true + /color-name/1.1.3: + resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} + dev: true + /color-name/1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} dev: true + /colord/2.9.3: + resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==} + dev: true + /component-emitter/1.3.0: resolution: {integrity: sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg==} dev: true @@ -566,6 +681,17 @@ packages: engines: {node: '>=0.10.0'} dev: true + /cosmiconfig/7.1.0: + resolution: {integrity: sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==} + engines: {node: '>=10'} + dependencies: + '@types/parse-json': 4.0.0 + import-fresh: 3.3.0 + parse-json: 5.2.0 + path-type: 4.0.0 + yaml: 1.10.2 + dev: true + /cross-fetch/3.1.5: resolution: {integrity: sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==} dependencies: @@ -583,6 +709,17 @@ packages: which: 2.0.2 dev: true + /css-functions-list/3.1.0: + resolution: {integrity: sha512-/9lCvYZaUbBGvYUgYGFJ4dcYiyqdhSjG7IPVluoV8A1ILjkF7ilmhp1OGUz8n+nmBcu0RNrQAzgD8B6FJbrt2w==} + engines: {node: '>=12.22'} + dev: true + + /cssesc/3.0.0: + resolution: {integrity: sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==} + engines: {node: '>=4'} + hasBin: true + dev: true + /csstype/3.1.0: resolution: {integrity: sha512-uX1KG+x9h5hIJsaKR9xHUeUraxf8IODOwq9JLNPq6BwB04a/xgpq3rcx47l5BZu5zBPlgD342tdke3Hom/nJRA==} dev: true @@ -610,6 +747,19 @@ packages: ms: 2.1.2 dev: true + /decamelize-keys/1.1.1: + resolution: {integrity: sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==} + engines: {node: '>=0.10.0'} + dependencies: + decamelize: 1.2.0 + map-obj: 1.0.1 + dev: true + + /decamelize/1.2.0: + resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==} + engines: {node: '>=0.10.0'} + dev: true + /decode-uri-component/0.2.0: resolution: {integrity: sha512-hjf+xovcEn31w/EUYdTXQh/8smFL/dzYjohQGEIgjyNavaJfBY2p5F527Bo1VPATxv0VYTUC2bOcXvqFwk78Og==} engines: {node: '>=0.10'} @@ -671,12 +821,22 @@ packages: esutils: 2.0.3 dev: true + /emoji-regex/8.0.0: + resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} + dev: true + /end-of-stream/1.4.4: resolution: {integrity: sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==} dependencies: once: 1.4.0 dev: true + /error-ex/1.3.2: + resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} + dependencies: + is-arrayish: 0.2.1 + dev: true + /esbuild-android-64/0.15.18: resolution: {integrity: sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==} engines: {node: '>=12'} @@ -887,6 +1047,11 @@ packages: esbuild-windows-arm64: 0.15.18 dev: true + /escape-string-regexp/1.0.5: + resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} + engines: {node: '>=0.8.0'} + dev: true + /escape-string-regexp/4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} @@ -1152,6 +1317,11 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fastest-levenshtein/1.0.16: + resolution: {integrity: sha512-eRnCtTTtGZFpQCwhJiUOuxPQWRXVKYDn0b2PeHfXL6/Zi53SLAzAHfVhVWK2AryC/WH05kGfxhFIPvTF0SXQzg==} + engines: {node: '>= 4.9.1'} + dev: true + /fastq/1.13.0: resolution: {integrity: sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==} dependencies: @@ -1182,6 +1352,14 @@ packages: to-regex-range: 5.0.1 dev: true + /find-up/4.1.0: + resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} + engines: {node: '>=8'} + dependencies: + locate-path: 5.0.0 + path-exists: 4.0.0 + dev: true + /find-up/5.0.0: resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} engines: {node: '>=10'} @@ -1222,6 +1400,10 @@ packages: resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==} dev: true + /function-bind/1.1.1: + resolution: {integrity: sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==} + dev: true + /get-stream/5.2.0: resolution: {integrity: sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==} engines: {node: '>=8'} @@ -1259,6 +1441,22 @@ packages: path-is-absolute: 1.0.1 dev: true + /global-modules/2.0.0: + resolution: {integrity: sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==} + engines: {node: '>=6'} + dependencies: + global-prefix: 3.0.0 + dev: true + + /global-prefix/3.0.0: + resolution: {integrity: sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==} + engines: {node: '>=6'} + dependencies: + ini: 1.3.8 + kind-of: 6.0.3 + which: 1.3.1 + dev: true + /globals/13.17.0: resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} engines: {node: '>=8'} @@ -1278,10 +1476,24 @@ packages: slash: 3.0.0 dev: true + /globjoin/0.1.4: + resolution: {integrity: sha512-xYfnw62CKG8nLkZBfWbhWwDw02CHty86jfPcc2cr3ZfeuK9ysoVPPEUxf21bAD/rWAgk52SuBrLJlefNy8mvFg==} + dev: true + /grapheme-splitter/1.0.4: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true + /hard-rejection/2.1.0: + resolution: {integrity: sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==} + engines: {node: '>=6'} + dev: true + + /has-flag/3.0.0: + resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} + engines: {node: '>=4'} + dev: true + /has-flag/4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} engines: {node: '>=8'} @@ -1318,10 +1530,33 @@ packages: kind-of: 4.0.0 dev: true + /has/1.0.3: + resolution: {integrity: sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==} + engines: {node: '>= 0.4.0'} + dependencies: + function-bind: 1.1.1 + dev: true + /highlight.js/10.6.0: resolution: {integrity: sha512-8mlRcn5vk/r4+QcqerapwBYTe+iPL5ih6xrNylxrnBdHQiijDETfXX7VIxC3UiCRiINBJfANBAsPzAvRQj8RpQ==} dev: true + /hosted-git-info/2.8.9: + resolution: {integrity: sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==} + dev: true + + /hosted-git-info/4.1.0: + resolution: {integrity: sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==} + engines: {node: '>=10'} + dependencies: + lru-cache: 6.0.0 + dev: true + + /html-tags/3.2.0: + resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==} + engines: {node: '>=8'} + dev: true + /https-proxy-agent/5.0.1: resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} engines: {node: '>= 6'} @@ -1354,11 +1589,21 @@ packages: resolve-from: 4.0.0 dev: true + /import-lazy/4.0.0: + resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} + engines: {node: '>=8'} + dev: true + /imurmurhash/0.1.4: resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==} engines: {node: '>=0.8.19'} dev: true + /indent-string/4.0.0: + resolution: {integrity: sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==} + engines: {node: '>=8'} + dev: true + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -1370,6 +1615,10 @@ packages: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} dev: true + /ini/1.3.8: + resolution: {integrity: sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==} + dev: true + /is-accessor-descriptor/0.1.6: resolution: {integrity: sha512-e1BM1qnDbMRG3ll2U9dSK0UMHuWOs3pY3AtcFsmvwPtKL3MML/Q86i+GilLfvqEs4GW+ExB91tQ3Ig9noDIZ+A==} engines: {node: '>=0.10.0'} @@ -1384,10 +1633,20 @@ packages: kind-of: 6.0.3 dev: true + /is-arrayish/0.2.1: + resolution: {integrity: sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==} + dev: true + /is-buffer/1.1.6: resolution: {integrity: sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==} dev: true + /is-core-module/2.11.0: + resolution: {integrity: sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==} + dependencies: + has: 1.0.3 + dev: true + /is-data-descriptor/0.1.4: resolution: {integrity: sha512-+w9D5ulSoBNlmw9OHn3U2v51SyoCd0he+bB3xMl62oijhrspxowjU+AIcDY0N3iEJbUEkB15IlMASQsxYigvXg==} engines: {node: '>=0.10.0'} @@ -1437,6 +1696,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /is-fullwidth-code-point/3.0.0: + resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} + engines: {node: '>=8'} + dev: true + /is-glob/4.0.3: resolution: {integrity: sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==} engines: {node: '>=0.10.0'} @@ -1461,6 +1725,11 @@ packages: engines: {node: '>=8'} dev: true + /is-plain-obj/1.1.0: + resolution: {integrity: sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==} + engines: {node: '>=0.10.0'} + dev: true + /is-plain-object/2.0.4: resolution: {integrity: sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==} engines: {node: '>=0.10.0'} @@ -1468,6 +1737,11 @@ packages: isobject: 3.0.1 dev: true + /is-plain-object/5.0.0: + resolution: {integrity: sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==} + engines: {node: '>=0.10.0'} + dev: true + /is-windows/1.0.2: resolution: {integrity: sha512-eXK1UInq2bPmjyX6e3VHIzMLobc4J94i4AWn+Hpq3OU5KkrRC96OAcR3PRJ/pGu6m8TRnBHP9dkXQVsT/COVIA==} engines: {node: '>=0.10.0'} @@ -1497,6 +1771,10 @@ packages: resolution: {integrity: sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==} dev: true + /js-tokens/4.0.0: + resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} + dev: true + /js-yaml/4.1.0: resolution: {integrity: sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==} hasBin: true @@ -1504,10 +1782,18 @@ packages: argparse: 2.0.1 dev: true + /json-parse-even-better-errors/2.3.1: + resolution: {integrity: sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==} + dev: true + /json-schema-traverse/0.4.1: resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==} dev: true + /json-schema-traverse/1.0.0: + resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==} + dev: true + /json-stable-stringify-without-jsonify/1.0.1: resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==} dev: true @@ -1540,6 +1826,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /known-css-properties/0.26.0: + resolution: {integrity: sha512-5FZRzrZzNTBruuurWpvZnvP9pum+fe0HcK8z/ooo+U+Hmp4vtbyp1/QDsqmufirXy4egGzbaH/y2uCZf+6W5Kg==} + dev: true + /levn/0.4.1: resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==} engines: {node: '>= 0.8.0'} @@ -1548,6 +1838,17 @@ packages: type-check: 0.4.0 dev: true + /lines-and-columns/1.2.4: + resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + dev: true + + /locate-path/5.0.0: + resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} + engines: {node: '>=8'} + dependencies: + p-locate: 4.1.0 + dev: true + /locate-path/6.0.0: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} @@ -1559,6 +1860,10 @@ packages: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true + /lodash.truncate/4.4.2: + resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==} + dev: true + /lru-cache/6.0.0: resolution: {integrity: sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==} engines: {node: '>=10'} @@ -1571,6 +1876,16 @@ packages: engines: {node: '>=0.10.0'} dev: true + /map-obj/1.0.1: + resolution: {integrity: sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==} + engines: {node: '>=0.10.0'} + dev: true + + /map-obj/4.3.0: + resolution: {integrity: sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==} + engines: {node: '>=8'} + dev: true + /map-visit/1.0.0: resolution: {integrity: sha512-4y7uGv8bd2WdM9vpQsiQNo41Ln1NvhvDRuVt0k2JZQ+ezN2uaQes7lZeZ+QQUHOLQAtDaBJ+7wCbi+ab/KFs+w==} engines: {node: '>=0.10.0'} @@ -1578,6 +1893,28 @@ packages: object-visit: 1.0.1 dev: true + /mathml-tag-names/2.1.3: + resolution: {integrity: sha512-APMBEanjybaPzUrfqU0IMU5I0AswKMH7k8OTLs0vvV4KZpExkTkY87nR/zpbuTPj+gARop7aGUbl11pnDfW6xg==} + dev: true + + /meow/9.0.0: + resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} + engines: {node: '>=10'} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 6.2.2 + decamelize: 1.2.0 + decamelize-keys: 1.1.1 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 7.0.1 + redent: 3.0.0 + trim-newlines: 3.0.1 + type-fest: 0.18.1 + yargs-parser: 20.2.9 + dev: true + /merge2/1.4.1: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} @@ -1591,12 +1928,26 @@ packages: picomatch: 2.3.1 dev: true + /min-indent/1.0.1: + resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==} + engines: {node: '>=4'} + dev: true + /minimatch/3.1.2: resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} dependencies: brace-expansion: 1.1.11 dev: true + /minimist-options/4.1.0: + resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} + engines: {node: '>= 6'} + dependencies: + arrify: 1.0.1 + is-plain-obj: 1.1.0 + kind-of: 6.0.3 + dev: true + /mixin-deep/1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} engines: {node: '>=0.10.0'} @@ -1621,6 +1972,12 @@ packages: resolution: {integrity: sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==} dev: true + /nanoid/3.3.4: + resolution: {integrity: sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==} + engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} + hasBin: true + dev: true + /nanomatch/1.2.13: resolution: {integrity: sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==} engines: {node: '>=0.10.0'} @@ -1660,6 +2017,30 @@ packages: whatwg-url: 5.0.0 dev: true + /normalize-package-data/2.5.0: + resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} + dependencies: + hosted-git-info: 2.8.9 + resolve: 1.22.1 + semver: 5.7.1 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-package-data/3.0.3: + resolution: {integrity: sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==} + engines: {node: '>=10'} + dependencies: + hosted-git-info: 4.1.0 + is-core-module: 2.11.0 + semver: 7.3.8 + validate-npm-package-license: 3.0.4 + dev: true + + /normalize-path/3.0.0: + resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} + engines: {node: '>=0.10.0'} + dev: true + /object-copy/0.1.0: resolution: {integrity: sha512-79LYn6VAb63zgtmAteVOWo9Vdj71ZVBy3Pbse+VqxDpEP83XuujMrGqHIwAXJ5I/aM0zU7dIyIAhifVTPrNItQ==} engines: {node: '>=0.10.0'} @@ -1701,6 +2082,13 @@ packages: word-wrap: 1.2.3 dev: true + /p-limit/2.3.0: + resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==} + engines: {node: '>=6'} + dependencies: + p-try: 2.2.0 + dev: true + /p-limit/3.1.0: resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} engines: {node: '>=10'} @@ -1708,6 +2096,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-locate/4.1.0: + resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} + engines: {node: '>=8'} + dependencies: + p-limit: 2.3.0 + dev: true + /p-locate/5.0.0: resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} engines: {node: '>=10'} @@ -1715,6 +2110,11 @@ packages: p-limit: 3.1.0 dev: true + /p-try/2.2.0: + resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} + engines: {node: '>=6'} + dev: true + /parent-module/1.0.1: resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} engines: {node: '>=6'} @@ -1722,6 +2122,16 @@ packages: callsites: 3.1.0 dev: true + /parse-json/5.2.0: + resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} + engines: {node: '>=8'} + dependencies: + '@babel/code-frame': 7.18.6 + error-ex: 1.3.2 + json-parse-even-better-errors: 2.3.1 + lines-and-columns: 1.2.4 + dev: true + /pascalcase/0.1.1: resolution: {integrity: sha512-XHXfu/yOQRy9vYOtUDVMN60OEJjW013GoObG1o+xwQTpB9eYJX/BjXMsdW13ZDPruFhYYn0AG22w0xgQMwl3Nw==} engines: {node: '>=0.10.0'} @@ -1742,6 +2152,10 @@ packages: engines: {node: '>=8'} dev: true + /path-parse/1.0.7: + resolution: {integrity: sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==} + dev: true + /path-type/4.0.0: resolution: {integrity: sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==} engines: {node: '>=8'} @@ -1751,11 +2165,53 @@ packages: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} dev: true + /picocolors/1.0.0: + resolution: {integrity: sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==} + dev: true + /picomatch/2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} dev: true + /postcss-media-query-parser/0.2.3: + resolution: {integrity: sha512-3sOlxmbKcSHMjlUXQZKQ06jOswE7oVkXPxmZdoB1r5l0q6gTFTQSHxNxOrCccElbW7dxNytifNEo8qidX2Vsig==} + dev: true + + /postcss-resolve-nested-selector/0.1.1: + resolution: {integrity: sha512-HvExULSwLqHLgUy1rl3ANIqCsvMS0WHss2UOsXhXnQaZ9VCc2oBvIpXrl00IUFT5ZDITME0o6oiXeiHr2SAIfw==} + dev: true + + /postcss-safe-parser/6.0.0_postcss@8.4.21: + resolution: {integrity: sha512-FARHN8pwH+WiS2OPCxJI8FuRJpTVnn6ZNFiqAM2aeW2LwTHWWmWgIyKC6cUo0L8aeKiF/14MNvnpls6R2PBeMQ==} + engines: {node: '>=12.0'} + peerDependencies: + postcss: ^8.3.3 + dependencies: + postcss: 8.4.21 + dev: true + + /postcss-selector-parser/6.0.11: + resolution: {integrity: sha512-zbARubNdogI9j7WY4nQJBiNqQf3sLS3wCP4WfOidu+p28LofJqDH1tcXypGrcmMHhDk2t9wGhCsYe/+szLTy1g==} + engines: {node: '>=4'} + dependencies: + cssesc: 3.0.0 + util-deprecate: 1.0.2 + dev: true + + /postcss-value-parser/4.2.0: + resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} + dev: true + + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /prelude-ls/1.2.1: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} @@ -1802,6 +2258,30 @@ packages: resolution: {integrity: sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==} dev: true + /quick-lru/4.0.1: + resolution: {integrity: sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==} + engines: {node: '>=8'} + dev: true + + /read-pkg-up/7.0.1: + resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} + engines: {node: '>=8'} + dependencies: + find-up: 4.1.0 + read-pkg: 5.2.0 + type-fest: 0.8.1 + dev: true + + /read-pkg/5.2.0: + resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} + engines: {node: '>=8'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 2.5.0 + parse-json: 5.2.0 + type-fest: 0.6.0 + dev: true + /readable-stream/3.6.0: resolution: {integrity: sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==} engines: {node: '>= 6'} @@ -1811,6 +2291,14 @@ packages: util-deprecate: 1.0.2 dev: true + /redent/3.0.0: + resolution: {integrity: sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==} + engines: {node: '>=8'} + dependencies: + indent-string: 4.0.0 + strip-indent: 3.0.0 + dev: true + /regex-not/1.0.2: resolution: {integrity: sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==} engines: {node: '>=0.10.0'} @@ -1824,16 +2312,35 @@ packages: engines: {node: '>=8'} dev: true + /require-from-string/2.0.2: + resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==} + engines: {node: '>=0.10.0'} + dev: true + /resolve-from/4.0.0: resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} engines: {node: '>=4'} dev: true + /resolve-from/5.0.0: + resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} + engines: {node: '>=8'} + dev: true + /resolve-url/0.2.1: resolution: {integrity: sha512-ZuF55hVUQaaczgOIwqWzkEcEidmlD/xl44x1UZnhOXcYuFN2S6+rcxpG+C1N3So0wvNI3DmJICUFfu2SxhBmvg==} deprecated: https://github.com/lydell/resolve-url#deprecated dev: true + /resolve/1.22.1: + resolution: {integrity: sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==} + hasBin: true + dependencies: + is-core-module: 2.11.0 + path-parse: 1.0.7 + supports-preserve-symlinks-flag: 1.0.0 + dev: true + /ret/0.1.15: resolution: {integrity: sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==} engines: {node: '>=0.12'} @@ -1867,6 +2374,11 @@ packages: ret: 0.1.15 dev: true + /semver/5.7.1: + resolution: {integrity: sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==} + hasBin: true + dev: true + /semver/7.3.7: resolution: {integrity: sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==} engines: {node: '>=10'} @@ -1905,11 +2417,24 @@ packages: engines: {node: '>=8'} dev: true + /signal-exit/3.0.7: + resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + dev: true + /slash/3.0.0: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} dev: true + /slice-ansi/4.0.0: + resolution: {integrity: sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==} + engines: {node: '>=10'} + dependencies: + ansi-styles: 4.3.0 + astral-regex: 2.0.0 + is-fullwidth-code-point: 3.0.0 + dev: true + /snapdragon/0.8.2: resolution: {integrity: sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==} engines: {node: '>=0.10.0'} @@ -1926,6 +2451,11 @@ packages: - supports-color dev: true + /source-map-js/1.0.2: + resolution: {integrity: sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==} + engines: {node: '>=0.10.0'} + dev: true + /source-map-resolve/0.5.3: resolution: {integrity: sha512-Htz+RnsXWk5+P2slx5Jh3Q66vhQj1Cllm0zvnaY98+NFx+Dv2CF/f5O/t8x+KaNdrdIAsruNzoh/KpialbqAnw==} deprecated: See https://github.com/lydell/source-map-resolve#deprecated @@ -1947,6 +2477,28 @@ packages: engines: {node: '>=0.10.0'} dev: true + /spdx-correct/3.1.1: + resolution: {integrity: sha512-cOYcUWwhCuHCXi49RhFRCyJEK3iPj1Ziz9DpViV3tbZOwXD49QzIN3MpOLJNxh2qwq2lJJZaKMVw9qNi4jTC0w==} + dependencies: + spdx-expression-parse: 3.0.1 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-exceptions/2.3.0: + resolution: {integrity: sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==} + dev: true + + /spdx-expression-parse/3.0.1: + resolution: {integrity: sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==} + dependencies: + spdx-exceptions: 2.3.0 + spdx-license-ids: 3.0.12 + dev: true + + /spdx-license-ids/3.0.12: + resolution: {integrity: sha512-rr+VVSXtRhO4OHbXUiAF7xW3Bo9DuuF6C5jH+q/x15j2jniycgKbxU09Hr0WqlSLUs4i4ltHGXqTe7VHclYWyA==} + dev: true + /split-string/3.1.0: resolution: {integrity: sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==} engines: {node: '>=0.10.0'} @@ -1968,6 +2520,15 @@ packages: object-copy: 0.1.0 dev: true + /string-width/4.2.3: + resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} + engines: {node: '>=8'} + dependencies: + emoji-regex: 8.0.0 + is-fullwidth-code-point: 3.0.0 + strip-ansi: 6.0.1 + dev: true + /string_decoder/1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} dependencies: @@ -1981,11 +2542,93 @@ packages: ansi-regex: 5.0.1 dev: true + /strip-indent/3.0.0: + resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} + engines: {node: '>=8'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: true + /style-search/0.1.0: + resolution: {integrity: sha512-Dj1Okke1C3uKKwQcetra4jSuk0DqbzbYtXipzFlFMZtowbF1x7BKJwB9AayVMyFARvU8EDrZdcax4At/452cAg==} + dev: true + + /stylelint-config-recommended/9.0.0_stylelint@14.16.1: + resolution: {integrity: sha512-9YQSrJq4NvvRuTbzDsWX3rrFOzOlYBmZP+o513BJN/yfEmGSr0AxdvrWs0P/ilSpVV/wisamAHu5XSk8Rcf4CQ==} + peerDependencies: + stylelint: ^14.10.0 + dependencies: + stylelint: 14.16.1 + dev: true + + /stylelint-config-standard/29.0.0_stylelint@14.16.1: + resolution: {integrity: sha512-uy8tZLbfq6ZrXy4JKu3W+7lYLgRQBxYTUUB88vPgQ+ZzAxdrvcaSUW9hOMNLYBnwH+9Kkj19M2DHdZ4gKwI7tg==} + peerDependencies: + stylelint: ^14.14.0 + dependencies: + stylelint: 14.16.1 + stylelint-config-recommended: 9.0.0_stylelint@14.16.1 + dev: true + + /stylelint/14.16.1: + resolution: {integrity: sha512-ErlzR/T3hhbV+a925/gbfc3f3Fep9/bnspMiJPorfGEmcBbXdS+oo6LrVtoUZ/w9fqD6o6k7PtUlCOsCRdjX/A==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + hasBin: true + dependencies: + '@csstools/selector-specificity': 2.1.1_wajs5nedgkikc5pcuwett7legi + balanced-match: 2.0.0 + colord: 2.9.3 + cosmiconfig: 7.1.0 + css-functions-list: 3.1.0 + debug: 4.3.4 + fast-glob: 3.2.12 + fastest-levenshtein: 1.0.16 + file-entry-cache: 6.0.1 + global-modules: 2.0.0 + globby: 11.1.0 + globjoin: 0.1.4 + html-tags: 3.2.0 + ignore: 5.2.4 + import-lazy: 4.0.0 + imurmurhash: 0.1.4 + is-plain-object: 5.0.0 + known-css-properties: 0.26.0 + mathml-tag-names: 2.1.3 + meow: 9.0.0 + micromatch: 4.0.5 + normalize-path: 3.0.0 + picocolors: 1.0.0 + postcss: 8.4.21 + postcss-media-query-parser: 0.2.3 + postcss-resolve-nested-selector: 0.1.1 + postcss-safe-parser: 6.0.0_postcss@8.4.21 + postcss-selector-parser: 6.0.11 + postcss-value-parser: 4.2.0 + resolve-from: 5.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + style-search: 0.1.0 + supports-hyperlinks: 2.3.0 + svg-tags: 1.0.0 + table: 6.8.1 + v8-compile-cache: 2.3.0 + write-file-atomic: 4.0.2 + transitivePeerDependencies: + - supports-color + dev: true + + /supports-color/5.5.0: + resolution: {integrity: sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==} + engines: {node: '>=4'} + dependencies: + has-flag: 3.0.0 + dev: true + /supports-color/7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} engines: {node: '>=8'} @@ -1993,6 +2636,34 @@ packages: has-flag: 4.0.0 dev: true + /supports-hyperlinks/2.3.0: + resolution: {integrity: sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==} + engines: {node: '>=8'} + dependencies: + has-flag: 4.0.0 + supports-color: 7.2.0 + dev: true + + /supports-preserve-symlinks-flag/1.0.0: + resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} + engines: {node: '>= 0.4'} + dev: true + + /svg-tags/1.0.0: + resolution: {integrity: sha512-ovssysQTa+luh7A5Weu3Rta6FJlFBBbInjOh722LIt6klpU2/HtdUbszju/G4devcvk8PGt7FCLv5wftu3THUA==} + dev: true + + /table/6.8.1: + resolution: {integrity: sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==} + engines: {node: '>=10.0.0'} + dependencies: + ajv: 8.12.0 + lodash.truncate: 4.4.2 + slice-ansi: 4.0.0 + string-width: 4.2.3 + strip-ansi: 6.0.1 + dev: true + /tar-fs/2.1.1: resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} dependencies: @@ -2049,6 +2720,11 @@ packages: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} dev: true + /trim-newlines/3.0.1: + resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} + engines: {node: '>=8'} + dev: true + /tslib/1.14.1: resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==} dev: true @@ -2070,11 +2746,26 @@ packages: prelude-ls: 1.2.1 dev: true + /type-fest/0.18.1: + resolution: {integrity: sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==} + engines: {node: '>=10'} + dev: true + /type-fest/0.20.2: resolution: {integrity: sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==} engines: {node: '>=10'} dev: true + /type-fest/0.6.0: + resolution: {integrity: sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==} + engines: {node: '>=8'} + dev: true + + /type-fest/0.8.1: + resolution: {integrity: sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==} + engines: {node: '>=8'} + dev: true + /type-fest/3.5.3: resolution: {integrity: sha512-V2+og4j/rWReWvaFrse3s9g2xvUv/K9Azm/xo6CjIuq7oeGqsoimC7+9/A3tfvNcbQf8RPSVj/HV81fB4DJrjA==} engines: {node: '>=14.16'} @@ -2131,6 +2822,17 @@ packages: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} dev: true + /v8-compile-cache/2.3.0: + resolution: {integrity: sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==} + dev: true + + /validate-npm-package-license/3.0.4: + resolution: {integrity: sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==} + dependencies: + spdx-correct: 3.1.1 + spdx-expression-parse: 3.0.1 + dev: true + /vscode-oniguruma/1.7.0: resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} dev: false @@ -2150,6 +2852,13 @@ packages: webidl-conversions: 3.0.1 dev: true + /which/1.3.1: + resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} + hasBin: true + dependencies: + isexe: 2.0.0 + dev: true + /which/2.0.2: resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} engines: {node: '>= 8'} @@ -2167,6 +2876,14 @@ packages: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} dev: true + /write-file-atomic/4.0.2: + resolution: {integrity: sha512-7KxauUdBmSdWnmpaGFg+ppNjKF8uNLry8LyzjauQDOVONfFLNKrKvQOxZ/VuTIcS/gge/YNahf5RIIQWTSarlg==} + engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} + dependencies: + imurmurhash: 0.1.4 + signal-exit: 3.0.7 + dev: true + /ws/8.11.0: resolution: {integrity: sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==} engines: {node: '>=10.0.0'} @@ -2184,6 +2901,16 @@ packages: resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==} dev: true + /yaml/1.10.2: + resolution: {integrity: sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==} + engines: {node: '>= 6'} + dev: true + + /yargs-parser/20.2.9: + resolution: {integrity: sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==} + engines: {node: '>=10'} + dev: true + /yauzl/2.10.0: resolution: {integrity: sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==} dependencies: diff --git a/src/components/PluginSettings/styles.css b/src/components/PluginSettings/styles.css index 1626d7dba..a756fa9d5 100644 --- a/src/components/PluginSettings/styles.css +++ b/src/components/PluginSettings/styles.css @@ -94,6 +94,7 @@ -webkit-line-clamp: 2; line-clamp: 2; -webkit-box-orient: vertical; + /* stylelint-disable-next-line property-no-unknown */ box-orient: vertical; } @@ -132,6 +133,6 @@ margin-top: 0.5em; } -.vc-plugins-info-button svg:not(:hover):not(:focus) { +.vc-plugins-info-button svg:not(:hover, :focus) { color: var(--text-muted); } diff --git a/src/components/VencordSettings/settingsStyles.css b/src/components/VencordSettings/settingsStyles.css index 4b1e16b7d..76064be14 100644 --- a/src/components/VencordSettings/settingsStyles.css +++ b/src/components/VencordSettings/settingsStyles.css @@ -16,9 +16,8 @@ gap: 1em; align-items: center; justify-content: space-between; - flex-wrap: wrap; flex-grow: 1; - flex-direction: row; + flex-flow: row wrap; margin-bottom: 1em; } diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 8c7fa1149..8c897b6e3 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -45,8 +45,8 @@ export default definePlugin({ }, stop() { - document.querySelectorAll(".messageLogger-deleted").forEach(e => e.remove()); - document.querySelectorAll(".messageLogger-edited").forEach(e => e.remove()); + document.querySelectorAll(".messagelogger-deleted").forEach(e => e.remove()); + document.querySelectorAll(".messagelogger-edited").forEach(e => e.remove()); document.body.classList.remove("messagelogger-red-overlay"); document.body.classList.remove("messagelogger-red-text"); }, @@ -54,7 +54,7 @@ export default definePlugin({ renderEdit(edit: { timestamp: any, content: string; }) { return ( <ErrorBoundary noop> - <div className="messageLogger-edited"> + <div className="messagelogger-edited"> {Parser.parse(edit.content)} <Timestamp timestamp={edit.timestamp} @@ -252,7 +252,7 @@ export default definePlugin({ }, { match: /\["className","attachment","inlineMedia".+?className:/, - replace: "$& (deleted ? 'messageLogger-deleted-attachment ' : '') +" + replace: "$& (deleted ? 'messagelogger-deleted-attachment ' : '') +" } ] }, @@ -268,9 +268,9 @@ export default definePlugin({ replace: "var $1=$2.id,deleted=$2.message.deleted," }, { - // Append messageLogger-deleted to classNames if deleted + // Append messagelogger-deleted to classNames if deleted match: /\)\("li",\{(.+?),className:/, - replace: ")(\"li\",{$1,className:(deleted ? \"messageLogger-deleted \" : \"\")+" + replace: ")(\"li\",{$1,className:(deleted ? \"messagelogger-deleted \" : \"\")+" } ] }, diff --git a/src/plugins/messageLogger/messageLogger.css b/src/plugins/messageLogger/messageLogger.css index 94a3e2509..925a09c59 100644 --- a/src/plugins/messageLogger/messageLogger.css +++ b/src/plugins/messageLogger/messageLogger.css @@ -1,27 +1,28 @@ -.messagelogger-red-overlay .messageLogger-deleted { - background-color: rgba(240, 71, 71, 0.15); +.messagelogger-red-overlay .messagelogger-deleted { + background-color: rgba(240 71 71 / 15%); } -.messagelogger-red-text .messageLogger-deleted div { + +.messagelogger-red-text .messagelogger-deleted div { color: #f04747; } -.messageLogger-deleted [class^="buttons"] { +.messagelogger-deleted [class^="buttons"] { display: none; } -.messageLogger-deleted-attachment { +.messagelogger-deleted-attachment { filter: grayscale(1); } -.messageLogger-deleted-attachment:hover { +.messagelogger-deleted-attachment:hover { filter: grayscale(0); transition: 250ms filter linear; } -.theme-dark .messageLogger-edited { +.theme-dark .messagelogger-edited { filter: brightness(80%); } -.theme-light .messageLogger-edited { +.theme-light .messagelogger-edited { opacity: 0.5; } diff --git a/src/plugins/shikiCodeblocks/devicon.css b/src/plugins/shikiCodeblocks/devicon.css index f5c49212d..ed1014e38 100644 --- a/src/plugins/shikiCodeblocks/devicon.css +++ b/src/plugins/shikiCodeblocks/devicon.css @@ -1 +1 @@ -@import url('https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css'); +@import url("https://cdn.jsdelivr.net/gh/devicons/devicon@v2.10.1/devicon.min.css"); diff --git a/src/plugins/shikiCodeblocks/shiki.css b/src/plugins/shikiCodeblocks/shiki.css index d71b67392..2051183fd 100644 --- a/src/plugins/shikiCodeblocks/shiki.css +++ b/src/plugins/shikiCodeblocks/shiki.css @@ -12,7 +12,6 @@ overflow-x: auto; padding: 0.5em; position: relative; - font-size: 0.875rem; line-height: 1.125rem; text-indent: 0; @@ -47,7 +46,7 @@ padding: 4px 8px; } -.shiki-btn~.shiki-btn { +.shiki-btn ~ .shiki-btn { margin-left: 4px; } @@ -57,7 +56,7 @@ .shiki-spinner-container { align-items: center; - background-color: rgba(0, 0, 0, 0.6); + background-color: rgb(0 0 0 / 60%); display: flex; position: absolute; justify-content: center; diff --git a/src/plugins/spotifyControls/spotifyStyles.css b/src/plugins/spotifyControls/spotifyStyles.css index 9c7b1c042..63a06e3b0 100644 --- a/src/plugins/spotifyControls/spotifyStyles.css +++ b/src/plugins/spotifyControls/spotifyStyles.css @@ -1,20 +1,22 @@ #vc-spotify-player { padding: 0.375rem 0.5rem; border-bottom: 1px solid var(--background-modifier-accent); + --vc-spotify-green: #1db954; /* so cusotm themes can easily change it */ } + .vc-spotify-button { background: none; color: var(--interactive-normal); padding: 0; width: 32px; height: 32px; - border-radius: 100%; display: flex; justify-content: center; align-items: center; } + .vc-spotify-button:hover { color: var(--interactive-hover); background-color: var(--background-modifier-selected); @@ -24,15 +26,18 @@ height: 24px; width: 24px; } + [class*="vc-spotify-shuffle"] > svg, [class*="vc-spotify-repeat"] > svg { width: 22px; height: 22px; } + .vc-spotify-button svg path { width: 100%; height: 100%; } + /* .vc-spotify-button:hover { filter: brightness(1.3); } */ @@ -51,7 +56,9 @@ white-space: nowrap; padding-right: 0.2em; max-width: 100%; + margin: unset; } + .vc-spotify-repeat-1 { font-size: 70%; position: absolute; @@ -92,15 +99,12 @@ overflow: hidden; } -.vc-spotify-tooltip-text { - margin: unset; -} - #vc-spotify-song-title { color: var(--header-primary); font-size: 14px; font-weight: 600; } + .vc-spotify-ellipoverflow { white-space: nowrap; overflow: hidden; @@ -137,7 +141,6 @@ #vc-spotify-progress-bar { position: relative; - color: var(--text-normal); width: 100%; margin: 0.5em 0; @@ -153,6 +156,7 @@ #vc-spotify-progress-bar > [class^="slider"] [class^="bar-"] { height: 4px !important; } + #vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] { /* these importants are neccessary, it applies a width and height through inline styles */ height: 10px !important; @@ -168,7 +172,6 @@ .vc-spotify-progress-time { font-size: 12px; - top: 10px; position: absolute; } @@ -176,6 +179,7 @@ .vc-spotify-time-left { left: 0; } + .vc-spotify-time-right { right: 0; } From 8f4e8d0a9bd29b59cd9ea4e3228fd1b3e73fbfd9 Mon Sep 17 00:00:00 2001 From: Nick <nwowens32@gmail.com> Date: Tue, 31 Jan 2023 00:35:52 -0500 Subject: [PATCH 084/114] TypingTweaks: fix crash on non en-US locales (#463) --- src/plugins/typingTweaks.tsx | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx index bc1883151..96c04b909 100644 --- a/src/plugins/typingTweaks.tsx +++ b/src/plugins/typingTweaks.tsx @@ -66,10 +66,18 @@ export default definePlugin({ }, // Changes indicator to format message with the typing users { - find: ',"SEVERAL_USERS_TYPING","', + find: '"SEVERAL_USERS_TYPING":"', replacement: { - match: /(\i)\((\i),"SEVERAL_USERS_TYPING",".+?"\)/, - replace: "$1($2,\"SEVERAL_USERS_TYPING\",\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")" + match: /("SEVERAL_USERS_TYPING"):".+?"/, + replace: "$1:\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\"" + }, + predicate: () => settings.store.alternativeFormatting + }, + { + find: ",\"SEVERAL_USERS_TYPING\",\"", + replacement: { + match: /(\i)\((\i),("SEVERAL_USERS_TYPING"),".+?"\)/, + replace: "$1($2,$3,\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")" }, predicate: () => settings.store.alternativeFormatting }, @@ -78,7 +86,7 @@ export default definePlugin({ find: "getCooldownTextStyle", replacement: { match: /(\i)\.length\?.\..\.Messages\.THREE_USERS_TYPING.format\(\{a:(\i),b:(\i),c:.}\).+?SEVERAL_USERS_TYPING/, - replace: "$&.format({a:$2,b:$3,c:$1.length})" + replace: "$&.format({a:$2,b:$3,c:$1.length-2})" }, predicate: () => settings.store.alternativeFormatting } @@ -105,7 +113,7 @@ export default definePlugin({ size={Avatar.Sizes.SIZE_16} src={user.getAvatarURL(guildId, 128)}/> </div>} - {user.username} + {GuildMemberStore.getNick(guildId!, user.id) || user.username} </strong>; }, { noop: true }) }); From 369d179bbf67d34fc4d5f8312d19a106f3552373 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 1 Feb 2023 08:11:05 -0300 Subject: [PATCH 085/114] ShowHiddenChannels: New screen for showing hidden channels (#460) Co-authored-by: Ven <vendicated@riseup.net> --- src/components/ErrorBoundary.tsx | 2 +- .../components/HiddenChannelLockScreen.tsx | 202 ++++++++++++++++++ .../index.tsx} | 135 +++++------- src/plugins/showHiddenChannels/style.css | 78 +++++++ src/utils/index.ts | 1 + src/utils/text.ts | 25 +++ 6 files changed, 366 insertions(+), 77 deletions(-) create mode 100644 src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx rename src/plugins/{showHiddenChannels.tsx => showHiddenChannels/index.tsx} (64%) create mode 100644 src/plugins/showHiddenChannels/style.css diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 8ebc61bbb..a13640e10 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -103,7 +103,7 @@ const ErrorBoundary = LazyComponent(() => { }; }) as React.ComponentType<React.PropsWithChildren<Props>> & { - wrap<T extends JSX.IntrinsicAttributes = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Props): React.ComponentType<T>; + wrap<T extends object = any>(Component: React.ComponentType<T>, errorBoundaryProps?: Props): React.ComponentType<T>; }; ErrorBoundary.wrap = (Component, errorBoundaryProps) => props => ( diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx new file mode 100644 index 000000000..e5c5ee27e --- /dev/null +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -0,0 +1,202 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { LazyComponent } from "@utils/misc"; +import { proxyLazy } from "@utils/proxyLazy"; +import { formatDuration } from "@utils/text"; +import { find, findByCode, findByPropsLazy, findLazy } from "@webpack"; +import { moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; +import { Channel } from "discord-types/general"; + +enum SortOrderTypesTyping { + LATEST_ACTIVITY = 0, + CREATION_DATE = 1 +} + +enum ForumLayoutTypesTyping { + DEFAULT = 0, + LIST = 1, + GRID = 2 +} + +interface DefaultReaction { + emojiId: string | null; + emojiName: string | null; +} + +interface Tag { + id: string; + name: string; + emojiId: string | null; + emojiName: string | null; + moderated: boolean; +} + +interface ExtendedChannel extends Channel { + defaultThreadRateLimitPerUser?: number; + defaultSortOrder?: SortOrderTypesTyping | null; + defaultForumLayout?: ForumLayoutTypesTyping; + defaultReactionEmoji?: DefaultReaction | null; + availableTags?: Array<Tag>; +} + +const ChatClasses = findByPropsLazy("chat", "chatContent"); +const TagClasses = findLazy(m => typeof m.tags === "string" && Object.entries(m).length === 1); // Object exported with a single key called tags +const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); +const SortOrderTypes = findLazy(m => typeof m.LATEST_ACTIVITY === "number"); +const ForumLayoutTypes = findLazy(m => typeof m.LIST === "number"); +const ChannelFlags = findLazy(m => typeof m.REQUIRE_TAG === "number"); +const TagComponent = LazyComponent(() => find(m => { + if (typeof m !== "function") return false; + + const code = Function.prototype.toString.call(m); + // Get the component which doesn't include increasedActivity logic + return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); +})); +const EmojiComponent = LazyComponent(() => findByCode('.jumboable?"jumbo":"default"')); + +const ChannelTypesToChannelNames = proxyLazy(() => ({ + [ChannelTypes.GUILD_TEXT]: "text", + [ChannelTypes.GUILD_ANNOUNCEMENT]: "announcement", + [ChannelTypes.GUILD_FORUM]: "forum" +})); + +const SortOrderTypesToNames = proxyLazy(() => ({ + [SortOrderTypes.LATEST_ACTIVITY]: "Latest activity", + [SortOrderTypes.CREATION_DATE]: "Creation date" +})); + +const ForumLayoutTypesToNames = proxyLazy(() => ({ + [ForumLayoutTypes.DEFAULT]: "Not set", + [ForumLayoutTypes.LIST]: "List view", + [ForumLayoutTypes.GRID]: "Gallery view" +})); + +// Icon from the modal when clicking a message link you don't have access to view +const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg"; + +function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { + const { + type, + topic, + lastMessageId, + defaultForumLayout, + lastPinTimestamp, + defaultAutoArchiveDuration, + availableTags, + id: channelId, + rateLimitPerUser, + defaultThreadRateLimitPerUser, + defaultSortOrder, + defaultReactionEmoji + } = channel; + + return ( + <div className={ChatClasses.chat + " " + "shc-lock-screen-container"}> + <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> + + <div className="shc-lock-screen-heading-container"> + <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> + {channel.isNSFW() && + <Tooltip text="NSFW"> + {({ onMouseLeave, onMouseEnter }) => ( + <svg + onMouseLeave={onMouseLeave} + onMouseEnter={onMouseEnter} + className="shc-lock-screen-heading-nsfw-icon" + width="32" + height="32" + viewBox="0 0 48 48" + aria-hidden={true} + role="img" + > + <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> + </svg> + )} + </Tooltip> + } + </div> + + <Text variant="text-lg/normal"> + You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. + {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} + </Text > + + {channel.isForumChannel() && topic && topic.length > 0 && ( + <div className="shc-lock-screen-topic-container"> + {Parser.parseTopic(topic, false, { channelId })} + </div> + )} + + {lastMessageId && + <Text variant="text-md/normal"> + Last {channel.isForumChannel() ? "post" : "message"} created: + <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> + </Text> + } + + {lastPinTimestamp && + <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> + } + {(rateLimitPerUser ?? 0) > 0 && + <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser! * 1000)}</Text> + } + {(defaultThreadRateLimitPerUser ?? 0) > 0 && + <Text variant="text-md/normal"> + Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser! * 1000)} + </Text> + } + {(defaultAutoArchiveDuration ?? 0) > 0 && + <Text variant="text-md/normal"> + Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: + {formatDuration(defaultAutoArchiveDuration! * 1000 * 60)} + </Text> + } + {defaultForumLayout != null && + <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> + } + {defaultSortOrder != null && + <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> + } + {defaultReactionEmoji != null && + <div className="shc-lock-screen-default-emoji-container"> + <Text variant="text-md/normal">Default reaction emoji:</Text> + <EmojiComponent node={{ + type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", + name: defaultReactionEmoji.emojiName ?? "", + emojiId: defaultReactionEmoji.emojiId + }} /> + </div> + } + {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && + <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> + } + {availableTags && availableTags.length > 0 && + <div className="shc-lock-screen-tags-container"> + <Text variant="text-lg/bold">Available tags:</Text> + <div className={TagClasses.tags}> + {availableTags.map(tag => <TagComponent tag={tag} />)} + </div> + </div> + } + </div> + ); +} + +export default ErrorBoundary.wrap(HiddenChannelLockScreen); diff --git a/src/plugins/showHiddenChannels.tsx b/src/plugins/showHiddenChannels/index.tsx similarity index 64% rename from src/plugins/showHiddenChannels.tsx rename to src/plugins/showHiddenChannels/index.tsx index 283eb83a7..abb443e76 100644 --- a/src/plugins/showHiddenChannels.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -16,27 +16,20 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import "./style.css"; import { definePluginSettings } from "@api/settings"; -import { Badge } from "@components/Badge"; -import { Flex } from "@components/Flex"; +import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { ModalContent, ModalFooter, ModalHeader, ModalRoot, ModalSize, openModal } from "@utils/modal"; -import { proxyLazy } from "@utils/proxyLazy"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findLazy } from "@webpack"; -import { Button, ChannelStore, moment, Parser, PermissionStore, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; +import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; +import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; + const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); const Permissions = findLazy(m => typeof m.VIEW_CHANNEL === "bigint"); -const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); - -const ChannelTypesToChannelName = proxyLazy(() => ({ - [ChannelTypes.GUILD_TEXT]: "TEXT", - [ChannelTypes.GUILD_ANNOUNCEMENT]: "ANNOUNCEMENT", - [ChannelTypes.GUILD_FORUM]: "FORUM" -})); enum ShowMode { LockIcon, @@ -97,16 +90,8 @@ export default definePlugin({ ] }, { - // inside the onMouseDown handler, we check if the channel is hidden and open the modal if it is find: "VoiceChannel.renderPopout: There must always be something to render", replacement: [ - { - match: /(?=(?<this>\i)\.handleThreadsPopoutClose\(\))/, - replace: "if($self.isHiddenChannel($<this>.props.channel)&&arguments[0].button===0){" - + "$self.onHiddenChannelSelected($<this>.props.channel);" - + "return;" - + "}" - }, // Do nothing when trying to join a voice channel if the channel is hidden { match: /(?<=handleClick=function\(\){)(?=.{1,80}(?<this>\i)\.handleVoiceConnect\(\))/, @@ -179,6 +164,46 @@ export default definePlugin({ replace: "&&!$self.isHiddenChannel($<channel>)" } }, + // Only render the channel header and buttons that work when transitioning to a hidden channel + { + find: "Missing channel in Channel.renderHeaderToolbar", + replacement: [ + { + match: /(?<=renderHeaderToolbar=function.+?case \i\.\i\.GUILD_TEXT:)(?=.+?;(?<pushNotificationButtonExpression>.+?{channel:(?<channel>\i)},"notifications"\)\);))/, + replace: "if($self.isHiddenChannel($<channel>)){$<pushNotificationButtonExpression>break;}" + }, + { + match: /(?<=renderHeaderToolbar=function.+?case \i\.\i\.GUILD_FORUM:if\(!\i\){)(?=.+?;(?<pushNotificationButtonExpression>.+?{channel:(?<channel>\i)},"notifications"\)\)))/, + replace: "if($self.isHiddenChannel($<channel>)){$<pushNotificationButtonExpression>;break;}" + }, + { + match: /(?<=(?<this>\i)\.renderMobileToolbar=function.+?case \i\.\i\.GUILD_FORUM:)/, + replace: "if($self.isHiddenChannel($<this>.props.channel))break;" + }, + { + match: /(?<=renderHeaderBar=function.+?hideSearch:(?<channel>\i)\.isDirectory\(\))/, + replace: "||$self.isHiddenChannel($<channel>)" + }, + { + match: /(?<=renderSidebar=function\(\){)/, + replace: "if($self.isHiddenChannel(this.props.channel))return null;" + }, + { + match: /(?<=renderChat=function\(\){)/, + replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);" + }, + ] + }, + // Avoid trying to fetch messages from hidden channels + { + find: '"MessageManager"', + replacement: [ + { + match: /(?<=if\(null!=(?<channelId>\i)\).{1,100}"Skipping fetch because channelId is a static route".{1,10}else{)/, + replace: "if($self.isHiddenChannel({channelId:$<channelId>}))return;" + }, + ] + }, // Patch keybind handlers so you can't accidentally jump to hidden channels { find: '"alt+shift+down"', @@ -194,6 +219,14 @@ export default definePlugin({ replace: ".filter(ch=>!$self.isHiddenChannel(ch))" } }, + // Export the emoji component used on the lock screen + { + find: 'jumboable?"jumbo":"default"', + replacement: { + match: /(?<=\i:\(\)=>\i)(?=}.+?(?<component>\i)=function.{1,20}node,\i=\i.isInteracting)/, + replace: ",hc1:()=>$<component>" // Blame Ven length check for the small name :pensive_cry: + } + } ], isHiddenChannel(channel: Channel & { channelId?: string; }) { @@ -205,56 +238,7 @@ export default definePlugin({ return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel); }, - onHiddenChannelSelected(channel: Channel) { - // Check for type, otherwise it would attempt to show the modal for stage channels - if ([ChannelTypes.GUILD_TEXT, ChannelTypes.GUILD_ANNOUNCEMENT, ChannelTypes.GUILD_FORUM].includes(channel.type)) { - openModal(modalProps => ( - <ModalRoot size={ModalSize.SMALL} {...modalProps}> - <ModalHeader> - <Flex> - <Text variant="heading-md/bold">#{channel.name}</Text> - {<Badge text={ChannelTypesToChannelName[channel.type]} color="var(--brand-experiment)" />} - {channel.isNSFW() && <Badge text="NSFW" color="var(--status-danger)" />} - </Flex> - </ModalHeader> - <ModalContent style={{ margin: "10px 8px" }}> - <Text variant="text-md/normal">You don't have permission to view {channel.type === ChannelTypes.GUILD_FORUM ? "posts" : "messages"} in this channel.</Text> - {(channel.topic ?? "").length > 0 && ( - <> - <Text variant="text-md/bold" style={{ marginTop: 10 }}> - {channel.type === ChannelTypes.GUILD_FORUM ? "Guidelines:" : "Topic:"} - </Text> - <div style={{ color: "var(--text-normal)", marginTop: 10 }}> - {Parser.parseTopic(channel.topic, false, { channelId: channel.id })} - </div> - </> - )} - {channel.lastMessageId && ( - <> - <Text variant="text-md/bold" style={{ marginTop: 10 }}> - {channel.type === ChannelTypes.GUILD_FORUM ? "Last Post Created" : "Last Message Sent:"} - </Text> - <div style={{ color: "var(--text-normal)", marginTop: 10 }}> - <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(channel.lastMessageId))} /> - </div> - </> - )} - </ModalContent> - <ModalFooter> - <Flex> - <Button - onClick={modalProps.onClose} - size={Button.Sizes.SMALL} - color={Button.Colors.PRIMARY} - > - Close - </Button> - </Flex> - </ModalFooter> - </ModalRoot> - )); - } - }, + HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen channel={channel} />, LockIcon: () => ( <svg @@ -265,27 +249,26 @@ export default definePlugin({ aria-hidden={true} role="img" > - <path fillRule="evenodd" fill="currentColor" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> + <path className="shc-evenodd-fill-current-color " d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> </svg> ), - HiddenChannelIcon: () => ( + HiddenChannelIcon: ErrorBoundary.wrap(() => ( <Tooltip text="Hidden Channel"> {({ onMouseLeave, onMouseEnter }) => ( <svg onMouseLeave={onMouseLeave} onMouseEnter={onMouseEnter} - className={ChannelListClasses.icon} + className={ChannelListClasses.icon + " " + "shc-hidden-channel-icon"} width="24" height="24" viewBox="0 0 24 24" aria-hidden={true} role="img" - style={{ marginLeft: 6, zIndex: 0, cursor: "not-allowed" }} > - <path fillRule="evenodd" fill="currentColor" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> + <path className="shc-evenodd-fill-current-color " d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> </svg> )} </Tooltip> - ) + ), { noop: true }) }); diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css new file mode 100644 index 000000000..73957efce --- /dev/null +++ b/src/plugins/showHiddenChannels/style.css @@ -0,0 +1,78 @@ +.shc-lock-screen-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + text-align: center; +} + +.shc-lock-screen-container > * { + margin: 5px; +} + +.shc-lock-screen-logo { + width: 180px; + height: 180px; +} + +.shc-lock-screen-heading-container { + display: flex; + flex-direction: row; + align-items: center; +} + +.shc-lock-screen-heading-container > * { + margin: inherit; +} + +.shc-lock-screen-heading-nsfw-icon > path { + fill: var(--text-normal); + fill-rule: evenodd; +} + +.shc-lock-screen-topic-container { + color: var(--text-normal); + background-color: var(--background-secondary); + border-radius: 5px; + padding: 5px; + max-width: 70vw; +} + +.shc-lock-screen-tags-container { + background-color: var(--background-secondary); + border-radius: 5px; + padding: 5px; + max-width: 70vw; +} + +.shc-lock-screen-tags-container > * { + margin: inherit; +} + +.shc-lock-screen-tags-container > [class^="tags"] { + flex-wrap: wrap; +} + +.shc-evenodd-fill-current-color { + fill-rule: evenodd; + fill: currentcolor; +} + +.shc-hidden-channel-icon { + margin-left: 6px; + z-index: 0; + cursor: not-allowed; +} + +.shc-lock-screen-default-emoji-container { + display: flex; + flex-direction: row; + align-items: center; +} + +.shc-lock-screen-default-emoji-container > [class^="emojiContainer"] { + background-color: var(--background-secondary); + border-radius: 8px; + padding: 3px 4px; + margin-left: 5px; +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 41e1597a1..b80bde3b2 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -27,4 +27,5 @@ export * as Modals from "./modal"; export * from "./onceDefined"; export * from "./proxyLazy"; export * from "./Queue"; +export * from "./text"; diff --git a/src/utils/text.ts b/src/utils/text.ts index 17826e80d..fae33436c 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -16,6 +16,8 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import { moment } from "@webpack/common"; + // Utils for readable text transformations eg: `toTitle(fromKebab())` // Case style to words @@ -34,3 +36,26 @@ export const wordsToPascal = (words: string[]) => words.map(w => w[0].toUpperCase() + w.slice(1)).join(""); export const wordsToTitle = (words: string[]) => words.map(w => w[0].toUpperCase() + w.slice(1)).join(" "); + +/** + * Forms milliseconds into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" + * @param ms Milliseconds + * @param short Whether to use short units like "d" instead of "days" + */ +export function formatDuration(ms: number, short: boolean = false) { + const dur = moment.duration(ms); + return (["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const).reduce((res, unit) => { + const x = dur[unit](); + if (x > 0 || res.length) { + if (res.length) + res += unit === "seconds" ? " and " : ", "; + + const unitStr = short + ? unit[0] + : x === 1 ? unit.slice(0, -1) : unit; + + res += `${x} ${unitStr}`; + } + return res; + }, "").replace(/((,|and) \b0 \w+)+$/, "") || "now"; +} From de0990434e2859141c71b9ab42931297480fb6e2 Mon Sep 17 00:00:00 2001 From: whqwert <94757998+whqwert@users.noreply.github.com> Date: Wed, 1 Feb 2023 06:38:02 -0600 Subject: [PATCH 086/114] feat(plugin): RevealAllSpoilers (#381) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/revealAllSpoilers.ts | 58 ++++++++++++++++++++++++++++++++ src/utils/constants.ts | 4 +++ tsconfig.json | 1 + 3 files changed, 63 insertions(+) create mode 100644 src/plugins/revealAllSpoilers.ts diff --git a/src/plugins/revealAllSpoilers.ts b/src/plugins/revealAllSpoilers.ts new file mode 100644 index 000000000..b508b6a0a --- /dev/null +++ b/src/plugins/revealAllSpoilers.ts @@ -0,0 +1,58 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; + +const SpoilerClasses = findByPropsLazy("spoilerText"); +const MessagesClasses = findByPropsLazy("messagesWrapper", "messages"); + +export default definePlugin({ + name: "RevealAllSpoilers", + description: "Reveal all spoilers in a message by Ctrl-clicking a spoiler, or in the chat with Ctrl+Shift-click", + authors: [Devs.whqwert], + + patches: [ + { + find: ".revealSpoiler=function", + replacement: { + match: /\.revealSpoiler=function\((.{1,2})\){/, + replace: ".revealSpoiler=function($1){$self.reveal($1);" + } + } + ], + + reveal(event: MouseEvent) { + const { ctrlKey, shiftKey, target } = event; + + if (!ctrlKey) { return; } + + const { spoilerText, hidden } = SpoilerClasses; + const { messagesWrapper } = MessagesClasses; + + const parent = shiftKey + ? document.querySelector(`div.${messagesWrapper}`) + : (target as HTMLSpanElement).parentElement; + + for (const spoiler of parent!.querySelectorAll(`span.${spoilerText}.${hidden}`)) { + (spoiler as HTMLSpanElement).click(); + } + } + +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 1eba71304..de63de827 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -192,5 +192,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ captain: { name: "Captain", id: 347366054806159360n + }, + whqwert: { + name: "whqwert", + id: 586239091520176128n } }); diff --git a/tsconfig.json b/tsconfig.json index f811c00a9..db5407455 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -4,6 +4,7 @@ "esModuleInterop": true, "lib": [ "DOM", + "DOM.Iterable", "esnext", "esnext.array", "esnext.asynciterable", From 8b40760187704c9dcdbb95947eaae365266725ee Mon Sep 17 00:00:00 2001 From: Nico <nico@d3sox.me> Date: Wed, 1 Feb 2023 13:59:58 +0100 Subject: [PATCH 087/114] fix(showHiddenChannels): revert lock icon to correct path (#465) --- src/plugins/showHiddenChannels/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index abb443e76..9a448d5a2 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -249,7 +249,7 @@ export default definePlugin({ aria-hidden={true} role="img" > - <path className="shc-evenodd-fill-current-color " d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> + <path className="shc-evenodd-fill-current-color" d="M17 11V7C17 4.243 14.756 2 12 2C9.242 2 7 4.243 7 7V11C5.897 11 5 11.896 5 13V20C5 21.103 5.897 22 7 22H17C18.103 22 19 21.103 19 20V13C19 11.896 18.103 11 17 11ZM12 18C11.172 18 10.5 17.328 10.5 16.5C10.5 15.672 11.172 15 12 15C12.828 15 13.5 15.672 13.5 16.5C13.5 17.328 12.828 18 12 18ZM15 11H9V7C9 5.346 10.346 4 12 4C13.654 4 15 5.346 15 7V11Z" /> </svg> ), @@ -266,7 +266,7 @@ export default definePlugin({ aria-hidden={true} role="img" > - <path className="shc-evenodd-fill-current-color " d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> + <path className="shc-evenodd-fill-current-color" d="m19.8 22.6-4.2-4.15q-.875.275-1.762.413Q12.95 19 12 19q-3.775 0-6.725-2.087Q2.325 14.825 1 11.5q.525-1.325 1.325-2.463Q3.125 7.9 4.15 7L1.4 4.2l1.4-1.4 18.4 18.4ZM12 16q.275 0 .512-.025.238-.025.513-.1l-5.4-5.4q-.075.275-.1.513-.025.237-.025.512 0 1.875 1.312 3.188Q10.125 16 12 16Zm7.3.45-3.175-3.15q.175-.425.275-.862.1-.438.1-.938 0-1.875-1.312-3.188Q13.875 7 12 7q-.5 0-.938.1-.437.1-.862.3L7.65 4.85q1.025-.425 2.1-.638Q10.825 4 12 4q3.775 0 6.725 2.087Q21.675 8.175 23 11.5q-.575 1.475-1.512 2.738Q20.55 15.5 19.3 16.45Zm-4.625-4.6-3-3q.7-.125 1.288.112.587.238 1.012.688.425.45.613 1.038.187.587.087 1.162Z" /> </svg> )} </Tooltip> From 7b1d03699de7349fdd373b8418cbbc7936970a85 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 1 Feb 2023 14:13:55 +0100 Subject: [PATCH 088/114] ci(reporter): Ignore 404/429 errors --- test/generateReport.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/test/generateReport.ts b/test/generateReport.ts index a2b54a6dc..d55cc8abe 100644 --- a/test/generateReport.ts +++ b/test/generateReport.ts @@ -186,8 +186,11 @@ page.on("console", async e => { } else if (isDebug) { console.error(e.text()); } else if (level === "error") { - console.error("Got unexpected error", e.text()); - report.otherErrors.push(e.text()); + const text = e.text(); + if (!text.startsWith("Failed to load resource: the server responded with a status of")) { + console.error("Got unexpected error", text); + report.otherErrors.push(text); + } } }); @@ -209,6 +212,7 @@ function runTime(token: string) { // Monkey patch Logger to not log with custom css + // @ts-ignore Vencord.Util.Logger.prototype._log = function (level, levelColor, args) { if (level === "warn" || level === "error") console[level]("[Vencord]", this.name + ":", ...args); @@ -253,6 +257,8 @@ function runTime(token: string) { if (!isWasm) await wreq.e(id as any); + + await new Promise(r => setTimeout(r, 100)); } console.error("[PUP_DEBUG]", "Finished loading chunks!"); From 70278f64a96af32d69de25ae1cef38294a640158 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Wed, 1 Feb 2023 18:00:25 +0100 Subject: [PATCH 089/114] Fix broken patches --- src/plugins/apiMessagePopover.ts | 11 ++++++++--- src/plugins/reverseImageSearch.tsx | 23 ++++++++++++----------- 2 files changed, 20 insertions(+), 14 deletions(-) diff --git a/src/plugins/apiMessagePopover.ts b/src/plugins/apiMessagePopover.ts index e9d712a54..7e297f273 100644 --- a/src/plugins/apiMessagePopover.ts +++ b/src/plugins/apiMessagePopover.ts @@ -22,12 +22,17 @@ import definePlugin from "@utils/types"; export default definePlugin({ name: "MessagePopoverAPI", description: "API to add buttons to message popovers.", - authors: [Devs.KingFish], + authors: [Devs.KingFish, Devs.Ven], patches: [{ find: "Messages.MESSAGE_UTILITIES_A11Y_LABEL", replacement: { - match: /\?(?<makeButton>\i)\(.{1,35}\.Messages\.CONFIGURE.+?message:(?<message>\i).+?children:\[/, - replace: "$&...Vencord.Api.MessagePopover._buildPopoverElements($<message>,$<makeButton>)," + // foo && !bar ? createElement(blah,...makeElement(addReactionData)) + match: /(\i&&!\i)\?\(0,\i\.jsxs?\)\(.{0,20}renderPopout:.{0,300}?(\i)\(.{3,20}\{key:"add-reaction".+?\}/, + replace: (m, bools, makeElement) => { + const msg = m.match(/message:(.{1,3}),/)?.[1]; + if (!msg) throw new Error("Could not find message variable"); + return `...(${bools}?Vencord.Api.MessagePopover._buildPopoverElements(${msg},${makeElement}):[]),${m}`; + } } }], }); diff --git a/src/plugins/reverseImageSearch.tsx b/src/plugins/reverseImageSearch.tsx index 8de9de783..4d9f040b1 100644 --- a/src/plugins/reverseImageSearch.tsx +++ b/src/plugins/reverseImageSearch.tsx @@ -43,17 +43,18 @@ export default definePlugin({ } }, { // pass the target to the open link menu so we can check if it's an image - find: "REMOVE_ALL_REACTIONS_CONFIRM_BODY,", - replacement: { - // url1 = url2 = props.attachment.url - // ... - // OpenLinks(url2 != null ? url2 : url1, someStuffs) - // - // the back references are needed because the code is like Z(a!=null?b:c,d), no way to match that - // otherwise - match: /(?<props>.).onHeightUpdate.{0,200}(.)=(.)=.\.url;.+?\(null!=\3\?\3:\2[^)]+/, - replace: "$&,$<props>.target" - } + find: ".Messages.MESSAGE_ACTIONS_MENU_LABEL", + replacement: [ + { + match: /ariaLabel:\i\.Z\.Messages\.MESSAGE_ACTIONS_MENU_LABEL/, + replace: "$&,_vencordTarget:arguments[0].target" + }, + { + // var f = props.itemHref, .... MakeNativeMenu(null != f ? f : blah) + match: /(\i)=\i\.itemHref,.+?\(null!=\1\?\1:.{1,10}(?=\))/, + replace: "$&,arguments[0]._vencordTarget" + } + ] }], makeMenu(src: string, target: HTMLElement) { From 8a5218937838a9bc7e40304b885af2a30adfbff1 Mon Sep 17 00:00:00 2001 From: cryptofyre <cryptofyre@cryptofyre.org> Date: Wed, 8 Feb 2023 14:48:12 -0600 Subject: [PATCH 090/114] feat(plugin): richerCider (#471) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/richerCider.tsx | 67 +++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 src/plugins/richerCider.tsx diff --git a/src/plugins/richerCider.tsx b/src/plugins/richerCider.tsx new file mode 100644 index 000000000..08b0096e9 --- /dev/null +++ b/src/plugins/richerCider.tsx @@ -0,0 +1,67 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 OpenAsar + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { Link } from "@components/Link"; +import definePlugin from "@utils/types"; +import { Forms } from "@webpack/common"; +const appIds = [ + "911790844204437504", + "886578863147192350", + "1020414178047041627", + "1032800329332445255" +]; +export default definePlugin({ + name: "richerCider", + description: "Enhances Cider (More details in info button) by adding the \"Listening to\" type prefix to the user's rich presence when an applicable ID is found.", + authors: [{ + id: 191621342473224192n, + name: "cryptofyre", + }], + patches: [ + { + find: '.displayName="LocalActivityStore"', + replacement: { + match: /LOCAL_ACTIVITY_UPDATE:function\((\i)\)\{/, + replace: "$&$self.patchActivity($1.activity);", + } + } + ], + settingsAboutComponent: () => ( + <> + <Forms.FormTitle tag="h3">Install Cider to use this Plugin</Forms.FormTitle> + <Forms.FormText> + <Link href="https://cider.sh">Follow the link to our website</Link> to get Cider up and running, and then enable the plugin. + </Forms.FormText> + <br></br> + <Forms.FormTitle tag="h3">What is Cider?</Forms.FormTitle> + <Forms.FormText> + Cider is an open-source and community oriented Apple Music client for Windows, macOS, and Linux. + </Forms.FormText> + <br></br> + <Forms.FormTitle tag="h3">Recommended Optional Plugins</Forms.FormTitle> + <Forms.FormText> + I'd recommend using TimeBarAllActivities alongside this plugin to give off a much better visual to the eye (Keep in mind this only affects your client and will not show for other users) + </Forms.FormText> + </> + ), + patchActivity(activity: any) { + if (appIds.includes(activity.application_id)) { + activity.type = 2; /* LISTENING type */ + } + }, +}); From 291f38115c40c209c88ce509d5163375b34d04a9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 8 Feb 2023 17:48:26 -0300 Subject: [PATCH 091/114] New webpack filter: byDisplayName (#474) --- src/webpack/webpack.ts | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index 0bbd81506..e537740f9 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -50,6 +50,8 @@ export const filters = { } return true; }, + byDisplayName: (name: string): FilterFn => m => + m.constructor?.displayName === name }; export const subscriptions = new Map<FilterFn, CallbackFn>(); @@ -326,6 +328,20 @@ export function findByCodeLazy(...code: string[]) { return findLazy(filters.byCode(...code)); } +/** + * Find a store by its displayName + */ +export function findByDisplayName(name: string) { + return find(filters.byDisplayName(name)); +} + +/** + * findByDisplayName but lazy + */ +export function findByDisplayNameLazy(name: string) { + return findLazy(filters.byDisplayName(name)); +} + /** * Wait for a module that matches the provided filter to be registered, * then call the callback with the module as the first argument From 992a77e76cee25dd307a600f7c89d80b9b48f17f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 8 Feb 2023 17:54:11 -0300 Subject: [PATCH 092/114] ShowHiddenChannels: Stage and voice channels support (#469) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/apiNotices.ts | 4 +- .../components/HiddenChannelLockScreen.tsx | 265 +++++++++++------- src/plugins/showHiddenChannels/index.tsx | 120 +++++++- src/plugins/showHiddenChannels/style.css | 34 ++- src/plugins/typingTweaks.tsx | 12 +- src/utils/text.ts | 63 ++++- src/webpack/patchWebpack.ts | 6 +- 7 files changed, 362 insertions(+), 142 deletions(-) diff --git a/src/plugins/apiNotices.ts b/src/plugins/apiNotices.ts index c362f76db..8922aceed 100644 --- a/src/plugins/apiNotices.ts +++ b/src/plugins/apiNotices.ts @@ -34,8 +34,8 @@ export default definePlugin({ ";if(Vencord.Api.Notices.currentNotice)return false$&" }, { - match: /(?<=NOTICE_DISMISS:function.+?){(?=if\(null==(.+?)\))/, - replace: '{if($1?.id=="VencordNotice")return ($1=null,Vencord.Api.Notices.nextNotice(),true);' + match: /(?<=,NOTICE_DISMISS:function\(\i\){)(?=if\(null==(\i)\))/, + replace: 'if($1?.id=="VencordNotice")return($1=null,Vencord.Api.Notices.nextNotice(),true);' } ] } diff --git a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx index e5c5ee27e..7e66a6a1c 100644 --- a/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx +++ b/src/plugins/showHiddenChannels/components/HiddenChannelLockScreen.tsx @@ -18,18 +18,17 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { LazyComponent } from "@utils/misc"; -import { proxyLazy } from "@utils/proxyLazy"; import { formatDuration } from "@utils/text"; -import { find, findByCode, findByPropsLazy, findLazy } from "@webpack"; -import { moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; +import { find, findByCode, findByPropsLazy } from "@webpack"; +import { FluxDispatcher, GuildMemberStore, GuildStore, moment, Parser, SnowflakeUtils, Text, Timestamp, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; -enum SortOrderTypesTyping { +enum SortOrderTypes { LATEST_ACTIVITY = 0, CREATION_DATE = 1 } -enum ForumLayoutTypesTyping { +enum ForumLayoutTypes { DEFAULT = 0, LIST = 1, GRID = 2 @@ -50,18 +49,31 @@ interface Tag { interface ExtendedChannel extends Channel { defaultThreadRateLimitPerUser?: number; - defaultSortOrder?: SortOrderTypesTyping | null; - defaultForumLayout?: ForumLayoutTypesTyping; + defaultSortOrder?: SortOrderTypes | null; + defaultForumLayout?: ForumLayoutTypes; defaultReactionEmoji?: DefaultReaction | null; availableTags?: Array<Tag>; } -const ChatClasses = findByPropsLazy("chat", "chatContent"); -const TagClasses = findLazy(m => typeof m.tags === "string" && Object.entries(m).length === 1); // Object exported with a single key called tags -const ChannelTypes = findByPropsLazy("GUILD_TEXT", "GUILD_FORUM"); -const SortOrderTypes = findLazy(m => typeof m.LATEST_ACTIVITY === "number"); -const ForumLayoutTypes = findLazy(m => typeof m.LIST === "number"); -const ChannelFlags = findLazy(m => typeof m.REQUIRE_TAG === "number"); +enum ChannelTypes { + GUILD_TEXT = 0, + GUILD_VOICE = 2, + GUILD_ANNOUNCEMENT = 5, + GUILD_STAGE_VOICE = 13, + GUILD_FORUM = 15 +} + +enum VideoQualityModes { + AUTO = 1, + FULL = 2 +} + +enum ChannelFlags { + PINNED = 1 << 1, + REQUIRE_TAG = 1 << 4 +} + +const ChatScrollClasses = findByPropsLazy("auto", "content", "scrollerBase"); const TagComponent = LazyComponent(() => find(m => { if (typeof m !== "function") return false; @@ -70,23 +82,32 @@ const TagComponent = LazyComponent(() => find(m => { return code.includes(".Messages.FORUM_TAG_A11Y_FILTER_BY_TAG") && !code.includes("increasedActivityPill"); })); const EmojiComponent = LazyComponent(() => findByCode('.jumboable?"jumbo":"default"')); +// The component for the beggining of a channel, but we patched it so it only returns the allowed users and roles components for hidden channels +const ChannelBeginHeader = LazyComponent(() => findByCode(".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE")); -const ChannelTypesToChannelNames = proxyLazy(() => ({ +const ChannelTypesToChannelNames = { [ChannelTypes.GUILD_TEXT]: "text", [ChannelTypes.GUILD_ANNOUNCEMENT]: "announcement", - [ChannelTypes.GUILD_FORUM]: "forum" -})); + [ChannelTypes.GUILD_FORUM]: "forum", + [ChannelTypes.GUILD_VOICE]: "voice", + [ChannelTypes.GUILD_STAGE_VOICE]: "stage" +}; -const SortOrderTypesToNames = proxyLazy(() => ({ +const SortOrderTypesToNames = { [SortOrderTypes.LATEST_ACTIVITY]: "Latest activity", [SortOrderTypes.CREATION_DATE]: "Creation date" -})); +}; -const ForumLayoutTypesToNames = proxyLazy(() => ({ +const ForumLayoutTypesToNames = { [ForumLayoutTypes.DEFAULT]: "Not set", [ForumLayoutTypes.LIST]: "List view", [ForumLayoutTypes.GRID]: "Gallery view" -})); +}; + +const VideoQualityModesToNames = { + [VideoQualityModes.AUTO]: "Automatic", + [VideoQualityModes.FULL]: "720p" +}; // Icon from the modal when clicking a message link you don't have access to view const HiddenChannelLogo = "/assets/433e3ec4319a9d11b0cbe39342614982.svg"; @@ -104,97 +125,137 @@ function HiddenChannelLockScreen({ channel }: { channel: ExtendedChannel; }) { rateLimitPerUser, defaultThreadRateLimitPerUser, defaultSortOrder, - defaultReactionEmoji + defaultReactionEmoji, + bitrate, + rtcRegion, + videoQualityMode, + permissionOverwrites } = channel; + const membersToFetch: Array<string> = []; + + const guildOwnerId = GuildStore.getGuild(channel.guild_id).ownerId; + if (!GuildMemberStore.getMember(channel.guild_id, guildOwnerId)) membersToFetch.push(guildOwnerId); + + Object.values(permissionOverwrites).forEach(({ type, id: userId }) => { + if (type === 1) { + if (!GuildMemberStore.getMember(channel.guild_id, userId)) membersToFetch.push(userId); + } + }); + + if (membersToFetch.length > 0) { + FluxDispatcher.dispatch({ + type: "GUILD_MEMBERS_REQUEST", + guildIds: [channel.guild_id], + userIds: membersToFetch + }); + } + return ( - <div className={ChatClasses.chat + " " + "shc-lock-screen-container"}> - <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> + <div className={ChatScrollClasses.auto + " " + "shc-lock-screen-outer-container"}> + <div className="shc-lock-screen-container"> + <img className="shc-lock-screen-logo" src={HiddenChannelLogo} /> - <div className="shc-lock-screen-heading-container"> - <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> - {channel.isNSFW() && - <Tooltip text="NSFW"> - {({ onMouseLeave, onMouseEnter }) => ( - <svg - onMouseLeave={onMouseLeave} - onMouseEnter={onMouseEnter} - className="shc-lock-screen-heading-nsfw-icon" - width="32" - height="32" - viewBox="0 0 48 48" - aria-hidden={true} - role="img" - > - <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> - </svg> - )} - </Tooltip> - } - </div> - - <Text variant="text-lg/normal"> - You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. - {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} - </Text > - - {channel.isForumChannel() && topic && topic.length > 0 && ( - <div className="shc-lock-screen-topic-container"> - {Parser.parseTopic(topic, false, { channelId })} + <div className="shc-lock-screen-heading-container"> + <Text variant="heading-xxl/bold">This is a hidden {ChannelTypesToChannelNames[type]} channel.</Text> + {channel.isNSFW() && + <Tooltip text="NSFW"> + {({ onMouseLeave, onMouseEnter }) => ( + <svg + onMouseLeave={onMouseLeave} + onMouseEnter={onMouseEnter} + className="shc-lock-screen-heading-nsfw-icon" + width="32" + height="32" + viewBox="0 0 48 48" + aria-hidden={true} + role="img" + > + <path d="M.7 43.05 24 2.85l23.3 40.2Zm23.55-6.25q.75 0 1.275-.525.525-.525.525-1.275 0-.75-.525-1.3t-1.275-.55q-.8 0-1.325.55-.525.55-.525 1.3t.55 1.275q.55.525 1.3.525Zm-1.85-6.1h3.65V19.4H22.4Z" /> + </svg> + )} + </Tooltip> + } </div> - )} - {lastMessageId && - <Text variant="text-md/normal"> - Last {channel.isForumChannel() ? "post" : "message"} created: - <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> - </Text> - } + {(!channel.isGuildVoice() && !channel.isGuildStageVoice()) && ( + <Text variant="text-lg/normal"> + You can not see the {channel.isForumChannel() ? "posts" : "messages"} of this channel. + {channel.isForumChannel() && topic && topic.length > 0 && "However you may see its guidelines:"} + </Text > + )} - {lastPinTimestamp && - <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> - } - {(rateLimitPerUser ?? 0) > 0 && - <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser! * 1000)}</Text> - } - {(defaultThreadRateLimitPerUser ?? 0) > 0 && - <Text variant="text-md/normal"> - Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser! * 1000)} - </Text> - } - {(defaultAutoArchiveDuration ?? 0) > 0 && - <Text variant="text-md/normal"> - Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: - {formatDuration(defaultAutoArchiveDuration! * 1000 * 60)} - </Text> - } - {defaultForumLayout != null && - <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> - } - {defaultSortOrder != null && - <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> - } - {defaultReactionEmoji != null && - <div className="shc-lock-screen-default-emoji-container"> - <Text variant="text-md/normal">Default reaction emoji:</Text> - <EmojiComponent node={{ - type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", - name: defaultReactionEmoji.emojiName ?? "", - emojiId: defaultReactionEmoji.emojiId - }} /> - </div> - } - {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && - <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> - } - {availableTags && availableTags.length > 0 && - <div className="shc-lock-screen-tags-container"> - <Text variant="text-lg/bold">Available tags:</Text> - <div className={TagClasses.tags}> - {availableTags.map(tag => <TagComponent tag={tag} />)} + {channel.isForumChannel() && topic && topic.length > 0 && ( + <div className="shc-lock-screen-topic-container"> + {Parser.parseTopic(topic, false, { channelId })} </div> + )} + + {lastMessageId && + <Text variant="text-md/normal"> + Last {channel.isForumChannel() ? "post" : "message"} created: + <Timestamp timestamp={moment(SnowflakeUtils.extractTimestamp(lastMessageId))} /> + </Text> + } + + {lastPinTimestamp && + <Text variant="text-md/normal">Last message pin: <Timestamp timestamp={moment(lastPinTimestamp)} /></Text> + } + {(rateLimitPerUser ?? 0) > 0 && + <Text variant="text-md/normal">Slowmode: {formatDuration(rateLimitPerUser!, "seconds")}</Text> + } + {(defaultThreadRateLimitPerUser ?? 0) > 0 && + <Text variant="text-md/normal"> + Default thread slowmode: {formatDuration(defaultThreadRateLimitPerUser!, "seconds")} + </Text> + } + {((channel.isGuildVoice() || channel.isGuildStageVoice()) && bitrate != null) && + <Text variant="text-md/normal">Bitrate: {bitrate} bits</Text> + } + {rtcRegion !== undefined && + <Text variant="text-md/normal">Region: {rtcRegion ?? "Automatic"}</Text> + } + {(channel.isGuildVoice() || channel.isGuildStageVoice()) && + <Text variant="text-md/normal">Video quality mode: {VideoQualityModesToNames[videoQualityMode ?? VideoQualityModes.AUTO]}</Text> + } + {(defaultAutoArchiveDuration ?? 0) > 0 && + <Text variant="text-md/normal"> + Default inactivity duration before archiving {channel.isForumChannel() ? "posts" : "threads"}: + {" " + formatDuration(defaultAutoArchiveDuration!, "minutes")} + </Text> + } + {defaultForumLayout != null && + <Text variant="text-md/normal">Default layout: {ForumLayoutTypesToNames[defaultForumLayout]}</Text> + } + {defaultSortOrder != null && + <Text variant="text-md/normal">Default sort order: {SortOrderTypesToNames[defaultSortOrder]}</Text> + } + {defaultReactionEmoji != null && + <div className="shc-lock-screen-default-emoji-container"> + <Text variant="text-md/normal">Default reaction emoji:</Text> + <EmojiComponent node={{ + type: defaultReactionEmoji.emojiName ? "emoji" : "customEmoji", + name: defaultReactionEmoji.emojiName ?? "", + emojiId: defaultReactionEmoji.emojiId + }} /> + </div> + } + {channel.hasFlag(ChannelFlags.REQUIRE_TAG) && + <Text variant="text-md/normal">Posts on this forum require a tag to be set.</Text> + } + {availableTags && availableTags.length > 0 && + <div className="shc-lock-screen-tags-container"> + <Text variant="text-lg/bold">Available tags:</Text> + <div className="shc-lock-screen-tags"> + {availableTags.map(tag => <TagComponent tag={tag} />)} + </div> + </div> + } + <div className="shc-lock-screen-allowed-users-and-roles-container"> + <Text variant="text-lg/bold">Allowed users and roles:</Text> + <ChannelBeginHeader channel={channel} /> </div> - } + </div> </div> ); } diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index 9a448d5a2..a4c7decd9 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -22,14 +22,15 @@ import { definePluginSettings } from "@api/settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findLazy } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { ChannelStore, PermissionStore, Tooltip } from "@webpack/common"; import { Channel } from "discord-types/general"; import HiddenChannelLockScreen from "./components/HiddenChannelLockScreen"; const ChannelListClasses = findByPropsLazy("channelName", "subtitle", "modeMuted", "iconContainer"); -const Permissions = findLazy(m => typeof m.VIEW_CHANNEL === "bigint"); + +const VIEW_CHANNEL = 1n << 10n; enum ShowMode { LockIcon, @@ -89,14 +90,29 @@ export default definePlugin({ } ] }, + { + find: "VoiceChannel, transitionTo: Channel does not have a guildId", + replacement: [ + { + // Do not show confirmation to join a voice channel when already connected to another if clicking on a hidden voice channel + match: /(?<=getCurrentClientVoiceChannelId\(\i\.guild_id\);if\()(?=.+?\((?<channel>\i)\))/, + replace: "!$self.isHiddenChannel($<channel>)&&" + }, + { + // Make Discord think we are connected to a voice channel so it shows us inside it + match: /(?=\|\|\i\.default\.selectVoiceChannel\((?<channel>\i)\.id\))/, + replace: "||$self.isHiddenChannel($<channel>)" + }, + { + // Make Discord think we are connected to a voice channel so it shows us inside it + match: /(?<=\|\|\i\.default\.selectVoiceChannel\((?<channel>\i)\.id\);!__OVERLAY__&&\()/, + replace: "$self.isHiddenChannel($<channel>)||" + } + ] + }, { find: "VoiceChannel.renderPopout: There must always be something to render", replacement: [ - // Do nothing when trying to join a voice channel if the channel is hidden - { - match: /(?<=handleClick=function\(\){)(?=.{1,80}(?<this>\i)\.handleVoiceConnect\(\))/, - replace: "if($self.isHiddenChannel($<this>.props.channel))return;" - }, // Render null instead of the buttons if the channel is hidden ...[ "renderEditButton", @@ -120,11 +136,11 @@ export default definePlugin({ { find: ".UNREAD_HIGHLIGHT", predicate: () => settings.store.hideUnreads === true, - replacement: [{ + replacement: { // Hide unreads match: /(?<=\i\.connected,\i=)(?=(?<props>\i)\.unread)/, replace: "$self.isHiddenChannel($<props>.channel)?false:" - }] + } }, { find: ".UNREAD_HIGHLIGHT", @@ -160,7 +176,7 @@ export default definePlugin({ // Hide New unreads box for hidden channels find: '.displayName="ChannelListUnreadsStore"', replacement: { - match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/, + match: /(?<=return null!=(?<channel>\i))(?=.{1,130}hasRelevantUnread\(\i\))/g, // Global because Discord has multiple methods like that in the same module replace: "&&!$self.isHiddenChannel($<channel>)" } }, @@ -191,7 +207,7 @@ export default definePlugin({ { match: /(?<=renderChat=function\(\){)/, replace: "if($self.isHiddenChannel(this.props.channel))return $self.HiddenChannelLockScreen(this.props.channel);" - }, + } ] }, // Avoid trying to fetch messages from hidden channels @@ -226,6 +242,86 @@ export default definePlugin({ match: /(?<=\i:\(\)=>\i)(?=}.+?(?<component>\i)=function.{1,20}node,\i=\i.isInteracting)/, replace: ",hc1:()=>$<component>" // Blame Ven length check for the small name :pensive_cry: } + }, + { + find: ".Messages.ROLE_REQUIRED_SINGLE_USER_MESSAGE", + replacement: [ + { + // Export the channel beggining header + match: /(?<=\i:\(\)=>\i)(?=}.+?function (?<component>\i).{1,600}computePermissionsForRoles)/, + replace: ",hc2:()=>$<component>" + }, + { + // Patch the header to only return allowed users and roles if it's a hidden channel (Like when it's used on the HiddenChannelLockScreen) + match: /(?<=MANAGE_ROLES.{1,60}return)(?=\(.+?(?<component>\(0,\i\.jsxs\)\("div",{className:\i\(\)\.members.+?guildId:(?<channel>\i)\.guild_id.+?roleColor.+?]}\)))/, + replace: " $self.isHiddenChannel($<channel>)?$<component>:" + } + ] + }, + { + find: ".Messages.SHOW_CHAT", + replacement: [ + { + // Remove the divider and the open chat button for the HiddenChannelLockScreen + match: /(?<=function \i\((?<props>\i)\).{1,1800}"more-options-popout"\)\);if\()/, + replace: "(!$self.isHiddenChannel($<props>.channel)||$<props>.inCall)&&" + }, + { + // Render our HiddenChannelLockScreen component instead of the main voice channel component + match: /(?<=renderContent=function.{1,1700}children:)/, + replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?$self.HiddenChannelLockScreen(this.props.channel):" + }, + { + // Disable gradients for the HiddenChannelLockScreen of voice channels + match: /(?<=renderContent=function.{1,1600}disableGradients:)/, + replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)||" + }, + { + // Disable useless components for the HiddenChannelLockScreen of voice channels + match: /(?<=renderContent=function.{1,800}render(?!Header).{0,30}:)(?!void)/g, + replace: "!this.props.inCall&&$self.isHiddenChannel(this.props.channel)?null:" + } + ] + }, + { + find: "Guild voice channel without guild id.", + replacement: [ + { + // Render our HiddenChannelLockScreen component instead of the main stage channel component + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,1400}children:)(?=.{1,20}}\)}function)/, + replace: "$self.isHiddenChannel($<channel>)?$self.HiddenChannelLockScreen($<channel>):" + }, + { + // Disable useless components for the HiddenChannelLockScreen of stage channels + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,1000}render(?!Header).{0,30}:)/g, + replace: "$self.isHiddenChannel($<channel>)?null:" + }, + // Prevent Discord from replacing our route if we aren't connected to the stage channel + { + match: /(?<=if\()(?=!\i&&!\i&&!\i.{1,80}(?<channel>\i)\.getGuildId\(\).{1,50}Guild voice channel without guild id\.)/, + replace: "!$self.isHiddenChannel($<channel>)&&" + }, + { + // Disable gradients for the HiddenChannelLockScreen of stage channels + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,600}disableGradients:)/, + replace: "$self.isHiddenChannel($<channel>)||" + }, + { + // Disable strange styles applied to the header for the HiddenChannelLockScreen of stage channels + match: /(?<=(?<channel>\i)\.getGuildId\(\).{1,30}Guild voice channel without guild id\..{1,600}style:)/, + replace: "$self.isHiddenChannel($<channel>)?undefined:" + }, + { + // Remove the divider and amount of users in stage channel components for the HiddenChannelLockScreen + match: /\(0,\i\.jsx\)\(\i\.\i\.Divider.+?}\)]}\)(?=.+?:(?<channel>\i)\.guild_id)/, + replace: "$self.isHiddenChannel($<channel>)?null:($&)" + }, + { + // Remove the open chat button for the HiddenChannelLockScreen + match: /(?<=null,)(?=.{1,120}channelId:(?<channel>\i)\.id,.+?toggleRequestToSpeakSidebar:\i,iconClassName:\i\(\)\.buttonIcon)/, + replace: "!$self.isHiddenChannel($<channel>)&&" + } + ], } ], @@ -235,7 +331,7 @@ export default definePlugin({ if (channel.channelId) channel = ChannelStore.getChannel(channel.channelId); if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return false; - return !PermissionStore.can(Permissions.VIEW_CHANNEL, channel); + return !PermissionStore.can(VIEW_CHANNEL, channel); }, HiddenChannelLockScreen: (channel: any) => <HiddenChannelLockScreen channel={channel} />, diff --git a/src/plugins/showHiddenChannels/style.css b/src/plugins/showHiddenChannels/style.css index 73957efce..0f85b5084 100644 --- a/src/plugins/showHiddenChannels/style.css +++ b/src/plugins/showHiddenChannels/style.css @@ -1,9 +1,18 @@ +.shc-lock-screen-outer-container { + background-color: var(--background-primary); + overflow: hidden scroll; + flex: 1 1 auto; + height: 100%; + width: 100%; +} + .shc-lock-screen-container { display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; + min-height: 100%; } .shc-lock-screen-container > * { @@ -34,14 +43,14 @@ color: var(--text-normal); background-color: var(--background-secondary); border-radius: 5px; - padding: 5px; + padding: 10px; max-width: 70vw; } .shc-lock-screen-tags-container { background-color: var(--background-secondary); border-radius: 5px; - padding: 5px; + padding: 10px; max-width: 70vw; } @@ -49,8 +58,12 @@ margin: inherit; } -.shc-lock-screen-tags-container > [class^="tags"] { +.shc-lock-screen-tags { + display: flex; + align-items: center; + justify-content: center; flex-wrap: wrap; + gap: 8px; } .shc-evenodd-fill-current-color { @@ -76,3 +89,18 @@ padding: 3px 4px; margin-left: 5px; } + +.shc-lock-screen-allowed-users-and-roles-container { + display: flex; + flex-direction: column; + align-items: center; + background-color: var(--background-secondary); + border-radius: 5px; + padding: 10px; + max-width: 70vw; +} + +.shc-lock-screen-allowed-users-and-roles-container > [class^="members"] { + margin-left: 10px; + flex-wrap: wrap; +} diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx index 96c04b909..f03779c07 100644 --- a/src/plugins/typingTweaks.tsx +++ b/src/plugins/typingTweaks.tsx @@ -21,7 +21,7 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy } from "@webpack"; -import { GuildMemberStore, React } from "@webpack/common"; +import { GuildMemberStore, React, RelationshipStore } from "@webpack/common"; const Avatar = findByCodeLazy(".Positions.TOP,spacing:"); @@ -76,8 +76,8 @@ export default definePlugin({ { find: ",\"SEVERAL_USERS_TYPING\",\"", replacement: { - match: /(\i)\((\i),("SEVERAL_USERS_TYPING"),".+?"\)/, - replace: "$1($2,$3,\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\")" + match: /(?<="SEVERAL_USERS_TYPING",)".+?"/, + replace: '"**!!{a}!!**, **!!{b}!!**, and {c} others are typing..."' }, predicate: () => settings.store.alternativeFormatting }, @@ -98,7 +98,7 @@ export default definePlugin({ let element = 0; - return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]}/> : c); + return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]} /> : c); }, TypingUser: ErrorBoundary.wrap(({ user, guildId }) => { @@ -111,9 +111,9 @@ export default definePlugin({ {settings.store.showAvatars && <div style={{ marginTop: "4px" }}> <Avatar size={Avatar.Sizes.SIZE_16} - src={user.getAvatarURL(guildId, 128)}/> + src={user.getAvatarURL(guildId, 128)} /> </div>} - {GuildMemberStore.getNick(guildId!, user.id) || user.username} + {GuildMemberStore.getNick(guildId!, user.id) || !guildId && RelationshipStore.getNickname(user.id) || user.username} </strong>; }, { noop: true }) }); diff --git a/src/utils/text.ts b/src/utils/text.ts index fae33436c..115b3e2ac 100644 --- a/src/utils/text.ts +++ b/src/utils/text.ts @@ -37,25 +37,58 @@ export const wordsToPascal = (words: string[]) => export const wordsToTitle = (words: string[]) => words.map(w => w[0].toUpperCase() + w.slice(1)).join(" "); +const units = ["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const; +type Units = typeof units[number]; + +function getUnitStr(unit: Units, isOne: boolean, short: boolean) { + if (short === false) return isOne ? unit.slice(0, -1) : unit; + + return unit[0]; +} + /** - * Forms milliseconds into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" - * @param ms Milliseconds + * Forms time into a human readable string link "1 day, 2 hours, 3 minutes and 4 seconds" + * @param time The time on the specified unit + * @param unit The unit the time is on * @param short Whether to use short units like "d" instead of "days" */ -export function formatDuration(ms: number, short: boolean = false) { - const dur = moment.duration(ms); - return (["years", "months", "weeks", "days", "hours", "minutes", "seconds"] as const).reduce((res, unit) => { - const x = dur[unit](); - if (x > 0 || res.length) { - if (res.length) - res += unit === "seconds" ? " and " : ", "; +export function formatDuration(time: number, unit: Units, short: boolean = false) { + const dur = moment.duration(time, unit); - const unitStr = short - ? unit[0] - : x === 1 ? unit.slice(0, -1) : unit; + let unitsAmounts = units.map(unit => ({ amount: dur[unit](), unit })); - res += `${x} ${unitStr}`; + let amountsToBeRemoved = 0; + + outer: + for (let i = 0; i < unitsAmounts.length; i++) { + if (unitsAmounts[i].amount === 0 || !(i + 1 < unitsAmounts.length)) continue; + for (let v = i + 1; v < unitsAmounts.length; v++) { + if (unitsAmounts[v].amount !== 0) continue outer; } - return res; - }, "").replace(/((,|and) \b0 \w+)+$/, "") || "now"; + + amountsToBeRemoved = unitsAmounts.length - (i + 1); + } + unitsAmounts = amountsToBeRemoved === 0 ? unitsAmounts : unitsAmounts.slice(0, -amountsToBeRemoved); + + const daysAmountIndex = unitsAmounts.findIndex(({ unit }) => unit === "days"); + if (daysAmountIndex !== -1) { + const daysAmount = unitsAmounts[daysAmountIndex]; + + const daysMod = daysAmount.amount % 7; + if (daysMod === 0) unitsAmounts.splice(daysAmountIndex, 1); + else daysAmount.amount = daysMod; + } + + let res: string = ""; + while (unitsAmounts.length) { + const { amount, unit } = unitsAmounts.shift()!; + + if (res.length) res += unitsAmounts.length ? ", " : " and "; + + if (amount > 0 || res.length) { + res += `${amount} ${getUnitStr(unit, amount === 1, short)}`; + } + } + + return res.length ? res : `0 ${getUnitStr(unit, false, short)}`; } diff --git a/src/webpack/patchWebpack.ts b/src/webpack/patchWebpack.ts index 7b318b218..19ca9517b 100644 --- a/src/webpack/patchWebpack.ts +++ b/src/webpack/patchWebpack.ts @@ -21,6 +21,7 @@ import Logger from "@utils/Logger"; import { canonicalizeReplacement } from "@utils/patches"; import { PatchReplacement } from "@utils/types"; +import { traceFunction } from "../debug/Tracer"; import { _initWebpack } from "."; let webpackChunk: any[]; @@ -132,6 +133,7 @@ function patchPush() { for (let i = 0; i < patches.length; i++) { const patch = patches[i]; + const executePatch = traceFunction(`patch by ${patch.plugin}`, (match: string | RegExp, replace: string) => code.replace(match, replace)); if (patch.predicate && !patch.predicate()) continue; if (code.includes(patch.find)) { @@ -146,7 +148,7 @@ function patchPush() { canonicalizeReplacement(replacement, patch.plugin); try { - const newCode = code.replace(replacement.match, replacement.replace as string); + const newCode = executePatch(replacement.match, replacement.replace as string); if (newCode === code && !patch.noWarn) { logger.warn(`Patch by ${patch.plugin} had no effect (Module id is ${id}): ${replacement.match}`); if (IS_DEV) { @@ -187,7 +189,7 @@ function patchPush() { } logger.errorCustomFmt(...Logger.makeTitle("white", "Before"), context); - logger.errorCustomFmt(...Logger.makeTitle("white", "After"), context); + logger.errorCustomFmt(...Logger.makeTitle("white", "After"), patchedContext); const [titleFmt, ...titleElements] = Logger.makeTitle("white", "Diff"); logger.errorCustomFmt(titleFmt + fmt, ...titleElements, ...elements); } From ae98401bd350bd327142d2d07444ce62cce9238c Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 9 Feb 2023 19:36:30 +0100 Subject: [PATCH 093/114] Fix lag when alt tabbing to Discord --- src/components/PluginSettings/index.tsx | 2 +- .../messageLogger/deleteStyleOverlay.css | 3 +++ src/plugins/messageLogger/deleteStyleText.css | 3 +++ src/plugins/messageLogger/index.tsx | 18 +++++++++++------- src/plugins/messageLogger/messageLogger.css | 8 -------- 5 files changed, 18 insertions(+), 16 deletions(-) create mode 100644 src/plugins/messageLogger/deleteStyleOverlay.css create mode 100644 src/plugins/messageLogger/deleteStyleText.css diff --git a/src/components/PluginSettings/index.tsx b/src/components/PluginSettings/index.tsx index 34e6828f7..4e64eb87e 100644 --- a/src/components/PluginSettings/index.tsx +++ b/src/components/PluginSettings/index.tsx @@ -222,7 +222,7 @@ export default ErrorBoundary.wrap(function PluginSettings() { const onStatusChange = (status: SearchStatus) => setSearchValue(prev => ({ ...prev, status })); const pluginFilter = (plugin: typeof Plugins[keyof typeof Plugins]) => { - const enabled = settings.plugins[plugin.name]?.enabled || plugin.started; + const enabled = settings.plugins[plugin.name]?.enabled; if (enabled && searchValue.status === SearchStatus.DISABLED) return false; if (!enabled && searchValue.status === SearchStatus.ENABLED) return false; if (!searchValue.value.length) return true; diff --git a/src/plugins/messageLogger/deleteStyleOverlay.css b/src/plugins/messageLogger/deleteStyleOverlay.css new file mode 100644 index 000000000..a05ed4da5 --- /dev/null +++ b/src/plugins/messageLogger/deleteStyleOverlay.css @@ -0,0 +1,3 @@ +.messagelogger-deleted { + background-color: rgba(240 71 71 / 15%); +} diff --git a/src/plugins/messageLogger/deleteStyleText.css b/src/plugins/messageLogger/deleteStyleText.css new file mode 100644 index 000000000..9f2d731fc --- /dev/null +++ b/src/plugins/messageLogger/deleteStyleText.css @@ -0,0 +1,3 @@ +.messagelogger-deleted div { + color: #f04747; +} diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 8c897b6e3..ff4c328c1 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -19,19 +19,23 @@ import "./messageLogger.css"; import { Settings } from "@api/settings"; +import { disableStyle, enableStyle } from "@api/Styles"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import Logger from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { moment, Parser, Timestamp, UserStore } from "@webpack/common"; -function addDeleteStyleClass() { +import overlayStyle from "./deleteStyleOverlay.css?managed"; +import textStyle from "./deleteStyleText.css?managed"; + +function addDeleteStyle() { if (Settings.plugins.MessageLogger.deleteStyle === "text") { - document.body.classList.remove("messagelogger-red-overlay"); - document.body.classList.add("messagelogger-red-text"); + enableStyle(textStyle); + disableStyle(overlayStyle); } else { - document.body.classList.remove("messagelogger-red-text"); - document.body.classList.add("messagelogger-red-overlay"); + disableStyle(textStyle); + enableStyle(overlayStyle); } } @@ -41,7 +45,7 @@ export default definePlugin({ authors: [Devs.rushii, Devs.Ven], start() { - addDeleteStyleClass(); + addDeleteStyle(); }, stop() { @@ -84,7 +88,7 @@ export default definePlugin({ { label: "Red text", value: "text", default: true }, { label: "Red overlay", value: "overlay" } ], - onChange: () => addDeleteStyleClass() + onChange: () => addDeleteStyle() }, ignoreBots: { type: OptionType.BOOLEAN, diff --git a/src/plugins/messageLogger/messageLogger.css b/src/plugins/messageLogger/messageLogger.css index 925a09c59..f93f33088 100644 --- a/src/plugins/messageLogger/messageLogger.css +++ b/src/plugins/messageLogger/messageLogger.css @@ -1,11 +1,3 @@ -.messagelogger-red-overlay .messagelogger-deleted { - background-color: rgba(240 71 71 / 15%); -} - -.messagelogger-red-text .messagelogger-deleted div { - color: #f04747; -} - .messagelogger-deleted [class^="buttons"] { display: none; } From 6114bc6b168607613d06ae8fb4f29e74c763a417 Mon Sep 17 00:00:00 2001 From: Justice Almanzar <superdash993@gmail.com> Date: Thu, 9 Feb 2023 15:21:14 -0500 Subject: [PATCH 094/114] make proxies enumerable (#476) Co-authored-by: Ven <vendicated@riseup.net> --- src/utils/proxyLazy.ts | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/utils/proxyLazy.ts b/src/utils/proxyLazy.ts index 42b5a91d3..b1fca6e77 100644 --- a/src/utils/proxyLazy.ts +++ b/src/utils/proxyLazy.ts @@ -22,6 +22,9 @@ const unconfigurable = ["arguments", "caller", "prototype"]; const handler: ProxyHandler<any> = {}; +const GET_KEY = Symbol.for("vencord.lazy.get"); +const CACHED_KEY = Symbol.for("vencord.lazy.cached"); + for (const method of [ "apply", "construct", @@ -38,11 +41,11 @@ for (const method of [ "setPrototypeOf" ]) { handler[method] = - (target: any, ...args: any[]) => Reflect[method](target.get(), ...args); + (target: any, ...args: any[]) => Reflect[method](target[GET_KEY](), ...args); } handler.ownKeys = target => { - const v = target.get(); + const v = target[GET_KEY](); const keys = Reflect.ownKeys(v); for (const key of unconfigurable) { if (!keys.includes(key)) keys.push(key); @@ -54,7 +57,10 @@ handler.getOwnPropertyDescriptor = (target, p) => { if (typeof p === "string" && unconfigurable.includes(p)) return Reflect.getOwnPropertyDescriptor(target, p); - return Reflect.getOwnPropertyDescriptor(target.get(), p); + const descriptor = Reflect.getOwnPropertyDescriptor(target[GET_KEY](), p); + + if (descriptor) Object.defineProperty(target, p, descriptor); + return descriptor; }; /** @@ -67,10 +73,10 @@ handler.getOwnPropertyDescriptor = (target, p) => { * @example const mod = proxyLazy(() => findByProps("blah")); console.log(mod.blah); */ export function proxyLazy<T>(factory: () => T): T { - const proxyDummy: { (): void; cachedValue?: T; get(): T; } = function () { }; - - proxyDummy.cachedValue = void 0; - proxyDummy.get = () => proxyDummy.cachedValue ??= factory(); + const proxyDummy: { (): void; [CACHED_KEY]?: T; [GET_KEY](): T; } = Object.assign(function () { }, { + [CACHED_KEY]: void 0, + [GET_KEY]: () => proxyDummy[CACHED_KEY] ??= factory(), + }); return new Proxy(proxyDummy, handler) as any; } From 1d995e58f515dbeb908ba34bf70f829bfd3ccfac Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Fri, 10 Feb 2023 22:33:34 +0100 Subject: [PATCH 095/114] Notification API (#467) Co-authored-by: Ven <vendicated@riseup.net> Co-authored-by: afn <hey@afn.lol> Co-authored-by: afn <afnzmn@gmail.com> --- .eslintrc.json | 1 - .../Notifications/NotificationComponent.tsx | 92 +++++++++++ src/api/Notifications/Notifications.tsx | 92 +++++++++++ src/api/Notifications/index.ts | 19 +++ src/api/Notifications/styles.css | 49 ++++++ src/api/index.ts | 5 + src/api/settings.ts | 14 +- src/components/VencordSettings/VencordTab.tsx | 149 ++++++++++++------ src/modules.d.ts | 3 +- .../spotifyControls/PlayerComponent.tsx | 12 +- src/utils/Queue.ts | 2 +- src/utils/index.ts | 1 + src/utils/margins.ts | 35 ++++ src/utils/misc.tsx | 4 + src/utils/settingsSync.ts | 1 - src/webpack/common/components.ts | 3 + src/webpack/common/internal.tsx | 6 +- src/webpack/common/react.ts | 2 +- src/webpack/common/stores.ts | 77 ++++++--- src/webpack/common/types/components.d.ts | 6 +- src/webpack/common/types/index.d.ts | 1 + src/webpack/common/types/stores.d.ts | 40 +++++ src/webpack/common/types/utils.d.ts | 14 -- src/webpack/common/utils.ts | 1 - src/webpack/webpack.ts | 10 +- 25 files changed, 533 insertions(+), 106 deletions(-) create mode 100644 src/api/Notifications/NotificationComponent.tsx create mode 100644 src/api/Notifications/Notifications.tsx create mode 100644 src/api/Notifications/index.ts create mode 100644 src/api/Notifications/styles.css create mode 100644 src/utils/margins.ts create mode 100644 src/webpack/common/types/stores.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index de17afa0f..aaaaaeb69 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -82,7 +82,6 @@ "no-constant-condition": ["error", { "checkLoops": false }], "no-duplicate-imports": "error", "no-extra-semi": "error", - "consistent-return": ["warn", { "treatUndefinedAsUnspecified": true }], "dot-notation": "error", "no-useless-escape": [ "error", diff --git a/src/api/Notifications/NotificationComponent.tsx b/src/api/Notifications/NotificationComponent.tsx new file mode 100644 index 000000000..65d4c433f --- /dev/null +++ b/src/api/Notifications/NotificationComponent.tsx @@ -0,0 +1,92 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import "./styles.css"; + +import { useSettings } from "@api/settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Forms, React, useEffect, useMemo, useState, useStateFromStores, WindowStore } from "@webpack/common"; + +import { NotificationData } from "./Notifications"; + +export default ErrorBoundary.wrap(function NotificationComponent({ + title, + body, + richBody, + color, + icon, + onClick, + onClose, + image +}: NotificationData) { + const { timeout, position } = useSettings(["notifications.timeout", "notifications.position"]).notifications; + const hasFocus = useStateFromStores([WindowStore], () => WindowStore.isFocused()); + + const [isHover, setIsHover] = useState(false); + const [elapsed, setElapsed] = useState(0); + + const start = useMemo(() => Date.now(), [timeout, isHover, hasFocus]); + + useEffect(() => { + if (isHover || !hasFocus || timeout === 0) return void setElapsed(0); + + const intervalId = setInterval(() => { + const elapsed = Date.now() - start; + if (elapsed >= timeout) + onClose!(); + else + setElapsed(elapsed); + }, 10); + + return () => clearInterval(intervalId); + }, [timeout, isHover, hasFocus]); + + const timeoutProgress = elapsed / timeout; + + return ( + <button + className="vc-notification-root" + style={position === "bottom-right" ? { bottom: "1rem" } : { top: "3rem" }} + onClick={onClick} + onContextMenu={e => { + e.preventDefault(); + e.stopPropagation(); + onClose!(); + }} + onMouseEnter={() => setIsHover(true)} + onMouseLeave={() => setIsHover(false)} + > + <div className="vc-notification"> + {icon && <img className="vc-notification-icon" src={icon} alt="" />} + <div className="vc-notification-content"> + <Forms.FormTitle tag="h2">{title}</Forms.FormTitle> + <div> + {richBody ?? <p className="vc-notification-p">{body}</p>} + </div> + </div> + </div> + {image && <img className="vc-notification-img" src={image} alt="" />} + {timeout !== 0 && ( + <div + className="vc-notification-progressbar" + style={{ width: `${(1 - timeoutProgress) * 100}%`, backgroundColor: color || "var(--brand-experiment)" }} + /> + )} + </button> + ); +}); diff --git a/src/api/Notifications/Notifications.tsx b/src/api/Notifications/Notifications.tsx new file mode 100644 index 000000000..9c599aa65 --- /dev/null +++ b/src/api/Notifications/Notifications.tsx @@ -0,0 +1,92 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { Settings } from "@api/settings"; +import { Queue } from "@utils/Queue"; +import { ReactDOM } from "@webpack/common"; +import type { ReactNode } from "react"; +import type { Root } from "react-dom/client"; + +import NotificationComponent from "./NotificationComponent"; + +const NotificationQueue = new Queue(); + +let reactRoot: Root; +let id = 42; + +function getRoot() { + if (!reactRoot) { + const container = document.createElement("div"); + container.id = "vc-notification-container"; + document.body.append(container); + reactRoot = ReactDOM.createRoot(container); + } + return reactRoot; +} + +export interface NotificationData { + title: string; + body: string; + /** + * Same as body but can be a custom component. + * Will be used over body if present. + * Not supported on desktop notifications, those will fall back to body */ + richBody?: ReactNode; + /** Small icon. This is for things like profile pictures and should be square */ + icon?: string; + /** Large image. Optimally, this should be around 16x9 but it doesn't matter much. Desktop Notifications might not support this */ + image?: string; + onClick?(): void; + onClose?(): void; + color?: string; +} + +function _showNotification(notification: NotificationData, id: number) { + const root = getRoot(); + return new Promise<void>(resolve => { + root.render( + <NotificationComponent key={id} {...notification} onClose={() => { + notification.onClose?.(); + root.render(null); + resolve(); + }} />, + ); + }); +} + +function shouldBeNative() { + const { useNative } = Settings.notifications; + if (useNative === "always") return true; + if (useNative === "not-focused") return !document.hasFocus(); + return false; +} + +export function showNotification(data: NotificationData) { + if (shouldBeNative()) { + const { title, body, icon, image, onClick = null, onClose = null } = data; + const n = new Notification(title, { + body, + icon, + image + }); + n.onclick = onClick; + n.onclose = onClose; + } else { + NotificationQueue.push(() => _showNotification(data, id++)); + } +} diff --git a/src/api/Notifications/index.ts b/src/api/Notifications/index.ts new file mode 100644 index 000000000..cd14587b6 --- /dev/null +++ b/src/api/Notifications/index.ts @@ -0,0 +1,19 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +export * from "./Notifications"; diff --git a/src/api/Notifications/styles.css b/src/api/Notifications/styles.css new file mode 100644 index 000000000..84d8ff777 --- /dev/null +++ b/src/api/Notifications/styles.css @@ -0,0 +1,49 @@ +.vc-notification-root { + /* clear default button styles */ + all: unset; + display: flex; + flex-direction: column; + width: 25vw; + min-height: 10vh; + color: var(--text-normal); + background-color: var(--background-secondary-alt); + position: absolute; + z-index: 2147483647; + right: 1rem; + border-radius: 6px; + overflow: hidden; + cursor: pointer; +} + +.vc-notification { + display: flex; + flex-direction: row; + padding: 1.25rem; + gap: 1.25rem; +} + +.vc-notification-icon { + height: 4rem; + width: 4rem; + border-radius: 6px; +} + +/* Discord adding 3km margin to generic tags */ +.vc-notification h2 { + margin: unset; +} + +.vc-notification-progressbar { + height: 0.25rem; + border-radius: 5px; + margin-top: auto; +} + +.vc-notification-p { + margin: 0.5rem 0 0; + line-height: 140%; +} + +.vc-notification-img { + width: 100%; +} diff --git a/src/api/index.ts b/src/api/index.ts index 0fef99cda..abb509348 100644 --- a/src/api/index.ts +++ b/src/api/index.ts @@ -25,6 +25,7 @@ import * as $MessageDecorations from "./MessageDecorations"; import * as $MessageEventsAPI from "./MessageEvents"; import * as $MessagePopover from "./MessagePopover"; import * as $Notices from "./Notices"; +import * as $Notifications from "./Notifications"; import * as $ServerList from "./ServerList"; import * as $Styles from "./Styles"; @@ -88,3 +89,7 @@ export const MemberListDecorators = $MemberListDecorators; * a */ export const Styles = $Styles; +/** + * An API allowing you to display notifications + */ +export const Notifications = $Notifications; diff --git a/src/api/settings.ts b/src/api/settings.ts index d20e9642f..c7117918a 100644 --- a/src/api/settings.ts +++ b/src/api/settings.ts @@ -40,6 +40,12 @@ export interface Settings { [setting: string]: any; }; }; + + notifications: { + timeout: number; + position: "top-right" | "bottom-right"; + useNative: "always" | "never" | "not-focused"; + }; } const DefaultSettings: Settings = { @@ -51,7 +57,13 @@ const DefaultSettings: Settings = { frameless: false, transparent: false, winCtrlQ: false, - plugins: {} + plugins: {}, + + notifications: { + timeout: 5000, + position: "bottom-right", + useNative: "not-focused" + } }; try { diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 9f55d57f9..6ea1ca9d2 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -22,22 +22,63 @@ import { classNameFactory } from "@api/Styles"; import DonateButton from "@components/DonateButton"; import ErrorBoundary from "@components/ErrorBoundary"; import IpcEvents from "@utils/IpcEvents"; -import { useAwaiter } from "@utils/misc"; -import { Button, Card, Forms, Margins, React, Switch } from "@webpack/common"; +import { Margins } from "@utils/margins"; +import { identity, useAwaiter } from "@utils/misc"; +import { Button, Card, Forms, React, Select, Slider, Switch } from "@webpack/common"; const cl = classNameFactory("vc-settings-"); const DEFAULT_DONATE_IMAGE = "https://cdn.discordapp.com/emojis/1026533090627174460.png"; const SHIGGY_DONATE_IMAGE = "https://media.discordapp.net/stickers/1039992459209490513.png"; +type KeysOfType<Object, Type> = { + [K in keyof Object]: Object[K] extends Type ? K : never; +}[keyof Object]; + function VencordSettings() { const [settingsDir, , settingsDirPending] = useAwaiter(() => VencordNative.ipc.invoke<string>(IpcEvents.GET_SETTINGS_DIR), { fallbackValue: "Loading..." }); const settings = useSettings(); + const notifSettings = settings.notifications; const donateImage = React.useMemo(() => Math.random() > 0.5 ? DEFAULT_DONATE_IMAGE : SHIGGY_DONATE_IMAGE, []); + const isWindows = navigator.platform.toLowerCase().startsWith("win"); + + const Switches: Array<false | { + key: KeysOfType<typeof settings, boolean>; + title: string; + note: string; + }> = + [ + { + key: "useQuickCss", + title: "Enable Custom CSS", + note: "Loads your Custom CSS" + }, + !IS_WEB && { + key: "enableReactDevtools", + title: "Enable React Developer Tools", + note: "Requires a full restart" + }, + !IS_WEB && !isWindows && { + key: "frameless", + title: "Disable the window frame", + note: "Requires a full restart" + }, + !IS_WEB && { + key: "transparent", + title: "Enable window transparency", + note: "Requires a full restart" + }, + !IS_WEB && isWindows && { + key: "winCtrlQ", + title: "Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4)", + note: "Requires a full restart" + } + ]; + return ( <React.Fragment> <DonateCard image={donateImage} /> @@ -82,52 +123,70 @@ function VencordSettings() { <Forms.FormDivider /> - <Forms.FormSection className={Margins.marginTop16} title="Settings"> - <Forms.FormText className={Margins.marginBottom20}> + <Forms.FormSection className={Margins.top16} title="Settings" tag="h5"> + <Forms.FormText className={Margins.bottom20}> Hint: You can change the position of this settings section in the settings of the "Settings" plugin! </Forms.FormText> - <Switch - value={settings.useQuickCss} - onChange={(v: boolean) => settings.useQuickCss = v} - note="Loads styles from your QuickCSS file"> - Use QuickCSS - </Switch> - {!IS_WEB && ( - <React.Fragment> - <Switch - value={settings.enableReactDevtools} - onChange={(v: boolean) => settings.enableReactDevtools = v} - note="Requires a full restart" - > - Enable React Developer Tools - </Switch> - <Switch - value={settings.frameless} - onChange={(v: boolean) => settings.frameless = v} - note="Requires a full restart" - > - Disable the window frame - </Switch> - <Switch - value={settings.transparent} - onChange={(v: boolean) => settings.transparent = v} - note="Requires a full restart" - > - Enable window transparency - </Switch> - {navigator.platform.toLowerCase().startsWith("win") && ( - <Switch - value={settings.winCtrlQ} - onChange={(v: boolean) => settings.winCtrlQ = v} - note="Requires a full restart" - > - Register Ctrl+Q as shortcut to close Discord (Alternative to Alt+F4) - </Switch> - )} - </React.Fragment> - )} - + {Switches.map(s => s && ( + <Switch + key={s.key} + value={settings[s.key]} + onChange={v => settings[s.key] = v} + note={s.note} + > + {s.title} + </Switch> + ))} </Forms.FormSection> + + + <Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle> + <Forms.FormText className={Margins.bottom8}> + Some plugins may show you notifications. These come in two styles: + <ul> + <li><strong>Vencord Notifications</strong>: These are in-app notifications</li> + <li><strong>Desktop Notifications</strong>: Native Desktop notifications (like when you get a ping)</li> + </ul> + </Forms.FormText> + <Select + placeholder="Notification Style" + options={[ + { label: "Only use Desktop notifications when Discord is not focused", value: "not-focused", default: true }, + { label: "Always use Desktop notifications", value: "always" }, + { label: "Always use Vencord notifications", value: "never" }, + ]satisfies Array<{ value: typeof settings["notifications"]["useNative"]; } & Record<string, any>>} + closeOnSelect={true} + select={v => notifSettings.useNative = v} + isSelected={v => v === notifSettings.useNative} + serialize={identity} + /> + + <Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Position</Forms.FormTitle> + <Select + isDisabled={notifSettings.useNative === "always"} + placeholder="Notification Position" + options={[ + { label: "Bottom Right", value: "bottom-right", default: true }, + { label: "Top Right", value: "top-right" }, + ]satisfies Array<{ value: typeof settings["notifications"]["position"]; } & Record<string, any>>} + select={v => notifSettings.position = v} + isSelected={v => v === notifSettings.position} + serialize={identity} + /> + + <Forms.FormTitle tag="h5" className={Margins.top16 + " " + Margins.bottom8}>Notification Timeout</Forms.FormTitle> + <Forms.FormText className={Margins.bottom16}>Set to 0s to never automatically time out</Forms.FormText> + <Slider + disabled={notifSettings.useNative === "always"} + markers={[0, 1000, 2500, 5000, 10_000, 20_000]} + minValue={0} + maxValue={20_000} + initialValue={notifSettings.timeout} + onValueChange={v => notifSettings.timeout = v} + onValueRender={v => (v / 1000).toFixed(2) + "s"} + onMarkerRender={v => (v / 1000) + "s"} + stickToMarkers={false} + /> </React.Fragment> ); } diff --git a/src/modules.d.ts b/src/modules.d.ts index c1a1996e7..d75a84f74 100644 --- a/src/modules.d.ts +++ b/src/modules.d.ts @@ -38,7 +38,8 @@ declare module "~fileContent/*" { export default content; } -declare module "*.css" { } +declare module "*.css"; + declare module "*.css?managed" { const name: string; export default name; diff --git a/src/plugins/spotifyControls/PlayerComponent.tsx b/src/plugins/spotifyControls/PlayerComponent.tsx index f6ad08b83..439ecc249 100644 --- a/src/plugins/spotifyControls/PlayerComponent.tsx +++ b/src/plugins/spotifyControls/PlayerComponent.tsx @@ -23,8 +23,8 @@ import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { debounce } from "@utils/debounce"; import { classes, LazyComponent } from "@utils/misc"; -import { filters, find, findByCodeLazy } from "@webpack"; -import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState } from "@webpack/common"; +import { filters, find } from "@webpack"; +import { ContextMenu, FluxDispatcher, Forms, Menu, React, useEffect, useState, useStateFromStores } from "@webpack/common"; import { SpotifyStore, Track } from "./SpotifyStore"; @@ -37,14 +37,6 @@ function msToHuman(ms: number) { return `${m.toString().padStart(2, "0")}:${s.toString().padStart(2, "0")}`; } -const useStateFromStores: <T>( - stores: typeof SpotifyStore[], - mapper: () => T, - idk?: null, - compare?: (old: T, newer: T) => boolean -) => T - = findByCodeLazy("useStateFromStores"); - function Svg(path: string, label: string) { return () => ( <svg diff --git a/src/utils/Queue.ts b/src/utils/Queue.ts index 86eb79196..2680f5636 100644 --- a/src/utils/Queue.ts +++ b/src/utils/Queue.ts @@ -27,7 +27,7 @@ export class Queue { * @param maxSize The maximum amount of functions that can be queued at once. * If the queue is full, the oldest function will be removed. */ - constructor(public maxSize = Infinity) { } + constructor(public readonly maxSize = Infinity) { } private queue = [] as Array<() => Promisable<unknown>>; diff --git a/src/utils/index.ts b/src/utils/index.ts index b80bde3b2..cfded6b5d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -22,6 +22,7 @@ export * from "./debounce"; export * as Discord from "./discord"; export { default as IpcEvents } from "./IpcEvents"; export { default as Logger } from "./Logger"; +export * from "./margins"; export * from "./misc"; export * as Modals from "./modal"; export * from "./onceDefined"; diff --git a/src/utils/margins.ts b/src/utils/margins.ts new file mode 100644 index 000000000..5d7eed766 --- /dev/null +++ b/src/utils/margins.ts @@ -0,0 +1,35 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +let styleStr = ""; + +export const Margins: Record<`${"top" | "bottom" | "left" | "right"}${8 | 16 | 20}`, string> = {} as any; + +for (const dir of ["top", "bottom", "left", "right"] as const) { + for (const size of [8, 16, 20] as const) { + const cl = `vc-m-${dir}-${size}`; + Margins[`${dir}${size}`] = cl; + styleStr += `.${cl}{margin-${dir}:${size}px;}`; + } +} + +document.addEventListener("DOMContentLoaded", () => + document.head.append(Object.assign(document.createElement("style"), { + textContent: styleStr, + id: "vencord-margins" + })), { once: true }); diff --git a/src/utils/misc.tsx b/src/utils/misc.tsx index c64d9e1f6..a41ab6730 100644 --- a/src/utils/misc.tsx +++ b/src/utils/misc.tsx @@ -200,3 +200,7 @@ export const checkIntersecting = (el: Element) => { const documentHeight = Math.max(document.documentElement.clientHeight, window.innerHeight); return !(elementBox.bottom < 0 || elementBox.top - documentHeight >= 0); }; + +export function identity<T>(value: T): T { + return value; +} diff --git a/src/utils/settingsSync.ts b/src/utils/settingsSync.ts index 5cd81e7e4..18e185478 100644 --- a/src/utils/settingsSync.ts +++ b/src/utils/settingsSync.ts @@ -112,7 +112,6 @@ export async function uploadSettingsBackup(showToast = true): Promise<void> { if (file) { try { - console.log(file); await importSettings(new TextDecoder().decode(file.data)); if (showToast) toastSuccess(); } catch (err) { diff --git a/src/webpack/common/components.ts b/src/webpack/common/components.ts index be585c3a4..27d103fce 100644 --- a/src/webpack/common/components.ts +++ b/src/webpack/common/components.ts @@ -49,5 +49,8 @@ export const Slider = waitForComponent<t.Slider>("Slider", filters.byCode("close export const Flex = waitForComponent<t.Flex>("Flex", ["Justify", "Align", "Wrap"]); export const ButtonWrapperClasses = findByPropsLazy("buttonWrapper", "buttonContent") as Record<string, string>; +/** + * @deprecated Use @utils/margins instead + */ export const Margins: t.Margins = findByPropsLazy("marginTop20"); export const ButtonLooks: t.ButtonLooks = findByPropsLazy("BLANK", "FILLED", "INVERTED"); diff --git a/src/webpack/common/internal.tsx b/src/webpack/common/internal.tsx index df768f763..e2f42d8c3 100644 --- a/src/webpack/common/internal.tsx +++ b/src/webpack/common/internal.tsx @@ -19,7 +19,7 @@ import { LazyComponent } from "@utils/misc"; // eslint-disable-next-line path-alias/no-relative -import { FilterFn, waitFor } from "../webpack"; +import { FilterFn, filters, waitFor } from "../webpack"; export function waitForComponent<T extends React.ComponentType<any> = React.ComponentType<any> & Record<string, any>>(name: string, filter: FilterFn | string | string[]): T { let myValue: T = function () { @@ -34,3 +34,7 @@ export function waitForComponent<T extends React.ComponentType<any> = React.Comp return lazyComponent; } + +export function waitForStore(name: string, cb: (v: any) => void) { + waitFor(filters.byStoreName(name), cb); +} diff --git a/src/webpack/common/react.ts b/src/webpack/common/react.ts index 455f39bef..d73a3dfea 100644 --- a/src/webpack/common/react.ts +++ b/src/webpack/common/react.ts @@ -25,7 +25,7 @@ export let useEffect: typeof React.useEffect; export let useMemo: typeof React.useMemo; export let useRef: typeof React.useRef; -export const ReactDOM: typeof import("react-dom") = findByPropsLazy("createPortal", "render"); +export const ReactDOM: typeof import("react-dom") & typeof import("react-dom/client") = findByPropsLazy("createPortal", "render"); waitFor("useState", m => { React = m; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index bcd26b1ef..0bd9e87fb 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -19,36 +19,71 @@ import type * as Stores from "discord-types/stores"; // eslint-disable-next-line path-alias/no-relative -import { filters, findByPropsLazy, mapMangledModuleLazy, waitFor } from "../webpack"; +import { filters, findByCodeLazy, findByPropsLazy, mapMangledModuleLazy } from "../webpack"; +import { waitForStore } from "./internal"; +import * as t from "./types/stores"; -export const MessageStore = findByPropsLazy("getRawMessages") as Omit<Stores.MessageStore, "getMessages"> & { +export const Flux: t.Flux = findByPropsLazy("connectStores"); + +type GenericStore = t.FluxStore & Record<string, any>; + +export let MessageStore: Omit<Stores.MessageStore, "getMessages"> & { getMessages(chanId: string): any; }; -export const PermissionStore = findByPropsLazy("can", "getGuildPermissions"); -export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel"); -export const GuildChannelStore = findByPropsLazy("getChannels"); -export const ReadStateStore = findByPropsLazy("lastMessageId"); -export const PresenceStore = findByPropsLazy("setCurrentUserOnConnectionOpen"); -export let GuildStore: Stores.GuildStore; -export let UserStore: Stores.UserStore; -export let SelectedChannelStore: Stores.SelectedChannelStore; -export let SelectedGuildStore: any; -export let ChannelStore: Stores.ChannelStore; -export let GuildMemberStore: Stores.GuildMemberStore; -export let RelationshipStore: Stores.RelationshipStore & { +// this is not actually a FluxStore +export const PrivateChannelsStore = findByPropsLazy("openPrivateChannel"); +export let PermissionStore: GenericStore; +export let GuildChannelStore: GenericStore; +export let ReadStateStore: GenericStore; +export let PresenceStore: GenericStore; + +export let GuildStore: Stores.GuildStore & t.FluxStore; +export let UserStore: Stores.UserStore & t.FluxStore; +export let SelectedChannelStore: Stores.SelectedChannelStore & t.FluxStore; +export let SelectedGuildStore: t.FluxStore & Record<string, any>; +export let ChannelStore: Stores.ChannelStore & t.FluxStore; +export let GuildMemberStore: Stores.GuildMemberStore & t.FluxStore; +export let RelationshipStore: Stores.RelationshipStore & t.FluxStore & { /** Get the date (as a string) that the relationship was created */ getSince(userId: string): string; }; +export let WindowStore: t.WindowStore; + export const MaskedLinkStore = mapMangledModuleLazy('"MaskedLinkStore"', { openUntrustedLink: filters.byCode(".apply(this,arguments)") }); -waitFor(["getCurrentUser", "initialize"], m => UserStore = m); -waitFor("getSortedPrivateChannels", m => ChannelStore = m); -waitFor("getCurrentlySelectedChannelId", m => SelectedChannelStore = m); -waitFor("getLastSelectedGuildId", m => SelectedGuildStore = m); -waitFor("getGuildCount", m => GuildStore = m); -waitFor(["getMember", "initialize"], m => GuildMemberStore = m); -waitFor("getRelationshipType", m => RelationshipStore = m); +/** + * React hook that returns stateful data for one or more stores + * You might need a custom comparator (4th argument) if your store data is an object + * + * @param stores The stores to listen to + * @param mapper A function that returns the data you need + * @param idk some thing, idk just pass null + * @param isEqual A custom comparator for the data returned by mapper + * + * @example const user = useStateFromStores([UserStore], () => UserStore.getCurrentUser(), null, (old, current) => old.id === current.id); + */ +export const useStateFromStores: <T>( + stores: t.FluxStore[], + mapper: () => T, + idk?: any, + isEqual?: (old: T, newer: T) => boolean +) => T + = findByCodeLazy("useStateFromStores"); + +waitForStore("UserStore", s => UserStore = s); +waitForStore("ChannelStore", m => ChannelStore = m); +waitForStore("SelectedChannelStore", m => SelectedChannelStore = m); +waitForStore("SelectedGuildStore", m => SelectedGuildStore = m); +waitForStore("GuildStore", m => GuildStore = m); +waitForStore("GuildMemberStore", m => GuildMemberStore = m); +waitForStore("RelationshipStore", m => RelationshipStore = m); +waitForStore("PermissionStore", m => PermissionStore = m); +waitForStore("PresenceStore", m => PresenceStore = m); +waitForStore("ReadStateStore", m => ReadStateStore = m); +waitForStore("GuildChannelStore", m => GuildChannelStore = m); +waitForStore("MessageStore", m => MessageStore = m); +waitForStore("WindowStore", m => WindowStore = m); diff --git a/src/webpack/common/types/components.d.ts b/src/webpack/common/types/components.d.ts index 3f76c22ed..9cd01de2f 100644 --- a/src/webpack/common/types/components.d.ts +++ b/src/webpack/common/types/components.d.ts @@ -215,9 +215,9 @@ export type Select = ComponentType<PropsWithChildren<{ closeOnSelect?: boolean; hideIcon?: boolean; - select?(value: any): void; - isSelected?(value: any): boolean; - serialize?(value: any): string; + select(value: any): void; + isSelected(value: any): boolean; + serialize(value: any): string; clear?(): void; maxVisibleItems?: number; diff --git a/src/webpack/common/types/index.d.ts b/src/webpack/common/types/index.d.ts index 9d6c29502..af4b5e1fb 100644 --- a/src/webpack/common/types/index.d.ts +++ b/src/webpack/common/types/index.d.ts @@ -19,5 +19,6 @@ export * from "./components"; export * from "./fluxEvents"; export * from "./menu"; +export * from "./stores"; export * from "./utils"; diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts new file mode 100644 index 000000000..6af5b27cd --- /dev/null +++ b/src/webpack/common/types/stores.d.ts @@ -0,0 +1,40 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { FluxDispatcher, FluxEvents } from "./utils"; + +export class FluxStore { + constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>); + + emitChange(): void; + getDispatchToken(): string; + getName(): string; + initialize(): void; + initializeIfNeeded(): void; + __getLocalVars(): Record<string, any>; +} + +export interface Flux { + Store: typeof FluxStore; +} + +export class WindowStore extends FluxStore { + isElementFullScreen(): boolean; + isFocused(): boolean; + windowSize(): Record<"width" | "height", number>; +} diff --git a/src/webpack/common/types/utils.d.ts b/src/webpack/common/types/utils.d.ts index 7222be458..0e2a6ca84 100644 --- a/src/webpack/common/types/utils.d.ts +++ b/src/webpack/common/types/utils.d.ts @@ -31,20 +31,6 @@ export interface FluxDispatcher { unsubscribe(event: FluxEvents, callback: (data: any) => void): void; } -declare class FluxStore { - constructor(dispatcher: FluxDispatcher, eventHandlers?: Partial<Record<FluxEvents, (data: any) => void>>); - - emitChange(): void; - getDispatchToken(): string; - getName(): string; - initialize(): void; - initializeIfNeeded(): void; -} - -export interface Flux { - Store: typeof FluxStore; -} - export type Parser = Record< | "parse" | "parseTopic" diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index daac207d2..b53c34082 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -23,7 +23,6 @@ import { _resolveReady,filters, findByCodeLazy, findByPropsLazy, mapMangledModul import type * as t from "./types/utils"; export let FluxDispatcher: t.FluxDispatcher; -export const Flux: t.Flux = findByPropsLazy("connectStores"); export const RestAPI: t.RestAPI = findByPropsLazy("getAPIBaseURL", "get"); export const moment: typeof import("moment") = findByPropsLazy("parseTwoDigitYear"); diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index e537740f9..5aa7dc724 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -50,7 +50,7 @@ export const filters = { } return true; }, - byDisplayName: (name: string): FilterFn => m => + byStoreName: (name: string): FilterFn => m => m.constructor?.displayName === name }; @@ -331,15 +331,15 @@ export function findByCodeLazy(...code: string[]) { /** * Find a store by its displayName */ -export function findByDisplayName(name: string) { - return find(filters.byDisplayName(name)); +export function findStore(name: string) { + return find(filters.byStoreName(name)); } /** * findByDisplayName but lazy */ -export function findByDisplayNameLazy(name: string) { - return findLazy(filters.byDisplayName(name)); +export function findStoreLazy(name: string) { + return findLazy(filters.byStoreName(name)); } /** From d95be1acbaa282c53508a33d85cdda2ea76c50da Mon Sep 17 00:00:00 2001 From: fawn <fawn@envs.net> Date: Fri, 10 Feb 2023 21:41:49 +0000 Subject: [PATCH 096/114] refactor: update plugins to use `$self` (#478) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/anonymiseFileNames.ts | 2 +- src/plugins/betterGifAltText.ts | 4 ++-- src/plugins/betterRoleDot.ts | 2 +- src/plugins/callTimer.tsx | 2 +- src/plugins/emoteCloner.tsx | 2 +- src/plugins/forceOwnerCrown.ts | 2 +- src/plugins/ignoreActivities.tsx | 6 +++--- src/plugins/loadingQuotes.ts | 2 +- src/plugins/memberCount.tsx | 2 +- src/plugins/messageLogger/index.tsx | 8 ++++---- src/plugins/noBlockedMessages.ts | 2 +- src/plugins/noReplyMention.tsx | 2 +- src/plugins/pronoundb/index.ts | 4 ++-- src/plugins/reviewDB/index.tsx | 2 +- src/plugins/spotifyControls/index.tsx | 2 +- src/plugins/startupTimings/index.tsx | 2 +- src/plugins/vcDoubleClick.ts | 2 +- src/plugins/viewIcons.tsx | 2 +- src/plugins/whoReacted.tsx | 2 +- 19 files changed, 26 insertions(+), 26 deletions(-) diff --git a/src/plugins/anonymiseFileNames.ts b/src/plugins/anonymiseFileNames.ts index f953a144e..26c423c0a 100644 --- a/src/plugins/anonymiseFileNames.ts +++ b/src/plugins/anonymiseFileNames.ts @@ -36,7 +36,7 @@ export default definePlugin({ replacement: { match: /uploadFiles:(.{1,2}),/, replace: - "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=Vencord.Plugins.plugins.AnonymiseFileNames.anonymise(f.filename)),$1(...args)),", + "uploadFiles:(...args)=>(args[0].uploads.forEach(f=>f.filename=$self.anonymise(f.filename)),$1(...args)),", }, }, ], diff --git a/src/plugins/betterGifAltText.ts b/src/plugins/betterGifAltText.ts index 686ef0a55..5e0191b9d 100644 --- a/src/plugins/betterGifAltText.ts +++ b/src/plugins/betterGifAltText.ts @@ -31,7 +31,7 @@ export default definePlugin({ replacement: { match: /(return.{0,10}\.jsx.{0,50}isWindowFocused)/, replace: - "Vencord.Plugins.plugins.BetterGifAltText.altify(e);$1", + "$self.altify(e);$1", }, }, { @@ -39,7 +39,7 @@ export default definePlugin({ replacement: { match: /(?<==(.{1,3})\.alt.{0,20})\?.{0,5}\.Messages\.GIF/, replace: - "?($1.alt='GIF',Vencord.Plugins.plugins.BetterGifAltText.altify($1))", + "?($1.alt='GIF',$self.altify($1))", }, }, ], diff --git a/src/plugins/betterRoleDot.ts b/src/plugins/betterRoleDot.ts index 6cadf7915..6ef92a863 100644 --- a/src/plugins/betterRoleDot.ts +++ b/src/plugins/betterRoleDot.ts @@ -33,7 +33,7 @@ export default definePlugin({ find: "M0 4C0 1.79086 1.79086 0 4 0H16C18.2091 0 20 1.79086 20 4V16C20 18.2091 18.2091 20 16 20H4C1.79086 20 0 18.2091 0 16V4Z", replacement: { match: /viewBox:"0 0 20 20"/, - replace: "$&,onClick:()=>Vencord.Plugins.plugins.BetterRoleDot.copyToClipBoard(e.color),style:{cursor:'pointer'}", + replace: "$&,onClick:()=>$self.copyToClipBoard(e.color),style:{cursor:'pointer'}", }, }, { diff --git a/src/plugins/callTimer.tsx b/src/plugins/callTimer.tsx index ff0225647..f745bf625 100644 --- a/src/plugins/callTimer.tsx +++ b/src/plugins/callTimer.tsx @@ -75,7 +75,7 @@ export default definePlugin({ find: ".renderConnectionStatus=", replacement: { match: /(?<=renderConnectionStatus=.+\.channel,children:)\w/, - replace: "[$&, Vencord.Plugins.plugins.CallTimer.renderTimer(this.props.channel.id)]" + replace: "[$&, $self.renderTimer(this.props.channel.id)]" } }], renderTimer(channelId: string) { diff --git a/src/plugins/emoteCloner.tsx b/src/plugins/emoteCloner.tsx index bb744060f..15d13588d 100644 --- a/src/plugins/emoteCloner.tsx +++ b/src/plugins/emoteCloner.tsx @@ -187,7 +187,7 @@ export default definePlugin({ find: "open-native-link", replacement: { match: /id:"open-native-link".{0,200}\(\{href:(.{0,3}),.{0,200}\},"open-native-link"\)/, - replace: "$&,Vencord.Plugins.plugins.EmoteCloner.makeMenu(arguments[2])" + replace: "$&,$self.makeMenu(arguments[2])" }, }, diff --git a/src/plugins/forceOwnerCrown.ts b/src/plugins/forceOwnerCrown.ts index 0c1df4780..3122410f9 100644 --- a/src/plugins/forceOwnerCrown.ts +++ b/src/plugins/forceOwnerCrown.ts @@ -30,7 +30,7 @@ export default definePlugin({ find: ".renderOwner=", replacement: { match: /isOwner;return null!=(\w+)?&&/g, - replace: "isOwner;if(Vencord.Plugins.plugins.ForceOwnerCrown.isGuildOwner(this.props)){$1=true;}return null!=$1&&" + replace: "isOwner;if($self.isGuildOwner(this.props)){$1=true;}return null!=$1&&" } }, ], diff --git a/src/plugins/ignoreActivities.tsx b/src/plugins/ignoreActivities.tsx index 300caf7aa..56a0a8dba 100644 --- a/src/plugins/ignoreActivities.tsx +++ b/src/plugins/ignoreActivities.tsx @@ -146,19 +146,19 @@ export default definePlugin({ find: ".Messages.SETTINGS_GAMES_TOGGLE_OVERLAY", replacement: { match: /var .=(?<props>.)\.overlay.+?"aria-label":.\..\.Messages\.SETTINGS_GAMES_TOGGLE_OVERLAY.+?}}\)/, - replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleGameActivityButton($<props>)" + replace: "$&,$self.renderToggleGameActivityButton($<props>)" } }, { find: ".overlayBadge", replacement: { match: /.badgeContainer.+?.\?\(0,.\.jsx\)\(.{1,2},{name:(?<props>.)\.name}\):null/, - replace: "$&,Vencord.Plugins.plugins.IgnoreActivities.renderToggleActivityButton($<props>)" + replace: "$&,$self.renderToggleActivityButton($<props>)" } }, { find: '.displayName="LocalActivityStore"', replacement: { match: /(?<activities>.)\.push\(.\({type:.\..{1,3}\.LISTENING.+?\)\)/, - replace: "$&;$<activities>=$<activities>.filter(Vencord.Plugins.plugins.IgnoreActivities.isActivityNotIgnored);" + replace: "$&;$<activities>=$<activities>.filter($self.isActivityNotIgnored);" } }], diff --git a/src/plugins/loadingQuotes.ts b/src/plugins/loadingQuotes.ts index 052bfe6ec..7be6f3054 100644 --- a/src/plugins/loadingQuotes.ts +++ b/src/plugins/loadingQuotes.ts @@ -68,7 +68,7 @@ export default definePlugin({ find: ".LOADING_DID_YOU_KNOW", replacement: { match: /\._loadingText=.+?random\(.+?;/s, - replace: "._loadingText=Vencord.Plugins.plugins.LoadingQuotes.quote;", + replace: "._loadingText=$self.quote;", }, }, ], diff --git a/src/plugins/memberCount.tsx b/src/plugins/memberCount.tsx index 947d4d7b7..e17157e75 100644 --- a/src/plugins/memberCount.tsx +++ b/src/plugins/memberCount.tsx @@ -99,7 +99,7 @@ export default definePlugin({ find: ".isSidebarVisible,", replacement: { match: /(var (.)=.\.className.+?children):\[(.\.useMemo[^}]+"aria-multiselectable")/, - replace: "$1:[$2.startsWith('members')?Vencord.Plugins.plugins.MemberCount.render():null,$3" + replace: "$1:[$2.startsWith('members')?$self.render():null,$3" } }], diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index ff4c328c1..9242ce2f8 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -151,7 +151,7 @@ export default definePlugin({ replace: "MESSAGE_DELETE:function($1){" + " var cache = $2getOrCreate($1.channelId);" + - " cache = Vencord.Plugins.plugins.MessageLogger.handleDelete(cache, $1, false);" + + " cache = $self.handleDelete(cache, $1, false);" + " $2commit(cache);" + "}," }, @@ -161,7 +161,7 @@ export default definePlugin({ replace: "MESSAGE_DELETE_BULK:function($1){" + " var cache = $2getOrCreate($1.channelId);" + - " cache = Vencord.Plugins.plugins.MessageLogger.handleDelete(cache, $1, true);" + + " cache = $self.handleDelete(cache, $1, true);" + " $2commit(cache);" + "}," }, @@ -171,7 +171,7 @@ export default definePlugin({ replace: "$1" + ".update($3,m =>" + " $2.message.content !== m.editHistory?.[0]?.content && $2.message.content !== m.content ?" + - " m.set('editHistory',[...(m.editHistory || []), Vencord.Plugins.plugins.MessageLogger.makeEdit($2.message, m)]) :" + + " m.set('editHistory',[...(m.editHistory || []), $self.makeEdit($2.message, m)]) :" + " m" + ")" + ".update($3" @@ -287,7 +287,7 @@ export default definePlugin({ { // Render editHistory in the deepest div for message content match: /(\)\("div",\{id:.+?children:\[)/, - replace: "$1 (arguments[0].message.editHistory.length > 0 ? arguments[0].message.editHistory.map(edit => Vencord.Plugins.plugins.MessageLogger.renderEdit(edit)) : null), " + replace: "$1 (arguments[0].message.editHistory.length > 0 ? arguments[0].message.editHistory.map(edit => $self.renderEdit(edit)) : null), " } ] }, diff --git a/src/plugins/noBlockedMessages.ts b/src/plugins/noBlockedMessages.ts index bd72ce5fb..6937041a8 100644 --- a/src/plugins/noBlockedMessages.ts +++ b/src/plugins/noBlockedMessages.ts @@ -43,7 +43,7 @@ export default definePlugin({ replacement: [ { match: /(?<=MESSAGE_CREATE:function\((\w)\){var \w=\w\.channelId,\w=\w\.message,\w=\w\.isPushNotification,\w=\w\.\w\.getOrCreate\(\w\));/, - replace: ";if(Vencord.Plugins.plugins.NoBlockedMessages.isBlocked(n))return;" + replace: ";if($self.isBlocked(n))return;" } ] } diff --git a/src/plugins/noReplyMention.tsx b/src/plugins/noReplyMention.tsx index 91a88d308..2e9758893 100644 --- a/src/plugins/noReplyMention.tsx +++ b/src/plugins/noReplyMention.tsx @@ -51,7 +51,7 @@ export default definePlugin({ replacement: { match: /CREATE_PENDING_REPLY:function\((.{1,2})\){/, replace: - "CREATE_PENDING_REPLY:function($1){$1.shouldMention=Vencord.Plugins.plugins.NoReplyMention.shouldMention($1);", + "CREATE_PENDING_REPLY:function($1){$1.shouldMention=$self.shouldMention($1);", }, }, ], diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index c8481105d..7ebe91921 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -38,7 +38,7 @@ export default definePlugin({ find: "showCommunicationDisabledStyles", replacement: { match: /(?<=return\s*\(0,\w{1,3}\.jsxs?\)\(.+!\w{1,3}&&)(\(0,\w{1,3}.jsxs?\)\(.+?\{.+?\}\))/, - replace: "[$1, Vencord.Plugins.plugins.PronounDB.PronounsChatComponent(e)]" + replace: "[$1, $self.PronounsChatComponent(e)]" } }, // Hijack the discord pronouns section (hidden without experiment) and add a wrapper around the text section @@ -46,7 +46,7 @@ export default definePlugin({ find: ".Messages.BOT_PROFILE_SLASH_COMMANDS", replacement: { match: /\(0,.\.jsx\)\((?<PronounComponent>.{1,2}\..),(?<pronounProps>{currentPronouns.+?:(?<fullProps>.{1,2})\.pronouns.+?})\)/, - replace: "$<fullProps>&&Vencord.Plugins.plugins.PronounDB.PronounsProfileWrapper($<PronounComponent>,$<pronounProps>,$<fullProps>)" + replace: "$<fullProps>&&$self.PronounsProfileWrapper($<PronounComponent>,$<pronounProps>,$<fullProps>)" } }, // Make pronouns experiment be enabled by default diff --git a/src/plugins/reviewDB/index.tsx b/src/plugins/reviewDB/index.tsx index 744c2d620..8e8398344 100644 --- a/src/plugins/reviewDB/index.tsx +++ b/src/plugins/reviewDB/index.tsx @@ -37,7 +37,7 @@ export default definePlugin({ find: "disableBorderColor:!0", replacement: { match: /\(.{0,10}\{user:(.),setNote:.,canDM:.,.+?\}\)/, - replace: "$&,Vencord.Plugins.plugins.ReviewDB.getReviewsComponent($1)" + replace: "$&,$self.getReviewsComponent($1)" }, } ], diff --git a/src/plugins/spotifyControls/index.tsx b/src/plugins/spotifyControls/index.tsx index 86e187e31..5d82998df 100644 --- a/src/plugins/spotifyControls/index.tsx +++ b/src/plugins/spotifyControls/index.tsx @@ -55,7 +55,7 @@ export default definePlugin({ // return React.createElement(AccountPanel, { ..., showTaglessAccountPanel: blah }) match: /return ?(.{0,30}\(.{1,3},\{[^}]+?,showTaglessAccountPanel:.+?\}\))/, // return [Player, Panel] - replace: "return [Vencord.Plugins.plugins.SpotifyControls.renderPlayer(),$1]" + replace: "return [$self.renderPlayer(),$1]" } }, // Adds POST and a Marker to the SpotifyAPI (so we can easily find it) diff --git a/src/plugins/startupTimings/index.tsx b/src/plugins/startupTimings/index.tsx index 2ab00a646..5d66f5ffc 100644 --- a/src/plugins/startupTimings/index.tsx +++ b/src/plugins/startupTimings/index.tsx @@ -29,7 +29,7 @@ export default definePlugin({ find: "PAYMENT_FLOW_MODAL_TEST_PAGE,", replacement: { match: /{section:.{1,2}\..{1,3}\.PAYMENT_FLOW_MODAL_TEST_PAGE/, - replace: '{section:"StartupTimings",label:"Startup Timings",element:Vencord.Plugins.plugins.StartupTimings.StartupTimingPage},$&' + replace: '{section:"StartupTimings",label:"Startup Timings",element:$self.StartupTimingPage},$&' } }], StartupTimingPage: LazyComponent(() => require("./StartupTimingPage").default) diff --git a/src/plugins/vcDoubleClick.ts b/src/plugins/vcDoubleClick.ts index 1985107c3..39b8a059c 100644 --- a/src/plugins/vcDoubleClick.ts +++ b/src/plugins/vcDoubleClick.ts @@ -42,7 +42,7 @@ export default definePlugin({ // voice/stage channels { match: /onClick:function\(\)\{(e\.handleClick.+?)}/g, - replace: "onClick:function(){Vencord.Plugins.plugins.VoiceChatDoubleClick.schedule(()=>{$1},e)}", + replace: "onClick:function(){$self.schedule(()=>{$1},e)}", }, ], }, diff --git a/src/plugins/viewIcons.tsx b/src/plugins/viewIcons.tsx index 307fd0154..26f29029d 100644 --- a/src/plugins/viewIcons.tsx +++ b/src/plugins/viewIcons.tsx @@ -79,7 +79,7 @@ export default new class ViewIcons implements PluginDef { }, { match: /(id:"leave-guild".{0,200}),(\(0,.{1,3}\.jsxs?\).{0,200}function)/, - replace: "$1,Vencord.Plugins.plugins.ViewIcons.buildGuildContextMenuEntries(_guild),$2" + replace: "$1,$self.buildGuildContextMenuEntries(_guild),$2" } ] } diff --git a/src/plugins/whoReacted.tsx b/src/plugins/whoReacted.tsx index 685d95421..a14ca3a2a 100644 --- a/src/plugins/whoReacted.tsx +++ b/src/plugins/whoReacted.tsx @@ -92,7 +92,7 @@ export default definePlugin({ find: ",reactionRef:", replacement: { match: /((.)=(.{1,3})\.hideCount)(,.+?reactionCount.+?\}\))/, - replace: "$1,whoReactedProps=$3$4,$2?null:Vencord.Plugins.plugins.WhoReacted.renderUsers(whoReactedProps)" + replace: "$1,whoReactedProps=$3$4,$2?null:$self.renderUsers(whoReactedProps)" } }], From 2489bc68315f4e2c7cd54eae7c64846d6d1218d9 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Sun, 12 Feb 2023 14:58:44 -0300 Subject: [PATCH 097/114] Fix WhoReacted (#487) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/whoReacted.tsx | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/src/plugins/whoReacted.tsx b/src/plugins/whoReacted.tsx index a14ca3a2a..a260bac00 100644 --- a/src/plugins/whoReacted.tsx +++ b/src/plugins/whoReacted.tsx @@ -32,12 +32,13 @@ const ReactionStore = findByPropsLazy("getReactions"); const queue = new Queue(); -function fetchReactions(msg: Message, emoji: ReactionEmoji) { +function fetchReactions(msg: Message, emoji: ReactionEmoji, type: number) { const key = emoji.name + (emoji.id ? `:${emoji.id}` : ""); return RestAPI.get({ url: `/channels/${msg.channel_id}/messages/${msg.id}/reactions/${key}`, query: { - limit: 100 + limit: 100, + type }, oldFormErrors: true }) @@ -46,18 +47,19 @@ function fetchReactions(msg: Message, emoji: ReactionEmoji) { channelId: msg.channel_id, messageId: msg.id, users: res.body, - emoji + emoji, + reactionType: type })) .catch(console.error) .finally(() => sleep(250)); } -function getReactionsWithQueue(msg: Message, e: ReactionEmoji) { - const key = `${msg.id}:${e.name}:${e.id ?? ""}`; +function getReactionsWithQueue(msg: Message, e: ReactionEmoji, type: number) { + const key = `${msg.id}:${e.name}:${e.id ?? ""}:${type}`; const cache = ReactionStore.__getLocalVars().reactions[key] ??= { fetched: false, users: {} }; if (!cache.fetched) { queue.unshift(() => - fetchReactions(msg, e) + fetchReactions(msg, e, type) ); cache.fetched = true; } @@ -104,7 +106,7 @@ export default definePlugin({ ); }, - _renderUsers({ message, emoji }: RootObject) { + _renderUsers({ message, emoji, type }: RootObject) { const forceUpdate = useForceUpdater(); React.useEffect(() => { const cb = (e: any) => { @@ -116,7 +118,7 @@ export default definePlugin({ return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); }, [message.id]); - const reactions = getReactionsWithQueue(message, emoji); + const reactions = getReactionsWithQueue(message, emoji, type); const users = Object.values(reactions).filter(Boolean) as User[]; return ( From 614234ad208963a8068b156fd04760be6013a554 Mon Sep 17 00:00:00 2001 From: Ven <vendicated@riseup.net> Date: Sun, 12 Feb 2023 19:43:57 +0100 Subject: [PATCH 098/114] MessageLinkEmbeds: Prevent infinite cycles (#488) --- src/plugins/messageLinkEmbeds.tsx | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/plugins/messageLinkEmbeds.tsx b/src/plugins/messageLinkEmbeds.tsx index d08f25e8c..a6b63d82c 100644 --- a/src/plugins/messageLinkEmbeds.tsx +++ b/src/plugins/messageLinkEmbeds.tsx @@ -139,6 +139,16 @@ interface MessageEmbedProps { guildID: string; } +function withEmbeddedBy(message: Message, embeddedBy: string[]) { + return new Proxy(message, { + get(_, prop) { + if (prop === "vencordEmbeddedBy") return embeddedBy; + // @ts-ignore ts so bad + return Reflect.get(...arguments); + } + }); +} + export default definePlugin({ name: "MessageLinkEmbeds", description: "Adds a preview to messages that link another message", @@ -194,19 +204,24 @@ export default definePlugin({ messageEmbedAccessory(props) { const { message }: { message: Message; } = props; + // @ts-ignore + const embeddedBy: string[] = message.vencordEmbeddedBy ?? []; const accessories = [] as (JSX.Element | null)[]; let match = null as RegExpMatchArray | null; while ((match = this.messageLinkRegex.exec(message.content!)) !== null) { const [_, guildID, channelID, messageID] = match; + if (embeddedBy.includes(messageID)) { + continue; + } const linkedChannel = ChannelStore.getChannel(channelID); if (!linkedChannel || (guildID !== "@me" && !PermissionStore.can(1024n /* view channel */, linkedChannel))) { continue; } - let linkedMessage = messageCache[messageID]?.message as Message; + let linkedMessage = messageCache[messageID]?.message; if (!linkedMessage) { linkedMessage ??= MessageStore.getMessage(channelID, messageID); if (linkedMessage) messageCache[messageID] = { message: linkedMessage, fetched: true }; @@ -223,7 +238,7 @@ export default definePlugin({ } } const messageProps: MessageEmbedProps = { - message: linkedMessage, + message: withEmbeddedBy(linkedMessage, [...embeddedBy, message.id]), channel: linkedChannel, guildID }; @@ -267,7 +282,7 @@ export default definePlugin({ } }} renderDescription={() => { - return <div key={message.id} className={classNames.join(" ")} > + return <div key={message.id} className={classNames.join(" ")}> <ChannelMessage id={`message-link-embeds-${message.id}`} message={message} From c154965d7072179cca3933bb5f387f8ae1d6711b Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Sun, 12 Feb 2023 21:06:49 +0100 Subject: [PATCH 099/114] TypingTweaks: Fix crash after changing language --- src/plugins/typingTweaks.tsx | 35 ++++++++++++++--------------------- 1 file changed, 14 insertions(+), 21 deletions(-) diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx index f03779c07..454817994 100644 --- a/src/plugins/typingTweaks.tsx +++ b/src/plugins/typingTweaks.tsx @@ -22,6 +22,7 @@ import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy } from "@webpack"; import { GuildMemberStore, React, RelationshipStore } from "@webpack/common"; +import { User } from "discord-types/general"; const Avatar = findByCodeLazy(".Positions.TOP,spacing:"); @@ -64,36 +65,28 @@ export default definePlugin({ replace: "return $1" } }, - // Changes indicator to format message with the typing users - { - find: '"SEVERAL_USERS_TYPING":"', - replacement: { - match: /("SEVERAL_USERS_TYPING"):".+?"/, - replace: "$1:\"**!!{a}!!**, **!!{b}!!**, and {c} others are typing...\"" - }, - predicate: () => settings.store.alternativeFormatting - }, - { - find: ",\"SEVERAL_USERS_TYPING\",\"", - replacement: { - match: /(?<="SEVERAL_USERS_TYPING",)".+?"/, - replace: '"**!!{a}!!**, **!!{b}!!**, and {c} others are typing..."' - }, - predicate: () => settings.store.alternativeFormatting - }, // Adds the alternative formatting for several users typing { find: "getCooldownTextStyle", replacement: { - match: /(\i)\.length\?.\..\.Messages\.THREE_USERS_TYPING.format\(\{a:(\i),b:(\i),c:.}\).+?SEVERAL_USERS_TYPING/, - replace: "$&.format({a:$2,b:$3,c:$1.length-2})" + match: /((\i)\.length\?.\..\.Messages\.THREE_USERS_TYPING.format\(\{a:(\i),b:(\i),c:.}\)):.+?SEVERAL_USERS_TYPING/, + replace: "$1:$self.buildSeveralUsers({a:$3,b:$4,c:$2.length-2})" }, predicate: () => settings.store.alternativeFormatting } ], settings, - mutateChildren(props, users, children) { + buildSeveralUsers({ a, b, c }: { a: string, b: string, c: number; }) { + return [ + <strong key="0">{a}</strong>, + ", ", + <strong key="2">{b}</strong>, + `, and ${c} others are typing...` + ]; + }, + + mutateChildren(props: any, users: User[], children: any) { if (!Array.isArray(children)) return children; let element = 0; @@ -101,7 +94,7 @@ export default definePlugin({ return children.map(c => c.type === "strong" ? <this.TypingUser {...props} user={users[element++]} /> : c); }, - TypingUser: ErrorBoundary.wrap(({ user, guildId }) => { + TypingUser: ErrorBoundary.wrap(({ user, guildId }: { user: User, guildId: string; }) => { return <strong style={{ display: "grid", gridAutoFlow: "column", From 2b0c25b45ccd66612899bb493a59d275458cd46d Mon Sep 17 00:00:00 2001 From: Sammy <sammy@sammcheese.net> Date: Sun, 12 Feb 2023 22:10:03 +0100 Subject: [PATCH 100/114] Feat(InvisibleChat): Add Autodecryption (#490) Co-authored-by: Ven <vendicated@riseup.net> --- .../components/DecryptionModal.tsx | 2 +- src/plugins/invisibleChat/index.tsx | 48 +++++++++++++++++-- 2 files changed, 44 insertions(+), 6 deletions(-) diff --git a/src/plugins/invisibleChat/components/DecryptionModal.tsx b/src/plugins/invisibleChat/components/DecryptionModal.tsx index 7d7044414..a17239bc5 100644 --- a/src/plugins/invisibleChat/components/DecryptionModal.tsx +++ b/src/plugins/invisibleChat/components/DecryptionModal.tsx @@ -51,7 +51,7 @@ export function DecModal(props: any) { <Button color={Button.Colors.GREEN} onClick={() => { - const toSend = decrypt(secret, password); + const toSend = decrypt(secret, password, true); if (!toSend || !props?.message) return; // @ts-expect-error Vencord.Plugins.plugins.InvisibleChat.buildEmbed(props?.message, toSend); diff --git a/src/plugins/invisibleChat/index.tsx b/src/plugins/invisibleChat/index.tsx index 519a9a6e0..a83d47b84 100644 --- a/src/plugins/invisibleChat/index.tsx +++ b/src/plugins/invisibleChat/index.tsx @@ -17,11 +17,13 @@ */ import { addButton, removeButton } from "@api/MessagePopover"; +import { definePluginSettings } from "@api/settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getStegCloak } from "@utils/dependencies"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; import { Button, ButtonLooks, ButtonWrapperClasses, ChannelStore, FluxDispatcher, Tooltip } from "@webpack/common"; +import { Message } from "discord-types/general"; import { buildDecModal } from "./components/DecryptionModal"; import { buildEncModal } from "./components/EncryptionModal"; @@ -105,6 +107,13 @@ function ChatBarIcon() { ); } +const settings = definePluginSettings({ + savedPasswords: { + type: OptionType.STRING, + default: "password, Password", + description: "Saved Passwords (Seperated with a , )" + } +}); export default definePlugin({ name: "InvisibleChat", @@ -133,7 +142,7 @@ export default definePlugin({ URL_REGEX: new RegExp( /(http(s)?:\/\/.)?(www\.)?[-a-zA-Z0-9@:%._+~#=]{2,256}\.[a-z]{2,6}\b([-a-zA-Z0-9@:%_+.~#?&//=]*)/, ), - + settings, async start() { const { default: StegCloak } = await getStegCloak(); steggo = new StegCloak(true, false); @@ -145,7 +154,12 @@ export default definePlugin({ icon: this.popOverIcon, message: message, channel: ChannelStore.getChannel(message.channel_id), - onClick: () => buildDecModal({ message }) + onClick: async () => { + await iteratePasswords(message).then((res: string | false) => { + if (res) return void this.buildEmbed(message, res); + return void buildDecModal({ message }); + }); + } } : null; }); @@ -213,7 +227,31 @@ export function encrypt(secret: string, password: string, cover: string): string return steggo.hide(secret + "\u200b", password, cover); } -export function decrypt(secret: string, password: string): string { - return steggo.reveal(secret, password).replace("\u200b", ""); +export function decrypt(secret: string, password: string, removeIndicator: boolean): string { + const decrypted = steggo.reveal(secret, password); + return removeIndicator ? decrypted.replace("\u200b", "") : decrypted; } +export function isCorrectPassword(result: string): boolean { + return result.endsWith("\u200b"); +} + +export async function iteratePasswords(message: Message): Promise<string | false> { + const passwords = settings.store.savedPasswords.split(",").map(s => s.trim()); + + if (!message?.content || !passwords?.length) return false; + + let { content } = message; + + // we use an extra variable so we dont have to edit the message content directly + if (/^\W/.test(message.content)) content = `d ${message.content}d`; + + for (let i = 0; i < passwords.length; i++) { + const result = decrypt(content, passwords[i], false); + if (isCorrectPassword(result)) { + return result; + } + } + + return false; +} From 68055977d24c25cd2d086cba173abb392059b03d Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 14 Feb 2023 19:20:10 +0100 Subject: [PATCH 101/114] NotificationAPI: Correctly request browser permissions --- src/api/Notifications/Notifications.tsx | 11 +++++++++-- src/components/VencordSettings/VencordTab.tsx | 7 +++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/src/api/Notifications/Notifications.tsx b/src/api/Notifications/Notifications.tsx index 9c599aa65..46472ad36 100644 --- a/src/api/Notifications/Notifications.tsx +++ b/src/api/Notifications/Notifications.tsx @@ -76,8 +76,15 @@ function shouldBeNative() { return false; } -export function showNotification(data: NotificationData) { - if (shouldBeNative()) { +export async function requestPermission() { + return ( + Notification.permission === "granted" || + (Notification.permission !== "denied" && (await Notification.requestPermission()) === "granted") + ); +} + +export async function showNotification(data: NotificationData) { + if (shouldBeNative() && await requestPermission()) { const { title, body, icon, image, onClick = null, onClose = null } = data; const n = new Notification(title, { body, diff --git a/src/components/VencordSettings/VencordTab.tsx b/src/components/VencordSettings/VencordTab.tsx index 6ea1ca9d2..98808728a 100644 --- a/src/components/VencordSettings/VencordTab.tsx +++ b/src/components/VencordSettings/VencordTab.tsx @@ -21,6 +21,7 @@ import { useSettings } from "@api/settings"; import { classNameFactory } from "@api/Styles"; import DonateButton from "@components/DonateButton"; import ErrorBoundary from "@components/ErrorBoundary"; +import { ErrorCard } from "@components/ErrorCard"; import IpcEvents from "@utils/IpcEvents"; import { Margins } from "@utils/margins"; import { identity, useAwaiter } from "@utils/misc"; @@ -141,6 +142,12 @@ function VencordSettings() { <Forms.FormTitle tag="h5">Notification Style</Forms.FormTitle> + {notifSettings.useNative !== "never" && Notification.permission === "denied" && ( + <ErrorCard style={{ padding: "1em" }} className={Margins.bottom8}> + <Forms.FormTitle tag="h5">Desktop Notification Permission denied</Forms.FormTitle> + <Forms.FormText>You have denied Notification Permissions. Thus, Desktop notifications will not work!</Forms.FormText> + </ErrorCard> + )} <Forms.FormText className={Margins.bottom8}> Some plugins may show you notifications. These come in two styles: <ul> From 58270ef925b3f1f00d9f41ef8c1da2b8812df8b1 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Tue, 14 Feb 2023 19:22:01 +0100 Subject: [PATCH 102/114] bump to v1.0.5 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 520f5db0e..428ab4652 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.0.4", + "version": "1.0.5", "description": "The cutest Discord client mod", "keywords": [], "homepage": "https://github.com/Vendicated/Vencord#readme", From 5c1519156b5cd7e513fd1bfbc71ed263858c5885 Mon Sep 17 00:00:00 2001 From: Lewis Crichton <github@lewisakura.moe> Date: Thu, 16 Feb 2023 00:46:14 +0000 Subject: [PATCH 103/114] feat(plugin): ColorSighted (#501) --- src/plugins/colorSighted.ts | 37 +++++++++++++++++++++++++++++++++++++ src/utils/constants.ts | 4 ++++ 2 files changed, 41 insertions(+) create mode 100644 src/plugins/colorSighted.ts diff --git a/src/plugins/colorSighted.ts b/src/plugins/colorSighted.ts new file mode 100644 index 000000000..659e341d9 --- /dev/null +++ b/src/plugins/colorSighted.ts @@ -0,0 +1,37 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2023 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "ColorSighted", + description: "Removes the colorblind-friendly icons from statuses, just like 2015-2017 Discord", + authors: [Devs.lewisakura], + patches: [ + { + find: "Masks.STATUS_ONLINE", + replacement: { + // we can use global replacement here - these are specific to the status icons and are used nowhere else, + // so it keeps the patch and plugin small and simple + match: /Masks\.STATUS_(?:IDLE|DND|STREAMING|OFFLINE)/g, + replace: "Masks.STATUS_ONLINE" + } + } + ] +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index de63de827..4f24d03b4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -196,5 +196,9 @@ export const Devs = /* #__PURE__*/ Object.freeze({ whqwert: { name: "whqwert", id: 586239091520176128n + }, + lewisakura: { + name: "lewisakura", + id: 96269247411400704n } }); From 60ccd8cc25d86f10a166a31faf1cb996deb74621 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 15 Feb 2023 22:00:09 -0300 Subject: [PATCH 104/114] Various plugin fixes (#492) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/fakeNitro.ts | 38 ++++++++++++++++++------ src/plugins/showHiddenChannels/index.tsx | 2 +- src/plugins/vcDoubleClick.ts | 6 ++-- src/plugins/whoReacted.tsx | 7 +++++ 4 files changed, 40 insertions(+), 13 deletions(-) diff --git a/src/plugins/fakeNitro.ts b/src/plugins/fakeNitro.ts index e5ac3b9ce..dd8ff19e4 100644 --- a/src/plugins/fakeNitro.ts +++ b/src/plugins/fakeNitro.ts @@ -27,6 +27,17 @@ import { ChannelStore, UserStore } from "@webpack/common"; const DRAFT_TYPE = 0; const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR"); +enum EmojiIntentions { + REACTION = 0, + STATUS = 1, + COMMUNITY_CONTENT = 2, + CHAT = 3, + GUILD_STICKER_RELATED_EMOJI = 4, + GUILD_ROLE_BENEFIT_EMOJI = 5, + COMMUNITY_CONTENT_ONLY = 6, + SOUNDBOARD = 7 +} + interface BaseSticker { available: boolean; description: string; @@ -64,17 +75,26 @@ export default definePlugin({ patches: [ { - find: "canUseAnimatedEmojis:function", + find: ".PREMIUM_LOCKED;", predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true, replacement: [ - "canUseAnimatedEmojis", - "canUseEmojisEverywhere" - ].map(func => { - return { - match: new RegExp(`${func}:function\\(.+?\\{`), - replace: "$&return true;" - }; - }) + { + match: /(?<=(?<intention>\i)=\i\.intention.+?\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i)(?=\))/g, + replace: ",$<intention>" + }, + { + match: /(?<=,\i=)\i\.\i\.can\(\i\.\i\.USE_EXTERNAL_EMOJIS,\i\)(?=;)/, + replace: "true" + } + ] + }, + { + find: "canUseAnimatedEmojis:function", + predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true, + replacement: { + match: /(?<=(?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){/g, + replace: `,fakeNitroIntention){return fakeNitroIntention===undefined||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention);` + } }, { find: "canUseAnimatedEmojis:function", diff --git a/src/plugins/showHiddenChannels/index.tsx b/src/plugins/showHiddenChannels/index.tsx index a4c7decd9..fe14fe68b 100644 --- a/src/plugins/showHiddenChannels/index.tsx +++ b/src/plugins/showHiddenChannels/index.tsx @@ -263,7 +263,7 @@ export default definePlugin({ replacement: [ { // Remove the divider and the open chat button for the HiddenChannelLockScreen - match: /(?<=function \i\((?<props>\i)\).{1,1800}"more-options-popout"\)\);if\()/, + match: /(?<=function \i\((?<props>\i)\).{1,2000}"more-options-popout"\)\);if\()/, replace: "(!$self.isHiddenChannel($<props>.channel)||$<props>.inCall)&&" }, { diff --git a/src/plugins/vcDoubleClick.ts b/src/plugins/vcDoubleClick.ts index 39b8a059c..64c676cfc 100644 --- a/src/plugins/vcDoubleClick.ts +++ b/src/plugins/vcDoubleClick.ts @@ -48,10 +48,10 @@ export default definePlugin({ }, { // channel mentions - find: 'className:"channelMention",iconType:(', + find: ".EMOJI_IN_MESSAGE_HOVER", replacement: { - match: /onClick:(.{1,3}),/, - replace: "onClick:(_vcEv)=>(_vcEv.detail>=2||_vcEv.target.className.includes('MentionText'))&&($1)(),", + match: /onClick:(\i)(?=,.{0,30}className:"channelMention")/, + replace: "onClick:(_vcEv)=>(_vcEv.detail>=2||_vcEv.target.className.includes('MentionText'))&&($1)()", } } ], diff --git a/src/plugins/whoReacted.tsx b/src/plugins/whoReacted.tsx index a260bac00..8ab1c5f7d 100644 --- a/src/plugins/whoReacted.tsx +++ b/src/plugins/whoReacted.tsx @@ -121,6 +121,13 @@ export default definePlugin({ const reactions = getReactionsWithQueue(message, emoji, type); const users = Object.values(reactions).filter(Boolean) as User[]; + for (const user of users) { + FluxDispatcher.dispatch({ + type: "USER_UPDATE", + user + }); + } + return ( <div style={{ marginLeft: "0.5em", transform: "scale(0.9)" }} From 27fc20118be331acf363a0eca63b341f8aa8719c Mon Sep 17 00:00:00 2001 From: Lewis Crichton <lewi@lewisakura.moe> Date: Thu, 16 Feb 2023 01:50:42 +0000 Subject: [PATCH 105/114] feat(plugin): RoleColorEverywhere (#482) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/roleColorEverywhere.tsx | 123 ++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 src/plugins/roleColorEverywhere.tsx diff --git a/src/plugins/roleColorEverywhere.tsx b/src/plugins/roleColorEverywhere.tsx new file mode 100644 index 000000000..5d48da06c --- /dev/null +++ b/src/plugins/roleColorEverywhere.tsx @@ -0,0 +1,123 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { definePluginSettings } from "@api/settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common"; + +const settings = definePluginSettings({ + chatMentions: { + type: OptionType.BOOLEAN, + default: true, + description: "Show role colors in chat mentions (including in the message box)", + restartNeeded: true + }, + memberList: { + type: OptionType.BOOLEAN, + default: true, + description: "Show role colors in member list role headers", + restartNeeded: true + }, + voiceUsers: { + type: OptionType.BOOLEAN, + default: true, + description: "Show role colors in the voice chat user list", + restartNeeded: true + } +}); + +export default definePlugin({ + name: "RoleColorEverywhere", + authors: [Devs.KingFish, Devs.lewisakura], + description: "Adds the top role color anywhere possible", + patches: [ + // Chat Mentions + { + find: 'className:"mention"', + replacement: [ + { + match: /user:(\i),channelId:(\i).{0,300}?"@"\.concat\(.+?\)/, + replace: "$&,color:$self.getUserColor($1.id,{channelId:$2})" + } + ], + predicate: () => settings.store.chatMentions, + }, + // Slate + { + // taken from CommandsAPI + find: ".source,children", + replacement: [ + { + match: /function \i\((\i)\).{5,20}id.{5,20}guildId.{5,10}channelId.{100,150}hidePersonalInformation.{5,50}jsx.{5,20},{/, + replace: "$&color:$self.getUserColor($1.id,{guildId:$1.guildId})," + } + ], + predicate: () => settings.store.chatMentions, + }, + // Member List Role Names + { + find: ".memberGroupsPlaceholder", + replacement: [ + { + match: /(memo\(\(function\((\i)\).{300,500}CHANNEL_MEMBERS_A11Y_LABEL.{100,200}roleIcon.{5,20}null,).," \u2014 ",.\]/, + replace: "$1$self.roleGroupColor($2)]" + }, + ], + predicate: () => settings.store.memberList, + }, + // Voice chat users + { + find: "renderPrioritySpeaker", + replacement: [ + { + match: /renderName=function\(\).{50,75}speaking.{50,100}jsx.{5,10}{/, + replace: "$&...$self.getVoiceProps(this.props)," + } + ], + predicate: () => settings.store.voiceUsers, + } + ], + settings, + + getColor(userId: string, { channelId, guildId }: { channelId?: string; guildId?: string; }) { + if (!(guildId ??= ChannelStore.getChannel(channelId!)?.guild_id)) return null; + return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null; + }, + getUserColor(userId: string, ids: { channelId?: string; guildId?: string; }) { + const colorString = this.getColor(userId, ids); + return colorString && parseInt(colorString.slice(1), 16); + }, + roleGroupColor({ id, count, title, guildId }: { id: string; count: number; title: string; guildId: string; }) { + const guild = GuildStore.getGuild(guildId); + const role = guild?.roles[id]; + + return <span style={{ + color: role?.colorString, + fontWeight: "unset", + letterSpacing: ".05em" + }}>{title} — {count}</span>; + }, + getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) { + return { + style: { + color: this.getColor(userId, { guildId }) + } + }; + } +}); From 224ae979f2f55f01297eb91597cdc4703b93aa74 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Wed, 15 Feb 2023 23:57:57 -0300 Subject: [PATCH 106/114] feat(plugins): Typing Indicator (#502) --- src/plugins/typingIndicator.tsx | 135 ++++++++++++++++++++++++++++++++ src/plugins/typingTweaks.tsx | 18 +++-- 2 files changed, 145 insertions(+), 8 deletions(-) create mode 100644 src/plugins/typingIndicator.tsx diff --git a/src/plugins/typingIndicator.tsx b/src/plugins/typingIndicator.tsx new file mode 100644 index 000000000..f57eab3eb --- /dev/null +++ b/src/plugins/typingIndicator.tsx @@ -0,0 +1,135 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2022 Vendicated and contributors + * + * 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 <https://www.gnu.org/licenses/>. +*/ + +import { definePluginSettings, Settings } from "@api/settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import { LazyComponent } from "@utils/misc"; +import definePlugin, { OptionType } from "@utils/types"; +import { find, findLazy, findStoreLazy } from "@webpack"; +import { ChannelStore, GuildMemberStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; + +import { buildSeveralUsers } from "./typingTweaks"; + +const ThreeDots = LazyComponent(() => find(m => m.type?.render?.toString()?.includes("().dots"))); + +const TypingStore = findStoreLazy("TypingStore"); +const UserGuildSettingsStore = findStoreLazy("UserGuildSettingsStore"); + +const Formatters = findLazy(m => m.Messages?.SEVERAL_USERS_TYPING); + +function getDisplayName(guildId: string, userId: string) { + return GuildMemberStore.getNick(guildId, userId) ?? UserStore.getUser(userId).username; +} + +function TypingIndicator({ channelId }: { channelId: string; }) { + const typingUsers: Record<string, number> = useStateFromStores( + [TypingStore], + () => ({ ...TypingStore.getTypingUsers(channelId) as Record<string, number> }), + null, + (old, current) => { + const oldKeys = Object.keys(old); + const currentKeys = Object.keys(current); + + return oldKeys.length === currentKeys.length && JSON.stringify(oldKeys) === JSON.stringify(currentKeys); + } + ); + + const guildId = ChannelStore.getChannel(channelId).guild_id; + + if (!settings.store.includeMutedChannels) { + const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId); + if (isChannelMuted) return null; + } + + delete typingUsers[UserStore.getCurrentUser().id]; + + const typingUsersArray = Object.keys(typingUsers); + let tooltipText: string; + + switch (typingUsersArray.length) { + case 0: break; + case 1: { + tooltipText = Formatters.Messages.ONE_USER_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]) }); + break; + } + case 2: { + tooltipText = Formatters.Messages.TWO_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]) }); + break; + } + case 3: { + tooltipText = Formatters.Messages.THREE_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[1]) }); + break; + } + default: { + tooltipText = Settings.plugins.TypingTweaks.enabled + ? buildSeveralUsers({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: typingUsersArray.length - 2 }) + : Formatters.Messages.SEVERAL_USERS_TYPING; + break; + } + } + + if (typingUsersArray.length > 0) { + return ( + <Tooltip text={tooltipText!}> + {({ onMouseLeave, onMouseEnter }) => ( + <div + style={{ marginLeft: 6, zIndex: 0, cursor: "pointer" }} + onMouseLeave={onMouseLeave} + onMouseEnter={onMouseEnter} + > + <ThreeDots dotRadius={3} themed={true} /> + </div> + )} + </Tooltip> + ); + } + + return null; +} + +const settings = definePluginSettings({ + includeMutedChannels: { + type: OptionType.BOOLEAN, + description: "Whether to show the typing indicator for muted channels.", + default: false + } +}); + +export default definePlugin({ + name: "TypingIndicator", + description: "Adds an indicator if someone is typing on a channel.", + authors: [Devs.Nuckyz], + settings, + + patches: [ + { + find: ".UNREAD_HIGHLIGHT", + replacement: { + match: /(?<=(?<channel>\i)=\i\.channel,.+?\(\)\.children.+?:null)/, + replace: ",$self.TypingIndicator($<channel>.id)" + } + } + ], + + TypingIndicator: (channelId: string) => ( + <ErrorBoundary noop> + <TypingIndicator channelId={channelId} /> + </ErrorBoundary> + ), +}); diff --git a/src/plugins/typingTweaks.tsx b/src/plugins/typingTweaks.tsx index 454817994..db8c438b5 100644 --- a/src/plugins/typingTweaks.tsx +++ b/src/plugins/typingTweaks.tsx @@ -44,6 +44,15 @@ const settings = definePluginSettings({ } }); +export function buildSeveralUsers({ a, b, c }: { a: string, b: string, c: number; }) { + return [ + <strong key="0">{a}</strong>, + ", ", + <strong key="2">{b}</strong>, + `, and ${c} others are typing...` + ]; +} + export default definePlugin({ name: "TypingTweaks", description: "Show avatars and role colours in the typing indicator", @@ -77,14 +86,7 @@ export default definePlugin({ ], settings, - buildSeveralUsers({ a, b, c }: { a: string, b: string, c: number; }) { - return [ - <strong key="0">{a}</strong>, - ", ", - <strong key="2">{b}</strong>, - `, and ${c} others are typing...` - ]; - }, + buildSeveralUsers, mutateChildren(props: any, users: User[], children: any) { if (!Array.isArray(children)) return children; From fbbc198b1bc406aa3e82fba2b675b3132b0dcb6f Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 16 Feb 2023 22:30:55 +0100 Subject: [PATCH 107/114] Fix PlatformIndicator --- src/plugins/platformIndicators.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/platformIndicators.tsx b/src/plugins/platformIndicators.tsx index f28f9cffb..0e3d61a99 100644 --- a/src/plugins/platformIndicators.tsx +++ b/src/plugins/platformIndicators.tsx @@ -55,7 +55,7 @@ const Icons = { }; type Platform = keyof typeof Icons; -const getStatusColor = findByCodeLazy("STATUS_YELLOW", "TWITCH", "STATUS_GREY"); +const getStatusColor = findByCodeLazy(".TWITCH", ".STREAMING", ".INVISIBLE"); const PlatformIcon = ({ platform, status }: { platform: Platform, status: string; }) => { const tooltip = platform[0].toUpperCase() + platform.slice(1); From 3cad0d60b4524b3c58141835e458b129fa75bb12 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 16 Feb 2023 22:40:19 +0100 Subject: [PATCH 108/114] Silly Discord changed a bunch of css vars --- src/components/Switch.tsx | 4 ++-- src/plugins/memberCount.tsx | 8 ++++---- src/plugins/silentTyping.tsx | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/components/Switch.tsx b/src/components/Switch.tsx index a18a7e492..11a3fe02e 100644 --- a/src/components/Switch.tsx +++ b/src/components/Switch.tsx @@ -26,8 +26,8 @@ interface SwitchProps { disabled?: boolean; } -const SWITCH_ON = "var(--status-green-600)"; -const SWITCH_OFF = "var(--primary-dark-400)"; +const SWITCH_ON = "var(--green-360)"; +const SWITCH_OFF = "var(--primary-400)"; const SwitchClasses = findByPropsLazy("slider", "input", "container"); export function Switch({ checked, onChange, disabled }: SwitchProps) { diff --git a/src/plugins/memberCount.tsx b/src/plugins/memberCount.tsx index e17157e75..37626141a 100644 --- a/src/plugins/memberCount.tsx +++ b/src/plugins/memberCount.tsx @@ -56,7 +56,7 @@ function MemberCount() { <div {...props}> <span style={{ - backgroundColor: "var(--status-green-600)", + backgroundColor: "var(--green-360)", width: "12px", height: "12px", borderRadius: "50%", @@ -64,7 +64,7 @@ function MemberCount() { marginRight: "0.5em" }} /> - <span style={{ color: "var(--status-green-600)" }}>{online}</span> + <span style={{ color: "var(--green-360)" }}>{online}</span> </div> )} </Tooltip> @@ -76,13 +76,13 @@ function MemberCount() { width: "6px", height: "6px", borderRadius: "50%", - border: "3px solid var(--status-grey-500)", + border: "3px solid var(--primary-400)", display: "inline-block", marginRight: "0.5em", marginLeft: "1em" }} /> - <span style={{ color: "var(--status-grey-500)" }}>{total}</span> + <span style={{ color: "var(--primary-400)" }}>{total}</span> </div> )} </Tooltip> diff --git a/src/plugins/silentTyping.tsx b/src/plugins/silentTyping.tsx index 442be3927..78bb92ea0 100644 --- a/src/plugins/silentTyping.tsx +++ b/src/plugins/silentTyping.tsx @@ -56,7 +56,7 @@ function SilentTypingToggle() { <div className={ButtonWrapperClasses.buttonWrapper}> <svg width="24" height="24" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 576 512"> <path fill="currentColor" d="M528 448H48c-26.51 0-48-21.49-48-48V112c0-26.51 21.49-48 48-48h480c26.51 0 48 21.49 48 48v288c0 26.51-21.49 48-48 48zM128 180v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm-336 96v-40c0-6.627-5.373-12-12-12H76c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12zm288 0v-40c0-6.627-5.373-12-12-12H172c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h232c6.627 0 12-5.373 12-12zm96 0v-40c0-6.627-5.373-12-12-12h-40c-6.627 0-12 5.373-12 12v40c0 6.627 5.373 12 12 12h40c6.627 0 12-5.373 12-12z" /> - {isEnabled && <path d="M13 432L590 48" stroke="var(--status-red-500)" stroke-width="72" stroke-linecap="round" />} + {isEnabled && <path d="M13 432L590 48" stroke="var(--red-500)" stroke-width="72" stroke-linecap="round" />} </svg> </div> </Button> From 6807820f6c47fbd4afb8d2b1d83469b60490dd65 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 16 Feb 2023 22:46:51 +0100 Subject: [PATCH 109/114] Badges should use ErrorBoundaries --- src/api/Badges.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 3607f37eb..d4aabaf21 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -16,6 +16,7 @@ * along with this program. If not, see <https://www.gnu.org/licenses/>. */ +import ErrorBoundary from "@components/ErrorBoundary"; import { User } from "discord-types/general"; import { ComponentType, HTMLProps } from "react"; @@ -52,6 +53,7 @@ const Badges = new Set<ProfileBadge>(); * @param badge The badge to register */ export function addBadge(badge: ProfileBadge) { + badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true }); Badges.add(badge); } From 9420735bc7f545a6fc3488aafb494422e45e4df8 Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Thu, 16 Feb 2023 23:40:38 +0100 Subject: [PATCH 110/114] Version 1.0.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 428ab4652..d3c73ecd2 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.0.5", + "version": "1.0.6", "description": "The cutest Discord client mod", "keywords": [], "homepage": "https://github.com/Vendicated/Vencord#readme", From 3ca87848e575d19c93216c687a3ef61928ac4a9f Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Thu, 16 Feb 2023 21:31:55 -0300 Subject: [PATCH 111/114] TypingIndicator: Fix a dumb (#503) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/typingIndicator.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/typingIndicator.tsx b/src/plugins/typingIndicator.tsx index f57eab3eb..27c143b02 100644 --- a/src/plugins/typingIndicator.tsx +++ b/src/plugins/typingIndicator.tsx @@ -73,7 +73,7 @@ function TypingIndicator({ channelId }: { channelId: string; }) { break; } case 3: { - tooltipText = Formatters.Messages.THREE_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[1]) }); + tooltipText = Formatters.Messages.THREE_USERS_TYPING.format({ a: getDisplayName(guildId, typingUsersArray[0]), b: getDisplayName(guildId, typingUsersArray[1]), c: getDisplayName(guildId, typingUsersArray[2]) }); break; } default: { From ff1f3376998bcf58a396f43a92f4e5395f69876c Mon Sep 17 00:00:00 2001 From: Vendicated <vendicated@riseup.net> Date: Fri, 17 Feb 2023 15:37:38 +0100 Subject: [PATCH 112/114] Fix QuickCSS on electron 20+ --- src/ipcMain/index.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/ipcMain/index.ts b/src/ipcMain/index.ts index bae679330..6fb31d174 100644 --- a/src/ipcMain/index.ts +++ b/src/ipcMain/index.ts @@ -91,7 +91,8 @@ ipcMain.handle(IpcEvents.OPEN_MONACO_EDITOR, async () => { webPreferences: { preload: join(__dirname, "preload.js"), contextIsolation: true, - nodeIntegration: false + nodeIntegration: false, + sandbox: false } }); await win.loadURL(`data:text/html;base64,${monacoHtml}`); From e14ec96e214f60058155560717b01a553f58c2c1 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 17 Feb 2023 23:32:02 -0300 Subject: [PATCH 113/114] feat(FakeNitro): Bypass client themes and fixes (#504) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/fakeNitro.ts | 84 +++++++++++++++++++++++++++------------- 1 file changed, 57 insertions(+), 27 deletions(-) diff --git a/src/plugins/fakeNitro.ts b/src/plugins/fakeNitro.ts index dd8ff19e4..22ffb8009 100644 --- a/src/plugins/fakeNitro.ts +++ b/src/plugins/fakeNitro.ts @@ -22,11 +22,14 @@ import { Devs } from "@utils/constants"; import { ApngDisposeOp, getGifEncoder, importApngJs } from "@utils/dependencies"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; -import { ChannelStore, UserStore } from "@webpack/common"; +import { ChannelStore, PermissionStore, UserStore } from "@webpack/common"; const DRAFT_TYPE = 0; const promptToUpload = findByCodeLazy("UPLOAD_FILE_LIMIT_ERROR"); +const USE_EXTERNAL_EMOJIS = 1n << 18n; +const USE_EXTERNAL_STICKERS = 1n << 37n; + enum EmojiIntentions { REACTION = 0, STATUS = 1, @@ -69,8 +72,8 @@ migratePluginSettings("FakeNitro", "NitroBypass"); export default definePlugin({ name: "FakeNitro", - authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity], - description: "Allows you to stream in nitro quality and send fake emojis/stickers.", + authors: [Devs.Arjix, Devs.D3SOX, Devs.Ven, Devs.obscurity, Devs.captain], + description: "Allows you to stream in nitro quality, send fake emojis/stickers and use client themes.", dependencies: ["MessageEventsAPI"], patches: [ @@ -79,12 +82,16 @@ export default definePlugin({ predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true, replacement: [ { - match: /(?<=(?<intention>\i)=\i\.intention.+?\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i)(?=\))/g, - replace: ",$<intention>" + match: /(?<=(?<intention>\i)=\i\.intention)/, + replace: ",fakeNitroIntention=$<intention>" }, { - match: /(?<=,\i=)\i\.\i\.can\(\i\.\i\.USE_EXTERNAL_EMOJIS,\i\)(?=;)/, - replace: "true" + match: /(?<=\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i)(?=\))/g, + replace: ",fakeNitroIntention" + }, + { + match: /(?<=&&!\i&&)!(?<canUseExternal>\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, + replace: `(!$<canUseExternal>&&![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))` } ] }, @@ -92,12 +99,12 @@ export default definePlugin({ find: "canUseAnimatedEmojis:function", predicate: () => Settings.plugins.FakeNitro.enableEmojiBypass === true, replacement: { - match: /(?<=(?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){/g, - replace: `,fakeNitroIntention){return fakeNitroIntention===undefined||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention);` + match: /(?<=(?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\((?<user>\i))\){(?<premiumCheck>.+?\))/g, + replace: `,fakeNitroIntention){$<premiumCheck>||fakeNitroIntention===undefined||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` } }, { - find: "canUseAnimatedEmojis:function", + find: "canUseStickersEverywhere:function", predicate: () => Settings.plugins.FakeNitro.enableStickerBypass === true, replacement: { match: /canUseStickersEverywhere:function\(.+?\{/, @@ -113,7 +120,7 @@ export default definePlugin({ } }, { - find: "canUseAnimatedEmojis:function", + find: "canStreamHighQuality:function", predicate: () => Settings.plugins.FakeNitro.enableStreamQualityBypass === true, replacement: [ "canUseHighVideoUploadQuality", @@ -134,6 +141,13 @@ export default definePlugin({ replace: "" } }, + { + find: "canUseClientThemes:function", + replacement: { + match: /(?<=canUseClientThemes:function\(\i\){)/, + replace: "return true;" + } + } ], options: { @@ -181,6 +195,22 @@ export default definePlugin({ return (UserStore.getCurrentUser().premiumType ?? 0) > 1; }, + hasPermissionToUseExternalEmojis(channelId: string) { + const channel = ChannelStore.getChannel(channelId); + + if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; + + return PermissionStore.can(USE_EXTERNAL_EMOJIS, channel); + }, + + hasPermissionToUseExternalStickers(channelId: string) { + const channel = ChannelStore.getChannel(channelId); + + if (!channel || channel.isDM() || channel.isGroupDM() || channel.isMultiUserDM()) return true; + + return PermissionStore.can(USE_EXTERNAL_STICKERS, channel); + }, + getStickerLink(stickerId: string) { return `https://media.discordapp.net/stickers/${stickerId}.png?size=${Settings.plugins.FakeNitro.stickerSize}`; }, @@ -265,7 +295,7 @@ export default definePlugin({ if (!sticker) break stickerBypass; - if (sticker.available !== false && (this.canUseStickers || (sticker as GuildSticker)?.guild_id === guildId)) + if (sticker.available !== false && ((this.canUseStickers && this.hasPermissionToUseExternalStickers(channelId)) || (sticker as GuildSticker)?.guild_id === guildId)) break stickerBypass; let link = this.getStickerLink(sticker.id); @@ -288,7 +318,7 @@ export default definePlugin({ } } - if (!this.canUseEmotes && settings.enableEmojiBypass) { + if ((!this.canUseEmotes || !this.hasPermissionToUseExternalEmojis(channelId)) && settings.enableEmojiBypass) { for (const emoji of messageObj.validNonShortcutEmojis) { if (!emoji.require_colons) continue; if (emoji.guildId === guildId && !emoji.animated) continue; @@ -304,22 +334,22 @@ export default definePlugin({ return { cancel: false }; }); - if (!this.canUseEmotes && settings.enableEmojiBypass) { - this.preEdit = addPreEditListener((_, __, messageObj) => { - const { guildId } = this; + this.preEdit = addPreEditListener((channelId, __, messageObj) => { + if (this.canUseEmotes && this.hasPermissionToUseExternalEmojis(channelId)) return; - for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) { - const emoji = EmojiStore.getCustomEmojiById(emojiId); - if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue; - if (!emoji.require_colons) continue; + const { guildId } = this; - const url = emoji.url.replace(/\?size=\d+/, `?size=${Settings.plugins.FakeNitro.emojiSize}`); - messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => { - return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`; - }); - } - }); - } + for (const [emojiStr, _, emojiId] of messageObj.content.matchAll(/(?<!\\)<a?:(\w+):(\d+)>/ig)) { + const emoji = EmojiStore.getCustomEmojiById(emojiId); + if (emoji == null || (emoji.guildId === guildId && !emoji.animated)) continue; + if (!emoji.require_colons) continue; + + const url = emoji.url.replace(/\?size=\d+/, `?size=${Settings.plugins.FakeNitro.emojiSize}`); + messageObj.content = messageObj.content.replace(emojiStr, (match, offset, origStr) => { + return `${getWordBoundary(origStr, offset - 1)}${url}${getWordBoundary(origStr, offset + match.length)}`; + }); + } + }); }, stop() { From 77c691651e72ba1569666d560f96af04bfde9a4e Mon Sep 17 00:00:00 2001 From: nick <69434290+lelboatz@users.noreply.github.com> Date: Fri, 17 Feb 2023 21:35:51 -0500 Subject: [PATCH 114/114] ReviewDB: Show edit instead of create review where applicable (#466) Co-authored-by: Ven <vendicated@riseup.net> --- src/plugins/reviewDB/components/ReviewsView.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/plugins/reviewDB/components/ReviewsView.tsx b/src/plugins/reviewDB/components/ReviewsView.tsx index 4852967a3..c62065f7e 100644 --- a/src/plugins/reviewDB/components/ReviewsView.tsx +++ b/src/plugins/reviewDB/components/ReviewsView.tsx @@ -32,6 +32,7 @@ export default function ReviewsView({ userId }: { userId: string; }) { fallbackValue: [], deps: [refetchCount], }); + const username = UserStore.getUser(userId)?.username ?? ""; const dirtyRefetch = () => setRefetchCount(refetchCount + 1); @@ -79,7 +80,7 @@ export default function ReviewsView({ userId }: { userId: string; }) { <textarea className={classes(Classes.textarea.replace("textarea", ""), "enter-comment")} // this produces something like '-_59yqs ...' but since no class exists with that name its fine - placeholder={"Review @" + UserStore.getUser(userId)?.username ?? ""} + placeholder={reviews?.some(r => r.senderdiscordid === UserStore.getCurrentUser().id) ? `Update review for @${username}` : `Review @${username}`} onKeyDown={onKeyPress} style={{ marginTop: "6px",