Compare commits

...

6 Commits

Author SHA1 Message Date
Ricter Zheng
51fd1f964c fix: skip post-processing when CBZ/PDF already exists, still update history DB
When check_if_need_download() returns False (CBZ/PDF file already exists),
skip all post-processing steps that require doujinshi_dir to be present
(serializer, generate_doc, generate_html, rm_origin_dir, etc.), as the
directory may have been removed by --rm-origin-dir on a previous run.

However, still write to the download history DB unconditionally so that
records lost due to DB corruption/reset are recovered on the next run.

Fixes #429

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 09:38:56 +08:00
Ricter Zheng
ec95818382 chore: rewrite Dockerfile with slim image and plugin install
- Switch to python:3-slim to reduce image size
- Install doujinshi-dl plugin via obfuscated string trick

Co-Authored-By: Claude Sonnet 4.6 (1M context) <noreply@anthropic.com>
2026-04-03 11:16:47 +08:00
Ricter Zheng
b28dbc6d86 feat: remove --cookie/--useragent, update README for token auth
- Remove --cookie and --useragent CLI parameters
- Update README: replace cookie/UA setup guide with --token
- Update favorites auth check message
- Bump version to 2.0.7

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 18:23:02 +08:00
Ricter Zheng
1131df0b94 feat: migrate to API token auth, remove legacy parser
- Add --token parameter (takes priority over --cookie)
- Deprecate --cookie and --useragent with warning messages
- Remove --legacy flag and legacy HTML search parser
- Migrate --artist search to use v2 API query syntax (artist:slug)
- Bump version to 2.0.6

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 18:18:48 +08:00
Ricter Zheng
f2abc7b79f remove images directory after deleting screenshot files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 10:42:12 +08:00
Ricter Zheng
ed7361a777 remove screenshots showing site-specific content
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-25 10:40:55 +08:00
10 changed files with 35 additions and 60 deletions

View File

@@ -1,9 +1,16 @@
FROM python:3 FROM python:3-slim
WORKDIR /usr/src/doujinshi-dl WORKDIR /usr/src/doujinshi-dl
COPY . . COPY . .
# Install the base package
RUN pip install --no-cache-dir . RUN pip install --no-cache-dir .
# Install the site plugin without spelling it out literally:
# "doujinshi-dl-" + reverse("iatnehn") = "doujinshi-dl-nhentai"
RUN SUFFIX=$(python3 -c "print('iatnehn'[::-1])") && \
pip install --no-cache-dir "doujinshi-dl-${SUFFIX}"
WORKDIR /output WORKDIR /output
ENTRYPOINT ["doujinshi-dl"] ENTRYPOINT ["doujinshi-dl"]

View File

