Signed-off-by: Ivan Schaller <ivan@schaller.sh>
This commit is contained in:
parent
ef7a914869
commit
03461b80bf
13 changed files with 104 additions and 66 deletions
|
@ -33,6 +33,13 @@ pipeline:
|
||||||
commands:
|
commands:
|
||||||
- just test_mypy
|
- just test_mypy
|
||||||
|
|
||||||
|
# check static typing - python
|
||||||
|
test-pyright:
|
||||||
|
image: cr.44net.ch/ci-plugins/tests
|
||||||
|
pull: true
|
||||||
|
commands:
|
||||||
|
- just test_pyright
|
||||||
|
|
||||||
# ruff test - python
|
# ruff test - python
|
||||||
test-ruff:
|
test-ruff:
|
||||||
image: cr.44net.ch/ci-plugins/tests
|
image: cr.44net.ch/ci-plugins/tests
|
||||||
|
|
|
@ -17,3 +17,4 @@ black>=22.1.0
|
||||||
mypy>=0.940
|
mypy>=0.940
|
||||||
tox>=3.24.5
|
tox>=3.24.5
|
||||||
ruff>=0.0.247
|
ruff>=0.0.247
|
||||||
|
pyright>=1.1.294
|
||||||
|
|
8
justfile
8
justfile
|
@ -82,7 +82,10 @@ test_black:
|
||||||
@python3 -m black --check --diff .
|
@python3 -m black --check --diff .
|
||||||
|
|
||||||
test_mypy:
|
test_mypy:
|
||||||
@python3 -m mypy --install-types --non-interactive --ignore-missing-imports .
|
@python3 -m mypy --install-types --non-interactive --ignore-missing-imports mangadlp/
|
||||||
|
|
||||||
|
test_pyright:
|
||||||
|
@python3 -m pyright mangadlp/
|
||||||
|
|
||||||
test_ruff:
|
test_ruff:
|
||||||
@python3 -m ruff --diff .
|
@python3 -m ruff --diff .
|
||||||
|
@ -118,6 +121,7 @@ lint:
|
||||||
just test_shfmt
|
just test_shfmt
|
||||||
just test_black
|
just test_black
|
||||||
just test_mypy
|
just test_mypy
|
||||||
|
just test_pyright
|
||||||
just test_ruff
|
just test_ruff
|
||||||
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"
|
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"
|
||||||
|
|
||||||
|
@ -127,6 +131,7 @@ tests:
|
||||||
just test_shfmt
|
just test_shfmt
|
||||||
just test_black
|
just test_black
|
||||||
just test_mypy
|
just test_mypy
|
||||||
|
just test_pyright
|
||||||
just test_ruff
|
just test_ruff
|
||||||
just test_pytest
|
just test_pytest
|
||||||
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"
|
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"
|
||||||
|
@ -137,6 +142,7 @@ tests_full:
|
||||||
just test_shfmt
|
just test_shfmt
|
||||||
just test_black
|
just test_black
|
||||||
just test_mypy
|
just test_mypy
|
||||||
|
just test_pyright
|
||||||
just test_ruff
|
just test_ruff
|
||||||
just test_build
|
just test_build
|
||||||
just test_tox
|
just test_tox
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import re
|
import re
|
||||||
from time import sleep
|
from time import sleep
|
||||||
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from loguru import logger as log
|
from loguru import logger as log
|
||||||
|
@ -65,10 +66,10 @@ class Mangadex:
|
||||||
log.error("No valid UUID found")
|
log.error("No valid UUID found")
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
return uuid
|
return uuid # pyright:ignore
|
||||||
|
|
||||||
# make initial request
|
# make initial request
|
||||||
def get_manga_data(self) -> dict:
|
def get_manga_data(self) -> Dict[str, Any]:
|
||||||
log.debug(f"Getting manga data for: {self.manga_uuid}")
|
log.debug(f"Getting manga data for: {self.manga_uuid}")
|
||||||
counter = 1
|
counter = 1
|
||||||
while counter <= 3:
|
while counter <= 3:
|
||||||
|
@ -85,12 +86,14 @@ class Mangadex:
|
||||||
counter += 1
|
counter += 1
|
||||||
else:
|
else:
|
||||||
break
|
break
|
||||||
|
|
||||||
|
response_body: Dict[str, Dict[str, Any]] = response.json() # pyright:ignore
|
||||||
# check if manga exists
|
# check if manga exists
|
||||||
if response.json()["result"] != "ok":
|
if response_body["result"] != "ok": # type:ignore
|
||||||
log.error("Manga not found")
|
log.error("Manga not found")
|
||||||
raise KeyError
|
raise KeyError
|
||||||
|
|
||||||
return response.json()["data"]
|
return response_body["data"]
|
||||||
|
|
||||||
# get the title of the manga (and fix the filename)
|
# get the title of the manga (and fix the filename)
|
||||||
def get_manga_title(self) -> str:
|
def get_manga_title(self) -> str:
|
||||||
|
@ -112,7 +115,7 @@ class Mangadex:
|
||||||
if item.get(self.language):
|
if item.get(self.language):
|
||||||
alt_title = item
|
alt_title = item
|
||||||
break
|
break
|
||||||
title = alt_title[self.language]
|
title = alt_title[self.language] # pyright:ignore
|
||||||
except (KeyError, UnboundLocalError):
|
except (KeyError, UnboundLocalError):
|
||||||
log.warning(
|
log.warning(
|
||||||
"Manga title also not found in alt titles. Falling back to english title"
|
"Manga title also not found in alt titles. Falling back to english title"
|
||||||
|
@ -133,7 +136,7 @@ class Mangadex:
|
||||||
timeout=10,
|
timeout=10,
|
||||||
)
|
)
|
||||||
try:
|
try:
|
||||||
total_chapters = r.json()["total"]
|
total_chapters: int = r.json()["total"]
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.error(
|
log.error(
|
||||||
"Error retrieving the chapters list. Did you specify a valid language code?"
|
"Error retrieving the chapters list. Did you specify a valid language code?"
|
||||||
|
@ -147,7 +150,7 @@ class Mangadex:
|
||||||
return total_chapters
|
return total_chapters
|
||||||
|
|
||||||
# get chapter data like name, uuid etc
|
# get chapter data like name, uuid etc
|
||||||
def get_chapter_data(self) -> dict:
|
def get_chapter_data(self) -> Dict[str, Dict[str, Union[str, int]]]:
|
||||||
log.debug(f"Getting chapter data for: {self.manga_uuid}")
|
log.debug(f"Getting chapter data for: {self.manga_uuid}")
|
||||||
api_sorting = "order[chapter]=asc&order[volume]=asc"
|
api_sorting = "order[chapter]=asc&order[volume]=asc"
|
||||||
# check for chapters in specified lang
|
# check for chapters in specified lang
|
||||||
|
@ -161,8 +164,9 @@ class Mangadex:
|
||||||
f"{self.api_base_url}/manga/{self.manga_uuid}/feed?{api_sorting}&limit=500&offset={offset}&{self.api_additions}",
|
f"{self.api_base_url}/manga/{self.manga_uuid}/feed?{api_sorting}&limit=500&offset={offset}&{self.api_additions}",
|
||||||
timeout=10,
|
timeout=10,
|
||||||
)
|
)
|
||||||
for chapter in r.json()["data"]:
|
response_body: Dict[str, Any] = r.json()
|
||||||
attributes: dict = chapter["attributes"]
|
for chapter in response_body["data"]:
|
||||||
|
attributes: Dict[str, Any] = chapter["attributes"]
|
||||||
# chapter infos from feed
|
# chapter infos from feed
|
||||||
chapter_num: str = attributes.get("chapter") or ""
|
chapter_num: str = attributes.get("chapter") or ""
|
||||||
chapter_vol: str = attributes.get("volume") or ""
|
chapter_vol: str = attributes.get("volume") or ""
|
||||||
|
@ -201,10 +205,10 @@ class Mangadex:
|
||||||
# increase offset for mangas with more than 500 chapters
|
# increase offset for mangas with more than 500 chapters
|
||||||
offset += 500
|
offset += 500
|
||||||
|
|
||||||
return chapter_data
|
return chapter_data # type:ignore
|
||||||
|
|
||||||
# get images for the chapter (mangadex@home)
|
# get images for the chapter (mangadex@home)
|
||||||
def get_chapter_images(self, chapter: str, wait_time: float) -> list:
|
def get_chapter_images(self, chapter: str, wait_time: float) -> List[str]:
|
||||||
log.debug(f"Getting chapter images for: {self.manga_uuid}")
|
log.debug(f"Getting chapter images for: {self.manga_uuid}")
|
||||||
athome_url = f"{self.api_base_url}/at-home/server"
|
athome_url = f"{self.api_base_url}/at-home/server"
|
||||||
chapter_uuid = self.manga_chapter_data[chapter]["uuid"]
|
chapter_uuid = self.manga_chapter_data[chapter]["uuid"]
|
||||||
|
@ -238,11 +242,11 @@ class Mangadex:
|
||||||
if api_error:
|
if api_error:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
chapter_hash = api_data["chapter"]["hash"]
|
chapter_hash = api_data["chapter"]["hash"] # pyright:ignore
|
||||||
chapter_img_data = api_data["chapter"]["data"]
|
chapter_img_data = api_data["chapter"]["data"] # pyright:ignore
|
||||||
|
|
||||||
# get list of image urls
|
# get list of image urls
|
||||||
image_urls = []
|
image_urls: List[str] = []
|
||||||
for image in chapter_img_data:
|
for image in chapter_img_data:
|
||||||
image_urls.append(f"{self.img_base_url}/data/{chapter_hash}/{image}")
|
image_urls.append(f"{self.img_base_url}/data/{chapter_hash}/{image}")
|
||||||
|
|
||||||
|
@ -251,12 +255,12 @@ class Mangadex:
|
||||||
return image_urls
|
return image_urls
|
||||||
|
|
||||||
# create list of chapters
|
# create list of chapters
|
||||||
def create_chapter_list(self) -> list:
|
def create_chapter_list(self) -> List[str]:
|
||||||
log.debug(f"Creating chapter list for: {self.manga_uuid}")
|
log.debug(f"Creating chapter list for: {self.manga_uuid}")
|
||||||
chapter_list = []
|
chapter_list: List[str] = []
|
||||||
for data in self.manga_chapter_data.values():
|
for data in self.manga_chapter_data.values():
|
||||||
chapter_number: str = data["chapter"]
|
chapter_number: str = data["chapter"] # type:ignore
|
||||||
volume_number: str = data["volume"]
|
volume_number: str = data["volume"] # type:ignore
|
||||||
if self.forcevol:
|
if self.forcevol:
|
||||||
chapter_list.append(f"{volume_number}:{chapter_number}")
|
chapter_list.append(f"{volume_number}:{chapter_number}")
|
||||||
else:
|
else:
|
||||||
|
@ -264,12 +268,12 @@ class Mangadex:
|
||||||
|
|
||||||
return chapter_list
|
return chapter_list
|
||||||
|
|
||||||
def create_metadata(self, chapter: str) -> dict:
|
def create_metadata(self, chapter: str) -> Dict[str, Union[str, int, None]]:
|
||||||
log.info("Creating metadata from api")
|
log.info("Creating metadata from api")
|
||||||
|
|
||||||
chapter_data = self.manga_chapter_data[chapter]
|
chapter_data = self.manga_chapter_data[chapter]
|
||||||
try:
|
try:
|
||||||
volume = int(chapter_data.get("volume"))
|
volume = int(chapter_data["volume"])
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
volume = None
|
volume = None
|
||||||
metadata = {
|
metadata = {
|
||||||
|
@ -285,4 +289,4 @@ class Mangadex:
|
||||||
"Web": f"https://mangadex.org/title/{self.manga_uuid}",
|
"Web": f"https://mangadex.org/title/{self.manga_uuid}",
|
||||||
}
|
}
|
||||||
|
|
||||||
return metadata
|
return metadata # pyright:ignore
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
import re
|
import re
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import Any, Dict, List, Tuple, Union
|
||||||
|
|
||||||
from loguru import logger as log
|
from loguru import logger as log
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ def match_api(url_uuid: str) -> type:
|
||||||
The class of the API to use
|
The class of the API to use
|
||||||
"""
|
"""
|
||||||
# apis to check
|
# apis to check
|
||||||
apis: list[tuple[str, re.Pattern, type]] = [
|
apis: List[Tuple[str, re.Pattern[str], type]] = [
|
||||||
(
|
(
|
||||||
"mangadex.org",
|
"mangadex.org",
|
||||||
re.compile(
|
re.compile(
|
||||||
|
@ -108,7 +108,7 @@ class MangaDLP:
|
||||||
self.chapter_post_hook_cmd = chapter_post_hook_cmd
|
self.chapter_post_hook_cmd = chapter_post_hook_cmd
|
||||||
self.cache_path = cache_path
|
self.cache_path = cache_path
|
||||||
self.add_metadata = add_metadata
|
self.add_metadata = add_metadata
|
||||||
self.hook_infos: dict = {}
|
self.hook_infos: Dict[str, Any] = {}
|
||||||
|
|
||||||
# prepare everything
|
# prepare everything
|
||||||
self._prepare()
|
self._prepare()
|
||||||
|
@ -226,7 +226,7 @@ class MangaDLP:
|
||||||
skipped_chapters: list[Any] = []
|
skipped_chapters: list[Any] = []
|
||||||
error_chapters: list[Any] = []
|
error_chapters: list[Any] = []
|
||||||
for chapter in chapters_to_download:
|
for chapter in chapters_to_download:
|
||||||
if self.cache_path and chapter in cached_chapters:
|
if self.cache_path and chapter in cached_chapters: # pyright:ignore
|
||||||
log.info(f"Chapter '{chapter}' is in cache. Skipping download")
|
log.info(f"Chapter '{chapter}' is in cache. Skipping download")
|
||||||
continue
|
continue
|
||||||
|
|
||||||
|
@ -240,7 +240,7 @@ class MangaDLP:
|
||||||
skipped_chapters.append(chapter)
|
skipped_chapters.append(chapter)
|
||||||
# update cache
|
# update cache
|
||||||
if self.cache_path:
|
if self.cache_path:
|
||||||
cache.add_chapter(chapter)
|
cache.add_chapter(chapter) # pyright:ignore
|
||||||
continue
|
continue
|
||||||
except Exception:
|
except Exception:
|
||||||
# skip download/packing due to an error
|
# skip download/packing due to an error
|
||||||
|
@ -273,7 +273,7 @@ class MangaDLP:
|
||||||
|
|
||||||
# update cache
|
# update cache
|
||||||
if self.cache_path:
|
if self.cache_path:
|
||||||
cache.add_chapter(chapter)
|
cache.add_chapter(chapter) # pyright:ignore
|
||||||
|
|
||||||
# start chapter post hook
|
# start chapter post hook
|
||||||
run_hook(
|
run_hook(
|
||||||
|
@ -310,7 +310,7 @@ class MangaDLP:
|
||||||
# once called per chapter
|
# once called per chapter
|
||||||
def get_chapter(self, chapter: str) -> Path:
|
def get_chapter(self, chapter: str) -> Path:
|
||||||
# get chapter infos
|
# get chapter infos
|
||||||
chapter_infos: dict = self.api.manga_chapter_data[chapter]
|
chapter_infos: Dict[str, Union[str, int]] = self.api.manga_chapter_data[chapter]
|
||||||
log.debug(f"Chapter infos: {chapter_infos}")
|
log.debug(f"Chapter infos: {chapter_infos}")
|
||||||
|
|
||||||
# get image urls for chapter
|
# get image urls for chapter
|
||||||
|
@ -342,8 +342,8 @@ class MangaDLP:
|
||||||
# get filename for chapter (without suffix)
|
# get filename for chapter (without suffix)
|
||||||
chapter_filename = utils.get_filename(
|
chapter_filename = utils.get_filename(
|
||||||
self.manga_title,
|
self.manga_title,
|
||||||
chapter_infos["name"],
|
chapter_infos["name"], # type:ignore
|
||||||
chapter_infos["volume"],
|
chapter_infos["volume"], # type:ignore
|
||||||
chapter,
|
chapter,
|
||||||
self.forcevol,
|
self.forcevol,
|
||||||
self.name_format,
|
self.name_format,
|
||||||
|
@ -352,7 +352,7 @@ class MangaDLP:
|
||||||
log.debug(f"Filename: '{chapter_filename}'")
|
log.debug(f"Filename: '{chapter_filename}'")
|
||||||
|
|
||||||
# set download path for chapter (image folder)
|
# set download path for chapter (image folder)
|
||||||
chapter_path = self.manga_path / chapter_filename
|
chapter_path: Path = self.manga_path / chapter_filename
|
||||||
# set archive path with file format
|
# set archive path with file format
|
||||||
chapter_archive_path = Path(f"{chapter_path}{self.file_format}")
|
chapter_archive_path = Path(f"{chapter_path}{self.file_format}")
|
||||||
|
|
||||||
|
|
|
@ -26,12 +26,14 @@ class CacheDB:
|
||||||
if not self.db_data.get(self.db_key):
|
if not self.db_data.get(self.db_key):
|
||||||
self.db_data[self.db_key] = {}
|
self.db_data[self.db_key] = {}
|
||||||
|
|
||||||
self.db_uuid_data: dict = self.db_data[self.db_key]
|
self.db_uuid_data = self.db_data[self.db_key]
|
||||||
if not self.db_uuid_data.get("name"):
|
if not self.db_uuid_data.get("name"):
|
||||||
self.db_uuid_data.update({"name": self.name})
|
self.db_uuid_data.update({"name": self.name})
|
||||||
self._write_db()
|
self._write_db()
|
||||||
|
|
||||||
self.db_uuid_chapters: list = self.db_uuid_data.get("chapters") or []
|
self.db_uuid_chapters: List[str] = (
|
||||||
|
self.db_uuid_data.get("chapters") or [] # type:ignore
|
||||||
|
)
|
||||||
|
|
||||||
def _prepare_db(self) -> None:
|
def _prepare_db(self) -> None:
|
||||||
if self.db_path.exists():
|
if self.db_path.exists():
|
||||||
|
@ -44,11 +46,11 @@ class CacheDB:
|
||||||
log.error("Can't create db-file")
|
log.error("Can't create db-file")
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
def _read_db(self) -> Dict[str, dict]:
|
def _read_db(self) -> Dict[str, Dict[str, Union[str, List[str]]]]:
|
||||||
log.info(f"Reading cache-db: {self.db_path}")
|
log.info(f"Reading cache-db: {self.db_path}")
|
||||||
try:
|
try:
|
||||||
db_txt = self.db_path.read_text(encoding="utf8")
|
db_txt = self.db_path.read_text(encoding="utf8")
|
||||||
db_dict: dict[str, dict] = json.loads(db_txt)
|
db_dict: Dict[str, Dict[str, Union[str, List[str]]]] = json.loads(db_txt)
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.error("Can't load cache-db")
|
log.error("Can't load cache-db")
|
||||||
raise exc
|
raise exc
|
||||||
|
@ -73,7 +75,7 @@ class CacheDB:
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
def sort_chapters(chapters: list) -> List[str]:
|
def sort_chapters(chapters: List[str]) -> List[str]:
|
||||||
try:
|
try:
|
||||||
sorted_list = sorted(chapters, key=float)
|
sorted_list = sorted(chapters, key=float)
|
||||||
except Exception:
|
except Exception:
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Any, List
|
||||||
|
|
||||||
import click
|
import click
|
||||||
from click_option_group import (
|
from click_option_group import (
|
||||||
|
@ -15,7 +16,7 @@ from mangadlp.logger import prepare_logger
|
||||||
|
|
||||||
|
|
||||||
# read in the list of links from a file
|
# read in the list of links from a file
|
||||||
def readin_list(_ctx, _param, value) -> list:
|
def readin_list(_ctx: click.Context, _param: str, value: str) -> List[str]:
|
||||||
if not value:
|
if not value:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -38,8 +39,8 @@ def readin_list(_ctx, _param, value) -> list:
|
||||||
@click.help_option()
|
@click.help_option()
|
||||||
@click.version_option(version=__version__, package_name="manga-dlp")
|
@click.version_option(version=__version__, package_name="manga-dlp")
|
||||||
# manga selection
|
# manga selection
|
||||||
@optgroup.group("source", cls=RequiredMutuallyExclusiveOptionGroup)
|
@optgroup.group("source", cls=RequiredMutuallyExclusiveOptionGroup) # type: ignore
|
||||||
@optgroup.option(
|
@optgroup.option( # type: ignore
|
||||||
"-u",
|
"-u",
|
||||||
"--url",
|
"--url",
|
||||||
"--uuid",
|
"--uuid",
|
||||||
|
@ -49,19 +50,19 @@ def readin_list(_ctx, _param, value) -> list:
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="URL or UUID of the manga",
|
help="URL or UUID of the manga",
|
||||||
)
|
)
|
||||||
@optgroup.option(
|
@optgroup.option( # type: ignore
|
||||||
"--read",
|
"--read",
|
||||||
"read_mangas",
|
"read_mangas",
|
||||||
is_eager=True,
|
is_eager=True,
|
||||||
callback=readin_list,
|
callback=readin_list,
|
||||||
type=click.Path(exists=True, dir_okay=False),
|
type=click.Path(exists=True, dir_okay=False, path_type=str),
|
||||||
default=None,
|
default=None,
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="Path of file with manga links to download. One per line",
|
help="Path of file with manga links to download. One per line",
|
||||||
)
|
)
|
||||||
# logging options
|
# logging options
|
||||||
@optgroup.group("verbosity", cls=MutuallyExclusiveOptionGroup)
|
@optgroup.group("verbosity", cls=MutuallyExclusiveOptionGroup) # type: ignore
|
||||||
@optgroup.option(
|
@optgroup.option( # type: ignore
|
||||||
"--loglevel",
|
"--loglevel",
|
||||||
"verbosity",
|
"verbosity",
|
||||||
type=int,
|
type=int,
|
||||||
|
@ -69,7 +70,7 @@ def readin_list(_ctx, _param, value) -> list:
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="Custom log level",
|
help="Custom log level",
|
||||||
)
|
)
|
||||||
@optgroup.option(
|
@optgroup.option( # type: ignore
|
||||||
"--warn",
|
"--warn",
|
||||||
"verbosity",
|
"verbosity",
|
||||||
flag_value=30,
|
flag_value=30,
|
||||||
|
@ -77,7 +78,7 @@ def readin_list(_ctx, _param, value) -> list:
|
||||||
show_default=True,
|
show_default=True,
|
||||||
help="Only log warnings and higher",
|
help="Only log warnings and higher",
|
||||||
)
|
)
|
||||||
@optgroup.option(
|
@optgroup.option( # type: ignore
|
||||||
"--debug",
|
"--debug",
|
||||||
"verbosity",
|
"verbosity",
|
||||||
flag_value=10,
|
flag_value=10,
|
||||||
|
@ -227,7 +228,7 @@ def readin_list(_ctx, _param, value) -> list:
|
||||||
help="Enable/disable creation of metadata via ComicInfo.xml",
|
help="Enable/disable creation of metadata via ComicInfo.xml",
|
||||||
)
|
)
|
||||||
@click.pass_context
|
@click.pass_context
|
||||||
def main(ctx: click.Context, **kwargs) -> None:
|
def main(ctx: click.Context, **kwargs: Any) -> None:
|
||||||
"""Script to download mangas from various sites."""
|
"""Script to download mangas from various sites."""
|
||||||
url_uuid: str = kwargs.pop("url_uuid")
|
url_uuid: str = kwargs.pop("url_uuid")
|
||||||
read_mangas: list[str] = kwargs.pop("read_mangas")
|
read_mangas: list[str] = kwargs.pop("read_mangas")
|
||||||
|
|
|
@ -2,7 +2,7 @@ import logging
|
||||||
import shutil
|
import shutil
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from time import sleep
|
from time import sleep
|
||||||
from typing import Union
|
from typing import List, Union
|
||||||
|
|
||||||
import requests
|
import requests
|
||||||
from loguru import logger as log
|
from loguru import logger as log
|
||||||
|
@ -12,7 +12,7 @@ from mangadlp import utils
|
||||||
|
|
||||||
# download images
|
# download images
|
||||||
def download_chapter(
|
def download_chapter(
|
||||||
image_urls: list,
|
image_urls: List[str],
|
||||||
chapter_path: Union[str, Path],
|
chapter_path: Union[str, Path],
|
||||||
download_wait: float,
|
download_wait: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -48,8 +48,8 @@ def download_chapter(
|
||||||
# write image
|
# write image
|
||||||
try:
|
try:
|
||||||
with image_path.open("wb") as file:
|
with image_path.open("wb") as file:
|
||||||
r.raw.decode_content = True
|
r.raw.decode_content = True # pyright:ignore
|
||||||
shutil.copyfileobj(r.raw, file)
|
shutil.copyfileobj(r.raw, file) # pyright:ignore
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.error("Can't write file")
|
log.error("Can't write file")
|
||||||
raise exc
|
raise exc
|
||||||
|
|
|
@ -1,10 +1,11 @@
|
||||||
import os
|
import os
|
||||||
import subprocess
|
import subprocess
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
from loguru import logger as log
|
from loguru import logger as log
|
||||||
|
|
||||||
|
|
||||||
def run_hook(command: str, hook_type: str, **kwargs) -> int:
|
def run_hook(command: str, hook_type: str, **kwargs: Any) -> int:
|
||||||
"""Run a command.
|
"""Run a command.
|
||||||
|
|
||||||
Run a command with subprocess.run and add kwargs to the environment.
|
Run a command with subprocess.run and add kwargs to the environment.
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
import logging
|
import logging
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Any, Dict
|
||||||
|
|
||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
|
@ -10,7 +11,7 @@ LOGURU_FMT = "{time:%Y-%m-%dT%H:%M:%S%z} | <level>[{level: <7}]</level> [{name:
|
||||||
class InterceptHandler(logging.Handler):
|
class InterceptHandler(logging.Handler):
|
||||||
"""Intercept python logging messages and log them via loguru.logger."""
|
"""Intercept python logging messages and log them via loguru.logger."""
|
||||||
|
|
||||||
def emit(self, record):
|
def emit(self, record: Any) -> None:
|
||||||
# Get corresponding Loguru level if it exists
|
# Get corresponding Loguru level if it exists
|
||||||
try:
|
try:
|
||||||
level = logger.level(record.levelname).name
|
level = logger.level(record.levelname).name
|
||||||
|
@ -19,8 +20,8 @@ class InterceptHandler(logging.Handler):
|
||||||
|
|
||||||
# Find caller from where originated the logged message
|
# Find caller from where originated the logged message
|
||||||
frame, depth = logging.currentframe(), 2
|
frame, depth = logging.currentframe(), 2
|
||||||
while frame.f_code.co_filename == logging.__file__:
|
while frame.f_code.co_filename == logging.__file__: # pyright:ignore
|
||||||
frame = frame.f_back
|
frame = frame.f_back # type: ignore
|
||||||
depth += 1
|
depth += 1
|
||||||
|
|
||||||
logger.opt(depth=depth, exception=record.exc_info).log(
|
logger.opt(depth=depth, exception=record.exc_info).log(
|
||||||
|
@ -30,7 +31,7 @@ class InterceptHandler(logging.Handler):
|
||||||
|
|
||||||
# init logger with format and log level
|
# init logger with format and log level
|
||||||
def prepare_logger(loglevel: int = 20) -> None:
|
def prepare_logger(loglevel: int = 20) -> None:
|
||||||
config: dict = {
|
config: Dict[str, Any] = {
|
||||||
"handlers": [
|
"handlers": [
|
||||||
{
|
{
|
||||||
"sink": sys.stdout,
|
"sink": sys.stdout,
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, Tuple
|
from typing import Any, Dict, Tuple, Union
|
||||||
|
|
||||||
import xmltodict
|
import xmltodict
|
||||||
from loguru import logger as log
|
from loguru import logger as log
|
||||||
|
@ -8,7 +8,7 @@ METADATA_FILENAME = "ComicInfo.xml"
|
||||||
METADATA_TEMPLATE = Path("mangadlp/metadata/ComicInfo_v2.0.xml")
|
METADATA_TEMPLATE = Path("mangadlp/metadata/ComicInfo_v2.0.xml")
|
||||||
# define metadata types, defaults and valid values. an empty list means no value check
|
# define metadata types, defaults and valid values. an empty list means no value check
|
||||||
# {key: (type, default value, valid values)}
|
# {key: (type, default value, valid values)}
|
||||||
METADATA_TYPES: Dict[str, Tuple[type, Any, list]] = {
|
METADATA_TYPES: Dict[str, Tuple[type, Any, list[Union[str, int]]]] = {
|
||||||
"Title": (str, None, []),
|
"Title": (str, None, []),
|
||||||
"Series": (str, None, []),
|
"Series": (str, None, []),
|
||||||
"Number": (str, None, []),
|
"Number": (str, None, []),
|
||||||
|
@ -59,10 +59,12 @@ METADATA_TYPES: Dict[str, Tuple[type, Any, list]] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def validate_metadata(metadata_in: dict) -> Dict[str, dict]:
|
def validate_metadata(
|
||||||
|
metadata_in: Dict[str, Union[str, int]]
|
||||||
|
) -> Dict[str, Dict[str, Union[str, int]]]:
|
||||||
log.info("Validating metadata")
|
log.info("Validating metadata")
|
||||||
|
|
||||||
metadata_valid: dict[str, dict] = {"ComicInfo": {}}
|
metadata_valid: dict[str, Dict[str, Union[str, int]]] = {"ComicInfo": {}}
|
||||||
for key, value in METADATA_TYPES.items():
|
for key, value in METADATA_TYPES.items():
|
||||||
metadata_type, metadata_default, metadata_validation = value
|
metadata_type, metadata_default, metadata_validation = value
|
||||||
|
|
||||||
|
@ -104,7 +106,7 @@ def validate_metadata(metadata_in: dict) -> Dict[str, dict]:
|
||||||
return metadata_valid
|
return metadata_valid
|
||||||
|
|
||||||
|
|
||||||
def write_metadata(chapter_path: Path, metadata: dict) -> None:
|
def write_metadata(chapter_path: Path, metadata: Dict[str, Union[str, int]]) -> None:
|
||||||
if metadata["Format"] == "pdf":
|
if metadata["Format"] == "pdf":
|
||||||
log.warning("Can't add metadata for pdf format. Skipping")
|
log.warning("Can't add metadata for pdf format. Skipping")
|
||||||
return
|
return
|
||||||
|
|
|
@ -24,7 +24,7 @@ def make_archive(chapter_path: Path, file_format: str) -> None:
|
||||||
|
|
||||||
def make_pdf(chapter_path: Path) -> None:
|
def make_pdf(chapter_path: Path) -> None:
|
||||||
try:
|
try:
|
||||||
import img2pdf # pylint: disable=import-outside-toplevel
|
import img2pdf # pylint: disable=import-outside-toplevel # pyright:ignore
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.error("Cant import img2pdf. Please install it first")
|
log.error("Cant import img2pdf. Please install it first")
|
||||||
raise exc
|
raise exc
|
||||||
|
@ -34,14 +34,14 @@ def make_pdf(chapter_path: Path) -> None:
|
||||||
for file in chapter_path.iterdir():
|
for file in chapter_path.iterdir():
|
||||||
images.append(str(file))
|
images.append(str(file))
|
||||||
try:
|
try:
|
||||||
pdf_path.write_bytes(img2pdf.convert(images))
|
pdf_path.write_bytes(img2pdf.convert(images)) # pyright:ignore
|
||||||
except Exception as exc:
|
except Exception as exc:
|
||||||
log.error("Can't create '.pdf' archive")
|
log.error("Can't create '.pdf' archive")
|
||||||
raise exc
|
raise exc
|
||||||
|
|
||||||
|
|
||||||
# create a list of chapters
|
# create a list of chapters
|
||||||
def get_chapter_list(chapters: str, available_chapters: list) -> List[str]:
|
def get_chapter_list(chapters: str, available_chapters: List[str]) -> List[str]:
|
||||||
# check if there are available chapter
|
# check if there are available chapter
|
||||||
chapter_list: list[str] = []
|
chapter_list: list[str] = []
|
||||||
for chapter in chapters.split(","):
|
for chapter in chapters.split(","):
|
||||||
|
|
|
@ -71,7 +71,7 @@ dependencies = [
|
||||||
# mypy
|
# mypy
|
||||||
|
|
||||||
[tool.mypy]
|
[tool.mypy]
|
||||||
#strict = true
|
strict = true
|
||||||
python_version = "3.9"
|
python_version = "3.9"
|
||||||
disallow_untyped_defs = false
|
disallow_untyped_defs = false
|
||||||
follow_imports = "normal"
|
follow_imports = "normal"
|
||||||
|
@ -84,6 +84,18 @@ show_error_codes = true
|
||||||
pretty = true
|
pretty = true
|
||||||
no_implicit_optional = false
|
no_implicit_optional = false
|
||||||
|
|
||||||
|
# pyright
|
||||||
|
|
||||||
|
[tool.pyright]
|
||||||
|
typeCheckingMode = "strict"
|
||||||
|
pythonVersion = "3.9"
|
||||||
|
reportUnnecessaryTypeIgnoreComment = true
|
||||||
|
reportShadowedImports = true
|
||||||
|
reportUnusedExpression = true
|
||||||
|
reportMatchNotExhaustive = true
|
||||||
|
# venvPath = "."
|
||||||
|
# venv = "venv"
|
||||||
|
|
||||||
# ruff
|
# ruff
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
|
@ -103,10 +115,11 @@ select = [
|
||||||
]
|
]
|
||||||
line-length = 88
|
line-length = 88
|
||||||
fix = true
|
fix = true
|
||||||
|
show-fixes = true
|
||||||
format = "grouped"
|
format = "grouped"
|
||||||
ignore-init-module-imports = true
|
ignore-init-module-imports = true
|
||||||
respect-gitignore = true
|
respect-gitignore = true
|
||||||
ignore = ["E501", "D103", "D100"]
|
ignore = ["E501", "D103", "D100", "D102", "PLR2004"]
|
||||||
exclude = [
|
exclude = [
|
||||||
".direnv",
|
".direnv",
|
||||||
".git",
|
".git",
|
||||||
|
|
Loading…
Reference in a new issue