🔄 卡若AI 同步 2026-03-03 10:15 | 更新:水桥平台对接、卡木、火炬、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 14 个

This commit is contained in:
2026-03-03 10:15:45 +08:00
parent 64a81a4b3b
commit e48aa3afdd
19 changed files with 613 additions and 78 deletions

View File

@@ -18,6 +18,7 @@
- **脚本**:永平项目下 `scripts/send_chapter_poster_to_feishu.py`
- **依赖**`pip install requests Pillow`;飞书应用凭证写在 `scripts/.env.feishu`FEISHU_APP_ID、FEISHU_APP_SECRET
- **固定群 webhook**:脚本内置默认发到 **Soul 彩民团队** 飞书群webhook 为 `https://open.feishu.cn/open-apis/bot/v2/hook/14a7e0d3-864d-4709-ad40-0def6edba566`。无需复制链接,直接运行命令即可。
- **自定义 webhook--webhook**:用 `--webhook "https://..."` 可指定其他群。**推送前必须确认**目标群名称含有「瘦」或「IP」否则不要推送脚本无法从 webhook 获知群名,需人工在飞书里核对。
- **命令示例**(上传完成后执行):
```bash

View File

@@ -8,7 +8,9 @@
| 项目 | 规范 |
|:---|:---|
| **「我」** | **整篇文章最多出现三次**。多用「这边」「直接」「就」「场上」等替代;成稿后全文搜索「我」,超过 3 处必须改写。 |
| **「我」** | **整篇文章最多出现三次**(仅指叙述者视角;对话引用里的「我」不占名额)。成稿后全文搜索「我」,超过 3 处必须改写。 |
| **「这边」「那边」** | **不要用**。改用灵活表述:房主、场上、就、手头、这种模式、知识星球/小程序 等指代或省略主语。 |
| **「回答说」** | **不要用**。房主是强势角色,文章目的是让读者产生深度认同感;房主的话用**直接陈述**呈现,不写「回答说,……」,让语气与性格一致、话一出口就立住。 |
| **「卡若」** | 每篇最多提一次;不需要时可完全不出现。 |
---
@@ -47,7 +49,7 @@
- **推进方式**:时间线或事件线,逻辑清晰(如「有人问 → 回答」「3 号问 → …」)
- **分段**:每段一个主题,小主题隐于叙述中,不列段头小标题
- **穿插**:细节、对话、观点分析
- **多用对话**增强真实感「X 号问」「有人问」「直接回答」「这边说」等)
- **多用对话**增强真实感「X 号问」「有人问」等);房主的话**直接接在问句后**,不写「回答说」,语气强势、可认同。
- **分享句(两处,强制)**:约 20% 处一句、结尾一句,各不超过 50 字,围绕本节主题、紧扣内容,留余味或可执行。**不要出现「干货」二字**,不要用「干货:」或「**干货**:」等格式,直接写一句金句即可,可单独成段。
---

View File

@@ -86,7 +86,7 @@ OUT_DIR = Path("/Users/karuo/Documents/聊天记录/soul")
def run_playwright_page_export(from_num: int, to_num: int) -> int:
"""在 Playwright 页面内直接请求导出,绕过 Cookie,保证成功"""
"""在 Playwright 用系统默认浏览器打开,页面内直接请求导出,复用已有登录/Cookie。"""
import re
items = [(n, t, tok, d) for n, t, tok, d in SOUL_ITEMS if from_num <= n <= to_num]
if not items:
@@ -96,12 +96,29 @@ def run_playwright_page_export(from_num: int, to_num: int) -> int:
except ImportError:
print("❌ 需安装 playwright: pip install playwright && playwright install chromium", file=sys.stderr)
return 1
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_export_")
sys.path.insert(0, str(SCRIPT_DIR))
try:
from playwright_default_browser import launch_playwright_with_default_browser
except ImportError:
launch_playwright_with_default_browser = None
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
if launch_playwright_with_default_browser:
ctx, get_page, cleanup = launch_playwright_with_default_browser(p, headless=False, timeout=15000)
pg = get_page()
else:
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_export_")
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(ud, ignore_errors=True)
pg.goto(MINUTES_URL, wait_until="domcontentloaded", timeout=25000)
print(" ⚠️ 请在此窗口登录飞书妙记(看到列表即可),等待 90 秒…")
time.sleep(90)
@@ -128,47 +145,66 @@ def run_playwright_page_export(from_num: int, to_num: int) -> int:
except Exception as e:
print(f"{topic}: {e}")
time.sleep(1)
ctx.close()
try:
cleanup()
except Exception:
pass
print(f"✅ 页面内导出完成 {saved}/{len(items)} 场,目录: {OUT_DIR}")
return 0
finally:
import shutil
shutil.rmtree(ud, ignore_errors=True)
except Exception as e:
print(f"❌ Playwright 导出异常: {e}", file=sys.stderr)
return 1
def collect_cookie_via_playwright_standalone() -> str:
"""Playwright 启动独立 Chromium,用户登录后抓 Cookie。"""
"""Playwright 用系统默认浏览器打开,用户登录后抓 Cookie,复用已有登录态"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
return ""
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_cookie_")
sys.path.insert(0, str(SCRIPT_DIR))
try:
from playwright_default_browser import launch_playwright_with_default_browser
except ImportError:
launch_playwright_with_default_browser = None
cookie_str = ""
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(
user_data_dir=ud,
headless=False,
args=["--start-maximized"],
viewport={"width": 1280, "height": 900},
timeout=15000,
)
try:
if launch_playwright_with_default_browser:
ctx, get_page, cleanup = launch_playwright_with_default_browser(p, headless=False, timeout=15000)
pg = get_page()
else:
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_cookie_")
ctx = p.chromium.launch_persistent_context(
user_data_dir=ud,
headless=False,
args=["--start-maximized"],
viewport={"width": 1280, "height": 900},
timeout=15000,
)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(ud, ignore_errors=True)
try:
pg.goto(MINUTES_URL, wait_until="domcontentloaded", timeout=25000)
except Exception:
pg = ctx.new_page() if not ctx.pages else ctx.pages[0]
try:
pg.goto(MINUTES_URL, wait_until="domcontentloaded", timeout=25000)
except Exception:
pass
pass
print(" ⚠️ 请在此窗口完成飞书妙记登录(输入账号密码直到看到列表),等待 120 秒…")
time.sleep(120)
cookies = ctx.cookies("https://cunkebao.feishu.cn")
if not cookies:
cookies = ctx.cookies()
ctx.close()
try:
cleanup()
except Exception:
pass
seen = set()
parts = []
for c in cookies:
@@ -176,12 +212,8 @@ def collect_cookie_via_playwright_standalone() -> str:
seen.add(c["name"])
parts.append(f"{c['name']}={c.get('value','')}")
cookie_str = "; ".join(parts)
finally:
import shutil
try:
shutil.rmtree(ud, ignore_errors=True)
except Exception:
pass
except Exception as e:
print(f" Playwright 取 Cookie 失败: {e}", file=sys.stderr)
return cookie_str if len(cookie_str) > 200 else ""

View File

@@ -71,18 +71,37 @@ def export_via_cookie(object_token: str) -> str | None:
def export_via_playwright_page(object_token: str, title: str = "", wait_sec: int = 30) -> str | None:
"""Playwright 打开妙记页,在页面内 fetch 导出接口(带 credentials无感拿正文"""
"""Playwright 用系统默认浏览器打开妙记页,在页面内 fetch 导出接口(带 credentials复用已有登录/Cookie"""
try:
from playwright.sync_api import sync_playwright
except ImportError:
return None
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_one_")
sys.path.insert(0, str(SCRIPT_DIR))
try:
from playwright_default_browser import launch_playwright_with_default_browser
except ImportError:
launch_playwright_with_default_browser = None
result = [None]
cleanup = lambda: None
try:
with sync_playwright() as p:
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
if launch_playwright_with_default_browser:
ctx, get_page, cleanup = launch_playwright_with_default_browser(p, headless=False, timeout=15000)
pg = get_page()
else:
import tempfile
ud = tempfile.mkdtemp(prefix="feishu_one_")
ctx = p.chromium.launch_persistent_context(ud, headless=False, timeout=15000)
pg = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(ud, ignore_errors=True)
pg.goto(f"https://cunkebao.feishu.cn/minutes/{object_token}", wait_until="domcontentloaded", timeout=25000)
print(f" 页面已打开,等待 {wait_sec} 秒(若未登录请先登录)…")
time.sleep(wait_sec)
@@ -115,11 +134,38 @@ def export_via_playwright_page(object_token: str, title: str = "", wait_sec: int
result[0] = text
elif len(text) > 400:
result[0] = text
# 兜底:从页面 DOM 抓取正文(先点「文字记录」再取整页或大块文本)
if not result[0]:
try:
# 尝试点击「文字记录」tab 再取内容
for label in ["文字记录", "文字"]:
try:
pg.get_by_text(label, exact=False).first.click(timeout=3000)
time.sleep(1.5)
break
except Exception:
pass
dom_text = pg.evaluate("""() => {
let out = '';
const candidates = document.querySelectorAll('[class*="content"], [class*="paragraph"], [class*="segment"], [class*="transcript"], [class*="record"], .ne-doc-body, [role="main"]');
for (const el of candidates) {
const t = (el.innerText || el.textContent || '').trim();
if (t.length > 800 && (t.includes('') || t.includes(':') || /\\d{1,2}:\\d{2}:\\d{2}/.test(t))) { out = t; break; }
}
if (!out) out = document.body.innerText || document.body.textContent || '';
return out;
}""")
if dom_text and len(dom_text.strip()) > 300:
result[0] = dom_text.strip()
except Exception:
pass
except Exception as e:
print(f" Playwright 失败: {e}", file=sys.stderr)
finally:
import shutil
shutil.rmtree(ud, ignore_errors=True)
try:
cleanup()
except Exception:
pass
return result[0]

View File

@@ -0,0 +1,192 @@
#!/usr/bin/env python3
"""
检测系统默认浏览器,并用该浏览器启动 Playwright优先使用其用户数据目录以保留登录/Cookie。
供 feishu_minutes_one_url、auto_cookie_and_export、逆向获取Cookie并下载单条 等脚本使用。
"""
from __future__ import annotations
import platform
import subprocess
import sys
from pathlib import Path
def get_system_default_browser():
"""
检测系统当前使用的默认浏览器。
返回 (engine, channel, profile_path):
- engine: "chromium" | "webkit" | "firefox"
- channel: 仅 chromium 时有效,"chrome" | "msedge" | "chromium" | None
- profile_path: 用户数据目录用于复用登录态None 表示不使用系统 profile
"""
system = platform.system()
home = Path.home()
if system == "Darwin": # macOS
# 优先读取系统默认 HTTP/HTTPS 处理程序
try:
out = subprocess.run(
["defaults", "read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"],
capture_output=True,
text=True,
timeout=5,
)
if out.returncode == 0 and out.stdout:
for line in out.stdout.splitlines():
if "https" in line.lower() or "http" in line.lower():
continue
# 解析 LSHandlers 较复杂,改为按应用存在性检测
except Exception:
pass
# 按优先级检测已安装的浏览器与多数用户习惯一致Chrome/Edge 常用)
apps = [
("Google Chrome", "chromium", "chrome", home / "Library/Application Support/Google/Chrome"),
("Microsoft Edge", "chromium", "msedge", home / "Library/Application Support/Microsoft Edge"),
("Chromium", "chromium", "chromium", home / "Library/Application Support/Chromium"),
("Safari", "webkit", None, None),
("Firefox", "firefox", None, home / "Library/Application Support/Firefox"),
]
for app_name, engine, channel, profile in apps:
app_path = Path(f"/Applications/{app_name}.app")
if app_path.exists():
if engine == "firefox" and profile:
# Firefox 使用 Profiles/xxx.default 子目录
profiles = profile / "Profiles"
if profiles.exists():
for d in profiles.iterdir():
if d.is_dir() and (d / "prefs.js").exists():
profile = d
break
else:
profile = None
out_profile = str(profile) if profile and profile.exists() else None
return (engine, channel, out_profile)
# 若都未找到,退回 ChromiumPlaywright 自带)
return ("chromium", None, None)
if system == "Windows":
try:
out = subprocess.run(
["reg", "query", "HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\https\\UserChoice", "/v", "ProgId"],
capture_output=True,
text=True,
timeout=5,
)
if out.returncode == 0 and "chrome" in out.stdout.lower():
return ("chromium", "chrome", None)
if out.returncode == 0 and "edge" in out.stdout.lower():
return ("chromium", "msedge", None)
except Exception:
pass
return ("chromium", None, None)
return ("chromium", None, None)
def launch_playwright_with_default_browser(sync_playwright, headless: bool = False, timeout: int = 15000):
"""
使用系统默认浏览器启动 Playwright返回 (context_or_browser, page_getter, cleanup)。
page_getter 用于获取当前 page可能是 context.pages[0] 或 new_page
cleanup 用于关闭 context/browser。
若使用持久 context则尽量复用系统 profile避免 Cookie 无法登录。
"""
import tempfile
p = sync_playwright
engine, channel, profile_path = get_system_default_browser()
if engine == "chromium":
channel_info = f" (channel={channel})" if channel else ""
print(f" 使用系统浏览器: Chromium 系{channel_info}profile={profile_path or '临时目录'}")
# 优先尝试用系统 profile 启动(已登录则直接用)
if profile_path and channel:
try:
ctx = p.chromium.launch_persistent_context(
profile_path,
channel=channel,
headless=headless,
timeout=timeout,
args=["--no-first-run"],
)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
return ctx, lambda: page, cleanup
except Exception as e:
if "already in use" in str(e).lower() or "User data directory" in str(e):
print(f" 系统浏览器正在使用中,改用临时目录(请在新窗口内登录一次): {e}")
else:
print(f" 使用系统 profile 失败,改用临时目录: {e}")
# 使用临时目录 + 指定 channel仍为系统安装的 Chrome/Edge
user_data = tempfile.mkdtemp(prefix="feishu_playwright_")
try:
kwargs = {"headless": headless, "timeout": timeout}
if channel:
kwargs["channel"] = channel
ctx = p.chromium.launch_persistent_context(user_data, **kwargs)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(user_data, ignore_errors=True)
return ctx, lambda: page, cleanup
except Exception:
import shutil
shutil.rmtree(user_data, ignore_errors=True)
raise
if engine == "webkit":
print(" 使用系统浏览器: Safari (WebKit)")
browser = p.webkit.launch(headless=headless)
ctx = browser
page = browser.new_page()
def cleanup():
try:
browser.close()
except Exception:
pass
return ctx, lambda: page, cleanup
if engine == "firefox":
print(" 使用系统浏览器: Firefox")
browser = p.firefox.launch(headless=headless)
ctx = browser
page = browser.new_page()
def cleanup():
try:
browser.close()
except Exception:
pass
return ctx, lambda: page, cleanup
# 默认
print(" 使用 Playwright 自带 Chromium")
user_data = tempfile.mkdtemp(prefix="feishu_playwright_")
ctx = p.chromium.launch_persistent_context(user_data, headless=headless, timeout=timeout)
page = ctx.pages[0] if ctx.pages else ctx.new_page()
def cleanup():
try:
ctx.close()
except Exception:
pass
import shutil
shutil.rmtree(user_data, ignore_errors=True)
return ctx, lambda: page, cleanup

View File

@@ -0,0 +1,35 @@
/**
* 在已打开的飞书妙记页面cunkebao.feishu.cn/minutes/xxx按 F12 → Console 粘贴整段运行,
* 会提取当前页「文字记录」区域文字并复制到剪贴板,同时打印到控制台。
*/
(function () {
function getText() {
const candidates = document.querySelectorAll(
'[class*="content"], [class*="paragraph"], [class*="segment"], [class*="transcript"], [class*="record"], .ne-doc-body, [role="main"]'
);
for (const el of candidates) {
const t = (el.innerText || el.textContent || "").trim();
if (t.length > 800 && (t.includes("") || t.includes(":") || /\d{1,2}:\d{2}:\d{2}/.test(t))) {
return t;
}
}
return (document.body && (document.body.innerText || document.body.textContent)) || "";
}
const text = getText().trim();
if (!text) {
console.warn("未找到长文本,请先点击「文字记录」选项卡再运行本脚本。");
return;
}
const title = (document.title || "妙记").replace(/\s*\|\s*.*$/, "");
const out = "标题: " + title + "\n\n文字记录:\n\n" + text;
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText(out).then(function () {
console.log("已复制到剪贴板,共 " + out.length + " 字。可粘贴到记事本保存。");
}).catch(function () {
console.log(out.slice(0, 500) + "\n...(共 " + out.length + " 字,请手动选择上方输出复制)");
});
} else {
console.log(out.slice(0, 2000) + "\n...(共 " + out.length + " 字)");
}
console.log("前 500 字预览:", out.slice(0, 500));
})();

View File

@@ -122,7 +122,7 @@ def main() -> int:
try:
from feishu_minutes_one_url import export_via_playwright_page
print(" Cookie 导出 401改用 Playwright 页面内获取…")
text = export_via_playwright_page(token, title=title, wait_sec=45)
text = export_via_playwright_page(token, title=title, wait_sec=60)
except Exception as e:
print(" Playwright 兜底失败:", e, file=sys.stderr)
if text:

View File

@@ -347,6 +347,14 @@ python3 scripts/feishu_wiki_create_doc.py --parent KNf7wA8Rki1NSdkkSIqcdFtTnWb -
JSON 格式:与 `团队入职流程与新人登记表_feishu_blocks.json` 相同,含 `children` 数组(飞书 docx blocks
### 读书笔记发飞书(链接与子目录)
- **原则**:读书笔记写完后同步发到飞书知识库,发到**指定链接(父节点)下的对应子目录**。
- **链接分析**:飞书知识库链接格式为 `https://cunkebao.feishu.cn/wiki/<node_token>`,其中 `wiki/` 后的 `node_token` 即为父节点;若读书笔记放在「读书笔记」节点下,则用该节点 token 为 parent若其下还有分类子目录个人提升/创业/商业思维/投资/人际关系),则先在对应子节点下再建文档。
- **目录结构建议**:父节点 = 读书笔记根如「2、我写的日记」或「读书笔记」→ 其下可建子目录「读书笔记」或按分类建「个人提升」「创业」等 → 单篇笔记为该目录下的一篇文档。
- **执行**:用统一文章上传脚本,`--parent` 取目标父节点 token即链接中的 node_token`--title` 与本地笔记标题一致,`--md` 指向 `个人/2、我写的日记/读书笔记/xxx.md`。若需按分类落子目录,需先有该分类子节点 token再以该 token 为 parent 创建文档。
- **配置**:读书笔记默认本地路径为 `个人/2、我写的日记/读书笔记/`;飞书父节点 token 可配置为环境变量 `FEISHU_READING_WIKI_PARENT` 或写在读书笔记 Skill 的「飞书读书笔记配置」中;用户提供飞书「读书笔记」节点链接后,从链接提取 token 填入即可。
---
## 飞书导出 JSON 按原格式上传

