novel-downloader 1.4.3__py3-none-any.whl → 1.4.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -6,7 +6,7 @@ novel_downloader
6
6
  Core package for the Novel Downloader project.
7
7
  """
8
8
 
9
- __version__ = "1.4.3"
9
+ __version__ = "1.4.5"
10
10
 
11
11
  __author__ = "Saudade Z"
12
12
  __email__ = "saudadez217@gmail.com"
@@ -22,8 +22,8 @@ class QidianBrowser(BaseBrowser):
22
22
 
23
23
  HOMEPAGE_URL = "https://www.qidian.com/"
24
24
  BOOKCASE_URL = "https://my.qidian.com/bookcase/"
25
- BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
26
- # BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
25
+ # BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
26
+ BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
27
27
  CHAPTER_URL = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
28
28
 
29
29
  LOGIN_URL = "https://passport.qidian.com/"
@@ -27,7 +27,8 @@ class QidianSession(BaseSession):
27
27
 
28
28
  HOMEPAGE_URL = "https://www.qidian.com/"
29
29
  BOOKCASE_URL = "https://my.qidian.com/bookcase/"
30
- BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
30
+ # BOOK_INFO_URL = "https://book.qidian.com/info/{book_id}/"
31
+ BOOK_INFO_URL = "https://www.qidian.com/book/{book_id}/"
31
32
  CHAPTER_URL = "https://www.qidian.com/chapter/{book_id}/{chapter_id}/"
32
33
 
33
34
  LOGIN_URL = "https://passport.qidian.com/"
@@ -10,36 +10,19 @@ time, status, word count, summary, and volume-chapter structure.
10
10
  """
11
11
 
12
12
  import logging
13
+ import re
14
+ from datetime import datetime
13
15
  from typing import Any
14
16
 
15
17
  from lxml import html
16
18
 
17
19
  logger = logging.getLogger(__name__)
18
20
 
19
- _AUTHOR_XPATH = (
20
- 'string(//div[contains(@class, "book-info")]//a[contains(@class, "writer")])'
21
- )
22
-
23
21
 
24
22
  def _chapter_url_to_id(url: str) -> str:
25
23
  return url.rstrip("/").split("/")[-1]
26
24
 
27
25
 
28
- def _get_volume_name(
29
- vol_elem: html.HtmlElement,
30
- ) -> str:
31
- """
32
- Extracts the volume title from a <div class="volume"> element using lxml.
33
- Ignores <a> tags, and extracts text from other elements.
34
- """
35
- h3_candidates = vol_elem.xpath(".//h3")
36
- if not h3_candidates:
37
- return ""
38
- texts = vol_elem.xpath(".//h3//text()[not(ancestor::a)]")
39
- full_text = "".join(texts).strip()
40
- return full_text.split(chr(183))[0].strip()
41
-
42
-
43
26
  def parse_book_info(html_str: str) -> dict[str, Any]:
