Files
Pickup-Config/iobrokerSkript.js
root 81e7f94817 js
2025-11-09 11:08:11 +01:00

390 lines
15 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 = "<b>Ergebnisse der FoodSharing-Anfragen:</b>\n\n";
if (results.length > 0) {
message += "<b>Erfolg:</b>\n<pre>ID | Name | Anmeldung offen\n";
message += "--------|------------------------------------|-----------------\n";
results.forEach(result => {
message += `${result.ID.padEnd(8)}| ${result.Name.padEnd(36)}| ${result.AnmeldungOffen}\n`;
});
message += "</pre>\n";
} else {
message += "Keine erfolgreichen Anfragen.\n";
}
if (errors.length > 0) {
message += "\n<b>Fehler:</b>\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();
});