Skip to content

Commit

Permalink
Add global middleware
Browse files Browse the repository at this point in the history
Functions the same as command middleware except it applies to ALL commands including base commands, and will be run before any command-specific middleware is run.
  • Loading branch information
zajrik committed Mar 14, 2017
1 parent fb33225 commit b1ecc4d
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 19 deletions.
42 changes: 42 additions & 0 deletions src/lib/bot/Bot.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { Command } from '../command/Command';
import { CommandLoader } from '../command/CommandLoader';
import { CommandRegistry } from '../command/CommandRegistry';
import { CommandDispatcher } from '../command/CommandDispatcher';
import { MiddlewareFunction } from '../types/MiddlewareFunction';

/**
* The Discord.js Client instance. Contains bot-specific [storage]{@link Bot#storage},
Expand All @@ -30,6 +31,7 @@ export class Bot extends Client
public version: string;
public disableBase: string[];
public config: any;
public _middleware: MiddlewareFunction[];

public storage: LocalStorage;
public guildStorages: GuildStorageRegistry<string, GuildStorage>;
Expand Down Expand Up @@ -148,6 +150,9 @@ export class Bot extends Client
*/
this.disableBase = botOptions.disableBase || [];

// Middleware function storage for the bot instance
this._middleware = [];

this._guildDataStorage = new LocalStorage('storage/guild-storage');
this._guildSettingStorage = new LocalStorage('storage/guild-settings');
this._guildStorageLoader = new GuildStorageLoader(this);
Expand Down Expand Up @@ -328,6 +333,43 @@ export class Bot extends Client
this._guildStorageLoader.cleanGuilds(this._guildDataStorage, this._guildSettingStorage);
}

/**
* Adds a middleware function to be used when any command is run
* to make modifications to args or determine if the command can
* be run. Takes a function that will receive the message object
* and the array of args.
*
* A middleware function must return an array where the first item
* is the message object and the second item is the args array.
* If a middleware function returns a string, or throws a string/error,
* it will be sent to the calling channel as a message and the command
* execution will be aborted. If a middleware function does not return
* anything or returns something other than an array or string, it will
* fail silently.
*
* Example:
* ```js
* this.use((message, args) => [message, args.map(a => a.toUpperCase())]);
* ```
* This will add a middleware function to all commands that will attempt
* to transform all args to uppercase. This will of course fail if any
* of the args are not a string.
*
* Note: Middleware functions should only be added to the bot one time each,
* and thus should not be added within any sort of event or loop.
* Multiple middleware functions can be added to the via multiple calls
* to this method
* @memberof Bot
* @instance
* @param {MiddlewareFunction} fn Middleware function. `(message, args) => [message, args]`
* @returns {Bot} This Bot instance
*/
public use(fn: MiddlewareFunction): this
{
this._middleware.push(fn);
return this;
}

//#region Discord.js events

public on(event: 'channelCreate', listener: (channel: Channel) => void): this;
Expand Down
14 changes: 6 additions & 8 deletions src/lib/command/Command.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Bot } from '../bot/Bot';
import { PermissionResolvable, Message } from 'discord.js';

import { Bot } from '../bot/Bot';
import { MiddlewareFunction } from '../types/MiddlewareFunction';
import { CommandInfo } from '../types/CommandInfo';
import { ArgOpts } from '../types/ArgOpts';

Expand Down Expand Up @@ -28,7 +28,7 @@ export class Command<T extends Bot>
public overloads: string;

public _classloc: string;
public _middleware: Array<(message: Message, args: string[]) => Promise<[Message, any[]]> | [Message, any[]]>;
public _middleware: MiddlewareFunction[];

