Bot API
Everything @gryt/bot exports, generated from the source
Generated from the source and regenerated by CI, so it cannot fall behind the code. Editing this page by hand gets the edit wiped on the next run — change what the generator reads instead.
This is the reference. Writing a bot is the page that teaches the shape: the handshake, waiting to be approved, and what a bot does when an admin says no.
Everything below is exported from @gryt/bot. Nothing else in the package is
public.
import { GrytBot, parseCommand, splitArgs, createIdentity, loadIdentity, PERMISSIONS_BEFORE_CATALOGUE } from "@gryt/bot";GrytBot
A bot on one Gryt server.
It joins like any other client and is a member like any other: no bot account type, no bot token, no bypass. What it may do is what its role says.
So the interesting failure is a permission failure. can() answers from what
the server said, and sending without the permission throws here with the
permission's name rather than surfacing as a server:error later.
const bot = new GrytBot({ host: "localhost:5001", nickname: "Helper" });
bot.command("ping", async (ctx) => ctx.reply("pong"), {
description: "Check I am alive",
requires: ["send_messages"],
});
await bot.start();can()
can(permission: Permission): booleanFalse before ready: a bot acting on what it assumes it may do finds out
over the wire. True for a permission the server's catalogue does not have —
that is a server older than the permission, not a refusal.
command()
command(name: string, handler: (ctx: CommandContext) => void | Promise<void>, options: CommandOptions = {}): thisRegister a command. Later registrations of the same name replace earlier.
start()
start(): Promise<ServerInfo>Connect, join, and resolve once the server has said who this bot is.
On a first run against a server that has not approved it, this does not
resolve — the bot is waiting at the door, which is a state and not a
failure. Listen for waiting, and for ready when somebody answers.
stop()
stop(): Promise<void>Leave, and stop reconnecting.
send()
send(conversationId: string, text: string): Promise<void>Post in a channel.
Throws before sending anything if the bot lacks send_messages, naming
the permission. The server would refuse it anyway; failing here means the
stack trace points at the call rather than at a socket handler.
react()
react(conversationId: string, messageId: string, reactionSrc: string): Promise<void>delete()
delete(conversationId: string, messageId: string, opts: { own?: boolean } = {}): Promise<void>Delete a message.
Its own is delete_own_messages; anybody else's is manage_messages. The
caller says which it is, because the bot does not necessarily know who sent
a message it was handed.
fetch()
fetch(conversationId: string, limit = 50): Promise<void>Ask for the last N messages in a channel. Answered on the message event.
GrytBotEvents
| Field | Type | What it is |
|---|---|---|
ready | [ServerInfo] | |
message | [Message] | |
members | [Member[]] | |
channels | [Channel[]] | |
error | [Error] | Something the server refused. Always worth logging; often a permission. |
waiting | [string] | Turned away pending approval. Not an error — see the note in wire. |
disconnected | [string] |
GrytBotOptions
| Field | Type | What it is |
|---|---|---|
host | string | chat.example.com, or localhost:5001. Scheme optional. |
nickname? | string | What the member list will call it. |
identityPath? | string | Where the key lives. The file is the bot — see identity.ts. |
identity? | BotIdentity | An already-loaded identity, if you keep the key somewhere of your own. |
wants? | string[] | Sent once, the first time it turns up, and shown to whoever approves it. Ask for the least that works. Fixed from then on. A later run declaring more gets the answer to the question the first one asked — the run asking for more may not be yours. |
description? | string | One line, shown beside the ask. Say what the bot is for. |
botToken? | string | A token from a registration an operator set up in advance. The unattended path: no approval to wait for, because the approving already happened. Single-use — the first bot to present it becomes that registration. |
prefix? | string | The command prefix. Set to "" to turn the router off entirely. |
helpCommand? | boolean | Whether to answer help with a listing. On by default. |
secure? | boolean | Force http/https rather than guessing from the host. |
parseCommand
Which command a message invokes, if any.
Case-insensitive on the command word, because somebody will type !Help and
being right about it helps nobody.
parseCommand(text: string, prefix: string): { name: string; rest: string } | nullsplitArgs
splitArgs(rest: string): string[]CommandContext
| Field | Type | What it is |
|---|---|---|
message | Message | The message that invoked it. |
rest | string | Everything after the command word, trimmed. Empty string, never null. |
args | string[] | rest split on whitespace, with empties dropped. |
reply | (text: string): Promise<void> | Reply in the channel the command came from. |
react | (emoji: string): Promise<void> | React to the message that invoked it. |
CommandOptions
| Field | Type | What it is |
|---|---|---|
description? | string | One line, for the help listing. |
requires? | Permission[] | Permissions the bot needs before this command is worth offering. Checked before the handler runs, so a command that replies is skipped outright on a server where the bot may not post — rather than running, failing at the socket, and leaving whoever typed it watching nothing happen. This is about the bot, not about the person who typed it: what they may do is the server's business and it is already enforcing it. |
RegisteredCommand
| Field | Type | What it is |
|---|---|---|
name | string | |
handler | (ctx: CommandContext) => void | Promise<void> |
createIdentity
Mint a new identity. The bot this belongs to has never been seen anywhere.
createIdentity(): Promise<{ identity: BotIdentity; privateJwk: JWK; }>loadIdentity
The second run must be the same bot: same id, same role, same history. A key generated per start arrives as a new member every time.
Written 0o600 — anyone who can read this file can be this bot.
loadIdentity(path: string): Promise<BotIdentity>PERMISSIONS_BEFORE_CATALOGUE
Stands in for the catalogue on a server too old to publish one, so an absent permission is not read as a refusal.
Frozen. It describes a release that has already happened, and never grows.
PERMISSIONS_BEFORE_CATALOGUE: readonly Permission[] = [ "send_messages", "attach_files", "add_reactions", "join_voice", "speak", "share_video", "share_screen", "change_nickname", "change_avatar", "create_invite", "manage_invites", "manage_messages", "kick_members", "ban_members", "mute_members", "manage_reports", "manage_join_requests", "manage_channels", "manage_emojis", "manage_webhooks", "manage_roles", "manage_server", "view_audit_log", ]BotIdentity
| Field | Type | What it is |
|---|---|---|
subject | string | The id this bot will be known by on every server. Derived from the key. |
certificate | (): Promise<string> | A certificate that vouches for itself, valid for an hour. |
assertion | (audience: string, nonce: string): Promise<string> | Proof, bound to one server's challenge, that this bot holds the key. |
Attachment
| Field | Type | What it is |
|---|---|---|
file_id | string | |
mime | string | null | |
size | number | null | |
original_name | string | null | |
width | number | null | |
height | number | null | |
has_thumbnail | boolean |
Channel
| Field | Type | What it is |
|---|---|---|
id | string | |
name | string | |
type | "text" | "voice" |
ServerRefusal
Why the server refused something.
The machine-readable half is error, which is what the server actually
sends; code is accepted too because it is the name half of this SDK reached
for first and getting it wrong turned "waiting to be approved" into a crash.
| Field | Type | What it is |
|---|---|---|
error? | string | |
code? | string | |
message? | string | |
permission? | string | Named when the refusal was a permission. |
Member
| Field | Type | What it is |
|---|---|---|
serverUserId | string | |
nickname | string | |
role | string | |
status | "online" | "in_voice" | "afk" | "offline" | |
avatarFileId | string | null |
Message
| Field | Type | What it is |
|---|---|---|
conversationId | string | |
messageId | string | |
senderServerUserId | string | |
senderNickname | string | null | |
text | string | null | |
attachments | Attachment[] | |
createdAt | Date | |
editedAt | Date | null | |
replyToMessageId | string | null | |
isSystem | boolean | True for the "X joined the server" kind of message, which has no sender. |
isOwn | boolean | True when this bot sent it. Handlers are not called for these. |
Permission
A permission id. The server owns the list; see GrytBot.permissions.
export type Permission = string;ServerInfo
| Field | Type | What it is |
|---|---|---|
serverId | string | |
name | string | |
description | string | |
permissions | Permission[] | What this bot may do here. Empty until the server has said. |
role | string | The role this bot holds. |