92 lines
3.0 KiB
TypeScript
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;
|
|
}
|