From 52c51fbf3ff25f5ebc163eaa7216d52fdb76fd4b Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 11 May 2026 20:02:16 +0200 Subject: [PATCH 1/4] feat: add rate limiter module --- src/rateLimiter.js | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/rateLimiter.js diff --git a/src/rateLimiter.js b/src/rateLimiter.js new file mode 100644 index 0000000..311dce4 --- /dev/null +++ b/src/rateLimiter.js @@ -0,0 +1,32 @@ +const MAX_REQUESTS_PER_DAY = parseInt(process.env.MAX_REQUESTS_PER_DAY, 10) || 5 + +const userCounts = new Map() +let currentDate = new Date().toDateString() + +function resetIfNeeded() { + const today = new Date().toDateString() + if (today !== currentDate) { + userCounts.clear() + currentDate = today + console.log('Rate limiter: daily counters reset') + } +} + +function checkLimit(userId) { + resetIfNeeded() + const count = userCounts.get(userId) || 0 + return { + allowed: count < MAX_REQUESTS_PER_DAY, + count, + max: MAX_REQUESTS_PER_DAY + } +} + +function increment(userId) { + resetIfNeeded() + const count = (userCounts.get(userId) || 0) + 1 + userCounts.set(userId, count) + return count +} + +module.exports = { checkLimit, increment, MAX_REQUESTS_PER_DAY } From 3d933c96a6611efd22390357ff2e801428092890 Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 11 May 2026 20:07:07 +0200 Subject: [PATCH 2/4] feat: integrate rate limiter into message handler --- src/messageHandler.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/messageHandler.js b/src/messageHandler.js index d57d9f7..789a84b 100644 --- a/src/messageHandler.js +++ b/src/messageHandler.js @@ -1,5 +1,6 @@ const { findDienst, getPngFilePaths } = require('./dienstService') const { sendTextMessage, sendImageMessage, notifyAdmin } = require('./whatsappApi') +const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter') const BOT_NAME = (process.env.BOT_NAME || 'EdGpt').toLowerCase() const ALLOWED_GROUP = process.env.ALLOWED_GROUP @@ -40,6 +41,15 @@ async function handleWebhook(payload) { const author = message.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.`) From 100b2af449d70cd4844a27ba28fe17f1cafdd544 Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 11 May 2026 20:10:42 +0200 Subject: [PATCH 3/4] feat: add rate limiting config option --- .env.example | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.env.example b/.env.example index 952158d..168e1aa 100644 --- a/.env.example +++ b/.env.example @@ -11,5 +11,8 @@ BOT_NAME=EdGpt DIENSTEN_JSON=./diensten/dienst-tijden.json DIENSTEN_PNG=./diensten/png +# Rate limiting +MAX_REQUESTS_PER_DAY=5 + # Admin notifications ADMIN_NUMBER=31612345678@c.us From 7176f09387518a08d216aa0e2d420d7269389da6 Mon Sep 17 00:00:00 2001 From: Eddy Date: Mon, 11 May 2026 20:11:45 +0200 Subject: [PATCH 4/4] docs: add spam protection section and rateLimiter.js to README --- README.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/README.md b/README.md index 15cf63d..d6f8366 100644 --- a/README.md +++ b/README.md @@ -114,6 +114,7 @@ npx pm2 logs dienst-bot src/ index.js # Express webhook server messageHandler.js # Berichtverwerking en filtering + rateLimiter.js # Spam protectie (max 5 per dag) dienstService.js # JSON lookup en PNG bestandspaden whatsappApi.js # WhatsApp API client (bericht + afbeelding) ``` @@ -124,6 +125,10 @@ src/ - **Fout bij verzenden PNG**: de admin krijgt een WhatsApp melding - **Webhook fout**: wordt gelogd in PM2 logs +## Spam protectie + +Elke gebruiker kan maximaal `MAX_REQUESTS_PER_DAY` (standaard 5) diensten per dag opvragen. Bij overschrijding krijgt de gebruiker een melding. De teller wordt dagelijks automatisch gereset (bij de eerste request na middernacht). + ## Licentie Privé project — niet voor distributie.