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>
This commit is contained in:
Siavash Sameni
2026-03-29 09:45:45 +04:00
parent 067f1ea20b
commit 8603087afb
14 changed files with 660 additions and 120 deletions

View File

@@ -7,6 +7,20 @@ use axum::{
use crate::errors::AppResult;
use crate::state::AppState;
/// Convert a fingerprint hex string to a stable i64 ID (for Telegram compatibility).
/// Uses first 8 bytes of the fingerprint as a positive i64.
pub fn fp_to_numeric_id(fp: &str) -> i64 {
let clean: String = fp.chars().filter(|c| c.is_ascii_hexdigit()).take(16).collect();
let bytes = hex::decode(&clean).unwrap_or_default();
if bytes.len() >= 8 {
let mut arr = [0u8; 8];
arr.copy_from_slice(&bytes[..8]);
i64::from_be_bytes(arr) & 0x7FFFFFFFFFFFFFFF // ensure positive
} else {
0
}
}
pub fn routes() -> Router<AppState> {
Router::new().route("/resolve/:address", get(resolve_address))
}
@@ -27,6 +41,7 @@ async fn resolve_address(
return Ok(Json(serde_json::json!({
"address": address,
"fingerprint": fp,
"numeric_id": fp_to_numeric_id(&fp),
"type": "eth",
})));
}
@@ -40,6 +55,7 @@ async fn resolve_address(
return Ok(Json(serde_json::json!({
"address": address,
"fingerprint": fp,
"numeric_id": fp_to_numeric_id(fp),
"type": "eth",
"federated": true,
})));
@@ -61,6 +77,7 @@ async fn resolve_address(
return Ok(Json(serde_json::json!({
"address": address,
"fingerprint": fp,
"numeric_id": fp_to_numeric_id(&fp),
"type": "alias",
})));
}
@@ -70,6 +87,7 @@ async fn resolve_address(
return Ok(Json(serde_json::json!({
"address": address,
"fingerprint": fp,
"numeric_id": fp_to_numeric_id(&fp),
"type": "alias",
"federated": true,
})));
@@ -93,6 +111,7 @@ async fn resolve_address(
return Ok(Json(serde_json::json!({
"address": address,
"fingerprint": fp,
"numeric_id": fp_to_numeric_id(&fp),
"eth_address": eth,
"type": "fingerprint",
})));