腾讯文档同步钉钉文档

SKILL效率
效率 未标注权限 许可证不明确
安装这个技能

把腾讯文档(文件夹/单篇)高保真同步到钉钉知识库。当要把腾讯文档的文档、在线表格、流程图搬到钉钉知识库,并要求"格式不变、图片不丢、代码块不丢、表格合并单元格不丢、同名覆盖不新建、不要导出成本地 Word"时使用。覆盖三条链路:文字文档(docx→JSONML,保真度最高:真合并单元格/真代码块/真标题)、在线表格(导出→钉钉在线表格)、流程图(flowchart.read→自渲染 SVG)。

一句话选型结论

按资料字段整理

腾讯文档同步钉钉文档是效率类的 Agent Skill,安装方式未标注,权限级别未标注,许可证不明确,有可直接复制的安装命令。技能本质是给模型读的说明书,效果取决于模型与客户端;装完先用一个小任务验证,再放进正式流程。

分类效率 安装方式未标注 权限级别未标注权限 许可证许可证不明确
累计安装 未标注 本周 未标注
GitHub 星标 未标注 仓库 未标注
下载量 未标注 技能市场下载数
权限级别 未标注权限 安装前请确认风险范围
许可证 许可证不明确 未说明

安装

未标注
在支持 Skills 的客户端里执行,装完问一句「你有哪些技能」验证
skillhub install user_d8449002/tencent-docs-to-dingtalk-wiki
  1. 1
    确认客户端支持

    确认你的客户端支持 Skills(Claude、Codex、Cursor 等),并升级到较新的版本。

  2. 2
    拿到技能文件

    执行上面的命令安装;也可以把仓库里的 SKILL.md 手动放进客户端的 skills 目录。

  3. 3
    刷新技能列表

    重新打开客户端,或刷新技能列表,让新放进来的技能被加载。

  4. 4
    验证是否生效

    问一句「你有哪些技能」,确认列表里出现它,再拿一个小任务试跑。

技能档案

效率
字段来自技能仓库与公开索引,中文说明由本站标注
作者 / 维护方user_d8449002仓库 未标注 · 版本 未标注
宿主客户端未标注技能可被这些客户端加载
安装方式未标注安装包 community
权限级别未标注权限安装前请确认风险范围
许可证未说明许可证不明确,商用前请自行确认条款
仓库可用性available技能仓库当前是否可访问
最近核验未标注上游最后核验时间 · 数据更新 1791620763
正文体量31.6 KBSKILL.md 原文大小,越大说明说明书写得越细

典型使用场景

3 类
按分类整理的常见用法,具体以 SKILL.md 为准
整理待办与日程

把零散任务收进清单,排出优先级。

批量处理重复任务

把固定动作做成模板,重复执行。

把流程写成清单

把口头流程整理成可复用的步骤。

选择建议

装之前先看这几条
按条目字段给出的判断,不替你决定用不用

值得选的地方

  • 有可直接复制的安装命令,不用自己翻文档找入口。

先想清楚的地方

  • 没有标注权限级别,实际能做什么要装好后确认。
  • 许可证不明确,商用或二次分发前先到仓库确认条款。
  • 没有做站内核验,内容以作者仓库为准。
  • 技能只是说明书,实际效果取决于模型与客户端,装完先用小任务试一遍。

更适合

  • 需要在效率场景里给模型补一套做法的人。
  • 愿意先读一遍 SKILL.md、确认它会让模型做什么的人。

不太适合

  • 对许可证要求很严、必须明确可商用的人。
  • 只想装完就自动变强、不打算看说明书的人。

同类对比

6 个同类
同分类里挑最接近的几条,字段以各自详情页为准
技能权限级别安装方式累计安装宿主客户端安装命令
腾讯文档同步钉钉文档当前 未标注权限 未标注 未标注 未标注 有
Nano PDF 文档 联网访问 ClawHub 市场 424.9 万 OpenClaw 有
自动更新程序 浏览器控制 ClawHub 市场 296.4 万 OpenClaw 有
Gemini 助手 联网访问 ClawHub 市场 254.7 万 OpenClaw 有
word DOCX 文档 联网访问 ClawHub 市场 239.6 万 OpenClaw 有
Excel XLSX 前端 联网访问 ClawHub 市场 227.0 万 OpenClaw 有

SKILL.md 全文

31.6 KB
模型实际读到的说明书原文,来自技能仓库

腾讯文档 → 钉钉知识库 高保真同步

为什么不能直接用 get_content

腾讯文档 MCP 的 get_content 只返回纯文本投影,实测会丢三类东西:

