#!/usr/bin/env python3 """Create a Feishu document and populate it with content blocks. Usage: python3 scripts/create_doc.py <markdown_file> Reads Feishu credentials from ~/.hermes/.env (FEISHU_APP_ID, FEISHU_APP_SECRET). Creates a docx document with the given title, then converts basic markdown to Feishu doc blocks and adds them. Currently supports: # H1, ## H2, ### H3, - bullet, blank line separator, plain text paragraphs. For richer content, extend build_blocks(). """ import json, os, re, requests, sys def get_feishu_token(): """Get tenant access token from Feishu Open API.""" env_path = os.path.expanduser("~/.hermes/.env") creds = {} with open(env_path) as f: for line in f: line = line.strip() if not line or line.startswith("#") or "=" not in line: continue k, v = line.split("=", 1) creds[k.strip()] = v.strip().strip('"').strip("'") aid = creds.get("FEISHU_APP_ID") sec = creds.get("FEISHU_APP_SECRET") if not aid or not sec: sys.exit("FEISHU_APP_ID or FEISHU_APP_SECRET not found in .env") r = requests.post( "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal", json={"app_id": aid, "app_secret": sec}, timeout=10, ) return r.json()["tenant_access_token"], aid, sec def text_element(content): return [{"text_run": {"content": content}}] def build_blocks(markdown_text): """Convert basic markdown to Feishu doc blocks.""" blocks = [] lines = markdown_text.strip().split("\n") i = 0 while i < len(lines): line = lines[i] # Blank line → divider or skip if not line.strip(): # Two blank lines → divider if i + 1 < len(lines) and not lines[i + 1].strip(): blocks.append({"block_type": 22, "divider": {}}) i += 2 continue i += 1 continue # Heading 3 if line.startswith("### "): blocks.append({"block_type": 5, "heading3": {"elements": text_element(line[4:])}}) # Heading 2 elif line.startswith("## "): blocks.append({"block_type": 4, "heading2": {"elements": text_element(line[3:])}}) # Heading 1 elif line.startswith("# "): blocks.append({"block_type": 3, "heading1": {"elements": text_element(line[2:])}}) # Bullet elif line.startswith("- ") or line.startswith("* "): blocks.append({"block_type": 12, "bullet": {"elements": text_element(line[2:])}}) # Plain text else: blocks.append({"block_type": 2, "text": {"elements": text_element(line)}}) i += 1 return blocks def main(): if len(sys.argv) < 2: print("Usage: python3 create_doc.py <title> [markdown_file]") print(" If no markdown_file, reads from stdin.") sys.exit(1) title = sys.argv[1] if len(sys.argv) >= 3: with open(sys.argv[2], encoding="utf-8") as f: markdown = f.read() else: markdown = sys.stdin.read() token, _, _ = get_feishu_token() headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} # Step 1: Create document print(f"Creating doc: {title}") r = requests.post( "https://open.feishu.cn/open-apis/docx/v1/documents", headers=headers, json={"title": title}, timeout=10, ) result = r.json() if result.get("code") != 0: sys.exit(f"Create failed: {result.get('msg')}") doc_id = result["data"]["document"]["document_id"] print(f" doc_id: {doc_id}") # Step 2: Build and add blocks blocks = build_blocks(markdown) if not blocks: print(" No blocks to add (empty markdown)") else: batch_size = 50 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=headers, json={"children": batch}, timeout=15, ) result = r.json() if result.get("code") != 0: sys.exit(f"Block add failed (batch {i//batch_size}): {result.get('msg')}") print(f" Batch {i//batch_size + 1}: {len(batch)} blocks OK") url = f"https://xiteng.feishu.cn/docx/{doc_id}" print(f"\nāœ… {url}") print(doc_id) if __name__ == "__main__": main()