discord-akairo
Version:
A highly customizable bot framework for Discord.js.
377 lines (329 loc) • 12.7 kB
JavaScript
const AkairoError = require('../../../util/AkairoError');
const Argument = require('./Argument');
const { ArgumentMatches } = require('../../../util/Constants');
const Flag = require('../Flag');
/**
* Runs arguments.
* @param {Command} command - Command to run for.
* @private
*/
class ArgumentRunner {
constructor(command) {
this.command = command;
}
/**
* The Akairo client.
* @type {AkairoClient}
*/
get client() {
return this.command.client;
}
/**
* The command handler.
* @type {CommandHandler}
*/
get handler() {
return this.command.handler;
}
/**
* Runs the arguments.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentGenerator} generator - Argument generator.
* @returns {Promise<Flag|any>}
*/
async run(message, parsed, generator) {
const state = {
usedIndices: new Set(),
phraseIndex: 0,
index: 0
};
const augmentRest = val => {
if (Flag.is(val, 'continue')) {
val.rest = parsed.all.slice(state.index).map(x => x.raw).join('');
}
};
const iter = generator(message, parsed, state);
let curr = await iter.next();
while (!curr.done) {
const value = curr.value;
if (ArgumentRunner.isShortCircuit(value)) {
augmentRest(value);
return value;
}
const res = await this.runOne(message, parsed, state, new Argument(this.command, value));
if (ArgumentRunner.isShortCircuit(res)) {
augmentRest(res);
return res;
}
curr = await iter.next(res);
}
augmentRest(curr.value);
return curr.value;
}
/**
* Runs one argument.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
runOne(message, parsed, state, arg) {
const cases = {
[ArgumentMatches.PHRASE]: this.runPhrase,
[ArgumentMatches.FLAG]: this.runFlag,
[ArgumentMatches.OPTION]: this.runOption,
[ArgumentMatches.REST]: this.runRest,
[ArgumentMatches.SEPARATE]: this.runSeparate,
[ArgumentMatches.TEXT]: this.runText,
[ArgumentMatches.CONTENT]: this.runContent,
[ArgumentMatches.REST_CONTENT]: this.runRestContent,
[ArgumentMatches.NONE]: this.runNone
};
const runFn = cases[arg.match];
if (runFn == null) {
throw new AkairoError('UNKNOWN_MATCH_TYPE', arg.match);
}
return runFn.call(this, message, parsed, state, arg);
}
/**
* Runs `phrase` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
async runPhrase(message, parsed, state, arg) {
if (arg.unordered || arg.unordered === 0) {
const indices = typeof unordered === 'number'
? Array.from(parsed.phrases.keys()).slice(arg.unordered)
: Array.isArray(arg.unordered)
? arg.unordered
: Array.from(parsed.phrases.keys());
for (const i of indices) {
if (state.usedIndices.has(i)) {
continue;
}
const phrase = parsed.phrases[i] ? parsed.phrases[i].value : '';
// `cast` is used instead of `process` since we do not want prompts.
const res = await arg.cast(message, phrase);
if (res != null) {
state.usedIndices.add(i);
return res;
}
}
// No indices matched.
return arg.process(message, '');
}
const index = arg.index == null ? state.phraseIndex : arg.index;
const ret = arg.process(message, parsed.phrases[index] ? parsed.phrases[index].value : '');
if (arg.index == null) {
ArgumentRunner.increaseIndex(parsed, state);
}
return ret;
}
/**
* Runs `rest` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
async runRest(message, parsed, state, arg) {
const index = arg.index == null ? state.phraseIndex : arg.index;
const rest = parsed.phrases.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
const ret = await arg.process(message, rest);
if (arg.index == null) {
ArgumentRunner.increaseIndex(parsed, state);
}
return ret;
}
/**
* Runs `separate` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
async runSeparate(message, parsed, state, arg) {
const index = arg.index == null ? state.phraseIndex : arg.index;
const phrases = parsed.phrases.slice(index, index + arg.limit);
if (!phrases.length) {
const ret = await arg.process(message, '');
if (arg.index != null) {
ArgumentRunner.increaseIndex(parsed, state);
}
return ret;
}
const res = [];
for (const phrase of phrases) {
const response = await arg.process(message, phrase.value);
if (Flag.is(response, 'cancel')) {
return response;
}
res.push(response);
}
if (arg.index != null) {
ArgumentRunner.increaseIndex(parsed, state);
}
return res;
}
/**
* Runs `flag` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
runFlag(message, parsed, state, arg) {
const names = Array.isArray(arg.flag) ? arg.flag : [arg.flag];
if (arg.multipleFlags) {
const amount = parsed.flags.filter(flag =>
names.some(name =>
name.toLowerCase() === flag.key.toLowerCase()
)
).length;
return amount;
}
const flagFound = parsed.flags.some(flag =>
names.some(name =>
name.toLowerCase() === flag.key.toLowerCase()
)
);
return arg.default == null ? flagFound : !flagFound;
}
/**
* Runs `option` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
async runOption(message, parsed, state, arg) {
const names = Array.isArray(arg.flag) ? arg.flag : [arg.flag];
if (arg.multipleFlags) {
const values = parsed.optionFlags.filter(flag =>
names.some(name =>
name.toLowerCase() === flag.key.toLowerCase()
)
).map(x => x.value).slice(0, arg.limit);
const res = [];
for (const value of values) {
res.push(await arg.process(message, value));
}
return res;
}
const foundFlag = parsed.optionFlags.find(flag =>
names.some(name =>
name.toLowerCase() === flag.key.toLowerCase()
)
);
return arg.process(message, foundFlag != null ? foundFlag.value : '');
}
/**
* Runs `text` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
runText(message, parsed, state, arg) {
const index = arg.index == null ? 0 : arg.index;
const text = parsed.phrases.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
return arg.process(message, text);
}
/**
* Runs `content` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
runContent(message, parsed, state, arg) {
const index = arg.index == null ? 0 : arg.index;
const content = parsed.all.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
return arg.process(message, content);
}
/**
* Runs `restContent` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
async runRestContent(message, parsed, state, arg) {
const index = arg.index == null ? state.index : arg.index;
const rest = parsed.all.slice(index, index + arg.limit).map(x => x.raw).join('').trim();
const ret = await arg.process(message, rest);
if (arg.index == null) {
ArgumentRunner.increaseIndex(parsed, state);
}
return ret;
}
/**
* Runs `none` match.
* @param {Message} message - Message that triggered the command.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {Argument} arg - Current argument.
* @returns {Promise<Flag|any>}
*/
runNone(message, parsed, state, arg) {
return arg.process(message, '');
}
/**
* Modifies state by incrementing the indices.
* @param {ContentParserResult} parsed - Parsed data from ContentParser.
* @param {ArgumentRunnerState} state - Argument handling state.
* @param {number} n - Number of indices to increase by.
* @returns {Promise<Flag|any>}
*/
static increaseIndex(parsed, state, n = 1) {
state.phraseIndex += n;
while (n > 0) {
do {
state.index++;
} while (parsed.all[state.index] && parsed.all[state.index].type !== 'Phrase');
n--;
}
}
/**
* Checks if something is a flag that short circuits.
* @param {any} value - A value.
* @returns {boolean}
*/
static isShortCircuit(value) {
return Flag.is(value, 'cancel') || Flag.is(value, 'retry') || Flag.is(value, 'continue');
}
/**
* Creates an argument generator from argument options.
* @param {ArgumentOptions[]} args - Argument options.
* @returns {GeneratorFunction}
*/
static fromArguments(args) {
return function* generate() {
const res = {};
for (const [id, arg] of args) {
res[id] = yield arg;
}
return res;
};
}
}
module.exports = ArgumentRunner;
/**
* State for the argument runner.
* @typedef {Object} ArgumentRunnerState
* @prop {Set<number>} usedIndices - Indices already used for unordered match.
* @prop {number} phraseIndex - Index in terms of phrases.
* @prop {number} index - Index in terms of the raw strings.
*/