more tests
This commit is contained in:
parent
09fa094c32
commit
61b32a5981
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [Bot](./conan-discord.bot.md) > [commands](./conan-discord.bot.commands.md)
|
||||
|
||||
## Bot.commands property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
commands: ReadonlyArray<Command>;
|
||||
```
|
|
@ -0,0 +1,15 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [Bot](./conan-discord.bot.md) > [connect](./conan-discord.bot.connect.md)
|
||||
|
||||
## Bot.connect() method
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
connect(): Promise<boolean>;
|
||||
```
|
||||
<b>Returns:</b>
|
||||
|
||||
Promise<boolean>
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [Bot](./conan-discord.bot.md) > [getUser](./conan-discord.bot.getuser.md)
|
||||
|
||||
## Bot.getUser() method
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
getUser(ident: string): Promise<Author | undefined>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| ident | string | |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
Promise<[Author](./conan-discord.author.md) \| undefined>
|
||||
|
|
@ -12,9 +12,17 @@ Necessary methods for a bot.
|
|||
export interface Bot
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [commands](./conan-discord.bot.commands.md) | | ReadonlyArray<[Command](./conan-discord.command.md)<!-- -->> | |
|
||||
|
||||
## Methods
|
||||
|
||||
| Method | Description |
|
||||
| --- | --- |
|
||||
| [connect()](./conan-discord.bot.connect.md) | |
|
||||
| [destroy()](./conan-discord.bot.destroy.md) | |
|
||||
| [getUser(ident)](./conan-discord.bot.getuser.md) | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [BotContext](./conan-discord.botcontext.md) > [args](./conan-discord.botcontext.args.md)
|
||||
|
||||
## BotContext.args property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
args: ParsedArgs;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [BotContext](./conan-discord.botcontext.md) > [data](./conan-discord.botcontext.data.md)
|
||||
|
||||
## BotContext.data property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
data: DataLayer;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [BotContext](./conan-discord.botcontext.md) > [logger](./conan-discord.botcontext.logger.md)
|
||||
|
||||
## BotContext.logger property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
logger: Logger;
|
||||
```
|
|
@ -0,0 +1,23 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [BotContext](./conan-discord.botcontext.md)
|
||||
|
||||
## BotContext interface
|
||||
|
||||
Context passed to a [Bot](./conan-discord.bot.md) when it is created.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface BotContext
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [args](./conan-discord.botcontext.args.md) | | [ParsedArgs](./conan-discord.parsedargs.md) | |
|
||||
| [data](./conan-discord.botcontext.data.md) | | DataLayer | |
|
||||
| [logger](./conan-discord.botcontext.logger.md) | | Logger | |
|
||||
| [rcon](./conan-discord.botcontext.rcon.md) | | [RconClient](./conan-discord.rconclient.md) | |
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [BotContext](./conan-discord.botcontext.md) > [rcon](./conan-discord.botcontext.rcon.md)
|
||||
|
||||
## BotContext.rcon property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
rcon: RconClient;
|
||||
```
|
|
@ -11,5 +11,5 @@ Constructor for a Bot.
|
|||
```typescript
|
||||
export type BotCtor = (context: BotContext, commands: ReadonlyArray<Command>) => Promise<Bot>;
|
||||
```
|
||||
<b>References:</b> [Command](./conan-discord.command.md)<!-- -->, [Bot](./conan-discord.bot.md)
|
||||
<b>References:</b> [BotContext](./conan-discord.botcontext.md)<!-- -->, [Command](./conan-discord.command.md)<!-- -->, [Bot](./conan-discord.bot.md)
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [CommandContext](./conan-discord.commandcontext.md) > [bot](./conan-discord.commandcontext.bot.md)
|
||||
|
||||
## CommandContext.bot property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
bot: Bot;
|
||||
```
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [CommandContext](./conan-discord.commandcontext.md) > [data](./conan-discord.commandcontext.data.md)
|
||||
|
||||
## CommandContext.data property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
data: DataLayer;
|
||||
```
|
|
@ -17,7 +17,9 @@ export interface CommandContext
|
|||
| Property | Modifiers | Type | Description |
|
||||
| --- | --- | --- | --- |
|
||||
| [args](./conan-discord.commandcontext.args.md) | | [ParsedArgs](./conan-discord.parsedargs.md) | |
|
||||
| [bot](./conan-discord.commandcontext.bot.md) | | [Bot](./conan-discord.bot.md) | |
|
||||
| [command](./conan-discord.commandcontext.command.md) | | [Command](./conan-discord.command.md) | |
|
||||
| [data](./conan-discord.commandcontext.data.md) | | DataLayer | |
|
||||
| [logger](./conan-discord.commandcontext.logger.md) | | Logger | |
|
||||
| [rcon](./conan-discord.commandcontext.rcon.md) | | [RconClient](./conan-discord.rconclient.md) | |
|
||||
|
||||
|
|
|
@ -11,15 +11,16 @@ Requires [ParsedArgs.discordToken](./conan-discord.parsedargs.discordtoken.md) t
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function discordConnect(botContext: BotContext, commands: ReadonlyArray<Command>): Promise<DiscordBot>;
|
||||
export declare function discordConnect(botContext: BotContext, commands: ReadonlyArray<Command>, clientCtor?: typeof Client): Promise<DiscordBot>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| botContext | BotContext | |
|
||||
| botContext | [BotContext](./conan-discord.botcontext.md) | |
|
||||
| commands | ReadonlyArray<[Command](./conan-discord.command.md)<!-- -->> | |
|
||||
| clientCtor | typeof Client | <i>(Optional)</i> |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [LOCAL\_DISCRIMINATOR](./conan-discord.local_discriminator.md)
|
||||
|
||||
## LOCAL\_DISCRIMINATOR variable
|
||||
|
||||
Discriminator for local authors.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
LOCAL_DISCRIMINATOR = "local"
|
||||
```
|
|
@ -11,12 +11,12 @@
|
|||
| [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(botContext, commands)](./conan-discord.discordconnect.md) | <p>Connect to a Discord server using a bot token.</p><p>Requires [ParsedArgs.discordToken](./conan-discord.parsedargs.discordtoken.md) to be set in <code>args</code>.</p> |
|
||||
| [discordConnect(botContext, commands, clientCtor)](./conan-discord.discordconnect.md) | <p>Connect to a Discord server using a bot token.</p><p>Requires [ParsedArgs.discordToken](./conan-discord.parsedargs.discordtoken.md) to be set in <code>args</code>.</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. |
|
||||
| [parseBody(msg, command, prefix, options, positionals)](./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(botContext, 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> |
|
||||
| [readlineConnect(botContext, commands, readlineFactory)](./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. |
|
||||
|
||||
|
@ -26,6 +26,7 @@
|
|||
| --- | --- |
|
||||
| [Author](./conan-discord.author.md) | Message author. |
|
||||
| [Bot](./conan-discord.bot.md) | Necessary methods for a bot. |
|
||||
| [BotContext](./conan-discord.botcontext.md) | Context passed to a [Bot](./conan-discord.bot.md) when it is created. |
|
||||
| [Command](./conan-discord.command.md) | Bot command with optional authn/authz. |
|
||||
| [CommandContext](./conan-discord.commandcontext.md) | Context passed to [Command.run](./conan-discord.command.run.md) when invoked. |
|
||||
| [DiscordBot](./conan-discord.discordbot.md) | Discord-only methods. |
|
||||
|
@ -35,6 +36,13 @@
|
|||
| [ReadlineBot](./conan-discord.readlinebot.md) | Readline-only methods. |
|
||||
| [Roles](./conan-discord.roles.md) | Roles who should be able to execute a command. |
|
||||
|
||||
## Variables
|
||||
|
||||
| Variable | Description |
|
||||
| --- | --- |
|
||||
| [LOCAL\_DISCRIMINATOR](./conan-discord.local_discriminator.md) | Discriminator for local authors. |
|
||||
| [ROLE\_ADMIN\_ONLY](./conan-discord.role_admin_only.md) | Role for admin-only commands. |
|
||||
|
||||
## Type Aliases
|
||||
|
||||
| Type Alias | Description |
|
||||
|
|
|
@ -9,7 +9,9 @@ Remove the command name from a message, parse any CLI args, and return them with
|
|||
<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]>;
|
||||
export declare function parseBody<ArgT, OptionT extends BodyOptions<ArgT> = BodyOptions<ArgT>, ArgVT = ArgT & {
|
||||
_: Array<string>;
|
||||
}>(msg: string, command: Command, prefix: string, options: OptionT, positionals?: Array<[StringsOnly<keyof ArgT>, PositionalOptions]>): Promise<[string, ArgVT]>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
@ -20,8 +22,9 @@ export declare function parseBody<ArgT, OptionT extends BodyOptions<ArgT> = Body
|
|||
| command | [Command](./conan-discord.command.md) | |
|
||||
| prefix | string | |
|
||||
| options | OptionT | |
|
||||
| positionals | Array<\[[StringsOnly](./conan-discord.stringsonly.md)<!-- --><keyof ArgT>, PositionalOptions\]> | <i>(Optional)</i> |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
Promise<\[string, ArgT\]>
|
||||
Promise<\[string, ArgVT\]>
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [ParsedArgs](./conan-discord.parsedargs.md) > [data](./conan-discord.parsedargs.data.md)
|
||||
|
||||
## ParsedArgs.data property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
data: string;
|
||||
```
|
|
@ -19,8 +19,10 @@ export interface ParsedArgs
|
|||
| [bots](./conan-discord.parsedargs.bots.md) | | Array<string> | |
|
||||
| [commandAdmins](./conan-discord.parsedargs.commandadmins.md) | | Array<string> | |
|
||||
| [commandPrefix](./conan-discord.parsedargs.commandprefix.md) | | string | |
|
||||
| [data](./conan-discord.parsedargs.data.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 | |
|
||||
| [sqliteDatabase](./conan-discord.parsedargs.sqlitedatabase.md) | | string | |
|
||||
|
||||
|
|
|
@ -0,0 +1,11 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [ParsedArgs](./conan-discord.parsedargs.md) > [sqliteDatabase](./conan-discord.parsedargs.sqlitedatabase.md)
|
||||
|
||||
## ParsedArgs.sqliteDatabase property
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
sqliteDatabase: string;
|
||||
```
|
|
@ -9,8 +9,9 @@ Readline-only methods.
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export interface ReadlineBot
|
||||
export interface ReadlineBot extends Bot
|
||||
```
|
||||
<b>Extends:</b> [Bot](./conan-discord.bot.md)
|
||||
|
||||
## Methods
|
||||
|
||||
|
|
|
@ -11,15 +11,16 @@ This reads input from stdin and send replies through the logging system, and is
|
|||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
export declare function readlineConnect(botContext: BotContext, commands: ReadonlyArray<Command>): Promise<ReadlineBot>;
|
||||
export declare function readlineConnect(botContext: BotContext, commands: ReadonlyArray<Command>, readlineFactory?: typeof createInterface): Promise<ReadlineBot>;
|
||||
```
|
||||
|
||||
## Parameters
|
||||
|
||||
| Parameter | Type | Description |
|
||||
| --- | --- | --- |
|
||||
| botContext | BotContext | |
|
||||
| botContext | [BotContext](./conan-discord.botcontext.md) | |
|
||||
| commands | ReadonlyArray<[Command](./conan-discord.command.md)<!-- -->> | |
|
||||
| readlineFactory | typeof createInterface | <i>(Optional)</i> |
|
||||
|
||||
<b>Returns:</b>
|
||||
|
||||
|
|
|
@ -0,0 +1,13 @@
|
|||
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
|
||||
|
||||
[Home](./index.md) > [conan-discord](./conan-discord.md) > [ROLE\_ADMIN\_ONLY](./conan-discord.role_admin_only.md)
|
||||
|
||||
## ROLE\_ADMIN\_ONLY variable
|
||||
|
||||
Role for admin-only commands.
|
||||
|
||||
<b>Signature:</b>
|
||||
|
||||
```typescript
|
||||
ROLE_ADMIN_ONLY: Roles
|
||||
```
|
|
@ -4,7 +4,12 @@
|
|||
|
||||
```ts
|
||||
|
||||
import Logger from 'bunyan';
|
||||
/// <reference types="node" />
|
||||
|
||||
import { Client } from 'discord.js';
|
||||
import { createInterface } from 'node:readline/promises';
|
||||
import { Logger } from 'noicejs';
|
||||
import { PositionalOptions } from 'yargs';
|
||||
import yargs from 'yargs';
|
||||
|
||||
// @public
|
||||
|
@ -25,12 +30,30 @@ export type BodyOptions<T> = {
|
|||
|
||||
// @public
|
||||
export interface Bot {
|
||||
// (undocumented)
|
||||
commands: ReadonlyArray<Command>;
|
||||
// (undocumented)
|
||||
connect(): Promise<boolean>;
|
||||
// (undocumented)
|
||||
destroy(): void;
|
||||
// (undocumented)
|
||||
getUser(ident: string): Promise<Author | undefined>;
|
||||
}
|
||||
|
||||
// @public
|
||||
export interface BotContext {
|
||||
// (undocumented)
|
||||
args: ParsedArgs;
|
||||
// Warning: (ae-forgotten-export) The symbol "DataLayer" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// (undocumented)
|
||||
data: DataLayer;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
rcon: RconClient;
|
||||
}
|
||||
|
||||
// Warning: (ae-forgotten-export) The symbol "BotContext" needs to be exported by the entry point index.d.ts
|
||||
//
|
||||
// @public
|
||||
export type BotCtor = (context: BotContext, commands: ReadonlyArray<Command>) => Promise<Bot>;
|
||||
|
||||
|
@ -54,8 +77,12 @@ export interface CommandContext {
|
|||
// (undocumented)
|
||||
args: ParsedArgs;
|
||||
// (undocumented)
|
||||
bot: Bot;
|
||||
// (undocumented)
|
||||
command: Command;
|
||||
// (undocumented)
|
||||
data: DataLayer;
|
||||
// (undocumented)
|
||||
logger: Logger;
|
||||
// (undocumented)
|
||||
rcon: RconClient;
|
||||
|
@ -71,7 +98,10 @@ export interface DiscordBot extends Bot {
|
|||
}
|
||||
|
||||
// @public
|
||||
export function discordConnect(botContext: BotContext, commands: ReadonlyArray<Command>): Promise<DiscordBot>;
|
||||
export function discordConnect(botContext: BotContext, commands: ReadonlyArray<Command>, clientCtor?: typeof Client): Promise<DiscordBot>;
|
||||
|
||||
// @public
|
||||
export const LOCAL_DISCRIMINATOR = "local";
|
||||
|
||||
// @public
|
||||
export function matchCommands(msg: string, commands: ReadonlyArray<Command>, prefix: string): ReadonlyArray<Command>;
|
||||
|
@ -90,7 +120,9 @@ export interface Message {
|
|||
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]>;
|
||||
export function parseBody<ArgT, OptionT extends BodyOptions<ArgT> = BodyOptions<ArgT>, ArgVT = ArgT & {
|
||||
_: Array<string>;
|
||||
}>(msg: string, command: Command, prefix: string, options: OptionT, positionals?: Array<[StringsOnly<keyof ArgT>, PositionalOptions]>): Promise<[string, ArgVT]>;
|
||||
|
||||
// @public
|
||||
export interface ParsedArgs {
|
||||
|
@ -101,6 +133,8 @@ export interface ParsedArgs {
|
|||
// (undocumented)
|
||||
commandPrefix: string;
|
||||
// (undocumented)
|
||||
data: string;
|
||||
// (undocumented)
|
||||
discordToken: string;
|
||||
// (undocumented)
|
||||
rconHost: string;
|
||||
|
@ -108,6 +142,8 @@ export interface ParsedArgs {
|
|||
rconPassword: string;
|
||||
// (undocumented)
|
||||
rconPort: number;
|
||||
// (undocumented)
|
||||
sqliteDatabase: string;
|
||||
}
|
||||
|
||||
// @public
|
||||
|
@ -124,17 +160,20 @@ export interface RconClient {
|
|||
export function rconConnect(args: ParsedArgs, logger: Logger): Promise<RconClient>;
|
||||
|
||||
// @public
|
||||
export interface ReadlineBot {
|
||||
export interface ReadlineBot extends Bot {
|
||||
// (undocumented)
|
||||
destroy(): void;
|
||||
}
|
||||
|
||||
// @public
|
||||
export function readlineConnect(botContext: BotContext, commands: ReadonlyArray<Command>): Promise<ReadlineBot>;
|
||||
export function readlineConnect(botContext: BotContext, commands: ReadonlyArray<Command>, readlineFactory?: typeof createInterface): Promise<ReadlineBot>;
|
||||
|
||||
// @public
|
||||
export function removeCommandName(msg: string, command: Command, prefix: string): string;
|
||||
|
||||
// @public
|
||||
export const ROLE_ADMIN_ONLY: Roles;
|
||||
|
||||
// @public
|
||||
export interface Roles {
|
||||
admin: boolean;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import { doesExist, mustExist } from '@apextoaster/js-utils';
|
||||
import { Client, Events, GatewayIntentBits, Message, Partials } from 'discord.js';
|
||||
import { Client, Events, FormattingPatterns, GatewayIntentBits, Message, Partials } from 'discord.js';
|
||||
|
||||
import { Author, Command, CommandContext } from '../command/index.js';
|
||||
import { catchAndLog } from '../utils/async.js';
|
||||
import { matchCommands } from '../utils/command.js';
|
||||
import { authorName, matchCommands } from '../utils/command.js';
|
||||
import { Bot, BotContext } from './index.js';
|
||||
|
||||
/**
|
||||
|
@ -22,10 +22,14 @@ export interface DiscordBot extends Bot {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export async function discordConnect(botContext: BotContext, commands: ReadonlyArray<Command>): Promise<DiscordBot> {
|
||||
export async function discordConnect(
|
||||
botContext: BotContext,
|
||||
commands: ReadonlyArray<Command>,
|
||||
clientCtor: typeof Client = Client
|
||||
): Promise<DiscordBot> {
|
||||
const { args, logger } = botContext;
|
||||
|
||||
const client = new Client({
|
||||
const client = new clientCtor({
|
||||
intents: [
|
||||
GatewayIntentBits.DirectMessages,
|
||||
GatewayIntentBits.DirectMessageReactions,
|
||||
|
@ -45,10 +49,11 @@ export async function discordConnect(botContext: BotContext, commands: ReadonlyA
|
|||
}, 'logged in and ready');
|
||||
});
|
||||
|
||||
async function onMessage(msg: Message) {
|
||||
const name = `${msg.author.username}#${msg.author.discriminator}`;
|
||||
async function onMessage(message: Message) {
|
||||
const { author, content } = message;
|
||||
const name = authorName(author);
|
||||
logger.debug({
|
||||
text: msg.content,
|
||||
text: content,
|
||||
user: name,
|
||||
}, 'message created');
|
||||
|
||||
|
@ -57,7 +62,7 @@ export async function discordConnect(botContext: BotContext, commands: ReadonlyA
|
|||
return;
|
||||
}
|
||||
|
||||
const matchingCommands = matchCommands(msg.content, commands, args.commandPrefix);
|
||||
const matchingCommands = matchCommands(content, commands, args.commandPrefix);
|
||||
for (const command of matchingCommands) {
|
||||
logger.debug({
|
||||
command: command.name,
|
||||
|
@ -70,16 +75,16 @@ export async function discordConnect(botContext: BotContext, commands: ReadonlyA
|
|||
};
|
||||
|
||||
if (doesExist(command.auth)) {
|
||||
const allowed = await command.auth(msg.author, context);
|
||||
logger.debug({ allowed, author: msg.author, command }, 'command auth check');
|
||||
const allowed = await command.auth(author, context);
|
||||
logger.debug({ allowed, author, command }, 'command auth check');
|
||||
|
||||
if (allowed === false) {
|
||||
logger.warn({ author: msg.author, command }, 'author attempted to use unauthorized command');
|
||||
logger.warn({ author, command }, 'author attempted to use unauthorized command');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
command.run(msg, context).catch((err) => {
|
||||
command.run(message, context).catch((err) => {
|
||||
logger.error(err, 'error executing command');
|
||||
});
|
||||
}
|
||||
|
@ -99,7 +104,8 @@ export async function discordConnect(botContext: BotContext, commands: ReadonlyA
|
|||
client.destroy();
|
||||
},
|
||||
async getUser(ident: string): Promise<Author> {
|
||||
const match = /<@(\d+)>/.exec(ident);
|
||||
// const match = /<@(\d+)>/.exec(ident);
|
||||
const match = FormattingPatterns.User.exec(ident);
|
||||
|
||||
if (match) {
|
||||
const [_full, snowflake] = Array.from(match);
|
||||
|
|
|
@ -30,10 +30,10 @@ export const READLINE_AUTHOR: Author = {
|
|||
*
|
||||
* @public
|
||||
*/
|
||||
export async function readlineConnect(botContext: BotContext, commands: ReadonlyArray<Command>): Promise<ReadlineBot> {
|
||||
export async function readlineConnect(botContext: BotContext, commands: ReadonlyArray<Command>, readlineFactory: typeof createInterface = createInterface): Promise<ReadlineBot> {
|
||||
const { args, logger } = botContext;
|
||||
|
||||
const rl = createInterface({ input, output });
|
||||
const rl = readlineFactory({ input, output });
|
||||
|
||||
async function onLine(line: string) {
|
||||
logger.debug({ line }, 'message received');
|
||||
|
|
|
@ -23,6 +23,11 @@ export interface Roles {
|
|||
other: boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Discriminator for local authors.
|
||||
*
|
||||
* @public
|
||||
*/
|
||||
export const LOCAL_DISCRIMINATOR = 'local';
|
||||
|
||||
/**
|
||||
|
|
|
@ -0,0 +1,41 @@
|
|||
import { expect } from 'chai';
|
||||
import { Client, ClientOptions } from 'discord.js';
|
||||
import { NullLogger } from 'noicejs';
|
||||
import { createStubInstance, stub } from 'sinon';
|
||||
|
||||
import { parseArgs } from '../../src/args.js';
|
||||
import { discordConnect } from '../../src/bot/discord.js';
|
||||
import { MemoryDataLayer } from '../../src/data/memory.js';
|
||||
import { mockRcon } from '../helpers.js';
|
||||
|
||||
describe('discord bot', () => {
|
||||
it('should watch for the client ready event', async () => {
|
||||
const args = await parseArgs(['--discordToken=""', '--rconPassword=""']);
|
||||
const rcon = mockRcon();
|
||||
|
||||
const destroyMock = stub<[], void>();
|
||||
class MockDiscord<Ready extends boolean> extends Client<Ready> {
|
||||
constructor(options: ClientOptions) {
|
||||
super(options);
|
||||
|
||||
return createStubInstance<Client<Ready>>(Client, {
|
||||
destroy: destroyMock,
|
||||
on: stub(),
|
||||
once: stub(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const bot = await discordConnect({
|
||||
args,
|
||||
data: new MemoryDataLayer(args),
|
||||
logger: NullLogger.global,
|
||||
rcon,
|
||||
}, [], MockDiscord);
|
||||
|
||||
bot.destroy();
|
||||
expect(destroyMock).to.have.callCount(1);
|
||||
});
|
||||
|
||||
it('should watch for messages and match commands');
|
||||
});
|
|
@ -1,19 +1,28 @@
|
|||
import { expect } from 'chai';
|
||||
import { NullLogger } from 'noicejs';
|
||||
import { match, mock } from 'sinon';
|
||||
|
||||
import { parseArgs } from '../../src/args.js';
|
||||
import { help } from '../../src/command/help.js';
|
||||
import { CommandContext, Message } from '../../src/command/index.js';
|
||||
import { MemoryDataLayer } from '../../src/data/memory.js';
|
||||
import { mockBot, mockRcon } from '../helpers.js';
|
||||
|
||||
describe('ping command', () => {
|
||||
it('should reply with a pong', async () => {
|
||||
const args = await parseArgs([
|
||||
'--discordToken=""',
|
||||
'--rconPassword=""',
|
||||
'--commandPrefix="!"',
|
||||
]);
|
||||
const ctx: CommandContext = {
|
||||
args: {
|
||||
commandPrefix: '!',
|
||||
},
|
||||
bot: {
|
||||
commands: [help],
|
||||
},
|
||||
} as unknown as CommandContext;
|
||||
args,
|
||||
bot: mockBot([help], {}),
|
||||
command: help,
|
||||
data: new MemoryDataLayer(args),
|
||||
logger: NullLogger.global,
|
||||
rcon: mockRcon(),
|
||||
};
|
||||
const replyMock = mock().named('Message.reply').resolves();
|
||||
const msg: Message = {
|
||||
author: {
|
||||
|
@ -29,4 +38,3 @@ describe('ping command', () => {
|
|||
expect(replyMock).to.have.callCount(1).and.been.calledWith(match(`!${help.name}: ${help.desc}`));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
import { expect } from 'chai';
|
||||
|
||||
import { COMMANDS } from '../../src/command/index.js';
|
||||
|
||||
// so smelly
|
||||
describe('index file', () => {
|
||||
it('should have a list of commands', async () => {
|
||||
expect(COMMANDS.length).to.be.greaterThan(0);
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
import { expect } from 'chai';
|
||||
import { NullLogger } from 'noicejs';
|
||||
import { mock } from 'sinon';
|
||||
|
||||
import { parseArgs } from '../../src/args.js';
|
||||
import { Author, CommandContext, Message } from '../../src/command/index.js';
|
||||
import { karma } from '../../src/command/karma.js';
|
||||
import { MemoryDataLayer } from '../../src/data/memory.js';
|
||||
import { mockRcon } from '../helpers.js';
|
||||
|
||||
describe('karma command', () => {
|
||||
it('should store karma', async () => {
|
||||
const author: Author = {
|
||||
discriminator: '0000',
|
||||
username: 'test',
|
||||
};
|
||||
const args = await parseArgs([
|
||||
'--discordToken=""',
|
||||
'--rconPassword=""',
|
||||
'--commandPrefix="!"',
|
||||
]);
|
||||
const ctx: CommandContext = {
|
||||
args,
|
||||
bot: {
|
||||
commands: [],
|
||||
connect: () => Promise.resolve(true),
|
||||
destroy: () => { /* noop */ },
|
||||
getUser: mock().atLeast(1).resolves(author),
|
||||
},
|
||||
command: karma,
|
||||
data: new MemoryDataLayer(args),
|
||||
logger: NullLogger.global,
|
||||
rcon: mockRcon(),
|
||||
};
|
||||
|
||||
const replyMock = mock().named('Message.reply').resolves();
|
||||
const msg: Message = {
|
||||
author,
|
||||
content: '',
|
||||
reply: replyMock,
|
||||
};
|
||||
|
||||
await karma.run(msg, ctx);
|
||||
|
||||
// start at 0
|
||||
expect(replyMock).to.have.been.calledWith('test#0000 has 0 karma');
|
||||
|
||||
replyMock.resetHistory();
|
||||
const msg2: Message = {
|
||||
author,
|
||||
content: '!karma ++++',
|
||||
reply: replyMock,
|
||||
};
|
||||
|
||||
await karma.run(msg2, ctx);
|
||||
|
||||
// ++(+++)
|
||||
expect(replyMock).to.have.been.calledWith('test#0000 has 3 karma');
|
||||
});
|
||||
});
|
|
@ -0,0 +1,28 @@
|
|||
import { Bot } from '../src/bot/index.js';
|
||||
import { Author, Command } from '../src/command/index.js';
|
||||
import { RconClient } from '../src/utils/rcon.js';
|
||||
|
||||
export function mockBot(commands: Array<Command>, users: Record<string, Author>): Bot {
|
||||
return {
|
||||
commands,
|
||||
connect: () => Promise.resolve(true),
|
||||
destroy: () => { /* noop */ },
|
||||
getUser: (name: string) => Promise.resolve(users[name]),
|
||||
};
|
||||
}
|
||||
|
||||
export function mockRcon(commands: Record<string, string> = {}): RconClient {
|
||||
const client: RconClient = {
|
||||
async connect() {
|
||||
return client;
|
||||
},
|
||||
end() {
|
||||
// noop
|
||||
},
|
||||
async send(command: string) {
|
||||
return commands[command];
|
||||
},
|
||||
};
|
||||
|
||||
return client;
|
||||
}
|
|
@ -1,7 +1,7 @@
|
|||
import { expect } from 'chai';
|
||||
|
||||
import { Command } from '../../src/command/index.js';
|
||||
import { matchCommands } from '../../src/utils/command.js';
|
||||
import { matchCommands, parseBody, removeCommandName } from '../../src/utils/command.js';
|
||||
|
||||
const TEST_COMMANDS: Array<Command> = [{
|
||||
name: 'foo',
|
||||
|
@ -30,11 +30,44 @@ describe('command utils', () => {
|
|||
});
|
||||
|
||||
describe('remove command name helper', () => {
|
||||
it('should remove the command name from the message');
|
||||
it('should remove the command name from the message if it matches', async () => {
|
||||
expect(removeCommandName('!foo bar', TEST_COMMANDS[0], '!')).to.equal('bar');
|
||||
expect(removeCommandName('!foo bar', TEST_COMMANDS[1], '!')).to.equal('!foo bar');
|
||||
});
|
||||
});
|
||||
|
||||
describe('parse body helper', () => {
|
||||
it('should parse options from the message');
|
||||
it('should return the remainder of the message');
|
||||
it('should parse dashed options from the message', async () => {
|
||||
interface TestArgs {
|
||||
count: number;
|
||||
};
|
||||
|
||||
const [_body, args] = await parseBody<TestArgs>('!foo --count 3', TEST_COMMANDS[0], '!', {
|
||||
count: {
|
||||
default: 0,
|
||||
type: 'number',
|
||||
},
|
||||
});
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-magic-numbers
|
||||
expect(args.count).to.equal(3);
|
||||
});
|
||||
|
||||
it('should parse positional arguments from the message');
|
||||
|
||||
it('should return the remainder of the message', async () => {
|
||||
interface TestArgs {
|
||||
count: number;
|
||||
};
|
||||
|
||||
const [body, _args] = await parseBody<TestArgs>('!foo --count 3 bar', TEST_COMMANDS[0], '!', {
|
||||
count: {
|
||||
default: 0,
|
||||
type: 'number',
|
||||
},
|
||||
});
|
||||
|
||||
expect(body).to.equal('bar');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue