/** * HuggingClaw WhatsApp Guardian * * Automates the WhatsApp pairing process on HuggingFace Spaces. * Handles the "515 Restart" by monitoring the channel status and * re-applying the configuration after a successful scan. */ "use strict"; const { WebSocket } = require('/home/node/.openclaw/openclaw-app/node_modules/ws'); const { randomUUID } = require('node:crypto'); const GATEWAY_URL = "ws://127.0.0.1:7860"; const GATEWAY_TOKEN = process.env.GATEWAY_TOKEN || "huggingclaw"; const CHECK_INTERVAL = 5000; const WAIT_TIMEOUT = 120000; let isWaiting = false; let hasShownWaitMessage = false; async function createConnection() { return new Promise((resolve, reject) => { const ws = new WebSocket(GATEWAY_URL); let resolved = false; ws.on("message", (data) => { const msg = JSON.parse(data.toString()); if (msg.type === "event" && msg.event === "connect.challenge") { ws.send(JSON.stringify({ type: "req", id: randomUUID(), method: "connect", params: { auth: { token: GATEWAY_TOKEN }, client: { id: "wa-guardian", platform: "linux", mode: "backend", version: "1.0.0" }, scopes: ["operator.admin", "operator.pairing", "operator.read", "operator.write"] } })); return; } if (!resolved && msg.type === "res" && msg.ok) { resolved = true; resolve(ws); } }); ws.on("error", (e) => { if (!resolved) reject(e); }); setTimeout(() => { if (!resolved) { ws.close(); reject(new Error("Timeout")); } }, 10000); }); } async function callRpc(ws, method, params) { return new Promise((resolve, reject) => { const id = randomUUID(); const handler = (data) => { const msg = JSON.parse(data.toString()); if (msg.id === id) { ws.removeListener("message", handler); resolve(msg); } }; ws.on("message", handler); ws.send(JSON.stringify({ type: "req", id, method, params })); setTimeout(() => { ws.removeListener("message", handler); reject(new Error("RPC Timeout")); }, WAIT_TIMEOUT + 5000); }); } async function checkStatus() { if (isWaiting) return; let ws; try { ws = await createConnection(); // Check if WhatsApp channel exists and its status const statusRes = await callRpc(ws, "channels.status", {}); const channels = (statusRes.payload || statusRes.result)?.channels || {}; const wa = channels.whatsapp; if (!wa) { ws.close(); return; } // If connected, we are good if (wa.connected) { ws.close(); return; } // If "Ready to pair", we wait for the scan isWaiting = true; if (!hasShownWaitMessage) { console.log("\n[guardian] 📱 WhatsApp pairing in progress. Please scan the QR code in the Control UI."); hasShownWaitMessage = true; } console.log("[guardian] Waiting for pairing completion..."); const waitRes = await callRpc(ws, "web.login.wait", { timeoutMs: WAIT_TIMEOUT }); const result = waitRes.payload || waitRes.result; if (result && (result.connected || (result.message && result.message.includes("515")))) { console.log("[guardian] ✅ Pairing completed! Saving session and restarting gateway..."); hasShownWaitMessage = false; // Auto-reapply config to finalize pairing const getRes = await callRpc(ws, "config.get", {}); if (getRes.ok) { await callRpc(ws, "config.apply", { raw: getRes.payload.raw, baseHash: getRes.payload.hash }); console.log("[guardian] Configuration re-applied."); } } } catch (e) { // Normal timeout or gateway starting up } finally { isWaiting = false; if (ws) ws.close(); } } console.log("[guardian] ⚔️ WhatsApp Guardian active. Monitoring pairing status..."); setInterval(checkStatus, CHECK_INTERVAL); setTimeout(checkStatus, 10000);