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