diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md index 63dc6f04..54a9ba8a 100755 --- a/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/SKILL.md @@ -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 --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**(飞书开放平台 → 权限配置 → 勾选并让用户重新授权)。 --- diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py index 2801af90..1caecc0c 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/upload_json_to_feishu_doc.py @@ -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 --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) diff --git a/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py b/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py index a0fdb1eb..07b32811 100644 --- a/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py +++ b/03_卡木(木)/木果_项目模板/PPT制作/脚本/generate_novel_illustrations.py @@ -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"] diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 002098e0..eb0621ce 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -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 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 507b4d45..7c67d35e 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -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) |