Scripting — API Reference

Complete reference for the KokoAPI object passed to every script's init function.

export default function init(api: KokoAPI) {
  // api.on, api.irc, api.store, etc.
}

Events

api.on(event, handler, priority?)

Register an event handler. Returns an unsubscribe function.

const unsub = api.on("irc.privmsg", (event, ctx) => {
  // handle message
})

// Later, to remove:
unsub()

Parameters:

Param Type Description
event string Event name (see event list below)
handler (data: any, ctx: EventContext) => void Handler function
priority number Optional. Default: EventPriority.NORMAL (50)

Handlers run in descending priority order. Handlers registered through the API are automatically removed when the script is unloaded.

api.once(event, handler, priority?)

Same as api.on() but the handler fires only once, then removes itself.

api.emit(event, data?)

Emit a custom event. The event name is automatically prefixed with script. to avoid collisions with built-in events. Returns false if a handler called stop().

api.emit("my-event", { foo: "bar" })
// Other scripts can listen with: api.on("script.my-event", handler)

api.EventPriority

Priority constants for event handler ordering:

Constant Value Use case
HIGHEST 100 Filters, spam blockers — run first
HIGH 75 Pre-processing
NORMAL 50 Default for most handlers
LOW 25 Post-processing
LOWEST 0 Logging, analytics — run last

EventContext

The second argument to every event handler:

api.on("irc.privmsg", (event, ctx) => {
  ctx.stop()    // Prevent lower-priority handlers AND the built-in
                // store update from running
  ctx.stopped   // boolean — whether stop() has been called
})

ctx.stop() must be called synchronously (before any await). When a handler stops an event, the built-in kokoIRC handler that would normally update the store (add the message to the buffer, update nick lists, etc.) is skipped entirely.


IRC Events

These events fire when the IRC server sends data. Each event can be intercepted — if a handler calls ctx.stop(), the default store update is suppressed.

irc.privmsg

A channel or private message.

interface IrcMessageEvent {
  connectionId: string
  nick: string
  ident?: string
  hostname?: string
  target: string       // channel name or your nick (for PMs)
  message: string      // raw message text
  tags?: Record<string, string>
  time?: string        // server-time tag
  isChannel: boolean   // true if target is a channel
}

irc.action

A CTCP ACTION (/me message). Same payload as irc.privmsg.

irc.notice

interface IrcNoticeEvent {
  connectionId: string
  nick?: string        // undefined for server notices
  target?: string
  message: string
  from_server?: boolean
}

irc.join

interface IrcJoinEvent {
  connectionId: string
  nick: string
  ident?: string
  hostname?: string
  channel: string
  account?: string     // IRCv3 account-notify
}

irc.part

interface IrcPartEvent {
  connectionId: string
  nick: string
  ident?: string
  hostname?: string
  channel: string
  message?: string     // part reason
}

irc.quit

interface IrcQuitEvent {
  connectionId: string
  nick: string
  ident?: string
  hostname?: string
  message?: string     // quit reason
}

irc.kick

interface IrcKickEvent {
  connectionId: string
  nick: string         // who kicked
  ident?: string
  hostname?: string
  channel: string
  kicked: string       // who was kicked
  message?: string     // kick reason
}

irc.nick

interface IrcNickEvent {
  connectionId: string
  nick: string         // old nick
  new_nick: string     // new nick
  ident?: string
  hostname?: string
}

irc.topic

interface IrcTopicEvent {
  connectionId: string
  nick?: string        // who changed it (undefined for initial topic)
  channel: string
  topic: string
}

irc.mode

interface IrcModeEvent {
  connectionId: string
  nick?: string        // who set the mode
  target: string       // channel or nick
  modes: Array<{ mode: string; param?: string }>
}

irc.invite

interface IrcInviteEvent {
  connectionId: string
  nick: string         // who invited you
  channel: string
}

irc.ctcp_request

interface IrcCtcpEvent {
  connectionId: string
  nick: string
  type: string         // e.g. "PING", "TIME"
  message?: string
}

irc.ctcp_response

Same shape as irc.ctcp_request.

irc.wallops

