feat: KöPi Tages-Cron um 06:00 Uhr mit Pushover-Benachrichtigung bei Angebotsänderung

This commit is contained in:
2026-07-10 13:26:33 +02:00
parent 940bf3f103
commit 1900d73490
2 changed files with 85 additions and 3 deletions

View File

@@ -1,5 +1,6 @@
const express = require('express');
const https = require('https');
const db = require('../../db');
const { authenticate } = require('../../middleware/auth');
const router = express.Router();
@@ -40,10 +41,12 @@ function getAddress(retailer) {
}
// ── Angebote via Puppeteer von marktguru ─────────────────────────────────────
async function scrapeMarktguru() {
async function scrapeMarktguru(forceRefresh = false) {
const cacheKey = 'koepi:mg';
const cached = cacheGet(cacheKey);
if (cached) return cached;
if (!forceRefresh) {
const cached = cacheGet(cacheKey);
if (cached) return cached;
}
let puppeteer;
try { puppeteer = require('puppeteer-core'); }
@@ -256,6 +259,69 @@ router.get('/img', authenticate, (req, res) => {
} catch { res.status(400).end(); }
});
// ── Tages-Cron: neue/geänderte Angebote per Pushover melden ─────────────────
const KOEPI_SETTINGS_KEY = 'koepi_last_offers';
// Vergleichbare, stabile Signatur eines Angebots (Reihenfolge-unabhängig)
function offerSignature(offers) {
return offers
.map(o => `${(o.retailer || '').toLowerCase().trim()}|${(o.price || '').trim()}|${(o.validity || o.dateRange || '').trim()}`)
.sort()
.join('\n');
}
function formatOfferMessage(offers) {
if (!offers.length) return 'Aktuell keine König Pilsener Angebote gefunden.';
return offers
.map(o => `${o.retailer || '?'}: ${o.price || '?'} (${o.validity || o.dateRange || 'Gültigkeit unbekannt'})`)
.join('\n');
}
async function sendKoepiPushover(offers) {
const recipients = db.prepare(`
SELECT user_key, app_token, retry, expire FROM pushover_settings
WHERE user_key IS NOT NULL AND app_token IS NOT NULL
`).all();
const message = formatOfferMessage(offers);
for (const r of recipients) {
try {
const params = {
token: r.app_token,
user: r.user_key,
title: '🍺 KöPi Angebote',
message,
};
if (r.retry && r.expire) { params.priority = 2; params.retry = r.retry; params.expire = r.expire; }
await fetch('https://api.pushover.net/1/messages.json', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams(params),
});
} catch (e) { console.error('KöPi Pushover Fehler:', e.message); }
}
}
// Wird täglich um 06:00 vom Scheduler in index.js aufgerufen
async function runDailyCheck() {
try {
const { offers } = await scrapeMarktguru(true);
const currentSig = offerSignature(offers);
const lastSig = db.prepare('SELECT value FROM admin_settings WHERE key=?').get(KOEPI_SETTINGS_KEY)?.value || '';
if (currentSig === lastSig) {
console.log('🍺 KöPi-Check: keine Änderungen an den Angeboten.');
return;
}
await sendKoepiPushover(offers);
db.prepare('INSERT OR REPLACE INTO admin_settings (key, value) VALUES (?, ?)')
.run(KOEPI_SETTINGS_KEY, currentSig);
console.log(`🍺 KöPi-Check: ${offers.length} Angebot(e) geändert Pushover versendet.`);
} catch (e) {
console.error('KöPi Tages-Check Fehler:', e.message);
}
}
// ── Routes ────────────────────────────────────────────────────────────────────
router.get('/offers', authenticate, async (req, res) => {
try { res.json(await scrapeMarktguru()); }
@@ -274,3 +340,4 @@ router.post('/clear-cache', authenticate, (req, res) => {
});
module.exports = router;
module.exports.runDailyCheck = runDailyCheck;