fix: restore anime search functionality and server stability
- 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:
@@ -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"""
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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"
|
||||
|
||||
Reference in New Issue
Block a user