feat(video-analysis): 抖音免浏览器解析链路 + 快手支持 + 小红书加固 + 发送防重复
- 抖音(2026-10-13):删除 SSR + playwright 旧链路(douyin_ssr.py 移除, douyin_api.py 精简为下载工具),新增 douyin_web.py(x-tt-argus 网关头 + web detail 接口 + 最高码率梯/多 CDN 回退下载)与 douyin_sign.py (纯 Python a_bogus 签名,vendor 自 f2(Apache-2.0),依赖 gmssl SM3); 实测 10/10 成功率、单作品 <1s(旧浏览器链路 5-15s 且随机撞验证码) - 快手(2026-10-08):kuaishou_content.py 分享页 INIT_STATE 静态解析(无登录态), 清晰度梯最佳档 + 图集 URL 组装,入口/策略/Web 面板同步接入 - 小红书:按链接来源定国际/国内站优先序、视频 backupUrls 逐候选回退、 图片 urlDefault→infoList WB_DFT→url 三档回退 - 长视频防重复发送(2026-10-13):媒体发送带 per-call _timeout(默认 300s), 超时=结果未知不再降级重试;auto/active 两条 handler 互斥 - Web 解析封面抽帧 ffmpeg 加 CREATE_NO_WINDOW(pythonw 下不再弹 cmd 黑窗) - 依赖:+gmssl;测试:新增 test_douyin_web / test_kuaishou_parse / test_video_cleanup, 扩充 test_video_policy / test_video_sender_link / test_video_group_file / test_video_media_naming Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
1 parent
a31aab00e8
commit
a789a44f5d
25 files changed
+3536
-895
No files matched your search
@@ -11,7 +11,11 @@ from nonebot.rule import to_me
|
||||
from nonebot_plugin_alconna import UniMessage
|
||||
from nonebot_plugin_alconna.uniseg import get_target
|
||||
|
||||
from ..services.fetchers.bilibili_content import fetch_bilibili_content, resolve_short_link
|
||||
from ..services.fetchers.bilibili_content import (
|
||||
fetch_bilibili_content,
|
||||
resolve_short_link,
|
||||
)
|
||||
from ..services.fetchers.kuaishou_content import fetch_kuaishou_content
|
||||
from ..services.fetchers.rednote_content import fetch_rednote_content
|
||||
from .douyin import parse_douyin, process_douyin_res
|
||||
from .sender import PendingMedia, send_pending_media
|
||||
@@ -32,6 +36,8 @@ VALID_HOSTS = [
|
||||
"iesdouyin.com",
|
||||
"m.douyin.com",
|
||||
"jingxuan.douyin.com",
|
||||
"kuaishou.com",
|
||||
"chenzhongtech.com",
|
||||
"x.com",
|
||||
"twitter.com",
|
||||
"xiaohongshu.com",
|
||||
@@ -39,7 +45,16 @@ VALID_HOSTS = [
|
||||
"xhslink.cn",
|
||||
]
|
||||
|
||||
auto_video_handler = on_message(priority=10, block=False)
|
||||
async def _not_to_me(event: Event) -> bool:
|
||||
"""不是 @bot / 昵称开头的消息(`to_me()` 的反面;Rule 没有 `__invert__`)。"""
|
||||
return not event.is_tome()
|
||||
|
||||
|
||||
# auto 与 active 是互斥的两条路:@bot(或昵称开头)的消息归 active 管,其余归
|
||||
# auto。两个 matcher 都是 block=False(不拦其它插件),若 auto 不排除 to_me,
|
||||
# 一条 @bot 的链接消息会同时命中两个 handler → 解析发送两遍(长视频要等几分钟,
|
||||
# 重复非常显眼;2026-10-13 排查发送重复时发现)。
|
||||
auto_video_handler = on_message(priority=10, block=False, rule=_not_to_me)
|
||||
active_video_handler = on_message(priority=10, block=False, rule=to_me())
|
||||
|
||||
|
||||
@@ -203,14 +218,41 @@ async def dispatch_url(
|
||||
logger.warning(f"媒体解析:未能获取到作品:{url}")
|
||||
return None, None
|
||||
return await process_douyin_res(
|
||||
title, parsed_path, is_private, image_post,
|
||||
policy=policy, platform=platform,
|
||||
title,
|
||||
parsed_path,
|
||||
is_private,
|
||||
image_post,
|
||||
policy=policy,
|
||||
platform=platform,
|
||||
)
|
||||
except Exception:
|
||||
await UniMessage.text(f"无法解析到媒体:{url}").send()
|
||||
logger.exception(f"媒体解析:无法解析到媒体:{url}")
|
||||
return None, None
|
||||
|
||||
if "kuaishou.com" in url or "chenzhongtech.com" in url:
|
||||
await UniMessage.text("检测到链接,正在处理,请稍候...").send()
|
||||
try:
|
||||
title, parsed_path = await fetch_kuaishou_content(url)
|
||||
if not title and parsed_path is None:
|
||||
await UniMessage.text(f"无法解析到媒体:{url}").send()
|
||||
return None, None
|
||||
if parsed_path == []:
|
||||
await UniMessage.text(title).send()
|
||||
return None, None
|
||||
return await process_douyin_res(
|
||||
title,
|
||||
parsed_path,
|
||||
is_private,
|
||||
isinstance(parsed_path, list),
|
||||
policy=policy,
|
||||
platform=platform,
|
||||
)
|
||||
except Exception:
|
||||
await UniMessage.text(f"无法解析到媒体:{url}").send()
|
||||
logger.exception(f"快手解析:无法解析:{url}")
|
||||
return None, None
|
||||
|
||||
if any(
|
||||
kw in url
|
||||
for kw in (
|
||||
@@ -230,8 +272,12 @@ async def dispatch_url(
|
||||
await UniMessage.text(title).send()
|
||||
return None, None
|
||||
return await process_douyin_res(
|
||||
title, parsed_path, is_private,
|
||||
isinstance(parsed_path, list), policy=policy, platform=platform,
|
||||
title,
|
||||
parsed_path,
|
||||
is_private,
|
||||
isinstance(parsed_path, list),
|
||||
policy=policy,
|
||||
platform=platform,
|
||||
)
|
||||
except Exception:
|
||||
await UniMessage.text(f"无法解析到内容:{url}").send()
|
||||
@@ -249,8 +295,12 @@ async def dispatch_url(
|
||||
await UniMessage.text(title).send()
|
||||
return None, None
|
||||
return await process_douyin_res(
|
||||
title, parsed_path, is_private,
|
||||
isinstance(parsed_path, list), policy=policy, platform=platform,
|
||||
title,
|
||||
parsed_path,
|
||||
is_private,
|
||||
isinstance(parsed_path, list),
|
||||
policy=policy,
|
||||
platform=platform,
|
||||
)
|
||||
except Exception:
|
||||
await UniMessage.text(f"无法解析到内容:{url}").send()
|
||||
|
||||
Reference in new issue
Block a user