From 55c7a1b2336fd7ed5c5b5908b3fca63ca06dc9da Mon Sep 17 00:00:00 2001 From: creations Date: Tue, 24 Mar 2026 20:33:29 -0400 Subject: [PATCH 01/25] add equibopstreamfixes (#925) * add equibopstreamfixes * .equibop * Update index.tsx * fakenitro conflict * add from fakenitro --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- .../equibopStreamFixes.equibop/index.tsx | 298 ++++++++++++++++++ 1 file changed, 298 insertions(+) create mode 100644 src/equicordplugins/equibopStreamFixes.equibop/index.tsx diff --git a/src/equicordplugins/equibopStreamFixes.equibop/index.tsx b/src/equicordplugins/equibopStreamFixes.equibop/index.tsx new file mode 100644 index 000000000..dca6d785b --- /dev/null +++ b/src/equicordplugins/equibopStreamFixes.equibop/index.tsx @@ -0,0 +1,298 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { isPluginEnabled } from "@api/PluginManager"; +import { definePluginSettings } from "@api/Settings"; +import fakeNitro from "@plugins/fakeNitro"; +import { EquicordDevs } from "@utils/constants"; +import { localStorage } from "@utils/localStorage"; +import definePlugin, { OptionType } from "@utils/types"; + +interface StreamQualityOpts { + bitrateMin?: number; + bitrateMax?: number; + bitrateTarget?: number; + capture?: { + width?: number; + height?: number; + framerate?: number; + pixelCount?: number; + }; + encode?: { + width?: number; + height?: number; + framerate?: number; + pixelCount?: number; + }; +} + +const settings = definePluginSettings({ + unlockQualityOptions: { + type: OptionType.BOOLEAN, + description: "Unlock stream quality options regardless of Nitro status", + default: true, + restartNeeded: true, + }, + removeResolutionCap: { + type: OptionType.BOOLEAN, + description: "Allow resolutions above 720p at 60fps", + default: true, + restartNeeded: true, + }, + forceEncoderSettings: { + type: OptionType.BOOLEAN, + description: "Force encoder to use configured resolution/fps", + default: true, + restartNeeded: true, + }, + preventDownscale: { + type: OptionType.BOOLEAN, + description: "Prevent Discord from downscaling stream resolution", + default: true, + restartNeeded: true, + }, + preventLqDowngrade: { + type: OptionType.BOOLEAN, + description: "Prevent LQ simulcast downgrade during bandwidth dips", + default: true, + restartNeeded: true, + }, + keyframeInterval: { + type: OptionType.NUMBER, + description: "Keyframe interval in ms (0 = encoder default, 5000 = every 5s)", + default: 5000, + restartNeeded: true, + }, + minBitrate: { + type: OptionType.NUMBER, + description: "Minimum encoder bitrate in kbps", + default: 500, + restartNeeded: true, + }, + raiseBitrateCaps: { + type: OptionType.BOOLEAN, + description: "Raise default desktop bitrate caps (600kbps target → 10Mbps, 3.5Mbps max → 40Mbps)", + default: true, + restartNeeded: true, + }, + preventFramerateReduction: { + type: OptionType.BOOLEAN, + description: "Prevent Discord from reducing stream framerate when not speaking", + default: true, + restartNeeded: true, + }, + bitsPerPixelPct: { + type: OptionType.NUMBER, + description: "Bits per pixel percentage for target bitrate (8 = 0.08 bpp, 12 = 0.12bpp)", + default: 8, + restartNeeded: false, + }, +}); + +export default definePlugin({ + name: "EquibopStreamFixes", + description: "Tries to fix stream quality on Equibop by patching Discord's encoder and quality restrictions.", + authors: [EquicordDevs.creations], + settings, + + patches: [ + { + find: "this.getDefaultGoliveQuality()", + replacement: [ + // override default stream quality with configured bitrate/resolution/fps + { + match: /(this\.goliveMaxQuality)=(this\.getDefaultGoliveQuality\(\))/, + replace: "$1=$self.patchStreamQuality($2)", + }, + // inject configured quality when stream settings change mid-stream + { + match: /setGoliveQuality\((\i)\)\{/, + replace: "setGoliveQuality($1){$1=$self.patchGoliveArgs($1);", + }, + // override encoder min/max bitrate limits + { + match: + /(\i)\.encodingVideoMinBitRate=\i\.bitrateMin,\i\.encodingVideoMaxBitRate=\i\.bitrateMax/, + replace: "$1.encodingVideoMinBitRate=$self.getMinBitrate(),$1.encodingVideoMaxBitRate=$self.getBitrateMax()", + }, + ], + }, + // unlock stream quality options for all users regardless of nitro + { + find: "canUseCustomStickersEverywhere:", + replacement: [ + { + match: /canStreamQuality:\i,/, + replace: "canStreamQuality:()=>true,", + }, + { + match: /canUseHighVideoUploadQuality:\i,/, + replace: "canUseHighVideoUploadQuality:()=>true,", + }, + ], + predicate: () => settings.store.unlockQualityOptions && !isPluginEnabled(fakeNitro.name), + }, + // remove guild premium tier restriction from stream fps options + { + find: "#{intl::STREAM_FPS_OPTION}", + replacement: { + match: /guildPremiumTier:\i\.\i\.TIER_\d,?/g, + replace: "", + }, + predicate: () => settings.store.unlockQualityOptions && !isPluginEnabled(fakeNitro.name), + }, + // allow resolutions above 720p at 60fps + { + find: ".RESOLUTION_720&&", + replacement: { + match: /\i===\i\.\i\.RESOLUTION_720&&\i!==\i\.\i\.FPS_60/, + replace: "false", + }, + predicate: () => settings.store.removeResolutionCap, + }, + // force encoder width/height/fps to configured values + { + find: "}setDesktopEncodingOptions(", + replacement: [ + { + match: /setDesktopEncodingOptions\((\i),(\i),(\i)\)\{/, + replace: "$&$1=$self.getWidth();$2=$self.getHeight();$3=$self.getFps();", + predicate: () => settings.store.forceEncoderSettings, + }, + // set keyframe interval for periodic quality refresh + { + match: /keyframeInterval=0/, + replace: "keyframeInterval=$self.getKeyframeInterval()", + }, + ], + }, + + // prevent LQ simulcast downgrade during bandwidth dips + { + find: "Attempting to downgrade to LQ simulcast", + replacement: { + match: /"LQ"===\i&&!\i&&\i&&\(/, + replace: "false&&(", + }, + predicate: () => settings.store.preventLqDowngrade, + }, + // raise default desktop bitrate caps at the source + { + find: "desktopBitrate:{", + replacement: { + match: /desktopBitrate:\{min:5e5,max:35e5,target:6e5\}/, + replace: "desktopBitrate:{min:5e5,max:4e7,target:1e7}", + }, + predicate: () => settings.store.raiseBitrateCaps, + }, + // prevent framerate reduction when not speaking + { + find: "Reduced framerate after", + replacement: { + match: /this\.framerateReductionTimeout=setTimeout/, + replace: "this.framerateReductionTimeout=void 0&&setTimeout", + }, + predicate: () => settings.store.preventFramerateReduction, + }, + ], + + getStreamConfig() { + let quality: { frameRate?: string; resolution?: string; } | undefined; + try { + const state = JSON.parse(localStorage.getItem("EquibopState") ?? "{}"); + quality = state.screenshareQuality; + } catch { } + const framerate = Number(quality?.frameRate ?? 30); + const height = Number(quality?.resolution ?? 720); + const width = Math.round(height * (16 / 9)); + const pixelCount = width * height; + + const pixelRate = pixelCount * framerate; + const bpp = (settings.store.bitsPerPixelPct ?? 8) / 100; + const bitrateTarget = Math.min(20000000, Math.max(1500000, Math.round(pixelRate * bpp)),); + const bitrateMax = Math.min(40000000, Math.max(bitrateTarget * 2, 8000000)); + + return { framerate, height, width, pixelCount, bitrateTarget, bitrateMax }; + }, + + getWidth() { + return this.getStreamConfig().width; + }, + getHeight() { + return this.getStreamConfig().height; + }, + getFps() { + return this.getStreamConfig().framerate; + }, + getBitrateMax() { + return this.getStreamConfig().bitrateMax; + }, + coerceResolution(res: { height?: number; width?: number; }) { + if (!res) return res; + const config = this.getStreamConfig(); + return { + ...res, + height: Math.max(res.height ?? 0, config.height), + width: Math.max(res.width ?? 0, config.width), + }; + }, + coerceFps(fps: number | undefined) { + const config = this.getStreamConfig(); + return typeof fps === "number" + ? Math.max(fps, config.framerate) + : config.framerate; + }, + getMinBitrate() { + return (settings.store.minBitrate ?? 500) * 1000; + }, + getKeyframeInterval() { + return settings.store.keyframeInterval ?? 5000; + }, + patchGoliveArgs(opts: StreamQualityOpts) { + const config = this.getStreamConfig(); + return { + ...opts, + bitrateTarget: config.bitrateTarget, + capture: { + ...opts.capture, + width: config.width, + height: config.height, + framerate: config.framerate, + }, + encode: { + ...opts.encode, + width: config.width, + height: config.height, + framerate: config.framerate, + pixelCount: config.pixelCount, + }, + }; + }, + patchStreamQuality(opts: StreamQualityOpts) { + const config = this.getStreamConfig(); + + Object.assign(opts, { + bitrateMin: this.getMinBitrate(), + bitrateMax: config.bitrateMax, + bitrateTarget: config.bitrateTarget, + }); + if (opts?.encode) { + Object.assign(opts.encode, { + framerate: config.framerate, + width: config.width, + height: config.height, + pixelCount: config.pixelCount, + }); + } + Object.assign((opts.capture ??= {}), { + framerate: config.framerate, + width: config.width, + height: config.height, + pixelCount: config.pixelCount, + }); + return opts; + }, +}); From 5c3c5a05f38bbbefcd0885d34c847cd3bfe0188a Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Tue, 24 Mar 2026 23:40:30 -0400 Subject: [PATCH 02/25] Fix Finds --- .../equibopStreamFixes.equibop/index.tsx | 18 ++---------------- src/webpack/common/utils.ts | 2 +- 2 files changed, 3 insertions(+), 17 deletions(-) diff --git a/src/equicordplugins/equibopStreamFixes.equibop/index.tsx b/src/equicordplugins/equibopStreamFixes.equibop/index.tsx index dca6d785b..fb9a99b3f 100644 --- a/src/equicordplugins/equibopStreamFixes.equibop/index.tsx +++ b/src/equicordplugins/equibopStreamFixes.equibop/index.tsx @@ -54,12 +54,6 @@ const settings = definePluginSettings({ default: true, restartNeeded: true, }, - preventLqDowngrade: { - type: OptionType.BOOLEAN, - description: "Prevent LQ simulcast downgrade during bandwidth dips", - default: true, - restartNeeded: true, - }, keyframeInterval: { type: OptionType.NUMBER, description: "Keyframe interval in ms (0 = encoder default, 5000 = every 5s)", @@ -134,6 +128,7 @@ export default definePlugin({ }, ], predicate: () => settings.store.unlockQualityOptions && !isPluginEnabled(fakeNitro.name), + noWarn: true, }, // remove guild premium tier restriction from stream fps options { @@ -143,6 +138,7 @@ export default definePlugin({ replace: "", }, predicate: () => settings.store.unlockQualityOptions && !isPluginEnabled(fakeNitro.name), + noWarn: true, }, // allow resolutions above 720p at 60fps { @@ -169,16 +165,6 @@ export default definePlugin({ }, ], }, - - // prevent LQ simulcast downgrade during bandwidth dips - { - find: "Attempting to downgrade to LQ simulcast", - replacement: { - match: /"LQ"===\i&&!\i&&\i&&\(/, - replace: "false&&(", - }, - predicate: () => settings.store.preventLqDowngrade, - }, // raise default desktop bitrate caps at the source { find: "desktopBitrate:{", diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts index a23668bc6..4318c89ae 100644 --- a/src/webpack/common/utils.ts +++ b/src/webpack/common/utils.ts @@ -192,7 +192,7 @@ export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getU export const ColorUtils = mapMangledModuleLazy("Invalid hex color format", { rgbToHex: filters.byCode(".toString(16).slice(1)"), - hexToRgba: filters.byCode("rgba(", "??"), + hexToRgba: filters.byCode("`rgba(${"), hexToRgb: filters.byCode(".rgb();return"), rgbToHsl: filters.byCode("saturation:", "lightness:"), mixColors: filters.byCode(".substring(1,3),16)"), From 08bac5c64d078bc7a6ab4ef3b4f1425a308305c3 Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:53:01 -0400 Subject: [PATCH 03/25] Dragify: Fix Patch --- src/equicordplugins/dragify/index.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equicordplugins/dragify/index.tsx b/src/equicordplugins/dragify/index.tsx index 183f67d1e..1fd9cdeed 100644 --- a/src/equicordplugins/dragify/index.tsx +++ b/src/equicordplugins/dragify/index.tsx @@ -166,9 +166,9 @@ export default definePlugin({ patches: [ // Voice user rows (voice channel sidebar list) { - find: "location:\"VoiceUser\"", + find: '"system:click_outside","user:escape"', replacement: { - match: /(?<=previewIsOpen:.{0,100})"data-dnd-name":(\i)\.name,/, + match: /(?<=setIsHangStatusInputFocused:.{0,150})"data-dnd-name":(\i)\.name,/, replace: "$&\"data-dragify-user\":!0,\"data-user-id\":arguments[0].user?.id,draggable:!0,onDragStart:e=>$self.onUserDragStart(e,arguments[0].user)," } }, From 07e6f929f83fee1a451b06fca81b13e82fc4541a Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 26 Mar 2026 09:34:15 -0400 Subject: [PATCH 04/25] Fix Patches --- src/equicordplugins/declutter/index.tsx | 2 +- src/equicordplugins/dragify/index.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/equicordplugins/declutter/index.tsx b/src/equicordplugins/declutter/index.tsx index dad0e2560..e816d9159 100644 --- a/src/equicordplugins/declutter/index.tsx +++ b/src/equicordplugins/declutter/index.tsx @@ -155,7 +155,7 @@ export default definePlugin({ }, { // Profile banner animation effect - find: "bannerAdjustment,noBorderRadius", + find: "bannerAdjustment,isHovering", replacement: { match: /\i=\i=>\{(?=.{0,50}\.useReducedMotion\))/, replace: "$&return null;" diff --git a/src/equicordplugins/dragify/index.tsx b/src/equicordplugins/dragify/index.tsx index 1fd9cdeed..a1105f878 100644 --- a/src/equicordplugins/dragify/index.tsx +++ b/src/equicordplugins/dragify/index.tsx @@ -168,7 +168,7 @@ export default definePlugin({ { find: '"system:click_outside","user:escape"', replacement: { - match: /(?<=setIsHangStatusInputFocused:.{0,150})"data-dnd-name":(\i)\.name,/, + match: /(?<=GuildChannelUserContextMenu.{0,100})"data-dnd-name":(\i)\.name,/, replace: "$&\"data-dragify-user\":!0,\"data-user-id\":arguments[0].user?.id,draggable:!0,onDragStart:e=>$self.onUserDragStart(e,arguments[0].user)," } }, From 0d0e749f9605e3035e0dfc13a987e64a4f47809e Mon Sep 17 00:00:00 2001 From: thororen1234 <78185467+thororen1234@users.noreply.github.com> Date: Thu, 26 Mar 2026 10:45:01 -0400 Subject: [PATCH 05/25] ProfileCollectionsAPI: Update --- src/api/ProfileCollections.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/api/ProfileCollections.tsx b/src/api/ProfileCollections.tsx index 30cd403cd..2a8a16783 100644 --- a/src/api/ProfileCollections.tsx +++ b/src/api/ProfileCollections.tsx @@ -59,7 +59,7 @@ function ProfileCollections({ props }: { props: any; }) { return Array.from(profileCollections) .map(([id, { render: Collection }]) => ( logger.error(`Failed to render profile collection: ${id}`, e.error)}> - {Collection({ ...props, user: props.user ?? props.currentUser })} + )); } From 8ad745d5cb0d37d4b04e34847c541b67dc07b685 Mon Sep 17 00:00:00 2001 From: Ape <60411288+Aperre@users.noreply.github.com> Date: Fri, 27 Mar 2026 13:50:32 +0100 Subject: [PATCH 06/25] feat(musicControls): add lyrics custom provider settings (#931) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(musicControls): add lyrics custom provider settings * 😳 --- .../musicControls/settings.tsx | 69 +++++++++++++++++-- .../musicControls/spotify/lyrics/api.tsx | 2 +- .../lyrics/providers/SpotifyAPI/index.ts | 15 +++- src/utils/constants.ts | 4 ++ 4 files changed, 81 insertions(+), 9 deletions(-) diff --git a/src/equicordplugins/musicControls/settings.tsx b/src/equicordplugins/musicControls/settings.tsx index d79fe73dd..aaebfe179 100644 --- a/src/equicordplugins/musicControls/settings.tsx +++ b/src/equicordplugins/musicControls/settings.tsx @@ -6,10 +6,12 @@ import { definePluginSettings } from "@api/Settings"; import { disableStyle, enableStyle } from "@api/Styles"; +import { ButtonCompat } from "@components/Button"; import { HeadingSecondary } from "@components/Heading"; import { Paragraph } from "@components/Paragraph"; +import { SettingsSection } from "@components/settings/tabs/plugins/components/Common"; import { makeRange, OptionType } from "@utils/types"; -import { Button, MaskedLink, showToast, Toasts } from "@webpack/common"; +import { MaskedLink, Select, showToast, TextInput, Toasts } from "@webpack/common"; import hoverOnlyStyle from "./hoverOnly.css?managed"; import { clearLyricsCache, removeTranslations } from "./spotify/lyrics/api"; @@ -36,6 +38,46 @@ function InstallInstructions() { ); } +function LyricsProviderSettings() { + const { store } = settings; + + return ( + <> + +