├── Editer.py ├── README.md ├── lightnovel.py ├── lightnovel_gui.py ├── requirements.txt ├── resource ├── book.png ├── book.py ├── example1.png ├── example2.png ├── logo.png ├── logo.py ├── logo_big.png └── trans_base64.py └── utils.py /Editer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding:utf-8 -*- 3 | 4 | import requests # 用来抓取网页的html源码 5 | from bs4 import BeautifulSoup # 用于代替正则式 取源码中相应标签中的内容 6 | import time # 时间相关操作 7 | import os 8 | from rich.progress import track as tqdm 9 | from utils import * 10 | import zipfile 11 | import shutil 12 | import re 13 | import pickle 14 | from PIL import Image 15 | import time 16 | import threading 17 | from concurrent.futures import ThreadPoolExecutor, wait 18 | import pickle 19 | 20 | lock = threading.RLock() 21 | 22 | class Editer(object): 23 | def __init__(self, root_path, book_no='0000', volume_no=1): 24 | self.url_head = 'https://www.wenku8.net' 25 | self.header = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.67 Safari/537.36 Edg/87.0.664.47', 'referer': self.url_head} 26 | 27 | self.main_page = f'{self.url_head}/book/{book_no}.htm' 28 | self.color_chap_name = '插图' 29 | self.color_page_name = '彩页' 30 | self.html_buffer = dict() 31 | 32 | main_html = self.get_html(self.main_page ,is_gbk=True) 33 | self.cata_page = self.url_head + re.search(r'小说目录', main_html).group(1) 34 | cata_html = self.get_html(self.cata_page ,is_gbk=True) 35 | bf = BeautifulSoup(cata_html, 'html.parser') 36 | self.title = bf.find('div', {"id": "title"}).text 37 | self.author = bf.find('div', {"id": "info"}).text[3:] 38 | self.cover_url = re.findall(r'Access denied | www.wenku8.net used Cloudflare to restrict access' in req.text: 63 | time.sleep(3) 64 | else: 65 | break 66 | req = req.text 67 | return req 68 | 69 | def get_html_img(self, url, is_buffer=False): 70 | if is_buffer: 71 | while not url in self.html_buffer.keys(): 72 | time.sleep(0.1) 73 | if url in self.html_buffer.keys(): 74 | return self.html_buffer[url] 75 | while True: 76 | try: 77 | req=requests.get(url, headers=self.header) 78 | break 79 | except Exception as e: 80 | pass 81 | lock.acquire() 82 | self.html_buffer[url] = req.content 83 | lock.release() 84 | return req.content 85 | 86 | def make_folder(self): 87 | os.makedirs(self.temp_path, exist_ok=True) 88 | 89 | self.text_path = os.path.join(self.temp_path, 'OEBPS/Text') 90 | os.makedirs(self.text_path, exist_ok=True) 91 | 92 | self.img_path = os.path.join(self.temp_path, 'OEBPS/Images') 93 | os.makedirs(self.img_path, exist_ok=True) 94 | 95 | def get_index_url(self): 96 | self.volume = {} 97 | self.volume['chap_urls'] = [] 98 | self.volume['chap_names'] = [] 99 | volume_title_list, chap_names_list, chap_urls_list = self.get_chap_list(is_print=False) 100 | if len(volume_title_list)') and not line.startswith('
'): 153 | text_chap += (line+'\n') 154 | text_chap = BeautifulSoup(text_chap, 'html.parser').get_text() 155 | return text_chap 156 | 157 | def get_text(self): 158 | self.make_folder() 159 | img_strs = [] #记录后文中出现的所有图片位置 160 | text_no=0 #text_no正文章节编号(排除插图) chap_no 是所有章节编号 161 | for chap_no, (chap_name, chap_url) in enumerate(zip(self.volume['chap_names'], self.volume['chap_urls'])): 162 | if chap_name == self.color_chap_name: 163 | text = self.get_chap_text(chap_url, chap_name, True) 164 | text_html_color = text2htmls(self.color_page_name, text) 165 | else: 166 | text = self.get_chap_text(chap_url, chap_name) 167 | text_html = text2htmls(chap_name, text) 168 | textfile = self.text_path + f'/{str(text_no).zfill(2)}.xhtml' 169 | with open(textfile, 'w+', encoding='utf-8') as f: 170 | f.writelines(text_html) 171 | for text_line in text_html: 172 | img_str = re.search(r"", text_line) 173 | if img_str is not None: 174 | img_strs.append(img_str.group(0)) 175 | text_no += 1 176 | 177 | # 将彩页中后文已经出现的图片删除,避免重复 178 | if self.is_color_page: #判断彩页是否存在 179 | text_html_color_new = [] 180 | textfile = self.text_path + '/color.xhtml' 181 | for text_line in text_html_color: 182 | is_save = True 183 | for img_str in img_strs: 184 | if img_str in text_line: 185 | is_save = False 186 | break 187 | if is_save: 188 | text_html_color_new.append(text_line) 189 | 190 | with open(textfile, 'w+', encoding='utf-8') as f: 191 | f.writelines(text_html_color_new) 192 | 193 | 194 | def get_image(self, is_gui=False, signal=None): 195 | for url in self.img_url_map.keys(): 196 | self.pool.submit(self.get_html_img, url) 197 | img_path = self.img_path 198 | if is_gui: 199 | len_iter = len(self.img_url_map.items()) 200 | signal.emit('start') 201 | for i, (img_url, img_name) in enumerate(self.img_url_map.items()): 202 | content = self.get_html_img(img_url, is_buffer=True) 203 | with open(img_path+f'/{img_name}.jpg', 'wb') as f: 204 | f.write(content) #写入二进制内容 205 | signal.emit(int(100*(i+1)/len_iter)) 206 | signal.emit('end') 207 | else: 208 | for img_url, img_name in tqdm(self.img_url_map.items()): 209 | content = self.get_html_img(img_url) 210 | with open(img_path+f'/{img_name}.jpg', 'wb') as f: 211 | f.write(content) #写入二进制内容 212 | 213 | def get_cover(self, is_gui=False, signal=None): 214 | textfile = os.path.join(self.text_path, 'cover.xhtml') 215 | img_w, img_h = 300, 300 216 | try: 217 | imgfile = os.path.join(self.img_path, '00.jpg') 218 | img = Image.open(imgfile) 219 | img_w, img_h = img.size 220 | signal_msg = (imgfile, img_h, img_w) 221 | if is_gui: 222 | signal.emit(signal_msg) 223 | except Exception as e: 224 | print(e) 225 | print('没有封面图片,请自行用第三方EPUB编辑器手动添加封面') 226 | img_htmls = get_cover_html(img_w, img_h) 227 | with open(textfile, 'w+', encoding='utf-8') as f: 228 | f.writelines(img_htmls) 229 | 230 | def check_volume(self, is_gui=False, signal=None, editline=None): 231 | #没有检测到插图页,手动输入插图页标题 232 | if self.color_chap_name not in self.volume['chap_names']: 233 | self.is_color_page = False 234 | self.img_url_map[self.cover_url] = str(len(self.img_url_map)).zfill(2) 235 | print('**************') 236 | print('提示:没有彩页,但主页封面存在,将使用主页的封面图片作为本卷图书封面') 237 | print('**************') 238 | 239 | def hand_in_msg(self, error_msg='', is_gui=False, signal=None, editline=None): 240 | if is_gui: 241 | print(error_msg) 242 | signal.emit('hang') 243 | time.sleep(1) 244 | while not editline.isHidden(): 245 | time.sleep(1) 246 | content = editline.text() 247 | editline.clear() 248 | else: 249 | content = input(error_msg) 250 | return content 251 | 252 | def hand_in_url(self, chap_name, is_gui=False, signal=None, editline=None): 253 | error_msg = f'章节\"{chap_name}\"连接失效,请手动输入该章节链接(手机版“{self.url_head}”开头的链接):' 254 | return self.hand_in_msg(error_msg, is_gui, signal, editline) 255 | 256 | def hand_in_color_page_name(self, is_gui=False, signal=None, editline=None): 257 | if is_gui: 258 | error_msg = f'插图页面不存在,需要下拉选择插图页标题,若不需要插图页则保持本栏为空直接点确定:' 259 | editline.addItems(self.volume['chap_names']) 260 | editline.setCurrentIndex(-1) 261 | else: 262 | error_msg = f'插图页面不存在,需要手动输入插图页标题,若不需要插图页则不输入直接回车:' 263 | return self.hand_in_msg(error_msg, is_gui, signal, editline) 264 | 265 | def get_toc(self): 266 | if self.is_color_page: 267 | ind = self.volume["chap_names"].index(self.color_chap_name) 268 | self.volume["chap_names"].pop(ind) 269 | toc_htmls = get_toc_html(self.title, self.volume["chap_names"]) 270 | textfile = self.temp_path + '/OEBPS/toc.ncx' 271 | with open(textfile, 'w+', encoding='utf-8') as f: 272 | f.writelines(toc_htmls) 273 | 274 | def get_content(self): 275 | num_chap = len(self.volume["chap_names"]) 276 | num_img = len(os.listdir(self.img_path)) 277 | content_htmls = get_content_html(self.title + '-' + self.volume['book_name'], self.author, num_chap, num_img, self.is_color_page) 278 | textfile = self.temp_path + '/OEBPS/content.opf' 279 | with open(textfile, 'w+', encoding='utf-8') as f: 280 | f.writelines(content_htmls) 281 | 282 | def get_epub_head(self): 283 | mimetype = 'application/epub+zip' 284 | mimetypefile = self.temp_path + '/mimetype' 285 | with open(mimetypefile, 'w+', encoding='utf-8') as f: 286 | f.write(mimetype) 287 | metainf_folder = os.path.join(self.temp_path, 'META-INF') 288 | os.makedirs(metainf_folder, exist_ok=True) 289 | container = metainf_folder + '/container.xml' 290 | container_htmls = get_container_html() 291 | with open(container, 'w+', encoding='utf-8') as f: 292 | f.writelines(container_htmls) 293 | 294 | def get_epub(self): 295 | epub_file = (self.epub_path + '/' + check_chars(self.title) + '-' + check_chars(self.volume['book_name']) + '.epub') 296 | with zipfile.ZipFile(epub_file, "w", zipfile.ZIP_DEFLATED) as zf: 297 | for dirpath, _, filenames in os.walk(self.temp_path): 298 | fpath = dirpath.replace(self.temp_path,'') #这一句很重要,不replace的话,就从根目录开始复制 299 | fpath = fpath and fpath + os.sep or '' 300 | for filename in filenames: 301 | zf.write(os.path.join(dirpath, filename), fpath+filename) 302 | shutil.rmtree(self.temp_path) 303 | return epub_file -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 |
5 | 6 |

