import { Client, GatewayIntentBits, ChannelType, REST, Routes, TextChannel, Interaction, } from "discord.js"; import { DisclawDatabase } from "./db/database"; import { DisclawConfig, EnvConfig } from "./config/loader"; import { newAgentCommand, handleNewAgent } from "./commands/new-agent"; import { routeMessage } from "./router"; import { childLogger } from "./runtime/logger"; const log = childLogger("bot"); export async function startBot( envConfig: EnvConfig, disclawConfig: DisclawConfig, db: DisclawDatabase ): Promise { const client = new Client({ intents: [ GatewayIntentBits.Guilds, GatewayIntentBits.GuildMessages, GatewayIntentBits.MessageContent, ], }); // Track the management channel id once resolved let managementChannelId: string | null = null; // --- Event: Ready --- client.once("ready", async () => { if (!client.user) return; log.info({ tag: client.user.tag }, "Bot online"); const guild = envConfig.DISCORD_GUILD_ID ? client.guilds.cache.get(envConfig.DISCORD_GUILD_ID) : client.guilds.cache.first(); if (!guild) { log.error("No guild found. Is the bot added to a server?"); process.exit(1); } log.info({ guildName: guild.name, guildId: guild.id }, "Connected to guild"); // --- Ensure management channel exists --- const managementName = disclawConfig.management_channel; let existingWorkspace = db.getWorkspaceByName(managementName); if (existingWorkspace) { // Verify the channel still exists on Discord const existingChannel = guild.channels.cache.get( existingWorkspace.channel_id ); if (existingChannel) { managementChannelId = existingWorkspace.channel_id; log.info({ channelName: managementName }, "Management channel found"); } else { // Channel was deleted from Discord, recreate it existingWorkspace = undefined; } } if (!existingWorkspace) { // Look for an existing channel with that name first let mgmtChannel: TextChannel; const found = guild.channels.cache.find( (ch) => ch.type === ChannelType.GuildText && ch.name === managementName ) as TextChannel | undefined; if (found) { db.createWorkspace( found.id, guild.id, managementName, "__management__" ); managementChannelId = found.id; mgmtChannel = found; log.info({ channelName: managementName }, "Adopted existing channel"); } else { const newChannel = await guild.channels.create({ name: managementName, type: ChannelType.GuildText, topic: "DisClaw Management -- verwende /new-agent um Agenten zu erstellen", }); db.createWorkspace( newChannel.id, guild.id, managementName, "__management__" ); managementChannelId = newChannel.id; mgmtChannel = newChannel; log.info({ channelName: managementName }, "Created management channel"); } // Send welcome message using the direct channel reference await mgmtChannel .send( "DisClaw ist bereit. Verwende `/new-agent` um einen neuen Agenten zu erstellen." ) .catch(() => {}); } // --- Register slash commands --- const rest = new REST({ version: "10" }).setToken( envConfig.DISCORD_BOT_TOKEN ); try { await rest.put( Routes.applicationGuildCommands(client.user.id, guild.id), { body: [newAgentCommand.toJSON()], } ); log.info("Slash commands registered."); } catch (err) { log.error({ err }, "Failed to register slash commands"); } }); // --- Event: interactionCreate (slash commands) --- client.on("interactionCreate", async (interaction: Interaction) => { if (!interaction.isChatInputCommand()) return; if (interaction.commandName === "new-agent") { await handleNewAgent(interaction, db, disclawConfig); } }); // --- Event: messageCreate (agent routing) --- client.on("messageCreate", async (message) => { // Ignore bot messages if (message.author.bot) return; // Only handle text channels in guilds if (!message.guild || message.channel.type !== ChannelType.GuildText) return; await routeMessage(message, db, disclawConfig, managementChannelId); }); // --- Login --- await client.login(envConfig.DISCORD_BOT_TOKEN); return client; }