# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

Ohm Stream Downloader is a FastAPI-based web application for downloading anime episodes and media files from various file hosting services (1fichier, Doodstream, Rapidfile, Uptobox, VidMoly, SendVid, Sibnet, Lpayer, Vidzy, LuLuvid, Uqload) and streaming platforms (Anime-Sama, Neko-Sama, Anime-Ultime, Vostfree, French-Manga, FS7). It features a modern web interface, parallel downloads, pause/resume support, video streaming, personalized recommendations, JWT authentication, and Sonarr webhook integration for automated downloads.

## Development Commands

```bash
# Create and activate virtual environment
python3 -m venv venv
source venv/bin/activate  # On Windows: venv\Scripts\activate

# Install dependencies
pip install -r requirements.txt

# Install JavaScript test dependencies (optional, for frontend tests)
npm install

# Run development server (auto-reload)
uvicorn main:app --reload --host 0.0.0.0 --port 3000

# Access web interface
# Open http://localhost:3000/web in browser

# --- Python Tests (pytest) ---

# Run all tests
pytest

# Run tests with coverage report
pytest --cov=app --cov-report=html

# Run only unit tests (fast, isolated)
pytest -m "unit"

# Run only integration tests
pytest -m "integration"

# Exclude slow tests
pytest -m "not slow"

# Verbose output
pytest -v

# Show print debugging
pytest -s

# Run specific test file
pytest tests/test_sonarr.py -v

# Run specific test class
pytest tests/test_sonarr.py::TestSonarrHandler -v

# Run specific test
pytest tests/test_sonarr.py::TestSonarrHandler::test_add_mapping -v

# --- JavaScript Tests (vitest) ---

# Run all JavaScript tests
npm test

# Run JavaScript tests in watch mode
npm run test:watch

# Run specific JavaScript test file
npx vitest run static/js/__tests__/auth-api.test.js
```

## Architecture

**Directory Structure:**
```
Ohm_streaming/
├── main.py                      # FastAPI application startup & middleware
├── app/
│   ├── routers/                 # FastAPI routers (API endpoints organized by feature)
│   │   ├── __init__.py          # Exports all routers
│   │   ├── router_auth.py       # /api/auth/* routes (user authentication)
│   │   ├── router_anime.py      # /api/anime/* and /api/series/* routes
│   │   ├── router_downloads.py  # /api/download/* routes
│   │   ├── router_favorites.py  # /api/favorites/* routes
│   │   ├── router_player.py     # /player/* and /watch/* routes
│   │   ├── router_recommendations.py  # /api/recommendations and /api/releases routes
│   │   ├── router_root.py       # / and /web routes
│   │   ├── router_sonarr.py     # /api/sonarr/* and /api/webhook/sonarr routes
│   │   ├── router_static.py     # /static/* and /video/* routes
│   │   └── router_watchlist.py  # /api/watchlist/* routes
│   ├── models/                  # Pydantic models (DownloadTask, AnimeMetadata, Sonarr, etc.)
│   ├── downloaders/             # Host-specific downloaders (organized structure)
│   │   ├── base.py              # BaseDownloader abstract class (legacy, kept for compatibility)
│   │   ├── __init__.py          # Factory function (three-tier: anime sites → series sites → video players)
│   │   ├── anime_sites/         # Anime streaming sites (catalogs)
│   │   │   ├── base.py          # BaseAnimeSite abstract class
│   │   │   ├── __init__.py      # Anime site factory
│   │   │   ├── animesama.py     # Anime-Sama (anime provider)
│   │   │   ├── animeultime.py   # Anime-Ultime (anime provider)
│   │   │   ├── nekosama.py      # Neko-Sama (anime provider)
│   │   │   ├── vostfree.py      # Vostfree (anime provider)
│   │   │   └── frenchmanga.py   # French-Manga (anime provider)
│   │   ├── series_sites/        # TV series streaming sites (catalogs)
│   │   │   ├── base.py          # BaseSeriesSite abstract class
│   │   │   ├── __init__.py      # Series site factory
│   │   │   └── fs7.py           # FS7 (French Stream)
│   │   └── video_players/       # File hosting services (players)
│   │       ├── base.py          # BaseVideoPlayer abstract class
│   │       ├── __init__.py      # Video player factory
│   │       ├── unfichier.py     # 1fichier.com handler
│   │       ├── doodstream.py    # Doodstream handler
│   │       ├── rapidfile.py     # Rapidfile handler
│   │       ├── uptobox.py       # Uptobox handler
│   │       ├── vidmoly.py       # VidMoly handler
│   │       ├── sendvid.py       # SendVid handler
│   │       ├── sibnet.py        # Sibnet handler
│   │       ├── lpayer.py        # Lpayer handler
│   │       ├── vidzy.py         # Vidzy handler
│   │       ├── luluv.py         # LuLuvid handler
│   │       └── uqload.py        # Uqload handler
│   ├── providers.py             # Provider configuration (domains, icons, colors)
│   ├── config.py                # Environment-based configuration (Pydantic Settings)
│   ├── utils.py                 # Security utilities (sanitize_filename, is_safe_filename)
│   ├── download_manager.py      # Manages download queue, progress, parallel downloads
│   ├── favorites.py             # Favorites management system (JSON-based)
│   ├── recommendation_engine.py # Analyzes download history for personalized recommendations
│   ├── recommendations.py       # Fetches latest releases from anime sources
│   ├── kitsu_api.py             # Kitsu API integration for anime metadata
│   ├── sonarr_handler.py        # Sonarr webhook integration handler
│   ├── auth.py                  # JWT authentication system
│   └── models/
│       ├── __init__.py          # Core models (DownloadTask, AnimeMetadata, etc.)
│       └── sonarr.py            # Sonarr Pydantic models
├── downloads/                   # Downloaded files storage
├── templates/
│   ├── index.html               # Main web interface
│   ├── player.html              # Video player page
│   └── base.html                # Base template
├── static/                      # Static assets (CSS, JS, images)
│   ├── js/
│   │   ├── __tests__/           # JavaScript tests (vitest)
│   │   │   ├── auth-api.test.js
│   │   │   ├── auth-utils.test.js
│   │   │   └── smoke.test.js
│   │   ├── auth.js              # Authentication UI logic
│   │   ├── auth-api.js          # Authentication API client
│   │   ├── auth-ui.js           # Authentication UI components
│   │   └── auth-utils.js        # Authentication utilities
├── tests/                       # Python test suite with fixtures
│   ├── e2e/                     # End-to-end tests (Playwright)
└── vitest.config.js             # Vitest configuration for JS tests
```

