https://github.com/ds17f/watchseries-sonarr
watchseries.bar scraper + Torznab indexer + fake-qBittorrent download client for Sonarr/Radarr.
https://github.com/ds17f/watchseries-sonarr
Last synced: 20 days ago
JSON representation
watchseries.bar scraper + Torznab indexer + fake-qBittorrent download client for Sonarr/Radarr.
- Host: GitHub
- URL: https://github.com/ds17f/watchseries-sonarr
- Owner: ds17f
- Created: 2026-05-23T18:46:09.000Z (2 months ago)
- Default Branch: main
- Last Pushed: 2026-05-23T23:59:27.000Z (2 months ago)
- Last Synced: 2026-05-24T00:23:31.841Z (2 months ago)
- Language: Python
- Size: 107 KB
- Stars: 0
- Watchers: 0
- Forks: 0
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# watchseries-sonarr
> Use `watchseries.bar` as a normal indexer + download client for Sonarr and
> Radarr — or as a standalone CLI to grab a show.
Two things in one repo:
1. **`download.py`** — a CLI that takes a `watchseries.bar` URL and writes
MP4s to disk. Useful by itself.
2. **A small service** that pretends to be a [Torznab](https://torznab.github.io/spec-1.3-draft/)
indexer **and** a qBittorrent v2 download client. Sonarr/Radarr/Prowlarr
treat it like any other indexer + torrent client; under the hood it
scrapes `watchseries.bar` via [api.videasy.net](https://www.videasy.net/),
decrypts the source list with a bundled WASM module, and `ffmpeg`s the
HLS stream to MP4.
Source extraction is vendored from
[walterwhite-69/Videasy.net-Decryptor](https://github.com/walterwhite-69/Videasy.net-Decryptor) (MIT).
## Why both pieces
The CLI is the smallest possible thing: "I want this show, run a command,
get files." The service exists for when you want to keep doing that without
running commands by hand — Sonarr's normal search/grab workflow does it for
you, with renaming, library import, and notifications.
You can use one without the other. They share `src/watchseries/scraper.py`
so any improvements benefit both.
---
## Use case 1 — CLI
### Setup
```sh
git clone https://github.com/ds17f/watchseries-sonarr.git
cd watchseries-sonarr
npm install
pip install fastapi 'uvicorn[standard]' python-multipart # service deps; CLI uses stdlib only
```
System deps: Python 3.10+, Node.js 18+, `ffmpeg` on PATH.
### Run
```sh
# Whole series — walks seasons until none more are available.
python3 download.py "https://watchseries.bar/tv/men-of-a-certain-age/16208"
# Single season at lower quality, custom output dir.
python3 download.py URL --season 1 --end-season 1 --quality 720p --out /mnt/media
# Single episode.
python3 download.py URL --season 2 --episode 5 --end-season 2
# Movie.
python3 download.py "https://watchseries.bar/movie//"
```
Default output: `~/Downloads/watchseries//Season NN/ - sNNeMM.mp4`.
Already-downloaded episodes (>1MB) are skipped on re-run, so it's safe to
resume after an interruption.
Flags:
| Flag | Default | Meaning |
|----------------|-------------|---------|
| `--season N` | `1` | start at season N |
| `--episode N` | `1` | start at episode N (within the start season) |
| `--end-season N` | none | stop after season N |
| `--quality Q` | `1080p` | preferred quality (`1080p`/`720p`/`360p`); falls back if unavailable |
| `--out DIR` | `~/Downloads/watchseries` | output root |
---
## Use case 2 — service wired into Sonarr / Radarr
### What it gives you
- An indexer at `http://:8765/torznab/api` that responds to
`t=caps`, `t=tvsearch`, `t=movie`, `t=search` — i.e. exactly what
Prowlarr expects from a "Generic Torznab" entry.
- A qBittorrent-compatible API at `http://:8765/api/v2/...` that
satisfies Sonarr's "qBittorrent" download client.
The two routers live in one FastAPI process (`src/watchseries/main.py`), so
it's one container, one port, one log.
### Run with Docker
```sh
git clone https://github.com/ds17f/watchseries-sonarr.git
cd watchseries-sonarr
cp .env.example .env
$EDITOR .env # at minimum set DOWNLOADS_DIR to where you want files
docker compose up -d --build
curl http://localhost:8765/health
# {"status":"ok","missing":[],"download_dir":"/downloads","jobs":0}
```
### Configuration via `.env`
| Variable | Default | Purpose |
|----------------------|------------------------|---------|
| `DOWNLOADS_DIR` | `./downloads` | Host path bind-mounted as `/downloads` inside the container. Point at Sonarr's existing download folder so import works without remote-path mapping. |
| `HOST_PORT` | `8765` | Host port to expose the service on. |
| `PUID` / `PGID` | `1000` / `1000` | User/group the container runs as. Must own the `DOWNLOADS_DIR`. (Common NAS setups use 1000/100.) |
| `TZ` | `UTC` | Timezone (for log timestamps + scheduling). |
| `WSG_INDEXER_TITLE` | `WatchSeries.bar` | Display name in the Torznab capabilities. |
| `TMDB_API_KEY` | _(unset)_ | Optional. If set, the indexer can resolve `tvdbid`/`imdbid` and do text search. Without it, callers must pass `tmdbid` directly. Get a key at https://www.themoviedb.org/settings/api. |
### Wire it into Sonarr
#### 1. Add the indexer (in Prowlarr, recommended)
Prowlarr → Indexers → Add → **Generic Torznab**:
- **URL:** `http://watchseries-grabber:8765/torznab` (use the container name if
Prowlarr is in the same Docker network; otherwise the host + port)
- **API Path:** `/api`
- **API Key:** any non-empty string (we don't enforce one)
- **Categories:** 5000 (TV), 2040 (Movies/HD)
Then sync to Sonarr / Radarr. You can also add it directly in
Sonarr → Settings → Indexers → Add → Torznab if you don't use Prowlarr.
#### 2. Add the download client
Sonarr → Settings → Download Clients → Add → **qBittorrent**:
- **Host:** `watchseries-grabber` (Docker name) or your host's IP
- **Port:** `8765` (or whatever `HOST_PORT` you set)
- **URL Base:** _(leave blank)_
- **Username:** `watchseries`
- **Password:** anything
- **Category:** `tv-watchseries` (Sonarr) / `movies-watchseries` (Radarr)
- **Use SSL:** off
Authentication is a no-op; we accept any creds and any login attempt
succeeds.
#### 3. Path mapping
Inside the container files are written to `/downloads`, which is the
`DOWNLOADS_DIR` host path you set in `.env`. If that same host path is
mounted into your Sonarr container as `/downloads` too (the typical setup),
Sonarr's import works with no Remote Path Mapping.
If Sonarr sees the same files under a different path, configure
Sonarr → Settings → Download Clients → Remote Path Mappings.
### Routing specific shows through this indexer
The cleanest way to "make Sonarr auto-grab show X from WatchSeries and ignore
torrent indexers for it": **indexer tags**.
1. **Create a tag.** Sonarr → Settings → Tags → `+` → e.g. `watchseries`.
(Tags are auto-created the first time you assign one, too.)
2. **Tag the indexer.** Sonarr → Settings → Indexers → WatchSeries → Tags:
`watchseries`. Same in Radarr if you use it for movies.
3. **Tag each show you want routed here.** Sonarr → Series → Edit → Tags:
`watchseries`. Bulk-edit works too.
Once both sides share the tag, Sonarr's behaviour becomes:
- **Tagged series → only the matching indexers search it.** WatchSeries
is the only source for those shows; RSS sync (~15 min) auto-grabs new
monitored episodes; interactive search returns only WatchSeries results.
- **Untagged series → only untagged indexers search it.** Other shows keep
using torrents as normal; WatchSeries never sees them.
This is preferred over a Custom-Format-by-release-group approach because:
- No score fights — a higher-scored torrent won't beat the WatchSeries
release just because it's a higher quality tier you don't actually care
about for that show.
- Search load is per-show. We don't get hammered by RSS queries for shows
that aren't on watchseries.bar anyway.
- Clean per-show opt-in, no global preferences to maintain.
If you instead want WatchSeries as a *fallback* (only used when nothing
else returns), no tag config needed — just lower its **Indexer Priority**
(Sonarr → Indexer → Priority field, lower number = higher priority, so set
WatchSeries to 50). Sonarr considers all indexers but breaks ties by
priority and falls back to lower-priority sources when higher ones return
nothing.
### Caveats
- **`TMDB_API_KEY` strongly recommended.** Without it, only ID-based searches
resolve — Sonarr usually passes `tvdbid` for shows, which means we need
to look up the TMDB id, which needs the key.
- **One upstream provider currently.** We use `mb-flix` (one of ~12
endpoints exposed by `api.videasy.net`). If it stops working, swap the
`API` constant in `src/watchseries/scraper.py`. Known alternates:
`cdn`, `moviebox`, `1movies`, `m4uhd`, `hdmovie`, `lamovie`, `superflix`,
`meine`.
- **No real torrent.** The `urn:btih:` info-hash in our magnets is a
deterministic SHA-1 of the request payload, not a real torrent hash. Don't
put us behind a VPN that blocks non-torrent traffic on these "magnet"
URIs — they're plain HTTP under the hood.
### How does it work?
When Sonarr searches the indexer, we return RSS items with magnets like:
```
magnet:?xt=urn:btih:
&dn=
&xs=watchseries://tv//s/e?quality=1080p&title=...&year=...
```
The `xs=` parameter carries the real payload — media type, TMDB id,
season/episode, clean title (needed for the upstream API match), year.
Sonarr forwards this magnet to its download client; our fake qBittorrent
unwraps it and starts a job. The job runs the same pipeline as the CLI:
```
api.videasy.net/mb-flix → hex ciphertext
node decrypt.js → JSON {sources: [{url, quality}], subtitles: [...]}
ffmpeg → out.mp4 in /downloads//Season NN/
```
While downloading, the job reports `state: downloading` with a `progress`
fraction computed from ffmpeg's stderr; when done it reports `state:
pausedUP` with `content_path` pointing at the folder Sonarr should import.
---
## Layout
```
.
├── decrypt.js # WASM bridge (vendored, MIT)
├── module.wasm
├── package.json # crypto-js, hashids
├── requirements.txt # fastapi, uvicorn, python-multipart
├── Dockerfile
├── docker-compose.yml
├── docker-compose.homelab.yml # external network override (optional)
├── .env.example
├── download.py # CLI entry point
└── src/watchseries/
├── scraper.py # videasy fetch + decrypt + ffmpeg
├── jobs.py # JobManager + magnet codec
├── torznab.py # /torznab/api router
├── fakeqbt.py # /api/v2 router (qBittorrent mimic)
└── main.py # FastAPI app
```
## Legal
This scrapes a piracy aggregator. Where you are matters. Use accordingly.