🔄 卡若AI 同步 2026-03-02 02:41 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 14 个

This commit is contained in:
2026-03-02 02:41:07 +08:00
parent 1e31a54573
commit 716d5b9fee
5 changed files with 118 additions and 27 deletions

View File

@@ -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 43board/bitable**:多维表格 → 在云空间新建多维表格bitable并将该块嵌入文档
- 其他类型todo、callout 等)按导出结构透传
应用需具备「创建多维表格」权限;若无权限,多维表格块会退化为一段说明文字。
- **判定为多维表格时**:仅调用 bitable 创建接口,产出多维表格链接,不建文档。
- **判定为文档时**block_type 2/3/4/6 等 → 对应正文/标题块block_type 43 → 新建多维表格并嵌入该文档。
- 创建多维表格需应用具备权限并完成用户授权:**bitable:app**、**base:app:create**(飞书开放平台 → 权限配置 → 勾选并让用户重新授权)。
---

View File

@@ -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)

View File

@@ -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"]

View File

@@ -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 个 |

View File

@@ -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) |