Added examples, improved readme
This commit is contained in:
parent
a2e627c691
commit
b41c076181
84
README.md
84
README.md
@ -1,40 +1,46 @@
|
|||||||
A command parser initially made for quickly making discord bots, but works as a standalone parser too.
|
# Command Parser
|
||||||
|
|
||||||
Your commands are expected to inherit from the Command class
|
A command parser initially made for quickly making discord bots, but works as a standalone parser too.
|
||||||
|
|
||||||
**Example usage**
|
Your commands are expected to inherit from the Command class or implement the ICommand interface (requires slightly more work given that interfaces don't provide default implementations for the required functionality). The latter is required if you for any reason need to extend your own superclass of command/component. See examples for more about extending/implementing.
|
||||||
```js
|
|
||||||
|
Example projects that implement the parser:
|
||||||
const commands: Command[] = [
|
[Discord music bot by me](https://git.corgi.wtf/Navy.gif/music-bot)
|
||||||
new Command({
|
[My webserver template](https://git.corgi.wtf/Navy.gif/webserver-framework) (uses parser for CLI and listening for input on stdin)
|
||||||
name: 'ping',
|
|
||||||
options: [
|
**Example usage**
|
||||||
{
|
```js
|
||||||
name: 'respond', //
|
|
||||||
type: OptionType.BOOLEAN, // Boolean type expects the parser to have some kind of string -> boolean resolver, see the resolver interface for which methods are expected
|
const commands: Command[] = [
|
||||||
required: true, // Will throw error if not provided
|
new Command({
|
||||||
defaultValue: true, // The value that will be given back if no value is given, or if option is required but not provided
|
name: 'ping',
|
||||||
flag: true // The parser will look for the option in the "--respond value" form
|
options: [
|
||||||
}
|
{
|
||||||
]
|
name: 'respond', //
|
||||||
})
|
type: OptionType.BOOLEAN, // Boolean type expects the parser to have some kind of string -> boolean resolver, see the resolver interface for which methods are expected
|
||||||
]; // Command definitions
|
required: true, // Will throw error if not provided
|
||||||
|
defaultValue: true, // The value that will be given back if no value is given, or if option is required but not provided
|
||||||
const parser = new Parser({commands, prefix: ''}); // Empty prefix
|
flag: true // The parser will look for the option in the "--respond value" form
|
||||||
const parsed = await parser.parseMessage('ping --respond');
|
}
|
||||||
|
]
|
||||||
/**
|
})
|
||||||
* Parsed will be an object cotaining properties args, command, subcommand, and subcommandgroup
|
]; // Command definitions
|
||||||
* the args property is an object of the given options mapped by their names, e.g.
|
|
||||||
* {
|
const parser = new Parser({commands, prefix: ''}); // Empty prefix
|
||||||
* respond: {
|
const parsed = await parser.parseMessage('ping --respond');
|
||||||
* ... properties,
|
|
||||||
* value: true
|
/**
|
||||||
* }
|
* Parsed will be an object cotaining properties args, command, subcommand, and subcommandgroup
|
||||||
* }
|
* the args property is an object of the given options mapped by their names, e.g.
|
||||||
*
|
* {
|
||||||
*/
|
* respond: {
|
||||||
|
* ... properties,
|
||||||
```
|
* value: true
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
Some of the option types have built-in parsers (e.g. numbers, strings), but others will require the resolver implementation, and in some cases CommandOption extensions.
|
Some of the option types have built-in parsers (e.g. numbers, strings), but others will require the resolver implementation, and in some cases CommandOption extensions.
|
190
examples/Command.ts
Normal file
190
examples/Command.ts
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
/**
|
||||||
|
* SAMPLE CODE
|
||||||
|
* Reference for how to extend a command option and implement a custom
|
||||||
|
* command that inherits a custom superclass while being compatible with the parser by implementing the interface
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
import { ICommand, OptionType, CommandOpts, SubcommandOption, SubcommandGroupOption } from '@navy.gif/commandparser';
|
||||||
|
import ExtendedCommandOption from './ExtendedCommandOption.js';
|
||||||
|
|
||||||
|
|
||||||
|
type Snowflake = string
|
||||||
|
type DiscordClient = {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComponentOptions = {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandDefinition = {
|
||||||
|
aliases: never[];
|
||||||
|
help: string | undefined;
|
||||||
|
restricted: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
guildOnly: boolean;
|
||||||
|
dmOnly: boolean;
|
||||||
|
limited: null;
|
||||||
|
options: ExtendedCommandOption[];
|
||||||
|
}
|
||||||
|
|
||||||
|
type User = {
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
class Component
|
||||||
|
{
|
||||||
|
// eslint-disable-next-line no-useless-constructor
|
||||||
|
constructor (_client: DiscordClient, _options: ComponentOptions)
|
||||||
|
{
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
|
get name ()
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class CommandError extends Error
|
||||||
|
{
|
||||||
|
constructor (_command: Command, _options: object)
|
||||||
|
{
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract class Command extends Component implements ICommand
|
||||||
|
{
|
||||||
|
#aliases: string[];
|
||||||
|
#options: ExtendedCommandOption[];
|
||||||
|
#help?: string | undefined;
|
||||||
|
|
||||||
|
#restricted: boolean; // Developer only
|
||||||
|
#disabled: boolean;
|
||||||
|
#guildOnly: boolean;
|
||||||
|
#dmOnly: boolean;
|
||||||
|
#limited: Snowflake[] | null; // Limited to specific roles
|
||||||
|
|
||||||
|
constructor (client: DiscordClient, def: CommandDefinition)
|
||||||
|
{
|
||||||
|
super(client, {
|
||||||
|
...def,
|
||||||
|
type: 'command'
|
||||||
|
});
|
||||||
|
|
||||||
|
this.#aliases = def.aliases ?? [];
|
||||||
|
this.#help = def.help;
|
||||||
|
|
||||||
|
this.#restricted = def.restricted ?? false;
|
||||||
|
this.#disabled = def.disabled ?? false;
|
||||||
|
this.#guildOnly = def.guildOnly ?? false;
|
||||||
|
this.#dmOnly = def.dmOnly ?? false;
|
||||||
|
this.#limited = def.limited ?? null;
|
||||||
|
|
||||||
|
this.#options = [];
|
||||||
|
|
||||||
|
if (def.options)
|
||||||
|
{
|
||||||
|
for (const opt of def.options)
|
||||||
|
{
|
||||||
|
if (opt instanceof ExtendedCommandOption)
|
||||||
|
this.#options.push(opt);
|
||||||
|
else
|
||||||
|
this.#options.push(new ExtendedCommandOption(opt));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.options.some(opt => [ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type))
|
||||||
|
&& this.options.some(opt => ![ OptionType.SUB_COMMAND, OptionType.SUB_COMMAND_GROUP ].includes(opt.type) && opt.name !== 'help'))
|
||||||
|
throw new Error('Cannot have subcommand(group)s on the same level as an option');
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
abstract execute(message: unknown, args: CommandOpts): Promise<unknown>;
|
||||||
|
|
||||||
|
protected timeout (user?: User)
|
||||||
|
{
|
||||||
|
throw new CommandError(this, { reason: 'Command timed out', user });
|
||||||
|
}
|
||||||
|
|
||||||
|
get restricted ()
|
||||||
|
{
|
||||||
|
return this.#restricted;
|
||||||
|
}
|
||||||
|
|
||||||
|
get limited ()
|
||||||
|
{
|
||||||
|
return this.#limited;
|
||||||
|
}
|
||||||
|
|
||||||
|
get disabled ()
|
||||||
|
{
|
||||||
|
return this.#disabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
get guildOnly ()
|
||||||
|
{
|
||||||
|
return this.#guildOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
get dmOnly ()
|
||||||
|
{
|
||||||
|
return this.#dmOnly;
|
||||||
|
}
|
||||||
|
|
||||||
|
get aliases ()
|
||||||
|
{
|
||||||
|
return this.#aliases;
|
||||||
|
}
|
||||||
|
|
||||||
|
get options ()
|
||||||
|
{
|
||||||
|
return this.#options;
|
||||||
|
}
|
||||||
|
|
||||||
|
get help ()
|
||||||
|
{
|
||||||
|
return this.#help;
|
||||||
|
}
|
||||||
|
|
||||||
|
get subcommands (): SubcommandOption[]
|
||||||
|
{
|
||||||
|
return this._subcommands(this.options);
|
||||||
|
}
|
||||||
|
|
||||||
|
get subcommandGroups (): SubcommandGroupOption[]
|
||||||
|
{
|
||||||
|
return this.options.filter((opt) => opt.type === OptionType.SUB_COMMAND_GROUP);
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommand (name: string): SubcommandOption | null
|
||||||
|
{
|
||||||
|
name = name.toLowerCase();
|
||||||
|
return this.subcommands.find((cmd) => cmd.name === name || cmd.aliases.includes(name)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
subcommandGroup (name: string): SubcommandGroupOption | null
|
||||||
|
{
|
||||||
|
name = name.toLowerCase();
|
||||||
|
return this.subcommandGroups.find((grp) => grp.name === name || grp.aliases.includes(name)) || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private _subcommands (options: ExtendedCommandOption[]): SubcommandOption[]
|
||||||
|
{
|
||||||
|
const subcommands: SubcommandOption[] = [];
|
||||||
|
for (const opt of options)
|
||||||
|
{
|
||||||
|
if (opt.type === OptionType.SUB_COMMAND)
|
||||||
|
subcommands.push(opt);
|
||||||
|
else if (opt.type === OptionType.SUB_COMMAND_GROUP)
|
||||||
|
subcommands.push(...this._subcommands(opt.options));
|
||||||
|
}
|
||||||
|
|
||||||
|
return subcommands;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Command;
|
80
examples/ExtendedCommandOption.ts
Normal file
80
examples/ExtendedCommandOption.ts
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
/* eslint-disable max-classes-per-file */
|
||||||
|
import { CommandOption, IResolver } from '@navy.gif/commandparser';
|
||||||
|
|
||||||
|
class Resolver implements IResolver
|
||||||
|
{
|
||||||
|
resolveMember<Member, Guild> (_resolveable: string, _strict: boolean, _guild: Guild): Promise<Member | null>
|
||||||
|
{
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
resolveRole<Role, Guild> (_resolveable: string, _strict: boolean, _guild: Guild): Promise<Role | null>
|
||||||
|
{
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
resolveBoolean (_resolveable: string): boolean | null
|
||||||
|
{
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
resolveTime (_resolveable: string): number | null
|
||||||
|
{
|
||||||
|
throw new Error('Method not implemented.');
|
||||||
|
}
|
||||||
|
resolveUsers<User> (_rawValue: string[], _strict: boolean)
|
||||||
|
{
|
||||||
|
const users: User[] = [];
|
||||||
|
return users;
|
||||||
|
}
|
||||||
|
async resolveUser<User> (arg0: string, strict: boolean)
|
||||||
|
{
|
||||||
|
const users = await this.resolveUsers<User>([ arg0 ], strict);
|
||||||
|
return users[0] ?? null;
|
||||||
|
}
|
||||||
|
resolveChannel<Channel> (_arg0: string)
|
||||||
|
{
|
||||||
|
return {} as Channel;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// These functions are called by the command option class
|
||||||
|
// All you need to do is implement them as you need
|
||||||
|
class ExtendedCommandOption extends CommandOption
|
||||||
|
{
|
||||||
|
declare options: ExtendedCommandOption[];
|
||||||
|
declare protected resolver: Resolver;
|
||||||
|
protected async USERS ()
|
||||||
|
{
|
||||||
|
if (!this.resolver)
|
||||||
|
throw new Error('Missing resolver');
|
||||||
|
if (!this.rawValue)
|
||||||
|
throw new Error('Missing raw value');
|
||||||
|
const member = await this.resolver.resolveUsers(this.rawValue, this.strict);
|
||||||
|
if (!member)
|
||||||
|
return { error: true };
|
||||||
|
return { value: member, removed: this.rawValue };
|
||||||
|
}
|
||||||
|
protected async USER ()
|
||||||
|
{
|
||||||
|
if (!this.resolver)
|
||||||
|
throw new Error('Missing resolver');
|
||||||
|
if (!this.rawValue)
|
||||||
|
throw new Error('Missing raw value');
|
||||||
|
const member = await this.resolver.resolveUser(this.rawValue[0], this.strict);
|
||||||
|
if (!member)
|
||||||
|
return { error: true };
|
||||||
|
return { value: member, removed: [ this.rawValue[0] ] };
|
||||||
|
}
|
||||||
|
protected async TEXT_CHANNEL ()
|
||||||
|
{
|
||||||
|
if (!this.resolver)
|
||||||
|
throw new Error('Missing resolver');
|
||||||
|
if (!this.rawValue)
|
||||||
|
throw new Error('Missing raw value');
|
||||||
|
const channel = await this.resolver.resolveChannel < {isTextBased:() => boolean}>(this.rawValue[0]);
|
||||||
|
if (!channel || !channel.isTextBased())
|
||||||
|
return { error: true };
|
||||||
|
return { value: channel, removed: [ this.rawValue[0] ] };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ExtendedCommandOption;
|
@ -109,5 +109,6 @@
|
|||||||
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
},
|
},
|
||||||
"compileOnSave": true
|
"compileOnSave": true,
|
||||||
|
"include": ["src", "index.ts"]
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user