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: "???"
This commit is contained in:
parent
64ea3d596f
commit
d6bc7b631b
|
@ -6,7 +6,7 @@
|
|||
[subrepo]
|
||||
remote = git@github.com:benji7425/discord-bot-core.git
|
||||
branch = master
|
||||
commit = 81a8c678cad14ed8162dd52475bff7ed02b6bc4e
|
||||
parent = 052a0bf7e6a782290e50aa54f504be670dff776d
|
||||
commit = 180d069b012d38d6d3c539df5a04475683139b01
|
||||
parent = 176b7b8ad9ca41d1b9f7a15869ed5ed95ccad25d
|
||||
method = merge
|
||||
cmdver = 0.3.1
|
||||
|
|
|
@ -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
|
||||
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");
|
||||
|
||||
//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 {
|
||||
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)) : {};
|
||||
|
||||
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.beforeLogin();
|
||||
this.login(this._token);
|
||||
}
|
||||
|
||||
beforeLogin() {
|
||||
setInterval(() => this.writeFile(), InternalConfig.saveIntervalSec * 1000);
|
||||
this.emit("beforeLogin");
|
||||
}
|
||||
|
||||
onReady() {
|
||||
this.user.setGame(InternalConfig.website.replace("http://", ""));
|
||||
CoreUtil.dateLog(`Registered bot ${this.user.username}`);
|
||||
}
|
||||
|
||||
onMessage(message) {
|
||||
if (message.channel.type === "text" && message.member)
|
||||
HandleMessage(message, this.commands, this.guildsData[message.guild.id] || new this.guildDataModel(message.guild.id));
|
||||
}
|
||||
|
||||
onDebug(info) {
|
||||
if (!InternalConfig.debugIgnores.some(x => info.startsWith(x)))
|
||||
CoreUtil.dateLog(info);
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
}
|
||||
|
||||
writeFile() {
|
||||
JsonFile.writeFile(
|
||||
this.dataFile,
|
||||
this.guildsData,
|
||||
err => {
|
||||
if (err) DiscordUtil.dateError("Error writing file", err);
|
||||
});
|
||||
err => { if (err) CoreUtil.dateError(`Error writing data file! ${err.message || 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.actual.login(this.token);
|
||||
/**
|
||||
* @param {*} json
|
||||
* @param {*} guildDataModel
|
||||
*/
|
||||
fromJSON(json) {
|
||||
const guildsData = Object.keys(json);
|
||||
guildsData.forEach(guildID => { json[guildID] = new this.guildDataModel(json[guildID]); });
|
||||
return json;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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;
|
||||
};
|
|
@ -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
|
||||
};
|
Loading…
Reference in New Issue