@kookapp/js-sdk
v0.1.3
Published
KOOK Bot JavaScript/TypeScript SDK
Readme
@kookapp/js-sdk
KOOK Bot JavaScript/TypeScript SDK, providing WebSocket connection management, REST API client, card message builder, directive system, and plugin system.
Installation
npm install @kookapp/js-sdk
# or
yarn add @kookapp/js-sdkQuick Start
import { KookClient } from '@kookapp/js-sdk'
const client = new KookClient({
botToken: 'your-bot-token',
})
client.on('textChannelEvent', (event) => {
console.log(`${event.extra.author.username}: ${event.content}`)
})
client.on('systemEvent', (event) => {
console.log('System event:', event.extra.type)
})
await client.connect()Obtaining a Bot Token
- Go to the KOOK Developer Platform
- Create or select an application
- Navigate to the "Bot" page and copy the Token
Core Modules
KookClient
Top-level client that combines WebSocket, REST API, directive, and plugin systems.
import { KookClient } from '@kookapp/js-sdk'
const client = new KookClient({
botToken: 'your-token',
baseUrl: 'https://www.kookapp.cn', // optional, default
requestTimeoutMs: 15000, // optional, REST timeout
compression: true, // optional, WebSocket compression
autoReconnect: true, // optional, auto reconnect on disconnect
eventQueueMaxSize: 2000, // optional, maximum buffered out-of-order events
timing: { heartbeatIntervalMs: 30000 }, // optional, WebSocket timing
})
// Resolves only after authentication and the WebSocket handshake complete.
await client.connect()
// Bot info (available after connect)
console.log(client.me?.username)
// Event listeners
client.on('textChannelEvent', (event, sn) => {
/* ... */
})
client.on('systemEvent', (event, sn) => {
/* ... */
})
client.on('event', (event, sn) => {
/* ... */
})
client.on('stateChange', (newState, oldState) => {
/* ... */
})
client.on('open', () => {
/* ... */
})
client.on('close', () => {
/* ... */
})
client.on('error', (message) => {
/* ... */
})
client.on('reset', () => {
/* ... */
})
client.on('reconnecting', (reason) => {
/* ... */
})
// Disconnect the transport while keeping plugins loaded for a later reconnect.
client.disconnect()
// Permanently stop the client and unload all plugins.
client.destroy()Sequenced events are delivered only when their sn values are contiguous. If an event gap cannot be filled before
timing.eventGapTimeoutMs (6000 ms by default), the client opens a resume gateway and asks KOOK to replay the missing
events. It never force-delivers later events across a known gap.
RestClient
HTTP API client with automatic rate limiting.
// Access via client.api
const result = await client.api.createMessage({
type: 9, // KMarkdown
target_id: 'channel-id',
content: 'Hello!',
})
if (result.success) {
console.log('Message sent:', result.data.msg_id)
}All REST API methods return KResponseExt<T> with { success: boolean, code: number, message: string, data: T }.
Network, timeout, cancellation, parsing, and HTTP failures are represented by success: false.
Available methods:
| Method | Description |
| ----------------------------- | ----------------------------- |
| createMessage(props) | Send a message to a channel |
| updateMessage(props) | Update an existing message |
| deleteMessage(props) | Delete a message |
| addReaction(props) | Add a reaction to a message |
| deleteReaction(props) | Remove a reaction |
| uploadAsset(formData) | Upload a file and get its URL |
| getSelfUser() | Get the bot's own user info |
| getUser(props) | Get a user's info |
| listGuilds() | List guilds the bot is in |
| viewGuild(props) | Get guild details |
| listGuildMembers(props) | List members of a guild |
| listChannels(props) | List channels of a guild |
| createChannel(props) | Create a channel |
| deleteChannel(props) | Delete a channel |
| listGuildRoles(props) | List guild roles |
| createGuildRole(props) | Create a guild role |
| grantRole(props) | Grant a role to a user |
| revokeRole(props) | Revoke a role from a user |
| createDirectMessage(props) | Send a direct message |
| request(url, method, data?) | Raw API request |
For a standalone RestClient (without WebSocket):
import { RestClient } from '@kookapp/js-sdk'
const api = new RestClient({ token: 'your-token' })
const result = await api.createMessage({ type: 9, target_id: '...', content: '...' })RestClient uses a 15-second request timeout by default. Raw requests and openGateway() also accept an
AbortSignal, a per-request timeout, and an option to disable the single automatic 429 retry:
const controller = new AbortController()
const result = await api.request('/api/v3/user/me', 'GET', undefined, false, {
signal: controller.signal,
timeoutMs: 5000,
retryOnRateLimit: true,
})CardBuilder
Chainable builder for KOOK card messages.
import { CardBuilder } from '@kookapp/js-sdk'
const card = CardBuilder.fromTemplate({ initialCard: { theme: 'info' } })
.addHeader('Title')
.addKMarkdownText('**Bold** text')
.addDivider()
.addImage('https://example.com/image.png')
.addContext('Footer text')
.build()
await client.api.createMessage({
type: 10, // Card
target_id: 'channel-id',
content: card,
})Available methods:
| Method | Description |
| ------------------------------------------ | ------------------------------------------------------------------------------ |
| .theme(theme) | Set card theme: primary, secondary, info, warning, danger, success |
| .size(size) | Set card size: sm, lg |
| .color(hex) | Set card border color |
| .addHeader(text) | Add a header |
| .addKMarkdownText(content) | Add KMarkdown text section |
| .addPlainText(text) | Add plain text section |
| .addIconWithKMarkdownText(iconUrl, text) | Add text with icon |
| .addImage(url) | Add an image container |
| .addFile(title, url, size) | Add a file module |
| .addDivider() | Add a divider |
| .addContext(text) | Add plain text context |
| .addKMarkdownContext(content) | Add KMarkdown context |
| .addActionGroup(buttons) | Add a button group |
| .addHourCountDown(endAt) | Add hour countdown |
| .addDayCountDown(endAt) | Add day countdown |
| .addSecondCountDown(endAt) | Add second countdown |
| .undoLastAdd() | Remove the last added module |
| .createSnapshot() | Create a snapshot for rollback (returns CardSnapshot \| null) |
| .restore(snapshot) | Restore from a snapshot |
| .build() | Serialize to JSON string |
| .serializedLength | Get the serialized length |
| .lastModule | Get the last module |
Directive System
Register slash-style commands (/command parameter) with permission control.
// Register a directive
client.registerDirective({
triggerWord: 'ping', // or ['ping', 'p'] for aliases
description: 'Ping pong',
parameterDescription: '',
permissionGroups: ['everyone'],
handler: async (context) => {
await client.api.createMessage({
type: 9,
target_id: context.event.target_id,
content: 'Pong!',
quote: context.event.msg_id,
})
},
})
// Create a dispatcher
const dispatcher = client.createDispatcher({
permissionResolver: (userId, userRoles, required) => {
if (required.includes('everyone')) return true
return userRoles.some((r) => required.includes(r))
},
onPermissionDenied: async (context) => {
await client.api.createMessage({
type: 9,
target_id: context.event.target_id,
content: 'Permission denied',
})
},
})
// In your event handler
client.on('textChannelEvent', async (event) => {
const handled = await dispatcher.dispatch(event)
if (!handled) {
// Not a directive, handle as normal message
}
})The DirectiveContext passed to handlers contains:
interface DirectiveContext {
event: KEvent<KTextChannelExtra> // Original event
directive: string // Matched directive name
parameter: string | undefined // Parameter after the directive
user: KUser // Author of the message
mentionRoleIds: number[] // Mentioned role IDs
mentionUserIds: string[] // Mentioned user IDs
}Plugin System
Extend the bot with reusable plugins.
import { KookPlugin, PluginContext } from '@kookapp/js-sdk'
const myPlugin: KookPlugin = {
name: 'my-plugin',
description: 'A custom plugin',
async onLoad(context: PluginContext) {
context.logger.info('Plugin loaded!')
},
onUnload() {
// Cleanup
},
}
await client.use(myPlugin)Plugin names must be unique. client.use() rejects if onLoad() fails, and the failed plugin is not registered.
Asynchronous event hooks are isolated so a rejected hook is logged without creating an unhandled rejection.
Plugin lifecycle hooks:
| Hook | Description |
| -------------------------------- | --------------------------------------------- |
| onLoad(context) | Called when the plugin is loaded |
| onUnload() | Called when the plugin is unloaded |
| onReset() | Called when the WebSocket connection is reset |
| onEvent(event, sn?) | Called for all events |
| onTextChannelEvent(event, sn?) | Called for text channel events |
| onSystemEvent(event, sn?) | Called for system events |
Types
Common types exported from the SDK:
import type {
// WebSocket timing configuration
CreateMessageProps,
// createMessage parameters
CreateMessageResult,
// createMessage return data
// Card message structure
KCardElement,
// Role info
KCardMessage,
// Card element
KCardModule,
KEvent,
// Detailed user info
KGuild,
// Card module
KResponse,
// API response
KResponseExt,
// Guild info
KRole,
// User info
KSelfUser,
// Text channel event extra data
KSystemEventExtra,
// WebSocket event
KTextChannelExtra,
// System event extra data
KUser,
// Bot's own user info
KUserDetail,
// Extended API response with success flag
KWSState,
// WebSocket connection state
WsTimingConfig,
} from '@kookapp/js-sdk'Constant objects (use these instead of enums):
import {
// Connection states: Idle, Connected, etc.
ChannelTypes,
// Channel types
KCardSizes,
// Card sizes: sm, lg
KCardThemes,
KEventTypes,
// Event types: System, KMarkdown, Card, etc.
KMessageKinds,
// WebSocket message kinds: Event, Hello, Ping, Pong, etc.
KWSStates,
// Card themes: primary, secondary, etc.
RequestMethods, // HTTP methods: GET, POST, etc.
} from '@kookapp/js-sdk'Utilities
import {
// Min-heap priority queue
KMessageQueue,
// Message queue for event ordering
// Async task queue with concurrency control
PriorityQueue,
// Decompress WebSocket message
TaskQueue,
createLogger,
// Convert object to URL query string
decompressKMessage,
// Create a logger instance
extractContent,
// Extract plain text from KMarkdown event
isExplicitlyMentioningBot,
// Remove KMarkdown labels from text
parseDirective,
// Parse directive from event
queryFromObject,
// Check if event @mentions the bot
removingKMarkdownLabels,
} from '@kookapp/js-sdk'Error Handling
Data-plane helpers use result values where practical, while lifecycle methods reject when their requested operation cannot be completed:
RestClientmethods returnKResponseExt<T>withsuccess: falseon failureKookClient.connect()rejects when authentication fails or the WebSocket cannot become readyKookClient.use()rejects when a plugin is duplicated, cancelled, or fails to loaddecompressKMessage()returnsnullon failureCardBuilder.createSnapshot()returnsnullon failureCardBuilder.build()returns'[]'on failure- Synchronous and asynchronous WebSocket listeners and plugin hooks are isolated and logged individually
Requirements
- Node.js >= 18.0.0
- TypeScript >= 5.7.0 (for development)
