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 include_drafts: false
ignore_patterns: ignore_patterns:
- "pnpm-lock.yaml"
- "pnpm-workspace.yaml" - "pnpm-workspace.yaml"
- "dist/**" - "dist/**"
- "browser/**" - "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. 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. 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.** 4. **No FakeDeafen or FakeMute.**
5. **No StereoMic-related plugins.** 5. **No StereoMic-related plugins.**
6. **No UI-only hide/redesign plugins.** Use CSS for that. (Negotiable in rare cases.) 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: **Prefer declarative APIs** over manual registration:
- `flux: { EVENT_NAME(data) {} }` not manual FluxDispatcher subscribe - `flux: { EVENT_NAME(data) {} }` not manual FluxDispatcher subscribe
- `contextMenus: { "nav-id": fn }` not manual addContextMenuPatch - `contextMenus: { "nav-id": fn }` not manual addContextMenuPatch
- `chatBarButton: { render, icon }` not addChatBarButton - `chatBarButton: { render, icon }` not addChatBarButton
- `messagePopoverButton: { render, icon }` not addMessagePopoverButton - `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` **Reject deprecated fields:** `renderChatBarButton`, `renderMessagePopoverButton`, `options`
@ -68,34 +55,33 @@ export default definePlugin({ options: { myOption: { /* ... */ } } });
Flag these and suggest the fix: Flag these and suggest the fix:
| Bad | Good | Reason | | Bad | Good | Reason |
|-----|------|--------| | ---------------------------------------------------------------- | ------------------------------------------- | ----------------------------------- |
| `value !== null && value !== undefined` | `value` or `isNonNullish(value)` | Verbose | | `value !== null && value !== undefined` | `value` or `value != null` | Verbose |
| `array && array.length > 0` | `array.length` | Redundant check | | `array && array.length > 0` | `array?.length` | Redundant check |
| `settings?.store?.value` | `settings.store.value` | Store is always defined | | `settings?.store?.value` | `settings.store.value` | Store is always defined |
| `value \|\| defaultValue` | `value ?? defaultValue` | `\|\|` falsifies `0`, `""`, `false` | | `value \|\| defaultValue` | `value ?? defaultValue` | `\|\|` falsifies `0`, `""`, `false` |
| `` `${classA} ${classB}` `` | `classes(classA, classB)` | Handles null/false gracefully | | `` `${classA} ${classB}` `` | `classes(classA, classB)` | Handles null/false gracefully |
| `"vc-plugin-class"` hardcoded | `cl("class")` via `classNameFactory` | Consistent, typo-proof | | `"vc-plugin-class"` hardcoded | `cl("class")` via `classNameFactory` | Consistent, typo-proof |
| `console.log/warn` | Remove it | No logging in plugin code | | `console.log/warn` | Remove it | No logging in plugin code |
| `cdn.discordapp.com/avatars/...` | `IconUtils.getUserAvatarURL(user)` | Handles animated, sizing, CDN | | `cdn.discordapp.com/avatars/...` | `IconUtils.getUserAvatarURL(user)` | Handles animated, sizing, CDN |
| `cdn.discordapp.com/icons/...` | `IconUtils.getGuildIconURL(...)` | Same | | `cdn.discordapp.com/icons/...` | `IconUtils.getGuildIconURL(...)` | Same |
| `cdn.discordapp.com/banners/...` | `IconUtils.getUserBannerURL(...)` | Same | | `cdn.discordapp.com/banners/...` | `IconUtils.getUserBannerURL(...)` | Same |
| `cdn.discordapp.com/emojis/...` | `IconUtils.getEmojiURL(...)` | Same | | `cdn.discordapp.com/emojis/...` | `IconUtils.getEmojiURL(...)` | Same |
| `/api/v9/...` or `/users/@me` | `Constants.Endpoints.*` or `RestAPI` | Endpoints change | | `/api/v9/...` or `/users/@me` | `Constants.Endpoints.*` or `RestAPI` | Endpoints change |
| `@api/Styles` for classNameFactory | `@utils/css` | `@api/Styles` is deprecated | | `@api/Styles` for classNameFactory | `@utils/css` | `@api/Styles` is deprecated |
| `any` for Discord objects | Import from `@vencord/discord-types` | Type safety | | `any` for Discord objects | Import from `@vencord/discord-types` | Type safety |
| `as unknown as` casting | Find the correct type | Unsafe | | `as unknown as` casting | Find the correct type | Unsafe |
| `React.memo()` | Remove it | Not needed | | `React.memo()` | Remove it | Not needed |
| `React.cloneElement` / `React.isValidElement` / `React.Children` | Find another approach | Forbidden | | `React.cloneElement` / `React.isValidElement` / `React.Children` | Find another approach | Forbidden |
| `React.lazy(() => import(...))` | `LazyComponent` from `@utils/lazyReact` | Framework-integrated | | `React.lazy(() => import(...))` | `LazyComponent` from `@utils/lazyReact` | Framework-integrated |
| Empty `catch {}` | Handle the error (toast, fallback, rethrow) | Silent failures | | Empty `catch {}` | Handle the error (toast, fallback, rethrow) | Silent failures |
| CSS-only plugins | Must have actual logic/patches | Not allowed | | CSS-only plugins | Must have actual logic/patches | Not allowed |
| Commented-out dead code | Delete it | Git has history | | Commented-out dead code | Delete it | Git has history |
| `document.querySelector(...)` | Use webpack patches | DOM manipulation forbidden | | `document.querySelector(...)` | Use webpack patches | DOM manipulation forbidden |
| `Vencord.Plugins.plugins["X"]` | `isPluginEnabled` + direct import | Proper interop | | `Vencord.Plugins.plugins["X"]` | `isPluginEnabled` + direct import | Proper interop |
| `plugin.started` check | `isPluginEnabled(plugin.name)` | Proper interop | | `plugin.started` check | `isPluginEnabled(plugin.name)` | Proper interop |
| Unexplained magic numbers | Named constants | Readability | | Unexplained magic numbers | Named constants | Readability |
| Unused imports | Remove them | Cleanliness |
--- ---
@ -140,7 +126,7 @@ Note: ESLint already enforces `prefer-const`, `prefer-destructuring`, `eqeqeq`,
**Conditional rendering:** return `null`, never `undefined` or bare `return;` **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: **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
- `settings.store.key` — reactive, auto-persists (React components) - `settings.store.key` — reactive, auto-persists (React components)
- `settings.plain.key` — non-reactive raw value (performance-critical code) - `settings.plain.key` — non-reactive raw value (basically never use this, only for special cases)
- **Never** `settings.use()` with arrays in variables. Mutate then reassign instead.
--- ---
@ -199,10 +184,10 @@ Patches modify Discord's minified webpack modules. Stability is paramount.
- **One patch per concern.** Each replacement does one thing. - **One patch per concern.** Each replacement does one thing.
- **Surgical.** Match only what needs replacing, let `find` target the module. - **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`. - **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 generic patterns.** `/className:\i/` alone is too broad. Add stable anchors.
- **No raw intl hashes.** Use `#{intl::KEY_NAME}` not `.aA4Vce`. - **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`. - **Capture groups only when reused** in replace via `$1`, `$2` (or as replace function parameter).
- **`$&`** for append/prepend, **`$self`** for plugin method calls. - **`$&`** for append/prepend, **`$self`** for plugin method calls.
```typescript ```typescript
@ -264,8 +249,8 @@ showToast("Module - not found");
- `Map`/`Set` for frequent lookups, not arrays - `Map`/`Set` for frequent lookups, not arrays
- `.find()` / `.some()` not `.filter()[0]` - `.find()` / `.some()` not `.filter()[0]`
- No spread `...` in loops
- `Promise.all()` for parallel async - `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) - Event listeners added but never removed (FluxDispatcher, DOM events, MessageEvents)
- Intervals/timeouts set but never cleared - Intervals/timeouts set but never cleared
- MutationObservers created but never disconnected
- Context menu patches added but never removed (use declarative `contextMenus` instead) - Context menu patches added but never removed (use declarative `contextMenus` instead)
- Chat bar buttons added but never removed (use declarative `chatBarButton` instead) - Chat bar buttons added but never removed (use declarative `chatBarButton` instead)
- Styles enabled but never disabled (use `managedStyle` 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 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] labels: [bug]
title: "[Bug] <title>" title: "[Bug] <title>"
@ -7,9 +7,16 @@ body:
- type: markdown - type: markdown
attributes: attributes:
value: | 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 - type: textarea
id: bug-description id: bug-description
@ -46,7 +53,7 @@ body:
id: crash-log id: crash-log
attributes: attributes:
label: Errors 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: | value: |
``` ```
Replace this text with your crash-log. Replace this text with your crash-log.
@ -60,7 +67,8 @@ body:
label: Request Agreement 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 description: We only accept reports for bugs that happen on Discord Stable. Canary and PTB are Development branches and may be unstable
options: 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 - label: I am using Discord Stable or tried on Stable and this bug happens there as well
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: I am a Equicord/Vencord Developer - label: This is not a support request
required: true - label: I am willing to submit a fix for this myself

View file

@ -1,8 +1,11 @@
blank_issues_enabled: false blank_issues_enabled: false
contact_links: contact_links:
- name: Equicord Support Server - name: 💬 Discord Community
url: https://equicord.org/discord url: https://equicord.org/discord
about: If you need help regarding Equicord, please join our support server! about: For plugin requests, support questions, and general help - join our Discord server
- name: Equicord Installer - name: 🔌 Plugin Requests
url: https://github.com/Equicord/Equilotl url: https://equicord.org/discord
about: You can find the Equicord Installer here 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 # 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! 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>. 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). 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. 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`.
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. 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 ## Writing a Plugin
@ -20,6 +30,10 @@ Developing a plugin is the primary way to contribute.
Before starting your plugin: 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. - Join our Discord server.
- Check existing pull requests to avoid duplicate work. - 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. - 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 ## Plugin Rules
To keep Equicord stable, secure and maintainable, all plugins must follow these 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. 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). 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. 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}", find: "#{intl::AUTH_SESSIONS_OS_UNKNOWN}",
replacement: [ replacement: [
{ {
match: /(?<=#{intl::AUTH_SESSIONS_ACTIVE_RECENTLY}.{0,230}role:"listitem",children:\[.{0,15},\{Icon:)(\i)/, match: /(#{intl::AUTH_SESSIONS_ACTIVE_RECENTLY}.{0,230}role:"listitem",children:\[.{0,15},\{Icon:)\i/,
replace: "()=>$self.renderIcon({...arguments[0],DeviceIcon:$1})" replace: "$1()=>$self.renderIcon(arguments[0])"
}, },
{ {
match: /("horizontal",gap:"xs",children:)\[.{0,250}"text-subtle",children:\i\}\)\]\}\),/, match: /("horizontal",gap:"xs",children:)\[.{0,250}"text-subtle",children:\i\}\)\]\}\),/,
@ -122,7 +122,7 @@ export default definePlugin({
); );
}, { noop: true }), }, { 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); const PlatformIcon = GetPlatformIcon(session.client_info.platform);
return ( return (