From cb09663742c32799853da574749c392892407a84 Mon Sep 17 00:00:00 2001 From: Jason Durand Date: Sun, 8 Mar 2020 17:47:58 -0400 Subject: [PATCH] CopyPasted from misaBot --- package-lock.json | 21 +++++-- package.json | 4 +- src/Command.ts | 74 +++++++++++++++++++++++ src/commands/echo.ts | 12 ++++ src/commands/echolog.ts | 13 ++++ src/commands/help.ts | 42 +++++++++++++ src/commands/reloadcommand.ts | 58 ++++++++++++++++++ src/index.ts | 108 ++++++++++++++++++++++++++++++++++ 8 files changed, 327 insertions(+), 5 deletions(-) create mode 100644 src/Command.ts create mode 100644 src/commands/echo.ts create mode 100644 src/commands/echolog.ts create mode 100644 src/commands/help.ts create mode 100644 src/commands/reloadcommand.ts create mode 100644 src/index.ts diff --git a/package-lock.json b/package-lock.json index f228baf..b95c56c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "conjurebot", - "version": "0.1.0", + "version": "0.0.1", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -9,6 +9,19 @@ "resolved": "https://registry.npmjs.org/@discordjs/collection/-/collection-0.1.5.tgz", "integrity": "sha512-CU1q0UXQUpFNzNB7gufgoisDHP7n+T3tkqTsp3MNUkVJ5+hS3BCvME8uCXAUFlz+6T2FbTCu75A+yQ7HMKqRKw==" }, + "@types/node": { + "version": "13.9.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-13.9.0.tgz", + "integrity": "sha512-0ARSQootUG1RljH2HncpsY2TJBfGQIKOOi7kxzUY6z54ePu/ZD+wJA8zI2Q6v8rol2qpG/rvqsReco8zNMPvhQ==" + }, + "@types/ws": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.2.2.tgz", + "integrity": "sha512-oqnI3DbGCVI9zJ/WHdFo3CUE8jQ8CVQDUIKaDtlTcNeT4zs6UCg9Gvk5QrFx2QPkRszpM6yc8o0p4aGjCsTi+w==", + "requires": { + "@types/node": "*" + } + }, "abort-controller": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", @@ -36,9 +49,9 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=" }, "discord.js": { - "version": "12.0.1", - "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.0.1.tgz", - "integrity": "sha512-lUlrkAWSb5YTB1WpSZHjeUXxGlHK8VDjrlHLEP4lJj+etFAellURpmRYl29OPJ/7arQWB879pP4rvhhzpdOF7w==", + "version": "12.0.2", + "resolved": "https://registry.npmjs.org/discord.js/-/discord.js-12.0.2.tgz", + "integrity": "sha512-iZiEA4Y61gqq/EjFfLXnkRK9pLapnax/vTVDUhs/mAhyqozAy0GOlk/MZI9rSa1iIoKTWRq6P9CRKhLNT2wUnA==", "requires": { "@discordjs/collection": "^0.1.5", "abort-controller": "^3.0.0", diff --git a/package.json b/package.json index fe6e137..32a0c45 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "author": "misabiko", "license": "ISC", "dependencies": { - "discord.js": "^12.0.1", + "@types/node": "^13.9.0", + "@types/ws": "^7.2.2", + "discord.js": "^12.0.2", "typescript": "^3.8.3" } } diff --git a/src/Command.ts b/src/Command.ts new file mode 100644 index 0000000..e1bb778 --- /dev/null +++ b/src/Command.ts @@ -0,0 +1,74 @@ +import {Message} from 'discord.js'; +import {CustomClient} from "./index"; + +export class CommandCollection { + private commands : { [commandName : string] : Command } = {}; + + get(commandName : string, aliased = true) : Command { + let command = this.commands[commandName]; + + if (!command && aliased) + command = Object.values(this.commands).find( + (cmd : Command) => cmd.checkName(commandName) + ) as Command; + + return command; + } + + has(commandName : string, aliased = true) : boolean { + return !!this.get(commandName, aliased); + } + + add(command : Command) { + if (this.commands[command.name]) + return console.error(`Command ${command.name} is already in the collection`); + + this.commands[command.name] = command; + } + + clear(exceptions : Command[]) { + for (const commandName in this.commands) + if (!exceptions.find(cmd => cmd.checkName(commandName))) + delete this.commands[commandName]; + } + + getNames() { + return Object.keys(this.commands); + } +} + +export class Command { + description? : string; + args? : string[]; + guildOnly? : boolean; + cooldown? : number; + aliases? : string[]; + usage? : string; + permissions? : string[]; + + constructor( + public name : string, + public execute : (message? : Message, args? : string[], client? : CustomClient) => void, + { + description = null as string, + args = null as string[], + guildOnly = false, + cooldown = 0, + aliases = null as string[], + usage = null as string, + permissions = null as string[], + } + ) { + this.description = description; + this.args = args; + this.guildOnly = guildOnly; + this.cooldown = cooldown; + this.aliases = aliases; + this.usage = usage; + this.permissions = permissions; + } + + checkName(commandName : string) { + return commandName === this.name || (this.aliases && this.aliases.includes(commandName)); + } +} \ No newline at end of file diff --git a/src/commands/echo.ts b/src/commands/echo.ts new file mode 100644 index 0000000..2277b65 --- /dev/null +++ b/src/commands/echo.ts @@ -0,0 +1,12 @@ +import {Command} from "../Command"; + +const echo = new Command( + 'echo', + (message, args) => { + message.channel.send(args.join(' ')); + }, + { + description: "Echo's the arguments as a message" + } +); +export default echo; \ No newline at end of file diff --git a/src/commands/echolog.ts b/src/commands/echolog.ts new file mode 100644 index 0000000..fedc1ff --- /dev/null +++ b/src/commands/echolog.ts @@ -0,0 +1,13 @@ +import {Command} from "../Command"; + +const echolog = new Command( + 'echolog', + (message, args) => { + console.log("Echolog:"); + for (const arg of args) + console.log(arg); + }, + { + description: "Echo's the arguments on the console" + }); +export default echolog; \ No newline at end of file diff --git a/src/commands/help.ts b/src/commands/help.ts new file mode 100644 index 0000000..e06339f --- /dev/null +++ b/src/commands/help.ts @@ -0,0 +1,42 @@ +import {Command} from "../Command"; +import {CustomClient} from "../index"; + +const {prefix} = require("../config.json"); + +const help = new Command( + 'help', + (message, args) => { + const data = []; + const {commandCollection} = message.client as CustomClient; + + if (!args.length) { + data.push("Here's a list of the commands:"); + data.push(commandCollection.getNames().join(", ")); + data.push(`\nSend \`${prefix}help [command name]\` for info on a specific command.`); + + return message.channel.send(data, {split: true}); + } + + const name = args[0].toLowerCase(); + const command = commandCollection.get(name); + + if (!command) + return message.reply(`${name} isn't not a valid command, call ${prefix}help for the list of available commands.`); + + data.push(`**Name:** ${command.name}`); + + if (command.aliases) data.push(`**Aliases:** ${command.aliases.join(', ')}`); + if (command.description) data.push(`**Description:** ${command.description}`); + if (command.usage) data.push(`**Usage:** ${prefix}${command.name} ${command.usage}`); + + data.push(`**Cooldown:** ${command.cooldown || 1} second(s)`); + + message.channel.send(data, {split: true}); + }, + { + description: "List every commands or info about a specific command.", + aliases: ["commands"], + usage: "[command name]" + } +); +export default help; \ No newline at end of file diff --git a/src/commands/reloadcommand.ts b/src/commands/reloadcommand.ts new file mode 100644 index 0000000..0631e53 --- /dev/null +++ b/src/commands/reloadcommand.ts @@ -0,0 +1,58 @@ +import * as fs from "fs"; +import * as path from "path"; +import {Message} from "discord.js"; +import {Command} from "../Command"; +import {CustomClient} from "../index"; + +const {userDict} = require("../config.json"); + +const reloadcommand = new Command( + 'reloadcommand', + async (message, args, client) => { + if (args.length) + await reloadSingleCommand(args[0].toLowerCase(), client, message); + else + await reloadCommands(client, message); + }, { + description: "Reloads either every, or a given command module.", + aliases: ["reload"], + usage: "[commandName]", + permissions: [userDict.misabiko] + } +); +export default reloadcommand; + +async function reloadSingleCommand(commandName : string, client : CustomClient, message : Message) { + const {commandCollection} = client; + + if (commandName === 'reloadcommand') + return message.channel.send("You have to restart the bot to reload this command."); + + if (!commandCollection.has(commandName)) + return message.channel.send(`The ${commandName} command doesn't exist.`); + + if (await client.importCommand(`./${commandName}.js`, message)) + await message.channel.send(`Successfully reloaded the ${commandName} command.`); + else + await message.channel.send(`Failed to reload the ${commandName} command.`); +} + +async function reloadCommands(client : CustomClient, message : Message) { + const {commandCollection} = client; + + commandCollection.clear([ + commandCollection.get('reloadcommand') + ]); + + const commandFiles = fs.readdirSync(__dirname).filter(file => file.endsWith(".js")); + + let hadErrors = false; + for (const file of commandFiles) + if (file !== "reloadcommand.js") + hadErrors = hadErrors || !(await client.importCommand(path.join(__dirname, file), message)); + + if (hadErrors) + await message.channel.send(`Failed to reload some commands.`); + else + await message.channel.send(`Successfully reloaded the commands.`); +} \ No newline at end of file diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..2fc39b8 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,108 @@ +import * as path from 'path'; +import * as fs from 'fs'; +import * as Discord from "discord.js"; +import {Message, Snowflake} from "discord.js"; +import {Command, CommandCollection} from "./Command"; +import {prefix, token, userDict} from "./config.json"; + +export class CustomClient extends Discord.Client { + commandCollection = new CommandCollection(); + cooldowns : { [name : string] : Discord.Collection } = {}; + prefix = prefix; + + async importCommand(commandPath : string, message? : Message) : Promise { + try { + this.commandCollection.add((await import(commandPath)).default); + }catch(error) { + const errorMessage = "Error while importing command " + commandPath; + console.error(errorMessage); + console.error(error); + + if (message) + await message.reply(errorMessage); + else { + const user = await this.users.fetch(userDict.misabiko); + await user.send(errorMessage); + } + + return false; + } + + return true; + } + + handleMessage(message : Message) { + //If message doesn't have prefix or is bot-made, ignore + if (!message.content.startsWith(prefix) || message.author.bot) return; + + const args = message.content.slice(prefix.length).split(/ +/); + const commandName = args.shift().toLowerCase(); + + const command = client.commandCollection.get(commandName); + if (!command) return; + + if (command.args && !args.length) { + let reply = `You didn't provide any arguments, ${message.author}.`; + + if (command.usage) + reply += `\nUsage: \`${prefix}${command.name} ${command.usage}\``; + + return message.channel.send(reply); + } + + if (command.guildOnly && message.channel.type !== 'text') + return message.reply(`You can only call the ${command} commmand on a server.`); + + if (command.permissions && !command.permissions.includes(message.author.id)) + return message.reply(`You don't have the permission for that command, ask <@${userDict.misabiko}> for help.`); + + if (!this.cooldowns[command.name]) + this.cooldowns[command.name] = new Discord.Collection(); + + const timeLeft = this.getTimeLeft(command, message.author.id); + if (timeLeft) + return message.reply(`Please wait ${timeLeft.toFixed(1)} more second(s) before reusing the \`${command.name}\` command.`); + + try { + command.execute(message, args, client); + }catch (error) { + console.error(error); + message.reply("There was an error trying to execute the command."); + } + } + + getTimeLeft(command : Command, authorId : string) { + const now = Date.now(); + const timestamps : Discord.Collection = this.cooldowns[command.name]; + const cooldownAmount = command.cooldown * 1000; + + if (!timestamps.has(authorId)) { + timestamps.set(authorId, now); + setTimeout(() => timestamps.delete(authorId), cooldownAmount); + }else { + const expirationTime = timestamps.get(authorId) + cooldownAmount; + + if (now < expirationTime) + return (expirationTime - now) / 1000; + + timestamps.set(authorId, now); + setTimeout(() => timestamps.delete(authorId), cooldownAmount); + } + } + + getCommand(message : string) { + return message.substring(this.prefix.length); + } +} + +const client = new CustomClient(); +const commandFiles = fs.readdirSync(path.join(__dirname, "commands")).filter((file : string) => file.endsWith(".js")); + +for (const file of commandFiles) + client.importCommand(path.join(__dirname, "commands", file)).then(); + +client.on("message", message => client.handleMessage(message as Message)); + +client.on("ready", () => console.log("Ready!")); + +client.login(token).then(() => console.log("ConjureBot logged in!")); \ No newline at end of file