Apply code style from .eslintrc

cshd
Dragory 2020-08-13 00:08:37 +03:00
parent b628ac1bfa
commit 86a060410f
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
34 changed files with 606 additions and 606 deletions

View File

@ -1,5 +1,5 @@
const Eris = require('eris'); const Eris = require("eris");
const config = require('./cfg'); const config = require("./cfg");
const bot = new Eris.Client(config.token, { const bot = new Eris.Client(config.token, {
getAllUsers: true, getAllUsers: true,

View File

@ -1,29 +1,29 @@
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const Ajv = require('ajv'); const Ajv = require("ajv");
const schema = require('./data/cfg.schema.json'); const schema = require("./data/cfg.schema.json");
/** @type {ModmailConfig} */ /** @type {ModmailConfig} */
let config = {}; let config = {};
// Config files to search for, in priority order // Config files to search for, in priority order
const configFiles = [ const configFiles = [
'config.ini', "config.ini",
'config.json', "config.json",
'config.json5', "config.json5",
'config.js', "config.js",
// Possible config files when file extensions are hidden // Possible config files when file extensions are hidden
'config.ini.ini', "config.ini.ini",
'config.ini.txt', "config.ini.txt",
'config.json.json', "config.json.json",
'config.json.txt', "config.json.txt",
]; ];
let foundConfigFile; let foundConfigFile;
for (const configFile of configFiles) { for (const configFile of configFiles) {
try { try {
fs.accessSync(__dirname + '/../' + configFile); fs.accessSync(__dirname + "/../" + configFile);
foundConfigFile = configFile; foundConfigFile = configFile;
break; break;
} catch (e) {} } catch (e) {}
@ -33,14 +33,14 @@ for (const configFile of configFiles) {
if (foundConfigFile) { if (foundConfigFile) {
console.log(`Loading configuration from ${foundConfigFile}...`); console.log(`Loading configuration from ${foundConfigFile}...`);
try { try {
if (foundConfigFile.endsWith('.js')) { if (foundConfigFile.endsWith(".js")) {
config = require(`../${foundConfigFile}`); config = require(`../${foundConfigFile}`);
} else { } else {
const raw = fs.readFileSync(__dirname + '/../' + foundConfigFile, {encoding: "utf8"}); const raw = fs.readFileSync(__dirname + "/../" + foundConfigFile, {encoding: "utf8"});
if (foundConfigFile.endsWith('.ini') || foundConfigFile.endsWith('.ini.txt')) { if (foundConfigFile.endsWith(".ini") || foundConfigFile.endsWith(".ini.txt")) {
config = require('ini').decode(raw); config = require("ini").decode(raw);
} else { } else {
config = require('json5').parse(raw); config = require("json5").parse(raw);
} }
} }
} catch (e) { } catch (e) {
@ -49,11 +49,11 @@ if (foundConfigFile) {
} }
// Set dynamic default values which can't be set in the schema directly // Set dynamic default values which can't be set in the schema directly
config.dbDir = path.join(__dirname, '..', 'db'); config.dbDir = path.join(__dirname, "..", "db");
config.logDir = path.join(__dirname, '..', 'logs'); // Only used for migrating data from older Modmail versions config.logDir = path.join(__dirname, "..", "logs"); // Only used for migrating data from older Modmail versions
// Load config values from environment variables // Load config values from environment variables
const envKeyPrefix = 'MM_'; const envKeyPrefix = "MM_";
let loadedEnvValues = 0; let loadedEnvValues = 0;
for (const [key, value] of Object.entries(process.env)) { for (const [key, value] of Object.entries(process.env)) {
@ -64,10 +64,10 @@ for (const [key, value] of Object.entries(process.env)) {
const configKey = key.slice(envKeyPrefix.length) const configKey = key.slice(envKeyPrefix.length)
.toLowerCase() .toLowerCase()
.replace(/([a-z])_([a-z])/g, (m, m1, m2) => `${m1}${m2.toUpperCase()}`) .replace(/([a-z])_([a-z])/g, (m, m1, m2) => `${m1}${m2.toUpperCase()}`)
.replace('__', '.'); .replace("__", ".");
config[configKey] = value.includes('||') config[configKey] = value.includes("||")
? value.split('||') ? value.split("||")
: value; : value;
loadedEnvValues++; loadedEnvValues++;
@ -80,15 +80,15 @@ if (process.env.PORT && ! process.env.MM_PORT) {
} }
if (loadedEnvValues > 0) { if (loadedEnvValues > 0) {
console.log(`Loaded ${loadedEnvValues} ${loadedEnvValues === 1 ? 'value' : 'values'} from environment variables`); console.log(`Loaded ${loadedEnvValues} ${loadedEnvValues === 1 ? "value" : "values"} from environment variables`);
} }
// Convert config keys with periods to objects // Convert config keys with periods to objects
// E.g. commandAliases.mv -> commandAliases: { mv: ... } // E.g. commandAliases.mv -> commandAliases: { mv: ... }
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
if (! key.includes('.')) continue; if (! key.includes(".")) continue;
const keys = key.split('.'); const keys = key.split(".");
let cursor = config; let cursor = config;
for (let i = 0; i < keys.length; i++) { for (let i = 0; i < keys.length; i++) {
if (i === keys.length - 1) { if (i === keys.length - 1) {
@ -102,20 +102,20 @@ for (const [key, value] of Object.entries(config)) {
delete config[key]; delete config[key];
} }
if (! config['knex']) { if (! config["knex"]) {
config.knex = { config.knex = {
client: 'sqlite', client: "sqlite",
connection: { connection: {
filename: path.join(config.dbDir, 'data.sqlite') filename: path.join(config.dbDir, "data.sqlite")
}, },
useNullAsDefault: true useNullAsDefault: true
}; };
} }
// Make sure migration settings are always present in knex config // Make sure migration settings are always present in knex config
Object.assign(config['knex'], { Object.assign(config["knex"], {
migrations: { migrations: {
directory: path.join(config.dbDir, 'migrations') directory: path.join(config.dbDir, "migrations")
} }
}); });
@ -141,7 +141,7 @@ if (config.newThreadCategoryId) {
// Delete empty string options (i.e. "option=" without a value in config.ini) // Delete empty string options (i.e. "option=" without a value in config.ini)
for (const [key, value] of Object.entries(config)) { for (const [key, value] of Object.entries(config)) {
if (value === '') { if (value === "") {
delete config[key]; delete config[key];
} }
} }
@ -152,7 +152,7 @@ const ajv = new Ajv({ useDefaults: true, coerceTypes: "array" });
// https://github.com/ajv-validator/ajv/issues/141#issuecomment-270692820 // https://github.com/ajv-validator/ajv/issues/141#issuecomment-270692820
const truthyValues = ["1", "true", "on"]; const truthyValues = ["1", "true", "on"];
const falsyValues = ["0", "false", "off"]; const falsyValues = ["0", "false", "off"];
ajv.addKeyword('coerceBoolean', { ajv.addKeyword("coerceBoolean", {
compile(value) { compile(value) {
return (data, dataPath, parentData, parentKey) => { return (data, dataPath, parentData, parentKey) => {
if (! value) { if (! value) {
@ -183,7 +183,7 @@ ajv.addKeyword('coerceBoolean', {
}, },
}); });
ajv.addKeyword('multilineString', { ajv.addKeyword("multilineString", {
compile(value) { compile(value) {
return (data, dataPath, parentData, parentKey) => { return (data, dataPath, parentData, parentKey) => {
if (! value) { if (! value) {
@ -208,12 +208,12 @@ const validate = ajv.compile(schema);
const configIsValid = validate(config); const configIsValid = validate(config);
if (! configIsValid) { if (! configIsValid) {
console.error('Issues with configuration options:'); console.error("Issues with configuration options:");
for (const error of validate.errors) { for (const error of validate.errors) {
console.error(`The "${error.dataPath.slice(1)}" option ${error.message}`); console.error(`The "${error.dataPath.slice(1)}" option ${error.message}`);
} }
console.error(''); console.error("");
console.error('Please restart the bot after fixing the issues mentioned above.'); console.error("Please restart the bot after fixing the issues mentioned above.");
process.exit(1); process.exit(1);
} }

View File

@ -1,9 +1,9 @@
const { CommandManager, defaultParameterTypes, TypeConversionError, IParameter, ICommandConfig } = require('knub-command-manager'); const { CommandManager, defaultParameterTypes, TypeConversionError, IParameter, ICommandConfig } = require("knub-command-manager");
const Eris = require('eris'); const Eris = require("eris");
const config = require('./cfg'); const config = require("./cfg");
const utils = require('./utils'); const utils = require("./utils");
const threads = require('./data/threads'); const threads = require("./data/threads");
const Thread = require('./data/Thread'); const Thread = require("./data/Thread");
module.exports = { module.exports = {
createCommandManager(bot) { createCommandManager(bot) {
@ -27,7 +27,7 @@ module.exports = {
const handlers = {}; const handlers = {};
const aliasMap = new Map(); const aliasMap = new Map();
bot.on('messageCreate', async msg => { bot.on("messageCreate", async msg => {
if (msg.author.bot) return; if (msg.author.bot) return;
if (msg.author.id === bot.user.id) return; if (msg.author.id === bot.user.id) return;
if (! msg.content) return; if (! msg.content) return;

View File

@ -1,16 +1,16 @@
const moment = require('moment'); const moment = require("moment");
const Eris = require('eris'); const Eris = require("eris");
const bot = require('../bot'); const bot = require("../bot");
const knex = require('../knex'); const knex = require("../knex");
const utils = require('../utils'); const utils = require("../utils");
const config = require('../cfg'); const config = require("../cfg");
const attachments = require('./attachments'); const attachments = require("./attachments");
const { formatters } = require('../formatters'); const { formatters } = require("../formatters");
const ThreadMessage = require('./ThreadMessage'); const ThreadMessage = require("./ThreadMessage");
const {THREAD_MESSAGE_TYPE, THREAD_STATUS} = require('./constants'); const {THREAD_MESSAGE_TYPE, THREAD_STATUS} = require("./constants");
/** /**
* @property {String} id * @property {String} id
@ -41,12 +41,12 @@ class Thread {
// Try to open a DM channel with the user // Try to open a DM channel with the user
const dmChannel = await this.getDMChannel(); const dmChannel = await this.getDMChannel();
if (! dmChannel) { if (! dmChannel) {
throw new Error('Could not open DMs with the user. They may have blocked the bot or set their privacy settings higher.'); throw new Error("Could not open DMs with the user. They may have blocked the bot or set their privacy settings higher.");
} }
let firstMessage; let firstMessage;
if (typeof content === 'string') { if (typeof content === "string") {
// Content is a string, chunk it and send it as individual messages. // Content is a string, chunk it and send it as individual messages.
// Files (attachments) are only sent with the last message. // Files (attachments) are only sent with the last message.
const chunks = utils.chunk(content, 2000); const chunks = utils.chunk(content, 2000);
@ -79,7 +79,7 @@ class Thread {
try { try {
let firstMessage; let firstMessage;
if (typeof content === 'string') { if (typeof content === "string") {
// Content is a string, chunk it and send it as individual messages. // Content is a string, chunk it and send it as individual messages.
// Files (attachments) are only sent with the last message. // Files (attachments) are only sent with the last message.
const chunks = utils.chunk(content, 2000); const chunks = utils.chunk(content, 2000);
@ -120,16 +120,16 @@ class Thread {
} }
const dmChannel = await this.getDMChannel(); const dmChannel = await this.getDMChannel();
const insertedIds = await knex('thread_messages').insert({ const insertedIds = await knex("thread_messages").insert({
thread_id: this.id, thread_id: this.id,
created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss'), created_at: moment.utc().format("YYYY-MM-DD HH:mm:ss"),
is_anonymous: 0, is_anonymous: 0,
dm_channel_id: dmChannel.id, dm_channel_id: dmChannel.id,
...data ...data
}); });
const threadMessage = await knex('thread_messages') const threadMessage = await knex("thread_messages")
.where('id', insertedIds[0]) .where("id", insertedIds[0])
.select(); .select();
return new ThreadMessage(threadMessage[0]); return new ThreadMessage(threadMessage[0]);
@ -142,8 +142,8 @@ class Thread {
* @private * @private
*/ */
async _updateThreadMessage(id, data) { async _updateThreadMessage(id, data) {
await knex('thread_messages') await knex("thread_messages")
.where('id', id) .where("id", id)
.update(data); .update(data);
} }
@ -153,8 +153,8 @@ class Thread {
* @private * @private
*/ */
async _deleteThreadMessage(id) { async _deleteThreadMessage(id) {
await knex('thread_messages') await knex("thread_messages")
.where('id', id) .where("id", id)
.delete(); .delete();
} }
@ -163,8 +163,8 @@ class Thread {
* @private * @private
*/ */
_lastMessageNumberInThreadSQL() { _lastMessageNumberInThreadSQL() {
return knex('thread_messages AS tm_msg_num_ref') return knex("thread_messages AS tm_msg_num_ref")
.select(knex.raw('MAX(tm_msg_num_ref.message_number)')) .select(knex.raw("MAX(tm_msg_num_ref.message_number)"))
.whereRaw(`tm_msg_num_ref.thread_id = '${this.id}'`) .whereRaw(`tm_msg_num_ref.thread_id = '${this.id}'`)
.toSQL() .toSQL()
.sql; .sql;
@ -212,7 +212,7 @@ class Thread {
message_type: THREAD_MESSAGE_TYPE.TO_USER, message_type: THREAD_MESSAGE_TYPE.TO_USER,
user_id: moderator.id, user_id: moderator.id,
user_name: fullModeratorName, user_name: fullModeratorName,
body: '', body: "",
is_anonymous: (isAnonymous ? 1 : 0), is_anonymous: (isAnonymous ? 1 : 0),
dm_message_id: dmMessage.id dm_message_id: dmMessage.id
}); });
@ -227,7 +227,7 @@ class Thread {
// Interrupt scheduled closing, if in progress // Interrupt scheduled closing, if in progress
if (this.scheduled_close_at) { if (this.scheduled_close_at) {
await this.cancelScheduledClose(); await this.cancelScheduledClose();
await this.postSystemMessage(`Cancelling scheduled closing of this thread due to new reply`); await this.postSystemMessage("Cancelling scheduled closing of this thread due to new reply");
} }
return true; return true;
@ -306,11 +306,11 @@ class Thread {
async postSystemMessage(content, file = null, opts = {}) { async postSystemMessage(content, file = null, opts = {}) {
const msg = await this._postToThreadChannel(content, file); const msg = await this._postToThreadChannel(content, file);
if (msg && opts.saveToLog !== false) { if (msg && opts.saveToLog !== false) {
const finalLogBody = opts.logBody || msg.content || '<empty message>'; const finalLogBody = opts.logBody || msg.content || "<empty message>";
await this._addThreadMessageToDB({ await this._addThreadMessageToDB({
message_type: THREAD_MESSAGE_TYPE.SYSTEM, message_type: THREAD_MESSAGE_TYPE.SYSTEM,
user_id: null, user_id: null,
user_name: '', user_name: "",
body: finalLogBody, body: finalLogBody,
is_anonymous: 0, is_anonymous: 0,
inbox_message_id: msg.id, inbox_message_id: msg.id,
@ -329,11 +329,11 @@ class Thread {
async sendSystemMessageToUser(content, file = null, opts = {}) { async sendSystemMessageToUser(content, file = null, opts = {}) {
const msg = await this._sendDMToUser(content, file); const msg = await this._sendDMToUser(content, file);
if (opts.saveToLog !== false) { if (opts.saveToLog !== false) {
const finalLogBody = opts.logBody || msg.content || '<empty message>'; const finalLogBody = opts.logBody || msg.content || "<empty message>";
await this._addThreadMessageToDB({ await this._addThreadMessageToDB({
message_type: THREAD_MESSAGE_TYPE.SYSTEM_TO_USER, message_type: THREAD_MESSAGE_TYPE.SYSTEM_TO_USER,
user_id: null, user_id: null,
user_name: '', user_name: "",
body: finalLogBody, body: finalLogBody,
is_anonymous: 0, is_anonymous: 0,
dm_message_id: msg.id, dm_message_id: msg.id,
@ -381,9 +381,9 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async updateChatMessageInLogs(msg) { async updateChatMessageInLogs(msg) {
await knex('thread_messages') await knex("thread_messages")
.where('thread_id', this.id) .where("thread_id", this.id)
.where('dm_message_id', msg.id) .where("dm_message_id", msg.id)
.update({ .update({
body: msg.content body: msg.content
}); });
@ -394,9 +394,9 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async deleteChatMessageFromLogs(messageId) { async deleteChatMessageFromLogs(messageId) {
await knex('thread_messages') await knex("thread_messages")
.where('thread_id', this.id) .where("thread_id", this.id)
.where('dm_message_id', messageId) .where("dm_message_id", messageId)
.delete(); .delete();
} }
@ -404,10 +404,10 @@ class Thread {
* @returns {Promise<ThreadMessage[]>} * @returns {Promise<ThreadMessage[]>}
*/ */
async getThreadMessages() { async getThreadMessages() {
const threadMessages = await knex('thread_messages') const threadMessages = await knex("thread_messages")
.where('thread_id', this.id) .where("thread_id", this.id)
.orderBy('created_at', 'ASC') .orderBy("created_at", "ASC")
.orderBy('id', 'ASC') .orderBy("id", "ASC")
.select(); .select();
return threadMessages.map(row => new ThreadMessage(row)); return threadMessages.map(row => new ThreadMessage(row));
@ -418,9 +418,9 @@ class Thread {
* @returns {Promise<ThreadMessage>} * @returns {Promise<ThreadMessage>}
*/ */
async findThreadMessageByMessageNumber(messageNumber) { async findThreadMessageByMessageNumber(messageNumber) {
const data = await knex('thread_messages') const data = await knex("thread_messages")
.where('thread_id', this.id) .where("thread_id", this.id)
.where('message_number', messageNumber) .where("message_number", messageNumber)
.select(); .select();
return data ? new ThreadMessage(data) : null; return data ? new ThreadMessage(data) : null;
@ -434,15 +434,15 @@ class Thread {
console.log(`Closing thread ${this.id}`); console.log(`Closing thread ${this.id}`);
if (silent) { if (silent) {
await this.postSystemMessage('Closing thread silently...'); await this.postSystemMessage("Closing thread silently...");
} else { } else {
await this.postSystemMessage('Closing thread...'); await this.postSystemMessage("Closing thread...");
} }
} }
// Update DB status // Update DB status
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
status: THREAD_STATUS.CLOSED status: THREAD_STATUS.CLOSED
}); });
@ -451,7 +451,7 @@ class Thread {
const channel = bot.getChannel(this.channel_id); const channel = bot.getChannel(this.channel_id);
if (channel) { if (channel) {
console.log(`Deleting channel ${this.channel_id}`); console.log(`Deleting channel ${this.channel_id}`);
await channel.delete('Thread closed'); await channel.delete("Thread closed");
} }
} }
@ -462,8 +462,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async scheduleClose(time, user, silent) { async scheduleClose(time, user, silent) {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
scheduled_close_at: time, scheduled_close_at: time,
scheduled_close_id: user.id, scheduled_close_id: user.id,
@ -476,8 +476,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async cancelScheduledClose() { async cancelScheduledClose() {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
scheduled_close_at: null, scheduled_close_at: null,
scheduled_close_id: null, scheduled_close_id: null,
@ -490,8 +490,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async suspend() { async suspend() {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
status: THREAD_STATUS.SUSPENDED, status: THREAD_STATUS.SUSPENDED,
scheduled_suspend_at: null, scheduled_suspend_at: null,
@ -504,8 +504,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async unsuspend() { async unsuspend() {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
status: THREAD_STATUS.OPEN status: THREAD_STATUS.OPEN
}); });
@ -517,8 +517,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async scheduleSuspend(time, user) { async scheduleSuspend(time, user) {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
scheduled_suspend_at: time, scheduled_suspend_at: time,
scheduled_suspend_id: user.id, scheduled_suspend_id: user.id,
@ -530,8 +530,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async cancelScheduledSuspend() { async cancelScheduledSuspend() {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
scheduled_suspend_at: null, scheduled_suspend_at: null,
scheduled_suspend_id: null, scheduled_suspend_id: null,
@ -544,8 +544,8 @@ class Thread {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async setAlert(userId) { async setAlert(userId) {
await knex('threads') await knex("threads")
.where('id', this.id) .where("id", this.id)
.update({ .update({
alert_id: userId alert_id: userId
}); });

View File

@ -1,13 +1,13 @@
const Eris = require('eris'); const Eris = require("eris");
const fs = require('fs'); const fs = require("fs");
const https = require('https'); const https = require("https");
const {promisify} = require('util'); const {promisify} = require("util");
const tmp = require('tmp'); const tmp = require("tmp");
const config = require('../cfg'); const config = require("../cfg");
const utils = require('../utils'); const utils = require("../utils");
const mv = promisify(require('mv')); const mv = promisify(require("mv"));
const getUtils = () => require('../utils'); const getUtils = () => require("../utils");
const access = promisify(fs.access); const access = promisify(fs.access);
const readFile = promisify(fs.readFile); const readFile = promisify(fs.readFile);
@ -20,7 +20,7 @@ const attachmentStorageTypes = {};
function getErrorResult(msg = null) { function getErrorResult(msg = null) {
return { return {
url: `Attachment could not be saved${msg ? ': ' + msg : ''}`, url: `Attachment could not be saved${msg ? ": " + msg : ""}`,
failed: true failed: true
}; };
} }
@ -61,8 +61,8 @@ async function saveLocalAttachment(attachment) {
function downloadAttachment(attachment, tries = 0) { function downloadAttachment(attachment, tries = 0) {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (tries > 3) { if (tries > 3) {
console.error('Attachment download failed after 3 tries:', attachment); console.error("Attachment download failed after 3 tries:", attachment);
reject('Attachment download failed after 3 tries'); reject("Attachment download failed after 3 tries");
return; return;
} }
@ -71,16 +71,16 @@ function downloadAttachment(attachment, tries = 0) {
https.get(attachment.url, (res) => { https.get(attachment.url, (res) => {
res.pipe(writeStream); res.pipe(writeStream);
writeStream.on('finish', () => { writeStream.on("finish", () => {
writeStream.end(); writeStream.end();
resolve({ resolve({
path: filepath, path: filepath,
cleanup: cleanupCallback cleanup: cleanupCallback
}); });
}); });
}).on('error', (err) => { }).on("error", (err) => {
fs.unlink(filepath); fs.unlink(filepath);
console.error('Error downloading attachment, retrying'); console.error("Error downloading attachment, retrying");
resolve(downloadAttachment(attachment, tries++)); resolve(downloadAttachment(attachment, tries++));
}); });
}); });
@ -103,7 +103,7 @@ function getLocalAttachmentPath(attachmentId) {
* @returns {Promise<String>} * @returns {Promise<String>}
*/ */
function getLocalAttachmentUrl(attachmentId, desiredName = null) { function getLocalAttachmentUrl(attachmentId, desiredName = null) {
if (desiredName == null) desiredName = 'file.bin'; if (desiredName == null) desiredName = "file.bin";
return getUtils().getSelfUrl(`attachments/${attachmentId}/${desiredName}`); return getUtils().getSelfUrl(`attachments/${attachmentId}/${desiredName}`);
} }
@ -113,19 +113,19 @@ function getLocalAttachmentUrl(attachmentId, desiredName = null) {
*/ */
async function saveDiscordAttachment(attachment) { async function saveDiscordAttachment(attachment) {
if (attachment.size > 1024 * 1024 * 8) { if (attachment.size > 1024 * 1024 * 8) {
return getErrorResult('attachment too large (max 8MB)'); return getErrorResult("attachment too large (max 8MB)");
} }
const attachmentChannelId = config.attachmentStorageChannelId; const attachmentChannelId = config.attachmentStorageChannelId;
const inboxGuild = utils.getInboxGuild(); const inboxGuild = utils.getInboxGuild();
if (! inboxGuild.channels.has(attachmentChannelId)) { if (! inboxGuild.channels.has(attachmentChannelId)) {
throw new Error('Attachment storage channel not found!'); throw new Error("Attachment storage channel not found!");
} }
const attachmentChannel = inboxGuild.channels.get(attachmentChannelId); const attachmentChannel = inboxGuild.channels.get(attachmentChannelId);
if (! (attachmentChannel instanceof Eris.TextChannel)) { if (! (attachmentChannel instanceof Eris.TextChannel)) {
throw new Error('Attachment storage channel must be a text channel!'); throw new Error("Attachment storage channel must be a text channel!");
} }
const file = await attachmentToDiscordFileObject(attachment); const file = await attachmentToDiscordFileObject(attachment);

View File

@ -1,13 +1,13 @@
const moment = require('moment'); const moment = require("moment");
const knex = require('../knex'); const knex = require("../knex");
/** /**
* @param {String} userId * @param {String} userId
* @returns {Promise<{ isBlocked: boolean, expiresAt: string }>} * @returns {Promise<{ isBlocked: boolean, expiresAt: string }>}
*/ */
async function getBlockStatus(userId) { async function getBlockStatus(userId) {
const row = await knex('blocked_users') const row = await knex("blocked_users")
.where('user_id', userId) .where("user_id", userId)
.first(); .first();
return { return {
@ -32,15 +32,15 @@ async function isBlocked(userId) {
* @param {String} blockedBy * @param {String} blockedBy
* @returns {Promise} * @returns {Promise}
*/ */
async function block(userId, userName = '', blockedBy = null, expiresAt = null) { async function block(userId, userName = "", blockedBy = null, expiresAt = null) {
if (await isBlocked(userId)) return; if (await isBlocked(userId)) return;
return knex('blocked_users') return knex("blocked_users")
.insert({ .insert({
user_id: userId, user_id: userId,
user_name: userName, user_name: userName,
blocked_by: blockedBy, blocked_by: blockedBy,
blocked_at: moment.utc().format('YYYY-MM-DD HH:mm:ss'), blocked_at: moment.utc().format("YYYY-MM-DD HH:mm:ss"),
expires_at: expiresAt expires_at: expiresAt
}); });
} }
@ -51,8 +51,8 @@ async function block(userId, userName = '', blockedBy = null, expiresAt = null)
* @returns {Promise} * @returns {Promise}
*/ */
async function unblock(userId) { async function unblock(userId) {
return knex('blocked_users') return knex("blocked_users")
.where('user_id', userId) .where("user_id", userId)
.delete(); .delete();
} }
@ -63,8 +63,8 @@ async function unblock(userId) {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function updateExpiryTime(userId, expiresAt) { async function updateExpiryTime(userId, expiresAt) {
return knex('blocked_users') return knex("blocked_users")
.where('user_id', userId) .where("user_id", userId)
.update({ .update({
expires_at: expiresAt expires_at: expiresAt
}); });
@ -74,11 +74,11 @@ async function updateExpiryTime(userId, expiresAt) {
* @returns {String[]} * @returns {String[]}
*/ */
async function getExpiredBlocks() { async function getExpiredBlocks() {
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss'); const now = moment.utc().format("YYYY-MM-DD HH:mm:ss");
const blocks = await knex('blocked_users') const blocks = await knex("blocked_users")
.whereNotNull('expires_at') .whereNotNull("expires_at")
.where('expires_at', '<=', now) .where("expires_at", "<=", now)
.select(); .select();
return blocks.map(block => block.user_id); return blocks.map(block => block.user_id);

View File

@ -27,41 +27,41 @@ module.exports = {
}, },
ACCIDENTAL_THREAD_MESSAGES: [ ACCIDENTAL_THREAD_MESSAGES: [
'ok', "ok",
'okay', "okay",
'thanks', "thanks",
'ty', "ty",
'k', "k",
'kk', "kk",
'thank you', "thank you",
'thanx', "thanx",
'thnx', "thnx",
'thx', "thx",
'tnx', "tnx",
'ok thank you', "ok thank you",
'ok thanks', "ok thanks",
'ok ty', "ok ty",
'ok thanx', "ok thanx",
'ok thnx', "ok thnx",
'ok thx', "ok thx",
'ok no problem', "ok no problem",
'ok np', "ok np",
'okay thank you', "okay thank you",
'okay thanks', "okay thanks",
'okay ty', "okay ty",
'okay thanx', "okay thanx",
'okay thnx', "okay thnx",
'okay thx', "okay thx",
'okay no problem', "okay no problem",
'okay np', "okay np",
'okey thank you', "okey thank you",
'okey thanks', "okey thanks",
'okey ty', "okey ty",
'okey thanx', "okey thanx",
'okey thnx', "okey thnx",
'okey thx', "okey thx",
'okey no problem', "okey no problem",
'okey np', "okey np",
'cheers' "cheers"
], ],
}; };

View File

@ -1,8 +1,8 @@
const path = require('path'); const path = require("path");
const fs = require('fs'); const fs = require("fs");
const toJsdoc = require('json-schema-to-jsdoc'); const toJsdoc = require("json-schema-to-jsdoc");
const schema = require('./cfg.schema.json'); const schema = require("./cfg.schema.json");
const target = path.join(__dirname, 'cfg.jsdoc.js'); const target = path.join(__dirname, "cfg.jsdoc.js");
// Fix up some custom types for the JSDoc conversion // Fix up some custom types for the JSDoc conversion
const schemaCopy = JSON.parse(JSON.stringify(schema)); const schemaCopy = JSON.parse(JSON.stringify(schema));
@ -20,4 +20,4 @@ for (const propertyDef of Object.values(schemaCopy.properties)) {
} }
const result = toJsdoc(schemaCopy); const result = toJsdoc(schemaCopy);
fs.writeFileSync(target, result, { encoding: 'utf8' }); fs.writeFileSync(target, result, { encoding: "utf8" });

View File

@ -1,14 +1,14 @@
const moment = require('moment'); const moment = require("moment");
const knex = require('../knex'); const knex = require("../knex");
const Snippet = require('./Snippet'); const Snippet = require("./Snippet");
/** /**
* @param {String} trigger * @param {String} trigger
* @returns {Promise<Snippet>} * @returns {Promise<Snippet>}
*/ */
async function getSnippet(trigger) { async function getSnippet(trigger) {
const snippet = await knex('snippets') const snippet = await knex("snippets")
.where('trigger', trigger) .where("trigger", trigger)
.first(); .first();
return (snippet ? new Snippet(snippet) : null); return (snippet ? new Snippet(snippet) : null);
@ -22,11 +22,11 @@ async function getSnippet(trigger) {
async function addSnippet(trigger, body, createdBy = 0) { async function addSnippet(trigger, body, createdBy = 0) {
if (await getSnippet(trigger)) return; if (await getSnippet(trigger)) return;
return knex('snippets').insert({ return knex("snippets").insert({
trigger, trigger,
body, body,
created_by: createdBy, created_by: createdBy,
created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss') created_at: moment.utc().format("YYYY-MM-DD HH:mm:ss")
}); });
} }
@ -35,8 +35,8 @@ async function addSnippet(trigger, body, createdBy = 0) {
* @returns {Promise<void>} * @returns {Promise<void>}
*/ */
async function deleteSnippet(trigger) { async function deleteSnippet(trigger) {
return knex('snippets') return knex("snippets")
.where('trigger', trigger) .where("trigger", trigger)
.delete(); .delete();
} }
@ -44,7 +44,7 @@ async function deleteSnippet(trigger) {
* @returns {Promise<Snippet[]>} * @returns {Promise<Snippet[]>}
*/ */
async function getAllSnippets() { async function getAllSnippets() {
const snippets = await knex('snippets') const snippets = await knex("snippets")
.select(); .select();
return snippets.map(s => new Snippet(s)); return snippets.map(s => new Snippet(s));

View File

@ -1,19 +1,19 @@
const {User, Member} = require('eris'); const {User, Member} = require("eris");
const transliterate = require('transliteration'); const transliterate = require("transliteration");
const moment = require('moment'); const moment = require("moment");
const uuid = require('uuid'); const uuid = require("uuid");
const humanizeDuration = require('humanize-duration'); const humanizeDuration = require("humanize-duration");
const bot = require('../bot'); const bot = require("../bot");
const knex = require('../knex'); const knex = require("../knex");
const config = require('../cfg'); const config = require("../cfg");
const utils = require('../utils'); const utils = require("../utils");
const updates = require('./updates'); const updates = require("./updates");
const Thread = require('./Thread'); const Thread = require("./Thread");
const {callBeforeNewThreadHooks} = require("../hooks/beforeNewThread"); const {callBeforeNewThreadHooks} = require("../hooks/beforeNewThread");
const {THREAD_STATUS, DISOCRD_CHANNEL_TYPES} = require('./constants'); const {THREAD_STATUS, DISOCRD_CHANNEL_TYPES} = require("./constants");
const MINUTES = 60 * 1000; const MINUTES = 60 * 1000;
const HOURS = 60 * MINUTES; const HOURS = 60 * MINUTES;
@ -23,8 +23,8 @@ const HOURS = 60 * MINUTES;
* @returns {Promise<Thread>} * @returns {Promise<Thread>}
*/ */
async function findById(id) { async function findById(id) {
const thread = await knex('threads') const thread = await knex("threads")
.where('id', id) .where("id", id)
.first(); .first();
return (thread ? new Thread(thread) : null); return (thread ? new Thread(thread) : null);
@ -35,9 +35,9 @@ async function findById(id) {
* @returns {Promise<Thread>} * @returns {Promise<Thread>}
*/ */
async function findOpenThreadByUserId(userId) { async function findOpenThreadByUserId(userId) {
const thread = await knex('threads') const thread = await knex("threads")
.where('user_id', userId) .where("user_id", userId)
.where('status', THREAD_STATUS.OPEN) .where("status", THREAD_STATUS.OPEN)
.first(); .first();
return (thread ? new Thread(thread) : null); return (thread ? new Thread(thread) : null);
@ -70,7 +70,7 @@ async function createNewThreadForUser(user, opts = {}) {
const existingThread = await findOpenThreadByUserId(user.id); const existingThread = await findOpenThreadByUserId(user.id);
if (existingThread) { if (existingThread) {
throw new Error('Attempted to create a new thread for a user with an existing open thread!'); throw new Error("Attempted to create a new thread for a user with an existing open thread!");
} }
// If set in config, check that the user's account is old enough (time since they registered on Discord) // If set in config, check that the user's account is old enough (time since they registered on Discord)
@ -132,7 +132,7 @@ async function createNewThreadForUser(user, opts = {}) {
// Use the user's name+discrim for the thread channel's name // Use the user's name+discrim for the thread channel's name
// Channel names are particularly picky about what characters they allow, so we gotta do some clean-up // Channel names are particularly picky about what characters they allow, so we gotta do some clean-up
let cleanName = transliterate.slugify(user.username); let cleanName = transliterate.slugify(user.username);
if (cleanName === '') cleanName = 'unknown'; if (cleanName === "") cleanName = "unknown";
cleanName = cleanName.slice(0, 95); // Make sure the discrim fits cleanName = cleanName.slice(0, 95); // Make sure the discrim fits
const channelName = `${cleanName}-${user.discriminator}`; const channelName = `${cleanName}-${user.discriminator}`;
@ -161,7 +161,7 @@ async function createNewThreadForUser(user, opts = {}) {
let createdChannel; let createdChannel;
try { try {
createdChannel = await utils.getInboxGuild().createChannel(channelName, DISOCRD_CHANNEL_TYPES.GUILD_TEXT, { createdChannel = await utils.getInboxGuild().createChannel(channelName, DISOCRD_CHANNEL_TYPES.GUILD_TEXT, {
reason: 'New Modmail thread', reason: "New Modmail thread",
parentID: newThreadCategoryId, parentID: newThreadCategoryId,
}); });
} catch (err) { } catch (err) {
@ -175,7 +175,7 @@ async function createNewThreadForUser(user, opts = {}) {
user_id: user.id, user_id: user.id,
user_name: `${user.username}#${user.discriminator}`, user_name: `${user.username}#${user.discriminator}`,
channel_id: createdChannel.id, channel_id: createdChannel.id,
created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss') created_at: moment.utc().format("YYYY-MM-DD HH:mm:ss")
}); });
const newThread = await findById(newThreadId); const newThread = await findById(newThreadId);
@ -216,7 +216,7 @@ async function createNewThreadForUser(user, opts = {}) {
infoHeaderItems.push(`ID **${user.id}**`); infoHeaderItems.push(`ID **${user.id}**`);
} }
let infoHeader = infoHeaderItems.join(', '); let infoHeader = infoHeaderItems.join(", ");
// Guild member info // Guild member info
for (const [guildId, guildData] of userGuildData.entries()) { for (const [guildId, guildData] of userGuildData.entries()) {
@ -235,10 +235,10 @@ async function createNewThreadForUser(user, opts = {}) {
if (config.rolesInThreadHeader && guildData.member.roles.length) { if (config.rolesInThreadHeader && guildData.member.roles.length) {
const roles = guildData.member.roles.map(roleId => guildData.guild.roles.get(roleId)).filter(Boolean); const roles = guildData.member.roles.map(roleId => guildData.guild.roles.get(roleId)).filter(Boolean);
headerItems.push(`ROLES **${roles.map(r => r.name).join(', ')}**`); headerItems.push(`ROLES **${roles.map(r => r.name).join(", ")}**`);
} }
const headerStr = headerItems.join(', '); const headerStr = headerItems.join(", ");
if (mainGuilds.length === 1) { if (mainGuilds.length === 1) {
infoHeader += `\n${headerStr}`; infoHeader += `\n${headerStr}`;
@ -253,7 +253,7 @@ async function createNewThreadForUser(user, opts = {}) {
infoHeader += `\n\nThis user has **${userLogCount}** previous modmail threads. Use \`${config.prefix}logs\` to see them.`; infoHeader += `\n\nThis user has **${userLogCount}** previous modmail threads. Use \`${config.prefix}logs\` to see them.`;
} }
infoHeader += '\n────────────────'; infoHeader += "\n────────────────";
await newThread.postSystemMessage(infoHeader); await newThread.postSystemMessage(infoHeader);
@ -280,10 +280,10 @@ async function createNewThreadForUser(user, opts = {}) {
*/ */
async function createThreadInDB(data) { async function createThreadInDB(data) {
const threadId = uuid.v4(); const threadId = uuid.v4();
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss'); const now = moment.utc().format("YYYY-MM-DD HH:mm:ss");
const finalData = Object.assign({created_at: now, is_legacy: 0}, data, {id: threadId}); const finalData = Object.assign({created_at: now, is_legacy: 0}, data, {id: threadId});
await knex('threads').insert(finalData); await knex("threads").insert(finalData);
return threadId; return threadId;
} }
@ -293,8 +293,8 @@ async function createThreadInDB(data) {
* @returns {Promise<Thread>} * @returns {Promise<Thread>}
*/ */
async function findByChannelId(channelId) { async function findByChannelId(channelId) {
const thread = await knex('threads') const thread = await knex("threads")
.where('channel_id', channelId) .where("channel_id", channelId)
.first(); .first();
return (thread ? new Thread(thread) : null); return (thread ? new Thread(thread) : null);
@ -305,9 +305,9 @@ async function findByChannelId(channelId) {
* @returns {Promise<Thread>} * @returns {Promise<Thread>}
*/ */
async function findOpenThreadByChannelId(channelId) { async function findOpenThreadByChannelId(channelId) {
const thread = await knex('threads') const thread = await knex("threads")
.where('channel_id', channelId) .where("channel_id", channelId)
.where('status', THREAD_STATUS.OPEN) .where("status", THREAD_STATUS.OPEN)
.first(); .first();
return (thread ? new Thread(thread) : null); return (thread ? new Thread(thread) : null);
@ -318,9 +318,9 @@ async function findOpenThreadByChannelId(channelId) {
* @returns {Promise<Thread>} * @returns {Promise<Thread>}
*/ */
async function findSuspendedThreadByChannelId(channelId) { async function findSuspendedThreadByChannelId(channelId) {
const thread = await knex('threads') const thread = await knex("threads")
.where('channel_id', channelId) .where("channel_id", channelId)
.where('status', THREAD_STATUS.SUSPENDED) .where("status", THREAD_STATUS.SUSPENDED)
.first(); .first();
return (thread ? new Thread(thread) : null); return (thread ? new Thread(thread) : null);
@ -331,9 +331,9 @@ async function findSuspendedThreadByChannelId(channelId) {
* @returns {Promise<Thread[]>} * @returns {Promise<Thread[]>}
*/ */
async function getClosedThreadsByUserId(userId) { async function getClosedThreadsByUserId(userId) {
const threads = await knex('threads') const threads = await knex("threads")
.where('status', THREAD_STATUS.CLOSED) .where("status", THREAD_STATUS.CLOSED)
.where('user_id', userId) .where("user_id", userId)
.select(); .select();
return threads.map(thread => new Thread(thread)); return threads.map(thread => new Thread(thread));
@ -344,10 +344,10 @@ async function getClosedThreadsByUserId(userId) {
* @returns {Promise<number>} * @returns {Promise<number>}
*/ */
async function getClosedThreadCountByUserId(userId) { async function getClosedThreadCountByUserId(userId) {
const row = await knex('threads') const row = await knex("threads")
.where('status', THREAD_STATUS.CLOSED) .where("status", THREAD_STATUS.CLOSED)
.where('user_id', userId) .where("user_id", userId)
.first(knex.raw('COUNT(id) AS thread_count')); .first(knex.raw("COUNT(id) AS thread_count"));
return parseInt(row.thread_count, 10); return parseInt(row.thread_count, 10);
} }
@ -365,24 +365,24 @@ async function findOrCreateThreadForUser(user, opts = {}) {
} }
async function getThreadsThatShouldBeClosed() { async function getThreadsThatShouldBeClosed() {
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss'); const now = moment.utc().format("YYYY-MM-DD HH:mm:ss");
const threads = await knex('threads') const threads = await knex("threads")
.where('status', THREAD_STATUS.OPEN) .where("status", THREAD_STATUS.OPEN)
.whereNotNull('scheduled_close_at') .whereNotNull("scheduled_close_at")
.where('scheduled_close_at', '<=', now) .where("scheduled_close_at", "<=", now)
.whereNotNull('scheduled_close_at') .whereNotNull("scheduled_close_at")
.select(); .select();
return threads.map(thread => new Thread(thread)); return threads.map(thread => new Thread(thread));
} }
async function getThreadsThatShouldBeSuspended() { async function getThreadsThatShouldBeSuspended() {
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss'); const now = moment.utc().format("YYYY-MM-DD HH:mm:ss");
const threads = await knex('threads') const threads = await knex("threads")
.where('status', THREAD_STATUS.OPEN) .where("status", THREAD_STATUS.OPEN)
.whereNotNull('scheduled_suspend_at') .whereNotNull("scheduled_suspend_at")
.where('scheduled_suspend_at', '<=', now) .where("scheduled_suspend_at", "<=", now)
.whereNotNull('scheduled_suspend_at') .whereNotNull("scheduled_suspend_at")
.select(); .select();
return threads.map(thread => new Thread(thread)); return threads.map(thread => new Thread(thread));

View File

@ -1,16 +1,16 @@
const url = require('url'); const url = require("url");
const https = require('https'); const https = require("https");
const moment = require('moment'); const moment = require("moment");
const knex = require('../knex'); const knex = require("../knex");
const config = require('../cfg'); const config = require("../cfg");
const UPDATE_CHECK_FREQUENCY = 12; // In hours const UPDATE_CHECK_FREQUENCY = 12; // In hours
let updateCheckPromise = null; let updateCheckPromise = null;
async function initUpdatesTable() { async function initUpdatesTable() {
const row = await knex('updates').first(); const row = await knex("updates").first();
if (! row) { if (! row) {
await knex('updates').insert({ await knex("updates").insert({
available_version: null, available_version: null,
last_checked: null, last_checked: null,
}); });
@ -24,48 +24,48 @@ async function initUpdatesTable() {
*/ */
async function refreshVersions() { async function refreshVersions() {
await initUpdatesTable(); await initUpdatesTable();
const { last_checked } = await knex('updates').first(); const { last_checked } = await knex("updates").first();
// Only refresh available version if it's been more than UPDATE_CHECK_FREQUENCY since our last check // Only refresh available version if it's been more than UPDATE_CHECK_FREQUENCY since our last check
if (last_checked != null && last_checked > moment.utc().subtract(UPDATE_CHECK_FREQUENCY, 'hours').format('YYYY-MM-DD HH:mm:ss')) return; if (last_checked != null && last_checked > moment.utc().subtract(UPDATE_CHECK_FREQUENCY, "hours").format("YYYY-MM-DD HH:mm:ss")) return;
const packageJson = require('../../package.json'); const packageJson = require("../../package.json");
const repositoryUrl = packageJson.repository && packageJson.repository.url; const repositoryUrl = packageJson.repository && packageJson.repository.url;
if (! repositoryUrl) return; if (! repositoryUrl) return;
const parsedUrl = url.parse(repositoryUrl); const parsedUrl = url.parse(repositoryUrl);
if (parsedUrl.hostname !== 'github.com') return; if (parsedUrl.hostname !== "github.com") return;
const [, owner, repo] = parsedUrl.pathname.split('/'); const [, owner, repo] = parsedUrl.pathname.split("/");
if (! owner || ! repo) return; if (! owner || ! repo) return;
https.get( https.get(
{ {
hostname: 'api.github.com', hostname: "api.github.com",
path: `/repos/${owner}/${repo}/tags`, path: `/repos/${owner}/${repo}/tags`,
headers: { headers: {
'User-Agent': `Modmail Bot (https://github.com/${owner}/${repo}) (${packageJson.version})` "User-Agent": `Modmail Bot (https://github.com/${owner}/${repo}) (${packageJson.version})`
} }
}, },
async res => { async res => {
if (res.statusCode !== 200) { if (res.statusCode !== 200) {
await knex('updates').update({ await knex("updates").update({
last_checked: moment.utc().format('YYYY-MM-DD HH:mm:ss') last_checked: moment.utc().format("YYYY-MM-DD HH:mm:ss")
}); });
console.warn(`[WARN] Got status code ${res.statusCode} when checking for available updates`); console.warn(`[WARN] Got status code ${res.statusCode} when checking for available updates`);
return; return;
} }
let data = ''; let data = "";
res.on('data', chunk => data += chunk); res.on("data", chunk => data += chunk);
res.on('end', async () => { res.on("end", async () => {
const parsed = JSON.parse(data); const parsed = JSON.parse(data);
if (! Array.isArray(parsed) || parsed.length === 0) return; if (! Array.isArray(parsed) || parsed.length === 0) return;
const latestVersion = parsed[0].name; const latestVersion = parsed[0].name;
await knex('updates').update({ await knex("updates").update({
available_version: latestVersion, available_version: latestVersion,
last_checked: moment.utc().format('YYYY-MM-DD HH:mm:ss') last_checked: moment.utc().format("YYYY-MM-DD HH:mm:ss")
}); });
}); });
} }
@ -78,11 +78,11 @@ async function refreshVersions() {
* @returns {Number} 1 if version a is larger than b, -1 is version a is smaller than b, 0 if they are equal * @returns {Number} 1 if version a is larger than b, -1 is version a is smaller than b, 0 if they are equal
*/ */
function compareVersions(a, b) { function compareVersions(a, b) {
const aParts = a.split('.'); const aParts = a.split(".");
const bParts = b.split('.'); const bParts = b.split(".");
for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) { for (let i = 0; i < Math.max(aParts.length, bParts.length); i++) {
let aPart = parseInt((aParts[i] || '0').match(/\d+/)[0] || '0', 10); let aPart = parseInt((aParts[i] || "0").match(/\d+/)[0] || "0", 10);
let bPart = parseInt((bParts[i] || '0').match(/\d+/)[0] || '0', 10); let bPart = parseInt((bParts[i] || "0").match(/\d+/)[0] || "0", 10);
if (aPart > bPart) return 1; if (aPart > bPart) return 1;
if (aPart < bPart) return -1; if (aPart < bPart) return -1;
} }
@ -92,9 +92,9 @@ function compareVersions(a, b) {
async function getAvailableUpdate() { async function getAvailableUpdate() {
await initUpdatesTable(); await initUpdatesTable();
const packageJson = require('../../package.json'); const packageJson = require("../../package.json");
const currentVersion = packageJson.version; const currentVersion = packageJson.version;
const { available_version: availableVersion } = await knex('updates').first(); const { available_version: availableVersion } = await knex("updates").first();
if (availableVersion == null) return null; if (availableVersion == null) return null;
if (currentVersion == null) return availableVersion; if (currentVersion == null) return availableVersion;

View File

@ -1,7 +1,7 @@
const Eris = require('eris'); const Eris = require("eris");
const utils = require('./utils'); const utils = require("./utils");
const config = require('./cfg'); const config = require("./cfg");
const ThreadMessage = require('./data/ThreadMessage'); const ThreadMessage = require("./data/ThreadMessage");
/** /**
* Function to format the DM that is sent to the user when a staff member replies to them via !reply * Function to format the DM that is sent to the user when a staff member replies to them via !reply
@ -116,7 +116,7 @@ const defaultFormatters = {
const mainRole = utils.getMainRole(moderator); const mainRole = utils.getMainRole(moderator);
const modName = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username); const modName = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username);
const modInfo = opts.isAnonymous const modInfo = opts.isAnonymous
? (mainRole ? mainRole.name : 'Moderator') ? (mainRole ? mainRole.name : "Moderator")
: (mainRole ? `(${mainRole.name}) ${modName}` : modName); : (mainRole ? `(${mainRole.name}) ${modName}` : modName);
return `**${modInfo}:** ${text}`; return `**${modInfo}:** ${text}`;
@ -126,7 +126,7 @@ const defaultFormatters = {
const mainRole = utils.getMainRole(moderator); const mainRole = utils.getMainRole(moderator);
const modName = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username); const modName = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username);
const modInfo = opts.isAnonymous const modInfo = opts.isAnonymous
? `(Anonymous) (${modName}) ${mainRole ? mainRole.name : 'Moderator'}` ? `(Anonymous) (${modName}) ${mainRole ? mainRole.name : "Moderator"}`
: (mainRole ? `(${mainRole.name}) ${modName}` : modName); : (mainRole ? `(${mainRole.name}) ${modName}` : modName);
let result = `**${modInfo}:** ${text}`; let result = `**${modInfo}:** ${text}`;
@ -147,13 +147,13 @@ const defaultFormatters = {
// Mirroring the DM formatting here... // Mirroring the DM formatting here...
const modInfo = opts.isAnonymous const modInfo = opts.isAnonymous
? (mainRole ? mainRole.name : 'Moderator') ? (mainRole ? mainRole.name : "Moderator")
: (mainRole ? `(${mainRole.name}) ${modName}` : modName); : (mainRole ? `(${mainRole.name}) ${modName}` : modName);
let result = `**${modInfo}:** ${text}`; let result = `**${modInfo}:** ${text}`;
if (opts.attachmentLinks && opts.attachmentLinks.length) { if (opts.attachmentLinks && opts.attachmentLinks.length) {
result += '\n'; result += "\n";
for (const link of opts.attachmentLinks) { for (const link of opts.attachmentLinks) {
result += `\n**Attachment:** ${link}`; result += `\n**Attachment:** ${link}`;
} }
@ -165,8 +165,8 @@ const defaultFormatters = {
}, },
formatUserReplyThreadMessage(user, msg, opts = {}) { formatUserReplyThreadMessage(user, msg, opts = {}) {
const content = (msg.content.trim() === '' && msg.embeds.length) const content = (msg.content.trim() === "" && msg.embeds.length)
? '<message contains embeds>' ? "<message contains embeds>"
: msg.content; : msg.content;
let result = `**${user.username}#${user.discriminator}:** ${content}`; let result = `**${user.username}#${user.discriminator}:** ${content}`;
@ -178,7 +178,7 @@ const defaultFormatters = {
} }
if (config.threadTimestamps) { if (config.threadTimestamps) {
const formattedTimestamp = utils.getTimestamp(msg.timestamp, 'x'); const formattedTimestamp = utils.getTimestamp(msg.timestamp, "x");
result = `[${formattedTimestamp}] ${result}`; result = `[${formattedTimestamp}] ${result}`;
} }
@ -186,8 +186,8 @@ const defaultFormatters = {
}, },
formatUserReplyLogMessage(user, msg, opts = {}) { formatUserReplyLogMessage(user, msg, opts = {}) {
const content = (msg.content.trim() === '' && msg.embeds.length) const content = (msg.content.trim() === "" && msg.embeds.length)
? '<message contains embeds>' ? "<message contains embeds>"
: msg.content; : msg.content;
let result = content; let result = content;

View File

@ -1,4 +1,4 @@
const Eris = require('eris'); const Eris = require("eris");
/** /**
* @callback BeforeNewThreadHook_SetCategoryId * @callback BeforeNewThreadHook_SetCategoryId

View File

@ -1,25 +1,25 @@
// Verify NodeJS version // Verify NodeJS version
const nodeMajorVersion = parseInt(process.versions.node.split('.')[0], 10); const nodeMajorVersion = parseInt(process.versions.node.split(".")[0], 10);
if (nodeMajorVersion < 11) { if (nodeMajorVersion < 11) {
console.error('Unsupported NodeJS version! Please install Node.js 11, 12, 13, or 14.'); console.error("Unsupported NodeJS version! Please install Node.js 11, 12, 13, or 14.");
process.exit(1); process.exit(1);
} }
// Verify node modules have been installed // Verify node modules have been installed
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
try { try {
fs.accessSync(path.join(__dirname, '..', 'node_modules')); fs.accessSync(path.join(__dirname, "..", "node_modules"));
} catch (e) { } catch (e) {
console.error('Please run "npm ci" before starting the bot'); console.error("Please run \"npm ci\" before starting the bot");
process.exit(1); process.exit(1);
} }
// Error handling // Error handling
process.on('uncaughtException', err => { process.on("uncaughtException", err => {
// Unknown message types (nitro boosting messages at the time) should be safe to ignore // Unknown message types (nitro boosting messages at the time) should be safe to ignore
if (err && err.message && err.message.startsWith('Unhandled MESSAGE_CREATE type')) { if (err && err.message && err.message.startsWith("Unhandled MESSAGE_CREATE type")) {
return; return;
} }
@ -28,27 +28,27 @@ process.on('uncaughtException', err => {
process.exit(1); process.exit(1);
}); });
let testedPackage = ''; let testedPackage = "";
try { try {
const packageJson = require('../package.json'); const packageJson = require("../package.json");
const modules = Object.keys(packageJson.dependencies); const modules = Object.keys(packageJson.dependencies);
modules.forEach(mod => { modules.forEach(mod => {
testedPackage = mod; testedPackage = mod;
fs.accessSync(path.join(__dirname, '..', 'node_modules', mod)) fs.accessSync(path.join(__dirname, "..", "node_modules", mod))
}); });
} catch (e) { } catch (e) {
console.error(`Please run "npm ci" again! Package "${testedPackage}" is missing.`); console.error(`Please run "npm ci" again! Package "${testedPackage}" is missing.`);
process.exit(1); process.exit(1);
} }
const config = require('./cfg'); const config = require("./cfg");
const utils = require('./utils'); const utils = require("./utils");
const main = require('./main'); const main = require("./main");
const knex = require('./knex'); const knex = require("./knex");
const legacyMigrator = require('./legacy/legacyMigrator'); const legacyMigrator = require("./legacy/legacyMigrator");
// Force crash on unhandled rejections (use something like forever/pm2 to restart) // Force crash on unhandled rejections (use something like forever/pm2 to restart)
process.on('unhandledRejection', err => { process.on("unhandledRejection", err => {
if (err instanceof utils.BotError || (err && err.code)) { if (err instanceof utils.BotError || (err && err.code)) {
// We ignore stack traces for BotErrors (the message has enough info) and network errors from Eris (their stack traces are unreadably long) // We ignore stack traces for BotErrors (the message has enough info) and network errors from Eris (their stack traces are unreadably long)
console.error(`Error: ${err.message}`); console.error(`Error: ${err.message}`);
@ -63,34 +63,34 @@ process.on('unhandledRejection', err => {
// Make sure the database is up to date // Make sure the database is up to date
const [completed, newMigrations] = await knex.migrate.list(); const [completed, newMigrations] = await knex.migrate.list();
if (newMigrations.length > 0) { if (newMigrations.length > 0) {
console.log('Updating database. This can take a while. Don\'t close the bot!'); console.log("Updating database. This can take a while. Don't close the bot!");
await knex.migrate.latest(); await knex.migrate.latest();
console.log('Done!'); console.log("Done!");
} }
// Migrate legacy data if we need to // Migrate legacy data if we need to
if (await legacyMigrator.shouldMigrate()) { if (await legacyMigrator.shouldMigrate()) {
console.log('=== MIGRATING LEGACY DATA ==='); console.log("=== MIGRATING LEGACY DATA ===");
console.log('Do not close the bot!'); console.log("Do not close the bot!");
console.log(''); console.log("");
await legacyMigrator.migrate(); await legacyMigrator.migrate();
const relativeDbDir = (path.isAbsolute(config.dbDir) ? config.dbDir : path.resolve(process.cwd(), config.dbDir)); const relativeDbDir = (path.isAbsolute(config.dbDir) ? config.dbDir : path.resolve(process.cwd(), config.dbDir));
const relativeLogDir = (path.isAbsolute(config.logDir) ? config.logDir : path.resolve(process.cwd(), config.logDir)); const relativeLogDir = (path.isAbsolute(config.logDir) ? config.logDir : path.resolve(process.cwd(), config.logDir));
console.log(''); console.log("");
console.log('=== LEGACY DATA MIGRATION FINISHED ==='); console.log("=== LEGACY DATA MIGRATION FINISHED ===");
console.log(''); console.log("");
console.log('IMPORTANT: After the bot starts, please verify that all logs, threads, blocked users, and snippets are still working correctly.'); console.log("IMPORTANT: After the bot starts, please verify that all logs, threads, blocked users, and snippets are still working correctly.");
console.log('Once you\'ve done that, the following files/directories are no longer needed. I would recommend keeping a backup of them, however.'); console.log("Once you've done that, the following files/directories are no longer needed. I would recommend keeping a backup of them, however.");
console.log(''); console.log("");
console.log('FILE: ' + path.resolve(relativeDbDir, 'threads.json')); console.log("FILE: " + path.resolve(relativeDbDir, "threads.json"));
console.log('FILE: ' + path.resolve(relativeDbDir, 'blocked.json')); console.log("FILE: " + path.resolve(relativeDbDir, "blocked.json"));
console.log('FILE: ' + path.resolve(relativeDbDir, 'snippets.json')); console.log("FILE: " + path.resolve(relativeDbDir, "snippets.json"));
console.log('DIRECTORY: ' + relativeLogDir); console.log("DIRECTORY: " + relativeLogDir);
console.log(''); console.log("");
console.log('Starting the bot...'); console.log("Starting the bot...");
} }
// Start the bot // Start the bot

View File

@ -1,2 +1,2 @@
const config = require('./cfg'); const config = require("./cfg");
module.exports = require('knex')(config.knex); module.exports = require("knex")(config.knex);

View File

@ -1,6 +1,6 @@
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const config = require('../cfg'); const config = require("../cfg");
const dbDir = config.dbDir; const dbDir = config.dbDir;
@ -15,7 +15,7 @@ class JSONDB {
this.useCloneByDefault = useCloneByDefault; this.useCloneByDefault = useCloneByDefault;
this.load = new Promise(resolve => { this.load = new Promise(resolve => {
fs.readFile(path, {encoding: 'utf8'}, (err, data) => { fs.readFile(path, {encoding: "utf8"}, (err, data) => {
if (err) return resolve(def); if (err) return resolve(def);
let unserialized; let unserialized;
@ -39,7 +39,7 @@ class JSONDB {
save(newData) { save(newData) {
const serialized = JSON.stringify(newData); const serialized = JSON.stringify(newData);
this.load = new Promise((resolve, reject) => { this.load = new Promise((resolve, reject) => {
fs.writeFile(this.path, serialized, {encoding: 'utf8'}, () => { fs.writeFile(this.path, serialized, {encoding: "utf8"}, () => {
resolve(newData); resolve(newData);
}); });
}); });

View File

@ -1,15 +1,15 @@
const fs = require('fs'); const fs = require("fs");
const path = require('path'); const path = require("path");
const promisify = require('util').promisify; const promisify = require("util").promisify;
const moment = require('moment'); const moment = require("moment");
const Eris = require('eris'); const Eris = require("eris");
const knex = require('../knex'); const knex = require("../knex");
const config = require('../cfg'); const config = require("../cfg");
const jsonDb = require('./jsonDb'); const jsonDb = require("./jsonDb");
const threads = require('../data/threads'); const threads = require("../data/threads");
const {THREAD_STATUS, THREAD_MESSAGE_TYPE} = require('../data/constants'); const {THREAD_STATUS, THREAD_MESSAGE_TYPE} = require("../data/constants");
const readDir = promisify(fs.readdir); const readDir = promisify(fs.readdir);
const readFile = promisify(fs.readFile); const readFile = promisify(fs.readFile);
@ -17,43 +17,43 @@ const access = promisify(fs.access);
const writeFile = promisify(fs.writeFile); const writeFile = promisify(fs.writeFile);
async function migrate() { async function migrate() {
console.log('Migrating open threads...'); console.log("Migrating open threads...");
await migrateOpenThreads(); await migrateOpenThreads();
console.log('Migrating logs...'); console.log("Migrating logs...");
await migrateLogs(); await migrateLogs();
console.log('Migrating blocked users...'); console.log("Migrating blocked users...");
await migrateBlockedUsers(); await migrateBlockedUsers();
console.log('Migrating snippets...'); console.log("Migrating snippets...");
await migrateSnippets(); await migrateSnippets();
await writeFile(path.join(config.dbDir, '.migrated_legacy'), ''); await writeFile(path.join(config.dbDir, ".migrated_legacy"), "");
} }
async function shouldMigrate() { async function shouldMigrate() {
// If there is a file marking a finished migration, assume we don't need to migrate // If there is a file marking a finished migration, assume we don't need to migrate
const migrationFile = path.join(config.dbDir, '.migrated_legacy'); const migrationFile = path.join(config.dbDir, ".migrated_legacy");
try { try {
await access(migrationFile); await access(migrationFile);
return false; return false;
} catch (e) {} } catch (e) {}
// If there are any old threads, we need to migrate // If there are any old threads, we need to migrate
const oldThreads = await jsonDb.get('threads', []); const oldThreads = await jsonDb.get("threads", []);
if (oldThreads.length) { if (oldThreads.length) {
return true; return true;
} }
// If there are any old blocked users, we need to migrate // If there are any old blocked users, we need to migrate
const blockedUsers = await jsonDb.get('blocked', []); const blockedUsers = await jsonDb.get("blocked", []);
if (blockedUsers.length) { if (blockedUsers.length) {
return true; return true;
} }
// If there are any old snippets, we need to migrate // If there are any old snippets, we need to migrate
const snippets = await jsonDb.get('snippets', {}); const snippets = await jsonDb.get("snippets", {});
if (Object.keys(snippets).length) { if (Object.keys(snippets).length) {
return true; return true;
} }
@ -71,12 +71,12 @@ async function migrateOpenThreads() {
const bot = new Eris.Client(config.token); const bot = new Eris.Client(config.token);
const toReturn = new Promise(resolve => { const toReturn = new Promise(resolve => {
bot.on('ready', async () => { bot.on("ready", async () => {
const oldThreads = await jsonDb.get('threads', []); const oldThreads = await jsonDb.get("threads", []);
const promises = oldThreads.map(async oldThread => { const promises = oldThreads.map(async oldThread => {
const existingOpenThread = await knex('threads') const existingOpenThread = await knex("threads")
.where('channel_id', oldThread.channelId) .where("channel_id", oldThread.channelId)
.first(); .first();
if (existingOpenThread) return; if (existingOpenThread) return;
@ -86,9 +86,9 @@ async function migrateOpenThreads() {
const threadMessages = await oldChannel.getMessages(1000); const threadMessages = await oldChannel.getMessages(1000);
const log = threadMessages.reverse().map(msg => { const log = threadMessages.reverse().map(msg => {
const date = moment.utc(msg.timestamp, 'x').format('YYYY-MM-DD HH:mm:ss'); const date = moment.utc(msg.timestamp, "x").format("YYYY-MM-DD HH:mm:ss");
return `[${date}] ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`; return `[${date}] ${msg.author.username}#${msg.author.discriminator}: ${msg.content}`;
}).join('\n') + '\n'; }).join("\n") + "\n";
const newThread = { const newThread = {
status: THREAD_STATUS.OPEN, status: THREAD_STATUS.OPEN,
@ -100,14 +100,14 @@ async function migrateOpenThreads() {
const threadId = await threads.createThreadInDB(newThread); const threadId = await threads.createThreadInDB(newThread);
await knex('thread_messages').insert({ await knex("thread_messages").insert({
thread_id: threadId, thread_id: threadId,
message_type: THREAD_MESSAGE_TYPE.LEGACY, message_type: THREAD_MESSAGE_TYPE.LEGACY,
user_id: oldThread.userId, user_id: oldThread.userId,
user_name: '', user_name: "",
body: log, body: log,
is_anonymous: 0, is_anonymous: 0,
created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss') created_at: moment.utc().format("YYYY-MM-DD HH:mm:ss")
}); });
}); });
@ -128,38 +128,38 @@ async function migrateLogs() {
for (let i = 0; i < logFiles.length; i++) { for (let i = 0; i < logFiles.length; i++) {
const logFile = logFiles[i]; const logFile = logFiles[i];
if (! logFile.endsWith('.txt')) continue; if (! logFile.endsWith(".txt")) continue;
const [rawDate, userId, threadId] = logFile.slice(0, -4).split('__'); const [rawDate, userId, threadId] = logFile.slice(0, -4).split("__");
const date = `${rawDate.slice(0, 10)} ${rawDate.slice(11).replace('-', ':')}`; const date = `${rawDate.slice(0, 10)} ${rawDate.slice(11).replace("-", ":")}`;
const fullPath = path.join(logDir, logFile); const fullPath = path.join(logDir, logFile);
const contents = await readFile(fullPath, {encoding: 'utf8'}); const contents = await readFile(fullPath, {encoding: "utf8"});
const newThread = { const newThread = {
id: threadId, id: threadId,
status: THREAD_STATUS.CLOSED, status: THREAD_STATUS.CLOSED,
user_id: userId, user_id: userId,
user_name: '', user_name: "",
channel_id: null, channel_id: null,
is_legacy: 1, is_legacy: 1,
created_at: date created_at: date
}; };
await knex.transaction(async trx => { await knex.transaction(async trx => {
const existingThread = await trx('threads') const existingThread = await trx("threads")
.where('id', newThread.id) .where("id", newThread.id)
.first(); .first();
if (existingThread) return; if (existingThread) return;
await trx('threads').insert(newThread); await trx("threads").insert(newThread);
await trx('thread_messages').insert({ await trx("thread_messages").insert({
thread_id: newThread.id, thread_id: newThread.id,
message_type: THREAD_MESSAGE_TYPE.LEGACY, message_type: THREAD_MESSAGE_TYPE.LEGACY,
user_id: userId, user_id: userId,
user_name: '', user_name: "",
body: contents, body: contents,
is_anonymous: 0, is_anonymous: 0,
created_at: date created_at: date
@ -174,19 +174,19 @@ async function migrateLogs() {
} }
async function migrateBlockedUsers() { async function migrateBlockedUsers() {
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss'); const now = moment.utc().format("YYYY-MM-DD HH:mm:ss");
const blockedUsers = await jsonDb.get('blocked', []); const blockedUsers = await jsonDb.get("blocked", []);
for (const userId of blockedUsers) { for (const userId of blockedUsers) {
const existingBlockedUser = await knex('blocked_users') const existingBlockedUser = await knex("blocked_users")
.where('user_id', userId) .where("user_id", userId)
.first(); .first();
if (existingBlockedUser) return; if (existingBlockedUser) return;
await knex('blocked_users').insert({ await knex("blocked_users").insert({
user_id: userId, user_id: userId,
user_name: '', user_name: "",
blocked_by: null, blocked_by: null,
blocked_at: now blocked_at: now
}); });
@ -194,17 +194,17 @@ async function migrateBlockedUsers() {
} }
async function migrateSnippets() { async function migrateSnippets() {
const now = moment.utc().format('YYYY-MM-DD HH:mm:ss'); const now = moment.utc().format("YYYY-MM-DD HH:mm:ss");
const snippets = await jsonDb.get('snippets', {}); const snippets = await jsonDb.get("snippets", {});
const promises = Object.entries(snippets).map(async ([trigger, data]) => { const promises = Object.entries(snippets).map(async ([trigger, data]) => {
const existingSnippet = await knex('snippets') const existingSnippet = await knex("snippets")
.where('trigger', trigger) .where("trigger", trigger)
.first(); .first();
if (existingSnippet) return; if (existingSnippet) return;
return knex('snippets').insert({ return knex("snippets").insert({
trigger, trigger,
body: data.text, body: data.text,
is_anonymous: data.isAnonymous ? 1 : 0, is_anonymous: data.isAnonymous ? 1 : 0,

View File

@ -1,58 +1,58 @@
const Eris = require('eris'); const Eris = require("eris");
const path = require('path'); const path = require("path");
const config = require('./cfg'); const config = require("./cfg");
const bot = require('./bot'); const bot = require("./bot");
const knex = require('./knex'); const knex = require("./knex");
const {messageQueue} = require('./queue'); const {messageQueue} = require("./queue");
const utils = require('./utils'); const utils = require("./utils");
const { createCommandManager } = require('./commands'); const { createCommandManager } = require("./commands");
const { getPluginAPI, loadPlugin } = require('./plugins'); const { getPluginAPI, loadPlugin } = require("./plugins");
const { callBeforeNewThreadHooks } = require('./hooks/beforeNewThread'); const { callBeforeNewThreadHooks } = require("./hooks/beforeNewThread");
const blocked = require('./data/blocked'); const blocked = require("./data/blocked");
const threads = require('./data/threads'); const threads = require("./data/threads");
const updates = require('./data/updates'); const updates = require("./data/updates");
const reply = require('./modules/reply'); const reply = require("./modules/reply");
const close = require('./modules/close'); const close = require("./modules/close");
const snippets = require('./modules/snippets'); const snippets = require("./modules/snippets");
const logs = require('./modules/logs'); const logs = require("./modules/logs");
const move = require('./modules/move'); const move = require("./modules/move");
const block = require('./modules/block'); const block = require("./modules/block");
const suspend = require('./modules/suspend'); const suspend = require("./modules/suspend");
const webserver = require('./modules/webserver'); const webserver = require("./modules/webserver");
const greeting = require('./modules/greeting'); const greeting = require("./modules/greeting");
const typingProxy = require('./modules/typingProxy'); const typingProxy = require("./modules/typingProxy");
const version = require('./modules/version'); const version = require("./modules/version");
const newthread = require('./modules/newthread'); const newthread = require("./modules/newthread");
const idModule = require('./modules/id'); const idModule = require("./modules/id");
const alert = require('./modules/alert'); const alert = require("./modules/alert");
const {ACCIDENTAL_THREAD_MESSAGES} = require('./data/constants'); const {ACCIDENTAL_THREAD_MESSAGES} = require("./data/constants");
module.exports = { module.exports = {
async start() { async start() {
console.log('Connecting to Discord...'); console.log("Connecting to Discord...");
bot.once('ready', async () => { bot.once("ready", async () => {
console.log('Connected! Waiting for guilds to become available...'); console.log("Connected! Waiting for guilds to become available...");
await Promise.all([ await Promise.all([
...config.mainGuildId.map(id => waitForGuild(id)), ...config.mainGuildId.map(id => waitForGuild(id)),
waitForGuild(config.mailGuildId) waitForGuild(config.mailGuildId)
]); ]);
console.log('Initializing...'); console.log("Initializing...");
initStatus(); initStatus();
initBaseMessageHandlers(); initBaseMessageHandlers();
console.log('Loading plugins...'); console.log("Loading plugins...");
const pluginResult = await initPlugins(); const pluginResult = await initPlugins();
console.log(`Loaded ${pluginResult.loadedCount} plugins (${pluginResult.builtInCount} built-in plugins, ${pluginResult.externalCount} external plugins)`); console.log(`Loaded ${pluginResult.loadedCount} plugins (${pluginResult.builtInCount} built-in plugins, ${pluginResult.externalCount} external plugins)`);
console.log(''); console.log("");
console.log('Done! Now listening to DMs.'); console.log("Done! Now listening to DMs.");
console.log(''); console.log("");
}); });
bot.connect(); bot.connect();
@ -65,7 +65,7 @@ function waitForGuild(guildId) {
} }
return new Promise(resolve => { return new Promise(resolve => {
bot.on('guildAvailable', guild => { bot.on("guildAvailable", guild => {
if (guild.id === guildId) { if (guild.id === guildId) {
resolve(); resolve();
} }
@ -89,7 +89,7 @@ function initBaseMessageHandlers() {
* 1) If alwaysReply is enabled, reply to the user * 1) If alwaysReply is enabled, reply to the user
* 2) If alwaysReply is disabled, save that message as a chat message in the thread * 2) If alwaysReply is disabled, save that message as a chat message in the thread
*/ */
bot.on('messageCreate', async msg => { bot.on("messageCreate", async msg => {
if (! utils.messageIsOnInboxServer(msg)) return; if (! utils.messageIsOnInboxServer(msg)) return;
if (msg.author.bot) return; if (msg.author.bot) return;
@ -116,7 +116,7 @@ function initBaseMessageHandlers() {
* 1) Find the open modmail thread for this user, or create a new one * 1) Find the open modmail thread for this user, or create a new one
* 2) Post the message as a user reply in the thread * 2) Post the message as a user reply in the thread
*/ */
bot.on('messageCreate', async msg => { bot.on("messageCreate", async msg => {
if (! (msg.channel instanceof Eris.PrivateChannel)) return; if (! (msg.channel instanceof Eris.PrivateChannel)) return;
if (msg.author.bot) return; if (msg.author.bot) return;
if (msg.type !== 0) return; // Ignore pins etc. if (msg.type !== 0) return; // Ignore pins etc.
@ -133,7 +133,7 @@ function initBaseMessageHandlers() {
if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return; if (config.ignoreAccidentalThreads && msg.content && ACCIDENTAL_THREAD_MESSAGES.includes(msg.content.trim().toLowerCase())) return;
thread = await threads.createNewThreadForUser(msg.author, { thread = await threads.createNewThreadForUser(msg.author, {
source: 'dm', source: "dm",
}); });
} }
@ -148,13 +148,13 @@ function initBaseMessageHandlers() {
* 1) If that message was in DMs, and we have a thread open with that user, post the edit as a system message in the thread * 1) If that message was in DMs, and we have a thread open with that user, post the edit as a system message in the thread
* 2) If that message was moderator chatter in the thread, update the corresponding chat message in the DB * 2) If that message was moderator chatter in the thread, update the corresponding chat message in the DB
*/ */
bot.on('messageUpdate', async (msg, oldMessage) => { bot.on("messageUpdate", async (msg, oldMessage) => {
if (! msg || ! msg.author) return; if (! msg || ! msg.author) return;
if (msg.author.bot) return; if (msg.author.bot) return;
if (await blocked.isBlocked(msg.author.id)) return; if (await blocked.isBlocked(msg.author.id)) return;
// Old message content doesn't persist between bot restarts // Old message content doesn't persist between bot restarts
const oldContent = oldMessage && oldMessage.content || '*Unavailable due to bot restart*'; const oldContent = oldMessage && oldMessage.content || "*Unavailable due to bot restart*";
const newContent = msg.content; const newContent = msg.content;
// Ignore edit events with changes only in embeds etc. // Ignore edit events with changes only in embeds etc.
@ -181,7 +181,7 @@ function initBaseMessageHandlers() {
/** /**
* When a staff message is deleted in a modmail thread, delete it from the database as well * When a staff message is deleted in a modmail thread, delete it from the database as well
*/ */
bot.on('messageDelete', async msg => { bot.on("messageDelete", async msg => {
if (! msg.author) return; if (! msg.author) return;
if (msg.author.bot) return; if (msg.author.bot) return;
if (! utils.messageIsOnInboxServer(msg)) return; if (! utils.messageIsOnInboxServer(msg)) return;
@ -196,7 +196,7 @@ function initBaseMessageHandlers() {
/** /**
* When the bot is mentioned on the main server, ping staff in the log channel about it * When the bot is mentioned on the main server, ping staff in the log channel about it
*/ */
bot.on('messageCreate', async msg => { bot.on("messageCreate", async msg => {
if (! utils.messageIsOnMainServer(msg)) return; if (! utils.messageIsOnMainServer(msg)) return;
if (! msg.mentions.some(user => user.id === bot.user.id)) return; if (! msg.mentions.some(user => user.id === bot.user.id)) return;
if (msg.author.bot) return; if (msg.author.bot) return;
@ -215,7 +215,7 @@ function initBaseMessageHandlers() {
let content; let content;
const mainGuilds = utils.getMainGuilds(); const mainGuilds = utils.getMainGuilds();
const staffMention = (config.pingOnBotMention ? utils.getInboxMention() : ''); const staffMention = (config.pingOnBotMention ? utils.getInboxMention() : "");
if (mainGuilds.length === 1) { if (mainGuilds.length === 1) {
content = `${staffMention}Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}(${msg.author.id})**: "${msg.cleanContent}"\n\n<https:\/\/discordapp.com\/channels\/${msg.channel.guild.id}\/${msg.channel.id}\/${msg.id}>`; content = `${staffMention}Bot mentioned in ${msg.channel.mention} by **${msg.author.username}#${msg.author.discriminator}(${msg.author.id})**: "${msg.cleanContent}"\n\n<https:\/\/discordapp.com\/channels\/${msg.channel.guild.id}\/${msg.channel.id}\/${msg.id}>`;

View File

@ -1,8 +1,8 @@
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
commands.addInboxThreadCommand('alert', '[opt:string]', async (msg, args, thread) => { commands.addInboxThreadCommand("alert", "[opt:string]", async (msg, args, thread) => {
if (args.opt && args.opt.startsWith('c')) { if (args.opt && args.opt.startsWith("c")) {
await thread.setAlert(null); await thread.setAlert(null);
await thread.postSystemMessage(`Cancelled new message alert`); await thread.postSystemMessage("Cancelled new message alert");
} else { } else {
await thread.setAlert(msg.author.id); await thread.setAlert(msg.author.id);
await thread.postSystemMessage(`Pinging ${msg.author.username}#${msg.author.discriminator} when this thread gets a new reply`); await thread.postSystemMessage(`Pinging ${msg.author.username}#${msg.author.discriminator} when this thread gets a new reply`);

View File

@ -1,5 +1,5 @@
const humanizeDuration = require('humanize-duration'); const humanizeDuration = require("humanize-duration");
const moment = require('moment'); const moment = require("moment");
const blocked = require("../data/blocked"); const blocked = require("../data/blocked");
const utils = require("../utils"); const utils = require("../utils");
@ -23,7 +23,7 @@ module.exports = ({ bot, knex, config, commands }) => {
setTimeout(expiredBlockLoop, 2000); setTimeout(expiredBlockLoop, 2000);
} }
bot.on('ready', expiredBlockLoop); bot.on("ready", expiredBlockLoop);
const blockCmd = async (msg, args, thread) => { const blockCmd = async (msg, args, thread) => {
const userIdToBlock = args.userId || (thread && thread.user_id); const userIdToBlock = args.userId || (thread && thread.user_id);
@ -31,16 +31,16 @@ module.exports = ({ bot, knex, config, commands }) => {
const isBlocked = await blocked.isBlocked(userIdToBlock); const isBlocked = await blocked.isBlocked(userIdToBlock);
if (isBlocked) { if (isBlocked) {
msg.channel.createMessage('User is already blocked'); msg.channel.createMessage("User is already blocked");
return; return;
} }
const expiresAt = args.blockTime const expiresAt = args.blockTime
? moment.utc().add(args.blockTime, 'ms').format('YYYY-MM-DD HH:mm:ss') ? moment.utc().add(args.blockTime, "ms").format("YYYY-MM-DD HH:mm:ss")
: null; : null;
const user = bot.users.get(userIdToBlock); const user = bot.users.get(userIdToBlock);
await blocked.block(userIdToBlock, (user ? `${user.username}#${user.discriminator}` : ''), msg.author.id, expiresAt); await blocked.block(userIdToBlock, (user ? `${user.username}#${user.discriminator}` : ""), msg.author.id, expiresAt);
if (expiresAt) { if (expiresAt) {
const humanized = humanizeDuration(args.blockTime, { largest: 2, round: true }); const humanized = humanizeDuration(args.blockTime, { largest: 2, round: true });
@ -50,8 +50,8 @@ module.exports = ({ bot, knex, config, commands }) => {
} }
}; };
commands.addInboxServerCommand('block', '<userId:userId> [blockTime:delay]', blockCmd); commands.addInboxServerCommand("block", "<userId:userId> [blockTime:delay]", blockCmd);
commands.addInboxServerCommand('block', '[blockTime:delay]', blockCmd); commands.addInboxServerCommand("block", "[blockTime:delay]", blockCmd);
const unblockCmd = async (msg, args, thread) => { const unblockCmd = async (msg, args, thread) => {
const userIdToUnblock = args.userId || (thread && thread.user_id); const userIdToUnblock = args.userId || (thread && thread.user_id);
@ -59,12 +59,12 @@ module.exports = ({ bot, knex, config, commands }) => {
const isBlocked = await blocked.isBlocked(userIdToUnblock); const isBlocked = await blocked.isBlocked(userIdToUnblock);
if (! isBlocked) { if (! isBlocked) {
msg.channel.createMessage('User is not blocked'); msg.channel.createMessage("User is not blocked");
return; return;
} }
const unblockAt = args.unblockDelay const unblockAt = args.unblockDelay
? moment.utc().add(args.unblockDelay, 'ms').format('YYYY-MM-DD HH:mm:ss') ? moment.utc().add(args.unblockDelay, "ms").format("YYYY-MM-DD HH:mm:ss")
: null; : null;
const user = bot.users.get(userIdToUnblock); const user = bot.users.get(userIdToUnblock);
@ -78,10 +78,10 @@ module.exports = ({ bot, knex, config, commands }) => {
} }
}; };
commands.addInboxServerCommand('unblock', '<userId:userId> [unblockDelay:delay]', unblockCmd); commands.addInboxServerCommand("unblock", "<userId:userId> [unblockDelay:delay]", unblockCmd);
commands.addInboxServerCommand('unblock', '[unblockDelay:delay]', unblockCmd); commands.addInboxServerCommand("unblock", "[unblockDelay:delay]", unblockCmd);
commands.addInboxServerCommand('is_blocked', '[userId:userId]',async (msg, args, thread) => { commands.addInboxServerCommand("is_blocked", "[userId:userId]",async (msg, args, thread) => {
const userIdToCheck = args.userId || (thread && thread.user_id); const userIdToCheck = args.userId || (thread && thread.user_id);
if (! userIdToCheck) return; if (! userIdToCheck) return;

View File

@ -1,10 +1,10 @@
const moment = require('moment'); const moment = require("moment");
const Eris = require('eris'); const Eris = require("eris");
const config = require('../cfg'); const config = require("../cfg");
const utils = require('../utils'); const utils = require("../utils");
const threads = require('../data/threads'); const threads = require("../data/threads");
const blocked = require('../data/blocked'); const blocked = require("../data/blocked");
const {messageQueue} = require('../queue'); const {messageQueue} = require("../queue");
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
// Check for threads that are scheduled to be closed and close them // Check for threads that are scheduled to be closed and close them
@ -39,7 +39,7 @@ module.exports = ({ bot, knex, config, commands }) => {
scheduledCloseLoop(); scheduledCloseLoop();
// Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel. // Close a thread. Closing a thread saves a log of the channel's contents and then deletes the channel.
commands.addGlobalCommand('close', '[opts...]', async (msg, args) => { commands.addGlobalCommand("close", "[opts...]", async (msg, args) => {
let thread, closedBy; let thread, closedBy;
let hasCloseMessage = !! config.closeMessage; let hasCloseMessage = !! config.closeMessage;
@ -56,11 +56,11 @@ module.exports = ({ bot, knex, config, commands }) => {
// We need to add this operation to the message queue so we don't get a race condition // We need to add this operation to the message queue so we don't get a race condition
// between showing the close command in the thread and closing the thread // between showing the close command in the thread and closing the thread
await messageQueue.add(async () => { await messageQueue.add(async () => {
thread.postSystemMessage('Thread closed by user, closing...'); thread.postSystemMessage("Thread closed by user, closing...");
await thread.close(true); await thread.close(true);
}); });
closedBy = 'the user'; closedBy = "the user";
} else { } else {
// A staff member is closing the thread // A staff member is closing the thread
if (! utils.messageIsOnInboxServer(msg)) return; if (! utils.messageIsOnInboxServer(msg)) return;
@ -70,18 +70,18 @@ module.exports = ({ bot, knex, config, commands }) => {
if (! thread) return; if (! thread) return;
if (args.opts && args.opts.length) { if (args.opts && args.opts.length) {
if (args.opts.includes('cancel') || args.opts.includes('c')) { if (args.opts.includes("cancel") || args.opts.includes("c")) {
// Cancel timed close // Cancel timed close
if (thread.scheduled_close_at) { if (thread.scheduled_close_at) {
await thread.cancelScheduledClose(); await thread.cancelScheduledClose();
thread.postSystemMessage(`Cancelled scheduled closing`); thread.postSystemMessage("Cancelled scheduled closing");
} }
return; return;
} }
// Silent close (= no close message) // Silent close (= no close message)
if (args.opts.includes('silent') || args.opts.includes('s')) { if (args.opts.includes("silent") || args.opts.includes("s")) {
silentClose = true; silentClose = true;
} }
@ -90,12 +90,12 @@ module.exports = ({ bot, knex, config, commands }) => {
if (delayStringArg) { if (delayStringArg) {
const delay = utils.convertDelayStringToMS(delayStringArg); const delay = utils.convertDelayStringToMS(delayStringArg);
if (delay === 0 || delay === null) { if (delay === 0 || delay === null) {
thread.postSystemMessage(`Invalid delay specified. Format: "1h30m"`); thread.postSystemMessage("Invalid delay specified. Format: \"1h30m\"");
return; return;
} }
const closeAt = moment.utc().add(delay, 'ms'); const closeAt = moment.utc().add(delay, "ms");
await thread.scheduleClose(closeAt.format('YYYY-MM-DD HH:mm:ss'), msg.author, silentClose ? 1 : 0); await thread.scheduleClose(closeAt.format("YYYY-MM-DD HH:mm:ss"), msg.author, silentClose ? 1 : 0);
let response; let response;
if (silentClose) { if (silentClose) {
@ -129,7 +129,7 @@ module.exports = ({ bot, knex, config, commands }) => {
}); });
// Auto-close threads if their channel is deleted // Auto-close threads if their channel is deleted
bot.on('channelDelete', async (channel) => { bot.on("channelDelete", async (channel) => {
if (! (channel instanceof Eris.TextChannel)) return; if (! (channel instanceof Eris.TextChannel)) return;
if (channel.guild.id !== utils.getInboxGuild().id) return; if (channel.guild.id !== utils.getInboxGuild().id) return;

View File

@ -1,12 +1,12 @@
const path = require('path'); const path = require("path");
const fs = require('fs'); const fs = require("fs");
const config = require('../cfg'); const config = require("../cfg");
const utils = require('../utils'); const utils = require("../utils");
module.exports = ({ bot }) => { module.exports = ({ bot }) => {
if (! config.enableGreeting) return; if (! config.enableGreeting) return;
bot.on('guildMemberAdd', (guild, member) => { bot.on("guildMemberAdd", (guild, member) => {
const guildGreeting = config.guildGreetings[guild.id]; const guildGreeting = config.guildGreetings[guild.id];
if (! guildGreeting || (! guildGreeting.message && ! guildGreeting.attachment)) return; if (! guildGreeting || (! guildGreeting.message && ! guildGreeting.attachment)) return;
@ -14,7 +14,7 @@ module.exports = ({ bot }) => {
bot.getDMChannel(member.id).then(channel => { bot.getDMChannel(member.id).then(channel => {
if (! channel) return; if (! channel) return;
channel.createMessage(message || '', file) channel.createMessage(message || "", file)
.catch(e => { .catch(e => {
if (e.code === 50007) return; if (e.code === 50007) return;
throw e; throw e;

View File

@ -1,9 +1,9 @@
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
commands.addInboxThreadCommand('id', [], async (msg, args, thread) => { commands.addInboxThreadCommand("id", [], async (msg, args, thread) => {
thread.postSystemMessage(thread.user_id); thread.postSystemMessage(thread.user_id);
}); });
commands.addInboxThreadCommand('dm_channel_id', [], async (msg, args, thread) => { commands.addInboxThreadCommand("dm_channel_id", [], async (msg, args, thread) => {
const dmChannel = await thread.getDMChannel(); const dmChannel = await thread.getDMChannel();
thread.postSystemMessage(dmChannel.id); thread.postSystemMessage(dmChannel.id);
}); });

View File

@ -1,5 +1,5 @@
const threads = require("../data/threads"); const threads = require("../data/threads");
const moment = require('moment'); const moment = require("moment");
const utils = require("../utils"); const utils = require("../utils");
const LOG_LINES_PER_PAGE = 10; const LOG_LINES_PER_PAGE = 10;
@ -30,7 +30,7 @@ module.exports = ({ bot, knex, config, commands }) => {
const threadLines = await Promise.all(userThreads.map(async thread => { const threadLines = await Promise.all(userThreads.map(async thread => {
const logUrl = await thread.getLogUrl(); const logUrl = await thread.getLogUrl();
const formattedDate = moment.utc(thread.created_at).format('MMM Do [at] HH:mm [UTC]'); const formattedDate = moment.utc(thread.created_at).format("MMM Do [at] HH:mm [UTC]");
return `\`${formattedDate}\`: <${logUrl}>`; return `\`${formattedDate}\`: <${logUrl}>`;
})); }));
@ -38,26 +38,26 @@ module.exports = ({ bot, knex, config, commands }) => {
? `**Log files for <@${userId}>** (page **${page}/${maxPage}**, showing logs **${start + 1}-${end}/${totalUserThreads}**):` ? `**Log files for <@${userId}>** (page **${page}/${maxPage}**, showing logs **${start + 1}-${end}/${totalUserThreads}**):`
: `**Log files for <@${userId}>:**`; : `**Log files for <@${userId}>:**`;
message += `\n${threadLines.join('\n')}`; message += `\n${threadLines.join("\n")}`;
if (isPaginated) { if (isPaginated) {
message += `\nTo view more, add a page number to the end of the command`; message += "\nTo view more, add a page number to the end of the command";
} }
// Send the list of logs in chunks of 15 lines per message // Send the list of logs in chunks of 15 lines per message
const lines = message.split('\n'); const lines = message.split("\n");
const chunks = utils.chunk(lines, 15); const chunks = utils.chunk(lines, 15);
let root = Promise.resolve(); let root = Promise.resolve();
chunks.forEach(lines => { chunks.forEach(lines => {
root = root.then(() => msg.channel.createMessage(lines.join('\n'))); root = root.then(() => msg.channel.createMessage(lines.join("\n")));
}); });
}; };
commands.addInboxServerCommand('logs', '<userId:userId> [page:number]', logsCmd); commands.addInboxServerCommand("logs", "<userId:userId> [page:number]", logsCmd);
commands.addInboxServerCommand('logs', '[page:number]', logsCmd); commands.addInboxServerCommand("logs", "[page:number]", logsCmd);
commands.addInboxServerCommand('loglink', [], async (msg, args, thread) => { commands.addInboxServerCommand("loglink", [], async (msg, args, thread) => {
if (! thread) { if (! thread) {
thread = await threads.findSuspendedThreadByChannelId(msg.channel.id); thread = await threads.findSuspendedThreadByChannelId(msg.channel.id);
if (! thread) return; if (! thread) return;
@ -65,21 +65,21 @@ module.exports = ({ bot, knex, config, commands }) => {
const logUrl = await thread.getLogUrl(); const logUrl = await thread.getLogUrl();
const query = []; const query = [];
if (args.verbose) query.push('verbose=1'); if (args.verbose) query.push("verbose=1");
if (args.simple) query.push('simple=1'); if (args.simple) query.push("simple=1");
let qs = query.length ? `?${query.join('&')}` : ''; let qs = query.length ? `?${query.join("&")}` : "";
thread.postSystemMessage(`Log URL: ${logUrl}${qs}`); thread.postSystemMessage(`Log URL: ${logUrl}${qs}`);
}, { }, {
options: [ options: [
{ {
name: 'verbose', name: "verbose",
shortcut: 'v', shortcut: "v",
isSwitch: true, isSwitch: true,
}, },
{ {
name: 'simple', name: "simple",
shortcut: 's', shortcut: "s",
isSwitch: true, isSwitch: true,
}, },
], ],

View File

@ -1,12 +1,12 @@
const config = require('../cfg'); const config = require("../cfg");
const Eris = require('eris'); const Eris = require("eris");
const transliterate = require("transliteration"); const transliterate = require("transliteration");
const erisEndpoints = require('eris/lib/rest/Endpoints'); const erisEndpoints = require("eris/lib/rest/Endpoints");
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
if (! config.allowMove) return; if (! config.allowMove) return;
commands.addInboxThreadCommand('move', '<category:string$>', async (msg, args, thread) => { commands.addInboxThreadCommand("move", "<category:string$>", async (msg, args, thread) => {
const searchStr = args.category; const searchStr = args.category;
const normalizedSearchStr = transliterate.slugify(searchStr); const normalizedSearchStr = transliterate.slugify(searchStr);
@ -41,7 +41,7 @@ module.exports = ({ bot, knex, config, commands }) => {
}); });
if (containsRankings[0][1] === 0) { if (containsRankings[0][1] === 0) {
thread.postSystemMessage('No matching category'); thread.postSystemMessage("No matching category");
return; return;
} }

View File

@ -2,10 +2,10 @@ const utils = require("../utils");
const threads = require("../data/threads"); const threads = require("../data/threads");
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
commands.addInboxServerCommand('newthread', '<userId:userId>', async (msg, args, thread) => { commands.addInboxServerCommand("newthread", "<userId:userId>", async (msg, args, thread) => {
const user = bot.users.get(args.userId); const user = bot.users.get(args.userId);
if (! user) { if (! user) {
utils.postSystemMessageWithFallback(msg.channel, thread, 'User not found!'); utils.postSystemMessageWithFallback(msg.channel, thread, "User not found!");
return; return;
} }
@ -18,7 +18,7 @@ module.exports = ({ bot, knex, config, commands }) => {
const createdThread = await threads.createNewThreadForUser(user, { const createdThread = await threads.createNewThreadForUser(user, {
quiet: true, quiet: true,
ignoreRequirements: true, ignoreRequirements: true,
source: 'command', source: "command",
}); });
createdThread.postSystemMessage(`Thread was opened by ${msg.author.username}#${msg.author.discriminator}`); createdThread.postSystemMessage(`Thread was opened by ${msg.author.username}#${msg.author.discriminator}`);

View File

@ -1,71 +1,71 @@
const attachments = require("../data/attachments"); const attachments = require("../data/attachments");
const utils = require('../utils'); const utils = require("../utils");
const config = require('../cfg'); const config = require("../cfg");
const Thread = require('../data/Thread'); const Thread = require("../data/Thread");
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
// Mods can reply to modmail threads using !r or !reply // Mods can reply to modmail threads using !r or !reply
// These messages get relayed back to the DM thread between the bot and the user // These messages get relayed back to the DM thread between the bot and the user
commands.addInboxThreadCommand('reply', '[text$]', async (msg, args, thread) => { commands.addInboxThreadCommand("reply", "[text$]", async (msg, args, thread) => {
if (! args.text && msg.attachments.length === 0) { if (! args.text && msg.attachments.length === 0) {
utils.postError(msg.channel, 'Text or attachment required'); utils.postError(msg.channel, "Text or attachment required");
return; return;
} }
const replied = await thread.replyToUser(msg.member, args.text || '', msg.attachments, false); const replied = await thread.replyToUser(msg.member, args.text || "", msg.attachments, false);
if (replied) msg.delete(); if (replied) msg.delete();
}, { }, {
aliases: ['r'] aliases: ["r"]
}); });
// Anonymous replies only show the role, not the username // Anonymous replies only show the role, not the username
commands.addInboxThreadCommand('anonreply', '[text$]', async (msg, args, thread) => { commands.addInboxThreadCommand("anonreply", "[text$]", async (msg, args, thread) => {
if (! args.text && msg.attachments.length === 0) { if (! args.text && msg.attachments.length === 0) {
utils.postError(msg.channel, 'Text or attachment required'); utils.postError(msg.channel, "Text or attachment required");
return; return;
} }
const replied = await thread.replyToUser(msg.member, args.text || '', msg.attachments, true); const replied = await thread.replyToUser(msg.member, args.text || "", msg.attachments, true);
if (replied) msg.delete(); if (replied) msg.delete();
}, { }, {
aliases: ['ar'] aliases: ["ar"]
}); });
if (config.allowStaffEdit) { if (config.allowStaffEdit) {
commands.addInboxThreadCommand('edit', '<messageNumber:number> <text:string$>', async (msg, args, thread) => { commands.addInboxThreadCommand("edit", "<messageNumber:number> <text:string$>", async (msg, args, thread) => {
const threadMessage = await thread.findThreadMessageByMessageNumber(args.messageNumber); const threadMessage = await thread.findThreadMessageByMessageNumber(args.messageNumber);
if (! threadMessage) { if (! threadMessage) {
utils.postError(msg.channel, 'Unknown message number'); utils.postError(msg.channel, "Unknown message number");
return; return;
} }
if (threadMessage.user_id !== msg.author.id) { if (threadMessage.user_id !== msg.author.id) {
utils.postError(msg.channel, 'You can only edit your own replies'); utils.postError(msg.channel, "You can only edit your own replies");
return; return;
} }
await thread.editStaffReply(msg.member, threadMessage, args.text) await thread.editStaffReply(msg.member, threadMessage, args.text)
}, { }, {
aliases: ['e'] aliases: ["e"]
}); });
} }
if (config.allowStaffDelete) { if (config.allowStaffDelete) {
commands.addInboxThreadCommand('delete', '<messageNumber:number>', async (msg, args, thread) => { commands.addInboxThreadCommand("delete", "<messageNumber:number>", async (msg, args, thread) => {
const threadMessage = await thread.findThreadMessageByMessageNumber(args.messageNumber); const threadMessage = await thread.findThreadMessageByMessageNumber(args.messageNumber);
if (! threadMessage) { if (! threadMessage) {
utils.postError(msg.channel, 'Unknown message number'); utils.postError(msg.channel, "Unknown message number");
return; return;
} }
if (threadMessage.user_id !== msg.author.id) { if (threadMessage.user_id !== msg.author.id) {
utils.postError(msg.channel, 'You can only delete your own replies'); utils.postError(msg.channel, "You can only delete your own replies");
return; return;
} }
await thread.deleteStaffReply(msg.member, threadMessage); await thread.deleteStaffReply(msg.member, threadMessage);
}, { }, {
aliases: ['d'] aliases: ["d"]
}); });
} }
}; };

View File

@ -1,11 +1,11 @@
const threads = require('../data/threads'); const threads = require("../data/threads");
const snippets = require('../data/snippets'); const snippets = require("../data/snippets");
const config = require('../cfg'); const config = require("../cfg");
const utils = require('../utils'); const utils = require("../utils");
const { parseArguments } = require('knub-command-manager'); const { parseArguments } = require("knub-command-manager");
const whitespaceRegex = /\s/; const whitespaceRegex = /\s/;
const quoteChars = ["'", '"']; const quoteChars = ["'", "\""];
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
/** /**
@ -21,13 +21,13 @@ module.exports = ({ bot, knex, config, commands }) => {
const index = parseInt(match.slice(1, -1), 10) - 1; const index = parseInt(match.slice(1, -1), 10) - 1;
return (args[index] != null ? args[index] : match); return (args[index] != null ? args[index] : match);
}) })
.replace(/\\{/g, '{'); .replace(/\\{/g, "{");
} }
/** /**
* When a staff member uses a snippet (snippet prefix + trigger word), find the snippet and post it as a reply in the thread * When a staff member uses a snippet (snippet prefix + trigger word), find the snippet and post it as a reply in the thread
*/ */
bot.on('messageCreate', async msg => { bot.on("messageCreate", async msg => {
if (! utils.messageIsOnInboxServer(msg)) return; if (! utils.messageIsOnInboxServer(msg)) return;
if (! utils.isStaff(msg.member)) return; if (! utils.isStaff(msg.member)) return;
@ -75,7 +75,7 @@ module.exports = ({ bot, knex, config, commands }) => {
}); });
// Show or add a snippet // Show or add a snippet
commands.addInboxServerCommand('snippet', '<trigger> [text$]', async (msg, args, thread) => { commands.addInboxServerCommand("snippet", "<trigger> [text$]", async (msg, args, thread) => {
const snippet = await snippets.get(args.trigger); const snippet = await snippets.get(args.trigger);
if (snippet) { if (snippet) {
@ -97,10 +97,10 @@ module.exports = ({ bot, knex, config, commands }) => {
} }
} }
}, { }, {
aliases: ['s'] aliases: ["s"]
}); });
commands.addInboxServerCommand('delete_snippet', '<trigger>', async (msg, args, thread) => { commands.addInboxServerCommand("delete_snippet", "<trigger>", async (msg, args, thread) => {
const snippet = await snippets.get(args.trigger); const snippet = await snippets.get(args.trigger);
if (! snippet) { if (! snippet) {
utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist!`); utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist!`);
@ -110,10 +110,10 @@ module.exports = ({ bot, knex, config, commands }) => {
await snippets.del(args.trigger); await snippets.del(args.trigger);
utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" deleted!`); utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" deleted!`);
}, { }, {
aliases: ['ds'] aliases: ["ds"]
}); });
commands.addInboxServerCommand('edit_snippet', '<trigger> [text$]', async (msg, args, thread) => { commands.addInboxServerCommand("edit_snippet", "<trigger> [text$]", async (msg, args, thread) => {
const snippet = await snippets.get(args.trigger); const snippet = await snippets.get(args.trigger);
if (! snippet) { if (! snippet) {
utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist!`); utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" doesn't exist!`);
@ -125,14 +125,14 @@ module.exports = ({ bot, knex, config, commands }) => {
utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" edited!`); utils.postSystemMessageWithFallback(msg.channel, thread, `Snippet "${args.trigger}" edited!`);
}, { }, {
aliases: ['es'] aliases: ["es"]
}); });
commands.addInboxServerCommand('snippets', [], async (msg, args, thread) => { commands.addInboxServerCommand("snippets", [], async (msg, args, thread) => {
const allSnippets = await snippets.all(); const allSnippets = await snippets.all();
const triggers = allSnippets.map(s => s.trigger); const triggers = allSnippets.map(s => s.trigger);
triggers.sort(); triggers.sort();
utils.postSystemMessageWithFallback(msg.channel, thread, `Available snippets (prefix ${config.snippetPrefix}):\n${triggers.join(', ')}`); utils.postSystemMessageWithFallback(msg.channel, thread, `Available snippets (prefix ${config.snippetPrefix}):\n${triggers.join(", ")}`);
}); });
}; };

View File

@ -1,9 +1,9 @@
const moment = require('moment'); const moment = require("moment");
const threads = require("../data/threads"); const threads = require("../data/threads");
const utils = require('../utils'); const utils = require("../utils");
const config = require('../cfg'); const config = require("../cfg");
const {THREAD_STATUS} = require('../data/constants'); const {THREAD_STATUS} = require("../data/constants");
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
// Check for threads that are scheduled to be suspended and suspend them // Check for threads that are scheduled to be suspended and suspend them
@ -29,20 +29,20 @@ module.exports = ({ bot, knex, config, commands }) => {
scheduledSuspendLoop(); scheduledSuspendLoop();
commands.addInboxThreadCommand('suspend cancel', [], async (msg, args, thread) => { commands.addInboxThreadCommand("suspend cancel", [], async (msg, args, thread) => {
// Cancel timed suspend // Cancel timed suspend
if (thread.scheduled_suspend_at) { if (thread.scheduled_suspend_at) {
await thread.cancelScheduledSuspend(); await thread.cancelScheduledSuspend();
thread.postSystemMessage(`Cancelled scheduled suspension`); thread.postSystemMessage("Cancelled scheduled suspension");
} else { } else {
thread.postSystemMessage(`Thread is not scheduled to be suspended`); thread.postSystemMessage("Thread is not scheduled to be suspended");
} }
}); });
commands.addInboxThreadCommand('suspend', '[delay:delay]', async (msg, args, thread) => { commands.addInboxThreadCommand("suspend", "[delay:delay]", async (msg, args, thread) => {
if (args.delay) { if (args.delay) {
const suspendAt = moment.utc().add(args.delay, 'ms'); const suspendAt = moment.utc().add(args.delay, "ms");
await thread.scheduleSuspend(suspendAt.format('YYYY-MM-DD HH:mm:ss'), msg.author); await thread.scheduleSuspend(suspendAt.format("YYYY-MM-DD HH:mm:ss"), msg.author);
thread.postSystemMessage(`Thread will be suspended in ${utils.humanizeDelay(args.delay)}. Use \`${config.prefix}suspend cancel\` to cancel.`); thread.postSystemMessage(`Thread will be suspended in ${utils.humanizeDelay(args.delay)}. Use \`${config.prefix}suspend cancel\` to cancel.`);
@ -50,18 +50,18 @@ module.exports = ({ bot, knex, config, commands }) => {
} }
await thread.suspend(); await thread.suspend();
thread.postSystemMessage(`**Thread suspended!** This thread will act as closed until unsuspended with \`!unsuspend\``); thread.postSystemMessage("**Thread suspended!** This thread will act as closed until unsuspended with `!unsuspend`");
}); });
commands.addInboxServerCommand('unsuspend', [], async (msg, args, thread) => { commands.addInboxServerCommand("unsuspend", [], async (msg, args, thread) => {
if (thread) { if (thread) {
thread.postSystemMessage(`Thread is not suspended`); thread.postSystemMessage("Thread is not suspended");
return; return;
} }
thread = await threads.findSuspendedThreadByChannelId(msg.channel.id); thread = await threads.findSuspendedThreadByChannelId(msg.channel.id);
if (! thread) { if (! thread) {
msg.channel.createMessage(`Not in a thread`); msg.channel.createMessage("Not in a thread");
return; return;
} }
@ -72,6 +72,6 @@ module.exports = ({ bot, knex, config, commands }) => {
} }
await thread.unsuspend(); await thread.unsuspend();
thread.postSystemMessage(`**Thread unsuspended!**`); thread.postSystemMessage("**Thread unsuspended!**");
}); });
}; };

View File

@ -1,4 +1,4 @@
const config = require('../cfg'); const config = require("../cfg");
const threads = require("../data/threads"); const threads = require("../data/threads");
const Eris = require("eris"); const Eris = require("eris");

View File

@ -1,18 +1,18 @@
const path = require('path'); const path = require("path");
const fs = require('fs'); const fs = require("fs");
const {promisify} = require('util'); const {promisify} = require("util");
const utils = require("../utils"); const utils = require("../utils");
const updates = require('../data/updates'); const updates = require("../data/updates");
const config = require('../cfg'); const config = require("../cfg");
const access = promisify(fs.access); const access = promisify(fs.access);
const readFile = promisify(fs.readFile); const readFile = promisify(fs.readFile);
const GIT_DIR = path.join(__dirname, '..', '..', '.git'); const GIT_DIR = path.join(__dirname, "..", "..", ".git");
module.exports = ({ bot, knex, config, commands }) => { module.exports = ({ bot, knex, config, commands }) => {
commands.addInboxServerCommand('version', [], async (msg, args, thread) => { commands.addInboxServerCommand("version", [], async (msg, args, thread) => {
const packageJson = require('../../package.json'); const packageJson = require("../../package.json");
const packageVersion = packageJson.version; const packageVersion = packageJson.version;
let response = `Modmail v${packageVersion}`; let response = `Modmail v${packageVersion}`;
@ -27,12 +27,12 @@ module.exports = ({ bot, knex, config, commands }) => {
if (isGit) { if (isGit) {
let commitHash; let commitHash;
const HEAD = await readFile(path.join(GIT_DIR, 'HEAD'), {encoding: 'utf8'}); const HEAD = await readFile(path.join(GIT_DIR, "HEAD"), {encoding: "utf8"});
if (HEAD.startsWith('ref:')) { if (HEAD.startsWith("ref:")) {
// Branch // Branch
const ref = HEAD.match(/^ref: (.*)$/m)[1]; const ref = HEAD.match(/^ref: (.*)$/m)[1];
commitHash = (await readFile(path.join(GIT_DIR, ref), {encoding: 'utf8'})).trim(); commitHash = (await readFile(path.join(GIT_DIR, ref), {encoding: "utf8"})).trim();
} else { } else {
// Detached head // Detached head
commitHash = HEAD.trim(); commitHash = HEAD.trim();

View File

@ -1,18 +1,18 @@
const http = require('http'); const http = require("http");
const mime = require('mime'); const mime = require("mime");
const url = require('url'); const url = require("url");
const fs = require('fs'); const fs = require("fs");
const qs = require('querystring'); const qs = require("querystring");
const moment = require('moment'); const moment = require("moment");
const config = require('../cfg'); const config = require("../cfg");
const threads = require('../data/threads'); const threads = require("../data/threads");
const attachments = require('../data/attachments'); const attachments = require("../data/attachments");
const {THREAD_MESSAGE_TYPE} = require('../data/constants'); const {THREAD_MESSAGE_TYPE} = require("../data/constants");
function notfound(res) { function notfound(res) {
res.statusCode = 404; res.statusCode = 404;
res.end('Page Not Found'); res.end("Page Not Found");
} }
async function serveLogs(req, res, pathParts, query) { async function serveLogs(req, res, pathParts, query) {
@ -41,7 +41,7 @@ async function serveLogs(req, res, pathParts, query) {
return message.body; return message.body;
} }
let line = `[${moment.utc(message.created_at).format('YYYY-MM-DD HH:mm:ss')}]`; let line = `[${moment.utc(message.created_at).format("YYYY-MM-DD HH:mm:ss")}]`;
if (query.verbose) { if (query.verbose) {
if (message.dm_channel_id) { if (message.dm_channel_id) {
@ -72,12 +72,12 @@ async function serveLogs(req, res, pathParts, query) {
return line; return line;
}); });
const openedAt = moment(thread.created_at).format('YYYY-MM-DD HH:mm:ss'); const openedAt = moment(thread.created_at).format("YYYY-MM-DD HH:mm:ss");
const header = `# Modmail thread with ${thread.user_name} (${thread.user_id}) started at ${openedAt}. All times are in UTC+0.`; const header = `# Modmail thread with ${thread.user_name} (${thread.user_id}) started at ${openedAt}. All times are in UTC+0.`;
const fullResponse = header + '\n\n' + lines.join('\n'); const fullResponse = header + "\n\n" + lines.join("\n");
res.setHeader('Content-Type', 'text/plain; charset=UTF-8'); res.setHeader("Content-Type", "text/plain; charset=UTF-8");
res.end(fullResponse); res.end(fullResponse);
} }
@ -92,11 +92,11 @@ function serveAttachments(req, res, pathParts) {
fs.access(attachmentPath, (err) => { fs.access(attachmentPath, (err) => {
if (err) return notfound(res); if (err) return notfound(res);
const filenameParts = desiredFilename.split('.'); const filenameParts = desiredFilename.split(".");
const ext = (filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : 'bin'); const ext = (filenameParts.length > 1 ? filenameParts[filenameParts.length - 1] : "bin");
const fileMime = mime.getType(ext); const fileMime = mime.getType(ext);
res.setHeader('Content-Type', fileMime); res.setHeader("Content-Type", fileMime);
const read = fs.createReadStream(attachmentPath); const read = fs.createReadStream(attachmentPath);
read.pipe(res); read.pipe(res);
@ -106,20 +106,20 @@ function serveAttachments(req, res, pathParts) {
module.exports = () => { module.exports = () => {
const server = http.createServer((req, res) => { const server = http.createServer((req, res) => {
const parsedUrl = url.parse(`http://${req.url}`); const parsedUrl = url.parse(`http://${req.url}`);
const pathParts = parsedUrl.pathname.split('/').filter(v => v !== ''); const pathParts = parsedUrl.pathname.split("/").filter(v => v !== "");
const query = qs.parse(parsedUrl.query); const query = qs.parse(parsedUrl.query);
if (parsedUrl.pathname.startsWith('/logs/')) { if (parsedUrl.pathname.startsWith("/logs/")) {
serveLogs(req, res, pathParts, query); serveLogs(req, res, pathParts, query);
} else if (parsedUrl.pathname.startsWith('/attachments/')) { } else if (parsedUrl.pathname.startsWith("/attachments/")) {
serveAttachments(req, res, pathParts, query); serveAttachments(req, res, pathParts, query);
} else { } else {
notfound(res); notfound(res);
} }
}); });
server.on('error', err => { server.on("error", err => {
console.log('[WARN] Web server error:', err.message); console.log("[WARN] Web server error:", err.message);
}); });
server.listen(config.port); server.listen(config.port);

View File

@ -1,6 +1,6 @@
const attachments = require('./data/attachments'); const attachments = require("./data/attachments");
const { beforeNewThread } = require('./hooks/beforeNewThread'); const { beforeNewThread } = require("./hooks/beforeNewThread");
const formats = require('./formatters'); const formats = require("./formatters");
module.exports = { module.exports = {
getPluginAPI({ bot, knex, config, commands }) { getPluginAPI({ bot, knex, config, commands }) {

View File

@ -1,9 +1,9 @@
const Eris = require('eris'); const Eris = require("eris");
const bot = require('./bot'); const bot = require("./bot");
const moment = require('moment'); const moment = require("moment");
const humanizeDuration = require('humanize-duration'); const humanizeDuration = require("humanize-duration");
const publicIp = require('public-ip'); const publicIp = require("public-ip");
const config = require('./cfg'); const config = require("./cfg");
class BotError extends Error {} class BotError extends Error {}
@ -18,7 +18,7 @@ let logChannel = null;
*/ */
function getInboxGuild() { function getInboxGuild() {
if (! inboxGuild) inboxGuild = bot.guilds.find(g => g.id === config.mailGuildId); if (! inboxGuild) inboxGuild = bot.guilds.find(g => g.id === config.mailGuildId);
if (! inboxGuild) throw new BotError('The bot is not on the modmail (inbox) server!'); if (! inboxGuild) throw new BotError("The bot is not on the modmail (inbox) server!");
return inboxGuild; return inboxGuild;
} }
@ -32,9 +32,9 @@ function getMainGuilds() {
if (mainGuilds.length !== config.mainGuildId.length) { if (mainGuilds.length !== config.mainGuildId.length) {
if (config.mainGuildId.length === 1) { if (config.mainGuildId.length === 1) {
console.warn(`[WARN] The bot hasn't joined the main guild!`); console.warn("[WARN] The bot hasn't joined the main guild!");
} else { } else {
console.warn(`[WARN] The bot hasn't joined one or more main guilds!`); console.warn("[WARN] The bot hasn't joined one or more main guilds!");
} }
} }
@ -50,11 +50,11 @@ function getLogChannel() {
const logChannel = inboxGuild.channels.get(config.logChannelId); const logChannel = inboxGuild.channels.get(config.logChannelId);
if (! logChannel) { if (! logChannel) {
throw new BotError('Log channel (logChannelId) not found!'); throw new BotError("Log channel (logChannelId) not found!");
} }
if (! (logChannel instanceof Eris.TextChannel)) { if (! (logChannel instanceof Eris.TextChannel)) {
throw new BotError('Make sure the logChannelId option is set to a text channel!'); throw new BotError("Make sure the logChannelId option is set to a text channel!");
} }
return logChannel; return logChannel;
@ -155,7 +155,7 @@ function getUserMention(str) {
* @returns {String} * @returns {String}
*/ */
function getTimestamp(...momentArgs) { function getTimestamp(...momentArgs) {
return moment.utc(...momentArgs).format('HH:mm'); return moment.utc(...momentArgs).format("HH:mm");
} }
/** /**
@ -164,7 +164,7 @@ function getTimestamp(...momentArgs) {
* @returns {String} * @returns {String}
*/ */
function disableLinkPreviews(str) { function disableLinkPreviews(str) {
return str.replace(/(^|[^<])(https?:\/\/\S+)/ig, '$1<$2>'); return str.replace(/(^|[^<])(https?:\/\/\S+)/ig, "$1<$2>");
} }
/** /**
@ -172,7 +172,7 @@ function disableLinkPreviews(str) {
* @param {String} path * @param {String} path
* @returns {Promise<String>} * @returns {Promise<String>}
*/ */
async function getSelfUrl(path = '') { async function getSelfUrl(path = "") {
if (config.url) { if (config.url) {
return `${config.url}/${path}`; return `${config.url}/${path}`;
} else { } else {
@ -216,9 +216,9 @@ function chunk(items, chunkSize) {
*/ */
function trimAll(str) { function trimAll(str) {
return str return str
.split('\n') .split("\n")
.map(str => str.trim()) .map(str => str.trim())
.join('\n'); .join("\n");
} }
const delayStringRegex = /^([0-9]+)(?:([dhms])[a-z]*)?/i; const delayStringRegex = /^([0-9]+)(?:([dhms])[a-z]*)?/i;
@ -234,17 +234,17 @@ function convertDelayStringToMS(str) {
str = str.trim(); str = str.trim();
while (str !== '' && (match = str.match(delayStringRegex)) !== null) { while (str !== "" && (match = str.match(delayStringRegex)) !== null) {
if (match[2] === 'd') ms += match[1] * 1000 * 60 * 60 * 24; if (match[2] === "d") ms += match[1] * 1000 * 60 * 60 * 24;
else if (match[2] === 'h') ms += match[1] * 1000 * 60 * 60; else if (match[2] === "h") ms += match[1] * 1000 * 60 * 60;
else if (match[2] === 's') ms += match[1] * 1000; else if (match[2] === "s") ms += match[1] * 1000;
else if (match[2] === 'm' || ! match[2]) ms += match[1] * 1000 * 60; else if (match[2] === "m" || ! match[2]) ms += match[1] * 1000 * 60;
str = str.slice(match[0].length); str = str.slice(match[0].length);
} }
// Invalid delay string // Invalid delay string
if (str !== '') { if (str !== "") {
return null; return null;
} }
@ -256,11 +256,11 @@ function getInboxMention() {
const mentions = []; const mentions = [];
for (const role of mentionRoles) { for (const role of mentionRoles) {
if (role == null) continue; if (role == null) continue;
else if (role === 'here') mentions.push('@here'); else if (role === "here") mentions.push("@here");
else if (role === 'everyone') mentions.push('@everyone'); else if (role === "everyone") mentions.push("@everyone");
else mentions.push(`<@&${role}>`); else mentions.push(`<@&${role}>`);
} }
return mentions.join(' ') + ' '; return mentions.join(" ") + " ";
} }
function postSystemMessageWithFallback(channel, thread, text) { function postSystemMessageWithFallback(channel, thread, text) {
@ -286,7 +286,7 @@ function setDataModelProps(target, props) {
target[prop] = null; target[prop] = null;
} else { } else {
// Set the value as a string in the same format it's returned in SQLite // Set the value as a string in the same format it's returned in SQLite
target[prop] = moment.utc(props[prop]).format('YYYY-MM-DD HH:mm:ss'); target[prop] = moment.utc(props[prop]).format("YYYY-MM-DD HH:mm:ss");
} }
} else { } else {
target[prop] = props[prop]; target[prop] = props[prop];
@ -299,11 +299,11 @@ function isSnowflake(str) {
return str && snowflakeRegex.test(str); return str && snowflakeRegex.test(str);
} }
const humanizeDelay = (delay, opts = {}) => humanizeDuration(delay, Object.assign({conjunction: ' and '}, opts)); const humanizeDelay = (delay, opts = {}) => humanizeDuration(delay, Object.assign({conjunction: " and "}, opts));
const markdownCharsRegex = /([\\_*|`~])/g; const markdownCharsRegex = /([\\_*|`~])/g;
function escapeMarkdown(str) { function escapeMarkdown(str) {
return str.replace(markdownCharsRegex, '\\$1'); return str.replace(markdownCharsRegex, "\\$1");
} }
function disableCodeBlocks(str) { function disableCodeBlocks(str) {
@ -314,7 +314,7 @@ function disableCodeBlocks(str) {
* *
*/ */
function readMultilineConfigValue(str) { function readMultilineConfigValue(str) {
return Array.isArray(str) ? str.join('\n') : str; return Array.isArray(str) ? str.join("\n") : str;
} }
module.exports = { module.exports = {