🔄 卡若AI 同步 2026-03-02 02:41 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个
This commit is contained in:
@@ -351,24 +351,24 @@ JSON 格式:与 `团队入职流程与新人登记表_feishu_blocks.json` 相
|
||||
|
||||
## 飞书导出 JSON 按原格式上传
|
||||
|
||||
将飞书导出的 JSON 文件(含 `content` + `blocks`)**按原有类型**上传为 Wiki 子文档:文档保持文档、多维表格会新建多维表格并嵌入、问卷/思维笔记等按 JSON 内类型还原。
|
||||
将飞书导出的 JSON 文件(含 `content` + `blocks`)上传时,**先根据 JSON 类型决定创建什么**,再执行创建,避免「该是多维表格却建成文档」的错误。
|
||||
|
||||
**规则**:原 JSON 里是什么格式就生成什么格式;不把多维表格/看板换成链接,直接生成对应文档或多维表格块。
|
||||
**规则(强制)**:
|
||||
1. **先看 JSON 类型**:根为 block_type 43(多维表格/board),或根为 page 且直接子块中唯一实质内容为一块多维表格 → 判定为**多维表格**。
|
||||
2. **多维表格** → 只创建**飞书多维表格**(独立应用),不创建文档;结果链接为 `https://cunkebao.feishu.cn/base/{app_token}`。
|
||||
3. **文档** → 创建 Wiki 文档并写入块;其中的 block_type 43 会新建多维表格并嵌入文档内。
|
||||
|
||||
```bash
|
||||
# 上传单个导出 JSON(默认父节点:日记/新研究)
|
||||
# 上传单个导出 JSON(自动判断文档/多维表格)
|
||||
python3 脚本/upload_json_to_feishu_doc.py /path/to/xxx.json
|
||||
|
||||
# 指定父节点与标题
|
||||
# 指定父节点与标题(仅创建文档时 --parent 生效)
|
||||
python3 脚本/upload_json_to_feishu_doc.py /path/to/xxx.json --parent <wiki_node_token> --title "文档标题"
|
||||
```
|
||||
|
||||
- **block_type 2**:正文 → 正文块
|
||||
- **block_type 3/4/6**:标题 → 对应标题块
|
||||
- **block_type 43(board/bitable)**:多维表格 → 在云空间新建多维表格(bitable),并将该块嵌入文档
|
||||
- 其他类型(todo、callout 等)按导出结构透传
|
||||
|
||||
应用需具备「创建多维表格」权限;若无权限,多维表格块会退化为一段说明文字。
|
||||
- **判定为多维表格时**:仅调用 bitable 创建接口,产出多维表格链接,不建文档。
|
||||
- **判定为文档时**:block_type 2/3/4/6 等 → 对应正文/标题块;block_type 43 → 新建多维表格并嵌入该文档。
|
||||
- 创建多维表格需应用具备权限并完成用户授权:**bitable:app**、**base:app:create**(飞书开放平台 → 权限配置 → 勾选并让用户重新授权)。
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
将飞书导出的 JSON 文件(含 content + blocks)按**原有格式**上传为飞书 Wiki 子文档。
|
||||
- 文档→文档,多维表格→多维表格(block_type 43 会新建多维表格并嵌入),问卷/思维笔记等按类型还原。
|
||||
将飞书导出的 JSON 文件(含 content + blocks)按**原有类型**上传:先根据 JSON 判断是文档还是多维表格,再创建对应类型。
|
||||
- 若 JSON 表示的是多维表格(根为 board/block_type 43,或文档内唯一实质内容为一块多维表格)→ 只创建飞书多维表格,不创建文档。
|
||||
- 若 JSON 表示的是文档 → 创建 Wiki 文档并写入块(其中 block_type 43 会新建多维表格并嵌入)。
|
||||
用法: python3 upload_json_to_feishu_doc.py /path/to/xxx.json
|
||||
可选: --parent <wiki_node_token> --title "文档标题"
|
||||
"""
|
||||
@@ -19,6 +20,67 @@ from feishu_wiki_create_doc import create_wiki_doc, get_token, CONFIG
|
||||
|
||||
# 默认 Wiki 父节点(与 SKILL 中「日记分享/新研究」一致)
|
||||
DEFAULT_PARENT = "KNf7wA8Rki1NSdkkSIqcdFtTnWb"
|
||||
# 飞书多维表格 base URL(独立应用)
|
||||
FEISHU_BASE_URL = "https://cunkebao.feishu.cn/base"
|
||||
|
||||
|
||||
def detect_export_type(data: dict) -> tuple[str, str]:
|
||||
"""
|
||||
根据 JSON 结构判断导出类型:多维表格 or 文档。
|
||||
返回 ( "bitable" | "docx", 用于命名的 title )。
|
||||
规则:根为 block_type 43 → 多维表格;根为 page(1) 且直接子块中唯一实质内容为一块 43 → 多维表格;否则为文档。
|
||||
"""
|
||||
blocks = data.get("blocks") or []
|
||||
if not blocks:
|
||||
return "docx", (data.get("content") or "未命名").split("\n")[0].strip() or "未命名"
|
||||
|
||||
by_id = {b.get("block_id"): b for b in blocks}
|
||||
root = None
|
||||
for b in blocks:
|
||||
if b.get("block_type") == 43 and b.get("parent_id") == "":
|
||||
# 根节点本身就是多维表格
|
||||
name = (data.get("content") or "多维表格").split("\n")[0].strip() or "多维表格"
|
||||
return "bitable", name
|
||||
if b.get("block_type") == 1 or b.get("parent_id") == "":
|
||||
root = b
|
||||
break
|
||||
|
||||
if not root:
|
||||
return "docx", (data.get("content") or "未命名").split("\n")[0].strip() or "未命名"
|
||||
|
||||
# 根为 page(1):看直接子块是否「仅一块多维表格 + 至多一条短文本(标题/标签)」
|
||||
child_ids = root.get("children") or []
|
||||
has_board = False
|
||||
name_from_page = ""
|
||||
if root.get("page") and root["page"].get("elements"):
|
||||
for el in root["page"]["elements"]:
|
||||
c = el.get("text_run", {}).get("content", "").strip()
|
||||
if c:
|
||||
name_from_page = c
|
||||
break
|
||||
if not name_from_page and data.get("content"):
|
||||
name_from_page = data["content"].split("\n")[0].strip() or "多维表格"
|
||||
|
||||
non_empty_text_count = 0
|
||||
board_count = 0
|
||||
name_from_text = ""
|
||||
for bid in child_ids:
|
||||
b = by_id.get(bid)
|
||||
if not b:
|
||||
continue
|
||||
if b.get("block_type") == 43:
|
||||
board_count += 1
|
||||
elif b.get("block_type") == 2 and b.get("text"):
|
||||
content = "".join(el.get("text_run", {}).get("content", "") for el in (b["text"].get("elements") or [])).strip()
|
||||
if content and not content.startswith("http"):
|
||||
non_empty_text_count += 1
|
||||
if not name_from_text:
|
||||
name_from_text = content[:50]
|
||||
|
||||
# 若直接子块里有一块多维表格,且其余至多一条非链接文本(标题/标签)→ 视为「以多维表格为主」,创建多维表格
|
||||
if board_count >= 1 and non_empty_text_count <= 1:
|
||||
return "bitable", name_from_text or name_from_page or "多维表格"
|
||||
return "docx", name_from_page or "未命名"
|
||||
|
||||
|
||||
def _to_api_block(b: dict) -> dict | None:
|
||||
@@ -55,8 +117,8 @@ def _to_api_block(b: dict) -> dict | None:
|
||||
return out
|
||||
|
||||
|
||||
def create_bitable_app(access_token: str, name: str, folder_token: str | None = None) -> str | None:
|
||||
"""在飞书云空间创建多维表格,返回 app_token。"""
|
||||
def create_bitable_app(access_token: str, name: str, folder_token: str | None = None) -> tuple[str | None, str]:
|
||||
"""在飞书云空间创建多维表格。返回 (app_token, error_msg)。"""
|
||||
url = "https://open.feishu.cn/open-apis/bitable/v1/apps"
|
||||
headers = {"Authorization": f"Bearer {access_token}", "Content-Type": "application/json"}
|
||||
payload = {"name": name or "多维表格"}
|
||||
@@ -65,8 +127,9 @@ def create_bitable_app(access_token: str, name: str, folder_token: str | None =
|
||||
r = requests.post(url, headers=headers, json=payload, timeout=30)
|
||||
data = r.json()
|
||||
if data.get("code") == 0:
|
||||
return data.get("data", {}).get("app_token")
|
||||
return None
|
||||
return data.get("data", {}).get("app_token"), ""
|
||||
msg = data.get("msg", str(data))
|
||||
return None, msg
|
||||
|
||||
|
||||
def blocks_from_export_json(data: dict) -> tuple[str, list]:
|
||||
@@ -135,7 +198,7 @@ def resolve_bitable_placeholders(children: list, access_token: str, default_name
|
||||
for i, c in enumerate(children):
|
||||
if isinstance(c, dict) and c.get("_bitable_placeholder") and c.get("block_type") == 43:
|
||||
name = c.get("name") or default_name
|
||||
app_token = create_bitable_app(access_token, name)
|
||||
app_token, _ = create_bitable_app(access_token, name)
|
||||
if app_token:
|
||||
out.append({"block_type": 43, "bitable": {"token": app_token}})
|
||||
time.sleep(0.4)
|
||||
@@ -149,10 +212,10 @@ def resolve_bitable_placeholders(children: list, access_token: str, default_name
|
||||
|
||||
|
||||
def main():
|
||||
ap = argparse.ArgumentParser(description="将飞书导出 JSON 按原格式上传为飞书 Wiki 文档")
|
||||
ap = argparse.ArgumentParser(description="将飞书导出 JSON 按类型上传:先判断文档/多维表格,再创建对应类型")
|
||||
ap.add_argument("json_path", help="JSON 文件路径(含 content + blocks)")
|
||||
ap.add_argument("--parent", default=DEFAULT_PARENT, help="Wiki 父节点 token")
|
||||
ap.add_argument("--title", default=None, help="覆盖文档标题(默认从 JSON 解析)")
|
||||
ap.add_argument("--parent", default=DEFAULT_PARENT, help="Wiki 父节点 token(仅创建文档时使用)")
|
||||
ap.add_argument("--title", default=None, help="覆盖标题/名称(默认从 JSON 解析)")
|
||||
args = ap.parse_args()
|
||||
|
||||
path = Path(args.json_path)
|
||||
@@ -163,19 +226,45 @@ def main():
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
|
||||
title, children = blocks_from_export_json(data)
|
||||
export_type, name = detect_export_type(data)
|
||||
if args.title:
|
||||
title = args.title
|
||||
name = args.title
|
||||
|
||||
token = get_token(args.parent)
|
||||
if not token:
|
||||
print("❌ 无法获取飞书 Token")
|
||||
sys.exit(1)
|
||||
|
||||
children = resolve_bitable_placeholders(children, token, default_name="流量来源")
|
||||
# 先根据 JSON 类型决定创建什么
|
||||
if export_type == "bitable":
|
||||
print("=" * 50)
|
||||
print(f"📤 检测为多维表格,创建飞书多维表格:{name}")
|
||||
print("=" * 50)
|
||||
app_token, err = create_bitable_app(token, name)
|
||||
if app_token:
|
||||
result = f"{FEISHU_BASE_URL}/{app_token}"
|
||||
print("✅ 创建成功(多维表格)")
|
||||
print(f"📎 {result}")
|
||||
try:
|
||||
import subprocess
|
||||
subprocess.run(["open", result], capture_output=True)
|
||||
except Exception:
|
||||
pass
|
||||
else:
|
||||
print(f"❌ 多维表格创建失败:{err or '未知错误'}")
|
||||
print(" 请检查应用是否具备「创建多维表格」权限(飞书开放平台 → 权限配置)")
|
||||
sys.exit(1)
|
||||
print("=" * 50)
|
||||
return
|
||||
|
||||
# 文档:创建 Wiki 文档并写入块
|
||||
title, children = blocks_from_export_json(data)
|
||||
if args.title:
|
||||
title = args.title
|
||||
children = resolve_bitable_placeholders(children, token, default_name=name or "多维表格")
|
||||
|
||||
print("=" * 50)
|
||||
print(f"📤 上传为飞书文档(按原格式):{title}")
|
||||
print(f"📤 检测为文档,上传为飞书文档:{title}")
|
||||
print(f" 父节点: {args.parent}")
|
||||
print(f" 块数: {len(children)}")
|
||||
print("=" * 50)
|
||||
|
||||
@@ -120,7 +120,7 @@ def _draw_speed_lines(draw, W, H, n=40, color=(255, 220, 230, 80)):
|
||||
|
||||
def _draw_vignette(img, strength=0.4):
|
||||
"""四角暗角"""
|
||||
from PIL import ImageDraw
|
||||
from PIL import Image, ImageDraw
|
||||
W, H = img.size
|
||||
overlay = Image.new("RGBA", (W, H), (0, 0, 0, 0))
|
||||
draw = ImageDraw.Draw(overlay)
|
||||
@@ -133,7 +133,6 @@ def _draw_vignette(img, strength=0.4):
|
||||
draw.point((x, y), fill=(0, 0, 0, a))
|
||||
out = Image.new("RGB", (W, H))
|
||||
out.paste(img, (0, 0))
|
||||
from PIL import Image
|
||||
out.paste(overlay, (0, 0), overlay)
|
||||
return out
|
||||
|
||||
@@ -200,7 +199,8 @@ def generate_with_pil():
|
||||
for r in range(350, 100, -15):
|
||||
alpha = 25 if r > 200 else 15
|
||||
gdraw.ellipse([cx - r, cy - r, cx + r, cy + r], outline=(255, 245, 255, alpha), width=4)
|
||||
img.paste(glow, (0, 0), glow)
|
||||
img_rgba = img.convert("RGBA")
|
||||
img = Image.alpha_composite(img_rgba, glow).convert("RGB")
|
||||
|
||||
draw = ImageDraw.Draw(img)
|
||||
s = sc["silhouette"]
|
||||
|
||||
@@ -193,3 +193,4 @@
|
||||
| 2026-03-02 00:14:40 | 🔄 卡若AI 同步 2026-03-02 00:14 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 14 个 |
|
||||
| 2026-03-02 00:25:52 | 🔄 卡若AI 同步 2026-03-02 00:25 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 |
|
||||
| 2026-03-02 02:30:31 | 🔄 卡若AI 同步 2026-03-02 02:30 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 |
|
||||
| 2026-03-02 02:35:50 | 🔄 卡若AI 同步 2026-03-02 02:35 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 |
|
||||
|
||||
@@ -196,3 +196,4 @@
|
||||
| 2026-03-02 00:14:40 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 00:14 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-02 00:25:52 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 00:25 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-02 02:30:31 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 02:30 | 更新:金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
| 2026-03-02 02:35:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-02 02:35 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||
|
||||
Reference in New Issue
Block a user