**Core Components:**

### 0. Configuration (`app/config.py`)
- `Settings` class using Pydantic Settings for environment-based configuration
- Loads from `.env` file with sensible defaults
- Provides `get_settings()` function for accessing configuration globally

### 1. DownloadManager (`app/download_manager.py`)
- Manages all download tasks with parallel download limit (default: 3 concurrent)
- Handles pause/resume/cancel operations
- Tracks progress, speed, and file chunks for resume support
- Uses `asyncio.Semaphore` to limit concurrent downloads
- Auto-restores completed downloads from disk on server startup

### 2. Downloaders (`app/downloaders/`)

**Architecture:**
The downloaders are organized into three categories with separate base classes:

**Anime Sites** (`app/downloaders/anime_sites/`):
- Provide anime catalogs, metadata, and episode listings
- Link to video players for actual file hosting
- Inherit from `BaseAnimeSite` abstract class
- Factory: `get_anime_site(url)` in `anime_sites/__init__.py`
- Implement: `search_anime()`, `get_episodes()`, `get_anime_metadata()`, `get_download_link()`

**Series Sites** (`app/downloaders/series_sites/`):
- Provide TV series catalogs, metadata, and episode listings
- Similar to anime sites but for general TV series content
- Inherit from `BaseSeriesSite` abstract class
- Factory: `get_series_site(url)` in `series_sites/__init__.py`
- Implement: `search_anime()`, `get_episodes()`, `get_anime_metadata()`, `get_download_link()`

**Video Players** (`app/downloaders/video_players/`):
- Host actual video files and provide direct download links
- Extract URLs from embedded players and handle file downloads
- Inherit from `BaseVideoPlayer` abstract class
- Factory: `get_video_player(url)` in `video_players/__init__.py`
- Implement: `get_download_link(url, target_filename=None)`

**Three-Tier Factory Pattern:**
- `get_downloader(url)` in main `__init__.py` checks: anime sites → series sites → video players
- Falls back to `GenericDownloader` if no match
- This separation allows anime/series sites to delegate to video players for actual downloads

