Files
company-kb/tools/feishu-pull.py
yangqianqian 84f7950589 Initial commit: company-kb framework
- 完整的知识库框架(P0-P5 路线图)
- 12篇治理文档
- 工具脚本(kb-init.sh, kb-lint-fm.py, feishu-sync.py, kb-bot)
- 7个 Claude 命令
- 示例项目和测试项目(wanniu-l1)
- 契约文件(meta/kb-contract.yaml)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 18:24:39 +08:00

132 lines
4.2 KiB
Python
Executable File
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env python3
"""feishu-pull.py —— 飞书半自动单向导出(飞书 → docs/
MVP 形态:半自动单向。飞书只作「文档来源」,不直接写 Git。
凭据走环境变量(绝不入库):
FEISHU_APP_ID, FEISHU_APP_SECRET
用法:
# 干跑(默认):只打印将要导出什么,不落盘
python3 tools/feishu-pull.py <代号> --doc <飞书文档token或url>
# 实际写入 projects/<代号>/docs/
python3 tools/feishu-pull.py <代号> --doc <token> --write
AI 自动补齐 frontmattertitle(H1回填) / ingested(今天) / source(飞书文档) / source_link。
去重:写前查 meta/ingest-manifest.json 的内容 hash。
注意:本文件是接口骨架。飞书 SDK 调用留 TODO接入时填 _fetch_doc()。
凭据缺失时优雅报错,不泄露。
"""
import sys
import os
import json
import hashlib
import argparse
import datetime
ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
MANIFEST = os.path.join(ROOT, "meta", "ingest-manifest.json")
def load_manifest():
if os.path.isfile(MANIFEST):
with open(MANIFEST, encoding="utf-8") as f:
return json.load(f)
return {"_comment": "", "entries": {}}
def save_manifest(m):
with open(MANIFEST, "w", encoding="utf-8") as f:
json.dump(m, f, ensure_ascii=False, indent=2)
def content_hash(text):
return hashlib.sha256(text.encode("utf-8")).hexdigest()
def _fetch_doc(token):
"""接入飞书时实现:用 tenant_access_token 拉文档,返回 (标题, markdown 正文, 原始链接)。
现为占位,避免误导:明确报未接入。"""
app_id = os.environ.get("FEISHU_APP_ID")
app_secret = os.environ.get("FEISHU_APP_SECRET")
if not app_id or not app_secret:
raise RuntimeError(
"缺凭据:请设置环境变量 FEISHU_APP_ID / FEISHU_APP_SECRET不要写进代码或 .env 入库)"
)
# TODO: 接入 lark-oapi SDK
# 1) POST /open-apis/auth/v3/tenant_access_token/internal
# 2) GET /open-apis/docx/v1/documents/{token}/raw_content
raise NotImplementedError(
"飞书 SDK 调用尚未接入P2 阶段实现)。当前仅提供 frontmatter 自动补齐与去重骨架。"
)
def build_page(title, body, source_link):
today = datetime.date.today().isoformat()
fm = (
"---\n"
"type: doc\n"
f'title: "{title}"\n'
"status: seed\n"
f"created: {today}\n"
f"ingested: {today}\n"
"source: 飞书文档\n"
f"source_link: {source_link}\n"
"tags: []\n"
"related: []\n"
"---\n\n"
f"# {title}\n\n"
f"{body}\n"
)
return fm
def slugify(title):
keep = "".join(c if c.isalnum() or c in " -_" else "" for c in title)
return "-".join(keep.split())[:60] or "untitled"
def main():
ap = argparse.ArgumentParser(description="飞书半自动单向导出")
ap.add_argument("code", help="项目代号(英文/拼音)")
ap.add_argument("--doc", required=True, help="飞书文档 token 或 URL")
ap.add_argument("--write", action="store_true", help="实际写盘(默认干跑)")
args = ap.parse_args()
outdir = os.path.join(ROOT, "projects", args.code, "docs")
try:
title, body, link = _fetch_doc(args.doc)
except (RuntimeError, NotImplementedError) as e:
print(f"[飞书导出未就绪] {e}", file=sys.stderr)
sys.exit(2)
page = build_page(title, body, link)
h = content_hash(body)
manifest = load_manifest()
if h in manifest.get("entries", {}):
print(f"[去重] 内容已存在于 {manifest['entries'][h]},跳过。")
return
fname = slugify(title) + ".md"
dest = os.path.join(outdir, fname)
rel = os.path.relpath(dest, ROOT)
if not args.write:
print(f"[干跑] 将写入: {rel}\n--- frontmatter 预览 ---\n{page[:400]}...")
print("加 --write 实际落盘。")
return
os.makedirs(outdir, exist_ok=True)
with open(dest, "w", encoding="utf-8") as f:
f.write(page)
manifest.setdefault("entries", {})[h] = rel
save_manifest(manifest)
print(f"[已导出] {rel}")
if __name__ == "__main__":
main()