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.
## Creating a plugin
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
Plugins are simply `.js` files that export a function that gets called when the plugin is loaded.
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.
```js
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"`.
### 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
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.

View File

@ -5,6 +5,51 @@ const utils = require("./utils");
const threads = require("./data/threads");
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 = {
createCommandManager(bot) {
const manager = new CommandManager({
@ -52,6 +97,7 @@ module.exports = {
/**
* Add a command that can be invoked anywhere
* @type {AddGlobalCommandFn}
*/
const addGlobalCommand = (trigger, parameters, handler, commandConfig = {}) => {
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
* @type {AddInboxServerCommandFn}
*/
const addInboxServerCommand = (trigger, parameters, handler, commandConfig = {}) => {
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
* @param {string|RegExp} trigger
* @param {string|IParameter[]} parameters
* @param {InboxThreadCommandHandler} handler
* @param {ICommandConfig} commandConfig
* @type {AddInboxThreadCommandFn}
*/
const addInboxThreadCommand = (trigger, parameters, handler, commandConfig = {}) => {
const aliases = aliasMap.has(trigger) ? [...aliasMap.get(trigger)] : [];
@ -124,6 +161,9 @@ module.exports = {
};
};
/**
* @type {AddAliasFn}
*/
const addAlias = (originalCmd, alias) => {
if (! aliasMap.has(originalCmd)) {
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
* @param {Eris.Attachment} attachment
* @returns {{url: string}}
* @callback AddAttachmentStorageTypeFn
* @param {string} name
* @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 };
}
};
/**
* An attachment storage option that downloads each attachment and serves them from a local web server
* @param {Eris.Attachment} attachment
* @param {Number=0} tries
* @returns {Promise<{ url: string }>}
* @type {AttachmentStorageTypeHandler}
*/
async function saveLocalAttachment(attachment) {
let saveLocalAttachment; // Workaround to inconsistent IDE bug with @type and anonymous functions
saveLocalAttachment = async (attachment) => {
const targetPath = getLocalAttachmentPath(attachment.id);
try {
@ -60,14 +93,12 @@ async function saveLocalAttachment(attachment) {
const url = await getLocalAttachmentUrl(attachment.id, attachment.filename);
return { url };
}
};
/**
* @param {Eris.Attachment} attachment
* @param {Number} tries
* @returns {Promise<{ path: string, cleanup: function }>}
* @type {DownloadAttachmentFn}
*/
function downloadAttachment(attachment, tries = 0) {
const downloadAttachment = (attachment, tries = 0) => {
return new Promise((resolve, reject) => {
if (tries > 3) {
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
@ -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.
* The re-posted attachment is then linked in the actual thread.
* @param {Eris.Attachment} attachment
* @returns {Promise<{ url: string }>}
* @type {AttachmentStorageTypeHandler}
*/
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) {
return getErrorResult("attachment too large (max 8MB)");
}
@ -144,7 +175,7 @@ async function saveDiscordAttachment(attachment) {
if (! savedAttachment) return getErrorResult();
return { url: savedAttachment.url };
}
};
async function createDiscordAttachmentMessage(channel, file, tries = 0) {
tries++;
@ -197,9 +228,12 @@ function saveAttachment(attachment) {
return attachmentSavePromises[attachment.id];
}
function addStorageType(name, handler) {
/**
* @type AddAttachmentStorageTypeFn
*/
const addStorageType = (name, handler) => {
attachmentStorageTypes[name] = handler;
}
};
addStorageType("original", passthroughOriginalAttachment);
addStorageType("local", saveLocalAttachment);

View File

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

View File

@ -26,17 +26,24 @@ const Eris = require("eris");
* @return {void|Promise<void>}
*/
/**
* @callback AddBeforeNewThreadHookFn
* @param {BeforeNewThreadHookData} fn
* @return {void}
*/
/**
* @type BeforeNewThreadHookData[]
*/
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);
}
};
/**
* @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");
module.exports = {
/**
* @param bot
* @param knex
* @param config
* @param commands
* @returns {PluginAPI}
*/
getPluginAPI({ bot, knex, config, commands }) {
return {
bot,