This commit is contained in:
root
2025-11-09 11:08:11 +01:00
parent a33d4484b7
commit 81e7f94817

390
iobrokerSkript.js Normal file
View File

@@ -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<70>ne aufr<66>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<73>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<EFBFBD>fung f<>r ${storeLabel} (${id}) <20> Nur Benachrichtigung: ${onlyNotify}` +
(translatedWeekday ? ` <20> nur an ${translatedWeekday}` : '') +
` <20> 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<6F> You",
"37245": "Erdbeerland Enderle GBR",
"35401": "Kalinka - Internationale Lebensmittel",
"31080": "Gem<65>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<47>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<47>nwinkel
"43694", //Blumen Deniz Rastatt
"32807", //Claus Reformwaren
"47510", //BIOLAND G<>RTNEREI LUTZ
"42141", //Restaurant Pok<6F> You
"37245", //Erdbeerland Enderle GBR
"35401", //Kalinka - Internationale Lebensmittel
"31080", //Gem<65>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<50>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<75>llig verz<72>gerten Start des Skripts
function startScriptWithRandomDelay(random = false) {
const maxDelay = random ? 10 * 60 * 1000 : 1000; // bis zu 10 Minuten Verz<72>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<69>ende Requests ausf<73>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);
}
});
}
// <20>berpr<70>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<50>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 <20>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<68>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<75>lliger Verz<72>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<75>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 <20>nderungen des Datenpunkts
on({ id: configState, change: "any" }, () => {
console.log("?? Konfiguration ge<67>ndert. Pickup-Checks werden neu geladen.");
setupPickupCheckSchedules();
});