From c68c4668dc8f3c259689fe21a47ba4728e3693f1 Mon Sep 17 00:00:00 2001 From: theo Date: Sat, 8 Aug 2026 17:58:45 +0100 Subject: [PATCH 1/6] ExpressionCloner: fix clone modal being blank (#4495) --- src/plugins/expressionCloner/index.tsx | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/src/plugins/expressionCloner/index.tsx b/src/plugins/expressionCloner/index.tsx index 8efe2069e..9f99cc15e 100644 --- a/src/plugins/expressionCloner/index.tsx +++ b/src/plugins/expressionCloner/index.tsx @@ -22,7 +22,7 @@ import { BaseText } from "@components/BaseText"; import { CheckedTextInput } from "@components/CheckedTextInput"; import { Flex } from "@components/Flex"; import { Devs } from "@utils/constants"; -import { getGuildAcronym } from "@utils/discord"; +import { getGuildAcronym, hasGuildFeature } from "@utils/discord"; import { Logger } from "@utils/Logger"; import definePlugin from "@utils/types"; import { Guild, GuildSticker } from "@vencord/discord-types"; @@ -33,8 +33,6 @@ import { Promisable } from "type-fest"; const uploadEmoji = findByCodeLazy(".GUILD_EMOJIS(", "EMOJI_UPLOAD_START"); -const getGuildMaxEmojiSlots = findByCodeLazy(".additionalEmojiSlots") as (guild: Guild) => number; - interface Sticker extends GuildSticker { t: "Sticker"; } @@ -72,6 +70,13 @@ function getGuildMaxStickerSlots(guild: Guild) { return PremiumTierStickerLimitMap[guild.premiumTier] ?? PremiumTierStickerLimitMap[0]; } +function getGuildMaxEmojiSlots(guild: Guild) { + return Math.max( + hasGuildFeature(guild, "MORE_EMOJI") ? 200 : 50, + 50 + (guild.premiumFeatures?.additionalEmojiSlots ?? 0) + ); +} + function getUrl(data: Data, size: number) { if (data.t === "Emoji") return `${location.protocol}//${window.GLOBAL_ENV.CDN_HOST}/emojis/${data.id}.webp?size=${size}&lossless=true&animated=true`; From 9c6f16f0f03371b3f2b4dd191ab76156ab370142 Mon Sep 17 00:00:00 2001 From: ssalggnikool Date: Sat, 8 Aug 2026 16:17:40 -0500 Subject: [PATCH 2/6] WhoReacted: hide users according to NoBlockedMessage settings (#4400) --- src/plugins/noBlockedMessages/index.ts | 11 ++++++----- src/plugins/whoReacted/index.tsx | 23 +++++++++++++++-------- src/utils/constants.ts | 4 ++++ 3 files changed, 25 insertions(+), 13 deletions(-) diff --git a/src/plugins/noBlockedMessages/index.ts b/src/plugins/noBlockedMessages/index.ts index e89aa27b9..1e763a7ed 100644 --- a/src/plugins/noBlockedMessages/index.ts +++ b/src/plugins/noBlockedMessages/index.ts @@ -85,18 +85,19 @@ export default definePlugin({ } ], - shouldIgnoreMessage(message: Message) { + shouldIgnoreUser(userId: string) { try { - if (RelationshipStore.isBlocked(message.author.id)) { - return true; - } - return settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(message.author.id); + return RelationshipStore.isBlocked(userId) || (settings.store.applyToIgnoredUsers && RelationshipStore.isIgnored(userId)); } catch (e) { new Logger("NoBlockedMessages").error("Failed to check if user is blocked or ignored:", e); return false; } }, + shouldIgnoreMessage(message: Message) { + return this.shouldIgnoreUser(message?.author?.id); + }, + shouldHide(props: MessageDeleteProps): boolean { try { const collapsedReason = props.collapsedReason(); diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 6dab3c7cc..1c19ce7cb 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -16,7 +16,9 @@ * along with this program. If not, see . */ +import { isPluginEnabled } from "@api/PluginManager"; import ErrorBoundary from "@components/ErrorBoundary"; +import NoBlockedMessagesPlugin from "@plugins/noBlockedMessages"; import { Devs } from "@utils/constants"; import { sleep } from "@utils/misc"; import { Queue } from "@utils/Queue"; @@ -86,7 +88,7 @@ function handleClickAvatar(event: React.UIEvent) { event.stopPropagation(); } -function ReactionUsers({ message, emoji, type }: ReactionProps) { +function ReactionUsers({ message, users }: { message: Message, users: User[]; }) { const forceUpdate = useForceUpdater(); useLayoutEffect(() => { // bc need to prevent autoscrolling @@ -105,9 +107,6 @@ function ReactionUsers({ message, emoji, type }: ReactionProps) { return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); }, [message.id, forceUpdate]); - const reactions = getReactionsWithQueue(message, emoji, type); - const users = Array.from(reactions, ([id]) => UserStore.getUser(id)).filter(Boolean); - return (
{ - return props.message.reactions.length > 10 + renderUsers: ErrorBoundary.wrap(({ message, emoji, type }: ReactionProps) => { + if (message.reactions.length > 10) return null; + + const reactionMap = getReactionsWithQueue(message, emoji, type); + let users = Array.from(reactionMap, ([id]) => UserStore.getUser(id)).filter(Boolean); + + if (isPluginEnabled(NoBlockedMessagesPlugin.name)) + users = users.filter(user => !NoBlockedMessagesPlugin.shouldIgnoreUser(user.id)); + + return users.length === 0 ? null - : ; + : ; }, { noop: true }), setScrollObj(scroll: any) { diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 2a7b6af50..4cd10cf6b 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -650,6 +650,10 @@ export const Devs = /* #__PURE__*/ Object.freeze({ name: "Lunascape", id: 383365021415243776n }, + paige: { + name: "paige", + id: 1375697625864601650n + }, jax: { name: "jax", id: 1493703027801194598n From 7f040327a512d1e87ce32832a4a28bd72487051c Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sat, 8 Aug 2026 23:46:26 +0200 Subject: [PATCH 3/6] chore: various improvements Co-Authored-By: LockBlock-dev <68129141+LockBlock-dev@users.noreply.github.com> Co-Authored-By: sadan <117494111+sadan4@users.noreply.github.com> --- .gemini/config.yaml | 8 - .gemini/styleguide.md | 365 ------------------ .github/pull_request_template.md | 4 + .../src/stores/MessageStore.d.ts | 5 + packages/discord-types/src/utils.d.ts | 9 +- src/plugins/messageLinkEmbeds/index.tsx | 2 +- src/utils/types.ts | 6 + src/webpack/common/stores.ts | 8 +- 8 files changed, 25 insertions(+), 382 deletions(-) delete mode 100644 .gemini/config.yaml delete mode 100644 .gemini/styleguide.md diff --git a/.gemini/config.yaml b/.gemini/config.yaml deleted file mode 100644 index 50e5bdbd7..000000000 --- a/.gemini/config.yaml +++ /dev/null @@ -1,8 +0,0 @@ -code_review: - disable: false - max_review_comments: -1 - pull_request_opened: - help: false - summary: false - code_review: false - include_drafts: false diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md deleted file mode 100644 index fd92f22e0..000000000 --- a/.gemini/styleguide.md +++ /dev/null @@ -1,365 +0,0 @@ -# Vencord Code Review Style Guide - -You are reviewing PRs for **Vencord**, a modification for the Discord web app (and Electron app). It uses TypeScript, React, and webpack patching to inject into Discord. The codebase has extensive built-in utilities that contributors MUST use. Be direct, actionable, no pleasantries. Use natural, human language. Be blunt when code is bad. - ---- - -## Plugin Acceptance (Instant Reject) - -If a plugin breaks ANY of these, reject the entire PR. These are non-negotiable: - -1. **No simple slash-command plugins** (e.g. `/cat`). Should be a [user-installable Discord app](https://discord.com/developers/docs/change-log#userinstallable-apps-preview) instead. -2. **No simple text replacement plugins.** The built-in TextReplace plugin already covers this. -3. **No raw DOM manipulation / MutationObserver.** Always use patches and React. -4. **No FakeDeafen or FakeMute.** -5. **No StereoMic-related plugins.** -6. **No UI-only hide/redesign plugins.** Use CSS for that. (Negotiable in rare cases.) -7. **No plugins targeting specific third-party Discord bots.** Official Discord apps are fine. -8. **No selfbot or API abuse.** Auto-replies, animated statuses, message pruning, Nitro snipers, etc. -9. **No untrusted third-party APIs.** Well-known services (Google, GitHub) are acceptable. -10. **No plugins requiring users to provide their own API keys.** -11. **No new dependencies** unless strictly necessary and well justified. - ---- - -## Plugin Structure - -Default export via `definePlugin` from `@utils/types`. Non-negotiable. - -```typescript -import definePlugin from "@utils/types"; -import { Devs } from "@utils/constants"; - -export default definePlugin({ - name: "PluginName", // PascalCase, matches directory name - description: "Does something", // Capital first - authors: [Devs.Name] -}); -``` - -**Prefer declarative APIs** over manual registration: - -- `flux: { EVENT_NAME(data) {} }` not manual FluxDispatcher subscribe -- `contextMenus: { "nav-id": fn }` not manual addContextMenuPatch -- `chatBarButton: { render, icon }` not addChatBarButton -- `messagePopoverButton: { render, icon }` not addMessagePopoverButton -- `managedStyle` for CSS not manual enableStyle/disableStyle in start/stop - -**Reject deprecated fields:** `renderChatBarButton`, `renderMessagePopoverButton`, `options` - -**Lifecycle balance:** anything registered in `start()` must be cleaned up in `stop()`. - ---- - -## Forbidden Patterns - -Flag these and suggest the fix: - -| Bad | Good | Reason | -| ---------------------------------------------------------------- | ------------------------------------------- | ----------------------------------- | -| `value !== null && value !== undefined` | `value` or `value != null` | Verbose | -| `array && array.length > 0` | `array?.length` | Redundant check | -| `settings?.store?.value` | `settings.store.value` | Store is always defined | -| `value \|\| defaultValue` | `value ?? defaultValue` | `\|\|` falsifies `0`, `""`, `false` | -| `` `${classA} ${classB}` `` | `classes(classA, classB)` | Handles null/false gracefully | -| `"vc-plugin-class"` hardcoded | `cl("class")` via `classNameFactory` | Consistent, typo-proof | -| `console.log/warn` | Remove it | No logging in plugin code | -| `cdn.discordapp.com/avatars/...` | `IconUtils.getUserAvatarURL(user)` | Handles animated, sizing, CDN | -| `cdn.discordapp.com/icons/...` | `IconUtils.getGuildIconURL(...)` | Same | -| `cdn.discordapp.com/banners/...` | `IconUtils.getUserBannerURL(...)` | Same | -| `cdn.discordapp.com/emojis/...` | `IconUtils.getEmojiURL(...)` | Same | -| `/api/v9/...` or `/users/@me` | `Constants.Endpoints.*` or `RestAPI` | Endpoints change | -| `@api/Styles` for classNameFactory | `@utils/css` | `@api/Styles` is deprecated | -| `any` for Discord objects | Import from `@vencord/discord-types` | Type safety | -| `as unknown as` casting | Find the correct type | Unsafe | -| `React.memo()` | Remove it | Not needed | -| `React.cloneElement` / `React.isValidElement` / `React.Children` | Find another approach | Forbidden | -| `React.lazy(() => import(...))` | `LazyComponent` from `@utils/lazyReact` | Framework-integrated | -| Empty `catch {}` | Handle the error (toast, fallback, rethrow) | Silent failures | -| CSS-only plugins | Must have actual logic/patches | Not allowed | -| Commented-out dead code | Delete it | Git has history | -| `document.querySelector(...)` | Use webpack patches | DOM manipulation forbidden | -| `Vencord.Plugins.plugins["X"]` | `isPluginEnabled` + direct import | Proper interop | -| `plugin.started` check | `isPluginEnabled(plugin.name)` | Proper interop | -| Unexplained magic numbers | Named constants | Readability | - ---- - -## TypeScript and Code Style - -**Prefer:** `?.` optional chaining, `??` nullish coalescing, `const`, arrow functions, destructuring, template literals, object shorthand, array methods (`.map`, `.filter`, `.find`, `.some`). - -**Style:** early returns over nested conditions, trust TypeScript inference (don't annotate obvious types), inline single-use variables, flat over nested, explicit over implicit. - -**Less is more.** Less code with the same functionality is always better. KISS over clever. - -**No comments.** Never add comments unless explicitly asked. However, preserve existing comments as they contain important context. - -```typescript -// GOOD — early return, destructuring, inference, no comments -const getUser = (id: string) => { - const user = UserStore.getUser(id); - if (!user) return null; - const { username, discriminator } = user; - return `${username}#${discriminator}`; -}; - -// BAD — nested, verbose, unnecessary annotations -function getUser(id: string): string | null { - // Get the user from the store - const user = UserStore.getUser(id); - if (user) { - const username: string = user.username; - const discriminator: string = user.discriminator; - return username + "#" + discriminator; - } else { - return null; - } -} -``` - -Note: ESLint already enforces `prefer-const`, `prefer-destructuring`, `eqeqeq`, strict equality, unused imports, and import sorting. Focus review on patterns ESLint cannot catch: code philosophy, unnecessary complexity, missing early returns, and unnecessary comments. - ---- - -## React - -**Conditional rendering:** return `null`, never `undefined` or bare `return;` - -**ErrorBoundary:** wrap any component that is used in patches (`$self.renderThing()`): `renderThing: ErrorBoundary.wrap((props) => { ... }, { noop: true })` - -**useEffect cleanup:** always return cleanup when subscribing to events, timers, or resources: - -```typescript -useEffect(() => { - const handler = () => { /* ... */ }; - window.addEventListener("resize", handler); - return () => window.removeEventListener("resize", handler); -}, []); -``` - ---- - -## Discord Types - -Never use `any` for Discord objects. Import proper types: - -```typescript -import { User, Channel, Guild, GuildMember, Message, Role } from "@vencord/discord-types"; -``` - -Store types, component prop types, and action types are all available. Never use `as unknown as` casting as a workaround. - ---- - -## CSS and Class Names - -**Every plugin** must use `classNameFactory` from `@utils/css` for class names: - -```typescript -import { classNameFactory } from "@utils/css"; -const cl = classNameFactory("vc-my-plugin-"); -cl("container") // "vc-my-plugin-container" -``` - -**Combining classes** from different sources: use `classes()` from `@utils/misc`: - -```typescript -className={classes(cl("wrapper"), someDiscordClass, isActive && cl("active"))} -``` - ---- - -## Settings - -- `settings.store.key` — reactive, auto-persists (React components) -- `settings.plain.key` — non-reactive raw value (basically never use this, only for special cases) - ---- - -## Patch Quality - -Patches modify Discord's minified webpack modules. Stability is paramount. - -- **One patch per concern.** Each replacement does one thing. -- **Surgical.** Match only what needs replacing, let `find` target the module. -- **No hardcoded minified vars.** Never `e`, `t`, `n`, `r`, `i`, `eD`, `eH` etc. Use `\i`. -- **Bounded gaps.** `.{0,50}?` not `.+?` or `.*?` (unbounded = cross-match bugs). -- **No generic patterns.** `/className:\i/` alone is too broad. Add stable anchors. -- **No raw intl hashes.** Use `#{intl::KEY_NAME}` not `.aA4Vce`. If key is unknown, use `#{intl::aA4Vce::raw}`. -- **Capture groups only when reused** in replace via `$1`, `$2` (or as replace function parameter). -- **`$&`** for append/prepend, **`$self`** for plugin method calls. - -```typescript -// GOOD -patches: [{ - find: "#{intl::PIN_MESSAGE}),icon:", - replacement: { - match: /#{intl::PIN_MESSAGE}\)/, - replace: "$self.getPinLabel(arguments[0]))" - } -}] - -// BAD -patches: [{ - find: "pinMessage", - replacement: { - match: /label:e\.pinned\?.+?pinMessage\)/, - replace: "$self.getPinLabel(e))" - } -}] -``` - ---- - -## Plugin Interop - -```typescript -// GOOD -import { isPluginEnabled } from "@api/PluginManager"; -import otherPlugin from "@plugins/otherPlugin"; -if (!isPluginEnabled(otherPlugin.name)) return null; -otherPlugin.someFunction(); - -// BAD -Vencord.Plugins.plugins["OtherPlugin"].someFunction(); -(somePlugin as unknown as { method(): void }).method(); -``` - ---- - -## Text and Descriptions - -- Plugin and setting descriptions: capital first letter. -- Error messages and toasts: natural human text, no dashes or robotic formatting. - -```typescript -// GOOD -description: "Adds a button to copy message content." -showToast("Module not found"); - -// BAD -description: "adds a button to copy message content" -showToast("Module - not found"); -``` - ---- - -## Performance - -- `Map`/`Set` for frequent lookups, not arrays -- `.find()` / `.some()` not `.filter()[0]` -- `Promise.all()` for parallel async -- no local scope webpack `find*` functions (including start or stop methods), always find*Lazy on top level - ---- - -## Memory Leaks and Cleanup - -Plugins that don't use patches don't require a Discord restart to apply. Their `start()` and `stop()` must work cleanly so users can toggle them on/off without leaking resources. - -**Every `start()` must have a matching `stop()`.** If `start()` subscribes, registers, or creates anything, `stop()` must undo it. Common leaks to flag: - -- Event listeners added but never removed (FluxDispatcher, DOM events, MessageEvents) -- Intervals/timeouts set but never cleared -- Context menu patches added but never removed (use declarative `contextMenus` instead) -- Chat bar buttons added but never removed (use declarative `chatBarButton` instead) -- Styles enabled but never disabled (use `managedStyle` instead) -- Flux subscriptions without corresponding unsubscriptions - -**Prefer declarative APIs** that handle cleanup automatically: `flux`, `contextMenus`, `chatBarButton`, `messagePopoverButton`, `managedStyle`. These don't need manual cleanup. - -**useEffect must return cleanup** when it creates subscriptions, timers, observers, or any persistent resource. No exceptions. - -**Watch for stale closures and references.** If a plugin stores references to DOM nodes, channels, or users in module scope, check that they're nulled out in `stop()`. - -```typescript -// GOOD — clean start/stop, no leaks -start() { - this.interval = setInterval(this.update, 5000); - document.addEventListener("visibilitychange", this.onVisibilityChange); -}, -stop() { - clearInterval(this.interval); - document.removeEventListener("visibilitychange", this.onVisibilityChange); -} - -// BAD — leaks interval and listener forever -start() { - setInterval(this.update, 5000); - document.addEventListener("visibilitychange", this.onVisibilityChange); -}, -stop() {} -``` - ---- - -## Code Hygiene - -- **Delete dead code**, don't comment it out. Git preserves history. -- **No overengineering.** No premature abstractions, no "just in case" handling, no features not requested. Three similar lines beats a one-use helper. If code can be half the size and do the same thing, it should be. -- **No bloat.** Flag unnecessary wrapper functions, redundant type annotations, excessive error handling for things that can't fail, useless try/catch around synchronous code, or 50 lines doing what 10 could do. Code should be lean. -- **No unused imports.** Every import must be referenced. -- **No logging in plugin code besides for errors.** No `console.log/warn`. - ---- - -## AI Slop Detection - -If a PR shows signs of being AI-generated, mention it but **still review the code fully**. Do not reject or stop reviewing just because it looks AI-generated. Note it as an observation, then continue reviewing every issue as you normally would. Common AI slop patterns: - -- Excessive comments explaining obvious code ("// get the user", "// check if null", "// return the result") -- Overly verbose variable names that read like sentences (`isUserCurrentlyLoggedInToTheApplication`) -- Unnecessary abstractions and wrapper functions for trivial operations -- "Just in case" error handling everywhere, including places that can never fail -- Reimplementing utilities that already exist in the codebase (check the Built-in Utilities section) -- Generic, boilerplate-heavy code that looks like it was prompted with "write a Discord plugin that..." -- Excessive JSDoc/TSDoc on every function, even internal one-liners -- Overly defensive coding: null checks on things that are never null, type guards on known types -- Cookie-cutter patterns repeated without understanding (e.g. wrapping every single line in try/catch) -- Descriptions and messages that sound robotic or templated rather than human-written - -If you suspect it, say something like: "This looks like it might be AI-generated. There's a lot of unnecessary comments, over-engineering, and reimplemented utilities." Then proceed with the full review, flagging every concrete issue as usual. - ---- - -## Built-in Utilities Reference - -If a PR reimplements any of these, flag it. They already exist: - -**@utils/misc:** `classes`, `sleep`, `isObject`, `isObjectEmpty`, `parseUrl`, `pluralise`, `identity` -**@utils/guards:** `isTruthy`, `isNonNullish` -**@utils/text:** `formatDuration`, `formatDurationMs`, `humanFriendlyJoin`, `makeCodeblock`, `toInlineCode`, `escapeRegExp` -**@utils/discord:** `getCurrentChannel`, `getCurrentGuild`, `getIntlMessage`, `openPrivateChannel`, `insertTextIntoChatInputBox`, `sendMessage`, `copyWithToast`, `openUserProfile`, `fetchUserProfile`, `getUniqueUsername`, `openInviteModal` -**@utils/css:** `classNameFactory`, `classNameToSelector` -**@utils/clipboard:** `copyToClipboard` -**@utils/modal:** `openModal`, `closeModal`, `ModalRoot`, `ModalHeader`, `ModalContent`, `ModalFooter`, `ModalCloseButton` -**@utils/margins:** `Margins.top8`, `.top16`, `.bottom8`, `.bottom16` etc. -**@utils/web:** `saveFile`, `chooseFile` -**@utils/lazy:** `proxyLazy`, `makeLazy` -**@utils/lazyReact:** `LazyComponent` -**@utils/react:** `useAwaiter`, `useForceUpdater`, `useTimer` -**@api/DataStore:** `get`, `set`, `del` (IndexedDB, async) -**@api/Commands:** `sendBotMessage`, `findOption` - -**@webpack/common:** - -- Stores: `UserStore`, `GuildStore`, `ChannelStore`, `GuildMemberStore`, `SelectedChannelStore`, `SelectedGuildStore`, `PresenceStore`, `RelationshipStore`, `MessageStore`, `EmojiStore`, `ThemeStore`, `PermissionStore`, `VoiceStateStore`, 30+ more -- Actions: `RestAPI`, `FluxDispatcher`, `MessageActions`, `NavigationRouter`, `ChannelRouter`, `ChannelActionCreators`, `SettingsRouter` -- Utils: `Constants` (`.Endpoints`), `SnowflakeUtils`, `Parser`, `PermissionsBits`, `moment`, `lodash`, `IconUtils`, `ColorUtils`, `ImageUtils`, `DateUtils`, `UsernameUtils`, `DisplayProfileUtils` -- Components: `Tooltip`, `TextInput`, `TextArea`, `Select`, `Slider`, `Avatar`, `Menu`, `Popout`, `ScrollerThin`, `Timestamp`, `MaskedLink`, `ColorPicker` -- Toasts: `Toasts`, `showToast` -- React: `useState`, `useEffect`, `useCallback`, `useStateFromStores` - -**@webpack finders:** `findByPropsLazy`, `findByCodeLazy`, `findStoreLazy`, `findComponentByCodeLazy`, `findExportedComponentLazy` - -**@components/:** `ErrorBoundary`, `Flex`, `Button`, `Paragraph`, `Heading`, `BaseText`, `Span`, `ErrorCard`, `Link`, `CodeBlock`, `FormSwitch` - ---- - -## Review Severity - -1. **CRITICAL** — `any` types, hardcoded CDN/API URLs, direct DOM manipulation, security issues, massive overengineering/bloat, suspected AI slop (note it, but still review everything) -2. **HIGH** — Missing useEffect cleanup, forbidden React APIs, hardcoded class names, console logging statements, missing definePluginSettings, unnecessary abstractions -3. **MEDIUM** — Anti-patterns (`||` instead of `??`), utility reimplementations, commented-out code, bad description format, excessive comments -4. **LOW** — Style preferences, minor performance wins, organization diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 868a3a862..89356ffa3 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,9 +1,13 @@ ## Describe your Changes +## Screenshots (if applicable) + ## Checklist before submitting + - [ ] I have read the [CONTRIBUTING.md](./CONTRIBUTING.md) file and made sure this pull request complies with it - [ ] This pull request was written by me, and not an AI agent diff --git a/packages/discord-types/src/stores/MessageStore.d.ts b/packages/discord-types/src/stores/MessageStore.d.ts index e8a095e7a..faec2189e 100644 --- a/packages/discord-types/src/stores/MessageStore.d.ts +++ b/packages/discord-types/src/stores/MessageStore.d.ts @@ -34,6 +34,11 @@ export interface ChannelMessages { _before: MessageCache; _after: MessageCache; _map: Record; + // has a ton more undocumented methods + some(predicate: (message: Message, index: number, array: Message[]) => unknown, thisArg?: any): boolean; + forEach(callback: (message: Message, index: number, array: Message[]) => void, thisArg?: any): void; + receiveMessage(msg: any): this; + get(msgId: string): Message | undefined; } export class MessageStore extends FluxStore { diff --git a/packages/discord-types/src/utils.d.ts b/packages/discord-types/src/utils.d.ts index 6d06d6400..b0a6ee343 100644 --- a/packages/discord-types/src/utils.d.ts +++ b/packages/discord-types/src/utils.d.ts @@ -255,16 +255,17 @@ export interface ExpressionPickerStoreState extends Record { activeView: ActiveView | null; lastActiveView: ActiveView | null; activeViewType: any | null; + activeChannelId: string | null; searchQuery: string; isSearchSuggestion: boolean, pickerId: string; } export interface ExpressionPickerStore { - openExpressionPicker(activeView: ActiveView, activeViewType?: any): void; - closeExpressionPicker(activeViewType?: any): void; - toggleMultiExpressionPicker(activeViewType?: any): void; - toggleExpressionPicker(activeView: ActiveView, activeViewType?: any): void; + openExpressionPicker(activeView: ActiveView, activeViewType?: any, channelId?: string): void; + closeExpressionPicker(activeViewType?: any, channelId?: string): void; + toggleMultiExpressionPicker(activeViewType?: any, channelId?: string): void; + toggleExpressionPicker(activeView: ActiveView, activeViewType?: any, channelId?: string): void; setExpressionPickerView(activeView: ActiveView): void; setSearchQuery(searchQuery: string, isSearchSuggestion?: boolean): void; useExpressionPickerStore(): ExpressionPickerStoreState; diff --git a/src/plugins/messageLinkEmbeds/index.tsx b/src/plugins/messageLinkEmbeds/index.tsx index 65f563e0e..bdf283104 100644 --- a/src/plugins/messageLinkEmbeds/index.tsx +++ b/src/plugins/messageLinkEmbeds/index.tsx @@ -146,7 +146,7 @@ async function fetchMessage(channelID: string, messageID: string) { const msg = res?.body?.[0]; if (!msg) return; - const message: Message = MessageStore.getMessages(msg.channel_id).receiveMessage(msg).get(msg.id); + const message = MessageStore.getMessages(msg.channel_id).receiveMessage(msg).get(msg.id); if (!message) return; messageCache.set(message.id, { diff --git a/src/utils/types.ts b/src/utils/types.ts index 3dadd40ad..5d25b170b 100644 --- a/src/utils/types.ts +++ b/src/utils/types.ts @@ -427,3 +427,9 @@ export type PluginNative = T | { [K in keyof T]?: never; }; + +export type ConstEnumToRuntimeEnum = { + [K in keyof T as T[K] extends number ? T[K] : never]: K; +} & { + [K in keyof T]: T[K]; +}; diff --git a/src/webpack/common/stores.ts b/src/webpack/common/stores.ts index 86d7a8a56..6ce75acc0 100644 --- a/src/webpack/common/stores.ts +++ b/src/webpack/common/stores.ts @@ -16,7 +16,9 @@ * along with this program. If not, see . */ +import { ConstEnumToRuntimeEnum } from "@utils/types"; import * as t from "@vencord/discord-types"; +import * as enums from "@vencord/discord-types/enums"; import { findByCodeLazy, findByPropsLazy } from "@webpack"; import { waitForStore } from "./internal"; @@ -25,11 +27,9 @@ export const Flux: t.Flux = findByPropsLazy("connectStores"); export type GenericStore = t.FluxStore & Record; -export const DraftType = findByPropsLazy("ChannelMessage", "SlashCommand"); +export const DraftType: ConstEnumToRuntimeEnum = findByPropsLazy("ChannelMessage", "SlashCommand"); -export let MessageStore: Omit & GenericStore & { - getMessages(chanId: string): any; -}; +export let MessageStore: t.MessageStore; export let PermissionStore: t.PermissionStore; export let GuildChannelStore: t.GuildChannelStore; From bc2bcb506ecf89dc0ca06a8654f31ca4c02b73f8 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 9 Aug 2026 02:59:18 +0200 Subject: [PATCH 4/6] MessageLogger: fix Remove History not working for deletes --- src/plugins/messageLogger/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/messageLogger/index.tsx b/src/plugins/messageLogger/index.tsx index 18a4a0d0e..130f0c9b5 100644 --- a/src/plugins/messageLogger/index.tsx +++ b/src/plugins/messageLogger/index.tsx @@ -138,7 +138,7 @@ function clearMessageHistory(msg: MLMessage) { FluxDispatcher.dispatch({ type: "MESSAGE_DELETE", channelId: msg.channel_id, - messageId: msg.id, + id: msg.id, mlDeleted: true }); } else { @@ -195,7 +195,7 @@ const patchMessageContextMenu: NavContextMenuPatchCallback = (children, props) = }; const patchChannelContextMenu: NavContextMenuPatchCallback = (children, { channel }) => { - const messages = MessageStore.getMessages(channel?.id) as MLMessage[]; + const messages = MessageStore.getMessages(channel?.id); if (!messages?.some(msg => doesMessageHaveHistory(msg))) return; const group = findGroupChildrenByChildId("mark-channel-read", children) ?? children; From e1058b3b7055933b607040093a8afdb06c098dc5 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Sun, 9 Aug 2026 03:18:33 +0200 Subject: [PATCH 5/6] fix WhoReacted --- src/plugins/whoReacted/index.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/plugins/whoReacted/index.tsx b/src/plugins/whoReacted/index.tsx index 1c19ce7cb..6e4bffd1e 100644 --- a/src/plugins/whoReacted/index.tsx +++ b/src/plugins/whoReacted/index.tsx @@ -89,24 +89,12 @@ function handleClickAvatar(event: React.UIEvent) { } function ReactionUsers({ message, users }: { message: Message, users: User[]; }) { - const forceUpdate = useForceUpdater(); - useLayoutEffect(() => { // bc need to prevent autoscrolling if (Scroll?.scrollCounter > 0) { Scroll.setAutomaticAnchor(null); } }); - useEffect(() => { - const cb = (e: any) => { - if (e?.messageId === message.id) - forceUpdate(); - }; - FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb); - - return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); - }, [message.id, forceUpdate]); - return (
{ + const forceUpdate = useForceUpdater(); + + useEffect(() => { + const cb = (e: any) => { + if (e?.messageId === message.id) + forceUpdate(); + }; + FluxDispatcher.subscribe("MESSAGE_REACTION_ADD_USERS", cb); + + return () => FluxDispatcher.unsubscribe("MESSAGE_REACTION_ADD_USERS", cb); + }, [message.id, forceUpdate]); + if (message.reactions.length > 10) return null; const reactionMap = getReactionsWithQueue(message, emoji, type); From aa5a764b7ff9cd0d433201dafcc8966e0edbebd7 Mon Sep 17 00:00:00 2001 From: Vendicated Date: Mon, 10 Aug 2026 04:15:43 +0200 Subject: [PATCH 6/6] v1.15.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 559e98cf7..4319c61b7 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vencord", "private": "true", - "version": "1.15.0", + "version": "1.15.1", "description": "The cutest Discord client mod", "homepage": "https://github.com/Vendicated/Vencord#readme", "bugs": {