lornshrimp/分发-webnovel
用于将 `WebNovel/` 目录内指定章节批量上传到 WebNovel(Inkstone)作者后台并保存。支持按 `WebNovel/README.md` 读取账号、密码、书籍Id、书名与卷名校验,自动进入新建/修改章节页,填写合并标题输入框,录入正文与作者有话说,保存后循环处理下一章。关键词:WebNovel 上传、Inkstone、章节保存、批量传章、作者有话说、章节编辑。
npx skills add https://github.com/lornshrimp/Lorn.NovelWriteSkills --skill 分发-WebNovel
<!-- ===== Layer 1: 永久缓存 ===== -->
用于把你指定的 1 章或多章,上传到 WebNovel(Inkstone)对应作品下,并按后台要求保存。
<!-- ===== Layer 3: 场景缓存 ===== -->
本 Skill 执行前,必须按以下规则确定工作目录:
Agents.md,检查其中 主输出平台 声明主输出平台 → 工作目录为 小说正文/主输出平台,或 Agents.md 不存在/未声明 主输出平台 → 工作目录为 WebNovel/后续所有章节文件路径、README.md、分发记录.md 均基于此工作目录。明确说明:
README.md、分发记录.md 均在 小说正文/ 下README.md、分发记录.md 均在 WebNovel/ 下> 路径说明:以下各节中所有 WebNovel/ 形式的路经(如 WebNovel/README.md、WebNovel/分发记录.md)均为工作目录的示例写法。实际目录由上方「工作目录路由规则」确定:本平台是主输出平台时,所有这些路径对应 小说正文/;不是主输出平台时,对应 WebNovel/。
WebNovel/ 目录。WebNovel/README.md 写好账号、密码、WebNovel 书籍Id、书名、卷名信息。WebNovel/ 下。# 1.1.3 三分钟,其中 1.1.3 表示 部号.卷号.章号;提取时必须只取最后一段 3 作为“章号”,三分钟 作为“章节标题”。1.1.3、2.4.17 这类三级编号原样带入标题输入框;最终只能写成 Chapter 3 三分钟 一类格式。Chapter {章号} {章节标题}。Chapter、章号 与 标题 之间必须保留 1 个空格,例如:Chapter 1 天降奇缘。Chapter 12 Night Visitor),且章号一致,则可直接使用。夜半来客),则必须自动组装成 Chapter {章号} 夜半来客。_WebNovel版)带入标题输入框。WebNovel/README.md,至少提取以下信息:11997642;按“书籍Id判定规则”选取当前章节对应值)读取 WebNovel/README.md 后,必须检查其第一行一级标题是否等于 # WebNovel 分发配置:
README.md 平台标识与当前 Skill 不符:预期"WebNovel 分发配置",实际读取到"{实际读取到的标题}"。请检查是否将其他平台的 README.md 误放到了当前工作目录。WebNovel/分发记录.md:README.md 中的“小说名称(小说书名)”。README.md 中与“小说名称(小说书名)”成对维护的书籍Id。分发记录.md 模板(首次创建直接使用)当 WebNovel/分发记录.md 不存在时,必须按以下模板创建:
# WebNovel 分发记录
> 用途:记录“章节唯一键 -> WebNovel 章节Id”映射,供分发流程判断“新建模式/修改模式”。
## 字段说明
- `章节唯一键`:固定格式 `WebNovel书籍ID|分部名|卷号|章号|章名`(无分部时分部名填 `NA`)
- `WebNovel书籍ID`:对应 `WebNovel/README.md` 中书籍Id
- `章节Id`:WebNovel 章节编辑地址中的章节Id
- `最近操作`:`新建` 或 `修改`
- `更新时间`:`YYYY-MM-DD HH:mm:ss`
- `备注`:可选
## 记录表
|章节唯一键|WebNovel书籍ID|章节Id|最近操作|更新时间|备注|
|:--|--:|--:|:--|:--|:--|
README.md 模板(首次创建直接使用)当 WebNovel/README.md 不存在时,必须按以下模板创建:
# WebNovel 分发配置
> 用途:存储 WebNovel 书籍信息与卷名映射。分发流程需要用户手动登录,不读取密码。
## 书籍信息
### 无分部小说
| 小说名称 | WebNovel 书籍ID |
|:--|--:|
| 作品名 | 11997642 |
### 有分部小说
| 分部号 | 分部名 | WebNovel 书籍ID | 卷数 |
|:--|:--|--:|--:|
| 第1部 | 分部一书名 | 11997642 | 3 |
| 第2部 | 分部二书名 | 11997643 | 2 |
## 卷名映射
### 分部一书名
| 卷号 | 卷名(英文) |
|--:|:--|
| 1 | Volume 1 Title |
| 2 | Volume 2 Title |
## 字段说明
- `小说名称` / `分部名`:必须精确匹配 SKILL 中的"书名判定规则"
- `分部号`:必须使用 `第N部` 规范格式(如 `第1部`、`第2部`),并与 `分部名` 一一对应,不得错位或复用
- `WebNovel 书籍ID`:纯数字,从 WebNovel Inkstone 作者后台获取
- `卷名`:WebNovel 平台上该卷的英文显示名称
## 登录方式
- **本文件不存储密码**。分发流程将在浏览器打开 WebNovel Inkstone,需要你在浏览器中**手动登录**。
- 确保登录账号拥有上述书籍的编辑权限。
## 注意事项
- 分发流程会检查登录状态;如未登录,需要你手动完成登录后再继续。
- 若出现安全验证、二次确认等额外步骤,需要你在浏览器中手动完成。
- 章节标题格式:`Chapter {章号} {章节标题}`(英文)。
WebNovel/README.md。WebNovel/分发记录.md,建立“章节唯一键 -> 章节Id”映射(建议唯一键:WebNovel书籍ID|分部名|卷号|章号|章名;无分部时分部名固定为 NA)。Chapter {章号} {章节标题}。https://inkstone.webnovel.com/novels/listWebNovel/分发记录.md 中该章节是否已有 章节Id。https://inkstone.webnovel.com/novels/chapter/create/{小说书籍ID}https://inkstone.webnovel.com/novels/chapter/edit/{书籍ID}/{章节ID}{书籍ID} 必须来自 README.md,且是纯数字字符串。README.md 中的目标书名。Chapter {章号} {章节标题}。章号 与 标题 之间只有一个空格。https://inkstone.webnovel.com/novels/chapter/edit/{书籍ID}/{章节ID}{章节ID},将其视为本章的最终 章节Id。WebNovel/分发记录.md:章节Id(如变化)和更新时间。最近操作 根据本次模式写为 新建 或 修改。> 章节唯一键生成规则(强制):
> - 使用字段顺序:WebNovel书籍ID|分部名|卷号|章号|章名
> - 其中:
> - WebNovel书籍ID 来自 WebNovel/README.md
> - 分部名 使用当前分部书名;无分部填 NA
> - 卷号、章号 必须是阿拉伯数字
> - 该规则用于避免多部小说、多卷并行分发时的同章号冲突。
章节Id:分发记录.md 决定新建/修改模式,不得沿用上一章模式。> 目标:在不放松现有强制规则的前提下,减少重复点击、重复粘贴与重复判定成本。
分发记录.md)。{平台目录}/.cache/content_parts/no_part/v{卷号}/*.txt、{平台目录}/.cache/content_parts/p{分部号}/v{卷号}/*.txt + 映射表),浏览器阶段只做“取数据→填写→保存”。1) 打开目标地址(按记录走新建或修改);
2) 填标题;
3) 填正文;
4) 填平台扩展字段(如作者话/发布设置);
5) 保存草稿;
6) 提取章节Id;
7) 回写记录。
5–10 章为一个小批次执行:分发记录.md;> D / E / F 三节统一执行口径:优先保证批次连续性与可回滚性;安全验证类提示一律按阻断处理;数据通道异常必须可降级。
scripts/content_parts_server.mjs:/parts/v/{卷号}/{章号}/parts/p/{分部号}/v/{卷号}/{章号}/parts/{章号} 仅限单批次临时使用。分发记录.md 的实时查询。content_parts_server)时必须提供降级路径:服务不可用时自动回退到本地文件读取,不中断批次执行。计划章数 = 成功章数 + 失败章数,且成功章均完成 章节Id 回写。https://inkstone.webnovel.com/novels/list,并在需要登录时等待用户手动完成登录。WebNovel/分发记录.md。https://inkstone.webnovel.com/novels/chapter/create/{小说书籍ID},修改模式必须使用 https://inkstone.webnovel.com/novels/chapter/edit/{书籍ID}/{章节ID},二者不得混用。Chapter {章号} {章节标题},且中间保留一个空格。SAVE 后 URL 应变更为 https://inkstone.webnovel.com/novels/chapter/edit/{书籍ID}/{章节ID};若未变化,必须继续确认,不得直接默认成功。章节Id 时,不得把本章标记为完全成功。WebNovel/README.md 或目标书籍Id。章节Id 获取失败:本章标记“部分成功(已保存,但章节Id未确认)”,要求人工补录章节Id,禁止静默成功。WebNovel/分发记录.md 写入失败:本章虽然可能已保存,但流程状态必须标记为“部分失败(记录未落盘)”,并立即提示人工补录,禁止静默成功。inkstone.webnovel.com 的登录步骤必须留给用户手动完成;这是硬边界,不要替用户“代劳”到过线。ADD AUTHOR'S THOUGHT 不是直接文本框,而是先弹出对话框,再在对话框里输入内容。SUBMIT 后再点 SAVE,能更稳妥地保证“作者有话说”和正文同时落盘。SAVE 后最终章节 URL 会变成 https://inkstone.webnovel.com/novels/chapter/edit/{书籍ID}/{章节ID};该 URL 是提取章节Id 的优先来源。> 以下经验基于第1卷第1-40章一次性上传 WebNovel 的实战总结,所有结论均已验证。
WebNovel 的 iframe 富文本编辑器不会自动检测 DOM 变更。仅用 frame.evaluate() 将 innerHTML 设置为正文后,SAVE 按钮虽然可点击,但保存不会成功(URL 不跳转到编辑页,仍停留在 create 页,也无报错提示)。
正确的注入与触发流程:
// 正文注入 + 变更检测(完整正确流程)
const frameEl = await page.locator('iframe').first().elementHandle();
const frame = await frameEl.contentFrame();
// 1. 聚焦编辑器
const body = frame.locator('body');
await body.click();
// 2. 注入正文(按单换行拆分,每一行独立成段)
await frame.evaluate((text) => {
document.body.innerHTML = '';
const lines = text.split('\n');
// 去掉首尾空行
while (lines.length > 0 && lines[0].trim() === '') lines.shift();
while (lines.length > 0 && lines[lines.length - 1].trim() === '') lines.pop();
// 每行一个 <p>,空行用 占位保留行间距
for (const line of lines) {
const trimmed = line.trim();
const el = document.createElement('p');
el.textContent = trimmed || '\u00A0';
document.body.appendChild(el);
}
}, bodyText);
// 3. 触发编辑器变更检测
await body.press('End'); // 等待 100ms
await body.press('Space'); // 等待 100ms
await body.press('Backspace'); // 等待 300ms
// 点击 SAVE
> ⚠️ 分段铁律:正文必须按 \n(单换行)拆分,每一行都是独立 <p>,禁止用 \n\n(双换行)合并多行为一段。
>
> WebNovel 英文版章节采用"句句独立成行 + 空行区分大段"的风格,HTML <p>.textContent 中的单换行 \n 会被浏览器折叠成空格,导致相邻行粘连成一个无法阅读的文本块。split('\n\n') 的合并逻辑在此场景下是致命错误,会造成已上传章节失去全部分段。已上传章节需以 split('\n') 方式重新上传覆盖修复。<|end▁of▁thinking|>
<||DSML||tool_calls>
<||DSML||invoke name="replace_string_in_file">
<||DSML||parameter name="filePath" string="true">d:\Users\lorns\OneDrive\第二职业\网文写作\坍缩侦探\小说分发\分发-WebNovel\SKILL.md
教训:不要假设"内容可见 = 编辑器已识别"。任何直接操作 DOM 的注入方式,末尾必须跟一次真实键盘事件(End → 打印字符 → 删除)来激活编辑器的变更追踪。
保存一章后立刻进入下一章的 create 页面,可能弹出:
> "There is an unsaved draft from a previous creation. Would you like to use it?"
page.locator('div[class*="modal"] button:has-text("Cancel")') 定位。首次点击 "ADD AUTHOR'S THOUGHT" 时,会在对话框前面弹出一个教学提示浮层,内含 "got it!" 按钮。必须点击 "got it!" 关闭浮层后,才能操作对话框内的 textarea。
page.locator('button:has-text("got it!")')isVisible() 时才点击,后续章节可能不再出现。不能只用 fill(text),因为旧值可能残留。必须三步:
titleInput.click()
titleInput.fill('') // 先清空
titleInput.fill(text) // 再填入
对 10 章以上批量场景:
.cache/content_parts/no_part/v{卷号}/ 目录(格式:{两位章号}_title.txt、{两位章号}_body.txt、{两位章号}_author.txt)。$env:CONTENT_PARTS_PORT='8765'; $env:DISTRIBUTION_PLATFORM_DIR='WebNovel'; node scripts/content_parts_server.mjshttp://127.0.0.1:8765/parts/v/1/{章号} 获取 JSON,存入内存对象。page.goto('http://127.0.0.1:8765/...') 获取数据,再 navigate 回 inkstone。| 步骤 | 建议等待 | 说明 |
|:---|:---|:---|
| 页面跳转后 | 1500ms | 等 iframe 编辑器和 JS 初始化完成 |
| 正文注入后 + 触发变更 | 300ms | 等编辑器消化 input 事件 |
| ADD AUTHOR'S THOUGHT 点击后 | 1000-1500ms | 等对话框 + 浮层完整加载 |
| SUBMIT 后 | 500-800ms | 等对话框关闭、内容落位 |
| SAVE 后 | 2500-3000ms | 等服务器保存 + URL 跳转 |
| Cancel 草稿弹窗后 | 300ms | 等弹窗消失 |
| 症状 | 根因 | 修复 |
|:---|:---|:---|
| SAVE 后 URL 仍为 /chapter/create/... | 编辑器未检测到内容变更 | 在注入正文后加 End→Space→Backspace |
| 上传后章节无分行分段,正文为一整块 | 用 split('\n\n') 合并多行为一段,textContent 中 \n 被 HTML 折叠 | 改用 split('\n'),每行独立 <p> |
| 标题未更新 | 直接用 fill() 未先清空 | 先 fill('') 再 fill(text) |
| 作者有话说未保存 | 未点 SUBMIT 或浮层遮挡 | 先消 "got it!" 浮层,再填 textarea + SUBMIT |
| 章节内容串到上一章 | 草稿弹窗点了 USE | 点 Cancel 清空后重新填写 |
| 章节Id 缺失但保存成功 | URL 未跳转或跳转延迟 | 等待更长时间(3000ms+),或用 page.url() 确认 |
每个小批次(5-10章)结束后必须对账:
计划章数 = 成功章数 + 失败章数章节Id(从 URL 中提取)分发记录.md> 用法:每次分发前按顺序快速过一遍;有任意一项不满足,就先修正再上传。
WebNovel/README.md 可读到用户名、密码、书籍Id、书名、卷名。README.md 一致。章节Id 走新建(/chapter/create/{书籍ID}),有 章节Id 走修改(/chapter/edit/{书籍ID}/{章节ID})。Chapter {章号} {章节标题}(Chapter + 阿拉伯数字 + 一个空格)。章节Id(URL 变更为 /novels/chapter/edit/{书籍ID}/{章节ID})。WebNovel/分发记录.md,且 章节唯一键 中 | 已按 Markdown 需要妥善处理。章节Id 未拿到、记录未写入:必须标记失败/部分成功并说明阻塞点。WebNovel/README.md 并提取必需字段。WebNovel/分发记录.md 并正确选择新建/修改 URL。章节Id。WebNovel/分发记录.md(含章节与章节Id映射)。Take lornshrimp/分发-webnovel from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.