**BaseAnimeSite Interface:**
- `can_handle(url)` - Check if this anime site can handle the URL
- `search_anime(query, lang)` - Search for anime, returns list with title, url, cover_image
- `get_episodes(anime_url, lang)` - Get episode list with episode_number, url, title, host
- `get_anime_metadata(anime_url)` - Get metadata dict (synopsis, genres, rating, release_year, studio, poster_image, total_episodes, status)
- `get_download_link(url)` - Get video player URL from episode page (NOT direct download link)

**BaseSeriesSite Interface:**
- `can_handle(url)` - Check if this series site can handle the URL
- `search_anime(query, lang)` - Search for series, returns list with title, url, cover_image, lang
- `get_episodes(anime_url, lang)` - Get episode list with episode_number, url, title, host
- `get_anime_metadata(anime_url)` - Get metadata dict (title, synopsis, genres, rating, release_year, studio, poster_image, total_episodes, status, languages)
- `get_download_link(url)` - Get video player URL from episode page (NOT direct download link)

**BaseVideoPlayer Interface:**
- `can_handle(url)` - Check if this player can handle the URL
- `get_download_link(url, target_filename=None)` - Extract direct download link and filename
  - Note: `target_filename` parameter is optional but MUST be supported for VidMoly/SendVid compatibility
  - Always use `sanitize_filename()` on extracted filenames!

**Key Patterns:**
- All downloaders use httpx.AsyncClient for HTTP requests
- BeautifulSoup with lxml for HTML parsing
- Async/await throughout for non-blocking I/O
- Fuzzy search using jieba for Chinese text segmentation and typo tolerance
- Security: Filename sanitization enforced via `app.utils` functions

**URL Format Convention:**
- **Pipe-separated format**: `video_url|anime_page_url|episode_title`
  - Preserves metadata through the download process
  - Example: `https://vidmoly.to/abc123|https://anime-sama.si/catalogue/naruto/s1/vostfr/|Episode+1`
  - `target_filename` parameter allows anime/series sites to suggest filenames
  - Video players extract the final download link and filename

### 3. Provider Configuration (`app/providers.py`)
- `ANIME_PROVIDERS` - Anime streaming sites configuration
- `FILE_HOSTS` - File hosting services configuration
- Each provider has: name, domains, icon, color, url_pattern
- `detect_provider_from_url(url)` - Identify provider from URL

### 4. Router Architecture (`app/routers/`)

**Overview:**
- API endpoints have been migrated from a monolithic `main.py` (2200+ lines) to modular routers
- Each router is responsible for a specific feature domain
- Routers are imported and registered in `main.py` using FastAPI's APIRouter
- This improves maintainability, testability, and code organization

**Router Organization:**
- `router_auth.py` - `/api/auth/*` - User registration, login, token refresh, profile management
- `router_anime.py` - `/api/anime/*` and `/api/series/*` - Search, metadata, episodes, downloads
- `router_downloads.py` - `/api/download/*` - Download task management (pause, resume, cancel, delete)
- `router_favorites.py` - `/api/favorites/*` - Favorites CRUD operations
- `router_player.py` - `/player/*` and `/watch/*` - Video player endpoints
- `router_recommendations.py` - `/api/recommendations` and `/api/releases/latest` - Personalization and latest releases
- `router_root.py` - `/` and `/web` - Root and main web interface routes
- `router_sonarr.py` - `/api/sonarr/*` and `/api/webhook/sonarr` - Sonarr integration and webhooks
- `router_static.py` - `/static/*` and `/video/*` - Static file serving and video streaming
- `router_watchlist.py` - `/api/watchlist/*` - Watchlist and auto-download scheduler management

**Key Benefits:**
- Clear separation of concerns - each router handles one feature area
- Easier testing - routers can be tested independently
- Better navigation - smaller files focused on specific functionality
- Shared dependencies via FastAPI's dependency injection (e.g., `download_manager`, `get_current_user_from_token`)
- No URL changes - frontend remains fully compatible

**When Adding New Endpoints:**
1. Identify which router the endpoint belongs to based on its URL prefix
2. Add the endpoint function to the appropriate router file in `app/routers/`
3. Use FastAPI dependencies for shared services (`download_manager`, `templates`, authentication)
4. Follow existing patterns for error handling and response models

### 5. API Endpoints

**Download Management:**
- `POST /api/download` - Create new download task
- `GET /api/downloads` - List all download tasks
- `GET /api/download/{task_id}` - Get task details
- `POST /api/download/{task_id}/pause` - Pause download
- `POST /api/download/{task_id}/resume` - Resume download
- `DELETE /api/download/{task_id}` - Delete task (keeps completed files)
- `GET /api/download/{task_id}/file` - Download completed file

