|
| 1 | +# 🍿 CineSeekerr |
| 2 | + |
| 3 | +[](https://github.com/Emanuele-Patruno/cineseekerr/actions/workflows/ci.yml) |
| 4 | +[](LICENSE) |
| 5 | + |
| 6 | + |
| 7 | + |
| 8 | +A self-hosted Telegram bot that turns *"hey, let's watch Dune tonight"* into a movie |
| 9 | +sitting in your Plex library — by chatting with it. It searches your |
| 10 | +[Prowlarr](https://prowlarr.com/) indexers, lets you pick quality/audio/subtitles through |
| 11 | +inline buttons, hands the torrent to [qBittorrent](https://www.qbittorrent.org/) and |
| 12 | +renames the finished download so [Plex](https://www.plex.tv/) matches it perfectly. |
| 13 | + |
| 14 | +No Radarr required — CineSeekerr talks Telegram instead of a web UI, in the same spirit as |
| 15 | +the `*arr` family (Radarr, Sonarr, Prowlarr) it plugs into. Designed for a small home NAS |
| 16 | +stack (built on a UGREEN DXP4800 Plus, but any Docker host works). |
| 17 | + |
| 18 | +<p align="center"> |
| 19 | + <img src="docs/screenshot-placeholder.png" alt="Bot conversation screenshot" width="380"> |
| 20 | + <br><em>(screenshot coming soon)</em> |
| 21 | +</p> |
| 22 | + |
| 23 | +## ✨ Features |
| 24 | + |
| 25 | +- **Movie disambiguation via TMDB** — type a title (even the localized one), pick the |
| 26 | + right movie from up to 5 candidates with year and poster. |
| 27 | +- **Smart release parsing** — resolution, audio languages (incl. Italian scene tags like |
| 28 | + `iTA-ENG`, `DLMux`, `BDMux`), subtitles (`SUB iTA`…), codec and source are extracted |
| 29 | + from every release name. |
| 30 | +- **Dynamic filters, never a dead end** — filter buttons are built from the actual |
| 31 | + results (`Quality: [1080p (14)] [2160p (6)] [720p (3)]`); an option leading to zero |
| 32 | + results is never offered, and steps with a single option are skipped. |
| 33 | +- **MULTI-aware language filtering** — picking *ITA* also includes `MULTI`/`DUAL` |
| 34 | + releases, which usually carry the Italian track. |
| 35 | +- **Top-5 by seeders** — with size, indexer and parsed details, one tap to download. |
| 36 | +- **Plex-ready renaming** — when the download completes the bot renames it to |
| 37 | + `Title (Year)` *through the qBittorrent API*, so seeding continues and the bot needs no |
| 38 | + access to the media volume. |
| 39 | +- **Private by default** — a chat-ID whitelist; anyone else is silently ignored. |
| 40 | +- **One clean message** — the bot edits a single message through the whole flow instead |
| 41 | + of spamming the chat. |
| 42 | + |
| 43 | +## 🏗 Architecture |
| 44 | + |
| 45 | +```mermaid |
| 46 | +flowchart LR |
| 47 | + U[Telegram user] <-->|long polling| B[CineSeekerr] |
| 48 | + B -->|1. disambiguate| T[TMDB API] |
| 49 | + B -->|2. search| P[Prowlarr] |
| 50 | + P --> F[FlareSolverr] |
| 51 | + B -->|3. add torrent + 5. rename| Q[qBittorrent] |
| 52 | + Q -->|4. downloads to| M[/media/Film/] |
| 53 | + X[Plex] -->|watches| M |
| 54 | +``` |
| 55 | + |
| 56 | +The conversation is a per-chat state machine (`bot/`), external services live behind thin |
| 57 | +typed clients (`client/`), and the heart of the project is a pure, dependency-free release |
| 58 | +name parser (`parser/`) with an extensive test suite. |
| 59 | + |
| 60 | +``` |
| 61 | +src/main/java/com/cineseekerr/bot/ |
| 62 | +├── bot/ # state machine, filters, download watcher, Plex rename |
| 63 | +├── client/ # TMDB, Prowlarr, qBittorrent (Spring RestClient) |
| 64 | +├── config/ # typed configuration properties |
| 65 | +├── model/ # records: releases, torrents, parsed attributes |
| 66 | +└── parser/ # ReleaseNameParser — pure, no dependencies |
| 67 | +``` |
| 68 | + |
| 69 | +## 🚀 Setup |
| 70 | + |
| 71 | +### Prerequisites |
| 72 | + |
| 73 | +A Docker host already running **Prowlarr** (with your indexers configured) and |
| 74 | +**qBittorrent**, sharing a Docker network. Plex and FlareSolverr fit the picture but the |
| 75 | +bot doesn't talk to them directly. |
| 76 | + |
| 77 | +### 1. Create the Telegram bot |
| 78 | + |
| 79 | +1. Open [@BotFather](https://t.me/BotFather) → `/newbot`, pick a name and username. |
| 80 | +2. Save the **token** (`123456789:AAF...`). |
| 81 | +3. Get your **chat ID** from [@userinfobot](https://t.me/userinfobot) (just send it any |
| 82 | + message). |
| 83 | + |
| 84 | +### 2. Get the API keys |
| 85 | + |
| 86 | +- **TMDB**: create an account on [themoviedb.org](https://www.themoviedb.org/), then |
| 87 | + *Settings → API*. Both the **v3 API Key** and the **v4 Read Access Token** work — the |
| 88 | + bot auto-detects which one you gave it. |
| 89 | +- **Prowlarr**: *Settings → General → API Key*. |
| 90 | + |
| 91 | +### 3. Deploy |
| 92 | + |
| 93 | +Copy [docker-compose.yml](docker-compose.yml), fill in the environment variables, point |
| 94 | +the `networks` section at the Docker network your media stack uses, then: |
| 95 | + |
| 96 | +```bash |
| 97 | +docker compose up -d |
| 98 | +``` |
| 99 | + |
| 100 | +Send `/start` to your bot. Done. |
| 101 | + |
| 102 | +## ⚙️ Configuration |
| 103 | + |
| 104 | +Everything is configured through environment variables: |
| 105 | + |
| 106 | +| Variable | Required | Default | Description | |
| 107 | +|---|---|---|---| |
| 108 | +| `TELEGRAM_BOT_TOKEN` | ✅ | — | Bot token from BotFather | |
| 109 | +| `TELEGRAM_ALLOWED_CHAT_IDS` | ✅ | — | Comma-separated whitelist of chat IDs; all other chats are ignored | |
| 110 | +| `TMDB_API_KEY` | ✅ | — | TMDB v3 API key **or** v4 Read Access Token | |
| 111 | +| `TMDB_LANGUAGE` | | `it-IT` | Language for titles and plots shown in chat | |
| 112 | +| `TMDB_BASE_URL` | | `https://api.themoviedb.org/3` | Override for proxies | |
| 113 | +| `PROWLARR_URL` | ✅ | — | e.g. `http://prowlarr:9696` (container name on the shared network) | |
| 114 | +| `PROWLARR_API_KEY` | ✅ | — | Prowlarr API key | |
| 115 | +| `QBITTORRENT_URL` | ✅ | — | e.g. `http://qbittorrent:8080` | |
| 116 | +| `QBITTORRENT_USER` | ✅ | — | qBittorrent WebUI username | |
| 117 | +| `QBITTORRENT_PASS` | ✅ | — | qBittorrent WebUI password | |
| 118 | +| `MEDIA_ROOT_FOLDER` | | `/volume1/media/Film` | Save path **as seen by the qBittorrent container** | |
| 119 | +| `DOWNLOAD_POLL_INTERVAL` | | `PT30S` | How often to check for completed downloads (ISO-8601) | |
| 120 | + |
| 121 | +## 💬 Commands |
| 122 | + |
| 123 | +| Command | Effect | |
| 124 | +|---|---| |
| 125 | +| *any text* | Search that movie title | |
| 126 | +| `/cerca <title>` | Same as typing the title | |
| 127 | +| `/stato` | Progress of active downloads (%, speed, ETA), each with a button to stop it | |
| 128 | +| `/annulla` | Cancel the current operation | |
| 129 | +| `/help` | Command list | |
| 130 | + |
| 131 | +## 🛠 Development |
| 132 | + |
| 133 | +```bash |
| 134 | +./mvnw verify # build + full test suite |
| 135 | +./mvnw spring-boot:run # run locally (reads the same env variables) |
| 136 | +docker build -t cineseekerr . |
| 137 | +``` |
| 138 | + |
| 139 | +The project intentionally keeps the parser (`ReleaseNameParser`) pure and framework-free: |
| 140 | +if you want to improve scene-name coverage, that class plus its ~50-case test suite is |
| 141 | +the only place to touch. |
| 142 | + |
| 143 | +Notable test coverage: 130+ tests including the full conversation flow (mocked Telegram), |
| 144 | +qBittorrent session expiry/re-login, dynamic filter invariants ("never offer a |
| 145 | +zero-result option") and 45+ real-world release names. |
| 146 | + |
| 147 | +## 🗺 Roadmap |
| 148 | + |
| 149 | +- [ ] Redis-backed conversation state (interface already in place) |
| 150 | +- [ ] Persist pending downloads across restarts |
| 151 | +- [ ] TV series support |
| 152 | +- [ ] Disk space check before downloading |
| 153 | +- [ ] Optional per-user quality profiles |
| 154 | +- [ ] English UI translation |
| 155 | + |
| 156 | +## 📄 License |
| 157 | + |
| 158 | +[MIT](LICENSE) — do whatever you want, no warranty. |
| 159 | + |
| 160 | +> **Note**: this bot is a private automation tool for your own media stack. Make sure |
| 161 | +> whatever you download complies with the laws of your country. |
0 commit comments