Skip to content

Commit a5743a9

Browse files
authored
Fix Pylint errors and refactor code
1 parent 0d536a8 commit a5743a9

6 files changed

Lines changed: 173 additions & 105 deletions

File tree

manga_downloader.py

Lines changed: 73 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
"""A manga downloader and PDF generator for MangaWorld.
22
33
This module allows you to download manga chapters from a given manga URL, process each
4-
chapter, and generate PDF files for the downloaded images. It utilizes `requests` for
5-
HTTP requests, `BeautifulSoup` for HTML parsing, and `rich` for displaying a progress
6-
bar during the download and conversion process.
4+
chapter, and generate PDF files for the downloaded images.
75
"""
86

97
from __future__ import annotations
108

119
import asyncio
1210
from pathlib import Path
1311
from typing import TYPE_CHECKING
14-
import aiohttp
1512

13+
import aiohttp
1614
from rich.live import Live
1715

1816
from src.config import DOWNLOAD_FOLDER, parse_arguments
@@ -21,24 +19,63 @@
2119
extract_download_links,
2220
extract_manga_type,
2321
extract_volume_info,
22+
fetch_chapter_data,
2423
)
2524
from src.download_utils import download_chapter, run_in_parallel
26-
from src.crawler_utils import fetch_chapter_data
2725
from src.format_utils import extract_manga_info
2826
from src.general_utils import clear_terminal, fetch_page, validate_chapter_range
2927
from src.pdf_generator import generate_pdf_files
30-
from src.progress_utils import create_progress_bar, create_progress_table, create_select_items_list
28+
from src.progress_utils import (
29+
create_progress_bar,
30+
create_progress_table,
31+
create_select_items_list,
32+
)
3133

3234
if TYPE_CHECKING:
3335
from rich.progress import Progress
3436

3537

36-
def process_pdf_generation(manga_name: str, job_progress: Progress, single_pdf=False) -> None:
38+
def process_pdf_generation(
39+
manga_name: str, job_progress: Progress, *, single_pdf: bool = False,
40+
) -> None:
3741
"""Process the generation of PDF files for a specific manga."""
3842
manga_parent_folder = Path(DOWNLOAD_FOLDER) / manga_name
3943
generate_pdf_files(str(manga_parent_folder), job_progress, single_pdf=single_pdf)
4044

4145

