Compare commits
3 Commits
main
...
fb9b8390a3
Author | SHA1 | Date | |
---|---|---|---|
fb9b8390a3 | |||
319658b52a | |||
ddc21e9a57 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -69,7 +69,12 @@ staticfiles/
|
||||
media/
|
||||
|
||||
# Database
|
||||
/db.sqlite3
|
||||
.data/
|
||||
data/
|
||||
data/db.sqlite3
|
||||
./db.sqlite3
|
||||
db.sqlite3
|
||||
.db.sqlite3
|
||||
|
||||
# Environment files
|
||||
.env.local
|
||||
|
311
README.md
311
README.md
@@ -1,228 +1,133 @@
|
||||
# Subscribarr
|
||||
|
||||
# Subscribarr
|
||||
<p align="center">
|
||||
<a href="LICENSE"><img src="https://img.shields.io/badge/license-MIT-green.svg" alt="License MIT"></a>
|
||||
<img src="https://img.shields.io/badge/python-3.13-blue.svg" alt="Python 3.13">
|
||||
<img src="https://img.shields.io/badge/django-5.x-092e20?logo=django&logoColor=white" alt="Django 5">
|
||||
<img src="https://img.shields.io/badge/docker-ready-2496ED?logo=docker&logoColor=white" alt="Docker ready">
|
||||
<img src="https://img.shields.io/badge/ntfy-supported-4c1" alt="ntfy supported">
|
||||
<img src="https://img.shields.io/badge/Apprise-supported-4c1" alt="Apprise supported">
|
||||
</p>
|
||||
|
||||
Subscribarr is a notification tool for the *Arr ecosystem (Sonarr, Radarr) and Jellyfin. Users can subscribe to shows/movies; when new episodes/releases are available (and actually present), Subscribarr sends email notifications.
|
||||
<!-- Optional dynamic badges (uncomment and replace OWNER/REPO / IMAGE if you want):
|
||||
<p align="center">
|
||||
<a href="https://github.com/OWNER/REPO/releases"><img src="https://img.shields.io/github/v/release/OWNER/REPO?sort=semver" alt="latest release"></a>
|
||||
<a href="https://hub.docker.com/r/OWNER/IMAGE"><img src="https://img.shields.io/docker/pulls/OWNER/IMAGE" alt="docker pulls"></a>
|
||||
<a href="https://github.com/OWNER/REPO/commits/main"><img src="https://img.shields.io/github/commit-activity/m/OWNER/REPO" alt="commit activity"></a>
|
||||
</p>
|
||||
-->
|
||||
|
||||
---
|
||||
|
||||
## Screenshots
|
||||
|
||||

|
||||

