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");