novel-downloader 1.4.4__py3-none-any.whl → 1.5.0__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.
- novel_downloader/__init__.py +1 -1
- novel_downloader/cli/__init__.py +2 -2
- novel_downloader/cli/config.py +1 -83
- novel_downloader/cli/download.py +4 -5
- novel_downloader/cli/export.py +4 -1
- novel_downloader/cli/main.py +2 -0
- novel_downloader/cli/search.py +123 -0
- novel_downloader/config/__init__.py +3 -10
- novel_downloader/config/adapter.py +190 -54
- novel_downloader/config/loader.py +2 -3
- novel_downloader/core/__init__.py +13 -13
- novel_downloader/core/downloaders/__init__.py +10 -11
- novel_downloader/core/downloaders/base.py +152 -26
- novel_downloader/core/downloaders/biquge.py +5 -1
- novel_downloader/core/downloaders/common.py +157 -378
- novel_downloader/core/downloaders/esjzone.py +5 -1
- novel_downloader/core/downloaders/linovelib.py +5 -1
- novel_downloader/core/downloaders/qianbi.py +291 -4
- novel_downloader/core/downloaders/qidian.py +199 -285
- novel_downloader/core/downloaders/registry.py +67 -0
- novel_downloader/core/downloaders/sfacg.py +5 -1
- novel_downloader/core/downloaders/yamibo.py +5 -1
- novel_downloader/core/exporters/__init__.py +10 -11
- novel_downloader/core/exporters/base.py +87 -7
- novel_downloader/core/exporters/biquge.py +5 -8
- novel_downloader/core/exporters/common/__init__.py +2 -2
- novel_downloader/core/exporters/common/epub.py +82 -166
- novel_downloader/core/exporters/common/main_exporter.py +0 -60
- novel_downloader/core/exporters/common/txt.py +82 -83
- novel_downloader/core/exporters/epub_util.py +157 -1330
- novel_downloader/core/exporters/esjzone.py +5 -8
- novel_downloader/core/exporters/linovelib/__init__.py +2 -2
- novel_downloader/core/exporters/linovelib/epub.py +157 -212
- novel_downloader/core/exporters/linovelib/main_exporter.py +2 -59
- novel_downloader/core/exporters/linovelib/txt.py +67 -63
- novel_downloader/core/exporters/qianbi.py +5 -8
- novel_downloader/core/exporters/qidian.py +14 -4
- novel_downloader/core/exporters/registry.py +53 -0
- novel_downloader/core/exporters/sfacg.py +5 -8
- novel_downloader/core/exporters/txt_util.py +67 -0
- novel_downloader/core/exporters/yamibo.py +5 -8
- novel_downloader/core/fetchers/__init__.py +19 -24
- novel_downloader/core/fetchers/base/__init__.py +3 -3
- novel_downloader/core/fetchers/base/browser.py +23 -4
- novel_downloader/core/fetchers/base/session.py +30 -5
- novel_downloader/core/fetchers/biquge/__init__.py +3 -3
- novel_downloader/core/fetchers/biquge/browser.py +5 -0
- novel_downloader/core/fetchers/biquge/session.py +6 -1
- novel_downloader/core/fetchers/esjzone/__init__.py +3 -3
- novel_downloader/core/fetchers/esjzone/browser.py +5 -0
- novel_downloader/core/fetchers/esjzone/session.py +6 -1
- novel_downloader/core/fetchers/linovelib/__init__.py +3 -3
- novel_downloader/core/fetchers/linovelib/browser.py +6 -1
- novel_downloader/core/fetchers/linovelib/session.py +6 -1
- novel_downloader/core/fetchers/qianbi/__init__.py +3 -3
- novel_downloader/core/fetchers/qianbi/browser.py +5 -0
- novel_downloader/core/fetchers/qianbi/session.py +5 -0
- novel_downloader/core/fetchers/qidian/__init__.py +3 -3
- novel_downloader/core/fetchers/qidian/browser.py +12 -4
- novel_downloader/core/fetchers/qidian/session.py +11 -3
- novel_downloader/core/fetchers/registry.py +71 -0
- novel_downloader/core/fetchers/sfacg/__init__.py +3 -3
- novel_downloader/core/fetchers/sfacg/browser.py +5 -0
- novel_downloader/core/fetchers/sfacg/session.py +5 -0
- novel_downloader/core/fetchers/yamibo/__init__.py +3 -3
- novel_downloader/core/fetchers/yamibo/browser.py +5 -0
- novel_downloader/core/fetchers/yamibo/session.py +6 -1
- novel_downloader/core/interfaces/__init__.py +7 -5
- novel_downloader/core/interfaces/searcher.py +18 -0
- novel_downloader/core/parsers/__init__.py +10 -11
- novel_downloader/core/parsers/{biquge/main_parser.py → biquge.py} +7 -2
- novel_downloader/core/parsers/{esjzone/main_parser.py → esjzone.py} +7 -2
- novel_downloader/core/parsers/{linovelib/main_parser.py → linovelib.py} +7 -2
- novel_downloader/core/parsers/{qianbi/main_parser.py → qianbi.py} +7 -2
- novel_downloader/core/parsers/qidian/__init__.py +2 -2
- novel_downloader/core/parsers/qidian/chapter_encrypted.py +23 -21
- novel_downloader/core/parsers/qidian/chapter_normal.py +1 -1
- novel_downloader/core/parsers/qidian/main_parser.py +10 -21
- novel_downloader/core/parsers/qidian/utils/__init__.py +11 -11
- novel_downloader/core/parsers/qidian/utils/decryptor_fetcher.py +5 -6
- novel_downloader/core/parsers/qidian/utils/node_decryptor.py +2 -2
- novel_downloader/core/parsers/registry.py +68 -0
- novel_downloader/core/parsers/{sfacg/main_parser.py → sfacg.py} +7 -2
- novel_downloader/core/parsers/{yamibo/main_parser.py → yamibo.py} +7 -2
- novel_downloader/core/searchers/__init__.py +20 -0
- novel_downloader/core/searchers/base.py +92 -0
- novel_downloader/core/searchers/biquge.py +83 -0
- novel_downloader/core/searchers/esjzone.py +84 -0
- novel_downloader/core/searchers/qianbi.py +131 -0
- novel_downloader/core/searchers/qidian.py +87 -0
- novel_downloader/core/searchers/registry.py +63 -0
- novel_downloader/locales/en.json +12 -4
- novel_downloader/locales/zh.json +12 -4
- novel_downloader/models/__init__.py +4 -30
- novel_downloader/models/config.py +12 -6
- novel_downloader/models/search.py +16 -0
- novel_downloader/models/types.py +0 -2
- novel_downloader/resources/config/settings.toml +31 -4
- novel_downloader/resources/css_styles/intro.css +83 -0
- novel_downloader/resources/css_styles/main.css +30 -89
- novel_downloader/utils/__init__.py +52 -0
- novel_downloader/utils/chapter_storage.py +244 -224
- novel_downloader/utils/constants.py +1 -21
- novel_downloader/utils/epub/__init__.py +34 -0
- novel_downloader/utils/epub/builder.py +377 -0
- novel_downloader/utils/epub/constants.py +77 -0
- novel_downloader/utils/epub/documents.py +403 -0
- novel_downloader/utils/epub/models.py +134 -0
- novel_downloader/utils/epub/utils.py +212 -0
- novel_downloader/utils/file_utils/__init__.py +10 -14
- novel_downloader/utils/file_utils/io.py +20 -51
- novel_downloader/utils/file_utils/normalize.py +2 -2
- novel_downloader/utils/file_utils/sanitize.py +2 -3
- novel_downloader/utils/fontocr/__init__.py +5 -5
- novel_downloader/utils/{hash_store.py → fontocr/hash_store.py} +4 -3
- novel_downloader/utils/{hash_utils.py → fontocr/hash_utils.py} +2 -2
- novel_downloader/utils/fontocr/ocr_v1.py +13 -1
- novel_downloader/utils/fontocr/ocr_v2.py +13 -1
- novel_downloader/utils/fontocr/ocr_v3.py +744 -0
- novel_downloader/utils/i18n.py +2 -0
- novel_downloader/utils/logger.py +2 -0
- novel_downloader/utils/network.py +110 -251
- novel_downloader/utils/state.py +1 -0
- novel_downloader/utils/text_utils/__init__.py +18 -17
- novel_downloader/utils/text_utils/diff_display.py +4 -5
- novel_downloader/utils/text_utils/numeric_conversion.py +253 -0
- novel_downloader/utils/text_utils/text_cleaner.py +179 -0
- novel_downloader/utils/text_utils/truncate_utils.py +62 -0
- novel_downloader/utils/time_utils/__init__.py +3 -3
- novel_downloader/utils/time_utils/datetime_utils.py +4 -5
- novel_downloader/utils/time_utils/sleep_utils.py +2 -3
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/METADATA +2 -2
- novel_downloader-1.5.0.dist-info/RECORD +164 -0
- novel_downloader/config/site_rules.py +0 -94
- novel_downloader/core/factory/__init__.py +0 -20
- novel_downloader/core/factory/downloader.py +0 -73
- novel_downloader/core/factory/exporter.py +0 -58
- novel_downloader/core/factory/fetcher.py +0 -96
- novel_downloader/core/factory/parser.py +0 -86
- novel_downloader/core/fetchers/common/__init__.py +0 -14
- novel_downloader/core/fetchers/common/browser.py +0 -79
- novel_downloader/core/fetchers/common/session.py +0 -79
- novel_downloader/core/parsers/biquge/__init__.py +0 -10
- novel_downloader/core/parsers/common/__init__.py +0 -13
- novel_downloader/core/parsers/common/helper.py +0 -323
- novel_downloader/core/parsers/common/main_parser.py +0 -106
- novel_downloader/core/parsers/esjzone/__init__.py +0 -10
- novel_downloader/core/parsers/linovelib/__init__.py +0 -10
- novel_downloader/core/parsers/qianbi/__init__.py +0 -10
- novel_downloader/core/parsers/sfacg/__init__.py +0 -10
- novel_downloader/core/parsers/yamibo/__init__.py +0 -10
- novel_downloader/models/browser.py +0 -21
- novel_downloader/models/site_rules.py +0 -99
- novel_downloader/models/tasks.py +0 -33
- novel_downloader/resources/css_styles/volume-intro.css +0 -56
- novel_downloader/resources/json/replace_word_map.json +0 -4
- novel_downloader/resources/text/blacklist.txt +0 -22
- novel_downloader/utils/text_utils/chapter_formatting.py +0 -46
- novel_downloader/utils/text_utils/font_mapping.py +0 -28
- novel_downloader/utils/text_utils/text_cleaning.py +0 -107
- novel_downloader-1.4.4.dist-info/RECORD +0 -165
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/WHEEL +0 -0
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/entry_points.txt +0 -0
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/licenses/LICENSE +0 -0
- {novel_downloader-1.4.4.dist-info → novel_downloader-1.5.0.dist-info}/top_level.txt +0 -0
@@ -3,23 +3,19 @@
|
|
3
3
|
novel_downloader.core.exporters.common.txt
|
4
4
|
------------------------------------------
|
5
5
|
|
6
|
-
|
7
|
-
|
8
|
-
This module defines `common_save_as_txt` function, which assembles and formats
|
9
|
-
a novel based on metadata and chapter files found in the raw data directory.
|
10
|
-
It is intended to be used by `CommonExporter` as part of the save/export process.
|
6
|
+
Defines `common_export_as_txt` to assemble and export a novel
|
7
|
+
into a single `.txt` file. Intended for use by `CommonExporter`.
|
11
8
|
"""
|
12
9
|
|
13
10
|
from __future__ import annotations
|
14
11
|
|
15
|
-
import json
|
16
12
|
from typing import TYPE_CHECKING
|
17
13
|
|
18
|
-
from novel_downloader.
|
19
|
-
|
20
|
-
|
21
|
-
format_chapter,
|
14
|
+
from novel_downloader.core.exporters.txt_util import (
|
15
|
+
build_txt_chapter,
|
16
|
+
build_txt_header,
|
22
17
|
)
|
18
|
+
from novel_downloader.utils import get_cleaner, save_as_txt
|
23
19
|
|
24
20
|
if TYPE_CHECKING:
|
25
21
|
from .main_exporter import CommonExporter
|
@@ -30,102 +26,105 @@ def common_export_as_txt(
|
|
30
26
|
book_id: str,
|
31
27
|
) -> None:
|
32
28
|
"""
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
2.
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
29
|
+
Export a novel as a single text file by merging all chapter data.
|
30
|
+
|
31
|
+
Steps:
|
32
|
+
1. Load book metadata (title, author, summary, word count, update time,
|
33
|
+
volumes, and chapters).
|
34
|
+
2. For each volume:
|
35
|
+
a. Append the volume title.
|
36
|
+
b. Batch-fetch all chapters in that volume to minimize SQLite calls.
|
37
|
+
c. Append each chapter's title, content, and optional author note.
|
38
|
+
3. Build a header with book metadata and the latest chapter title.
|
39
|
+
4. Concatenate header and all chapter contents.
|
40
|
+
5. Save the resulting .txt file to the output directory
|
41
|
+
(e.g., '{book_name}.txt').
|
42
|
+
|
43
|
+
:param exporter: The CommonExporter instance managing paths and config.
|
44
|
+
:param book_id: Identifier of the novel (subdirectory under raw data).
|
43
45
|
"""
|
44
46
|
TAG = "[Exporter]"
|
45
47
|
# --- Paths & options ---
|
46
|
-
raw_base = exporter._raw_data_dir / book_id
|
47
48
|
out_dir = exporter.output_dir
|
48
49
|
out_dir.mkdir(parents=True, exist_ok=True)
|
50
|
+
cleaner = get_cleaner(
|
51
|
+
enabled=exporter._config.clean_text,
|
52
|
+
config=exporter._config.cleaner_cfg,
|
53
|
+
)
|
49
54
|
|
50
55
|
# --- Load book_info.json ---
|
51
|
-
|
52
|
-
|
53
|
-
info_text = info_path.read_text(encoding="utf-8")
|
54
|
-
book_info = json.loads(info_text)
|
55
|
-
except Exception as e:
|
56
|
-
exporter.logger.error("%s Failed to load %s: %s", TAG, info_path, e)
|
56
|
+
book_info = exporter._load_book_info(book_id)
|
57
|
+
if not book_info:
|
57
58
|
return
|
58
59
|
|
59
60
|
# --- Compile chapters ---
|
60
61
|
parts: list[str] = []
|
61
|
-
|
62
|
-
volumes = book_info.get("volumes", [])
|
62
|
+
latest_chapter_title: str = ""
|
63
63
|
|
64
|
-
for vol in volumes:
|
65
|
-
vol_name = vol.get("volume_name", "")
|
66
|
-
vol_name = clean_chapter_title(vol_name)
|
64
|
+
for vol in book_info.get("volumes", []):
|
65
|
+
vol_name = cleaner.clean_title(vol.get("volume_name", ""))
|
67
66
|
if vol_name:
|
68
|
-
|
69
|
-
parts.append(
|
67
|
+
# e.g. "\n\n====== 第三卷 ======\n\n"
|
68
|
+
parts.append(f"\n\n{'=' * 6} {vol_name} {'=' * 6}\n\n")
|
70
69
|
exporter.logger.info("%s Processing volume: %s", TAG, vol_name)
|
71
|
-
|
72
|
-
|
73
|
-
|
70
|
+
|
71
|
+
# Batch-fetch chapters for this volume
|
72
|
+
chap_ids = [
|
73
|
+
chap.get("chapterId")
|
74
|
+
for chap in vol.get("chapters", [])
|
75
|
+
if chap.get("chapterId")
|
76
|
+
]
|
77
|
+
chap_map = exporter._get_chapters(book_id, chap_ids)
|
78
|
+
|
79
|
+
for chap_meta in vol.get("chapters", []):
|
80
|
+
chap_id = chap_meta.get("chapterId")
|
74
81
|
if not chap_id:
|
75
|
-
exporter.logger.warning(
|
82
|
+
exporter.logger.warning(
|
83
|
+
"%s Missing chapterId, skipping: %s", TAG, chap_meta
|
84
|
+
)
|
76
85
|
continue
|
77
86
|
|
78
|
-
|
79
|
-
|
87
|
+
chap_title = cleaner.clean_title(chap_meta.get("title", ""))
|
88
|
+
data = chap_map.get(chap_id)
|
89
|
+
if not data:
|
80
90
|
exporter.logger.info(
|
81
|
-
"%s Missing chapter
|
91
|
+
"%s Missing chapter: %s (%s), skipping.",
|
82
92
|
TAG,
|
83
93
|
chap_title,
|
84
94
|
chap_id,
|
85
95
|
)
|
86
96
|
continue
|
87
97
|
|
88
|
-
# Extract
|
89
|
-
title =
|
90
|
-
content =
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
parts.append(format_chapter(clean_title, content, author_say))
|
95
|
-
latest_chapter = clean_title
|
96
|
-
|
97
|
-
# --- Build header ---
|
98
|
-
name = book_info.get("book_name")
|
99
|
-
author = book_info.get("author")
|
100
|
-
words = book_info.get("word_count")
|
101
|
-
updated = book_info.get("update_time")
|
102
|
-
summary = book_info.get("summary")
|
103
|
-
|
104
|
-
header_lines = []
|
105
|
-
|
106
|
-
if name:
|
107
|
-
header_lines.append(f"书名: {name}")
|
108
|
-
|
109
|
-
if author:
|
110
|
-
header_lines.append(f"作者: {author}")
|
98
|
+
# Extract and clean fields
|
99
|
+
title = cleaner.clean_title(data.get("title", chap_title))
|
100
|
+
content = cleaner.clean_content(data.get("content", ""))
|
101
|
+
extra = data.get("extra", {})
|
102
|
+
author_note = cleaner.clean_content(extra.get("author_say", ""))
|
111
103
|
|
112
|
-
|
113
|
-
|
104
|
+
extras = {"作者说": author_note} if author_note else {}
|
105
|
+
parts.append(
|
106
|
+
build_txt_chapter(title=title, paragraphs=content, extras=extras)
|
107
|
+
)
|
114
108
|
|
115
|
-
|
116
|
-
header_lines.append(f"更新日期: {updated}")
|
109
|
+
latest_chapter_title = title
|
117
110
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
|
111
|
+
# --- Build header ---
|
112
|
+
name = book_info.get("book_name") or ""
|
113
|
+
author = book_info.get("author") or ""
|
114
|
+
words = book_info.get("word_count") or ""
|
115
|
+
updated = book_info.get("update_time") or ""
|
116
|
+
summary = book_info.get("summary") or ""
|
117
|
+
|
118
|
+
header_fields = [
|
119
|
+
("书名", name),
|
120
|
+
("作者", author),
|
121
|
+
("总字数", words),
|
122
|
+
("更新日期", updated),
|
123
|
+
("原文截至", latest_chapter_title),
|
124
|
+
("内容简介", summary),
|
125
|
+
]
|
126
|
+
|
127
|
+
header = build_txt_header(header_fields)
|
129
128
|
|
130
129
|
final_text = header + "\n\n" + "\n\n".join(parts).strip()
|
131
130
|
|
@@ -134,9 +133,9 @@ def common_export_as_txt(
|
|
134
133
|
out_path = out_dir / out_name
|
135
134
|
|
136
135
|
# --- Save final text ---
|
137
|
-
|
138
|
-
|
136
|
+
result = save_as_txt(content=final_text, filepath=out_path)
|
137
|
+
if result:
|
139
138
|
exporter.logger.info("%s Novel saved to: %s", TAG, out_path)
|
140
|
-
|
141
|
-
exporter.logger.error("%s Failed to
|
139
|
+
else:
|
140
|
+
exporter.logger.error("%s Failed to write novel to %s", TAG, out_path)
|
142
141
|
return
|