🔄 卡若AI 同步 2026-03-10 19:36 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个

This commit is contained in:
2026-03-10 19:36:19 +08:00
parent d00a4954e6
commit f212b741cb
16 changed files with 1507 additions and 329 deletions

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,163 @@
{
"cookies": [
{
"name": "sessionid",
"value": "BgAAlUekVXtrtxMKBuoTfHRptlfxmDAWjCoVppaDKWcrAiVDAycStUCN%2BkcYWkC786pzHlNnmrbJG1NkDrDjw1epdXydipxraFq1fqWAOIA%3D",
"domain": "channels.weixin.qq.com",
"path": "/",
"expires": 1807693838.404647,
"httpOnly": false,
"secure": true,
"sameSite": "None"
},
{
"name": "wxuin",
"value": "1925733981",
"domain": "channels.weixin.qq.com",
"path": "/",
"expires": 1807693838.404685,
"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;1773134435681"
},
{
"name": "__ml::page_e7b7ede3-dadd-44d8-bfee-f0467dc018c6",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"6dd2b03b-87f5-48eb-b7cd-7b848f34b4ea\",\"step\":2,\"refAccessId\":\"716a0cd8-b79e-4d09-96a5-3608ca3b56a6\",\"refPageId\":\"MicroPost\"}"
},
{
"name": "__ml::hb_ts",
"value": "1773134395731"
},
{
"name": "__ml::page_e6ebcfbf-55f8-459f-8cbd-e25593f752aa",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"5c9016f6-ca55-487d-bfff-e731be19c890\",\"step\":1}"
},
{
"name": "__ml::page_5f9512e8-0a4e-41bd-9f10-c846a52f811c",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"3c75cadf-81f0-4d36-a869-53c55b7bf8bb\",\"step\":1}"
},
{
"name": "__ml::page_d43dfe7c-183b-4b7f-9b0d-fc0037082ad2",
"value": "{\"pageId\":\"PostCreate\",\"accessId\":\"2bda158d-425c-42be-8902-b54bd90fcebc\",\"step\":1}"
},
{
"name": "__ml::page",
"value": "[\"9fa40fc4-98eb-4d7b-8e22-1ec96a1e1712\",\"5dcbd7e6-1d0a-4fad-a073-cbe548ef78ab\",\"7b06f5f0-dbea-49f7-b8f2-584bbb5f5255\",\"e6ebcfbf-55f8-459f-8cbd-e25593f752aa\",\"99804864-a1fe-4ba0-8ae6-969f5c7ea8ec\",\"be6dc06b-00e1-4f0f-8225-da6660790adc\",\"4d9862c3-8891-459f-a432-0993148c4e48\",\"17b7c164-a20f-4a38-8811-4c4504de332f\",\"efde92e1-a447-48d3-8691-ed52d1ecf999\",\"d43dfe7c-183b-4b7f-9b0d-fc0037082ad2\",\"be10d348-b3e2-4fc6-8252-5ac529b60e61\",\"4feaceae-d5a5-44ed-9d0c-a61790d0ac88\",\"4493896b-8ace-49b1-8ec3-d33697af23a2\",\"770458db-9fff-4509-b630-d46ab45465eb\",\"66c89831-55ca-437b-a518-7a96a07c6fdc\",\"5f9512e8-0a4e-41bd-9f10-c846a52f811c\",\"e7b7ede3-dadd-44d8-bfee-f0467dc018c6\",\"bc60cd6e-0a51-4cd3-9d93-15d4a1414562\",\"83a755e9-c380-49fd-be06-2f29a20deb8d\",\"9cc52efb-076f-4482-a99e-954872e95a52\"]"
},
{
"name": "__ml::page_4493896b-8ace-49b1-8ec3-d33697af23a2",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"c63ac4ee-58ba-495a-bc33-8b30d3d488c6\",\"step\":2,\"refAccessId\":\"c44c0221-f278-494e-95d4-2ccecf6eb900\",\"refPageId\":\"MicroPost\"}"
},
{
"name": "__ml::page_efde92e1-a447-48d3-8691-ed52d1ecf999",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"ea094f50-eaf0-48be-8d17-03447ec3c21a\",\"step\":1}"
},
{
"name": "__ml::page_4d9862c3-8891-459f-a432-0993148c4e48",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"5220a072-43fe-4917-9be1-3d9651c32cc8\",\"step\":1}"
},
{
"name": "AssistantUploadedInfoStorageKey_3899420810",
"value": "[{\"fileUploadedInfoKey\":\"金融AI获客体系 后端30人沉淀12年前端丢手机.mp4:1773049087899:13904487:video/mp4:cf94106b56208a5f60024854fd65c749\",\"isUsedQuickUpload\":false,\"uploadChunkRecord\":[{\"index\":0,\"reqTime\":1773134417475,\"resTime\":1773134419978,\"cost\":2503,\"success\":true},{\"index\":1,\"reqTime\":1773134417500,\"resTime\":1773134419632,\"cost\":2132,\"success\":true}],\"uuid\":\"5ead770c-fcb7-4069-bd32-cf8632fb00ce\",\"uploadTaskId\":\"CkRmMDM2NTU1NTQzMDVmYjBkZmM3YmM1MTQyMWFjMThjZTQzZWQ2MDc5N2MzNjQxOGE4MGY5MTRiNDgxNWJkMDAwMzMwYxI+NTY5YWZlMjUxMDAwNzI0OWNlODZjNzA4YTAwMDAwMGZiMDAwMDRmNGU1MzQ4MjFiMzUxZjE1NmEyM2NlY2Mw59TQBg==\",\"uploadTaskIdTimeStamp\":1773134417433,\"transFlag\":\"0_0\",\"partInfo\":[{\"PartNumber\":1,\"ETag\":\"\\\"7aa7d5d4489b865e3fabba21d6441651e5affa44\\\"\"},{\"PartNumber\":2,\"ETag\":\"\\\"0b210f5d82059972fd1356f41ebe3bbcc0fb4afd\\\"\"}],\"uploadSuccessResp\":{\"data\":{\"DownloadURL\":\"http://wxapp.tc.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQDo10woW6qVWuvF2I1kB1OPp2P3icKiaZ5zdLS4iaIsaxPDSvhIfDBgB3TyQukaRq52SDcRBGorELARBE3gV4iaLdV4&findertoken=088ae1b1c30e10d4c4bfcd061800223c66696e64657275706c6f616475726c5f333839393432303831305f313737333133343432303230355f363731353536363339363131303136333839362a2030376231363265623862393934663434346134383130623433313133663761303801400348005000580260ce9e01&hy=SH&idx=1&m=&scene=2&token=x5Y29zUxcibCDPn6ryZn3m8ZIGECqs0ey1a4K09cBLCpm4WxcwIocbCw6j4NZh9wiaZZII3O5U87cVu45stAvtAnrES61UruMwEGib3TLk2yoKEvvia6u8nU6A&uzid=7a152\",\"httpsUrl\":\"https://finder.video.qq.com/251/20302/stodownload?bizid=1023&dotrans=0&encfilekey=Cvvj5Ix3eewK0tHtibORqcsqchXNh0Gf3sJcaYqC2rQDo10woW6qVWuvF2I1kB1OPp2P3icKiaZ5zdLS4iaIsaxPDSvhIfDBgB3TyQukaRq52SDcRBGorELARBE3gV4iaLdV4&findertoken=088ae1b1c30e10d4c4bfcd061800223c66696e64657275706c6f616475726c5f333839393432303831305f313737333133343432303230355f363731353536363339363131303136333839362a2030376231363265623862393934663434346134383130623433313133663761303801400348005000580260ce9e01&hy=SH&idx=1&m=&scene=2&token=x5Y29zUxcibCDPn6ryZn3m8ZIGECqs0ey1a4K09cBLCpm4WxcwIocbCw6j4NZh9wiaZZII3O5U87cVu45stAvtAnrES61UruMwEGib3TLk2yoKEvvia6u8nU6A&uzid=7a152\"}}}]"
},
{
"name": "__ml::page_5dcbd7e6-1d0a-4fad-a073-cbe548ef78ab",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"35c98491-9b79-4ac5-aea8-7b009c6c73f3\",\"step\":2,\"refAccessId\":\"a66782b9-9954-46f9-b10a-a70547d5cb90\",\"refPageId\":\"PostCreate\"}"
},
{
"name": "__ml::page_7b06f5f0-dbea-49f7-b8f2-584bbb5f5255",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"519b9718-cf53-4413-afb5-34b6170d56fe\",\"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_be10d348-b3e2-4fc6-8252-5ac529b60e61",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"550e4b8d-a7b6-4e07-9483-478f6092cc5d\",\"step\":1}"
},
{
"name": "__ml::page_9cc52efb-076f-4482-a99e-954872e95a52",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"8088110d-acd4-4ff9-a9bb-20c320749269\",\"step\":1}"
},
{
"name": "__ml::page_99804864-a1fe-4ba0-8ae6-969f5c7ea8ec",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"67682578-18a2-4d06-9243-838f5310df15\",\"step\":2,\"refAccessId\":\"173adf5d-8e34-4ba6-a8ba-4f4ede0d98c5\",\"refPageId\":\"MicroPost\"}"
},
{
"name": "__ml::page_bc60cd6e-0a51-4cd3-9d93-15d4a1414562",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"8950bd30-9573-4ace-955f-aef4834f5d48\",\"step\":2,\"refAccessId\":\"0d5130ac-b06b-48bb-92ad-9ded3632fd27\",\"refPageId\":\"PostCreate\"}"
},
{
"name": "__ml::page_66c89831-55ca-437b-a518-7a96a07c6fdc",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"51c9dcd4-561d-4151-b148-03b62c02a870\",\"step\":1}"
},
{
"name": "__ml::aid",
"value": "\"bd71a1bd-04de-40a0-9f51-bbb8a5742f95\""
},
{
"name": "__rx::aid",
"value": "\"bd71a1bd-04de-40a0-9f51-bbb8a5742f95\""
},
{
"name": "finder_login_token",
"value": ""
},
{
"name": "__ml::page_9fa40fc4-98eb-4d7b-8e22-1ec96a1e1712",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"7507f92b-ea29-4278-b825-7a15bb166c07\",\"step\":2,\"refAccessId\":\"4dfed0e6-3b01-4104-bf31-31fe396e7c90\",\"refPageId\":\"MicroPost\"}"
},
{
"name": "__ml::page_be6dc06b-00e1-4f0f-8225-da6660790adc",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"5951b6d4-0b5b-4e8a-97fe-fb6f2e279eda\",\"step\":2,\"refAccessId\":\"e8c08c35-6dab-4f95-b5c9-3630440cb41a\",\"refPageId\":\"PostCreate\"}"
},
{
"name": "__ml::page_17b7c164-a20f-4a38-8811-4c4504de332f",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"c05f2163-1df4-46cb-ade2-e1c8e55e3510\",\"step\":1}"
},
{
"name": "finder_username",
"value": "v2_060000231003b20faec8c5e48919cbd5cb05e53db077dd1924028a806c10cffd891eb5a80ce7@finder"
},
{
"name": "_finger_print_device_id",
"value": "6fd704941768442b12a996d2652fc61e"
},
{
"name": "MICRO_VISITED_NAME",
"value": "{\"content\":15}"
},
{
"name": "__ml::page_4feaceae-d5a5-44ed-9d0c-a61790d0ac88",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"483570fa-e6a0-4ffb-9346-9b5f2f8381f4\",\"step\":1}"
},
{
"name": "UvFirstReportLocalKey",
"value": "1773072000000"
},
{
"name": "__ml::page_83a755e9-c380-49fd-be06-2f29a20deb8d",
"value": "{\"pageId\":\"MicroPost\",\"accessId\":\"44db79de-9a51-457c-956a-06eeab7d8ebd\",\"step\":1}"
},
{
"name": "__ml::page_770458db-9fff-4509-b630-d46ab45465eb",
"value": "{\"pageId\":\"PostList\",\"accessId\":\"6e166aa7-4132-4d21-a03c-215fb557eab5\",\"step\":2,\"refAccessId\":\"2225f708-4e6b-4aef-a741-c2950fd397dc\",\"refPageId\":\"PostCreate\"}"
},
{
"name": "finder_uin",
"value": ""
}
]
}
]
}

