🔄 卡若AI 同步 2026-02-25 10:22 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个
This commit is contained in:
@@ -14,12 +14,18 @@ import subprocess
|
|||||||
import requests
|
import requests
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
import time
|
import time
|
||||||
|
import re
|
||||||
|
|
||||||
# ============ 配置 ============
|
# ============ 配置 ============
|
||||||
CONFIG = {
|
CONFIG = {
|
||||||
'APP_ID': 'cli_a48818290ef8100d',
|
'APP_ID': 'cli_a48818290ef8100d',
|
||||||
'APP_SECRET': 'dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4',
|
'APP_SECRET': 'dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4',
|
||||||
'WIKI_TOKEN': 'JZiiwxEjHiRxouk8hSPcqBn6nrd',
|
'WIKI_TOKEN': 'JZiiwxEjHiRxouk8hSPcqBn6nrd',
|
||||||
|
# 按月份路由到对应日志文档,避免跨月误写
|
||||||
|
'MONTH_WIKI_TOKENS': {
|
||||||
|
1: 'JZiiwxEjHiRxouk8hSPcqBn6nrd', # 2026年1月 运营团队启动
|
||||||
|
2: 'Jn2EwXP2OiTujNkAbNCcDcM7nRA', # 2026年2月 (突破执行)
|
||||||
|
},
|
||||||
'SERVICE_PORT': 5050,
|
'SERVICE_PORT': 5050,
|
||||||
'TOKEN_FILE': os.path.join(os.path.dirname(__file__), '.feishu_tokens.json')
|
'TOKEN_FILE': os.path.join(os.path.dirname(__file__), '.feishu_tokens.json')
|
||||||
}
|
}
|
||||||
@@ -217,20 +223,50 @@ def build_blocks(date_str, tasks):
|
|||||||
|
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
def write_log(token, date_str=None, tasks=None):
|
|
||||||
|
def parse_month_from_date_str(date_str):
|
||||||
|
"""从如 '2月25日' 提取月份整数"""
|
||||||
|
m = re.search(r'(\d+)\s*月', date_str or '')
|
||||||
|
if not m:
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return int(m.group(1))
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def resolve_wiki_token_for_date(date_str, explicit_wiki_token=None):
|
||||||
|
"""根据日期路由文档token;允许显式覆盖"""
|
||||||
|
if explicit_wiki_token:
|
||||||
|
return explicit_wiki_token
|
||||||
|
month = parse_month_from_date_str(date_str)
|
||||||
|
if month and month in CONFIG.get('MONTH_WIKI_TOKENS', {}):
|
||||||
|
return CONFIG['MONTH_WIKI_TOKENS'][month]
|
||||||
|
return CONFIG['WIKI_TOKEN']
|
||||||
|
|
||||||
|
def write_log(token, date_str=None, tasks=None, wiki_token=None):
|
||||||
"""写入日志(倒序插入:新日期在最上面)"""
|
"""写入日志(倒序插入:新日期在最上面)"""
|
||||||
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
|
headers = {'Authorization': f'Bearer {token}', 'Content-Type': 'application/json'}
|
||||||
|
|
||||||
if not date_str or not tasks:
|
if not date_str or not tasks:
|
||||||
date_str, tasks = get_today_tasks()
|
date_str, tasks = get_today_tasks()
|
||||||
|
target_wiki_token = resolve_wiki_token_for_date(date_str, wiki_token)
|
||||||
|
|
||||||
# 获取文档ID
|
# 获取文档ID
|
||||||
r = requests.get(f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={CONFIG['WIKI_TOKEN']}",
|
r = requests.get(f"https://open.feishu.cn/open-apis/wiki/v2/spaces/get_node?token={target_wiki_token}",
|
||||||
headers=headers, timeout=30)
|
headers=headers, timeout=30)
|
||||||
if r.json().get('code') != 0:
|
if r.json().get('code') != 0:
|
||||||
print(f"❌ 获取文档失败")
|
print(f"❌ 获取文档失败")
|
||||||
return False
|
return False
|
||||||
doc_id = r.json()['data']['node']['obj_token']
|
node = r.json()['data']['node']
|
||||||
|
doc_id = node['obj_token']
|
||||||
|
doc_title = node.get('title', '')
|
||||||
|
|
||||||
|
# 防串月:日期月份与文档标题不一致时拒绝写入
|
||||||
|
month = parse_month_from_date_str(date_str)
|
||||||
|
if month and f"{month}月" not in doc_title:
|
||||||
|
print(f"❌ 月份校验失败:{date_str} 不应写入《{doc_title}》")
|
||||||
|
return False
|
||||||
|
|
||||||
# 获取blocks检查日期是否存在
|
# 获取blocks检查日期是否存在
|
||||||
r = requests.get(f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks",
|
r = requests.get(f"https://open.feishu.cn/open-apis/docx/v1/documents/{doc_id}/blocks",
|
||||||
@@ -261,16 +297,18 @@ def write_log(token, date_str=None, tasks=None):
|
|||||||
headers=headers, json={'children': content_blocks, 'index': insert_index}, timeout=30)
|
headers=headers, json={'children': content_blocks, 'index': insert_index}, timeout=30)
|
||||||
|
|
||||||
if r.json().get('code') == 0:
|
if r.json().get('code') == 0:
|
||||||
print(f"✅ {date_str} 日志写入成功")
|
print(f"✅ {date_str} 日志写入成功 -> {doc_title}")
|
||||||
return True
|
return True
|
||||||
else:
|
else:
|
||||||
print(f"❌ 写入失败: {r.json().get('msg')}")
|
print(f"❌ 写入失败: {r.json().get('msg')}")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def open_result():
|
def open_result(wiki_token=None):
|
||||||
"""打开飞书查看结果"""
|
"""打开飞书查看结果"""
|
||||||
subprocess.run(['open', WIKI_URL], capture_output=True)
|
token = wiki_token or CONFIG['WIKI_TOKEN']
|
||||||
print(f"📎 已打开飞书: {WIKI_URL}")
|
url = f"https://cunkebao.feishu.cn/wiki/{token}"
|
||||||
|
subprocess.run(['open', url], capture_output=True)
|
||||||
|
print(f"📎 已打开飞书: {url}")
|
||||||
|
|
||||||
# ============ 主流程 ============
|
# ============ 主流程 ============
|
||||||
def main():
|
def main():
|
||||||
@@ -289,14 +327,16 @@ def main():
|
|||||||
print("❌ 无法获取Token")
|
print("❌ 无法获取Token")
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 3. 写入日志
|
# 3. 写入日志(按月份自动路由)
|
||||||
print("\n📝 Step 3: 写入日志...")
|
print("\n📝 Step 3: 写入日志...")
|
||||||
if not write_log(token):
|
date_str, tasks = get_today_tasks()
|
||||||
|
target_wiki_token = resolve_wiki_token_for_date(date_str)
|
||||||
|
if not write_log(token, date_str, tasks, target_wiki_token):
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
|
||||||
# 4. 打开结果
|
# 4. 打开结果
|
||||||
print("\n🎉 Step 4: 完成!")
|
print("\n🎉 Step 4: 完成!")
|
||||||
open_result()
|
open_result(target_wiki_token)
|
||||||
|
|
||||||
print("\n" + "=" * 50)
|
print("\n" + "=" * 50)
|
||||||
print("✅ 全部完成!")
|
print("✅ 全部完成!")
|
||||||
|
|||||||
@@ -35,11 +35,25 @@ def _image_placeholder(idx: int, path: str) -> dict:
|
|||||||
return {"__image__": path, "__index__": idx}
|
return {"__image__": path, "__index__": idx}
|
||||||
|
|
||||||
|
|
||||||
|
def _clean_inline_markdown(text: str) -> str:
|
||||||
|
"""清理常见行内 markdown 标记,输出更适合飞书阅读的纯文本。"""
|
||||||
|
t = text
|
||||||
|
# 粗体/斜体标记
|
||||||
|
t = re.sub(r"\*\*(.*?)\*\*", r"\1", t)
|
||||||
|
t = re.sub(r"__(.*?)__", r"\1", t)
|
||||||
|
t = re.sub(r"\*(.*?)\*", r"\1", t)
|
||||||
|
t = re.sub(r"_(.*?)_", r"\1", t)
|
||||||
|
# 行内代码保留内容,去掉反引号
|
||||||
|
t = re.sub(r"`([^`]+)`", r"\1", t)
|
||||||
|
return t.strip()
|
||||||
|
|
||||||
|
|
||||||
def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list:
|
def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list:
|
||||||
"""将 Markdown 转为飞书 blocks"""
|
"""将 Markdown 转为飞书 blocks"""
|
||||||
blocks = []
|
blocks = []
|
||||||
image_paths = image_paths or []
|
image_paths = image_paths or []
|
||||||
img_idx = 0
|
img_idx = 0
|
||||||
|
first_h1_consumed = False
|
||||||
|
|
||||||
in_code = False
|
in_code = False
|
||||||
code_lines = []
|
code_lines = []
|
||||||
@@ -73,16 +87,33 @@ def md_to_blocks(md: str, image_paths: list[str] | None = None) -> list:
|
|||||||
|
|
||||||
# 标题
|
# 标题
|
||||||
if line.startswith("# "):
|
if line.startswith("# "):
|
||||||
blocks.append(_h1(line[2:].strip()))
|
# 避免正文和文档标题重复:默认跳过第一行 H1
|
||||||
|
if first_h1_consumed:
|
||||||
|
blocks.append(_h1(_clean_inline_markdown(line[2:].strip())))
|
||||||
|
else:
|
||||||
|
first_h1_consumed = True
|
||||||
elif line.startswith("## "):
|
elif line.startswith("## "):
|
||||||
blocks.append(_h2(line[3:].strip()))
|
blocks.append(_h2(_clean_inline_markdown(line[3:].strip())))
|
||||||
elif line.startswith("### "):
|
elif line.startswith("### "):
|
||||||
blocks.append(_h3(line[4:].strip()))
|
blocks.append(_h3(_clean_inline_markdown(line[4:].strip())))
|
||||||
elif line.lstrip().startswith(">"):
|
elif line.lstrip().startswith(">"):
|
||||||
# 引用块转普通说明行,降低写入失败概率
|
# 引用块转普通说明行,降低写入失败概率
|
||||||
blocks.append(_text(line.lstrip()[1:].strip()))
|
quote = line.lstrip()
|
||||||
|
while quote.startswith(">"):
|
||||||
|
quote = quote[1:].lstrip()
|
||||||
|
quote = _clean_inline_markdown(quote)
|
||||||
|
if quote:
|
||||||
|
blocks.append(_text(quote))
|
||||||
elif line.strip():
|
elif line.strip():
|
||||||
blocks.append(_text(line.strip()))
|
raw = line.strip()
|
||||||
|
# 无序列表统一成 •,减少 markdown 观感噪音
|
||||||
|
if re.match(r"^[-*]\s+", raw):
|
||||||
|
raw = "• " + re.sub(r"^[-*]\s+", "", raw)
|
||||||
|
# 有序列表统一成 1)2)样式
|
||||||
|
raw = re.sub(r"^(\d+)\.\s+", r"\1)", raw)
|
||||||
|
cleaned = _clean_inline_markdown(raw)
|
||||||
|
if cleaned:
|
||||||
|
blocks.append(_text(cleaned))
|
||||||
|
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
|
|||||||
if SCRIPT_DIR not in sys.path:
|
if SCRIPT_DIR not in sys.path:
|
||||||
sys.path.insert(0, SCRIPT_DIR)
|
sys.path.insert(0, SCRIPT_DIR)
|
||||||
|
|
||||||
from auto_log import get_token_silent, write_log, open_result
|
from auto_log import get_token_silent, write_log, open_result, resolve_wiki_token_for_date
|
||||||
|
|
||||||
DATE_STR = "2月25日"
|
DATE_STR = "2月25日"
|
||||||
TASKS = [
|
TASKS = [
|
||||||
@@ -46,5 +46,5 @@ if __name__ == "__main__":
|
|||||||
print("📝 写入飞书日志(2月25日)...")
|
print("📝 写入飞书日志(2月25日)...")
|
||||||
ok = write_log(token, DATE_STR, TASKS)
|
ok = write_log(token, DATE_STR, TASKS)
|
||||||
if ok:
|
if ok:
|
||||||
open_result()
|
open_result(resolve_wiki_token_for_date(DATE_STR))
|
||||||
sys.exit(0 if ok else 1)
|
sys.exit(0 if ok else 1)
|
||||||
|
|||||||
@@ -135,3 +135,4 @@
|
|||||||
| 2026-02-25 09:24:48 | 🔄 卡若AI 同步 2026-02-25 09:24 | 更新:水桥平台对接、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 |
|
| 2026-02-25 09:24:48 | 🔄 卡若AI 同步 2026-02-25 09:24 | 更新:水桥平台对接、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 |
|
||||||
| 2026-02-25 09:28:50 | 🔄 卡若AI 同步 2026-02-25 09:28 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 |
|
| 2026-02-25 09:28:50 | 🔄 卡若AI 同步 2026-02-25 09:28 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 |
|
||||||
| 2026-02-25 09:31:15 | 🔄 卡若AI 同步 2026-02-25 09:31 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 13 个 |
|
| 2026-02-25 09:31:15 | 🔄 卡若AI 同步 2026-02-25 09:31 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 13 个 |
|
||||||
|
| 2026-02-25 10:15:21 | 🔄 卡若AI 同步 2026-02-25 10:15 | 更新:水桥平台对接、运营中枢、运营中枢工作台 | 排除 >20MB: 13 个 |
|
||||||
|
|||||||
@@ -138,3 +138,4 @@
|
|||||||
| 2026-02-25 09:24:48 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 09:24 | 更新:水桥平台对接、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
| 2026-02-25 09:24:48 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 09:24 | 更新:水桥平台对接、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||||
| 2026-02-25 09:28:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 09:28 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
| 2026-02-25 09:28:50 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 09:28 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||||
| 2026-02-25 09:31:15 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 09:31 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
| 2026-02-25 09:31:15 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 09:31 | 更新:运营中枢、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||||
|
| 2026-02-25 10:15:21 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-25 10:15 | 更新:水桥平台对接、运营中枢、运营中枢工作台 | 排除 >20MB: 13 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
|
||||||
|
|||||||
Reference in New Issue
Block a user