Merge branch 'main' into BypassDND

This commit is contained in:
Inbestigator 2024-11-20 21:29:42 -08:00 committed by GitHub
commit 7478a275b3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 327 additions and 180 deletions

View file

@ -1,7 +1,7 @@
{ {
"name": "vencord", "name": "vencord",
"private": "true", "private": "true",
"version": "1.10.6", "version": "1.10.7",
"description": "The cutest Discord client mod", "description": "The cutest Discord client mod",
"homepage": "https://github.com/Vendicated/Vencord#readme", "homepage": "https://github.com/Vendicated/Vencord#readme",
"bugs": { "bugs": {

View file

@ -225,7 +225,7 @@ page.on("console", async e => {
plugin, plugin,
type, type,
id, id,
match: regex.replace(/\[A-Za-z_\$\]\[\\w\$\]\*/g, "\\i"), match: regex.replace(/\(\?:\[A-Za-z_\$\]\[\\w\$\]\*\)/g, "\\i"),
error: await maybeGetError(e.args()[3]) error: await maybeGetError(e.args()[3])
}); });

View file

@ -247,7 +247,7 @@ function FullPatchInput({ setFind, setParsedFind, setMatch, setReplacement }: Fu
} }
try { try {
const parsed = (0, eval)(`(${fullPatch})`) as Patch; const parsed = (0, eval)(`([${fullPatch}][0])`) as Patch;
if (!parsed.find) throw new Error("No 'find' field"); if (!parsed.find) throw new Error("No 'find' field");
if (!parsed.replacement) throw new Error("No 'replacement' field"); if (!parsed.replacement) throw new Error("No 'replacement' field");

View file

@ -27,7 +27,12 @@ export async function loadLazyChunks() {
const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g); const LazyChunkRegex = canonicalizeMatch(/(?:(?:Promise\.all\(\[)?(\i\.e\("?[^)]+?"?\)[^\]]*?)(?:\]\))?)\.then\(\i\.bind\(\i,"?([^)]+?)"?\)\)/g);
let foundCssDebuggingLoad = false;
async function searchAndLoadLazyChunks(factoryCode: string) { async function searchAndLoadLazyChunks(factoryCode: string) {
// Workaround to avoid loading the CSS debugging chunk which turns the app pink
const hasCssDebuggingLoad = foundCssDebuggingLoad ? false : (foundCssDebuggingLoad = factoryCode.includes(".cssDebuggingEnabled&&"));
const lazyChunks = factoryCode.matchAll(LazyChunkRegex); const lazyChunks = factoryCode.matchAll(LazyChunkRegex);
const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>(); const validChunkGroups = new Set<[chunkIds: number[], entryPoint: number]>();
@ -43,6 +48,16 @@ export async function loadLazyChunks() {
let invalidChunkGroup = false; let invalidChunkGroup = false;
for (const id of chunkIds) { for (const id of chunkIds) {
if (hasCssDebuggingLoad) {
if (chunkIds.length > 1) {
throw new Error("Found multiple chunks in factory that loads the CSS debugging chunk");
}
invalidChunks.add(id);
invalidChunkGroup = true;
break;
}
if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue; if (wreq.u(id) == null || wreq.u(id) === "undefined.js") continue;
const isWorkerAsset = await fetch(wreq.p + wreq.u(id)) const isWorkerAsset = await fetch(wreq.p + wreq.u(id))

View file

@ -59,15 +59,7 @@ export default definePlugin({
replace: "$&return;" replace: "$&return;"
} }
] ]
},
{
find: ".installedLogHooks)",
replacement: {
// if getDebugLogging() returns false, the hooks don't get installed.
match: "getDebugLogging(){",
replace: "getDebugLogging(){return false;"
} }
},
], ],
startAt: StartAt.Init, startAt: StartAt.Init,

View file

@ -65,7 +65,7 @@ export default definePlugin({
replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}` replace: (_, sectionTypes, commaOrSemi, elements, element) => `${commaOrSemi} $self.addSettings(${elements}, ${element}, ${sectionTypes}) ${commaOrSemi}`
}, },
{ {
match: /({(?=.+?function (\i).{0,120}(\i)=\i\.useMemo.{0,60}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/, match: /({(?=.+?function (\i).{0,160}(\i)=\i\.useMemo.{0,140}return \i\.useMemo\(\(\)=>\i\(\3).+?function\(\){return )\2(?=})/,
replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})` replace: (_, rest, settingsHook) => `${rest}$self.wrapSettingsHook(${settingsHook})`
} }
] ]

View file

@ -28,10 +28,18 @@ export default definePlugin({
patches: [ patches: [
{ {
find: 'action:"EXPAND_ROLES"', find: 'action:"EXPAND_ROLES"',
replacement: { replacement: [
{
match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/, match: /(roles:\i(?=.+?(\i)\(!0\)[,;]\i\({action:"EXPAND_ROLES"}\)).+?\[\i,\2\]=\i\.useState\()!1\)/,
replace: (_, rest, setExpandedRoles) => `${rest}!0)` replace: (_, rest, setExpandedRoles) => `${rest}!0)`
},
{
// Fix not calculating non-expanded roles because the above patch makes the default "expanded",
// which makes the collapse button never show up and calculation never occur
match: /(?<=useLayoutEffect\(\(\)=>{if\()\i/,
replace: isExpanded => "false"
} }
]
} }
] ]
}); });

View file

@ -275,16 +275,16 @@ export default definePlugin({
}, },
makeGuildsBarGuildListFilter(isBetterFolders: boolean) { makeGuildsBarGuildListFilter(isBetterFolders: boolean) {
try {
return child => { return child => {
if (isBetterFolders) { if (isBetterFolders) {
try {
return child?.props?.["aria-label"] === getIntlMessage("SERVERS"); return child?.props?.["aria-label"] === getIntlMessage("SERVERS");
} catch (e) {
console.error(e);
}
} }
return true; return true;
}; };
} catch {
return true;
}
}, },
makeGuildsBarTreeFilter(isBetterFolders: boolean) { makeGuildsBarTreeFilter(isBetterFolders: boolean) {

View file

@ -130,6 +130,27 @@ export default definePlugin({
replace: "" replace: ""
} }
}, },
// Zustand section
{
find: "[DEPRECATED] Passing a vanilla store will be unsupported in a future version. Instead use `import { useStore } from 'zustand'`.",
replacement: [
{
match: /&&console\.warn\("\[DEPRECATED\] Passing a vanilla store will be unsupported in a future version\. Instead use `import { useStore } from 'zustand'`\."\)/,
replace: ""
},
{
match: /console\.warn\("\[DEPRECATED\] Use `createWithEqualityFn` instead of `create` or use `useStoreWithEqualityFn` instead of `useStore`\. They can be imported from 'zustand\/traditional'\. https:\/\/github\.com\/pmndrs\/zustand\/discussions\/1937"\),/,
replace: ""
}
]
},
{
find: "[DEPRECATED] `getStorage`, `serialize` and `deserialize` options are deprecated. Use `storage` option instead.",
replacement: {
match: /console\.warn\("\[DEPRECATED\] `getStorage`, `serialize` and `deserialize` options are deprecated\. Use `storage` option instead\."\),/,
replace: ""
}
},
// Patches discords generic logger function // Patches discords generic logger function
{ {
find: "Σ:", find: "Σ:",
@ -147,5 +168,5 @@ export default definePlugin({
replace: "$self.NoopLogger()" replace: "$self.NoopLogger()"
} }
} }
], ]
}); });

