This repository has been archived by the owner on Mar 6, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
/
mangadex.py
executable file
·213 lines (174 loc) · 7.01 KB
/
mangadex.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
#!/usr/bin/env python3
import re
import unicodedata
from contextlib import contextmanager
from functools import lru_cache
from os import chdir, rename
from pathlib import Path
from shutil import make_archive
import requests
@contextmanager
def workdir(path: Path):
"""Changes working directory and returns to previous on exit."""
prev_cwd = Path.cwd()
chdir(path)
try:
yield
finally:
chdir(prev_cwd)
def _to_zeroed_number(number: int):
try:
return f"{int(number):02d}"
except ValueError:
if len(str(number)) == 3:
return f"0{number}"
return str(number)
def asciify(data: str):
return (
unicodedata.normalize("NFKD", data).encode("ascii", "ignore").decode()
)
class MangaDex:
"""Small CLI utility to help you download Mangas from Mangadex.org
and let's you compress them as a .cbz file once downloaded to read
on eReaders.
All methods have a cbz optional argument.
If True, it'll compress the final result to a cbz file.
By volume for "all" and "volume". By chapter for "chapter".
Additionnal flags:
--lang_code: Default to "gb". The lang code for the language you want
to retrieve your manga in.
--dl_folder: Default to "dl". The path to the folder you want your
downloads to occur in.
--dest_folder: Default to "dl/cbz". The path where to put the
compressed .cbz file if cbz is set to True.
Examples:
Download everything:
mangadex-dl --manga_id=35855 --download_all
Download a volume:
mangadex-dl --manga_id=35855 --download_volume 10
Download a chapter:
mangadex-dl --manga_id=35855 --download_chapter 156.5
"""
def __init__(
self,
manga_id: int,
lang_code: str = "gb",
dl_folder: Path = "dl",
dest_folder: Path = "dl/cbz",
):
self._api_url = "https://mangadex.org/api"
self._default_lang_code = lang_code
self._dl_folder = Path(dl_folder)
self._cbz_path = Path(dest_folder)
self._manga_id = manga_id
self._manga_title = self._get_manga_title()
self._dl_folder.mkdir(parents=True, exist_ok=True)
self._cbz_path.mkdir(parents=True, exist_ok=True)
print(f'Will download "{self._manga_title}"')
@lru_cache(maxsize=1)
def _get_manga_info(self):
params = {"type": "manga", "id": self._manga_id}
return requests.get(self._api_url, params=params).json()
def _get_manga_title(self):
info = self._get_manga_info()
return asciify(info["manga"]["title"])
def _get_chapters_list(self):
full_chapters_list = self._get_manga_info()["chapter"]
return [
{chapter_id: details}
for chapter_id, details in full_chapters_list.items()
if details["lang_code"] == self._default_lang_code
]
def _to_cbz(self, name: str, path: Path):
make_archive(
f"{self._cbz_path}/{name}", "zip", f"{self._dl_folder}/{path}"
)
with workdir(self._cbz_path):
rename(f"{name}.zip", f"{name}.cbz")
print(f"Created the CBZ file: {name}.cbz")
def _download_chapter_id(self, chapter_id: int, cbz: bool = False):
params = {"server": "null", "type": "chapter", "id": chapter_id}
data = requests.get(self._api_url, params=params)
json = data.json()
manga_hash = json["hash"]
server = json["server"]
files = json["page_array"]
volume = _to_zeroed_number(json["volume"]) or "01"
chapter = _to_zeroed_number(json["chapter"])
title = (
asciify(json["title"]) or "No title available for this chapter."
)
print(f"Downloading Volume {volume}, Chapter {chapter}: {title}")
chapter_path = Path(
f"{self._manga_title}/Volume {volume}/Chapter {chapter}"
)
for image in files:
search = re.search(r"(\d+)\.(\w+)", image)
if not search:
print(
f"Oops, unable to process image {image} in chapter {chapter}!"
)
continue
img_num = _to_zeroed_number(search.group(1))
img_ext = search.group(2)
final_name = f"x{img_num}.{img_ext}"
file_url = f"{server}/{manga_hash}/{image}"
image_content = requests.get(file_url)
with workdir(self._dl_folder):
chapter_path.mkdir(parents=True, exist_ok=True)
with open(f"{chapter_path}/{final_name}", "wb") as chapter_img:
chapter_img.write(image_content.content)
if cbz:
if json["title"]:
chapter_name = (
f"{self._manga_title} - Chapter {chapter} - {title}"
)
else:
chapter_name = f"{self._manga_title} - Chapter {chapter}"
self._to_cbz(chapter_name, chapter_path)
def download_all(self, cbz: bool = True):
"""Will download every chapters available of a given Manga ID."""
chapters = [
chapter_id
for chapters_list in self._get_chapters_list()
for chapter_id in chapters_list.keys()
]
for chapter_id in chapters:
self._download_chapter_id(chapter_id)
if not cbz:
return
folders = []
with workdir(self._dl_folder):
for item in Path(self._manga_title).iterdir():
if item.is_dir():
folders.append(Path(item))
for folder in folders:
self._to_cbz(
f"{self._manga_title} - {str(item).split('/')[1]}", folder
)
def download_volume(self, volume: int, cbz: bool = True):
"""Will download all the chapters for the given volume of the Manga ID"""
for chapters in self._get_chapters_list():
for chapter_id, chapter_details in chapters.items():
if int(chapter_details["volume"]) == volume:
self._download_chapter_id(chapter_id)
if cbz:
volume_num = _to_zeroed_number(volume)
volume_path = Path(f"{self._manga_title}/Volume {volume_num}")
self._to_cbz(
f"{self._manga_title} - Volume {volume_num}", volume_path
)
def download_chapter(self, chapter: int, cbz: bool = False):
"""Will download a given chapter for the Manga ID"""
for chapter_item in self._get_chapters_list():
for chapter_id, chapter_details in chapter_item.items():
try:
if int(chapter_details["chapter"]) == chapter:
return self._download_chapter_id(chapter_id, cbz)
except ValueError:
if chapter_details["chapter"] == str(chapter):
return self._download_chapter_id(chapter_id, cbz)
print(f"Unable to find the chapter {chapter}!")
if __name__ == "__main__":
import fire
fire.Fire(MangaDex, name="mangadex-dl")