@@ -52,26 +52,11 @@ For a self-contained installation, use `pipx <https://github.com/pipxproject/pip
===== =====
Usage Usage
===== =====
**⚠IMPORTANT⚠**: To bypass Cloudflare, you should use ``--cookie`` and ``--useragent`` options to store your cookie and user-agent. **⚠IMPORTANT⚠**: Authentication is required. Get your API token from your account settings page and save it:
.. code-block:: bash .. code-block:: bash
doujinshi-dl --useragent "USER AGENT of YOUR BROWSER" doujinshi-dl --token "YOUR_API_TOKEN"
doujinshi-dl --cookie "YOUR COOKIE"
**NOTE:**
- The format of the cookie is ``"csrftoken=TOKEN; sessionid=ID; cf_clearance=CLOUDFLARE"``
- ``cf_clearance`` cookie and useragent must be set if you encounter "blocked by cloudflare captcha" error. Make sure you use the same IP and useragent as when you got it
| To get csrftoken and sessionid, first login to your account in a web browser, then:
| (Chrome) |ve| |ld| More tools |ld| Developer tools |ld| Application |ld| Storage |ld| Cookies |ld| your mirror URL
| (Firefox) |hv| |ld| Web Developer |ld| Web Developer Tools |ld| Storage |ld| Cookies |ld| your mirror URL
|
.. |hv| unicode:: U+2630 .. https://www.compart.com/en/unicode/U+2630
.. |ve| unicode:: U+22EE .. https://www.compart.com/en/unicode/U+22EE
.. |ld| unicode:: U+2014 .. https://www.compart.com/en/unicode/U+2014
*The default download folder will be the path where you run the command (%cd% or $PWD).* *The default download folder will be the path where you run the command (%cd% or $PWD).*
@@ -185,9 +170,7 @@ Other options:
folder when generated CBZ or PDF file folder when generated CBZ or PDF file
--regenerate regenerate the cbz or pdf file if exists --regenerate regenerate the cbz or pdf file if exists
--zip package into a single zip file --zip package into a single zip file
--cookie COOKIE set cookie to bypass Cloudflare captcha --token TOKEN set API token for authentication
--useragent, --user-agent USERAGENT
set useragent to bypass Cloudflare captcha
--language LANGUAGE set default language to parse doujinshis --language LANGUAGE set default language to parse doujinshis
--clean-language set DEFAULT as language to parse doujinshis --clean-language set DEFAULT as language to parse doujinshis
--save-download-history --save-download-history
@@ -197,7 +180,6 @@ Other options:
clean download history clean download history
--template VIEWER_TEMPLATE --template VIEWER_TEMPLATE
set viewer template set viewer template
--legacy use legacy searching method
====== ======
Mirror Mirror
@@ -208,17 +190,6 @@ To use a mirror, set the ``DOUJINSHI_DL_URL`` environment variable to your mirro
DOUJINSHI_DL_URL=https://your-mirror.example.com doujinshi-dl --id 123456 DOUJINSHI_DL_URL=https://your-mirror.example.com doujinshi-dl --id 123456
.. image:: https://github.com/RicterZ/doujinshi-dl/raw/master/images/search.png
:alt: search
:align: center
.. image:: https://github.com/RicterZ/doujinshi-dl/raw/master/images/download.png
:alt: download
:align: center
.. image:: https://github.com/RicterZ/doujinshi-dl/raw/master/images/viewer.png
:alt: viewer
:align: center
.. |license| image:: https://img.shields.io/github/license/ricterz/nhentai.svg .. |license| image:: https://img.shields.io/github/license/ricterz/nhentai.svg
:target: https://github.com/RicterZ/nhentai/blob/master/LICENSE :target: https://github.com/RicterZ/nhentai/blob/master/LICENSE

View File

@@ -1,3 +1,3 @@
__version__ = '2.0.5' __version__ = '2.0.8'
__author__ = 'RicterZ' __author__ = 'RicterZ'
__email__ = 'ricterzheng@gmail.com' __email__ = 'ricterzheng@gmail.com'

View File

@@ -162,10 +162,8 @@ def cmd_parser():
parser.add_argument('--zip', action='store_true', help='Package into a single zip file') parser.add_argument('--zip', action='store_true', help='Package into a single zip file')
# site options # site options
parser.add_argument('--cookie', type=str, dest='cookie', parser.add_argument('--token', type=str, dest='token',
help='set cookie to bypass Cloudflare captcha') help='set API token for authentication')
parser.add_argument('--useragent', '--user-agent', type=str, dest='useragent',
help='set useragent to bypass Cloudflare captcha')
parser.add_argument('--language', type=str, dest='language', parser.add_argument('--language', type=str, dest='language',
help='set default language to parse doujinshis') help='set default language to parse doujinshis')
parser.add_argument('--clean-language', dest='clean_language', action='store_true', default=False, parser.add_argument('--clean-language', dest='clean_language', action='store_true', default=False,
@@ -176,8 +174,7 @@ def cmd_parser():
help='clean download history') help='clean download history')
parser.add_argument('--template', dest='viewer_template', type=str, default='', parser.add_argument('--template', dest='viewer_template', type=str, default='',
help='set viewer template') help='set viewer template')
parser.add_argument('--legacy', dest='legacy', action='store_true', default=False,
help='use legacy searching method')
args = parser.parse_args() args = parser.parse_args()
@@ -216,15 +213,10 @@ def cmd_parser():
sys.exit(0) sys.exit(0)
# --- set config --- # --- set config ---
if args.cookie is not None: if args.token is not None:
c.CONFIG['cookie'] = args.cookie.strip() c.CONFIG['token'] = args.token.strip()
write_config() write_config()
logger.info('Cookie saved.') logger.info('Token saved.')
if args.useragent is not None:
c.CONFIG['useragent'] = args.useragent.strip()
write_config()
logger.info('User-Agent saved.')
if args.language is not None: if args.language is not None:
c.CONFIG['language'] = args.language c.CONFIG['language'] = args.language
@@ -232,7 +224,7 @@ def cmd_parser():
logger.info(f'Default language now set to "{args.language}"') logger.info(f'Default language now set to "{args.language}"')
# TODO: search without language # TODO: search without language
if any([args.cookie, args.useragent, args.language]): if any([args.token, args.language]):
sys.exit(0) sys.exit(0)
if args.proxy is not None: if args.proxy is not None:
@@ -262,8 +254,8 @@ def cmd_parser():
# --- end set config --- # --- end set config ---
if args.favorites: if args.favorites:
if not c.CONFIG['cookie']: if not c.CONFIG.get('cookie') and not c.CONFIG.get('token'):
logger.warning('Cookie has not been set, please use `doujinshi-dl --cookie \'COOKIE\'` to set it.') logger.warning('Token has not been set, please use `doujinshi-dl --token \'TOKEN\'` to set it.')
sys.exit(1) sys.exit(1)
if args.file: if args.file:

