#!/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)