1
0
Fork 0

add retries to rcon, fix table parsing, document things

This commit is contained in:
Sean Sube 2022-12-16 17:04:57 -06:00
parent 0c6e37d7a6
commit 471a5b9ade
80 changed files with 1746 additions and 67 deletions

39
.api-extractor.json Normal file
View File

@ -0,0 +1,39 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/api-extractor/v7/api-extractor.schema.json",
"projectFolder": ".",
"mainEntryPointFilePath": "<projectFolder>/out/src/index.d.ts",
"apiReport": {
"enabled": true,
"reportFolder": "<projectFolder>/docs/",
"reportTempFolder": "<projectFolder>/out/tmp/"
},
"docModel": {
"enabled": true,
"apiJsonFilePath": "<projectFolder>/out/api/<unscopedPackageName>.api.json"
},
"dtsRollup": {
"enabled": true,
"untrimmedFilePath": "<projectFolder>/out/index.d.ts",
"betaTrimmedFilePath": "<projectFolder>/out/index-beta.d.ts",
"publicTrimmedFilePath": "<projectFolder>/out/index-public.d.ts"
},
"tsdocMetadata": {
},
"messages": {
"compilerMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"extractorMessageReporting": {
"default": {
"logLevel": "warning"
}
},
"tsdocMessageReporting": {
"default": {
"logLevel": "warning"
}
}
}
}

View File

@ -204,14 +204,15 @@
"no-fallthrough": "off",
"no-invalid-this": "error",
"no-irregular-whitespace": "error",
"no-magic-numbers": [
"@typescript-eslint/no-magic-numbers": [
"error",
{
"ignore": [
0,
1,
10
]
],
"ignoreEnums": true
}
],
"no-multiple-empty-lines": "error",

View File

@ -1,4 +1,4 @@
.PHONY: build ci clean lint package run test
.PHONY: build ci clean docs docs-local lint package run test
# JS targets
node_modules: deps
@ -12,6 +12,14 @@ clean:
deps:
yarn install
docs:
yarn api-extractor run -c .api-extractor.json
yarn api-documenter markdown -i out/api -o docs/api
docs-local:
yarn api-extractor run -c .api-extractor.json --local
yarn api-documenter markdown -i out/api -o docs/api
build: deps
yarn tsc

View File

@ -2,3 +2,5 @@
This is a Discord bot for Conan Exiles and other RCON games, allowing you to see who is online and run other commands
from chat.
Heavily inspired by https://github.com/krisberg/conan-exiles-discord-chatbot

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Author](./conan-discord.author.md) &gt; [discriminator](./conan-discord.author.discriminator.md)
## Author.discriminator property
<b>Signature:</b>
```typescript
discriminator: string;
```

View File

@ -0,0 +1,21 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Author](./conan-discord.author.md)
## Author interface
Message author.
<b>Signature:</b>
```typescript
export interface Author
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [discriminator](./conan-discord.author.discriminator.md) | | string | |
| [username](./conan-discord.author.username.md) | | string | |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Author](./conan-discord.author.md) &gt; [username](./conan-discord.author.username.md)
## Author.username property
<b>Signature:</b>
```typescript
username: string;
```

View File

@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [authorName](./conan-discord.authorname.md)
## authorName() function
Friendly name for an Author.
<b>Signature:</b>
```typescript
export declare function authorName(author: Author): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| author | [Author](./conan-discord.author.md) | |
<b>Returns:</b>
string

View File

@ -0,0 +1,17 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [BodyOptions](./conan-discord.bodyoptions.md)
## BodyOptions type
Transform a type into its yargs options.
<b>Signature:</b>
```typescript
export type BodyOptions<T> = {
[key in StringsOnly<keyof T>]: yargs.Options;
};
```
<b>References:</b> [StringsOnly](./conan-discord.stringsonly.md)

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Bot](./conan-discord.bot.md) &gt; [destroy](./conan-discord.bot.destroy.md)
## Bot.destroy() method
<b>Signature:</b>
```typescript
destroy(): void;
```
<b>Returns:</b>
void

View File

@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Bot](./conan-discord.bot.md)
## Bot interface
Necessary methods for a bot.
<b>Signature:</b>
```typescript
export interface Bot
```
## Methods
| Method | Description |
| --- | --- |
| [destroy()](./conan-discord.bot.destroy.md) | |

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [BotCtor](./conan-discord.botctor.md)
## BotCtor type
Constructor for a Bot.
<b>Signature:</b>
```typescript
export type BotCtor = (args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>) => Promise<Bot>;
```
<b>References:</b> [ParsedArgs](./conan-discord.parsedargs.md)<!-- -->, [RconClient](./conan-discord.rconclient.md)<!-- -->, [Command](./conan-discord.command.md)<!-- -->, [Bot](./conan-discord.bot.md)

View File

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [checkAuthor](./conan-discord.checkauthor.md)
## checkAuthor() function
Check if an author is allowed to execute a command.
<b>Signature:</b>
```typescript
export declare function checkAuthor(author: Author, admins: Array<string>, roles: Roles): boolean;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| author | [Author](./conan-discord.author.md) | |
| admins | Array&lt;string&gt; | |
| roles | [Roles](./conan-discord.roles.md) | |
<b>Returns:</b>
boolean

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Command](./conan-discord.command.md) &gt; [auth](./conan-discord.command.auth.md)
## Command.auth property
<b>Signature:</b>
```typescript
auth?: (author: Author, context: CommandContext) => Promise<boolean>;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Command](./conan-discord.command.md) &gt; [desc](./conan-discord.command.desc.md)
## Command.desc property
<b>Signature:</b>
```typescript
desc: string;
```

View File

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Command](./conan-discord.command.md)
## Command interface
Bot command with optional authn/authz.
<b>Signature:</b>
```typescript
export interface Command
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [auth?](./conan-discord.command.auth.md) | | (author: [Author](./conan-discord.author.md)<!-- -->, context: [CommandContext](./conan-discord.commandcontext.md)<!-- -->) =&gt; Promise&lt;boolean&gt; | <i>(Optional)</i> |
| [desc](./conan-discord.command.desc.md) | | string | |
| [name](./conan-discord.command.name.md) | | string | |
| [run](./conan-discord.command.run.md) | | (msg: [Message](./conan-discord.message.md)<!-- -->, context: [CommandContext](./conan-discord.commandcontext.md)<!-- -->) =&gt; Promise&lt;void&gt; | |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Command](./conan-discord.command.md) &gt; [name](./conan-discord.command.name.md)
## Command.name property
<b>Signature:</b>
```typescript
name: string;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Command](./conan-discord.command.md) &gt; [run](./conan-discord.command.run.md)
## Command.run property
<b>Signature:</b>
```typescript
run: (msg: Message, context: CommandContext) => Promise<void>;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [CommandContext](./conan-discord.commandcontext.md) &gt; [args](./conan-discord.commandcontext.args.md)
## CommandContext.args property
<b>Signature:</b>
```typescript
args: ParsedArgs;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [CommandContext](./conan-discord.commandcontext.md) &gt; [command](./conan-discord.commandcontext.command.md)
## CommandContext.command property
<b>Signature:</b>
```typescript
command: Command;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [CommandContext](./conan-discord.commandcontext.md) &gt; [logger](./conan-discord.commandcontext.logger.md)
## CommandContext.logger property
<b>Signature:</b>
```typescript
logger: Logger;
```

View File

@ -0,0 +1,23 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [CommandContext](./conan-discord.commandcontext.md)
## CommandContext interface
Context passed to `Command.run` when invoked.
<b>Signature:</b>
```typescript
export interface CommandContext
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [args](./conan-discord.commandcontext.args.md) | | [ParsedArgs](./conan-discord.parsedargs.md) | |
| [command](./conan-discord.commandcontext.command.md) | | [Command](./conan-discord.command.md) | |
| [logger](./conan-discord.commandcontext.logger.md) | | Logger | |
| [rcon](./conan-discord.commandcontext.rcon.md) | | [RconClient](./conan-discord.rconclient.md) | |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [CommandContext](./conan-discord.commandcontext.md) &gt; [rcon](./conan-discord.commandcontext.rcon.md)
## CommandContext.rcon property
<b>Signature:</b>
```typescript
rcon: RconClient;
```

