diff --git a/src/ComicInfoBuilder.py b/src/ComicInfoBuilder.py index 9f03eb4..a87d237 100644 --- a/src/ComicInfoBuilder.py +++ b/src/ComicInfoBuilder.py @@ -789,7 +789,7 @@ class ComicInfoBuilder: if favs is not None: rows.append(f"Favorites{favs:,}") if rows: - link = f'MyAnimeList' if url else "MyAnimeList" + link = f'MyAnimeList' if url else "MyAnimeList" parts.append(f"

{link} stats as of {as_of}:

{''.join(rows)}
") # 2. Description — Markdown → HTML (middle) ----------------------- diff --git a/src/KavitaPersonUpdater.py b/src/KavitaPersonUpdater.py index cb5c8ef..0bd93cf 100644 --- a/src/KavitaPersonUpdater.py +++ b/src/KavitaPersonUpdater.py @@ -432,7 +432,7 @@ def _build_character_description(details: dict) -> str: url = details.get("url") or "" favorites = details.get("favorites") if url and favorites is not None: - parts.append(f'

Favorites: {favorites:,}

') + parts.append(f'

Favorites: {favorites:,}

') about = (details.get("about") or "").strip() if about: parts.append(_plain_to_html(about)) @@ -469,7 +469,7 @@ def _build_person_description(details: dict) -> str: f'{website}' ) if favorites is not None: - fav_cell = (f'{favorites:,}' if url + fav_cell = (f'{favorites:,}' if url else f"{favorites:,}") rows.append( f"Member Favorites{fav_cell}") diff --git a/src/SuwayomiMover.py b/src/SuwayomiMover.py index ab29468..644b86f 100644 --- a/src/SuwayomiMover.py +++ b/src/SuwayomiMover.py @@ -69,12 +69,30 @@ _SOURCE_LABEL_RE = re.compile( re.IGNORECASE, ) +# Characters that Windows (and SMB shares) forbid in path components. +_WIN_ILLEGAL_RE = re.compile(r'[\\/*?"<>|]') + def _natural_key(name: str) -> list: return [int(p) if p.isdigit() else p.lower() for p in re.split(r"(\d+)", name)] +def _sanitize_dirname(name: str) -> str: + """ + Makes a string safe to use as a Windows (or SMB) directory name. + + Rules applied: + - ": " or ":" surrounded by optional spaces -> " - " + ("Call of the Night: Paradise Arc" -> "Call of the Night - Paradise Arc") + - Remaining Windows-illegal chars (\\ / * ? " < > |) are stripped. + - Leading/trailing dots and spaces are removed (Windows restriction). + """ + name = re.sub(r"\s*:\s*", " - ", name) + name = _WIN_ILLEGAL_RE.sub("", name) + return name.strip(". ") + + _SUWAYOMI_WANTED = {"Title", "Series", "Number", "Summary", "Writer", "Penciller", "Genre", "Web", "Year", "Month", "Day"} @@ -326,8 +344,9 @@ class SuwayomiMover: except Exception as exc: print(f" [warn] metadata fetch failed: {exc}") - # Destination folder uses the MangaBaka canonical title. - dest_series = self._dst / mangabaka_title + # Destination folder uses the MangaBaka canonical title, sanitized for + # Windows / SMB paths (no colons, illegal chars, leading/trailing dots). + dest_series = self._dst / _sanitize_dirname(mangabaka_title) dest_series.mkdir(parents=True, exist_ok=True) chapter_results: list[dict] = [] @@ -403,7 +422,7 @@ if __name__ == "__main__": ) # Process a single series - result = mover.process_series("Yofukashi no Uta") + result = mover.process_series("Yofukashi no Uta_ Rakuen-hen") ok = sum(1 for c in result["chapters"] if c["ok"]) failed = sum(1 for c in result["chapters"] if not c["ok"]) print(f"\nDone: {ok} ok, {failed} failed")