From af67ddefa14edb896d63afe026d4736ebf954169 Mon Sep 17 00:00:00 2001 From: dolfies Date: Mon, 22 Apr 2024 19:46:11 -0400 Subject: [PATCH 01/20] ShowTimeouts->ShowHiddenThings ~show invite-disabled tooltip too (#2375) --- src/plugins/showHiddenThings/README.md | 11 +++++++ .../index.ts | 32 +++++++++++++++++-- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 src/plugins/showHiddenThings/README.md rename src/plugins/{showTimeouts => showHiddenThings}/index.ts (51%) diff --git a/src/plugins/showHiddenThings/README.md b/src/plugins/showHiddenThings/README.md new file mode 100644 index 000000000..b41e2d94d --- /dev/null +++ b/src/plugins/showHiddenThings/README.md @@ -0,0 +1,11 @@ +# ShowHiddenThings + +Displays various moderator-only elements regardless of permissions. + +## Features + +- Show member timeout icons in chat +![](https://github.com/Vendicated/Vencord/assets/47677887/75e1f6ba-8921-4188-9c2d-c9c3f9d07101) + +- Show the invites paused tooltip in the server list +![](https://github.com/Vendicated/Vencord/assets/47677887/b6a923d2-ac55-40d9-b4f8-fa6fc117148b) diff --git a/src/plugins/showTimeouts/index.ts b/src/plugins/showHiddenThings/index.ts similarity index 51% rename from src/plugins/showTimeouts/index.ts rename to src/plugins/showHiddenThings/index.ts index b0774bed4..e7be929bf 100644 --- a/src/plugins/showTimeouts/index.ts +++ b/src/plugins/showHiddenThings/index.ts @@ -16,20 +16,46 @@ * along with this program. If not, see . */ +import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +import definePlugin, { OptionType } from "@utils/types"; +const settings = definePluginSettings({ + showTimeouts: { + type: OptionType.BOOLEAN, + description: "Show member timeout icons in chat.", + default: true, + }, + showInvitesPaused: { + type: OptionType.BOOLEAN, + description: "Show the invites paused tooltip in the server list.", + default: true, + }, +}); + +migratePluginSettings("ShowHiddenThings", "ShowTimeouts"); export default definePlugin({ - name: "ShowTimeouts", - description: "Display member timeout icons in chat regardless of permissions.", + name: "ShowHiddenThings", + tags: ["ShowTimeouts", "ShowInvitesPaused"], + description: "Displays various moderator-only elements regardless of permissions.", authors: [Devs.Dolfies], patches: [ { find: "showCommunicationDisabledStyles", + predicate: () => settings.store.showTimeouts, replacement: { match: /&&\i\.\i\.canManageUser\(\i\.\i\.MODERATE_MEMBERS,\i\.author,\i\)/, replace: "", }, }, + { + find: "useShouldShowInvitesDisabledNotif:", + predicate: () => settings.store.showInvitesPaused, + replacement: { + match: /\i\.\i\.can\(\i\.Permissions.MANAGE_GUILD,\i\)/, + replace: "true", + }, + } ], + settings, }); From 5cf014cb06c759d2af643a00272a10e2816d5d4e Mon Sep 17 00:00:00 2001 From: Amia <9750071+aamiaa@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:06:19 +0200 Subject: [PATCH 02/20] New Plugin: BetterSessions (#1324) Co-authored-by: V --- src/plugins/betterSessions/README.md | 5 + .../components/RenameButton.tsx | 37 +++ .../betterSessions/components/RenameModal.tsx | 94 ++++++++ .../betterSessions/components/icons.tsx | 106 ++++++++ src/plugins/betterSessions/index.tsx | 227 ++++++++++++++++++ src/plugins/betterSessions/types.ts | 32 +++ src/plugins/betterSessions/utils.ts | 90 +++++++ 7 files changed, 591 insertions(+) create mode 100644 src/plugins/betterSessions/README.md create mode 100644 src/plugins/betterSessions/components/RenameButton.tsx create mode 100644 src/plugins/betterSessions/components/RenameModal.tsx create mode 100644 src/plugins/betterSessions/components/icons.tsx create mode 100644 src/plugins/betterSessions/index.tsx create mode 100644 src/plugins/betterSessions/types.ts create mode 100644 src/plugins/betterSessions/utils.ts diff --git a/src/plugins/betterSessions/README.md b/src/plugins/betterSessions/README.md new file mode 100644 index 000000000..cf13e6c50 --- /dev/null +++ b/src/plugins/betterSessions/README.md @@ -0,0 +1,5 @@ +# BetterSessions + +Enhances the sessions (devices) menu. Allows you to view exact timestamps, give each session a custom name, and receive notifications about new sessions. + +![](https://github.com/Vendicated/Vencord/assets/9750071/4a44b617-bb8f-4dcb-93f1-b7d2575ed3d8) diff --git a/src/plugins/betterSessions/components/RenameButton.tsx b/src/plugins/betterSessions/components/RenameButton.tsx new file mode 100644 index 000000000..a0c95a6f4 --- /dev/null +++ b/src/plugins/betterSessions/components/RenameButton.tsx @@ -0,0 +1,37 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { openModal } from "@utils/modal"; +import { Button } from "@webpack/common"; + +import { SessionInfo } from "../types"; +import { RenameModal } from "./RenameModal"; + +export function RenameButton({ session, state }: { session: SessionInfo["session"], state: [string, React.Dispatch>]; }) { + return ( + + ); +} diff --git a/src/plugins/betterSessions/components/RenameModal.tsx b/src/plugins/betterSessions/components/RenameModal.tsx new file mode 100644 index 000000000..1c5783c0e --- /dev/null +++ b/src/plugins/betterSessions/components/RenameModal.tsx @@ -0,0 +1,94 @@ +/* + * 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 } from "@utils/modal"; +import { Button, Forms, React, TextInput } from "@webpack/common"; +import { KeyboardEvent } from "react"; + +import { SessionInfo } from "../types"; +import { getDefaultName, savedSessionsCache, saveSessionsToDataStore } from "../utils"; + +export function RenameModal({ props, session, state }: { props: ModalProps, session: SessionInfo["session"], state: [string, React.Dispatch>]; }) { + const [title, setTitle] = state; + const [value, setValue] = React.useState(savedSessionsCache.get(session.id_hash)?.name ?? ""); + + function onSaveClick() { + savedSessionsCache.set(session.id_hash, { name: value, isNew: false }); + if (value !== "") { + setTitle(`${value}*`); + } else { + setTitle(getDefaultName(session.client_info)); + } + + saveSessionsToDataStore(); + props.onClose(); + } + + return ( + + + Rename + + + + New device name + ) => { + if (e.key === "Enter") { + onSaveClick(); + } + }} + /> + + + + + + + + + ); +} diff --git a/src/plugins/betterSessions/components/icons.tsx b/src/plugins/betterSessions/components/icons.tsx new file mode 100644 index 000000000..bd745e76c --- /dev/null +++ b/src/plugins/betterSessions/components/icons.tsx @@ -0,0 +1,106 @@ +/* + * 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 { LazyComponent } from "@utils/react"; +import { findByCode } from "@webpack"; +import { SVGProps } from "react"; + +export const DiscordIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const ChromeIcon = (props: React.PropsWithChildren>) => ( + + + + + + +); + +export const EdgeIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const FirefoxIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const IEIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const OperaIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const SafariIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const UnknownIcon = (props: React.PropsWithChildren>) => ( + + + +); + +export const MobileIcon = LazyComponent(() => findByCode("M15.5 1h-8C6.12 1 5 2.12 5 3.5v17C5 21.88 6.12 23 7.5 23h8c1.38")); diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx new file mode 100644 index 000000000..bb79870d2 --- /dev/null +++ b/src/plugins/betterSessions/index.tsx @@ -0,0 +1,227 @@ +/* + * 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 { showNotification } from "@api/Notifications"; +import { definePluginSettings } from "@api/Settings"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; +import { React, RestAPI, Tooltip } from "@webpack/common"; + +import { RenameButton } from "./components/RenameButton"; +import { Session, SessionInfo } from "./types"; +import { fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, savedSessionsCache, saveSessionsToDataStore } from "./utils"; + +const AuthSessionsStore = findByPropsLazy("getSessions"); +const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open"); + +const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer"); +const SessionIconClasses = findByPropsLazy("sessionIcon"); + +const BlobMask = findExportedComponentLazy("BlobMask"); + +const settings = definePluginSettings({ + backgroundCheck: { + type: OptionType.BOOLEAN, + description: "Check for new sessions in the background, and display notifications when they are detected", + default: false, + restartNeeded: true + }, + checkInterval: { + description: "How often to check for new sessions in the background (if enabled), in minutes", + type: OptionType.NUMBER, + default: 20, + restartNeeded: true + } +}); + +export default definePlugin({ + name: "BetterSessions", + description: "Enhances the sessions (devices) menu. Allows you to view exact timestamps, give each session a custom name, and receive notifications about new sessions.", + authors: [Devs.amia], + + settings: settings, + + patches: [ + { + find: "Messages.AUTH_SESSIONS_SESSION_LOG_OUT", + replacement: [ + // Replace children with a single label with state + { + match: /({variant:"eyebrow",className:\i\.sessionInfoRow,children:).{70,110}{children:"\\xb7"}\),\(0,\i\.\i\)\("span",{children:\i\[\d+\]}\)\]}\)\]/, + replace: "$1$self.renderName(arguments[0])" + }, + { + match: /({variant:"text-sm\/medium",className:\i\.sessionInfoRow,children:.{70,110}{children:"\\xb7"}\),\(0,\i\.\i\)\("span",{children:)(\i\[\d+\])}/, + replace: "$1$self.renderTimestamp({ ...arguments[0], timeLabel: $2 })}" + }, + // Replace the icon + { + match: /\.currentSession:null\),children:\[(?<=,icon:(\i)\}.+?)/, + replace: "$& $self.renderIcon({ ...arguments[0], DeviceIcon: $1 }), false &&" + } + ] + }, + { + // Add the ability to change BlobMask's lower badge height + // (it allows changing width so we can mirror that logic) + find: "this.getBadgePositionInterpolation(", + replacement: { + match: /(\i\.animated\.rect,{id:\i,x:48-\(\i\+8\)\+4,y:)28(,width:\i\+8,height:)24,/, + replace: (_, leftPart, rightPart) => `${leftPart} 48 - ((this.props.lowerBadgeHeight ?? 16) + 8) + 4 ${rightPart} (this.props.lowerBadgeHeight ?? 16) + 8,` + } + } + ], + + renderName: ErrorBoundary.wrap(({ session }: SessionInfo) => { + const savedSession = savedSessionsCache.get(session.id_hash); + + const state = React.useState(savedSession?.name ? `${savedSession.name}*` : getDefaultName(session.client_info)); + const [title, setTitle] = state; + + // Show a "NEW" badge if the session is seen for the first time + return ( + <> + {title} + {(savedSession == null || savedSession.isNew) && ( +
+ NEW +
+ )} + + + ); + }, { noop: true }), + + renderTimestamp: ErrorBoundary.wrap(({ session, timeLabel }: { session: Session, timeLabel: string; }) => { + return ( + + {props => ( + + {timeLabel} + + )} + + ); + }, { noop: true }), + + renderIcon: ErrorBoundary.wrap(({ session, DeviceIcon }: { session: Session, DeviceIcon: React.ComponentType; }) => { + const PlatformIcon = GetPlatformIcon(session.client_info.platform); + + return ( + + + + } + lowerBadgeWidth={20} + lowerBadgeHeight={20} + > +
+ +
+
+ ); + }, { noop: true }), + + async checkNewSessions() { + const data = await RestAPI.get({ + url: "/auth/sessions" + }); + + for (const session of data.body.user_sessions) { + if (savedSessionsCache.has(session.id_hash)) continue; + + savedSessionsCache.set(session.id_hash, { name: "", isNew: true }); + showNotification({ + title: "BetterSessions", + body: `New session:\n${session.client_info.os} · ${session.client_info.platform} · ${session.client_info.location}`, + permanent: true, + onClick: () => UserSettingsModal.open("Sessions") + }); + } + + saveSessionsToDataStore(); + }, + + flux: { + USER_SETTINGS_ACCOUNT_RESET_AND_CLOSE_FORM() { + const lastFetchedHashes: string[] = AuthSessionsStore.getSessions().map((session: SessionInfo["session"]) => session.id_hash); + + // Add new sessions to cache + lastFetchedHashes.forEach(idHash => { + if (!savedSessionsCache.has(idHash)) savedSessionsCache.set(idHash, { name: "", isNew: false }); + }); + + // Delete removed sessions from cache + if (lastFetchedHashes.length > 0) { + savedSessionsCache.forEach((_, idHash) => { + if (!lastFetchedHashes.includes(idHash)) savedSessionsCache.delete(idHash); + }); + } + + // Dismiss the "NEW" badge of all sessions. + // Since the only way for a session to be marked as "NEW" is going to the Devices tab, + // closing the settings means they've been viewed and are no longer considered new. + savedSessionsCache.forEach(data => { + data.isNew = false; + }); + saveSessionsToDataStore(); + } + }, + + async start() { + await fetchNamesFromDataStore(); + + this.checkNewSessions(); + if (settings.store.backgroundCheck) { + this.checkInterval = setInterval(this.checkNewSessions, settings.store.checkInterval * 60 * 1000); + } + }, + + stop() { + clearInterval(this.checkInterval); + } +}); diff --git a/src/plugins/betterSessions/types.ts b/src/plugins/betterSessions/types.ts new file mode 100644 index 000000000..9026d5313 --- /dev/null +++ b/src/plugins/betterSessions/types.ts @@ -0,0 +1,32 @@ +/* + * 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 . +*/ + +export interface SessionInfo { + session: { + id_hash: string; + approx_last_used_time: Date; + client_info: { + os: string; + platform: string; + location: string; + }; + }, + current?: boolean; +} + +export type Session = SessionInfo["session"]; diff --git a/src/plugins/betterSessions/utils.ts b/src/plugins/betterSessions/utils.ts new file mode 100644 index 000000000..3015dc47c --- /dev/null +++ b/src/plugins/betterSessions/utils.ts @@ -0,0 +1,90 @@ +/* + * 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 { DataStore } from "@api/index"; +import { UserStore } from "@webpack/common"; + +import { ChromeIcon, DiscordIcon, EdgeIcon, FirefoxIcon, IEIcon, MobileIcon, OperaIcon, SafariIcon, UnknownIcon } from "./components/icons"; +import { SessionInfo } from "./types"; + +const getDataKey = () => `BetterSessions_savedSessions_${UserStore.getCurrentUser().id}`; + +export const savedSessionsCache: Map = new Map(); + +export function getDefaultName(clientInfo: SessionInfo["session"]["client_info"]) { + return `${clientInfo.os} · ${clientInfo.platform}`; +} + +export function saveSessionsToDataStore() { + return DataStore.set(getDataKey(), savedSessionsCache); +} + +export async function fetchNamesFromDataStore() { + const savedSessions = await DataStore.get>(getDataKey()) || new Map(); + savedSessions.forEach((data, idHash) => { + savedSessionsCache.set(idHash, data); + }); +} + +export function GetOsColor(os: string) { + switch (os) { + case "Windows Mobile": + case "Windows": + return "#55a6ef"; // Light blue + case "Linux": + return "#cdcd31"; // Yellow + case "Android": + return "#7bc958"; // Green + case "Mac OS X": + case "iOS": + return ""; // Default to white/black (theme-dependent) + default: + return "#f3799a"; // Pink + } +} + +export function GetPlatformIcon(platform: string) { + switch (platform) { + case "Discord Android": + case "Discord iOS": + case "Discord Client": + return DiscordIcon; + case "Android Chrome": + case "Chrome iOS": + case "Chrome": + return ChromeIcon; + case "Edge": + return EdgeIcon; + case "Firefox": + return FirefoxIcon; + case "Internet Explorer": + return IEIcon; + case "Opera Mini": + case "Opera": + return OperaIcon; + case "Mobile Safari": + case "Safari": + return SafariIcon; + case "BlackBerry": + case "Facebook Mobile": + case "Android Mobile": + return MobileIcon; + default: + return UnknownIcon; + } +} From 7f0e7dd02bd241f9999da77642189868164b6e54 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 24 Apr 2024 05:23:50 +0200 Subject: [PATCH 03/20] fix FakeProfileThemes --- src/plugins/fakeProfileThemes/index.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index c61437269..a1b629d10 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -92,8 +92,9 @@ export default definePlugin({ match: /(?<=getUserProfile\(\i\){return )(\i\[\i\])/, replace: "$self.colorDecodeHook($1)" } - }, { - find: ".USER_SETTINGS_PROFILE_THEME_ACCENT", + }, + { + find: ".USER_SETTINGS_RESET_PROFILE_THEME", replacement: { match: /RESET_PROFILE_THEME}\)(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" From d5f70070efed3a6c2f481ffe4abce3c5ad45e57b Mon Sep 17 00:00:00 2001 From: Vendicated Date: Wed, 24 Apr 2024 05:27:14 +0200 Subject: [PATCH 04/20] fix badges --- src/plugins/_api/badges.tsx | 2 +- src/plugins/betterSessions/index.tsx | 4 ++-- src/utils/constants.ts | 8 ++++++++ 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/plugins/_api/badges.tsx b/src/plugins/_api/badges.tsx index 16b244a19..6b1a79cd5 100644 --- a/src/plugins/_api/badges.tsx +++ b/src/plugins/_api/badges.tsx @@ -65,7 +65,7 @@ export default definePlugin({ patches: [ /* Patch the badge list component on user profiles */ { - find: "Messages.PROFILE_USER_BADGES,role:", + find: 'id:"premium",', replacement: [ { match: /&&(\i)\.push\(\{id:"premium".+?\}\);/, diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index bb79870d2..539508f80 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -21,14 +21,14 @@ import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; -import { findByPropsLazy, findExportedComponentLazy } from "@webpack"; +import { findByPropsLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; import { React, RestAPI, Tooltip } from "@webpack/common"; import { RenameButton } from "./components/RenameButton"; import { Session, SessionInfo } from "./types"; import { fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, savedSessionsCache, saveSessionsToDataStore } from "./utils"; -const AuthSessionsStore = findByPropsLazy("getSessions"); +const AuthSessionsStore = findStoreLazy("AuthSessionsStore"); const UserSettingsModal = findByPropsLazy("saveAccountChanges", "open"); const TimestampClasses = findByPropsLazy("timestampTooltip", "blockquoteContainer"); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 3098e29d0..917fabf43 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -437,6 +437,14 @@ export const Devs = /* #__PURE__*/ Object.freeze({ Byron: { name: "byeoon", id: 1167275288036655133n + }, + Kaitlyn: { + name: "kaitlyn", + id: 306158896630988801n + }, + PolisanTheEasyNick: { + name: "Oleh Polisan", + id: 242305263313485825n } } satisfies Record); From 6a69701b54107cbb696615949ba0594a097bf010 Mon Sep 17 00:00:00 2001 From: My-Name-Is-Jeff <37018278+My-Name-Is-Jeff@users.noreply.github.com> Date: Tue, 23 Apr 2024 23:30:23 -0400 Subject: [PATCH 05/20] fix ValidUser (#2381) --- src/plugins/validUser/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index d3decd941..2fce693e8 100644 --- a/src/plugins/validUser/index.tsx +++ b/src/plugins/validUser/index.tsx @@ -120,7 +120,7 @@ export default definePlugin({ find: 'className:"mention"', replacement: { // mention = { react: function (data, parse, props) { if (data.userId == null) return RoleMention() else return UserMention() - match: /react(?=\(\i,\i,\i\).{0,50}return null==.{0,70}\?\(0,\i\.jsx\)\((\i\.\i),.+?jsx\)\((\i\.\i),\{className:"mention")/, + match: /react(?=\(\i,\i,\i\).{0,100}return null==.{0,70}\?\(0,\i\.jsx\)\((\i\.\i),.+?jsx\)\((\i\.\i),\{className:"mention")/, // react: (...args) => OurWrapper(RoleMention, UserMention, ...args), originalReact: theirFunc replace: "react:(...args)=>$self.renderMention($1,$2,...args),originalReact" } From d55205c55ad9ffa82fa03837ebb4eae623d28705 Mon Sep 17 00:00:00 2001 From: dolfies Date: Tue, 23 Apr 2024 23:41:53 -0400 Subject: [PATCH 06/20] feat(plugin): ImplicitRelationships (#947) Co-authored-by: Angelos Bouklis <53124886+ArjixWasTaken@users.noreply.github.com> Co-authored-by: V --- src/plugins/implicitRelationships/README.md | 7 + src/plugins/implicitRelationships/index.ts | 182 ++++++++++++++++++++ src/plugins/sortFriendRequests/index.tsx | 2 +- 3 files changed, 190 insertions(+), 1 deletion(-) create mode 100644 src/plugins/implicitRelationships/README.md create mode 100644 src/plugins/implicitRelationships/index.ts diff --git a/src/plugins/implicitRelationships/README.md b/src/plugins/implicitRelationships/README.md new file mode 100644 index 000000000..a76e298f7 --- /dev/null +++ b/src/plugins/implicitRelationships/README.md @@ -0,0 +1,7 @@ +# ImplicitRelationships + +Shows your implicit relationships in the Friends tab. + +Implicit relationships on Discord are people with whom you've frecently interacted and share a mutual server; even though Discord thinks you should be friends with them, you haven't added them as friends. + +![](https://camo.githubusercontent.com/6927161ee0c933f7ef6d61f243cca3e6ea4c8db9d1becd8cbf73c45e1bd0d127/68747470733a2f2f692e646f6c66692e65732f7055447859464662674d2e706e673f6b65793d736e3950343936416c32444c7072) diff --git a/src/plugins/implicitRelationships/index.ts b/src/plugins/implicitRelationships/index.ts new file mode 100644 index 000000000..9ae9fb512 --- /dev/null +++ b/src/plugins/implicitRelationships/index.ts @@ -0,0 +1,182 @@ +/* + * 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 { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { findByProps, findStoreLazy } from "@webpack"; +import { ChannelStore, FluxDispatcher, GuildStore, RelationshipStore, SnowflakeUtils, UserStore } from "@webpack/common"; +import { Settings } from "Vencord"; + +const UserAffinitiesStore = findStoreLazy("UserAffinitiesStore"); + +interface UserAffinity { + user_id: string; + affinity: number; +} + +export default definePlugin({ + name: "ImplicitRelationships", + description: "Shows your implicit relationships in the Friends tab.", + authors: [Devs.Dolfies], + patches: [ + // Counts header + { + find: ".FRIENDS_ALL_HEADER", + replacement: { + match: /toString\(\)\}\);case (\i\.\i)\.BLOCKED/, + replace: 'toString()});case $1.IMPLICIT:return "Implicit — "+arguments[1];case $1.BLOCKED' + }, + }, + // No friends page + { + find: "FriendsEmptyState: Invalid empty state", + replacement: { + match: /case (\i\.\i)\.ONLINE:(?=return (\i)\.SECTION_ONLINE)/, + replace: "case $1.ONLINE:case $1.IMPLICIT:" + }, + }, + // Sections header + { + find: ".FRIENDS_SECTION_ONLINE", + replacement: { + match: /(\(0,\i\.jsx\)\(\i\.TabBar\.Item,\{id:\i\.\i)\.BLOCKED,className:([^\s]+?)\.item,children:\i\.\i\.Messages\.BLOCKED\}\)/, + replace: "$1.IMPLICIT,className:$2.item,children:\"Implicit\"}),$&" + }, + }, + // Sections content + { + find: '"FriendsStore"', + replacement: { + match: /(?<=case (\i\.\i)\.BLOCKED:return (\i)\.type===\i\.\i\.BLOCKED)/, + replace: ";case $1.IMPLICIT:return $2.type===5" + }, + }, + // Piggyback relationship fetch + { + find: ".fetchRelationships()", + replacement: { + match: /(\i\.\i)\.fetchRelationships\(\)/, + // This relationship fetch is actually completely useless, but whatevs + replace: "$1.fetchRelationships(),$self.fetchImplicitRelationships()" + }, + }, + // Modify sort -- thanks megu for the patch (from sortFriendRequests) + { + find: "getRelationshipCounts(){", + replacement: { + predicate: () => Settings.plugins.ImplicitRelationships.sortByAffinity, + match: /\.sortBy\(\i=>\i\.comparator\)/, + replace: "$&.sortBy((row) => $self.sortList(row))" + } + }, + + // Add support for the nonce parameter to Discord's shitcode + { + find: ".REQUEST_GUILD_MEMBERS", + replacement: { + match: /\.send\(8,{/, + replace: "$&nonce:arguments[1].nonce," + } + }, + { + find: "GUILD_MEMBERS_REQUEST:", + replacement: { + match: /presences:!!(\i)\.presences/, + replace: "$&,nonce:$1.nonce" + }, + }, + { + find: ".not_found", + replacement: { + match: /notFound:(\i)\.not_found/, + replace: "$&,nonce:$1.nonce" + }, + } + ], + settings: definePluginSettings( + { + sortByAffinity: { + type: OptionType.BOOLEAN, + default: true, + description: "Whether to sort implicit relationships by their affinity to you.", + restartNeeded: true + }, + } + ), + + sortList(row: any) { + return row.type === 5 + ? -UserAffinitiesStore.getUserAffinity(row.user.id)?.affinity ?? 0 + : row.comparator; + }, + + async fetchImplicitRelationships() { + // Implicit relationships are defined as users that you: + // 1. Have an affinity for + // 2. Do not have a relationship with // TODO: Check how this works with pending/blocked relationships + // 3. Have a mutual guild with + const userAffinities: Set = UserAffinitiesStore.getUserAffinitiesUserIds(); + const nonFriendAffinities = Array.from(userAffinities).filter( + id => !RelationshipStore.getRelationshipType(id) + ); + + // I would love to just check user cache here (falling back to the gateway of course) + // However, users in user cache may just be there because they share a DM or group DM with you + // So there's no guarantee that a user being in user cache means they have a mutual with you + // To get around this, we request users we have DMs with, and ignore them below if we don't get them back + const dmUserIds = new Set( + Object.values(ChannelStore.getSortedPrivateChannels()).flatMap(c => c.recipients) + ); + const toRequest = nonFriendAffinities.filter(id => !UserStore.getUser(id) || dmUserIds.has(id)); + const allGuildIds = Object.keys(GuildStore.getGuilds()); + const sentNonce = SnowflakeUtils.fromTimestamp(Date.now()); + let count = allGuildIds.length * Math.ceil(toRequest.length / 100); + + // OP 8 Request Guild Members allows 100 user IDs at a time + const ignore = new Set(toRequest); + const relationships = RelationshipStore.getRelationships(); + const callback = ({ nonce, members }) => { + if (nonce !== sentNonce) return; + members.forEach(member => { + ignore.delete(member.user.id); + }); + + nonFriendAffinities.map(id => UserStore.getUser(id)).filter(user => user && !ignore.has(user.id)).forEach(user => relationships[user.id] = 5); + RelationshipStore.emitChange(); + if (--count === 0) { + FluxDispatcher.unsubscribe("GUILD_MEMBERS_CHUNK", callback); + } + }; + + FluxDispatcher.subscribe("GUILD_MEMBERS_CHUNK", callback); + for (let i = 0; i < toRequest.length; i += 100) { + FluxDispatcher.dispatch({ + type: "GUILD_MEMBERS_REQUEST", + guildIds: allGuildIds, + userIds: toRequest.slice(i, i + 100), + nonce: sentNonce, + }); + } + }, + + start() { + const { FriendsSections } = findByProps("FriendsSections"); + FriendsSections.IMPLICIT = "IMPLICIT"; + } +}); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index c40a18140..32579a803 100644 --- a/src/plugins/sortFriendRequests/index.tsx +++ b/src/plugins/sortFriendRequests/index.tsx @@ -42,7 +42,7 @@ export default definePlugin({ find: "getRelationshipCounts(){", replacement: { match: /\.sortBy\(\i=>\i\.comparator\)/, - replace: ".sortBy((row) => $self.sortList(row))" + replace: "$&.sortBy((row) => $self.sortList(row))" } }, { find: ".Messages.FRIEND_REQUEST_CANCEL", From 9e0aa4b23ccaef9e030d20d3881135875a3364e2 Mon Sep 17 00:00:00 2001 From: Koda!! <162957404+Kodarru@users.noreply.github.com> Date: Tue, 23 Apr 2024 20:51:49 -0700 Subject: [PATCH 07/20] feat(plugin): StreamerModeOnStream (#2320) Co-authored-by: V --- src/plugins/streamerModeOnStream/index.ts | 45 +++++++++++++++++++++++ src/utils/constants.ts | 4 ++ 2 files changed, 49 insertions(+) create mode 100644 src/plugins/streamerModeOnStream/index.ts diff --git a/src/plugins/streamerModeOnStream/index.ts b/src/plugins/streamerModeOnStream/index.ts new file mode 100644 index 000000000..03b83f577 --- /dev/null +++ b/src/plugins/streamerModeOnStream/index.ts @@ -0,0 +1,45 @@ +/* + * Vencord, a modification for Discord's desktop app + * Copyright (c) 2024 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"; +import { FluxDispatcher, UserStore } from "@webpack/common"; + +interface StreamEvent { + streamKey: string; +} + +function toggleStreamerMode({ streamKey }: StreamEvent, value: boolean) { + if (!streamKey.endsWith(UserStore.getCurrentUser().id)) return; + + FluxDispatcher.dispatch({ + type: "STREAMER_MODE_UPDATE", + key: "enabled", + value + }); +} + +export default definePlugin({ + name: "StreamerModeOnStream", + description: "Automatically enables streamer mode when you start streaming in Discord", + authors: [Devs.Kodarru], + flux: { + STREAM_CREATE: d => toggleStreamerMode(d, true), + STREAM_DELETE: d => toggleStreamerMode(d, false) + } +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 917fabf43..8ab0bffb3 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -426,6 +426,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "newwares", id: 421405303951851520n }, + Kodarru: { + name: "Kodarru", + id: 785227396218748949n + }, nakoyasha: { name: "nakoyasha", id: 222069018507345921n From 4fce88fa8f048e648240a42e553c740a2927afda Mon Sep 17 00:00:00 2001 From: Elvyra <88881326+EdVraz@users.noreply.github.com> Date: Wed, 24 Apr 2024 23:05:02 +0200 Subject: [PATCH 08/20] Fix OnePingPerDM and RoleColorEverywhere patches (#2387) --- src/plugins/onePingPerDM/index.ts | 2 +- src/plugins/roleColorEverywhere/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/onePingPerDM/index.ts b/src/plugins/onePingPerDM/index.ts index ae38343db..e9cde6523 100644 --- a/src/plugins/onePingPerDM/index.ts +++ b/src/plugins/onePingPerDM/index.ts @@ -49,7 +49,7 @@ export default definePlugin({ replace: "$&if(!$self.isPrivateChannelRead(arguments[0]?.message))return;else " }, { - match: /sound:(\i\?\i:void 0,volume:\i,onClick)/, + match: /sound:(\i\?\i:void 0,soundpack:\i,volume:\i,onClick)/, replace: "sound:!$self.isPrivateChannelRead(arguments[0]?.message)?undefined:$1" }] }], diff --git a/src/plugins/roleColorEverywhere/index.tsx b/src/plugins/roleColorEverywhere/index.tsx index 71c291fef..942697801 100644 --- a/src/plugins/roleColorEverywhere/index.tsx +++ b/src/plugins/roleColorEverywhere/index.tsx @@ -94,7 +94,7 @@ export default definePlugin({ find: "renderPrioritySpeaker", replacement: [ { - match: /renderName\(\).{0,100}speaking:.{50,150}"div",{/, + match: /renderName\(\).{0,100}speaking:.{50,200}"div",{/, replace: "$&...$self.getVoiceProps(this.props)," } ], From 36327ebd70eadddda03be9b7b36b182e79ae7dde Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 26 Apr 2024 15:55:28 -0300 Subject: [PATCH 09/20] PronounDB: Fix patch --- src/plugins/pronoundb/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/plugins/pronoundb/index.ts b/src/plugins/pronoundb/index.ts index 61edd191e..b14b26572 100644 --- a/src/plugins/pronoundb/index.ts +++ b/src/plugins/pronoundb/index.ts @@ -56,7 +56,7 @@ export default definePlugin({ find: ".pronouns,children", replacement: [ { - match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i;/, + match: /{user:(\i),[^}]*,pronouns:(\i),[^}]*}=\i.*?;(?=return)/, replace: "$&let vcPronounSource;[$2,vcPronounSource]=$self.useProfilePronouns($1.id);" }, PRONOUN_TOOLTIP_PATCH From c10466f607d889c06afdee46f7af31b1062e78b5 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:30:52 -0300 Subject: [PATCH 10/20] MoreUserTags: Fix patches --- src/plugins/moreUserTags/index.tsx | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/plugins/moreUserTags/index.tsx b/src/plugins/moreUserTags/index.tsx index d1ad941b0..4ac542216 100644 --- a/src/plugins/moreUserTags/index.tsx +++ b/src/plugins/moreUserTags/index.tsx @@ -198,7 +198,8 @@ export default definePlugin({ replacement: [ // make the tag show the right text { - match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.BOT_TAG_BOT/, + // FIXME: Remove the BOT_TAG_BOT variant when the change arrives in stable + match: /(switch\((\i)\){.+?)case (\i(?:\.\i)?)\.BOT:default:(\i)=.{0,40}(\i\.\i\.Messages)\.(?:APP_TAG|BOT_TAG_BOT)/, replace: (_, origSwitch, variant, tags, displayedText, strings) => `${origSwitch}default:{${displayedText} = $self.getTagText(${tags}[${variant}], ${strings})}` }, @@ -321,19 +322,20 @@ export default definePlugin({ isOPTag: (tag: number) => tag === Tag.Types.ORIGINAL_POSTER || tags.some(t => tag === Tag.Types[`${t.name}-OP`]), + // FIXME: Remove the BOT_TAG_BOT variants from strings when the change arrives in stable getTagText(passedTagName: string, strings: Record) { - if (!passedTagName) return strings.BOT_TAG_BOT; + if (!passedTagName) return strings.APP_TAG ?? strings.BOT_TAG_BOT; const [tagName, variant] = passedTagName.split("-"); const tag = tags.find(({ name }) => tagName === name); - if (!tag) return strings.BOT_TAG_BOT; - if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return strings.BOT_TAG_BOT; + if (!tag) return strings.APP_TAG ?? strings.BOT_TAG_BOT; + if (variant === "BOT" && tagName !== "WEBHOOK" && this.settings.store.dontShowForBots) return strings.APP_TAG ?? strings.BOT_TAG_BOT; const tagText = settings.store.tagSettings?.[tag.name]?.text || tag.displayName; switch (variant) { case "OP": return `${strings.BOT_TAG_FORUM_ORIGINAL_POSTER} • ${tagText}`; case "BOT": - return `${strings.BOT_TAG_BOT} • ${tagText}`; + return `${strings.APP_TAG ?? strings.BOT_TAG_BOT} • ${tagText}`; default: return tagText; } From fafd46d2028e702d7341e1eb404c68fede49bdd3 Mon Sep 17 00:00:00 2001 From: Nuckyz <61953774+Nuckyz@users.noreply.github.com> Date: Fri, 26 Apr 2024 18:31:22 -0300 Subject: [PATCH 11/20] FriendsSince: Remove workaround for stable compatibility --- src/plugins/friendsSince/index.tsx | 16 +++------------- 1 file changed, 3 insertions(+), 13 deletions(-) diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 69d8e739d..d6b7d1072 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -7,22 +7,12 @@ import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; import { getCurrentChannel } from "@utils/discord"; -import { makeLazy } from "@utils/lazy"; import definePlugin from "@utils/types"; -import { filters, find, findByPropsLazy, handleModuleNotFound } from "@webpack"; +import { findByPropsLazy } from "@webpack"; import { React, RelationshipStore } from "@webpack/common"; const { Heading, Text } = findByPropsLazy("Heading", "Text"); -// Workaround for module differing on stable & canary -// FIXME: remove once merged into stable -const getMemberSinceContainer = makeLazy(() => { - for (const name of ["memberSinceWrapper", "memberSinceContainer"]) { - const mod = find(filters.byProps(name), { isIndirect: true }); - if (mod) return mod[name]; - } - handleModuleNotFound("findByProps", "memberSinceWrapper/memberSinceContainer"); - return ""; -}); +const container = findByPropsLazy("memberSinceWrapper"); const { getCreatedAtDate } = findByPropsLazy("getCreatedAtDate"); const clydeMoreInfo = findByPropsLazy("clydeMoreInfo"); const locale = findByPropsLazy("getLocale"); @@ -59,7 +49,7 @@ export default definePlugin({ Friends Since -
+
{!!getCurrentChannel()?.guild_id && (