whatsapp-probeBaileys · présence passive · heatmap · inférence timezone · profil comportemental
WhatsApp diffuse les événements de présence (
available / unavailable) via le protocole WebSocket de WhatsApp Web. Le script ci-dessous utilise Baileys pour s'y connecter avec votre compte, souscrire à la présence du numéro cible et logger chaque changement de statut. La cible ne reçoit aucune notification. La visibilité du statut dépend des réglages de confidentialité du compte cible.Numéro cible (pré-rempli dans le script) :
target>
Installation
# Créer un dossier dédié
mkdir wa-probe && cd wa-probe
npm init -y
npm install @whiskeysockets/baileys @hapi/boom pino qrcode-terminal
# Télécharger le script monitor.js, puis :
node monitor.js
# Scanner le QR avec WhatsApp → Paramètres → Appareils liés
# Le log est écrit dans ./presence-log.json
Script monitor.js
#!/usr/bin/env node
/**
* WhatsApp Presence Monitor — Baileys-based
* Logs online/offline status of a target number to presence-log.json
*
* Setup:
* npm init -y
* npm install @whiskeysockets/baileys @hapi/boom pino qrcode-terminal
*
* Run:
* node monitor.js
*
* Then scan the QR code with your WhatsApp (Settings → Linked Devices)
* The log file is written to ./presence-log.json
* Import that file in the Analyse tab to build the behavioral profile.
*/
const { default: makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, DisconnectReason, makeCacheableSignalKeyStore } = require("@whiskeysockets/baileys");
const { Boom } = require("@hapi/boom");
const pino = require("pino");
const fs = require("fs");
const TARGET = "+XXXXXXXXXXX"; // E.164 format e.g. +33612345678
const LOG_FILE = "./presence-log.json";
const INTERVAL_MS = 20_000; // poll every 20 seconds (reduce to 10s for tighter resolution)
const jid = TARGET.replace("+", "") + "@s.whatsapp.net";
let log = fs.existsSync(LOG_FILE)
? JSON.parse(fs.readFileSync(LOG_FILE, "utf8"))
: [];
function save(entry) {
log.push(entry);
fs.writeFileSync(LOG_FILE, JSON.stringify(log, null, 2));
}
function fmt(ms) {
if (ms < 60000) return ms / 1000 + "s";
if (ms < 3600000) return (ms / 60000).toFixed(1) + "m";
return (ms / 3600000).toFixed(2) + "h";
}
async function start() {
const { state, saveCreds } = await useMultiFileAuthState("./wa-session");
const { version } = await fetchLatestBaileysVersion();
const sock = makeWASocket({
version,
auth: {
creds: state.creds,
keys: makeCacheableSignalKeyStore(state.keys, pino({ level: "silent" })),
},
logger: pino({ level: "silent" }),
printQRInTerminal: true,
markOnlineOnConnect: false,
generateHighQualityLinkPreview: false,
});
sock.ev.on("creds.update", saveCreds);
let lastStatus = null;
let lastOnlineAt = null;
sock.ev.on("connection.update", async ({ connection, lastDisconnect, qr }) => {
if (connection === "close") {
const reason = new Boom(lastDisconnect?.error)?.output?.statusCode;
const shouldReconnect = reason !== DisconnectReason.loggedOut;
console.log("[!] Connection closed, reason:", reason, "— reconnecting:", shouldReconnect);
if (shouldReconnect) setTimeout(start, 5_000);
return;
}
if (connection === "open") {
console.log("[+] Connected to WhatsApp");
console.log("[*] Subscribing to presence:", TARGET);
try {
await sock.presenceSubscribe(jid);
console.log("[+] Presence subscription active");
console.log("[*] Monitoring started. Ctrl+C to stop. Log:", LOG_FILE);
} catch (e) {
console.error("[!] Presence subscribe failed:", e.message);
console.log("[~] Falling back to polling via profile query every", fmt(INTERVAL_MS));
// Fallback polling loop
setInterval(async () => {
try {
const [result] = await sock.onWhatsApp(jid);
const online = result?.exists ?? false;
const status = online ? "online" : "offline";
if (status !== lastStatus) {
const entry = { ts: Date.now(), status };
save(entry);
if (status === "online") lastOnlineAt = Date.now();
if (status === "offline" && lastOnlineAt) {
const dur = Date.now() - lastOnlineAt;
console.log(`[${new Date().toISOString()}] offline (session: ${fmt(dur)})`);
} else {
console.log(`[${new Date().toISOString()}] ${status}`);
}
lastStatus = status;
}
} catch { /* connection issue */ }
}, INTERVAL_MS);
}
}
});
sock.ev.on("presence.update", ({ id, presences }) => {
if (id !== jid) return;
const p = presences[jid];
if (!p) return;
// available / unavailable / composing / recording
const isOnline = p.lastKnownPresence === "available" || p.lastKnownPresence === "composing" || p.lastKnownPresence === "recording";
const status = isOnline ? "online" : "offline";
if (status !== lastStatus) {
const entry = { ts: Date.now(), status, raw: p.lastKnownPresence };
save(entry);
if (status === "online") {
lastOnlineAt = Date.now();
console.log(`[${new Date().toISOString()}] ONLINE`);
} else {
const dur = lastOnlineAt ? Date.now() - lastOnlineAt : null;
console.log(`[${new Date().toISOString()}] offline${dur ? " (session: " + fmt(dur) + ")" : ""}`);
lastOnlineAt = null;
}
lastStatus = status;
}
});
}
start().catch(console.error);
Format de log généré
[
{ "ts": 1710500000000, "status": "online" },
{ "ts": 1710500120000, "status": "offline" },
{ "ts": 1710506400000, "status": "online" },
...
]Copiez le contenu de
presence-log.json dans l'onglet Analyse pour générer le profil comportemental.Portée : WhatsApp permet à chaque utilisateur de restreindre la visibilité de son statut (Paramètres → Confidentialité → Dernière connexion / En ligne). Si la cible a désactivé le statut en ligne, les événements ne seront pas émis. La surveillance passive par présence est une technique OSINT documentée utilisée dans le cadre de journalisme d'investigation et de recherche en sécurité.
Architecture
La surveillance de présence WhatsApp nécessite une connexion WebSocket persistante (plusieurs heures à plusieurs jours) — incompatible avec les fonctions serverless. Le script monitor.js (Baileys) s'exécute localement et log les événements en JSON. L'analyse comportementale est ensuite réalisée ici, entièrement dans le navigateur.
Résolution recommandée : 20-30 secondes · Durée minimale pour un profil fiable : 48-72h · Idéale : 7 jours
Limites : Si la cible a désactivé "Dernière connexion / En ligne" (Confidentialité WhatsApp), aucun événement de présence n'est émis. L'inférence de timezone est statistique (±1-2h) — à confirmer avec les heures de pointe.