{"id":50742230,"url":"https://github.com/o51r15/inspectarr","last_synced_at":"2026-06-10T18:00:34.128Z","repository":{"id":363088341,"uuid":"1261891374","full_name":"o51r15/inspectarr","owner":"o51r15","description":"Torrent watchdog for *arr ecosystems. Polls qBittorrent categories, detects downloads that match configurable bad-file rules (e.g. .exe files in a TV category), blocklists them in Sonarr (Radarr planned), deletes the torrent and files, logs all events to JSON Lines, and notifies via Pushover.","archived":false,"fork":false,"pushed_at":"2026-06-07T11:00:28.000Z","size":20,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-07T12:20:17.045Z","etag":null,"topics":["arrs","arrstack","plex","radarr","sonarr"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/o51r15.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":"ROADMAP.md","authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-07T09:43:46.000Z","updated_at":"2026-06-07T11:00:31.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/o51r15/inspectarr","commit_stats":null,"previous_names":["o51r15/inspectarr"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/o51r15/inspectarr","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o51r15%2Finspectarr","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o51r15%2Finspectarr/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o51r15%2Finspectarr/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o51r15%2Finspectarr/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/o51r15","download_url":"https://codeload.github.com/o51r15/inspectarr/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/o51r15%2Finspectarr/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34163252,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-10T02:00:07.152Z","response_time":89,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["arrs","arrstack","plex","radarr","sonarr"],"created_at":"2026-06-10T18:00:25.541Z","updated_at":"2026-06-10T18:00:34.107Z","avatar_url":"https://github.com/o51r15.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"\u003cp align=\"center\"\u003e\n  \u003cimg src=\"assets/inspectarr-banner.jpg\" alt=\"Inspectarr\" width=\"900\"\u003e\n\u003c/p\u003e\n\n# inspectarr\n\nTorrent watchdog for *arr ecosystems. Polls qBittorrent categories, detects\ndownloads that match configurable bad-file rules (e.g. `.exe` files in a TV\ncategory), blocklists them in Sonarr, Radarr, or Lidarr, deletes the torrent and\nfiles, logs all events to JSON Lines, and notifies via Pushover.\n\nRuns two ways: a **web UI with a built-in scheduler daemon** (`web.py`), or a\n**one-shot CLI** (`inspectarr.py`) for manual runs and testing.\n\n---\n\n## Quick Start (Web UI)\n\n```bash\ncp config.example.yaml config.yaml\n# edit config.yaml with your URLs, credentials, and rules\npip install -r requirements.txt\npython3 web.py\n```\n\nOpen `http://\u003chost\u003e:8585`. The scheduler starts **off** — enable it from the\ndashboard once you've confirmed your config and categories are correct.\n\n## Quick Start (CLI)\n\n```bash\ncp config.example.yaml config.yaml\npython3 inspectarr.py --dry-run    # confirm matches without deleting\npython3 inspectarr.py              # live run\n```\n\n## Requirements\n\n- Python 3.12+\n- qBittorrent with Web UI enabled\n- Sonarr v4\n\n```bash\npip install -r requirements.txt   # requests, pyyaml, flask\n```\n\n---\n\n## Web UI\n\nServed on port `8585` by default (configurable via `web.port`). Four pages:\n\n| Page | What it does |\n|---|---|\n| **Dashboard** | Scheduler status, last-scan stats (checked / flagged / actioned), last flagged torrent, recent run history. Live-updates every 5s. |\n| **Scheduler** | Start/stop the daemon, run-now, poll interval, last/next run, run history. |\n| **Logs** | Paginated JSON Lines viewer (100/page), level filter, color-coded badges, auto-refresh, clear-log. |\n| **Config** | Full form editor for every option, plus a raw-YAML mode for advanced edits. Test-connection buttons for qBittorrent, Sonarr, Radarr, and Lidarr. Rules are a dynamic add/remove builder. |\n\nThe scheduler reloads `config.yaml` from disk before every scan, so changes\nsaved in the Config page take effect on the next cycle — no restart needed.\nChanging `web.port` is the one exception; that requires a restart.\n\n---\n\n## Docker\n\nThe container runs the web UI + scheduler by default.\n\n```bash\ndocker build -t inspectarr .\ndocker run -d \\\n  --name inspectarr \\\n  -p 8585:8585 \\\n  -v ./data:/app/data \\\n  -v ./config.yaml:/app/config.yaml \\\n  inspectarr\n```\n\nOr with Docker Compose (using the included `docker-compose.yml`):\n\n```bash\ndocker compose up -d\n```\n\nTo run a one-off CLI scan against a running container:\n\n```bash\ndocker exec inspectarr python inspectarr.py --dry-run\n```\n\n---\n\n## Systemd (auto-start on boot)\n\nA `inspectarr.service` unit file is included. Copy it and enable it:\n\n```bash\nsudo cp inspectarr.service /etc/systemd/system/\n```\n\nThen enable and start:\n\n```bash\nsudo systemctl daemon-reload\nsudo systemctl enable inspectarr\nsudo systemctl start inspectarr\n```\n\nCheck status and logs:\n\n```bash\nsudo systemctl status inspectarr\nsudo journalctl -u inspectarr -f\n```\n\n---\n\n## Configuration\n\nSee `config.example.yaml` for all options with inline documentation.\n\nKey settings:\n\n| Setting | Purpose |\n|---|---|\n| `rules[].conditions.match_mode` | `any` = flag on any bad file; `primary` = only if largest file is bad |\n| `rules[].conditions.bad_extensions` | List of file extensions to flag (e.g. `.exe`, `.zip`) |\n| `rules[].conditions.min_file_size_mb` | Flag if the primary (largest) file is below this size in MB |\n| `rules[].conditions.bad_filename_patterns` | List of regex patterns matched against filenames |\n| `on_arr_failure` | `delete` = remove from qBit anyway; `abort` = skip and retry |\n| `poll_interval_seconds` | How often the scheduler daemon scans (default: 300) |\n| `retry.max_attempts` | How many times to retry before giving up (default: 10) |\n| `retry.interval_seconds` | Seconds between retry attempts (default: 600) |\n| `web.port` | Web UI port (default: 8585) |\n| `web.scheduler_autostart` | `true` = start the scheduler automatically on launch (default: false) |\n| `dry_run` | `true` = log matches only, no deletions |\n\n---\n\n## CLI Flags\n\n```\npython3 inspectarr.py                    # single scan run\npython3 inspectarr.py --config /path     # alternate config location\npython3 inspectarr.py --dry-run          # override config dry_run=true\npython3 inspectarr.py --daemon           # not in the CLI — use web.py instead\npython3 inspectarr.py --retry-now        # force flush retry queue, then scan\n```\n\nThe continuous scheduler lives in `web.py`; the CLI is single-shot only.\n\n---\n\n## Persistent Data\n\nEverything in `data/` — mount as a Docker volume:\n\n| File | Contents |\n|---|---|\n| `inspectarr.db` | SQLite: processed hashes + retry queue |\n| `inspectarr.log.json` | JSON Lines: one event object per line |\n\n---\n\n## Extending to Other *arrs\n\nSonarr, Radarr, and Lidarr are all supported. To add a new *arr app (e.g. Prowlarr):\n\n1. Implement `core/arrs/\u003capp\u003e.py` mirroring `sonarr.py` against the target API\n2. Register it in `_build_arr_client()` in `core/scanner.py`\n3. Add config fields to `ArrsConfig` in `core/config.py`\n4. Set `arrs.\u003capp\u003e.enabled: true` in config and add rules with `app: \u003capp\u003e`\n\nThe abstract base and scanner already handle the rest.\n\n---\n\n## Project Layout\n\n```\ninspectarr/\n├── inspectarr.py            # CLI entry point (single-shot)\n├── web.py                   # Web UI + scheduler daemon entry point\n├── core/                    # Core logic — no awareness of the UI\n│   ├── config.py            # Config loader + dataclasses\n│   ├── scanner.py           # Main orchestrator\n│   ├── rules.py             # Rule evaluation engine\n│   ├── qbit.py              # qBittorrent Web API v2 client\n│   ├── arrs/\n│   │   ├── base.py          # AbstractArrClient\n│   │   ├── sonarr.py        # Sonarr v4 client\n│   │   ├── radarr.py        # Radarr v3 client\n│   │   └── lidarr.py        # Lidarr v2 client\n│   ├── notifier.py          # Pushover client\n│   └── state.py             # SQLite + JSON Lines log\n├── ui/                      # Web UI layer\n│   ├── scheduler.py         # Background scheduler daemon thread\n│   ├── routes/              # Flask blueprints (dashboard, config, logs, scheduler)\n│   ├── templates/           # Jinja2 templates\n│   └── static/              # CSS + JS + logo.png\n├── assets/\n│   └── inspectarr-banner.jpg\n├── config.example.yaml\n├── docker-compose.yml\n├── inspectarr.service\n├── data/                    # Runtime state (gitignored, Docker volume)\n├── Dockerfile\n└── requirements.txt\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo51r15%2Finspectarr","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fo51r15%2Finspectarr","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fo51r15%2Finspectarr/lists"}