diff --git a/app.js b/app.js index 4fc77a3..4df42f7 100644 --- a/app.js +++ b/app.js @@ -1,5 +1,10 @@ import 'dotenv/config'; import express from 'express'; +import { verifyDiscordRequest } from './utils.js'; +import { + TEST_COMMAND, + hasGuildCommands, +} from './commands.js'; // Create an express app const app = express(); @@ -8,8 +13,15 @@ const app = express(); const PORT = process.env.PORT || 3000; // Parse request body and verifies incoming requests using discord-interactions package -app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) })); +app.use(express.json({ + verify: verifyDiscordRequest(process.env.PUBLIC_KEY) +})); app.listen(PORT, () => { console.log('Listening on port', PORT); + + // Check if guild commands from commands.js are installed (if not, install them) + hasGuildCommands(process.env.APP_ID, process.env.GUILD_ID, [ + TEST_COMMAND, + ]); }); diff --git a/commands.js b/commands.js new file mode 100644 index 0000000..177ffdb --- /dev/null +++ b/commands.js @@ -0,0 +1,47 @@ +import { discordRequest } from './utils.js'; + +export const TEST_COMMAND = { + name: 'test', + description: 'Basic guild command', + type: 1, +}; + +export async function hasGuildCommands(appId, guildId, commands) { + if (guildId === '' || appId === '') { + return; + } + + commands.forEach((c) => hasGuildCommand(appId, guildId, c)); +} + +async function hasGuildCommand(appId, guildId, command) { + const endpoint = `applications/${appId}/guilds/${guildId}/commands`; + + try { + const res = await discordRequest(endpoint, { method: 'GET' }); + const data = await res.json(); + + if (data) { + const installedNames = data.map((c) => c['name']); + // This is just matching on the name, so it's not good for updates + if (!installedNames.includes(command['name'])) { + console.log(`Installing "${command['name']}"`); + installGuildCommand(appId, guildId, command); + } else { + console.log(`"${command['name']}" command already installed`); + } + } + } catch (err) { + console.error(err); + } +} + +export async function installGuildCommand(appId, guildId, command) { + const endpoint = `applications/${appId}/guilds/${guildId}/commands`; + + try { + await discordRequest(endpoint, { method: 'POST', body: command }); + } catch (err) { + console.error(err); + } +} diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..95798f2 --- /dev/null +++ b/utils.js @@ -0,0 +1,45 @@ +import 'dotenv/config'; +import fetch from 'node-fetch'; +import { verifyKey } from 'discord-interactions'; + +const baseURL = 'https://discord.com/api/v10/'; + +export function verifyDiscordRequest(clientKey) { + return function (req, res, buf, encoding) { + const signature = req.get('X-Signature-Ed25519'); + const timestamp = req.get('X-Signature-Timestamp'); + + const isValidRequest = verifyKey(buf, signature, timestamp, clientKey); + if (!isValidRequest) { + res.status(401).send('Bad request signature'); + throw new Error('Bad request signature'); + } + }; +} + +export async function discordRequest(endpoint, options) { + const url = baseURL + endpoint; + + if (options.body) { + options.body = JSON.stringify(options.body); + } + + // Use node-fetch to make requests + const res = await fetch(url, { + headers: { + 'Authorization': `Bot ${process.env.DISCORD_TOKEN}`, + 'Content-Type': 'application/json; charset=UTF-8', + 'User-Agent': 'DiscordBot (https://github.com/erynofwales/thirteenth-friend, 1.0.0)', + }, + ...options + }); + + if (!res.ok) { + const data = await res.json(); + console.error('Request failed with status:', res.status); + throw new Error(JSON.stringify(data)); + } + + return res; +} +