Compare commits

..

11 Commits

Author SHA1 Message Date
1b7f19ee18 0.5.25, fix #393 2025-02-26 00:13:41 +08:00
132f4c83da Merge branch 'master' of github.com:RicterZ/nhentai 2025-02-26 00:12:49 +08:00
6789b2b363 fix bug of cover.webp.webp 2025-02-25 23:51:13 +08:00
a6ac725ca7 Merge pull request #392 from akakishi/master
Update installation instructions in README.rst
2025-02-23 20:29:15 +08:00
b32962bca4 Update README.rst
File `setup.py` was removed in a previous commit; updated README to reflect the new installation process.
2025-02-23 01:18:54 -03:00
8a7be0e33d 0.5.24 2025-02-09 20:16:44 +08:00
0a47527461 optimize logger output #390 2025-02-09 20:15:17 +08:00
023c8969eb add global retry for search, download, fetch favorites 2025-02-09 20:02:52 +08:00
29c3abbe5c Merge branch 'master' of github.com:RicterZ/nhentai 2025-02-08 16:21:08 +08:00
057fae8a83 0.5.23 2025-02-03 15:47:51 +08:00
ba59dcf4db add up/down arrow 2025-01-16 22:40:53 +08:00
9 changed files with 50 additions and 29 deletions

View File

@ -22,7 +22,7 @@ From Github:
git clone https://github.com/RicterZ/nhentai git clone https://github.com/RicterZ/nhentai
cd nhentai cd nhentai
python setup.py install pip install --no-cache-dir .
Build Docker container: Build Docker container:

View File

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

View File

