diff --git a/src/scheduler/scheduler.js b/src/scheduler/scheduler.js index 0227211..45003f2 100644 --- a/src/scheduler/scheduler.js +++ b/src/scheduler/scheduler.js @@ -1,5 +1,5 @@ -import { buildTable, formatNumber, truncateString } from '../util/table.js'; -import { daysBetween, formatDateInTz, formatDateTimeInTz, weekdayInTz } from '../util/time.js'; +import { buildTable } from '../util/table.js'; +import { daysBetween, formatDateInTz, weekdayInTz } from '../util/time.js'; import { resolveDaysWarning } from '../util/group-settings.js'; // One-shot daily check, invoked from api/cron.js. The cron schedule lives in @@ -38,7 +38,8 @@ async function checkGroup(groupId, silent, now, config, store, sender, appleScra } const threshold = resolveDaysWarning(group, config); - const stale = []; + const staleApple = []; + const staleGoogle = []; for (const info of group.appleApps) { try { @@ -48,15 +49,10 @@ async function checkGroup(groupId, silent, now, config, store, sender, appleScra if (Number.isNaN(updatedMs)) continue; const days = daysBetween(updatedMs, now.getTime()); if (days > threshold) { - stale.push({ + staleApple.push({ appId: info.appId, - title: app.title, days, updated: formatDateInTz(new Date(updatedMs), config.timezone), - score: app.score, - reviews: Number(app.reviews ?? 0), - ratings: Number(app.ratings ?? 0), - isApple: true, }); } } catch (err) { @@ -72,15 +68,10 @@ async function checkGroup(groupId, silent, now, config, store, sender, appleScra if (!Number.isFinite(updatedMs)) continue; const days = daysBetween(updatedMs, now.getTime()); if (days > threshold) { - stale.push({ + staleGoogle.push({ appId: info.appId, - title: app.title, days, updated: formatDateInTz(new Date(updatedMs), config.timezone), - score: app.score, - reviews: Number(app.reviews ?? 0), - ratings: Number(app.ratings ?? 0), - isApple: false, }); } } catch (err) { @@ -88,31 +79,27 @@ async function checkGroup(groupId, silent, now, config, store, sender, appleScra } } - if (stale.length === 0) { + const total = staleApple.length + staleGoogle.length; + if (total === 0) { logger.info({ groupId }, 'All apps up-to-date'); return; } - const message = buildReport(groupId, stale, now, config, threshold); + const message = buildReport(staleApple, staleGoogle); if (silent) await sender.sendMessageSilent(groupId, message); else await sender.sendMessage(groupId, message); } -function buildReport(groupId, apps, now, config, threshold) { - const headers = ['App', 'Store', 'Days', 'Updated', 'Score', 'Reviews', 'Ratings']; - const rows = apps.map((a) => [ - truncateString(a.title || '', 30), - a.isApple ? 'Apple' : 'Google', - String(a.days), - a.updated, - Number(a.score ?? 0).toFixed(1), - String(a.reviews), - formatNumber(a.ratings), - ]); - return ( - `Daily App Check Report\n` + - `Date: ${formatDateTimeInTz(now, config.timezone)}\n` + - `Group: ${groupId}\n` + - `Apps not updated in >${threshold} days: ${apps.length}\n\n` + - `
${buildTable(headers, rows)}
` - ); +function buildReport(staleApple, staleGoogle) { + const total = staleApple.length + staleGoogle.length; + const headers = ['#', 'AppId', 'Updated', 'Days']; + let out = `You have ${total} app(s) need to be updated!\n`; + if (staleApple.length > 0) { + const rows = staleApple.map((a, i) => [String(i + 1), a.appId, a.updated, String(a.days)]); + out += `${staleApple.length} Apple Apps:\n\n${buildTable(headers, rows)}\n\n`; + } + if (staleGoogle.length > 0) { + const rows = staleGoogle.map((a, i) => [String(i + 1), a.appId, a.updated, String(a.days)]); + out += `${staleGoogle.length} Google Apps:\n\n${buildTable(headers, rows)}\n`; + } + return out; } diff --git a/src/util/table.js b/src/util/table.js index 77c7b37..b86e48e 100644 --- a/src/util/table.js +++ b/src/util/table.js @@ -51,16 +51,3 @@ function writeSeparator(widths) { function padRight(s, len) { return s.length >= len ? s : s + ' '.repeat(len - s.length); } - -export function truncateString(s, maxLen) { - if (s.length <= maxLen) return s; - if (maxLen <= 3) return s.slice(0, maxLen); - return s.slice(0, maxLen - 3) + '...'; -} - -export function formatNumber(n) { - const v = Number(n); - if (v >= 1_000_000) return `${(v / 1_000_000).toFixed(1)}M`; - if (v >= 1_000) return `${(v / 1_000).toFixed(1)}K`; - return String(v); -} diff --git a/src/util/time.js b/src/util/time.js index 8980cc2..923d187 100644 --- a/src/util/time.js +++ b/src/util/time.js @@ -9,21 +9,6 @@ export function formatDateInTz(date, timezone) { return fmt.format(date); } -// Format datetime as "YYYY-MM-DD HH:MM" in given IANA timezone. -export function formatDateTimeInTz(date, timezone) { - const fmt = new Intl.DateTimeFormat('en-CA', { - timeZone: timezone, - year: 'numeric', - month: '2-digit', - day: '2-digit', - hour: '2-digit', - minute: '2-digit', - hour12: false, - }); - // en-CA produces "YYYY-MM-DD, HH:MM" — strip the comma. - return fmt.format(date).replace(', ', ' '); -} - // Day-of-week (0=Sun..6=Sat) for `date` in given timezone. export function weekdayInTz(date, timezone) { const fmt = new Intl.DateTimeFormat('en-US', { timeZone: timezone, weekday: 'short' });