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 (
+
+ );
+}
+
+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