View file

@ -93,7 +93,7 @@ export const useAuthorizationStore = proxyLazy(() => zustandCreate(
} as AuthorizationState), } as AuthorizationState),
{ {
name: "decor-auth", name: "decor-auth",
getStorage: () => indexedDBStorage, storage: indexedDBStorage,
partialize: state => ({ tokens: state.tokens }), partialize: state => ({ tokens: state.tokens }),
onRehydrateStorage: () => state => state?.init() onRehydrateStorage: () => state => state?.init()
} }

View file

@ -95,10 +95,13 @@ export const useUsersDecorationsStore = proxyLazy(() => zustandCreate((set: any,
} as UsersDecorationsState))); } as UsersDecorationsState)));
export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined { export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | null | undefined {
try {
const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null); const [decorAvatarDecoration, setDecorAvatarDecoration] = useState<string | null>(user ? useUsersDecorationsStore.getState().getAsset(user.id) ?? null : null);
useEffect(() => { useEffect(() => {
const destructor = useUsersDecorationsStore.subscribe( const destructor = (() => {
try {
return useUsersDecorationsStore.subscribe(
state => { state => {
if (!user) return; if (!user) return;
const newDecorAvatarDecoration = state.getAsset(user.id); const newDecorAvatarDecoration = state.getAsset(user.id);
@ -106,13 +109,25 @@ export function useUserDecorAvatarDecoration(user?: User): AvatarDecoration | nu
if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration); if (decorAvatarDecoration !== newDecorAvatarDecoration) setDecorAvatarDecoration(newDecorAvatarDecoration);
} }
); );
} catch {
return () => { };
}
})();
try {
if (user) { if (user) {
const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState(); const { fetch: fetchUserDecorAvatarDecoration } = useUsersDecorationsStore.getState();
fetchUserDecorAvatarDecoration(user.id); fetchUserDecorAvatarDecoration(user.id);
} }
} catch { }
return destructor; return destructor;
}, []); }, []);
return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null; return decorAvatarDecoration ? { asset: decorAvatarDecoration, skuId: SKU_ID } : null;
} catch (e) {
console.error(e);
}
return null;
} }

View file