public constructor(bot: T, info: CommandInfo = null)
{
Expand Down Expand Up @@ -253,19 +253,17 @@ export class Command<T extends Bot>
* ```
* This will add a middleware function to the command that will attempt
* to transform all args to uppercase. This will of course fail if any
* of the args are not a string. This could be ensured with the `stringArgs`
* `argOpts` flag, or via another middleware function added before this one
* that converts all args to strings.
* of the args are not a string.
*
* Note: Middleware functions should only be added to a command one time each,
* and thus should be added in the Command's constructor. Multiple middleware
* functions can be added to a command via multiple calls to this method
* @memberof Command
* @instance
* @param {Function} fn Middleware function. `(message, args) => [message, args]`
* @param {MiddlewareFunction} fn Middleware function. `(message, args) => [message, args]`
* @returns {Command} This command instance
*/
public use(fn: (message: Message, args: any[]) => Promise<[Message, any[]]> | [Message, any[]]): this
public use(fn: MiddlewareFunction): this
{
this._middleware.push(fn);
return this;
Expand Down
6 changes: 4 additions & 2 deletions src/lib/command/CommandDispatcher.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PermissionResolvable, TextChannel, User } from 'discord.js';
import { MiddlewareFunction } from '../types/MiddlewareFunction';
import { Message } from '../types/Message';
import { GuildStorage } from '../storage/GuildStorage';
import { Command } from '../command/Command';
Expand Down Expand Up @@ -54,11 +55,12 @@ export class CommandDispatcher<T extends Bot>
.filter(a => a !== '');

let middlewarePassed: boolean = true;
for (let middleware of command._middleware)
let middleware: MiddlewareFunction[] = this._bot._middleware.concat(command._middleware);
for (let func of middleware)
try
{
let result: Promise<[Message, any[]]> | [Message, any[]] =
middleware.call(command, message, args);
func.call(command, message, args);
if (result instanceof Promise) result = await result;
if (!(result instanceof Array))
{
Expand Down
3 changes: 2 additions & 1 deletion src/lib/command/middleware/Expect.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { Bot } from '../../bot/Bot';
import { MiddlewareFunction } from '../../types/MiddlewareFunction';
import { ExpectArgType } from '../../types/ExpectArgType';
import { Message } from '../../types/Message';
import { Command } from '../Command';
import { GuildMember, Role, TextChannel, User } from 'discord.js';

export function expect<T extends Bot, U extends Command<T>>(argTypes: { [name: string]: ExpectArgType }): (message: Message, args: any[]) => [Message, any[]]
export function expect<T extends Bot, U extends Command<T>>(argTypes: { [name: string]: ExpectArgType }): MiddlewareFunction
{
return function(message, args): [Message, any[]]
{
Expand Down
12 changes: 5 additions & 7 deletions src/lib/command/middleware/Middleware.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import { Message } from '../../types/Message';
import { resolveArgs } from './ResolveArgs';
import { expect } from './Expect';
import { Bot } from '../../bot/Bot';
import { Command } from '../Command';
import { expect } from './Expect';
import { resolveArgs } from './ResolveArgs';
import { MiddlewareFunction } from '../../types/MiddlewareFunction';
import { ResolveArgType } from '../../types/ResolveArgType';
import { ExpectArgType } from '../../types/ExpectArgType';

Expand Down Expand Up @@ -34,8 +34,7 @@ export class Middleware
* @returns {Function} <pre class="prettyprint"><code>(message: Message, args: any[]) => [Message, any[]]</code></pre>
*/
public static resolveArgs: <T extends Bot, U extends Command<T>>(argTypes: { [name: string]: ResolveArgType }) =>
(message: Message, args: any[]) =>
Promise<[Message, any[]]> = resolveArgs;
MiddlewareFunction = resolveArgs;

/**
* Takes an object mapping argument names to argument types that
Expand Down Expand Up @@ -63,6 +62,5 @@ export class Middleware
* @returns {Function} <pre class="prettyprint"><code>(message: Message, args: any[]) => [Message, any[]]</code></pre>
*/
public static expect: <T extends Bot, U extends Command<T>>(argTypes: { [name: string]: ExpectArgType }) =>
(message: Message, args: any[]) =>
[Message, any[]] = expect;
MiddlewareFunction = expect;
}
3 changes: 2 additions & 1 deletion src/lib/command/middleware/ResolveArgs.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { Bot } from '../../bot/Bot';
import { Message } from '../../types/Message';
import { MiddlewareFunction } from '../../types/MiddlewareFunction';
import { ResolveArgType } from '../../types/ResolveArgType';
import { Util } from '../../Util';
import { Command } from '../Command';
import { Collection, GuildMember, Role, TextChannel, User } from 'discord.js';

export function resolveArgs<T extends Bot, U extends Command<T>>(argTypes: { [name: string]: ResolveArgType }): (message: Message, args: any[]) => Promise<[Message, any[]]>
export function resolveArgs<T extends Bot, U extends Command<T>>(argTypes: { [name: string]: ResolveArgType }): MiddlewareFunction
{
return async function(message, args): Promise<[Message, any[]]>
{
Expand Down
11 changes: 11 additions & 0 deletions src/lib/types/MiddlewareFunction.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Message } from './Message';

export type MiddlewareFunction = (message: Message, args: any[]) => Promise<[Message, any[]]> | [Message, any[]];

/**
* @typedef {Function} MiddlewareFunction A function that takes a Message object and an array of args,
* does anything with them, and returns an array where the first item is the Message object and the
* second item is the array of args.<br><br>It should be noted that the command dispatcher will attempt
* to bind the called Command instance to the middleware function, so if it is not an arrow function
* `this` within a middleware function will be the Command instance at runtime
*/

0 comments on commit b1ecc4d

Please sign in to comment.