diff --git a/docs/supportedsites.md b/docs/supportedsites.md index bca3e4240d..77688da4a5 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -433,12 +433,6 @@ Consider all sites to be NSFW unless otherwise known. Soundtracks - - Kohlchan - https://kohlchan.net/ - Boards, Threads - - Komikcast https://komikcast.site/ @@ -1046,6 +1040,22 @@ Consider all sites to be NSFW unless otherwise known. + + LynxChan Imageboards + + + Kohlchan + https://kohlchan.net/ + Boards, Threads + + + + Endchan + https://endchan.org/ + Boards, Threads + + + Nijie Instances diff --git a/gallery_dl/extractor/__init__.py b/gallery_dl/extractor/__init__.py index 98c43ca988..4d1706874d 100644 --- a/gallery_dl/extractor/__init__.py +++ b/gallery_dl/extractor/__init__.py @@ -74,12 +74,12 @@ "keenspot", "kemonoparty", "khinsider", - "kohlchan", "komikcast", "lightroom", "lineblog", "livedoor", "luscious", + "lynxchan", "mangadex", "mangafox", "mangahere", diff --git a/gallery_dl/extractor/kohlchan.py b/gallery_dl/extractor/kohlchan.py deleted file mode 100644 index c96dedcffc..0000000000 --- a/gallery_dl/extractor/kohlchan.py +++ /dev/null @@ -1,78 +0,0 @@ -# -*- coding: utf-8 -*- - -# This program is free software; you can redistribute it and/or modify -# it under the terms of the GNU General Public License version 2 as -# published by the Free Software Foundation. - -"""Extractors for https://kohlchan.net/""" - -from .common import Extractor, Message -from .. import text -import itertools - - -class KohlchanThreadExtractor(Extractor): - """Extractor for Kohlchan threads""" - category = "kohlchan" - subcategory = "thread" - directory_fmt = ("{category}", "{boardUri}", - "{threadId} {subject|message[:50]}") - filename_fmt = "{postId}{num:?-//} {filename}.{extension}" - archive_fmt = "{boardUri}_{postId}_{num}" - pattern = r"(?:https?://)?kohlchan\.net/([^/?#]+)/res/(\d+)" - test = ("https://kohlchan.net/a/res/4594.html", { - "pattern": r"https://kohlchan\.net/\.media/[0-9a-f]{64}(\.\w+)?$", - "count": ">= 80", - }) - - def __init__(self, match): - Extractor.__init__(self, match) - self.board, self.thread = match.groups() - - def items(self): - url = "https://kohlchan.net/{}/res/{}.json".format( - self.board, self.thread) - thread = self.request(url).json() - thread["postId"] = thread["threadId"] - posts = thread.pop("posts") - - yield Message.Directory, thread - - for post in itertools.chain((thread,), posts): - files = post.pop("files", ()) - if files: - thread.update(post) - for num, file in enumerate(files): - file.update(thread) - file["num"] = num - url = "https://kohlchan.net" + file["path"] - text.nameext_from_url(file["originalName"], file) - yield Message.Url, url, file - - -class KohlchanBoardExtractor(Extractor): - """Extractor for Kohlchan boards""" - category = "kohlchan" - subcategory = "board" - pattern = (r"(?:https?://)?kohlchan\.net" - r"/([^/?#]+)/(?:(?:catalog|\d+)\.html)?$") - test = ( - ("https://kohlchan.net/a/", { - "pattern": KohlchanThreadExtractor.pattern, - "count": ">= 100", - }), - ("https://kohlchan.net/a/2.html"), - ("https://kohlchan.net/a/catalog.html"), - ) - - def __init__(self, match): - Extractor.__init__(self, match) - self.board = match.group(1) - - def items(self): - url = "https://kohlchan.net/{}/catalog.json".format(self.board) - for thread in self.request(url).json(): - url = "https://kohlchan.net/{}/res/{}.html".format( - self.board, thread["threadId"]) - thread["_extractor"] = KohlchanThreadExtractor - yield Message.Queue, url, thread diff --git a/gallery_dl/extractor/lynxchan.py b/gallery_dl/extractor/lynxchan.py new file mode 100644 index 0000000000..bbcf9c0f4d --- /dev/null +++ b/gallery_dl/extractor/lynxchan.py @@ -0,0 +1,104 @@ +# -*- coding: utf-8 -*- + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License version 2 as +# published by the Free Software Foundation. + +"""Extractors for LynxChan Imageboards""" + +from .common import BaseExtractor, Message +from .. import text +import itertools + + +class LynxchanExtractor(BaseExtractor): + """Base class for LynxChan extractors""" + basecategory = "lynxchan" + + +BASE_PATTERN = LynxchanExtractor.update({ + "kohlchan": { + "root": "https://kohlchan.net", + "pattern": r"kohlchan\.net" + }, + "endchan": { + "root": None, + "pattern": r"endchan\.(?:org|net|gg)", + }, +}) + + +class LynxchanThreadExtractor(LynxchanExtractor): + """Extractor for LynxChan threads""" + subcategory = "thread" + directory_fmt = ("{category}", "{boardUri}", + "{threadId} {subject|message[:50]}") + filename_fmt = "{postId}{num:?-//} {filename}.{extension}" + archive_fmt = "{boardUri}_{postId}_{num}" + pattern = BASE_PATTERN + r"/([^/?#]+)/res/(\d+)" + test = ( + ("https://kohlchan.net/a/res/4594.html", { + "pattern": r"https://kohlchan\.net/\.media/[0-9a-f]{64}(\.\w+)?$", + "count": ">= 80", + }), + ("https://endchan.org/yuri/res/193483.html", { + "pattern": r"https://endchan\.org/\.media/[^.]+(\.\w+)?$", + "count" : ">= 19", + }), + ("https://endchan.org/yuri/res/33621.html"), + ) + + def __init__(self, match): + LynxchanExtractor.__init__(self, match) + index = match.lastindex + self.board = match.group(index-1) + self.thread = match.group(index) + + def items(self): + url = "{}/{}/res/{}.json".format(self.root, self.board, self.thread) + thread = self.request(url).json() + thread["postId"] = thread["threadId"] + posts = thread.pop("posts", ()) + + yield Message.Directory, thread + for post in itertools.chain((thread,), posts): + files = post.pop("files", ()) + if files: + thread.update(post) + for num, file in enumerate(files): + file.update(thread) + file["num"] = num + url = self.root + file["path"] + text.nameext_from_url(file["originalName"], file) + yield Message.Url, url, file + + +class LynxchanBoardExtractor(LynxchanExtractor): + """Extractor for LynxChan boards""" + subcategory = "board" + pattern = BASE_PATTERN + r"/([^/?#]+)(?:/index|/catalog|/\d+|/?$)" + test = ( + ("https://kohlchan.net/a/", { + "pattern": LynxchanThreadExtractor.pattern, + "count": ">= 100", + }), + ("https://kohlchan.net/a/2.html"), + ("https://kohlchan.net/a/catalog.html"), + ("https://endchan.org/yuri/", { + "pattern": LynxchanThreadExtractor.pattern, + "count" : ">= 9", + }), + ("https://endchan.org/yuri/catalog.html"), + ) + + def __init__(self, match): + LynxchanExtractor.__init__(self, match) + self.board = match.group(match.lastindex) + + def items(self): + url = "{}/{}/catalog.json".format(self.root, self.board) + for thread in self.request(url).json(): + url = "{}/{}/res/{}.html".format( + self.root, self.board, thread["threadId"]) + thread["_extractor"] = LynxchanThreadExtractor + yield Message.Queue, url, thread diff --git a/scripts/supportedsites.py b/scripts/supportedsites.py index 8cff63c37d..71e31f7148 100755 --- a/scripts/supportedsites.py +++ b/scripts/supportedsites.py @@ -251,6 +251,7 @@ "gelbooru_v01": "Gelbooru Beta 0.1.11", "gelbooru_v02": "Gelbooru Beta 0.2", "lolisafe" : "lolisafe and chibisafe", + "lynxchan" : "LynxChan Imageboards", "moebooru" : "Moebooru and MyImouto", "vichan" : "vichan Imageboards", }