@ -10,6 +10,7 @@ import { openInviteModal } from "@utils/discord";
import { Margins } from "@utils/margins"; import { Margins } from "@utils/margins";
import { classes, copyWithToast } from "@utils/misc"; import { classes, copyWithToast } from "@utils/misc";
import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal"; import { closeAllModals, ModalCloseButton, ModalContent, ModalFooter, ModalHeader, ModalProps, ModalRoot, ModalSize, openModal } from "@utils/modal";
import { Queue } from "@utils/Queue";
import { findComponentByCodeLazy } from "@webpack"; import { findComponentByCodeLazy } from "@webpack";
import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common"; import { Alerts, Button, FluxDispatcher, Forms, GuildStore, NavigationRouter, Parser, Text, Tooltip, useEffect, UserStore, UserUtils, useState } from "@webpack/common";
import { User } from "discord-types/general"; import { User } from "discord-types/general";
@ -49,6 +50,8 @@ interface SectionHeaderProps {
section: Section; section: Section;
} }
const fetchAuthorsQueue = new Queue();
function SectionHeader({ section }: SectionHeaderProps) { function SectionHeader({ section }: SectionHeaderProps) {
const hasSubtitle = typeof section.subtitle !== "undefined"; const hasSubtitle = typeof section.subtitle !== "undefined";
const hasAuthorIds = typeof section.authorIds !== "undefined"; const hasAuthorIds = typeof section.authorIds !== "undefined";
@ -56,17 +59,18 @@ function SectionHeader({ section }: SectionHeaderProps) {
const [authors, setAuthors] = useState<User[]>([]); const [authors, setAuthors] = useState<User[]>([]);
useEffect(() => { useEffect(() => {
(async () => { fetchAuthorsQueue.push(async () => {
if (!section.authorIds) return; if (!section.authorIds) return;
for (const authorId of section.authorIds) { for (const authorId of section.authorIds) {
const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId); const author = UserStore.getUser(authorId) ?? await UserUtils.getUser(authorId).catch(() => null);
if (author == null) continue;
setAuthors(authors => [...authors, author]); setAuthors(authors => [...authors, author]);
} }
})(); });
}, [section.authorIds]); }, [section.authorIds]);
return <div> return <div>
<Flex> <Flex>
<Forms.FormTitle style={{ flexGrow: 1 }}>{section.title}</Forms.FormTitle> <Forms.FormTitle style={{ flexGrow: 1 }}>{section.title}</Forms.FormTitle>

View file

@ -20,7 +20,7 @@ import { AvatarDecorationModalPreview } from "../components";
const FileUpload = findComponentByCodeLazy("fileUploadInput,"); const FileUpload = findComponentByCodeLazy("fileUploadInput,");
const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', { const { HelpMessage, HelpMessageTypes } = mapMangledModuleLazy('POSITIVE=3]="POSITIVE', {
HelpMessageTypes: filters.byProps("POSITIVE", "WARNING"), HelpMessageTypes: filters.byProps("POSITIVE", "WARNING", "INFO"),
HelpMessage: filters.byCode(".iconDiv") HelpMessage: filters.byCode(".iconDiv")
}); });
@ -119,8 +119,8 @@ function CreateDecorationModal(props: ModalProps) {
/> />
</div> </div>
</div> </div>
<Forms.FormText type="description" className={Margins.bottom16}> <HelpMessage messageType={HelpMessageTypes.INFO} className={Margins.bottom8}>
<br />You can receive updates on your decoration's review by joining <Link To receive updates on your decoration's review, join <Link
href={`https://discord.gg/${INVITE_KEY}`} href={`https://discord.gg/${INVITE_KEY}`}
onClick={async e => { onClick={async e => {
e.preventDefault(); e.preventDefault();
@ -138,8 +138,8 @@ function CreateDecorationModal(props: ModalProps) {
}} }}
> >
Decor's Discord server Decor's Discord server
</Link>. </Link> and allow direct messages.
</Forms.FormText> </HelpMessage>
</ErrorBoundary> </ErrorBoundary>
</ModalContent> </ModalContent>
<ModalFooter className={cl("modal-footer")}> <ModalFooter className={cl("modal-footer")}>

View file

@ -20,11 +20,11 @@ import { addPreEditListener, addPreSendListener, removePreEditListener, removePr
import { definePluginSettings } from "@api/Settings"; import { definePluginSettings } from "@api/Settings";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies"; import { ApngBlendOp, ApngDisposeOp, importApngJs } from "@utils/dependencies";
import { getCurrentGuild } from "@utils/discord"; import { getCurrentGuild, getEmojiURL } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import definePlugin, { OptionType, Patch } from "@utils/types"; import definePlugin, { OptionType, Patch } from "@utils/types";
import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack"; import { findByCodeLazy, findByPropsLazy, findStoreLazy, proxyLazyWebpack } from "@webpack";
import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, IconUtils, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common"; import { Alerts, ChannelStore, DraftType, EmojiStore, FluxDispatcher, Forms, GuildMemberStore, lodash, Parser, PermissionsBits, PermissionStore, UploadHandler, UserSettingsActionCreators, UserStore } from "@webpack/common";
import type { Emoji } from "@webpack/types"; import type { Emoji } from "@webpack/types";
import type { Message } from "discord-types/general"; import type { Message } from "discord-types/general";
import { applyPalette, GIFEncoder, quantize } from "gifenc"; import { applyPalette, GIFEncoder, quantize } from "gifenc";
@ -920,7 +920,7 @@ export default definePlugin({
const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`; const emojiString = `<${emoji.animated ? "a" : ""}:${emoji.originalName || emoji.name}:${emoji.id}>`;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize })); const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name); url.searchParams.set("name", emoji.name);
@ -953,7 +953,7 @@ export default definePlugin({
hasBypass = true; hasBypass = true;
const url = new URL(IconUtils.getEmojiURL({ id: emoji.id, animated: emoji.animated, size: s.emojiSize })); const url = new URL(getEmojiURL(emoji.id, emoji.animated, s.emojiSize));
url.searchParams.set("size", s.emojiSize.toString()); url.searchParams.set("size", s.emojiSize.toString());
url.searchParams.set("name", emoji.name); url.searchParams.set("name", emoji.name);

View file

@ -28,7 +28,7 @@ import { getIntlMessage } from "@utils/discord";
import { Logger } from "@utils/Logger"; import { Logger } from "@utils/Logger";
import { classes } from "@utils/misc"; import { classes } from "@utils/misc";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { findByPropsLazy } from "@webpack";
import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common"; import { ChannelStore, FluxDispatcher, Menu, MessageStore, Parser, SelectedChannelStore, Timestamp, UserStore, useStateFromStores } from "@webpack/common";
import { Message } from "discord-types/general"; import { Message } from "discord-types/general";
@ -43,7 +43,6 @@ interface MLMessage extends Message {
} }
const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage"); const styles = findByPropsLazy("edited", "communicationDisabled", "isSystemMessage");
const getMessage = findByCodeLazy('replace(/^\\n+|\\n+$/g,"")');
function addDeleteStyle() { function addDeleteStyle() {
if (Settings.plugins.MessageLogger.deleteStyle === "text") { if (Settings.plugins.MessageLogger.deleteStyle === "text") {
@ -312,9 +311,8 @@ export default definePlugin({
); );
}, },
Messages: {
// DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}") // DELETED_MESSAGE_COUNT: getMessage("{count, plural, =0 {No deleted messages} one {{count} deleted message} other {{count} deleted messages}}")
// TODO: find a better way to generate intl messages // TODO: Find a better way to generate intl messages
DELETED_MESSAGE_COUNT: () => ({ DELETED_MESSAGE_COUNT: () => ({
ast: [[ ast: [[
6, 6,
@ -339,8 +337,7 @@ export default definePlugin({
0, 0,
"cardinal" "cardinal"
]] ]]
}) }),
},
patches: [ patches: [
{ {
@ -531,7 +528,7 @@ export default definePlugin({
}, },
{ {
match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\?.*?:/, match: /(\i).type===\i\.\i\.MESSAGE_GROUP_BLOCKED\?.*?:/,
replace: '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.Messages.DELETED_MESSAGE_COUNT:', replace: '$&$1.type==="MESSAGE_GROUP_DELETED"?$self.DELETED_MESSAGE_COUNT:',
}, },
], ],
predicate: () => Settings.plugins.MessageLogger.collapseDeleted predicate: () => Settings.plugins.MessageLogger.collapseDeleted

View file

@ -28,8 +28,8 @@ import { Message } from "discord-types/general";
const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked"); const RelationshipStore = findByPropsLazy("getRelationships", "isBlocked");
interface MessageDeleteProps { interface MessageDeleteProps {
// i18n message i18n.t["+FcYMz"] if deleted, with args // Internal intl message for BLOCKED_MESSAGE_COUNT
collapsedReason: () => any collapsedReason: () => any;
} }
export default definePlugin({ export default definePlugin({

View file

@ -20,7 +20,7 @@ const settings = definePluginSettings({
export default definePlugin({ export default definePlugin({
name: "NoMosaic", name: "NoMosaic",
authors: [Devs.AutumnVN], authors: [Devs.AutumnVN],
description: "Removes Discord new image mosaic", description: "Removes Discord image mosaic",
tags: ["image", "mosaic", "media"], tags: ["image", "mosaic", "media"],
settings, settings,
@ -29,8 +29,8 @@ export default definePlugin({
{ {
find: '=>"IMAGE"===', find: '=>"IMAGE"===',
replacement: { replacement: {
match: /=>"IMAGE"===\i\|\|"VIDEO"===\i;/, match: /=>"IMAGE"===\i\|\|"VIDEO"===\i(?:\|\|("VISUAL_PLACEHOLDER"===\i))?;/,
replace: "=>false;" replace: (_, visualPlaceholderPred) => visualPlaceholderPred != null ? `=>${visualPlaceholderPred};` : "=>false;"
} }
}, },
{ {

View file

@ -87,7 +87,7 @@ export default definePlugin({
{ {
find: "trackAnnouncementMessageLinkClicked({", find: "trackAnnouncementMessageLinkClicked({",
replacement: { replacement: {
match: /function (\i\(\i,\i\)\{)(?=.{0,100}trusted:)/, match: /function (\i\(\i,\i\)\{)(?=.{0,150}trusted:)/,
replace: "async function $1 if(await $self.handleLink(...arguments)) return;" replace: "async function $1 if(await $self.handleLink(...arguments)) return;"
} }
}, },

View file

@ -30,10 +30,10 @@ export default definePlugin({
{ {
find: ".removeMosaicItemHoverButton),", find: ".removeMosaicItemHoverButton),",
replacement: { replacement: {
match: /\.nonMediaMosaicItem\]:!(\i).{0,50}?children:\[\S,(\S)/, match: /\.nonMediaMosaicItem\]:.{0,40}children:\[(?<=showDownload:(\i).+?isVisualMediaType:(\i).+?)/,
replace: "$&,$1&&$2&&$self.renderPiPButton()," replace: "$&$1&&$2&&$self.renderPiPButton(),"
}, }
}, }
], ],
renderPiPButton: ErrorBoundary.wrap(() => { renderPiPButton: ErrorBoundary.wrap(() => {

View file

@ -50,6 +50,8 @@ async function runMigrations() {
export async function syncAndRunChecks() { export async function syncAndRunChecks() {
await runMigrations(); await runMigrations();
if (UserStore.getCurrentUser() == null) return;
const [oldGuilds, oldGroups, oldFriends] = await DataStore.getMany([ const [oldGuilds, oldGroups, oldFriends] = await DataStore.getMany([
guildsKey(), guildsKey(),
groupsKey(), groupsKey(),

View file

@ -20,6 +20,7 @@ import { definePluginSettings } from "@api/Settings";
import ErrorBoundary from "@components/ErrorBoundary"; import ErrorBoundary from "@components/ErrorBoundary";
import { makeRange } from "@components/PluginSettings/components"; import { makeRange } from "@components/PluginSettings/components";
import { Devs } from "@utils/constants"; import { Devs } from "@utils/constants";
import { Logger } from "@utils/Logger";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findByCodeLazy } from "@webpack"; import { findByCodeLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common"; import { ChannelStore, GuildMemberStore, GuildStore } from "@webpack/common";
@ -51,6 +52,12 @@ const settings = definePluginSettings({
description: "Show role colors in the reactors list", description: "Show role colors in the reactors list",
restartNeeded: true restartNeeded: true
}, },
pollResults: {
type: OptionType.BOOLEAN,
default: true,
description: "Show role colors in the poll results",
restartNeeded: true
},
colorChatMessages: { colorChatMessages: {
type: OptionType.BOOLEAN, type: OptionType.BOOLEAN,
default: false, default: false,
@ -62,14 +69,15 @@ const settings = definePluginSettings({
description: "Intensity of message coloring.", description: "Intensity of message coloring.",
markers: makeRange(0, 100, 10), markers: makeRange(0, 100, 10),
default: 30 default: 30
}, }
}); });
export default definePlugin({ export default definePlugin({
name: "RoleColorEverywhere", name: "RoleColorEverywhere",
authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN, Devs.Kyuuhachi], authors: [Devs.KingFish, Devs.lewisakura, Devs.AutumnVN, Devs.Kyuuhachi, Devs.jamesbt365],
description: "Adds the top role color anywhere possible", description: "Adds the top role color anywhere possible",
settings,
patches: [ patches: [
// Chat Mentions // Chat Mentions
{ {
@ -77,82 +85,133 @@ export default definePlugin({
replacement: [ replacement: [
{ {
match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/, match: /onContextMenu:\i,color:\i,\.\.\.\i(?=,children:)(?<=user:(\i),channel:(\i).{0,500}?)/,
replace: "$&,color:$self.getUserColor($1?.id,{channelId:$2?.id})" replace: "$&,color:$self.getColorInt($1?.id,$2?.id)"
} }
], ],
predicate: () => settings.store.chatMentions, predicate: () => settings.store.chatMentions
}, },
// Slate // Slate
{ {
find: ".userTooltip,children", find: ".userTooltip,children",
replacement: [ replacement: [
{ {
match: /let\{id:(\i),guildId:(\i)[^}]*\}.*?\.\i,{(?=children)/, match: /let\{id:(\i),guildId:\i,channelId:(\i)[^}]*\}.*?\.\i,{(?=children)/,
replace: "$&color:$self.getUserColor($1,{guildId:$2})," replace: "$&color:$self.getColorInt($1,$2),"
} }
], ],
predicate: () => settings.store.chatMentions, predicate: () => settings.store.chatMentions
}, },
// Member List Role Headers
{ {
find: 'tutorialId:"whos-online', find: 'tutorialId:"whos-online',
replacement: [ replacement: [
{ {
match: /null,\i," — ",\i\]/, match: /null,\i," — ",\i\]/,
replace: "null,$self.roleGroupColor(arguments[0])]" replace: "null,$self.RoleGroupColor(arguments[0])]"
}, },
], ],
predicate: () => settings.store.memberList, predicate: () => settings.store.memberList
}, },
{ {
find: "#{intl::THREAD_BROWSER_PRIVATE}", find: "#{intl::THREAD_BROWSER_PRIVATE}",
replacement: [ replacement: [
{ {
match: /children:\[\i," — ",\i\]/, match: /children:\[\i," — ",\i\]/,
replace: "children:[$self.roleGroupColor(arguments[0])]" replace: "children:[$self.RoleGroupColor(arguments[0])]"
}, },
], ],
predicate: () => settings.store.memberList, predicate: () => settings.store.memberList
}, },
// Voice Users
{ {
find: "renderPrioritySpeaker", find: "renderPrioritySpeaker(){",
replacement: [ replacement: [
{ {
match: /renderName\(\){.+?usernameSpeaking\]:.+?(?=children)/, match: /renderName\(\){.+?usernameSpeaking\]:.+?(?=children)/,
replace: "$&...$self.getVoiceProps(this.props)," replace: "$&style:$self.getColorStyle(this?.props?.user?.id,this?.props?.guildId),"
} }
], ],
predicate: () => settings.store.voiceUsers, predicate: () => settings.store.voiceUsers
}, },
// Reaction List
{ {
find: ".reactorDefault", find: ".reactorDefault",
replacement: { replacement: {
match: /,onContextMenu:e=>.{0,15}\((\i),(\i),(\i)\).{0,250}tag:"strong"/, match: /,onContextMenu:\i=>.{0,15}\((\i),(\i),(\i)\).{0,250}tag:"strong"/,
replace: "$&,style:{color:$self.getColor($2?.id,$1)}" replace: "$&,style:$self.getColorStyle($2?.id,$1?.channel?.id)"
}, },
predicate: () => settings.store.reactorsList, predicate: () => settings.store.reactorsList,
}, },
// Poll Results
{
find: ",reactionVoteCounts",
replacement: {
match: /\.nickname,(?=children:)/,
replace: "$&style:$self.getColorStyle(arguments[0]?.user?.id,arguments[0]?.channel?.id),"
},
predicate: () => settings.store.pollResults
},
// Messages
{ {
find: "#{intl::MESSAGE_EDITED}", find: "#{intl::MESSAGE_EDITED}",
replacement: { replacement: {
match: /(?<=isUnsupported\]:(\i)\.isUnsupported\}\),)(?=children:\[)/, match: /(?<=isUnsupported\]:(\i)\.isUnsupported\}\),)(?=children:\[)/,
replace: "style:{color:$self.useMessageColor($1)}," replace: "style:$self.useMessageColorStyle($1),"
},
predicate: () => settings.store.colorChatMessages,
}, },
predicate: () => settings.store.colorChatMessages
}
], ],
settings,
getColor(userId: string, { channelId, guildId }: { channelId?: string; guildId?: string; }) { getColorString(userId: string, channelOrGuildId: string) {
if (!(guildId ??= ChannelStore.getChannel(channelId!)?.guild_id)) return null; try {
const guildId = ChannelStore.getChannel(channelOrGuildId)?.guild_id ?? GuildStore.getGuild(channelOrGuildId)?.id;
if (guildId == null) return null;
return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null; return GuildMemberStore.getMember(guildId, userId)?.colorString ?? null;
} catch (e) {
new Logger("RoleColorEverywhere").error("Failed to get color string", e);
}
return null;
}, },
getUserColor(userId: string, ids: { channelId?: string; guildId?: string; }) { getColorInt(userId: string, channelOrGuildId: string) {
const colorString = this.getColor(userId, ids); const colorString = this.getColorString(userId, channelOrGuildId);
return colorString && parseInt(colorString.slice(1), 16); return colorString && parseInt(colorString.slice(1), 16);
}, },
roleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => { getColorStyle(userId: string, channelOrGuildId: string) {
const colorString = this.getColorString(userId, channelOrGuildId);
return colorString && {
color: colorString
};
},
useMessageColor(message: any) {
try {
const { messageSaturation } = settings.use(["messageSaturation"]);
const author = useMessageAuthor(message);
if (author.colorString != null && messageSaturation !== 0) {
return `color-mix(in oklab, ${author.colorString} ${messageSaturation}%, var(--text-normal))`;
}
} catch (e) {
new Logger("RoleColorEverywhere").error("Failed to get message color", e);
}
return null;
},
useMessageColorStyle(message: any) {
const color = this.useMessageColor(message);
return color && {
color
};
},
RoleGroupColor: ErrorBoundary.wrap(({ id, count, title, guildId, label }: { id: string; count: number; title: string; guildId: string; label: string; }) => {
const role = GuildStore.getRole(guildId, id); const role = GuildStore.getRole(guildId, id);
return ( return (
@ -164,25 +223,5 @@ export default definePlugin({
{title ?? label} &mdash; {count} {title ?? label} &mdash; {count}
</span> </span>
); );
}, { noop: true }), }, { noop: true })
getVoiceProps({ user: { id: userId }, guildId }: { user: { id: string; }; guildId: string; }) {
return {
style: {
color: this.getColor(userId, { guildId })
}
};
},
useMessageColor(message: any) {
try {
const { messageSaturation } = settings.use(["messageSaturation"]);
const author = useMessageAuthor(message);
if (author.colorString !== undefined && messageSaturation !== 0)
return `color-mix(in oklab, ${author.colorString} ${messageSaturation}%, var(--text-normal))`;
} catch (e) {
console.error("[RCE] failed to get message color", e);
}
return undefined;
},
}); });

View file

@ -1,6 +1,6 @@
/* /*
* Vencord, a modification for Discord's desktop app * Vencord, a modification for Discord's desktop app
* Copyright (c) 2022 Vendicated and contributors * Copyright (c) 2024 Vendicated and contributors
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -18,9 +18,9 @@
import { IShikiTheme } from "@vap/shiki"; import { IShikiTheme } from "@vap/shiki";
export const SHIKI_REPO = "shikijs/shiki"; export const SHIKI_REPO = "shikijs/textmate-grammars-themes";
export const SHIKI_REPO_COMMIT = "0b28ad8ccfbf2615f2d9d38ea8255416b8ac3043"; export const SHIKI_REPO_COMMIT = "2d87559c7601a928b9f7e0f0dda243d2fb6d4499";
export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/shiki/themes/${name}.json`; export const shikiRepoTheme = (name: string) => `https://raw.githubusercontent.com/${SHIKI_REPO}/${SHIKI_REPO_COMMIT}/packages/tm-themes/themes/${name}.json`;
export const themes = { export const themes = {
// Default // Default
@ -30,33 +30,59 @@ export const themes = {
MaterialCandy: "https://raw.githubusercontent.com/millsp/material-candy/master/material-candy.json", MaterialCandy: "https://raw.githubusercontent.com/millsp/material-candy/master/material-candy.json",
// More from Shiki repo // More from Shiki repo
Andromeeda: shikiRepoTheme("andromeeda"),
AuroraX: shikiRepoTheme("aurora-x"),
AyuDark: shikiRepoTheme("ayu-dark"),
CatppuccinLatte: shikiRepoTheme("catppuccin-latte"),
CatppuccinFrappe: shikiRepoTheme("catppuccin-frappe"),
CatppuccinMacchiato: shikiRepoTheme("catppuccin-macchiato"),
CatppuccinMocha: shikiRepoTheme("catppuccin-mocha"),
DraculaSoft: shikiRepoTheme("dracula-soft"), DraculaSoft: shikiRepoTheme("dracula-soft"),
Dracula: shikiRepoTheme("dracula"), Dracula: shikiRepoTheme("dracula"),
EverforestDark: shikiRepoTheme("everforest-dark"),
EverforestLight: shikiRepoTheme("everforest-light"),
GithubDarkDefault: shikiRepoTheme("github-dark-default"),
GithubDarkDimmed: shikiRepoTheme("github-dark-dimmed"), GithubDarkDimmed: shikiRepoTheme("github-dark-dimmed"),
GithubDarkHighContrast: shikiRepoTheme("github-dark-high-contrast"),
GithubDark: shikiRepoTheme("github-dark"), GithubDark: shikiRepoTheme("github-dark"),
GithubLightDefault: shikiRepoTheme("github-light-default"),
GithubLightHighContrast: shikiRepoTheme("github-light-high-contrast"),
GithubLight: shikiRepoTheme("github-light"), GithubLight: shikiRepoTheme("github-light"),
Houston: shikiRepoTheme("houston"),
KanagawaDragon: shikiRepoTheme("kanagawa-dragon"),
KanagawaLotus: shikiRepoTheme("kanagawa-lotus"),
KanagawaWave: shikiRepoTheme("kanagawa-wave"),
LaserWave: shikiRepoTheme("laserwave"),
LightPlus: shikiRepoTheme("light-plus"), LightPlus: shikiRepoTheme("light-plus"),
MaterialDarker: shikiRepoTheme("material-darker"), MaterialDarker: shikiRepoTheme("material-theme-darker"),
MaterialDefault: shikiRepoTheme("material-default"), MaterialDefault: shikiRepoTheme("material-theme"),
MaterialLighter: shikiRepoTheme("material-lighter"), MaterialLighter: shikiRepoTheme("material-theme-lighter"),
MaterialOcean: shikiRepoTheme("material-ocean"), MaterialOcean: shikiRepoTheme("material-theme-ocean"),
MaterialPalenight: shikiRepoTheme("material-palenight"), MaterialPalenight: shikiRepoTheme("material-theme-palenight"),
MinDark: shikiRepoTheme("min-dark"), MinDark: shikiRepoTheme("min-dark"),
MinLight: shikiRepoTheme("min-light"), MinLight: shikiRepoTheme("min-light"),
Monokai: shikiRepoTheme("monokai"), Monokai: shikiRepoTheme("monokai"),
NightOwl: shikiRepoTheme("night-owl"),
Nord: shikiRepoTheme("nord"), Nord: shikiRepoTheme("nord"),
OneDarkPro: shikiRepoTheme("one-dark-pro"), OneDarkPro: shikiRepoTheme("one-dark-pro"),
OneLight: shikiRepoTheme("one-light"),
Plastic: shikiRepoTheme("plastic"),
Poimandres: shikiRepoTheme("poimandres"), Poimandres: shikiRepoTheme("poimandres"),
Red: shikiRepoTheme("red"),
RosePineDawn: shikiRepoTheme("rose-pine-dawn"), RosePineDawn: shikiRepoTheme("rose-pine-dawn"),
RosePineMoon: shikiRepoTheme("rose-pine-moon"), RosePineMoon: shikiRepoTheme("rose-pine-moon"),
RosePine: shikiRepoTheme("rose-pine"), RosePine: shikiRepoTheme("rose-pine"),
SlackDark: shikiRepoTheme("slack-dark"), SlackDark: shikiRepoTheme("slack-dark"),
SlackOchin: shikiRepoTheme("slack-ochin"), SlackOchin: shikiRepoTheme("slack-ochin"),
SnazzyLight: shikiRepoTheme("snazzy-light"),
SolarizedDark: shikiRepoTheme("solarized-dark"), SolarizedDark: shikiRepoTheme("solarized-dark"),
SolarizedLight: shikiRepoTheme("solarized-light"), SolarizedLight: shikiRepoTheme("solarized-light"),
Synthwave84: shikiRepoTheme("synthwave-84"),
TokyoNight: shikiRepoTheme("tokyo-night"),
Vesper: shikiRepoTheme("vesper"),
VitesseBlack: shikiRepoTheme("vitesse-black"),
VitesseDark: shikiRepoTheme("vitesse-dark"), VitesseDark: shikiRepoTheme("vitesse-dark"),
VitesseLight: shikiRepoTheme("vitesse-light"), VitesseLight: shikiRepoTheme("vitesse-light"),
CssVariables: shikiRepoTheme("css-variables"),
}; };
export const themeCache = new Map<string, IShikiTheme>(); export const themeCache = new Map<string, IShikiTheme>();

View file

@ -107,7 +107,7 @@ export default definePlugin({
predicate: () => settings.store.disableDisallowedDiscoveryFilters, predicate: () => settings.store.disableDisallowedDiscoveryFilters,
all: true, all: true,
replacement: { replacement: {
match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0\}\)/g, match: /\i\.\i\.get\(\{url:\i\.\i\.GUILD_DISCOVERY_VALID_TERM,query:\{term:\i\},oldFormErrors:!0,rejectWithError:!1\}\)/g,
replace: "Promise.resolve({ body: { valid: true } })" replace: "Promise.resolve({ body: { valid: true } })"
} }
} }

View file

@ -24,7 +24,7 @@ import { Devs } from "@utils/constants";
import { getIntlMessage } from "@utils/discord"; import { getIntlMessage } from "@utils/discord";
import definePlugin, { OptionType } from "@utils/types"; import definePlugin, { OptionType } from "@utils/types";
import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack"; import { findComponentByCodeLazy, findExportedComponentLazy, findStoreLazy } from "@webpack";
import { ChannelStore, GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common"; import { GuildMemberStore, RelationshipStore, SelectedChannelStore, Tooltip, UserStore, useStateFromStores } from "@webpack/common";
import { buildSeveralUsers } from "../typingTweaks"; import { buildSeveralUsers } from "../typingTweaks";
@ -44,7 +44,7 @@ function getDisplayName(guildId: string, userId: string) {
return GuildMemberStore.getNick(guildId, userId) ?? (user as any).globalName ?? user.username; return GuildMemberStore.getNick(guildId, userId) ?? (user as any).globalName ?? user.username;
} }
function TypingIndicator({ channelId }: { channelId: string; }) { function TypingIndicator({ channelId, guildId }: { channelId: string; guildId: string; }) {
const typingUsers: Record<string, number> = useStateFromStores( const typingUsers: Record<string, number> = useStateFromStores(
[TypingStore], [TypingStore],
() => ({ ...TypingStore.getTypingUsers(channelId) as Record<string, number> }), () => ({ ...TypingStore.getTypingUsers(channelId) as Record<string, number> }),
@ -57,7 +57,6 @@ function TypingIndicator({ channelId }: { channelId: string; }) {
} }
); );
const currentChannelId: string = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId()); const currentChannelId: string = useStateFromStores([SelectedChannelStore], () => SelectedChannelStore.getChannelId());
const guildId = ChannelStore.getChannel(channelId).guild_id;
if (!settings.store.includeMutedChannels) { if (!settings.store.includeMutedChannels) {
const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId); const isChannelMuted = UserGuildSettingsStore.isChannelMuted(guildId, channelId);
@ -165,7 +164,7 @@ export default definePlugin({
find: "UNREAD_IMPORTANT:", find: "UNREAD_IMPORTANT:",
replacement: { replacement: {
match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/, match: /\.name\),.{0,120}\.children.+?:null(?<=,channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id)" replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())"
} }
}, },
// Theads // Theads
@ -174,14 +173,14 @@ export default definePlugin({
find: "M11 9H4C2.89543 9 2 8.10457 2 7V1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1V7C0 9.20914 1.79086 11 4 11H11C11.5523 11 12 10.5523 12 10C12 9.44771 11.5523 9 11 9Z", find: "M11 9H4C2.89543 9 2 8.10457 2 7V1C2 0.447715 1.55228 0 1 0C0.447715 0 0 0.447715 0 1V7C0 9.20914 1.79086 11 4 11H11C11.5523 11 12 10.5523 12 10C12 9.44771 11.5523 9 11 9Z",
replacement: { replacement: {
match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/, match: /mentionsCount:\i.+?null(?<=channel:(\i).+?)/,
replace: "$&,$self.TypingIndicator($1.id)" replace: "$&,$self.TypingIndicator($1.id,$1.getGuildId())"
} }
} }
], ],
TypingIndicator: (channelId: string) => ( TypingIndicator: (channelId: string, guildId: string) => (
<ErrorBoundary noop> <ErrorBoundary noop>
<TypingIndicator channelId={channelId} /> <TypingIndicator channelId={channelId} guildId={guildId} />
</ErrorBoundary> </ErrorBoundary>
), ),
}); });

