141 lines
4.5 KiB
Python
141 lines
4.5 KiB
Python
#!/usr/bin/env python3
|
|
"""Create a Feishu document and populate it with content blocks.
|
|
|
|
Usage: python3 scripts/create_doc.py <title> <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()
|