From 841453204464d870bf1c09f1b2135dce8de0cd6c Mon Sep 17 00:00:00 2001 From: benji7425 Date: Tue, 19 Sep 2017 23:26:30 +0100 Subject: [PATCH] git subrepo commit (merge) discord-bot-core subrepo: subdir: "discord-bot-core" merged: "77cb9dd" upstream: origin: "git@github.com:benji7425/discord-bot-core.git" branch: "master" commit: "8e6d6ce" git-subrepo: version: "0.3.1" origin: "???" commit: "???" --- discord-bot-core/.gitrepo | 2 +- discord-bot-core/bootstrapper.js | 72 ------------ discord-bot-core/client.js | 109 ++++++++++++++++++ discord-bot-core/index.js | 4 +- .../{config.json => internal-config.json} | 0 discord-bot-core/message-handler.js | 95 ++++++++------- discord-bot-core/package.json | 1 - 7 files changed, 166 insertions(+), 117 deletions(-) delete mode 100644 discord-bot-core/bootstrapper.js create mode 100644 discord-bot-core/client.js rename discord-bot-core/{config.json => internal-config.json} (100%) diff --git a/discord-bot-core/.gitrepo b/discord-bot-core/.gitrepo index 815ac70..0b1b881 100644 --- a/discord-bot-core/.gitrepo +++ b/discord-bot-core/.gitrepo @@ -6,7 +6,7 @@ [subrepo] remote = git@github.com:benji7425/discord-bot-core.git branch = master - commit = c4f07d0a8d4585083c3f167c1dce55cbeeda05d2 + commit = 8e6d6ce99e581f5ac5fb899112ef33bcf3e16a34 parent = 341964a90ddca9afc684fd9331fc4177b8b68c04 method = merge cmdver = 0.3.1 diff --git a/discord-bot-core/bootstrapper.js b/discord-bot-core/bootstrapper.js deleted file mode 100644 index 0dae333..0000000 --- a/discord-bot-core/bootstrapper.js +++ /dev/null @@ -1,72 +0,0 @@ -//node imports -const FileSystem = require("fs"); //manage files -const Util = require("util"); - -//external lib imports -const Discord = require("discord.js"); -const JsonFile = require("jsonfile"); //save/load data to/from json - -const Config = require("./config.json"); -const ParentPackageJSON = require("../package.json"); //used to provide some info about the bot -const DiscordUtil = require("./util.js"); //some discordjs helper functions of mine -const MessageHandler = require("./message-handler.js"); - -function bootstrap(token, component, guildDataModel, commands) { - process.on("uncaughtException", (err) => { - DiscordUtil.dateError("Uncaught exception!", err); - }); - - const client = new Discord.Client(); - - client.on("ready", () => { - onReady(client, component, guildDataModel, commands); - - client.user.setGame("benji7425.github.io"); - DiscordUtil.dateLog("Registered bot " + client.user.username); - }); - - client.on("disconnect", eventData => { - DiscordUtil.dateError("Bot was disconnected!", eventData.code, eventData.reason); - }); - - client.login(token); -} - -function onReady(client, component, guildDataModel, commands) { - const saveFile = Util.format(Config.saveFile, ParentPackageJSON.name + ""); - - const guildsData = - FileSystem.existsSync(saveFile) ? - fromJSON(JsonFile.readFileSync(saveFile), guildDataModel) : {}; - - const writeFile = () => - JsonFile.writeFile( - saveFile, - guildsData, - err => { - if (err) DiscordUtil.dateError("Error writing file", err); - }); - - setInterval(() => writeFile(), Config.saveIntervalSec * 1000); - - client.on("message", message => { - if (message.author.id !== client.user.id) { - if (message.channel.type === "dm") - MessageHandler.handleDirectMessage({ client, message }); - else if (message.channel.type === "text" && message.member) - MessageHandler.handleTextMessage({ client, commands, message, guildDataModel, guildsData, component, writeFile }); - } - }); - - component.onReady(client, guildsData) - .then(() => writeFile()) - .catch(err => DiscordUtil.dateError(err)); -} - -function fromJSON(json, guildDataModel) { - const guildsData = Object.keys(json); - guildsData.forEach(guildID => { json[guildID] = new guildDataModel(json[guildID]); }); - return json; -} - -module.exports = bootstrap; \ No newline at end of file diff --git a/discord-bot-core/client.js b/discord-bot-core/client.js new file mode 100644 index 0000000..fbe284f --- /dev/null +++ b/discord-bot-core/client.js @@ -0,0 +1,109 @@ +//node imports +const FileSystem = require("fs"); //checking if files exist + +//external lib imports +const Discord = require("discord.js"); //discord interaction +const JsonFile = require("jsonfile"); //saving to/reading from json + +//component imports +const DiscordUtil = require("./util.js"); //some helper methods +const MessageHandler = require("./message-handler.js"); //message handling +const Config = require("./internal-config.json"); //some configuration values + +class CoreClient { + /** + * @param {string} token + * @param {string} dataFile + * @param {object} guildDataModel + * @param {object[]} commands + */ + constructor(token, dataFile, commands, implementations, guildDataModel) { + this.actual = new Discord.Client(); + + this.token = token; + this.dataFile = dataFile; + this.commands = commands; + this.implementations = implementations; + this.guildDataModel = guildDataModel; + this.guildsData = FileSystem.existsSync(this.dataFile) ? + fromJSON(JsonFile.readFileSync(this.dataFile), this.guildDataModel) : {}; + + process.on("uncaughtException", err => onUncaughtException(this, err)); + } + + writeFile() { + JsonFile.writeFile( + this.dataFile, + this.guildsData, + err => { + if (err) DiscordUtil.dateError("Error writing file", err); + }); + } + + bootstrap() { + this.actual.on("ready", () => onReady(this)); + + this.actual.on("disconnect", eventData => DiscordUtil.dateError("Disconnect!", eventData.code, eventData.reason)); + + this.actual.on("message", message => { + if (message.author.id === this.actual.user.id) + return; + if (message.channel.type === "dm") + MessageHandler.handleDirectMessage(this, message); + else if (message.channel.type === "text" && message.member) + MessageHandler.handleTextMessage(this, message, this.guildsData) + .then(msg => { + if (msg) message.reply(msg); + }) + .catch(err => { + message.reply(err); + DiscordUtil.dateError(`Command error in guild ${message.guild.name}\n`, err.message || err); + }) + .then(() => this.writeFile()); + }); + + this.actual.login(this.token); + } +} + +/** + * @param {*} coreClient + */ +function onReady(coreClient) { + coreClient.actual.user.setGame("benji7425.github.io"); + DiscordUtil.dateLog("Registered bot " + coreClient.actual.user.username); + + setInterval(() => coreClient.writeFile(), Config.saveIntervalSec * 1000); + + if (coreClient.implementations.onReady) + coreClient.implementations.onReady(coreClient) + .then(() => coreClient.writeFile()) + .catch(err => DiscordUtil.dateError(err)); +} + +/** + * @param {*} coreClient + * @param {*} err + */ +function onUncaughtException(coreClient, err) { + DiscordUtil.dateError(err.message || err); + DiscordUtil.dateLog("Destroying existing client..."); + coreClient.actual.destroy().then(() => { + DiscordUtil.dateLog("Client destroyed, recreating..."); + coreClient.actual = new Discord.Client(); + coreClient.bootstrap(); + }); +} + +/** + * Convert json from file to a usable format + * @param {object} json json from file + * @param {*} guildDataModel + */ +function fromJSON(json, guildDataModel) { + const guildsData = Object.keys(json); + guildsData.forEach(guildID => { json[guildID] = new guildDataModel(json[guildID]); }); + return json; +} + +module.exports = CoreClient; \ No newline at end of file diff --git a/discord-bot-core/index.js b/discord-bot-core/index.js index 1080d1e..826b29b 100644 --- a/discord-bot-core/index.js +++ b/discord-bot-core/index.js @@ -1,7 +1,7 @@ -const Config = require("./config.json"); +const Config = require("./internal-config.json"); module.exports = { - bootstrap: require("./bootstrapper.js"), + Client: require("./client.js"), util: require("./util.js"), details: { website: Config.website, diff --git a/discord-bot-core/config.json b/discord-bot-core/internal-config.json similarity index 100% rename from discord-bot-core/config.json rename to discord-bot-core/internal-config.json diff --git a/discord-bot-core/message-handler.js b/discord-bot-core/message-handler.js index b488fd1..7c91670 100644 --- a/discord-bot-core/message-handler.js +++ b/discord-bot-core/message-handler.js @@ -4,60 +4,67 @@ const Util = require("util"); //external lib imports const Discord = require("discord.js"); -//lib components -const Config = require("./config.json"); //generic lib configuration -const DiscordUtil = require("./util.js"); //some discordjs helper functions of mine +//component imports +const Config = require("./internal-config.json"); //generic lib configuration const ParentPackageJSON = require("../package.json"); //used to provide some info about the bot -function handleDirectMessage(client, message) { - message.reply(Util.format(Config.generic.defaultDMResponse, Config.generic.website, Config.generic.discordInvite)); +/** + * Handle a direct message to the bot + * @param {*} coreClient Core.Client + * @param {*} message Discord.Message + */ +function handleDirectMessage(coreClient, message) { + message.reply(Util.format(Config.defaultDMResponse, Config.website, Config.discordInvite)); } -function handleTextMessage({ client, commands, message, guildDataModel, guildsData, component, writeFile }) { - const isCommand = message.content.startsWith(message.guild.me.toString()); - let guildData = guildsData[message.guild.id]; +/** + * + * @param {*} coreClient Core.Client + * @param {*} message Discord.Message + * @param {*[]} guildsData GuildData[] + */ +function handleTextMessage(coreClient, message, guildsData) { + return new Promise((resolve, reject) => { + const isCommand = message.content.startsWith(message.guild.me.toString()); + let guildData = guildsData[message.guild.id]; - if (!guildData) - guildData = guildsData[message.guild.id] = new guildDataModel({ id: message.guild.id }); + if (!guildData) + guildData = guildsData[message.guild.id] = new coreClient.guildDataModel({ id: message.guild.id }); - if (isCommand) { - Object.assign(commands, Config.commands); + if (!isCommand) + return coreClient.implementations.onTextMessage(message, guildData).then(msg => resolve(msg)); + + Object.assign(coreClient.commands, Config.commands); const userIsAdmin = message.member.permissions.has("ADMINISTRATOR"); - const botName = "@" + (message.guild.me.nickname || client.user.username); - const { command, commandProp, params, expectedParamCount } = getCommandDetails(message, commands, userIsAdmin) || { command: null, commandProp: null, params: null, expectedParamCount: null }; - const invoke = component[commandProp]; + const botName = "@" + (message.guild.me.nickname || coreClient.actual.user.username); + const { command, commandProp, params, expectedParamCount } = getCommandDetails(message, coreClient.commands, userIsAdmin) || { command: null, commandProp: null, params: null, expectedParamCount: null }; + const invoke = coreClient.implementations[commandProp]; if (!command || !params || isNaN(expectedParamCount)) - return; + return reject(`'${message.content.split(" ")[1]}' is not a recognised command`); - switch (command) { - case Config.commands.version: - message.reply(`${ParentPackageJSON.name} v${ParentPackageJSON.version}`); - break; - case Config.commands.help: - message.channel.send(createHelpEmbed(botName, commands, userIsAdmin)); - break; - default: - if (invoke && params.length >= expectedParamCount) { - invoke({ params, guildData, botName, message, client }) - .then(msg => { - message.reply(msg); - writeFile(); - }) - .catch(err => { - message.reply(err); - DiscordUtil.dateError(err); - }); - } - else - message.reply(`Incorrect syntax!\n**Expected:** *${botName} ${command.syntax}*\n**Need help?** *${botName} ${commands.help.command}*`); - break; + if (command === Config.commands.version) + resolve(`${ParentPackageJSON.name} v${ParentPackageJSON.version}`); + else if (command === Config.commands.help) + message.channel.send(createHelpEmbed(botName, coreClient.commands, userIsAdmin)); + else { + if (invoke && params.length >= expectedParamCount) + invoke({ command, params: params, guildData, botName, message, coreClient }) + .then(msg => + resolve(msg)) + .catch(err => reject(err)); + else + reject(`Incorrect syntax!\n**Expected:** *${botName} ${command.syntax}*\n**Need help?** *${botName} ${coreClient.commands.help.command}*`); } - } - else - component.onTextMessage(message, guildData); + }); } +/** + * Determine details about a command invoked via a message + * @param {*} message Discord.Message + * @param {*[]} commands commands array (probably from commands.json) + * @param {boolean} userIsAdmin whether the user is an admin + */ function getCommandDetails(message, commands, userIsAdmin) { const splitMessage = message.content.toLowerCase().split(/ +/); const commandStr = splitMessage[1]; @@ -79,6 +86,12 @@ function getCommandDetails(message, commands, userIsAdmin) { return { command, commandProp, params: finalisedParams, expectedParamCount }; } +/** + * Create a help embed for available commands + * @param {string} name name of the bot + * @param {*[]} commands commands array + * @param {boolean} userIsAdmin whether the user is admin + */ function createHelpEmbed(name, commands, userIsAdmin) { const commandsArr = Object.keys(commands).map(x => commands[x]).filter(x => userIsAdmin || !x.admin); diff --git a/discord-bot-core/package.json b/discord-bot-core/package.json index e360982..3b81dca 100644 --- a/discord-bot-core/package.json +++ b/discord-bot-core/package.json @@ -3,7 +3,6 @@ "main": "index.js", "dependencies": { "discord.js": "11.2.0", - "discordjs-util": "git+https://github.com/benji7425/discordjs-util.git", "jsonfile": "3.0.1", "parent-package-json": "2.0.1", "simple-file-writer": "2.0.0"