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
@@ -15,6 +15,7 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
import re
|
||||
import zipfile
|
||||
from collections.abc import Iterable
|
||||
@@ -23,6 +24,7 @@ from pathlib import Path
|
||||
from typing import TYPE_CHECKING, Any
|
||||
|
||||
from nonebot import get_bot, logger
|
||||
from nonebot.exception import NetworkError
|
||||
|
||||
if TYPE_CHECKING: # 仅类型检查:运行期不导入,单测可裸加载本模块
|
||||
from ..policy import Policy
|
||||
@@ -154,12 +156,42 @@ def _file_uri(path: Path) -> str:
|
||||
return "file:///" + str(path.resolve()).replace("\\", "/")
|
||||
|
||||
|
||||
#: 群文件上传 per-call 超时(秒)——大文件在协议端「下载 + 上传群文件」会超过
|
||||
#: NoneBot 默认的 30s(同 handlers/sender.py 的 `send_timeout`,就地实现以保持
|
||||
#: 本模块可裸加载;配置键同一个 `video_analysis_send_timeout`)。
|
||||
_DEFAULT_SEND_TIMEOUT = 300.0
|
||||
|
||||
|
||||
def _send_timeout() -> float:
|
||||
raw = (
|
||||
os.environ.get("video_analysis_send_timeout")
|
||||
or os.environ.get("VIDEO_ANALYSIS_SEND_TIMEOUT")
|
||||
or ""
|
||||
).strip()
|
||||
try:
|
||||
value = float(raw)
|
||||
except ValueError:
|
||||
return _DEFAULT_SEND_TIMEOUT
|
||||
return value if value > 0 else _DEFAULT_SEND_TIMEOUT
|
||||
|
||||
|
||||
def _unknown_outcome(exc: BaseException) -> bool:
|
||||
"""超时类错误 = 结果未知:协议端可能仍在处理并最终送达(同 sender 的判定)。
|
||||
|
||||
这种失败**不能**再降级到本地直传重试——那会让群文件里出现两份。
|
||||
"""
|
||||
return isinstance(exc, NetworkError) or isinstance(
|
||||
exc, (asyncio.TimeoutError, TimeoutError)
|
||||
)
|
||||
|
||||
|
||||
async def _call_upload(
|
||||
file_value: str, group_id: int, name: str, folder_id: str | None = None
|
||||
) -> None:
|
||||
params: dict = {"group_id": group_id, "file": file_value, "name": name}
|
||||
if folder_id:
|
||||
params["folder"] = folder_id
|
||||
params["_timeout"] = _send_timeout()
|
||||
await get_bot().call_api("upload_group_file", **params)
|
||||
|
||||
|
||||
@@ -181,7 +213,9 @@ async def upload_group_file(
|
||||
) -> bool:
|
||||
"""上传单个文件到群文件,返回是否成功。
|
||||
|
||||
两级投递,**S3 链接优先、本地直传兜底**:
|
||||
两级投递,**S3 链接优先、本地直传兜底**;只有第 1 级**明确失败**才降级,
|
||||
超时(结果未知)直接收手——协议端可能还在传,再直传一份就重复了
|
||||
(见 `_unknown_outcome`)。
|
||||
|
||||
本环境实测 NapCat 读不到 bot 进程写的本地文件(`realpath ... ENOENT`,
|
||||
媒体消息的本地直发 55 次全失败、换 S3 链接后次次成功),所以直传只留作
|
||||
@@ -206,6 +240,14 @@ async def upload_group_file(
|
||||
try:
|
||||
await _call_upload(url, group_id, display_name, folder_id)
|
||||
except Exception as e:
|
||||
if _unknown_outcome(e):
|
||||
# 超时=结果未知:协议端可能还在传,再走本地直传会出两份群文件
|
||||
logger.warning(
|
||||
f"群文件 S3 链接上传超时,结果未知(协议端可能仍在传输),"
|
||||
f"不再降级直传以免重复(群 {group_id} / {display_name}): "
|
||||
f"{str(e)[:160]}"
|
||||
)
|
||||
return True
|
||||
logger.warning(
|
||||
f"群文件 S3 链接上传失败(群 {group_id} / {display_name}): "
|
||||
f"{str(e)[:160]};改用本地路径重试"
|
||||
@@ -220,6 +262,12 @@ async def upload_group_file(
|
||||
try:
|
||||
await _call_upload(_file_uri(path), group_id, display_name, folder_id)
|
||||
except Exception as e:
|
||||
if _unknown_outcome(e):
|
||||
logger.warning(
|
||||
f"群文件上传超时,结果未知(协议端可能仍在传输)"
|
||||
f"(群 {group_id} / {display_name}): {str(e)[:160]}"
|
||||
)
|
||||
return True
|
||||
logger.warning(
|
||||
f"群文件上传失败(群 {group_id} / {display_name}): {str(e)[:160]}"
|
||||
)
|
||||
|
||||
Reference in new issue
Block a user