/** * WhatsApp Authentication Script * * Run this during setup to authenticate with WhatsApp. * Displays QR code, waits for scan, saves credentials, then exits. * * Usage: npx tsx src/whatsapp-auth.ts */ import fs from 'fs'; import path from 'path'; import pino from 'pino'; import qrcode from 'qrcode-terminal'; import readline from 'readline'; import makeWASocket, { Browsers, DisconnectReason, fetchLatestWaWebVersion, makeCacheableSignalKeyStore, useMultiFileAuthState, } from '@whiskeysockets/baileys'; const AUTH_DIR = './store/auth'; const QR_FILE = './store/qr-data.txt'; const STATUS_FILE = './store/auth-status.txt'; const logger = pino({ level: 'warn', // Quiet logging - only show errors }); // Check for --pairing-code flag and phone number const usePairingCode = process.argv.includes('--pairing-code'); const phoneArg = process.argv.find((_, i, arr) => arr[i - 1] === '--phone'); function askQuestion(prompt: string): Promise { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); return new Promise((resolve) => { rl.question(prompt, (answer) => { rl.close(); resolve(answer.trim()); }); }); } async function connectSocket( phoneNumber?: string, isReconnect = false, ): Promise { const { state, saveCreds } = await useMultiFileAuthState(AUTH_DIR); if (state.creds.registered && !isReconnect) { fs.writeFileSync(STATUS_FILE, 'already_authenticated'); console.log('āœ“ Already authenticated with WhatsApp'); console.log( ' To re-authenticate, delete the store/auth folder and run again.', ); process.exit(0); } const { version } = await fetchLatestWaWebVersion({}).catch((err) => { logger.warn( { err }, 'Failed to fetch latest WA Web version, using default', ); return { version: undefined }; }); const sock = makeWASocket({ version, auth: { creds: state.creds, keys: makeCacheableSignalKeyStore(state.keys, logger), }, printQRInTerminal: false, logger, browser: Browsers.macOS('Chrome'), }); if (usePairingCode && phoneNumber && !state.creds.me) { // Request pairing code after a short delay for connection to initialize // Only on first connect (not reconnect after 515) setTimeout(async () => { try { const code = await sock.requestPairingCode(phoneNumber!); console.log(`\nšŸ”— Your pairing code: ${code}\n`); console.log(' 1. Open WhatsApp on your phone'); console.log(' 2. Tap Settings → Linked Devices → Link a Device'); console.log(' 3. Tap "Link with phone number instead"'); console.log(` 4. Enter this code: ${code}\n`); fs.writeFileSync(STATUS_FILE, `pairing_code:${code}`); } catch (err: any) { console.error('Failed to request pairing code:', err.message); process.exit(1); } }, 3000); } sock.ev.on('connection.update', (update) => { const { connection, lastDisconnect, qr } = update; if (qr) { // Write raw QR data to file so the setup skill can render it fs.writeFileSync(QR_FILE, qr); console.log('Scan this QR code with WhatsApp:\n'); console.log(' 1. Open WhatsApp on your phone'); console.log(' 2. Tap Settings → Linked Devices → Link a Device'); console.log(' 3. Point your camera at the QR code below\n'); qrcode.generate(qr, { small: true }); } if (connection === 'close') { const reason = (lastDisconnect?.error as any)?.output?.statusCode; if (reason === DisconnectReason.loggedOut) { fs.writeFileSync(STATUS_FILE, 'failed:logged_out'); console.log('\nāœ— Logged out. Delete store/auth and try again.'); process.exit(1); } else if (reason === DisconnectReason.timedOut) { fs.writeFileSync(STATUS_FILE, 'failed:qr_timeout'); console.log('\nāœ— QR code timed out. Please try again.'); process.exit(1); } else if (reason === 515) { // 515 = stream error, often happens after pairing succeeds but before // registration completes. Reconnect to finish the handshake. console.log('\n⟳ Stream error (515) after pairing — reconnecting...'); connectSocket(phoneNumber, true); } else { fs.writeFileSync(STATUS_FILE, `failed:${reason || 'unknown'}`); console.log('\nāœ— Connection failed. Please try again.'); process.exit(1); } } if (connection === 'open') { fs.writeFileSync(STATUS_FILE, 'authenticated'); // Clean up QR file now that we're connected try { fs.unlinkSync(QR_FILE); } catch {} console.log('\nāœ“ Successfully authenticated with WhatsApp!'); console.log(' Credentials saved to store/auth/'); console.log(' You can now start the NanoClaw service.\n'); // Give it a moment to save credentials, then exit setTimeout(() => process.exit(0), 1000); } }); sock.ev.on('creds.update', saveCreds); } async function authenticate(): Promise { fs.mkdirSync(AUTH_DIR, { recursive: true }); // Clean up any stale QR/status files from previous runs try { fs.unlinkSync(QR_FILE); } catch {} try { fs.unlinkSync(STATUS_FILE); } catch {} let phoneNumber = phoneArg; if (usePairingCode && !phoneNumber) { phoneNumber = await askQuestion( 'Enter your phone number (with country code, no + or spaces, e.g. 14155551234): ', ); } console.log('Starting WhatsApp authentication...\n'); await connectSocket(phoneNumber); } authenticate().catch((err) => { console.error('Authentication failed:', err.message); process.exit(1); });