fix(plugins): 审查修复——示例插件与通知渠道的若干问题

- run-command:等待进程退出而不是等 stderr 关闭(后台子进程不再导致误判超时),
  stderr 写临时文件取末尾;只替换整项为 {字段} 的参数,{files} 展开为每个文件
  一个参数,夹带的占位符原样保留;不把宿主自己的 AB_* 环境变量传给命令;README
  说明脚本从 AB_* 环境变量读取事件数据
- strm-writer:路径规范化后为媒体根目录本身(. / ..)时跳过,不再写出 STRM 目录之外的文件
- slack-notifier / onebot-notifier:带海报发送失败时去掉图片只发文字重试一次
- wpush-notifier / onebot-notifier:非 2xx 时报告 HTTP 状态,响应不是 JSON 时不再报解析异常
- 插件通知渠道的 extra 包含已填写的已声明字段(group、icon 等),不只是 model_extra
- 新增通知渠道时切换类型会清掉上一类型的字段,避免 Bark 带上残留的 Webhook URL
This commit is contained in:
Estrella Pan
2026-10-21 11:05:59 +02:00
parent 4b739bcfc3
commit 81bbf9c71d
17 changed files with 310 additions and 64 deletions

View File

@@ -36,13 +36,27 @@ def run(options: dict, event=ORGANIZED) -> None:
asyncio.run(plugin.on_event(event))
def test_on_event_organized_passes_env_and_placeholders_without_shell(tmp_path):
def test_on_event_organized_passes_env_and_placeholders_without_shell(
tmp_path, monkeypatch
):
# 宿主自己的 AB_* 凭据不传给命令
monkeypatch.setenv("AB_DOWNLOADER_PASSWORD", "s3cret")
out = tmp_path / "out.json"
command = [sys.executable, "-c", DUMP, str(out), "{torrent_hash}", "$HOME; {x}"]
command = [
*(sys.executable, "-c", DUMP, str(out)),
*("{torrent_hash}", "$HOME; {x}", "pre-{torrent_hash}", "{files}"),
]
run({"command": command})
got = json.loads(out.read_text())
# $HOME 与 ; 按字面传入:没有经过 shell;未知占位符原样保留
assert got["argv"] == ["abc", "$HOME; {x}"]
# $HOME 与 ; 按字面传入:没有经过 shell;未知占位符与夹在参数里的占位符原样保留;
# {files} 展开为每个文件一个参数
assert got["argv"] == [
"abc",
"$HOME; {x}",
"pre-{torrent_hash}",
"/dl/Show/Season 1/Show S01E01.mkv",
"/dl/Show/Season 1/Show S01E01.ass",
]
assert got["env"] == {
"AB_EVENT": "torrent.organized",
"AB_BANGUMI_ID": "",
@@ -85,6 +99,17 @@ def test_on_event_timeout_kills_command(caplog):
assert "已终止" in caplog.text
def test_on_event_background_child_does_not_delay_success(caplog):
# 后台进程继承了 stderr;命令本身已退出,不应等到后台进程结束
command = ["sh", "-c", "sleep 5 & exit 0"]
plugin, _ = create_plugin(RunCommand, {"command": command, "timeout": 2})
start = time.monotonic()
with caplog.at_level(logging.INFO):
asyncio.run(plugin.on_event(ORGANIZED))
assert time.monotonic() - start < 2
assert "已完成" in caplog.text and "已终止" not in caplog.text
def test_on_event_nonzero_exit_logs_code_and_stderr(caplog):
script = "import sys; sys.stderr.write('boom'); sys.exit(3)"
with caplog.at_level(logging.WARNING):