7 |      EPUB下载器 8 |

9 | 10 | 11 | 12 | 13 | 14 | [文库吧/轻小说文库](www.wenku8.net)(wenku8)网站小说下载,EPUB打包。 15 | 16 | 特性: 17 | 18 | * Fluent Design风格界面,下载进度与书籍封面显示,主题切换,下载目录自定义。 19 | * 前后端分离,同时支持命令行版本。 20 | * EPUB格式打包,支持多种阅读器。 21 | * 插图排版。 22 | * 书籍批量下载。 23 | * 图片多线程下载。 24 | * 缺失链接自动修复。 25 | * 自定义彩页。 26 | * ................... 27 | 28 | 29 | 有建议或bug可以提issue,由于软件更新频繁,可以加QQ群获得更多信息:563072544 30 | 31 | 图形界面使用[PyQt-Fluent-Widgets](https://pyqt-fluent-widgets.readthedocs.io/en/latest/index.html)界面编写。 32 | 33 | [release](https://github.com/ShqWW/lightnovel-download/releases/tag/downloader)页面发布了已经打包好的exe可执行程序,包括图形化版本和命令行版本(系统最低要求Windows 10)。 34 | 35 | 界面样例: 36 |
37 | 38 | 39 |
40 | 41 | ## 使用前安装需要的包 42 | ``` 43 | pip install -r requirements.txt -i https://pypi.org/simple/ 44 | ``` 45 | ## 使用命令行模式运行(无需安装图形界面库,支持Linux): 46 | ``` 47 | python bilinovel.py 48 | ``` 49 | 50 | ## 使用图形界面运行: 51 | ``` 52 | python bilinovel_gui.py 53 | ``` 54 | 55 | ## 使用pyinstaller打包: 56 | ``` 57 | pip install pyinstaller 58 | ``` 59 | ``` 60 | pyinstaller -F -w -i .\resource\logo.png --paths=C:\Users\haoru\bilinovel-download .\lightnovel_gui.py --clean 61 | ``` 62 | ``` 63 | pyinstaller -F -i .\resource\logo.png --paths=C:\Users\haoru\bilinovel-download .\lightnovel.py --clean 64 | ``` 65 | 66 | ## 相关项目: 67 | 68 | * [轻小说文库EPUB下载器](https://github.com/ShqWW/lightnovel-download) 69 | 70 | * [哔哩轻小说EPUB下载器](https://github.com/ShqWW/bilinovel-download) 71 | 72 | * [拷贝漫画EPUB下载器](https://github.com/ShqWW/copymanga-download) 73 | 74 | 75 | 76 | ## EPUB书籍编辑和管理工具推荐: 77 | 1. [Sigil](https://sigil-ebook.com/) 78 | 2. [Calibre](https://www.calibre-ebook.com/) 79 | 80 | -------------------------------------------------------------------------------- /lightnovel.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | from Editer import Editer 3 | import os 4 | import shutil 5 | from utils import * 6 | 7 | def parse_args(): 8 | """Parse input arguments.""" 9 | parser = argparse.ArgumentParser(description='config') 10 | parser.add_argument('--book_no', default='0000', type=str) 11 | parser.add_argument('--volume_no', default='1', type=int) 12 | parser.add_argument('--no_input', default=False, type=bool) 13 | args = parser.parse_args() 14 | return args 15 | 16 | 17 | def query_chaps(book_no): 18 | print('未输入卷号,将返回书籍目录信息......') 19 | editer = Editer(root_path='./out', book_no=book_no) 20 | print('*******************************') 21 | print(editer.title, editer.author) 22 | print('*******************************') 23 | editer.get_chap_list() 24 | print('*******************************') 25 | print('请输入所需要的卷号进行下载(多卷可以用英文逗号分隔或直接使用连字符,详情见说明)') 26 | 27 | temp_path = '' 28 | 29 | def delete_tmp(): 30 | print(temp_path) 31 | if os.path.exists(temp_path): 32 | shutil.rmtree(temp_path) 33 | 34 | def download_single_volume(root_path, 35 | book_no, 36 | volume_no, 37 | is_gui=False, 38 | hang_signal=None, 39 | progressring_signal=None, 40 | cover_signal=None, 41 | edit_line_hang=None): 42 | 43 | editer = Editer(root_path=root_path, book_no=book_no, volume_no=volume_no) 44 | print('正在积极地获取书籍信息....') 45 | success = editer.get_index_url() 46 | if not success: 47 | print('书籍信息获取失败') 48 | return 49 | print(editer.title + '-' + editer.volume['book_name'], editer.author) 50 | print('****************************') 51 | temp_path = editer.temp_path 52 | editer.check_volume(is_gui=is_gui, signal=hang_signal, editline=edit_line_hang) 53 | print('正在下载文本....') 54 | print('*********************') 55 | editer.get_text() 56 | print('*********************') 57 | 58 | print('正在下载插图.....................................') 59 | editer.get_image(is_gui=is_gui, signal=progressring_signal) 60 | 61 | print('正在编辑元数据....') 62 | editer.get_cover(is_gui=is_gui, signal=cover_signal) 63 | editer.get_toc() 64 | editer.get_content() 65 | editer.get_epub_head() 66 | 67 | print('正在生成电子书....') 68 | epub_file = editer.get_epub() 69 | print('生成成功!', f'电子书路径【{epub_file}】') 70 | 71 | 72 | def downloader_router(root_path, 73 | book_no, 74 | volume_no, 75 | is_gui=False, 76 | hang_signal=None, 77 | progressring_signal=None, 78 | cover_signal=None, 79 | edit_line_hang=None): 80 | is_multi_chap = False 81 | if len(book_no)==0: 82 | print('请检查输入是否完整正确!') 83 | return 84 | elif volume_no == '': 85 | query_chaps(book_no) 86 | return 87 | elif volume_no.isdigit(): 88 | volume_no = int(volume_no) 89 | if volume_no<=0: 90 | print('请检查输入是否完整正确!') 91 | return 92 | elif "-" in volume_no: 93 | start, end = map(str, volume_no.split("-")) 94 | if start.isdigit() and end.isdigit() and int(start)>0 and int(start) None: 35 | result = super().terminate() 36 | return result 37 | 38 | class EmittingStr(QObject): 39 | textWritten = pyqtSignal(str) # 定义一个发送str的信号 40 | def write(self, text): 41 | self.textWritten.emit(str(text)) 42 | def flush(self): 43 | pass 44 | def isatty(self): 45 | pass 46 | 47 | class SettingWidget(QFrame): 48 | def __init__(self, text: str, parent=None): 49 | super().__init__(parent=parent) 50 | 51 | self.parent = parent 52 | self.expandLayout = ExpandLayout(self) 53 | self.setObjectName(text.replace(' ', '-')) 54 | self.setting_group = SettingCardGroup(self.tr("下载设置"), self) 55 | 56 | self.download_path_card = PushSettingCard( 57 | self.tr('选择文件夹'), 58 | FIF.DOWNLOAD, 59 | self.tr("下载目录"), 60 | self.parent.out_path, 61 | self.setting_group 62 | ) 63 | self.themeMode = OptionsConfigItem( 64 | None, "ThemeMode", Theme.DARK, OptionsValidator(Theme), None) 65 | 66 | self.threadMode = OptionsConfigItem( 67 | None, "ThreadMode", True, BoolValidator()) 68 | 69 | self.theme_card = OptionsSettingCard( 70 | self.themeMode, 71 | FIF.BRUSH, 72 | self.tr('应用主题'), 73 | self.tr("更改外观"), 74 | texts=[ 75 | self.tr('亮'), self.tr('暗'), 76 | self.tr('跟随系统设置') 77 | ], 78 | parent=self.parent 79 | ) 80 | 81 | self.setting_group.addSettingCard(self.download_path_card) 82 | self.setting_group.addSettingCard(self.theme_card) 83 | self.expandLayout.setSpacing(28) 84 | self.expandLayout.setContentsMargins(20, 10, 20, 0) 85 | self.expandLayout.addWidget(self.setting_group) 86 | 87 | self.download_path_card.clicked.connect(self.download_path_changed) 88 | self.theme_card.optionChanged.connect(self.theme_changed) 89 | 90 | def download_path_changed(self): 91 | """ download folder card clicked slot """ 92 | self.parent.out_path = QFileDialog.getExistingDirectory( 93 | self, self.tr("Choose folder"), self.parent.out_path) 94 | self.download_path_card.contentLabel.setText(self.parent.out_path) 95 | 96 | def theme_changed(self): 97 | theme_name = self.theme_card.choiceLabel.text() 98 | self.parent.set_theme(theme_name) 99 | if os.path.exists('./config'): 100 | shutil.rmtree('./config') 101 | 102 | 103 | 104 | 105 | class HomeWidget(QFrame): 106 | 107 | progressring_signal = pyqtSignal(object) 108 | end_signal = pyqtSignal(object) 109 | hang_signal = pyqtSignal(object) 110 | clear_signal = pyqtSignal(object) 111 | cover_signal = pyqtSignal(object) 112 | 113 | def __init__(self, text: str, parent=None): 114 | super().__init__(parent=parent) 115 | self.setObjectName(text) 116 | self.parent = parent 117 | self.label_book = SubtitleLabel('书号:', self) 118 | self.label_volumn = SubtitleLabel('卷号:', self) 119 | self.editline_book = LineEdit(self) 120 | self.editline_volumn = LineEdit(self) 121 | validator = QRegExpValidator(QRegExp("\\d+")) # 正则表达式匹配阿拉伯数字 122 | self.editline_book.setValidator(validator) 123 | # self.editline_volumn.setValidator(validator) 124 | 125 | self.editline_book.setMaxLength(4) 126 | # self.editline_volumn.setMaxLength(2) 127 | 128 | # self.editline_book.setText('2059') 129 | # self.editline_volumn.setText('3') 130 | self.book_icon = QPixmap() 131 | self.book_icon.loadFromData(base64.b64decode(book_base64)) 132 | self.cover_w, self.cover_h = 152, 230 133 | 134 | self.label_cover = ImageLabel(self.book_icon, self) 135 | self.label_cover.setBorderRadius(8, 8, 8, 8) 136 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 137 | 138 | self.text_screen = TextEdit() 139 | self.text_screen.setReadOnly(True) 140 | self.text_screen.setFixedHeight(self.cover_h) 141 | 142 | self.progressRing = ProgressRing(self) 143 | self.progressRing.setValue(0) 144 | self.progressRing.setTextVisible(True) 145 | self.progressRing.setFixedSize(50, 50) 146 | 147 | self.btn_run = PushButton('确定', self) 148 | self.btn_run.setShortcut(Qt.Key_Return) 149 | self.btn_stop = PushButton('取消', self) 150 | self.btn_hang = PushButton('确定', self) 151 | 152 | self.editline_hang = EditableComboBox(self) 153 | self.gridLayout = QGridLayout(self) 154 | self.screen_layout = QGridLayout() 155 | self.btn_layout = QGridLayout() 156 | self.hang_layout = QGridLayout() 157 | 158 | self.label_book.setFont(font_label) 159 | self.label_volumn.setFont(font_label) 160 | self.editline_book.setFont(font_label) 161 | self.editline_volumn.setFont(font_label) 162 | self.text_screen.setFont(font_msg) 163 | self.editline_hang.setFont(font_msg) 164 | 165 | self.gridLayout.addWidget(self.editline_book, 0, 1) 166 | self.gridLayout.addWidget(self.editline_volumn, 1, 1) 167 | self.gridLayout.addWidget(self.label_book, 0, 0) 168 | self.gridLayout.addWidget(self.label_volumn, 1, 0) 169 | 170 | self.gridLayout.addLayout(self.btn_layout, 2, 1, 1, 1) 171 | self.btn_layout.addWidget(self.btn_run, 2, 1) 172 | self.btn_layout.addWidget(self.btn_stop, 2, 2) 173 | 174 | self.gridLayout.addLayout(self.screen_layout, 3, 0, 2, 2) 175 | 176 | self.screen_layout.addWidget(self.progressRing, 0, 1, Qt.AlignLeft|Qt.AlignBottom) 177 | self.screen_layout.addWidget(self.text_screen, 0, 0) 178 | self.screen_layout.addWidget(self.label_cover, 0, 1) 179 | 180 | 181 | 182 | self.gridLayout.addLayout(self.hang_layout, 5, 0, 1, 2) 183 | self.hang_layout.addWidget(self.editline_hang, 0, 0) 184 | self.hang_layout.addWidget(self.btn_hang, 0, 1) 185 | 186 | self.screen_layout.setContentsMargins(0,0,0,0) 187 | self.btn_layout.setContentsMargins(0,0,0,0) 188 | self.gridLayout.setContentsMargins(20, 10, 20, 10) 189 | 190 | self.btn_run.clicked.connect(self.process_start) 191 | self.btn_stop.clicked.connect(self.process_stop) 192 | self.btn_hang.clicked.connect(self.process_continue) 193 | 194 | self.progressring_signal.connect(self.progressring_msg) 195 | self.end_signal.connect(self.process_end) 196 | self.hang_signal.connect(self.process_hang) 197 | self.clear_signal.connect(self.clear_screen) 198 | self.cover_signal.connect(self.display_cover) 199 | 200 | self.progressRing.hide() 201 | self.btn_hang.hide() 202 | self.editline_hang.hide() 203 | self.btn_stop.setEnabled(False) 204 | 205 | sys.stdout = EmittingStr(textWritten=self.outputWritten) 206 | sys.stderr = EmittingStr(textWritten=self.outputWritten) 207 | self.text_screen.setText(self.parent.welcome_text) 208 | 209 | def process_start(self): 210 | self.label_cover.setImage(self.book_icon) 211 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 212 | self.btn_run.setEnabled(False) 213 | self.btn_run.setText('正在下载') 214 | self.btn_stop.setEnabled(True) 215 | self.main_thread = MainThread(self) 216 | self.main_thread.start() 217 | 218 | def process_end(self, input=None): 219 | self.btn_run.setEnabled(True) 220 | self.btn_run.setText('开始下载') 221 | self.btn_run.setShortcut(Qt.Key_Return) 222 | self.btn_stop.setEnabled(False) 223 | self.progressRing.hide() 224 | self.btn_hang.hide() 225 | self.editline_hang.clear() 226 | self.editline_hang.hide() 227 | if input=='refresh': 228 | self.label_cover.setImage(self.book_icon) 229 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 230 | self.clear_signal.emit('') 231 | self.text_screen.setText(self.parent.welcome_text) 232 | 233 | def outputWritten(self, text): 234 | cursor = self.text_screen.textCursor() 235 | scrollbar=self.text_screen.verticalScrollBar() 236 | is_bottom = (scrollbar.value()>=scrollbar.maximum() - 15) 237 | cursor.movePosition(QTextCursor.End) 238 | cursor.insertText(text) 239 | if is_bottom: 240 | self.text_screen.setTextCursor(cursor) 241 | # self.text_screen.ensureCursorVisible() 242 | 243 | def clear_screen(self): 244 | self.text_screen.clear() 245 | 246 | def display_cover(self, signal_msg): 247 | filepath, img_h, img_w = signal_msg 248 | self.label_cover.setImage(filepath) 249 | self.label_cover.setFixedSize(int(img_w*self.cover_h/img_h), self.cover_h) 250 | 251 | def progressring_msg(self, input): 252 | if input == 'start': 253 | self.label_cover.setImage(self.book_icon) 254 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 255 | self.progressRing.show() 256 | elif input == 'end': 257 | self.progressRing.hide() 258 | self.progressRing.setValue(0) 259 | else: 260 | self.progressRing.setValue(input) 261 | 262 | def process_hang(self, input=None): 263 | self.btn_hang.setEnabled(True) 264 | self.btn_hang.setShortcut(Qt.Key_Return) 265 | self.btn_hang.show() 266 | self.editline_hang.show() 267 | 268 | def process_continue(self, input=None): 269 | self.btn_hang.hide() 270 | self.btn_hang.setEnabled(False) 271 | self.editline_hang.hide() 272 | 273 | 274 | def process_stop(self): 275 | self.main_thread.terminate() 276 | self.end_signal.emit('refresh') 277 | 278 | 279 | 280 | 281 | class Window(FluentWindow): 282 | def __init__(self): 283 | super().__init__() 284 | 285 | self.out_path = os.path.join(os.path.expanduser('~'), 'Downloads') 286 | self.head = 'https://www.wenku8.net' 287 | split_str = '**************************************\n ' 288 | self.welcome_text = f'使用说明(共4条,记得下拉):\n{split_str}1.轻小说文库{self.head},根据书籍网址输入书号以及下载的卷号,书号最多输入4位阿拉伯数字。\n{split_str}2.例如小说网址是{self.head}/book/3138.htm,则书号输入3138。\n{split_str}3.要查询书籍卷号卷名等信息,则可以只输入书号不输入卷号,点击确定会返回书籍卷名称和对应的卷号。\n{split_str}4.根据上一步返回的信息确定自己想下载的卷号,要下载编号[2]对应卷,则卷号输入2。想下载多卷比如[1]至[3]对应卷,则卷号输入1-3或1,2,3(英文逗号分隔,编号也可以不连续)并点击确定。' 289 | self.homeInterface = HomeWidget('Home Interface', self) 290 | self.settingInterface = SettingWidget('Setting Interface', self) 291 | self.initNavigation() 292 | self.initWindow() 293 | 294 | def initNavigation(self): 295 | self.addSubInterface(self.homeInterface, FIF.HOME, '主界面') 296 | self.addSubInterface(self.settingInterface, FIF.SETTING, '设置', NavigationItemPosition.BOTTOM) 297 | 298 | def initWindow(self): 299 | self.resize(700, 460) 300 | pixmap = QPixmap() 301 | pixmap.loadFromData(base64.b64decode(logo_base64)) 302 | self.setWindowIcon(QIcon(pixmap)) 303 | self.setWindowTitle('轻小说文库EPUB下载器') 304 | self.setFont(font_label) 305 | 306 | desktop = QApplication.desktop().availableGeometry() 307 | w, h = desktop.width(), desktop.height() 308 | self.move(w//2 - self.width()//2, h//2 - self.height()//2) 309 | 310 | def set_theme(self, mode=None): 311 | if mode=='亮': 312 | setTheme(Theme.LIGHT) 313 | elif mode=='暗': 314 | setTheme(Theme.DARK) 315 | elif mode=='跟随系统设置': 316 | setTheme(Theme.AUTO) 317 | theme = qconfig.theme 318 | if theme == Theme.DARK: 319 | self.homeInterface.label_book.setTextColor(QColor(255,255,255)) 320 | self.homeInterface.label_volumn.setTextColor(QColor(255,255,255)) 321 | elif theme == Theme.LIGHT: 322 | self.homeInterface.label_book.setTextColor(QColor(0,0,0)) 323 | self.homeInterface.label_volumn.setTextColor(QColor(0,0,0)) 324 | 325 | 326 | 327 | if __name__ == '__main__': 328 | QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) 329 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) 330 | QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) 331 | 332 | setTheme(Theme.DARK) 333 | setThemeColor('#559DCD') 334 | app = QApplication(sys.argv) 335 | w = Window() 336 | w.show() 337 | app.exec_() 338 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # pip install -r requirements.txt -i https://pypi.org/simple/ 2 | requests 3 | bs4 4 | rich 5 | pyqt5 6 | PyQt-Fluent-Widgets[full] 7 | selenium -------------------------------------------------------------------------------- /resource/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/lightnovel-download/f42ff913a6ef9b1dd0e2331ac77a80a5b4d89bba/resource/book.png -------------------------------------------------------------------------------- /resource/book.py: -------------------------------------------------------------------------------- 1 | book_base64 = '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' -------------------------------------------------------------------------------- /resource/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/lightnovel-download/f42ff913a6ef9b1dd0e2331ac77a80a5b4d89bba/resource/example1.png -------------------------------------------------------------------------------- /resource/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/lightnovel-download/f42ff913a6ef9b1dd0e2331ac77a80a5b4d89bba/resource/example2.png -------------------------------------------------------------------------------- /resource/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/lightnovel-download/f42ff913a6ef9b1dd0e2331ac77a80a5b4d89bba/resource/logo.png -------------------------------------------------------------------------------- /resource/logo.py: -------------------------------------------------------------------------------- 1 | logo_base64 = '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' -------------------------------------------------------------------------------- /resource/logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/lightnovel-download/f42ff913a6ef9b1dd0e2331ac77a80a5b4d89bba/resource/logo_big.png -------------------------------------------------------------------------------- /resource/trans_base64.py: -------------------------------------------------------------------------------- 1 | from PIL import Image 2 | import base64 3 | # from resource.logo import logo_base64 4 | import io 5 | 6 | # # 从Base64编码数据中获取图像数据 7 | # image_bytes = base64.b64decode(logo_base64) 8 | 9 | # # 将图像数据解码为Image对象 10 | # image = Image.open(io.BytesIO(image_bytes)) 11 | 12 | # # 显示图像 13 | # image.show() 14 | 15 | 16 | def image_to_base64(image_path): 17 | with open(image_path, "rb") as image_file: 18 | encoded_string = base64.b64encode(image_file.read()) 19 | return encoded_string.decode("utf-8") 20 | 21 | image_path = "resource/logo.png " 22 | base64_string = image_to_base64(image_path) 23 | print(base64_string) -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | def get_cover_html(img_w, img_h): 2 | img_htmls = [] 3 | img_msg = ' \n' 4 | img_htmls.append('\n') 5 | img_htmls.append('\n') 7 | img_htmls.append('\n') 8 | img_htmls.append('\n') 9 | img_htmls.append(' Cover\n') 10 | img_htmls.append('\n') 11 | img_htmls.append('\n') 12 | img_htmls.append('
\n') 13 | img_htmls.append(' \n') 14 | img_htmls.append(img_msg) 15 | img_htmls.append(' \n') 16 | img_htmls.append('
\n') 17 | img_htmls.append('\n') 18 | img_htmls.append('') 19 | return img_htmls 20 | 21 | 22 | def text2htmls(chap_name, text): 23 | text_lines = text.split('\n') 24 | text_body = [] 25 | text_body.append('\n') 26 | text_body.append('

' + chap_name + '

\n') 27 | for text_line in text_lines: 28 | if text_line.startswith('[img:'): 29 | img_no = text_line[5:7] 30 | text_line_html = f' \"{img_no}\"\n' 31 | else: 32 | text_line_html = '

' + text_line + '

\n' 33 | text_body.append(text_line_html) 34 | text_body.append('\n') 35 | text_head = [] 36 | text_head.append('\n') 37 | text_head.append('\n') 39 | text_head.append('\n') 40 | text_head.append('\n') 41 | text_head.append(''+ chap_name+'\n') 42 | text_head.append('\n') 43 | text_head.append('\n') 44 | text_htmls = text_head + text_body + [''] 45 | return text_htmls 46 | 47 | def get_toc_html(title, chap_names): 48 | toc_htmls = [] 49 | toc_htmls.append('\n') 50 | toc_htmls.append('\n\n') 52 | toc_htmls.append('\n') 53 | toc_htmls.append(' \n') 54 | toc_htmls.append(' \n') 55 | toc_htmls.append(' \n') 56 | toc_htmls.append(' \n') 57 | toc_htmls.append(' \n') 58 | toc_htmls.append(' \n') 59 | toc_htmls.append('\n') 60 | toc_htmls.append(' '+ title +'\n') 61 | toc_htmls.append('\n') 62 | toc_htmls.append('\n') 63 | for chap_no, chap_name in enumerate(chap_names): 64 | toc_htmls.append(' \n') 65 | toc_htmls.append(' \n') 66 | toc_htmls.append(' '+ chap_name +'\n') 67 | toc_htmls.append(' \n') 68 | toc_htmls.append(' \n') 69 | toc_htmls.append(' \n') 70 | toc_htmls.append('\n') 71 | toc_htmls.append('') 72 | return toc_htmls 73 | 74 | 75 | def get_content_html(title, author, num_chap, num_img, img_exist=False): 76 | content_htmls = [] 77 | content_htmls.append('\n') 78 | content_htmls.append('\n') 79 | content_htmls.append(' \n') 80 | content_htmls.append(' urn:uuid:942b8224-476b-463b-9078-cdfab0ee2686\n') 81 | content_htmls.append(' zh\n') 82 | content_htmls.append(' '+ title +'\n') 83 | content_htmls.append(' '+ author +'\n') 84 | content_htmls.append(' \n') 85 | content_htmls.append(' \n') 86 | content_htmls.append(' \n') 87 | content_htmls.append(' \n') 88 | content_htmls.append(' \n') 89 | if img_exist: 90 | content_htmls.append(' \n') 91 | for chap_no in range(num_chap): 92 | content_htmls.append(' \n') 93 | 94 | 95 | for img_no in range(num_img): 96 | content_htmls.append(' \n') 97 | 98 | content_htmls.append(' \n') 99 | content_htmls.append(' \n') 100 | 101 | 102 | content_htmls.append(' \n') 103 | content_htmls.append(' \n') 104 | for chap_no in range(num_chap): 105 | content_htmls.append(' \n') 106 | 107 | content_htmls.append(' \n') 108 | content_htmls.append(' \n') 109 | content_htmls.append(' \n') 110 | content_htmls.append(' \n') 111 | content_htmls.append('\n') 112 | return content_htmls 113 | 114 | 115 | def get_container_html(): 116 | container_htmls = [] 117 | container_htmls.append('\n') 118 | container_htmls.append('\n') 119 | container_htmls.append(' \n') 120 | container_htmls.append(' \n') 121 | container_htmls.append(' \n') 122 | container_htmls.append('\n') 123 | return container_htmls 124 | 125 | 126 | def get_color_html(colorimg_num): 127 | color_htmls = [] 128 | color_htmls.append('\n') 129 | color_htmls.append('\n') 130 | color_htmls.append('\n') 131 | 132 | color_htmls.append(' 彩插\n') 133 | color_htmls.append('\n') 134 | color_htmls.append('\n') 135 | for i in range(1, colorimg_num): 136 | color_htmls.append(' \"'+str(i).zfill(2)+'\"\n') 137 | color_htmls.append('\n') 138 | color_htmls.append('') 139 | return color_htmls 140 | 141 | def check_chars(win_chars): 142 | win_illegal_chars = '?*"<>|:/\\' 143 | new_chars = '' 144 | for char in win_chars: 145 | if char in win_illegal_chars: 146 | new_chars += '\u25A0' 147 | else: 148 | new_chars += char 149 | return new_chars 150 | --------------------------------------------------------------------------------