🔄 卡若AI 同步 2026-03-23 13:58 | 更新:水桥平台对接、卡木、运营中枢工作台 | 排除 >20MB: 11 个
This commit is contained in:
@@ -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、送礼1;Soul推流无截图数据填0→脚本跳过第5行保留空
|
# 130场 2026-03-21:视频号直播结束页 02:25:49≈146min;观众总数2278、最高在线355、新增关注4、总热度3、送礼1;Soul推流无截图数据填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;无总热度/礼物展示填0;Soul推流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}')
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -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 个 |
|
||||||
|
|||||||
@@ -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) |
|
||||||
|
|||||||
Reference in New Issue
Block a user