diff --git a/.gitignore b/.gitignore index 36fb77e..ad0a91b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ node_modules/ /index.js dist +.env diff --git a/commands.ts b/commands.ts deleted file mode 100644 index d992fbc..0000000 --- a/commands.ts +++ /dev/null @@ -1,169 +0,0 @@ -import type { Client, Message } from 'discord.js'; -import fetch from 'node-fetch'; - -type Commands = { - [cmd: string]: (c: Client, e: Message) => void | Promise; -}; - -export const commands: Commands = { - '!ping': async (c, e) => { - await e.reply(`${c.ws.ping}ms`); - }, - - '!why': async (c, e) => { - await e.reply({ - embeds: [ - { - title: 'Why PolyMC exists', - description: - 'https://polymc.org/wiki/overview/faq/#why-did-our-community-choose-to-fork\nhttps://polymc.org/news/moving-on/', - color: 'GREYPLE', - }, - ], - }); - }, - - '!paths': async (c, e) => { - await e.reply({ - embeds: [ - { - title: 'Data directories', - description: 'Where PolyMC stores your data (e.g. instances)', - color: 'AQUA', - fields: [ - { - name: 'Portable (Windows / Linux)', - value: 'In the PolyMC folder', - }, - { - name: 'Windows', - value: '`%APPDATA%/PolyMC`', - }, - { - name: 'macOS', - value: '`~/Library/Application Support/PolyMC`', - }, - { name: 'Linux', value: '`~/.local/share/PolyMC`' }, - { - name: 'Flatpak', - value: '`~/.var/app/org.polymc.PolyMC/data/PolyMC`', - }, - ], - }, - ], - }); - }, - - '!cursed': async (c, e) => { - await e.reply({ - embeds: [ - { - title: "What's wrong with CurseForge?", - description: ` -CurseForge added a new option to block third party clients like PolyMC from accessing mod files, and they started to enforce this option lately. - -We can't allow you to download those mods directly from PolyMC, but PolyMC 1.3.1 and higher have a workaround to let modpacks work: letting you to download those opted out mods manually. -We highly encourage asking authors that opted out to stop doing so. -`.trim(), - color: 'ORANGE', - }, - ], - }); - }, - - '!migrate': async (c, e) => { - await e.reply('https://polymc.org/wiki/getting-started/migrating-multimc/'); - }, - - '!build': async (c, e) => { - await e.reply('https://polymc.org/wiki/development/build-instructions/'); - }, - - '!java': async (c, e) => { - await e.reply('https://polymc.org/wiki/getting-started/installing-java/'); - }, - - '!eta': async (c, e) => { - await e.reply('Sometime'); - }, - - '!members': async (c, e) => { - const mems = await e.guild?.members.fetch().then((r) => r.toJSON()); - if (!mems) return; - - await e.reply({ - embeds: [ - { - title: `${mems.length} total members!`, - description: `${ - mems.filter( - (m) => - m.presence?.status === 'online' || - m.presence?.status === 'idle' || - m.presence?.status === 'dnd' - ).length - } online members, and ${ - mems.filter((m) => m.presence?.status === 'invisible').length - } members that are pretending to be offline`, - color: 'GOLD', - }, - ], - }); - }, - - '!stars': async (c, e) => { - const count = await fetch('https://api.github.com/repos/PolyMC/PolyMC') - .then((r) => r.json() as Promise<{ stargazers_count: number }>) - .then((j) => j.stargazers_count); - - await e.reply({ - embeds: [ - { - title: `⭐ ${count} total stars!`, - color: 'GOLD', - }, - ], - }); - }, - - // '!polycatgen': async (c, e) => { - // if (!e.guild) return; - // if ( - // e.channelId !== POLYCAT_CHANNEL_ID && - // process.env.NODE_ENV !== 'development' - // ) - // return; - - // await e.guild.emojis.fetch(); - // const polycat = e.guild.emojis.cache.find( - // (emoji) => emoji.name?.toLowerCase() === 'polycat' - // ); - - // await e.reply( - // `.\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}\n${polycat}${polycat}${polycat}${polycat}${polycat}` - // ); - // }, - - '!piracy': async (c, e) => { - await e.reply({ - embeds: [ - { - title: "We don't tolerate piracy!", - description: - "PolyMC has always been legal, legitimate & appropriate. We don't and never will have features such as offline login without an official account.", - color: 'DARK_RED', - }, - ], - }); - }, -}; - -export const aliases: { [a: string]: string } = { - '!curse': '!cursed', - '!curseforge': '!cursed', - '!cf': '!cursed', - '!diff': '!why', - '!migr': '!migrate', - '!j': '!java', - '!multimc': '!migrate', -}; diff --git a/index.ts b/index.ts deleted file mode 100644 index 04c8d38..0000000 --- a/index.ts +++ /dev/null @@ -1,121 +0,0 @@ -import { Client, Intents } from 'discord.js'; -import { commands, aliases } from './commands'; - -import * as BuildConfig from './constants'; -import { isBad } from './badLinks'; - -import { green, bold, blue, underline, yellow } from 'kleur/colors'; -import urlRegex from 'url-regex'; -import removeMarkdown from 'remove-markdown'; - -const client = new Client({ - intents: [ - Intents.FLAGS.GUILDS, - Intents.FLAGS.GUILD_MESSAGES, - Intents.FLAGS.DIRECT_MESSAGES, - Intents.FLAGS.GUILD_MEMBERS, - Intents.FLAGS.GUILD_PRESENCES, - Intents.FLAGS.GUILD_MESSAGE_REACTIONS, - Intents.FLAGS.GUILD_BANS, - ], -}); - -client.login(process.env.DISCORD_TOKEN); - -client.once('ready', async () => { - console.log(green('Discord bot ready!')); - - if (process.env.NODE_ENV !== 'development') - console.warn(yellow(bold('Running in production mode!'))); - - console.log( - 'Invite link:', - blue( - underline( - client.generateInvite({ - scopes: ['bot'], - permissions: ['ADMINISTRATOR'], - }) - ) - ) - ); - - const POLYMC_GUILD = await client.guilds.fetch(BuildConfig.GUILD_ID); - const DEBUG_CHANNEL = POLYMC_GUILD.channels.cache.get( - BuildConfig.DEBUG_CHANNEL_ID - ); - - if (!DEBUG_CHANNEL || !DEBUG_CHANNEL.isText()) throw new Error(); - DEBUG_CHANNEL.send({ - embeds: [ - { - title: 'Started!', - description: new Date().toISOString(), - color: 'AQUA', - }, - ], - }); - - client.on('messageCreate', async (e) => { - if (!e.content) return; - if (!e.channel.isText()) return; - if (e.author === client.user) return; - - if ( - process.env.NODE_ENV === 'development' && - e.channelId !== BuildConfig.DEBUG_CHANNEL_ID - ) { - return; - } else if ( - process.env.NODE_ENV !== 'development' && - e.channelId === BuildConfig.DEBUG_CHANNEL_ID - ) { - return; - } - - // phishing link filter - { - const urlMatches = [...e.content.matchAll(urlRegex())]; - - if (urlMatches.length) { - console.log('Found links in message from', e.author.tag); - - for (const match of urlMatches) { - console.log('[link]', match[0]); - if (await isBad(match[0])) { - await e.reply({ - embeds: [ - { - title: 'Hold on!', - description: - 'There seems to be a phishing / malware link in your message.', - color: 'RED', - }, - ], - }); - - return; - } - } - } - } - - // neat - { - const cleanContent = removeMarkdown(e.content).toLowerCase(); - if (cleanContent.split(' ').includes('neat')) { - console.log('[neat]', cleanContent); - await e.reply('Neat is a mod by Vazkii.'); - } - } - - const cmd = e.content.split(' ')[0]; - if (!cmd.startsWith('!')) return; - let func = commands[cmd]; - func = func ?? commands[aliases[cmd]]; - - if (func !== undefined) { - await func(client, e); - } - }); -}); diff --git a/package.json b/package.json index ee08c12..f8d08e2 100644 --- a/package.json +++ b/package.json @@ -1,15 +1,17 @@ { "name": "polly", - "version": "0.0.1", + "version": "1.0.0", "license": "MIT", "scripts": { - "dev": "NODE_ENV=development nodemon --watch index.ts --exec esno index.ts", - "build": "esbuild index.ts --format=cjs --platform=node --target=node17 --minify --bundle --sourcemap --external:discord.js --outdir=dist", + "dev": "NODE_ENV=development nodemon --ext ts,json --watch src --exec esno src/index.ts", + "build": "tsc", "lint": "eslint **/*.ts" }, "dependencies": { "@cliqz/adblocker": "^1.23.8", + "discord-command-parser": "^1.5.3", "discord.js": "^13.7.0", + "dotenv": "^16.0.1", "kleur": "^4.1.4", "node-fetch": "^3.2.4", "remove-markdown": "^0.5.0", @@ -17,7 +19,7 @@ }, "devDependencies": { "@esbuild-plugins/node-resolve": "^0.1.4", - "@types/remove-markdown": "^0.3.1", + "@types/bad-words": "^3.0.1", "@types/node": "^17.0.38", "@typescript-eslint/eslint-plugin": "^5.25.0", "@typescript-eslint/parser": "^5.25.0", diff --git a/badLinks.ts b/src/badLinks.ts similarity index 100% rename from badLinks.ts rename to src/badLinks.ts diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..1675ad5 --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,32 @@ +import { MessageEmbed } from 'discord.js'; +import { commands } from '..'; +import { Command } from '..'; + +export const cmd: Command = { + name: 'help', + desc: 'Shows this menu.', + exec: async (e) => { + const embed = new MessageEmbed() + .setTitle('Help Menu') + .setColor('DARK_GREEN'); + let comman = commands; + comman.sort((x, y) => { + return x.name == 'help' ? -1 : y.name == 'help' ? 1 : 0; + }); + for (const i in comman) { + const cmd = comman[i]; + const resp = []; + if (cmd.desc) { + resp.push(cmd.desc); + } + if (cmd.aliases && cmd.aliases[0]) { + resp.push(`**Aliases**: ${cmd.aliases.join(', ')}`); + } + if (cmd.examples && cmd.examples[0]) { + resp.push(`**Examples**: \n${cmd.examples.join('\n> ')}`); + } + embed.addField('!' + cmd.name, resp.join('\n')); + } + return e.reply({ embeds: [embed] }); + }, +}; diff --git a/src/commands/members.ts b/src/commands/members.ts new file mode 100644 index 0000000..11a36d7 --- /dev/null +++ b/src/commands/members.ts @@ -0,0 +1,28 @@ +import { Command } from '../index'; + +export const cmd: Command = { + name: 'members', + desc: 'Shows the amount of online users in PolyMC Discord', + aliases: ['mems', 'memcount'], + exec: async (e) => { + const memes = await e.guild?.members.fetch().then((r) => r.toJSON()); + if (!memes) return; + + return e.reply({ + embeds: [ + { + title: `${memes.length} total members!`, + description: `${ + memes.filter( + (m) => + m.presence?.status === 'online' || + m.presence?.status === 'idle' || + m.presence?.status === 'dnd' + ).length + } online members`, + color: 'GOLD', + }, + ], + }); + }, +}; diff --git a/src/commands/ping.ts b/src/commands/ping.ts new file mode 100644 index 0000000..abf46e4 --- /dev/null +++ b/src/commands/ping.ts @@ -0,0 +1,10 @@ +import { Command } from '../index'; + +export const cmd: Command = { + name: 'ping', + desc: 'Shows the ping of the bot', + aliases: ['test'], + exec: async (e) => { + return await e.reply(`${e.client.ws.ping}ms`); + }, +}; diff --git a/src/commands/stars.ts b/src/commands/stars.ts new file mode 100644 index 0000000..8ee5aed --- /dev/null +++ b/src/commands/stars.ts @@ -0,0 +1,20 @@ +import { Command } from '../index'; + +export const cmd: Command = { + name: 'stars', + desc: 'Shows the number of stars in PolyMC', + aliases: ['star', 'stargazers'], + exec: async (e) => { + const count = await fetch('https://api.github.com/repos/PolyMC/PolyMC') + .then((r) => r.json() as Promise<{ stargazers_count: number }>) + .then((j) => j.stargazers_count); + return e.reply({ + embeds: [ + { + title: `⭐ ${count} total stars!`, + color: 'GOLD', + }, + ], + }); + }, +}; diff --git a/src/commands/tags.ts b/src/commands/tags.ts new file mode 100644 index 0000000..5643d39 --- /dev/null +++ b/src/commands/tags.ts @@ -0,0 +1,27 @@ +import { MessageEmbed } from 'discord.js'; +import { Command } from '../index'; +import { tags } from '../index'; + +export const cmd: Command = { + name: 'tags', + desc: 'Lists the tags available', + exec: async (e) => { + const em = new MessageEmbed().setTitle('tags').setColor('DARK_GREEN'); + + for (let i in tags) { + const tag = tags[i]; + let text = ''; + if (tag.aliases && tag.aliases[0]) { + text += '**Aliases**: ' + tag.aliases.join(', '); + } + + if (tag.text) { + text += tag.text; + } else if (tag.embed) { + text += '\n[embedded message]'; + } + em.addField(tag.name, text); + } + return e.reply({ embeds: [em] }); + }, +}; diff --git a/constants.ts b/src/constants.ts similarity index 100% rename from constants.ts rename to src/constants.ts diff --git a/src/filters.ts b/src/filters.ts new file mode 100644 index 0000000..fb873c0 --- /dev/null +++ b/src/filters.ts @@ -0,0 +1,34 @@ +import * as BuildConfig from './constants'; +import { Message } from 'discord.js'; +import { isBad } from './badLinks'; +import urlRegex from 'url-regex'; + +// true if message is ok, false if filtered +export async function filterMessage(e: Message): Promise { + // url matcher + const urlMatches = [...e.content.matchAll(urlRegex())]; + + if (urlMatches.length) { + console.log('Found links in message from', e.author.tag); + + for (const match of urlMatches) { + console.log('[link]', match[0]); + if (await isBad(match[0])) { + await e.reply({ + embeds: [ + { + title: 'Hold on!', + description: + 'There seems to be a phishing / malware link in your message.', + color: 'RED', + }, + ], + }); + + return false; + } + } + } + + return true; +} diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..e6cde6b --- /dev/null +++ b/src/index.ts @@ -0,0 +1,157 @@ +import { + Client, + Intents, + Message, + MessageEmbed, + MessageEmbedOptions, +} from 'discord.js'; + +import * as BuildConfig from './constants'; +import { filterMessage } from './filters'; +import { green, bold, blue, underline, yellow } from 'kleur/colors'; +import * as parser from 'discord-command-parser'; +import fs from 'fs'; +import path, { dirname } from 'path'; +import { SuccessfulParsedMessage } from 'discord-command-parser'; +import dotenv from 'dotenv'; +import { parseLog } from './mclogs'; +dotenv.config(); + +export interface Command { + name: string; + aliases?: Array; + desc?: string; + examples?: Array; + exec( + m: Message, + p: SuccessfulParsedMessage> + ): Promise | Promise | any; +} + +type Commands = Array; +export let commands: Commands = []; + +interface Tag { + name: string; + aliases?: Array; + text?: string; + embed?: MessageEmbedOptions; +} + +type Tags = Array; +export const tags: Tags = JSON.parse( + fs.readFileSync(path.join(__dirname, 'tags.json'), 'utf8') +); + +const client = new Client({ + intents: [ + Intents.FLAGS.GUILDS, + Intents.FLAGS.GUILD_MESSAGES, + Intents.FLAGS.DIRECT_MESSAGES, + Intents.FLAGS.GUILD_MEMBERS, + Intents.FLAGS.GUILD_PRESENCES, + Intents.FLAGS.GUILD_MESSAGE_REACTIONS, + Intents.FLAGS.GUILD_BANS, + ], +}); + +const dir = fs.readdirSync(path.join(__dirname, '/commands')); +for (const i in dir) { + const cmdName = dir[i]; + const cmd: Command = require(path.join(__dirname, '/commands/', cmdName)).cmd; + commands.push(cmd); +} + +client.once('ready', async () => { + console.log(green('Discord bot ready!')); + + if (process.env.NODE_ENV !== 'development') + console.warn(yellow(bold('Running in production mode!'))); + + console.log( + 'Invite link:', + blue( + underline( + client.generateInvite({ + scopes: ['bot'], + permissions: ['ADMINISTRATOR'], + }) + ) + ) + ); + + const POLYMC_GUILD = await client.guilds.fetch(BuildConfig.GUILD_ID); + + client.on('messageCreate', async (e) => { + if (!e.content) return; + if (!e.channel.isText()) return; + if (e.author === client.user) return; + + if ( + process.env.NODE_ENV === 'development' && + e.channelId !== BuildConfig.DEBUG_CHANNEL_ID + ) { + return; + } else if ( + process.env.NODE_ENV !== 'development' && + e.channelId === BuildConfig.DEBUG_CHANNEL_ID + ) { + return; + } + + const commanded = await parseMsg(e); + if (commanded) return; + + const log = await parseLog(e.content); + if (log != null) { + e.reply({ embeds: [log] }); + return; + } + + const filtered = await filterMessage(e); + if (!filtered) { + return; + } + }); +}); + +async function parseMsg(e: Message) { + const parsed = parser.parse(e, '!', { + allowBots: true, + }); + + if (!parsed.success) return false; + const cmd = commands.find( + (c) => c.name == parsed.command || c.aliases?.includes(parsed.command) + ); + + if (!cmd) { + const tag = tags.find( + (t) => t.name == parsed.command || t.aliases?.includes(parsed.command) + ); + if (tag) { + if (tag.text) { + e.reply(tag.text); + return true; + } else if (tag.embed) { + const em = new MessageEmbed(tag.embed); + e.reply({ embeds: [em] }); + return true; + } + } + return false; + } + try { + await cmd.exec(e, parsed); + } catch (err: any) { + // ts moment + const em = new MessageEmbed() + .setTitle('Error') + .setColor('RED') + .setDescription(err); + e.reply({ embeds: [em] }); + } + return true; +} + +client.login(process.env.DISCORD_TOKEN); diff --git a/src/mclogs.ts b/src/mclogs.ts new file mode 100644 index 0000000..d44a560 --- /dev/null +++ b/src/mclogs.ts @@ -0,0 +1,75 @@ +import { getLatest } from './version'; +import { MessageEmbed } from 'discord.js'; +const reg = /https\:\/\/mclo.gs\/[^ ]*/g; + +type analyzer = (text: string) => Promise | null>; +const javaAnalyzer: analyzer = async (text) => { + if (text.includes('This instance is not compatible with Java version')) { + const xp = + /Please switch to one of the following Java versions for this instance:[\r\n]+([^\r\n]+)/g; + + let ver: string; + const m = text.match(xp); + if (!m || !m[0]) { + ver = ''; + } else { + ver = m[0].split('\n')[1]; + } + + return [ + 'WrongJavaVersion', + `Please switch to the following: \`${ver}\`\nFor more information, type \`!java\``, + ]; + } + return null; +}; + +const versionAnalyzer: analyzer = async (text) => { + const vers = text.match(/PolyMC version: [0-9].[0-9].[0-9]/g); + if (vers && vers[0]) { + const latest = await getLatest(); + const current = vers[0].replace('PolyMC version: ', ''); + if (latest != current) { + return [ + 'OutdatedPolyMC', + `Your installed version is ${current}, while the newest version is ${latest}.\nPlease update, for more info see https://polymc.org/download/`, + ]; + } + } + return null; +}; + +const analyzers: analyzer[] = [javaAnalyzer, versionAnalyzer]; + +export async function parseLog(s: string): Promise { + const r = s.match(reg); + if (r == null || !r[0]) return null; + const link = r[0]; // for now only first url + const id = link.replace('https://mclo.gs/', ''); + if (!id) return null; + const apiUrl = 'https://api.mclo.gs/1/raw/' + id; + let log: string; + try { + const f = await fetch(apiUrl); + if (f.status != 200) { + throw 'nope'; + } + log = await f.text(); + } catch (_) { + return null; + } + const embed = new MessageEmbed() + .setTitle('Log analyzer') + .setColor('DARK_GREEN') + .setDescription(`Analysis of ${link} [${apiUrl}] [ID: ${id}]`); + for (let i in analyzers) { + const analyzer = analyzers[i]; + const out = await analyzer(log); + if (out) embed.addField(out[0], out[1]); + } + if (embed.fields[0]) return embed; + else { + embed.addField('Analyze failed', 'No issues found automatically'); + return embed; + } +} diff --git a/src/tags.json b/src/tags.json new file mode 100644 index 0000000..ed524a5 --- /dev/null +++ b/src/tags.json @@ -0,0 +1,76 @@ +[ + { + "name": "migrate", + "text": "https://polymc.org/wiki/getting-started/migrating-multimc/", + "aliases": ["migr", "mmc"] + }, + { + "name": "matrix", + "text": "https://matrix.to/#/#polymc:matrix.org" + }, + { + "name": "log", + "text": "https://i.imgur.com/gsrgYzg.png" + }, + { + "name": "java", + "text": "https://polymc.org/wiki/getting-started/installing-java/", + "aliases": ["j"] + }, + { + "name": "paths", + "aliases": ["dirs", "locate"], + "embed": { + "title": "Data directories", + "description": "Where PolyMC stores your data (e.g. instances)", + "color": "AQUA", + "fields": [ + { + "name": "Portable (Windows / Linux)", + "value": "In the PolyMC folder" + }, + { + "name": "Windows", + "value": "`%APPDATA%/PolyMC`" + }, + { + "name": "macOS", + "value": "`~/Library/Application Support/PolyMC`" + }, + { + "name": "Linux", + "value": "`~/.local/share/PolyMC`" + }, + { + "name": "Flatpak", + "value": "`~/.var/app/org.polymc.PolyMC/data/PolyMC`" + } + ] + } + }, + { + "name": "build", + "text": "https://polymc.org/wiki/development/build-instructions/" + }, + { + "name": "eta", + "text": "Sometime" + }, + { + "name": "curseforge", + "embed": { + "title": "What's wrong with CurseForge?", + "description": "CurseForge added a new option to block third party clients like PolyMC from accessing mod files, and they started to enforce this option lately. We can't allow you to download those mods directly from PolyMC, but PolyMC 1.3.1 and higher have a workaround to let modpacks work: letting you to download those opted out mods manually. We highly encourage asking authors that opted out to stop doing so.", + "color": "ORANGE" + }, + "aliases": ["cf", "curse", "cursed"] + }, + { + "name": "piracy", + "embed": { + "title": "We don't tolerate piracy!", + "description": "PolyMC has always been legal, legitimate & appropriate. We don't and never will have features such as offline login without an official account.", + "color": "DARK_RED" + } + } +] diff --git a/src/version.ts b/src/version.ts new file mode 100644 index 0000000..e4abff5 --- /dev/null +++ b/src/version.ts @@ -0,0 +1,11 @@ +let cachedVer: string; +let cachedTimestamp: number; + +export async function getLatest(): Promise { + if (cachedVer && Date.now() - cachedTimestamp < 600000) return cachedVer; // 10min + const f = await fetch('https://api.github.com/repos/PolyMC/PolyMC/releases'); + const versions = await f.json(); + cachedVer = versions[0].tag_name; + cachedTimestamp = Date.now(); + return versions[0].tag_name; +} diff --git a/tsconfig.json b/tsconfig.json index d66bcea..e50018f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -3,6 +3,9 @@ "strict": true, "esModuleInterop": true, "downlevelIteration": true, - "target": "ES5" + "target": "ES2018", + "moduleResolution": "node", + "rootDir": "src", + "outDir": "dist" } } diff --git a/yarn.lock b/yarn.lock index f584469..d692147 100644 --- a/yarn.lock +++ b/yarn.lock @@ -239,11 +239,6 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.34.tgz#3b0b6a50ff797280b8d000c6281d229f9c538cef" integrity sha512-XImEz7XwTvDBtzlTnm8YvMqGW/ErMWBsKZ+hMTvnDIjGCKxwK5Xpc+c/oQjOauwq8M4OS11hEkpjX8rrI/eEgA== -"@types/remove-markdown@^0.3.1": - version "0.3.1" - resolved "https://registry.yarnpkg.com/@types/remove-markdown/-/remove-markdown-0.3.1.tgz#82bc3664c313f50f7c77f1bb59935f567689dc63" - integrity sha512-JpJNEJEsmmltyL2LdE8KRjJ0L2ad5vgLibqNj85clohT9AyTrfN6jvHxStPshDkmtcL/ShFu0p2tbY7DBS1mqQ== - "@types/node@^17.0.38": version "17.0.38" resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.38.tgz#f8bb07c371ccb1903f3752872c89f44006132947" @@ -623,6 +618,11 @@ discord-api-types@^0.31.1: resolved "https://registry.yarnpkg.com/discord-api-types/-/discord-api-types-0.31.2.tgz#8d131e25340bd695815af3bb77128a6993c1b516" integrity sha512-gpzXTvFVg7AjKVVJFH0oJGC0q0tO34iJGSHZNz9u3aqLxlD6LfxEs9wWVVikJqn9gra940oUTaPFizCkRDcEiA== +discord-command-parser@^1.5.3: + version "1.5.3" + resolved "https://registry.yarnpkg.com/discord-command-parser/-/discord-command-parser-1.5.3.tgz#ba27097aa0976fa9287ea81f8d8cdd82f2887317" + integrity sha512-YWgalkrbly0dJCyLw7p9SX3RC7HIxOrSTz/8vKjlmYPyyZmMCGmKwpXu6HkPXRZ20L6QqftVWigSw6fDK2zemg== + discord.js@^13.7.0: version "13.7.0" resolved "https://registry.yarnpkg.com/discord.js/-/discord.js-13.7.0.tgz#5172f7f5d816e2c7296015d335b54e46968d9c67" @@ -652,6 +652,11 @@ dot-prop@^5.2.0: dependencies: is-obj "^2.0.0" +dotenv@^16.0.1: + version "16.0.1" + resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.0.1.tgz#8f8f9d94876c35dac989876a5d3a82a267fdce1d" + integrity sha512-1K6hR6wtk2FviQ4kEiSjFiH5rpzEVi8WW0x96aztHVMhEspNpc4DVOUTEHtEva5VThQ8IaBX1Pe4gSzpVVUsKQ== + duplexer3@^0.1.4: version "0.1.4" resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"