View file

@ -129,14 +129,22 @@ export default definePlugin({
buildSeveralUsers, buildSeveralUsers,
mutateChildren(props: any, users: User[], children: any) { mutateChildren(props: any, users: User[], children: any) {
if (!Array.isArray(children)) return children; try {
if (!Array.isArray(children)) {
return children;
}
let element = 0; let element = 0;
return children.map(c => return children.map(c =>
c.type === "strong" c.type === "strong" || (typeof c !== "string" && !React.isValidElement(c))
? <TypingUser {...props} user={users[element++]} /> ? <TypingUser {...props} user={users[element++]} />
: c : c
); );
} catch (e) {
console.error(e);
}
return children;
} }
}); });

View file

@ -209,10 +209,11 @@ export default definePlugin({
}, },
// Group DMs top small & large icon // Group DMs top small & large icon
{ {
find: /\.recipients\.length>=2(?!<isMultiUserDM.{0,50})/, find: '["aria-hidden"],"aria-label":',
replacement: { replacement: {
match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/, match: /null==\i\.icon\?.+?src:(\(0,\i\.\i\).+?\))(?=[,}])/,
replace: (m, iconUrl) => `${m},onClick:()=>$self.openAvatar(${iconUrl})` // We have to check that icon is not an unread GDM in the server bar
replace: (m, iconUrl) => `${m},onClick:()=>arguments[0]?.size!=="SIZE_48"&&$self.openAvatar(${iconUrl})`
} }
}, },
// User DMs top small icon // User DMs top small icon

