/* * Vencord, a Discord client mod * Copyright (c) 2024 Vendicated and contributors * SPDX-License-Identifier: GPL-3.0-or-later */ import { NavContextMenuPatchCallback } from "@api/ContextMenu"; import { definePluginSettings } from "@api/Settings"; import { makeRange } from "@components/PluginSettings/components"; import { Devs } from "@utils/constants"; import definePlugin, { OptionType } from "@utils/types"; import { findStoreLazy } from "@webpack"; import { GuildChannelStore, Menu, React, RestAPI, UserStore } from "@webpack/common"; import type { Channel } from "discord-types/general"; const VoiceStateStore = findStoreLazy("VoiceStateStore"); async function runSequential(promises: Promise[]): Promise { const results: T[] = []; for (let i = 0; i < promises.length; i++) { const promise = promises[i]; const result = await promise; results.push(result); if (i % settings.store.waitAfter === 0) { await new Promise(resolve => setTimeout(resolve, settings.store.waitSeconds * 1000)); } } return results; } function sendPatch(channel: Channel, body: Record, bypass = false) { const usersVoice = VoiceStateStore.getVoiceStatesForChannel(channel.id); // Get voice states by channel id const myId = UserStore.getCurrentUser().id; // Get my user id const promises: Promise[] = []; Object.keys(usersVoice).forEach((key, index) => { const userVoice = usersVoice[key]; if (bypass || userVoice.userId !== myId) { promises.push(RestAPI.patch({ url: `/guilds/${channel.guild_id}/members/${userVoice.userId}`, body: body })); } }); runSequential(promises).catch(error => { console.error("VoiceChatUtilities failed to run", error); }); } interface VoiceChannelContextProps { channel: Channel; } const VoiceChannelContext: NavContextMenuPatchCallback = (children, { channel }: VoiceChannelContextProps) => { // only for voice and stage channels if (!channel || (channel.type !== 2 && channel.type !== 13)) return; const userCount = Object.keys(VoiceStateStore.getVoiceStatesForChannel(channel.id)).length; if (userCount === 0) return; const guildChannels: { VOCAL: { channel: Channel, comparator: number; }[]; } = GuildChannelStore.getChannels(channel.guild_id); const voiceChannels = guildChannels.VOCAL.map(({ channel }) => channel).filter(({ id }) => id !== channel.id); children.splice( -1, 0, sendPatch(channel, { channel_id: null, })} /> sendPatch(channel, { mute: true, })} /> sendPatch(channel, { mute: false, })} /> sendPatch(channel, { deaf: true, })} /> sendPatch(channel, { deaf: false, })} /> {voiceChannels.map(voiceChannel => { return ( sendPatch(channel, { channel_id: voiceChannel.id, }, true)} /> ); })} ); }; const settings = definePluginSettings({ waitAfter: { type: OptionType.SLIDER, description: "Amount of API actions to perform before waiting (to avoid rate limits)", default: 5, markers: makeRange(1, 20), }, waitSeconds: { type: OptionType.SLIDER, description: "Time to wait between each action (in seconds)", default: 2, markers: makeRange(1, 10, .5), } }); export default definePlugin({ name: "VoiceChatUtilities", description: "This plugin allows you to perform multiple actions on an entire channel (move, mute, disconnect, etc.) (originally by dutake)", authors: [Devs.D3SOX], settings, contextMenus: { "channel-context": VoiceChannelContext }, });