feat: Add AGENTS.md and new downloaders with metadata enrichment

- Add AGENTS.md for agentic coding guidelines
- Add Oneupload and Smoothpre video player downloaders
- Add MetadataEnrichment service with Kitsu API fallback
- Add tests for metadata enrichment and provider detection
- Update .gitignore to ignore runtime config files
This commit is contained in:
root
2026-02-24 20:14:31 +00:00
parent da5403a307
commit 2482a1fe58
7 changed files with 2119 additions and 0 deletions
+182
View File
@@ -0,0 +1,182 @@
# AGENTS.md - Agentic Coding Guidelines
This file provides guidance for AI agents working in this repository.
## Quick Start
```bash
# Setup
python3 -m venv venv && source venv/bin/activate
pip install -r requirements.txt
# Run dev server
uvicorn main:app --reload --host 0.0.0.0 --port 3000
```
## Build, Lint & Test Commands
### Running Tests
```bash
# All tests
pytest
# With coverage
pytest --cov=app --cov-report=html
# Unit only (fast)
pytest -m "unit"
# Exclude slow tests
pytest -m "not slow"
# Verbose with print debugging
pytest -v -s
```
### Running Single Tests
```bash
# Specific file
pytest tests/test_sonarr.py -v
# Specific class
pytest tests/test_sonarr.py::TestSonarrHandler -v
# Specific test
pytest tests/test_sonarr.py::TestSonarrHandler::test_add_mapping -v
# Pattern match
pytest -k "test_download" -v
```
## Code Style
### Imports (PEP 8 order)
1. Standard library (`os`, `json`, `asyncio`)
2. Third-party (`httpx`, `beautifulsoup4`, `fastapi`)
3. Local app (`app.config`, `app.utils`)
```python
import os
import asyncio
from typing import Optional
import httpx
from fastapi import APIRouter, HTTPException
from app.config import get_settings
from app.models import DownloadTask, DownloadStatus
```
### Formatting
- **Line length**: 120 chars max
- **Indentation**: 4 spaces
- **Blank lines**: 2 between top-level, 1 between inline
### Type Annotations
- Use explicit types
- Use `Optional[X]` not `X | None`
- Use `list[X]`, `dict[X, Y]`
```python
# Good
async def get_download_link(url: str, target_filename: Optional[str] = None) -> tuple[str, str]:
results: list[dict[str, str]] = []
# Avoid
async def get_download_link(url, target_filename=None):
results = []
```
### Naming Conventions
| Element | Convention | Example |
|---------|------------|---------|
| Modules | snake_case | `download_manager.py` |
| Classes | PascalCase | `DownloadManager` |
| Functions | snake_case | `get_download_link()` |
| Constants | UPPER_SNAKE | `MAX_PARALLEL_DOWNLOADS` |
| Variables | snake_case | `download_task` |
| Enums | PascalCase | `DownloadStatus` |
| Enum values | UPPER_SNAKE | `DownloadStatus.PENDING` |
### Async/Await
- Always use for I/O operations
- Close clients properly to avoid leaks
```python
async def close(self):
await self.client.aclose()
```
### Error Handling
- Use try/except for recoverable errors
- Raise specific exceptions (`HTTPException`, `ValueError`)
- Never use empty except blocks
- Log errors appropriately
```python
try:
result = await client.get(url)
except httpx.TimeoutException:
logger.warning(f"Request timeout for {url}")
raise HTTPException(status_code=504, detail="Request timeout")
```
### File Operations
- Always sanitize filenames: `app.utils.sanitize_filename()`
- Validate paths: `app.utils.is_safe_filename()`
### Testing
- Use pytest with pytest-asyncio
- Mark tests: `@pytest.mark.unit`, `@pytest.mark.integration`
- Use fixtures from `tests/conftest.py`
```python
@pytest.mark.unit
@pytest.mark.asyncio
async def test_download_manager():
manager = DownloadManager(max_parallel=3)
assert manager.max_parallel == 3
```
### Security
- Never hardcode secrets - use environment variables
- Validate all inputs (URLs, filenames)
- Use HMAC for webhook verification when configured
- Limit CORS origins - never use `*` in production
## Architecture Patterns
**Three-Tier Downloader:**
1. `app/downloaders/anime_sites/` - Anime catalogs
2. `app/downloaders/series_sites/` - TV series catalogs
3. `app/downloaders/video_players/` - File hosting
Each has base class and factory. When adding providers:
1. Inherit from appropriate base class
2. Implement required methods
3. Register in factory
4. Add to providers config in `app/providers.py`
**URL Convention**: Pipe-separated format preserves metadata:
```
video_url|anime_page_url|episode_title
```
## Key Files
| File | Purpose |
|------|---------|
| `main.py` | FastAPI app, endpoints |
| `app/config.py` | Pydantic Settings |
| `app/download_manager.py` | Download queue |
| `app/utils.py` | sanitize_filename |
| `app/auth.py` | JWT auth |
| `app/models/__init__.py` | Pydantic models |
## Configuration
- Use `.env` from `.env.example`
- JWT_SECRET_KEY must change in production