Files
2026-03-14 15:08:12 +08:00
..
2026-03-14 15:08:12 +08:00
2026-03-14 15:08:12 +08:00
2026-03-14 15:08:12 +08:00
2026-03-14 15:08:12 +08:00

插件系统v1实验性

Warning

插件是进程内动态库(dll / so / dylib),与宿主进程同权限运行
没有沙箱、权限隔离、签名校验,也没有网络或文件系统限制
插件还可以读取完整的宿主配置(包括 sessdata)

当前示例

  • 当前仓库的示例插件只有一个:basic-example
  • 位置:src-plugin/examples/basic-example
  • 用途:演示 Descriptor、Hook 处理、异步逻辑、读取宿主配置、返回修改后的 payload

快速构建示例

cd src-plugin/examples
cargo build --release

构建产物位于 src-plugin/examples/target/release/,文件名按平台分别是:

  • Windows: basic_example.dll
  • Linux: libbasic_example.so
  • macOS: libbasic_example.dylib

如何加载插件

  • 在应用设置页的插件面板点击“添加插件”,选择动态库文件
  • 后端要求路径必须是绝对路径,且文件必须存在
  • 成功后会写入 app_data_dir/plugin.json 持久化配置
  • plugin.json 保存字段:pathenabledprioritydescriptor

Descriptor插件内声明

PluginDescriptorV1 由插件代码返回,宿主不会直接修改:

  • sdk_api_versionSDK API 版本,当前必须等于 1
  • id:插件 ID
  • name:展示名称
  • version:插件版本
  • hooks:声明插件希望被调用的 Hook 点列表
  • failure_policy:失败策略,FailOpenFailClosed
  • description:插件描述

运行顺序与优先级

  • Hook 点相同的插件,宿主按 priority 从大到小执行
  • 每个 Hook 点按顺序串行执行,不是并行
  • 前一个插件返回后的修改,会成为后一个插件看到的输入

Hook 时机(以实际代码为准)

HookPoint 触发位置
AfterPrepare prepare() 成功之后,开始下载前
BeforeVideoProcess 视频任务和音频任务结束后,视频处理任务前
OnCompleted 所有任务结束后,completed_ts 已写入后

三个 Hook 都可读写 progress,但修改只有在当前 Hook 返回后才会被宿主应用。

输入输出协议

  • 输入:HookInputV1 { hook_point, payload, readonly_meta }
  • 输出:HookOutputV1 { payload }
  • payload 是枚举 HookPayloadV1,必须与 hook_point 匹配
  • 不匹配会被判定为插件输出无效,再按失败策略处理
  • readonly_meta 包含 app_versionosarchprocess_id

可修改范围与约束

  • payload.progress 大多数字段都可被插件修改
  • task_id 明确禁止修改,改动会被宿主拒绝
  • 宿主没有提供修改Config的 Host API

失败策略

  • FailOpen:插件出错时记录日志,继续执行后续流程
  • FailClosed:插件出错时中断当前下载流程并返回错误
  • 插件返回 Err(...) 时,宿主会调用 bilibili_video_downloader_plugin_last_error_v1 读取错误文本

Host API当前仅 v1

插件可在 on_hook 中调用:

  • host::get_config():读取宿主配置快照(HostConfigV1

注意:

  • 该配置是只读快照
  • 返回内容包含敏感信息(例如 sessdata

SDK 入口(插件侧)

  • 实现 traitPluginV1
  • 使用宏导出:export_plugin_v1!(YourPluginType)
  • 插件类型需要满足:Default + Send + 'static

常规最小结构:

use bilibili_video_downloader_plugin_sdk::{
    HookInputV1, HookOutputV1, PluginDescriptorV1, PluginV1, export_plugin_v1, eyre
};

#[derive(Default)]
struct MyPlugin;

impl PluginV1 for MyPlugin {
    fn descriptor(&self) -> PluginDescriptorV1 {
        unimplemented!()
    }

    fn on_hook(&mut self, input: HookInputV1) -> eyre::Result<HookOutputV1> {
        let _ = input;
        unimplemented!()
    }
}

export_plugin_v1!(MyPlugin);