diff --git a/src/equicordplugins/voiceButtons/settings.ts b/src/equicordplugins/voiceButtons/settings.ts
index fb7a3cfcb..fb0d4a435 100644
--- a/src/equicordplugins/voiceButtons/settings.ts
+++ b/src/equicordplugins/voiceButtons/settings.ts
@@ -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;
+ },
+ }
});
diff --git a/src/equicordplugins/voiceButtons/utils.tsx b/src/equicordplugins/voiceButtons/utils.tsx
index d6982df49..3b4d60784 100644
--- a/src/equicordplugins/voiceButtons/utils.tsx
+++ b/src/equicordplugins/voiceButtons/utils.tsx
@@ -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 (
: }
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 (
: }
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"
+ );
+ }
}
}
}}
diff --git a/src/equicordplugins/voiceRejoin/index.tsx b/src/equicordplugins/voiceRejoin/index.tsx
new file mode 100644
index 000000000..ddd673a62
--- /dev/null
+++ b/src/equicordplugins/voiceRejoin/index.tsx
@@ -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);
+ },
+ },
+});
diff --git a/src/webpack/common/utils.ts b/src/webpack/common/utils.ts
index 358cb7674..63778f5cc 100644
--- a/src/webpack/common/utils.ts
+++ b/src/webpack/common/utils.ts
@@ -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");