feat: 飞书知识库读写工具:增删查改节点、写入页面、创建早报、记录踩坑,基于 tenant_access_token(修复表格写入顺序)

This commit is contained in:
NightStar
2026-08-19 14:43:32 +08:00
parent 0e4d41ed6f
commit d2cd815c42
+49 -46
View File
@@ -48,57 +48,20 @@ def write_blocks(node_token, blocks):
headers=h, json={'start_index': 0, 'end_index': len(cids)} headers=h, json={'start_index': 0, 'end_index': len(cids)}
) )
# Separate table groups from normal blocks # Write blocks in source order: normal blocks interleaved with tables
normal_blocks = []
table_groups = []
i = 0
while i < len(blocks):
b = blocks[i]
if b.get('__table'):
group = []
table_prefix = b.get('block_id', '')
j = i
# Collect table + cells + cell content (all tagged with __table or have matching ecc_ block_id)
while j < len(blocks):
bj = blocks[j]
bid = bj.get('block_id', '')
# Same table group: __table marker, cell/child with matching prefix, or continuation
if bj.get('__table') and j != i:
break
if bj.get('block_type') == 31 and not bj.get('__table'):
break
# Stop at unrelated blocks (non-table, non-cell blocks without matching prefix)
if not bj.get('__table') and not bid.startswith('ecc_t'):
# Only include empty text blocks with block_id (spacers next to table)
if bj.get('block_type') == 2 and bid:
elements = bj.get('text', {}).get('elements', [])
if elements and all(e.get('text_run', {}).get('content', '') == '' for e in elements):
pass # empty spacer included
else:
break
else:
break
group.append(bj)
j += 1
table_groups.append(group)
i = j
else:
normal_blocks.append(b)
i += 1
# Write normal blocks in batches
batch_size = 25 batch_size = 25
for idx in range(0, len(normal_blocks), batch_size):
batch = normal_blocks[idx:idx + batch_size] def write_normal_batch(batch):
for idx in range(0, len(batch), batch_size):
chunk = batch[idx:idx + batch_size]
r = requests.post( r = requests.post(
f'https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{doc_id}/children', f'https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{doc_id}/children',
headers=h, json={'children': batch, 'index': -1} headers=h, json={'children': chunk, 'index': -1}
) )
if r.json().get('code') != 0: if r.json().get('code') != 0:
raise Exception(f"Write batch {idx // batch_size} failed: {r.json().get('msg')}") raise Exception(f"Write batch failed: {r.json().get('msg')}")
# Write table groups via descendant API def write_table_group(group):
for group_idx, group in enumerate(table_groups):
table_block = group[0] table_block = group[0]
descendants = [strip_table_meta(b) for b in group] descendants = [strip_table_meta(b) for b in group]
table_bid = table_block.get('block_id', table_block.get('children', [''])[0]) table_bid = table_block.get('block_id', table_block.get('children', [''])[0])
@@ -113,7 +76,47 @@ def write_blocks(node_token, blocks):
) )
rd = r.json() rd = r.json()
if rd.get('code') != 0: if rd.get('code') != 0:
raise Exception(f"Table {group_idx} descendant API failed: {rd.get('msg')}") raise Exception(f"Table descendant API failed: {rd.get('msg')}")
normal_batch = []
i = 0
while i < len(blocks):
b = blocks[i]
if b.get('__table'):
# Flush pending normal blocks before the table to keep source order
if normal_batch:
write_normal_batch(normal_batch)
normal_batch = []
# Collect this table group: table + cells + cell content
group = []
j = i
while j < len(blocks):
bj = blocks[j]
bid = bj.get('block_id', '')
if bj.get('__table') and j != i:
break
if bj.get('block_type') == 31 and not bj.get('__table'):
break
if not bj.get('__table') and not bid.startswith('ecc_t'):
if bj.get('block_type') == 2 and bid:
elements = bj.get('text', {}).get('elements', [])
if elements and all(e.get('text_run', {}).get('content', '') == '' for e in elements):
pass # empty spacer included
else:
break
else:
break
group.append(bj)
j += 1
write_table_group(group)
i = j
else:
normal_batch.append(b)
i += 1
# Flush remaining normal blocks
if normal_batch:
write_normal_batch(normal_batch)
return True return True