const express = require('express');
const {
InteractionType,
InteractionResponseType,
InteractionResponseFlags,
MessageComponentTypes,
ButtonStyleTypes,
verifyKeyMiddleware,
verifyWebhookEventMiddleware,
WebhookType,
} = require('discord-interactions');
const app = express();
// Health check endpoint (useful for monitoring)
app.get('/health', (req, res) => {
res.send('ok');
});
// Interactions endpoint
app.post(
'/interactions',
verifyKeyMiddleware(process.env.CLIENT_PUBLIC_KEY),
(req, res) => {
const interaction = req.body;
// Handle different interaction types
if (interaction.type === InteractionType.APPLICATION_COMMAND) {
const commandName = interaction.data.name;
// Route to command handlers
const response = handleCommand(commandName, interaction);
res.send(response);
}
else if (interaction.type === InteractionType.MESSAGE_COMPONENT) {
const customId = interaction.data.custom_id;
// Handle component interactions
const response = handleComponent(customId, interaction);
res.send(response);
}
else if (interaction.type === InteractionType.MODAL_SUBMIT) {
const customId = interaction.data.custom_id;
// Handle modal submissions
const response = handleModal(customId, interaction);
res.send(response);
}
}
);
// Webhook events endpoint
app.post(
'/events',
verifyWebhookEventMiddleware(process.env.CLIENT_PUBLIC_KEY),
(req, res) => {
const event = req.body;
if (event.type === WebhookType.EVENT) {
// Process event asynchronously
processEvent(event.data.event).catch(console.error);
}
}
);
// Command handlers
function handleCommand(commandName, interaction) {
switch (commandName) {
case 'hello':
return {
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `Hello, ${interaction.member?.user?.username || 'friend'}!`,
},
};
case 'buttons':
return {
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: 'Click a button!',
components: [
{
type: MessageComponentTypes.ACTION_ROW,
components: [
{
type: MessageComponentTypes.BUTTON,
style: ButtonStyleTypes.PRIMARY,
label: 'Primary',
custom_id: 'button_primary',
},
{
type: MessageComponentTypes.BUTTON,
style: ButtonStyleTypes.SUCCESS,
label: 'Success',
custom_id: 'button_success',
},
],
},
],
},
};
default:
return {
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: 'Unknown command',
flags: InteractionResponseFlags.EPHEMERAL,
},
};
}
}
// Component handlers
function handleComponent(customId, interaction) {
if (customId.startsWith('button_')) {
return {
type: InteractionResponseType.UPDATE_MESSAGE,
data: {
content: `You clicked the ${customId.replace('button_', '')} button!`,
components: [], // Remove buttons
},
};
}
return {
type: InteractionResponseType.UPDATE_MESSAGE,
data: {
content: 'Component interaction received',
},
};
}
// Modal handlers
function handleModal(customId, interaction) {
return {
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: 'Modal submitted successfully!',
flags: InteractionResponseFlags.EPHEMERAL,
},
};
}
// Event processor
async function processEvent(event) {
console.log('📨 Event received:', event.type);
// Add your event processing logic here
switch (event.type) {
case 'APPLICATION_AUTHORIZED':
console.log('App authorized by user:', event.user?.username);
break;
case 'ENTITLEMENT_CREATE':
console.log('New entitlement created');
break;
default:
console.log('Unhandled event type:', event.type);
}
}
const PORT = process.env.PORT || 8999;
app.listen(PORT, () => {
console.log(`Discord webhook server listening at http://localhost:${PORT}`);
console.log(`Interactions endpoint: http://localhost:${PORT}/interactions`);
console.log(`Events endpoint: http://localhost:${PORT}/events`);
});