Files
minecraft-account-manager/apps/discord-bot/src/index.ts
T
dmg 86c87153b4
CI / validate (push) Successful in 4m55s
Release / release (push) Successful in 9m46s
feat(logging): add structured server diagnostics
2026-08-01 17:37:52 -04:00

92 lines
3.0 KiB
TypeScript

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<ButtonBuilder>().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;
}