'use strict'; /** * waNotifyService.js — kirim notifikasi WA mengikuti gateway aktif * (gowa / baileys / fonnte / meta). Mendukung teks, gambar, dan dokumen. */ const fs = require('fs'); const path = require('path'); const { getSetting } = require('../config/settingsManager'); function gateway() { return String(getSetting('wa_gateway_type', 'gowa') || 'baileys').toLowerCase(); } function spx(t) { return String(t || '').replace(/\{([^{}|]+(?:\|[^{}|]+)+)\}/g, function (m, c) { const arr = c.split('|'); return arr[Math.floor(Math.random() * arr.length)].trim(); }); } function publicBaseUrl() { let base = String(getSetting('public_base_url', '') || '').trim(); if (base) return base.replace(/\/+$/, ''); const host = String(getSetting('server_host', '') || '').trim(); const port = Number(getSetting('server_port', 3001) || 3001); if (!host) return ''; const proto = port === 443 ? 'https' : 'http'; const h = /^https?:\/\//i.test(host) ? host.replace(/\/+$/, '') : (proto + '://' + host); return (port === 80 || port === 443) ? h : (h + ':' + port); } function saveTemp(buffer, ext) { try { const dir = path.join(__dirname, '..', 'public', 'uploads', 'notif'); fs.mkdirSync(dir, { recursive: true }); const name = 'notif-' + Date.now() + '-' + Math.random().toString(36).slice(2, 8) + '.' + (ext || 'bin'); fs.writeFileSync(path.join(dir, name), buffer); const base = publicBaseUrl(); return (base ? base : '') + '/uploads/notif/' + name; } catch (e) { return ''; } } async function sendText(phone, text) { try { const whatsappService = require('./whatsappService'); await whatsappService.sendWhatsAppMessage(phone, spx(text)); return true; } catch (e) { return false; } } async function sendMetaImage(phone, buffer, caption) { try { const phoneId = getSetting('meta_phone_number_id', ''); const token = getSetting('meta_access_token', ''); if (!phoneId || !token) return await sendText(phone, caption || ''); const fd = new FormData(); fd.append('messaging_product', 'whatsapp'); fd.append('type', 'image/png'); fd.append('file', new Blob([buffer], { type: 'image/png' }), 'qris.png'); const up = await fetch('https://graph.facebook.com/v20.0/' + phoneId + '/media', { method: 'POST', headers: { Authorization: 'Bearer ' + token }, body: fd }); const upj = await up.json().catch(() => null); if (!up.ok || !upj || !upj.id) return await sendText(phone, caption || ''); let digits = String(phone).replace(/\D/g, ''); if (digits.startsWith('0')) digits = '62' + digits.slice(1); const send = await fetch('https://graph.facebook.com/v20.0/' + phoneId + '/messages', { method: 'POST', headers: { Authorization: 'Bearer ' + token, 'Content-Type': 'application/json' }, body: JSON.stringify({ messaging_product: 'whatsapp', to: digits, type: 'image', image: { id: upj.id, caption: caption || '' } }) }); return send.ok; } catch (e) { return false; } } async function sendImage(phone, imageBuffer, caption) { const gw = gateway(); caption = spx(caption || ''); try { if (gw === 'gowa') { await require('./gowaWhatsappService').sendGowaImage(phone, imageBuffer, caption || ''); return true; } if (gw === 'baileys') { const mod = await import('./whatsappBot.mjs'); return await mod.sendWAImage(phone, imageBuffer, caption || ''); } if (gw === 'fonnte') { const url = saveTemp(imageBuffer, 'png'); if (!url) return await sendText(phone, caption || ''); const fonnte = require('./fonnteWhatsappService'); await fonnte.sendFonnteMessage(phone, caption || '', { url: url, filename: 'qris.png' }); return true; } if (gw === 'meta') return await sendMetaImage(phone, imageBuffer, caption); return await sendText(phone, caption || ''); } catch (e) { return false; } } async function sendDocument(phone, docBuffer, filename, caption, mimetype) { const gw = gateway(); const fn = filename || 'file.pdf'; caption = spx(caption || ''); try { if (gw === 'gowa') { await require('./gowaWhatsappService').sendGowaDocument(phone, docBuffer, fn, caption || '', mimetype || 'application/pdf'); return true; } if (gw === 'baileys') { const mod = await import('./whatsappBot.mjs'); return await mod.sendWADocument(phone, docBuffer, fn, caption || '', mimetype || 'application/pdf'); } if (gw === 'fonnte') { const ext = (fn.split('.').pop() || 'bin'); const url = saveTemp(docBuffer, ext); if (!url) return await sendText(phone, caption || ''); const fonnte = require('./fonnteWhatsappService'); await fonnte.sendFonnteMessage(phone, caption || '', { url: url, filename: fn }); return true; } // meta + lain: fallback teks return await sendText(phone, caption || ''); } catch (e) { return false; } } function normalizeNum(p) { let d = String(p || '').replace(/\D/g, ''); if (!d) return ''; if (d.startsWith('0')) d = '62' + d.slice(1); return d.length >= 8 ? d : ''; } async function sendToStaff(text) { const set = new Set(); try { const admins = getSetting('whatsapp_admin_numbers', []); (Array.isArray(admins) ? admins : String(admins || '').split(',')).forEach((p) => { const n = normalizeNum(p); if (n) set.add(n); }); } catch (e) {} try { const techs = require('./techService').getAllTechnicians() || []; techs.filter((t) => t.is_active === 1 && t.phone).forEach((t) => { const n = normalizeNum(t.phone); if (n) set.add(n); }); } catch (e) {} let sent = 0; for (const p of set) { if (await sendText(p, text)) sent++; } return { sent: sent, total: set.size }; } let _connCache = { at: 0, val: false }; async function isConnected() { const now = Date.now(); if (now - _connCache.at < 5000) return _connCache.val; const gw = gateway(); let val = false; try { if (gw === 'gowa') { const st = await require('./gowaWhatsappService').gowaStatus(); const d = (st && st.data && st.data.results) || (st && st.data) || {}; val = !!(d.is_connected || d.is_logged_in || d.connected); } else if (gw === 'baileys') { const mod = await import('./whatsappBot.mjs'); const s = mod.whatsappStatus; val = !!(s && s.connection === 'open'); } else { val = true; } } catch (e) { val = false; } _connCache = { at: now, val: val }; return val; } // Notifikasi untuk ADMIN/monitoring → Telegram (utama). WA (sendToStaff) sebagai fallback. async function notifyAdmin(text) { try { const tg = require('./telegramBot'); if (tg && typeof tg.notifyAdmin === 'function') { const ok = await tg.notifyAdmin(text); if (ok) return true; } } catch (e) {} return sendToStaff(text); } module.exports = { sendText, sendImage, sendDocument, sendToStaff, notifyAdmin, isConnected, gateway };