Add AniList resolver as MAL fallback; fix SeriesGroup, tag formatting, empty-cache bug
This commit is contained in:
+44
-27
@@ -47,6 +47,7 @@ import requests
|
||||
from MangadexVolumeResolver import MangaDexVolumeResolver
|
||||
from MangaBakaWorksResolver import MangaBakaWorksResolver
|
||||
from MALResolver import MALResolver
|
||||
from AniListResolver import AniListResolver
|
||||
|
||||
try:
|
||||
from PIL import Image
|
||||
@@ -168,7 +169,8 @@ class ComicInfoBuilder:
|
||||
session: "requests.Session | None" = None,
|
||||
volume_resolver: "MangaDexVolumeResolver | None" = None,
|
||||
works_resolver: "MangaBakaWorksResolver | None" = None,
|
||||
mal_resolver: "MALResolver | None" = None):
|
||||
mal_resolver: "MALResolver | None" = None,
|
||||
al_resolver: "AniListResolver | None" = None):
|
||||
if not manga_title or not str(manga_title).strip():
|
||||
raise ValueError("manga_title must not be empty.")
|
||||
|
||||
@@ -190,9 +192,11 @@ class ComicInfoBuilder:
|
||||
api_base_url=api_base_url,
|
||||
request_timeout=request_timeout,
|
||||
session=self._session))
|
||||
# MALResolver is a Singleton — it manages its own session and caches.
|
||||
# Both resolvers are Singletons — they manage their own sessions/caches.
|
||||
self._mal_resolver = mal_resolver or MALResolver(
|
||||
request_timeout=request_timeout)
|
||||
self._al_resolver = al_resolver or AniListResolver(
|
||||
request_timeout=request_timeout)
|
||||
|
||||
self._metadata: "dict | None" = None
|
||||
self._pages: list[dict] = []
|
||||
@@ -405,6 +409,8 @@ class ComicInfoBuilder:
|
||||
mal_id = (self._mal_id_from_source(md)
|
||||
or self._mal_resolver.find_mal_id(
|
||||
md.get("title") or self._manga_title))
|
||||
al_id = self._al_id_from_source(md)
|
||||
|
||||
mal_stats = self._mal_resolver.get_stats(mal_id)
|
||||
add("Summary", self._build_summary(md, sd, mal_stats))
|
||||
|
||||
@@ -432,10 +438,12 @@ class ComicInfoBuilder:
|
||||
# to display form ("Slice Of Life") so Kavita / readers show them
|
||||
# consistently with the (already-titled-cased) Tags field.
|
||||
add("Genre", ", ".join(_format_term(g) for g in (md.get("genres") or [])))
|
||||
add("Tags", ", ".join(md.get("tags") or []))
|
||||
add("Tags", ", ".join(_format_term(t) for t in (md.get("tags") or [])))
|
||||
|
||||
# ----- Characters from MAL ------------------------------------------
|
||||
# ----- Characters — MAL first, AniList fallback ---------------------
|
||||
characters = self._mal_resolver.get_characters(mal_id)
|
||||
if not characters and al_id:
|
||||
characters = self._al_resolver.get_characters(al_id)
|
||||
add("Characters", ", ".join(characters) if characters else None)
|
||||
|
||||
# ----- Web links ----------------------------------------------------
|
||||
@@ -571,32 +579,28 @@ class ComicInfoBuilder:
|
||||
# ======================================================================
|
||||
def _determine_series_group(self, md: dict) -> "str | None":
|
||||
"""
|
||||
Determines the SeriesGroup value from MangaDex relationships.
|
||||
Determines SeriesGroup from MangaBaka's relationships_v2 field.
|
||||
|
||||
- If the series has a `main_story` parent -> use that title.
|
||||
- If the series itself has child works (spin-offs, sequels …)
|
||||
-> use the series own title so all related works are grouped.
|
||||
- Otherwise -> None (no SeriesGroup).
|
||||
- If the series has a 'parent' relationship entry → fetch the parent
|
||||
series and return its MangaBaka title (so arcs/sequels appear under
|
||||
the root series in Kavita).
|
||||
- Otherwise → return the series' own title (it is the root, or a
|
||||
standalone series with no parent).
|
||||
"""
|
||||
manga_id = self._mangadex_id_from_source(md)
|
||||
if not manga_id:
|
||||
return None
|
||||
try:
|
||||
relations = self._volume_resolver.get_series_relations(manga_id)
|
||||
except Exception:
|
||||
return None
|
||||
for rel in (md.get("relationships_v2") or []):
|
||||
if rel.get("relation_type") == "parent":
|
||||
parent_id = rel.get("to_series_id")
|
||||
if parent_id is not None:
|
||||
try:
|
||||
parent_md = self._fetch_series_by_id(parent_id)
|
||||
parent_title = parent_md.get("title")
|
||||
if parent_title:
|
||||
return parent_title
|
||||
except Exception:
|
||||
pass
|
||||
break
|
||||
|
||||
if not relations:
|
||||
return None
|
||||
|
||||
main_stories = relations.get("main_story") or []
|
||||
if main_stories:
|
||||
return main_stories[0]
|
||||
|
||||
if any(t in relations for t in _CHILD_RELATION_TYPES):
|
||||
return md.get("title") or self._manga_title
|
||||
|
||||
return None
|
||||
return md.get("title") or self._manga_title
|
||||
|
||||
# ======================================================================
|
||||
# Title helpers
|
||||
@@ -867,6 +871,19 @@ class ComicInfoBuilder:
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _al_id_from_source(md: dict) -> "int | None":
|
||||
for raw_key, info in (md.get("source") or {}).items():
|
||||
if _normalise_key(raw_key) == "anilist":
|
||||
if isinstance(info, dict):
|
||||
mid = info.get("id")
|
||||
if mid is not None:
|
||||
try:
|
||||
return int(mid)
|
||||
except (TypeError, ValueError):
|
||||
pass
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _publishers_by_type(md: dict, ptype: str) -> "str | None":
|
||||
names = [p.get("name") for p in (md.get("publishers") or [])
|
||||
|
||||
Reference in New Issue
Block a user