aktueller stand
This commit is contained in:
@@ -14,11 +14,13 @@ const DEFAULT_SETTINGS = {
|
||||
storeWatchInitialDelayMinSeconds: 10,
|
||||
storeWatchInitialDelayMaxSeconds: 60,
|
||||
storeWatchRequestDelayMs: 1000,
|
||||
storeWatchStatusCacheMaxAgeMinutes: 120,
|
||||
storePickupCheckDelayMs: 400,
|
||||
ignoredSlots: [
|
||||
{
|
||||
storeId: '51450',
|
||||
description: 'TVS'
|
||||
slotName: 'TVS',
|
||||
info: ''
|
||||
}
|
||||
],
|
||||
notifications: {
|
||||
@@ -56,11 +58,15 @@ function sanitizeIgnoredSlots(slots = []) {
|
||||
return DEFAULT_SETTINGS.ignoredSlots;
|
||||
}
|
||||
return slots
|
||||
.map((slot) => ({
|
||||
storeId: slot?.storeId ? String(slot.storeId) : '',
|
||||
description: slot?.description ? String(slot.description) : ''
|
||||
}))
|
||||
.filter((slot) => slot.storeId);
|
||||
.map((slot) => {
|
||||
const slotName = slot?.slotName ?? slot?.description;
|
||||
return {
|
||||
storeId: slot?.storeId ? String(slot.storeId) : '',
|
||||
slotName: slotName ? String(slotName) : '',
|
||||
info: slot?.info ? String(slot.info) : ''
|
||||
};
|
||||
})
|
||||
.filter((slot) => slot.storeId && slot.slotName);
|
||||
}
|
||||
|
||||
function sanitizeString(value) {
|
||||
@@ -120,6 +126,10 @@ function readSettings() {
|
||||
parsed.storeWatchRequestDelayMs,
|
||||
DEFAULT_SETTINGS.storeWatchRequestDelayMs
|
||||
),
|
||||
storeWatchStatusCacheMaxAgeMinutes: sanitizeNumber(
|
||||
parsed.storeWatchStatusCacheMaxAgeMinutes,
|
||||
DEFAULT_SETTINGS.storeWatchStatusCacheMaxAgeMinutes
|
||||
),
|
||||
storePickupCheckDelayMs: sanitizeNumber(
|
||||
parsed.storePickupCheckDelayMs,
|
||||
DEFAULT_SETTINGS.storePickupCheckDelayMs
|
||||
@@ -154,6 +164,10 @@ function writeSettings(patch = {}) {
|
||||
patch.storeWatchRequestDelayMs,
|
||||
current.storeWatchRequestDelayMs
|
||||
),
|
||||
storeWatchStatusCacheMaxAgeMinutes: sanitizeNumber(
|
||||
patch.storeWatchStatusCacheMaxAgeMinutes,
|
||||
current.storeWatchStatusCacheMaxAgeMinutes
|
||||
),
|
||||
storePickupCheckDelayMs: sanitizeNumber(
|
||||
patch.storePickupCheckDelayMs,
|
||||
current.storePickupCheckDelayMs
|
||||
|
||||
@@ -6,7 +6,7 @@ const notificationService = require('./notificationService');
|
||||
const { readConfig, writeConfig } = require('./configStore');
|
||||
const { readStoreWatch, writeStoreWatch } = require('./storeWatchStore');
|
||||
const { readJournal, writeJournal } = require('./journalStore');
|
||||
const { setStoreStatus, persistStoreStatusCache } = require('./storeStatusCache');
|
||||
const { getStoreStatus, setStoreStatus, persistStoreStatusCache } = require('./storeStatusCache');
|
||||
const { sendDormantPickupWarning, sendJournalReminderNotification } = require('./notificationService');
|
||||
const { ensureSession, withSessionRetry } = require('./sessionRefresh');
|
||||
|
||||
@@ -17,6 +17,53 @@ function wait(ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const DEFAULT_STORE_WATCH_STATUS_MAX_AGE_MINUTES = 120;
|
||||
const storeWatchInFlight = new Map();
|
||||
|
||||
async function fetchSharedStoreStatus(session, storeId, { forceRefresh = false, maxAgeMs } = {}) {
|
||||
if (!storeId) {
|
||||
return { status: null, fetchedAt: null, fromCache: false };
|
||||
}
|
||||
const cacheEntry = getStoreStatus(storeId);
|
||||
const cachedStatus = cacheEntry?.teamSearchStatus;
|
||||
const hasCachedStatus = cachedStatus === 0 || cachedStatus === 1;
|
||||
const cachedAt = Number(cacheEntry?.fetchedAt) || 0;
|
||||
const effectiveMaxAge =
|
||||
Number.isFinite(maxAgeMs) && maxAgeMs >= 0
|
||||
? maxAgeMs
|
||||
: DEFAULT_STORE_WATCH_STATUS_MAX_AGE_MINUTES * 60 * 1000;
|
||||
const cacheFresh = hasCachedStatus && Date.now() - cachedAt <= effectiveMaxAge;
|
||||
if (cacheFresh && !forceRefresh) {
|
||||
return { status: cachedStatus, fetchedAt: cachedAt, fromCache: true };
|
||||
}
|
||||
|
||||
const key = String(storeId);
|
||||
if (!forceRefresh && storeWatchInFlight.has(key)) {
|
||||
return storeWatchInFlight.get(key);
|
||||
}
|
||||
|
||||
const fetchPromise = (async () => {
|
||||
const details = await withSessionRetry(
|
||||
session,
|
||||
() => foodsharingClient.fetchStoreDetails(storeId, session.cookieHeader, session),
|
||||
{ label: 'fetchStoreDetails' }
|
||||
);
|
||||
const status = details?.teamSearchStatus === 1 ? 1 : 0;
|
||||
const fetchedAt = Date.now();
|
||||
setStoreStatus(storeId, { teamSearchStatus: status, fetchedAt });
|
||||
return { status, fetchedAt, fromCache: false };
|
||||
})();
|
||||
|
||||
storeWatchInFlight.set(key, fetchPromise);
|
||||
try {
|
||||
return await fetchPromise;
|
||||
} finally {
|
||||
if (storeWatchInFlight.get(key) === fetchPromise) {
|
||||
storeWatchInFlight.delete(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const weekdayMap = {
|
||||
Montag: 'Monday',
|
||||
Dienstag: 'Tuesday',
|
||||
@@ -103,6 +150,9 @@ function resolveSettings(settings) {
|
||||
storeWatchRequestDelayMs: Number.isFinite(settings.storeWatchRequestDelayMs)
|
||||
? settings.storeWatchRequestDelayMs
|
||||
: DEFAULT_SETTINGS.storeWatchRequestDelayMs,
|
||||
storeWatchStatusCacheMaxAgeMinutes: Number.isFinite(settings.storeWatchStatusCacheMaxAgeMinutes)
|
||||
? settings.storeWatchStatusCacheMaxAgeMinutes
|
||||
: DEFAULT_SETTINGS.storeWatchStatusCacheMaxAgeMinutes,
|
||||
ignoredSlots: Array.isArray(settings.ignoredSlots) ? settings.ignoredSlots : DEFAULT_SETTINGS.ignoredSlots,
|
||||
notifications: {
|
||||
ntfy: {
|
||||
@@ -305,10 +355,8 @@ function shouldIgnoreSlot(entry, pickup, settings) {
|
||||
if (String(rule.storeId) !== entry.id) {
|
||||
return false;
|
||||
}
|
||||
if (rule.description) {
|
||||
return pickup.description === rule.description;
|
||||
}
|
||||
return true;
|
||||
const slotName = rule.slotName || rule.description;
|
||||
return slotName ? pickup.description === slotName : true;
|
||||
});
|
||||
}
|
||||
|
||||
@@ -469,12 +517,13 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
|
||||
for (let index = 0; index < watchers.length; index += 1) {
|
||||
const watcher = watchers[index];
|
||||
try {
|
||||
const details = await withSessionRetry(
|
||||
session,
|
||||
() => foodsharingClient.fetchStoreDetails(watcher.storeId, session.cookieHeader, session),
|
||||
{ label: 'fetchStoreDetails' }
|
||||
);
|
||||
const status = details?.teamSearchStatus === 1 ? 1 : 0;
|
||||
const cacheMaxAgeMs = Math.max(
|
||||
0,
|
||||
Number(settings?.storeWatchStatusCacheMaxAgeMinutes ?? DEFAULT_STORE_WATCH_STATUS_MAX_AGE_MINUTES)
|
||||
) * 60 * 1000;
|
||||
const { status, fromCache } = await fetchSharedStoreStatus(session, watcher.storeId, {
|
||||
maxAgeMs: cacheMaxAgeMs
|
||||
});
|
||||
const checkedAt = Date.now();
|
||||
if (status === 1 && watcher.lastTeamSearchStatus !== 1) {
|
||||
await notificationService.sendStoreWatchNotification({
|
||||
@@ -490,8 +539,9 @@ async function checkWatchedStores(sessionId, settings = DEFAULT_SETTINGS, option
|
||||
}
|
||||
watcher.lastStatusCheckAt = checkedAt;
|
||||
changed = true;
|
||||
setStoreStatus(watcher.storeId, { teamSearchStatus: status, fetchedAt: checkedAt });
|
||||
statusCacheUpdated = true;
|
||||
if (!fromCache) {
|
||||
statusCacheUpdated = true;
|
||||
}
|
||||
summary.push({
|
||||
storeId: watcher.storeId,
|
||||
storeName: watcher.storeName,
|
||||
|
||||
Reference in New Issue
Block a user