aktueller Stand

This commit is contained in:
root
2025-11-09 13:50:17 +01:00
parent 81e7f94817
commit dc95156793
16 changed files with 2152 additions and 1148 deletions

View File

@@ -0,0 +1,179 @@
const axios = require('axios');
const BASE_URL = 'https://foodsharing.de';
const client = axios.create({
baseURL: BASE_URL,
timeout: 20000,
headers: {
'User-Agent': 'pickup-config/1.0 (+https://foodsharing.de)',
Accept: 'application/json, text/plain, */*'
}
});
function extractCsrfToken(cookies = []) {
if (!Array.isArray(cookies)) {
return null;
}
const tokenCookie = cookies.find((cookie) => cookie.startsWith('CSRF_TOKEN='));
if (!tokenCookie) {
return null;
}
return tokenCookie.split(';')[0].split('=')[1];
}
function serializeCookies(cookies = []) {
if (!Array.isArray(cookies)) {
return '';
}
return cookies.map((c) => c.split(';')[0]).join('; ');
}
function buildHeaders(cookieHeader, csrfToken) {
const headers = {};
if (cookieHeader) {
headers.cookie = cookieHeader;
}
if (csrfToken) {
headers['x-csrf-token'] = csrfToken;
}
return headers;
}
async function getCurrentUserDetails(cookieHeader) {
const response = await client.get('/api/user/current/details', {
headers: buildHeaders(cookieHeader)
});
return response.data;
}
async function login(email, password) {
const payload = {
email,
password,
remember_me: true
};
const headers = {
'sec-ch-ua': '"Chromium";v="128", "Not;A=Brand";v="24", "Google Chrome";v="128"',
Referer: BASE_URL,
DNT: '1',
'sec-ch-ua-mobile': '?0',
'sec-ch-ua-platform': '"Linux"',
'Content-Type': 'application/json; charset=utf-8'
};
const response = await client.post('/api/user/login', payload, { headers });
const cookies = response.headers['set-cookie'] || [];
const csrfToken = extractCsrfToken(cookies);
const cookieHeader = serializeCookies(cookies);
const details = await getCurrentUserDetails(cookieHeader);
if (!details?.id) {
throw new Error('Profil-ID konnte nicht ermittelt werden.');
}
const nameParts = [];
if (details.firstname) {
nameParts.push(details.firstname);
}
if (details.lastname) {
nameParts.push(details.lastname);
}
return {
csrfToken,
cookieHeader,
profile: {
id: String(details.id),
name: nameParts.length > 0 ? nameParts.join(' ') : details.email || email,
email: details.email || email
}
};
}
async function checkSession(cookieHeader, profileId) {
if (!cookieHeader) {
return false;
}
try {
await client.get(`/api/wall/foodsaver/${profileId}?limit=1`, {
headers: buildHeaders(cookieHeader)
});
return true;
} catch {
return false;
}
}
async function fetchProfile(cookieHeader) {
try {
return await getCurrentUserDetails(cookieHeader);
} catch (error) {
console.warn('Profil konnte nicht geladen werden:', error.message);
return null;
}
}
async function fetchStores(cookieHeader, profileId) {
if (!profileId) {
return [];
}
try {
const response = await client.get(`/api/user/${profileId}/stores`, {
headers: buildHeaders(cookieHeader),
params: { activeStores: 1 }
});
const stores = response.data || [];
if (!Array.isArray(stores)) {
return [];
}
return stores.map((store) => ({
id: String(store.id),
name: store.name || `Store ${store.id}`,
pickupStatus: store.pickupStatus,
membershipStatus: store.membershipStatus,
isManaging: !!store.isManaging,
city: store.city || '',
street: store.street || '',
zip: store.zip || ''
}));
} catch (error) {
console.warn('Stores konnten nicht geladen werden:', error.message);
return [];
}
}
async function fetchPickups(storeId, cookieHeader) {
const response = await client.get(`/api/stores/${storeId}/pickups`, {
headers: buildHeaders(cookieHeader)
});
return response.data?.pickups || [];
}
async function pickupRuleCheck(storeId, utcDate, profileId, session) {
const response = await client.get(`/api/stores/${storeId}/pickupRuleCheck/${utcDate}/${profileId}`, {
headers: buildHeaders(session.cookieHeader, session.csrfToken)
});
return response.data?.result === true;
}
async function bookSlot(storeId, utcDate, profileId, session) {
await client.post(
`/api/stores/${storeId}/pickups/${utcDate}/${profileId}`,
{},
{
headers: buildHeaders(session.cookieHeader, session.csrfToken)
}
);
}
module.exports = {
login,
checkSession,
fetchProfile,
fetchStores,
fetchPickups,
pickupRuleCheck,
bookSlot
};