Compare commits

..

3 Commits
0.5.7 ... dev

Author SHA1 Message Date
a0f6e3f857 Merge pull request #308 from myc1ou1d/dev
fix nhentai.net api sort option changed from popular to popular-all
2024-04-01 00:53:02 +08:00
413657e076 Merge branch 'RicterZ:dev' into dev 2024-04-01 00:12:41 +08:00
0b80458c6f fix nhentai.net api sort option changed from popular to popular-all 2024-03-11 16:34:53 +08:00
13 changed files with 66 additions and 143 deletions

View File

@ -11,8 +11,6 @@ nhentai
nhentai is a CLI tool for downloading doujinshi from `nhentai.net <https://nhentai.net>`_ nhentai is a CLI tool for downloading doujinshi from `nhentai.net <https://nhentai.net>`_
GUI version: `https://github.com/edgar1016/nhentai-GUI <https://github.com/edgar1016/nhentai-GUI>`_
=================== ===================
Manual Installation Manual Installation
=================== ===================
@ -143,9 +141,7 @@ Supported doujinshi folder formatter:
- %t: Doujinshi name - %t: Doujinshi name
- %s: Doujinshi subtitle (translated name) - %s: Doujinshi subtitle (translated name)
- %a: Doujinshi authors' name - %a: Doujinshi authors' name
- %g: Doujinshi groups name
- %p: Doujinshi pretty name - %p: Doujinshi pretty name
- %ag: Doujinshi authors name or groups name
Other options: Other options:
@ -202,8 +198,6 @@ Other options:
-P, --pdf generate PDF file -P, --pdf generate PDF file
--rm-origin-dir remove downloaded doujinshi dir when generated CBZ or --rm-origin-dir remove downloaded doujinshi dir when generated CBZ or
PDF file PDF file
--move-to-folder remove files in doujinshi dir then move new file to folder
when generated CBZ or PDF file
--meta generate a metadata file in doujinshi format --meta generate a metadata file in doujinshi format
--regenerate-cbz regenerate the cbz file if exists --regenerate-cbz regenerate the cbz file if exists

View File

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

View File

