dingtalk-wiki 1.2.14 → 1.2.16

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.
Files changed (2) hide show
  1. package/index.js +134 -28
  2. package/package.json +1 -1
package/index.js CHANGED
@@ -213,7 +213,28 @@ async function rebuildSearchIndex() {
213
213
  const blocks = await dingtalk.docRequest('GET', `/v1.0/doc/suites/documents/${childId}/blocks`);
214
214
  const blockData = blocks.result?.data || [];
215
215
  content = blockData.map(b => extractBlockText(b)).join('\n\n');
216
- } catch (e) { /* content unavailable */ }
216
+ } catch (e) { /* content unavailable via blocks */ }
217
+
218
+ // Fallback: 存储 v1.0 下载 API(对上传的 .md 等文件有效)
219
+ if (!content && child.spaceId) {
220
+ try {
221
+ const token = await dingtalk.getAccessToken();
222
+ const dlRes = await axios({
223
+ method: 'POST',
224
+ url: `${DINGTALK_API_V2}/v1.0/storage/spaces/${child.spaceId}/dentries/${child.dentryId}/downloadInfos/query`,
225
+ headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
226
+ params: { unionId: await dingtalk.getCurrentUserUnionId() || '' },
227
+ data: {}
228
+ });
229
+ const data = dlRes.data;
230
+ const downloadUrl = data.downloadInfo?.resourceUrl || data.resourceUrl || data.downloadUrl || data.url;
231
+ const downloadHeaders = data.downloadInfo?.headers || data.headers || {};
232
+ if (downloadUrl) {
233
+ const contentRes = await axios({ method: 'GET', url: downloadUrl, headers: downloadHeaders, responseType: 'text' });
234
+ content = contentRes.data;
235
+ }
236
+ } catch (e) { /* storage v1.0 unavailable */ }
237
+ }
217
238
 
