feat: KöPi Tages-Cron um 06:00 Uhr mit Pushover-Benachrichtigung bei Angebotsänderung
This commit is contained in:
@@ -256,6 +256,7 @@ server.listen(4000, () => console.log('🚀 Dicken Dock läuft auf Port 4000'));
|
|||||||
|
|
||||||
// ── Push-Zeitplaner Hintergrund-Job ──────────────────────────────────────────
|
// ── Push-Zeitplaner Hintergrund-Job ──────────────────────────────────────────
|
||||||
const db = require('./db');
|
const db = require('./db');
|
||||||
|
const koepiRoutes = require('./tools/koepi/routes');
|
||||||
|
|
||||||
async function sendPushoverMsg(userKey, appToken, message, opts = {}) {
|
async function sendPushoverMsg(userKey, appToken, message, opts = {}) {
|
||||||
try {
|
try {
|
||||||
@@ -296,6 +297,20 @@ async function runScheduler() {
|
|||||||
console.log(`✓ Push gesendet an user ${row.user_id}: "${row.message}" (fällig: ${row.scheduled_at})`);
|
console.log(`✓ Push gesendet an user ${row.user_id}: "${row.message}" (fällig: ${row.scheduled_at})`);
|
||||||
}
|
}
|
||||||
} catch(e) { console.error('Push-Job Fehler:', e.message); }
|
} catch(e) { console.error('Push-Job Fehler:', e.message); }
|
||||||
|
|
||||||
|
// KöPi Tages-Check: täglich um 06:00 (localtime), Duplikatschutz über admin_settings-Datum
|
||||||
|
try {
|
||||||
|
const now = db.prepare("SELECT datetime('now','localtime') as t").get().t;
|
||||||
|
const date = now.slice(0, 10);
|
||||||
|
const time = now.slice(11, 16);
|
||||||
|
if (time === '06:00') {
|
||||||
|
const lastRunDate = db.prepare("SELECT value FROM admin_settings WHERE key='koepi_cron_last_date'").get()?.value;
|
||||||
|
if (lastRunDate !== date) {
|
||||||
|
db.prepare("INSERT OR REPLACE INTO admin_settings (key, value) VALUES ('koepi_cron_last_date', ?)").run(date);
|
||||||
|
await koepiRoutes.runDailyCheck();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch(e) { console.error('KöPi-Cron Fehler:', e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log(`⏰ Push-Scheduler aktiv – nächste Prüfung in ${Math.round(msToNextMinute/1000)}s`);
|
console.log(`⏰ Push-Scheduler aktiv – nächste Prüfung in ${Math.round(msToNextMinute/1000)}s`);
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
const https = require('https');
|
const https = require('https');
|
||||||
|
const db = require('../../db');
|
||||||
const { authenticate } = require('../../middleware/auth');
|
const { authenticate } = require('../../middleware/auth');
|
||||||
const router = express.Router();
|
const router = express.Router();
|
||||||
|
|
||||||
@@ -40,10 +41,12 @@ function getAddress(retailer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// ── Angebote via Puppeteer von marktguru ─────────────────────────────────────
|
// ── Angebote via Puppeteer von marktguru ─────────────────────────────────────
|
||||||
async function scrapeMarktguru() {
|
async function scrapeMarktguru(forceRefresh = false) {
|
||||||
const cacheKey = 'koepi:mg';
|
const cacheKey = 'koepi:mg';
|
||||||
const cached = cacheGet(cacheKey);
|
if (!forceRefresh) {
|
||||||
if (cached) return cached;
|
const cached = cacheGet(cacheKey);
|
||||||
|
if (cached) return cached;
|
||||||
|
}
|
||||||
|
|
||||||
let puppeteer;
|
let puppeteer;
|
||||||
try { puppeteer = require('puppeteer-core'); }
|
try { puppeteer = require('puppeteer-core'); }
|
||||||
@@ -256,6 +259,69 @@ router.get('/img', authenticate, (req, res) => {
|
|||||||
} catch { res.status(400).end(); }
|
} 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 ────────────────────────────────────────────────────────────────────
|
// ── Routes ────────────────────────────────────────────────────────────────────
|
||||||
router.get('/offers', authenticate, async (req, res) => {
|
router.get('/offers', authenticate, async (req, res) => {
|
||||||
try { res.json(await scrapeMarktguru()); }
|
try { res.json(await scrapeMarktguru()); }
|
||||||
@@ -274,3 +340,4 @@ router.post('/clear-cache', authenticate, (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
module.exports = router;
|
module.exports = router;
|
||||||
|
module.exports.runDailyCheck = runDailyCheck;
|
||||||
|
|||||||
Reference in New Issue
Block a user