Releases: discord-akairo/discord-akairo
8.1.0
Changelog
Additions
- Added MongooseProvider for use with the
mongoose
library. Thanks to @zerefdev. - Added support for promises in
Command#condition
. Thanks to @papaia.
Changes
- Removed peer deps due to npm 7. Thanks to @papaia.
- Many documentation fixes and improvements. Thanks to all who contributed.
Fixes
- Fixed
Argument.union
andArgument.product
not bindingthis
. Thanks to @perilstar. - Fixed
separate
match not returning short circuit flags. Thanks to @Norviah. - Fixed undefined timer in cooldowns. Thanks to @Mzato0001.
- Fixed typings of
CommandHandler#cooldowns
. Thanks to @papaia. - Fixed typings of
Constants
. Thanks to @papaia.
8.0.0
Akairo has been updated to Node 10 and Discord.js v12, now taking advantage of async/await!
Many breaking changes come in this version in order to make the framework better and more consistent.
For help with updating from v7 to v8, see the guide here.
Also, join the official Akairo Discord server and ask questions!
Summary
Structural Rework of Handlers
Before, the client and the handlers were intertwined and this made it hard for customization.
With this rework, it is easier to do things like your own command handler and such.
Your custom client class will now look something like this:
class CoolClient extends Client {
constructor() {
super({ <options> });
this.commandHandler = new CommandHandler(this, { <options> });
// etc.
}
start() {
this.commandHandler.loadAll();
// etc.
}
}
Command Locks
The command locks system allow you to lock down a command while it is in use.
For example, a command can only be used one at a time in a guild, in a channel, by a user, or by your own metric.
class SpecialCommand extends Command {
constructor() {
super('special', {
<options>
lock: 'channel'
// or perhaps, returning a string:
// lock: msg => something(msg.author)
});
}
// etc.
}
Generator Arguments
A large rework into how commands and arguments are parsed and processed have been done.
The main thing is that arguments are now processed via generators, which will allow you to put JavaScript into your argument handling:
class NumbersCommand extends Command {
constructor() {
super('numbers', { <options> });
}
*args() {
const x = yield { type: 'integer', default: 0 };
const y = yield { type: x > 10 ? 'integer' : 'string' };
console.log('debug', x, y);
return { x, y };
}
}
This method supports async and also allows you to implement your own parsing if you wish, with the hard part taken care of:
async *args(message, parsed, state) {
await message.util.send('Hold on I\'m working on it...');
console.log(parsed.phrases);
// ['a', 'bunch', 'of', 'input']
}
The old method still works if you do not use arguments that depend on previous arguments, so you can still have args: [{ <options> }, <more args>]
.
If you do, note that the ability to access previous arguments has been removed, e.g. type: (phrase, message, args) => <code>
has been changed to type: (message, phrase) => <code>
.
Cancelling Arguments
A utility known as Flag
has been added, which allow you to signify special operations.
For example, to cancel a command during argument handling, use Flag.cancel
:
async *args(message) {
const x = yield { type: 'integer' };
if (x == null) {
await message.util.send('No integer found!');
return Flag.cancel();
}
const y = yield { type: x > 10 ? 'integer' : 'string' };
return { x, y };
}
Expressing Failure
To express failure inside argument parsing, normally you would return null
.
However, sometimes more data needs to be given, e.g. why the failure happened or what caused the failure.
Here, the new Flag.fail
can be used:
type: (msg, phrase) => {
const toMembers = this.handler.resolver.type('members');
const members = toMembers(phrase);
if (members.size !== 1) {
return Flag.fail(member.size);
}
return members;
}
You can also use it when choosing a default, e.g. default: (message, { failure }) => <code>
.
Otherwise
To cancel the command early in argument parsing, you can use Flag.cancel
as in above, but you can also do it a bit more automatically with the otherwise
option.
It will send some message when the argument fails parsing.
Combine it with Flag.fail
for some good user-facing error messages!
type: (msg, phrase) => {
const toMembers = this.handler.resolver.type('members');
const members = toMembers(phrase);
if (members.size !== 1) {
return Flag.fail(member.size);
}
return members;
},
otherwise: (msg, { failure }) => `Bad input, that gave me ${failure.value} members instead of one!`
// Or just `otherwise: 'Bad input!'` if you want.
Continuing Parsing
To facilitate subcommands, there is also Flag.continue
.
Using it, you can run another command with the rest of the phrases that are not yet parsed.
*args() {
const sub = yield {
type: ['cmd1', 'cmd2', 'cmd3'],
default: 'cmd1'
};
// Will run the command with ID `cmd1`, `cmd2`, or `cmd3`.
return Flag.continue(sub);
}
So now, if a user is to type !command cmd1 1 2 3
, the input 1 2 3
will be passed on to cmd1
to parse and run.
Unordered Arguments
A new argument match type is unordered
which allows for more natural user input.
That is, the user can enter their arguments in any order as they want and Akairo will pick up the right ones!
args: [
{
id: 'member',
type: 'member',
match: 'unordered'
},
{
id: 'number',
type: 'number',
match: 'unordered'
}
]
Now, !command person 5
works as expected, but also !command 5 person
.
Of course, if there is ambiguity, the thing that comes first gets priority (e.g. someone might be named 5
).
Composing Types
So that you do not have to use type functions as much, there are new static Argument
functions to help you out!
// Either a user or an integer or a string .
type: Argument.union('user', 'integer', 'string')
// Everything at once, will give back [integer, number, string].
type: Argument.product('integer', 'number', 'string')
// Check that some property of the parsed value is correct.
type: Argument.validate('string', (msg, value) => value.length === 100)
// Shortcut for number ranges:
type: Argument.range('integer', 1, 100)
// Attach the input with the result or failure using `Flag.fail`.
type: Argument.withInput('integer')
Better Prompts
For prompts, some extra data has been added so that you can customize your prompts to your heart's content.
In conjuction with Flag.fail
, it is very powerful!
const members = yield {
type: (msg, phrase) => {
const toMembers = this.handler.resolver.type('members');
const members = toMembers(phrase);
if (members.size !== 1) {
return Flag.fail(member.size);
}
return members;
},
prompt: {
start: 'Please give me a member!',
retry: (msg, { failure }) => `Please refine your search, ${failure.data} members were found.`
}
};
The second parameter also contains information like how many retries there has been, the message that caused the prompt, etc.
And More!
Other important features include:
- Filtering files for handlers to load.
- Promise support in more places, e.g. prefixes and permissions.
- More information passed to events.
- More
CommandUtil
parse properties. - Better handling and choosing of prefixes.
- More built-in argument types.
- Argument match
separate
for handling phrases one by one. - Breaking out of prompts by using another command.
- Default text and text modifications for prompts.
- Consistency in callback parameter ordering.
- Consistency in callback return types.
There are even more features and also a lot of breaking changes.
Read the full changelog below for more.
Changelog
General
- Added many previously private/protected methods to the public interface.
- Added
AkairoClient#isOwner
for owner check. - Added an
extensions
option for AkairoHandler to load only files with those extensions. - Added a
loadFilter
option for AkairoHandler to filter files. - Added parameter
directory
toAkairoHandler#loadAll
in order to load from a specified directory. - Added parameter
filter
toAkairoHandler#loadAll
to filter files to load. - Added
ClientUtil#attachment
. - Added support for Promise values in more places.
- Permissions checks in commands.
- Argument default value function.
- Prefix and allow mention functions.
- Prompt content functions.
- Changed errors thrown by Akairo to instances of AkairoError (extends Error).
- Changed structure of AkairoClient.
- It no longer takes the options meant for other handlers.
- It no longer automatically create the handlers.
- You must now create and build the handlers manually, see the updating guide for an example.
- Changed all handlers' constructors to
(client, options)
. - Changed all modules' constructors to
(id, options)
. - Changed methods into properties (this means you may have to check if they are a function before using them):
Argument#default
Command#regex
(previouslyCommand#trigger
)CommandHandler#prefix
CommandHandler#allowMention
- Changed
AkairoClient#load
event to pass anisReload
param. - Changed type checks for Promise and EventEmitter, polyfilled items (e.g. Bluebird, EventEmitter 2/3/4) will now work.
- Renamed
ClientUtil#fetchMemberFrom
toClientUtil#fetchMember
. - Renamed typedefs in typings.
- Removed support for selfbots.
- Removed support for instance exports.
- This means
module.exports = new Command(...)
, for example, will no longer be loaded. - This also means
AkairoHandler#load
no longer takes a module instance. - Removed
AkairoModule#exec
, should be im...
- This means
8.0.0-beta.8
This update fixes the NPM release.
8.0.0-beta.7
Fixes
- Fixed
compose
andcomposeWithFailure
.
8.0.0-beta.6
Changes
- Added
Argument.tagged
andArgument.taggedWithInput
. - Added
Argument.taggedUnion
. - Added
Argument.composeWithFailure
, splits off the last option ofArgument.compose
. - Changed
otherwise
andmodifyOtherwise
to allow an empty string, which will do nothing, like propmts. - Changed
Argument.compose
(and thereforeArgument.composeWithFailure
) to work with variable arguments. - Changed
Argument.range
to work on things with alength
orsize
property. - Changed the name of
Argument.tuple
toArgument.product
. - Changed typings and documentation to specify more about objects.
Fixes
- Fixed typings for
argumentDefaults
. - Fixed
otherwise
being used when an argument doesn't have it specified.
8.0.0-beta.5
This release is last known to work with discord.js commit 04fa56d
.
Changes
- Added
modifyOtherwise
. - Replaced
defaultPrompt
withargumentDefaults
which includeprompt
,otherwise
, andmodifyOtherwise
.
Fixes
- Fixed
this
context of*args
functions.
8.0.0-beta.4
This release has no code changes, only documentation changes.
7.5.6
This release has no code changes, only documentation changes.
8.0.0-beta.3
This release is last known to work with discord.js commit 9b2bf03
.
Changes
- Added
restContent
match which works likerest
but also matches flags. - Changed
Flag.continue
to also take the rest of the flags (i.e. it works likerestContent
instead ofrest
now).- This is for the rare case where the parent command of a subcommand has flags.
It would make more sense for the flags after the subcommand name to be delegated to the subcommand instead of the parent command.
If you wish to have therest
behavior, you can matchrest
yourself.
- This is for the rare case where the parent command of a subcommand has flags.
Fixes
- Fixed
text
andcontent
match behaviors being switched around. - Fixed
content
andrest
match not keeping whitespace and quotes.
8.0.0-beta.2
This release is last known to work with discord.js commit 9b2bf03
.
The focus of this release is a complete rewrite of how arguments are handled internally, which lends itself to some awesome new features!
Of course, there are breaking changes from 8.0.0-beta.1, but if you are reading this, you were prepared anyways.
Changes
Internals
- Removed ArgumentParser, replaced by ArgumentRunner which is for internal use only.
- Rewrote ContentParser, it is now for internal use only.
- Reworked ParsingFlag and renamed it to Flag.
- Removed Control; new way to control parsing is to use generators, see below.
Commands
- Removed
Command#args
. - Removed
Command#parser
. - Removed
ArgumentOptions#description
andArgument#description
.
Arguments
- Changed
message
parameters by moving them to the front of functions and removed previous arguments from ALL functions.- e.g.
type
is now(message, phrase)
instead of(phrase, message, args)
. - e.g. prompt
start
is now(message, data)
instead of(message, args, data)
. - And others; new way to access previous arguments is to use generators, see below.
- e.g.
- Changed phrase index to not increase when using unordered arguments or when the
index
option is set. - Changed argument option
multipleFlags
to be affected bylimit
and also work onflag
match by counting occurences.
Additions
Arguments
- Reimplemented arguments with generators.
- Old way still works but will just be a weaker version of this.
- Removes the need for Control and previous argument parameters.
// Takes `(message, parsed, state)`. // The latter two are internal data but can be useful. async *args(message) { // Get arguments as normal using `yield`. const x = yield { type: 'integer', default: 0 }; // Use previous arguments by referring to the identifier. // This replaces `Control.if` and `Control.case`. const y = yield (x > 10 ? { type: 'integer' } : { type: 'string' }); // `Control.do` is replaced by just doing it: console.log('debug', x, y); // If you want to do what `Control.cancel` did, use `Flag.cancel`. // Before, this would've been a combination of `Control.if`, `Control.do`, and `Control.cancel`. Ew! if (x > 10 && !y) { await message.util.send('`y` cannot be empty!'); return Flag.cancel(); } // When done (equivalent to `Control.end`), return what you need: return { x, y }; }
- Flags require some options to parse since we can no longer predict them.
- When using the old way, we can still predict them, so these options are ignored then.
flags: ['--f'], optionFlags: ['--o'], *args() { const f = yield { match: 'flag', flag: '--f' }; const o = yield { match: 'option', flag: '--o' }; return { f, o }; }
- Added new
Flag.fail
for failing argument parsing with more data.- Acts like
null
orundefined
but will give you extra data in itsvalue
property. - Parse failures are now passed as the
failure
property (along withphrase
) in the second parameter ofdefault
functions. - And also as
failure
in ArgumentPromptData.type: (msg, phrase) => { const toMembers = this.handler.resolver.type('members'); const members = toMembers(phrase); if (members.size !== 1) { return Flag.fail(member.size); } return members; }, prompt: { start: 'Please give me a member!', retry: (msg, { failure }) => `Please refine your search, ${failure.value} members were found.` }
- Acts like
- Added new
Flag.continue
for subcommands-like behaviour.- Will continue the argument parsing in another command with the rest of the input.
- Also option for ignoring permission checks and continuing with some other input.
*args() { const sub = yield { type: ['cmd1', 'cmd2', 'cmd3'], default: 'cmd1' }; return Flag.continue(sub); }
- Added argument option
otherwise
that sends some message when argument parsing fails.- Has information like
default
. - Overrides
default
and prompts.
type: 'integer', otherwise: 'An integer was expected!' // Also: type: 'integer', otherwise: (msg, { phrase }) => `An integer was expected! You gave ${phrase || 'nothing'}, which is not one.`
- Has information like
- Added
Argument.withInput
that attaches the original phrase along with the output. - Added
Argument.isFailure
that checks if something is a failure (null
,undefined
, fail flag).