{"skill":{"slug":"article-archiver","displayName":"Article Archiver","summary":"Automatically archive web articles and Twitter Articles to Feishu documents. ALWAYS trigger when user shares URLs from x.com (Twitter), mp.weixin.qq.com (WeC...","description":"---\nname: article-archiver\ndescription: Automatically archive web articles and Twitter Articles to Feishu documents. ALWAYS trigger when user shares URLs from x.com (Twitter), mp.weixin.qq.com (WeChat articles), or says \"保存/记录/收藏 [链接]\". Handles long articles (600+ lines), multiple images (20+ images), preserves formatting (bold, code blocks, lists), and sends success notification to user. Use immediately when detecting article URLs, don't ask for confirmation.\n---\n\n# Article Archiver\n\n自动抓取网页文章和 Twitter Article，完整归档到飞书文档。\n\n## ⚠️ 关键实现要点（必读）\n\n**1. 必须读取配置文件获取归档位置**：\n```bash\nsource ~/.openclaw/workspace/skills/article-archiver/config/feishu-locations.sh\n# 使用 $DEFAULT_SPACE_ID 和 $DEFAULT_PARENT_NODE\n```\n\n**2. 图片处理流程**：\n- 使用 `agent-browser` 打开页面\n- 滚动加载所有图片（懒加载）\n- 用 `img.preview` 或类似选择器过滤内容图片（排除头像、图标）\n- 分析每张图片在文章中的位置（找到前面最近的段落文本）\n- 下载图片到 `/tmp/`\n- 使用 `feishu_doc upload_image` 并指定 `after_block_id` 插入到正确位置\n- 关键：`upload_image` 支持 `after_block_id` 参数，可以精确控制图片位置\n\n**3. 样式保留**：\n- 引用块：使用 `>` 开头\n- 粗体：使用 `**text**`\n- 列表：使用 `-` 或 `1.`\n- 代码块：使用 ` ``` `\n\n**4. 内容传递方式**：\n- 使用 OpenClaw 的 `read` 工具读取文件\n- 不能在 feishu_doc 参数中使用 `$(cat file)`\n- 大文件分批处理（每批 100 行）\n\n**5. 去重检查**：\n- 归档前先搜索同名文档\n- 如果存在，返回已有文档链接\n- 不要重复创建\n\n**6. 防止乱码和格式问题**：\n- **UTF-8 编码**：\n  - 使用 `html-to-markdown-fixed.js`（而不是 `html-to-markdown-final.js`）\n  - 该脚本使用 Buffer 确保 UTF-8 编码正确，避免中文字符被截断\n  - 自动移除替换字符 `\\ufffd` 和 `���`\n- **标题格式修复**：\n  - 脚本会自动修复 `# \\n\\n内容` 格式为 `# 内容`\n  - 避免飞书渲染时标题为空、内容在下一行的问题\n- **验证方法**：\n  - 写入飞书前，检查 JSON 输出中是否有 `\\ufffd` 或 `���`\n  - 检查标题格式是否正确（标题标记和内容在同一行）\n- **如果仍有问题**：\n  - 不要手动重写原文内容，必须从原始来源精确提取\n  - 长文本分段写入时，不要在多字节字符中间截断\n\n**7. 避免重复标题**：\n- 飞书文档已经有页面标题（Page title），不要在正文开头再写一次标题\n- 从原文提取内容时，跳过第一个 H1 标题（通常就是文章标题）\n- 如果 web_fetch 或 agent-browser 提取的内容以标题开头，删除第一行\n- 正文应该从引言、摘要或第一段正文开始\n- 元数据后直接接正文，不要插入标题\n\n**8. 元数据格式（重要）**：\n- **必须使用引用块格式**（灰色背景，有样式，好看）\n- **标准格式**（参考 https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe）：\n  ```markdown\n  > **原始链接：** https://example.com/article\n  > **归档时间：** 2026-03-12 22:16:00\n  > **来源：** example.com\n  > **作者：** 张三 (@username)\n  \n  ---\n  ```\n- **关键点**：\n  - 每行以 `>` 开头（引用块）\n  - 字段名用 `**粗体**` + 冒号\n  - 字段值用普通文本\n  - 元数据和正文之间用分隔线 `---` 分开\n- **❌ 不要使用**：\n  - Markdown 表格（会被渲染成飞书表格，不美观）\n  - 普通文本（没有样式，不好看）\n  - Emoji（📄、📅、✍️）在引用块中显示不好\n\n**9. 图片位置精确控制**：\n- 图片必须插入到正确的段落之后，不能随意放置\n- 使用 `feishu_doc upload_image` 时，必须指定 `after_block_id`\n- 步骤：\n  1. 先写入全部正文内容\n  2. 使用 `feishu_doc list_blocks` 获取所有 block_id\n  3. 根据图片前面的文本内容，找到对应的 block_id\n  4. 用 `after_block_id` 参数将图片插入到正确位置\n- 不要在写入正文时就尝试插入图片（会导致位置错乱）\n\n## 核心功能\n\n- ✅ 支持长文章（600+ 行）\n- ✅ 支持多图片（20+ 张），图片插入到正确位置\n- ✅ 保留完整格式（粗体、代码块、列表）\n- ✅ 按月份自动组织\n- ✅ 自动去重（检测重复 URL）\n\n## 使用方法\n\n### 触发条件\n\n**自动触发**（无需用户明确说\"归档\"）：\n- 用户发送 Twitter/X 链接：`https://x.com/...` 或 `https://twitter.com/...`\n- 用户发送微信公众号链接：`https://mp.weixin.qq.com/...`\n- 用户说\"保存 [链接]\"、\"记录 [链接]\"、\"收藏 [链接]\"\n\n**手动触发**：\n- 用户明确说\"归档这篇文章\"、\"保存到飞书\"\n- 用户发送其他网页链接并询问是否归档\n\n**触发后的行为**：\n1. 立即开始归档，不要询问确认\n2. 归档过程中可以简短提示\"正在归档...\"\n3. 归档成功后发送通知消息（包含文档链接）\n4. 归档失败时说明原因，提供解决建议\n\n### 归档流程\n\n#### 0. 去重检查（必须先执行）\n\n**在开始归档前，必须先检查文档是否已存在**：\n\n```bash\n# 1. 提取文章标题（使用修复版脚本）\nTITLE=$(node scripts/html-to-markdown-fixed.js \"$URL\" \"$(cat config/twitter-cookies.txt)\" | jq -r '.title')\n\n# 2. 在知识库中搜索同名文档\nfeishu_wiki nodes --space-id 7527734827164909572 --parent-node NqZvwBqMTiTEtkkMsRoc76rznce | \\\n  jq -r '.nodes[] | select(.title == \"'\"$TITLE\"'\") | .node_token'\n\n# 3. 如果找到同名文档，跳过归档\nif [ -n \"$EXISTING_NODE\" ]; then\n  echo \"⚠️ 文档已存在，跳过归档\"\n  echo \"已存在的文档：https://qingzhao.feishu.cn/wiki/$EXISTING_NODE\"\n  exit 0\nfi\n```\n\n**去重规则**：\n- 标题完全一样 → 视为已存在\n- 跳过归档，返回已存在文档的链接\n- 不要重复创建相同标题的文档\n\n**实现方式**：\n1. 先提取文章标题（不需要完整内容）\n2. 在目标位置搜索同名文档\n3. 如果存在，直接返回链接并跳过\n4. 如果不存在，继续正常归档流程\n\n#### 1. 识别文章类型\n\n```javascript\nif (url.includes('x.com') && url.includes('/status/')) {\n  // Twitter Article - 需要特殊处理\n  return 'twitter-article';\n} else {\n  // 普通网页\n  return 'web-page';\n}\n```\n\n#### 2. 抓取内容\n\n**Twitter Article**（长文章 + 多图片）：\n```bash\n# 使用修复版脚本（解决乱码和格式问题）\ncd ~/.openclaw/workspace/skills/article-archiver\nnode scripts/html-to-markdown-fixed.js \"$URL\" \"$(cat config/twitter-cookies.txt)\" > /tmp/article.json\n```\n\n**普通网页**：\n```bash\n# 使用 web_fetch\nweb_fetch --url <url> --extract-mode markdown\n```\n\n#### 3. 分段写入（核心方法）\n\n**⚠️ 重要：正确的文件内容传递方式**\n\n在 OpenClaw 会话中，**不能使用 bash 命令替换 `$(cat file)` 直接传给 feishu_doc**，因为会被当成字面字符串。\n\n**正确方法**：\n\n**方法 1：使用 OpenClaw 的 read 工具**（推荐）\n```javascript\n// 1. 先用 read 工具读取文件\nconst content = await read({ path: '/tmp/content.txt' });\n\n// 2. 然后传给 feishu_doc\nawait feishu_doc({\n  action: 'append',\n  doc_token: DOC_TOKEN,\n  content: content\n});\n```\n\n**方法 2：分批读取和上传**（适合大文件）\n```javascript\n// 读取文件总行数\nconst totalLines = parseInt(await exec({ \n  command: 'wc -l < /tmp/article-body.txt' \n}));\n\nconst BATCH_SIZE = 100;\nlet currentLine = 1;\n\nwhile (currentLine <= totalLines) {\n  const endLine = Math.min(currentLine + BATCH_SIZE - 1, totalLines);\n  \n  // 读取这批内容\n  const content = await read({\n    path: '/tmp/article-body.txt',\n    offset: currentLine,\n    limit: BATCH_SIZE\n  });\n  \n  // 上传到飞书\n  await feishu_doc({\n    action: 'append',\n    doc_token: DOC_TOKEN,\n    content: content\n  });\n  \n  currentLine = endLine + 1;\n  \n  // 延迟，避免 API 限流\n  await new Promise(resolve => setTimeout(resolve, 300));\n}\n```\n\n**图片处理**（关键步骤）：\n```bash\n# 1. 使用 agent-browser 打开页面并提取图片\nagent-browser open \"https://example.com/article\"\nsleep 3\n\n# 2. 滚动页面加载所有图片（懒加载）\nagent-browser scroll down 5000\nsleep 2\nagent-browser scroll down 5000\nsleep 2\n\n# 3. 提取图片 URL（过滤头像和 SVG）\nagent-browser eval 'JSON.stringify(\n  Array.from(document.querySelectorAll(\"img\"))\n    .filter(img => !img.src.startsWith(\"data:\") && \n                   !img.src.includes(\"avatar\") && \n                   img.naturalWidth > 300)\n    .map(img => img.src)\n)' > /tmp/image-urls.json\n\n# 4. 下载并上传每张图片\ncat /tmp/image-urls.json | jq -r '.[]' | while read url; do\n  curl -s -o /tmp/img.jpg \"$url\"\n  # 使用 feishu_doc upload_image 上传\n  # 注意：必须用 file_path 参数，不是 path\ndone\n\n# 5. 关闭浏览器\nagent-browser close\n```\n\n**图片上传注意事项**：\n- 使用 `feishu_doc` 的 `upload_image` action\n- 参数名是 `file_path`，不是 `path`\n- 每次上传后延迟 300ms 避免限流\n- 图片会自动插入到文档末尾\n\n**样式保留**：\n- 使用 Markdown 格式保留样式\n- 引用块：使用 `>` 开头\n- 代码块：使用 ` ``` ` 包裹\n- 列表：使用 `-` 或 `1.` 开头\n- 粗体：使用 `**text**`\n\n**关键原则**：\n- 必须先读取文件内容到变量\n- 然后把变量传给 feishu_doc\n- 不能在 feishu_doc 参数中使用 bash 命令替换\n- 大文件分批处理（每批 100 ���）\n- 分批之间延迟 0.3 秒\n- 图片单独上传，不要嵌入 Markdown\n\n#### 4. 添加元数据头部\n\n**标准格式**（引用块 + 粗体字段名，参考 https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe）：\n\n```javascript\n// 提取元数据\nconst metadata = JSON.parse(await exec({ \n  command: 'cat /tmp/article.json' \n}));\n\nconst title = metadata.title;\nconst author = metadata.author;\nconst username = metadata.username;\nconst timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');\n\n// 判断来源\nlet source, authorText;\nif (url.includes('x.com') || url.includes('twitter.com')) {\n  source = 'x.com (Twitter Article)';\n  authorText = `${author} (@${username})`;\n} else if (url.includes('mp.weixin.qq.com')) {\n  source = '微信公众号';\n  authorText = author;\n} else {\n  source = url.match(/https?:\\/\\/([^/]+)/)[1];\n  authorText = author;\n}\n\n// 写入元数据头部（引用块格式）\nawait feishu_doc({\n  action: 'append',\n  doc_token: DOC_TOKEN,\n  content: `> **原始链接：** ${url}\n> **归档时间：** ${timestamp}\n> **来源：** ${source}\n> **作者：** ${authorText}\n\n---`\n});\n```\n\n**关键点**：\n- 每行以 `>` 开头（引用块）\n- 字段名用 `**粗体**` + 冒号\n- 字段值用普通文本\n- 不要用 Emoji（📄、📅、✍️）\n- 元数据和正文之间用分隔线 `---` 分开\n\n#### 5. 写入正文内容\n\n**使用 OpenClaw read 工具读取并写入**：\n\n```javascript\n// 读取正文内容\nconst content = await read({ path: '/tmp/article.json' });\nconst articleContent = JSON.parse(content).content;\n\n// 写入正文\nawait feishu_doc({\n  action: 'append',\n  doc_token: DOC_TOKEN,\n  content: articleContent\n});\n```\n\n**飞书会自动处理**：\n- Markdown 图片语法 `![alt](url)` → 自动下载并转换为内部图片\n- 代码块 ` ```language ... ``` ` → 渲染为代码块\n- 粗体、列表、标题等 → 正确渲染\n\n#### 6. 验证完整性\n\n```bash\n# 读取文档，检查块数\nfeishu_doc read --doc_token $DOC_TOKEN\n\n# 验证：\n# - 文字段落数 = 预期段落数\n# - 图片数 = 预期图片数\n# - 总块数 = 文字段落数 + 图片数\n```\n\n#### 7. 发送成功通知\n\n**成功消息格式**：\n```\n✅ 文章已归档到飞书\n\n📄 标题：[文章标题]\n🔗 原文：[原始链接]\n📁 位置：学习资料抓取 → 待阅读\n🔗 飞书：[飞书文档链接]\n\n📊 统计：\n- 内容：[行数] 行\n- 图片：[数量] 张\n- 格式：完整保留\n```\n\n**失败消息格式**：\n```\n❌ 归档失败\n\n原因：[失败原因]\n建议：[解决建议]\n\n原文链接：[原始链接]\n```\n\n**使用 message 工具发送**：\n```javascript\n// 成功通知\nmessage({\n  action: \"send\",\n  target: \"ou_a39e6975059f975a7ffd25892243b64a\",  // 威哥的 user_id\n  message: \"✅ 文章已归档到飞书\\n\\n📄 标题：...\\n🔗 飞书：...\"\n});\n\n// 失败通知\nmessage({\n  action: \"send\",\n  target: \"ou_a39e6975059f975a7ffd25892243b64a\",\n  message: \"❌ 归档失败\\n\\n原因：...\\n建议：...\"\n});\n```\n\n## 配置\n\n### 飞书归档位置\n\n**配置文件**：`config/feishu-locations.sh`\n\n```bash\n# 默认归档位置\nDEFAULT_SPACE_ID=\"7527734827164909572\"\nDEFAULT_PARENT_NODE=\"YziUwLVlBi9BX7kVtkJcf7nQns2\"  # 学习资料抓取 → 待阅读\n\n# 可选的归档位置\nLOCATION_TO_READ=\"7527734827164909572:YziUwLVlBi9BX7kVtkJcf7nQns2\"  # 待阅读\nLOCATION_ROOT=\"7527734827164909572:Lfj1d3Pcmo0o2IxrHrOcMsffnZb\"      # 根目录\n```\n\n**使用方法**：\n\n1. **修改默认位置**：\n   ```bash\n   # 编辑配置文件\n   vim ~/.openclaw/workspace/skills/article-archiver/config/feishu-locations.sh\n   \n   # 修改 DEFAULT_PARENT_NODE 为目标 node_token\n   DEFAULT_PARENT_NODE=\"YziUwLVlBi9BX7kVtkJcf7nQns2\"\n   ```\n\n2. **临时指定位置**（调用时传参）：\n   ```bash\n   # 归档到指定节点\n   feishu_wiki create --parent-node <node_token> --title \"文章标题\"\n   ```\n\n3. **添加新位置**：\n   ```bash\n   # 在配置文件中添加\n   LOCATION_ARCHIVE=\"7527734827164909572:XXX\"  # 归档目录\n   LOCATION_DRAFT=\"7527734827164909572:YYY\"    # 草稿目录\n   ```\n\n**获取 node_token**：\n```bash\n# 方法 1：从飞书 URL 中提取\n# https://qingzhao.feishu.cn/wiki/YziUwLVlBi9BX7kVtkJcf7nQns2\n# node_token = YziUwLVlBi9BX7kVtkJcf7nQns2\n\n# 方法 2：使用 feishu_wiki 工具查询\nfeishu_wiki nodes --space-id 7527734827164909572\n```\n\n**当前配置的位置**：\n- **待阅读**（默认）：`YziUwLVlBi9BX7kVtkJcf7nQns2`\n  - 路径：学习资料抓取 → 待阅读\n  - 用途：新归档的文章默认放这里\n- **根目录**：`Lfj1d3Pcmo0o2IxrHrOcMsffnZb`\n  - 路径：学习资料抓取\n  - 用途：直接归档到根目录\n\n### Twitter Cookie\n\nTwitter Article 需要登录 Cookie：\n\n```bash\n# Cookie 文件位置\n~/.openclaw/workspace/skills/article-archiver/config/twitter-cookies.txt\n\n# 格式\nauth_token=xxx; ct0=yyy; twid=zzz\n\n# 更新 Cookie\necho \"auth_token=xxx; ct0=yyy; twid=zzz\" > config/twitter-cookies.txt\n```\n\n### 文档格式\n\n```markdown\n# 学习资料抓取\n\n# 2026-03\n\n## 文章标题\n\n> **原始链接**：https://example.com/article\n> \n> **归档时间**：2026-03-11 20:00:00\n> \n> **来源**：example.com\n> \n> **作者**：作者名 (@username)\n\n文章内容...\n\n![图片1](image_url)\n\n更多内容...\n\n![图片2](image_url)\n\n---\n```\n\n## 支持的平台\n\n### Twitter / X\n\n**URL 格式**：\n- `https://x.com/username/status/123456789`\n- `https://twitter.com/username/status/123456789`\n\n**特点**：\n- 支持长文章（Twitter Article）\n- 支持多图片（20+ 张）\n- 需要 Cookie 认证\n\n### 微信公众号\n\n**URL 格式**：\n- `https://mp.weixin.qq.com/s/...`\n\n**特点**：\n- 支持富文本格式\n- 支持图片\n- 无需认证\n\n### 普通网页\n\n**支持的网站**：\n- 技术博客（Medium、Dev.to、掘金等）\n- 新闻网站\n- 个人博客\n\n**特点**：\n- 使用 `web_fetch` 抓取\n- 自动提取正文\n- 保留基本格式\n\n## 使用示例\n\n### 示例 1：Twitter 链接（自动触发）\n\n**用户**：https://x.com/mkdir700/status/2020652753190887566\n\n**系统行为**：\n1. 检测到 Twitter 链接，自动触发归档\n2. **检查去重**：在知识库中搜索同名文档\n3. 如果不存在，继续归档流程\n4. 提示：\"正在归档...\"\n5. 抓取完整内容（672 行 + 24 张图片）\n6. 归档到飞书（学习资料抓取 → 待阅读）\n7. 发送成功通知：\n   ```\n   ✅ 文章已归档到飞书\n   \n   📄 标题：月成本不到 100 元，如何实现 Token 自由\n   🔗 原文：https://x.com/mkdir700/status/2020652753190887566\n   📁 位置：学习资料抓取 → 待阅读\n   🔗 飞书：https://qingzhao.feishu.cn/wiki/XXX\n   \n   📊 统计：\n   - 内容：672 行\n   - 图片：24 张\n   - 格式：完整保留\n   ```\n\n### 示例 2：重复文档（自动跳过）\n\n**用户**：https://x.com/mkdir700/status/2020652753190887566\n\n**系统行为**：\n1. 检测到 Twitter 链接，自动触发归档\n2. **检查去重**：发现同名文档已存在\n3. 跳过归档，返回已存在文档的链接\n4. 发送通知：\n   ```\n   ⚠️ 文档已存在，跳过归档\n   \n   📄 标题：月成本不到 100 元，如何实现 Token 自由\n   🔗 已存在的文档：https://qingzhao.feishu.cn/wiki/XXX\n   ```\n\n### 示例 2：微信公众号（自动触发）\n\n**用户**：https://mp.weixin.qq.com/s/abc123\n\n**系统行为**：\n1. 检测到微信公众号链接，自动触发归档\n2. 抓取文章内容\n3. 归档到飞书\n4. 发送成功通知\n\n### 示例 3：手动触发\n\n**用户**：保存 https://example.com/article\n\n**系统行为**：\n1. 检测到\"保存\"关键词 + 链接\n2. 自动触发归档\n3. 归档成功后发送通知\n\n### 示例 4：归档失败\n\n**用户**：https://x.com/someuser/status/123（Cookie 过期）\n\n**系统行为**：\n1. 尝试抓取，发现 Cookie 过期\n2. 发送失败通知：\n   ```\n   ❌ 归档失败\n   \n   原因：Twitter Cookie 已过期\n   建议：\n   1. 浏览器登录 x.com\n   2. 复制 Cookie（auth_token, ct0, twid）\n   3. 更新配置文件：config/twitter-cookies.txt\n   \n   原文链接：https://x.com/someuser/status/123\n   ```\n\n## 脚本工具\n\n### html-to-markdown-fixed.js（推荐使用）\n\n从 Twitter Article HTML 转换为格式化 Markdown，修复了 UTF-8 乱码和标题格式问题：\n\n```bash\nnode scripts/html-to-markdown-fixed.js <article_url> <cookie_string>\n```\n\n**功能**：\n- ✅ 修复 UTF-8 编码问题（使用 Buffer 确保中文字符不被截断）\n- ✅ 修复标题格式问题（`# \\n\\n内容` → `# 内容`）\n- ✅ 自动移除替换字符 `\\ufffd` 和 `���`\n- ✅ 保留粗体（`**text**`）\n- ✅ 保留代码块（` ```shell ... ``` `）\n- ✅ 保留列表（`- item`）\n- ✅ 提取图片 URL\n\n**与旧版本的区别**：\n- `html-to-markdown-final.js`：旧版本，存在乱码和格式问题\n- `html-to-markdown-fixed.js`：新版本，已修复所有已知问题\n\n### archive-long-article.sh\n\n处理长文章（600+ 行）+ 多图片（20+ 张）：\n\n```bash\ncd ~/.openclaw/workspace/skills/article-archiver/scripts\n./archive-long-article.sh <article_url> <doc_token>\n```\n\n**功能**：\n1. 抓取完整内容（使用 html-to-markdown-fixed.js）\n2. 提取图片 URL\n3. 按图片位置分段\n4. 生成段落清单（manifest.json）\n5. 输出 feishu_doc 命令（需要在 OpenClaw 环境执行）\n\n## 常见问题\n\n### Q1: 中文出现乱码（���）？\n\n**原因**：UTF-8 多字节字符在传输或序列化时被截断。\n\n**解决**：\n- 使用 `html-to-markdown-fixed.js` 而不是 `html-to-markdown-final.js`\n- 该脚本使用 Buffer 确保 UTF-8 编码正确\n- 自动移除替换字符\n\n### Q2: 标题为空，内容在下一行？\n\n**原因**：Markdown 格式 `# \\n\\n内容` 导致飞书渲染时标题为空。\n\n**解决**：\n- 使用 `html-to-markdown-fixed.js`\n- 脚本会自动修复标题格式为 `# 内容`\n\n### Q3: 图片位置不对，都堆在文档末尾？\n\n**原因**：先写完所有文字，再统一上传图片。\n\n**解决**：按图片位置分段，交替写入文本和图片。\n\n### Q4: 内容不完整，只有开头部分？\n\n**原因**：长文章一次性写入失败。\n\n**解决**：使用 `archive-long-article.sh` 脚本，分段处理。\n\n### Q5: 格式丢失（粗体、代码块）？\n\n**原因**：使用了简单的文本提取，没有保留 HTML 格式。\n\n**解决**：使用 `html-to-markdown.js` 脚本，正确转换格式。\n\n### Q4: 执行慢、消耗大量 token？\n\n**原因**：手动逐个调用 `feishu_doc` 工具。\n\n**解决**：用 bash 脚本批量处理，脚本执行不消耗 token。\n\n### Q5: Cookie 过期怎么办？\n\n**症状**：抓取 Twitter Article 失败，返回登录页面。\n\n**解决**：\n1. 浏览器登录 x.com\n2. 打开开发者工具 → Application → Cookies\n3. 复制 `auth_token`, `ct0`, `twid`\n4. 更新 `config/twitter-cookies.txt`\n\n## 成功案例\n\n### 案例 1：mkdir700 长文章\n\n- **URL**：https://x.com/mkdir700/status/2020652753190887566\n- **标题**：月成本不到 100 元，如何实现 Token 自由\n- **内容**：672 行\n- **图片**：24 张\n- **结果**：✅ 完整归档，图片位置正确，格式完整\n- **文档**：https://qingzhao.feishu.cn/docx/NZHpd5xHxoTjYPxlVfpcaKtOnvh\n\n### 案例 2：暂星的文章\n\n- **URL**：https://x.com/lumoswhy/status/2030807300257300613\n- **内容**：451 行\n- **图片**：5 张\n- **结果**：✅ 完整归档，格式正确\n\n## 注意事项\n\n1. **版权**：仅用于个人学习和归档\n2. **去重**：同一 URL 不重复归档\n3. **错误处理**：抓取失败时提示用户，记录日志\n4. **API 限流**：每次操作间隔 0.3 秒\n5. **Cookie 管理**：定期检查 Twitter Cookie 是否过期\n\n## 改进历史\n\n- **2026-03-10**：初始版本，支持基本归档\n- **2026-03-11**：重大改进\n  - 支持长文章（600+ 行）\n  - 支持多图片（20+ 张）\n  - 图片位置正确（分段写入 + 交替插入）\n  - 格式完整保留（粗体、代码块、列表）\n  - 添加 `archive-long-article.sh` 脚本\n  - 添加完整性验证\n  - 添加飞书归档位置配置（支持灵活修改目标位置）\n  - **添加自动触发机制**（检测 Twitter/微信公众号链接）\n  - **添加成功/失败通知**（自动推送消息给用户）\n  - **支持微信公众号**（mp.weixin.qq.com）\n\n---\n\n*最后更新：2026-03-11 20:55*\n*维护者：影*\n\n## 最新改进（2026-03-11）\n\n### 使用 turndown 库\n\narticle-archiver 现在使用业界标准的 **turndown** 库进行 HTML 到 Markdown 的转换，确保高质量的格式保留。\n\n**核心脚本**：`scripts/html-to-markdown-final.js`\n\n**关键特性**：\n1. ✅ **代码块支持**：正确识别 `<pre>/<code>` 标签，保留语言标注（bash、json、yaml、text 等）\n2. ✅ **图片支持**：保留 Markdown 图片语法 `![alt](url)`，飞书会自动下载并转换\n3. ✅ **格式保留**：粗体、列表、标题、引用块、链接等全部正确转换\n4. ✅ **GFM 扩展**：支持 GitHub Flavored Markdown（表格、删除线等）\n\n**技术栈**：\n- **Playwright**：无头浏览器，处理动态加载的内容\n- **turndown**：HTML 转 Markdown（8.8k GitHub stars）\n- **turndown-plugin-gfm**：GFM 扩展支持\n\n**代码块处理**：\n```javascript\n// 自定义代码块规则：保留语言标注\nturndownService.addRule('codeBlock', {\n  filter: function (node) {\n    return node.nodeName === 'PRE' && node.querySelector('code');\n  },\n  replacement: function (content, node) {\n    const code = node.querySelector('code');\n    const language = code.className.replace('language-', '') || '';\n    const text = code.textContent;\n    return '\\n\\n```' + language + '\\n' + text + '\\n```\\n\\n';\n  }\n});\n```\n\n**图片处理**：\n- turndown 默认将 `<img>` 转换为 `![alt](url)` 格式\n- 飞书 Markdown 解析器会自动下载外链图片并转换为内部图片\n- 无需手动上传图片，速度快，不会超时\n\n**元数据头部**：\n```markdown\n> **原始链接：** https://x.com/username/status/123456789\n> **归档时间：** 2026-03-11 22:50:00\n> **来源：** x.com (Twitter Article)\n> **作者：** 作者名 (@username)\n\n---\n```\n\n### 归档质量\n\n**测试案例**：mkdir700 的 sub2api 教程\n- 原文：https://x.com/mkdir700/status/2020652753190887566\n- 归档结果：\n  - ✅ 代码块：16 个（bash、json、yaml、text）\n  - ✅ 图片：24 张（全部自动转换）\n  - ✅ 总块数：229 个\n  - ✅ 格式：完整保留\n\n**性能**：\n- 提取时间：~10 秒\n- 上传时间：~3 分钟（包括图片自动下载）\n- 总耗时：~4 分钟\n\n### 故障排查\n\n**问题 1：代码块丢失**\n- **原因**：turndown 默认规则无法识别某些代码块结构\n- **解决**：添加自定义 `codeBlock` 规则，显式处理 `<pre>/<code>` 标签\n\n**问题 2：图片丢失**\n- **原因**：自定义规则移除了图片\n- **解决**：移除 `removeImages` 规则，让 turndown 保留图片的 Markdown 语法\n\n**问题 3：图片位置不对**\n- **原因**：先收集所有图片，然后按顺序插入，无法保证位置准确\n- **解决**：使用 turndown 的默认行为，在遍历 DOM 时即时转换图片\n\n**问题 4：Cookie 过期**\n- **症状**：无法访问 Twitter Article，返回登录页面\n- **解决**：更新 `config/twitter-cookies.txt` 文件\n\n### 依赖安装\n\n```bash\ncd ~/.openclaw/workspace/skills/article-archiver\nnpm install turndown turndown-plugin-gfm playwright\n```\n\n### 使用最新脚本\n\n```bash\n# 提取文章（包含代码块和图片）\nnode scripts/html-to-markdown-final.js \\\n  \"https://x.com/username/status/123456789\" \\\n  \"$(cat config/twitter-cookies.txt)\" \\\n  > /tmp/article.json\n\n# 检查提取结果\njq '.content' /tmp/article.json | head -50\n\n# 统计代码块和图片\necho \"代码块: $(jq -r '.content' /tmp/article.json | grep -c '```')\"\necho \"图片: $(jq -r '.content' /tmp/article.json | grep -c '!\\[')\"\n```\n\n### 未来改进\n\n- [ ] 支持更多平台（Medium、知乎、微信公众号）\n- [ ] 自动检测重复 URL\n- [ ] 支持批量归档\n- [ ] 添加标签和分类\n- [ ] 支持全文搜索\n\n## 已知限制和解决方案（2026-03-12）\n\n### 1. 飞书嵌入文档无法抓取\n\n**问题**：\n- 某些网站（如火山引擎开发者社区）的文章是嵌入的飞书文档\n- 页面显示\"暂时无法在飞书文档外展示此内容\"\n- 无法通过普通网页抓取获取内容和图片\n\n**识别方法**：\n```javascript\n// 检查是否为飞书嵌入文档\nconst firstParagraph = document.querySelector('article p')?.textContent;\nif (firstParagraph?.includes('暂时无法在飞书文档外展示此内容')) {\n  // 这是飞书嵌入文档，无法抓取\n}\n```\n\n**解决方案**：\n- **跳过归档**：这类文章本身就在飞书里，不需要重新归档\n- **提示用户**：告知用户这是飞书文档，提供原始飞书链接\n- **示例**：https://developer.volcengine.com/articles/7615547765435432996\n  - 实际内容在：https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe\n\n### 2. 嵌套代码块问题\n\n**问题**：\n- 当原文包含 markdown 代码块（教程类文章），飞书的 Markdown 解析器无法正确处理嵌套的代码块\n- 例如：markdown 代码块中包含 markdown 代码示例\n\n**表现**：\n- 代码块内容会跑到外面，变成普通文本\n- 格式混乱\n\n**解决方案**：\n- 目前无完美解决方案（飞书 Markdown 解析器限制）\n- 内容完整，但格式可能不完美\n- 建议：接受当前格式，或在飞书中手动调整\n\n### 2. 嵌套代码块问题\n\n**问题**：\n- 某些 Twitter 图片链接格式特殊，飞书无法自动转换\n- 例如：`https://pbs.twimg.com/media/...?format=jpg&name=small`\n\n**解决方案**：\n1. **方案 A**（推荐）：使用 turndown 提取的 Markdown，一次性写入，飞书自动处理\n   - 优点：快速、简单\n   - 缺点：某些特殊格式图片可能无法转换\n\n2. **方案 B**：手动下载并上传图片\n   - 下载图片到本地\n   - 使用 `feishu_doc upload_image` 逐个上传\n   - 优点：图片一定能显示\n   - 缺点：慢、复杂\n\n3. **方案 C**：分段写入（文本 → 图片 → 文本）\n   - 按原文顺序交替写入文本和图片\n   - 优点：图片位置准确\n   - 缺点：最慢、最复杂\n\n**推荐流程**：\n1. 先尝试方案 A（一次性写入）\n2. 如果图片无法显示，再使用方案 B（手动上传）\n\n### 3. Twitter 图片处理\n\n**重要规则**：\n- ❌ 不要在云空间根目录创建文档（用户看不到，没有操作权限）\n- ✅ 必须在知识库中创建文档（使用 `feishu_wiki create`）\n- ✅ 默认位置：`NqZvwBqMTiTEtkkMsRoc76rznce`（学习资料抓取 → 待阅读）\n\n**正确的创建方式**：\n```bash\nfeishu_wiki create \\\n  --space-id 7527734827164909572 \\\n  --parent-node NqZvwBqMTiTEtkkMsRoc76rznce \\\n  --title \"$TITLE\" \\\n  --obj-type docx\n```\n\n### 4. 归档位置\n\n**功能**：\n- 在归档前检查是否已存在同名文档\n- 标题完全一样 → 视为已存在\n- 跳过归档，返回已存在文档的链接\n\n**使用方法**：\n```bash\n# 检查去重\nRESULT=$(bash scripts/check-duplicate.sh \"$URL\")\n\n# 解析结果\nif [[ $RESULT == EXISTS:* ]]; then\n  NODE_TOKEN=$(echo \"$RESULT\" | cut -d: -f2)\n  TITLE=$(echo \"$RESULT\" | cut -d: -f3)\n  echo \"⚠️ 文档已存在：https://qingzhao.feishu.cn/wiki/$NODE_TOKEN\"\n  exit 0\nfi\n```\n\n**脚本位置**：`scripts/check-duplicate.sh`\n\n## 最佳实践总结\n\n### 归档流程（推荐）\n\n1. **去重检查**：\n```bash\nbash scripts/check-duplicate.sh \"$URL\"\n```\n\n2. **提取文章**：\n```bash\nnode scripts/html-to-markdown-final.js \"$URL\" \"$(cat config/twitter-cookies.txt)\" > /tmp/article.json\n```\n\n3. **创建文档**（知识库）：\n```bash\nTITLE=$(jq -r '.title' /tmp/article.json)\nfeishu_wiki create --space-id 7527734827164909572 --parent-node NqZvwBqMTiTEtkkMsRoc76rznce --title \"$TITLE\" --obj-type docx\n```\n\n4. **添加元数据头部**（引用块格式）：\n```javascript\n// 提取元数据\nconst metadata = JSON.parse(await exec({ command: 'cat /tmp/article.json' }));\nconst timestamp = new Date().toISOString().slice(0, 19).replace('T', ' ');\n\n// 判断来源\nlet source, authorText;\nif (url.includes('x.com') || url.includes('twitter.com')) {\n  source = 'x.com (Twitter Article)';\n  authorText = `${metadata.author} (@${metadata.username})`;\n} else if (url.includes('mp.weixin.qq.com')) {\n  source = '微信公众号';\n  authorText = metadata.author;\n} else {\n  source = url.match(/https?:\\/\\/([^/]+)/)[1];\n  authorText = metadata.author;\n}\n\n// 写入元数据头部\nawait feishu_doc({\n  action: 'append',\n  doc_token: DOC_TOKEN,\n  content: `> **原始链接：** ${url}\n> **归档时间：** ${timestamp}\n> **来源：** ${source}\n> **作者：** ${authorText}\n\n---`\n});\n```\n\n5. **写入正文内容**：\n```javascript\n// 读取正文内容\nconst content = await read({ path: '/tmp/article.json' });\nconst articleContent = JSON.parse(content).content;\n\n// 写入正文\nawait feishu_doc({\n  action: 'append',\n  doc_token: DOC_TOKEN,\n  content: articleContent\n});\n```\n\n6. **验证结果**：\n```bash\nfeishu_doc list_blocks --doc_token $DOC_TOKEN | jq '.blocks | length'\n```\n\n### 故障排查\n\n**问题 1：图片无法显示**\n- 检查图片 URL 格式\n- 尝试手动下载并上传\n\n**问题 2：代码块格式错误**\n- 检查是否有嵌套代码块\n- 接受当前格式，或手动调整\n\n**问题 3：文档创建在错误位置**\n- 检查是否使用 `feishu_wiki create`\n- 检查 `parent_node` 参数\n\n**问题 4：重复归档**\n- 使用去重检查脚本\n- 检查标题是否完全一致\n\n**问题 5：元数据头部没有样式**\n- **症状**：元数据头部是普通文本，没有灰色背景\n- **原因**：没有使用引用块格式\n- **解决**：\n  - 每行以 `>` 开头（引用块）\n  - 字段名用 `**粗体**` + 冒号\n  - 参考正确格式：https://qingzhao.feishu.cn/wiki/ZVCFwN7bci1uyhknLpucA18FnSe\n  - 不要用 Emoji（📄、📅、✍️）\n  - 不要用 Markdown 表格\n\n**问题 6：内容写入错误（显示命令而非内容）**\n- **症状**：文档中显示 `$(cat /tmp/file.md)` 而不是实际内容\n- **原因**：在 OpenClaw 中使用了 bash 命令替换，不会被执行\n- **解决**：\n  - ❌ 错误：`feishu_doc append --content \"$(cat file.md)\"`\n  - ✅ 正确：先用 `read` 工具读取文件，再传递内容\n  ```javascript\n  const content = await read({ path: '/tmp/file.md' });\n  await feishu_doc({ action: 'append', content: content });\n  ```\n\n**问题 7：UTF-8 乱码（字符被截断）**\n- **症状**：`那么��读和自媒体`（\"阅\"字显示为 `��`）\n- **原因**：多字节 UTF-8 字符在传输或分段时被截断\n- **解决**：\n  - 确保完整的字符串传递，不要在字符中间截断\n  - 使用 OpenClaw 的 `read` 工具读取文件（自动处理编码）\n  - 避免使用 bash 命令的字符串截取（如 `head -c`）\n\n**问题 8：归档位置错误**\n- **症状**：文档创建在错误的知识库位置\n- **原因**：使用了错误的 `parent_node_token`\n- **解决**：\n  - 检查配置文件：`config/feishu-locations.sh`\n  - 确认 `DEFAULT_PARENT_NODE` 的值\n  - 正确位置：`NqZvwBqMTiTEtkkMsRoc76rznce`（学习资料抓取 → 待阅读）\n  - 错误位置：`YziUwLVlBi9BX7kVtkJcf7nQns2`（旧位置）\n\n","topics":["Feishu","Twitter"],"tags":{"latest":"1.0.0"},"stats":{"comments":0,"downloads":665,"installsAllTime":25,"installsCurrent":0,"stars":0,"versions":1},"createdAt":1773559401910,"updatedAt":1779078297999},"latestVersion":{"version":"1.0.0","createdAt":1773559401910,"changelog":"- Initial release of article-archiver.\n- Automatically archives shared web and Twitter (X) articles to Feishu documents, triggered by links or specific user phrases.\n- Supports long articles (600+ lines), multiple images (20+), and preserves formatting (bold, code blocks, lists).\n- Ensures precise image placement and complete metadata with stylish citation block.\n- Includes robust duplicate detection by article title, and notifies user on success.\n- Fully automatic: archives as soon as a supported URL is detected—no extra confirmation needed.","license":"MIT-0"},"metadata":null,"owner":{"handle":"yemoo","userId":"s17d1aedfb4x20wdpfwb6crkwh83j4b9","displayName":"yemoo","image":"https://avatars.githubusercontent.com/u/1298185?v=4"},"moderation":{"isSuspicious":false,"isMalwareBlocked":false,"verdict":"clean","reasonCodes":["review.llm_review"],"summary":"Review: review.llm_review","engineVersion":"v2.4.24","updatedAt":1780089895116}}