🎁 福利专区全网大模型免费应用 + 新用户福利 + 注册活动入口,低成本玩转 AI

听歌伴侣 Soyoanon-mygo/dsh-music-companion

DeepSeek Harness
Soyoanon-mygo7 天前有提交已提供中文文档扫描:未发现敏感能力

一个能让dsh听懂你的歌单 并根据你的歌品向你主动推荐歌曲的dsh插件

36.1/100
综合分
3
GitHub 星标
—
npm 周下载
—/5
用户评分

数据截至 2026/10/29 · 周下载取自 npm 全网(含 CI / 镜像),非本站安装数;综合分由星标、活跃度与兼容性检查合成。

自动兼容检查未通过:缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装。以下命令仍可尝试,但可能失败。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add Soyoanon-mygo/dsh-music-companion
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

判定依据:实装验证 · 静态安装检查 · 自动扫描 · 仓库维护状态
信任档位:已验证
是什么
dsh 原生插件 · other
装得上吗
本站已在 dsh 0.2.1-rc.2 下真实安装成功
非静态推断
L5 · 运行时冒烟等级说明 →
安全吗
本站尚未对该插件做风险分级
暂未覆盖,不等同于无风险
还在维护吗
活跃:最近一次提交在 7 天前
档位由哪些信号合成

本站实装验证(真实安装 L4 覆盖全站;L5 为运行时冒烟或实机截图,覆盖范围小于 L4) · 验证所用 dsh 版本 · 静态安装检查 · 风险分级 · 仓库维护状态。 下方各区块是它的证据明细。

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查需源码安装

缺少 main/exports/bin 入口声明;仓库 package.json 标记 private,未发布到 npm,需从源码安装

展开查看检查明细 ↓

以下结论由程序自动检查 npm 包、engines 声明与入口文件得出,未做人工实机验证——能装不等于用着没问题。

✗npm 包dsh-music-companion(未发布到 npm,仅可源码安装)
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✗入口文件缺少入口声明

源码安装时若 pnpm 拦截 prepare 脚本,按其提示在 pnpm-workspace.yaml 的 allowBuilds 中放行后重跑。

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/11/4 06:37:22

安全扫描自动 · 静态分析源码
v2.7.0
🟢 扫描:未发现敏感能力尚无人工评估
展开查看扫描说明与证据 ↓

静态扫描未发现敏感能力:最多只发网络请求、读取自身元数据或只读文件,未发现执行命令、文件写入或敏感信息读取。

