Browse Source

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
benji7425 4 years ago
parent
commit
d6bc7b631b
  1. 4
      discord-bot-core/.gitrepo
  2. 6
      discord-bot-core/BaseGuildData.js
  3. 35
      discord-bot-core/HandleMessage.js
  4. 159
      discord-bot-core/client.js
  5. 12
      discord-bot-core/command.js
  6. 11
      discord-bot-core/index.js
  7. 20
      discord-bot-core/internal-config.json
  8. 112
      discord-bot-core/message-handler.js

4
discord-bot-core/.gitrepo

@ -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

6
discord-bot-core/BaseGuildData.js

@ -0,0 +1,6 @@
module.exports = class BaseGuildData {
/**@param param */
constructor(id) {
this.id = id;
}
};

35
discord-bot-core/HandleMessage.js

@ -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;

159
discord-bot-core/client.js

@ -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;
}
};

12
discord-bot-core/command.js

@ -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;
}
};

11
discord-bot-core/index.js

@ -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
}
};

20
discord-bot-core/internal-config.json

@ -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"
]
}

112
discord-bot-core/message-handler.js

@ -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…
Cancel
Save