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>
This commit is contained in:
MasterPepe 2025-12-25 19:03:18 +00:00 • committed by GitHub
parent 050dd1cdcc
commit 80bdd440b4
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 135 additions and 1 deletions

View file

@ -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"

View file

@ -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 (
<svg
className={className}
width="20"
height="20"
viewBox="0 0 24 24"
>
<path
fill={!active ? "var(--status-danger)" : "currentColor"}
mask={!active ? "url(#radarmask)" : void 0}
fillRule="evenodd"
clipRule="evenodd"
d="M12 3a1 1 0 0 0-1-1h-.06a1 1 0 0 0-.74.32L5.92 7H3a1 1 0 0 0-1 1v8a1 1 0 0 0 1 1h2.92l4.28 4.68a1 1 0 0 0 .74.32H11a1 1 0 0 0 1-1V3ZM15.1 20.75c-.58.14-1.1-.33-1.1-.92v-.03c0-.5.37-.92.85-1.05a7 7 0 0 0 0-13.5A1.11 1.11 0 0 1 14 4.2v-.03c0-.6.52-1.06 1.1-.92a9 9 0 0 1 0 17.5Z"
/>
<path
fill={!active ? "var(--status-danger)" : "currentColor"}
mask={!active ? "url(#radarmask)" : void 0}
fillRule="evenodd"
clipRule="evenodd"
d="M15.16 16.51c-.57.28-1.16-.2-1.16-.83v-.14c0-.43.28-.8.63-1.02a3 3 0 0 0 0-5.04c-.35-.23-.63-.6-.63-1.02v-.14c0-.63.59-1.1 1.16-.83a5 5 0 0 1 0 9.02Z"
/>
{!active && <>
<path fill="var(--status-danger)" d={redLinePath} />
<mask id="radarmask">
<rect fill="white" x="0" y="0" width="24" height="24" />
<path fill="black" d={maskBlackPath} />
</mask>
</>}
</svg>
);
}
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 (
<UserAreaButton
tooltipText={hideTooltips ? void 0 : "Mic Test Loopback"}
icon={<MicLoopbackIcon active={loopbackActive} className={iconForeground} />}
role="switch"
aria-checked={!loopbackActive}
redGlow={!loopbackActive}
plated={nameplate != null}
onClick={handleToggle}
/>
);
}
const MicLoopbackUserAreaButton: UserAreaButtonFactory = props => <MicLoopbackButton {...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;
},
});

View file

@ -1217,6 +1217,10 @@ export const EquicordDevs = Object.freeze({
name: "soap phia",
id: 1012095822957133976n
},
benjii: {
name: "Benjii",
id: 463702169443368970n
},
} satisfies Record<string, Dev>);
// iife so #__PURE__ works correctly