feat: add core modules - dienstService, whatsappApi, messageHandler, webhook server

This commit is contained in:
Eddy 2026-05-11 18:52:50 +02:00
commit 3e6b33f103
4 changed files with 163 additions and 0 deletions

24
src/dienstService.js Normal file
View file

@ -0,0 +1,24 @@
const fs = require('fs')
const path = require('path')
let dienstenCache = null
function loadDiensten() {
if (dienstenCache) return dienstenCache
const jsonPath = process.env.DIENSTEN_JSON
const raw = fs.readFileSync(jsonPath, 'utf-8')
dienstenCache = JSON.parse(raw)
return dienstenCache
}
function findDienst(dienstnummer) {
const diensten = loadDiensten()
return diensten.find(d => d.dienstnummer === dienstnummer) || null
}
function getPngFilePaths(dienst) {
const pngDir = process.env.DIENSTEN_PNG
return dienst.paginas.map(p => path.join(pngDir, p.bestandsnaam))
}
module.exports = { loadDiensten, findDienst, getPngFilePaths }

31
src/index.js Normal file
View file

@ -0,0 +1,31 @@
require('dotenv').config()
const express = require('express')
const { handleWebhook } = require('./messageHandler')
const { loadDiensten } = require('./dienstService')
const app = express()
app.use(express.json({ limit: '10mb' }))
app.post('/webhook', async (req, res) => {
try {
await handleWebhook(req.body)
res.json({ success: true })
} catch (err) {
console.error('Webhook processing error:', err.message)
res.status(500).json({ success: false, error: err.message })
}
})
app.get('/health', (req, res) => {
res.json({ status: 'ok' })
})
const PORT = process.env.WEBHOOK_PORT || 3002
loadDiensten()
app.listen(PORT, () => {
console.log(`dienst-bot listening on port ${PORT}`)
console.log(`Allowed group: ${process.env.ALLOWED_GROUP}`)
console.log(`Bot name: ${process.env.BOT_NAME}`)
})

62
src/messageHandler.js Normal file
View file

@ -0,0 +1,62 @@
const { findDienst, getPngFilePaths } = require('./dienstService')
const { sendTextMessage, sendImageMessage, notifyAdmin } = require('./whatsappApi')
const BOT_NAME = (process.env.BOT_NAME || 'EdGpt').toLowerCase()
const ALLOWED_GROUP = process.env.ALLOWED_GROUP
function extractDienstnummer(body) {
const match = body.match(/\b(\d{4})\b/)
return match ? match[1] : null
}
function isBotMentioned(message) {
if (message._data?.mentionedJidList?.length > 0) {
return true
}
if (message.body && message.body.toLowerCase().includes(`@${BOT_NAME}`)) {
return true
}
return false
}
async function handleWebhook(payload) {
if (payload.dataType !== 'message') return
if (payload.sessionId !== process.env.WHATSAPP_SESSION) return
const message = payload.data?.message
if (!message) return
if (message.fromMe) return
if (message.from !== ALLOWED_GROUP) return
if (!isBotMentioned(message)) return
const dienstnummer = extractDienstnummer(message.body || '')
if (!dienstnummer) return
const author = message.author
if (!author) return
const dienst = findDienst(dienstnummer)
if (!dienst) {
await sendTextMessage(author, `Dienstnummer ${dienstnummer} niet gevonden.`)
return
}
const pngPaths = getPngFilePaths(dienst)
if (!pngPaths || pngPaths.length === 0) {
await sendTextMessage(author, `Geen pagina's gevonden voor dienst ${dienstnummer}.`)
return
}
for (const pngPath of pngPaths) {
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 }

46
src/whatsappApi.js Normal file
View file

@ -0,0 +1,46 @@
const axios = require('axios')
const fs = require('fs')
const API_BASE = process.env.WHATSAPP_API_ENDPOINT
const SESSION = process.env.WHATSAPP_SESSION
async function sendTextMessage(chatId, text) {
const url = `${API_BASE}/client/sendMessage/${SESSION}`
const response = await axios.post(url, {
chatId,
contentType: 'string',
content: text
})
return response.data
}
async function sendImageMessage(chatId, filePath, filename) {
const fileBuffer = fs.readFileSync(filePath)
const base64Data = fileBuffer.toString('base64')
const url = `${API_BASE}/client/sendMessage/${SESSION}`
const response = await axios.post(url, {
chatId,
contentType: 'MessageMedia',
content: {
mimetype: 'image/png',
data: base64Data,
filename: filename
}
})
return response.data
}
async function notifyAdmin(errorText) {
const adminNumber = process.env.ADMIN_NUMBER
if (!adminNumber) {
console.error('ADMIN_NUMBER not configured, skipping admin notification')
return
}
try {
await sendTextMessage(adminNumber, `[dienst-bot fout] ${errorText}`)
} catch (err) {
console.error('Failed to notify admin:', err.message)
}
}
module.exports = { sendTextMessage, sendImageMessage, notifyAdmin }