dingtalk-wiki 1.2.16 → 1.2.18
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.
- package/index.js +30 -472
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -223,7 +223,7 @@ async function rebuildSearchIndex() {
|
|
|
223
223
|
method: 'POST',
|
|
224
224
|
url: `${DINGTALK_API_V2}/v1.0/storage/spaces/${child.spaceId}/dentries/${child.dentryId}/downloadInfos/query`,
|
|
225
225
|
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
226
|
-
params: { unionId: await dingtalk.
|
|
226
|
+
params: { unionId: await dingtalk.resolveOperatorId(null) },
|
|
227
227
|
data: {}
|
|
228
228
|
});
|
|
229
229
|
const data = dlRes.data;
|
|
@@ -747,28 +747,6 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
747
747
|
required: ['node_id']
|
|
748
748
|
}
|
|
749
749
|
},
|
|
750
|
-
{
|
|
751
|
-
name: 'debug_resolve_doc_key',
|
|
752
|
-
description: '[调试] 诊断 docKey 解析问题,测试所有可能的 API 路径并返回原始响应',
|
|
753
|
-
inputSchema: {
|
|
754
|
-
type: 'object',
|
|
755
|
-
properties: {
|
|
756
|
-
doc_key: {
|
|
757
|
-
type: 'string',
|
|
758
|
-
description: '文档标识(dentryUuid / docKey / nodeId / URL)'
|
|
759
|
-
},
|
|
760
|
-
workspace_id: {
|
|
761
|
-
type: 'string',
|
|
762
|
-
description: '知识库 ID(可选)'
|
|
763
|
-
},
|
|
764
|
-
operator_id: {
|
|
765
|
-
type: 'string',
|
|
766
|
-
description: '操作者 unionid(不传则使用默认用户)'
|
|
767
|
-
}
|
|
768
|
-
},
|
|
769
|
-
required: ['doc_key']
|
|
770
|
-
}
|
|
771
|
-
},
|
|
772
750
|
{
|
|
773
751
|
name: 'search_wiki',
|
|
774
752
|
description: '按名称搜索知识库中的文档和文件夹(遍历目录树,无需索引即可使用)',
|
|
@@ -893,17 +871,17 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
893
871
|
},
|
|
894
872
|
{
|
|
895
873
|
name: 'get_wiki_doc_content',
|
|
896
|
-
description: '
|
|
874
|
+
description: '读取文档内容。对 .adoc 在线文档使用 blocks API;对上传的 .md 等文件使用存储下载 API(需 Storage.DownloadInfo.Read 权限)。',
|
|
897
875
|
inputSchema: {
|
|
898
876
|
type: 'object',
|
|
899
877
|
properties: {
|
|
900
878
|
doc_key: {
|
|
901
879
|
type: 'string',
|
|
902
|
-
description: '文档标识。支持
|
|
880
|
+
description: '文档标识。支持 nodeId(dentryUuid)、docKey,或文档 URL。'
|
|
903
881
|
},
|
|
904
882
|
workspace_id: {
|
|
905
883
|
type: 'string',
|
|
906
|
-
description: '知识库 ID
|
|
884
|
+
description: '知识库 ID(可选)。自动通过 wiki/nodes API 解析。'
|
|
907
885
|
},
|
|
908
886
|
operator_id: {
|
|
909
887
|
type: 'string',
|
|
@@ -915,13 +893,13 @@ server.setRequestHandler(ListToolsRequestSchema, async () => {
|
|
|
915
893
|
},
|
|
916
894
|
{
|
|
917
895
|
name: 'update_wiki_doc_content',
|
|
918
|
-
description: '
|
|
896
|
+
description: '覆写 .adoc 在线文档正文内容(⚠️ 全量覆盖,不可撤销)。对上传的 .md 等文件不支持修改。',
|
|
919
897
|
inputSchema: {
|
|
920
898
|
type: 'object',
|
|
921
899
|
properties: {
|
|
922
900
|
doc_key: {
|
|
923
901
|
type: 'string',
|
|
924
|
-
description: '文档标识。支持
|
|
902
|
+
description: '文档标识。支持 nodeId(dentryUuid)、docKey,或文档 URL。仅 .adoc 在线文档支持写入。'
|
|
925
903
|
},
|
|
926
904
|
content: {
|
|
927
905
|
type: 'string',
|
|
@@ -1214,6 +1192,11 @@ async function resolveDocKey(docKey, operatorId, dingtalkInstance) {
|
|
|
1214
1192
|
const node = nodeInfo.node || nodeInfo;
|
|
1215
1193
|
console.error(`[DEBUG] resolveDocKey: wiki/nodes response keys=${Object.keys(node).join(',')}`);
|
|
1216
1194
|
|
|
1195
|
+
// 缓存 workspaceId,供后续 fallback 使用
|
|
1196
|
+
if (node.workspaceId) {
|
|
1197
|
+
dingtalkInstance._resolvedWorkspaceId = node.workspaceId;
|
|
1198
|
+
}
|
|
1199
|
+
|
|
1217
1200
|
// wiki/nodes 的 nodeId = dentryUuid,直接可用
|
|
1218
1201
|
// 但 node.document.docKey 可能是不同的值,优先使用
|
|
1219
1202
|
if (node.document?.docKey) {
|
|
@@ -1329,410 +1312,6 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1329
1312
|
};
|
|
1330
1313
|
}
|
|
1331
1314
|
|
|
1332
|
-
case 'debug_resolve_doc_key': {
|
|
1333
|
-
const { doc_key: docKey, workspace_id: workspaceId, operator_id } = args;
|
|
1334
|
-
if (operator_id) {
|
|
1335
|
-
dingtalk.setOperatorId(operator_id);
|
|
1336
|
-
}
|
|
1337
|
-
|
|
1338
|
-
const token = await dingtalk.getAccessToken();
|
|
1339
|
-
const opId = await dingtalk.resolveOperatorId(operator_id || null);
|
|
1340
|
-
const steps = [];
|
|
1341
|
-
const input = String(docKey);
|
|
1342
|
-
|
|
1343
|
-
steps.push({ step: '输入', result: input });
|
|
1344
|
-
|
|
1345
|
-
// Step 1: URL extraction
|
|
1346
|
-
const urlMatch = input.match(/\/i\/nodes\/([^\/\?#]+)/);
|
|
1347
|
-
const urlExtracted = urlMatch ? urlMatch[1] : null;
|
|
1348
|
-
steps.push({ step: 'URL 提取', result: urlExtracted || '无匹配', raw: urlMatch ? urlMatch[0] : null });
|
|
1349
|
-
|
|
1350
|
-
// Step 2: wiki/nodes API
|
|
1351
|
-
let wikiNodeResponse = null;
|
|
1352
|
-
let wikiWsId = null;
|
|
1353
|
-
try {
|
|
1354
|
-
const nodeRes = await axios({
|
|
1355
|
-
method: 'GET',
|
|
1356
|
-
url: `${DINGTALK_API_V2}/v2.0/wiki/nodes/${input}`,
|
|
1357
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1358
|
-
params: { operatorId: opId }
|
|
1359
|
-
});
|
|
1360
|
-
wikiNodeResponse = nodeRes.data;
|
|
1361
|
-
wikiWsId = wikiNodeResponse.workspaceId || wikiNodeResponse.node?.workspaceId || null;
|
|
1362
|
-
const node = wikiNodeResponse;
|
|
1363
|
-
steps.push({
|
|
1364
|
-
step: 'wiki/nodes API',
|
|
1365
|
-
result: '成功',
|
|
1366
|
-
raw: node
|
|
1367
|
-
});
|
|
1368
|
-
} catch (e) {
|
|
1369
|
-
steps.push({
|
|
1370
|
-
step: 'wiki/nodes API',
|
|
1371
|
-
result: '失败',
|
|
1372
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1373
|
-
});
|
|
1374
|
-
}
|
|
1375
|
-
|
|
1376
|
-
const effectiveWsId = workspaceId || wikiWsId;
|
|
1377
|
-
|
|
1378
|
-
// Step 3: doc metadata API (GET /v1.0/doc/suites/documents/{docKey})
|
|
1379
|
-
try {
|
|
1380
|
-
const metaRes = await axios({
|
|
1381
|
-
method: 'GET',
|
|
1382
|
-
url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}`,
|
|
1383
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1384
|
-
params: { operatorId: opId }
|
|
1385
|
-
});
|
|
1386
|
-
steps.push({
|
|
1387
|
-
step: 'doc metadata API (GET /documents/{docKey})',
|
|
1388
|
-
result: '成功',
|
|
1389
|
-
raw: metaRes.data
|
|
1390
|
-
});
|
|
1391
|
-
} catch (e) {
|
|
1392
|
-
steps.push({
|
|
1393
|
-
step: 'doc metadata API (GET /documents/{docKey})',
|
|
1394
|
-
result: '失败',
|
|
1395
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1396
|
-
});
|
|
1397
|
-
}
|
|
1398
|
-
|
|
1399
|
-
// Step 4: blocks API
|
|
1400
|
-
try {
|
|
1401
|
-
const blocksRes = await axios({
|
|
1402
|
-
method: 'GET',
|
|
1403
|
-
url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/blocks`,
|
|
1404
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1405
|
-
params: { operatorId: opId }
|
|
1406
|
-
});
|
|
1407
|
-
const blockCount = blocksRes.data?.result?.data?.length || 0;
|
|
1408
|
-
steps.push({
|
|
1409
|
-
step: 'blocks API',
|
|
1410
|
-
result: `成功 (${blockCount} 个 block)`,
|
|
1411
|
-
raw: blocksRes.data
|
|
1412
|
-
});
|
|
1413
|
-
} catch (e) {
|
|
1414
|
-
steps.push({
|
|
1415
|
-
step: 'blocks API',
|
|
1416
|
-
result: '失败',
|
|
1417
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1418
|
-
});
|
|
1419
|
-
}
|
|
1420
|
-
|
|
1421
|
-
// Step 5: overwriteContent API (test with empty content — don't actually write)
|
|
1422
|
-
try {
|
|
1423
|
-
const overwriteRes = await axios({
|
|
1424
|
-
method: 'POST',
|
|
1425
|
-
url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/overwriteContent`,
|
|
1426
|
-
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
1427
|
-
params: { operatorId: opId },
|
|
1428
|
-
data: { content: 'test', contentType: 'markdown' }
|
|
1429
|
-
});
|
|
1430
|
-
steps.push({
|
|
1431
|
-
step: 'overwriteContent API',
|
|
1432
|
-
result: '成功(可写入)',
|
|
1433
|
-
raw: overwriteRes.data
|
|
1434
|
-
});
|
|
1435
|
-
} catch (e) {
|
|
1436
|
-
steps.push({
|
|
1437
|
-
step: 'overwriteContent API',
|
|
1438
|
-
result: '失败',
|
|
1439
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1440
|
-
});
|
|
1441
|
-
}
|
|
1442
|
-
|
|
1443
|
-
// Step 6: search docs API (v1.0/doc/docs) — 需要 workspaceId + keyword
|
|
1444
|
-
if (effectiveWsId) {
|
|
1445
|
-
// Try without keyword first (might list all)
|
|
1446
|
-
try {
|
|
1447
|
-
const searchRes = await axios({
|
|
1448
|
-
method: 'GET',
|
|
1449
|
-
url: `${DINGTALK_API_V2}/v1.0/doc/docs`,
|
|
1450
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1451
|
-
params: { operatorId: opId, workspaceId: effectiveWsId, maxResults: 50 }
|
|
1452
|
-
});
|
|
1453
|
-
const docs = searchRes.data?.docs || [];
|
|
1454
|
-
const matched = docs.find(d => d.nodeBO?.nodeId === input || d.nodeBO?.dentryUuid === input || d.docKey === input || d.nodeBO?.dentryId === input);
|
|
1455
|
-
steps.push({
|
|
1456
|
-
step: `doc search API (workspace=${effectiveWsId}, no keyword)`,
|
|
1457
|
-
result: `成功 (${docs.length} 篇文档)` + (matched ? ', 找到匹配!' : ', 未找到匹配'),
|
|
1458
|
-
raw: docs.length > 0 ? { count: docs.length, firstDocDocKey: docs[0]?.docKey || '(无)', firstDocNodeId: docs[0]?.nodeBO?.nodeId || '(无)', firstDocName: docs[0]?.name || '(无)' } : '空结果'
|
|
1459
|
-
});
|
|
1460
|
-
} catch (e) {
|
|
1461
|
-
steps.push({
|
|
1462
|
-
step: `doc search API (workspace=${effectiveWsId}, no keyword)`,
|
|
1463
|
-
result: '失败',
|
|
1464
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1465
|
-
});
|
|
1466
|
-
}
|
|
1467
|
-
// Try with keyword from wiki/nodes name if available
|
|
1468
|
-
const wikiName = wikiNodeResponse?.node?.name || wikiNodeResponse?.name || '';
|
|
1469
|
-
if (wikiName) {
|
|
1470
|
-
try {
|
|
1471
|
-
const searchRes = await axios({
|
|
1472
|
-
method: 'GET',
|
|
1473
|
-
url: `${DINGTALK_API_V2}/v1.0/doc/docs`,
|
|
1474
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1475
|
-
params: { operatorId: opId, workspaceId: effectiveWsId, keyword: wikiName.replace(/\.\w+$/, ''), maxResults: 10 }
|
|
1476
|
-
});
|
|
1477
|
-
const docs = searchRes.data?.docs || [];
|
|
1478
|
-
steps.push({
|
|
1479
|
-
step: `doc search API (keyword="${wikiName}")`,
|
|
1480
|
-
result: `成功 (${docs.length} 篇)`,
|
|
1481
|
-
raw: docs.length > 0 ? docs.map(d => ({ docKey: d.docKey || '(无)', name: d.name || '(无)', nodeId: d.nodeBO?.nodeId || '(无)', dentryUuid: d.nodeBO?.dentryUuid || '(无)' })) : '空结果'
|
|
1482
|
-
});
|
|
1483
|
-
} catch (e) {
|
|
1484
|
-
steps.push({
|
|
1485
|
-
step: `doc search API (keyword="${wikiName}")`,
|
|
1486
|
-
result: '失败',
|
|
1487
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1488
|
-
});
|
|
1489
|
-
}
|
|
1490
|
-
}
|
|
1491
|
-
} else {
|
|
1492
|
-
steps.push({ step: 'doc search API', result: '跳过(无法获取 workspace_id)' });
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
// Step 7: 在知识库目录中查找此节点 + 查看是否有 docKey 字段
|
|
1496
|
-
if (effectiveWsId) {
|
|
1497
|
-
try {
|
|
1498
|
-
const dirRes = await axios({
|
|
1499
|
-
method: 'GET',
|
|
1500
|
-
url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${effectiveWsId}/directories`,
|
|
1501
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1502
|
-
params: { operatorId: opId, maxResults: 500 }
|
|
1503
|
-
});
|
|
1504
|
-
const children = dirRes.data?.children || [];
|
|
1505
|
-
const match = children.find(c => c.dentryUuid === input || c.dentryId === input || c.id === input);
|
|
1506
|
-
// Show first few children's available fields so user can see what fields exist
|
|
1507
|
-
const sampleKeys = children.length > 0 ? Object.keys(children[0]) : [];
|
|
1508
|
-
steps.push({
|
|
1509
|
-
step: `知识库目录 (workspace=${effectiveWsId})`,
|
|
1510
|
-
result: `${children.length} 个子节点` + (match ? ', 找到匹配!' : ', 未找到匹配'),
|
|
1511
|
-
raw: match ? match : { sampleFields: sampleKeys, firstChildSample: children[0] || null }
|
|
1512
|
-
});
|
|
1513
|
-
} catch (e) {
|
|
1514
|
-
steps.push({
|
|
1515
|
-
step: `知识库目录 (workspace=${effectiveWsId})`,
|
|
1516
|
-
result: '失败',
|
|
1517
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1520
|
-
}
|
|
1521
|
-
|
|
1522
|
-
// Step 8: 用 workspaceId 再次尝试 overwriteContent(确认文档套件是否完全不可用)
|
|
1523
|
-
if (effectiveWsId) {
|
|
1524
|
-
try {
|
|
1525
|
-
const overwriteRes = await axios({
|
|
1526
|
-
method: 'POST',
|
|
1527
|
-
url: `${DINGTALK_API_V2}/v1.0/doc/suites/documents/${input}/overwriteContent`,
|
|
1528
|
-
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
1529
|
-
params: { operatorId: opId },
|
|
1530
|
-
data: { content: 'test', contentType: 'markdown' }
|
|
1531
|
-
});
|
|
1532
|
-
steps.push({
|
|
1533
|
-
step: 'overwriteContent(带 workspaceId)',
|
|
1534
|
-
result: '成功',
|
|
1535
|
-
raw: overwriteRes.data
|
|
1536
|
-
});
|
|
1537
|
-
} catch (e) {
|
|
1538
|
-
steps.push({
|
|
1539
|
-
step: 'overwriteContent(带 workspaceId)',
|
|
1540
|
-
result: '失败',
|
|
1541
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1542
|
-
});
|
|
1543
|
-
}
|
|
1544
|
-
}
|
|
1545
|
-
|
|
1546
|
-
// Step 9: 递归遍历目录树查找文档 + 存储API下载内容
|
|
1547
|
-
let foundDentry = null;
|
|
1548
|
-
let foundSpaceId = null;
|
|
1549
|
-
if (effectiveWsId) {
|
|
1550
|
-
try {
|
|
1551
|
-
// BFS 遍历目录树查找 dentry
|
|
1552
|
-
const queue = [''];
|
|
1553
|
-
const visited = new Set();
|
|
1554
|
-
while (queue.length > 0 && !foundDentry) {
|
|
1555
|
-
const parentId = queue.shift();
|
|
1556
|
-
if (visited.has(parentId)) continue;
|
|
1557
|
-
visited.add(parentId);
|
|
1558
|
-
try {
|
|
1559
|
-
const dirParams = { operatorId: opId, maxResults: 500 };
|
|
1560
|
-
if (parentId) dirParams.dentryId = parentId;
|
|
1561
|
-
const dirRes = await axios({
|
|
1562
|
-
method: 'GET',
|
|
1563
|
-
url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${effectiveWsId}/directories`,
|
|
1564
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1565
|
-
params: dirParams
|
|
1566
|
-
});
|
|
1567
|
-
const children = dirRes.data?.children || [];
|
|
1568
|
-
for (const child of children) {
|
|
1569
|
-
if (child.dentryUuid === input || child.dentryId === input) {
|
|
1570
|
-
foundDentry = child;
|
|
1571
|
-
foundSpaceId = child.spaceId;
|
|
1572
|
-
break;
|
|
1573
|
-
}
|
|
1574
|
-
if (child.hasChildren && child.dentryId && !visited.has(child.dentryId)) {
|
|
1575
|
-
queue.push(child.dentryId);
|
|
1576
|
-
}
|
|
1577
|
-
}
|
|
1578
|
-
} catch (e) { /* skip */ }
|
|
1579
|
-
}
|
|
1580
|
-
|
|
1581
|
-
if (foundDentry) {
|
|
1582
|
-
steps.push({
|
|
1583
|
-
step: 'BFS 目录遍历找到文档',
|
|
1584
|
-
result: `spaceId=${foundSpaceId}, dentryId=${foundDentry.dentryId}, name=${foundDentry.name}`,
|
|
1585
|
-
raw: foundDentry
|
|
1586
|
-
});
|
|
1587
|
-
|
|
1588
|
-
// 尝试存储 API 获取 dentry 详情(含 docKey)
|
|
1589
|
-
try {
|
|
1590
|
-
const dentryRes = await axios({
|
|
1591
|
-
method: 'GET',
|
|
1592
|
-
url: `${DINGTALK_API_V2}/v2.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}`,
|
|
1593
|
-
headers: { 'x-acs-dingtalk-access-token': token },
|
|
1594
|
-
params: { operatorId: opId }
|
|
1595
|
-
});
|
|
1596
|
-
steps.push({
|
|
1597
|
-
step: '存储 API dentry 详情',
|
|
1598
|
-
result: '成功',
|
|
1599
|
-
raw: dentryRes.data
|
|
1600
|
-
});
|
|
1601
|
-
} catch (e) {
|
|
1602
|
-
steps.push({
|
|
1603
|
-
step: '存储 API dentry 详情',
|
|
1604
|
-
result: '失败',
|
|
1605
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1606
|
-
});
|
|
1607
|
-
}
|
|
1608
|
-
|
|
1609
|
-
// 尝试存储 API 下载
|
|
1610
|
-
try {
|
|
1611
|
-
const dlRes = await axios({
|
|
1612
|
-
method: 'POST',
|
|
1613
|
-
url: `${DINGTALK_API_V2}/v2.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}/downloadInfos/query`,
|
|
1614
|
-
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
1615
|
-
params: { operatorId: opId },
|
|
1616
|
-
data: {}
|
|
1617
|
-
});
|
|
1618
|
-
steps.push({
|
|
1619
|
-
step: '存储 API 获取下载链接',
|
|
1620
|
-
result: '成功',
|
|
1621
|
-
raw: dlRes.data
|
|
1622
|
-
});
|
|
1623
|
-
// 尝试从下载链接获取内容
|
|
1624
|
-
const dlInfo = dlRes.data;
|
|
1625
|
-
const downloadUrl = dlInfo.headerSignatureInfo?.resourceUrls?.[0] || dlInfo.downloadUrl || dlInfo.url;
|
|
1626
|
-
if (downloadUrl) {
|
|
1627
|
-
try {
|
|
1628
|
-
const contentRes = await axios({ method: 'GET', url: downloadUrl, responseType: 'text' });
|
|
1629
|
-
steps.push({
|
|
1630
|
-
step: '存储 API 下载内容',
|
|
1631
|
-
result: `成功 (${contentRes.data.length} 字符)`,
|
|
1632
|
-
raw: contentRes.data.slice(0, 500) + (contentRes.data.length > 500 ? '\n... (截断)' : '')
|
|
1633
|
-
});
|
|
1634
|
-
} catch (dlErr) {
|
|
1635
|
-
steps.push({
|
|
1636
|
-
step: '存储 API 下载内容',
|
|
1637
|
-
result: '失败',
|
|
1638
|
-
raw: dlErr.message
|
|
1639
|
-
});
|
|
1640
|
-
}
|
|
1641
|
-
}
|
|
1642
|
-
} catch (e) {
|
|
1643
|
-
steps.push({
|
|
1644
|
-
step: '存储 API 获取下载链接',
|
|
1645
|
-
result: '失败',
|
|
1646
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1647
|
-
});
|
|
1648
|
-
}
|
|
1649
|
-
} else {
|
|
1650
|
-
steps.push({ step: 'BFS 目录遍历', result: '未找到匹配(文档可能在深层目录)' });
|
|
1651
|
-
}
|
|
1652
|
-
} catch (e) {
|
|
1653
|
-
steps.push({ step: 'BFS 目录遍历', result: '失败', raw: e.message });
|
|
1654
|
-
}
|
|
1655
|
-
}
|
|
1656
|
-
|
|
1657
|
-
// Step 10: 存储 API v1.0 - 文件下载(.md 文件内容 = 文件下载)
|
|
1658
|
-
if (foundSpaceId) {
|
|
1659
|
-
try {
|
|
1660
|
-
const dlRes = await axios({
|
|
1661
|
-
method: 'POST',
|
|
1662
|
-
url: `${DINGTALK_API_V2}/v1.0/storage/spaces/${foundSpaceId}/dentries/${foundDentry.dentryId}/downloadInfos/query`,
|
|
1663
|
-
headers: { 'x-acs-dingtalk-access-token': token, 'Content-Type': 'application/json' },
|
|
1664
|
-
params: { unionId: opId },
|
|
1665
|
-
data: {}
|
|
1666
|
-
});
|
|
1667
|
-
const data = dlRes.data;
|
|
1668
|
-
steps.push({
|
|
1669
|
-
step: '存储 v1.0 获取下载链接 (POST /v1.0/storage/spaces/{sid}/dentries/{did}/downloadInfos/query)',
|
|
1670
|
-
result: '成功',
|
|
1671
|
-
raw: data
|
|
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
|
-
}
|
|
1688
|
-
} catch (e) {
|
|
1689
|
-
steps.push({
|
|
1690
|
-
step: '存储 v1.0 获取下载链接',
|
|
1691
|
-
result: '失败',
|
|
1692
|
-
raw: { message: e.response?.data?.message || e.message, code: e.response?.data?.code || '(无)', status: e.response?.status || '(无)' }
|
|
1693
|
-
});
|
|
1694
|
-
}
|
|
1695
|
-
}
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
const lines = ['🔍 docKey 诊断报告', '', `输入: ${input}`, `operatorId: ${opId}`, ''];
|
|
1700
|
-
steps.forEach(s => {
|
|
1701
|
-
lines.push(`--- ${s.step} ---`);
|
|
1702
|
-
lines.push(`结果: ${s.result}`);
|
|
1703
|
-
if (s.raw) {
|
|
1704
|
-
lines.push(`详情: ${JSON.stringify(s.raw, null, 2)}`);
|
|
1705
|
-
}
|
|
1706
|
-
lines.push('');
|
|
1707
|
-
});
|
|
1708
|
-
const isAdoc = foundDentry?.name?.endsWith('.adoc') || input.length > 20;
|
|
1709
|
-
lines.push('💡 诊断结论:');
|
|
1710
|
-
lines.push(` 输入 ID: ${input}`);
|
|
1711
|
-
lines.push(` 操作者: ${opId}`);
|
|
1712
|
-
lines.push('');
|
|
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 在线文档有效' : '❌ 同上错误'}`);
|
|
1721
|
-
lines.push(' 4️⃣ doc metadata → ❌ 404(该端点不存在)');
|
|
1722
|
-
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');
|
|
1732
|
-
|
|
1733
|
-
return { content: [{ type: 'text', text: lines.join('\n') }] };
|
|
1734
|
-
}
|
|
1735
|
-
|
|
1736
1315
|
case 'create_wiki_doc': {
|
|
1737
1316
|
const { workspace_id, parent_node_id, name, doc_type = 'DOC', operator_id } = args;
|
|
1738
1317
|
if (operator_id) {
|
|
@@ -1771,6 +1350,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1771
1350
|
});
|
|
1772
1351
|
|
|
1773
1352
|
const doc = response.data;
|
|
1353
|
+
const nodeId = doc.nodeId || doc.id;
|
|
1774
1354
|
const typeLabels = {
|
|
1775
1355
|
DOC: '文档',
|
|
1776
1356
|
WORKBOOK: '表格',
|
|
@@ -1790,7 +1370,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1790
1370
|
'',
|
|
1791
1371
|
`${typeIcon} ${name}`,
|
|
1792
1372
|
`🗂️ 类型: ${doc_type}`,
|
|
1793
|
-
`🆔 Node ID: ${
|
|
1373
|
+
`🆔 Node ID: ${nodeId}`,
|
|
1794
1374
|
];
|
|
1795
1375
|
|
|
1796
1376
|
if (doc.docKey) {
|
|
@@ -1807,7 +1387,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1807
1387
|
}
|
|
1808
1388
|
|
|
1809
1389
|
setImmediate(() => {
|
|
1810
|
-
wikiIndex.add(
|
|
1390
|
+
wikiIndex.add(nodeId || doc.docKey, {
|
|
1811
1391
|
title: name,
|
|
1812
1392
|
content: args.content || '',
|
|
1813
1393
|
workspaceId: workspace_id || doc.workspaceId,
|
|
@@ -1815,7 +1395,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1815
1395
|
url: doc.url || '',
|
|
1816
1396
|
type: doc_type,
|
|
1817
1397
|
});
|
|
1818
|
-
const contentId = doc.docKey || doc.dentryUuid ||
|
|
1398
|
+
const contentId = doc.docKey || doc.dentryUuid || nodeId;
|
|
1819
1399
|
if (args.content && contentId) {
|
|
1820
1400
|
dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${contentId}/overwriteContent`, {
|
|
1821
1401
|
operatorId: opId,
|
|
@@ -1870,7 +1450,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1870
1450
|
const result = await dingtalk.docRequest('GET', `/v2.0/wiki/nodes/${node_id}`, {
|
|
1871
1451
|
operatorId: operator_id || null
|
|
1872
1452
|
});
|
|
1873
|
-
const node = result;
|
|
1453
|
+
const node = result.node || result;
|
|
1874
1454
|
let output = `📄 节点详情\n\n`;
|
|
1875
1455
|
output += `名称: ${node.name || '-'}\n`;
|
|
1876
1456
|
output += `ID: ${node.id || node.nodeId || '-'}\n`;
|
|
@@ -1932,7 +1512,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1932
1512
|
for (const child of children) {
|
|
1933
1513
|
const name = child.name || '';
|
|
1934
1514
|
if (name.includes(keyword)) {
|
|
1935
|
-
matchedNodes.push({ name, nodeId: child.dentryId || child.nodeId || child.id, workspaceId: ws.workspaceId, workspaceName: ws.name, type: child.contentType === 'folder' ? '文件夹' : '文档', url: child.url || '' });
|
|
1515
|
+
matchedNodes.push({ name, nodeId: child.dentryUuid || child.dentryId || child.nodeId || child.id, workspaceId: ws.workspaceId, workspaceName: ws.name, type: child.contentType === 'folder' ? '文件夹' : '文档', url: child.url || '' });
|
|
1936
1516
|
}
|
|
1937
1517
|
const childId = child.dentryId || child.nodeId || child.id;
|
|
1938
1518
|
if (child.hasChildren && childId && !visited.has(childId)) queue.push(childId);
|
|
@@ -1988,6 +1568,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
1988
1568
|
const icon = item.type === 'FOLDER' || item.type === 'folder' ? '📁' : '📄';
|
|
1989
1569
|
output += `${i + 1}. ${icon} ${item.title}\n`;
|
|
1990
1570
|
output += ` 知识库: ${item.workspaceName} (${item.workspaceId})\n`;
|
|
1571
|
+
output += ` Node ID: ${item.id}\n`;
|
|
1991
1572
|
output += ` 匹配度: ${(item.score * 100).toFixed(0)}%\n`;
|
|
1992
1573
|
if (item.url) output += ` 链接: ${item.url}\n`;
|
|
1993
1574
|
if (item.content) {
|
|
@@ -2059,26 +1640,14 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2059
1640
|
if (operator_id) {
|
|
2060
1641
|
dingtalk.setOperatorId(operator_id);
|
|
2061
1642
|
}
|
|
1643
|
+
dingtalk._resolvedWorkspaceId = null;
|
|
2062
1644
|
|
|
2063
1645
|
let realDocKey = await resolveDocKey(docKey, operator_id || null, dingtalk);
|
|
2064
1646
|
console.error(`[DEBUG] get_wiki_doc_content: input=${docKey}, resolved=${realDocKey}`);
|
|
2065
1647
|
|
|
2066
|
-
|
|
2067
|
-
if (realDocKey === docKey && workspaceId) {
|
|
2068
|
-
try {
|
|
2069
|
-
const searchRes = await dingtalk.docRequest('GET', `/v1.0/doc/docs`, {
|
|
2070
|
-
operatorId: operator_id || null,
|
|
2071
|
-
extraParams: { workspaceId, keyword: '', maxResults: 50 }
|
|
2072
|
-
});
|
|
2073
|
-
const docs = searchRes.docs || [];
|
|
2074
|
-
const matched = docs.find(d => d.nodeBO?.nodeId === docKey || d.nodeBO?.nodeId === realDocKey) || docs[0];
|
|
2075
|
-
if (matched?.nodeBO?.nodeId) {
|
|
2076
|
-
realDocKey = matched.nodeBO.nodeId;
|
|
2077
|
-
console.error(`[DEBUG] get_wiki_doc_content: search fallback resolved to ${realDocKey}`);
|
|
2078
|
-
}
|
|
2079
|
-
} catch (e) { console.error(`[DEBUG] get_wiki_doc_content: search fallback failed: ${e.message}`); }
|
|
2080
|
-
}
|
|
1648
|
+
const effectiveWsId = workspaceId || dingtalk._resolvedWorkspaceId;
|
|
2081
1649
|
|
|
1650
|
+
// 尝试 blocks API 读取 .adoc 在线文档内容
|
|
2082
1651
|
try {
|
|
2083
1652
|
const result = await dingtalk.docRequest('GET', `/v1.0/doc/suites/documents/${realDocKey}/blocks`, { operatorId: operator_id || null });
|
|
2084
1653
|
const blocks = result.result?.data || [];
|
|
@@ -2100,7 +1669,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2100
1669
|
const isDocKeyIllegal = blocksErr.message.includes('doc key is illegal');
|
|
2101
1670
|
|
|
2102
1671
|
// Fallback: 尝试存储 v1.0 下载 API(对上传的 .md 等文件有效)
|
|
2103
|
-
if (isDocKeyIllegal &&
|
|
1672
|
+
if (isDocKeyIllegal && effectiveWsId) {
|
|
2104
1673
|
try {
|
|
2105
1674
|
const token = await dingtalk.getAccessToken();
|
|
2106
1675
|
const opId = await dingtalk.resolveOperatorId(operator_id || null);
|
|
@@ -2119,7 +1688,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2119
1688
|
if (parentId) dirParams.dentryId = parentId;
|
|
2120
1689
|
const dirRes = await axios({
|
|
2121
1690
|
method: 'GET',
|
|
2122
|
-
url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${
|
|
1691
|
+
url: `${DINGTALK_API_V2}/v2.0/doc/spaces/${effectiveWsId}/directories`,
|
|
2123
1692
|
headers: { 'x-acs-dingtalk-access-token': token },
|
|
2124
1693
|
params: dirParams
|
|
2125
1694
|
});
|
|
@@ -2163,14 +1732,16 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2163
1732
|
let hint = '';
|
|
2164
1733
|
if (isDocKeyIllegal) {
|
|
2165
1734
|
hint = `\n\n原因: 此文档不是 .adoc 在线文档,blocks API 无法读取。`;
|
|
2166
|
-
if (
|
|
2167
|
-
hint += `\n已尝试存储 v1.0 下载 API
|
|
1735
|
+
if (effectiveWsId) {
|
|
1736
|
+
hint += `\n已尝试存储 v1.0 下载 API 但仍失败(需开通 Storage.DownloadInfo.Read 权限)。`;
|
|
1737
|
+
} else {
|
|
1738
|
+
hint += `\n请提供 workspace_id 以尝试存储 v1.0 下载 API。`;
|
|
2168
1739
|
}
|
|
2169
1740
|
}
|
|
2170
1741
|
return {
|
|
2171
1742
|
content: [{
|
|
2172
1743
|
type: 'text',
|
|
2173
|
-
text: `❌ 读取文档内容失败\n错误: ${blocksErr.message}${hint}\n\n调试信息:\n- 输入 doc_key: ${docKey}\n- 解析后 realDocKey: ${realDocKey}\n- workspaceId: ${
|
|
1744
|
+
text: `❌ 读取文档内容失败\n错误: ${blocksErr.message}${hint}\n\n调试信息:\n- 输入 doc_key: ${docKey}\n- 解析后 realDocKey: ${realDocKey}\n- workspaceId: ${effectiveWsId || '未提供'}\n- operator_id: ${operator_id || '未提供(将自动解析)'}`
|
|
2174
1745
|
}],
|
|
2175
1746
|
isError: true
|
|
2176
1747
|
};
|
|
@@ -2185,20 +1756,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2185
1756
|
|
|
2186
1757
|
let realDocKey = await resolveDocKey(docKey, operator_id || null, dingtalk);
|
|
2187
1758
|
|
|
2188
|
-
|
|
2189
|
-
try {
|
|
2190
|
-
const searchRes = await dingtalk.docRequest('GET', `/v1.0/doc/docs`, {
|
|
2191
|
-
operatorId: operator_id || null,
|
|
2192
|
-
extraParams: { workspaceId, keyword: '', maxResults: 50 }
|
|
2193
|
-
});
|
|
2194
|
-
const docs = searchRes.docs || [];
|
|
2195
|
-
const matched = docs.find(d => d.nodeBO?.nodeId === docKey || d.nodeBO?.nodeId === realDocKey) || docs[0];
|
|
2196
|
-
if (matched?.nodeBO?.nodeId) {
|
|
2197
|
-
realDocKey = matched.nodeBO.nodeId;
|
|
2198
|
-
}
|
|
2199
|
-
} catch (e) { /* ignore */ }
|
|
2200
|
-
}
|
|
2201
|
-
|
|
1759
|
+
// 尝试 overwriteContent API 写入 .adoc 在线文档内容
|
|
2202
1760
|
try {
|
|
2203
1761
|
await dingtalk.docRequest('POST', `/v1.0/doc/suites/documents/${realDocKey}/overwriteContent`, {
|
|
2204
1762
|
operatorId: operator_id || null,
|
|
@@ -2208,7 +1766,7 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
|
|
|
2208
1766
|
const isDocKeyIllegal = e.message.includes('doc key is illegal');
|
|
2209
1767
|
let hint = '';
|
|
2210
1768
|
if (isDocKeyIllegal) {
|
|
2211
|
-
hint = `\n\n原因:
|
|
1769
|
+
hint = `\n\n原因: 此文档不是 .adoc 在线文档(可能是上传的 .md 文件),不支持修改。overwriteContent 仅对 .adoc 在线文档有效。`;
|
|
2212
1770
|
}
|
|
2213
1771
|
return {
|
|
2214
1772
|
content: [{ type: 'text', text: `❌ 写入文档内容失败\n${e.message}${hint}` }],
|