summary
This commit is contained in:
+90
-46
@@ -105,6 +105,42 @@ def _format_term(value: str) -> str:
|
|||||||
return str(value).replace("_", " ").strip().title() if value else ""
|
return str(value).replace("_", " ").strip().title() if value else ""
|
||||||
|
|
||||||
|
|
||||||
|
# Markdown backslash escape sequences recognised by CommonMark (e.g. \- → -)
|
||||||
|
_MD_ESCAPE_RE = re.compile(r'\\([\\`*_{}\[\]()\#+\-.!|~])')
|
||||||
|
|
||||||
|
|
||||||
|
def _md_to_html(text: str) -> str:
|
||||||
|
"""
|
||||||
|
Converts a subset of Markdown (as produced by MangaBaka) to HTML.
|
||||||
|
|
||||||
|
Handles: backslash escapes, [text](url) links, **bold**, *italic*,
|
||||||
|
blank-line paragraph splits, and single-newline line breaks.
|
||||||
|
Produces compact HTML with no raw newline characters — Kavita renders
|
||||||
|
every bare \\n as a <br>, so all line-breaks must be explicit.
|
||||||
|
"""
|
||||||
|
if not text:
|
||||||
|
return ""
|
||||||
|
# Unescape Markdown backslash sequences (\- → -, \* → *, …)
|
||||||
|
text = _MD_ESCAPE_RE.sub(r'\1', text)
|
||||||
|
# [text](url) → <a href="url">text</a>
|
||||||
|
text = re.sub(
|
||||||
|
r'\[([^\]]+)\]\(([^)]+)\)',
|
||||||
|
lambda m: f'<a href="{m.group(2)}">{m.group(1)}</a>',
|
||||||
|
text,
|
||||||
|
)
|
||||||
|
# **bold** before *italic* so ** is not mistaken for two *
|
||||||
|
text = re.sub(r'\*\*(.+?)\*\*', r'<strong>\1</strong>', text, flags=re.DOTALL)
|
||||||
|
text = re.sub(r'\*(.+?)\*', r'<em>\1</em>', text, flags=re.DOTALL)
|
||||||
|
# Split on blank lines → <p> blocks; single newlines → <br>
|
||||||
|
parts: list[str] = []
|
||||||
|
for para in re.split(r'\n{2,}', text.strip()):
|
||||||
|
para = para.strip()
|
||||||
|
if para:
|
||||||
|
parts.append(f"<p>{para.replace(chr(10), '<br>')}</p>")
|
||||||
|
return "".join(parts) # no raw \n — every \n becomes a <br> in Kavita
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Main class
|
# Main class
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
@@ -320,8 +356,7 @@ class ComicInfoBuilder:
|
|||||||
timeout=self.request_timeout)
|
timeout=self.request_timeout)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
data = resp.json().get("data") or []
|
data = resp.json().get("data") or []
|
||||||
|
return data[0] if data else None
|
||||||
return data[0] # I trust the API's relevance sorting and just take the first result, if any
|
|
||||||
|
|
||||||
def _fetch_series_by_id(self, series_id) -> dict:
|
def _fetch_series_by_id(self, series_id) -> dict:
|
||||||
url = f"{self.api_base_url}/series/{series_id}"
|
url = f"{self.api_base_url}/series/{series_id}"
|
||||||
@@ -665,60 +700,69 @@ class ComicInfoBuilder:
|
|||||||
def _build_summary(self, md: dict, sd: dict,
|
def _build_summary(self, md: dict, sd: dict,
|
||||||
mal_stats: "dict | None") -> "str | None":
|
mal_stats: "dict | None") -> "str | None":
|
||||||
"""
|
"""
|
||||||
Builds the <Summary> content.
|
Builds <Summary> as HTML (Kavita supports HTML in this field).
|
||||||
Appends a MAL statistics table (if available) after the description.
|
|
||||||
|
Structure (top → bottom):
|
||||||
|
1. MAL statistics — HTML link + table with padded columns
|
||||||
|
2. Series description — Markdown converted to HTML
|
||||||
|
3. Alternate titles — HTML table
|
||||||
|
|
||||||
|
IMPORTANT: no raw \\n characters anywhere in the output — Kavita
|
||||||
|
renders every bare newline as a <br>. Sections are separated with
|
||||||
|
an explicit <br> instead.
|
||||||
"""
|
"""
|
||||||
desc = (md.get("description") or sd.get("Summary") or "").strip()
|
# Inline style applied to label cells for readable column spacing.
|
||||||
|
_TD = 'style="padding-right:1.5em"'
|
||||||
|
|
||||||
if not mal_stats:
|
|
||||||
return desc or None
|
|
||||||
|
|
||||||
as_of = mal_stats.get("as_of", "")
|
|
||||||
score = mal_stats.get("score")
|
|
||||||
rank = mal_stats.get("rank")
|
|
||||||
scored = mal_stats.get("scored_by")
|
|
||||||
pop = mal_stats.get("popularity")
|
|
||||||
members = mal_stats.get("members")
|
|
||||||
favs = mal_stats.get("favorites")
|
|
||||||
url = mal_stats.get("url", "")
|
|
||||||
|
|
||||||
rows: list[str] = []
|
|
||||||
if score is not None: rows.append(f"Score\t{score}")
|
|
||||||
if rank is not None: rows.append(f"Ranked\t#{rank}")
|
|
||||||
if scored is not None: rows.append(f"Scored by\t{scored:,} users")
|
|
||||||
if pop is not None: rows.append(f"Popularity\t#{pop}")
|
|
||||||
if members is not None: rows.append(f"Members\t{members:,}")
|
|
||||||
if favs is not None: rows.append(f"Favorites\t{favs:,}")
|
|
||||||
|
|
||||||
if not rows:
|
|
||||||
return desc or None
|
|
||||||
|
|
||||||
table = f"[MyAnimeList]({url}) stats as of {as_of}:\n" + "\n".join(rows)
|
|
||||||
return f"{desc}\n\n{table}" if desc else table
|
|
||||||
|
|
||||||
def _build_notes(self, md: dict) -> "str | None":
|
|
||||||
"""
|
|
||||||
Builds the <Notes> field containing alternate titles and the
|
|
||||||
MangaBaka metadata source URL.
|
|
||||||
"""
|
|
||||||
parts: list[str] = []
|
parts: list[str] = []
|
||||||
|
|
||||||
|
# 1. MAL stats table (top) ----------------------------------------
|
||||||
|
if mal_stats:
|
||||||
|
url = mal_stats.get("url", "")
|
||||||
|
as_of = mal_stats.get("as_of", "")
|
||||||
|
score = mal_stats.get("score")
|
||||||
|
rank = mal_stats.get("rank")
|
||||||
|
scored = mal_stats.get("scored_by")
|
||||||
|
pop = mal_stats.get("popularity")
|
||||||
|
members = mal_stats.get("members")
|
||||||
|
favs = mal_stats.get("favorites")
|
||||||
|
|
||||||
|
rows: list[str] = []
|
||||||
|
if score is not None: rows.append(f"<tr><td {_TD}>Score</td><td>{score}</td></tr>")
|
||||||
|
if rank is not None: rows.append(f"<tr><td {_TD}>Ranked</td><td>#{rank}</td></tr>")
|
||||||
|
if scored is not None: rows.append(f"<tr><td {_TD}>Scored by</td><td>{scored:,} users</td></tr>")
|
||||||
|
if pop is not None: rows.append(f"<tr><td {_TD}>Popularity</td><td>#{pop}</td></tr>")
|
||||||
|
if members is not None: rows.append(f"<tr><td {_TD}>Members</td><td>{members:,}</td></tr>")
|
||||||
|
if favs is not None: rows.append(f"<tr><td {_TD}>Favorites</td><td>{favs:,}</td></tr>")
|
||||||
|
|
||||||
|
if rows:
|
||||||
|
link = f'<a href="{url}">MyAnimeList</a>' if url else "MyAnimeList"
|
||||||
|
parts.append(f"<p>{link} stats as of {as_of}:</p><table>{''.join(rows)}</table>")
|
||||||
|
|
||||||
|
# 2. Description — Markdown → HTML (middle) -----------------------
|
||||||
|
desc_raw = (md.get("description") or sd.get("Summary") or "").strip()
|
||||||
|
if desc_raw:
|
||||||
|
parts.append(_md_to_html(desc_raw))
|
||||||
|
|
||||||
|
# 3. Alternate titles table (bottom) ------------------------------
|
||||||
alt = self._collect_alt_titles(md)
|
alt = self._collect_alt_titles(md)
|
||||||
if alt:
|
if alt:
|
||||||
label_map = {"en": "EN", "de": "DE",
|
label_map = {"en": "EN", "de": "DE", "romaji": "Romaji", "jp": "JP (Kanji)"}
|
||||||
"romaji": "Romaji", "jp": "JP (kanji)"}
|
alt_rows: list[str] = []
|
||||||
lines = []
|
|
||||||
for code in ("en", "de", "romaji", "jp"):
|
for code in ("en", "de", "romaji", "jp"):
|
||||||
if code in alt:
|
if code in alt:
|
||||||
lines.append(f"• {label_map[code]}: {alt[code]}")
|
alt_rows.append(
|
||||||
if lines:
|
f"<tr><td {_TD}>{label_map[code]}</td><td>{alt[code]}</td></tr>"
|
||||||
parts.append("Alternate titles:\n" + "\n".join(lines))
|
)
|
||||||
|
if alt_rows:
|
||||||
|
parts.append(f"<table>{''.join(alt_rows)}</table>")
|
||||||
|
|
||||||
|
return "<br>".join(parts) if parts else None
|
||||||
|
|
||||||
|
def _build_notes(self, md: dict) -> "str | None":
|
||||||
|
"""Builds the <Notes> field with the MangaBaka metadata source URL."""
|
||||||
series_id = str(md.get("id") or "")
|
series_id = str(md.get("id") or "")
|
||||||
if series_id:
|
return f"Metadata source: https://mangabaka.org/{series_id}" if series_id else None
|
||||||
parts.append(f"Metadata source: https://mangabaka.org/{series_id}")
|
|
||||||
|
|
||||||
return "\n\n".join(parts) if parts else None
|
|
||||||
|
|
||||||
# ======================================================================
|
# ======================================================================
|
||||||
# Static helpers
|
# Static helpers
|
||||||
|
|||||||
Reference in New Issue
Block a user