From 269c1f99545d7ca3088b31a691ccb14e7dd2e348 Mon Sep 17 00:00:00 2001 From: benji7425 Date: Fri, 1 Sep 2017 15:51:05 +0100 Subject: [PATCH] Squash merge v2 template code --- .gitignore | 79 +++++++++++++++++++++++++ .npmrc | 3 + .vscode/launch.json | 14 +++++ .vscode/settings.json | 9 +++ CHANGELOG.md | 3 + app/bot.js | 13 +++++ app/config.json | 22 +++++++ app/index.js | 121 +++++++++++++++++++++++++++++++++++++++ app/models/guild-data.js | 7 +++ bootstrap.js | 20 +++++++ package.json | 12 ++++ 11 files changed, 303 insertions(+) create mode 100644 .gitignore create mode 100644 .npmrc create mode 100644 .vscode/launch.json create mode 100644 .vscode/settings.json create mode 100644 CHANGELOG.md create mode 100644 app/bot.js create mode 100644 app/config.json create mode 100644 app/index.js create mode 100644 app/models/guild-data.js create mode 100644 bootstrap.js create mode 100644 package.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..00b213c --- /dev/null +++ b/.gitignore @@ -0,0 +1,79 @@ +### Discord bots #### +guilds.json +token.json +log + + +# Created by https://www.gitignore.io/api/node,visualstudiocode + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_cache +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json +.history + +# End of https://www.gitignore.io/api/node,visualstudiocode diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..799e6bc --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +save=true +save-exact=true +cache=node_cache diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..120515c --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,14 @@ +{ + // Use IntelliSense to learn about possible Node.js debug attributes. + // Hover to view descriptions of existing attributes. + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 + "version": "0.2.0", + "configurations": [ + { + "type": "node", + "request": "launch", + "name": "Launch Program", + "program": "${workspaceRoot}/bootstrap.js" + } + ] +} \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..728ef00 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,9 @@ +{ + "files.exclude": { + "log": true, + ".npmrc": true, + "node_modules": true, + "node_cache": true, + "token.json": true + } +} \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 0000000..8deb76e --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,3 @@ +# Changelog + +## Unreleased \ No newline at end of file diff --git a/app/bot.js b/app/bot.js new file mode 100644 index 0000000..ff81e7d --- /dev/null +++ b/app/bot.js @@ -0,0 +1,13 @@ +const GuildData = require("./models/guild-data.js"); + +module.exports = { + onCommand(commandObj, commandsObj, params, guildData, message) { + switch (commandObj.command) { + case commandsObj.commandName.command: + return; //return promise! + } + }, + onNonCommandMsg(message, guildData) { + return; + } +}; \ No newline at end of file diff --git a/app/config.json b/app/config.json new file mode 100644 index 0000000..e4c69c2 --- /dev/null +++ b/app/config.json @@ -0,0 +1,22 @@ +{ + "generic": { + "saveFile": "./guilds.json", + "saveIntervalSec": 60, + "website": "https://benji7425.github.io", + "discordInvite": "https://discord.gg/SSkbwSJ", + "defaultDMResponse": "This bot does not have any handling for direct messages. To learn more or get help please visit %s, or join my Discord server here: %s" }, + "commands": { + "version": { + "command": "version", + "description": "Returns the bot version", + "syntax": "version", + "admin": false + }, + "help": { + "command": "help", + "description": "Display information about commands available to you", + "syntax": "help", + "admin": false + } + } +} \ No newline at end of file diff --git a/app/index.js b/app/index.js new file mode 100644 index 0000000..759e2b7 --- /dev/null +++ b/app/index.js @@ -0,0 +1,121 @@ +//node imports +const FileSystem = require("fs"); //manage files +const Util = require("util"); //various node utilities + +//external lib imports +const Discord = require("discord.js"); +const JsonFile = require("jsonfile"); //save/load data to/from json + +//my imports +const DiscordUtil = require("discordjs-util"); //some discordjs helper functions of mine + +//app components +const GuildData = require("./models/guild-data.js"); //data structure for guilds +const PackageJSON = require("../package.json"); //used to provide some info about the bot +const Bot = require("./bot.js"); + +//global vars +let writeFile = null; + +//use module.exports as a psuedo "onready" function +module.exports = (client, config = null) => { + config = config || require("./config.json"); //load config file + const guildsData = FileSystem.existsSync(config.generic.saveFile) ? fromJSON(JsonFile.readFileSync(config.generic.saveFile)) : {}; //read data from file, or generate new one if file doesn't exist + + //create our writeFile function that will allow other functions to save data to json without needing access to the full guildsData or config objects + //then set an interval to automatically save data to file + writeFile = () => JsonFile.writeFile(config.generic.saveFile, guildsData, err => { if (err) DiscordUtil.dateError("Error writing file", err); }); + setInterval(() => writeFile(), config.generic.saveIntervalSec * 1000); + + //handle messages + client.on("message", message => { + if (message.author.id !== client.user.id) { //check the bot isn't triggering itself + + //check whether we need to use DM or text channel handling + if (message.channel.type === "dm") + HandleMessage.dm(client, config, message); + else if (message.channel.type === "text" && message.member) + HandleMessage.text(client, config, message, guildsData); + } + }); +}; + +const HandleMessage = { + dm: (client, config, message) => { + message.reply(Util.format(config.generic.defaultDMResponse, config.generic.website, config.generic.discordInvite)); + }, + text: (client, config, message, guildsData) => { + const isCommand = message.content.startsWith(message.guild.me.toString()); + let guildData = guildsData[message.guild.id]; + + if (!guildData) + guildData = guildsData[message.guild.id] = new GuildData({ id: message.guild.id }); + + if (isCommand) { + const userIsAdmin = message.member.permissions.has("ADMINISTRATOR"); + const botName = "@" + (message.guild.me.nickname || client.user.username); + + const split = message.content.toLowerCase().split(/\ +/); //split the message at whitespace + const command = split[1]; //extract the command used + const commandObj = config.commands[Object.keys(config.commands).find(x => config.commands[x].command.toLowerCase() === command)]; //find the matching command object + + if (!commandObj || (!commandObj.admin && !userIsAdmin)) + return; + + const params = split.slice(2, split.length); //extract the parameters passed for the command + const expectedParamCount = commandObj.syntax.split(/\ +/).length - 1; //calculate the number of expected command params + + let finalisedParams; + if (params.length > expectedParamCount) //if we have more params than needed + finalisedParams = params.slice(0, expectedParamCount - 1).concat([params.slice(expectedParamCount - 1, params.length).join(" ")]); + else //else we either have exactly the right amount, or not enough + finalisedParams = params; + + //find which command was used and handle it + switch (command) { + case config.commands.version.command: + message.reply(`${PackageJSON.name} v${PackageJSON.version}`); + break; + case config.commands.help.command: + message.channel.send(createHelpEmbed(botName, config, userIsAdmin)); + break; + default: + if (finalisedParams.length >= expectedParamCount) + Bot.onCommand(commandObj, config.commands, finalisedParams, guildData, message) + .then(msg => { + message.reply(msg); + writeFile(); + }) + .catch(err => { + message.reply(err); + DiscordUtil.dateError(err); + }); + else + message.reply(`Incorrect syntax!\n**Expected:** *${botName} ${commandObj.syntax}*\n**Need help?** *${botName} ${config.commands.help.command}*`); + break; + } + } + else + Bot.onNonCommandMsg(message, guildData); + } +}; + +function fromJSON(json) { + const guildsData = Object.keys(json); + guildsData.forEach(guildID => { json[guildID] = new GuildData(json[guildID]); }); + return json; +} + +function createHelpEmbed(name, config, userIsAdmin) { + const commandsArr = Object.keys(config.commands).map(x => config.commands[x]).filter(x => userIsAdmin || !x.admin); + + const embed = new Discord.RichEmbed().setTitle("__Help__"); + + commandsArr.forEach(command => { + embed.addField(command.command, `${command.description}\n**Usage:** *${name} ${command.syntax}*${userIsAdmin && command.admin ? "\n***Admin only***" : ""}`); + }); + + embed.addField("__Need more help?__", `[Visit my website](${config.generic.website}) or [Join my Discord](${config.generic.discordInvite})`, true); + + return { embed }; +} \ No newline at end of file diff --git a/app/models/guild-data.js b/app/models/guild-data.js new file mode 100644 index 0000000..2060d58 --- /dev/null +++ b/app/models/guild-data.js @@ -0,0 +1,7 @@ +const DiscordUtil = require("discordjs-util"); + +module.exports = class GuildData { + constructor({ id }) { + this.id = id; + } +}; \ No newline at end of file diff --git a/bootstrap.js b/bootstrap.js new file mode 100644 index 0000000..a78913e --- /dev/null +++ b/bootstrap.js @@ -0,0 +1,20 @@ +const Discord = require("discord.js"); +const DiscordUtil = require("discordjs-util"); + +const client = new Discord.Client(); + +process.on("uncaughtException", (err) => { + DiscordUtil.dateError("Uncaught exception!", err); +}); + +client.login(require("./token.json").token); + +client.on("ready", () => { + DiscordUtil.dateLog("Registered bot " + client.user.username); + require("./app/index.js")(client); + client.user.setPresence({ game: { name: "benji7425.github.io", type: 0 } }); +}); + +client.on("disconnect", eventData => { + DiscordUtil.dateError("Bot was disconnected!", eventData.code, eventData.reason); +}); \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 0000000..329536c --- /dev/null +++ b/package.json @@ -0,0 +1,12 @@ +{ + "version": "0.1.0", + "main": "app/index.js", + "scripts": { + "start": "node bootstrap.js" + }, + "dependencies": { + "discord.js": "11.1.0", + "discordjs-util": "git+https://github.com/benji7425/discordjs-util.git", + "jsonfile": "3.0.1" + } +} \ No newline at end of file