Bot Development Guide

This guide walks you through creating custom bots for ChatNet. Bots are file-driven plugins that can greet users, respond to messages, call external APIs, and more. No manual SQL is needed — bots are auto-installed when an admin visits the Bots page.

File Structure

Each bot lives in its own folder under src/bots/ and must contain a bot.php entry point:

src/bots/ my_bot/ bot.php ← required entry point

bot.php must define a class that extends BotBase and return an instance at the end of the file:

<?php class MyBot extends BotBase { // ... } return new MyBot();

Required Identity Methods

Every bot must implement these five methods. ChatNet uses them during auto-install to create the bot's user account and database row.

public function getSlug() { return 'my_bot'; } // Unique ID (matches folder name) public function getName() { return 'My Bot'; } // Display name in admin public function getDescription() { return 'Does something'; } // Short description public function getUserName() { return 'my-bot'; } // Bot's system username public function getDisplayName() { return 'My Bot'; } // Bot's display name
MethodPurpose
getSlug()Unique identifier stored in cn_bots.bot_type. Must match the folder name.
getName()Display name shown in the admin bot list
getDescription()Short description shown below the bot name
getUserName()Username for the bot's system user account (e.g. my-bot)
getDisplayName()First name for the bot's user account, shown in chat messages

When auto-installed, ChatNet creates a system user (user_type = 5) and a cn_bots row. The bot is disabled by default — an admin must enable it.


Configuration Fields (Optional)

Override getConfigFields() to define settings that appear in the admin UI. Config values are stored as JSON in the cn_bots.config column.

public function getConfigFields() { return [ [ 'name' => 'greeting', 'label' => 'Greeting Text', 'type' => 'text', 'default' => 'Hello!', 'help' => 'Message the bot sends', ], [ 'name' => 'max_length', 'label' => 'Max Length', 'type' => 'number', 'default' => 200, 'min' => 1, 'max' => 1000, ], [ 'name' => 'mode', 'label' => 'Response Mode', 'type' => 'select', 'default' => 'auto', 'options' => [ ['value' => 'auto', 'label' => 'Automatic'], ['value' => 'manual', 'label' => 'Manual'], ], ], [ 'name' => 'enabled_feature', 'label' => 'Enable Feature', 'type' => 'toggle', 'default' => false, ], ]; }

Supported Field Types

TypeRenders As
textSingle-line text input
textareaMulti-line text area
numberNumeric input (supports min and max)
selectDropdown (requires options array of {value, label})
toggleOn/off switch

Field Properties

PropertyRequiredDescription
nameYesKey used to store/retrieve the value
labelYesLabel shown in the admin form
typeYesField type (see table above)
defaultNoDefault value used on first install
helpNoHelper text shown below the field
optionsFor selectArray of {value, label} objects
min / maxFor numberMinimum and maximum allowed values
room_overrideNoSet to true to allow per-room overrides

Per-Room Config Overrides (Optional)

To allow admins to override specific config fields on a per-room basis, override getRoomOverrideFields():

public function getRoomOverrideFields() { return ['greeting']; // field names that can differ per room }

Also set 'room_override' => true on the corresponding fields in getConfigFields() for clarity. Use the getMergedConfig() helper at runtime to get the final config — it applies room-level overrides automatically.


Event Hooks

Bots respond to events by overriding hook methods. All hooks receive a $context array containing event data and the bot's database rows.

onUserJoinedRoom($context)

Called when a user joins a room. Use this for welcome messages or join notifications.

Context KeyType
user_idint — the user who joined
room_idint — the room they joined
bot_rowarray — the bot's cn_bots row
bot_room_rowarray|null — the cn_bot_rooms row (if any)
public function onUserJoinedRoom($context) { $config = $this->getMergedConfig($context['bot_row'], $context['bot_room_row']); $group = $this->getFirstActiveGroup($context['room_id']); if ($group) { $this->sendMessage($context['bot_row']['user_id'], $config['greeting'], $group['id']); } }

onMessageSent($context)

Called synchronously after a message is saved. This runs inline during the message save flow, so keep it fast. Return null to do nothing, or return an async signal for slow work.

Context KeyType
messagestring — the message text
group_idint — the chat group ID
room_idint — the chat room ID
sender_idint — the user who sent the message
message_typeint — message type (1 = text)
bot_rowarray — the bot's cn_bots row
bot_room_rowarray|null — the cn_bot_rooms row (if any)
public function onMessageSent($context) { // Only handle text messages if ($context['message_type'] != 1) { return null; } if (stripos($context['message'], '!mybot') === false) { return null; // not triggered } // Option A: respond immediately (only for fast operations) $this->sendMessage($context['bot_row']['user_id'], 'Got it!', $context['group_id']); return null; // Option B: signal async processing (for slow work like API calls) return [ 'bot_slug' => $this->getSlug(), 'needs_async' => true, ]; }

onAsyncRespond($context)

Called via a separate AJAX request when onMessageSent() returned needs_async => true. Use this for slow operations like AI calls, external APIs, or heavy processing.

Context KeyType
messagestring — the original message text
group_idint — the chat group ID
room_idint — the chat room ID
bot_rowarray — the bot's cn_bots row
bot_room_rowarray|null — the cn_bot_rooms row (if any)
public function onAsyncRespond($context) { $result = file_get_contents('https://api.example.com/data'); if (!$result) { return false; } $this->sendMessage($context['bot_row']['user_id'], $result, $context['group_id']); return true; }

Built-in Helper Methods

BotBase provides these helpers that you can use in your bot:

MethodDescription
$this->sendMessage($bot_user_id, $message, $group_id)Send a chat message as the bot
$this->getMergedConfig($bot_row, $bot_room_row)Get config with per-room overrides applied
$this->getFirstActiveGroup($room_id)Get the first active chat group in a room

You also have access to ChatNet's service container via app() — for example app('db') for database queries, app('auth') for user lookups.


Complete Example

Here is a minimal bot that responds when users type !ping:

<?php // src/bots/ping/bot.php class PingBot extends BotBase { public function getSlug() { return 'ping'; } public function getName() { return 'Ping Bot'; } public function getDescription() { return 'Replies with Pong when a user types !ping'; } public function getUserName() { return 'ping-bot'; } public function getDisplayName() { return 'Ping Bot'; } public function getConfigFields() { return [ [ 'name' => 'response', 'label' => 'Response Message', 'type' => 'text', 'default' => 'Pong!', 'help' => 'What the bot replies with', ], ]; } public function onMessageSent($context) { if ($context['message_type'] != 1) { return null; } if (stripos($context['message'], '!ping') === false) { return null; } $config = $this->getMergedConfig($context['bot_row'], $context['bot_room_row']); $this->sendMessage($context['bot_row']['user_id'], $config['response'], $context['group_id']); return null; } } return new PingBot();

To install: Create the file at src/bots/ping/bot.php, then visit Admin > Bots — the bot will auto-install and appear in the list. Enable it and set the room mode to start using it.


Bot Lifecycle Summary

  1. Create src/bots/your_bot/bot.php extending BotBase
  2. Admin visits the Bots page → autoInstall() creates the user account and cn_bots row (disabled by default)
  3. Admin enables the bot and sets the room mode (All Rooms, Whitelist, or Blacklist)
  4. ChatNet dispatches events (onUserJoinedRoom, onMessageSent) to enabled bots in matching rooms
  5. If onMessageSent returns needs_async => true, the client fires a follow-up AJAX call to onAsyncRespond

Tips