View File

@@ -0,0 +1,77 @@
#!/usr/bin/env python3
"""
写入 3月3日 飞书日志到 3 月文档。昨日目标与今年总目标一致,百分比按总目标;今日 20 条视频 + 1 朋友圈,视频 Skill 四屏切片完成 20 个视频。
"""
import os
import sys
from pathlib import Path
SCRIPT_DIR = Path(__file__).resolve().parent
sys.path.insert(0, str(SCRIPT_DIR))
from auto_log import get_token_silent, write_log, open_result, CONFIG
def _get_march_wiki_token():
raw = (CONFIG.get("MONTH_WIKI_TOKENS") or {}).get(3) or os.environ.get("FEISHU_MARCH_WIKI_TOKEN") or ""
return (raw or "").strip() or None
def build_tasks_0303():
"""3月3日昨日目标一致、总目标一致、百分比按此今日 20 条视频 + 1 朋友圈;视频 Skill 四屏切片 20 个;百分比。"""
return [
{
"person": "卡若",
"events": ["今日复盘", "本月与最终目标", "今日核心", "视频Skill四屏切片"],
"quadrant": "重要紧急",
"t_targets": [
"昨日目标与今年总目标一致,百分比按总目标执行",
"本月目标约 12%,距最终目标差 88%",
"今日核心:每天 20 条 Soul 视频 + 20:00 发 1 条朋友圈",
"视频 Skill 四屏切片:完成 20 个视频(当日完成度见反馈)",
],
"n_process": [
"【复盘】昨日目标一致、今年总目标一致,百分比按 2026年整体目标 对齐",
"【2月突破执行】延续 3 月,本月/最终目标百分比已按进度写入",
"【今日】20 条视频(四屏切片)+ 1 条朋友圈;视频切片 Skill 操作执行",
],
"t_thoughts": [
"今日一条核心20 条 Soul 视频 + 8 点 1 条朋友圈,持续拉齐与最终目标",
"四屏切片完成 20 个视频,按当日完成数看百分比",
],
"w_work": [
"20 条 Soul 视频(四屏切片)",
"20:00 发 1 条朋友圈",
"视频 Skill 操作",
"飞书日志",
],
"f_feedback": [
"本月/最终目标 12% / 100%,差 88%",
"今日核心→20 条 Soul + 8 点朋友圈 🔄",
"四屏切片 20 条→当日完成度待填 % 🔄",
],
}
]
def main():
token = get_token_silent()
if not token:
print("❌ 无法获取飞书 Token")
sys.exit(1)
march_token = _get_march_wiki_token()
if not march_token:
print("❌ 未配置 3 月文档,请设置 FEISHU_MARCH_WIKI_TOKEN")
sys.exit(1)
tasks = build_tasks_0303()
ok = write_log(token, "3月3日", tasks, march_token, overwrite=False)
if ok:
open_result(march_token)
print("✅ 3月3日 飞书日志已写入")
else:
print("❌ 写入失败")
sys.exit(1)
if __name__ == "__main__":
main()