86 lines
2.9 KiB
TypeScript
86 lines
2.9 KiB
TypeScript
import { getDb, WatchedItem } from './database';
|
|
import { checkStockStatus } from './scraper';
|
|
import { sendTelegramAlert } from './telegram';
|
|
|
|
const MIN_INTERVAL_SECONDS = 30;
|
|
const timers = new Map<number, NodeJS.Timeout>();
|
|
|
|
async function runCheck(itemId: number): Promise<void> {
|
|
const db = getDb();
|
|
const item = db.prepare('SELECT * FROM watched_items WHERE id = ?').get(itemId) as WatchedItem | undefined;
|
|
|
|
if (!item || !item.is_active) return;
|
|
|
|
console.log(`[Scheduler] Checking item ${itemId} — ${item.name || item.url}`);
|
|
|
|
try {
|
|
const result = await checkStockStatus(item.url);
|
|
|
|
const now = new Date().toISOString();
|
|
let alertSent = item.alert_sent;
|
|
|
|
// Fire alert only on transition to in_stock when no alert has been sent yet
|
|
if (result.status === 'in_stock' && !item.alert_sent) {
|
|
const displayName = result.name || item.name || item.url;
|
|
await sendTelegramAlert(
|
|
`🟢 <b>Back in Stock!</b>\n\n` +
|
|
`<b>${displayName}</b>\n\n` +
|
|
`<a href="${item.url}">Open in Store →</a>`
|
|
);
|
|
alertSent = 1;
|
|
}
|
|
|
|
// Build update payload
|
|
const updates: Record<string, string | number> = {
|
|
last_status: result.status,
|
|
alert_sent: alertSent,
|
|
check_count: item.check_count + 1,
|
|
last_checked_at: now,
|
|
};
|
|
|
|
// Populate name and thumbnail on first successful scrape
|
|
if (result.name && !item.name) updates.name = result.name;
|
|
if (result.thumbnail && !item.thumbnail_url) updates.thumbnail_url = result.thumbnail;
|
|
|
|
const setClause = Object.keys(updates).map(k => `${k} = @${k}`).join(', ');
|
|
db.prepare(`UPDATE watched_items SET ${setClause} WHERE id = @id`).run({ ...updates, id: itemId });
|
|
|
|
console.log(`[Scheduler] Item ${itemId} → ${result.status} (check #${item.check_count + 1})`);
|
|
} catch (err) {
|
|
console.error(`[Scheduler] Error checking item ${itemId}:`, err);
|
|
// Still increment count so the UI shows activity
|
|
db.prepare('UPDATE watched_items SET check_count = check_count + 1, last_checked_at = ? WHERE id = ?')
|
|
.run(new Date().toISOString(), itemId);
|
|
}
|
|
}
|
|
|
|
export function startItem(item: WatchedItem): void {
|
|
stopItem(item.id);
|
|
|
|
const intervalMs = Math.max(MIN_INTERVAL_SECONDS, item.check_interval) * 1000;
|
|
const timer = setInterval(() => runCheck(item.id), intervalMs);
|
|
timers.set(item.id, timer);
|
|
|
|
console.log(`[Scheduler] Started item ${item.id} — interval ${intervalMs / 1000}s`);
|
|
}
|
|
|
|
export function stopItem(id: number): void {
|
|
const timer = timers.get(id);
|
|
if (timer) {
|
|
clearInterval(timer);
|
|
timers.delete(id);
|
|
console.log(`[Scheduler] Stopped item ${id}`);
|
|
}
|
|
}
|
|
|
|
export function initScheduler(): void {
|
|
const db = getDb();
|
|
const activeItems = db.prepare('SELECT * FROM watched_items WHERE is_active = 1').all() as WatchedItem[];
|
|
|
|
for (const item of activeItems) {
|
|
startItem(item);
|
|
}
|
|
|
|
console.log(`[Scheduler] Initialized — ${activeItems.length} active item(s)`);
|
|
}
|