From be9854f423c9183d2b3a49b5a0b863dc81a96c38 Mon Sep 17 00:00:00 2001 From: Noah Heague Date: Fri, 20 Aug 2021 20:50:09 +1000 Subject: [PATCH 1/3] Added basic slash command for help --- bot/README.md | 2 +- bot/package.json | 3 +++ bot/src/index.ts | 33 +++++++++++++++++++++++++++++ bot/yarn.lock | 54 ++++++++++++++++++++++++++++++++++++++++++++++-- 4 files changed, 89 insertions(+), 3 deletions(-) diff --git a/bot/README.md b/bot/README.md index 78b129bb2..215e23f6c 100644 --- a/bot/README.md +++ b/bot/README.md @@ -2,7 +2,7 @@ Automatically translate streaming service links. -> [Invite the bot](https://discordapp.com/oauth2/authorize?client_id=686462477956808742&permissions=8&scope=bot) +> [Invite the bot](https://discord.com/api/oauth2/authorize?client_id=686462477956808742&permissions=0&scope=applications.commands%20bot) > [Join the Server](https://discord.gg/a5a7NQV) diff --git a/bot/package.json b/bot/package.json index 999ced7c1..60eb36e7a 100644 --- a/bot/package.json +++ b/bot/package.json @@ -12,11 +12,14 @@ "@types/jest": "^27.0.1", "@typescript-eslint/eslint-plugin": "^4.29.1", "@typescript-eslint/parser": "^4.29.1", + "discord-api-types": "^0.22.0", "eslint": "^7.32.0", "jest": "^27.0.6", "typescript": "^4.3.5" }, "dependencies": { + "@discordjs/builders": "^0.5.0", + "@discordjs/rest": "^0.1.0-canary.0", "deezer-api-ts": "^1.2.1", "discord.js": "^13.1.0", "songlink-api": "^1.0.0", diff --git a/bot/src/index.ts b/bot/src/index.ts index d6eee881d..4bf4a5878 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -1,4 +1,7 @@ import { Client, Intents } from "discord.js"; +import { REST } from "@discordjs/rest"; +import { Routes } from "discord-api-types/v9"; +import { SlashCommandBuilder } from "@discordjs/builders"; import { LoggerLayers, platformRegex } from "./config"; import { createLinkReply } from "./createEmbed/createEmbed"; import { getSongLinkResponse } from "./getSongLink/getSongLink"; @@ -37,8 +40,38 @@ client.on("messageCreate", async (message) => { return; }); +client.on("interactionCreate", async (interaction) => { + if (!interaction.isCommand()) return; + + if (interaction.commandName === "help") { + /** @TODO Prettify this! */ + await interaction.reply("Send a link to a streaming service and I will reply with a rich embed.\nPrepend youtube links with `?`\n\nGithub: https://github.com/Puffycheeses/music_linkr\nDiscord: https://discord.gg/a5a7NQV"); + } +}); + +const rest = new REST({ version: "9" }).setToken(process.env.DISCORDTOKEN); client.login(process.env.DISCORDTOKEN).then(() => { logger.info("Logged in successfully \n"); }).catch((err) => { logger.error("Log in Failed:", err); +}).then(() => { + client.guilds.cache.map(async (guild) => { + try { + logger.info(`Registering Slash command with ${guild.id}`); + await rest.put( + Routes.applicationGuildCommands(client.application.id, guild.id), + { + body: [ + new SlashCommandBuilder() + .setName("help") + .setDescription("Shows information about Music Linkr") + .toJSON() + ] + } + ); + } catch (e) { + logger.error(`Error on Guild: ${guild.id}`, e); + } + }); }); + diff --git a/bot/yarn.lock b/bot/yarn.lock index ed32a6063..636fba2f8 100644 --- a/bot/yarn.lock +++ b/bot/yarn.lock @@ -942,7 +942,7 @@ "@discordjs/builders@^0.5.0": version "0.5.0" - resolved "https://registry.npmjs.org/@discordjs/builders/-/builders-0.5.0.tgz" + resolved "https://registry.yarnpkg.com/@discordjs/builders/-/builders-0.5.0.tgz#646cbea9cc67f68639e6fb70ed1278b26dacdb14" integrity sha512-HP5y4Rqw68o61Qv4qM5tVmDbWi4mdTFftqIOGRo33SNPpLJ1Ga3KEIR2ibKofkmsoQhEpLmopD1AZDs3cKpHuw== dependencies: "@sindresorhus/is" "^4.0.1" @@ -951,6 +951,11 @@ ts-mixer "^6.0.0" tslib "^2.3.0" +"@discordjs/collection@^0.1.6": + version "0.1.6" + resolved "https://registry.yarnpkg.com/@discordjs/collection/-/collection-0.1.6.tgz#9e9a7637f4e4e0688fd8b2b5c63133c91607682c" + integrity sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ== + "@discordjs/collection@^0.2.1": version "0.2.1" resolved "https://registry.npmjs.org/@discordjs/collection/-/collection-0.2.1.tgz" @@ -965,6 +970,20 @@ combined-stream "^1.0.8" mime-types "^2.1.12" +"@discordjs/rest@^0.1.0-canary.0": + version "0.1.0-canary.0" + resolved "https://registry.yarnpkg.com/@discordjs/rest/-/rest-0.1.0-canary.0.tgz#666f9a1a0c1f2f5a09a3a79f77aeddaeafbcbcc1" + integrity sha512-d+s//ISYVV+e0w/926wMEeO7vju+Pn11x1JM4tcmVMCHSDgpi6pnFCNAXF1TEdnDcy7xf9tq5cf2pQkb/7ySTQ== + dependencies: + "@discordjs/collection" "^0.1.6" + "@sapphire/async-queue" "^1.1.4" + "@sapphire/snowflake" "^1.3.5" + abort-controller "^3.0.0" + discord-api-types "^0.18.1" + form-data "^4.0.0" + node-fetch "^2.6.1" + tslib "^2.3.0" + "@eslint/eslintrc@^0.4.3": version "0.4.3" resolved "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz" @@ -1205,6 +1224,11 @@ resolved "https://registry.npmjs.org/@sapphire/async-queue/-/async-queue-1.1.4.tgz" integrity sha512-fFrlF/uWpGOX5djw5Mu2Hnnrunao75WGey0sP0J3jnhmrJ5TAPzHYOmytD5iN/+pMxS+f+u/gezqHa9tPhRHEA== +"@sapphire/snowflake@^1.3.5": + version "1.3.6" + resolved "https://registry.yarnpkg.com/@sapphire/snowflake/-/snowflake-1.3.6.tgz#166e8c5c08d01c861edd7e2edc80b5739741715f" + integrity sha512-QnzuLp+p9D7agynVub/zqlDVriDza9y3STArBhNiNBUgIX8+GL5FpQxstRfw1jDr5jkZUjcuKYAHxjIuXKdJAg== + "@sindresorhus/is@^4.0.0", "@sindresorhus/is@^4.0.1": version "4.0.1" resolved "https://registry.npmjs.org/@sindresorhus/is/-/is-4.0.1.tgz" @@ -1465,6 +1489,13 @@ abab@^2.0.3, abab@^2.0.5: resolved "https://registry.npmjs.org/abab/-/abab-2.0.5.tgz" integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q== +abort-controller@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" + integrity sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg== + dependencies: + event-target-shim "^5.0.0" + acorn-globals@^6.0.0: version "6.0.0" resolved "https://registry.npmjs.org/acorn-globals/-/acorn-globals-6.0.0.tgz" @@ -2069,9 +2100,14 @@ dir-glob@^3.0.1: dependencies: path-type "^4.0.0" +discord-api-types@^0.18.1: + version "0.18.1" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.18.1.tgz#5d08ed1263236be9c21a22065d0e6b51f790f492" + integrity sha512-hNC38R9ZF4uaujaZQtQfm5CdQO58uhdkoHQAVvMfIL0LgOSZeW575W8H6upngQOuoxWd8tiRII3LLJm9zuQKYg== + discord-api-types@^0.22.0: version "0.22.0" - resolved "https://registry.npmjs.org/discord-api-types/-/discord-api-types-0.22.0.tgz" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.22.0.tgz#34dc57fe8e016e5eaac5e393646cd42a7e1ccc2a" integrity sha512-l8yD/2zRbZItUQpy7ZxBJwaLX/Bs2TGaCthRppk8Sw24LOIWg12t9JEreezPoYD0SQcC2htNNo27kYEpYW/Srg== discord.js@^13.1.0: @@ -2296,6 +2332,11 @@ esutils@^2.0.2: resolved "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz" integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g== +event-target-shim@^5.0.0: + version "5.0.1" + resolved "https://registry.yarnpkg.com/event-target-shim/-/event-target-shim-5.0.1.tgz#5d4d3ebdf9583d63a5333ce2deb7480ab2b05789" + integrity sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ== + execa@^5.0.0: version "5.1.1" resolved "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz" @@ -2434,6 +2475,15 @@ form-data@^3.0.0: combined-stream "^1.0.8" mime-types "^2.1.12" +form-data@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-4.0.0.tgz#93919daeaf361ee529584b9b31664dc12c9fa452" + integrity sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww== + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz" From 25ee1137852f253d7448deb783fbff1e19793870 Mon Sep 17 00:00:00 2001 From: Noah Heague Date: Sat, 21 Aug 2021 12:19:42 +1000 Subject: [PATCH 2/3] Better slash command handling --- bot/.eslintrc.json | 4 ++ bot/src/config.ts | 3 +- bot/src/help/help.ts | 5 +++ bot/src/index.ts | 53 +++++++++++-------------- bot/src/slashCommands/register.ts | 64 +++++++++++++++++++++++++++++++ 5 files changed, 97 insertions(+), 32 deletions(-) create mode 100644 bot/src/help/help.ts create mode 100644 bot/src/slashCommands/register.ts diff --git a/bot/.eslintrc.json b/bot/.eslintrc.json index 31be53b9b..1e9e45d15 100644 --- a/bot/.eslintrc.json +++ b/bot/.eslintrc.json @@ -31,6 +31,10 @@ "semi": [ "error", "always" + ], + "object-curly-spacing": [ + "error", + "always" ] } } diff --git a/bot/src/config.ts b/bot/src/config.ts index f320bfb46..f76fa5026 100644 --- a/bot/src/config.ts +++ b/bot/src/config.ts @@ -6,7 +6,8 @@ export const allowedChannels = [ export enum LoggerLayers { client = "client", songLink = "songLink", - embedGenerator = "embedGenerator" + embedGenerator = "embedGenerator", + slashCommands = "slashCommands" } export const platformRegex = [ diff --git a/bot/src/help/help.ts b/bot/src/help/help.ts new file mode 100644 index 000000000..f2151f795 --- /dev/null +++ b/bot/src/help/help.ts @@ -0,0 +1,5 @@ +import { CommandInteraction } from "discord.js"; + +export const help = async (interaction: CommandInteraction): Promise => { + await interaction.reply("Send a link to a streaming service and I will reply with a rich embed.\nPrepend youtube links with `?`\n\nGithub: https://github.com/Puffycheeses/music_linkr\nDiscord: https://discord.gg/a5a7NQV"); +}; diff --git a/bot/src/index.ts b/bot/src/index.ts index 4bf4a5878..06dbbb987 100644 --- a/bot/src/index.ts +++ b/bot/src/index.ts @@ -1,10 +1,8 @@ import { Client, Intents } from "discord.js"; -import { REST } from "@discordjs/rest"; -import { Routes } from "discord-api-types/v9"; -import { SlashCommandBuilder } from "@discordjs/builders"; import { LoggerLayers, platformRegex } from "./config"; import { createLinkReply } from "./createEmbed/createEmbed"; import { getSongLinkResponse } from "./getSongLink/getSongLink"; +import { registerSlashCommands, slashCommands, tidyGuildCommands, SlashCommand } from "./slashCommands/register"; import { createLayeredLogger } from "./utils/logger"; import { shouldReply } from "./utils/utils"; @@ -42,36 +40,29 @@ client.on("messageCreate", async (message) => { client.on("interactionCreate", async (interaction) => { if (!interaction.isCommand()) return; + try { + const command = slashCommands.find((command: SlashCommand) => { + return command.info.name === interaction.commandName; + }); - if (interaction.commandName === "help") { - /** @TODO Prettify this! */ - await interaction.reply("Send a link to a streaming service and I will reply with a rich embed.\nPrepend youtube links with `?`\n\nGithub: https://github.com/Puffycheeses/music_linkr\nDiscord: https://discord.gg/a5a7NQV"); + if (command && command.exec) { + logger.info(`Running ${command.info.name} interaction`); + command.exec(interaction); + } + } catch (e) { + logger.error("Failed to run interaction"); } }); -const rest = new REST({ version: "9" }).setToken(process.env.DISCORDTOKEN); -client.login(process.env.DISCORDTOKEN).then(() => { - logger.info("Logged in successfully \n"); -}).catch((err) => { - logger.error("Log in Failed:", err); -}).then(() => { - client.guilds.cache.map(async (guild) => { - try { - logger.info(`Registering Slash command with ${guild.id}`); - await rest.put( - Routes.applicationGuildCommands(client.application.id, guild.id), - { - body: [ - new SlashCommandBuilder() - .setName("help") - .setDescription("Shows information about Music Linkr") - .toJSON() - ] - } - ); - } catch (e) { - logger.error(`Error on Guild: ${guild.id}`, e); - } - }); -}); +(async () => { + try { + await client.login(process.env.DISCORDTOKEN); + logger.info(`Logged in successfully to ${client.guilds.cache.size} guilds`); + } catch (e) { + logger.error("Log in Failed:", e); + } finally { + await tidyGuildCommands(client); + await registerSlashCommands(client); + } +})(); diff --git a/bot/src/slashCommands/register.ts b/bot/src/slashCommands/register.ts new file mode 100644 index 000000000..7ef04e8ab --- /dev/null +++ b/bot/src/slashCommands/register.ts @@ -0,0 +1,64 @@ +import { SlashCommandBuilder } from "@discordjs/builders"; +import { REST } from "@discordjs/rest"; +import { APIApplicationCommandOption } from "discord-api-types"; +import { Routes } from "discord-api-types/v9"; +import { Client, CommandInteraction, Interaction } from "discord.js"; +import { LoggerLayers } from "../config"; +import { help } from "../help/help"; +import { createLayeredLogger } from "../utils/logger"; + +const logger = createLayeredLogger(LoggerLayers.slashCommands); + +export interface SlashCommand { + info: { + name: string; + description: string; + options: APIApplicationCommandOption[]; + }; + exec(interaction: CommandInteraction): Promise; +} + +export const slashCommands: SlashCommand[] = [ + { + info: new SlashCommandBuilder() + .setName("help") + .setDescription("Shows information about Music Linkr") + .toJSON(), + exec: async (interaction: CommandInteraction) => await help(interaction), + } +]; + +const rest = new REST({ version: "9" }).setToken(process.env.DISCORDTOKEN); +export const registerSlashCommands = async (client: Client): Promise => { + try { + logger.info("Registering slash commands"); + await rest.put( + Routes.applicationCommands(client.application.id), + { + body: slashCommands.map(command => command.info) + } + ); + } catch (e) { + logger.error("Failed to register slash commands", e); + } finally { + logger.info("Successfully registered slash commands"); + } +}; + +/** @deprecated Remove once no guilds have legacy commands */ +export const tidyGuildCommands = async (client: Client): Promise => { + try { + logger.info("Removing legacy slash commands"); + const guilds = client.guilds.cache; + guilds.forEach(guild => { + const commands = guild.commands.cache; + commands.forEach(command => { + command.delete(); + }); + }); + } catch (e) { + logger.error("Failed to remove legacy slash commands"); + } finally { + logger.info("Successfully removed legacy slash commands"); + } +}; From 038c01dedf69624bc187ba1cdff7b5ee070627b9 Mon Sep 17 00:00:00 2001 From: Noah Heague Date: Sat, 21 Aug 2021 12:24:06 +1000 Subject: [PATCH 3/3] Fixed ESLint errors --- bot/.eslintrc.json | 3 +++ bot/babel.config.js | 1 + bot/src/config.ts | 1 + bot/src/getSongLink/getSongLink.test.ts | 2 +- bot/src/getSongLink/getSongLink.ts | 2 +- bot/src/slashCommands/register.ts | 2 +- bot/src/utils/logger.ts | 4 ++-- bot/src/utils/utils.ts | 1 - 8 files changed, 10 insertions(+), 6 deletions(-) diff --git a/bot/.eslintrc.json b/bot/.eslintrc.json index 1e9e45d15..b920e4ce3 100644 --- a/bot/.eslintrc.json +++ b/bot/.eslintrc.json @@ -35,6 +35,9 @@ "object-curly-spacing": [ "error", "always" + ], + "@typescript-eslint/no-unused-vars": [ + "error" ] } } diff --git a/bot/babel.config.js b/bot/babel.config.js index d7f99df07..03f48c7c1 100644 --- a/bot/babel.config.js +++ b/bot/babel.config.js @@ -1,3 +1,4 @@ +/* eslint-disable no-undef */ module.exports = { presets: [ [ diff --git a/bot/src/config.ts b/bot/src/config.ts index f76fa5026..2fe5d070d 100644 --- a/bot/src/config.ts +++ b/bot/src/config.ts @@ -1,3 +1,4 @@ +// noinspection JSUnusedGlobalSymbols /** @deprecated replace with database call & cache */ export const allowedChannels = [ "876321394005254214" diff --git a/bot/src/getSongLink/getSongLink.test.ts b/bot/src/getSongLink/getSongLink.test.ts index 129de741c..6132fb8df 100644 --- a/bot/src/getSongLink/getSongLink.test.ts +++ b/bot/src/getSongLink/getSongLink.test.ts @@ -1,5 +1,5 @@ import { SonglinkResponse } from "songlink-api/lib/types/Response"; -import {getDetailedMetadata, getIdFromEntity} from "./getSongLink"; +import { getDetailedMetadata, getIdFromEntity } from "./getSongLink"; interface MockSonglinkResponse extends Omit, "entitiesByUniqueId"> { linksByPlatform: { diff --git a/bot/src/getSongLink/getSongLink.ts b/bot/src/getSongLink/getSongLink.ts index 6a54c79d0..d862b47b8 100644 --- a/bot/src/getSongLink/getSongLink.ts +++ b/bot/src/getSongLink/getSongLink.ts @@ -84,7 +84,7 @@ export const getSongLinkResponse = async (url: string): Promise => logger.info("Fetching songLink"); try { - const rawData = await getLinks({url: url}, {apiKey: process.env.SONGLINKAPIKEY}); + const rawData = await getLinks({ url: url }, { apiKey: process.env.SONGLINKAPIKEY }); const firstEntity = rawData.entitiesByUniqueId[Object.keys(rawData.entitiesByUniqueId)[0]]; const basicMetadata: MetaData = { diff --git a/bot/src/slashCommands/register.ts b/bot/src/slashCommands/register.ts index 7ef04e8ab..7689cba8f 100644 --- a/bot/src/slashCommands/register.ts +++ b/bot/src/slashCommands/register.ts @@ -2,7 +2,7 @@ import { SlashCommandBuilder } from "@discordjs/builders"; import { REST } from "@discordjs/rest"; import { APIApplicationCommandOption } from "discord-api-types"; import { Routes } from "discord-api-types/v9"; -import { Client, CommandInteraction, Interaction } from "discord.js"; +import { Client, CommandInteraction } from "discord.js"; import { LoggerLayers } from "../config"; import { help } from "../help/help"; import { createLayeredLogger } from "../utils/logger"; diff --git a/bot/src/utils/logger.ts b/bot/src/utils/logger.ts index 73a89792c..d4150440f 100644 --- a/bot/src/utils/logger.ts +++ b/bot/src/utils/logger.ts @@ -2,7 +2,7 @@ import { createLogger, transports, format, Logger } from "winston"; import { LoggerLayers } from "../config"; const { combine, timestamp, label, printf, align, colorize } = format; -const loggerFormat = printf(({level, message, label, timestamp}) => { +const loggerFormat = printf(({ level, message, label, timestamp }) => { return `${timestamp} [${label}] ${level} ${message}`; }); @@ -11,7 +11,7 @@ export const createLayeredLogger = (layer: LoggerLayers): Logger => { return createLogger({ transports: [ new transports.Console() ], format: combine( - label({label: layer}), + label({ label: layer }), colorize(), timestamp(), align(), diff --git a/bot/src/utils/utils.ts b/bot/src/utils/utils.ts index 954cf0cf4..42b3a415b 100644 --- a/bot/src/utils/utils.ts +++ b/bot/src/utils/utils.ts @@ -1,5 +1,4 @@ import { Message } from "discord.js"; -import { allowedChannels } from "../config"; export const shouldReply = (message: Message): boolean => message.author.id !== "876320894295887944" &&