Bot_v1.0 / index.js
JustJoin's picture
Update index.js
a769bff verified
Raw
History Blame Contribute Delete
10.5 kB
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'; // Driver penghubung ke PostgreSQL Supabase
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';
// MENYELARASKAN IMPORT: Menggunakan 3 Agen Baru Terpadu
import { jalankanRouterAgent } from './agents/routerAgent.js';
import { jalankanJadwalAgent } from './agents/jadwalAgent.js';
import { jalankanlainnyaAgent } from './agents/lainnyaAgent.js';
// --- TAMENG PERTAMA: INSTANSIASI EXPRESS SERVER (SINGLE PORT RULE) ---
const app = express();
const PORT = process.env.PORT || 7860; // Port standar yang diwajibkan Hugging Face Spaces
app.listen(PORT, '0.0.0.0', () => {
console.log(`Server Odysseus aktif dan mendengarkan di port ${PORT}`);
});
// Jalur Web Utama (Untuk cek status via browser)
app.get('/', (req, res) => {
res.status(200).send('Bot Pelayanan DNA Kids Aktif dan Berjalan di Hugging Face 🚀\n');
});
// Jalur Khusus PING (Untuk ditembak secara berkala oleh Cron-Job.org / Uptime)
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!');
});
// Jalankan server di port tunggal
app.listen(PORT, () => {
console.log(`🌍 [SERVER HTTP] Server Express berhasil mengunci port ${PORT}. Siap menerima ketukan eksternal.`);
});
// --- KONFIGURASI MEMORI & STATE BOT ---
const cooldownChat = new Map();
const memoriSesiChat = new Map();
let urutanGagalKonek = 0;
const MAKSIMAL_GAGAL = 5;
// --- ADAPTER AUTHENTICATION POSTGRESQL (SUPABASE) ---
// Membuat pool koneksi ke database cloud
const pool = new pg.Pool({ connectionString: DATABASE_URL });
async function gunakanAutentikasiPostgres() {
// Membuat tabel penyimpanan session otomatis di database jika belum tersedia
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;
}
// Fungsi pembantu (Helper) transaksi data SQL
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]);
}
// --- ENGINE UTAMA WHATSAPP ---
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...");
// Mengganti useMultiFileAuthState dengan adapter database PostgreSQL kita
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
});
// --- EVENT LISTENER KONEKSI ---
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);
// --- EVENT LISTENER PESAN MASUK ---
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);
});