| #!/usr/bin/env node
|
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
|
|
| const fs = require('fs');
|
| const path = require('path');
|
| const { WebSocket } = require('ws');
|
| const readline = require('readline');
|
|
|
| class QRDetectionManager {
|
| constructor() {
|
| this.ws = null;
|
| this.isPaused = false;
|
| this.qrDetected = false;
|
| this.qrSourcePath = null;
|
| this.scanCompleted = false;
|
| this.timeout = null;
|
| this.qrTimeout = 300000;
|
|
|
|
|
| this.log = (level, message, data = {}) => {
|
| const logEntry = {
|
| timestamp: new Date().toISOString(),
|
| level,
|
| module: 'qr-detection-manager',
|
| message,
|
| ...data
|
| };
|
| console.log(JSON.stringify(logEntry));
|
| };
|
|
|
| this.log('info', 'QR Detection Manager initialized');
|
| }
|
|
|
| async connectWebSocket(spaceUrl) {
|
| try {
|
|
|
| let host = spaceUrl.replace(/^https?:\/\//, '').replace(/\/$/, '');
|
| const wsUrl = `wss://${host}`;
|
| const fullWsUrl = `${wsUrl}/queue/join`;
|
|
|
| this.log('info', 'Connecting to WebSocket', { url: fullWsUrl });
|
|
|
| this.ws = new WebSocket(fullWsUrl);
|
|
|
| this.ws.on('open', () => {
|
| this.log('info', 'WebSocket connection established');
|
| this.startMonitoring();
|
| });
|
|
|
| this.ws.on('message', (data) => {
|
| this.handleWebSocketMessage(data);
|
| });
|
|
|
| this.ws.on('error', (error) => {
|
| this.log('error', 'WebSocket error', { error: error.message });
|
| });
|
|
|
| this.ws.on('close', () => {
|
| this.log('info', 'WebSocket connection closed');
|
| });
|
|
|
| } catch (error) {
|
| this.log('error', 'Failed to connect to WebSocket', { error: error.message });
|
| }
|
| }
|
|
|
| handleWebSocketMessage(data) {
|
|
|
|
|
| }
|
|
|
| startMonitoring() {
|
| this.log('info', 'Starting QR code monitoring');
|
|
|
|
|
| const pingInterval = setInterval(() => {
|
| if (this.ws && this.ws.readyState === WebSocket.OPEN) {
|
| this.ws.ping();
|
| } else {
|
| clearInterval(pingInterval);
|
| }
|
| }, 30000);
|
|
|
|
|
| this.setupQRDetection();
|
| }
|
|
|
| setupQRDetection() {
|
| this.log('info', 'Setting up QR code detection');
|
|
|
|
|
| this.timeout = setTimeout(() => {
|
| if (!this.scanCompleted) {
|
| this.log('warning', 'QR scan timeout reached');
|
| this.outputQRPrompt('❌ QR scan timeout. Please restart the process.', 'timeout');
|
| process.exit(1);
|
| }
|
| }, this.qrTimeout);
|
|
|
|
|
| this.monitorForQR();
|
| }
|
|
|
| monitorForQR() {
|
| const homeDir = process.env.HOME || '/home/node';
|
|
|
| const qrCheckInterval = setInterval(() => {
|
| if (this.scanCompleted) {
|
| clearInterval(qrCheckInterval);
|
| return;
|
| }
|
|
|
|
|
| const qrPaths = [
|
| path.join(homeDir, '.openclaw/credentials/whatsapp/qr.png'),
|
| path.join(homeDir, '.openclaw/workspace/qr.png'),
|
| path.join(homeDir, 'logs/qr.png'),
|
| ];
|
|
|
| for (const qrPath of qrPaths) {
|
| if (fs.existsSync(qrPath)) {
|
| this.qrSourcePath = qrPath;
|
| this.handleQRDetected(qrPath);
|
| break;
|
| }
|
| }
|
|
|
|
|
| this.checkLogsForQR();
|
| }, 2000);
|
| }
|
|
|
| checkLogsForQR() {
|
| try {
|
| const homeDir = process.env.HOME || '/home/node';
|
| const logPaths = [
|
| path.join(homeDir, 'logs/app.log'),
|
| path.join(homeDir, '.openclaw/workspace/startup.log'),
|
| path.join(homeDir, '.openclaw/workspace/sync.log'),
|
| ];
|
|
|
| for (const logPath of logPaths) {
|
| if (fs.existsSync(logPath)) {
|
| const logContent = fs.readFileSync(logPath, 'utf8');
|
| if (this.isQRInLogContent(logContent)) {
|
| this.handleQRDetected('log');
|
| break;
|
| }
|
| }
|
| }
|
| } catch (error) {
|
|
|
| }
|
| }
|
|
|
| isQRInLogContent(content) {
|
|
|
| const qrPatterns = [
|
| /qr code/i,
|
| /scan.*qr/i,
|
| /please scan/i,
|
| /waiting.*qr/i,
|
| /login.*qr/i,
|
| /whatsapp.*qr/i,
|
| /authentication.*qr/i
|
| ];
|
|
|
| return qrPatterns.some(pattern => pattern.test(content));
|
| }
|
|
|
| handleQRDetected(source) {
|
| if (this.qrDetected) {
|
| return;
|
| }
|
|
|
| this.qrDetected = true;
|
| this.log('info', 'QR code detected', { source });
|
|
|
|
|
| this.isPaused = true;
|
|
|
|
|
| this.outputQRPrompt('⏳ Waiting for WhatsApp QR code scan...', 'waiting');
|
| this.outputQRPrompt('📱 Please scan the QR code with your phone to continue.', 'qr');
|
|
|
|
|
| this.monitorScanCompletion();
|
| }
|
|
|
| outputQRPrompt(message, type) {
|
|
|
| process.stdout.write('\x1b[2J\x1b[0f');
|
|
|
|
|
| const separator = '='.repeat(60);
|
| console.log(`\n${separator}`);
|
| console.log(`🔐 WHATSAPP LOGIN REQUIRED`);
|
| console.log(`${separator}\n`);
|
| console.log(message);
|
| console.log(`\n${separator}`);
|
|
|
|
|
| if (type === 'waiting') {
|
| console.log('⏳ Operation paused - waiting for QR scan...');
|
| } else if (type === 'qr') {
|
| console.log('📱 Use your WhatsApp app to scan the QR code');
|
| } else if (type === 'success') {
|
| console.log('✅ QR scan completed successfully!');
|
| } else if (type === 'timeout') {
|
| console.log('❌ QR scan timeout - please try again');
|
| }
|
|
|
| console.log(`${separator}\n`);
|
|
|
|
|
| this.log(type === 'success' ? 'info' : 'warning', 'QR prompt output', {
|
| message,
|
| type,
|
| isPaused: this.isPaused
|
| });
|
| }
|
|
|
| monitorScanCompletion() {
|
| this.log('info', 'Monitoring for QR scan completion');
|
|
|
|
|
| const completionCheck = setInterval(() => {
|
| if (this.checkScanCompletion()) {
|
| clearInterval(completionCheck);
|
| this.handleScanCompleted();
|
| }
|
| }, 1000);
|
| }
|
|
|
| checkScanCompletion() {
|
| const homeDir = process.env.HOME || '/home/node';
|
|
|
|
|
| if (this.qrSourcePath && !fs.existsSync(this.qrSourcePath)) {
|
| return true;
|
| }
|
|
|
|
|
| try {
|
| const logPaths = [
|
| path.join(homeDir, 'logs/app.log'),
|
| path.join(homeDir, '.openclaw/workspace/startup.log'),
|
| path.join(homeDir, '.openclaw/workspace/sync.log'),
|
| ];
|
|
|
| for (const logPath of logPaths) {
|
| if (fs.existsSync(logPath)) {
|
| const logContent = fs.readFileSync(logPath, 'utf8');
|
| if (this.isLoginInLogContent(logContent)) {
|
| return true;
|
| }
|
| }
|
| }
|
| } catch (error) {
|
|
|
| }
|
|
|
|
|
| const sessionPaths = [
|
| path.join(homeDir, '.openclaw/credentials/whatsapp/creds.json'),
|
| path.join(homeDir, '.openclaw/credentials/whatsapp/session.json'),
|
| ];
|
|
|
| for (const sessionPath of sessionPaths) {
|
| if (fs.existsSync(sessionPath)) {
|
| return true;
|
| }
|
| }
|
|
|
| return false;
|
| }
|
|
|
| isLoginInLogContent(content) {
|
|
|
| const loginPatterns = [
|
| /login.*successful/i,
|
| /authentication.*success/i,
|
| /session.*established/i,
|
| /connected.*whatsapp/i,
|
| /qr.*scanned/i,
|
| /scan.*completed/i,
|
| /user.*authenticated/i
|
| ];
|
|
|
| return loginPatterns.some(pattern => pattern.test(content));
|
| }
|
|
|
| handleScanCompleted() {
|
| this.scanCompleted = true;
|
| this.isPaused = false;
|
|
|
|
|
| if (this.timeout) {
|
| clearTimeout(this.timeout);
|
| }
|
|
|
|
|
| this.outputQRPrompt('✅ QR code scanned successfully. Login completed.', 'success');
|
|
|
| this.log('info', 'QR scan completed, resuming operations');
|
|
|
|
|
| setTimeout(() => {
|
|
|
| process.exit(0);
|
| }, 3000);
|
| }
|
|
|
| async waitForQRScan() {
|
| return new Promise((resolve, reject) => {
|
| const checkInterval = setInterval(() => {
|
| if (this.scanCompleted) {
|
| clearInterval(checkInterval);
|
| resolve();
|
| }
|
| }, 1000);
|
|
|
|
|
| setTimeout(() => {
|
| clearInterval(checkInterval);
|
| reject(new Error('QR scan timeout'));
|
| }, this.qrTimeout);
|
| });
|
| }
|
|
|
| close() {
|
| if (this.ws) {
|
| this.ws.close();
|
| }
|
| if (this.timeout) {
|
| clearTimeout(this.timeout);
|
| }
|
| this.log('info', 'QR Detection Manager closed');
|
| }
|
| }
|
|
|
|
|
| async function main() {
|
| const args = process.argv.slice(2);
|
| const spaceUrl = args[0] || process.env.SPACE_HOST || '';
|
|
|
| const manager = new QRDetectionManager();
|
|
|
| try {
|
| await manager.connectWebSocket(spaceUrl);
|
|
|
|
|
| process.on('SIGINT', () => {
|
| manager.log('info', 'Received SIGINT, shutting down gracefully');
|
| manager.close();
|
| process.exit(0);
|
| });
|
|
|
| process.on('SIGTERM', () => {
|
| manager.log('info', 'Received SIGTERM, shutting down gracefully');
|
| manager.close();
|
| process.exit(0);
|
| });
|
|
|
| } catch (error) {
|
| manager.log('error', 'QR Detection Manager failed', { error: error.message });
|
| process.exit(1);
|
| }
|
| }
|
|
|
| if (require.main === module) {
|
| main();
|
| }
|
|
|
| module.exports = QRDetectionManager; |