Files
ohm_streaming/app/routers/router_watchlist.py
T
root d4d8d8a3b6
CI / Test (Python 3.11) (push) Has been cancelled
CI / Test (Python 3.12) (push) Has been cancelled
CI / Lint (push) Has been cancelled
CI / Type Check (push) Has been cancelled
CI / Summary (push) Has been cancelled
refactor: migrate main.py to modular routers and add project roadmap
- Migrated monolithic main.py to feature-scoped routers in app/routers/
- Added GEMINI.md for project context and AI instructional guidelines
- Updated README.md with a comprehensive modernization plan (SQL migration, robust scraping DSL, frontend modernization)
- Improved authentication with cookie support and modular JS
- Updated test suite and documentation
2026-03-24 10:12:04 +00:00

460 lines
16 KiB
Python

"""
Watchlist management routes for Ohm Stream Downloader API.
"""
import re
from typing import List, Optional
from fastapi import APIRouter, BackgroundTasks, Depends, HTTPException
from app.download_manager import DownloadManager
from app.downloaders import get_downloader
from app.models import DownloadRequest
from app.models.auth import User
from app.models.watchlist import (
WatchlistItem,
WatchlistItemCreate,
WatchlistItemUpdate,
WatchlistSettings,
WatchlistStatus,
)
from app.routers.router_auth import get_current_user_from_token
router = APIRouter(prefix="/api/watchlist", tags=["watchlist"])
def get_download_manager() -> DownloadManager:
from main import download_manager
return download_manager
@router.post("", response_model=WatchlistItem)
async def add_to_watchlist(
item_data: WatchlistItemCreate,
current_user: User = Depends(get_current_user_from_token),
):
"""Add an anime to the watchlist"""
from main import watchlist_manager
try:
item = watchlist_manager.create(current_user.id, item_data)
return item
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error adding to watchlist: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("", response_model=List[WatchlistItem])
async def get_watchlist(
status: Optional[WatchlistStatus] = None,
current_user: User = Depends(get_current_user_from_token),
):
"""Get user's watchlist"""
from main import watchlist_manager
try:
items = watchlist_manager.get_all(user_id=current_user.id, status=status)
return items
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error getting watchlist: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/settings", response_model=WatchlistSettings)
async def get_watchlist_settings(
current_user: User = Depends(get_current_user_from_token),
):
"""Get global watchlist settings"""
from main import watchlist_manager
try:
settings = watchlist_manager.get_settings()
return settings
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error getting watchlist settings: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.put("/settings", response_model=WatchlistSettings)
async def update_watchlist_settings(
settings: WatchlistSettings,
current_user: User = Depends(get_current_user_from_token),
):
"""Update global watchlist settings"""
from main import auto_download_scheduler, watchlist_manager
try:
updated_settings = watchlist_manager.update_settings(settings)
if auto_download_scheduler.is_running():
auto_download_scheduler.restart()
return updated_settings
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error updating watchlist settings: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/stats")
async def get_watchlist_stats(
current_user: User = Depends(get_current_user_from_token),
):
"""Get watchlist statistics"""
from main import watchlist_manager
try:
stats = watchlist_manager.get_stats(user_id=current_user.id)
return stats
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error getting watchlist stats: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/check-all")
async def check_all_watchlist_items(
current_user: User = Depends(get_current_user_from_token),
):
"""Manually trigger a check for all due watchlist items"""
from main import episode_checker, watchlist_manager
try:
results = await episode_checker.check_all_due()
user_results = []
for result in results:
item = watchlist_manager.get_by_id(result.watchlist_item_id)
if item and item.user_id == current_user.id:
user_results.append(result)
return {
"status": "success",
"checked": len(user_results),
"total_new_episodes": sum(r.new_episodes_found for r in user_results),
"total_downloaded": sum(len(r.episodes_downloaded) for r in user_results),
"results": user_results,
}
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error checking all watchlist items: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/scheduler/status")
async def get_scheduler_status(
current_user: User = Depends(get_current_user_from_token),
):
"""Get auto-download scheduler status"""
from main import auto_download_scheduler, watchlist_manager
try:
return {
"running": auto_download_scheduler.is_running(),
"next_run": auto_download_scheduler.get_next_run_time(),
"settings": watchlist_manager.get_settings(),
}
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error getting scheduler status: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/scheduler/start")
async def start_scheduler(
current_user: User = Depends(get_current_user_from_token),
):
"""Start the auto-download scheduler"""
from main import auto_download_scheduler
try:
if auto_download_scheduler.is_running():
return {
"status": "already_running",
"message": "Scheduler is already running",
}
auto_download_scheduler.start()
return {"status": "started", "message": "Scheduler started successfully"}
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error starting scheduler: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/scheduler/stop")
async def stop_scheduler(
current_user: User = Depends(get_current_user_from_token),
):
"""Stop the auto-download scheduler"""
from main import auto_download_scheduler
try:
if not auto_download_scheduler.is_running():
return {"status": "not_running", "message": "Scheduler is not running"}
auto_download_scheduler.stop()
return {"status": "stopped", "message": "Scheduler stopped successfully"}
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error stopping scheduler: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{item_id}", response_model=WatchlistItem)
async def get_watchlist_item(
item_id: str,
current_user: User = Depends(get_current_user_from_token),
):
"""Get a specific watchlist item"""
from main import watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
return item
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error getting watchlist item: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{item_id}", response_model=WatchlistItem)
async def update_watchlist_item(
item_id: str,
update_data: WatchlistItemUpdate,
current_user: User = Depends(get_current_user_from_token),
):
"""Update a watchlist item"""
from main import watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
updated_item = watchlist_manager.update(item_id, update_data)
return updated_item
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error updating watchlist item: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{item_id}")
async def delete_watchlist_item(
item_id: str,
current_user: User = Depends(get_current_user_from_token),
):
"""Delete an anime from the watchlist"""
from main import watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
success = watchlist_manager.delete(item_id)
if not success:
raise HTTPException(status_code=500, detail="Failed to delete item")
return {"status": "success", "message": "Item deleted from watchlist"}
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error deleting watchlist item: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{item_id}/check")
async def check_watchlist_item(
item_id: str,
current_user: User = Depends(get_current_user_from_token),
):
"""Manually trigger a check for new episodes"""
from main import episode_checker, watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
result = await episode_checker.manual_check(item_id)
if not result:
raise HTTPException(status_code=500, detail="Check failed")
return result
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error checking watchlist item: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{item_id}/download-all")
async def download_all_episodes(
item_id: str,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user_from_token),
):
"""Download the LATEST SEASON episodes for a watchlist item"""
from main import download_manager, watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
downloader = get_downloader(item.anime_url)
latest_season_url = item.anime_url
if hasattr(downloader, "get_seasons"):
try:
seasons = await downloader.get_seasons(item.anime_url)
if seasons and len(seasons) > 0:
latest_season = seasons[-1]
latest_season_url = latest_season.get("url", item.anime_url)
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.warning(f"Could not fetch seasons, using default URL: {e}")
episodes = await downloader.get_episodes(latest_season_url, item.lang)
if not episodes:
return {
"status": "warning",
"message": f"No episodes found for {item.anime_title}",
"result": {"new_episodes_found": 0, "episodes_downloaded": []},
}
task_ids = []
season_match = re.search(r"saison(\d+)", latest_season_url, re.IGNORECASE)
season_num = season_match.group(1) if season_match else "1"
anime_title_clean = (
item.anime_title.replace("/", "-").replace("\\", "-").strip()
)
for episode in episodes:
ep_num = episode.get("episode", "01")
filename = f"{anime_title_clean} - S{season_num} - Episode {ep_num}.mp4"
request = DownloadRequest(url=episode["url"], filename=filename)
task = download_manager.create_task(request)
task_ids.append(task.id)
background_tasks.add_task(download_manager.start_download, task.id)
watchlist_manager.update(
item_id,
{"last_episode_downloaded": len(episodes), "total_episodes": len(episodes)},
)
return {
"status": "success",
"message": f"Downloading {len(task_ids)} episodes from latest season for {item.anime_title}",
"task_ids": task_ids,
"total_episodes": len(episodes),
"season_url": latest_season_url,
}
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error downloading all episodes: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{item_id}/pause", response_model=WatchlistItem)
async def pause_watchlist_item(
item_id: str,
current_user: User = Depends(get_current_user_from_token),
):
"""Pause automatic downloading for a specific anime"""
from main import watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
update_data = WatchlistItemUpdate(status=WatchlistStatus.PAUSED)
updated_item = watchlist_manager.update(item_id, update_data)
return updated_item
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error pausing watchlist item: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))
@router.post("/{item_id}/resume", response_model=WatchlistItem)
async def resume_watchlist_item(
item_id: str,
current_user: User = Depends(get_current_user_from_token),
):
"""Resume automatic downloading for a paused anime"""
from main import watchlist_manager
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
raise HTTPException(status_code=404, detail="Watchlist item not found")
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
update_data = WatchlistItemUpdate(status=WatchlistStatus.ACTIVE)
updated_item = watchlist_manager.update(item_id, update_data)
return updated_item
except HTTPException:
raise
except Exception as e:
from logging import getLogger
logger = getLogger(__name__)
logger.error(f"Error resuming watchlist item: {e}", exc_info=True)
raise HTTPException(status_code=500, detail=str(e))