interface IrcWallopsEvent {
  connectionId: string
  nick?: string
  message: string
  from_server?: boolean
}

App Events

These events are emitted by kokoIRC itself, not the IRC protocol.

command_input

Fired before a command is executed. Call ctx.stop() to prevent the command from running.

interface CommandInputEvent {
  command: string      // command name without "/"
  args: string[]
  connectionId: string
}

connected

interface ConnectedEvent {
  connectionId: string
  nick: string
}

disconnected

interface DisconnectedEvent {
  connectionId: string
}

Commands

api.command(name, def)

Register a custom slash command. The command is available as /name in the input bar.

api.command("greet", {
  handler: (args, connectionId) => {
    const target = args[0] ?? "world"
    api.irc.say(target, `Hello, ${target}!`, connectionId)
  },
  description: "Send a greeting",
  usage: "/greet <nick>",
})

ScriptCommandDef:

Field Type Description
handler (args: string[], connectionId: string) => void Command handler
description string Shown in /help
usage string? Usage string (optional)

api.removeCommand(name)

Remove a command registered by this script.


IRC Methods

All IRC methods take an optional connectionId as the last parameter. If omitted, the active buffer's connection is used.

api.irc.say(target, message, connectionId?)

Send a PRIVMSG to a channel or nick.

api.irc.action(target, message, connectionId?)

Send a CTCP ACTION (/me).

api.irc.notice(target, message, connectionId?)

Send a NOTICE.

api.irc.join(channel, key?, connectionId?)

Join a channel, optionally with a key.

api.irc.part(channel, message?, connectionId?)

Leave a channel with an optional part message.

api.irc.raw(line, connectionId?)

Send a raw IRC protocol line.

api.irc.raw("AWAY :Gone fishing")
api.irc.raw("AWAY :Gone fishing", "myserver")

api.irc.changeNick(nick, connectionId?)

Change your nickname.

api.irc.whois(nick, connectionId?)

Send a WHOIS query.

api.irc.getClient(connectionId?)

Get the underlying IRC framework Client object for advanced use. Returns undefined if the connection doesn't exist.


UI Methods

api.ui.addLocalEvent(text)

Add a local event message to the active buffer. These are informational messages visible only to the user (not sent over IRC).

api.ui.addLocalEvent("Script loaded successfully")

api.ui.addMessage(bufferId, message)

Add a message to a specific buffer. The id and timestamp fields are set automatically.

api.ui.addMessage(bufferId, {
  type: "event",
  text: "Something happened",
  highlight: false,
})

api.ui.switchBuffer(bufferId)

Switch the active buffer.

api.ui.makeBufferId(connectionId, name)

Generate a buffer ID from a connection ID and buffer name (channel or nick).

const bufId = api.ui.makeBufferId("myserver", "#channel")
api.ui.switchBuffer(bufId)

Store

api.store

Read-only access to the Zustand store. Available methods:

Method Returns
getConnections() Map<string, Connection>
getBuffers() Map<string, Buffer>
getActiveBufferId() string | null
getConfig() AppConfig | null
getConnection(id) Connection | undefined
getBuffer(id) Buffer | undefined
subscribe(listener) () => void (unsubscribe)
const conns = api.store.getConnections()
for (const [id, conn] of conns) {
  api.log(`Connected to ${conn.host} as ${conn.nick}`)
}

Config

api.config.get(key, defaultValue)

Get a per-script config value. Lookup order: user TOML override > script config export > defaultValue.

api.config.set(key, value)

Set a per-script config value at runtime. This updates the in-memory config (not the TOML file).


Timers

api.timer(ms, handler)

Set a repeating interval. Returns a TimerHandle with a clear() method. Automatically cleared on script unload.

const ping = api.timer(60_000, () => {
  api.log("One minute has passed")
})

// Later:
ping.clear()

api.timeout(ms, handler)

Set a one-shot timeout. Returns a TimerHandle. Automatically cleared on script unload.


Logging

api.log(...args)

Log a message to the console. Only outputs when scripts.debug = true in config.toml. Messages are prefixed with [script:<name>].

[scripts]
debug = true
api.log("Processing message from", event.nick)
// Console: [script:my-script] Processing message from Alice