Add full JSDocs for the plugin API

cshd
Dragory 2020-08-16 23:26:04 +03:00
parent 60ae79d4e4
commit d5219556a7
No known key found for this signature in database
GPG Key ID: 5F387BA66DF8AAC1
7 changed files with 204 additions and 68 deletions

View File

@ -7,17 +7,11 @@ The path is relative to the bot's folder.
Plugins are automatically loaded on bot startup. Plugins are automatically loaded on bot startup.
## Creating a plugin ## Creating a plugin
Create a `.js` file that exports a function. Plugins are simply `.js` files that export a function that gets called when the plugin is loaded.
This function will be called when the plugin is loaded, with 1 argument: an object that has the following properties:
* `bot` - the [Eris Client object](https://abal.moe/Eris/docs/Client)
* `knex` - the [Knex database object](https://knexjs.org/#Builder)
* `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
See [src/plugins.js#L4](../src/plugins.js#L4) for more details For details about the function arguments, see [Plugin API](#plugin-api) below.
### Example plugin file ### Example plugin
This example adds a command `!mycommand` that replies with `"Reply from my custom plugin!"` when the command is used inside a modmail inbox thread channel. This example adds a command `!mycommand` that replies with `"Reply from my custom plugin!"` when the command is used inside a modmail inbox thread channel.
```js ```js
module.exports = function({ bot, knex, config, commands }) { module.exports = function({ bot, knex, config, commands }) {
@ -40,6 +34,36 @@ 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"`.
### Plugin API
The first and only argument to the plugin function is an object with the following properties:
| Property | Description |
| -------- | ----------- |
| `bot` | [Eris Client instance](https://abal.moe/Eris/docs/Client) |
| `knex` | [Knex database object](https://knexjs.org/#Builder) |
| `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 |
| — `addStorageType(name, handler)` | Function to add a new attachment storage type |
| — `downloadAttachment(attachment)` | Function to add a new attachment storage type |
* `bot` - the [Eris Client object](https://abal.moe/Eris/docs/Client)
* `knex` - the [Knex database object](https://knexjs.org/#Builder)
* `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
* `attachments.addStorageType(name, handler)`
Create a `.js` file that exports a function.
This function will be called when the plugin is loaded, with 1 argument: an object that has the following properties:
* `bot` - the [Eris Client object](https://abal.moe/Eris/docs/Client)
* `knex` - the [Knex database object](https://knexjs.org/#Builder)
* `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
See [src/plugins.js#L4](../src/plugins.js#L4) for more details
## Work in progress ## Work in progress
The current plugin API is fairly rudimentary and will be expanded on in the future. The current plugin API is fairly rudimentary and will be expanded on in the future.
The API can change in non-major releases during this early stage. Keep an eye on [CHANGELOG.md](../CHANGELOG.md) for any changes. The API can change in non-major releases during this early stage. Keep an eye on [CHANGELOG.md](../CHANGELOG.md) for any changes.

View File

@ -5,6 +5,51 @@ const utils = require("./utils");
const threads = require("./data/threads"); const threads = require("./data/threads");
const Thread = require("./data/Thread"); const Thread = require("./data/Thread");
/**
* @callback CommandFn
* @param {Eris.Message} msg
* @param {object} args
*/
/**
* @callback InboxThreadCommandHandler
* @param {Eris.Message} msg
* @param {object} args
* @param {Thread} thread
*/
/**
* @callback AddGlobalCommandFn
* @param {string} trigger
* @param {string} parameters
* @param {CommandFn} handler
* @param {ICommandConfig} commandConfig
*/
/**
* @callback AddInboxServerCommandFn
* @param {string} trigger
* @param {string} parameters
* @param {CommandFn} handler
* @param {ICommandConfig} commandConfig
*/
/**
* @callback AddInboxThreadCommandFn
* Add a command that can only be invoked in a thread on the inbox server
*
* @param {string} trigger
* @param {string} parameters
* @param {InboxThreadCommandHandler} handler
* @param {ICommandConfig} commandConfig
*/
/**
* @callback AddAliasFn
* @param {string} originalCmd
* @param {string} alias
*/
module.exports = { module.exports = {
createCommandManager(bot) { createCommandManager(bot) {
const manager = new CommandManager({ const manager = new CommandManager({
@ -52,6 +97,7 @@ module.exports = {
/** /**
* Add a command that can be invoked anywhere * Add a command that can be invoked anywhere
* @type {AddGlobalCommandFn}
*/ */
const addGlobalCommand = (trigger, parameters, handler, commandConfig = {}) => { const addGlobalCommand = (trigger, parameters, handler, commandConfig = {}) => {
let aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : []; let aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : [];
@ -63,6 +109,7 @@ module.exports = {
/** /**
* Add a command that can only be invoked on the inbox server * Add a command that can only be invoked on the inbox server
* @type {AddInboxServerCommandFn}
*/ */
const addInboxServerCommand = (trigger, parameters, handler, commandConfig = {}) => { const addInboxServerCommand = (trigger, parameters, handler, commandConfig = {}) => {
const aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : []; const aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : [];
@ -86,19 +133,9 @@ module.exports = {
}; };
}; };
/**
* @callback InboxThreadCommandHandler
* @param {Eris.Message} msg
* @param {object} args
* @param {Thread} thread
*/
/** /**
* Add a command that can only be invoked in a thread on the inbox server * Add a command that can only be invoked in a thread on the inbox server
* @param {string|RegExp} trigger * @type {AddInboxThreadCommandFn}
* @param {string|IParameter[]} parameters
* @param {InboxThreadCommandHandler} handler
* @param {ICommandConfig} commandConfig
*/ */
const addInboxThreadCommand = (trigger, parameters, handler, commandConfig = {}) => { const addInboxThreadCommand = (trigger, parameters, handler, commandConfig = {}) => {
const aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : []; const aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : [];
@ -124,6 +161,9 @@ module.exports = {
}; };
}; };
/**
* @type {AddAliasFn}
*/
const addAlias = (originalCmd, alias) => { const addAlias = (originalCmd, alias) => {
if (! aliasMap.has(originalCmd)) { if (! aliasMap.has(originalCmd)) {
aliasMap.set(originalCmd, new Set()); aliasMap.set(originalCmd, new Set());

View File

@ -26,21 +26,54 @@ function getErrorResult(msg = null) {
} }
/** /**
* An attachment storage option that simply forwards the original attachment URL * @callback AddAttachmentStorageTypeFn
* @param {Eris.Attachment} attachment * @param {string} name
* @returns {{url: string}} * @param {AttachmentStorageTypeHandler} handler
*/ */
function passthroughOriginalAttachment(attachment) {
/**
* @callback AttachmentStorageTypeHandler
* @param {Eris.Attachment} attachment
* @return {AttachmentStorageTypeResult|Promise<AttachmentStorageTypeResult>}
*/
/**
* @typedef {object} AttachmentStorageTypeResult
* @property {string} url
*/
/**
* @callback DownloadAttachmentFn
* @param {Eris.Attachment} attachment
* @param {number?} tries Used internally, don't pass
* @return {Promise<DownloadAttachmentResult>}
*/
/**
* @typedef {object} DownloadAttachmentResult
* @property {string} path
* @property {DownloadAttachmentCleanupFn} cleanup
*/
/**
* @callback DownloadAttachmentCleanupFn
* @return {void}
*/
/**
* @type {AttachmentStorageTypeHandler}
*/
let passthroughOriginalAttachment; // Workaround to inconsistent IDE bug with @type and anonymous functions
passthroughOriginalAttachment = (attachment) => {
return { url: attachment.url }; return { url: attachment.url };
} };
/** /**
* An attachment storage option that downloads each attachment and serves them from a local web server * An attachment storage option that downloads each attachment and serves them from a local web server
* @param {Eris.Attachment} attachment * @type {AttachmentStorageTypeHandler}
* @param {Number=0} tries
* @returns {Promise<{ url: string }>}
*/ */
async function saveLocalAttachment(attachment) { let saveLocalAttachment; // Workaround to inconsistent IDE bug with @type and anonymous functions
saveLocalAttachment = async (attachment) => {
const targetPath = getLocalAttachmentPath(attachment.id); const targetPath = getLocalAttachmentPath(attachment.id);
try { try {
@ -60,14 +93,12 @@ async function saveLocalAttachment(attachment) {
const url = await getLocalAttachmentUrl(attachment.id, attachment.filename); const url = await getLocalAttachmentUrl(attachment.id, attachment.filename);
return { url }; return { url };
} };
/** /**
* @param {Eris.Attachment} attachment * @type {DownloadAttachmentFn}
* @param {Number} tries
* @returns {Promise<{ path: string, cleanup: function }>}
*/ */
function downloadAttachment(attachment, tries = 0) { const downloadAttachment = (attachment, tries = 0) => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
if (tries > 3) { if (tries > 3) {
console.error("Attachment download failed after 3 tries:", attachment); console.error("Attachment download failed after 3 tries:", attachment);
@ -94,7 +125,7 @@ function downloadAttachment(attachment, tries = 0) {
}); });
}); });
}); });
} };
/** /**
* Returns the filesystem path for the given attachment id * Returns the filesystem path for the given attachment id
@ -119,10 +150,10 @@ function getLocalAttachmentUrl(attachmentId, desiredName = null) {
/** /**
* An attachment storage option that downloads each attachment and re-posts them to a specified Discord channel. * An attachment storage option that downloads each attachment and re-posts them to a specified Discord channel.
* The re-posted attachment is then linked in the actual thread. * The re-posted attachment is then linked in the actual thread.
* @param {Eris.Attachment} attachment * @type {AttachmentStorageTypeHandler}
* @returns {Promise<{ url: string }>}
*/ */
async function saveDiscordAttachment(attachment) { let saveDiscordAttachment; // Workaround to inconsistent IDE bug with @type and anonymous functions
saveDiscordAttachment = async (attachment) => {
if (attachment.size > 1024 * 1024 * 8) { if (attachment.size > 1024 * 1024 * 8) {
return getErrorResult("attachment too large (max 8MB)"); return getErrorResult("attachment too large (max 8MB)");
} }
@ -144,7 +175,7 @@ async function saveDiscordAttachment(attachment) {
if (! savedAttachment) return getErrorResult(); if (! savedAttachment) return getErrorResult();
return { url: savedAttachment.url }; return { url: savedAttachment.url };
} };
async function createDiscordAttachmentMessage(channel, file, tries = 0) { async function createDiscordAttachmentMessage(channel, file, tries = 0) {
tries++; tries++;
@ -197,9 +228,12 @@ function saveAttachment(attachment) {
return attachmentSavePromises[attachment.id]; return attachmentSavePromises[attachment.id];
} }
function addStorageType(name, handler) { /**
* @type AddAttachmentStorageTypeFn
*/
const addStorageType = (name, handler) => {
attachmentStorageTypes[name] = handler; attachmentStorageTypes[name] = handler;
} };
addStorageType("original", passthroughOriginalAttachment); addStorageType("original", passthroughOriginalAttachment);
addStorageType("local", saveLocalAttachment); addStorageType("local", saveLocalAttachment);

View File

@ -212,6 +212,20 @@ const defaultFormatters = {
*/ */
const formatters = { ...defaultFormatters }; const formatters = { ...defaultFormatters };
/**
* @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(FormatLog): void} setLogFormatter
*/
/**
* @type {FormattersExport}
*/
module.exports = { module.exports = {
formatters: new Proxy(formatters, { formatters: new Proxy(formatters, {
set() { set() {
@ -219,50 +233,26 @@ module.exports = {
}, },
}), }),
/**
* @param {FormatStaffReplyDM} fn
* @return {void}
*/
setStaffReplyDMFormatter(fn) { setStaffReplyDMFormatter(fn) {
formatters.formatStaffReplyDM = fn; formatters.formatStaffReplyDM = fn;
}, },
/**
* @param {FormatStaffReplyThreadMessage} fn
* @return {void}
*/
setStaffReplyThreadMessageFormatter(fn) { setStaffReplyThreadMessageFormatter(fn) {
formatters.formatStaffReplyThreadMessage = fn; formatters.formatStaffReplyThreadMessage = fn;
}, },
/**
* @param {FormatUserReplyThreadMessage} fn
* @return {void}
*/
setUserReplyThreadMessageFormatter(fn) { setUserReplyThreadMessageFormatter(fn) {
formatters.formatUserReplyThreadMessage = fn; formatters.formatUserReplyThreadMessage = fn;
}, },
/**
* @param {FormatStaffReplyEditNotificationThreadMessage} fn
* @return {void}
*/
setStaffReplyEditNotificationThreadMessageFormatter(fn) { setStaffReplyEditNotificationThreadMessageFormatter(fn) {
formatters.formatStaffReplyEditNotificationThreadMessage = fn; formatters.formatStaffReplyEditNotificationThreadMessage = fn;
}, },
/**
* @param {FormatStaffReplyDeletionNotificationThreadMessage} fn
* @return {void}
*/
setStaffReplyDeletionNotificationThreadMessageFormatter(fn) { setStaffReplyDeletionNotificationThreadMessageFormatter(fn) {
formatters.formatStaffReplyDeletionNotificationThreadMessage = fn; formatters.formatStaffReplyDeletionNotificationThreadMessage = fn;
}, },
/**
* @param {FormatLog} fn
* @return {void}
*/
setLogFormatter(fn) { setLogFormatter(fn) {
formatters.formatLog = fn; formatters.formatLog = fn;
}, },

View File

@ -26,17 +26,24 @@ const Eris = require("eris");
* @return {void|Promise<void>} * @return {void|Promise<void>}
*/ */
/**
* @callback AddBeforeNewThreadHookFn
* @param {BeforeNewThreadHookData} fn
* @return {void}
*/
/** /**
* @type BeforeNewThreadHookData[] * @type BeforeNewThreadHookData[]
*/ */
const beforeNewThreadHooks = []; const beforeNewThreadHooks = [];
/** /**
* @param {BeforeNewThreadHookData} fn * @type {AddBeforeNewThreadHookFn}
*/ */
function beforeNewThread(fn) { let beforeNewThread; // Workaround to inconsistent IDE bug with @type and anonymous functions
beforeNewThread = (fn) => {
beforeNewThreadHooks.push(fn); beforeNewThreadHooks.push(fn);
} };
/** /**
* @param {{ * @param {{

34
src/pluginApi.js Normal file
View File

@ -0,0 +1,34 @@
const { CommandManager } = require("knub-command-manager");
const { Client } = require("eris");
const Knex = require("knex");
/**
* @typedef {object} PluginAPI
* @property {Client} bot
* @property {Knex} knex
* @property {ModmailConfig} config
* @property {PluginCommandsAPI} commands
* @property {PluginAttachmentsAPI} attachments
* @property {PluginHooksAPI} hooks
* @property {FormattersExport} formats
*/
/**
* @typedef {object} PluginCommandsAPI
* @property {CommandManager} manager
* @property {AddGlobalCommandFn} addGlobalCommand
* @property {AddInboxServerCommandFn} addInboxServerCommand
* @property {AddInboxThreadCommandFn} addInboxThreadCommand
* @property {AddAliasFn} addAlias
*/
/**
* @typedef {object} PluginAttachmentsAPI
* @property {AddAttachmentStorageTypeFn} addStorageType
* @property {DownloadAttachmentFn} downloadAttachment
*/
/**
* @typedef {object} PluginHooksAPI
* @property {AddBeforeNewThreadHookFn} beforeNewThread
*/

View File

@ -3,6 +3,13 @@ const { beforeNewThread } = require("./hooks/beforeNewThread");
const formats = require("./formatters"); const formats = require("./formatters");
module.exports = { module.exports = {
/**
* @param bot
* @param knex
* @param config
* @param commands
* @returns {PluginAPI}
*/
getPluginAPI({ bot, knex, config, commands }) { getPluginAPI({ bot, knex, config, commands }) {
return { return {
bot, bot,