后端音视频前端【免费下载链接】navidrome Your Personal Streaming Service项目地址https://gitcode.com/gh_mirrors/na/navidrome点击查看免费下载导读Discord Rich Presence 插件Rust 实现 是 Navidrome 插件生态中的一份能力演示型示例当你在 Navidrome 中播放歌曲时它会通过 Discord Gateway 的 WebSocket 连接把正在播放的曲目、专辑封面、播放进度实时显示到 Discord 的 Rich Presence 状态栏中。本文以该插件的源码与文档为骨架讲解其配置方式、构建安装流程、三大能力Scrobbler、SchedulerCallback、WebSocketCallback的协作机制以及 Rust 版插件开发套件 nd-pdk 的典型调用方式帮助你理解 Navidrome 插件系统的跨语言设计与一次插件同时注册多个能力的实现套路。插件概览一个只读监听 实时外发的示例与 Navidrome 内置的 Last.fm / ListenBrainz 等回传播放记录型插件不同discord-rich-presence-rs 的定位是把播放状态实时推送到第三方聊天平台它需要与 Discord 的 Gateway 建立长连接并持续收发心跳。插件本身的源码目录包含文件职责src/lib.rs插件主体能力注册、配置读取、Scrobbler / SchedulerCallback / WebSocket 回调实现src/rpc.rsDiscord Gateway 通信连接管理、Identify、心跳、Presence 更新、封面图片处理manifest.json插件清单权限声明与 JSON Schema 配置定义Cargo.toml依赖声明nd-pdk、extism-pdk、serde与cdylib构建目标插件对外暴露的功能Feature如下均可在 src/lib.rs 的文档注释与 README 中找到对应在 Discord Rich Presence 中显示当前正在播放的曲目展示专辑封面大图资产通过 start/end 时间戳展示曲目播放进度曲目播放结束后自动清除 Presence支持多用户每个 Navidrome 用户对应一个 Discord Token。实现层面它一次性注册了六个 WASM 导出函数对应三类能力// src/lib.rs: 能力注册宏 nd_pdk::register_scrobbler!(DiscordPlugin); nd_pdk::register_scheduler_callback!(DiscordPlugin); nd_pdk::register_websocket_text_message!(DiscordPlugin); nd_pdk::register_websocket_binary_message!(DiscordPlugin); nd_pdk::register_websocket_error!(DiscordPlugin); nd_pdk::register_websocket_close!(DiscordPlugin);⚠️ 安全警告务必先读README 的警告段落明确说明该插件仅用于演示目的。它要求在 Navidrome 配置中明文存放 Discord Token这存在两个问题不安全Token 不应以明文形式存储可能违反 Discord 服务条款使用用户 Token 而非 Bot Token 接入 Gateway。因此使用风险自负。作为对照manifest.json 的 config 描述中同样以 WARNING: Store tokens securely!、keep this secret! 做了双重警示。能力设计一次实现三种能力协作Navidrome 的插件系统以能力Capability为单位插件在 manifest.json 中声明所需权限并通过 PDK 的注册宏向宿主Navidrome 进程导出对应函数。本插件实现了三种能力1. Scrobbler接收播放事件Scrobbler trait 要求实现四个方法本插件在 src/lib.rs 中全部实现is_authorized检查请求中的用户名是否出现在配置的users映射中未授权用户直接返回false代码路径 lib.rs L117-L129now_playing核心方法完成连接 Discord → 计算时间戳 → 发送 Presence → 调度清除定时器的完整链路scrobble/playback_reportDiscord Rich Presence 不需要这两个事件直接返回Ok(())空实现。now_playing的具体步骤lib.rs L131-L204读取配置并校验用户 Token缺失返回SCROBBLER_ERROR_NOT_AUTHORIZED其他失败返回SCROBBLER_ERROR_RETRY_LATER调用rpc::connect建立/复用 Discord Gateway 连接取消上一次遗留的清除定时器{username}-clear由当前系统时间减去req.position反推start_time加上track.duration得到end_time毫秒级时间戳组装Activity活动类型type: 2即 Listeningdetails 为曲名、state 为歌手、assets 为大图封面并发送调度一个一次性定时器在duration - position 5秒后清除 Presence。错误码常量定义在 scrobbler.rs 的 L25-L31SCROBBLER_ERROR_NOT_AUTHORIZED、SCROBBLER_ERROR_RETRY_LATER、SCROBBLER_ERROR_UNRECOVERABLE。2. SchedulerCallback心跳与活动清除CallbackProvider trait 只有一个on_callback方法宿主在定时器触发时回调插件。本插件用 payload 区分两类任务lib.rs L221-L251heartbeat对指定用户发送 Discord 心跳每 41 秒一次见下文。心跳失败说明连接已断此时清理连接状态并返回错误下一次now_playing会重新连接clear-activity曲目播放完毕清除 Presence 并断开用户连接schedule_id 形如{username}-clear通过trim_end_matches(-clear)还原用户名。SchedulerCallbackRequest 携带schedule_id本插件用其承载用户名、payload任务标识和is_recurring三个字段。3. WebSocketCallback与 Discord Gateway 双向通信插件通过 WebSocket 与 Discord Gateway 通信因此实现了 websocket 能力的四个回调lib.rs L257-L293on_text_message解析 Gateway 下发的 JSON。op: 10Hello无需处理连接时已提前 Identify、op: 11Heartbeat ACK忽略、op: 1服务端请求心跳则回复心跳同时维护s序列号rpc.rs L224-L255on_binary_messageDiscord 不发二进制消息空实现on_error/on_close连接异常或关闭时通过反向映射discord.reverse.{conn_id}找到对应用户并清理其全部状态取消心跳调度、删除缓存键。配置UI 表单与 JSON Schema配置入口是 Navidrome 的插件设置界面Settings → Plugins → discord-rich-presence-rs。README 中的配置表如下字段含义与 manifest.json 的 JSON Schema 完全对应Key描述示例clientid你的 Discord 应用 ID123456789012345678user.name指定 Navidrome 用户的 Discord Tokenuser.alicetoken123每个用户以user.前缀作为独立配置键。需要注意的是README 中user.name的平铺键写法是文档侧对多用户场景的通俗示意实际 manifest.json 的 Schemamanifest.json L29-L67把配置定义为结构化 JSONclientid字符串minLength: 17、maxLength: 20且正则^[0-9]$Discord 应用 ID 是 17~20 位纯数字users对象数组每个对象含usernameNavidrome 用户名与tokenDiscord Token两个必填字段minItems: 1两者均为顶层必填required: [clientid, users]。对应的解析逻辑在 src/lib.rs 的 get_configconfig::get(clientid)取客户端 IDconfig::get(users)拿到 JSON 字符串后用serde_json::from_str反序列化为VecUserToken跳过用户名或 Token 为空的条目组装成HashMapusername, token。uiSchemamanifest.json L68-L96定义了表单布局users数组以elementLabelProp: username展示每条记录内层用 HorizontalLayout 并排渲染用户名与 Token 输入框Navidrome UI 据此自动生成可用的配置表单。配置值的获取步骤Client ID在 Discord 开发者后台创建应用复制 Application IDDiscord Token需要从 Discord 客户端提取用户 Token出于安全原因不推荐与上文警告一致多用户在配置中添加多个用户键user.user1 token1 user.user2 token2若按 manifest 的结构化 schema 配置等价写法为把users设为一个 JSON 数组例如[{username:user1,token:token1},{username:user2,token:token2}]——这正是 get_config 期望解析的格式。构建从 Cargo.toml 到 .ndp 插件包构建命令README 的 Building 章节# 在 plugins/examples 目录下执行 make discord-rich-presence-rs.ndp # 产物 discord-rich-presence-rs.ndp 内含 # - manifest.json # - plugin.wasm这条命令背后是 plugins/examples/Makefile 的通用 Rust 构建规则Makefile 通过$(wildcard */Cargo.toml)自动发现所有 Rust 插件目录Makefile L9因此无需为每个插件单独维护构建目标先构建 WASMMakefile L96-L98 使用cargo build --release --target wasm32-wasip1编译目标为 WASI 而非纯 wasm32-unknown-unknown以支持文件系统等能力然后把产物从target/wasm32-wasip1/release/复制为{插件名}.wasm注意 Rust crate 名使用下划线discord_rich_presence_rs而插件目录名使用连字符discord-rich-presence-rs复制时通过$(subst -,_,$*)做名称转换再打包 .ndpMakefile L65-L69 用zip -j把manifest.json与plugin.wasm打成插件包.ndp本质是一个 zip 容器。构建依赖由 Cargo.toml 声明[lib] crate-type [cdylib] # 编译为 C 动态库形式供 extism 宿主加载 [dependencies] nd-pdk { path ../../pdk/rust/nd-pdk } # 本地路径依赖插件开发套件 extism-pdk 1.2 # Extism PDK 基础库 serde { version 1.0, features [derive] } serde_json 1.0nd-pdk crate 是对三个子 crate 的统一出口nd_pdk_host宿主服务调用封装、nd_pdk_capabilities能力 trait 与注册宏、nd_pdk_types共享数据类型并重导出extism_pdk方便使用。安装与启用按 README 的 Installation 章节 操作用上面的命令构建插件把.ndp文件复制到 Navidrome 的插件目录在 Navidrome UISettings → Plugins中启用并配置插件如需要重启 Navidrome。启用后权限声明manifest.json L7-L28会向宿主申请users代表用户处理 scrobble、http仅允许访问discord.com用于获取 Gateway 地址与上传封面、websocket仅允许连接gateway.discord.gg、cache存储连接状态与序列号、scheduler调度心跳与活动清除、artwork获取曲目封面 URL。requiredHosts白名单意味着插件只能与 Discord 相关域名通信这是 Navidrome 插件沙箱的权限约束体现。nd-pdk 库调用指南四种宿主服务的典型用法README 的 Using nd-pdk Library 章节给出了 nd-pdk 的四个典型调用示例源码中均有对应实现use nd_pdk::host::{artwork, cache, scheduler, websocket}; use std::collections::HashMap; // 获取曲目封面 URL let url artwork::get_track_url(track_id, 300)?; // 缓存操作 cache::set_string(key, value, 3600)?; if let Some(value) cache::get_string(key)? { // 使用缓存值 } // 调度任务 scheduler::schedule_one_time(60, payload, task-id)?; scheduler::schedule_recurring(every 30s, heartbeat, heartbeat-task)?; // WebSocket 操作 let conn_id websocket::connect(wss://example.com/socket, HashMap::new(), my-conn)?; websocket::send_text(conn_id, Hello)?;artwork封面 URL 与本地地址过滤插件通过artwork::get_track_url(track_id, 300)获取 300px 封面 URLlib.rs L96-L110。获取失败或 URL 以http://localhost开头即指向 Navidrome 本机、Discord 无法访问时返回空字符串随后会回退到默认图片。cache连接状态的持久化rpc.rs 以discord.前缀在宿主 KV 缓存中维护四类状态缓存键用途discord.connection.{username}用户名 → 连接 ID 映射discord.token.{username}用户 TokenTTL 86400sdiscord.sequence.{username}Gateway 消息序列号sdiscord.reverse.{conn_id}连接 ID → 用户名反向映射discord.image.{hash}已处理封面的mp:资产路径普通封面 TTL 4h默认封面 TTL 48hscheduler一次性与周期任务schedule_one_time(remaining_seconds, PAYLOAD_CLEAR_ACTIVITY, {username}-clear)曲目播完预留 5 秒余量后清除 Presenceschedule_recurring(every 41s, PAYLOAD_HEARTBEAT, username)Discord Gateway 要求的心跳间隔rpc.rs 常量HEARTBEAT_INTERVAL: i32 41schedule_id 直接用用户名承载。websocketGateway 连接生命周期websocket::connect(gateway, headers, username)建立连接后插件立即发送 Identify 帧op: 2intents 为 0properties 伪装为 Discord 桌面客户端见 rpc.rs L396-L440并通过websocket::send_text发送心跳op: 1、Presence 更新op: 3等消息断连时用websocket::close_connection(conn_id, 1000, reason)主动关闭。运行链路从 NowPlaying 到 Presence 更新综合 lib.rs 与 rpc.rs一次完整的播放状态展示按如下顺序发生Navidrome 用户开始播放 → 宿主调用插件导出的nd_scrobbler_now_playing传入NowPlayingRequest含用户名、TrackInfo、当前播放位置秒数结构定义见 scrobbler.rs L41-L53now_playing校验授权并调用rpc::connect若已有存活连接则复用否则清理旧状态、缓存 Token、通过 HTTP 请求https://discord.com/api/gateway获取 Gateway 地址rpc.rs L378-L394、建立 WebSocket、立即 Identify计算 start/end 时间戳毫秒组装 Activitytype: 2Listeningdetails曲名state歌手large_image封面、large_text专辑名封面 URL 通过process_image交给 Discordexternal-assetsAPI 处理成mp:资产路径rpc.rs L466-L548失败回退到内置默认图通过op: 3Presence 帧发送到 Gateway同时调度播放结束后清除的一次性定时器心跳每 41 秒由调度器触发曲目结束后clear-activity回调发送空 activities 的 Presence 帧并断开连接。从示例到实践你能带走什么这个示例插件虽然功能简单却是理解 Navidrome 插件系统的一张完整地图跨语言生态同一套能力协议manifest WASM 导出同时支持 Go、Python、Rust 三种 PDKplugins/examples/Makefile 中按go.mod/plugin/__init__.py/Cargo.toml自动发现三类插件Rust 实现展示了 nd-pdk 如何把宿主服务artwork、cache、scheduler、websocket、http、config封装成安全可调用的 API多能力注册一个插件类型通过六个register_*!宏同时导出 Scrobbler、SchedulerCallback、WebSocketCallback 三组函数on_callback用 payload 区分心跳与活动清除WebSocket 回调按连接 ID 回溯用户沙箱与权限requiredHosts白名单、JSON Schema 配置校验、错误码约定not_authorized / retry_later / unrecoverable都是开发真实插件时必须遵循的规范安全底线明文 Token 只适合本地演示生产级接入应改用 Bot Token、OAuth 或安全的凭据托管方案——这也是 README 反复强调的警告。该插件源码位于 plugins/examples/discord-rich-presence-rs/配套的 Rust PDK 实现在 plugins/pdk/rust/构建与打包规则见 plugins/examples/Makefile许可证为 GPL-3.0。赞分享后端音视频前端【免费下载链接】navidrome Your Personal Streaming Service项目地址https://gitcode.com/gh_mirrors/na/navidrome点击查看免费下载相关推荐Navidrome Rust 插件开发指南基于 nd-pdk 的 WASM 插件完整实战Navidrome Rust 插件开发指南基于 nd pdk 的 WASM 插件完整实战 Navidrome 是一个自托管的个人音乐流媒体服务其插件体系基于后端音视频前端Navidrome Rust 插件开发指南nd-pdk-host 主机服务封装详解Navidrome Rust 插件开发指南nd pdk host 主机服务封装详解 Navidrome 为 Rust 插件开发者提供了一组自动生成的主机服务后端音视频前端Navidrome 插件开发入门基于 minimal 示例与 Go PDK 编写 MetadataAgent 插件Navidrome 插件开发入门基于 minimal 示例与 Go PDK 编写 MetadataAgent 插件 本篇指南以 Navidrome 仓库中的后端音视频前端上一篇图片盲水印技术如何实现无痕版权保护与信息隐藏下一篇AutoHotkey与Node.js交互5种高效Web服务集成方案终极指南创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考