diff --git a/src/plugins/_api/badges.tsx b/src/plugins/_api/badges.tsx index 48e5422c..5f2cf483 100644 --- a/src/plugins/_api/badges.tsx +++ b/src/plugins/_api/badges.tsx @@ -86,7 +86,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/README.md b/src/plugins/betterSessions/README.md new file mode 100644 index 00000000..cf13e6c5 --- /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 00000000..a0c95a6f --- /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 00000000..1c5783c0 --- /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 00000000..bd745e76 --- /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 00000000..539508f8 --- /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, 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 = findStoreLazy("AuthSessionsStore"); +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/showTimeouts/index.ts b/src/plugins/betterSessions/types.ts similarity index 59% rename from src/plugins/showTimeouts/index.ts rename to src/plugins/betterSessions/types.ts index b0774bed..9026d531 100644 --- a/src/plugins/showTimeouts/index.ts +++ b/src/plugins/betterSessions/types.ts @@ -16,20 +16,17 @@ * along with this program. If not, see . */ -import { Devs } from "@utils/constants"; -import definePlugin from "@utils/types"; +export interface SessionInfo { + session: { + id_hash: string; + approx_last_used_time: Date; + client_info: { + os: string; + platform: string; + location: string; + }; + }, + current?: boolean; +} -export default definePlugin({ - name: "ShowTimeouts", - description: "Display member timeout icons in chat regardless of permissions.", - authors: [Devs.Dolfies], - patches: [ - { - find: "showCommunicationDisabledStyles", - replacement: { - match: /&&\i\.\i\.canManageUser\(\i\.\i\.MODERATE_MEMBERS,\i\.author,\i\)/, - replace: "", - }, - }, - ], -}); +export type Session = SessionInfo["session"]; diff --git a/src/plugins/betterSessions/utils.ts b/src/plugins/betterSessions/utils.ts new file mode 100644 index 00000000..3015dc47 --- /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; + } +} diff --git a/src/plugins/implicitRelationships/README.md b/src/plugins/implicitRelationships/README.md new file mode 100644 index 00000000..a76e298f --- /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 00000000..9ae9fb51 --- /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/showHiddenThings/README.md b/src/plugins/showHiddenThings/README.md new file mode 100644 index 00000000..b41e2d94 --- /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/showHiddenThings/index.ts b/src/plugins/showHiddenThings/index.ts new file mode 100644 index 00000000..e7be929b --- /dev/null +++ b/src/plugins/showHiddenThings/index.ts @@ -0,0 +1,61 @@ +/* + * 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, migratePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +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: "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, +}); diff --git a/src/plugins/sortFriendRequests/index.tsx b/src/plugins/sortFriendRequests/index.tsx index c40a1814..32579a80 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", diff --git a/src/plugins/streamerModeOnStream/index.ts b/src/plugins/streamerModeOnStream/index.ts new file mode 100644 index 00000000..03b83f57 --- /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/plugins/validUser/index.tsx b/src/plugins/validUser/index.tsx index d3decd94..2fce693e 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" } diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2e60efa6..c866f0f4 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -428,6 +428,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "newwares", id: 421405303951851520n }, + Kodarru: { + name: "Kodarru", + id: 785227396218748949n + }, nakoyasha: { name: "nakoyasha", id: 222069018507345921n @@ -439,6 +443,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);