46+
def download_chapter_with_progress(
47+
manga_name: str,
48+
download_links: list[str],
49+
pages_per_chapter: list[int],
50+
*,
51+
generate_pdf: bool = False,
52+
volume_name: str | None = None,
53+
) -> None:
54+
"""Download the chapters of a manga and displays a progress bar.
55+
56+
Optionally generate a PDF of the manga chapters if requested.
57+
"""
58+
task_description = (
59+
manga_name if volume_name is None else f"{manga_name} - {volume_name}"
60+
)
61+
working_path = manga_name if volume_name is None else f"{manga_name}/{volume_name}"
62+
63+
job_progress = create_progress_bar()
64+
progress_table = create_progress_table(task_description, job_progress)
65+
66+
with Live(progress_table, refresh_per_second=10):
67+
run_in_parallel(
68+
download_chapter,
69+
download_links,
70+
job_progress,
71+
pages_per_chapter,
72+
working_path,
73+
)
74+
if generate_pdf:
75+
single_pdf = volume_name is not None
76+
process_pdf_generation(working_path, job_progress, single_pdf=single_pdf)
77+
78+
4279
async def process_manga_download(
4380
url: str,
4481
start_chapter: int | None = None,
@@ -54,37 +91,35 @@ async def process_manga_download(
5491

5592
if volume_mode:
5693
volumes = extract_volume_info(soup)
57-
volume_names = [v['name'] for v in volumes]
58-
selected_indices = create_select_items_list(volume_names)
59-
for idx in selected_indices:
60-
volume = volumes[idx]
61-
chapter_urls = [c['url'] for c in volume['chapters']]
94+
volume_names = [volume["name"] for volume in volumes]
95+
selected_indexes = create_select_items_list(volume_names)
96+
97+
for indx in selected_indexes:
98+
volume = volumes[indx]
99+
chapter_urls = [chapter["url"] for chapter in volume["chapters"]]
62100
pages_per_chapter = []
101+
63102
async with aiohttp.ClientSession() as session:
64103
tasks = [fetch_chapter_data(url, session) for url in chapter_urls]
65104
results = await asyncio.gather(*tasks)
66-
for result in results:
67-
pages_per_chapter.append(result[1] if result and result[1] else None)
105+
pages_per_chapter = [
106+
result[1] if result and result[1] else None for result in results
107+
]
68108

69109
download_links = await extract_download_links(
70110
chapter_urls,
71111
0,
72112
len(chapter_urls),
73113
manga_type,
74114
)
75-
job_progress = create_progress_bar()
76-
progress_table = create_progress_table(f"{manga_name} - {volume['name']}", job_progress)
77-
78-
with Live(progress_table, refresh_per_second=10):
79-
run_in_parallel(
80-
download_chapter,
81-
download_links,
82-
job_progress,
83-
pages_per_chapter,
84-
f"{manga_name}/{volume['name']}"
85-
)
86-
if generate_pdf:
87-
process_pdf_generation(f"{manga_name}/{volume['name']}", job_progress, single_pdf=True)
115+
download_chapter_with_progress(
116+
manga_name,
117+
download_links,
118+
pages_per_chapter,
119+
generate_pdf=generate_pdf,
120+
volume_name=volume["name"],
121+
)
122+
88123
else:
89124
chapter_urls, pages_per_chapter = await extract_chapters_info(soup)
90125
start_index, end_index = validate_chapter_range(
@@ -98,28 +133,24 @@ async def process_manga_download(
98133
end_index,
99134
manga_type,
100135
)
101-
102-
job_progress = create_progress_bar()
103-
progress_table = create_progress_table(manga_name, job_progress)
104-
105-
with Live(progress_table, refresh_per_second=10):
106-
run_in_parallel(
107-
download_chapter,
108-
download_links,
109-
job_progress,
110-
pages_per_chapter[start_index:end_index],
111-
manga_name,
112-
)
113-
if generate_pdf:
114-
process_pdf_generation(manga_name, job_progress)
136+
download_chapter_with_progress(
137+
manga_name,
138+
download_links,
139+
pages_per_chapter[start_index:end_index],
140+
generate_pdf=generate_pdf,
141+
)
115142

116143

117144
async def main() -> None:
118145
"""Initiate the manga download process from a given URL."""
119146
clear_terminal()
120147
args = parse_arguments()
121148
await process_manga_download(
122-
args.url, start_chapter=args.start, end_chapter=args.end, generate_pdf=args.pdf, volume_mode=args.volume
149+
args.url,
150+
start_chapter=args.start,
151+
end_chapter=args.end,
152+
generate_pdf=args.pdf,
153+
volume_mode=args.volume,
123154
)
124155

125156

src/config.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
# ============================
2222
FIRST_PAGE_SUFFIX_REGEX = r"1\.(png|gif|jpg)$"
2323
MANGA_TYPE_REGEX = r'"typeT":\s*"([^"]*)"'
24+
COOKIE_REGEX = r'document\.cookie="([^;]+)'
25+
LINK_REGEX = r'location\.href="([^"]+)"'
2426

2527
# ============================
2628
# Download Settings
@@ -39,10 +41,14 @@
3941
# ============================
4042
# Image Download Settings
4143
# ============================
42-
PAGE_EXTENSIONS = [".jpg", ".png", ".gif", ".webp"] # List of supported image
43-
# extensions for download.
44-
ImageFile.LOAD_TRUNCATED_IMAGES = True # Allow loading of truncated
45-
# images.
44+
# List of supported image extensions for download.
45+
PAGE_EXTENSIONS = [".jpg", ".png", ".gif", ".webp"]
46+
47+
# List of supported image extensions for PDF generation.
48+
IMAGE_FORMATS_FOR_PDF = {".jpg", ".jpeg", ".png", ".webp", ".gif"}
49+
50+
# Allow loading of truncated images.
51+
ImageFile.LOAD_TRUNCATED_IMAGES = True
4652

4753
# ============================
4854
# HTTP / Network

src/crawler_utils.py

Lines changed: 29 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -194,40 +194,54 @@ def extract_manga_type(soup: BeautifulSoup, manga_slug: str) -> str | None:
194194

195195
return None
196196

197-
def extract_volume_info(soup):
198-
"""
199-
Extracts the volume list and relative list of chapter URLs.
197+
198+
def extract_volume_info(soup: BeautifulSoup) -> list[dict]:
199+
"""Extract the volume list and relative list of chapter URLs.
200+
200201
If a page doesn't contain volumes, it'll retrieve a volume with all chapters.
201202
Output:
202203
[
203204
{"name": "Volume 1", "chapters": [{"title": ..., "url": ...}, ...]},
204205
...
205206
]
206207
"""
207-
"""Fetch the download link for the first image in a chapter page."""
208208
volumes = []
209-
volume_elements = soup.find_all("div", class_="volume-element")
209+
volume_elements = soup.find_all("div", {"class": "volume-element"})
210+
210211
if volume_elements:
211212
for vol in volume_elements:
212213
# Volume name
213-
name_tag = vol.find("p", class_="volume-name")
214+
name_tag = vol.find("p", {"class": "volume-name"})
214215
volume_name = name_tag.get_text(strip=True) if name_tag else "Volume"
216+
215217
# Volume chapters
216218
chapters = []
217-
chapters_container = vol.find("div", class_="volume-chapters")
219+
chapters_container = vol.find("div", {"class": "volume-chapters"})
220+
218221
if chapters_container:
219-
chapter_divs = chapters_container.find_all("div", class_="chapter")
222+
chapter_divs = chapters_container.find_all("div", {"class": "chapter"})
223+
220224
for chap in chapter_divs:
221-
a_tag = chap.find("a", class_="chap", title=True)
225+
a_tag = chap.find("a", {"class": "chap"}, title=True)
222226
if a_tag:
223-
chapters.append({
224-
"title": a_tag["title"],
225-
"url": a_tag["href"]
226-
})
227+
chapters.append(
228+
{
229+
"title": a_tag["title"],
230+
"url": a_tag["href"],
231+
},
232+
)
233+
227234
if chapters:
228-
volumes.append({"name": volume_name, "chapters": sorted(chapters, key=lambda c: c['title'])})
235+
volumes.append(
236+
{
237+
"name": volume_name,
238+
"chapters": sorted(chapters, key=lambda chap: chap["title"]),
239+
},
240+
)
241+
229242
else:
230243
# No available volumes
231244
logging.error("The selected link doesn't have available volumes.")
232245
return None
233-
return sorted(volumes, key=lambda v: v['name'])
246+
247+
return sorted(volumes, key=lambda vol: vol["name"])

src/general_utils.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
from aiohttp import ClientSession
1616
from bs4 import BeautifulSoup
1717

18+
from .config import COOKIE_REGEX, LINK_REGEX
19+
1820

1921
async def check_real_page(
2022
initial_response: BeautifulSoup,
@@ -29,8 +31,7 @@ async def check_real_page(
2931
and "document.cookie" in initial_response.body.script.text
3032
):
3133
# Extract the cookie
32-
cookie_regex = r'document\.cookie="([^;]+)'
33-
match = re.search(cookie_regex, initial_response.body.script.text)
34+
match = re.search(COOKIE_REGEX, initial_response.body.script.text)
3435

3536
if match:
3637
cookie = match.group(1)
@@ -39,8 +40,7 @@ async def check_real_page(
3940
return initial_response
4041

4142
# Extract the link
42-
link_regex = r'location\.href="([^"]+)"'
43-
match = re.search(link_regex, initial_response.body.script.text)
43+
match = re.search(LINK_REGEX, initial_response.body.script.text)
4444

4545
if match:
4646
link = match.group(1) # Extracted link

0 commit comments

Comments
 (0)