From ab54a67ea3acf74edef5a4bad853c8971331c83b Mon Sep 17 00:00:00 2001 From: V Date: Wed, 20 May 2026 21:45:53 +0200 Subject: [PATCH 1/3] Update contributing guideline and pr & issue templates (#4233) --- .gemini/config.yaml | 8 + .gemini/styleguide.md | 365 ++++++++++++++++++++ .github/ISSUE_TEMPLATE/blank.yml | 25 -- .github/ISSUE_TEMPLATE/bug_report.yml | 22 +- .github/ISSUE_TEMPLATE/config.yml | 13 +- .github/ISSUE_TEMPLATE/developer-banner.png | Bin 31992 -> 0 bytes .github/ISSUE_TEMPLATE/feature_request.yml | 45 +++ .github/pull_request_template.md | 9 + CONTRIBUTING.md | 17 +- 9 files changed, 463 insertions(+), 41 deletions(-) create mode 100644 .gemini/config.yaml create mode 100644 .gemini/styleguide.md delete mode 100644 .github/ISSUE_TEMPLATE/blank.yml delete mode 100644 .github/ISSUE_TEMPLATE/developer-banner.png create mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml create mode 100644 .github/pull_request_template.md diff --git a/.gemini/config.yaml b/.gemini/config.yaml new file mode 100644 index 000000000..50e5bdbd7 --- /dev/null +++ b/.gemini/config.yaml @@ -0,0 +1,8 @@ +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 new file mode 100644 index 000000000..fd92f22e0 --- /dev/null +++ b/.gemini/styleguide.md @@ -0,0 +1,365 @@ +# 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/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml deleted file mode 100644 index 89588f3d8..000000000 --- a/.github/ISSUE_TEMPLATE/blank.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Blank Issue -description: Create a blank issue. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. - -body: - - type: markdown - attributes: - value: | - ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) - - GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. - - - type: textarea - id: content - attributes: - label: Content - validations: - required: true - - - type: checkboxes - id: agreement-check - attributes: - label: Request Agreement - options: - - label: I have read the requirements for opening an issue above - required: true diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c08f46357..793b2315a 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -1,5 +1,5 @@ name: Bug/Crash Report -description: Create a bug or crash report for Vencord. ALWAYS FIRST USE OUR SUPPORT CHANNEL! ONLY USE THIS FORM IF YOU ARE A CONTRIBUTOR OR WERE TOLD TO DO SO IN THE SUPPORT CHANNEL. +description: Create a bug or crash report for Vencord. labels: [bug] title: "[Bug] " @@ -7,9 +7,16 @@ body: - type: markdown attributes: value: | - ![Are you a developer? No? This form is not for you!](https://github.com/Vendicated/Vencord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true) + GitHub Issues are for development, not support! - GitHub Issues are for development, not support! Please use our [support server](https://vencord.dev/discord) unless you are a Vencord Developer. + Some plugins may sometimes break, but we have automatic tracking for that. So please **do not report broken plugins**, it is useless. + However, if a plugin works fine, but has a bug in its functionality, then you can report that here. + + If you are experiencing any of the following issues, go to our [Discord server](https://discord.gg/D9uwnFnqmd) for support: + - Vencord doesn't work or crashes + - A plugin doesn't work anymore + - You are having issues installing Vencord + - You want to ask a question about Vencord or one of its plugins - type: textarea id: bug-description @@ -46,7 +53,7 @@ body: id: crash-log attributes: label: Errors - description: Open the Developer Console with Ctrl/Cmd + Shift + i. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". + description: Open the Developer Console with `Ctrl+Shift+i` / `Cmd+Option+i`. Then look for any red errors (Ignore network errors like Failed to load resource) and paste them between the "```". value: | ``` Replace this text with your crash-log. @@ -60,7 +67,8 @@ body: label: Request Agreement description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable options: + - label: I've searched existing issues and this hasn't been reported before - label: I am using Discord Stable or tried on Stable and this bug happens there as well - required: true - - label: I am a Vencord Developer - required: true + - label: I installed Vencord via an official method (download from vencord.dev or from source), not via something like AUR or Nix packages + - label: This is not a support request + - label: I am willing to submit a fix for this myself diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index bc5d97662..9e96e5f4b 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,11 @@ blank_issues_enabled: false contact_links: - - name: Vencord Support Server + - name: 💬 Discord Community url: https://discord.gg/D9uwnFnqmd - about: If you need help regarding Vencord, please join our support server! - - name: Vencord Installer - url: https://github.com/Vencord/Installer - about: You can find the Vencord Installer here + about: For plugin requests, support questions, and general help - join our Discord server + - name: 🔌 Plugin Requests + url: https://discord.gg/D9uwnFnqmd + about: Want a new plugin? Request it in our Discord community, not here + - name: ❓ Support & Questions + url: https://discord.gg/D9uwnFnqmd + about: Need help? Ask in our Discord server for faster responses diff --git a/.github/ISSUE_TEMPLATE/developer-banner.png b/.github/ISSUE_TEMPLATE/developer-banner.png deleted file mode 100644 index 5fa12fc370750c460f8d0959d5f73828c63b6e3d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 31992 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 WKXLqM0$yeQOioH!vP#@2=>GuWUFmuN diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 000000000..2a8d59f98 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,45 @@ +name: Feature Request +description: Have an idea for a new feature? Request it here! No plugin requests, go to our Discord server for that +labels: [enhancement] +title: "[Feature] <title>" + +body: + - type: markdown + attributes: + value: | + This form is not for any of the following: + - Plugin requests — Go to our [Discord server](https://discord.gg/D9uwnFnqmd) for that + - Feature requests for plugins — Go to our [Discord server](https://discord.gg/D9uwnFnqmd) for that + + - type: textarea + id: feature-description + attributes: + label: A clear and concise description of the feature you'd like to see + validations: + required: true + + - type: textarea + id: implementation-notes + attributes: + label: Implementation Notes + description: If you have ideas about how this could be implemented technically, share them here + validations: + required: false + + - type: textarea + id: additional-context + attributes: + label: Additional context + description: Screenshots, mockups, examples from other apps, etc. + validations: + required: false + + - type: checkboxes + id: agreement-check + attributes: + label: Request Agreement + options: + - label: I've searched existing issues and this hasn't been suggested before + - label: This is a **core modification feature**, not a plugin request (those go in Discord) + - label: This feature would benefit most users, not just a specific use case + - label: I am willing to work on this myself diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 000000000..868a3a862 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,9 @@ +<!-- +We do not accept PRs that were created by AI. You will be permanently blocked with no further warning if you submit AI generated PRs. +--> + +## Describe your Changes + +## 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/CONTRIBUTING.md b/CONTRIBUTING.md index 0146065e0..2e8e96838 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,11 @@ # Contributing to Vencord +> [!NOTE] +> **AI Usage Notice** +> +> Your contribution must be majority human written! Some AI assistance like inline suggestions is acceptable, but "vibecoded" contributions are not welcome. +> Also **do not** use AI to generate your pull request description, README.md or in communication. Ignoring this rule will lead to a permanent block. + Vencord is a community project and welcomes any kind of contribution from anyone! We have development documentation for new contributors, which can be found at <https://docs.vencord.dev>. @@ -10,17 +16,20 @@ All contributions should be made in accordance with our [Code of Conduct](./CODE Contributions can be sent via pull requests. If you're new to Git, check [this guide](https://opensource.com/article/19/7/create-pull-request-github). -Pull requests can be made either to the `main` or the `dev` branch. However, unless you're an advanced user, I recommend sticking to `main`. This is because the dev branch might contain unstable changes and be force pushed frequently, which could cause conflicts in your pull request. +Pull requests can be made either to the `main` or the `dev` branch. However, unless you're an advanced user, I recommend sticking to `main`. +This is because the dev branch might contain unstable changes and be force pushed frequently, which could cause conflicts in your pull request. + +Before working on a major change, I highly recommend opening a feature request for it, making sure to check "I am willing to work on this myself", +so we can discuss before you invest time. Alternatively, you can also do so in our Discord server's development +channels. This saves you a lot of time in case your feature is considered too niche or rejected for any other reason. ## Write a plugin Writing a plugin is the primary way to contribute. Before starting your plugin: +- Consider if this plugin would be useful to a large portion of the userbase. We do not accept niche plugins - Check existing pull requests to see if someone is already working on a similar plugin -- Check our [plugin requests tracker](https://github.com/Vencord/plugin-requests/issues) to see if there is an existing request, or if the same idea has been rejected -- If there isn't an existing request, [open one](https://github.com/Vencord/plugin-requests/issues/new?assignees=&labels=&projects=&template=request.yml) yourself - and include that you'd like to work on this yourself. Then wait for feedback to see if the idea even has any chance of being accepted. Or maybe others have some ideas to improve it! - Familarise yourself with our plugin rules below to ensure your plugin is not banned ### Plugin Rules From 596bfff94e3c8737f43590cf3c2379e9e607c810 Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Fri, 22 May 2026 14:48:40 -0400 Subject: [PATCH 2/3] Fix FakeProfileThemes & FriendsSince (#4235) --- src/plugins/fakeProfileThemes/index.tsx | 2 +- src/plugins/friendsSince/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/plugins/fakeProfileThemes/index.tsx b/src/plugins/fakeProfileThemes/index.tsx index 319ce2750..b1a284deb 100644 --- a/src/plugins/fakeProfileThemes/index.tsx +++ b/src/plugins/fakeProfileThemes/index.tsx @@ -207,7 +207,7 @@ export default definePlugin({ }, }, { - find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}", + find: "#{intl::USER_SETTINGS_RESET_PROFILE_THEME}),onClick:", replacement: { match: /#{intl::USER_SETTINGS_RESET_PROFILE_THEME}\).+?}\)(?=\])(?<=color:(\i),.{0,500}?color:(\i),.{0,500}?)/, replace: "$&,$self.addCopy3y3Button({primary:$1,accent:$2})" diff --git a/src/plugins/friendsSince/index.tsx b/src/plugins/friendsSince/index.tsx index 8adf143b6..3886cbff1 100644 --- a/src/plugins/friendsSince/index.tsx +++ b/src/plugins/friendsSince/index.tsx @@ -27,7 +27,7 @@ export default definePlugin({ patches: [ // DM User Sidebar { - find: "#{intl::PREMIUM_GIFTING_BUTTON}),action:", + find: "#{intl::PROVISIONAL_ACCOUNT}),headingIcon:", replacement: { match: /#{intl::USER_PROFILE_MEMBER_SINCE}\),.{0,100}userId:(\i\.id)}\)}\)/, replace: "$&,$self.FriendsSinceComponent({userId:$1,isSidebar:true})" From a50ab50cafa55bd7efbd1ea16e2dd33c2ba3176f Mon Sep 17 00:00:00 2001 From: thororen <78185467+thororen1234@users.noreply.github.com> Date: Wed, 27 May 2026 22:44:45 -0400 Subject: [PATCH 3/3] fix BetterSessions (#4249) --- .../components/RenameButton.tsx | 5 +- src/plugins/betterSessions/index.tsx | 89 +++++++++---------- src/plugins/betterSessions/styles.css | 32 +++++++ src/plugins/betterSessions/utils.ts | 2 + 4 files changed, 80 insertions(+), 48 deletions(-) diff --git a/src/plugins/betterSessions/components/RenameButton.tsx b/src/plugins/betterSessions/components/RenameButton.tsx index b20649d06..db2e9ef65 100644 --- a/src/plugins/betterSessions/components/RenameButton.tsx +++ b/src/plugins/betterSessions/components/RenameButton.tsx @@ -6,6 +6,7 @@ import { Button } from "@components/Button"; import { SessionInfo } from "@plugins/betterSessions/types"; +import { cl } from "@plugins/betterSessions/utils"; import { openModal } from "@webpack/common"; import { RenameModal } from "./RenameModal"; @@ -15,7 +16,7 @@ export function RenameButton({ session, state }: { session: SessionInfo["session <Button variant="secondary" size="xs" - className="vc-betterSessions-rename-btn" + className={cl("rename-btn")} onClick={() => openModal(props => ( <RenameModal @@ -36,7 +37,7 @@ export function NewButton() { <Button variant="dangerPrimary" size="min" - className="vc-betterSessions-new-btn" + className={cl("new-btn")} > NEW </Button> diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index bfe1f59cf..db7efdf4c 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -21,6 +21,7 @@ import "./styles.css"; import { showNotification } from "@api/Notifications"; import { definePluginSettings } from "@api/Settings"; import ErrorBoundary from "@components/ErrorBoundary"; +import { Paragraph } from "@components/Paragraph"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findComponentByCodeLazy, findCssClassesLazy, findStoreLazy } from "@webpack"; @@ -28,13 +29,10 @@ import { Constants, React, RestAPI, SettingsRouter, Tooltip } from "@webpack/com import { NewButton, RenameButton } from "./components/RenameButton"; import { Session, SessionInfo } from "./types"; -import { fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, savedSessionsCache, saveSessionsToDataStore } from "./utils"; +import { cl, fetchNamesFromDataStore, getDefaultName, GetOsColor, GetPlatformIcon, savedSessionsCache, saveSessionsToDataStore } from "./utils"; const AuthSessionsStore = findStoreLazy("AuthSessionsStore"); - const TimestampClasses = findCssClassesLazy("timestamp", "blockquoteContainer"); -const SessionIconClasses = findCssClassesLazy("sessionIcon"); - const BlobMask = findComponentByCodeLazy("!1,lowerBadgeSize:"); const settings = definePluginSettings({ @@ -61,24 +59,26 @@ export default definePlugin({ patches: [ { - find: "#{intl::AUTH_SESSIONS_SESSION_LOG_OUT}", + find: "#{intl::AUTH_SESSIONS_OS_UNKNOWN}", replacement: [ - // Replace children with a single label with state { - match: /({variant:"eyebrow",className:\i\.\i,children:).{70,110}{children:"\\xb7"}\),\(0,\i\.\i\)\("span",{children:\i\[\d+\]}\)\]}\)\]/, - replace: "$1$self.renderName(arguments[0])" + match: /(#{intl::AUTH_SESSIONS_ACTIVE_RECENTLY}.{0,230}role:"listitem",children:\[.{0,15},\{Icon:)\i/, + replace: "$1()=>$self.renderIcon(arguments[0])" }, { - match: /({variant:"text-sm\/medium",className:\i\.\i,children:.{70,110}{children:"\\xb7"}\),\(0,\i\.\i\)\("span",{children:)(\i\[\d+\])}/, - replace: "$1$self.renderTimestamp({...arguments[0],timeLabel:$2})}" + match: /("horizontal",gap:"xs",children:)\[.{0,250}"text-subtle",children:\i\}\)\]\}\),/, + replace: "$1$self.renderName(arguments[0])})," }, - // Replace the icon { - match: /(?<=Icon:(\i).{0,250}className:\i\.\i,children:\[)/, - replace: "$self.renderIcon({...arguments[0],DeviceIcon:$1}),false&&" - } + match: /("text-muted",children:)\i(?=\}\)\]\}\),.{0,120}\.client_info\?\.location)/, + replace: "$1$self.renderDescription(arguments[0])" + }, + { + match: /:\i\(\i\.approx_last_used_time\).{0,40}\(0,\i\.jsxs?\)\(\i,\{/, + replace: "$&session:arguments[0]?.session," + }, ] - } + }, ], renderName: ErrorBoundary.wrap(({ session }: SessionInfo) => { @@ -86,32 +86,43 @@ export default definePlugin({ const state = React.useState(savedSession?.name ? `${savedSession.name}*` : getDefaultName(session.client_info)); const [title, setTitle] = state; - // Show a "NEW" badge if the session is seen for the first time return ( <> - <span>{title}</span> - {(savedSession == null || savedSession.isNew) && ( - <NewButton /> - )} - <RenameButton session={session} state={state} /> + <Paragraph size="md" weight="semibold" color="text-strong">{title}</Paragraph> + <div className={cl("footer-buttons")}> + {(savedSession == null || savedSession.isNew) && ( + <NewButton /> + )} + <RenameButton session={session} state={state} /> + </div> </> ); }, { noop: true }), - renderTimestamp: ErrorBoundary.wrap(({ session, timeLabel }: { session: Session, timeLabel: string; }) => { + renderDescription: ErrorBoundary.wrap(({ session, description }: { session: Session, description: string; }) => { + const [label, timeLabel] = description.split(" \xb7 "); + return ( - <Tooltip text={session.approx_last_used_time.toLocaleString()}> - {props => ( - <span {...props} className={TimestampClasses.timestamp}> - {timeLabel} - </span> + <div className={cl("description")}> + <Paragraph size="sm" weight="normal" color="text-muted">{label}</Paragraph> + {timeLabel && ( + <> + {" \xb7 "} + <Tooltip text={session.approx_last_used_time.toLocaleString()}> + {props => ( + <span {...props} className={TimestampClasses.timestamp}> + {timeLabel} + </span> + )} + </Tooltip> + </> )} - </Tooltip> + </div> ); }, { noop: true }), - renderIcon: ErrorBoundary.wrap(({ session, DeviceIcon }: { session: Session, DeviceIcon: React.ComponentType<any>; }) => { + renderIcon: ErrorBoundary.wrap(({ session, icon: DeviceIcon }: { session: Session; icon: React.ComponentType<any>; }) => { const PlatformIcon = GetPlatformIcon(session.client_info.platform); return ( @@ -120,22 +131,8 @@ export default definePlugin({ style={{ cursor: "unset" }} selected={false} lowerBadge={ - <div - style={{ - width: "20px", - height: "20px", - - display: "flex", - justifyContent: "center", - alignItems: "center", - overflow: "hidden", - - borderRadius: "50%", - backgroundColor: "var(--interactive-icon-default)", - color: "var(--background-base-lower)", - }} - > - <PlatformIcon width={14} height={14} /> + <div className={cl("lowerBadge")}> + <PlatformIcon width={14} height={14} className={cl("lowerBadge-icon")} /> </div> } lowerBadgeSize={{ @@ -144,7 +141,7 @@ export default definePlugin({ }} > <div - className={SessionIconClasses.sessionIcon} + className={cl("icon")} style={{ backgroundColor: GetOsColor(session.client_info.os) }} > <DeviceIcon size="md" color="currentColor" /> diff --git a/src/plugins/betterSessions/styles.css b/src/plugins/betterSessions/styles.css index fafba1ed0..f590ce387 100644 --- a/src/plugins/betterSessions/styles.css +++ b/src/plugins/betterSessions/styles.css @@ -2,6 +2,8 @@ display: flex; gap: 0.5em; align-items: center; + justify-content: center; + margin-top: 2px; } .vc-betterSessions-rename-btn { @@ -14,4 +16,34 @@ translate: 0 -2px; pointer-events: none; padding: 1px 4px; +} + +.vc-betterSessions-lowerBadge { + width: 20px; + height: 20px; + display: flex; + justify-content: center; + align-items: center; + overflow: hidden; + border-radius: 50%; + background-color: var(--interactive-icon-default); + color: var(--background-base-lower); +} + +.vc-betterSessions-lowerBadge-icon { + display: block; +} + +.vc-betterSessions-icon { + align-items: center; + border-radius: var(--radius-sm); + display: flex; + height: 48px; + justify-content: center; + width: 48px +} + +.vc-betterSessions-description { + display: flex; + gap: 4px; } \ No newline at end of file diff --git a/src/plugins/betterSessions/utils.ts b/src/plugins/betterSessions/utils.ts index de9d70af2..658efc8ac 100644 --- a/src/plugins/betterSessions/utils.ts +++ b/src/plugins/betterSessions/utils.ts @@ -17,6 +17,7 @@ */ import * as DataStore from "@api/DataStore"; +import { classNameFactory } from "@utils/css"; import { UserStore } from "@webpack/common"; import { ChromeIcon, DiscordIcon, EdgeIcon, FirefoxIcon, IEIcon, MobileIcon, OperaIcon, SafariIcon, UnknownIcon } from "./components/icons"; @@ -24,6 +25,7 @@ import { SessionInfo } from "./types"; const getDataKey = () => `BetterSessions_savedSessions_${UserStore.getCurrentUser().id}`; +export const cl = classNameFactory("vc-betterSessions-"); export const savedSessionsCache: Map<string, { name: string, isNew: boolean; }> = new Map(); export function getDefaultName(clientInfo: SessionInfo["session"]["client_info"]) {