From 56091a6df7741e30b7607b7b8380503ec1c69b2f Mon Sep 17 00:00:00 2001 From: spiral Date: Fri, 21 Oct 2022 17:22:01 +0000 Subject: [PATCH] rewrite this is a rewrite of the bot in typescript. detritus is used as a discord library instead of discord.js --- Dockerfile | 4 +- bot.js | 45 ----- bot.ts | 48 +++++ command/avatar.js | 39 ---- command/avatar.ts | 25 +++ command/changelog.js | 66 ------ command/changelog.ts | 67 +++++++ command/clean.js | 17 -- command/clean.ts | 25 +++ command/embed.js | 182 ----------------- command/eval.js | 40 ---- command/eval.ts | 40 ++++ command/help.js | 132 ------------ command/help.ts | 125 ++++++++++++ command/{ticket.js => ticket.ts} | 30 +-- config.json => config.ts | 2 +- event/guild-member-add.js | 7 - event/message.js | 12 -- event/ready.js | 19 -- messageHandler.ts | 46 +++++ messages.json | 3 +- model/command-category.js | 8 - model/command-category.ts | 9 + model/command-permission.js | 36 ---- model/command-permission.ts | 9 + model/command.js | 105 ---------- model/guild.js | 173 ---------------- model/jira.js | 37 ---- model/jira.ts | 30 +++ model/messages.js | 65 ------ model/messages.ts | 58 ++++++ package.json | 9 +- yarn.lock | 334 +++++++++++++++++++++---------- 33 files changed, 731 insertions(+), 1116 deletions(-) delete mode 100644 bot.js create mode 100644 bot.ts delete mode 100644 command/avatar.js create mode 100644 command/avatar.ts delete mode 100644 command/changelog.js create mode 100644 command/changelog.ts delete mode 100644 command/clean.js create mode 100644 command/clean.ts delete mode 100644 command/embed.js delete mode 100644 command/eval.js create mode 100644 command/eval.ts delete mode 100644 command/help.js create mode 100644 command/help.ts rename command/{ticket.js => ticket.ts} (55%) rename config.json => config.ts (96%) delete mode 100644 event/guild-member-add.js delete mode 100644 event/message.js delete mode 100644 event/ready.js create mode 100644 messageHandler.ts delete mode 100644 model/command-category.js create mode 100644 model/command-category.ts delete mode 100644 model/command-permission.js create mode 100644 model/command-permission.ts delete mode 100644 model/command.js delete mode 100644 model/guild.js delete mode 100644 model/jira.js create mode 100644 model/jira.ts delete mode 100644 model/messages.js create mode 100644 model/messages.ts diff --git a/Dockerfile b/Dockerfile index 3623ef7..ac798dc 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ FROM alpine:latest -RUN apk add nodejs-current yarn +RUN apk add nodejs-current npm yarn git WORKDIR /app COPY package.json yarn.lock /app/ @@ -8,4 +8,4 @@ RUN yarn COPY . /app -CMD ["node", "bot.js"] \ No newline at end of file +CMD ["yarn", "ts-node", "bot.ts"] \ No newline at end of file diff --git a/bot.js b/bot.js deleted file mode 100644 index ac979a3..0000000 --- a/bot.js +++ /dev/null @@ -1,45 +0,0 @@ -const Logger = require('@lilywonhalf/pretty-logger'); - -const { Client } = require('discord.js'); -const Config = require('./config.json'); -const Command = require('./model/command'); -const fs = require('fs'); -const dotenv = require('dotenv') - -dotenv.config(); - -global.bot = new Client({ fetchAllMembers: true }); -global.isRightGuild = (guildSnowflake) => guildSnowflake === Config.guild; - -const crashRecover = (exception) => { - Logger.exception(exception); - Logger.notice('Need reboot'); -}; - -process.on('uncaughtException', crashRecover); -bot.on('error', crashRecover); - -Command.init(); - -const help = require("./command/help"); -bot.ws.on('INTERACTION_CREATE', help.interactionHandler); - -bot.on('ready', () => { - fs.readdirSync('./event/') - .filter(filename => filename.endsWith('.js')) - .map(filename => filename.substr(0, filename.length - 3)) - .forEach(filename => { - const event = filename.replace(/([_-][a-z])/gu, character => `${character.substr(1).toUpperCase()}`); - - if (filename !== 'ready') { - bot.on(event, require(`./event/${filename}`)); - } else { - require(`./event/${filename}`)(); - } - }); -}); - -Logger.info('--------'); - -Logger.info('Logging in...'); -bot.login(process.env.token); diff --git a/bot.ts b/bot.ts new file mode 100644 index 0000000..bcbf8f0 --- /dev/null +++ b/bot.ts @@ -0,0 +1,48 @@ +import * as dotenv from 'dotenv'; +dotenv.config(); + +import config from "./config"; + +import { Socket } from 'detritus-client-socket/lib/gateway'; +import { Client } from "detritus-client-rest"; + +import * as types from 'discord-api-types/v10'; + +const socket = new Socket(process.env.token!, { intents: 33283 }); +export const restClient = new Client(process.env.token); + +import handleMessage from './messageHandler'; +import help from './command/help'; + +socket.on('packet', async (evt: types.GatewayDispatchPayload) => handleEvtInner(evt).catch(e => console.error(e, JSON.stringify(e, null, 2)))); + +async function handleEvtInner(evt: types.GatewayDispatchPayload) { + if (evt.op != 0) return; + if (evt.t == 'READY') { + console.log("successfully logged in:", evt.d.user); + } + + // i am quite sure this is the correct type + // @ts-expect-error + if (evt.t == 'INTERACTION_CREATE') await help.interactionHandler(evt.d); + + if (evt.t == 'GUILD_MEMBER_ADD') { + if (evt.d.guild_id != config.guild) return; + if (evt.d.user!.bot) return; + + const msg = `Welcome, <@${evt.d.user!.id}>! If you joined for any specific support questions ` + + `please check out <#863171642905591830> first to see if your issue is known, ` + + `and make sure that your app is up-to-date before posting.`; + await restClient.createMessage(config.channels.joins, { content: msg }); + } + + if (evt.t == 'MESSAGE_CREATE') { + if (evt.d.guild_id != config.guild) return; + if (evt.d.author.bot) return; + await handleMessage(evt.d).catch(console.error); + } +}; + + +console.log('Logging in...'); +socket.connect("https://gateway.discord.gg"); \ No newline at end of file diff --git a/command/avatar.js b/command/avatar.js deleted file mode 100644 index c88cc05..0000000 --- a/command/avatar.js +++ /dev/null @@ -1,39 +0,0 @@ -const Logger = require('@lilywonhalf/pretty-logger'); -const Discord = require('discord.js'); -const CommandCategory = require('../model/command-category'); -const Guild = require('../model/guild'); - -module.exports = { - aliases: ['av'], - category: CommandCategory.FUN, - isAllowedForContext: () => true, - description: 'Displays the avatar of the specified member.', - process: async (message, args) => { - let user = null; - - if (args.length > 0) { - const result = Guild.findDesignatedMemberInMessage(message); - - if (result.foundMembers.length > 0) { - if (result.foundMembers[0].user !== undefined) { - user = result.foundMembers[0].user; - } else { - user = result.foundMembers[0]; - } - } - } else { - user = message.author; - } - - if (user !== null) { - const url = user.displayAvatarURL({ dynamic: true }); - - message.channel.send(new Discord.MessageAttachment( - url + '?size=2048', - user.id + url.substr(url.lastIndexOf('.')) - )).catch(error => Logger.warning(error.toString())); - } else { - message.reply('I... Have no idea who that could be, sorry.'); - } - } -} diff --git a/command/avatar.ts b/command/avatar.ts new file mode 100644 index 0000000..1c15034 --- /dev/null +++ b/command/avatar.ts @@ -0,0 +1,25 @@ +import { APIGuildMember, APIUser, GatewayMessageCreateDispatchData } from "discord-api-types/v10"; +import { restClient } from "../bot"; +import CommandCategory from "../model/command-category"; + +export default { + aliases: ['avatar', 'av'], + category: CommandCategory.FUN, + isAllowedForContext: (_: GatewayMessageCreateDispatchData) => true, + description: 'Displays the avatar of the specified member.', + process: async (message: GatewayMessageCreateDispatchData, args: string[]) => { + let user: APIUser; + + if (message.mentions.length == 0) + user = message.author; + + else if (message.mentions.length > 1) + return restClient.createMessage(message.channel_id, `<@${message.author.id}>, there are too many mentions in this message! ` + +`please pick only one user whose avatar you want to show.`); + + else user = message.mentions[0]; + + const url = `https://cdn.discordapp.com/avatars/${user.id}/${user.avatar}.png?size=2048`; + await restClient.createMessage(message.channel_id, { embed: { image: { url } } }); + } +} diff --git a/command/changelog.js b/command/changelog.js deleted file mode 100644 index 401b229..0000000 --- a/command/changelog.js +++ /dev/null @@ -1,66 +0,0 @@ -const Logger = require('@lilywonhalf/pretty-logger'); -const Config = require('../config.json'); -const CommandCategory = require('../model/command-category'); -const CommandPermission = require('../model/command-permission'); -const { search } = require('../model/jira'); - -const MAX_CHARACTERS = 1950; - -module.exports = { - aliases: ['change-log', 'cl'], - category: CommandCategory.MODERATION, - isAllowedForContext: CommandPermission.isMemberMod, - description: 'Builds the changelog for a given version', - process: async (message, args) => { - - const errorHandler = async (error) => { - if (error) { - Logger.exception(error); - } - - await message.reactions.removeAll(); - await message.react('❌'); - } - - await message.react('⏳').catch(() => {}); - const issues = args.length > 0 && args[0] ? await search(args[0]).catch(errorHandler) : await search().catch(errorHandler); - - const taskType = Config.jira.issueTypes.task; - const bugType = Config.jira.issueTypes.bug; - const features = issues.filter(issue => parseInt(issue.fields.issuetype.id) === taskType); - const bugs = issues.filter(issue => parseInt(issue.fields.issuetype.id) === bugType); - - if (features.length < 1 && bugs.length < 1) { - await message.channel.send('No issues found.'); - await message.reactions.removeAll().catch(() => {}); - - return; - } - - const output = `${features.map( - issue => `* Feature: ${issue.key} - ${issue.fields.summary}` - ).join('\n')}\n\n${bugs.map( - issue => `* Fixed: ${issue.key} - ${issue.fields.summary}` - ).join('\n')}`.trim(); - const messages = []; - let currentMessage = '```'; - - for (let line of output.split('\n')) { - if (currentMessage.length + line.length >= MAX_CHARACTERS) { - messages.push(`${currentMessage}\`\`\``); - currentMessage = '```'; - } - - currentMessage = `${currentMessage}\n${line}`; - } - - messages.push(`${currentMessage}\`\`\``); - - for (let messageToSend of messages) { - await message.channel.send(messageToSend).catch(() => {}); - } - - await message.reactions.removeAll().catch(() => {}); - await message.react('✔').catch(() => {}); - } -} diff --git a/command/changelog.ts b/command/changelog.ts new file mode 100644 index 0000000..ca86016 --- /dev/null +++ b/command/changelog.ts @@ -0,0 +1,67 @@ +import { GatewayMessageCreateDispatchData } from 'discord-api-types/v10'; + +import CommandCategory from '../model/command-category'; +import * as CommandPermission from '../model/command-permission'; + +import config from '../config'; +import { restClient } from '../bot'; + +import search from '../model/jira'; + +const MAX_CHARACTERS = 1950; + +export default { + aliases: ['changelog', 'change-log', 'cl'], + category: CommandCategory.MODERATION, + isAllowedForContext: CommandPermission.isMemberMod, + description: 'Builds the changelog for a given version', + process: async (message: GatewayMessageCreateDispatchData, args: string[]) => { + const errorHandler = async (error: any) => { + await restClient.deleteReactions(message.channel_id, message.id); + await restClient.createReaction(message.channel_id, message.id, '❌'); + + throw error; + } + + await restClient.createReaction(message.channel_id, message.id, '⏳').catch(() => {}); + const issues = args.length > 0 && args[0] ? await search(args[0]).catch(errorHandler) : await search().catch(errorHandler); + + const taskType = config.jira.issueTypes.task; + const bugType = config.jira.issueTypes.bug; + const features = issues?.filter(issue => parseInt(issue.fields.issuetype.id) === taskType) ?? []; + const bugs = issues?.filter(issue => parseInt(issue.fields.issuetype.id) === bugType) ?? []; + + if (features.length < 1 && bugs.length < 1) { + await restClient.createMessage(message.channel_id, 'No issues found.'); + await restClient.deleteReactions(message.channel_id, message.id); + + return; + } + + const output = `${features.map( + issue => `* Feature: ${issue.key} - ${issue.fields.summary}` + ).join('\n')}\n\n${bugs.map( + issue => `* Fixed: ${issue.key} - ${issue.fields.summary}` + ).join('\n')}`.trim(); + const messages: string[] = []; + let currentMessage = '```'; + + for (let line of output.split('\n')) { + if (currentMessage.length + line.length >= MAX_CHARACTERS) { + messages.push(`${currentMessage}\`\`\``); + currentMessage = '```'; + } + + currentMessage = `${currentMessage}\n${line}`; + } + + messages.push(`${currentMessage}\`\`\``); + + for (let messageToSend of messages) { + await restClient.createMessage(message.channel_id, messageToSend); + } + + await restClient.deleteReactions(message.channel_id, message.id).catch(() => {}); + await restClient.createReaction(message.channel_id, message.id, '✔'); + } +} diff --git a/command/clean.js b/command/clean.js deleted file mode 100644 index b7f4483..0000000 --- a/command/clean.js +++ /dev/null @@ -1,17 +0,0 @@ -const Config = require('../config.json'); -const CommandCategory = require('../model/command-category'); -const CommandPermission = require('../model/command-permission'); - -module.exports = { - aliases: ['clear', 'purge'], - category: CommandCategory.MODERATION, - isAllowedForContext: CommandPermission.isMemberMod, - description: 'Deletes messages in bulk.', - process: async (message, args) => { - if (args.length > 0 && parseInt(args[0]) > 0) { - await message.channel.bulkDelete(Math.min(parseInt(args[0]) + 1, 100)); - } else { - message.reply(`You have to tell me how many messages I should clean. \`${Config.prefix}clean 10\` for example.`); - } - } -} diff --git a/command/clean.ts b/command/clean.ts new file mode 100644 index 0000000..d4a1a7b --- /dev/null +++ b/command/clean.ts @@ -0,0 +1,25 @@ +import { APIMessage, GatewayMessageCreateDispatchData } from 'discord-api-types/v10'; + +import CommandCategory from '../model/command-category'; +import * as CommandPermission from '../model/command-permission'; + +import config from '../config'; +import { restClient } from '../bot'; + +export default { + aliases: ['clean', 'clear', 'purge'], + category: CommandCategory.MODERATION, + isAllowedForContext: CommandPermission.isMemberMod, + description: 'Deletes messages in bulk.', + process: async (message: GatewayMessageCreateDispatchData, args: string[]) => { + if (args.length > 0 && parseInt(args[0]) > 0) { + // sometimes abstractions are nice... + // await message.channel.bulkDelete(Math.min(parseInt(args[0]) + 1, 100)); + + const messages = await restClient.fetchMessages(message.channel_id, { limit: Math.min(parseInt(args[0]) + 1, 100) }); + restClient.bulkDeleteMessages(message.channel_id, messages.map((x: APIMessage) => x.id)); + } else { + restClient.createMessage(message.channel_id, `You have to tell me how many messages I should clean. \`${config.prefix}clean 10\` for example.`); + } + } +} diff --git a/command/embed.js b/command/embed.js deleted file mode 100644 index 1dcbf1c..0000000 --- a/command/embed.js +++ /dev/null @@ -1,182 +0,0 @@ -const { MessageEmbed, Message } = require('discord.js'); -const CommandCategory = require('../model/command-category'); -const CommandPermission = require('../model/command-permission'); - -const RGB_REGEX = /(\d{1,3})\D*,\D*(\d{1,3})\D*,\D*(\d{1,3})\D*/u; - -class EmbedDialog -{ - /** - * @param {Message} message - */ - constructor(message) { - this.embed = new MessageEmbed(); - this.message = message; - this.channel = message.channel; - this.destinationChannel = null; - - this.prompt = (question, hideSkip = false) => { - return this.channel.send(`${question}\n*${!hideSkip ? '`skip` to skip, ' : ''}\`cancel\` to cancel*`); - }; - - this.isMessageSkip = message => message.content.toLowerCase() === 'skip'; - - this.messageFilter = testedMessage => { - const byAuthor = testedMessage.author.id === message.author.id; - const hasContent = testedMessage.cleanContent.trim().length > 0; - - return byAuthor && hasContent; - } - - this.channelMessageFilter = testedMessage => { - return this.messageFilter(testedMessage) - && (this.isMessageSkip(testedMessage) || testedMessage.mentions.channels.size > 0); - } - - this.returnFirstOfCollection = collection => collection && collection.size ? collection.first() : null; - this.awaitOptions = { max: 1, time: 5 * MINUTE, errors: ['time'] }; - - /** - * @param {function} filter - * @returns {Promise} - */ - this.awaitMessage = (filter = this.messageFilter) => { - return this.channel.awaitMessages( - filter, - this.awaitOptions - ).then(async collection => { - let message = this.returnFirstOfCollection(collection); - - if (message && message.content.toLowerCase() === 'cancel') { - message = null; - await this.channel.send('Cancelling embed creation.'); - } - - return message; - }).catch(async () => { - await this.channel.send('Time out, cancelling embed creation.'); - }); - }; - } - - async execute() { - let confirmation = true; - - await this.prompt('#️⃣ In which **channel** would you like this embed to be posted?'); - const channelMessage = await this.awaitMessage(this.channelMessageFilter); - - if (!channelMessage) { - return null; - } - - if (this.isMessageSkip(channelMessage)) { - this.destinationChannel = this.channel; - confirmation = false; - } else { - this.destinationChannel = channelMessage.mentions.channels.first(); - } - - await this.prompt('📰 What do you want the **title** of this embed to be (you can also ping someone so it appears as they are saying what is going to be the description)?'); - const titleMessage = await this.awaitMessage(this.messageFilter); - - if (!titleMessage) { - return null; - } - - if (!this.isMessageSkip(titleMessage)) { - if (titleMessage.mentions.members.size > 0) { - const member = titleMessage.mentions.members.first(); - this.embed.setAuthor(member.displayName, member.user.displayAvatarURL({dynamic: true})); - } else { - this.embed.setTitle(titleMessage.content); - } - } - - await this.prompt('🎨 What do you want the **color** of this embed to be?'); - const colourMessage = await this.awaitMessage(this.messageFilter); - - if (!colourMessage) { - return null; - } - - if (this.isMessageSkip(colourMessage)) { - this.embed.setColor(APP_MAIN_COLOUR); - } else { - if (colourMessage.content.startsWith('#')) { - this.embed.setColor(parseInt(colourMessage.content.substr(1), 16)); - } else if (colourMessage.content.startsWith('0x')) { - this.embed.setColor(parseInt(colourMessage.content.substr(2), 16)); - } else if (RGB_REGEX.test(colourMessage.content)) { - const [, red, green, blue] = colourMessage.content.match(RGB_REGEX); - this.embed.setColor([parseInt(red), parseInt(green), parseInt(blue)]); - } else { - this.embed.setColor(colourMessage.content.toUpperCase().replace(/[^A-Z]+/gu, '_')); - } - } - - await this.prompt('💬 What do you want the **description** (contents) of this embed to be?', true); - const descriptionMessage = await this.awaitMessage(this.messageFilter); - - if (!descriptionMessage) { - return null; - } - - this.embed.setDescription(descriptionMessage.content); - await this.destinationChannel.send(this.embed); - - if (confirmation) { - return this.channel.send(`✅ The embed has been posted in ${this.destinationChannel}.`); - } - } -} - -class Embed -{ - static instance = null; - - constructor() { - if (Embed.instance !== null) { - return Embed.instance; - } - - this.aliases = []; - this.category = CommandCategory.MODERATION; - this.isAllowedForContext = CommandPermission.isMemberModOrHelper; - this.description = 'Allows to post an embed'; - } - - /** - * @param {Message} message - * @param {Array} args - */ - async process(message, args) { - if (args.length > 0) { - let destinationChannel = message.channel; - let deleteMessage = true; - - if (/<#\d+>/u.test(args[0])) { - destinationChannel = message.mentions.channels.first(); - args.shift(); - deleteMessage = false; - } - - const embed = new MessageEmbed(); - - embed.setColor(APP_MAIN_COLOUR); - embed.setDescription(args.join(' ')); - await destinationChannel.send(embed); - - if (deleteMessage) { - await message.delete(); - } else { - await message.react('✅'); - } - } else { - const dialog = new EmbedDialog(message); - - return dialog.execute(); - } - } -} - -module.exports = new Embed(); diff --git a/command/eval.js b/command/eval.js deleted file mode 100644 index 23a9f4b..0000000 --- a/command/eval.js +++ /dev/null @@ -1,40 +0,0 @@ -const Discord = require('discord.js'); -const Logger = require('@lilywonhalf/pretty-logger'); -const Config = require('../config.json'); -const CommandCategory = require('../model/command-category'); -const CommandPermission = require('../model/command-permission'); - -// Including every model here so that it's ready to be used by the command -const Guild = require('../model/guild'); - -const JAVASCRIPT_LOGO_URL = 'https://i.discord.fr/IEV8.png'; - -module.exports = { - aliases: [], - category: CommandCategory.BOT_MANAGEMENT, - isAllowedForContext: CommandPermission.isMommy, - process: async (message) => { - const code = message.content - .substr(Config.prefix.length + 'eval'.length) - .trim() - .replace(/(`{3})js\n(.+)\n\1/iu, '$2') - .trim(); - - await message.react('✔'); - Logger.notice('Eval: ' + code); - let output = null; - - try { - output = eval(`${code}`); // Spoopy! 🎃 🦇 👻 ☠ 🕷 - } catch (exception) { - output = `**${exception.name}: ${exception.message}**\n${exception.stack}`; - } - - const embed = new Discord.MessageEmbed() - .setAuthor('Eval', JAVASCRIPT_LOGO_URL) - .setColor(0x00FF00) - .setDescription(output); - - message.channel.send(embed).catch(error => Logger.warning(error.toString())); - } -}; diff --git a/command/eval.ts b/command/eval.ts new file mode 100644 index 0000000..ed5554e --- /dev/null +++ b/command/eval.ts @@ -0,0 +1,40 @@ +import CommandCategory from "../model/command-category"; +import * as CommandPermission from '../model/command-permission'; + +import config from "../config"; +import { APIMessage } from "discord-api-types/v10"; +import { restClient } from "../bot"; + +const JAVASCRIPT_LOGO_URL = 'https://i.discord.fr/IEV8.png'; + +export default { + aliases: ['eval'], + category: CommandCategory.BOT_MANAGEMENT, + isAllowedForContext: CommandPermission.isMommy, + process: async (message: APIMessage) => { + const code = message.content + .substr(config.prefix.length + 'eval'.length) + .trim() + .replace(/(`{3})js\n(.+)\n\1/iu, '$2') + .trim(); + + console.log('Eval: ' + code); + let output: string | null = null; + + try { + output = eval(`${code}`); // Spoopy! 🎃 🦇 👻 ☠ 🕷 + } catch (exception) { + // @ts-expect-error + output = `**${exception.name}: ${exception.message}**\n${exception.stack}`; + } + + restClient.createMessage(message.channel_id, { embed: { + author: { + name: 'Eval', + iconUrl: JAVASCRIPT_LOGO_URL, + }, + color: 0x00FF00, + description: output!, + }}).catch(error => console.warn(error.toString())); + } +}; diff --git a/command/help.js b/command/help.js deleted file mode 100644 index 165cb57..0000000 --- a/command/help.js +++ /dev/null @@ -1,132 +0,0 @@ -const CommandPermission = require('../model/command-permission'); -const CommandCategory = require('../model/command-category'); - -const commands = require('../model/command').commandList; -const messages = require('../model/messages'); - -const cachelessRequire = (path) => { - if (!typeof path === 'string') { - delete require.cache[require.resolve(path)]; - } - - return typeof path === 'string' ? require(path) : null; -}; - -const cleanString = (str) => str === null || str === undefined ? null : str - .replace('_', ' ') - .split(' ') - .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) - .join(' '); - -const mainPage = (id) => { - const buttons = Object.keys(CommandCategory).map(c => ({ - type: 2, - style: 1, - label: cleanString(c), - custom_id: `help-${id}-${c}`, - })) - - return { - embeds: [{ - title: 'Help command', - color: 0xA95B44, - description: - 'Spot stands for **S**imply **P**lural b**ot**. This bot is mainly used to generate changelogs and help ' + - 'with the definition of features, so recurring questions can be answered more quickly.\n' + - '\n' + - 'Commands are classified by categories because displaying every command in this small box would be ' + - 'confusing and would break Discord\'s character limit. Click on a buttom below to show the commands ' + - 'available in the corresponding category, or type `.help `.\n' - }], - components: [{ - 'type': 1, - 'components': buttons, - }] - } - -} - -const categoryPage = (cat, id, direct = false) => { - const c = Object.values(CommandCategory).map(x => cleanString(x).toLowerCase()); - if (!c.includes(cat.toLowerCase())) - return; - - let data = { - embeds: [{ - title: `Help command | ${cleanString(cat)}`, - color: 0xA95B44, - }], - components: [{ - 'type': 1, - 'components': [{ - type: 2, - style: 2, - label: 'Back', - custom_id: `help-${id}-home`, - }], - }] - }; - - if (direct) delete data.components; - - if (cat.toLowerCase() === cleanString(CommandCategory.RESOURCE).toLowerCase()) - data.embeds[0].fields = messages.getList(); - else - data.embeds[0].fields = Array.from(commands.keys()).map(x => { - const cmd = cachelessRequire(commands.get(x)); - if (cleanString(cmd.category)?.toLowerCase() !== cat.toLowerCase()) return; - return { name: x, value: cmd.description ?? 'No description.'}; - }).filter(x => x); - - return data; -} - -module.exports = { - aliases: ["commands"], - category: CommandCategory.INFO, - isAllowedForContext: () => true, - description: 'Provides the list of commands.', - process: async (message, args) => { - if (!args[0]) - await bot.api.channels(message.channel.id).messages.post({ data: mainPage(message.author.id) }); - else - await bot.api.channels(message.channel.id).messages.post({ - data: - categoryPage(args.join(' '), message.author.id, true) - ?? { - content: 'Category not found.', - message_reference: { message_id: message.id, guild_id: message.guild?.id }, - allowed_mentions: { parse: [] }, - } - }) - } -} - -module.exports.interactionHandler = async (event) => { - const user = event.member ? event.member.user.id : event.user.id; - const customId = event.data.custom_id; - - let ret; - - if (!customId.startsWith(`help-${user}`)) { - ret = { - type: 4, - data: { - flags: 64, - content: 'This help command was sent by someone else. Please run `.help` again.', - }, - } - } else if (customId.endsWith('home')) { - ret = { type: 7, data: mainPage(user) }; - } else { - let page = categoryPage(cleanString(customId.split(`help-${user}-`).join('')), user); - - if (page) { - ret = { type: 7, data: page }; - } else { - ret = { type: 4, data: { flags: 64, content: 'Category not found.' }} - } - } - - return await bot.api.interactions(event.id, event.token).callback.post({ data: ret }) -} diff --git a/command/help.ts b/command/help.ts new file mode 100644 index 0000000..7c7eea1 --- /dev/null +++ b/command/help.ts @@ -0,0 +1,125 @@ +import { restClient } from "../bot"; +import CommandCategory from "../model/command-category"; +import * as CommandPermission from '../model/command-permission'; +import * as types from 'discord-api-types/v10'; + +import { Command, commands } from "../messageHandler"; +import * as messages from "../model/messages"; +import { GatewayMessageCreateDispatchData } from "discord-api-types/v10"; + +const cleanString = (str: string | null | undefined) => (str === null || str === undefined) ? undefined : str + .replace('_', ' ') + .split(' ') + .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()) + .join(' '); + +const mainPage = (id: string) => { + const buttons = Object.values(CommandCategory).filter(x => typeof x === 'string').map(c => ({ + type: 2, + style: 1, + label: cleanString(c), + customId: `help-${id}-${c}`, + })) + + return { + embeds: [{ + title: 'Help command', + color: 0xA95B44, + description: + 'Spot stands for **S**imply **P**lural b**ot**. This bot is mainly used to generate changelogs and help ' + + 'with the definition of features, so recurring questions can be answered more quickly.\n' + + '\n' + + 'Commands are classified by categories because displaying every command in this small box would be ' + + 'confusing and would break Discord\'s character limit. Click on a buttom below to show the commands ' + + 'available in the corresponding category, or type `.help `.\n' + }], + components: [{ + 'type': 1, + 'components': buttons, + }] + } + +} + +const categoryPage = (cat: string, id: string, direct = false) => { + const c = Object.values(CommandCategory).filter(x => typeof x === 'string').map(x => cleanString(x)?.toLowerCase()); + if (!c.includes(cat.toLowerCase())) + return; + + let data = { + embeds: [{ + title: `Help command | ${cleanString(cat)}`, + color: 0xA95B44, + }], + components: [{ + 'type': 1, + 'components': [{ + type: 2, + style: 2, + label: 'Back', + customId: `help-${id}-home`, + }], + }], + }; + + // @ts-expect-error + if (direct) delete data.components; + + if (cat == CommandCategory.RESOURCE) + // @ts-expect-error + data.embeds[0].fields = messages.getList(); + else + // @ts-expect-error + data.embeds[0].fields = commands.map((cmd: Command) => { + if (cmd.category !== cat) return; + return { name: cmd.aliases[0], value: cmd.description ?? 'No description.'}; + }).filter(x => x); + + return data; +} + +export default { + aliases: ['help', "commands", 'h'], + category: CommandCategory.INFO, + isAllowedForContext: (_: GatewayMessageCreateDispatchData) => true, + description: 'Provides the list of commands.', + process: async (message: GatewayMessageCreateDispatchData, args: string[]) => { + if (!args[0]) + await restClient.createMessage(message.channel_id, mainPage(message.author.id) ); + else + await restClient.createMessage(message.channel_id, categoryPage(args.join(' '), message.author.id, true) + ?? { + content: 'Category not found.', + messageReference: { messageId: message.id, guildId: message.guild_id }, + allowedMentions: { parse: [] }, + }) + }, + interactionHandler: async (event: types.APIMessageComponentInteraction) => { + const user = event.member ? event.member.user.id : event.user!.id; + const customId = event.data.custom_id; + + let ret; + + if (!customId.startsWith(`help-${user}`)) { + ret = { + type: 4, + data: { + flags: 64, + content: 'This help command was sent by someone else. Please run `.help` again.', + }, + } + } else if (customId.endsWith('home')) { + ret = { type: 7, data: mainPage(user) }; + } else { + let page = categoryPage(cleanString(customId.split(`help-${user}-`).join(''))!, user); + + if (page) { + ret = { type: 7, data: page }; + } else { + ret = { type: 4, data: { flags: 64, content: 'Category not found.' }} + } + } + + return await restClient.createInteractionResponse(event.id, event.token, ret); + } +}; diff --git a/command/ticket.js b/command/ticket.ts similarity index 55% rename from command/ticket.js rename to command/ticket.ts index 372ee6d..9d693ef 100644 --- a/command/ticket.js +++ b/command/ticket.ts @@ -1,36 +1,38 @@ -const CommandCategory = require('../model/command-category'); -const CommandPermission = require('../model/command-permission'); +import CommandCategory from "../model/command-category"; +import * as CommandPermission from '../model/command-permission'; -const axios = require('axios'); +import axios from "axios"; +import { APIMessage, GatewayMessageCreateDispatchData } from "discord-api-types/v10"; +import { restClient } from "../bot"; -module.exports = { - aliases: [], +export default { + aliases: ['ticket'], category: CommandCategory.MODERATION, isAllowedForContext: CommandPermission.isMemberModOrHelper, - process: async (message) => { - if (message.reference == null) - return message.reply("missing reply"); + process: async (message: GatewayMessageCreateDispatchData, args: string[]) => { + if (message.message_reference == null) + return restClient.createMessage(message.channel_id, `<@${message.author.id}>, missing reply`); - let reply = await message.channel.messages.fetch(message.reference.messageID); + let reply: APIMessage = await restClient.fetchMessage(message.channel_id, message.message_reference.message_id!); let user = null; - if (reply.webhookID != null) { + if (reply.webhook_id != null) { let pkmsg = await axios(`https://api.pluralkit.me/v2/messages/${reply.id}`); user = pkmsg.data.sender; } else { user = reply.author.id; } - message.delete(); - message.client.api.channels(message.channel.id).messages.post({ data: { + restClient.deleteMessage(message.channel_id, message.id).catch(() => {}); + restClient.createMessage(message.channel_id, { content: `<@${user}>: Please send us a support ticket for your specific issue using this link: \n` + `You can also click the button below this message.\n\n` + `Select the support type that best fits your issue. Make sure to fill out any information that is requested.\n\n` + `Once you fill out the ticket, you will receive an email with a confirmation.\nWhen the developer answers the ticket, you will get another email, ` + `so please watch your email (and spam folder) for a reply.`, - message_reference: { message_id: reply.id }, + messageReference: { messageId: reply.id }, components: [{ type: 1, components: [{ type: 2, style: 5, label: "Submit ticket", url: "https://apparyllis.atlassian.net/servicedesk/customer/portals" }]}] - }}); + }); } }; \ No newline at end of file diff --git a/config.json b/config.ts similarity index 96% rename from config.json rename to config.ts index 82b158d..c01b435 100644 --- a/config.json +++ b/config.ts @@ -1,4 +1,4 @@ -{ +export default { "prefix": ".", "guild": "793906174356619286", "mom": "840806601957965864", diff --git a/event/guild-member-add.js b/event/guild-member-add.js deleted file mode 100644 index f1b55b5..0000000 --- a/event/guild-member-add.js +++ /dev/null @@ -1,7 +0,0 @@ -const Guild = require('../model/guild'); - -module.exports = async (member) => { - if ((member.guild === null || isRightGuild(member.guild.id)) && !member.user.bot) { - Guild.guildMemberAddHandler(member); - } -}; diff --git a/event/message.js b/event/message.js deleted file mode 100644 index 3fa5deb..0000000 --- a/event/message.js +++ /dev/null @@ -1,12 +0,0 @@ -const Command = require('../model/command'); - -/** - * @param {Message} message - */ -module.exports = async (message) => { - const user = message.author; - - if ((message.guild === null || isRightGuild(message.guild.id)) && !user.bot) { - await Command.parseMessage(message); - } -}; diff --git a/event/ready.js b/event/ready.js deleted file mode 100644 index d955764..0000000 --- a/event/ready.js +++ /dev/null @@ -1,19 +0,0 @@ -const Logger = require('@lilywonhalf/pretty-logger'); -const Config = require('../config.json'); -const Guild = require('../model/guild'); - -module.exports = async () => { - Logger.info('Logged in as ' + bot.user.username + '#' + bot.user.discriminator); - - Logger.info('--------'); - - Logger.info('Syncing guilds...'); - await Guild.init(); - Logger.info('Guilds synced. Serving in ' + Guild.discordGuild.name); - - Logger.info('--------'); - - if (process.argv[3] === '--reboot') { - bot.users.cache.get(Config.mom).send(`I'm back :) !`); - } -}; diff --git a/messageHandler.ts b/messageHandler.ts new file mode 100644 index 0000000..7eb2315 --- /dev/null +++ b/messageHandler.ts @@ -0,0 +1,46 @@ +import * as types from 'discord-api-types/v10'; +import CommandCategory from './model/command-category'; +import { restClient } from './bot'; +import config from './config'; + +import avatar from './command/avatar'; +import changelog from './command/changelog'; +import clean from './command/clean'; +import eval from './command/eval'; +import help from './command/help'; +import ticket from './command/ticket'; + +import * as customMessages from './model/messages'; + +export interface Command { + aliases: string[]; + category: CommandCategory; + isAllowedForContext(_: types.GatewayMessageCreateDispatchData): boolean; + description?: string; + process(_: types.GatewayMessageCreateDispatchData, __: string[]): Promise; +} + +const privCommands: Record = {}; + +export const commands = [avatar, changelog, clean, eval, help, ticket]; + + +commands.map((x: Command) => { + x.aliases.map(a => privCommands[a] = x); + }); + +export default async function handleMessage(message: types.GatewayMessageCreateDispatchData) { + if (!message.content.toLowerCase().startsWith(config.prefix)) + return false; + + let args = message.content.substr(config.prefix.length).trim().split(' '); + const calledCommand = args.shift()!.toLowerCase(); + + const embed = customMessages.getEmbed(calledCommand); + if (embed != null) + return await restClient.createMessage(message.channel_id, { embed }); + + const foundCommand = privCommands[calledCommand]; + if (foundCommand == null || !foundCommand.isAllowedForContext(message)) return; + await foundCommand.process(message, args); +} \ No newline at end of file diff --git a/messages.json b/messages.json index 008f32b..aec6f7c 100644 --- a/messages.json +++ b/messages.json @@ -1,6 +1,5 @@ { - "categories": - { + "categories": { "guides": { "name": "Guides", "desc": "Guides on how to do things in the app", diff --git a/model/command-category.js b/model/command-category.js deleted file mode 100644 index 6e2f204..0000000 --- a/model/command-category.js +++ /dev/null @@ -1,8 +0,0 @@ -const CommandCategory = { - MODERATION: 'moderation', - BOT_MANAGEMENT: 'bot_management', - FUN: 'fun', - RESOURCE: 'resource' -}; - -module.exports = CommandCategory; diff --git a/model/command-category.ts b/model/command-category.ts new file mode 100644 index 0000000..22ac41e --- /dev/null +++ b/model/command-category.ts @@ -0,0 +1,9 @@ +enum CommandCategory { + INFO = 'Info', + MODERATION = 'Moderation', + BOT_MANAGEMENT = 'Bot Managenent', + FUN = 'Fun', + RESOURCE = 'Resource' +} + +export default CommandCategory; \ No newline at end of file diff --git a/model/command-permission.js b/model/command-permission.js deleted file mode 100644 index dc0f847..0000000 --- a/model/command-permission.js +++ /dev/null @@ -1,36 +0,0 @@ -const Config = require('../config.json'); -const Guild = require('./guild'); - -const CommandPermission = { - /** - * @param {Message} message - * @returns {Promise.} - */ - isMommy: async (message) => { - const member = await Guild.getMemberFromMessage(message); - - return member.id === Config.mom; - }, - - /** - * @param {Message} message - * @returns {Promise.} - */ - isMemberMod: async (message) => { - const member = await Guild.getMemberFromMessage(message); - - return member.id === Config.mom || await Guild.isMemberMod(member); - }, - - /** - * @param {Message} message - * @returns {Promise.} - */ - isMemberModOrHelper: async (message) => { - const member = await Guild.getMemberFromMessage(message); - - return await CommandPermission.isMemberMod(message) || await Guild.isMemberHelper(member); - }, -}; - -module.exports = CommandPermission; diff --git a/model/command-permission.ts b/model/command-permission.ts new file mode 100644 index 0000000..9308195 --- /dev/null +++ b/model/command-permission.ts @@ -0,0 +1,9 @@ +import { GatewayMessageCreateDispatchData } from 'discord-api-types/v10'; +import config from '../config'; + +export const isMommy = (message: GatewayMessageCreateDispatchData) => message.author.id == config.mom; +export const isMemberMod = (message: GatewayMessageCreateDispatchData) => message.member!.roles.includes(config.roles.mod); +export const isMemberModOrHelper = (message: GatewayMessageCreateDispatchData) => + message.member!.roles.includes(config.roles.mod) + || message.member!.roles.includes(config.roles.helper); + diff --git a/model/command.js b/model/command.js deleted file mode 100644 index f303dff..0000000 --- a/model/command.js +++ /dev/null @@ -1,105 +0,0 @@ -const fs = require('fs'); -const Discord = require('discord.js'); -const Config = require('../config.json'); -const Guild = require('./guild'); - -const customMessages = require("./messages"); - -const cachelessRequire = (path) => { - if (typeof path === 'string') { - delete require.cache[require.resolve(path)]; - } - - return typeof path === 'string' ? require(path) : null; -}; - -const Command = { - commandList: new Discord.Collection(), - commandAliases: {}, - - init: () => { - Command.commandList = new Discord.Collection(); - Command.commandAliases = {}; - - fs.readdirSync('command/').forEach(file => { - if (file.substr(file.lastIndexOf('.')).toLowerCase() === '.js') { - const commandPath = `../command/${file}`; - const commandInstance = cachelessRequire(commandPath); - - if (commandInstance !== null) { - const commandName = file.substr(0, file.lastIndexOf('.')).toLowerCase(); - - Command.commandList.set(commandName, commandPath); - - if (commandInstance.aliases !== undefined && commandInstance.aliases !== null) { - commandInstance.aliases.forEach(alias => { - Command.commandAliases[alias.toLowerCase()] = commandName; - }); - } - } - } - }); - }, - - /** - * @param {Message} message - * @returns {boolean} - */ - parseMessage: async (message) => { - if (!message.content.toLowerCase().startsWith(Config.prefix)) - return false; - - let content = message.content.substr(Config.prefix.length).trim().split(' '); - const calledCommand = content.shift().toLowerCase(); - - if (embed = customMessages.getEmbed(calledCommand)) - return await message.channel.send({embed}); - - if (!await Command.isValid(calledCommand, message)) return; - - const member = await Guild.getMemberFromMessage(message); - if (member === null) - return message.reply('sorry, you do not seem to be on the server.'); - - let commandName = calledCommand; - - if (Command.commandAliases.hasOwnProperty(calledCommand)) - commandName = Command.commandAliases[calledCommand]; - - const commandInstance = cachelessRequire(Command.commandList.get(commandName)); - - if (commandInstance !== null) - commandInstance.process(message, content, Command); - else - Command.commandList.delete(commandName); - }, - - /** - * @param {string} command - * @param {Message} message - * @return {Promise.} - */ - isValid: async (command, message) => { - let canonicalCommand = command.toLowerCase(); - let valid = Command.commandList.has(canonicalCommand); - - if (!valid && Command.commandAliases.hasOwnProperty(canonicalCommand)) { - canonicalCommand = Command.commandAliases[command]; - valid = Command.commandList.has(canonicalCommand); - } - - const commandInstance = cachelessRequire(Command.commandList.get(canonicalCommand)); - - if (commandInstance === null) { - Command.commandList.delete(canonicalCommand); - } - - valid = valid - && commandInstance !== null - && await commandInstance.isAllowedForContext(message); - - return valid; - } -}; - -module.exports = Command; diff --git a/model/guild.js b/model/guild.js deleted file mode 100644 index 1bc1ee3..0000000 --- a/model/guild.js +++ /dev/null @@ -1,173 +0,0 @@ -const Logger = require('@lilywonhalf/pretty-logger'); -const Config = require('../config.json'); -const Discord = require('discord.js'); - -const cachelessRequire = (path) => { - if (typeof path === 'string') { - delete require.cache[require.resolve(path)]; - } - - return typeof path === 'string' ? require(path) : null; -}; - -const Guild = { - /** {Guild} */ - discordGuild: null, - - /** {TextChannel} */ - joinsChannel: null, - - /** {Collection} */ - messagesCache: new Discord.Collection(), - - init: async () => { - Guild.discordGuild = bot.guilds.cache.find(guild => guild.id === Config.guild); - Guild.joinsChannel = Guild.discordGuild.channels.cache.find(channel => channel.id === Config.channels.joins); - }, - - /** - * @param message - * @returns {Promise.} - */ - getMemberFromMessage: async (message) => { - return await Guild.discordGuild.members.fetch(message.author).catch(exception => { - Logger.error(exception.toString()); - - return null; - }); - }, - - /** - * @param {GuildMember} member - */ - isMemberMod: (member) => { - return member !== undefined && member !== null && member.roles.cache.has(Config.roles.mod); - }, - - /** - * @param {GuildMember} member - */ - isMemberHelper: (member) => { - return member !== undefined && member !== null && member.roles.cache.has(Config.roles.helper); - }, - - /** - * @param {string} roleName - * @returns {Role|null} - */ - getRoleByName: (roleName) => { - return roleName === undefined || roleName === null ? null : Guild.discordGuild.roles.cache.find( - role => role.name.toLowerCase() === roleName.toLowerCase() - ); - }, - - /** - * @param {GuildMember} member - * @param {Snowflake} snowflake - The Role snowflake. - * @returns {boolean} - */ - memberHasRole: (member, snowflake) => { - return member !== undefined && member !== null && member.roles.cache.some(role => role.id === snowflake); - }, - - /** - * @param {Message} message - * @returns {Discord.MessageEmbed} - */ - messageToEmbed: async (message) => { - const member = await Guild.getMemberFromMessage(message); - const suffix = member !== null && member.nickname !== null && member.nickname !== undefined ? ` aka ${member.nickname}` : ''; - const embeds = message.embeds.filter(embed => embed.author.name && embed.author.iconURL); - - let authorName = `${message.author.username}#${message.author.discriminator}${suffix}`; - let authorImage = message.author.displayAvatarURL({ dynamic: true }); - let description = message.content; - let timestamp = message.createdTimestamp; - let image = null; - - if (message.attachments.size > 0) { - image = message.attachments.first().url; - } - - if (description.length < 1 && embeds.length > 0) { - const embed = embeds[0]; - description = embed.description ? embed.description.trim() : ''; - - if (message.author.bot) { - if (embed.author) { - authorName = embed.author.name; - authorImage = embed.author.iconURL; - } - - if (embed.timestamp) { - timestamp = embed.timestamp; - } - - if (embed.image) { - image = embed.image.url; - } - } - } - - return new Discord.MessageEmbed() - .setAuthor(authorName, authorImage) - .setColor(0x00FF00) - .setDescription(description) - .setTimestamp(timestamp) - .setImage(image); - }, - - /** - * @param {Message} message - * @returns {{certain: boolean, foundMembers: Array}} - */ - findDesignatedMemberInMessage: (message) => { - let foundMembers = []; - let certain = true; - const memberList = bot.users.cache.concat(Guild.discordGuild.members.cache); - - if (message.mentions.members !== null && message.mentions.members.size > 0) { - foundMembers = message.mentions.members.array(); - } else if (message.content.match(/[0-9]{18}/u) !== null) { - const ids = message.content.match(/[0-9]{18}/gu); - - ids.map(id => { - if (memberList.has(id)) { - foundMembers.push(memberList.get(id)); - } - }); - } else { - certain = false; - memberList.forEach(member => { - const user = member.user === undefined ? member : member.user; - - const hasNickname = member.nickname !== undefined && member.nickname !== null; - const nickname = hasNickname ? `${member.nickname.toLowerCase()}#${user.discriminator}` : ''; - const username = `${user.username.toLowerCase()}#${user.discriminator}`; - const content = message.cleanContent.toLowerCase().split(' ').splice(1).join(' '); - - if (content.length > 0) { - const contentInNickname = hasNickname ? nickname.indexOf(content) > -1 : false; - const contentInUsername = username.indexOf(content) > -1; - const nicknameInContent = hasNickname ? content.indexOf(nickname) > -1 : false; - const usernameInContent = content.indexOf(username) > -1; - - if (contentInNickname || contentInUsername || nicknameInContent || usernameInContent) { - foundMembers.push(member); - } - } - }); - } - - return { - certain, - foundMembers - }; - }, - - guildMemberAddHandler: (member) => { - Guild.joinsChannel.send(`Welcome, ${member}! If you joined for any specific support questions please check out <#863171642905591830> first to see if your issue is known, and make sure that your app is up-to-date before posting.`); - } -}; - -module.exports = Guild; \ No newline at end of file diff --git a/model/jira.js b/model/jira.js deleted file mode 100644 index ab50550..0000000 --- a/model/jira.js +++ /dev/null @@ -1,37 +0,0 @@ -const axios = require('axios'); -const Config = require('../config.json'); - -const ENDPOINTS = { - search: '/search' -}; - -class Jira -{ - async search(version) { - const params = { - 'jql': 'project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20updated%20%3E%3D%20-24h%20ORDER%20BY%20created%20DESC', - 'maxResults': 100, - 'startAt': 0 - }; - - if (version) { - params.jql = `project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20%22Fixed%20In%20Version%5BNumber%5D%22%20%3D%20%22${version}%22%20ORDER%20BY%20created%20DESC`; - } - - const issues = []; - let response = null; - - do { - const httpParams = Object.keys(params).map(key => `${key}=${params[key]}`).join('&'); - const url = `${Config.jira.baseUrl}${ENDPOINTS.search}?${httpParams}`; - response = await axios(url); - - issues.push(...response.data.issues); - params.startAt += 100; - } while (response.data.issues.length > 99 && response.data.total > 100); - - return issues; - } -} - -module.exports = new Jira(); \ No newline at end of file diff --git a/model/jira.ts b/model/jira.ts new file mode 100644 index 0000000..52ba88f --- /dev/null +++ b/model/jira.ts @@ -0,0 +1,30 @@ +import * as axios from 'axios'; +import config from '../config'; + +export default async function search(version?: string): Promise { + const params = { + 'jql': 'project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20updated%20%3E%3D%20-24h%20ORDER%20BY%20created%20DESC', + 'maxResults': 100, + 'startAt': 0 + }; + + if (version) { + params.jql = `project%20%3D%20SP%20AND%20status%20in%20(%22Claimed%20Fixed%22%2C%20%22In%20Pre-Testing%22)%20AND%20%22Fixed%20In%20Version%5BNumber%5D%22%20%3D%20%22${version}%22%20ORDER%20BY%20created%20DESC`; + } + + const issues: any[] = []; + let response: any | null = null; + + do { + // @ts-expect-error + const httpParams = Object.keys(params).map(key => `${key}=${params[key]}`).join('&'); + const url = `${config.jira.baseUrl}/search?${httpParams}`; + // what even is this + response = JSON.parse((await new axios.Axios({}).get(url)).data); + + issues.push(...response!.issues); + params.startAt += 100; + } while (response!.issues.length > 99 && response!.total > 100); + + return issues; +} \ No newline at end of file diff --git a/model/messages.js b/model/messages.js deleted file mode 100644 index 760e67d..0000000 --- a/model/messages.js +++ /dev/null @@ -1,65 +0,0 @@ -const fs = require("fs") -const path = require('path'); - -const messages = {}; - -const load = () => -{ - const messagesFile = fs.readFileSync(path.join(__dirname, '..', 'messages.json')) - const messagesJson = JSON.parse(messagesFile); - - const messagesList = messagesJson["faq"]; - - messagesList.forEach((msg) => { - messages[msg["title"]] = msg - }) -} - -load(); - -// interface Message { -// names string[]; -// description string; -// title string; -// text string; -// friendlyName string; -// category string; -// } - -module.exports = { - get names() { - return names; - }, - get messages() { - return messages; - }, - get: (name) => messages[name], - getList: () => Object.keys(messages).map(msg => ({ name: messages[msg].names[0], value: messages[msg].description })), - getEmbed: (name) => { - - let foundMsg = null - Object.keys(messages).forEach((msgKey) => - { - if (foundMsg) return; - - const msg = messages[msgKey] - - if (msg.names.includes(name)) - { - foundMsg = msg; - return; - } - }); - - if (!foundMsg) return; - - return { - color: "A95B44", - author: { - name: foundMsg.title, - iconURL: bot.user.displayAvatarURL({ dynamic: true }), - }, - description: foundMsg.text, - } - } -} diff --git a/model/messages.ts b/model/messages.ts new file mode 100644 index 0000000..32b0509 --- /dev/null +++ b/model/messages.ts @@ -0,0 +1,58 @@ +import { restClient } from "../bot"; + +interface Message { + names: string[]; + description: string; + title: string; + text: string; + friendlyName: string; + category: string; +} + +export const messages: Record = {}; +let avatarUrl = ""; + +const load = async () => +{ + const messagesList = require('../messages.json')["faq"]; + messagesList.forEach((msg: any) => { + messages[msg["title"]] = msg + }) + + const userInfo = await restClient.fetchMe(); + avatarUrl = `https://cdn.discordapp.com/avatars/${userInfo.id}/${userInfo.avatar}.png`; + console.log(avatarUrl); +} + +load(); + + +export const get = (name: string) => messages[name]; +export const getList = () => Object.keys(messages).map(msg => ({ name: messages[msg].names[0], value: messages[msg].description })); + +export const getEmbed = (name: string) => { + let foundMsg: Message | undefined; + Object.keys(messages).forEach((msgKey) => + { + if (foundMsg) return; + + const msg = messages[msgKey] + + if (msg.names.includes(name)) + { + foundMsg = msg; + return; + } + }); + + if (foundMsg == null) return; + + return { + color: 0xA95B44, + author: { + name: foundMsg.title, + iconUrl: avatarUrl, + }, + description: foundMsg.text, + } +} diff --git a/package.json b/package.json index 954521c..00c15c9 100644 --- a/package.json +++ b/package.json @@ -9,9 +9,12 @@ "author": "Lily Wonhalf ", "license": "ISC", "dependencies": { - "@lilywonhalf/pretty-logger": "^1.1.3", "axios": "^1.1.3", - "discord.js": "^14.6.0", - "dotenv": "^16.0.3" + "detritus-client-rest": "git+https://github.com/Tupperbox/DiscordRest", + "detritus-client-socket": "git+https://github.com/Tupperbox/DiscordSocket", + "discord-api-types": "^0.37.14", + "dotenv": "^16.0.3", + "ts-node": "^10.9.1", + "typescript": "^4.8.4" } } diff --git a/yarn.lock b/yarn.lock index 4174558..b83d32d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,138 +2,250 @@ # yarn lockfile v1 -"@discordjs/collection@^0.1.6": - "integrity" "sha512-utRNxnd9kSS2qhyivo9lMlt5qgAUasH2gb7BEOn6p0efFh24gjGomHzWKMAPn2hEReOPQZCJaRKoURwRotKucQ==" - "resolved" "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.6.tgz" - "version" "0.1.6" - -"@discordjs/form-data@^3.0.1": - "integrity" "sha512-ZfFsbgEXW71Rw/6EtBdrP5VxBJy4dthyC0tpQKGKmYFImlmmrykO14Za+BiIVduwjte0jXEBlhSKf0MWbFp9Eg==" - "resolved" "https://registry.npmjs.org/@discordjs/form-data/-/form-data-3.0.1.tgz" - "version" "3.0.1" +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== dependencies: - "asynckit" "^0.4.0" - "combined-stream" "^1.0.8" - "mime-types" "^2.1.12" + "@jridgewell/trace-mapping" "0.3.9" -"@lilywonhalf/pretty-logger@^1.1.3": - "integrity" "sha512-DcEGjCrQV2j6Hl29G4zD5M/84hzrt1xw7uPOZtZLWFzrCB5OWS/64RWl/y5/om0walTIYf7Nv00hCzfRS8gw4A==" - "resolved" "https://registry.npmjs.org/@lilywonhalf/pretty-logger/-/pretty-logger-1.1.3.tgz" - "version" "1.1.3" +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== -"abort-controller@^3.0.0": - "integrity" "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==" - "resolved" "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz" - "version" "3.0.0" +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== dependencies: - "event-target-shim" "^5.0.0" + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" -"asynckit@^0.4.0": - "integrity" "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" - "resolved" "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" - "version" "0.4.0" +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== -"axios@^0.21.1": - "integrity" "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==" - "resolved" "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz" - "version" "0.21.4" +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + +"@types/node@^14.17.1": + version "14.18.32" + resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.32.tgz#8074f7106731f1a12ba993fe8bad86ee73905014" + integrity sha512-Y6S38pFr04yb13qqHf8uk1nHE3lXgQ30WZbv1mLliV9pt0NjvqdWttLcrOYLnXbOafknVYRHZGoMSpR9UwfYow== + +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz" + integrity "sha1-x57Zf380y48robyXkLzDZkdLS3k= sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + +axios@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/axios/-/axios-1.1.3.tgz#8274250dada2edf53814ed7db644b9c2866c1e35" + integrity sha512-00tXVRwKx/FZr/IDVFt4C+f9FYairX517WoGCL6dpOntqLkZofjhu43F/Xl44UOpqa+9sLFDrG/XAnFsUYgkDA== dependencies: - "follow-redirects" "^1.14.0" + follow-redirects "^1.15.0" + form-data "^4.0.0" + proxy-from-env "^1.1.0" -"combined-stream@^1.0.8": - "integrity" "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==" - "resolved" "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" - "version" "1.0.8" +combined-stream@^1.0.8: + version "1.0.8" + resolved "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz" + integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg== dependencies: - "delayed-stream" "~1.0.0" + delayed-stream "~1.0.0" -"delayed-stream@~1.0.0": - "integrity" "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" - "resolved" "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" - "version" "1.0.0" +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== -"discord.js@^12.2.0": - "integrity" "sha512-D3nkOa/pCkNyn6jLZnAiJApw2N9XrIsXUAdThf01i7yrEuqUmDGc7/CexVWwEcgbQR97XQ+mcnqJpmJ/92B4Aw==" - "resolved" "https://registry.npmjs.org/discord.js/-/discord.js-12.5.3.tgz" - "version" "12.5.3" +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz" + integrity "sha1-3zrhmayt+31ECqrgsp4icrJOxhk= sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + +"detritus-client-rest@git+https://github.com/Tupperbox/DiscordRest": + version "0.10.5" + resolved "git+https://github.com/Tupperbox/DiscordRest#15c7aa2d1782c55c30a60b53f754b6938e658346" dependencies: - "@discordjs/collection" "^0.1.6" - "@discordjs/form-data" "^3.0.1" - "abort-controller" "^3.0.0" - "node-fetch" "^2.6.1" - "prism-media" "^1.2.9" - "setimmediate" "^1.0.5" - "tweetnacl" "^1.0.3" - "ws" "^7.4.4" + detritus-rest "^0.7.0" + detritus-utils "^0.4.0" -"dotenv@^16.0.3": - "integrity" "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==" - "resolved" "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" - "version" "16.0.3" - -"event-target-shim@^5.0.0": - "integrity" "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" - "resolved" "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz" - "version" "5.0.1" - -"follow-redirects@^1.14.0": - "integrity" "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" - "resolved" "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz" - "version" "1.15.2" - -"mime-db@1.50.0": - "integrity" "sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==" - "resolved" "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz" - "version" "1.50.0" - -"mime-types@^2.1.12": - "integrity" "sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g==" - "resolved" "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz" - "version" "2.1.33" +"detritus-client-socket@git+https://github.com/Tupperbox/DiscordSocket": + version "0.8.3" + resolved "git+https://github.com/Tupperbox/DiscordSocket#44c6815e2e0cb0c6fdf42bc77ff91127413b5c58" dependencies: - "mime-db" "1.50.0" + "@types/node" "^14.17.1" + detritus-utils "^0.4.0" + ws "^7.4.6" -"node-fetch@^2.6.1": - "integrity" "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==" - "resolved" "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz" - "version" "2.6.7" +detritus-rest@^0.7.0: + version "0.7.0" + resolved "https://registry.yarnpkg.com/detritus-rest/-/detritus-rest-0.7.0.tgz#15d7a98c7038935de7ba2a88eeed83aba1c88ed7" + integrity sha512-T5FNXpyv5F69ZXEz6z8mWo5yiz7Mhrg+HQsZumih/uDeqf3+1AvZgNI4XLscG1D56F0o5DSyXkoOxDgd2oeTgA== dependencies: - "whatwg-url" "^5.0.0" + form-data "^3.0.0" + node-fetch "^2.6.1" -"prism-media@^1.2.9": - "integrity" "sha512-L6UsGHcT6i4wrQhFF1aPK+MNYgjRqR2tUoIqEY+CG1NqVkMjPRKzS37j9f8GiYPlD6wG9ruBj+q5Ax+bH8Ik1g==" - "resolved" "https://registry.npmjs.org/prism-media/-/prism-media-1.3.2.tgz" - "version" "1.3.2" +detritus-utils@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/detritus-utils/-/detritus-utils-0.4.0.tgz#42c01e029bd716c5be30a82ee4c621136f64ef35" + integrity sha512-XO9crk1eCOecajzOg4WMsvcgXV/3GAltV0nt+kXXmglwcKyh/5DpQ7jmQ/2HycEN0dGBdgLCLPzYMxrWPN9gpw== -"setimmediate@^1.0.5": - "integrity" "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU= sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" - "resolved" "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz" - "version" "1.0.5" +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== -"tr46@~0.0.3": - "integrity" "sha1-gYT9NH2snNwYWZLzpmIuFLnZq2o= sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==" - "resolved" "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz" - "version" "0.0.3" +discord-api-types@^0.37.14: + version "0.37.14" + resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.37.14.tgz#0fb10a5bb3ff9415afac12d7eaecc68b430d372a" + integrity sha512-byBH7SfDCMJwxdqeS8k5sihltH88/YPhuwx+vF2cftSxFLdxyHyU/ZxDL3bq+LB2c4ls/TymE76/ISlLfniUXg== -"tweetnacl@^1.0.3": - "integrity" "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==" - "resolved" "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz" - "version" "1.0.3" +dotenv@^16.0.3: + version "16.0.3" + resolved "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz" + integrity sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ== -"webidl-conversions@^3.0.0": - "integrity" "sha1-JFNCdeKnvGvnvIZhHMFq4KVlSHE= sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==" - "resolved" "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz" - "version" "3.0.1" +follow-redirects@^1.15.0: + version "1.15.2" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.2.tgz#b460864144ba63f2681096f274c4e57026da2c13" + integrity sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA== -"whatwg-url@^5.0.0": - "integrity" "sha1-lmRU6HZUYuN2RNNib2dCzotwll0= sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==" - "resolved" "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz" - "version" "5.0.0" +form-data@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f" + integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg== dependencies: - "tr46" "~0.0.3" - "webidl-conversions" "^3.0.0" + asynckit "^0.4.0" + combined-stream "^1.0.8" + mime-types "^2.1.12" -"ws@^7.4.4": - "integrity" "sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==" - "resolved" "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz" - "version" "7.5.5" +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" + +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + +mime-db@1.50.0: + version "1.50.0" + resolved "https://registry.npmjs.org/mime-db/-/mime-db-1.50.0.tgz" + integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A== + +mime-types@^2.1.12: + version "2.1.33" + resolved "https://registry.npmjs.org/mime-types/-/mime-types-2.1.33.tgz" + integrity sha512-plLElXp7pRDd0bNZHw+nMd52vRYjLwQjygaNg7ddJ2uJtTlmnTCjWuPKxVu6//AdaRuME84SvLW91sIkBqGT0g== + dependencies: + mime-db "1.50.0" + +node-fetch@^2.6.1: + version "2.6.7" + resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.7.tgz#24de9fba827e3b4ae44dc8b20256a379160052ad" + integrity sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ== + dependencies: + whatwg-url "^5.0.0" + +proxy-from-env@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/proxy-from-env/-/proxy-from-env-1.1.0.tgz#e102f16ca355424865755d2c9e8ea4f24d58c3e2" + integrity sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg== + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + integrity sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw== + +ts-node@^10.9.1: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + +typescript@^4.8.4: + version "4.8.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6" + integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ== + +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + integrity sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ== + +whatwg-url@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-5.0.0.tgz#966454e8765462e37644d3626f6742ce8b70965d" + integrity sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw== + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +ws@^7.4.6: + version "7.5.9" + resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591" + integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q== + +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==