From 7907d0d3bd256dcd5f4b2774295a81149a239215 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mike=20F=C3=A4hrmann?= Date: Mon, 12 May 2025 12:58:22 +0200 Subject: [PATCH] [mangadex] add 'following' extractor (#7487) also fixes the URL pattern for the Updates feed at https://mangadex.org/titles/feed --- docs/supportedsites.md | 2 +- gallery_dl/extractor/mangadex.py | 55 ++++++++++++++++++++++---------- scripts/supportedsites.py | 4 ++- test/results/mangadex.py | 28 ++++++++++++++-- 4 files changed, 68 insertions(+), 21 deletions(-) diff --git a/docs/supportedsites.md b/docs/supportedsites.md index 54020c81..91eabef6 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -562,7 +562,7 @@ Consider all listed sites to potentially be NSFW. MangaDex https://mangadex.org/ - Authors, Chapters, Followed Feed, Lists, Manga + Authors, Chapters, Updates Feed, Library, MDLists, Manga Supported diff --git a/gallery_dl/extractor/mangadex.py b/gallery_dl/extractor/mangadex.py index e99d19a4..02b21e5c 100644 --- a/gallery_dl/extractor/mangadex.py +++ b/gallery_dl/extractor/mangadex.py @@ -41,6 +41,12 @@ class MangadexExtractor(Extractor): self._cache[uuid] = data yield Message.Queue, self.root + "/chapter/" + uuid, data + def _items_manga(self): + data = {"_extractor": MangadexMangaExtractor} + for manga in self.manga(): + url = "{}/title/{}".format(self.root, manga["id"]) + yield Message.Queue, url, data + def _transform(self, chapter): relationships = defaultdict(list) for item in chapter["relationships"]: @@ -127,7 +133,7 @@ class MangadexChapterExtractor(MangadexExtractor): class MangadexMangaExtractor(MangadexExtractor): """Extractor for manga from mangadex.org""" subcategory = "manga" - pattern = BASE_PATTERN + r"/(?:title|manga)/(?!feed$)([0-9a-f-]+)" + pattern = BASE_PATTERN + r"/(?:title|manga)/(?!follows|feed$)([0-9a-f-]+)" example = ("https://mangadex.org/title" "/01234567-89ab-cdef-0123-456789abcdef") @@ -136,17 +142,29 @@ class MangadexMangaExtractor(MangadexExtractor): class MangadexFeedExtractor(MangadexExtractor): - """Extractor for chapters from your Followed Feed""" + """Extractor for chapters from your Updates Feed""" subcategory = "feed" - pattern = BASE_PATTERN + r"/title/feed$()" + pattern = BASE_PATTERN + r"/titles?/feed$()" example = "https://mangadex.org/title/feed" def chapters(self): return self.api.user_follows_manga_feed() +class MangadexFollowingExtractor(MangadexExtractor): + """Extractor for followed manga from your Library""" + subcategory = "following" + pattern = BASE_PATTERN + r"/titles?/follows(?:\?([^#]+))?$" + example = "https://mangadex.org/title/follows" + + items = MangadexExtractor._items_manga + + def manga(self): + return self.api.user_follows_manga() + + class MangadexListExtractor(MangadexExtractor): - """Extractor for mangadex lists""" + """Extractor for mangadex MDLists""" subcategory = "list" pattern = (BASE_PATTERN + r"/list/([0-9a-f-]+)(?:/[^/?#]*)?(?:\?tab=(\w+))?") @@ -158,17 +176,17 @@ class MangadexListExtractor(MangadexExtractor): if match.group(2) == "feed": self.subcategory = "list-feed" else: - self.items = self._items_titles + self.items = self._items_manga def chapters(self): return self.api.list_feed(self.uuid) - def _items_titles(self): - data = {"_extractor": MangadexMangaExtractor} - for item in self.api.list(self.uuid)["relationships"]: - if item["type"] == "manga": - url = "{}/title/{}".format(self.root, item["id"]) - yield Message.Queue, url, data + def manga(self): + return [ + item + for item in self.api.list(self.uuid)["relationships"] + if item["type"] == "manga" + ] class MangadexAuthorExtractor(MangadexExtractor): @@ -244,6 +262,10 @@ class MangadexAPI(): } return self._pagination_chapters("/manga/" + uuid + "/feed", params) + def user_follows_manga(self): + params = {"contentRating": None} + return self._pagination_manga("/user/follows/manga", params) + def user_follows_manga_feed(self): params = {"order[publishAt]": "desc"} return self._pagination_chapters("/user/follows/manga/feed", params) @@ -327,7 +349,7 @@ class MangadexAPI(): self.extractor.wait(until=until) continue - msg = ", ".join('{title}: {detail}'.format_map(error) + msg = ", ".join('{title}: "{detail}"'.format_map(error) for error in response.json()["errors"]) raise exception.StopExtraction( "%s %s (%s)", response.status_code, response.reason, msg) @@ -353,10 +375,11 @@ class MangadexAPI(): def _pagination(self, endpoint, params): config = self.extractor.config - ratings = config("ratings") - if ratings is None: - ratings = ("safe", "suggestive", "erotica", "pornographic") - params["contentRating[]"] = ratings + if "contentRating" not in params: + ratings = config("ratings") + if ratings is None: + ratings = ("safe", "suggestive", "erotica", "pornographic") + params["contentRating[]"] = ratings params["offset"] = 0 api_params = config("api-parameters") diff --git a/scripts/supportedsites.py b/scripts/supportedsites.py index 7fbf3ee5..a7ee6592 100755 --- a/scripts/supportedsites.py +++ b/scripts/supportedsites.py @@ -295,7 +295,9 @@ SUBCATEGORY_MAP = { "blog-posts": "Blog Posts", }, "mangadex": { - "feed" : "Followed Feed", + "feed": "Updates Feed", + "following" : "Library", + "list": "MDLists", }, "nijie": { "followed": "Followed Users", diff --git a/test/results/mangadex.py b/test/results/mangadex.py index 6f30c8d0..032d98c7 100644 --- a/test/results/mangadex.py +++ b/test/results/mangadex.py @@ -108,9 +108,31 @@ __tests__ = ( }, { - "#url" : "https://mangadex.org/title/feed", - "#category": ("", "mangadex", "feed"), + "#url" : "https://mangadex.org/titles/feed", "#class" : mangadex.MangadexFeedExtractor, + "#auth" : True, +}, + +{ + "#url" : "https://mangadex.org/title/feed", + "#class" : mangadex.MangadexFeedExtractor, + "#auth" : True, +}, + +{ + "#url" : "https://mangadex.org/titles/follows", + "#class" : mangadex.MangadexFollowingExtractor, + "#auth" : True, + "#urls" : ( + "https://mangadex.org/title/cad76ec6-ca22-42f6-96f8-eca164da6545", + "https://mangadex.org/title/7546ff2d-2310-47a4-b1f3-1a2561f20ce7", + ), +}, + +{ + "#url" : "https://mangadex.org/title/follows", + "#class" : mangadex.MangadexFollowingExtractor, + "#auth" : True, }, { @@ -134,8 +156,8 @@ __tests__ = ( "#category": ("", "mangadex", "list-feed"), "#class" : mangadex.MangadexListExtractor, "#urls" : ( - "https://mangadex.org/chapter/fa8a695d-260f-4dcc-95a3-1f30e66d6571", "https://mangadex.org/chapter/c765d6d5-5712-4360-be0b-0c8e0914fc94", + "https://mangadex.org/chapter/fa8a695d-260f-4dcc-95a3-1f30e66d6571", "https://mangadex.org/chapter/788766b9-41c6-422e-97ba-552f03ba9655", ), },