44
27
  """
45
28
  Extract metadata: title, author, cover_url, update_time, status,
@@ -52,59 +35,53 @@ def parse_book_info(html_str: str) -> dict[str, Any]:
52
35
  try:
53
36
  doc = html.fromstring(html_str)
54
37
 
55
- book_name = doc.xpath('string(//h1/em[@id="bookName"])').strip()
56
- info["book_name"] = book_name
38
+ info["book_name"] = doc.xpath('string(//h1[@id="bookName"])').strip()
57
39
 
58
- author = doc.xpath(_AUTHOR_XPATH).strip()
59
- info["author"] = author
40
+ info["author"] = doc.xpath('string(//a[@class="writer-name"])').strip()
60
41
 
61
- cover_url = doc.xpath('string(//div[@class="book-img"]//img/@src)').strip()
62
- info["cover_url"] = cover_url
42
+ book_id = doc.xpath('//a[@id="bookImg"]/@data-bid')[0]
43
+ info[
44
+ "cover_url"
45
+ ] = f"https://bookcover.yuewen.com/qdbimg/349573/{book_id}/600.webp"
63
46
 
64
- update_raw = (
65
- doc.xpath('string(//span[contains(@class, "update-time")])')
66
- .replace("更新时间", "")
47
+ ut = (
48
+ doc.xpath('string(//span[@class="update-time"])')
49
+ .replace("更新时间:", "")
67
50
  .strip()
68
51
  )
69
- info["update_time"] = update_raw
52
+ if re.match(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$", ut):
53
+ info["update_time"] = ut
54
+ else:
55
+ info["update_time"] = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
70
56
 
71
- status = doc.xpath('string(//p[@class="tag"]/span[@class="blue"][1])').strip()
72
- info["serial_status"] = status
57
+ info["serial_status"] = doc.xpath(
58
+ 'string(//p[@class="book-attribute"]/span[1])'
59
+ ).strip()
73
60
 
74
- tags = doc.xpath('//p[@class="tag"]/a[@class="red"]/text()')
61
+ tags = doc.xpath('//p[contains(@class,"all-label")]//a/text()')
75
62
  info["tags"] = [t.strip() for t in tags if t.strip()]
76
63
 
77
- wc_number = doc.xpath("string(//p[em and cite][1]/em[1])").strip()
78
- wc_unit = doc.xpath("string(//p[em and cite][1]/cite[1])").strip()
79
- info["word_count"] = (
80
- (wc_number + wc_unit) if wc_number and wc_unit else "Unknown"
81
- )
64
+ info["word_count"] = doc.xpath('string(//p[@class="count"]/em[1])').strip()
82
65
 
83
66
  summary = doc.xpath('string(//p[@class="intro"])').strip()
84
67
  info["summary_brief"] = summary
85
68
 
86
- intro_list = doc.xpath('//div[@class="book-intro"]/p')[0]
87
- detail_intro = "\n".join(intro_list.itertext()).strip()
88
- info["summary"] = detail_intro
69
+ raw = doc.xpath('//p[@id="book-intro-detail"]//text()')
70
+ info["summary"] = "\n".join(line.strip() for line in raw if line.strip())
89
71
 
90
72
  volumes = []
91
- for vol_div in doc.xpath('//div[@class="volume-wrap"]/div[@class="volume"]'):
92
- volume_name = _get_volume_name(vol_div)
73
+ for vol in doc.xpath('//div[@id="allCatalog"]//div[@class="catalog-volume"]'):
74
+ vol_name = vol.xpath('string(.//h3[@class="volume-name"])').strip()
75
+ vol_name = vol_name.split(chr(183))[0].strip()
93
76
  chapters = []
94
- for li in vol_div.xpath(".//li"):
95
- a = li.xpath(".//a")[0] if li.xpath(".//a") else None
96
- if a is None or "href" not in a.attrib:
97
- continue
98
- href = a.attrib["href"].strip()
99
- title = "".join(a.itertext()).strip()
77
+ for li in vol.xpath('.//ul[contains(@class,"volume-chapters")]/li'):
78
+ a = li.xpath('.//a[@class="chapter-name"]')[0]
79
+ title = a.text.strip()
80
+ url = a.get("href")
100
81
  chapters.append(
101
- {
102
- "title": title,
103
- "url": href,
104
- "chapterId": _chapter_url_to_id(href),
105
- }
82
+ {"title": title, "url": url, "chapterId": _chapter_url_to_id(url)}
106
83
  )
107
- volumes.append({"volume_name": volume_name, "chapters": chapters})
84
+ volumes.append({"volume_name": vol_name, "chapters": chapters})
108
85
  info["volumes"] = volumes
109
86
 
110
87
  except Exception as e:
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env python3
2
2
  """
3
3
  novel_downloader.tui.screens.login
4
- ---------------------------------
4
+ ----------------------------------
5
5
 
