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.`
|
||||
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
|
||||
Alias for [mainServerId](#mainServerId)
|
||||
|
||||
|
|
|
@ -12,6 +12,8 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
|||
<dd></dd>
|
||||
<dt><a href="#PluginAttachmentsAPI">PluginAttachmentsAPI</a> : <code>object</code></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#PluginLogsAPI">PluginLogsAPI</a> : <code>object</code></dt>
|
||||
<dd></dd>
|
||||
<dt><a href="#PluginHooksAPI">PluginHooksAPI</a> : <code>object</code></dt>
|
||||
<dd></dd>
|
||||
</dl>
|
||||
|
@ -29,6 +31,7 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
|||
| config | <code>ModmailConfig</code> |
|
||||
| commands | [<code>PluginCommandsAPI</code>](#PluginCommandsAPI) |
|
||||
| attachments | [<code>PluginAttachmentsAPI</code>](#PluginAttachmentsAPI) |
|
||||
| logs | [<code>PluginLogsAPI</code>](#PluginLogsAPI) |
|
||||
| hooks | [<code>PluginHooksAPI</code>](#PluginHooksAPI) |
|
||||
| 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> |
|
||||
| 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>
|
||||
|
||||
## PluginHooksAPI : <code>object</code>
|
||||
|
@ -66,4 +83,5 @@ Scroll down to [PluginAPI](#PluginAPI) for a list of properties available to plu
|
|||
| Name | Type |
|
||||
| --- | --- |
|
||||
| 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"`.
|
||||
|
||||
### 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
|
||||
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 |
|
||||
| `commands` | An object with functions to add and manage commands |
|
||||
| `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 |
|
||||
| `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
|
||||
if (config.categoryAutomation && config.categoryAutomation.newThreadFromGuild && ! config.categoryAutomation.newThreadFromServer) {
|
||||
config.categoryAutomation.newThreadFromServer = config.categoryAutomation.newThreadFromGuild;
|
||||
|
|
|
@ -727,13 +727,6 @@ class Thread {
|
|||
|
||||
await this._deleteThreadMessage(threadMessage.id);
|
||||
}
|
||||
|
||||
/**
|
||||
* @returns {Promise<String>}
|
||||
*/
|
||||
getLogUrl() {
|
||||
return utils.getSelfUrl(`logs/${this.id}`);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Thread;
|
||||
|
|
|
@ -56,6 +56,10 @@
|
|||
* @property {boolean} [createThreadOnMention=false]
|
||||
* @property {boolean} [notifyOnMainServerLeave=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 {string} [url]
|
||||
* @property {array} [extraIntents=[]]
|
||||
|
|
|
@ -316,6 +316,26 @@
|
|||
"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": {
|
||||
"type": "number",
|
||||
"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 Eris = require("eris");
|
||||
const config = require("../cfg");
|
||||
const utils = require("../utils");
|
||||
const threads = require("../data/threads");
|
||||
const blocked = require("../data/blocked");
|
||||
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);
|
||||
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
|
||||
async function applyScheduledCloses() {
|
||||
const threadsToBeClosed = await threads.getThreadsThatShouldBeClosed();
|
||||
|
@ -18,11 +44,7 @@ module.exports = ({ bot, knex, config, commands }) => {
|
|||
|
||||
await thread.close(false, thread.scheduled_close_silent);
|
||||
|
||||
const logUrl = await thread.getLogUrl();
|
||||
utils.postLog(utils.trimAll(`
|
||||
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}
|
||||
Logs: ${logUrl}
|
||||
`));
|
||||
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed as scheduled by ${thread.scheduled_close_name}`);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,11 +143,7 @@ module.exports = ({ bot, knex, config, commands }) => {
|
|||
await thread.sendSystemMessageToUser(closeMessage).catch(() => {});
|
||||
}
|
||||
|
||||
const logUrl = await thread.getLogUrl();
|
||||
utils.postLog(utils.trimAll(`
|
||||
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}
|
||||
Logs: ${logUrl}
|
||||
`));
|
||||
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed by ${closedBy}`);
|
||||
});
|
||||
|
||||
// Auto-close threads if their channel is deleted
|
||||
|
@ -144,10 +162,6 @@ module.exports = ({ bot, knex, config, commands }) => {
|
|||
|
||||
await thread.close(true);
|
||||
|
||||
const logUrl = await thread.getLogUrl();
|
||||
utils.postLog(utils.trimAll(`
|
||||
Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted
|
||||
Logs: ${logUrl}
|
||||
`));
|
||||
await sendCloseNotification(thread.id, `Modmail thread with ${thread.user_name} (${thread.user_id}) was closed automatically because the channel was deleted`);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
const threads = require("../data/threads");
|
||||
const moment = require("moment");
|
||||
const utils = require("../utils");
|
||||
const { getLogUrl, getLogFile, getLogCustomResponse, saveLogToStorage } = require("../data/logs");
|
||||
|
||||
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) => {
|
||||
let userId = args.userId || (thread && thread.user_id);
|
||||
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);
|
||||
|
||||
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]");
|
||||
return `\`${formattedDate}\`: <${logUrl}>`;
|
||||
return `\`${formattedDate}\`: ${formattedLogUrl}`;
|
||||
}));
|
||||
|
||||
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", "[page:number]", logsCmd);
|
||||
|
||||
commands.addInboxServerCommand("loglink", [], async (msg, args, thread) => {
|
||||
if (! thread) {
|
||||
thread = await threads.findSuspendedThreadByChannelId(msg.channel.id);
|
||||
if (! thread) return;
|
||||
}
|
||||
commands.addInboxServerCommand("log", "[threadId:string]", logCmd);
|
||||
commands.addInboxServerCommand("loglink", "[threadId:string]", logCmd);
|
||||
|
||||
const logUrl = await thread.getLogUrl();
|
||||
const query = [];
|
||||
if (args.verbose) query.push("verbose=1");
|
||||
if (args.simple) query.push("simple=1");
|
||||
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,
|
||||
},
|
||||
],
|
||||
hooks.afterThreadClose(async ({ threadId }) => {
|
||||
const thread = await threads.findById(threadId);
|
||||
const threadMessages = await thread.getThreadMessages();
|
||||
await saveLogToStorage(thread, threadMessages);
|
||||
});
|
||||
};
|
||||
|
|
|
@ -9,6 +9,7 @@ const Knex = require("knex");
|
|||
* @property {ModmailConfig} config
|
||||
* @property {PluginCommandsAPI} commands
|
||||
* @property {PluginAttachmentsAPI} attachments
|
||||
* @property {PluginLogsAPI} logs
|
||||
* @property {PluginHooksAPI} hooks
|
||||
* @property {FormattersExport} formats
|
||||
*/
|
||||
|
@ -28,6 +29,15 @@ const Knex = require("knex");
|
|||
* @property {DownloadAttachmentFn} downloadAttachment
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} PluginLogsAPI
|
||||
* @property {AddLogStorageTypeFn} addStorageType
|
||||
* @property {SaveLogToStorageFn} saveLogToStorage
|
||||
* @property {GetLogUrlFn} getLogUrl
|
||||
* @property {GetLogFileFn} getLogFile
|
||||
* @property {GetLogCustomResponseFn} getLogCustomResponse
|
||||
*/
|
||||
|
||||
/**
|
||||
* @typedef {object} PluginHooksAPI
|
||||
* @property {AddBeforeNewThreadHookFn} beforeNewThread
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
const attachments = require("./data/attachments");
|
||||
const logs = require("./data/logs");
|
||||
const { beforeNewThread } = require("./hooks/beforeNewThread");
|
||||
const { afterThreadClose } = require("./hooks/afterThreadClose");
|
||||
const formats = require("./formatters");
|
||||
|
@ -27,6 +28,13 @@ module.exports = {
|
|||
addStorageType: attachments.addStorageType,
|
||||
downloadAttachment: attachments.downloadAttachment
|
||||
},
|
||||
logs: {
|
||||
addStorageType: logs.addStorageType,
|
||||
saveLogToStorage: logs.saveLogToStorage,
|
||||
getLogUrl: logs.getLogUrl,
|
||||
getLogFile: logs.getLogFile,
|
||||
getLogCustomResponse: logs.getLogCustomResponse,
|
||||
},
|
||||
hooks: {
|
||||
beforeNewThread,
|
||||
afterThreadClose,
|
||||
|
|
|
@ -61,7 +61,7 @@ function getLogChannel() {
|
|||
}
|
||||
|
||||
function postLog(...args) {
|
||||
getLogChannel().createMessage(...args);
|
||||
return getLogChannel().createMessage(...args);
|
||||
}
|
||||
|
||||
function postError(channel, str, opts = {}) {
|
||||
|
|
Loading…
Reference in New Issue