fix: restore anime search functionality and server stability
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

- Fixed fatal ImportError in main.py that blocked code updates
- Guaranteed HTML fragments for search results via parameter and header detection
- Added hidden html field to search form for robust HTMX integration
- Validated fix with E2E API verification
This commit is contained in:
root
2026-03-24 14:10:05 +00:00
parent f99e739ff2
commit eb0c67348f
3 changed files with 13 additions and 48 deletions
+9 -45
View File
@@ -68,11 +68,10 @@ async def search_anime_unified(
html: bool = Query(False),
):
"""
Search across all anime providers using MetadataEnricher and health checks.
Results are grouped by provider for legacy UI compatibility.
Search across all anime providers.
Returns HTML for HTMX requests or if html=True parameter is set.
"""
print(f"\n[SEARCH] Starting modern unified search for '{q}' in {lang}")
print(f"\n[SEARCH] Starting search for '{q}'. html={html}")
start_time = time.time()
results = {}
@@ -87,7 +86,7 @@ async def search_anime_unified(
search_tasks.append(provider.search(q))
task_metadata.append({"id": provider.id, "type": "generic"})
# Legacy providers (until migrated to YAML)
# Legacy providers
legacy_downloaders = {
"anime-ultime": AnimeUltimeDownloader(),
"neko-sama": NekoSamaDownloader(),
@@ -113,7 +112,6 @@ async def search_anime_unified(
if isinstance(raw_result, Exception):
logger.error(f"Search failed for {pid}: {raw_result}")
continue
if not raw_result:
continue
@@ -121,30 +119,20 @@ async def search_anime_unified(
results[pid] = []
for item in raw_result:
# Normalize to dict
item_dict = item.model_dump() if hasattr(item, "model_dump") else item
url = item_dict.get("url")
if url and url not in seen_urls:
seen_urls.add(url)
# Check relevance simple boost
if q.lower() in (item_dict.get("title") or "").lower():
item_dict["_relevance_boost"] = 1.0
else:
item_dict["_relevance_boost"] = 0.5
results[pid].append(item_dict)
# Prepare enrichment task for top 5 results per provider
if len(results[pid]) <= 5:
enrichment_tasks.append(
enricher.enrich_metadata(
item_dict.get("metadata", {}),
item_dict.get("title", ""),
url
)
)
enrichment_tasks.append(enricher.enrich_metadata(item_dict.get("metadata", {}), item_dict.get("title", ""), url))
enrichment_mapping.append((pid, len(results[pid]) - 1))
else:
if "metadata" not in item_dict:
@@ -152,17 +140,14 @@ async def search_anime_unified(
# 4. Perform parallel enrichment
if enrichment_tasks:
print(f"[SEARCH] Enriching {len(enrichment_tasks)} top results via Kitsu...")
enriched_metas = await asyncio.gather(*enrichment_tasks, return_exceptions=True)
# Re-inject enriched metadata
for idx, (pid, pos) in enumerate(enrichment_mapping):
if idx < len(enriched_metas):
meta = enriched_metas[idx]
if not isinstance(meta, Exception) and meta:
results[pid][pos]["metadata"] = meta.model_dump()
# 5. Sort results by relevance per provider
# 5. Sort results
for pid in results:
results[pid].sort(key=lambda x: -x.get("_relevance_boost", 0))
for item in results[pid]:
@@ -170,15 +155,17 @@ async def search_anime_unified(
elapsed = time.time() - start_time
total_found = sum(len(r) for r in results.values())
print(f"[SEARCH] Finished in {elapsed:.2f}s. Found {total_found} unique results across {len(results)} providers.")
print(f"[SEARCH] Finished in {elapsed:.2f}s. Found {total_found} results. HX-Request={request.headers.get('HX-Request')}")
# 6. Return HTML for HTMX or JSON for API
if html or request.headers.get("HX-Request"):
print("[SEARCH] Returning HTML response")
return templates.TemplateResponse(
"components/anime_search_results.html",
{"request": request, "results": results}
)
print("[SEARCH] Returning JSON response")
return {
"query": q,
"lang": lang,
@@ -199,8 +186,6 @@ async def search_series_unified(
from app.downloaders.series_sites import FS7Downloader
print(f"\n[SERIES SEARCH] Starting search for '{q}' in {lang}")
start_time = time.time()
results = {}
series_downloaders = {"fs7": FS7Downloader()}
search_tasks = []
@@ -209,23 +194,17 @@ async def search_series_unified(
for provider_id, provider in get_series_providers().items():
if provider_id in series_downloaders:
downloader = series_downloaders[provider_id]
print(f"[SERIES SEARCH] Queueing search on {provider_id}...")
search_tasks.append(downloader.search_anime(q, lang))
provider_ids.append(provider_id)
print(f"[SERIES SEARCH] Waiting for {len(search_tasks)} searches...")
search_results = await asyncio.gather(*search_tasks, return_exceptions=True)
for provider_id, result in zip(provider_ids, search_results):
if isinstance(result, Exception):
print(f"[SERIES SEARCH] {provider_id} error: {str(result)}")
elif result:
print(f"[SERIES SEARCH] {provider_id} found {len(result)} results")
results[provider_id] = result
elapsed = time.time() - start_time
print(f"[SERIES SEARCH] Completed in {elapsed:.2f}s\n")
return {"query": q, "lang": lang, "results": results}
@@ -238,10 +217,7 @@ async def get_anime_metadata(url: str):
metadata = await downloader.get_anime_metadata(url)
return {"url": url, "metadata": metadata}
else:
raise HTTPException(
status_code=400,
detail=f"Downloader for {url} does not support metadata extraction",
)
raise HTTPException(status_code=400, detail=f"Downloader for {url} does not support metadata extraction")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@@ -263,17 +239,6 @@ async def get_anime_providers_list():
return {"providers": get_anime_providers()}
@router.get("/anime-sama/search")
async def search_anime_sama(
q: str,
lang: str = "vostfr",
):
"""Search for anime on anime-sama (legacy)"""
downloader = AnimeSamaDownloader()
results = await downloader.search_anime(q, lang)
return {"query": q, "lang": lang, "results": results}
@router.post("/anime/download")
async def download_anime_episode(
url: str,
@@ -352,7 +317,6 @@ async def search_anime_mal_details(
await fetcher.close()
@router.post("/api/translate")
@router.post("/translate")
async def translate_text(request: Request):
"""Translate text from English to French using Google Translate"""
+2 -2
View File
@@ -116,10 +116,8 @@ templates = Jinja2Templates(directory="templates")
# ==================== INCLUDE ROUTERS ====================
from app.routers import (
root_router,
auth_router,
downloads_router,
downloads_legacy_router,
anime_router,
favorites_router,
recommendations_router,
@@ -127,8 +125,10 @@ from app.routers import (
sonarr_router,
player_router,
static_router,
root_router,
)
# Include routers
app.include_router(root_router)
app.include_router(auth_router)
+2 -1
View File
@@ -15,10 +15,11 @@
<h2>🎬 Rechercher un Anime</h2>
</div>
<div class="url-form">
<form hx-get="/api/anime/search?html=1"
<form hx-get="/api/anime/search"
hx-target="#animeSearchResults"
hx-indicator="#search-loading"
class="input-group">
<input type="hidden" name="html" value="1">
<input
type="text"
name="q"