Files
ROMFASTSQL/proxmox/lxc171-claude-agent/maria-whatsapp-bridge/whatsapp/index.js
Claude Agent 3c8a29c2e9 fix(maria): recunoaste JID-urile LID, altfel toate mesajele erau aruncate tacut
Dupa asocierea de azi, puntea nu raspundea la niciun mesaj desi `connected: true`
si consumer-ul rula. WhatsApp livreaza self-chat-ul pe conturile noi cu un JID de
tip LID (`51947713372214@lid`), nu cu `<numar>@s.whatsapp.net`, iar filtrul
`TEST_MODE_SELF_CHAT_ONLY` compara doar cu al doilea — deci arunca tot.

- `ownLid` calculat din `sock.user.lid` (cu rezerva pe `creds.me.lid`), fara
  sufixul de dispozitiv: credentialele au "51947713372214:13@lid", dar
  remoteJid-ul mesajelor vine fara ":13".
- `isSelfChat` accepta acum ambele formate.
- Logul de conectare si `/status` arata si LID-ul.

Cauza pentru care a fost greu de gasit: mesajele respinse dispareau fara nicio
urma. Acum fiecare motiv de respingere se logheaza (nu e self-chat, fromMe in
afara self-chat, fara continut text), cu JID-urile comparate. Exact linia asta a
aratat problema in 30 de secunde:
  [whatsapp] ignorat (nu e self-chat): remoteJid=51947713372214@lid ownJid=40723197939@s.whatsapp.net

Verificat live: "token efactura expirat" -> preluat, cautat in RAG, raspuns trimis.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Q4uzvgm7AyJch5WH8QHRhY
2026-08-31 21:02:24 +00:00

270 lines
9.5 KiB
JavaScript

