feat: download latest season only + fix lpayer CDN + HLS support

- Watchlist 'Suivre' now downloads only the latest season instead of all episodes
- Fix lpayer CDN 403 errors by adding proper Referer header for IP ranges
- Add HLS/m3u8 stream download support using ffmpeg
- Improve episode filename format: 'Anime - SX - Episode XX.mp4'
- Add CDN detection for lpayer IPs (185.237.x.x, 203.188.x.x, /mik/ path)
This commit is contained in:
root
2026-03-01 09:29:16 +00:00
parent 42daab1e50
commit d179694fb2
4 changed files with 224 additions and 21 deletions
+57 -8
View File
@@ -2094,9 +2094,12 @@ async def check_watchlist_item(
@app.post("/api/watchlist/{item_id}/download-all", tags=["Watchlist"])
async def download_all_episodes(
item_id: str,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user_from_token)
):
"""Download ALL episodes for a watchlist item (used when first following an anime)"""
"""Download the LATEST SEASON episodes for a watchlist item (used when first following an anime)"""
from app.downloaders import get_downloader
try:
item = watchlist_manager.get_by_id(item_id)
if not item:
@@ -2105,18 +2108,64 @@ async def download_all_episodes(
if item.user_id != current_user.id:
raise HTTPException(status_code=403, detail="Access denied")
# Temporarily set last_episode_downloaded to 0 to trigger download of ALL episodes
watchlist_manager.update(item_id, {"last_episode_downloaded": 0})
downloader = get_downloader(item.anime_url)
latest_season_url = item.anime_url # Default to current URL
result = await episode_checker.manual_check(item_id)
# Try to get the latest season if provider supports it
if hasattr(downloader, 'get_seasons'):
try:
seasons = await downloader.get_seasons(item.anime_url)
if seasons and len(seasons) > 0:
# Get the last season (most recent)
latest_season = seasons[-1]
latest_season_url = latest_season.get('url', item.anime_url)
logger.info(f"Found {len(seasons)} seasons, using latest: {latest_season.get('title', 'unknown')}")
except Exception as e:
logger.warning(f"Could not fetch seasons, using default URL: {e}")
# Note: download_new_episodes already updates last_episode_downloaded via update_check_time
# So we don't restore the original value - the new value reflects what was actually downloaded
# Get episodes from the latest season
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": []}
}
# Create download tasks for all episodes of the latest season
task_ids = []
# Extract season number from URL for filename
import re
season_match = re.search(r'saison(\d+)', latest_season_url, re.IGNORECASE)
season_num = season_match.group(1) if season_match else "1"
# Clean anime title for filename
anime_title_clean = item.anime_title.replace('/', '-').replace('\\', '-').strip()
for episode in episodes:
# Build a nice filename: "Anime Title - S1 - Episode 01.mp4"
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)
# Update watchlist with total episodes count
watchlist_manager.update(item_id, {
"last_episode_downloaded": len(episodes),
"total_episodes": len(episodes)
})
return {
"status": "success",
"message": f"Downloading all episodes for {item.anime_title}",
"result": result
"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