|
|
|
|
@@ -34,6 +34,12 @@ const watcherImageCache = new Map<
|
|
|
|
|
|
|
|
|
|
const delay = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
|
|
|
|
|
|
|
|
const randomBetween = (min: number, max: number) => {
|
|
|
|
|
const lower = Math.ceil(min);
|
|
|
|
|
const upper = Math.floor(max);
|
|
|
|
|
return Math.floor(Math.random() * (upper - lower + 1)) + lower;
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const toListItem = (watcher: Watcher): WatcherListItem => ({
|
|
|
|
|
...watcher,
|
|
|
|
|
hasCookie: Boolean(watcher.cookieEncrypted),
|
|
|
|
|
@@ -169,8 +175,21 @@ const toCookieHeader = (cookieValue: string, targetUrl: string) => {
|
|
|
|
|
.join("; ");
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const computeNextRunAt = (intervalMinutes: number) => {
|
|
|
|
|
return new Date(Date.now() + intervalMinutes * 60_000).toISOString();
|
|
|
|
|
const computeNextRunAt = (tracker: { minIntervalMinutes: number; jitterSeconds: number }, intervalMinutes: number) => {
|
|
|
|
|
const baseMs = intervalMinutes * 60_000;
|
|
|
|
|
const minMs = tracker.minIntervalMinutes * 60_000;
|
|
|
|
|
const jitterMs = tracker.jitterSeconds > 0
|
|
|
|
|
? randomBetween(-tracker.jitterSeconds, tracker.jitterSeconds) * 1000
|
|
|
|
|
: 0;
|
|
|
|
|
return new Date(Date.now() + Math.max(minMs, baseMs + jitterMs)).toISOString();
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const validateTrackerInterval = (tracker: { label: string; minIntervalMinutes: number }, intervalMinutes: number) => {
|
|
|
|
|
if (intervalMinutes < tracker.minIntervalMinutes) {
|
|
|
|
|
throw new Error(
|
|
|
|
|
`${tracker.label} icin minimum izleme araligi ${tracker.minIntervalMinutes} dakikadir.`
|
|
|
|
|
);
|
|
|
|
|
}
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
const deriveLiveStatus = (
|
|
|
|
|
@@ -315,7 +334,18 @@ const persistWatcherProgress = async (payload: {
|
|
|
|
|
|
|
|
|
|
export const listTrackers = async () => {
|
|
|
|
|
try {
|
|
|
|
|
return await listScraperTrackers();
|
|
|
|
|
const remoteTrackers = await listScraperTrackers();
|
|
|
|
|
return remoteTrackers.map((tracker) => {
|
|
|
|
|
const local = getTrackerDefinition(tracker.key);
|
|
|
|
|
return local
|
|
|
|
|
? {
|
|
|
|
|
...tracker,
|
|
|
|
|
minIntervalMinutes: local.minIntervalMinutes,
|
|
|
|
|
jitterSeconds: local.jitterSeconds,
|
|
|
|
|
recommendedIntervalLabel: local.recommendedIntervalLabel,
|
|
|
|
|
}
|
|
|
|
|
: tracker;
|
|
|
|
|
});
|
|
|
|
|
} catch (error) {
|
|
|
|
|
logger.warn({ error }, "Falling back to local watcher tracker registry");
|
|
|
|
|
return trackerRegistry;
|
|
|
|
|
@@ -358,6 +388,7 @@ export const createWatcher = async (payload: {
|
|
|
|
|
if (!payload.cookie.trim()) {
|
|
|
|
|
throw new Error("Cookie is required");
|
|
|
|
|
}
|
|
|
|
|
validateTrackerInterval(tracker, payload.intervalMinutes);
|
|
|
|
|
if (tracker.key === "privatehd" && !payload.wishlistUrl?.trim()) {
|
|
|
|
|
throw new Error("PrivateHD icin wishlist URL zorunludur.");
|
|
|
|
|
}
|
|
|
|
|
@@ -372,7 +403,7 @@ export const createWatcher = async (payload: {
|
|
|
|
|
cookieHint: buildCookieHint(payload.cookie),
|
|
|
|
|
intervalMinutes: payload.intervalMinutes,
|
|
|
|
|
enabled: payload.enabled ?? true,
|
|
|
|
|
nextRunAt: computeNextRunAt(payload.intervalMinutes),
|
|
|
|
|
nextRunAt: computeNextRunAt(tracker, payload.intervalMinutes),
|
|
|
|
|
totalImported: 0,
|
|
|
|
|
totalSeen: 0,
|
|
|
|
|
createdAt: nowIso(),
|
|
|
|
|
@@ -396,13 +427,18 @@ export const updateWatcher = async (
|
|
|
|
|
}
|
|
|
|
|
) => {
|
|
|
|
|
const updated = await persistWatcher(watcherId, (watcher) => {
|
|
|
|
|
const tracker = getTrackerDefinition(watcher.tracker);
|
|
|
|
|
if (!tracker) {
|
|
|
|
|
throw new Error("Unsupported tracker");
|
|
|
|
|
}
|
|
|
|
|
const next: Watcher = {
|
|
|
|
|
...watcher,
|
|
|
|
|
updatedAt: nowIso(),
|
|
|
|
|
};
|
|
|
|
|
if (typeof payload.intervalMinutes === "number") {
|
|
|
|
|
validateTrackerInterval(tracker, payload.intervalMinutes);
|
|
|
|
|
next.intervalMinutes = payload.intervalMinutes;
|
|
|
|
|
next.nextRunAt = computeNextRunAt(payload.intervalMinutes);
|
|
|
|
|
next.nextRunAt = computeNextRunAt(tracker, payload.intervalMinutes);
|
|
|
|
|
}
|
|
|
|
|
if (typeof payload.enabled === "boolean") {
|
|
|
|
|
next.enabled = payload.enabled;
|
|
|
|
|
@@ -758,7 +794,7 @@ export const runWatcherById = async (watcherId: string) => {
|
|
|
|
|
...current,
|
|
|
|
|
lastRunAt: nowIso(),
|
|
|
|
|
lastError: undefined,
|
|
|
|
|
nextRunAt: computeNextRunAt(current.intervalMinutes),
|
|
|
|
|
nextRunAt: computeNextRunAt(tracker, current.intervalMinutes),
|
|
|
|
|
updatedAt: nowIso(),
|
|
|
|
|
}));
|
|
|
|
|
const cookie = decryptWatcherCookie(watcher.cookieEncrypted);
|
|
|
|
|
|