View File

@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [commandName](./conan-discord.commandname.md)
## commandName() function
Wrapper for append with a default prefix.
<b>Signature:</b>
```typescript
export declare function commandName(command: Command, prefix: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| command | [Command](./conan-discord.command.md) | |
| prefix | string | |
<b>Returns:</b>
string

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [DiscordBot](./conan-discord.discordbot.md) &gt; [destroy](./conan-discord.discordbot.destroy.md)
## DiscordBot.destroy() method
<b>Signature:</b>
```typescript
destroy(): void;
```
<b>Returns:</b>
void

View File

@ -0,0 +1,21 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [DiscordBot](./conan-discord.discordbot.md)
## DiscordBot interface
Discord-only methods.
<b>Signature:</b>
```typescript
export interface DiscordBot extends Bot
```
<b>Extends:</b> [Bot](./conan-discord.bot.md)
## Methods
| Method | Description |
| --- | --- |
| [destroy()](./conan-discord.discordbot.destroy.md) | |

View File

@ -0,0 +1,29 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [discordConnect](./conan-discord.discordconnect.md)
## discordConnect() function
Connect to a Discord server using a bot token.
Requires `args.discordToken` to be set.
<b>Signature:</b>
```typescript
export declare function discordConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<DiscordBot>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| args | [ParsedArgs](./conan-discord.parsedargs.md) | |
| logger | Logger | |
| rcon | [RconClient](./conan-discord.rconclient.md) | |
| commands | ReadonlyArray&lt;[Command](./conan-discord.command.md)<!-- -->&gt; | |
<b>Returns:</b>
Promise&lt;[DiscordBot](./conan-discord.discordbot.md)<!-- -->&gt;

View File

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [matchCommands](./conan-discord.matchcommands.md)
## matchCommands() function
Match command names against a message body.
<b>Signature:</b>
```typescript
export declare function matchCommands(msg: string, commands: ReadonlyArray<Command>, prefix: string): ReadonlyArray<Command>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| msg | string | |
| commands | ReadonlyArray&lt;[Command](./conan-discord.command.md)<!-- -->&gt; | |
| prefix | string | |
<b>Returns:</b>
ReadonlyArray&lt;[Command](./conan-discord.command.md)<!-- -->&gt;

45
docs/api/conan-discord.md Normal file
View File

@ -0,0 +1,45 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md)
## conan-discord package
## Functions
| Function | Description |
| --- | --- |
| [authorName(author)](./conan-discord.authorname.md) | Friendly name for an Author. |
| [checkAuthor(author, admins, roles)](./conan-discord.checkauthor.md) | Check if an author is allowed to execute a command. |
| [commandName(command, prefix)](./conan-discord.commandname.md) | Wrapper for append with a default prefix. |
| [discordConnect(args, logger, rcon, commands)](./conan-discord.discordconnect.md) | <p>Connect to a Discord server using a bot token.</p><p>Requires <code>args.discordToken</code> to be set.</p> |
| [matchCommands(msg, commands, prefix)](./conan-discord.matchcommands.md) | Match command names against a message body. |
| [parseArgs(argv)](./conan-discord.parseargs.md) | Parse CLI options and environment variables. |
| [parseBody(msg, command, prefix, options)](./conan-discord.parsebody.md) | Remove the command name from a message, parse any CLI args, and return them with the remainder of the message body. |
| [rconConnect(args, logger)](./conan-discord.rconconnect.md) | Create (and monkey-patch) an RCON client. |
| [readlineConnect(args, logger, rcon, commands)](./conan-discord.readlineconnect.md) | <p>Start the readline bot interface.</p><p>This reads input from stdin and send replies through the logging system, and is meant for interactive debugging.</p> |
| [removeCommandName(msg, command, prefix)](./conan-discord.removecommandname.md) | Parse and remove the command name from a message body. |
| [sendWithRetry(client, command, logger, retries)](./conan-discord.sendwithretry.md) | Send an RCON command and attempt to reconnect and retry if it fails. |
## Interfaces
| Interface | Description |
| --- | --- |
| [Author](./conan-discord.author.md) | Message author. |
| [Bot](./conan-discord.bot.md) | Necessary methods for a bot. |
| [Command](./conan-discord.command.md) | Bot command with optional authn/authz. |
| [CommandContext](./conan-discord.commandcontext.md) | Context passed to <code>Command.run</code> when invoked. |
| [DiscordBot](./conan-discord.discordbot.md) | Discord-only methods. |
| [Message](./conan-discord.message.md) | Text message with ability to reply. |
| [ParsedArgs](./conan-discord.parsedargs.md) | CLI options. |
| [RconClient](./conan-discord.rconclient.md) | RCON client methods. |
| [ReadlineBot](./conan-discord.readlinebot.md) | Readline-only methods. |
| [Roles](./conan-discord.roles.md) | Roles who should be able to execute a command. |
## Type Aliases
| Type Alias | Description |
| --- | --- |
| [BodyOptions](./conan-discord.bodyoptions.md) | Transform a type into its yargs options. |
| [BotCtor](./conan-discord.botctor.md) | Constructor for a Bot. |
| [StringsOnly](./conan-discord.stringsonly.md) | Filter out string types. |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Message](./conan-discord.message.md) &gt; [author](./conan-discord.message.author.md)
## Message.author property
<b>Signature:</b>
```typescript
author: Author;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Message](./conan-discord.message.md) &gt; [content](./conan-discord.message.content.md)
## Message.content property
<b>Signature:</b>
```typescript
content: string;
```

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Message](./conan-discord.message.md)
## Message interface
Text message with ability to reply.
<b>Signature:</b>
```typescript
export interface Message
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [author](./conan-discord.message.author.md) | | [Author](./conan-discord.author.md) | |
| [content](./conan-discord.message.content.md) | | string | |
## Methods
| Method | Description |
| --- | --- |
| [reply(msg)](./conan-discord.message.reply.md) | |

View File

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Message](./conan-discord.message.md) &gt; [reply](./conan-discord.message.reply.md)
## Message.reply() method
<b>Signature:</b>
```typescript
reply(msg: string): Promise<Message>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| msg | string | |
<b>Returns:</b>
Promise&lt;[Message](./conan-discord.message.md)<!-- -->&gt;

View File