View File

@@ -0,0 +1,422 @@
# -*- coding: utf-8 -*-
"""
多平台视频发布 - 违禁词/敏感词过滤模块
用于抖音、视频号、快手、B站、小红书等平台的内容合规过滤。
支持分类违禁词库、安全替换、平台差异化策略。
"""
from __future__ import annotations
import re
from enum import Enum
from typing import Callable
# =============================================================================
# 违禁词分类枚举
# =============================================================================
class Category(str, Enum):
"""违禁词分类"""
POLITICAL = "政治敏感词"
FINANCIAL = "金融违禁词"
MEDICAL = "医疗违禁词"
EXAGGERATION = "夸大宣传词"
TRAFFIC = "引流违禁词"
VULGAR = "低俗敏感词"
PLATFORM = "平台规避词"
# =============================================================================
# 违禁词库(分类存储)
# =============================================================================
BANNED_WORDS: dict[Category, list[str]] = {
Category.POLITICAL: [
"六四", "天安门", "法轮功", "台独", "藏独", "疆独", "反华", "反共",
"颠覆", "暴动", "分裂", "邪教", "敏感人物", "敏感事件",
],
Category.FINANCIAL: [
"赚钱", "暴利", "暴富", "躺赚", "轻松月入", "稳赚", "零风险", "保本",
"年化收益", "高回报", "日入过万", "月入过万", "一夜暴富", "财富自由",
"割韭菜", "套利", "内幕", "炒股", "荐股", "代客理财", "保底收益",
"无风险", "稳赚不赔", "必涨", "稳赚", "躺赢", "薅羊毛", "返利",
"月入X万", "日入X万", "躺着赚钱", "睡后收入", "财务自由捷径",
],
Category.MEDICAL: [
"根治", "特效", "祖传秘方", "包治", "药到病除", "偏方", "神药",
"抗癌", "防癌", "治愈", "疗效显著", "立竿见影", "一吃就灵",
"纯天然无副作用", "绝对安全", "百试百灵", "国家级秘方",
],
Category.EXAGGERATION: [
"全网最", "史上最", "世界第一", "中国第一", "行业第一", "第一",
"最好", "最强", "最大", "最高", "绝对", "100%", "百分百",
"顶级", "极致", "无敌", "碾压", "吊打", "秒杀", "最强没有之一",
"全网独家", "独家秘方", "绝无仅有", "唯一", "必备", "必买",
],
Category.TRAFFIC: [
"加微信", "加我微信", "私聊", "私信", "点击链接", "扫码", "关注领取",
"加QQ", "加群", "进群", "添加客服", "添加助理", "V我", "私我",
"评论区扣1", "主页有", "主页领取", "链接在简介", "评论区置顶",
"扫码进群", "微信同号", "联系方式", "留联系方式", "导流",
],
Category.VULGAR: [
"约炮", "约P", "约pao", "色情", "", "", "激情", "一夜情",
"大胸", "爆乳", "嫩模", "外围", "包养", "卖淫", "嫖娼",
],
Category.PLATFORM: [
"ICU", "", "彩票", "博彩", "棋牌", "赌博", "六合彩", "时时彩",
"老虎机", "百家乐", "德州扑克", "红包群", "赌博群", "外挂",
],
}
# =============================================================================
# 替换映射表(违禁词 -> 安全替代词)
# 注意:长词条优先匹配,顺序会影响替换结果
# =============================================================================
REPLACEMENT_MAP: dict[str, str] = {
# 金融类
"躺着赚钱": "被动获得收益",
"躺赢": "稳健收益",
"躺赚": "被动收入",
"一夜暴富": "快速积累",
"暴富": "财务自由",
"暴利": "高毛利",
"割韭菜": "收割用户",
"赚钱": "获得收益",
"稳赚不赔": "稳健回报",
"稳赚": "稳定收益",
"零风险": "低风险",
"无风险": "可控风险",
"保本": "本金保障型",
"年化收益": "年化回报",
"高回报": "良好回报",
"日入过万": "日营收过万",
"月入过万": "月营收过万",
"轻松月入": "月度营收",
"日入X万": "日营收可观",
"月入X万": "月营收可观",
"睡后收入": "被动收入",
"财富自由捷径": "财务规划路径",
"套利": "价差策略",
"退税": "税务优化",
"薅羊毛": "权益获取",
"返利": "返佣",
"代客理财": "资产管理服务",
"荐股": "投资建议",
"炒股": "证券投资",
"内幕": "深度信息",
"保底收益": "预期收益",
"必涨": "看涨预期",
# 医疗类
"根治": "显著改善",
"特效": "显著效果",
"祖传秘方": "传统配方",
"包治": "针对改善",
"药到病除": "效果明显",
"偏方": "民间方法",
"神药": "高效产品",
"抗癌": "辅助调理",
"防癌": "健康维护",
"治愈": "改善",
"疗效显著": "效果较好",
"立竿见影": "见效较快",
"一吃就灵": "使用有效",
"纯天然无副作用": "天然成分",
"国家级秘方": "专业配方",
"百试百灵": "口碑较好",
"绝对安全": "相对安全",
# 夸大类
"全网最": "非常",
"史上最": "极为",
"世界第一": "行业领先",
"中国第一": "国内领先",
"行业第一": "业内领先",
"最好": "很好",
"最强": "很强",
"最大": "很大",
"最高": "很高",
"绝对": "非常",
"100%": "高比例",
"百分百": "高比例",
"顶级": "高端",
"极致": "出色",
"无敌": "出众",
"碾压": "优于",
"吊打": "远超",
"秒杀": "超值",
"最强没有之一": "非常强",
"全网独家": "特色",
"独家秘方": "独特配方",
"绝无仅有": "少有",
"唯一": "优选",
"必备": "推荐",
"必买": "值得入手",
# 引流类
"加微信": "通过平台联系",
"加我微信": "平台内沟通",
"私聊": "私信沟通",
"私信": "消息沟通",
"点击链接": "查看详情",
"扫码": "扫码查看",
"关注领取": "关注后获取",
"加QQ": "平台联系",
"加群": "加入社群",
"进群": "加入社群",
"添加客服": "联系客服",
"添加助理": "联系助理",
"V我": "联系我",
"私我": "私信我",
"评论区扣1": "评论区互动",
"主页有": "主页可见",
"主页领取": "主页获取",
"链接在简介": "详见简介",
"评论区置顶": "置顶说明",
"扫码进群": "扫码加入",
"微信同号": "平台联系",
"联系方式": "联系通道",
"留联系方式": "留联系方式",
"导流": "引导关注",
# 平台规避
"ICU": "重症监护",
"": "博弈",
"彩票": "彩券",
"博彩": "竞猜",
"棋牌": "棋类游戏",
"赌博": "竞猜类",
"六合彩": "彩券",
"时时彩": "彩券",
"老虎机": "游戏机",
"百家乐": "纸牌游戏",
"德州扑克": "扑克游戏",
"红包群": "福利群",
"赌博群": "兴趣群",
# 工具/业务类
"外挂": "辅助工具",
}
# 仅在 strict 模式下替换的词(变现、私域等常用词在宽松模式下保留)
STRICT_ONLY_REPLACEMENTS: dict[str, str] = {
"变现": "商业转化",
"私域": "用户池",
}
# =============================================================================
# 平台严格度配置
# =============================================================================
PlatformStrictness = {
"抖音": "strict",
"douyin": "strict",
"视频号": "medium",
"channels": "medium",
"快手": "medium",
"kuaishou": "medium",
"B站": "relaxed",
"bilibili": "relaxed",
"哔哩哔哩": "relaxed",
"小红书": "strict",
"xiaohongshu": "strict",
}
# =============================================================================
# 数据结构
# =============================================================================
# =============================================================================
# 核心函数
# =============================================================================
def _build_patterns(strict: bool = False) -> list[tuple[str, str, str]]:
"""
构建 (原词, 替换词, 分类) 的匹配元组列表。
按词长度降序,保证长词优先匹配。
strict=True 时额外纳入 STRICT_ONLY_REPLACEMENTS如 变现、私域)。
"""
items: list[tuple[str, str, str]] = []
for cat, words in BANNED_WORDS.items():
for w in words:
rep = REPLACEMENT_MAP.get(w)
if not rep and strict:
rep = STRICT_ONLY_REPLACEMENTS.get(w)
if rep:
items.append((w, rep, cat.value))
if strict:
for w, rep in STRICT_ONLY_REPLACEMENTS.items():
if w not in {x[0] for x in items}:
items.append((w, rep, "金融违禁词"))
# 去重并按长度降序
seen: set[str] = set()
unique: list[tuple[str, str, str]] = []
for w, rep, cat in items:
if w not in seen:
seen.add(w)
unique.append((w, rep, cat))
unique.sort(key=lambda x: -len(x[0]))
return unique
def _escape_regex(s: str) -> str:
"""转义正则特殊字符"""
return re.escape(s)
def filter_text(text: str, strict: bool = False) -> tuple[str, list[str]]:
"""
过滤文本中的违禁词,替换为安全词。
Args:
text: 待过滤文本
strict: 若为 True启用更严格的过滤包括 变现、私域 等)
Returns:
(过滤后文本, 替换记录列表)
"""
if not text or not isinstance(text, str):
return text or "", []
replacements_made: list[str] = []
result = text
patterns = _build_patterns(strict)
for word, replacement, _ in patterns:
pattern = re.compile(_escape_regex(word), re.IGNORECASE)
for m in pattern.finditer(result):
old_slice = result[m.start() : m.end()]
# 仅当实际替换发生且与原文不同时记录
if old_slice != replacement:
replacements_made.append(f"{old_slice}{replacement}")
result = pattern.sub(replacement, result)
return result, replacements_made
def check_text(text: str) -> list[dict]:
"""
检测文本中的违禁词,返回详情列表(不替换)。
Returns:
[{"word": str, "start": int, "end": int, "category": str, "replacement": str}, ...]
"""
if not text or not isinstance(text, str):
return []
findings: list[dict] = []
patterns = _build_patterns(strict=True) # 用完整映射做检测
for word, replacement, category in patterns:
pattern = re.compile(_escape_regex(word), re.IGNORECASE)
for m in pattern.finditer(text):
findings.append(
{
"word": m.group(),
"start": m.start(),
"end": m.end(),
"category": category,
"replacement": replacement,
}
)
# 按 start 排序
findings.sort(key=lambda x: x["start"])
return findings
def get_platform_filter(platform: str) -> Callable[[str], tuple[str, list[str]]]:
"""
根据平台返回对应的过滤函数。
Args:
platform: 平台名,如 抖音、视频号、快手、B站、小红书
Returns:
接受 (text: str) 的过滤函数,返回 (filtered_text, replacements)
"""
level = PlatformStrictness.get(platform, PlatformStrictness.get(platform.lower(), "medium"))
strict = level == "strict"
def _filter(t: str) -> tuple[str, list[str]]:
return filter_text(t, strict=strict)
return _filter
def filter_for_platform(text: str, platform: str) -> str:
"""
按平台规则过滤文本(仅返回过滤后的文本)。
Args:
text: 待过滤文本
platform: 平台名
Returns:
过滤后的文本
"""
try:
filt = get_platform_filter(platform)
filtered, _ = filt(text)
return filtered
except Exception:
return text
# =============================================================================
# 主程序:演示与测试
# =============================================================================
if __name__ == "__main__":
DEMO_TEXTS = [
"信任不是求来的,发三个月邮件拿下德国总代理 #销售思维",
"ICU出来一年多活着就要在互联网上留下东西",
"后端花170万搭体系前端几十块就能参与",
"懒人也能赚钱?动作简单、有利可图、正反馈",
]
print("=" * 60)
print("内容过滤模块 - 测试")
print("=" * 60)
for i, raw in enumerate(DEMO_TEXTS, 1):
print(f"\n【原文 {i}")
print(f" {raw}")
# check_text
issues = check_text(raw)
if issues:
print(f" 检测到 {len(issues)} 处违禁/敏感词:")
for it in issues:
print(f" - [{it['category']}] 「{it['word']}」 → {it['replacement']} (位置 {it['start']}-{it['end']})")
else:
print(" 未检测到违禁词")
# filter_text (非严格)
filtered, reps = filter_text(raw, strict=False)
print(f"\n【过滤后(非严格)】")
print(f" {filtered}")
if reps:
print(f" 替换记录: {reps}")
# filter_text (严格)
filtered_strict, reps_strict = filter_text(raw, strict=True)
if filtered_strict != filtered or reps_strict != reps:
print(f"\n【过滤后(严格)】")
print(f" {filtered_strict}")
if reps_strict:
print(f" 替换记录: {reps_strict}")
# 平台级过滤演示
print("\n" + "=" * 60)
print("平台级过滤演示(以「懒人也能赚钱?」为例)")
print("=" * 60)
sample = "懒人也能赚钱?动作简单、有利可图、正反馈"
for platform in ["抖音", "视频号", "B站"]:
out = filter_for_platform(sample, platform)
print(f" {platform}: {out}")

