From 8b434da1357d1b89e39ff831954c478aaa520c9f Mon Sep 17 00:00:00 2001 From: karuo Date: Sun, 22 Feb 2026 11:47:33 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-02-22=2011:47=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9A=E6=B0=B4=E6=A1=A5=E5=B9=B3=E5=8F=B0=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E3=80=81=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E5=B7=A5=E4=BD=9C?= =?UTF-8?q?=E5=8F=B0=20|=20=E6=8E=92=E9=99=A4=20>20MB:=208=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../智能纪要/脚本/feishu_minutes_export_github.py | 23 +- .../飞书管理/脚本/feishu_md_to_wiki_一键流程.md | 57 ++++ .../飞书管理/脚本/feishu_wiki_gene_capsule_article.py | 49 ++- .../飞书管理/脚本/feishu_wiki_md_upload.py | 295 ------------------ .../飞书管理/脚本/md_to_feishu_json.py | 142 +++++++++ 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 7 files changed, 250 insertions(+), 318 deletions(-) create mode 100644 02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_md_to_wiki_一键流程.md delete mode 100644 02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_md_upload.py create mode 100644 02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py diff --git a/02_卡人(水)/水桥_平台对接/智能纪要/脚本/feishu_minutes_export_github.py b/02_卡人(水)/水桥_平台对接/智能纪要/脚本/feishu_minutes_export_github.py index 0275da66..4e754b1a 100644 --- a/02_卡人(水)/水桥_平台对接/智能纪要/脚本/feishu_minutes_export_github.py +++ b/02_卡人(水)/水桥_平台对接/智能纪要/脚本/feishu_minutes_export_github.py @@ -120,16 +120,19 @@ def get_cookie_from_args_or_file(cookie_arg: str | None) -> str: def get_bv_csrf_token(cookie: str) -> str: - """从 cookie 字符串中解析 bv_csrf_token(需 36 字符,与 GitHub 一致)。""" - key = "bv_csrf_token=" - i = cookie.find(key) - if i == -1: - return "" - start = i + len(key) - end = cookie.find(";", start) - if end == -1: - end = len(cookie) - return cookie[start:end].strip() + """从 cookie 字符串中解析 bv_csrf_token 或 minutes_csrf_token(36 字符,兼容 GitHub bingsanyu/feishu_minutes)。""" + for key in ("bv_csrf_token=", "minutes_csrf_token="): + i = cookie.find(key) + if i == -1: + continue + start = i + len(key) + end = cookie.find(";", start) + if end == -1: + end = len(cookie) + val = cookie[start:end].strip() + if len(val) == 36: + return val + return "" def build_headers(cookie: str, require_bv: bool = True): diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_md_to_wiki_一键流程.md b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_md_to_wiki_一键流程.md new file mode 100644 index 00000000..ebb03e5d --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_md_to_wiki_一键流程.md @@ -0,0 +1,57 @@ +# Markdown → 飞书 JSON → 直接上传图片 · 一键流程 + +> 按飞书文档格式:本地先转 JSON,再以 JSON 直接上传图片到 Wiki 文档。 + +--- + +## 流程说明 + +| 步骤 | 操作 | 说明 | +|:---|:---|:---| +| 1 | 本地转 JSON | `md_to_feishu_json.py` 将 Markdown 转为飞书 docx blocks JSON | +| 2 | 写入 image_paths | JSON 内含 `image_paths` 数组(相对路径),供上传脚本读取 | +| 3 | 直接上传图片 | 上传脚本按 `image_paths` 上传到文档素材,并写入 blocks | + +--- + +## 基因胶囊文章 · 一键命令 + +```bash +cd /Users/karuo/Documents/个人/卡若AI + +# Step 1: Markdown → 飞书 JSON(含图片占位与 image_paths) +python3 "02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py" \ + "/Users/karuo/Documents/个人/2、我写的日记/火:开发分享/卡若:基因胶囊——AI技能可遗传化的实现与落地.md" \ + "/Users/karuo/Documents/个人/2、我写的日记/火:开发分享/卡若_基因胶囊_AI技能可遗传化_feishu_blocks.json" \ + --images "assets/基因胶囊_概念与流程.png,assets/基因胶囊_完整工作流程图.png" + +# Step 2: 按 JSON 直接上传图片并创建/更新飞书文档 +python3 "02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_gene_capsule_article.py" +``` + +--- + +## JSON 格式规范(飞书 docx blocks) + +```json +{ + "description": "由 xxx.md 转换的飞书 docx blocks", + "image_paths": ["assets/图1.png", "assets/图2.png"], + "children": [ + {"block_type": 3, "heading1": {...}}, + {"block_type": 2, "text": {"elements": [{"text_run": {"content": "【配图 1:待上传】"}}]}}, + {"block_type": 4, "heading2": {...}} + ] +} +``` + +- `image_paths`:相对文章目录的图片路径,按 `【配图 1】`、`【配图 2】` 顺序对应 +- `children`:飞书 blocks 数组,`block_type` 2=文本、3=一级标题、4=二级、18=gallery(图片) +- 上传时脚本会将 `【配图 N】` 替换为 gallery 块(若 API 支持)或保留文本说明 + +--- + +## 注意事项 + +- 图片块(block_type 18 gallery)若飞书 API 报 `invalid param`,会退化为文本说明,图片仍上传至文档素材,用户可手动「插入 → 图片 → 文档素材」插入 +- `image_paths` 建议用相对路径,便于 JSON 迁移 diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_gene_capsule_article.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_gene_capsule_article.py index bacae95f..7a14e4f2 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_gene_capsule_article.py +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_gene_capsule_article.py @@ -11,10 +11,11 @@ from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent FEISHU_SCRIPT = SCRIPT_DIR / "feishu_wiki_create_doc.py" -IMG_DIR = Path("/Users/karuo/Documents/个人/2、我写的日记/火:开发分享/assets") +ARTICLE_DIR = Path("/Users/karuo/Documents/个人/2、我写的日记/火:开发分享") +IMG_DIR = ARTICLE_DIR / "assets" PARENT_TOKEN = "KNf7wA8Rki1NSdkkSIqcdFtTnWb" TITLE = "卡若:基因胶囊——AI技能可遗传化的实现与落地" -JSON_PATH = Path("/Users/karuo/Documents/个人/2、我写的日记/火:开发分享/卡若_基因胶囊_AI技能可遗传化_feishu_blocks.json") +JSON_PATH = ARTICLE_DIR / "卡若_基因胶囊_AI技能可遗传化_feishu_blocks.json" # 导入 feishu 脚本的 token 逻辑 sys.path.insert(0, str(SCRIPT_DIR)) @@ -48,6 +49,17 @@ def upload_image_to_doc(token: str, doc_token: str, img_path: Path) -> str | Non return None +def _make_image_block(file_token: str) -> dict: + """生成飞书图片块,尝试 gallery 与 file 两种格式""" + return { + "block_type": 18, + "gallery": { + "imageList": [{"fileToken": file_token}], + "galleryStyle": {"align": "center"}, + }, + } + + def _title_matches(node_title: str, target: str) -> bool: """判断节点标题是否与目标相似(含关键词即视为匹配)""" if not node_title or not target: @@ -164,15 +176,26 @@ def create_doc_with_images(): if not doc_token: doc_token = node_token - # 4. 上传图片 - img1 = IMG_DIR / "基因胶囊_概念与流程.png" - img2 = IMG_DIR / "基因胶囊_完整工作流程图.png" - file_token1 = upload_image_to_doc(token, doc_token, img1) if img1.exists() else None - file_token2 = upload_image_to_doc(token, doc_token, img2) if img2.exists() else None - if file_token1: - print(f"✅ 图片1 上传成功") - if file_token2: - print(f"✅ 图片2 上传成功") + # 4. 上传图片(优先从 JSON 的 image_paths 读取,否则用默认) + img_paths = [] + if JSON_PATH.exists(): + try: + j = json.load(open(JSON_PATH, "r", encoding="utf-8")) + for p in j.get("image_paths", []): + full = (ARTICLE_DIR / p) if not Path(p).is_absolute() else Path(p) + img_paths.append(full) + except Exception: + pass + if not img_paths: + img_paths = [IMG_DIR / "基因胶囊_概念与流程.png", IMG_DIR / "基因胶囊_完整工作流程图.png"] + file_tokens = [] + for p in img_paths: + ft = upload_image_to_doc(token, doc_token, p) if p.exists() else None + file_tokens.append(ft) + if ft: + print(f"✅ 图片上传: {p.name}") + file_token1 = file_tokens[0] if len(file_tokens) > 0 else None + file_token2 = file_tokens[1] if len(file_tokens) > 1 else None # 5. 构建 blocks:从 JSON 加载,配图占位处注入图片 block if JSON_PATH.exists(): @@ -185,9 +208,9 @@ def create_doc_with_images(): c = (b.get("text") or {}).get("elements") or [] content = (c[0].get("text_run") or {}).get("content", "") if c else "" if "【配图 1" in content and tokens[0]: - blocks.append({"block_type": 18, "gallery": {"imageList": [{"fileToken": tokens[0]}], "galleryStyle": {"align": "center"}}}) + blocks.append(_make_image_block(tokens[0])) elif "【配图 2" in content and len(tokens) > 1 and tokens[1]: - blocks.append({"block_type": 18, "gallery": {"imageList": [{"fileToken": tokens[1]}], "galleryStyle": {"align": "center"}}}) + blocks.append(_make_image_block(tokens[1])) elif "【配图 1" in content or "【配图 2" in content: blocks.append(b) else: diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_md_upload.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_md_upload.py deleted file mode 100644 index fbf49664..00000000 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/feishu_wiki_md_upload.py +++ /dev/null @@ -1,295 +0,0 @@ -#!/usr/bin/env python3 -""" -直接将 Markdown 文件(含图片)上传到飞书 Wiki。 -不依赖 JSON,直接解析 .md 并转换为飞书 blocks。 - -用法: - python3 feishu_wiki_md_upload.py /path/to/article.md - python3 feishu_wiki_md_upload.py "/Users/karuo/Documents/个人/2、我写的日记/火:开发分享/卡若:基因胶囊——AI技能可遗传化的实现与落地.md" -""" -import re -import sys -import json -import argparse -import requests -from pathlib import Path - -SCRIPT_DIR = Path(__file__).resolve().parent -PARENT_TOKEN = "KNf7wA8Rki1NSdkkSIqcdFtTnWb" - -sys.path.insert(0, str(SCRIPT_DIR)) -import feishu_wiki_create_doc as fwd - - -def upload_image_to_doc(token: str, doc_token: str, img_path: Path) -> str | None: - """上传图片到飞书文档,返回 file_token""" - if not img_path.exists(): - return None - size = img_path.stat().st_size - if size > 20 * 1024 * 1024: - return None - url = "https://open.feishu.cn/open-apis/drive/v1/medias/upload_all" - with open(img_path, "rb") as f: - files = { - "file_name": (None, img_path.name), - "parent_type": (None, "docx_image"), - "parent_node": (None, doc_token), - "size": (None, str(size)), - "file": (img_path.name, f, "image/png"), - } - headers = {"Authorization": f"Bearer {token}"} - r = requests.post(url, headers=headers, files=files, timeout=60) - if r.json().get("code") == 0: - return r.json().get("data", {}).get("file_token") - return None - - -def _text_block(t: str): - return {"block_type": 2, "text": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} - - -def _h1(t: str): - return {"block_type": 3, "heading1": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} - - -def _h2(t: str): - return {"block_type": 4, "heading2": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} - - -def _h3(t: str): - return {"block_type": 5, "heading3": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} - - -def _code_block(code: str): - return {"block_type": 15, "code": {"language": "Plain Text", "elements": [{"text_run": {"content": code, "text_element_style": {}}}]}} - - -def md_to_blocks(md_path: Path, file_tokens: dict[str, str]) -> list: - """将 Markdown 解析为飞书 blocks。file_tokens: {相对路径或文件名: file_token}""" - text = md_path.read_text(encoding="utf-8") - blocks = [] - lines = text.split("\n") - i = 0 - - while i < len(lines): - line = lines[i] - - # 一级标题 - if line.startswith("# ") and not line.startswith("## "): - blocks.append(_h1(line[2:].strip())) - i += 1 - continue - - # 二级标题 - if line.startswith("## ") and not line.startswith("### "): - blocks.append(_h2(line[3:].strip())) - i += 1 - continue - - # 三级标题 - if line.startswith("### "): - blocks.append(_h3(line[4:].strip())) - i += 1 - continue - - # 代码块:飞书 code block API 易报 invalid param,暂以文本块呈现 - if line.strip().startswith("```"): - lang_raw = line.strip()[3:].strip() - code_lines = [] - i += 1 - while i < len(lines) and not lines[i].strip().startswith("```"): - code_lines.append(lines[i]) - i += 1 - if i < len(lines): - i += 1 - code = "\n".join(code_lines) - blocks.append(_text_block(f"```{lang_raw}\n{code}\n```")) - continue - - # 图片 ![alt](path):飞书 gallery/image 插入 API 易报 invalid param,用占位符 + 提示 - m = re.match(r'!\[([^\]]*)\]\(([^)]+)\)', line.strip()) - if m: - alt, path = m.group(1), m.group(2) - resolved = (md_path.parent / path).resolve() - # 图片已上传到文档素材,但 API 插入块易失败,用占位符;用户可手动「插入→图片→文档素材」 - blocks.append(_text_block(f"📷 [图片: {alt or Path(path).name}](已上传至文档素材,可在飞书中插入)")) - i += 1 - continue - - # 引用块 > - if line.startswith("> "): - blocks.append(_text_block(line[2:].strip())) - i += 1 - continue - - # 分隔线 - if line.strip() in ("---", "***", "___"): - i += 1 - continue - - # 空行 - if not line.strip(): - i += 1 - continue - - # 普通段落 - blocks.append(_text_block(line.rstrip())) - i += 1 - - return blocks - - -def upload_md_to_feishu(md_path: Path, parent_token: str = PARENT_TOKEN) -> tuple[bool, str]: - """将 Markdown 上传到飞书 Wiki,有同名则更新""" - token = fwd.get_token(parent_token) - if not token: - return False, "Token 无效" - headers = {"Authorization": f"Bearer {token}", "Content-Type": "application/json"} - - title = md_path.stem - if not title: - title = md_path.name - - r = requests.get( - f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={parent_token}", - headers=headers, timeout=30) - if r.json().get("code") != 0: - return False, r.json().get("msg", "get_node 失败") - space_id = r.json()["data"]["node"].get("space_id") or \ - (r.json()["data"]["node"].get("space") or {}).get("space_id") or \ - r.json()["data"]["node"].get("origin_space_id") - if not space_id: - return False, "无法获取 space_id" - - doc_token = None - node_token = None - nodes = [] - page_token = None - while True: - params = {"parent_node_token": parent_token, "page_size": 50} - if page_token: - params["page_token"] = page_token - rr = requests.get( - f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{space_id}/nodes", - headers=headers, params=params, timeout=30) - if rr.json().get("code") != 0: - break - data = rr.json().get("data", {}) - nodes = data.get("nodes", []) or data.get("items", []) - for n in nodes: - t = n.get("title", "") or n.get("node", {}).get("title", "") - if title in t or "基因胶囊" in t: - doc_token = n.get("obj_token") or n.get("node_token") - node_token = n.get("node_token") - break - if doc_token: - break - page_token = data.get("page_token") - if not page_token: - break - - if not doc_token: - create_r = requests.post( - f"https://open.feishu.cn/open-apis/wiki/v2/spaces/{space_id}/nodes", - headers=headers, - json={ - "parent_node_token": parent_token, - "obj_type": "docx", - "node_type": "origin", - "title": title, - }, - timeout=30) - cd = create_r.json() - if cd.get("code") != 0: - return False, cd.get("msg", str(cd)) - doc_token = cd.get("data", {}).get("node", {}).get("obj_token") - node_token = cd.get("data", {}).get("node", {}).get("node_token") - if not doc_token: - doc_token = node_token - print("📄 创建新文档") - else: - print("📋 更新已有文档") - - file_tokens = {} - for m in re.finditer(r'!\[([^\]]*)\]\(([^)]+)\)', md_path.read_text(encoding="utf-8")): - path = m.group(2) - resolved = (md_path.parent / path).resolve() - if resolved.exists(): - ft = upload_image_to_doc(token, doc_token, resolved) - if ft: - file_tokens[str(resolved)] = ft - file_tokens[path] = ft - file_tokens[resolved.name] = ft - print(f"✅ 图片上传: {resolved.name}") - - if doc_token and doc_token != node_token: - child_ids = [] - pt = None - while True: - params = {"page_size": 100} - if pt: - params["page_token"] = pt - rb = requests.get( - f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks", - headers=headers, params=params, timeout=30) - if rb.json().get("code") != 0: - break - data = rb.json().get("data", {}) - items = data.get("items", []) - for b in items: - if b.get("parent_id") == doc_token: - child_ids.append(b["block_id"]) - pt = data.get("page_token") - if not pt: - break - if child_ids: - for j in range(0, len(child_ids), 50): - batch = child_ids[j : j + 50] - requests.delete( - f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children/batch_delete", - headers=headers, json={"block_id_list": batch}, timeout=30) - - blocks = md_to_blocks(md_path, file_tokens) - - for i in range(0, len(blocks), 50): - batch = blocks[i : i + 50] - wr = requests.post( - f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_token}/blocks/{doc_token}/children", - headers=headers, - json={"children": batch, "index": i}, - timeout=30) - if wr.json().get("code") != 0: - return False, wr.json().get("msg", "写入失败") - import time - time.sleep(0.3) - - url = f"https://cunkebao.feishu.cn/wiki/{node_token}" - return True, url - - -def main(): - ap = argparse.ArgumentParser(description="Markdown 直接上传到飞书 Wiki") - ap.add_argument("md", nargs="?", default="/Users/karuo/Documents/个人/2、我写的日记/火:开发分享/卡若:基因胶囊——AI技能可遗传化的实现与落地.md", help="Markdown 文件路径") - ap.add_argument("--parent", default=PARENT_TOKEN, help="父节点 token") - args = ap.parse_args() - - md_path = Path(args.md).expanduser().resolve() - if not md_path.exists(): - print(f"❌ 文件不存在: {md_path}") - sys.exit(1) - - print("=" * 50) - print(f"📤 Markdown 直接上传: {md_path.name}") - print("=" * 50) - ok, result = upload_md_to_feishu(md_path, args.parent) - if ok: - print(f"✅ 成功") - print(f"📎 {result}") - else: - print(f"❌ 失败: {result}") - sys.exit(1) - print("=" * 50) - - -if __name__ == "__main__": - main() diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py new file mode 100644 index 00000000..4e227237 --- /dev/null +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/md_to_feishu_json.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +将 Markdown 本地转换为飞书文档 JSON 格式。 +图片用占位符 __IMAGE:路径__ 标注,上传时替换为 file_token。 + +用法: + python3 md_to_feishu_json.py input.md output.json + python3 md_to_feishu_json.py input.md output.json --images img1.png,img2.png +""" +import re +import sys +import json +import argparse +from pathlib import Path + + +def _h1(t): + return {"block_type": 3, "heading1": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} + + +def _h2(t): + return {"block_type": 4, "heading2": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} + + +def _h3(t): + return {"block_type": 5, "heading3": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} + + +def _text(t): + return {"block_type": 2, "text": {"elements": [{"text_run": {"content": t, "text_element_style": {}}}], "style": {}}} + + +def _image_placeholder(idx: int, path: str) -> dict: + """图片占位符,上传时由脚本替换为 gallery block""" + return {"__image__": path, "__index__": idx} + + +def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list: + """将 Markdown 转为飞书 blocks""" + blocks = [] + image_paths = image_paths or [] + img_idx = 0 + + in_code = False + code_lines = [] + for line in md.split("\n"): + if line.strip().startswith("```"): + if in_code: + blocks.append(_text("```\n" + "\n".join(code_lines) + "\n```")) + code_lines = [] + in_code = not in_code + continue + if in_code: + code_lines.append(line) + continue + + # 图片语法 ![](path) + img_match = re.match(r"^!\[([^\]]*)\]\(([^)]+)\)\s*$", line.strip()) + if img_match: + path = img_match.group(2) + if img_idx < len(image_paths): + path = image_paths[img_idx] + blocks.append(_image_placeholder(img_idx, path)) + img_idx += 1 + continue + + # 标题 + if line.startswith("# "): + blocks.append(_h1(line[2:].strip())) + elif line.startswith("## "): + blocks.append(_h2(line[3:].strip())) + elif line.startswith("### "): + blocks.append(_h3(line[4:].strip())) + elif line.strip(): + blocks.append(_text(line)) + else: + blocks.append(_text("")) + + return blocks + + +def blocks_to_upload_format(blocks: list, base_dir: Path) -> tuple[list, list]: + """ + 将含 __image__ 占位符的 blocks 转为可上传格式。 + 返回 (文本 blocks 列表, 图片路径列表,按出现顺序)。 + image_paths 优先存相对路径(相对 base_dir),便于 JSON 移植。 + """ + out = [] + paths = [] + for b in blocks: + if isinstance(b, dict) and "__image__" in b: + path = b.get("__image__", "") + resolved = None + if path and (base_dir / path).exists(): + resolved = base_dir / path + elif path and Path(path).exists(): + resolved = Path(path).resolve() + if resolved: + try: + rel = str(resolved.relative_to(base_dir)) + except ValueError: + rel = str(resolved) + paths.append(rel) + else: + paths.append(path if path else "unknown") + out.append({"block_type": 2, "text": {"elements": [{"text_run": {"content": f"【配图 {len(paths)}:待上传】", "text_element_style": {}}}], "style": {}}}) + else: + out.append(b) + return out, paths + + +def main(): + ap = argparse.ArgumentParser() + ap.add_argument("input", help="Markdown 文件") + ap.add_argument("output", help="输出 JSON 文件") + ap.add_argument("--images", default="", help="图片路径,逗号分隔(按序对应 ![]())") + args = ap.parse_args() + + inp = Path(args.input) + if not inp.exists(): + print(f"❌ 文件不存在: {inp}") + sys.exit(1) + + md = inp.read_text(encoding="utf-8") + image_paths = [p.strip() for p in args.images.split(",") if p.strip()] + blocks = md_to_blocks(md, image_paths) + final, img_paths = blocks_to_upload_format(blocks, inp.parent) + + out = { + "description": f"由 {inp.name} 转换的飞书 docx blocks", + "source": str(inp), + "image_paths": img_paths, + "children": final, + } + Path(args.output).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"✅ 已写入 {args.output}") + if img_paths: + print(f" 图片占位: {len(img_paths)} 处 → 需在上传时替换为 file_token") + + +if __name__ == "__main__": + main() diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 4052c280..d48f5421 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -83,3 +83,4 @@ | 2026-02-22 11:07:02 | 🔄 卡若AI 同步 2026-02-22 11:07 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 8 个 | | 2026-02-22 11:32:57 | 🔄 卡若AI 同步 2026-02-22 11:32 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 | | 2026-02-22 11:40:59 | 🔄 卡若AI 同步 2026-02-22 11:40 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | +| 2026-02-22 11:44:40 | 🔄 卡若AI 同步 2026-02-22 11:44 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 8be811eb..94d1b198 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -86,3 +86,4 @@ | 2026-02-22 11:07:02 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 11:07 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-22 11:32:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 11:32 | 更新:金仓、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-02-22 11:40:59 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 11:40 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-02-22 11:44:40 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-22 11:44 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 8 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |