kobun4 Documentation

kobun4 is a user-extensible bot for multiple chat protocols.

Scripting Documentation

kobun4’s commands (generally prefixed with .) are all scriptable via the web UI at https://kobun.company/editor.

Scripts can be written via the web interface and are run in a sandbox, which has certain limitations you should be aware of. They can be written in any executable format, and will be directly execed by the executor in the sandbox (a #! on the first line is required for specifying the script interpreter).

When a user executes a script (via .<command name>), a script is execed in the sandbox and the argument sent to its stdin. Its stdout and stderr, up to a limit (generally 5MB each) are captured and sent to the bridge requesting execution, as well as exit code.

  • On exit code 0, the bridge will report success, along with the contents of stdout (success).
  • On any exit code other than 2, the bridge will report failure, along with the contents of stderr (script failure).
  • On exit code 2, the bridge will report failure, along with the contents of stdout (script error).
  • On SIGKILL, the bridge will report that execution took too long.
  • On any other signal, the bridge will report the signal details.

Clients

The scripting environment comes with a set of pre-written clients for easier interaction with the scripting API in /usr/lib/k4.

Python

#!/usr/bin/python3

import sys
sys.path.insert(0, '/usr/lib/k4')
import k4

client = k4.Client()

All members of the context are exposed via client.context.

Services can be accessed directly via members on the client object, e.g.:

user_name = client.Bridge.GetUserInfo(id='example').name

Lua

#!/usr/bin/lua

package.path = '/usr/lib/k4/?.lua;' .. package.path
k4 = require('k4')

client = k4.Client.new()

The Lua client is experimental.

All members of the context are exposed via client.context.

Services can be accessed via the call method, e.g.:

user_name = client.call('Bridge.GetUserInfo', {id='example'}).name

JavaScript (Node.js)

#!/usr/bin/node

const k4 = require('/usr/lib/k4/k4');

const client = new k4.Client();

The Node.js client is experimental.

All members of the context are exposed via client.context.

Services can be accessed via the call method, e.g.:

client.call('Bridge.GetUserInfo', {id: 'example'}, function (err, resp) {
   var user_name = resp.name;
});

Warning

You must call client.close() when you are done with the client, or Node.js will hang indefinitely.

Bridges

Bridges are kobun4’s way of communicating with chat services.

Discord

Discord

The discord bridge connects to the Discord chat service.

Mappings
  • Users: Mapped to Discord users via their numeric ID.
  • Channels: Mapped to Discord channels via their numeric ID.
  • Groups: Mapped to Discord servers (also known as guilds) via their numeric ID.
  • Networks: Only a single network exists, named discord.
Output Formats

The Discord bridge supports the following output formats:

  • text: Specifies a Discord message in plain text. The output will be placed in an embed, and the text message content will contain success information. The embed will be green on success and red on failure.
  • rich: Specifies a Discord message in rich format.
  • discord.embed: Specifies a Discord embed. The output will be unmarshaled from JSON as an embed and sent.
  • discord.embed_multipart: Specifies a multipart Discord embed to send. The output will be parsed as a multipart MIME message. The first part of the multipart request will be interpreted as the embed and unmarshaled from JSON. The remaining parts will be considered file attachments.
Deputy Permissions

Warning

Please make sure you understand the security implications of granting Kobun administrative permissions! The developers of Kobun are not liable for any damages or losses incurred by enabling these features!

The following deputy commands require these permissions to be granted:

  • DeleteInputMessage: Manage Messages

Context

The context contains immutable information about the state of the world in which the script was executed in.

It is contained in the environment variable K4_CONTEXT, marshaled as JSON.

The following fields are always available:

bridgeName

The name of the chat service or otherwise entity that requested execution of the script (the bridge).

commandName

The name of the command used to run the script.

userId

The ID of the user who requested the command to be executed.

Warning

userId does not completely uniquely identify a user. In order to identify a user completely, it must be used in combination with networkId and bridgeName, e.g. in the form bridgeName/networkId/userId.

channelId

The ID of the channel the command was executed on.

groupId

The ID of the group the command was executed on.

networkId

The ID of the network the command was executed on.

scriptCommandPrefix

The prefix for script commands (usually .).

extra

Additional chat service-specific information.

Services

kobun4 exposes a set of services to scripts. These services allow scripts to communicate out-of-band with kobun4 to access facilities that extend beyond simple input/output.

Services are accessible via a socket connection on file descriptor 3. Requests are sent JSON-marshaled with no line breaks permitted, terminated with a newline:

{"id": /* (number) sequence number */, "method": /* (string) method name */, "params": [/* (object) request body */]}

Requests are similarly received JSON-marshaled on a single line, terminated with a newline:

{"id": /* (number) sequence number */, "error": /* (string?) error, null if no error */, "result": /* (object?) result, null if error */}

The response’s ID must match the request’s ID, otherwise the response must not be considered the response to a sent request.

The following sections describe the services available via this RPC interface.

NetworkInfo

The network information service provides scripts with the ability to interact with the network they’re running on.

NetworkInfo.GetUserInfo(id: string) → {name: string, extra: object}

Looks up a user’s information by their user ID.

Parameters:id – The ID to look up.
Returns:Information about the user. extra contains additional network-specific information.
NetworkInfo.GetChannelInfo(id: string) → {name: string, isOneOnOne: boolean, extra: object}

Looks up a channel’s information by its channel ID.

Parameters:id – The ID to look up.
Returns:Information about the channel. isOneOnOne is true if and only if the channel is a private channel with the bot. extra contains additional network-specific information.
NetworkInfo.GetGroupInfo() → {name: string, extra: object}

Looks up the current group’s information.

Returns:Information about the group. extra contains additional network-specific information.
NetworkInfo.GetChannelMemberInfo(channelId: string, userId: string) → {name: string, roles: string[], extra: object}

Looks up a channel member’s information by a channel ID and their user ID.

Parameters:
  • channelId – The ID of the channel the user is a member of.
  • userId – The user ID of the member to look up.
Returns:

Information about the channel member. name may contain their channel-specific username – if channel-specific usernames do not exist, their regular username will be returned. extra contains additional network-specific information.

NetworkInfo.GetGroupMemberInfo(userId: string) → {name: string, roles: string[], extra: object}

Looks up a group member’s information by their user ID.

Parameters:userId – The user ID of the member to look up.
Returns:Information about the group member. name may contain their group-specific username – if group-specific usernames do not exist, their regular username will be returned. extra contains additional network-specific information.

Output

The output service allows scripts to set out-of-band metadata on the output of scripts.

Output.SetFormat(format: string)

Sets the output format of the script. The default is text, which will be interpreted as simple text output. Other formats are dependent on the chat service the script is being executed on.

Parameters:format – The output format to use.
Output.SetPrivate(private: boolean)

Sets the output of the script to be sent to a private message.

Parameters:private – Whether or not the output should be sent via a private message.
Output.SetExpires(expires: boolean)

Sets whether or not the message should expire.

Parameters:expire – Whether or not the message should expire.
Formats
text

Plain text format.

rich

Rich content format. Must be in the JSON with the following format:

{
    "fallback": "...",       // (required) fallback plain text for non-rich content bridges
    "color": 0,              // (optional) color as an 24-bit integer in RGB order
    "author": "...",         // (optional) author of the content
    "authorLink": "...",     // (optional) link to the author
    "authorIconURL": "...",  // (optional) URL to an icon representing the author
    "title": "...",          // (optional) title of the content
    "titleLink": "...",      // (optional) link for the title
    "text: "... ",           // (optional) description of the content
    "fields": [              // (optional) list of fields describing the content
        {
            "name": "...",   // (required) name of the field
            "value": "...",  // (required) content of the field
            "inline": false  // (optional) attempt to save space by packing fields horizontally when possible
        },
        ...
    ],
    "imageURL": "...",       // (optional) URL to an image representing the content
    "thumbnailURL": "...",   // (optional) URL to a thumbnail representing the content
    "footer": "...",         // (optional) text to place in the footer
    "footerIconURL": "...",  // (optional) icon to show next to the footer
    "timestamp": 0           // (optional) UNIX timestamp of when the content was produced
}

Deputy

The deputy service allows Kobun to perform certain restricted administrative tasks on behalf of the command issuer.

These commands are those that the command issuer would have been able to take themselves.

Please refer to the documentation for your bridge to determine how to grant the correct permissions for these features.

Warning

Please make sure you understand the security implications of granting Kobun administrative permissions! The developers of Kobun are not liable for any damages or losses incurred by enabling these features!

Deputy.DeleteInputMessage()

Deletes the input message used to trigger the command, if supported.

Messaging

Note

Permissions to use the messaging service must be granted explicitly by an operator of Kobun.

The messaging service allows scripts to message users or channels out-of-band.

Messaging.MessageUser(id: string, format: string, content: string)

Sends a direct message to a user.

Parameters:
  • id – The user ID to send the message to.
  • format – The output format to send the message with.
  • content – The content to send.
Messaging.MessageChannel(id: string, format: string, content: string)

Sends a message to a channel.

Parameters:
  • id – The channel ID to send the message to.
  • format – The output format to send the message with.
  • content – The content to send.

Storage

The kobun4 scripting environment provides both ephemeral and persistent storage.

/mnt/private

/mnt/private is the persistent storage location on a per-account basis. It is limited in size and is guaranteed to be persistent from one script execution to the next.

Persistent storage can be accessed via WebDAV at the URL https://storage.kobun.company.

/tmp

/tmp is the ephemeral storage location. It is limited in size and will be wiped from one script execution to the next.

/mnt/scripts

/mnt/scripts is a read-only mount containing all scripts, with <account handle>/<script name> paths.

Developer Documentation

This section of the documentation is dedicated to development of kobun4 itself. If you are looking for documentation on writing scripts, please consult the scripting documentation.

Sandbox

All kobun4 commands are run in an nsjail sandbox. Scripts are subject to various restrictions, depending on the user who owns the script:

  • A clean chroot, independent of the host.
  • A maximum memory limitation.
  • A maximum time limit.
  • A single persistent storage area in /mnt/private.
  • A single ephemeral storage area in /tmp.
  • Throttled network access, if permitted.

Operations Documentation

This section covers the running and the operation of your own instance of kobun4.

Postgres

kobun4 uses Postgres for data storage. The executor and each bridge require their own database, and their schemas are available in schema.sql in each component’s directory.

Each component should have its own Postgres user, to ensure isolation between processes.

Networking

kobun4 uses Linux virtual “veth” Ethernet devices for networking. In order to use veth, some configuration is required.

Make sure IP forwarding is enabled in /etc/sysctl.conf:

net.ipv4.ip_forward=1

Make sure there are appropriate bridge and veth interfaces, with one half of the veth interface in a network namespace named kobun4. For Debian/Ubuntu, these can be configured inside /etc/network/interfaces (with bandwidth throttling managed by wondershaper):

auto br0
iface br0 inet static
    bridge_ports none
    address 10.0.0.1
    netmask 255.255.255.0
    network 10.0.0.0
    broadcast 10.0.0.255

auto hostveth0
iface hostveth0 inet manual
    pre-up ip link add dev hostveth0 type veth peer name guestveth0
    pre-up ip link set dev hostveth0 master br0
    pre-up ip netns add kobun4
    pre-up ip link set guestveth0 netns kobun4
    up ip link set dev hostveth0 up
    up ip netns exec kobun4 ip link set dev lo up
    up ip netns exec kobun4 ip link set dev guestveth0 up
    post-up ip netns exec kobun4 ip addr add 10.0.0.2/8 broadcast 10.255.255.255 dev guestveth0
    post-up ip netns exec kobun4 ip route add default via 10.0.0.1
    post-up wondershaper hostveth0 1024 1024
    down wondershaper remove hostveth0
    down ip link set dev hostveth0 down
    post-down ip link del hostveth0
    post-down ip netns del kobun4

iptables should also be set up to allow NAT:

iptables -t nat -A POSTROUTING -o br0 -j MASQUERADE
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE

To allow Kobun to use network namespaces, you must also grant the executor user permission to run nsenter for the namespace. In your sudoers file, which you can edit using visudo, add:

kobun4-executor ALL=(ALL) NOPASSWD: /usr/bin/nsenter -n/run/netns/kobun4 sudo -u kobun4-executor *

Storage

kobun4 recommends that quotas should be set up for user storage locations to prevent any one user from using all available disk space. Any file system with per-directory quotas can be used, but ZFS is the most straightforward.

You must first create a storage pool for all user storage:

zpool create kobun4-executor-storage <path to disk/image> -m /var/lib/kobun4/executor/storage

For each registered user, you must also create their directory and set their quota:

zfs create kobun4-executor-storage/exampleuser
zfs set quota=20M kobun4-executor-storage/exampleuser

systemd Configuration

kobun4 uses systemd units for daemon configuration. If you have installed kobun4 into /opt/kobun4, the units are available in the systemd subdirectory.

systemctl can be used to enable the units:

systemctl enable /opt/kobun4/systemd/kobun4-executor.service
systemctl enable /opt/kobun4/systemd/kobun4-discordbridge.service

Service configurations are stored in /etc/kobun4 as each component’s name. Please consult the unit files for the applicable environment variables.

The unit files specify that each component is run under a POSIX user with the same name as the component (e.g. executor runs under the kobun4-executor user).