Add VoiceRejoin & Update VoiceButtons

* server mute / deafen feature yay :3

* Update settings.ts

* Add VoiceRejoin plugin for automatic voice channel rejoin

* preventReconnectIfCallEnded option to Select

* change rejoindelay

* t

* Update utils.tsx

* t

* Update utils.tsx

---------

Co-authored-by: thororen1234 <78185467+thororen1234@users.noreply.github.com>
This commit is contained in:
Eazvy 2025-11-16 15:23:57 -05:00 • committed by GitHub
parent 9cbf3819cc
commit 25e568409d
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 237 additions and 32 deletions

View file

@ -38,6 +38,18 @@ export const settings = definePluginSettings({
description: "Toggles their video upon clicking deafen button.",
restartNeeded: false,
},
useServer: {
type: OptionType.BOOLEAN,
description: "Use server mute/deafen instead of local when you have permission.",
default: false,
restartNeeded: false,
},
serverSelf: {
type: OptionType.BOOLEAN,
description: "Deafen / Mute yourself on the server when using mute/deafen.",
default: false,
restartNeeded: false,
},
showButtonsSelf: {
type: OptionType.SELECT,
default: "display",
@ -60,4 +72,15 @@ export const settings = definePluginSettings({
{ label: "Both", value: "both" },
],
}
}, {
useServer: {
disabled() {
return !this.store.showMuteButton && !this.store.showDeafenButton;
},
},
serverSelf: {
disabled() {
return !this.store.useServer && !this.store.showMuteButton && !this.store.showDeafenButton;
},
}
});

View file

