mirror of
https://github.com/EstrellaXD/Auto_Bangumi.git
synced 2026-07-27 09:00:41 +08:00
feat(offset): add automatic season/episode offset detection
- Add offset detector to identify season mismatches between RSS and TMDB - Only suggest season_offset (user sets episode_offset manually) - Add background scanner for existing bangumi rules - Add detect-offset and dismiss-review API endpoints - Add warning banner in edit dialog with auto-detect button - Add iOS-style notification badge for needs_review items - Yellow badge with "!" for warnings, purple badge for multi-rule count - Combined badge shows "! | 2" when both conditions apply - Yellow glow animation on cards needing review - Highlight warning items in rule selection popup Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,18 +1,59 @@
|
||||
from typing import Literal, Optional
|
||||
|
||||
from fastapi import APIRouter, Depends
|
||||
from fastapi.responses import JSONResponse
|
||||
from pydantic import BaseModel
|
||||
|
||||
from module.conf import settings
|
||||
from module.database import Database
|
||||
from module.manager import TorrentManager
|
||||
from module.models import APIResponse, Bangumi, BangumiUpdate
|
||||
from module.parser.analyser.offset_detector import (
|
||||
OffsetSuggestion as DetectorSuggestion,
|
||||
)
|
||||
from module.parser.analyser.offset_detector import detect_offset_mismatch
|
||||
from module.parser.analyser.tmdb_parser import tmdb_parser
|
||||
from module.security.api import UNAUTHORIZED, get_current_user
|
||||
|
||||
from .response import u_response
|
||||
|
||||
|
||||
class OffsetSuggestion(BaseModel):
|
||||
"""Legacy offset suggestion model."""
|
||||
suggested_offset: int
|
||||
reason: str
|
||||
|
||||
|
||||
class TMDBSummary(BaseModel):
|
||||
"""Summary of TMDB data for display."""
|
||||
title: str
|
||||
total_seasons: int
|
||||
season_episode_counts: dict[int, int]
|
||||
status: Optional[str]
|
||||
virtual_season_starts: Optional[dict[int, list[int]]] = None # {1: [1, 29], ...}
|
||||
|
||||
|
||||
class OffsetSuggestionDetail(BaseModel):
|
||||
"""Detailed offset suggestion from detector."""
|
||||
season_offset: int
|
||||
episode_offset: int
|
||||
reason: str
|
||||
confidence: Literal["high", "medium", "low"]
|
||||
|
||||
|
||||
class DetectOffsetRequest(BaseModel):
|
||||
"""Request body for detect-offset endpoint."""
|
||||
title: str
|
||||
parsed_season: int
|
||||
parsed_episode: int
|
||||
|
||||
|
||||
class DetectOffsetResponse(BaseModel):
|
||||
"""Response for detect-offset endpoint."""
|
||||
has_mismatch: bool
|
||||
suggestion: Optional[OffsetSuggestionDetail]
|
||||
tmdb_info: Optional[TMDBSummary]
|
||||
|
||||
router = APIRouter(prefix="/bangumi", tags=["bangumi"])
|
||||
|
||||
|
||||
@@ -202,3 +243,99 @@ async def suggest_offset(bangumi_id: int):
|
||||
with TorrentManager() as manager:
|
||||
resp = await manager.suggest_offset(bangumi_id)
|
||||
return resp
|
||||
|
||||
|
||||
@router.post(
|
||||
path="/detect-offset",
|
||||
response_model=DetectOffsetResponse,
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
async def detect_offset(request: DetectOffsetRequest):
|
||||
"""Detect season/episode mismatch with TMDB data.
|
||||
|
||||
Called by frontend before adding/subscribing to check if offsets are needed.
|
||||
"""
|
||||
language = settings.rss_parser.language
|
||||
tmdb_info = await tmdb_parser(request.title, language)
|
||||
|
||||
if not tmdb_info:
|
||||
return DetectOffsetResponse(
|
||||
has_mismatch=False,
|
||||
suggestion=None,
|
||||
tmdb_info=None,
|
||||
)
|
||||
|
||||
# Detect mismatch
|
||||
suggestion = detect_offset_mismatch(
|
||||
parsed_season=request.parsed_season,
|
||||
parsed_episode=request.parsed_episode,
|
||||
tmdb_info=tmdb_info,
|
||||
)
|
||||
|
||||
# Build TMDB summary
|
||||
tmdb_summary = TMDBSummary(
|
||||
title=tmdb_info.title,
|
||||
total_seasons=tmdb_info.last_season,
|
||||
season_episode_counts=tmdb_info.season_episode_counts or {},
|
||||
status=tmdb_info.series_status,
|
||||
virtual_season_starts=tmdb_info.virtual_season_starts,
|
||||
)
|
||||
|
||||
if suggestion:
|
||||
return DetectOffsetResponse(
|
||||
has_mismatch=True,
|
||||
suggestion=OffsetSuggestionDetail(
|
||||
season_offset=suggestion.season_offset,
|
||||
episode_offset=suggestion.episode_offset,
|
||||
reason=suggestion.reason,
|
||||
confidence=suggestion.confidence,
|
||||
),
|
||||
tmdb_info=tmdb_summary,
|
||||
)
|
||||
|
||||
return DetectOffsetResponse(
|
||||
has_mismatch=False,
|
||||
suggestion=None,
|
||||
tmdb_info=tmdb_summary,
|
||||
)
|
||||
|
||||
|
||||
@router.post(
|
||||
path="/dismiss-review/{bangumi_id}",
|
||||
response_model=APIResponse,
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
async def dismiss_review(bangumi_id: int):
|
||||
"""Clear the needs_review flag for a bangumi after user reviews."""
|
||||
with Database() as db:
|
||||
success = db.bangumi.clear_needs_review(bangumi_id)
|
||||
|
||||
if success:
|
||||
return JSONResponse(
|
||||
status_code=200,
|
||||
content={
|
||||
"status": True,
|
||||
"msg_en": "Review dismissed.",
|
||||
"msg_zh": "已取消检查标记。",
|
||||
},
|
||||
)
|
||||
else:
|
||||
return JSONResponse(
|
||||
status_code=404,
|
||||
content={
|
||||
"status": False,
|
||||
"msg_en": f"Bangumi {bangumi_id} not found.",
|
||||
"msg_zh": f"未找到番剧 {bangumi_id}。",
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@router.get(
|
||||
path="/needs-review",
|
||||
response_model=list[Bangumi],
|
||||
dependencies=[Depends(get_current_user)],
|
||||
)
|
||||
async def get_needs_review():
|
||||
"""Get all bangumi that need review for offset mismatch."""
|
||||
with Database() as db:
|
||||
return db.bangumi.get_needs_review()
|
||||
|
||||
Reference in New Issue
Block a user