import pkg, { DisconnectReason } from '@whiskeysockets/baileys'; import pino from 'pino'; import mqtt from 'mqtt'; import fs from 'fs'; const { makeWASocket, useMultiFileAuthState, fetchLatestBaileysVersion, } = pkg; const allowedNumbers = [ '62882009051780@s.whatsapp.net', '6287862977046@s.whatsapp.net', '6289521851079@s.whatsapp.net', ]; // Status lokal perabotan (backup jika diperlukan) const status = { 'lampu kamar': false, 'lampu teras': false, 'kipas': false, 'pintu': false, }; // Penyimpan siapa yang meminta status const pendingStatusRequests = {}; // Fungsi konversi status jadi teks function getStatusString(statusObj) { return Object.entries(statusObj) .map(([device, value]) => { if (device === 'pintu') { return `- ${device} : ${value === 'dibuka' ? '🔓 Dibuka' : '🔒 Dikunci'}`; } else { return `- ${device} : ${value === true || value === 'true' ? '✅ Menyala' : '❌ Mati'}`; } }) .join('\n'); // MQTT setup const mqttClient = mqtt.connect('mqtt://test.mosquitto.org'); mqttClient.on('connect', () => { console.log('📡 Terhubung ke MQTT broker: test.mosquitto.org'); }); mqttClient.on('error', (err) => { console.error('❌ MQTT Error:', err); }); // Tangani balasan dari ESP32 ke topik status mqttClient.on('message', async (topic, message) => { if (topic === 'cristine/status') { try { const parsedStatus = JSON.parse(message.toString()); const now = Date.now(); for (const [jid, time] of Object.entries(pendingStatusRequests)) { if (now - time <= 10000) { // valid 10 detik await sock.sendMessage(jid, { text: `📊 Status Real-time:\n${getStatusString(parsedStatus)}` }); } } // Kosongkan request setelah dibalas for (const jid of Object.keys(pendingStatusRequests)) { delete pendingStatusRequests[jid]; } } catch (e) { console.error('❌ Gagal parsing status dari ESP32:', e); } } }); mqttClient.subscribe('cristine/status'); // Fungsi utama bot let sock; async function startBot() { const { version } = await fetchLatestBaileysVersion(); const { state, saveCreds } = await useMultiFileAuthState('./auth_info_baileys'); sock = makeWASocket({ version, auth: state, printQRInTerminal: true, logger: pino({ level: 'silent' }), }); sock.ev.on('creds.update', saveCreds); sock.ev.on('connection.update', async ({ connection, lastDisconnect }) => { if (connection === 'close') { const reasonCode = lastDisconnect?.error?.output?.statusCode; const isLoggedOut = reasonCode === DisconnectReason.loggedOut; console.log('⚠️ Koneksi terputus:', reasonCode); if (isLoggedOut) { fs.rmSync('./auth_info_baileys', { recursive: true, force: true }); console.log('🔁 Silakan restart untuk login ulang.'); } else { startBot(); } } if (connection === 'open') { console.log('✅ Bot WhatsApp berhasil online!'); } }); sock.ev.on('messages.upsert', async ({ messages }) => { const msg = messages[0]; if (!msg.message || msg.key.fromMe) return; const sender = msg.key.remoteJid; if (!allowedNumbers.includes(sender)) return; const text = msg.message.conversation || msg.message?.extendedTextMessage?.text || msg.message?.imageMessage?.caption || msg.message?.buttonsResponseMessage?.selectedButtonId || msg.message?.listResponseMessage?.singleSelectReply?.selectedRowId; if (!text) return; const command = text.trim().toLowerCase(); console.log(`📩 Pesan dari ${sender}:`, command); switch (command) { case 'menu': await sock.sendMessage(sender, { text: '📋 Menu Kontrol:\n' + '- nyalakan lampu kamar\n' + '- matikan lampu kamar\n' + '- nyalakan lampu teras\n' + '- matikan lampu teras\n' + '- nyalakan kipas\n' + '- matikan kipas\n' + '- buka pintu\n' + '- kunci pintu\n' + '- status', }); break; case 'nyalakan lampu kamar': status['lampu kamar'] = true; mqttClient.publish('cristine/control/lampu_kamar', 'ON'); await sock.sendMessage(sender, { text: '💡 Lampu kamar *dinyalakan*.' }); break; case 'matikan lampu kamar': status['lampu kamar'] = false; mqttClient.publish('cristine/control/lampu_kamar', 'OFF'); await sock.sendMessage(sender, { text: '💡 Lampu kamar *dimatikan*.' }); break; case 'nyalakan lampu teras': status['lampu teras'] = true; mqttClient.publish('cristine/control/lampu_teras', 'ON'); await sock.sendMessage(sender, { text: '💡 Lampu teras *dinyalakan*.' }); break; case 'matikan lampu teras': status['lampu teras'] = false; mqttClient.publish('cristine/control/lampu_teras', 'OFF'); await sock.sendMessage(sender, { text: '💡 Lampu teras *dimatikan*.' }); break; case 'nyalakan kipas': status['kipas'] = true; mqttClient.publish('cristine/control/kipas', 'ON'); await sock.sendMessage(sender, { text: '🌀 Kipas *dinyalakan*.' }); break; case 'matikan kipas': status['kipas'] = false; mqttClient.publish('cristine/control/kipas', 'OFF'); await sock.sendMessage(sender, { text: '🌀 Kipas *dimatikan*.' }); break; case 'buka pintu': status['pintu'] = true; mqttClient.publish('cristine/control/pintu', 'ON'); await sock.sendMessage(sender, { text: '🚪 Pintu *dibuka*.' }); break; case 'kunci pintu': status['pintu'] = false; mqttClient.publish('cristine/control/pintu', 'OFF'); await sock.sendMessage(sender, { text: '🚪 Pintu *dikunci*.' }); break; case 'status': pendingStatusRequests[sender] = Date.now(); mqttClient.publish('cristine/request/status', 'REQUEST'); await sock.sendMessage(sender, { text: '⏳ Meminta status dari perangkat, mohon tunggu...' }); break; default: await sock.sendMessage(sender, { text: `⚠️ Perintah tidak dikenali.\nKetik *menu* untuk melihat daftar perintah.` }); break; } }); } } startBot();