From 8b0e8c656d1c4bef31cd07d59501a4f0ae326efd Mon Sep 17 00:00:00 2001 From: Duy Nguyen <7498938+nthduy@users.noreply.github.com> Date: Sun, 25 Jan 2026 15:56:52 +0100 Subject: [PATCH] feat(mangafreak): add support for MangaFreak Add chapter and manga extractors for ww2.mangafreak.me with support for bonus chapters (e.g., 167e suffix). --- docs/supportedsites.md | 6 ++ gallery_dl/extractor/__init__.py | 1 + gallery_dl/extractor/mangafreak.py | 116 +++++++++++++++++++++++++++++ scripts/supportedsites.py | 1 + test/results/mangafreak.py | 48 ++++++++++++ 5 files changed, 172 insertions(+) create mode 100644 gallery_dl/extractor/mangafreak.py create mode 100644 test/results/mangafreak.py diff --git a/docs/supportedsites.md b/docs/supportedsites.md index cebbc033..958f14db 100644 --- a/docs/supportedsites.md +++ b/docs/supportedsites.md @@ -667,6 +667,12 @@ Consider all listed sites to potentially be NSFW. Chapters, Manga + + MangaFreak + https://ww2.mangafreak.me/ + Chapters, Manga + + MangaPark https://mangapark.net/ diff --git a/gallery_dl/extractor/__init__.py b/gallery_dl/extractor/__init__.py index b57d7517..465f0e31 100644 --- a/gallery_dl/extractor/__init__.py +++ b/gallery_dl/extractor/__init__.py @@ -127,6 +127,7 @@ modules = [ "mangadex", "mangafire", "mangafox", + "mangafreak", "mangahere", "manganelo", "mangapark", diff --git a/gallery_dl/extractor/mangafreak.py b/gallery_dl/extractor/mangafreak.py new file mode 100644 index 00000000..ac6f0e5c --- /dev/null +++ b/gallery_dl/extractor/mangafreak.py @@ -0,0 +1,116 @@ +# -*- coding: utf-8 -*- + +# Copyright 2025 Mike Fährmann +# +# 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://ww2.mangafreak.me/""" + +from .common import ChapterExtractor, MangaExtractor +from .. import text + +BASE_PATTERN = r"(?:https?://)?(?:ww\d\.)?mangafreak\.me" + + +class MangafreakBase(): + """Base class for mangafreak extractors""" + category = "mangafreak" + root = "https://ww2.mangafreak.me" + + +class MangafreakChapterExtractor(MangafreakBase, ChapterExtractor): + """Extractor for mangafreak manga chapters""" + pattern = BASE_PATTERN + r"(/Read1_(.+)_(\d+[a-z]?))" + example = "https://ww2.mangafreak.me/Read1_Onepunch_Man_1" + + def __init__(self, match): + ChapterExtractor.__init__(self, match, self.root + match.group(1)) + self.manga_slug, self.chapter = match.groups()[1:] + + def metadata(self, page): + extr = text.extract_from(page) + manga = text.unescape(extr("", " Chapter ")) + title = text.unescape(extr("", " - MangaFreak")) + chapter_str = extr("# ", " MANGA ONLINE") + + # Parse chapter number and minor suffix (e.g., "167e" -> chapter=167, minor="e") + chapter, sep, minor = self.chapter.partition("e") if "e" in self.chapter else (self.chapter, "", "") + + return { + "manga" : manga, + "title" : title, + "chapter" : text.parse_int(chapter), + "chapter_minor": sep + minor, + "chapter_string": self.chapter, + "manga_slug" : self.manga_slug, + "lang" : "en", + "language" : "English", + } + + def images(self, page): + # Extract all <img> tags pointing to manga images + return [ + (url, None) + for url in text.extract_iter(page, '<img src="https://images.mangafreak.me/mangas/', '"') + ] + + +class MangafreakMangaExtractor(MangafreakBase, MangaExtractor): + """Extractor for mangafreak manga series""" + chapterclass = MangafreakChapterExtractor + pattern = BASE_PATTERN + r"(/Manga/([^/?#]+))" + example = "https://ww2.mangafreak.me/Manga/Onepunch_Man" + + def __init__(self, match): + MangaExtractor.__init__(self, match, self.root + match.group(1)) + self.manga_slug = match.group(2) + + def chapters(self, page): + extr = text.extract_from(page) + manga = text.unescape(extr("<title>", " Manga")) + + # Extract chapter list from table + chapter_list = text.extr(page, "<tbody>", "</tbody>") + if not chapter_list: + return [] + + data = { + "manga" : manga, + "manga_slug" : self.manga_slug, + "lang" : "en", + "language" : "English", + } + + results = [] + for row in text.extract_iter(chapter_list, "<tr>", "</tr>"): + # Extract chapter link and date from each row + chapter_link = text.extr(row, '<a href="', '"') + if not chapter_link: + continue + + # Build full URL if relative + if chapter_link.startswith("/"): + url = self.root + chapter_link + else: + url = self.root + "/" + chapter_link + + # Parse chapter info from URL like /Read1_Onepunch_Man_167e + chapter_part = url.rsplit("/", 1)[-1] # Read1_Onepunch_Man_167e + if chapter_part.startswith("Read1_"): + parts = chapter_part.split("_") + if len(parts) >= 3: + chapter_str = parts[-1] + # Parse chapter number and minor suffix + chapter, sep, minor = chapter_str.partition("e") if "e" in chapter_str else (chapter_str, "", "") + + chapter_data = { + "chapter" : text.parse_int(chapter), + "chapter_minor": sep + minor, + "chapter_string": chapter_str, + **data, + } + results.append((url, chapter_data)) + + return results diff --git a/scripts/supportedsites.py b/scripts/supportedsites.py index 0f950c06..797540a8 100755 --- a/scripts/supportedsites.py +++ b/scripts/supportedsites.py @@ -115,6 +115,7 @@ CATEGORY_MAP = { "kabeuchi" : "かべうち", "kaliscan" : "KaliScan", "mangafire" : "MangaFire", + "mangafreak" : "MangaFreak", "mangareader" : "MangaReader", "mangataro" : "MangaTaro", "s3ndpics" : "S3ND", diff --git a/test/results/mangafreak.py b/test/results/mangafreak.py new file mode 100644 index 00000000..3466bb0f --- /dev/null +++ b/test/results/mangafreak.py @@ -0,0 +1,48 @@ +# -*- 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. + +from gallery_dl.extractor import mangafreak + + +__tests__ = ( +{ + "#url" : "https://ww2.mangafreak.me/Read1_Onepunch_Man_1", + "#class" : mangafreak.MangafreakChapterExtractor, + "#pattern" : r"https://images\.mangafreak\.me/mangas/onepunch_man/onepunch_man_1/onepunch_man_1_\d+\.jpg", + "#count" : 24, + + "chapter" : 1, + "chapter_minor": "", + "chapter_string": "1", + "lang" : "en", + "language" : "English", + "manga" : "Onepunch Man", + "manga_slug" : "Onepunch_Man", +}, + +{ + "#url" : "https://ww2.mangafreak.me/Read1_Onepunch_Man_167e", + "#class" : mangafreak.MangafreakChapterExtractor, + + "chapter" : 167, + "chapter_minor": "e", + "chapter_string": "167e", +}, + +{ + "#url" : "https://ww2.mangafreak.me/Manga/Onepunch_Man", + "#class" : mangafreak.MangafreakMangaExtractor, + "#pattern" : mangafreak.MangafreakChapterExtractor.pattern, + "#count" : range(150, 250), + + "lang" : "en", + "language" : "English", + "manga" : "Onepunch-Man", + "manga_slug" : "Onepunch_Man", + "chapter" : int, +}, + +)