| import baileysPkg from '@whiskeysockets/baileys';
|
| const { default: makeWASocket, DisconnectReason, fetchLatestBaileysVersion } = baileysPkg;
|
| import { Boom } from '@hapi/boom';
|
| import qrcode from 'qrcode-terminal';
|
| import express from 'express';
|
| import pino from 'pino';
|
| import pg from 'pg';
|
| import { KATA_PEMICU, ID_GRUP_ANNOUNCEMENT, GRUP_DI_IZINKAN, NOMOR_KOORDINATOR_JADWAL, DATABASE_URL } from './config/env.js';
|
| import { periksaJadwalOtomatis, dataJadwalGlobal } from './services/googleSheets.js';
|
|
|
|
|
| import { jalankanRouterAgent } from './agents/routerAgent.js';
|
| import { jalankanJadwalAgent } from './agents/jadwalAgent.js';
|
| import { jalankanlainnyaAgent } from './agents/lainnyaAgent.js';
|
|
|
|
|
| const app = express();
|
| const PORT = process.env.PORT || 7860;
|
|
|
| app.listen(PORT, '0.0.0.0', () => {
|
| console.log(`Server Odysseus aktif dan mendengarkan di port ${PORT}`);
|
| });
|
|
|
|
|
| app.get('/', (req, res) => {
|
| res.status(200).send('Bot Pelayanan DNA Kids Aktif dan Berjalan di Hugging Face 🚀\n');
|
| });
|
|
|
|
|
| app.get('/ping', (req, res) => {
|
| const waktuWIB = new Date().toLocaleString("id-ID", { timeZone: "Asia/Jakarta" });
|
| console.log(`⏰ [PING RECEIVED] Sinyal penjaga masuk pada pukul: ${waktuWIB} WIB. Menjaga kontainer tetap menyala.`);
|
| res.status(200).send('Bot Terjaga!');
|
| });
|
|
|
|
|
| app.listen(PORT, () => {
|
| console.log(`🌍 [SERVER HTTP] Server Express berhasil mengunci port ${PORT}. Siap menerima ketukan eksternal.`);
|
| });
|
|
|
|
|
| const cooldownChat = new Map();
|
| const memoriSesiChat = new Map();
|
|
|
| let urutanGagalKonek = 0;
|
| const MAKSIMAL_GAGAL = 5;
|
|
|
|
|
|
|
| const pool = new pg.Pool({ connectionString: DATABASE_URL });
|
|
|
| async function gunakanAutentikasiPostgres() {
|
|
|
| await pool.query(`
|
| CREATE TABLE IF NOT EXISTS sesi_bot_dna (
|
| id VARCHAR(255) PRIMARY KEY,
|
| data TEXT NOT NULL
|
| );
|
| `);
|
|
|
| const authStateDB = {
|
| state: {
|
| creds: await ambilDataSesi('creds') || {},
|
| keys: {
|
| get: async (type, ids) => {
|
| const data = {};
|
| for (const id of ids) {
|
| data[id] = await ambilDataSesi(`${type}-${id}`);
|
| }
|
| return data;
|
| },
|
| set: async (data) => {
|
| for (const type in data) {
|
| for (const id in data[type]) {
|
| const value = data[type][id];
|
| const keyName = `${type}-${id}`;
|
| if (value) {
|
| await simpanDataSesi(keyName, value);
|
| } else {
|
| await hapusDataSesi(keyName);
|
| }
|
| }
|
| }
|
| }
|
| }
|
| },
|
| saveCreds: async () => {
|
| await simpanDataSesi('creds', authStateDB.state.creds);
|
| }
|
| };
|
| return authStateDB;
|
| }
|
|
|
|
|
| async function ambilDataSesi(id) {
|
| const res = await pool.query('SELECT data FROM sesi_bot_dna WHERE id = $1', [id]);
|
| return res.rows.length ? JSON.parse(res.rows[0].data) : null;
|
| }
|
| async function simpanDataSesi(id, data) {
|
| const jsonString = JSON.stringify(data);
|
| await pool.query('INSERT INTO sesi_bot_dna (id, data) VALUES ($1, $2) ON CONFLICT (id) DO UPDATE SET data = $2', [id, jsonString]);
|
| }
|
| async function hapusDataSesi(id) {
|
| await pool.query('DELETE FROM sesi_bot_dna WHERE id = $1', [id]);
|
| }
|
|
|
|
|
| async function hubungkanKeWhatsApp() {
|
| if (urutanGagalKonek >= MAKSIMAL_GAGAL) {
|
| console.error("❌ Gagal menembus WebSocket WA secara beruntun. Menghentikan percobaan...");
|
| process.exit(1);
|
| }
|
|
|
| try {
|
| console.log("⏳ Mengambil data sesi login aman dari Cloud Database...");
|
|
|
| const { state, saveCreds } = await gunakanAutentikasiPostgres();
|
|
|
| console.log("⏳ Mengambil data versi protokol WhatsApp Web terbaru...");
|
| const { version, isLatest } = await fetchLatestBaileysVersion();
|
| console.log(`ℹ️ Menggunakan WA Web v${version.join('.')}, Terkini: ${isLatest}`);
|
|
|
| const sock = makeWASocket({
|
| version,
|
| auth: state,
|
| printQRInTerminal: false,
|
| logger: pino({ level: 'silent' }),
|
| browser: ['Windows', 'Chrome', '122.0.0.0'],
|
| connectTimeoutMs: 60000,
|
| defaultQueryTimeoutMs: 60000,
|
| keepAliveIntervalMs: 30000
|
| });
|
|
|
|
|
| sock.ev.on('connection.update', (update) => {
|
| const { connection, lastDisconnect, qr } = update;
|
|
|
| if (qr) {
|
| console.log('\n====== SCAN QR CODE INI VIA WHATSAPP KAMU ======');
|
| qrcode.generate(qr, { small: true });
|
| }
|
|
|
| if (connection === 'close') {
|
| const errorDetail = lastDisconnect?.error;
|
| const statusCode = errorDetail?.output?.statusCode || (errorDetail instanceof Boom ? errorDetail.output.statusCode : undefined);
|
| const alasanEror = errorDetail?.message || errorDetail || "Masalah Jaringan/Handshake Jarak Jauh";
|
|
|
| console.log(`⚠️ Koneksi Terputus! Status: ${statusCode} | Alasan: ${alasanEror}`);
|
|
|
| const harusKonekUlang = statusCode !== DisconnectReason.loggedOut;
|
| if (harusKonekUlang) {
|
| urutanGagalKonek++;
|
| console.log(`🔄 Mencoba koneksi ulang (${urutanGagalKonek}/${MAKSIMAL_GAGAL}) dalam 10 detik...`);
|
| setTimeout(hubungkanKeWhatsApp, 10000);
|
| }
|
| } else if (connection === 'open') {
|
| console.log('\n✅ Bot Otomisasi SM Versi Modular Odysseus Sukses Terhubung via Supabase!\n');
|
| urutanGagalKonek = 0;
|
| periksaJadwalOtomatis();
|
| }
|
| });
|
|
|
| sock.ev.on('creds.update', saveCreds);
|
|
|
|
|
| sock.ev.on('messages.upsert', async ({ messages }) => {
|
| const m = messages[0];
|
| if (!m.message) return;
|
|
|
| const idPengirim = m.key.remoteJid;
|
| const teksMasingPesan = m.message?.conversation || m.message?.extendedTextMessage?.text || "";
|
|
|
| console.log(`📩 [LOG MASUK] Dari: ${idPengirim} | Isi Pesan: "${teksMasingPesan}"`);
|
|
|
| if (idPengirim === NOMOR_KOORDINATOR_JADWAL && teksMasingPesan.toLowerCase() === '!update') {
|
| await sock.sendMessage(idPengirim, { text: '⏳ Sedang meremot pembaruan Google Sheets...' });
|
| await periksaJadwalOtomatis();
|
| await sock.sendMessage(idPengirim, { text: '✅ Sinkronisasi jadwal berhasil diperbarui!' });
|
| return;
|
| }
|
|
|
| if (idPengirim === 'status@broadcast') return;
|
|
|
| const arrayPemicu = Array.isArray(KATA_PEMICU) ? KATA_PEMICU : [KATA_PEMICU];
|
| const pemicuCocok = arrayPemicu.find(p => teksMasingPesan.toLowerCase().startsWith(p.toLowerCase()));
|
|
|
| if (m.key.fromMe && !pemicuCocok) return;
|
| if (idPengirim === ID_GRUP_ANNOUNCEMENT) return;
|
| if (teksMasingPesan.trim() === "" || !pemicuCocok) return;
|
|
|
| const apakahDariGrup = idPengirim.endsWith('@g.us');
|
| if (apakahDariGrup && !GRUP_DI_IZINKAN.includes(idPengirim)) return;
|
|
|
| const pertanyaanBersih = teksMasingPesan.slice(pemicuCocok.length).trim();
|
| if (!pertanyaanBersih) return;
|
|
|
| const waktuSekarang = Date.now();
|
| const waktuTerakhirChat = cooldownChat.get(idPengirim) || 0;
|
| if (waktuSekarang - waktuTerakhirChat < 10000) {
|
| const sisaWaktu = Math.ceil((10000 - (waktuSekarang - waktuTerakhirChat)) / 1000);
|
| await sock.sendMessage(idPengirim, { text: `⚠️ *Sistem Cooldown!* Coba lagi dalam ${sisaWaktu} detik.` });
|
| return;
|
| }
|
| cooldownChat.set(idPengirim, waktuSekarang);
|
|
|
| if (!memoriSesiChat.has(idPengirim)) memoriSesiChat.set(idPengirim, []);
|
| let riwayat = memoriSesiChat.get(idPengirim);
|
|
|
| try {
|
| await sock.sendPresenceUpdate('composing', idPengirim);
|
|
|
| const kategori = await jalankanRouterAgent(pertanyaanBersih);
|
| let jawabanAkhir = "";
|
|
|
| if (kategori === "jadwal") {
|
| jawabanAkhir = await jalankanJadwalAgent(pertanyaanBersih, dataJadwalGlobal, riwayat);
|
| } else {
|
| jawabanAkhir = await jalankanlainnyaAgent(pertanyaanBersih, riwayat);
|
| }
|
|
|
| riwayat.push({ role: "user", content: pertanyaanBersih });
|
| riwayat.push({ role: "assistant", content: jawabanAkhir });
|
| if (riwayat.length > 6) riwayat.splice(0, 2);
|
|
|
| await sock.sendMessage(idPengirim, { text: jawabanAkhir });
|
|
|
| } catch (errProses) {
|
| console.error("❌ Terjadi eror saat memproses pesan:", errProses);
|
| }
|
| });
|
|
|
| } catch (error) {
|
| console.error("❌ Terjadi eror saat menginisialisasi koneksi:", error);
|
| }
|
| }
|
|
|
| hubungkanKeWhatsApp();
|
|
|
| process.on('uncaughtException', (err) => {
|
| console.error('Terperangkap Fatal Error Sistem:', err);
|
| }); |