Browse Source
git subrepo pull discord-bot-core
git subrepo pull discord-bot-core
subrepo: subdir: "discord-bot-core" merged: "180d069" upstream: origin: "git@github.com:benji7425/discord-bot-core.git" branch: "master" commit: "180d069" git-subrepo: version: "0.3.1" origin: "???" commit: "???"master

8 changed files with 133 additions and 226 deletions
-
4discord-bot-core/.gitrepo
-
6discord-bot-core/BaseGuildData.js
-
35discord-bot-core/HandleMessage.js
-
159discord-bot-core/client.js
-
12discord-bot-core/command.js
-
11discord-bot-core/index.js
-
20discord-bot-core/internal-config.json
-
112discord-bot-core/message-handler.js
@ -0,0 +1,6 @@ |
|||
module.exports = class BaseGuildData { |
|||
/**@param param */ |
|||
constructor(id) { |
|||
this.id = id; |
|||
} |
|||
}; |
@ -0,0 +1,35 @@ |
|||
// @ts-ignore
|
|||
const ParentPackageJSON = require("../package.json"); |
|||
|
|||
/**@param param*/ |
|||
function handleMessage(message, commands, guildData) { |
|||
if (!message.content.startsWith(message.guild.me.toString())) //criteria for a command is the bot being tagged
|
|||
return; |
|||
|
|||
const botName = "@" + (message.guild.me.nickname || message.guild.me.user.username), |
|||
isMemberAdmin = message.member.permissions.has("ADMINISTRATOR"), |
|||
split = message.content.split(/ +/), |
|||
params = split.slice(2, split.length), |
|||
command = commands[Object.keys(commands).find(x => commands[x].name.toLowerCase() === (split[1] || "").toLowerCase())]; |
|||
|
|||
if (!command) |
|||
handleInternalCommand(message, params); |
|||
else if (params.length < command.expectedParamCount) |
|||
message.reply(`Incorrect syntax!\n**Expected:** *${botName} ${command.syntax}*\n**Need help?** *${botName} help*`); |
|||
else if(isMemberAdmin || !command.admin) |
|||
command.invoke({ message, params, guildData }); |
|||
} |
|||
|
|||
/**@param param*/ |
|||
function handleInternalCommand(message, split) { |
|||
if (split[1].toLowerCase() === "version") |
|||
message.reply(`${ParentPackageJSON.name} v${ParentPackageJSON.version}`); |
|||
else if(split[1].toLowerCase() === "help") |
|||
message.reply(createHelpEmbed()); |
|||
} |
|||
|
|||
function createHelpEmbed() { |
|||
return "not yet implemented"; |
|||
} |
|||
|
|||
module.exports = handleMessage; |
@ -1,111 +1,84 @@ |
|||
//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 { |
|||
const FileSystem = require("fs"); |
|||
const Discord = require("discord.js"); |
|||
const JsonFile = require("jsonfile"); |
|||
const RequireAll = require("require-all"); |
|||
const CoreUtil = require("./util.js"); |
|||
const HandleMessage = require("./HandleMessage.js"); |
|||
// @ts-ignore
|
|||
const InternalConfig = require("./internal-config.json"); |
|||
|
|||
module.exports = class Client extends Discord.Client { |
|||
/** |
|||
* @param {string} token |
|||
* @param {string} dataFile |
|||
* @param {object} guildDataModel |
|||
* @param {object[]} commands |
|||
* @param {*} token |
|||
* @param {*} dataFile |
|||
* @param {*} commandsDir |
|||
* @param {*} guildDataModel |
|||
*/ |
|||
constructor(token, dataFile, commands, implementations, guildDataModel) { |
|||
this.actual = new Discord.Client(); |
|||
constructor(token, dataFile, commandsDir, guildDataModel) { |
|||
super(); |
|||
|
|||
this.token = token; |
|||
this._token = token; |
|||
this.dataFile = dataFile; |
|||
this.commands = commands; |
|||
this.implementations = implementations; |
|||
this.commandsDir = commandsDir; |
|||
this.guildDataModel = guildDataModel; |
|||
this.guildsData = FileSystem.existsSync(this.dataFile) ? |
|||
fromJSON(JsonFile.readFileSync(this.dataFile), this.guildDataModel) : {}; |
|||
|
|||
process.on("uncaughtException", err => onUncaughtException(this, err)); |
|||
} |
|||
this.commands = RequireAll(this.commandsDir); |
|||
this.guildsData = FileSystem.existsSync(this.dataFile) ? this.fromJSON(JsonFile.readFileSync(this.dataFile)) : {}; |
|||
|
|||
writeFile() { |
|||
JsonFile.writeFile( |
|||
this.dataFile, |
|||
this.guildsData, |
|||
err => { |
|||
if (err) DiscordUtil.dateError("Error writing file", err); |
|||
}); |
|||
this.on("ready", this.onReady); |
|||
this.on("message", this.onMessage); |
|||
this.on("debug", this.onDebug); |
|||
process.on("uncaughtException", err => this.onUnhandledException(this, 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 => { |
|||
if (err) { |
|||
message.reply(err); |
|||
DiscordUtil.dateError(`Command error in guild ${message.guild.name}\n`, err.message || err); |
|||
} |
|||
}) |
|||
.then(() => this.writeFile()); |
|||
}); |
|||
this.beforeLogin(); |
|||
this.login(this._token); |
|||
} |
|||
|
|||
this.actual.login(this.token); |
|||
beforeLogin() { |
|||
setInterval(() => this.writeFile(), InternalConfig.saveIntervalSec * 1000); |
|||
this.emit("beforeLogin"); |
|||
} |
|||
} |
|||
|
|||
/** |
|||
* @param {*} coreClient |
|||
*/ |
|||
function onReady(coreClient) { |
|||
coreClient.actual.user.setGame("benji7425.github.io"); |
|||
DiscordUtil.dateLog("Registered bot " + coreClient.actual.user.username); |
|||
onReady() { |
|||
this.user.setGame(InternalConfig.website.replace("http://", "")); |
|||
CoreUtil.dateLog(`Registered bot ${this.user.username}`); |
|||
} |
|||
|
|||
setInterval(() => coreClient.writeFile(), Config.saveIntervalSec * 1000); |
|||
onMessage(message) { |
|||
if (message.channel.type === "text" && message.member) |
|||
HandleMessage(message, this.commands, this.guildsData[message.guild.id] || new this.guildDataModel(message.guild.id)); |
|||
} |
|||
|
|||
if (coreClient.implementations.onReady) |
|||
coreClient.implementations.onReady(coreClient) |
|||
.then(() => coreClient.writeFile()) |
|||
.catch(err => DiscordUtil.dateError(err)); |
|||
} |
|||
onDebug(info) { |
|||
if (!InternalConfig.debugIgnores.some(x => info.startsWith(x))) |
|||
CoreUtil.dateLog(info); |
|||
} |
|||
|
|||
/** |
|||
* @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(); |
|||
}); |
|||
} |
|||
onUnhandledException(client, err) { |
|||
CoreUtil.dateError(err.message || err); |
|||
CoreUtil.dateLog("Destroying existing client..."); |
|||
client.destroy().then(() => { |
|||
CoreUtil.dateLog("Client destroyed, recreating..."); |
|||
client.login(client._token); |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 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; |
|||
} |
|||
writeFile() { |
|||
JsonFile.writeFile( |
|||
this.dataFile, |
|||
this.guildsData, |
|||
err => { if (err) CoreUtil.dateError(`Error writing data file! ${err.message || err}`); }); |
|||
} |
|||
|
|||
module.exports = CoreClient; |
|||
/** |
|||
* @param {*} json |
|||
* @param {*} guildDataModel |
|||
*/ |
|||
fromJSON(json) { |
|||
const guildsData = Object.keys(json); |
|||
guildsData.forEach(guildID => { json[guildID] = new this.guildDataModel(json[guildID]); }); |
|||
return json; |
|||
} |
|||
}; |
@ -0,0 +1,12 @@ |
|||
module.exports = class Command { |
|||
/**@param param */ |
|||
constructor({ name, description, syntax, admin, invoke }) { |
|||
this.name = name; |
|||
this.description = description; |
|||
this.syntax = syntax; |
|||
this.admin = admin; |
|||
this.invoke = invoke; |
|||
|
|||
this.expectedParamCount = this.syntax.split(/ +/).length - 1; |
|||
} |
|||
}; |
@ -1,10 +1,13 @@ |
|||
const Config = require("./internal-config.json"); |
|||
// @ts-ignore
|
|||
const InternalConfig = require("./internal-config.json"); |
|||
|
|||
module.exports = { |
|||
Client: require("./client.js"), |
|||
util: require("./util.js"), |
|||
BaseGuildData: require("./BaseGuildData.js"), |
|||
Command: require("./Command.js"), |
|||
util: require("./Util.js"), |
|||
details: { |
|||
website: Config.website, |
|||
discordInvite: Config.discordInvite |
|||
website: InternalConfig.website, |
|||
discordInvite: InternalConfig.discordInvite |
|||
} |
|||
}; |
@ -1,21 +1,11 @@ |
|||
{ |
|||
"saveFile": "./%s-data.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 |
|||
} |
|||
} |
|||
"debugIgnores":[ |
|||
"[ws] [connection] Sending a heartbeat", |
|||
"[ws] [connection] Heartbeat acknowledged", |
|||
"Authenticated using token" |
|||
] |
|||
} |
@ -1,112 +0,0 @@ |
|||
//node imports
|
|||
const Util = require("util"); |
|||
|
|||
//external lib imports
|
|||
const Discord = require("discord.js"); |
|||
|
|||
//component imports
|
|||
const Config = require("./internal-config.json"); //generic lib configuration
|
|||
const ParentPackageJSON = require("../package.json"); //used to provide some info about the bot
|
|||
|
|||
/** |
|||
* 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)); |
|||
} |
|||
|
|||
/** |
|||
* |
|||
* @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 coreClient.guildDataModel({ id: message.guild.id }); |
|||
|
|||
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 || 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 reject(); |
|||
|
|||
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}*`); |
|||
} |
|||
}); |
|||
} |
|||
|
|||
/** |
|||
* 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]; |
|||
const commandProp = Object.keys(commands).find(x => commands[x].command.toLowerCase() === commandStr); |
|||
const command = commands[commandProp]; |
|||
|
|||
if (!command || (command.admin && !userIsAdmin)) |
|||
return; |
|||
|
|||
const params = splitMessage.slice(2, splitMessage.length); |
|||
const expectedParamCount = command.syntax.split(/ +/).length - 1; |
|||
|
|||
let finalisedParams; |
|||
if (params.length > expectedParamCount) |
|||
finalisedParams = params.slice(0, expectedParamCount - 1).concat([params.slice(expectedParamCount - 1, params.length).join(" ")]); |
|||
else |
|||
finalisedParams = params; |
|||
|
|||
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); |
|||
|
|||
const embed = new Discord.RichEmbed().setTitle(`__Help__ for ${(ParentPackageJSON.name + "").replace("discord-bot-", "")}`); |
|||
|
|||
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.website}) or [Join my Discord](${Config.discordInvite})`, true); |
|||
|
|||
return { embed }; |
|||
} |
|||
|
|||
module.exports = { |
|||
handleDirectMessage, |
|||
handleTextMessage |
|||
}; |
Write
Preview
Loading…
Cancel
Save
issues.context.reference_issue