@ -171,22 +171,24 @@ def cmd_parser():
# --- set config --- # --- set config ---
if args.cookie is not None: if args.cookie is not None:
constant.CONFIG['cookie'] = args.cookie constant.CONFIG['cookie'] = args.cookie.strip()
write_config() write_config()
logger.info('Cookie saved.') logger.info('Cookie saved.')
sys.exit(0)
elif args.useragent is not None: if args.useragent is not None:
constant.CONFIG['useragent'] = args.useragent constant.CONFIG['useragent'] = args.useragent.strip()
write_config() write_config()
logger.info('User-Agent saved.') logger.info('User-Agent saved.')
sys.exit(0)
elif args.language is not None: if args.language is not None:
constant.CONFIG['language'] = args.language constant.CONFIG['language'] = args.language
write_config() write_config()
logger.info(f'Default language now set to "{args.language}"') logger.info(f'Default language now set to "{args.language}"')
sys.exit(0)
# TODO: search without language # TODO: search without language
if any([args.cookie, args.useragent, args.language]):
sys.exit(0)
if args.proxy is not None: if args.proxy is not None:
proxy_url = urlparse(args.proxy) proxy_url = urlparse(args.proxy)
if not args.proxy == '' and proxy_url.scheme not in ('http', 'https', 'socks5', 'socks5h', if not args.proxy == '' and proxy_url.scheme not in ('http', 'https', 'socks5', 'socks5h',

View File

@ -4,8 +4,6 @@ import shutil
import sys import sys
import signal import signal
import platform import platform
import urllib
import urllib3.exceptions import urllib3.exceptions
from nhentai import constant from nhentai import constant
@ -15,7 +13,6 @@ from nhentai.doujinshi import Doujinshi
from nhentai.downloader import Downloader from nhentai.downloader import Downloader
from nhentai.logger import logger from nhentai.logger import logger
from nhentai.constant import BASE_URL from nhentai.constant import BASE_URL
from nhentai.serializer import serialize_json
from nhentai.utils import generate_html, generate_doc, generate_main_html, generate_metadata_file, \ from nhentai.utils import generate_html, generate_doc, generate_main_html, generate_metadata_file, \
paging, check_cookie, signal_handler, DB, move_to_folder paging, check_cookie, signal_handler, DB, move_to_folder
@ -52,6 +49,9 @@ def main():
page_list = paging(options.page) page_list = paging(options.page)
if options.retry:
constant.RETRY_TIMES = int(options.retry)
if options.favorites: if options.favorites:
if not options.is_download: if not options.is_download:
logger.warning('You do not specify --download option') logger.warning('You do not specify --download option')
@ -87,7 +87,7 @@ def main():
if not options.is_show: if not options.is_show:
downloader = Downloader(path=options.output_dir, threads=options.threads, downloader = Downloader(path=options.output_dir, threads=options.threads,
timeout=options.timeout, delay=options.delay, timeout=options.timeout, delay=options.delay,
retry=options.retry, exit_on_fail=options.exit_on_fail, exit_on_fail=options.exit_on_fail,
no_filename_padding=options.no_filename_padding) no_filename_padding=options.no_filename_padding)
for doujinshi_id in doujinshi_ids: for doujinshi_id in doujinshi_ids:

View File

@ -37,6 +37,8 @@ FAV_URL = f'{BASE_URL}/favorites/'
PATH_SEPARATOR = os.path.sep PATH_SEPARATOR = os.path.sep
RETRY_TIMES = 3
IMAGE_URL = f'{urlparse(BASE_URL).scheme}://i1.{urlparse(BASE_URL).hostname}/galleries' IMAGE_URL = f'{urlparse(BASE_URL).scheme}://i1.{urlparse(BASE_URL).hostname}/galleries'
IMAGE_URL_MIRRORS = [ IMAGE_URL_MIRRORS = [

View File

@ -34,13 +34,12 @@ def download_callback(result):
class Downloader(Singleton): class Downloader(Singleton):
def __init__(self, path='', threads=5, timeout=30, delay=0, retry=3, exit_on_fail=False, def __init__(self, path='', threads=5, timeout=30, delay=0, exit_on_fail=False,
no_filename_padding=False): no_filename_padding=False):
self.threads = threads self.threads = threads
self.path = str(path) self.path = str(path)
self.timeout = timeout self.timeout = timeout
self.delay = delay self.delay = delay
self.retry = retry
self.exit_on_fail = exit_on_fail self.exit_on_fail = exit_on_fail
self.folder = None self.folder = None
self.semaphore = None self.semaphore = None
@ -101,7 +100,7 @@ class Downloader(Singleton):
return -1, url return -1, url
except (httpx.HTTPStatusError, httpx.TimeoutException, httpx.ConnectError) as e: except (httpx.HTTPStatusError, httpx.TimeoutException, httpx.ConnectError) as e:
if retried < self.retry: if retried < constant.RETRY_TIMES:
logger.warning(f'Download {filename} failed, retrying({retried + 1}) times...') logger.warning(f'Download {filename} failed, retrying({retried + 1}) times...')
return await self.download( return await self.download(
url=url, url=url,
@ -111,7 +110,7 @@ class Downloader(Singleton):
proxy=proxy, proxy=proxy,
) )
else: else:
logger.warning(f'Download {filename} failed with {self.retry} times retried, skipped') logger.warning(f'Download {filename} failed with {constant.RETRY_TIMES} times retried, skipped')
return -2, url return -2, url
except NHentaiImageNotExistException as e: except NHentaiImageNotExistException as e:

View File

@ -92,13 +92,27 @@ def favorites_parser(page=None):
page_range_list = range(1, pages + 1) page_range_list = range(1, pages + 1)
for page in page_range_list: for page in page_range_list:
try: logger.info(f'Getting doujinshi ids of page {page}')
logger.info(f'Getting doujinshi ids of page {page}')
resp = request('get', f'{constant.FAV_URL}?page={page}').content
result.extend(_get_title_and_id(resp)) i = 0
except Exception as e: while i <= constant.RETRY_TIMES + 1:
logger.error(f'Error: {e}, continue') i += 1
if i > 3:
logger.error(f'Failed to get favorites at page {page} after 3 times retried, skipped')
break
try:
resp = request('get', f'{constant.FAV_URL}?page={page}').content
temp_result = _get_title_and_id(resp)
if not temp_result:
logger.warning(f'Failed to get favorites at page {page}, retrying ({i} times) ...')
continue
else:
result.extend(temp_result)
break
except Exception as e:
logger.warning(f'Error: {e}, retrying ({i} times) ...')
return result return result
@ -151,7 +165,9 @@ def doujinshi_parser(id_, counter=0):
doujinshi_cover = html.find('div', attrs={'id': 'cover'}) doujinshi_cover = html.find('div', attrs={'id': 'cover'})
# img_id = re.search('/galleries/([0-9]+)/cover.(jpg|png|gif|webp)$', # img_id = re.search('/galleries/([0-9]+)/cover.(jpg|png|gif|webp)$',
# doujinshi_cover.a.img.attrs['data-src']) # doujinshi_cover.a.img.attrs['data-src'])
img_id = re.search(r'/galleries/(\d+)/cover\.\w+$', doujinshi_cover.a.img.attrs['data-src'])
# fix cover.webp.webp
img_id = re.search(r'/galleries/(\d+)/cover(.webp)?\.\w+$', doujinshi_cover.a.img.attrs['data-src'])
ext = [] ext = []
for i in html.find_all('div', attrs={'class': 'thumb-container'}): for i in html.find_all('div', attrs={'class': 'thumb-container'}):
@ -261,7 +277,7 @@ def search_parser(keyword, sorting, page, is_page_all=False):
i = 0 i = 0
logger.info(f'Searching doujinshis using keywords "{keyword}" on page {p}{total}') logger.info(f'Searching doujinshis using keywords "{keyword}" on page {p}{total}')
while i < 3: while i < constant.RETRY_TIMES:
try: try:
url = request('get', url=constant.SEARCH_URL, params={'query': keyword, url = request('get', url=constant.SEARCH_URL, params={'query': keyword,
'page': p, 'sort': sorting}).url 'page': p, 'sort': sorting}).url

View File

@ -49,8 +49,8 @@ document.onkeypress = event => {
switch (event.key.toLowerCase()) { switch (event.key.toLowerCase()) {
// Previous Image // Previous Image
case 'w': case 'w':
scrollBy(0, -40); scrollBy(0, -40);
break; break;
case 'a': case 'a':
changePage(currentPage - 1); changePage(currentPage - 1);
break; break;
@ -61,7 +61,7 @@ document.onkeypress = event => {
// Next Image // Next Image
case ' ': case ' ':
case 's': case 's':
scrollBy(0, 40); scrollBy(0, 40);
break; break;
case 'd': case 'd':
changePage(currentPage + 1); changePage(currentPage + 1);
@ -75,11 +75,13 @@ document.onkeydown = event =>{
changePage(currentPage - 1); changePage(currentPage - 1);
break; break;
case 38: //up case 38: //up
changePage(currentPage - 1);
break; break;
case 39: //right case 39: //right
changePage(currentPage + 1); changePage(currentPage + 1);
break; break;
case 40: //down case 40: //down
changePage(currentPage + 1);
break; break;
} }
}; };

View File

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