replace axios with node-fetch and change code formatting

This commit is contained in:
Shay
2022-04-07 15:26:43 -07:00
parent 04b7ea9add
commit 6cea182ff0
9 changed files with 559 additions and 506 deletions

289
app.js
View File

@@ -1,20 +1,24 @@
import 'dotenv/config'
import express from 'express'
import axios from 'axios';
import { InteractionType, InteractionResponseType } from 'discord-interactions';
import { VerifyDiscordRequest, getRandomEmoji, ComponentType, ButtonStyle, DiscordAPI } from './utils.js';
import 'dotenv/config';
import express from 'express';
import { InteractionType, InteractionResponseType, InteractionResponseFlags } from 'discord-interactions';
import {
VerifyDiscordRequest,
getRandomEmoji,
ComponentType,
ButtonStyle,
DiscordRequest,
} from './utils.js';
import { getShuffledOptions, getResult } from './game.js';
import { CHALLENGE_COMMAND, TEST_COMMAND, HasGuildCommands } from './commands.js';
import {
CHALLENGE_COMMAND,
TEST_COMMAND,
HasGuildCommands,
} from './commands.js';
// Create an express app
const app = express();
// Parse request body and verifies incoming requests using discord-interactions package
app.use(express.json({verify: VerifyDiscordRequest(process.env.PUBLIC_KEY)}));
// Create HTTP client instance with token
const client = axios.create({
headers: {'Authorization': `Bot ${process.env.DISCORD_TOKEN}`}
});
app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) }));
// Store for in-progress games. In production, you'd want to use a DB
const activeGames = {};
@@ -23,141 +27,158 @@ const activeGames = {};
* Interactions endpoint URL where Discord will send HTTP requests
*/
app.post('/interactions', async function (req, res) {
// Interaction type and data
const { type, id, data } = req.body;
// Interaction type and data
const { type, id, data } = req.body;
/**
* Handle verification requests
*/
if (type === InteractionType.PING) {
return res.send({ "type": InteractionResponseType.PONG });
/**
* Handle verification requests
*/
if (type === InteractionType.PING) {
return res.send({ type: InteractionResponseType.PONG });
}
/**
* Handle slash command requests
* See https://discord.com/developers/docs/interactions/application-commands#slash-commands
*/
if (type === InteractionType.APPLICATION_COMMAND) {
const { name } = data;
// "test" guild command
if (name === 'test') {
// Send a message into the channel where command was triggered from
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
// Fetches a random emoji to send from a helper function
content: 'hello world ' + getRandomEmoji(),
},
});
}
// "challenge" guild command
if (name === 'challenge' && id) {
const userId = req.body.member.user.id;
// User's object choice
const objectName = req.body.data.options[0].value;
/**
* Handle slash command requests
* See https://discord.com/developers/docs/interactions/application-commands#slash-commands
*/
if (type === InteractionType.APPLICATION_COMMAND){
const { name } = data;
// "test" guild command
if (name === "test") {
// Send a message into the channel where command was triggered from
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": {
// Fetches a random emoji to send from a helper function
"content": "hello world " + getRandomEmoji()
}
});
}
// "challenge" guild command
if (name === "challenge" && id) {
const userId = req.body.member.user.id;
// User's object choice
const objectName = req.body.data.options[0].value;
// Create active game using message ID as the game ID
activeGames[id] = {
id: userId,
objectName: objectName,
};
// Create active game using message ID as the game ID
activeGames[id] = {
"id": userId,
"objectName": objectName
};
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": {
// Fetches a random emoji to send from a helper function
"content": `Rock papers scissors challenge from <@${userId}>`,
"components": [{
"type": ComponentType.ACTION,
"components": [{
"type": ComponentType.BUTTON,
// Append the game ID to use later on
"custom_id": `accept_button_${req.body.id}`,
"label": "Accept",
"style": ButtonStyle.PRIMARY
}]
}]
}
});
}
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
// Fetches a random emoji to send from a helper function
content: `Rock papers scissors challenge from <@${userId}>`,
components: [
{
type: ComponentType.ACTION,
components: [
{
type: ComponentType.BUTTON,
// Append the game ID to use later on
custom_id: `accept_button_${req.body.id}`,
label: 'Accept',
style: ButtonStyle.PRIMARY,
},
],
},
],
},
});
}
}
/**
* Handle requests from interactive components
* See https://discord.com/developers/docs/interactions/message-components#responding-to-a-component-interaction
*/
if (type === InteractionType.MESSAGE_COMPONENT){
// custom_id set in payload when sending message component
const componentId = data.custom_id;
/**
* Handle requests from interactive components
* See https://discord.com/developers/docs/interactions/message-components#responding-to-a-component-interaction
*/
if (type === InteractionType.MESSAGE_COMPONENT) {
// custom_id set in payload when sending message component
const componentId = data.custom_id;
if (componentId.startsWith('accept_button_')) {
// get the associated game ID
const gameId = componentId.replace('accept_button_', '');
// Delete message with token in request body
const url = DiscordAPI(`webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`);
try {
await res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": {
// Fetches a random emoji to send from a helper function
"content": "What's your object of choice?",
// Indicates it'll be an ephemeral message
"flags": 64,
"components": [{
"type": ComponentType.ACTION,
"components": [{
"type": ComponentType.SELECT,
// Append game ID
"custom_id": `select_choice_${gameId}`,
"options": getShuffledOptions()
}]
}]
}
});
if (componentId.startsWith('accept_button_')) {
// get the associated game ID
const gameId = componentId.replace('accept_button_', '');
// Delete message with token in request body
const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`;
try {
await res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
// Fetches a random emoji to send from a helper function
content: 'What is your object of choice?',
// Indicates it'll be an ephemeral message
flags: InteractionResponseFlags.EPHEMERAL,
components: [
{
type: ComponentType.ACTION,
components: [
{
type: ComponentType.SELECT,
// Append game ID
custom_id: `select_choice_${gameId}`,
options: getShuffledOptions(),
},
],
},
],
},
});
await client({ url, method: 'delete' });
} catch (err) {
console.error('Error sending message:', err);
}
} else if (componentId.startsWith('select_choice_')) {
// get the associated game ID
const gameId = componentId.replace('select_choice_', '');
if (activeGames[gameId]) {
// Get user ID and object choice for responding user
const userId = req.body.member.user.id;
const objectName = data.values[0];
// Calculate result from helper function
const resultStr = getResult(activeGames[gameId], {id: userId, objectName});
// Remove game from storage
delete activeGames[gameId];
// Update message with token in request body
const url = DiscordAPI(`webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`);
try {
// Send results
await res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": { "content": resultStr }
});
await client({ url, method: 'patch', data: {
"content": "Nice choice " + getRandomEmoji(),
"components": []
}});
} catch (err) {
console.error('Error sending message:', err);
}
await DiscordRequest(endpoint, { method: 'DELETE' });
} catch (err) {
console.error('Error sending message:', err);
}
} else if (componentId.startsWith('select_choice_')) {
// get the associated game ID
const gameId = componentId.replace('select_choice_', '');
if (activeGames[gameId]) {
// Get user ID and object choice for responding user
const userId = req.body.member.user.id;
const objectName = data.values[0];
// Calculate result from helper function
const resultStr = getResult(activeGames[gameId], {
id: userId,
objectName,
});
// Remove game from storage
delete activeGames[gameId];
// Update message with token in request body
const endpoint = `webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`;
try {
// Send results
await res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { content: resultStr },
});
await DiscordRequest(endpoint, {
method: 'PATCH',
body: {
content: "Nice choice " + getRandomEmoji(),
components: []
}
});
} catch (err) {
console.error('Error sending message:', err);
}
}
}
}
});
app.listen(3000, () => {
console.log('Listening on port 3000');
console.log('Listening on port 3000');
// Check if guild commands from commands.json are installed (if not, install them)
HasGuildCommands(client, process.env.APP_ID, process.env.GUILD_ID, [TEST_COMMAND, CHALLENGE_COMMAND]);
// Check if guild commands from commands.json are installed (if not, install them)
HasGuildCommands(process.env.APP_ID, process.env.GUILD_ID, [
TEST_COMMAND,
CHALLENGE_COMMAND,
]);
});

View File

@@ -1,79 +1,80 @@
import { getRPSChoices } from "./game.js";
import { capitalize, DiscordAPI } from "./utils.js";
import { getRPSChoices } from './game.js';
import { capitalize, DiscordRequest } from './utils.js';
export async function HasGuildCommands(client, appId, guildId, commands) {
if (guildId === '' || appId === '') return;
export async function HasGuildCommands(appId, guildId, commands) {
if (guildId === '' || appId === '') return;
commands.forEach(c => HasGuildCommand(client, appId, guildId, c));
commands.forEach((c) => HasGuildCommand(appId, guildId, c));
}
// Checks for a command
async function HasGuildCommand(client, appId, guildId, command) {
// API URL to get and post guild commands
const url = DiscordAPI(`applications/${appId}/guilds/${guildId}/commands`);
async function HasGuildCommand(appId, guildId, command) {
// API endpoint to get and post guild commands
const endpoint = `applications/${appId}/guilds/${guildId}/commands`;
try {
const { data } = await client({ url, method: 'get'});
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"])) {
InstallGuildCommand(client, appId, guildId, command);
} else {
console.log(`"${command["name"]}" command already installed`)
}
}
} catch (err) {
console.error('Error installing commands: ', err);
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'])) {
InstallGuildCommand(appId, guildId, command);
} else {
console.log(`"${command['name']}" command already installed`);
}
}
} catch (err) {
console.error('Error installing commands: ', err);
}
}
// Installs a command
export async function InstallGuildCommand(client, appId, guildId, command) {
// API URL to get and post guild commands
const url = DiscordAPI(`applications/${appId}/guilds/${guildId}/commands`);
// install command
try {
await client({ url, method: 'post', data: command});
} catch (err) {
console.error('Error installing commands: ', err);
}
export async function InstallGuildCommand(appId, guildId, command) {
// API endpoint to get and post guild commands
const endpoint = `applications/${appId}/guilds/${guildId}/commands`;
// install command
try {
await DiscordRequest(endpoint, { method: 'POST', body: command });
} catch (err) {
console.error('Error installing commands: ', err);
}
}
// Get the game choices from game.js
function createCommandChoices() {
const choices = getRPSChoices();
const commandChoices = [];
const choices = getRPSChoices();
const commandChoices = [];
for (let choice of choices) {
commandChoices.push({
"name": capitalize(choice),
"value": choice.toLowerCase()
});
}
for (let choice of choices) {
commandChoices.push({
name: capitalize(choice),
value: choice.toLowerCase(),
});
}
return commandChoices;
return commandChoices;
}
// Simple test command
export const TEST_COMMAND = {
"name": "test",
"description": "Basic guild command",
"type": 1
name: 'test',
description: 'Basic guild command',
type: 1,
};
// Command containing options
export const CHALLENGE_COMMAND = {
"name": "challenge",
"description": "Challenge to a match of rock paper scissors",
"options": [
{
"type": 3,
"name": "object",
"description": "Pick your object",
"required": true,
"choices": createCommandChoices()
}
],
"type": 1
name: 'challenge',
description: 'Challenge to a match of rock paper scissors',
options: [
{
type: 3,
name: 'object',
description: 'Pick your object',
required: true,
choices: createCommandChoices(),
},
],
type: 1,
};

View File

@@ -1,61 +1,65 @@
import 'dotenv/config'
import express from 'express'
import 'dotenv/config';
import express from 'express';
import { InteractionType, InteractionResponseType } from 'discord-interactions';
import { VerifyDiscordRequest, ComponentType, ButtonStyle } from '../utils.js';
// Create and configure express app
const app = express();
app.use(express.json({verify: VerifyDiscordRequest(process.env.PUBLIC_KEY)}));
app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) }));
app.post('/interactions', function (req, res) {
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND){
// Slash command with name of "test"
if (data.name === "test") {
// Send a message with a button
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": {
"content": 'A message with a button',
// Buttons are inside of action rows
"components": [{
"type": ComponentType.ACTION,
"components": [{
"type": ComponentType.BUTTON,
// Value for your app to identify the button
"custom_id": "my_button",
"label": "Click",
"style": ButtonStyle.PRIMARY
}]
}]
}
});
}
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND) {
// Slash command with name of "test"
if (data.name === 'test') {
// Send a message with a button
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: 'A message with a button',
// Buttons are inside of action rows
components: [
{
type: ComponentType.ACTION,
components: [
{
type: ComponentType.BUTTON,
// Value for your app to identify the button
custom_id: 'my_button',
label: 'Click',
style: ButtonStyle.PRIMARY,
},
],
},
],
},
});
}
}
/**
* Handle requests from interactive components
*/
if (type === InteractionType.MESSAGE_COMPONENT){
// custom_id set in payload when sending message component
const componentId = data.custom_id;
// user who clicked button
const userId = req.body.member.user.id;
/**
* Handle requests from interactive components
*/
if (type === InteractionType.MESSAGE_COMPONENT) {
// custom_id set in payload when sending message component
const componentId = data.custom_id;
// user who clicked button
const userId = req.body.member.user.id;
if (componentId === 'my_button') {
console.log(req.body);
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": { "content": `<@${userId} clicked the button` }
});
}
if (componentId === 'my_button') {
console.log(req.body);
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { content: `<@${userId} clicked the button` },
});
}
}
});
app.listen(3000, () => {
console.log('Listening on port 3000');
console.log('Listening on port 3000');
});

View File

@@ -1,70 +1,65 @@
import 'dotenv/config'
import express from 'express'
import express from 'express';
import { InteractionType, InteractionResponseType } from 'discord-interactions';
import { VerifyDiscordRequest, DiscordAPI } from '../utils.js';
import axios from 'axios';
import { VerifyDiscordRequest, DiscordRequest } from '../utils.js';
// Create and configure express app
const app = express();
app.use(express.json({verify: VerifyDiscordRequest(process.env.PUBLIC_KEY)}));
app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) }));
app.post('/interactions', function (req, res) {
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND){
// Slash command with name of "test"
if (data.name === "test") {
// Send a message as response
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": { "content": "A wild message appeared" }
});
}
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND) {
// Slash command with name of "test"
if (data.name === 'test') {
// Send a message as response
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { content: 'A wild message appeared' },
});
}
}
});
function createCommand() {
const appId = process.env.APP_ID;
const guildId = process.env.GUILD_ID;
/**
* Globally-scoped slash commands (generally only recommended for production)
* See https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
*/
// const globalUrl = DiscordAPI(`applications/${appId}/commands`);
/**
* Guild-scoped slash commands
* See https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command
*/
const guildUrl = DiscordAPI(`applications/${appId}/guilds/${guildId}/commands`);
const commandBody = {
"name": "test",
"description": "Just your average command",
// chat command (see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types)
"type": 1
};
async function createCommand() {
const appId = process.env.APP_ID;
const guildId = process.env.GUILD_ID;
try {
// Send HTTP request with bot token
let res = await axios({
url: guildUrl,
method: 'post',
data: commandBody,
headers: {'Authorization': `Bot ${process.env.DISCORD_TOKEN}`}
});
console.log(res.body);
} catch (err) {
console.error('Error installing commands: ', err);
}
/**
* Globally-scoped slash commands (generally only recommended for production)
* See https://discord.com/developers/docs/interactions/application-commands#create-global-application-command
*/
// const globalEndpoint = `applications/${appId}/commands`;
/**
* Guild-scoped slash commands
* See https://discord.com/developers/docs/interactions/application-commands#create-guild-application-command
*/
const guildEndpoint = `applications/${appId}/guilds/${guildId}/commands`;
const commandBody = {
name: 'test',
description: 'Just your average command',
// chat command (see https://discord.com/developers/docs/interactions/application-commands#application-command-object-application-command-types)
type: 1,
};
try {
// Send HTTP request with bot token
const res = await DiscordRequest(guildEndpoint, {
method: 'POST',
body: commandBody,
});
console.log(await res.json());
} catch (err) {
console.error('Error installing commands: ', err);
}
}
app.listen(3000, () => {
console.log('Listening on port 3000');
console.log('Listening on port 3000');
createCommand();
createCommand();
});

View File

@@ -1,84 +1,86 @@
import 'dotenv/config'
import express from 'express'
import 'dotenv/config';
import express from 'express';
import { InteractionType, InteractionResponseType } from 'discord-interactions';
import { VerifyDiscordRequest, ComponentType } from '../utils.js';
// Create and configure express app
const app = express();
app.use(express.json({verify: VerifyDiscordRequest(process.env.PUBLIC_KEY)}));
app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) }));
app.post('/interactions', function (req, res) {
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND){
// Slash command with name of "test"
if (data.name === "test") {
// Send a modal as response
return res.send({
"type": InteractionResponseType.APPLICATION_MODAL,
"data": {
"custom_id": "my_modal",
"title": "Modal title",
"components": [
{
// Text inputs must be inside of an action component
"type": ComponentType.ACTION,
"components": [
{
// See https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
"type": ComponentType.INPUT,
"custom_id": "my_text",
"style": 1,
"label": "Type some text"
}
]
},
{
"type": ComponentType.ACTION,
"components": [
{
"type": ComponentType.INPUT,
"custom_id": "my_longer_text",
// Bigger text box for input
"style": 2,
"label": "Type some (longer) text"
}
]
}
]
}
});
}
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND) {
// Slash command with name of "test"
if (data.name === 'test') {
// Send a modal as response
return res.send({
type: InteractionResponseType.APPLICATION_MODAL,
data: {
custom_id: 'my_modal',
title: 'Modal title',
components: [
{
// Text inputs must be inside of an action component
type: ComponentType.ACTION,
components: [
{
// See https://discord.com/developers/docs/interactions/message-components#text-inputs-text-input-structure
type: ComponentType.INPUT,
custom_id: 'my_text',
style: 1,
label: 'Type some text',
},
],
},
{
type: ComponentType.ACTION,
components: [
{
type: ComponentType.INPUT,
custom_id: 'my_longer_text',
// Bigger text box for input
style: 2,
label: 'Type some (longer) text',
},
],
},
],
},
});
}
}
/**
* Handle modal submissions
*/
if (type === InteractionType.APPLICATION_MODAL_SUBMIT) {
// custom_id of modal
const modalId = data.custom_id;
// user ID of member who filled out modal
const userId = req.body.member.user.id;
/**
* Handle modal submissions
*/
if (type === InteractionType.APPLICATION_MODAL_SUBMIT) {
// custom_id of modal
const modalId = data.custom_id;
// user ID of member who filled out modal
const userId = req.body.member.user.id;
if (modalId === 'my_modal') {
let modalValues = '';
// Get value of text inputs
for (let action of data.components) {
let inputComponent = action.components[0];
modalValues += `${inputComponent.custom_id}: ${inputComponent.value}\n`
}
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": { "content": `<@${userId}> typed the following (in a modal):\n\n${modalValues}` }
})
}
if (modalId === 'my_modal') {
let modalValues = '';
// Get value of text inputs
for (let action of data.components) {
let inputComponent = action.components[0];
modalValues += `${inputComponent.custom_id}: ${inputComponent.value}\n`;
}
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: `<@${userId}> typed the following (in a modal):\n\n${modalValues}`,
},
});
}
}
});
app.listen(3000, () => {
console.log('Listening on port 3000');
console.log('Listening on port 3000');
});

View File

@@ -1,76 +1,80 @@
import 'dotenv/config'
import express from 'express'
import 'dotenv/config';
import express from 'express';
import { InteractionType, InteractionResponseType } from 'discord-interactions';
import { VerifyDiscordRequest, ComponentType } from '../utils.js';
// Create and configure express app
const app = express();
app.use(express.json({verify: VerifyDiscordRequest(process.env.PUBLIC_KEY)}));
app.use(express.json({ verify: VerifyDiscordRequest(process.env.PUBLIC_KEY) }));
app.post('/interactions', function (req, res) {
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND){
// Slash command with name of "test"
if (data.name === "test") {
// Send a message with a button
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": {
"content": 'A message with a button',
// Selects are inside of action rows
"components": [{
"type": ComponentType.ACTION,
"components": [{
"type": ComponentType.SELECT,
// Value for your app to identify the select menu interactions
"custom_id": "my_select",
// Select options - see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
"options": [
{
"label": "Option #1",
"value": "option_1",
"description": "The very first option"
},
{
"label": "Second option",
"value": "option_2",
"description": "The second AND last option"
}
]
}]
}]
}
});
}
// Interaction type and data
const { type, data } = req.body;
/**
* Handle slash command requests
*/
if (type === InteractionType.APPLICATION_COMMAND) {
// Slash command with name of "test"
if (data.name === 'test') {
// Send a message with a button
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: {
content: 'A message with a button',
// Selects are inside of action rows
components: [
{
type: ComponentType.ACTION,
components: [
{
type: ComponentType.SELECT,
// Value for your app to identify the select menu interactions
custom_id: 'my_select',
// Select options - see https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
options: [
{
label: 'Option #1',
value: 'option_1',
description: 'The very first option',
},
{
label: 'Second option',
value: 'option_2',
description: 'The second AND last option',
},
],
},
],
},
],
},
});
}
}
/**
* Handle requests from interactive components
*/
if (type === InteractionType.MESSAGE_COMPONENT){
// custom_id set in payload when sending message component
const componentId = data.custom_id;
/**
* Handle requests from interactive components
*/
if (type === InteractionType.MESSAGE_COMPONENT) {
// custom_id set in payload when sending message component
const componentId = data.custom_id;
if (componentId === 'my_select') {
console.log(req.body);
if (componentId === 'my_select') {
console.log(req.body);
// Get selected option from payload
const selectedOption = data.values[0];
const userId = req.body.member.user.id;
// Get selected option from payload
const selectedOption = data.values[0];
const userId = req.body.member.user.id;
// Send results
return res.send({
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
"data": { "content": `<@${userId}> selected ${selectedOption}` }
});
}
// Send results
return res.send({
type: InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
data: { content: `<@${userId}> selected ${selectedOption}` },
});
}
}
});
app.listen(3000, () => {
console.log('Listening on port 3000');
console.log('Listening on port 3000');
});

155
game.js
View File

@@ -1,92 +1,103 @@
import { capitalize } from './utils.js'
import { capitalize } from './utils.js';
export function getResult(p1, p2) {
let gameResult;
if (RPSChoices[p1.objectName] && RPSChoices[p1.objectName][p2.objectName]) {
// o1 wins
gameResult = { win: p1, lose: p2, verb: RPSChoices[p1.objectName][p2.objectName] };
} else if (RPSChoices[p2.objectName] && RPSChoices[p2.objectName][p1.objectName]) {
// o2 wins
gameResult = { win: p2, lose: p1, verb: RPSChoices[p2.objectName][p1.objectName] };
} else {
// tie -- win/lose don't
gameResult = { win: p1, lose: p2, verb: 'tie' };
}
let gameResult;
if (RPSChoices[p1.objectName] && RPSChoices[p1.objectName][p2.objectName]) {
// o1 wins
gameResult = {
win: p1,
lose: p2,
verb: RPSChoices[p1.objectName][p2.objectName],
};
} else if (
RPSChoices[p2.objectName] &&
RPSChoices[p2.objectName][p1.objectName]
) {
// o2 wins
gameResult = {
win: p2,
lose: p1,
verb: RPSChoices[p2.objectName][p1.objectName],
};
} else {
// tie -- win/lose don't
gameResult = { win: p1, lose: p2, verb: 'tie' };
}
return formatResult(gameResult)
return formatResult(gameResult);
}
function formatResult(result) {
const { win, lose, verb } = result;
return verb === 'tie' ?
`<@${win.id}> and <@${lose.id}> draw with **${win.objectName}**` :
`<@${win.id}>'s **${win.objectName}** ${verb} <@${lose.id}>'s **${lose.objectName}**`;
const { win, lose, verb } = result;
return verb === 'tie'
? `<@${win.id}> and <@${lose.id}> draw with **${win.objectName}**`
: `<@${win.id}>'s **${win.objectName}** ${verb} <@${lose.id}>'s **${lose.objectName}**`;
}
// this is just to figure out winner + verb
const RPSChoices = {
"rock": {
"description": "sedimentary, igneous, or perhaps even metamorphic",
"virus": "outwaits",
"computer": "smashes",
"scissors": "crushes"
},
"cowboy": {
"description": "yeehaw~",
"scissors": "puts away",
"wumpus": "lassos",
"rock": "steel-toe kicks"
},
"scissors": {
"description": "careful ! sharp ! edges !!",
"paper": "cuts",
"computer": "cuts cord of",
"virus": "cuts DNA of"
},
"virus": {
"description": "genetic mutation, malware, or something inbetween",
"cowboy": "infects",
"computer": "corrupts",
"wumpus": "infects"
},
"computer": {
"description": "beep boop beep bzzrrhggggg",
"cowboy": "overwhelms",
"paper": "uninstalls firmware for",
"wumpus": "deletes assets for"
},
"wumpus": {
"description": "the purple Discord fella",
"paper": "draws picture on",
"rock": "paints cute face on",
"scissors": "admires own reflection in"
},
"paper": {
"description": "versatile and iconic",
"virus": "ignores",
"cowboy": "gives papercut to",
"rock": "covers"
}
rock: {
description: 'sedimentary, igneous, or perhaps even metamorphic',
virus: 'outwaits',
computer: 'smashes',
scissors: 'crushes',
},
cowboy: {
description: 'yeehaw~',
scissors: 'puts away',
wumpus: 'lassos',
rock: 'steel-toe kicks',
},
scissors: {
description: 'careful ! sharp ! edges !!',
paper: 'cuts',
computer: 'cuts cord of',
virus: 'cuts DNA of',
},
virus: {
description: 'genetic mutation, malware, or something inbetween',
cowboy: 'infects',
computer: 'corrupts',
wumpus: 'infects',
},
computer: {
description: 'beep boop beep bzzrrhggggg',
cowboy: 'overwhelms',
paper: 'uninstalls firmware for',
wumpus: 'deletes assets for',
},
wumpus: {
description: 'the purple Discord fella',
paper: 'draws picture on',
rock: 'paints cute face on',
scissors: 'admires own reflection in',
},
paper: {
description: 'versatile and iconic',
virus: 'ignores',
cowboy: 'gives papercut to',
rock: 'covers',
},
};
export function getRPSChoices() {
return Object.keys(RPSChoices);
return Object.keys(RPSChoices);
}
// Function to fetch shuffled options for select menu
export function getShuffledOptions() {
const allChoices = getRPSChoices();
const options = [];
const allChoices = getRPSChoices();
const options = [];
for (let c of allChoices) {
// Formatted for select menus
// https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
options.push({
"label": capitalize(c),
"value": c.toLowerCase(),
"description": RPSChoices[c]["description"]
});
}
for (let c of allChoices) {
// Formatted for select menus
// https://discord.com/developers/docs/interactions/message-components#select-menu-object-select-option-structure
options.push({
label: capitalize(c),
value: c.toLowerCase(),
description: RPSChoices[c]['description'],
});
}
return options.sort(() => Math.random() - 0.5);
return options.sort(() => Math.random() - 0.5);
}

View File

@@ -10,10 +10,10 @@
"author": "Shay DeWael",
"license": "MIT",
"dependencies": {
"axios": "^0.26.1",
"discord-interactions": "^3.1.0",
"dotenv": "^16.0.0",
"express": "^4.17.3"
"express": "^4.17.3",
"node-fetch": "^3.2.3"
},
"devDependencies": {
"nodemon": "^2.0.15"

View File

@@ -1,3 +1,5 @@
import 'dotenv/config';
import fetch from 'node-fetch';
import { verifyKey } from 'discord-interactions';
export function VerifyDiscordRequest(clientKey) {
@@ -10,32 +12,45 @@ export function VerifyDiscordRequest(clientKey) {
res.status(401).send('Bad request signature');
throw new Error('Bad request signature');
}
}
};
}
export function DiscordAPI(url) { return 'https://discord.com/api/v9/' + url };
export function DiscordRequest(endpoint, options) {
// append endpoint to root API URL
const url = 'https://discord.com/api/v9/' + endpoint;
// Stringify payloads
if (options.body) options.body = JSON.stringify(options.body);
// Use node-fetch to make requests
return fetch(url, {
headers: {
Authorization: `Bot ${process.env.DISCORD_TOKEN}`,
'Content-Type': 'application/json; charset=UTF-8',
},
...options
});
}
// Simple method that returns a random emoji from list
export function getRandomEmoji() {
const emojiList = ['😭', '😄', '😌', '🤓', '😎', '😤', '🤖', '😶‍🌫️', '🌏', '📸', '💿', '👋', '🌊', '✨'];
const emojiList = ['😭','😄','😌','🤓','😎','😤','🤖','😶‍🌫️','🌏','📸','💿','👋','🌊','✨'];
return emojiList[Math.floor(Math.random() * emojiList.length)];
}
export function capitalize(str) {
return str.charAt(0).toUpperCase() + str.slice(1);
return str.charAt(0).toUpperCase() + str.slice(1);
}
export const ComponentType = {
ACTION: 1,
BUTTON: 2,
SELECT: 3,
INPUT: 4
}
ACTION: 1,
BUTTON: 2,
SELECT: 3,
INPUT: 4,
};
export const ButtonStyle = {
PRIMARY: 1,
SECONDARY: 2,
SUCCESS: 3,
DANGER: 4,
LINK: 5
}
PRIMARY: 1,
SECONDARY: 2,
SUCCESS: 3,
DANGER: 4,
LINK: 5,
};