Allow log storage handlers to store data. Add shouldSave() function to log storage handlers.

cshd
Dragory 2020-10-03 16:10:27 +03:00
parent 3937c0a838
commit 0d2202d38c
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
5 changed files with 132 additions and 55 deletions

View File

@ -25,11 +25,31 @@ const {THREAD_MESSAGE_TYPE, THREAD_STATUS, DISCORD_MESSAGE_ACTIVITY_TYPES} = req
* @property {String} scheduled_close_name
* @property {Number} scheduled_close_silent
* @property {String} alert_ids
* @property {String} log_storage_type
* @property {Object} log_storage_data
* @property {String} created_at
*/
class Thread {
constructor(props) {
utils.setDataModelProps(this, props);
if (props.log_storage_data) {
if (typeof props.log_storage_data === "string") {
this.log_storage_data = JSON.parse(props.log_storage_data);
}
}
}
getSQLProps() {
return Object.entries(this).reduce((obj, [key, value]) => {
if (typeof value === "function") return obj;
if (typeof value === "object" && value != null) {
obj[key] = JSON.stringify(value);
} else {
obj[key] = value;
}
return obj;
}, {});
}
/**
@ -506,6 +526,7 @@ class Thread {
}
// Update DB status
this.status = THREAD_STATUS.CLOSED;
await knex("threads")
.where("id", this.id)
.update({
@ -727,6 +748,25 @@ class Thread {
await this._deleteThreadMessage(threadMessage.id);
}
/**
* @param {String} storageType
* @param {Object|null} storageData
* @returns {Promise<void>}
*/
async updateLogStorageValues(storageType, storageData) {
this.log_storage_type = storageType;
this.log_storage_data = storageData;
const { log_storage_type, log_storage_data } = this.getSQLProps();
await knex("threads")
.where("id", this.id)
.update({
log_storage_type,
log_storage_data,
});
}
}
module.exports = Thread;

View File

