# featherChat Bot Development Reference ## Prerequisites Server must run with `--enable-bots`: ```bash warzone-server --bind 0.0.0.0:7700 --enable-bots ``` ## Creating a Bot Message `@botfather` in the chat client (TUI or web): ``` You: /peer @botfather You: /newbot MyAssistantBot BotFather: Done! Your new bot @myassistantbot is ready. Token: a1b2c3d4e5f6a7b8:9876543210abcdef... Keep this token secret! ``` BotFather commands: - `/newbot ` — create bot (name must end with bot/Bot) - `/mybots` — list your bots - `/deletebot ` — delete bot you own - `/token ` — show token for your bot - `/help` — show commands ## How Users Message Bots When a user messages a bot alias (`@*bot`, `@*Bot`, `@*_bot`, `@botfather`), the client **automatically sends plaintext** — no E2E encryption. The bot receives readable `text` in getUpdates. This is automatic — no configuration needed. The client detects the bot alias suffix. ## API Base ``` http://SERVER:7700/v1/bot/TOKEN/METHOD ``` ## Endpoints ### getMe ``` GET /v1/bot/TOKEN/getMe → {"ok":true,"result":{"id":123456,"id_str":"aabbccdd...","is_bot":true,"first_name":"MyBot"}} ``` ### getUpdates ``` POST /v1/bot/TOKEN/getUpdates {"offset":LAST_ID+1,"timeout":50,"limit":100} ``` Response: ```json {"ok":true,"result":[ {"update_id":1,"message":{ "message_id":"uuid", "from":{"id":123456,"is_bot":false}, "chat":{"id":123456,"type":"private"}, "date":1711612800, "text":"Hello bot!" }} ]} ``` **Fields:** - `offset` — skip updates < offset (acknowledge processed). **Always use this.** - `timeout` — long-poll seconds (max 50, matches Telegram) - `limit` — max updates (default 100) - `from.id` — numeric (per-bot unique hash, different bots see different IDs for same user) - No raw fingerprint exposed to bots (privacy: bots can't correlate users cross-bot) ### sendMessage ``` POST /v1/bot/TOKEN/sendMessage { "chat_id": "fingerprint_hex_or_numeric_id", "text": "Hello!", "parse_mode": "HTML", "reply_to_message_id": "msg_uuid", "reply_markup": { "inline_keyboard": [ [{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}] ] } } → {"ok":true,"result":{"message_id":"uuid","delivered":true}} ``` `chat_id` accepts: hex fingerprint string, numeric i64, or `0x` ETH address. `parse_mode` "HTML" renders ``, ``, ``, `` in web client. ### editMessageText ``` POST /v1/bot/TOKEN/editMessageText {"chat_id":"..","message_id":"uuid","text":"Updated","reply_markup":{...}} ``` ### answerCallbackQuery ``` POST /v1/bot/TOKEN/answerCallbackQuery {"callback_query_id":"id","text":"Done!","show_alert":false} → {"ok":true,"result":true} ``` ### sendDocument ``` POST /v1/bot/TOKEN/sendDocument {"chat_id":"..","document":"filename_or_url","caption":"optional"} ``` ### Webhooks ``` POST /v1/bot/TOKEN/setWebhook {"url":"https://mybot.example.com/hook"} POST /v1/bot/TOKEN/deleteWebhook GET /v1/bot/TOKEN/getWebhookInfo ``` When set, updates are POSTed to the URL instead of queued for getUpdates. ## Update Types **User message (plaintext — default for bot recipients):** ```json {"update_id":1,"message":{"message_id":"id","from":{"id":123,"id_str":"fp"},"chat":{"id":123,"id_str":"fp","type":"private"},"text":"Hello bot!","date":1234567890}} ``` **Bot-to-bot message:** ```json {"update_id":2,"message":{"message_id":"id","from":{"id":456,"is_bot":true},"chat":{"id":456,"type":"private"},"text":"inter-bot msg","date":1234567890}} ``` **E2E encrypted (user sent without bot detection — rare):** ```json {"update_id":3,"message":{"text":null,"raw_encrypted":"base64..."}} ``` **File:** ```json {"update_id":4,"message":{"document":{"file_name":"report.pdf","file_size":1234}}} ``` ## Python Echo Bot ```python import requests, time TOKEN = "YOUR_TOKEN" # from @botfather /newbot API = f"http://localhost:7700/v1/bot/{TOKEN}" offset = 0 while True: r = requests.post(f"{API}/getUpdates", json={"offset": offset, "timeout": 50}).json() for u in r.get("result", []): offset = u["update_id"] + 1 msg = u.get("message", {}) text = msg.get("text") 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(0.1) ``` ## Python Menu Bot (Inline Keyboard) ```python import requests TOKEN = "YOUR_TOKEN" API = f"http://localhost:7700/v1/bot/{TOKEN}" offset = 0 def menu(chat_id): requests.post(f"{API}/sendMessage", json={ "chat_id": chat_id, "text": "Pick one:", "reply_markup": {"inline_keyboard": [ [{"text": "A", "callback_data": "a"}, {"text": "B", "callback_data": "b"}] ]} }) while True: r = requests.post(f"{API}/getUpdates", json={"offset": offset, "timeout": 50}).json() for u in r.get("result", []): offset = u["update_id"] + 1 msg = u.get("message", {}) text, cid = msg.get("text", ""), msg.get("chat", {}).get("id", "") if text == "/start": menu(cid) elif text: requests.post(f"{API}/sendMessage", json={"chat_id": cid, "text": f"You said: {text}"}) ``` ## Node.js Echo Bot ```javascript const TOKEN = process.env.BOT_TOKEN; const API = `http://localhost:7700/v1/bot/${TOKEN}`; let offset = 0; (async () => { while (true) { try { const r = await (await fetch(`${API}/getUpdates`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({offset, timeout: 50}) })).json(); for (const u of r.result || []) { offset = u.update_id + 1; const {text, chat} = u.message || {}; if (text && chat?.id) await fetch(`${API}/sendMessage`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({chat_id: chat.id, text: `Echo: ${text}`}) }); } } catch(e) { console.error(e); await new Promise(r => setTimeout(r, 3000)); } } })(); ``` ## Bot Bridge (TG Library Compatibility) For unmodified Telegram bots (python-telegram-bot, aiogram, Telegraf): ```bash python3 tools/bot-bridge.py --server http://localhost:7700 --token YOUR_TOKEN --port 8081 ``` Then point your TG bot at the bridge: ```python # python-telegram-bot from telegram import Bot bot = Bot(token="TOKEN", base_url="http://localhost:8081/botTOKEN") # Telegraf (Node.js) const bot = new Telegraf("TOKEN", { telegram: { apiRoot: "http://localhost:8081" } }) ``` The bridge translates numeric chat_id ↔ fingerprints automatically. ## Differences from Telegram | Feature | Telegram | featherChat | |---------|----------|-------------| | chat_id | integer | string fp, numeric, or 0x ETH (all accepted) | | User→bot messages | plaintext | plaintext (auto-detected by client) | | Bot creation | @BotFather chat | @botfather chat (same flow) | | getUpdates timeout | up to 50s | up to 50s | | from.id | integer | integer (per-bot unique hash, no raw fp exposed) | | File upload | multipart | JSON reference (v1) | | Inline keyboards | full | stored + delivered, no popup | | Webhooks | HTTPS POST | HTTP POST (delivered live) | | parse_mode HTML | rendered | rendered in web client | | Media groups | yes | not yet | ## Voice Calls Bots cannot initiate or participate in voice calls. Voice is peer-to-peer only between human clients (web or TUI). Call signaling messages (`CallSignal` type) are delivered to bots via getUpdates as `text="/call_Offer"` etc., but bots should ignore them -- there is no audio path for bots. ## Key Rules 1. **Always use offset** in getUpdates — without it you reprocess messages 2. **chat_id** — use `msg.chat.id` (numeric, per-bot unique) for replies 3. **Bot names** must end with `bot`, `Bot`, or `_bot` 4. **Only @botfather** can create bots — direct API registration requires botfather_token 5. **Server needs --enable-bots** — without it all bot endpoints return 403 6. **Plaintext by default** — user clients auto-detect bot aliases and skip E2E 7. **E2E bots** — register with `e2e:true` + bundle for encrypted sessions (advanced)