@ -81,9 +81,9 @@ def cmd_parser():
help='all search results') help='all search results')
parser.add_option('--page', '--page-range', type='string', dest='page', action='store', default='1', parser.add_option('--page', '--page-range', type='string', dest='page', action='store', default='1',
help='page number of search results. e.g. 1,2-5,14') help='page number of search results. e.g. 1,2-5,14')
parser.add_option('--sorting', '--sort', dest='sorting', action='store', default='popular', parser.add_option('--sorting', '--sort', dest='sorting', action='store', default='popular-all',
help='sorting of doujinshi (recent / popular / popular-[today|week])', help='sorting of doujinshi (recent / popular-all / popular-[today|week])',
choices=['recent', 'popular', 'popular-today', 'popular-week', 'date']) choices=['recent', 'popular-all', 'popular-today', 'popular-week', 'date'])
# download options # download options
parser.add_option('--output', '-o', type='string', dest='output_dir', action='store', default='./', parser.add_option('--output', '-o', type='string', dest='output_dir', action='store', default='./',

View File

@ -46,7 +46,7 @@ def main():
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')
doujinshis = favorites_parser() if options.page_all else favorites_parser(page=page_list) doujinshis = favorites_parser(page=page_list)
elif options.keyword: elif options.keyword:
if constant.CONFIG['language']: if constant.CONFIG['language']:
@ -57,10 +57,6 @@ def main():
doujinshis = _search_parser(options.keyword, sorting=options.sorting, page=page_list, doujinshis = _search_parser(options.keyword, sorting=options.sorting, page=page_list,
is_page_all=options.page_all) is_page_all=options.page_all)
elif options.artist:
doujinshis = legacy_search_parser(options.artist, sorting=options.sorting, page=page_list,
is_page_all=options.page_all, type_='ARTIST')
elif not doujinshi_ids: elif not doujinshi_ids:
doujinshi_ids = options.id doujinshi_ids = options.id
@ -100,9 +96,9 @@ def main():
if not options.is_nohtml and not options.is_cbz and not options.is_pdf: if not options.is_nohtml and not options.is_cbz and not options.is_pdf:
generate_html(options.output_dir, doujinshi, template=constant.CONFIG['template']) generate_html(options.output_dir, doujinshi, template=constant.CONFIG['template'])
elif options.is_cbz: elif options.is_cbz:
generate_cbz(options.output_dir, doujinshi, options.rm_origin_dir, True, options.move_to_folder) generate_cbz(options.output_dir, doujinshi, options.rm_origin_dir)
elif options.is_pdf: elif options.is_pdf:
generate_pdf(options.output_dir, doujinshi, options.rm_origin_dir, options.move_to_folder) generate_pdf(options.output_dir, doujinshi, options.rm_origin_dir)
if options.main_viewer: if options.main_viewer:
generate_main_html(options.output_dir) generate_main_html(options.output_dir)

View File

@ -3,23 +3,6 @@ import os
import tempfile import tempfile
from urllib.parse import urlparse from urllib.parse import urlparse
from platform import system
def get_nhentai_home() -> str:
home = os.getenv('HOME', tempfile.gettempdir())
if system() == 'Linux':
xdgdat = os.getenv('XDG_DATA_HOME')
if xdgdat and os.path.exists(os.path.join(xdgdat, 'nhentai')):
return os.path.join(xdgdat, 'nhentai')
if home and os.path.exists(os.path.join(home, '.nhentai')):
return os.path.join(home, '.nhentai')
if xdgdat:
return os.path.join(xdgdat, 'nhentai')
# Use old default path in other systems
return os.path.join(home, '.nhentai')
DEBUG = os.getenv('DEBUG', False) DEBUG = os.getenv('DEBUG', False)
@ -28,22 +11,15 @@ BASE_URL = os.getenv('NHENTAI', 'https://nhentai.net')
DETAIL_URL = f'{BASE_URL}/g' DETAIL_URL = f'{BASE_URL}/g'
LEGACY_SEARCH_URL = f'{BASE_URL}/search/' LEGACY_SEARCH_URL = f'{BASE_URL}/search/'
SEARCH_URL = f'{BASE_URL}/api/galleries/search' SEARCH_URL = f'{BASE_URL}/api/galleries/search'
ARTIST_URL = f'{BASE_URL}/artist/'
TAG_API_URL = f'{BASE_URL}/api/galleries/tagged' TAG_API_URL = f'{BASE_URL}/api/galleries/tagged'
LOGIN_URL = f'{BASE_URL}/login/' LOGIN_URL = f'{BASE_URL}/login/'
CHALLENGE_URL = f'{BASE_URL}/challenge' CHALLENGE_URL = f'{BASE_URL}/challenge'
FAV_URL = f'{BASE_URL}/favorites/' FAV_URL = f'{BASE_URL}/favorites/'
IMAGE_URL = f'{urlparse(BASE_URL).scheme}://i.{urlparse(BASE_URL).hostname}/galleries' IMAGE_URL = f'{urlparse(BASE_URL).scheme}://i.{urlparse(BASE_URL).hostname}/galleries'
IMAGE_URL_MIRRORS = [
f'{urlparse(BASE_URL).scheme}://i3.{urlparse(BASE_URL).hostname}'
f'{urlparse(BASE_URL).scheme}://i5.{urlparse(BASE_URL).hostname}'
f'{urlparse(BASE_URL).scheme}://i7.{urlparse(BASE_URL).hostname}'
]
NHENTAI_HOME = get_nhentai_home() NHENTAI_HOME = os.path.join(os.getenv('HOME', tempfile.gettempdir()), '.nhentai')
NHENTAI_HISTORY = os.path.join(NHENTAI_HOME, 'history.sqlite3') NHENTAI_HISTORY = os.path.join(NHENTAI_HOME, 'history.sqlite3')
NHENTAI_CONFIG_FILE = os.path.join(NHENTAI_HOME, 'config.json') NHENTAI_CONFIG_FILE = os.path.join(NHENTAI_HOME, 'config.json')
@ -54,8 +30,7 @@ CONFIG = {
'cookie': '', 'cookie': '',
'language': '', 'language': '',
'template': '', 'template': '',
'useragent': 'nhentai command line client (https://github.com/RicterZ/nhentai)', 'useragent': 'nhentai command line client (https://github.com/RicterZ/nhentai)'
'max_filename': 85
} }
LANGUAGE_ISO = { LANGUAGE_ISO = {

View File

@ -20,10 +20,9 @@ class DoujinshiInfo(dict):
def __getattr__(self, item): def __getattr__(self, item):
try: try:
ret = dict.__getitem__(self, item) return dict.__getitem__(self, item)
return ret if ret else 'Unknown'
except KeyError: except KeyError:
return 'Unknown' return ''
class Doujinshi(object): class Doujinshi(object):
@ -39,12 +38,8 @@ class Doujinshi(object):
self.url = f'{DETAIL_URL}/{self.id}' self.url = f'{DETAIL_URL}/{self.id}'
self.info = DoujinshiInfo(**kwargs) self.info = DoujinshiInfo(**kwargs)
ag_value = self.info.groups if self.info.artists == 'Unknown' else self.info.artists
name_format = name_format.replace('%ag', format_filename(ag_value))
name_format = name_format.replace('%i', format_filename(str(self.id))) name_format = name_format.replace('%i', format_filename(str(self.id)))
name_format = name_format.replace('%a', format_filename(self.info.artists)) name_format = name_format.replace('%a', format_filename(self.info.artists))
name_format = name_format.replace('%g', format_filename(self.info.groups))
name_format = name_format.replace('%t', format_filename(self.name)) name_format = name_format.replace('%t', format_filename(self.name))
name_format = name_format.replace('%p', format_filename(self.pretty_name)) name_format = name_format.replace('%p', format_filename(self.pretty_name))
@ -52,16 +47,15 @@ class Doujinshi(object):
self.filename = format_filename(name_format, 255, True) self.filename = format_filename(name_format, 255, True)
self.table = [ self.table = [
['Parodies', self.info.parodies], ["Parodies", self.info.parodies],
['Doujinshi', self.name], ["Doujinshi", self.name],
['Subtitle', self.info.subtitle], ["Subtitle", self.info.subtitle],
['Characters', self.info.characters], ["Characters", self.info.characters],
['Authors', self.info.artists], ["Authors", self.info.artists],
['Groups', self.info.groups], ["Languages", self.info.languages],
['Languages', self.info.languages], ["Tags", self.info.tags],
['Tags', self.info.tags], ["URL", self.url],
['URL', self.url], ["Pages", self.pages],
['Pages', self.pages],
] ]
def __repr__(self): def __repr__(self):

View File

@ -67,14 +67,10 @@ class Downloader(Singleton):
try: try:
response = request('get', url, stream=True, timeout=self.timeout, proxies=proxy) response = request('get', url, stream=True, timeout=self.timeout, proxies=proxy)
if response.status_code != 200: if response.status_code != 200:
path = urlparse(url).path raise NHentaiImageNotExistException
for mirror in constant.IMAGE_URL_MIRRORS:
print(f'{mirror}{path}') except NHentaiImageNotExistException as e:
mirror_url = f'{mirror}{path}' raise e
response = request('get', mirror_url, stream=True,
timeout=self.timeout, proxies=proxy)
if response.status_code == 200:
break
except Exception as e: except Exception as e:
i += 1 i += 1
@ -127,7 +123,6 @@ class Downloader(Singleton):
logger.warning(f'CBZ file "{folder}.cbz" exists, ignored download request') logger.warning(f'CBZ file "{folder}.cbz" exists, ignored download request')
return return
logger.info(f'Doujinshi will be saved at "{folder}"')
if not os.path.exists(folder): if not os.path.exists(folder):
try: try:
os.makedirs(folder) os.makedirs(folder)

View File

@ -135,7 +135,6 @@ def doujinshi_parser(id_, counter=0):
logger.warning(f'Error: {e}, ignored') logger.warning(f'Error: {e}, ignored')
return None return None
# print(response)
html = BeautifulSoup(response, 'html.parser') html = BeautifulSoup(response, 'html.parser')
doujinshi_info = html.find('div', attrs={'id': 'info'}) doujinshi_info = html.find('div', attrs={'id': 'info'})
@ -241,21 +240,13 @@ def print_doujinshi(doujinshi_list):
print(tabulate(tabular_data=doujinshi_list, headers=headers, tablefmt='rst')) print(tabulate(tabular_data=doujinshi_list, headers=headers, tablefmt='rst'))
def legacy_search_parser(keyword, sorting, page, is_page_all=False, type_='SEARCH'): def legacy_search_parser(keyword, sorting, page, is_page_all=False):
logger.info(f'Searching doujinshis of keyword {keyword}') logger.info(f'Searching doujinshis of keyword {keyword}')
result = [] result = []
if type_ not in ('SEARCH', 'ARTIST', ):
raise ValueError('Invalid type')
if is_page_all: if is_page_all:
if type_ == 'SEARCH':
response = request('get', url=constant.LEGACY_SEARCH_URL, response = request('get', url=constant.LEGACY_SEARCH_URL,
params={'q': keyword, 'page': 1, 'sort': sorting}).content params={'q': keyword, 'page': 1, 'sort': sorting}).content
else:
url = constant.ARTIST_URL + keyword + '/' + ('' if sorting == 'recent' else sorting)
response = request('get', url=url, params={'page': 1}).content
html = BeautifulSoup(response, 'lxml') html = BeautifulSoup(response, 'lxml')
pagination = html.find(attrs={'class': 'pagination'}) pagination = html.find(attrs={'class': 'pagination'})
last_page = pagination.find(attrs={'class': 'last'}) last_page = pagination.find(attrs={'class': 'last'})
@ -267,13 +258,8 @@ def legacy_search_parser(keyword, sorting, page, is_page_all=False, type_='SEARC
for p in pages: for p in pages:
logger.info(f'Fetching page {p} ...') logger.info(f'Fetching page {p} ...')
if type_ == 'SEARCH':
response = request('get', url=constant.LEGACY_SEARCH_URL, response = request('get', url=constant.LEGACY_SEARCH_URL,
params={'q': keyword, 'page': p, 'sort': sorting}).content params={'q': keyword, 'page': p, 'sort': sorting}).content
else:
url = constant.ARTIST_URL + keyword + '/' + ('' if sorting == 'recent' else sorting)
response = request('get', url=url, params={'page': p}).content
if response is None: if response is None:
logger.warning(f'No result in response in page {p}') logger.warning(f'No result in response in page {p}')
continue continue
@ -327,9 +313,7 @@ def search_parser(keyword, sorting, page, is_page_all=False):
for row in response['result']: for row in response['result']:
title = row['title']['english'] title = row['title']['english']
title = title[:constant.CONFIG['max_filename']] + '..' if \ title = title[:85] + '..' if len(title) > 85 else title
len(title) > constant.CONFIG['max_filename'] else title
result.append({'id': row['id'], 'title': title}) result.append({'id': row['id'], 'title': title})
not_exists_persist = False not_exists_persist = False

View File

@ -163,7 +163,7 @@ def generate_main_html(output_dir='./'):
logger.warning(f'Writing Main Viewer failed ({e})') logger.warning(f'Writing Main Viewer failed ({e})')
def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False, write_comic_info=True, move_to_folder=False): def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False, write_comic_info=True):
if doujinshi_obj is not None: if doujinshi_obj is not None:
doujinshi_dir = os.path.join(output_dir, doujinshi_obj.filename) doujinshi_dir = os.path.join(output_dir, doujinshi_obj.filename)
if os.path.exists(doujinshi_dir+".cbz"): if os.path.exists(doujinshi_dir+".cbz"):
@ -188,31 +188,19 @@ def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False, write_
if rm_origin_dir: if rm_origin_dir:
shutil.rmtree(doujinshi_dir, ignore_errors=True) shutil.rmtree(doujinshi_dir, ignore_errors=True)
if move_to_folder:
for filename in os.listdir(doujinshi_dir):
file_path = os.path.join(doujinshi_dir, filename)
if os.path.isfile(file_path):
try:
os.remove(file_path)
except Exception as e:
print(f"Error deleting file: {e}")
shutil.move(cbz_filename, doujinshi_dir)
logger.log(16, f'Comic Book CBZ file has been written to "{doujinshi_dir}"') logger.log(16, f'Comic Book CBZ file has been written to "{doujinshi_dir}"')
def generate_pdf(output_dir='.', doujinshi_obj=None, rm_origin_dir=False, move_to_folder=False): def generate_pdf(output_dir='.', doujinshi_obj=None, rm_origin_dir=False):
try: try:
import img2pdf import img2pdf
"""Write images to a PDF file using img2pdf.""" """Write images to a PDF file using img2pdf."""
if doujinshi_obj is not None: if doujinshi_obj is not None:
doujinshi_dir = os.path.join(output_dir, doujinshi_obj.filename) doujinshi_dir = os.path.join(output_dir, doujinshi_obj.filename)
filename = doujinshi_obj.filename.replace('/', '-')
pdf_filename = os.path.join( pdf_filename = os.path.join(
os.path.join(doujinshi_dir, '..'), os.path.join(doujinshi_dir, '..'),
f'{filename}.pdf' f'{doujinshi_obj.filename}.pdf'
) )
else: else:
pdf_filename = './doujinshi.pdf' pdf_filename = './doujinshi.pdf'
@ -226,22 +214,11 @@ def generate_pdf(output_dir='.', doujinshi_obj=None, rm_origin_dir=False, move_t
full_path_list = ( full_path_list = (
[os.path.join(doujinshi_dir, image) for image in file_list] [os.path.join(doujinshi_dir, image) for image in file_list]
) )
pdf_f.write(img2pdf.convert(full_path_list, rotation=img2pdf.Rotation.ifvalid)) pdf_f.write(img2pdf.convert(full_path_list))
if rm_origin_dir: if rm_origin_dir:
shutil.rmtree(doujinshi_dir, ignore_errors=True) shutil.rmtree(doujinshi_dir, ignore_errors=True)
if move_to_folder:
for filename in os.listdir(doujinshi_dir):
file_path = os.path.join(doujinshi_dir, filename)
if os.path.isfile(file_path):
try:
os.remove(file_path)
except Exception as e:
print(f"Error deleting file: {e}")
shutil.move(pdf_filename, doujinshi_dir)
logger.log(16, f'PDF file has been written to "{doujinshi_dir}"') logger.log(16, f'PDF file has been written to "{doujinshi_dir}"')
except ImportError: except ImportError:
@ -258,7 +235,7 @@ def format_filename(s, length=MAX_FIELD_LENGTH, _truncate_only=False):
# maybe you can use `--format` to select a suitable filename # maybe you can use `--format` to select a suitable filename
if not _truncate_only: if not _truncate_only:
ban_chars = '\\\'/:,;*?"<>|\t\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b' ban_chars = '\\\'/:,;*?"<>|\t'
filename = s.translate(str.maketrans(ban_chars, ' ' * len(ban_chars))).strip() filename = s.translate(str.maketrans(ban_chars, ' ' * len(ban_chars))).strip()
filename = ' '.join(filename.split()) filename = ' '.join(filename.split())
@ -313,11 +290,11 @@ def generate_metadata_file(output_dir, table, doujinshi_obj=None):
f = open(os.path.join(doujinshi_dir, 'info.txt'), 'w', encoding='utf-8') f = open(os.path.join(doujinshi_dir, 'info.txt'), 'w', encoding='utf-8')
fields = ['TITLE', 'ORIGINAL TITLE', 'AUTHOR', 'ARTIST', 'GROUPS', 'CIRCLE', 'SCANLATOR', fields = ['TITLE', 'ORIGINAL TITLE', 'AUTHOR', 'ARTIST', 'CIRCLE', 'SCANLATOR',
'TRANSLATOR', 'PUBLISHER', 'DESCRIPTION', 'STATUS', 'CHAPTERS', 'PAGES', 'TRANSLATOR', 'PUBLISHER', 'DESCRIPTION', 'STATUS', 'CHAPTERS', 'PAGES',
'TAGS', 'TYPE', 'LANGUAGE', 'RELEASED', 'READING DIRECTION', 'CHARACTERS', 'TAGS', 'TYPE', 'LANGUAGE', 'RELEASED', 'READING DIRECTION', 'CHARACTERS',
'SERIES', 'PARODY', 'URL'] 'SERIES', 'PARODY', 'URL']
special_fields = ['PARODY', 'TITLE', 'ORIGINAL TITLE', 'CHARACTERS', 'AUTHOR', 'GROUPS', special_fields = ['PARODY', 'TITLE', 'ORIGINAL TITLE', 'CHARACTERS', 'AUTHOR',
'LANGUAGE', 'TAGS', 'URL', 'PAGES'] 'LANGUAGE', 'TAGS', 'URL', 'PAGES']
for i in range(len(fields)): for i in range(len(fields)):

View File

@ -139,7 +139,7 @@ function filter_searcher(){
break break
} }
} }
if (verifier){doujinshi_id.push(data[i].Folder.replace("_", " "));} if (verifier){doujinshi_id.push(data[i].Folder);}
} }
var gallery = document.getElementsByClassName("gallery-favorite"); var gallery = document.getElementsByClassName("gallery-favorite");
for (var i = 0; i < gallery.length; i++){ for (var i = 0; i < gallery.length; i++){

43
poetry.lock generated
View File

@ -1,9 +1,10 @@
# This file is automatically @generated by Poetry 1.8.3 and should not be changed by hand. # This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand.
[[package]] [[package]]
name = "beautifulsoup4" name = "beautifulsoup4"
version = "4.11.2" version = "4.11.2"
description = "Screen-scraping library" description = "Screen-scraping library"
category = "main"
optional = false optional = false
python-versions = ">=3.6.0" python-versions = ">=3.6.0"
files = [ files = [
@ -20,19 +21,21 @@ lxml = ["lxml"]
[[package]] [[package]]
name = "certifi" name = "certifi"
version = "2024.7.4" version = "2022.12.7"
description = "Python package for providing Mozilla's CA Bundle." description = "Python package for providing Mozilla's CA Bundle."
category = "main"
optional = false optional = false
python-versions = ">=3.6" python-versions = ">=3.6"
files = [ files = [
{file = "certifi-2024.7.4-py3-none-any.whl", hash = "sha256:c198e21b1289c2ab85ee4e67bb4b4ef3ead0892059901a8d5b622f24a1101e90"}, {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"},
{file = "certifi-2024.7.4.tar.gz", hash = "sha256:5a1e7645bc0ec61a09e26c36f6106dd4cf40c6db3a1fb6352b0244e7fb057c7b"}, {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"},
] ]
[[package]] [[package]]
name = "charset-normalizer" name = "charset-normalizer"
version = "3.0.1" version = "3.0.1"
description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet."
category = "main"
optional = false optional = false
python-versions = "*" python-versions = "*"
files = [ files = [
@ -128,19 +131,21 @@ files = [
[[package]] [[package]]
name = "idna" name = "idna"
version = "3.7" version = "3.4"
description = "Internationalized Domain Names in Applications (IDNA)" description = "Internationalized Domain Names in Applications (IDNA)"
category = "main"
optional = false optional = false
python-versions = ">=3.5" python-versions = ">=3.5"
files = [ files = [
{file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, {file = "idna-3.4-py3-none-any.whl", hash = "sha256:90b77e79eaa3eba6de819a0c442c0b4ceefc341a7a2ab77d7562bf49f425c5c2"},
{file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, {file = "idna-3.4.tar.gz", hash = "sha256:814f528e8dead7d329833b91c5faa87d60bf71824cd12a7530b5526063d02cb4"},
] ]
[[package]] [[package]]
name = "iso8601" name = "iso8601"
version = "1.1.0" version = "1.1.0"
description = "Simple module to parse ISO 8601 dates" description = "Simple module to parse ISO 8601 dates"
category = "main"
optional = false optional = false
python-versions = ">=3.6.2,<4.0" python-versions = ">=3.6.2,<4.0"
files = [ files = [
@ -150,20 +155,21 @@ files = [
[[package]] [[package]]
name = "requests" name = "requests"
version = "2.32.0" version = "2.28.2"
description = "Python HTTP for Humans." description = "Python HTTP for Humans."
category = "main"
optional = false optional = false
python-versions = ">=3.8" python-versions = ">=3.7, <4"
files = [ files = [
{file = "requests-2.32.0-py3-none-any.whl", hash = "sha256:f2c3881dddb70d056c5bd7600a4fae312b2a300e39be6a118d30b90bd27262b5"}, {file = "requests-2.28.2-py3-none-any.whl", hash = "sha256:64299f4909223da747622c030b781c0d7811e359c37124b4bd368fb8c6518baa"},
{file = "requests-2.32.0.tar.gz", hash = "sha256:fa5490319474c82ef1d2c9bc459d3652e3ae4ef4c4ebdd18a21145a47ca4b6b8"}, {file = "requests-2.28.2.tar.gz", hash = "sha256:98b1b2782e3c6c4904938b84c0eb932721069dfdb9134313beff7c83c2df24bf"},
] ]
[package.dependencies] [package.dependencies]
certifi = ">=2017.4.17" certifi = ">=2017.4.17"
charset-normalizer = ">=2,<4" charset-normalizer = ">=2,<4"
idna = ">=2.5,<4" idna = ">=2.5,<4"
urllib3 = ">=1.21.1,<3" urllib3 = ">=1.21.1,<1.27"
[package.extras] [package.extras]
socks = ["PySocks (>=1.5.6,!=1.5.7)"] socks = ["PySocks (>=1.5.6,!=1.5.7)"]
@ -173,6 +179,7 @@ use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"]
name = "soupsieve" name = "soupsieve"
version = "2.4" version = "2.4"
description = "A modern CSS selector implementation for Beautiful Soup." description = "A modern CSS selector implementation for Beautiful Soup."
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -184,6 +191,7 @@ files = [
name = "tabulate" name = "tabulate"
version = "0.9.0" version = "0.9.0"
description = "Pretty-print tabular data" description = "Pretty-print tabular data"
category = "main"
optional = false optional = false
python-versions = ">=3.7" python-versions = ">=3.7"
files = [ files = [
@ -196,17 +204,18 @@ widechars = ["wcwidth"]
[[package]] [[package]]
name = "urllib3" name = "urllib3"
version = "1.26.19" version = "1.26.14"
description = "HTTP library with thread-safe connection pooling, file post, and more." description = "HTTP library with thread-safe connection pooling, file post, and more."
category = "main"
optional = false optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.7" python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
files = [ files = [
{file = "urllib3-1.26.19-py2.py3-none-any.whl", hash = "sha256:37a0344459b199fce0e80b0d3569837ec6b6937435c5244e7fd73fa6006830f3"}, {file = "urllib3-1.26.14-py2.py3-none-any.whl", hash = "sha256:75edcdc2f7d85b137124a6c3c9fc3933cdeaa12ecb9a6a959f22797a0feca7e1"},
{file = "urllib3-1.26.19.tar.gz", hash = "sha256:3e3d753a8618b86d7de333b4223005f68720bcd6a7d2bcb9fbd2229ec7c1e429"}, {file = "urllib3-1.26.14.tar.gz", hash = "sha256:076907bf8fd355cde77728471316625a4d2f7e713c125f51953bb5b3eecf4f72"},
] ]
[package.extras] [package.extras]
brotli = ["brotli (==1.0.9)", "brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"] brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)", "brotlipy (>=0.6.0)"]
secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"] secure = ["certifi", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "ipaddress", "pyOpenSSL (>=0.14)", "urllib3-secure-extra"]
socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"]

View File

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

View File

@ -1,6 +1,5 @@
requests requests
soupsieve soupsieve
setuptools
BeautifulSoup4 BeautifulSoup4
tabulate tabulate
iso8601 iso8601