readshell 0.3.1 → 1.0.0
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.
- package/LICENSE +17 -13
- package/README.md +139 -90
- package/dist/index.js +1235 -216
- package/dist/index.js.map +1 -1
- package/package.json +10 -5
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cli/parser.ts","../src/services/BookService.ts","../src/db/client.ts","../src/config/paths.ts","../src/utils/logger.ts","../src/db/models/Book.ts","../src/db/models/Chapter.ts","../src/db/models/Recent.ts","../src/db/models/Progress.ts","../src/parsers/TxtParser.ts","../src/parsers/EpubParser.ts","../src/parsers/index.ts","../src/utils/hash.ts","../src/locales/zh.ts","../src/locales/en.ts","../src/config/AppConfig.ts","../src/locales/index.ts","../src/cli/commands/import.ts","../src/services/ProgressService.ts","../src/ui/renderApp.ts","../src/ui/App.tsx","../src/ui/pages/ResumePage.tsx","../src/ui/pages/LibraryPage.tsx","../src/ui/pages/ReaderPage.tsx","../src/ui/components/TextRenderer.tsx","../src/ui/components/StatusBar.tsx","../src/ui/components/ChapterNav.tsx","../src/ui/hooks/useReader.ts","../src/ui/hooks/useKeyboard.ts","../src/utils/stringWidth.ts","../src/utils/paginate.ts","../src/services/ChapterService.ts","../src/services/RecentService.ts","../src/db/models/Bookmark.ts","../src/services/BookmarkService.ts","../src/utils/bossKey.ts","../src/utils/time.ts","../src/cli/commands/resume.ts","../src/cli/commands/open.ts","../src/cli/commands/library.ts","../src/cli/commands/remove.ts","../src/cli/commands/lang.ts","../src/cli/commands/update.ts","../src/db/migrate.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI 参数解析器\n * 使用 yargs 解析子命令\n */\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { importCommand } from './commands/import.js';\nimport { resumeCommand } from './commands/resume.js';\nimport { openCommand } from './commands/open.js';\nimport { libraryCommand } from './commands/library.js';\nimport { removeCommand } from './commands/remove.js';\nimport { langCommand } from './commands/lang.js';\nimport { updateCommand } from './commands/update.js';\n\nexport function createParser() {\n const version = typeof APP_VERSION !== 'undefined' ? APP_VERSION : 'dev';\n\n return yargs(hideBin(process.argv))\n .scriptName('novel')\n .usage('$0 <command> [options]')\n .command(importCommand)\n .command(resumeCommand)\n .command(openCommand)\n .command(libraryCommand)\n .command(removeCommand)\n .command(langCommand)\n .command(updateCommand)\n .demandCommand(1, '请指定一个命令。使用 --help 查看可用命令。')\n .strict()\n .alias('h', 'help')\n .alias('v', 'version')\n .version(version)\n .epilogue('ReadShell — 终端内低打断轻阅读工具');\n}\n","/**\n * 书籍业务逻辑\n * 书籍 CRUD、导入、去重\n */\n\nimport { resolve } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { nanoid } from 'nanoid';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ChapterModel } from '../db/models/Chapter.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { ProgressModel } from '../db/models/Progress.js';\nimport { parseFile } from '../parsers/index.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\n\nexport class BookService {\n private bookModel = new BookModel();\n private chapterModel = new ChapterModel();\n private recentModel = new RecentModel();\n private progressModel = new ProgressModel();\n\n /**\n * 导入书籍文件\n */\n async importBook(filePath: string): Promise<BookRecord> {\n const absPath = resolve(filePath);\n\n // 检查文件存在\n if (!existsSync(absPath)) {\n throw new Error(`文件不存在: ${absPath}`);\n }\n\n // 检测文件格式\n const format = this.detectFormat(absPath);\n if (!format) {\n throw new Error('不支持的文件格式。目前支持: .txt, .epub');\n }\n\n // 计算文件 hash 用于去重\n const fileHash = await computeFileHash(absPath);\n const existing = this.bookModel.findByHash(fileHash);\n if (existing) {\n logger.debug(`文件已存在: ${existing.title} (${existing.id})`);\n return existing;\n }\n\n // 解析文件\n const parsed = await parseFile(absPath, format);\n\n // 获取文件大小\n const stats = statSync(absPath);\n\n // 创建书籍记录\n const book: BookRecord = {\n id: nanoid(),\n title: parsed.title,\n author: parsed.author || null,\n file_path: absPath,\n format,\n file_hash: fileHash,\n file_size: stats.size,\n created_at: Date.now(),\n };\n\n this.bookModel.insert(book);\n\n // 保存章节索引\n if (parsed.chapters.length > 0) {\n this.chapterModel.insertMany(\n parsed.chapters.map((ch, idx) => ({\n book_id: book.id,\n chapter_no: idx,\n title: ch.title,\n byte_offset: ch.byteOffset,\n })),\n );\n }\n\n logger.debug(`导入成功: ${book.title}`);\n return book;\n }\n\n /**\n * 查找书籍(ID 或模糊匹配书名)\n */\n findBook(target: string): BookRecord | undefined {\n // 先尝试精确 ID 匹配\n const byId = this.bookModel.findById(target);\n if (byId) return byId;\n\n // 再尝试书名模糊匹配\n const results = this.bookModel.searchByTitle(target);\n return results[0];\n }\n\n /**\n * 搜索书籍\n */\n searchBooks(keyword: string): BookRecord[] {\n return this.bookModel.searchByTitle(keyword);\n }\n\n /**\n * 获取所有书籍\n */\n getAllBooks(): BookRecord[] {\n return this.bookModel.findAll();\n }\n\n /**\n * 删除书籍及相关数据\n */\n deleteBook(id: string): void {\n this.chapterModel.deleteByBookId(id);\n this.progressModel.delete(id);\n this.recentModel.delete(id);\n this.bookModel.delete(id);\n }\n\n /**\n * 检测文件格式\n */\n private detectFormat(filePath: string): 'txt' | 'epub' | null {\n const ext = filePath.toLowerCase().split('.').pop();\n if (ext === 'txt') return 'txt';\n if (ext === 'epub') return 'epub';\n return null;\n }\n}\n","/**\n * SQLite 数据库连接初始化\n * 使用 better-sqlite3 同步 API\n */\n\nimport Database from 'better-sqlite3';\nimport { getDbPath } from '../config/paths.js';\nimport { logger } from '../utils/logger.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * 获取数据库连接实例(单例模式)\n */\nexport function getDb(): Database.Database {\n if (!db) {\n const dbPath = getDbPath();\n logger.debug(`数据库路径: ${dbPath}`);\n\n db = new Database(dbPath);\n\n // 启用 WAL 模式以提升写入性能\n db.pragma('journal_mode = WAL');\n // 启用外键约束\n db.pragma('foreign_keys = ON');\n }\n\n return db;\n}\n\n/**\n * 关闭数据库连接\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n logger.debug('数据库连接已关闭');\n }\n}\n\n// 进程退出时自动关闭数据库\nprocess.on('exit', () => closeDb());\nprocess.on('SIGINT', () => {\n closeDb();\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n closeDb();\n process.exit(0);\n});\n","/**\n * 跨平台路径管理\n * 数据库、配置文件位置\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { mkdirSync, existsSync } from 'fs';\n\n/**\n * 获取应用数据目录\n * ~/.config/readshell/ (macOS/Linux)\n */\nexport function getAppDataDir(): string {\n const platform = process.platform;\n let configDir: string;\n\n if (platform === 'darwin') {\n configDir = join(homedir(), 'Library', 'Application Support', 'readshell');\n } else if (platform === 'win32') {\n configDir = join(process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'readshell');\n } else {\n // Linux / 其他\n configDir = join(process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config'), 'readshell');\n }\n\n // 确保目录存在\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n return configDir;\n}\n\n/**\n * 获取数据库文件路径\n */\nexport function getDbPath(): string {\n return join(getAppDataDir(), 'readshell.db');\n}\n\n/**\n * 获取配置文件路径\n */\nexport function getConfigPath(): string {\n return join(getAppDataDir(), 'config.json');\n}\n","/**\n * 调试日志\n * 仅 DEBUG=1 时输出\n */\n\nconst isDebug = process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true';\n\nexport const logger = {\n debug: (...args: unknown[]): void => {\n if (isDebug) {\n console.error('[DEBUG]', ...args);\n }\n },\n\n info: (...args: unknown[]): void => {\n console.error('[INFO]', ...args);\n },\n\n warn: (...args: unknown[]): void => {\n console.error('[WARN]', ...args);\n },\n\n error: (...args: unknown[]): void => {\n console.error('[ERROR]', ...args);\n },\n};\n","/**\n * books 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookRecord {\n id: string;\n title: string;\n author: string | null;\n file_path: string;\n format: 'txt' | 'epub';\n file_hash: string;\n file_size: number | null;\n created_at: number;\n}\n\nexport class BookModel {\n /**\n * 插入新书\n */\n insert(book: BookRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO books (id, title, author, file_path, format, file_hash, file_size, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(book.id, book.title, book.author, book.file_path, book.format, book.file_hash, book.file_size, book.created_at);\n }\n\n /**\n * 通过 ID 获取书籍\n */\n findById(id: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE id = ?').get(id) as BookRecord | undefined;\n }\n\n /**\n * 通过文件 hash 查找(去重用)\n */\n findByHash(hash: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE file_hash = ?').get(hash) as BookRecord | undefined;\n }\n\n /**\n * 模糊搜索书名\n */\n searchByTitle(keyword: string): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE title LIKE ? ORDER BY created_at DESC').all(`%${keyword}%`) as BookRecord[];\n }\n\n /**\n * 获取所有书籍\n */\n findAll(): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books ORDER BY created_at DESC').all() as BookRecord[];\n }\n\n /**\n * 删除书籍\n */\n delete(id: string): void {\n const db = getDb();\n db.prepare('DELETE FROM books WHERE id = ?').run(id);\n }\n}\n","/**\n * chapter_index 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ChapterRecord {\n id?: number;\n book_id: string;\n chapter_no: number;\n title: string | null;\n byte_offset: number;\n}\n\nexport class ChapterModel {\n /**\n * 批量插入章节索引\n */\n insertMany(chapters: Omit<ChapterRecord, 'id'>[]): void {\n const db = getDb();\n const stmt = db.prepare(`\n INSERT OR REPLACE INTO chapter_index (book_id, chapter_no, title, byte_offset)\n VALUES (?, ?, ?, ?)\n `);\n\n db.transaction(() => {\n for (const chapter of chapters) {\n stmt.run(chapter.book_id, chapter.chapter_no, chapter.title, chapter.byte_offset);\n }\n })();\n }\n\n /**\n * 获取指定书籍的所有章节\n */\n findByBookId(bookId: string): ChapterRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? ORDER BY chapter_no').all(bookId) as ChapterRecord[];\n }\n\n /**\n * 获取指定章节\n */\n findChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? AND chapter_no = ?').get(bookId, chapterNo) as ChapterRecord | undefined;\n }\n\n /**\n * 获取书籍章节总数\n */\n getChapterCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM chapter_index WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除指定书籍的章节索引\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM chapter_index WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * recent_reads 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface RecentRecord {\n book_id: string;\n opened_at: number;\n open_count: number;\n}\n\nexport class RecentModel {\n /**\n * 记录打开(插入或更新计数)\n */\n recordOpen(bookId: string): void {\n const db = getDb();\n const now = Date.now();\n db.prepare(`\n INSERT INTO recent_reads (book_id, opened_at, open_count)\n VALUES (?, ?, 1)\n ON CONFLICT(book_id) DO UPDATE SET\n opened_at = excluded.opened_at,\n open_count = open_count + 1\n `).run(bookId, now);\n }\n\n /**\n * 获取最近阅读列表(按时间倒序)\n */\n getRecent(limit: number = 20): RecentRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM recent_reads ORDER BY opened_at DESC LIMIT ?').all(limit) as RecentRecord[];\n }\n\n /**\n * 删除记录\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM recent_reads WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_progress 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ProgressRecord {\n book_id: string;\n chapter_no: number;\n byte_offset: number;\n percent: number;\n updated_at: number;\n opened_at: number;\n}\n\nexport class ProgressModel {\n /**\n * 保存或更新阅读进度\n */\n upsert(progress: ProgressRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_progress (book_id, chapter_no, byte_offset, percent, updated_at, opened_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(book_id) DO UPDATE SET\n chapter_no = excluded.chapter_no,\n byte_offset = excluded.byte_offset,\n percent = excluded.percent,\n updated_at = excluded.updated_at,\n opened_at = excluded.opened_at\n `).run(progress.book_id, progress.chapter_no, progress.byte_offset, progress.percent, progress.updated_at, progress.opened_at);\n }\n\n /**\n * 获取指定书籍的阅读进度\n */\n findByBookId(bookId: string): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress WHERE book_id = ?').get(bookId) as ProgressRecord | undefined;\n }\n\n /**\n * 获取最近打开的书籍进度(用于 resume)\n */\n getLastOpened(): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress ORDER BY opened_at DESC LIMIT 1').get() as ProgressRecord | undefined;\n }\n\n /**\n * 删除指定书籍的进度\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_progress WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * TXT 文件解析器\n * 编码检测、章节切割\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\n\nexport interface ParsedChapter {\n title: string;\n byteOffset: number;\n}\n\nexport interface ParsedBook {\n title: string;\n author: string | null;\n content: string;\n chapters: ParsedChapter[];\n}\n\n// 常见的章节标题正则\nconst CHAPTER_PATTERNS = [\n /^第[零一二三四五六七八九十百千万\\d]+[章节回卷集部篇]/m,\n /^Chapter\\s+\\d+/im,\n /^CHAPTER\\s+\\d+/m,\n /^第\\s*\\d+\\s*[章节回]/m,\n];\n\n/**\n * 解析 TXT 文件\n */\nexport async function parseTxt(filePath: string): Promise<ParsedBook> {\n // 读取原始字节\n const buffer = readFileSync(filePath);\n\n // 自动检测编码\n const encoding = detect(buffer) || 'utf-8';\n logger.debug(`检测到编码: ${encoding}`);\n\n // 转换为 UTF-8 字符串\n const content = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n // 从文件名提取标题\n const title = extractTitle(filePath);\n\n // 提取章节索引\n const chapters = extractChapters(content);\n\n return {\n title,\n author: null,\n content,\n chapters,\n };\n}\n\n/**\n * 从文件名提取书名\n */\nfunction extractTitle(filePath: string): string {\n const basename = filePath.split('/').pop() || filePath;\n // 去掉扩展名\n return basename.replace(/\\.txt$/i, '').trim() || '未命名';\n}\n\n/**\n * 提取章节索引\n */\nfunction extractChapters(content: string): ParsedChapter[] {\n const chapters: ParsedChapter[] = [];\n const lines = content.split('\\n');\n let byteOffset = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n for (const pattern of CHAPTER_PATTERNS) {\n if (pattern.test(trimmed)) {\n chapters.push({\n title: trimmed,\n byteOffset,\n });\n break;\n }\n }\n\n // 计算 byte offset(UTF-8 编码)\n byteOffset += Buffer.byteLength(line + '\\n', 'utf-8');\n }\n\n logger.debug(`检测到 ${chapters.length} 个章节`);\n return chapters;\n}\n","/**\n * EPUB 文件解析器\n * 元数据 + 章节提取,并转为纯文本阅读格式\n */\n\nimport { EPub } from 'epub2';\nimport { convert } from 'html-to-text';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\nimport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 解析 EPUB 文件\n */\nexport async function parseEpub(filePath: string): Promise<ParsedBook> {\n logger.debug(`开始解析 EPUB: ${filePath}`);\n\n const epub = await openEpub(filePath);\n\n const title = epub.metadata.title || '未知书名';\n const author = epub.metadata.creator || null;\n\n const chapters: ParsedChapter[] = [];\n let fullContent = '';\n let currentByteOffset = 0;\n\n // epub.flow contains the reading order\n for (const chapterRef of epub.flow) {\n if (!chapterRef.id) continue;\n\n try {\n const htmlText = await getChapterHtml(epub, chapterRef.id);\n \n // 使用 html-to-text 转为纯文本,保持换行,去除无关标签\n const plainText = convert(htmlText, {\n wordwrap: false,\n selectors: [\n // 忽略图片和链接\n { selector: 'img', format: 'skip' },\n { selector: 'a', options: { ignoreHref: true } },\n ],\n });\n\n if (!plainText.trim()) continue; // 跳过空章节\n\n const chapterTitle = chapterRef.title || '无标题章节';\n\n chapters.push({\n title: chapterTitle,\n byteOffset: currentByteOffset,\n });\n\n // 加上章节标题作为正文本页头部\n const chapterContent = `\\n\\n${chapterTitle}\\n\\n${plainText}\\n`;\n fullContent += chapterContent;\n\n currentByteOffset += Buffer.byteLength(chapterContent, 'utf-8');\n } catch (err) {\n logger.debug(`警告: 读取章节 ${chapterRef.id} 失败`, err);\n }\n }\n\n return {\n title,\n author,\n content: fullContent,\n chapters,\n };\n}\n\n/**\n * 提取 EPUB 元数据\n */\nexport async function getEpubMetadata(filePath: string): Promise<{ title: string; author: string | null }> {\n const epub = await openEpub(filePath);\n return {\n title: epub.metadata.title || '未知',\n author: epub.metadata.creator || null,\n };\n}\n\n// ============== 辅助函数 ==============\n\n/**\n * 包装 epub2 基于回调的初始化过程为 Promise\n */\nfunction openEpub(filePath: string): Promise<EPub> {\n return new Promise((resolve, reject) => {\n const epub = new EPub(filePath);\n epub.on('error', (err) => reject(err));\n epub.on('end', () => resolve(epub));\n epub.parse();\n });\n}\n\n/**\n * 从 HTML/XML 原始字节中提取编码声明\n * 优先读取 XML 声明 (<?xml encoding=\"GBK\">) 和 meta charset\n */\nfunction detectHtmlEncoding(buffer: Buffer): string {\n // 用 latin1 无损读取前 1KB,仅为提取编码声明\n const head = buffer.slice(0, 1024).toString('latin1');\n\n // 1. 优先查找 XML 声明:<?xml version=\"1.0\" encoding=\"GBK\"?>\n const xmlDecl = head.match(/<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']/i);\n if (xmlDecl?.[1]) return xmlDecl[1];\n\n // 2. 查找 HTML meta charset:<meta charset=\"GBK\"> 或 Content-Type: charset=GBK\n const metaCharset = head.match(/charset=[\"']?([A-Za-z0-9_-]+)[\"']?/i);\n if (metaCharset?.[1] && metaCharset[1].toLowerCase() !== 'utf-8') {\n return metaCharset[1];\n }\n\n // 3. 回退:使用 chardet 字节模式探测\n const chardetResult = detect(buffer);\n return chardetResult || 'utf-8';\n}\n\n/**\n * 获取单个章节的 HTML 原文并自动转码\n * 包装为 Promise\n */\nfunction getChapterHtml(epub: any, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // 使用 getFile 获取原始 Buffer,避免 epub2 内部 getChapterRaw 强制转 utf-8 导致的乱码\n epub.getFile(chapterId, (err: Error | null, data: Buffer) => {\n if (err) return reject(err);\n\n const buffer = data;\n const encoding = detectHtmlEncoding(buffer);\n\n const htmlText = encoding.toLowerCase().replace('-', '') === 'utf8' || encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n resolve(htmlText);\n });\n });\n}\n","/**\n * 解析器分发\n * 按文件格式分发到对应解析器\n */\n\nimport { parseTxt, type ParsedBook } from './TxtParser.js';\nimport { parseEpub } from './EpubParser.js';\n\nexport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 根据格式解析文件\n */\nexport async function parseFile(filePath: string, format: 'txt' | 'epub'): Promise<ParsedBook> {\n switch (format) {\n case 'txt':\n return parseTxt(filePath);\n case 'epub':\n return parseEpub(filePath);\n default:\n throw new Error(`不支持的文件格式: ${format}`);\n }\n}\n","/**\n * 文件 hash 计算(去重用)\n */\n\nimport { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\n\n/**\n * 计算文件的 SHA-256 hash\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n const buffer = readFileSync(filePath);\n const hash = createHash('sha256');\n hash.update(buffer);\n return hash.digest('hex');\n}\n","export default {\n // Common\n 'common.yes': '是',\n 'common.no': '否',\n 'common.confirm': '确认',\n 'common.cancel': '取消',\n 'common.quit': '按 q 退出',\n\n // CLI\n 'cli.import.desc': '导入本地文件或目录到书架',\n 'cli.import.help': '文件或目录路径(支持 .txt / .epub)',\n 'cli.import.success': '✓ 已导入:',\n 'cli.import.fail': '导入失败:',\n 'cli.import.not_found': '路径不存在:',\n 'cli.import.unsupported': '不支持的文件格式。目前支持: .txt, .epub',\n 'cli.import.scan_dir': '扫描目录',\n 'cli.import.found_files': '找到以下书籍:',\n 'cli.import.confirm_batch': '是否确认导入上述 {0} 本书?(y/N)',\n 'cli.import.canceled': '✓ 导入已取消',\n\n 'cli.resume.desc': '恢复上次阅读',\n 'cli.resume.none': '✗ 没有找到最近阅读记录,请先使用 novel import <file> 或 novel open <book-id> 打开一本书。',\n\n 'cli.open.desc': '打开指定书籍',\n 'cli.open.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.open.not_found': '✗ 未找到匹配书籍:',\n\n 'cli.library.desc': '查看书架书籍列表',\n 'cli.library.help': '可选关键字搜索',\n 'cli.library.none': '✗ 书架为空。使用 novel import <file> 导入你的第一本书。',\n 'cli.library.search_none': '📚 未找到匹配「{0}」的书籍。',\n 'cli.library.search_result': '📚 搜索结果 ({0} 本):\\n',\n\n 'cli.remove.desc': '从书架移除书籍(仅删除记录,不删源文件)',\n 'cli.remove.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.remove.not_found': '✗ 未找到匹配书籍:',\n 'cli.remove.success': '✓ 已移除书籍:',\n 'cli.remove.fail': '移除书籍失败:',\n\n 'cli.lang.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n 'cli.config.desc': '修改应用配置',\n 'cli.config.line_spacing.desc': '行间距: 0 (紧凑) | 1 (适中) | 2 (宽松)',\n 'cli.config.line_spacing.success': '✓ 行间距已设置为: {0}',\n 'cli.config.reading_mode.desc': '阅读模式: page (翻页) | scroll (滚动)',\n\n 'cli.update.desc': '检查最新版本并自动更新',\n 'cli.update.checking': '正在检查更新...',\n 'cli.update.latest': '✓ 当前已是最新版本 (v{0})',\n 'cli.update.updating': '发现新版本 v{0} (当前 v{1}),正在更新...',\n 'cli.update.success': '✓ 升级成功!请重新运行 novel 命令。',\n 'cli.update.fail': '✗ 升级失败: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 加载书架...',\n 'tui.lib.empty.title': '📚 书架',\n 'tui.lib.empty.desc': '书架为空。使用 novel import <file> 导入你的第一本书。',\n 'tui.lib.title': '📚 书架 ({0} 本)',\n 'tui.lib.tips': ' ↑↓/jk 选择 · Enter 打开 · d/x 删除 · q 退出',\n\n // TUI - Reader\n 'tui.reader.loading': '读取中...',\n 'tui.reader.bookmark_add': '✓ 增加书签: {0}',\n 'tui.reader.status.remaining': '预计剩余 {0}',\n\n 'tui.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.hint': '按 C 键弹出此清单,按 Tab 切换书签',\n 'tui.nav.empty': '没有记录',\n 'tui.nav.page': '第 {0} / {1} 页',\n};\n","import type { LocaleDictionary } from './types.js';\n\nconst en: LocaleDictionary = {\n // Common\n 'common.yes': 'Yes',\n 'common.no': 'No',\n 'common.confirm': 'Confirm',\n 'common.cancel': 'Cancel',\n 'common.quit': 'Press q to quit',\n\n // CLI\n 'cli.import.desc': 'Import local file or directory to library',\n 'cli.import.help': 'File or directory path (supports .txt / .epub)',\n 'cli.import.success': '✓ Imported:',\n 'cli.import.fail': 'Import failed:',\n 'cli.import.not_found': 'Path not found:',\n 'cli.import.unsupported': 'Unsupported file format. Currently supports: .txt, .epub',\n 'cli.import.scan_dir': 'Scanning directory',\n 'cli.import.found_files': 'Found following books:',\n 'cli.import.confirm_batch': 'Confirm importing these {0} books? (y/N)',\n 'cli.import.canceled': '✓ Import canceled',\n\n 'cli.resume.desc': 'Resume last reading',\n 'cli.resume.none': '✗ No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.',\n\n 'cli.open.desc': 'Open specific book',\n 'cli.open.help': 'Book ID or title (fuzzy match)',\n 'cli.open.not_found': '✗ Book not found:',\n\n 'cli.library.desc': 'View book list / library',\n 'cli.library.help': 'Optional keyword search',\n 'cli.library.none': '✗ Library is empty. Use `novel import <file>` to import your first book.',\n 'cli.library.search_none': '📚 No books found matching \"{0}\".',\n 'cli.library.search_result': '📚 Search results ({0} books):\\n',\n\n 'cli.remove.desc': 'Remove book from library (only deletes records, not source file)',\n 'cli.remove.help': 'Book ID or title (fuzzy match)',\n 'cli.remove.not_found': '✗ Book not found:',\n 'cli.remove.success': '✓ Book removed:',\n 'cli.remove.fail': 'Failed to remove book:',\n\n 'cli.lang.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n 'cli.config.desc': 'Modify application configuration',\n 'cli.config.line_spacing.desc': 'Line spacing: 0 (tight) | 1 (medium) | 2 (loose)',\n 'cli.config.line_spacing.success': '✓ Line spacing set to: {0}',\n 'cli.config.reading_mode.desc': 'Reading mode: page (paging) | scroll (scrolling)',\n\n 'cli.update.desc': 'Check for the latest version and update automatically',\n 'cli.update.checking': 'Checking for updates...',\n 'cli.update.latest': '✓ Already up to date (v{0})',\n 'cli.update.updating': 'New version v{0} found (current v{1}), updating...',\n 'cli.update.success': '✓ Successfully updated! Please restart novel command.',\n 'cli.update.fail': '✗ Update failed: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 Loading library...',\n 'tui.lib.empty.title': '📚 Library',\n 'tui.lib.empty.desc': 'Library is empty. Use `novel import <file>` to import your first book.',\n 'tui.lib.title': '📚 Library ({0} books)',\n 'tui.lib.tips': ' ↑↓/jk select · Enter open · d/x delete · q quit',\n\n // TUI - Reader\n 'tui.reader.loading': 'Loading...',\n 'tui.reader.bookmark_add': '✓ Bookmark added: {0}',\n 'tui.reader.status.remaining': 'Est. remaining {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[All Chapters]',\n 'tui.nav.tab.bookmarks': '[My Bookmarks]',\n 'tui.nav.tips': 'Enter jump · Tab switch · Esc/q close',\n 'tui.nav.hint': 'Press C to open this list, press Tab to switch to bookmarks',\n 'tui.nav.empty': 'No records',\n 'tui.nav.page': 'Page {0} / {1}',\n};\n\nexport default en;\n","/**\n * 应用配置管理\n * 读写 ~/.config/readshell/config.json\n */\n\nimport Conf from 'conf';\n\ninterface ReadShellConfig {\n /** 每页显示行数(0 = 自适应终端高度) */\n linesPerPage: number;\n /** 是否显示状态栏 */\n showStatusBar: boolean;\n /** 阅读模式: 'page' | 'scroll' */\n readingMode: 'page' | 'scroll';\n /** 界面语言 */\n language: 'zh' | 'en';\n /** 行间距 (0-2) */\n lineSpacing: number;\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n lineSpacing: 0,\n};\n\nconst config = new Conf<ReadShellConfig>({\n projectName: 'readshell',\n defaults,\n});\n\nexport function getConfig(): ReadShellConfig {\n return {\n linesPerPage: config.get('linesPerPage'),\n showStatusBar: config.get('showStatusBar'),\n readingMode: config.get('readingMode'),\n language: config.get('language'),\n lineSpacing: config.get('lineSpacing'),\n };\n}\n\nexport function setConfig<K extends keyof ReadShellConfig>(key: K, value: ReadShellConfig[K]): void {\n config.set(key, value);\n}\n\nexport { config };\n","import zh from './zh.js';\nimport en from './en.js';\nimport type { LocaleDictionary, LocaleKeys } from './types.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nconst dictionaries: Record<'zh' | 'en', LocaleDictionary> = {\n zh,\n en,\n};\n\nlet currentLang: 'zh' | 'en' = 'zh';\nlet currentDict: LocaleDictionary = dictionaries.zh;\n\n/**\n * 初始化并加载当前配置的语言\n */\nexport function initI18n() {\n const config = getConfig();\n currentLang = config.language || 'zh';\n currentDict = dictionaries[currentLang] || dictionaries.zh;\n}\n\n/**\n * 切换语言字典(运行时热切换支持)\n */\nexport function setLanguage(lang: 'zh' | 'en') {\n currentLang = lang;\n currentDict = dictionaries[lang] || dictionaries.zh;\n}\n\n/**\n * 获取翻译词条\n * 支持占位符 {0}, {1} 替换\n */\nexport function t(key: LocaleKeys, ...args: (string | number)[]): string {\n let template = currentDict[key];\n if (!template) {\n return key;\n }\n \n if (args.length > 0) {\n args.forEach((arg, index) => {\n template = template.replace(`{${index}}`, String(arg));\n });\n }\n return template;\n}\n","/**\n * novel import <file> — 导入本地文件\n * 支持 txt,基础去重,导入后自动入书架\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { statSync, readdirSync } from 'node:fs';\nimport { resolve, join, extname } from 'node:path';\nimport * as readline from 'node:readline/promises';\n\nexport interface ImportArgs {\n file: string;\n}\n\n// 递归扫描目录文件\nfunction scanDirectory(dir: string): string[] {\n let results: string[] = [];\n try {\n const list = readdirSync(dir);\n for (const file of list) {\n const fullPath = join(dir, file);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results = results.concat(scanDirectory(fullPath));\n } else {\n const ext = extname(fullPath).toLowerCase();\n if (ext === '.txt' || ext === '.epub') {\n results.push(fullPath);\n }\n }\n }\n } catch (err) {\n logger.error('Scan error:', err);\n }\n return results;\n}\n\nexport const importCommand: CommandModule<object, ImportArgs> = {\n command: 'import <file>',\n describe: t('cli.import.desc'),\n builder: (yargs) => {\n return yargs.positional('file', {\n describe: t('cli.import.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const targetPath = resolve(argv.file);\n let stat;\n try {\n stat = statSync(targetPath);\n } catch (e) {\n console.log(`${t('cli.import.not_found')} ${targetPath}`);\n process.exit(1);\n }\n\n const bookService = new BookService();\n\n if (stat.isDirectory()) {\n console.log(`${t('cli.import.scan_dir')} ${targetPath}...`);\n const files = scanDirectory(targetPath);\n\n if (files.length === 0) {\n console.log(`✗ ` + t('cli.import.unsupported'));\n return;\n }\n\n console.log(t('cli.import.found_files'));\n files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const answer = await rl.question(t('cli.import.confirm_batch', files.length) + ' ');\n rl.close();\n\n if (answer.toLowerCase() === 'y') {\n for (const file of files) {\n try {\n const book = await bookService.importBook(file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n } catch (err) {\n console.log(`${t('cli.import.fail')} ${file} - ${err}`);\n }\n }\n } else {\n console.log(t('cli.import.canceled'));\n }\n } else {\n // 单文件导入\n const book = await bookService.importBook(argv.file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n }\n } catch (error) {\n console.log(`${t('cli.import.fail')} ${error}`);\n process.exit(1);\n }\n },\n};\n","/**\n * 阅读进度业务逻辑\n * 进度读写、resume 核心逻辑\n */\n\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { logger } from '../utils/logger.js';\n\nexport class ProgressService {\n private progressModel = new ProgressModel();\n private recentModel = new RecentModel();\n\n /**\n * 保存阅读进度(退出时调用)\n */\n saveProgress(bookId: string, chapterNo: number, byteOffset: number, percent: number): void {\n const now = Date.now();\n\n this.progressModel.upsert({\n book_id: bookId,\n chapter_no: chapterNo,\n byte_offset: byteOffset,\n percent,\n updated_at: now,\n opened_at: now,\n });\n\n // 同时更新最近阅读记录\n this.recentModel.recordOpen(bookId);\n\n logger.debug(`进度已保存: book=${bookId}, chapter=${chapterNo}, offset=${byteOffset}, ${(percent * 100).toFixed(1)}%`);\n }\n\n /**\n * 获取指定书籍的阅读进度(resume 时调用)\n */\n getProgress(bookId: string): ProgressRecord | undefined {\n return this.progressModel.findByBookId(bookId);\n }\n\n /**\n * 获取最近打开的书籍进度(启动时调用,决定 resume 哪本书)\n */\n getLastOpenedBook(): ProgressRecord | undefined {\n return this.progressModel.getLastOpened();\n }\n}\n","/**\n * Ink TUI 渲染启动函数\n * 封装 ink.render(<App />) 的统一入口\n */\n\nimport React from 'react';\nimport { render } from 'ink';\nimport { App, type PageRoute } from './App.js';\n\ninterface RenderOptions {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\n/**\n * 启动 Ink TUI 应用\n */\nexport function renderApp(options: RenderOptions = {}): void {\n const { initialPage = 'resume', bookId, initialByteOffset } = options;\n\n const { waitUntilExit } = render(\n React.createElement(App, {\n initialPage,\n bookId,\n initialByteOffset,\n }),\n );\n\n waitUntilExit().catch(() => {\n // Ink 退出时的清理\n process.exit(0);\n });\n}\n","/**\n * App 根组件\n * 管理页面路由状态\n */\n\nimport React, { useState } from 'react';\nimport { Box, Text } from 'ink';\nimport { ResumePage } from './pages/ResumePage.js';\nimport { LibraryPage } from './pages/LibraryPage.js';\nimport { ReaderPage } from './pages/ReaderPage.js';\n\nexport type PageRoute = 'resume' | 'library' | 'reader';\n\ninterface AppProps {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\nexport function App({ initialPage = 'resume', bookId, initialByteOffset }: AppProps) {\n const [currentPage, setCurrentPage] = useState<PageRoute>(initialPage);\n const [currentBookId, setCurrentBookId] = useState<string | undefined>(bookId);\n const [currentByteOffset, setCurrentByteOffset] = useState<number | undefined>(initialByteOffset);\n\n const navigateTo = (page: PageRoute, targetBookId?: string, byteOffset?: number) => {\n setCurrentPage(page);\n if (targetBookId) setCurrentBookId(targetBookId);\n if (byteOffset !== undefined) setCurrentByteOffset(byteOffset);\n };\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {currentPage === 'resume' && (\n <ResumePage onNavigate={navigateTo} />\n )}\n {currentPage === 'library' && (\n <LibraryPage onNavigate={navigateTo} />\n )}\n {currentPage === 'reader' && currentBookId && (\n <ReaderPage\n bookId={currentBookId}\n initialByteOffset={currentByteOffset}\n onNavigate={navigateTo}\n />\n )}\n {currentPage === 'reader' && !currentBookId && (\n <Box>\n <Text color=\"red\">错误: 未指定书籍</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * 继续阅读页\n * 启动默认页,自动查询最近阅读并跳转到阅读器\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { Box, Text, useApp } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\n\ninterface ResumePageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function ResumePage({ onNavigate }: ResumePageProps) {\n const { exit } = useApp();\n const [checking, setChecking] = useState(true);\n\n useEffect(() => {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n setChecking(false);\n return;\n }\n\n // 验证书籍存在\n const bookModel = new BookModel();\n const book = bookModel.findById(lastProgress.book_id);\n\n if (!book) {\n setChecking(false);\n return;\n }\n\n // 自动跳转到阅读器\n onNavigate('reader', book.id, lastProgress.byte_offset);\n }, [onNavigate]);\n\n if (checking) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 检查阅读记录...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">\n 📖 ReadShell — 终端内轻阅读\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>暂无阅读记录。</Text>\n <Text dimColor>使用 novel import <file> 导入一本书开始阅读。</Text>\n <Box marginTop={1}>\n <Text dimColor>按 q 退出</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * 书架页\n * 交互式书架列表,上下键选择,Enter 打开阅读\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport type { BookRecord } from '../../db/models/Book.js';\nimport { t } from '../../locales/index.js';\n\ninterface LibraryPageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function LibraryPage({ onNavigate }: LibraryPageProps) {\n const { exit } = useApp();\n const [books, setBooks] = useState<BookRecord[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(true);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useEffect(() => {\n const bookService = new BookService();\n setBooks(bookService.getAllBooks());\n setLoading(false);\n }, []);\n\n useInput((input, key) => {\n if (input === 'q') {\n exit();\n return;\n }\n\n if (books.length === 0) return;\n\n // 上下导航\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n }\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(prev + 1, books.length - 1));\n }\n\n // 删除选中的书 (backspace 键或 d 键)\n if (key.backspace || key.delete || input === 'd' || input === 'x') {\n const selected = books[selectedIndex];\n if (selected) {\n const bookService = new BookService();\n bookService.deleteBook(selected.id);\n \n // 更新列表\n setBooks((prev) => {\n const next = prev.filter((b) => b.id !== selected.id);\n // 调整选中游标,防止越界\n if (selectedIndex >= next.length) {\n setSelectedIndex(Math.max(0, next.length - 1));\n }\n return next;\n });\n }\n return;\n }\n\n // Enter 打开选中的书\n if (key.return) {\n const selected = books[selectedIndex];\n if (selected) {\n const progressService = new ProgressService();\n const progress = progressService.getProgress(selected.id);\n onNavigate('reader', selected.id, progress?.byte_offset ?? 0);\n }\n }\n }, { isActive: isRawModeSupported });\n\n if (loading) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">{t('tui.lib.loading')}</Text>\n </Box>\n );\n }\n\n if (books.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.empty.title')}</Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>{t('tui.lib.empty.desc')}</Text>\n <Box marginTop={1}>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.title', books.length)}</Text>\n <Text dimColor>{t('tui.lib.tips')}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {books.map((book, index) => {\n const isSelected = index === selectedIndex;\n \n return (\n <Box key={book.id} paddingX={1} justifyContent=\"space-between\">\n <Box>\n <Text\n color={isSelected ? 'cyan' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▸ ' : ' '}\n {book.title}\n </Text>\n <Text dimColor> ({book.format})</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器页 — 核心阅读体验\n *\n * 功能:\n * - 加载书籍文件内容,按终端尺寸分页\n * - 键盘翻页(空格/j/↓ 下一页, k/↑ 上一页)\n * - 状态栏显示书名 + 进度百分比\n * - 退出时(q)自动保存进度\n * - resume 进入时根据 byte_offset 定位到对应页\n */\n\nimport React, { useState, useEffect, useRef, useMemo } from 'react';\nimport { Box, Text, useApp, useStdout } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { TextRenderer } from '../components/TextRenderer.js';\nimport { StatusBar } from '../components/StatusBar.js';\nimport { ChapterNav } from '../components/ChapterNav.js';\nimport { useReader } from '../hooks/useReader.js';\nimport { useKeyboard } from '../hooks/useKeyboard.js';\nimport { paginate, type Page } from '../../utils/paginate.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { parseFile, type ParsedBook } from '../../parsers/index.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { ChapterService } from '../../services/ChapterService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport { BookmarkService } from '../../services/BookmarkService.js';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { triggerBossKey } from '../../utils/bossKey.js';\nimport { estimateReadingTime, formatReadingTime } from '../../utils/time.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { getConfig } from '../../config/AppConfig.js';\n\ninterface ReaderPageProps {\n bookId: string;\n initialByteOffset?: number;\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\n/**\n * 阅读器内部组件 — 在 pages 准备好后渲染\n */\nfunction ReaderContent({\n book,\n bookId,\n pages,\n initialByteOffset,\n termHeight,\n contentHeight,\n lineSpacing,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n lineSpacing: number;\n}) {\n const { exit } = useApp();\n const [chapterTitle, setChapterTitle] = useState<string | undefined>();\n const [currentChapter, setCurrentChapter] = useState<ChapterRecord | undefined>();\n const [showChapterNav, setShowChapterNav] = useState(false);\n const [allChapters, setAllChapters] = useState<ChapterRecord[]>([]);\n const [allBookmarks, setAllBookmarks] = useState<BookmarkRecord[]>([]);\n const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n const progressServiceRef = useRef(new ProgressService());\n const chapterServiceRef = useRef(new ChapterService());\n const bookmarkServiceRef = useRef(new BookmarkService());\n\n const reader = useReader(pages, initialByteOffset);\n\n // 保存进度的通用方法\n const saveReadingProgress = () => {\n const offset = reader.getCurrentOffset();\n const percent = reader.getPercent();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);\n const chapterNo = chapter?.chapter_no ?? 0;\n\n progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);\n logger.debug(`进度已保存: offset=${offset}, ${(percent * 100).toFixed(1)}%`);\n };\n\n // 加载并更新当前章节信息\n useEffect(() => {\n const currentOffset = reader.getCurrentOffset();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);\n setCurrentChapter(chapter ?? undefined);\n setChapterTitle(chapter?.title ?? undefined);\n }, [reader.currentPage, bookId]);\n\n // 获取该书全部章节及书签用于渲染导航\n useEffect(() => {\n const chaptersList = chapterServiceRef.current.getChaptersByBookId(bookId);\n setAllChapters(chaptersList);\n \n if (showChapterNav) {\n setAllBookmarks(bookmarkServiceRef.current.getBookmarksByBookId(bookId));\n }\n }, [bookId, showChapterNav]);\n\n /**\n * 取当前页首行生成书签名\n */\n const handleAddBookmark = () => {\n const currentPageInfo = reader.getCurrentPage();\n if (!currentPageInfo) return;\n\n let markTitle = '无标题书签';\n for (const line of currentPageInfo.lines) {\n const stripped = line.trim();\n if (stripped.length > 0) {\n markTitle = stripped.slice(0, 15) + (stripped.length > 15 ? '...' : '');\n break;\n }\n }\n\n const currentOffset = reader.getCurrentOffset();\n bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);\n \n setToastMessage(t('tui.reader.bookmark_add', markTitle));\n setTimeout(() => setToastMessage(null), 2000);\n };\n\n /**\n * 自动在组件卸载时保存进度\n */\n useEffect(() => {\n return () => {\n saveReadingProgress();\n };\n }, [bookId, reader]);\n\n // 拦截全局键盘事件\n useKeyboard(\n {\n onNext: () => reader.nextPage(),\n onPrev: () => reader.prevPage(),\n onQuit: () => exit(),\n onChapterList: () => setShowChapterNav(true),\n onBossKey: () => {\n saveReadingProgress(); // 老板来了!先存盘\n triggerBossKey(); // 再跑路\n },\n onBookmarkAdd: handleAddBookmark,\n },\n !showChapterNav, // 如果浮层显示,则停止普通的阅读快捷键\n );\n\n const currentPage = reader.getCurrentPage();\n const currentLines = currentPage?.lines ?? [];\n // The contentHeight prop is already passed, but the instruction redefines it.\n // Assuming the user intends to use this new calculation for contentHeight within ReaderContent.\n const calculatedContentHeight = Math.max(1, termHeight - 2);\n\n // 计算剩余阅读时间:总字符近似于字节数的 1/3 (utf-8 场景下),中文字符占绝大部分\n const totalChars = (book.file_size ?? 0) / 3;\n const remainingChars = Math.max(0, totalChars * (1 - reader.getPercent()));\n const remainingMinutes = estimateReadingTime(remainingChars, true);\n const remainingTimeStr = formatReadingTime(remainingMinutes);\n\n return (\n <Box flexDirection=\"column\" height={termHeight}>\n {/* 相对定位于容器中,使用 flex 布局进行展现。章节模式下隐藏正文 */}\n {!showChapterNav ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} paddingX={1}>\n <TextRenderer lines={currentLines} height={calculatedContentHeight} lineSpacing={lineSpacing} />\n </Box>\n <StatusBar\n bookTitle={book.title}\n percent={reader.getPercent()}\n chapterTitle={chapterTitle}\n currentPage={reader.currentPage + 1}\n totalPages={reader.totalPages}\n remainingTime={remainingTimeStr}\n />\n {toastMessage && (\n <Box alignSelf=\"flex-end\" marginTop={-2} marginRight={1} borderStyle=\"round\" borderColor=\"green\" paddingX={1}>\n <Text color=\"green\">{toastMessage}</Text>\n </Box>\n )}\n </>\n ) : (\n <ChapterNav\n chapters={allChapters}\n bookmarks={allBookmarks}\n currentChapterId={currentChapter?.id}\n termHeight={termHeight}\n onSelect={(offset) => {\n reader.goToOffset(offset);\n setShowChapterNav(false);\n }}\n onClose={() => setShowChapterNav(false)}\n />\n )}\n </Box>\n );\n}\n\nexport function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }: ReaderPageProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n const [book, setBook] = useState<BookRecord | null>(null);\n const [pages, setPages] = useState<Page[] | null>(null);\n const [error, setError] = useState<string | null>(null);\n\n const termWidth = stdout?.columns ?? 80;\n const termHeight = stdout?.rows ?? 24;\n\n const appConfig = getConfig();\n const lineSpacing = appConfig.lineSpacing || 0;\n \n // 核心优化:如果行间距 > 0,则物理行数会翻倍。为了保证不超出屏幕,分页时的内容高度需要对应缩小。\n const contentHeight = Math.max(Math.floor((termHeight - 3) / (1 + lineSpacing)), 2);\n\n // 加载书籍内容\n useEffect(() => {\n try {\n const bookModel = new BookModel();\n const bookRecord = bookModel.findById(bookId);\n\n if (!bookRecord) {\n setError(`书籍不存在: ${bookId}`);\n return;\n }\n\n setBook(bookRecord);\n\n // 记录打开事件\n const recentService = new RecentService();\n recentService.recordOpen(bookId);\n\n // 异步读取并解析文件内容 (支持 TXT 和 EPUB)\n parseFile(bookRecord.file_path, bookRecord.format as 'txt' | 'epub')\n .then((parsed: ParsedBook) => {\n // 核心优化:如果处于滚动模式,步进减半以实现平滑过渡\n const stepSize = appConfig.readingMode === 'scroll' \n ? Math.max(1, Math.floor(contentHeight / 2)) \n : contentHeight;\n\n // 分页\n const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight, stepSize);\n setPages(paginatedPages);\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页, 模式: ${appConfig.readingMode}`);\n })\n .catch((err: Error) => {\n setError(`内容解析失败: ${err instanceof Error ? err.message : String(err)}`);\n });\n } catch (err) {\n setError(`加载过程出错: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, [bookId, termWidth, contentHeight]);\n\n // 非 TTY 下支持 q 退出\n useKeyboard({\n onQuit: () => exit(),\n });\n\n // 错误\n if (error) {\n return (\n <Box padding={1} flexDirection=\"column\">\n <Text color=\"red\">✗ {error}</Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n );\n }\n\n // 加载中\n if (!book || !pages) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 {t('tui.reader.loading')}</Text>\n </Box>\n );\n }\n\n // pages 准备好后渲染阅读器内容\n return (\n <ReaderContent\n book={book}\n bookId={bookId}\n pages={pages}\n initialByteOffset={initialByteOffset}\n termHeight={termHeight}\n contentHeight={contentHeight}\n lineSpacing={lineSpacing}\n />\n );\n}\n","/**\n * 正文渲染组件\n * 显示分页后的文本行,并支持智能高亮\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\n\ninterface TextRendererProps {\n lines: string[];\n height?: number;\n lineSpacing?: number;\n}\n\n/**\n * 处理单行内的智能高亮匹配\n * 例如将 「XXX」或 “XXX” 进行暗化或着色,提升大段文字沉浸感\n */\nfunction renderLineWithHighlight(line: string) {\n if (!line) return <Text> </Text>; // 保留空行\n\n // 匹配对话大纲的正则 (包括中英文常见方括号/双引号)\n // 此处拆分为:对话段落与非对话段落\n // (「.*?」|“.*?”|『.*?』|《.*?》)\n const regex = /(「.*?」|“.*?”|『.*?』|《.*?》)/g;\n const parts = line.split(regex);\n\n return (\n <Text>\n {parts.map((part, index) => {\n // 如果是正则匹配出的组(即被高亮的部分)\n if (regex.test(part)) {\n // 由于 Regex 的全局状态,test 之后要注意\n // 但其实 split 后,奇数项是捕获组的内容\n }\n \n // 简单判定:如果是奇数项,就是捕获组\n const isHighlight = index % 2 === 1;\n\n if (isHighlight) {\n // 对话颜色应用 dimColor,让环境和描述语句突出,或者是相反。\n // 这里将对话变暗 (dimColor),减轻视觉疲劳\n return <Text key={index} dimColor>{part}</Text>;\n }\n\n // 常规文本\n return <Text key={index}>{part}</Text>;\n })}\n </Text>\n );\n}\n\nexport function TextRenderer({ lines, height, lineSpacing = 0 }: TextRendererProps) {\n // 补齐空行以占满屏幕高度(避免内容跳动)\n const displayLines = [...lines];\n if (height && displayLines.length < height) {\n const padding = height - displayLines.length;\n for (let i = 0; i < padding; i++) {\n displayLines.push('');\n }\n }\n\n return (\n <Box flexDirection=\"column\">\n {displayLines.map((line, index) => (\n <Box key={index} marginBottom={lineSpacing}>\n {renderLineWithHighlight(line)}\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * 底部状态栏组件\n * 显示书名、章节名、页码和进度百分比\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\nimport { t } from '../../locales/index.js';\n\ninterface StatusBarProps {\n bookTitle: string;\n chapterTitle?: string;\n percent: number;\n currentPage: number;\n totalPages: number;\n remainingTime?: string;\n}\n\nexport function StatusBar({\n bookTitle,\n chapterTitle,\n percent,\n currentPage,\n totalPages,\n remainingTime,\n}: StatusBarProps) {\n const displayPercent = (percent * 100).toFixed(1);\n const titleDisplay = chapterTitle ? `${bookTitle} · ${chapterTitle}` : bookTitle;\n\n return (\n <Box flexDirection=\"row\" justifyContent=\"space-between\" borderStyle=\"single\" borderTop={false} borderLeft={false} borderRight={false} paddingX={1}>\n <Box>\n <Text color=\"gray\">📖 {titleDisplay}</Text>\n </Box>\n\n <Box>\n {remainingTime && (\n <Text dimColor>{t('tui.reader.status.remaining', remainingTime)} </Text>\n )}\n <Text color=\"gray\">\n {currentPage}/{totalPages} \n </Text>\n <Text color=\"gray\">\n {displayPercent}% \n </Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 章节导航浮层组件\n * 允许在阅读器内唤起章节列表,并支持翻页与选择跳转\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { t } from '../../locales/index.js';\n\ninterface ChapterNavProps {\n chapters: ChapterRecord[];\n bookmarks: BookmarkRecord[];\n currentChapterId?: number;\n termHeight: number;\n onSelect: (byteOffset: number) => void;\n onClose: () => void;\n}\n\nexport function ChapterNav({\n chapters,\n bookmarks,\n currentChapterId,\n termHeight,\n onSelect,\n onClose,\n}: ChapterNavProps) {\n const [activeTab, setActiveTab] = useState<'chapters' | 'bookmarks'>('chapters');\n const isBookmarks = activeTab === 'bookmarks';\n const currentList = isBookmarks ? bookmarks : chapters;\n\n // 找到当前章节的初始索引\n const initialIndex = currentChapterId && !isBookmarks\n ? Math.max(\n 0,\n chapters.findIndex((c) => c.id === currentChapterId),\n )\n : 0;\n\n const [selectedIndex, setSelectedIndex] = useState(initialIndex);\n\n // 切换 tab 时重置索引\n useEffect(() => {\n setSelectedIndex(isBookmarks ? 0 : initialIndex);\n }, [activeTab, initialIndex, isBookmarks]);\n\n // 一页展示多少个项(留出上下边距)\n const pageSize = Math.max(5, termHeight - 6);\n\n // 计算当前可视窗口的起始和结束索引\n const windowStart = Math.max(0, Math.floor(selectedIndex / pageSize) * pageSize);\n const visibleItems = currentList.slice(windowStart, windowStart + pageSize);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n // 退出\n if (key.escape || input === 'q') {\n onClose();\n return;\n }\n \n // 切换视图\n if (key.tab) {\n setActiveTab(prev => prev === 'chapters' ? 'bookmarks' : 'chapters');\n return;\n }\n\n // 确认\n if (key.return) {\n if (currentList[selectedIndex]) {\n onSelect(currentList[selectedIndex].byte_offset);\n }\n return;\n }\n\n // 上移\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n }\n\n // 下移\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(currentList.length - 1, prev + 1));\n }\n },\n { isActive: isRawModeSupported },\n );\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor=\"green\"\n paddingX={2}\n paddingY={1}\n width=\"80%\"\n alignSelf=\"center\"\n marginTop={2}\n >\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Box>\n <Text bold color={activeTab === 'chapters' ? 'green' : 'gray'}>{t('tui.nav.tab.chapters')} </Text>\n <Text bold color={activeTab === 'bookmarks' ? 'green' : 'gray'}>{t('tui.nav.tab.bookmarks')}</Text>\n </Box>\n <Text dimColor>{t('tui.nav.tips')}</Text>\n </Box>\n\n {visibleItems.length === 0 ? (\n <Text dimColor>{t('tui.nav.empty')}</Text>\n ) : (\n visibleItems.map((item, idx) => {\n const actualIndex = windowStart + idx;\n const isSelected = actualIndex === selectedIndex;\n \n return (\n <Text\n key={item.id}\n color={isSelected ? 'green' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▶ ' : ' '}\n {item.title}\n </Text>\n );\n })\n )}\n\n <Box marginTop={1} justifyContent=\"flex-end\">\n <Text dimColor>\n {t('tui.nav.page', Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1)}\n </Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器核心状态 Hook\n * 管理当前页面、offset、分页等状态\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport type { Page } from '../../utils/paginate.js';\n\ninterface ReaderState {\n currentPage: number;\n totalPages: number;\n}\n\nexport function useReader(pages: Page[], initialByteOffset?: number) {\n // 根据 initialByteOffset 找到初始页\n const initialPage = useMemo(() => {\n if (!initialByteOffset || pages.length === 0) return 0;\n\n // 找到 byte_offset <= initialByteOffset 的最后一页\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= initialByteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n return targetPage;\n }, [pages, initialByteOffset]);\n\n const [state, setState] = useState<ReaderState>({\n currentPage: initialPage,\n totalPages: pages.length,\n });\n\n const nextPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.min(prev.currentPage + 1, prev.totalPages - 1),\n }));\n }, []);\n\n const prevPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(prev.currentPage - 1, 0),\n }));\n }, []);\n\n const goToPage = useCallback((pageNum: number) => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(0, Math.min(pageNum, prev.totalPages - 1)),\n }));\n }, []);\n\n /**\n * 根据 byte_offset 跳转到对应页\n */\n const goToOffset = useCallback((byteOffset: number) => {\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= byteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n setState((prev) => ({\n ...prev,\n currentPage: targetPage,\n }));\n }, [pages]);\n\n const getCurrentPage = useCallback((): Page | undefined => {\n return pages[state.currentPage];\n }, [state.currentPage, pages]);\n\n /**\n * 获取当前页的 byte_offset(用于保存进度)\n */\n const getCurrentOffset = useCallback((): number => {\n return pages[state.currentPage]?.byteOffset ?? 0;\n }, [state.currentPage, pages]);\n\n const getPercent = useCallback((): number => {\n if (state.totalPages === 0) return 0;\n return (state.currentPage + 1) / state.totalPages;\n }, [state.currentPage, state.totalPages]);\n\n const isFirstPage = state.currentPage === 0;\n const isLastPage = state.currentPage === state.totalPages - 1;\n\n return {\n ...state,\n nextPage,\n prevPage,\n goToPage,\n goToOffset,\n getCurrentPage,\n getCurrentOffset,\n getPercent,\n isFirstPage,\n isLastPage,\n };\n}\n","/**\n * 键盘事件统一管理 Hook\n * 在非 TTY 环境下安全降级\n */\n\nimport { useInput } from 'ink';\n\ninterface KeyboardHandlers {\n onNext?: () => void;\n onPrev?: () => void;\n onQuit?: () => void;\n onChapterList?: () => void;\n onHelp?: () => void;\n onBossKey?: () => void;\n onBookmarkAdd?: () => void;\n}\n\nexport function useKeyboard(handlers: KeyboardHandlers, isActive: boolean = true) {\n const isRawModeSupported = process.stdin.isTTY ?? false;\n const shouldListen = isRawModeSupported && isActive;\n\n useInput((input, key) => {\n // 翻页:空格 / j / 下箭头 / f → 下一页\n if (input === ' ' || input === 'j' || key.downArrow || input === 'f') {\n handlers.onNext?.();\n }\n\n // 上一页:k / 上箭头 / b\n if (input === 'k' || key.upArrow || input === 'b') {\n handlers.onPrev?.();\n }\n\n // 退出:q\n if (input === 'q') {\n handlers.onQuit?.();\n }\n\n // 章节列表:c\n if (input === 'c') {\n handlers.onChapterList?.();\n }\n\n // 帮助:?\n if (input === '?') {\n handlers.onHelp?.();\n }\n \n // 老板键\n if (handlers.onBossKey && (key.escape || input === 'esc' || input === 'b' || input === 'B')) {\n handlers.onBossKey?.();\n }\n \n // 按 m 或 M 加书签\n if (handlers.onBookmarkAdd && (input === 'm' || input === 'M')) {\n handlers.onBookmarkAdd?.();\n }\n }, { isActive: shouldListen });\n}\n","/**\n * 字符串宽度计算\n * 处理中文字符宽度为 2\n */\n\n/**\n * 获取字符串在终端中的显示宽度\n */\nexport function getStringWidth(str: string): number {\n let width = 0;\n for (const char of str) {\n width += isFullWidth(char) ? 2 : 1;\n }\n return width;\n}\n\n/**\n * 判断字符是否为全角字符\n * CJK 统一表意字符 + 全角标点\n */\nfunction isFullWidth(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n return (\n // CJK 统一表意字符\n (code >= 0x4e00 && code <= 0x9fff) ||\n // CJK 统一表意字符扩展 A\n (code >= 0x3400 && code <= 0x4dbf) ||\n // CJK 统一表意字符扩展 B\n (code >= 0x20000 && code <= 0x2a6df) ||\n // CJK 兼容表意字符\n (code >= 0xf900 && code <= 0xfaff) ||\n // 全角 ASCII、全角标点\n (code >= 0xff01 && code <= 0xff60) ||\n (code >= 0xffe0 && code <= 0xffe6) ||\n // CJK 标点符号\n (code >= 0x3000 && code <= 0x303f) ||\n // 日文平假名/片假名\n (code >= 0x3040 && code <= 0x30ff) ||\n // 韩文音节\n (code >= 0xac00 && code <= 0xd7af)\n );\n}\n","/**\n * 文本分页算法\n * 按终端宽高切割文本为页面\n */\n\nimport { getStringWidth } from './stringWidth.js';\n\nexport interface Page {\n /** 页面内容行 */\n lines: string[];\n /** 此页在原始文本中的 byte offset 起始位置 */\n byteOffset: number;\n}\n\n/**\n * 将文本按终端尺寸分页\n * @param text 原始文本\n * @param width 终端宽度(列数)\n * @param height 可用行数(扣除状态栏后)\n * @param step 步进行数(用于平滑滚动,不传则默认为 height)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number, step?: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n const actualStep = step || height;\n\n // 将原始文本行按终端宽度折行\n const wrappedLines: { text: string; byteOffset: number }[] = [];\n let currentOffset = 0;\n\n for (const rawLine of rawLines) {\n const wrapped = wrapLine(rawLine, width);\n for (const line of wrapped) {\n wrappedLines.push({ text: line, byteOffset: currentOffset });\n }\n currentOffset += Buffer.byteLength(rawLine + '\\n', 'utf-8');\n }\n\n // 按高度切割为页\n for (let i = 0; i < wrappedLines.length; i += actualStep) {\n const pageLines = wrappedLines.slice(i, i + height);\n if (pageLines.length === 0) break;\n\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n\n // 已经处理完所有行\n if (i + actualStep >= wrappedLines.length) break;\n }\n\n return pages;\n}\n\n/**\n * 将一行文本按终端宽度折行\n * 处理中英文混排(中文字符宽度为 2)\n */\nexport function wrapLine(line: string, width: number): string[] {\n if (line.length === 0) return [''];\n\n const result: string[] = [];\n let currentLine = '';\n let currentWidth = 0;\n\n for (const char of line) {\n const charWidth = getStringWidth(char);\n\n if (currentWidth + charWidth > width) {\n result.push(currentLine);\n currentLine = char;\n currentWidth = charWidth;\n } else {\n currentLine += char;\n currentWidth += charWidth;\n }\n }\n\n if (currentLine.length > 0) {\n result.push(currentLine);\n }\n\n return result.length > 0 ? result : [''];\n}\n","/**\n * 章节索引业务逻辑\n * 章节索引构建与查询\n */\n\nimport { ChapterModel, type ChapterRecord } from '../db/models/Chapter.js';\n\nexport class ChapterService {\n private chapterModel = new ChapterModel();\n\n /**\n * 获取指定书籍的所有章节\n */\n getChapters(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 获取指定章节信息\n */\n getChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n return this.chapterModel.findChapter(bookId, chapterNo);\n }\n\n /**\n * 获取章节总数\n */\n getChapterCount(bookId: string): number {\n return this.chapterModel.getChapterCount(bookId);\n }\n\n /**\n * 获取指定书籍下的所有章节\n */\n getChaptersByBookId(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 根据 offset 查询当前所属章节(用于高亮当前所在章)\n */\n getChapterByOffset(bookId: string, byteOffset: number): ChapterRecord | undefined {\n const chapters = this.chapterModel.findByBookId(bookId);\n if (chapters.length === 0) return undefined;\n\n // 找到 offset 所在的章节(最后一个 byte_offset <= 给定 offset 的章节)\n let current: ChapterRecord | undefined;\n for (const chapter of chapters) {\n if (chapter.byte_offset <= byteOffset) {\n current = chapter;\n } else {\n break;\n }\n }\n return current;\n }\n}\n","/**\n * 最近阅读业务逻辑\n */\n\nimport { RecentModel } from '../db/models/Recent.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\n\nexport class RecentService {\n private recentModel = new RecentModel();\n private bookModel = new BookModel();\n\n /**\n * 获取最近阅读的书籍列表(包含书籍详情)\n */\n getRecentBooks(limit: number = 20): BookRecord[] {\n const recentRecords = this.recentModel.getRecent(limit);\n\n return recentRecords\n .map((record) => this.bookModel.findById(record.book_id))\n .filter((book): book is BookRecord => book !== undefined);\n }\n\n /**\n * 记录打开事件\n */\n recordOpen(bookId: string): void {\n this.recentModel.recordOpen(bookId);\n }\n}\n","/**\n * bookmarks 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookmarkRecord {\n id?: number;\n book_id: string;\n title: string;\n byte_offset: number;\n created_at: number;\n}\n\nexport class BookmarkModel {\n /**\n * 插入书签\n */\n insert(bookmark: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO bookmarks (book_id, title, byte_offset, created_at)\n VALUES (?, ?, ?, ?)\n `).run(bookmark.book_id, bookmark.title, bookmark.byte_offset, bookmark.created_at);\n }\n\n /**\n * 获取指定书籍的所有书签\n */\n findByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 获取指定书签\n */\n findById(id: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as BookmarkRecord | undefined;\n }\n\n /**\n * 获取书籍书签总数\n */\n getCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM bookmarks WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除书签\n */\n delete(id: number): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE id = ?').run(id);\n }\n\n /**\n * 移除整本书的书签 (配合彻底清理书籍使用)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * 书签管理服务\n * 处理针对某一位置打标记录以及后续的跳转\n */\n\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\n\nexport class BookmarkService {\n private bookmarkModel: BookmarkModel;\n\n constructor() {\n this.bookmarkModel = new BookmarkModel();\n }\n\n /**\n * 增加一条书签\n * @param title 该书签展现给用户的文案(一句话大纲)\n */\n addBookmark(bookId: string, title: string, byteOffset: number): void {\n this.bookmarkModel.insert({\n book_id: bookId,\n title,\n byte_offset: byteOffset,\n created_at: Date.now(),\n });\n }\n\n /**\n * 罗列该书全部的书签\n */\n getBookmarksByBookId(bookId: string): BookmarkRecord[] {\n return this.bookmarkModel.findByBookId(bookId);\n }\n\n /**\n * 删掉对应书签\n */\n removeBookmark(id: number): void {\n this.bookmarkModel.delete(id);\n }\n}\n","/**\n * 防打断老板键 (Boss Key)\n * 瞬间退出并用伪装日志覆盖屏幕\n */\n\nexport function triggerBossKey(): void {\n // 1. 彻底清空当前屏幕并清除滚动回放缓冲区 (Scrollback Buffer)\n // \\u001b[3J: 清除滚动条历史\n // \\u001b[2J: 清除当前可视区域\n // \\u001b[1;1H: 移动光标到左上角\n process.stdout.write('\\u001b[3J\\u001b[2J\\u001b[1;1H');\n console.clear();\n\n // 2. 打印极度逼真的伪装日志(终端报错风格)\n const fakeLog = `\nfile:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916\n throw new Error(\\`[vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\\`);\n ^\n\nError: [vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\n at Object.run (file:///Users/yindawei/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916:13)\n at async file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:722:7\n at async startVite (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:700:5)\n at async Object.handler (file:///Users/yindawei/project/node_modules/vite/dist/node/cli.js:650:1)\n\nNode.js v20.11.0\n`;\n\n console.log(fakeLog);\n\n // 3. 强制安静终止应用,不留痕迹\n process.exit(0);\n}\n","/**\n * 阅读时间估算\n */\n\n/**\n * 估算阅读时间(分钟)\n * @param charCount 字符数\n * @param isChinese 是否中文内容\n * @returns 估计阅读分钟数\n */\nexport function estimateReadingTime(charCount: number, isChinese: boolean = true): number {\n // 中文平均阅读速度约 500 字/分钟\n // 英文平均阅读速度约 250 词/分钟(约 1250 字符/分钟)\n const charsPerMinute = isChinese ? 500 : 1250;\n return Math.ceil(charCount / charsPerMinute);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatReadingTime(minutes: number): string {\n if (minutes < 60) {\n return `${minutes} 分钟`;\n }\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours} 小时 ${mins} 分钟` : `${hours} 小时`;\n}\n","/**\n * novel resume — 恢复上次阅读\n * 启动后默认入口,精确恢复到上次 offset,零摩擦\n */\n\nimport type { CommandModule } from 'yargs';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookService } from '../../services/BookService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport const resumeCommand: CommandModule = {\n command: 'resume',\n describe: t('cli.resume.desc'),\n handler: async () => {\n try {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n // 验证书籍仍然存在\n const bookService = new BookService();\n const book = bookService.findBook(lastProgress.book_id);\n if (!book) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n logger.debug(`恢复阅读: ${book.title}, offset=${lastProgress.byte_offset}`);\n\n // 启动 Ink TUI 阅读器,恢复到上次 offset\n renderApp({\n initialPage: 'reader',\n bookId: lastProgress.book_id,\n initialByteOffset: lastProgress.byte_offset,\n });\n } catch (error) {\n logger.error('恢复阅读失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel open <id|name> — 打开指定书\n * 支持 book-id 或模糊匹配书名\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface OpenArgs {\n target: string;\n}\n\nexport const openCommand: CommandModule<object, OpenArgs> = {\n command: 'open <target>',\n describe: t('cli.open.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.open.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.open.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n // 检查是否有之前的阅读进度\n const progressService = new ProgressService();\n const progress = progressService.getProgress(book.id);\n const byteOffset = progress?.byte_offset ?? 0;\n\n logger.debug(`打开: ${book.title}, offset=${byteOffset}`);\n\n // 启动 Ink TUI 阅读器\n renderApp({\n initialPage: 'reader',\n bookId: book.id,\n initialByteOffset: byteOffset,\n });\n } catch (error) {\n logger.error('打开失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel library — 书架列表\n * 含最近阅读排序 + 搜索\n */\n\nimport type { CommandModule } from 'yargs';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface LibraryArgs {\n search?: string;\n}\n\nexport const libraryCommand: CommandModule<object, LibraryArgs> = {\n command: 'list',\n aliases: ['library'],\n describe: t('cli.library.desc'),\n builder: (yargs) => {\n return yargs.option('search', {\n alias: 's',\n describe: t('cli.library.help'),\n type: 'string',\n });\n },\n handler: async (argv) => {\n try {\n // 如果有搜索参数,以非交互模式输出\n if (argv.search) {\n const bookService = new BookService();\n const books = bookService.searchBooks(argv.search);\n\n if (books.length === 0) {\n console.log(t('cli.library.search_none', argv.search));\n return;\n }\n\n console.log(t('cli.library.search_result', books.length));\n books.forEach((book, index) => {\n console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);\n });\n return;\n }\n\n // 无搜索参数,启动交互式书架\n renderApp({ initialPage: 'library' });\n } catch (error) {\n logger.error('获取书架失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel remove <id|name> — 移除书籍\n * 删除相关所有数据,包括记录和本地进度的 DB 项\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface RemoveArgs {\n target: string;\n}\n\nexport const removeCommand: CommandModule<object, RemoveArgs> = {\n command: 'remove <target>',\n describe: t('cli.remove.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.remove.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.remove.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n bookService.deleteBook(book.id);\n console.log(`${t('cli.remove.success')} ${book.title}`);\n } catch (error) {\n logger.error('移除书籍失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel lang <zh|en> — 切换语言\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig } from '../../config/AppConfig.js';\nimport { t, setLanguage } from '../../locales/index.js';\n\nexport interface LangArgs {\n target: 'zh' | 'en';\n}\n\nexport const langCommand: CommandModule<object, LangArgs> = {\n command: 'lang <target>',\n describe: t('cli.lang.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.lang.help'),\n type: 'string',\n choices: ['zh', 'en'] as const,\n demandOption: true,\n });\n },\n handler: (argv) => {\n const lang = argv.target;\n if (lang === 'zh' || lang === 'en') {\n setConfig('language', lang);\n setLanguage(lang);\n console.log(t('cli.lang.success', lang));\n } else {\n console.log(t('cli.lang.unsupported', lang));\n process.exit(1);\n }\n },\n};\n","import type { CommandModule } from 'yargs';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { t } from '../../locales/index.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const updateCommand: CommandModule = {\n command: 'update',\n describe: t('cli.update.desc'),\n handler: async () => {\n try {\n console.log(t('cli.update.checking'));\n\n // 获取本地版本号 (由 tsup 在构建时注入)\n const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0.2.2';\n\n // 获取 NPM 最新版本\n const npmOutput = execSync('npm view readshell version', { encoding: 'utf-8' });\n const latestVersion = npmOutput.trim();\n\n if (!latestVersion) {\n throw new Error('Could not fetch npm version');\n }\n\n if (latestVersion === localVersion) {\n console.log(t('cli.update.latest', localVersion));\n return;\n }\n\n console.log(t('cli.update.updating', latestVersion, localVersion));\n \n // 执行升级指令\n execSync('npm install -g readshell@latest', { stdio: 'inherit' });\n \n console.log(t('cli.update.success'));\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.log(t('cli.update.fail', msg));\n logger.error('更新失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * 数据库迁移逻辑\n * 程序启动时检查并执行建表/迁移\n */\n\nimport { getDb } from './client.js';\nimport { logger } from '../utils/logger.js';\n\nconst SCHEMA_VERSION = 2;\n\n/**\n * 初始化数据库(建表 + 版本管理)\n */\nexport function initDatabase(): void {\n const db = getDb();\n\n // 创建版本管理表\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY\n );\n `);\n\n const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < SCHEMA_VERSION) {\n logger.debug(`数据库迁移: v${currentVersion} → v${SCHEMA_VERSION}`);\n migrate(db, currentVersion);\n }\n}\n\nfunction migrate(db: ReturnType<typeof getDb>, fromVersion: number): void {\n const migrations: Record<number, string> = {\n 1: `\n -- 书籍元数据\n CREATE TABLE IF NOT EXISTS books (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT,\n file_path TEXT NOT NULL,\n format TEXT NOT NULL,\n file_hash TEXT NOT NULL,\n file_size INTEGER,\n created_at INTEGER NOT NULL\n );\n\n -- 核心状态表\n CREATE TABLE IF NOT EXISTS reading_progress (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n chapter_no INTEGER NOT NULL DEFAULT 0,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n percent REAL NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n opened_at INTEGER NOT NULL\n );\n\n -- 最近阅读排序\n CREATE TABLE IF NOT EXISTS recent_reads (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n opened_at INTEGER NOT NULL,\n open_count INTEGER NOT NULL DEFAULT 1\n );\n\n -- 章节索引\n CREATE TABLE IF NOT EXISTS chapter_index (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n chapter_no INTEGER NOT NULL,\n title TEXT,\n byte_offset INTEGER NOT NULL,\n UNIQUE(book_id, chapter_no)\n );\n\n -- 索引\n CREATE INDEX IF NOT EXISTS idx_chapter_book ON chapter_index(book_id);\n CREATE INDEX IF NOT EXISTS idx_recent_opened ON recent_reads(opened_at DESC);\n `,\n 2: `\n -- 书签管理\n CREATE TABLE IF NOT EXISTS bookmarks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n title TEXT NOT NULL,\n byte_offset INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_bookmarks_book ON bookmarks(book_id);\n `,\n };\n\n db.transaction(() => {\n for (let v = fromVersion + 1; v <= SCHEMA_VERSION; v++) {\n const sql = migrations[v];\n if (sql) {\n db.exec(sql);\n logger.debug(`已执行迁移 v${v}`);\n }\n }\n\n // 更新版本号\n db.prepare('DELETE FROM schema_version').run();\n db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);\n })();\n}\n","#!/usr/bin/env node\n\n/**\n * ReadShell — 终端内低打断轻阅读工具\n * 程序主入口,解析 argv,路由到子命令\n */\n\nimport { createParser } from './cli/parser.js';\nimport { initDatabase } from './db/migrate.js';\nimport { initI18n } from './locales/index.js';\nimport { logger } from './utils/logger.js';\n\nasync function main() {\n try {\n // 初始化数据库(自动建表/迁移)\n initDatabase();\n \n // 初始化多语言本地化模块\n initI18n();\n\n // 解析命令行参数并执行对应命令\n const parser = createParser();\n await parser.parse();\n } catch (error) {\n logger.error('程序启动失败:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","db","db","db","db","detect","iconv","resolve","detect","iconv","readFileSync","existsSync","config","statSync","resolve","join","yargs","React","useState","Box","Text","useState","useEffect","Box","Text","useApp","jsx","jsxs","useApp","useState","useEffect","Box","Text","useState","useEffect","Box","Text","useApp","Box","Text","jsx","Box","Text","jsx","jsxs","Box","Text","useState","useEffect","Box","Text","useInput","jsx","jsxs","useState","useEffect","useInput","Box","Text","useState","useInput","db","jsx","jsxs","useApp","useState","useEffect","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","db"]}
|
|
1
|
+
{"version":3,"sources":["../src/cli/parser.ts","../src/services/BookService.ts","../src/db/client.ts","../src/config/paths.ts","../src/utils/logger.ts","../src/db/models/Book.ts","../src/db/models/Chapter.ts","../src/db/models/Recent.ts","../src/db/models/Progress.ts","../src/db/models/Bookmark.ts","../src/db/models/ReadingSession.ts","../src/parsers/TxtParser.ts","../src/parsers/EpubParser.ts","../src/parsers/index.ts","../src/utils/hash.ts","../src/locales/zh.ts","../src/locales/en.ts","../src/config/AppConfig.ts","../src/locales/index.ts","../src/cli/commands/import.ts","../src/services/ProgressService.ts","../src/ui/renderApp.ts","../src/ui/App.tsx","../src/ui/pages/ResumePage.tsx","../src/services/SyncFolderService.ts","../src/ui/pages/LibraryPage.tsx","../src/ui/pages/ReaderPage.tsx","../src/ui/components/TextRenderer.tsx","../src/ui/components/StatusBar.tsx","../src/ui/components/ChapterNav.tsx","../src/ui/hooks/useReader.ts","../src/ui/hooks/useKeyboard.ts","../src/utils/stringWidth.ts","../src/utils/paginate.ts","../src/services/ChapterService.ts","../src/services/RecentService.ts","../src/services/BookmarkService.ts","../src/utils/bossKey.ts","../src/utils/time.ts","../src/utils/pendingSync.ts","../src/cli/commands/resume.ts","../src/cli/commands/open.ts","../src/cli/commands/library.ts","../src/cli/commands/remove.ts","../src/cli/commands/lang.ts","../src/cli/commands/update.ts","../src/cli/commands/config.ts","../src/cli/commands/sync.ts","../src/cli/commands/bookmarks.ts","../src/services/StatsService.ts","../src/cli/commands/stats.ts","../src/db/migrate.ts","../src/index.ts"],"sourcesContent":["/**\n * CLI 参数解析器\n * 使用 yargs 解析子命令\n */\n\nimport yargs from 'yargs';\nimport { hideBin } from 'yargs/helpers';\nimport { importCommand } from './commands/import.js';\nimport { resumeCommand } from './commands/resume.js';\nimport { openCommand } from './commands/open.js';\nimport { libraryCommand } from './commands/library.js';\nimport { removeCommand } from './commands/remove.js';\nimport { langCommand } from './commands/lang.js';\nimport { updateCommand } from './commands/update.js';\nimport { configCommand } from './commands/config.js';\nimport { syncCommand } from './commands/sync.js';\nimport { bookmarksCommand } from './commands/bookmarks.js';\nimport { statsCommand } from './commands/stats.js';\n\nexport function createParser() {\n const version = typeof APP_VERSION !== 'undefined' ? APP_VERSION : 'dev';\n\n return yargs(hideBin(process.argv))\n .scriptName('novel')\n .usage('$0 <command> [options]')\n .command(importCommand)\n .command(resumeCommand)\n .command(openCommand)\n .command(libraryCommand)\n .command(removeCommand)\n .command(langCommand)\n .command(updateCommand)\n .command(configCommand)\n .command(syncCommand)\n .command(bookmarksCommand)\n .command(statsCommand)\n .demandCommand(1, '请指定一个命令。使用 --help 查看可用命令。')\n .strict()\n .alias('h', 'help')\n .alias('v', 'version')\n .version(version)\n .epilogue('ReadShell — 终端内低打断轻阅读工具');\n}\n","/**\n * 书籍业务逻辑\n * 书籍 CRUD、导入、去重\n */\n\nimport { resolve } from 'path';\nimport { existsSync, statSync } from 'fs';\nimport { nanoid } from 'nanoid';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ChapterModel } from '../db/models/Chapter.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { ProgressModel } from '../db/models/Progress.js';\nimport { BookmarkModel } from '../db/models/Bookmark.js';\nimport { ReadingSessionModel } from '../db/models/ReadingSession.js';\nimport { parseFile } from '../parsers/index.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\nimport { t } from '../locales/index.js';\n\nexport class BookService {\n private bookModel = new BookModel();\n private chapterModel = new ChapterModel();\n private recentModel = new RecentModel();\n private progressModel = new ProgressModel();\n\n /**\n * 导入书籍文件\n */\n async importBook(filePath: string): Promise<BookRecord> {\n const absPath = resolve(filePath);\n\n // 检查文件存在\n if (!existsSync(absPath)) {\n throw new Error(`文件不存在: ${absPath}`);\n }\n\n // 检测文件格式\n const format = this.detectFormat(absPath);\n if (!format) {\n throw new Error(t('cli.import.unsupported'));\n }\n\n // 计算文件 hash 用于去重\n const fileHash = await computeFileHash(absPath);\n const existing = this.bookModel.findByHash(fileHash);\n if (existing) {\n logger.debug(`文件已存在: ${existing.title} (${existing.id})`);\n return existing;\n }\n\n // 解析文件\n const parsed = await parseFile(absPath, format);\n\n // 获取文件大小\n const stats = statSync(absPath);\n\n // 创建书籍记录\n const book: BookRecord = {\n id: nanoid(),\n title: parsed.title,\n author: parsed.author || null,\n file_path: absPath,\n format,\n file_hash: fileHash,\n file_size: stats.size,\n created_at: Date.now(),\n };\n\n this.bookModel.insert(book);\n\n // 保存章节索引\n if (parsed.chapters.length > 0) {\n this.chapterModel.insertMany(\n parsed.chapters.map((ch, idx) => ({\n book_id: book.id,\n chapter_no: idx,\n title: ch.title,\n byte_offset: ch.byteOffset,\n })),\n );\n }\n\n logger.debug(`导入成功: ${book.title}`);\n return book;\n }\n\n /**\n * 查找书籍(ID 或模糊匹配书名)\n */\n findBook(target: string): BookRecord | undefined {\n // 先尝试精确 ID 匹配\n const byId = this.bookModel.findById(target);\n if (byId) return byId;\n\n // 再尝试书名模糊匹配\n const results = this.bookModel.searchByTitle(target);\n return results[0];\n }\n\n /**\n * 搜索书籍\n */\n searchBooks(keyword: string): BookRecord[] {\n return this.bookModel.searchByTitle(keyword);\n }\n\n /**\n * 获取所有书籍\n */\n getAllBooks(): BookRecord[] {\n return this.bookModel.findAll();\n }\n\n /**\n * 删除书籍及相关数据\n */\n deleteBook(id: string): void {\n this.chapterModel.deleteByBookId(id);\n this.progressModel.delete(id);\n this.recentModel.delete(id);\n new BookmarkModel().deleteByBookId(id);\n new ReadingSessionModel().deleteByBookId(id);\n this.bookModel.delete(id);\n }\n\n /**\n * 检测文件格式\n */\n private detectFormat(filePath: string): 'txt' | 'epub' | 'md' | null {\n const ext = filePath.toLowerCase().split('.').pop();\n if (ext === 'txt') return 'txt';\n if (ext === 'epub') return 'epub';\n if (ext === 'md') return 'md';\n return null;\n }\n}\n","/**\n * SQLite 数据库连接初始化\n * 使用 better-sqlite3 同步 API\n */\n\nimport Database from 'better-sqlite3';\nimport { getDbPath } from '../config/paths.js';\nimport { logger } from '../utils/logger.js';\n\nlet db: Database.Database | null = null;\n\n/**\n * 获取数据库连接实例(单例模式)\n */\nexport function getDb(): Database.Database {\n if (!db) {\n const dbPath = getDbPath();\n logger.debug(`数据库路径: ${dbPath}`);\n\n db = new Database(dbPath);\n\n // 启用 WAL 模式以提升写入性能\n db.pragma('journal_mode = WAL');\n // 启用外键约束\n db.pragma('foreign_keys = ON');\n }\n\n return db;\n}\n\n/**\n * 关闭数据库连接\n */\nexport function closeDb(): void {\n if (db) {\n db.close();\n db = null;\n logger.debug('数据库连接已关闭');\n }\n}\n\n// 进程退出时自动关闭数据库\nprocess.on('exit', () => closeDb());\nprocess.on('SIGINT', () => {\n closeDb();\n process.exit(0);\n});\nprocess.on('SIGTERM', () => {\n closeDb();\n process.exit(0);\n});\n","/**\n * 跨平台路径管理\n * 数据库、配置文件位置\n */\n\nimport { join } from 'path';\nimport { homedir } from 'os';\nimport { mkdirSync, existsSync } from 'fs';\n\n/**\n * 获取应用数据目录\n * 默认 ~/.config/readshell/(macOS 为 ~/Library/Application Support/readshell),\n * 可用环境变量 READSHELL_HOME 覆盖(测试或多实例隔离用)\n */\nexport function getAppDataDir(): string {\n const override = process.env['READSHELL_HOME'];\n let configDir: string;\n\n if (override) {\n configDir = override;\n } else {\n const platform = process.platform;\n if (platform === 'darwin') {\n configDir = join(homedir(), 'Library', 'Application Support', 'readshell');\n } else if (platform === 'win32') {\n configDir = join(process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming'), 'readshell');\n } else {\n // Linux / 其他\n configDir = join(process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config'), 'readshell');\n }\n }\n\n // 确保目录存在\n if (!existsSync(configDir)) {\n mkdirSync(configDir, { recursive: true });\n }\n\n return configDir;\n}\n\n/**\n * 获取数据库文件路径\n */\nexport function getDbPath(): string {\n return join(getAppDataDir(), 'readshell.db');\n}\n\n/**\n * 获取配置文件路径\n */\nexport function getConfigPath(): string {\n return join(getAppDataDir(), 'config.json');\n}\n","/**\n * 调试日志\n * 仅 DEBUG=1 时输出\n */\n\nconst isDebug = process.env['DEBUG'] === '1' || process.env['DEBUG'] === 'true';\n\nexport const logger = {\n debug: (...args: unknown[]): void => {\n if (isDebug) {\n console.error('[DEBUG]', ...args);\n }\n },\n\n info: (...args: unknown[]): void => {\n console.error('[INFO]', ...args);\n },\n\n warn: (...args: unknown[]): void => {\n console.error('[WARN]', ...args);\n },\n\n error: (...args: unknown[]): void => {\n console.error('[ERROR]', ...args);\n },\n};\n","/**\n * books 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookRecord {\n id: string;\n title: string;\n author: string | null;\n file_path: string;\n format: 'txt' | 'epub' | 'md';\n file_hash: string;\n file_size: number | null;\n created_at: number;\n}\n\nexport class BookModel {\n /**\n * 插入新书\n */\n insert(book: BookRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO books (id, title, author, file_path, format, file_hash, file_size, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `).run(book.id, book.title, book.author, book.file_path, book.format, book.file_hash, book.file_size, book.created_at);\n }\n\n /**\n * 通过 ID 获取书籍\n */\n findById(id: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE id = ?').get(id) as BookRecord | undefined;\n }\n\n /**\n * 通过文件 hash 查找(去重用)\n */\n findByHash(hash: string): BookRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE file_hash = ?').get(hash) as BookRecord | undefined;\n }\n\n /**\n * 模糊搜索书名\n */\n searchByTitle(keyword: string): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books WHERE title LIKE ? ORDER BY created_at DESC').all(`%${keyword}%`) as BookRecord[];\n }\n\n /**\n * 获取所有书籍\n */\n findAll(): BookRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM books ORDER BY created_at DESC').all() as BookRecord[];\n }\n\n /**\n * 更新书名/作者(文件夹同步导入后恢复远端元数据用)\n */\n updateMeta(id: string, title: string, author: string | null): void {\n const db = getDb();\n db.prepare('UPDATE books SET title = ?, author = ? WHERE id = ?').run(title, author, id);\n }\n\n /**\n * 删除书籍\n */\n delete(id: string): void {\n const db = getDb();\n db.prepare('DELETE FROM books WHERE id = ?').run(id);\n }\n}\n","/**\n * chapter_index 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ChapterRecord {\n id?: number;\n book_id: string;\n chapter_no: number;\n title: string | null;\n byte_offset: number;\n}\n\nexport class ChapterModel {\n /**\n * 批量插入章节索引\n */\n insertMany(chapters: Omit<ChapterRecord, 'id'>[]): void {\n const db = getDb();\n const stmt = db.prepare(`\n INSERT OR REPLACE INTO chapter_index (book_id, chapter_no, title, byte_offset)\n VALUES (?, ?, ?, ?)\n `);\n\n db.transaction(() => {\n for (const chapter of chapters) {\n stmt.run(chapter.book_id, chapter.chapter_no, chapter.title, chapter.byte_offset);\n }\n })();\n }\n\n /**\n * 获取指定书籍的所有章节\n */\n findByBookId(bookId: string): ChapterRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? ORDER BY chapter_no').all(bookId) as ChapterRecord[];\n }\n\n /**\n * 获取指定章节\n */\n findChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM chapter_index WHERE book_id = ? AND chapter_no = ?').get(bookId, chapterNo) as ChapterRecord | undefined;\n }\n\n /**\n * 获取书籍章节总数\n */\n getChapterCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM chapter_index WHERE book_id = ?').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除指定书籍的章节索引\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM chapter_index WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * recent_reads 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface RecentRecord {\n book_id: string;\n opened_at: number;\n open_count: number;\n}\n\nexport class RecentModel {\n /**\n * 记录打开(插入或更新计数)\n */\n recordOpen(bookId: string): void {\n const db = getDb();\n const now = Date.now();\n db.prepare(`\n INSERT INTO recent_reads (book_id, opened_at, open_count)\n VALUES (?, ?, 1)\n ON CONFLICT(book_id) DO UPDATE SET\n opened_at = excluded.opened_at,\n open_count = open_count + 1\n `).run(bookId, now);\n }\n\n /**\n * 获取最近阅读列表(按时间倒序)\n */\n getRecent(limit: number = 20): RecentRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM recent_reads ORDER BY opened_at DESC LIMIT ?').all(limit) as RecentRecord[];\n }\n\n /**\n * 删除记录\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM recent_reads WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_progress 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ProgressRecord {\n book_id: string;\n chapter_no: number;\n byte_offset: number;\n percent: number;\n updated_at: number;\n opened_at: number;\n}\n\nexport class ProgressModel {\n /**\n * 保存或更新阅读进度\n */\n upsert(progress: ProgressRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_progress (book_id, chapter_no, byte_offset, percent, updated_at, opened_at)\n VALUES (?, ?, ?, ?, ?, ?)\n ON CONFLICT(book_id) DO UPDATE SET\n chapter_no = excluded.chapter_no,\n byte_offset = excluded.byte_offset,\n percent = excluded.percent,\n updated_at = excluded.updated_at,\n opened_at = excluded.opened_at\n `).run(progress.book_id, progress.chapter_no, progress.byte_offset, progress.percent, progress.updated_at, progress.opened_at);\n }\n\n /**\n * 获取指定书籍的阅读进度\n */\n findByBookId(bookId: string): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress WHERE book_id = ?').get(bookId) as ProgressRecord | undefined;\n }\n\n /**\n * 获取最近打开的书籍进度(用于 resume)\n */\n getLastOpened(): ProgressRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_progress ORDER BY opened_at DESC LIMIT 1').get() as ProgressRecord | undefined;\n }\n\n /**\n * 删除指定书籍的进度\n */\n delete(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_progress WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * bookmarks 表数据操作\n * deleted=1 的行为墓碑记录:本地删除不物理移除,供文件夹同步传播删除事件\n */\n\nimport { getDb } from '../client.js';\n\nexport interface BookmarkRecord {\n id?: number;\n book_id: string;\n title: string;\n byte_offset: number;\n created_at: number;\n /** 最近一次变更(新增/删除)时间,同步 LWW 依据 */\n updated_at: number;\n /** 1 = 墓碑(已删除) */\n deleted: number;\n}\n\nexport class BookmarkModel {\n /**\n * 插入书签\n */\n insert(bookmark: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO bookmarks (book_id, title, byte_offset, created_at, updated_at, deleted)\n VALUES (?, ?, ?, ?, ?, ?)\n `).run(\n bookmark.book_id,\n bookmark.title,\n bookmark.byte_offset,\n bookmark.created_at,\n bookmark.updated_at,\n bookmark.deleted,\n );\n }\n\n /**\n * 获取指定书籍的所有有效书签(不含墓碑)\n */\n findByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? AND deleted = 0 ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 获取指定书籍的全部书签记录(含墓碑,同步导出用)\n */\n findAllByBookId(bookId: string): BookmarkRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? ORDER BY created_at DESC').all(bookId) as BookmarkRecord[];\n }\n\n /**\n * 按 (book_id, byte_offset) 查找有效书签\n */\n findByOffset(bookId: string, byteOffset: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? AND byte_offset = ? AND deleted = 0').get(bookId, byteOffset) as BookmarkRecord | undefined;\n }\n\n /**\n * 按 (book_id, byte_offset) 查找书签记录(含墓碑,同步合并用)\n */\n findByOffsetAny(bookId: string, byteOffset: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE book_id = ? AND byte_offset = ?').get(bookId, byteOffset) as BookmarkRecord | undefined;\n }\n\n /**\n * 按 (book_id, byte_offset) 软删除书签(写墓碑,同步传播删除用)\n */\n deleteByOffset(bookId: string, byteOffset: number): void {\n const db = getDb();\n db.prepare('UPDATE bookmarks SET deleted = 1, updated_at = ? WHERE book_id = ? AND byte_offset = ? AND deleted = 0')\n .run(Date.now(), bookId, byteOffset);\n }\n\n /**\n * 同步合并:按 (book_id, byte_offset) 覆盖写入一条书签记录(可为墓碑)\n */\n saveByOffset(record: Omit<BookmarkRecord, 'id'>): void {\n const db = getDb();\n const existing = this.findByOffsetAny(record.book_id, record.byte_offset);\n if (existing?.id !== undefined) {\n db.prepare(`\n UPDATE bookmarks SET title = ?, created_at = ?, updated_at = ?, deleted = ?\n WHERE id = ?\n `).run(record.title, record.created_at, record.updated_at, record.deleted, existing.id);\n } else {\n this.insert(record);\n }\n }\n\n /**\n * 获取指定书签\n */\n findById(id: number): BookmarkRecord | undefined {\n const db = getDb();\n return db.prepare('SELECT * FROM bookmarks WHERE id = ?').get(id) as BookmarkRecord | undefined;\n }\n\n /**\n * 获取书籍有效书签总数\n */\n getCount(bookId: string): number {\n const db = getDb();\n const result = db.prepare('SELECT COUNT(*) as count FROM bookmarks WHERE book_id = ? AND deleted = 0').get(bookId) as { count: number };\n return result.count;\n }\n\n /**\n * 删除书签(写墓碑)\n */\n delete(id: number): void {\n const db = getDb();\n db.prepare('UPDATE bookmarks SET deleted = 1, updated_at = ? WHERE id = ? AND deleted = 0').run(Date.now(), id);\n }\n\n /**\n * 移除整本书的书签 (配合彻底清理书籍使用,物理删除含墓碑)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM bookmarks WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * reading_sessions 表数据操作\n */\n\nimport { getDb } from '../client.js';\n\nexport interface ReadingSessionRecord {\n id: string;\n book_id: string;\n started_at: number;\n ended_at: number;\n bytes_read: number;\n}\n\nexport class ReadingSessionModel {\n /**\n * 记录一次阅读会话\n */\n insert(session: ReadingSessionRecord): void {\n const db = getDb();\n db.prepare(`\n INSERT INTO reading_sessions (id, book_id, started_at, ended_at, bytes_read)\n VALUES (?, ?, ?, ?, ?)\n `).run(session.id, session.book_id, session.started_at, session.ended_at, session.bytes_read);\n }\n\n /**\n * 按 id 合并远端会话(已存在则跳过)\n */\n insertIfAbsent(session: ReadingSessionRecord): boolean {\n const db = getDb();\n const result = db.prepare(`\n INSERT OR IGNORE INTO reading_sessions (id, book_id, started_at, ended_at, bytes_read)\n VALUES (?, ?, ?, ?, ?)\n `).run(session.id, session.book_id, session.started_at, session.ended_at, session.bytes_read);\n return result.changes > 0;\n }\n\n /**\n * 获取指定书籍的全部会话\n */\n findByBookId(bookId: string): ReadingSessionRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_sessions WHERE book_id = ? ORDER BY started_at ASC').all(bookId) as ReadingSessionRecord[];\n }\n\n /**\n * 获取全部会话(统计与同步导出用)\n */\n findAll(): ReadingSessionRecord[] {\n const db = getDb();\n return db.prepare('SELECT * FROM reading_sessions ORDER BY started_at ASC').all() as ReadingSessionRecord[];\n }\n\n /**\n * 删除指定书籍的全部会话(配合彻底清理书籍使用)\n */\n deleteByBookId(bookId: string): void {\n const db = getDb();\n db.prepare('DELETE FROM reading_sessions WHERE book_id = ?').run(bookId);\n }\n}\n","/**\n * TXT 文件解析器\n * 编码检测、章节切割\n */\n\nimport { readFileSync } from 'fs';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\n\nexport interface ParsedChapter {\n title: string;\n byteOffset: number;\n}\n\nexport interface ParsedBook {\n title: string;\n author: string | null;\n content: string;\n chapters: ParsedChapter[];\n}\n\n// 常见的章节标题正则\nconst CHAPTER_PATTERNS = [\n /^第[零一二三四五六七八九十百千万\\d]+[章节回卷集部篇]/m,\n /^Chapter\\s+\\d+/im,\n /^CHAPTER\\s+\\d+/m,\n /^第\\s*\\d+\\s*[章节回]/m,\n];\n\n/**\n * 解析 TXT 文件\n */\nexport async function parseTxt(filePath: string): Promise<ParsedBook> {\n // 读取原始字节\n const buffer = readFileSync(filePath);\n\n // 自动检测编码\n const encoding = detect(buffer) || 'utf-8';\n logger.debug(`检测到编码: ${encoding}`);\n\n // 转换为 UTF-8 字符串\n const content = encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n // 从文件名提取标题\n const title = extractTitle(filePath);\n\n // 提取章节索引\n const chapters = extractChapters(content);\n\n return {\n title,\n author: null,\n content,\n chapters,\n };\n}\n\n/**\n * 从文件名提取书名\n */\nfunction extractTitle(filePath: string): string {\n const basename = filePath.split('/').pop() || filePath;\n // 去掉扩展名\n return basename.replace(/\\.(txt|md)$/i, '').trim() || '未命名';\n}\n\n/**\n * 提取章节索引\n */\nfunction extractChapters(content: string): ParsedChapter[] {\n const chapters: ParsedChapter[] = [];\n const lines = content.split('\\n');\n let byteOffset = 0;\n\n for (const line of lines) {\n const trimmed = line.trim();\n\n for (const pattern of CHAPTER_PATTERNS) {\n if (pattern.test(trimmed)) {\n chapters.push({\n title: trimmed,\n byteOffset,\n });\n break;\n }\n }\n\n // 计算 byte offset(UTF-8 编码)\n byteOffset += Buffer.byteLength(line + '\\n', 'utf-8');\n }\n\n logger.debug(`检测到 ${chapters.length} 个章节`);\n return chapters;\n}\n","/**\n * EPUB 文件解析器\n * 元数据 + 章节提取,并转为纯文本阅读格式\n */\n\nimport { EPub } from 'epub2';\nimport { convert } from 'html-to-text';\nimport { detect } from 'chardet';\nimport iconv from 'iconv-lite';\nimport { logger } from '../utils/logger.js';\nimport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 解析 EPUB 文件\n */\nexport async function parseEpub(filePath: string): Promise<ParsedBook> {\n logger.debug(`开始解析 EPUB: ${filePath}`);\n\n const epub = await openEpub(filePath);\n\n const title = epub.metadata.title || '未知书名';\n const author = epub.metadata.creator || null;\n\n const chapters: ParsedChapter[] = [];\n let fullContent = '';\n let currentByteOffset = 0;\n\n // epub.flow contains the reading order\n for (const chapterRef of epub.flow) {\n if (!chapterRef.id) continue;\n\n try {\n const htmlText = await getChapterHtml(epub, chapterRef.id);\n \n // 使用 html-to-text 转为纯文本,保持换行,去除无关标签\n const plainText = convert(htmlText, {\n wordwrap: false,\n selectors: [\n // 忽略图片和链接\n { selector: 'img', format: 'skip' },\n { selector: 'a', options: { ignoreHref: true } },\n ],\n });\n\n if (!plainText.trim()) continue; // 跳过空章节\n\n const chapterTitle = chapterRef.title || '无标题章节';\n\n chapters.push({\n title: chapterTitle,\n byteOffset: currentByteOffset,\n });\n\n // 加上章节标题作为正文本页头部\n const chapterContent = `\\n\\n${chapterTitle}\\n\\n${plainText}\\n`;\n fullContent += chapterContent;\n\n currentByteOffset += Buffer.byteLength(chapterContent, 'utf-8');\n } catch (err) {\n logger.debug(`警告: 读取章节 ${chapterRef.id} 失败`, err);\n }\n }\n\n return {\n title,\n author,\n content: fullContent,\n chapters,\n };\n}\n\n/**\n * 提取 EPUB 元数据\n */\nexport async function getEpubMetadata(filePath: string): Promise<{ title: string; author: string | null }> {\n const epub = await openEpub(filePath);\n return {\n title: epub.metadata.title || '未知',\n author: epub.metadata.creator || null,\n };\n}\n\n// ============== 辅助函数 ==============\n\n/**\n * 包装 epub2 基于回调的初始化过程为 Promise\n */\nfunction openEpub(filePath: string): Promise<EPub> {\n return new Promise((resolve, reject) => {\n const epub = new EPub(filePath);\n epub.on('error', (err) => reject(err));\n epub.on('end', () => resolve(epub));\n epub.parse();\n });\n}\n\n/**\n * 从 HTML/XML 原始字节中提取编码声明\n * 优先读取 XML 声明 (<?xml encoding=\"GBK\">) 和 meta charset\n */\nfunction detectHtmlEncoding(buffer: Buffer): string {\n // 用 latin1 无损读取前 1KB,仅为提取编码声明\n const head = buffer.slice(0, 1024).toString('latin1');\n\n // 1. 优先查找 XML 声明:<?xml version=\"1.0\" encoding=\"GBK\"?>\n const xmlDecl = head.match(/<\\?xml[^>]*encoding=[\"']([^\"']+)[\"']/i);\n if (xmlDecl?.[1]) return xmlDecl[1];\n\n // 2. 查找 HTML meta charset:<meta charset=\"GBK\"> 或 Content-Type: charset=GBK\n const metaCharset = head.match(/charset=[\"']?([A-Za-z0-9_-]+)[\"']?/i);\n if (metaCharset?.[1] && metaCharset[1].toLowerCase() !== 'utf-8') {\n return metaCharset[1];\n }\n\n // 3. 回退:使用 chardet 字节模式探测\n const chardetResult = detect(buffer);\n return chardetResult || 'utf-8';\n}\n\n/**\n * 获取单个章节的 HTML 原文并自动转码\n * 包装为 Promise\n */\nfunction getChapterHtml(epub: any, chapterId: string): Promise<string> {\n return new Promise((resolve, reject) => {\n // 使用 getFile 获取原始 Buffer,避免 epub2 内部 getChapterRaw 强制转 utf-8 导致的乱码\n epub.getFile(chapterId, (err: Error | null, data: Buffer) => {\n if (err) return reject(err);\n\n const buffer = data;\n const encoding = detectHtmlEncoding(buffer);\n\n const htmlText = encoding.toLowerCase().replace('-', '') === 'utf8' || encoding.toLowerCase() === 'utf-8'\n ? buffer.toString('utf-8')\n : iconv.decode(buffer, encoding);\n\n resolve(htmlText);\n });\n });\n}\n","/**\n * 解析器分发\n * 按文件格式分发到对应解析器\n */\n\nimport { parseTxt, type ParsedBook } from './TxtParser.js';\nimport { parseEpub } from './EpubParser.js';\n\nexport type { ParsedBook, ParsedChapter } from './TxtParser.js';\n\n/**\n * 根据格式解析文件\n */\nexport async function parseFile(filePath: string, format: 'txt' | 'epub' | 'md'): Promise<ParsedBook> {\n switch (format) {\n case 'txt':\n case 'md':\n // Markdown 按纯文本处理\n return parseTxt(filePath);\n case 'epub':\n return parseEpub(filePath);\n default:\n throw new Error(`不支持的文件格式: ${format}`);\n }\n}\n","/**\n * 文件 hash 计算(去重用)\n */\n\nimport { createHash } from 'crypto';\nimport { readFileSync } from 'fs';\n\n/**\n * 计算文件的 SHA-256 hash\n */\nexport async function computeFileHash(filePath: string): Promise<string> {\n const buffer = readFileSync(filePath);\n const hash = createHash('sha256');\n hash.update(buffer);\n return hash.digest('hex');\n}\n","export default {\n // Common\n 'common.yes': '是',\n 'common.no': '否',\n 'common.confirm': '确认',\n 'common.cancel': '取消',\n 'common.quit': '按 q 退出',\n\n // CLI\n 'cli.import.desc': '导入本地文件或目录到书架',\n 'cli.import.help': '文件或目录路径(支持 .txt / .epub / .md)',\n 'cli.import.success': '✓ 已导入:',\n 'cli.import.fail': '导入失败:',\n 'cli.import.not_found': '路径不存在:',\n 'cli.import.unsupported': '不支持的文件格式。目前支持: .txt, .epub, .md',\n 'cli.import.scan_dir': '扫描目录',\n 'cli.import.found_files': '找到以下书籍:',\n 'cli.import.confirm_batch': '是否确认导入上述 {0} 本书?(y/N)',\n 'cli.import.canceled': '✓ 导入已取消',\n\n 'cli.resume.desc': '恢复上次阅读',\n 'cli.resume.none': '✗ 没有找到最近阅读记录,请先使用 novel import <file> 或 novel open <book-id> 打开一本书。',\n\n 'cli.open.desc': '打开指定书籍',\n 'cli.open.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.open.not_found': '✗ 未找到匹配书籍:',\n\n 'cli.library.desc': '查看书架书籍列表',\n 'cli.library.help': '可选关键字搜索',\n 'cli.library.none': '✗ 书架为空。使用 novel import <file> 导入你的第一本书。',\n 'cli.library.search_none': '📚 未找到匹配「{0}」的书籍。',\n 'cli.library.search_result': '📚 搜索结果 ({0} 本):\\n',\n\n 'cli.remove.desc': '从书架移除书籍(仅删除记录,不删源文件)',\n 'cli.remove.help': '书籍 ID 或书名(支持模糊匹配)',\n 'cli.remove.not_found': '✗ 未找到匹配书籍:',\n 'cli.remove.success': '✓ 已移除书籍:',\n 'cli.remove.fail': '移除书籍失败:',\n\n 'cli.lang.desc': '切换界面语言',\n 'cli.lang.help': '目标语言(zh 中文 | en 英文)',\n 'cli.lang.success': '✓ 语言已切换为: {0}',\n 'cli.lang.unsupported': '✗ 不支持的语言: {0}',\n\n 'cli.config.desc': '修改应用配置',\n 'cli.config.line_spacing.desc': '行间距: 0 (紧凑) | 1 (适中) | 2 (宽松)',\n 'cli.config.line_spacing.success': '✓ 行间距已设置为: {0}',\n 'cli.config.reading_mode.desc': '阅读模式: page (翻页) | scroll (滚动)',\n\n 'cli.update.desc': '检查最新版本并自动更新',\n 'cli.update.checking': '正在检查更新...',\n 'cli.update.latest': '✓ 当前已是最新版本 (v{0})',\n 'cli.update.updating': '发现新版本 v{0} (当前 v{1}),正在更新...',\n 'cli.update.success': '✓ 升级成功!请重新运行 novel 命令。',\n 'cli.update.fail': '✗ 升级失败: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 加载书架...',\n 'tui.lib.empty.title': '📚 书架',\n 'tui.lib.empty.desc': '书架为空。使用 novel import <file> 导入你的第一本书。',\n 'tui.lib.title': '📚 书架 ({0} 本)',\n 'tui.lib.tips': ' ↑↓/jk 选择 · Enter 打开 · d/x 删除 · q 退出',\n\n // TUI - Reader\n 'tui.reader.loading': '读取中...',\n 'tui.reader.bookmark_add': '✓ 增加书签: {0}',\n 'tui.reader.status.remaining': '预计剩余 {0}',\n\n 'tui.nav.tab.chapters': '[全部章节]',\n 'tui.nav.tab.bookmarks': '[我的书签]',\n 'tui.nav.tips': 'Enter 跳转 · Tab 切换 · Esc/q 关闭',\n 'tui.nav.hint': '按 C 键弹出此清单,按 Tab 切换书签',\n 'tui.nav.empty': '没有记录',\n 'tui.nav.page': '第 {0} / {1} 页',\n\n // TUI - Help\n 'tui.help.title': '快捷键',\n 'tui.help.next': ' Space / j / ↓ / f 下一页',\n 'tui.help.prev': ' k / ↑ 上一页 · g 首页 · G 末页',\n 'tui.help.nav': ' c 章节与书签 · Tab 切换 · m 加书签',\n 'tui.help.boss': ' b / Esc 老板键 · q 退出 · ? 帮助',\n\n // Sync / Stats / Bookmarks\n 'cli.sync.desc': '通过同步文件夹在多设备间同步进度、书签与阅读统计',\n 'cli.sync.dir': '同步目录路径(如 iCloud Drive / Dropbox / 坚果云 内的文件夹)',\n 'cli.sync.with_books': '同时同步书源文件(其他设备可自动导入缺失的书)',\n 'cli.sync.off': '关闭文件夹同步',\n 'cli.sync.disabled': '✓ 已关闭文件夹同步',\n 'cli.sync.dir_set': '✓ 同步目录已设置: {0}',\n 'cli.sync.dir_invalid': '✗ 目录不存在或不是文件夹: {0}',\n 'cli.sync.no_dir': '尚未设置同步目录。请先运行: novel sync --dir <路径>',\n 'cli.sync.running': '同步中...',\n 'cli.sync.success': '✓ 同步完成(合并 {0} 条,来自 {1} 台设备)',\n 'cli.sync.failed': '同步失败:{0}',\n 'cli.stats.desc': '阅读统计:热力图与连续天数',\n 'cli.stats.title': '📊 阅读统计',\n 'cli.stats.less': '少',\n 'cli.stats.more': '多',\n 'cli.stats.summary': '阅读天数 {0} · 总时长 {1} 分钟 · 读过 {2} 本 · 当前连续 {3} 天 · 最长连续 {4} 天',\n 'cli.bookmarks.desc': '书签管理',\n 'cli.bookmarks.export.desc': '导出全部书签',\n 'cli.bookmarks.export.format': '输出格式: json | md',\n 'cli.bookmarks.export.out': '输出文件路径(默认打印到终端)',\n 'cli.bookmarks.export.empty': '没有书签可导出',\n 'cli.bookmarks.export.success': '✓ 已导出 {0} 条书签到 {1}',\n 'cli.bookmarks.export.fail': '导出书签失败: {0}',\n};\n","import type { LocaleDictionary } from './types.js';\n\nconst en: LocaleDictionary = {\n // Common\n 'common.yes': 'Yes',\n 'common.no': 'No',\n 'common.confirm': 'Confirm',\n 'common.cancel': 'Cancel',\n 'common.quit': 'Press q to quit',\n\n // CLI\n 'cli.import.desc': 'Import local file or directory to library',\n 'cli.import.help': 'File or directory path (supports .txt / .epub / .md)',\n 'cli.import.success': '✓ Imported:',\n 'cli.import.fail': 'Import failed:',\n 'cli.import.not_found': 'Path not found:',\n 'cli.import.unsupported': 'Unsupported file format. Currently supports: .txt, .epub, .md',\n 'cli.import.scan_dir': 'Scanning directory',\n 'cli.import.found_files': 'Found following books:',\n 'cli.import.confirm_batch': 'Confirm importing these {0} books? (y/N)',\n 'cli.import.canceled': '✓ Import canceled',\n\n 'cli.resume.desc': 'Resume last reading',\n 'cli.resume.none': '✗ No recent reading record found. Please use `novel import <file>` or `novel open <book-id>` first.',\n\n 'cli.open.desc': 'Open specific book',\n 'cli.open.help': 'Book ID or title (fuzzy match)',\n 'cli.open.not_found': '✗ Book not found:',\n\n 'cli.library.desc': 'View book list / library',\n 'cli.library.help': 'Optional keyword search',\n 'cli.library.none': '✗ Library is empty. Use `novel import <file>` to import your first book.',\n 'cli.library.search_none': '📚 No books found matching \"{0}\".',\n 'cli.library.search_result': '📚 Search results ({0} books):\\n',\n\n 'cli.remove.desc': 'Remove book from library (only deletes records, not source file)',\n 'cli.remove.help': 'Book ID or title (fuzzy match)',\n 'cli.remove.not_found': '✗ Book not found:',\n 'cli.remove.success': '✓ Book removed:',\n 'cli.remove.fail': 'Failed to remove book:',\n\n 'cli.lang.desc': 'Switch interface language',\n 'cli.lang.help': 'Target language (zh Chinese | en English)',\n 'cli.lang.success': '✓ Language switched to: {0}',\n 'cli.lang.unsupported': '✗ Unsupported language: {0}',\n\n 'cli.config.desc': 'Modify application configuration',\n 'cli.config.line_spacing.desc': 'Line spacing: 0 (tight) | 1 (medium) | 2 (loose)',\n 'cli.config.line_spacing.success': '✓ Line spacing set to: {0}',\n 'cli.config.reading_mode.desc': 'Reading mode: page (paging) | scroll (scrolling)',\n\n 'cli.update.desc': 'Check for the latest version and update automatically',\n 'cli.update.checking': 'Checking for updates...',\n 'cli.update.latest': '✓ Already up to date (v{0})',\n 'cli.update.updating': 'New version v{0} found (current v{1}), updating...',\n 'cli.update.success': '✓ Successfully updated! Please restart novel command.',\n 'cli.update.fail': '✗ Update failed: {0}',\n\n // TUI - Library\n 'tui.lib.loading': '📚 Loading library...',\n 'tui.lib.empty.title': '📚 Library',\n 'tui.lib.empty.desc': 'Library is empty. Use `novel import <file>` to import your first book.',\n 'tui.lib.title': '📚 Library ({0} books)',\n 'tui.lib.tips': ' ↑↓/jk select · Enter open · d/x delete · q quit',\n\n // TUI - Reader\n 'tui.reader.loading': 'Loading...',\n 'tui.reader.bookmark_add': '✓ Bookmark added: {0}',\n 'tui.reader.status.remaining': 'Est. remaining {0}',\n\n // TUI - ChapterNav\n 'tui.nav.tab.chapters': '[All Chapters]',\n 'tui.nav.tab.bookmarks': '[My Bookmarks]',\n 'tui.nav.tips': 'Enter jump · Tab switch · Esc/q close',\n 'tui.nav.hint': 'Press C to open this list, press Tab to switch to bookmarks',\n 'tui.nav.empty': 'No records',\n 'tui.nav.page': 'Page {0} / {1}',\n\n // TUI - Help\n 'tui.help.title': 'Keyboard shortcuts',\n 'tui.help.next': ' Space / j / ↓ / f next page',\n 'tui.help.prev': ' k / ↑ prev page · g first · G last',\n 'tui.help.nav': ' c chapters & bookmarks · Tab switch · m bookmark',\n 'tui.help.boss': ' b / Esc boss key · q quit · ? help',\n\n // Sync / Stats / Bookmarks\n 'cli.sync.desc': 'Sync progress, bookmarks and reading stats across devices via a synced folder',\n 'cli.sync.dir': 'Sync folder path (e.g. a folder inside iCloud Drive / Dropbox / Syncthing)',\n 'cli.sync.with_books': 'Also sync book files (other devices auto-import missing books)',\n 'cli.sync.off': 'Disable folder sync',\n 'cli.sync.disabled': '✓ Folder sync disabled',\n 'cli.sync.dir_set': '✓ Sync folder set: {0}',\n 'cli.sync.dir_invalid': '✗ Path does not exist or is not a directory: {0}',\n 'cli.sync.no_dir': 'No sync folder configured. Run: novel sync --dir <path>',\n 'cli.sync.running': 'Syncing...',\n 'cli.sync.success': '✓ Sync complete (merged {0} items from {1} devices)',\n 'cli.sync.failed': 'Sync failed: {0}',\n 'cli.stats.desc': 'Reading stats: heatmap and streaks',\n 'cli.stats.title': '📊 Reading Stats',\n 'cli.stats.less': 'Less',\n 'cli.stats.more': 'More',\n 'cli.stats.summary': '{0} reading days · {1} minutes total · {2} books · current streak {3}d · longest {4}d',\n 'cli.bookmarks.desc': 'Manage bookmarks',\n 'cli.bookmarks.export.desc': 'Export all bookmarks',\n 'cli.bookmarks.export.format': 'Output format: json | md',\n 'cli.bookmarks.export.out': 'Output file path (defaults to stdout)',\n 'cli.bookmarks.export.empty': 'No bookmarks to export',\n 'cli.bookmarks.export.success': '✓ Exported {0} bookmarks to {1}',\n 'cli.bookmarks.export.fail': 'Failed to export bookmarks: {0}',\n};\n\nexport default en;\n","/**\n * 应用配置管理\n * 读写 <数据目录>/config.json(见 config/paths.ts)\n */\n\nimport Conf from 'conf';\nimport { randomUUID } from 'node:crypto';\nimport { existsSync, copyFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { homedir } from 'node:os';\nimport { getAppDataDir } from './paths.js';\n\nexport type BossKeyLang = 'nodejs' | 'python' | 'java' | 'c' | 'go';\n\ninterface ReadShellConfig {\n /** 每页显示行数(0 = 自适应终端高度) */\n linesPerPage: number;\n /** 是否显示状态栏 */\n showStatusBar: boolean;\n /** 阅读模式: 'page' | 'scroll' */\n readingMode: 'page' | 'scroll';\n /** 界面语言 */\n language: 'zh' | 'en';\n /** 行间距 (0-2) */\n lineSpacing: number;\n /** 老板键伪装语言 */\n bossKeyLang: BossKeyLang;\n /** 本设备 ID(文件夹同步用,首次使用时生成) */\n deviceId?: string;\n /** 文件夹同步目录(novel sync --dir 设置) */\n syncDir?: string;\n}\n\nconst defaults: ReadShellConfig = {\n linesPerPage: 0,\n showStatusBar: true,\n readingMode: 'page',\n language: 'zh',\n lineSpacing: 0,\n bossKeyLang: 'nodejs',\n};\n\nconst configDir = getAppDataDir();\n\n// 兼容旧版本:conf 默认把配置写到 env-paths 目录(projectName + \"-nodejs\"),\n// 1.0 起统一放到数据目录。新位置不存在而旧位置存在时迁移一次,保留用户设置。\n// 设置了 READSHELL_HOME(测试/隔离实例)时不读取真实用户的旧配置。\nfunction legacyConfigPath(): string {\n const name = 'readshell-nodejs';\n if (process.platform === 'darwin') {\n return join(homedir(), 'Library', 'Preferences', name, 'config.json');\n }\n if (process.platform === 'win32') {\n const appData = process.env['APPDATA'] || join(homedir(), 'AppData', 'Roaming');\n return join(appData, name, 'Config', 'config.json');\n }\n const xdgConfig = process.env['XDG_CONFIG_HOME'] || join(homedir(), '.config');\n return join(xdgConfig, name, 'config.json');\n}\n\nconst newConfigPath = join(configDir, 'config.json');\nif (!process.env['READSHELL_HOME'] && !existsSync(newConfigPath)) {\n const legacyPath = legacyConfigPath();\n if (existsSync(legacyPath)) {\n try {\n copyFileSync(legacyPath, newConfigPath);\n } catch {\n // 迁移失败不阻塞启动,使用默认配置\n }\n }\n}\n\nconst config = new Conf<ReadShellConfig>({\n cwd: configDir,\n defaults,\n});\n\n// 一次性清理:1.0 移除了云账号体系,删掉旧版本遗留的 auth*/lastSyncAt 键\nfor (const legacyKey of [\n 'authToken',\n 'authUserId',\n 'authDeviceId',\n 'authTier',\n 'authExpiresAt',\n 'lastSyncAt',\n]) {\n config.delete(legacyKey as never);\n}\n\nexport function getConfig(): ReadShellConfig {\n return {\n linesPerPage: config.get('linesPerPage'),\n showStatusBar: config.get('showStatusBar'),\n readingMode: config.get('readingMode'),\n language: config.get('language'),\n lineSpacing: config.get('lineSpacing'),\n bossKeyLang: config.get('bossKeyLang'),\n deviceId: config.get('deviceId'),\n syncDir: config.get('syncDir'),\n };\n}\n\nexport function setConfig<K extends keyof ReadShellConfig>(key: K, value: ReadShellConfig[K]): void {\n config.set(key, value);\n}\n\n/**\n * 获取本设备 ID,首次调用时生成并持久化\n */\nexport function getDeviceId(): string {\n let id = config.get('deviceId');\n if (!id) {\n id = randomUUID();\n config.set('deviceId', id);\n }\n return id;\n}\n\nexport function getSyncDir(): string | null {\n return config.get('syncDir') ?? null;\n}\n\nexport function setSyncDir(dir: string): void {\n config.set('syncDir', dir);\n}\n\nexport function clearSyncDir(): void {\n config.delete('syncDir' as never);\n}\n\nexport { config };\n","import zh from './zh.js';\nimport en from './en.js';\nimport type { LocaleDictionary, LocaleKeys } from './types.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nconst dictionaries: Record<'zh' | 'en', LocaleDictionary> = {\n zh,\n en,\n};\n\nlet currentLang: 'zh' | 'en' = 'zh';\nlet currentDict: LocaleDictionary = dictionaries.zh;\n\n/**\n * 初始化并加载当前配置的语言\n */\nexport function initI18n() {\n const config = getConfig();\n currentLang = config.language || 'zh';\n currentDict = dictionaries[currentLang] || dictionaries.zh;\n}\n\n/**\n * 切换语言字典(运行时热切换支持)\n */\nexport function setLanguage(lang: 'zh' | 'en') {\n currentLang = lang;\n currentDict = dictionaries[lang] || dictionaries.zh;\n}\n\n/**\n * 获取翻译词条\n * 支持占位符 {0}, {1} 替换\n */\nexport function t(key: LocaleKeys, ...args: (string | number)[]): string {\n let template = currentDict[key];\n if (!template) {\n return key;\n }\n \n if (args.length > 0) {\n args.forEach((arg, index) => {\n template = template.replace(`{${index}}`, String(arg));\n });\n }\n return template;\n}\n","/**\n * novel import <file> — 导入本地文件\n * 支持 txt,基础去重,导入后自动入书架\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { statSync, readdirSync } from 'node:fs';\nimport { resolve, join, extname } from 'node:path';\nimport * as readline from 'node:readline/promises';\n\nexport interface ImportArgs {\n file: string;\n}\n\n// 递归扫描目录文件\nfunction scanDirectory(dir: string): string[] {\n let results: string[] = [];\n try {\n const list = readdirSync(dir);\n for (const file of list) {\n const fullPath = join(dir, file);\n const stat = statSync(fullPath);\n if (stat.isDirectory()) {\n results = results.concat(scanDirectory(fullPath));\n } else {\n const ext = extname(fullPath).toLowerCase();\n if (ext === '.txt' || ext === '.epub' || ext === '.md') {\n results.push(fullPath);\n }\n }\n }\n } catch (err) {\n logger.error('Scan error:', err);\n }\n return results;\n}\n\nexport const importCommand: CommandModule<object, ImportArgs> = {\n command: 'import <file>',\n describe: t('cli.import.desc'),\n builder: (yargs) => {\n return yargs.positional('file', {\n describe: t('cli.import.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const targetPath = resolve(argv.file);\n let stat;\n try {\n stat = statSync(targetPath);\n } catch (e) {\n console.log(`${t('cli.import.not_found')} ${targetPath}`);\n process.exit(1);\n }\n\n const bookService = new BookService();\n\n if (stat.isDirectory()) {\n console.log(`${t('cli.import.scan_dir')} ${targetPath}...`);\n const files = scanDirectory(targetPath);\n\n if (files.length === 0) {\n console.log(`✗ ` + t('cli.import.unsupported'));\n return;\n }\n\n console.log(t('cli.import.found_files'));\n files.forEach((f, i) => console.log(` ${i + 1}. ${f}`));\n\n const rl = readline.createInterface({\n input: process.stdin,\n output: process.stdout,\n });\n\n const answer = await rl.question(t('cli.import.confirm_batch', files.length) + ' ');\n rl.close();\n\n if (answer.toLowerCase() === 'y') {\n for (const file of files) {\n try {\n const book = await bookService.importBook(file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n } catch (err) {\n console.log(`${t('cli.import.fail')} ${file} - ${err}`);\n }\n }\n } else {\n console.log(t('cli.import.canceled'));\n }\n } else {\n // 单文件导入\n const book = await bookService.importBook(argv.file);\n console.log(`${t('cli.import.success')} ${book.title} (${book.id})`);\n }\n } catch (error) {\n console.log(`${t('cli.import.fail')} ${error}`);\n process.exit(1);\n }\n },\n};\n","/**\n * 阅读进度业务逻辑\n * 进度读写、resume 核心逻辑\n */\n\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { RecentModel } from '../db/models/Recent.js';\nimport { logger } from '../utils/logger.js';\n\nexport class ProgressService {\n private progressModel = new ProgressModel();\n private recentModel = new RecentModel();\n\n /**\n * 保存阅读进度(退出时调用)\n */\n saveProgress(bookId: string, chapterNo: number, byteOffset: number, percent: number): void {\n const now = Date.now();\n\n this.progressModel.upsert({\n book_id: bookId,\n chapter_no: chapterNo,\n byte_offset: byteOffset,\n percent,\n updated_at: now,\n opened_at: now,\n });\n\n // 同时更新最近阅读记录\n this.recentModel.recordOpen(bookId);\n\n logger.debug(`进度已保存: book=${bookId}, chapter=${chapterNo}, offset=${byteOffset}, ${(percent * 100).toFixed(1)}%`);\n }\n\n /**\n * 获取指定书籍的阅读进度(resume 时调用)\n */\n getProgress(bookId: string): ProgressRecord | undefined {\n return this.progressModel.findByBookId(bookId);\n }\n\n /**\n * 获取最近打开的书籍进度(启动时调用,决定 resume 哪本书)\n */\n getLastOpenedBook(): ProgressRecord | undefined {\n return this.progressModel.getLastOpened();\n }\n}\n","/**\n * Ink TUI 渲染启动函数\n * 封装 ink.render(<App />) 的统一入口\n */\n\nimport React from 'react';\nimport { render } from 'ink';\nimport { App, type PageRoute } from './App.js';\nimport { isBossKeyActive, performBossKeyAction } from '../utils/bossKey.js';\nimport { drainPendingSync } from '../utils/pendingSync.js';\n\ninterface RenderOptions {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\n/**\n * 启动 Ink TUI 应用\n */\nexport function renderApp(options: RenderOptions = {}): void {\n const { initialPage = 'resume', bookId, initialByteOffset } = options;\n\n const { waitUntilExit } = render(\n React.createElement(App, {\n initialPage,\n bookId,\n initialByteOffset,\n }),\n );\n\n waitUntilExit()\n .then(async () => {\n // 检查是否是因为老板键退出:先输出伪装画面,再静默等待同步\n if (isBossKeyActive()) {\n performBossKeyAction();\n }\n // 等待退出前注册的同步完成(最多 2s),保证进度/书签/会话不丢\n await drainPendingSync(2000);\n process.exit(0);\n })\n .catch(() => {\n // 异常退出清理\n process.exit(1);\n });\n}\n","/**\n * App 根组件\n * 管理页面路由状态\n */\n\nimport React, { useState } from 'react';\nimport { Box, Text } from 'ink';\nimport { ResumePage } from './pages/ResumePage.js';\nimport { LibraryPage } from './pages/LibraryPage.js';\nimport { ReaderPage } from './pages/ReaderPage.js';\n\nexport type PageRoute = 'resume' | 'library' | 'reader';\n\ninterface AppProps {\n initialPage?: PageRoute;\n bookId?: string;\n initialByteOffset?: number;\n}\n\nexport function App({ initialPage = 'resume', bookId, initialByteOffset }: AppProps) {\n const [currentPage, setCurrentPage] = useState<PageRoute>(initialPage);\n const [currentBookId, setCurrentBookId] = useState<string | undefined>(bookId);\n const [currentByteOffset, setCurrentByteOffset] = useState<number | undefined>(initialByteOffset);\n\n const navigateTo = (page: PageRoute, targetBookId?: string, byteOffset?: number) => {\n setCurrentPage(page);\n if (targetBookId) setCurrentBookId(targetBookId);\n if (byteOffset !== undefined) setCurrentByteOffset(byteOffset);\n };\n\n return (\n <Box flexDirection=\"column\" width=\"100%\">\n {currentPage === 'resume' && (\n <ResumePage onNavigate={navigateTo} />\n )}\n {currentPage === 'library' && (\n <LibraryPage onNavigate={navigateTo} />\n )}\n {currentPage === 'reader' && currentBookId && (\n <ReaderPage\n bookId={currentBookId}\n initialByteOffset={currentByteOffset}\n onNavigate={navigateTo}\n />\n )}\n {currentPage === 'reader' && !currentBookId && (\n <Box>\n <Text color=\"red\">错误: 未指定书籍</Text>\n </Box>\n )}\n </Box>\n );\n}\n","/**\n * 继续阅读页\n * 启动默认页,自动查询最近阅读并跳转到阅读器\n */\n\nimport React, { useEffect, useState } from 'react';\nimport { Box, Text, useApp } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { syncOnOpen } from '../../services/SyncFolderService.js';\n\ninterface ResumePageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function ResumePage({ onNavigate }: ResumePageProps) {\n const { exit } = useApp();\n const [checking, setChecking] = useState(true);\n\n useEffect(() => {\n // 先从同步文件夹拉取其他设备的进度(最多 ~2s,静默失败)\n void syncOnOpen().finally(() => {\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n setChecking(false);\n return;\n }\n\n // 验证书籍存在\n const bookModel = new BookModel();\n const book = bookModel.findById(lastProgress.book_id);\n\n if (!book) {\n setChecking(false);\n return;\n }\n\n // 自动跳转到阅读器\n onNavigate('reader', book.id, lastProgress.byte_offset);\n });\n }, [onNavigate]);\n\n if (checking) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 检查阅读记录...</Text>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">\n 📖 ReadShell — 终端内轻阅读\n </Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>暂无阅读记录。</Text>\n <Text dimColor>使用 novel import <file> 导入一本书开始阅读。</Text>\n <Box marginTop={1}>\n <Text dimColor>按 q 退出</Text>\n </Box>\n </Box>\n </Box>\n );\n}\n","/**\n * 文件夹同步服务\n *\n * 无服务器同步:每台设备在 <syncDir>/readshell-sync/devices/<deviceId>.json\n * 写一份自己的数据快照(原子写入:tmp + rename),再读取其他设备的文件做合并。\n * 目录本身交给 iCloud / Dropbox / OneDrive / 坚果云 / Syncthing 等任意同步盘。\n *\n * 合并规则:\n * - 书籍身份 = 文件 hash\n * - 进度:按 clientUpdatedAt 与本地 updated_at 做 last-write-wins\n * - 书签:按 `${hash}:${byteOffset}` 做 LWW,含删除墓碑\n * - 会话:按 id 求并集(stats 因此能跨设备聚合)\n * - 本地没有的书:条目保留在对方文件里,但不写入本地(不产生孤儿数据)\n */\n\nimport { existsSync, mkdirSync, readdirSync, readFileSync, writeFileSync, renameSync, copyFileSync } from 'node:fs';\nimport { join } from 'node:path';\nimport { hostname } from 'node:os';\nimport { getDeviceId, getSyncDir, setSyncDir, clearSyncDir } from '../config/AppConfig.js';\nimport { ProgressModel, type ProgressRecord } from '../db/models/Progress.js';\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\nimport { ReadingSessionModel } from '../db/models/ReadingSession.js';\nimport { BookService } from './BookService.js';\nimport { computeFileHash } from '../utils/hash.js';\nimport { logger } from '../utils/logger.js';\nimport type {\n DeviceSyncFile,\n SyncBookEntry,\n SyncBookmarkEntry,\n SyncProgressEntry,\n SyncSessionEntry,\n} from '../types/sync.js';\n\nconst SYNC_ROOT = 'readshell-sync';\nconst SCHEMA_VERSION = 1;\n\nexport interface SyncResult {\n /** 合并进本地的条目数(进度 + 书签 + 会话 + 导入的书) */\n pulled: number;\n /** 本次读到的其他设备文件数 */\n devices: number;\n /** 因损坏被跳过的设备文件数 */\n skipped: number;\n}\n\nexport interface SyncOptions {\n /** 同时把书源文件拷进 <syncDir>/readshell-sync/books/,并从那里补拉缺失的书 */\n withBooks?: boolean;\n}\n\nfunction isValidDeviceFile(data: unknown): data is DeviceSyncFile {\n if (typeof data !== 'object' || data === null) return false;\n const d = data as Record<string, unknown>;\n return (\n typeof d['deviceId'] === 'string' &&\n Array.isArray(d['books']) &&\n Array.isArray(d['progress']) &&\n Array.isArray(d['bookmarks']) &&\n Array.isArray(d['sessions'])\n );\n}\n\nfunction toMillis(iso: unknown): number | null {\n if (typeof iso !== 'string') return null;\n const ms = new Date(iso).getTime();\n return Number.isNaN(ms) ? null : ms;\n}\n\n/**\n * 把一台远端设备的快照合并进本地 SQLite(纯数据操作,便于单测)\n * 返回应用的条目数\n */\nexport function mergeRemoteDevice(\n remote: DeviceSyncFile,\n deps: {\n findBookByHash: (hash: string) => BookRecord | undefined;\n findProgress: (bookId: string) => ProgressRecord | undefined;\n upsertProgress: (progress: ProgressRecord) => void;\n findBookmarkByOffset: (bookId: string, byteOffset: number) => BookmarkRecord | undefined;\n saveBookmark: (bookmark: Omit<BookmarkRecord, 'id'>) => void;\n insertSession: (session: { id: string; book_id: string; started_at: number; ended_at: number; bytes_read: number }) => boolean;\n },\n): number {\n let count = 0;\n\n for (const p of remote.progress) {\n if (typeof p?.bookId !== 'string') continue;\n const book = deps.findBookByHash(p.bookId);\n if (!book) continue;\n\n const remoteTs = toMillis(p.clientUpdatedAt);\n if (remoteTs === null) continue;\n\n const local = deps.findProgress(book.id);\n // 远端不新于本地 → 本地赢,跳过\n if (local && remoteTs <= local.updated_at) continue;\n\n deps.upsertProgress({\n book_id: book.id,\n chapter_no: local?.chapter_no ?? 0,\n byte_offset: p.byteOffset,\n percent: p.percentage,\n updated_at: remoteTs,\n // opened_at 只在远端更新时才前移\n opened_at: Math.max(local?.opened_at ?? 0, remoteTs),\n });\n count++;\n }\n\n for (const b of remote.bookmarks) {\n if (typeof b?.bookId !== 'string' || typeof b?.byteOffset !== 'number') continue;\n const book = deps.findBookByHash(b.bookId);\n if (!book) continue;\n\n const remoteTs = toMillis(b.updatedAt) ?? toMillis(b.createdAt);\n if (remoteTs === null) continue;\n\n const local = deps.findBookmarkByOffset(book.id, b.byteOffset);\n // 远端不新于本地 → 本地赢,跳过\n if (local && remoteTs <= local.updated_at) continue;\n\n const createdAt = toMillis(b.createdAt) ?? remoteTs;\n deps.saveBookmark({\n book_id: book.id,\n title: b.label ?? '',\n byte_offset: b.byteOffset,\n created_at: createdAt,\n updated_at: remoteTs,\n deleted: b.deleted ? 1 : 0,\n });\n count++;\n }\n\n for (const s of remote.sessions) {\n if (typeof s?.id !== 'string' || typeof s?.bookId !== 'string') continue;\n const book = deps.findBookByHash(s.bookId);\n if (!book) continue;\n\n const startedAt = toMillis(s.startedAt);\n const endedAt = toMillis(s.endedAt);\n if (startedAt === null || endedAt === null) continue;\n\n if (\n deps.insertSession({\n id: s.id,\n book_id: book.id,\n started_at: startedAt,\n ended_at: endedAt,\n bytes_read: typeof s.bytesRead === 'number' ? s.bytesRead : 0,\n })\n ) {\n count++;\n }\n }\n\n return count;\n}\n\nexport class SyncFolderService {\n private readonly deviceId = getDeviceId();\n\n constructor(private readonly syncDir: string) {}\n\n private get rootDir(): string {\n return join(this.syncDir, SYNC_ROOT);\n }\n\n private get devicesDir(): string {\n return join(this.rootDir, 'devices');\n }\n\n private get booksDir(): string {\n return join(this.rootDir, 'books');\n }\n\n private get ownFile(): string {\n return join(this.devicesDir, `${this.deviceId}.json`);\n }\n\n /**\n * 收集本机快照\n */\n private collectLocal(): DeviceSyncFile {\n const bookModel = new BookModel();\n const progressModel = new ProgressModel();\n const bookmarkModel = new BookmarkModel();\n const sessionModel = new ReadingSessionModel();\n\n const books: SyncBookEntry[] = [];\n const progress: SyncProgressEntry[] = [];\n const bookmarks: SyncBookmarkEntry[] = [];\n\n for (const book of bookModel.findAll()) {\n books.push({\n hash: book.file_hash,\n title: book.title,\n author: book.author,\n format: book.format,\n size: book.file_size,\n });\n\n const p = progressModel.findByBookId(book.id);\n if (p) {\n progress.push({\n bookId: book.file_hash,\n byteOffset: p.byte_offset,\n percentage: p.percent,\n clientUpdatedAt: new Date(p.updated_at).toISOString(),\n });\n }\n\n for (const b of bookmarkModel.findAllByBookId(book.id)) {\n bookmarks.push({\n id: `${book.file_hash}:${b.byte_offset}`,\n bookId: book.file_hash,\n byteOffset: b.byte_offset,\n label: b.title,\n deleted: b.deleted === 1,\n updatedAt: new Date(b.updated_at || b.created_at).toISOString(),\n createdAt: new Date(b.created_at).toISOString(),\n });\n }\n }\n\n const sessions: SyncSessionEntry[] = [];\n for (const s of sessionModel.findAll()) {\n const book = bookModel.findById(s.book_id);\n if (!book) continue; // 书籍已删,跳过\n sessions.push({\n id: s.id,\n bookId: book.file_hash,\n startedAt: new Date(s.started_at).toISOString(),\n endedAt: new Date(s.ended_at).toISOString(),\n bytesRead: s.bytes_read,\n });\n }\n\n return {\n schemaVersion: SCHEMA_VERSION,\n deviceId: this.deviceId,\n deviceName: hostname(),\n updatedAt: new Date().toISOString(),\n books,\n progress,\n bookmarks,\n sessions,\n };\n }\n\n /**\n * 原子写入本机设备文件(tmp + rename,避免同步盘读到半截文件)\n */\n private writeOwnFile(): void {\n mkdirSync(this.devicesDir, { recursive: true });\n const data = this.collectLocal();\n const tmp = `${this.ownFile}.tmp`;\n writeFileSync(tmp, JSON.stringify(data, null, 2), 'utf-8');\n renameSync(tmp, this.ownFile);\n }\n\n /**\n * 读取所有其他设备文件;损坏/半截 JSON 跳过并记 debug 日志\n */\n private readRemoteFiles(): { files: DeviceSyncFile[]; skipped: number } {\n const files: DeviceSyncFile[] = [];\n let skipped = 0;\n\n let names: string[] = [];\n try {\n names = readdirSync(this.devicesDir);\n } catch {\n return { files, skipped };\n }\n\n for (const name of names) {\n if (!name.endsWith('.json') || name === `${this.deviceId}.json`) continue;\n try {\n const parsed: unknown = JSON.parse(readFileSync(join(this.devicesDir, name), 'utf-8'));\n if (!isValidDeviceFile(parsed)) {\n skipped++;\n logger.debug(`同步:跳过格式非法的设备文件 ${name}`);\n continue;\n }\n files.push(parsed);\n } catch (err) {\n skipped++;\n logger.debug(`同步:跳过损坏的设备文件 ${name}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return { files, skipped };\n }\n\n private mergeDeps() {\n const progressModel = new ProgressModel();\n const bookmarkModel = new BookmarkModel();\n const bookModel = new BookModel();\n const sessionModel = new ReadingSessionModel();\n\n return {\n findBookByHash: (hash: string) => bookModel.findByHash(hash),\n findProgress: (bookId: string) => progressModel.findByBookId(bookId),\n upsertProgress: (p: ProgressRecord) => progressModel.upsert(p),\n findBookmarkByOffset: (bookId: string, offset: number) => bookmarkModel.findByOffsetAny(bookId, offset),\n saveBookmark: (b: Omit<BookmarkRecord, 'id'>) => bookmarkModel.saveByOffset(b),\n insertSession: (s: { id: string; book_id: string; started_at: number; ended_at: number; bytes_read: number }) =>\n sessionModel.insertIfAbsent(s),\n };\n }\n\n /**\n * 把本地书源文件拷进 books/(--with-books),已存在则跳过\n */\n private pushBookFiles(): void {\n const bookModel = new BookModel();\n mkdirSync(this.booksDir, { recursive: true });\n\n for (const book of bookModel.findAll()) {\n const target = join(this.booksDir, `${book.file_hash}.${book.format}`);\n if (existsSync(target)) continue;\n try {\n if (existsSync(book.file_path)) {\n copyFileSync(book.file_path, target);\n }\n } catch (err) {\n logger.debug(`同步:拷贝书源文件失败 ${book.title}: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n }\n\n /**\n * 远端有记录但本地没有的书,从 books/ 目录自动导入(--with-books)\n * 返回导入数量\n */\n private async pullBookFiles(remoteFiles: DeviceSyncFile[]): Promise<number> {\n const bookModel = new BookModel();\n const bookService = new BookService();\n let imported = 0;\n\n const wanted = new Map<string, SyncBookEntry>();\n for (const remote of remoteFiles) {\n for (const b of remote.books) {\n if (typeof b?.hash === 'string' && !wanted.has(b.hash)) {\n wanted.set(b.hash, b);\n }\n }\n }\n\n for (const [hash, entry] of wanted) {\n if (bookModel.findByHash(hash)) continue;\n const ext = entry.format || 'txt';\n\n const candidate = join(this.booksDir, `${hash}.${ext}`);\n if (!existsSync(candidate)) continue;\n try {\n // 校验文件 hash 与记录一致再导入\n const actual = await computeFileHash(candidate);\n if (actual !== hash) {\n logger.debug(`同步:books/${hash}.${ext} hash 不匹配,跳过`);\n continue;\n }\n const book = await bookService.importBook(candidate);\n // 文件名是 hash,导入后用远端元数据恢复书名/作者\n bookModel.updateMeta(book.id, entry.title, entry.author);\n imported++;\n } catch (err) {\n logger.debug(`同步:导入 ${candidate} 失败: ${err instanceof Error ? err.message : String(err)}`);\n }\n }\n\n return imported;\n }\n\n /**\n * 只拉取:读取其他设备文件并合并(阅读器打开时调用)\n */\n async pull(options: SyncOptions = {}): Promise<SyncResult> {\n const { files, skipped } = this.readRemoteFiles();\n\n let pulled = 0;\n if (options.withBooks) {\n pulled += await this.pullBookFiles(files);\n }\n\n const deps = this.mergeDeps();\n for (const remote of files) {\n pulled += mergeRemoteDevice(remote, deps);\n }\n\n return { pulled, devices: files.length, skipped };\n }\n\n /**\n * 完整同步:先写本机快照(push),再合并其他设备(pull)\n */\n async sync(options: SyncOptions = {}): Promise<SyncResult> {\n mkdirSync(this.devicesDir, { recursive: true });\n this.writeOwnFile();\n if (options.withBooks) {\n this.pushBookFiles();\n }\n return this.pull(options);\n }\n}\n\n/**\n * 已配置同步目录时返回服务实例,否则 null\n */\nexport function createFolderSyncService(): SyncFolderService | null {\n const dir = getSyncDir();\n if (!dir) return null;\n return new SyncFolderService(dir);\n}\n\n/**\n * 打开阅读器前的拉取:已配置同步目录时合并其他设备数据,最多等待 timeoutMs,静默失败\n */\nexport async function syncOnOpen(timeoutMs = 2000): Promise<void> {\n const service = createFolderSyncService();\n if (!service) return;\n await Promise.race([\n service.pull().catch(() => {}),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),\n ]);\n}\n\n","/**\n * 书架页\n * 交互式书架列表,上下键选择,Enter 打开阅读\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useApp, useInput } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport type { BookRecord } from '../../db/models/Book.js';\nimport { t } from '../../locales/index.js';\nimport { syncOnOpen } from '../../services/SyncFolderService.js';\n\ninterface LibraryPageProps {\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\nexport function LibraryPage({ onNavigate }: LibraryPageProps) {\n const { exit } = useApp();\n const [books, setBooks] = useState<BookRecord[]>([]);\n const [selectedIndex, setSelectedIndex] = useState(0);\n const [loading, setLoading] = useState(true);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useEffect(() => {\n const bookService = new BookService();\n setBooks(bookService.getAllBooks());\n setLoading(false);\n }, []);\n\n useInput((input, key) => {\n if (input === 'q') {\n exit();\n return;\n }\n\n if (books.length === 0) return;\n\n // 上下导航\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(prev - 1, 0));\n }\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(prev + 1, books.length - 1));\n }\n\n // 删除选中的书 (backspace 键或 d 键)\n if (key.backspace || key.delete || input === 'd' || input === 'x') {\n const selected = books[selectedIndex];\n if (selected) {\n const bookService = new BookService();\n bookService.deleteBook(selected.id);\n \n // 更新列表\n setBooks((prev) => {\n const next = prev.filter((b) => b.id !== selected.id);\n // 调整选中游标,防止越界\n if (selectedIndex >= next.length) {\n setSelectedIndex(Math.max(0, next.length - 1));\n }\n return next;\n });\n }\n return;\n }\n\n // Enter 打开选中的书(先拉取同步文件夹里的其他设备进度)\n if (key.return) {\n const selected = books[selectedIndex];\n if (selected) {\n void syncOnOpen().finally(() => {\n const progressService = new ProgressService();\n const progress = progressService.getProgress(selected.id);\n onNavigate('reader', selected.id, progress?.byte_offset ?? 0);\n });\n }\n }\n }, { isActive: isRawModeSupported });\n\n if (loading) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">{t('tui.lib.loading')}</Text>\n </Box>\n );\n }\n\n if (books.length === 0) {\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.empty.title')}</Text>\n <Box marginTop={1} flexDirection=\"column\">\n <Text dimColor>{t('tui.lib.empty.desc')}</Text>\n <Box marginTop={1}>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n </Box>\n );\n }\n\n return (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.lib.title', books.length)}</Text>\n <Text dimColor>{t('tui.lib.tips')}</Text>\n <Box flexDirection=\"column\" marginTop={1}>\n {books.map((book, index) => {\n const isSelected = index === selectedIndex;\n \n return (\n <Box key={book.id} paddingX={1} justifyContent=\"space-between\">\n <Box>\n <Text\n color={isSelected ? 'cyan' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▸ ' : ' '}\n {book.title}\n </Text>\n <Text dimColor> ({book.format})</Text>\n </Box>\n </Box>\n );\n })}\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器页 — 核心阅读体验\n *\n * 功能:\n * - 加载书籍文件内容,按终端尺寸分页\n * - 键盘翻页(空格/j/↓ 下一页, k/↑ 上一页)\n * - 状态栏显示书名 + 进度百分比\n * - 退出时(q)自动保存进度\n * - resume 进入时根据 byte_offset 定位到对应页\n */\n\nimport React, { useState, useEffect, useRef, useMemo } from 'react';\nimport { randomUUID } from 'node:crypto';\nimport { Box, Text, useApp, useStdout } from 'ink';\nimport type { PageRoute } from '../App.js';\nimport { TextRenderer } from '../components/TextRenderer.js';\nimport { StatusBar } from '../components/StatusBar.js';\nimport { ChapterNav } from '../components/ChapterNav.js';\nimport { useReader } from '../hooks/useReader.js';\nimport { useKeyboard } from '../hooks/useKeyboard.js';\nimport { paginate, type Page } from '../../utils/paginate.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { parseFile, type ParsedBook } from '../../parsers/index.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { ChapterService } from '../../services/ChapterService.js';\nimport { RecentService } from '../../services/RecentService.js';\nimport { BookmarkService } from '../../services/BookmarkService.js';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { ReadingSessionModel } from '../../db/models/ReadingSession.js';\nimport { triggerBossKey } from '../../utils/bossKey.js';\nimport { estimateReadingTime, formatReadingTime } from '../../utils/time.js';\nimport { registerPendingSync } from '../../utils/pendingSync.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { getConfig } from '../../config/AppConfig.js';\nimport { createFolderSyncService } from '../../services/SyncFolderService.js';\n\ninterface ReaderPageProps {\n bookId: string;\n initialByteOffset?: number;\n onNavigate: (page: PageRoute, bookId?: string, byteOffset?: number) => void;\n}\n\n/**\n * 阅读器内部组件 — 在 pages 准备好后渲染\n */\nfunction ReaderContent({\n book,\n bookId,\n pages,\n initialByteOffset,\n termHeight,\n contentHeight,\n lineSpacing,\n totalChars,\n}: {\n book: BookRecord;\n bookId: string;\n pages: Page[];\n initialByteOffset?: number;\n termHeight: number;\n contentHeight: number;\n lineSpacing: number;\n totalChars: number;\n}) {\n const { exit } = useApp();\n const [chapterTitle, setChapterTitle] = useState<string | undefined>();\n const [currentChapter, setCurrentChapter] = useState<ChapterRecord | undefined>();\n const [showChapterNav, setShowChapterNav] = useState(false);\n const [showHelp, setShowHelp] = useState(false);\n const [allChapters, setAllChapters] = useState<ChapterRecord[]>([]);\n const [allBookmarks, setAllBookmarks] = useState<BookmarkRecord[]>([]);\n const [toastMessage, setToastMessage] = useState<string | null>(null);\n\n const progressServiceRef = useRef(new ProgressService());\n const chapterServiceRef = useRef(new ChapterService());\n const bookmarkServiceRef = useRef(new BookmarkService());\n\n const reader = useReader(pages, initialByteOffset);\n\n // 保存进度的通用方法\n const saveReadingProgress = () => {\n const offset = reader.getCurrentOffset();\n const percent = reader.getPercent();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, offset);\n const chapterNo = chapter?.chapter_no ?? 0;\n\n progressServiceRef.current.saveProgress(bookId, chapterNo, offset, percent);\n logger.debug(`进度已保存: offset=${offset}, ${(percent * 100).toFixed(1)}%`);\n };\n\n // 加载并更新当前章节信息\n useEffect(() => {\n const currentOffset = reader.getCurrentOffset();\n const chapter = chapterServiceRef.current.getChapterByOffset(bookId, currentOffset);\n setCurrentChapter(chapter ?? undefined);\n setChapterTitle(chapter?.title ?? undefined);\n }, [reader.currentPage, bookId]);\n\n // 获取该书全部章节及书签用于渲染导航\n useEffect(() => {\n const chaptersList = chapterServiceRef.current.getChaptersByBookId(bookId);\n setAllChapters(chaptersList);\n \n if (showChapterNav) {\n setAllBookmarks(bookmarkServiceRef.current.getBookmarksByBookId(bookId));\n }\n }, [bookId, showChapterNav]);\n\n /**\n * 取当前页首行生成书签名\n */\n const handleAddBookmark = () => {\n const currentPageInfo = reader.getCurrentPage();\n if (!currentPageInfo) return;\n\n let markTitle = '无标题书签';\n for (const line of currentPageInfo.lines) {\n const stripped = line.trim();\n if (stripped.length > 0) {\n markTitle = stripped.slice(0, 15) + (stripped.length > 15 ? '...' : '');\n break;\n }\n }\n\n const currentOffset = reader.getCurrentOffset();\n bookmarkServiceRef.current.addBookmark(bookId, markTitle, currentOffset);\n \n setToastMessage(t('tui.reader.bookmark_add', markTitle));\n setTimeout(() => setToastMessage(null), 2000);\n };\n\n /**\n * 卸载时(仅一次):写入本次阅读会话,并注册退出同步\n * renderApp 会在 process.exit 前等待该同步完成(带超时)\n */\n const readerRef = useRef(reader);\n readerRef.current = reader;\n\n useEffect(() => {\n const startedAt = Date.now();\n const startOffset = readerRef.current.getCurrentOffset();\n\n return () => {\n const endedAt = Date.now();\n const endOffset = readerRef.current.getCurrentOffset();\n\n // 跳过过短的会话(< 5s),bytes_read 不允许为负\n if (endedAt - startedAt >= 5000) {\n new ReadingSessionModel().insert({\n id: randomUUID(),\n book_id: bookId,\n started_at: startedAt,\n ended_at: endedAt,\n bytes_read: Math.max(0, endOffset - startOffset),\n });\n }\n\n // 注册退出同步:写本机设备文件并合并其他设备(文件夹同步,静默失败)\n registerPendingSync(\n (async () => {\n const syncService = createFolderSyncService();\n if (syncService) {\n await syncService.sync();\n }\n })().catch(() => {}),\n );\n };\n }, [bookId, book]);\n\n /**\n * 翻页/卸载时自动保存进度(cleanup 在每次渲染间也会触发,保证进度不丢)\n */\n useEffect(() => {\n return () => {\n saveReadingProgress();\n };\n }, [bookId, reader]);\n\n // 拦截全局键盘事件\n useKeyboard(\n {\n onNext: () => reader.nextPage(),\n onPrev: () => reader.prevPage(),\n onQuit: () => exit(),\n onChapterList: () => setShowChapterNav(true),\n onBossKey: () => {\n saveReadingProgress(); // 老板来了!先存盘\n triggerBossKey(); // 设置老板键激活状态\n exit(); // 告诉 Ink 退出,renderApp 会接手后续伪装动作\n },\n onBookmarkAdd: handleAddBookmark,\n onGoToStart: () => reader.goToFirst(),\n onGoToEnd: () => reader.goToLast(),\n onHelp: () => setShowHelp((v) => !v),\n },\n !showChapterNav && !showHelp, // 浮层显示时停止普通的阅读快捷键\n );\n\n const currentPage = reader.getCurrentPage();\n const currentLines = currentPage?.lines ?? [];\n const calculatedContentHeight = Math.max(1, termHeight - 2);\n\n // 计算剩余阅读时间:使用解析后的实际文本长度(epub 压缩文件大小不可靠)\n const remainingChars = Math.max(0, totalChars * (1 - reader.getPercent()));\n const remainingMinutes = estimateReadingTime(remainingChars, true);\n const remainingTimeStr = formatReadingTime(remainingMinutes);\n\n return (\n <Box flexDirection=\"column\" height={termHeight}>\n {showHelp ? (\n <Box flexDirection=\"column\" padding={1}>\n <Text bold color=\"cyan\">{t('tui.help.title')}</Text>\n <Text>{t('tui.help.next')}</Text>\n <Text>{t('tui.help.prev')}</Text>\n <Text>{t('tui.help.nav')}</Text>\n <Text>{t('tui.help.boss')}</Text>\n </Box>\n ) : !showChapterNav ? (\n <>\n <Box flexDirection=\"column\" flexGrow={1} paddingX={1}>\n <TextRenderer lines={currentLines} height={calculatedContentHeight} lineSpacing={lineSpacing} />\n </Box>\n <StatusBar\n bookTitle={book.title}\n percent={reader.getPercent()}\n chapterTitle={chapterTitle}\n currentPage={reader.currentPage + 1}\n totalPages={reader.totalPages}\n remainingTime={remainingTimeStr}\n />\n {toastMessage && (\n <Box alignSelf=\"flex-end\" marginTop={-2} marginRight={1} borderStyle=\"round\" borderColor=\"green\" paddingX={1}>\n <Text color=\"green\">{toastMessage}</Text>\n </Box>\n )}\n </>\n ) : (\n <ChapterNav\n chapters={allChapters}\n bookmarks={allBookmarks}\n currentChapterId={currentChapter?.id}\n termHeight={termHeight}\n onSelect={(offset) => {\n reader.goToOffset(offset);\n setShowChapterNav(false);\n }}\n onClose={() => setShowChapterNav(false)}\n />\n )}\n </Box>\n );\n}\n\nexport function ReaderPage({ bookId, initialByteOffset, onNavigate: _onNavigate }: ReaderPageProps) {\n const { exit } = useApp();\n const { stdout } = useStdout();\n\n const [book, setBook] = useState<BookRecord | null>(null);\n const [pages, setPages] = useState<Page[] | null>(null);\n const [totalChars, setTotalChars] = useState(0);\n const [error, setError] = useState<string | null>(null);\n\n const termWidth = stdout?.columns ?? 80;\n const termHeight = stdout?.rows ?? 24;\n\n const appConfig = getConfig();\n const lineSpacing = appConfig.lineSpacing || 0;\n \n // 核心优化:如果行间距 > 0,则物理行数会翻倍。为了保证不超出屏幕,分页时的内容高度需要对应缩小。\n const contentHeight = Math.max(Math.floor((termHeight - 3) / (1 + lineSpacing)), 2);\n\n // 加载书籍内容\n useEffect(() => {\n try {\n const bookModel = new BookModel();\n const bookRecord = bookModel.findById(bookId);\n\n if (!bookRecord) {\n setError(`书籍不存在: ${bookId}`);\n return;\n }\n\n setBook(bookRecord);\n\n // 记录打开事件\n const recentService = new RecentService();\n recentService.recordOpen(bookId);\n\n // 异步读取并解析文件内容 (支持 TXT / EPUB / MD)\n parseFile(bookRecord.file_path, bookRecord.format)\n .then((parsed: ParsedBook) => {\n // 核心优化:如果处于滚动模式,步进减半以实现平滑过渡\n const stepSize = appConfig.readingMode === 'scroll' \n ? Math.max(1, Math.floor(contentHeight / 2)) \n : contentHeight;\n\n // 分页\n const paginatedPages = paginate(parsed.content, termWidth - 2, contentHeight, stepSize);\n setPages(paginatedPages);\n setTotalChars(parsed.content.length);\n logger.debug(`加载完成: ${bookRecord.title}, ${paginatedPages.length} 页, 模式: ${appConfig.readingMode}`);\n })\n .catch((err: Error) => {\n setError(`内容解析失败: ${err instanceof Error ? err.message : String(err)}`);\n });\n } catch (err) {\n setError(`加载过程出错: ${err instanceof Error ? err.message : String(err)}`);\n }\n }, [bookId, termWidth, contentHeight]);\n\n // 非 TTY 下支持 q 退出\n useKeyboard({\n onQuit: () => exit(),\n });\n\n // 错误\n if (error) {\n return (\n <Box padding={1} flexDirection=\"column\">\n <Text color=\"red\">✗ {error}</Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n );\n }\n\n // 加载中\n if (!book || !pages) {\n return (\n <Box padding={1}>\n <Text color=\"cyan\">📖 {t('tui.reader.loading')}</Text>\n </Box>\n );\n }\n\n // pages 准备好后渲染阅读器内容\n return (\n <ReaderContent\n book={book}\n bookId={bookId}\n pages={pages}\n initialByteOffset={initialByteOffset}\n termHeight={termHeight}\n contentHeight={contentHeight}\n lineSpacing={lineSpacing}\n totalChars={totalChars}\n />\n );\n}\n","/**\n * 正文渲染组件\n * 显示分页后的文本行,并支持智能高亮\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\n\ninterface TextRendererProps {\n lines: string[];\n height?: number;\n lineSpacing?: number;\n}\n\n/**\n * 处理单行内的智能高亮匹配\n * 例如将 「XXX」或 “XXX” 进行暗化或着色,提升大段文字沉浸感\n */\nfunction renderLineWithHighlight(line: string) {\n if (!line) return <Text> </Text>; // 保留空行\n\n // 匹配对话大纲的正则 (包括中英文常见方括号/双引号)\n // 此处拆分为:对话段落与非对话段落\n // (「.*?」|“.*?”|『.*?』|《.*?》)\n const regex = /(「.*?」|“.*?”|『.*?』|《.*?》)/g;\n const parts = line.split(regex);\n\n return (\n <Text>\n {parts.map((part, index) => {\n // 如果是正则匹配出的组(即被高亮的部分)\n if (regex.test(part)) {\n // 由于 Regex 的全局状态,test 之后要注意\n // 但其实 split 后,奇数项是捕获组的内容\n }\n \n // 简单判定:如果是奇数项,就是捕获组\n const isHighlight = index % 2 === 1;\n\n if (isHighlight) {\n // 对话颜色应用 dimColor,让环境和描述语句突出,或者是相反。\n // 这里将对话变暗 (dimColor),减轻视觉疲劳\n return <Text key={index} dimColor>{part}</Text>;\n }\n\n // 常规文本\n return <Text key={index}>{part}</Text>;\n })}\n </Text>\n );\n}\n\nexport function TextRenderer({ lines, height, lineSpacing = 0 }: TextRendererProps) {\n // 补齐空行以占满屏幕高度(避免内容跳动)\n const displayLines = [...lines];\n if (height && displayLines.length < height) {\n const padding = height - displayLines.length;\n for (let i = 0; i < padding; i++) {\n displayLines.push('');\n }\n }\n\n return (\n <Box flexDirection=\"column\">\n {displayLines.map((line, index) => (\n <Box key={index} marginBottom={lineSpacing}>\n {renderLineWithHighlight(line)}\n </Box>\n ))}\n </Box>\n );\n}\n","/**\n * 底部状态栏组件\n * 显示书名、章节名、页码和进度百分比\n */\n\nimport React from 'react';\nimport { Box, Text } from 'ink';\nimport { t } from '../../locales/index.js';\n\ninterface StatusBarProps {\n bookTitle: string;\n chapterTitle?: string;\n percent: number;\n currentPage: number;\n totalPages: number;\n remainingTime?: string;\n}\n\nexport function StatusBar({\n bookTitle,\n chapterTitle,\n percent,\n currentPage,\n totalPages,\n remainingTime,\n}: StatusBarProps) {\n const displayPercent = (percent * 100).toFixed(1);\n const titleDisplay = chapterTitle ? `${bookTitle} · ${chapterTitle}` : bookTitle;\n\n return (\n <Box flexDirection=\"row\" justifyContent=\"space-between\" borderStyle=\"single\" borderTop={false} borderLeft={false} borderRight={false} paddingX={1}>\n <Box>\n <Text color=\"gray\">📖 {titleDisplay}</Text>\n </Box>\n\n <Box>\n {remainingTime && (\n <Text dimColor>{t('tui.reader.status.remaining', remainingTime)} </Text>\n )}\n <Text color=\"gray\">\n {currentPage}/{totalPages} \n </Text>\n <Text color=\"gray\">\n {displayPercent}% \n </Text>\n <Text dimColor>{t('common.quit')}</Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 章节导航浮层组件\n * 允许在阅读器内唤起章节列表,并支持翻页与选择跳转\n */\n\nimport React, { useState, useEffect } from 'react';\nimport { Box, Text, useInput } from 'ink';\nimport type { ChapterRecord } from '../../db/models/Chapter.js';\nimport type { BookmarkRecord } from '../../db/models/Bookmark.js';\nimport { t } from '../../locales/index.js';\n\ninterface ChapterNavProps {\n chapters: ChapterRecord[];\n bookmarks: BookmarkRecord[];\n currentChapterId?: number;\n termHeight: number;\n onSelect: (byteOffset: number) => void;\n onClose: () => void;\n}\n\nexport function ChapterNav({\n chapters,\n bookmarks,\n currentChapterId,\n termHeight,\n onSelect,\n onClose,\n}: ChapterNavProps) {\n const [activeTab, setActiveTab] = useState<'chapters' | 'bookmarks'>('chapters');\n const isBookmarks = activeTab === 'bookmarks';\n const currentList = isBookmarks ? bookmarks : chapters;\n\n // 找到当前章节的初始索引\n const initialIndex = currentChapterId && !isBookmarks\n ? Math.max(\n 0,\n chapters.findIndex((c) => c.id === currentChapterId),\n )\n : 0;\n\n const [selectedIndex, setSelectedIndex] = useState(initialIndex);\n\n // 切换 tab 时重置索引\n useEffect(() => {\n setSelectedIndex(isBookmarks ? 0 : initialIndex);\n }, [activeTab, initialIndex, isBookmarks]);\n\n // 一页展示多少个项(留出上下边距)\n const pageSize = Math.max(5, termHeight - 6);\n\n // 计算当前可视窗口的起始和结束索引\n const windowStart = Math.max(0, Math.floor(selectedIndex / pageSize) * pageSize);\n const visibleItems = currentList.slice(windowStart, windowStart + pageSize);\n\n const isRawModeSupported = process.stdin.isTTY ?? false;\n\n useInput(\n (input, key) => {\n // 退出\n if (key.escape || input === 'q') {\n onClose();\n return;\n }\n \n // 切换视图\n if (key.tab) {\n setActiveTab(prev => prev === 'chapters' ? 'bookmarks' : 'chapters');\n return;\n }\n\n // 确认\n if (key.return) {\n if (currentList[selectedIndex]) {\n onSelect(currentList[selectedIndex].byte_offset);\n }\n return;\n }\n\n // 上移\n if (key.upArrow || input === 'k') {\n setSelectedIndex((prev) => Math.max(0, prev - 1));\n }\n\n // 下移\n if (key.downArrow || input === 'j') {\n setSelectedIndex((prev) => Math.min(currentList.length - 1, prev + 1));\n }\n },\n { isActive: isRawModeSupported },\n );\n\n return (\n <Box\n flexDirection=\"column\"\n borderStyle=\"round\"\n borderColor=\"green\"\n paddingX={2}\n paddingY={1}\n width=\"80%\"\n alignSelf=\"center\"\n marginTop={2}\n >\n <Box justifyContent=\"space-between\" marginBottom={1}>\n <Box>\n <Text bold color={activeTab === 'chapters' ? 'green' : 'gray'}>{t('tui.nav.tab.chapters')} </Text>\n <Text bold color={activeTab === 'bookmarks' ? 'green' : 'gray'}>{t('tui.nav.tab.bookmarks')}</Text>\n </Box>\n <Text dimColor>{t('tui.nav.tips')}</Text>\n </Box>\n\n {visibleItems.length === 0 ? (\n <Text dimColor>{t('tui.nav.empty')}</Text>\n ) : (\n visibleItems.map((item, idx) => {\n const actualIndex = windowStart + idx;\n const isSelected = actualIndex === selectedIndex;\n \n return (\n <Text\n key={item.id}\n color={isSelected ? 'green' : undefined}\n bold={isSelected}\n >\n {isSelected ? '▶ ' : ' '}\n {item.title}\n </Text>\n );\n })\n )}\n\n <Box marginTop={1} justifyContent=\"flex-end\">\n <Text dimColor>\n {t('tui.nav.page', Math.floor(selectedIndex / pageSize) + 1, Math.ceil(currentList.length / pageSize) || 1)}\n </Text>\n </Box>\n </Box>\n );\n}\n","/**\n * 阅读器核心状态 Hook\n * 管理当前页面、offset、分页等状态\n */\n\nimport { useState, useCallback, useMemo } from 'react';\nimport type { Page } from '../../utils/paginate.js';\n\ninterface ReaderState {\n currentPage: number;\n totalPages: number;\n}\n\nexport function useReader(pages: Page[], initialByteOffset?: number) {\n // 根据 initialByteOffset 找到初始页\n const initialPage = useMemo(() => {\n if (!initialByteOffset || pages.length === 0) return 0;\n\n // 找到 byte_offset <= initialByteOffset 的最后一页\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= initialByteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n return targetPage;\n }, [pages, initialByteOffset]);\n\n const [state, setState] = useState<ReaderState>({\n currentPage: initialPage,\n totalPages: pages.length,\n });\n\n const nextPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.min(prev.currentPage + 1, prev.totalPages - 1),\n }));\n }, []);\n\n const prevPage = useCallback(() => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(prev.currentPage - 1, 0),\n }));\n }, []);\n\n const goToPage = useCallback((pageNum: number) => {\n setState((prev) => ({\n ...prev,\n currentPage: Math.max(0, Math.min(pageNum, prev.totalPages - 1)),\n }));\n }, []);\n\n /**\n * 根据 byte_offset 跳转到对应页\n */\n const goToOffset = useCallback((byteOffset: number) => {\n let targetPage = 0;\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].byteOffset <= byteOffset) {\n targetPage = i;\n } else {\n break;\n }\n }\n setState((prev) => ({\n ...prev,\n currentPage: targetPage,\n }));\n }, [pages]);\n\n const goToFirst = useCallback(() => {\n setState((prev) => ({ ...prev, currentPage: 0 }));\n }, []);\n\n const goToLast = useCallback(() => {\n setState((prev) => ({ ...prev, currentPage: Math.max(0, prev.totalPages - 1) }));\n }, []);\n\n const getCurrentPage = useCallback((): Page | undefined => {\n return pages[state.currentPage];\n }, [state.currentPage, pages]);\n\n /**\n * 获取当前页的 byte_offset(用于保存进度)\n */\n const getCurrentOffset = useCallback((): number => {\n return pages[state.currentPage]?.byteOffset ?? 0;\n }, [state.currentPage, pages]);\n\n const getPercent = useCallback((): number => {\n if (state.totalPages === 0) return 0;\n return (state.currentPage + 1) / state.totalPages;\n }, [state.currentPage, state.totalPages]);\n\n const isFirstPage = state.currentPage === 0;\n const isLastPage = state.currentPage === state.totalPages - 1;\n\n return {\n ...state,\n nextPage,\n prevPage,\n goToPage,\n goToOffset,\n goToFirst,\n goToLast,\n getCurrentPage,\n getCurrentOffset,\n getPercent,\n isFirstPage,\n isLastPage,\n };\n}\n","/**\n * 键盘事件统一管理 Hook\n * 在非 TTY 环境下安全降级\n */\n\nimport { useInput } from 'ink';\n\ninterface KeyboardHandlers {\n onNext?: () => void;\n onPrev?: () => void;\n onQuit?: () => void;\n onChapterList?: () => void;\n onHelp?: () => void;\n onBossKey?: () => void;\n onBookmarkAdd?: () => void;\n onGoToStart?: () => void;\n onGoToEnd?: () => void;\n}\n\nexport function useKeyboard(handlers: KeyboardHandlers, isActive: boolean = true) {\n const isRawModeSupported = process.stdin.isTTY ?? false;\n const shouldListen = isRawModeSupported && isActive;\n\n useInput((input, key) => {\n // 翻页:空格 / j / 下箭头 / f → 下一页\n if (input === ' ' || input === 'j' || key.downArrow || input === 'f') {\n handlers.onNext?.();\n }\n\n // 上一页:k / 上箭头\n if (input === 'k' || key.upArrow) {\n handlers.onPrev?.();\n }\n\n // 跳转:g 首页 / G 末页\n if (input === 'g') {\n handlers.onGoToStart?.();\n }\n if (input === 'G') {\n handlers.onGoToEnd?.();\n }\n\n // 退出:q\n if (input === 'q') {\n handlers.onQuit?.();\n }\n\n // 章节列表:c\n if (input === 'c') {\n handlers.onChapterList?.();\n }\n\n // 帮助:?\n if (input === '?') {\n handlers.onHelp?.();\n }\n \n // 老板键\n if (handlers.onBossKey && (key.escape || input === 'esc' || input === 'b' || input === 'B')) {\n handlers.onBossKey?.();\n }\n \n // 按 m 或 M 加书签\n if (handlers.onBookmarkAdd && (input === 'm' || input === 'M')) {\n handlers.onBookmarkAdd?.();\n }\n }, { isActive: shouldListen });\n}\n","/**\n * 字符串宽度计算\n * 处理中文字符宽度为 2\n */\n\n/**\n * 获取字符串在终端中的显示宽度\n */\nexport function getStringWidth(str: string): number {\n let width = 0;\n for (const char of str) {\n width += isFullWidth(char) ? 2 : 1;\n }\n return width;\n}\n\n/**\n * 判断字符是否为全角字符\n * CJK 统一表意字符 + 全角标点\n */\nfunction isFullWidth(char: string): boolean {\n const code = char.codePointAt(0);\n if (code === undefined) return false;\n\n return (\n // CJK 统一表意字符\n (code >= 0x4e00 && code <= 0x9fff) ||\n // CJK 统一表意字符扩展 A\n (code >= 0x3400 && code <= 0x4dbf) ||\n // CJK 统一表意字符扩展 B\n (code >= 0x20000 && code <= 0x2a6df) ||\n // CJK 兼容表意字符\n (code >= 0xf900 && code <= 0xfaff) ||\n // 全角 ASCII、全角标点\n (code >= 0xff01 && code <= 0xff60) ||\n (code >= 0xffe0 && code <= 0xffe6) ||\n // CJK 标点符号\n (code >= 0x3000 && code <= 0x303f) ||\n // 日文平假名/片假名\n (code >= 0x3040 && code <= 0x30ff) ||\n // 韩文音节\n (code >= 0xac00 && code <= 0xd7af)\n );\n}\n","/**\n * 文本分页算法\n * 按终端宽高切割文本为页面\n */\n\nimport { getStringWidth } from './stringWidth.js';\n\nexport interface Page {\n /** 页面内容行 */\n lines: string[];\n /** 此页在原始文本中的 byte offset 起始位置 */\n byteOffset: number;\n}\n\n/**\n * 将文本按终端尺寸分页\n * @param text 原始文本\n * @param width 终端宽度(列数)\n * @param height 可用行数(扣除状态栏后)\n * @param step 步进行数(用于平滑滚动,不传则默认为 height)\n * @returns 分页结果\n */\nexport function paginate(text: string, width: number, height: number, step?: number): Page[] {\n const pages: Page[] = [];\n const rawLines = text.split('\\n');\n const actualStep = step || height;\n\n // 将原始文本行按终端宽度折行\n const wrappedLines: { text: string; byteOffset: number }[] = [];\n let currentOffset = 0;\n\n for (const rawLine of rawLines) {\n const wrapped = wrapLine(rawLine, width);\n for (const line of wrapped) {\n wrappedLines.push({ text: line, byteOffset: currentOffset });\n }\n currentOffset += Buffer.byteLength(rawLine + '\\n', 'utf-8');\n }\n\n // 按高度切割为页\n for (let i = 0; i < wrappedLines.length; i += actualStep) {\n const pageLines = wrappedLines.slice(i, i + height);\n if (pageLines.length === 0) break;\n\n pages.push({\n lines: pageLines.map((l) => l.text),\n byteOffset: pageLines[0]?.byteOffset ?? 0,\n });\n\n // 已经处理完所有行\n if (i + actualStep >= wrappedLines.length) break;\n }\n\n return pages;\n}\n\n/**\n * 将一行文本按终端宽度折行\n * 处理中英文混排(中文字符宽度为 2)\n */\nexport function wrapLine(line: string, width: number): string[] {\n if (line.length === 0) return [''];\n\n const result: string[] = [];\n let currentLine = '';\n let currentWidth = 0;\n\n for (const char of line) {\n const charWidth = getStringWidth(char);\n\n if (currentWidth + charWidth > width) {\n result.push(currentLine);\n currentLine = char;\n currentWidth = charWidth;\n } else {\n currentLine += char;\n currentWidth += charWidth;\n }\n }\n\n if (currentLine.length > 0) {\n result.push(currentLine);\n }\n\n return result.length > 0 ? result : [''];\n}\n","/**\n * 章节索引业务逻辑\n * 章节索引构建与查询\n */\n\nimport { ChapterModel, type ChapterRecord } from '../db/models/Chapter.js';\n\nexport class ChapterService {\n private chapterModel = new ChapterModel();\n\n /**\n * 获取指定书籍的所有章节\n */\n getChapters(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 获取指定章节信息\n */\n getChapter(bookId: string, chapterNo: number): ChapterRecord | undefined {\n return this.chapterModel.findChapter(bookId, chapterNo);\n }\n\n /**\n * 获取章节总数\n */\n getChapterCount(bookId: string): number {\n return this.chapterModel.getChapterCount(bookId);\n }\n\n /**\n * 获取指定书籍下的所有章节\n */\n getChaptersByBookId(bookId: string): ChapterRecord[] {\n return this.chapterModel.findByBookId(bookId);\n }\n\n /**\n * 根据 offset 查询当前所属章节(用于高亮当前所在章)\n */\n getChapterByOffset(bookId: string, byteOffset: number): ChapterRecord | undefined {\n const chapters = this.chapterModel.findByBookId(bookId);\n if (chapters.length === 0) return undefined;\n\n // 找到 offset 所在的章节(最后一个 byte_offset <= 给定 offset 的章节)\n let current: ChapterRecord | undefined;\n for (const chapter of chapters) {\n if (chapter.byte_offset <= byteOffset) {\n current = chapter;\n } else {\n break;\n }\n }\n return current;\n }\n}\n","/**\n * 最近阅读业务逻辑\n */\n\nimport { RecentModel } from '../db/models/Recent.js';\nimport { BookModel, type BookRecord } from '../db/models/Book.js';\n\nexport class RecentService {\n private recentModel = new RecentModel();\n private bookModel = new BookModel();\n\n /**\n * 获取最近阅读的书籍列表(包含书籍详情)\n */\n getRecentBooks(limit: number = 20): BookRecord[] {\n const recentRecords = this.recentModel.getRecent(limit);\n\n return recentRecords\n .map((record) => this.bookModel.findById(record.book_id))\n .filter((book): book is BookRecord => book !== undefined);\n }\n\n /**\n * 记录打开事件\n */\n recordOpen(bookId: string): void {\n this.recentModel.recordOpen(bookId);\n }\n}\n","/**\n * 书签管理服务\n * 处理针对某一位置打标记录以及后续的跳转\n */\n\nimport { BookmarkModel, type BookmarkRecord } from '../db/models/Bookmark.js';\n\nexport class BookmarkService {\n private bookmarkModel: BookmarkModel;\n\n constructor() {\n this.bookmarkModel = new BookmarkModel();\n }\n\n /**\n * 增加一条书签\n * @param title 该书签展现给用户的文案(一句话大纲)\n */\n addBookmark(bookId: string, title: string, byteOffset: number): void {\n const now = Date.now();\n this.bookmarkModel.insert({\n book_id: bookId,\n title,\n byte_offset: byteOffset,\n created_at: now,\n updated_at: now,\n deleted: 0,\n });\n }\n\n /**\n * 罗列该书全部的书签\n */\n getBookmarksByBookId(bookId: string): BookmarkRecord[] {\n return this.bookmarkModel.findByBookId(bookId);\n }\n\n /**\n * 删掉对应书签\n */\n removeBookmark(id: number): void {\n this.bookmarkModel.delete(id);\n }\n}\n","import type { BossKeyLang } from '../config/AppConfig.js';\nimport { getConfig } from '../config/AppConfig.js';\n\nlet isBossKeyEnabled = false;\n\n/**\n * 标记老板键已触发\n */\nexport function triggerBossKey(): void {\n isBossKeyEnabled = true;\n}\n\n/**\n * 检查老板键是否处于激活状态\n */\nexport function isBossKeyActive(): boolean {\n return isBossKeyEnabled;\n}\n\nconst fakeLogs: Record<BossKeyLang, string> = {\n nodejs: `\nfile:///home/user/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916\n throw new Error(\\`[vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\\`);\n ^\n\nError: [vite] Failed to resolve module import \"./App.vue\". Check if the file exists.\n at Object.run (file:///home/user/project/node_modules/vite/dist/node/chunks/dep-BbV93i69.js:43916:13)\n at async file:///home/user/project/node_modules/vite/dist/node/cli.js:722:7\n at async startVite (file:///home/user/project/node_modules/vite/dist/node/cli.js:700:5)\n at async Object.handler (file:///home/user/project/node_modules/vite/dist/node/cli.js:650:1)\n\nNode.js v20.11.0\n`,\n\n python: `\nTraceback (most recent call last):\n File \"/home/user/project/main.py\", line 42, in <module>\n result = process_data(df)\n File \"/home/user/project/utils/pipeline.py\", line 118, in process_data\n return df.groupby(\"user_id\").apply(transform)\n File \"/home/user/project/utils/pipeline.py\", line 97, in transform\n raise ValueError(f\"Missing required column: '{col}'\")\nValueError: Missing required column: 'timestamp'\n\nDuring handling of the above exception, another exception occurred:\n\nTraceback (most recent call last):\n File \"/home/user/project/main.py\", line 47, in <module>\n raise RuntimeError(\"Pipeline failed. Check logs for details.\")\nRuntimeError: Pipeline failed. Check logs for details.\n`,\n\n java: `\nException in thread \"main\" java.lang.NullPointerException: Cannot invoke \"String.length()\" because \"str\" is null\n\tat com.example.app.StringUtils.process(StringUtils.java:34)\n\tat com.example.app.DataProcessor.run(DataProcessor.java:112)\n\tat com.example.app.Main.main(Main.java:21)\n\nBUILD FAILURE\n[ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.11.0:compile\n[ERROR] -> [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException\n`,\n\n c: `\nmake[2]: *** [CMakeFiles/app.dir/src/main.c.o] Error 1\nmake[1]: *** [CMakeFiles/app.dir/all] Error 2\nmake: *** [all] Error 2\n\n/home/user/project/src/main.c:87:5: error: use of undeclared identifier 'ctx'\n ctx->buffer = malloc(sizeof(Buffer));\n ^\n/home/user/project/src/main.c:102:12: warning: implicit declaration of function 'init_buffer' [-Wimplicit-function-declaration]\n result = init_buffer(ctx, DEFAULT_SIZE);\n ^\n2 errors, 1 warning generated.\n`,\n\n go: `\n# command-line-arguments\n./main.go:58:13: undefined: parseConfig\n./main.go:74:9: cannot use result (variable of type *Response) as type Handler\n./main.go:91:2: declared and not used: errCh\n\ngo: github.com/example/app@v1.3.2: reading github.com/example/app/go.mod at revision v1.3.2: unknown revision v1.3.2\nexit status 1\n`,\n};\n\n/**\n * 执行伪装动作(清屏并打印伪装日志)\n * 注意:不再自行退出进程,由 renderApp 在等待退出同步完成后统一退出\n */\nexport function performBossKeyAction(): void {\n // 1. 彻底清空当前屏幕并清除滚动回放缓冲区 (Scrollback Buffer)\n process.stdout.write('\\u001b[3J\\u001b[2J\\u001b[1;1H');\n\n // 2. 根据用户配置选择伪装语言\n const lang = getConfig().bossKeyLang ?? 'nodejs';\n const fakeLog = fakeLogs[lang];\n\n process.stdout.write(fakeLog + '\\n');\n}\n","/**\n * 阅读时间估算\n */\n\n/**\n * 估算阅读时间(分钟)\n * @param charCount 字符数\n * @param isChinese 是否中文内容\n * @returns 估计阅读分钟数\n */\nexport function estimateReadingTime(charCount: number, isChinese: boolean = true): number {\n // 中文平均阅读速度约 500 字/分钟\n // 英文平均阅读速度约 250 词/分钟(约 1250 字符/分钟)\n const charsPerMinute = isChinese ? 500 : 1250;\n return Math.ceil(charCount / charsPerMinute);\n}\n\n/**\n * 格式化时间显示\n */\nexport function formatReadingTime(minutes: number): string {\n if (minutes < 60) {\n return `${minutes} 分钟`;\n }\n const hours = Math.floor(minutes / 60);\n const mins = minutes % 60;\n return mins > 0 ? `${hours} 小时 ${mins} 分钟` : `${hours} 小时`;\n}\n","/**\n * 退出前待完成的文件夹同步 Promise 注册表\n * ReaderPage 卸载时注册,renderApp 在 process.exit 前等待(带超时)\n */\n\nlet pendingSync: Promise<unknown> | null = null;\n\nexport function registerPendingSync(promise: Promise<unknown>): void {\n pendingSync = promise;\n}\n\n/**\n * 等待已注册的同步完成,最多 wait timeoutMs;无注册或超时都立即返回\n */\nexport async function drainPendingSync(timeoutMs = 2000): Promise<void> {\n if (!pendingSync) return;\n await Promise.race([\n pendingSync.catch(() => {}),\n new Promise<void>((resolve) => setTimeout(resolve, timeoutMs)),\n ]);\n}\n","/**\n * novel resume — 恢复上次阅读\n * 启动后默认入口,精确恢复到上次 offset,零摩擦\n */\n\nimport type { CommandModule } from 'yargs';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { BookService } from '../../services/BookService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { syncOnOpen } from '../../services/SyncFolderService.js';\n\nexport const resumeCommand: CommandModule = {\n command: 'resume',\n describe: t('cli.resume.desc'),\n handler: async () => {\n try {\n // 恢复前先从同步文件夹拉取其他设备的进度(最多 ~2s,静默失败)\n await syncOnOpen();\n\n const progressService = new ProgressService();\n const lastProgress = progressService.getLastOpenedBook();\n\n if (!lastProgress) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n // 验证书籍仍然存在\n const bookService = new BookService();\n const book = bookService.findBook(lastProgress.book_id);\n if (!book) {\n console.log(t('cli.resume.none'));\n return;\n }\n\n logger.debug(`恢复阅读: ${book.title}, offset=${lastProgress.byte_offset}`);\n\n // 启动 Ink TUI 阅读器,恢复到上次 offset\n renderApp({\n initialPage: 'reader',\n bookId: lastProgress.book_id,\n initialByteOffset: lastProgress.byte_offset,\n });\n } catch (error) {\n logger.error('恢复阅读失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel open <id|name> — 打开指定书\n * 支持 book-id 或模糊匹配书名\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { ProgressService } from '../../services/ProgressService.js';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\nimport { syncOnOpen } from '../../services/SyncFolderService.js';\n\nexport interface OpenArgs {\n target: string;\n}\n\nexport const openCommand: CommandModule<object, OpenArgs> = {\n command: 'open <target>',\n describe: t('cli.open.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.open.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.open.not_found')} ${argv.target}`);\n process.exit(1);\n }\n // 打开前先从同步文件夹拉取其他设备的进度/书签(最多 ~2s,静默失败)\n await syncOnOpen();\n\n // 检查是否有之前的阅读进度\n const progressService = new ProgressService();\n const progress = progressService.getProgress(book.id);\n const byteOffset = progress?.byte_offset ?? 0;\n\n logger.debug(`打开: ${book.title}, offset=${byteOffset}`);\n\n // 启动 Ink TUI 阅读器\n renderApp({\n initialPage: 'reader',\n bookId: book.id,\n initialByteOffset: byteOffset,\n });\n } catch (error) {\n logger.error('打开失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel library — 书架列表\n * 含最近阅读排序 + 搜索\n */\n\nimport type { CommandModule } from 'yargs';\nimport { renderApp } from '../../ui/renderApp.js';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface LibraryArgs {\n search?: string;\n}\n\nexport const libraryCommand: CommandModule<object, LibraryArgs> = {\n command: 'list',\n aliases: ['library'],\n describe: t('cli.library.desc'),\n builder: (yargs) => {\n return yargs.option('search', {\n alias: 's',\n describe: t('cli.library.help'),\n type: 'string',\n });\n },\n handler: async (argv) => {\n try {\n // 如果有搜索参数,以非交互模式输出\n if (argv.search) {\n const bookService = new BookService();\n const books = bookService.searchBooks(argv.search);\n\n if (books.length === 0) {\n console.log(t('cli.library.search_none', argv.search));\n return;\n }\n\n console.log(t('cli.library.search_result', books.length));\n books.forEach((book, index) => {\n console.log(` ${index + 1}. ${book.title} [${book.id}] (${book.format})`);\n });\n return;\n }\n\n // 无搜索参数,启动交互式书架\n renderApp({ initialPage: 'library' });\n } catch (error) {\n logger.error('获取书架失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel remove <id|name> — 移除书籍\n * 删除相关所有数据,包括记录和本地进度的 DB 项\n */\n\nimport type { CommandModule } from 'yargs';\nimport { BookService } from '../../services/BookService.js';\nimport { logger } from '../../utils/logger.js';\nimport { t } from '../../locales/index.js';\n\nexport interface RemoveArgs {\n target: string;\n}\n\nexport const removeCommand: CommandModule<object, RemoveArgs> = {\n command: 'remove <target>',\n describe: t('cli.remove.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.remove.help'),\n type: 'string',\n demandOption: true,\n });\n },\n handler: async (argv) => {\n try {\n const bookService = new BookService();\n const book = bookService.findBook(argv.target);\n\n if (!book) {\n console.log(`${t('cli.remove.not_found')} ${argv.target}`);\n process.exit(1);\n }\n\n bookService.deleteBook(book.id);\n console.log(`${t('cli.remove.success')} ${book.title}`);\n } catch (error) {\n logger.error('移除书籍失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel lang <zh|en> — 切换语言\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig } from '../../config/AppConfig.js';\nimport { t, setLanguage } from '../../locales/index.js';\n\nexport interface LangArgs {\n target: 'zh' | 'en';\n}\n\nexport const langCommand: CommandModule<object, LangArgs> = {\n command: 'lang <target>',\n describe: t('cli.lang.desc'),\n builder: (yargs) => {\n return yargs.positional('target', {\n describe: t('cli.lang.help'),\n type: 'string',\n choices: ['zh', 'en'] as const,\n demandOption: true,\n });\n },\n handler: (argv) => {\n const lang = argv.target;\n if (lang === 'zh' || lang === 'en') {\n setConfig('language', lang);\n setLanguage(lang);\n console.log(t('cli.lang.success', lang));\n } else {\n console.log(t('cli.lang.unsupported', lang));\n process.exit(1);\n }\n },\n};\n","import type { CommandModule } from 'yargs';\nimport { execSync } from 'node:child_process';\nimport { readFileSync } from 'node:fs';\nimport { t } from '../../locales/index.js';\nimport { logger } from '../../utils/logger.js';\n\nexport const updateCommand: CommandModule = {\n command: 'update',\n describe: t('cli.update.desc'),\n handler: async () => {\n try {\n console.log(t('cli.update.checking'));\n\n // 获取本地版本号 (由 tsup 在构建时注入)\n const localVersion = typeof APP_VERSION !== 'undefined' ? APP_VERSION : '0.2.2';\n\n // 获取 NPM 最新版本\n const npmOutput = execSync('npm view readshell version', { encoding: 'utf-8' });\n const latestVersion = npmOutput.trim();\n\n if (!latestVersion) {\n throw new Error('Could not fetch npm version');\n }\n\n if (latestVersion === localVersion) {\n console.log(t('cli.update.latest', localVersion));\n return;\n }\n\n console.log(t('cli.update.updating', latestVersion, localVersion));\n\n // 执行升级指令\n execSync('npm install -g readshell@latest', { stdio: 'inherit' });\n \n console.log(t('cli.update.success'));\n } catch (error) {\n const msg = error instanceof Error ? error.message : String(error);\n console.log(t('cli.update.fail', msg));\n logger.error('更新失败:', error);\n process.exit(1);\n }\n },\n};\n","/**\n * novel config <key> <value> — 修改应用配置\n */\n\nimport type { CommandModule } from 'yargs';\nimport { setConfig, type BossKeyLang } from '../../config/AppConfig.js';\nimport { t } from '../../locales/index.js';\n\nconst BOSS_KEY_LANGS: BossKeyLang[] = ['nodejs', 'python', 'java', 'c', 'go'];\n\nexport interface ConfigArgs {\n key: string;\n value: string;\n}\n\nexport const configCommand: CommandModule<object, ConfigArgs> = {\n command: 'config <key> <value>',\n describe: t('cli.config.desc'),\n builder: (yargs) => {\n return yargs\n .positional('key', {\n describe: '配置项名称 (language|line-spacing|reading-mode|boss-key-lang)',\n type: 'string',\n choices: ['language', 'line-spacing', 'reading-mode', 'boss-key-lang'] as const,\n demandOption: true,\n })\n .positional('value', {\n describe: '配置项内容',\n type: 'string',\n demandOption: true,\n });\n },\n handler: (argv) => {\n const { key, value } = argv;\n\n try {\n if (key === 'language') {\n if (value === 'zh' || value === 'en') {\n setConfig('language', value);\n console.log(t('cli.lang.success', value));\n } else {\n console.log(t('cli.lang.unsupported', value));\n }\n } else if (key === 'line-spacing') {\n const spacing = parseInt(value, 10);\n if (!isNaN(spacing) && spacing >= 0 && spacing <= 2) {\n setConfig('lineSpacing', spacing);\n console.log(t('cli.config.line_spacing.success', spacing));\n } else {\n console.log(t('cli.config.line_spacing.desc'));\n }\n } else if (key === 'reading-mode') {\n if (value === 'page' || value === 'scroll') {\n setConfig('readingMode', value);\n console.log(`✓ Reading mode set to: ${value}`);\n } else {\n console.log(t('cli.config.reading_mode.desc'));\n }\n } else if (key === 'boss-key-lang') {\n if (BOSS_KEY_LANGS.includes(value as BossKeyLang)) {\n setConfig('bossKeyLang', value as BossKeyLang);\n console.log(`✓ Boss key language set to: ${value}`);\n } else {\n console.log(`✗ Unsupported language. Choose from: ${BOSS_KEY_LANGS.join(', ')}`);\n }\n }\n } catch (err) {\n console.error('✗ Failed to update config:', err);\n process.exit(1);\n }\n },\n};\n","/**\n * novel sync — 文件夹同步\n * --dir <path> 设置同步目录并立即同步(目录交给 iCloud/Dropbox/坚果云/Syncthing 等同步)\n * --with-books 同时同步书源文件\n * --off 关闭同步\n * 无参数 使用已保存的目录同步一次\n */\n\nimport type { CommandModule } from 'yargs';\nimport { existsSync, statSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { t } from '../../locales/index.js';\nimport { getSyncDir, setSyncDir, clearSyncDir } from '../../config/AppConfig.js';\nimport { SyncFolderService } from '../../services/SyncFolderService.js';\n\ninterface SyncArgs {\n dir?: string;\n 'with-books'?: boolean;\n off?: boolean;\n}\n\nexport const syncCommand: CommandModule = {\n command: 'sync',\n describe: t('cli.sync.desc'),\n builder: (yargs) =>\n yargs\n .option('dir', {\n describe: t('cli.sync.dir'),\n type: 'string',\n })\n .option('with-books', {\n describe: t('cli.sync.with_books'),\n type: 'boolean',\n default: false,\n })\n .option('off', {\n describe: t('cli.sync.off'),\n type: 'boolean',\n default: false,\n }),\n handler: async (argv) => {\n const args = argv as unknown as SyncArgs;\n\n if (args.off) {\n clearSyncDir();\n console.log(t('cli.sync.disabled'));\n return;\n }\n\n if (args.dir) {\n const dir = resolve(args.dir);\n if (!existsSync(dir) || !statSync(dir).isDirectory()) {\n console.log(t('cli.sync.dir_invalid', dir));\n process.exit(1);\n }\n setSyncDir(dir);\n console.log(t('cli.sync.dir_set', dir));\n }\n\n const syncDir = getSyncDir();\n if (!syncDir) {\n console.log(t('cli.sync.no_dir'));\n process.exit(1);\n }\n\n console.log(t('cli.sync.running'));\n try {\n const result = await new SyncFolderService(syncDir).sync({ withBooks: args['with-books'] });\n console.log(t('cli.sync.success', String(result.pulled), String(result.devices)));\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.log(t('cli.sync.failed', msg));\n process.exit(1);\n }\n },\n};\n","/**\n * novel bookmarks export — 导出全部书签\n * 按书籍分组,支持 json / md 两种格式\n */\n\nimport type { CommandModule } from 'yargs';\nimport { writeFileSync } from 'node:fs';\nimport { resolve } from 'node:path';\nimport { t } from '../../locales/index.js';\nimport { BookModel, type BookRecord } from '../../db/models/Book.js';\nimport { BookmarkModel, type BookmarkRecord } from '../../db/models/Bookmark.js';\n\ninterface ExportArgs {\n format?: string;\n out?: string;\n}\n\ninterface ExportEntry {\n label: string;\n byteOffset: number;\n percent: number | null;\n createdAt: string;\n}\n\ninterface ExportGroup {\n title: string;\n author: string | null;\n format: string;\n bookmarks: ExportEntry[];\n}\n\n/**\n * 计算书签在书中的大致位置(0-1),无法估算时返回 null\n */\nfunction bookmarkPercent(book: BookRecord, b: BookmarkRecord): number | null {\n if (!book.file_size || book.file_size <= 0) return null;\n return Math.min(1, Math.max(0, b.byte_offset / book.file_size));\n}\n\nfunction collectGroups(): ExportGroup[] {\n const bookModel = new BookModel();\n const bookmarkModel = new BookmarkModel();\n const groups: ExportGroup[] = [];\n\n for (const book of bookModel.findAll()) {\n const bookmarks = bookmarkModel.findByBookId(book.id);\n if (bookmarks.length === 0) continue;\n\n groups.push({\n title: book.title,\n author: book.author,\n format: book.format,\n bookmarks: bookmarks.map((b) => ({\n label: b.title,\n byteOffset: b.byte_offset,\n percent: bookmarkPercent(book, b),\n createdAt: new Date(b.created_at).toISOString(),\n })),\n });\n }\n\n return groups;\n}\n\nfunction toMarkdown(groups: ExportGroup[]): string {\n const lines: string[] = ['# ReadShell Bookmarks', ''];\n for (const g of groups) {\n lines.push(`## ${g.title}${g.author ? ` — ${g.author}` : ''}`, '');\n for (const b of g.bookmarks) {\n const pct = b.percent !== null ? ` (${(b.percent * 100).toFixed(1)}%)` : '';\n lines.push(`- ${b.label} — offset ${b.byteOffset}${pct} — ${b.createdAt}`);\n }\n lines.push('');\n }\n return lines.join('\\n');\n}\n\nexport const bookmarksCommand: CommandModule = {\n command: 'bookmarks',\n describe: t('cli.bookmarks.desc'),\n builder: (yargs) =>\n yargs.command({\n command: 'export',\n describe: t('cli.bookmarks.export.desc'),\n builder: (yy) =>\n yy\n .option('format', {\n describe: t('cli.bookmarks.export.format'),\n type: 'string',\n choices: ['json', 'md'] as const,\n default: 'md' as const,\n })\n .option('out', {\n describe: t('cli.bookmarks.export.out'),\n type: 'string',\n }),\n handler: async (argv) => {\n const args = argv as unknown as ExportArgs;\n try {\n const groups = collectGroups();\n const total = groups.reduce((n, g) => n + g.bookmarks.length, 0);\n if (total === 0) {\n console.log(t('cli.bookmarks.export.empty'));\n return;\n }\n\n const output = args.format === 'json'\n ? JSON.stringify(groups, null, 2)\n : toMarkdown(groups);\n\n if (args.out) {\n const outPath = resolve(args.out);\n writeFileSync(outPath, output + '\\n', 'utf-8');\n console.log(t('cli.bookmarks.export.success', String(total), outPath));\n } else {\n console.log(output);\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n console.log(t('cli.bookmarks.export.fail', msg));\n process.exit(1);\n }\n },\n }).demandCommand(1),\n handler: () => {},\n};\n","/**\n * 阅读统计\n * 聚合 reading_sessions(含文件夹同步合并进来的其他设备会话),\n * 输出 GitHub 风格的终端热力图 + 总量统计\n */\n\nimport type { ReadingSessionRecord } from '../db/models/ReadingSession.js';\nimport { getStringWidth } from '../utils/stringWidth.js';\n\nexport interface StatsSummary {\n /** 'YYYY-MM-DD' → 当日阅读分钟数 */\n minutesByDay: Map<string, number>;\n /** 有阅读记录的天数 */\n readingDays: number;\n /** 总阅读分钟数 */\n totalMinutes: number;\n /** 读过的书数量 */\n booksRead: number;\n /** 当前连续阅读天数 */\n currentStreak: number;\n /** 历史最长连续阅读天数 */\n longestStreak: number;\n}\n\nfunction dayKey(d: Date): string {\n const m = String(d.getMonth() + 1).padStart(2, '0');\n const day = String(d.getDate()).padStart(2, '0');\n return `${d.getFullYear()}-${m}-${day}`;\n}\n\nfunction dayDiff(a: string, b: string): number {\n const [ay, am, ad] = a.split('-').map(Number);\n const [by, bm, bd] = b.split('-').map(Number);\n return Math.round((Date.UTC(by!, bm! - 1, bd!) - Date.UTC(ay!, am! - 1, ad!)) / 86400000);\n}\n\n/**\n * 聚合会话为统计摘要(纯函数,便于单测)\n * 会话时长计入开始日;跨天会话不拆分\n */\nexport function aggregateSessions(\n sessions: Pick<ReadingSessionRecord, 'book_id' | 'started_at' | 'ended_at'>[],\n now: Date = new Date(),\n): StatsSummary {\n const minutesByDay = new Map<string, number>();\n const books = new Set<string>();\n let totalMinutes = 0;\n\n for (const s of sessions) {\n const minutes = Math.max(0, (s.ended_at - s.started_at) / 60000);\n if (minutes <= 0) continue;\n const key = dayKey(new Date(s.started_at));\n minutesByDay.set(key, (minutesByDay.get(key) ?? 0) + minutes);\n books.add(s.book_id);\n totalMinutes += minutes;\n }\n\n const days = [...minutesByDay.keys()].sort();\n\n // 最长连续:排序后扫描相邻差为 1 的区间\n let longestStreak = 0;\n let run = 0;\n for (let i = 0; i < days.length; i++) {\n run = i > 0 && dayDiff(days[i - 1]!, days[i]!) === 1 ? run + 1 : 1;\n longestStreak = Math.max(longestStreak, run);\n }\n\n // 当前连续:从今天或昨天往前数(今天还没读不算断签)\n let currentStreak = 0;\n if (days.length > 0) {\n const today = dayKey(now);\n const yesterday = dayKey(new Date(now.getTime() - 86400000));\n const last = days[days.length - 1]!;\n if (last === today || last === yesterday) {\n currentStreak = 1;\n for (let i = days.length - 2; i >= 0; i--) {\n if (dayDiff(days[i]!, days[i + 1]!) === 1) currentStreak++;\n else break;\n }\n }\n }\n\n return {\n minutesByDay,\n readingDays: days.length,\n totalMinutes: Math.round(totalMinutes),\n booksRead: books.size,\n currentStreak,\n longestStreak,\n };\n}\n\n// 5 级强度:0 / <15 / <30 / <60 / >=60 分钟\nconst LEVEL_CHARS = ['·', '░', '▒', '▓', '█'];\n// GitHub 暗色绿阶(256 色)\nconst LEVEL_COLORS = ['38;5;238', '38;5;22', '38;5;28', '38;5;34', '38;5;40'];\n\nfunction levelOf(minutes: number): number {\n if (minutes <= 0) return 0;\n if (minutes < 15) return 1;\n if (minutes < 30) return 2;\n if (minutes < 60) return 3;\n return 4;\n}\n\nexport interface HeatmapOptions {\n /** 终端宽度(列数),用于自适应周数 */\n width: number;\n /** 星期标签(周一/周三/周五行),如 ['一','三','五'] 或 ['Mon','Wed','Fri'] */\n dayLabels: [string, string, string];\n /** 是否输出 ANSI 颜色(非 TTY 时用纯字符) */\n color: boolean;\n now?: Date;\n}\n\n/**\n * 渲染 GitHub 风格热力图:53 周 × 7 天,周为列、周一到周日为行。\n * 终端太窄时自动减少周数。\n */\nexport function renderHeatmap(minutesByDay: Map<string, number>, opts: HeatmapOptions): string {\n const now = opts.now ?? new Date();\n const labelWidth = Math.max(...opts.dayLabels.map((l) => getStringWidth(l))) + 1;\n // 每列 2 字符宽;至少显示 4 周\n const weeks = Math.max(4, Math.min(53, Math.floor((opts.width - labelWidth) / 2)));\n\n // 本周一作为最后一列的起点\n const dow = (now.getDay() + 6) % 7; // 周一 = 0\n const endMonday = new Date(now.getFullYear(), now.getMonth(), now.getDate() - dow);\n const start = new Date(endMonday.getTime() - (weeks - 1) * 7 * 86400000);\n const todayKey = dayKey(now);\n\n const cell = (date: Date): string => {\n const key = dayKey(date);\n if (key > todayKey) return ' ';\n const level = levelOf(minutesByDay.get(key) ?? 0);\n if (!opts.color) return `${LEVEL_CHARS[level]} `;\n return `\\x1b[${LEVEL_COLORS[level]}m█\\x1b[0m `;\n };\n\n const lines: string[] = [];\n for (let row = 0; row < 7; row++) {\n const label = row === 0 ? opts.dayLabels[0] : row === 2 ? opts.dayLabels[1] : row === 4 ? opts.dayLabels[2] : '';\n let line = label + ' '.repeat(Math.max(0, labelWidth - getStringWidth(label)));\n for (let w = 0; w < weeks; w++) {\n line += cell(new Date(start.getTime() + (w * 7 + row) * 86400000));\n }\n lines.push(line.trimEnd());\n }\n\n return lines.join('\\n');\n}\n\n/**\n * 渲染图例:少 → 多\n */\nexport function renderLegend(less: string, more: string, color: boolean): string {\n const cells = LEVEL_CHARS.map((ch, i) =>\n color ? `\\x1b[${LEVEL_COLORS[i]}m█\\x1b[0m` : ch,\n ).join(' ');\n return `${less} ${cells} ${more}`;\n}\n","/**\n * novel stats — 阅读统计\n * GitHub 风格热力图(近一年)+ 总量:阅读天数、总时长、书籍数、连续天数\n */\n\nimport type { CommandModule } from 'yargs';\nimport { t } from '../../locales/index.js';\nimport { getConfig } from '../../config/AppConfig.js';\nimport { ReadingSessionModel } from '../../db/models/ReadingSession.js';\nimport { aggregateSessions, renderHeatmap, renderLegend } from '../../services/StatsService.js';\n\nexport const statsCommand: CommandModule = {\n command: 'stats',\n describe: t('cli.stats.desc'),\n handler: () => {\n const sessions = new ReadingSessionModel().findAll();\n const stats = aggregateSessions(sessions);\n const lang = getConfig().language;\n\n const width = process.stdout.columns ?? 80;\n const color = Boolean(process.stdout.isTTY);\n const dayLabels: [string, string, string] = lang === 'en' ? ['Mon', 'Wed', 'Fri'] : ['一', '三', '五'];\n\n console.log(t('cli.stats.title'));\n console.log('');\n console.log(renderHeatmap(stats.minutesByDay, { width, dayLabels, color }));\n console.log('');\n console.log(renderLegend(t('cli.stats.less'), t('cli.stats.more'), color));\n console.log('');\n console.log(t('cli.stats.summary',\n String(stats.readingDays),\n String(stats.totalMinutes),\n String(stats.booksRead),\n String(stats.currentStreak),\n String(stats.longestStreak),\n ));\n },\n};\n","/**\n * 数据库迁移逻辑\n * 程序启动时检查并执行建表/迁移\n */\n\nimport { getDb } from './client.js';\nimport { logger } from '../utils/logger.js';\n\nconst SCHEMA_VERSION = 4;\n\n/**\n * 初始化数据库(建表 + 版本管理)\n */\nexport function initDatabase(): void {\n const db = getDb();\n\n // 创建版本管理表\n db.exec(`\n CREATE TABLE IF NOT EXISTS schema_version (\n version INTEGER PRIMARY KEY\n );\n `);\n\n const row = db.prepare('SELECT version FROM schema_version LIMIT 1').get() as\n | { version: number }\n | undefined;\n const currentVersion = row?.version ?? 0;\n\n if (currentVersion < SCHEMA_VERSION) {\n logger.debug(`数据库迁移: v${currentVersion} → v${SCHEMA_VERSION}`);\n migrate(db, currentVersion);\n }\n}\n\nfunction migrate(db: ReturnType<typeof getDb>, fromVersion: number): void {\n const migrations: Record<number, string> = {\n 1: `\n -- 书籍元数据\n CREATE TABLE IF NOT EXISTS books (\n id TEXT PRIMARY KEY,\n title TEXT NOT NULL,\n author TEXT,\n file_path TEXT NOT NULL,\n format TEXT NOT NULL,\n file_hash TEXT NOT NULL,\n file_size INTEGER,\n created_at INTEGER NOT NULL\n );\n\n -- 核心状态表\n CREATE TABLE IF NOT EXISTS reading_progress (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n chapter_no INTEGER NOT NULL DEFAULT 0,\n byte_offset INTEGER NOT NULL DEFAULT 0,\n percent REAL NOT NULL DEFAULT 0,\n updated_at INTEGER NOT NULL,\n opened_at INTEGER NOT NULL\n );\n\n -- 最近阅读排序\n CREATE TABLE IF NOT EXISTS recent_reads (\n book_id TEXT PRIMARY KEY REFERENCES books(id),\n opened_at INTEGER NOT NULL,\n open_count INTEGER NOT NULL DEFAULT 1\n );\n\n -- 章节索引\n CREATE TABLE IF NOT EXISTS chapter_index (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n chapter_no INTEGER NOT NULL,\n title TEXT,\n byte_offset INTEGER NOT NULL,\n UNIQUE(book_id, chapter_no)\n );\n\n -- 索引\n CREATE INDEX IF NOT EXISTS idx_chapter_book ON chapter_index(book_id);\n CREATE INDEX IF NOT EXISTS idx_recent_opened ON recent_reads(opened_at DESC);\n `,\n 2: `\n -- 书签管理\n CREATE TABLE IF NOT EXISTS bookmarks (\n id INTEGER PRIMARY KEY AUTOINCREMENT,\n book_id TEXT NOT NULL REFERENCES books(id),\n title TEXT NOT NULL,\n byte_offset INTEGER NOT NULL,\n created_at INTEGER NOT NULL\n );\n\n CREATE INDEX IF NOT EXISTS idx_bookmarks_book ON bookmarks(book_id);\n `,\n 3: `\n -- 阅读会话(用于统计与跨设备同步)\n CREATE TABLE IF NOT EXISTS reading_sessions (\n id TEXT PRIMARY KEY,\n book_id TEXT NOT NULL REFERENCES books(id),\n started_at INTEGER NOT NULL,\n ended_at INTEGER NOT NULL,\n bytes_read INTEGER NOT NULL DEFAULT 0,\n synced INTEGER NOT NULL DEFAULT 0\n );\n\n CREATE INDEX IF NOT EXISTS idx_sessions_book ON reading_sessions(book_id);\n `,\n 4: `\n -- 书签墓碑:文件夹同步需要传播删除事件\n ALTER TABLE bookmarks ADD COLUMN updated_at INTEGER NOT NULL DEFAULT 0;\n ALTER TABLE bookmarks ADD COLUMN deleted INTEGER NOT NULL DEFAULT 0;\n UPDATE bookmarks SET updated_at = created_at;\n\n -- 文件夹同步按 id 去重合并会话,不再需要 synced 标记\n ALTER TABLE reading_sessions DROP COLUMN synced;\n `,\n };\n\n db.transaction(() => {\n for (let v = fromVersion + 1; v <= SCHEMA_VERSION; v++) {\n const sql = migrations[v];\n if (sql) {\n db.exec(sql);\n logger.debug(`已执行迁移 v${v}`);\n }\n }\n\n // 更新版本号\n db.prepare('DELETE FROM schema_version').run();\n db.prepare('INSERT INTO schema_version (version) VALUES (?)').run(SCHEMA_VERSION);\n })();\n}\n","#!/usr/bin/env node\n\n/**\n * ReadShell — 终端内低打断轻阅读工具\n * 程序主入口,解析 argv,路由到子命令\n */\n\nimport { createParser } from './cli/parser.js';\nimport { initDatabase } from './db/migrate.js';\nimport { initI18n } from './locales/index.js';\nimport { logger } from './utils/logger.js';\n\nasync function main() {\n try {\n // 初始化数据库(自动建表/迁移)\n initDatabase();\n \n // 初始化多语言本地化模块\n initI18n();\n\n // 解析命令行参数并执行对应命令\n const parser = createParser();\n await parser.parse();\n } catch (error) {\n logger.error('程序启动失败:', error);\n process.exit(1);\n }\n}\n\nmain();\n"],"mappings":"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","names":["existsSync","configDir","db","db","db","db","db","db","detect","iconv","resolve","detect","iconv","readFileSync","existsSync","join","homedir","join","homedir","existsSync","config","existsSync","statSync","resolve","join","yargs","React","useState","Box","Text","existsSync","mkdirSync","readdirSync","readFileSync","copyFileSync","join","join","mkdirSync","readdirSync","readFileSync","existsSync","copyFileSync","resolve","useState","useEffect","Box","Text","useApp","jsx","jsxs","useApp","useState","useEffect","Box","Text","useState","useEffect","randomUUID","Box","Text","useApp","Box","Text","jsx","Box","Text","jsx","jsxs","Box","Text","useState","useEffect","Box","Text","useInput","jsx","jsxs","useState","useEffect","useInput","Box","Text","useState","useInput","resolve","jsx","jsxs","useApp","useState","useEffect","randomUUID","Box","Text","jsx","jsxs","useState","Box","Text","React","yargs","yargs","yargs","yargs","yargs","existsSync","statSync","resolve","yargs","resolve","existsSync","statSync","writeFileSync","resolve","yargs","resolve","writeFileSync","SCHEMA_VERSION","db"]}
|