218
239
  if (!content) {
219
240
  try {
@@ -1523,11 +1544,11 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1523
1544
  }
1524
1545
 
1525
1546
  // Step 9: 递归遍历目录树查找文档 + 存储API下载内容
1547
+ let foundDentry = null;
1548
+ let foundSpaceId = null;
1526
1549
  if (effectiveWsId) {
1527
1550
  try {
1528
1551
  // BFS 遍历目录树查找 dentry
1529
- let foundDentry = null;
1530
- let foundSpaceId = null;
1531
1552
  const queue = [''];
1532
1553
  const visited = new Set();
1533
1554
  while (queue.length > 0 && !foundDentry) {
@@ -1633,29 +1654,40 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1633
1654
  }
1634
1655
  }
1635
1656
 
1636
- // Step 10: doc_2.0 API - DocContent + QueryDocContent
1637
- for (const cfg of [
1638
- { label: 'DocContent /v2.0/doc/dentries/{uuid}/contents', params: { operatorId: opId }, data: undefined },
1639
- { label: 'DocContent + targetFormat query', params: { operatorId: opId, targetFormat: 'markdown' }, data: undefined },
1640
- { label: 'QueryDocContent /v2.0/doc/query/{uuid}/contents', path: `/v2.0/doc/query/${input}/contents`, params: { operatorId: opId, targetFormat: 'markdown' }, data: undefined },
1641
- ]) {
1657
+ // Step 10: 存储 API v1.0 - 文件下载(.md 文件内容 = 文件下载)
1658
+ if (foundSpaceId) {
1642
1659
  try {
1643
- const path = cfg.path || `/v2.0/doc/dentries/${input}/contents`;
1644
- const docContentRes = await axios({
1645
- method: 'GET',
1646
- url: `${DINGTALK_API_V2}${path}`,
1660
+ const dlRes = await axios({
1661
+ method: 'POST',
1662
+ url: `${DINGTALK_API_V2}/v1.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}/downloadInfos/query`,
1647
1663
  headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
1648
- params: cfg.params,
1649
- data: cfg.data
1664
+ params: { unionId: opId },
1665
+ data: {}
1650
1666
  });
1667
+ const data = dlRes.data;
1651
1668
  steps.push({
1652
- step: cfg.label,
1669
+ step: '存储 v1.0 获取下载链接 (POST /v1.0/storage/spaces/{sid}/dentries/{did}/downloadInfos/query)',
1653
1670
  result: '成功',
1654
- raw: docContentRes.data
1671
+ raw: data
1655
1672
  });
1673
+ // 如果有下载链接,尝试下载内容
1674
+ const downloadUrl = data.downloadInfo?.resourceUrl || data.resourceUrl || data.downloadUrl || data.url;
1675
+ const downloadHeaders = data.downloadInfo?.headers || data.headers || {};
1676
+ if (downloadUrl) {
1677
+ try {
1678
+ const contentRes = await axios({ method: 'GET', url: downloadUrl, headers: downloadHeaders, responseType: 'text' });
1679
+ steps.push({
1680
+ step: '文件内容下载',
1681
+ result: `成功 (${contentRes.data.length} 字符)`,
1682
+ raw: contentRes.data.slice(0, 1000) + (contentRes.data.length > 1000 ? '\n...(截断)' : '')
1683
+ });
1684
+ } catch (dlErr) {
1685
+ steps.push({ step: '文件内容下载', result: '失败', raw: dlErr.message });
1686
+ }
1687
+ }
1656
1688
  } catch (e) {
1657
1689
  steps.push({
1658
- step: cfg.label,
1690
+ step: '存储 v1.0 获取下载链接',
1659
1691
  result: '失败',
1660
1692
  raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
1661
1693
  });
@@ -1673,21 +1705,30 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
1673
1705
  }
1674
1706
  lines.push('');
1675
1707
  });
1708
+ const isAdoc = foundDentry?.name?.endsWith('.adoc') || input.length > 20;
1676
1709
  lines.push('💡 诊断结论:');
1677
1710
  lines.push(` 输入 ID: ${input}`);
1678
1711
  lines.push(` 操作者: ${opId}`);
1679
1712
  lines.push('');
1680
- lines.push(' 1️⃣ wiki/nodes API → ✅ 成功。节点存在,但无 document.docKey');
1681
- lines.push(' 2️⃣ blocks API → ❌ "doc key is illegal"');
1682
- lines.push(' 3️⃣ overwriteContent 同上错误');
1713
+ if (foundDentry) {
1714
+ lines.push(` 文档名: ${foundDentry.name}`);
1715
+ lines.push(` 类型推断: ${foundDentry.name?.endsWith('.adoc') ? '✅ .adoc 在线文档(可读写)' : foundDentry.name?.match(/\.(md|docx|xlsx|txt|xmind|mark|markdown)$/i) ? '导入的文档(非原生. adoc,内容 API 不支持)' : '⚠️ 未知类型'}`);
1716
+ }
1717
+ lines.push('');
1718
+ lines.push(' 1️⃣ wiki/nodes API → ✅ 成功。节点存在');
1719
+ lines.push(` 2️⃣ blocks API → ${foundDentry?.name?.endsWith('.adoc') ? '✅ 应对 .adoc 在线文档有效' : '❌ "doc key is illegal"(非 .adoc 文档无 docKey)'}`);
1720
+ lines.push(` 3️⃣ overwriteContent → ${foundDentry?.name?.endsWith('.adoc') ? '✅ 应对 .adoc 在线文档有效' : '❌ 同上错误'}`);
1683
1721
  lines.push(' 4️⃣ doc metadata → ❌ 404(该端点不存在)');
1684
1722
  lines.push('');
1685
- lines.push('📌 DocContent API (GET /v2.0/doc/dentries/{uuid}/contents) ✅ 接受 dentryUuid');
1686
- lines.push(' 上次失败原因: 503 ServiceUnavailable(临时服务器问题)');
1687
- if (effectiveWsId) {
1688
- lines.push(` 此文档 workspaceId: ${effectiveWsId}`);
1689
- }
1690
- lines.push('📌 请重新运行此工具,已加自动重试逻辑');
1723
+ lines.push('📌 关键区别:');
1724
+ lines.push(' - 知识库"导入为在线文档" → 转为 .adoc → 有 docKey → blocks API 可读写');
1725
+ lines.push(' - "上传文件" → 保持原格式 → 无 docKey → blocks API 不可用');
1726
+ lines.push(' - create_wiki_doc 创建的是 .adoc 在线文档 → blocks API 可读写');
1727
+ lines.push('');
1728
+ lines.push('📌 存储 v1.0 下载 API (POST /v1.0/storage/spaces/{sid}/dentries/{did}/downloadInfos/query)');
1729
+ lines.push(' 使用 unionId 参数,可获取上传文件的下载链接');
1730
+ lines.push('📌 DocContent API (GET /v2.0/doc/dentries/{uuid}/contents) 此前 503');
1731
+ lines.push('📌 请重新运行此工具测试以上 API');
1691
1732
 
1692
1733
  return { content: [{ type: 'text', text: lines.join('\n') }] };
1693
1734
  }
@@ -2057,9 +2098,74 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
2057
2098
  };
2058
2099
  } catch (blocksErr) {
2059
2100
  const isDocKeyIllegal = blocksErr.message.includes('doc key is illegal');
2101
+
2102
+ // Fallback: 尝试存储 v1.0 下载 API(对上传的 .md 等文件有效)
2103
+ if (isDocKeyIllegal && workspaceId) {
2104
+ try {
2105
+ const token = await dingtalk.getAccessToken();
2106
+ const opId = await dingtalk.resolveOperatorId(operator_id || null);
2107
+
2108
+ // BFS 遍历查找 dentry
2109
+ const queue = [''];
2110
+ const visited = new Set();
2111
+ let foundDentry = null;
2112
+ let foundSpaceId = null;
2113
+ while (queue.length > 0 && !foundDentry) {
2114
+ const parentId = queue.shift();
2115
+ if (visited.has(parentId)) continue;
2116
+ visited.add(parentId);
2117
+ try {
2118
+ const dirParams = { operatorId: opId, maxResults: 500 };
2119
+ if (parentId) dirParams.dentryId = parentId;
2120
+ const dirRes = await axios({
2121
+ method: 'GET',
2122
+ url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${workspaceId}/directories`,
2123
+ headers: { 'x-acs-dingtalk-access-token': token },
2124
+ params: dirParams
2125
+ });
2126
+ const children = dirRes.data?.children || [];
2127
+ for (const child of children) {
2128
+ if (child.dentryUuid === realDocKey || child.dentryId === realDocKey || child.id === realDocKey) {
2129
+ foundDentry = child;
2130
+ foundSpaceId = child.spaceId;
2131
+ break;
2132
+ }
2133
+ if (child.hasChildren && child.dentryId && !visited.has(child.dentryId)) {
2134
+ queue.push(child.dentryId);
2135
+ }
2136
+ }
2137
+ } catch (e) { /* skip */ }
2138
+ }
2139
+
2140
+ if (foundDentry && foundSpaceId) {
2141
+ const dlRes = await axios({
2142
+ method: 'POST',
2143
+ url: `${DINGTALK_API_V2}/v1.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}/downloadInfos/query`,
2144
+ headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
2145
+ params: { unionId: opId },
2146
+ data: {}
2147
+ });
2148
+ const data = dlRes.data;
2149
+ const downloadUrl = data.downloadInfo?.resourceUrl || data.resourceUrl || data.downloadUrl || data.url;
2150
+ const downloadHeaders = data.downloadInfo?.headers || data.headers || {};
2151
+ if (downloadUrl) {
2152
+ const contentRes = await axios({ method: 'GET', url: downloadUrl, headers: downloadHeaders, responseType: 'text' });
2153
+ return {
2154
+ content: [{ type: 'text', text: contentRes.data }]
2155
+ };
2156
+ }
2157
+ }
2158
+ } catch (fallbackErr) {
2159
+ console.error(`[钉钉MCP] 存储 v1.0 fallback 失败: ${fallbackErr.message}`);
2160
+ }
2161
+ }
2162
+
2060
2163
  let hint = '';
2061
2164
  if (isDocKeyIllegal) {
2062
- hint = `\n\n原因: 此文档的 dentryUuid 在 Doc Suite API 中无关联 docKey。blocks/overwriteContent\n仅对通过 create_wiki_doc 创建的文档有效。对于知识库中已有文档,钉钉未提供公\n开 REST API 读取内容。可通过 DingTalk 官方 MCP 服务器读取(get_document_content)。`;
2165
+ hint = `\n\n原因: 此文档不是 .adoc 在线文档,blocks API 无法读取。`;
2166
+ if (workspaceId) {
2167
+ hint += `\n已尝试存储 v1.0 下载 API 但仍失败。`;
2168
+ }
2063
2169
  }
2064
2170
  return {
2065
2171
  content: [{
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dingtalk-wiki",
3
- "version": "1.2.14",
3
+ "version": "1.2.16",
4
4
  "description": "DingTalk Wiki / Docs read-write MCP server that fills the gap left by DingTalk official MCP.",
5
5
  "main": "index.js",
6
6
  "bin": {