aboutsummaryrefslogtreecommitdiff
path: root/projects/discord-bot/source
diff options
context:
space:
mode:
authorAlex Pooley (@zuedev) <zuedev@gmail.com>2026-05-11 16:22:33 +0100
committerAlex Pooley (@zuedev) <zuedev@gmail.com>2026-05-11 16:22:33 +0100
commitd3eeaf9a213f0be2376573b97b3f8e75a9ab142b (patch)
tree47560e6b3f11f9856b24419fb4db1d49a756bb55 /projects/discord-bot/source
parentd4e548543c976e3de5c24eef8d5f26c22aa28457 (diff)
downloadunnamed-group-d3eeaf9a213f0be2376573b97b3f8e75a9ab142b.tar
unnamed-group-d3eeaf9a213f0be2376573b97b3f8e75a9ab142b.tar.gz
unnamed-group-d3eeaf9a213f0be2376573b97b3f8e75a9ab142b.tar.bz2
unnamed-group-d3eeaf9a213f0be2376573b97b3f8e75a9ab142b.tar.xz
unnamed-group-d3eeaf9a213f0be2376573b97b3f8e75a9ab142b.zip
rename
Diffstat (limited to 'projects/discord-bot/source')
-rw-r--r--projects/discord-bot/source/main.js201
1 files changed, 201 insertions, 0 deletions
diff --git a/projects/discord-bot/source/main.js b/projects/discord-bot/source/main.js
new file mode 100644
index 0000000..3a66ea2
--- /dev/null
+++ b/projects/discord-bot/source/main.js
@@ -0,0 +1,201 @@
+import {
+ Client,
+ GatewayIntentBits,
+ Partials,
+ ActivityType,
+ Events,
+ PermissionFlagsBits,
+ ChannelType,
+ Routes,
+} from "discord.js";
+
+const discord = new Client({
+ intents: Object.values(GatewayIntentBits),
+ partials: [Partials.Message],
+ presence: {
+ activities: [
+ {
+ type: ActivityType.Watching,
+ name: "my boot sequence",
+ },
+ ],
+ },
+});
+
+discord.on(Events.ClientReady, async () => {
+ discord.user.setPresence({
+ activities: [
+ {
+ type: ActivityType.Listening,
+ name: "my parents argue",
+ },
+ ],
+ });
+
+ // every 10 seconds...
+ setInterval(() => {
+ // find empty user-created voice channels and delete them
+ discord.guilds.cache.forEach((guild) => {
+ guild.channels.cache.forEach((channel) => {
+ if (
+ channel.type === ChannelType.GuildVoice &&
+ channel.name.endsWith("'s Voice Channel") &&
+ channel.members.size === 0
+ ) {
+ console.log(
+ `Deleting empty voice channel: ${channel.name} in guild: ${guild.name}`
+ );
+ channel.delete().catch(console.error);
+ }
+ });
+ });
+ }, 10 * 1000);
+
+ const commands = {
+ global: [],
+ guild: [],
+ };
+
+ // register guild commands
+ await discord.rest.put(
+ Routes.applicationGuildCommands(
+ discord.application.id,
+ process.env.DISCORD_GUILD_ID
+ ),
+ {
+ body: commands.guild,
+ }
+ );
+
+ // register global commands
+ await discord.rest.put(Routes.applicationCommands(discord.application.id), {
+ body: commands.global,
+ });
+
+ console.log(
+ `Bot is ready! Logged in as: ${discord.user.tag} (${discord.user.id})`
+ );
+});
+
+discord.on(Events.MessageCreate, async (message) => {
+ // ignore messages from bots
+ if (message.author.bot) return;
+
+ // ignore messages not in the specified guild
+ if (message.guild.id !== process.env.DISCORD_GUILD_ID) return;
+
+ if (message.content.startsWith(`<@!${discord.user.id}>`)) {
+ let command = message.content.replace(`<@!${discord.user.id}>`, "").trim();
+
+ switch (command) {
+ case "purge-members-without-roles":
+ // user should have administrator permissions to run this command
+ if (!message.member.permissions.has(PermissionFlagsBits.Administrator)) {
+ message.reply("You do not have permission to run this command.");
+ return;
+ }
+
+ let deletedCount = 0;
+
+ await message.guild.members.fetch();
+
+ message.guild.members.cache.forEach(async (member) => {
+ if (member.user.bot) return; // ignore bots
+ if (member.roles.cache.size === 1) {
+ // only has the @everyone role
+ try {
+ await member.kick("Purged for not having a role");
+ deletedCount++;
+ } catch (error) {
+ console.error(`Failed to kick member ${member.id}:`, error);
+ }
+ }
+ });
+
+ message.channel.send(
+ `Purged ${deletedCount} members without roles.`
+ );
+ break;
+ default:
+ break;
+ }
+ }
+});
+
+discord.on(Events.VoiceStateUpdate, async (oldState, newState) => {
+ // ignore events from other guilds not specified in the .env file
+ if (newState.guild.id !== process.env.DISCORD_GUILD_ID) return;
+
+ console.log(`Voice state updated:`, {
+ user: {
+ id: newState.member.id,
+ username: newState.member.user.username,
+ discriminator: newState.member.user.discriminator,
+ },
+ oldChannel: {
+ id: oldState.channelId,
+ name: oldState.channel?.name,
+ },
+ newChannel: {
+ id: newState.channelId,
+ name: newState.channel?.name,
+ },
+ });
+
+ // has the user joined the "create voice channel" voice channel?
+ if (newState.channel?.name === "create voice channel") {
+ console.log(
+ `User ${newState.member.user.username} has joined the "create voice channel" voice channel. Creating a new voice channel...`
+ );
+
+ // does the user already have a voice channel?
+ if (
+ newState.guild.channels.cache.some((channel) => {
+ return (
+ channel.type === ChannelType.GuildVoice &&
+ channel.name === `${newState.member.user.username}'s Voice Channel`
+ );
+ })
+ ) {
+ console.log(
+ `User ${newState.member.user.username} already has a voice channel. Not creating a new one.`
+ );
+ } else {
+ console.log(
+ `User ${newState.member.user.username} does not have a voice channel. Creating a new one.`
+ );
+
+ const guild = newState.guild;
+ const newChannel = await guild.channels.create({
+ name: `${newState.member.user.username}'s Voice Channel`,
+ type: ChannelType.GuildVoice,
+ parent: newState.channel.parentId, // use the same parent as the "create voice channel" channel
+ permissionOverwrites: [
+ {
+ id: guild.roles.everyone.id,
+ allow: [PermissionFlagsBits.Connect, PermissionFlagsBits.Speak],
+ },
+ ],
+ });
+
+ console.log(`Created new voice channel: ${newChannel.name}`);
+ }
+
+ // get the id of the new (or not new) voice channel
+ const voiceChannelId = newState.guild.channels.cache.find((channel) => {
+ return (
+ channel.type === ChannelType.GuildVoice &&
+ channel.name === `${newState.member.user.username}'s Voice Channel`
+ );
+ });
+
+ // move the user to the new voice channel
+ await newState.member.voice.setChannel(voiceChannelId);
+
+ console.log(
+ `Moved user ${newState.member.user.username} to their voice channel.`
+ );
+ }
+});
+
+discord.login(process.env.DISCORD_BOT_TOKEN);