fix: move allowed_numbers filtering from JS bridge to Python handler

The JS bridge used LIDs (internal WhatsApp identifiers) for sender
matching which never matched actual phone numbers. Moved filtering
to Python handler.py where config is read fresh each poll cycle.

- Add senderNumber (resolved phone) to bridge message payload
- Filter in poll_messages() with normalized number comparison
- Remove --allowed-numbers CLI arg and JS-side filtering
- Fix ensure_bridge_http_up not recording _bridge_config
- Fix falsy empty-dict check in bridge restart detection
This commit is contained in:
linuztx 2026-03-31 08:38:51 +08:00
parent 64ee177897
commit 61fa1bf487
6 changed files with 37 additions and 31 deletions

View file

@ -15,7 +15,6 @@ class QrCode(ApiHandler):
port = int(config.get("bridge_port", 3100))
session_dir = files.get_abs_path("usr/whatsapp/sessions")
cache_dir = files.get_abs_path("usr/whatsapp/media")
allowed_numbers = config.get("allowed_numbers") or []
mode = config.get("mode", "dedicated")
from plugins._whatsapp_integration.helpers.bridge_manager import (
@ -31,7 +30,7 @@ class QrCode(ApiHandler):
if not is_process_alive():
try:
ok = await ensure_bridge_http_up(
port, session_dir, cache_dir, allowed_numbers, mode=mode,
port, session_dir, cache_dir, mode=mode,
)
if not ok:
return {

View file

@ -15,7 +15,6 @@ class Start(ApiHandler):
port = int(config.get("bridge_port", 3100))
session_dir = files.get_abs_path("usr/whatsapp/sessions")
cache_dir = files.get_abs_path("usr/whatsapp/media")
allowed_numbers = config.get("allowed_numbers") or []
mode = config.get("mode", "dedicated")
from plugins._whatsapp_integration.helpers.bridge_manager import (
@ -28,7 +27,7 @@ class Start(ApiHandler):
try:
ok = await ensure_bridge_http_up(
port, session_dir, cache_dir, allowed_numbers, mode=mode,
port, session_dir, cache_dir, mode=mode,
)
if ok:
return {"success": True, "message": "Bridge started"}

View file

@ -57,13 +57,12 @@ async def _poll_loop() -> None:
port = int(config.get("bridge_port", 3100))
session_dir = files.get_abs_path("usr/whatsapp/sessions")
cache_dir = files.get_abs_path("usr/whatsapp/media")
allowed_numbers = config.get("allowed_numbers") or []
mode = config.get("mode", "dedicated")
# Detect config changes that require bridge restart
desired = {"port": port, "mode": mode, "allowed_numbers": sorted(allowed_numbers)}
desired = {"port": port, "mode": mode}
running = bridge_manager.get_running_config()
if bridge_started and bridge_manager.is_process_alive() and running and running != desired:
if bridge_started and bridge_manager.is_process_alive() and running != desired:
PrintStyle.info(f"WhatsApp: config changed, restarting bridge")
await bridge_manager.stop_bridge()
bridge_started = False
@ -72,7 +71,7 @@ async def _poll_loop() -> None:
if not bridge_started or not bridge_manager.is_process_alive():
try:
bridge_started = await bridge_manager.start_bridge(
port, session_dir, cache_dir, allowed_numbers, mode=mode,
port, session_dir, cache_dir, mode=mode,
)
except Exception as e:
PrintStyle.error(f"WhatsApp bridge start error: {format_error(e)}")

View file

@ -73,7 +73,6 @@ async def start_bridge(
port: int,
session_dir: str,
cache_dir: str,
allowed_numbers: list[str] | None = None,
mode: str = "dedicated",
) -> bool:
global _bridge_process
@ -91,8 +90,6 @@ async def start_bridge(
"--cache-dir", cache_dir,
"--mode", mode,
]
if allowed_numbers:
cmd += ["--allowed-numbers", ",".join(allowed_numbers)]
_kill_port_process(port)
PrintStyle.info("WhatsApp: starting bridge")
@ -104,7 +101,7 @@ async def start_bridge(
), port)
_start_log_reader(_bridge_process)
_bridge_config.clear()
_bridge_config.update({"port": port, "mode": mode, "allowed_numbers": sorted(allowed_numbers or [])})
_bridge_config.update({"port": port, "mode": mode})
# Wait for bridge to become healthy
for _ in range(20):
@ -151,7 +148,6 @@ async def ensure_bridge_http_up(
port: int,
session_dir: str,
cache_dir: str,
allowed_numbers: list[str] | None = None,
mode: str = "dedicated",
) -> bool:
"""Start bridge if needed and wait for HTTP server only (not WA connection)."""
@ -170,8 +166,6 @@ async def ensure_bridge_http_up(
"--cache-dir", cache_dir,
"--mode", mode,
]
if allowed_numbers:
cmd += ["--allowed-numbers", ",".join(allowed_numbers)]
_kill_port_process(port)
PrintStyle.info("WhatsApp: starting bridge for pairing")
@ -182,6 +176,8 @@ async def ensure_bridge_http_up(
cwd=BRIDGE_DIR,
), port)
_start_log_reader(_bridge_process)
_bridge_config.clear()
_bridge_config.update({"port": port, "mode": mode})
for _ in range(20):
await asyncio.sleep(0.5)

View file

@ -25,6 +25,17 @@ from plugins._whatsapp_integration.helpers import bridge_manager
PLUGIN_NAME = "_whatsapp_integration"
MEDIA_FOLDER = "usr/whatsapp/media"
# Regex: strip everything from first '@' or ':' onward, then leading '+' and '0's
_NUM_STRIP_RE = re.compile(r"[@:].*")
_NUM_PREFIX_RE = re.compile(r"^[+0]+")
def _normalize_number(raw: str) -> str:
"""Strip JID suffix, device suffix, leading + and 0s to get a plain number."""
n = _NUM_STRIP_RE.sub("", raw)
n = _NUM_PREFIX_RE.sub("", n)
return n
# Context data keys (no underscore prefix — must persist across restarts)
CTX_WA_CHAT_ID = "wa_chat_id"
CTX_WA_SENDER_NAME = "wa_sender_name"
@ -80,8 +91,21 @@ async def poll_messages(config: dict) -> None:
if not messages:
return
# Allowed-numbers filtering (authoritative check — bridge.js is secondary)
allowed_numbers = config.get("allowed_numbers") or []
allowed_set = {_normalize_number(n) for n in allowed_numbers} if allowed_numbers else set()
for msg in messages:
try:
# Filter by allowed numbers if configured
if allowed_set:
sender_num = _normalize_number(msg.get("senderNumber", "") or msg.get("senderId", ""))
if sender_num not in allowed_set:
PrintStyle.debug(
f"WhatsApp: ignored message from {sender_num} "
f"(senderId: {msg.get('senderId', '')}, allowed: {allowed_set})"
)
continue
await _dispatch_message(config, msg)
except Exception as e:
PrintStyle.error(f"WhatsApp dispatch error: {format_error(e)}")
@ -126,7 +150,7 @@ async def _start_new_chat(config: dict, msg: dict) -> None:
from helpers import projects
sender_name = msg.get("senderName", "Unknown")
sender_number = msg.get("senderId", "").replace("@s.whatsapp.net", "").replace("@lid", "")
sender_number = msg.get("senderNumber", "") or _normalize_number(msg.get("senderId", ""))
chat_id = msg.get("chatId", "")
is_group = msg.get("isGroup", False)
@ -260,7 +284,7 @@ def _md_to_whatsapp(text: str) -> str:
def _build_user_message(agent: Agent, msg: dict) -> str:
sender_name = msg.get("senderName", "Unknown")
sender_number = msg.get("senderId", "").replace("@s.whatsapp.net", "").replace("@lid", "")
sender_number = msg.get("senderNumber", "") or _normalize_number(msg.get("senderId", ""))
is_group = msg.get("isGroup", False)
prompt = "fw.wa.user_message_group.md" if is_group else "fw.wa.user_message.md"
text = agent.read_prompt(

View file

@ -46,7 +46,7 @@ const SESSION_DIR = getArg('session', path.join(process.env.HOME || '~', '.agent
const CACHE_DIR = getArg('cache-dir', path.join(process.env.HOME || '~', '.agent-zero', 'whatsapp', 'media'));
const PAIR_ONLY = args.includes('--pair-only');
const MODE = getArg('mode', 'dedicated'); // "dedicated" or "self-chat"
const ALLOWED_NUMBERS = (getArg('allowed-numbers', '') || '').split(',').map(s => s.trim()).filter(Boolean);
mkdirSync(SESSION_DIR, { recursive: true });
mkdirSync(CACHE_DIR, { recursive: true });
@ -197,14 +197,7 @@ async function startSocket() {
// Skip status broadcasts
if (chatId === 'status@broadcast') continue;
// Check allowlist (resolve LID -> phone if needed)
if (!msg.key.fromMe && ALLOWED_NUMBERS.length > 0) {
const resolvedNumber = lidToPhone[senderNumber] || senderNumber;
if (!ALLOWED_NUMBERS.includes(resolvedNumber)) {
console.log(`[bridge] Ignored message from ${resolvedNumber} (not in allowed numbers)`);
continue;
}
}
// Allowed-numbers filtering is handled in Python (handler.py)
// Unwrap documentWithCaptionMessage (Baileys wraps captioned docs)
if (msg.message.documentWithCaptionMessage?.message?.documentMessage) {
@ -379,6 +372,7 @@ async function startSocket() {
messageId: msg.key.id,
chatId,
senderId,
senderNumber: resolvedSender,
senderName: msg.pushName || resolvedSender,
chatName,
isGroup,
@ -616,11 +610,6 @@ if (PAIR_ONLY) {
app.listen(PORT, '127.0.0.1', () => {
console.log(`[bridge] WhatsApp bridge listening on port ${PORT} (mode: ${MODE})`);
console.log(`[bridge] Session: ${SESSION_DIR}`);
if (ALLOWED_NUMBERS.length > 0) {
console.log(`[bridge] Allowed numbers: ${ALLOWED_NUMBERS.join(', ')}`);
} else {
console.log('[bridge] No allowed numbers set — all messages will be processed');
}
console.log();
startSocket();
});