# xiaoshuoread **Repository Path**: web2026_1/xiaoshuoread ## Basic Information - **Project Name**: xiaoshuoread - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-08-28 - **Last Updated**: 2026-08-29 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 书阁 · 小说阅读器 纯前端的 H5 小说阅读器。粘一个下载链接或者拖一个文件进去,它负责解压、认编码、分章、排版,然后记住你读到哪。 没有后端,没有账号,书和进度都存在浏览器本地(IndexedDB),关掉再打开还在原处。 ## 跑起来 ```bash ./dev.sh # 先报当前状态,再选运行 / 停止 ./dev.sh start # 直接启动(端口被占就先接管) ./dev.sh stop # 直接停止 ./dev.sh -p 3000 # 换端口 ./dev.sh -o # 启动后打开浏览器 ``` 直接跑 `./dev.sh` 会先看一眼端口:在跑就问你「重启 / 停止 / 什么都不做」,没跑就问「启动 / 什么都不做」,回车走默认项。被别的脚本调用(stdin 不是终端)时不会卡住,按 `start` 处理。第一次跑会自动 `npm install`。 也可以用原始命令:`npm install && npm run dev`。 打包成静态站点: ```bash npm run build # 产物在 dist/ npm run preview # 本地预览打包结果 ``` `dist/` 是纯静态文件,丢到任意静态托管(GitHub Pages、Vercel、Nginx、对象存储)都能跑。因为用了 ES Module 和 Web Worker,**必须通过 http(s) 访问,直接双击打开 `index.html` 不行**。 ## 四种导入方式 1. **粘链接**:输入框里粘贴下载地址,点「抓取」。 2. **拖文件**:把 txt / zip / rar / epub 拖到窗口任意位置。 3. **选文件**:手机上点「选择文件」,从文件 App 里挑。 4. **用地址直接打开**:见下面「一个链接直接开读」。 支持的格式:`.txt`、`.zip`、`.rar`(RAR4/RAR5)、`.epub`、`.gz`。压缩包里有多个 txt 会按文件名顺序拼成一本。编码方面 UTF-8 / GBK / GB18030 / Big5 / UTF-16 都会自动认。 ### 一个链接直接开读 把下载地址挂在页面地址上,打开就自动下载并进阅读器,不用点任何按钮: ``` http://localhost:5173/openstory?url=http://file.9191net.com/ebooktxts/20110210/都市特种兵1.rar http://localhost:5173/?url=<下载地址> # 任何静态托管都能用 http://localhost:5173/#/open?url=<下载地址> # 纯 hash,最保险 ``` 地址里的中文不用手动转义,浏览器会处理;只有当下载地址本身带 `&` 时才需要 `encodeURIComponent`。参数名 `url`、`openstory`、`book`、`src` 都认。 **这本书如果已经在书架上,会跳过下载,直接接着上次的位置读**(按下载地址匹配)。处理完地址栏里的参数会被抹掉,刷新不会重下一遍。 书架上长按书脊 →「复制打开链接」,可以拿到这本书的可分享地址。 注意 `/openstory` 这种路径形式需要托管支持 SPA 回退(dev、preview、Vercel、Netlify 都行;GitHub Pages 这类会 404),那种情况下用 `/?url=` 或 `#/open?url=`。 ### 链接是怎么抓下来的 浏览器不允许网页跨域读取别人网站的文件,除非对方明确允许(CORS)—— 小说下载站基本都没开这个口子。但**跑 `npm run dev` 的这台机器是可以直接下载的**,所以抓取按这个顺序走: ``` 直连 → 本地中转 → 你自己填的镜像 → allorigins → codetabs → cors.lol ``` 每一步的结果都实时显示出来。其中「本地中转」是 dev / preview 服务器里的一段 20 行中间件(`tools/grab-plugin.ts`):页面请求同源的 `/__grab?url=...`,Node 那边把文件取回来再转给页面,全程不落盘、不经过任何第三方。 所以在本地跑的时候,粘链接 → 点抓取 → 直接进阅读器,没有别的步骤。手机连同一个 Wi-Fi 打开 `http://电脑IP:5173` 也一样,下载由电脑代劳。 **`npm run build` 出来的 `dist/` 里没有这段中间件**,纯静态部署时抓取会退到公共镜像;镜像也失败的话,界面会给出「让浏览器下载 xxx.rar」按钮 —— 浏览器自己下载不受跨域限制 —— 下完点「选择刚下载的文件」即可,Chrome 会直接定位到下载文件夹。 想让静态部署也能一步到位,在「导入 → 自定义镜像」里填一个自己的跨域代理。比如部署一个 Cloudflare Worker: ```js export default { async fetch(request) { const target = new URL(request.url).searchParams.get('url'); if (!target) return new Response('missing url', { status: 400 }); const upstream = await fetch(target, { redirect: 'follow' }); const headers = new Headers(upstream.headers); headers.set('Access-Control-Allow-Origin', '*'); return new Response(upstream.body, { status: upstream.status, headers }); }, }; ``` 然后把 `https://你的worker.workers.dev/?url={url}` 填进去,`{url}` 是原链接的占位符。注意代理机器要能访问到目标站点 —— 公共镜像连不上国内小说站,多半就是这个原因。 ## 阅读器 - 四套主题:纸 / 宣 / 竹 / 夜,字体宋黑楷三选一,字号、行距、页宽都能调 - **智能排版**(默认开):合并按固定宽度硬折的断行、清掉站点广告行、还原 `<p>` 这类网页残渣 - 左侧书脊导航条:整本书的章节刻度,朱砂方印是你现在的位置,桌面端可以直接拖着跳 - 目录抽屉支持搜章节名;进度按「第几段 + 段内比例」记录,换字号后回到的还是同一句话 键盘快捷键:`←` `→` 上一章下一章,`空格` 翻页(到底自动进下一章),`T` 目录,`Esc` 回书架。手机上点屏幕中间收起/展开工具栏。 ## 部署到现有站点的子目录 假设挂在 `https://yoursite.com/reader/` 下面: ```bash ./build.sh # 生成 release/reader/ 和 release/reader.zip(约 0.5MB / 6 个文件) ./build.sh -d novel # 换个目录名,要和网站上的子路径一致 ./build.sh -s # 打包完在 localhost:8080 起一份,浏览器里先验收再上传 ./build.sh -h # 看全部参数 ``` `-s` 起的那份自带抓取中转,效果等同线上放好 `grab.php`,可以完整试一遍再决定上传。 1. 把 `release/reader/` 整个目录传到站点里,比如 `/var/www/yoursite/reader/` —— 里面已经含了 `index.html`、`assets/` 和 `grab.php`,不用再自己拼 2. nginx 加一条 `try_files $uri $uri/ /reader/index.html;`,让 `/reader/openstory?url=...` 回落到入口页(完整示例见 `deploy/nginx.conf.example`) 3. 主站里直接跳过来: ```html 在线阅读 ``` 书架顶栏的站名、返回链接、栏目名在 `src/site.ts` 里改: ```ts export const SITE = { name: '久久小说吧', home: 'http://www.9191net.com/', section: '在线阅读', }; ``` 配色取自主站(顶栏紫 `#9e34c4`、页面米 `#f6f3ec`、标签橙 `#ff8800`),在 `src/styles.css` 顶部的主题变量里,换站点改那几个值就行。 几个要留意的: - 打包用的是相对路径(`base: './'`),换目录不用改配置。但要保证访问 `/reader` 时服务器会 301 到 `/reader/`,否则相对路径会往上跑一层 - **主站是 https、小说链接是 http 的话,必须有中转**。浏览器既不让 https 页面直连 http 资源,也会拦截 https 页面发起的 http 下载,这种情况下没有中转就走不通 - 服务器没有 PHP 就用 `deploy/grab.mjs`(Node 版,可以挂到现有服务上,也可以 `node deploy/grab.mjs ./dist 8080 /reader` 独立跑) - 中转只转发不落盘,另外挡掉了内网地址,不会被当成内网跳板 ## 代码结构 ``` deploy/ grab.php 线上抓取中转(PHP),和 index.html 放一起即可 grab.mjs 线上抓取中转(Node),可挂载也可独立运行 nginx.conf.example 子目录部署的 nginx 片段 tools/ grab-plugin.ts dev / preview 的本地抓取中转(/__grab),不进构建产物 src/ lib/ decode.ts 编码探测(BOM → UTF-8 严格校验 → 常用字打分选 GBK/Big5) sniff.ts 看文件头判断格式 archive.ts rar(WASM unrar)/ zip / gzip 解包,epub 解析 parse.ts 分章、正文清洗、硬折行合并 book.worker.ts 解压+解码+分章都在 Worker 里,不卡界面 fetchBook.ts 链接抓取:直连、本地中转、镜像轮询、网页里嗅探附件地址 db.ts IndexedDB:书目、正文、进度三个 store ui/ 书架、阅读器、目录、外观、导入 ``` ## 已知限制 - 不支持 7z 和分卷压缩包,也不支持带密码的压缩包 - 单个文件超过 80MB 会被拒(浏览器内存扛不住) - 清空浏览器数据 = 清空书架,重要的书请留好原文件 - 阅读进度只在本机,不跨设备同步