# featherChat Bot Development Reference ## Setup Server: `http://HOST:7700` All bot endpoints: `/v1/bot//METHOD` Register bot: ``` POST /v1/bot/register {"name":"MyBot","fingerprint":"any_32_hex_chars"} → {"ok":true,"result":{"token":"TOKEN","alias":"@mybot_bot"}} ``` Bot names must end with Bot/bot/_bot. Token format: `:`. ## Endpoints ### getMe ``` GET /v1/bot/TOKEN/getMe → {"ok":true,"result":{"id":"fp","is_bot":true,"first_name":"MyBot","username":"MyBot"}} ``` ### getUpdates (long-poll) ``` POST /v1/bot/TOKEN/getUpdates {"offset":LAST_UPDATE_ID+1,"timeout":30,"limit":100} → {"ok":true,"result":[{"update_id":N,"message":{...}}]} ``` offset: skip updates with id < offset (acknowledge processed) timeout: long-poll seconds (max 30) limit: max updates to return (default 100) ### sendMessage ``` POST /v1/bot/TOKEN/sendMessage { "chat_id": "FINGERPRINT", "text": "Hello!", "parse_mode": "HTML", // optional "reply_to_message_id": "MSG_ID", // optional "reply_markup": { // optional, inline keyboard "inline_keyboard": [ [{"text":"Yes","callback_data":"yes"},{"text":"No","callback_data":"no"}] ] } } → {"ok":true,"result":{"message_id":"UUID","delivered":true}} ``` ### answerCallbackQuery ``` POST /v1/bot/TOKEN/answerCallbackQuery {"callback_query_id":"ID","text":"Done!","show_alert":false} → {"ok":true,"result":true} ``` ### editMessageText ``` POST /v1/bot/TOKEN/editMessageText {"chat_id":"FP","message_id":"MSG_ID","text":"Updated text","reply_markup":{...}} ``` ### sendDocument ``` POST /v1/bot/TOKEN/sendDocument {"chat_id":"FP","document":"filename_or_url","caption":"optional"} ``` ### setWebhook / deleteWebhook / getWebhookInfo ``` POST /v1/bot/TOKEN/setWebhook {"url":"https://mybot.example.com/webhook"} POST /v1/bot/TOKEN/deleteWebhook GET /v1/bot/TOKEN/getWebhookInfo ``` ## Update Types Messages from users arrive in getUpdates as: **Plaintext (from other bots):** ```json {"update_id":1,"message":{"message_id":"id","from":{"id":"fp","is_bot":true},"chat":{"id":"fp","type":"private"},"text":"Hello"}} ``` **Encrypted (from users with E2E sessions):** ```json {"update_id":2,"message":{"message_id":"id","from":{"id":"fp","is_bot":false},"chat":{"id":"fp"},"text":null,"raw_encrypted":"base64..."}} ``` Note: v1 bots cannot decrypt E2E messages. They see text=null + raw_encrypted blob. **Call signal:** ```json {"update_id":3,"message":{"text":"/call_Offer","call_signal":{"type":"Offer","payload":"..."}}} ``` **File:** ```json {"update_id":4,"message":{"document":{"file_name":"report.pdf","file_size":1234}}} ``` ## Python Examples ### Echo Bot ```python import requests, time TOKEN = "YOUR_TOKEN" API = f"http://localhost:7700/v1/bot/{TOKEN}" offset = 0 while True: resp = requests.post(f"{API}/getUpdates", json={"offset": offset, "timeout": 30}).json() for update in resp.get("result", []): offset = update["update_id"] + 1 msg = update.get("message", {}) chat_id = msg.get("chat", {}).get("id", "") text = msg.get("text") if text and chat_id: requests.post(f"{API}/sendMessage", json={"chat_id": chat_id, "text": f"Echo: {text}"}) ``` ### Inline Keyboard Bot ```python import requests TOKEN = "YOUR_TOKEN" API = f"http://localhost:7700/v1/bot/{TOKEN}" offset = 0 def send_menu(chat_id): requests.post(f"{API}/sendMessage", json={ "chat_id": chat_id, "text": "Choose an option:", "reply_markup": { "inline_keyboard": [ [{"text": "Option A", "callback_data": "a"}, {"text": "Option B", "callback_data": "b"}], [{"text": "Help", "callback_data": "help"}] ] } }) while True: resp = requests.post(f"{API}/getUpdates", json={"offset": offset, "timeout": 30}).json() for update in resp.get("result", []): offset = update["update_id"] + 1 msg = update.get("message", {}) text = msg.get("text", "") chat_id = msg.get("chat", {}).get("id", "") if text == "/start": send_menu(chat_id) elif text: requests.post(f"{API}/sendMessage", json={"chat_id": chat_id, "text": f"You said: {text}"}) ``` ### Node.js Echo Bot ```javascript const API = `http://localhost:7700/v1/bot/${process.env.BOT_TOKEN}`; let offset = 0; async function poll() { while (true) { try { const res = await fetch(`${API}/getUpdates`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({offset, timeout: 30}) }); const data = await res.json(); for (const update of data.result || []) { offset = update.update_id + 1; const msg = update.message; if (msg?.text && msg?.chat?.id) { await fetch(`${API}/sendMessage`, { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({chat_id: msg.chat.id, text: `Echo: ${msg.text}`}) }); } } } catch (e) { console.error(e); await new Promise(r => setTimeout(r, 3000)); } } } poll(); ``` ## Differences from Telegram | Feature | Telegram | featherChat | |---------|----------|------------| | chat_id | numeric | hex fingerprint string | | getUpdates timeout | up to 50s | up to 30s | | User messages | plaintext | E2E encrypted (text=null in v1) | | Bot messages | plaintext | plaintext (no E2E) | | File upload | multipart form | JSON reference (v1) | | Inline keyboards | full support | stored + delivered, no popup | | Callback queries | full popup | acknowledged, no popup | | Webhooks | full HTTPS | URL stored, delivery planned | | Media groups | supported | not yet | | parse_mode | renders HTML/MD | stored, not rendered (v1) | ## Key Patterns **Always use offset** — without it, the same messages are returned every poll. **chat_id is the sender's fingerprint** — use `msg.chat.id` or `msg.from.id`. **Bot alias** — users message bots via `@mybot_bot` which resolves to the bot's fingerprint. **Error handling** — all responses have `{"ok": bool}`. Check `ok` before accessing `result`. **Rate limits** — 200 concurrent server requests, no per-bot limit (be reasonable).