From 6eb9b973c04b68d512879451ef6a3d57af154780 Mon Sep 17 00:00:00 2001 From: Dragory <2606411+Dragory@users.noreply.github.com> Date: Mon, 25 May 2020 01:33:10 +0300 Subject: [PATCH] More consistent log formatting. Store DM channel IDs and inbox message IDs with thread messages. Add !dm_channel_id. Add message numbers to the database in preparation for !edit and !delete. Some code reorganisation. --- ...206002418_add_number_to_thread_messages.js | 19 + ...dd_thread_message_id_to_thread_messages.js | 19 + ...13_add_dm_channel_id_to_thread_messages.js | 19 + src/data/Thread.js | 471 ++++++++++++------ src/data/ThreadMessage.js | 5 +- src/data/constants.js | 3 +- src/index.js | 7 +- src/main.js | 19 +- src/modules/id.js | 5 + src/modules/webserver.js | 19 +- src/utils.js | 3 +- 11 files changed, 417 insertions(+), 172 deletions(-) create mode 100644 db/migrations/20191206002418_add_number_to_thread_messages.js create mode 100644 db/migrations/20191206011638_add_thread_message_id_to_thread_messages.js create mode 100644 db/migrations/20191206180113_add_dm_channel_id_to_thread_messages.js diff --git a/db/migrations/20191206002418_add_number_to_thread_messages.js b/db/migrations/20191206002418_add_number_to_thread_messages.js new file mode 100644 index 0000000..3564b90 --- /dev/null +++ b/db/migrations/20191206002418_add_number_to_thread_messages.js @@ -0,0 +1,19 @@ +const Knex = require('knex'); + +/** + * @param {Knex} knex + */ +exports.up = async function(knex) { + await knex.schema.table('thread_messages', table => { + table.integer('message_number').unsigned().nullable(); + }); +}; + +/** + * @param {Knex} knex + */ +exports.down = async function(knex) { + await knex.schema.table('thread_messages', table => { + table.dropColumn('message_number'); + }); +}; diff --git a/db/migrations/20191206011638_add_thread_message_id_to_thread_messages.js b/db/migrations/20191206011638_add_thread_message_id_to_thread_messages.js new file mode 100644 index 0000000..475c066 --- /dev/null +++ b/db/migrations/20191206011638_add_thread_message_id_to_thread_messages.js @@ -0,0 +1,19 @@ +const Knex = require('knex'); + +/** + * @param {Knex} knex + */ +exports.up = async function(knex) { + await knex.schema.table('thread_messages', table => { + table.string('inbox_message_id', 20).nullable().unique(); + }); +}; + +/** + * @param {Knex} knex + */ +exports.down = async function(knex) { + await knex.schema.table('thread_messages', table => { + table.dropColumn('inbox_message_id'); + }); +}; diff --git a/db/migrations/20191206180113_add_dm_channel_id_to_thread_messages.js b/db/migrations/20191206180113_add_dm_channel_id_to_thread_messages.js new file mode 100644 index 0000000..4b0aa6a --- /dev/null +++ b/db/migrations/20191206180113_add_dm_channel_id_to_thread_messages.js @@ -0,0 +1,19 @@ +const Knex = require('knex'); + +/** + * @param {Knex} knex + */ +exports.up = async function(knex) { + await knex.schema.table('thread_messages', table => { + table.string('dm_channel_id', 20).nullable(); + }); +}; + +/** + * @param {Knex} knex + */ +exports.down = async function(knex) { + await knex.schema.table('thread_messages', table => { + table.dropColumn('dm_channel_id'); + }); +}; diff --git a/src/data/Thread.js b/src/data/Thread.js index 23e9e4a..ab9898d 100644 --- a/src/data/Thread.js +++ b/src/data/Thread.js @@ -29,165 +29,140 @@ class Thread { } /** - * @param {Eris~Member} moderator - * @param {String} text - * @param {Eris~MessageFile[]} replyAttachments - * @param {Boolean} isAnonymous - * @returns {Promise} Whether we were able to send the reply + * @param {Eris.Member} moderator + * @param {string} text + * @param {boolean} isAnonymous + * @returns {string} + * @private */ - async replyToUser(moderator, text, replyAttachments = [], isAnonymous = false) { - // Username to reply with - let modUsername, logModUsername; + _formatStaffReplyDM(moderator, text, isAnonymous) { const mainRole = utils.getMainRole(moderator); + const modName = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username); + const modInfo = isAnonymous + ? (mainRole ? mainRole.name : 'Moderator') + : (mainRole ? `(${mainRole.name}) ${modName}` : modName); - if (isAnonymous) { - modUsername = (mainRole ? mainRole.name : 'Moderator'); - logModUsername = `(Anonymous) (${moderator.user.username}) ${mainRole ? mainRole.name : 'Moderator'}`; - } else { - const name = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username); - modUsername = (mainRole ? `(${mainRole.name}) ${name}` : name); - logModUsername = modUsername; - } + return `**${modInfo}:** ${text}`; + } - // Build the reply message - let dmContent = `**${modUsername}:** ${text}`; - let threadContent = `**${logModUsername}:** ${text}`; - let logContent = text; + /** + * @param {Eris.Member} moderator + * @param {string} text + * @param {boolean} isAnonymous + * @param {number} messageNumber + * @param {number} timestamp + * @returns {string} + * @private + */ + _formatStaffReplyThreadMessage(moderator, text, isAnonymous, messageNumber, timestamp) { + const mainRole = utils.getMainRole(moderator); + const modName = (config.useNicknames ? moderator.nick || moderator.user.username : moderator.user.username); + const modInfo = isAnonymous + ? `(Anonymous) (${modName}) ${mainRole ? mainRole.name : 'Moderator'}` + : (mainRole ? `(${mainRole.name}) ${modName}` : modName); + + // TODO: Add \`[${messageNumber}]\` here once !edit and !delete exist + let result = `**${modInfo}:** ${text}`; if (config.threadTimestamps) { - const timestamp = utils.getTimestamp(); - threadContent = `[${timestamp}] » ${threadContent}`; + const formattedTimestamp = timestamp ? utils.getTimestamp(timestamp, 'x') : utils.getTimestamp(); + result = `[${formattedTimestamp}] ${result}`; } - // Prepare attachments, if any - let files = []; + return result; + } - if (replyAttachments.length > 0) { - for (const attachment of replyAttachments) { - let savedAttachment; + /** + * @param {Eris.Member} moderator + * @param {string} text + * @param {boolean} isAnonymous + * @param {string[]} attachmentLinks + * @returns {string} + * @private + */ + _formatStaffReplyLogMessage(moderator, text, isAnonymous, attachmentLinks = []) { + const mainRole = utils.getMainRole(moderator); + const modName = moderator.user.username; - await Promise.all([ - attachments.attachmentToDiscordFileObject(attachment).then(file => { - files.push(file); - }), - attachments.saveAttachment(attachment).then(result => { - savedAttachment = result; - }) - ]); + // Mirroring the DM formatting here... + const modInfo = isAnonymous + ? (mainRole ? mainRole.name : 'Moderator') + : (mainRole ? `(${mainRole.name}) ${modName}` : modName); - logContent += `\n\n**Attachment:** ${savedAttachment.url}`; + let result = `**${modInfo}:** ${text}`; + + if (attachmentLinks.length) { + result += '\n'; + for (const link of attachmentLinks) { + result += `\n**Attachment:** ${link}`; } } - // Send the reply DM - let dmMessage; - try { - dmMessage = await this.postToUser(dmContent, files); - } catch (e) { - await this.addThreadMessageToDB({ - message_type: THREAD_MESSAGE_TYPE.COMMAND, - user_id: moderator.id, - user_name: logModUsername, - body: logContent - }); - - await this.postSystemMessage(`Error while replying to user: ${e.message}`); - - return false; - } - - // Send the reply to the modmail thread - await this.postToThreadChannel(threadContent, files); - - // Add the message to the database - await this.addThreadMessageToDB({ - message_type: THREAD_MESSAGE_TYPE.TO_USER, - user_id: moderator.id, - user_name: logModUsername, - body: logContent, - is_anonymous: (isAnonymous ? 1 : 0), - dm_message_id: dmMessage.id - }); - - if (this.scheduled_close_at) { - await this.cancelScheduledClose(); - await this.postSystemMessage(`Cancelling scheduled closing of this thread due to new reply`); - } - - return true; + return result; } /** - * @param {Eris~Message} msg - * @returns {Promise} + * @param {Eris.User} user + * @param {string} body + * @param {Eris.EmbedBase[]} embeds + * @param {string[]} formattedAttachments + * @param {number} timestamp + * @return string + * @private */ - async receiveUserReply(msg) { - let content = msg.content; - if (msg.content.trim() === '' && msg.embeds.length) { - content = ''; - } + _formatUserReplyThreadMessage(user, body, embeds, formattedAttachments = [], timestamp) { + const content = (body.trim() === '' && embeds.length) + ? '' + : body; - let threadContent = `**${msg.author.username}#${msg.author.discriminator}:** ${content}`; - let logContent = msg.content; + let result = `**${user.username}#${user.discriminator}:** ${content}`; - if (config.threadTimestamps) { - const timestamp = utils.getTimestamp(msg.timestamp, 'x'); - threadContent = `[${timestamp}] « ${threadContent}`; - } - - // Prepare attachments, if any - let attachmentFiles = []; - - for (const attachment of msg.attachments) { - const savedAttachment = await attachments.saveAttachment(attachment); - - // Forward small attachments (<2MB) as attachments, just link to larger ones - const formatted = '\n\n' + await utils.formatAttachment(attachment, savedAttachment.url); - logContent += formatted; // Logs always contain the link - - if (config.relaySmallAttachmentsAsAttachments && attachment.size <= 1024 * 1024 * 2) { - const file = await attachments.attachmentToDiscordFileObject(attachment); - attachmentFiles.push(file); - } else { - threadContent += formatted; + if (formattedAttachments.length) { + for (const formatted of formattedAttachments) { + result += `\n\n${formatted}`; } } - await this.postToThreadChannel(threadContent, attachmentFiles); - await this.addThreadMessageToDB({ - message_type: THREAD_MESSAGE_TYPE.FROM_USER, - user_id: this.user_id, - user_name: `${msg.author.username}#${msg.author.discriminator}`, - body: logContent, - is_anonymous: 0, - dm_message_id: msg.id - }); - - if (this.scheduled_close_at) { - await this.cancelScheduledClose(); - await this.postSystemMessage(`<@!${this.scheduled_close_id}> Thread that was scheduled to be closed got a new reply. Cancelling.`); + if (config.threadTimestamps) { + const formattedTimestamp = timestamp ? utils.getTimestamp(timestamp, 'x') : utils.getTimestamp(); + result = `[${formattedTimestamp}] ${result}`; } - if (this.alert_id) { - await this.setAlert(null); - await this.postSystemMessage(`<@!${this.alert_id}> New message from ${this.user_name}`); - } + return result; } /** - * @returns {Promise} + * @param {Eris.User} user + * @param {string} body + * @param {Eris.EmbedBase[]} embeds + * @param {string[]} formattedAttachments + * @return string + * @private */ - getDMChannel() { - return bot.getDMChannel(this.user_id); + _formatUserReplyLogMessage(user, body, embeds, formattedAttachments = []) { + const content = (body.trim() === '' && embeds.length) + ? '' + : body; + + let result = content; + + if (formattedAttachments.length) { + for (const formatted of formattedAttachments) { + result += `\n\n${formatted}`; + } + } + + return result; } /** - * @param {String} text - * @param {Eris~MessageFile|Eris~MessageFile[]} file - * @returns {Promise} + * @param {string} text + * @param {Eris.MessageFile|Eris.MessageFile[]} file + * @returns {Promise} * @throws Error + * @private */ - async postToUser(text, file = null) { + async _sendDMToUser(text, file = null) { // Try to open a DM channel with the user const dmChannel = await this.getDMChannel(); if (! dmChannel) { @@ -206,9 +181,10 @@ class Thread { } /** - * @returns {Promise} + * @returns {Promise} + * @private */ - async postToThreadChannel(...args) { + async _postToThreadChannel(...args) { try { if (typeof args[0] === 'string') { const chunks = utils.chunk(args[0], 2000); @@ -232,17 +208,208 @@ class Thread { } /** - * @param {String} text + * @param {Object} data + * @returns {Promise} + * @private + */ + async _addThreadMessageToDB(data) { + if (data.message_type === THREAD_MESSAGE_TYPE.TO_USER) { + data.message_number = knex.raw(`IFNULL((${this._lastMessageNumberInThreadSQL()}), 0) + 1`); + } + + const dmChannel = await this.getDMChannel(); + const insertedIds = await knex('thread_messages').insert({ + thread_id: this.id, + created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss'), + is_anonymous: 0, + dm_channel_id: dmChannel.id, + ...data + }); + + const threadMessage = await knex('thread_messages') + .where('id', insertedIds[0]) + .select(); + + return new ThreadMessage(threadMessage[0]); + } + + /** + * @param {string} id + * @param {Object} data + * @returns {Promise} + * @private + */ + async _updateThreadMessage(id, data) { + await knex('thread_messages') + .where('id', id) + .update(data); + } + + /** + * @returns {string} + * @private + */ + _lastMessageNumberInThreadSQL() { + return knex('thread_messages') + .select(knex.raw('MAX(message_number)')) + .whereRaw(`thread_messages.thread_id = '${this.id}'`) + .toSQL() + .sql; + } + + /** + * @param {Eris.Member} moderator + * @param {string} text + * @param {Eris.MessageFile[]} replyAttachments + * @param {boolean} isAnonymous + * @returns {Promise} Whether we were able to send the reply + */ + async replyToUser(moderator, text, replyAttachments = [], isAnonymous = false) { + const fullModeratorName = `${moderator.user.username}#${moderator.user.discriminator}`; + + // Prepare attachments, if any + const files = []; + const attachmentLinks = []; + + if (replyAttachments.length > 0) { + for (const attachment of replyAttachments) { + await Promise.all([ + attachments.attachmentToDiscordFileObject(attachment).then(file => { + files.push(file); + }), + attachments.saveAttachment(attachment).then(result => { + attachmentLinks.push(result.url); + }) + ]); + } + } + + // Send the reply DM + const dmContent = this._formatStaffReplyDM(moderator, text, isAnonymous); + let dmMessage; + try { + dmMessage = await this._sendDMToUser(dmContent, files); + } catch (e) { + await this.postSystemMessage(`Error while replying to user: ${e.message}`); + return false; + } + + // Save the log entry + const logContent = this._formatStaffReplyLogMessage(moderator, text, isAnonymous, attachmentLinks); + const threadMessage = await this._addThreadMessageToDB({ + message_type: THREAD_MESSAGE_TYPE.TO_USER, + user_id: moderator.id, + user_name: fullModeratorName, + body: logContent, + is_anonymous: (isAnonymous ? 1 : 0), + dm_message_id: dmMessage.id + }); + + // Show the reply in the inbox thread + const inboxContent = this._formatStaffReplyThreadMessage(moderator, text, isAnonymous, threadMessage.message_number, null); + const inboxMessage = await this._postToThreadChannel(inboxContent, files); + await this._updateThreadMessage(threadMessage.id, { inbox_message_id: inboxMessage.id }); + + // Interrupt scheduled closing, if in progress + if (this.scheduled_close_at) { + await this.cancelScheduledClose(); + await this.postSystemMessage(`Cancelling scheduled closing of this thread due to new reply`); + } + + return true; + } + + /** + * @param {Eris.Message} msg + * @returns {Promise} + */ + async receiveUserReply(msg) { + // Prepare attachments + const attachmentFiles = []; + const threadFormattedAttachments = []; + const logFormattedAttachments = []; + + // TODO: Save attachment info with the message, use that to re-create attachment formatting in + // TODO: this._formatUserReplyLogMessage and this._formatUserReplyThreadMessage + for (const attachment of msg.attachments) { + const savedAttachment = await attachments.saveAttachment(attachment); + + const formatted = await utils.formatAttachment(attachment, savedAttachment.url); + logFormattedAttachments.push(formatted); + + // Forward small attachments (<2MB) as attachments, link to larger ones + if (config.relaySmallAttachmentsAsAttachments && attachment.size <= config.smallAttachmentLimit) { + const file = await attachments.attachmentToDiscordFileObject(attachment); + attachmentFiles.push(file); + } else { + threadFormattedAttachments.push(formatted); + } + } + + // Save log entry + const logContent = this._formatUserReplyLogMessage(msg.author, msg.content, msg.embeds, logFormattedAttachments); + const threadMessage = await this._addThreadMessageToDB({ + message_type: THREAD_MESSAGE_TYPE.FROM_USER, + user_id: this.user_id, + user_name: `${msg.author.username}#${msg.author.discriminator}`, + body: logContent, + is_anonymous: 0, + dm_message_id: msg.id + }); + + // Show user reply in the inbox thread + const inboxContent = this._formatUserReplyThreadMessage(msg.author, msg.content, msg.embeds, threadFormattedAttachments, null); + const inboxMessage = await this._postToThreadChannel(inboxContent, attachmentFiles); + await this._updateThreadMessage(threadMessage.id, { inbox_message_id: inboxMessage.id }); + + // Interrupt scheduled closing, if in progress + if (this.scheduled_close_at) { + await this.cancelScheduledClose(); + await this.postSystemMessage(`<@!${this.scheduled_close_id}> Thread that was scheduled to be closed got a new reply. Cancelling.`); + } + + if (this.alert_id) { + await this.setAlert(null); + await this.postSystemMessage(`<@!${this.alert_id}> New message from ${this.user_name}`); + } + } + + /** + * @returns {Promise} + */ + getDMChannel() { + return bot.getDMChannel(this.user_id); + } + + /** + * @param {string|Eris.MessageContent} content * @param {*} args * @returns {Promise} */ - async postSystemMessage(text, ...args) { - const msg = await this.postToThreadChannel(text, ...args); - await this.addThreadMessageToDB({ + async postSystemMessage(content, ...args) { + const msg = await this._postToThreadChannel(content, ...args); + await this._addThreadMessageToDB({ message_type: THREAD_MESSAGE_TYPE.SYSTEM, user_id: null, user_name: '', - body: typeof text === 'string' ? text : text.content, + body: typeof content === 'string' ? content : content.content, + is_anonymous: 0, + dm_message_id: msg.id + }); + } + + /** + * @param {string|Eris.MessageContent} content + * @param {*} args + * @returns {Promise} + */ + async sendSystemMessageToUser(content, ...args) { + const msg = await this._sendDMToUser(content, ...args); + await this._addThreadMessageToDB({ + message_type: THREAD_MESSAGE_TYPE.SYSTEM_TO_USER, + user_id: null, + user_name: '', + body: typeof content === 'string' ? content : content.content, is_anonymous: 0, dm_message_id: msg.id }); @@ -253,15 +420,15 @@ class Thread { * @returns {Promise} */ async postNonLogMessage(...args) { - await this.postToThreadChannel(...args); + await this._postToThreadChannel(...args); } /** * @param {Eris.Message} msg * @returns {Promise} */ - async saveChatMessage(msg) { - return this.addThreadMessageToDB({ + async saveChatMessageToLogs(msg) { + return this._addThreadMessageToDB({ message_type: THREAD_MESSAGE_TYPE.CHAT, user_id: msg.author.id, user_name: `${msg.author.username}#${msg.author.discriminator}`, @@ -271,8 +438,8 @@ class Thread { }); } - async saveCommandMessage(msg) { - return this.addThreadMessageToDB({ + async saveCommandMessageToLogs(msg) { + return this._addThreadMessageToDB({ message_type: THREAD_MESSAGE_TYPE.COMMAND, user_id: msg.author.id, user_name: `${msg.author.username}#${msg.author.discriminator}`, @@ -286,7 +453,7 @@ class Thread { * @param {Eris.Message} msg * @returns {Promise} */ - async updateChatMessage(msg) { + async updateChatMessageInLogs(msg) { await knex('thread_messages') .where('thread_id', this.id) .where('dm_message_id', msg.id) @@ -299,26 +466,13 @@ class Thread { * @param {String} messageId * @returns {Promise} */ - async deleteChatMessage(messageId) { + async deleteChatMessageFromLogs(messageId) { await knex('thread_messages') .where('thread_id', this.id) .where('dm_message_id', messageId) .delete(); } - /** - * @param {Object} data - * @returns {Promise} - */ - async addThreadMessageToDB(data) { - await knex('thread_messages').insert({ - thread_id: this.id, - created_at: moment.utc().format('YYYY-MM-DD HH:mm:ss'), - is_anonymous: 0, - ...data - }); - } - /** * @returns {Promise} */ @@ -332,6 +486,19 @@ class Thread { return threadMessages.map(row => new ThreadMessage(row)); } + /** + * @param {number} messageNumber + * @returns {Promise} + */ + async findThreadMessageByMessageNumber(messageNumber) { + const data = await knex('thread_messages') + .where('thread_id', this.id) + .where('message_number', messageNumber) + .select(); + + return data ? new ThreadMessage(data) : null; + } + /** * @returns {Promise} */ diff --git a/src/data/ThreadMessage.js b/src/data/ThreadMessage.js index 2704287..e132aaa 100644 --- a/src/data/ThreadMessage.js +++ b/src/data/ThreadMessage.js @@ -4,11 +4,14 @@ const utils = require("../utils"); * @property {Number} id * @property {String} thread_id * @property {Number} message_type + * @property {Number} message_number * @property {String} user_id * @property {String} user_name * @property {String} body * @property {Number} is_anonymous - * @property {Number} dm_message_id + * @property {String} dm_channel_id + * @property {String} dm_message_id + * @property {String} inbox_message_id * @property {String} created_at */ class ThreadMessage { diff --git a/src/data/constants.js b/src/data/constants.js index 13a30e8..01bae0c 100644 --- a/src/data/constants.js +++ b/src/data/constants.js @@ -11,7 +11,8 @@ module.exports = { FROM_USER: 3, TO_USER: 4, LEGACY: 5, - COMMAND: 6 + COMMAND: 6, + SYSTEM_TO_USER: 7 }, ACCIDENTAL_THREAD_MESSAGES: [ diff --git a/src/index.js b/src/index.js index dca80ac..c2acebf 100644 --- a/src/index.js +++ b/src/index.js @@ -61,7 +61,12 @@ process.on('unhandledRejection', err => { (async function() { // Make sure the database is up to date - await knex.migrate.latest(); + const migrationDelta = await knex.migrate.status(); + if (migrationDelta !== 0) { + console.log('Updating database. This can take a while. Don\'t close the bot!'); + await knex.migrate.latest(); + console.log('Done!'); + } // Migrate legacy data if we need to if (await legacyMigrator.shouldMigrate()) { diff --git a/src/main.js b/src/main.js index b8ef515..e8f7258 100644 --- a/src/main.js +++ b/src/main.js @@ -94,8 +94,7 @@ function initBaseMessageHandlers() { if (msg.content.startsWith(config.prefix) || msg.content.startsWith(config.snippetPrefix)) { // Save commands as "command messages" - if (msg.content.startsWith(config.snippetPrefix)) return; // Ignore snippets - thread.saveCommandMessage(msg); + thread.saveCommandMessageToLogs(msg); } else if (config.alwaysReply) { // AUTO-REPLY: If config.alwaysReply is enabled, send all chat messages in thread channels as replies if (! utils.isStaff(msg.member)) return; // Only staff are allowed to reply @@ -104,7 +103,7 @@ function initBaseMessageHandlers() { if (replied) msg.delete(); } else { // Otherwise just save the messages as "chat" in the logs - thread.saveChatMessage(msg); + thread.saveChatMessageToLogs(msg); } }); @@ -133,7 +132,9 @@ function initBaseMessageHandlers() { thread = await threads.createNewThreadForUser(msg.author); } - if (thread) await thread.receiveUserReply(msg); + if (thread) { + await thread.receiveUserReply(msg); + } }); }); @@ -151,10 +152,10 @@ function initBaseMessageHandlers() { const oldContent = oldMessage && oldMessage.content || '*Unavailable due to bot restart*'; const newContent = msg.content; - // Ignore bogus edit events with no changes + // Ignore edit events with changes only in embeds etc. if (newContent.trim() === oldContent.trim()) return; - // 1) Edit in DMs + // 1) If this edit was in DMs if (msg.channel instanceof Eris.PrivateChannel) { const thread = await threads.findOpenThreadByUserId(msg.author.id); if (! thread) return; @@ -163,12 +164,12 @@ function initBaseMessageHandlers() { thread.postSystemMessage(editMessage); } - // 2) Edit in the thread + // 2) If this edit was a chat message in the thread else if (utils.messageIsOnInboxServer(msg) && utils.isStaff(msg.member)) { const thread = await threads.findOpenThreadByChannelId(msg.channel.id); if (! thread) return; - thread.updateChatMessage(msg); + thread.updateChatMessageInLogs(msg); } }); @@ -184,7 +185,7 @@ function initBaseMessageHandlers() { const thread = await threads.findOpenThreadByChannelId(msg.channel.id); if (! thread) return; - thread.deleteChatMessage(msg.id); + thread.deleteChatMessageFromLogs(msg.id); }); /** diff --git a/src/modules/id.js b/src/modules/id.js index e3cf2e2..18fecdf 100644 --- a/src/modules/id.js +++ b/src/modules/id.js @@ -2,4 +2,9 @@ module.exports = ({ bot, knex, config, commands }) => { commands.addInboxThreadCommand('id', [], async (msg, args, thread) => { thread.postSystemMessage(thread.user_id); }); + + commands.addInboxThreadCommand('dm_channel_id', [], async (msg, args, thread) => { + const dmChannel = await thread.getDMChannel(); + thread.postSystemMessage(dmChannel.id); + }); }; diff --git a/src/modules/webserver.js b/src/modules/webserver.js index 5d8b2c9..cef84b7 100644 --- a/src/modules/webserver.js +++ b/src/modules/webserver.js @@ -30,15 +30,20 @@ async function serveLogs(res, pathParts) { let line = `[${moment.utc(message.created_at).format('YYYY-MM-DD HH:mm:ss')}] `; - if (message.message_type === THREAD_MESSAGE_TYPE.SYSTEM) { - // System messages don't need the username - line += message.body; - } else if (message.message_type === THREAD_MESSAGE_TYPE.FROM_USER) { - line += `[FROM USER] ${message.user_name}: ${message.body}`; + if (message.message_type === THREAD_MESSAGE_TYPE.FROM_USER) { + line += `[FROM USER] [${message.user_name}] ${message.body}`; } else if (message.message_type === THREAD_MESSAGE_TYPE.TO_USER) { - line += `[TO USER] ${message.user_name}: ${message.body}`; + line += `[TO USER] [${message.user_name}] ${message.body}`; + } else if (message.message_type === THREAD_MESSAGE_TYPE.SYSTEM) { + line += `[SYSTEM] ${message.body}`; + } else if (message.message_type === THREAD_MESSAGE_TYPE.SYSTEM_TO_USER) { + line += `[SYSTEM TO USER] ${message.body}`; + } else if (message.message_type === THREAD_MESSAGE_TYPE.CHAT) { + line += `[CHAT] [${message.user_name}] ${message.body}`; + } else if (message.message_type === THREAD_MESSAGE_TYPE.COMMAND) { + line += `[COMMAND] [${message.user_name}] ${message.body}`; } else { - line += `${message.user_name}: ${message.body}`; + line += `[${message.user_name}] ${message.body}`; } return line; diff --git a/src/utils.js b/src/utils.js index 3e475ab..acb1439 100644 --- a/src/utils.js +++ b/src/utils.js @@ -73,12 +73,13 @@ function postError(channel, str, opts = {}) { /** * Returns whether the given member has permission to use modmail commands - * @param member + * @param {Eris.Member} member * @returns {boolean} */ function isStaff(member) { if (! member) return false; if (config.inboxServerPermission.length === 0) return true; + if (member.guild.ownerID === member.id) return true; return config.inboxServerPermission.some(perm => { if (isSnowflake(perm)) {