reader-shell 1.0.3 → 1.0.4

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.
@@ -3,7 +3,10 @@
3
3
  "allow": [
4
4
  "Bash(git add:*)",
5
5
  "Bash(git commit:*)",
6
- "Bash(git push:*)"
6
+ "Bash(git push:*)",
7
+ "Bash(node:*)",
8
+ "Bash(timeout:*)",
9
+ "Read(//Users/xuejichang/Documents/book/**)"
7
10
  ],
8
11
  "deny": [],
9
12
  "ask": []
package/cli.js CHANGED
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  import { startReader } from './src/ui.js';
3
3
  import path from 'path';
4
- const epubPath = process.argv[2];
5
- if (!epubPath) {
6
- console.error('用法: reader-shell <epub文件路径>');
4
+ const filePath = process.argv[2];
5
+ if (!filePath) {
6
+ console.error('用法: reader-shell <文件路径>');
7
7
  process.exit(1);
8
8
  }
9
- startReader(path.resolve(epubPath));
9
+ startReader(path.resolve(filePath));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reader-shell",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "main": "cli.js",
5
5
  "type": "module",
6
6
  "scripts": {
@@ -13,7 +13,8 @@
13
13
  "dependencies": {
14
14
  "blessed": "^0.1.81",
15
15
  "chalk": "^5.4.1",
16
- "epub": "^1.3.0"
16
+ "epub": "^1.3.0",
17
+ "iconv-lite": "^0.7.0"
17
18
  },
18
19
  "bin": {
19
20
  "reader-shell": "./cli.js"
@@ -0,0 +1,164 @@
1
+ import fs from 'fs';
2
+ import path from 'path';
3
+ import iconv from 'iconv-lite';
4
+
5
+ /**
6
+ * 检测文件编码
7
+ * @param {Buffer} buffer 文件内容
8
+ * @returns {string} 编码名称
9
+ */
10
+ function detectEncoding(buffer) {
11
+ // 检查BOM标记
12
+ if (buffer.length >= 3 && buffer[0] === 0xEF && buffer[1] === 0xBB && buffer[2] === 0xBF) {
13
+ return 'utf8';
14
+ }
15
+
16
+ if (buffer.length >= 2 && buffer[0] === 0xFF && buffer[1] === 0xFE) {
17
+ return 'utf16le';
18
+ }
19
+
20
+ if (buffer.length >= 2 && buffer[0] === 0xFE && buffer[1] === 0xFF) {
21
+ return 'utf16be';
22
+ }
23
+
24
+ // 简单的编码检测:尝试UTF-8解码
25
+ try {
26
+ const utf8Content = buffer.toString('utf8');
27
+ // 检查是否包含UTF-8无效字符
28
+ if (!utf8Content.includes('�')) {
29
+ return 'utf8';
30
+ }
31
+ } catch (e) {
32
+ // UTF-8解码失败,尝试其他编码
33
+ }
34
+
35
+ // 尝试GBK/GB2312编码(中文Windows常用编码)
36
+ try {
37
+ const gbkContent = iconv.decode(buffer, 'gbk');
38
+ // 检查解码是否成功(没有太多乱码字符)
39
+ if (!gbkContent.includes('�') || gbkContent.indexOf('�') / gbkContent.length < 0.1) {
40
+ return 'gbk';
41
+ }
42
+ } catch (e) {
43
+ // GBK解码失败
44
+ }
45
+
46
+ // 默认使用UTF-8
47
+ return 'utf8';
48
+ }
49
+
50
+ /**
51
+ * 以正确的编码读取文件
52
+ * @param {string} filePath 文件路径
53
+ * @returns {Promise<string>} 文件内容
54
+ */
55
+ function readFileWithCorrectEncoding(filePath) {
56
+ return new Promise((resolve, reject) => {
57
+ fs.readFile(filePath, (err, buffer) => {
58
+ if (err) {
59
+ reject(err);
60
+ return;
61
+ }
62
+
63
+ const encoding = detectEncoding(buffer);
64
+ let content;
65
+
66
+ try {
67
+ if (encoding === 'utf8') {
68
+ content = buffer.toString('utf8');
69
+ } else {
70
+ content = iconv.decode(buffer, encoding);
71
+ }
72
+ resolve(content);
73
+ } catch (e) {
74
+ // 如果指定编码解码失败,尝试UTF-8
75
+ try {
76
+ content = buffer.toString('utf8');
77
+ resolve(content);
78
+ } catch (utf8Err) {
79
+ reject(new Error(`无法解码文件,尝试的编码: ${encoding}`));
80
+ }
81
+ }
82
+ });
83
+ });
84
+ }
85
+
86
+ /**
87
+ * 解析txt文件,返回章节文本数组
88
+ * @param {string} txtPath txt文件路径
89
+ * @returns {Promise<string[]>} 章节文本数组
90
+ */
91
+ function parseTxt(txtPath) {
92
+ return new Promise((resolve, reject) => {
93
+ readFileWithCorrectEncoding(txtPath).then(data => {
94
+ let content = data;
95
+ let chapters = [];
96
+
97
+ // 先尝试按明确的章节标题分割
98
+ const lines = content.split('\n');
99
+ let currentChapter = '';
100
+ let chapterHeaders = [];
101
+
102
+ // 寻找所有章节标题的行号
103
+ lines.forEach((line, index) => {
104
+ if (/^第[一二三四五六七八九十百千万0-9]+[章节]/.test(line.trim())) {
105
+ chapterHeaders.push(index);
106
+ }
107
+ });
108
+
109
+ if (chapterHeaders.length > 1) {
110
+ // 按找到的章节标题分割
111
+ for (let i = 0; i < chapterHeaders.length; i++) {
112
+ const startLine = chapterHeaders[i];
113
+ const endLine = i < chapterHeaders.length - 1 ? chapterHeaders[i + 1] : lines.length;
114
+
115
+ const chapterLines = lines.slice(startLine, endLine);
116
+ const chapterContent = chapterLines.join('\n').trim();
117
+
118
+ if (chapterContent.length > 0) {
119
+ chapters.push(chapterContent);
120
+ }
121
+ }
122
+ }
123
+
124
+ // 如果没有找到章节分割,按段落分割
125
+ if (chapters.length === 0) {
126
+ // 按多个空行分割
127
+ const sections = content.split(/\n\s*\n\s*\n/);
128
+ chapters = sections.filter(section => section.trim().length > 0);
129
+
130
+ // 如果分割后章节太少,按单个空行分割,然后每10个段落合并
131
+ if (chapters.length <= 1) {
132
+ const paragraphs = content.split(/\n\s*\n/);
133
+ chapters = [];
134
+ const chapterSize = 10;
135
+ for (let i = 0; i < paragraphs.length; i += chapterSize) {
136
+ const chapter = paragraphs.slice(i, i + chapterSize).join('\n\n');
137
+ if (chapter.trim().length > 0) {
138
+ chapters.push(chapter);
139
+ }
140
+ }
141
+ }
142
+ }
143
+
144
+ // 如果还是没有合适的章节,整个文件作为一章
145
+ if (chapters.length === 0) {
146
+ chapters = [content];
147
+ }
148
+
149
+ // 清理每章内容
150
+ chapters = chapters.map(chapter => {
151
+ return chapter
152
+ .replace(/\r\n/g, '\n') // 统一换行符
153
+ .replace(/\n{3,}/g, '\n\n') // 去除多余的空行
154
+ .trim();
155
+ }).filter(chapter => chapter.length > 0);
156
+
157
+ resolve(chapters);
158
+ }).catch(err => {
159
+ reject(err);
160
+ });
161
+ });
162
+ }
163
+
164
+ export { parseTxt, detectEncoding, readFileWithCorrectEncoding };
package/src/ui.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import blessed from "blessed";
2
2
  import chalk from "chalk";
3
3
  import { parseEpub } from "./epubReader.js";
4
+ import { parseTxt } from "./txtReader.js";
4
5
  import path from "path";
5
6
  import { getBookProgress, setBookProgress } from "./progressStore.js";
6
7
  import { config } from "./config.js";
@@ -16,9 +17,19 @@ const colorFns = {
16
17
  yellow: chalk.yellow,
17
18
  };
18
19
 
19
- async function startReader(epubPath) {
20
- const chapters = await parseEpub(epubPath);
21
- const progress = getBookProgress(epubPath);
20
+ async function startReader(filePath) {
21
+ const ext = path.extname(filePath).toLowerCase();
22
+ let chapters;
23
+
24
+ if (ext === '.epub') {
25
+ chapters = await parseEpub(filePath);
26
+ } else if (ext === '.txt') {
27
+ chapters = await parseTxt(filePath);
28
+ } else {
29
+ throw new Error(`不支持的文件格式: ${ext}`);
30
+ }
31
+
32
+ const progress = getBookProgress(filePath);
22
33
  let currentChapter = (progress && progress.chapter) || 0;
23
34
  let currentScroll = (progress && progress.scroll) || 0;
24
35
 
@@ -34,7 +45,7 @@ async function startReader(epubPath) {
34
45
  top: 0,
35
46
  left: 0,
36
47
  width: "100%",
37
- height: "80%",
48
+ height: "70%",
38
49
  border: "none",
39
50
  content: config.bossContent
40
51
  });
@@ -95,7 +106,7 @@ async function startReader(epubPath) {
95
106
  }
96
107
 
97
108
  function saveProgress() {
98
- setBookProgress(epubPath, { chapter: currentChapter, scroll: currentScroll });
109
+ setBookProgress(filePath, { chapter: currentChapter, scroll: currentScroll });
99
110
  }
100
111
 
101
112
  function changeChapter(chapter) {
@@ -122,7 +133,7 @@ async function startReader(epubPath) {
122
133
  changeChapter(-1)
123
134
  return
124
135
  }
125
- render();
136
+ render(true);
126
137
  });
127
138
  screen.key(["down", "d"], () => {
128
139
  currentScroll++;
@@ -130,7 +141,7 @@ async function startReader(epubPath) {
130
141
  changeChapter(1)
131
142
  return
132
143
  }
133
- render();
144
+ render(true);
134
145
  });
135
146
  screen.key(["a"], () => {
136
147
  // 老板键
@@ -158,11 +169,11 @@ async function startReader(epubPath) {
158
169
  }
159
170
 
160
171
  // 允许命令行直接运行
161
- const epubPath = process.argv[2];
162
- if (!epubPath) {
163
- console.error("用法: node src/ui.js <epub文件路径>");
172
+ const filePath = process.argv[2];
173
+ if (!filePath) {
174
+ console.error("用法: node src/ui.js <文件路径>");
164
175
  process.exit(1);
165
176
  }
166
- startReader(path.resolve(epubPath));
177
+ startReader(path.resolve(filePath));
167
178
 
168
179
  export { startReader };