86 lines
3.6 KiB
Python
86 lines
3.6 KiB
Python
#!/usr/bin/env python3
|
|
|
|
"""Helpers for resolving watchlist identities from queued download jobs."""
|
|
|
|
import re
|
|
|
|
|
|
def _resolution_result(show_id, title, reason, return_reason):
|
|
if return_reason:
|
|
return show_id, title, reason
|
|
return show_id, title
|
|
|
|
|
|
def _normalized_variants(title, normalize_title_key_fn, base_title_variants_fn):
|
|
del base_title_variants_fn
|
|
variants = set()
|
|
normalized_title = normalize_title_key_fn(title)
|
|
if normalized_title:
|
|
variants.add(normalized_title)
|
|
stripped = re.sub(r"\b(19|20)\d{2}\b", " ", normalized_title)
|
|
stripped = re.sub(r"\s+", " ", stripped).strip()
|
|
if stripped:
|
|
variants.add(stripped)
|
|
return variants
|
|
|
|
|
|
def titles_confidently_match(expected_title, candidate_title, normalize_title_key_fn, base_title_variants_fn):
|
|
expected_variants = _normalized_variants(expected_title, normalize_title_key_fn, base_title_variants_fn)
|
|
candidate_variants = _normalized_variants(candidate_title, normalize_title_key_fn, base_title_variants_fn)
|
|
if not expected_variants or not candidate_variants:
|
|
return False
|
|
return bool(expected_variants & candidate_variants)
|
|
|
|
|
|
def resolve_job_watchlist_identity(
|
|
job,
|
|
*,
|
|
search_fn,
|
|
normalize_title_key_fn,
|
|
base_title_variants_fn,
|
|
default_mode,
|
|
return_reason=False,
|
|
):
|
|
show_id = str(job.get("show_id") or "").strip()
|
|
title = str(job.get("title") or job.get("anime_name") or "").strip()
|
|
if show_id:
|
|
return _resolution_result(show_id, title, "", return_reason)
|
|
raw_index = job.get("result_index")
|
|
if raw_index in (None, ""):
|
|
index = None
|
|
else:
|
|
try:
|
|
index = max(1, int(raw_index))
|
|
except (TypeError, ValueError):
|
|
return _resolution_result("", title, "invalid queued result index", return_reason)
|
|
query = str(job.get("query") or title).strip()
|
|
if not query or not title:
|
|
return _resolution_result("", title, "missing queued search query or title", return_reason)
|
|
try:
|
|
results = search_fn(query, job.get("mode") or default_mode)
|
|
except Exception as exc:
|
|
return _resolution_result("", title, f"search fallback failed: {exc}", return_reason)
|
|
matches = []
|
|
for position, result in enumerate(results, start=1):
|
|
candidate_title = str(result.get("title") or "").strip()
|
|
if not titles_confidently_match(title, candidate_title, normalize_title_key_fn, base_title_variants_fn):
|
|
continue
|
|
resolved_show_id = str(result.get("id") or "").strip()
|
|
if not resolved_show_id:
|
|
continue
|
|
matches.append((position, result, candidate_title, resolved_show_id))
|
|
if not matches:
|
|
candidate_title = str((results[0].get("title") if results else "") or "").strip()
|
|
return _resolution_result("", title, f"title mismatch with fallback search result: {candidate_title or 'unknown'}", return_reason)
|
|
if index is not None:
|
|
indexed = next((match for match in matches if match[0] == index), None)
|
|
if indexed is not None:
|
|
_position, result, candidate_title, resolved_show_id = indexed
|
|
return _resolution_result(resolved_show_id, candidate_title or title, "", return_reason)
|
|
if len(matches) != 1:
|
|
return _resolution_result("", title, "ambiguous fallback search results", return_reason)
|
|
_position, result, candidate_title, resolved_show_id = matches[0]
|
|
if not resolved_show_id:
|
|
return _resolution_result("", title, "fallback search result did not include a show_id", return_reason)
|
|
return _resolution_result(resolved_show_id, candidate_title or title, "", return_reason)
|