Security: - Bot registration restricted to BotFather (requires botfather_token) - Direct POST /v1/bot/register without BotFather auth → rejected Deploy: - systemd service reads /home/warzone/server.env for EXTRA_ARGS - deploy/warzone-server.env.mequ: no bots (default) - deploy/warzone-server.env.kh3rad3ree: --enable-bots - setup.sh copies per-hostname env file Docs updated: - LLM_HELP.md: BotFather flow, plaintext bot messaging, E2E option, bridge - LLM_BOT_DEV.md: botfather_token requirement, E2E mode, bridge section - BOT_API.md: full BotFather flow, ownership, numeric IDs, webhook delivery - SERVER.md: --enable-bots flag, per-instance config, bot system section - USAGE.md: bot messaging, BotFather, bridge tool Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
441 lines
12 KiB
Markdown
441 lines
12 KiB
Markdown
# featherChat Bot API
|
|
|
|
## Overview
|
|
|
|
featherChat exposes a **Telegram Bot API-compatible** HTTP interface, allowing
|
|
developers to build bots that interact with featherChat users using familiar
|
|
patterns. Bots are created exclusively through **@botfather**, receive a token,
|
|
and communicate via long-polling or webhooks.
|
|
|
|
The server must be started with `--enable-bots` to activate bot functionality.
|
|
|
|
Key properties:
|
|
|
|
- **BotFather is required** -- only `@botfather` can register bots. It is
|
|
auto-created on first server start (token printed in server logs).
|
|
- Bot aliases **must** end with `Bot`, `bot`, or `_bot` (auto-enforced on
|
|
registration).
|
|
- Bots receive encrypted user messages as **base64 blobs** (`raw_encrypted`
|
|
field) unless registered as E2E bots. Plaintext bot-to-bot messages are
|
|
delivered with a readable `text` field.
|
|
- Bot-sent messages are **plaintext** (not E2E encrypted) unless the bot is
|
|
registered in E2E mode.
|
|
- `chat_id` accepts both hex fingerprints and numeric IDs (Telegram
|
|
compatibility). Numeric IDs are also returned in `from.id`.
|
|
- Each bot has an `owner` field linking to the creating user's fingerprint.
|
|
|
|
---
|
|
|
|
## Quick Start
|
|
|
|
```
|
|
1. Message @botfather to create a bot (or use BotFather token from server logs).
|
|
BotFather registers the bot via:
|
|
POST /v1/bot/register
|
|
{"name": "WeatherBot", "fingerprint": "aabbccdd...", "botfather_token": "<bf_token>"}
|
|
|
|
2. Extract the token from the response.
|
|
|
|
3. Poll for updates:
|
|
POST /v1/bot/<token>/getUpdates
|
|
{"timeout": 50}
|
|
|
|
4. Send a reply:
|
|
POST /v1/bot/<token>/sendMessage
|
|
{"chat_id": "<sender_fingerprint_or_numeric_id>", "text": "Hello!"}
|
|
```
|
|
|
|
---
|
|
|
|
## Endpoints
|
|
|
|
### 1. Register a Bot
|
|
|
|
```
|
|
POST /v1/bot/register
|
|
```
|
|
|
|
Creates a new bot, stores it in the server database, and auto-registers an
|
|
alias. **Only @botfather can call this endpoint** -- a valid `botfather_token`
|
|
is required.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"name": "MyBot",
|
|
"fingerprint": "aabbccdd1122334455667788aabbccdd",
|
|
"botfather_token": "<botfather_token>",
|
|
"owner": "<creator_fingerprint>"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|--------------------|--------|---------------------------------------------------|
|
|
| `name` | string | Display name. Alias suffix auto-added if needed. |
|
|
| `fingerprint` | string | Hex-encoded public key fingerprint for the bot. |
|
|
| `botfather_token` | string | BotFather authorization token (required). |
|
|
| `owner` | string | Fingerprint of the user who requested creation. |
|
|
|
|
**E2E bot registration** (optional additional fields):
|
|
|
|
| Field | Type | Description |
|
|
|---------------|--------|--------------------------------------------------|
|
|
| `e2e` | bool | Set to `true` to register as an E2E bot. |
|
|
| `bundle` | object | Full prekey bundle (identity_key, signed_prekey, signature, one_time_prekeys). |
|
|
| `eth_address` | string | Ethereum address for the bot. |
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"result": {
|
|
"token": "aabbccdd11223344:9f8e7d6c5b4a39281706abcdef012345",
|
|
"name": "MyBot",
|
|
"fingerprint": "aabbccdd1122334455667788aabbccdd",
|
|
"alias": "@mybot_bot",
|
|
"owner": "<creator_fingerprint>"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Token format:** `<first-16-chars-of-fingerprint>:<32-hex-random-bytes>`
|
|
|
|
**Alias rules:**
|
|
|
|
- If the name already ends with `Bot`, `bot`, or `_bot`, the alias is the
|
|
lowercased name (e.g. `WeatherBot` -> `@weatherbot`).
|
|
- Otherwise `_bot` is appended (e.g. `weather` -> `@weather_bot`).
|
|
- The alias is registered in both directions (alias -> fingerprint and
|
|
fingerprint -> alias).
|
|
|
|
---
|
|
|
|
### 2. Get Bot Info
|
|
|
|
```
|
|
GET /v1/bot/:token/getMe
|
|
```
|
|
|
|
Returns information about the bot in a Telegram-compatible shape.
|
|
|
|
**Response (valid token):**
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"result": {
|
|
"id": "aabbccdd1122334455667788aabbccdd",
|
|
"is_bot": true,
|
|
"first_name": "MyBot",
|
|
"username": "MyBot"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response (invalid token):**
|
|
|
|
```json
|
|
{
|
|
"ok": false,
|
|
"description": "invalid token"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. Get Updates (Long-Poll)
|
|
|
|
```
|
|
POST /v1/bot/:token/getUpdates
|
|
```
|
|
|
|
Returns queued messages for the bot and deletes them from the queue.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"timeout": 5
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|-----------|------|------------------------------------------------------|
|
|
| `timeout` | u64 | Optional. Long-poll wait in seconds. **Capped at 50.** |
|
|
|
|
If the queue is empty and `timeout > 0`, the server waits up to `timeout`
|
|
seconds (max 50) before returning an empty result, giving new messages a chance
|
|
to arrive.
|
|
|
|
> **Note:** If a webhook is configured via `setWebhook`, updates are delivered
|
|
> live to the webhook URL via POST instead of being queued for polling.
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"result": [ ...updates... ]
|
|
}
|
|
```
|
|
|
|
#### Update Types
|
|
|
|
**Encrypted message** (from a user — bot must decrypt if it has a session):
|
|
|
|
```json
|
|
{
|
|
"update_id": 1,
|
|
"message": {
|
|
"message_id": "uuid",
|
|
"from": {
|
|
"id": "sender_fingerprint",
|
|
"is_bot": false,
|
|
"first_name": "sender_finge"
|
|
},
|
|
"chat": {
|
|
"id": "sender_fingerprint",
|
|
"type": "private"
|
|
},
|
|
"date": 1711670400,
|
|
"text": null,
|
|
"raw_encrypted": "base64-encoded-wiremessage..."
|
|
}
|
|
}
|
|
```
|
|
|
|
**Key exchange** (X3DH session initiation — same shape as encrypted message):
|
|
|
|
```json
|
|
{
|
|
"update_id": 2,
|
|
"message": {
|
|
"message_id": "uuid",
|
|
"from": { "id": "sender_fp", "is_bot": false, "first_name": "sender_fp..." },
|
|
"chat": { "id": "sender_fp", "type": "private" },
|
|
"date": 1711670400,
|
|
"text": null,
|
|
"raw_encrypted": "base64-encoded-keyexchange..."
|
|
}
|
|
}
|
|
```
|
|
|
|
**Call signal:**
|
|
|
|
```json
|
|
{
|
|
"update_id": 3,
|
|
"message": {
|
|
"message_id": "uuid",
|
|
"from": { "id": "sender_fp", "is_bot": false, "first_name": "sender_fp..." },
|
|
"chat": { "id": "sender_fp", "type": "private" },
|
|
"date": 1711670400,
|
|
"text": "/call_Offer",
|
|
"call_signal": {
|
|
"type": "Offer",
|
|
"payload": "SDP or ICE data..."
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**File header:**
|
|
|
|
```json
|
|
{
|
|
"update_id": 4,
|
|
"message": {
|
|
"message_id": "uuid",
|
|
"from": { "id": "sender_fp", "is_bot": false, "first_name": "sender_fp..." },
|
|
"chat": { "id": "sender_fp", "type": "private" },
|
|
"date": 1711670400,
|
|
"document": {
|
|
"file_name": "report.pdf",
|
|
"file_size": 204800
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
**Bot message (plaintext, from another bot via `sendMessage`):**
|
|
|
|
```json
|
|
{
|
|
"update_id": 5,
|
|
"message": {
|
|
"message_id": "uuid",
|
|
"from": {
|
|
"id": "other_bot_fingerprint",
|
|
"is_bot": true
|
|
},
|
|
"chat": {
|
|
"id": "other_bot_fingerprint",
|
|
"type": "private"
|
|
},
|
|
"date": 1711670400,
|
|
"text": "Hello from the other bot!"
|
|
}
|
|
}
|
|
```
|
|
|
|
> **Note:** Receipt and internal wire messages (FileChunk, GroupSenderKey,
|
|
> SenderKeyDistribution) are silently skipped and never delivered as updates.
|
|
|
|
---
|
|
|
|
### 4. Send Message
|
|
|
|
```
|
|
POST /v1/bot/:token/sendMessage
|
|
```
|
|
|
|
Sends a **plaintext** message to a user or another bot.
|
|
|
|
**Request:**
|
|
|
|
```json
|
|
{
|
|
"chat_id": "aabbccdd1122334455667788aabbccdd",
|
|
"text": "Hello from MyBot!"
|
|
}
|
|
```
|
|
|
|
| Field | Type | Description |
|
|
|--------------|--------|-----------------------------------------------------------|
|
|
| `chat_id` | string/int | Recipient fingerprint (hex), Ethereum address, or numeric ID. |
|
|
| `text` | string | Message body. |
|
|
| `parse_mode` | string | Optional. `"HTML"` renders basic tags (<b>, <i>, <code>, <a>). |
|
|
|
|
`chat_id` accepts hex fingerprint strings, Ethereum addresses, or numeric
|
|
integer IDs (Telegram compatibility). Non-hex characters in string chat_ids are
|
|
stripped and the value is lowercased before routing.
|
|
|
|
**Response:**
|
|
|
|
```json
|
|
{
|
|
"ok": true,
|
|
"result": {
|
|
"message_id": "550e8400-e29b-41d4-a716-446655440000",
|
|
"chat": {
|
|
"id": "aabbccdd1122334455667788aabbccdd",
|
|
"type": "private"
|
|
},
|
|
"text": "Hello from MyBot!",
|
|
"date": 1711670400,
|
|
"delivered": true
|
|
}
|
|
}
|
|
```
|
|
|
|
The `delivered` field indicates whether the message was sent over a live
|
|
WebSocket connection (`true`) or queued for later retrieval (`false`).
|
|
|
|
---
|
|
|
|
## Alias Rules
|
|
|
|
| Rule | Detail |
|
|
|------|--------|
|
|
| Bot aliases **must** end with `Bot`, `bot`, or `_bot` | Enforced at registration time. |
|
|
| Non-bot users **cannot** register aliases with these suffixes | Reserved for bots. |
|
|
| Auto-registered on bot creation | No separate alias step needed. |
|
|
| Users message bots via alias | e.g. `@mybot_bot`, resolved like any other alias. |
|
|
|
|
---
|
|
|
|
## Differences from Telegram Bot API
|
|
|
|
| Feature | Telegram | featherChat |
|
|
|---------|----------|-------------|
|
|
| `chat_id` type | Numeric integer | Hex fingerprint string or numeric integer (both accepted) |
|
|
| `getUpdates` timeout | Up to 50s | Capped at **50s** |
|
|
| Message content | Always plaintext | Encrypted messages arrive as `raw_encrypted` base64; E2E bots can decrypt |
|
|
| Bot-sent messages | Plaintext | Plaintext by default; E2E mode available |
|
|
| `from.id` | Numeric integer | Numeric integer (`from.id_str` has hex fingerprint) |
|
|
| `parse_mode` | Renders HTML/Markdown | HTML rendered (<b>, <i>, <code>, <a>) |
|
|
| Inline keyboards / callback queries | Supported | Stored + delivered, no popup |
|
|
| Webhooks (`setWebhook`) | Supported | Implemented -- updates delivered live to webhook URL |
|
|
| Media groups | Supported | Not yet (planned) |
|
|
| File download (`getFile`) | Supported | Not yet (planned) |
|
|
|
|
---
|
|
|
|
## Example: Simple Echo Bot (Python)
|
|
|
|
```python
|
|
import requests
|
|
import time
|
|
|
|
TOKEN = "your_bot_token"
|
|
API = f"http://localhost:7700/v1/bot/{TOKEN}"
|
|
|
|
while True:
|
|
resp = requests.post(f"{API}/getUpdates", json={"timeout": 50}).json()
|
|
for update in resp.get("result", []):
|
|
msg = update.get("message", {})
|
|
text = msg.get("text") or "[encrypted]"
|
|
chat_id = msg.get("chat", {}).get("id", "")
|
|
if text and chat_id:
|
|
requests.post(f"{API}/sendMessage", json={
|
|
"chat_id": chat_id,
|
|
"text": f"Echo: {text}",
|
|
})
|
|
time.sleep(1)
|
|
```
|
|
|
|
### Example: Registration (curl)
|
|
|
|
```bash
|
|
curl -X POST http://localhost:7700/v1/bot/register \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"name": "EchoBot", "fingerprint": "aabbccdd1122334455667788aabbccdd"}'
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication
|
|
|
|
All bot endpoints (except `/register`) are authenticated by the **token** in
|
|
the URL path. Tokens are generated at registration time and stored server-side.
|
|
There is no expiration mechanism in v1 -- tokens remain valid until the server
|
|
database is cleared.
|
|
|
|
The token grants full access to poll and send messages as the bot. **Treat it
|
|
like a password.**
|
|
|
|
---
|
|
|
|
## Internal Details
|
|
|
|
- Bot info is stored in the `tokens` sled tree under key `bot:<token>`.
|
|
- A reverse lookup `bot_fp:<fingerprint>` -> `<token>` is also maintained.
|
|
- Aliases are stored in the `aliases` sled tree (`a:<alias>` -> fingerprint,
|
|
`fp:<fingerprint>` -> alias).
|
|
- Queued messages live in the `messages` sled tree under prefix
|
|
`queue:<bot_fingerprint>:*` and are deleted after `getUpdates` consumes them.
|
|
- Messages are delivered via `deliver_or_queue` -- live WebSocket if online,
|
|
otherwise queued.
|
|
|
|
---
|
|
|
|
## Bot Bridge (`tools/bot-bridge.py`)
|
|
|
|
A compatibility layer for existing Telegram bot libraries. Translates between
|
|
featherChat Bot API and standard TG libraries (python-telegram-bot, aiogram,
|
|
Telegraf). Handles differences like fingerprint-based chat_id, numeric ID
|
|
translation, and webhook forwarding.
|
|
|
|
```bash
|
|
python tools/bot-bridge.py --token YOUR_BOT_TOKEN --server http://localhost:7700
|
|
```
|
|
|
|
---
|
|
|
|
## Future Plans
|
|
|
|
- **File send/receive APIs** -- `sendDocument`, `getFile`.
|
|
- **Group bot support** -- bots in group chats with sender-key encryption.
|