mirror of
				https://github.com/RicterZ/nhentai.git
				synced 2025-11-04 02:50:55 +01:00 
			
		
		
		
	Merge branch 'master' of github.com:RicterZ/nhentai
This commit is contained in:
		@@ -1,3 +1,5 @@
 | 
				
			|||||||
include README.md
 | 
					include README.md
 | 
				
			||||||
include requirements.txt
 | 
					include requirements.txt
 | 
				
			||||||
include nhentai/doujinshi.html
 | 
					include nhentai/viewer/index.html
 | 
				
			||||||
 | 
					include nhentai/viewer/styles.css
 | 
				
			||||||
 | 
					include nhentai/viewer/scripts.js
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -47,6 +47,8 @@ nhentai --login "username:password" --download
 | 
				
			|||||||
`--timeout`:指定下载图片的超时时间,默认为 30 秒。  
 | 
					`--timeout`:指定下载图片的超时时间,默认为 30 秒。  
 | 
				
			||||||
`--proxy`:指定下载的代理,例如: http://127.0.0.1:8080/
 | 
					`--proxy`:指定下载的代理,例如: http://127.0.0.1:8080/
 | 
				
			||||||
`--login`:nhentai 账号的“用户名:密码”组合
 | 
					`--login`:nhentai 账号的“用户名:密码”组合
 | 
				
			||||||
 | 
					`--nohtml`:nhentai Don't generate HTML
 | 
				
			||||||
 | 
					`--cbz`:nhentai Generate Comic Book CBZ file
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### nHentai Mirror
 | 
					### nHentai Mirror
 | 
				
			||||||
如果想用自建镜像下载 nhentai 的本子,需要搭建 nhentai.net 和 i.nhentai.net 的反向代理。  
 | 
					如果想用自建镜像下载 nhentai 的本子,需要搭建 nhentai.net 和 i.nhentai.net 的反向代理。  
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,3 +1,3 @@
 | 
				
			|||||||
__version__ = '0.2.12'
 | 
					__version__ = '0.2.14'
 | 
				
			||||||
__author__ = 'Ricter'
 | 
					__author__ = 'RicterZ'
 | 
				
			||||||
__email__ = 'ricterzheng@gmail.com'
 | 
					__email__ = 'ricterzheng@gmail.com'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,6 +2,7 @@
 | 
				
			|||||||
from __future__ import print_function
 | 
					from __future__ import print_function
 | 
				
			||||||
import sys
 | 
					import sys
 | 
				
			||||||
from optparse import OptionParser
 | 
					from optparse import OptionParser
 | 
				
			||||||
 | 
					from nhentai import __version__
 | 
				
			||||||
try:
 | 
					try:
 | 
				
			||||||
    from itertools import ifilter as filter
 | 
					    from itertools import ifilter as filter
 | 
				
			||||||
except ImportError:
 | 
					except ImportError:
 | 
				
			||||||
