diff --git a/README.md b/README.md index 3139237..d2e91d8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,23 @@ -# Team Codemonkey Discord Bot +# Venom Discord Bot -This is the Discord bot for Team Codemonkey \ No newline at end of file +This is the Discord bot for Creation Asylum. + +## Development + +- Requires [NodeJS](https://nodejs.org/), recommend at least the latest LTS version. +- Run `yarn` or `npm install` to install dependencies + +### Environment Variables + +At a minimum you need to provide the Discord bots Token, which can be found on the Bot tab of a Discord application. See table below for possible values. + +| key | description | example | +|-------------------|-------------|---------| +| BOT_TRIGGER | Prefix of message to let bot know you are speaking to it +| DISCORD_BOT_TOKEN | Discord bots Token +| ENVIRONMENT | What environment the bot is running in | `production`, `development` or `test` | +| LOG_LEVEL | What level of logs should be displayed in console | `error`, `warn`, `info`, `verbose`, `debug` or `silly` | + +### Bot commands + +To add a command you create a Typescript file in `src/bot/commands/[filename].ts` and ensure it implements the \src/bot/commands/ICommand.ts` interface. You can see the other files in this directory for implementation examples. Also ensure you export this file in `src/bot/commands/index.ts`. diff --git a/package.json b/package.json index 3ac85d5..180ed3e 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { - "name": "teamcodemonkey-bot", + "name": "venom", "version": "0.0.1", - "description": "A bot for the Team Codemonkey Discord", + "description": "A bot for the Creation Asylum Discord", "main": "index.js", - "repository": "https://github.com/jondeaves/tcm-bot", - "bugs": "https://github.com/jondeaves/jondeaves/issues", + "repository": "https://github.com/jondeaves/venom", + "bugs": "https://github.com/jondeaves/venom/issues", "author": "Jon Deaves https://jondeaves.me", "contributors": [ "Jon Deaves https://jondeaves.me" @@ -35,4 +35,4 @@ "tslint-react": "~5.0.0", "typescript": "~3.9.7" } -} +} \ No newline at end of file diff --git a/src/.env.template b/src/.env.template new file mode 100644 index 0000000..f369443 --- /dev/null +++ b/src/.env.template @@ -0,0 +1,4 @@ +BOT_TRIGGER=! +DISCORD_BOT_TOKEN=[replace with own token] +ENVIRONMENT=production +LOG_LEVEL=error \ No newline at end of file diff --git a/src/app.ts b/src/app.ts index 9c81ac8..3056a4b 100644 --- a/src/app.ts +++ b/src/app.ts @@ -1,10 +1,13 @@ +import { exit } from 'process'; import Discord from 'discord.js'; import container from './inversity.config'; import ConfigService from './core/services/config.service'; import LoggerService from './core/services/logger.service'; -import { exit } from 'process'; + +import ICommand from './bot/commands/ICommand'; +import rawCommands from './bot/commands'; export default class App { private _configService: ConfigService = container.resolve(ConfigService); @@ -14,11 +17,40 @@ export default class App { public async init(): Promise { this._discordClient = new Discord.Client(); + const commandList = new Discord.Collection(); + rawCommands.forEach(rawCommand => { + commandList.set(rawCommand.name, rawCommand); + }); + + // Triggers once after connecting to server this._discordClient.once('ready', () => { this._loggerService.log('info', "The Bot is connected to Discord server"); }); + // Triggers on every message the bot can see + this._discordClient.on('message', async message => { + const prefix = this._configService.get('BOT_TRIGGER'); + + // If the message either doesn't start with the prefix or was sent by a bot, exit early. + if (!message.content.startsWith(prefix) || message.author.bot) return; + + const args = message.content.slice(prefix.length).trim().split(/ +/); + const commandName = args.shift().toLowerCase(); + const command = commandList.get(commandName) || commandList.find(cmd => cmd.aliases && cmd.aliases.includes(commandName)); + + if (!command) { + return message.reply('Monkey no understand that command yet!'); + }; + + try { + await command.execute(message, args, prefix, commandList); + } catch (error) { + console.error(error); + message.reply('there was an error trying to execute that command!'); + } + }); + this._discordClient.login(this._configService.get('DISCORD_BOT_TOKEN')) .catch((reason) => { this._loggerService.log('error', `Cannot initialise Discord client. Check the token: ${this._configService.get('DISCORD_BOT_TOKEN')}`); diff --git a/src/bot/commands/ICommand.ts b/src/bot/commands/ICommand.ts new file mode 100644 index 0000000..c359bcf --- /dev/null +++ b/src/bot/commands/ICommand.ts @@ -0,0 +1,8 @@ +import Discord, { Collection } from 'discord.js'; + +export default interface ICommand { + name: string; + aliases?: string[]; + description: string; + execute: (message: Discord.Message, args: string[], prefix?: string, commands?: Collection) => Promise, +} \ No newline at end of file diff --git a/src/bot/commands/help.ts b/src/bot/commands/help.ts new file mode 100644 index 0000000..60124c5 --- /dev/null +++ b/src/bot/commands/help.ts @@ -0,0 +1,47 @@ +import Discord, { Collection } from 'discord.js'; + +import ICommand from './ICommand'; + +const command: ICommand = { + name: 'help', + aliases: ['commands'], + description: 'Lists available commands!', + async execute(message: Discord.Message, args: string[], prefix: string, commands: Collection) { + const data = []; + + if (!args.length) { + // Get for all commands + data.push('Here\'s a list of all my commands:\n'); + data.push(commands.map(command => command.name).join(', ')); + data.push(`\nYou can send \`${prefix}help [command name]\` to get info on a specific command!`); + } else { + // Get description of single command + const name = args[0].toLowerCase(); + const command = commands.get(name) || commands.find(c => c.aliases && c.aliases.includes(name)); + + if (!command) { + message.reply('that\'s not a valid command!'); + } else { + data.push(`**Name:** ${command.name}`); + + if (command.aliases) data.push(`**Aliases:** ${command.aliases.join(', ')}`); + if (command.description) data.push(`**Description:** ${command.description}`); + } + } + + + + try { + await message.author.send(data, { split: true }); + if (message.channel.type === 'dm') + return; + message.reply('I\'ve sent you a DM with all my commands!'); + } + catch (error) { + console.error(`Could not send help DM to ${message.author.tag}.\n`, error); + message.reply('it seems like I can\'t DM you! Do you have DMs disabled?'); + } + }, +}; + +export default command; \ No newline at end of file diff --git a/src/bot/commands/index.ts b/src/bot/commands/index.ts new file mode 100644 index 0000000..3bfaee0 --- /dev/null +++ b/src/bot/commands/index.ts @@ -0,0 +1,9 @@ +import help from './help'; +import ping from './ping'; +import see from './see'; + +export default [ + help, + ping, + see +] \ No newline at end of file diff --git a/src/bot/commands/ping.ts b/src/bot/commands/ping.ts new file mode 100644 index 0000000..eb85c6b --- /dev/null +++ b/src/bot/commands/ping.ts @@ -0,0 +1,14 @@ +import Discord from 'discord.js'; + +import ICommand from './ICommand'; + +const command: ICommand = { + name: 'ping', + aliases: ['hello'], + description: 'Ping!', + async execute(message: Discord.Message, args: string[]) { + message.reply('Pong.'); + }, +}; + +export default command; \ No newline at end of file diff --git a/src/bot/commands/see.ts b/src/bot/commands/see.ts new file mode 100644 index 0000000..2bbd102 --- /dev/null +++ b/src/bot/commands/see.ts @@ -0,0 +1,13 @@ +import Discord from 'discord.js'; + +import ICommand from './ICommand'; + +const command: ICommand = { + name: 'see', + description: 'See!', + async execute(message: Discord.Message, args: string[]) { + message.reply(`Server: ${message.guild.name}\nYour username: ${message.author.username}\nYour ID: ${message.author.id}`); + }, +}; + +export default command; \ No newline at end of file diff --git a/src/core/services/config.service.ts b/src/core/services/config.service.ts index 9f9ae1b..15a75f6 100644 --- a/src/core/services/config.service.ts +++ b/src/core/services/config.service.ts @@ -31,6 +31,7 @@ export default class ConfigService { private setup(): void { this.config = { + BOT_TRIGGER: process.env.BOT_TRIGGER || '!', DISCORD_BOT_TOKEN: process.env.DISCORD_BOT_TOKEN, ENVIRONMENT: (process.env.NODE_ENV as Environment) || 'development', LOG_LEVEL: (process.env.LOG_LEVEL as LogLevel) || 'info', diff --git a/src/core/types/Config.ts b/src/core/types/Config.ts index aa97f74..8b12d5a 100644 --- a/src/core/types/Config.ts +++ b/src/core/types/Config.ts @@ -2,6 +2,7 @@ import LogLevel from "./LogLevel"; import Environment from "./Environment"; export default interface Config { + BOT_TRIGGER: string; DISCORD_BOT_TOKEN: string; ENVIRONMENT: Environment; LOG_LEVEL: LogLevel;