View file

@ -155,6 +155,7 @@ export default definePlugin({
"guild-context": MakeContextCallback("Guild"), "guild-context": MakeContextCallback("Guild"),
"channel-context": MakeContextCallback("Channel"), "channel-context": MakeContextCallback("Channel"),
"thread-context": MakeContextCallback("Channel"), "thread-context": MakeContextCallback("Channel"),
"gdm-context": MakeContextCallback("Channel"),
"user-context": MakeContextCallback("User") "user-context": MakeContextCallback("User")
}, },

View file

@ -520,8 +520,8 @@ export const Devs = /* #__PURE__*/ Object.freeze({
id: 721717126523781240n, id: 721717126523781240n,
}, },
nyx: { nyx: {
name: "verticalsync", name: "verticalsync.",
id: 328165170536775680n id: 1207087393929171095n
}, },
nekohaxx: { nekohaxx: {
name: "nekohaxx", name: "nekohaxx",
@ -579,6 +579,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({
name: "SomeAspy", name: "SomeAspy",
id: 516750892372852754n, id: 516750892372852754n,
}, },
jamesbt365: {
name: "jamesbt365",
id: 158567567487795200n,
},
} satisfies Record<string, Dev>); } satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly // iife so #__PURE__ works correctly

View file

@ -19,7 +19,7 @@
import "./discord.css"; import "./discord.css";
import { MessageObject } from "@api/MessageEvents"; import { MessageObject } from "@api/MessageEvents";
import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common"; import { ChannelStore, ComponentDispatch, Constants, FluxDispatcher, GuildStore, i18n, IconUtils, InviteActions, MessageActions, PrivateChannelsStore, RestAPI, SelectedChannelStore, SelectedGuildStore, UserProfileActions, UserProfileStore, UserSettingsActionCreators, UserUtils } from "@webpack/common";
import { Channel, Guild, Message, User } from "discord-types/general"; import { Channel, Guild, Message, User } from "discord-types/general";
import { Except } from "type-fest"; import { Except } from "type-fest";
@ -212,3 +212,14 @@ export async function fetchUserProfile(id: string, options?: FetchUserProfileOpt
export function getUniqueUsername(user: User) { export function getUniqueUsername(user: User) {
return user.discriminator === "0" ? user.username : user.tag; return user.discriminator === "0" ? user.username : user.tag;
} }
/**
* Get the URL for an emoji. This function always returns a gif URL for animated emojis, instead of webp
* @param id The emoji id
* @param animated Whether the emoji is animated
* @param size The size for the emoji
*/
export function getEmojiURL(id: string, animated: boolean, size: number) {
const url = IconUtils.getEmojiURL({ id, animated, size });
return animated ? url.replace(".webp", ".gif") : url;
}

View file

@ -21,8 +21,8 @@ import { Patch, PatchReplacement, ReplaceFn } from "./types";
export function canonicalizeMatch<T extends RegExp | string>(match: T): T { export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
let partialCanon = typeof match === "string" ? match : match.source; let partialCanon = typeof match === "string" ? match : match.source;
partialCanon = partialCanon.replaceAll(/#{intl::([A-Za-z_$][\w$]*)}/g, (_, key) => { partialCanon = partialCanon.replaceAll(/#{intl::([\w$+/]*)(?:::(\w+))?}/g, (_, key, modifier) => {
const hashed = runtimeHashMessageKey(key); const hashed = modifier === "raw" ? key : runtimeHashMessageKey(key);
const isString = typeof match === "string"; const isString = typeof match === "string";
const hasSpecialChars = !Number.isNaN(Number(hashed[0])) || hashed.includes("+") || hashed.includes("/"); const hasSpecialChars = !Number.isNaN(Number(hashed[0])) || hashed.includes("+") || hashed.includes("/");
@ -40,7 +40,7 @@ export function canonicalizeMatch<T extends RegExp | string>(match: T): T {
return partialCanon as T; return partialCanon as T;
} }
const canonSource = partialCanon.replaceAll(String.raw`\i`, String.raw`(?:[A-Za-z_$][\w$]*)`); const canonSource = partialCanon.replaceAll("\\i", String.raw`(?:[A-Za-z_$][\w$]*)`);
return new RegExp(canonSource, match.flags) as T; return new RegExp(canonSource, match.flags) as T;
} }

