From bf24b9f46be96b9ad0fc2eeb8a087abd1c324179 Mon Sep 17 00:00:00 2001 From: Rauf Date: Mon, 13 Jan 2020 17:38:12 -0500 Subject: [PATCH] feat: added events and commands --- package-lock.json | 126 +++++++++++++++++++++-------- package.json | 5 +- src/PluginClient.ts | 9 +++ src/events/Event.ts | 8 ++ src/events/eventLoader.ts | 21 +++++ src/events/guildCreate.ts | 13 +++ src/events/lifeguardCommandUsed.ts | 26 ++++++ src/events/message.ts | 9 +++ src/events/ready.ts | 14 ++++ src/index.ts | 21 +++-- src/plugins/Command.ts | 25 ++++++ src/plugins/Plugin.ts | 8 ++ src/plugins/debug/ping.ts | 30 +++++++ src/plugins/info/help.ts | 17 ++++ src/plugins/pluginLoader.ts | 34 ++++++++ 15 files changed, 324 insertions(+), 42 deletions(-) create mode 100644 src/PluginClient.ts create mode 100644 src/events/Event.ts create mode 100644 src/events/eventLoader.ts create mode 100644 src/events/guildCreate.ts create mode 100644 src/events/lifeguardCommandUsed.ts create mode 100644 src/events/message.ts create mode 100644 src/events/ready.ts create mode 100644 src/plugins/Command.ts create mode 100644 src/plugins/Plugin.ts create mode 100644 src/plugins/debug/ping.ts create mode 100644 src/plugins/info/help.ts create mode 100644 src/plugins/pluginLoader.ts diff --git a/package-lock.json b/package-lock.json index 725299d..54e1099 100644 --- a/package-lock.json +++ b/package-lock.json @@ -76,6 +76,11 @@ } } }, + "@discordjs/collection": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.3.tgz", + "integrity": "sha512-4ek19SmNcPI92942RkuBrZrBK8hg7nG+ae/skkNNDeOaUG+XvxTPkv/jPZVgXwVPDkU5EFsewsI+0n4dTwFvgA==" + }, "@sindresorhus/is": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-0.14.0.tgz", @@ -100,8 +105,23 @@ "@types/node": { "version": "10.17.13", "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.13.tgz", - "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==", - "dev": true + "integrity": "sha512-pMCcqU2zT4TjqYFrWtYHKal7Sl30Ims6ulZ4UFXxI4xbtQqK/qqKwkDoBFCfooRqqmRu9vY3xaJRwxSh673aYg==" + }, + "@types/ws": { + "version": "6.0.4", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-6.0.4.tgz", + "integrity": "sha512-PpPrX7SZW9re6+Ha8ojZG4Se8AZXgf0GK6zmfqEuCsY49LFDNXO3SByp44X3dFEqtB73lkCDAdUazhAjVPiNwg==", + "requires": { + "@types/node": "*" + } + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } }, "ansi-align": { "version": "3.0.0", @@ -183,10 +203,10 @@ "integrity": "sha1-iYUI2iIm84DfkEcoRWhJwVAaSw0=", "dev": true }, - "async-limiter": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/async-limiter/-/async-limiter-1.0.1.tgz", - "integrity": "sha512-csOlWGAcRFJaI6m+F2WKdnMKr4HhdhFVBk0H/QbJFMCr+uO2kwohwXQPxw/9OCxp05r5ghVBFSyioixx3gfkNQ==" + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=" }, "balanced-match": { "version": "1.0.0", @@ -429,6 +449,14 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, "commander": { "version": "2.20.3", "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", @@ -539,6 +567,11 @@ "integrity": "sha512-J7thop4u3mRTkYRQ+Vpfwy2G5Ehoy82I14+14W4YMDLKdWloI9gSzRbV30s/NckQGVJtPkWNcW4oMAUigTdqiQ==", "dev": true }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" + }, "diff": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.1.tgz", @@ -546,15 +579,17 @@ "dev": true }, "discord.js": { - "version": "11.5.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-11.5.1.tgz", - "integrity": "sha512-tGhV5xaZXE3Z+4uXJb3hYM6gQ1NmnSxp9PClcsSAYFVRzH6AJH74040mO3afPDMWEAlj8XsoPXXTJHTxesqcGw==", + "version": "github:discordjs/discord.js#ee0b7c155a1767ed42e2756e53d56368e8b69929", + "from": "github:discordjs/discord.js", "requires": { - "long": "^4.0.0", - "prism-media": "^0.0.3", - "snekfetch": "^3.6.4", - "tweetnacl": "^1.0.0", - "ws": "^6.0.0" + "@discordjs/collection": "^0.1.1", + "abort-controller": "^3.0.0", + "form-data": "^2.3.3", + "node-fetch": "^2.3.0", + "prism-media": "^1.0.0", + "setimmediate": "^1.0.5", + "tweetnacl": "^1.0.1", + "ws": "^7.2.0" } }, "dot-prop": { @@ -614,6 +649,11 @@ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true }, + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, "execa": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/execa/-/execa-0.7.0.tgz", @@ -658,6 +698,16 @@ "locate-path": "^2.0.0" } }, + "form-data": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.1.tgz", + "integrity": "sha512-m21N3WOmEEURgk6B9GLOE4RuWOFf28Lhh9qGYeNlGq4VDXUlJy2th2slBNU8Gp8EzloYZOibZJ7t5ecIrFSjVA==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.6", + "mime-types": "^2.1.12" + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -1056,11 +1106,6 @@ "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==", "dev": true }, - "long": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/long/-/long-4.0.0.tgz", - "integrity": "sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==" - }, "loud-rejection": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/loud-rejection/-/loud-rejection-1.6.0.tgz", @@ -1119,6 +1164,19 @@ "yargs-parser": "^10.0.0" } }, + "mime-db": { + "version": "1.43.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.43.0.tgz", + "integrity": "sha512-+5dsGEEovYbT8UY9yD7eE4XTc4UwJ1jBYlgaQQF38ENsKR3wj/8q8RFZrF9WIZpB2V1ArTVFUva8sAul1NzRzQ==" + }, + "mime-types": { + "version": "2.1.26", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.26.tgz", + "integrity": "sha512-01paPWYgLrkqAyrlDorC1uDwl2p3qZT7yl806vW7DvDoxwXi46jsjFbg+WdwotBIk6/MbEhO/dh5aZ5sNj/dWQ==", + "requires": { + "mime-db": "1.43.0" + } + }, "mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", @@ -1177,6 +1235,11 @@ "integrity": "sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=", "dev": true }, + "node-fetch": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.0.tgz", + "integrity": "sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==" + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -1352,9 +1415,9 @@ "dev": true }, "prism-media": { - "version": "0.0.3", - "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-0.0.3.tgz", - "integrity": "sha512-c9KkNifSMU/iXT8FFTaBwBMr+rdVcN+H/uNv1o+CuFeTThNZNTOrQ+RgXA1yL/DeLk098duAeRPP3QNPNbhxYQ==" + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/prism-media/-/prism-media-1.1.0.tgz", + "integrity": "sha512-W+oxjRyjtd7hw3pefNZuc7YEZ6VICORJvVNfCPs0+7CsJ43CqMjGAYGjPL3hQ82vw03EVra+CiX4zisqOBUUGw==" }, "pseudomap": { "version": "1.0.2", @@ -1530,6 +1593,11 @@ "semver": "^5.0.3" } }, + "setimmediate": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=" + }, "shebang-command": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz", @@ -1551,11 +1619,6 @@ "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=", "dev": true }, - "snekfetch": { - "version": "3.6.4", - "resolved": "https://registry.npmjs.org/snekfetch/-/snekfetch-3.6.4.tgz", - "integrity": "sha512-NjxjITIj04Ffqid5lqr7XdgwM7X61c/Dns073Ly170bPQHLm6jkmelye/eglS++1nfTWktpP6Y2bFXjdPlQqdw==" - }, "spdx-correct": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.1.0.tgz", @@ -1992,12 +2055,9 @@ } }, "ws": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ws/-/ws-6.2.1.tgz", - "integrity": "sha512-GIyAXC2cB7LjvpgMt9EKS2ldqr0MTrORaleiOno6TweZ6r3TKtoFQWay/2PceJ3RuBasOHzXNn5Lrw1X0bEjqA==", - "requires": { - "async-limiter": "~1.0.0" - } + "version": "7.2.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.2.1.tgz", + "integrity": "sha512-sucePNSafamSKoOqoNfBd8V0StlkzJKL2ZAhGQinCfNQ+oacw+Pk7lcdAElecBF2VkLNZRiIb5Oi1Q5lVUVt2A==" }, "xdg-basedir": { "version": "3.0.0", diff --git a/package.json b/package.json index 914a3a7..b599a01 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,8 @@ "devDependencies": { "gts": "^1.1.2", "typescript": "~3.7.0", - "@types/node": "^10.0.3" + "@types/node": "^10.0.3", + "@types/ws": "^6.0.4" }, "scripts": { "check": "gts check", @@ -22,6 +23,6 @@ "posttest": "npm.cmd run check" }, "dependencies": { - "discord.js": "^11.5.1" + "discord.js": "github:discordjs/discord.js" } } diff --git a/src/PluginClient.ts b/src/PluginClient.ts new file mode 100644 index 0000000..18d7387 --- /dev/null +++ b/src/PluginClient.ts @@ -0,0 +1,9 @@ +import { Client, ClientOptions, Collection } from 'discord.js'; +import { Plugin } from './plugins/Plugin'; + +export class PluginClient extends Client { + plugins!: Collection; + constructor(options?: ClientOptions) { + super(options); + } +} diff --git a/src/events/Event.ts b/src/events/Event.ts new file mode 100644 index 0000000..7fe5f3f --- /dev/null +++ b/src/events/Event.ts @@ -0,0 +1,8 @@ +import { PluginClient } from '../PluginClient'; + +// tslint:disable-next-line: no-any +type EventFunc = (lifeguard: PluginClient, ...args: any[]) => void; + +export class Event { + constructor(public name: string, public func: EventFunc) {} +} diff --git a/src/events/eventLoader.ts b/src/events/eventLoader.ts new file mode 100644 index 0000000..5219423 --- /dev/null +++ b/src/events/eventLoader.ts @@ -0,0 +1,21 @@ +import { Client } from 'discord.js'; +import { readdir } from 'fs'; +import { promisify } from 'util'; +import { Event } from './Event'; + +const readDir = promisify(readdir); + +export async function EventLoader(lifeguard: Client) { + const eventFiles = await readDir('./build/src/events'); + + for await (const file of eventFiles) { + if (file.endsWith('js') && file !== 'Event.js') { + const { event } = require(`./${file}`); + if (event instanceof Event) { + lifeguard.on(event.name, (...args: []) => { + event.func(lifeguard, ...args); + }); + } + } + } +} diff --git a/src/events/guildCreate.ts b/src/events/guildCreate.ts new file mode 100644 index 0000000..ccf097f --- /dev/null +++ b/src/events/guildCreate.ts @@ -0,0 +1,13 @@ +import { Event } from './Event'; + +export const event = new Event('guildCreate', lifeguard => { + if (lifeguard.user) { + lifeguard.user.setPresence({ + activity: { + name: `${lifeguard.users.size} people in the pool`, + type: 'WATCHING', + }, + status: 'online', + }); + } +}); diff --git a/src/events/lifeguardCommandUsed.ts b/src/events/lifeguardCommandUsed.ts new file mode 100644 index 0000000..50198d6 --- /dev/null +++ b/src/events/lifeguardCommandUsed.ts @@ -0,0 +1,26 @@ +import { Event } from './Event'; +import { Message } from 'discord.js'; +import { prefix } from '../config/bot'; +import { PluginClient } from '../PluginClient'; + +function parseContent(content: string) { + const split = content.split(' '); + const cmdName = split[0].slice(prefix.length); + split.shift(); + return [cmdName, ...split]; +} + +function getCommandFromPlugin(lifeguard: PluginClient, cmdName: string) { + const plugin = lifeguard.plugins.find(p => p.has(cmdName)); + const command = plugin?.get(cmdName); + return command; +} + +export const event = new Event( + 'lifeguardCommandUsed', + async (lifeguard, msg: Message) => { + const [cmdName, ...args] = parseContent(msg.content); + const cmd = getCommandFromPlugin(lifeguard, cmdName); + cmd?.func(lifeguard, msg, args); + } +); diff --git a/src/events/message.ts b/src/events/message.ts new file mode 100644 index 0000000..de393a9 --- /dev/null +++ b/src/events/message.ts @@ -0,0 +1,9 @@ +import { Event } from './Event'; +import { Message } from 'discord.js'; +import { prefix } from '../config/bot'; + +export const event = new Event('message', async (lifeguard, msg: Message) => { + if (msg.content.startsWith(prefix)) { + lifeguard.emit('lifeguardCommandUsed', msg); + } +}); diff --git a/src/events/ready.ts b/src/events/ready.ts new file mode 100644 index 0000000..dd6a506 --- /dev/null +++ b/src/events/ready.ts @@ -0,0 +1,14 @@ +import { Event } from './Event'; + +export const event = new Event('ready', lifeguard => { + console.log('Connected to Discord'); + if (lifeguard.user) { + lifeguard.user.setPresence({ + activity: { + name: `${lifeguard.users.size} people in the pool`, + type: 'WATCHING', + }, + status: 'online', + }); + } +}); diff --git a/src/index.ts b/src/index.ts index 66c2fd4..de52783 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,12 +1,19 @@ -import { Client } from 'discord.js'; import { token } from './config/bot'; +import { EventLoader } from './events/eventLoader'; +import { PluginLoader } from './plugins/pluginLoader'; +import { PluginClient } from './PluginClient'; -const lifeguard = new Client(); +const lifeguard = new PluginClient(); -lifeguard - .login(token) - .then(_ => +EventLoader(lifeguard); +PluginLoader().then(plugins => { + lifeguard.plugins = plugins; +}); + +lifeguard.login(token).then(() => { + if (lifeguard.user) { console.log( `Logged in to ${lifeguard.user.username}#${lifeguard.user.discriminator}` - ) - ); + ); + } +}); diff --git a/src/plugins/Command.ts b/src/plugins/Command.ts new file mode 100644 index 0000000..f61d46a --- /dev/null +++ b/src/plugins/Command.ts @@ -0,0 +1,25 @@ +import { Message, PermissionString } from 'discord.js'; +import { PluginClient } from '../PluginClient'; + +type CommandFunction = ( + lifeguard: PluginClient, + msg: Message, + args: string[] +) => void; + +interface CommandOptions { + alias?: string[]; + guildOnly?: boolean; + hidden?: boolean; + level: number; + usage: string[]; + permissions?: PermissionString[]; +} + +export class Command { + constructor( + public name: string, + public func: CommandFunction, + public options: CommandOptions + ) {} +} diff --git a/src/plugins/Plugin.ts b/src/plugins/Plugin.ts new file mode 100644 index 0000000..861611d --- /dev/null +++ b/src/plugins/Plugin.ts @@ -0,0 +1,8 @@ +import { Collection } from 'discord.js'; +import { Command } from './Command'; + +export class Plugin extends Collection { + constructor() { + super(); + } +} diff --git a/src/plugins/debug/ping.ts b/src/plugins/debug/ping.ts new file mode 100644 index 0000000..04f6b7e --- /dev/null +++ b/src/plugins/debug/ping.ts @@ -0,0 +1,30 @@ +import { Command } from '../Command'; +import { MessageEmbed } from 'discord.js'; + +export const command = new Command( + 'ping', + async (lifeguard, msg, args) => { + const m = await msg.channel.send('Ping?'); + m.delete({ timeout: 100 }); + + const embed = new MessageEmbed() + .setTitle('Pong! :ping_pong:') + .addField('Bot Latency', `${Math.round(lifeguard.ws.ping)}ms`) + .addField( + 'Message Latency', + `${m.createdTimestamp - msg.createdTimestamp}ms` + ) + .setColor(0x7289da) + .setFooter( + `Executed By ${msg.author.tag}`, + msg.author.avatarURL() ?? msg.author.defaultAvatarURL + ) + .setTimestamp(); + + msg.channel.send(embed); + }, + { + level: 0, + usage: ['ping'], + } +); diff --git a/src/plugins/info/help.ts b/src/plugins/info/help.ts new file mode 100644 index 0000000..d1cc34c --- /dev/null +++ b/src/plugins/info/help.ts @@ -0,0 +1,17 @@ +import { Command } from '../Command'; + +export const command = new Command( + 'help', + (lifeguard, msg, args) => { + const cmds = Array.from(lifeguard.plugins.values()) + .map(plugin => Array.from(plugin.values())) + .reduce((acc, val) => acc.concat(val), []) + .filter(cmd => !cmd.options.hidden) + .sort((a, b) => a.name.localeCompare(b.name)); + msg.channel.send(cmds.map(c => c.name).join('\n')); + }, + { + level: 0, + usage: ['help', 'help [name]'], + } +); diff --git a/src/plugins/pluginLoader.ts b/src/plugins/pluginLoader.ts new file mode 100644 index 0000000..5dc5dd3 --- /dev/null +++ b/src/plugins/pluginLoader.ts @@ -0,0 +1,34 @@ +import { promisify } from 'util'; +import { readdir, lstat } from 'fs'; +import { Collection } from 'discord.js'; +import { Plugin } from './Plugin'; +import { Command } from './Command'; + +export async function PluginLoader() { + const readDir = promisify(readdir); + const stats = promisify(lstat); + + const plugins = new Collection(); + + const pluginDir = './build/src/plugins'; + const folders = await readDir(pluginDir); + for await (const folder of folders) { + const folderDir = `${pluginDir}/${folder}`; + const info = await stats(folderDir); + if (info.isDirectory()) { + const plugin = new Plugin(); + + const files = await readDir(`${folderDir}`); + for await (const file of files) { + if (file.endsWith('.js')) { + const command: Command = require(`./${folder}/${file}`).command; + plugin.set(command.name, command); + } + } + + plugins.set(folder, plugin); + } + } + + return plugins; +}