diff --git a/iobrokerSkript.js b/iobrokerSkript.js new file mode 100644 index 0000000..8b35527 --- /dev/null +++ b/iobrokerSkript.js @@ -0,0 +1,390 @@ +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();
+});
\ No newline at end of file