View file

@ -163,9 +163,13 @@ waitFor(["open", "saveAccountChanges"], m => SettingsRouter = m);
export const PermissionsBits: t.PermissionsBits = findLazy(m => typeof m.ADMINISTRATOR === "bigint"); export const PermissionsBits: t.PermissionsBits = findLazy(m => typeof m.ADMINISTRATOR === "bigint");
export const zustandCreate = findByCodeLazy("will be removed in v4"); export const { zustandCreate } = mapMangledModuleLazy(["useSyncExternalStoreWithSelector:", "Object.assign", /(\i)\?(\i)\(\1\):\2/], {
zustandCreate: filters.byCode(/(\i)\?(\i)\(\1\):\2/)
});
export const zustandPersist = findByCodeLazy("[zustand persist middleware]"); export const { zustandPersist } = mapMangledModuleLazy(".onRehydrateStorage)?", {
zustandPersist: filters.byCode(/(\(\i,\i\))=>.+?\i\1/)
});
export const MessageActions = findByPropsLazy("editMessage", "sendMessage"); export const MessageActions = findByPropsLazy("editMessage", "sendMessage");
export const MessageCache = findByPropsLazy("clearCache", "_channelMessages"); export const MessageCache = findByPropsLazy("clearCache", "_channelMessages");
@ -181,7 +185,7 @@ export const ExpressionPickerStore: t.ExpressionPickerStore = mapMangledModuleLa
toggleExpressionPicker: filters.byCode(/getState\(\)\.activeView===\i\?\i\(\):\i\(/), toggleExpressionPicker: filters.byCode(/getState\(\)\.activeView===\i\?\i\(\):\i\(/),
setExpressionPickerView: filters.byCode(/setState\({activeView:\i,lastActiveView:/), setExpressionPickerView: filters.byCode(/setState\({activeView:\i,lastActiveView:/),
setSearchQuery: filters.byCode("searchQuery:"), setSearchQuery: filters.byCode("searchQuery:"),
useExpressionPickerStore: filters.byCode("Object.is") useExpressionPickerStore: filters.byCode(/\(\i,\i=\i\)=>/)
}); });
export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', { export const PopoutActions: t.PopoutActions = mapMangledModuleLazy('type:"POPOUT_WINDOW_OPEN"', {