@ -9,7 +9,8 @@ const { formatters } = require("../formatters");
/**
* @typedef {object} LogStorageTypeHandler
* @property {LogStorageTypeHandlerSaveFn?} save
* @property {LogStorageTypeHandlerSaveFn} save
* @property {LogStorageTypeHandlerShouldSaveFn?} shouldSave
* @property {LogStorageTypeHandlerGetUrlFn?} getUrl
* @property {LogStorageTypeHandlerGetFileFn?} getFile
* @property {LogStorageTypeHandlerGetCustomResponseFn?} getCustomResponse
@ -19,18 +20,24 @@ const { formatters } = require("../formatters");
* @callback LogStorageTypeHandlerSaveFn
* @param {Thread} thread
* @param {ThreadMessage[]} threadMessages
* @return {void|Promise<void>}
* @return {Object|Promise<Object>|null|Promise<null>} Information about the saved log that can be used to retrieve the log later
*/
/**
* @callback LogStorageTypeHandlerShouldSaveFn
* @param {Thread} thread
* @return {boolean|Promise<boolean>} Whether the log should be saved at this time
*/
/**
* @callback LogStorageTypeHandlerGetUrlFn
* @param {string} threadId
* @param {Thread} thread
* @return {string|Promise<string>|null|Promise<null>}
*/
/**
* @callback LogStorageTypeHandlerGetFileFn
* @param {string} threadId
* @param {Thread} thread
* @return {Eris.MessageFile|Promise<Eris.MessageFile>|null|Promise<null>>}
*/
@ -42,7 +49,7 @@ const { formatters } = require("../formatters");
/**
* @callback LogStorageTypeHandlerGetCustomResponseFn
* @param {string} threadId
* @param {Thread} thread
* @return {LogStorageTypeHandlerGetCustomResponseResult|Promise<LogStorageTypeHandlerGetCustomResponseResult>|null|Promise<null>>}
*/
@ -70,95 +77,110 @@ const addStorageType = (name, handler) => {
/**
* @type {SaveLogToStorageFn}
*/
const saveLogToStorage = async (thread, threadMessages) => {
const { save } = logStorageTypes[config.logStorage];
const saveLogToStorage = async (thread, overrideType = null) => {
const storageType = overrideType || config.logStorage;
const { save, shouldSave } = logStorageTypes[storageType] || {};
if (shouldSave && ! await shouldSave(thread)) return;
if (save) {
await save(thread, threadMessages);
const threadMessages = await thread.getThreadMessages();
const storageData = await save(thread, threadMessages);
await thread.updateLogStorageValues(storageType, storageData);
}
};
/**
* @callback GetLogUrlFn
* @param {string} threadId
* @param {Thread} thread
* @returns {Promise<string|null>}
*/
/**
* @type {GetLogUrlFn}
*/
const getLogUrl = async (threadId) => {
const { getUrl } = logStorageTypes[config.logStorage];
const getLogUrl = async (thread) => {
if (! thread.log_storage_type) {
await saveLogToStorage(thread);
}
const { getUrl } = logStorageTypes[thread.log_storage_type] || {};
return getUrl
? getUrl(threadId)
? getUrl(thread)
: null;
};
/**
* @callback GetLogFileFn
* @param {string} threadId
* @param {Thread} thread
* @returns {Promise<Eris.MessageFile|null>}
*/
/**
* @type {GetLogFileFn}
*/
const getLogFile = async (threadId) => {
const { getFile } = logStorageTypes[config.logStorage];
const getLogFile = async (thread) => {
if (! thread.log_storage_type) {
await saveLogToStorage(thread);
}
const { getFile } = logStorageTypes[thread.log_storage_type] || {};
return getFile
? getFile(threadId)
? getFile(thread)
: null;
};
/**
* @callback GetLogCustomResponseFn
* @param {string} threadId
* @param {Thread} threadId
* @returns {Promise<LogStorageTypeHandlerGetCustomResult|null>}
*/
/**
* @type {GetLogCustomResponseFn}
*/
const getLogCustomResponse = async (threadId) => {
const { getCustomResponse } = logStorageTypes[config.logStorage];
const getLogCustomResponse = async (thread) => {
if (! thread.log_storage_type) {
await saveLogToStorage(thread);
}
const { getCustomResponse } = logStorageTypes[thread.log_storage_type] || {};
return getCustomResponse
? getCustomResponse(threadId)
? getCustomResponse(thread)
: null;
};
addStorageType("local", {
getUrl(threadId) {
return utils.getSelfUrl(`logs/${threadId}`);
save() {
return null;
},
getUrl(thread) {
return utils.getSelfUrl(`logs/${thread.id}`);
},
});
const getLogAttachmentFilename = threadId => {
const filename = `${threadId}.txt`;
const fullPath = path.resolve(config.logOptions.attachmentDirectory, filename);
const fullPath = path.join(config.logOptions.attachmentDirectory, filename);
return { filename, fullPath };
};
addStorageType("attachment", {
shouldSave(thread) {
return thread.status === THREAD_STATUS.CLOSED;
},
async save(thread, threadMessages) {
const { fullPath } = getLogAttachmentFilename(thread.id);
const { fullPath, filename } = getLogAttachmentFilename(thread.id);
const formatLogResult = await formatters.formatLog(thread, threadMessages);
fs.writeFileSync(fullPath, formatLogResult.content, { encoding: "utf8" });
return { fullPath, filename };
},
async getUrl(threadId) {
if (! config.logOptions.allowAttachmentUrlFallback) {
return null;
}
const { fullPath } = getLogAttachmentFilename(threadId);
try {
fs.accessSync(fullPath);
return null;
} catch (e) {
return utils.getSelfUrl(`logs/${threadId}`);
}
},
async getFile(threadId) {
const { filename, fullPath } = getLogAttachmentFilename(threadId);
async getFile(thread) {
const { fullPath, filename } = thread.log_storage_data || {};
if (! fullPath) return;
try {
fs.accessSync(fullPath);

View File

@ -0,0 +1,13 @@
exports.up = async function(knex) {
await knex.schema.table("threads", table => {
table.string("log_storage_type", 255).nullable().defaultTo(null);
table.text("log_storage_data").nullable().defaultTo(null);
});
};
exports.down = async function(knex) {
await knex.schema.table("threads", table => {
table.dropColumn("log_storage_type");
table.dropColumn("log_storage_data");
});
};

View File

@ -7,15 +7,15 @@ const { messageQueue } = require("../queue");
const { getLogUrl, getLogFile, getLogCustomResponse } = require("../data/logs");
module.exports = ({ bot, knex, config, commands }) => {
async function sendCloseNotification(threadId, body) {
const logCustomResponse = await getLogCustomResponse(threadId);
async function sendCloseNotification(thread, body) {
const logCustomResponse = await getLogCustomResponse(thread);
if (logCustomResponse) {
await utils.postLog(body);
await utils.postLog(logCustomResponse.content, logCustomResponse.file);
return;
}
const logUrl = await getLogUrl(threadId);
const logUrl = await getLogUrl(thread);
if (logUrl) {
utils.postLog(utils.trimAll(`
${body}
@ -24,7 +24,7 @@ module.exports = ({ bot, knex, config, commands }) => {
return;
}
const logFile = await getLogFile(threadId);
const logFile = await getLogFile(thread);
if (logFile) {
utils.postLog(body, logFile);
return;
@ -44,7 +44,7 @@ module.exports = ({ bot, knex, config, commands }) => {
await thread.close(false, thread.scheduled_close_silent);
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}`);
await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}`);
}
}
@ -143,7 +143,7 @@ module.exports = ({ bot, knex, config, commands }) => {
await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
}
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}`);
await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}`);
});
// Auto-close threads if their channel is deleted
@ -162,6 +162,6 @@ module.exports = ({ bot, knex, config, commands }) => {
await thread.close(true);
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted`);
await sendCloseNotification(thread, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted`);
});
};

View File

@ -30,7 +30,7 @@ module.exports = ({ bot, knex, config, commands, hooks }) => {
userThreads = userThreads.slice((page - 1) * LOG_LINES_PER_PAGE, page * LOG_LINES_PER_PAGE);
const threadLines = await Promise.all(userThreads.map(async thread => {
const logUrl = await getLogUrl(thread.id);
const logUrl = await getLogUrl(thread);
const formattedLogUrl = logUrl
? `<${logUrl}>`
: `View log with \`${config.prefix}log ${thread.id}\``
@ -58,22 +58,25 @@ module.exports = ({ bot, knex, config, commands, hooks }) => {
});
};
const logCmd = async (msg, args, thread) => {
const threadId = args.threadId || (thread && thread.id);
const logCmd = async (msg, args, _thread) => {
const threadId = args.threadId || (_thread && _thread.id);
if (! threadId) return;
const customResponse = await getLogCustomResponse(threadId);
const thread = await threads.findById(threadId);
if (! thread) return;
const customResponse = await getLogCustomResponse(thread);
if (customResponse && (customResponse.content || customResponse.file)) {
msg.channel.createMessage(customResponse.content, customResponse.file);
}
const logUrl = await getLogUrl(threadId);
const logUrl = await getLogUrl(thread);
if (logUrl) {
msg.channel.createMessage(`Open the following link to view the log:\n<${logUrl}>`);
return;
}
const logFile = await getLogFile(threadId);
const logFile = await getLogFile(thread);
if (logFile) {
msg.channel.createMessage("Download the following file to view the log:", logFile);
return;
@ -90,7 +93,6 @@ module.exports = ({ bot, knex, config, commands, hooks }) => {
hooks.afterThreadClose(async ({ threadId }) => {
const thread = await threads.findById(threadId);
const threadMessages = await thread.getThreadMessages();
await saveLogToStorage(thread, threadMessages);
await saveLogToStorage(thread);
});
};