import { Database } from '../db/database.js'; import { CloudflareWorkerServer, SlashCreator } from 'slash-create/web'; import { isSnowflake, type Snowflake } from 'discord-snowflake'; import { AuthorCommand, GenerateCommand, ResponseCommand } from './commands.js'; import { type IRequestStrict, Router } from 'itty-router'; import { getQueryArray } from '../request/query.js'; function getAuthorization(username: string, password: string): string { return btoa(username + ':' + password); } async function getToken(env: Pick) { const tokenRequest = new Request(`https://discord.com/api/v10/oauth2/token`, { headers: new Headers({ 'Content-Type': 'application/x-www-form-urlencoded', 'Authorization': `Basic ${getAuthorization(env.DISCORD_APP_ID, env.DISCORD_APP_SECRET)}` }), body: new URLSearchParams({ 'grant_type': 'client_credentials', 'scope': 'applications.commands.update' }), method: 'POST' }); const tokenResponse = await fetch(tokenRequest); if (tokenResponse.status !== 200) { const text = await tokenResponse.text(); console.error(`Failed getting token`, text); throw Error(text); } const json = await tokenResponse.json() as { access_token: string }; return 'Bearer ' + json.access_token; } export interface DiscordEnv { readonly BASE_URL: string; readonly DISCORD_APP_ID: string; readonly DISCORD_APP_SECRET: string; readonly DISCORD_PUBLIC_KEY: string; readonly DISCORD_DEV_GUILD_IDS?: string; } interface SlashCreatorContext { cfServer: CloudflareWorkerServer; slashCreator: SlashCreator; } async function getSlashCreator( { DISCORD_APP_ID, DISCORD_APP_SECRET, DISCORD_PUBLIC_KEY, DISCORD_DEV_GUILD_IDS, BASE_URL }: DiscordEnv, db: Database ): Promise { if (DISCORD_APP_ID === "" || DISCORD_APP_SECRET === "" || DISCORD_PUBLIC_KEY === "") { throw Error("Discord is not configured on this build") } const server = new CloudflareWorkerServer(); const creator = new SlashCreator({ allowedMentions: { everyone: false, roles: false, users: false }, applicationID: DISCORD_APP_ID, componentTimeouts: true, defaultImageSize: 0, disableTimeouts: false, handleCommandsManually: false, publicKey: DISCORD_PUBLIC_KEY, unknownCommandResponse: true, token: await getToken({ DISCORD_APP_ID, DISCORD_APP_SECRET }) }); const withGuilds: Snowflake[] = DISCORD_DEV_GUILD_IDS ? DISCORD_DEV_GUILD_IDS.split(',').flatMap(v => isSnowflake(v) ? [v] : []) : []; creator.withServer(server); creator.registerCommand(new GenerateCommand(creator, db)); creator.registerCommand(new AuthorCommand(creator, db)); creator.registerCommand(new ResponseCommand(creator, db, BASE_URL)); creator.registerCommand(new GenerateCommand(creator, db, withGuilds)); creator.registerCommand(new AuthorCommand(creator, db, withGuilds)); creator.registerCommand(new ResponseCommand(creator, db, BASE_URL, withGuilds)); return { cfServer: server, slashCreator: creator }; } export function discordRouter(base: string) { const router = Router({ base }); router.all('/interactions', async (req, env, db, ctx) => (await getSlashCreator(env, db)).cfServer.fetch(req, null, ctx)); router.get('/sync', async (req, env, db, _ctx) => { let servers = getQueryArray(req.query['server']); const { slashCreator } = await getSlashCreator(env, db); if (servers.length === 0) { await slashCreator.syncCommands({ syncGuilds: true, deleteCommands: true, skipGuildErrors: false }); } else { for (const id of servers) { await slashCreator.syncCommandsIn(id, true); } } return new Response('Commands successfully synced!', { status: 200, statusText: 'OK' }); }); return router; }