@ -0,0 +1,24 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [parseArgs](./conan-discord.parseargs.md)
## parseArgs() function
Parse CLI options and environment variables.
<b>Signature:</b>
```typescript
export declare function parseArgs(argv: Array<string>): Promise<ParsedArgs>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| argv | Array&lt;string&gt; | |
<b>Returns:</b>
Promise&lt;[ParsedArgs](./conan-discord.parsedargs.md)<!-- -->&gt;

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [parseBody](./conan-discord.parsebody.md)
## parseBody() function
Remove the command name from a message, parse any CLI args, and return them with the remainder of the message body.
<b>Signature:</b>
```typescript
export declare function parseBody<ArgT, OptionT extends BodyOptions<ArgT> = BodyOptions<ArgT>>(msg: string, command: Command, prefix: string, options: OptionT): Promise<[string, ArgT]>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| msg | string | |
| command | [Command](./conan-discord.command.md) | |
| prefix | string | |
| options | OptionT | |
<b>Returns:</b>
Promise&lt;\[string, ArgT\]&gt;

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [bots](./conan-discord.parsedargs.bots.md)
## ParsedArgs.bots property
<b>Signature:</b>
```typescript
bots: Array<string>;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [commandAdmins](./conan-discord.parsedargs.commandadmins.md)
## ParsedArgs.commandAdmins property
<b>Signature:</b>
```typescript
commandAdmins: Array<string>;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [commandPrefix](./conan-discord.parsedargs.commandprefix.md)
## ParsedArgs.commandPrefix property
<b>Signature:</b>
```typescript
commandPrefix: string;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [discordToken](./conan-discord.parsedargs.discordtoken.md)
## ParsedArgs.discordToken property
<b>Signature:</b>
```typescript
discordToken: string;
```

View File

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md)
## ParsedArgs interface
CLI options.
<b>Signature:</b>
```typescript
export interface ParsedArgs
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [bots](./conan-discord.parsedargs.bots.md) | | Array&lt;string&gt; | |
| [commandAdmins](./conan-discord.parsedargs.commandadmins.md) | | Array&lt;string&gt; | |
| [commandPrefix](./conan-discord.parsedargs.commandprefix.md) | | string | |
| [discordToken](./conan-discord.parsedargs.discordtoken.md) | | string | |
| [rconHost](./conan-discord.parsedargs.rconhost.md) | | string | |
| [rconPassword](./conan-discord.parsedargs.rconpassword.md) | | string | |
| [rconPort](./conan-discord.parsedargs.rconport.md) | | number | |

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [rconHost](./conan-discord.parsedargs.rconhost.md)
## ParsedArgs.rconHost property
<b>Signature:</b>
```typescript
rconHost: string;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [rconPassword](./conan-discord.parsedargs.rconpassword.md)
## ParsedArgs.rconPassword property
<b>Signature:</b>
```typescript
rconPassword: string;
```

View File

@ -0,0 +1,11 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ParsedArgs](./conan-discord.parsedargs.md) &gt; [rconPort](./conan-discord.parsedargs.rconport.md)
## ParsedArgs.rconPort property
<b>Signature:</b>
```typescript
rconPort: number;
```

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [RconClient](./conan-discord.rconclient.md) &gt; [connect](./conan-discord.rconclient.connect.md)
## RconClient.connect() method
<b>Signature:</b>
```typescript
connect(): Promise<this>;
```
<b>Returns:</b>
Promise&lt;this&gt;

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [RconClient](./conan-discord.rconclient.md) &gt; [end](./conan-discord.rconclient.end.md)
## RconClient.end() method
<b>Signature:</b>
```typescript
end(): void;
```
<b>Returns:</b>
void

View File

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [RconClient](./conan-discord.rconclient.md)
## RconClient interface
RCON client methods.
<b>Signature:</b>
```typescript
export interface RconClient
```
## Methods
| Method | Description |
| --- | --- |
| [connect()](./conan-discord.rconclient.connect.md) | |
| [end()](./conan-discord.rconclient.end.md) | |
| [send(command)](./conan-discord.rconclient.send.md) | |

View File

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [RconClient](./conan-discord.rconclient.md) &gt; [send](./conan-discord.rconclient.send.md)
## RconClient.send() method
<b>Signature:</b>
```typescript
send(command: string): Promise<string>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| command | string | |
<b>Returns:</b>
Promise&lt;string&gt;

View File

@ -0,0 +1,25 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [rconConnect](./conan-discord.rconconnect.md)
## rconConnect() function
Create (and monkey-patch) an RCON client.
<b>Signature:</b>
```typescript
export declare function rconConnect(args: ParsedArgs, logger: Logger): Promise<RconClient>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| args | [ParsedArgs](./conan-discord.parsedargs.md) | |
| logger | Logger | |
<b>Returns:</b>
Promise&lt;[RconClient](./conan-discord.rconclient.md)<!-- -->&gt;

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ReadlineBot](./conan-discord.readlinebot.md) &gt; [destroy](./conan-discord.readlinebot.destroy.md)
## ReadlineBot.destroy() method
<b>Signature:</b>
```typescript
destroy(): void;
```
<b>Returns:</b>
void

View File

@ -0,0 +1,20 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [ReadlineBot](./conan-discord.readlinebot.md)
## ReadlineBot interface
Readline-only methods.
<b>Signature:</b>
```typescript
export interface ReadlineBot
```
## Methods
| Method | Description |
| --- | --- |
| [destroy()](./conan-discord.readlinebot.destroy.md) | |

View File

@ -0,0 +1,29 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [readlineConnect](./conan-discord.readlineconnect.md)
## readlineConnect() function
Start the readline bot interface.
This reads input from stdin and send replies through the logging system, and is meant for interactive debugging.
<b>Signature:</b>
```typescript
export declare function readlineConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<ReadlineBot>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| args | [ParsedArgs](./conan-discord.parsedargs.md) | |
| logger | Logger | |
| rcon | [RconClient](./conan-discord.rconclient.md) | |
| commands | ReadonlyArray&lt;[Command](./conan-discord.command.md)<!-- -->&gt; | |
<b>Returns:</b>
Promise&lt;[ReadlineBot](./conan-discord.readlinebot.md)<!-- -->&gt;

View File

