import "dotenv/config"; import { createMagicLink, LoginRateLimitedError } from "@minecraft-account-manager/auth"; import { createAuthRepository, createDatabase, recordEvent, } from "@minecraft-account-manager/database"; import { ActionRowBuilder, ButtonBuilder, ButtonStyle, Client, Events, GatewayIntentBits, } from "discord.js"; import { commandNames, requiredEnvironment } from "./config"; import { logger } from "./logger"; const token = requiredEnvironment("DISCORD_BOT_TOKEN"); const appUrl = requiredEnvironment("APP_URL"); const databaseUrl = requiredEnvironment("DATABASE_URL"); const discordGuildId = requiredEnvironment("DISCORD_GUILD_ID"); const { db } = createDatabase(databaseUrl); const authRepository = createAuthRepository(db); const client = new Client({ intents: [GatewayIntentBits.Guilds] }); client.once(Events.ClientReady, (readyClient) => { logger.info( { event: "discord.ready", botUserId: readyClient.user.id, botUsername: readyClient.user.username }, "Discord bot is ready", ); }); client.on(Events.InteractionCreate, async (interaction) => { if (!interaction.isChatInputCommand()) return; if (!commandNames.includes(interaction.commandName as (typeof commandNames)[number])) return; await interaction.deferReply({ ephemeral: true }); try { if (interaction.guildId !== discordGuildId) { await interaction.editReply("This command is only available in the configured community server."); return; } const magicLink = await createMagicLink( { id: interaction.user.id, username: interaction.user.username, globalName: interaction.user.globalName, }, { repository: authRepository, appUrl }, ); await recordEvent(db, { type: "games.minecraft.account-manager.auth.magic-link.created", source: "/discord-bot", subject: `discord-user/${interaction.user.id}`, data: { command: interaction.commandName, guildId: interaction.guildId }, }); const row = new ActionRowBuilder().addComponents( new ButtonBuilder() .setLabel(interaction.commandName === "register" ? "Start registration" : "Open my account") .setStyle(ButtonStyle.Link) .setURL(magicLink.url), ); await interaction.editReply({ content: "This private link works once and expires in 10 minutes. Do not share it.", components: [row], }); } catch (error) { if (error instanceof LoginRateLimitedError) { await interaction.editReply("Please wait 30 seconds before requesting another private account link."); return; } logger.error( { err: error, event: "discord.magic_link_failed", command: interaction.commandName }, "Failed to create Discord account link", ); await interaction.editReply("I could not create an account link. Please try again shortly."); } }); try { await client.login(token); } catch (error) { logger.fatal({ err: error, event: "discord.login_failed" }, "Discord bot login failed"); process.exitCode = 1; }