🔄 卡若AI 同步 2026-03-23 13:58 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-23 13:58:05 +08:00
parent 9a0e32812d
commit aeb0176e45
6 changed files with 76 additions and 9 deletions

View File

@@ -73,8 +73,8 @@ ROWS = {
'129': [ 'AI手机金融坏账投流', 200, 0, 250, 14, 187, 4, 561, 21, 31 ], '129': [ 'AI手机金融坏账投流', 200, 0, 250, 14, 187, 4, 561, 21, 31 ],
# 130场 2026-03-21视频号直播结束页 02:25:49≈146min观众总数2278、最高在线355、新增关注4、总热度3、送礼1Soul推流无截图数据填0→脚本跳过第5行保留空 # 130场 2026-03-21视频号直播结束页 02:25:49≈146min观众总数2278、最高在线355、新增关注4、总热度3、送礼1Soul推流无截图数据填0→脚本跳过第5行保留空
'130': [ 'Soul爆量脸视频号问微信', 146, 0, 2278, 0, 3, 1, 3, 4, 355 ], '130': [ 'Soul爆量脸视频号问微信', 146, 0, 2278, 0, 3, 1, 3, 4, 355 ],
# 131场 2026-03-23文章口径场观2580/进房328/128min视频号结束页补关注4、最高在线75、点赞评论分享计互动礼物灵魂力无截图填0 # 131场 2026-03-23视频号结束页 02:05:55≈126min观众1144、最高75、关注4、点赞1595+评论498+分享12无总热度/礼物展示填0Soul推流0→跳过第5行
'131': [ '视频号中枢Soul哨兵', 128, 2580, 328, 0, 2105, 0, 0, 4, 75 ], '131': [ '视频号中枢Soul哨兵', 126, 0, 1144, 0, 2105, 0, 0, 4, 75 ],
} }
# 场次→按日期列填写时的日期(表头为当月日期 1~31 # 场次→按日期列填写时的日期(表头为当月日期 1~31
SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23', '113': '2', '114': '3', '115': '4', '116': '5', '117': '6', '118': '7', '119': '8', '124': '14', '126': '17', '127': '18', '128': '19', '129': '20', '130': '21', '131': '23'} SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23', '113': '2', '114': '3', '115': '4', '116': '5', '117': '6', '118': '7', '119': '8', '124': '14', '126': '17', '127': '18', '128': '19', '129': '20', '130': '21', '131': '23'}
@@ -96,7 +96,6 @@ PARTY_VIDEO_LINKS = {
'127': 'https://cunkebao.feishu.cn/minutes/obcnhybw322112tad6916v8r', '127': 'https://cunkebao.feishu.cn/minutes/obcnhybw322112tad6916v8r',
'129': 'https://cunkebao.feishu.cn/minutes/obcnjb994323l12lhl448177', '129': 'https://cunkebao.feishu.cn/minutes/obcnjb994323l12lhl448177',
'130': 'https://cunkebao.feishu.cn/minutes/obcnj1y95z73n53e8m6m1s3j', '130': 'https://cunkebao.feishu.cn/minutes/obcnj1y95z73n53e8m6m1s3j',
# 131场urls_soul_party.txt 中紧接 130 的新妙记
'131': 'https://cunkebao.feishu.cn/minutes/obcnjzx7dyxco67btud7y8gz', '131': 'https://cunkebao.feishu.cn/minutes/obcnjzx7dyxco67btud7y8gz',
} }
@@ -112,7 +111,17 @@ TEAM_MEETING_LINKS = {
'126': 'https://kcnxrqd5ata7.feishu.cn/minutes/obcng991jg3114b2nj99548d', '126': 'https://kcnxrqd5ata7.feishu.cn/minutes/obcng991jg3114b2nj99548d',
'127': 'https://cunkebao.feishu.cn/minutes/obcnhxs8usi8c7n27a9f66ux', '127': 'https://cunkebao.feishu.cn/minutes/obcnhxs8usi8c7n27a9f66ux',
'129': 'https://kcnxrqd5ata7.feishu.cn/minutes/obcnjbn178iy6919od4119ww', '129': 'https://kcnxrqd5ata7.feishu.cn/minutes/obcnjbn178iy6919od4119ww',
'131': 'https://kcnxrqd5ata7.feishu.cn/minutes/obcnjzx7dyxco67btud7y8gz', # 131与派对同源妙记时可同链若售商品侧另有团队会议妙记请改此处
'131': 'https://cunkebao.feishu.cn/minutes/obcnjzx7dyxco67btud7y8gz',
}
# 补充直链A 列无独立标签的空行):场次 → [(行号1-based, 完整URL), ...]
# 131第30行=3月运营报表 Wiki第32行=今日总结嵌入图 fileToken 对应的云空间文件链(与格内 embed 一致)
SESSION_SUPPLEMENT_ROW_LINKS = {
'131': [
(30, 'https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=bJR5sA'),
(32, 'https://cunkebao.feishu.cn/file/HNZEb0mxqoW38KxlFLAcKb2Rnef'),
],
} }
# 小程序当日运营数据:日期号 → {访问次数, 访客, 交易金额},填表时自动写入对应日期列 # 小程序当日运营数据:日期号 → {访问次数, 访客, 交易金额},填表时自动写入对应日期列
@@ -225,6 +234,23 @@ def _write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_lette
print(f'⚠️ 团队会议链接写入未成功: {code} {body}') print(f'⚠️ 团队会议链接写入未成功: {code} {body}')
def _write_session_supplement_row_links(token, spreadsheet_token, sheet_id, col_letter, session):
"""按 SESSION_SUPPLEMENT_ROW_LINKS 写入报表 Wiki、今日总结图文件链等固定行号"""
pairs = (SESSION_SUPPLEMENT_ROW_LINKS or {}).get(session) or []
for row_num, link in pairs:
link = (link or '').strip()
if not link or not row_num:
continue
rng = f"{sheet_id}!{col_letter}{row_num}:{col_letter}{row_num}"
code, body = update_sheet_range(token, spreadsheet_token, rng, [[link]], value_input_option='USER_ENTERED')
if code == 401 or body.get('code') in (99991677, 99991663):
return
if code == 200 and body.get('code') in (0, None):
print(f'✅ 已写入补充链接 → {col_letter}{row_num}')
else:
print(f'⚠️ 补充链接写入未成功 {col_letter}{row_num}: {code} {body}')
def load_token(): def load_token():
if not os.path.exists(TOKEN_FILE): if not os.path.exists(TOKEN_FILE):
print('❌ 未找到飞书 Token 文件:', TOKEN_FILE) print('❌ 未找到飞书 Token 文件:', TOKEN_FILE)
@@ -541,6 +567,7 @@ def main():
_write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter, month=month) _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter, month=month)
_write_party_video_link(token, spreadsheet_token, sheet_id, vals, col_letter, session) _write_party_video_link(token, spreadsheet_token, sheet_id, vals, col_letter, session)
_write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_letter, session) _write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_letter, session)
_write_session_supplement_row_links(token, spreadsheet_token, sheet_id, col_letter, session)
_maybe_send_group(session, raw) _maybe_send_group(session, raw)
return return
print(f'⚠️ 逐格写入成功但校验未通过:{msg}') print(f'⚠️ 逐格写入成功但校验未通过:{msg}')
@@ -563,6 +590,7 @@ def main():
_write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter, month=month) _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter, month=month)
_write_party_video_link(token, spreadsheet_token, sheet_id, vals, col_letter, session) _write_party_video_link(token, spreadsheet_token, sheet_id, vals, col_letter, session)
_write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_letter, session) _write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_letter, session)
_write_session_supplement_row_links(token, spreadsheet_token, sheet_id, col_letter, session)
_maybe_send_group(session, raw) _maybe_send_group(session, raw)
return return
print(f'⚠️ 写入成功但校验未通过:{msg}') print(f'⚠️ 写入成功但校验未通过:{msg}')
@@ -593,6 +621,7 @@ def main():
_write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter, month=month) _write_miniprogram_extra(token, spreadsheet_token, sheet_id, vals, date_col, col_letter, month=month)
_write_party_video_link(token, spreadsheet_token, sheet_id, vals, col_letter, session) _write_party_video_link(token, spreadsheet_token, sheet_id, vals, col_letter, session)
_write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_letter, session) _write_team_meeting_link(token, spreadsheet_token, sheet_id, vals, col_letter, session)
_write_session_supplement_row_links(token, spreadsheet_token, sheet_id, col_letter, session)
_maybe_send_group(session, raw) _maybe_send_group(session, raw)
return return
print(f'⚠️ 逐格写入成功但校验未通过:{msg}') print(f'⚠️ 逐格写入成功但校验未通过:{msg}')