**Anime Features:**
- `GET /api/anime/search` - Unified search across all providers
- `GET /api/anime/metadata` - Get anime metadata
- `GET /api/anime/episodes` - Get episode list
- `POST /api/anime/download` - Download single episode
- `POST /api/anime/download-season` - Download entire season

**Video Streaming:**
- `GET /video/{task_id}` - Stream video with Range support
- `GET /stream/{filename}` - Stream by filename
- `GET /player/{task_id}` - Video player page
- `GET /watch/{filename}` - Player by filename

**Recommendations & Favorites:**
- `GET /api/recommendations` - Personalized recommendations
- `GET /api/releases/latest` - Latest anime releases
- `GET /api/favorites` - List favorites
- `POST /api/favorites` - Add favorite
- `DELETE /api/favorites/{anime_id}` - Remove favorite

**Sonarr Integration:**
- `POST /api/webhook/sonarr` - Receive Sonarr webhooks
- `GET /api/sonarr/config` - Get Sonarr configuration
- `PUT /api/sonarr/config` - Update Sonarr configuration
- `GET /api/sonarr/mappings` - List Sonarr to anime mappings
- `POST /api/sonarr/mappings` - Create/update mapping
- `DELETE /api/sonarr/mappings/{series_id}` - Delete mapping
- `GET /api/sonarr/search` - Search anime for mapping
- `GET /api/sonarr/episodes` - Get episode list
- `GET /api/sonarr/suggest` - Suggest anime matches
- `POST /api/sonarr/download` - Manually trigger download

### 6. Web Interface
- Single-page app at `/web` (templates/index.html)
- Auto-refreshes every second to show progress
- Video player with seeking support (HTTP Range headers)
- Dark theme with gradients and animations

### 7. Security Utilities (`app/utils.py`)
- `sanitize_filename(filename, max_length=255)` - Sanitize filenames to prevent path traversal
  - Removes dangerous characters: `\ / : * ? " < > |`
  - Strips path separators and leading dots/dashes
  - Limits filename length while preserving extension
