mirror of
https://github.com/RicterZ/nhentai.git
synced 2025-04-19 18:31:24 +02:00
commit
8ed1b89277
1
.gitignore
vendored
1
.gitignore
vendored
@ -6,3 +6,4 @@ dist/
|
||||
.python-version
|
||||
.DS_Store
|
||||
output/
|
||||
venv/
|
||||
|
@ -11,10 +11,9 @@ install:
|
||||
|
||||
script:
|
||||
- echo 268642 > /tmp/test.txt
|
||||
- nhentai --cookie "csrftoken=xIh7s9d4NB8qSLN7eJZG9064zsV84aHEYFoAU49Ib9anqmoT0pZRw6TIdayLzQuT; sessionid=un101zfgpglsyffdnsm72le4euuisp7t"
|
||||
- nhentai --cookie "_ga=GA1.2.2000087053.1558179358; __cfduid=d8930f7b43d04e1b2117719e28386b2e31593148489; csrftoken=3914GQGSmmqQyfQTBswNgfXuhFiefu8sAgOnsfZWiiqS4PJpKivuTp34p2USV6xu; sessionid=be0w2lwlprlmld3ahg9i592ipsuaw840"
|
||||
- nhentai --search umaru
|
||||
- nhentai --id=152503,146134 -t 10 --output=/tmp/ --cbz
|
||||
- nhentai --tag lolicon --sorting popular
|
||||
- nhentai -F
|
||||
- nhentai --file /tmp/test.txt
|
||||
- nhentai --id=152503,146134 --gen-main --output=/tmp/
|
||||
|
81
README.rst
81
README.rst
@ -19,15 +19,30 @@ nhentai
|
||||
|
||||
nHentai is a CLI tool for downloading doujinshi from <http://nhentai.net>
|
||||
|
||||
============
|
||||
Installation
|
||||
============
|
||||
===================
|
||||
Manual Installation
|
||||
===================
|
||||
.. code-block::
|
||||
|
||||
git clone https://github.com/RicterZ/nhentai
|
||||
cd nhentai
|
||||
python setup.py install
|
||||
|
||||
|
||||
==================
|
||||
Installation (pip)
|
||||
==================
|
||||
Alternatively, install from PyPI with pip:
|
||||
|
||||
.. code-block::
|
||||
|
||||
pip install nhentai
|
||||
|
||||
For a self-contained installation, use `Pipx <https://github.com/pipxproject/pipx/>`_:
|
||||
|
||||
.. code-block::
|
||||
|
||||
pipx install nhentai
|
||||
|
||||
=====================
|
||||
Installation (Gentoo)
|
||||
=====================
|
||||
@ -50,6 +65,8 @@ Set your nhentai cookie against captcha:
|
||||
|
||||
nhentai --cookie "YOUR COOKIE FROM nhentai.net"
|
||||
|
||||
**NOTE**: The format of the cookie is `"csrftoken=TOKEN; sessionid=ID"`
|
||||
|
||||
Download specified doujinshi:
|
||||
|
||||
.. code-block:: bash
|
||||
@ -62,53 +79,20 @@ Download doujinshi with ids specified in a file (doujinshi ids split by line):
|
||||
|
||||
nhentai --file=doujinshi.txt
|
||||
|
||||
Set search default language
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --language=english
|
||||
|
||||
Search a keyword and download the first page:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --search="tomori" --page=1 --download
|
||||
|
||||
Download by tag name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --tag lolicon --download --page=2
|
||||
|
||||
Download by language:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --language english --download --page=2
|
||||
|
||||
Download by artist name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --artist henreader --download
|
||||
|
||||
Download by character name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --character "kuro von einsbern" --download
|
||||
|
||||
Download by parody name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --parody "the idolmaster" --download
|
||||
|
||||
Download by group name:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --group clesta --download
|
||||
|
||||
Download using multiple tags (--tag, --character, --paordy and --group supported):
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
nhentai --tag "lolicon, teasing" --artist "tamano kedama, atte nanakusa"
|
||||
# you also can download by tags and multiple keywords
|
||||
nhentai --search="tag:lolicon, artist:henreader, tag:full color"
|
||||
nhentai --search="lolicon, henreader, full color"
|
||||
|
||||
Download your favorites with delay:
|
||||
|
||||
@ -170,8 +154,9 @@ Other options:
|
||||
--no-html don't generate HTML after downloading
|
||||
--gen-main generate a main viewer contain all the doujin in the folder
|
||||
-C, --cbz generate Comic Book CBZ File
|
||||
-P --pdf generate PDF file
|
||||
--rm-origin-dir remove downloaded doujinshi dir when generated CBZ
|
||||
file.
|
||||
or PDF file.
|
||||
|
||||
# nHentai options
|
||||
--cookie=COOKIE set cookie of nhentai to bypass Google recaptcha
|
||||
@ -183,7 +168,7 @@ nHentai Mirror
|
||||
If you want to use a mirror, you should set up a reverse proxy of `nhentai.net` and `i.nhentai.net`.
|
||||
For example:
|
||||
|
||||
.. code-block::
|
||||
.. code-block::
|
||||
|
||||
i.h.loli.club -> i.nhentai.net
|
||||
h.loli.club -> nhentai.net
|
||||
|
@ -1,3 +1,3 @@
|
||||
__version__ = '0.3.9'
|
||||
__version__ = '0.4.0'
|
||||
__author__ = 'RicterZ'
|
||||
__email__ = 'ricterzheng@gmail.com'
|
||||
|
@ -38,7 +38,7 @@ def banner():
|
||||
def cmd_parser():
|
||||
parser = OptionParser('\n nhentai --search [keyword] --download'
|
||||
'\n NHENTAI=http://h.loli.club nhentai --id [ID ...]'
|
||||
'\n nhentai --file [filename]'
|
||||
'\n nhentai --file [filename]'
|
||||
'\n\nEnvironment Variable:\n'
|
||||
' NHENTAI nhentai mirror url')
|
||||
# operation options
|
||||
@ -50,14 +50,6 @@ def cmd_parser():
|
||||
parser.add_option('--id', type='string', dest='id', action='store', help='doujinshi ids set, e.g. 1,2,3')
|
||||
parser.add_option('--search', '-s', type='string', dest='keyword', action='store',
|
||||
help='search doujinshi by keyword')
|
||||
parser.add_option('--tag', type='string', dest='tag', action='store', help='download doujinshi by tag')
|
||||
parser.add_option('--artist', type='string', dest='artist', action='store', help='download doujinshi by artist')
|
||||
parser.add_option('--character', type='string', dest='character', action='store',
|
||||
help='download doujinshi by character')
|
||||
parser.add_option('--parody', type='string', dest='parody', action='store', help='download doujinshi by parody')
|
||||
parser.add_option('--group', type='string', dest='group', action='store', help='download doujinshi by group')
|
||||
parser.add_option('--language', type='string', dest='language', action='store',
|
||||
help='download doujinshi by language')
|
||||
parser.add_option('--favorites', '-F', action='store_true', dest='favorites',
|
||||
help='list or download your favorites.')
|
||||
|
||||
@ -95,12 +87,16 @@ def cmd_parser():
|
||||
help='generate a main viewer contain all the doujin in the folder')
|
||||
parser.add_option('--cbz', '-C', dest='is_cbz', action='store_true',
|
||||
help='generate Comic Book CBZ File')
|
||||
parser.add_option('--pdf', '-P', dest='is_pdf', action='store_true',
|
||||
help='generate PDF file')
|
||||
parser.add_option('--rm-origin-dir', dest='rm_origin_dir', action='store_true', default=False,
|
||||
help='remove downloaded doujinshi dir when generated CBZ file.')
|
||||
help='remove downloaded doujinshi dir when generated CBZ or PDF file.')
|
||||
|
||||
# nhentai options
|
||||
parser.add_option('--cookie', type='str', dest='cookie', action='store',
|
||||
help='set cookie of nhentai to bypass Google recaptcha')
|
||||
parser.add_option('--language', type='str', dest='language', action='store',
|
||||
help='set default language to parse doujinshis')
|
||||
parser.add_option('--save-download-history', dest='is_save_download_history', action='store_true',
|
||||
default=False, help='save downloaded doujinshis, whose will be skipped if you re-download them')
|
||||
parser.add_option('--clean-download-history', action='store_true', default=False, dest='clean_download_history',
|
||||
@ -120,9 +116,7 @@ def cmd_parser():
|
||||
generate_html()
|
||||
exit(0)
|
||||
|
||||
if args.main_viewer and not args.id and not args.keyword and \
|
||||
not args.tag and not args.artist and not args.character and \
|
||||
not args.parody and not args.group and not args.language and not args.favorites:
|
||||
if args.main_viewer and not args.id and not args.keyword and not args.favorites:
|
||||
generate_main_html()
|
||||
exit(0)
|
||||
|
||||
@ -151,6 +145,25 @@ def cmd_parser():
|
||||
logger.info('Cookie saved.')
|
||||
exit(0)
|
||||
|
||||
if os.path.exists(constant.NHENTAI_LANGUAGE) and not args.language:
|
||||
with open(constant.NHENTAI_LANGUAGE, 'r') as f:
|
||||
constant.LANGUAGE = f.read()
|
||||
args.language = f.read()
|
||||
|
||||
if args.language:
|
||||
try:
|
||||
if not os.path.exists(constant.NHENTAI_HOME):
|
||||
os.mkdir(constant.NHENTAI_HOME)
|
||||
|
||||
with open(constant.NHENTAI_LANGUAGE, 'w') as f:
|
||||
f.write(args.language)
|
||||
except Exception as e:
|
||||
logger.error('Cannot create NHENTAI_HOME: {}'.format(str(e)))
|
||||
exit(1)
|
||||
|
||||
logger.info('Default language now is {}.'.format(args.language))
|
||||
exit(0)
|
||||
|
||||
if os.path.exists(constant.NHENTAI_PROXY):
|
||||
with open(constant.NHENTAI_PROXY, 'r') as f:
|
||||
link = f.read()
|
||||
@ -189,15 +202,12 @@ def cmd_parser():
|
||||
_ = [i.strip() for i in f.readlines()]
|
||||
args.id = set(int(i) for i in _ if i.isdigit())
|
||||
|
||||
if (args.is_download or args.is_show) and not args.id and not args.keyword and \
|
||||
not args.tag and not args.artist and not args.character and \
|
||||
not args.parody and not args.group and not args.language and not args.favorites:
|
||||
if (args.is_download or args.is_show) and not args.id and not args.keyword and not args.favorites:
|
||||
logger.critical('Doujinshi id(s) are required for downloading')
|
||||
parser.print_help()
|
||||
exit(1)
|
||||
|
||||
if not args.keyword and not args.id and not args.tag and not args.artist and \
|
||||
not args.character and not args.parody and not args.group and not args.language and not args.favorites:
|
||||
if not args.keyword and not args.id and not args.favorites:
|
||||
parser.print_help()
|
||||
exit(1)
|
||||
|
||||
|
@ -6,12 +6,12 @@ import platform
|
||||
import time
|
||||
|
||||
from nhentai.cmdline import cmd_parser, banner
|
||||
from nhentai.parser import doujinshi_parser, search_parser, print_doujinshi, favorites_parser, tag_parser
|
||||
from nhentai.parser import doujinshi_parser, search_parser, print_doujinshi, favorites_parser
|
||||
from nhentai.doujinshi import Doujinshi
|
||||
from nhentai.downloader import Downloader
|
||||
from nhentai.logger import logger
|
||||
from nhentai.constant import BASE_URL
|
||||
from nhentai.utils import generate_html, generate_cbz, generate_main_html, check_cookie, signal_handler, DB
|
||||
from nhentai.utils import generate_html, generate_cbz, generate_main_html, generate_pdf, check_cookie, signal_handler, DB
|
||||
|
||||
|
||||
def main():
|
||||
@ -21,13 +21,12 @@ def main():
|
||||
|
||||
from nhentai.constant import PROXY
|
||||
# constant.PROXY will be changed after cmd_parser()
|
||||
if PROXY != {}:
|
||||
if PROXY:
|
||||
logger.info('Using proxy: {0}'.format(PROXY))
|
||||
|
||||
# check your cookie
|
||||
check_cookie()
|
||||
|
||||
index = 0
|
||||
doujinshis = []
|
||||
doujinshi_ids = []
|
||||
doujinshi_list = []
|
||||
@ -38,33 +37,16 @@ def main():
|
||||
|
||||
doujinshis = favorites_parser(options.page_range)
|
||||
|
||||
elif options.tag:
|
||||
doujinshis = tag_parser(options.tag, sorting=options.sorting, max_page=options.max_page)
|
||||
|
||||
elif options.artist:
|
||||
index = 1
|
||||
|
||||
elif options.character:
|
||||
index = 2
|
||||
|
||||
elif options.parody:
|
||||
index = 3
|
||||
|
||||
elif options.group:
|
||||
index = 4
|
||||
|
||||
elif options.language:
|
||||
index = 5
|
||||
|
||||
elif options.keyword:
|
||||
from nhentai.constant import LANGUAGE
|
||||
if LANGUAGE:
|
||||
logger.info('Using deafult language: {0}'.format(LANGUAGE))
|
||||
options.keyword += ', language:{}'.format(LANGUAGE)
|
||||
doujinshis = search_parser(options.keyword, sorting=options.sorting, page=options.page)
|
||||
|
||||
elif not doujinshi_ids:
|
||||
doujinshi_ids = options.id
|
||||
|
||||
if index:
|
||||
doujinshis = tag_parser(options.language, max_page=options.max_page, index=index)
|
||||
|
||||
print_doujinshi(doujinshis)
|
||||
if options.is_download and doujinshis:
|
||||
doujinshi_ids = [i['id'] for i in doujinshis]
|
||||
@ -100,10 +82,12 @@ def main():
|
||||
with DB() as db:
|
||||
db.add_one(doujinshi.id)
|
||||
|
||||
if not options.is_nohtml and not options.is_cbz:
|
||||
if not options.is_nohtml and not options.is_cbz and not options.is_pdf:
|
||||
generate_html(options.output_dir, doujinshi)
|
||||
elif options.is_cbz:
|
||||
generate_cbz(options.output_dir, doujinshi, options.rm_origin_dir)
|
||||
elif options.is_pdf:
|
||||
generate_pdf(options.output_dir, doujinshi, options.rm_origin_dir)
|
||||
|
||||
if options.main_viewer:
|
||||
generate_main_html(options.output_dir)
|
||||
@ -119,6 +103,5 @@ def main():
|
||||
|
||||
signal.signal(signal.SIGINT, signal_handler)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
|
@ -12,17 +12,10 @@ except ImportError:
|
||||
BASE_URL = os.getenv('NHENTAI', 'https://nhentai.net')
|
||||
|
||||
__api_suspended_DETAIL_URL = '%s/api/gallery' % BASE_URL
|
||||
__api_suspended_SEARCH_URL = '%s/api/galleries/search' % BASE_URL
|
||||
|
||||
DETAIL_URL = '%s/g' % BASE_URL
|
||||
SEARCH_URL = '%s/search/' % BASE_URL
|
||||
SEARCH_URL = '%s/api/galleries/search' % BASE_URL
|
||||
|
||||
TAG_URL = ['%s/tag' % BASE_URL,
|
||||
'%s/artist' % BASE_URL,
|
||||
'%s/character' % BASE_URL,
|
||||
'%s/parody' % BASE_URL,
|
||||
'%s/group' % BASE_URL,
|
||||
'%s/language' % BASE_URL]
|
||||
|
||||
TAG_API_URL = '%s/api/galleries/tagged' % BASE_URL
|
||||
LOGIN_URL = '%s/login/' % BASE_URL
|
||||
@ -35,8 +28,10 @@ IMAGE_URL = '%s://i.%s/galleries' % (u.scheme, u.hostname)
|
||||
NHENTAI_HOME = os.path.join(os.getenv('HOME', tempfile.gettempdir()), '.nhentai')
|
||||
NHENTAI_PROXY = os.path.join(NHENTAI_HOME, 'proxy')
|
||||
NHENTAI_COOKIE = os.path.join(NHENTAI_HOME, 'cookie')
|
||||
NHENTAI_LANGUAGE = os.path.join(NHENTAI_HOME, 'language')
|
||||
NHENTAI_HISTORY = os.path.join(NHENTAI_HOME, 'history.sqlite3')
|
||||
|
||||
PROXY = {}
|
||||
|
||||
COOKIE = ''
|
||||
LANGUAGE = ''
|
||||
|
@ -120,15 +120,15 @@ def page_range_parser(page_range, max_page_num):
|
||||
else:
|
||||
try:
|
||||
left = int(range_str[:idx])
|
||||
right = int(range_str[idx+1:])
|
||||
right = int(range_str[idx + 1:])
|
||||
if right > max_page_num:
|
||||
right = max_page_num
|
||||
for page in range(left, right+1):
|
||||
for page in range(left, right + 1):
|
||||
pages.add(page)
|
||||
except ValueError:
|
||||
logger.error('page range({0}) is not valid'.format(page_range))
|
||||
|
||||
return list(pages)
|
||||
|
||||
return list(pages)
|
||||
|
||||
|
||||
def doujinshi_parser(id_):
|
||||
@ -143,7 +143,7 @@ def doujinshi_parser(id_):
|
||||
|
||||
try:
|
||||
response = request('get', url)
|
||||
if response.status_code in (200, ):
|
||||
if response.status_code in (200,):
|
||||
response = response.content
|
||||
else:
|
||||
logger.debug('Slow down and retry ({}) ...'.format(id_))
|
||||
@ -179,11 +179,9 @@ def doujinshi_parser(id_):
|
||||
doujinshi['ext'] = ext
|
||||
|
||||
pages = 0
|
||||
for _ in doujinshi_info.find_all('div', class_=''):
|
||||
pages = re.search('([\d]+) pages', _.text)
|
||||
if pages:
|
||||
pages = pages.group(1)
|
||||
break
|
||||
for _ in doujinshi_info.find_all('div', class_='tag-container field-name'):
|
||||
if re.search('Pages:', _.text):
|
||||
pages = _.find('span', class_='name').string
|
||||
doujinshi['pages'] = int(pages)
|
||||
|
||||
# gain information of the doujinshi
|
||||
@ -192,7 +190,7 @@ def doujinshi_parser(id_):
|
||||
for field in information_fields:
|
||||
field_name = field.contents[0].strip().strip(':')
|
||||
if field_name in needed_fields:
|
||||
data = [sub_field.contents[0].strip() for sub_field in
|
||||
data = [sub_field.find('span', attrs={'class': 'name'}).contents[0].strip() for sub_field in
|
||||
field.find_all('a', attrs={'class': 'tag'})]
|
||||
doujinshi[field_name.lower()] = ', '.join(data)
|
||||
|
||||
@ -202,7 +200,7 @@ def doujinshi_parser(id_):
|
||||
return doujinshi
|
||||
|
||||
|
||||
def search_parser(keyword, sorting='date', page=1):
|
||||
def old_search_parser(keyword, sorting='date', page=1):
|
||||
logger.debug('Searching doujinshis of keyword {0}'.format(keyword))
|
||||
response = request('get', url=constant.SEARCH_URL, params={'q': keyword, 'page': page, 'sort': sorting}).content
|
||||
|
||||
@ -222,50 +220,15 @@ def print_doujinshi(doujinshi_list):
|
||||
tabulate(tabular_data=doujinshi_list, headers=headers, tablefmt='rst'))
|
||||
|
||||
|
||||
def tag_parser(tag_name, sorting='date', max_page=1, index=0):
|
||||
result = []
|
||||
tag_name = tag_name.lower()
|
||||
if ',' in tag_name:
|
||||
tag_name = [i.strip().replace(' ', '-') for i in tag_name.split(',')]
|
||||
else:
|
||||
tag_name = tag_name.strip().replace(' ', '-')
|
||||
if sorting == 'date':
|
||||
sorting = ''
|
||||
|
||||
for p in range(1, max_page + 1):
|
||||
if sys.version_info >= (3, 0, 0):
|
||||
unicode_ = str
|
||||
else:
|
||||
unicode_ = unicode
|
||||
|
||||
if isinstance(tag_name, (str, unicode_)):
|
||||
logger.debug('Fetching page {0} for doujinshi with tag \'{1}\''.format(p, tag_name))
|
||||
response = request('get', url='%s/%s/%s?page=%d' % (constant.TAG_URL[index], tag_name, sorting, p)).content
|
||||
result += _get_title_and_id(response)
|
||||
else:
|
||||
for i in tag_name:
|
||||
logger.debug('Fetching page {0} for doujinshi with tag \'{1}\''.format(p, i))
|
||||
response = request('get',
|
||||
url='%s/%s/%s?page=%d' % (constant.TAG_URL[index], i, sorting, p)).content
|
||||
result += _get_title_and_id(response)
|
||||
|
||||
if not result:
|
||||
logger.error('Cannot find doujinshi id of tag \'{0}\''.format(tag_name))
|
||||
return
|
||||
|
||||
if not result:
|
||||
logger.warn('No results for tag \'{}\''.format(tag_name))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __api_suspended_search_parser(keyword, sorting, page):
|
||||
def search_parser(keyword, sorting, page):
|
||||
logger.debug('Searching doujinshis using keywords {0}'.format(keyword))
|
||||
keyword = '+'.join([i.strip().replace(' ', '-').lower() for i in keyword.split(',')])
|
||||
result = []
|
||||
i = 0
|
||||
while i < 5:
|
||||
try:
|
||||
response = request('get', url=constant.SEARCH_URL, params={'query': keyword, 'page': page, 'sort': sorting}).json()
|
||||
url = request('get', url=constant.SEARCH_URL, params={'query': keyword, 'page': page, 'sort': sorting}).url
|
||||
response = request('get', url.replace('%2B', '+')).json()
|
||||
except Exception as e:
|
||||
i += 1
|
||||
if not i < 5:
|
||||
@ -289,29 +252,6 @@ def __api_suspended_search_parser(keyword, sorting, page):
|
||||
return result
|
||||
|
||||
|
||||
def __api_suspended_tag_parser(tag_id, sorting, max_page=1):
|
||||
logger.info('Searching for doujinshi with tag id {0}'.format(tag_id))
|
||||
result = []
|
||||
response = request('get', url=constant.TAG_API_URL, params={'sort': sorting, 'tag_id': tag_id}).json()
|
||||
page = max_page if max_page <= response['num_pages'] else int(response['num_pages'])
|
||||
|
||||
for i in range(1, page + 1):
|
||||
logger.info('Getting page {} ...'.format(i))
|
||||
|
||||
if page != 1:
|
||||
response = request('get', url=constant.TAG_API_URL,
|
||||
params={'sort': sorting, 'tag_id': tag_id}).json()
|
||||
for row in response['result']:
|
||||
title = row['title']['english']
|
||||
title = title[:85] + '..' if len(title) > 85 else title
|
||||
result.append({'id': row['id'], 'title': title})
|
||||
|
||||
if not result:
|
||||
logger.warn('No results for tag id {}'.format(tag_id))
|
||||
|
||||
return result
|
||||
|
||||
|
||||
def __api_suspended_doujinshi_parser(id_):
|
||||
if not isinstance(id_, (int,)) and (isinstance(id_, (str,)) and not id_.isdigit()):
|
||||
raise Exception('Doujinshi id({0}) is not valid'.format(id_))
|
||||
|
@ -1,9 +1,10 @@
|
||||
# coding: utf-8
|
||||
import json
|
||||
import os
|
||||
from xml.sax.saxutils import escape
|
||||
|
||||
|
||||
def serialize(doujinshi, dir):
|
||||
def serialize_json(doujinshi, dir):
|
||||
metadata = {'title': doujinshi.name,
|
||||
'subtitle': doujinshi.info.subtitle}
|
||||
if doujinshi.info.date:
|
||||
@ -28,6 +29,51 @@ def serialize(doujinshi, dir):
|
||||
json.dump(metadata, f, separators=','':')
|
||||
|
||||
|
||||
def serialize_comicxml(doujinshi, dir):
|
||||
from iso8601 import parse_date
|
||||
with open(os.path.join(dir, 'ComicInfo.xml'), 'w') as f:
|
||||
f.write('<?xml version="1.0" encoding="utf-8"?>\n')
|
||||
f.write('<ComicInfo xmlns:xsd="http://www.w3.org/2001/XMLSchema" '
|
||||
'xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">\n')
|
||||
|
||||
xml_write_simple_tag(f, 'Manga', 'Yes')
|
||||
|
||||
xml_write_simple_tag(f, 'Title', doujinshi.name)
|
||||
xml_write_simple_tag(f, 'Summary', doujinshi.info.subtitle)
|
||||
xml_write_simple_tag(f, 'PageCount', doujinshi.pages)
|
||||
xml_write_simple_tag(f, 'URL', doujinshi.url)
|
||||
xml_write_simple_tag(f, 'NhentaiId', doujinshi.id)
|
||||
xml_write_simple_tag(f, 'Genre', doujinshi.info.categories)
|
||||
|
||||
xml_write_simple_tag(f, 'BlackAndWhite', 'No' if doujinshi.info.tags and 'full color' in doujinshi.info.tags else 'Yes')
|
||||
|
||||
if doujinshi.info.date:
|
||||
dt = parse_date(doujinshi.info.date)
|
||||
xml_write_simple_tag(f, 'Year', dt.year)
|
||||
xml_write_simple_tag(f, 'Month', dt.month)
|
||||
xml_write_simple_tag(f, 'Day', dt.day)
|
||||
if doujinshi.info.parodies:
|
||||
xml_write_simple_tag(f, 'Series', doujinshi.info.parodies)
|
||||
if doujinshi.info.characters:
|
||||
xml_write_simple_tag(f, 'Characters', doujinshi.info.characters)
|
||||
if doujinshi.info.tags:
|
||||
xml_write_simple_tag(f, 'Tags', doujinshi.info.tags)
|
||||
if doujinshi.info.artists:
|
||||
xml_write_simple_tag(f, 'Writer', ' & '.join([i.strip() for i in doujinshi.info.artists.split(',')]))
|
||||
# if doujinshi.info.groups:
|
||||
# metadata['group'] = [i.strip() for i in doujinshi.info.groups.split(',')]
|
||||
if doujinshi.info.languages:
|
||||
languages = [i.strip() for i in doujinshi.info.languages.split(',')]
|
||||
xml_write_simple_tag(f, 'Translated', 'Yes' if 'translated' in languages else 'No')
|
||||
[xml_write_simple_tag(f, 'Language', i) for i in languages if i != 'translated']
|
||||
|
||||
f.write('</ComicInfo>')
|
||||
|
||||
|
||||
def xml_write_simple_tag(f, name, val, indent=1):
|
||||
f.write('{}<{}>{}</{}>\n'.format(' ' * indent, name, escape(str(val)), name))
|
||||
|
||||
|
||||
def merge_json():
|
||||
lst = []
|
||||
output_dir = "./"
|
||||
|
@ -9,10 +9,11 @@ import zipfile
|
||||
import shutil
|
||||
import requests
|
||||
import sqlite3
|
||||
import img2pdf
|
||||
|
||||
from nhentai import constant
|
||||
from nhentai.logger import logger
|
||||
from nhentai.serializer import serialize, set_js_database
|
||||
from nhentai.serializer import serialize_json, serialize_comicxml, set_js_database
|
||||
|
||||
|
||||
def request(method, url, **kwargs):
|
||||
@ -86,7 +87,7 @@ def generate_html(output_dir='.', doujinshi_obj=None):
|
||||
js = readfile('viewer/scripts.js')
|
||||
|
||||
if doujinshi_obj is not None:
|
||||
serialize(doujinshi_obj, doujinshi_dir)
|
||||
serialize_json(doujinshi_obj, doujinshi_dir)
|
||||
name = doujinshi_obj.name
|
||||
if sys.version_info < (3, 0):
|
||||
name = doujinshi_obj.name.encode('utf-8')
|
||||
@ -102,9 +103,9 @@ def generate_html(output_dir='.', doujinshi_obj=None):
|
||||
with open(os.path.join(doujinshi_dir, 'index.html'), 'wb') as f:
|
||||
f.write(data.encode('utf-8'))
|
||||
|
||||
logger.log(15, 'HTML Viewer has been write to \'{0}\''.format(os.path.join(doujinshi_dir, 'index.html')))
|
||||
logger.log(15, 'HTML Viewer has been written to \'{0}\''.format(os.path.join(doujinshi_dir, 'index.html')))
|
||||
except Exception as e:
|
||||
logger.warning('Writen HTML Viewer failed ({})'.format(str(e)))
|
||||
logger.warning('Writing HTML Viewer failed ({})'.format(str(e)))
|
||||
|
||||
|
||||
def generate_main_html(output_dir='./'):
|
||||
@ -150,7 +151,7 @@ def generate_main_html(output_dir='./'):
|
||||
|
||||
image_html += element.format(FOLDER=folder, IMAGE=image, TITLE=title)
|
||||
if image_html == '':
|
||||
logger.warning('None index.html found, --gen-main paused.')
|
||||
logger.warning('No index.html found, --gen-main paused.')
|
||||
return
|
||||
try:
|
||||
data = main.format(STYLES=css, SCRIPTS=js, PICTURE=image_html)
|
||||
@ -163,14 +164,16 @@ def generate_main_html(output_dir='./'):
|
||||
shutil.copy(os.path.dirname(__file__)+'/viewer/logo.png', './')
|
||||
set_js_database()
|
||||
logger.log(
|
||||
15, 'Main Viewer has been write to \'{0}main.html\''.format(output_dir))
|
||||
15, 'Main Viewer has been written to \'{0}main.html\''.format(output_dir))
|
||||
except Exception as e:
|
||||
logger.warning('Writen Main Viewer failed ({})'.format(str(e)))
|
||||
logger.warning('Writing Main Viewer failed ({})'.format(str(e)))
|
||||
|
||||
|
||||
def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False):
|
||||
def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False, write_comic_info=False):
|
||||
if doujinshi_obj is not None:
|
||||
doujinshi_dir = os.path.join(output_dir, doujinshi_obj.filename)
|
||||
if write_comic_info:
|
||||
serialize_comicxml(doujinshi_obj, doujinshi_dir)
|
||||
cbz_filename = os.path.join(os.path.join(doujinshi_dir, '..'), '{}.cbz'.format(doujinshi_obj.filename))
|
||||
else:
|
||||
cbz_filename = './doujinshi.cbz'
|
||||
@ -188,7 +191,35 @@ def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False):
|
||||
if rm_origin_dir:
|
||||
shutil.rmtree(doujinshi_dir, ignore_errors=True)
|
||||
|
||||
logger.log(15, 'Comic Book CBZ file has been write to \'{0}\''.format(doujinshi_dir))
|
||||
logger.log(15, 'Comic Book CBZ file has been written to \'{0}\''.format(doujinshi_dir))
|
||||
|
||||
|
||||
def generate_pdf(output_dir='.', doujinshi_obj=None, rm_origin_dir=False):
|
||||
"""Write images to a PDF file using img2pdf."""
|
||||
if doujinshi_obj is not None:
|
||||
doujinshi_dir = os.path.join(output_dir, doujinshi_obj.filename)
|
||||
pdf_filename = os.path.join(
|
||||
os.path.join(doujinshi_dir, '..'),
|
||||
'{}.pdf'.format(doujinshi_obj.filename)
|
||||
)
|
||||
else:
|
||||
pdf_filename = './doujinshi.pdf'
|
||||
doujinshi_dir = '.'
|
||||
|
||||
file_list = os.listdir(doujinshi_dir)
|
||||
file_list.sort()
|
||||
|
||||
logger.info('Writing PDF file to path: {}'.format(pdf_filename))
|
||||
with open(pdf_filename, 'wb') as pdf_f:
|
||||
full_path_list = (
|
||||
[os.path.join(doujinshi_dir, image) for image in file_list]
|
||||
)
|
||||
pdf_f.write(img2pdf.convert(full_path_list))
|
||||
|
||||
if rm_origin_dir:
|
||||
shutil.rmtree(doujinshi_dir, ignore_errors=True)
|
||||
|
||||
logger.log(15, 'PDF file has been written to \'{0}\''.format(doujinshi_dir))
|
||||
|
||||
|
||||
def format_filename(s):
|
||||
@ -202,6 +233,9 @@ and append a file extension like '.txt', so I avoid the potential of using
|
||||
an invalid filename.
|
||||
|
||||
"""
|
||||
return s
|
||||
|
||||
# maybe you can use `--format` to select a suitable filename
|
||||
valid_chars = "-_.()[] %s%s" % (string.ascii_letters, string.digits)
|
||||
filename = ''.join(c for c in s if c in valid_chars)
|
||||
if len(filename) > 100:
|
||||
|
@ -148,7 +148,7 @@ blockquote {
|
||||
-webkit-user-select: none; /* Safari */
|
||||
-khtml-user-select: none; /* Konqueror HTML */
|
||||
-moz-user-select: none; /* Old versions of Firefox */
|
||||
ms-user-select: none; /* Internet Explorer/Edge */
|
||||
-ms-user-select: none; /* Internet Explorer/Edge */
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
@ -157,7 +157,7 @@ blockquote {
|
||||
padding: 5px 0px 5px 15px;
|
||||
text-decoration: none;
|
||||
font-size: 15px;
|
||||
color: #0d0d0d9;
|
||||
color: #0d0d0d;
|
||||
display: block;
|
||||
text-align: left;
|
||||
}
|
||||
@ -329,4 +329,4 @@ html.theme-black .gallery:hover .caption {
|
||||
html.theme-black .caption {
|
||||
background-color: #404040;
|
||||
color: #d9d9d9
|
||||
}
|
||||
}
|
||||
|
@ -4,3 +4,5 @@ BeautifulSoup4>=4.0.0
|
||||
threadpool>=1.2.7
|
||||
tabulate>=0.7.5
|
||||
future>=0.15.2
|
||||
img2pdf>=0.3.6
|
||||
iso8601 >= 0.1
|
||||
|
Loading…
x
Reference in New Issue
Block a user