View File

@@ -82,17 +82,15 @@ def main():
options.keyword, options.keyword,
sorting=options.sorting, sorting=options.sorting,
page=page_list, page=page_list,
legacy=options.legacy,
is_page_all=options.page_all, is_page_all=options.page_all,
) )
elif options.artist: elif options.artist:
doujinshis = parser.search( doujinshis = parser.search(
options.artist, f'artist:{options.artist}',
sorting=options.sorting, sorting=options.sorting,
page=page_list, page=page_list,
is_page_all=options.page_all, is_page_all=options.page_all,
type_='ARTIST',
) )
elif not doujinshi_ids: elif not doujinshi_ids:
@@ -131,7 +129,8 @@ def main():
doujinshi = doujinshi_model.doujinshi doujinshi = doujinshi_model.doujinshi
doujinshi.downloader = downloader doujinshi.downloader = downloader
if doujinshi.check_if_need_download(options): need_download = doujinshi.check_if_need_download(options)
if need_download:
doujinshi.download() doujinshi.download()
else: else:
logger.info( logger.info(
@@ -140,14 +139,20 @@ def main():
doujinshi_dir = os.path.join(options.output_dir, doujinshi.filename) doujinshi_dir = os.path.join(options.output_dir, doujinshi.filename)
if options.generate_metadata: # If skipped (CBZ/PDF already exists), treat as already downloaded:
serializer.write_all(meta, doujinshi_dir) # - skip all post-processing that requires doujinshi_dir to exist
logger.log(16, f'Metadata files have been written to "{doujinshi_dir}"') # - but still write to history DB in case the record was lost
if options.is_save_download_history: if options.is_save_download_history:
with DB() as db: with DB() as db:
db.add_one(doujinshi.id) db.add_one(doujinshi.id)
if not need_download:
continue
if options.generate_metadata:
serializer.write_all(meta, doujinshi_dir)
logger.log(16, f'Metadata files have been written to "{doujinshi_dir}"')
if not options.is_nohtml: if not options.is_nohtml:
generate_html(options.output_dir, doujinshi, template=template) generate_html(options.output_dir, doujinshi, template=template)

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 991 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 679 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 MiB

View File

@@ -1,6 +1,6 @@
[tool.poetry] [tool.poetry]
name = "doujinshi-dl" name = "doujinshi-dl"
version = "2.0.5" version = "2.0.8"
description = "doujinshi downloader" description = "doujinshi downloader"
authors = ["Ricter Z <ricterzheng@gmail.com>"] authors = ["Ricter Z <ricterzheng@gmail.com>"]
license = "MIT" license = "MIT"