mirror of
https://github.com/cassoule/flopobot_v2.git
synced 2026-03-18 21:40:27 +01:00
Add basic app with examples
This commit is contained in:
4
.env.sample
Normal file
4
.env.sample
Normal file
@@ -0,0 +1,4 @@
|
||||
APP_ID=<YOUR_APP_ID>
|
||||
GUILD_ID=<YOUR_GUILD_ID>
|
||||
DISCORD_TOKEN=<YOUR_BOT_TOKEN>
|
||||
PUBLIC_KEY=<YOUR_PUBLIC_KEY>
|
||||
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
node_modules
|
||||
.env
|
||||
package-lock.json
|
||||
34
README.md
Normal file
34
README.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# Getting started app for Discord
|
||||
|
||||
This project contains a basic Rock-Paper-Scissors-style Discord app built for the [getting started guide](TODO).
|
||||
|
||||
A version of this code is also hosted [on Glitch](TODO).
|
||||
|
||||
## Project structure
|
||||
Below is a basic overview of the project structure:
|
||||
|
||||
```
|
||||
├── examples -> short, feature-specific sample apps
|
||||
│ ├── button.js
|
||||
│ ├── command.js
|
||||
│ ├── modal.js
|
||||
│ ├── selectMenu.js
|
||||
├── .env.sample -> sample .env file
|
||||
├── app.js -> main entrypoint for app
|
||||
├── commands.js -> slash command payloads + helpers
|
||||
├── game.js -> logic specific to RPS
|
||||
├── utils.js -> utility functions and enums
|
||||
├── package.json
|
||||
├── README.md
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
## Running app locally
|
||||
|
||||
|
||||
## Resources
|
||||
- Join the **[Discord Developers server](https://discord.gg/discord-developers)** to ask questions about the API, attend events hosted by the Discord API team, and interact with other devs.
|
||||
- Read **[the documentation](https://discord.com/developers/docs/intro)** for in-depth information about API features
|
||||
|
||||
|
||||
|
||||
154
app.js
Normal file
154
app.js
Normal file
@@ -0,0 +1,154 @@
|
||||
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 { getShuffledOptions, getResult } from './game.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}`}
|
||||
});
|
||||
|
||||
// Store for in-progress games. In production, you'd want to use a DB
|
||||
let activeGames = {};
|
||||
|
||||
/**
|
||||
* Interactions endpoint URL where Discord will send HTTP requests
|
||||
*/
|
||||
app.post('/interactions', function (req, res) {
|
||||
// Interaction type and data
|
||||
let { type, id, data } = req.body;
|
||||
|
||||
/**
|
||||
* Handle verification requests
|
||||
*/
|
||||
if (type === InteractionType.PING) {
|
||||
return res.json({ "type": InteractionResponseType.PONG });
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle slash command requests
|
||||
* See https://discord.com/developers/docs/interactions/application-commands#slash-commands
|
||||
*/
|
||||
if (type === InteractionType.APPLICATION_COMMAND){
|
||||
let { 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) {
|
||||
let userId = req.body.member.user.id;
|
||||
// User's object choice
|
||||
let objectName = req.body.data.options[0].value;
|
||||
|
||||
// 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
|
||||
}]
|
||||
}]
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
let componentId = data.custom_id;
|
||||
|
||||
if (componentId.startsWith('accept_button_')) {
|
||||
// get the associated game ID
|
||||
let gameId = componentId.replace('accept_button_', '');
|
||||
// Delete message with token in request body
|
||||
let url = DiscordAPI(`/webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`);
|
||||
client({ url, method: 'delete' }).catch(e => console.error(`Error deleting message: ${e}`));
|
||||
|
||||
return 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()
|
||||
}]
|
||||
}]
|
||||
}
|
||||
});
|
||||
} else if (componentId.startsWith('select_choice_')) {
|
||||
// get the associated game ID
|
||||
let gameId = componentId.replace('select_choice_', '');
|
||||
|
||||
if (activeGames[gameId]) {
|
||||
// Get user ID and object choice for responding user
|
||||
let userId = req.body.member.user.id;
|
||||
let objectName = data.values[0];
|
||||
// Calculate result from helper function
|
||||
let resultStr = getResult(activeGames[gameId], {id: userId, objectName});
|
||||
|
||||
// Remove game from storage
|
||||
delete activeGames[gameId];
|
||||
// Update message with token in request body
|
||||
let url = DiscordAPI(`/webhooks/${process.env.APP_ID}/${req.body.token}/messages/${req.body.message.id}`);
|
||||
client({ url, method: 'patch', data: {
|
||||
"content": `Nice choice ${getRandomEmoji()}`,
|
||||
"components": []
|
||||
}}).catch(e => console.error(`Error deleting message: ${e}`));
|
||||
|
||||
// Send results
|
||||
return res.send({
|
||||
"type": InteractionResponseType.CHANNEL_MESSAGE_WITH_SOURCE,
|
||||
"data": { "content": resultStr }
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(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]);
|
||||
});
|
||||
75
commands.js
Normal file
75
commands.js
Normal file
@@ -0,0 +1,75 @@
|
||||
import { getRPSChoices } from "./game.js";
|
||||
import { capitalize, DiscordAPI } from "./utils.js";
|
||||
|
||||
export function HasGuildCommands(client, appId, guildId, commands) {
|
||||
if (guildId === '' || appId === '') return;
|
||||
|
||||
commands.forEach((c) => HasGuildCommand(client, 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`);
|
||||
|
||||
try {
|
||||
let { data } = await client({ url, method: 'get'});
|
||||
if (data) {
|
||||
let 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"])) {
|
||||
await InstallGuildCommand(client, appId, guildId, command);
|
||||
} else {
|
||||
console.log(`"${command["name"]}" command already installed`)
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(`Error installing commands: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
return client({ url, method: 'post', data: command});
|
||||
}
|
||||
|
||||
// Get the game choices from game.js
|
||||
function createCommandChoices() {
|
||||
let choices = getRPSChoices();
|
||||
let commandChoices = [];
|
||||
|
||||
for (let choice of choices) {
|
||||
commandChoices.push({
|
||||
"name": capitalize(choice),
|
||||
"value": choice.toLowerCase()
|
||||
});
|
||||
}
|
||||
|
||||
return commandChoices;
|
||||
}
|
||||
|
||||
// Simple test command
|
||||
export const TEST_COMMAND = {
|
||||
"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
|
||||
};
|
||||
61
examples/button.js
Normal file
61
examples/button.js
Normal file
@@ -0,0 +1,61 @@
|
||||
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.post('/interactions', function (req, res) {
|
||||
// Interaction type and data
|
||||
let { 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
|
||||
let componentId = data.custom_id;
|
||||
// user who clicked button
|
||||
let 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` }
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Listening on port 3000');
|
||||
});
|
||||
70
examples/command.js
Normal file
70
examples/command.js
Normal file
@@ -0,0 +1,70 @@
|
||||
import 'dotenv/config'
|
||||
import express from 'express'
|
||||
import { InteractionType, InteractionResponseType } from 'discord-interactions';
|
||||
import { VerifyDiscordRequest } from './utils.js';
|
||||
import axios from 'axios';
|
||||
|
||||
// Create and configure express app
|
||||
const app = express();
|
||||
app.use(express.json({verify: VerifyDiscordRequest(process.env.PUBLIC_KEY)}));
|
||||
|
||||
app.post('/interactions', function (req, res) {
|
||||
// Interaction type and data
|
||||
let { 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() {
|
||||
let appId = process.env.APP_ID;
|
||||
let 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`);
|
||||
let 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
|
||||
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 command: ${err}`)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
app.listen(3000, () => {
|
||||
console.log('Listening on port 3000');
|
||||
|
||||
createCommand();
|
||||
});
|
||||
0
examples/modal.js
Normal file
0
examples/modal.js
Normal file
76
examples/selectMenu.js
Normal file
76
examples/selectMenu.js
Normal file
@@ -0,0 +1,76 @@
|
||||
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.post('/interactions', function (req, res) {
|
||||
// Interaction type and data
|
||||
let { 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
|
||||
let componentId = data.custom_id;
|
||||
|
||||
if (componentId === 'my_select') {
|
||||
console.log(req.body);
|
||||
|
||||
// Get selected option from payload
|
||||
let selectedOption = data.values[0];
|
||||
let userId = req.body.member.user.id;
|
||||
|
||||
// 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');
|
||||
});
|
||||
92
game.js
Normal file
92
game.js
Normal file
@@ -0,0 +1,92 @@
|
||||
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' };
|
||||
}
|
||||
|
||||
return formatResult(gameResult)
|
||||
}
|
||||
|
||||
function formatResult(result) {
|
||||
let { 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"
|
||||
}
|
||||
};
|
||||
|
||||
export function getRPSChoices() {
|
||||
return Object.keys(RPSChoices);
|
||||
}
|
||||
|
||||
// Function to fetch shuffled options for select menu
|
||||
export function getShuffledOptions() {
|
||||
let allChoices = getRPSChoices();
|
||||
let 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(choice),
|
||||
"value": c.toLowerCase(),
|
||||
"description": RPSChoices[c]["description"]
|
||||
});
|
||||
}
|
||||
|
||||
return options.sort(() => Math.random() - 0.5);
|
||||
}
|
||||
21
package.json
Normal file
21
package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "getting-started",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"axios": "^0.26.1",
|
||||
"discord-interactions": "^3.1.0",
|
||||
"dotenv": "^16.0.0",
|
||||
"express": "^4.17.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^2.0.15"
|
||||
}
|
||||
}
|
||||
40
utils.js
Normal file
40
utils.js
Normal file
@@ -0,0 +1,40 @@
|
||||
import { verifyKey } from 'discord-interactions';
|
||||
|
||||
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) {
|
||||
return res.status(401).end('Bad request signature');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function DiscordAPI(url) { return 'https://discord.com/api/v9/' + url };
|
||||
|
||||
// Simple method that returns a random emoji from list
|
||||
export function getRandomEmoji() {
|
||||
let emojiList = ['😭', '😄', '😌', '🤓', '😎', '😤', '🤖', '😶🌫️', '🌏', '📸', '💿', '👋', '🌊', '✨'];
|
||||
return emojiList[Math.floor(Math.random() * emojiList.length)];
|
||||
}
|
||||
|
||||
export function capitalize(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
export const ComponentType = {
|
||||
ACTION: 1,
|
||||
BUTTON: 2,
|
||||
SELECT: 3,
|
||||
INPUT: 4
|
||||
}
|
||||
|
||||
export const ButtonStyle = {
|
||||
PRIMARY: 1,
|
||||
SECONDARY: 2,
|
||||
SUCCESS: 3,
|
||||
DANGER: 4,
|
||||
LINK: 5
|
||||
}
|
||||
Reference in New Issue
Block a user