Merge remote-tracking branch 'upstream/dev' into dev

This commit is contained in:
thororen1234 2026-05-27 23:12:05 -04:00
commit 002b733f0f
No known key found for this signature in database
10 changed files with 132 additions and 94 deletions

View file

@ -9,7 +9,6 @@ code_review:
include_drafts: false
ignore_patterns:
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml"
- "dist/**"
- "browser/**"

View file

@ -10,7 +10,7 @@ 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.** Always use patches and React.
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.)
@ -37,26 +37,13 @@ export default definePlugin({
});
```
**Settings** must use `definePluginSettings` from `@api/Settings`. Reject inline `options` objects.
```typescript
// GOOD
const settings = definePluginSettings({
myOption: { type: OptionType.BOOLEAN, description: "Enables the feature.", default: true }
});
export default definePlugin({ settings, /* ... */ });
// BAD
export default definePlugin({ options: { myOption: { /* ... */ } } });
```
**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
- `managedStyle` for CSS not manual enableStyle/disableStyle in start/stop
**Reject deprecated fields:** `renderChatBarButton`, `renderMessagePopoverButton`, `options`
@ -68,34 +55,33 @@ export default definePlugin({ options: { myOption: { /* ... */ } } });
Flag these and suggest the fix:
| Bad | Good | Reason |
|-----|------|--------|
| `value !== null && value !== undefined` | `value` or `isNonNullish(value)` | 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 |
| Unused imports | Remove them | Cleanliness |
| 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 |
---
@ -140,7 +126,7 @@ Note: ESLint already enforces `prefer-const`, `prefer-destructuring`, `eqeqeq`,
**Conditional rendering:** return `null`, never `undefined` or bare `return;`
**ErrorBoundary:** wrap complex components: `ErrorBoundary.wrap(MyComponent, { noop: true })`
**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:
@ -187,8 +173,7 @@ className={classes(cl("wrapper"), someDiscordClass, isActive && cl("active"))}
## Settings
- `settings.store.key` — reactive, auto-persists (React components)
- `settings.plain.key` — non-reactive raw value (performance-critical code)
- **Never** `settings.use()` with arrays in variables. Mutate then reassign instead.
- `settings.plain.key` — non-reactive raw value (basically never use this, only for special cases)
---
@ -199,10 +184,10 @@ 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).
- **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`.
- **Capture groups only when reused** in replace via `$1`, `$2`.
- **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
@ -264,8 +249,8 @@ showToast("Module - not found");
- `Map`/`Set` for frequent lookups, not arrays
- `.find()` / `.some()` not `.filter()[0]`
- No spread `...` in loops
- `Promise.all()` for parallel async
- no local scope webpack `find*` functions (including start or stop methods), always find*Lazy on top level
---
@ -277,7 +262,6 @@ Plugins that don't use patches don't require a Discord restart to apply. Their `
- Event listeners added but never removed (FluxDispatcher, DOM events, MessageEvents)
- Intervals/timeouts set but never cleared
- MutationObservers created but never disconnected
- 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)

View file

@ -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/Equicord/Equicord/blob/main/.github/ISSUE_TEMPLATE/developer-banner.png?raw=true)
GitHub Issues are for development, not support! Please use our [support server](https://equicord.org/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

View file

@ -1,5 +1,5 @@
name: Bug/Crash Report
description: Create a bug or crash report for Equicord. 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 Equicord.
labels: [bug]
title: "[Bug] <title>"
@ -7,9 +7,16 @@ body:
- type: markdown
attributes:
value: |
![Are you a developer? No? This form is not for you!](https://github.com/Equicord/Equicord/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://equicord.org/discord) unless you are a Equicord/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://equicord.org/discord) for support:
- Equicord doesn't work or crashes
- A plugin doesn't work anymore
- You are having issues installing Equicord
- You want to ask a question about Equicord 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 Equicord/Vencord Developer
required: true
- label: I installed Equicord via an official method (download from equicord.org 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

View file

@ -1,8 +1,11 @@
blank_issues_enabled: false
contact_links:
- name: Equicord Support Server
- name: 💬 Discord Community
url: https://equicord.org/discord
about: If you need help regarding Equicord, please join our support server!
- name: Equicord Installer
url: https://github.com/Equicord/Equilotl
about: You can find the Equicord Installer here
about: For plugin requests, support questions, and general help - join our Discord server
- name: 🔌 Plugin Requests
url: https://equicord.org/discord
about: Want a new plugin? Request it in our Discord community, not here
- name: ❓ Support & Questions
url: https://equicord.org/discord
about: Need help? Ask in our Discord server for faster responses

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View file

@ -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

9
.github/pull_request_template.md vendored Normal file
View file

@ -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

View file

@ -1,5 +1,11 @@
# Introduction
> [!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.
Equicord 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.equicord.org>.
@ -11,8 +17,12 @@ We have development documentation for new contributors, which can be found at <h
Contributions are submitted through pull requests. If you are new to Git or GitHub, we recommend reading [this guide](https://opensource.com/article/19/7/create-pull-request-github).
Pull requests can target either the `main` or `dev` branch.
Unless you are experienced and know what you are doing, **always target `main`**. The `dev` branch may contain unstable changes and can be force-pushed frequently, which may break 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.
## Writing a Plugin
@ -20,6 +30,10 @@ Developing 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
- Familarise yourself with our plugin rules below to ensure your plugin is not banned
- Join our Discord server.
- Check existing pull requests to avoid duplicate work.
- Check the [plugin requests tracker](https://discord.com/channels/1173279886065029291/1419347113745059961) to see if your idea already exists or was rejected.
@ -33,6 +47,7 @@ Before starting your plugin:
## Plugin Rules
To keep Equicord stable, secure and maintainable, all plugins must follow these rules:
1. No simple slash-command plugins (e.g. `/cat`). If applicable, create 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 — always use proper patches and React.

View file

@ -62,8 +62,8 @@ export default definePlugin({
find: "#{intl::AUTH_SESSIONS_OS_UNKNOWN}",
replacement: [
{
match: /(?<=#{intl::AUTH_SESSIONS_ACTIVE_RECENTLY}.{0,230}role:"listitem",children:\[.{0,15},\{Icon:)(\i)/,
replace: "()=>$self.renderIcon({...arguments[0],DeviceIcon:$1})"
match: /(#{intl::AUTH_SESSIONS_ACTIVE_RECENTLY}.{0,230}role:"listitem",children:\[.{0,15},\{Icon:)\i/,
replace: "$1()=>$self.renderIcon(arguments[0])"
},
{
match: /("horizontal",gap:"xs",children:)\[.{0,250}"text-subtle",children:\i\}\)\]\}\),/,
@ -122,7 +122,7 @@ export default definePlugin({
);
}, { 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 (