View File

@@ -29,6 +29,7 @@ import subprocess
import sys import sys
import time import time
from pathlib import Path from pathlib import Path
from datetime import datetime, timedelta
SCRIPT_DIR = Path(__file__).parent SCRIPT_DIR = Path(__file__).parent
BASE_DIR = SCRIPT_DIR.parent.parent BASE_DIR = SCRIPT_DIR.parent.parent
@@ -116,6 +117,32 @@ def print_platform_account_status(all_results: list[PublishResult], targets: lis
print(f"{'=' * 60}\n") print(f"{'=' * 60}\n")
def _enforce_channels_schedule_slots(
schedule_times: list | None,
total_videos: int,
*,
min_delay_minutes: int = 10,
) -> list:
"""
视频号强制定时:
- 若无排期,补一套排期;
- 任一发布时间若过近(<= min_delay_minutes自动顺延。
"""
now = datetime.now()
min_dt = now + timedelta(minutes=min_delay_minutes)
if not schedule_times:
schedule_times = [min_dt + timedelta(minutes=55 * i) for i in range(total_videos)]
return schedule_times
fixed = []
for i, st in enumerate(schedule_times):
if st <= min_dt:
fixed.append(min_dt + timedelta(minutes=55 * i))
else:
fixed.append(st)
return fixed
def _ensure_channels_cookie_or_login(*, auto_login: bool) -> None: def _ensure_channels_cookie_or_login(*, auto_login: bool) -> None:
"""发视频号前对齐双路径 Cookie。默认静默仅 auto_login 且未设 NO_AUTO_CHANNELS_LOGIN 时才调起扫码。""" """发视频号前对齐双路径 Cookie。默认静默仅 auto_login 且未设 NO_AUTO_CHANNELS_LOGIN 时才调起扫码。"""
sync_channels_cookie_files() sync_channels_cookie_files()
@@ -620,8 +647,12 @@ async def _publish_one_round(args: argparse.Namespace) -> tuple[int, int]:
if (p, v.name) not in published_set: if (p, v.name) not in published_set:
total_new += 1 total_new += 1
force_channels_timed = "视频号" in targets
effective_now = bool(args.now) and not force_channels_timed
if args.now and force_channels_timed:
print(" [i] 检测到视频号任务:已忽略 --now改为按时间节点定时发布。")
schedule_times = None schedule_times = None
if not args.now and total_new > 1: if (not effective_now and total_new > 1) or force_channels_timed:
if args.legacy_schedule: if args.legacy_schedule:
schedule_times = generate_schedule( schedule_times = generate_schedule(
len(videos), len(videos),
@@ -631,6 +662,8 @@ async def _publish_one_round(args: argparse.Namespace) -> tuple[int, int]:
) )
else: else:
schedule_times = generate_smart_schedule(len(videos)) schedule_times = generate_smart_schedule(len(videos))
if force_channels_timed:
schedule_times = _enforce_channels_schedule_slots(schedule_times, len(videos))
print(f"\n{'='*60}") print(f"\n{'='*60}")
print(f" 分发计划 ({mode})") print(f" 分发计划 ({mode})")
@@ -641,7 +674,7 @@ async def _publish_one_round(args: argparse.Namespace) -> tuple[int, int]:
sched_label = "立即发布" sched_label = "立即发布"
if schedule_times: if schedule_times:
sched_label = "定时排期(智能错峰)" if not args.legacy_schedule else "定时排期legacy 随机间隔)" sched_label = "定时排期(智能错峰)" if not args.legacy_schedule else "定时排期legacy 随机间隔)"
print(f" 发布方式: {sched_label if not args.now else '立即发布'}") print(f" 发布方式: {sched_label if not effective_now else '立即发布'}")
if not args.no_dedup: if not args.no_dedup:
skipped = len(videos) * len(targets) - total_new skipped = len(videos) * len(targets) - total_new
if skipped > 0: if skipped > 0:

View File

@@ -152,6 +152,9 @@ class VideoMeta:
def hashtags(self, platform: str) -> str: def hashtags(self, platform: str) -> str:
"""# 标签字符串""" """# 标签字符串"""
tags = self._smart_tags(platform) tags = self._smart_tags(platform)
if platform == "视频号":
# 视频号按新规则:去掉 Soul 相关 # 标签,仅保留业务/品牌相关标签。
tags = [t for t in tags if "soul" not in t.lower()]
parts = [f"#{t}" for t in tags] parts = [f"#{t}" for t in tags]
if platform == "视频号": if platform == "视频号":
parts.extend(CHANNELS_FIXED_TAGS) parts.extend(CHANNELS_FIXED_TAGS)

View File

@@ -746,9 +746,9 @@ def _scheduled_ts_for_channels(scheduled_time) -> int:
else: else:
ts = int(scheduled_time) ts = int(scheduled_time)
now = int(time.time()) now = int(time.time())
# 2 分钟内视为「立即」,避免 postTime 过近被服务端拒绝 # 视频号新规则:统一走定时,不走立即发表;过近时间点自动顺延到 10 分钟后。
if ts <= now + 120: if ts <= now + 600:
return 0 return now + 600
return ts return ts

View File

@@ -423,3 +423,4 @@
| 2026-03-22 21:22:06 | 🔄 卡若AI 同步 2026-03-22 21:22 | 更新Cursor规则、金仓、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-22 21:22:06 | 🔄 卡若AI 同步 2026-03-22 21:22 | 更新Cursor规则、金仓、卡木、总索引与入口、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-23 09:48:42 | [强制] 🔄 卡若AI 同步 2026-03-23 09:48 | 更新Cursor规则、金仓、水桥平台对接、卡木、火炬、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-23 09:48:42 | [强制] 🔄 卡若AI 同步 2026-03-23 09:48 | 更新Cursor规则、金仓、水桥平台对接、卡木、火炬、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-23 13:36:13 | [强制] 🔄 卡若AI 同步 2026-03-23 13:35 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | | 2026-03-23 13:36:13 | [强制] 🔄 卡若AI 同步 2026-03-23 13:35 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-23 13:49:45 | [强制] 🔄 卡若AI 同步 2026-03-23 13:49 | 更新:金仓、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 |

View File

@@ -426,3 +426,4 @@
| 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) | | 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) |
| 2026-03-23 09:48:42 | 成功(强制) | 成功 | 🔄 卡若AI 同步 2026-03-23 09:48 | 更新Cursor规则、金仓、水桥平台对接、卡木、火炬、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-23 09:48:42 | 成功(强制) | 成功 | 🔄 卡若AI 同步 2026-03-23 09:48 | 更新Cursor规则、金仓、水桥平台对接、卡木、火炬、运营中枢、运营中枢参考资料、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-23 13:36:13 | 成功(强制) | 成功 | 🔄 卡若AI 同步 2026-03-23 13:35 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) | | 2026-03-23 13:36:13 | 成功(强制) | 成功 | 🔄 卡若AI 同步 2026-03-23 13:35 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-23 13:49:45 | 成功(强制) | 成功 | 🔄 卡若AI 同步 2026-03-23 13:49 | 更新:金仓、水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |