feat: friend list, bot API, ETH addressing, deep links, docs overhaul
Tier 1 — New features: - E2E encrypted friend list: server stores opaque blob (POST/GET /v1/friends), protocol-level encrypt/decrypt with HKDF-derived key, 4 tests - Telegram Bot API compatibility: /bot/register, /bot/:token/getUpdates, sendMessage, getMe — TG-style Update objects with proper message mapping - ETH address resolution: GET /v1/resolve/:address (0x.../alias/@.../fp), bidirectional ETH↔fp mapping stored on key registration - Seed recovery: /seed command in TUI + web client - URL deep links: /message/@alias, /message/0xABC, /group/#ops - Group members with online status in GET /groups/:name/members Tier 2 — UX polish: - TUI: /friend, /friend <addr>, /unfriend <addr> with presence checking - Web: friend commands, showGroupMembers() on group join - Web: ETH address in header, clickable addresses (click→peer or copy) - Bot: full WireMessage→TG Update mapping (encrypted base64, CallSignal, FileHeader, bot_message JSON) Documentation: - USAGE.md rewritten: complete user guide with all commands - SERVER.md rewritten: full admin guide with all 50+ endpoints - CLIENT.md rewritten: architecture, commands, keyboard, storage - LLM_HELP.md created: 1083-word token-optimized reference for helper LLM Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
102
warzone/crates/warzone-server/src/routes/resolve.rs
Normal file
102
warzone/crates/warzone-server/src/routes/resolve.rs
Normal file
@@ -0,0 +1,102 @@
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
routing::get,
|
||||
Json, Router,
|
||||
};
|
||||
|
||||
use crate::errors::AppResult;
|
||||
use crate::state::AppState;
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new().route("/resolve/:address", get(resolve_address))
|
||||
}
|
||||
|
||||
/// Resolve an address to a fingerprint.
|
||||
///
|
||||
/// Accepts: ETH address (`0x...`), alias (`@name`), or raw fingerprint.
|
||||
async fn resolve_address(
|
||||
State(state): State<AppState>,
|
||||
Path(address): Path<String>,
|
||||
) -> AppResult<Json<serde_json::Value>> {
|
||||
let addr = address.trim().to_lowercase();
|
||||
|
||||
// ETH address: 0x...
|
||||
if addr.starts_with("0x") {
|
||||
if let Some(fp_bytes) = state.db.eth_addresses.get(addr.as_bytes())? {
|
||||
let fp = String::from_utf8_lossy(&fp_bytes).to_string();
|
||||
return Ok(Json(serde_json::json!({
|
||||
"address": address,
|
||||
"fingerprint": fp,
|
||||
"type": "eth",
|
||||
})));
|
||||
}
|
||||
// Try federation
|
||||
if let Some(ref federation) = state.federation {
|
||||
let url = format!("{}/v1/resolve/{}", federation.config.peer.url, addr);
|
||||
if let Ok(resp) = federation.client.get(&url).send().await {
|
||||
if resp.status().is_success() {
|
||||
if let Ok(data) = resp.json::<serde_json::Value>().await {
|
||||
if let Some(fp) = data.get("fingerprint").and_then(|v| v.as_str()) {
|
||||
return Ok(Json(serde_json::json!({
|
||||
"address": address,
|
||||
"fingerprint": fp,
|
||||
"type": "eth",
|
||||
"federated": true,
|
||||
})));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return Ok(Json(serde_json::json!({ "error": "address not found" })));
|
||||
}
|
||||
|
||||
// Alias: @name
|
||||
if addr.starts_with('@') {
|
||||
let alias = &addr[1..];
|
||||
// Try local alias resolution
|
||||
let alias_key = format!("a:{}", alias);
|
||||
if let Some(fp_bytes) = state.db.aliases.get(alias_key.as_bytes())? {
|
||||
let fp = String::from_utf8_lossy(&fp_bytes).to_string();
|
||||
return Ok(Json(serde_json::json!({
|
||||
"address": address,
|
||||
"fingerprint": fp,
|
||||
"type": "alias",
|
||||
})));
|
||||
}
|
||||
// Try federation
|
||||
if let Some(ref federation) = state.federation {
|
||||
if let Some(fp) = federation.resolve_remote_alias(alias).await {
|
||||
return Ok(Json(serde_json::json!({
|
||||
"address": address,
|
||||
"fingerprint": fp,
|
||||
"type": "alias",
|
||||
"federated": true,
|
||||
})));
|
||||
}
|
||||
}
|
||||
return Ok(Json(serde_json::json!({ "error": "alias not found" })));
|
||||
}
|
||||
|
||||
// Raw fingerprint: just echo back with optional reverse ETH lookup
|
||||
let fp = addr
|
||||
.chars()
|
||||
.filter(|c| c.is_ascii_hexdigit())
|
||||
.collect::<String>();
|
||||
if fp.len() == 32 {
|
||||
let rev_key = format!("rev:{}", fp);
|
||||
let eth = state
|
||||
.db
|
||||
.eth_addresses
|
||||
.get(rev_key.as_bytes())?
|
||||
.map(|v| String::from_utf8_lossy(&v).to_string());
|
||||
return Ok(Json(serde_json::json!({
|
||||
"address": address,
|
||||
"fingerprint": fp,
|
||||
"eth_address": eth,
|
||||
"type": "fingerprint",
|
||||
})));
|
||||
}
|
||||
|
||||
Ok(Json(serde_json::json!({ "error": "unrecognized address format" })))
|
||||
}
|
||||
Reference in New Issue
Block a user