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,6 +11,7 @@ import zipfile
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from nonebot.adapters.onebot.v11.exception import NetworkError
|
||||
|
||||
_MODULE_PATH = (
|
||||
Path(__file__).resolve().parents[1]
|
||||
@@ -223,6 +224,75 @@ async def test_missing_file_short_circuits(gf, tmp_path, monkeypatch):
|
||||
assert called == []
|
||||
|
||||
|
||||
# ────────── 传输超时=结果未知:不降级直传(防两份群文件) ──────────
|
||||
# 与 handlers/sender.py 同一物理事实:超时只是 bot 不再等待,协议端会把这次
|
||||
# 上传做完(大文件远超默认 30s);此时再走本地直传就是重复投递。
|
||||
|
||||
|
||||
def test_send_timeout_default_and_env(gf, monkeypatch):
|
||||
monkeypatch.delenv("video_analysis_send_timeout", raising=False)
|
||||
monkeypatch.delenv("VIDEO_ANALYSIS_SEND_TIMEOUT", raising=False)
|
||||
assert gf._send_timeout() == 300.0
|
||||
|
||||
monkeypatch.setenv("video_analysis_send_timeout", "77")
|
||||
assert gf._send_timeout() == 77.0
|
||||
|
||||
monkeypatch.setenv("video_analysis_send_timeout", "bad")
|
||||
assert gf._send_timeout() == 300.0
|
||||
|
||||
|
||||
async def test_upload_call_carries_per_call_timeout(gf, monkeypatch):
|
||||
"""upload_group_file API 带 `_timeout`(适配器 per-call 超时)。"""
|
||||
monkeypatch.setenv("video_analysis_send_timeout", "77")
|
||||
calls: list[dict] = []
|
||||
|
||||
class _Bot:
|
||||
async def call_api(self, api, **kwargs):
|
||||
calls.append({"api": api, **kwargs})
|
||||
|
||||
monkeypatch.setattr(gf, "get_bot", lambda: _Bot())
|
||||
await gf._call_upload("file:///D:/x.zip", 123, "x.zip")
|
||||
assert calls == [
|
||||
{
|
||||
"api": "upload_group_file",
|
||||
"group_id": 123,
|
||||
"file": "file:///D:/x.zip",
|
||||
"name": "x.zip",
|
||||
"_timeout": 77.0,
|
||||
}
|
||||
]
|
||||
|
||||
|
||||
async def test_s3_timeout_does_not_fall_back_to_local(gf, tmp_path, monkeypatch):
|
||||
"""S3 链接上传超时=结果未知(协议端可能还在传)→ 不再直传,避免两份。"""
|
||||
seen: list[str] = []
|
||||
link = "http://192.168.2.15:5246/PLANA/x.zip?sig=1"
|
||||
|
||||
async def fake_call(file_value, group_id, name, folder_id=None):
|
||||
seen.append(file_value)
|
||||
raise NetworkError("WebSocket call api upload_group_file timeout")
|
||||
|
||||
monkeypatch.setattr(gf, "_call_upload", fake_call)
|
||||
monkeypatch.setattr(gf, "_s3_url", lambda path, policy: link)
|
||||
|
||||
file = _make_files(tmp_path / "src", ["a.zip"])[0]
|
||||
assert await gf.upload_group_file(file, 123) is True
|
||||
assert seen == [link] # 只有 S3 那一次,没有第二次 file:// 直传
|
||||
|
||||
|
||||
async def test_local_upload_timeout_reports_unknown(gf, tmp_path, monkeypatch):
|
||||
"""本地直传超时同样按「可能已送达」处理(不误报失败即可)。"""
|
||||
|
||||
async def fake_call(*args, **kwargs):
|
||||
raise NetworkError("timeout")
|
||||
|
||||
monkeypatch.setattr(gf, "_call_upload", fake_call)
|
||||
monkeypatch.setattr(gf, "_s3_url", lambda path, policy: "")
|
||||
|
||||
file = _make_files(tmp_path / "src", ["a.zip"])[0]
|
||||
assert await gf.upload_group_file(file, 123) is True
|
||||
|
||||
|
||||
# ───────────────────────── 投递编排 ─────────────────────────
|
||||
|
||||
|
||||
@@ -241,9 +311,7 @@ async def test_zip_mode_uploads_single_archive(gf, tmp_path, monkeypatch):
|
||||
archive = tmp_path / "pack.zip"
|
||||
archive.write_bytes(b"zip")
|
||||
calls = _record_uploads(gf, monkeypatch, archive)
|
||||
monkeypatch.setattr(
|
||||
gf, "build_archive", lambda *a, **kw: archive
|
||||
)
|
||||
monkeypatch.setattr(gf, "build_archive", lambda *a, **kw: archive)
|
||||
|
||||
files = _make_files(tmp_path / "src", ["a.txt", "b.txt"])
|
||||
ok = await gf.upload_group_files(files, 123, title="t", password="pw")
|
||||
@@ -271,9 +339,7 @@ async def test_raw_mode_prefixes_work_name_for_multi_files(gf, tmp_path, monkeyp
|
||||
monkeypatch.setattr(gf, "upload_group_file", fake_upload)
|
||||
|
||||
images = _make_files(tmp_path / "src", ["001.jpg", "002.jpg"])
|
||||
await gf.upload_group_files(
|
||||
images, 123, zip_files=False, rel_dir="作者_9/海边日落"
|
||||
)
|
||||
await gf.upload_group_files(images, 123, zip_files=False, rel_dir="作者_9/海边日落")
|
||||
assert seen == ["海边日落_001.jpg", "海边日落_002.jpg"]
|
||||
|
||||
seen.clear()
|
||||
|
||||
Reference in new issue
Block a user