diff --git a/README.md b/README.md index 177c038..dada2a7 100644 --- a/README.md +++ b/README.md @@ -76,7 +76,7 @@ These are keyed per piece of media, not per user. If multiple users requested th - Skipped if Radarr reports `isAvailable: false` (unreleased) or Sonarr reports `status: "upcoming"`. - **Auto-resolves** when the file appears. -- Manual close cooldown: **3 days**. +- Manual close: no cooldown — reopens on the next refresh if the file still isn't there. --- @@ -92,7 +92,7 @@ These are keyed per piece of media, not per user. If multiple users requested th - Only fires for series with `status: "ended"` in Sonarr. Continuing shows are excluded because missing episodes may not have aired yet. - Completion is calculated as `episodeFileCount / totalEpisodeCount` (not Sonarr's `percentOfEpisodes`, which measures against monitored episodes only). - **Auto-resolves** when all episodes are on disk. -- Manual close cooldown: **3 days**. +- Manual close: no cooldown — reopens on the next refresh if episodes are still missing. --- @@ -107,7 +107,7 @@ These are keyed per piece of media, not per user. If multiple users requested th - One alert per request item, not per user. - Skipped if the content is unreleased. - **Auto-resolves** when the request is approved or declined. -- Manual close cooldown: **3 days**. +- Manual close: no cooldown — reopens on the next refresh if still pending. --- @@ -133,7 +133,7 @@ Rather than checking lifetime play counts, this looks at recency: if a user's la |---|---|---| | `GHOST_RECENT_REQUESTS` | `5` | Number of recent approved requests to evaluate. Also the minimum required before the alert can fire. | -- Manual close cooldown: **14 days**. +- Manual close cooldown: **7 days**. --- @@ -146,7 +146,7 @@ Rather than checking lifetime play counts, this looks at recency: if a user's la | `MIN_REQUESTS_WATCHRATE` | `10` | Minimum requests before the ratio is considered meaningful. | | `LOW_WATCH_RATE` | `0.2` | Ratio of `plays / requests` below which an alert fires (default: under 20%). | -- Manual close cooldown: **14 days**. +- Manual close cooldown: **7 days**. --- @@ -158,7 +158,7 @@ Rather than checking lifetime play counts, this looks at recency: if a user's la This usually means emails don't align between the two services. Check that users have the same email address in both Overseerr and Tautulli (or that display names match as a fallback). -- Manual close cooldown: **1 day**. +- Manual close: no cooldown. --- @@ -183,6 +183,7 @@ clears closed │ immediately re-open ``` -- **Auto-resolved** alerts have no cooldown and will reopen immediately if the condition returns. -- **Manually closed** alerts suppress re-opening for a category-specific number of days (see cooldowns above). -- Reopening a manually closed alert via the UI clears the cooldown immediately. +- **Auto-resolved** alerts reopen immediately if the condition returns. +- **Content alerts** (unfulfilled, pending) have no cooldown on manual close — they reopen on the next refresh if the condition still exists. Closing is an acknowledgment, not a suppression. +- **User-behavior alerts** (ghost, watchrate) suppress re-opening for 7 days after a manual close. +- Reopening a manually closed alert via the UI always clears the cooldown immediately. diff --git a/src/lib/db.ts b/src/lib/db.ts index 730ee78..5a30722 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -16,19 +16,19 @@ import { const DATA_DIR = join(process.cwd(), "data"); const DB_PATH = join(DATA_DIR, "alerts.json"); -// Cooldown days applied on MANUAL close — suppresses re-opening for this long. -// Auto-resolved alerts have no cooldown and can reopen immediately if the -// condition returns. +// Cooldown days applied on MANUAL close. +// 0 = no cooldown: content alerts reopen immediately on the next refresh if +// the condition still exists. Closing is an acknowledgment, not a suppression. +// >0 = user-behavior alerts: suppress re-opening for this many days so a single +// acknowledgment isn't immediately undone by the next refresh. const COOLDOWN: Record = { - unfulfilled: 3, - pending: 3, - ghost: 14, - watchrate: 14, - declined: 14, - "tautulli-no-matches": 1, - "dark-library": 30, + unfulfilled: 0, + pending: 0, + ghost: 7, + watchrate: 7, + "tautulli-no-matches": 0, }; -const DEFAULT_COOLDOWN = 7; +const DEFAULT_COOLDOWN = 0; interface Store { nextId: number; @@ -205,13 +205,17 @@ export function closeAlert(id: number): Alert | null { if (!alert) return null; const cooldownDays = COOLDOWN[alert.category] ?? DEFAULT_COOLDOWN; - const suppressUntil = new Date(); - suppressUntil.setDate(suppressUntil.getDate() + cooldownDays); + let suppressedUntil: string | null = null; + if (cooldownDays > 0) { + const d = new Date(); + d.setDate(d.getDate() + cooldownDays); + suppressedUntil = d.toISOString(); + } alert.status = "closed"; alert.closeReason = "manual"; alert.closedAt = new Date().toISOString(); - alert.suppressedUntil = suppressUntil.toISOString(); + alert.suppressedUntil = suppressedUntil; save(store); return toAlert(alert); }