const weekdayMap={Montag:"Monday",Dienstag:"Tuesday",Mittwoch:"Wednesday",Donnerstag:"Thursday",Freitag:"Friday",Samstag:"Saturday",Sonntag:"Sunday"}; const configState = 'mqtt.0.foodsharing.pickupCheck.config.meik'; let scheduledJobs = []; // Zeitpläne aufräumen function clearAllScheduledJobs() { scheduledJobs.forEach(job => clearSchedule(job)); scheduledJobs = []; } // Neue Funktion: Config aus dem Datenpunkt parsen function parseConfigState() { try { const raw = getState(configState)?.val; if (!raw) throw new Error("Leerer Konfigurationswert"); const parsed = JSON.parse(raw); if (!Array.isArray(parsed)) throw new Error("Konfiguration ist kein Array"); return parsed; } catch (e) { console.error("Fehler beim Parsen der Konfiguration:", e.message); sendTelegramMessage(telegramInstance, `? Fehler beim Parsen der Pickup-Konfiguration: ${e.message}`); return []; } } // Pickup-Checks ausführen mit aktueller Konfiguration function setupPickupCheckSchedules() { clearAllScheduledJobs(); // Vorherige Tasks stoppen const pickupCheckConfig = parseConfigState(); pickupCheckConfig.forEach(({ id, active, onlyNotify, label, desiredWeekday, desiredDate, checkProfileId }) => { if (active) { const storeLabel = label || masterStores[id] || "Unbekannter Store"; const translatedWeekday = desiredWeekday && weekdayMap[desiredWeekday] ? weekdayMap[desiredWeekday] : desiredWeekday; const job = scheduleWithRandomDelay('*/10 7-22 * * *', () => handleLoginAndRequests( (storeIds, session) => { console.log(`Prüfung für ${storeLabel} (${id}) – Nur Benachrichtigung: ${onlyNotify}` + (translatedWeekday ? ` – nur an ${translatedWeekday}` : '') + ` – checkProfileId: ${checkProfileId}`); checkPickupData(id, !!checkProfileId, session, onlyNotify, translatedWeekday, desiredDate); }, null ) ); scheduledJobs.push(job); // Job merken } }); } // Zentrale Definition der Stores mit ID und sprechendem Namen const masterStores = { "51485": "Hornbach KA - Hagsfeld", "43694": "Blumen Deniz Rastatt", "32807": "Claus Reformwaren", "47510": "BIOLAND GÄRTNEREI LUTZ", "42141": "Restaurant Poké You", "37245": "Erdbeerland Enderle GBR", "35401": "Kalinka - Internationale Lebensmittel", "31080": "Gemüsebau Gabelmann", "28513": "denn's Biomarkt", // Weitere Stores, die nur in Pickup-Checks genutzt werden: "63448": "Penny Baden-Oos", "42264": "Bildungshaus St. Bernhard, Rastatt", "51450": "Hornbach KA - Grünwinkel", // Für diese StoreID gilt die TVS-Ausnahme "42322": "Edeka Fischer Haueneberstein", "33875": "Cap Markt", "44972": "Aldi Biblisweg", "44975": "Aldi Kuppenheim" }; // Extrahierte Store-IDs als Array für sendFoodSharingRequests const storeIds = [ //"51485", //Hornbach KA -Hagsfeld //"51450", //Hornbach KA-Grünwinkel "43694", //Blumen Deniz Rastatt "32807", //Claus Reformwaren "47510", //BIOLAND GÄRTNEREI LUTZ "42141", //Restaurant Poké You "37245", //Erdbeerland Enderle GBR "35401", //Kalinka - Internationale Lebensmittel "31080", //Gemüsebau Gabelmann "28513", //denn's Biomarkt //"29374", //Rastatter Wochenmarkt ]; // Anmeldedaten const userEmail = "ddda@gmx.de"; const userPassword = "a"; // Telegram-Bot-Instanz und Profil-ID (für Pickup-Prüfung) const telegramInstance = 'telegram.0'; const profileId = 839246; // Globale Variablen für Session und CSRF-Token let sessionCookies = null; let csrfToken = null; // Funktion zum Extrahieren des CSRF-Tokens aus den Cookies function extractCsrfToken(cookies) { let token = null; cookies.forEach(cookieStr => { if (cookieStr.includes("CSRF_TOKEN=")) { // Beispiel-Cookie: "CSRF_TOKEN=wert; Path=/; Domain=foodsharing.de; ..." token = cookieStr.split(';')[0].split('=')[1]; } }); return token; } // Funktion zum zufällig verzögerten Start des Skripts function startScriptWithRandomDelay(random = false) { const maxDelay = random ? 10 * 60 * 1000 : 1000; // bis zu 10 Minuten Verzögerung const delay = Math.floor(Math.random() * maxDelay); console.log(`Das Skript wird in ${Math.round(delay / 1000)} Sekunden gestartet.`); setTimeout(() => { // sendFoodSharingRequests nutzt hier die zentrale masterStores-Liste handleLoginAndRequests(sendFoodSharingRequests, storeIds); }, delay); } // Session-Handling: Login und anschließende Requests ausführen function handleLoginAndRequests(callback, storeIds = null) { checkSessionValidity().then(isValid => { if (!isValid) { login(userEmail, userPassword).then(() => { callback(storeIds, sessionCookies); }).catch(error => { console.error(`Login-Fehler: ${error}`); sendTelegramMessage(telegramInstance, "Login fehlgeschlagen."); }); } else { callback(storeIds, sessionCookies); } }); } // Überprüfung der Session-Gültigkeit async function checkSessionValidity() { if (!sessionCookies) { return false; } const axios = require('axios'); const headers = { "cookie": sessionCookies }; try { const response = await axios.get('https://foodsharing.de/api/wall/foodsaver/839246?limit=1', { headers }); return response.status === 200; } catch (error) { return false; } } // Login-Funktion inkl. CSRF-Token-Extraktion async function login(userEmail, userPassword) { const axios = require('axios'); const loginData = { email: userEmail, password: userPassword, remember_me: true }; const headers = { "sec-ch-ua": "\"Chromium\";v=\"128\", \"Not;A=Brand\";v=\"24\", \"Google Chrome\";v=\"128\"", "Referer": "https://foodsharing.de/", "DNT": "1", "sec-ch-ua-mobile": "?0", "sec-ch-ua-platform": "\"Windows\"", "Content-Type": "application/json; charset=utf-8" }; const response = await axios.post('https://foodsharing.de/api/user/login', loginData, { headers }); if (response.status === 200) { sessionCookies = response.headers['set-cookie']; // Alle Cookies speichern csrfToken = extractCsrfToken(sessionCookies); // CSRF-Token extrahieren console.log("Login erfolgreich. CSRF Token: " + csrfToken); } else { throw new Error("Login fehlgeschlagen."); } } // Funktion zum Senden der FoodSharing-Anfragen (zeigt Ergebnisse per Telegram an) function sendFoodSharingRequests(storeIds, session) { const axios = require('axios'); let results = []; let errors = []; let shouldSendMessage = false; storeIds.forEach(storeId => { const storeUrl = `https://foodsharing.de/api/map/stores/${storeId}`; const storeHeaders = { "cookie": session }; axios.get(storeUrl, { headers: storeHeaders }) .then(storeResponse => { const storeData = storeResponse.data; if (storeData.maySendRequest) { shouldSendMessage = true; } results.push({ ID: storeId, Name: masterStores[storeId] || storeData.name, AnmeldungOffen: storeData.maySendRequest ? "Ja" : "Nein" }); if (results.length + errors.length === storeIds.length) { if (shouldSendMessage || errors.length > 0) { sendResultsViaTelegram(results, errors); } } }) .catch(error => { errors.push(`Fehler bei der Store-Anfrage für Store ${storeId} (${masterStores[storeId] || "unbekannt"}): ${error.message}`); if (results.length + errors.length === storeIds.length) { sendResultsViaTelegram(results, errors); } }); }); } // Erweiterte Funktion zur Prüfung der Pickup-Daten mit automatischer Buchung function checkPickupData(storeId, checkProfileId, session, onlyNotify = false, desiredWeekday = null, desiredDate = null) { const axios = require('axios'); const storeUrl = `https://foodsharing.de/api/stores/${storeId}/pickups`; const headers = { "cookie": session }; axios.get(storeUrl, { headers }) .then(response => { const pickups = response.data.pickups; let hasProfileId = false; let availablePickup = null; pickups.forEach(pickup => { const pickupDate = new Date(pickup.date); const weekday = pickupDate.toLocaleDateString("en-US", { weekday: 'long' }); let matchesFilter = true; if (desiredDate) { const desired = new Date(desiredDate); matchesFilter = pickupDate.getFullYear() === desired.getFullYear() && pickupDate.getMonth() === desired.getMonth() && pickupDate.getDate() === desired.getDate(); } if (desiredWeekday && !matchesFilter) { matchesFilter = weekday === desiredWeekday; } if (!matchesFilter) return; if (checkProfileId && pickup.occupiedSlots.some(slot => slot.profile.id === profileId)) { hasProfileId = true; } else if (pickup.isAvailable && !availablePickup) { availablePickup = pickup; } }); // Sonderfall TVS für StoreID 51450 if (storeId === "51450" && availablePickup && availablePickup.description === "TVS") { console.log(`Slot mit Beschreibung "TVS" gefunden für Store ${storeId}. Buchung wird übersprungen.`); return; } if ((!checkProfileId || !hasProfileId) && availablePickup) { const localDate = availablePickup.date; const utcDate = new Date(localDate).toISOString(); const readableDate = new Date(localDate).toLocaleString("de-DE"); if (onlyNotify) { console.log(`Benachrichtigung: Freier Slot für ${masterStores[storeId] || storeId} am ${readableDate}`); sendTelegramMessage(telegramInstance, `?? Freier Slot gefunden für ${masterStores[storeId] || storeId} am ${readableDate} (nur Benachrichtigung).`); } else { bookSlot(storeId, utcDate, localDate); } } else { console.log(`Kein passender Slot für Store ${storeId} (${masterStores[storeId] || "unbekannt"}) gefunden.`); } }) .catch(error => { console.error(`Fehler bei Pickup-Abfrage für Store ${storeId}: ${error.message}`); }); } // Funktion zum automatischen Buchen eines freien Slots function bookSlot(storeId, utcDate, localDate) { const axios = require('axios'); console.log(`Starte Buchung für Store ${storeId} (${masterStores[storeId] || "unbekannt"}) am ${localDate} (UTC: ${utcDate})...`); // 1. Pickup-Rule Check durchführen axios.get(`https://foodsharing.de/api/stores/${storeId}/pickupRuleCheck/${utcDate}/${profileId}`, { headers: { "cookie": sessionCookies, "x-csrf-token": csrfToken } }) .then(ruleResponse => { if (ruleResponse.data.result === true) { // 2. Slot buchen, wenn Rule Check erfolgreich war axios.post(`https://foodsharing.de/api/stores/${storeId}/pickups/${utcDate}/${profileId}`, {}, { headers: { "cookie": sessionCookies, "Content-Type": "application/json; charset=utf-8", "x-csrf-token": csrfToken } }) .then(bookingResponse => { console.log("Buchung Response:", bookingResponse.data); sendTelegramMessage(telegramInstance, `Slot gebucht für Store ${storeId} (${masterStores[storeId] || "unbekannt"}) am ${localDate}`); }) .catch(bookingError => { console.error("Buchung Fehler:", bookingError.message); sendTelegramMessage(telegramInstance, `Buchungsfehler für Store ${storeId} (${masterStores[storeId] || "unbekannt"}): ${bookingError.message}`); }); } else { console.log("Pickup Rule Check fehlgeschlagen."); sendTelegramMessage(telegramInstance, `Pickup Rule Check fehlgeschlagen für Store ${storeId} (${masterStores[storeId] || "unbekannt"}) am ${localDate}`); } }) .catch(ruleError => { console.error("Fehler beim Pickup Rule Check:", ruleError.message); sendTelegramMessage(telegramInstance, `Fehler beim Pickup Rule Check für Store ${storeId} (${masterStores[storeId] || "unbekannt"}): ${ruleError.message}`); }); } // Funktion zum Senden der Ergebnisse und Fehler per Telegram function sendResultsViaTelegram(results, errors) { let message = "Ergebnisse der FoodSharing-Anfragen:\n\n"; if (results.length > 0) { message += "Erfolg:\n
ID | Name | Anmeldung offen\n";
message += "--------|------------------------------------|-----------------\n";
results.forEach(result => {
message += `${result.ID.padEnd(8)}| ${result.Name.padEnd(36)}| ${result.AnmeldungOffen}\n`;
});
message += "\n";
} else {
message += "Keine erfolgreichen Anfragen.\n";
}
if (errors.length > 0) {
message += "\nFehler:\n";
errors.forEach(error => {
message += `${error}\n`;
});
}
sendTelegramMessage(telegramInstance, message, true);
}
// Funktion zum Senden einer Nachricht per Telegram
function sendTelegramMessage(instance, message, isHtml = false) {
const options = { text: message };
if (isHtml) {
options.parse_mode = 'HTML';
}
sendTo(instance, options);
}
// Funktion zur Planung mit zufälliger Verzögerung
function scheduleWithRandomDelay(cronExpression, callback) {
const job = schedule(cronExpression, function () {
let delay = Math.floor(Math.random() * 110 * 1000) + 10 * 1000; // 10s bis 120s
setTimeout(callback, delay);
});
return job;
}
// Zufälliger Start des Skripts
startScriptWithRandomDelay();
// Zeitplan: Täglich um 7 Uhr morgens bzw. 15 Uhr (hier per Cron-Ausdruck)
schedule('0 7,15 * * *', function () {
startScriptWithRandomDelay(true);
});
// Initialisierung
setupPickupCheckSchedules();
// Trigger bei Änderungen des Datenpunkts
on({ id: configState, change: "any" }, () => {
console.log("?? Konfiguration geändert. Pickup-Checks werden neu geladen.");
setupPickupCheckSchedules();
});