Apply code style from .eslintrc
parent
b628ac1bfa
commit
86a060410f
|
@ -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,
|
||||||
|
|
76
src/cfg.js
76
src/cfg.js
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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"
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
|
@ -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" });
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
const Eris = require('eris');
|
const Eris = require("eris");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @callback BeforeNewThreadHook_SetCategoryId
|
* @callback BeforeNewThreadHook_SetCategoryId
|
||||||
|
|
68
src/index.js
68
src/index.js
|
@ -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
|
||||||
|
|
|
@ -1,2 +1,2 @@
|
||||||
const config = require('./cfg');
|
const config = require("./cfg");
|
||||||
module.exports = require('knex')(config.knex);
|
module.exports = require("knex")(config.knex);
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
|
|
90
src/main.js
90
src/main.js
|
@ -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}>`;
|
||||||
|
|
|
@ -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`);
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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);
|
||||||
});
|
});
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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}`);
|
||||||
|
|
|
@ -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"]
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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(", ")}`);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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!**");
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -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");
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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 }) {
|
||||||
|
|
58
src/utils.js
58
src/utils.js
|
@ -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 = {
|
||||||
|
|
Loading…
Reference in New Issue