Add support for alternative log storage types
parent
a7e863da6a
commit
3a7f7ffc90
|
@ -189,6 +189,25 @@ See ["Permissions" on this page](https://abal.moe/Eris/docs/reference) for suppo
|
||||||
**Default:** `You haven't been a member of the server for long enough to contact modmail.`
|
**Default:** `You haven't been a member of the server for long enough to contact modmail.`
|
||||||
If `requiredTimeOnServer` is set, users that are too new will be sent this message if they try to message modmail.
|
If `requiredTimeOnServer` is set, users that are too new will be sent this message if they try to message modmail.
|
||||||
|
|
||||||
|
#### logStorage
|
||||||
|
**Default:** `local`
|
||||||
|
Controls how logs are stored. Possible values:
|
||||||
|
* `local` - Logs are served from a local web server via links
|
||||||
|
* `attachment` - Logs are sent as attachments
|
||||||
|
* `none` - Logs are not available through the bot
|
||||||
|
|
||||||
|
#### logOptions
|
||||||
|
Options for logs
|
||||||
|
|
||||||
|
##### logOptions.attachmentDirectory
|
||||||
|
**Default:** `logs`
|
||||||
|
When using `logStorage = "attachment"`, the directory where the log files are stored
|
||||||
|
|
||||||
|
##### logOptions.allowAttachmentUrlFallback
|
||||||
|
**Default:** `off`
|
||||||
|
When using `logStorage = "attachment"`, if enabled, threads that don't have a log file will send a log link instead.
|
||||||
|
Useful if transitioning from `logStorage = "local"` (the default).
|
||||||
|
|
||||||
#### mainGuildId
|
#### mainGuildId
|
||||||
Alias for [mainServerId](#mainServerId)
|
Alias for [mainServerId](#mainServerId)
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,8 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
<dt><a href="#PluginAttachmentsAPI">PluginAttachmentsAPI</a> : <code>object</code></dt>
|
<dt><a href="#PluginAttachmentsAPI">PluginAttachmentsAPI</a> : <code>object</code></dt>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
|
<dt><a href="#PluginLogsAPI">PluginLogsAPI</a> : <code>object</code></dt>
|
||||||
|
<dd></dd>
|
||||||
<dt><a href="#PluginHooksAPI">PluginHooksAPI</a> : <code>object</code></dt>
|
<dt><a href="#PluginHooksAPI">PluginHooksAPI</a> : <code>object</code></dt>
|
||||||
<dd></dd>
|
<dd></dd>
|
||||||
</dl>
|
</dl>
|
||||||
|
@ -29,6 +31,7 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
||||||
| config | <code>ModmailConfig</code> |
|
| config | <code>ModmailConfig</code> |
|
||||||
| commands | [<code>PluginCommandsAPI</code>](#PluginCommandsAPI) |
|
| commands | [<code>PluginCommandsAPI</code>](#PluginCommandsAPI) |
|
||||||
| attachments | [<code>PluginAttachmentsAPI</code>](#PluginAttachmentsAPI) |
|
| attachments | [<code>PluginAttachmentsAPI</code>](#PluginAttachmentsAPI) |
|
||||||
|
| logs | [<code>PluginLogsAPI</code>](#PluginLogsAPI) |
|
||||||
| hooks | [<code>PluginHooksAPI</code>](#PluginHooksAPI) |
|
| hooks | [<code>PluginHooksAPI</code>](#PluginHooksAPI) |
|
||||||
| formats | <code>FormattersExport</code> |
|
| formats | <code>FormattersExport</code> |
|
||||||
|
|
||||||
|
@ -57,6 +60,20 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
||||||
| addStorageType | <code>AddAttachmentStorageTypeFn</code> |
|
| addStorageType | <code>AddAttachmentStorageTypeFn</code> |
|
||||||
| downloadAttachment | <code>DownloadAttachmentFn</code> |
|
| downloadAttachment | <code>DownloadAttachmentFn</code> |
|
||||||
|
|
||||||
|
<a name="PluginLogsAPI"></a>
|
||||||
|
|
||||||
|
## PluginLogsAPI : <code>object</code>
|
||||||
|
**Kind**: global typedef
|
||||||
|
**Properties**
|
||||||
|
|
||||||
|
| Name | Type |
|
||||||
|
| --- | --- |
|
||||||
|
| addStorageType | <code>AddLogStorageTypeFn</code> |
|
||||||
|
| saveLogToStorage | <code>SaveLogToStorageFn</code> |
|
||||||
|
| getLogUrl | <code>GetLogUrlFn</code> |
|
||||||
|
| getLogFile | <code>GetLogFileFn</code> |
|
||||||
|
| getLogCustomResponse | <code>GetLogCustomResponseFn</code> |
|
||||||
|
|
||||||
<a name="PluginHooksAPI"></a>
|
<a name="PluginHooksAPI"></a>
|
||||||
|
|
||||||
## PluginHooksAPI : <code>object</code>
|
## PluginHooksAPI : <code>object</code>
|
||||||
|
@ -66,4 +83,5 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
||||||
| Name | Type |
|
| Name | Type |
|
||||||
| --- | --- |
|
| --- | --- |
|
||||||
| beforeNewThread | <code>AddBeforeNewThreadHookFn</code> |
|
| beforeNewThread | <code>AddBeforeNewThreadHookFn</code> |
|
||||||
|
| afterThreadClose | <code>AddAfterThreadCloseHookFn</code> |
|
||||||
|
|
||||||
|
|
|
@ -34,6 +34,25 @@ module.exports = function({ attachments }) {
|
||||||
```
|
```
|
||||||
To use this custom attachment storage type, you would set the `attachmentStorage` config option to `"original"`.
|
To use this custom attachment storage type, you would set the `attachmentStorage` config option to `"original"`.
|
||||||
|
|
||||||
|
### Example of a custom log storage type
|
||||||
|
This example adds a custom type for the `logStorage` option called `"pastebin"` that uploads logs to Pastebin.
|
||||||
|
The code that handles API calls to Pastebin is left out, as it's not relevant to the example.
|
||||||
|
```js
|
||||||
|
module.exports = function({ logs, formatters }) {
|
||||||
|
logs.addStorageType('pastebin', {
|
||||||
|
async save(thread, threadMessages) {
|
||||||
|
const formatLogResult = await formatters.formatLog(thread, threadMessages);
|
||||||
|
// formatLogResult.content contains the log text
|
||||||
|
// Some code here that uploads the full text to Pastebin, and stores the Pastebin link in the database
|
||||||
|
},
|
||||||
|
|
||||||
|
getUrl(threadId) {
|
||||||
|
// Find the previously-saved Pastebin link from the database based on the thread id, and return it
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
```
|
||||||
|
|
||||||
### Plugin API
|
### Plugin API
|
||||||
The first and only argument to the plugin function is an object with the following properties:
|
The first and only argument to the plugin function is an object with the following properties:
|
||||||
|
|
||||||
|
@ -44,6 +63,7 @@ The first and only argument to the plugin function is an object with the followi
|
||||||
| `config` | The loaded config |
|
| `config` | The loaded config |
|
||||||
| `commands` | An object with functions to add and manage commands |
|
| `commands` | An object with functions to add and manage commands |
|
||||||
| `attachments` | An object with functions to save attachments and manage attachment storage types |
|
| `attachments` | An object with functions to save attachments and manage attachment storage types |
|
||||||
|
| `logs` | An object with functions to get attachment URLs/files and manage log storage types |
|
||||||
| `hooks` | An object with functions to add *hooks* that are called at specific times, e.g. before a new thread is created |
|
| `hooks` | An object with functions to add *hooks* that are called at specific times, e.g. before a new thread is created |
|
||||||
| `formats` | An object with functions that allow you to replace the default functions used for formatting messages and logs |
|
| `formats` | An object with functions that allow you to replace the default functions used for formatting messages and logs |
|
||||||
|
|
||||||
|
|
|
@ -121,6 +121,10 @@ if (! config.sqliteOptions) {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (! config.logOptions) {
|
||||||
|
config.logOptions = {};
|
||||||
|
}
|
||||||
|
|
||||||
// categoryAutomation.newThreadFromGuild => categoryAutomation.newThreadFromServer
|
// categoryAutomation.newThreadFromGuild => categoryAutomation.newThreadFromServer
|
||||||
if (config.categoryAutomation && config.categoryAutomation.newThreadFromGuild && ! config.categoryAutomation.newThreadFromServer) {
|
if (config.categoryAutomation && config.categoryAutomation.newThreadFromGuild && ! config.categoryAutomation.newThreadFromServer) {
|
||||||
config.categoryAutomation.newThreadFromServer = config.categoryAutomation.newThreadFromGuild;
|
config.categoryAutomation.newThreadFromServer = config.categoryAutomation.newThreadFromGuild;
|
||||||
|
|
|
@ -727,13 +727,6 @@ class Thread {
|
||||||
|
|
||||||
await this._deleteThreadMessage(threadMessage.id);
|
await this._deleteThreadMessage(threadMessage.id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @returns {Promise<String>}
|
|
||||||
*/
|
|
||||||
getLogUrl() {
|
|
||||||
return utils.getSelfUrl(`logs/${this.id}`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = Thread;
|
module.exports = Thread;
|
||||||
|
|
|
@ -56,6 +56,10 @@
|
||||||
* @property {boolean} [createThreadOnMention=false]
|
* @property {boolean} [createThreadOnMention=false]
|
||||||
* @property {boolean} [notifyOnMainServerLeave=true]
|
* @property {boolean} [notifyOnMainServerLeave=true]
|
||||||
* @property {boolean} [notifyOnMainServerJoin=true]
|
* @property {boolean} [notifyOnMainServerJoin=true]
|
||||||
|
* @property {string} [logStorage="local"]
|
||||||
|
* @property {object} [logOptions]
|
||||||
|
* @property {string} logOptions.attachmentDirectory
|
||||||
|
* @property {*} [logOptions.allowAttachmentUrlFallback=false]
|
||||||
* @property {number} [port=8890]
|
* @property {number} [port=8890]
|
||||||
* @property {string} [url]
|
* @property {string} [url]
|
||||||
* @property {array} [extraIntents=[]]
|
* @property {array} [extraIntents=[]]
|
||||||
|
|
|
@ -316,6 +316,26 @@
|
||||||
"default": true
|
"default": true
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"logStorage": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "local"
|
||||||
|
},
|
||||||
|
|
||||||
|
"logOptions": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"attachmentDirectory": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "logs"
|
||||||
|
},
|
||||||
|
"allowAttachmentUrlFallback": {
|
||||||
|
"$ref": "#/definitions/customBoolean",
|
||||||
|
"default": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["attachmentDirectory"]
|
||||||
|
},
|
||||||
|
|
||||||
"port": {
|
"port": {
|
||||||
"type": "number",
|
"type": "number",
|
||||||
"maximum": 65535,
|
"maximum": 65535,
|
||||||
|
|
|
@ -0,0 +1,184 @@
|
||||||
|
const Thread = require("./Thread");
|
||||||
|
const ThreadMessage = require("./ThreadMessage");
|
||||||
|
const utils = require("../utils");
|
||||||
|
const config = require("../cfg");
|
||||||
|
const { THREAD_STATUS } = require("./constants");
|
||||||
|
const path = require("path");
|
||||||
|
const fs = require("fs");
|
||||||
|
const { formatters } = require("../formatters");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} LogStorageTypeHandler
|
||||||
|
* @property {LogStorageTypeHandlerSaveFn?} save
|
||||||
|
* @property {LogStorageTypeHandlerGetUrlFn?} getUrl
|
||||||
|
* @property {LogStorageTypeHandlerGetFileFn?} getFile
|
||||||
|
* @property {LogStorageTypeHandlerGetCustomResponseFn?} getCustomResponse
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback LogStorageTypeHandlerSaveFn
|
||||||
|
* @param {Thread} thread
|
||||||
|
* @param {ThreadMessage[]} threadMessages
|
||||||
|
* @return {void|Promise<void>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback LogStorageTypeHandlerGetUrlFn
|
||||||
|
* @param {string} threadId
|
||||||
|
* @return {string|Promise<string>|null|Promise<null>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback LogStorageTypeHandlerGetFileFn
|
||||||
|
* @param {string} threadId
|
||||||
|
* @return {Eris.MessageFile|Promise<Eris.MessageFile>|null|Promise<null>>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} LogStorageTypeHandlerGetCustomResult
|
||||||
|
* @property {Eris.MessageContent?} content
|
||||||
|
* @property {Eris.MessageFile?} file
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback LogStorageTypeHandlerGetCustomResponseFn
|
||||||
|
* @param {string} threadId
|
||||||
|
* @return {LogStorageTypeHandlerGetCustomResponseResult|Promise<LogStorageTypeHandlerGetCustomResponseResult>|null|Promise<null>>}
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback AddLogStorageTypeFn
|
||||||
|
* @param {string} name
|
||||||
|
* @param {LogStorageTypeHandler} handler
|
||||||
|
*/
|
||||||
|
|
||||||
|
const logStorageTypes = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type AddLogStorageTypeFn
|
||||||
|
*/
|
||||||
|
const addStorageType = (name, handler) => {
|
||||||
|
logStorageTypes[name] = handler;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback SaveLogToStorageFn
|
||||||
|
* @param {Thread} thread
|
||||||
|
* @param {ThreadMessage[]} threadMessages
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @type {SaveLogToStorageFn}
|
||||||
|
*/
|
||||||
|
const saveLogToStorage = async (thread, threadMessages) => {
|
||||||
|
const { save } = logStorageTypes[config.logStorage];
|
||||||
|
if (save) {
|
||||||
|
await save(thread, threadMessages);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback GetLogUrlFn
|
||||||
|
* @param {string} threadId
|
||||||
|
* @returns {Promise<string|null>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @type {GetLogUrlFn}
|
||||||
|
*/
|
||||||
|
const getLogUrl = async (threadId) => {
|
||||||
|
const { getUrl } = logStorageTypes[config.logStorage];
|
||||||
|
return getUrl
|
||||||
|
? getUrl(threadId)
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback GetLogFileFn
|
||||||
|
* @param {string} threadId
|
||||||
|
* @returns {Promise<Eris.MessageFile|null>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @type {GetLogFileFn}
|
||||||
|
*/
|
||||||
|
const getLogFile = async (threadId) => {
|
||||||
|
const { getFile } = logStorageTypes[config.logStorage];
|
||||||
|
return getFile
|
||||||
|
? getFile(threadId)
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @callback GetLogCustomResponseFn
|
||||||
|
* @param {string} threadId
|
||||||
|
* @returns {Promise<LogStorageTypeHandlerGetCustomResult|null>}
|
||||||
|
*/
|
||||||
|
/**
|
||||||
|
* @type {GetLogCustomResponseFn}
|
||||||
|
*/
|
||||||
|
const getLogCustomResponse = async (threadId) => {
|
||||||
|
const { getCustomResponse } = logStorageTypes[config.logStorage];
|
||||||
|
return getCustomResponse
|
||||||
|
? getCustomResponse(threadId)
|
||||||
|
: null;
|
||||||
|
};
|
||||||
|
|
||||||
|
addStorageType("local", {
|
||||||
|
getUrl(threadId) {
|
||||||
|
return utils.getSelfUrl(`logs/${threadId}`);
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const getLogAttachmentFilename = threadId => {
|
||||||
|
const filename = `${threadId}.txt`;
|
||||||
|
const fullPath = path.resolve(config.logOptions.attachmentDirectory, filename);
|
||||||
|
|
||||||
|
return { filename, fullPath };
|
||||||
|
};
|
||||||
|
|
||||||
|
addStorageType("attachment", {
|
||||||
|
async save(thread, threadMessages) {
|
||||||
|
const { fullPath } = getLogAttachmentFilename(thread.id);
|
||||||
|
|
||||||
|
const formatLogResult = await formatters.formatLog(thread, threadMessages);
|
||||||
|
fs.writeFileSync(fullPath, formatLogResult.content, { encoding: "utf8" });
|
||||||
|
},
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.accessSync(fullPath);
|
||||||
|
} catch (e) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
file: fs.readFileSync(fullPath, { encoding: "utf8" }),
|
||||||
|
name: filename,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
addStorageType("none", {});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
addStorageType,
|
||||||
|
saveLogToStorage,
|
||||||
|
getLogUrl,
|
||||||
|
getLogFile,
|
||||||
|
getLogCustomResponse,
|
||||||
|
};
|
|
@ -1,12 +1,38 @@
|
||||||
const moment = require("moment");
|
const moment = require("moment");
|
||||||
const Eris = require("eris");
|
const Eris = require("eris");
|
||||||
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");
|
||||||
|
const { getLogUrl, getLogFile, getLogCustomResponse } = require("../data/logs");
|
||||||
|
|
||||||
module.exports = ({ bot, knex, config, commands }) => {
|
module.exports = ({ bot, knex, config, commands }) => {
|
||||||
|
async function sendCloseNotification(threadId, body) {
|
||||||
|
const logCustomResponse = await getLogCustomResponse(threadId);
|
||||||
|
if (logCustomResponse) {
|
||||||
|
await utils.postLog(body);
|
||||||
|
await utils.postLog(logCustomResponse.content, logCustomResponse.file);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logUrl = await getLogUrl(threadId);
|
||||||
|
if (logUrl) {
|
||||||
|
utils.postLog(utils.trimAll(`
|
||||||
|
${body}
|
||||||
|
Logs: ${logUrl}
|
||||||
|
`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFile = await getLogFile(threadId);
|
||||||
|
if (logFile) {
|
||||||
|
utils.postLog(body, logFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
utils.postLog(body);
|
||||||
|
}
|
||||||
|
|
||||||
// Check for threads that are scheduled to be closed and close them
|
// Check for threads that are scheduled to be closed and close them
|
||||||
async function applyScheduledCloses() {
|
async function applyScheduledCloses() {
|
||||||
const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed();
|
const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed();
|
||||||
|
@ -18,11 +44,7 @@ module.exports = ({ bot, knex, config, commands }) => {
|
||||||
|
|
||||||
await thread.close(false, thread.scheduled_close_silent);
|
await thread.close(false, thread.scheduled_close_silent);
|
||||||
|
|
||||||
const logUrl = await thread.getLogUrl();
|
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}`);
|
||||||
utils.postLog(utils.trimAll(`
|
|
||||||
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}
|
|
||||||
Logs: ${logUrl}
|
|
||||||
`));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -121,11 +143,7 @@ module.exports = ({ bot, knex, config, commands }) => {
|
||||||
await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
|
await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
|
||||||
}
|
}
|
||||||
|
|
||||||
const logUrl = await thread.getLogUrl();
|
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}`);
|
||||||
utils.postLog(utils.trimAll(`
|
|
||||||
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}
|
|
||||||
Logs: ${logUrl}
|
|
||||||
`));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// Auto-close threads if their channel is deleted
|
// Auto-close threads if their channel is deleted
|
||||||
|
@ -144,10 +162,6 @@ module.exports = ({ bot, knex, config, commands }) => {
|
||||||
|
|
||||||
await thread.close(true);
|
await thread.close(true);
|
||||||
|
|
||||||
const logUrl = await thread.getLogUrl();
|
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted`);
|
||||||
utils.postLog(utils.trimAll(`
|
|
||||||
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted
|
|
||||||
Logs: ${logUrl}
|
|
||||||
`));
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
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 { getLogUrl, getLogFile, getLogCustomResponse, saveLogToStorage } = require("../data/logs");
|
||||||
|
|
||||||
const LOG_LINES_PER_PAGE = 10;
|
const LOG_LINES_PER_PAGE = 10;
|
||||||
|
|
||||||
module.exports = ({ bot, knex, config, commands }) => {
|
module.exports = ({ bot, knex, config, commands, hooks }) => {
|
||||||
const logsCmd = async (msg, args, thread) => {
|
const logsCmd = async (msg, args, thread) => {
|
||||||
let userId = args.userId || (thread && thread.user_id);
|
let userId = args.userId || (thread && thread.user_id);
|
||||||
if (! userId) return;
|
if (! userId) return;
|
||||||
|
@ -29,9 +30,12 @@ module.exports = ({ bot, knex, config, commands }) => {
|
||||||
userThreads = userThreads.slice((page - 1) * LOG_LINES_PER_PAGE, page * LOG_LINES_PER_PAGE);
|
userThreads = userThreads.slice((page - 1) * LOG_LINES_PER_PAGE, page * LOG_LINES_PER_PAGE);
|
||||||
|
|
||||||
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 getLogUrl(thread.id);
|
||||||
|
const formattedLogUrl = logUrl
|
||||||
|
? `<${logUrl}>`
|
||||||
|
: `View log with \`${config.prefix}log ${thread.id}\``
|
||||||
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}\`: ${formattedLogUrl}`;
|
||||||
}));
|
}));
|
||||||
|
|
||||||
let message = isPaginated
|
let message = isPaginated
|
||||||
|
@ -54,34 +58,39 @@ module.exports = ({ bot, knex, config, commands }) => {
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const logCmd = async (msg, args, thread) => {
|
||||||
|
const threadId = args.threadId || (thread && thread.id);
|
||||||
|
if (! threadId) return;
|
||||||
|
|
||||||
|
const customResponse = await getLogCustomResponse(threadId);
|
||||||
|
if (customResponse && (customResponse.content || customResponse.file)) {
|
||||||
|
msg.channel.createMessage(customResponse.content, customResponse.file);
|
||||||
|
}
|
||||||
|
|
||||||
|
const logUrl = await getLogUrl(threadId);
|
||||||
|
if (logUrl) {
|
||||||
|
msg.channel.createMessage(`Open the following link to view the log:\n<${logUrl}>`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logFile = await getLogFile(threadId);
|
||||||
|
if (logFile) {
|
||||||
|
msg.channel.createMessage("Download the following file to view the log:", logFile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
msg.channel.createMessage("This thread's logs are not currently available");
|
||||||
|
};
|
||||||
|
|
||||||
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("log", "[threadId:string]", logCmd);
|
||||||
if (! thread) {
|
commands.addInboxServerCommand("loglink", "[threadId:string]", logCmd);
|
||||||
thread = await threads.findSuspendedThreadByChannelId(msg.channel.id);
|
|
||||||
if (! thread) return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const logUrl = await thread.getLogUrl();
|
hooks.afterThreadClose(async ({ threadId }) => {
|
||||||
const query = [];
|
const thread = await threads.findById(threadId);
|
||||||
if (args.verbose) query.push("verbose=1");
|
const threadMessages = await thread.getThreadMessages();
|
||||||
if (args.simple) query.push("simple=1");
|
await saveLogToStorage(thread, threadMessages);
|
||||||
let qs = query.length ? `?${query.join("&")}` : "";
|
|
||||||
|
|
||||||
thread.postSystemMessage(`Log URL: ${logUrl}${qs}`);
|
|
||||||
}, {
|
|
||||||
options: [
|
|
||||||
{
|
|
||||||
name: "verbose",
|
|
||||||
shortcut: "v",
|
|
||||||
isSwitch: true,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "simple",
|
|
||||||
shortcut: "s",
|
|
||||||
isSwitch: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
|
@ -9,6 +9,7 @@ const Knex = require("knex");
|
||||||
* @property {ModmailConfig} config
|
* @property {ModmailConfig} config
|
||||||
* @property {PluginCommandsAPI} commands
|
* @property {PluginCommandsAPI} commands
|
||||||
* @property {PluginAttachmentsAPI} attachments
|
* @property {PluginAttachmentsAPI} attachments
|
||||||
|
* @property {PluginLogsAPI} logs
|
||||||
* @property {PluginHooksAPI} hooks
|
* @property {PluginHooksAPI} hooks
|
||||||
* @property {FormattersExport} formats
|
* @property {FormattersExport} formats
|
||||||
*/
|
*/
|
||||||
|
@ -28,6 +29,15 @@ const Knex = require("knex");
|
||||||
* @property {DownloadAttachmentFn} downloadAttachment
|
* @property {DownloadAttachmentFn} downloadAttachment
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef {object} PluginLogsAPI
|
||||||
|
* @property {AddLogStorageTypeFn} addStorageType
|
||||||
|
* @property {SaveLogToStorageFn} saveLogToStorage
|
||||||
|
* @property {GetLogUrlFn} getLogUrl
|
||||||
|
* @property {GetLogFileFn} getLogFile
|
||||||
|
* @property {GetLogCustomResponseFn} getLogCustomResponse
|
||||||
|
*/
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @typedef {object} PluginHooksAPI
|
* @typedef {object} PluginHooksAPI
|
||||||
* @property {AddBeforeNewThreadHookFn} beforeNewThread
|
* @property {AddBeforeNewThreadHookFn} beforeNewThread
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
const attachments = require("./data/attachments");
|
const attachments = require("./data/attachments");
|
||||||
|
const logs = require("./data/logs");
|
||||||
const { beforeNewThread } = require("./hooks/beforeNewThread");
|
const { beforeNewThread } = require("./hooks/beforeNewThread");
|
||||||
const { afterThreadClose } = require("./hooks/afterThreadClose");
|
const { afterThreadClose } = require("./hooks/afterThreadClose");
|
||||||
const formats = require("./formatters");
|
const formats = require("./formatters");
|
||||||
|
@ -27,6 +28,13 @@ module.exports = {
|
||||||
addStorageType: attachments.addStorageType,
|
addStorageType: attachments.addStorageType,
|
||||||
downloadAttachment: attachments.downloadAttachment
|
downloadAttachment: attachments.downloadAttachment
|
||||||
},
|
},
|
||||||
|
logs: {
|
||||||
|
addStorageType: logs.addStorageType,
|
||||||
|
saveLogToStorage: logs.saveLogToStorage,
|
||||||
|
getLogUrl: logs.getLogUrl,
|
||||||
|
getLogFile: logs.getLogFile,
|
||||||
|
getLogCustomResponse: logs.getLogCustomResponse,
|
||||||
|
},
|
||||||
hooks: {
|
hooks: {
|
||||||
beforeNewThread,
|
beforeNewThread,
|
||||||
afterThreadClose,
|
afterThreadClose,
|
||||||
|
|
|
@ -61,7 +61,7 @@ function getLogChannel() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function postLog(...args) {
|
function postLog(...args) {
|
||||||
getLogChannel().createMessage(...args);
|
return getLogChannel().createMessage(...args);
|
||||||
}
|
}
|
||||||
|
|
||||||
function postError(channel, str, opts = {}) {
|
function postError(channel, str, opts = {}) {
|
||||||
|
|
Loading…
Reference in New Issue