Update naming and formatting for core .js files
parent
4a8c90bf7d
commit
c4d1247be3
@ -0,0 +1,35 @@
|
||||
{
|
||||
"env": {
|
||||
"browser": true,
|
||||
"node": true,
|
||||
"commonjs": true,
|
||||
"es6": true
|
||||
},
|
||||
"extends": "eslint:recommended",
|
||||
"parserOptions": {
|
||||
"sourceType": "module"
|
||||
},
|
||||
"rules": {
|
||||
"indent": [
|
||||
"warn",
|
||||
4,
|
||||
{
|
||||
"SwitchCase": 1
|
||||
}
|
||||
],
|
||||
"quotes": [
|
||||
"warn",
|
||||
"double"
|
||||
],
|
||||
"semi": [
|
||||
"error",
|
||||
"always"
|
||||
],
|
||||
"no-undef": "error",
|
||||
"no-unused-vars": "warn",
|
||||
"eqeqeq": [
|
||||
"error",
|
||||
"always"
|
||||
]
|
||||
}
|
||||
}
|
@ -1,102 +0,0 @@
|
||||
const CoreUtil = require("./Util.js");
|
||||
const Camo = require("camo");
|
||||
const CronJob = require("cron").CronJob;
|
||||
const Discord = require("discord.js");
|
||||
const HandleGuildMessage = require("./HandleGuildMessage");
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
const RequireAll = require("require-all");
|
||||
const Util = require("./Util.js");
|
||||
|
||||
let neDB;
|
||||
|
||||
module.exports = class Client extends Discord.Client {
|
||||
/**
|
||||
* Construct a new Discord.Client with some added functionality
|
||||
* @param {string} token bot token
|
||||
* @param {string} commandsDir location of dir containing commands .js files
|
||||
* @param {*} guildDataModel GuildData model to be used for app; must extend BaseGuildData
|
||||
*/
|
||||
constructor(token, commandsDir, guildDataModel) {
|
||||
super({
|
||||
messageCacheMaxSize: 16,
|
||||
disabledEvents: InternalConfig.disabledEvents
|
||||
});
|
||||
|
||||
this._token = token;
|
||||
this.commandsDir = commandsDir;
|
||||
this.guildDataModel = guildDataModel;
|
||||
|
||||
this.commands = RequireAll(this.commandsDir);
|
||||
|
||||
this.on("ready", this._onReady);
|
||||
this.on("message", this._onMessage);
|
||||
this.on("debug", this._onDebug);
|
||||
this.on("guildCreate", this._onGuildCreate);
|
||||
this.on("guildDelete", this._onGuildDelete);
|
||||
process.on("uncaughtException", err => this._onUnhandledException(this, err));
|
||||
}
|
||||
|
||||
_onReady() {
|
||||
this.user.setGame(InternalConfig.website.replace(/^https?:\/\//, ""));
|
||||
CoreUtil.dateLog(`Registered bot ${this.user.username}`);
|
||||
|
||||
this.removeDeletedGuilds();
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
if (message.channel.type === "text" && message.member)
|
||||
HandleGuildMessage(this, message, this.commands);
|
||||
}
|
||||
|
||||
_onDebug(info) {
|
||||
info = info.replace(/Authenticated using token [^ ]+/, "Authenticated using token [redacted]");
|
||||
if (!InternalConfig.debugIgnores.some(x => info.startsWith(x)))
|
||||
CoreUtil.dateDebug(info);
|
||||
}
|
||||
|
||||
_onGuildCreate(guild) {
|
||||
CoreUtil.dateLog(`Added to guild ${guild.name}`);
|
||||
}
|
||||
|
||||
_onGuildDelete(guild) {
|
||||
this.guildDataModel.findOneAndDelete({ guildID: guild.id });
|
||||
|
||||
CoreUtil.dateLog(`Removed from guild ${guild.name}, removing data for this guild`);
|
||||
}
|
||||
|
||||
_onUnhandledException(client, err) {
|
||||
CoreUtil.dateError("Unhandled exception!\n", err);
|
||||
CoreUtil.dateLog("Destroying existing client...");
|
||||
client.destroy().then(() => {
|
||||
CoreUtil.dateLog("Client destroyed, recreating...");
|
||||
setTimeout(() => client.login(client._token), InternalConfig.reconnectTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
Camo.connect("nedb://guilds-data").then(db => {
|
||||
neDB = db;
|
||||
new CronJob(InternalConfig.dbCompactionSchedule, compactCollections, null, true);
|
||||
|
||||
this.emit("beforeLogin");
|
||||
this.login(this._token);
|
||||
});
|
||||
}
|
||||
|
||||
removeDeletedGuilds() {
|
||||
this.guildDataModel.find().then(guildDatas => {
|
||||
for (let guildData of guildDatas)
|
||||
if (!this.guilds.get(guildData.guildID))
|
||||
guildData.delete();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function compactCollections() {
|
||||
/*I realise it is a bit of a cheat to just access _collections in this manner, but in the absence of
|
||||
camo actually having any kind of solution for this it's the easiest method I could come up with.
|
||||
Maybe at some point in future I should fork camo and add this feature. The compaction function is NeDB only
|
||||
and camo is designed to work with both NeDB and MongoDB, which is presumably why it doesn't alraedy exist */
|
||||
for (let collectionName of Object.keys(neDB._collections))
|
||||
neDB._collections[collectionName].persistence.compactDatafile();
|
||||
}
|
@ -1,15 +0,0 @@
|
||||
module.exports = class Command {
|
||||
constructor({ name, description, syntax, admin, invoke }) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.syntax = syntax;
|
||||
this.admin = admin;
|
||||
this.invoke = invoke;
|
||||
|
||||
const params = this.syntax.split(/ +/);
|
||||
const optionalParams = params.filter(x => x.match(/^\[.+\]$/));
|
||||
|
||||
this.maxParamCount = params.length - 1;
|
||||
this.expectedParamCount = this.maxParamCount - optionalParams.length;
|
||||
}
|
||||
};
|
@ -1,53 +0,0 @@
|
||||
const RequireAll = require("require-all");
|
||||
|
||||
const internalCommands = RequireAll(__dirname + "/core-commands");
|
||||
|
||||
function handleGuildMessage(client, message, commands) {
|
||||
if (isCommand(message))
|
||||
client.guildDataModel.findOne({ guildID: message.guild.id })
|
||||
.then(guildData =>
|
||||
handleGuildCommand(
|
||||
client,
|
||||
message,
|
||||
Object.assign({}, internalCommands, commands),
|
||||
guildData || client.guildDataModel.create({ guildID: message.guild.id })
|
||||
));
|
||||
}
|
||||
|
||||
function handleGuildCommand(client, message, commands, guildData) {
|
||||
const { botName, isMemberAdmin, params, command } = parseDetails(message, commands);
|
||||
|
||||
if (!command)
|
||||
return;
|
||||
|
||||
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, client, commands, isMemberAdmin })
|
||||
.then(response => {
|
||||
guildData.save();
|
||||
if (response) message.reply(response);
|
||||
})
|
||||
.catch(err => err && message.reply(err));
|
||||
}
|
||||
|
||||
function parseDetails(message, commands) {
|
||||
const split = message.content.split(/ +/);
|
||||
const commandName = Object.keys(commands).find(x =>
|
||||
/**/ commands[x].name.toLowerCase() === (split[1] || "").toLowerCase());
|
||||
|
||||
return {
|
||||
botName: "@" + (message.guild.me.nickname || message.guild.me.user.username),
|
||||
isMemberAdmin: message.member.permissions.has("ADMINISTRATOR"),
|
||||
params: split.slice(2, split.length),
|
||||
command: commands[commandName]
|
||||
};
|
||||
}
|
||||
|
||||
function isCommand(message) {
|
||||
//criteria for a command is bot being mentioned
|
||||
return new RegExp(`^<@!?${/[0-9]{18}/.exec(message.guild.me.toString())[0]}>`).exec(message.content);
|
||||
}
|
||||
|
||||
module.exports = handleGuildMessage;
|
@ -1,74 +0,0 @@
|
||||
// @ts-ignore
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
const Console = require("console");
|
||||
const SimpleFileWriter = require("simple-file-writer");
|
||||
|
||||
const logWriter = new SimpleFileWriter("./console.log");
|
||||
const debugLogWriter = new SimpleFileWriter("./debug.log");
|
||||
|
||||
function ask(client, textChannel, member, question) {
|
||||
//return a promise which will resolve once the user next sends a message in this textChannel
|
||||
return new Promise((resolve, reject) => {
|
||||
const cancelAsk = () => {
|
||||
client.removeListener("message", handler);
|
||||
textChannel.send("Response to question timed out");
|
||||
};
|
||||
|
||||
const askTimeout = setTimeout(cancelAsk, InternalConfig.askTimeout);
|
||||
|
||||
const handler = responseMessage => {
|
||||
if (responseMessage.channel.id === textChannel.id && responseMessage.member && responseMessage.member.id === member.id) {
|
||||
clearTimeout(askTimeout);
|
||||
resolve(responseMessage);
|
||||
}
|
||||
};
|
||||
|
||||
client.on("message", handler);
|
||||
|
||||
textChannel.send(member.toString() + " " + question).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function dateLog(...args) {
|
||||
doDateLog(Console.log, logWriter, args, "INFO");
|
||||
}
|
||||
|
||||
function dateError(...args) {
|
||||
doDateLog(Console.error, logWriter, args, "ERROR");
|
||||
}
|
||||
|
||||
function dateDebugError(...args) {
|
||||
doDateLog(null, null, args, "DEBUG ERROR");
|
||||
}
|
||||
|
||||
function dateDebug(...args) {
|
||||
doDateLog(null, null, args, "DEBUG");
|
||||
}
|
||||
|
||||
function doDateLog(consoleMethod, fileWriter, args, prefix = "") {
|
||||
args = formatArgs([`[${prefix}]`].concat(args));
|
||||
|
||||
if (consoleMethod !== null)
|
||||
consoleMethod.apply(this, args);
|
||||
|
||||
if (fileWriter !== null)
|
||||
fileWriter.write(formatArgsForFile(args));
|
||||
|
||||
debugLogWriter.write(formatArgsForFile(args));
|
||||
}
|
||||
|
||||
function formatArgs(args) {
|
||||
return [`[${new Date().toUTCString()}]`].concat(args);
|
||||
}
|
||||
|
||||
function formatArgsForFile(args) {
|
||||
return args.join(" ") + "\n";
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dateError,
|
||||
dateLog,
|
||||
dateDebug,
|
||||
dateDebugError,
|
||||
ask
|
||||
};
|
@ -1,9 +1,9 @@
|
||||
const Camo = require("camo");
|
||||
|
||||
module.exports = class BaseGuildData extends Camo.Document {
|
||||
constructor() {
|
||||
super();
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this.guildID = String;
|
||||
}
|
||||
this.guildID = String;
|
||||
}
|
||||
};
|
@ -0,0 +1,102 @@
|
||||
const CoreUtil = require("./util.js");
|
||||
const Camo = require("camo");
|
||||
const CronJob = require("cron").CronJob;
|
||||
const Discord = require("discord.js");
|
||||
const HandleGuildMessage = require("./handle-guild-message.js");
|
||||
// @ts-ignore
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
const RequireAll = require("require-all");
|
||||
|
||||
let neDB;
|
||||
|
||||
module.exports = class Client extends Discord.Client {
|
||||
/**
|
||||
* Construct a new Discord.Client with some added functionality
|
||||
* @param {string} token bot token
|
||||
* @param {string} commandsDir location of dir containing commands .js files
|
||||
* @param {*} guildDataModel GuildData model to be used for app; must extend BaseGuildData
|
||||
*/
|
||||
constructor(token, commandsDir, guildDataModel) {
|
||||
super({
|
||||
messageCacheMaxSize: 16,
|
||||
disabledEvents: InternalConfig.disabledEvents
|
||||
});
|
||||
|
||||
this._token = token;
|
||||
this.commandsDir = commandsDir;
|
||||
this.guildDataModel = guildDataModel;
|
||||
|
||||
this.commands = RequireAll(this.commandsDir);
|
||||
|
||||
this.on("ready", this._onReady);
|
||||
this.on("message", this._onMessage);
|
||||
this.on("debug", this._onDebug);
|
||||
this.on("guildCreate", this._onGuildCreate);
|
||||
this.on("guildDelete", this._onGuildDelete);
|
||||
process.on("uncaughtException", err => this._onUnhandledException(this, err));
|
||||
}
|
||||
|
||||
_onReady() {
|
||||
this.user.setGame(InternalConfig.website.replace(/^https?:\/\//, ""));
|
||||
CoreUtil.dateLog(`Registered bot ${this.user.username}`);
|
||||
|
||||
this.removeDeletedGuilds();
|
||||
}
|
||||
|
||||
_onMessage(message) {
|
||||
if (message.channel.type === "text" && message.member)
|
||||
HandleGuildMessage(this, message, this.commands);
|
||||
}
|
||||
|
||||
_onDebug(info) {
|
||||
info = info.replace(/Authenticated using token [^ ]+/, "Authenticated using token [redacted]");
|
||||
if (!InternalConfig.debugIgnores.some(x => info.startsWith(x)))
|
||||
CoreUtil.dateDebug(info);
|
||||
}
|
||||
|
||||
_onGuildCreate(guild) {
|
||||
CoreUtil.dateLog(`Added to guild ${guild.name}`);
|
||||
}
|
||||
|
||||
_onGuildDelete(guild) {
|
||||
this.guildDataModel.findOneAndDelete({ guildID: guild.id });
|
||||
|
||||
CoreUtil.dateLog(`Removed from guild ${guild.name}, removing data for this guild`);
|
||||
}
|
||||
|
||||
_onUnhandledException(client, err) {
|
||||
CoreUtil.dateError("Unhandled exception!\n", err);
|
||||
CoreUtil.dateLog("Destroying existing client...");
|
||||
client.destroy().then(() => {
|
||||
CoreUtil.dateLog("Client destroyed, recreating...");
|
||||
setTimeout(() => client.login(client._token), InternalConfig.reconnectTimeout);
|
||||
});
|
||||
}
|
||||
|
||||
bootstrap() {
|
||||
Camo.connect("nedb://guilds-data").then(db => {
|
||||
neDB = db;
|
||||
new CronJob(InternalConfig.dbCompactionSchedule, compactCollections, null, true);
|
||||
|
||||
this.emit("beforeLogin");
|
||||
this.login(this._token);
|
||||
});
|
||||
}
|
||||
|
||||
removeDeletedGuilds() {
|
||||
this.guildDataModel.find().then(guildDatas => {
|
||||
for (let guildData of guildDatas)
|
||||
if (!this.guilds.get(guildData.guildID))
|
||||
guildData.delete();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
function compactCollections() {
|
||||
/*I realise it is a bit of a cheat to just access _collections in this manner, but in the absence of
|
||||
camo actually having any kind of solution for this it's the easiest method I could come up with.
|
||||
Maybe at some point in future I should fork camo and add this feature. The compaction function is NeDB only
|
||||
and camo is designed to work with both NeDB and MongoDB, which is presumably why it doesn't alraedy exist */
|
||||
for (let collectionName of Object.keys(neDB._collections))
|
||||
neDB._collections[collectionName].persistence.compactDatafile();
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
module.exports = class Command {
|
||||
constructor({ name, description, syntax, admin, invoke }) {
|
||||
this.name = name;
|
||||
this.description = description;
|
||||
this.syntax = syntax;
|
||||
this.admin = admin;
|
||||
this.invoke = invoke;
|
||||
|
||||
const params = this.syntax.split(/ +/);
|
||||
const optionalParams = params.filter(x => x.match(/^\[.+\]$/));
|
||||
|
||||
this.maxParamCount = params.length - 1;
|
||||
this.expectedParamCount = this.maxParamCount - optionalParams.length;
|
||||
}
|
||||
};
|
@ -0,0 +1,53 @@
|
||||
const RequireAll = require("require-all");
|
||||
|
||||
const internalCommands = RequireAll(__dirname + "/core-commands");
|
||||
|
||||
function handleGuildMessage(client, message, commands) {
|
||||
if (isCommand(message))
|
||||
client.guildDataModel.findOne({ guildID: message.guild.id })
|
||||
.then(guildData =>
|
||||
handleGuildCommand(
|
||||
client,
|
||||
message,
|
||||
Object.assign({}, internalCommands, commands),
|
||||
guildData || client.guildDataModel.create({ guildID: message.guild.id })
|
||||
));
|
||||
}
|
||||
|
||||
function handleGuildCommand(client, message, commands, guildData) {
|
||||
const { botName, isMemberAdmin, params, command } = parseDetails(message, commands);
|
||||
|
||||
if (!command)
|
||||
return;
|
||||
|
||||
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, client, commands, isMemberAdmin })
|
||||
.then(response => {
|
||||
guildData.save();
|
||||
if (response) message.reply(response);
|
||||
})
|
||||
.catch(err => err && message.reply(err));
|
||||
}
|
||||
|
||||
function parseDetails(message, commands) {
|
||||
const split = message.content.split(/ +/);
|
||||
const commandName = Object.keys(commands).find(x =>
|
||||
/**/ commands[x].name.toLowerCase() === (split[1] || "").toLowerCase());
|
||||
|
||||
return {
|
||||
botName: "@" + (message.guild.me.nickname || message.guild.me.user.username),
|
||||
isMemberAdmin: message.member.permissions.has("ADMINISTRATOR"),
|
||||
params: split.slice(2, split.length),
|
||||
command: commands[commandName]
|
||||
};
|
||||
}
|
||||
|
||||
function isCommand(message) {
|
||||
//criteria for a command is bot being mentioned
|
||||
return new RegExp(`^<@!?${/[0-9]{18}/.exec(message.guild.me.toString())[0]}>`).exec(message.content);
|
||||
}
|
||||
|
||||
module.exports = handleGuildMessage;
|
@ -1,21 +1,21 @@
|
||||
{
|
||||
"dbCompactionSchedule": "0 * * * * *",
|
||||
"restartSchedule": "0 0 0 * * *",
|
||||
"restartTimeout": 5000,
|
||||
"website": "https://benji7425.github.io",
|
||||
"discordInvite": "https://discord.gg/SSkbwSJ",
|
||||
"debugIgnores": [
|
||||
"[ws] [connection] Sending a heartbeat",
|
||||
"[ws] [connection] Heartbeat acknowledged"
|
||||
],
|
||||
"disabledEvents": [
|
||||
"CHANNEL_PINS_UPDATE",
|
||||
"GUILD_BAN_ADD",
|
||||
"GUILD_BAN_REMOVE",
|
||||
"PRESENCE_UPDATE",
|
||||
"TYPING_START",
|
||||
"USER_NOTE_UPDATE",
|
||||
"USER_SETTINGS_UPDATE"
|
||||
],
|
||||
"askTimeout": 60000
|
||||
"dbCompactionSchedule": "0 * * * * *",
|
||||
"restartSchedule": "0 0 0 * * *",
|
||||
"restartTimeout": 5000,
|
||||
"website": "https://benji7425.github.io",
|
||||
"discordInvite": "https://discord.gg/SSkbwSJ",
|
||||
"debugIgnores": [
|
||||
"[ws] [connection] Sending a heartbeat",
|
||||
"[ws] [connection] Heartbeat acknowledged"
|
||||
],
|
||||
"disabledEvents": [
|
||||
"CHANNEL_PINS_UPDATE",
|
||||
"GUILD_BAN_ADD",
|
||||
"GUILD_BAN_REMOVE",
|
||||
"PRESENCE_UPDATE",
|
||||
"TYPING_START",
|
||||
"USER_NOTE_UPDATE",
|
||||
"USER_SETTINGS_UPDATE"
|
||||
],
|
||||
"askTimeout": 60000
|
||||
}
|
@ -0,0 +1,74 @@
|
||||
// @ts-ignore
|
||||
const InternalConfig = require("./internal-config.json");
|
||||
const Console = require("console");
|
||||
const SimpleFileWriter = require("simple-file-writer");
|
||||
|
||||
const logWriter = new SimpleFileWriter("./console.log");
|
||||
const debugLogWriter = new SimpleFileWriter("./debug.log");
|
||||
|
||||
function ask(client, textChannel, member, question) {
|
||||
//return a promise which will resolve once the user next sends a message in this textChannel
|
||||
return new Promise((resolve, reject) => {
|
||||
const cancelAsk = () => {
|
||||
client.removeListener("message", handler);
|
||||
textChannel.send("Response to question timed out");
|
||||
};
|
||||
|
||||
const askTimeout = setTimeout(cancelAsk, InternalConfig.askTimeout);
|
||||
|
||||
const handler = responseMessage => {
|
||||
if (responseMessage.channel.id === textChannel.id && responseMessage.member && responseMessage.member.id === member.id) {
|
||||
clearTimeout(askTimeout);
|
||||
resolve(responseMessage);
|
||||
}
|
||||
};
|
||||
|
||||
client.on("message", handler);
|
||||
|
||||
textChannel.send(member.toString() + " " + question).catch(reject);
|
||||
});
|
||||
}
|
||||
|
||||
function dateLog(...args) {
|
||||
doDateLog(Console.log, logWriter, args, "INFO");
|
||||
}
|
||||
|
||||
function dateError(...args) {
|
||||
doDateLog(Console.error, logWriter, args, "ERROR");
|
||||
}
|
||||
|
||||
function dateDebugError(...args) {
|
||||
doDateLog(null, null, args, "DEBUG ERROR");
|
||||
}
|
||||
|
||||
function dateDebug(...args) {
|
||||
doDateLog(null, null, args, "DEBUG");
|
||||
}
|
||||
|
||||
function doDateLog(consoleMethod, fileWriter, args, prefix = "") {
|
||||
args = formatArgs([`[${prefix}]`].concat(args));
|
||||
|
||||
if (consoleMethod !== null)
|
||||
consoleMethod.apply(this, args);
|
||||
|
||||
if (fileWriter !== null)
|
||||
fileWriter.write(formatArgsForFile(args));
|
||||
|
||||
debugLogWriter.write(formatArgsForFile(args));
|
||||
}
|
||||
|
||||
function formatArgs(args) {
|
||||
return [`[${new Date().toUTCString()}]`].concat(args);
|
||||
}
|
||||
|
||||
function formatArgsForFile(args) {
|
||||
return args.join(" ") + "\n";
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
dateError,
|
||||
dateLog,
|
||||
dateDebug,
|
||||
dateDebugError,
|
||||
ask
|
||||
};
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue