ramirez/src/formatters.js

367 lines
13 KiB
JavaScript
Raw Normal View History

2020-08-12 17:08:37 -04:00
const Eris = require("eris");
const utils = require("./utils");
const config = require("./cfg");
const ThreadMessage = require("./data/ThreadMessage");
const {THREAD_MESSAGE_TYPE} = require("./data/constants");
const moment = require("moment");
/**
* Function to format the DM that is sent to the user when a staff member replies to them via !reply
* @callback FormatStaffReplyDM
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to send as a DM
*/
/**
* Function to format a staff reply in a thread channel
* @callback FormatStaffReplyThreadMessage
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to post in the thread channel
*/
/**
* Function to format a user reply in a thread channel
* @callback FormatUserReplyThreadMessage
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to post in the thread channel
*/
/**
* Function to format the inbox channel notification for a staff reply edit
* @callback FormatStaffReplyEditNotificationThreadMessage
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to post in the thread channel
*/
/**
* Function to format the inbox channel notification for a staff reply deletion
* @callback FormatStaffReplyDeletionNotificationThreadMessage
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to post in the thread channel
*/
/**
* Function to format a system message in a thread channel
* @callback FormatSystemThreadMessage
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to post in the thread channel
*/
/**
* Function to format a system message sent to the user in a thread channel
* @callback FormatSystemToUserThreadMessage
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to post in the thread channel
*/
/**
* Function to format the DM that is sent to the user when the bot sends a system message to the user
* @callback FormatSystemToUserDM
* @param {ThreadMessage} threadMessage
* @return {Eris.MessageContent} Message content to send as a DM
*/
/**
* @typedef {Object} FormatLogOptions
* @property {Boolean?} simple
* @property {Boolean?} verbose
*/
/**
* @typedef {Object} FormatLogResult
* @property {String} content Contents of the entire log
* @property {*?} extra
*/
/**
* Function to format the inbox channel notification for a staff reply deletion
* @callback FormatLog
* @param {Thread} thread
* @param {ThreadMessage[]} threadMessages
* @param {FormatLogOptions={}} opts
* @return {FormatLogResult}
*/
/**
* @typedef MessageFormatters
* @property {FormatStaffReplyDM} formatStaffReplyDM
* @property {FormatStaffReplyThreadMessage} formatStaffReplyThreadMessage
* @property {FormatUserReplyThreadMessage} formatUserReplyThreadMessage
* @property {FormatStaffReplyEditNotificationThreadMessage} formatStaffReplyEditNotificationThreadMessage
* @property {FormatStaffReplyDeletionNotificationThreadMessage} formatStaffReplyDeletionNotificationThreadMessage
* @property {FormatSystemThreadMessage} formatSystemThreadMessage
* @property {FormatSystemToUserThreadMessage} formatSystemToUserThreadMessage
* @property {FormatSystemToUserDM} formatSystemToUserDM
* @property {FormatLog} formatLog
*/
/**
* @type {MessageFormatters}
*/
const defaultFormatters = {
formatStaffReplyDM(threadMessage) {
const roleName = threadMessage.role_name || config.fallbackRoleName;
const modInfo = threadMessage.is_anonymous
? roleName
: (roleName ? `(${roleName}) ${threadMessage.user_name}` : threadMessage.user_name);
return modInfo
? `**${modInfo}:** ${threadMessage.body}`
: threadMessage.body;
},
formatStaffReplyThreadMessage(threadMessage) {
const roleName = threadMessage.role_name || config.fallbackRoleName;
const modInfo = threadMessage.is_anonymous
? (roleName ? `(Anonymous) (${threadMessage.user_name}) ${roleName}` : `(Anonymous) (${threadMessage.user_name})`)
: (roleName ? `(${roleName}) ${threadMessage.user_name}` : threadMessage.user_name);
let result = modInfo
? `**${modInfo}:** ${threadMessage.body}`
: threadMessage.body;
if (config.threadTimestamps) {
const formattedTimestamp = utils.getTimestamp(threadMessage.created_at);
result = `[${formattedTimestamp}] ${result}`;
}
result = `\`${threadMessage.message_number}\` ${result}`;
return result;
},
formatUserReplyThreadMessage(threadMessage) {
let result = `**${threadMessage.user_name}:** ${threadMessage.body}`;
for (const link of threadMessage.attachments) {
result += `\n\n${link}`;
}
if (config.threadTimestamps) {
const formattedTimestamp = utils.getTimestamp(threadMessage.created_at);
result = `[${formattedTimestamp}] ${result}`;
}
return result;
},
formatStaffReplyEditNotificationThreadMessage(threadMessage) {
const originalThreadMessage = threadMessage.getMetadataValue("originalThreadMessage");
const newBody = threadMessage.getMetadataValue("newBody");
2020-08-16 18:53:21 -04:00
let content = `**${originalThreadMessage.user_name}** (\`${originalThreadMessage.user_id}\`) edited reply \`${originalThreadMessage.message_number}\``;
if (originalThreadMessage.body.length < 200 && newBody.length < 200) {
2020-08-16 18:53:21 -04:00
// Show edits of small messages inline
content += ` from \`${utils.disableInlineCode(originalThreadMessage.body)}\` to \`${newBody}\``;
2020-08-16 18:53:21 -04:00
} else {
// Show edits of long messages in two code blocks
content += ":";
content += `\n\nBefore:\n\`\`\`${utils.disableCodeBlocks(originalThreadMessage.body)}\`\`\``;
content += `\nAfter:\n\`\`\`${utils.disableCodeBlocks(newBody)}\`\`\``;
2020-08-16 18:53:21 -04:00
}
2020-08-13 18:19:01 -04:00
return content;
},
formatStaffReplyDeletionNotificationThreadMessage(threadMessage) {
const originalThreadMessage = threadMessage.getMetadataValue("originalThreadMessage");
let content = `**${originalThreadMessage.user_name}** (\`${originalThreadMessage.user_id}\`) deleted reply \`${originalThreadMessage.message_number}\``;
2020-08-16 18:53:21 -04:00
if (originalThreadMessage.body.length < 200) {
2020-08-16 18:53:21 -04:00
// Show the original content of deleted small messages inline
content += ` (message content: \`${utils.disableInlineCode(originalThreadMessage.body)}\`)`;
2020-08-16 18:53:21 -04:00
} else {
// Show the original content of deleted large messages in a code block
content += ":\n```" + utils.disableCodeBlocks(originalThreadMessage.body) + "```";
2020-08-16 18:53:21 -04:00
}
2020-08-13 18:19:01 -04:00
return content;
},
formatSystemThreadMessage(threadMessage) {
let result = threadMessage.body;
for (const link of threadMessage.attachments) {
result += `\n\n${link}`;
}
return result;
},
formatSystemToUserThreadMessage(threadMessage) {
let result = `**[SYSTEM TO USER]** ${threadMessage.body}`;
for (const link of threadMessage.attachments) {
result += `\n\n${link}`;
}
return result;
},
formatSystemToUserDM(threadMessage) {
let result = threadMessage.body;
for (const link of threadMessage.attachments) {
result += `\n\n${link}`;
}
return result;
},
formatLog(thread, threadMessages, opts = {}) {
if (opts.simple) {
threadMessages = threadMessages.filter(message => {
return (
message.message_type !== THREAD_MESSAGE_TYPE.SYSTEM
&& message.message_type !== THREAD_MESSAGE_TYPE.SYSTEM_TO_USER
&& message.message_type !== THREAD_MESSAGE_TYPE.CHAT
&& message.message_type !== THREAD_MESSAGE_TYPE.COMMAND
);
});
}
const lines = threadMessages.map(message => {
// Legacy messages (from 2018) are the entire log in one message, so just serve them as they are
if (message.message_type === THREAD_MESSAGE_TYPE.LEGACY) {
return message.body;
}
let line = `[${moment.utc(message.created_at).format("YYYY-MM-DD HH:mm:ss")}]`;
if (opts.verbose) {
if (message.dm_channel_id) {
line += ` [DM CHA ${message.dm_channel_id}]`;
}
if (message.dm_message_id) {
line += ` [DM MSG ${message.dm_message_id}]`;
}
}
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) {
if (opts.verbose) {
line += ` [TO USER] [${message.message_number || "0"}] [${message.user_name}]`;
} else {
line += ` [TO USER] [${message.user_name}]`;
}
if (message.use_legacy_format) {
// Legacy format (from pre-2.31.0) includes the role and username in the message body, so serve that as is
line += ` ${message.body}`;
} else if (message.is_anonymous) {
if (message.role_name) {
line += ` (Anonymous) ${message.role_name}: ${message.body}`;
} else {
line += ` (Anonymous) Moderator: ${message.body}`;
}
} else {
if (message.role_name) {
line += ` (${message.role_name}) ${message.user_name}: ${message.body}`;
} else {
line += ` ${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 if (message.message_type === THREAD_MESSAGE_TYPE.REPLY_EDITED) {
const originalThreadMessage = message.getMetadataValue("originalThreadMessage");
line += ` [REPLY EDITED] ${originalThreadMessage.user_name} edited reply ${originalThreadMessage.message_number}:`;
line += `\n\nBefore:\n${originalThreadMessage.body}`;
line += `\n\nAfter:\n${message.getMetadataValue("newBody")}`;
} else if (message.message_type === THREAD_MESSAGE_TYPE.REPLY_DELETED) {
const originalThreadMessage = message.getMetadataValue("originalThreadMessage");
line += ` [REPLY DELETED] ${originalThreadMessage.user_name} deleted reply ${originalThreadMessage.message_number}:`;
line += `\n\n${originalThreadMessage.body}`;
} else {
line += ` [${message.user_name}] ${message.body}`;
}
return line;
});
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 fullResult = header + "\n\n" + lines.join("\n");
return {
content: fullResult,
};
},
};
/**
* @type {MessageFormatters}
*/
const formatters = { ...defaultFormatters };
2020-08-16 16:26:04 -04:00
/**
* @typedef {object} FormattersExport
* @property {MessageFormatters} formatters Read only
* @property {function(FormatStaffReplyDM): void} setStaffReplyDMFormatter
* @property {function(FormatStaffReplyThreadMessage): void} setStaffReplyThreadMessageFormatter
* @property {function(FormatUserReplyThreadMessage): void} setUserReplyThreadMessageFormatter
* @property {function(FormatStaffReplyEditNotificationThreadMessage): void} setStaffReplyEditNotificationThreadMessageFormatter
* @property {function(FormatStaffReplyDeletionNotificationThreadMessage): void} setStaffReplyDeletionNotificationThreadMessageFormatter
* @property {function(FormatSystemThreadMessage): void} setSystemThreadMessageFormatter
* @property {function(FormatSystemToUserThreadMessage): void} setSystemToUserThreadMessageFormatter
* @property {function(FormatSystemToUserDM): void} setSystemToUserDMFormatter
2020-08-16 16:26:04 -04:00
* @property {function(FormatLog): void} setLogFormatter
*/
/**
* @type {FormattersExport}
*/
module.exports = {
formatters: new Proxy(formatters, {
set() {
throw new Error("Please use the formatter setter functions instead of modifying the formatters directly");
},
}),
setStaffReplyDMFormatter(fn) {
formatters.formatStaffReplyDM = fn;
},
setStaffReplyThreadMessageFormatter(fn) {
formatters.formatStaffReplyThreadMessage = fn;
},
setUserReplyThreadMessageFormatter(fn) {
formatters.formatUserReplyThreadMessage = fn;
},
setStaffReplyEditNotificationThreadMessageFormatter(fn) {
formatters.formatStaffReplyEditNotificationThreadMessage = fn;
},
setStaffReplyDeletionNotificationThreadMessageFormatter(fn) {
formatters.formatStaffReplyDeletionNotificationThreadMessage = fn;
},
setSystemThreadMessageFormatter(fn) {
formatters.formatSystemThreadMessage = fn;
},
setSystemToUserThreadMessageFormatter(fn) {
formatters.formatSystemToUserThreadMessage = fn;
},
setSystemToUserDMFormatter(fn) {
formatters.formatSystemToUserDM = fn;
},
setLogFormatter(fn) {
formatters.formatLog = fn;
},
};