@@ -20,13 +21,13 @@ except NameError:
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def banner():
 | 
					def banner():
 | 
				
			||||||
    logger.info(u'''nHentai: あなたも変態。 いいね?
 | 
					    logger.info(u'''nHentai ver %s: あなたも変態。 いいね?
 | 
				
			||||||
       _   _            _        _
 | 
					       _   _            _        _
 | 
				
			||||||
 _ __ | | | | ___ _ __ | |_ __ _(_)
 | 
					 _ __ | | | | ___ _ __ | |_ __ _(_)
 | 
				
			||||||
| '_ \| |_| |/ _ \ '_ \| __/ _` | |
 | 
					| '_ \| |_| |/ _ \ '_ \| __/ _` | |
 | 
				
			||||||
| | | |  _  |  __/ | | | || (_| | |
 | 
					| | | |  _  |  __/ | | | || (_| | |
 | 
				
			||||||
|_| |_|_| |_|\___|_| |_|\__\__,_|_|
 | 
					|_| |_|_| |_|\___|_| |_|\__\__,_|_|
 | 
				
			||||||
''')
 | 
					''' % __version__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def cmd_parser():
 | 
					def cmd_parser():
 | 
				
			||||||
@@ -56,6 +57,12 @@ def cmd_parser():
 | 
				
			|||||||
    parser.add_option('--login', '-l', type='str', dest='login', action='store',
 | 
					    parser.add_option('--login', '-l', type='str', dest='login', action='store',
 | 
				
			||||||
                      help='username:password pair of nhentai account')
 | 
					                      help='username:password pair of nhentai account')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser.add_option('--nohtml', dest='is_nohtml', action='store_true',
 | 
				
			||||||
 | 
					                      help='Don\'t generate HTML')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    parser.add_option('--cbz', dest='is_cbz', action='store_true',
 | 
				
			||||||
 | 
					                      help='Generate Comic Book CBZ File')                      
 | 
				
			||||||
 | 
					                      
 | 
				
			||||||
    try:
 | 
					    try:
 | 
				
			||||||
        sys.argv = list(map(lambda x: unicode(x.decode(sys.stdin.encoding)), sys.argv))
 | 
					        sys.argv = list(map(lambda x: unicode(x.decode(sys.stdin.encoding)), sys.argv))
 | 
				
			||||||
    except (NameError, TypeError):
 | 
					    except (NameError, TypeError):
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,7 +10,7 @@ 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.utils import generate_html
 | 
					from nhentai.utils import generate_html, generate_cbz
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def main():
 | 
					def main():
 | 
				
			||||||
@@ -47,10 +47,13 @@ def main():
 | 
				
			|||||||
        for doujinshi in doujinshi_list:
 | 
					        for doujinshi in doujinshi_list:
 | 
				
			||||||
            doujinshi.downloader = downloader
 | 
					            doujinshi.downloader = downloader
 | 
				
			||||||
            doujinshi.download()
 | 
					            doujinshi.download()
 | 
				
			||||||
            generate_html(options.output_dir, doujinshi)
 | 
					            if not options.is_nohtml and not options.is_cbz:
 | 
				
			||||||
 | 
					                generate_html(options.output_dir, doujinshi)
 | 
				
			||||||
 | 
					            elif options.is_cbz:
 | 
				
			||||||
 | 
					                generate_cbz(options.output_dir, doujinshi)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        if not platform.system() == 'Windows':
 | 
					        if not platform.system() == 'Windows':
 | 
				
			||||||
            logger.log(15, '🍺 All done.')
 | 
					            logger.log(15, '🍻 All done.')
 | 
				
			||||||
        else:
 | 
					        else:
 | 
				
			||||||
            logger.log(15, 'All done.')
 | 
					            logger.log(15, 'All done.')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,126 +0,0 @@
 | 
				
			|||||||
<!DOCTYPE html>
 | 
					 | 
				
			||||||
<html lang="en">
 | 
					 | 
				
			||||||
<head>
 | 
					 | 
				
			||||||
    <meta charset="UTF-8">
 | 
					 | 
				
			||||||
    <title>{TITLE}</title>
 | 
					 | 
				
			||||||
    <style>
 | 
					 | 
				
			||||||
        html, body {{
 | 
					 | 
				
			||||||
            background-color: #e8e6e6;
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
            padding: 0;
 | 
					 | 
				
			||||||
            margin: 0;
 | 
					 | 
				
			||||||
            overflow: hidden;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .container img {{
 | 
					 | 
				
			||||||
            display: block;
 | 
					 | 
				
			||||||
            width: 100%;
 | 
					 | 
				
			||||||
            margin: 30px 0;
 | 
					 | 
				
			||||||
            padding:  10px;
 | 
					 | 
				
			||||||
            cursor: pointer;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .container {{
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
            overflow: scroll;
 | 
					 | 
				
			||||||
            background: #e8e6e6;
 | 
					 | 
				
			||||||
            width: 200px;
 | 
					 | 
				
			||||||
            padding: 30px;
 | 
					 | 
				
			||||||
            float: left;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .image {{
 | 
					 | 
				
			||||||
            margin-left: 260px;
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
            background: #222;
 | 
					 | 
				
			||||||
            text-align: center;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .image img {{
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .i a {{
 | 
					 | 
				
			||||||
            display: block;
 | 
					 | 
				
			||||||
            position: absolute;
 | 
					 | 
				
			||||||
            top: 0;
 | 
					 | 
				
			||||||
            width: 50%;
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .i {{
 | 
					 | 
				
			||||||
            position: relative;
 | 
					 | 
				
			||||||
            height: 100%;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        .current {{
 | 
					 | 
				
			||||||
            background: #BBB;
 | 
					 | 
				
			||||||
            border-radius: 10px;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
    </style>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    <script>
 | 
					 | 
				
			||||||
        function cursorfocus(elem) {{
 | 
					 | 
				
			||||||
            var container = document.getElementsByClassName('container')[0];
 | 
					 | 
				
			||||||
            container.scrollTop = elem.offsetTop - 500;
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        function getImage(type) {{
 | 
					 | 
				
			||||||
            var current = document.getElementsByClassName("current")[0];
 | 
					 | 
				
			||||||
            current.className = "image-item";
 | 
					 | 
				
			||||||
            var img_src = type == 1 ? current.getAttribute('attr-next') : current.getAttribute('attr-prev');
 | 
					 | 
				
			||||||
            if (img_src === "") {{
 | 
					 | 
				
			||||||
                img_src = current.src;
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
            var img_list = document.getElementsByClassName("image-item");
 | 
					 | 
				
			||||||
            for (i=0; i<img_list.length; i++) {{
 | 
					 | 
				
			||||||
                if (img_list[i].src.endsWith(img_src)) {{
 | 
					 | 
				
			||||||
                    img_list[i].className = "image-item current";
 | 
					 | 
				
			||||||
                    cursorfocus(img_list[i]);
 | 
					 | 
				
			||||||
                    break;
 | 
					 | 
				
			||||||
                }}
 | 
					 | 
				
			||||||
            }}
 | 
					 | 
				
			||||||
            var display = document.getElementById("dest");
 | 
					 | 
				
			||||||
            display.src = img_src;
 | 
					 | 
				
			||||||
            display.focus();
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
    </script>
 | 
					 | 
				
			||||||
</head>
 | 
					 | 
				
			||||||
<body>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<div class="container">
 | 
					 | 
				
			||||||
{IMAGES}</div>
 | 
					 | 
				
			||||||
<div class="image">
 | 
					 | 
				
			||||||
    <div class="i">
 | 
					 | 
				
			||||||
        <img src="" id="dest">
 | 
					 | 
				
			||||||
        <a href="javascript:getImage(-1)" style="left: 0;"></a>
 | 
					 | 
				
			||||||
        <a href="javascript:getImage(1)" style="left: 50%;"></a>
 | 
					 | 
				
			||||||
    </div>
 | 
					 | 
				
			||||||
</div>
 | 
					 | 
				
			||||||
</body>
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
<script>
 | 
					 | 
				
			||||||
    var img_list = document.getElementsByClassName("image-item");
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    var display = document.getElementById("dest");
 | 
					 | 
				
			||||||
    display.src = img_list[0].src;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    for (var i = 0; i < img_list.length; i++) {{
 | 
					 | 
				
			||||||
        img_list[i].addEventListener('click', function() {{
 | 
					 | 
				
			||||||
            var current = document.getElementsByClassName("current")[0];
 | 
					 | 
				
			||||||
            current.className = "image-item";
 | 
					 | 
				
			||||||
            this.className = "image-item current";
 | 
					 | 
				
			||||||
            var display = document.getElementById("dest");
 | 
					 | 
				
			||||||
            display.src = this.src;
 | 
					 | 
				
			||||||
            display.focus();
 | 
					 | 
				
			||||||
        }}, false);
 | 
					 | 
				
			||||||
    }}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    document.onkeypress = function(e) {{
 | 
					 | 
				
			||||||
        if (e.keyCode == 32) {{
 | 
					 | 
				
			||||||
            getImage(1);
 | 
					 | 
				
			||||||
        }}
 | 
					 | 
				
			||||||
    }}
 | 
					 | 
				
			||||||
</script>
 | 
					 | 
				
			||||||
</html>
 | 
					 | 
				
			||||||
@@ -27,7 +27,7 @@ def login_parser(username, password):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    s.get(constant.LOGIN_URL)
 | 
					    s.get(constant.LOGIN_URL)
 | 
				
			||||||
    content = s.get(constant.LOGIN_URL).content
 | 
					    content = s.get(constant.LOGIN_URL).content
 | 
				
			||||||
    html = BeautifulSoup(content, 'html.parser')
 | 
					    html = BeautifulSoup(content, 'html.parser').encode("ascii")
 | 
				
			||||||
    csrf_token_elem = html.find('input', attrs={'name': 'csrfmiddlewaretoken'})
 | 
					    csrf_token_elem = html.find('input', attrs={'name': 'csrfmiddlewaretoken'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if not csrf_token_elem:
 | 
					    if not csrf_token_elem:
 | 
				
			||||||
@@ -44,7 +44,7 @@ def login_parser(username, password):
 | 
				
			|||||||
        logger.error('Login failed, please check your username and password')
 | 
					        logger.error('Login failed, please check your username and password')
 | 
				
			||||||
        exit(1)
 | 
					        exit(1)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    html = BeautifulSoup(s.get(constant.FAV_URL).content, 'html.parser')
 | 
					    html = BeautifulSoup(s.get(constant.FAV_URL).content, 'html.parser').encode("ascii")
 | 
				
			||||||
    count = html.find('span', attrs={'class': 'count'})
 | 
					    count = html.find('span', attrs={'class': 'count'})
 | 
				
			||||||
    if not count:
 | 
					    if not count:
 | 
				
			||||||
        logger.error('Cannot get count of your favorites, maybe login failed.')
 | 
					        logger.error('Cannot get count of your favorites, maybe login failed.')
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,8 @@ from __future__ import unicode_literals, print_function
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import os
 | 
					import os
 | 
				
			||||||
import string
 | 
					import string
 | 
				
			||||||
 | 
					import zipfile
 | 
				
			||||||
 | 
					import shutil
 | 
				
			||||||
from nhentai.logger import logger
 | 
					from nhentai.logger import logger
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,48 +30,74 @@ def urlparse(url):
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    return urlparse(url)
 | 
					    return urlparse(url)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def readfile(path):
 | 
				
			||||||
 | 
					    loc = os.path.dirname(__file__)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    with open(os.path.join(loc, path), 'r') as file:
 | 
				
			||||||
 | 
					        return file.read()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def generate_html(output_dir='.', doujinshi_obj=None):
 | 
					def generate_html(output_dir='.', doujinshi_obj=None):
 | 
				
			||||||
    image_html = ''
 | 
					    image_html = ''
 | 
				
			||||||
    previous = ''
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if doujinshi_obj is not None:
 | 
					    if doujinshi_obj is not None:
 | 
				
			||||||
        doujinshi_dir = os.path.join(output_dir, format_filename('%s-%s' % (doujinshi_obj.id,
 | 
					        doujinshi_dir = os.path.join(output_dir, format_filename('%s-%s' % (doujinshi_obj.id,
 | 
				
			||||||
                                                                            doujinshi_obj.name[:200])))
 | 
					                                                                            str(doujinshi_obj.name[:200]))))
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        doujinshi_dir = '.'
 | 
					        doujinshi_dir = '.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    file_list = os.listdir(doujinshi_dir)
 | 
					    file_list = os.listdir(doujinshi_dir)
 | 
				
			||||||
    file_list.sort()
 | 
					    file_list.sort()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for index, image in enumerate(file_list):
 | 
					    for image in file_list:
 | 
				
			||||||
        if not os.path.splitext(image)[1] in ('.jpg', '.png'):
 | 
					        if not os.path.splitext(image)[1] in ('.jpg', '.png'):
 | 
				
			||||||
            continue
 | 
					            continue
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        try:
 | 
					        image_html += '<img src="{0}" class="image-item"/>\n'\
 | 
				
			||||||
            next_ = file_list[file_list.index(image) + 1]
 | 
					            .format(image)
 | 
				
			||||||
        except IndexError:
 | 
					 | 
				
			||||||
            next_ = ''
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        image_html += '<img src="{0}" class="image-item {1}" attr-prev="{2}" attr-next="{3}">\n'\
 | 
					    html = readfile('viewer/index.html')
 | 
				
			||||||
            .format(image, 'current' if index == 0 else '', previous, next_)
 | 
					    css = readfile('viewer/styles.css')
 | 
				
			||||||
        previous = image
 | 
					    js = readfile('viewer/scripts.js')
 | 
				
			||||||
 | 
					 | 
				
			||||||
    with open(os.path.join(os.path.dirname(__file__), 'doujinshi.html'), 'r') as template:
 | 
					 | 
				
			||||||
        html = template.read()
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if doujinshi_obj is not None:
 | 
					    if doujinshi_obj is not None:
 | 
				
			||||||
        title = doujinshi_obj.name
 | 
					        title = doujinshi_obj.name
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        title = 'nHentai HTML Viewer'
 | 
					        title = 'nHentai HTML Viewer'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    data = html.format(TITLE=title, IMAGES=image_html)
 | 
					    data = html.format(TITLE=title, IMAGES=image_html, SCRIPTS=js, STYLES=css)
 | 
				
			||||||
    with open(os.path.join(doujinshi_dir, 'index.html'), 'w') as f:
 | 
					    with open(os.path.join(doujinshi_dir, 'index.html'), 'w') as f:
 | 
				
			||||||
        f.write(data)
 | 
					        f.write(data)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    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 write to \'{0}\''.format(os.path.join(doujinshi_dir, 'index.html')))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					def generate_cbz(output_dir='.', doujinshi_obj=None):
 | 
				
			||||||
 | 
					    if doujinshi_obj is not None:
 | 
				
			||||||
 | 
					        doujinshi_dir = os.path.join(output_dir, format_filename('%s-%s' % (doujinshi_obj.id,
 | 
				
			||||||
 | 
					                                                                            str(doujinshi_obj.name[:200]))))    
 | 
				
			||||||
 | 
					        cbz_filename = os.path.join(output_dir, format_filename('%s-%s.cbz' % (doujinshi_obj.id,
 | 
				
			||||||
 | 
					                                                                            str(doujinshi_obj.name[:200]))))
 | 
				
			||||||
 | 
					    else:
 | 
				
			||||||
 | 
					        cbz_filename = './doujinshi.cbz'
 | 
				
			||||||
 | 
					        doujinshi_dir = '.'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    file_list = os.listdir(doujinshi_dir)
 | 
				
			||||||
 | 
					    file_list.sort()
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    with zipfile.ZipFile(cbz_filename, 'w') as cbz_pf:
 | 
				
			||||||
 | 
					        for image in file_list:
 | 
				
			||||||
 | 
					            image_path = os.path.join(doujinshi_dir, image)
 | 
				
			||||||
 | 
					            cbz_pf.write(image_path, image)
 | 
				
			||||||
 | 
					            
 | 
				
			||||||
 | 
					    shutil.rmtree(doujinshi_dir, ignore_errors=True)
 | 
				
			||||||
 | 
					    logger.log(15, 'Comic Book CBZ file has been write to \'{0}\''.format(doujinshi_dir))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def format_filename(s):
 | 
					def format_filename(s):
 | 
				
			||||||
    """Take a string and return a valid filename constructed from the string.
 | 
					    """Take a string and return a valid filename constructed from the string.
 | 
				
			||||||
Uses a whitelist approach: any characters not present in valid_chars are
 | 
					Uses a whitelist approach: any characters not present in valid_chars are
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										24
									
								
								nhentai/viewer/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										24
									
								
								nhentai/viewer/index.html
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,24 @@
 | 
				
			|||||||
 | 
					<!DOCTYPE html>
 | 
				
			||||||
 | 
					<html>
 | 
				
			||||||
 | 
					<head>
 | 
				
			||||||
 | 
					    <meta charset="UTF-8">
 | 
				
			||||||
 | 
					    <title>{TITLE}</title>
 | 
				
			||||||
 | 
					    <style>
 | 
				
			||||||
 | 
					{STYLES}
 | 
				
			||||||
 | 
					    </style>
 | 
				
			||||||
 | 
					</head>
 | 
				
			||||||
 | 
					<body>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<nav id="list">
 | 
				
			||||||
 | 
					{IMAGES}</nav>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<div id="image-container">
 | 
				
			||||||
 | 
					    <span id="page-num"></span>
 | 
				
			||||||
 | 
					    <div id="dest"></div>
 | 
				
			||||||
 | 
					</div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					<script>
 | 
				
			||||||
 | 
					{SCRIPTS}
 | 
				
			||||||
 | 
					</script>
 | 
				
			||||||
 | 
					</body>
 | 
				
			||||||
 | 
					</html>
 | 
				
			||||||
							
								
								
									
										62
									
								
								nhentai/viewer/scripts.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								nhentai/viewer/scripts.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,62 @@
 | 
				
			|||||||
 | 
					const pages = Array.from(document.querySelectorAll('img.image-item'));
 | 
				
			||||||
 | 
					let currentPage = 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function changePage(pageNum) {
 | 
				
			||||||
 | 
					    const previous = pages[currentPage];
 | 
				
			||||||
 | 
					    const current = pages[pageNum];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (current == null) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    
 | 
				
			||||||
 | 
					    previous.classList.remove('current');
 | 
				
			||||||
 | 
					    current.classList.add('current');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    currentPage = pageNum;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const display = document.getElementById('dest');
 | 
				
			||||||
 | 
					    display.style.backgroundImage = `url("${current.src}")`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    document.getElementById('page-num')
 | 
				
			||||||
 | 
					        .innerText = [
 | 
				
			||||||
 | 
					                (pageNum + 1).toLocaleString(),
 | 
				
			||||||
 | 
					                pages.length.toLocaleString()
 | 
				
			||||||
 | 
					            ].join('\u200a/\u200a');
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					changePage(0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('list').onclick = event => {
 | 
				
			||||||
 | 
					    if (pages.includes(event.target)) {
 | 
				
			||||||
 | 
					        changePage(pages.indexOf(event.target));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.getElementById('image-container').onclick = event => {
 | 
				
			||||||
 | 
					    const width = document.getElementById('image-container').clientWidth;
 | 
				
			||||||
 | 
					    const clickPos = event.clientX / width;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (clickPos < 0.5) {
 | 
				
			||||||
 | 
					        changePage(currentPage - 1);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					        changePage(currentPage + 1);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					document.onkeypress = event => {
 | 
				
			||||||
 | 
					    switch (event.key.toLowerCase()) {
 | 
				
			||||||
 | 
					        // Previous Image
 | 
				
			||||||
 | 
					        case 'arrowleft':
 | 
				
			||||||
 | 
					        case 'a':
 | 
				
			||||||
 | 
					            changePage(currentPage - 1);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // Next Image
 | 
				
			||||||
 | 
					        case ' ':
 | 
				
			||||||
 | 
					        case 'enter':
 | 
				
			||||||
 | 
					        case 'arrowright':
 | 
				
			||||||
 | 
					        case 'd':
 | 
				
			||||||
 | 
					            changePage(currentPage + 1);
 | 
				
			||||||
 | 
					            break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										69
									
								
								nhentai/viewer/styles.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										69
									
								
								nhentai/viewer/styles.css
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,69 @@
 | 
				
			|||||||
 | 
					*, *::after, *::before {
 | 
				
			||||||
 | 
					    box-sizing: border-box;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					img {
 | 
				
			||||||
 | 
					    vertical-align: middle;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					html, body {
 | 
				
			||||||
 | 
					    display: flex;
 | 
				
			||||||
 | 
					    background-color: #e8e6e6;
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
 | 
					    margin: 0;
 | 
				
			||||||
 | 
					    font-family: sans-serif;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#list {
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    overflow: auto;
 | 
				
			||||||
 | 
					    width: 260px;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#list img {
 | 
				
			||||||
 | 
					    width: 200px;
 | 
				
			||||||
 | 
					    padding: 10px;
 | 
				
			||||||
 | 
					    border-radius: 10px;
 | 
				
			||||||
 | 
					    margin: 15px 0;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#list img.current {
 | 
				
			||||||
 | 
					    background: #0003;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#image-container {
 | 
				
			||||||
 | 
					    flex: auto;
 | 
				
			||||||
 | 
					    height: 100vh;
 | 
				
			||||||
 | 
					    background: #222;
 | 
				
			||||||
 | 
					    color: #fff;
 | 
				
			||||||
 | 
					    text-align: center;
 | 
				
			||||||
 | 
					    cursor: pointer;
 | 
				
			||||||
 | 
					    -webkit-user-select: none;
 | 
				
			||||||
 | 
					    user-select: none;
 | 
				
			||||||
 | 
					    position: relative;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#image-container #dest {
 | 
				
			||||||
 | 
					    height: 100%;
 | 
				
			||||||
 | 
					    width: 100%;
 | 
				
			||||||
 | 
					    background-size: contain;
 | 
				
			||||||
 | 
					    background-repeat: no-repeat;
 | 
				
			||||||
 | 
					    background-position: center;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					#image-container #page-num {
 | 
				
			||||||
 | 
					    position: absolute;
 | 
				
			||||||
 | 
					    font-size: 18pt;
 | 
				
			||||||
 | 
					    left: 10px;
 | 
				
			||||||
 | 
					    bottom: 5px;
 | 
				
			||||||
 | 
					    font-weight: bold;
 | 
				
			||||||
 | 
					    opacity: 0.75;
 | 
				
			||||||
 | 
					    text-shadow: /* Duplicate the same shadow to make it very strong */
 | 
				
			||||||
 | 
					        0 0 2px #222,
 | 
				
			||||||
 | 
					        0 0 2px #222,
 | 
				
			||||||
 | 
					        0 0 2px #222;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user