2024-04-17 14:29:47 -04:00
|
|
|
/*
|
|
|
|
* Vencord, a Discord client mod
|
|
|
|
* Copyright (c) 2024 Vendicated and contributors
|
|
|
|
* SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
*/
|
|
|
|
|
|
|
|
import { NavContextMenuPatchCallback } from "@api/ContextMenu";
|
2024-06-01 14:32:22 -04:00
|
|
|
import { definePluginSettings } from "@api/Settings";
|
|
|
|
import { makeRange } from "@components/PluginSettings/components";
|
2024-04-18 19:15:01 -04:00
|
|
|
import { Devs, EquicordDevs } from "@utils/constants";
|
2024-06-01 14:32:22 -04:00
|
|
|
import definePlugin, { OptionType } from "@utils/types";
|
2024-07-20 04:16:06 -04:00
|
|
|
import { findStoreLazy } from "@webpack";
|
|
|
|
import { GuildChannelStore, Menu, React, RestAPI, UserStore } from "@webpack/common";
|
2024-04-17 14:29:47 -04:00
|
|
|
import type { Channel } from "discord-types/general";
|
|
|
|
|
2024-07-20 04:16:06 -04:00
|
|
|
const VoiceStateStore = findStoreLazy("VoiceStateStore");
|
|
|
|
|
2024-06-01 14:32:22 -04:00
|
|
|
async function runSequential<T>(promises: Promise<T>[]): Promise<T[]> {
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2024-04-17 14:29:47 -04:00
|
|
|
function sendPatch(channel: Channel, body: Record<string, any>, bypass = false) {
|
|
|
|
const usersVoice = VoiceStateStore.getVoiceStatesForChannel(channel.id); // Get voice states by channel id
|
|
|
|
const myId = UserStore.getCurrentUser().id; // Get my user id
|
|
|
|
|
2024-06-01 14:32:22 -04:00
|
|
|
const promises: Promise<any>[] = [];
|
2024-04-17 14:29:47 -04:00
|
|
|
Object.keys(usersVoice).forEach((key, index) => {
|
|
|
|
const userVoice = usersVoice[key];
|
|
|
|
|
|
|
|
if (bypass || userVoice.userId !== myId) {
|
2024-06-01 14:32:22 -04:00
|
|
|
promises.push(RestAPI.patch({
|
|
|
|
url: `/guilds/${channel.guild_id}/members/${userVoice.userId}`,
|
|
|
|
body: body
|
|
|
|
}));
|
2024-04-17 14:29:47 -04:00
|
|
|
}
|
|
|
|
});
|
2024-06-01 14:32:22 -04:00
|
|
|
|
|
|
|
runSequential(promises).catch(error => {
|
|
|
|
console.error("VoiceChatUtilities failed to run", error);
|
|
|
|
});
|
2024-04-17 14:29:47 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
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,
|
|
|
|
<Menu.MenuItem
|
|
|
|
label="Voice Tools"
|
|
|
|
key="voice-tools"
|
|
|
|
id="voice-tools"
|
|
|
|
>
|
|
|
|
<Menu.MenuItem
|
|
|
|
key="voice-tools-disconnect-all"
|
|
|
|
id="voice-tools-disconnect-all"
|
|
|
|
label="Disconnect all"
|
|
|
|
action={() => sendPatch(channel, {
|
|
|
|
channel_id: null,
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Menu.MenuItem
|
|
|
|
key="voice-tools-mute-all"
|
|
|
|
id="voice-tools-mute-all"
|
|
|
|
label="Mute all"
|
|
|
|
action={() => sendPatch(channel, {
|
|
|
|
mute: true,
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Menu.MenuItem
|
|
|
|
key="voice-tools-unmute-all"
|
|
|
|
id="voice-tools-unmute-all"
|
|
|
|
label="Unmute all"
|
|
|
|
action={() => sendPatch(channel, {
|
|
|
|
mute: false,
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Menu.MenuItem
|
|
|
|
key="voice-tools-deafen-all"
|
|
|
|
id="voice-tools-deafen-all"
|
|
|
|
label="Deafen all"
|
|
|
|
action={() => sendPatch(channel, {
|
|
|
|
deaf: true,
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Menu.MenuItem
|
|
|
|
key="voice-tools-undeafen-all"
|
|
|
|
id="voice-tools-undeafen-all"
|
|
|
|
label="Undeafen all"
|
|
|
|
action={() => sendPatch(channel, {
|
|
|
|
deaf: false,
|
|
|
|
})}
|
|
|
|
/>
|
|
|
|
|
|
|
|
<Menu.MenuItem
|
|
|
|
label="Move all"
|
|
|
|
key="voice-tools-move-all"
|
|
|
|
id="voice-tools-move-all"
|
|
|
|
>
|
|
|
|
{voiceChannels.map(voiceChannel => {
|
|
|
|
return (
|
|
|
|
<Menu.MenuItem
|
|
|
|
key={voiceChannel.id}
|
|
|
|
id={voiceChannel.id}
|
|
|
|
label={voiceChannel.name}
|
|
|
|
action={() => sendPatch(channel, {
|
|
|
|
channel_id: voiceChannel.id,
|
|
|
|
}, true)}
|
|
|
|
/>
|
|
|
|
);
|
|
|
|
})}
|
|
|
|
|
|
|
|
</Menu.MenuItem>
|
|
|
|
</Menu.MenuItem>
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2024-06-01 14:32:22 -04:00
|
|
|
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),
|
|
|
|
}
|
|
|
|
});
|
2024-04-17 14:29:47 -04:00
|
|
|
|
|
|
|
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)",
|
2024-04-18 19:15:01 -04:00
|
|
|
authors: [EquicordDevs.Dams, Devs.D3SOX],
|
2024-06-01 14:32:22 -04:00
|
|
|
settings,
|
2024-04-17 14:29:47 -04:00
|
|
|
contextMenus: {
|
|
|
|
"channel-context": VoiceChannelContext
|
|
|
|
},
|
2024-04-18 19:15:01 -04:00
|
|
|
});
|