import { AutoRouter } from 'itty-router'; import { InteractionResponseType, InteractionType, verifyKey, } from 'discord-interactions'; import { HOT_COMMAND, ROLEPLAY_COMMAND, NSFW_COMMAND, TOP_COMMAND, } from './commands.js'; import { getCutePost, getRoleplayPost, getNSFWPost, getTopPost, } from './reddit.js'; class JSONResponse extends Response { constructor(body, init) { const jsonBody = JSON.stringify(body); init = init || { headers: { 'content-type': 'application/json;charset=UTF-8', }, }; super(jsonBody, init); } } const router = AutoRouter(); function createPostEmbed(post) { const mediaUrl = post.media?.reddit_video?.fallback_url || post.secure_media?.reddit_video?.fallback_url || post.url; let description = post.selftext || ''; if (description.length > 1000) description = description.substring(0, 997).trim() + ' ...'; const embed = { title: post.title, description: description, url: `https://reddit.com${post.permalink}`, color: 0xff4500, author: { name: `u/${post.author}`, url: `https://reddit.com/u/${post.author}`, }, fields: [ { name: 'Score', value: `${post.score} ⬆️`, inline: true, }, { name: 'Comments', value: `${post.num_comments} 💬`, inline: true, }, ], timestamp: new Date(post.created_utc * 1000).toISOString(), footer: { text: 'r/okbuddyumamusume', }, }; if (mediaUrl) if (post.media?.reddit_video || post.secure_media?.reddit_video) embed.video = { url: mediaUrl }; else embed.image = { url: mediaUrl }; return embed; } router.get('/', (_request, environment) => { return new Response(`👋 ${environment.DISCORD_APPLICATION_ID}`); }); router.post('/', async (request, environment) => { const { isValid, interaction } = await server.verifyDiscordRequest( request, environment, ); if (!isValid || !interaction) return new Response('Bad request signature.', { status: 401 }); if (interaction.type === InteractionType.PING) return new JSONResponse({ type: InteractionResponseType.PONG, }); if (interaction.type === InteractionType.APPLICATION_COMMAND) { switch (interaction.data.name.toLowerCase()) { case HOT_COMMAND.name.toLowerCase(): { try { const post = await getCutePost(); const embed = createPostEmbed(post); return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { embeds: [embed], }, }); } catch { return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: '❌ No posts found. Try again later!', flags: 64, }, }); } } case ROLEPLAY_COMMAND.name.toLowerCase(): { try { const post = await getRoleplayPost(); const embed = createPostEmbed(post); return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { embeds: [embed], }, }); } catch { return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: '❌ No roleplay posts found. Try again later!', flags: 64, }, }); } } case NSFW_COMMAND.name.toLowerCase(): { if (!interaction.channel_id || !interaction.channel?.nsfw) { return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: '❌ This command can only be used in NSFW channels.', flags: 64, }, }); } try { const post = await getNSFWPost(); const embed = createPostEmbed(post); return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { embeds: [embed], }, }); } catch { return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: '❌ No NSFW posts found. Try again later!', flags: 64, }, }); } } case TOP_COMMAND.name.toLowerCase(): { try { const time = interaction.data.options?.[0]?.value || 'day'; const post = await getTopPost(time); const embed = createPostEmbed(post); return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { embeds: [embed], }, }); } catch { return new JSONResponse({ type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE, data: { content: '❌ No top posts found. Try again later!', flags: 64, }, }); } } default: return new JSONResponse({ error: 'Unknown Type' }, { status: 400 }); } } console.error('Unknown Type'); return new JSONResponse({ error: 'Unknown Type' }, { status: 400 }); }); router.all('*', () => new Response('Not Found.', { status: 404 })); async function verifyDiscordRequest(request, environment) { const signature = request.headers.get('x-signature-ed25519'); const timestamp = request.headers.get('x-signature-timestamp'); const body = await request.text(); const isValidRequest = signature && timestamp && (await verifyKey( body, signature, timestamp, environment.DISCORD_PUBLIC_KEY, )); if (!isValidRequest) return { isValid: false }; return { interaction: JSON.parse(body), isValid: true }; } const server = { verifyDiscordRequest, fetch: router.fetch, }; export default server;