Files
featherChat/warzone/tools/bot-bridge.py
Siavash Sameni 8603087afb v0.0.27: TG-compatible bots — plaintext send, numeric IDs, webhooks, BotFather
Bot compatibility:
- Clients send plaintext bot_message to bot aliases (no E2E encryption)
- Numeric chat_id: fp_to_numeric_id() deterministic hash, accept string/number
- Webhook delivery: POST updates to bot's webhook URL (async, fire-and-forget)
- getUpdates timeout raised to 50s (was 30, TG uses 50)
- parse_mode HTML rendered in web client
- E2E bot registration: optional seed + bundle for encrypted bot sessions

BotFather + instance control:
- --enable-bots CLI flag (default: disabled)
- BotFather auto-created on first start (@botfather alias)
- Bot ownership: owner fingerprint stored in bot_info
- All bot endpoints return 403 when disabled

Bot Bridge:
- tools/bot-bridge.py: TG-compatible proxy for unmodified TG bots
- Translates chat_id int↔string, proxies getUpdates/sendMessage
- README with python-telegram-bot and Telegraf examples

Test fixes:
- Updated tests for ETH address display in header/messages

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-29 09:45:45 +04:00

176 lines
6.3 KiB
Python
Executable File

#!/usr/bin/env python3
"""
featherChat E2E Bot Bridge
Runs a local Telegram-compatible API server that proxies to featherChat.
Your Telegram bot connects to this bridge instead of api.telegram.org.
Usage:
python bot-bridge.py --server http://featherchat:7700 --token YOUR_BOT_TOKEN --port 8081
Your bot code:
# Instead of: bot = Bot(token="...", base_url="https://api.telegram.org")
# Use: bot = Bot(token="...", base_url="http://localhost:8081")
Architecture:
[TG Bot] <--HTTP--> [Bridge :8081] <--HTTP--> [featherChat :7700]
The bridge translates between Telegram API format and featherChat Bot API,
handling the chat_id type differences and other incompatibilities.
"""
import argparse
import json
import sys
import time
from http.server import HTTPServer, BaseHTTPRequestHandler
from urllib.parse import urlparse
from urllib.request import Request, urlopen
from urllib.error import URLError
class BotBridgeHandler(BaseHTTPRequestHandler):
server_url = ""
bot_token = ""
def log_message(self, format, *args):
print(f"[bridge] {args[0]}" if args else "")
def do_GET(self):
self._proxy()
def do_POST(self):
self._proxy()
def _proxy(self):
# Extract the method from URL: /bot<token>/methodName
path = self.path
# Strip /bot<token>/ prefix if present (TG libraries send this)
if path.startswith(f'/bot{self.bot_token}/'):
method = path[len(f'/bot{self.bot_token}/'):]
elif path.startswith('/bot'):
# Library might send a different token format
parts = path.split('/', 3)
method = parts[3] if len(parts) > 3 else parts[-1]
else:
method = path.lstrip('/')
# Read request body
content_length = int(self.headers.get('Content-Length', 0))
body = self.rfile.read(content_length) if content_length > 0 else b''
# Transform request for featherChat
fc_url = f"{self.server_url}/v1/bot/{self.bot_token}/{method}"
# Transform body if needed
if body and method == 'sendMessage':
body = self._transform_send_message(body)
try:
req = Request(fc_url, data=body if body else None, method=self.command)
req.add_header('Content-Type', 'application/json')
with urlopen(req, timeout=60) as resp:
response_body = resp.read()
# Transform response
if method == 'getUpdates':
response_body = self._transform_updates(response_body)
self.send_response(resp.status)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(response_body)
except URLError as e:
self.send_response(502)
self.send_header('Content-Type', 'application/json')
self.end_headers()
self.wfile.write(json.dumps({
"ok": False,
"description": f"Bridge error: {e}"
}).encode())
def _transform_send_message(self, body):
"""Transform sendMessage: convert numeric chat_id to string if needed."""
try:
data = json.loads(body)
# chat_id: featherChat accepts both string and number now
# No transformation needed -- pass through
return json.dumps(data).encode()
except:
return body
def _transform_updates(self, body):
"""Transform getUpdates response: ensure chat_id is integer for TG libs."""
try:
data = json.loads(body)
if data.get('ok') and data.get('result'):
for update in data['result']:
msg = update.get('message', {})
# Convert string IDs to numeric for TG library compatibility
if 'from' in msg and isinstance(msg['from'].get('id'), str):
fp = msg['from']['id']
msg['from']['id_str'] = fp
msg['from']['id'] = _fp_to_numeric(fp)
if 'chat' in msg and isinstance(msg['chat'].get('id'), str):
fp = msg['chat']['id']
msg['chat']['id_str'] = fp
msg['chat']['id'] = _fp_to_numeric(fp)
return json.dumps(data).encode()
except:
return body
def _fp_to_numeric(fp: str) -> int:
"""Convert fingerprint hex string to positive i64 (same as server's fp_to_numeric_id)."""
clean = ''.join(c for c in fp if c in '0123456789abcdefABCDEF')[:16]
if len(clean) >= 16:
return int(clean, 16) & 0x7FFFFFFFFFFFFFFF
return 0
def main():
parser = argparse.ArgumentParser(description='featherChat E2E Bot Bridge')
parser.add_argument('--server', required=True, help='featherChat server URL (e.g., http://localhost:7700)')
parser.add_argument('--token', required=True, help='Bot token from /v1/bot/register')
parser.add_argument('--port', type=int, default=8081, help='Local port for TG-compatible API (default: 8081)')
args = parser.parse_args()
BotBridgeHandler.server_url = args.server.rstrip('/')
BotBridgeHandler.bot_token = args.token
# Verify bot token
try:
req = Request(f"{args.server}/v1/bot/{args.token}/getMe")
with urlopen(req, timeout=5) as resp:
data = json.loads(resp.read())
if not data.get('ok'):
print(f"ERROR: Invalid bot token")
sys.exit(1)
bot_name = data['result'].get('first_name', '?')
print(f"Bot: {bot_name}")
except Exception as e:
print(f"ERROR: Cannot reach server: {e}")
sys.exit(1)
server = HTTPServer(('127.0.0.1', args.port), BotBridgeHandler)
print(f"Bridge running on http://127.0.0.1:{args.port}")
print(f"Proxying to {args.server}")
print(f"")
print(f"Configure your bot:")
print(f" base_url = 'http://127.0.0.1:{args.port}/bot{args.token}'")
print(f"")
print(f"Example (python-telegram-bot):")
print(f" from telegram import Bot")
print(f" bot = Bot(token='{args.token}', base_url='http://127.0.0.1:{args.port}/bot{args.token}')")
try:
server.serve_forever()
except KeyboardInterrupt:
print("\nBridge stopped.")
if __name__ == '__main__':
main()