diff --git a/README.md b/README.md index 23ab96a..91b3b16 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ > De feature ratelimiting is gemaakt door middel van vibecoding met [OpenCode 1.14.40](https://opencode.ai) en Deepseek v4 Pro -WhatsApp bot die dienstschema's (PNG) verstuurt op basis van een dienstnummer in een WhatsApp groep, en dagelijks een samenvatting stuurt. +WhatsApp bot die dienstschema's (PNG) verstuurt op basis van een dienstnummer in een WhatsApp groep. ## Hoe het werkt @@ -114,10 +114,9 @@ npx pm2 logs dienst-bot ``` src/ - index.js # Express webhook server + cron job + index.js # Express webhook server messageHandler.js # Berichtverwerking en filtering rateLimiter.js # Spam protectie (max 5 per dag) - dailyStats.js # Dagelijkse aanvraag-teller dienstService.js # JSON lookup en PNG bestandspaden whatsappApi.js # WhatsApp API client (bericht + afbeelding) ``` @@ -132,16 +131,6 @@ src/ 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). -## Dagelijkse samenvatting - -Elke dag om **10:00** stuurt de bot een WhatsApp-bericht naar `ADMIN_NUMBER` met het totaal aantal dienstaanvragen van de vorige dag (00:00 t/m 23:59). - -``` -Aantal dienstaanvragen gisteren: N -``` - -De teller wordt bijgehouden in memory door `dailyStats.js` en gereset na verzending van de samenvatting. De scheduling gebruikt `node-cron` (`0 10 * * *`). - ## Licentie Privé project — niet voor distributie. diff --git a/docs/superpowers/plans/2025-05-12-daily-summary.md b/docs/superpowers/plans/2025-05-12-daily-summary.md deleted file mode 100644 index 3c4c5a7..0000000 --- a/docs/superpowers/plans/2025-05-12-daily-summary.md +++ /dev/null @@ -1,238 +0,0 @@ -# Dagelijkse WhatsApp samenvatting — Implementation Plan - -> **Voor agentic workers:** REQUIRED SUB-SKILL: Gebruik superpowers:subagent-driven-development (aanbevolen) of superpowers:executing-plans om dit plan taak-voor-taak te implementeren. Stappen gebruiken checkbox (`- [ ]`) syntax voor tracking. - -**Goal:** Elke dag om 10:00 een WhatsApp-bericht naar de admin sturen met het totaal aantal dienstaanvragen van de vorige dag. - -**Architecture:** Nieuwe module `dailyStats.js` houdt een dagtotaal bij in memory. `messageHandler.js` roept increment aan bij elke aanvraag. `node-cron` triggert om 10:00 de reset en verzending via bestaande `whatsappApi.sendTextMessage()`. - -**Tech Stack:** Node.js, node-cron (nieuwe dependency), bestaande axios/express stack - -**Files:** -- Create: `src/dailyStats.js` -- Modify: `src/messageHandler.js` -- Modify: `src/index.js` -- Modify: `package.json` - ---- - -### Task 1: Create dailyStats module - -**Files:** -- Create: `src/dailyStats.js` - -- [ ] **Step 1: Write the dailyStats module** - -```javascript -let dailyTotal = 0 -let yesterdayTotal = 0 - -function increment() { - dailyTotal++ -} - -function getYesterdayTotal() { - return yesterdayTotal -} - -function resetDay() { - yesterdayTotal = dailyTotal - dailyTotal = 0 -} - -module.exports = { increment, getYesterdayTotal, resetDay } -``` - -- [ ] **Step 2: Verify the module loads without errors** - -Run: `node -e "const m = require('./src/dailyStats'); m.increment(); m.increment(); m.resetDay(); console.log(m.getYesterdayTotal() === 2 ? 'OK' : 'FAIL')"` -Expected: `OK` - -- [ ] **Step 3: Commit** - -```bash -git add src/dailyStats.js -git commit -m "feat: add dailyStats module for tracking daily request totals" -``` - ---- - -### Task 2: Integrate dailyStats into messageHandler - -**Files:** -- Modify: `src/messageHandler.js` - -- [ ] **Step 1: Add dailyStats import and increment call** - -`src/messageHandler.js` line 1-4, add import: - -```javascript -const { findDienst, getPngFilePaths } = require('./dienstService') -const { sendTextMessage, sendImageMessage, notifyAdmin } = require('./whatsappApi') -const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter') -const { increment: incrementDaily } = require('./dailyStats') -``` - -`src/messageHandler.js` after `increment(author)` (line 51), add: - -```javascript - increment(author) - incrementDaily() -``` - -- [ ] **Step 2: Verify messageHandler still loads** - -Run: `node -e "require('./src/messageHandler'); console.log('OK')"` -Expected: `OK` (may show dotenv warning, that's fine) - -- [ ] **Step 3: Commit** - -```bash -git add src/messageHandler.js -git commit -m "feat: integrate dailyStats counter into message handler" -``` - ---- - -### Task 3: Add node-cron and schedule daily 10:00 summary - -**Files:** -- Modify: `package.json` -- Modify: `src/index.js` - -- [ ] **Step 1: Install node-cron** - -Run: `npm install node-cron` -Expected: node-cron added to `node_modules` and `package.json` - -- [ ] **Step 2: Add cron job to index.js** - -`src/index.js` — replace the entire file: - -```javascript -require('dotenv').config() -const cron = require('node-cron') -const express = require('express') -const { handleWebhook } = require('./messageHandler') -const { loadDiensten } = require('./dienstService') -const { resetDay, getYesterdayTotal } = require('./dailyStats') -const { sendTextMessage } = require('./whatsappApi') - -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() - -cron.schedule('0 10 * * *', () => { - const adminNumber = process.env.ADMIN_NUMBER - if (!adminNumber) { - console.error('ADMIN_NUMBER not configured, skipping daily summary') - return - } - - resetDay() - const total = getYesterdayTotal() - - sendTextMessage(adminNumber, `Aantal dienstaanvragen gisteren: ${total}`) - .then(() => console.log(`Daily summary sent: ${total} requests`)) - .catch(err => console.error('Failed to send daily summary:', err.message)) -}) - -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}`) - console.log('Daily summary cron job scheduled for 10:00') -}) -``` - -- [ ] **Step 3: Verify server starts without errors** - -Run: `timeout 3 node src/index.js || true` -Expected: Output shows `dienst-bot listening on port ...` and `Daily summary cron job scheduled for 10:00` - -- [ ] **Step 4: Test cron callback manually** - -Run: `node -e " -require('dotenv').config() -const { increment, resetDay, getYesterdayTotal } = require('./src/dailyStats') -increment() -increment() -increment() -resetDay() -console.log('Total:', getYesterdayTotal()) -console.log(getYesterdayTotal() === 3 ? 'PASS' : 'FAIL') -"` -Expected: `Total: 3` followed by `PASS` - -- [ ] **Step 5: Commit** - -```bash -git add package.json package-lock.json src/index.js -git commit -m "feat: add node-cron daily summary job at 10:00" -``` - ---- - -### Task 4: Verify end-to-end flow - -**Files:** (none, verification only) - -- [ ] **Step 1: Start the server** - -Run: `node src/index.js &` -Wait 2 seconds for startup. - -- [ ] **Step 2: Simulate a webhook request to increment the counter** - -Run: -```bash -curl -s -X POST http://localhost:3002/webhook \ - -H 'Content-Type: application/json' \ - -d '{"dataType":"message","sessionId":"EdGpt","data":{"message":{"from":"120363407356808413@g.us","fromMe":false,"author":"test@c.us","body":"@EdGpt 0001","_data":{"mentionedJidList":["bot@c.us"]}}}}}' -``` -Expected: `{"success":true}` - -- [ ] **Step 3: Trigger the cron callback directly to verify summary message** - -Run: -```bash -node -e " -require('dotenv').config() -const { resetDay, getYesterdayTotal } = require('./src/dailyStats') -const { sendTextMessage } = require('./src/whatsappApi') -resetDay() -const total = getYesterdayTotal() -console.log('Would send to admin:', process.env.ADMIN_NUMBER) -console.log('Message:', 'Aantal dienstaanvragen gisteren: ' + total) -console.log(total > 0 ? 'PASS' : 'FAIL - expected >0') -" -``` -Expected: `PASS` (shows message with total > 0) - -- [ ] **Step 4: Stop the server** - -Run: `kill %1 2>/dev/null; true` - -- [ ] **Step 5: Commit any remaining changes** - -```bash -git status -# Only commit if there are remaining changes -``` \ No newline at end of file diff --git a/docs/superpowers/plans/2026-05-11-spam-protection.md b/docs/superpowers/plans/2026-05-11-spam-protection.md deleted file mode 100644 index b25c1a3..0000000 --- a/docs/superpowers/plans/2026-05-11-spam-protection.md +++ /dev/null @@ -1,174 +0,0 @@ -# Spam Protection Implementation Plan - -> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. - -**Goal:** Limit each user to max 5 dienst requests per day, with automatic daily reset. - -**Architecture:** In-memory rate limiter module that tracks request counts per user per day. Integrated into the message handler pipeline before dienst lookup. Sends a friendly warning when limit is exceeded. - -**Tech Stack:** Node.js, no new dependencies needed - ---- - -### Task 1: Create rate limiter module - -**Files:** -- Create: `src/rateLimiter.js` - -- [ ] **Step 1: Create `src/rateLimiter.js`** - -```js -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 } -``` - -- [ ] **Step 2: Commit** - -```bash -git add src/rateLimiter.js -git commit -m "feat: add rate limiter module" -``` - ---- - -### Task 2: Integrate rate limiter into message handler - -**Files:** -- Modify: `src/messageHandler.js:1,25-36` - -- [ ] **Step 1: Add rate limiter import at top of `src/messageHandler.js`** - -Add after line 2: - -```js -const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter') -``` - -- [ ] **Step 2: Add rate limit check in `handleWebhook` after dienstnummer extraction** - -Insert between the `dienstnummer` null check (line 36) and the `console.log` (line 38), replacing lines 38-40 with: - -```js - console.log(`Processing dienst ${dienstnummer} for ${message.author}`) - - 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) -``` - -- [ ] **Step 3: Verify the file looks correct** - -Run: `node -e "require('./src/messageHandler')"` (should not throw) - -- [ ] **Step 4: Commit** - -```bash -git add src/messageHandler.js -git commit -m "feat: integrate rate limiter into message handler" -``` - ---- - -### Task 3: Add config to .env and restart - -**Files:** -- Modify: `.env` -- Modify: `.env.example` - -- [ ] **Step 1: Add `MAX_REQUESTS_PER_DAY` to `.env`** - -Append to `.env`: - -``` -# Rate limiting -MAX_REQUESTS_PER_DAY=5 -``` - -- [ ] **Step 2: Add `MAX_REQUESTS_PER_DAY` to `.env.example`** - -Append to `.env.example`: - -``` -# Rate limiting -MAX_REQUESTS_PER_DAY=5 -``` - -- [ ] **Step 3: Restart bot and verify** - -```bash -npx pm2 restart dienst-bot && npx pm2 save -npx pm2 logs dienst-bot --lines 5 --nostream -``` - -Expected: bot starts without errors, logs show listening on port 3002. - -- [ ] **Step 4: Commit and push** - -```bash -git add .env.example -git commit -m "feat: add rate limiting config option" -git push -``` - ---- - -### Task 4: Update README - -**Files:** -- Modify: `README.md` - -- [ ] **Step 1: Add rate limiting section to README.md** - -Add after the "Foutafhandeling" section: - -```markdown -## 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). -``` - -- [ ] **Step 2: Commit and push** - -```bash -git add README.md -git commit -m "docs: add spam protection section to README" -git push -``` diff --git a/docs/superpowers/specs/2025-05-12-daily-summary-design.md b/docs/superpowers/specs/2025-05-12-daily-summary-design.md deleted file mode 100644 index f28efd3..0000000 --- a/docs/superpowers/specs/2025-05-12-daily-summary-design.md +++ /dev/null @@ -1,96 +0,0 @@ -# Dagelijkse WhatsApp samenvatting — Design - -## Doel - -Elke dag om 10:00 uur een WhatsApp-bericht sturen naar de admin (31620248011) met het totaal aantal dienstaanvragen van de vorige dag (00:00 t/m 23:59). - -## Requirement samenvatting - -| Item | Keuze | -|------|-------| -| Inhoud bericht | Alleen totaal aantal | -| Periode | Vorige kalenderdag (00:00-23:59) | -| Persistentie | In-memory (geen disk) | -| Verzendtijd | 10:00 elke ochtend | -| Ontvanger | `ADMIN_NUMBER` uit `.env` (31620248011@c.us) | - -## Architectuur - -### Nieuwe module: `src/dailyStats.js` - -Houdt twee tellers bij in memory: - -- `dailyTotal` — aantal aanvragen vandaag (lopende dag) -- `yesterdayTotal` — aantal aanvragen van gisteren (voor verzending om 10:00) - -Publieke API: - -- `increment()` — verhoogt `dailyTotal` met 1 -- `getYesterdayTotal()` — returned `yesterdayTotal` -- `resetDay()` — zet `yesterdayTotal = dailyTotal`, dan `dailyTotal = 0` - -### Scheduling: `node-cron` - -Cron expressie `0 10 * * *` triggert exact om 10:00 elke dag een callback die: - -1. `dailyStats.resetDay()` aanroept (snapshot van gisteren) -2. `sendTextMessage(adminNumber, bericht)` aanroept om het totaal te versturen -3. Bij falen van verzending: error loggen, maar reset blijft van kracht - -### Bestaande modules — wijzigingen - -**`src/index.js`** -- Importeert `node-cron` en `dailyStats` -- Registreert cron job bij server startup -- Huidige `express` setup blijft ongewijzigd - -**`src/messageHandler.js`** -- Importeert `dailyStats` -- Roept `dailyStats.increment()` aan na de bestaande `increment(author)` (rate limiter) - -### Data flow - -``` -webhook POST /webhook - → messageHandler.handleWebhook() - → rateLimiter.increment(author) [bestaand] - → dailyStats.increment() [nieuw, 1 regel] - -om 10:00 (node-cron trigger): - → dailyStats.resetDay() - → whatsappApi.sendTextMessage(admin, "Aantal dienstaanvragen gisteren: N") -``` - -### Foutafhandeling - -- Verzending faalt → error wordt gelogd, geen retry, reset is al gedaan -- Server herstart voor 10:00 → `dailyTotal` en `yesterdayTotal` zijn 0, cron start opnieuw, om 10:00 wordt bericht met "0" verstuurd (verlies van telling geaccepteerd conform in-memory keuze) -- Server draait niet om 10:00 → geen bericht die dag - -### Afhankelijkheden - -- `node-cron` toevoegen aan `package.json` -- Geen nieuwe `.env` variabelen — `ADMIN_NUMBER` wordt hergebruikt - -### Berichtformaat - -``` -Aantal dienstaanvragen gisteren: N -``` - -Waar `N` het totaal aantal is van 00:00 tot 23:59 van de vorige kalenderdag. - -## Teststrategie - -1. **`dailyStats` unit tests** - - `increment()` verhoogt `dailyTotal` correct - - `resetDay()` kopieert `dailyTotal` naar `yesterdayTotal` en zet `dailyTotal` op 0 - - `getYesterdayTotal()` returned gecachte waarde - -2. **Integratietest** - - Cron callback handmatig aanroepen → verifieer dat `sendTextMessage` wordt aangeroepen met juiste admin nummer en bericht - -## Scope & grenzen - -- Binnen scope: teller bijhouden, cron trigger, WhatsApp verzending -- Buiten scope: persistentie op disk, uitsplitsingen per gebruiker/dienst, herstel van gemiste data na crash, historische rapportages \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index aa4225f..8698c05 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,8 +10,7 @@ "dependencies": { "axios": "^1.6.0", "dotenv": "^16.4.0", - "express": "^4.18.0", - "node-cron": "^4.2.1" + "express": "^4.18.0" } }, "node_modules/accepts": { @@ -659,15 +658,6 @@ "node": ">= 0.6" } }, - "node_modules/node-cron": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/node-cron/-/node-cron-4.2.1.tgz", - "integrity": "sha512-lgimEHPE/QDgFlywTd8yTR61ptugX3Qer29efeyWw2rv259HtGBNn1vZVmp8lB9uo9wC0t/AT4iGqXxia+CJFg==", - "license": "ISC", - "engines": { - "node": ">=6.0.0" - } - }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", diff --git a/package.json b/package.json index f99a483..1e07eb6 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,6 @@ "dependencies": { "axios": "^1.6.0", "dotenv": "^16.4.0", - "express": "^4.18.0", - "node-cron": "^4.2.1" + "express": "^4.18.0" } } diff --git a/src/dailyStats.js b/src/dailyStats.js deleted file mode 100644 index 0e4100c..0000000 --- a/src/dailyStats.js +++ /dev/null @@ -1,17 +0,0 @@ -let dailyTotal = 0 -let yesterdayTotal = 0 - -function increment() { - dailyTotal++ -} - -function getYesterdayTotal() { - return yesterdayTotal -} - -function resetDay() { - yesterdayTotal = dailyTotal - dailyTotal = 0 -} - -module.exports = { increment, getYesterdayTotal, resetDay } \ No newline at end of file diff --git a/src/index.js b/src/index.js index a8d9901..8a9c0c7 100644 --- a/src/index.js +++ b/src/index.js @@ -1,10 +1,7 @@ require('dotenv').config() -const cron = require('node-cron') const express = require('express') const { handleWebhook } = require('./messageHandler') const { loadDiensten } = require('./dienstService') -const { resetDay, getYesterdayTotal } = require('./dailyStats') -const { sendTextMessage } = require('./whatsappApi') const app = express() app.use(express.json({ limit: '10mb' })) @@ -27,24 +24,8 @@ const PORT = process.env.WEBHOOK_PORT || 3002 loadDiensten() -cron.schedule('0 10 * * *', () => { - const adminNumber = process.env.ADMIN_NUMBER - if (!adminNumber) { - console.error('ADMIN_NUMBER not configured, skipping daily summary') - return - } - - resetDay() - const total = getYesterdayTotal() - - sendTextMessage(adminNumber, `Aantal dienstaanvragen gisteren: ${total}`) - .then(() => console.log(`Daily summary sent: ${total} requests`)) - .catch(err => console.error('Failed to send daily summary:', err.message)) -}) - 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}`) - console.log('Daily summary cron job scheduled for 10:00') -}) \ No newline at end of file +}) diff --git a/src/messageHandler.js b/src/messageHandler.js index e15631f..789a84b 100644 --- a/src/messageHandler.js +++ b/src/messageHandler.js @@ -1,7 +1,6 @@ const { findDienst, getPngFilePaths } = require('./dienstService') const { sendTextMessage, sendImageMessage, notifyAdmin } = require('./whatsappApi') const { checkLimit, increment, MAX_REQUESTS_PER_DAY } = require('./rateLimiter') -const { increment: incrementDaily } = require('./dailyStats') const BOT_NAME = (process.env.BOT_NAME || 'EdGpt').toLowerCase() const ALLOWED_GROUP = process.env.ALLOWED_GROUP @@ -50,7 +49,6 @@ async function handleWebhook(payload) { } increment(author) - incrementDaily() const dienst = findDienst(dienstnummer) if (!dienst) {