发起外部网络请求
查看扫描证据(共 7 处,展示前 7 条)
  • 发起网络请求
    packages/mcp-music/src/server.mjs:29
    const res = await fetch(`${HUB}${pathAndQuery}`, {
  • 发起网络请求
    packages/mcp-music/src/server.mjs:40
    const res = await fetch(`${HUB}${pathname}`, {
  • 发起网络请求
    packages/music-hub/src/notify.mjs:170
    const res = await fetch(target.url, {
  • 发起网络请求
    packages/watcher/lib/notify.mjs:170
    const res = await fetch(target.url, {
  • 发起网络请求
    packages/watcher/lib/ticker.mjs:120
    const res = await fetch(`${HUB_URL}/v1/now-playing`, {
  • 发起网络请求
    packages/watcher/lib/tools.mjs:23
    const res = await fetch(`${HUB}${pathAndQuery}`, {
  • 发起网络请求
    packages/watcher/lib/tools.mjs:31
    const res = await fetch(`${HUB}${pathname}`, {
扫描于 2026/11/3(0 天前) · 范围 全包扫描 · 来源 GitHub 仓库

静态扫描只反映源码中出现的调用,不构成安全背书;人工评估与自动扫描相互独立、互不覆盖。

给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
dsh-music-companion 让你的 DSH 知道你在听什么,并且听得懂。 一个很小的扩展:桌面端读出系统媒体会话里正在播放的歌,经一个极简 hub 传给远端智能体。于是她能在聊天里自然接一句「你在听《春日影》?」,也能慢慢攒出你的听歌口味,哪天主动推一首过来。(本项目大部分代码由 dsh 生成,可能存在一些未知 bug,欢迎提交修改) 目前的更新状态:v0.3 更新完成,暂未上架 dsh 插件市场 它长什么样 下面两段是真实对话(已脱敏)。 她读得懂你在听什么 从今天的记录里看:你的主线是日系乐队那种"动画片尾曲感"——ずっと真夜中でいいのに、ヨルシカ、MyGO、トゲナシトゲアリ,术力口那一块也不少。 但你又很吃杂食:从白光的老上海跳到巴西 funk、phonk,中间夹一笔说唱,还听了一段《觉醒年代》配乐。 晚上你偏抒情:悔やむと書いてミライ、影色舞、綺麗事这一串,情绪挺沉。白天赶工就挑有鼓点的。 还有一个发现,你一天扫了六十来首,很多听了 20 秒就切——你不是在听,你是在挑。 她读到的是 music_history:歌名、歌手、以及每首听了多久。"20 秒就切"和"从头听到尾"在她眼里是两种完全不同的事。 她会自己开口 (你切到《アシェンプテル》,暂停在中段)还在放《アシェンプテル》,暂停在中段。是路上听了一半就下车了,还是切出去不听了?
展开完整文档(README 剩余 1 节)
dsh-music-companion

让你的 DSH 知道你在听什么,并且听得懂。

一个很小的扩展:桌面端读出系统媒体会话里正在播放的歌,经一个极简 hub 传给远端智能体。于是她能在聊天里自然接一句「你在听《春日影》?」,也能慢慢攒出你的听歌口味,哪天主动推一首过来。(本项目大部分代码由 dsh 生成,可能存在一些未知 bug,欢迎提交修改)

目前的更新状态:v0.3 更新完成,暂未上架 dsh 插件市场

它长什么样

下面两段是真实对话(已脱敏)。

她读得懂你在听什么

从今天的记录里看:你的主线是日系乐队那种"动画片尾曲感"——ずっと真夜中でいいのに、ヨルシカ、MyGO、トゲナシトゲアリ,术力口那一块也不少。
但你又很吃杂食:从白光的老上海跳到巴西 funk、phonk,中间夹一笔说唱,还听了一段《觉醒年代》配乐。
晚上你偏抒情:悔やむと書いてミライ、影色舞、綺麗事这一串,情绪挺沉。白天赶工就挑有鼓点的。
还有一个发现,你一天扫了六十来首,很多听了 20 秒就切——你不是在听,你是在挑。

她读到的是 music_history:歌名、歌手、以及每首听了多久。"20 秒就切"和"从头听到尾"在她眼里是两种完全不同的事。

她会自己开口

(你切到《アシェンプテル》,暂停在中段)还在放《アシェンプテル》,暂停在中段。是路上听了一半就下车了,还是切出去不听了?到家了吗?

这次不是你去问她。behavior.onSongChange 决定她能不能开口,quietHours 和 minGapSeconds 决定她多久才说一次——免得变成每换一首歌就播报的复读机。

它不做什么

先划清边界,省得误会:

- 不是播放器。 在 DSH 里放歌这件事已经有人做得很好了(见相关项目)。
- 不上传音频。 只传几行元数据,没有音频流。
- 不碰你的账号凭证。 不读 cookie、不登录、不调网易云的私有接口。歌曲 ID 是从客户端自己的播放队列文件里读的,纯粹是本地文件。
- 不保证 7×24 采集。 插件形态下,只有 DSH 进程活着 的时候才读得到系统媒体会话——DSH 退出(托盘图标右键「退出」)或电脑睡眠期间什么都不记。想做到「只要开机就在听」,要么给 DSH 开开机自启,要么用独立进程方式跑(见快速开始里的方式 B)。两条路的数据格式完全一样,同时跑也不冲突。

它做什么

| 能力 | 版本 |
| 知道你此刻在听什么、有没有在听、听到第几秒 | 现版本 |
| 记住你最近听过哪些歌、每首听了多久 | 现版本 |
| 拿到歌曲 ID、时长、来源(音乐 / B站视频 / 其他) | 现版本 |
| 智能体主动把歌推进你的推荐队列 | 现版本 |
| 换歌时自己决定要不要搭话(而不是每首都播报) | 现版本 |
| 记住口味,推歌之后知道有没有被听 | 现版本 |
| 打包成 DSH host 插件 | 现版本 |
| 上架 DSH 插件市场 | v0.4 |
| 让 dsh 能登上网易云 开启一起听 | v0.4 |

架构

三层,中间只有一个 HTTP 边界。任何一层挂了都不影响聊天——音乐是附加层,不是依赖。

┌───────────────────────────┐
│ 你的电脑 │ │ │ │ 网易云 / Spotify / B站 │ │ ↓ 系统媒体会话(SMTC) │ │ watcher ── 每 20s 读一次 │ │ │ + 本地队列查 song id│ └──────┼────────────────────┘ │ POST /v1/now-playing │ (每轮都发,带进度) ▼ ┌───────────────────────────┐ │ music-hub(一台小服务器) │ │ 收 · 存 · 给 │ │ 纯 Node,零依赖,无数据库 │ └──────┬────────────────────┘ │ 127.0.0.1 ▼ ┌───────────────────────────┐ │ mcp-music │ │ 三个工具:在听什么 / 最近听 │ │ / 推首歌 │ └──────┬────────────────────┘ ▼ DSH agent

为什么用系统媒体会话

Windows 10 起有个系统级接口 Windows.Media.Control,任务栏那个播放小条就是它。它不碰任何播放器的内部结构,换网易云、Spotify、foobar2000 都能读到,也不会随客户端版本失效。

代价是网易云不上报播放位置(Position 永远是 0)。所以它的进度是 watcher 自己累加推断的:换歌从 0 开始,按前一次采样的播放状态累加,暂停期间不计时。支持播放位置的播放器优先使用 SMTC 报告的位置。

听歌时长和播放位置分开记录。 拖到第 3 分钟不代表听了 3 分钟;listenedSeconds 只累计实际采样到的播放时间。播放器消失、来源被过滤、SMTC 读取失败或采样间隔超过两轮时,不补算中间的空白时间。轮询仍有边界误差,网易云的估算进度也无法跟随拖动进度条。

为什么读本地队列拿 song id

智能体想查歌词、拉热评、找相似歌,都得先有数字 id,而 SMTC 只给歌名和歌手。按名字去搜索接口反查,同名歌一撞就废。

网易云客户端自己会在 %LOCALAPPDATA%\Netease\CloudMusic\webdata\file\playingList 里写一份播放队列,带精确 id 和时长,是明文 JSON。读它比调接口更准、更快、也更不容易被风控。

匹配时有个坑:SMTC 常常只报主歌手(「指错」),本地队列里却是全部歌手(「指错/星尘」)。所以匹配是分级的——先找歌手集合完全一致的,再找歌手互相包含的,最后如果歌名在队列里唯一就直接用。

快速开始

需要 Node.js ≥ 22。只能在 Windows 上用(读的是系统媒体会话)。

方式 A:装成 DSH 插件(推荐,一步搞定)

dsh plugin --profile <你的 profile> add /absolute/path/to/packages/watcher
装完重启一次 DSH——host 插件只在启动时加载,装着不重启是跑不起来的。

插件自己做三件事:

1. 起一个本地 hub(127.0.0.1:8787,数据在 ~/.dsh-music-companion/data/)
2. 每 20 秒读一次系统媒体会话,上报给 hub
3. 把四个工具注册进智能体(music_now_playing / music_history / music_recommend / music_taste)

重启后直接问她「我在听什么」,不用另起进程、也不用配 MCP。

采集只在 DSH 进程活着时进行。 DSH 退出(托盘图标右键「退出」)或电脑睡眠期间不记录。要真正「开机就在听」,给 DSH 开开机自启即可。

想接自己的服务器? 在 config.json 里把 hub.url 指向它——只要不是 127.0.0.1 / localhost,插件就会跳过内置 hub,直接往那边报(跨机器场景见方式 B)。

方式 B:手动部署(跨机器 / 调试用)

三件套分开跑,适合 watcher 和智能体不在同一台机器的情况。

1. 起 hub

MUSIC_HUB_TOKEN=$(openssl rand -hex 24) node packages/music-hub/src/hub.mjs

默认监听 127.0.0.1:8787。数据落在 packages/music-hub/data/,纯 JSON,出问题 cat 一下就能明白。

要跨机器用,把 MUSIC_HUB_HOST 设成 0.0.0.0,并且务必设一个足够长的 MUSIC_HUB_TOKEN——/v1/ 全部接口都要鉴权。

2. 起 watcher

MUSIC_HUB_URL=http://127.0.0.1:8787 \
MUSIC_HUB_TOKEN=<上面那个> \
node packages/watcher/watcher.mjs

加 --once 只读一次就退出,适合确认到底读到了什么;平时调试不重启 DSH 就用这个方式。

3. 挂 mcp-music

npm install --prefix packages/mcp-music

然后在 DSH 的 MCP 配置里加一条:

mcp-servers:
mcp-music:
command: node
args: ["/absolute/path/to/packages/mcp-music/src/server.mjs"]
env:
MUSIC_HUB_URL: "http://127.0.0.1:8787"
MUSIC_HUB_TOKEN: "<同一个 token>"

现在问她「我在听什么」,她会去查。

每 20 秒读一次,每轮都上报(进度在变),但相同的歌只会写一次历史。

配置与调参

三个组件共用一份 config.json,改一个地方三个都读到。

文件放哪

按顺序找,取第一个存在的:

1. $MUSIC_CONFIG 指定的路径(临时切换配置、调试用)
2. ./config.json(在仓库根目录跑时最方便)
3. $XDG_CONFIG_HOME/dsh-music-companion/config.json
4. ~/.config/dsh-music-companion/config.json
5. %APPDATA%\dsh-music-companion\config.json(Windows)

仓库根目录的 config.example.json 复制成 config.json 就能用,后者已被 .gitignore 排除。

优先级:环境变量 > config.json > 内置默认值。 环境变量永远能临时压过文件,试参数时不用改文件。

完整参数

| 键 | 环境变量 | 默认 | 说明 |
| hub.url | MUSIC_HUB_URL | http://127.0.0.1:8787 | hub 地址,watcher 与 mcp 都读 |
| hub.token | MUSIC_HUB_TOKEN | 空 | /v1/ 全接口的鉴权密钥 |
| hub.host | MUSIC_HUB_HOST | 127.0.0.1 | hub 绑定地址,跨机要改成 0.0.0.0 |
| hub.port | MUSIC_HUB_PORT | 8787 | hub 端口 |
| hub.dataDir | MUSIC_HUB_DATA | packages/music-hub/data | 数据落盘目录 |
| hub.staleSeconds | MUSIC_HUB_STALE_SECONDS | 600 | 多久没更新就标 stale: true |
| watcher.pollSeconds | MUSIC_POLL_SECONDS | 20 | 多久读一次系统媒体会话 |
| watcher.device | MUSIC_DEVICE | desktop-windows | 设备标识,多机时区分 |
| watcher.ignoreSources | — | [] | 不当作"在听歌"的来源 |

过滤不想被记录的来源

watcher.ignoreSources 收 netease / bilibili / other 三个值。被过滤的来源 watcher 根本不会上报,hub 和智能体都看不到——看网课不该进听歌记录。

"watcher": { "ignoreSources": ["bilibili"] }

主动开口

behavior 段决定「她什么时候该主动说话」。换歌时 hub 按策略决定要不要叫醒智能体,叫醒之后说不说由智能体自己判断——不想说就当没看见。

| 键 | 说明 | 默认 |
| onSongChange | always 每首都接话 / ask 看她心情 / silent 从不主动 | ask |
| minGapSeconds | 两次主动开口之间的最小间隔,防刷屏 | 180 |
| quietHours | 这段时间内绝不主动说话,支持跨零点 | ["23:30","07:00"] |
| ignoreSource | 这些来源的变化不触发发言 | ["bilibili"] |
| notify.kind | none 只记录不打扰 / qq-bridge 通过 QQ 桥接叫醒 / webhook 通用 | none |
| notify.url | qq-bridge 模式下的地址,如 http://127.0.0.1:3100 | — |
| notify.consoleToken | 桥接控制台令牌 | — |
| notify.key | 会话标识,如 private:1234567 | — |
| notify.message | 叫醒时的提示词,支持 {title} {artists} {album} | 见 config.example.json |

为什么只叫醒、不直接把歌名塞进对话:把歌名塞给它,它就只会复述歌名;只告诉它「有变化了」,它才会自己决定要不要看、要不要开口。真人也不是每首歌都要点评一句。

一个必须知道的细节:qq-bridge 的 notify-ai 触发的是 silent 回合——那一轮智能体的文字输出不会自动发到 QQ,它必须显式调用发送工具(例如 qq_send_private_message)才发得出去。这是设计使然(避免打扰),不是 bug。

调试

看 watcher 到底读到了什么:

node packages\watcher\watcher.mjs --once

启动时会打三行:配置来源、读到的本地队列条数、这一轮的歌。链路通不通一眼就看得出来。

确认 hub 收到没有:

curl -sS -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8787/v1/now-playing

看 hub 日志:

tail -f /var/log/music-hub.log

塞一条假数据试场景(注意 track 这一层不能省,直接发 {"title":...} 会返回 track.title is required):

curl -sS -X POST -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/json" \
-d '{"track":{"title":"晴天","artists":["周杰伦"],"duration":269},"progress":30,"playing":true}' \
http://127.0.0.1:8787/v1/now-playing

Windows 上别在 PowerShell 里硬拼 JSON 引号,写成文件再 --data-binary @payload.json 省事得多。

工具

| 工具 | 说明 |
| music_now_playing | 现在在听什么:歌名、歌手、专辑、ID、时长、进度、累计听歌时间、来源 |
| music_history(limit) | 最近听过的歌,倒序,附「上一首听了多久」 |
| music_recommend(title, artist, reason?, id?) | 把一首歌放进推荐队列,等桌面端拉取 |
| music_taste(...) | 读/写她对主人口味的了解。不带参数是读,带参数是整体覆盖写 |

前两个只读,后两个有副作用。

推荐闭环:music_recommend 写进去的是 status: "pending"。等你真的听了那首歌(watcher 上报的歌名/歌手能对上),hub 会自动把它标成 listened 并记下听到的时刻——这样她下次就知道自己的推荐准不准。

口味怎么来的:music_taste 是一个空槽,插件不替你总结。她可以自己从 music_history 里看出「这个人连续听了三首回春丹」,然后调 music_taste 写下来:歌手亲密度(-5..5)、风格标签、几句笔记。下次再推歌前先读一次,就不会推你已经明确不喜欢的东西。

数据长什么样

hub 存的 now-playing.json:

{
"track": {
"title": "陪葬夏天的健忘录",
"artists": ["指错"],
"album": "陪葬夏天的健忘录", "id": "2757490399", "duration": 218 }, "progress": 51, "playing": true, "listenedSeconds": 51, "device": "desktop-windows", "source": "netease", "startedAt": "2026-10-26T10:12:45.821Z", "updatedAt": "2026-10-26T10:13:36.317Z", "stale": false, "ageSeconds": 0 }

当前状态的 listenedSeconds 是当前歌曲累计听了多久;历史中一条记录的同名字段仍表示「换到这首歌之前,上一首累计听了多久」,第一条为 null。暂停/继续不会重复追加历史,暂停时选中的歌要等真正播放才入历史。配合上一首的 duration 就能判断你是听完了还是跳过:

- 听了 40s / 218s ≈ 18% → 跳过
- 听了 195s / 218s ≈ 89% → 听完

计数会随当前状态落盘,hub 重启后继续累计。旧 watcher 没有上报听歌计数时,hub 按播放状态和有效上报间隔估算,超过 hub.staleSeconds 的空白不计入。已有历史不改写;旧版当前状态缺少计数时从升级后的有效采样开始统计,不用墙上时间回推。

运行测试

仓库根目录执行,无需安装额外依赖,也无需实际打开播放器:

npm test
npm run test:smoke

回归测试覆盖插件与独立 hub 的 HTTP 行为、暂停/恢复、切歌、拖动进度、采集间断、重启恢复和连接配置。测试使用临时目录与临时端口,不读取个人配置或真实 SMTC 媒体数据。CI 在 Windows / Linux 的 Node.js 22 和 24 上运行。

设计取舍

- 状态用 JSON 文件,不用数据库。 单用户、写频率不到 0.1Hz、读全在本机。文件的好处是出问题能直接看,智能体也能直接读。
- 同一首歌反复暂停不重复记。 幂等键是 歌名|歌手,一首歌听一下午只算听了一次。
- 进度是自己算的,不是读的。 网易云不报位置,只能靠累加计时。这是已知的精度损失,明写在数据里,不假装准。
- hub 不主动把歌名塞进对话。 v0.2 会改成「只告诉智能体有变化」,由她自己决定接不接话——真人也不是每首歌都要评论。
- 来源单独一个字段。 SMTC 会把 B 站视频也算成「正在播放」,所以 source 用来区分 netease / bilibili / other,免得听歌记录里混进一堆网课。

相关项目

这几个都是「在 DSH 里放歌」,和我们方向不同,但值得一看:

- doitian/dsh-music — 带 AI DJ 的网易云播放器
- ZhaoZiyangShaanxi/dsh-netease-music — 读取本地客户端实现自动登录
- Caiccc-a11y/dsh-wyymusic-player — 右侧栏播放器

许可

MIT
预览分享卡片长什么样 ↓Soyoanon-mygo/dsh-music-companion
分享 · DeepSeek Harness Hub
Soyoanon-mygo/dsh-music-companion

一个能让dsh听懂你的歌单 并根据你的歌品向你主动推荐歌曲的dsh插件

这是你的仓库?认领条目

回到顶部