Content alerts (unfulfilled, pending, tautulli-no-matches) now have zero cooldown on manual close — they reopen immediately on the next refresh if the condition still exists. Closing is an acknowledgment of the current state, not a suppression of future alerts. User-behavior alerts (ghost, watchrate) keep a cooldown (7 days) so a single manual close isn't immediately undone by the next refresh. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
190 lines
6.7 KiB
Markdown
190 lines
6.7 KiB
Markdown
# OverSnitch
|
|
|
|
A self-hosted dashboard for monitoring Overseerr/Jellyseerr users — who's requesting, how much storage they're consuming, how often they actually watch what they request, and whether anything needs your attention.
|
|
|
|
Built with Next.js 16, TypeScript, and Tailwind CSS.
|
|
|
|
---
|
|
|
|
## Features
|
|
|
|
- **Leaderboard** — per-user request count, total storage, average GB per request, and optional Tautulli watch stats (plays, watch hours), each ranked against the full userbase
|
|
- **Alerting** — automatic alerts for stalled downloads, neglected requesters, and abusive patterns, with open/close state, notes, and auto-resolve when conditions clear
|
|
- **SWR caching** — stats are cached server-side for 5 minutes and seeded from localStorage on the client, so the dashboard is instant on return visits
|
|
|
|
---
|
|
|
|
## Setup
|
|
|
|
### 1. Clone and install
|
|
|
|
```bash
|
|
git clone https://gitea.thewrightserver.net/josh/OverSnitch.git
|
|
cd OverSnitch
|
|
npm install
|
|
```
|
|
|
|
### 2. Configure environment
|
|
|
|
Create `.env.local` in the project root:
|
|
|
|
```env
|
|
# Required
|
|
SEERR_URL=http://overseerr:5055
|
|
SEERR_API=your_overseerr_api_key
|
|
|
|
RADARR_URL=http://radarr:7878
|
|
RADARR_API=your_radarr_api_key
|
|
|
|
SONARR_URL=http://sonarr:8989
|
|
SONARR_API=your_sonarr_api_key
|
|
|
|
# Optional — enables watch time stats and ghost/watch-rate alerts
|
|
TAUTULLI_URL=http://tautulli:8181
|
|
TAUTULLI_API=your_tautulli_api_key
|
|
|
|
# Optional — if your services use self-signed certs
|
|
# NODE_TLS_REJECT_UNAUTHORIZED=0
|
|
```
|
|
|
|
### 3. Run
|
|
|
|
```bash
|
|
npm run dev # development
|
|
npm run build && npm start # production
|
|
```
|
|
|
|
---
|
|
|
|
## Alerts
|
|
|
|
Alerts are generated on every stats refresh and persisted in `data/alerts.json` (gitignored). They have two states — **Open** and **Closed** — and can be manually closed with a per-category cooldown, or auto-resolved when the underlying condition clears.
|
|
|
|
### Content alerts
|
|
|
|
These are keyed per piece of media, not per user. If multiple users requested the same item they're grouped into a single alert.
|
|
|
|
---
|
|
|
|
#### Not Downloaded
|
|
|
|
> A movie or TV show was approved but no file exists in Radarr/Sonarr.
|
|
|
|
| Parameter | Default | Description |
|
|
|---|---|---|
|
|
| `UNFULFILLED_MIN_AGE_HOURS` | `12` | Hours since approval before alerting. Prevents noise on brand-new requests. |
|
|
|
|
- Skipped if Radarr reports `isAvailable: false` (unreleased) or Sonarr reports `status: "upcoming"`.
|
|
- **Auto-resolves** when the file appears.
|
|
- Manual close: no cooldown — reopens on the next refresh if the file still isn't there.
|
|
|
|
---
|
|
|
|
#### Incomplete Download
|
|
|
|
> An ended TV series is missing one or more episodes.
|
|
|
|
| Parameter | Default | Description |
|
|
|---|---|---|
|
|
| `UNFULFILLED_MIN_AGE_HOURS` | `12` | Hours since approval before alerting. |
|
|
| Completion threshold | `100%` | Any missing episode on a finished series triggers this alert. |
|
|
|
|
- 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: no cooldown — reopens on the next refresh if episodes are still missing.
|
|
|
|
---
|
|
|
|
#### Pending Approval
|
|
|
|
> A request has been sitting unapproved for too long.
|
|
|
|
| Parameter | Default | Description |
|
|
|---|---|---|
|
|
| `PENDING_MIN_AGE_DAYS` | `2` | Days a request must be pending before alerting. |
|
|
|
|
- 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: no cooldown — reopens on the next refresh if still pending.
|
|
|
|
---
|
|
|
|
### User behavior alerts
|
|
|
|
These fire once per user. Ghost Requester takes priority over Low Watch Rate — a user will only ever have one behavior alert open at a time. Both require the user to be "established" (at least one request older than `USER_MIN_AGE_DAYS`) to avoid flagging new users.
|
|
|
|
> Requires Tautulli to be configured.
|
|
|
|
| Parameter | Default | Description |
|
|
|---|---|---|
|
|
| `USER_MIN_AGE_DAYS` | `14` | Days since oldest request before a user is eligible for behavior alerts. |
|
|
|
|
---
|
|
|
|
#### Ghost Requester
|
|
|
|
> A user hasn't watched anything on Plex since before their last N approved requests were made.
|
|
|
|
Rather than checking lifetime play counts, this looks at recency: if a user's last Plex activity predates all of their most recent N approved requests, they're not watching what they're requesting.
|
|
|
|
| Parameter | Default | Description |
|
|
|---|---|---|
|
|
| `GHOST_RECENT_REQUESTS` | `5` | Number of recent approved requests to evaluate. Also the minimum required before the alert can fire. |
|
|
|
|
- Manual close cooldown: **7 days**.
|
|
|
|
---
|
|
|
|
#### Low Watch Rate
|
|
|
|
> A user watches a small fraction of what they request.
|
|
|
|
| Parameter | Default | Description |
|
|
|---|---|---|
|
|
| `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: **7 days**.
|
|
|
|
---
|
|
|
|
### System alerts
|
|
|
|
#### No Tautulli Watch Data
|
|
|
|
> Tautulli is configured but no plays matched any Overseerr user.
|
|
|
|
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: no cooldown.
|
|
|
|
---
|
|
|
|
## Alert lifecycle
|
|
|
|
```
|
|
Condition detected
|
|
│
|
|
▼
|
|
[OPEN] ◄──────────────────────────────────────┐
|
|
│ │
|
|
┌────┴───────────────┐ Cooldown │
|
|
│ │ expires │
|
|
▼ ▼ │
|
|
Condition Manually │
|
|
clears closed │
|
|
│ │ │
|
|
▼ ▼ │
|
|
[AUTO-RESOLVED] [CLOSED] ──── Condition ────────┘
|
|
no cooldown cooldown returns after
|
|
reopens suppresses cooldown
|
|
immediately re-open
|
|
```
|
|
|
|
- **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.
|