feat: Add favorites management system with full API endpoints
Implement a comprehensive favorites system for anime tracking with the following features:
- Add/remove anime from favorites with unique anime_id
- Toggle favorite status (add if not exists, remove if exists)
- List favorites with sorting (title, rating, year, created_at, updated_at)
- Filter favorites by provider and genre
- Get detailed statistics (total count, provider breakdown, genre distribution, top-rated)
- Persistent storage using JSON file (favorites.json)
- Full REST API with 6 endpoints
API Endpoints:
- GET /api/favorites - List all favorites with sorting/filtering
- POST /api/favorites - Add anime to favorites
- DELETE /api/favorites/{anime_id} - Remove from favorites
- GET /api/favorites/{anime_id} - Get specific favorite details
- GET /api/favorites/stats - Get favorites statistics
- POST /api/favorites/toggle - Toggle favorite status
Generated with [Claude Code](https://claude.ai/code)
via [Happy](https://happy.engineering)
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Happy <yesreply@happy.engineering>
This commit is contained in:
@@ -16,6 +16,7 @@ from app.models import DownloadRequest, DownloadTask, DownloadStatus
|
||||
from app.download_manager import DownloadManager
|
||||
from app.downloaders import AnimeSamaDownloader
|
||||
from app import providers
|
||||
from app.favorites import get_favorites_manager
|
||||
|
||||
app = FastAPI(title="Ohm Stream Downloader")
|
||||
|
||||
@@ -42,7 +43,7 @@ async def root():
|
||||
return {
|
||||
"message": "Ohm Stream Downloader API",
|
||||
"status": "running",
|
||||
"version": "2.1",
|
||||
"version": "2.2",
|
||||
"endpoints": {
|
||||
"POST /api/download": "Start a new download",
|
||||
"GET /api/downloads": "List all downloads",
|
||||
@@ -55,6 +56,12 @@ async def root():
|
||||
"GET /api/anime/metadata": "Get detailed anime metadata (synopsis, genres, rating, etc.)",
|
||||
"GET /api/anime/episodes": "Get episode list for an anime",
|
||||
"POST /api/anime/download-season": "Download all episodes of a season",
|
||||
"GET /api/favorites": "List all favorite anime",
|
||||
"POST /api/favorites": "Add anime to favorites",
|
||||
"DELETE /api/favorites/{anime_id}": "Remove from favorites",
|
||||
"GET /api/favorites/{anime_id}": "Get favorite anime details",
|
||||
"GET /api/favorites/stats": "Get favorites statistics",
|
||||
"POST /api/favorites/toggle": "Toggle anime in favorites",
|
||||
"GET /web": "Web interface"
|
||||
}
|
||||
}
|
||||
@@ -547,6 +554,143 @@ async def video_player_by_filename(request: Request, filename: str):
|
||||
})
|
||||
|
||||
|
||||
# ==================== FAVORITES API ====================
|
||||
|
||||
@app.get("/api/favorites")
|
||||
async def list_favorites(
|
||||
sort_by: str = "created_at",
|
||||
order: str = "desc",
|
||||
filter_provider: str = None,
|
||||
filter_genre: str = None
|
||||
):
|
||||
"""
|
||||
List all favorite anime with optional sorting and filtering
|
||||
|
||||
Query params:
|
||||
- sort_by: title, rating, year, created_at, updated_at (default: created_at)
|
||||
- order: asc, desc (default: desc)
|
||||
- filter_provider: Filter by provider (anime-sama, neko-sama, etc.)
|
||||
- filter_genre: Filter by genre (Action, Adventure, etc.)
|
||||
"""
|
||||
fav_manager = get_favorites_manager()
|
||||
favorites = await fav_manager.list_favorites(
|
||||
sort_by=sort_by,
|
||||
order=order,
|
||||
filter_provider=filter_provider,
|
||||
filter_genre=filter_genre
|
||||
)
|
||||
return {
|
||||
"favorites": favorites,
|
||||
"total": len(favorites),
|
||||
"filters": {
|
||||
"sort_by": sort_by,
|
||||
"order": order,
|
||||
"provider": filter_provider,
|
||||
"genre": filter_genre
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@app.post("/api/favorites")
|
||||
async def add_favorite(request: Request):
|
||||
"""
|
||||
Add an anime to favorites
|
||||
|
||||
Body params (JSON):
|
||||
- anime_id: Unique identifier (e.g., provider + slug)
|
||||
- title: Anime title
|
||||
- url: Anime page URL
|
||||
- provider: Provider name
|
||||
- metadata: Optional metadata dict (synopsis, genres, rating, etc.)
|
||||
- poster_url: Optional poster image URL
|
||||
"""
|
||||
import json
|
||||
data = await request.json()
|
||||
|
||||
required_fields = ["anime_id", "title", "url", "provider"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
raise HTTPException(status_code=400, detail=f"Missing required field: {field}")
|
||||
|
||||
fav_manager = get_favorites_manager()
|
||||
favorite = await fav_manager.add_favorite(
|
||||
anime_id=data["anime_id"],
|
||||
title=data["title"],
|
||||
url=data["url"],
|
||||
provider=data["provider"],
|
||||
metadata=data.get("metadata"),
|
||||
poster_url=data.get("poster_url")
|
||||
)
|
||||
|
||||
return {"status": "added", "favorite": favorite}
|
||||
|
||||
|
||||
@app.delete("/api/favorites/{anime_id}")
|
||||
async def remove_favorite(anime_id: str):
|
||||
"""Remove an anime from favorites"""
|
||||
fav_manager = get_favorites_manager()
|
||||
removed = await fav_manager.remove_favorite(anime_id)
|
||||
|
||||
if not removed:
|
||||
raise HTTPException(status_code=404, detail="Favorite not found")
|
||||
|
||||
return {"status": "removed", "anime_id": anime_id}
|
||||
|
||||
|
||||
@app.get("/api/favorites/{anime_id}")
|
||||
async def get_favorite(anime_id: str):
|
||||
"""Get details of a specific favorite anime"""
|
||||
fav_manager = get_favorites_manager()
|
||||
favorite = await fav_manager.get_favorite(anime_id)
|
||||
|
||||
if not favorite:
|
||||
raise HTTPException(status_code=404, detail="Favorite not found")
|
||||
|
||||
return {"favorite": favorite}
|
||||
|
||||
|
||||
@app.get("/api/favorites/stats")
|
||||
async def get_favorites_stats():
|
||||
"""Get statistics about favorites"""
|
||||
fav_manager = get_favorites_manager()
|
||||
stats = await fav_manager.get_stats()
|
||||
return stats
|
||||
|
||||
|
||||
@app.post("/api/favorites/toggle")
|
||||
async def toggle_favorite(request: Request):
|
||||
"""
|
||||
Toggle an anime in favorites (add if not exists, remove if exists)
|
||||
|
||||
Body params (JSON):
|
||||
- anime_id: Unique identifier
|
||||
- title: Anime title
|
||||
- url: Anime page URL
|
||||
- provider: Provider name
|
||||
- metadata: Optional metadata dict
|
||||
- poster_url: Optional poster image URL
|
||||
"""
|
||||
import json
|
||||
data = await request.json()
|
||||
|
||||
required_fields = ["anime_id", "title", "url", "provider"]
|
||||
for field in required_fields:
|
||||
if field not in data:
|
||||
raise HTTPException(status_code=400, detail=f"Missing required field: {field}")
|
||||
|
||||
fav_manager = get_favorites_manager()
|
||||
result = await fav_manager.toggle_favorite(
|
||||
anime_id=data["anime_id"],
|
||||
title=data["title"],
|
||||
url=data["url"],
|
||||
provider=data["provider"],
|
||||
metadata=data.get("metadata"),
|
||||
poster_url=data.get("poster_url")
|
||||
)
|
||||
|
||||
return result
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
uvicorn.run(
|
||||
"main:app",
|
||||
|
||||
Reference in New Issue
Block a user