Merge pull request '[2.3.1] - 2023-03-12' (#41) from dev into master
All checks were successful
ci/woodpecker/tag/tests Pipeline was successful
ci/woodpecker/tag/publish_release Pipeline was successful
ci/woodpecker/tag/publish_docker_arm64 Pipeline was successful
ci/woodpecker/tag/publish_docker_amd64 Pipeline was successful
ci/woodpecker/tag/publish_docker_manifest Pipeline was successful
ci/woodpecker/push/tests Pipeline was successful

Reviewed-on: #41
This commit is contained in:
Ivan Schaller 2023-03-12 04:47:37 +01:00
commit 29fe262ef7
28 changed files with 531 additions and 450 deletions

View file

@ -26,7 +26,7 @@ pipeline:
branch: master
event: pull_request
commands:
- python3 -m hatch build --clean
- just test_build
# create release-notes
test-create-release-notes:

View file

@ -26,4 +26,4 @@ pipeline:
branch: master
event: pull_request
commands:
- python3 -m tox
- just test_tox

View file

@ -29,4 +29,4 @@ pipeline:
- grep -v img2pdf contrib/requirements_dev.txt > contrib/requirements_dev_arm64.txt
- rm -f contrib/requirements_dev.txt
- mv contrib/requirements_dev_arm64.txt contrib/requirements_dev.txt
- python3 -m tox
- just test_tox

View file

@ -17,51 +17,29 @@ pipeline:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- shfmt -d -i 4 -bn -ci -sr .
- just test_shfmt
# check code style - python
test-black:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m black --check --diff .
# check imports - python
test-isort:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m isort --check-only --diff .
# check unused and missing imports - python
test-autoflake:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m autoflake --remove-all-unused-imports -r -v mangadlp/
- python3 -m autoflake --check --remove-all-unused-imports -r -v mangadlp/
- just test_black
# check static typing - python
test-mypy:
test-pyright:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m mypy --install-types --non-interactive mangadlp/
- just install_deps
- just test_pyright
# mccabe, pycodestyle, pyflakes tests - python
test-pylama:
# ruff test - python
test-ruff:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m pylama mangadlp/
# pylint test - python
test-pylint:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m pip install -r requirements.txt
- python3 -m pylint --fail-under 9 mangadlp/
- just test_ruff
# test mkdocs generation
test-mkdocs:
@ -72,14 +50,14 @@ pipeline:
- cd docs || exit 1
- python3 -m mkdocs build --strict
# test code with different python versions - python
# test code with pytest - python
test-tox-pytest:
when:
event: [ push ]
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m tox -e basic
- just test_pytest
# generate coverage report - python
test-tox-coverage:
@ -89,7 +67,7 @@ pipeline:
image: cr.44net.ch/ci-plugins/tests
pull: true
commands:
- python3 -m tox -e coverage
- just test_coverage
# analyse code with sonarqube and upload it
sonarqube-analysis:

View file

@ -7,442 +7,458 @@ to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
- Add support for more sites
- Add support for more sites
## [2.3.1] - 2023-03-12
### Added
- Added TypedDicts for type checkers and type annotation
### Fixed
- Fixed some typos in the README
### Changed
- Switched from pylint/pylama/isort/autoflake to ruff
- Switched from mypy to pyright and added strict type checking
- Updated the api template
## [2.3.0] - 2023-02-15
### Added
- Metadata is now added to each chapter. Schema
standard: [https://anansi-project.github.io/docs/comicinfo/schemas/v2.0](https://anansi-project.github.io/docs/comicinfo/schemas/v2.0)
- Added `xmltodict` as a package requirement
- Cache now also saves the manga title
- New tests
- More typo annotations for function, compatible with python3.8
- File format checker if you use the MangaDLP class directly
- Metadata is now added to each chapter. Schema
standard: [https://anansi-project.github.io/docs/comicinfo/schemas/v2.0](https://anansi-project.github.io/docs/comicinfo/schemas/v2.0)
- Added `xmltodict` as a package requirement
- Cache now also saves the manga title
- New tests
- More typo annotations for function, compatible with python3.8
- File format checker if you use the MangaDLP class directly
### Fixed
- API template typos
- Some useless type annotations
- API template typos
- Some useless type annotations
### Changed
- Simplified the chapter info generation
- Updated the license year
- Updated the API template
- Updated the API detection and removed it from the MangaDLP class
- Simplified the chapter info generation
- Updated the license year
- Updated the API template
- Updated the API detection and removed it from the MangaDLP class
## [2.2.20] - 2023-02-12
### Fixed
- Script now doesn't exit if multiple mangas were requested and one had an error
- Script now doesn't exit if multiple mangas were requested and one had an error
## [2.2.19] - 2023-02-11
### Added
- First version of the chapter cache (very basic functionality)
- First version of the chapter cache (very basic functionality)
### Fixed
- Fixed all exception re-raises to include the original stack trace
- Fixed all exception re-raises to include the original stack trace
### Changed
- Simplified chapter download loop
- Simplified chapter download loop
## [2.2.18] - 2023-01-21
### Fixed
- Fixed manga titles on non english language
- Fixed title & filename fixing to not use `ascii` but `uft8`
- Fixed manga titles on non english language
- Fixed title & filename fixing to not use `ascii` but `uft8`
### Added
- Fallback title to english of none was found in requested language
- More debug logs
- More tests
- Fallback title to english of none was found in requested language
- More debug logs
- More tests
### Changed
- Now uses the first found alt-title. Before it was the last
- Removed `sys.exit` in the api
- Now uses the first found alt-title. Before it was the last
- Removed `sys.exit` in the api
## [2.2.17] - 2023-01-15
### Fixed
- Set a timeout of 10 seconds for the api requests
- Set a timeout of 10 seconds for the api requests
### Added
- `--name-format` and `--name-format-none` flags to add a custom naming scheme for the downloaded files. See
docs: https://manga-dlp.ivn.sh/download/
- More debug log messages
- More tests for the custom naming scheme
- More type hints
- `--name-format` and `--name-format-none` flags to add a custom naming scheme for the downloaded files. See
docs: https://manga-dlp.ivn.sh/download/
- More debug log messages
- More tests for the custom naming scheme
- More type hints
### Changed
- Make `--format` a `click.Choice` option
- In the `--format` option the leading dot is now invalid. `--format .cbz` -> `--format cbz`
- Changed empty values from the api from None to an empty string
- Minor code readability improvements
- Make `--format` a `click.Choice` option
- In the `--format` option the leading dot is now invalid. `--format .cbz` -> `--format cbz`
- Changed empty values from the api from None to an empty string
- Minor code readability improvements
## [2.2.16] - 2022-12-30
### Fixed
- Log level is now fixed and should not default to 0
- Docker schedule should now work again
- Log level is now fixed and should not default to 0
- Docker schedule should now work again
### Changed
- Integrate logging logs to loguru via custom sink
- Simplify docker shell scripts
- Integrate logging logs to loguru via custom sink
- Simplify docker shell scripts
## [2.2.15] - 2022-12-29
### Added
- `--warn` and `--loglevel` flags
- `--warn` and `--loglevel` flags
### Removed
- Remove `--lean` and `--verbose` flags and remove custom log levels
- Remove `--lean` and `--verbose` flags and remove custom log levels
### Changed
- Move from standard library logging to [loguru](https://loguru.readthedocs.io/en/stable/index.html)
- Move from standard library argparse to [click](https://click.palletsprojects.com/en/8.1.x/)
- Move from standard library logging to [loguru](https://loguru.readthedocs.io/en/stable/index.html)
- Move from standard library argparse to [click](https://click.palletsprojects.com/en/8.1.x/)
## [2.2.14] - 2022-10-06
### Changed
- Changed logging format to ISO 8601
- Small logging corrections
- Changed logging format to ISO 8601
- Small logging corrections
## [2.2.13] - 2022-08-15
### Added
- Option to run custom hooks before and after each chapter/manga download
- _Tests for the new hooks_
- _Docs for the new hooks_
- _Tests for mkdocs generation_
- Option to run custom hooks before and after each chapter/manga download
- _Tests for the new hooks_
- _Docs for the new hooks_
- _Tests for mkdocs generation_
### Changed
- Verbose and Debug logging now have a space as a seperator between log level-name and log-level
- APIs now have an attribute with their name (for the hooks) - `api.api_name`
- Docs moved to Cloudflare pages (generated with mkdocs)
- Verbose and Debug logging now have a space as a seperator between log level-name and log-level
- APIs now have an attribute with their name (for the hooks) - `api.api_name`
- Docs moved to Cloudflare pages (generated with mkdocs)
## [2.1.12] - 2022-07-25
### Fixed
- Image publishing with `hatch` on pypi should now work again
- The schedule fixer for the new `.sh` schedule should now work correctly
- Image publishing with `hatch` on pypi should now work again
- The schedule fixer for the new `.sh` schedule should now work correctly
### Added
- More CI tests: `pylint`, `pylama` and `autoflake`
- New function in `get_release_notes.sh` to get the latest version
- Docstrings for `MangaDLP` and the api module `Mangadex`
- More CI tests: `pylint`, `pylama` and `autoflake`
- New function in `get_release_notes.sh` to get the latest version
- Docstrings for `MangaDLP` and the api module `Mangadex`
### Changed
- CI workflow is now faster and runs natively on arm64 (before it was buildx/emulation)
- `Pylint`/`pylama` code improvements
- Version management is now done with `hatch` (in `__about__.py`)
- CI workflow is now faster and runs natively on arm64 (before it was buildx/emulation)
- `Pylint`/`pylama` code improvements
- Version management is now done with `hatch` (in `__about__.py`)
## [2.1.11] - 2022-07-18
### Fixed
- The `--read` option now filters empty lines, so it will not generate an error anymore
- An error which was caused by the interactive input method when you did not specify a chapter or to list them
- Some typos
- The `--read` option now filters empty lines, so it will not generate an error anymore
- An error which was caused by the interactive input method when you did not specify a chapter or to list them
- Some typos
### Added
- Options to configure the default schedule in the docker container via environment variables
- Section the the docker [README.md](docker/README.md) for the new environment variables
- `autoflake` test in `justfile`
- Some more things which get logged
- Options to configure the default schedule in the docker container via environment variables
- Section the the docker [README.md](docker/README.md) for the new environment variables
- `autoflake` test in `justfile`
- Some more things which get logged
### Changed
- **BREAKING**: renamed the default schedule from `daily` to `daily.sh`. Don't forget to fix your bind-mounts to
overwrite
the default schedule
- Added the `.sh` suffix to the s6 init scripts for better compatibility
- Adjusted the new logging implementation. It shows now more info about the module the log is from, and some other
improvements
- **BREAKING**: renamed the default schedule from `daily` to `daily.sh`. Don't forget to fix your bind-mounts to
overwrite
the default schedule
- Added the `.sh` suffix to the s6 init scripts for better compatibility
- Adjusted the new logging implementation. It shows now more info about the module the log is from, and some other
improvements
## [2.1.10] - 2022-07-14
### Fixed
- Removed some unused files
- Removed some unused files
### Added
- `logger.py` for all log related settings and functions
- `logger.py` for all log related settings and functions
### Changed
- Logging of output. The script now uses the `logging` library
- Logging of output. The script now uses the `logging` library
## [2.1.9] - 2022-06-26
### Fixed
- Timeouts in tests, due to api limitations. Now added a wait time between tests
- Pytest path
- Timeouts in tests, due to api limitations. Now added a wait time between tests
- Pytest path
### Added
- `--lean` flag for less output
- [justfile](https://github.com/casey/just) for setting up a dev environment and testing the code
- [asdf](https://github.com/asdf-vm/asdf) for version management
- Dev requirements in [contrib/requirements_dev.txt](contrib/requirements_dev.txt)
- `README` in [contrib](contrib)
- `--lean` flag for less output
- [justfile](https://github.com/casey/just) for setting up a dev environment and testing the code
- [asdf](https://github.com/asdf-vm/asdf) for version management
- Dev requirements in [contrib/requirements_dev.txt](contrib/requirements_dev.txt)
- `README` in [contrib](contrib)
### Changed
- Handling of verbosity and logging. Now there are 4 types of verbosity: `normal`, `lean`, `verbose` and `debug`
- CI/CD pipeline for testing and releases
- Coverage testing now also done with `tox`
- Default verbosity of docker container is now `--lean`
- Reorganised [pyproject.toml](pyproject.toml)
- Handling of verbosity and logging. Now there are 4 types of verbosity: `normal`, `lean`, `verbose` and `debug`
- CI/CD pipeline for testing and releases
- Coverage testing now also done with `tox`
- Default verbosity of docker container is now `--lean`
- Reorganised [pyproject.toml](pyproject.toml)
## [2.1.8] - 2022-06-22
### Fixed
- Interactive input
- Interactive input
## [2.1.7] - 2022-06-22
### Added
- tox version testing
- New pre-release tests
- Build info's with hatch
- [Pypi](https://pypi.org/project/manga-dlp/) build with hatch
- Pypi section in `README.md`
- [Snyk](https://app.snyk.io/org/olofvndrhr-t6h/project/aae9609d-a4e4-41f8-b1ac-f2561b2ad4e3) test results
in `README.md`
- tox version testing
- New pre-release tests
- Build info's with hatch
- [Pypi](https://pypi.org/project/manga-dlp/) build with hatch
- Pypi section in `README.md`
- [Snyk](https://app.snyk.io/org/olofvndrhr-t6h/project/aae9609d-a4e4-41f8-b1ac-f2561b2ad4e3) test results
in `README.md`
### Changed
- Moved code from `manga-dlp.py` to `input.py` for uniformity
- The default entrypoint is now `mangadlp.input:main`
- Moved code from `manga-dlp.py` to `input.py` for uniformity
- The default entrypoint is now `mangadlp.input:main`
## [2.1.6] - 2022-06-21
### Fixed
- Docker labels are now working
- Global variables are now fully uppercase
- Some errors with static types
- Docker labels are now working
- Global variables are now fully uppercase
- Some errors with static types
### Added
- bump2version config for releases
- More tests with: `mypy` and `isort`
- New issue templates
- bump2version config for releases
- More tests with: `mypy` and `isort`
- New issue templates
### Changed
- Release workflow now is based on configuration files
- Switched from `setup.py` to `pyproject.toml`
- `README.md` now has sorted badges
- Imports are now sorted with `isort`
- Static types are now checked with `mypy`
- Release note generation is now simplified
- Release workflow now is based on configuration files
- Switched from `setup.py` to `pyproject.toml`
- `README.md` now has sorted badges
- Imports are now sorted with `isort`
- Static types are now checked with `mypy`
- Release note generation is now simplified
## [2.1.5] - 2022-06-18
### Fixed
- Image names now have a suffix, as some comic readers have problems with no
suffix [fixes issue #2]
- Image names now have a suffix, as some comic readers have problems with no
suffix [fixes issue #2]
### Added
- `--format` section in the README
- `--format` section in the README
## [2.1.4] - 2022-05-29
### Fixed
- Docker container now works again
- Fixed cron in docker container
- Docker container now works again
- Fixed cron in docker container
### Changed
- Docker container scheduling is now more practical
- Docker container scheduling is now more practical
## [2.1.3] - 2022-05-29
### Fixed
- Error-chapters and skipped-chapters list are now shown again
- The Interactive input version now matches `--version`
- Error-chapters and skipped-chapters list are now shown again
- The Interactive input version now matches `--version`
### Added
- Ability to list chapters with interactive input
- Ability to list chapters with interactive input
### Changed
- Replace `exit()` with `sys.exit()`
- Renamed class methods to not look like dunder methods
- Script execution moved from `os.system()` to `subprocess.call()`
- Replace `exit()` with `sys.exit()`
- Renamed class methods to not look like dunder methods
- Script execution moved from `os.system()` to `subprocess.call()`
## [2.1.2] - 2022-05-20
### Fixed
- List chapters when none were specified
- Typos
- List chapters when none were specified
- Typos
### Added
- Ability to download whole volumes
- Ability to download whole volumes
### Changed
- Moved processing of list with links to input.py
- Updated README for volume and chapter selection
- Moved processing of list with links to input.py
- Updated README for volume and chapter selection
## [2.1.1] - 2022-05-18
### Fixed
- Progress bar on verbose output
- Sonarqube link for CI
- A few typos
- Removed unnecessary escapes from file rename regex
- Progress bar on verbose output
- Sonarqube link for CI
- A few typos
- Removed unnecessary escapes from file rename regex
### Added
- API template
- API template
### Changed
- Updated docker baseimage
- Rewrote app.py to a class
- Updated docker baseimage
- Rewrote app.py to a class
## [2.1.0] - 2022-05-16
### Fixed
- Detection of files. Now it will skip them again
- Detection of files. Now it will skip them again
### Added
- Ability to save the chapters as pdf (only on amd64/x86)
- New output formats: rar, zip
- Progress bar to show image download
- Interactive input if no command line flags are given
- Better KeyboardInterrupt handling
- Better error handling
- Removed duplicate code
- Ability to save the chapters as pdf (only on amd64/x86)
- New output formats: rar, zip
- Progress bar to show image download
- Interactive input if no command line flags are given
- Better KeyboardInterrupt handling
- Better error handling
- Removed duplicate code
### Changed
- How the variables are used inside the script
- Variables have now the same name as in other scripts (mostly)
- Better retrying when a task fails
- How the variables are used inside the script
- Variables have now the same name as in other scripts (mostly)
- Better retrying when a task fails
## [2.0.8] - 2022-05-13
### Changed
- Rewrote parts of script to be easier to maintain
- Moved the input script to the base folder
- Moved all arguments to a class
- Docker container creation
- Rewrote parts of script to be easier to maintain
- Moved the input script to the base folder
- Moved all arguments to a class
- Docker container creation
## [2.0.7] - 2022-05-13
### Changed
- Changed CI/CD Platform from Drone-CI to Woodpecker-CI
- Release title is now only the version
- Changed CI/CD Platform from Drone-CI to Woodpecker-CI
- Release title is now only the version
## [2.0.6] - 2022-05-11
### Fixed
- Filenames on windows (ntfs). Removed double quote from file and folder names
- Filenames on windows (ntfs). Removed double quote from file and folder names
## [2.0.5] - 2022-05-11
### Fixed
- Better error handling on "KeyboardInterrupt"
- Release notes now fixed
- Better error handling on "KeyboardInterrupt"
- Release notes now fixed
### Added
- New test cases
- New test cases
## [2.0.4] - 2022-05-10
### Added
- New test cases for more coverage
- Github release
- Updated docker baseimage
- New test cases for more coverage
- Github release
- Updated docker baseimage
## [2.0.3] - 2022-05-10
### Fixed
- Test cases now work again
- Sonarqube settings
- Test cases now work again
- Sonarqube settings
### Added
- Coverage report in sonarqube
- Gitea release
- Coverage report in sonarqube
- Gitea release
## [2.0.2] - 2022-05-09
### Fixed
- Restart failed api requests
- Added wait time for image gathering, as to stop api rate limiting from mangadex
- "--wait" options now works properly again
- Restart failed api requests
- Added wait time for image gathering, as to stop api rate limiting from mangadex
- "--wait" options now works properly again
## [2.0.1] - 2022-05-09
### Fixed
- Regex for removing illegal characters in the filenames now doesn't remove quotes
- Updated docker baseimage and fixed the mangadlp tag in it
- Update license for 2022
- Regex for removing illegal characters in the filenames now doesn't remove quotes
- Updated docker baseimage and fixed the mangadlp tag in it
- Update license for 2022
### Added
- Quick start section in README
- Preperation for pypi
- Quick start section in README
- Preperation for pypi
## [2.0.0] - 2022-05-09
### Fixed
- Support for new mangadex api
- Support for new mangadex api
### Changed
- Code is now formatted with [black](https://github.com/psf/black)
- Now also supports just the uuid for managex (not a full link)
- Code is now formatted with [black](https://github.com/psf/black)
- Now also supports just the uuid for managex (not a full link)

View file

@ -19,30 +19,42 @@ Code Analysis
Meta
[![Code style](https://img.shields.io/badge/code%20style-black-black)](https://github.com/psf/black)
[![Linter](https://img.shields.io/badge/linter-pylint-yellowgreen)](https://pylint.pycqa.org/en/latest/)
[![Types](https://img.shields.io/badge/types-mypy-blue)](https://github.com/python/mypy)
[![Imports](https://img.shields.io/badge/imports-isort-ef8336.svg)](https://github.com/pycqa/isort)
[![Linter](https://img.shields.io/badge/linter-ruff-red)](https://github.com/charliermarsh/ruff)
[![Types](https://img.shields.io/badge/types-pyright-blue)](https://github.com/microsoft/pyright)
[![Tests](https://img.shields.io/badge/tests-pytest%20%7C%20tox-yellow)](https://github.com/pytest-dev/pytest/)
[![Coverage](https://img.shields.io/badge/coverage-coveragepy-green)](https://github.com/nedbat/coveragepy)
[![License](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://snyk.io/learn/what-is-mit-license/)
[![Compatibility](https://img.shields.io/pypi/pyversions/manga-dlp)](https://pypi.org/project/manga-dlp/)
---
## Description
A manga download script written in python. It only supports [mangadex.org](https://mangadex.org/) for now. But support
for other sites is planned.
for other sites is _planned™_.
Before downloading a new chapter, the script always checks if there is already a chapter with the same name in the
download directory. If found the chapter is skipped. So you can run the script on a schedule to only download new
chapters without any additional setup.
The default behaiviour is to pack the images to a [cbz archive](https://en.wikipedia.org/wiki/Comic_book_archive). If
you just want the folder with all the pictures use the flag `--nocbz`.
you just want the folder with all the pictures use the flag `--format ""`.
## _Currently_ Supported sites
- [Mangadex.org](https://mangadex.org/)
- [Mangadex.org](https://mangadex.org/)
## Features (not complete)
- Metadata support with [ComicInfo.xml](https://anansi-project.github.io/docs/comicinfo/intro)
- Json caching
- Custom hooks after/before each download
- Custom chapter name format
- Volume support
- Multiple archive formats supported (cbz,cbr,zip,none)
- Language selection
- Download all chapters directly
- And others...
## Usage
@ -124,20 +136,20 @@ verbosity: [mutually_exclusive]
For suggestions for improvement, just open a pull request.
If you want to add support for a new site, there is an api [template file](./contrib/api_template.py) which you can use.
And more infos and tools in the contrib [README.md](contrib/README.md)
If you want to add support for a new site, there is an api [template file](contrib/api_template.py) which you can use.
And more infos and tools are in the contrib [README.md](contrib/README.md)
Otherwise, you can open am issue with the name of the site which you want support for. (not guaranteed to be
implemented)
Otherwise, you can open an issue with the name of the site which you want support for (not guaranteed to be
implemented).
If you encounter any bugs, also just open an issue with a description of the problem.
## TODO's
- <del>Make docker container for easy distribution</del>
--> [Dockerhub](https://hub.docker.com/repository/docker/olofvndrhr/manga-dlp)
- <del>Automate release</del>
--> Done with woodpecker-ci
- <del>Make pypi package</del>
--> Done with release [2.1.7](https://pypi.org/project/manga-dlp/)
- Add more supported sites
- <del>Make docker container for easy distribution</del>
--> [Dockerhub](https://hub.docker.com/r/olofvndrhr/manga-dlp)
- <del>Automate release</del>
--> Done with woodpecker-ci
- <del>Make pypi package</del>
--> Done with release [2.1.7](https://pypi.org/project/manga-dlp/)
- Add more supported sites

View file

@ -1,9 +1,14 @@
from typing import Dict, List, Union
from mangadlp.types import ChapterData,ComicInfo
# api template for manga-dlp
class YourAPI:
"""Your API Class.
Get infos for a manga from example.org
Get infos for a manga from example.org.
Args:
url_uuid (str): URL or UUID of the manga
@ -22,10 +27,8 @@ class YourAPI:
api_base_url = "https://api.mangadex.org"
img_base_url = "https://uploads.mangadex.org"
def __init__(self, url_uuid, language, forcevol):
"""
get infos to initiate class
"""
def __init__(self, url_uuid: str, language: str, forcevol: bool):
"""get infos to initiate class."""
self.api_name = "Your API Name"
self.url_uuid = url_uuid
@ -36,22 +39,24 @@ class YourAPI:
self.manga_uuid = "abc"
self.manga_title = "abc"
self.chapter_list = ["1", "2", "2.1", "5", "10"]
self.manga_chapter_data = { # example data
self.manga_chapter_data: Dict[str, ChapterData] = { # example data
"1": {
"uuid": "abc",
"volume": "1",
"chapter": "1",
"name": "test",
"pages" 2,
},
"2": {
"uuid": "abc",
"volume": "1",
"chapter": "2",
"name": "test",
"pages": 45,
},
}
# or with --forcevol
self.manga_chapter_data = {
self.manga_chapter_data: Dict[str, ChapterData] = {
"1:1": {
"uuid": "abc",
"volume": "1",
@ -66,9 +71,8 @@ class YourAPI:
},
}
def get_chapter_images(chapter: str, download_wait: float) -> list:
"""
Get chapter images as a list (full links)
def get_chapter_images(self, chapter: str, wait_time: float) -> List[str]:
"""Get chapter images as a list (full links).
Args:
chapter: The chapter number (chapter data index)
@ -77,7 +81,6 @@ class YourAPI:
Returns:
The list of urls of the page images
"""
# example
return [
"https://abc.def/image/123.png",
@ -85,10 +88,10 @@ class YourAPI:
"https://abc.def/image/12345.png",
]
def create_metadata(self, chapter: str) -> dict:
"""
Get metadata with correct keys for ComicInfo.xml
Provide as much metadata as possible. empty/false values will be ignored
def create_metadata(self, chapter: str) -> ComicInfo:
"""Get metadata with correct keys for ComicInfo.xml.
Provide as much metadata as possible. empty/false values will be ignored.
Args:
chapter: The chapter number (chapter data index)
@ -96,7 +99,6 @@ class YourAPI:
Returns:
The metadata as a dict
"""
# metadata types. have to be valid
# {key: (type, default value, valid values)}
{
@ -155,7 +157,7 @@ class YourAPI:
# example
return {
"Volume": "abc",
"Volume": 1,
"LanguageISO": "en",
"Title": "test",
}

View file

@ -14,9 +14,7 @@ hatchling>=1.11.0
pytest>=7.0.0
coverage>=6.3.1
black>=22.1.0
isort>=5.10.0
pylint>=2.13.0
mypy>=0.940
tox>=3.24.5
autoflake>=1.4
pylama>=8.3.8
ruff>=0.0.247
pyright>=1.1.294

View file

@ -17,30 +17,42 @@ Code Analysis
Meta
[![Code style](https://img.shields.io/badge/code%20style-black-black)](https://github.com/psf/black)
[![Linter](https://img.shields.io/badge/linter-pylint-yellowgreen)](https://pylint.pycqa.org/en/latest/)
[![Types](https://img.shields.io/badge/types-mypy-blue)](https://github.com/python/mypy)
[![Imports](https://img.shields.io/badge/imports-isort-ef8336.svg)](https://github.com/pycqa/isort)
[![Linter](https://img.shields.io/badge/linter-ruff-red)](https://github.com/charliermarsh/ruff)
[![Types](https://img.shields.io/badge/types-pyright-blue)](https://github.com/microsoft/pyright)
[![Tests](https://img.shields.io/badge/tests-pytest%20%7C%20tox-yellow)](https://github.com/pytest-dev/pytest/)
[![Coverage](https://img.shields.io/badge/coverage-coveragepy-green)](https://github.com/nedbat/coveragepy)
[![License](https://img.shields.io/badge/license-MIT-9400d3.svg)](https://snyk.io/learn/what-is-mit-license/)
[![Compatibility](https://img.shields.io/pypi/pyversions/manga-dlp)](https://pypi.org/project/manga-dlp/)
---
## Description
A manga download script written in python. It only supports [mangadex.org](https://mangadex.org/) for now. But support
for other sites is planned.
for other sites is _planned™_.
Before downloading a new chapter, the script always checks if there is already a chapter with the same name in the
download directory. If found the chapter is skipped. So you can run the script on a schedule to only download new
chapters without any additional setup.
The default behaiviour is to pack the images to a [cbz archive](https://en.wikipedia.org/wiki/Comic_book_archive). If
you just want the folder with all the pictures use the flag `--nocbz`.
you just want the folder with all the pictures use the flag `--format ""`.
## _Currently_ Supported sites
- [Mangadex.org](https://mangadex.org/)
- [Mangadex.org](https://mangadex.org/)
## Features (not complete)
- Metadata support with [ComicInfo.xml](https://anansi-project.github.io/docs/comicinfo/intro)
- Json caching
- Custom hooks after/before each download
- Custom chapter name format
- Volume support
- Multiple archive formats supported (cbz,cbr,zip,none)
- Language selection
- Download all chapters directly
- And others...
## Usage
@ -82,7 +94,7 @@ mangadlp <args> # call script directly
### With docker
See the docker [README](docker/)
See the docker [README](https://manga-dlp.ivn.sh/docker/)
## Options
@ -122,22 +134,20 @@ verbosity: [mutually_exclusive]
For suggestions for improvement, just open a pull request.
If you want to add support for a new site, there is an
api [template file](https://github.com/olofvndrhr/manga-dlp/blob/master/contrib/api_template.py) which you can use.
And more infos and tools in the
contrib [README.md](https://github.com/olofvndrhr/manga-dlp/blob/master/contrib/README.md)
If you want to add support for a new site, there is an api [template file](https://github.com/olofvndrhr/manga-dlp/tree/master/contrib/api_template.py) which you can use.
And more infos and tools are in the contrib [README.md](https://github.com/olofvndrhr/manga-dlp/tree/master/contrib/README.md)
Otherwise, you can open am issue with the name of the site which you want support for. (not guaranteed to be
implemented)
Otherwise, you can open an issue with the name of the site which you want support for (not guaranteed to be
implemented).
If you encounter any bugs, also just open an issue with a description of the problem.
## TODO's
- <del>Make docker container for easy distribution</del>
--> [Dockerhub](https://hub.docker.com/repository/docker/olofvndrhr/manga-dlp)
- <del>Automate release</del>
--> Done with woodpecker-ci
- <del>Make pypi package</del>
--> Done with release [2.1.7](https://pypi.org/project/manga-dlp/)
- Add more supported sites
- <del>Make docker container for easy distribution</del>
--> [Dockerhub](https://hub.docker.com/r/olofvndrhr/manga-dlp)
- <del>Automate release</del>
--> Done with woodpecker-ci
- <del>Make pypi package</del>
--> Done with release [2.1.7](https://pypi.org/project/manga-dlp/)
- Add more supported sites

View file

@ -68,45 +68,39 @@ create_venv:
@python3 -m venv venv
install_deps:
@echo "installing dependencies"
@pip3 install -r requirements.txt
install_deps_dev:
@echo "installing dependencies"
@pip3 install -r contrib/requirements_dev.txt
test_shfmt:
@find . -type f \( -name "**.sh" -and -not -path "./venv/*" -and -not -path "./.tox/*" \) -exec shfmt -d -i 4 -bn -ci -sr "{}" \+;
@find . -type f \( -name "**.sh" -and -not -path "./.**" -and -not -path "./venv**" \) -exec shfmt -d -i 4 -bn -ci -sr "{}" \+;
test_black:
@python3 -m black --check --diff .
@python3 -m black --check --diff mangadlp/
test_isort:
@python3 -m isort --check-only --diff .
test_pyright:
@python3 -m pyright mangadlp/
test_mypy:
@python3 -m mypy --install-types --non-interactive mangadlp/
test_ruff:
@python3 -m ruff --diff mangadlp/
test_ci_conf:
@woodpecker-cli lint .woodpecker/
test_pytest:
@python3 -m tox -e basic
test_autoflake:
@python3 -m autoflake --remove-all-unused-imports -r -v mangadlp/
@python3 -m autoflake --check --remove-all-unused-imports -r -v mangadlp/
test_pylama:
@python3 -m pylama --options tox.ini mangadlp/
test_pylint:
@python3 -m pylint --fail-under 9 mangadlp/
test_coverage:
@python3 -m tox -e coverage
test_tox:
@python3 -m tox
test_tox_coverage:
@python3 -m tox -e coverage
test_build:
@python3 -m hatch build
test_ci_conf:
@woodpecker-cli lint .woodpecker/
@python3 -m hatch build --clean
test_docker_build:
@docker build . -f docker/Dockerfile.amd64 -t manga-dlp:test
@ -123,11 +117,8 @@ lint:
-just test_ci_conf
just test_shfmt
just test_black
just test_isort
just test_mypy
just test_autoflake
just test_pylama
just test_pylint
just test_pyright
just test_ruff
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"
tests:
@ -135,11 +126,8 @@ tests:
-just test_ci_conf
just test_shfmt
just test_black
just test_isort
just test_mypy
just test_autoflake
just test_pylama
just test_pylint
just test_pyright
just test_ruff
just test_pytest
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"
@ -148,13 +136,10 @@ tests_full:
-just test_ci_conf
just test_shfmt
just test_black
just test_isort
just test_mypy
just test_autoflake
just test_pylama
just test_pylint
just test_pyright
just test_ruff
just test_build
just test_tox
just test_tox_coverage
just test_coverage
just test_docker_build
@echo -e "\n\033[0;32m=== ALL DONE ===\033[0m\n"

View file

@ -1 +1 @@
__version__ = "2.3.0"
__version__ = "2.3.1"

View file

@ -1,15 +1,18 @@
import re
from time import sleep
from typing import Any, Dict, List
import requests
from loguru import logger as log
from mangadlp import utils
from mangadlp.types import ChapterData, ComicInfo
class Mangadex:
"""Mangadex API Class.
Get infos for a manga from mangadex.org
Get infos for a manga from mangadex.org.
Args:
url_uuid (str): URL or UUID of the manga
@ -64,10 +67,10 @@ class Mangadex:
log.error("No valid UUID found")
raise exc
return uuid
return uuid # pyright:ignore
# 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}")
counter = 1
while counter <= 3:
@ -84,12 +87,14 @@ class Mangadex:
counter += 1
else:
break
response_body: Dict[str, Dict[str, Any]] = response.json() # pyright:ignore
# check if manga exists
if response.json()["result"] != "ok":
if response_body["result"] != "ok": # type:ignore
log.error("Manga not found")
raise KeyError
return response.json()["data"]
return response_body["data"]
# get the title of the manga (and fix the filename)
def get_manga_title(self) -> str:
@ -111,7 +116,7 @@ class Mangadex:
if item.get(self.language):
alt_title = item
break
title = alt_title[self.language]
title = alt_title[self.language] # pyright:ignore
except (KeyError, UnboundLocalError):
log.warning(
"Manga title also not found in alt titles. Falling back to english title"
@ -132,7 +137,7 @@ class Mangadex:
timeout=10,
)
try:
total_chapters = r.json()["total"]
total_chapters: int = r.json()["total"]
except Exception as exc:
log.error(
"Error retrieving the chapters list. Did you specify a valid language code?"
@ -146,13 +151,13 @@ class Mangadex:
return total_chapters
# get chapter data like name, uuid etc
def get_chapter_data(self) -> dict:
def get_chapter_data(self) -> Dict[str, ChapterData]:
log.debug(f"Getting chapter data for: {self.manga_uuid}")
api_sorting = "order[chapter]=asc&order[volume]=asc"
# check for chapters in specified lang
total_chapters = self.check_chapter_lang()
chapter_data = {}
chapter_data: dict[str, ChapterData] = {}
last_volume, last_chapter = ("", "")
offset = 0
while offset < total_chapters: # if more than 500 chapters
@ -160,8 +165,9 @@ class Mangadex:
f"{self.api_base_url}/manga/{self.manga_uuid}/feed?{api_sorting}&limit=500&offset={offset}&{self.api_additions}",
timeout=10,
)
for chapter in r.json()["data"]:
attributes: dict = chapter["attributes"]
response_body: Dict[str, Any] = r.json()
for chapter in response_body["data"]:
attributes: Dict[str, Any] = chapter["attributes"]
# chapter infos from feed
chapter_num: str = attributes.get("chapter") or ""
chapter_vol: str = attributes.get("volume") or ""
@ -203,7 +209,7 @@ class Mangadex:
return chapter_data
# 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}")
athome_url = f"{self.api_base_url}/at-home/server"
chapter_uuid = self.manga_chapter_data[chapter]["uuid"]
@ -237,11 +243,11 @@ class Mangadex:
if api_error:
return []
chapter_hash = api_data["chapter"]["hash"]
chapter_img_data = api_data["chapter"]["data"]
chapter_hash = api_data["chapter"]["hash"] # pyright:ignore
chapter_img_data = api_data["chapter"]["data"] # pyright:ignore
# get list of image urls
image_urls = []
image_urls: List[str] = []
for image in chapter_img_data:
image_urls.append(f"{self.img_base_url}/data/{chapter_hash}/{image}")
@ -250,9 +256,9 @@ class Mangadex:
return image_urls
# 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}")
chapter_list = []
chapter_list: List[str] = []
for data in self.manga_chapter_data.values():
chapter_number: str = data["chapter"]
volume_number: str = data["volume"]
@ -263,15 +269,15 @@ class Mangadex:
return chapter_list
def create_metadata(self, chapter: str) -> dict:
def create_metadata(self, chapter: str) -> ComicInfo:
log.info("Creating metadata from api")
chapter_data = self.manga_chapter_data[chapter]
try:
volume = int(chapter_data.get("volume"))
volume = int(chapter_data["volume"])
except (ValueError, TypeError):
volume = None
metadata = {
metadata: ComicInfo = {
"Volume": volume,
"Number": chapter_data.get("chapter"),
"PageCount": chapter_data.get("pages"),

View file

@ -1,7 +1,7 @@
import re
import shutil
from pathlib import Path
from typing import Any, Union
from typing import Any, Dict, List, Tuple, Union
from loguru import logger as log
@ -10,11 +10,12 @@ from mangadlp.api.mangadex import Mangadex
from mangadlp.cache import CacheDB
from mangadlp.hooks import run_hook
from mangadlp.metadata import write_metadata
from mangadlp.types import ChapterData
from mangadlp.utils import get_file_format
def match_api(url_uuid: str) -> type:
"""Match the correct api class from a string
"""Match the correct api class from a string.
Args:
url_uuid: url/uuid to check
@ -22,9 +23,8 @@ def match_api(url_uuid: str) -> type:
Returns:
The class of the API to use
"""
# apis to check
apis: list[tuple[str, re.Pattern, type]] = [
apis: List[Tuple[str, re.Pattern[str], type]] = [
(
"mangadex.org",
re.compile(
@ -53,6 +53,7 @@ def match_api(url_uuid: str) -> type:
class MangaDLP:
"""Download Mangas from supported sites.
After initialization, start the script with the function get_manga().
Args:
@ -108,7 +109,7 @@ class MangaDLP:
self.chapter_post_hook_cmd = chapter_post_hook_cmd
self.cache_path = cache_path
self.add_metadata = add_metadata
self.hook_infos: dict = {}
self.hook_infos: Dict[str, Any] = {}
# prepare everything
self._prepare()
@ -226,7 +227,7 @@ class MangaDLP:
skipped_chapters: list[Any] = []
error_chapters: list[Any] = []
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")
continue
@ -240,7 +241,7 @@ class MangaDLP:
skipped_chapters.append(chapter)
# update cache
if self.cache_path:
cache.add_chapter(chapter)
cache.add_chapter(chapter) # pyright:ignore
continue
except Exception:
# skip download/packing due to an error
@ -273,7 +274,7 @@ class MangaDLP:
# update cache
if self.cache_path:
cache.add_chapter(chapter)
cache.add_chapter(chapter) # pyright:ignore
# start chapter post hook
run_hook(
@ -310,7 +311,7 @@ class MangaDLP:
# once called per chapter
def get_chapter(self, chapter: str) -> Path:
# get chapter infos
chapter_infos: dict = self.api.manga_chapter_data[chapter]
chapter_infos: ChapterData = self.api.manga_chapter_data[chapter]
log.debug(f"Chapter infos: {chapter_infos}")
# get image urls for chapter
@ -352,7 +353,7 @@ class MangaDLP:
log.debug(f"Filename: '{chapter_filename}'")
# 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
chapter_archive_path = Path(f"{chapter_path}{self.file_format}")

View file

@ -26,12 +26,14 @@ class CacheDB:
if not self.db_data.get(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"):
self.db_uuid_data.update({"name": self.name})
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:
if self.db_path.exists():
@ -44,11 +46,11 @@ class CacheDB:
log.error("Can't create db-file")
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}")
try:
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:
log.error("Can't load cache-db")
raise exc
@ -73,7 +75,7 @@ class CacheDB:
raise exc
def sort_chapters(chapters: list) -> List[str]:
def sort_chapters(chapters: List[str]) -> List[str]:
try:
sorted_list = sorted(chapters, key=float)
except Exception:

View file

@ -1,5 +1,6 @@
import sys
from pathlib import Path
from typing import Any, List
import click
from click_option_group import (
@ -15,7 +16,7 @@ from mangadlp.logger import prepare_logger
# 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:
return []
@ -38,8 +39,8 @@ def readin_list(_ctx, _param, value) -> list:
@click.help_option()
@click.version_option(version=__version__, package_name="manga-dlp")
# manga selection
@optgroup.group("source", cls=RequiredMutuallyExclusiveOptionGroup)
@optgroup.option(
@optgroup.group("source", cls=RequiredMutuallyExclusiveOptionGroup) # type: ignore
@optgroup.option( # type: ignore
"-u",
"--url",
"--uuid",
@ -49,19 +50,19 @@ def readin_list(_ctx, _param, value) -> list:
show_default=True,
help="URL or UUID of the manga",
)
@optgroup.option(
@optgroup.option( # type: ignore
"--read",
"read_mangas",
is_eager=True,
callback=readin_list,
type=click.Path(exists=True, dir_okay=False),
type=click.Path(exists=True, dir_okay=False, path_type=str),
default=None,
show_default=True,
help="Path of file with manga links to download. One per line",
)
# logging options
@optgroup.group("verbosity", cls=MutuallyExclusiveOptionGroup)
@optgroup.option(
@optgroup.group("verbosity", cls=MutuallyExclusiveOptionGroup) # type: ignore
@optgroup.option( # type: ignore
"--loglevel",
"verbosity",
type=int,
@ -69,7 +70,7 @@ def readin_list(_ctx, _param, value) -> list:
show_default=True,
help="Custom log level",
)
@optgroup.option(
@optgroup.option( # type: ignore
"--warn",
"verbosity",
flag_value=30,
@ -77,7 +78,7 @@ def readin_list(_ctx, _param, value) -> list:
show_default=True,
help="Only log warnings and higher",
)
@optgroup.option(
@optgroup.option( # type: ignore
"--debug",
"verbosity",
flag_value=10,
@ -227,12 +228,8 @@ def readin_list(_ctx, _param, value) -> list:
help="Enable/disable creation of metadata via ComicInfo.xml",
)
@click.pass_context
def main(ctx: click.Context, **kwargs) -> None:
"""
Script to download mangas from various sites
"""
def main(ctx: click.Context, **kwargs: Any) -> None:
"""Script to download mangas from various sites."""
url_uuid: str = kwargs.pop("url_uuid")
read_mangas: list[str] = kwargs.pop("read_mangas")
verbosity: int = kwargs.pop("verbosity")

View file

@ -2,7 +2,7 @@ import logging
import shutil
from pathlib import Path
from time import sleep
from typing import Union
from typing import List, Union
import requests
from loguru import logger as log
@ -12,7 +12,7 @@ from mangadlp import utils
# download images
def download_chapter(
image_urls: list,
image_urls: List[str],
chapter_path: Union[str, Path],
download_wait: float,
) -> None:
@ -48,8 +48,8 @@ def download_chapter(
# write image
try:
with image_path.open("wb") as file:
r.raw.decode_content = True
shutil.copyfileobj(r.raw, file)
r.raw.decode_content = True # pyright:ignore
shutil.copyfileobj(r.raw, file) # pyright:ignore
except Exception as exc:
log.error("Can't write file")
raise exc

View file

@ -1,11 +1,15 @@
import os
import subprocess
from typing import Any
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 with subprocess.run and add kwargs to the environment.
Args:
command (str): command to run
hook_type (str): type of the hook
@ -14,7 +18,6 @@ def run_hook(command: str, hook_type: str, **kwargs) -> int:
Returns:
exit_code (int): exit code of command
"""
# check if hook commands are empty
if not command or command == "None":
log.debug(f"Hook '{hook_type}' empty. Not running")

View file

@ -1,5 +1,6 @@
import logging
import sys
from typing import Any, Dict
from loguru import logger
@ -8,11 +9,9 @@ LOGURU_FMT = "{time:%Y-%m-%dT%H:%M:%S%z} | <level>[{level: <7}]</level> [{name:
# from loguru docs
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
try:
level = logger.level(record.levelname).name
@ -21,8 +20,8 @@ class InterceptHandler(logging.Handler):
# Find caller from where originated the logged message
frame, depth = logging.currentframe(), 2
while frame.f_code.co_filename == logging.__file__:
frame = frame.f_back
while frame.f_code.co_filename == logging.__file__: # pyright:ignore
frame = frame.f_back # type: ignore
depth += 1
logger.opt(depth=depth, exception=record.exc_info).log(
@ -32,7 +31,7 @@ class InterceptHandler(logging.Handler):
# init logger with format and log level
def prepare_logger(loglevel: int = 20) -> None:
config: dict = {
config: Dict[str, Any] = {
"handlers": [
{
"sink": sys.stdout,

View file

@ -1,14 +1,18 @@
from pathlib import Path
from typing import Any, Dict, Tuple
from typing import Any, Dict, List, Tuple, Union
import xmltodict
from loguru import logger as log
from mangadlp.types import ComicInfo
METADATA_FILENAME = "ComicInfo.xml"
METADATA_TEMPLATE = Path("mangadlp/metadata/ComicInfo_v2.0.xml")
# define metadata types, defaults and valid values. an empty list means no value check
# {key: (type, default value, valid values)}
METADATA_TYPES: Dict[str, Tuple[type, Any, list]] = {
METADATA_TYPES: Dict[
str, Tuple[Any, Union[str, int, None], List[Union[str, int, None]]]
] = {
"Title": (str, None, []),
"Series": (str, None, []),
"Number": (str, None, []),
@ -59,10 +63,10 @@ METADATA_TYPES: Dict[str, Tuple[type, Any, list]] = {
}
def validate_metadata(metadata_in: dict) -> Dict[str, dict]:
def validate_metadata(metadata_in: ComicInfo) -> Dict[str, ComicInfo]:
log.info("Validating metadata")
metadata_valid: dict[str, dict] = {"ComicInfo": {}}
metadata_valid: dict[str, ComicInfo] = {"ComicInfo": {}}
for key, value in METADATA_TYPES.items():
metadata_type, metadata_default, metadata_validation = value
@ -75,7 +79,7 @@ def validate_metadata(metadata_in: dict) -> Dict[str, dict]:
# check if metadata key is available
try:
md_to_check = metadata_in[key]
md_to_check: Union[str, int, None] = metadata_in[key]
except KeyError:
continue
# check if provided metadata item is empty
@ -84,7 +88,7 @@ def validate_metadata(metadata_in: dict) -> Dict[str, dict]:
# check if metadata type is correct
log.debug(f"Key:{key} -> value={type(md_to_check)} -> check={metadata_type}")
if not isinstance(md_to_check, metadata_type): # noqa
if not isinstance(md_to_check, metadata_type):
log.warning(
f"Metadata has wrong type: {key}:{metadata_type} -> {md_to_check}"
)
@ -104,8 +108,8 @@ def validate_metadata(metadata_in: dict) -> Dict[str, dict]:
return metadata_valid
def write_metadata(chapter_path: Path, metadata: dict) -> None:
if metadata["Format"] == "pdf":
def write_metadata(chapter_path: Path, metadata: ComicInfo) -> None:
if metadata["Format"] == "pdf": # pyright:ignore
log.warning("Can't add metadata for pdf format. Skipping")
return

50
mangadlp/types.py Normal file
View file

@ -0,0 +1,50 @@
from typing import Optional, TypedDict
class ComicInfo(TypedDict, total=False):
"""ComicInfo.xml basic types.
Validation is done via metadata.validate_metadata()
All valid types and values are specified in metadata.METADATA_TYPES
"""
Title: Optional[str]
Series: Optional[str]
Number: Optional[str]
Count: Optional[int]
Volume: Optional[int]
AlternateSeries: Optional[str]
AlternateNumber: Optional[str]
AlternateCount: Optional[int]
Summary: Optional[str]
Notes: Optional[str]
Year: Optional[int]
Month: Optional[int]
Day: Optional[int]
Writer: Optional[str]
Colorist: Optional[str]
Publisher: Optional[str]
Genre: Optional[str]
Web: Optional[str]
PageCount: Optional[int]
LanguageISO: Optional[str]
Format: Optional[str]
BlackAndWhite: Optional[str]
Manga: Optional[str]
ScanInformation: Optional[str]
SeriesGroup: Optional[str]
AgeRating: Optional[str]
CommunityRating: Optional[int]
class ChapterData(TypedDict):
"""Basic chapter-data types.
All values have to be provided.
"""
uuid: str
volume: str
chapter: str
name: str
pages: int

View file

@ -24,7 +24,7 @@ def make_archive(chapter_path: Path, file_format: str) -> None:
def make_pdf(chapter_path: Path) -> None:
try:
import img2pdf # pylint: disable=import-outside-toplevel
import img2pdf # pylint: disable=import-outside-toplevel # pyright:ignore
except Exception as exc:
log.error("Cant import img2pdf. Please install it first")
raise exc
@ -34,14 +34,14 @@ def make_pdf(chapter_path: Path) -> None:
for file in chapter_path.iterdir():
images.append(str(file))
try:
pdf_path.write_bytes(img2pdf.convert(images))
pdf_path.write_bytes(img2pdf.convert(images)) # pyright:ignore
except Exception as exc:
log.error("Can't create '.pdf' archive")
raise exc
# 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
chapter_list: list[str] = []
for chapter in chapters.split(","):

View file

@ -1,5 +1,5 @@
[build-system]
requires = ["hatchling>=1.11.0"]
requires = ["hatchling>=1.11.0"]
build-backend = "hatchling.build"
[project]
@ -9,14 +9,8 @@ description = "A cli manga downloader"
readme = "README.md"
license = "MIT"
requires-python = ">=3.8"
authors = [
{ name = "Ivan Schaller", email = "ivan@schaller.sh" },
]
keywords = [
"manga",
"downloader",
"mangadex",
]
authors = [{ name = "Ivan Schaller", email = "ivan@schaller.sh" }]
keywords = ["manga", "downloader", "mangadex"]
classifiers = [
"License :: OSI Approved :: MIT License",
"Natural Language :: English",
@ -30,17 +24,17 @@ dependencies = [
"loguru>=0.6.0",
"click>=8.1.3",
"click-option-group>=0.5.5",
"xmltodict>=0.13.0"
"xmltodict>=0.13.0",
]
[project.urls]
Homepage = "https://github.com/olofvndrhr/manga-dlp"
History = "https://github.com/olofvndrhr/manga-dlp/commits/master"
Tracker = "https://github.com/olofvndrhr/manga-dlp/issues"
Source = "https://github.com/olofvndrhr/manga-dlp"
History = "https://github.com/olofvndrhr/manga-dlp/commits/master"
Tracker = "https://github.com/olofvndrhr/manga-dlp/issues"
Source = "https://github.com/olofvndrhr/manga-dlp"
[project.scripts]
mangadlp = "mangadlp.cli:main"
mangadlp = "mangadlp.cli:main"
manga-dlp = "mangadlp.cli:main"
[tool.hatch.version]
@ -69,44 +63,86 @@ dependencies = [
"pytest>=7.0.0",
"coverage>=6.3.1",
"black>=22.1.0",
"isort>=5.10.0",
"pylint>=2.13.0",
"mypy>=0.940",
"tox>=3.24.5",
"autoflake>=1.4",
"pylama>=8.3.8",
"ruff>=0.0.247",
]
[tool.isort]
py_version = 39
skip_gitignore = true
line_length = 88
profile = "black"
multi_line_output = 3
include_trailing_comma = true
use_parentheses = true
# pyright
[tool.mypy]
python_version = "3.9"
disallow_untyped_defs = false
follow_imports = "normal"
ignore_missing_imports = true
warn_no_return = false
warn_unused_ignores = true
show_error_context = true
show_column_numbers = true
show_error_codes = true
pretty = true
no_implicit_optional = false
[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.9"
reportUnnecessaryTypeIgnoreComment = true
reportShadowedImports = true
reportUnusedExpression = true
reportMatchNotExhaustive = true
# venvPath = "."
# venv = "venv"
# ruff
[tool.ruff]
target-version = "py39"
select = [
"E", # pycodetyle err
"W", # pycodetyle warn
"D", # pydocstyle
"C90", # mccabe
"I", # isort
"PLE", # pylint err
"PLW", # pylint warn
"PLC", # pylint convention
"PLR", # pylint refactor
"F", # pyflakes
"RUF", # ruff specific
]
line-length = 88
fix = true
show-fixes = true
format = "grouped"
ignore-init-module-imports = true
respect-gitignore = true
ignore = ["E501", "D103", "D100", "D102", "PLR2004"]
exclude = [
".direnv",
".git",
".mypy_cache",
".ruff_cache",
".svn",
".venv",
"venv",
"__pypackages__",
"build",
"dist",
"venv",
]
[tool.ruff.per-file-ignores]
"__init__.py" = ["D104"]
[tool.ruff.pylint]
max-args = 10
[tool.ruff.mccabe]
max-complexity = 10
[tool.ruff.pydocstyle]
convention = "google"
[tool.ruff.pycodestyle]
max-doc-length = 88
# pytest
[tool.pytest.ini_options]
pythonpath = [
"."
]
pythonpath = ["."]
# coverage
[tool.coverage.run]
source = ["mangadlp"]
branch = true
source = ["mangadlp"]
branch = true
command_line = "-m pytest --exitfirst"
[tool.coverage.report]
@ -127,12 +163,3 @@ exclude_lines = [
"@(abc.)?abstractmethod",
]
ignore_errors = true
[tool.pylint.main]
py-version = "3.9"
[tool.pylint.logging]
logging-modules = ["logging", "loguru"]
disable = "C0301, C0114, C0116, W0703, R0902, R0913, E0401, W1203"
good-names = "r"
logging-format-style = "new"

View file

@ -16,8 +16,6 @@ def test_read_and_url():
def test_no_read_and_url():
url_uuid = "https://mangadex.org/title/0aea9f43-d4a9-4bf7-bebc-550a512f9b95/shikimori-s-not-just-a-cutie"
link_file = "tests/testfile.txt"
language = "en"
chapters = "1"
file_format = "cbz"
@ -30,7 +28,6 @@ def test_no_read_and_url():
def test_no_chaps():
url_uuid = "https://mangadex.org/title/0aea9f43-d4a9-4bf7-bebc-550a512f9b95/shikimori-s-not-just-a-cutie"
language = "en"
chapters = ""
file_format = "cbz"
download_path = "tests"
command_args = f"-u {url_uuid} -l {language} --path {download_path} --format {file_format} --debug"

View file

@ -40,7 +40,7 @@ def test_manga_pre_hook(wait_10s):
manga_pre_hook,
]
script_path = "manga-dlp.py"
command = ["python3", script_path] + command_args
command = ["python3", script_path, *command_args]
assert subprocess.call(command) == 0
assert hook_file.is_file()
@ -72,7 +72,7 @@ def test_manga_post_hook(wait_10s):
manga_post_hook,
]
script_path = "manga-dlp.py"
command = ["python3", script_path] + command_args
command = ["python3", script_path, *command_args]
assert subprocess.call(command) == 0
assert hook_file.is_file()
@ -104,7 +104,7 @@ def test_chapter_pre_hook(wait_10s):
chapter_pre_hook,
]
script_path = "manga-dlp.py"
command = ["python3", script_path] + command_args
command = ["python3", script_path, *command_args]
assert subprocess.call(command) == 0
assert hook_file.is_file()
@ -136,7 +136,7 @@ def test_chapter_post_hook(wait_10s):
chapter_post_hook,
]
script_path = "manga-dlp.py"
command = ["python3", script_path] + command_args
command = ["python3", script_path, *command_args]
assert subprocess.call(command) == 0
assert hook_file.is_file()
@ -176,7 +176,7 @@ def test_all_hooks(wait_10s):
chapter_post_hook,
]
script_path = "manga-dlp.py"
command = ["python3", script_path] + command_args
command = ["python3", script_path, *command_args]
assert subprocess.call(command) == 0
assert Path("tests/manga-pre2.txt").is_file()

View file

@ -6,7 +6,7 @@ from mangadlp.cache import CacheDB
def test_cache_creation():
cache_file = Path("cache.json")
cache = CacheDB(cache_file, "abc", "en", "test")
CacheDB(cache_file, "abc", "en", "test")
assert cache_file.exists()
cache_file.unlink()

View file

@ -133,7 +133,7 @@ def test_metadata_chapter_validity(wait_20s):
schema = xmlschema.XMLSchema("mangadlp/metadata/ComicInfo_v2.0.xsd")
script_path = "manga-dlp.py"
command = ["python3", script_path] + command_args
command = ["python3", script_path, *command_args]
assert subprocess.call(command) == 0
assert metadata_path.is_file()

View file

@ -56,7 +56,7 @@ def test_alt_title_fallback():
forcevol = False
test = Mangadex(url_uuid, language, forcevol)
assert test.manga_title == "Iruma à lécole des démons"
assert test.manga_title == "Iruma à lécole des démons" # noqa
def test_chapter_infos():
@ -206,7 +206,6 @@ def test_get_chapter_images():
test = Mangadex(url_uuid, language, forcevol)
img_base_url = "https://uploads.mangadex.org"
chapter_hash = "0752bc5db298beff6b932b9151dd8437"
chapter_uuid = "e86ec2c4-c5e4-4710-bfaa-7604f00939c7"
chapter_num = "1"
test_list = [
f"{img_base_url}/data/{chapter_hash}/x1-0deb4c9bfedd5be49e0a90cfb17cf343888239898c9e7451d569c0b3ea2971f4.jpg",

View file

@ -24,8 +24,3 @@ commands =
coverage erase
coverage run
coverage xml -i
[pylama]
format = pycodestyle
linters = mccabe,pycodestyle,pyflakes
ignore = E501,C901,C0301