- `is_safe_filename(filename)` - Validate filename safety
  - Checks for path traversal patterns (`..`, `/`, `\`)
  - Detects absolute paths and drive letters
  - Used throughout the codebase for file operations

### 8. Authentication System (`app/auth.py`)
- **UserManager** - JSON-based user storage in `config/users.json`
  - User registration with bcrypt password hashing
  - Password truncated to 72 bytes (bcrypt limitation)
  - User authentication and last login tracking
- **JWT Tokens** - Stateless authentication with refresh token support
  - Access tokens: 24-hour expiration (configurable via `ACCESS_TOKEN_EXPIRE_MINUTES`)
  - Refresh tokens: 30-day expiration (stored in `config/refresh_tokens.json`)
  - HS256 algorithm with JWT_SECRET_KEY (change in production!)
  - Token verification and user extraction
- **Password Security**
  - bcrypt hashing with passlib
  - Automatic deprecated scheme migration
- **JWT Secret Validation** (in `app/config.py`)
  - Default secret is rejected at startup (security enforcement)
  - Minimum 32 characters required
  - Use `Settings.generate_secret()` to generate secure secrets
- **Configuration**
  - `JWT_SECRET_KEY` environment variable (MUST be changed from default)
  - Users stored in `config/users.json`
  - Refresh tokens stored in `config/refresh_tokens.json`

**Authentication Endpoints:**
- `POST /api/auth/register` - User registration
- `POST /api/auth/login` - Login and receive JWT token
- `GET /api/auth/me` - Get current user profile
- `PUT /api/auth/me` - Update user profile

### 9. Recommendation Engine (`app/recommendation_engine.py`)
- Analyzes download history to generate personalized recommendations
- Tracks genre preferences and viewing patterns
- Scores anime based on user's download history
- Used by `/api/recommendations` endpoint

### 10. Kitsu API (`app/kitsu_api.py`)
- Integrates with Kitsu anime database for metadata
- Fetches anime information by title or ID
- Provides enriched metadata (synopsis, genres, ratings, poster images)
- Used as fallback when provider metadata is incomplete

### 11. Watchlist & Auto-Download System

**WatchlistManager** (`app/watchlist.py`):
- JSON-based storage in `config/watchlist.json`
- Per-user watchlist management (multi-tenant)
- CRUD operations for tracked anime
- Statistics and queries
- Settings management in `config/watchlist_settings.json`

**EpisodeChecker** (`app/episode_checker.py`):
- Checks for new episodes for anime in watchlist
- Downloads episodes automatically when detected
- Integrates with existing downloaders
- Handles errors and retries
- Lazy initialization to avoid circular imports

**AutoDownloadScheduler** (`app/auto_download_scheduler.py`):
- APScheduler-based periodic checking
- Configurable intervals (1-168 hours)
- Start/stop control via API
- Next run tracking
- Background task execution

**Watchlist Models** (`app/models/watchlist.py`):
- `WatchlistItem` - Tracked anime with settings
- `WatchlistStatus` - ACTIVE, PAUSED, COMPLETED, ARCHIVED
- `QualityPreference` - AUTO, 1080p, 720p, 480p
- `WatchlistSettings` - Global configuration
- `AutoDownloadResult` - Operation results

**Watchlist Endpoints:**
- `GET /api/watchlist` - List user's watchlist (with status filter)
- `POST /api/watchlist` - Add anime to watchlist
- `GET /api/watchlist/{item_id}` - Get specific item
- `PUT /api/watchlist/{item_id}` - Update watchlist item
- `DELETE /api/watchlist/{item_id}` - Remove from watchlist
- `POST /api/watchlist/{item_id}/check` - Check specific anime
- `POST /api/watchlist/check-all` - Check all due items
- `POST /api/watchlist/{item_id}/pause` - Pause tracking
- `POST /api/watchlist/{item_id}/resume` - Resume tracking
- `GET /api/watchlist/settings` - Get global settings
- `PUT /api/watchlist/settings` - Update settings
- `GET /api/watchlist/stats` - Get watchlist statistics
- `GET /api/watchlist/scheduler/status` - Get scheduler status
- `POST /api/watchlist/scheduler/start` - Start scheduler
- `POST /api/watchlist/scheduler/stop` - Stop scheduler

### 12. Pydantic Models (`app/models/`)
- **`__init__.py`** - Core models:
  - `DownloadStatus` - Enum for task states (PENDING, DOWNLOADING, PAUSED, COMPLETED, FAILED, CANCELLED)
  - `HostType` - Enum for file host types (RAPIDFILE, UNFICHIER, DOODSTREAM, OTHER)
  - `DownloadTask` - Main task model with progress tracking
  - `DownloadRequest` - Request model for creating downloads
  - `AnimeMetadata` - Anime information (synopsis, genres, rating, release_year, studio, etc.)
  - `AnimeSearchResult` - Enhanced search result with metadata
- **`sonarr.py`** - Sonarr-specific models:
  - `SonarrWebhookPayload` - Complete webhook payload schema
  - `SonarrEventType` - Enum for event types (Grab, Download, Rename, Delete, Test)
  - `SonarrMapping` - Mapping between Sonarr series and anime providers
  - `SonarrConfig` - Webhook configuration (enabled, secret, auto-download, etc.)
- **`auth.py`** - Authentication models:
  - `UserCreate` - User registration request
  - `UserLogin` - Login request
  - `User` - User profile
  - `Token` - JWT token response
- **`watchlist.py`** - Watchlist models:
  - `WatchlistItem` - Tracked anime item
  - `WatchlistItemCreate` - Create request
  - `WatchlistItemUpdate` - Update request
  - `WatchlistStatus` - Status enum
  - `WatchlistSettings` - Global settings

## Test Structure

**Python Test Organization (tests/):**
- `conftest.py` - Pytest configuration and fixtures
- `test_models.py` - Pydantic model tests
- `test_downloaders.py` - Downloader tests
- `test_download_manager.py` - DownloadManager tests
- `test_favorites.py` - Favorites system tests
- `test_api.py` - FastAPI endpoint tests
- `test_sonarr.py` - Sonarr integration tests
- `test_anime_sama_seasons.py` - Anime-Sama season handling tests
- `test_translate_api.py` - Translation API tests
- `test_delete_and_restore.py` - Delete and restore functionality tests
- `test_french_manga.py` - French-Manga provider tests
- `test_jwt_secret_validation.py` - JWT secret key validation tests
- `test_token_refresh.py` - Token refresh functionality tests

**JavaScript Test Organization (static/js/__tests__/):**
- `smoke.test.js` - Basic smoke tests
- `auth-api.test.js` - Authentication API client tests
- `auth-utils.test.js` - Authentication utility function tests
- Uses Vitest with jsdom environment
- Coverage reports generated in `htmlcov/` (shared with Python tests)

**Fixtures in conftest.py:**
- `temp_dir` - Temporary directory
- `temp_download_dir` - Temporary download directory
- `download_manager` - DownloadManager instance
- `favorites_manager` - FavoritesManager instance
- `mock_httpx_client` - Mock for httpx.AsyncClient
- `sample_download_task` - Sample task data
- `sample_anime_metadata` - Sample metadata

**Test Markers:**
- `unit` - Unit tests (isolated, fast) - auto-applied
- `integration` - Integration tests (API endpoints) - auto-applied
- `asyncio` - Async tests - auto-applied
- `slow` - Slow tests - manual
- `network` - Requires network - manual

**pytest.ini Configuration:**
- Auto-applies markers for async and integration tests
- Coverage enabled by default (`--cov=app`)
- HTML coverage report generated in `htmlcov/`
- Verbose output with local variables in tracebacks
- 300-second timeout for tests
- `asyncio_mode = auto` for async test support

**Running Single Test:**
```bash
# Run specific test file
pytest tests/test_sonarr.py -v

# Run specific test class
pytest tests/test_sonarr.py::TestSonarrHandler -v

# Run specific test
pytest tests/test_sonarr.py::TestSonarrHandler::test_add_mapping -v
```

## Adding New Host Support

To add support for a new file hosting service:

1. Create new file in `app/downloaders/video_players/` (e.g., `myhost.py`)
2. Inherit from `BaseVideoPlayer`
3. Implement required methods (`can_handle`, `get_download_link`)
4. Add to imports in `app/downloaders/video_players/__init__.py`
5. Add to `players` list in `get_video_player()`
6. Add configuration to `FILE_HOSTS` in `app/providers.py`

Example:
```python
from .base import BaseVideoPlayer
from bs4 import BeautifulSoup

class MyHostDownloader(BaseVideoPlayer):
    def can_handle(self, url: str) -> bool:
        return "myhost.com" in url.lower()

    async def get_download_link(self, url: str, target_filename: Optional[str] = None) -> tuple[str, str]:
        soup = BeautifulSoup(await self._fetch_page(url), 'lxml')
        # ... extraction logic ...
        # IMPORTANT: Always sanitize filenames!
        from app.utils import sanitize_filename
        filename = sanitize_filename(extracted_filename)
        return download_url, filename

    async def close(self):
        # IMPORTANT: Always close the HTTP client
        await self.client.aclose()
```

**Important:**
- Always close the HTTP client in your downloader to avoid resource leaks
- Use `sanitize_filename()` from `app.utils` when extracting filenames from URLs
- Use `is_safe_filename()` to validate filenames before file operations
- The `target_filename` parameter is required for compatibility with anime/series sites

## Adding New Series Site

To add a new TV series streaming provider (similar to anime sites but for general TV series):

1. Create new file in `app/downloaders/series_sites/` (e.g., `mysite.py`)
2. Inherit from `BaseSeriesSite`
3. Implement series-specific methods:
   - `search_anime(query, lang)` - Return list of series with title, url, cover_image, lang
   - `get_episodes(anime_url, lang)` - Return list of episodes
   - `get_anime_metadata(anime_url)` - Return metadata dict (should include languages field)
   - `get_download_link(url)` - Return video player URL from episode page
4. Add to imports in `app/downloaders/series_sites/__init__.py`
5. Add to `sites` list in `get_series_site()`

BaseSeriesSite is nearly identical to BaseAnimeSite but designed for general TV series content rather than anime-specific content.

## Sonarr Integration

The application includes full Sonarr webhook support for automated anime downloads.

### Architecture

**SonarrHandler (`app/sonarr_handler.py`):**
- Processes incoming webhooks from Sonarr
- Manages series mappings (Sonarr TVDB ID → Anime Provider URL)
- Supports HMAC SHA256 signature verification for security
- Auto-triggers downloads on Grab events
- Provides search and suggestion APIs for mapping setup

**Sonarr Models (`app/models/sonarr.py`):**
- `SonarrWebhookPayload` - Complete webhook payload schema
- `SonarrEventType` - Enum for event types (Grab, Download, Rename, Delete, Test)
- `SonarrMapping` - Mapping between Sonarr series and anime providers
- `SonarrConfig` - Webhook configuration (enabled, secret, auto-download, etc.)

### Workflow

1. **Setup in Sonarr:**
   - Configure webhook: Settings > Connect > Sonarr > Webhook
   - URL: `http://your-server:3000/api/webhook/sonarr`
   - Enable "Grab" event

2. **Create Mappings:**
   - Get Sonarr series TVDB ID from series details
   - Search anime: `GET /api/sonarr/search?q={title}`
   - Create mapping: `POST /api/sonarr/mappings`

3. **Automatic Download:**
   - Sonarr grabs new episode → Sends webhook
   - Ohm Stream Downloader receives webhook
   - Looks up mapping by TVDB ID
   - Finds matching episode on anime provider
   - Creates and starts download task

### Configuration Files

- `config/sonarr.json` - Webhook configuration
- `config/sonarr_mappings.json` - Series mappings

### Example Mapping

```json
{
  "sonarr_series_id": 79644,
  "sonarr_title": "Naruto Shippuden",
  "anime_provider": "anime-sama",
  "anime_url": "https://anime-sama.si/catalogue/naruto-shippuden/saison1/vostfr/",
  "anime_title": "Naruto Shippuden",
  "lang": "vostfr",
  "quality_preference": "1080p",
  "auto_download": true
}
```

### Security

- Optional HMAC SHA256 signature verification
- Configure secret in both Sonarr and Ohm Stream Downloader
- Enable with `verify_hmac: true` in config

### Testing

- Test endpoint: `POST /api/webhook/test/sonarr`
- Manual trigger: `POST /api/sonarr/download`
- Get suggestions: `GET /api/sonarr/suggest?sonarr_title={title}`

**Documentation:** See `docs/SONARR_INTEGRATION.md` for complete setup guide.

## Adding New Anime Provider

To add a new anime streaming provider:

1. Create new file in `app/downloaders/anime_sites/` (e.g., `mysite.py`)
2. Inherit from `BaseAnimeSite`
3. Implement anime-specific methods:
   - `search_anime(query, lang)` - Return list of anime with title, url, cover_image
   - `get_episodes(anime_url, lang)` - Return list of episodes
   - `get_anime_metadata(anime_url)` - Return metadata dict
   - `get_download_link(url)` - Return video player URL from episode page
4. Add to imports in `app/downloaders/anime_sites/__init__.py`
5. Add to `sites` list in `get_anime_site()`
6. Add to `ANIME_PROVIDERS` in `app/providers.py`
7. Update `main.py` to include in unified search

Metadata should include:
- synopsis, genres, rating, release_year, studio, poster_image, total_episodes, status

## Working with Routers

**Adding New Endpoints:**
1. Identify which router handles the URL prefix you need
2. Edit the appropriate router file in `app/routers/`
3. Use FastAPI's APIRouter pattern with proper dependencies
4. Import the router in `app/routers/__init__.py` if creating a new router
5. Register the router in `main.py`

**Example - Adding a new endpoint to router_anime.py:**
```python
from fastapi import APIRouter, Depends
from app.download_manager import DownloadManager

router = APIRouter(prefix="/api/anime", tags=["anime"])

@router.get("/custom-endpoint")
async def custom_endpoint(
    download_manager: DownloadManager = Depends(lambda: download_manager)
):
    # Your logic here
    return {"status": "success"}
```

**Common Dependencies:**
- `download_manager: DownloadManager = Depends(lambda: download_manager)` - Access download queue
- `current_user: User = Depends(get_current_user_from_token)` - Authenticated user
- `templates: Jinja2Templates = Depends(lambda: templates)` - Template rendering

**Router Organization Principles:**
- Group related endpoints by URL prefix
- Keep routers focused on a single feature area
- Use dependency injection for shared services
- Tag routers for OpenAPI documentation

## Configuration

The application uses environment variables for configuration via `app/config.py` (Pydantic Settings).

**Environment Variables (.env):**
```bash
# Copy the example file
cp .env.example .env

# Edit .env to configure:
APP_NAME=Ohm Stream Downloader      # Application name
DEBUG=false                          # Debug mode
HOST=0.0.0.0                        # Server host
PORT=3000                           # Server port
DOWNLOAD_DIR=downloads              # Download storage location
MAX_PARALLEL_DOWNLOADS=3            # Maximum concurrent downloads
CHUNK_SIZE=1048576                  # Download chunk size (1MB)
CORS_ORIGINS=...                    # Comma-separated allowed origins
HTTP_TIMEOUT=10.0                   # HTTP request timeout (seconds)
DOWNLOAD_TIMEOUT=300                # Download timeout (seconds)
LOG_LEVEL=INFO                      # Logging level
JWT_SECRET_KEY=change-me-in-production  # JWT signing key (MUST be changed, min 32 chars)
# Generate a secure key with: python -c "from app.config import Settings; print(Settings.generate_secret())"
```

**Configuration Files:**
- `.env` - Environment configuration (create from .env.example)
- `config/users.json` - User authentication database (created automatically)
- `config/refresh_tokens.json` - Refresh token storage (created automatically)
- `config/sonarr.json` - Sonarr webhook configuration (created automatically)
- `config/sonarr_mappings.json` - Sonarr to anime provider mappings (created automatically)
- `config/watchlist.json` - User watchlist items (created automatically)
- `config/watchlist_settings.json` - Watchlist global settings (created automatically)
- `config/.gitkeep` - Ensures config directory is tracked in git
- Example files: `config/sonarr.example.json`, `config/sonarr_mappings.example.json`

**Documentation:**
- `README.md` - User-facing features and roadmap
- `CLAUDE.md` - This file (developer guide)
- `docs/SONARR_INTEGRATION.md` - Complete Sonarr setup guide
- `docs/SONARR_IMPLEMENTATION.md` - Technical implementation summary
- `docs/IMPROVEMENTS_2024-01-24.md` - Recent security and quality improvements
- `docs/WATCHLIST_AUTO_DOWNLOAD.md` - Watchlist system documentation

## Security

**Filename Sanitization (`app/utils.py`):**
- `sanitize_filename()` - Removes dangerous characters (`\ / : * ? " < > |`)
- `is_safe_filename()` - Validates against path traversal patterns
- Used throughout the codebase for all file operations
- Prevents `../../../etc/passwd` style attacks
- Limits filename length to 255 characters

**CORS Configuration:**
- Restricted origins (not `*`) in production
- Specific allowed methods (GET, POST, PUT, DELETE, PATCH, OPTIONS)
- Configured in `main.py` via environment variables

**Authentication:**
- JWT token-based authentication with 24-hour access token expiration
- Refresh token support with 30-day expiration
- bcrypt password hashing with passlib
- Passwords truncated to 72 bytes (bcrypt limitation)
- JWT secret key validation (minimum 32 characters, default rejected)
- Credentials stored in `config/users.json`
- Refresh tokens stored in `config/refresh_tokens.json`

## Key Implementation Details

**Resume Support:**
- Downloads use HTTP Range headers to resume from last byte
- Files downloaded in 1MB chunks
- Partial files cleaned up on cancel
- Resume position tracked in `downloaded_bytes` field

**Domain Handling:**
- Anime providers use dynamic domain detection (e.g., Anime-Sama fetches current domain from anime-sama.pw)
- Multiple domains per provider supported in configuration
- Domain detection via `detect_provider_from_url(url)` in providers.py

**Task Lifecycle:**
- PENDING → DOWNLOADING → PAUSED / COMPLETED / CANCELLED / FAILED
- Active downloads tracked in `active_downloads` dict
- All tasks stored in `tasks` dict with UUID keys
- Completed files preserved when deleting tasks (only partial files removed)

**Video Streaming:**
- Range header support for seeking in video player
- Serves from `/downloads` directory via StaticFiles
- Video extensions: .mp4, .mkv, .avi, .mov, .wmv, .flv, .webm

**Error Handling:**
- Graceful degradation with status tracking
- Network errors caught and reported in task status
- Automatic retry on resume
- Downloads > 1MB considered complete to skip small error files

## Dependencies

**Core:**
- fastapi - Web framework
- uvicorn - ASGI server
- httpx - Async HTTP client
- beautifulsoup4, lxml - HTML parsing
- aiofiles - Async file operations
- jieba - Chinese text segmentation for fuzzy search
- passlib[bcrypt] - Password hashing
- python-jose[cryptography] - JWT token handling
- apscheduler - Task scheduling for auto-download
- pydantic-settings - Environment-based configuration

**Python Testing:**
- pytest - Test framework
- pytest-asyncio - Async test support
- pytest-cov - Coverage reporting
- pytest-mock - Mocking support
- pytest-timeout - Test timeout handling
- pytest-html - HTML test reports

**JavaScript Testing (optional, for frontend):**
- vitest - Fast JavaScript test runner
- jsdom - DOM implementation for tests
- @playwright/test - End-to-end browser testing
