v0.0.34: fix bot sendMessage — store per-bot numeric ID reverse mapping

Per-bot numeric IDs (privacy feature) broke sendMessage because the
reverse lookup couldn't find the fingerprint from the per-bot hash.

Fix: store numid:<numeric_id> → fingerprint in tokens tree when
generating updates. resolve_chat_id checks this mapping first.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Siavash Sameni
2026-03-29 15:35:52 +04:00
parent 3489a7cf74
commit 7628ff7a75
5 changed files with 33 additions and 20 deletions

10
warzone/Cargo.lock generated
View File

@@ -2956,7 +2956,7 @@ dependencies = [
[[package]]
name = "warzone-client"
version = "0.0.33"
version = "0.0.34"
dependencies = [
"anyhow",
"argon2",
@@ -2989,7 +2989,7 @@ dependencies = [
[[package]]
name = "warzone-mule"
version = "0.0.33"
version = "0.0.34"
dependencies = [
"anyhow",
"clap",
@@ -2998,7 +2998,7 @@ dependencies = [
[[package]]
name = "warzone-protocol"
version = "0.0.33"
version = "0.0.34"
dependencies = [
"base64",
"bincode",
@@ -3023,7 +3023,7 @@ dependencies = [
[[package]]
name = "warzone-server"
version = "0.0.33"
version = "0.0.34"
dependencies = [
"anyhow",
"axum",
@@ -3053,7 +3053,7 @@ dependencies = [
[[package]]
name = "warzone-wasm"
version = "0.0.33"
version = "0.0.34"
dependencies = [
"base64",
"bincode",

View File

@@ -9,7 +9,7 @@ members = [
]
[workspace.package]
version = "0.0.33"
version = "0.0.34"
edition = "2021"
license = "MIT"
rust-version = "1.75"

View File

@@ -1,6 +1,6 @@
[package]
name = "warzone-protocol"
version = "0.0.33"
version = "0.0.34"
edition = "2021"
license = "MIT"
description = "Core crypto & wire protocol for featherChat (Warzone messenger)"

View File

@@ -101,6 +101,12 @@ fn resolve_chat_id(state: &AppState, chat_id: &serde_json::Value) -> Option<Stri
}
serde_json::Value::Number(n) => {
let num = n.as_i64().unwrap_or(0);
// Look up per-bot numeric ID reverse mapping
let numid_key = format!("numid:{}", num);
if let Some(fp_bytes) = state.db.tokens.get(numid_key.as_bytes()).ok().flatten() {
return Some(String::from_utf8_lossy(&fp_bytes).to_string());
}
// Fallback: scan all keys with global hash (legacy)
for item in state.db.keys.iter().flatten() {
let key_str = String::from_utf8_lossy(&item.0).to_string();
if !key_str.contains(':') && key_str.len() == 32 {
@@ -188,9 +194,9 @@ pub async fn try_bot_webhook(state: &AppState, to_fp: &str, message: &[u8]) -> b
let update = if let Ok(wire) =
bincode::deserialize::<warzone_protocol::message::WireMessage>(message)
{
wire_message_to_update(&wire, message, &token)
wire_message_to_update(state, &wire, message, &token)
} else if let Ok(bot_msg) = serde_json::from_slice::<serde_json::Value>(message) {
bot_json_to_update(&bot_msg, &token)
bot_json_to_update(state, &bot_msg, &token)
} else {
None
};
@@ -515,9 +521,9 @@ fn migrate_raw_queue(state: &AppState, bot_fp: &str, bot_token: &str) {
let update = if let Ok(wire) =
bincode::deserialize::<warzone_protocol::message::WireMessage>(&value)
{
wire_message_to_update(&wire, &value, bot_token)
wire_message_to_update(state, &wire, &value, bot_token)
} else if let Ok(bot_msg) = serde_json::from_slice::<serde_json::Value>(&value) {
bot_json_to_update(&bot_msg, bot_token)
bot_json_to_update(state, &bot_msg, bot_token)
} else {
None
};
@@ -533,8 +539,15 @@ fn migrate_raw_queue(state: &AppState, bot_fp: &str, bot_token: &str) {
}
}
/// Store a per-bot numeric ID → fingerprint reverse mapping.
fn store_numid_mapping(state: &AppState, numeric_id: i64, fingerprint: &str) {
let key = format!("numid:{}", numeric_id);
let _ = state.db.tokens.insert(key.as_bytes(), fingerprint.as_bytes());
}
/// Convert a `WireMessage` into a Telegram-style update JSON (without update_id).
fn wire_message_to_update(
state: &AppState,
wire: &warzone_protocol::message::WireMessage,
raw_bytes: &[u8],
bot_token: &str,
@@ -546,7 +559,7 @@ fn wire_message_to_update(
..
} => {
let raw_b64 = base64::engine::general_purpose::STANDARD.encode(raw_bytes);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token); store_numid_mapping(state, numeric, sender_fingerprint);
Some(serde_json::json!({
"message": {
"message_id": id,
@@ -571,7 +584,7 @@ fn wire_message_to_update(
..
} => {
let raw_b64 = base64::engine::general_purpose::STANDARD.encode(raw_bytes);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token); store_numid_mapping(state, numeric, sender_fingerprint);
Some(serde_json::json!({
"message": {
"message_id": id,
@@ -597,7 +610,7 @@ fn wire_message_to_update(
payload,
..
} => {
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token); store_numid_mapping(state, numeric, sender_fingerprint);
Some(serde_json::json!({
"message": {
"message_id": id,
@@ -626,7 +639,7 @@ fn wire_message_to_update(
file_size,
..
} => {
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(sender_fingerprint, bot_token); store_numid_mapping(state, numeric, sender_fingerprint);
Some(serde_json::json!({
"message": {
"message_id": id,
@@ -654,12 +667,12 @@ fn wire_message_to_update(
}
/// Convert a plaintext bot JSON message into a Telegram-style update (without update_id).
fn bot_json_to_update(bot_msg: &serde_json::Value, bot_token: &str) -> Option<serde_json::Value> {
fn bot_json_to_update(state: &AppState, bot_msg: &serde_json::Value, bot_token: &str) -> Option<serde_json::Value> {
let msg_type = bot_msg.get("type").and_then(|v| v.as_str())?;
match msg_type {
"bot_message" => {
let from_fp = bot_msg.get("from").and_then(|v| v.as_str()).unwrap_or("");
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(from_fp, bot_token);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(from_fp, bot_token); store_numid_mapping(state, numeric, from_fp);
Some(serde_json::json!({
"message": {
"message_id": bot_msg.get("id").and_then(|v| v.as_str()).unwrap_or(""),
@@ -678,7 +691,7 @@ fn bot_json_to_update(bot_msg: &serde_json::Value, bot_token: &str) -> Option<se
}
"callback_query" => {
let from_fp = bot_msg.get("from").and_then(|v| v.as_str()).unwrap_or("");
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(from_fp, bot_token);
let numeric = crate::routes::resolve::fp_to_numeric_id_for_bot(from_fp, bot_token); store_numid_mapping(state, numeric, from_fp);
Some(serde_json::json!({
"callback_query": {
"id": bot_msg.get("id").and_then(|v| v.as_str()).unwrap_or(""),

View File

@@ -50,7 +50,7 @@ async fn pwa_manifest() -> impl IntoResponse {
async fn service_worker() -> impl IntoResponse {
([(header::CONTENT_TYPE, "application/javascript")], r##"
const CACHE = 'wz-v15';
const CACHE = 'wz-v16';
const SHELL = ['/', '/wasm/warzone_wasm.js', '/wasm/warzone_wasm_bg.wasm', '/icon.svg', '/manifest.json'];
self.addEventListener('install', e => {
@@ -251,7 +251,7 @@ let pollTimer = null;
let ws = null; // WebSocket connection
let wasmReady = false;
const VERSION = '0.0.33';
const VERSION = '0.0.34';
let DEBUG = true; // toggle with /debug command
// ── Receipt tracking ──