feat: 飞书知识库读写工具:增删查改节点、写入页面、创建早报、记录踩坑,基于 tenant_access_token。

This commit is contained in:
NightStar
2026-07-30 15:41:42 +08:00
commit a2ce0f9b75
13 changed files with 875 additions and 0 deletions
+123
View File
@@ -0,0 +1,123 @@
#!/usr/bin/env python3
"""写入 Wiki 页面内容。支持 Markdown 文件和 Block JSON。"""
import json, sys, os, argparse
import subprocess
SPACE_ID = os.environ.get('FEISHU_SPACE_ID', '7664817589230570761')
def get_token():
r = subprocess.run(['python3', os.path.join(os.path.dirname(__file__), 'init_token.py')],
capture_output=True, text=True)
return json.loads(r.stdout)['token']
# Block helpers
def te(t, **kw):
d = {}
d.update(kw)
return {'text_run': {'content': t, 'text_element_style': d}}
def link_el(t, u):
return {'text_run': {'content': t, 'text_element_style': {'link': {'url': u}}}}
H1 = lambda t: {'block_type': 3, 'heading1': {'elements': [te(t, bold=True)], 'style': {}}}
H2 = lambda t: {'block_type': 4, 'heading2': {'elements': [te(t, bold=True)], 'style': {}}}
H3 = lambda t: {'block_type': 5, 'heading3': {'elements': [te(t, bold=True)], 'style': {}}}
DIV = lambda: {'block_type': 22, 'divider': {}}
TXT = lambda t: {'block_type': 2, 'text': {'elements': [te(t)], 'style': {}}}
BLD = lambda t: {'block_type': 2, 'text': {'elements': [te(t, bold=True)], 'style': {}}}
BUL = lambda t, lt='', lu='': {'block_type': 12, 'bullet': {'elements': [te(t)] + ([link_el(' ' + lt, lu)] if lt and lu else []), 'style': {}}}
def json_to_blocks(data):
"""Convert JSON block data to Feishu block objects"""
if isinstance(data, list):
return [json_to_blocks(d) for d in data]
bt = data.get('type', 'text')
text = data.get('text', '')
type_map = {
'h1': lambda: H1(text),
'h2': lambda: H2(text),
'h3': lambda: H3(text),
'text': lambda: TXT(text),
'bold': lambda: BLD(text),
'divider': lambda: DIV(),
'bullet': lambda: BUL(text, data.get('link_text', ''), data.get('link_url', '')),
}
return type_map.get(bt, lambda: TXT(text))()
def write_blocks(node_token, blocks):
import requests
token = get_token()
h = {'Authorization': 'Bearer ' + token, 'Content-Type': 'application/json'}
# Get node detail to find obj_token
r = requests.get(
'https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node',
params={'token': node_token}, headers=h
)
nd = r.json()
if nd.get('code') != 0:
raise Exception(f"Get node failed: {nd.get('msg')}")
doc_id = nd['data']['node']['obj_token']
# Clear defaults
r = requests.get(
f'https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{doc_id}',
headers=h
)
kids = r.json().get('data', {}).get('block', {}).get('children', [])
if kids:
cids = [c['block_id'] for c in kids]
requests.delete(
f'https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{doc_id}/children/batch_delete',
headers=h, json={'start_index': 0, 'end_index': len(cids)}
)
# Write in batches
batch_size = 25
for i in range(0, len(blocks), batch_size):
batch = blocks[i:i + batch_size]
r = requests.post(
f'https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks/{doc_id}/children',
headers=h, json={'children': batch, 'index': -1}
)
if r.json().get('code') != 0:
raise Exception(f"Write batch {i // batch_size} failed: {r.json().get('msg')}")
return True
if __name__ == '__main__':
p = argparse.ArgumentParser()
p.add_argument('--node', required=True, help='Node token')
p.add_argument('--file', default=None, help='Content file (JSON block array or Markdown)')
p.add_argument('--json', default=None, help='Inline JSON block array')
p.add_argument('--markdown', action='store_true', help='Treat --file as Markdown')
args = p.parse_args()
try:
if args.json:
data = json.loads(args.json)
elif args.file:
if args.markdown or args.file.endswith('.md'):
import subprocess
r = subprocess.run(
['python3', os.path.join(os.path.dirname(__file__), 'md_to_blocks.py'), args.file],
capture_output=True, text=True
)
if r.returncode != 0:
raise Exception(f"md_to_blocks failed: {r.stderr}")
data = json.loads(r.stdout)
else:
with open(args.file) as f:
data = json.load(f)
else:
data = json.load(sys.stdin)
if isinstance(data, list) and not isinstance(data, str):
blocks = data
else:
blocks = json_to_blocks(data)
write_blocks(args.node, blocks)
print('OK')
except Exception as e:
print(f'Error: {e}', file=sys.stderr)
sys.exit(1)