├── README.md ├── backend ├── bilinovel │ ├── Editer.py │ ├── bilinovel_router.py │ └── utils.py └── rubbish_secret_map.py ├── bilinovel.py ├── frontend ├── bilinovel_gui.py ├── cfg_utils.py ├── gui_utils.py ├── mainwindow.py └── setting.py ├── main.py ├── requirements.txt └── resource ├── __pycache__ ├── book.cpython-311.pyc ├── logo.cpython-311.pyc └── logo_big.cpython-311.pyc ├── book.png ├── book.py ├── example1.png ├── example2.png ├── logo.png ├── logo.py ├── logo_big.png ├── logo_big.py ├── mainpage.png └── trans_base64.py /README.md: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | 5 |

6 | EPUB下载器-Edge浏览器版 7 |

8 | 9 |
10 | 11 |
12 | 13 | [哔哩轻小说](https://www.linovelib.com)下载与EPUB打包-Edge浏览器版 14 | 15 | 特性: 16 | 17 | * Fluent Design风格界面,下载进度与书籍封面显示,主题切换,下载目录自定义。 18 | * 前后端分离,同时支持命令行版本。 19 | * EPUB格式打包,支持多种阅读器。 20 | * 支持[Kavita](https://www.kavitareader.com/)归档整理。 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 | ## 安装: 34 | 35 | 系统要求:Windows10及以上 36 | 37 | ### 使用前安装需要的包 38 | ``` 39 | pip install -r requirements.txt -i https://pypi.org/simple/ 40 | ``` 41 | ### 使用命令行模式下载小说: 42 | ``` 43 | python bilinovel.py 44 | ``` 45 | 46 | ### 使用图形界面运行: 47 | ``` 48 | python main.py 49 | ``` 50 | 在主界面选择下载小说/漫画 51 | 52 | ### 使用pyinstaller打包: 53 | ``` 54 | pip install pyinstaller 55 | ``` 56 | ``` 57 | pyinstaller -F -w -i .\resource\logo.png -n bilinovel-download .\main.py 58 | ``` 59 | 60 | ### 相关项目: 61 | 62 | * [轻小说文库EPUB下载器](https://github.com/ShqWW/lightnovel-download) 63 | 64 | * [哔哩轻小说EPUB下载器](https://github.com/ShqWW/bilinovel-download) 65 | 66 | * [拷贝漫画EPUB下载器](https://github.com/ShqWW/copymanga-download) 67 | 68 | 69 | ## EPUB书籍编辑和管理工具推荐: 70 | 1. [Sigil](https://sigil-ebook.com/) 71 | 2. [Calibre](https://www.calibre-ebook.com/) 72 | 3. [Kavita](https://www.kavitareader.com/) 73 | 74 | -------------------------------------------------------------------------------- /backend/bilinovel/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 backend.bilinovel.utils import * 10 | import zipfile 11 | import re 12 | # import pickle 13 | from PIL import Image 14 | import time 15 | import threading 16 | from concurrent.futures import ThreadPoolExecutor 17 | from DrissionPage import Chromium, ChromiumOptions 18 | import tempfile 19 | 20 | lock = threading.RLock() 21 | 22 | class Editer(object): 23 | def __init__(self, root_path, book_no='0000', volume_no=1, interval=0, num_thread=1): 24 | 25 | self.url_head = 'https://www.linovelib.com' 26 | 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, 'cookie':'night=1'} 27 | 28 | self.interval = float(interval)/1000 29 | self.main_page = f'{self.url_head}/novel/{book_no}.html' 30 | self.cata_page = f'{self.url_head}/novel/{book_no}/catalog' 31 | self.read_tool_page = f'{self.url_head}/themes/zhmb/js/readtool.js' 32 | self.color_chap_name = '插图' 33 | self.color_page_name = '彩页' 34 | self.html_buffer = dict() 35 | 36 | path = r'C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe' # 请改为你电脑内Chrome可执行文件路径 37 | co = ChromiumOptions().set_browser_path(path) 38 | self.tab = Chromium(co).latest_tab 39 | 40 | main_html = self.get_html(self.main_page) 41 | self.get_meta_data(main_html) 42 | 43 | self.img_url_map = dict() 44 | self.volume_no = volume_no 45 | 46 | self.epub_path = root_path 47 | # self.temp_path = os.path.join(self.epub_path, 'temp_'+ check_chars(self.book_name) + '_' + str(self.volume_no)) 48 | self.temp_path_io = tempfile.TemporaryDirectory() 49 | self.temp_path = self.temp_path_io.name 50 | 51 | self.missing_last_chap_list = [] 52 | self.is_color_page = True 53 | self.page_url_map = dict() 54 | self.ignore_urls = [] 55 | self.url_buffer = [] 56 | self.max_thread_num = 8 57 | self.pool = ThreadPoolExecutor(int(num_thread)) 58 | 59 | # 获取html文档内容 60 | def get_html(self, url, is_gbk=False): 61 | while True: 62 | self.tab.get(url) 63 | req = self.tab.html 64 | while 'Access denied | www.linovelib.com used Cloudflare to restrict access' in req: 65 | print('下载频繁,触发反爬,5秒后重试....') 66 | time.sleep(5) 67 | self.tab.get(url) 68 | req = self.tab.html 69 | if is_gbk: 70 | req.encoding = 'GBK' #这里是网页的编码转换,根据网页的实际需要进行修改,经测试这个编码没有问题 71 | break 72 | if self.interval>0: 73 | time.sleep(self.interval) 74 | return req 75 | 76 | def get_html_content(self, url, is_buffer=False): 77 | if is_buffer: 78 | while not url in self.html_buffer.keys(): 79 | time.sleep(0.1) 80 | if url in self.html_buffer.keys(): 81 | return self.html_buffer[url] 82 | while True: 83 | try: 84 | req=requests.get(url, headers=self.header) 85 | break 86 | except Exception as e: 87 | pass 88 | lock.acquire() 89 | self.html_buffer[url] = req.content 90 | lock.release() 91 | return req.content 92 | 93 | def get_meta_data(self, main_html): 94 | bf = BeautifulSoup(main_html, 'html.parser') 95 | self.book_name = bf.find('meta', {"property": "og:novel:book_name"})['content'] 96 | self.author = bf.find('meta', {"property": "og:novel:author"})['content'] 97 | 98 | brief = bf.find('div', {"class": "book-dec Jbook-dec hide"}) 99 | brief_to_delete = brief.find('div') 100 | brief_to_delete.extract() if brief_to_delete is not None else 0 101 | self.brief = brief.find_all('p')[0].text 102 | 103 | book_meta = bf.find('div', class_='book-label') 104 | self.publisher = book_meta.find('a', class_='label').text 105 | span_tag = book_meta.find('span') 106 | self.tag_list = [] 107 | if span_tag: 108 | for a_tag in span_tag.find_all('a'): 109 | self.tag_list.append(a_tag.text) 110 | 111 | try: 112 | self.cover_url_back = re.search(r'src=\"(.*?)\"', str(bf.find('div', {"class": "book-img fl"}))).group(1) 113 | except: 114 | self.cover_url_back = 'cid' 115 | 116 | def make_folder(self): 117 | os.makedirs(self.temp_path, exist_ok=True) 118 | 119 | self.text_path = os.path.join(self.temp_path, 'OEBPS/Text') 120 | os.makedirs(self.text_path, exist_ok=True) 121 | 122 | self.img_path = os.path.join(self.temp_path, 'OEBPS/Images') 123 | os.makedirs(self.img_path, exist_ok=True) 124 | 125 | def get_index_url(self): 126 | self.volume = {} 127 | self.volume['chap_urls'] = [] 128 | self.volume['chap_names'] = [] 129 | chap_html_list = self.get_chap_list(is_print=False) 130 | if len(chap_html_list)', re.DOTALL) 167 | text_html = pattern.sub('', text_html) 168 | 169 | img_urlre_list = re.findall(r"", text_html) 170 | for img_urlre in img_urlre_list: 171 | img_url_full = re.search(r'.[a-zA-Z]{3}/(.*?).(jpg|png|jpeg)', img_urlre) 172 | img_url_name = img_url_full.group(1) 173 | img_url_tail = img_url_full.group(0).split('.')[-1] 174 | img_url = f'https://img3.readpai.com/{img_url_name}.{img_url_tail}' 175 | 176 | if not img_url in self.img_url_map: 177 | self.img_url_map[img_url] = str(len(self.img_url_map)).zfill(2) 178 | img_symbol = f' \"{self.img_url_map[img_url]}\"\n' 179 | if '00' in img_symbol: 180 | text_html = text_html.replace(img_urlre, '') #默认第一张为封面图片 不写入彩页 181 | else: 182 | text_html = text_html.replace(img_urlre, img_symbol) 183 | symbol_index = text_html.index(img_symbol) 184 | if text_html[symbol_index-1] != '\n': 185 | text_html = text_html[:symbol_index] + '\n' + text_html[symbol_index:] 186 | 187 | text = BeautifulSoup(text_html, 'html.parser').find('div', class_='ads read-content1', id='TextContent') 188 | 189 | 190 | #删除反爬提示元素 191 | match = re.findall(r'', str(text)) 192 | if len(match) > 0: 193 | warn_element = text.find(f'p{match[0]}') 194 | warn_element.decompose() 195 | 196 | 197 | text = text.decode_contents() 198 | if text.startswith('\n'): 199 | text = text[1:] 200 | if text.endswith('\n\n'): 201 | text = text[:-1] 202 | 203 | msg = '


————————————以下为告示,读者请无视——————————————

' 204 | text = text[:text.find(msg)] 205 | 206 | #去除乱码 207 | if is_tansfer_rubbish_code: 208 | text = replace_rubbish_text(text) 209 | return text 210 | 211 | def remove_element(self, bf_item, id=None, class_=None): 212 | if id is not None: 213 | remove_list = bf_item.find_all(id=id) 214 | elif class_ is not None: 215 | remove_list = bf_item.find_all(class_=class_) 216 | for remove_element in remove_list: 217 | remove_element.decompose() 218 | 219 | def get_chap_text(self, url, chap_name, return_next_chapter=False): 220 | text_chap = '' 221 | page_no = 1 222 | url_ori = url 223 | next_chap_url = None 224 | while True: 225 | if page_no == 1: 226 | str_out = chap_name 227 | else: 228 | str_out = f' 正在下载第{page_no}页......' 229 | print(str_out) 230 | content_html = self.get_html(url, is_gbk=False) 231 | text = self.get_page_text(content_html) 232 | text_chap += text 233 | url_new = url_ori.replace('.html', '_{}.html'.format(page_no+1))[len(self.url_head):] 234 | if url_new in content_html: 235 | page_no += 1 236 | url = self.url_head + url_new 237 | else: 238 | if return_next_chapter: 239 | next_chap_url = self.url_head + re.search(r'书签下一页', content_html).group(1) 240 | break 241 | return text_chap, next_chap_url 242 | 243 | def get_text(self): 244 | self.make_folder() 245 | repeat_img_strs = [] #记录重复的图片 246 | text_no=0 #text_no正文章节编号(排除插图) chap_no 是所有章节编号 247 | for chap_no, (chap_name, chap_url) in enumerate(zip(self.volume['chap_names'], self.volume['chap_urls'])): 248 | is_fix_next_chap_url = (chap_name in self.missing_last_chap_list) 249 | text, next_chap_url = self.get_chap_text(chap_url, chap_name, return_next_chapter=is_fix_next_chap_url) 250 | 251 | if chap_name == self.color_chap_name: 252 | text_html_color = text2htmls(self.color_page_name, text) 253 | else: 254 | file_name = os.path.join(self.text_path, f'{str(text_no).zfill(2)}.xhtml') 255 | text_html = text2htmls(chap_name, text) 256 | text_no += 1 257 | with open(file_name, 'w+', encoding='utf-8') as f: 258 | f.write(text_html) 259 | repeat_img_strs += re.findall(r'[^', text_html) 260 | 261 | if is_fix_next_chap_url: 262 | self.volume['chap_urls'][chap_no+1] = next_chap_url #正向修复 263 | 264 | # 将彩页中后文已经出现的图片删除,避免重复 265 | if self.is_color_page: #判断彩页是否存在 266 | text_html_color_new = [] 267 | textfile = self.text_path + '/color.xhtml' 268 | for img_line in repeat_img_strs: 269 | if img_line in text_html_color: 270 | text_html_color = text_html_color.replace(img_line+'\n', '') 271 | 272 | with open(textfile, 'w+', encoding='utf-8') as f: 273 | f.write(text_html_color) 274 | 275 | def get_image(self, is_gui=False, signal=None): 276 | for url in self.img_url_map.keys(): 277 | self.pool.submit(self.get_html_content, url) 278 | img_path = self.img_path 279 | if is_gui: 280 | len_iter = len(self.img_url_map.items()) 281 | signal.emit('start') 282 | for i, (img_url, img_name) in enumerate(self.img_url_map.items()): 283 | content = self.get_html_content(img_url, is_buffer=True) 284 | with open(img_path+f'/{img_name}.jpg', 'wb') as f: 285 | f.write(content) #写入二进制内容 286 | signal.emit(int(100*(i+1)/len_iter)) 287 | signal.emit('end') 288 | else: 289 | for img_url, img_name in tqdm(self.img_url_map.items()): 290 | content = self.get_html_content(img_url) 291 | with open(img_path+f'/{img_name}.jpg', 'wb') as f: 292 | f.write(content) #写入二进制内容 293 | 294 | def get_cover(self, is_gui=False, signal=None): 295 | img_w, img_h = 300, 300 296 | try: 297 | imgfile = os.path.join(self.img_path, '00.jpg') 298 | img = Image.open(imgfile) 299 | img_w, img_h = img.size 300 | signal_msg = (imgfile, img_h, img_w) 301 | if is_gui: 302 | signal.emit(signal_msg) 303 | except Exception as e: 304 | print(e) 305 | print('没有封面图片,请自行用第三方EPUB编辑器手动添加封面') 306 | with open(os.path.join(self.text_path, 'cover.xhtml'), 'w+', encoding='utf-8') as f: 307 | f.write(get_cover_html(img_w, img_h)) 308 | 309 | def check_volume(self, is_gui=False, signal=None, editline=None): 310 | chap_names = self.volume['chap_names'] 311 | chap_num = len(self.volume['chap_names']) 312 | for chap_no, url in enumerate(self.volume['chap_urls']): 313 | if self.check_url(url): 314 | if not self.prev_fix_url(chap_no, chap_num): #先尝试反向递归修复 315 | if chap_no == 0: #第一个章节都反向修复失败 说明后面章节全部缺失,只能手动输入第一个章节,保证第一个章节一定有效 316 | self.volume['chap_urls'][0] = self.hand_in_url(chap_names[chap_no], is_gui, signal, editline) 317 | else: #其余章节反向修复失败 默认使用正向修复 318 | self.missing_last_chap_list.append(chap_names[chap_no-1]) 319 | 320 | #没有检测到插图页,手动输入插图页标题 321 | if self.color_chap_name not in self.volume['chap_names']: 322 | self.color_chap_name = self.hand_in_color_page_name(is_gui, signal, editline) 323 | self.volume['color_chap_name'] = self.color_chap_name 324 | 325 | #没有彩页 但主页封面存在,将主页封面设为书籍封面 326 | if self.color_chap_name=='': 327 | self.is_color_page = False 328 | if not self.check_url(self.cover_url_back): 329 | self.img_url_map[self.cover_url_back] = str(len(self.img_url_map)).zfill(2) 330 | print('**************') 331 | print('提示:没有彩页,但主页封面存在,将使用主页的封面图片作为本卷图书封面') 332 | print('**************') 333 | 334 | def check_url(self, url):#当检测有问题返回True 335 | return ('javascript' in url or 'cid' in url) 336 | 337 | def get_prev_url(self, chap_no): #获取前一个章节的链接 338 | content_html = self.get_html(self.volume['chap_urls'][chap_no], is_gbk=False) 339 | next_url = self.url_head + re.search(r'

上一页', content_html).group(1) 340 | return next_url 341 | 342 | def prev_fix_url(self, chap_no, chap_num): #反向递归修复缺失链接(后修复前),若成功修复返回True,否则返回False 343 | if chap_no==chap_num-1: #最后一个章节直接选择不修复 返回False 344 | return False 345 | elif self.check_url(self.volume['chap_urls'][chap_no+1]): 346 | if self.prev_fix_url(chap_no+1, chap_num): 347 | self.volume['chap_urls'][chap_no] = self.get_prev_url(chap_no+1) 348 | return True 349 | else: 350 | return False 351 | else: 352 | self.volume['chap_urls'][chap_no] = self.get_prev_url(chap_no+1) 353 | return True 354 | 355 | def hand_in_msg(self, error_msg='', is_gui=False, signal=None, editline=None): 356 | if is_gui: 357 | print(error_msg) 358 | signal.emit('hang') 359 | time.sleep(1) 360 | while not editline.isHidden(): 361 | time.sleep(1) 362 | content = editline.text() 363 | editline.clear() 364 | else: 365 | content = input(error_msg) 366 | return content 367 | 368 | def hand_in_url(self, chap_name, is_gui=False, signal=None, editline=None): 369 | error_msg = f'章节\"{chap_name}\"连接失效,请手动输入该章节链接(手机版“{self.url_head}”开头的链接):' 370 | return self.hand_in_msg(error_msg, is_gui, signal, editline) 371 | 372 | def hand_in_color_page_name(self, is_gui=False, signal=None, editline=None): 373 | if is_gui: 374 | error_msg = f'插图页面不存在,需要下拉选择插图页标题,若不需要插图页则保持本栏为空直接点确定:' 375 | editline.addItems(self.volume['chap_names']) 376 | editline.setCurrentIndex(-1) 377 | else: 378 | error_msg = f'插图页面不存在,需要手动输入插图页标题,若不需要插图页则不输入直接回车:' 379 | return self.hand_in_msg(error_msg, is_gui, signal, editline) 380 | 381 | def get_toc(self): 382 | if self.is_color_page: 383 | ind = self.volume["chap_names"].index(self.color_chap_name) 384 | self.volume["chap_names"].pop(ind) 385 | toc_htmls = get_toc_html(self.book_name, self.volume["chap_names"]) 386 | with open(os.path.join(self.temp_path, 'OEBPS/toc.ncx'), 'w+', encoding='utf-8') as f: 387 | f.write(toc_htmls) 388 | 389 | def get_content(self): 390 | content_html = get_content_html(self.book_name, self.volume['volume_name'], self.volume_no, self.author, self.publisher, self.brief, self.tag_list, len(self.volume["chap_names"]), len(os.listdir(self.img_path)), self.is_color_page) 391 | with open(os.path.join(self.temp_path, 'OEBPS/content.opf'), 'w+', encoding='utf-8') as f: 392 | f.write(content_html) 393 | 394 | def get_epub_head(self): 395 | metainf_folder = os.path.join(self.temp_path, 'META-INF') 396 | os.makedirs(metainf_folder, exist_ok=True) 397 | with open(os.path.join(metainf_folder, 'container.xml'), 'w+', encoding='utf-8') as f: 398 | f.write(get_container_html()) 399 | with open(os.path.join(self.temp_path, 'mimetype'), 'w+', encoding='utf-8') as f: 400 | f.write('application/epub+zip') 401 | 402 | def get_epub(self): 403 | epub_file = self.epub_path + '/' + check_chars(self.book_name + '-' + self.volume['volume_name']) + '.epub' 404 | with zipfile.ZipFile(epub_file, "w", zipfile.ZIP_DEFLATED) as zf: 405 | for dirpath, _, filenames in os.walk(self.temp_path): 406 | fpath = dirpath.replace(self.temp_path,'') #这一句很重要,不replace的话,就从根目录开始复制 407 | fpath = fpath and fpath + os.sep or '' 408 | for filename in filenames: 409 | zf.write(os.path.join(dirpath, filename), fpath+filename) 410 | self.temp_path_io.cleanup() 411 | return epub_file 412 | 413 | # def buffer(self): 414 | # filename = 'buffer.pkl' 415 | # filepath = os.path.join(self.temp_path, filename) 416 | # if os.path.isfile(filepath): 417 | # with open(filepath, 'rb') as f: 418 | # self.volume, self.img_url_map = pickle.load(f) 419 | # self.text_path = os.path.join(self.temp_path, 'OEBPS/Text') 420 | # os.makedirs(self.text_path, exist_ok=True) 421 | # self.img_path = os.path.join(self.temp_path, 'OEBPS/Images') 422 | # os.makedirs(self.img_path, exist_ok=True) 423 | # self.color_chap_name = self.volume['color_chap_name'] 424 | # else: 425 | # with open(filepath, 'wb') as f: 426 | # pickle.dump((self.volume ,self.img_url_map), f) 427 | 428 | # def is_buffer(self): 429 | # filename = 'buffer.pkl' 430 | # filepath = os.path.join(self.temp_path, filename) 431 | # return os.path.isfile(filepath) -------------------------------------------------------------------------------- /backend/bilinovel/bilinovel_router.py: -------------------------------------------------------------------------------- 1 | 2 | from .Editer import Editer 3 | from backend.bilinovel.utils import * 4 | 5 | 6 | 7 | def query_chaps(book_no): 8 | print('未输入卷号,将返回书籍目录信息......') 9 | editer = Editer(root_path='./out', book_no=book_no) 10 | print('--------------------------------') 11 | print(editer.book_name, editer.author) 12 | print('--------------------------------') 13 | editer.get_chap_list() 14 | print('--------------------------------') 15 | print('请输入所需要的卷号进行下载。') 16 | 17 | def download_single_volume(root_path, 18 | book_no, 19 | volume_no, 20 | interval, 21 | num_thread, 22 | is_gui=False, 23 | hang_signal=None, 24 | progressring_signal=None, 25 | cover_signal=None, 26 | edit_line_hang=None): 27 | 28 | editer = Editer(root_path=root_path, book_no=book_no, volume_no=volume_no, interval=interval, num_thread=num_thread) 29 | print('正在积极地获取书籍信息....') 30 | success = editer.get_index_url() 31 | if not success: 32 | print('书籍信息获取失败') 33 | return 34 | print(editer.book_name + '-' + editer.volume['volume_name'], editer.author) 35 | print('****************************') 36 | # if not editer.is_buffer(): 37 | editer.check_volume(is_gui=is_gui, signal=hang_signal, editline=edit_line_hang) 38 | print('正在下载文本....') 39 | print('*********************') 40 | editer.get_text() 41 | print('*********************') 42 | # editer.buffer() 43 | # else: 44 | # print('检测到文本文件,直接下载插图') 45 | # editer.buffer() 46 | 47 | 48 | print('正在下载插图.....................................') 49 | editer.get_image(is_gui=is_gui, signal=progressring_signal) 50 | 51 | print('正在编辑元数据....') 52 | editer.get_cover(is_gui=is_gui, signal=cover_signal) 53 | editer.get_toc() 54 | editer.get_content() 55 | editer.get_epub_head() 56 | 57 | print('正在生成电子书....') 58 | epub_file = editer.get_epub() 59 | print('生成成功!', f'电子书路径【{epub_file}】') 60 | 61 | 62 | def downloader_router(root_path, 63 | book_no, 64 | volume_no, 65 | interval=500, 66 | num_thread=1, 67 | is_gui=False, 68 | hang_signal=None, 69 | progressring_signal=None, 70 | cover_signal=None, 71 | edit_line_hang=None): 72 | is_multi_chap = False 73 | if len(book_no)==0: 74 | print('请检查输入是否完整正确!') 75 | return 76 | elif volume_no == '': 77 | query_chaps(book_no) 78 | return 79 | elif volume_no.isdigit(): 80 | volume_no = int(volume_no) 81 | if volume_no<=0: 82 | print('请检查输入是否完整正确!') 83 | return 84 | elif "-" in volume_no: 85 | start, end = map(str, volume_no.split("-")) 86 | if start.isdigit() and end.isdigit() and int(start)>0 and int(start) 6 | 7 | 8 | 9 | 10 | """ 11 | return text_html 12 | 13 | def get_cover_html(img_w, img_h): 14 | text_html = f""" 15 | 17 | 18 | 19 | Cover 20 | 21 | 22 |
23 | 24 | 25 | 26 |
27 | 28 | """ 29 | return text_html 30 | 31 | def text2htmls(chap_name, text): 32 | text_html = f""" 33 | 35 | 36 | 37 | {chap_name} 38 | 39 | 40 | 41 |

{chap_name}

42 | {text} 43 | 44 | """ 45 | return text_html 46 | 47 | def get_toc_html(title, chap_names): 48 | toc_html_template = """ 49 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | {title} 61 | 62 | 63 | {nav_points} 64 | 65 | """ 66 | nav_point_template = """ 67 | 68 | {chap_name} 69 | 70 | 71 | """ 72 | nav_points = '\n'.join( 73 | nav_point_template.format(nav_id=i+1, play_order=i+1, chap_name=chap_name, chap_no=str(i).zfill(2)) 74 | for i, chap_name in enumerate(chap_names) 75 | ) 76 | return toc_html_template.format(title=title, nav_points=nav_points) 77 | 78 | 79 | 80 | def get_content_html(book_name, volume_name, volume_no, author, publisher, brief, tag_list, num_chap, num_img, img_exist=False): 81 | content_html_template = """ 82 | 83 | 84 | zh-CN 85 | 86 | 87 | {title} 88 | {author} 89 | {publisher} 90 | {brief} 91 | {subjects} 92 | 93 | 94 | 95 | 96 | 97 | {xcolor} 98 | {chapters} 99 | {images} 100 | 101 | 102 | 103 | {spine_xcolor} 104 | {spine_chapters} 105 | 106 | 107 | 108 | 109 | """ 110 | 111 | subjects = '\n'.join(f' {tag}' for tag in tag_list) 112 | chapters = '\n'.join( 113 | f' ' 114 | for chap_no in range(num_chap) 115 | ) 116 | images = '\n'.join( 117 | f' ' 118 | for img_no in range(num_img) 119 | ) 120 | spine_chapters = '\n'.join( 121 | f' ' 122 | for chap_no in range(num_chap) 123 | ) 124 | 125 | xcolor = ' \n' if img_exist else '' 126 | spine_xcolor = ' \n' if img_exist else '' 127 | 128 | return content_html_template.format( 129 | series_name=book_name, 130 | series_no = volume_no, 131 | title=book_name+'-'+volume_name, 132 | author=author, 133 | publisher=publisher, 134 | brief = brief, 135 | subjects=subjects, 136 | chapters=chapters, 137 | images=images, 138 | xcolor=xcolor, 139 | spine_xcolor=spine_xcolor, 140 | spine_chapters=spine_chapters 141 | ) 142 | 143 | def check_chars(win_chars): 144 | win_illegal_chars = '?*"<>|:/' 145 | new_chars = '' 146 | for char in win_chars: 147 | if char in win_illegal_chars: 148 | new_chars += '\u25A0' 149 | else: 150 | new_chars += char 151 | return new_chars 152 | 153 | # def replace_rubbish_text(content_html): 154 | # soup = BeautifulSoup(content_html, 'html.parser') 155 | # ps = soup.find_all('p') 156 | # if not ps: 157 | # return str(soup) 158 | # last_p = ps[-1] 159 | # text = last_p.get_text() 160 | # sb = [] 161 | # for blank_char in text: 162 | # # if blank_char in blank_list: 163 | # # continue 164 | # replacement = rubbish_secret_map.get(blank_char) 165 | # t = replacement if replacement else blank_char 166 | # sb.append(t) 167 | # last_p.string = ''.join(sb) 168 | # return str(soup) 169 | 170 | chinese_punctuation = ",。!?、;:“”‘’()《》〈〉【】『』〖〗…—~+-=×÷·—‘’“”『』【】()《》〈〉「」『』〖〗〘〙〚〛〚〛〘〙〖〗〘〙〚〛〘〙〖〗〘〙" 171 | 172 | def replace_rubbish_text(content_html): 173 | soup = BeautifulSoup(content_html, 'html.parser') 174 | ps = soup.find_all('p') 175 | if not ps: 176 | return str(soup) 177 | last_p = ps[-1] 178 | text = last_p.get_text() 179 | sb = [] 180 | for blank_char in text: 181 | replace_strr = rubbish_secret_map.get(blank_char) 182 | if replace_strr is not None: 183 | sb.append(replace_strr) 184 | elif blank_char in chinese_punctuation: 185 | sb.append(blank_char) 186 | last_p.string = ''.join(sb) 187 | return str(soup) -------------------------------------------------------------------------------- /bilinovel.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import os 3 | 4 | from backend.bilinovel.bilinovel_router import downloader_router 5 | 6 | def parse_args(): 7 | """Parse input arguments.""" 8 | parser = argparse.ArgumentParser(description='config') 9 | parser.add_argument('--interval', default=4500, type=int) 10 | parser.add_argument('--num_thread', default='1', type=int) 11 | parser.add_argument('--out_path', default='./out', type=str) 12 | args = parser.parse_args() 13 | return args 14 | 15 | 16 | if __name__=='__main__': 17 | args = parse_args() 18 | os.makedirs(args.out_path, exist_ok=True) 19 | while True: 20 | book_no = input('请输入书籍号:') 21 | volume_no = input('请输入卷号(查看目录信息不输入直接按回车,下载多卷请使用逗号分隔或者连字符-):') 22 | downloader_router(root_path=args.out_path, book_no=book_no, volume_no=volume_no, interval=args.interval, num_thread=args.num_thread) 23 | # book_no = '2650' 24 | # volume_no = '1' 25 | # downloader_router(root_path=args.out_path, book_no=book_no, volume_no=volume_no, interval=args.interval, num_thread=args.num_thread) 26 | # exit(0) -------------------------------------------------------------------------------- /frontend/bilinovel_gui.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PyQt5.QtCore import Qt, pyqtSignal, QThread, QRegExp 3 | from PyQt5.QtGui import QTextCursor, QPixmap, QRegExpValidator 4 | from PyQt5.QtWidgets import QFrame, QGridLayout 5 | from qfluentwidgets import TextEdit, ImageLabel, LineEdit, PushButton, ProgressRing, SubtitleLabel, EditableComboBox 6 | 7 | import sys 8 | import base64 9 | from resource.book import book_base64 10 | from frontend.cfg_utils import * 11 | from backend.bilinovel.bilinovel_router import downloader_router 12 | from .gui_utils import font_label, font_msg, EmittingStr 13 | 14 | 15 | 16 | class MainThread(QThread): 17 | def __init__(self, parent): 18 | super().__init__() 19 | self.parent = parent 20 | sys.stdout = self.parent.msg_out 21 | sys.stderr = self.parent.msg_out 22 | 23 | def run(self): 24 | self.parent.clear_signal.emit('') 25 | try: 26 | book_no = self.parent.editline_book.text() 27 | volumn_no = self.parent.editline_volumn.text() 28 | out_path = read_config_dict('download_path') 29 | interval = read_config_dict('interval') 30 | num_thread = read_config_dict('numthread') 31 | downloader_router(out_path, book_no, volumn_no, interval, num_thread, True, self.parent.hang_signal, self.parent.progressring_signal, self.parent.cover_signal, self.parent.editline_hang) 32 | self.parent.end_signal.emit('') 33 | except Exception as e: 34 | self.parent.end_signal.emit('') 35 | print('错误,请检查网络情况或确认输入是否正确') 36 | print('错误信息:') 37 | print(e) 38 | def terminate(self) -> None: 39 | result = super().terminate() 40 | return result 41 | 42 | 43 | 44 | class NovelWidget(QFrame): 45 | 46 | progressring_signal = pyqtSignal(object) 47 | end_signal = pyqtSignal(object) 48 | hang_signal = pyqtSignal(object) 49 | clear_signal = pyqtSignal(object) 50 | cover_signal = pyqtSignal(object) 51 | 52 | def __init__(self, text: str, parent=None): 53 | super().__init__(parent=parent) 54 | self.setObjectName(text) 55 | self.parent = parent 56 | self.label_book = SubtitleLabel('书号:', self) 57 | self.label_volumn = SubtitleLabel('卷号:', self) 58 | self.editline_book = LineEdit(self) 59 | self.editline_volumn = LineEdit(self) 60 | validator = QRegExpValidator(QRegExp("\\d+")) # 正则表达式匹配阿拉伯数字 61 | self.editline_book.setValidator(validator) 62 | # self.editline_volumn.setValidator(validator) 63 | 64 | self.editline_book.setMaxLength(4) 65 | # self.editline_volumn.setMaxLength(2) 66 | 67 | # self.editline_book.setText('2059') 68 | # self.editline_volumn.setText('3') 69 | self.book_icon = QPixmap() 70 | self.book_icon.loadFromData(base64.b64decode(book_base64)) 71 | self.cover_w, self.cover_h = 152, 230 72 | 73 | self.label_cover = ImageLabel(self.book_icon, self) 74 | self.label_cover.setBorderRadius(8, 8, 8, 8) 75 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 76 | 77 | self.text_screen = TextEdit() 78 | self.text_screen.setReadOnly(True) 79 | self.text_screen.setFixedHeight(self.cover_h) 80 | 81 | self.progressRing = ProgressRing(self) 82 | self.progressRing.setValue(0) 83 | self.progressRing.setTextVisible(True) 84 | self.progressRing.setFixedSize(50, 50) 85 | 86 | self.btn_run = PushButton('确定', self) 87 | self.btn_run.setShortcut(Qt.Key_Return) 88 | self.btn_stop = PushButton('取消', self) 89 | self.btn_hang = PushButton('确定', self) 90 | 91 | self.editline_hang = EditableComboBox(self) 92 | self.gridLayout = QGridLayout(self) 93 | self.screen_layout = QGridLayout() 94 | self.btn_layout = QGridLayout() 95 | self.hang_layout = QGridLayout() 96 | 97 | self.label_book.setFont(font_label) 98 | self.label_volumn.setFont(font_label) 99 | self.editline_book.setFont(font_label) 100 | self.editline_volumn.setFont(font_label) 101 | self.text_screen.setFont(font_msg) 102 | self.editline_hang.setFont(font_msg) 103 | 104 | self.gridLayout.addWidget(self.editline_book, 0, 1) 105 | self.gridLayout.addWidget(self.editline_volumn, 1, 1) 106 | self.gridLayout.addWidget(self.label_book, 0, 0) 107 | self.gridLayout.addWidget(self.label_volumn, 1, 0) 108 | 109 | self.gridLayout.addLayout(self.btn_layout, 2, 1, 1, 1) 110 | self.btn_layout.addWidget(self.btn_run, 2, 1) 111 | self.btn_layout.addWidget(self.btn_stop, 2, 2) 112 | 113 | self.gridLayout.addLayout(self.screen_layout, 3, 0, 2, 2) 114 | 115 | self.screen_layout.addWidget(self.progressRing, 0, 1, Qt.AlignLeft|Qt.AlignBottom) 116 | self.screen_layout.addWidget(self.text_screen, 0, 0) 117 | self.screen_layout.addWidget(self.label_cover, 0, 1) 118 | 119 | 120 | 121 | self.gridLayout.addLayout(self.hang_layout, 5, 0, 1, 2) 122 | self.hang_layout.addWidget(self.editline_hang, 0, 0) 123 | self.hang_layout.addWidget(self.btn_hang, 0, 1) 124 | 125 | self.screen_layout.setContentsMargins(0,0,0,0) 126 | self.btn_layout.setContentsMargins(0,0,0,0) 127 | self.gridLayout.setContentsMargins(20, 10, 20, 10) 128 | 129 | self.btn_run.clicked.connect(self.process_start) 130 | self.btn_stop.clicked.connect(self.process_stop) 131 | self.btn_hang.clicked.connect(self.process_continue) 132 | 133 | self.progressring_signal.connect(self.progressring_msg) 134 | self.end_signal.connect(self.process_end) 135 | self.hang_signal.connect(self.process_hang) 136 | self.clear_signal.connect(self.clear_screen) 137 | self.cover_signal.connect(self.display_cover) 138 | 139 | self.progressRing.hide() 140 | self.btn_hang.hide() 141 | self.editline_hang.hide() 142 | self.btn_stop.setEnabled(False) 143 | 144 | self.msg_out = EmittingStr(textWritten=self.outputWritten) 145 | 146 | self.text_screen.setText(self.parent.novel_text) 147 | 148 | def process_start(self): 149 | self.label_cover.setImage(self.book_icon) 150 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 151 | self.btn_run.setEnabled(False) 152 | self.btn_run.setText('正在下载') 153 | self.btn_stop.setEnabled(True) 154 | self.main_thread = MainThread(self) 155 | self.main_thread.start() 156 | 157 | def process_end(self, input=None): 158 | self.btn_run.setEnabled(True) 159 | self.btn_run.setText('开始下载') 160 | self.btn_run.setShortcut(Qt.Key_Return) 161 | self.btn_stop.setEnabled(False) 162 | self.progressRing.hide() 163 | self.btn_hang.hide() 164 | self.editline_hang.clear() 165 | self.editline_hang.hide() 166 | if input=='refresh': 167 | self.label_cover.setImage(self.book_icon) 168 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 169 | self.clear_signal.emit('') 170 | self.text_screen.setText(self.parent.novel_text) 171 | 172 | def outputWritten(self, text): 173 | cursor = self.text_screen.textCursor() 174 | scrollbar=self.text_screen.verticalScrollBar() 175 | is_bottom = (scrollbar.value()>=scrollbar.maximum() - 15) 176 | cursor.movePosition(QTextCursor.End) 177 | cursor.insertText(text) 178 | if is_bottom: 179 | self.text_screen.setTextCursor(cursor) 180 | # self.text_screen.ensureCursorVisible() 181 | 182 | def clear_screen(self): 183 | self.text_screen.clear() 184 | 185 | def display_cover(self, signal_msg): 186 | filepath, img_h, img_w = signal_msg 187 | self.label_cover.setImage(filepath) 188 | self.label_cover.setFixedSize(int(img_w*self.cover_h/img_h), self.cover_h) 189 | 190 | def progressring_msg(self, input): 191 | if input == 'start': 192 | self.label_cover.setImage(self.book_icon) 193 | self.label_cover.setFixedSize(self.cover_w, self.cover_h) 194 | self.progressRing.show() 195 | elif input == 'end': 196 | self.progressRing.hide() 197 | self.progressRing.setValue(0) 198 | else: 199 | self.progressRing.setValue(input) 200 | 201 | def process_hang(self, input=None): 202 | self.btn_hang.setEnabled(True) 203 | self.btn_hang.setShortcut(Qt.Key_Return) 204 | self.btn_hang.show() 205 | self.editline_hang.show() 206 | 207 | def process_continue(self, input=None): 208 | self.btn_hang.hide() 209 | self.btn_hang.setEnabled(False) 210 | self.editline_hang.hide() 211 | 212 | 213 | def process_stop(self): 214 | self.main_thread.terminate() 215 | self.end_signal.emit('refresh') -------------------------------------------------------------------------------- /frontend/cfg_utils.py: -------------------------------------------------------------------------------- 1 | import sqlite3 2 | import os 3 | 4 | DBPATH = './bilinovel-config.db' 5 | 6 | CREATE_CONFIG_TABLE_SQL = ''' 7 | CREATE TABLE IF NOT EXISTS config ( 8 | KEY TEXT PRIMARY KEY, 9 | VALUE TEXT 10 | ); 11 | ''' 12 | 13 | initial_config = {"download_path": './', "theme": "Auto", "interval": "4500", "numthread": '4'} 14 | 15 | def initialize_db(): 16 | if not os.path.exists(DBPATH): 17 | with sqlite3.connect(DBPATH) as conn: 18 | cursor = conn.cursor() 19 | cursor.execute("PRAGMA journal_mode=DELETE") 20 | cursor.execute(''' 21 | CREATE TABLE IF NOT EXISTS config ( 22 | KEY TEXT PRIMARY KEY, 23 | VALUE TEXT 24 | ); 25 | ''') 26 | for key, value in initial_config.items(): 27 | cursor.execute("INSERT OR REPLACE INTO config (KEY, VALUE) VALUES (?, ?)", (key, value)) 28 | conn.commit() 29 | 30 | def read_config_dict(key=None): 31 | with sqlite3.connect(DBPATH) as conn: 32 | cursor = conn.cursor() 33 | if key is None: 34 | return None 35 | else: 36 | cursor.execute("SELECT VALUE FROM config WHERE KEY = ?", (key,)) 37 | result = cursor.fetchone() 38 | return result[0] if result else None 39 | 40 | def write_config_dict(key, value): 41 | with sqlite3.connect(DBPATH) as conn: 42 | cursor = conn.cursor() 43 | cursor.execute("PRAGMA journal_mode=DELETE") 44 | cursor.execute("INSERT OR REPLACE INTO config (KEY, VALUE) VALUES (?, ?)", (key, value)) 45 | conn.commit() -------------------------------------------------------------------------------- /frontend/gui_utils.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PyQt5.QtCore import pyqtSignal, QObject 3 | from PyQt5.QtGui import QFont 4 | 5 | font_label = QFont('幼圆', 18) 6 | font_label.setBold(True) 7 | font_msg = QFont('幼圆', 12) 8 | font_msg.setBold(True) 9 | 10 | class EmittingStr(QObject): 11 | textWritten = pyqtSignal(str) # 定义一个发送str的信号 12 | def write(self, text): 13 | self.textWritten.emit(str(text)) 14 | def flush(self): 15 | pass 16 | def isatty(self): 17 | pass 18 | # sys.stdout = EmittingStr(textWritten=outputWritten) 19 | # sys.stderr = EmittingStr(textWritten=outputWritten) -------------------------------------------------------------------------------- /frontend/mainwindow.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PyQt5.QtCore import QTimer, QSize 3 | from PyQt5.QtGui import QIcon, QPixmap, QColor 4 | from PyQt5.QtWidgets import QApplication 5 | from qfluentwidgets import (setTheme, Theme, Theme, setTheme, Theme, FluentWindow, NavigationItemPosition, qconfig, SplashScreen) 6 | 7 | from qfluentwidgets import FluentIcon as FIF 8 | import base64 9 | from resource.logo import logo_base64 10 | from resource.logo_big import logo_big_base64 11 | from backend.bilinovel.bilinovel_router import * 12 | 13 | from frontend.cfg_utils import * 14 | from frontend.gui_utils import font_label 15 | from frontend.bilinovel_gui import NovelWidget 16 | from frontend.setting import SettingWidget 17 | 18 | class MainWindow(FluentWindow): 19 | def __init__(self): 20 | super().__init__() 21 | pixmap = QPixmap() 22 | pixmap.loadFromData(base64.b64decode(logo_big_base64)) 23 | # create splash screen 24 | self.splashScreen = SplashScreen(QIcon(pixmap), self) 25 | self.splashScreen.setIconSize(QSize(400, 400)) 26 | self.splashScreen.raise_() 27 | 28 | initialize_db() 29 | self.out_path = read_config_dict("download_path") 30 | split_str = '--------------------------------\n ' 31 | self.novel_text = f'使用说明(必看):\n{split_str}1. https://www.linovelib.com,输入书号以及下载的卷号,例如网址是https://www.linovelib.com/novel/2704.html,则书号输入2704。若不确定卷号,可以只输入书号,点击确定会返回书籍卷名称和对应的卷号。\n{split_str}3.要下载编号[2]对应卷,则卷号输入2。想下载多卷比如[1]至[3]对应卷,则卷号输入1-3或1,2,3(英文逗号分隔,编号可以不连续)。' 32 | self.interval = read_config_dict('interval') 33 | self.NovelInterface = NovelWidget('Novel Interface', self) 34 | self.settingInterface = SettingWidget('Setting Interface', self) 35 | self.initNavigation() 36 | self.initWindow() 37 | QTimer.singleShot(50, lambda: self.set_theme(read_config_dict('theme'))) 38 | QTimer.singleShot(2000, lambda: self.splashScreen.close()) 39 | 40 | def initNavigation(self): 41 | self.addSubInterface(self.NovelInterface, FIF.BOOK_SHELF, '哔哩轻小说') 42 | self.addSubInterface(self.settingInterface, FIF.SETTING, '设置', NavigationItemPosition.BOTTOM) 43 | 44 | def initWindow(self): 45 | self.resize(700, 460) 46 | pixmap = QPixmap() 47 | pixmap.loadFromData(base64.b64decode(logo_base64)) 48 | 49 | self.setWindowIcon(QIcon(pixmap)) 50 | self.setWindowTitle('哔哩轻小说EPUB下载器-Edge浏览器版') 51 | self.setFont(font_label) 52 | 53 | desktop = QApplication.desktop().availableGeometry() 54 | w, h = desktop.width(), desktop.height() 55 | self.move(w//2 - self.width()//2, h//2 - self.height()//2) 56 | 57 | def set_theme(self, mode=None): 58 | if mode=='Light': 59 | setTheme(Theme.LIGHT) 60 | elif mode=='Dark': 61 | setTheme(Theme.DARK) 62 | elif mode== 'Auto': 63 | setTheme(Theme.AUTO) 64 | theme = qconfig.theme 65 | if theme == Theme.DARK: 66 | self.NovelInterface.label_book.setTextColor(QColor(255,255,255)) 67 | self.NovelInterface.label_volumn.setTextColor(QColor(255,255,255)) 68 | elif theme == Theme.LIGHT: 69 | self.NovelInterface.label_book.setTextColor(QColor(0,0,0)) 70 | self.NovelInterface.label_volumn.setTextColor(QColor(0,0,0)) -------------------------------------------------------------------------------- /frontend/setting.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PyQt5.QtCore import Qt 3 | from PyQt5.QtWidgets import QFileDialog, QWidget 4 | from qfluentwidgets import (Theme, PushSettingCard, SettingCardGroup, ExpandLayout, Theme, Theme, OptionsSettingCard, OptionsConfigItem, OptionsValidator, RangeSettingCard, ScrollArea, RangeValidator, RangeConfigItem) 5 | 6 | from qfluentwidgets import FluentIcon as FIF 7 | from .cfg_utils import read_config_dict, write_config_dict 8 | import os 9 | import shutil 10 | 11 | 12 | class SettingWidget(ScrollArea): 13 | def __init__(self, text, parent=None): 14 | super().__init__(parent=parent) 15 | self.scrollWidget = QWidget() 16 | self.expandLayout = ExpandLayout(self.scrollWidget) 17 | self.parent = parent 18 | self.setting_group = SettingCardGroup(self.tr("设置"), self.scrollWidget) 19 | 20 | self.download_path_card = PushSettingCard( 21 | self.tr('选择文件夹'), 22 | FIF.DOWNLOAD, 23 | self.tr("下载目录"), 24 | self.parent.out_path, 25 | self.setting_group 26 | ) 27 | 28 | theme_name = read_config_dict('theme') 29 | if theme_name == 'Light': 30 | self.themeMode = OptionsConfigItem( 31 | None, "ThemeMode", Theme.LIGHT, OptionsValidator(Theme), None) 32 | elif theme_name == 'Dark': 33 | self.themeMode = OptionsConfigItem( 34 | None, "ThemeMode", Theme.DARK, OptionsValidator(Theme), None) 35 | else: 36 | self.themeMode = OptionsConfigItem( 37 | None, "ThemeMode", Theme.AUTO, OptionsValidator(Theme), None) 38 | 39 | self.interval_card = RangeSettingCard( 40 | RangeConfigItem("interval", "时间间隔", int(read_config_dict("interval")), RangeValidator(0, 8000)), 41 | FIF.DATE_TIME, 42 | self.tr('下载时间间隔(毫秒)'), 43 | self.tr('如果页面频繁陷入超时,建议适当延长下载间隔'), 44 | self.setting_group 45 | ) 46 | 47 | self.thread_card = RangeSettingCard( 48 | RangeConfigItem("thread", "下载线程数量", int(read_config_dict("numthread")), RangeValidator(1, 10)), 49 | FIF.SPEED_HIGH, 50 | self.tr('小说插画下载线程数量'), 51 | self.tr('适当增加充分利用带宽,但不要太高'), 52 | self.setting_group 53 | ) 54 | 55 | self.theme_card = OptionsSettingCard( 56 | self.themeMode, 57 | FIF.BRUSH, 58 | self.tr('应用主题'), 59 | self.tr("更改外观"), 60 | texts=[ 61 | self.tr('亮'), self.tr('暗'), 62 | self.tr('跟随系统') 63 | ], 64 | parent=self.setting_group 65 | ) 66 | 67 | 68 | self.resize(1000, 800) 69 | self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) 70 | self.setViewportMargins(0, 10, 0, 20) 71 | self.setWidget(self.scrollWidget) 72 | self.setWidgetResizable(True) 73 | self.setObjectName('settingInterface2') 74 | 75 | # initialize style sheet 76 | self.scrollWidget.setObjectName('scrollWidget') 77 | qss = ''' 78 | SettingInterface, #scrollWidget { 79 | background-color: transparent; 80 | } 81 | 82 | QScrollArea { 83 | border: none; 84 | background-color: transparent; 85 | } 86 | 87 | QLabel#settingLabel { 88 | font: 33px 'Microsoft YaHei Light'; 89 | background-color: transparent; 90 | color: white; 91 | } 92 | 93 | ''' 94 | 95 | self.setStyleSheet(qss) 96 | 97 | self.setting_group.addSettingCard(self.download_path_card) 98 | self.setting_group.addSettingCard(self.interval_card) 99 | self.setting_group.addSettingCard(self.thread_card) 100 | self.setting_group.addSettingCard(self.theme_card) 101 | self.expandLayout.setSpacing(28) 102 | self.expandLayout.setContentsMargins(20, 10, 20, 0) 103 | self.expandLayout.addWidget(self.setting_group) 104 | 105 | self.download_path_card.clicked.connect(self.download_path_changed) 106 | self.theme_card.optionChanged.connect(self.theme_changed) 107 | self.interval_card.valueChanged.connect(self.interval_changed) 108 | self.thread_card.valueChanged.connect(self.thread_changed) 109 | 110 | 111 | def download_path_changed(self): 112 | """ download folder card clicked slot """ 113 | new_path = QFileDialog.getExistingDirectory( 114 | self, self.tr("Choose folder"), self.parent.out_path) 115 | if new_path: 116 | write_config_dict("download_path", new_path) 117 | self.download_path_card.contentLabel.setText(read_config_dict("download_path")) 118 | 119 | def theme_changed(self): 120 | theme_name = self.theme_card.choiceLabel.text() 121 | if theme_name == '亮': 122 | theme_mode = 'Light' 123 | elif theme_name == '暗': 124 | theme_mode = 'Dark' 125 | elif theme_name == '跟随系统': 126 | theme_mode = 'Auto' 127 | write_config_dict("theme", theme_mode) 128 | self.parent.set_theme(read_config_dict("theme")) 129 | self.delete() 130 | 131 | def interval_changed(self): 132 | interval = self.interval_card.valueLabel.text() 133 | write_config_dict("interval", interval) 134 | self.delete() 135 | 136 | def thread_changed(self): 137 | num_thread = self.thread_card.valueLabel.text() 138 | write_config_dict("numthread", num_thread) 139 | self.delete() 140 | 141 | def delete(self): 142 | if os.path.exists('./config'): 143 | shutil.rmtree('./config') -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # coding:utf-8 2 | from PyQt5.QtCore import Qt 3 | from PyQt5.QtWidgets import QApplication 4 | from qfluentwidgets import (setTheme, Theme, setThemeColor) 5 | import sys 6 | from frontend.mainwindow import MainWindow 7 | 8 | if __name__ == '__main__': 9 | QApplication.setHighDpiScaleFactorRoundingPolicy(Qt.HighDpiScaleFactorRoundingPolicy.PassThrough) 10 | QApplication.setAttribute(Qt.AA_EnableHighDpiScaling) 11 | QApplication.setAttribute(Qt.AA_UseHighDpiPixmaps) 12 | 13 | setTheme(Theme.DARK) 14 | setThemeColor('#FF7233') 15 | app = QApplication(sys.argv) 16 | w = MainWindow() 17 | w.show() 18 | app.exec_() 19 | -------------------------------------------------------------------------------- /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 8 | DrissionPage -------------------------------------------------------------------------------- /resource/__pycache__/book.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/__pycache__/book.cpython-311.pyc -------------------------------------------------------------------------------- /resource/__pycache__/logo.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/__pycache__/logo.cpython-311.pyc -------------------------------------------------------------------------------- /resource/__pycache__/logo_big.cpython-311.pyc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/__pycache__/logo_big.cpython-311.pyc -------------------------------------------------------------------------------- /resource/book.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/book.png -------------------------------------------------------------------------------- /resource/book.py: -------------------------------------------------------------------------------- 1 | book_base64 = 'iVBORw0KGgoAAAANSUhEUgAAAQ8AAAGKCAMAAAAljDRnAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAACHUExURQAAAL+/v7Ompq+vr66oqK+qqq+rq66qqq6rq6+pqa2rq6+tra6srK6pqa+rq62pqbCsrK+srK+srK6pqa+srK+srK+rq66qqq+rq6+rq7CsrK+rq6+rq7Crq66rq66qqrCrq6+rq6+rq6+rq6+rq6+rq66qqq6rq6+qqq+rq6+srLCrq7CsrNpNNrIAAAAmdFJOUwAEFCMsMEBITFNkZnJ0fICHnJ+go6+/wcLDx8/S19rf5+/w9fn7jVpzpgAAAAlwSFlzAAAywAAAMsABKGRa2wAABoxJREFUeF7t3Wl3ozYARuF0Y7q7q7t3mi5pM+3//32VxOuEFwOSHMAeuM+H2rFlQHdkO9A5Z+4AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADwck1zDB5b4d7h0OiZPWpOJVyoogH7ctD8B+2viSY+7rirJJOr42RHq2T4s+PcXpJouiV2UURzLbODIpppqc2/bTTPCtsuoklW2XIRTbHSdotogtW2WkTTq7fRUz7NrpX+0JvmcCj6Le3lS6SJdP9GaG6t7gybgijVRVLr50sLXekyw5UvNITD09G0+vPLNikPEjZVemoQrzSsnaV7AejZ0PQySQqKlKy0Aetdfxm5ADQ6ucPUSfD0QV/Y4snSTeKVQe1qwPjOp6Y19qqwL414kbmuv2jm7edU/Bxv0uanTO2591HTNfSymWK00g7id1H6NI6P1Ec6O/wH3U7I7GS0SP91s8YYU1fkbDEU5MjvYqzIUc8na8SIaoJMfQKOK9jD2IZPX5BrxYjKv5TzHxWDioqPFEkHt2aN3qqcdHbMJe+W4hU4XCR8YK9ao6bHhQdW+o4cDvKTbtejw8nT+Frln1BjHyPr0sHkaXyt8h4vem/Ec4V47hZ/K0q3QfnZTYeOJU/ja1X0uGSJhArT5/cpT0UYvSxP4yfFP6TGv4iqelQFibvSywqUXoHR8DyNH3c8HZ1+btX1KP1WP152zafgVFAj8zR+RPfqgh5qVfYo+B67sEUruwA1Lk/jR3SnrYda1T2mgzwtwgu9hT3Gg7xoZbQW6hFnqbutOXuMBHnp0ki8x9hDJTS+tWyPoSAXbefc29mjv6pnWRrJFnrMV2MbPfTgLOjh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OHo4ejh6OGu3uOif/xpwz2i+E+xVmXZeI9WRZRd9EhCFI2asp8eSbbJznoEx8n3zv56RONJ9tkjGEmy2x7xjaNXdW2rR/ZffO45L7KtHoX/JnhH/1t4Wz38mVLdj5KN9ah9w8jzItlYj94uyp2KbK1H1LQOh8OxYr2029liDxe6FFaJW9p+j1ZRlLCpvfSIDr7/Ib0heuEsbq9H0OSWiT+tV83iJntEpR8ngV4xi5vtEVeJXpuj8bO4To/TN21HfETPPiv4LAk0OGdgp0Fvt6v2iMdznP5tI15ajgcZh0cli0RDB6UEmX0GT1e01+pxrPqtK4ll4jFmi6Td9+XTDwhHqXuttKVFelwuZPlad0e0+3/WNNUhRqTN3ViP5LVuB7X7T+ZLkaRt3mKPx8cH3Q5o9x9izNoiSpu9zR4TReLew8LQT3OKW77ZHqNF4srQ3Zmlid1uj5EgC8UI0sRuuMfkx8gC0sRuuse6QdLEbrvHqtLE5uqx3Nt6Lcc0sbl61P9vllJrvWfiLObrMbJAwllFezb7fF6ZfkqnnoW/UVUHCTtNp0ORdto67VfjTLs85uvRDxIOaeCc/lw6xHnebWmXJfsMew071atayjFjj06Q6b/PMSyeqevlffkFcjwWdnDPTU455uxxF8+545+RfrzARBQ5a3NhipN0yaXz1x7n6BEP6kx8o/akN7TT5ky/ycTyGGuhrRsdRIeOtEvbPdHm8jR+Cem4XnfO+bs9Ovfv739KQ2c/z+3QbPM0fkkPti4mFsmCNNs8jV+YJ7kCzTZP45d33SKabZ7GL+yf+J/eIok/pcfXoNnmafw6Hq72vtFs8zR+LQ+9982/ul2aZpu34Hfck7UmPe7pt9as5U5nb8npF+8CKyyQ/3R7NeXL49oL5I1ul1V1XrR8kIlJr/KFW3maGM+6dAJhtLUlvflbd+agw3bh4cocVXSK2aVzTKODOfn5Dx1yz5+/aECHttClHRkdz1vqw79UwH2sp/fno++VoOPXT/TkHr37xb0ynHz7vp7aqfc+/U4lgt+/fKWHd+ydV59/88Nvj/c/fvXZB3oIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABUuLv7Hzo1zyb0ghw2AAAAAElFTkSuQmCC' -------------------------------------------------------------------------------- /resource/example1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/example1.png -------------------------------------------------------------------------------- /resource/example2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/example2.png -------------------------------------------------------------------------------- /resource/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/logo.png -------------------------------------------------------------------------------- /resource/logo.py: -------------------------------------------------------------------------------- 1 | logo_base64 = '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' -------------------------------------------------------------------------------- /resource/logo_big.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/logo_big.png -------------------------------------------------------------------------------- /resource/logo_big.py: -------------------------------------------------------------------------------- 1 | logo_big_base64 = 'iVBORw0KGgoAAAANSUhEUgAAAeMAAABkCAMAAACCa6jeAAAA21BMVEVHcEz////5lW3////4/P7/////////////////////////////////////////v7/////////////////////////////9k276lWv////8lm77lm77lW3////////7lW78lm//////////UxAAnuL7lm7/Uw//VBD/t6X/3dXU6fj/imqj0vFnuenl8vv/r5XC4fX/5d3+m3r/7un/XST/18v+yrckpeT/wa3/zr8/rOaHxu3/+PX/c0V4wOv/fVb9qIdUs+j/aTf/9PCw2PP/ooeWzO/+tpz/km8gTz35AAAAInRSTlMAdfDE/kWIgcBBM7Ce3BIECPLSIWiRUxUz5cOZgGEn3Fu6k74QPAAAGe5JREFUeNrtnQlbGknXhkU2FQIu6MQl0RRJN/sOgoK46///Rd85Vd08XVSvJt/kdS6eyUwSBGX67rPWqWJro4022mijjTbaaKONNvq3VP56dXF+drm9/Wt7+/Ls7Pzi6mt5a6P/EN+Ls+1fhrbPLjac/xv6eg6+Jufzr1sbfW6VLy5/Rejy4p+tjT6t/tFMOMSYN5Q/K+ELEI6ivLHlT6krEI5D+Wpro8+l8j9nBsb76X0Y5bONKX9yIx5MhkKIeW1jyv8Rlc/X8HX7Qmn+K0znm3L57+toL86z1vz0YDoUrOGUSN//2vjr/xHtpQopg2fqVIiDYmU/qulx6WfCs6ZtT4R4iyiW/0BLJJtLC9Lh8cnWXxa/EVZ6Z2cnn8+f7O7+7ziqSkGwivo7Kgil09z3UMTeUHyvCA+7j1a1Wp2BcZC2E0BOFQ58YObFSse/47J2Sb8HhQib+rJTyOezpQ8b3+5JPrv/24S/F4WjonZFxUqZb+GIob4y4VHVqtrV6liI7q8/Bfnohy/MivAolzSR2N3NEoF9dllSsMPjPH1hdzcBnJwI0WElOZiT40PnPsn+JuJvGbFSxXMDCVKn40D+HhiL9d4lPbe2sKqWXbXAOFyXMWMyIYZgyYfCo4OtJNrHzS0gH0vMZ31fntUsrHQgQqVBTnhPf9n/PStWiHt3DXbLJc1TZ65/3rQk5m+hiKG5EI/VqiV/2WAcC/Ju+P+yIJmGXOLkvVklLdmH7CWJ419EbOX9Xq7HsRO2iZ9SbVKr1eo1Gl5LTgjm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' -------------------------------------------------------------------------------- /resource/mainpage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ShqWW/bilinovel-download/5e3632da9f7536f5d506c24a3f21ab956f6d7db6/resource/mainpage.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 = "logo_big.png " 22 | base64_string = image_to_base64(image_path) 23 | print(base64_string) --------------------------------------------------------------------------------