diff --git a/.travis.yml b/.travis.yml index b0b5eef..1e49f11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,3 +19,4 @@ script: - NHENTAI=https://nhentai.net nhentai --tag lolicon - NHENTAI=https://nhentai.net nhentai -F - NHENTAI=https://nhentai.net nhentai --file /tmp/test.txt + - nhentai --id=152503,146134 --gen-main --output=/tmp/ diff --git a/MANIFEST.in b/MANIFEST.in index 60ce4b8..cf58764 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,5 +1,7 @@ -include README.md -include requirements.txt -include nhentai/viewer/index.html -include nhentai/viewer/styles.css -include nhentai/viewer/scripts.js +include README.md +include requirements.txt +include nhentai/viewer/index.html +include nhentai/viewer/styles.css +include nhentai/viewer/scripts.js +include nhentai/viewer/main.html +include nhentai/viewer/main.css diff --git a/README.rst b/README.rst index 28b37ef..5369190 100644 --- a/README.rst +++ b/README.rst @@ -1,187 +1,187 @@ -nhentai -======= - -.. code-block:: - - _ _ _ _ - _ __ | | | | ___ _ __ | |_ __ _(_) - | '_ \| |_| |/ _ \ '_ \| __/ _` | | - | | | | _ | __/ | | | || (_| | | - |_| |_|_| |_|\___|_| |_|\__\__,_|_| - - -あなたも変態。 いいね? - -|travis| -|pypi| -|license| - - -nHentai is a CLI tool for downloading doujinshi from - -============ -Installation -============ -.. code-block:: - - git clone https://github.com/RicterZ/nhentai - cd nhentai - python setup.py install - -===================== -Installation (Gentoo) -===================== -.. code-block:: - - layman -fa glicOne - sudo emerge net-misc/nhentai - -===== -Usage -===== -**IMPORTANT**: To bypass the nhentai frequency limit, you should use `--cookie` option to store your cookie. - -*The default download folder will be the path where you run the command (CLI path).* - - -Set your nhentai cookie against captcha: - -.. code-block:: bash - - nhentai --cookie 'YOUR COOKIE FROM nhentai.net' - -Download specified doujinshi: - -.. code-block:: bash - - nhentai --id=123855,123866 - -Download doujinshi with ids specified in a file (doujinshi ids split by line): - -.. code-block:: bash - - nhentai --file=doujinshi.txt - -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 your favorites with delay: - -.. code-block:: bash - - nhentai --favorites --download --delay 1 - -Format output doujinshi folder name: - -.. code-block:: bash - - nhentai --id 261100 --format '[%i]%s' - -Supported doujinshi folder formatter: - -- %i: Doujinshi id -- %t: Doujinshi name -- %s: Doujinshi subtitle (translated name) -- %a: Doujinshi authors' name - - -Other options: - -.. code-block:: - - Options: - # Operation options - -h, --help show this help message and exit - -D, --download download doujinshi (for search results) - -S, --show just show the doujinshi information - - # Doujinshi options - --id=ID doujinshi ids set, e.g. 1,2,3 - -s KEYWORD, --search=KEYWORD - search doujinshi by keyword - --tag=TAG download doujinshi by tag - -F, --favorites list or download your favorites. - - # Multi-page options - --page=PAGE page number of search results - --max-page=MAX_PAGE The max page when recursive download tagged doujinshi - - # Download options - -o OUTPUT_DIR, --output=OUTPUT_DIR - output dir - -t THREADS, --threads=THREADS - thread count for downloading doujinshi - -T TIMEOUT, --timeout=TIMEOUT - timeout for downloading doujinshi - -d DELAY, --delay=DELAY - slow down between downloading every doujinshi - -p PROXY, --proxy=PROXY - uses a proxy, for example: http://127.0.0.1:1080 - -f FILE, --file=FILE read gallery IDs from file. - --format=NAME_FORMAT format the saved folder name - - # Generating options - --html generate a html viewer at current directory - --no-html don't generate HTML after downloading - -C, --cbz generate Comic Book CBZ File - --rm-origin-dir remove downloaded doujinshi dir when generated CBZ - file. - - # nHentai options - --cookie=COOKIE set cookie of nhentai to bypass Google recaptcha - - -============== -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:: - - i.h.loli.club -> i.nhentai.net - h.loli.club -> nhentai.net - -Set `NHENTAI` env var to your nhentai mirror. - -.. code-block:: bash - - NHENTAI=http://h.loli.club nhentai --id 123456 - - -.. image:: ./images/search.png?raw=true - :alt: nhentai - :align: center -.. image:: ./images/download.png?raw=true - :alt: nhentai - :align: center -.. image:: ./images/viewer.png?raw=true - :alt: nhentai - :align: center - -============ -あなたも変態 -============ -.. image:: ./images/image.jpg?raw=true - :alt: nhentai - :align: center - - - -.. |travis| image:: https://travis-ci.org/RicterZ/nhentai.svg?branch=master - :target: https://travis-ci.org/RicterZ/nhentai - -.. |pypi| image:: https://img.shields.io/pypi/dm/nhentai.svg - :target: https://pypi.org/project/nhentai/ - -.. |license| image:: https://img.shields.io/github/license/ricterz/nhentai.svg - :target: https://github.com/RicterZ/nhentai/blob/master/LICENSE +nhentai +======= + +.. code-block:: + + _ _ _ _ + _ __ | | | | ___ _ __ | |_ __ _(_) + | '_ \| |_| |/ _ \ '_ \| __/ _` | | + | | | | _ | __/ | | | || (_| | | + |_| |_|_| |_|\___|_| |_|\__\__,_|_| + + +あなたも変態。 いいね? + +|travis| +|pypi| +|license| + + +nHentai is a CLI tool for downloading doujinshi from + +============ +Installation +============ +.. code-block:: + + git clone https://github.com/RicterZ/nhentai + cd nhentai + python setup.py install + +===================== +Installation (Gentoo) +===================== +.. code-block:: + + layman -fa glicOne + sudo emerge net-misc/nhentai + +===== +Usage +===== +**IMPORTANT**: To bypass the nhentai frequency limit, you should use `--cookie` option to store your cookie. + +*The default download folder will be the path where you run the command (CLI path).* + + +Set your nhentai cookie against captcha: + +.. code-block:: bash + + nhentai --cookie 'YOUR COOKIE FROM nhentai.net' + +Download specified doujinshi: + +.. code-block:: bash + + nhentai --id=123855,123866 + +Download doujinshi with ids specified in a file (doujinshi ids split by line): + +.. code-block:: bash + + nhentai --file=doujinshi.txt + +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 your favorites with delay: + +.. code-block:: bash + + nhentai --favorites --download --delay 1 + +Format output doujinshi folder name: + +.. code-block:: bash + + nhentai --id 261100 --format '[%i]%s' + +Supported doujinshi folder formatter: + +- %i: Doujinshi id +- %t: Doujinshi name +- %s: Doujinshi subtitle (translated name) +- %a: Doujinshi authors' name + + +Other options: + +.. code-block:: + + Options: + # Operation options + -h, --help show this help message and exit + -D, --download download doujinshi (for search results) + -S, --show just show the doujinshi information + + # Doujinshi options + --id=ID doujinshi ids set, e.g. 1,2,3 + -s KEYWORD, --search=KEYWORD + search doujinshi by keyword + --tag=TAG download doujinshi by tag + -F, --favorites list or download your favorites. + + # Multi-page options + --page=PAGE page number of search results + --max-page=MAX_PAGE The max page when recursive download tagged doujinshi + + # Download options + -o OUTPUT_DIR, --output=OUTPUT_DIR + output dir + -t THREADS, --threads=THREADS + thread count for downloading doujinshi + -T TIMEOUT, --timeout=TIMEOUT + timeout for downloading doujinshi + -d DELAY, --delay=DELAY + slow down between downloading every doujinshi + -p PROXY, --proxy=PROXY + uses a proxy, for example: http://127.0.0.1:1080 + -f FILE, --file=FILE read gallery IDs from file. + --format=NAME_FORMAT format the saved folder name + + # Generating options + --html generate a html viewer at current directory + --no-html don't generate HTML after downloading + -C, --cbz generate Comic Book CBZ File + --rm-origin-dir remove downloaded doujinshi dir when generated CBZ + file. + + # nHentai options + --cookie=COOKIE set cookie of nhentai to bypass Google recaptcha + + +============== +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:: + + i.h.loli.club -> i.nhentai.net + h.loli.club -> nhentai.net + +Set `NHENTAI` env var to your nhentai mirror. + +.. code-block:: bash + + NHENTAI=http://h.loli.club nhentai --id 123456 + + +.. image:: ./images/search.png?raw=true + :alt: nhentai + :align: center +.. image:: ./images/download.png?raw=true + :alt: nhentai + :align: center +.. image:: ./images/viewer.png?raw=true + :alt: nhentai + :align: center + +============ +あなたも変態 +============ +.. image:: ./images/image.jpg?raw=true + :alt: nhentai + :align: center + + + +.. |travis| image:: https://travis-ci.org/RicterZ/nhentai.svg?branch=master + :target: https://travis-ci.org/RicterZ/nhentai + +.. |pypi| image:: https://img.shields.io/pypi/dm/nhentai.svg + :target: https://pypi.org/project/nhentai/ + +.. |license| image:: https://img.shields.io/github/license/ricterz/nhentai.svg + :target: https://github.com/RicterZ/nhentai/blob/master/LICENSE diff --git a/nhentai/cmdline.py b/nhentai/cmdline.py index 579c514..2b87250 100644 --- a/nhentai/cmdline.py +++ b/nhentai/cmdline.py @@ -10,7 +10,7 @@ except ImportError: import nhentai.constant as constant from nhentai import __version__ -from nhentai.utils import urlparse, generate_html +from nhentai.utils import urlparse, generate_html, generate_main_html from nhentai.logger import logger try: @@ -69,7 +69,7 @@ def cmd_parser(): parser.add_option('--delay', '-d', type='int', dest='delay', action='store', default=0, help='slow down between downloading every doujinshi') parser.add_option('--proxy', '-p', type='string', dest='proxy', action='store', default='', - help='uses a proxy, for example: http://127.0.0.1:1080') + help='store a proxy, for example: -p \'http://127.0.0.1:1080\'') parser.add_option('--file', '-f', type='string', dest='file', action='store', help='read gallery IDs from file.') parser.add_option('--format', type='string', dest='name_format', action='store', help='format the saved folder name', default='[%i][%a][%t]') @@ -79,6 +79,8 @@ def cmd_parser(): help='generate a html viewer at current directory') parser.add_option('--no-html', dest='is_nohtml', action='store_true', help='don\'t generate HTML after downloading') + parser.add_option('--gen-main', dest='main_viewer', action='store_true', + 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('--rm-origin-dir', dest='rm_origin_dir', action='store_true', default=False, @@ -101,6 +103,10 @@ def cmd_parser(): generate_html() exit(0) + if args.main_viewer and not args.favorites and args.id is None and not args.is_download: + generate_main_html() + exit(0) + if os.path.exists(os.path.join(constant.NHENTAI_HOME, 'cookie')): with open(os.path.join(constant.NHENTAI_HOME, 'cookie'), 'r') as f: constant.COOKIE = f.read() @@ -119,6 +125,29 @@ def cmd_parser(): logger.info('Cookie saved.') exit(0) + if os.path.exists(os.path.join(constant.NHENTAI_HOME, 'proxy')): + with open(os.path.join(constant.NHENTAI_HOME, 'proxy'), 'r') as f: + link = f.read() + constant.PROXY = {'http': link, 'https': link} + + if args.proxy: + try: + if not os.path.exists(constant.NHENTAI_HOME): + os.mkdir(constant.NHENTAI_HOME) + + proxy_url = urlparse(args.proxy) + if proxy_url.scheme not in ('http', 'https'): + logger.error('Invalid protocol \'{0}\' of proxy, ignored'.format(proxy_url.scheme)) + else: + with open(os.path.join(constant.NHENTAI_HOME, 'proxy'), 'w') as f: + f.write(args.proxy) + except Exception as e: + logger.error('Cannot create NHENTAI_HOME: {}'.format(str(e))) + exit(1) + + logger.info('Proxy \'{0}\' saved.'.format(args.proxy)) + exit(0) + ''' if args.login: try: @@ -162,11 +191,4 @@ def cmd_parser(): logger.critical('Maximum number of used threads is 15') exit(1) - if args.proxy: - proxy_url = urlparse(args.proxy) - if proxy_url.scheme not in ('http', 'https'): - logger.error('Invalid protocol \'{0}\' of proxy, ignored'.format(proxy_url.scheme)) - else: - constant.PROXY = {'http': args.proxy, 'https': args.proxy} - return args diff --git a/nhentai/command.py b/nhentai/command.py index e20c2b8..907ec29 100644 --- a/nhentai/command.py +++ b/nhentai/command.py @@ -11,13 +11,18 @@ 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 +from nhentai.utils import generate_html, generate_cbz, generate_main_html def main(): banner() - logger.info('Using mirror: {0}'.format(BASE_URL)) options = cmd_parser() + logger.info('Using mirror: {0}'.format(BASE_URL)) + + from nhentai.constant import PROXY + # constant.PROXY will be changed after cmd_parser() + if PROXY != {}: + logger.info('Using proxy: {0}'.format(PROXY)) doujinshi_ids = [] doujinshi_list = [] @@ -61,7 +66,8 @@ def main(): generate_html(options.output_dir, doujinshi) elif options.is_cbz: generate_cbz(options.output_dir, doujinshi, options.rm_origin_dir) - + if options.main_viewer: + generate_main_html(options.output_dir) if not platform.system() == 'Windows': logger.log(15, '🍻 All done.') else: diff --git a/nhentai/utils.py b/nhentai/utils.py index f884d2f..9cafe37 100644 --- a/nhentai/utils.py +++ b/nhentai/utils.py @@ -81,6 +81,66 @@ def generate_html(output_dir='.', doujinshi_obj=None): except Exception as e: logger.warning('Writen HTML Viewer failed ({})'.format(str(e))) +def generate_main_html(output_dir='.'): + """Generete a main html to show all the contain doujinshi. + With a link to thier `index.html`. + Default output folder will be the CLI path.""" + count = 0 + image_html = '' + main = readfile('viewer/main.html') + css = readfile('viewer/main.css') + element = '\n\ + \n' + + if output_dir == '': + os.chdir('.') + else: + os.chdir(output_dir) + # switch to given dir + doujinshi_dirs = next(os.walk('.'))[1] + # https://stackoverflow.com/questions/141291/how-to-list-only-top-level-directories-in-python + + for folder in doujinshi_dirs: + if folder[0] is not '[': + continue + files = os.listdir(folder) + if 'index.html' in files: + count += 1 + else: + logger.warning('{} folder does not have index.html (try use --html arg first).' + .format(folder)) + continue + image = files[0] # 001.jpg or 001.png + if folder is not None: + title = folder.replace('_', ' ') + # if sys.version_info > (3, 0): + # title = title.encode('utf-8') + else: + title = 'nHentai HTML Viewer' + image_html += element.format(FOLDER=folder, IMAGE=image, TITLE=title) + + if image_html == '': + logger.warning('None index.html found, --gen-main paused.') + return + try: + data = main.format(STYLES=css, COUNT=count, PICTURE=image_html) + if sys.version_info < (3, 0): + with open('./main.html', 'w') as f: + f.write(data) + else: + with open('./main.html', 'wb') as f: + f.write(data.encode('utf-8')) + logger.log( + 15, 'Main Viewer has been write to \'{0}main.html\''.format(output_dir)) + except Exception as e: + logger.warning('Writen Main Viewer failed ({})'.format(str(e))) def generate_cbz(output_dir='.', doujinshi_obj=None, rm_origin_dir=False): if doujinshi_obj is not None: diff --git a/nhentai/viewer/main.css b/nhentai/viewer/main.css new file mode 100644 index 0000000..60494e3 --- /dev/null +++ b/nhentai/viewer/main.css @@ -0,0 +1,255 @@ +/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */ +/* Original from https://static.nhentai.net/css/main_style.9bb9b703e601.css */ +html { + font-family: sans-serif; + line-height: 1.15; + -ms-text-size-adjust: 100%; + -webkit-text-size-adjust: 100% +} + +body { + margin: 0 +} + +h1 { + font-size: 2em; + margin: .67em 0 +} + +a { + background-color: transparent; + -webkit-text-decoration-skip: objects +} + +a:active,a:hover { + outline-width: 0 +} + +abbr[title] { + border-bottom: none; + text-decoration: underline; + text-decoration: underline dotted +} + +b,strong { + font-weight: inherit +} + +b,strong { + font-weight: bolder +} + +code,kbd,samp { + font-family: monospace,monospace; + font-size: 1em +} + +small { + font-size: 80% +} + +sub,sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline +} + +sub { + bottom: -.25em +} + +sup { + top: -.5em +} + +img { + border-style: none +} + +label { + display: block; + font-weight: 700; + text-align: justify; + white-space: nowrap +} + +html { + box-sizing: border-box +} + +*,:after,:before { + box-sizing: inherit +} + +h1,h2,h3,h4,h5,h6 { + font-weight: 700 +} + +body,html { + font-family: 'Noto Sans',sans-serif; + font-size: 14px; + line-height: 1.42857143; + height: 100%; + margin: 0; + text-align: center; + color: #34495e; + background-color: #fff; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +a { + text-decoration: none; + color: #34495e +} + +a:hover { + text-decoration: none; + color: #ed2553 +} + +a.count { + color: #999 +} + +a.bold { + font-weight: 700 +} + +code { + color: #ed2553; + border: 1px solid #fbd3dd; + background-color: #fef0f3 +} + +blockquote { + border: 0 +} + +.container { + display: block; + clear: both; + margin-left: auto; + margin-right: auto; + margin-bottom: 10px; + margin-top: 10px; + padding: 10px; + border-radius: 9px; + background-color: #ecf0f1; + width: 100%; + max-width: 1200px +} + +.gallery,.gallery-favorite,.thumb-container { + display: inline-block; + vertical-align: top +} + +.gallery img,.gallery-favorite img,.thumb-container img { + display: block; + max-width: 100%; + height: auto +} + +@media screen and (min-width: 980px) { + .gallery,.gallery-favorite,.thumb-container { + width:19%; + margin: 3px; + margin-bottom: 8px + } +} + +@media screen and (max-width: 979px) { + .gallery,.gallery-favorite,.thumb-container { + width:24%; + margin: 2px + } +} + +@media screen and (max-width: 772px) { + .gallery,.gallery-favorite,.thumb-container { + width:32%; + margin: 1.5px + } +} + +@media screen and (max-width: 500px) { + .gallery,.gallery-favorite,.thumb-container { + width:49%; + margin: .5px + } +} + +.gallery a,.gallery-favorite a { + display: block +} + +.gallery a img,.gallery-favorite a img { + position: absolute +} + +.caption { + line-height: 15px; + left: 0; + right: 0; + top: 100%; + position: absolute; + z-index: 10; + overflow: hidden; + width: 100%; + max-height: 34px; + padding: 3px; + background-color: #fff; + font-weight: 700; + display: block; + text-align: center; + text-decoration: none; + color: #34495e +} + +.gallery { + position: relative; + margin-bottom: 3em +} + +.gallery:hover .caption { + max-height: 100%; + box-shadow: 0 10px 20px rgba(100,100,100,.5) +} + +.gallery-favorite .gallery { + width: 100% +} + +html.theme-black,html.theme-black body { + color: #d9d9d9; + background-color: #0d0d0d +} + +html.theme-black #thumbnail-container,html.theme-black .container { + background-color: #1f1f1f +} + +html.theme-black #thumbnail-container .lazyload,html.theme-black .lazyload { + background-color: #262626 +} + +html.theme-black #thumbnail-container .lazyload-loading,html.theme-black .lazyload-loading { + background-color: #2e2e2e +} + +html.theme-black .gallery:hover .caption { + box-shadow: 0 10px 20px rgba(0,0,0,.5) +} + +html.theme-black .caption { + background-color: #404040; + color: #d9d9d9 +} + +html.theme-black code { + color: #ed2553; + border: none; + background-color: #292929 +} \ No newline at end of file diff --git a/nhentai/viewer/main.html b/nhentai/viewer/main.html new file mode 100644 index 0000000..b966780 --- /dev/null +++ b/nhentai/viewer/main.html @@ -0,0 +1,30 @@ + + + + + + + + nHentai » Viewer + + + + + +
+ +

Main Folder({COUNT})

+ +
+ + {PICTURE} + +
+ +
+ + + + \ No newline at end of file diff --git a/nhentai/viewer/scripts.js b/nhentai/viewer/scripts.js index 9dc516c..8e3f8bf 100644 --- a/nhentai/viewer/scripts.js +++ b/nhentai/viewer/scripts.js @@ -46,14 +46,17 @@ document.getElementById('image-container').onclick = event => { document.onkeypress = event => { switch (event.key.toLowerCase()) { // Previous Image + case 'w': case 'a': changePage(currentPage - 1); break; - + // Return to previous page + case 'q': + window.history.go(-1); + break; // Next Image case ' ': - case 'esc': // future close page function - case 'enter': + case 's': case 'd': changePage(currentPage + 1); break;