内容 get_content 结果
代码块 完全丢失。代码块在腾讯侧是浮动 TextBox,不占正文文本流。例:请求成功返回: 后面直接跳到下一节,中间的 JSON 样例整段没有
图片 拿不到,需另外调 doc.get_images 拿 URL + pos
标题层级 / 表格结构 拿不到,需 doc.resolve_document_structure(但也读不到代码块正文,include_textbox_children 也展开不了,只返回 [TextBox])

结论:只要原文档可能有代码块或图片,就必须走「导出 → 还原」路线,不能用 get_content 打草稿。

环境准备

前置:本机装好 WorkBuddy,并在连接器里授权「腾讯文档」和「钉钉」。

脚本不写死本机路径,按「环境变量 > 常见安装位置」自动探测,找不到会打印该设哪个环境变量:

依赖 环境变量(优先级最高) 自动探测位置
腾讯文档 CLI 用的 python TD_PY ~/.workbuddy/binaries/python/versions/*/python.exe,最后退回当前解释器
腾讯文档 CLI 本体 TDOCS ~/.workbuddy/plugins/cache/*/tencent-docs-plugin/*/skills/tencent-docs/tencentdocs.py
钉钉 dws CLI DWS ① %APPDATA%\qianfan-desktop-app\lightsandbox\node\dws.cmd(Windows,目前实测有效)② ~/.workbuddy/binaries/node/cli-connector-packages/node_modules/dingtalk-workspace-cli/bin/dws.js
跑 dws.js 的 node DWS_NODE ~/.workbuddy/binaries/node/versions/*/node.exe → PATH 里的 node

> dws 的安装位置随 WorkBuddy 版本变过(早期在 .workbuddy/binaries/node/.../dws.js,后来挪到 qianfan 的 dws.cmd)。
> 两个候选都保留了;都不中就直接设 DWS,别去改脚本。

手动验证两条链路是否通:

"$DWS" wiki node list --workspace <知识库ID> --format json          # 钉钉
"$TD_PY" "$TDOCS" tdoc_call tencent-docs manage.folder_list '{"folder_id":"<FOLDER_ID>"}'   # 腾讯

Windows 下的两个老坑(与本机相关,换机器不一定复现):

  • Bash 里 head/tail/ls/sleep 可能不存在(Git Bash shim 损坏),会把整条命令连同 python 一起带崩(exit 127)。用 PowerShell 7 更稳。
  • PowerShell 的 stdout 偶尔抓不全 → 让脚本把结果写文件,再读文件。

三类型分流

