390 lines
15 KiB
JavaScript
390 lines
15 KiB
JavaScript
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();
|
||
}); |