6
6
  """
7
7
 
@@ -138,7 +138,7 @@ def calculate_time_difference(
138
138
 
139
139
  except Exception as e:
140
140
  logger.warning("[time] Failed to calculate time difference: %s", e)
141
- return 0, 0, 0, 0
141
+ return 999, 23, 59, 59
142
142
 
143
143
 
144
144
  __all__ = [
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: novel-downloader
3
- Version: 1.4.3
3
+ Version: 1.4.5
4
4
  Summary: A command-line tool for downloading Chinese web novels from Qidian and similar platforms.
5
5
  Author-email: Saudade Z <saudadez217@gmail.com>
6
6
  License: MIT License
@@ -1,4 +1,4 @@
1
- novel_downloader/__init__.py,sha256=JN4ThR5xMeCKx_BPUuZvUrtJJaTH5Tx7ORZZURIY0iY,218
1
+ novel_downloader/__init__.py,sha256=D4TLFZz_e2GGLXLUmjASiuLDco00RW6Lg3AI38kcgUg,218
2
2
  novel_downloader/cli/__init__.py,sha256=-2HAut_U1e67MZGdvbpEJ1n5J-bRchzto6L4c-nWeXY,174
3
3
  novel_downloader/cli/clean.py,sha256=hOk8SJQwBCw2oOObTdEI79wpnmZ25uB1s9LQK1-4LNU,4487
4
4
  novel_downloader/cli/config.py,sha256=C6QLfegZLp4legmu8KenqyYKNdrk47bH0z86ujLP0pY,6509
@@ -63,8 +63,8 @@ novel_downloader/core/fetchers/qianbi/__init__.py,sha256=h4Rve7fO1GcSJ-DlNC5zw7f
63
63
  novel_downloader/core/fetchers/qianbi/browser.py,sha256=1EmrSwpqSYhEO_ID3RJbUaAOhcqvVnMnch6iOafXbTA,3162
64
64
  novel_downloader/core/fetchers/qianbi/session.py,sha256=c3pJcgi9C1x9QYTBihvazHcgT7XTp2HBYfStTn6gSEg,3141
65
65
  novel_downloader/core/fetchers/qidian/__init__.py,sha256=2LshlX82lFpWZMV6yujHsfue9KM0-F1O3HvMCopIv9M,236
66
- novel_downloader/core/fetchers/qidian/browser.py,sha256=cUgJMrFe_MITHh4MGrDI2-a-MCqXSsoeJsbDus0l2LY,10448
67
- novel_downloader/core/fetchers/qidian/session.py,sha256=WrHsov15PoRDYbb1ZRaScJQGRZv6axz9_hVEC1Wt1PM,9746
66
+ novel_downloader/core/fetchers/qidian/browser.py,sha256=0L2iUyOlFNmgKtSb89VMrF7L-MjiJy4E6SSKWxzeSC8,10448
67
+ novel_downloader/core/fetchers/qidian/session.py,sha256=dsawkalQGjbS-E9lcOZMTr9qZz6w__gnmAg-uRUtDKI,9809
68
68
  novel_downloader/core/fetchers/sfacg/__init__.py,sha256=bQAIwERsX9XOKrP2LteFKX8Jlhw4oeUNwpZTHXn5RRg,230
69
69
  novel_downloader/core/fetchers/sfacg/browser.py,sha256=15PVS75PxEKR5W7mQbqVxoN0d4V1XVYVF0l1yy_sv_Y,5681
70
70
  novel_downloader/core/fetchers/sfacg/session.py,sha256=9K4emQCRq45vzYn-ZDX549tK2F92x2CBMp4ODohNOjc,5085
@@ -90,7 +90,7 @@ novel_downloader/core/parsers/linovelib/main_parser.py,sha256=LqC6W-Lk7sdbiqhYvq
90
90
  novel_downloader/core/parsers/qianbi/__init__.py,sha256=CNmoER8U2u4-ix5S0DDq-pHTtkLR0IZf2SLaTTYXee4,173
91
91
  novel_downloader/core/parsers/qianbi/main_parser.py,sha256=kMjGew_dmqjI9oIThyHgxThZZgS3IaESB_tNf1nkCKk,5069
92
92
  novel_downloader/core/parsers/qidian/__init__.py,sha256=fWWFeyythX0gpDCJ-2AslrRl2hq4vW2bx9hHh1W7mAw,173
93
- novel_downloader/core/parsers/qidian/book_info_parser.py,sha256=h5oAVJOOSZMV4U2GnSezVHPJOEUKUic_oD2gl94mEPQ,3721
93
+ novel_downloader/core/parsers/qidian/book_info_parser.py,sha256=TbP8nghoxzBi435cSJKAzWttBRUq49MHw6kn0klfZrU,3056
94
94
  novel_downloader/core/parsers/qidian/chapter_encrypted.py,sha256=uIVmRH96O_G0hcvg2nufUQDrZzq_XRhfvB_8YRW22g8,18532
95
95
  novel_downloader/core/parsers/qidian/chapter_normal.py,sha256=CClt6zVph7qjZ0HaiYzMPYlcFgnPIY783-SLVIXNL7E,4738
96
96
  novel_downloader/core/parsers/qidian/chapter_router.py,sha256=foVMlWtE-qUOvJD_4EDiuAVaNkFdeV_ZTCvS5IL7Orc,1957
@@ -126,7 +126,7 @@ novel_downloader/tui/app.py,sha256=ytV1u15nGCRj_ff_GeAL3W1XlU6r5Lh_k3HBcAjPRx0,7
126
126
  novel_downloader/tui/main.py,sha256=MBP8SrwEYTpGQm-V9W_4rKnTeslneORfkzFsU3Xj2yA,256
127
127
  novel_downloader/tui/screens/__init__.py,sha256=QsUM5cUEKm7nluQh9acEt37xRWbkZU3vqIpBduepDCU,203
128
128
  novel_downloader/tui/screens/home.py,sha256=nH3pRuCOFcSvNV4AYC6RRkWpphmRgY1bNK36OkSVfCg,6994
129
- novel_downloader/tui/screens/login.py,sha256=eEVmQFZKQX8mCt_qp96QK8t2pB15FeUrk_oV3jm_Mz4,2224
129
+ novel_downloader/tui/screens/login.py,sha256=GK8dXCtH5lGu3ilT1Cv2VmFCwHMspDNpzw0logj2lz0,2225
130
130
  novel_downloader/tui/styles/home_layout.tcss,sha256=VNJs339qiwNUuqwwdK6VkYThCdsqlw-2mEoZVCspdrw,974
131
131
  novel_downloader/tui/widgets/richlog_handler.py,sha256=bhFb0E7Z7-dRS07y_vMjxjPeCic9AD59MFPLhef_R5g,572
132
132
  novel_downloader/utils/__init__.py,sha256=4iUXNUzxeAnGmpGWsB4K_jckUYEW0u_LqWp_OM7mtK8,78
@@ -155,11 +155,11 @@ novel_downloader/utils/text_utils/diff_display.py,sha256=aUvjMcYO-1_P8ZYiYbmYbJO
155
155
  novel_downloader/utils/text_utils/font_mapping.py,sha256=Aos5skBhowDdPgnYmK0bpLtNm2hZg3RolNlTkxC9kO8,865
156
156
  novel_downloader/utils/text_utils/text_cleaning.py,sha256=zufXt0pc0vnUwgSHD_8LAef3ffqmX9rJ11YzW4rgwLA,2940
157
157
  novel_downloader/utils/time_utils/__init__.py,sha256=725vY2PvqFhjbAz0hCOuIuhSCK8HrEqQ_k3YwvubmXo,624
158
- novel_downloader/utils/time_utils/datetime_utils.py,sha256=1eyX8lTEqkQ-rEej_GrhbUpaIR5tsFdVSKT-q8s9X-g,4927
158
+ novel_downloader/utils/time_utils/datetime_utils.py,sha256=3sPdUjDC7Y7dcX6kaQSBu49BU1tnpERNKq0v9lnD170,4932
159
159
  novel_downloader/utils/time_utils/sleep_utils.py,sha256=C4XYeAtxoVZC9Ju6vhhP9sbOrSpdZG2Nm-x1IYO_OFA,3233
160
- novel_downloader-1.4.3.dist-info/licenses/LICENSE,sha256=XgmnH0mBf-qEiizoVAfJQAKzPB9y3rBa-ni7M0Aqv4A,1066
161
- novel_downloader-1.4.3.dist-info/METADATA,sha256=MiScyRXyeXncWbjtBIZsFDJqGfL925DraPy7v6TvakQ,7253
162
- novel_downloader-1.4.3.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
163
- novel_downloader-1.4.3.dist-info/entry_points.txt,sha256=u1Ns5xI_QJyL4HAFCgJvJdib9ugu7M9I2tnQwZjJxrk,112
164
- novel_downloader-1.4.3.dist-info/top_level.txt,sha256=hP4jYWM2LTm1jxsW4hqEB8N0dsRvldO2QdhggJT917I,17
165
- novel_downloader-1.4.3.dist-info/RECORD,,
160
+ novel_downloader-1.4.5.dist-info/licenses/LICENSE,sha256=XgmnH0mBf-qEiizoVAfJQAKzPB9y3rBa-ni7M0Aqv4A,1066
161
+ novel_downloader-1.4.5.dist-info/METADATA,sha256=Et3KgvnjS1RjOo-dX-GRZg6q4TU8uIzg1vMYfPR7sxs,7253
162
+ novel_downloader-1.4.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
163
+ novel_downloader-1.4.5.dist-info/entry_points.txt,sha256=u1Ns5xI_QJyL4HAFCgJvJdib9ugu7M9I2tnQwZjJxrk,112
164
+ novel_downloader-1.4.5.dist-info/top_level.txt,sha256=hP4jYWM2LTm1jxsW4hqEB8N0dsRvldO2QdhggJT917I,17
165
+ novel_downloader-1.4.5.dist-info/RECORD,,