From 0ef22121607f902940659fd61cf4216bba071ea7 Mon Sep 17 00:00:00 2001 From: karuo Date: Mon, 23 Mar 2026 09:48:28 +0800 Subject: [PATCH] =?UTF-8?q?=F0=9F=94=84=20=E5=8D=A1=E8=8B=A5AI=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=202026-03-23=2009:48=20|=20=E6=9B=B4=E6=96=B0?= =?UTF-8?q?=EF=BC=9ACursor=E8=A7=84=E5=88=99=E3=80=81=E9=87=91=E4=BB=93?= =?UTF-8?q?=E3=80=81=E6=B0=B4=E6=A1=A5=E5=B9=B3=E5=8F=B0=E5=AF=B9=E6=8E=A5?= =?UTF-8?q?=E3=80=81=E5=8D=A1=E6=9C=A8=E3=80=81=E7=81=AB=E7=82=AC=E3=80=81?= =?UTF-8?q?=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E3=80=81=E8=BF=90=E8=90=A5?= =?UTF-8?q?=E4=B8=AD=E6=9E=A2=E5=8F=82=E8=80=83=E8=B5=84=E6=96=99=E3=80=81?= =?UTF-8?q?=E8=BF=90=E8=90=A5=E4=B8=AD=E6=9E=A2=E5=B7=A5=E4=BD=9C=E5=8F=B0?= =?UTF-8?q?=20|=20=E6=8E=92=E9=99=A4=20>20MB:=2011=20=E4=B8=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .cursor/rules/karuo-ai.mdc | 3 +- .cursor/skills/kalu-party-yuning-ops/SKILL.md | 2 +- .../聊天记录管理/fallback/recent_chats_fallback.json | 34 +- .../飞书管理/复盘总结发飞书群_SKILL.md | 18 +- .../飞书管理/脚本/.feishu_tokens.json | 6 +- .../木叶_视频内容/多平台分发/SKILL.md | 35 +- .../多平台分发/cookies/视频号_cookies.json | 104 +++++- .../多平台分发/脚本/cookie_manager.py | 29 ++ .../多平台分发/脚本/distribute_all.py | 51 ++- .../木叶_视频内容/多平台分发/脚本/publish_log.json | 21 ++ .../多平台分发/脚本/video_metadata.py | 9 +- .../木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md | 4 +- .../视频切片/场次稿/第127场_20260318_highlights.json | 101 ++--- .../视频切片/场次稿/第128场_20260319_highlights.json | 10 + .../视频切片/脚本/build_127_highlights_from_srt.py | 145 ++++++++ .../木叶_视频内容/视频切片/脚本/soul_enhance.py | 157 ++++++-- .../视频号发布/REFERENCE_开放能力_数据与集成.md | 151 ++++++++ .../木叶_视频内容/视频号发布/SKILL.md | 8 +- .../木叶_视频内容/视频号发布/credentials/README.md | 45 +++ .../视频号发布/credentials/open_platform.env.example | 5 + .../视频号发布/脚本/channels_api_publish.py | 24 +- .../视频号发布/脚本/channels_login.py | 348 +++++++++++++----- .../视频号发布/脚本/channels_open_fetch.py | 99 +++++ .../视频号发布/脚本/channels_storage_state.json | 104 +++++- .../视频号发布/脚本/channels_token.json | 11 +- .../火炬_全栈消息/全栈开发/全栈测试/SKILL.md | 2 +- 运营中枢/使用手册/对话沉淀与优化规则.md | 4 +- 运营中枢/参考资料/卡若AI_Mongo对话留存闭环.md | 2 +- 运营中枢/工作台/gitea_push_log.md | 1 + 运营中枢/工作台/代码管理.md | 1 + 运营中枢/工作台/平台集成配置.md | 4 +- 运营中枢/工作台/飞书复盘总结发群说明.md | 16 +- 32 files changed, 1285 insertions(+), 269 deletions(-) create mode 100644 03_卡木(木)/木叶_视频内容/视频切片/场次稿/第128场_20260319_highlights.json create mode 100644 03_卡木(木)/木叶_视频内容/视频切片/脚本/build_127_highlights_from_srt.py create mode 100644 03_卡木(木)/木叶_视频内容/视频号发布/REFERENCE_开放能力_数据与集成.md create mode 100644 03_卡木(木)/木叶_视频内容/视频号发布/credentials/README.md create mode 100644 03_卡木(木)/木叶_视频内容/视频号发布/credentials/open_platform.env.example create mode 100644 03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_open_fetch.py diff --git a/.cursor/rules/karuo-ai.mdc b/.cursor/rules/karuo-ai.mdc index 748a29b3..852c14f6 100644 --- a/.cursor/rules/karuo-ai.mdc +++ b/.cursor/rules/karuo-ai.mdc @@ -37,7 +37,8 @@ alwaysApply: true - **每日对话收集**(每天仅一次):检查 `last_chat_collect_date.txt` → 非今日则执行 `python collect_chat_daily.py` - **Gitea 同步**:对话结束前有文件变更时执行 `bash 自动同步.sh`(单文件 >20MB 不提交) -- **飞书复盘发群**:对话完成后,执行 `python3 send_review_to_feishu_webhook.py "简洁复盘"`(≤500 字) +- **飞书复盘发群(默认关闭)**:**不**在每次对话结束时自动执行 `send_review_to_feishu_webhook.py`。仅当**同时**满足:① **捆绑明确**——用户本轮**明说**要发(如「复盘发飞书」「发群」),或**正在执行的某条 `SKILL.md` 步骤**写明本步须发飞书复盘(Agent 须在执行前在正文点明:**依据哪份 Skill、哪一步**);② **工作区绑定**——本轮主任务落在 **`/Users/karuo/Documents/个人/卡若AI`** 仓库内(多根工作区时以**当前对话主要改动的仓库**为准,主战场不是卡若AI 则**不发**,避免误推)。满足后再执行: + `python3 "/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本/send_review_to_feishu_webhook.py" "简洁复盘"`(≤500 字) - **终端命令**:一律直接执行,不询问,50 字内说明后执行 - **常规操作**:优先命令行 + 复用现成流程,不提问 - **默认零提问(强制)**:开发、改需求、跑脚本、查日志、部署类任务,**禁止**向卡若发起「是否执行」「要不要我…」「请选一个」等确认式提问。缺信息时:**先读仓库配置 / 代码 / 环境变量 / 文档** → 合理默认 → **直接做完**。仅当 **客观上无法代劳** 时极简说明缺什么(如:本机短信验证码、支付密码、用户明文密钥未配置、明确不可逆删除且规范要求人工确认)。 diff --git a/.cursor/skills/kalu-party-yuning-ops/SKILL.md b/.cursor/skills/kalu-party-yuning-ops/SKILL.md index 3087d477..14f9b076 100644 --- a/.cursor/skills/kalu-party-yuning-ops/SKILL.md +++ b/.cursor/skills/kalu-party-yuning-ops/SKILL.md @@ -40,7 +40,7 @@ Soul发到素材库、录屏文字、团队管理、运营分发、飞书写群 | Excel→飞书表格→日报图 | `02_卡人(水)/水桥_平台对接/飞书管理/Excel表格与日报_SKILL.md` | W13 | | 飞书 JSON 块格式 | `02_卡人(水)/水桥_平台对接/飞书管理/飞书JSON格式_SKILL.md` | W16 | | 卡猫 / 婼瑄复盘发群 | `02_卡人(水)/水桥_平台对接/飞书管理/卡猫复盘/SKILL.md` | W14 | -| 复盘 webhook 脚本 | `02_卡人(水)/水桥_平台对接/飞书管理/脚本/send_review_to_feishu_webhook.py` | — | +| 复盘 webhook 脚本 | `02_卡人(水)/水桥_平台对接/飞书管理/脚本/send_review_to_feishu_webhook.py` | 按需;对话结束**不**默认调用,见 `karuo-ai.mdc` | | Gitea / Git 推送 | `01_卡资(金)/金仓_存储备份/Gitea管理/SKILL.md` | G02 | | 语音转写纠错(闽南口音等) | `02_卡人(水)/水溪_整理归档/语音转写纠错/SKILL.md` | W03b | | 切片脚本 soul_enhance | `03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py` | 联动 M01 | diff --git a/01_卡资(金)/金仓_存储备份/聊天记录管理/fallback/recent_chats_fallback.json b/01_卡资(金)/金仓_存储备份/聊天记录管理/fallback/recent_chats_fallback.json index c15ed131..c2f2c38d 100644 --- a/01_卡资(金)/金仓_存储备份/聊天记录管理/fallback/recent_chats_fallback.json +++ b/01_卡资(金)/金仓_存储备份/聊天记录管理/fallback/recent_chats_fallback.json @@ -1,7 +1,39 @@ { -"updated": "2026-03-22T13:21:51.214081+00:00", +"updated": "2026-03-23T01:48:25.039999+00:00", "conversations": [ { +"对话ID": "98abcf81-b237-44b7-af5a-dfe6d5bcb0ed", +"名称": "卡路亚复盘推送功能讨论", +"项目": "飞书", +"首条消息": "卡路亚不默认把复盘推到指定的群内。不强制,每一次对话都推,现在先取消这个功能,那已推的一定是有捆绑的业务,捆绑确定清楚在这个工作区才推", +"创建时间": "2026-03-23T01:46:48.301000+00:00", +"消息数量": 43 +}, +{ +"对话ID": "8af5b60d-31d1-4486-b8e3-4f59cff74c8d", +"名称": "这个是视频号的,那个直播的,视频号直播的那个接口。直播的这个接口,看一下这个直播的接口有哪一些?我可以直接直播接口的开放能力,看一下这个开放能力具体能做些什么事情,以及咱们在咱们这个项目里面,在视频号,smart 这个项目里面能做哪一些操作?", +"项目": "微信管理", +"首条消息": "这个是视频号的,那个直播的,视频号直播的那个接口。直播的这个接口,看一下这个直播的接口有哪一些?我可以直接直播接口的开放能力,看一下这个开放能力具体能做些什么事情,以及咱们在咱们这个项目里面,在视频号,smart 这个项目里面能做哪一些操作?", +"创建时间": "2026-03-23T01:14:52.624000+00:00", +"消息数量": 81 +}, +{ +"对话ID": "6a7c9cba-36ab-423a-a3a4-178da1908343", +"名称": "微信豆充值网页发一个给我", +"项目": "微信管理", +"首条消息": "微信豆充值网页发一个给我", +"创建时间": "2026-03-23T00:27:19.558000+00:00", +"消息数量": 22 +}, +{ +"对话ID": "d932a36a-3023-425a-971b-bddaf36bebd8", +"名称": "soul 身份证注销 现在一张身份证只能使用3个,帮我看一下注销的一个形式,我的是我的号还被封了,怎么样解决这个无法登录、无法注销,怎么操作", +"项目": "Soul创业", +"首条消息": "soul 身份证注销 现在一张身份证只能使用3个,帮我看一下注销的一个形式,我的是我的号还被封了,怎么样解决这个无法登录、无法注销,怎么操作", +"创建时间": "2026-03-22T21:31:52.443000+00:00", +"消息数量": 10 +}, +{ "对话ID": "9f39025b-f695-4d7b-aff7-c124226e307e", "名称": "Exploration of soul project codebase", "项目": "Soul创业", diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/复盘总结发飞书群_SKILL.md b/02_卡人(水)/水桥_平台对接/飞书管理/复盘总结发飞书群_SKILL.md index 9cf44931..c490f594 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/复盘总结发飞书群_SKILL.md +++ b/02_卡人(水)/水桥_平台对接/飞书管理/复盘总结发飞书群_SKILL.md @@ -1,16 +1,16 @@ --- name: 复盘总结发飞书群 -description: 每次对话完成后,将简洁复盘总结发到指定飞书群(webhook);长对话必发,每次完成的任务都发。卡若AI 强制规则之一。 +description: 按需将简洁复盘发到飞书群(webhook)。默认不自动发;用户明说「复盘发飞书」或某 SKILL 步骤书面要求时才发,且须以卡若AI 工作区为主战场。 triggers: 复盘发飞书、飞书复盘、对话总结发群、复盘总结发群、FEISHU_REVIEW_WEBHOOK owner: 水桥 group: 水 -version: "1.0" -updated: "2026-03-12" +version: "1.1" +updated: "2026-03-23" --- # 复盘总结发飞书群(水桥) -> 对话结束、复盘写完后,将简洁复盘总结发到飞书群。**每次对话都发,长对话必发。** +> **默认不自动发。** 用户口令或**其他 Skill 步骤中写明须发**时才执行;执行前在对话中注明依据(Skill 名 + 步骤)。 > **卡罗拉 = 卡若AI**:飞书文案里两种叫法均可,与《卡若复盘格式》五块一致即可。 --- @@ -22,10 +22,10 @@ updated: "2026-03-12" --- -## 规则(卡若AI 强制) +## 规则(按需) -- **时机**:每次对话完成、完成「卡若复盘」后。 -- **频率**:每次对话完成都发;**长对话尤其必须发**。 +- **时机**:完成「卡若复盘」后,且满足 `.cursor/rules/karuo-ai.mdc` 中「捆绑明确 + 卡若AI 主工作区」。 +- **频率**:非每轮;仅用户要求或绑定的 Skill 流程要求时。 - **内容**:精简版复盘(不必五块全写,保留:时间、目标·结果·达成率、完成要点、下一步)。 --- @@ -52,8 +52,8 @@ python3 .../send_review_to_feishu_webhook.py --file /path/to/summary.txt ## 与其他规则的关系 -- **Cursor 规则**:`.cursor/rules/karuo-ai.mdc` 中「飞书复盘总结发群」— 对话结束后执行发送。 -- **对话沉淀与优化规则**:`运营中枢/使用手册/对话沉淀与优化规则.md` 二、2.1 飞书复盘总结发群(必做项)。 +- **Cursor 规则**:`.cursor/rules/karuo-ai.mdc`「飞书复盘发群(默认关闭)」。 +- **对话沉淀与优化规则**:`运营中枢/使用手册/对话沉淀与优化规则.md` §2.1 按需。 - **工作台说明**:`运营中枢/工作台/飞书复盘总结发群说明.md`。 --- diff --git a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json index dc8bd228..1483f026 100644 --- a/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json +++ b/02_卡人(水)/水桥_平台对接/飞书管理/脚本/.feishu_tokens.json @@ -1,6 +1,6 @@ { - "access_token": "u-fVns2XR7hbJ9Yeux1CLUrAlh3I31ghipPwGaYB4026lV", - "refresh_token": "ur-cDDIZhXHR2Iqc9jmEk39hQlh3KbxghgpU0GaJx4023hI", + "access_token": "u-fNvjYaC8h9SWRJjTGmOKuulh3c3xghUjWMGaJA4023lE", + "refresh_token": "ur-eSq5MlNZ5d9GkVd9PbvCDGlh1A3xghUjrwGaUQ4027hE", "name": "飞书用户", - "auth_time": "2026-03-21T15:26:20.093375" + "auth_time": "2026-03-23T08:28:12.167751" } \ No newline at end of file diff --git a/03_卡木(木)/木叶_视频内容/多平台分发/SKILL.md b/03_卡木(木)/木叶_视频内容/多平台分发/SKILL.md index e0c023df..77fc314f 100644 --- a/03_卡木(木)/木叶_视频内容/多平台分发/SKILL.md +++ b/03_卡木(木)/木叶_视频内容/多平台分发/SKILL.md @@ -7,14 +7,21 @@ description: > triggers: 多平台分发、一键分发、全平台发布、批量分发、视频分发 owner: 木叶 group: 木 -version: "4.0" -updated: "2026-03-11" +version: "4.1" +updated: "2026-03-20" --- -# 多平台分发 Skill(v4.0) +# 多平台分发 Skill(v4.1) > **核心原则**:API 发布为主,Playwright 为辅。确保确定性地分发到各平台。 -> **v4.0 变更**:视频号已切换为纯 API、统一元数据生成器、定时排期优化、简介/标签/分区自动填充。 +> **v4.1 变更**:视频号 Cookie 双路径自动对齐;指定 `--platforms 视频号` 时 Cookie 失效自动调起扫码登录;登录完成即写入并同步中央存储;执行上**先直达目的**(跑命令、保存 Cookie、再发),**不对用户反问**。 + +## 〇、执行原则(第一性原理) + +- **目标优先**:用户要「发到视频号 / 全平台」→ 直接执行 `distribute_all.py` 与必要登录脚本,再简短汇报结果。 +- **Cookie 优先**:任何登录成功 → **必须落盘**;视频号同时写入 `视频号发布/脚本/channels_storage_state.json` 与 `多平台分发/cookies/视频号_cookies.json`(脚本已自动同步)。 +- **少问多做**:缺 Cookie 时自动打开 `channels_login.py`(仅发视频号场景);除非环境无法弹窗,否则不先停下来问「要不要登录」。 +- **扫码只在 Cursor 里**:`channels_login.py` 用 `cursor://…/simple-browser` 打开登录页,**不**唤起 Safari/Chrome;要**完全避免**回退 Chromium,请用带 `--remote-debugging-port=9223` 的方式启动 Cursor(见脚本内说明)。 --- @@ -30,7 +37,7 @@ updated: "2026-03-11" > **关于视频号官方 API 边界**: > 按《视频号与腾讯相关 API 整理》结论,微信官方目前**没有开放「短视频上传/发布」接口**;本 Skill 中的视频号发布能力,属于对 `https://channels.weixin.qq.com` 视频号助手网页协议的逆向封装(DFS 上传 + `post_create`),仅在你本机使用,需自行承担协议变更与合规风险。 -> 官方可控能力(直播记录、橱窗、留资、罗盘数据、本地生活等)的服务端 API 入口为:`https://developers.weixin.qq.com/doc/channels/api/`,如需做直播/橱窗/留资集成,可基于该文档在单独 Skill 中扩展。 +> 官方可控能力(直播记录、橱窗、留资、罗盘数据、本地生活等)的服务端 API 入口为:`https://developers.weixin.qq.com/doc/channels/api/`。**整合脑图与接口速查**见同木叶的 `视频号发布/REFERENCE_开放能力_数据与集成.md`;开放平台凭证约定见 `视频号发布/credentials/README.md`(`.env.open_platform`)。 --- @@ -54,6 +61,10 @@ python3 distribute_all.py --video-dir "/path/to/videos/" # 检查 Cookie / 重试失败 python3 distribute_all.py --check python3 distribute_all.py --retry + +# 仅视频号:Cookie 失效时禁止自动弹窗登录(CI/无头环境) +python3 distribute_all.py --platforms 视频号 --no-auto-channels-login --video-dir "/path/to/成片" +# 或环境变量:NO_AUTO_CHANNELS_LOGIN=1 ``` --- @@ -71,7 +82,7 @@ python3 distribute_all.py --retry | 平台 | 定时方式 | 参数 | |------|----------|------| | B站 | API `meta.dtime` | Unix 时间戳(秒) | -| 视频号 | API 暂不支持原生定时 | 描述中标注时间/手动设置 | +| 视频号 | API `postTimingInfo.postTime`(秒级 Unix);首条若时间过近则立即发 | `channels_api_publish._scheduled_ts_for_channels` | | 抖音 | API `timing_ts` | Unix 时间戳 | | 快手 | Playwright UI | `schedule_helper.py` | | 小红书 | Playwright UI | `schedule_helper.py` | @@ -91,12 +102,12 @@ meta.description("B站") # 标题 + 标签 + 品牌标记 meta.tags_str("B站") # AI工具,效率提升,Soul派对,... meta.bilibili_meta() # B站投稿完整 meta(含 tid/tag/desc) meta.title_short() # 小红书短标题(≤20字) -meta.hashtags("视频号") # #AI工具 #效率提升 ... #小程序 卡若创业派对 +meta.hashtags("视频号") # … + #小程序卡若创业派对 #公众号卡若-4点起床的男人 ``` ### 4.1 内容结构 - **标题**:手工优化标题库优先,否则从文件名智能提取 -- **简介**:标题 + 换行 + 话题标签 + `#小程序 卡若创业派对` +- **简介**:标题 + 换行 + 话题标签;**视频号**固定追加 `#小程序卡若创业派对` `#公众号卡若-4点起床的男人` - **标签**:基于关键词匹配(AI/创业/副业/Soul 等 12 类)+ 通用标签 - **分区**:B站 tid=160(生活>日常) - **风控过滤**:`content_filter.py` 自动替换敏感词(70+ 映射,严格/宽松分级) @@ -121,9 +132,11 @@ meta.hashtags("视频号") # #AI工具 #效率提升 ... #小程序 卡若创 `cookie_manager.py` 统一管理: - 中央存储:`多平台分发/cookies/{平台}_cookies.json` -- 自动迁移:旧路径 → 中央存储(首次使用时) -- API 预检:5 平台各自 auth API 校验有效性 -- 防重复登录:有效 Cookie 不触发重新获取 +- **视频号双路径**:`sync_channels_cookie_files()`;登录脚本写 legacy 后 **copy** 到 `cookies/视频号_cookies.json` +- **登录页只在 Cursor 内打开**:`channels_login.py` v7 用 `cursor://vscode.simple-browser/show?url=…` 唤起 **Simple Browser**,**不**用系统默认浏览器。 +- **不落盘会话**:在 Cursor 已开 `--remote-debugging-port`(默认脚本连 `CHANNELS_CDP_URL=http://127.0.0.1:9223`)时,Playwright **CDP 附着** Cursor,从 Simple Browser 上下文导出 `storage_state`;**无 CDP** 时才回退独立 Chromium(`--playwright-only` 可强制只走 Chromium)。 +- `distribute_all.py` 指定 `--platforms 视频号` 且 Cookie 失效时自动跑 `channels_login.py`(可用 `--no-auto-channels-login` 关闭) +- API 预检:各平台 auth API --- diff --git a/03_卡木(木)/木叶_视频内容/多平台分发/cookies/视频号_cookies.json b/03_卡木(木)/木叶_视频内容/多平台分发/cookies/视频号_cookies.json index 1c6850a7..05eb8559 100644 --- a/03_卡木(木)/木叶_视频内容/多平台分发/cookies/视频号_cookies.json +++ b/03_卡木(木)/木叶_视频内容/多平台分发/cookies/视频号_cookies.json @@ -1 +1,103 @@ -{"cookies": [{"name": "sessionid", "value": "BgAAS6FvR%2B4TaJLl3izwT1G06CgfKh5skaBvkQJ0SQ59mfQT0ToHyODMpnLSAJK2P6L4CYgjALA2gzNgMX7ebaUJjn4t13wujVBySeUuMhI%3D", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1807755990.211326, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "wxuin", "value": "1053878081", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1807755990.211417, "httpOnly": false, "secure": true, "sameSite": "None"}], "origins": [{"origin": "https://channels.weixin.qq.com", "localStorage": [{"name": "finder_uin", "value": ""}, {"name": "__ml::hb_ts", "value": "1773201393923"}, {"name": "__ml::page_93c58d09-90c2-4860-95c9-4077b5b2d287", "value": "{\"pageId\":\"PostList\",\"accessId\":\"b11d6cc6-1c21-4992-9330-182337409198\",\"step\":1}"}, {"name": "__ml::page_f5f51f31-1d88-4fcd-99e1-62254b7ed285", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"1e5233f9-6667-4026-bb9d-e0388f0db908\",\"step\":1}"}, {"name": "__ml::page_8ecfb56a-5918-4670-9818-fd0fb7c60abd", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"1e24c58a-3912-4fd6-8bfb-db31389d6086\",\"step\":1}"}, {"name": "__ml::aid", "value": "\"30cbc62d-d0b3-48ee-9f2c-f140640f8b93\""}, {"name": "__rx::aid", "value": "\"30cbc62d-d0b3-48ee-9f2c-f140640f8b93\""}, {"name": "__ml::page_b324f4ed-38c8-4c0c-b09c-2d6ec995c200", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"99306fe8-b677-4cf6-8b28-c20385868fbc\",\"step\":1}"}, {"name": "__ml::page", "value": "[\"8ecfb56a-5918-4670-9818-fd0fb7c60abd\",\"42270804-eab1-4761-9df0-2f16beecb28a\",\"71226dfd-b262-43c3-b308-7fffb0b775b5\",\"420d22f6-759f-4a29-982c-69bc83d73345\",\"f5f51f31-1d88-4fcd-99e1-62254b7ed285\",\"93c58d09-90c2-4860-95c9-4077b5b2d287\",\"8ea51909-1f57-455e-8d4e-32fb5dc64bd8\",\"a0d4b3e0-5563-403c-9098-2d955f77b907\",\"b324f4ed-38c8-4c0c-b09c-2d6ec995c200\",\"b97eaae7-1e92-489f-8d9e-db44f9e1c736\"]"}, {"name": "finder_login_token", "value": ""}, {"name": "finder_username", "value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder"}, {"name": "_finger_print_device_id", "value": "6fd704941768442b12a996d2652fc61e"}, {"name": "__ml::page_8ea51909-1f57-455e-8d4e-32fb5dc64bd8", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"efbb7fe0-141b-4420-a687-4cee31dd5d93\",\"step\":2,\"refAccessId\":\"bfa0d2bd-0f15-47e7-aea4-487cf72189ea\",\"refPageId\":\"MicroPost\"}"}, {"name": "MICRO_VISITED_NAME", "value": "{\"postCard\":1,\"content\":4}"}, {"name": "AssistantUploadedInfoStorageKey_3899420810", "value": "[{\"fileUploadedInfoKey\":\"AI\u6bcf\u5929\u526a1000\u4e2a\u89c6\u9891 M4\u7535\u811124T\u7d20\u6750\u5e93\u5168\u7f51\u5206\u53d1.mp4:1773161628696:13368696:video/mp4:bb27bef6a6b0bc483253a1a7ef45ae9c\",\"isUsedQuickUpload\":false,\"uploadChunkRecord\":[{\"index\":0,\"reqTime\":1773201399478,\"resTime\":1773201403851,\"cost\":4373,\"success\":true},{\"index\":1,\"reqTime\":1773201399521,\"resTime\":1773201402274,\"cost\":2753,\"success\":true}],\"uuid\":\"4f012cdd-3bc7-4a41-8391-2b2a60986f7f\",\"uploadTaskId\":\"CkQyYWMyOGFmNDg1ZDU5MmFhN2IwYTA3NzNjNDVjMDdmYWFkMDhjZDcxOThjYjQxZTNhODAwYmNkZDFlOGNjNTAwYWQwYxI+NTY5YjBlN2Y3MDAwNmU1OGVlODZjNzA4YTAwMDAwMGZiMDAwMDRmNGU1MzQ4MDZjYWQxNzE1NmQxMDZhOWEw+PqvBg==\",\"uploadTaskIdTimeStamp\":1773201399412,\"transFlag\":\"0_0\",\"partInfo\":[{\"PartNumber\":1,\"ETag\":\"\\\"60ebdb28c6cb0ae3326b893bd6b7929f380f5f70\\\"\"},{\"PartNumber\":2,\"ETag\":\"\\\"3cb4f2daa588cbe0cb2e5c9a74e27f2e73ef4820\\\"\"}],\"uploadSuccessResp\":{\"data\":{\"DownloadURL\":\"http://wxapp.tc.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQAG5cbBO9Dcf1ChScJsbFWibO1jJuy1VJ37tcQcAPC8cdNu4oJTO2nxhsCXxtmNFNicsiakHLSTmLMpicXtibfgpgFkq&findertoken=088ae1b1c30e10fccfc3cd061800223c66696e64657275706c6f616475726c5f333839393432303831305f313737333230313430343038315f313938343932303738383533373031323730362a2037303537306663656335636336636131346533303861613565623334633434663801400348005000580260ce9e01&hy=SH&idx=1&m=&scene=2&token=x5Y29zUxcibDKpcKcWAN8FQSwnP6s7Av7qtG75jebJ9gv6VGeeAM6b3LzNtIBJImiasuHYk2pr0Prm8OCyVFtdjyGWLXRKiccCxCPzGa4gporkQ6ibQWKYmrbQ&uzid=7a1ac\",\"httpsUrl\":\"https://finder.video.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQAG5cbBO9Dcf1ChScJsbFWibO1jJuy1VJ37tcQcAPC8cdNu4oJTO2nxhsCXxtmNFNicsiakHLSTmLMpicXtibfgpgFkq&findertoken=088ae1b1c30e10fccfc3cd061800223c66696e64657275706c6f616475726c5f333839393432303831305f313737333230313430343038315f313938343932303738383533373031323730362a2037303537306663656335636336636131346533303861613565623334633434663801400348005000580260ce9e01&hy=SH&idx=1&m=&scene=2&token=x5Y29zUxcibDKpcKcWAN8FQSwnP6s7Av7qtG75jebJ9gv6VGeeAM6b3LzNtIBJImiasuHYk2pr0Prm8OCyVFtdjyGWLXRKiccCxCPzGa4gporkQ6ibQWKYmrbQ&uzid=7a1ac\"}}}]"}, {"name": "UvFirstReportLocalKey", "value": "1773158400000"}, {"name": "__ml::page_b97eaae7-1e92-489f-8d9e-db44f9e1c736", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"214ab773-06c6-4644-b6ca-3027f37a7172\",\"step\":1}"}, {"name": "__ml::page_42270804-eab1-4761-9df0-2f16beecb28a", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"d3417a18-ab92-4e50-abfc-436a997db360\",\"step\":1}"}, {"name": "__ml::page_420d22f6-759f-4a29-982c-69bc83d73345", "value": "{\"pageId\":\"PostCard\",\"accessId\":\"4b6e8654-40cf-459c-8169-edc29af30e23\",\"step\":1}"}, {"name": "__ml::page_71226dfd-b262-43c3-b308-7fffb0b775b5", "value": "{\"pageId\":\"Home\",\"accessId\":\"0ac0b09f-f112-4f71-9180-c8f628b0de22\",\"step\":1}"}, {"name": "__ml::page_a0d4b3e0-5563-403c-9098-2d955f77b907", "value": "{\"pageId\":\"PostList\",\"accessId\":\"bbfb71ad-63db-483e-9376-5043fb106f5a\",\"step\":2,\"refAccessId\":\"a07b18ab-e357-455d-8d59-9481284a7f42\",\"refPageId\":\"PostCreate\"}"}, {"name": "finder_ua_report_data", "value": "{\"browser\":\"Chrome\",\"browserVersion\":\"131.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}"}, {"name": "finder_route_meta", "value": "micro.content/post/create;index;1;1773201396251"}]}]} \ No newline at end of file +{ + "cookies": [ + { + "name": "sessionid", + "value": "BgAAO0pkRXR1Cnpkg8qKYYMziAr6J6R%2FMVqC2Jat0R1fmRXawRqzqlxIdOy47RaOqNJ8YCfDBsY9VFSx0UMt4JmiGxqBL6Wa6v0LDXbmykU%3D", + "domain": "channels.weixin.qq.com", + "path": "/", + "expires": 1808780189.672433, + "httpOnly": false, + "secure": true, + "sameSite": "None" + }, + { + "name": "wxuin", + "value": "1519919758", + "domain": "channels.weixin.qq.com", + "path": "/", + "expires": 1808780189.672495, + "httpOnly": false, + "secure": true, + "sameSite": "None" + } + ], + "origins": [ + { + "origin": "https://channels.weixin.qq.com", + "localStorage": [ + { + "name": "finder_route_meta", + "value": "micro.content/post/list;index;1;1774220268329" + }, + { + "name": "__ml::hb_ts", + "value": "1774220134814" + }, + { + "name": "__rx::aid", + "value": "\"80b5c5eb-802b-42f7-843e-6c7a4bf40ffa\"" + }, + { + "name": "__ml::aid", + "value": "\"80b5c5eb-802b-42f7-843e-6c7a4bf40ffa\"" + }, + { + "name": "__ml::page_79981609-2209-498e-b670-628160070448", + "value": "{\"pageId\":\"Home\",\"accessId\":\"6c054bc2-80ac-4b02-aa40-790e1223fcce\",\"step\":1}" + }, + { + "name": "__ml::page_7abb4872-1585-4f94-892c-2ff1f1222889", + "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"38dca924-46a7-4068-9ca1-0fdc1f51ffc6\",\"step\":1}" + }, + { + "name": "__ml::page", + "value": "[\"7abb4872-1585-4f94-892c-2ff1f1222889\",\"639f449e-0a27-4a6e-a189-e7bf16c74428\",\"79981609-2209-498e-b670-628160070448\",\"dd2d2b12-843f-496f-91b3-0e3326d52728\",\"446b419a-6e50-47a5-8688-9a17072dde6e\",\"6c27611b-edcd-43d8-8929-6bbf188b2e5e\"]" + }, + { + "name": "finder_login_token", + "value": "" + }, + { + "name": "__ml::page_6c27611b-edcd-43d8-8929-6bbf188b2e5e", + "value": "{\"pageId\":\"PostList\",\"accessId\":\"2a41b37b-b659-4bc5-a6e8-4e70f75fa49f\",\"step\":1}" + }, + { + "name": "finder_username", + "value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder" + }, + { + "name": "__ml::page_dd2d2b12-843f-496f-91b3-0e3326d52728", + "value": "{\"pageId\":\"PostCard\",\"accessId\":\"c2c6e55a-fa3f-41fd-8690-d4c2364aca65\",\"step\":1}" + }, + { + "name": "_finger_print_device_id", + "value": "99684cf0b0c8496a5094ef60138ec231" + }, + { + "name": "MICRO_VISITED_NAME", + "value": "{\"postCard\":1,\"content\":1}" + }, + { + "name": "__ml::page_639f449e-0a27-4a6e-a189-e7bf16c74428", + "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"d90a87c7-e749-4b53-8ab2-b7c8c25f252c\",\"step\":1}" + }, + { + "name": "UvFirstReportLocalKey", + "value": "1774195200000" + }, + { + "name": "finder_ua_report_data", + "value": "{\"browser\":\"Chrome\",\"browserVersion\":\"131.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}" + }, + { + "name": "__ml::page_446b419a-6e50-47a5-8688-9a17072dde6e", + "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"1b08b0c7-0031-4b66-99fb-037cb01f60ea\",\"step\":1}" + }, + { + "name": "finder_uin", + "value": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/cookie_manager.py b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/cookie_manager.py index f7466db0..c2d960b2 100644 --- a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/cookie_manager.py +++ b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/cookie_manager.py @@ -7,6 +7,7 @@ - 视频号保存时同步至 channels_storage_state.json 以兼容旧脚本 """ import json +import shutil import time from pathlib import Path from datetime import datetime @@ -236,11 +237,39 @@ def _check_platform_stub(platform: str, cookies: dict[str, str]) -> tuple[bool, return True, "存在(未做接口校验)" +def sync_channels_cookie_files() -> None: + """ + 视频号 Cookie 双路径对齐: + - 登录脚本写入:视频号发布/脚本/channels_storage_state.json(legacy) + - 预检读取:多平台分发/cookies/视频号_cookies.json(central) + 以较新 mtime 为准互相覆盖,避免一份过期、一份未更新导致「能登录却校验失败」。 + """ + legacy = PLATFORM_LEGACY_PATHS.get("视频号") + if not legacy: + return + _ensure_cookie_dir() + central = get_cookie_path("视频号") + if not legacy.exists() and not central.exists(): + return + if legacy.exists() and not central.exists(): + shutil.copy2(legacy, central) + return + if central.exists() and not legacy.exists(): + shutil.copy2(central, legacy) + return + if legacy.stat().st_mtime >= central.stat().st_mtime: + shutil.copy2(legacy, central) + else: + shutil.copy2(central, legacy) + + def check_cookie_valid(platform: str) -> tuple[bool, str]: """ 校验平台 cookie 是否有效,调用平台特定 auth API。 返回 (is_valid, message)。 """ + if platform == "视频号": + sync_channels_cookie_files() cookies = load_cookies(platform) if not cookies: return False, "文件不存在或为空" diff --git a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py index d4084f2c..babc3d25 100644 --- a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py +++ b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py @@ -24,6 +24,8 @@ import argparse import asyncio import importlib.util import json +import os +import subprocess import sys import time from pathlib import Path @@ -33,13 +35,47 @@ BASE_DIR = SCRIPT_DIR.parent.parent DEFAULT_VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 120场 20260320_output/成片_大师版") sys.path.insert(0, str(SCRIPT_DIR)) -from cookie_manager import check_cookie_valid, load_cookies, SUPPORTED_PLATFORMS +from cookie_manager import ( + check_cookie_valid, + load_cookies, + SUPPORTED_PLATFORMS, + sync_channels_cookie_files, +) from publish_result import (PublishResult, print_summary, save_results, load_published_set, load_failed_tasks) from title_generator import generate_title from schedule_generator import generate_schedule, format_schedule from video_metadata import VideoMeta +CHANNELS_LOGIN_SCRIPT = BASE_DIR / "视频号发布" / "脚本" / "channels_login.py" + + +def _ensure_channels_cookie_or_login(skip_auto: bool) -> None: + """指定发视频号时:先对齐双路径 Cookie;无效则直接调起扫码登录(保存后继续)。""" + if skip_auto or os.environ.get("NO_AUTO_CHANNELS_LOGIN"): + sync_channels_cookie_files() + return + sync_channels_cookie_files() + ok, _ = check_cookie_valid("视频号") + if ok: + return + if not CHANNELS_LOGIN_SCRIPT.exists(): + return + print( + "\n[*] 视频号 Cookie 无效 → 打开浏览器扫码登录(登录完成即写入并同步 Cookie)\n", + flush=True, + ) + try: + subprocess.run( + [sys.executable, str(CHANNELS_LOGIN_SCRIPT)], + cwd=str(CHANNELS_LOGIN_SCRIPT.parent), + timeout=600, + ) + except subprocess.TimeoutExpired: + print("[!] 登录流程超时(600s)", flush=True) + sync_channels_cookie_files() + + PLATFORM_CONFIG = { "抖音": { "script": BASE_DIR / "抖音发布" / "脚本" / "douyin_pure_api.py", @@ -315,8 +351,21 @@ async def main(): parser.add_argument("--min-gap", type=int, default=30, help="最小间隔(分钟)") parser.add_argument("--max-gap", type=int, default=120, help="最大间隔(分钟)") parser.add_argument("--max-hours", type=float, default=24.0, help="最大排期跨度(小时)") + parser.add_argument( + "--no-auto-channels-login", + action="store_true", + help="禁用「仅发视频号时」Cookie 失效自动弹窗登录", + ) args = parser.parse_args() + if ( + not args.check + and not args.retry + and args.platforms + and "视频号" in args.platforms + ): + _ensure_channels_cookie_or_login(args.no_auto_channels_login) + available, alerts = check_cookies_with_alert() if alerts: send_feishu_alert(alerts) diff --git a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/publish_log.json b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/publish_log.json index 30c7a897..7eee7490 100644 --- a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/publish_log.json +++ b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/publish_log.json @@ -264,3 +264,24 @@ {"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/流量端和交付端别同时扛,缺流量就做群主开派对.mp4", "title": "流量端和交付端别同时扛,缺流量就做群主开派对", "success": true, "status": "likely_published", "message": "发布按钮+确认已点击,视频可能仍在处理", "screenshot": "/tmp/xhs_result.png", "elapsed_sec": 49.58630394935608, "timestamp": "2026-03-20 16:45:03"} {"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/游戏辅助AI模型,高收益高风险,3到6个月必须收手.mp4", "title": "游戏辅助AI模型,高收益高风险,3到6个月必须收手", "success": true, "status": "likely_published", "message": "发布按钮+确认已点击,视频可能仍在处理", "screenshot": "/tmp/xhs_result.png", "elapsed_sec": 49.622374057769775, "timestamp": "2026-03-20 16:46:20"} {"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/面试三面流程 简历+测试+试岗,300份筛到2到3个人.mp4", "title": "面试三面流程 简历+测试+试岗,300份筛到2到3个人", "success": true, "status": "likely_published", "message": "发布按钮+确认已点击,视频可能仍在处理", "screenshot": "/tmp/xhs_result.png", "elapsed_sec": 49.556177854537964, "timestamp": "2026-03-20 16:47:36"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/Soul上像开老茶馆.mp4", "title": "Soul上像开老茶馆", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 78.16861295700073, "timestamp": "2026-03-23 06:59:50"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/三百七十万罚单亲历.mp4", "title": "三百七十万罚单亲历", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 21.983049869537354, "timestamp": "2026-03-23 07:00:15"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/三百简历只要两三个.mp4", "title": "三百简历只要两三个", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 22.617194890975952, "timestamp": "2026-03-23 07:00:41"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/三角洲模型怎么卖.mp4", "title": "三角洲模型怎么卖", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 49.14002799987793, "timestamp": "2026-03-23 07:01:33"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/上麦讲你上月做啥.mp4", "title": "上麦讲你上月做啥", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 49.91433119773865, "timestamp": "2026-03-23 07:02:26"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/二百七十万推流从哪来.mp4", "title": "二百七十万推流从哪来", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 21.54202175140381, "timestamp": "2026-03-23 07:02:50"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/什么话题最好起量.mp4", "title": "什么话题很好起量", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 70.53687381744385, "timestamp": "2026-03-23 07:04:04"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/保镖业务先讲清模式.mp4", "title": "保镖业务先讲清模式", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 22.21481990814209, "timestamp": "2026-03-23 07:04:29"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/分他靠自己赚不到的那块.mp4", "title": "分他靠自己赚不到的那块", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 22.66372299194336, "timestamp": "2026-03-23 07:04:55"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/场景里拍视频就链接.mp4", "title": "场景里拍视频就链接", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 22.18739891052246, "timestamp": "2026-03-23 07:05:20"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/学历不如可验证实操.mp4", "title": "学历不如可验证实操", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 22.074921131134033, "timestamp": "2026-03-23 07:05:45"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/性格不对团队白搭.mp4", "title": "性格不对团队白搭", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 21.877957105636597, "timestamp": "2026-03-23 07:06:10"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/想两万月薪先看月烧多少.mp4", "title": "想两万月薪先看月烧多少", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 27.32966685295105, "timestamp": "2026-03-23 07:06:40"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/政商金融矿产华侨.mp4", "title": "政商金融矿产华侨", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 44.0236439704895, "timestamp": "2026-03-23 07:07:27"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/最大产值不是写代码.mp4", "title": "很大产值不是写代码", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 22.436315059661865, "timestamp": "2026-03-23 07:07:52"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/现在缺的是流量.mp4", "title": "现在缺的是流量", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 48.646218061447144, "timestamp": "2026-03-23 07:08:44"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/筛对了人能跟十二年.mp4", "title": "筛对了人能跟十二年", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 48.89371585845947, "timestamp": "2026-03-23 07:09:36"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/线上更适合阿米巴.mp4", "title": "线上更适合阿米巴", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 21.64088201522827, "timestamp": "2026-03-23 07:10:01"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/群主和交付师不一样.mp4", "title": "群主和交付师不一样", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 21.92174482345581, "timestamp": "2026-03-23 07:10:26"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/职场课为什么卖不动.mp4", "title": "职场课为什么卖不动", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 21.752349138259888, "timestamp": "2026-03-23 07:10:50"} +{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片/链接要落到具体事.mp4", "title": "链接要落到具体事", "success": false, "status": "error", "message": "post_create errCode=300002 request failed", "elapsed_sec": 48.84101176261902, "timestamp": "2026-03-23 07:11:42"} diff --git a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/video_metadata.py b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/video_metadata.py index 2084842e..bf7fa800 100644 --- a/03_卡木(木)/木叶_视频内容/多平台分发/脚本/video_metadata.py +++ b/03_卡木(木)/木叶_视频内容/多平台分发/脚本/video_metadata.py @@ -20,6 +20,8 @@ from content_filter import filter_for_platform BRAND_TAG = "#卡若创业派对" MINI_PROGRAM = "#小程序 卡若创业派对" +# 视频号描述末尾固定话题(与微信搜一搜/话题展示一致,无空格写法) +CHANNELS_FIXED_TAGS = ("#小程序卡若创业派对", "#公众号卡若-4点起床的男人") PLATFORM_CATEGORIES = { "B站": {"tid": 160, "name": "生活 > 日常"}, @@ -116,6 +118,8 @@ class VideoMeta: tags_extra=curated.get("tags_extra", []), ) stem = Path(fname).stem + stem = re.sub(r"^soul\d+_\d+_", "", stem, flags=re.I) + stem = re.sub(r"^\d+场", "", stem) stem = re.sub(r'^\d+[._\-\s]*', '', stem) stem = stem.replace('_', ' ').replace(' ', ' ').strip() @@ -149,7 +153,10 @@ class VideoMeta: """# 标签字符串""" tags = self._smart_tags(platform) parts = [f"#{t}" for t in tags] - parts.append(MINI_PROGRAM) + if platform == "视频号": + parts.extend(CHANNELS_FIXED_TAGS) + else: + parts.append(MINI_PROGRAM) return " ".join(parts) def description(self, platform: str, max_len: int = 500) -> str: diff --git a/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md b/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md index 0ab32f97..8577e5c7 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md +++ b/03_卡木(木)/木叶_视频内容/视频切片/Soul竖屏切片_SKILL.md @@ -131,7 +131,7 @@ python3 soul_enhance.py \ ## 五、成片:封面 + 字幕 + 竖屏 -- **封面**:竖条画布内**不超出界面**;**半透明质感**(背景 alpha=165);深色渐变、左上角 Soul logo;**封面显示标题 = 成片文件名 = highlights.title**(去杠、去下划线后一致,无 `:|—/_`、无序号);标题严格居中、多行自动换行。透明度由 `VERTICAL_COVER_ALPHA` 调节。 +- **封面**:竖条画布内**不超出界面**;**冷色半透明渐变**(`VERTICAL_COVER_ALPHA` 约 148)+ **底部电影感渐隐**(`STYLE['cover']` 的 `vignette_*`)+ **顶栏单条 Soul 绿 + 可选 1px 淡金线** + **细白内框**;主标题为**柔阴影暖白字**(非粗描边),字体优先思源黑体 Bold;左上角双圈 Soul 标。**封面文案**优先 `hook_3sec`(见 `pick_cover_hook_text`)。成片文件名仍与 `highlights.title` 规则一致。 - **封面底层模糊(重要)**:**不要全屏强糊**。`soul_enhance.py` 默认 **`STYLE['cover']['bg_blur_mix']=0.1`**:清晰视频帧与一层高斯模糊按 **约 10% 混合**(`bg_blur_radius` 生成模糊层),界面仍大致可辨,仅轻微虚化衬托文字。若需更强/更弱,改脚本内两常量,勿回到「整帧 radius=50+ 全糊」。 - **字幕**:**封面一结束即叠字幕**(无额外「空几秒再等字」);SRT 安全起点为封面结束 + **约 0.05s** epsilon,避免与最后一帧封面打架。字幕**居中**在竖条内。先尝试**单次 FFmpeg 通道**(一次 pass 完成所有字幕叠加,最快);若失败自动回退到分批模式(batch_size=40);语助词在解析阶段已由 `clean_filler_words` 去除。重新加字幕时加 `--force-burn-subs`。⚠️ 注意:当前 FFmpeg 不支持 drawtext/subtitles 滤镜,只能用 PIL 图像 overlay 方案。(脚本常量:`SUBS_START_AFTER_COVER_SEC`,**默认 0.0**) - **字幕字形**:Whisper 词级轴常在**中日文之间插空格**,逐字/逐词显字时会像「字与字被撑开」;脚本在 `improve_subtitle_punctuation` 路径对 **CJK 相邻空白**做折叠(`_collapse_cjk_interchar_spaces`),保证整句显示正常、无异常中空。 @@ -142,7 +142,7 @@ python3 soul_enhance.py \ | 项 | 约定 | |----|------| -| **与封面对比** | 封面为**半透明墨绿渐变**;字幕为**暖深棕圆角条 + 琥珀色描边**,避免与主题绿混成一团 | +| **与封面对比** | 封面为**冷灰青渐变 + 底渐隐 + 顶栏绿**;字幕为**暖深棕圆角条 + 琥珀色描边**,避免与顶栏绿混成一团 | | **纠错** | `transcript.srt` 解析时走 `_improve_subtitle_text`(繁转简、CORRECTIONS 错词、违禁替换、去语助词);**渲染每一帧前**再走 `improve_subtitle_punctuation`,与口播稿对齐 | | **重点词** | `KEYWORDS` 列表命中则**亮金色高亮**(同字号同基线,仅颜色区分,避免大字号造成"两排字"),长词优先匹配 | | **逐字渐显** | 推荐成片加 **`--typewriter-subs`**:同一条字幕时间内前缀逐步加长,更贴人声节奏;配合 CJK 去空格避免字间假空白 | diff --git a/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第127场_20260318_highlights.json b/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第127场_20260318_highlights.json index 5c4d0d0b..089eec7b 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第127场_20260318_highlights.json +++ b/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第127场_20260318_highlights.json @@ -1,82 +1,23 @@ [ - { - "title": "高薪先看消耗", - "start_time": "00:07:30", - "end_time": "00:12:00", - "hook_3sec": "想两万月薪?先看AI月烧多少", - "question": "高薪硬指标是什么?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "想拿2万工资,AI月消耗至少1000块以上。消耗多,说明你是实实在在用AI在解决职业里的问题", - "reason": "TOKEN消耗硬指标" - }, - { - "title": "辅助暴利快收", - "start_time": "00:12:00", - "end_time": "00:18:00", - "hook_3sec": "游戏辅助来钱快,定性也狠", - "question": "做游戏AI能挣多少?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "三角洲辅助瞄准,AI训练人物识别模型。闲鱼抖音直播分销,单价500块一个人一个月", - "reason": "高收益高风险" - }, - { - "title": "保镖钱在后端", - "start_time": "00:18:00", - "end_time": "00:23:00", - "hook_3sec": "真赚的不是保镖费,是后端", - "question": "保镖怎么赚大钱?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "初级2万一个月,中级3万,高级4万。真正赚钱的是商务中介、介绍投资、拉业务合作", - "reason": "信任关系变现" - }, - { - "title": "别两头扛流量", - "start_time": "00:23:00", - "end_time": "00:28:00", - "hook_3sec": "流量交付同时扛,必崩", - "question": "做流量还是交付?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "现在这条赛道缺的是流量,不是交付。和群主合作要分钱,不分必被排挤", - "reason": "一端打穿" - }, - { - "title": "分钱分缺口", - "start_time": "00:28:00", - "end_time": "00:33:00", - "hook_3sec": "分他靠自己赚不到的那块", - "question": "招人怎么分钱?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "员工自己只能挣5000到8000,你给他1万,他多拿2000,他才愿意跟你干", - "reason": "分钱逻辑" - }, - { - "title": "推流就三板斧", - "start_time": "00:33:00", - "end_time": "00:38:00", - "hook_3sec": "二百七十万推流,密码就几条", - "question": "派对流量怎么来?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "流量密码就那几个:职场、搞钱、MBTI性格匹配,最容易共鸣", - "reason": "Soul数据复盘" - }, - { - "title": "面试三面定人", - "start_time": "00:38:00", - "end_time": "00:43:00", - "hook_3sec": "三百简历,最后只要两三个", - "question": "怎么筛人?", - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "二面线上做题、跟团队开25分钟会,看配合。三面定薪资岗位,7天试岗", - "reason": "面试流程" - }, - { - "title": "实操碾压学历", - "start_time": "00:43:00", - "end_time": "00:48:00", - "hook_3sec": "不学AI,连班都难上", - "question": null, - "cta_ending": "今天就到这里,点个关注下次不迷路", - "transcript_excerpt": "想拿2万工资,AI月消耗至少1000。这不是卡学历,是卡你有没有真在用AI干活", - "reason": "实操门槛" - } + {"title": "Soul本场数据复盘", "start_time": "00:08:14", "end_time": "00:10:10", "hook_3sec": "二百七十万推流从哪来", "question": "派对数据怎么看?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "127场推流与进房去重", "reason": "单主题_数据"}, + {"title": "高薪先看AI消耗", "start_time": "00:10:10", "end_time": "00:12:35", "hook_3sec": "想两万月薪先看月烧多少", "question": "高薪硬指标是什么?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "TOKEN消耗与岗位问法", "reason": "单主题_AI招人"}, + {"title": "性格测评怎么配团队", "start_time": "00:12:35", "end_time": "00:15:05", "hook_3sec": "性格不对团队白搭", "question": "性格怎么测?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "MBTI PDP 筛选", "reason": "单主题_测评"}, + {"title": "面试三面怎么筛人", "start_time": "00:28:22", "end_time": "00:30:55", "hook_3sec": "三百简历只要两三个", "question": "面试怎么筛?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "一面真二面测三面试岗", "reason": "单主题_面试"}, + {"title": "线上阿米巴分钱", "start_time": "00:30:55", "end_time": "00:33:18", "hook_3sec": "线上更适合阿米巴", "question": "线上怎么分钱?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "小结果分钱与阿米巴", "reason": "单主题_阿米巴"}, + {"title": "筛选精细化案例", "start_time": "00:33:18", "end_time": "00:36:08", "hook_3sec": "筛对了人能跟十二年", "question": "怎么减少试错?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "前端筛选与性格合群", "reason": "单主题_筛选"}, + {"title": "老茶馆与情绪价值", "start_time": "00:36:08", "end_time": "00:39:05", "hook_3sec": "Soul上像开老茶馆", "question": "怎么做深度社群?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "线上茶馆与参与感", "reason": "单主题_社群"}, + {"title": "链接与场景获客", "start_time": "00:39:05", "end_time": "00:42:05", "hook_3sec": "场景里拍视频就链接", "question": "怎么自然链接人?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "业务链接与场景", "reason": "单主题_链接"}, + {"title": "场景链接续聊", "start_time": "00:42:05", "end_time": "00:45:05", "hook_3sec": "链接要落到具体事", "question": "链接怎么变现?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "案例延伸", "reason": "单主题_链接续"}, + {"title": "派对上麦怎么玩", "start_time": "00:45:05", "end_time": "00:48:20", "hook_3sec": "上麦讲你上月做啥", "question": "怎么让别人记住你?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "上麦规则与业务曝光", "reason": "单主题_上麦"}, + {"title": "保镖业务怎么合作", "start_time": "00:48:45", "end_time": "00:51:15", "hook_3sec": "保镖业务先讲清模式", "question": "保镖合作聊什么?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "上麦介绍保镖业务", "reason": "单主题_保镖入口"}, + {"title": "保镖报价与客群", "start_time": "00:51:15", "end_time": "00:54:45", "hook_3sec": "政商金融矿产华侨", "question": "服务谁怎么报价?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "套餐与团队规模", "reason": "单主题_保镖报价"}, + {"title": "游戏模型怎么变现", "start_time": "00:54:50", "end_time": "00:57:25", "hook_3sec": "三角洲模型怎么卖", "question": "游戏AI能挣多少?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "分销与单价", "reason": "单主题_游戏变现"}, + {"title": "辅助风险与止损", "start_time": "00:57:25", "end_time": "00:59:55", "hook_3sec": "三百七十万罚单亲历", "question": "辅助最大的坑?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "定性与收手", "reason": "单主题_风险"}, + {"title": "别两头扛流量交付", "start_time": "01:23:55", "end_time": "01:26:25", "hook_3sec": "群主和交付师不一样", "question": "做流量还是交付?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "流量端交付端", "reason": "单主题_定位"}, + {"title": "赛道缺流量不缺交付", "start_time": "01:26:25", "end_time": "01:29:10", "hook_3sec": "现在缺的是流量", "question": "为什么难做?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "老师多流量贵", "reason": "单主题_赛道"}, + {"title": "摄影课与个人成长选择", "start_time": "01:36:10", "end_time": "01:38:50", "hook_3sec": "职场课为什么卖不动", "question": "摄影和成长怎么选?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "纪实与职场付费", "reason": "单主题_摄影"}, + {"title": "CTO值钱在链接信息", "start_time": "01:40:58", "end_time": "01:43:35", "hook_3sec": "最大产值不是写代码", "question": "CTO该干什么?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "前沿信息与链接", "reason": "单主题_CTO"}, + {"title": "职场搞钱话题密码", "start_time": "01:49:48", "end_time": "01:52:33", "hook_3sec": "什么话题最好起量", "question": "推流密码是啥?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "职场搞钱MBTI", "reason": "单主题_话题"}, + {"title": "分钱分缺口", "start_time": "01:52:33", "end_time": "01:55:18", "hook_3sec": "分他靠自己赚不到的那块", "question": "招人怎么分钱?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "分润逻辑", "reason": "单主题_分钱"}, + {"title": "实操碾压学历", "start_time": "02:08:20", "end_time": "02:11:05", "hook_3sec": "学历不如可验证实操", "question": null, "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "收尾金句", "reason": "单主题_收尾"} ] diff --git a/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第128场_20260319_highlights.json b/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第128场_20260319_highlights.json new file mode 100644 index 00000000..643f1a74 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频切片/场次稿/第128场_20260319_highlights.json @@ -0,0 +1,10 @@ +[ + {"title": "128场开门聊AI", "start_time": "00:04:33", "end_time": "00:07:22", "hook_3sec": "今天128场先聊筛选", "question": "独立开发怎么选模型?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "派对开场+API月耗筛选", "reason": "单主题_开场"}, + {"title": "AI落地还有空间", "start_time": "00:07:22", "end_time": "00:09:55", "hook_3sec": "说个案例你就懂", "question": "AI辅助怎么放大?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "实战案例与卖token", "reason": "单主题_AI案例"}, + {"title": "抖音加AI怎么剪", "start_time": "00:23:12", "end_time": "00:26:05", "hook_3sec": "我之前抖音就这么做", "question": "人效怎么拉高?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "抖音录制+AI结构", "reason": "单主题_抖音"}, + {"title": "一万次优于一万时", "start_time": "00:35:06", "end_time": "00:37:55", "hook_3sec": "我不认一万小时定律", "question": "刻意练习怎么算?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "一万次优化迭代", "reason": "单主题_方法论"}, + {"title": "百场派对堆数据", "start_time": "00:39:50", "end_time": "00:42:35", "hook_3sec": "开一百多场在干嘛", "question": "内容怎么瘦出来?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "话题统计与试错成本", "reason": "单主题_派对数据"}, + {"title": "最赚月是不是运气", "start_time": "00:46:40", "end_time": "00:49:25", "hook_3sec": "分享最赚钱那一个月", "question": "漏洞钱算不算能力?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "运气与偏踩案例", "reason": "单主题_赚钱节奏"}, + {"title": "十一月项目怎么推", "start_time": "00:52:50", "end_time": "00:55:45", "hook_3sec": "普通人能做啥值钱", "question": "ID和物料怎么组织?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "22年11月水平时间", "reason": "单主题_组织"}, + {"title": "派对链接月万人", "start_time": "01:19:35", "end_time": "01:21:50", "hook_3sec": "月进房一两万人", "question": "派对直接赚钱吗?", "cta_ending": "今天就到这里,点个关注下次不迷路", "transcript_excerpt": "曝光与链接资源", "reason": "单主题_派对价值"} +] diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/build_127_highlights_from_srt.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/build_127_highlights_from_srt.py new file mode 100644 index 00000000..4a6a0043 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/build_127_highlights_from_srt.py @@ -0,0 +1,145 @@ +#!/usr/bin/env python3 +""" +根据第127场 transcript.srt 关键词锚点,生成「单主题、短时长、条数多」的 highlights.json。 +""" +from __future__ import annotations + +import json +import re +import sys +from pathlib import Path + +# (锚点正则, 条目标题, hook_3sec, question) +ANCHORS: list[tuple[str, str, str, str | None]] = [ + (r"三个东西|三样东西|三个指标|招人.*看", "招人三指标总览", "现在招人我看三样", "招人最看重什么?"), + (r"TOKEN|消耗.*1000|一千块|月消耗", "高薪先看AI消耗", "想两万月薪先看月烧多少", "高薪硬指标是什么?"), + (r"MBTI|PDP|DISC|盖洛普|性格", "性格测评怎么配团队", "性格不对团队白搭", "性格怎么测?"), + (r"一面|二面|三面|试岗|简历.*真", "面试三面怎么筛人", "三百简历只要两三个", "面试怎么筛?"), + (r"三角洲|辅助瞄准|人物识别|闲鱼|五百.*月", "游戏模型怎么变现", "辅助来钱快渠道要狠", "游戏AI能挣多少?"), + (r"破坏计算机|370万|十八万|定性|收手|虚拟货币", "辅助风险与止损", "高收益是定时炸弹", "辅助最大的坑是什么?"), + (r"保镖|女保镖|初级.*两万|中级.*三万", "保镖报价与档位", "真赚的不是保镖费", "保镖能赚多少?"), + (r"后端|中介|投资|拉业务|信任", "保镖钱在后端关系", "高端信任才值钱", "保镖怎么赚大钱?"), + (r"流量端|交付端|同时做|非常累", "别两头扛流量交付", "流量交付同时扛必崩", "做流量还是交付?"), + (r"缺.*流量|老师太多", "赛道缺流量不缺交付", "这条赛道缺流量", "现在缺什么?"), + (r"群主|分钱|排挤", "和群主合作要分钱", "不分钱就被排挤", "群主合作怎么分?"), + (r"273万|推流|进房|进群|去重", "Soul本场数据复盘", "二百七十万推流从哪来", "派对数据怎么看?"), + (r"职场|搞钱|MBTI.*共鸣|流量密码", "推流三板斧话题", "流量密码就这几条", "什么话题最好起量?"), + (r"兴趣群|三十个群|五十人|开播", "新号冲人笨办法", "三十个群堆出五十人", "新号怎么破冷启动?"), + (r"分他.*挣不到|五千.*八千|给.*一万", "分钱分缺口", "分他靠自己赚不到的那块", "招人怎么分钱?"), + (r"CTO|链接.*人|前沿信息", "CTO值钱在链接信息", "最大产值不是写代码", "CTO该干什么?"), + (r"摄影|纪实|职场.*课|All in", "摄影课与个人成长选择", "职场课为什么卖不动", "摄影和成长怎么选?"), + (r"老茶馆|情绪价值|西西", "老茶馆与情绪价值", "Soul上情绪价值怎么做", "怎么做线上社群?"), + (r"不学.*AI|学历不重要|实操", "实操碾压学历", "不学AI连班都难上", None), +] + + +def parse_srt_times(path: Path) -> list[tuple[float, float, str]]: + raw = path.read_text(encoding="utf-8", errors="ignore") + blocks = re.split(r"\n\s*\n", raw.strip()) + out: list[tuple[float, float, str]] = [] + ts = re.compile( + r"(\d{2}):(\d{2}):(\d{2})[,.](\d{3})\s*-->\s*(\d{2}):(\d{2}):(\d{2})[,.](\d{3})" + ) + + def to_sec(h, m, s, ms): + return int(h) * 3600 + int(m) * 60 + int(s) + int(ms) / 1000.0 + + for b in blocks: + lines = [ln.strip() for ln in b.splitlines() if ln.strip()] + if len(lines) < 2: + continue + cue_line = next((ln for ln in lines if "-->" in ln), "") + m = ts.match(cue_line) + if not m: + continue + g = m.groups() + st = to_sec(g[0], g[1], g[2], g[3]) + et = to_sec(g[4], g[5], g[6], g[7]) + idx = lines.index(cue_line) + text = " ".join(lines[idx + 1 :]) + out.append((st, et, text)) + return out + + +def fmt_hms(sec: float) -> str: + sec = max(0, sec) + h = int(sec // 3600) + m = int((sec % 3600) // 60) + s = int(sec % 60) + return f"{h:02d}:{m:02d}:{s:02d}" + + +def find_anchor_events(subs: list[tuple[float, float, str]]) -> list[tuple[float, str, str, str | None]]: + """每条字幕至多触发一个锚点;同一锚点类型 90s 内重复忽略。""" + events: list[tuple[float, str, str, str | None]] = [] + last_idx: int | None = None + last_t = -999.0 + for st, _et, text in subs: + t = re.sub(r"\s+", "", text) + for idx, (pat, title, hook, q) in enumerate(ANCHORS): + if re.search(pat, t): + if last_idx == idx and st - last_t < 90: + break + events.append((st, title, hook, q)) + last_idx = idx + last_t = st + break + return events + + +def main(): + srt = Path(sys.argv[1]) if len(sys.argv) > 1 else None + out_json = Path(sys.argv[2]) if len(sys.argv) > 2 else Path("highlights.json") + if not srt or not srt.is_file(): + print("用法: build_127_highlights_from_srt.py ") + sys.exit(1) + + subs = parse_srt_times(srt) + if not subs: + print("❌ SRT 无有效条目") + sys.exit(1) + + events = find_anchor_events(subs) + if len(events) < 4: + print("❌ 锚点过少,请检查 SRT") + sys.exit(1) + + video_end = min(subs[-1][1] + 3.0, 8000.0) + MIN_SEC = 50 + MAX_SEC = 195 + CTA = "今天就到这里,点个关注下次不迷路" + + clips: list[dict] = [] + for i, (st, title, hook, q) in enumerate(events): + en = events[i + 1][0] if i + 1 < len(events) else video_end + if en - st > MAX_SEC: + en = st + MAX_SEC + if en - st < MIN_SEC: + if i + 1 < len(events): + en = min(events[i + 1][0], st + MIN_SEC) + else: + en = st + MIN_SEC + if en <= st: + continue + clips.append( + { + "title": title, + "start_time": fmt_hms(st), + "end_time": fmt_hms(en), + "hook_3sec": hook, + "question": q, + "cta_ending": CTA, + "transcript_excerpt": "", + "reason": "srt_anchor_v1", + } + ) + + out_json.parent.mkdir(parents=True, exist_ok=True) + out_json.write_text( + json.dumps(clips, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" + ) + print(f"✅ {len(clips)} 条 → {out_json}") + + +if __name__ == "__main__": + main() diff --git a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py index 786d0fa3..e8f282d9 100644 --- a/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py +++ b/03_卡木(木)/木叶_视频内容/视频切片/脚本/soul_enhance.py @@ -2,7 +2,7 @@ """ Soul切片增强脚本 v2.0 功能: -1. 封面贴片:高光 hook_3sec 优先(吸睛),竖屏底图为**清晰帧 + 约 10% 轻模糊混入**(非全糊)+ 渐变 +1. 封面贴片:高光 hook_3sec 优先(吸睛),竖屏底图为**清晰帧 + 约 10% 轻模糊混入**(非全糊)+ 冷色渐变;**顶栏单条 Soul 绿 + 底部电影感渐隐 + 细内框 + 柔阴影标题**(避免粗描边与多条绿边廉价感) 2. 烧录字幕(关键词高亮、可选逐字) 3. 切除检出的长静音并重映射字幕时间轴 4. 片尾 CTA(cta_ending)字幕条 @@ -351,19 +351,22 @@ FONT_PRIORITY = [ "/Library/Fonts/Arial Unicode.ttf", ] COVER_FONT_PRIORITY = [ - "/System/Library/Fonts/PingFang.ttc", # 苹方,封面优先 + FONT_BOLD, # 思源黑体 Bold,标题更有海报感 + "/System/Library/Fonts/PingFang.ttc", "/System/Library/Fonts/Supplemental/Songti.ttc", ] # Soul 品牌绿(绿点/绿色社交) SOUL_GREEN = (0, 210, 106) # #00D26A SOUL_GREEN_DARK = (0, 160, 80) -# 竖屏封面高级背景:深色渐变(不超出界面) -VERTICAL_COVER_TOP = (12, 32, 24) # 深墨绿 -VERTICAL_COVER_BOTTOM = (8, 48, 36) # 略亮绿 -VERTICAL_COVER_PADDING = 44 # 左右留白,保证文字不贴边、不超出 -# 成片封面半透明质感:背景层 alpha,便于透出底层画面 -VERTICAL_COVER_ALPHA = 165 # 0~255,越大越不透明 +# 竖屏封面高级背景:冷灰青渐变(比纯墨绿更显质感,与 Soul 绿顶栏形成对比) +VERTICAL_COVER_TOP = (18, 26, 34) # 深板岩 +VERTICAL_COVER_BOTTOM = (8, 14, 22) # 近黑蓝 +VERTICAL_COVER_PADDING = 48 # 左右留白,保证文字不贴边、不超出 +# 成片封面半透明质感:背景层 alpha,便于透出底层画面(略降,减少发灰) +VERTICAL_COVER_ALPHA = 148 # 0~255,越大越不透明 +# 点缀金(细线用,低存在感) +COVER_ACCENT_GOLD = (201, 169, 98) # 样式配置 STYLE = { @@ -373,12 +376,21 @@ STYLE = { 'bg_blur_radius': 14, # 仅用于生成模糊层的高斯半径,再与清晰帧 blend 'overlay_alpha': 200, 'duration': 2.5, + # 竖屏「高级封面」装饰(横版仍走原 overlay 逻辑) + 'dim_alpha': 88, # 首帧压暗,略提亮画面层次 + 'top_accent_px': 6, # 顶栏 Soul 绿实条高度 + 'gold_hairline': True, # 顶栏下 1px 淡金线 + 'vignette_from_ratio': 0.46, # 从下往上渐隐起点(占画面高度比例) + 'vignette_max_alpha': 138, + 'frame_inset_alpha': 42, # 内框白边透明度 }, 'hook': { 'font_size': 82, # 更大更清晰 'color': (255, 255, 255), 'outline_color': (30, 30, 50), 'outline_width': 5, + # 竖屏主标题:略暖白,与冷底对比 + 'vertical_fill': (252, 252, 250), }, 'subtitle': { 'font_size': 44, @@ -439,6 +451,43 @@ def draw_text_with_outline(draw, pos, text, font, color, outline_color, outline_ # 主体 draw.text((x, y), text, font=font, fill=color) + +def draw_text_with_soft_shadow(draw, pos, text, font, fill_rgb): + """竖屏封面标题:右下柔阴影 + 主字,比粗描边更偏杂志/海报质感""" + x, y = pos + if len(fill_rgb) == 4: + main = fill_rgb + else: + main = (*fill_rgb, 255) + layers = [ + (6, 6, 72), + (5, 5, 100), + (4, 4, 130), + (3, 3, 155), + (2, 2, 175), + (1, 1, 195), + ] + for dx, dy, a in layers: + draw.text((x + dx, y + dy), text, font=font, fill=(0, 0, 0, a)) + draw.text((x, y), text, font=font, fill=main) + + +def _apply_cover_bottom_vignette(img_rgba, from_ratio: float, max_alpha: int): + """底部电影感渐隐,压暗杂边、托住标题区;返回合成后的新图""" + w, h = img_rgba.size + y0 = int(max(0, min(1, from_ratio)) * h) + if y0 >= h - 2: + return img_rgba + layer = Image.new("RGBA", (w, h), (0, 0, 0, 0)) + ld = ImageDraw.Draw(layer) + span = max(h - y0, 1) + for y in range(y0, h): + t = (y - y0) / span + a = int(max_alpha * (t ** 1.35)) + a = max(0, min(255, a)) + ld.rectangle([0, y, w, y + 1], fill=(0, 0, 0, a)) + return Image.alpha_composite(img_rgba, layer) + def _normalize_title_for_display(title: str) -> str: """标题去杠去下划线:将 :|、—、/、_ 等全部替换为空格,避免文件名和封面出现杂符号""" if not title: @@ -1012,7 +1061,7 @@ def _strip_cover_number_prefix(text): def create_cover_image(hook_text, width, height, output_path, video_path=None): - """创建封面贴片。竖条(高 1080、宽由塑形)时:半透明渐变、文字在条内居中、左上角 Soul logo;不显示切片序号前缀。""" + """创建封面贴片。竖条:视频底 + 冷色渐变 + 底栏渐隐 + 顶栏 Soul 绿与细金线 + 内框 + 柔阴影标题 + 左上角标。""" hook_text = _to_simplified(str(hook_text or "").strip()) hook_text = _strip_cover_number_prefix(hook_text) if not hook_text: @@ -1052,7 +1101,8 @@ def create_cover_image(hook_text, width, height, output_path, video_path=None): bf = Image.blend(sharp, blurred, mix) else: bf = sharp - dim = Image.new("RGBA", (width, height), (0, 0, 0, 115)) + da = int(style.get("dim_alpha", 88)) + dim = Image.new("RGBA", (width, height), (0, 0, 0, da)) base = Image.alpha_composite(bf, dim) finally: try: @@ -1065,9 +1115,25 @@ def create_cover_image(hook_text, width, height, output_path, video_path=None): gdraw, width, height, VERTICAL_COVER_TOP, VERTICAL_COVER_BOTTOM, alpha=VERTICAL_COVER_ALPHA ) img = Image.alpha_composite(base, grad) - overlay = Image.new("RGBA", (width, height), (0, 0, 0, 60)) + overlay = Image.new("RGBA", (width, height), (0, 0, 0, 36)) img = Image.alpha_composite(img, overlay) + img = _apply_cover_bottom_vignette( + img, + float(style.get("vignette_from_ratio", 0.46)), + int(style.get("vignette_max_alpha", 138)), + ) draw = ImageDraw.Draw(img) + apx = int(style.get("top_accent_px", 6)) + draw.rectangle([0, 0, width, apx], fill=(*SOUL_GREEN, 255)) + if style.get("gold_hairline", True): + draw.rectangle([0, apx, width, apx + 1], fill=(*COVER_ACCENT_GOLD, 105)) + fa = int(style.get("frame_inset_alpha", 42)) + if fa > 0: + draw.rectangle( + [2, 2, width - 3, height - 3], + outline=(255, 255, 255, fa), + width=1, + ) else: # 横版:清晰帧 + 少量模糊混入(与竖条封面一致) if video_path and os.path.exists(video_path): @@ -1095,29 +1161,59 @@ def create_cover_image(hook_text, width, height, output_path, video_path=None): img = Image.alpha_composite(img, overlay) draw = ImageDraw.Draw(img) - # Soul 绿装饰线(顶部、底部) - for i in range(3): - alpha = 180 - i * 50 - draw.rectangle([0, i * 3, width, i * 3 + 2], fill=(*SOUL_GREEN, alpha)) - for i in range(3): - alpha = 180 - i * 50 - draw.rectangle([0, height - i * 3 - 2, width, height - i * 3], fill=(*SOUL_GREEN, alpha)) - - # 左上角 Soul logo 小图标(绿圆 + 白字 S),保证在界面内 - logo_x, logo_y = 28, 28 - logo_r = 20 - draw.ellipse([logo_x - logo_r, logo_y - logo_r, logo_x + logo_r, logo_y + logo_r], fill=SOUL_GREEN, outline=(255, 255, 255)) + # 横版保留原「上下 Soul 绿条」;竖屏已用顶栏 + 渐隐,不再叠多条绿边 + if not is_vertical: + for i in range(3): + alpha = 180 - i * 50 + draw.rectangle([0, i * 3, width, i * 3 + 2], fill=(*SOUL_GREEN, alpha)) + for i in range(3): + alpha = 180 - i * 50 + draw.rectangle([0, height - i * 3 - 2, width, height - i * 3], fill=(*SOUL_GREEN, alpha)) + + apx_v = int(style.get("top_accent_px", 6)) if is_vertical else 0 + hair_v = (1 if style.get("gold_hairline", True) else 0) if is_vertical else 0 + top_bar_h = apx_v + hair_v + if is_vertical: + logo_x, logo_y = 30, max(34, top_bar_h + 18) + logo_r = 21 + draw.ellipse( + [ + logo_x - logo_r - 2, + logo_y - logo_r - 2, + logo_x + logo_r + 2, + logo_y + logo_r + 2, + ], + outline=(255, 255, 255, 100), + width=2, + ) + draw.ellipse( + [logo_x - logo_r, logo_y - logo_r, logo_x + logo_r, logo_y + logo_r], + fill=SOUL_GREEN, + outline=(255, 255, 255, 165), + width=1, + ) + else: + logo_x, logo_y = 28, 28 + logo_r = 20 + draw.ellipse( + [logo_x - logo_r, logo_y - logo_r, logo_x + logo_r, logo_y + logo_r], + fill=SOUL_GREEN, + outline=(255, 255, 255), + ) try: - logo_font = get_cover_font(26) - draw.text((logo_x - 5, logo_y - 12), "S", font=logo_font, fill=(255, 255, 255)) + logo_font = get_cover_font(27 if is_vertical else 26) + sx = logo_x - (6 if is_vertical else 5) + sy = logo_y - (13 if is_vertical else 12) + draw.text((sx, sy), "S", font=logo_font, fill=(255, 255, 255)) except Exception: pass # 标题文字:竖屏时严格限制在 padding 内,多行居中,绝不超出界面 if is_vertical: max_text_width = width - 2 * VERTICAL_COVER_PADDING - cover_font_size = 48 + cover_font_size = 50 font = get_cover_font(cover_font_size) + vfill = hook_style.get("vertical_fill", (252, 252, 250)) lines = [] for _ in range(20): current_line = "" @@ -1145,12 +1241,7 @@ def create_cover_image(hook_text, width, height, output_path, video_path=None): x = (width - line_w) // 2 x = max(VERTICAL_COVER_PADDING, min(width - VERTICAL_COVER_PADDING - line_w, x)) y = start_y + i * line_height - draw_text_with_outline( - draw, (x, y), line, font, - hook_style['color'], - hook_style['outline_color'], - min(hook_style['outline_width'], 3) - ) + draw_text_with_soft_shadow(draw, (x, y), line, font, vfill) else: cover_font_size = hook_style['font_size'] font = get_cover_font(cover_font_size) @@ -2074,7 +2165,7 @@ def generate_index(highlights, output_dir): with open(index_path, 'w', encoding='utf-8') as f: f.write("# Soul派对 - 成片目录\n\n") f.write( - "**优化**: 高光 Hook 封面(强模糊底)+逐字字幕+去长静音+片尾 CTA+加速10%(竖屏宽随 crop-vf)\n\n" + "**优化**: 高光 Hook 封面(轻模糊底+冷色渐变+底渐隐+顶栏品牌色)+逐字字幕+去长静音+片尾 CTA+加速10%(竖屏宽随 crop-vf)\n\n" ) f.write("## 切片列表\n\n") f.write("| 序号 | 标题 | Hook | CTA |\n") diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/REFERENCE_开放能力_数据与集成.md b/03_卡木(木)/木叶_视频内容/视频号发布/REFERENCE_开放能力_数据与集成.md new file mode 100644 index 00000000..efb58252 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频号发布/REFERENCE_开放能力_数据与集成.md @@ -0,0 +1,151 @@ +# 视频号:开放能力 × 助手 Web API × 本项目集成参考 + +> 用途:把**视频号助手后台「开放能力」**、**微信开放平台「视频号助手 API」**与木叶现有**短视频纯 API 发布**放在一张图里,便于排需求、写脚本、对接运营数据。 +> 官方总入口: +> 接口列表页: + +--- + +## 一、两条轨道(不要混用) + +| 轨道 | 凭证 | 典型用途 | 本项目落点 | +|------|------|----------|------------| +| **A. 视频号助手网页(逆向/会话)** | `channels_storage_state.json`、`channels_token.json`、扫码 Cookie | **短视频上传与发布**(DFS + `post_create`) | `脚本/channels_api_publish.py`、`channels_login.py` | +| **B. 开放平台「视频号助手 API」** | **AppID + AppSecret** → `access_token` / `stable_token` | **直播记录、预约、橱窗、留资、大屏、罗盘**等官方服务端接口 | 见下文「官方接口清单」;凭证见 `credentials/README.md` | + +**边界(务必对齐多平台分发 Skill):** +微信**未开放**「用开放平台 API 直接上传/发布短视频」;短视频自动化目前只可走 **A 轨**。 +**B 轨**负责经营数据、带货、留资、直播场次等**合规官方能力**。 + +--- + +## 二、助手后台「开放能力」能联想到的产品动作(想象力清单) + +以下需在后台开通对应权限;是否开放以微信后台与文档为准。 + +### 2.1 直播全链路 + +| 阶段 | 可设想动作 | 依赖的官方能力方向 | +|------|------------|-------------------| +| 播前 | 把「直播预约」同步到飞书日历 / 派对排期表 | 直播预约记录 API | +| 播中 | 电商直播间指标看板(内部大屏,非替代微信客户端开播) | 直播大屏(**仅电商直播间**) | +| 播后 | 场次列表与 Soul 第 N 场对齐、自动写运营报表 | 直播记录 API | +| 转化 | 留资进多维表 / Mongo,再分给私域跟进 | 留资组件 + 留资直播数据 API | +| 带货 | 排品脚本:按策略上下架橱窗 | 橱窗管理 API | +| 复盘 | 周/月带货与人群报告自动生成 | 罗盘达人版 API | + +### 2.2 与小程序/私域联动(另一条文档线) + +- 同主体或关联主体小程序:`wx.getChannelsLiveInfo`、`wx.openChannelsLive`、`wx.reserveChannelsLive`、`channel-live` 等(打开/预约/内嵌直播),与 **A/B 轨互补**。 +- 文档: + +### 2.3 当前**未**在官方「助手 API」里直接等价的能力(预期管理) + +- 用 HTTP **代替手机/OBS 发起推流、开关播**(一般仍走客户端)。 +- **实时弹幕/评论流**全量拉取(若有,以单独公告/文档为准,勿与下表混为一谈)。 + +--- + +## 三、视频号助手 API(官方)— 与直播/经营强相关的路径速查 + +摘自接口列表页(路径以文档最新版为准)。 + +### 3.1 直播基础信息 + +| 说明 | 路径 | +|------|------| +| 获取当前直播记录 | `/channels/ec/finderlive/getfinderliverecordlist` | +| 获取当前直播预约记录 | `/channels/ec/finderlive/getfinderlivenoticerecordlist` | + +### 3.2 橱窗管理(带货) + +| 说明 | 路径 | +|------|------| +| 上架到橱窗 | `/channels/ec/window/product/add` | +| 橱窗商品详情 | `/channels/ec/window/product/get` | +| 橱窗商品列表 | `/channels/ec/window/product/list/get` | +| 下架 | `/channels/ec/window/product/off` | + +### 3.3 留资组件 + +| 说明 | 路径 | +|------|------| +| 按直播场次取留资详情 | `/channels/leads/get_leads_info_by_request_id` | +| 按时间取留资详情 | `/channels/leads/get_leads_info_by_component_id` | +| 留资 request_id 列表 | `/channels/leads/get_leads_request_id` | +| 留资组件直播推广记录 | `/channels/leads/get_leads_component_promote_record` | +| 留资组件 ID 列表 | `/channels/leads/get_leads_component_id` | + +### 3.4 留资相关直播数据 + +| 说明 | 路径 | +|------|------| +| 视频号账号信息 | `/channels/finderlive/get_finder_attr_by_appid` | +| 留资直播数据详情 | `/channels/finderlive/get_finder_live_data_list` | +| 账号留资数量 | `/channels/finderlive/get_finder_live_leads_data` | + +### 3.5 直播大屏 + +| 说明 | 路径 | +|------|------| +| 大屏直播列表(文档注明:仅电商直播间) | `/channels/livedashboard/getlivelist` | +| 大屏数据 | `/channels/livedashboard/getlivedata` | + +### 3.6 罗盘达人版(带货复盘) + +| 说明 | 路径 | +|------|------| +| 电商概览 | `/channels/ec/compass/finder/overall/get` | +| 带货商品数据 | `/channels/ec/compass/finder/product/data/get` | +| 带货商品列表 | `/channels/ec/compass/finder/product/list/get` | +| 带货人群数据 | `/channels/ec/compass/finder/sale/profile/data/get` | + +### 3.7 通用基础 + +- 取 token:`/cgi-bin/token`、`/cgi-bin/stable_token`(服务端长期跑任务建议了解 stable_token 策略)。 +- 额度:`/cgi-bin/openapi/quota/*`(以文档为准)。 + +--- + +## 四、A 轨:短视频「上传/发布」在本项目中的真实步骤(非开放平台) + +与 `SKILL.md` 一致,便于和 B 轨对照: + +1. `channels_login.py` → 落盘 `channels_storage_state.json`(及中央 Cookie 同步,见多平台分发 Skill)。 +2. `helper_upload_params` → DFS `applyuploaddfs` / `uploadpartdfs` / `completepartuploaddfs`。 +3. `post_create`(需 `finger-print-device-id`、`x-wechat-uin` 等)。 +4. 去重:`多平台分发/脚本/publish_log.jsonl`(若走 distribute 链路)。 + +--- + +## 五、推荐集成顺序(给「smart / 运营自动化」排期用) + +1. **已有**:A 轨短视频发布 + 分发日志(保持现状)。 +2. **先做低风险**:`getfinderliverecordlist` + `getfinderlivenoticerecordlist` → 写入飞书运营表或本地 JSONL。 +3. **有留资组件时**:接 `/channels/leads/*` 与 `get_finder_live_*`。 +4. **带货号**:橱窗 + 罗盘 + 大屏(确认账号类型是否电商直播)。 +5. **小程序**:与永平/soul 小程序需求单独立评审(主体绑定条件)。 + +--- + +## 六、凭证与操作约定 + +- **开放平台 AppID / AppSecret**:只放在 `credentials/.env.open_platform`(已被仓库根 `.gitignore` 的 `.env.*` 规则忽略),**永不提交 Git**。 +- **网页会话**:继续用 `脚本/channels_storage_state.json` 等;轮换策略见 `视频号发布/SKILL.md`。 +- 详细变量名与加载方式:`credentials/README.md`。 + +--- + +## 七、相关文件索引 + +| 文件 | 作用 | +|------|------| +| `视频号发布/SKILL.md` | A 轨发布流程与边界 | +| `视频号发布/REFERENCE_开放能力_数据与集成.md` | 本文:B 轨 + 集成脑图 | +| `视频号发布/credentials/README.md` | TOKEN 与环境变量约定 | +| `多平台分发/SKILL.md` | 全平台分发与视频号 Cookie 策略 | +| `脚本/channels_api_publish.py` | A 轨主脚本 | + +--- + +*文档版本:2026-03-23 · 木叶* diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/SKILL.md b/03_卡木(木)/木叶_视频内容/视频号发布/SKILL.md index 9bfab67f..8e2b6593 100644 --- a/03_卡木(木)/木叶_视频内容/视频号发布/SKILL.md +++ b/03_卡木(木)/木叶_视频内容/视频号发布/SKILL.md @@ -6,8 +6,8 @@ description: > triggers: 视频号发布、发布到视频号、视频号登录、视频号上传、微信视频号 owner: 木叶 group: 木 -version: "3.0" -updated: "2026-03-10" +version: "3.1" +updated: "2026-03-23" --- # 视频号发布 Skill(v3.0) @@ -115,6 +115,10 @@ python3 channels_api_publish.py | 文件 | 说明 | |------|------| +| `REFERENCE_开放能力_数据与集成.md` | **开放能力 + 官方助手 API + 直播/数据/橱窗/留资** 整合参考(与 A 轨发布对照) | +| `credentials/README.md` | **开放平台 AppID/AppSecret** 存放约定(`.env.open_platform`,勿提交) | +| `credentials/open_platform.env.example` | 环境变量模板 | +| `脚本/channels_open_fetch.py` | **开放平台**:拉账号/直播记录/预约/罗盘 GMV(无单条短视频播放接口) | | `脚本/channels_api_publish.py` | **主脚本**:纯 API 视频上传+发布 (v5) | | `脚本/channels_publish.py` | 旧版 Playwright 发布(备用) | | `脚本/channels_login.py` | Playwright 微信扫码登录 | diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/credentials/README.md b/03_卡木(木)/木叶_视频内容/视频号发布/credentials/README.md new file mode 100644 index 00000000..6e646286 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频号发布/credentials/README.md @@ -0,0 +1,45 @@ +# 视频号凭证目录(开放平台) + +## 用途 + +- **微信开放平台**绑定的视频号业务:用 **AppID + AppSecret** 换取 `access_token` / `stable_token`,再调用 [视频号助手 API](https://developers.weixin.qq.com/doc/channels/api/)。 +- 与 **网页扫码**得到的 `../脚本/channels_storage_state.json` **不是同一套凭证**,不要混在一个文件里。 + +## 标准做法(以后都按这个来) + +1. 在本目录维护 **`.env.open_platform`**(文件名以 `.env.` 开头,已被卡若AI 仓库根 `.gitignore` 中 `.env.*` 忽略,**不会进 Git**)。 +2. 需要示例可复制 **`open_platform.env.example`** 为 `.env.open_platform` 再填值。 +3. 脚本中读取示例(Python): + +```python +from pathlib import Path + +def load_open_platform_env(): + p = Path(__file__).resolve().parent.parent / "credentials" / ".env.open_platform" + if not p.exists(): + return {} + out = {} + for line in p.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#"): + continue + if "=" in line: + k, _, v = line.partition("=") + out[k.strip()] = v.strip().strip('"').strip("'") + return out +``` + +4. 取 token:按官方文档调用 `https://api.weixin.qq.com/cgi-bin/stable_token` 或 `token`(grant_type=client_credential),**不要把 token 写进本仓库**;可只在内存或本机另一个忽略文件里缓存。 + +## 变量说明 + +| 变量 | 必填 | 说明 | +|------|------|------| +| `WECHAT_OPEN_APPID` | 是 | 开放平台应用 AppID | +| `WECHAT_OPEN_APPSECRET` | 是 | 开放平台应用 AppSecret | +| `WECHAT_OPEN_STABLE_TOKEN` | 否 | 若你手动缓存 stable_token,可放这里(仍勿提交);否则留空每次用 Secret 换 | + +## 安全 + +- **AppSecret 泄露 = 立刻到公众平台重置**,并更新本文件。 +- 勿把本目录任何含 Secret 的文件拖进聊天、截图、PR。 diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/credentials/open_platform.env.example b/03_卡木(木)/木叶_视频内容/视频号发布/credentials/open_platform.env.example new file mode 100644 index 00000000..6f0f1a49 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频号发布/credentials/open_platform.env.example @@ -0,0 +1,5 @@ +# 复制为同目录下的 .env.open_platform 后填写(.env.open_platform 已被 .gitignore 忽略) +WECHAT_OPEN_APPID= +WECHAT_OPEN_APPSECRET= +# 可选:长期 stable_token 本地缓存(勿提交) +# WECHAT_OPEN_STABLE_TOKEN= diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_api_publish.py b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_api_publish.py index 5ee7fdc0..591099f2 100644 --- a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_api_publish.py +++ b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_api_publish.py @@ -42,7 +42,7 @@ try: except ImportError: VideoMeta = None -DESC_SUFFIX = " #小程序 卡若创业派对" +DESC_SUFFIX = " #小程序卡若创业派对 #公众号卡若-4点起床的男人" MINI_PROGRAM_LINK = "#小程序://卡若创业派对/gF4V8Vo4Ws4IiJa" CHUNK_SIZE = 8 * 1024 * 1024 @@ -692,13 +692,33 @@ async def _ensure_ctx() -> dict: return _ctx +def _scheduled_ts_for_channels(scheduled_time) -> int: + """ + distribute_all 传入 datetime;首条为「立即」时时间≈当前,应走 postTimingInfo 省略(立即发表)。 + 与 schedule_generator 一致:仅当发布时间明显在未来时才传定时。 + """ + if scheduled_time is None: + return 0 + from datetime import datetime + + if isinstance(scheduled_time, datetime): + ts = int(scheduled_time.timestamp()) + else: + ts = int(scheduled_time) + now = int(time.time()) + # 2 分钟内视为「立即」,避免 postTime 过近被服务端拒绝 + if ts <= now + 120: + return 0 + return ts + + async def publish_one_compat( video_path: str, title: str, idx: int, total: int, scheduled_time=None, ) -> PublishResult: """distribute_all.py 调用的简化接口""" ctx = await _ensure_ctx() - sched_ts = int(scheduled_time) if scheduled_time else 0 + sched_ts = _scheduled_ts_for_channels(scheduled_time) result = await publish_one( ctx["cookie_str"], ctx["finder_id"], ctx["uin"], ctx["finger_print"], ctx["aid"], diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_login.py b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_login.py index d1a74c87..d3468c58 100644 --- a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_login.py +++ b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_login.py @@ -1,8 +1,12 @@ #!/usr/bin/env python3 -"""视频号登录 v6 — 等待完整登录流程完成后提取 Cookie + rawKeyBuff""" +"""视频号登录 v7 — 优先 Cursor 内置 Simple Browser 扫码;会话落盘优先 CDP 附着 Cursor,失败再回退 Playwright。""" import asyncio import json +import os +import shutil +import subprocess import sys +import urllib.parse from pathlib import Path from playwright.async_api import async_playwright @@ -13,6 +17,8 @@ TOKEN_FILE = SCRIPT_DIR / "channels_token.json" LOGIN_URL = "https://channels.weixin.qq.com/login" QR_SCREENSHOT = Path("/tmp/channels_qr.png") +DEFAULT_CDP = os.environ.get("CHANNELS_CDP_URL", "http://127.0.0.1:9223") + UA = ( "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) " "AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36" @@ -24,6 +30,35 @@ REQUIRED_LS_KEYS = [ ] +def open_cursor_simple_browser(url: str) -> None: + """在 Cursor 编辑器内打开 Simple Browser(不唤起系统默认外部浏览器)。""" + enc = urllib.parse.quote(url, safe="") + deeplink = f"cursor://vscode.simple-browser/show?url={enc}" + try: + if sys.platform == "darwin": + subprocess.run(["open", deeplink], check=False) + elif sys.platform == "win32": + os.startfile(deeplink) # type: ignore[attr-defined] + else: + subprocess.run(["xdg-open", deeplink], check=False) + print(f"[✓] 已在 Cursor 内请求打开 Simple Browser(若未弹出:Cmd/Ctrl+Shift+P → Simple Browser: Show → 粘贴 URL)", flush=True) + print(f" URL: {url}", flush=True) + except Exception as e: + print(f"[!] 打开 Cursor Simple Browser 失败: {e}", flush=True) + print(f" 请手动在 Cursor 命令面板执行 Simple Browser: Show,粘贴: {url}", flush=True) + + +def _sync_to_central_cookie_store() -> None: + try: + _central = SCRIPT_DIR.parent.parent / "多平台分发" / "cookies" / "视频号_cookies.json" + _central.parent.mkdir(parents=True, exist_ok=True) + if COOKIE_FILE.exists(): + shutil.copy2(COOKIE_FILE, _central) + print(f"[✓] 已同步中央 Cookie: {_central}", flush=True) + except Exception as e: + print(f"[!] 同步中央 Cookie 失败: {e}", flush=True) + + async def extract_ls(page, keys): try: return await page.evaluate("""(keys) => { @@ -39,99 +74,51 @@ async def extract_ls(page, keys): return {} -async def main(): - print("即将弹出浏览器,请用微信扫码登录视频号助手。", flush=True) - print("登录后请等待页面跳转到「内容管理」页面,不要手动关闭浏览器。\n", flush=True) - - async with async_playwright() as pw: - browser = await pw.chromium.launch(headless=False) - context = await browser.new_context( - user_agent=UA, viewport={"width": 1280, "height": 720} - ) - await context.add_init_script( - "Object.defineProperty(navigator,'webdriver',{get:()=>undefined})" - ) - page = await context.new_page() - await page.goto(LOGIN_URL, timeout=60000) +async def wait_until_logged_in(page, label: str = "") -> bool: + prefix = f"[{label}] " if label else "" + for i in range(120): await asyncio.sleep(3) - - await page.screenshot(path=str(QR_SCREENSHOT)) - print(f"[QR] 二维码截图已保存: {QR_SCREENSHOT}", flush=True) - print("请用微信扫描浏览器中的二维码...\n", flush=True) - - # 只等待 URL 跳转到平台页面(不提前因 cookie 退出循环) - logged_in = False - for i in range(120): - await asyncio.sleep(3) - try: - url = page.url - except Exception: - break - - if "platform" in url and "login" not in url: - logged_in = True - print(f"[✓] 登录成功,已跳转到: {url[:100]}", flush=True) - break - - if i % 10 == 0: - print(f" 等待扫码并跳转中... ({i * 3}s)", flush=True) - - if not logged_in: - print("[✗] 6 分钟超时。", flush=True) - await browser.close() - return 1 - - # 等待页面 JS 执行完成(微前端加载、localStorage 写入) - print("[i] 等待平台 JS 加载完成...", flush=True) - await asyncio.sleep(10) - - # 提取 localStorage - ls_vals = {} - for attempt in range(60): - ls_vals = await extract_ls(page, REQUIRED_LS_KEYS) - if ls_vals.get("finder_raw"): - print(f"[✓] rawKeyBuff 已就绪 (等待 {attempt}s)", flush=True) - break - await asyncio.sleep(1) - if attempt % 15 == 14: - print(f" 等待 localStorage 写入... ({attempt + 1}s)", flush=True) - - # 如果 finder_raw 还是空,尝试点击内容管理触发加载 - if not ls_vals.get("finder_raw"): - print("[i] rawKeyBuff 未出现,尝试访问内容列表页...", flush=True) - try: - await page.goto( - "https://channels.weixin.qq.com/platform/post/list", - timeout=15000, wait_until="domcontentloaded", - ) - await asyncio.sleep(8) - for _ in range(30): - ls_vals = await extract_ls(page, REQUIRED_LS_KEYS) - if ls_vals.get("finder_raw"): - print("[✓] 导航后 rawKeyBuff 已就绪", flush=True) - break - await asyncio.sleep(1) - except Exception as e: - print(f"[!] 导航异常: {e}", flush=True) - - # 显示提取结果 - print("\n[i] localStorage 提取结果:", flush=True) - for k in REQUIRED_LS_KEYS: - v = ls_vals.get(k, "") - status = "✓" if v else "✗" - display = f"{v[:60]}..." if len(v) > 60 else (v or "(空)") - print(f" {status} {k}: {display}", flush=True) - - # 保存 storage_state try: - await context.storage_state(path=str(COOKIE_FILE)) - except Exception as e: - print(f"[!] 保存 storage_state 异常: {e}", flush=True) + url = page.url + except Exception: + break + if "platform" in url and "login" not in url: + print(f"{prefix}[✓] 登录成功,已跳转到: {url[:100]}", flush=True) + return True + if i % 10 == 0: + print(f"{prefix} 等待扫码并跳转中... ({i * 3}s)", flush=True) + print(f"{prefix}[✗] 6 分钟超时。", flush=True) + return False + + +async def save_session_from_context(context, page, ls_vals: dict) -> bool: + try: + state = await context.storage_state() + # 合并我们显式抓到的 finder keys(防止 storage_state 未含 localStorage) + origins = list(state.get("origins") or []) + merged_items = {} + for origin_block in origins: + if origin_block.get("origin") == "https://channels.weixin.qq.com": + for it in origin_block.get("localStorage") or []: + merged_items[it["name"]] = it.get("value", "") + for k, v in ls_vals.items(): + if v: + merged_items[k] = str(v) + new_ls = [{"name": k, "value": v} for k, v in merged_items.items()] + replaced = False + for ob in origins: + if ob.get("origin") == "https://channels.weixin.qq.com": + ob["localStorage"] = new_ls + replaced = True + break + if not replaced and new_ls: + origins.append({"origin": "https://channels.weixin.qq.com", "localStorage": new_ls}) + state["origins"] = origins + COOKIE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2)) + _sync_to_central_cookie_store() - # 保存 token 信息 cookies = await context.cookies() cookie_dict = {c["name"]: c["value"] for c in cookies} - token_data = { "sessionid": cookie_dict.get("sessionid", ""), "wxuin": cookie_dict.get("wxuin", ""), @@ -143,19 +130,182 @@ async def main(): "url": page.url, } TOKEN_FILE.write_text(json.dumps(token_data, ensure_ascii=False, indent=2)) + return bool(ls_vals.get("finder_raw")) + except Exception as e: + print(f"[!] 保存会话异常: {e}", flush=True) + return False - await browser.close() - print(f"\n[✓] Cookie 已保存: {COOKIE_FILE}", flush=True) - print(f"[✓] Token 已保存: {TOKEN_FILE}", flush=True) +async def try_capture_via_cdp(pw, cdp_url: str) -> bool: + """附着已带 --remote-debugging-port 的 Cursor/Chromium,从 Simple Browser 所在上下文导出。""" try: - sc = json.loads(COOKIE_FILE.read_text()).get("cookies", []) - print(f" Cookie 数量: {len(sc)}", flush=True) - except Exception: - pass - raw = ls_vals.get("finder_raw", "") - print(f" rawKeyBuff: {'✓ ' + raw[:30] + '...' if raw else '✗ 未提取到'}", flush=True) - return 0 if ls_vals.get("finder_raw") else 1 + browser = await pw.chromium.connect_over_cdp(cdp_url, timeout=8000) + except Exception as e: + print(f"[i] CDP 未连接 ({cdp_url}): {e}", flush=True) + return False + + target_page = None + for ctx in browser.contexts: + for page in ctx.pages: + u = page.url or "" + if "channels.weixin.qq.com" in u: + target_page = page + break + if target_page: + break + + if not target_page: + print("[!] CDP 已连接但未找到 channels.weixin.qq.com 页面(请确认已在 Cursor Simple Browser 打开视频号助手)", flush=True) + await browser.close() + return False + + ctx = target_page.context + print("[i] 已附着 Cursor/内置浏览器上下文,等待进入内容管理页…", flush=True) + for _ in range(120): + try: + u = target_page.url or "" + if "platform" in u and "login" not in u: + break + except Exception: + pass + await asyncio.sleep(3) + + print("[i] 等待 rawKeyBuff / localStorage 写入…", flush=True) + + ls_vals = {} + for attempt in range(120): + ls_vals = await extract_ls(target_page, REQUIRED_LS_KEYS) + if ls_vals.get("finder_raw"): + print(f"[✓] rawKeyBuff 已就绪 (CDP, {attempt}s)", flush=True) + break + try: + u = target_page.url or "" + if "login" in u and attempt > 20: + pass + except Exception: + pass + if attempt % 15 == 0 and attempt > 0: + print(f" 等待 localStorage… ({attempt}s)", flush=True) + await asyncio.sleep(1) + + if not ls_vals.get("finder_raw"): + try: + await target_page.goto( + "https://channels.weixin.qq.com/platform/post/list", + timeout=20000, + wait_until="domcontentloaded", + ) + await asyncio.sleep(8) + for _ in range(40): + ls_vals = await extract_ls(target_page, REQUIRED_LS_KEYS) + if ls_vals.get("finder_raw"): + print("[✓] 导航后 rawKeyBuff 已就绪 (CDP)", flush=True) + break + await asyncio.sleep(1) + except Exception as e: + print(f"[!] CDP 导航补全失败: {e}", flush=True) + + ok = await save_session_from_context(ctx, target_page, ls_vals) + await browser.close() + return ok + + +async def capture_via_playwright_external() -> bool: + """回退:独立 Chromium 窗口(仅当 CDP 不可用时)。""" + print("\n[i] 未使用 CDP → 将打开本机 Chromium 窗口仅用于写入 Cookie 文件(可扫码后尽快关闭)\n", flush=True) + ls_vals = {} + async with async_playwright() as pw: + browser = await pw.chromium.launch(headless=False) + context = await browser.new_context(user_agent=UA, viewport={"width": 1280, "height": 720}) + await context.add_init_script( + "Object.defineProperty(navigator,'webdriver',{get:()=>undefined})" + ) + page = await context.new_page() + await page.goto(LOGIN_URL, timeout=60000) + await asyncio.sleep(3) + await page.screenshot(path=str(QR_SCREENSHOT)) + print(f"[QR] 二维码截图: {QR_SCREENSHOT}", flush=True) + + if not await wait_until_logged_in(page, "Playwright"): + await browser.close() + return False + + print("[i] 等待平台 JS 加载完成...", flush=True) + await asyncio.sleep(10) + + for attempt in range(60): + ls_vals = await extract_ls(page, REQUIRED_LS_KEYS) + if ls_vals.get("finder_raw"): + print(f"[✓] rawKeyBuff 已就绪 (等待 {attempt}s)", flush=True) + break + await asyncio.sleep(1) + if attempt % 15 == 14: + print(f" 等待 localStorage 写入... ({attempt + 1}s)", flush=True) + + if not ls_vals.get("finder_raw"): + print("[i] rawKeyBuff 未出现,尝试访问内容列表页...", flush=True) + try: + await page.goto( + "https://channels.weixin.qq.com/platform/post/list", + timeout=15000, + wait_until="domcontentloaded", + ) + await asyncio.sleep(8) + for _ in range(30): + ls_vals = await extract_ls(page, REQUIRED_LS_KEYS) + if ls_vals.get("finder_raw"): + print("[✓] 导航后 rawKeyBuff 已就绪", flush=True) + break + await asyncio.sleep(1) + except Exception as e: + print(f"[!] 导航异常: {e}", flush=True) + + print("\n[i] localStorage 提取结果:", flush=True) + for k in REQUIRED_LS_KEYS: + v = ls_vals.get(k, "") + status = "✓" if v else "✗" + display = f"{v[:60]}..." if len(v) > 60 else (v or "(空)") + print(f" {status} {k}: {display}", flush=True) + + ok = await save_session_from_context(context, page, ls_vals) + await browser.close() + return ok + + +async def main() -> int: + force_pw = "--playwright-only" in sys.argv or os.environ.get("CHANNELS_LOGIN_PLAYWRIGHT_ONLY") + skip_cursor_tab = "--no-cursor-tab" in sys.argv + cdp_url = DEFAULT_CDP + + if not force_pw: + if not skip_cursor_tab: + print("→ 优先在 Cursor 内置 Simple Browser 打开视频号登录页(不打开系统默认浏览器)。\n", flush=True) + open_cursor_simple_browser(LOGIN_URL) + print( + "\n请在上一步打开的 **Cursor 编辑器内** Simple Browser 中用微信扫码并进入「内容管理」。\n" + f"若要让脚本**不弹额外 Chromium**:请用带远程调试端口的方式启动 Cursor,例如 macOS:\n" + f" /Applications/Cursor.app/Contents/MacOS/Cursor --remote-debugging-port=9223\n" + f"然后本脚本会通过 CDP ({cdp_url}) 读取会话并写入 Cookie。\n", + flush=True, + ) + if sys.stdin.isatty(): + try: + input("登录完成并进入内容管理后,按回车继续拉取 Cookie…\n") + except EOFError: + pass + else: + print("[i] 非交互终端:将轮询 CDP 最多约 6 分钟…", flush=True) + await asyncio.sleep(5) + + async with async_playwright() as pw: + if not force_pw: + if await try_capture_via_cdp(pw, cdp_url): + print(f"\n[✓] Cookie 已保存: {COOKIE_FILE}", flush=True) + return 0 + + ok = await capture_via_playwright_external() + print(f"\n[{'✓' if ok else '✗'}] Cookie 保存: {COOKIE_FILE}", flush=True) + return 0 if ok else 1 if __name__ == "__main__": diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_open_fetch.py b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_open_fetch.py new file mode 100644 index 00000000..5c86a561 --- /dev/null +++ b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_open_fetch.py @@ -0,0 +1,99 @@ +#!/usr/bin/env python3 +""" +视频号开放平台(助手 API)一键拉数:账号信息、直播记录、直播预约、罗盘日 GMV(含短视频成交字段)。 +凭证:../credentials/.env.open_platform(勿提交) + +说明:官方「视频号助手 API」不提供单条短视频播放量/列表;短视频维度仅有罗盘里的带货成交等经营指标(若有)。 +""" +from __future__ import annotations + +import json +import os +import sys +import urllib.parse +import urllib.request +from datetime import datetime, timedelta, timezone +from pathlib import Path + +SCRIPT_DIR = Path(__file__).resolve().parent +CRED_PATH = SCRIPT_DIR.parent / "credentials" / ".env.open_platform" +TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token" +API = "https://api.weixin.qq.com" + + +def load_env(path: Path) -> dict[str, str]: + if not path.exists(): + print(f"[!] 缺少 {path}", file=sys.stderr) + sys.exit(1) + out: dict[str, str] = {} + for line in path.read_text(encoding="utf-8").splitlines(): + line = line.strip() + if not line or line.startswith("#") or "=" not in line: + continue + k, _, v = line.partition("=") + out[k.strip()] = v.strip().strip('"').strip("'") + return out + + +def http_json(url: str, method: str = "GET", body: dict | None = None) -> dict: + data = None if body is None else json.dumps(body).encode("utf-8") + req = urllib.request.Request(url, data=data, method=method) + if body is not None: + req.add_header("Content-Type", "application/json") + with urllib.request.urlopen(req, timeout=45) as r: + return json.loads(r.read().decode()) + + +def get_access_token(appid: str, secret: str) -> str: + q = urllib.parse.urlencode( + {"grant_type": "client_credential", "appid": appid, "secret": secret} + ) + j = http_json(f"{TOKEN_URL}?{q}") + if "access_token" not in j: + raise RuntimeError(json.dumps(j, ensure_ascii=False)) + return j["access_token"] + + +def post_channels(at: str, path: str, body: dict | None = None) -> dict: + url = f"{API}{path}?access_token={at}" + return http_json(url, "POST", body or {}) + + +def main() -> None: + env = load_env(CRED_PATH) + appid = env.get("WECHAT_OPEN_APPID") + secret = env.get("WECHAT_OPEN_APPSECRET") + if not appid or not secret: + print("[!] .env.open_platform 需含 WECHAT_OPEN_APPID / WECHAT_OPEN_APPSECRET", file=sys.stderr) + sys.exit(1) + + at = get_access_token(appid, secret) + tz8 = timezone(timedelta(hours=8)) + today = datetime.now(tz8).strftime("%Y%m%d") + yesterday = (datetime.now(tz8) - timedelta(days=1)).strftime("%Y%m%d") + + out: dict = { + "fetched_at": datetime.now(tz8).isoformat(), + "finder_attr": post_channels(at, "/channels/finderlive/get_finder_attr_by_appid", {}), + "live_records": post_channels(at, "/channels/ec/finderlive/getfinderliverecordlist", {}), + "live_notices": post_channels(at, "/channels/ec/finderlive/getfinderlivenoticerecordlist", {}), + "compass_overall_yesterday": post_channels( + at, "/channels/ec/compass/finder/overall/get", {"ds": yesterday} + ), + "compass_overall_today": post_channels( + at, "/channels/ec/compass/finder/overall/get", {"ds": today} + ), + "livedashboard_list": post_channels(at, "/channels/livedashboard/getlivelist", {}), + } + + # stdout:给管道 / 飞书脚本用 + print(json.dumps(out, ensure_ascii=False, indent=2)) + + out_path = os.environ.get("CHANNELS_OPEN_FETCH_OUT") + if out_path: + Path(out_path).write_text(json.dumps(out, ensure_ascii=False, indent=2), encoding="utf-8") + print(f"\n# 已写入 {out_path}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_storage_state.json b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_storage_state.json index 2b6019e3..05eb8559 100644 --- a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_storage_state.json +++ b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_storage_state.json @@ -1 +1,103 @@ -{"cookies": [{"name": "sessionid", "value": "BgAALavrrg26toSTr%2Fh%2BEjYhRF5AJMeU6M75ATmitpiad6Wdkb0bz%2F3pP5boxKqKwq6I8OPHiRN%2Bd5Ry%2BzSpNDTgyEqs%2BBPuLXWl3V3aQLE%3D", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1808208350.946172, "httpOnly": false, "secure": true, "sameSite": "None"}, {"name": "wxuin", "value": "1532657520", "domain": "channels.weixin.qq.com", "path": "/", "expires": 1808208350.946238, "httpOnly": false, "secure": true, "sameSite": "None"}], "origins": [{"origin": "https://channels.weixin.qq.com", "localStorage": [{"name": "finder_uin", "value": ""}, {"name": "__ml::hb_ts", "value": "1773649774818"}, {"name": "__ml::page_55c09d27-4e77-4e1a-83a2-728d84d27018", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"c5349852-948a-4dab-b23d-964658874bca\",\"step\":1}"}, {"name": "__ml::page_99c9284b-75d5-4758-aaf9-5dbad28105f6", "value": "{\"pageId\":\"Home\",\"accessId\":\"893b609d-78c1-46f6-9174-ae8e8c3d3ce3\",\"step\":1}"}, {"name": "__ml::aid", "value": "\"1ca6fac7-85e2-4f28-855c-df4c40221082\""}, {"name": "__rx::aid", "value": "\"1ca6fac7-85e2-4f28-855c-df4c40221082\""}, {"name": "__ml::page", "value": "[\"dc2e3985-9cd2-4f72-9984-76cec727ddc9\",\"99c9284b-75d5-4758-aaf9-5dbad28105f6\",\"081f33fb-313b-4752-8ac3-8d5b3a38a252\",\"55c09d27-4e77-4e1a-83a2-728d84d27018\",\"49142f80-9f4d-42cd-b292-16ec9915c6d1\",\"3767d201-d3bc-4f8f-8fe2-605fde26d7b5\",\"acfd0716-ce18-49bf-9173-18322dfbc18e\",\"e8545fd8-613a-45a7-83c2-b0b804f1bebf\",\"ad429f0d-93a2-4063-a079-9e143937a130\",\"d2a0bc32-b1a1-4345-bf63-271415c72b17\",\"eebe5414-9205-4ae6-8179-0923132d6cbd\",\"9c91dd6d-e545-4b0a-9a74-b6619f700f01\",\"255e7376-9950-4f54-998f-fdbcebd22282\"]"}, {"name": "finder_login_token", "value": ""}, {"name": "__ml::page_e8545fd8-613a-45a7-83c2-b0b804f1bebf", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"dffc797a-b48d-419e-8b76-5ed74d9aa941\",\"step\":1}"}, {"name": "__ml::page_49142f80-9f4d-42cd-b292-16ec9915c6d1", "value": "{\"pageId\":\"PostList\",\"accessId\":\"5a75b2ce-74de-4dc0-be5c-1dafae2a6554\",\"step\":1}"}, {"name": "__ml::page_9c91dd6d-e545-4b0a-9a74-b6619f700f01", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"f451bce9-8831-41d8-b5fb-eab580ef25c2\",\"step\":1}"}, {"name": "finder_username", "value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder"}, {"name": "__ml::page_acfd0716-ce18-49bf-9173-18322dfbc18e", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"1b9686bb-413c-4f07-818a-eb65cfe5d5f2\",\"step\":1}"}, {"name": "_finger_print_device_id", "value": "3338b937c86cc20c9f88aafb6bd238c3"}, {"name": "__ml::page_eebe5414-9205-4ae6-8179-0923132d6cbd", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"72d405a7-e188-4a5a-8bd5-69d3c55d0774\",\"step\":1}"}, {"name": "__ml::page_081f33fb-313b-4752-8ac3-8d5b3a38a252", "value": "{\"pageId\":\"PostCard\",\"accessId\":\"0d7eb408-2fc1-4607-9de5-90a59587025f\",\"step\":1}"}, {"name": "MICRO_VISITED_NAME", "value": "{\"postCard\":1,\"content\":5}"}, {"name": "__ml::page_3767d201-d3bc-4f8f-8fe2-605fde26d7b5", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"046aa319-b462-4b4c-ac94-9c6d5fb1e6bc\",\"step\":1}"}, {"name": "AssistantUploadedInfoStorageKey_3899420810", "value": "[{\"fileUploadedInfoKey\":\"AI\u4e00\u90e8\u52673000\u52305000\uff0c100\u90e8\u771f\u4eba\u5267\u7684\u6210\u672c\u505a100\u90e8AI\u5267.mp4:1773625634504:12071079:video/mp4:d56001d1b6b0dab34d3c120574442b33\",\"isUsedQuickUpload\":false,\"uploadChunkRecord\":[{\"index\":0,\"reqTime\":1773649163286,\"resTime\":1773649166572,\"cost\":3286,\"success\":true},{\"index\":1,\"reqTime\":1773649163303,\"resTime\":1773649165946,\"cost\":2643,\"success\":true}],\"uuid\":\"e588250d-4ba1-4f4a-b527-2d4b00501a9d\",\"uploadTaskId\":\"CkRhM2IyZTI1NDI5ZTBkNDM0ZDU4MDg0ZmZlNjkxNDE1YzE0M2NhMDc3ZTA4MzQ4Y2RiYTQxNjA4M2E4Yjg4ZjAwOTQwYxI+NTY5YjdiZDBiMDAwMzBkMTFlODZjNzA4YTAwMDAwMGZiMDAwMDRmNGU1MzVhMmE2OTIxYzE1NmE3ZWMwYTcwp+HgBQ==\",\"uploadTaskIdTimeStamp\":1773649163196,\"transFlag\":\"0_0\",\"partInfo\":[{\"PartNumber\":1,\"ETag\":\"\\\"8619cf3d8eb8e8b055a2a38a37d59943de6014b3\\\"\"},{\"PartNumber\":2,\"ETag\":\"\\\"949a4496e6b2c50e14d6b5247c72a1508ab329ce\\\"\"}],\"uploadSuccessResp\":{\"data\":{\"DownloadURL\":\"http://wxapp.tc.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3YiaX8QrZIfDcz9v2gp9V3WlTu8ghrDNWdrf6zGICVnibrj9y3VDRR0L19SWdGJE3YVIE0FTsXc6sC8lH84rHJZgSoMJrIR5KCib&findertoken=088ae1b1c30e108efadecd061800223d66696e64657275706c6f616475726c5f333839393432303831305f313737333634393136363735385f31303234303038393130393639383039313138362a2063353163363161316466316536306434663538646631393836663465393137323801400348005000580260ce9e01&hy=SZ&idx=1&m=&scene=2&token=AxricY7RBHdUHfCd22jdXFVRcWDcoU1nAaeqXAMRfqQiafs4HC0A3YYiaT66RtgFtxryCK8F0lTXw7Fvz2TDjZEiaK8yeURibO9rTFleu5QURVBWrTw7hRnxQDQ&uzid=7a206\",\"httpsUrl\":\"https://finder.video.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3YiaX8QrZIfDcz9v2gp9V3WlTu8ghrDNWdrf6zGICVnibrj9y3VDRR0L19SWdGJE3YVIE0FTsXc6sC8lH84rHJZgSoMJrIR5KCib&findertoken=088ae1b1c30e108efadecd061800223d66696e64657275706c6f616475726c5f333839393432303831305f313737333634393136363735385f31303234303038393130393639383039313138362a2063353163363161316466316536306434663538646631393836663465393137323801400348005000580260ce9e01&hy=SZ&idx=1&m=&scene=2&token=AxricY7RBHdUHfCd22jdXFVRcWDcoU1nAaeqXAMRfqQiafs4HC0A3YYiaT66RtgFtxryCK8F0lTXw7Fvz2TDjZEiaK8yeURibO9rTFleu5QURVBWrTw7hRnxQDQ&uzid=7a206\"}}},{\"fileUploadedInfoKey\":\"\u592a\u65e9\u5f15\u5165\u8d44\u672c\u4f60\u7684\u65b9\u5411\u5c31\u4e0d\u662f\u4f60\u8bf4\u4e86\u7b97.mp4:1773626238462:11906382:video/mp4:fea26a3291b49f05e1e6ee4ed89a3898\",\"isUsedQuickUpload\":false,\"uploadChunkRecord\":[{\"index\":1,\"reqTime\":1773649479300,\"resTime\":1773649480869,\"cost\":1569,\"success\":true},{\"index\":0,\"reqTime\":1773649479358,\"resTime\":1773649482775,\"cost\":3417,\"success\":true}],\"uuid\":\"f797e96c-5f9f-4888-8a45-da3c2bcb8f9e\",\"uploadTaskId\":\"CkQxNWEyZGE3MjVhNzVlMzU0OWMxOGQ5YzA1YjhiNDY5OGY1NWNjYTBiZjZlMTQzYzE5ZDVkNmY1YzU4ODM0NTAwYTUwYxI+NTY5YjdiZTQ3MDAwNDhkOWVlODZjNzA4YTAwMDAwMGZiMDAwMDRmNGU1MzQ4MjQ5ODRiYzFlNmI1NTdlYjkwztrWBQ==\",\"uploadTaskIdTimeStamp\":1773649479271,\"transFlag\":\"0_0\",\"partInfo\":[{\"PartNumber\":1,\"ETag\":\"\\\"82054ad7af5108da9ae5de5c335d63d57bdbbcd1\\\"\"},{\"PartNumber\":2,\"ETag\":\"\\\"71269f5ae09284b103018977894bcc85901c6dfd\\\"\"}],\"uploadSuccessResp\":{\"data\":{\"DownloadURL\":\"http://wxapp.tc.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQBcmO4C9neeSQgwibjlHhibu29vdN3uLyDdAiaicoxKwRQxky3MhicmzdosfqQjO7HDLG0MYjwMBnZOe6dly7icBzCfka&findertoken=088ae1b1c30e10cbfcdecd061800223c66696e64657275706c6f616475726c5f333839393432303831305f313737333634393438323939345f383030323237313735333431363136383932312a2034396638353736393239353231316366623536633764343638303237303634633801400348005000580260ce9e01&hy=SH&idx=1&m=&scene=2&token=x5Y29zUxcibB3KuWgGExa5A2alciahhWxPXFLLibiasNiaYSibEwlzoA5S0Aw0siaS677yoZ2PIZN53k3tz0oicftoS1YqlnwlJicr9HZ6r8exA8eSv0eCBZRgOsEog&uzid=7a170\",\"httpsUrl\":\"https://finder.video.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQBcmO4C9neeSQgwibjlHhibu29vdN3uLyDdAiaicoxKwRQxky3MhicmzdosfqQjO7HDLG0MYjwMBnZOe6dly7icBzCfka&findertoken=088ae1b1c30e10cbfcdecd061800223c66696e64657275706c6f616475726c5f333839393432303831305f313737333634393438323939345f383030323237313735333431363136383932312a2034396638353736393239353231316366623536633764343638303237303634633801400348005000580260ce9e01&hy=SH&idx=1&m=&scene=2&token=x5Y29zUxcibB3KuWgGExa5A2alciahhWxPXFLLibiasNiaYSibEwlzoA5S0Aw0siaS677yoZ2PIZN53k3tz0oicftoS1YqlnwlJicr9HZ6r8exA8eSv0eCBZRgOsEog&uzid=7a170\"}}}]"}, {"name": "__ml::page_255e7376-9950-4f54-998f-fdbcebd22282", "value": "{\"pageId\":\"PostCreate\",\"accessId\":\"af0c3e0a-80d3-45ac-b34b-a90ddc08460b\",\"step\":1}"}, {"name": "UvFirstReportLocalKey", "value": "1773590400000"}, {"name": "__ml::page_ad429f0d-93a2-4063-a079-9e143937a130", "value": "{\"pageId\":\"PostList\",\"accessId\":\"2f3130c0-5ada-4319-9a16-8ff2e47e9f87\",\"step\":1}"}, {"name": "__ml::page_d2a0bc32-b1a1-4345-bf63-271415c72b17", "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"39670c4f-c69f-4d14-8c96-6f5e5b8405bd\",\"step\":1}"}, {"name": "finder_ua_report_data", "value": "{\"browser\":\"Chrome\",\"browserVersion\":\"143.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}"}, {"name": "__ml::page_dc2e3985-9cd2-4f72-9984-76cec727ddc9", "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"8d4a804a-6c90-403e-9998-4b96669bb4ba\",\"step\":1}"}, {"name": "finder_route_meta", "value": "micro.content/post/create;index;1;1773649477096"}]}]} \ No newline at end of file +{ + "cookies": [ + { + "name": "sessionid", + "value": "BgAAO0pkRXR1Cnpkg8qKYYMziAr6J6R%2FMVqC2Jat0R1fmRXawRqzqlxIdOy47RaOqNJ8YCfDBsY9VFSx0UMt4JmiGxqBL6Wa6v0LDXbmykU%3D", + "domain": "channels.weixin.qq.com", + "path": "/", + "expires": 1808780189.672433, + "httpOnly": false, + "secure": true, + "sameSite": "None" + }, + { + "name": "wxuin", + "value": "1519919758", + "domain": "channels.weixin.qq.com", + "path": "/", + "expires": 1808780189.672495, + "httpOnly": false, + "secure": true, + "sameSite": "None" + } + ], + "origins": [ + { + "origin": "https://channels.weixin.qq.com", + "localStorage": [ + { + "name": "finder_route_meta", + "value": "micro.content/post/list;index;1;1774220268329" + }, + { + "name": "__ml::hb_ts", + "value": "1774220134814" + }, + { + "name": "__rx::aid", + "value": "\"80b5c5eb-802b-42f7-843e-6c7a4bf40ffa\"" + }, + { + "name": "__ml::aid", + "value": "\"80b5c5eb-802b-42f7-843e-6c7a4bf40ffa\"" + }, + { + "name": "__ml::page_79981609-2209-498e-b670-628160070448", + "value": "{\"pageId\":\"Home\",\"accessId\":\"6c054bc2-80ac-4b02-aa40-790e1223fcce\",\"step\":1}" + }, + { + "name": "__ml::page_7abb4872-1585-4f94-892c-2ff1f1222889", + "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"38dca924-46a7-4068-9ca1-0fdc1f51ffc6\",\"step\":1}" + }, + { + "name": "__ml::page", + "value": "[\"7abb4872-1585-4f94-892c-2ff1f1222889\",\"639f449e-0a27-4a6e-a189-e7bf16c74428\",\"79981609-2209-498e-b670-628160070448\",\"dd2d2b12-843f-496f-91b3-0e3326d52728\",\"446b419a-6e50-47a5-8688-9a17072dde6e\",\"6c27611b-edcd-43d8-8929-6bbf188b2e5e\"]" + }, + { + "name": "finder_login_token", + "value": "" + }, + { + "name": "__ml::page_6c27611b-edcd-43d8-8929-6bbf188b2e5e", + "value": "{\"pageId\":\"PostList\",\"accessId\":\"2a41b37b-b659-4bc5-a6e8-4e70f75fa49f\",\"step\":1}" + }, + { + "name": "finder_username", + "value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder" + }, + { + "name": "__ml::page_dd2d2b12-843f-496f-91b3-0e3326d52728", + "value": "{\"pageId\":\"PostCard\",\"accessId\":\"c2c6e55a-fa3f-41fd-8690-d4c2364aca65\",\"step\":1}" + }, + { + "name": "_finger_print_device_id", + "value": "99684cf0b0c8496a5094ef60138ec231" + }, + { + "name": "MICRO_VISITED_NAME", + "value": "{\"postCard\":1,\"content\":1}" + }, + { + "name": "__ml::page_639f449e-0a27-4a6e-a189-e7bf16c74428", + "value": "{\"pageId\":\"LoginForIframe\",\"accessId\":\"d90a87c7-e749-4b53-8ab2-b7c8c25f252c\",\"step\":1}" + }, + { + "name": "UvFirstReportLocalKey", + "value": "1774195200000" + }, + { + "name": "finder_ua_report_data", + "value": "{\"browser\":\"Chrome\",\"browserVersion\":\"131.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}" + }, + { + "name": "__ml::page_446b419a-6e50-47a5-8688-9a17072dde6e", + "value": "{\"pageId\":\"MicroPost\",\"accessId\":\"1b08b0c7-0031-4b66-99fb-037cb01f60ea\",\"step\":1}" + }, + { + "name": "finder_uin", + "value": "" + } + ] + } + ] +} \ No newline at end of file diff --git a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_token.json b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_token.json index aa949814..145166ef 100644 --- a/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_token.json +++ b/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_token.json @@ -1,13 +1,10 @@ { - "sessionid": "BgAALavrrg26toSTr%2Fh%2BEjYhRF5AJMeU6M75ATmitpiad6Wdkb0bz%2F3pP5boxKqKwq6I8OPHiRN%2Bd5Ry%2BzSpNDTgyEqs%2BBPuLXWl3V3aQLE%3D", - "wxuin": "1532657520", - "cookie_str": "sessionid=BgAALavrrg26toSTr%2Fh%2BEjYhRF5AJMeU6M75ATmitpiad6Wdkb0bz%2F3pP5boxKqKwq6I8OPHiRN%2Bd5Ry%2BzSpNDTgyEqs%2BBPuLXWl3V3aQLE%3D; wxuin=1532657520", - "raw_cookies": "sessionid=BgAALavrrg26toSTr%2Fh%2BEjYhRF5AJMeU6M75ATmitpiad6Wdkb0bz%2F3pP5boxKqKwq6I8OPHiRN%2Bd5Ry%2BzSpNDTgyEqs%2BBPuLXWl3V3aQLE%3D; wxuin=1532657520", + "sessionid": "BgAAO0pkRXR1Cnpkg8qKYYMziAr6J6R%2FMVqC2Jat0R1fmRXawRqzqlxIdOy47RaOqNJ8YCfDBsY9VFSx0UMt4JmiGxqBL6Wa6v0LDXbmykU%3D", + "wxuin": "1519919758", + "cookie_str": "sessionid=BgAAO0pkRXR1Cnpkg8qKYYMziAr6J6R%2FMVqC2Jat0R1fmRXawRqzqlxIdOy47RaOqNJ8YCfDBsY9VFSx0UMt4JmiGxqBL6Wa6v0LDXbmykU%3D; wxuin=1519919758", "finder_raw": "", "finder_username": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder", "finder_uin": "", "finder_login_token": "", - "url": "https://channels.weixin.qq.com/platform", - "finder_route_meta": "micro.content/post/create;index;1;1773648359203", - "finder_ua_report_data": "{\"browser\":\"Chrome\",\"browserVersion\":\"143.0.0.0\",\"engine\":\"Webkit\",\"engineVersion\":\"537.36\",\"os\":\"Mac OS X\",\"osVersion\":\"10.15.7\",\"device\":\"desktop\",\"darkmode\":0}" + "url": "https://channels.weixin.qq.com/platform/post/list" } \ No newline at end of file diff --git a/04_卡火(火)/火炬_全栈消息/全栈开发/全栈测试/SKILL.md b/04_卡火(火)/火炬_全栈消息/全栈开发/全栈测试/SKILL.md index 6a7fa92f..a219afde 100644 --- a/04_卡火(火)/火炬_全栈消息/全栈开发/全栈测试/SKILL.md +++ b/04_卡火(火)/火炬_全栈消息/全栈开发/全栈测试/SKILL.md @@ -285,7 +285,7 @@ for (const col of collections) { 2. **更新本 SKILL 经验库**(本文件末尾 §八) -3. **发飞书复盘总结** +3. **飞书复盘总结(按需)**:仅当用户明说发群或规程要求时调用 `send_review_to_feishu_webhook.py`;默认不每轮自动发,见 `.cursor/rules/karuo-ai.mdc` --- diff --git a/运营中枢/使用手册/对话沉淀与优化规则.md b/运营中枢/使用手册/对话沉淀与优化规则.md index 79b8bc14..c1a98371 100644 --- a/运营中枢/使用手册/对话沉淀与优化规则.md +++ b/运营中枢/使用手册/对话沉淀与优化规则.md @@ -14,9 +14,9 @@ ## 二、对话后沉淀(必做项) -### 2.1 飞书复盘总结发群(强制) +### 2.1 飞书复盘总结发群(按需 · 非默认) -每次对话完成、复盘写完后,将**简洁复盘总结**(建议 ≤500 字)发到指定飞书群;**长对话必发**,每次完成的任务都发。 +**默认不发群。** 仅在**捆绑明确**且**工作区为卡若AI 主战场**时发送简洁复盘(建议 ≤500 字)。条件与 Cursor 规则 `.cursor/rules/karuo-ai.mdc`「飞书复盘发群」一致:用户**明说**要发,或**某 Skill 步骤**书面要求发(执行时注明 Skill 与步骤);多根工作区时主任务不在卡若AI 仓库则不发。 - **脚本**:`02_卡人(水)/水桥_平台对接/飞书管理/脚本/send_review_to_feishu_webhook.py` - **用法**:`python3 send_review_to_feishu_webhook.py "【卡若AI复盘】YYYY-MM-DD HH:mm\n🎯 目标·结果·达成率\n📌 完成要点\n▶ 下一步"` diff --git a/运营中枢/参考资料/卡若AI_Mongo对话留存闭环.md b/运营中枢/参考资料/卡若AI_Mongo对话留存闭环.md index c7cb757f..e960f6b7 100644 --- a/运营中枢/参考资料/卡若AI_Mongo对话留存闭环.md +++ b/运营中枢/参考资料/卡若AI_Mongo对话留存闭环.md @@ -54,5 +54,5 @@ python3 "/Users/karuo/Documents/个人/卡若AI/01_卡资(金)/金仓_存储 ## 五、与飞书 / Gitea 的相对顺序(建议) -- **Mongo 同步** → **飞书复盘 webhook**(若有)→ **Gitea 自动同步**(若本轮改仓库文件)→ **复盘块收尾**。 +- **Mongo 同步** → **飞书复盘 webhook**(**仅当**用户或 Skill 步骤明确要求,见 `karuo-ai.mdc`)→ **Gitea 自动同步**(若本轮改仓库文件)→ **复盘块收尾**。 具体以 `.cursor/rules/karuo-ai.mdc` 为准。 diff --git a/运营中枢/工作台/gitea_push_log.md b/运营中枢/工作台/gitea_push_log.md index 20037d89..e936fd4c 100644 --- a/运营中枢/工作台/gitea_push_log.md +++ b/运营中枢/工作台/gitea_push_log.md @@ -420,3 +420,4 @@ | 2026-03-22 13:22:16 | 🔄 卡若AI 同步 2026-03-22 13:22 | 更新:运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-22 13:23:40 | 🔄 卡若AI 同步 2026-03-22 13:23 | 更新:金仓、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-22 14:38:11 | 🔄 卡若AI 同步 2026-03-22 14:38 | 更新:Cursor规则、金仓、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | +| 2026-03-22 21:22:06 | 🔄 卡若AI 同步 2026-03-22 21:22 | 更新:Cursor规则、金仓、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | diff --git a/运营中枢/工作台/代码管理.md b/运营中枢/工作台/代码管理.md index 4f8e3d99..b3b48458 100644 --- a/运营中枢/工作台/代码管理.md +++ b/运营中枢/工作台/代码管理.md @@ -423,3 +423,4 @@ | 2026-03-22 13:22:16 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-22 13:22 | 更新:运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-22 13:23:40 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-22 13:23 | 更新:金仓、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-22 14:38:11 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-22 14:38 | 更新:Cursor规则、金仓、总索引与入口、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | +| 2026-03-22 21:22:06 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-22 21:22 | 更新:Cursor规则、金仓、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | diff --git a/运营中枢/工作台/平台集成配置.md b/运营中枢/工作台/平台集成配置.md index b074f70c..a16af83c 100644 --- a/运营中枢/工作台/平台集成配置.md +++ b/运营中枢/工作台/平台集成配置.md @@ -9,7 +9,7 @@ | key | 类型 | 名称 | enabled | 网关路径 | 外部地址 / webhook | 说明(可编辑) | |:---|:---|:---|:---:|:---|:---|:---| -| feishu_review | 飞书群机器人 | 卡若AI复盘 | ✅ | `/api/integrations/webhook/feishu-review` | `https://open.feishu.cn/open-apis/bot/v2/hook/8b7f996e-2892-4075-989f-aa5593ea4fbc` | 每次对话完成后发送简洁复盘总结;脚本:`send_review_to_feishu_webhook.py` | +| feishu_review | 飞书群机器人 | 卡若AI复盘 | ✅ | `/api/integrations/webhook/feishu-review` | `https://open.feishu.cn/open-apis/bot/v2/hook/8b7f996e-2892-4075-989f-aa5593ea4fbc` | **按需**:用户明说或 Skill 步骤要求时,由脚本 `send_review_to_feishu_webhook.py` 发简洁复盘;非每轮自动 | | github_push | GitHub Webhook | 代码推送通知 | ✅ | `/api/integrations/webhook/github-push` | (GitHub 仓库 Webhook 推送地址) | main 分支推送时通知到卡若AI 网关或飞书群,用于代码变更追踪 | > 说明列可自由编辑,用于给卡若AI / 人类解释该集成的用途、触发条件与注意点。 @@ -28,6 +28,6 @@ ## 三、与其他文档的关系 - **账号与 API**:具体凭证仍以 `00_账号与API索引.md` 为准,本文件只记录「集成级别」的信息(开关、路径、用途)。 -- **飞书复盘总结发群**:`feishu_review` 集成与 `send_review_to_feishu_webhook.py`、`复盘总结发飞书群_SKILL.md`、Cursor 规则中的「飞书复盘总结发群」一一对应。 +- **飞书复盘总结发群**:`feishu_review` 与 `send_review_to_feishu_webhook.py`、`复盘总结发飞书群_SKILL.md`、`.cursor/rules/karuo-ai.mdc` 中按需发群规则对应(默认不自动发)。 - **Gitea / GitHub 推送**:`github_push` 集成与 `Gitea管理` Skill 中的仓库推送策略、CI/通知策略关联。 diff --git a/运营中枢/工作台/飞书复盘总结发群说明.md b/运营中枢/工作台/飞书复盘总结发群说明.md index 53dc32f4..a8030a87 100644 --- a/运营中枢/工作台/飞书复盘总结发群说明.md +++ b/运营中枢/工作台/飞书复盘总结发群说明.md @@ -1,16 +1,16 @@ -# 飞书复盘总结发群(对话结束后) +# 飞书复盘总结发群(按需) -> **强制**:每次对话完成、复盘写完后,将**简洁复盘总结**发到指定飞书群。长对话必发,每次完成的任务都发。 -> 更新:2026-03-12 +> **默认不发。** 仅当业务与卡若AI 工作区**捆绑清楚**(用户明说或 Skill 步骤要求)时才发。 +> 更新:2026-03-23(取消「每轮必发」) --- ## 一、规则 -- **时机**:对话结束、完成「卡若复盘」后。 +- **时机**:对话结束、完成「卡若复盘」后,**且**满足 Cursor 规则中的「捆绑 + 工作区」条件(见 `.cursor/rules/karuo-ai.mdc`)。 - **内容**:简洁版复盘总结(建议 ≤500 字),包含:日期时间、目标·结果·达成率、完成的任务要点、下一步(若有)。 - **对象**:飞书群(通过 bot v2 webhook 推送)。 -- **频率**:每次对话完成都发;**长对话尤其必须发**。 +- **频率**:**非默认**;用户口令或 Skill 步骤要求时才发。多根工作区时以**本轮主改动的仓库**为准,主战场非卡若AI 不发。 --- @@ -41,6 +41,6 @@ python3 .../send_review_to_feishu_webhook.py --file /path/to/summary.txt ## 四、与规则 / Skill 的对应 -- **Cursor 规则**(`.cursor/rules/karuo-ai.mdc`):对话结束复盘后,执行发飞书群(见「复盘(所有对话强制)」后追加的「飞书复盘总结发群」)。 -- **对话沉淀与优化规则**(`运营中枢/使用手册/对话沉淀与优化规则.md`):沉淀必做项中增加「发简洁复盘总结到飞书群」。 -- **飞书管理 Skill**:已记录「复盘总结发飞书群」能力与脚本路径(见 `飞书管理/复盘总结发飞书群_SKILL.md` 或飞书相关 SKILL 内章节)。 +- **Cursor 规则**(`.cursor/rules/karuo-ai.mdc`):飞书发群为**按需**,见「飞书复盘发群(默认关闭)」。 +- **对话沉淀与优化规则**(`运营中枢/使用手册/对话沉淀与优化规则.md`):§2.1 为按需项,非每轮必做。 +- **飞书管理 Skill**:能力与脚本见 `飞书管理/复盘总结发飞书群_SKILL.md`;某运营闭环若在自身 `SKILL.md` 中写明「本步发飞书」,则该步执行时**才算**捆绑明确。