From 80bdd440b42a4f360d2f4a2846f8b635ec806a60 Mon Sep 17 00:00:00 2001 From: MasterPepe <56143265+Accentaro@users.noreply.github.com> Date: Thu, 25 Dec 2025 19:03:18 +0000 Subject: [PATCH] feat(plugin): Add MicLoopbackTester * Refactor BetterUserTools to use UserAreaAPI * some changes * lol * lint * fix const * move files back * Update index.tsx * Update index.tsx * rename --------- Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com> --- package.json | 2 +- .../micLoopbackTester/index.tsx | 130 ++++++++++++++++++ src/utils/constants.ts | 4 + 3 files changed, 135 insertions(+), 1 deletion(-) create mode 100644 src/equicordplugins/micLoopbackTester/index.tsx diff --git a/package.json b/package.json index 64f2eb2e2..b1f0952e6 100644 --- a/package.json +++ b/package.json @@ -92,7 +92,7 @@ "typescript-transform-paths": "^3.5.5", "zip-local": "^0.3.5" }, - "packageManager": "pnpm@10.26.0", + "packageManager": "pnpm@10.26.1", "pnpm": { "patchedDependencies": { "@types/less@3.0.8": "patches/@types__less@3.0.8.patch" diff --git a/src/equicordplugins/micLoopbackTester/index.tsx b/src/equicordplugins/micLoopbackTester/index.tsx new file mode 100644 index 000000000..f0f249fd6 --- /dev/null +++ b/src/equicordplugins/micLoopbackTester/index.tsx @@ -0,0 +1,130 @@ +/* + * Vencord, a Discord client mod + * Copyright (c) 2025 Vendicated and contributors + * SPDX-License-Identifier: GPL-3.0-or-later + */ + +import { UserAreaButton, UserAreaButtonFactory, UserAreaRenderProps } from "@api/UserArea"; +import { EquicordDevs } from "@utils/constants"; +import definePlugin from "@utils/types"; +import { MediaEngineStore, React, UserStore, VoiceActions, VoiceStateStore } from "@webpack/common"; + +let loopbackActive = false; +let selfDeafenedByPlugin = false; + +function isInVoiceChannel() { + const id = UserStore.getCurrentUser()?.id; + if (!id) return false; + const state = VoiceStateStore.getVoiceStateForUser(id); + return Boolean(state?.channelId); +} + +async function enableLoopback() { + try { + await VoiceActions.setLoopback("mic_test", true); + loopbackActive = true; + + if (isInVoiceChannel() && !MediaEngineStore.isSelfDeaf()) { + await VoiceActions.toggleSelfDeaf(); + selfDeafenedByPlugin = true; + } else { + selfDeafenedByPlugin = false; + } + + return true; + } catch (e) { + return false; + } +} + +async function disableLoopback() { + try { + await VoiceActions.setLoopback("mic_test", false); + loopbackActive = false; + + if (selfDeafenedByPlugin && MediaEngineStore.isSelfDeaf()) { + await VoiceActions.toggleSelfDeaf(); + } + selfDeafenedByPlugin = false; + } catch (e) { } +} + +function MicLoopbackIcon({ active = false, className = "" }: { active?: boolean; className?: string; }) { + const redLinePath = "M22.7 2.7a1 1 0 0 0-1.4-1.4l-20 20a1 1 0 1 0 1.4 1.4Z"; + const maskBlackPath = "M23.27 4.73 19.27 .73 -.27 20.27 3.73 24.27Z"; + + return ( + + + + {!active && <> + + + + + + } + + ); +} + +function MicLoopbackButton({ iconForeground, hideTooltips, nameplate }: UserAreaRenderProps) { + const [, forceUpdate] = React.useReducer(x => x + 1, 0); + + const handleToggle = React.useCallback(async () => { + if (loopbackActive) { + await disableLoopback(); + } else { + await enableLoopback(); + } + forceUpdate(); + }, []); + + return ( + } + role="switch" + aria-checked={!loopbackActive} + redGlow={!loopbackActive} + plated={nameplate != null} + onClick={handleToggle} + /> + ); +} + +const MicLoopbackUserAreaButton: UserAreaButtonFactory = props => ; + +export default definePlugin({ + name: "MicLoopbackTester", + description: "Adds mic loopback test icon to the user panel", + authors: [EquicordDevs.benjii], + dependencies: ["UserSettingsAPI", "UserAreaAPI"], + userAreaButton: { + icon: MicLoopbackIcon, + render: MicLoopbackUserAreaButton + }, + + stop() { + void disableLoopback(); + loopbackActive = false; + selfDeafenedByPlugin = false; + }, +}); diff --git a/src/utils/constants.ts b/src/utils/constants.ts index 0dd5047df..8da6e4e3e 100644 --- a/src/utils/constants.ts +++ b/src/utils/constants.ts @@ -1217,6 +1217,10 @@ export const EquicordDevs = Object.freeze({ name: "soap phia", id: 1012095822957133976n }, + benjii: { + name: "Benjii", + id: 463702169443368970n + }, } satisfies Record); // iife so #__PURE__ works correctly