Update naming and formatting for core .js files

master
benji7425 5 years ago
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
};

@ -2,7 +2,7 @@ const Camo = require("camo");
// @ts-ignore
module.exports = class BaseEmbeddedData extends Camo.EmbeddedDocument {
constructor() {
super();
}
constructor() {
super();
}
};

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

@ -2,13 +2,13 @@
const InternalConfig = require("./internal-config.json");
module.exports = {
Client: require("./Client.js"),
BaseGuildData: require("./BaseGuildData.js"),
BaseEmbeddedData: require("./BaseEmbeddedData.js"),
Command: require("./Command.js"),
util: require("./Util.js"),
details: {
website: InternalConfig.website,
discordInvite: InternalConfig.discordInvite
}
Client: require("./client.js"),
BaseGuildData: require("./base-guild-data.js"),
BaseEmbeddedData: require("./base-embedded-data.js"),
Command: require("./command.js"),
util: require("./util.js"),
details: {
website: InternalConfig.website,
discordInvite: InternalConfig.discordInvite
}
};

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

@ -11,23 +11,23 @@ restart();
new CronJob(InternalConfig.restartSchedule, restart, null, true);
function restart() {
ensureKilledInstance()
.then(bootstrapNewInstance)
.catch(DiscordUtil.dateError);
ensureKilledInstance()
.then(bootstrapNewInstance)
.catch(DiscordUtil.dateError);
}
function bootstrapNewInstance() {
instance = fork(process.argv[2]);
instance = fork(process.argv[2]);
}
function ensureKilledInstance() {
return new Promise((resolve, reject) => {
if (instance) {
instance.kill();
DiscordUtil.dateLog(`Killed existing instance for scheduled restart in ${InternalConfig.restartTimeout / 1000} sec`);
setTimeout(resolve, InternalConfig.restartTimeout);
}
else
resolve();
});
return new Promise((resolve, reject) => {
if (instance) {
instance.kill();
DiscordUtil.dateLog(`Killed existing instance for scheduled restart in ${InternalConfig.restartTimeout / 1000} sec`);
setTimeout(resolve, InternalConfig.restartTimeout);
}
else
resolve();
});
}

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

982
package-lock.json generated

File diff suppressed because it is too large Load Diff

@ -9,6 +9,7 @@
"@types/node": "9.3.0",
"camo": "git+https://github.com/scottwrobinson/camo.git",
"discord.js": "11.2.0",
"eslint": "4.16.0",
"get-urls": "7.0.0",
"jsonfile": "3.0.1",
"rss-parser": "2.12.0",

Loading…
Cancel
Save