Skip to content

Commit 8429eae

Browse files
author
FirstGameLife
committed
First commit
0 parents  commit 8429eae

56 files changed

Lines changed: 5002 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.dockerignore

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
target/
2+
.git/
3+
.github/
4+
.idea/
5+
*.iml
6+
.mvn/
7+
mvnw
8+
mvnw.cmd
9+
docker-compose.yml
10+
README.md
11+
LICENSE
12+
.env

.github/workflows/ci.yml

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: ["**"]
6+
pull_request:
7+
8+
jobs:
9+
build:
10+
runs-on: ubuntu-latest
11+
steps:
12+
- uses: actions/checkout@v4
13+
14+
- name: Set up JDK 21
15+
uses: actions/setup-java@v4
16+
with:
17+
distribution: temurin
18+
java-version: "21"
19+
cache: maven
20+
21+
- name: Build and test
22+
run: |
23+
chmod +x mvnw
24+
./mvnw -B verify
25+
26+
- name: Upload test reports on failure
27+
if: failure()
28+
uses: actions/upload-artifact@v4
29+
with:
30+
name: surefire-reports
31+
path: target/surefire-reports/

.github/workflows/docker.yml

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
name: Docker image
2+
3+
on:
4+
push:
5+
tags: ["v*"]
6+
7+
jobs:
8+
publish:
9+
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
packages: write
13+
steps:
14+
- uses: actions/checkout@v4
15+
16+
- name: Log in to GHCR
17+
uses: docker/login-action@v3
18+
with:
19+
registry: ghcr.io
20+
username: ${{ github.actor }}
21+
password: ${{ secrets.GITHUB_TOKEN }}
22+
23+
- name: Extract image metadata
24+
id: meta
25+
uses: docker/metadata-action@v5
26+
with:
27+
images: ghcr.io/${{ github.repository }}
28+
tags: |
29+
type=semver,pattern={{version}}
30+
type=semver,pattern={{major}}.{{minor}}
31+
latest
32+
33+
- name: Set up Buildx
34+
uses: docker/setup-buildx-action@v3
35+
36+
- name: Build and push
37+
uses: docker/build-push-action@v6
38+
with:
39+
context: .
40+
push: true
41+
tags: ${{ steps.meta.outputs.tags }}
42+
labels: ${{ steps.meta.outputs.labels }}
43+
cache-from: type=gha
44+
cache-to: type=gha,mode=max

.gitignore

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Maven
2+
target/
3+
!.mvn/wrapper/maven-wrapper.jar
4+
5+
# IDE
6+
.idea/
7+
*.iml
8+
.vscode/
9+
.settings/
10+
.classpath
11+
.project
12+
13+
# OS
14+
.DS_Store
15+
Thumbs.db
16+
17+
# Local environment (never commit secrets)
18+
.env
19+
*.local.yml
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
wrapperVersion=3.3.4
2+
distributionType=only-script
3+
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip

Dockerfile

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# ---------- Build stage ----------
2+
FROM maven:3.9-eclipse-temurin-21 AS build
3+
WORKDIR /build
4+
5+
# Cache dependencies separately from sources for fast rebuilds
6+
COPY pom.xml .
7+
RUN mvn -B -q dependency:go-offline
8+
9+
COPY src ./src
10+
# Tests run in CI; the image build only packages
11+
RUN mvn -B -q package -DskipTests
12+
13+
# ---------- Runtime stage ----------
14+
FROM eclipse-temurin:21-jre-alpine
15+
RUN addgroup -S cineseekerr && adduser -S cineseekerr -G cineseekerr
16+
USER cineseekerr
17+
WORKDIR /app
18+
19+
COPY --from=build /build/target/cineseekerr-*.jar app.jar
20+
21+
# Container-aware JVM sizing; the bot is tiny, keep the footprint small
22+
ENTRYPOINT ["java", "-XX:MaxRAMPercentage=75.0", "-jar", "app.jar"]

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Emanuele Patruno
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

README.md

Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
# 🍿 CineSeekerr
2+
3+
[![CI](https://github.com/Emanuele-Patruno/cineseekerr/actions/workflows/ci.yml/badge.svg)](https://github.com/Emanuele-Patruno/cineseekerr/actions/workflows/ci.yml)
4+
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
5+
![Java 21](https://img.shields.io/badge/Java-21-orange)
6+
![Spring Boot 3](https://img.shields.io/badge/Spring%20Boot-3.5-green)
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.

docker-compose.yml

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
# Example deployment for a NAS (UGREEN UGOS, Synology, ...) that already runs a media
2+
# stack. Only the bot service is defined here — Prowlarr, FlareSolverr, qBittorrent and
3+
# Plex are assumed to exist already; the bot just needs to share a Docker network with
4+
# Prowlarr and qBittorrent so it can reach them by container name.
5+
#
6+
# 1. Find the network your media stack uses: docker network ls
7+
# 2. Adjust the `networks` section below accordingly.
8+
# 3. Fill in the environment variables (or use an .env file next to this compose file).
9+
# 4. docker compose up -d
10+
11+
services:
12+
cineseekerr:
13+
# Use the prebuilt image...
14+
image: ghcr.io/emanuele-patruno/cineseekerr:latest
15+
# ...or build from source instead:
16+
# build: .
17+
container_name: cineseekerr
18+
restart: unless-stopped
19+
environment:
20+
# --- Telegram ---
21+
# Token from @BotFather
22+
TELEGRAM_BOT_TOKEN: "123456789:AAF..."
23+
# Comma-separated chat IDs allowed to use the bot (get yours from @userinfobot).
24+
# Everyone else is silently ignored.
25+
TELEGRAM_ALLOWED_CHAT_IDS: "123456789"
26+
27+
# --- TMDB (movie disambiguation) ---
28+
# Either a v3 API key or a v4 Read Access Token, both work.
29+
TMDB_API_KEY: "..."
30+
# Language for titles/plots shown in chat (default it-IT)
31+
# TMDB_LANGUAGE: "it-IT"
32+
33+
# --- Prowlarr (indexer search) ---
34+
# Container name + internal port, NOT the NAS IP: the bot talks over the
35+
# shared Docker network.
36+
PROWLARR_URL: "http://prowlarr:9696"
37+
# Prowlarr → Settings → General → API Key
38+
PROWLARR_API_KEY: "..."
39+
40+
# --- qBittorrent (download) ---
41+
QBITTORRENT_URL: "http://qbittorrent:8080"
42+
QBITTORRENT_USER: "admin"
43+
QBITTORRENT_PASS: "..."
44+
45+
# --- Media ---
46+
# Save path AS SEEN BY THE QBITTORRENT CONTAINER. The bot never touches the
47+
# filesystem itself (renames go through the qBittorrent API), so no volume
48+
# mount is needed here.
49+
MEDIA_ROOT_FOLDER: "/volume1/media/Film"
50+
51+
# How often to poll qBittorrent for completed downloads (ISO-8601, default PT30S)
52+
# DOWNLOAD_POLL_INTERVAL: "PT30S"
53+
networks:
54+
- media
55+
56+
networks:
57+
# The pre-existing network shared with Prowlarr/qBittorrent. If your stack was
58+
# created by another compose project, mark it external and use its real name
59+
# (e.g. "mediastack_default").
60+
media:
61+
external: true

docs/.gitkeep

Whitespace-only changes.

0 commit comments

Comments
 (0)