View File

@@ -1,202 +1,252 @@
#!/usr/bin/env python3
"""
多平台 Cookie 统一管理器
- 加载 Playwright storage_state.json
- 检查 Cookie 有效期
- 提供 cookie_str / headers
- 防止重复获取 Cookie
- 中央存储:多平台分发/cookies/{platform}_cookies.json
- Playwright storage_state 格式:{"cookies": [...], "origins": [...]}
- 支持视频号 auth API 校验,其它平台预留 stub
- 视频号保存时同步至 channels_storage_state.json 以兼容旧脚本
"""
import json
import time
from pathlib import Path
from datetime import datetime
from typing import Any
import httpx
class CookieManager:
"""统一管理各平台的 storage_state.json"""
# 常量
COOKIE_STORE_DIR = Path(__file__).parent.parent / "cookies"
CHANNELS_LEGACY_PATH = Path(__file__).parent.parent.parent / "视频号发布" / "脚本" / "channels_storage_state.json"
def __init__(self, state_path: Path, domain_filter: str = ""):
self.state_path = Path(state_path)
self.domain_filter = domain_filter
self._state = {}
self._cookies = {}
self._load()
SUPPORTED_PLATFORMS = ["视频号", "抖音", "快手", "B站", "小红书"]
def _load(self):
if not self.state_path.exists():
raise FileNotFoundError(f"Cookie 文件不存在: {self.state_path}")
with open(self.state_path, "r", encoding="utf-8") as f:
self._state = json.load(f)
self._cookies = self._extract_cookies()
# 各平台默认 cookie 域名
PLATFORM_DOMAINS = {
"视频号": "channels.weixin.qq.com",
"抖音": ".douyin.com",
"快手": ".kuaishou.com",
"B站": ".bilibili.com",
"小红书": ".xiaohongshu.com",
}
def _extract_cookies(self) -> dict:
result = {}
for c in self._state.get("cookies", []):
domain = c.get("domain", "")
if self.domain_filter and self.domain_filter not in domain:
continue
result[c["name"]] = {
"value": c["value"],
"domain": domain,
"expires": c.get("expires", -1),
"path": c.get("path", "/"),
}
return result
@property
def cookie_str(self) -> str:
return "; ".join(f"{k}={v['value']}" for k, v in self._cookies.items())
@property
def cookie_dict(self) -> dict:
return {k: v["value"] for k, v in self._cookies.items()}
def get(self, name: str, default: str = "") -> str:
info = self._cookies.get(name)
return info["value"] if info else default
def get_local_storage(self, origin_filter: str, key: str) -> str:
for origin in self._state.get("origins", []):
if origin_filter not in origin.get("origin", ""):
continue
for item in origin.get("localStorage", []):
if item["name"] == key:
return item["value"]
return ""
# 各平台核心 session cookie只检查这些的有效期忽略短期追踪 cookie
SESSION_COOKIES = {
"bilibili.com": ["SESSDATA", "bili_jct", "DedeUserID"],
"douyin.com": ["sessionid", "passport_csrf_token", "sid_guard"],
"weixin.qq.com": ["wedrive_session_id", "sess_key"],
"xiaohongshu.com": ["web_session", "a1", "webId"],
"kuaishou.com": ["kuaishou.server.web_st", "kuaishou.server.web_ph", "userId"],
}
def check_expiry(self) -> dict:
"""检查 Cookie 有效期(只看核心 session cookie忽略短期追踪 cookie"""
now = time.time()
session_names = set()
for domain_key, names in self.SESSION_COOKIES.items():
if self.domain_filter and domain_key in self.domain_filter:
session_names.update(names)
elif not self.domain_filter:
session_names.update(names)
max_session_expires = 0
has_session_cookie = False
long_lived_expires = float("inf")
for name, info in self._cookies.items():
exp = info.get("expires", -1)
if name in session_names:
has_session_cookie = True
if exp > 0 and exp > max_session_expires:
max_session_expires = exp
elif exp > 0 and (exp - now) > 3600:
if exp < long_lived_expires:
long_lived_expires = exp
if has_session_cookie and max_session_expires > 0:
best_exp = max_session_expires
elif has_session_cookie:
return {
"status": "ok",
"message": "Session cookie 存在(无明确过期时间)",
"remaining_hours": -1,
}
elif long_lived_expires < float("inf"):
best_exp = long_lived_expires
else:
all_expires = [
info["expires"] for info in self._cookies.values()
if info.get("expires", -1) > now
]
if all_expires:
best_exp = max(all_expires)
elif any(info.get("expires", -1) <= 0 for info in self._cookies.values()):
return {
"status": "ok",
"message": "Cookie 存在session 类型,无明确过期时间)",
"remaining_hours": -1,
}
else:
return {
"status": "expired",
"message": "Cookie 全部已过期",
}
remaining = (best_exp - now) / 3600
expires_at = datetime.fromtimestamp(best_exp).strftime("%Y-%m-%d %H:%M")
if remaining < 0:
status = "expired"
elif remaining < 1:
status = "expiring_soon"
elif remaining < 24:
status = "warning"
else:
status = "ok"
return {
"status": status,
"expires_at": expires_at,
"remaining_hours": round(remaining, 1),
"message": f"Cookie 有效至 {expires_at}(剩余 {remaining:.1f}h",
}
def is_valid(self) -> bool:
info = self.check_expiry()
return info["status"] != "expired"
@property
def file_age_hours(self) -> float:
if not self.state_path.exists():
return float("inf")
mtime = self.state_path.stat().st_mtime
return (time.time() - mtime) / 3600
def summary(self) -> str:
expiry = self.check_expiry()
age = self.file_age_hours
lines = [
f"Cookie 文件: {self.state_path.name}",
f"Cookie 数量: {len(self._cookies)}",
f"文件年龄: {age:.1f}h",
f"状态: {expiry['message']}",
]
return "\n".join(lines)
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
)
def check_all_cookies(base_dir: Path) -> dict:
"""检查所有平台的 Cookie 状态"""
platforms = {
"抖音": ("抖音发布/脚本/douyin_storage_state.json", "douyin.com"),
"B站": ("B站发布/脚本/bilibili_storage_state.json", "bilibili.com"),
"视频号": ("视频号发布/脚本/channels_storage_state.json", "weixin.qq.com"),
"小红书": ("小红书发布/脚本/xiaohongshu_storage_state.json", "xiaohongshu.com"),
"快手": ("快手发布/脚本/kuaishou_storage_state.json", "kuaishou.com"),
}
results = {}
for name, (rel_path, domain) in platforms.items():
path = base_dir / rel_path
def _ensure_cookie_dir() -> None:
"""确保 cookie 存储目录存在"""
COOKIE_STORE_DIR.mkdir(parents=True, exist_ok=True)
def get_cookie_path(platform: str) -> Path:
"""返回平台对应的 cookie 文件路径"""
_ensure_cookie_dir()
return COOKIE_STORE_DIR / f"{platform}_cookies.json"
def _dict_to_storage_cookies(cookies: dict, domain: str) -> list[dict]:
"""{name: value} 转为 storage_state 的 cookies 数组"""
now = time.time()
result = []
for name, value in cookies.items():
result.append({
"name": name,
"value": str(value),
"domain": domain,
"path": "/",
"expires": now + 86400 * 30, # 默认 30 天
"httpOnly": False,
"secure": True,
"sameSite": "None",
})
return result
def load_cookies(platform: str) -> dict[str, str] | None:
"""
从文件加载 cookies返回 {name: value},文件不存在或解析失败返回 None。
视频号:若中央存储不存在但旧路径 channels_storage_state.json 存在,自动迁移并加载。
"""
path = get_cookie_path(platform)
if not path.exists():
# 视频号:尝试从旧路径迁移
if platform == "视频号" and CHANNELS_LEGACY_PATH.exists():
try:
with open(CHANNELS_LEGACY_PATH, "r", encoding="utf-8") as f:
data = json.load(f)
_ensure_cookie_dir()
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except (json.JSONDecodeError, OSError):
pass
if not path.exists():
results[name] = {"status": "missing", "message": "未登录"}
return None
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
cookies = data.get("cookies", [])
return {c["name"]: c["value"] for c in cookies if isinstance(c.get("name"), str)}
except (json.JSONDecodeError, KeyError, TypeError) as e:
# 静默失败,返回 None
return None
def save_cookies(
platform: str,
cookies: dict[str, str],
extra_data: dict[str, Any] | None = None,
) -> None:
"""
保存 cookies 为 Playwright storage_state 格式。
cookies: {name: value}
extra_data: 可选,如 {"origins": [...]} 以保留 localStorage 等
"""
_ensure_cookie_dir()
path = get_cookie_path(platform)
domain = PLATFORM_DOMAINS.get(platform, ".example.com")
storage_cookies = _dict_to_storage_cookies(cookies, domain)
data: dict[str, Any] = {"cookies": storage_cookies, "origins": []}
if extra_data:
if "origins" in extra_data:
data["origins"] = extra_data["origins"]
if "cookies" in extra_data:
# 若 extra 中有完整 cookies 对象,可覆盖
data["cookies"] = extra_data["cookies"]
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
# 视频号:同步到旧路径以兼容 channels_publish 等脚本
if platform == "视频号":
try:
CHANNELS_LEGACY_PATH.parent.mkdir(parents=True, exist_ok=True)
with open(CHANNELS_LEGACY_PATH, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
except OSError:
pass
def _check_video_account_valid(cookies: dict[str, str]) -> tuple[bool, str]:
"""视频号POST auth/auth_data 校验errCode==0 为有效"""
url = "https://channels.weixin.qq.com/cgi-bin/mmfinderassistant-bin/auth/auth_data"
cookie_str = "; ".join(f"{k}={v}" for k, v in cookies.items())
headers = {
"User-Agent": UA,
"Referer": "https://channels.weixin.qq.com/",
"Cookie": cookie_str,
"Content-Type": "application/json",
}
try:
with httpx.Client(timeout=15.0) as client:
r = client.post(url, headers=headers, json={})
r.raise_for_status()
body = r.json()
except httpx.HTTPError as e:
return False, f"请求失败: {e}"
except json.JSONDecodeError as e:
return False, f"响应解析失败: {e}"
err = body.get("errCode", -1)
if err != 0:
msg = body.get("errMsg", "未知错误")
return False, f"接口返回 errCode={err}, {msg}"
# 提取昵称
data = body.get("data") or body
nickname = ""
if isinstance(data, dict):
nickname = data.get("nickname") or data.get("nickName") or ""
if nickname:
return True, f"有效 (昵称: {nickname})"
return True, "有效"
def _check_platform_stub(platform: str, cookies: dict[str, str]) -> tuple[bool, str]:
"""抖音、B站、快手、小红书暂为 stub仅检查文件存在和基本结构"""
if not cookies:
return False, "无 cookie 数据"
# 简单启发:有常见 session 字段则视为可能有效
session_keys = {
"抖音": ["sessionid"],
"B站": ["SESSDATA", "bili_jct"],
"快手": ["kuaishou.server.web_st", "userId"],
"小红书": ["web_session", "a1"],
}
keys = session_keys.get(platform, [])
found = any(k in cookies for k in keys)
if found:
return True, "存在(未做接口校验,仅供参考)"
return True, "存在(未做接口校验)"
def check_cookie_valid(platform: str) -> tuple[bool, str]:
"""
校验平台 cookie 是否有效,调用平台特定 auth API。
返回 (is_valid, message)。
"""
cookies = load_cookies(platform)
if not cookies:
return False, "文件不存在或为空"
if platform == "视频号":
return _check_video_account_valid(cookies)
if platform in ("抖音", "B站", "快手", "小红书"):
return _check_platform_stub(platform, cookies)
return False, f"不支持的平台: {platform}"
def get_valid_cookies(platform: str) -> dict[str, str] | None:
"""加载并校验 cookies若过期或无效返回 None"""
is_valid, _ = check_cookie_valid(platform)
if not is_valid:
return None
return load_cookies(platform)
def _format_expiry(cookies_raw: list[dict]) -> str:
"""从 storage_state 的 cookies 中提取最近过期时间"""
now = time.time()
expiries = [c.get("expires", 0) for c in cookies_raw if isinstance(c.get("expires"), (int, float))]
if not expiries:
return "未知"
max_exp = max(e for e in expiries if e > 0) if any(e > 0 for e in expiries) else 0
if max_exp <= 0:
return "Session"
remaining = (max_exp - now) / 3600
if remaining < 0:
return "已过期"
if remaining < 24:
return f"{remaining:.1f}h"
return f"{remaining / 24:.1f}"
def cookie_summary() -> str:
"""返回各平台 cookie 状态摘要(存在/有效/过期)"""
lines = ["=" * 50, " 多平台 Cookie 状态", "=" * 50, f"存储目录: {COOKIE_STORE_DIR}", ""]
for platform in SUPPORTED_PLATFORMS:
# 用 load_cookies 触发迁移(视频号从旧路径)
cookies_dict = load_cookies(platform)
if not cookies_dict:
lines.append(f" [○] {platform}: 未登录")
continue
try:
mgr = CookieManager(path, domain)
results[name] = mgr.check_expiry()
path = get_cookie_path(platform)
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
cookies_arr = data.get("cookies", [])
expiry = _format_expiry(cookies_arr)
is_valid, msg = check_cookie_valid(platform)
icon = "" if is_valid else ""
lines.append(f" [{icon}] {platform}: {msg} | 过期: {expiry}")
except Exception as e:
results[name] = {"status": "error", "message": str(e)}
return results
lines.append(f" [✗] {platform}: 解析失败 - {e}")
return "\n".join(lines)
if __name__ == "__main__":
base = Path(__file__).parent.parent.parent
print("=" * 50)
print(" 多平台 Cookie 状态检查")
print("=" * 50)
results = check_all_cookies(base)
for platform, info in results.items():
icon = {"ok": "", "warning": "", "expiring_soon": "", "expired": "", "missing": "", "error": ""}
print(f" [{icon.get(info['status'], '?')}] {platform}: {info['message']}")
_ensure_cookie_dir()
print(cookie_summary())

View File

@@ -17,21 +17,6 @@
{"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是太累,是脑子装太多,每天做减法 #做减法 #心理健康", "success": false, "status": "error", "message": "[Errno 32] Broken pipe", "elapsed_sec": 0.0, "timestamp": "2026-03-10 15:02:08"}
{"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/这套体系花了170万但前端几十块就能参与.mp4", "title": "后端花170万搭体系前端几十块就能参与 #商业认知 #体系思维", "success": false, "status": "error", "message": "[Errno 32] Broken pipe", "elapsed_sec": 0.0, "timestamp": "2026-03-10 15:02:11"}
{"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/金融AI获客体系 后端30人沉淀12年前端丢手机.mp4", "title": "后端30人沉淀12年前端就丢个手机号 #AI获客 #系统思维", "success": false, "status": "error", "message": "[Errno 32] Broken pipe", "elapsed_sec": 0.0, "timestamp": "2026-03-10 15:02:14"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/ICU出来一年多 活着要在互联网上留下东西.mp4", "title": "ICU出来一年多了。那之后我想明白一件事活着就要在互联网上留下点东西 #人生感悟 #创业觉醒 #Soul派对 #向死而生", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 1.2313990592956543, "timestamp": "2026-03-10 15:15:29"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4", "title": "20岁测MBTI40岁以后该学五行八卦了。年轻人用性格分类中年人靠命理运营自己 #MBTI #五行 #Soul派对 #认知觉醒", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.14910316467285156, "timestamp": "2026-03-10 15:15:32"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul业务模型 派对+切片+小程序全链路.mp4", "title": "一个人怎么跑通一条商业链路派对获客→AI切片→小程序变现全链路拆给你看 #Soul派对 #商业模式 #全链路 #一人公司", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.762732982635498, "timestamp": "2026-03-10 15:15:36"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4", "title": "AI剪辑有多快30秒到8分钟的切片半小时出10到30条。内容工厂的效率密码 #AI剪辑 #Soul派对 #内容效率 #批量生产", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.9359581470489502, "timestamp": "2026-03-10 15:15:40"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥.mp4", "title": "创业初期别急着找钱先找两个IS型人格。ENFJ负责链接ENTJ负责指挥比融资好使十倍 #MBTI创业 #团队搭建 #Soul派对 #合伙人", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 2.2842659950256348, "timestamp": "2026-03-10 15:15:45"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/刷牙听业务逻辑 Soul切片变现怎么跑.mp4", "title": "刷牙3分钟刚好听完一套变现逻辑。Soul切片怎么从0到日产30条碎片时间才是生产力 #Soul派对 #碎片创业 #副业逻辑 #效率", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 1.0495867729187012, "timestamp": "2026-03-10 15:15:49"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/国学易经怎么学 两小时七七八八,召唤作者对话.mp4", "title": "易经其实不难,两小时就能学个七七八八。关键是找到作者的思维频率,跟古人对话 #国学 #易经入门 #Soul派对 #终身学习", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.7680740356445312, "timestamp": "2026-03-10 15:15:53"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了1000曝光6到10块.mp4", "title": "广点通终于能投Soul了1000次曝光只要6到10块这个获客成本你敢信#Soul派对 #广点通投放 #低成本获客 #流量红利", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.13916993141174316, "timestamp": "2026-03-10 15:15:56"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4", "title": "信任不是求来的。一个卖外挂的小伙子,发了三个月邮件,拿下德国总代理。死磕比社交有用 #销售思维 #信任建立 #Soul派对 #死磕精神", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.8055217266082764, "timestamp": "2026-03-10 15:16:00"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/懒人的活法 动作简单有利可图正反馈.mp4", "title": "懒人也能赚钱关键就三个词动作简单、有利可图、正反馈。90%的人输在太勤快了 #Soul派对 #副业思维 #私域变现 #认知升级", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 1.6628410816192627, "timestamp": "2026-03-10 15:16:04"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/早起不是为了开派对,是不吵老婆睡觉.mp4", "title": "每天6点起床不是因为自律是因为老婆还在睡。创业人最真实的起床理由你猜到了吗#Soul派对 #创业日记 #晨间直播 #真实创业", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.5040900707244873, "timestamp": "2026-03-10 15:16:08"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/核心就两个字 筛选。能开派对坚持7天的人再谈.mp4", "title": "别跟所有人合作核心就两个字筛选。能坚持开7天派对的人才值得深聊 #筛选思维 #Soul派对 #创业认知 #人性", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.3050050735473633, "timestamp": "2026-03-10 15:16:11"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是因为太累,是因为脑子里装太多。每天放下一件事,做减法,睡眠自然好 #睡眠 #做减法 #Soul派对 #心理健康", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.4433469772338867, "timestamp": "2026-03-10 15:16:15"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/这套体系花了170万但前端几十块就能参与.mp4", "title": "后端花了170万搭的体系前端几十块就能参与。真正的商业模式是让别人低成本上车 #商业认知 #Soul派对 #低门槛创业 #体系思维", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.5846946239471436, "timestamp": "2026-03-10 15:16:18"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/金融AI获客体系 后端30人沉淀12年前端丢手机.mp4", "title": "后端30人沉淀了12年前端操作就是丢个手机号。金融AI获客体系把复杂留给自己 #AI获客 #金融科技 #Soul派对 #系统思维", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.187608003616333, "timestamp": "2026-03-10 15:16:21"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/ICU出来一年多 活着要在互联网上留下东西.mp4", "title": "ICU出来一年多活着就要在互联网上留下东西 #人生感悟 #创业觉醒", "success": false, "status": "failed", "message": "Playwright: 未找到上传控件", "elapsed_sec": 7.210780143737793, "timestamp": "2026-03-10 15:15:40"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4", "title": "20岁测MBTI40岁该学五行八卦了 #MBTI #认知觉醒", "success": false, "status": "failed", "message": "Playwright: 投稿超时", "screenshot": "/tmp/bilibili_result.png", "elapsed_sec": 312.01766705513, "timestamp": "2026-03-10 15:21:07"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4", "title": "AI剪辑半小时出10到30条切片内容工厂效率密码 #AI剪辑 #内容效率", "success": true, "status": "reviewing", "message": "纯API投稿成功 (9.6s)", "elapsed_sec": 9.607962131500244, "timestamp": "2026-03-10 15:21:20"}
@@ -45,9 +30,6 @@
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是太累,是脑子装太多,每天做减法 #做减法 #心理健康", "success": true, "status": "reviewing", "message": "纯API投稿成功 (3.0s)", "elapsed_sec": 3.016058921813965, "timestamp": "2026-03-10 15:22:18"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/这套体系花了170万但前端几十块就能参与.mp4", "title": "后端花170万搭体系前端几十块就能参与 #商业认知 #体系思维", "success": true, "status": "reviewing", "message": "纯API投稿成功 (6.0s)", "elapsed_sec": 5.999068021774292, "timestamp": "2026-03-10 15:22:27"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/金融AI获客体系 后端30人沉淀12年前端丢手机.mp4", "title": "后端30人沉淀12年前端就丢个手机号 #AI获客 #系统思维", "success": true, "status": "reviewing", "message": "纯API投稿成功 (4.1s)", "elapsed_sec": 4.084810972213745, "timestamp": "2026-03-10 15:22:34"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了1000曝光6到10块.mp4", "title": "广点通能投Soul了1000次曝光只要6到10块 #广点通 #低成本获客", "success": true, "status": "reviewing", "message": "已跳转到内容管理(发表成功)", "screenshot": "/tmp/channels_result.png", "elapsed_sec": 22.535322904586792, "timestamp": "2026-03-10 15:18:30"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/懒人的活法 动作简单有利可图正反馈.mp4", "title": "懒人也能赚钱?动作简单、有利可图、正反馈 #Soul派对 #副业思维", "success": true, "status": "reviewing", "message": "已跳转到内容管理(发表成功)", "screenshot": "/tmp/channels_result.png", "elapsed_sec": 19.76073694229126, "timestamp": "2026-03-10 15:19:17"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是太累,是脑子装太多,每天做减法 #做减法 #心理健康", "success": true, "status": "reviewing", "message": "已跳转到内容管理(发表成功)", "screenshot": "/tmp/channels_result.png", "elapsed_sec": 20.54416298866272, "timestamp": "2026-03-10 15:20:05"}
{"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul业务模型 派对+切片+小程序全链路.mp4", "title": "派对获客→AI切片→小程序变现全链路拆解 #商业模式 #一人公司", "success": true, "status": "reviewing", "message": "已提交,请确认截图", "screenshot": "/tmp/xhs_result.png", "elapsed_sec": 32.887428998947144, "timestamp": "2026-03-10 15:16:01"}
{"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4", "title": "AI剪辑半小时出10到30条切片内容工厂效率密码 #AI剪辑 #内容效率", "success": true, "status": "reviewing", "message": "已提交,请确认截图", "screenshot": "/tmp/xhs_result.png", "elapsed_sec": 26.56322979927063, "timestamp": "2026-03-10 15:16:31"}
{"platform": "小红书", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥.mp4", "title": "创业初期先找两个IS型人格比融资好使十倍 #MBTI创业 #团队搭建", "success": true, "status": "reviewing", "message": "已提交,请确认截图", "screenshot": "/tmp/xhs_result.png", "elapsed_sec": 24.749696016311646, "timestamp": "2026-03-10 15:16:59"}
@@ -73,23 +55,23 @@
{"platform": "快手", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是太累,是脑子装太多,每天做减法 #做减法 #心理健康", "success": true, "status": "reviewing", "message": "已提交,请确认截图", "screenshot": "/tmp/kuaishou_result.png", "elapsed_sec": 20.945794105529785, "timestamp": "2026-03-10 15:20:27"}
{"platform": "快手", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/这套体系花了170万但前端几十块就能参与.mp4", "title": "后端花170万搭体系前端几十块就能参与 #商业认知 #体系思维", "success": true, "status": "published", "message": "发布成功", "screenshot": "/tmp/kuaishou_result.png", "elapsed_sec": 31.37475895881653, "timestamp": "2026-03-10 15:21:02"}
{"platform": "快手", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/金融AI获客体系 后端30人沉淀12年前端丢手机.mp4", "title": "后端30人沉淀12年前端就丢个手机号 #AI获客 #系统思维", "success": true, "status": "published", "message": "发布成功", "screenshot": "/tmp/kuaishou_result.png", "elapsed_sec": 20.888609170913696, "timestamp": "2026-03-10 15:21:26"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/ICU出来一年多 活着要在互联网上留下东西.mp4", "title": "ICU出来一年多了。那之后我想明白一件事活着就要在互联网上留下点东西 #人生感悟 #创业觉醒 #Soul派对 #向死而生", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.1804661750793457, "timestamp": "2026-03-10 15:24:39"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4", "title": "20岁测MBTI40岁以后该学五行八卦了。年轻人用性格分类中年人靠命理运营自己 #MBTI #五行 #Soul派对 #认知觉醒", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.21412396430969238, "timestamp": "2026-03-10 15:24:43"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul业务模型 派对+切片+小程序全链路.mp4", "title": "一个人怎么跑通一条商业链路派对获客→AI切片→小程序变现全链路拆给你看 #Soul派对 #商业模式 #全链路 #一人公司", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.211669921875, "timestamp": "2026-03-10 15:24:46"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4", "title": "AI剪辑有多快30秒到8分钟的切片半小时出10到30条。内容工厂的效率密码 #AI剪辑 #Soul派对 #内容效率 #批量生产", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.3605477809906006, "timestamp": "2026-03-10 15:24:49"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥.mp4", "title": "创业初期别急着找钱先找两个IS型人格。ENFJ负责链接ENTJ负责指挥比融资好使十倍 #MBTI创业 #团队搭建 #Soul派对 #合伙人", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.15439581871032715, "timestamp": "2026-03-10 15:24:52"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/刷牙听业务逻辑 Soul切片变现怎么跑.mp4", "title": "刷牙3分钟刚好听完一套变现逻辑。Soul切片怎么从0到日产30条碎片时间才是生产力 #Soul派对 #碎片创业 #副业逻辑 #效率", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.16161012649536133, "timestamp": "2026-03-10 15:24:56"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/国学易经怎么学 两小时七七八八,召唤作者对话.mp4", "title": "易经其实不难,两小时就能学个七七八八。关键是找到作者的思维频率,跟古人对话 #国学 #易经入门 #Soul派对 #终身学习", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.15852904319763184, "timestamp": "2026-03-10 15:24:59"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了1000曝光6到10块.mp4", "title": "广点通终于能投Soul了1000次曝光只要6到10块这个获客成本你敢信#Soul派对 #广点通投放 #低成本获客 #流量红利", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.20926785469055176, "timestamp": "2026-03-10 15:25:02"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4", "title": "信任不是求来的。一个卖外挂的小伙子,发了三个月邮件,拿下德国总代理。死磕比社交有用 #销售思维 #信任建立 #Soul派对 #死磕精神", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.17077183723449707, "timestamp": "2026-03-10 15:25:05"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/懒人的活法 动作简单有利可图正反馈.mp4", "title": "懒人也能赚钱关键就三个词动作简单、有利可图、正反馈。90%的人输在太勤快了 #Soul派对 #副业思维 #私域变现 #认知升级", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.18039608001708984, "timestamp": "2026-03-10 15:25:08"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/早起不是为了开派对,是不吵老婆睡觉.mp4", "title": "每天6点起床不是因为自律是因为老婆还在睡。创业人最真实的起床理由你猜到了吗#Soul派对 #创业日记 #晨间直播 #真实创业", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.26729893684387207, "timestamp": "2026-03-10 15:25:12"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/核心就两个字 筛选。能开派对坚持7天的人再谈.mp4", "title": "别跟所有人合作核心就两个字筛选。能坚持开7天派对的人才值得深聊 #筛选思维 #Soul派对 #创业认知 #人性", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.16828489303588867, "timestamp": "2026-03-10 15:25:15"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是因为太累,是因为脑子里装太多。每天放下一件事,做减法,睡眠自然好 #睡眠 #做减法 #Soul派对 #心理健康", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.1948409080505371, "timestamp": "2026-03-10 15:25:18"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/这套体系花了170万但前端几十块就能参与.mp4", "title": "后端花了170万搭的体系前端几十块就能参与。真正的商业模式是让别人低成本上车 #商业认知 #Soul派对 #低门槛创业 #体系思维", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.2213449478149414, "timestamp": "2026-03-10 15:25:21"}
{"platform": "抖音", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/金融AI获客体系 后端30人沉淀12年前端丢手机.mp4", "title": "后端30人沉淀了12年前端操作就是丢个手机号。金融AI获客体系把复杂留给自己 #AI获客 #金融科技 #Soul派对 #系统思维", "success": false, "status": "error", "message": "Cookie 已过期", "elapsed_sec": 0.15867185592651367, "timestamp": "2026-03-10 15:25:24"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/ICU出来一年多 活着要在互联网上留下东西.mp4", "title": "ICU出来一年多活着就要在互联网上留下东西 #人生感悟 #创业觉醒", "success": true, "status": "reviewing", "message": "纯API投稿成功 (5.7s)", "elapsed_sec": 5.730467796325684, "timestamp": "2026-03-10 15:25:34"}
{"platform": "B站", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4", "title": "20岁测MBTI40岁该学五行八卦了 #MBTI #认知觉醒", "success": true, "status": "reviewing", "message": "纯API投稿成功 (5.3s)", "elapsed_sec": 5.3280439376831055, "timestamp": "2026-03-10 15:25:42"}
{"platform": "快手", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/ICU出来一年多 活着要在互联网上留下东西.mp4", "title": "ICU出来一年多活着就要在互联网上留下东西 #人生感悟 #创业觉醒", "success": true, "status": "reviewing", "message": "已提交,请确认截图", "screenshot": "/tmp/kuaishou_result.png", "elapsed_sec": 19.600856065750122, "timestamp": "2026-03-10 15:26:05"}
{"platform": "快手", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了1000曝光6到10块.mp4", "title": "广点通能投Soul了1000曝光只要6到10块 #广点通 #低成本获客", "success": true, "status": "published", "message": "发布成功", "screenshot": "/tmp/kuaishou_result.png", "elapsed_sec": 20.851877689361572, "timestamp": "2026-03-10 15:26:28"}
{"platform": "快手", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/核心就两个字 筛选。能开派对坚持7天的人再谈.mp4", "title": "核心就两个字筛选。能坚持7天的人才值得深聊 #筛选思维 #创业认知", "success": true, "status": "published", "message": "发布成功", "screenshot": "/tmp/kuaishou_result.png", "elapsed_sec": 18.800668001174927, "timestamp": "2026-03-10 15:26:50"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/ICU出来一年多 活着要在互联网上留下东西.mp4", "title": "ICU出来一年多活着就要在互联网上留下东西 #人生感悟 #创业觉醒", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/ICU出来一年多 活着要在互联网上留下东西_5_verify.png", "elapsed_sec": 31.226758003234863, "timestamp": "2026-03-10 18:11:45"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4", "title": "20岁测MBTI40岁该学五行八卦了 #MBTI #认知觉醒", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦_5_verify.png", "elapsed_sec": 30.47995686531067, "timestamp": "2026-03-10 18:12:24"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul业务模型 派对+切片+小程序全链路.mp4", "title": "派对获客→AI切片→小程序变现全链路拆解 #商业模式 #一人公司", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/Soul业务模型 派对+切片+小程序全链路_5_verify.png", "elapsed_sec": 30.663927793502808, "timestamp": "2026-03-10 18:13:03"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4", "title": "AI剪辑半小时出10到30条切片内容工厂效率密码 #AI剪辑 #内容效率", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/Soul切片30秒到8分钟 AI半小时能剪10到30个_5_verify.png", "elapsed_sec": 30.90933394432068, "timestamp": "2026-03-10 18:13:42"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥.mp4", "title": "创业初期先找两个IS型人格比融资好使十倍 #MBTI创业 #团队搭建", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥_5_verify.png", "elapsed_sec": 30.632225036621094, "timestamp": "2026-03-10 18:14:20"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/刷牙听业务逻辑 Soul切片变现怎么跑.mp4", "title": "刷牙3分钟听完一套变现逻辑 #碎片创业 #副业逻辑", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/刷牙听业务逻辑 Soul切片变现怎么跑_5_verify.png", "elapsed_sec": 30.63472008705139, "timestamp": "2026-03-10 18:14:59"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/国学易经怎么学 两小时七七八八,召唤作者对话.mp4", "title": "易经两小时学个七七八八,关键是跟古人对话 #国学 #易经入门", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/国学易经怎么学 两小时七七八八召唤作者对话_5_verify.png", "elapsed_sec": 30.53782320022583, "timestamp": "2026-03-10 18:15:38"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/广点通能投Soul了1000曝光6到10块.mp4", "title": "广点通能投Soul了1000次曝光只要6到10块 #广点通 #低成本获客", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/广点通能投Soul了1000曝光6到10块_5_verify.png", "elapsed_sec": 30.867189168930054, "timestamp": "2026-03-10 18:16:17"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4", "title": "信任不是求来的,发三个月邮件拿下德国总代理 #销售思维 #信任建立", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/建立信任不是求来的 卖外挂发邮件三个月拿下德国总代_5_verify.png", "elapsed_sec": 31.436657190322876, "timestamp": "2026-03-10 18:16:56"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/懒人的活法 动作简单有利可图正反馈.mp4", "title": "懒人也能赚钱?动作简单、有利可图、正反馈 #Soul派对 #副业思维", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/懒人的活法 动作简单有利可图正反馈_5_verify.png", "elapsed_sec": 30.71743607521057, "timestamp": "2026-03-10 18:17:35"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/早起不是为了开派对,是不吵老婆睡觉.mp4", "title": "每天6点起床不是因为自律是因为老婆还在睡 #Soul派对 #创业日记", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/早起不是为了开派对是不吵老婆睡觉_5_verify.png", "elapsed_sec": 30.861982822418213, "timestamp": "2026-03-10 18:18:14"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/核心就两个字 筛选。能开派对坚持7天的人再谈.mp4", "title": "核心就两个字筛选。能坚持7天的人才值得深聊 #筛选思维 #创业认知", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/核心就两个字 筛选。能开派对坚持7天的人再谈_5_verify.png", "elapsed_sec": 31.045093774795532, "timestamp": "2026-03-10 18:18:53"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/睡眠不好?每天放下一件事,做减法.mp4", "title": "睡不好不是太累,是脑子装太多,每天做减法 #做减法 #心理健康", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/睡眠不好每天放下一件事做减法_5_verify.png", "elapsed_sec": 32.4734902381897, "timestamp": "2026-03-10 18:19:34"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/这套体系花了170万但前端几十块就能参与.mp4", "title": "后端花170万搭体系前端几十块就能参与 #商业认知 #体系思维", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/这套体系花了170万但前端几十块就能参与_5_verify.png", "elapsed_sec": 34.45086717605591, "timestamp": "2026-03-10 18:20:16"}
{"platform": "视频号", "video_path": "/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片/金融AI获客体系 后端30人沉淀12年前端丢手机.mp4", "title": "后端30人沉淀12年前端就丢个手机号 #AI获客 #系统思维", "success": true, "status": "reviewing", "message": "API确认列表未匹配 (未在列表前20条中找到)", "screenshot": "/tmp/channels_ss/金融AI获客体系 后端30人沉淀12年前端丢手机_5_verify.png", "elapsed_sec": 3430.994603872299, "timestamp": "2026-03-10 19:17:35"}

View File

@@ -15,7 +15,7 @@ def generate_schedule(
min_gap: int = 30,
max_gap: int = 120,
max_hours: float = 24.0,
first_delay: int = 5,
first_delay: int = 125,
start_time: datetime = None,
) -> list[datetime]:
"""

View File

@@ -1,5 +1,8 @@
#!/usr/bin/env python3
"""获取抖音 Cookie - 弹窗浏览器 → 扫码登录 → 保存 storage_state"""
"""
抖音创作者平台登录 — 弹窗浏览器扫码 → 自动检测登录 → 保存 storage_state
扫码后无需手动操作,脚本自动检测登录状态并保存。
"""
import asyncio
from pathlib import Path
from playwright.async_api import async_playwright
@@ -8,8 +11,8 @@ COOKIE_FILE = Path(__file__).parent / "douyin_storage_state.json"
async def main():
print("即将弹出浏览器,请用抖音扫码登录。")
print("登录成功后,在 Playwright Inspector 窗口中点击绿色 ▶ 按钮\n")
print("即将弹出浏览器,请用抖音 APP 扫码登录。")
print("登录成功后脚本会自动保存 Cookie无需手动操作\n")
async with async_playwright() as pw:
browser = await pw.chromium.launch(headless=False)
@@ -26,7 +29,31 @@ async def main():
""")
page = await context.new_page()
await page.goto("https://creator.douyin.com/", timeout=60000)
await page.pause()
print("[i] 等待扫码登录... (最长等待 120 秒)")
try:
await page.wait_for_url(
"**/creator-micro/home**",
timeout=120000,
)
print("[✓] 检测到登录成功!正在保存 Cookie...")
except Exception:
print("[i] 未检测到自动跳转,尝试检测 Cookie...")
for _ in range(30):
cookies = await context.cookies()
has_session = any(
c["name"] in ("sessionid", "sessionid_ss", "passport_csrf_token")
for c in cookies
if "douyin.com" in c.get("domain", "")
)
if has_session:
print("[✓] 检测到登录 Cookie")
break
await asyncio.sleep(2)
else:
print("[⚠] 超时,尝试保存当前状态...")
await asyncio.sleep(2)
await context.storage_state(path=str(COOKIE_FILE))
await context.close()
await browser.close()

View File

@@ -49,35 +49,35 @@ UA = (
TITLES = {
"早起不是为了开派对,是不吵老婆睡觉.mp4":
"每天6点起床不是因为自律是因为老婆还在睡。创业人最真实的起床理由你猜到了吗#Soul派对 #创业日记 #晨间直播 #真实创业",
"每天6点起床不是因为自律是因为老婆还在睡。创业人最真实的起床理由你猜到了吗#Soul派对 #创业日记 #晨间直播 #真实创业 #小程序 卡若创业派对",
"懒人的活法 动作简单有利可图正反馈.mp4":
"懒人也能赚钱关键就三个词动作简单、有利可图、正反馈。90%的人输在太勤快了 #Soul派对 #副业思维 #私域变现 #认知升级",
"懒人也能有收益关键就三个词动作简单、有利可图、正反馈。90%的人输在太勤快了 #Soul派对 #副业思维 #商业转化 #认知升级 #小程序 卡若创业派对",
"初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥.mp4":
"创业初期别急着找钱先找两个IS型人格。ENFJ负责链接ENTJ负责指挥比融资好使十倍 #MBTI创业 #团队搭建 #Soul派对 #合伙人",
"创业初期别急着找钱先找两个IS型人格。ENFJ负责链接ENTJ负责指挥比融资好使十倍 #MBTI创业 #团队搭建 #Soul派对 #合伙人 #小程序 卡若创业派对",
"ICU出来一年多 活着要在互联网上留下东西.mp4":
"ICU出来一年多了。那之后我想明白一件事:活着,就要在互联网上留下点东西 #人生感悟 #创业觉醒 #Soul派对 #向死而生",
"重症监护出来一年多了。那之后我想明白一件事:活着,就要在互联网上留下点东西 #人生感悟 #创业觉醒 #Soul派对 #向死而生 #小程序 卡若创业派对",
"MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4":
"20岁测MBTI40岁以后该学五行八卦了。年轻人用性格分类中年人靠命理运营自己 #MBTI #五行 #Soul派对 #认知觉醒",
"20岁测MBTI40岁以后该学五行八卦了。年轻人用性格分类中年人靠命理运营自己 #MBTI #五行 #Soul派对 #认知觉醒 #小程序 卡若创业派对",
"Soul业务模型 派对+切片+小程序全链路.mp4":
"一个人怎么跑通一条商业链路派对获客→AI切片→小程序变现,全链路拆给你看 #Soul派对 #商业模式 #全链路 #一人公司",
"一个人怎么跑通一条商业链路派对获客→AI切片→小程序转化,全链路拆给你看 #Soul派对 #商业模式 #全链路 #一人公司 #小程序 卡若创业派对",
"Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4":
"AI剪辑有多快30秒到8分钟的切片半小时出10到30条。内容工厂的效率密码 #AI剪辑 #Soul派对 #内容效率 #批量生产",
"AI剪辑有多快30秒到8分钟的切片半小时出10到30条。内容工厂的效率密码 #AI剪辑 #Soul派对 #内容效率 #批量生产 #小程序 卡若创业派对",
"刷牙听业务逻辑 Soul切片变现怎么跑.mp4":
"刷牙3分钟刚好听完一套变现逻辑。Soul切片怎么从0到日产30条碎片时间才是生产力 #Soul派对 #碎片创业 #副业逻辑 #效率",
"刷牙3分钟刚好听完一套转化逻辑。Soul切片怎么从0到日产30条碎片时间才是生产力 #Soul派对 #碎片创业 #副业逻辑 #效率 #小程序 卡若创业派对",
"国学易经怎么学 两小时七七八八,召唤作者对话.mp4":
"易经其实不难,两小时就能学个七七八八。关键是找到作者的思维频率,跟古人对话 #国学 #易经入门 #Soul派对 #终身学习",
"易经其实不难,两小时就能学个七七八八。关键是找到作者的思维频率,跟古人对话 #国学 #易经入门 #Soul派对 #终身学习 #小程序 卡若创业派对",
"广点通能投Soul了1000曝光6到10块.mp4":
"广点通终于能投Soul了1000次曝光只要6到10块这个获客成本你敢信#Soul派对 #广点通投放 #低成本获客 #流量红利",
"广点通终于能投Soul了1000次曝光只要6到10块这个获客成本你敢信#Soul派对 #广点通投放 #低成本获客 #流量红利 #小程序 卡若创业派对",
"建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4":
"信任不是求来的。一个卖外挂的小伙子,发了三个月邮件,拿下德国总代理。死磕比社交有用 #销售思维 #信任建立 #Soul派对 #死磕精神",
"信任不是求来的。一个卖辅助工具的小伙子,发了三个月邮件,拿下德国总代理。死磕比社交有用 #销售思维 #信任建立 #Soul派对 #死磕精神 #小程序 卡若创业派对",
"核心就两个字 筛选。能开派对坚持7天的人再谈.mp4":
"别跟所有人合作核心就两个字筛选。能坚持开7天派对的人才值得深聊 #筛选思维 #Soul派对 #创业认知 #人性",
"别跟所有人合作核心就两个字筛选。能坚持开7天派对的人才值得深聊 #筛选思维 #Soul派对 #创业认知 #人性 #小程序 卡若创业派对",
"睡眠不好?每天放下一件事,做减法.mp4":
"睡不好不是因为太累,是因为脑子里装太多。每天放下一件事,做减法,睡眠自然好 #睡眠 #做减法 #Soul派对 #心理健康",
"睡不好不是因为太累,是因为脑子里装太多。每天放下一件事,做减法,睡眠自然好 #睡眠 #做减法 #Soul派对 #心理健康 #小程序 卡若创业派对",
"这套体系花了170万但前端几十块就能参与.mp4":
"后端花了170万搭的体系前端几十块就能参与。真正的商业模式是让别人低成本上车 #商业认知 #Soul派对 #低门槛创业 #体系思维",
"后端花了170万搭的体系前端几十块就能参与。真正的商业模式是让别人低成本上车 #商业认知 #Soul派对 #低门槛创业 #体系思维 #小程序 卡若创业派对",
"金融AI获客体系 后端30人沉淀12年前端丢手机.mp4":
"后端30人沉淀了12年前端操作就是丢个手机号。金融AI获客体系把复杂留给自己 #AI获客 #金融科技 #Soul派对 #系统思维",
"后端30人沉淀了12年前端操作就是丢个手机号。金融AI获客体系把复杂留给自己 #AI获客 #金融科技 #Soul派对 #系统思维 #小程序 卡若创业派对",
}

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,151 @@
#!/usr/bin/env python3
"""
视频号纯 API 登录 — 无浏览器
1. 调 auth_login_code 取 token
2. 用 token 拼 QR URL → 生成二维码图片
3. 轮询 auth_login_status 直到扫码完成
4. 保存 Cookie
"""
import asyncio
import json
import sys
import time
from pathlib import Path
import httpx
import qrcode
SCRIPT_DIR = Path(__file__).parent
COOKIE_FILE = SCRIPT_DIR / "channels_storage_state.json"
QR_IMAGE = Path("/tmp/channels_api_qr.png")
BASE = "https://channels.weixin.qq.com"
HEADERS = {
"User-Agent": (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
),
"Origin": BASE,
"Referer": f"{BASE}/login.html",
"Content-Type": "application/json",
}
def api_login():
client = httpx.Client(headers=HEADERS, follow_redirects=True, timeout=15)
# Step 1: 获取登录 token
print("[1] 获取登录 token...", flush=True)
r = client.post(f"{BASE}/cgi-bin/mmfinderassistant-bin/auth/auth_login_code", json={})
d = r.json()
if d.get("errCode") != 0:
print(f"[✗] auth_login_code 失败: {d}", flush=True)
return False
token = d["data"]["token"]
print(f" token = {token}", flush=True)
# Step 2: 生成二维码
# 视频号助手的扫码 URL 格式
qr_url = f"{BASE}/cgi-bin/mmfinderassistant-bin/auth/auth_login_qrcode?token={token}"
print(f"[2] 生成二维码...", flush=True)
print(f" QR URL: {qr_url}", flush=True)
img = qrcode.make(qr_url)
img.save(str(QR_IMAGE))
print(f" 二维码已保存: {QR_IMAGE}", flush=True)
print(f"\n ★ 请用微信扫描 {QR_IMAGE} 中的二维码 ★\n", flush=True)
# Step 3: 轮询登录状态
print("[3] 等待扫码...", flush=True)
for i in range(60):
time.sleep(3)
# 尝试多种参数格式
for params in [
{"token": token},
{"loginCode": token},
{"rawUrl": qr_url},
]:
try:
r2 = client.post(
f"{BASE}/cgi-bin/mmfinderassistant-bin/auth/auth_login_status",
json=params,
)
d2 = r2.json()
code = d2.get("errCode", -1)
if code == 0:
print(f"\n[✓] 登录成功!", flush=True)
# 提取 cookies
cookies = dict(client.cookies)
# 也检查 response headers 的 set-cookie
for resp_cookie in r2.cookies:
cookies[resp_cookie.name] = resp_cookie.value
print(f" Cookies: {list(cookies.keys())}", flush=True)
_save_cookies(cookies)
return True
if code != 10008: # 10008 = param error, try next format
break
except Exception:
pass
# 也尝试 GET
try:
r3 = client.get(
f"{BASE}/cgi-bin/mmfinderassistant-bin/auth/auth_login_status?token={token}"
)
d3 = r3.json()
if d3.get("errCode") == 0:
print(f"\n[✓] 登录成功!(GET)", flush=True)
cookies = dict(client.cookies)
for resp_cookie in r3.cookies:
cookies[resp_cookie.name] = resp_cookie.value
print(f" Cookies: {list(cookies.keys())}", flush=True)
_save_cookies(cookies)
return True
except Exception:
pass
# 尝试直接访问需要登录的页面看看 cookie 是否已设置
try:
r4 = client.get(f"{BASE}/platform/post/list")
if "platform" in str(r4.url) and "login" not in str(r4.url):
print(f"\n[✓] 检测到已登录!(redirect check)", flush=True)
cookies = dict(client.cookies)
print(f" Cookies: {list(cookies.keys())}", flush=True)
if "sessionid" in cookies:
_save_cookies(cookies)
return True
except Exception:
pass
if i % 5 == 0:
print(f" 等待中... ({i * 3}s)", flush=True)
print("[✗] 3 分钟超时", flush=True)
return False
def _save_cookies(cookies_dict: dict):
"""保存为 Playwright storage_state 格式(兼容 publish 脚本)"""
pw_cookies = []
for name, value in cookies_dict.items():
pw_cookies.append({
"name": name,
"value": value,
"domain": "channels.weixin.qq.com",
"path": "/",
"expires": -1,
"httpOnly": False,
"secure": True,
"sameSite": "None",
})
state = {"cookies": pw_cookies, "origins": []}
COOKIE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2))
print(f" Cookie 已保存: {COOKIE_FILE} ({len(pw_cookies)} 个)", flush=True)
if __name__ == "__main__":
ok = api_login()
sys.exit(0 if ok else 1)

View File

@@ -0,0 +1,340 @@
#!/usr/bin/env python3
"""
视频号纯 API 发布 — 无浏览器
流程: 读 Cookie → 上传视频(分片) → 发布 → 验证
"""
import asyncio
import hashlib
import json
import math
import sys
import time
from pathlib import Path
import httpx
SCRIPT_DIR = Path(__file__).parent
COOKIE_FILE = SCRIPT_DIR / "channels_storage_state.json"
VIDEO_DIR = Path("/Users/karuo/Movies/soul视频/soul 派对 119场 20260309_output/成片")
sys.path.insert(0, str(SCRIPT_DIR.parent.parent / "多平台分发" / "脚本"))
from publish_result import PublishResult, is_published, save_results, print_summary
BASE = "https://channels.weixin.qq.com"
UPLOAD_BASE = "https://finder-assistant.mp.video.tencent-cloud.com"
CHUNK_SIZE = 8 * 1024 * 1024 # 8MB
UA = (
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
)
TITLES = {
"早起不是为了开派对,是不吵老婆睡觉.mp4":
"每天6点起床不是因为自律是因为老婆还在睡 #Soul派对 #创业日记",
"懒人的活法 动作简单有利可图正反馈.mp4":
"懒人也能赚钱?动作简单、有利可图、正反馈 #Soul派对 #副业思维",
"初期团队先找两个IS比钱好使 ENFJ链接人ENTJ指挥.mp4":
"创业初期先找两个IS型人格比融资好使十倍 #MBTI创业 #团队搭建",
"ICU出来一年多 活着要在互联网上留下东西.mp4":
"ICU出来一年多活着就要在互联网上留下东西 #人生感悟 #创业觉醒",
"MBTI疗愈SOUL 年轻人测MBTI40到60岁走五行八卦.mp4":
"20岁测MBTI40岁该学五行八卦了 #MBTI #认知觉醒",
"Soul业务模型 派对+切片+小程序全链路.mp4":
"派对获客→AI切片→小程序变现全链路拆解 #商业模式 #一人公司",
"Soul切片30秒到8分钟 AI半小时能剪10到30个.mp4":
"AI剪辑半小时出10到30条切片内容工厂效率密码 #AI剪辑 #内容效率",
"刷牙听业务逻辑 Soul切片变现怎么跑.mp4":
"刷牙3分钟听完一套变现逻辑 #碎片创业 #副业逻辑",
"国学易经怎么学 两小时七七八八,召唤作者对话.mp4":
"易经两小时学个七七八八,关键是跟古人对话 #国学 #易经入门",
"广点通能投Soul了1000曝光6到10块.mp4":
"广点通能投Soul了1000次曝光只要6到10块 #广点通 #低成本获客",
"建立信任不是求来的 卖外挂发邮件三个月拿下德国总代.mp4":
"信任不是求来的,发三个月邮件拿下德国总代理 #销售思维 #信任建立",
"核心就两个字 筛选。能开派对坚持7天的人再谈.mp4":
"核心就两个字筛选。能坚持7天的人才值得深聊 #筛选思维 #创业认知",
"睡眠不好?每天放下一件事,做减法.mp4":
"睡不好不是太累,是脑子装太多,每天做减法 #做减法 #心理健康",
"这套体系花了170万但前端几十块就能参与.mp4":
"后端花170万搭体系前端几十块就能参与 #商业认知 #体系思维",
"金融AI获客体系 后端30人沉淀12年前端丢手机.mp4":
"后端30人沉淀12年前端就丢个手机号 #AI获客 #系统思维",
}
def load_cookies() -> dict:
if not COOKIE_FILE.exists():
return {}
state = json.loads(COOKIE_FILE.read_text())
return {c["name"]: c["value"] for c in state.get("cookies", [])}
def _build_client(cookies: dict) -> httpx.Client:
return httpx.Client(
cookies=cookies,
headers={
"User-Agent": UA,
"Origin": BASE,
"Referer": f"{BASE}/platform/post/create",
},
follow_redirects=True,
timeout=120,
)
def _api_post(client: httpx.Client, path: str, payload: dict | None = None) -> dict:
url = f"{BASE}/cgi-bin/mmfinderassistant-bin/{path}"
r = client.post(url, json=payload or {})
return r.json()
def _file_md5(path: str) -> str:
h = hashlib.md5()
with open(path, "rb") as f:
for chunk in iter(lambda: f.read(8192), b""):
h.update(chunk)
return h.hexdigest()
def check_auth(client: httpx.Client) -> dict | None:
"""验证 Cookie 有效性,返回用户信息"""
d = _api_post(client, "auth/auth_data")
if d.get("errCode") == 0:
return d.get("data", {}).get("finderUser", {})
return None
def upload_video(client: httpx.Client, video_path: str) -> dict | None:
"""上传视频到腾讯云,返回 media 信息"""
fpath = Path(video_path)
fsize = fpath.stat().st_size
fname = fpath.name
fmd5 = _file_md5(video_path)
n_parts = math.ceil(fsize / CHUNK_SIZE)
print(f" [上传] {fname} ({fsize / 1024 / 1024:.1f}MB, {n_parts} 分片)", flush=True)
# Step 1: Apply for upload
apply_payload = {
"mediaName": fname,
"mediaSize": fsize,
"mediaMd5": fmd5,
"mediaType": "video/mp4",
"chunkSize": CHUNK_SIZE,
}
r = client.post(f"{UPLOAD_BASE}/applyuploaddfs", json=apply_payload)
try:
d = r.json()
except Exception:
print(f" [上传] applyuploaddfs 失败: status={r.status_code} body={r.text[:200]}", flush=True)
return None
if "data" not in d:
print(f" [上传] applyuploaddfs 无 data: {json.dumps(d, ensure_ascii=False)[:200]}", flush=True)
return None
upload_id = d["data"].get("uploadId") or d["data"].get("UploadId")
if not upload_id:
print(f" [上传] 未获取到 uploadId: {json.dumps(d['data'], ensure_ascii=False)[:200]}", flush=True)
return None
print(f" [上传] uploadId = {upload_id[:30]}...", flush=True)
# Step 2: Upload parts
parts = []
with open(video_path, "rb") as f:
for i in range(n_parts):
chunk = f.read(CHUNK_SIZE)
part_num = i + 1
print(f" [上传] 分片 {part_num}/{n_parts} ({len(chunk) / 1024:.0f}KB)...", flush=True)
r2 = client.post(
f"{UPLOAD_BASE}/uploadpartdfs",
data={"uploadId": upload_id, "partNumber": str(part_num)},
files={"file": (fname, chunk, "application/octet-stream")},
timeout=120,
)
try:
d2 = r2.json()
etag = d2.get("data", {}).get("ETag") or d2.get("data", {}).get("etag")
if etag:
parts.append({"PartNumber": part_num, "ETag": etag})
print(f" [上传] 分片 {part_num} 完成 (ETag={etag[:20]}...)", flush=True)
else:
print(f" [上传] 分片 {part_num} 无 ETag: {json.dumps(d2, ensure_ascii=False)[:150]}", flush=True)
except Exception:
print(f" [上传] 分片 {part_num} 失败: status={r2.status_code}", flush=True)
if len(parts) != n_parts:
print(f" [上传] 分片不完整: {len(parts)}/{n_parts}", flush=True)
return None
# Step 3: Complete upload
complete_payload = {
"uploadId": upload_id,
"partInfo": parts,
}
r3 = client.post(f"{UPLOAD_BASE}/completepartuploaddfs", json=complete_payload)
try:
d3 = r3.json()
media_url = d3.get("data", {}).get("httpsUrl") or d3.get("data", {}).get("DownloadURL")
if media_url:
print(f" [上传] 完成! URL = {media_url[:60]}...", flush=True)
return {
"url": media_url,
"uploadId": upload_id,
"md5": fmd5,
"size": fsize,
"parts": parts,
"completeResp": d3.get("data", {}),
}
else:
print(f" [上传] complete 无 URL: {json.dumps(d3, ensure_ascii=False)[:200]}", flush=True)
except Exception:
print(f" [上传] complete 失败: status={r3.status_code}", flush=True)
return None
def publish_video(client: httpx.Client, media_info: dict, description: str) -> dict:
"""调用发布 API"""
payload = {
"description": description,
"mediaInfo": media_info,
}
return _api_post(client, "helper/helper_video_publish", payload)
def verify_published(client: httpx.Client, title_keyword: str) -> bool:
"""通过 post_list API 检查视频是否发布"""
d = _api_post(client, "post/post_list", {"currentPage": 1, "pageSize": 20})
if d.get("errCode") != 0:
return False
posts = d.get("data", {}).get("list", [])
kw = title_keyword[:15]
for p in posts:
desc = p.get("desc", p.get("description", ""))
if kw in desc:
return True
return False
def publish_one(
video_path: str,
title: str,
idx: int = 1,
total: int = 1,
skip_dedup: bool = False,
) -> PublishResult:
fname = Path(video_path).name
fsize = Path(video_path).stat().st_size
t0 = time.time()
print(f"\n[{idx}/{total}] {fname} ({fsize / 1024 / 1024:.1f}MB)", flush=True)
print(f" 标题: {title[:60]}", flush=True)
if not skip_dedup and is_published("视频号", video_path):
print(" [跳过] 已发布", flush=True)
return PublishResult(
platform="视频号", video_path=video_path, title=title,
success=True, status="skipped", message="去重跳过",
)
cookies = load_cookies()
if not cookies:
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=False, status="error", message="Cookie 不存在,请先运行 channels_api_login.py",
)
print(f" {r.log_line()}", flush=True)
return r
client = _build_client(cookies)
# 验证登录
user = check_auth(client)
if not user:
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=False, status="error", message="Cookie 已过期",
elapsed_sec=time.time() - t0,
)
print(f" {r.log_line()}", flush=True)
return r
# 上传
media = upload_video(client, video_path)
if not media:
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=False, status="error", message="视频上传失败",
elapsed_sec=time.time() - t0,
)
print(f" {r.log_line()}", flush=True)
return r
# 发布
print(" [发布] 调用 helper_video_publish...", flush=True)
pub_result = publish_video(client, media, title)
pub_code = pub_result.get("errCode", -1)
print(f" [发布] errCode={pub_code}", flush=True)
elapsed = time.time() - t0
if pub_code == 0:
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=True, status="reviewing",
message=f"API 发布成功 (errCode=0)",
elapsed_sec=elapsed,
)
else:
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=False, status="error",
message=f"发布失败: {json.dumps(pub_result, ensure_ascii=False)[:120]}",
elapsed_sec=elapsed,
)
print(f" {r.log_line()}", flush=True)
return r
def main():
cookies = load_cookies()
if not cookies:
print("[✗] Cookie 不存在,请先运行: python3 channels_api_login.py", flush=True)
return 1
client = _build_client(cookies)
user = check_auth(client)
if not user:
print("[✗] Cookie 已过期,请重新运行: python3 channels_api_login.py", flush=True)
return 1
print(f"[✓] 已登录: {user.get('nickname', '?')} (作品数: {user.get('feedsCount', '?')})", flush=True)
videos = sorted(VIDEO_DIR.glob("*.mp4"))
if not videos:
print("[✗] 未找到视频", flush=True)
return 1
print(f"{len(videos)} 条视频\n", flush=True)
results = []
for i, vp in enumerate(videos):
t = TITLES.get(vp.name, f"{vp.stem} #Soul派对 #创业日记")
r = publish_one(str(vp), t, i + 1, len(videos))
results.append(r)
if r.status != "skipped":
save_results([r])
if i < len(videos) - 1:
time.sleep(5)
actual = [r for r in results if r.status != "skipped"]
print_summary(actual)
ok = sum(1 for r in actual if r.success)
return 0 if ok == len(actual) else 1
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1,8 +1,9 @@
#!/usr/bin/env python3
"""
视频号发布 v2API 响应拦截 + 列表验证 + 小程序挂载
- 不再仅靠页面跳转判断成功;拦截 cgi-bin 响应 + 内容列表复核
- 支持扩展链接挂载小程序
视频号发布 v3headless Playwright + 描述写入修复 + 统一 Cookie
- API 响应拦截 + 列表验证双重确认
- 描述通过 clipboard/insertText 写入(不依赖 contenteditable.fill
- 所有描述追加 #小程序 卡若创业派对
"""
import asyncio
import json
@@ -23,9 +24,7 @@ UA = (
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/143.0.0.0 Safari/537.36"
)
MINIPROGRAM_APPID = "wxb8bbb2b10dec74aa"
MINIPROGRAM_TITLE = "Soul创业派对"
MINIPROGRAM_PAGE = "pages/read/read?mid=119"
DESC_SUFFIX = " #小程序 卡若创业派对"
TITLES = {
"早起不是为了开派对,是不吵老婆睡觉.mp4":
@@ -130,60 +129,8 @@ async def _verify_on_list(page, title_keyword: str) -> tuple[bool, str]:
return False, f"验证异常: {str(e)[:60]}"
async def _try_add_miniprogram(page) -> bool:
"""Attempt to attach miniprogram via the publish-page UI."""
try:
found = await page.evaluate("""() => {
const all = [...document.querySelectorAll('span, div, button, a, label')];
for (const el of all) {
const t = el.textContent.trim();
if ((t.includes('扩展链接') || t.includes('添加链接') || t === '短视频带货')
&& el.offsetParent !== null) {
el.click();
return 'clicked';
}
}
return 'not_found';
}""")
if found != "clicked":
print(" [小程序] 未找到「扩展链接」入口", flush=True)
return False
await asyncio.sleep(1.5)
mp_found = await page.evaluate("""() => {
const all = [...document.querySelectorAll('span, div, li, a')];
for (const el of all) {
if (el.textContent.trim() === '小程序' && el.offsetParent !== null) {
el.click();
return true;
}
}
return false;
}""")
if not mp_found:
print(" [小程序] 未找到「小程序」选项", flush=True)
return False
await asyncio.sleep(1.5)
await page.evaluate(f"""(appid) => {{
const inputs = document.querySelectorAll('input[type="text"]');
for (const inp of inputs) {{
if (inp.placeholder && (inp.placeholder.includes('AppID') || inp.placeholder.includes('appid')
|| inp.placeholder.includes('小程序'))) {{
inp.value = appid;
inp.dispatchEvent(new Event('input', {{bubbles:true}}));
return;
}}
}}
}}""", MINIPROGRAM_APPID)
await asyncio.sleep(0.5)
print(" [小程序] 已尝试填入 AppID", flush=True)
return True
except Exception as e:
print(f" [小程序] 异常: {str(e)[:60]}", flush=True)
return False
# ---------------------------------------------------------------------------
@@ -256,12 +203,15 @@ async def publish_one(
if "扫码" in body_text or "login" in page.url.lower():
await page.screenshot(path=ss("login"))
await browser.close()
return PublishResult(
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=False, status="error",
message="Cookie 已过期(需重新扫码登录)",
screenshot=ss("login"),
elapsed_sec=time.time() - t0,
)
print(f" {r.log_line()}", flush=True)
return r
await page.screenshot(path=ss("1_page"))
@@ -286,51 +236,46 @@ async def publish_one(
if not upload_ok:
await page.screenshot(path=ss("upload_timeout"))
await browser.close()
return PublishResult(
r = PublishResult(
platform="视频号", video_path=video_path, title=title,
success=False, status="error",
message="视频上传超时 (3 min)",
screenshot=ss("upload_timeout"),
elapsed_sec=time.time() - t0,
)
print(f" {r.log_line()}", flush=True)
return r
await asyncio.sleep(3)
await page.screenshot(path=ss("2_uploaded"))
# --- Step 3: fill description ---
print(" [3] 填写描述...", flush=True)
desc_filled = False
add_desc = page.locator("text=添加描述").first
if await add_desc.count() > 0:
await add_desc.click()
await asyncio.sleep(0.5)
active = page.locator('[contenteditable="true"]:visible').first
if await active.count() > 0:
await active.fill("")
await active.type(title, delay=15)
desc_filled = True
else:
await page.keyboard.type(title, delay=15)
desc_filled = True
if not desc_filled:
await page.evaluate(
"""(title) => {
const els = document.querySelectorAll('[contenteditable="true"]');
for (const el of els) {
if (el.offsetParent !== null) {
el.focus();
el.textContent = title;
el.dispatchEvent(new Event('input', {bubbles:true}));
return;
}
}
}""",
title,
)
await asyncio.sleep(0.5)
full_desc = title + DESC_SUFFIX
print(f" [3] 填写描述: {full_desc[:60]}...", flush=True)
# --- Step 3b: mini-program ---
print(" [3b] 尝试挂载小程序...", flush=True)
await _try_add_miniprogram(page)
editor = page.locator('.input-editor').first
if await editor.count() == 0:
editor = page.locator('[data-placeholder="添加描述"]').first
if await editor.count() > 0:
await editor.fill(full_desc)
await asyncio.sleep(0.5)
written = await editor.inner_text()
if full_desc[:15] in written:
print(f" [3] 描述已确认: {written[:50]}...", flush=True)
else:
print(f" [3] fill() 后验证失败, 尝试 click+type...", flush=True)
await editor.click()
await asyncio.sleep(0.3)
await page.keyboard.press("Meta+A")
await page.keyboard.press("Backspace")
await page.keyboard.type(full_desc, delay=8)
await asyncio.sleep(0.5)
else:
print(" [3] ⚠ 未找到描述编辑器 (.input-editor)", flush=True)
await asyncio.sleep(1)
await page.screenshot(path=ss("3_desc"))
# --- Step 4: publish ---
await page.evaluate("window.scrollTo(0, document.body.scrollHeight)")
@@ -462,12 +407,14 @@ async def main():
t = TITLES.get(vp.name, f"{vp.stem} #Soul派对 #创业日记")
r = await publish_one(str(vp), t, i + 1, len(videos))
results.append(r)
# 即时保存(防止中途崩溃丢失记录)
if r.status != "skipped":
save_results([r])
if i < len(videos) - 1:
await asyncio.sleep(8)
actual = [r for r in results if r.status != "skipped"]
print_summary(actual)
save_results(actual)
ok = sum(1 for r in actual if r.success)
return 0 if ok == len(actual) else 1

View File

@@ -0,0 +1,69 @@
#!/usr/bin/env python3
"""
手动设置视频号 Cookie — 不开浏览器
用法:
python3 channels_set_cookie.py <sessionid> <wxuin>
获取方式:
1. 在任意浏览器打开 https://channels.weixin.qq.com 并登录
2. F12 → Application → Cookies → channels.weixin.qq.com
3. 复制 sessionid 和 wxuin 的值
"""
import json
import sys
from pathlib import Path
import httpx
COOKIE_FILE = Path(__file__).parent / "channels_storage_state.json"
BASE = "https://channels.weixin.qq.com"
def main():
if len(sys.argv) < 3:
print("用法: python3 channels_set_cookie.py <sessionid> <wxuin>")
print("\n获取方式:")
print(" 1. 浏览器打开 https://channels.weixin.qq.com 并登录")
print(" 2. F12 → Application → Cookies")
print(" 3. 复制 sessionid 和 wxuin 值")
return 1
sessionid = sys.argv[1]
wxuin = sys.argv[2]
# 验证
cookies = {"sessionid": sessionid, "wxuin": wxuin}
print("验证 Cookie...", flush=True)
r = httpx.post(
f"{BASE}/cgi-bin/mmfinderassistant-bin/auth/auth_data",
cookies=cookies,
headers={"User-Agent": "Mozilla/5.0"},
json={},
timeout=10,
)
d = r.json()
if d.get("errCode") != 0:
print(f"[✗] Cookie 无效: errCode={d.get('errCode')} msg={d.get('errMsg')}")
return 1
user = d.get("data", {}).get("finderUser", {})
print(f"[✓] 验证通过: {user.get('nickname', '?')} (作品: {user.get('feedsCount', '?')})")
# 保存
state = {
"cookies": [
{"name": "sessionid", "value": sessionid, "domain": "channels.weixin.qq.com",
"path": "/", "expires": -1, "httpOnly": False, "secure": True, "sameSite": "None"},
{"name": "wxuin", "value": wxuin, "domain": "channels.weixin.qq.com",
"path": "/", "expires": -1, "httpOnly": False, "secure": True, "sameSite": "None"},
],
"origins": [],
}
COOKIE_FILE.write_text(json.dumps(state, ensure_ascii=False, indent=2))
print(f"[✓] Cookie 已保存: {COOKIE_FILE}")
return 0
if __name__ == "__main__":
sys.exit(main())

View File

@@ -267,3 +267,4 @@
| 2026-03-10 15:17:46 | 🔄 卡若AI 同步 2026-03-10 15:17 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-10 15:29:57 | 🔄 卡若AI 同步 2026-03-10 15:29 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-10 15:47:47 | 🔄 卡若AI 同步 2026-03-10 15:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |
| 2026-03-10 16:09:02 | 🔄 卡若AI 同步 2026-03-10 16:08 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 |

View File

@@ -270,3 +270,4 @@
| 2026-03-10 15:17:46 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 15:17 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-10 15:29:57 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 15:29 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-10 15:47:47 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 15:47 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |
| 2026-03-10 16:09:02 | 成功 | 成功 | 🔄 卡若AI 同步 2026-03-10 16:08 | 更新:卡木、运营中枢工作台 | 排除 >20MB: 11 个 | [仓库](http://open.quwanzhi.com:3000/fnvtk/karuo-ai) [百科](http://open.quwanzhi.com:3000/fnvtk/karuo-ai/wiki) |