diff --git a/src/plugins/automodContext/README.md b/src/plugins/automodContext/README.md new file mode 100644 index 00000000..f70d71d9 --- /dev/null +++ b/src/plugins/automodContext/README.md @@ -0,0 +1,5 @@ +# AutomodContext + +Allows you to jump to the messages surrounding an automod hit + +![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/d13740c8-2062-4553-b975-82fd3d6cc08b) diff --git a/src/plugins/automodContext/index.tsx b/src/plugins/automodContext/index.tsx new file mode 100644 index 00000000..5425c552 --- /dev/null +++ b/src/plugins/automodContext/index.tsx @@ -0,0 +1,73 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import ErrorBoundary from "@components/ErrorBoundary"; +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { findByPropsLazy } from "@webpack"; +import { Button, ChannelStore, Text } from "@webpack/common"; + +const { selectChannel } = findByPropsLazy("selectChannel", "selectVoiceChannel"); + +function jumpToMessage(channelId: string, messageId: string) { + const guildId = ChannelStore.getChannel(channelId)?.guild_id; + + selectChannel({ + guildId, + channelId, + messageId, + jumpType: "INSTANT" + }); +} + +function findChannelId(message: any): string | null { + const { embeds: [embed] } = message; + const channelField = embed.fields.find(({ rawName }) => rawName === "channel_id"); + + if (!channelField) { + return null; + } + + return channelField.rawValue; +} + +export default definePlugin({ + name: "AutomodContext", + description: "Allows you to jump to the messages surrounding an automod hit.", + authors: [Devs.JohnyTheCarrot], + + patches: [ + { + find: ".Messages.GUILD_AUTOMOD_REPORT_ISSUES", + replacement: { + match: /\.Messages\.ACTIONS.+?}\)(?=,(\(0.{0,40}\.dot.*?}\)),)/, + replace: (m, dot) => `${m},${dot},$self.renderJumpButton({message:arguments[0].message})` + } + } + ], + + renderJumpButton: ErrorBoundary.wrap(({ message }: { message: any; }) => { + const channelId = findChannelId(message); + + if (!channelId) { + return null; + } + + return ( + + ); + }, { noop: true }) +}); diff --git a/src/plugins/fakeNitro/index.tsx b/src/plugins/fakeNitro/index.tsx index eb7e2f81..f30aff2f 100644 --- a/src/plugins/fakeNitro/index.tsx +++ b/src/plugins/fakeNitro/index.tsx @@ -25,7 +25,7 @@ import { Logger } from "@utils/Logger"; import definePlugin, { OptionType } from "@utils/types"; import { findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; -import type { CustomEmoji } from "@webpack/types"; +import type { Emoji } from "@webpack/types"; import type { Message } from "discord-types/general"; import { applyPalette, GIFEncoder, quantize } from "gifenc"; import type { ReactElement, ReactNode } from "react"; @@ -54,16 +54,22 @@ const ClientThemeSettingsActionsCreators = proxyLazyWebpack(() => searchProtoCla const enum EmojiIntentions { - REACTION = 0, - STATUS = 1, - COMMUNITY_CONTENT = 2, - CHAT = 3, - GUILD_STICKER_RELATED_EMOJI = 4, - GUILD_ROLE_BENEFIT_EMOJI = 5, - COMMUNITY_CONTENT_ONLY = 6, - SOUNDBOARD = 7 + REACTION, + STATUS, + COMMUNITY_CONTENT, + CHAT, + GUILD_STICKER_RELATED_EMOJI, + GUILD_ROLE_BENEFIT_EMOJI, + COMMUNITY_CONTENT_ONLY, + SOUNDBOARD, + VOICE_CHANNEL_TOPIC, + GIFT, + AUTO_SUGGESTION, + POLLS } +const IS_BYPASSEABLE_INTENTION = `[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)`; + const enum StickerType { PNG = 1, APNG = 2, @@ -198,37 +204,43 @@ export default definePlugin({ patches: [ { find: ".PREMIUM_LOCKED;", + group: true, predicate: () => settings.store.enableEmojiBypass, replacement: [ { - // Create a variable for the intention of listing the emoji - match: /(?<=,intention:(\i).+?;)/, - replace: (_, intention) => `let fakeNitroIntention=${intention};` + // Create a variable for the intention of using the emoji + match: /(?<=\.USE_EXTERNAL_EMOJIS.+?;)(?<=intention:(\i).+?)/, + replace: (_, intention) => `const fakeNitroIntention=${intention};` }, { - // Send the intention of listing the emoji to the nitro permission check functions - match: /\.(?:canUseEmojisEverywhere|canUseAnimatedEmojis)\(\i(?=\))/g, - replace: '$&,typeof fakeNitroIntention!=="undefined"?fakeNitroIntention:void 0' + // Disallow the emoji for external if the intention doesn't allow it + match: /&&!\i&&!\i(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, + replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}` }, { - // Disallow the emoji if the intention doesn't allow it - match: /(&&!\i&&)!(\i)(?=\)return \i\.\i\.DISALLOW_EXTERNAL;)/, - replace: (_, rest, canUseExternal) => `${rest}(!${canUseExternal}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)))` + // Disallow the emoji for unavailable if the intention doesn't allow it + match: /!\i\.available(?=\)return \i\.\i\.GUILD_SUBSCRIPTION_UNAVAILABLE;)/, + replace: m => `${m}&&!${IS_BYPASSEABLE_INTENTION}` }, { - // Make the emoji always available if the intention allows it - match: /if\(!\i\.available/, - replace: m => `${m}&&(typeof fakeNitroIntention==="undefined"||![${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention))` + // Disallow the emoji for premium locked if the intention doesn't allow it + match: /!\i\.\i\.canUseEmojisEverywhere\(\i\)/, + replace: m => `(${m}&&!${IS_BYPASSEABLE_INTENTION})` + }, + { + // Allow animated emojis to be used if the intention allows it + match: /(?<=\|\|)\i\.\i\.canUseAnimatedEmojis\(\i\)/, + replace: m => `(${m}||${IS_BYPASSEABLE_INTENTION})` } ] }, - // Allow emojis and animated emojis to be sent everywhere + // Allows the usage of subscription-locked emojis { - find: "canUseAnimatedEmojis:function", - predicate: () => settings.store.enableEmojiBypass, + find: "isUnusableRoleSubscriptionEmoji:function", replacement: { - match: /((?:canUseEmojisEverywhere|canUseAnimatedEmojis):function\(\i)\){(.+?\))(?=})/g, - replace: (_, rest, premiumCheck) => `${rest},fakeNitroIntention){${premiumCheck}||fakeNitroIntention==null||[${EmojiIntentions.CHAT},${EmojiIntentions.GUILD_STICKER_RELATED_EMOJI}].includes(fakeNitroIntention)` + match: /isUnusableRoleSubscriptionEmoji:function/, + // Replace the original export with a func that always returns false and alias the original + replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function" } }, // Allow stickers to be sent everywhere @@ -242,10 +254,10 @@ export default definePlugin({ }, // Make stickers always available { - find: "\"SENDABLE\"", + find: '"SENDABLE"', predicate: () => settings.store.enableStickerBypass, replacement: { - match: /(\w+)\.available\?/, + match: /\i\.available\?/, replace: "true?" } }, @@ -408,15 +420,6 @@ export default definePlugin({ match: /canUseCustomNotificationSounds:function\(\i\){/, replace: "$&return true;" } - }, - // Allows the usage of subscription-locked emojis - { - find: "isUnusableRoleSubscriptionEmoji:function", - replacement: { - match: /isUnusableRoleSubscriptionEmoji:function/, - // replace the original export with a func that always returns false and alias the original - replace: "isUnusableRoleSubscriptionEmoji:()=>()=>false,isUnusableRoleSubscriptionEmojiOriginal:function" - } } ], @@ -812,8 +815,8 @@ export default definePlugin({ UploadHandler.promptToUpload([file], ChannelStore.getChannel(channelId), DraftType.ChannelMessage); }, - canUseEmote(e: CustomEmoji, channelId: string) { - if (e.require_colons === false) return true; + canUseEmote(e: Emoji, channelId: string) { + if (e.type === "UNICODE") return true; if (e.available === false) return false; const isUnusableRoleSubEmoji = RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmojiOriginal ?? RoleSubscriptionEmojiUtils.isUnusableRoleSubscriptionEmoji; diff --git a/src/plugins/index.ts b/src/plugins/index.ts index 3291885c..a434b4a6 100644 --- a/src/plugins/index.ts +++ b/src/plugins/index.ts @@ -170,13 +170,14 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: } try { p.start(); - p.started = true; } catch (e) { logger.error(`Failed to start ${name}\n`, e); return false; } } + p.started = true; + if (commands?.length) { logger.debug("Registering commands of plugin", name); for (const cmd of commands) { @@ -206,6 +207,7 @@ export const startPlugin = traceFunction("startPlugin", function startPlugin(p: export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plugin) { const { name, commands, flux, contextMenus } = p; + if (p.stop) { logger.info("Stopping plugin", name); if (!p.started) { @@ -214,13 +216,14 @@ export const stopPlugin = traceFunction("stopPlugin", function stopPlugin(p: Plu } try { p.stop(); - p.started = false; } catch (e) { logger.error(`Failed to stop ${name}\n`, e); return false; } } + p.started = false; + if (commands?.length) { logger.debug("Unregistering commands of plugin", name); for (const cmd of commands) { diff --git a/src/plugins/messageLatency/index.tsx b/src/plugins/messageLatency/index.tsx index 301e605f..d517c0e0 100644 --- a/src/plugins/messageLatency/index.tsx +++ b/src/plugins/messageLatency/index.tsx @@ -22,9 +22,10 @@ interface Diff { hours: number, minutes: number, seconds: number; + milliseconds: number; } -const DISCORD_KT_DELAY = 1471228.928; +const DISCORD_KT_DELAY = 1471228928; const HiddenVisually = findExportedComponentLazy("HiddenVisually"); export default definePlugin({ @@ -42,6 +43,11 @@ export default definePlugin({ type: OptionType.BOOLEAN, description: "Detect old Discord Android clients", default: true + }, + showMillis: { + type: OptionType.BOOLEAN, + description: "Show milliseconds", + default: false } }), @@ -55,12 +61,13 @@ export default definePlugin({ } ], - stringDelta(delta: number) { + stringDelta(delta: number, showMillis: boolean) { const diff: Diff = { - days: Math.round(delta / (60 * 60 * 24)), - hours: Math.round((delta / (60 * 60)) % 24), - minutes: Math.round((delta / (60)) % 60), - seconds: Math.round(delta % 60), + days: Math.round(delta / (60 * 60 * 24 * 1000)), + hours: Math.round((delta / (60 * 60 * 1000)) % 24), + minutes: Math.round((delta / (60 * 1000)) % 60), + seconds: Math.round(delta / 1000 % 60), + milliseconds: Math.round(delta % 1000) }; const str = (k: DiffKey) => diff[k] > 0 ? `${diff[k]} ${diff[k] > 1 ? k : k.substring(0, k.length - 1)}` : null; @@ -72,7 +79,7 @@ export default definePlugin({ return prev + ( isNonNullish(s) ? (prev !== "" - ? k === "seconds" + ? (showMillis ? k === "milliseconds" : k === "seconds") ? " and " : " " : "") + s @@ -84,18 +91,21 @@ export default definePlugin({ }, latencyTooltipData(message: Message) { - const { latency, detectDiscordKotlin } = this.settings.store; + const { latency, detectDiscordKotlin, showMillis } = this.settings.store; const { id, nonce } = message; // Message wasn't received through gateway if (!isNonNullish(nonce)) return null; let isDiscordKotlin = false; - let delta = Math.round((SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce)) / 1000); + let delta = SnowflakeUtils.extractTimestamp(id) - SnowflakeUtils.extractTimestamp(nonce); // milliseconds + if (!showMillis) { + delta = Math.round(delta / 1000) * 1000; + } // Old Discord Android clients have a delay of around 17 days // This is a workaround for that - if (-delta >= DISCORD_KT_DELAY - 86400) { // One day of padding for good measure + if (-delta >= DISCORD_KT_DELAY - 86400000) { // One day of padding for good measure isDiscordKotlin = detectDiscordKotlin; delta += DISCORD_KT_DELAY; } @@ -105,22 +115,23 @@ export default definePlugin({ // Can't do anything if the clock is behind const abs = Math.abs(delta); const ahead = abs !== delta; + const latencyMillis = latency * 1000; - const stringDelta = abs >= latency ? this.stringDelta(abs) : null; + const stringDelta = abs >= latencyMillis ? this.stringDelta(abs, showMillis) : null; // Also thanks dziurwa // 2 minutes - const TROLL_LIMIT = 2 * 60; + const TROLL_LIMIT = 2 * 60 * 1000; const fill: Fill = isDiscordKotlin ? ["status-positive", "status-positive", "text-muted"] : delta >= TROLL_LIMIT || ahead ? ["text-muted", "text-muted", "text-muted"] - : delta >= (latency * 2) + : delta >= (latencyMillis * 2) ? ["status-danger", "text-muted", "text-muted"] : ["status-warning", "status-warning", "text-muted"]; - return (abs >= latency || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null; + return (abs >= latencyMillis || isDiscordKotlin) ? { delta: stringDelta, ahead, fill, isDiscordKotlin } : null; }, Tooltip() { diff --git a/src/plugins/noDefaultHangStatus/README.md b/src/plugins/noDefaultHangStatus/README.md new file mode 100644 index 00000000..e6bc9f83 --- /dev/null +++ b/src/plugins/noDefaultHangStatus/README.md @@ -0,0 +1,5 @@ +# NoDefaultHangStatus + +Disable the default hang status when joining voice channels + +![Visualization](https://github.com/Vendicated/Vencord/assets/24937357/329a9742-236f-48f7-94ff-c3510eca505a) diff --git a/src/plugins/noDefaultHangStatus/index.ts b/src/plugins/noDefaultHangStatus/index.ts new file mode 100644 index 00000000..3f77feb2 --- /dev/null +++ b/src/plugins/noDefaultHangStatus/index.ts @@ -0,0 +1,24 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { Devs } from "@utils/constants"; +import definePlugin from "@utils/types"; + +export default definePlugin({ + name: "NoDefaultHangStatus", + description: "Disable the default hang status when joining voice channels", + authors: [Devs.D3SOX], + + patches: [ + { + find: "HangStatusTypes.CHILLING)", + replacement: { + match: /{enableHangStatus:(\i),/, + replace: "{_enableHangStatus:$1=false," + } + } + ] +}); diff --git a/src/plugins/partyMode/index.ts b/src/plugins/partyMode/index.ts index 06e87195..56c19c02 100644 --- a/src/plugins/partyMode/index.ts +++ b/src/plugins/partyMode/index.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { definePluginSettings } from "@api/Settings"; +import { definePluginSettings, migratePluginSettings } from "@api/Settings"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { FluxDispatcher } from "@webpack/common"; @@ -41,8 +41,9 @@ const settings = definePluginSettings({ }, }); +migratePluginSettings("PartyMode", "Party mode 🎉"); export default definePlugin({ - name: "Party mode 🎉", + name: "PartyMode", description: "Allows you to use party mode cause the party never ends ✨", authors: [Devs.UwUDev], settings, diff --git a/src/plugins/readAllNotificationsButton/index.tsx b/src/plugins/readAllNotificationsButton/index.tsx index c97e765a..ff361e8f 100644 --- a/src/plugins/readAllNotificationsButton/index.tsx +++ b/src/plugins/readAllNotificationsButton/index.tsx @@ -22,14 +22,34 @@ import { addServerListElement, removeServerListElement, ServerListRenderPosition import ErrorBoundary from "@components/ErrorBoundary"; import { Devs, EquicordDevs } from "@utils/constants"; import definePlugin from "@utils/types"; +import { findStoreLazy } from "@webpack"; import { Button, FluxDispatcher, GuildChannelStore, GuildStore, React, ReadStateStore } from "@webpack/common"; +import { Channel } from "discord-types/general"; + +interface ThreadJoined { + channel: Channel; + joinTimestamp: number; +} + +type ThreadsJoined = Record; +type ThreadsJoinedByParent = Record; + +interface ActiveJoinedThreadsStore { + getActiveJoinedThreadsForGuild(guildId: string): ThreadsJoinedByParent; +} + +const ActiveJoinedThreadsStore: ActiveJoinedThreadsStore = findStoreLazy("ActiveJoinedThreadsStore"); function onClick() { const channels: Array = []; Object.values(GuildStore.getGuilds()).forEach(guild => { - GuildChannelStore.getChannels(guild.id).SELECTABLE - .concat(GuildChannelStore.getChannels(guild.id).VOCAL) + GuildChannelStore.getChannels(guild.id).SELECTABLE // Array<{ channel, comparator }> + .concat(GuildChannelStore.getChannels(guild.id).VOCAL) // Array<{ channel, comparator }> + .concat( + Object.values(ActiveJoinedThreadsStore.getActiveJoinedThreadsForGuild(guild.id)) + .flatMap(threadChannels => Object.values(threadChannels)) + ) .forEach((c: { channel: { id: string; }; }) => { if (!ReadStateStore.hasUnread(c.channel.id)) return; diff --git a/src/plugins/replaceGoogleSearch/README.md b/src/plugins/replaceGoogleSearch/README.md new file mode 100644 index 00000000..1ab30212 --- /dev/null +++ b/src/plugins/replaceGoogleSearch/README.md @@ -0,0 +1,5 @@ +# ReplaceGoogleSearch + +Replaces the Google search with different Engines + +![Visualization](https://github.com/Vendicated/Vencord/assets/61953774/8b8158d2-0407-4d7b-9dff-a8b9bdc1a122) diff --git a/src/plugins/replaceGoogleSearch/index.tsx b/src/plugins/replaceGoogleSearch/index.tsx new file mode 100644 index 00000000..1b1a761f --- /dev/null +++ b/src/plugins/replaceGoogleSearch/index.tsx @@ -0,0 +1,107 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2024 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { findGroupChildrenByChildId, NavContextMenuPatchCallback } from "@api/ContextMenu"; +import { definePluginSettings } from "@api/Settings"; +import { Devs } from "@utils/constants"; +import definePlugin, { OptionType } from "@utils/types"; +import { Flex, Menu } from "@webpack/common"; + +const DefaultEngines = { + Google: "https://www.google.com/search?q=", + DuckDuckGo: "https://duckduckgo.com/", + Bing: "https://www.bing.com/search?q=", + Yahoo: "https://search.yahoo.com/search?p=", + Github: "https://github.com/search?q=", + Kagi: "https://kagi.com/search?q=", + Yandex: "https://yandex.com/search/?text=", + AOL: "https://search.aol.com/aol/search?q=", + Baidu: "https://www.baidu.com/s?wd=", + Wikipedia: "https://wikipedia.org/w/index.php?search=", +} as const; + +const settings = definePluginSettings({ + customEngineName: { + description: "Name of the custom search engine", + type: OptionType.STRING, + placeholder: "Google" + }, + customEngineURL: { + description: "The URL of your Engine", + type: OptionType.STRING, + placeholder: "https://google.com/search?q=" + } +}); + +function search(src: string, engine: string) { + open(engine + encodeURIComponent(src), "_blank"); +} + +function makeSearchItem(src: string) { + let Engines = {}; + + if (settings.store.customEngineName && settings.store.customEngineURL) { + Engines[settings.store.customEngineName] = settings.store.customEngineURL; + } + + Engines = { ...Engines, ...DefaultEngines }; + + return ( + + {Object.keys(Engines).map((engine, i) => { + const key = "vc-search-content-" + engine; + return ( + + + {engine} + + } + action={() => search(src, Engines[engine])} + /> + ); + })} + + ); +} + +const messageContextMenuPatch: NavContextMenuPatchCallback = (children, _props) => { + const selection = document.getSelection()?.toString(); + if (!selection) return; + + const group = findGroupChildrenByChildId("search-google", children); + if (group) { + const idx = group.findIndex(c => c?.props?.id === "search-google"); + if (idx !== -1) group[idx] = makeSearchItem(selection); + } +}; + +export default definePlugin({ + name: "ReplaceGoogleSearch", + description: "Replaces the Google search with different Engines", + authors: [Devs.Moxxie, Devs.Ethan], + + settings, + + contextMenus: { + "message": messageContextMenuPatch + } +}); diff --git a/src/plugins/showTimeoutDuration/index.tsx b/src/plugins/showTimeoutDuration/index.tsx index f57ee0fc..bfe80680 100644 --- a/src/plugins/showTimeoutDuration/index.tsx +++ b/src/plugins/showTimeoutDuration/index.tsx @@ -9,11 +9,11 @@ import "./styles.css"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; import { Devs } from "@utils/constants"; -import { Margins } from "@utils/margins"; import definePlugin, { OptionType } from "@utils/types"; import { findComponentLazy } from "@webpack"; -import { ChannelStore, Forms, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common"; +import { ChannelStore, GuildMemberStore, i18n, Text, Tooltip } from "@webpack/common"; import { Message } from "discord-types/general"; +import { FunctionComponent, ReactNode } from "react"; const CountDown = findComponentLazy(m => m.prototype?.render?.toString().includes(".MAX_AGE_NEVER")); @@ -26,7 +26,6 @@ const settings = definePluginSettings({ displayStyle: { description: "How to display the timeout duration", type: OptionType.SELECT, - restartNeeded: true, options: [ { label: "In the Tooltip", value: DisplayStyle.Tooltip }, { label: "Next to the timeout icon", value: DisplayStyle.Inline, default: true }, @@ -60,7 +59,7 @@ function renderTimeout(message: Message, inline: boolean) { export default definePlugin({ name: "ShowTimeoutDuration", description: "Shows how much longer a user's timeout will last, either in the timeout icon tooltip or next to it", - authors: [Devs.Ven], + authors: [Devs.Ven, Devs.Sqaaakoi], settings, @@ -70,33 +69,20 @@ export default definePlugin({ replacement: [ { match: /(\i)\.Tooltip,{(text:.{0,30}\.Messages\.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY)/, - get replace() { - if (settings.store.displayStyle === DisplayStyle.Inline) - return "$self.TooltipWrapper,{vcProps:arguments[0],$2"; - - return "$1.Tooltip,{text:$self.renderTimeoutDuration(arguments[0])"; - } + replace: "$self.TooltipWrapper,{message:arguments[0].message,$2" } ] } ], - renderTimeoutDuration: ErrorBoundary.wrap(({ message }: { message: Message; }) => { - return ( - <> - {i18n.Messages.GUILD_COMMUNICATION_DISABLED_ICON_TOOLTIP_BODY} - - {renderTimeout(message, false)} - - - ); - }, { noop: true }), - - TooltipWrapper: ErrorBoundary.wrap(({ vcProps: { message }, ...tooltipProps }: { vcProps: { message: Message; }; }) => { + TooltipWrapper: ErrorBoundary.wrap(({ message, children, text }: { message: Message; children: FunctionComponent; text: ReactNode; }) => { + if (settings.store.displayStyle === DisplayStyle.Tooltip) return ; return (
- - + {renderTimeout(message, true)} timeout remaining diff --git a/src/plugins/showTimeoutDuration/styles.css b/src/plugins/showTimeoutDuration/styles.css index 70a826e1..a6f830c3 100644 --- a/src/plugins/showTimeoutDuration/styles.css +++ b/src/plugins/showTimeoutDuration/styles.css @@ -2,3 +2,7 @@ display: flex; align-items: center; } + +.vc-std-wrapper [class*="communicationDisabled"] { + margin-right: 0; +} diff --git a/src/plugins/viewIcons/index.tsx b/src/plugins/viewIcons/index.tsx index f71777ad..104252f6 100644 --- a/src/plugins/viewIcons/index.tsx +++ b/src/plugins/viewIcons/index.tsx @@ -36,6 +36,10 @@ interface GuildContextProps { guild?: Guild; } +interface GroupDMContextProps { + channel: Channel; +} + const settings = definePluginSettings({ format: { type: OptionType.SELECT, @@ -145,10 +149,27 @@ const GuildContext: NavContextMenuPatchCallback = (children, { guild }: GuildCon )); }; +const GroupDMContext: NavContextMenuPatchCallback = (children, { channel }: GroupDMContextProps) => { + if (!channel) return; + + children.splice(-1, 0, ( + + + openImage(IconUtils.getChannelIconURL(channel)!) + } + icon={ImageIcon} + /> + + )); +}; + export default definePlugin({ name: "ViewIcons", - authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz], - description: "Makes avatars and banners in user profiles clickable, and adds View Icon/Banner entries in the user and server context menu", + authors: [Devs.Ven, Devs.TheKodeToad, Devs.Nuckyz, Devs.nyx], + description: "Makes avatars and banners in user profiles clickable, adds View Icon/Banner entries in the user, server and group channel context menu.", tags: ["ImageUtilities"], settings, @@ -157,11 +178,12 @@ export default definePlugin({ contextMenus: { "user-context": UserContext, - "guild-context": GuildContext + "guild-context": GuildContext, + "gdm-context": GroupDMContext }, patches: [ - // Make pfps clickable + // Profiles Modal pfp { find: "User Profile Modal - Context Menu", replacement: { @@ -169,7 +191,7 @@ export default definePlugin({ replace: "{src:$1,onClick:()=>$self.openImage($1)" } }, - // Make banners clickable + // Banners { find: ".NITRO_BANNER,", replacement: { @@ -180,12 +202,38 @@ export default definePlugin({ 'onClick:ev=>$1&&ev.target.style.backgroundImage&&$self.openImage($2),style:{cursor:$1?"pointer":void 0,' } }, + // User DMs "User Profile" popup in the right { find: ".avatarPositionPanel", replacement: { match: /(?<=avatarWrapperNonUserBot.{0,50})onClick:(\i\|\|\i)\?void 0(?<=,avatarSrc:(\i).+?)/, replace: "style:($1)?{cursor:\"pointer\"}:{},onClick:$1?()=>{$self.openImage($2)}" } + }, + // Group DMs top small & large icon + { + find: ".recipients.length>=2", + all: true, + replacement: { + match: /null==\i\.icon\?.+?src:(\(0,\i\.getChannelIconURL\).+?\))(?=[,}])/, + replace: (m, iconUrl) => `${m},onClick:()=>$self.openImage(${iconUrl})` + } + }, + // User DMs top small icon + { + find: ".cursorPointer:null,children", + replacement: { + match: /.Avatar,.+?src:(.+?\))(?=[,}])/, + replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})` + } + }, + // User Dms top large icon + { + find: 'experimentLocation:"empty_messages"', + replacement: { + match: /.Avatar,.+?src:(.+?\))(?=[,}])/, + replace: (m, avatarUrl) => `${m},onClick:()=>$self.openImage(${avatarUrl})` + } } ] }); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 7f82f4d2..7a9fdb28 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -444,6 +444,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "newwares", id: 421405303951851520n }, + JohnyTheCarrot: { + name: "JohnyTheCarrot", + id: 132819036282159104n + }, puv: { name: "puv", id: 469441552251355137n @@ -492,6 +496,18 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "ScattrdBlade", id: 678007540608532491n }, + Moxxie: { + name: "Moxxie", + id: 712653921692155965n, + }, + Ethan: { + name: "Ethan", + id: 721717126523781240n, + }, + nyx: { + name: "verticalsync", + id: 328165170536775680n + }, } satisfies Record); export const EquicordDevs = Object.freeze({ diff --git a/src/webpack/common/types/stores.d.ts b/src/webpack/common/types/stores.d.ts index 27715b5e..059924f5 100644 --- a/src/webpack/common/types/stores.d.ts +++ b/src/webpack/common/types/stores.d.ts @@ -63,7 +63,7 @@ export interface CustomEmoji { originalName?: string; require_colons: boolean; roles: string[]; - url: string; + type: "GUILD_EMOJI"; } export interface UnicodeEmoji { @@ -75,6 +75,7 @@ export interface UnicodeEmoji { }; index: number; surrogates: string; + type: "UNICODE"; uniqueName: string; useSpriteSheet: boolean; get allNamesString(): string; diff --git a/src/webpack/webpack.ts b/src/webpack/webpack.ts index cccfa7c2..a9f3273b 100644 --- a/src/webpack/webpack.ts +++ b/src/webpack/webpack.ts @@ -16,7 +16,7 @@ * along with this program. If not, see . */ -import { proxyLazy } from "@utils/lazy"; +import { makeLazy, proxyLazy } from "@utils/lazy"; import { LazyComponent } from "@utils/lazyReact"; import { Logger } from "@utils/Logger"; import { canonicalizeMatch } from "@utils/patches"; @@ -462,7 +462,7 @@ export async function extractAndLoadChunks(code: string[], matcher: RegExp = Def export function extractAndLoadChunksLazy(code: string[], matcher: RegExp = DefaultExtractAndLoadChunksRegex) { if (IS_DEV) lazyWebpackSearchHistory.push(["extractAndLoadChunks", [code, matcher]]); - return () => extractAndLoadChunks(code, matcher); + return makeLazy(() => extractAndLoadChunks(code, matcher)); } /**