@ -0,0 +1,26 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [removeCommandName](./conan-discord.removecommandname.md)
## removeCommandName() function
Parse and remove the command name from a message body.
<b>Signature:</b>
```typescript
export declare function removeCommandName(msg: string, command: Command, prefix: string): string;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| msg | string | |
| command | [Command](./conan-discord.command.md) | |
| prefix | string | |
<b>Returns:</b>
string

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Roles](./conan-discord.roles.md) &gt; [admin](./conan-discord.roles.admin.md)
## Roles.admin property
Admin role. Exclusive with the others: if this is true, authors must be a bot admin.
<b>Signature:</b>
```typescript
admin: boolean;
```

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Roles](./conan-discord.roles.md) &gt; [local](./conan-discord.roles.local.md)
## Roles.local property
Local role. Inclusive with the others: if this is true, local authors are allowed.
<b>Signature:</b>
```typescript
local: boolean;
```

View File

@ -0,0 +1,22 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Roles](./conan-discord.roles.md)
## Roles interface
Roles who should be able to execute a command.
<b>Signature:</b>
```typescript
export interface Roles
```
## Properties
| Property | Modifiers | Type | Description |
| --- | --- | --- | --- |
| [admin](./conan-discord.roles.admin.md) | | boolean | Admin role. Exclusive with the others: if this is true, authors must be a bot admin. |
| [local](./conan-discord.roles.local.md) | | boolean | Local role. Inclusive with the others: if this is true, local authors are allowed. |
| [other](./conan-discord.roles.other.md) | | boolean | Everyone else. Inclusive with the others: if this is true, authors without any other role are allowed. |

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [Roles](./conan-discord.roles.md) &gt; [other](./conan-discord.roles.other.md)
## Roles.other property
Everyone else. Inclusive with the others: if this is true, authors without any other role are allowed.
<b>Signature:</b>
```typescript
other: boolean;
```

View File

@ -0,0 +1,27 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [sendWithRetry](./conan-discord.sendwithretry.md)
## sendWithRetry() function
Send an RCON command and attempt to reconnect and retry if it fails.
<b>Signature:</b>
```typescript
export declare function sendWithRetry(client: RconClient, command: string, logger: Logger, retries?: number): Promise<string>;
```
## Parameters
| Parameter | Type | Description |
| --- | --- | --- |
| client | [RconClient](./conan-discord.rconclient.md) | |
| command | string | |
| logger | Logger | |
| retries | number | <i>(Optional)</i> |
<b>Returns:</b>
Promise&lt;string&gt;

View File

@ -0,0 +1,13 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [conan-discord](./conan-discord.md) &gt; [StringsOnly](./conan-discord.stringsonly.md)
## StringsOnly type
Filter out string types.
<b>Signature:</b>
```typescript
export type StringsOnly<T> = T extends string ? T : never;
```

12
docs/api/index.md Normal file
View File

@ -0,0 +1,12 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md)
## API Reference
## Packages
| Package | Description |
| --- | --- |
| [conan-discord](./conan-discord.md) | |

151
docs/conan-discord.api.md Normal file
View File

@ -0,0 +1,151 @@
## API Report File for "conan-discord"
> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
```ts
import Logger from 'bunyan';
import yargs from 'yargs';
// @public
export interface Author {
// (undocumented)
discriminator: string;
// (undocumented)
username: string;
}
// @public
export function authorName(author: Author): string;
// @public
export type BodyOptions<T> = {
[key in StringsOnly<keyof T>]: yargs.Options;
};
// @public
export interface Bot {
// (undocumented)
destroy(): void;
}
// @public
export type BotCtor = (args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>) => Promise<Bot>;
// @public
export function checkAuthor(author: Author, admins: Array<string>, roles: Roles): boolean;
// @public
export interface Command {
// (undocumented)
auth?: (author: Author, context: CommandContext) => Promise<boolean>;
// (undocumented)
desc: string;
// (undocumented)
name: string;
// (undocumented)
run: (msg: Message, context: CommandContext) => Promise<void>;
}
// @public
export interface CommandContext {
// (undocumented)
args: ParsedArgs;
// (undocumented)
command: Command;
// (undocumented)
logger: Logger;
// (undocumented)
rcon: RconClient;
}
// @public
export function commandName(command: Command, prefix: string): string;
// @public
export interface DiscordBot extends Bot {
// (undocumented)
destroy(): void;
}
// @public
export function discordConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<DiscordBot>;
// @public
export function matchCommands(msg: string, commands: ReadonlyArray<Command>, prefix: string): ReadonlyArray<Command>;
// @public
export interface Message {
// (undocumented)
author: Author;
// (undocumented)
content: string;
// (undocumented)
reply(msg: string): Promise<Message>;
}
// @public
export function parseArgs(argv: Array<string>): Promise<ParsedArgs>;
// @public
export function parseBody<ArgT, OptionT extends BodyOptions<ArgT> = BodyOptions<ArgT>>(msg: string, command: Command, prefix: string, options: OptionT): Promise<[string, ArgT]>;
// @public
export interface ParsedArgs {
// (undocumented)
bots: Array<string>;
// (undocumented)
commandAdmins: Array<string>;
// (undocumented)
commandPrefix: string;
// (undocumented)
discordToken: string;
// (undocumented)
rconHost: string;
// (undocumented)
rconPassword: string;
// (undocumented)
rconPort: number;
}
// @public
export interface RconClient {
// (undocumented)
connect(): Promise<this>;
// (undocumented)
end(): void;
// (undocumented)
send(command: string): Promise<string>;
}
// @public
export function rconConnect(args: ParsedArgs, logger: Logger): Promise<RconClient>;
// @public
export interface ReadlineBot {
// (undocumented)
destroy(): void;
}
// @public
export function readlineConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<ReadlineBot>;
// @public
export function removeCommandName(msg: string, command: Command, prefix: string): string;
// @public
export interface Roles {
admin: boolean;
local: boolean;
other: boolean;
}
// @public
export function sendWithRetry(client: RconClient, command: string, logger: Logger, retries?: number): Promise<string>;
// @public
export type StringsOnly<T> = T extends string ? T : never;
// (No @packageDocumentation comment for this package)
```

View File

@ -16,6 +16,8 @@
},
"devDependencies": {
"@istanbuljs/nyc-config-typescript": "^1.0.2",
"@microsoft/api-documenter": "^7.19.26",
"@microsoft/api-extractor": "^7.33.7",
"@mochajs/multi-reporter": "^1.1.0",
"@types/bunyan": "^1.8.8",
"@types/chai-as-promised": "^7.1.5",

View File

@ -1,5 +1,10 @@
import yargs from 'yargs';
/**
* CLI options.
*
* @public
*/
export interface ParsedArgs {
bots: Array<string>;
commandAdmins: Array<string>;
@ -13,6 +18,11 @@ export interface ParsedArgs {
export const APP_NAME = 'conan-discord';
export const ENV_NAME = 'CONAN_DISCORD';
/**
* Parse CLI options and environment variables.
*
* @public
*/
export async function parseArgs(argv: Array<string>): Promise<ParsedArgs> {
const parser = yargs(argv).usage(`Usage: ${APP_NAME} [options]`)
.options({

View File

@ -7,12 +7,25 @@ import { Command, CommandContext } from '../command/index.js';
import { catchAndLog } from '../utils/async.js';
import { matchCommands } from '../utils/command.js';
import { RconClient } from '../utils/rcon.js';
import { Bot } from './index.js';
export interface DiscordClient {
/**
* Discord-only methods.
*
* @public
*/
export interface DiscordBot extends Bot {
destroy(): void;
}
export async function discordConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<DiscordClient> {
/**
* Connect to a Discord server using a bot token.
*
* Requires `args.discordToken` to be set.
*
* @public
*/
export async function discordConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<DiscordBot> {
const client = new Client({
intents: [
GatewayIntentBits.DirectMessages,

View File

@ -6,13 +6,28 @@ import { RconClient } from '../utils/rcon.js';
import { discordConnect } from './discord.js';
import { readlineConnect } from './readline.js';
/**
* Necessary methods for a bot.
*
* @public
*/
export interface Bot {
// login(args: ParsedArgs): Promise<boolean>;
destroy(): void;
}
/**
* Constructor for a Bot.
*
* @public
*/
export type BotCtor = (args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>) => Promise<Bot>;
/**
* Available bots.
*
* @private
*/
export const BOTS: {[key: string]: BotCtor} = {
discord: discordConnect,
readline: readlineConnect,

View File

@ -9,13 +9,25 @@ import { catchAndLog } from '../utils/async.js';
import { LOCAL_DISCRIMINATOR, matchCommands } from '../utils/command.js';
import { RconClient } from '../utils/rcon.js';
export interface ReadlineClient {
/**
* Readline-only methods.
*
* @public
*/
export interface ReadlineBot {
destroy(): void;
}
export async function readlineConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<ReadlineClient> {
/**
* Start the readline bot interface.
*
* This reads input from stdin and send replies through the logging system, and is meant for interactive
* debugging.
*
* @public
*/
export async function readlineConnect(args: ParsedArgs, logger: Logger, rcon: RconClient, commands: ReadonlyArray<Command>): Promise<ReadlineBot> {
const rl = createInterface({ input, output });
rl.setPrompt('> ');
async function onLine(line: string) {
logger.debug({ line }, 'message received');
@ -71,6 +83,7 @@ export async function readlineConnect(args: ParsedArgs, logger: Logger, rcon: Rc
catchAndLog(onLine(line), logger, 'error handling line');
});
rl.setPrompt('> ');
rl.prompt();
return {

View File

@ -1,19 +1,21 @@
import { checkAuthor, removeCommand } from '../../utils/command.js';
import { checkAuthor, removeCommandName, ROLE_ADMIN_ONLY } from '../../utils/command.js';
import { sendWithRetry } from '../../utils/rcon.js';
import { Author, Command, CommandContext, Message } from '../index.js';
/**
* Run a SQL query on an RCON game server.
*
* This is a potentially dangerous command and limited to admins.
*/
export const rconSQL: Command = {
name: 'rcon-sql',
desc: 'SQL over rcon command',
async auth(author: Author, context: CommandContext) {
return checkAuthor(author, context.args.commandAdmins, {
admin: true,
local: false,
other: false,
});
return checkAuthor(author, context.args.commandAdmins, ROLE_ADMIN_ONLY);
},
async run(msg: Message, context: CommandContext) {
const query = removeCommand(msg.content, context.command, context.args.commandPrefix);
const result = await context.rcon.send(`sql ${query}`);
const query = removeCommandName(msg.content, context.command, context.args.commandPrefix);
const result = await sendWithRetry(context.rcon, `sql ${query}`, context.logger);
await msg.reply(result);
},
};

View File

@ -1,19 +1,21 @@
import { checkAuthor, removeCommand } from '../../utils/command.js';
import { checkAuthor, removeCommandName, ROLE_ADMIN_ONLY } from '../../utils/command.js';
import { sendWithRetry } from '../../utils/rcon.js';
import { Author, Command, CommandContext, Message } from '../index.js';
/**
* Run a command on an RCON game server.
*
* This is a potentially dangerous command and limited to admins.
*/
export const rcon: Command = {
name: 'rcon',
desc: 'generic rcon command',
desc: 'rcon command',
async auth(author: Author, context: CommandContext) {
return checkAuthor(author, context.args.commandAdmins, {
admin: true,
local: false,
other: false,
});
return checkAuthor(author, context.args.commandAdmins, ROLE_ADMIN_ONLY);
},
async run(msg: Message, context: CommandContext) {
const query = removeCommand(msg.content, context.command, context.args.commandPrefix);
const result = await context.rcon.send(query);
const query = removeCommandName(msg.content, context.command, context.args.commandPrefix);
const result = await sendWithRetry(context.rcon, query, context.logger);
await msg.reply(result);
},
};

View File

@ -1,12 +1,16 @@
import { codeBlock } from 'discord.js';
import { sendWithRetry } from '../../utils/rcon.js';
import { Command, CommandContext, Message } from '../index.js';
/**
* List players who are currently online. Should work for any RCON game server.
*/
export const conanOnline: Command = {
name: 'conan-online',
desc: 'list players who are currently online',
async run(msg: Message, context: CommandContext) {
const players = await context.rcon.send('listplayers');
const players = await sendWithRetry(context.rcon, 'listplayers', context.logger);
await msg.reply(`online players: ${codeBlock(players)}`);
},
};

View File

@ -1,18 +1,25 @@
import { codeBlock } from 'discord.js';
import { parseBody } from '../../utils/command.js';
import { sendWithRetry } from '../../utils/rcon.js';
import { parseTable } from '../../utils/table.js';
import { Command, CommandContext, Message } from '../index.js';
export interface ConanPlayersArgs {
/**
* Options for recent players command, row limit.
*/
export interface ConanRecentArgs {
count: number;
}
export const conanPlayers: Command = {
name: 'conan-players',
/**
* List players who have recently been online. Probably only works for Conan Exiles.
*/
export const conanRecent: Command = {
name: 'conan-recent',
desc: 'show the most recently online players',
async run(msg: Message, context: CommandContext) {
const [_body, args] = await parseBody<ConanPlayersArgs>(msg.content, context.command, context.args.commandPrefix, {
const [_body, args] = await parseBody<ConanRecentArgs>(msg.content, context.command, context.args.commandPrefix, {
count: {
default: 3,
type: 'number',
@ -24,7 +31,7 @@ export const conanPlayers: Command = {
count,
}, 'getting recent players');
const players = await context.rcon.send(`sql \
const players = await sendWithRetry(context.rcon, `sql \
SELECT \
char_name AS name, \
level, \
@ -33,7 +40,7 @@ SELECT \
FROM characters \
INNER JOIN guilds ON characters.guild = guilds.guildId \
ORDER BY lastTimeOnline DESC \
LIMIT ${count}`);
LIMIT ${count}`, context.logger);
context.logger.debug({
players: parseTable(players),

View File

@ -1,14 +1,19 @@
import { codeBlock } from 'discord.js';
import { matchName } from '../utils/command.js';
import { commandName } from '../utils/command.js';
import { Command, CommandContext, COMMANDS, Message } from './index.js';
/**
* Reply with a summary of the available commands.
*
* @todo get the available commands from context rather than const
*/
export const help: Command = {
name: 'help',
desc: 'list the available commands',
async run(msg: Message, context: CommandContext) {
const helps = COMMANDS.map((command) => {
const name = matchName(command, context.args.commandPrefix);
const name = commandName(command, context.args.commandPrefix);
return `${name}: ${command.desc}`;
});
const body = codeBlock(helps.join('\n'));

View File

@ -5,21 +5,36 @@ import { RconClient } from '../utils/rcon.js';
import { rconSQL } from './admin/rcon-sql.js';
import { rcon } from './admin/rcon.js';
import { conanOnline } from './conan/online.js';
import { conanPlayers } from './conan/players.js';
import { conanRecent } from './conan/recent.js';
import { help } from './help.js';
import { ping } from './ping.js';
/**
* Message author.
*
* @public
*/
export interface Author {
discriminator: string;
username: string;
}
/**
* Text message with ability to reply.
*
* @public
*/
export interface Message {
author: Author;
content: string;
reply(msg: string): Promise<Message>;
}
/**
* Bot command with optional authn/authz.
*
* @public
*/
export interface Command {
desc: string;
name: string;
@ -27,6 +42,11 @@ export interface Command {
run: (msg: Message, context: CommandContext) => Promise<void>;
}
/**
* Context passed to `Command.run` when invoked.
*
* @public
*/
export interface CommandContext {
args: ParsedArgs;
command: Command;
@ -34,9 +54,14 @@ export interface CommandContext {
rcon: RconClient;
}
/**
* Available commands.
*
* @private
*/
export const COMMANDS: Array<Command> = [
conanOnline,
conanPlayers,
conanRecent,
help,
ping,
rcon,

View File

@ -1,5 +1,8 @@
import { Command, Message } from './index.js';
/**
* Respond with a pong message. Provides a basic health check.
*/
export const ping: Command = {
name: 'ping',
desc: 'health check',

View File

@ -2,6 +2,12 @@ import process from 'node:process';
import { ExitCode, main } from './main.js';
/**
* A Discord chat bot for Conan Exiles game servers.
*
* @packageDocumentation
*/
const ARGS_START = 2; // trim the first few, yargs does not like them
/* c8 ignore start */
@ -13,3 +19,11 @@ main(process.argv.slice(ARGS_START)).then((code) => {
process.exit(ExitCode.ERROR);
});
/* c8 ignore stop */
export { ParsedArgs, parseArgs } from './args.js';
export { Author, Message, Command, CommandContext } from './command/index.js';
export { discordConnect, DiscordBot } from './bot/discord.js';
export { Bot, BotCtor } from './bot/index.js';
export { readlineConnect, ReadlineBot } from './bot/readline.js';
export { authorName, BodyOptions, checkAuthor, matchCommands, commandName, parseBody, removeCommandName, Roles, StringsOnly } from './utils/command';
export { RconClient, rconConnect, sendWithRetry } from './utils/rcon';

View File

@ -6,11 +6,17 @@ import { Bot, BOTS } from './bot/index.js';
import { COMMANDS } from './command/index.js';
import { rconConnect } from './utils/rcon.js';
/**
* Process exit codes.
*/
export enum ExitCode {
SUCCESS = 0,
ERROR = 1,
}
/**
* Connect to some bots and wait for commands or an exit signal.
*/
export async function main(argv: Array<string>): Promise<ExitCode> {
const args = await parseArgs(argv);
const logger = createLogger({

View File

@ -1,5 +1,8 @@
import Logger from 'bunyan';
/**
* Catch any errors from a promise and log them along with the given `msg`.
*/
export function catchAndLog(p: Promise<unknown>, logger: Logger, msg: string): void {
p.catch((err) => {
logger.error(err, msg);

View File

@ -2,34 +2,45 @@ import yargs from 'yargs';
import { Author, Command } from '../command/index.js';
/**
* Friendly name for an Author.
*
* @public
*/
export function authorName(author: Author): string {
return `${author.username}#${author.discriminator}`;
}
/**
* Wrapper for append with a default prefix.
*
* @public
*/
export function matchName(command: Command, prefix: string): string {
export function commandName(command: Command, prefix: string): string {
return `${prefix}${command.name}`;
}
/**
* Match command names against a message body.
*
* @public
*/
export function matchCommands(msg: string, commands: ReadonlyArray<Command>, prefix: string): ReadonlyArray<Command> {
const [head, ..._rest] = msg.split(' ');
return commands.filter((command) => {
const name = matchName(command, prefix);
const name = commandName(command, prefix);
return name === head;
});
}
/**
* Parse and remove the command name from a message body.
*
* @public
*/
export function removeCommand(msg: string, command: Command, prefix: string): string {
const name = matchName(command, prefix);
export function removeCommandName(msg: string, command: Command, prefix: string): string {
const name = commandName(command, prefix);
const [head, ...rest] = msg.split(' ');
if (head === name) {
@ -39,36 +50,80 @@ export function removeCommand(msg: string, command: Command, prefix: string): st
}
}
/**
* Filter out string types.
*
* @public
*/
export type StringsOnly<T> = T extends string ? T : never;
/**
* Transform a type into its yargs options.
*
* @public
*/
export type BodyOptions<T> = { [key in StringsOnly<keyof T>]: yargs.Options };
/**
* Remove the command name from a message, parse any CLI args, and return them with the remainder of the message body.
*
* @public
*/
export async function parseBody<
ArgT,
OptionT extends BodyOptions<ArgT> = BodyOptions<ArgT>
>(msg: string, command: Command, prefix: string, options: OptionT): Promise<[string, ArgT]> {
const body = removeCommand(msg, command, prefix);
const body = removeCommandName(msg, command, prefix);
const parser = yargs()
.exitProcess(false)
.options<OptionT>(options)
.help();
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const args = await parser.parse(body) as any; // TODO: hax, the compiler says there is no overlap
const args = await parser.parse(body) as unknown as ArgT; // TODO: hax, the compiler says there is no overlap
return [body, args];
}
/**
* Roles who should be able to execute a command.
*
* @public
*/
export interface Roles {
/**
* Admin role. Exclusive with the others: if this is true, authors must be a bot admin.
*/
admin: boolean;
/**
* Local role. Inclusive with the others: if this is true, local authors are allowed.
*/
local: boolean;
/**
* Everyone else. Inclusive with the others: if this is true, authors without any other role are allowed.
*/
other: boolean;
}
export const LOCAL_DISCRIMINATOR = 'local';
/**
* Role for admin-only commands.
*
* @public
*/
export const ROLE_ADMIN_ONLY: Roles = {
admin: true,
local: false,
other: false,
};
/**
* Check if an author is allowed to execute a command.
*
* @public
*/
export function checkAuthor(author: Author, admins: Array<string>, roles: Roles): boolean {
if (roles.admin) {
return admins.includes(authorName(author));

View File

@ -1,16 +1,42 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
/* eslint-disable no-invalid-this */
/* eslint-disable @typescript-eslint/no-explicit-any, @typescript-eslint/strict-boolean-expressions, no-invalid-this */
import Logger from 'bunyan';
import { BaseError } from 'noicejs';
import { Rcon } from 'rcon-client';
import rconPacket from 'rcon-client/lib/packet.js';
import rconPacket, { Packet } from 'rcon-client/lib/packet.js';
import { ParsedArgs } from '../args.js';
/**
* RCON client methods.
*
* @public
*/
export interface RconClient {
connect(): Promise<this>;
end(): void;
send(command: string): Promise<string>;
}
/**
* Secret interface used by the monkey-patch methods that allows access to protected fields of the RCON client that
* are marked as private in the types.
*
* @internal
*/
interface RconClientPrivates {
connect(): Promise<this>;
handlePacket(data: Buffer): void;
authenticated: boolean;
callbacks: Map<number, (packet: Packet) => void>;
requestId: number;
}
/**
* Create (and monkey-patch) an RCON client.
*
* @public
*/
export async function rconConnect(args: ParsedArgs, logger: Logger): Promise<RconClient> {
const client = new Rcon({
host: args.rconHost,
@ -19,18 +45,21 @@ export async function rconConnect(args: ParsedArgs, logger: Logger): Promise<Rco
timeout: 5000,
});
// monkey patch for https://github.com/janispritzkau/rcon-client/issues/21
function handlePacket(this: any, data: any) {
/**
* Monkey patch for https://github.com/janispritzkau/rcon-client/issues/21
*
* The server packet numbering repeats 0 or something, causing a mismatch and timeout.
*/
function handlePacket(this: RconClientPrivates, data: any) {
const packet = rconPacket.decodePacket(data);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
const id = this.authenticated ? (packet.id + 1) : (this.requestId - 1);
logger.debug({
id,
packetId: packet.id,
packetType: packet.type,
}, 'handle packet');
}, 'handle rcon packet');
const handler = this.callbacks.get(id);
// eslint-disable-next-line @typescript-eslint/strict-boolean-expressions
if (handler) {
handler(packet);
this.callbacks.delete(id);
@ -39,20 +68,54 @@ export async function rconConnect(args: ParsedArgs, logger: Logger): Promise<Rco
Reflect.set(client, 'handlePacket', handlePacket);
// lock to ensure a single retry
let retry = true;
const originalConnect = Reflect.get(client, 'connect');
/**
* Super hax: the server resets the packet ID when reconnecting, causing an authentication error.
*/
function connect(this: RconClientPrivates) {
this.requestId = 0;
return originalConnect.call(this);
}
Reflect.set(client, 'connect', connect);
client.on('error', (err: any) => {
logger.error(err, 'rcon client error');
retry = false;
client.connect().then(() => {
logger.info('rcon reconnected');
retry = true;
}, (connectError) => {
logger.error(connectError, 'rcon unable to reconnect');
});
});
return client.connect();
// eslint-disable-next-line no-return-await
return await client.connect();
}
/**
* Send an RCON command and attempt to reconnect and retry if it fails.
*
* @public
*/
export async function sendWithRetry(client: RconClient, command: string, logger: Logger, retries = 1): Promise<string> {
try {
return await client.send(command);
} catch (err) {
if (err instanceof Error) {
logger.error(err, 'error sending rcon command');
if (err.message === 'Not connected' && retries > 0) {
logger.debug({ command, retries }, 'retrying rcon command');
await client.connect();
return sendWithRetry(client, command, logger, retries - 1);
} else {
throw new BaseError('error sending command', err);
}
} else {
logger.error({ err }, 'unknown error sending rcon command');
throw err;
}
}
}

33
src/utils/state.ts Normal file
View File

@ -0,0 +1,33 @@
import { doesExist } from '@apextoaster/js-utils';
import { Author } from '../command/index.js';
import { authorName } from './command.js';
type States = 'none' | 'done';
/**
* Persistent per-author command state for interactive, multi-step commands.
*/
export class CommandState<T extends States> {
protected initialState: T;
protected userState: Map<string, T>;
constructor(initial: T) {
this.initialState = initial;
this.userState = new Map();
}
public setState(author: Author, state: T): void {
this.userState.set(authorName(author), state);
}
public getState(author: Author): T {
const name = authorName(author);
const state = this.userState.get(name);
if (doesExist(state)) {
return state;
} else {
return this.initialState;
}
}
}

View File

@ -1,3 +1,11 @@
enum SortOrder {
BEFORE = -1,
AFTER = +1,
};
/**
* Parse an RCON table into a usable dataset.
*/
export function parseTable(input: string): Array<Array<string>> {
// split lines
const lines = input.split('\n');
@ -9,7 +17,7 @@ export function parseTable(input: string): Array<Array<string>> {
for (const cells of fields) {
const match = cells[0].match(/^#(\d+) +(.+)$/);
if (match) {
const [index, head] = Array.from(match);
const [_full, index, head] = Array.from(match);
cells.shift();
cells.unshift(index, head);
}
@ -18,10 +26,9 @@ export function parseTable(input: string): Array<Array<string>> {
// sort by index
const sorted = fields.sort((a, b) => {
if (a[0] <= b[0]) {
// eslint-disable-next-line no-magic-numbers
return -1;
return SortOrder.BEFORE;
} else {
return +1;
return SortOrder.AFTER;
}
});

228
yarn.lock
View File

@ -112,6 +112,61 @@
"@jridgewell/resolve-uri" "3.1.0"
"@jridgewell/sourcemap-codec" "1.4.14"
"@microsoft/api-documenter@^7.19.26":
version "7.19.26"
resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.19.26.tgz#7014740131ddeb042c67858ced7cc3e937e844fc"
integrity sha512-sFhYmO8k6CMFJ20D/LP1B7GdH+JfwmSKO/xTXnm63WA3+AX7g94G4TlKlc1FXdHFS2qhHnlm4qZUD3fgfs1vqg==
dependencies:
"@microsoft/api-extractor-model" "7.25.3"
"@microsoft/tsdoc" "0.14.2"
"@rushstack/node-core-library" "3.53.3"
"@rushstack/ts-command-line" "4.13.1"
colors "~1.2.1"
js-yaml "~3.13.1"
resolve "~1.17.0"
"@microsoft/api-extractor-model@7.25.3":
version "7.25.3"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.25.3.tgz#1ad0fe161623564e5b36b73d5889066e36097389"
integrity sha512-WWxBUq77p2iZ+5VF7Nmrm3y/UtqCh5bYV8ii3khwq3w99+fXWpvfsAhgSLsC7k8XDQc6De4ssMxH6He/qe1pzg==
dependencies:
"@microsoft/tsdoc" "0.14.2"
"@microsoft/tsdoc-config" "~0.16.1"
"@rushstack/node-core-library" "3.53.3"
"@microsoft/api-extractor@^7.33.7":
version "7.33.7"
resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.33.7.tgz#3579f23469a9e02deb4e7aee705ddd2a221c7b8d"
integrity sha512-fQT2v/j/55DhvMFiopLtth66E7xTFNhnumMKgKY14SaG6qU/V1W0e4nOAgbA+SmLakQjAd1Evu06ofaVaxBPbA==
dependencies:
"@microsoft/api-extractor-model" "7.25.3"
"@microsoft/tsdoc" "0.14.2"
"@microsoft/tsdoc-config" "~0.16.1"
"@rushstack/node-core-library" "3.53.3"
"@rushstack/rig-package" "0.3.17"
"@rushstack/ts-command-line" "4.13.1"
colors "~1.2.1"
lodash "~4.17.15"
resolve "~1.17.0"
semver "~7.3.0"
source-map "~0.6.1"
typescript "~4.8.4"
"@microsoft/tsdoc-config@~0.16.1":
version "0.16.2"
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc-config/-/tsdoc-config-0.16.2.tgz#b786bb4ead00d54f53839a458ce626c8548d3adf"
integrity sha512-OGiIzzoBLgWWR0UdRJX98oYO+XKGf7tiK4Zk6tQ/E4IJqGCe7dvkTvgDZV5cFJUzLGDOjeAXrnZoA6QkVySuxw==
dependencies:
"@microsoft/tsdoc" "0.14.2"
ajv "~6.12.6"
jju "~1.4.0"
resolve "~1.19.0"
"@microsoft/tsdoc@0.14.2":
version "0.14.2"
resolved "https://registry.yarnpkg.com/@microsoft/tsdoc/-/tsdoc-0.14.2.tgz#c3ec604a0b54b9a9b87e9735dfc59e1a5da6a5fb"
integrity sha512-9b8mPpKrfeGRuhFH5iO1iwCLeIIsV6+H1sRfxbkoGXIyQE2BTsPd9zqSqQJ+pv5sJ/hT5M1zvOFL02MnEezFug==
"@mochajs/multi-reporter@^1.1.0":
version "1.1.0"
resolved "https://registry.yarnpkg.com/@mochajs/multi-reporter/-/multi-reporter-1.1.0.tgz#378aafd9b9ecbd612753899a3be35026b79b62a5"
@ -138,6 +193,38 @@
"@nodelib/fs.scandir" "2.1.5"
fastq "^1.6.0"
"@rushstack/node-core-library@3.53.3":
version "3.53.3"
resolved "https://registry.yarnpkg.com/@rushstack/node-core-library/-/node-core-library-3.53.3.tgz#e78e0dc1545f6cd7d80b0408cf534aefc62fbbe2"
integrity sha512-H0+T5koi5MFhJUd5ND3dI3bwLhvlABetARl78L3lWftJVQEPyzcgTStvTTRiIM5mCltyTM8VYm6BuCtNUuxD0Q==
dependencies:
"@types/node" "12.20.24"
colors "~1.2.1"
fs-extra "~7.0.1"
import-lazy "~4.0.0"
jju "~1.4.0"
resolve "~1.17.0"
semver "~7.3.0"
z-schema "~5.0.2"
"@rushstack/rig-package@0.3.17":
version "0.3.17"
resolved "https://registry.yarnpkg.com/@rushstack/rig-package/-/rig-package-0.3.17.tgz#687bd55603f2902447f3be246d93afac97095a1f"
integrity sha512-nxvAGeIMnHl1LlZSQmacgcRV4y1EYtgcDIrw6KkeVjudOMonlxO482PhDj3LVZEp6L7emSf6YSO2s5JkHlwfZA==
dependencies:
resolve "~1.17.0"
strip-json-comments "~3.1.1"
"@rushstack/ts-command-line@4.13.1":
version "4.13.1"
resolved "https://registry.yarnpkg.com/@rushstack/ts-command-line/-/ts-command-line-4.13.1.tgz#148b644b627131480363b4853b558ba5eaa0d75c"
integrity sha512-UTQMRyy/jH1IS2U+6pyzyn9xQ2iMcoUKkTcZUzOP/aaMiKlWLwCTDiBVwhw/M1crDx6apF9CwyjuWO9r1SBdJQ==
dependencies:
"@types/argparse" "1.0.38"
argparse "~1.0.9"
colors "~1.2.1"
string-argv "~0.3.1"
"@sapphire/async-queue@^1.5.0":
version "1.5.0"
resolved "https://registry.yarnpkg.com/@sapphire/async-queue/-/async-queue-1.5.0.tgz#2f255a3f186635c4fb5a2381e375d3dfbc5312d8"
@ -203,6 +290,11 @@
resolved "https://registry.yarnpkg.com/@tokenizer/token/-/token-0.3.0.tgz#fe98a93fe789247e998c75e74e9c7c63217aa276"
integrity sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==
"@types/argparse@1.0.38":
version "1.0.38"
resolved "https://registry.yarnpkg.com/@types/argparse/-/argparse-1.0.38.tgz#a81fd8606d481f873a3800c6ebae4f1d768a56a9"
integrity sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==
"@types/bunyan@^1.8.8":
version "1.8.8"
resolved "https://registry.yarnpkg.com/@types/bunyan/-/bunyan-1.8.8.tgz#8d6d33f090f37c07e2a80af30ae728450a101008"
@ -247,6 +339,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.11.15.tgz#de0e1fbd2b22b962d45971431e2ae696643d3f5d"
integrity sha512-VkhBbVo2+2oozlkdHXLrb3zjsRkpdnaU2bXmX8Wgle3PUi569eLRaHGlgETQHR7lLL1w7GiG3h9SnePhxNDecw==
"@types/node@12.20.24":
version "12.20.24"
resolved "https://registry.yarnpkg.com/@types/node/-/node-12.20.24.tgz#c37ac69cb2948afb4cef95f424fa0037971a9a5c"
integrity sha512-yxDeaQIAJlMav7fH5AQqPH1u8YIuhYJXYBzxaQ4PifsU0GDO38MSdmEDeRlIxrKbC6NbEaaEHDanWb+y30U8SQ==
"@types/semver@^7.3.12":
version "7.3.13"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.3.13.tgz#da4bfd73f49bd541d28920ab0e2bf0ee80f71c91"
@ -384,7 +481,7 @@ acorn@^8.8.0:
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.1.tgz#0a3f9cbecc4ec3bea6f0a80b66ae8dd2da250b73"
integrity sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==
ajv@^6.10.0, ajv@^6.12.4:
ajv@^6.10.0, ajv@^6.12.4, ajv@~6.12.6:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@ -419,6 +516,13 @@ anymatch@~3.1.2:
normalize-path "^3.0.0"
picomatch "^2.0.4"
argparse@^1.0.7, argparse@~1.0.9:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
dependencies:
sprintf-js "~1.0.2"
argparse@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
@ -633,6 +737,16 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
colors@~1.2.1:
version "1.2.5"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.2.5.tgz#89c7ad9a374bc030df8013241f68136ed8835afc"
integrity sha512-erNRLao/Y3Fv54qUa0LBB+//Uf3YwMUmdJinN20yMXm9zdKKqH9wt7R9IIVZ+K7ShzfpLV/Zg8+VyrBJYB4lpg==
commander@^2.20.3:
version "2.20.3"
resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33"
integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -980,6 +1094,11 @@ espree@^9.4.0:
acorn-jsx "^5.3.2"
eslint-visitor-keys "^3.3.0"
esprima@^4.0.0:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
esquery@^1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.4.0.tgz#2148ffc38b82e8c7057dfed48425b3e61f0f24a5"
@ -1106,6 +1225,15 @@ foreground-child@^2.0.0:
cross-spawn "^7.0.0"
signal-exit "^3.0.2"
fs-extra@~7.0.1:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs.realpath@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
@ -1238,6 +1366,11 @@ gopd@^1.0.1:
dependencies:
get-intrinsic "^1.1.3"
graceful-fs@^4.1.2, graceful-fs@^4.1.6:
version "4.2.10"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c"
integrity sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==
grapheme-splitter@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz#9cf3a665c6247479896834af35cf1dbb4400767e"
@ -1307,6 +1440,11 @@ import-fresh@^3.0.0, import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
import-lazy@~4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153"
integrity sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==
imurmurhash@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
@ -1366,7 +1504,7 @@ is-callable@^1.1.4, is-callable@^1.2.7:
resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
is-core-module@^2.8.1, is-core-module@^2.9.0:
is-core-module@^2.1.0, is-core-module@^2.8.1, is-core-module@^2.9.0:
version "2.11.0"
resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.11.0.tgz#ad4cb3e3863e814523c96f3f58d26cc570ff0144"
integrity sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==
@ -1497,6 +1635,11 @@ istanbul-reports@^3.1.4:
html-escaper "^2.0.0"
istanbul-lib-report "^3.0.0"
jju@~1.4.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/jju/-/jju-1.4.0.tgz#a3abe2718af241a2b2904f84a625970f389ae32a"
integrity sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==
js-sdsl@^4.1.4:
version "4.2.0"
resolved "https://registry.yarnpkg.com/js-sdsl/-/js-sdsl-4.2.0.tgz#278e98b7bea589b8baaf048c20aeb19eb7ad09d0"
@ -1509,6 +1652,14 @@ js-yaml@4.1.0, js-yaml@^4.1.0:
dependencies:
argparse "^2.0.1"
js-yaml@~3.13.1:
version "3.13.1"
resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.13.1.tgz#aff151b30bfdfa8e49e05da22e7415e9dfa37847"
integrity sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==
dependencies:
argparse "^1.0.7"
esprima "^4.0.0"
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -1526,6 +1677,13 @@ json5@^1.0.1:
dependencies:
minimist "^1.2.0"
jsonfile@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/jsonfile/-/jsonfile-4.0.0.tgz#8771aae0799b64076b76640fca058f9c10e33ecb"
integrity sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==
optionalDependencies:
graceful-fs "^4.1.6"
just-extend@^4.0.2:
version "4.2.1"
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
@ -1551,6 +1709,11 @@ lodash.get@^4.4.2:
resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99"
integrity sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==
lodash.isequal@^4.5.0:
version "4.5.0"
resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0"
integrity sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==
lodash.merge@^4.6.2:
version "4.6.2"
resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
@ -1561,7 +1724,7 @@ lodash.snakecase@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d"
integrity sha512-QZ1d4xoBHYUeuouhEq3lk3Uq7ldgyFXGBhg04+oRLnIz8o9T65Eh+8YdroUwn846zchkA9yDsDl5CVVaV2nqYw==
lodash@^4.17.21:
lodash@^4.17.21, lodash@~4.17.15:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -1850,7 +2013,7 @@ path-key@^3.1.0:
resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
path-parse@^1.0.7:
path-parse@^1.0.6, path-parse@^1.0.7:
version "1.0.7"
resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
@ -1977,6 +2140,21 @@ resolve@^1.20.0, resolve@^1.22.0:
path-parse "^1.0.7"
supports-preserve-symlinks-flag "^1.0.0"
resolve@~1.17.0:
version "1.17.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.17.0.tgz#b25941b54968231cc2d1bb76a79cb7f2c0bf8444"
integrity sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==
dependencies:
path-parse "^1.0.6"
resolve@~1.19.0:
version "1.19.0"
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.19.0.tgz#1af5bf630409734a067cae29318aac7fa29a267c"
integrity sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==
dependencies:
is-core-module "^2.1.0"
path-parse "^1.0.6"
reusify@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
@ -2027,7 +2205,7 @@ semver@^6.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
semver@^7.3.7:
semver@^7.3.7, semver@~7.3.0:
version "7.3.8"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.8.tgz#07a78feafb3f7b32347d725e33de7e2a2df67798"
integrity sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==
@ -2097,16 +2275,26 @@ source-map-support@^0.5.21:
buffer-from "^1.0.0"
source-map "^0.6.0"
source-map@^0.6.0:
source-map@^0.6.0, source-map@~0.6.1:
version "0.6.1"
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
sprintf-js@~1.0.2:
version "1.0.3"
resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
streamsearch@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/streamsearch/-/streamsearch-1.1.0.tgz#404dd1e2247ca94af554e841a8ef0eaa238da764"
integrity sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==
string-argv@~0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/string-argv/-/string-argv-0.3.1.tgz#95e2fbec0427ae19184935f816d74aaa4c5c19da"
integrity sha512-a1uQGz7IyVy9YwhqjZIZu1c8JO8dNIe20xBmSS6qu9kv++k3JGzCVmprbNN5Kn+BgzD5E7YYwg1CcjuJMRNsvg==
string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
@ -2153,7 +2341,7 @@ strip-bom@^3.0.0:
resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
strip-json-comments@3.1.1, strip-json-comments@^3.1.0, strip-json-comments@^3.1.1, strip-json-comments@~3.1.1:
version "3.1.1"
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
@ -2273,6 +2461,11 @@ typescript@^4.9.4:
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.4.tgz#a2a3d2756c079abda241d75f149df9d561091e78"
integrity sha512-Uz+dTXYzxXXbsFpM86Wh3dKCxrQqUcVMxwU54orwlJjOpO3ao8L7j5lH+dWfTwgCwIuM9GQ2kvVotzYJMXTBZg==
typescript@~4.8.4:
version "4.8.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.4.tgz#c464abca159669597be5f96b8943500b238e60e6"
integrity sha512-QCh+85mCy+h0IGff8r5XWzOVSbBO+KfeYrMQh7NJ58QujwcE22u+NUSmUxqF+un70P9GXKxa2HCNiTTMJknyjQ==
unbox-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
@ -2290,6 +2483,11 @@ undici@^5.13.0:
dependencies:
busboy "^1.6.0"
universalify@^0.1.0:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@ -2311,6 +2509,11 @@ v8-to-istanbul@^9.0.0:
"@types/istanbul-lib-coverage" "^2.0.1"
convert-source-map "^1.6.0"
validator@^13.7.0:
version "13.7.0"
resolved "https://registry.yarnpkg.com/validator/-/validator-13.7.0.tgz#4f9658ba13ba8f3d82ee881d3516489ea85c0857"
integrity sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==
which-boxed-primitive@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
@ -2428,3 +2631,14 @@ yocto-queue@^0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
z-schema@~5.0.2:
version "5.0.4"
resolved "https://registry.yarnpkg.com/z-schema/-/z-schema-5.0.4.tgz#ecad8bc5ef3283ae032d603286386cfb1380cce5"
integrity sha512-gm/lx3hDzJNcLwseIeQVm1UcwhWIKpSB4NqH89pTBtFns4k/HDHudsICtvG05Bvw/Mv3jMyk700y5dadueLHdA==
dependencies:
lodash.get "^4.4.2"
lodash.isequal "^4.5.0"
validator "^13.7.0"
optionalDependencies:
commander "^2.20.3"