diff --git a/.gemini/config.yaml b/.gemini/config.yaml index 5536dc86c..985bc07ae 100644 --- a/.gemini/config.yaml +++ b/.gemini/config.yaml @@ -9,7 +9,6 @@ code_review: include_drafts: false ignore_patterns: - - "pnpm-lock.yaml" - "pnpm-workspace.yaml" - "dist/**" - "browser/**" diff --git a/.gemini/styleguide.md b/.gemini/styleguide.md index 9be45b31e..85e572468 100644 --- a/.gemini/styleguide.md +++ b/.gemini/styleguide.md @@ -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) diff --git a/.github/ISSUE_TEMPLATE/blank.yml b/.github/ISSUE_TEMPLATE/blank.yml deleted file mode 100644 index 8deb782ca..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/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 diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index a31e0b88b..251a237f6 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 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] " @@ -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 diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index b608828d4..44bfe8a67 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -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 diff --git a/.github/ISSUE_TEMPLATE/developer-banner.png b/.github/ISSUE_TEMPLATE/developer-banner.png deleted file mode 100644 index 5fa12fc37..000000000 Binary files a/.github/ISSUE_TEMPLATE/developer-banner.png and /dev/null differ 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 d64831329..614c512c4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -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. diff --git a/src/plugins/betterSessions/index.tsx b/src/plugins/betterSessions/index.tsx index 5a00a75b3..db7efdf4c 100644 --- a/src/plugins/betterSessions/index.tsx +++ b/src/plugins/betterSessions/index.tsx @@ -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 (