// Punte WhatsApp (Baileys) pentru Maria — implicit doar self-chat (vezi
// TEST_MODE_SELF_CHAT_ONLY). Sesiunea WhatsApp (auth/) NU intra in git —
// contine chei de sesiune, e in ~/.maria-bridge/whatsapp-auth/ (vezi config.py).
const express = require('express');
const pino = require('pino');
const QRCode = require('qrcode');
const path = require('path');
let makeWASocket, useMultiFileAuthState, DisconnectReason, fetchLatestBaileysVersion;
const PORT = parseInt(process.env.BRIDGE_PORT || '8099', 10);
const HOST = process.env.BRIDGE_HOST || '127.0.0.1';
const AUTH_DIR = process.env.MARIA_BRIDGE_DIR
? path.join(process.env.MARIA_BRIDGE_DIR, 'whatsapp-auth')
: path.join(__dirname, 'auth');
const MAX_RECONNECT_ATTEMPTS = 5;
const TEST_MODE_SELF_CHAT_ONLY = (process.env.TEST_MODE_SELF_CHAT_ONLY || 'true') !== 'false';
const logger = pino({ level: 'warn' });
let sock = null;
let connected = false;
let phoneNumber = null;
let ownJid = null;
let ownLid = null; // acelasi cont, adresat in formatul nou LID
let currentQR = null;
let currentPairingCode = null;
let reconnectAttempts = 0;
let messageQueue = [];
let shuttingDown = false;
async function startConnection() {
if (!makeWASocket) {
const baileys = await import('@whiskeysockets/baileys');
makeWASocket = baileys.default;
useMultiFileAuthState = baileys.useMultiFileAuthState;
DisconnectReason = baileys.DisconnectReason;
fetchLatestBaileysVersion = baileys.fetchLatestBaileysVersion;
}
const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR);
const { version } = await fetchLatestBaileysVersion();
sock = makeWASocket({
version,
auth: state,
logger,
printQRInTerminal: false,
defaultQueryTimeoutMs: 60000,
});
sock.ev.on('creds.update', saveCreds);
sock.ev.on('connection.update', async (update) => {
const { connection, lastDisconnect, qr } = update;
if (qr) {
try {
currentQR = await QRCode.toDataURL(qr);
console.log('[whatsapp] QR code generated — scan with WhatsApp to link');
} catch (err) {
console.error('[whatsapp] Failed to generate QR code:', err.message);
}
}
if (connection === 'open') {
connected = true;
currentQR = null;
reconnectAttempts = 0;
phoneNumber = sock.user?.id?.split(':')[0] || sock.user?.id?.split('@')[0] || null;
ownJid = phoneNumber ? `${phoneNumber}@s.whatsapp.net` : null;
// WhatsApp adreseaza conturile si prin LID (Linked ID), nu doar prin numar. Pe
// conturile noi, self-chat-ul soseste ca `<lid>@lid`, deci comparatia doar cu
// `<numar>@s.whatsapp.net` arunca toate mesajele. Sufixul de dispozitiv (":13")
// apare in credentiale dar nu si in remoteJid-ul mesajelor — il taiem.
const rawLid = sock.user?.lid || sock.authState?.creds?.me?.lid || null;
ownLid = rawLid ? `${rawLid.split(':')[0].split('@')[0]}@lid` : null;
console.log(`[whatsapp] Connected as ${phoneNumber} (lid: ${ownLid || 'n/a'}, self-chat-only mode: ${TEST_MODE_SELF_CHAT_ONLY})`);
}
if (connection === 'close') {
connected = false;
phoneNumber = null;
ownLid = null;
const statusCode = lastDisconnect?.error?.output?.statusCode;
const shouldReconnect = statusCode !== DisconnectReason.loggedOut;
console.log(`[whatsapp] Disconnected (status: ${statusCode})`);
if (shouldReconnect && !shuttingDown) {
if (reconnectAttempts < MAX_RECONNECT_ATTEMPTS) {
reconnectAttempts++;
const delay = Math.min(1000 * Math.pow(2, reconnectAttempts), 30000);
console.log(`[whatsapp] Reconnecting in ${delay}ms (attempt ${reconnectAttempts}/${MAX_RECONNECT_ATTEMPTS})`);
setTimeout(startConnection, delay);
} else {
console.error(`[whatsapp] Max reconnect attempts reached (${MAX_RECONNECT_ATTEMPTS})`);
}
} else if (statusCode === DisconnectReason.loggedOut) {
console.log('[whatsapp] Logged out — delete auth dir and restart to re-link');
}
}
});
sock.ev.on('messages.upsert', ({ messages, type }) => {
if (type !== 'notify') return;
for (const msg of messages) {
if (msg.key.remoteJid === 'status@broadcast') continue;
const isGroup = msg.key.remoteJid.endsWith('@g.us');
const isSelfChat = (ownJid && msg.key.remoteJid === ownJid)
|| (ownLid && msg.key.remoteJid === ownLid);
// Mesajele aruncate se logheaza. Fara asta, un JID care nu se potriveste cu
// ownJid (ex. formatul @lid) face bridge-ul sa taca fara nicio urma in log.
if (TEST_MODE_SELF_CHAT_ONLY && !isSelfChat) {
console.log(`[whatsapp] ignorat (nu e self-chat): remoteJid=${msg.key.remoteJid} ownJid=${ownJid} ownLid=${ownLid}`);
continue;
}
if (msg.key.fromMe && !isGroup && !isSelfChat) {
console.log(`[whatsapp] ignorat (fromMe, nu e self-chat): remoteJid=${msg.key.remoteJid}`);
continue;
}
const text = msg.message?.conversation || msg.message?.extendedTextMessage?.text;
if (!text) {
console.log(`[whatsapp] ignorat (fara text): remoteJid=${msg.key.remoteJid} tipuri=${Object.keys(msg.message || {}).join(',')}`);
continue;
}
messageQueue.push({
from: msg.key.remoteJid,
participant: msg.key.participant || null,
pushName: msg.pushName || null,
text,
timestamp: msg.messageTimestamp,
id: msg.key.id,
isGroup,
fromMe: msg.key.fromMe || false,
});
console.log(`[whatsapp] Message from ${msg.pushName || 'unknown'} in ${msg.key.remoteJid}: ${text.substring(0, 80)}`);
}
});
}
const app = express();
app.use(express.json({ limit: '50mb' }));
app.get('/status', (_req, res) => {
res.json({
connected,
phone: phoneNumber,
selfChatOnly: TEST_MODE_SELF_CHAT_ONLY,
lid: ownLid,
qr: connected ? null : currentQR,
});
});
app.post('/pair', async (req, res) => {
if (connected) {
return res.json({ error: 'already connected' });
}
const { phone } = req.body || {};
if (!phone) {
return res.status(400).json({ error: 'missing "phone" in body' });
}
if (!sock) {
return res.status(503).json({ error: 'socket not ready yet, try again in a few seconds' });
}
try {
const code = await sock.requestPairingCode(phone.replace(/\D/g, ''));
currentPairingCode = code;
console.log(`[whatsapp] Pairing code for ${phone}: ${code}`);
res.json({ ok: true, code });
} catch (err) {
console.error('[whatsapp] Pairing code error:', err.message);
res.status(500).json({ error: err.message });
}
});
app.get('/pair-code', (_req, res) => {
if (connected) return res.json({ error: 'already connected' });
if (!currentPairingCode) return res.json({ error: 'no pairing code yet — POST /pair first' });
res.json({ code: currentPairingCode });
});
app.get('/qr', (_req, res) => {
if (connected) {
return res.json({ error: 'already connected' });
}
if (!currentQR) {
return res.json({ error: 'no QR code available yet' });
}
const html = `<!DOCTYPE html>
<html><head><title>WhatsApp QR</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<style>body{display:flex;justify-content:center;align-items:center;min-height:100vh;margin:0;background:#111;flex-direction:column;font-family:sans-serif;color:#fff}
img{width:400px;height:400px;border-radius:12px}p{margin-top:16px;opacity:.6}</style></head>
<body><img src="${currentQR}" alt="QR Code"/><p>Scan with WhatsApp &rarr; Linked Devices</p></body></html>`;
res.type('html').send(html);
});
app.post('/send', async (req, res) => {
const { to, text } = req.body || {};
if (!to || !text) {
return res.status(400).json({ ok: false, error: 'missing "to" or "text" in body' });
}
if (!connected || !sock) {
return res.status(503).json({ ok: false, error: 'not connected to WhatsApp' });
}
try {
const result = await sock.sendMessage(to, { text });
res.json({ ok: true, id: result.key.id });
} catch (err) {
console.error('[whatsapp] Send failed:', err.message);
res.status(500).json({ ok: false, error: err.message });
}
});
app.post('/react', async (req, res) => {
const { to, id, emoji, fromMe, participant } = req.body || {};
if (!to || !id || emoji == null) {
return res.status(400).json({ ok: false, error: 'missing "to", "id", or "emoji" in body' });
}
if (!connected || !sock) {
return res.status(503).json({ ok: false, error: 'not connected to WhatsApp' });
}
try {
const key = { remoteJid: to, id, fromMe: fromMe || false };
if (participant) key.participant = participant;
await sock.sendMessage(to, { react: { text: emoji, key } });
res.json({ ok: true });
} catch (err) {
console.error('[whatsapp] React failed:', err.message);
res.status(500).json({ ok: false, error: err.message });
}
});
app.get('/messages', (_req, res) => {
const messages = messageQueue.splice(0);
res.json({ messages });
});
const server = app.listen(PORT, HOST, () => {
console.log(`[whatsapp] Bridge API listening on http://${HOST}:${PORT}`);
startConnection().catch((err) => {
console.error('[whatsapp] Failed to start connection:', err.message);
});
});
function shutdown(signal) {
console.log(`[whatsapp] Received ${signal}, shutting down...`);
shuttingDown = true;
if (sock) {
sock.end(undefined);
}
server.close(() => {
console.log('[whatsapp] HTTP server closed');
process.exit(0);
});
setTimeout(() => process.exit(1), 5000);
}
process.on('SIGTERM', () => shutdown('SIGTERM'));
process.on('SIGINT', () => shutdown('SIGINT'));