🔄 卡若AI 同步 2026-02-19 11:54 | 更新:总索引与入口、金仓、水桥平台对接、运营中枢工作台 | 排除 >20MB: 5 个

This commit is contained in:
2026-02-19 11:54:37 +08:00
parent cecc39e0d3
commit 24c9f47a6e
25 changed files with 1928 additions and 54 deletions

3
.gitignore vendored
View File

@@ -18,6 +18,9 @@ __pycache__/
.env.*
*.log
sync_tokens.env
# 飞书妙记(用户 token / Cookie勿提交
**/智能纪要/脚本/feishu_user_token.txt
**/智能纪要/脚本/cookie_minutes.txt
# Node / 前端
node_modules/

View File

@@ -0,0 +1,143 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""
查询腾讯云指定日期范围的消费情况。
依赖pip install tencentcloud-sdk-python-common tencentcloud-sdk-python-billing
凭证:环境变量 TENCENTCLOUD_SECRET_ID、TENCENTCLOUD_SECRET_KEY或从 00_账号与API索引.md 读取。
用法:
python tencent_cloud_bill_recent_days.py [days] # 最近 N 天,默认 2
python tencent_cloud_bill_recent_days.py 2026-02-15 2026-02-19 # 指定起止日期
"""
import os
import re
import sys
from datetime import datetime, timedelta
def _find_karuo_ai_root():
d = os.path.dirname(os.path.abspath(__file__))
for _ in range(6):
if os.path.basename(d) == "卡若AI" or (os.path.isdir(os.path.join(d, "运营中枢")) and os.path.isdir(os.path.join(d, "01_卡资"))):
return d
d = os.path.dirname(d)
return None
def _read_creds_from_index():
root = _find_karuo_ai_root()
if not root:
return None, None
path = os.path.join(root, "运营中枢", "工作台", "00_账号与API索引.md")
if not os.path.isfile(path):
return None, None
with open(path, "r", encoding="utf-8") as f:
text = f.read()
secret_id = secret_key = None
in_tencent = False
for line in text.splitlines():
if "### 腾讯云" in line:
in_tencent = True
continue
if in_tencent and line.strip().startswith("###"):
break
if not in_tencent:
continue
m = re.search(r"\|\s*(?:密钥|SecretId|Secret\s*Id)\s*\|\s*`([^`]+)`", line, re.I)
if m:
val = m.group(1).strip()
if val.startswith("AKID"):
secret_id = val
else:
secret_key = val
m = re.search(r"\|\s*SecretKey\s*\|\s*`([^`]+)`", line, re.I)
if m:
secret_key = m.group(1).strip()
return secret_id or None, secret_key or None
def main(days=2, start_date=None, end_date=None):
secret_id = os.environ.get("TENCENTCLOUD_SECRET_ID")
secret_key = os.environ.get("TENCENTCLOUD_SECRET_KEY")
if not secret_id or not secret_key:
sid, skey = _read_creds_from_index()
if sid:
secret_id = secret_id or sid
if skey:
secret_key = secret_key or skey
if not secret_id or not secret_key:
print("未配置 TENCENTCLOUD_SECRET_ID 或 TENCENTCLOUD_SECRET_KEY")
print("请在本机设置环境变量,或在 运营中枢/工作台/00_账号与API索引.md 的腾讯云段落添加 SecretKey密钥为 SecretId")
print("\n直接查看消费:登录 https://console.cloud.tencent.com/expense/overview")
return 1
try:
from tencentcloud.common import credential
from tencentcloud.common.profile.client_profile import ClientProfile
from tencentcloud.common.profile.http_profile import HttpProfile
from tencentcloud.billing.v20180709 import billing_client, models
except ImportError:
print("请先安装: pip install tencentcloud-sdk-python-common tencentcloud-sdk-python-billing")
return 1
if start_date is None or end_date is None:
end_date = datetime.now().date()
start_date = end_date - timedelta(days=days - 1)
month = start_date.strftime("%Y-%m")
begin_time = f"{start_date} 00:00:00"
end_time = f"{end_date} 23:59:59"
cred = credential.Credential(secret_id, secret_key)
hp = HttpProfile(endpoint="billing.tencentcloudapi.com")
cp = ClientProfile(httpProfile=hp)
client = billing_client.BillingClient(cred, "", cp)
req = models.DescribeBillDetailRequest()
req.Month = month
req.BeginTime = begin_time
req.EndTime = end_time
req.Offset = 0
req.Limit = 300
total_cost = 0
details = []
while True:
resp = client.DescribeBillDetail(req)
if not resp.DetailSet:
break
for d in resp.DetailSet:
try:
cost = float(getattr(d, "RealTotalCost", 0) or getattr(d, "TotalCost", 0) or 0)
except (TypeError, ValueError):
cost = 0
total_cost += cost
details.append({
"product": getattr(d, "BusinessCodeName", "") or getattr(d, "ProductCodeName", "") or "-",
"cost": cost,
"time": getattr(d, "PayerUin", ""),
})
if len(resp.DetailSet) < req.Limit:
break
req.Offset += req.Limit
print(f"\n腾讯云消费({start_date} ~ {end_date}")
print("=" * 50)
print(f"合计:¥ {total_cost:.2f}")
if details:
by_product = {}
for x in details:
k = x["product"]
by_product[k] = by_product.get(k, 0) + x["cost"]
print("\n按产品汇总:")
for k, v in sorted(by_product.items(), key=lambda t: -t[1]):
print(f" {k}: ¥ {v:.2f}")
else:
print("(无明细或 API 未返回请登录控制台查看https://console.cloud.tencent.com/expense/overview")
return 0
if __name__ == "__main__":
start_date = end_date = None
if len(sys.argv) >= 3:
try:
start_date = datetime.strptime(sys.argv[1], "%Y-%m-%d").date()
end_date = datetime.strptime(sys.argv[2], "%Y-%m-%d").date()
except ValueError:
pass
days = int(sys.argv[1]) if len(sys.argv) == 2 and start_date is None else 2
sys.exit(main(days=days, start_date=start_date, end_date=end_date))

View File

@@ -1,11 +1,11 @@
---
name: 智能纪要
description: 派对/会议录音一键转结构化纪要并发飞书
triggers: 会议纪要、产研纪要、派对纪要、妙记
description: 派对/会议录音一键转结构化纪要;飞书妙记链接/内容识别与下载(单条/批量),执行完毕用复盘格式回复
triggers: 会议纪要、产研纪要、派对纪要、妙记、飞书妙记、飞书链接、cunkebao.feishu.cn/minutes、meetings.feishu.cn/minutes、妙记下载、第几场、指定场次、批量下载妙记、下载妙记
owner: 水桥
group: 水
version: "1.0"
updated: "2026-02-16"
version: "1.1"
updated: "2026-02-19"
---
# 派对纪要生成器
@@ -19,7 +19,7 @@ updated: "2026-02-16"
| 原则 | 说明 |
|:---|:---|
| **命令行 + API + TOKEN 优先** | 有飞书 API、有 TOKEN 的任务,一律先用命令行处理,不额外打开网页操作 |
| **先查已有经验** | 执行前查 `运营中枢/参考资料/飞书任务_命令行与API优先_经验总结.md``运营中枢/工作台/00_账号与API索引.md`(飞书 Token |
| **先查已有经验** | 执行前查 `运营中枢/参考资料/飞书任务_命令行与API优先_经验总结.md``运营中枢/工作台/00_账号与API索引.md`(飞书 Token;妙记 2091005/404 时查 `智能纪要/参考资料/飞书妙记下载-权限与排查说明.md` |
| **统一用命令行** | 妙记拉取、批量下载、产研日报等均提供一键命令,复用已完成过的 TOKEN/会议流程 |
飞书 TOKEN 与妙记/会议已完成流程见:`运营中枢/参考资料/飞书任务_命令行与API优先_经验总结.md`
@@ -169,16 +169,106 @@ python3 scripts/send_to_feishu.py --json "xxx_meeting.json"
---
## 📥 飞书妙记导出
## 📥 飞书妙记下载:成功链路与避坑(必读)
### 导出步骤
> **识别规则**:用户发 **飞书链接、飞书妙记、cunkebao.feishu.cn/minutes、meetings.feishu.cn/minutes、下载第几场、批量下载妙记** 等,一律按本 Skill 处理;**执行完毕后必须用复盘格式回复**(见 `运营中枢/参考资料/卡若复盘格式_固定规则.md`)。
1. 打开飞书妙记页面 `cunkebao.feishu.cn/minutes/xxx`
2. 点击右上角 **"..."** 菜单
3. 选择 **"导出文字记录"**
4. 下载txt文件到本地
### 强制:全自动、禁止要求用户手动操作
### 一键生成会议纪要
| 规则 | 说明 |
|:---|:---|
| **禁止在流程中要求用户「复制 Cookie」「打开网页」「F12 点选」等** | 执行方AI/脚本必须优先用自动方式完成仅在自动方式均不可用且必须一次性配置时才可说明需一次性配置 xxx 后重跑」,且之后同范围再次执行视为全自动 |
| **Cookie 获取顺序(全自动)** | 1脚本同目录 **cookie_minutes.txt** 第一行可为空 AI 或用户一次性写入2环境变量 **FEISHU_MINUTES_COOKIE**3**本机浏览器自动读取**browser_cookie3Safari/Chrome/Firefox/Edge Doubao 浏览器 Cookie 解密)。不得首先提示用户去复制 |
| **批量场次(如 90102** | 脚本先尝试从 **已保存列表缓存** `soul_minutes_90_102_list.txt`加载有则只做导出不拉列表无则拉列表 筛选 导出若导出因 Cookie 不足失败**自动保存列表到缓存**下次配置好 Cookie 后重跑即只做导出无需用户再指定范围或手动整理链接 |
### 成功下载链路(可实现下载的完整路径)
| 步骤 | 动作 | 说明 |
|:---|:---|:---|
| 1 | 获取 Cookie全自动优先 | 顺序cookie_minutes.txt 环境变量 **本机浏览器**Safari/Chrome/Firefox/Edge/Doubao)。仅在以上皆无时方可说明需一次性从 list 请求复制到 cookie_minutes.txt |
| 2 | bv_csrf_token | 导出接口 200 通常需 **36 位**列表接口可无脚本已支持无 bv 时仍尝试请求cunkebao/meetings 双域)。 |
| 3 | 列表拉取 | `GET meetings.feishu.cn 或 cunkebao.feishu.cn/minutes/api/space/list?size=50&space_name=1`分页 timestamp返回 list object_tokentopiccreate_time |
| 4 | 导出 | `POST meetings.feishu.cn 或 cunkebao.feishu.cn/minutes/api/export`paramsobject_tokenformat=2、add_speaker、add_timestamp请求头cookie、referer同域 |
| 5 | 列表缓存 | 若本次拉列表成功但部分/全部导出失败脚本将匹配到的场次列表写入 `脚本/soul_minutes_{from}_{to}_list.txt`下次执行同范围时**优先从该文件加载仅做导出**无需再拉列表 |
核心逻辑来源[GitHub bingsanyu/feishu_minutes](https://github.com/bingsanyu/feishu_minutes)
### 避坑清单
| | 原因 | 处理 |
|:---|:---|:---|
| 2091005 permission deny | 应用身份(tenant_access_token)无法访问用户创建的妙记 | **Cookie**list 请求复制或用户 token不依赖应用单点打通 |
| 401 / Something went wrong | Cookie bv_csrf_token 或过期 | **list?size=20&space_name=** 请求重拷完整 Cookie保证 36 bv_csrf_token |
| 404 page not found | 开放平台 transcript 接口路径/权限 | 改用 Cookie + meetings.feishu.cn 导出接口见上表 |
| 已存在仍重复下 | 未做本地已有完整 txt 则跳过 | 脚本已对 soul 目录 500 行且含说话人 txt 跳过 |
详细权限与排查`智能纪要/参考资料/飞书妙记下载-权限与排查说明.md`
### 指定第几节下载(单条)
- **指定妙记链接或 object_token**下载**单场**文字记录到指定目录
```bash
SCRIPT_DIR="/Users/karuo/Documents/个人/卡若AI/02_卡人/水桥_平台对接/智能纪要/脚本"
OUT="/Users/karuo/Documents/聊天记录/soul"
# 方式一GitHub 同款(推荐,需 cookie_minutes.txt 含 bv_csrf_token
python3 "$SCRIPT_DIR/feishu_minutes_export_github.py" "https://cunkebao.feishu.cn/minutes/obcnxrkz6k459k669544228c" -o "$OUT"
# 方式二:指定 object_token
python3 "$SCRIPT_DIR/feishu_minutes_export_github.py" -t obcnxrkz6k459k669544228c -o "$OUT"
# 方式三:通用 Cookie含自动读浏览器
python3 "$SCRIPT_DIR/fetch_single_minute_by_cookie.py" "https://cunkebao.feishu.cn/minutes/obcnxrkz6k459k669544228c" -o "$OUT"
```
### 下载指定妙记空间内「全部」或指定场次(批量)
- **按场次筛选** 90102 脚本拉列表后筛第N场再逐条导出**Cookie 全自动**文件 环境变量 本机浏览器已有列表缓存则**只做导出**不要求用户任何手动操作
- **输出目录**默认 `/Users/karuo/Documents/聊天记录/soul`已存在且为完整文字记录含说话人足够长则跳过
- **场次范围参数**`--from 90 --to 102`含首含尾)。
```bash
SCRIPT_DIR="/Users/karuo/Documents/个人/卡若AI/02_卡人/水桥_平台对接/智能纪要/脚本"
# 90102 场全自动Cookie 优先浏览器/文件,有缓存则只导出)
python3 "$SCRIPT_DIR/download_soul_minutes_101_to_103.py" --from 90 --to 102
# 101103 场(默认)
bash "$SCRIPT_DIR/download_101_to_103.sh"
# 或
python3 "$SCRIPT_DIR/download_soul_minutes_101_to_103.py"
```
- ** URL 列表批量**先得到 `urls_soul_party.txt`每行一条妙记链接
```bash
cd "$SCRIPT_DIR"
python3 batch_download_minutes_txt.py --list urls_soul_party.txt --output "/Users/karuo/Documents/聊天记录/soul"
```
- **一键 104 ** token 优先已有完整 txt 则直接成功
```bash
bash "$SCRIPT_DIR/download_104_to_soul.sh"
```
### 手动导出(无 Cookie 时兜底)
1. 打开妙记页 `cunkebao.feishu.cn/minutes/xxx`
2. 右上角「…」→「导出文字记录
3. 将下载的 txt 放到目标目录 soul
### 执行完毕回复规范
每次完成**飞书妙记相关**的下载/导出/批量任务后**必须用卡若复盘格式**收尾
目标·结果·达成率 过程 反思 总结 下一步 `运营中枢/参考资料/卡若复盘格式_固定规则.md`
---
## 📥 飞书妙记导出(与纪要生成联动)
### 从已导出 txt 生成会议纪要
```bash
# 从导出文件生成(自动发送飞书群)
@@ -194,35 +284,6 @@ python3 scripts/fetch_feishu_minutes.py --file "导出.txt" --title "产研团
- **Soul派对聊天记录**
- **其他会议文字记录**
### 批量下载多场妙记 TXT如「派对」「受」100 场)
飞书没有妙记列表API需先拿到**妙记链接列表**再批量拉取 TXT
**步骤 1得到 URL 列表文件 `urls.txt`**
- **方式 A推荐**在飞书客户端或网页打开 **视频会议 → 妙记**在列表里用搜索框输入派对」(soul 派对」),得到筛选结果后逐条点开每条记录复制浏览器地址栏链接形如 `https://cunkebao.feishu.cn/minutes/xxxxx`每行一个粘贴到 `urls.txt`
- **方式 B**若列表页支持复制链接或导出可一次性整理成每行一个 URL 的文本
**步骤 2批量下载 TXT**
```bash
cd /Users/karuo/Documents/个人/卡若AI/02_卡人/_团队成员/水桥/智能纪要/scripts
# 从 urls.txt 批量下载TXT 保存到默认 output 目录
python3 batch_download_minutes_txt.py --list urls.txt
# 指定输出目录(如 soul 派对 100 场)
python3 batch_download_minutes_txt.py --list urls.txt --output ./soul_party_100_txt
# 已下载过的跳过,避免重复
python3 batch_download_minutes_txt.py --list urls.txt --output ./soul_party_100_txt --skip-existing
# 先试跑前 3 条
python3 batch_download_minutes_txt.py --list urls.txt --limit 3
```
**说明**脚本内部调用飞书妙记 API 拉取文字记录若某条无妙记文字记录权限该条会保存为仅含标题+时长的占位 TXT可后续在妙记页手动导出文字记录后替换
---
## 📤 飞书集成配置
@@ -342,14 +403,24 @@ playwright install chromium
```
智能纪要/
├── scripts/
├── 脚本/ # 飞书妙记下载与产研日报(本 Skill 主用)
│ ├── feishu_minutes_export_github.py # ⭐ 单条导出GitHub 同款,需 bv_csrf_token
│ ├── fetch_single_minute_by_cookie.py # 单条导出Cookie/浏览器)
│ ├── download_soul_minutes_101_to_103.py # 批量 101103 场
│ ├── download_101_to_103.sh # 一键 101103
│ ├── download_104_to_soul.sh # 一键 104 场(已有完整 txt 则直接成功)
│ ├── cookie_minutes.txt # Cookie 配置(可选;无则自动读浏览器)
│ ├── soul_minutes_90_102_list.txt # 场次列表缓存(导出失败时自动生成,重跑即只做导出)
│ ├── fetch_feishu_minutes.py # 应用/用户 token 拉取
│ ├── fetch_minutes_list_by_cookie.py # 列表拉取 → urls_soul_party.txt
│ └── batch_download_minutes_txt.py # 按 URL 列表批量下载
├── scripts/ # 纪要生成与飞书发送
│ ├── daily_chanyan_to_feishu.py # ⭐ 产研会议日报≥5分钟→总结+图发飞书)
│ ├── full_pipeline.py # 完整流程(推荐)
│ ├── fetch_feishu_minutes.py # 飞书妙记导出/发飞书
│ ├── parse_chatlog.py # 解析聊天记录
│ ├── generate_meeting.py # 生成HTML
│ ├── md_to_summary_html.py # 总结md→HTML产研截图
│ ├── generate_review.py # 生成复盘HTML
│ ├── screenshot.py # 截图工具
│ └── send_to_feishu.py # 飞书发送(含凭证)
├── config/
@@ -357,6 +428,8 @@ playwright install chromium
├── templates/
│ ├── meeting.html # 派对纪要模板
│ └── review.html # 复盘总结模板
├── 参考资料/
│ └── 飞书妙记下载-权限与排查说明.md
├── output/ # 输出目录
└── SKILL.md # 本文档
```
@@ -377,6 +450,7 @@ playwright install chromium
| 日期 | 更新 |
|:---|:---|
| **2026-02-19** | 📌 飞书妙记下载**强制全自动禁止要求用户手动操作**Cookie 优先 cookie_minutes.txt 环境变量 本机浏览器Safari/Chrome/Firefox/Edge/Doubao批量支持 --from/--to 90102列表缓存 soul_minutes_{from}_{to}_list.txt重跑只做导出双域导出meetings + cunkebao执行完毕用复盘格式回复 |
| **2026-01-29** | 📌 产研会议日报daily_chanyan_to_feishu.py飞书 API/本地 txt 5分钟 总结+图发飞书全命令行 |
| **2026-01-28** | 🤖 融合本地模型支持离线智能摘要信息提取 |
| **2026-01-28** | 配置飞书凭证支持自动发送图片 |

View File

@@ -0,0 +1,152 @@
# 飞书妙记下载:权限与排查说明
> 用于明确「权限已开通仍报 2091005 / 404」的原因避免以后重复踩坑。
> 脚本使用的应用:**APP_ID = cli_a48818290ef8100d**APP_SECRET 见脚本或 00_账号与API索引。
> **Cookie 导出方案** 核心逻辑来自 [GitHub bingsanyu/feishu_minutes](https://github.com/bingsanyu/feishu_minutes):需从妙记列表请求 `list?size=20&space_name=` 复制 Cookie且必须包含 **bv_csrf_token**36 位);导出接口为 `POST https://meetings.feishu.cn/minutes/api/export`referer 为 `https://meetings.feishu.cn/minutes/me`。
---
## 一、凭证确认(防止用错应用)
| 项 | 值 | 说明 |
|----|-----|------|
| APP_ID | `cli_a48818290ef8100d` | 与 `fetch_feishu_minutes.py` / `batch_download_minutes_txt.py` 内置一致 |
| APP_SECRET | 脚本内默认 / 环境变量 `FEISHU_APP_SECRET` | 勿提交 Git仅本机或 00_账号与API索引 保管 |
脚本优先读环境变量 `FEISHU_APP_ID``FEISHU_APP_SECRET`,未设置则用上述默认值。**凭证正确时,获取 tenant_access_token 会成功**,若仍报错则多为权限或数据范围问题。
---
## 二、应用身份妙记权限全开(靠本应用直接访问妙记)
**目标**:只靠应用身份(不依赖用户 token / Cookie就能访问妙记时需在开放平台把妙记相关权限全开且**可访问的数据范围**设为「全部」并保存。
### 2.1 操作步骤(已为你打开权限页时可照做)
1. 打开 **应用身份权限tenant_access_token** 标签页。
2. 搜索「妙记」或「minutes」在「妙记」分类下**全部勾选开通**
- 查看、创建、编辑及管理妙记文件 `minutes:minutes`
- 查看妙记文件 `minutes:minutes:readonly`
- 获取妙记的基本信息 `minutes:minutes.basic:read`
- 下载妙记的音视频文件 `minutes:minutes.media:export`
- 获取妙记的统计信息 `minutes:minutes.statistics:read`
- **导出妙记转写的文字内容** `minutes:minutes.transcript:export`
3.**「查看妙记文件」** 和 **「导出妙记转写的文字内容」** 点 **「可访问的数据范围」** 旁的「配置」或进入配置:
- 选择 **「全部」**(不要选「按条件筛选」)。
- 点击 **「保存」**。
4. 等待约 1 分钟生效后,再运行下载命令。
### 2.2 权限与用途(速查)
| 权限名称 | 权限码 | 用途 |
|----------|--------|------|
| 查看妙记文件 | `minutes:minutes:readonly` | 调用「获取妙记信息」,否则 2091005 |
| 导出妙记转写的文字内容 | `minutes:minutes.transcript:export` | 调用「妙记文字记录/逐字稿」 |
| 获取妙记的基本信息 | `minutes:minutes.basic:read` | 可选,加强信息拉取 |
| 其他妙记相关 | 见上表 | 按需全开 |
**数据范围未选「全部」时,应用仍可能无法访问 cunkebao/思域 下的妙记,会继续 2091005。**
---
## 三、为什么「权限已开通」仍报 2091005 / 404
### 1. 应用身份有「可访问的数据范围」限制
- 使用 **应用身份tenant_access_token** 时,飞书规定:**只能访问该应用有权访问的资源**。
- 「查看妙记文件」已开通,只表示**具备该权限能力**,不表示能访问**任意**妙记。
- 若妙记是在 **cunkebao.feishu.cn / 个人空间 / 其他租户** 下由**用户**创建,通常**不属于**该应用的可访问范围,接口会返回 **2091005 permission deny**
- 权限页中「可访问的数据范围」若为空或未包含该妙记所在空间,就会出现「权限已开通但接口仍 2091005」的情况。
### 2. 2091005 的含义
- **错误码 2091005**permission deny表示**当前 token 对这篇妙记没有访问权限**。
- 可能原因:
- 应用身份下,该妙记不在应用可访问范围内;
- 或未开通「查看妙记文件」;
- 或妙记已删除/不可见。
### 3. 文字记录接口返回 404
- 脚本当前调用:`GET /open-apis/minutes/v1/minutes/{minute_token}/transcripts`
- 若返回 **404 page not found**:可能是接口路径变更,或同样因**无该妙记访问权限**被拒绝(部分网关以 404 返回)。
- 先解决 2091005让「获取妙记信息」通过再观察 transcript 是否仍 404。
---
## 四、以后如何避免「权限已开通仍失败」
### 1. 确认同一应用
- 开放平台里开通权限的 **应用** = 脚本里用的 **APP_ID**cli_a48818290ef8100d
- 若有多个应用,只给 A 开通而脚本用 B仍会 2091005。
### 2. 应用身份只适合「应用能访问到的妙记」
- 若妙记是**用户个人/其他空间**的,应用身份往往**无法**访问。
- 需要**同时用应用 + 用户身份**直接访问时,见下条「应用+用户双轨」。
### 3. 应用 + 用户身份双轨(直接访问个人/空间妙记)
脚本已支持**优先使用用户身份 token**,用同一应用在「用户身份」下访问你能看到的妙记,无需 Cookie。
- **步骤一:开放平台为该应用开通「用户身份」妙记权限**
在「开通权限」页中,**用户身份权限user_access_token** 下同样勾选:
**查看妙记文件**、**导出妙记转写的文字内容**,并保存。
- **步骤二:用本应用获取用户 access_token必须为本应用 OAuth 所得)**
- 00_账号与API索引 里的飞书 access_token / refresh_token 若是**其他应用**(如飞书 Suite App颁发的**不能**直接用于本应用;会报 99991668 Invalid access token。
- 须用 **cli_a48818290ef8100d** 做一次 OAuth 授权:
1. 飞书开放平台 → 该应用 → 安全设置 → 配置「重定向 URL」`http://localhost/feishu_callback`)。
2. 在浏览器打开授权链接(格式示例):
`https://open.feishu.cn/open-apis/authen/v1/authorize?app_id=cli_a48818290ef8100d&redirect_uri=你配置的URL&scope=minutes:minutes:readonly,minutes:minutes.transcript:export`
3. 登录并授权后,飞书会重定向到 redirect_uri 并带上 `code`,用 code 调「通过 code 获取 user_access_token」接口得到 **access_token****refresh_token**
4.**access_token** 填到脚本同目录 `feishu_user_token.txt` 第一行,**refresh_token** 填第二行(可选,用于自动刷新),保存。
- **步骤三:运行脚本**
```bash
cd 智能纪要/脚本
python3 fetch_feishu_minutes.py "https://cunkebao.feishu.cn/minutes/<minute_token>" --output "/Users/karuo/Documents/聊天记录/soul"
```
脚本会优先读 `feishu_user_token.txt` 或环境变量 `FEISHU_USER_ACCESS_TOKEN`,使用**用户身份**调妙记接口,即可访问你个人/空间下的妙记。
若用户 token 过期(99991668),脚本会尝试用第二行 refresh_token 自动刷新并写回文件。
- **小结**:要「应用也能用、个人也能直接访问」,就**同时**开通应用身份 + 用户身份妙记权限,并把**本应用** OAuth 得到的用户 token 填到 `feishu_user_token.txt`;这样一条命令即可直接访问该内容。
### 4. Cookie 方案(推荐用于 cunkebao / 个人妙记,且不想配 OAuth 时)
1. 浏览器打开 https://cunkebao.feishu.cn/minutes/home 并登录。
2. F12 → 网络 → 找到 `list?size=` 或导出相关请求 → 复制请求头中的 **Cookie**。
3. 粘贴到智能纪要脚本目录下的 `cookie_minutes.txt`(第一行,覆盖占位符)。
4. 执行:
```bash
cd /Users/karuo/Documents/个人/卡若AI/02_卡人/水桥_平台对接/智能纪要/脚本
python3 fetch_single_minute_by_cookie.py "https://cunkebao.feishu.cn/minutes/<minute_token>" --output "/Users/karuo/Documents/聊天记录/soul"
```
Cookie 有效时,会走 cunkebao/meetings 的导出接口,不依赖应用权限。
### 5. 手动导出
- 打开该场妙记页 → 右上角「…」→「导出文字记录」→ 将 TXT 保存到 `聊天记录/soul`。
---
## 五、排查清单(以后出现同类问题按此核对)
| 步骤 | 检查项 |
|------|--------|
| 1 | 脚本使用的 APP_ID 是否为 cli_a48818290ef8100d |
| 2 | 开放平台该应用下「查看妙记文件」「导出妙记转写的文字内容」是否均为「已开通」? |
| 3 | 该妙记是否在**应用可访问的数据范围**内(如企业内、应用已安装的空间)?若在 cunkebao/个人,优先用 Cookie 或手动导出。 |
| 4 | 是否可用 Cookie 方案cookie_minutes.txt 是否已粘贴有效 Cookie 并重试? |
| 5 | 若仅需单场文字,是否已尝试妙记页「…」→ 导出文字记录? |
| 6 | 若要用用户身份直接访问:是否已用**本应用** OAuth 获取 token 并填入 `feishu_user_token.txt`?用户身份权限是否已开通? |
---
## 六、相关文件
- 凭证集中存放卡若AI `运营中枢/工作台/00_账号与API索引.md`(飞书 Token / 应用凭证说明)
- 脚本默认凭证:`智能纪要/脚本/fetch_feishu_minutes.py` 内 FEISHU_APP_ID / FEISHU_APP_SECRET
- 下载步骤:`聊天记录/soul/飞书妙记下载说明.md`
- 批量下载:`智能纪要/参考资料/飞书妙记批量下载TXT说明.md`

View File

@@ -0,0 +1,91 @@
#!/usr/bin/env python3
"""通过 CDP 连接已开启远程调试的浏览器,访问 list 页取 Cookie 后导出 104 场并保存。"""
import sys
from pathlib import Path
OUT_FILE = Path("/Users/karuo/Documents/聊天记录/soul/soul 派对 104场 20260219.txt")
URL_LIST = "https://cunkebao.feishu.cn/minutes/home"
OBJECT_TOKEN = "obcnyg5nj2l8q281v32de6qz"
EXPORT_URL = "https://cunkebao.feishu.cn/minutes/api/export"
CDP_URL = "http://localhost:9222"
def main():
try:
from playwright.sync_api import sync_playwright
except ImportError:
print("NO_PLAYWRIGHT", file=sys.stderr)
return 2
import requests
with sync_playwright() as p:
try:
browser = p.chromium.connect_over_cdp(CDP_URL, timeout=8000)
except Exception as e:
print("CDP_CONNECT_FAIL", str(e), file=sys.stderr)
return 3
default_context = browser.contexts[0] if browser.contexts else None
if not default_context:
print("NO_CONTEXT", file=sys.stderr)
browser.close()
return 4
# 优先用已打开的 104 页,否则新开 list 页
pages = default_context.pages
page = None
for p in pages:
if "obcnyg5nj2l8q281v32de6qz" in (p.url or ""):
page = p
break
if not page and pages:
page = pages[0]
if not page:
page = default_context.new_page()
# 若当前不是 list 页则打开 list 以拿到 bv_csrf_token
if "space/list" not in (page.url or "") and "minutes/home" not in (page.url or ""):
page.goto(URL_LIST, wait_until="domcontentloaded", timeout=20000)
page.wait_for_timeout(4000)
cookies = default_context.cookies()
browser.close()
cookie_str = "; ".join([f"{c['name']}={c['value']}" for c in cookies])
bv = next((c["value"] for c in cookies if c.get("name") == "bv_csrf_token" and len(c.get("value", "")) == 36), None)
if len(cookie_str) < 100:
print("NO_COOKIE", file=sys.stderr)
return 5
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Cookie": cookie_str,
"Referer": "https://cunkebao.feishu.cn/minutes/",
}
if bv:
headers["bv-csrf-token"] = bv
r = requests.post(
EXPORT_URL,
params={"object_token": OBJECT_TOKEN, "format": 2, "add_speaker": "true", "add_timestamp": "false"},
headers=headers,
timeout=20,
)
r.encoding = "utf-8"
if r.status_code != 200:
print("EXPORT_HTTP", r.status_code, len(r.text), file=sys.stderr)
return 6
text = (r.text or "").strip()
if not text or len(text) < 100 or "Something went wrong" in text:
print("EXPORT_BODY", text[:300], file=sys.stderr)
return 7
if text.startswith("{"):
try:
j = r.json()
text = j.get("data") or j.get("content") or ""
if isinstance(text, dict):
text = text.get("content") or text.get("text") or ""
except Exception:
pass
OUT_FILE.parent.mkdir(parents=True, exist_ok=True)
OUT_FILE.write_text("日期: 20260219\n标题: soul 派对 104场 20260219\n\n文字记录:\n\n" + text, encoding="utf-8")
print("OK", str(OUT_FILE))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,7 @@
#!/bin/bash
# 下载 soul 派对 第101、102、103 场妙记文字记录到 soul 目录(需已配置 cookie_minutes.txt
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
cd "$SCRIPT_DIR"
PY="python3"
[ -x "$SCRIPT_DIR/.venv/bin/python" ] && PY="$SCRIPT_DIR/.venv/bin/python"
exec $PY download_soul_minutes_101_to_103.py "$@"

View File

@@ -0,0 +1,42 @@
#!/bin/bash
# 一键下载 104 场妙记:优先 GitHub 同款 Cookie 导出,再试通用 Cookie最后试应用/用户 token
# 核心逻辑参考https://github.com/bingsanyu/feishu_minutes
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT="/Users/karuo/Documents/聊天记录/soul"
TOKEN="obcnxrkz6k459k669544228c"
URL="https://cunkebao.feishu.cn/minutes/${TOKEN}"
cd "$SCRIPT_DIR"
PY="python3"
[ -x "$SCRIPT_DIR/.venv/bin/python" ] && PY="$SCRIPT_DIR/.venv/bin/python"
_check_saved() {
for f in "$OUT"/*.txt; do
[ -f "$f" ] && [ -s "$f" ] && ! grep -q "未解析到文字内容\|需在飞书妙记页面" "$f" 2>/dev/null && grep -q . "$f" && echo "已保存: $f" && return 0
done
return 1
}
# 0) 已有该场完整文字记录则直接成功
for f in "$OUT"/*.txt; do
[ -f "$f" ] && [ -s "$f" ] || continue
lines=$(wc -l < "$f" 2>/dev/null)
if [ "${lines:-0}" -ge 500 ] && grep -q "说话人" "$f" 2>/dev/null; then
echo "已存在完整文字记录,跳过下载: $f"
exit 0
fi
done
# 1) GitHub 同款cookie_minutes.txt 需含 bv_csrf_token来自 list?size=20& 请求)
if [ -f "$SCRIPT_DIR/cookie_minutes.txt" ] && grep -q "bv_csrf_token=" "$SCRIPT_DIR/cookie_minutes.txt" 2>/dev/null; then
echo "使用 GitHub 同款导出meetings.feishu.cn..."
if $PY feishu_minutes_export_github.py -t "$TOKEN" -o "$OUT" 2>/dev/null && _check_saved; then exit 0; fi
fi
# 2) 通用 Cookie含自动读浏览器
echo "尝试 Cookie文件/浏览器)拉取..."
if $PY fetch_single_minute_by_cookie.py "$URL" -o "$OUT" 2>/dev/null && _check_saved; then exit 0; fi
# 3) 应用/用户 token
echo "尝试应用/用户 token..."
$PY fetch_feishu_minutes.py "$URL" -o "$OUT"
exit 0

View File

@@ -0,0 +1,354 @@
#!/usr/bin/env python3
"""
拉取妙记列表,按场次范围筛选并下载文字记录到 soul 目录。
默认 101103 场;可用 --from 90 --to 102 指定 90102 场等。
依赖 Cookiecookie_minutes.txt需含 bv_csrf_token。与 feishu_minutes_export_github 同源。
"""
from __future__ import annotations
import argparse
import re
import sys
import time
from pathlib import Path
from datetime import datetime
try:
import requests
except ImportError:
requests = None
SCRIPT_DIR = Path(__file__).resolve().parent
COOKIE_FILE = SCRIPT_DIR / "cookie_minutes.txt"
OUT_DIR = Path("/Users/karuo/Documents/聊天记录/soul")
# 与 feishu_minutes_export_github 一致
LIST_URL = "https://meetings.feishu.cn/minutes/api/space/list"
EXPORT_URL = "https://meetings.feishu.cn/minutes/api/export"
REFERER = "https://meetings.feishu.cn/minutes/me"
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
FIELD_PATTERN = re.compile(r"第?(\d{2,3})场", re.I)
def _cookie_from_browser() -> str:
"""全自动:从本机浏览器读取飞书 Cookie无需用户手动复制。"""
import os as _os
# 1) 环境变量
c = _os.environ.get("FEISHU_MINUTES_COOKIE", "").strip()
if c and len(c) > 100 and "PASTE_YOUR" not in c:
return c
# 2) browser_cookie3
try:
import browser_cookie3
for domain in ("cunkebao.feishu.cn", "feishu.cn", ".feishu.cn"):
for loader in (browser_cookie3.safari, browser_cookie3.chrome, browser_cookie3.chromium, browser_cookie3.firefox, browser_cookie3.edge):
try:
cj = loader(domain_name=domain)
parts = [f"{c.name}={c.value}" for c in cj]
s = "; ".join(parts)
if len(s) > 100:
return s
except Exception:
continue
except ImportError:
pass
# 3) Doubao 浏览器 CookiemacOS
try:
import subprocess
import shutil
import tempfile
import sqlite3
import hashlib
for name in ("Doubao Browser Safe Storage", "Doubao Safe Storage"):
try:
key = subprocess.run(["security", "find-generic-password", "-s", name, "-w"], capture_output=True, text=True, timeout=5).stdout.strip()
if not key:
continue
except Exception:
continue
for profile in ("Default", "Profile 1", "Profile 2", "Profile 3"):
db = Path.home() / "Library/Application Support/Doubao" / profile / "Cookies"
if not db.exists():
continue
try:
tmp = tempfile.mktemp(suffix=".db")
shutil.copy2(db, tmp)
conn = sqlite3.connect(tmp)
cur = conn.cursor()
cur.execute("SELECT host_key, name, encrypted_value FROM cookies WHERE host_key LIKE '%feishu%' OR host_key LIKE '%cunkebao%'")
rows = cur.fetchall()
conn.close()
Path(tmp).unlink(missing_ok=True)
except Exception:
continue
if not rows:
continue
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes
derived = hashlib.pbkdf2_hmac("sha1", key.encode("utf-8"), b"saltysalt", 1003, dklen=16)
parts = []
for host, name, enc in rows:
if enc[:3] != b"v10":
continue
raw = enc[3:]
dec = Cipher(algorithms.AES(derived), modes.CBC(b" " * 16)).decryptor()
pt = dec.update(raw) + dec.finalize()
pad = pt[-1]
if isinstance(pad, int) and 1 <= pad <= 16:
pt = pt[:-pad]
for i in range(min(len(pt), 48)):
if i + 4 <= len(pt) and all(32 <= pt[j] < 127 for j in range(i, min(i + 8, len(pt)))):
val = pt[i:].decode("ascii", errors="ignore")
if val and "\x00" not in val:
parts.append(f"{name}={val}")
break
if len(parts) > 5:
return "; ".join(parts)
except Exception:
pass
return ""
def get_cookie() -> str:
"""优先级cookie_minutes.txt → 环境变量 → 自动从浏览器读取(全自动,无需用户手动)。"""
if COOKIE_FILE.exists():
for line in COOKIE_FILE.read_text(encoding="utf-8", errors="ignore").strip().splitlines():
line = line.strip()
if line and not line.startswith("#") and "PASTE_YOUR" not in line and len(line) > 100:
return line
c = _cookie_from_browser()
if c:
return c
return ""
def get_bv_csrf(cookie: str) -> str:
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()
def build_headers(cookie: str, require_bv: bool = False) -> dict:
"""require_bv=True 时强制 bv_csrf_token 36 位否则能带就带不带也尝试请求cunkebao 等可能可用)。"""
h = {
"User-Agent": USER_AGENT,
"cookie": cookie,
"referer": REFERER,
"content-type": "application/x-www-form-urlencoded",
}
bv = get_bv_csrf(cookie)
if len(bv) == 36:
h["bv-csrf-token"] = bv
elif require_bv:
raise ValueError("Cookie 需包含 bv_csrf_token36 位)。请从妙记 list 请求复制完整 Cookie。")
return h
def fetch_list(cookie: str, space_name: int = 1, size: int = 50, last_ts=None, base_url: str = None) -> list:
"""拉取妙记列表分页。base_url 默认 meetings可改为 cunkebao。"""
base = base_url or LIST_URL
url = f"{base}?size={size}&space_name={space_name}"
if last_ts:
url += f"&timestamp={last_ts}"
headers = build_headers(cookie, require_bv=False)
r = requests.get(url, headers=headers, timeout=30)
data = r.json()
if data.get("code") != 0:
raise RuntimeError(data.get("msg", "list api error") or r.text[:200])
inner = data.get("data", {})
lst = inner.get("list", [])
out = list(lst)
if inner.get("has_more") and lst:
last = lst[-1]
ts = last.get("share_time") or last.get("create_time")
if ts:
time.sleep(0.3)
out.extend(fetch_list(cookie, space_name, size, ts, base_url))
return out
def filter_by_field_range(all_items: list, from_num: int, to_num: int) -> list:
"""保留标题中含「第N场」且 N 在 [from_num, to_num] 的项(去重、按场次排序)。"""
seen = set()
matched = []
for item in all_items:
topic = (item.get("topic") or "").strip()
m = FIELD_PATTERN.search(topic)
if not m:
continue
num = int(m.group(1))
if not (from_num <= num <= to_num):
continue
token = item.get("object_token") or item.get("minute_token")
if not token or token in seen:
continue
seen.add(token)
matched.append((num, topic, token, item.get("create_time"), item.get("share_time")))
return sorted(matched, key=lambda x: x[0])
def export_transcript(cookie: str, object_token: str) -> str | None:
"""尝试 meetings 与 cunkebao 两个导出域名。"""
params = {"object_token": object_token, "add_speaker": "true", "add_timestamp": "false", "format": 2}
for export_base in (EXPORT_URL, "https://cunkebao.feishu.cn/minutes/api/export"):
ref = "https://cunkebao.feishu.cn/minutes/" if "cunkebao" in export_base else REFERER
headers = build_headers(cookie, require_bv=False)
headers["referer"] = ref
try:
r = requests.post(export_base, params=params, headers=headers, timeout=20)
r.encoding = "utf-8"
if r.status_code != 200:
continue
text = (r.text or "").strip()
if not text or len(text) < 20:
continue
if text.startswith("{"):
try:
j = r.json()
d = j.get("data")
if isinstance(d, str):
return d
if isinstance(d, dict):
return d.get("content") or d.get("text") or d.get("transcript")
except Exception:
pass
continue
if "<html" in text.lower()[:100] or "Something went wrong" in text:
continue
return text
except Exception:
continue
return None
def save_txt(output_dir: Path, title: str, body: str, date_str: str | None = None) -> Path:
output_dir.mkdir(parents=True, exist_ok=True)
date_str = date_str or datetime.now().strftime("%Y%m%d")
safe_title = re.sub(r'[\\/*?:"<>|]', "_", (title or "妙记"))
path = output_dir / f"{safe_title}_{date_str}.txt"
path.write_text(f"日期: {date_str}\n标题: {title}\n\n文字记录:\n\n{body}", encoding="utf-8")
return path
def main() -> int:
parser = argparse.ArgumentParser(description="soul 派对妙记按场次范围下载")
parser.add_argument("--from", "-f", type=int, default=101, dest="from_num", help="起始场次(含)")
parser.add_argument("--to", "-t", type=int, default=103, dest="to_num", help="结束场次(含)")
args = parser.parse_args()
from_num, to_num = args.from_num, args.to_num
if from_num > to_num:
from_num, to_num = to_num, from_num
if not requests:
print("请安装 requests: pip install requests", file=sys.stderr)
return 1
cookie = get_cookie()
if not cookie:
print("未获取到 Cookie已尝试 cookie_minutes.txt、环境变量、本机浏览器。请确保已登录飞书妙记并在 cookie_minutes.txt 第一行粘贴 list 请求的 Cookie。", file=sys.stderr)
return 1
try:
build_headers(cookie, require_bv=False)
except ValueError as e:
print(e, file=sys.stderr)
return 1
list_cache = SCRIPT_DIR / f"soul_minutes_{from_num}_{to_num}_list.txt"
items = []
# 优先从已保存列表加载(全自动重跑:无需再拉列表,直接导出)
if list_cache.exists():
try:
lines = list_cache.read_text(encoding="utf-8").strip().splitlines()
for line in lines:
if line.startswith("#") or not line.strip():
continue
parts = line.split("\t")
if len(parts) >= 3:
items.append((int(parts[0]), parts[1], parts[2], None, None))
if items:
items = sorted(items, key=lambda x: x[0])
print(f"📌 从缓存加载 {len(items)} 场,仅做导出(全自动)")
except Exception:
pass
if not items:
print(f"📋 拉取妙记列表(场次范围 {from_num}{to_num})…")
all_items = []
for base in (LIST_URL, "https://cunkebao.feishu.cn/minutes/api/space/list"):
try:
all_items = fetch_list(cookie, base_url=base)
if all_items:
break
except Exception as e:
if base == LIST_URL:
print(" meetings 列表失败,尝试 cunkebao…", e)
continue
if not all_items:
print("拉取列表失败,请确认 Cookie 有效且来自妙记 list 请求。", file=sys.stderr)
return 1
items = filter_by_field_range(all_items, from_num, to_num)
if not items:
print(f"未在列表中匹配到第{from_num}{to_num}场。")
return 0
print(f"📌 匹配到 {len(items)} 场: {[x[1] for x in items]}")
out_dir = OUT_DIR.resolve()
saved = 0
for num, topic, token, create_ts, share_ts in items:
# 若已存在完整文字记录则跳过(文件名需像妙记:含 第N场 且含 soul/派对,避免误判说明类文件)
skip = False
for f in out_dir.glob("*.txt"):
if f"{num}" not in f.name and (f"{num}" not in f.name or ("soul" not in f.name and "派对" not in f.name)):
continue
try:
t = f.read_text(encoding="utf-8", errors="ignore")
if len(t) > 5000 and "说话人" in t[:5000]:
skip = True
print(f" 跳过(已存在): {topic}")
saved += 1
break
except Exception:
pass
if skip:
continue
else:
text = export_transcript(cookie, token)
if not text:
print(f" ❌ 导出失败: {topic} ({token})")
continue
ts = create_ts or share_ts
if ts:
try:
if ts > 1e10:
ts = ts / 1000
date_str = datetime.fromtimestamp(ts).strftime("%Y%m%d")
except Exception:
date_str = datetime.now().strftime("%Y%m%d")
else:
date_str = datetime.now().strftime("%Y%m%d")
path = save_txt(out_dir, topic, text, date_str)
print(f"{topic} -> {path.name}")
saved += 1
time.sleep(0.5)
# 若有导出失败且本次是拉列表得到的 items保存列表供 Cookie 配置好后重跑(仅做导出)
failed = len(items) - saved
if failed > 0 and not list_cache.exists():
cache_lines = ["# 场次\t标题\tobject_token", "# 配置 cookie_minutes.txt 后重新执行本脚本即可只做导出"]
for num, topic, token, _, _ in items:
cache_lines.append(f"{num}\t{topic}\t{token}")
list_cache.write_text("\n".join(cache_lines), encoding="utf-8")
print(f"📁 已保存列表到 {list_cache.name},配置 cookie_minutes.txt从妙记 list 请求复制 Cookie后重新执行即可只做导出。")
print(f"✅ 共处理 {saved}/{len(items)} 场,保存目录: {out_dir}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,190 @@
#!/usr/bin/env python3
"""
飞书妙记单条导出(核心逻辑来自 GitHub bingsanyu/feishu_minutes
- Cookie 必须从「飞书妙记」列表请求中获取,且包含 bv_csrf_token36 位)。
- 获取方式:打开 https://meetings.feishu.cn/minutes/home → F12 → 网络 → 找到 list?size=20&space_name= 请求 → 复制请求头中的 Cookie。
- 若使用 cunkebao 等子域,请打开对应空间的妙记主页,从 list 请求复制 Cookie。
用法:
python3 feishu_minutes_export_github.py --cookie "..." --object-token obcnxrkz6k459k669544228c -o /path/to/soul
cookie 放在脚本同目录 cookie_minutes.txt 第一行
python3 feishu_minutes_export_github.py --object-token obcnxrkz6k459k669544228c -o /path/to/soul
"""
from __future__ import annotations
import argparse
import os
import re
import sys
from pathlib import Path
from datetime import datetime
try:
import requests
except ImportError:
requests = None
SCRIPT_DIR = Path(__file__).resolve().parent
COOKIE_FILE = SCRIPT_DIR / "cookie_minutes.txt"
# 与 bingsanyu/feishu_minutes 一致
EXPORT_URL = "https://meetings.feishu.cn/minutes/api/export"
REFERER = "https://meetings.feishu.cn/minutes/me"
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36"
def get_cookie_from_args_or_file(cookie_arg: str | None) -> str:
if cookie_arg and cookie_arg.strip() and "PASTE_YOUR" not in cookie_arg:
return cookie_arg.strip()
if COOKIE_FILE.exists():
raw = COOKIE_FILE.read_text(encoding="utf-8", errors="ignore").strip().splitlines()
for line in raw:
line = line.strip()
if line and not line.startswith("#") and "PASTE_YOUR" not in line:
return line
return ""
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()
def build_headers(cookie: str):
"""与 feishu_downloader.py 完全一致的请求头。"""
bv = get_bv_csrf_token(cookie)
if len(bv) != 36:
raise ValueError(
"Cookie 中未包含有效的 bv_csrf_token需 36 位)。"
"请从 飞书妙记主页 → F12 → 网络 → list?size=20& 请求 中复制完整 Cookie。"
)
return {
"User-Agent": USER_AGENT,
"cookie": cookie,
"bv-csrf-token": bv,
"referer": REFERER,
"content-type": "application/x-www-form-urlencoded",
}
def export_transcript(cookie: str, object_token: str, format_txt: bool = True, add_speaker: bool = True, add_timestamp: bool = False) -> str | None:
"""
调用妙记导出接口,与 GitHub feishu_downloader.get_minutes_url 一致:
POST exportparams: object_token, add_speaker, add_timestamp, format (2=txt, 3=srt)。
返回导出的文本,失败返回 None。
"""
if not requests:
return None
# format: 2=txt, 3=srt与 config.ini 一致)
params = {
"object_token": object_token,
"add_speaker": "true" if add_speaker else "false",
"add_timestamp": "true" if add_timestamp else "false",
"format": 2 if format_txt else 3,
}
headers = build_headers(cookie)
try:
r = requests.post(EXPORT_URL, params=params, headers=headers, timeout=20)
r.encoding = "utf-8"
if r.status_code != 200:
return None
text = (r.text or "").strip()
if not text or len(text) < 20:
return None
# 可能是 JSON 包装
if text.startswith("{"):
try:
j = r.json()
data = j.get("data")
if isinstance(data, str):
return data
if isinstance(data, dict):
return data.get("content") or data.get("text") or data.get("transcript")
except Exception:
pass
return None
if "<html" in text.lower()[:100] or "Something went wrong" in text:
return None
return text
except Exception:
return None
def extract_token_from_url(url_or_token: str) -> str:
m = re.search(r"/minutes/([a-zA-Z0-9]+)", url_or_token)
if m:
return m.group(1)
return url_or_token.strip()
def save_txt(output_dir: Path, title: str, body: str, date_str: str | None = None) -> Path:
output_dir.mkdir(parents=True, exist_ok=True)
date_str = date_str or datetime.now().strftime("%Y%m%d")
safe_title = re.sub(r'[\\/*?:"<>|]', "_", (title or "妙记"))
filename = f"{safe_title}_{date_str}.txt"
path = output_dir / filename
header = f"日期: {date_str}\n标题: {title}\n\n文字记录:\n\n" if title else f"日期: {date_str}\n\n文字记录:\n\n"
path.write_text(header + body, encoding="utf-8")
return path
def main() -> int:
if not requests:
print("请安装 requests: pip install requests", file=sys.stderr)
return 1
parser = argparse.ArgumentParser(description="飞书妙记单条导出GitHub bingsanyu/feishu_minutes 逻辑)")
parser.add_argument("url_or_token", nargs="?", default="", help="妙记链接或 object_token")
parser.add_argument("--cookie", "-c", default="", help="完整 Cookie或从 cookie_minutes.txt 读取)")
parser.add_argument("--object-token", "-t", default="", help="妙记 object_token")
parser.add_argument("--output", "-o", default="/Users/karuo/Documents/聊天记录/soul", help="输出目录")
parser.add_argument("--title", default="", help="可选标题(否则用默认文件名)")
parser.add_argument("--no-speaker", action="store_true", help="字幕不包含说话人")
parser.add_argument("--timestamp", action="store_true", help="字幕包含时间戳")
args = parser.parse_args()
cookie = get_cookie_from_args_or_file(args.cookie)
if not cookie:
print("未配置 Cookie。请", file=sys.stderr)
print(" 1. 打开 https://meetings.feishu.cn/minutes/home或你空间的妙记主页", file=sys.stderr)
print(" 2. F12 → 网络 → 找到 list?size=20&space_name= 请求 → 复制请求头中的 Cookie", file=sys.stderr)
print(" 3. 粘贴到脚本同目录 cookie_minutes.txt 第一行,或使用 --cookie \"...\"", file=sys.stderr)
return 1
object_token = args.object_token or extract_token_from_url(args.url_or_token)
if not object_token:
object_token = "obcnxrkz6k459k669544228c" # 104 场默认
try:
build_headers(cookie)
except ValueError as e:
print(str(e), file=sys.stderr)
return 1
print(f"📝 object_token: {object_token}")
print("📡 使用 meetings.feishu.cn 导出接口GitHub 同款)…")
text = export_transcript(cookie, object_token, format_txt=True, add_speaker=not args.no_speaker, add_timestamp=args.timestamp)
if not text:
print("❌ 导出失败。请确认:", file=sys.stderr)
print(" 1. Cookie 来自「妙记列表 list 请求」且包含 bv_csrf_token36 位)", file=sys.stderr)
print(" 2. 该妙记在当前登录空间内可访问", file=sys.stderr)
return 1
out_dir = Path(args.output).resolve()
title = args.title or f"妙记_{object_token}"
path = save_txt(out_dir, title, text)
print(f"✅ 已保存: {path}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -46,6 +46,8 @@ FEISHU_APP_SECRET = os.environ.get("FEISHU_APP_SECRET", "dhjU0qWd5AzicGWTf4cTqhC
# 输出目录
SCRIPT_DIR = Path(__file__).parent
OUTPUT_DIR = SCRIPT_DIR.parent / "output"
# 用户身份 token 文件(可选,一行一个 access_token用于访问个人/空间妙记)
FEISHU_USER_TOKEN_FILE = SCRIPT_DIR / "feishu_user_token.txt"
def get_tenant_access_token() -> str:
@@ -69,6 +71,66 @@ def get_tenant_access_token() -> str:
return None
def _read_user_token_file() -> tuple[str | None, str | None]:
"""从 feishu_user_token.txt 读取 access_token 和 refresh_token第一行 access第二行可选 refresh"""
if not FEISHU_USER_TOKEN_FILE.exists():
return (None, None)
try:
lines = [
L.strip() for L in FEISHU_USER_TOKEN_FILE.read_text(encoding="utf-8", errors="ignore").splitlines()
if L.strip() and not L.strip().startswith("#")
]
access = lines[0] if lines and "u-" in lines[0] else None
refresh = lines[1] if len(lines) > 1 and lines[1].startswith("ur-") else None
return (access, refresh)
except Exception:
return (None, None)
def _refresh_user_access_token(refresh_token: str) -> str | None:
"""用 refresh_token 刷新得到新的 user access_token须为本应用 OAuth 颁发的 refresh_token"""
# 飞书刷新用户 tokenPOSTapp_id / app_secret / refresh_token / grant_type
url = "https://open.feishu.cn/open-apis/authen/v1/refresh_access_token"
payload = {
"grant_type": "refresh_token",
"refresh_token": refresh_token,
"app_id": FEISHU_APP_ID,
"app_secret": FEISHU_APP_SECRET,
}
try:
r = requests.post(url, json=payload, timeout=10)
data = r.json()
if data.get("code") == 0:
return data.get("data", {}).get("access_token")
except Exception:
pass
return None
def get_minutes_token() -> tuple[str | None, str]:
"""
获取用于调用妙记接口的 token。优先用户身份可访问个人/空间妙记),否则应用身份。
若设置 FEISHU_USE_TENANT_ONLY=1 或传入 --tenant-only则仅用企业身份tenant_access_token不用浏览器/用户 token。
返回 (token, "user"|"app"),无 token 时 (None, "app")。
用户 token 须为本应用 cli_a48818290ef8100d OAuth 授权所得;若 99991668 可尝试在 feishu_user_token.txt 第二行填 refresh_token 自动刷新。
"""
# 强制仅用企业身份(应用 tenant_access_token不读用户 token
if os.environ.get("FEISHU_USE_TENANT_ONLY", "").strip().lower() in ("1", "true", "yes"):
token = get_tenant_access_token()
return (token, "app")
# 1) 环境变量:用户身份 token
user_token = os.environ.get("FEISHU_USER_ACCESS_TOKEN", "").strip()
if user_token and "u-" in user_token:
return (user_token, "user")
# 2) 脚本同目录 feishu_user_token.txt第一行 access_token第二行可选 refresh_token
access, refresh = _read_user_token_file()
if access:
return (access, "user")
# 3) 应用身份
token = get_tenant_access_token()
return (token, "app")
def extract_minute_token(url_or_token: str) -> str:
"""从URL或直接token中提取minute_token"""
# 如果是URL提取token
@@ -286,19 +348,36 @@ def fetch_and_save(url_or_token: str, output_dir: Path = None) -> Path:
minute_token = extract_minute_token(url_or_token)
print(f"📝 妙记Token: {minute_token}")
# 获取token
# 获取 token(优先用户身份,可访问个人/空间妙记;否则应用身份)
print("🔑 获取飞书访问令牌...")
token = get_tenant_access_token()
token, token_type = get_minutes_token()
if not token:
print("❌ 无法获取访问令牌")
return None
print(f" 使用: {'用户身份 (可访问个人/空间妙记)' if token_type == 'user' else '应用身份'}")
# 获取妙记信息
# 获取妙记信息(若仅开通「导出妙记文字」权限可能失败,则仅拉取文字)
print("📋 获取妙记基本信息...")
info = get_minutes_info(token, minute_token)
# 用户身份 token 无效(99991668)时尝试用 refresh_token 刷新后重试一次
if not info and token_type == "user":
_, refresh = _read_user_token_file()
if refresh:
print(" ⚠️ 用户 token 可能已过期,尝试刷新...")
new_access = _refresh_user_access_token(refresh)
if new_access:
try:
FEISHU_USER_TOKEN_FILE.write_text(
new_access + "\n" + refresh + "\n",
encoding="utf-8",
)
except Exception:
pass
token = new_access
info = get_minutes_info(token, minute_token)
if not info:
print("❌ 无法获取妙记信息")
return None
print(" ⚠️ 基本信息权限不足(2091005),尝试仅拉取文字记录(需开通「导出妙记转写的文字内容」)...")
info = {"title": f"妙记_{minute_token[:12]}", "duration": 0, "create_time": str(int(datetime.now().timestamp()))}
title = info.get("title", "未命名")
duration = format_timestamp(int(info.get("duration", 0)))
@@ -427,6 +506,7 @@ def main():
parser.add_argument("--file", "-f", type=str, help="从飞书导出的文字记录文件路径")
parser.add_argument("--title", type=str, help="指定标题(用于导出文件)")
parser.add_argument("--output", "-o", type=str, help="输出目录")
parser.add_argument("--tenant-only", action="store_true", help="仅用企业身份 tenant_access_tokenAPP_ID+APP_SECRET不用用户 token")
parser.add_argument("--generate", "-g", action="store_true",
help="获取后自动生成会议纪要并发送飞书")
parser.add_argument("--send-webhook", "-S", action="store_true",
@@ -435,6 +515,8 @@ def main():
help=f"飞书机器人 Webhook 地址(默认: 会议纪要群)")
args = parser.parse_args()
if getattr(args, "tenant_only", False):
os.environ["FEISHU_USE_TENANT_ONLY"] = "1"
if not REQUESTS_AVAILABLE:
print("❌ 需要安装 requests: pip install requests")

View File

@@ -0,0 +1,228 @@
#!/usr/bin/env python3
"""
用 Cookie 从 cunkebao 拉取单条妙记详情/文字并保存为 TXT不依赖开放平台妙记权限
需在脚本同目录 cookie_minutes.txt 中粘贴浏览器 Cookie飞书妙记页 F12→网络→list 请求头)。
用法:
python3 fetch_single_minute_by_cookie.py "https://cunkebao.feishu.cn/minutes/obcnxrkz6k459k669544228c" --output "/Users/karuo/Documents/聊天记录/soul"
"""
import json
import os
import re
import sys
from pathlib import Path
from datetime import datetime
try:
import requests
except ImportError:
requests = None
SCRIPT_DIR = Path(__file__).resolve().parent
COOKIE_FILE = SCRIPT_DIR / "cookie_minutes.txt"
MINUTE_TOKEN = "obcnxrkz6k459k669544228c"
BASE = "https://cunkebao.feishu.cn/minutes/api"
def _cookie_from_browser() -> str:
"""从本机默认/常用浏览器读取飞书 Cookiecunkebao 或 .feishu.cn多浏览器谁有就用谁"""
try:
import browser_cookie3
# 先试子域,再试父域(.feishu.cn 可能带 session
for domain in ("cunkebao.feishu.cn", "feishu.cn", ".feishu.cn"):
loaders = [
browser_cookie3.safari,
browser_cookie3.chrome,
browser_cookie3.chromium,
browser_cookie3.firefox,
browser_cookie3.edge,
]
for loader in loaders:
try:
cj = loader(domain_name=domain)
parts = [f"{c.name}={c.value}" for c in cj]
s = "; ".join(parts)
if len(s) > 100:
return s
except Exception:
continue
except ImportError:
pass
return ""
def get_cookie():
cookie = os.environ.get("FEISHU_MINUTES_COOKIE", "").strip()
if cookie and "PASTE_YOUR" not in cookie:
return cookie
if COOKIE_FILE.exists():
raw = COOKIE_FILE.read_text(encoding="utf-8", errors="ignore").strip().splitlines()
for line in raw:
line = line.strip()
if line and not line.startswith("#") and "PASTE_YOUR" not in line:
return line
cookie = _cookie_from_browser()
if cookie:
return cookie
return ""
def get_csrf(cookie: str) -> str:
for name in ("bv_csrf_token=", "minutes_csrf_token="):
i = cookie.find(name)
if i != -1:
start = i + len(name)
end = cookie.find(";", start)
if end == -1:
end = len(cookie)
return cookie[start:end].strip()
return ""
def make_headers(cookie: str, use_github_referer: bool = True):
"""与 GitHub bingsanyu/feishu_minutes 一致meetings 域用 referer minutes/me + bv-csrf-token。"""
# GitHub 使用referer https://meetings.feishu.cn/minutes/me
referer = "https://meetings.feishu.cn/minutes/me" if use_github_referer else "https://cunkebao.feishu.cn/minutes/"
h = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/113.0.0.0 Safari/537.36",
"cookie": cookie,
"referer": referer,
"content-type": "application/x-www-form-urlencoded",
}
csrf = get_csrf(cookie)
if csrf:
h["bv-csrf-token"] = csrf
return h
def try_export_text(cookie: str, minute_token: str) -> tuple[str | None, str | None]:
"""
调用妙记导出接口(与 GitHub bingsanyu/feishu_minutes 一致):
POST meetings.feishu.cn/minutes/api/exportparams: object_token, format=2, add_speaker, add_timestamp。
先试 meetings.feishu.cn推荐再试 cunkebao.feishu.cn。
返回 (标题或 None, 正文文本)。
"""
payload = {
"object_token": minute_token,
"format": 2,
"add_speaker": "true",
"add_timestamp": "false",
}
# 1) 优先 GitHub 同款meetings.feishu.cn + referer minutes/me
for domain, use_github in (
("https://meetings.feishu.cn/minutes/api/export", True),
("https://cunkebao.feishu.cn/minutes/api/export", False),
):
try:
headers = make_headers(cookie, use_github_referer=use_github)
r = requests.post(domain, params=payload, headers=headers, timeout=20)
r.encoding = "utf-8"
if r.status_code != 200:
continue
text = (r.text or "").strip()
if text.startswith("{") and ("transcript" in text or "content" in text or "text" in text):
try:
j = r.json()
text = (j.get("data") or j).get("content") or (j.get("data") or j).get("text") or j.get("transcript") or text
if isinstance(text, str) and len(text) > 50:
return (None, text)
except Exception:
pass
if text and len(text) > 50 and "PASTE_YOUR" not in text and "<html" not in text.lower()[:200] and "Something went wrong" not in text:
return (None, text)
except Exception:
continue
return (None, None)
def try_get_minute_detail(cookie: str, minute_token: str) -> dict | None:
"""尝试多种 cunkebao 可能的详情接口(获取标题等)"""
headers = make_headers(cookie)
urls_to_try = [
f"{BASE}/minute/detail?object_token={minute_token}",
f"{BASE}/space/minute_detail?object_token={minute_token}",
f"{BASE}/space/list?size=1&space_name=0",
]
for url in urls_to_try:
try:
r = requests.get(url, headers=headers, timeout=15)
if r.status_code != 200:
continue
data = r.json() if r.text.strip().startswith("{") else {}
if data.get("code") == 0 and data.get("data"):
inner = data.get("data")
if isinstance(inner, list) and inner:
for item in inner:
if (item.get("object_token") or item.get("minute_token")) == minute_token:
return item
return inner
if isinstance(data, dict) and ("topic" in data or "minute" in data):
return data.get("minute") or data
except Exception:
continue
return None
def save_txt(output_dir: Path, title: str, body: str, date_str: str = None) -> Path:
date_str = date_str or datetime.now().strftime("%Y%m%d")
safe_title = re.sub(r'[\\/*?:"<>|]', "_", (title or "妙记"))
filename = f"{safe_title}_{date_str}.txt"
path = output_dir / filename
header = f"日期: {date_str}\n标题: {title}\n\n文字记录:\n\n" if title else f"日期: {date_str}\n\n文字记录:\n\n"
path.write_text(header + body, encoding="utf-8")
return path
def main():
if not requests:
print("需要安装 requests: pip install requests")
return 1
cookie = get_cookie()
if not cookie or "PASTE_YOUR" in cookie:
print("未配置有效 Cookie。请")
print(" 1. 打开 https://meetings.feishu.cn/minutes/home或 cunkebao.feishu.cn/minutes/home并登录")
print(" 2. F12 → 网络 → 找到 list?size=20&space_name= 请求 → 复制请求头中的 Cookie需含 bv_csrf_token")
print(" 3. 粘贴到脚本同目录 cookie_minutes.txt 第一行")
print("或使用 GitHub 同款脚本: python3 feishu_minutes_export_github.py <链接> -o <输出目录>")
return 1
url_or_token = (sys.argv[1] if len(sys.argv) > 1 else None) or f"https://cunkebao.feishu.cn/minutes/{MINUTE_TOKEN}"
match = re.search(r"/minutes/([a-zA-Z0-9]+)", url_or_token)
minute_token = match.group(1) if match else MINUTE_TOKEN
output_dir = Path(os.environ.get("FEISHU_MINUTES_OUTPUT", "/Users/karuo/Documents/聊天记录/soul"))
for i, arg in enumerate(sys.argv):
if arg in ("-o", "--output") and i + 1 < len(sys.argv):
output_dir = Path(sys.argv[i + 1]).resolve()
break
output_dir.mkdir(parents=True, exist_ok=True)
print(f"📝 妙记 token: {minute_token}")
print("📡 使用 Cookie 请求妙记导出接口export")
title_from_export, body = try_export_text(cookie, minute_token)
if body:
# 优先用列表接口拿标题和日期
data = try_get_minute_detail(cookie, minute_token)
title = (data.get("topic") or data.get("title") or title_from_export or "妙记").strip()
create_time = data.get("create_time") or data.get("share_time")
if create_time:
try:
ts = int(create_time)
if ts > 1e10:
ts = ts // 1000
date_str = datetime.fromtimestamp(ts).strftime("%Y%m%d")
except Exception:
date_str = datetime.now().strftime("%Y%m%d")
else:
date_str = datetime.now().strftime("%Y%m%d")
path = save_txt(output_dir, title, body, date_str)
print(f"✅ 已保存: {path}")
return 0
print("❌ 导出接口未返回文字Cookie 可能失效或非该妙记所属空间)。请:")
print(" 1. 打开 https://cunkebao.feishu.cn/minutes/home 并搜索该场妙记,确认能打开")
print(" 2. F12 → 网络 → 找到 list 或 export 请求 → 复制请求头 Cookie 到 cookie_minutes.txt")
print(" 或到妙记页「…」→ 导出文字记录,将文件保存到输出目录。")
return 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,110 @@
#!/usr/bin/env python3
"""用 Doubao profile 副本启动浏览器,访问 list 页取 Cookie含 bv_csrf_token再请求导出 104 场并保存。"""
import os
import shutil
import subprocess
import sys
from pathlib import Path
PROFILE_SRC = Path.home() / "Library/Application Support/Doubao/Profile 2"
PROFILE_COPY = Path("/tmp/feishu_doubao_profile_playwright")
OUT_FILE = Path("/Users/karuo/Documents/聊天记录/soul/soul 派对 104场 20260219.txt")
URL_LIST = "https://cunkebao.feishu.cn/minutes/home"
OBJECT_TOKEN = "obcnyg5nj2l8q281v32de6qz"
EXPORT_URL = "https://cunkebao.feishu.cn/minutes/api/export"
def main():
# 1) 复制 profile排除易锁/大目录)
if PROFILE_COPY.exists():
shutil.rmtree(PROFILE_COPY, ignore_errors=True)
PROFILE_COPY.mkdir(parents=True, exist_ok=True)
exclude = {"Cache", "Code Cache", "GPUCache", "Session Storage", "Service Worker", "blob_storage", "LOCK"}
for f in PROFILE_SRC.iterdir():
if f.name in exclude or not f.exists():
continue
try:
dst = PROFILE_COPY / f.name
if f.is_dir():
shutil.copytree(f, dst, ignore=shutil.ignore_patterns("Cache", "Code Cache"), dirs_exist_ok=True)
else:
shutil.copy2(f, dst)
except Exception:
pass
(PROFILE_COPY / "LOCK").unlink(missing_ok=True)
# 2) Playwright 用该 profile 打开 list 页并取 Cookie
try:
from playwright.sync_api import sync_playwright
except ImportError:
print("NO_PLAYWRIGHT", file=sys.stderr)
return 2
import requests
with sync_playwright() as p:
try:
context = p.chromium.launch_persistent_context(
user_data_dir=str(PROFILE_COPY),
headless=True,
channel="chromium",
timeout=30000,
args=["--no-sandbox", "--disable-setuid-sandbox"],
)
except Exception as e:
print("LAUNCH_FAIL", str(e), file=sys.stderr)
return 3
page = context.pages[0] if context.pages else context.new_page()
# 等待 list 接口返回后再取 Cookie服务端会在 list 请求时设置 bv_csrf_token
with page.expect_response(lambda r: "space/list" in r.url or "list" in r.url and r.request.method == "GET") as resp_info:
page.goto(URL_LIST, wait_until="networkidle", timeout=30000)
try:
resp_info.value
except Exception:
pass
page.wait_for_timeout(3000)
cookies = context.cookies()
context.close()
cookie_str = "; ".join([f"{c['name']}={c['value']}" for c in cookies])
bv = next((c["value"] for c in cookies if c.get("name") == "bv_csrf_token" and len(c.get("value", "")) == 36), None)
if not cookie_str or len(cookie_str) < 100:
print("NO_COOKIE", file=sys.stderr)
return 4
headers = {
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36",
"Cookie": cookie_str,
"Referer": "https://cunkebao.feishu.cn/minutes/",
}
if bv:
headers["bv-csrf-token"] = bv
r = requests.post(
EXPORT_URL,
params={"object_token": OBJECT_TOKEN, "format": 2, "add_speaker": "true", "add_timestamp": "false"},
headers=headers,
timeout=20,
)
r.encoding = "utf-8"
if r.status_code != 200:
print("EXPORT_HTTP", r.status_code, len(r.text), file=sys.stderr)
return 5
text = (r.text or "").strip()
if not text or len(text) < 100 or "Something went wrong" in text or (text.startswith("{") and "error" in text.lower()):
print("EXPORT_BODY", text[:200], file=sys.stderr)
return 6
if text.startswith("{"):
try:
j = r.json()
text = (j.get("data") or j.get("content") or "")
if isinstance(text, dict):
text = text.get("content") or text.get("text") or ""
except Exception:
pass
OUT_FILE.parent.mkdir(parents=True, exist_ok=True)
OUT_FILE.write_text("日期: 20260219\n标题: soul 派对 104场 20260219\n\n文字记录:\n\n" + text, encoding="utf-8")
print("OK", str(OUT_FILE))
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -0,0 +1,3 @@
# 场次 标题 object_token
# 配置 cookie_minutes.txt 后重新执行本脚本即可只做导出
104 soul 派对 104场 20260219 obcnyg5nj2l8q281v32de6qz

View File

@@ -0,0 +1,19 @@
# 场次 标题 object_token
# 配置 cookie_minutes.txt 后重新执行本脚本即可只做导出
90 soul 派对90场 20250203 obcnng3c7z52839e3gt5e945
91 02:35-08:29 | soul 派对 91场 20260204 obcnoab15us275cq2w65l67p
91 soul 派对 91场 20260204 obcnn63j1a1833c8p3587x47
92 soul 派对 92场 20260205 obcnou5s4r4ydj2ok8n4w61w
93 soul 派对 93场 20260206 obcnpjlsm1l3yl1989rn36ai
94 soul 派对 94场 20260207 obcnp8hbwqop55772892p3y1
95 soul 派对 95场 20260209 obcnrkc8bagf1gx2bo4fim9j
96 soul 派对 96场 20260210 obcnr93ne88p8z19777cz8u6
97 soul 派对 97场 20260211 obcnsyr6p38y4223q5548429
98 soul 派对 98场 20260212 obcntntr26cj38pp5i1vvdhj
99 soul 派对 99场 20260213 obcnucnci1m2z422987lcb75
100 soul 派对 100场 20260214 obcnu1rfr452595c6l51a7e1
100 ip切片 目标派对51-100场 会员社群200人搭建的视频会议 obcnleg937yi97h4xc311829
100 ip切片 目标派对51-100场 会员社群200人搭建的视频会议 obcn82umur66e1f92qtcdews
100 ip切片 目标派对51-100场 会员社群200人搭建的视频会议 obcntns4t5ufqgo34y57d39k
101 soul 派对 101场 20260216 obcnwd5r563dopj1l3lp5pf1
102 soul 派对 102场 20260217 obcnw4122sy6yeic79tpzbuo

View File

@@ -0,0 +1 @@
https://cunkebao.feishu.cn/minutes/obcnxrkz6k459k669544228c

View File

@@ -0,0 +1,30 @@
#!/bin/bash
# 一键:先启动豆包浏览器(远程调试),再导出 104 场妙记并打开文件。
# 会先退出豆包再以调试端口重启,导出完成后可正常再打开豆包使用。
set -e
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT_FILE="/Users/karuo/Documents/聊天记录/soul/soul 派对 104场 20260219.txt"
echo "正在退出豆包浏览器…"
osascript -e 'quit app "Doubao"' 2>/dev/null || true
sleep 3
echo "正在以远程调试方式启动豆包9222…"
# 直接调用浏览器可执行文件才能传参;用 nohup 后台运行
"/Applications/Doubao.app/Contents/Helpers/Doubao Browser.app/Contents/MacOS/Doubao Browser" --remote-debugging-port=9222 &
BGPID=$!
echo "等待豆包就绪(约 12 秒)…"
sleep 12
# 检查 9222 是否在监听
if ! nc -z 127.0.0.1 9222 2>/dev/null; then
echo "警告9222 未就绪,继续尝试导出…"
fi
cd "$SCRIPT_DIR"
if python3 cdp_104_export.py 2>/dev/null; then
echo "✅ 104 场已保存"
open "$OUT_FILE"
exit 0
fi
echo "导出未成功。请在本机浏览器打开 https://cunkebao.feishu.cn/minutes/obcnyg5nj2l8q281v32de6qz 手动「导出文字记录」后保存到:"
echo " $OUT_FILE"
open "https://cunkebao.feishu.cn/minutes/obcnyg5nj2l8q281v32de6qz"
exit 1

View File

@@ -0,0 +1,21 @@
#!/bin/bash
# 一键下载 104 场妙记文字到 soul 目录。配置好 cookie_minutes.txt 后执行本脚本即可。
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT="/Users/karuo/Documents/聊天记录/soul"
cd "$SCRIPT_DIR"
PY="python3"
[ -x "$SCRIPT_DIR/.venv/bin/python" ] && PY="$SCRIPT_DIR/.venv/bin/python"
$PY download_soul_minutes_101_to_103.py --from 104 --to 104
if [ $? -eq 0 ]; then
if ls "$OUT"/soul*104*20260219*.txt 1>/dev/null 2>&1; then
echo "✅ 104 场已保存到: $OUT"
exit 0
fi
fi
echo ""
echo "未拿到 104 场正文(导出需含 bv_csrf_token 的 Cookie。请"
echo " 1. 打开 https://cunkebao.feishu.cn/minutes/home → F12 → 网络 → 找到 list?size=20& 请求"
echo " 2. 复制该请求头中的完整 Cookie → 粘贴到 $SCRIPT_DIR/cookie_minutes.txt 第一行"
echo " 3. 再执行: bash $SCRIPT_DIR/下载104场.sh"
exit 1

View File

@@ -0,0 +1,9 @@
#!/bin/bash
# 纯命令行、不用浏览器用飞书企业身份tenant_access_token拉取 104 场妙记并保存到 soul 目录。
# 凭证:脚本内置 APP_ID/APP_SECRET或环境变量 FEISHU_APP_ID / FEISHU_APP_SECRET。
# 需在飞书开放平台为该应用开通「查看妙记文件」「导出妙记转写的文字内容」,且可访问数据范围包含该妙记(或选「全部」)。
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
OUT="/Users/karuo/Documents/聊天记录/soul"
cd "$SCRIPT_DIR"
python3 fetch_feishu_minutes.py "https://cunkebao.feishu.cn/minutes/obcnyg5nj2l8q281v32de6qz" --tenant-only --output "$OUT"
exit $?

View File

@@ -1,7 +1,7 @@
---
name: 飞书管理
description: 飞书日志/文档自动写入与知识库管理
triggers: 飞书日志、写入飞书、飞书知识库
triggers: 飞书日志、写入飞书、飞书知识库、飞书运营报表、派对效果数据、104场写入
owner: 水桥
group: 水
version: "1.0"
@@ -186,6 +186,43 @@ python3 /Users/karuo/Documents/个人/卡若AI/02_卡人/飞书管理/s
---
## 飞书运营报表Soul 派对效果数据)
将 Soul 派对效果数据按**场次**写入飞书「火:运营报表」表格,**竖列**填入对应日期/场次列。
### 填写规则(以后都按此逻辑)
| 规则 | 说明 |
|:---|:---|
| **按数字填写** | 时长、推流、进房、互动、礼物、灵魂力、增加关注、最高在线 等均按**数字类型**写入,不按文本,便于表格公式与图表 |
| **不填比率三项** | 推流进房率、1分钟进多少人、加微率 由表格内公式自动计算,**导入时不填** |
| **只填前 10 项** | 主题、时长、Soul推流人数、进房人数、人均时长、互动数量、礼物、灵魂力、增加关注、最高在线 |
| **主题(标题)** | 从聊天记录提炼,**≤12 字**,须含**具体内容、干货与数值**(如:号商几毛卖十几 日销两万) |
| **竖列写入** | 表格结构为 A 列指标名、各列为日期/场次,数据写入该场次列的第 312 行(竖列) |
### 一键写入
```bash
# 写入 104 场(默认)
python3 /Users/karuo/Documents/个人/卡若AI/02_卡人/水桥_平台对接/飞书管理/脚本/soul_party_to_feishu_sheet.py
# 指定场次
python3 .../soul_party_to_feishu_sheet.py 103
python3 .../soul_party_to_feishu_sheet.py 104
```
### 表格与配置
| 项目 | 值 |
|:---|:---|
| 运营报表 | https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=7A3Cy9 |
| 工作表 | 2026年2月 soul 书卡若创业派对sheetId=7A3Cy9 |
| Token | 使用同目录 `.feishu_tokens.json`(与 auto_log 共用) |
新增场次时在脚本内 `ROWS` 中增加对应场次与 10 项数据即可。
---
## 飞书项目(玩值电竞 · 存客宝)
将玩值电竞 30 天/90 天甘特图任务同步到飞书项目需求管理。
@@ -215,9 +252,10 @@ python3 scripts/wanzhi_feishu_project_sync.py
└── scripts/
├── auto_log.py # 一键日志脚本(推荐)
├── write_today_custom.py # 自定义今日内容写入(写入后自动打开飞书)
├── soul_party_to_feishu_sheet.py # 飞书运营报表Soul 派对效果数据(按场次竖列、数字、不填比率)
├── wanzhi_feishu_project_sync.py # 玩值电竞→飞书项目任务同步
├── feishu_api.py # 后端服务
├── feishu_video_clip.py # 视频智能切片(新增)
├── feishu_video_clip.py # 视频智能切片
├── feishu_video_clip_README.md # 切片工具说明
└── .feishu_tokens.json # Token存储
```

View File

@@ -1,6 +1,6 @@
{
"access_token": "u-7FEQ3dsVpbBVy1HPRK6OCul5mUMBk1gNMoaaENM00Byi",
"refresh_token": "ur-58aJUppSxdOFYJhaRlNUaMl5miOBk1UNpEaaIMQ00xD6",
"access_token": "u-60GEtjudJdBVYbF.wQf8NJl5mqW5k1gNhoaaEMQ00wOi",
"refresh_token": "ur-5ie7_NeKh5aEV4nExO0m1nl5kWMBk1gPgUaaIQM00By6",
"name": "飞书用户",
"auth_time": "2026-02-17T10:27:47.958573"
}

View File

@@ -0,0 +1,274 @@
#!/usr/bin/env python3
"""
飞书运营报表 · Soul 派对效果数据写入(按场次竖列、数字类型、不填比率)
- 只填前 10 项主题、时长、Soul推流人数、进房人数、人均时长、互动数量、礼物、灵魂力、增加关注、最高在线
- 推流进房率、1分钟进多少人、加微率 由表格公式自动计算,导入时不填
- 数值按数字类型写入(非文本),便于表格公式与图表
"""
import os
import sys
import json
import requests
from urllib.parse import quote
# 卡若AI 飞书 Token 与 API
FEISHU_SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
TOKEN_FILE = os.path.join(FEISHU_SCRIPT_DIR, '.feishu_tokens.json')
WIKI_NODE_OR_SPREADSHEET_TOKEN = os.environ.get('FEISHU_SPREADSHEET_TOKEN', 'wikcnIgAGSNHo0t36idHJ668Gfd')
SHEET_ID = os.environ.get('FEISHU_SHEET_ID', '7A3Cy9')
# 写入列数:仅前 10 项(比率三项不填,表内公式自动算)
EFFECT_COLS = 10
# 各场效果数据主题≤12字且含干货与数值、时长、推流、进房…— 比率不写入
ROWS = {
'96': [ '', 0, 0, 0, 0, 0, 0, 0, 0, 0 ], # 96场无记录占位有数据后替换
'97': [ '', 0, 0, 0, 0, 0, 0, 0, 0, 0 ], # 97场无记录占位
'98': [ '', 0, 0, 0, 0, 0, 0, 0, 0, 0 ], # 98场无记录占位
'99': [ '', 116, 16976, 208, 0, 0, 4, 166, 12, 39 ], # 99场派对已关闭截图
'100': [ '', 0, 0, 0, 0, 0, 0, 0, 0, 0 ], # 100场无记录占位
'103': [ '号商几毛卖十几 日销两万', 155, 46749, 545, 7, 34, 1, 8, 13, 47 ],
'104': [ 'AI创业最赚钱一月分享', 140, 36221, 367, 7, 49, 0, 0, 11, 38 ],
}
def load_token():
if not os.path.exists(TOKEN_FILE):
print('❌ 未找到飞书 Token 文件:', TOKEN_FILE)
print('请先运行一次 auto_log.py 或完成飞书授权。')
return None
with open(TOKEN_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
return data.get('access_token')
def refresh_and_load_token():
"""若 token 过期,尝试用 refresh_token 刷新后返回新 token"""
if not os.path.exists(TOKEN_FILE):
return None
with open(TOKEN_FILE, 'r', encoding='utf-8') as f:
data = json.load(f)
refresh = data.get('refresh_token')
if not refresh:
return data.get('access_token')
app_id = os.environ.get('FEISHU_APP_ID', 'cli_a48818290ef8100d')
app_secret = os.environ.get('FEISHU_APP_SECRET', 'dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4')
r = requests.post(
'https://open.feishu.cn/open-apis/auth/v3/app_access_token/internal',
json={'app_id': app_id, 'app_secret': app_secret},
timeout=10
)
app_token = (r.json() or {}).get('app_access_token')
if not app_token:
return data.get('access_token')
r2 = requests.post(
'https://open.feishu.cn/open-apis/authen/v1/oidc/refresh_access_token',
headers={'Authorization': f'Bearer {app_token}', 'Content-Type': 'application/json'},
json={'grant_type': 'refresh_token', 'refresh_token': refresh},
timeout=10
)
out = r2.json()
if out.get('code') == 0 and out.get('data', {}).get('access_token'):
new_token = out['data']['access_token']
data['access_token'] = new_token
data['refresh_token'] = out['data'].get('refresh_token', refresh)
with open(TOKEN_FILE, 'w', encoding='utf-8') as f:
json.dump(data, f, ensure_ascii=False, indent=2)
return new_token
return data.get('access_token')
def get_sheet_meta(access_token, spreadsheet_token):
"""获取表格下的工作表列表,返回第一个 sheet 的 sheet_id用于 range"""
url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/metainfo'
r = requests.get(
url,
headers={'Authorization': f'Bearer {access_token}'},
timeout=15,
)
if r.status_code != 200:
return None
body = r.json()
if body.get('code') != 0:
return None
sheets = (body.get('data') or {}).get('sheets') or []
if not sheets:
return None
return sheets[0].get('sheetId') or sheets[0].get('title') or SHEET_ID
def read_sheet_range(access_token, spreadsheet_token, range_str):
"""读取表格范围,返回 values 或 None"""
url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values/{quote(range_str, safe="")}'
r = requests.get(
url,
headers={'Authorization': f'Bearer {access_token}'},
timeout=15,
)
if r.status_code != 200:
return None, r.status_code, r.json()
body = r.json()
if body.get('code') != 0:
return None, r.status_code, body
vals = (body.get('data') or {}).get('valueRange', {}).get('values') or []
return vals, r.status_code, body
def write_sheet_row(access_token, spreadsheet_token, sheet_id, values):
"""向飞书电子表格追加一行。range 用 sheet_id 或 工作表名"""
url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values_append'
range_str = f"{sheet_id}!A1:M1"
payload = {
'valueRange': {
'range': range_str,
'values': [values],
},
'insertDataOption': 'INSERT_ROWS',
}
r = requests.post(
url,
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
},
json=payload,
timeout=15,
)
return r.status_code, r.json()
def update_sheet_range(access_token, spreadsheet_token, range_str, values, value_input_option='RAW'):
"""向指定 range 写入/覆盖数据。values 二维数组value_input_option=RAW 按数字写入不转文本。"""
url = f'https://open.feishu.cn/open-apis/sheets/v2/spreadsheets/{spreadsheet_token}/values'
params = {'valueInputOption': value_input_option}
payload = {'valueRange': {'range': range_str, 'values': values}}
r = requests.put(
url,
params=params,
headers={
'Authorization': f'Bearer {access_token}',
'Content-Type': 'application/json',
},
json=payload,
timeout=15,
)
try:
body = r.json()
except Exception:
body = {'code': -1, 'msg': (r.text or '')[:200]}
return r.status_code, body
def _col_letter(n):
"""0->A, 1->B, ..., 25->Z, 26->AA"""
s = ''
while True:
s = chr(65 + n % 26) + s
n = n // 26
if n <= 0:
break
return s
def _to_cell_value(v):
"""主题可空/字符串其余按数字写入int/float便于表格公式。"""
if v == '' or v is None:
return ''
if isinstance(v, (int, float)):
return int(v) if isinstance(v, float) and v == int(v) else v
try:
return int(v)
except (ValueError, TypeError):
try:
return float(v)
except (ValueError, TypeError):
return str(v)
def main():
session = (sys.argv[1] if len(sys.argv) > 1 else '104').strip()
row = ROWS.get(session)
if not row:
print('❌ 未知场次,可用: 96, 97, 98, 99, 100, 103, 104')
sys.exit(1)
token = load_token() or refresh_and_load_token()
if not token:
sys.exit(1)
# 只取前 10 项,并按数字类型写入(主题可为空字符串)
raw = (row + [None] * EFFECT_COLS)[:EFFECT_COLS]
values = [_to_cell_value(raw[0])] + [_to_cell_value(raw[i]) for i in range(1, EFFECT_COLS)]
spreadsheet_token = WIKI_NODE_OR_SPREADSHEET_TOKEN
sheet_id = SHEET_ID
range_read = f"{sheet_id}!A1:Z30"
vals, read_code, read_body = read_sheet_range(token, spreadsheet_token, range_read)
# 表格结构第1行表头(2月、1、19)第2行「一、效果数据」+「104场」在某一列A列是指标名(主题、时长...),数据填在 104场 那一列的 3~15 行
target_col_0based = None
if vals and len(vals) >= 2:
for row_idx in (1, 0): # 先查第2行再第1行
row_cells = vals[row_idx] if row_idx < len(vals) else []
for col_idx, cell in enumerate(row_cells):
if f"{session}" in str(cell).strip():
target_col_0based = col_idx
break
if target_col_0based is not None:
break
if read_code != 200 or (read_body.get('code') not in (0, None)) and not vals:
print('⚠️ 读取表格失败:', read_code, read_body.get('msg', read_body))
if target_col_0based is not None:
col_letter = _col_letter(target_col_0based)
# 竖列写入T3:T15 一列values 为 [[v1],[v2],...[v13]]
range_col = f"{sheet_id}!{col_letter}3:{col_letter}{2 + len(values)}"
values_vertical = [[v] for v in values]
code, body = update_sheet_range(token, spreadsheet_token, range_col, values_vertical)
if code == 200 and body.get('code') == 0:
print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter}3:{col_letter}{2+len(values)},共{len(values)}格)')
return
if code == 401 or body.get('code') in (99991677, 99991663):
token = refresh_and_load_token()
if token:
code, body = update_sheet_range(token, spreadsheet_token, range_col, values_vertical)
if code == 200 and body.get('code') == 0:
print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter}')
return
# 单列多行若报 90202(columns of value>range),则逐格写入
err = body.get('code')
if err == 90202 or (err and 'range' in str(body.get('msg', '')).lower()):
all_ok = True
for r in range(3, 3 + len(values)):
one_cell = f"{sheet_id}!{col_letter}{r}"
code, body = update_sheet_range(token, spreadsheet_token, one_cell, [[values[r - 3]]])
if code != 200 or body.get('code') not in (0, None):
if code == 401 or body.get('code') in (99991677, 99991663):
token = refresh_and_load_token()
if token:
code, body = update_sheet_range(token, spreadsheet_token, one_cell, [[values[r - 3]]])
if code != 200 or body.get('code') not in (0, None):
all_ok = False
print('❌ 写入单元格失败:', one_cell, code, body)
break
if all_ok:
print(f'✅ 已写入飞书表格:{session}场 效果数据(竖列 {col_letter}3:{col_letter}{2+len(values)} 逐格)')
return
print('❌ 按列更新失败:', code, body)
code, body = write_sheet_row(token, spreadsheet_token, sheet_id, values)
if code == 200 and (body.get('code') == 0 or body.get('code') is None):
print(f'✅ 已追加一行:{session}场 效果数据')
return
if code == 401 or body.get('code') in (99991677, 99991663):
token = refresh_and_load_token()
if token:
code, body = write_sheet_row(token, spreadsheet_token, sheet_id, values)
if code == 200 and (body.get('code') == 0 or body.get('code') is None):
print(f'✅ 已追加一行:{session}场 效果数据')
return
print('❌ 写入失败:', code, body)
if body.get('code') in (99991663, 99991677):
print('Token 已过期,请重新授权飞书后再试。')
elif body.get('code') in (403, 404, 1254101):
print('若表格在 Wiki 内,请打开该电子表格→分享→复制链接,链接里 /sheets/ 后的一串为 spreadsheet_token执行')
print(' export FEISHU_SPREADSHEET_TOKEN=该token')
sys.exit(1)
if __name__ == '__main__':
main()

View File

@@ -50,7 +50,7 @@
| W05 | 需求拆解与计划制定 | 水泉 | 需求拆解、任务分析 | `02_卡人/水泉_规划拆解/需求拆解与计划制定/SKILL.md` | 大需求拆成可执行步骤 |
| W06 | 任务规划 | 水泉 | 任务规划、制定计划 | `02_卡人/水泉_规划拆解/任务规划/SKILL.md` | 制定执行计划与排期 |
| W07 | 飞书管理 | 水桥 | 飞书日志、写入飞书 | `02_卡人/水桥_平台对接/飞书管理/SKILL.md` | 飞书日志/文档自动化 |
| W08 | 智能纪要 | 水桥 | 会议纪要、产研纪要 | `02_卡人/水桥_平台对接/智能纪要/SKILL.md` | 会议录音转结构化纪要 |
| W08 | 智能纪要 | 水桥 | 会议纪要、产研纪要、**飞书妙记、飞书链接、妙记下载、第几场、指定场次、批量下载妙记、cunkebao.feishu.cn、meetings.feishu.cn/minutes** | `02_卡人/水桥_平台对接/智能纪要/SKILL.md` | 会议录音转结构化纪要;飞书妙记识别与下载(单条/批量),完毕用复盘格式回复 |
| W09 | 小程序管理 | 水桥 | 小程序、微信小程序 | `02_卡人/水桥_平台对接/小程序管理/SKILL.md` | 微信小程序发布与维护 |
## 木组 · 卡木(产品内容创造)

View File

@@ -42,7 +42,8 @@
| 项 | 值 |
|----|-----|
| APPID | `1251077262` |
| 密钥 | `AKIDjc6yO3nPeOuK2OKsJPBBVbTiiz0aPNHl` |
| SecretId密钥 | `AKIDjc6yO3nPeOuK2OKsJPBBVbTiiz0aPNHl` |
| SecretKey | (请到 控制台 → 访问管理 → API密钥 获取并填写,用于账单等 API |
### 阿里云
| 项 | 值 |

View File

@@ -25,3 +25,4 @@
| 2026-02-18 06:29:17 | 🔄 卡若AI 同步 2026-02-18 06:29 | 更新GitHub Actions、运营中枢工作台 | 排除 >20MB: 5 个 |
| 2026-02-18 06:41:56 | 🔄 卡若AI 同步 2026-02-18 06:41 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 5 个 |
| 2026-02-18 14:26:12 | 🔄 卡若AI 同步 2026-02-18 14:26 | 更新:运营中枢工作台 | 排除 >20MB: 5 个 |
| 2026-02-18 14:43:00 | 🔄 卡若AI 同步 2026-02-18 14:42 | 更新:运营中枢工作台 | 排除 >20MB: 5 个 |

View File

@@ -28,3 +28,4 @@
| 2026-02-18 06:29:17 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-18 06:29 | 更新GitHub Actions、运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-18 06:41:56 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-18 06:41 | 更新:水桥平台对接、运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-18 14:26:12 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-18 14:26 | 更新:运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-02-18 14:43:00 | 成功 | 成功 | 🔄 卡若AI 同步 2026-02-18 14:42 | 更新:运营中枢工作台 | 排除 >20MB: 5 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |