├── .gitattributes
├── .gitignore
├── Epub.py
├── HbookerAPI
├── CryptoUtil.py
├── HttpUtil.py
├── UrlConstants.py
└── __init__.py
├── README.md
├── book.py
├── bookshelf.py
├── cache.py
├── config.py
├── instance.py
├── list.txt
├── msg.py
├── run.py
├── template.epub
└── token_parser.py
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Byte-compiled / optimized / DLL files
2 | __pycache__/
3 | *.py[cod]
4 | *$py.class
5 |
6 | # C extensions
7 | *.so
8 |
9 | # Distribution / packaging
10 | .Python
11 | build/
12 | Cache/
13 | Hbooker/
14 | LocalCache/
15 | develop-eggs/
16 | dist/
17 | downloads/
18 | eggs/
19 | .eggs/
20 | lib/
21 | lib64/
22 | parts/
23 | sdist/
24 | var/
25 | wheels/
26 | *.egg-info/
27 | .installed.cfg
28 | *.egg
29 |
30 | # PyInstaller
31 | # Usually these files are written by a python script from a template
32 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
33 | *.manifest
34 | *.spec
35 |
36 | # Installer logs
37 | pip-log.txt
38 | pip-delete-this-directory.txt
39 |
40 | # Unit test / coverage reports
41 | htmlcov/
42 | .tox/
43 | .coverage
44 | .coverage.*
45 | .cache
46 | nosetests.xml
47 | coverage.xml
48 | *.cover
49 | .hypothesis/
50 |
51 | # Translations
52 | *.mo
53 | *.pot
54 |
55 | # Django stuff:
56 | *.log
57 | local_settings.py
58 |
59 | # Flask stuff:
60 | instance/
61 | .webassets-cache
62 |
63 | # Scrapy stuff:
64 | .scrapy
65 |
66 | # Sphinx documentation
67 | docs/_build/
68 |
69 | # PyBuilder
70 | target/
71 |
72 | # Jupyter Notebook
73 | .ipynb_checkpoints
74 |
75 | # pyenv
76 | .python-version
77 |
78 | # celery beat schedule file
79 | celerybeat-schedule
80 |
81 | # SageMath parsed files
82 | *.sage.py
83 |
84 | # Environments
85 | .env
86 | .venv
87 | env/
88 | venv/
89 | ENV/
90 |
91 | # Spyder project settings
92 | .spyderproject
93 | .spyproject
94 |
95 | # Rope project settings
96 | .ropeproject
97 |
98 | # mkdocs documentation
99 | /site
100 |
101 | # mypy
102 | .mypy_cache/
103 |
104 | .idea/
105 | HbookerAppNovelDownloader - Config.json
106 | Backup/
107 | Books/
108 | com.kuangxiangciweimao.novel_preferences.xml
109 |
--------------------------------------------------------------------------------
/Epub.py:
--------------------------------------------------------------------------------
1 | from datetime import datetime
2 | from shutil import copyfile
3 | from instance import *
4 | import urllib.request
5 | import zipfile
6 | import codecs
7 | import time
8 | import msg
9 | import os
10 | import re
11 |
12 | image_get_retry = 10
13 | image_download_display_delay = 1
14 | last_image_dl_start_time = None
15 |
16 |
17 | def str_mid(string: str, left: str, right: str, start=None, end=None):
18 | pos1 = string.find(left, start, end)
19 | if pos1 > -1:
20 | pos2 = string.find(right, pos1 + len(left), end)
21 | if pos2 > -1:
22 | return string[pos1 + len(left): pos2]
23 | return ''
24 |
25 |
26 | def get_all_files(dir_path: str):
27 | result = list()
28 | for _name in os.listdir(dir_path):
29 | if os.path.isdir(dir_path + '/' + _name):
30 | result.extend(get_all_files(dir_path + '/' + _name))
31 | else:
32 | result.append(dir_path + '/' + _name)
33 | return result
34 |
35 |
36 | def text_to_html_element_escape(text: str):
37 | return text.replace('&', '&').replace('<', '<').replace('>', '>')
38 |
39 |
40 | def html_element_to_text_unescape(element: str):
41 | return element.replace('&', '&').replace('<', '<').replace('>', '>')
42 |
43 |
44 | def backup_copy_add_suffix_if_exists_add_index(file_path: str, suffix: str):
45 | # fix_illegal_book_name_dir
46 | backup_dir = os.path.join(Vars.cfg.data['backup_dir'], re.sub('^(.+)\\.\\s*$', '\\1.',
47 | os.path.splitext(os.path.basename(file_path))[0]))
48 | file_basename = os.path.splitext(os.path.basename(file_path))[0] + ' ' + suffix
49 | file_ext = os.path.splitext(file_path)[1]
50 | if not os.path.isdir(backup_dir):
51 | os.makedirs(backup_dir)
52 | if os.path.exists(file_path):
53 | if os.path.exists(os.path.join(backup_dir, file_basename) + file_ext):
54 | index = 1
55 | while os.path.exists(os.path.join(backup_dir, file_basename) + ' ' + str(index) + file_ext):
56 | index += 1
57 | copyfile(file_path, os.path.join(backup_dir, file_basename) + ' ' + str(index) + file_ext)
58 | else:
59 | copyfile(file_path, os.path.join(backup_dir, file_basename) + file_ext)
60 | else:
61 | # 出現錯誤
62 | print("error: file dose not exists: " + file_path)
63 |
64 |
65 | def download_progress_reporthook(count, block_size, total_size):
66 | # this prints the image downloading progress
67 | # this is to show that the script is working
68 | # the timer is "not thread" safe, but working as intended
69 | # the timer is reset every time when a new download is triggered
70 | # it will print the progress if it's taking too long
71 | global last_image_dl_start_time
72 | if count == 0:
73 | last_image_dl_start_time = time.time()
74 | return
75 | duration = time.time() - last_image_dl_start_time
76 | if duration < image_download_display_delay:
77 | return
78 | progress_size = int(count * block_size)
79 | percent = int(progress_size * 100 / total_size)
80 | print("\rDownloading Image... %d%%, %d KB" % (percent, progress_size / 1024), end='')
81 |
82 |
83 | class EpubFile:
84 | _filepath = ''
85 | tempdir = ''
86 | _content_opf = ''
87 | _content_opf_manifest = ''
88 | _content_opf_spine = ''
89 | _chapter_format_manifest = ''
90 | _image_format_manifest = ''
91 | _chapter_format_spine = ''
92 | _toc_ncx = ''
93 | _toc_ncx_navMap = ''
94 | _chapter_format_navMap = ''
95 | _chapter_format = ''
96 |
97 | def __init__(self, filepath: str, tempdir: str, book_id: str, book_title: str, book_author: str, use_old_epub=True):
98 | self._filepath = filepath
99 | self.tempdir = tempdir
100 | if not os.path.isdir(tempdir):
101 | os.makedirs(tempdir)
102 | _template = zipfile.ZipFile(os.path.abspath(os.path.join(os.path.dirname(__file__), 'template.epub')))
103 | self.cover_template = bytes(_template.read('OEBPS/Text/cover.xhtml')).decode()
104 | self._content_opf = bytes(_template.read('OEBPS/content.opf')).decode()
105 | self._chapter_format_manifest = str_mid(self._content_opf, '${chapter_format_manifest}={{{', '}}}')
106 | self._image_format_manifest = str_mid(self._content_opf, '${image_format_manifest}={{{', '}}}')
107 | self._chapter_format_spine = str_mid(self._content_opf, '${chapter_format_spine}={{{', '}}}')
108 | self._toc_ncx = bytes(_template.read('OEBPS/toc.ncx')).decode()
109 | self._chapter_format_navMap = str_mid(self._toc_ncx, '${chapter_format_navMap}={{{', '}}}')
110 | self._chapter_format = bytes(_template.read('OEBPS/Text/${chapter_format}.xhtml')).decode()
111 | if os.path.isfile(filepath) and use_old_epub:
112 | try:
113 | with zipfile.ZipFile(self._filepath, 'r', zipfile.ZIP_DEFLATED) as _file:
114 | try:
115 | self._content_opf = bytes(_file.read('OEBPS/content.opf')).decode()
116 | self._toc_ncx = bytes(_file.read('OEBPS/toc.ncx')).decode()
117 | _file.extractall(self.tempdir)
118 | except (KeyError, NameError, IOError):
119 | self._content_opf = bytes(_template.read('OEBPS/content.opf')).decode()
120 | self._toc_ncx = bytes(_template.read('OEBPS/toc.ncx')).decode()
121 | finally:
122 | self._content_opf_manifest = str_mid(self._content_opf, '