From 70b83fdb253ef8d9e859458e8551c8b514fead96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=8D=A1=E8=8B=A5?= Date: Tue, 24 Mar 2026 12:00:04 +0800 Subject: [PATCH] =?UTF-8?q?chore:=20karuo-party=20=E5=88=86=E5=B9=B3?= =?UTF-8?q?=E5=8F=B0=20SKILL=20+=20=E5=A4=9A=E5=B9=B3=E5=8F=B0=E5=88=86?= =?UTF-8?q?=E5=8F=91=E6=9B=B4=E6=96=B0=EF=BC=9Bdb=20handler=20=E8=B0=83?= =?UTF-8?q?=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Made-with: Cursor --- .cursor/skills/karuo-party/SKILL.md | 18 ++- .../karuo-party/skills/多平台分发_SKILL.md | 40 ++++-- .../karuo-party/skills/平台_B站_SKILL.md | 34 +++++ .../karuo-party/skills/平台_小红书_SKILL.md | 34 +++++ .../karuo-party/skills/平台_抖音_SKILL.md | 34 +++++ .../karuo-party/skills/平台_视频号_SKILL.md | 38 ++++++ soul-api/internal/handler/db.go | 129 +++++++++++++++--- 7 files changed, 294 insertions(+), 33 deletions(-) create mode 100644 .cursor/skills/karuo-party/skills/平台_B站_SKILL.md create mode 100644 .cursor/skills/karuo-party/skills/平台_小红书_SKILL.md create mode 100644 .cursor/skills/karuo-party/skills/平台_抖音_SKILL.md create mode 100644 .cursor/skills/karuo-party/skills/平台_视频号_SKILL.md diff --git a/.cursor/skills/karuo-party/SKILL.md b/.cursor/skills/karuo-party/SKILL.md index 98f2fd8d..b0eabfe1 100644 --- a/.cursor/skills/karuo-party/SKILL.md +++ b/.cursor/skills/karuo-party/SKILL.md @@ -7,8 +7,8 @@ description: > triggers: 运营报表、视频切片、多平台分发、飞书视频下载、派对运营、卡若创业派对、派对填表、视频剪辑、一键分发、妙记下载 owner: 水岸 group: 运营 -version: "1.2" -updated: "2026-03-23" +version: "1.3" +updated: "2026-03-24" --- # 卡若创业派对运营 Skill 包 @@ -29,7 +29,7 @@ updated: "2026-03-23" ## 一、技能包组成 -本技能包包含以下 4 个核心子技能: +本技能包包含以下 8 个核心子技能: | # | 技能名 | 文件路径 | 触发词 | 用途 | |:--|:---|:---|:---|:---| @@ -37,6 +37,10 @@ updated: "2026-03-23" | ② | 飞书视频文字下载 | `skills/飞书视频文字下载_SKILL.md` | 妙记下载、飞书视频、飞书妙记 | 文字+视频→本地 | | ③ | 视频切片 | `skills/视频切片_SKILL.md` | 视频剪辑、切片发布 | 原视频→转录→高光→成片 | | ④ | 多平台分发 | `skills/多平台分发_SKILL.md` | 一键分发、全平台发布 | 成片→抖音/B站/视频号/小红书/快手 | +| ⑤ | 视频号发布 | `skills/平台_视频号_SKILL.md` | 视频号发布、视频号重传 | 账号校验→清理→定时发布 | +| ⑥ | B站发布 | `skills/平台_B站_SKILL.md` | B站发布、B站补发 | API优先→兜底→定时 | +| ⑦ | 小红书发布 | `skills/平台_小红书_SKILL.md` | 小红书发布、小红书补发 | UI自动化定时发布 | +| ⑧ | 抖音发布 | `skills/平台_抖音_SKILL.md` | 抖音发布、抖音补发 | API定时发布+失败重登 | --- @@ -131,6 +135,13 @@ python3 "$DIST_SCRIPT/distribute_all.py" --now 2. **线上失败/重复清理**:先查 `post_list`,删除失败条目;同标题仅保留最新一条(去重后再补发)。 3. **仅定时发布**:禁止立即发布;若页面定时控件失效,使用 `post_create` 注入定时参数并拦截立即发布。 +#### 分发统一总规则(强制) + +1. **间隔规则**:按“每一条相邻发布时间”计算,必须在 **10 分钟到 120 分钟** 之间。 +2. **账号状态规则**:发布前做账号可用性检查;若出现封禁/登录失效/鉴权失败,先执行重登,再重试失败条。 +3. **平台分治规则**:先按平台触发对应子 Skill,再执行发布命令,不混用平台规则。 +4. **收敛规则**:每轮结束输出成功/失败清单 + 重登命令;失败条必须可继续重试直到收敛。 + --- ## 四、完整流程(派对结束后) @@ -287,6 +298,7 @@ curl -sS -X POST -H "Content-Type: application/json" -d "$TEXT" "$FEISHU_PARTY_C | 版本 | 日期 | 说明 | |:---|:---|:---| +| 1.3 | 2026-03-24 | 升级为“总规则+平台子Skill”;统一相邻间隔 10~120 分钟;新增账号封禁/重登/重试收敛规则 | | 1.2 | 2026-03-23 | 新增视频号发布前置三步:头像昵称校验、失败/重复清理、强制定时发布(含请求注入兜底) | | 1.1 | 2026-03-21 | 新增 §九 闭环复盘发群:卡若五块复盘 + 飞书 Webhook v2(msg_type 必填) | | 1.0 | 2026-03-20 | 初版:整合运营报表、视频切片、多平台分发、飞书视频文字下载 4 大技能,统一凭证管理 | diff --git a/.cursor/skills/karuo-party/skills/多平台分发_SKILL.md b/.cursor/skills/karuo-party/skills/多平台分发_SKILL.md index 0f61a7b2..ef34db9a 100644 --- a/.cursor/skills/karuo-party/skills/多平台分发_SKILL.md +++ b/.cursor/skills/karuo-party/skills/多平台分发_SKILL.md @@ -7,14 +7,14 @@ description: > triggers: 多平台分发、一键分发、全平台发布、批量分发、视频分发 owner: 木叶 group: 木 -version: "4.4" -updated: "2026-03-23" +version: "4.5" +updated: "2026-03-24" --- -# 多平台分发 Skill(v4.4) +# 多平台分发 Skill(v4.5) > **核心原则**:API 发布为主,Playwright 为辅。确保确定性地分发到各平台。 -> **v4.4**:视频号新增发前强制检查(头像昵称校验、失败清理、同标题去重)与“仅定时发布(请求注入兜底)”。**v4.3**:默认静默登录。 +> **v4.5**:统一改为“按相邻条目”间隔控制,默认区间 **10~120 分钟**;并要求按平台子 Skill 执行对应规则后再发布。 ## 〇、执行原则(第一性原理) @@ -51,10 +51,10 @@ cd /Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容 # 默认智能错峰排期 python3 distribute_all.py -# 旧版随机间隔 -python3 distribute_all.py --legacy-schedule +# 规则间隔(推荐,按相邻条目 10~120 分钟) +python3 distribute_all.py --legacy-schedule --min-gap 10 --max-gap 120 -# 立即全部发布 +# 立即全部发布(仅在明确要求时) python3 distribute_all.py --now # 只发指定平台 @@ -71,6 +71,10 @@ python3 distribute_all.py --retry python3 distribute_all.py --platforms 视频号 --auto-channels-login --video-dir "/path/to/成片" # 独立 channels_api_publish 允许自动登录:CHANNELS_AUTO_LOGIN=1 # 强制永不自动登录:NO_AUTO_CHANNELS_LOGIN=1 + +# 平台站点上传 CLI(平台级,含账号检测+自动重登+重试) +python3 site_upload_cli.py check --platforms 抖音 B站 小红书 快手 +python3 site_upload_cli.py publish --platforms 抖音 B站 小红书 --video-dir "/path/to/成片" --min-gap 10 --max-gap 120 --until-success ``` --- @@ -79,7 +83,7 @@ python3 distribute_all.py --platforms 视频号 --auto-channels-login --video-di ### 3.1 默认(`generate_smart_schedule`) - 第 1 条立即;间隔与总跨度随条数自适应;本地 0–7 点尽量挪到午间(`SCHEDULE_NO_NIGHT_REFINE=1` 关闭) -- `--legacy-schedule` + `--min-gap` / `--max-gap` / `--max-hours` 为旧逻辑 +- `--legacy-schedule` + `--min-gap` / `--max-gap` / `--max-hours` 为固定区间逻辑(建议 `10~120`) - 去重时排期与目录列表下标对齐 ### 3.2 独立 `channels_api_publish.py`:同上智能排期转 Unix @@ -159,6 +163,26 @@ meta.hashtags("视频号") # … + #小程序卡若创业派对 #公众号卡 --- +## 六点六、账号异常自动处理(强制) + +1. 发布前先执行 `--check`;若平台 Cookie 无效,先走对应登录脚本。 +2. 发布中若出现封禁/风控/鉴权失败,立即输出处理建议与重登命令。 +3. 重登完成后优先执行 `--retry`,只重试失败条,不重复成功条。 +4. 失败超过 2 轮仍未收敛时,输出平台级阻塞原因并停止盲目重试。 + +--- + +## 六点七、平台子Skill分流(强制) + +- 视频号:按 `平台_视频号_SKILL.md` 执行。 +- B站:按 `平台_B站_SKILL.md` 执行。 +- 小红书:按 `平台_小红书_SKILL.md` 执行。 +- 抖音:按 `平台_抖音_SKILL.md` 执行。 + +多平台联发时,先加载各平台子Skill,再统一调度 `distribute_all.py`。 + +--- + ## 七、去重机制 - 日志:`publish_log.json`(JSON Lines) diff --git a/.cursor/skills/karuo-party/skills/平台_B站_SKILL.md b/.cursor/skills/karuo-party/skills/平台_B站_SKILL.md new file mode 100644 index 00000000..1b377cfe --- /dev/null +++ b/.cursor/skills/karuo-party/skills/平台_B站_SKILL.md @@ -0,0 +1,34 @@ +--- +name: 平台_B站发布 +description: B站发布专用规则。API优先,失败降级 Playwright,按相邻间隔 10~120 分钟定时发布。 +triggers: B站发布、B站补发、B站重试 +owner: 木叶 +group: 木 +version: "1.0" +updated: "2026-03-24" +--- + +# 平台子Skill:B站发布 + +## 一、强制规则 + +1. 默认 API 投稿,失败才降级 Playwright。 +2. 发布间隔按相邻条目控制在 `10~120` 分钟。 +3. 出现 406/超时等异常时先重试失败条,不重复成功条。 + +## 二、标准命令 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" \ + --platforms B站 \ + --video-dir "<成片目录>" \ + --legacy-schedule --min-gap 10 --max-gap 120 +``` + +## 三、登录异常处理 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/B站发布/脚本/bilibili_login.py" +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" --retry +``` + diff --git a/.cursor/skills/karuo-party/skills/平台_小红书_SKILL.md b/.cursor/skills/karuo-party/skills/平台_小红书_SKILL.md new file mode 100644 index 00000000..88ba07b4 --- /dev/null +++ b/.cursor/skills/karuo-party/skills/平台_小红书_SKILL.md @@ -0,0 +1,34 @@ +--- +name: 平台_小红书发布 +description: 小红书发布专用规则。UI自动化定时发布,按相邻间隔 10~120 分钟执行。 +triggers: 小红书发布、小红书补发 +owner: 木叶 +group: 木 +version: "1.0" +updated: "2026-03-24" +--- + +# 平台子Skill:小红书发布 + +## 一、强制规则 + +1. 逐条定时发布,禁止批量立即发布。 +2. 相邻发布时间间隔必须在 `10~120` 分钟。 +3. 若仅返回 likely_published,需在下一轮巡检确认状态。 + +## 二、标准命令 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" \ + --platforms 小红书 \ + --video-dir "<成片目录>" \ + --legacy-schedule --min-gap 10 --max-gap 120 +``` + +## 三、登录异常处理 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/小红书发布/脚本/xiaohongshu_login.py" +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" --retry +``` + diff --git a/.cursor/skills/karuo-party/skills/平台_抖音_SKILL.md b/.cursor/skills/karuo-party/skills/平台_抖音_SKILL.md new file mode 100644 index 00000000..316b102a --- /dev/null +++ b/.cursor/skills/karuo-party/skills/平台_抖音_SKILL.md @@ -0,0 +1,34 @@ +--- +name: 平台_抖音发布 +description: 抖音发布专用规则。API定时发布,按相邻间隔 10~120 分钟;遇风控/封禁优先账号处理。 +triggers: 抖音发布、抖音补发、抖音重试 +owner: 木叶 +group: 木 +version: "1.0" +updated: "2026-03-24" +--- + +# 平台子Skill:抖音发布 + +## 一、强制规则 + +1. 仅按定时发布,不做整批立即。 +2. 相邻发布时间间隔 `10~120` 分钟。 +3. 若出现封禁/风控提示,立刻停止盲目重试并提示账号处理。 + +## 二、标准命令 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" \ + --platforms 抖音 \ + --video-dir "<成片目录>" \ + --legacy-schedule --min-gap 10 --max-gap 120 +``` + +## 三、登录异常处理 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_login.py" +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" --retry +``` + diff --git a/.cursor/skills/karuo-party/skills/平台_视频号_SKILL.md b/.cursor/skills/karuo-party/skills/平台_视频号_SKILL.md new file mode 100644 index 00000000..520b5b4f --- /dev/null +++ b/.cursor/skills/karuo-party/skills/平台_视频号_SKILL.md @@ -0,0 +1,38 @@ +--- +name: 平台_视频号发布 +description: 视频号发布专用规则。发布前校验账号,清理密集/失败条目,按相邻间隔 10~120 分钟定时发布。 +triggers: 视频号发布、视频号重传、视频号补发 +owner: 木叶 +group: 木 +version: "1.0" +updated: "2026-03-24" +--- + +# 平台子Skill:视频号发布 + +## 一、强制规则 + +1. 发布前必须校验 `auth_data`(昵称、头像、登录态)。 +2. 发布前必须检查 `post_list`:失败条目删除、同标题去重。 +3. 仅允许定时发布,按相邻条目间隔 `10~120` 分钟。 +4. 定时控件失败时允许请求注入;注入失败则中止该条,禁止误发立即。 + +## 二、标准命令 + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_web_cli.py" \ + publish-dir \ + --video-dir "<成片目录>" \ + --legacy-schedule --min-gap 10 --max-gap 120 \ + --start-after-min 10 +``` + +## 三、登录异常处理 + +- 若出现 `300334` / `300002` / `finder_raw` 缺失:立即执行重登。 +- 重登命令: + +```bash +python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_login.py" --playwright-only +``` + diff --git a/soul-api/internal/handler/db.go b/soul-api/internal/handler/db.go index b15861ce..aa8169c3 100644 --- a/soul-api/internal/handler/db.go +++ b/soul-api/internal/handler/db.go @@ -47,6 +47,92 @@ func parseConfigBool(v interface{}) bool { } } +// isLikelyWxMiniProgramAppID 判断是否为微信小程序 AppID 常见形态:wx + 16 位十六进制(共 18 字符)。 +// 后台「链接标签」若 type=miniprogram 且在此列直接填真实 AppID,C 端会把该值当作 mpKey 去 linkedMiniprograms 里匹配 key; +// 若未单独配置 linked_miniprograms,会提示「未找到关联小程序配置」。mergeDirectMiniProgramLinksFromLinkTags 会据此自动补全映射。 +func isLikelyWxMiniProgramAppID(s string) bool { + s = strings.TrimSpace(s) + if len(s) != 18 || !strings.HasPrefix(s, "wx") { + return false + } + for i := 2; i < len(s); i++ { + c := s[i] + if (c >= '0' && c <= '9') || (c >= 'a' && c <= 'f') || (c >= 'A' && c <= 'F') { + continue + } + return false + } + return true +} + +func linkedMiniprogramItemKey(item gin.H) string { + if v, ok := item["key"]; ok && v != nil { + if s, ok := v.(string); ok { + return strings.TrimSpace(s) + } + } + return "" +} + +func linkedMiniprogramItemAppIDEmpty(item gin.H) bool { + v, ok := item["appId"] + if !ok || v == nil { + return true + } + s, ok := v.(string) + return !ok || strings.TrimSpace(s) == "" +} + +func linkedMiniprogramItemPathEmpty(item gin.H) bool { + v, ok := item["path"] + if !ok || v == nil { + return true + } + s, ok := v.(string) + return !ok || strings.TrimSpace(s) == "" +} + +// mergeDirectMiniProgramLinksFromLinkTags 将「直接填写微信 AppID」的链接标签并入 linkedMiniprograms,兼容现有小程序 navigateToMiniProgram 查表逻辑(不改 C 端)。 +func mergeDirectMiniProgramLinksFromLinkTags(linked *[]gin.H, tags []model.LinkTag) { + if linked == nil { + return + } + byKey := make(map[string]int) + for i := range *linked { + k := linkedMiniprogramItemKey((*linked)[i]) + if k != "" { + byKey[k] = i + } + } + for _, t := range tags { + if strings.TrimSpace(strings.ToLower(t.Type)) != "miniprogram" { + continue + } + app := strings.TrimSpace(t.AppID) + if app == "" || !isLikelyWxMiniProgramAppID(app) { + continue + } + path := strings.TrimSpace(t.PagePath) + if idx, ok := byKey[app]; ok { + item := (*linked)[idx] + if linkedMiniprogramItemAppIDEmpty(item) { + item["appId"] = app + } + if path != "" && linkedMiniprogramItemPathEmpty(item) { + item["path"] = path + } + (*linked)[idx] = item + continue + } + entry := gin.H{"key": app, "appId": app} + if path != "" { + entry["path"] = path + } + *linked = append(*linked, entry) + byKey[app] = len(*linked) - 1 + } +} + // defaultMpUi 小程序文案与导航默认值,存于 mp_config.mpUi;管理端系统设置可部分覆盖(深合并) func defaultMpUi() gin.H { return gin.H{ @@ -226,35 +312,34 @@ func buildMiniprogramConfig() gin.H { if _, has := out["userDiscount"]; !has { out["userDiscount"] = float64(5) } - // 链接标签列表(小程序 onLinkTagTap 需要 type;miniprogram 类型存 mpKey,用 key 查 linkedMiniprograms 得 appId) + // 链接标签列表(小程序 onLinkTagTap:miniprogram 类型下发 mpKey=C 端用其匹配 linkedMiniprograms[].key;历史设计为「密钥→appId」,现支持 app_id 列直接填微信 AppID 并由下方 merge 自动补 linkedMiniprograms) var linkTagRows []model.LinkTag - if err := db.Order("label ASC").Find(&linkTagRows).Error; err == nil { - tags := make([]gin.H, 0, len(linkTagRows)) - for _, t := range linkTagRows { - h := gin.H{"tagId": t.TagID, "label": t.Label, "url": t.URL, "type": t.Type, "pagePath": t.PagePath} - if t.Type == "miniprogram" { - h["mpKey"] = t.AppID // miniprogram 类型时 AppID 列存的是密钥 - } else { - h["appId"] = t.AppID - } - tags = append(tags, h) + _ = db.Order("label ASC").Find(&linkTagRows).Error + tags := make([]gin.H, 0, len(linkTagRows)) + for _, t := range linkTagRows { + h := gin.H{"tagId": t.TagID, "label": t.Label, "url": t.URL, "type": t.Type, "pagePath": t.PagePath} + if t.Type == "miniprogram" { + h["mpKey"] = t.AppID // 可为「关联表 key」或「直接 wx AppID」;后者由 mergeDirectMiniProgramLinksFromLinkTags 补全 linkedMiniprograms + } else { + h["appId"] = t.AppID } - out["linkTags"] = tags + tags = append(tags, h) } - // 关联小程序列表(key 为 32 位密钥,小程序用 key 查 appId 后 wx.navigateToMiniProgram) + out["linkTags"] = tags + + // 关联小程序列表(小程序:find(m => m.key === mpKey) → navigateToMiniProgram) + var linkedList []gin.H var linkedMpRow model.SystemConfig if err := db.Where("config_key = ?", "linked_miniprograms").First(&linkedMpRow).Error; err == nil && len(linkedMpRow.ConfigValue) > 0 { - var linkedList []gin.H - if err := json.Unmarshal(linkedMpRow.ConfigValue, &linkedList); err == nil && len(linkedList) > 0 { - out["linkedMiniprograms"] = linkedList - } else { - // JSON解析失败,使用空数组 - out["linkedMiniprograms"] = []gin.H{} + if err := json.Unmarshal(linkedMpRow.ConfigValue, &linkedList); err != nil { + linkedList = nil } - } else { - // 未找到配置或查询失败,使用空数组作为默认值 - out["linkedMiniprograms"] = []gin.H{} } + if linkedList == nil { + linkedList = []gin.H{} + } + mergeDirectMiniProgramLinksFromLinkTags(&linkedList, linkTagRows) + out["linkedMiniprograms"] = linkedList // 归一化 auditMode(兼容历史 bool / 字符串 / 数字) if mp, ok := out["mpConfig"].(gin.H); ok { mp["auditMode"] = parseConfigBool(mp["auditMode"])