@ -8,7 +8,7 @@ import "./styles.css";
import { Icon, User } from "@vencord/discord-types";
import { findComponentByCodeLazy, findStoreLazy } from "@webpack";
import { Button, ChannelStore, MediaEngineStore, NavigationRouter, Tooltip, UserStore, VoiceActions } from "@webpack/common";
import { Button, ChannelStore, GuildActions, MediaEngineStore, NavigationRouter, PermissionsBits, PermissionStore, Tooltip, UserStore, VoiceActions, VoiceStateStore } from "@webpack/common";
import { JSX } from "react";
import { settings } from "./settings";
@ -61,6 +61,29 @@ function getUserName(user: User) {
return username;
}
function canServerMuteDeafen(userId: string) {
if (!settings.store.useServer) return { canMute: false, canDeafen: false };
const voiceState = VoiceStateStore.getVoiceStateForUser(userId);
const channel = voiceState?.channelId ? ChannelStore.getChannel(voiceState.channelId) : null;
if (!channel?.guild_id) return { canMute: false, canDeafen: false };
const canMute = PermissionStore.can(PermissionsBits.MUTE_MEMBERS, channel);
const canDeafen = PermissionStore.can(PermissionsBits.DEAFEN_MEMBERS, channel);
return { canMute, canDeafen };
}
function getServerMuteDeafenState(userId: string) {
const voiceState = VoiceStateStore.getVoiceStateForUser(userId);
if (!voiceState) return { isServerMuted: false, isServerDeafened: false };
return {
isServerMuted: voiceState.mute ?? false,
isServerDeafened: voiceState.deaf ?? false
};
}
export function UserChatButton({ user }: { user: User; }) {
const isCurrent = (user.id === UserStore.getCurrentUser().id);
return (
@ -82,19 +105,42 @@ export function UserChatButton({ user }: { user: User; }) {
export function UserMuteButton({ user }: { user: User; }) {
const isCurrent = (user.id === UserStore.getCurrentUser().id);
const isMuted = (isCurrent && MediaEngineStore.isSelfMute()) || MediaEngineStore.isLocalMute(user.id);
const { canMute: canServerMute } = canServerMuteDeafen(user.id);
const { isServerMuted } = getServerMuteDeafenState(user.id);
const useServerMuteForSelf = isCurrent && settings.store.serverSelf;
const isLocalMuted = (isCurrent && MediaEngineStore.isSelfMute()) || MediaEngineStore.isLocalMute(user.id);
const isMuted = canServerMute ? isServerMuted : isLocalMuted;
const color = isMuted ? "var(--status-danger)" : "var(--channels-default)";
const muteAction = canServerMute && (useServerMuteForSelf || !isCurrent) ? "Server Mute" : "Mute";
const tooltipAction = isMuted ? (canServerMute && (useServerMuteForSelf || !isCurrent) ? "Unserver Mute" : "Unmute") : muteAction;
return (
<VoiceUserButton
user={user}
tooltip={`${isMuted ? "Unmute" : "Mute"} ${isCurrent ? "yourself" : `${getUserName(user)}`}`}
tooltip={`${tooltipAction} ${isCurrent ? "yourself" : `${getUserName(user)}`}`}
icon={isCurrent ? <MuteIconSelf muted={isMuted} size="sm" color={color} /> : <MuteIconOther muted={isMuted} size="sm" color={color} />}
onClick={() => {
if (isCurrent) {
VoiceActions.toggleSelfMute();
return;
if (canServerMute) {
if (!useServerMuteForSelf) {
VoiceActions.toggleSelfMute();
return;
}
const voiceState = VoiceStateStore.getVoiceStateForUser(user.id);
const channel = voiceState?.channelId ? ChannelStore.getChannel(voiceState.channelId) : null;
if (channel?.guild_id) {
GuildActions.setServerMute(channel.guild_id, user.id, !isServerMuted);
}
} else {
if (isCurrent) {
VoiceActions.toggleSelfMute();
} else {
VoiceActions.toggleLocalMute(user.id);
}
}
VoiceActions.toggleLocalMute(user.id);
}}
/>
);
@ -102,44 +148,68 @@ export function UserMuteButton({ user }: { user: User; }) {
export function UserDeafenButton({ user }: { user: User; }) {
const isCurrent = (user.id === UserStore.getCurrentUser().id);
const { canDeafen: canServerDeafen } = canServerMuteDeafen(user.id);
const { isServerDeafened } = getServerMuteDeafenState(user.id);
const useServerDeafenForSelf = isCurrent && settings.store.serverSelf;
const isMuted = MediaEngineStore.isLocalMute(user.id);
const isSoundboardMuted = SoundboardStore.isLocalSoundboardMuted(user.id);
const isVideoDisabled = MediaEngineStore.isLocalVideoDisabled(user.id);
const isDeafened = isCurrent && MediaEngineStore.isSelfDeaf() || isMuted && isSoundboardMuted && isVideoDisabled;
const isLocalDeafened = isCurrent && MediaEngineStore.isSelfDeaf() || isMuted && isSoundboardMuted && isVideoDisabled;
const isDeafened = canServerDeafen && (useServerDeafenForSelf || !isCurrent) ? isServerDeafened : isLocalDeafened;
const color = isDeafened ? "var(--status-danger)" : "var(--channels-default)";
const deafenAction = canServerDeafen && (useServerDeafenForSelf || !isCurrent) ? "Server Deafen" : "Deafen";
const tooltipAction = isDeafened ? (canServerDeafen && (useServerDeafenForSelf || !isCurrent) ? "Unserver Deafen" : "Undeafen") : deafenAction;
return (
<VoiceUserButton
user={user}
tooltip={`${isDeafened ? "Undeafen" : "Deafen"} ${isCurrent ? "yourself" : `${getUserName(user)}`}`}
tooltip={`${tooltipAction} ${isCurrent ? "yourself" : `${getUserName(user)}`}`}
icon={isCurrent ? <DeafenIconSelf muted={isDeafened} size="sm" color={color} /> : <DeafenIconOther muted={isDeafened} size="sm" color={color} />}
onClick={() => {
if (isCurrent) {
VoiceActions.toggleSelfDeaf();
return;
}
if (isMuted) {
VoiceActions.toggleLocalMute(user.id);
if (settings.store.muteSoundboard && isSoundboardMuted) {
VoiceActions.toggleLocalSoundboardMute(user.id);
if (canServerDeafen) {
if (!useServerDeafenForSelf) {
VoiceActions.toggleSelfDeaf();
return;
}
if (settings.store.disableVideo && isVideoDisabled) {
VoiceActions.setDisableLocalVideo(
user.id,
"ENABLED",
"default"
);
const voiceState = VoiceStateStore.getVoiceStateForUser(user.id);
const channel = voiceState?.channelId ? ChannelStore.getChannel(voiceState.channelId) : null;
if (channel?.guild_id) {
GuildActions.setServerDeaf(channel.guild_id, user.id, !isServerDeafened);
}
} else {
VoiceActions.toggleLocalMute(user.id);
if (settings.store.muteSoundboard && !isSoundboardMuted) {
VoiceActions.toggleLocalSoundboardMute(user.id);
if (isCurrent) {
VoiceActions.toggleSelfDeaf();
return;
}
if (settings.store.disableVideo && !isVideoDisabled) {
VoiceActions.setDisableLocalVideo(
user.id,
"DISABLED",
"default"
);
if (isMuted) {
VoiceActions.toggleLocalMute(user.id);
if (settings.store.muteSoundboard && isSoundboardMuted) {
VoiceActions.toggleLocalSoundboardMute(user.id);
}
if (settings.store.disableVideo && isVideoDisabled) {
VoiceActions.setDisableLocalVideo(
user.id,
"ENABLED",
"default"
);
}
} else {
VoiceActions.toggleLocalMute(user.id);
if (settings.store.muteSoundboard && !isSoundboardMuted) {
VoiceActions.toggleLocalSoundboardMute(user.id);
}
if (settings.store.disableVideo && !isVideoDisabled) {
VoiceActions.setDisableLocalVideo(
user.id,
"DISABLED",
"default"
);
}
}
}
}}

View file

@ -0,0 +1,111 @@
/*
* Vencord, a Discord client mod
* Copyright (c) 2024 Vendicated and contributors
* SPDX-License-Identifier: GPL-3.0-or-later
*/
import * as DataStore from "@api/DataStore";
import { definePluginSettings } from "@api/Settings";
import { EquicordDevs } from "@utils/constants";
import definePlugin, { makeRange, OptionType } from "@utils/types";
import { ChannelStore, FluxDispatcher, UserStore, VoiceStateStore } from "@webpack/common";
const DATASTORE_KEY = "VCLastVoiceChannel";
const DATASTORE_SESSION_KEY = "VCLastVoiceChannelSession";
const settings = definePluginSettings({
rejoinDelay: {
type: OptionType.SLIDER,
description: "Set Delay before rejoining voice channel.",
markers: makeRange(1, 10, 1),
default: 2,
stickToMarkers: true,
},
preventReconnectIfCallEnded: {
type: OptionType.SELECT,
description: "Do not reconnect if the call has ended or the voice channel is empty or does not exist.",
options: [
{ label: "None", value: "none", default: false },
{ label: "DMs only", value: "dms", default: false },
{ label: "Servers only", value: "servers", default: false },
{ label: "DMs and Servers", value: "both", default: true },
],
},
});
export default definePlugin({
name: "VoiceRejoin",
description: "Rejoins DM/Server call automatically when restarting Discord.",
authors: [EquicordDevs.omaw],
settings,
flux: {
VOICE_STATE_UPDATES({ voiceStates }: { voiceStates: any[]; }) {
const myUserId = UserStore?.getCurrentUser?.()?.id;
if (!myUserId) return;
const myState = voiceStates.find(s => s.userId === myUserId);
if (!myState) return;
if (myState.channelId) {
const saved = {
guildId: myState.guildId ?? null,
channelId: myState.channelId,
};
DataStore.set(DATASTORE_KEY, saved);
DataStore.set(DATASTORE_SESSION_KEY, true);
} else {
DataStore.set(DATASTORE_SESSION_KEY, false);
}
},
async CONNECTION_OPEN() {
const wasInVC = await DataStore.get(DATASTORE_SESSION_KEY);
if (wasInVC === false) {
DataStore.del(DATASTORE_KEY);
return;
}
setTimeout(async () => {
const saved = await DataStore.get(DATASTORE_KEY);
if (!saved?.channelId) return;
const preventionMode = settings.store.preventReconnectIfCallEnded;
if (preventionMode !== "none") {
const channel = ChannelStore.getChannel(saved.channelId);
if (!channel) {
DataStore.set(DATASTORE_SESSION_KEY, false);
return;
}
const isDM = !saved.guildId;
const shouldPrevent =
preventionMode === "both" ||
(preventionMode === "dms" && isDM) ||
(preventionMode === "servers" && !isDM);
if (shouldPrevent) {
const connectedUsers = VoiceStateStore.getVoiceStatesForChannel(saved.channelId);
const othersInCall = Object.values(connectedUsers).filter(
(vs: any) => vs.userId !== UserStore.getCurrentUser().id
);
if (othersInCall.length === 0) {
DataStore.set(DATASTORE_SESSION_KEY, false);
return;
}
}
}
FluxDispatcher.dispatch({
type: "VOICE_CHANNEL_SELECT",
guildId: saved.guildId,
channelId: saved.channelId,
});
DataStore.set(DATASTORE_SESSION_KEY, true);
}, settings.store.rejoinDelay * 1000);
},
},
});

View file

@ -180,6 +180,7 @@ export const InviteActions = findByPropsLazy("resolveInvite");
export const ChannelActionCreators = findByPropsLazy("openPrivateChannel");
export const VoiceActions = findByPropsLazy("toggleSelfMute");
export const GuildActions = findByPropsLazy("setServerMute", "setServerDeaf");
export const IconUtils: t.IconUtils = findByPropsLazy("getGuildBannerURL", "getUserAvatarURL");