js
This commit is contained in:
390
iobrokerSkript.js
Normal file
390
iobrokerSkript.js
Normal 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();
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user