TempVC module added.

This commit is contained in:
jrmyr 2025-05-04 18:26:56 +00:00
parent ce11c1e50e
commit 10d8a0b900
3 changed files with 591 additions and 4 deletions

585
_opt/tempvc.js Normal file
View File

@ -0,0 +1,585 @@
import { SlashCommandBuilder, PermissionFlagsBits, ChannelType, EmbedBuilder } from 'discord.js';
import { MessageFlags } from 'discord-api-types/v10';
/**
* tempvc module: temporary voice channel manager
*
* Admin commands (/vcadmin):
* - add <voice_channel> <category>
* - remove <voice_channel>
* - list
*
* User commands (/vc):
* Access Control:
* invite <user>
* kick <user>
* role <role>
* mode <whitelist|blacklist>
* limit <0-99>
* Presets:
* save <name>
* restore <name>
* reset
* Utilities:
* rename <new_name>
* info
* delete
*
* PocketBase collections required:
* tempvc_masters (guildId, masterChannelId, categoryId)
* tempvc_sessions (guildId, masterChannelId, channelId, ownerId, roleId, mode)
* tempvc_presets (guildId, userId, name, channelName, userLimit, roleId, invitedUserIds, mode)
*/
// Temporary Voice Channel module
// - /vcadmin: admin commands to add/remove/list spawn channels
// - /vc: user commands to manage own temp VC, presets save/restore
/**
* Slash commands for vcadmin and vc
*/
export const commands = [
// Administrator: manage spawn points
{
data: new SlashCommandBuilder()
.setName('vcadmin')
.setDescription('Configure temporary voice-channel spawn points (Admin only)')
.setDefaultMemberPermissions(PermissionFlagsBits.Administrator)
.setDMPermission(false)
.addSubcommand(sub =>
sub.setName('add')
.setDescription('Add a spawn voice channel and its temp category')
.addChannelOption(opt =>
opt.setName('voice_channel')
.setDescription('Voice channel to spawn from')
.setRequired(true)
.addChannelTypes(ChannelType.GuildVoice)
)
.addChannelOption(opt =>
opt.setName('category')
.setDescription('Category for new temp channels')
.setRequired(true)
.addChannelTypes(ChannelType.GuildCategory)
)
)
.addSubcommand(sub =>
sub.setName('remove')
.setDescription('Remove a spawn voice channel')
.addChannelOption(opt =>
opt.setName('voice_channel')
.setDescription('Voice channel to remove')
.setRequired(true)
.addChannelTypes(ChannelType.GuildVoice)
)
)
.addSubcommand(sub =>
sub.setName('list')
.setDescription('List all spawn voice channels and categories')
),
async execute(interaction, client) {
const guildId = interaction.guildId;
const sub = interaction.options.getSubcommand();
// ensure in-guild
if (!guildId) {
return interaction.reply({ content: 'This command can only be used in a server.', flags: MessageFlags.Ephemeral });
}
// init memory map for this guild
client.tempvc = client.tempvc || { masters: new Map(), sessions: new Map() };
if (!client.tempvc.masters.has(guildId)) {
client.tempvc.masters.set(guildId, new Map());
}
const guildMasters = client.tempvc.masters.get(guildId);
try {
if (sub === 'add') {
const vc = interaction.options.getChannel('voice_channel', true);
const cat = interaction.options.getChannel('category', true);
// persist
const existing = await client.pb.getFirst(
'tempvc_masters',
`guildId = "${guildId}" && masterChannelId = "${vc.id}"`
);
if (existing) {
await client.pb.updateOne('tempvc_masters', existing.id, {
guildId, masterChannelId: vc.id, categoryId: cat.id
});
} else {
await client.pb.createOne('tempvc_masters', {
guildId, masterChannelId: vc.id, categoryId: cat.id
});
}
// update memory
guildMasters.set(vc.id, cat.id);
await interaction.reply({
content: `Spawn channel <#${vc.id}> will now create temp VCs in <#${cat.id}>.`,
flags: MessageFlags.Ephemeral
});
} else if (sub === 'remove') {
const vc = interaction.options.getChannel('voice_channel', true);
if (!guildMasters.has(vc.id)) {
return interaction.reply({ content: 'That channel is not configured as a spawn point.', flags: MessageFlags.Ephemeral });
}
// remove from PB
const existing = await client.pb.getFirst(
'tempvc_masters',
`guildId = "${guildId}" && masterChannelId = "${vc.id}"`
);
if (existing) {
await client.pb.deleteOne('tempvc_masters', existing.id);
}
// update memory
guildMasters.delete(vc.id);
await interaction.reply({ content: `Removed spawn channel <#${vc.id}>.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'list') {
if (guildMasters.size === 0) {
return interaction.reply({ content: 'No spawn channels configured.', flags: MessageFlags.Ephemeral });
}
const lines = [];
for (const [mId, cId] of guildMasters.entries()) {
lines.push(`<#${mId}> → <#${cId}>`);
}
await interaction.reply({ content: '**Spawn channels:**\n' + lines.join('\n'), flags: MessageFlags.Ephemeral });
}
} catch (err) {
client.logger.error(`[module:tempvc][vcadmin] ${err.message}`);
await interaction.reply({ content: 'Operation failed, see logs.', flags: MessageFlags.Ephemeral });
}
}
},
// User: manage own temp VC and presets
{
data: new SlashCommandBuilder()
.setName('vc')
.setDescription('Manage your temporary voice channel')
.setDMPermission(false)
// Access Control
.addSubcommand(sub =>
sub.setName('invite')
.setDescription('Invite a user to this channel')
.addUserOption(opt => opt.setName('user').setDescription('User to invite').setRequired(true))
)
.addSubcommand(sub =>
sub.setName('kick')
.setDescription('Kick a user from this channel')
.addUserOption(opt => opt.setName('user').setDescription('User to kick').setRequired(true))
)
.addSubcommand(sub =>
sub.setName('role')
.setDescription('Set role to allow/deny access')
.addRoleOption(opt => opt.setName('role').setDescription('Role to allow/deny').setRequired(true))
)
.addSubcommand(sub =>
sub.setName('mode')
.setDescription('Switch role mode')
.addStringOption(opt =>
opt.setName('mode')
.setDescription('Mode: whitelist or blacklist')
.setRequired(true)
.addChoices(
{ name: 'whitelist', value: 'whitelist' },
{ name: 'blacklist', value: 'blacklist' }
)
)
)
.addSubcommand(sub =>
sub.setName('limit')
.setDescription('Set user limit (099)')
.addIntegerOption(opt => opt.setName('number').setDescription('Max users').setRequired(true))
)
// Presets
.addSubcommand(sub =>
sub.setName('save')
.setDescription('Save current settings as a preset')
.addStringOption(opt => opt.setName('name').setDescription('Preset name').setRequired(true).setAutocomplete(true))
)
.addSubcommand(sub =>
sub.setName('restore')
.setDescription('Restore settings from a preset')
.addStringOption(opt => opt.setName('name').setDescription('Preset name').setRequired(true).setAutocomplete(true))
)
.addSubcommand(sub => sub.setName('reset').setDescription('Reset channel to default settings'))
// Utilities
.addSubcommand(sub => sub.setName('rename').setDescription('Rename this channel').addStringOption(opt => opt.setName('new_name').setDescription('New channel name').setRequired(true)))
.addSubcommand(sub => sub.setName('info').setDescription('Show channel info'))
.addSubcommand(sub => sub.setName('delete').setDescription('Delete this channel')),
async execute(interaction, client) {
const guild = interaction.guild;
const member = interaction.member;
const sub = interaction.options.getSubcommand();
// must be in guild and in voice
if (!guild || !member || !member.voice.channel) {
return interaction.reply({ content: 'You must be in a temp voice channel to use this.', flags: MessageFlags.Ephemeral });
}
const voice = member.voice.channel;
client.tempvc = client.tempvc || { masters: new Map(), sessions: new Map() };
const sess = client.tempvc.sessions.get(voice.id);
if (!sess) {
return interaction.reply({ content: 'This is not one of my temporary channels.', flags: MessageFlags.Ephemeral });
}
if (sess.ownerId !== interaction.user.id) {
return interaction.reply({ content: 'Only the room owner can do that.', flags: MessageFlags.Ephemeral });
}
try {
if (sub === 'rename') {
const name = interaction.options.getString('new_name', true);
await voice.setName(name);
await interaction.reply({ content: `Channel renamed to **${name}**.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'invite') {
const u = interaction.options.getUser('user', true);
await voice.permissionOverwrites.edit(u.id, { Connect: true });
await interaction.reply({ content: `Invited <@${u.id}>.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'kick') {
const u = interaction.options.getUser('user', true);
const gm = await guild.members.fetch(u.id);
// move them out if in this channel
if (gm.voice.channelId === voice.id) {
await gm.voice.setChannel(null);
}
// remove any previous invite allow
try {
await voice.permissionOverwrites.delete(u.id);
} catch {}
await interaction.reply({ content: `Kicked <@${u.id}>.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'limit') {
let num = interaction.options.getInteger('number', true);
// enforce range 0-99
if (num < 0 || num > 99) {
return interaction.reply({ content: 'User limit must be between 0 (no limit) and 99.', flags: MessageFlags.Ephemeral });
}
await voice.setUserLimit(num);
await interaction.reply({ content: `User limit set to ${num}.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'role') {
const newRole = interaction.options.getRole('role', true);
const oldRoleId = sess.roleId;
// remove old role overwrite if any
if (oldRoleId && oldRoleId !== guild.roles.everyone.id) {
await voice.permissionOverwrites.delete(oldRoleId).catch(() => {});
}
// selecting @everyone resets all
if (newRole.id === guild.roles.everyone.id) {
// clear all overwrites
await voice.permissionOverwrites.set([
{ id: guild.roles.everyone.id, allow: [PermissionFlagsBits.Connect] },
{ id: sess.ownerId, allow: [PermissionFlagsBits.Connect, PermissionFlagsBits.MoveMembers, PermissionFlagsBits.ManageChannels] }
]);
sess.roleId = '';
await client.pb.updateOne('tempvc_sessions', sess.pbId, { roleId: '', mode: sess.mode });
return interaction.reply({ content: '@everyone can now connect.', flags: MessageFlags.Ephemeral });
}
if (sess.mode === 'whitelist') {
// whitelist: lock everyone, allow role
await voice.permissionOverwrites.edit(guild.roles.everyone.id, { Connect: false });
await voice.permissionOverwrites.edit(newRole.id, { Connect: true });
sess.roleId = newRole.id;
await client.pb.updateOne('tempvc_sessions', sess.pbId, { roleId: newRole.id, mode: sess.mode });
await interaction.reply({ content: `Whitelisted role <@&${newRole.id}>.`, flags: MessageFlags.Ephemeral });
} else {
// blacklist: allow everyone, deny role
await voice.permissionOverwrites.edit(guild.roles.everyone.id, { Connect: true });
await voice.permissionOverwrites.edit(newRole.id, { Connect: false });
sess.roleId = newRole.id;
await client.pb.updateOne('tempvc_sessions', sess.pbId, { roleId: newRole.id, mode: sess.mode });
await interaction.reply({ content: `Blacklisted role <@&${newRole.id}>.`, flags: MessageFlags.Ephemeral });
}
} else if (sub === 'delete') {
await interaction.reply({ content: 'Deleting your channel...', flags: MessageFlags.Ephemeral });
await client.pb.deleteOne('tempvc_sessions', sess.pbId);
client.tempvc.sessions.delete(voice.id);
await voice.delete('Owner deleted temp VC');
} else if (sub === 'info') {
const invites = voice.permissionOverwrites.cache
.filter(po => po.allow.has(PermissionFlagsBits.Connect) && ![guild.roles.everyone.id, sess.roleId].includes(po.id))
.map(po => `<@${po.id}>`);
const everyoneId = guild.roles.everyone.id;
const roleLine = (!sess.roleId || sess.roleId === everyoneId)
? '@everyone'
: `<@&${sess.roleId}>`;
const modeLine = sess.mode || 'whitelist';
const lines = [
`Owner: <@${sess.ownerId}>`,
`Name: ${voice.name}`,
`Role: ${roleLine} (${modeLine})`,
`User limit: ${voice.userLimit}`,
`Invites: ${invites.length ? invites.join(', ') : 'none'}`
];
await interaction.reply({ content: lines.join('\n'), flags: MessageFlags.Ephemeral });
} else if (sub === 'save') {
const name = interaction.options.getString('name', true);
// gather invites
const invited = voice.permissionOverwrites.cache
.filter(po => po.allow.has(PermissionFlagsBits.Connect) && ![guild.roles.everyone.id, sess.roleId].includes(po.id))
.map(po => po.id);
// upsert preset
const existing = await client.pb.getFirst(
'tempvc_presets',
`guildId = "${guild.id}" && userId = "${interaction.user.id}" && name = "${name}"`
);
const data = {
guildId: guild.id,
userId: interaction.user.id,
name,
channelName: voice.name,
userLimit: voice.userLimit,
roleId: sess.roleId || '',
invitedUserIds: invited,
mode: sess.mode || 'whitelist'
};
if (existing) {
await client.pb.updateOne('tempvc_presets', existing.id, data);
} else {
await client.pb.createOne('tempvc_presets', data);
}
await interaction.reply({ content: `Preset **${name}** saved.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'reset') {
// reset channel to default parameters
const owner = interaction.member;
const display = owner.displayName || owner.user.username;
const defaultName = `TempVC: ${display}`;
await voice.setName(defaultName);
await voice.setUserLimit(0);
// clear all overwrites except owner, default allow @everyone
await voice.permissionOverwrites.set([
{ id: guild.roles.everyone.id, allow: [PermissionFlagsBits.Connect] },
{ id: sess.ownerId, allow: [PermissionFlagsBits.Connect, PermissionFlagsBits.MoveMembers, PermissionFlagsBits.ManageChannels] }
]);
sess.roleId = guild.roles.everyone.id;
await client.pb.updateOne('tempvc_sessions', sess.pbId, { roleId: guild.roles.everyone.id, invitedUserIds: [] });
await interaction.reply({ content: 'Channel has been reset to default settings.', flags: MessageFlags.Ephemeral });
} else if (sub === 'mode') {
const mode = interaction.options.getString('mode', true);
sess.mode = mode;
// apply mode overwrites
if (mode === 'whitelist') {
// only allow whitelisted role + owner + invites
await voice.permissionOverwrites.edit(guild.roles.everyone.id, { Connect: false });
if (sess.roleId) await voice.permissionOverwrites.edit(sess.roleId, { Connect: true });
} else {
// blacklist: allow everyone, then deny the specified role
await voice.permissionOverwrites.edit(guild.roles.everyone.id, { Connect: true });
if (sess.roleId) await voice.permissionOverwrites.edit(sess.roleId, { Connect: false });
}
// persist mode
await client.pb.updateOne('tempvc_sessions', sess.pbId, { mode });
await interaction.reply({ content: `Channel mode set to **${mode}**.`, flags: MessageFlags.Ephemeral });
} else if (sub === 'restore') {
const name = interaction.options.getString('name', true);
const preset = await client.pb.getFirst(
'tempvc_presets',
`guildId = "${guild.id}" && userId = "${interaction.user.id}" && name = "${name}"`
);
if (!preset) {
return interaction.reply({ content: `Preset **${name}** not found.`, flags: MessageFlags.Ephemeral });
}
// apply settings
await voice.setName(preset.channelName);
await voice.setUserLimit(preset.userLimit);
// apply mode-based permissions
const mode = preset.mode || 'whitelist';
sess.mode = mode;
// clear existing overwrites for everyone and role
await voice.permissionOverwrites.edit(guild.roles.everyone.id, { Connect: mode === 'blacklist' });
if (preset.roleId) {
await voice.permissionOverwrites.edit(preset.roleId, { Connect: mode === 'whitelist' ? true : false });
}
// invite users explicitly
for (const uid of preset.invitedUserIds || []) {
await voice.permissionOverwrites.edit(uid, { Connect: true }).catch(() => {});
}
// persist session changes
await client.pb.updateOne(
'tempvc_sessions',
sess.pbId,
{ roleId: preset.roleId || '', mode }
);
sess.roleId = preset.roleId || '';
await interaction.reply({ content: `Preset **${name}** restored (mode: ${mode}).`, flags: MessageFlags.Ephemeral });
}
} catch (err) {
client.logger.error(`[module:tempvc][vc] ${err.message}`);
await interaction.reply({ content: 'Operation failed, see logs.', flags: MessageFlags.Ephemeral });
}
}
}
];
/**
* Initialize module: load PB state and hook events
*/
export async function init(client) {
// tempvc state: masters per guild, sessions map
client.tempvc = { masters: new Map(), sessions: new Map() };
// hook voice state updates
client.on('voiceStateUpdate', async (oldState, newState) => {
client.logger.debug(
`[module:tempvc] voiceStateUpdate: user=${newState.id} oldChannel=${oldState.channelId} newChannel=${newState.channelId}`
);
// cleanup on leave
if (oldState.channelId && oldState.channelId !== newState.channelId) {
const sess = client.tempvc.sessions.get(oldState.channelId);
const ch = oldState.guild.channels.cache.get(oldState.channelId);
if (sess && (!ch || ch.members.size === 0)) {
await client.pb.deleteOne('tempvc_sessions', sess.pbId).catch(()=>{});
client.tempvc.sessions.delete(oldState.channelId);
await ch?.delete('Empty temp VC cleanup').catch(()=>{});
}
}
// spawn on join
if (newState.channelId && newState.channelId !== oldState.channelId) {
const masters = client.tempvc.masters.get(newState.guild.id) || new Map();
client.logger.debug(
`[module:tempvc] Guild ${newState.guild.id} masters: ${[...masters.keys()].join(',')}`
);
client.logger.debug(
`[module:tempvc] Checking spawn for channel ${newState.channelId}: ${masters.has(newState.channelId)}`
);
if (masters.has(newState.channelId)) {
const catId = masters.get(newState.channelId);
const owner = newState.member;
const guild = newState.guild;
// default channel name
const displayName = owner.displayName || owner.user.username;
const name = `TempVC: ${displayName}`;
// create channel
// create voice channel, default permissions inherited from category (allow everyone)
const ch = await guild.channels.create({
name,
type: ChannelType.GuildVoice,
parent: catId,
permissionOverwrites: [
{ id: owner.id, allow: [PermissionFlagsBits.Connect, PermissionFlagsBits.MoveMembers, PermissionFlagsBits.ManageChannels] }
]
});
// move member
await owner.voice.setChannel(ch);
// persist session
const rec = await client.pb.createOne('tempvc_sessions', {
guildId: guild.id,
masterChannelId: newState.channelId,
channelId: ch.id,
ownerId: owner.id,
roleId: guild.roles.everyone.id,
mode: 'whitelist'
});
client.tempvc.sessions.set(ch.id, {
pbId: rec.id,
guildId: guild.id,
masterChannelId: newState.channelId,
ownerId: owner.id,
roleId: guild.roles.everyone.id,
mode: 'whitelist'
});
// send instructions to the voice channel itself
try {
const helpEmbed = new EmbedBuilder()
.setTitle('👋 Welcome to Your Temporary Voice Channel!')
.setColor('Blue')
.addFields(
{
name: 'Access Control',
value:
'• /vc invite <user> — Invite a user to this channel\n' +
'• /vc kick <user> — Kick a user from this channel\n' +
'• /vc role <role> — Set a role to allow/deny access\n' +
'• /vc mode <whitelist|blacklist> — Switch role mode\n' +
'• /vc limit <number> — Set user limit (099)',
},
{
name: 'Presets',
value:
'• /vc save <name> — Save current settings as a preset\n' +
'• /vc restore <name> — Restore settings from a preset\n' +
'• /vc reset — Reset channel to default settings',
},
{
name: 'Utilities',
value:
'• /vc rename <new_name> — Rename this channel\n' +
'• /vc info — Show channel info\n' +
'• /vc delete — Delete this channel',
}
);
await ch.send({ embeds: [helpEmbed] });
} catch (err) {
client.logger.error(`[module:tempvc] Error sending help message: ${err.message}`);
}
}
}
});
// autocomplete for /vc save & restore presets
client.on('interactionCreate', async interaction => {
if (!interaction.isAutocomplete() || interaction.commandName !== 'vc') return;
const sub = interaction.options.getSubcommand(false);
if (!['save', 'restore'].includes(sub)) return;
const focused = interaction.options.getFocused(true);
if (focused.name !== 'name') return;
const guildId = interaction.guildId;
const userId = interaction.user.id;
try {
const recs = await client.pb.getAll('tempvc_presets', {
filter: `guildId = "${guildId}" && userId = "${userId}"`
});
const choices = recs
.filter(r => r.name.toLowerCase().startsWith(focused.value.toLowerCase()))
.slice(0, 25)
.map(r => ({ name: r.name, value: r.name }));
await interaction.respond(choices);
} catch (err) {
client.logger.error(`[module:tempvc][autocomplete] ${err.message}`);
await interaction.respond([]);
}
});
// On ready: load masters/sessions, then check required permissions
client.on('ready', async () => {
// Load persistent spawn masters and active sessions
for (const guild of client.guilds.cache.values()) {
const gid = guild.id;
try {
const masters = await client.pb.getAll('tempvc_masters', { filter: `guildId = "${gid}"` }); // guildId = "X" works, but escaped quotes are allowed
const gm = new Map();
for (const rec of masters) gm.set(rec.masterChannelId, rec.categoryId);
client.tempvc.masters.set(gid, gm);
client.logger.info(`[module:tempvc] Loaded spawn masters for guild ${gid}: ${[...gm.keys()].join(', ')}`);
} catch (err) {
client.logger.error(`[module:tempvc] Error loading masters for guild ${gid}: ${err.message}`);
}
try {
const sessions = await client.pb.getAll('tempvc_sessions', { filter: `guildId = "${gid}"` });
for (const rec of sessions) {
const ch = guild.channels.cache.get(rec.channelId);
if (ch && ch.isVoiceBased()) {
client.tempvc.sessions.set(rec.channelId, {
pbId: rec.id,
guildId: gid,
masterChannelId: rec.masterChannelId,
ownerId: rec.ownerId,
roleId: rec.roleId || '',
mode: rec.mode || 'whitelist'
});
if (rec.roleId) await ch.permissionOverwrites.edit(rec.roleId, { Connect: true }).catch(()=>{});
await ch.permissionOverwrites.edit(rec.ownerId, { Connect: true, ManageChannels: true, MoveMembers: true });
} else {
await client.pb.deleteOne('tempvc_sessions', rec.id).catch(()=>{});
}
}
} catch (err) {
client.logger.error(`[module:tempvc] Error loading sessions for guild ${gid}: ${err.message}`);
}
}
// Verify necessary permissions
for (const guild of client.guilds.cache.values()) {
// get bot's member in this guild
let me = guild.members.me;
if (!me) {
try { me = await guild.members.fetch(client.user.id); } catch { /* ignore */ }
}
if (!me) continue;
const missing = [];
if (!me.permissions.has(PermissionFlagsBits.ManageChannels)) missing.push('ManageChannels');
if (!me.permissions.has(PermissionFlagsBits.MoveMembers)) missing.push('MoveMembers');
if (missing.length) {
client.logger.warn(
`[module:tempvc] Missing permissions in guild ${guild.id} (${guild.name}): ${missing.join(', ')}`
);
}
}
});
client.logger.info('[module:tempvc] Module initialized');
}

View File

@ -72,7 +72,8 @@ export default {
'gitUtils', 'gitUtils',
'responses', 'responses',
'responsesPrompt', 'responsesPrompt',
'responsesQuery' 'responsesQuery',
'tempvc'
] ]
}, },
@ -199,7 +200,7 @@ export default {
'botUtils', 'botUtils',
'pbUtils', 'pbUtils',
'gitUtils', 'gitUtils',
'condimentX', //'condimentX',
'responses', 'responses',
'responsesPrompt', 'responsesPrompt',
'responsesQuery', 'responsesQuery',

View File

@ -10,12 +10,13 @@ import { ansi, wrapAnsi } from './_src/ansiColors.js';
const initializeClient = async (clientConfig) => { const initializeClient = async (clientConfig) => {
// Create Discord client with intents // Create Discord client with intents
const client = new Client({ const client = new Client({
// Include GuildMembers intent to allow fetching all guild members // Include GuildVoiceStates and GuildMembers intents to track voice channel events
intents: [ intents: [
GatewayIntentBits.Guilds, GatewayIntentBits.Guilds,
GatewayIntentBits.GuildMessages, GatewayIntentBits.GuildMessages,
GatewayIntentBits.MessageContent, GatewayIntentBits.MessageContent,
GatewayIntentBits.GuildMembers GatewayIntentBits.GuildMembers,
GatewayIntentBits.GuildVoiceStates
] ]
}); });