manage.folder_list 返回的 url 可判类型:

  • docs.qq.com/doc/* → 文字文档 → docx 还原路线
  • docs.qq.com/sheet/* → 在线表格 → 导出后 dws doc import(钉钉在线表格)
  • docs.qq.com/flowchart/* → 流程图 → flowchart.read + 自渲染 SVG(腾讯导出会报错 导出processOn文件失败,此路不通)

路线 A:文字文档(docx → JSONML,默认)

> 为什么默认走 JSONML 而不是 Markdown:Markdown 表达不了合并单元格,代码块也只是围栏文本
> (早期方案里钉钉甚至把代码块转成 SVG 图片,看得见复制不了)。
> JSONML 能写出真正的合并单元格、真正的 code 块和真正的标题层级。
> 只有需要纯文本草稿时才用 --format md。

1. 导出(腾讯)

manage.export_file {"file_id": ...}            → task_id
manage.export_progress {"task_id": ...}        → 每 3s 轮询,progress==100 拿 file_url
                                                 典型耗时 3~6s
GET file_url                                   → 存到系统 temp 目录(不要存用户工作区)

2. 图片 URL

doc.get_images {"file_id":...} → [{image_url, pos, source}],按 pos 升序排好后,与 docx 里 drawing 的出现顺序一一对应。
数量常有 1~2 张的差额(封面/重复),对齐消耗用不完属正常。

3. 解析 docx 的关键点

  • 代码块 = <w:txbxContent>,其下每个 <w:p> 是一行 → 拼成围栏代码块。
    语言标签可按内容猜(以 {/[ 开头且带引号 → json;using System → csharp;< 开头 → xml…),猜不出留空。
  • 标题层级:腾讯导出的 pStyle/@w:val 是随机串(如 z2xbei),真实名称在 word/styles.xml 的 <w:style w:styleId=...><w:name w:val="heading 1"/>。必须先建 styleId→name 映射再判层级。
    Title 不转成 #(钉钉文档标题已经是它),heading N → #*N。
  • 表格:<w:tbl> → ["table", ...]。合并单元格必须按下面的写法,否则服务端静默忽略:
  • 纵向合并 w:vMerge(restart / continue):起始格算 rowSpan(向下数连续的 continue),
    被覆盖的位置要输出 ["tc", {"colSpan":1,"rowSpan":1,"hidden":true}, ["p",…]] —— hidden:true 是必须的。
  • 横向合并 w:gridSpan val=N:起始格 colSpan=N,后面 N-1 格不输出。
  • colSpan/rowSpan 即使等于 1 也显式写出,与钉钉 canonical 形态一致。
  • 物理网格必须完整:每行输出的 tc 数要等于列数(被覆盖处用 hidden 占位补齐)。
  • 列宽别均分:读 <w:tblGrid>/<w:gridCol w:w>,按比例缩放到总和 650pt 写进 colsWidth
    (钉钉只把它当比例用),否则宽表会被拉成等宽,观感和原文差很远。
  • 回读校验:dws doc +fetch --node <id> --detail full 拿 JSONML,续行应是 hidden 占位,数字对得上。
  • 超链接:腾讯 docx 里几乎没有 <w:hyperlink r:id>,链接是字段指令:
    <w:fldChar begin> → <w:instrText>HYPERLINK https://xxx \tdfe ...</w:instrText> → separate → <w:t>显示文字</w:t> → end。
    只取 <w:t> 会只剩文字、丢掉 URL。解析要点:HYPERLINK 后的 URL 常常不带引号,正则要同时支持 HYPERLINK "url" 和 HYPERLINK url。
    需要把腾讯链接改写成钉钉链接时,用 dws drive info 取 dentryId 拼 transit 链接:
    https://alidocs.dingtalk.com/api/doc/transit?spaceId=<spaceId>&dentryId=<dentryId>&corpId=<corpId>
    (这就是钉钉 UI 里复制文档链接得到的形式;腾讯 URL 的 slug 与 folder_list 返回的 url 末段一一对应))。
  • 图片:顶层 <w:p> 内 <a:blip> 个数 = 该段图片数,按序从 URL 队列取值,写 ![](url)。
  • 顺手清理 HYPERLINK xxx normalLink \tdfe -10 ... 这类导出残留。

3b. 自动编号(numbering.xml)与钉钉原生自动编号

源文档的编号不在文本里。 Word/腾讯的多级编号是段落上的 <w:numPr>(ilvl, numId)
引用 word/numbering.xml 的定义(numFmt 编号格式、lvlText 如 %1. / %1.%2、
start 起始值),由排版引擎算出 "1." / "2.1" / "一、" 再显示。只取 <w:t> 会整段丢编号
(典型反馈:"自动编号也没了")。

重建要点(见 build_numbering / Numbering / para_num_info / fmt_num):

  • w:num 把 numId 映射到 abstractNumId;w:lvl 按 ilvl 给出各级 numFmt/lvlText/start。
  • 计数按 numId(列表实例)分开;出现更浅层级时,更深层归零。
  • numFmt 要支持 decimal / lowerLetter / lowerRoman / chineseCountingThousand
    (一、二、…十、十一,中文数字得自己转)。
  • 空 <w:numPr/> = 没有编号;numId=0 = 显式取消编号。
  • 腾讯导出的 docx 常不写 w:suff,实测渲染为「编号紧贴文字」,所以缺省不加空格。

写到钉钉有两种做法,默认走原生自动编号:

做法 写法 何时用
原生自动编号(默认) 给 h1~h6 挂 attrs.list,编号由钉钉生成 有编号的标题(推荐)
固定编号 把算出的编号写进标题文本 --no-heading-auto-number,或 Markdown 载体(MD 表达不了 list)

原生自动编号的规范(照抄自「在钉钉 UI 里手动加一次自动编号 → 回读 JSONML」):

["h1", {
  "ind": {"hanging": 0, "left": 0},
  "list": {
    "listId": "heading-xxxxxxxxxxxx",
    "level": 0,
    "isOrdered": true, "isTaskList": false, "autoLevel": true,
    "listStyleType": "DEC_DEC_DEC_P",
    "listStyle": {"format": "decimal", "text": "%1", "align": "left"},
    "symbolStyle": {"bold": true, "sz": 21}
  }
}, ["span", {"data-type":"text"}, ["span", {"data-type":"leaf"}, "标题"]]]
  • listId 形如 heading- 前缀,同一编号序列的标题共享;level 为 0 起。
  • listStyle.text 按层级递增:%1 / %1.%2 / %1.%2.%3(四级即 %1.%2.%3.%4),
    配合 DEC_DEC_DEC_P 渲染成 1 / 1.1 / 1.1.1。
  • level 必须按「标题级别」算(h1→0、h2→1),不能用源 ilvl —— 源文档各级标题
    可能同属 ilvl=0(各自是独立列表),照搬会渲染成 1. 2. 3. 而不是 1 / 1.1。
  • 标题上手打的编号(1.2.1、一、、第一章)要先剥掉再挂 list,否则重复编号。
    中文序号(一、/第一章)也要识别,不能只认阿拉伯数字。
  • 编号样式若钉钉预设没有,就统一用 1 / 1.1 / 1.1.1(DEC_DEC_DEC_P)。
  • text 不带尾点(%1 不是 %1.)——尾点由 listStyleType 的 _P 决定。
  • symbolStyle 不要抄(踩过,2026-09-17):上面样例里的 "symbolStyle": {"bold": true, "sz": 21}
    是 h1 单级的取值,原样复制给所有层级会把各级标题字号拉平(h2 和 h3 一样大,用户一眼看出)。
    默认不写 symbolStyle,钉钉会按标题级别用各自默认字号;真要写就按层级给不同 sz。
  • 自动编号的数字字形是钉钉内置字,改不了字体族(踩过,2026-09-17):symbolStyle 里只有 bold / sz / szUnit 对
    编号数字字号和字重生效;fonts: {ascii, eastAsia, ...} 写在 symbolStyle 里会被静默忽略,编号数字始终用
    钉钉列表默认字(PDF 导出里看到的是 ArialMT / Arial-BoldMT)。所以「编号数字」和「中文标题」字体族天然不一致
    (如标题是 PingFangSC-Semibold、编号是 Arial),只能做到字号一致 + 字重一致(加 bold:true),
    字体族无法统一。若要编号与标题完全同字族,只能退回「手写编号文本」(牺牲自动重排)。— WorkBuddy国内版

缩进:ind 是段落属性,不能塞进 list(踩过)

"ind": {"hanging": 0, "left": 0} 必须和 list 平级放在 node[1] 里。
写进 list 内部服务端静默忽略,标题就按 list level 被逐层缩进——
h2/h3 会一节节往右跑。二/三级标题天然不该缩进,顶格即可。

node[1]["list"] = heading_list_attrs(lvl, list_id)
node[1]["ind"]  = {"hanging": 0, "left": 0}   # 平级!不是 list["ind"]

正文有序列表:只写本级占位符,不是全路径

正文和标题的 listStyle.text 规则不一样,混用会取不到计数器:

listStyleType level 0 / 1 / 2 的 text 渲染
标题 DEC_DEC_DEC_P %1 / %1.%2 / %1.%2.%3 1 / 1.1 / 1.1.1
正文 DEC_LEN_LROM_P %1. / %2. / %3. 1. / a. / i.
  • 正文每级只写本级占位符(level:1 → "%2."),不是 "%1.%2";
    正文 text 带尾点,标题不带。
  • 正文格式按 level 三级一轮:decimal → lowerLetter → lowerRoman → decimal…
  • 正文 listId 不带 heading- 前缀,按源 numId 分组(不同列表各自从 1 数)。
  • 剥编号后要判空:text.strip() 为空就别挂 list,否则出空节点。

  • 正文统一用 1. / a. / i. 这一种样式,不要按源 numFmt 去猜、也不要照抄参考文档里的
    正文样式——实测照搬/猜都容易错。标题仍是 1 / 1.1 / 1.1.1。

参考实现:scripts/sync_docs.py 的 heading_list_attrs / body_list_attrs。

4. 写入钉钉(JSONML)

# 新建
dws doc create --name "标题" --workspace <WS> --content-file body.json --content-format jsonml
# 覆盖已有(同名不新建)
dws doc update --node <nodeId> --content-file body.json \
               --content-format jsonml --mode overwrite --yes
  • 根节点必须是 ["root", {}, ...blocks],否则报 root 校验错误。
  • 内容用 --content-file 传;--content -(stdin)实测会传不进去,+create --content @file
    又只接受工作目录内的相对路径。
  • 常用节点:h1~h6 标题、p 段落、code {code, syntax} 代码块(可复制,不是图片)、
    table/tr/tc(colSpan/rowSpan)、img {src}(必须包在 p 里)、a {href}。
  • a 节点不接受裸字符串,子节点要写成
    ["a", {"href": url}, ["span", {"data-type":"text"}, ["span", {"data-type":"leaf"}, "文字"]]],
    否则 validator 报 bare text not allowed in "a"。
  • 图片 URL 会被钉钉抓取并内联到自己的 OSS(回读变成 alidocs2.oss-cn-zhangjiakou.aliyuncs.com),不用手动上传。
  • 取同名节点清单:dws wiki node list --workspace <WS>(dws doc list 已废弃)。
    必须递归子文件夹(--folder <nodeId>),见坑清单。

4b. 写入钉钉(Markdown,备选)

  • 已有同名节点 → dws doc update --node <id> --content-file x.md --mode overwrite --yes
  • 没有 → dws doc create --name <标题> --workspace <WS> --content-file x.md
  • 合并单元格只能二选一:--merge-mode blank(续行留空,标准做法,默认)或 fill(续行重复填值)。

5. 大文档必须分段

单篇超过约 3 万字符时,overwrite 会分片写入并报 TIMEOUT_ERR(可重试但常失败)。
可靠做法:12000 字符一段,第 1 段 overwrite,后续段 append。

成功判定要写成 '"success":true' in out.replace(" ", "").replace("\n", "") —— out 里混有 [INFO] 前缀行,且 JSON 是带空格的,写 '"success": true' 会永远判成失败。

路线 B:在线表格

导出得到 .xlsx → dws doc import --file <tmp.xlsx> --workspace <WS> --name <标题> -y → 钉钉在线表格,格式保真。
若知识库里已有同名旧文档(比如早期用纯文本方案建的空壳),先 dws drive delete --node <dentryUuid> -y 移到回收站(30 天可恢复),再导入,避免出现 XXX(1) 副本。

路线 C:流程图

腾讯侧导出必失败,改用:

  1. flowchart.read {"file_id":...} → mx_cells_xml(裸 mxCell)
  2. 自己解析:vertex=1 取 x/y/width/height + value(标签,内含 <div>/<br>,需清洗);edge=1 取 source/target
  3. 渲染成 SVG(矩形 + 中心文字 + 带箭头的连线),末尾叠加一张图
  4. dws doc create 建一篇文档写「节点清单 + 连线关系」→ dws doc media insert --node <id> --file x.svg -y 插图

参考实现:scripts/sync_docs.py --kind flowchart 的用法见文件末尾。

坑清单

现象 原因 / 解法
代码块变成看得到复制不了的图 走了 doc import --file xx.docx:钉钉把 TextBox 代码块转成了 data:image/svg+xml;base64。必须走 docx→Markdown 还原
doc update 报「第 1 片写入失败 … TIMEOUT_ERR」 内容太长,改分段 overwrite+append
脚本里明明成功却判失败 成功判定字符串没去空格,见上文
drive delete 报 nodeId 格式不合法…32 位 要传 32 位 dentryUuid。drive info 返回的 dentryId 是 12 位纯数字(如 236599452310),传它必然报错;直接用 nodeId(32 位)即可,或用 dws drive list 的 fileId。另:wiki node list 偶发给 31 位 nodeId 也要换
resolve_document_structure 的 mode 不生效 传 mode:2 仍返回 effective_mode=compact;要 full 得传 has_idx:true + idx
Git Bash 报 command not found: ls/head/tail/sleep shim 损坏,见「环境准备」
标题全变成普通段落 没做 styles.xml 的 styleId→name 映射
表格里的字"不见了" 纵向合并 vMerge:continue 是空单元格,要向下填充上方 restart 的内容
表格后面的列整体错位 横向合并 gridSpan 没展开补空,各行实际列数不一致
链接只剩文字没有 URL 超链接是 fldChar/instrText 字段,不是 <w:hyperlink>;且 HYPERLINK 后 URL 常不带引号
rowSpan/colSpan 写了却没合并 省略了被覆盖位置的 tc。网格必须完整,续行用 hidden 占位补齐,见「解析 docx 的关键点」
合并单元格行高被撑到整页、没合并的行也跟着变形/错位 续行占位只写了空 tc、漏了 "hidden": true。钉钉把空 tc 当真单元格 → 该行列位右移、行高按整页撑开。必须 {"colSpan":1,"rowSpan":1,"hidden":true}
表格每列被拉成等宽 colsWidth 一律均分。要按源 docx tblGrid 比例缩放
同名文档被重复新建成 XXX(1) node_map 只扫了根目录,文档其实在子文件夹里 → 必须递归 --folder
链接改写全部落空 同上(node_map 不递归)+ 单篇同步时没传 --folder,映射表是空的
bare text not allowed in "a" JSONML 里 a 的子节点不能是裸字符串,要包 span > leaf
JSONML 写入报「回读结果与完整初始内容不一致」 用了 --content -(stdin)。改用 --content-file
表格块回读只有 cells/colSize/rowSize 正常:CLI 投影不暴露合并信息,判据是续行被覆盖处为空串
想生成 transit 链接却拿不到数值 spaceId CLI 给不出(drive info 只有 dentryId,wiki space get 只有 workspaceId)。--space-id/--corp-id 不传时脚本自动退回 https://alidocs.dingtalk.com/i/nodes/<nodeId>,可用。要严格 transit 形式就让用户在 UI 里复制一条链接,从里面读出 spaceId
整轮同步崩在 node_map:'NoneType' object has no attribute 'get' wiki node list 返回的 nodes 数组里混着 null(不是空对象)。遍历要 isinstance(n, dict) 过滤
--exclude 传 file_id 完全没生效 --exclude 只匹配标题;要按 id 精确挑文档改用 --only(它同时认 id 和标题)
PowerShell 传中文参数匹配不到(ITEMS 0) 命令行中文被编码弄坏。挑文档一律用 ASCII 的 file_id,别用标题
JSONML 写完报 business error: success=false / HSF 超时 单篇太大。脚本会自动剥 uuid + 重试(JSONML_BIG 默认 12 万字符触发);再失败才退回 Markdown。退回后编号是写死在文本里的(MD 表达不了原生编号),超大文档的已知代价
导出报 drivecgi Service Error, code:11607 / no_task_id:{} 腾讯服务端对该文档的导出接口是坏的,CLI / MCP 两条通道、docx/pdf/txt/md/html 全部格式都返回同一个错,重试无效。降级方案:改用 get_content 取正文(代码块会被平铺到末尾且丢失归属,要按语义手工归位),然后手工拼 JSONML 写入 —— 代码块用真 ["code", {"syntax": ..., "code": ...}] 节点,标题照常挂 heading_list_attrs
粘贴的腾讯链接 slug 解不开(base64 非法) 链接被截断/大小写错。腾讯 slug 去掉首字母后是 file_id 的 urlsafe base64;解不开就穷举单字符修正(改 1 个字符 → base64 decode → query_file_info 验证),十几到几十次调用即可命中
doc block insert 报「JSON 语法错误: unexpected end of JSON input」 走了 dws.cmd。cmd.exe 会截断含中文的长 JSON 参数。改成直接 node <dws.js> ...(dws.js 在 ~/.workbuddy/binaries/node/cli-connector-packages/node_modules/dingtalk-workspace-cli/bin/dws.js),绕开 cmd.exe 后 60/60 全成功
逐块 insert 越跑失败越多(business-level error),且失败的都是小块 服务端限流,不是体积问题(32KB 的大表格反而成功)。要降频(块间 sleep ~0.4s)+ 指数退避(5/15/45s)
doc block insert 传数组想一次插多块 → Invalid JSONML node: element array 一次只能插一个块。多块要循环调用
精简 JSONML 想省体积:["p", {}, "文本"] 服务端(CLI)会自动 wrap raw string into span/text/leaf 修回去,等于白省。能省的只有 colSpan/rowSpan 等于 1 的字段(不会被补回)。别在 p 里塞裸字符串
用户给的钉钉链接当成目标文档,结果文档被塞到别处 / 报错 先用 dws drive info --node <id> 看 nodeType:folder 就当 --parent(文档建进该文件夹),file 才是对它做覆盖写。别想当然
回读 block list --content-format jsonml 看到 46 块全是 p,图片/编号一个都没有 回读是简化投影,图片和有序列表都不体现在 blockType 里。想确认图片和编号真的写进去了,用 dws drive export --node <id> --export-format markdown 导出后统计 ![ 条数和有序列表行数
drive export 明明成功却拿到 exit code 1 [INFO] 进度行走 stderr,PowerShell 把它判成 NativeCommandError。看输出里的 "success": true + 目标文件是否存在,别信 exit code
标题的手工编号是字母形式(a: / b. / h: xxx)时没挂原生编号,字母前缀还留着 MANUAL_NUM_ANY_RE 只认数字和中文数字(1.2.3 / 1、 / 一、 / 第一章),不认字母编号 → 这类标题 numbered=False,既没有原生编号、前缀也没剥。产后修法:回读找出无 list 的 h 块,doc block update 挂 heading_list_attrs(层级-1, 文档里已有的 listId) + ind:{left:0,hanging:0},同时剥掉 ^[a-zA-Z]\s*[::.、]\s*
修完一轮只修了一部分就说「没有更多目标」 block update 会重建块并换 blockId,导致后续分页漂移;而且分页请求偶发失败返回空(token 抖动)时若直接 break,会漏掉文档后半部分的块。必须:① 每修一个重新 block list;② 整页失败要重试整轮,不能 break 后拿残缺列表判断"修完了"
取块 ID 拿到 None block list --content-format jsonml 返回的块字段是顶层 blockId,不是 element.id(默认格式下才是 element.id)
sheet/other 全部 NEED_MANUAL ext=None,但文件明明下载到了 导出物被存成了 .bin,扩展名检测必然落空。现在 export_file() 会从 Content-Disposition / URL 推断真实扩展名(sheet→.xlsx)并拼到文件名上
OK_IMPORT 报成功,但钉钉里根本没这篇 dws doc import 的 --folder 与 --workspace 互斥,同时传报 if any flags in the group [folder workspace] are set none of the others can be。而且这个错误只在 stderr/返回码里,stdout 是空的 → 脚本原来只看 nodeId 就会误判成功。现在已改成显式判失败并列 ERR_IMPORT。用法:有 --parent 就只传 --folder,否则才传 --workspace
dws drive list --node <文件夹id> 返回 items: [],但文档明明在里面 列知识库子目录必须带 --workspace:dws drive list --workspace <WS> --folder <folderId>。只给 --node 会被路由到钉盘,返回空
--file 直通时某篇被判成 other、标题还变成 file_id manage.query_file_info 偶发返回空(token 抖动),type/title 拿不到。已加重试 3 次。误判成 other 会让 doc 走 import 路线 → 代码块变图片

超大文档(超过 JSONML 单次上限)怎么办

JSONML 单次 overwrite 有体积上限(实测 20 万字符 OK / 30 万字符失败),且服务端明确不支持 append
(原文:--content-format jsonml 当前仅支持 --mode overwrite,append 模式将在后续版本支持)。

❌ 不推荐:首写 + 逐块 block insert

理论上可行(doc block insert --content-format jsonml --element <单块> --index 999999,
标题 list / 表格合并 / 代码块都能保留),但实测在千块规模会崩:
1102 块的文档跑到 800+ 块时服务端限流加剧,出现连续失败(880/882/885/886/887/889…),
耗时 1h47m 仍失败 20+ 块,且失败块只能补到末尾造成错位。别在这条路上耗时间。

✅ 推荐:Markdown 回退 + 只给标题补原生编号

退回 Markdown 能保证内容 100% 完整(实测 1102 块 / 67 表格 / 298 图一个不少,
和源 JSONML 块数完全一致),代价是标题编号被写死进文本。补两步即可:

  1. 挂 list:对每个标题块 doc block update --block-id <id> --content-format jsonml --element <新块>,
    在 attrs 里加 list: heading_list_attrs(层级-1, 同一个listId)。305 个标题约 15 分钟,0 失败。
  2. 剥号:同一个 update 里把 ["span",{"data-type":"leaf"},"1.2.3 标题"] 的文本换成
    strip_manual_number() 剥掉前缀后的结果。否则会双重编号(1. 1.2.3 标题)。

三个坑(都踩过):
- 必须同时写段落级 ind: {"hanging":0,"left":0},和 list 平级放在 node[1]。
只挂 list 不加 ind,标题会按 list level 被逐层缩进(h2/h3 往右跑),
「标题不缩进、只有正文多级编号才缩进」这条就不成立。
heading_list_attrs() 只返回 list,ind 要调用方自己加(HEADING_IND)。
- block update 会重建块并返回新的 blockId。预先缓存的 id 会失效,
报错是空的 NOJSON。必须用最新一次 block list 的结果,别拿旧快照。
- 取 JSONML 里的文本要匹配 ["span",{...},["span",{...},"文本"]] 结构。
别写"遇到第一个 str 就返回"的递归——那会返回 tag 字符串 "span",导致剥号逻辑静默失效(stripped=0)。

改完必须用 block list 回读确认四项:headings / auto_numbered / ind_left0 / baked。
参考产出:《低代码框架功能说明V2》1102 块 / 305 标题,四项全绿。

⚠️ 探测体积上限会留下测试文档,用完必须删

为了确认「20 万字符 OK / 30 万字符失败」,会在知识库根目录建若干测试文档
(实测用过 SLIM_FULL_TEST / SLIM_FULL_TEST(1) / BIG_A_FULL / BIG_B_SLIM / LIM_300K
这类名字,一对一对应 %TEMP% 下的同名 .json)。这些是垃圾文档,会污染目录、
让用户在目录面板里看到莫名其妙的条目。规矩:

  1. 探测完立刻删,别等用户发现;
  2. 删除目标必须是测试文档,不要碰正式文档;
  3. 删除前先 dws drive +search-docs --query <名字> 列出候选、确认身份,
    再 dws drive delete --node <32位nodeId> -y(进回收站,30 天可恢复);
  4. 删完再用 +search-docs 复核 count: 0;
  5. 本地 %TEMP% 的同名 .json 一并删掉。

批量确认身份 + 删除的现成写法:

# 1) 搜索(只读)
node "$DWS" drive +search-docs --query LIM_300K --format json      # 给 nodeId
# 2) 元数据(拿 createTime / workspaceId / 父目录,核对确实是测试产物)
node "$DWS" drive info --node <nodeId> --format json
# 3) 删除(nodeId 必须是 32 位)
node "$DWS" drive delete --node <nodeId> -y --format json
# 4) 复核
node "$DWS" drive +search-docs --query LIM_300K --format json      # 期望 count: 0

常用命令

脚本:scripts/sync_docs.py(绝对路径见「环境准备」,用 3.13 的 python 跑)。

# 全量同步一个文件夹(默认 JSONML)
python sync_docs.py --folder <FOLDER_ID> --workspace <WS> \
  --kinds doc --format jsonml --exclude "要跳过的子文件夹" \
  --rewrite-links --space-id <spaceId> --corp-id <corpId>

# 同步进知识库里的某个子文件夹(不是根目录)
python sync_docs.py --folder <FOLDER_ID> --workspace <WS> \
  --parent <钉钉文件夹nodeId> --kinds doc --format jsonml

# 只同步一篇 / 几篇(--file 直通 file_id,不依赖文件夹)
python sync_docs.py --file <FILE_ID> --workspace <WS> --format jsonml
python sync_docs.py --file <FILE_ID1>,<FILE_ID2> --workspace <WS> --format jsonml

# 单篇同步但链接映射仍要全量:--folder 与 --file 一起传(folder 只用于建映射,不处理)
python sync_docs.py --folder <FOLDER_ID> --file <FILE_ID> --workspace <WS> --rewrite-links ...

# 只枚举看看有什么,不写入
python sync_docs.py --folder <FOLDER_ID> --workspace <WS> --list

# 单篇同步到知识库里的某个子文件夹(--file + --parent 组合,不依赖腾讯文件夹)
python sync_docs.py --file <FILE_ID> --workspace <WS> --parent <钉钉文件夹nodeId> --format jsonml
# 回读校验(图片/编号):block list 是简化投影,要用导出确认
DWS=<dws.js 路径>
node "$DWS" drive export --node <钉钉nodeId> --export-format markdown --output /tmp/x.md
# 然后统计 ![ 条数(图片)、^\s*\d+\.\s 行数(有序列表)
参数 说明
--file 逗号分隔 file_id,支持 <id> 或 <id>:<自定义标题>;与 --folder 二选一
--only 逗号分隔关键词,在已枚举出的条目里再筛 file_id / 标题
--kinds 只处理这些类型,如 --kinds doc(表格/流程图会被跳过)
--parent 写入指定钉钉文件夹的 nodeId(不传则建在知识库根目录)。传了之后查重范围也收窄到该文件夹内,避免把别处的同名文档误判成「已存在」而更新错对象。取法:dws wiki node list --workspace <WS> 里 nodeType=folder 的 nodeId
--update-only 只更新知识库里已存在的同名文档,不新建。用户手动删掉的文档不要再同步回来
--format jsonml(默认,真合并+真代码块)/ md(纯文本草稿)
--merge-mode 仅 md 生效:blank(续行留空,默认)/ fill(续行重复填值)
--rewrite-links 把正文里的腾讯链接改写成钉钉链接;配 --space-id / --corp-id 生成 transit 形式

怎么验证渲染结果(行高 / 合并 / 列宽)

回读 JSONML 只能证明结构对,视觉对不对必须看渲染。两个可复用的手段:

  1. 导出 PDF 看:dws doc export --node <id> --export-format pdf --output <tmp>.pdf -y,
    再用 pymupdf 渲染成 PNG 直接看(沙箱里没装就 python -m venv 后 pip install pymupdf)。
    想量行高就把 PDF 里的水平线 y 坐标抽出来,相邻两条线之差就是行高;
    判合并看某条横线是否只在部分列出现(某一列不出现 = 那一列被纵向合并覆盖)。
  2. 让 UI 当老师:想知道钉钉自己怎么表达某个结构,让用户在 UI 里手动做一遍,
    然后 dws doc +version-list --node <id> 找那一版,dws doc +fetch --node <id> --version N 把 JSONML 拉下来照抄。
    (本次 hidden:true 就是这么定位的:手动合并那一版里,续行是 {"colSpan":1,"rowSpan":1,"hidden":true}。)
  3. 小样对照实验:建一篇临时文档,塞几张结构相同、写法不同的小表,一次导出 PDF 就能对比出哪种写法对。
    注意 colsWidth 的长度会被当成列数——长度和实际列数不一致时服务端会补出幽灵列,干扰判断。

收尾

同步完务必回读校验(字数 / 代码块数 / 图片数 / 标题数),并清掉测试节点与 (1)、(2) 重复件。

常见问题

5 问
答案由本站依据条目字段整理
腾讯文档同步钉钉文档怎么安装?

在支持 Skills 的客户端里执行:skillhub install user_d8449002/tencent-docs-to-dingtalk-wiki。安装方式为未标注。装完刷新技能列表或重开客户端。

腾讯文档同步钉钉文档支持哪些客户端?

资料没有标注宿主客户端,但只要客户端支持 Skills(Claude、Codex、Cursor 等),一般都能加载。

腾讯文档同步钉钉文档权限高吗,安全吗?

权限级别为未标注权限。安装前先看它会读取或写入哪些内容。

腾讯文档同步钉钉文档收费吗?

本站不代收费用,也没有标注具体价格。是否收费取决于技能本身和所用的模型或客户端,正式使用前按作者说明确认。

腾讯文档同步钉钉文档用什么许可证?

许可证标注为 未说明(许可证不明确)。商用或二次分发前请到仓库确认条款原文。

热门专题

按场景挑技能
全部专题 →

技能内容以作者仓库为准;分类、权限级别与许可证状态的中文说明为中文整理,最近核验 未标注。