const { findDienst, getPngFilePaths } = require('./dienstService') const { sendTextMessage, sendImageMessage, notifyAdmin, sendChatSeen } = require('./whatsappApi') const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter') const { increment: incrementDaily } = require('./dailyStats') const ALLOWED_GROUP = process.env.ALLOWED_GROUP const ALLOWED_GROUP_NAMES = (process.env.ALLOWED_GROUP_NAME || '') .split(',') .map(name => name.trim()) .filter(Boolean) const ALLOWED_CHAT_IDS = new Set([ALLOWED_GROUP, ...ALLOWED_GROUP_NAMES].filter(Boolean)) // WuzAPI-webhook-payload-mapping. WuzAPI stuurt (WEBHOOK_FORMAT=json) het volledig // whatsmeow-event als { "event": {...}, "type": "Message", ... }. De relevante // velden (uit whatsmeow-types): event.Info.Chat/Sender = {User, Server}, // event.Info.IsFromMe (bool), event.Info.ID, event.Message.conversation. function jidFromObject(jid) { // whatsmeow-JID serialiseert als {User, RawAgent, Device, Integrator, Server} if (!jid || typeof jid !== 'object') return String(jid || '') const server = jid.Server || 's.whatsapp.net' const device = jid.Device ? `:${jid.Device}` : '' return `${jid.User}${device}@${server}` } function normalizeChatId(chatId) { return (chatId || '') .replace(/^\u200e/, '') .replace(/\s*\(\d+ berichten?\)$/, '') .trim() } function extractDienstnummer(body) { const match = body.match(/^\s*(\d{4,5})\s*$/) return match ? match[1] : null } async function handleWebhook(payload) { if (payload.type !== 'Message') return const event = payload.event if (!event || !event.Info) return const chatId = jidFromObject(event.Info.Chat) const author = jidFromObject(event.Info.Sender) if (event.Info.IsFromMe) return if (!ALLOWED_CHAT_IDS.has(normalizeChatId(chatId))) return const messageId = event.Info.ID // Tekst: conversation of extendedTextMessage.text; protobuf-veldnamen kunnen // zowel lowerCamel (protojson) als Go-vorm (Conversation) zijn — beide afdekken. const msg = event.Message || {} const body = msg.conversation || msg.Conversation || msg.extendedTextMessage?.text || msg.ExtendedTextMessage?.Text || '' const dienstnummer = extractDienstnummer(body) if (!dienstnummer) return incrementDaily() if (messageId) { sendChatSeen(chatId, messageId).catch(err => console.error('Failed to mark chat as seen:', err.message) ) } console.log(`Processing dienst ${dienstnummer} for ${author}`) if (!author) return const limit = checkLimit(author) if (!limit.allowed) { await sendTextMessage(author, `Je hebt vandaag het maximum van ${limit.max} diensten bereikt. Probeer het morgen opnieuw.`) console.log(`Rate limit reached for ${author} (${limit.count}/${limit.max})`) return } increment(author) const dienst = findDienst(dienstnummer) if (!dienst) { await sendTextMessage(author, `Dienstnummer ${dienstnummer} niet gevonden.`) return } // Media-pacing voor opeenvolgende sends gebeurt bridge-breed (wwebjs-api // serialiseert + throttle de media-sends; MEDIA_SEND_DELAY_MS in bridge-.env). const pngPaths = getPngFilePaths(dienst) if (!pngPaths || pngPaths.length === 0) { await sendTextMessage(author, `Geen pagina's gevonden voor dienst ${dienstnummer}.`) return } for (let i = 0; i < pngPaths.length; i++) { const pngPath = pngPaths[i] const filename = pngPath.split('/').pop() try { await sendImageMessage(author, pngPath, filename) } catch (err) { const errorMsg = `Fout bij verzenden ${filename} naar ${author}: ${err.message}` console.error(errorMsg) await notifyAdmin(errorMsg) } } } module.exports = { handleWebhook, extractDienstnummer }