diff --git a/eslint.config.mjs b/eslint.config.mjs index 07c70fa74..67327b938 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -105,7 +105,13 @@ export default tseslint.config( "no-invalid-regexp": "error", "no-constant-condition": ["error", { "checkLoops": false }], "no-duplicate-imports": "error", - "dot-notation": "error", + "@typescript-eslint/dot-notation": [ + "error", + { + "allowPrivateClassPropertyAccess": true, + "allowProtectedClassPropertyAccess": true + } + ], "no-useless-escape": [ "error", { diff --git a/package.json b/package.json index 6113731d2..abb11ee5c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.10.9", + "version": "1.11.0", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": { diff --git a/src/api/Badges.ts b/src/api/Badges.ts index 7a041f1ee..ee2f3a30c 100644 --- a/src/api/Badges.ts +++ b/src/api/Badges.ts @@ -57,7 +57,7 @@ const Badges = new Set(); * Register a new badge with the Badges API * @param badge The badge to register */ -export function addBadge(badge: ProfileBadge) { +export function addProfileBadge(badge: ProfileBadge) { badge.component &&= ErrorBoundary.wrap(badge.component, { noop: true }); Badges.add(badge); } @@ -66,7 +66,7 @@ export function addBadge(badge: ProfileBadge) { * Unregister a badge from the Badges API * @param badge The badge to remove */ -export function removeBadge(badge: ProfileBadge) { +export function removeProfileBadge(badge: ProfileBadge) { return Badges.delete(badge); } @@ -100,20 +100,3 @@ export interface BadgeUserArgs { userId: string; guildId: string; } - -interface ConnectedAccount { - type: string; - id: string; - name: string; - verified: boolean; -} - -interface Profile { - connectedAccounts: ConnectedAccount[]; - premiumType: number; - premiumSince: string; - premiumGuildSince?: any; - lastFetched: number; - profileFetchFailed: boolean; - application?: any; -} diff --git a/src/api/ChatButtons.tsx b/src/api/ChatButtons.tsx index d38f4ff50..c24e3886f 100644 --- a/src/api/ChatButtons.tsx +++ b/src/api/ChatButtons.tsx @@ -74,9 +74,9 @@ export interface ChatBarProps { }; } -export type ChatBarButton = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; +export type ChatBarButtonFactory = (props: ChatBarProps & { isMainChat: boolean; }) => JSX.Element | null; -const buttonFactories = new Map(); +const buttonFactories = new Map(); const logger = new Logger("ChatButtons"); export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) { @@ -91,7 +91,7 @@ export function _injectButtons(buttons: ReactNode[], props: ChatBarProps) { } } -export const addChatBarButton = (id: string, button: ChatBarButton) => buttonFactories.set(id, button); +export const addChatBarButton = (id: string, button: ChatBarButtonFactory) => buttonFactories.set(id, button); export const removeChatBarButton = (id: string) => buttonFactories.delete(id); export interface ChatBarButtonProps { diff --git a/src/api/MemberListDecorators.ts b/src/api/MemberListDecorators.tsx similarity index 61% rename from src/api/MemberListDecorators.ts rename to src/api/MemberListDecorators.tsx index ba5ec8d14..2199f4a6c 100644 --- a/src/api/MemberListDecorators.ts +++ b/src/api/MemberListDecorators.tsx @@ -16,6 +16,7 @@ * along with this program. If not, see . */ +import ErrorBoundary from "@components/ErrorBoundary"; import { Channel, User } from "discord-types/general/index.js"; import { JSX } from "react"; @@ -39,27 +40,32 @@ interface DecoratorProps { user: User; [key: string]: any; } -export type Decorator = (props: DecoratorProps) => JSX.Element | null; +export type MemberListDecoratorFactory = (props: DecoratorProps) => JSX.Element | null; type OnlyIn = "guilds" | "dms"; -export const decorators = new Map(); +export const decorators = new Map(); -export function addDecorator(identifier: string, decorator: Decorator, onlyIn?: OnlyIn) { - decorators.set(identifier, { decorator, onlyIn }); +export function addMemberListDecorator(identifier: string, render: MemberListDecoratorFactory, onlyIn?: OnlyIn) { + decorators.set(identifier, { render, onlyIn }); } -export function removeDecorator(identifier: string) { +export function removeMemberListDecorator(identifier: string) { decorators.delete(identifier); } export function __getDecorators(props: DecoratorProps): (JSX.Element | null)[] { const isInGuild = !!(props.guildId); - return Array.from(decorators.values(), decoratorObj => { - const { decorator, onlyIn } = decoratorObj; - // this can most likely be done cleaner - if (!onlyIn || (onlyIn === "guilds" && isInGuild) || (onlyIn === "dms" && !isInGuild)) { - return decorator(props); + return Array.from( + decorators.entries(), + ([key, { render: Decorator, onlyIn }]) => { + if ((onlyIn === "guilds" && !isInGuild) || (onlyIn === "dms" && isInGuild)) + return null; + + return ( + + + + ); } - return null; - }); + ); } diff --git a/src/api/MessageAccessories.ts b/src/api/MessageAccessories.tsx similarity index 63% rename from src/api/MessageAccessories.ts rename to src/api/MessageAccessories.tsx index 8454732f4..71664e93a 100644 --- a/src/api/MessageAccessories.ts +++ b/src/api/MessageAccessories.tsx @@ -16,28 +16,29 @@ * along with this program. If not, see . */ -import { JSX } from "react"; +import ErrorBoundary from "@components/ErrorBoundary"; +import { JSX, ReactNode } from "react"; -export type AccessoryCallback = (props: Record) => JSX.Element | null | Array; -export type Accessory = { - callback: AccessoryCallback; +export type MessageAccessoryFactory = (props: Record) => ReactNode; +export type MessageAccessory = { + render: MessageAccessoryFactory; position?: number; }; -export const accessories = new Map(); +export const accessories = new Map(); -export function addAccessory( +export function addMessageAccessory( identifier: string, - callback: AccessoryCallback, + render: MessageAccessoryFactory, position?: number ) { accessories.set(identifier, { - callback, + render, position, }); } -export function removeAccessory(identifier: string) { +export function removeMessageAccessory(identifier: string) { accessories.delete(identifier); } @@ -45,15 +46,12 @@ export function _modifyAccessories( elements: JSX.Element[], props: Record ) { - for (const accessory of accessories.values()) { - let accessories = accessory.callback(props); - if (accessories == null) - continue; - - if (!Array.isArray(accessories)) - accessories = [accessories]; - else if (accessories.length === 0) - continue; + for (const [key, accessory] of accessories.entries()) { + const res = ( + + + + ); elements.splice( accessory.position != null @@ -62,7 +60,7 @@ export function _modifyAccessories( : accessory.position : elements.length, 0, - ...accessories.filter(e => e != null) as JSX.Element[] + res ); } diff --git a/src/api/MessageDecorations.ts b/src/api/MessageDecorations.tsx similarity index 65% rename from src/api/MessageDecorations.ts rename to src/api/MessageDecorations.tsx index 0d69ab11c..740c95876 100644 --- a/src/api/MessageDecorations.ts +++ b/src/api/MessageDecorations.tsx @@ -16,10 +16,11 @@ * along with this program. If not, see . */ +import ErrorBoundary from "@components/ErrorBoundary"; import { Channel, Message } from "discord-types/general/index.js"; import { JSX } from "react"; -interface DecorationProps { +export interface MessageDecorationProps { author: { /** * Will be username if the user has no nickname @@ -45,20 +46,25 @@ interface DecorationProps { message: Message; [key: string]: any; } -export type Decoration = (props: DecorationProps) => JSX.Element | null; +export type MessageDecorationFactory = (props: MessageDecorationProps) => JSX.Element | null; -export const decorations = new Map(); +export const decorations = new Map(); -export function addDecoration(identifier: string, decoration: Decoration) { +export function addMessageDecoration(identifier: string, decoration: MessageDecorationFactory) { decorations.set(identifier, decoration); } -export function removeDecoration(identifier: string) { +export function removeMessageDecoration(identifier: string) { decorations.delete(identifier); } -export function __addDecorationsToMessage(props: DecorationProps): (JSX.Element | null)[] { - return [...decorations.values()].map(decoration => { - return decoration(props); - }); +export function __addDecorationsToMessage(props: MessageDecorationProps): (JSX.Element | null)[] { + return Array.from( + decorations.entries(), + ([key, Decoration]) => ( + + + + ) + ); } diff --git a/src/api/MessageEvents.ts b/src/api/MessageEvents.ts index d6eba748f..1b55ff340 100644 --- a/src/api/MessageEvents.ts +++ b/src/api/MessageEvents.ts @@ -73,11 +73,11 @@ export interface MessageExtra { openWarningPopout: (props: any) => any; } -export type SendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable; -export type EditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable; +export type MessageSendListener = (channelId: string, messageObj: MessageObject, extra: MessageExtra) => Promisable; +export type MessageEditListener = (channelId: string, messageId: string, messageObj: MessageObject) => Promisable; -const sendListeners = new Set(); -const editListeners = new Set(); +const sendListeners = new Set(); +const editListeners = new Set(); export async function _handlePreSend(channelId: string, messageObj: MessageObject, extra: MessageExtra, replyOptions: MessageReplyOptions) { extra.replyOptions = replyOptions; @@ -111,29 +111,29 @@ export async function _handlePreEdit(channelId: string, messageId: string, messa /** * Note: This event fires off before a message is sent, allowing you to edit the message. */ -export function addPreSendListener(listener: SendListener) { +export function addMessagePreSendListener(listener: MessageSendListener) { sendListeners.add(listener); return listener; } /** * Note: This event fires off before a message's edit is applied, allowing you to further edit the message. */ -export function addPreEditListener(listener: EditListener) { +export function addMessagePreEditListener(listener: MessageEditListener) { editListeners.add(listener); return listener; } -export function removePreSendListener(listener: SendListener) { +export function removeMessagePreSendListener(listener: MessageSendListener) { return sendListeners.delete(listener); } -export function removePreEditListener(listener: EditListener) { +export function removeMessagePreEditListener(listener: MessageEditListener) { return editListeners.delete(listener); } // Message clicks -type ClickListener = (message: Message, channel: Channel, event: MouseEvent) => void; +export type MessageClickListener = (message: Message, channel: Channel, event: MouseEvent) => void; -const listeners = new Set(); +const listeners = new Set(); export function _handleClick(message: Message, channel: Channel, event: MouseEvent) { // message object may be outdated, so (try to) fetch latest one @@ -147,11 +147,11 @@ export function _handleClick(message: Message, channel: Channel, event: MouseEve } } -export function addClickListener(listener: ClickListener) { +export function addMessageClickListener(listener: MessageClickListener) { listeners.add(listener); return listener; } -export function removeClickListener(listener: ClickListener) { +export function removeMessageClickListener(listener: MessageClickListener) { return listeners.delete(listener); } diff --git a/src/api/MessagePopover.tsx b/src/api/MessagePopover.tsx index eb68ed2d6..717879546 100644 --- a/src/api/MessagePopover.tsx +++ b/src/api/MessagePopover.tsx @@ -23,7 +23,7 @@ import type { ComponentType, MouseEventHandler } from "react"; const logger = new Logger("MessagePopover"); -export interface ButtonItem { +export interface MessagePopoverButtonItem { key?: string, label: string, icon: ComponentType, @@ -33,23 +33,23 @@ export interface ButtonItem { onContextMenu?: MouseEventHandler; } -export type getButtonItem = (message: Message) => ButtonItem | null; +export type MessagePopoverButtonFactory = (message: Message) => MessagePopoverButtonItem | null; -export const buttons = new Map(); +export const buttons = new Map(); -export function addButton( +export function addMessagePopoverButton( identifier: string, - item: getButtonItem, + item: MessagePopoverButtonFactory, ) { buttons.set(identifier, item); } -export function removeButton(identifier: string) { +export function removeMessagePopoverButton(identifier: string) { buttons.delete(identifier); } export function _buildPopoverElements( - Component: React.ComponentType, + Component: React.ComponentType, message: Message ) { const items: React.ReactNode[] = []; diff --git a/src/api/ServerList.ts b/src/api/ServerList.tsx similarity index 64% rename from src/api/ServerList.ts rename to src/api/ServerList.tsx index 462745b04..7a673c9df 100644 --- a/src/api/ServerList.ts +++ b/src/api/ServerList.tsx @@ -16,41 +16,36 @@ * along with this program. If not, see . */ -import { Logger } from "@utils/Logger"; -import { JSX } from "react"; - -const logger = new Logger("ServerListAPI"); +import ErrorBoundary from "@components/ErrorBoundary"; +import { ComponentType } from "react"; export const enum ServerListRenderPosition { Above, In, } -const renderFunctionsAbove = new Set(); -const renderFunctionsIn = new Set(); +const componentsAbove = new Set(); +const componentsBelow = new Set(); function getRenderFunctions(position: ServerListRenderPosition) { - return position === ServerListRenderPosition.Above ? renderFunctionsAbove : renderFunctionsIn; + return position === ServerListRenderPosition.Above ? componentsAbove : componentsBelow; } -export function addServerListElement(position: ServerListRenderPosition, renderFunction: Function) { +export function addServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) { getRenderFunctions(position).add(renderFunction); } -export function removeServerListElement(position: ServerListRenderPosition, renderFunction: Function) { +export function removeServerListElement(position: ServerListRenderPosition, renderFunction: ComponentType) { getRenderFunctions(position).delete(renderFunction); } export const renderAll = (position: ServerListRenderPosition) => { - const ret: Array = []; - - for (const renderFunction of getRenderFunctions(position)) { - try { - ret.unshift(renderFunction()); - } catch (e) { - logger.error("Failed to render server list element:", e); - } - } - - return ret; + return Array.from( + getRenderFunctions(position), + (Component, i) => ( + + + + ) + ); }; diff --git a/src/components/ErrorBoundary.tsx b/src/components/ErrorBoundary.tsx index 60ff1faf2..bb2df3421 100644 --- a/src/components/ErrorBoundary.tsx +++ b/src/components/ErrorBoundary.tsx @@ -70,8 +70,7 @@ const ErrorBoundary = LazyComponent(() => { componentDidCatch(error: Error, errorInfo: React.ErrorInfo) { this.props.onError?.({ error, errorInfo, props: this.props.wrappedProps }); - logger.error("A component threw an Error\n", error); - logger.error("Component Stack", errorInfo.componentStack); + logger.error(`${this.props.message || "A component threw an Error"}\n`, error, errorInfo.componentStack); } render() { diff --git a/src/components/PluginSettings/PluginModal.tsx b/src/components/PluginSettings/PluginModal.tsx index 96cc2602a..1252f52b6 100644 --- a/src/components/PluginSettings/PluginModal.tsx +++ b/src/components/PluginSettings/PluginModal.tsx @@ -83,6 +83,7 @@ const Components: Record null, [OptionType.ARRAY]: SettingArrayComponent, [OptionType.USERS]: SettingArrayComponent, [OptionType.CHANNELS]: SettingArrayComponent, @@ -134,7 +135,8 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti for (const [key, value] of Object.entries(tempSettings)) { const option = plugin.options[key]; pluginSettings[key] = value; - option?.onChange?.(value); + + if (option.type === OptionType.CUSTOM) continue; if (option?.restartNeeded) restartNeeded = true; } if (restartNeeded) onRestartNeeded(); @@ -146,7 +148,7 @@ export default function PluginModal({ plugin, onRestartNeeded, onClose, transiti return There are no settings for this plugin.; } else { const options = Object.entries(plugin.options).map(([key, setting]) => { - if (setting.hidden) return null; + if (setting.type === OptionType.CUSTOM || setting.hidden) return null; function onChange(newValue: any) { setTempSettings(s => ({ ...s, [key]: newValue })); diff --git a/src/plugins/_api/badges/index.tsx b/src/plugins/_api/badges/index.tsx index d02b5e1d8..2a83809d6 100644 --- a/src/plugins/_api/badges/index.tsx +++ b/src/plugins/_api/badges/index.tsx @@ -79,7 +79,7 @@ export default definePlugin({ replace: "...$1.props,$& $1.image??" }, { - match: /(?<=text:(\i)\.description,.{0,200})children:/, + match: /(?<="aria-label":(\i)\.description,.{0,200})children:/, replace: "children:$1.component ? $self.renderBadgeComponent({ ...$1 }) :" }, // conditionally override their onClick with badge.onClick if it exists @@ -102,8 +102,9 @@ export default definePlugin({ } }, + userProfileBadge: ContributorBadge, + async start() { - Vencord.Api.Badges.addBadge(ContributorBadge); await loadBadges(); }, diff --git a/src/plugins/_api/messageEvents.ts b/src/plugins/_api/messageEvents.ts index 0101b02c8..97ed1746d 100644 --- a/src/plugins/_api/messageEvents.ts +++ b/src/plugins/_api/messageEvents.ts @@ -31,7 +31,7 @@ export default definePlugin({ replace: (match, args) => "" + `async ${match}` + `if(await Vencord.Api.MessageEvents._handlePreEdit(${args}))` + - "return Promise.resolve({shoudClear:true,shouldRefocus:true});" + "return Promise.resolve({shouldClear:false,shouldRefocus:true});" } }, { @@ -39,12 +39,12 @@ export default definePlugin({ replacement: { // props.chatInputType...then((function(isMessageValid)... var parsedMessage = b.c.parse(channel,... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); // Lookbehind: validateMessage)({openWarningPopout:..., type: i.props.chatInputType, content: t, stickers: r, ...}).then((function(isMessageValid) - match: /(type:this\.props\.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptionsForReply\(\i\);)(?<=\)\(({.+?})\)\.then.+?)/, + match: /(\{openWarningPopout:.{0,100}type:this.props.chatInputType.+?\.then\()(\i=>\{.+?let (\i)=\i\.\i\.parse\((\i),.+?let (\i)=\i\.\i\.getSendMessageOptions\(\{.+?\}\);)(?<=\)\(({.+?})\)\.then.+?)/, // props.chatInputType...then((async function(isMessageValid)... var replyOptions = f.g.getSendMessageOptionsForReply(pendingReply); if(await Vencord.api...) return { shoudClear:true, shouldRefocus:true }; replace: (_, rest1, rest2, parsedMessage, channel, replyOptions, extra) => "" + `${rest1}async ${rest2}` + `if(await Vencord.Api.MessageEvents._handlePreSend(${channel}.id,${parsedMessage},${extra},${replyOptions}))` + - "return{shoudClear:true,shouldRefocus:true};" + "return{shouldClear:false,shouldRefocus:true};" } }, { diff --git a/src/plugins/_api/notices.ts b/src/plugins/_api/notices.ts index 0c6f6e1db..f0445924e 100644 --- a/src/plugins/_api/notices.ts +++ b/src/plugins/_api/notices.ts @@ -34,7 +34,7 @@ export default definePlugin({ }, { match: /(?<=,NOTICE_DISMISS:function\(\i\){)return null!=(\i)/, - replace: "if($1.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&" + replace: "if($1?.id==\"VencordNotice\")return($1=null,Vencord.Api.Notices.nextNotice(),true);$&" } ] } diff --git a/src/plugins/_core/supportHelper.tsx b/src/plugins/_core/supportHelper.tsx index 8687f843b..72b683249 100644 --- a/src/plugins/_core/supportHelper.tsx +++ b/src/plugins/_core/supportHelper.tsx @@ -16,7 +16,6 @@ * along with this program. If not, see . */ -import { addAccessory } from "@api/MessageAccessories"; import { definePluginSettings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import ErrorBoundary from "@components/ErrorBoundary"; @@ -143,7 +142,7 @@ export default definePlugin({ required: true, description: "Helps us provide support to you", authors: [Devs.Ven], - dependencies: ["UserSettingsAPI", "MessageAccessoriesAPI"], + dependencies: ["UserSettingsAPI"], settings, @@ -236,6 +235,85 @@ export default definePlugin({ } }, + renderMessageAccessory(props) { + const buttons = [] as JSX.Element[]; + + const shouldAddUpdateButton = + !IS_UPDATER_DISABLED + && ( + (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || + (props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) + ) + && props.message.content?.includes("update"); + + if (shouldAddUpdateButton) { + buttons.push( + + ); + } + + if (props.channel.id === SUPPORT_CHANNEL_ID) { + if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { + buttons.push( + , + + ); + } + + if (props.message.author.id === VENBOT_USER_ID) { + const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); + if (match) { + buttons.push( + + ); + } + } + } + + return buttons.length + ? {buttons} + : null; + }, + renderContributorDmWarningCard: ErrorBoundary.wrap(({ channel }) => { const userId = channel.getRecipientId(); if (!isPluginDev(userId)) return null; @@ -250,85 +328,4 @@ export default definePlugin({ ); }, { noop: true }), - - start() { - addAccessory("vencord-debug", props => { - const buttons = [] as JSX.Element[]; - - const shouldAddUpdateButton = - !IS_UPDATER_DISABLED - && ( - (props.channel.id === KNOWN_ISSUES_CHANNEL_ID) || - (props.channel.id === SUPPORT_CHANNEL_ID && props.message.author.id === VENBOT_USER_ID) - ) - && props.message.content?.includes("update"); - - if (shouldAddUpdateButton) { - buttons.push( - - ); - } - - if (props.channel.id === SUPPORT_CHANNEL_ID) { - if (props.message.content.includes("/vencord-debug") || props.message.content.includes("/vencord-plugins")) { - buttons.push( - , - - ); - } - - if (props.message.author.id === VENBOT_USER_ID) { - const match = CodeBlockRe.exec(props.message.content || props.message.embeds[0]?.rawDescription || ""); - if (match) { - buttons.push( - - ); - } - } - } - - return buttons.length - ? {buttons} - : null; - }); - }, }); diff --git a/src/plugins/accountPanelServerProfile/index.tsx b/src/plugins/accountPanelServerProfile/index.tsx index 1b468a6d8..a2fed5d79 100644 --- a/src/plugins/accountPanelServerProfile/index.tsx +++ b/src/plugins/accountPanelServerProfile/index.tsx @@ -16,14 +16,14 @@ import { User } from "discord-types/general"; interface UserProfileProps { popoutProps: Record; currentUser: User; - originalPopout: () => React.ReactNode; + originalRenderPopout: () => React.ReactNode; } const UserProfile = findComponentByCodeLazy("UserProfilePopoutWrapper: user cannot be undefined"); const styles = findByPropsLazy("accountProfilePopoutWrapper"); let openAlternatePopout = false; -let accountPanelRef: React.MutableRefObject | null> = { current: null }; +let accountPanelRef: React.RefObject | null> = { current: null }; const AccountPanelContextMenu = ErrorBoundary.wrap(() => { const { prioritizeServerProfile } = settings.use(["prioritizeServerProfile"]); @@ -73,12 +73,12 @@ export default definePlugin({ group: true, replacement: [ { - match: /(?<=\.SIZE_32\)}\);)/, + match: /(?<=\.AVATAR_SIZE\);)/, replace: "$self.useAccountPanelRef();" }, { match: /(\.AVATAR,children:.+?renderPopout:(\i)=>){(.+?)}(?=,position)(?<=currentUser:(\i).+?)/, - replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalPopout:()=>{${originalPopout}}})` + replace: (_, rest, popoutProps, originalPopout, currentUser) => `${rest}$self.UserProfile({popoutProps:${popoutProps},currentUser:${currentUser},originalRenderPopout:()=>{${originalPopout}}})` }, { match: /\.AVATAR,children:.+?(?=renderPopout:)/, @@ -112,17 +112,17 @@ export default definePlugin({ openAlternatePopout = false; }, - UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalPopout }: UserProfileProps) => { + UserProfile: ErrorBoundary.wrap(({ popoutProps, currentUser, originalRenderPopout }: UserProfileProps) => { if ( (settings.store.prioritizeServerProfile && openAlternatePopout) || (!settings.store.prioritizeServerProfile && !openAlternatePopout) ) { - return originalPopout(); + return originalRenderPopout(); } const currentChannel = getCurrentChannel(); if (currentChannel?.getGuildId() == null) { - return originalPopout(); + return originalRenderPopout(); } return ( diff --git a/src/plugins/alwaysAnimate/index.ts b/src/plugins/alwaysAnimate/index.ts index 97593990c..fc528466f 100644 --- a/src/plugins/alwaysAnimate/index.ts +++ b/src/plugins/alwaysAnimate/index.ts @@ -41,7 +41,7 @@ export default definePlugin({ }, { // Status emojis - find: "#{intl::GUILD_OWNER}", + find: "#{intl::GUILD_OWNER}),children:", replacement: { match: /(?<=\.activityEmoji,.+?animate:)\i/, replace: "!0" diff --git a/src/plugins/betterSettings/index.tsx b/src/plugins/betterSettings/index.tsx index 0a3f0bb84..5a97b0a6e 100644 --- a/src/plugins/betterSettings/index.tsx +++ b/src/plugins/betterSettings/index.tsx @@ -114,7 +114,7 @@ export default definePlugin({ { // Load menu TOC eagerly find: "#{intl::USER_SETTINGS_WITH_BUILD_OVERRIDE}", replacement: { - match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await Promise\.all[^};]*?\)\)).*?,(?=\1\(this)/, + match: /(\i)\(this,"handleOpenSettingsContextMenu",.{0,100}?null!=\i&&.{0,100}?(await [^};]*?\)\)).*?,(?=\1\(this)/, replace: "$&(async ()=>$2)()," }, predicate: () => settings.store.eagerLoad diff --git a/src/plugins/betterUploadButton/index.ts b/src/plugins/betterUploadButton/index.ts index 2406b71e5..c08d43282 100644 --- a/src/plugins/betterUploadButton/index.ts +++ b/src/plugins/betterUploadButton/index.ts @@ -27,7 +27,7 @@ export default definePlugin({ { find: '"ChannelAttachButton"', replacement: { - match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),\.\.\.(\i),/, + match: /\.attachButtonInner,"aria-label":.{0,50},onDoubleClick:(.+?:void 0),.{0,30}?\.\.\.(\i),/, replace: "$&onClick:$1,onContextMenu:$2.onClick,", }, }, diff --git a/src/plugins/callTimer/index.tsx b/src/plugins/callTimer/index.tsx index e16abc4a1..bdcca7772 100644 --- a/src/plugins/callTimer/index.tsx +++ b/src/plugins/callTimer/index.tsx @@ -75,8 +75,8 @@ export default definePlugin({ patches: [{ find: "renderConnectionStatus(){", replacement: { - match: /(?<=renderConnectionStatus\(\){.+\.channel,children:).+?}\):\i(?=}\))/, - replace: "[$&, $self.renderTimer(this.props.channel.id)]" + match: /(renderConnectionStatus\(\){.+\.channel,children:)(.+?}\):\i)(?=}\))/, + replace: "$1[$2,$self.renderTimer(this.props.channel.id)]" } }], diff --git a/src/plugins/clearURLs/index.ts b/src/plugins/clearURLs/index.ts index d1be6c6f5..f00c751d4 100644 --- a/src/plugins/clearURLs/index.ts +++ b/src/plugins/clearURLs/index.ts @@ -17,11 +17,7 @@ */ import { - addPreEditListener, - addPreSendListener, - MessageObject, - removePreEditListener, - removePreSendListener + MessageObject } from "@api/MessageEvents"; import { Devs } from "@utils/constants"; import definePlugin from "@utils/types"; @@ -36,7 +32,18 @@ export default definePlugin({ name: "ClearURLs", description: "Removes tracking garbage from URLs", authors: [Devs.adryd], - dependencies: ["MessageEventsAPI"], + + start() { + this.createRules(); + }, + + onBeforeMessageSend(_, msg) { + return this.onSend(msg); + }, + + onBeforeMessageEdit(_cid, _mid, msg) { + return this.onSend(msg); + }, escapeRegExp(str: string) { return (str && reHasRegExpChar.test(str)) @@ -133,17 +140,4 @@ export default definePlugin({ ); } }, - - start() { - this.createRules(); - this.preSend = addPreSendListener((_, msg) => this.onSend(msg)); - this.preEdit = addPreEditListener((_cid, _mid, msg) => - this.onSend(msg) - ); - }, - - stop() { - removePreSendListener(this.preSend); - removePreEditListener(this.preEdit); - }, }); diff --git a/src/plugins/consoleShortcuts/index.ts b/src/plugins/consoleShortcuts/index.ts index 25d48f1d8..be23d37c3 100644 --- a/src/plugins/consoleShortcuts/index.ts +++ b/src/plugins/consoleShortcuts/index.ts @@ -179,6 +179,16 @@ export default definePlugin({ description: "Adds shorter Aliases for many things on the window. Run `shortcutList` for a list.", authors: [Devs.Ven], + patches: [ + { + find: 'this,"_changeCallbacks",', + replacement: { + match: /\i\(this,"_changeCallbacks",/, + replace: "Reflect.defineProperty(this,Symbol.toStringTag,{value:this.getName(),configurable:!0,writable:!0,enumerable:!1}),$&" + } + } + ], + startAt: StartAt.Init, start() { const shortcuts = makeShortcuts(); diff --git a/src/plugins/customRPC/index.tsx b/src/plugins/customRPC/index.tsx index f4b9ab060..6f7719332 100644 --- a/src/plugins/customRPC/index.tsx +++ b/src/plugins/customRPC/index.tsx @@ -19,6 +19,7 @@ import { definePluginSettings, Settings } from "@api/Settings"; import { getUserSettingLazy } from "@api/UserSettings"; import { ErrorCard } from "@components/ErrorCard"; +import { Flex } from "@components/Flex"; import { Link } from "@components/Link"; import { Devs } from "@utils/constants"; import { isTruthy } from "@utils/guards"; @@ -27,15 +28,14 @@ import { classes } from "@utils/misc"; import { useAwaiter } from "@utils/react"; import definePlugin, { OptionType } from "@utils/types"; import { findByCodeLazy, findComponentByCodeLazy } from "@webpack"; -import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, GuildStore, React, SelectedChannelStore, SelectedGuildStore, UserStore } from "@webpack/common"; +import { ApplicationAssetUtils, Button, FluxDispatcher, Forms, React, UserStore } from "@webpack/common"; const useProfileThemeStyle = findByCodeLazy("profileThemeStyle:", "--profile-gradient-primary-color"); -const ActivityComponent = findComponentByCodeLazy("onOpenGameProfile"); +const ActivityView = findComponentByCodeLazy(".party?(0", ".card"); const ShowCurrentGame = getUserSettingLazy("status", "showCurrentGame")!; async function getApplicationAsset(key: string): Promise { - if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\/attachments\//.test(key)) return "mp:" + key.replace(/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//, ""); return (await ApplicationAssetUtils.fetchAssetIds(settings.store.appID!, [key]))[0]; } @@ -169,7 +169,7 @@ const settings = definePluginSettings({ value: TimestampMode.NOW }, { - label: "Same as your current time", + label: "Same as your current time (not reset after 24h)", value: TimestampMode.TIME }, { @@ -269,6 +269,7 @@ function isStreamLinkDisabled() { function isStreamLinkValid(value: string) { if (!isStreamLinkDisabled() && !/https?:\/\/(www\.)?(twitch\.tv|youtube\.com)\/\w+/.test(value)) return "Streaming link must be a valid URL."; + if (value && value.length > 512) return "Streaming link must be not longer than 512 characters."; return true; } @@ -277,8 +278,9 @@ function isTimestampDisabled() { } function isImageKeyValid(value: string) { - if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image. (e.g. https://i.imgur.com/...)"; - if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image. (e.g. https://media.tenor.com/...)"; + if (/https?:\/\/(cdn|media)\.discordapp\.(com|net)\//.test(value)) return "Don't use a Discord link. Use an Imgur image link instead."; + if (/https?:\/\/(?!i\.)?imgur\.com\//.test(value)) return "Imgur link must be a direct link to the image (e.g. https://i.imgur.com/...). Right click the image and click 'Copy image address'"; + if (/https?:\/\/(?!media\.)?tenor\.com\//.test(value)) return "Tenor link must be a direct link to the image (e.g. https://media.tenor.com/...). Right click the GIF and click 'Copy image address'"; return true; } @@ -390,13 +392,24 @@ async function setRpc(disable?: boolean) { export default definePlugin({ name: "CustomRPC", - description: "Allows you to set a custom rich presence.", + description: "Add a fully customisable Rich Presence (Game status) to your Discord profile", authors: [Devs.captain, Devs.AutumnVN, Devs.nin0dev], dependencies: ["UserSettingsAPI"], start: setRpc, stop: () => setRpc(true), settings, + patches: [ + { + find: ".party?(0", + all: true, + replacement: { + match: /\i\.id===\i\.id\?null:/, + replace: "" + } + } + ], + settingsAboutComponent: () => { const activity = useAwaiter(createActivity); const gameActivityEnabled = ShowCurrentGame.useSetting(); @@ -410,7 +423,7 @@ export default definePlugin({ style={{ padding: "1em" }} > Notice - Game activity isn't enabled, people won't be able to see your custom rich presence! + Activity Sharing isn't enabled, people won't be able to see your custom rich presence!