|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
---
|
||||
Lightweight web UI for Sonarr/Radarr subscriptions with Jellyfin login, calendar, and flexible notifications via Email, ntfy, and Apprise.
|
||||
|
||||
## Features
|
||||
- Sign in with Jellyfin (no separate user store)
|
||||
- Sonarr/Radarr‑style calendar (upcoming episodes/movies)
|
||||
- Subscribe/unsubscribe from the UI (series & movies)
|
||||
- Admin overview of all users’ subscriptions with posters
|
||||
- Per‑user notification channels:
|
||||
- Email (SMTP)
|
||||
- ntfy (Bearer token or Basic Auth)
|
||||
- Apprise (Discord, Gotify, Pushover, Webhooks, and many more)
|
||||
- Docker‑ready; environment‑driven security (ALLOWED_HOSTS, CSRF, proxy)
|
||||
|
||||
- **Login via Jellyfin** (use your Jellyfin account; admin status respected)
|
||||
- **Subscriptions** for series and movies; duplicate-send protection per user/day
|
||||
- **Email notifications** (SMTP configurable)
|
||||
- **Sonarr/Radarr integration** (calendar/status; optional file-presence check)
|
||||
- **Settings UI** for Jellyfin/Arr/Mail/Account
|
||||
- **Periodic check via cron** calling `manage.py check_new_media`
|
||||
## Screenshots
|
||||
<p align="center">
|
||||
<img src="./screenshots/SCR-20250811-lfrm.png" alt="Settings" width="800"><br/>
|
||||
<img src="./screenshots/SCR-20250811-lfvc.png" alt="Subscriptions" width="800"><br/>
|
||||
<img src="./screenshots/SCR-20250811-lfod.png" alt="Overview" width="800"><br/>
|
||||
<img src="./screenshots/SCR-20250811-lfyq.png" alt="Search" width="800"><br/>
|
||||
<img src="./screenshots/SCR-20250811-lgau.png" alt="Details" width="800"><br/>
|
||||
<img src="./screenshots/SCR-20250811-lgcz.png" alt="Notifications" width="800">
|
||||
</p>
|
||||
|
||||
---
|
||||
|
||||
## Architecture / Tech Stack
|
||||
|
||||
- **Backend:** Django + Django REST Framework
|
||||
- **Apps (examples):** `arr_api`, `accounts`, `settingspanel`
|
||||
- **Database:** SQLite by default (path configurable via env)
|
||||
- **Auth:** Jellyfin API (admin mirrored from Jellyfin policy)
|
||||
|
||||
---
|
||||
|
||||
## Quickstart (Docker)
|
||||
|
||||
### 1) Clone & run
|
||||
## Quickstart (Docker Compose)
|
||||
1) Ensure the lockfile matches your Pipfile (e.g., after adding packages):
|
||||
```bash
|
||||
git clone https://gitea.js-devop.de/jschaufuss/Subscribarr.git
|
||||
cd Subscribarr
|
||||
docker compose up -d --build
|
||||
pipenv lock
|
||||
```
|
||||
|
||||
- Default app port inside the container: **8000**
|
||||
- Optional: set `CRON_SCHEDULE` (e.g., `*/30 * * * *`) to enable periodic checks
|
||||
|
||||
### 2) Minimal `docker-compose.yml` (example)
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
subscribarr:
|
||||
build: .
|
||||
container_name: subscribarr
|
||||
ports:
|
||||
- "8081:8000"
|
||||
environment:
|
||||
# Django
|
||||
- DJANGO_DEBUG=true
|
||||
- USE_X_FORWARDED_HOST=true
|
||||
- DJANGO_SECURE_PROXY_SSL_HEADER=true
|
||||
- DJANGO_CSRF_COOKIE_SECURE=true
|
||||
- DJANGO_SESSION_COOKIE_SECURE=true
|
||||
- DJANGO_ALLOWED_HOSTS=*
|
||||
- DJANGO_SECRET_KEY=change-me
|
||||
- DB_PATH=/app/data/db.sqlite3
|
||||
- NOTIFICATIONS_ALLOW_DUPLICATES=false
|
||||
- DJANGO_CSRF_TRUSTED_ORIGINS="https://subscribarr.local.js-devop.de"
|
||||
# App Settings (optional, otherwise use first-run setup)
|
||||
#- JELLYFIN_URL=
|
||||
#- JELLYFIN_API_KEY=
|
||||
#- SONARR_URL=
|
||||
#- SONARR_API_KEY=
|
||||
#- RADARR_URL=
|
||||
#- RADARR_API_KEY=
|
||||
#- MAIL_HOST=
|
||||
#- MAIL_PORT=
|
||||
#- MAIL_SECURE=
|
||||
#- MAIL_USER=
|
||||
#- MAIL_PASSWORD=
|
||||
#- MAIL_FROM=
|
||||
# Admin bootstrap (optional)
|
||||
#- ADMIN_USERNAME=
|
||||
#- ADMIN_PASSWORD=
|
||||
#- ADMIN_EMAIL=
|
||||
# Cron schedule (default every 30min)
|
||||
- CRON_SCHEDULE=*/30 * * * *
|
||||
volumes:
|
||||
- ./data:/app/data
|
||||
restart: unless-stopped
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Environment Variables (selection)
|
||||
|
||||
| Variable | Purpose |
|
||||
|---|---|
|
||||
| `DJANGO_DEBUG` | `true` / `false` (disable in production). |
|
||||
| `DJANGO_ALLOWED_HOSTS` | Comma list of allowed hosts (e.g., `example.com,localhost`). |
|
||||
| `DJANGO_SECRET_KEY` | Django secret key. |
|
||||
| `DB_PATH` | SQLite path, e.g., `/app/data/db.sqlite3`. |
|
||||
| `NOTIFICATIONS_ALLOW_DUPLICATES` | Allow duplicate sends (`true`/`false`). |
|
||||
| `ADMIN_USERNAME` / `ADMIN_PASSWORD` / `ADMIN_EMAIL` | Optional: bootstrap an admin user on first run. |
|
||||
| `JELLYFIN_URL` / `JELLYFIN_API_KEY` | Base URL + API key for Jellyfin. |
|
||||
| `SONARR_URL` / `SONARR_API_KEY` | Base URL + API key for Sonarr. |
|
||||
| `RADARR_URL` / `RADARR_API_KEY` | Base URL + API key for Radarr. |
|
||||
| `MAIL_HOST` / `MAIL_PORT` / `MAIL_SECURE` | SMTP host/port/security (`starttls` / `ssl` / empty). |
|
||||
| `MAIL_USER` / `MAIL_PASSWORD` / `MAIL_FROM` | SMTP auth + sender address. |
|
||||
| `CRON_SCHEDULE` | Cron interval for periodic checks (e.g., `*/30 * * * *`). |
|
||||
|
||||
---
|
||||
|
||||
## First Run
|
||||
|
||||
1. Start the container (or dev server) and open `http://<host>:8081`.
|
||||
2. Complete the **first-time setup**: Jellyfin URL/API key (required), optional Sonarr/Radarr, SMTP.
|
||||
3. **Sign in** with Jellyfin credentials (admin users in Jellyfin become admins in Subscribarr).
|
||||
4. Adjust settings later at `/settings/`.
|
||||
|
||||
---
|
||||
|
||||
## Notifications & Cron
|
||||
|
||||
- The periodic job calls `check_new_media` which determines today’s items via Sonarr/Radarr calendars.
|
||||
- Email is sent only if the item is **present** (e.g., `hasFile`/downloaded) and not already recorded in the sent-log (duplicate guard).
|
||||
- Cron is configured using `CRON_SCHEDULE` and runs `python manage.py check_new_media`. Output is typically logged to `/app/cron.log` in the container.
|
||||
|
||||
---
|
||||
|
||||
## Routes / Endpoints (selected)
|
||||
|
||||
- `GET /` — Overview page with search/filter and subscribe actions
|
||||
- `GET/POST /settings/` — Jellyfin/Arr/Mail/Account configuration (auth required; admin for some actions)
|
||||
- Example subscribe endpoints (subject to change):
|
||||
- `POST /api/series/subscribe/<series_id>/`, `POST /api/series/unsubscribe/<series_id>/`
|
||||
- `POST /api/movies/subscribe/<movie_id>/`, `POST /api/movies/unsubscribe/<movie_id>/`
|
||||
|
||||
---
|
||||
|
||||
## Local Development (without Docker)
|
||||
|
||||
> Requires Python 3.12+ (recommended).
|
||||
|
||||
### 1) Clone
|
||||
2) Build and run:
|
||||
```bash
|
||||
git clone https://gitea.js-devop.de/jschaufuss/Subscribarr.git
|
||||
cd Subscribarr
|
||||
docker compose build
|
||||
docker compose up -d
|
||||
```
|
||||
3) Open the app and complete the first‑run setup (Jellyfin + Arr URLs/keys).
|
||||
|
||||
### 2) Create & activate a virtualenv
|
||||
Important environment variables (examples):
|
||||
- `DJANGO_ALLOWED_HOSTS=subscribarr.example.com,localhost,127.0.0.1`
|
||||
- `DJANGO_CSRF_TRUSTED_ORIGINS=https://subscribarr.example.com,http://subscribarr.example.com`
|
||||
- Reverse proxy/TLS:
|
||||
- `USE_X_FORWARDED_HOST=true`
|
||||
- `DJANGO_SECURE_PROXY_SSL_HEADER=true`
|
||||
- `DJANGO_CSRF_COOKIE_SECURE=true`
|
||||
- `DJANGO_SESSION_COOKIE_SECURE=true`
|
||||
|
||||
> Note: `DJANGO_CSRF_TRUSTED_ORIGINS` must include the exact scheme+host (+port if used).
|
||||
|
||||
### Local (Pipenv)
|
||||
```bash
|
||||
python -m venv .venv
|
||||
# Linux/macOS:
|
||||
source .venv/bin/activate
|
||||
# Windows (PowerShell):
|
||||
# .venv\Scripts\Activate.ps1
|
||||
pipenv sync
|
||||
pipenv run python manage.py migrate
|
||||
pipenv run python manage.py runserver
|
||||
```
|
||||
|
||||
### 3) Install dependencies (including Django)
|
||||
If the repository provides `requirements.txt`:
|
||||
## In‑App Configuration
|
||||
- Settings → Jellyfin: server URL + API key
|
||||
- Settings → Sonarr/Radarr: base URLs + API keys (with “Test” button)
|
||||
- Settings → Mail server: SMTP (host/port/TLS/SSL/user/password/from)
|
||||
- Settings → Notifications:
|
||||
- ntfy: server URL, default topic, Basic Auth or Bearer token
|
||||
- Apprise: default URL(s) (one per line)
|
||||
- Profile (per user):
|
||||
- Choose channel: Email, ntfy, or Apprise
|
||||
- ntfy topic (optional, overrides default)
|
||||
- Apprise URL(s) (optional, appended to defaults)
|
||||
|
||||
## ntfy Notes
|
||||
- Server URL: e.g., `https://ntfy.sh` or your own server
|
||||
- Auth:
|
||||
- Bearer token (Authorization header)
|
||||
- Basic Auth (username/password)
|
||||
- Topic selection:
|
||||
- Per user in the profile, or a global default topic in Settings
|
||||
|
||||
## Apprise Notes
|
||||
Provide one or more destination URLs (one per line), e.g.:
|
||||
- `gotify://TOKEN@gotify.example.com/`
|
||||
- `discord://webhook_id/webhook_token`
|
||||
- `mailto://user:pass@smtp.example.com`
|
||||
- `pover://user@token`
|
||||
- `json://webhook.example.com/path`
|
||||
|
||||
User URLs are added in addition to global defaults.
|
||||
|
||||
## Notification Logic
|
||||
- Series: on the air date, Subscribarr checks Sonarr for the episode and only notifies when `hasFile` is true (downloaded/present).
|
||||
- Movies: similar via Radarr `hasFile` and matching the release date (Digital/Disc/Cinema) for today.
|
||||
- Duplicate suppression: entries are recorded in `SentNotification` per user/title/day; if sending fails, no record is stored.
|
||||
- Fallback: if ntfy/Apprise fail, Subscribarr falls back to Email (when configured).
|
||||
|
||||
## Jobs / Manual Trigger
|
||||
- Periodic check via management command (e.g., cron):
|
||||
```bash
|
||||
pip install --upgrade pip wheel
|
||||
pip install -r requirements.txt
|
||||
pipenv run python manage.py check_new_media
|
||||
```
|
||||
If not, install the core stack explicitly:
|
||||
- In Docker:
|
||||
```bash
|
||||
pip install --upgrade pip wheel
|
||||
pip install "Django>=5" djangorestframework python-dotenv
|
||||
# add any additional libs your project uses as needed
|
||||
docker compose exec web python manage.py check_new_media
|
||||
```
|
||||
|
||||
### 4) Configure environment (dev)
|
||||
Create a `.env` (or export env vars) with at least:
|
||||
```env
|
||||
DJANGO_DEBUG=true
|
||||
DJANGO_SECRET_KEY=dev-secret
|
||||
DJANGO_ALLOWED_HOSTS=*
|
||||
DB_PATH=./data/db.sqlite3
|
||||
```
|
||||
Create the `data/` directory if it doesn’t exist.
|
||||
## Security & Proxy
|
||||
- Set `DJANGO_ALLOWED_HOSTS` to your hostnames.
|
||||
- Include all used origins in `DJANGO_CSRF_TRUSTED_ORIGINS` (http/https and port where applicable).
|
||||
- Behind a reverse proxy with TLS: enable `USE_X_FORWARDED_HOST`, `DJANGO_SECURE_PROXY_SSL_HEADER`, and secure cookie flags.
|
||||
|
||||
### 5) Database setup
|
||||
```bash
|
||||
python manage.py makemigrations
|
||||
python manage.py migrate
|
||||
```
|
||||
|
||||
### 6) (Optional) Create a superuser for the Django admin
|
||||
```bash
|
||||
python manage.py createsuperuser
|
||||
```
|
||||
|
||||
### 7) Run the dev server
|
||||
```bash
|
||||
python manage.py runserver 0.0.0.0:8000
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Model (high level)
|
||||
|
||||
- **User** (`accounts.User`): custom user with Jellyfin link and admin flag.
|
||||
- **Subscriptions** (`arr_api.SeriesSubscription`, `arr_api.MovieSubscription`): unique per user/title.
|
||||
- **SentNotification**: records delivered emails to avoid duplicates.
|
||||
- **AppSettings**: singleton for Jellyfin/Arr/Mail/Account configuration.
|
||||
|
||||
---
|
||||
|
||||
## Production Notes
|
||||
|
||||
- Set **`DEBUG=false`**, a strong **`DJANGO_SECRET_KEY`**, and proper **`DJANGO_ALLOWED_HOSTS`**.
|
||||
- Run behind a reverse proxy with HTTPS.
|
||||
- Collect static files if served by Django:
|
||||
```bash
|
||||
python manage.py collectstatic --noinput
|
||||
```
|
||||
- Use a persistent database volume (or switch to Postgres/MySQL) for production.
|
||||
|
||||
---
|
||||
## Tech Stack
|
||||
- Backend: Django 5 + DRF
|
||||
- Integrations: Sonarr/Radarr (API v3)
|
||||
- Auth: Jellyfin
|
||||
- Notifications: SMTP, ntfy (HTTP), Apprise
|
||||
- Frontend: Templates + FullCalendar
|
||||
- DB: SQLite (default)
|
||||
|
||||
## License
|
||||
|
||||
MIT (see `LICENSE`).
|
||||
|
@@ -361,9 +361,9 @@ def check_and_notify_users():
|
||||
html = render_to_string('arr_api/email/new_media_notification.html', ctx)
|
||||
except Exception:
|
||||
pass
|
||||
_dispatch_user_notification(sub.user, subject=subj, body_text=body, html_message=html)
|
||||
ok = _dispatch_user_notification(sub.user, subject=subj, body_text=body, html_message=html)
|
||||
# mark as sent unless duplicates are allowed
|
||||
if not getattr(settings, 'NOTIFICATIONS_ALLOW_DUPLICATES', False):
|
||||
if ok and not getattr(settings, 'NOTIFICATIONS_ALLOW_DUPLICATES', False):
|
||||
SentNotification.objects.create(
|
||||
user=sub.user,
|
||||
media_id=sub.series_id,
|
||||
@@ -423,8 +423,8 @@ def check_and_notify_users():
|
||||
html = render_to_string('arr_api/email/new_media_notification.html', ctx)
|
||||
except Exception:
|
||||
pass
|
||||
_dispatch_user_notification(sub.user, subject=subj, body_text=body, html_message=html)
|
||||
if not getattr(settings, 'NOTIFICATIONS_ALLOW_DUPLICATES', False):
|
||||
ok = _dispatch_user_notification(sub.user, subject=subj, body_text=body, html_message=html)
|
||||
if ok and not getattr(settings, 'NOTIFICATIONS_ALLOW_DUPLICATES', False):
|
||||
SentNotification.objects.create(
|
||||
user=sub.user,
|
||||
media_id=sub.movie_id,
|
||||
|
3
mailmap.txt
Normal file
3
mailmap.txt
Normal file
@@ -0,0 +1,3 @@
|
||||
Jan Schaufuss <jschaufuss@js-devop.de> <jschaufuss@leitwerk.de>
|
||||
Jan Schaufuss <jschaufuss@js-devop.de> Jan Schaufuss <jschaufuss@leitwerk.de>
|
||||
<jschaufuss@js-devop.de> <jschaufuss@leitwerk.de>
|
Reference in New Issue
Block a user