๐ Middleware
In the heart of your Robo, there lies a potent feature known as Middleware. These are functions that are executed before each and every command or event, granting you the ability to manipulate data, introduce conditional flows, and even halt execution of events or commands based on custom criteria.
They are like the night's watch, ever-vigilant before every event and command, including those nested inside modules and plugins!
Crafting Middleware ๐ ๏ธโ
Crafting middleware for your Robo is simple and straightforward. Create files inside the /src/middleware directory. The filenames don't really matter but remember they run alphabetically. Number prefixes can control the order (like 01-preliminary.js). Even if placed inside modules, middleware files still impact your entire Robo!
src/
โโโ middleware/
    โโโ 01-check-roles.js
    โโโ 02-set-timestamp.js
    โโโ 03-example-logger.js
The MiddlewareData Parcel ๐ฆโ
Each middleware function gets a MiddlewareData object which contains a payload array and a record object. The payload carries interaction objects that are passed to the event or command, while record provides insights about what's being intercepted.
The record object has key information:
- autoshows if the record was automatically generated.
- handleris a reference to the file handling the interaction. It usually contains a- defaultfunction and might have a- configobject.
- keyis a unique string representing a command or event. Subcommands appear as- parent/child.
- modulepoints to the module of the record. Nested modules appear as- parent/child.
- pathlocates the handler file.
- plugincontains name and path if the record is part of a plugin.
- typeindicates if the record is a 'command', 'context', or 'event'.
Manipulating Payloads ๐โ
Middleware is empowered to mutate the interaction objects, thus impacting the way events and commands behave:
export default function (data) {
	const [interaction] = data.payload
	interaction.middlewareTimestamp = Date.now()
}
Gatekeeping with Middleware ๐งโ
Middleware can be a gatekeeper, deciding if an event or command should proceed. If a middleware function returns { abort: true }, the event or command execution is halted. Here's an example:
import { isModuleEnabled } from './utils.js'
export default async function (data) {
	const moduleStatus = await isModuleEnabled(data.record.module)
	if (!moduleStatus) {
		return { abort: true }
	}
}
Command Control with Middleware ๐ฆโ
This ability can be leveraged for user roles-based access control:
export default function (data) {
	const [interaction] = data.payload
	const userRoles = interaction.member.roles.cache.map((role) => role.name)
	if (data.record.key.startsWith('admin/') && !userRoles.includes('Admin')) {
		interaction.reply('Sorry, this command is only for Admins!')
		return { abort: true }
	}
}
Efficient Middleware โฑ๏ธโ
Remember to keep your middleware lean and efficient. The execution of events and commands is on hold until middleware finishes. Keep the bot responsive!
Error Handling โ ๏ธโ
A crucial thing to note is that any errors thrown within a middleware will halt the execution of the associated event or command. Therefore, it's essential to implement robust error handling within your middleware to prevent unwanted disruptions.
Experimental Feature Warning ๐งโ
Middleware is still an experimental feature, and the API might change before we hit v1. Presently, it operates for all events, but future updates could let you specify target events. Stay tuned for updates!