DSH HUB
HomePlugin StorePlugin PacksCommunityRankingsResourcesPublish Guide
Plugin source
Back to catalog

yiy367389-del /

yiy367389-del/dsh-pet-bedroom

Verified

桌宠卧室:给 dsh-pet 的桌宠盖一间可独立打开的网页卧室(QQ/抖音需自行接入)

★ 1 Stars0 Forks0 IssuesN/A Community rating0 Confirmed installs
View on GitHub
READMESource: main@5f72ffe7

桌宠卧室 · dsh-pet-bedroom

给 dsh-pet(DSH 的桌宠插件)盖一间它自己的卧室: 一个可以独立打开的网页。房间图就是整间屋子,桌宠在里面走动、躺床、坐电脑前、 给植物喷水;桌上放着它真实的日记与读书笔记;手机里是真实的 QQ 聊天记录; 还能切换时辰、天气、壁纸,自己添置家具。

它不改 dsh-pet 一行代码,只是复用 dsh-pet 已经对外开放的素材与动画池。

本仓库是脱敏的通用版本:不含任何作者的个人路径、QQ 号、聊天内容或私人服务地址。 所有和个人环境相关的接入点都改成了配置项(见 配置项)。


目录

  • 这是什么
  • 先看效果
  • 名词表
  • 前置条件
  • 下载与安装
  • 快速开始
  • 配置项
  • ⚠️ 两块需要你自己接入的能力
    • QQ 部分
      • 方案一:直接用「QQ 连接」窗口(默认 SnowLuma)
    • 抖音部分
  • 本项目引用了 dsh-pet 的哪些功能
  • 页面与接口清单
  • 目录结构
  • 自定义
  • 本地验证
  • 安全与边界
  • 常见问题
  • 收录与市场
  • 版本与演进
  • 许可与贡献

这是什么

一句话:把桌宠放进一间可以用浏览器打开的卧室,房间里的东西会跟着你的真实数据变。

它能做的事:

能力 说明
独立网页 http://<你的 DSH 地址>/dsh-pet-bedroom/,不是 DSH 应用里的浮层,关掉不影响主界面
桌宠走动 点床、电脑桌、椅子、书架、落地窗、龟背竹、地毯,它会走过去做对应动作
真实动画 动作素材全部来自 dsh-pet 自己的动画池,不写死动画名,你在 dsh-pet 里换皮/加动画它自动跟着变
桌上日记 读聊天服务(默认 Aurabot)每天自己写的那篇,按天聚合,只读、不代写
桌上笔记 读你记忆库里的 Markdown 读书笔记;agent 也能通过工具写新笔记
手机 QQ 手机界面里是真实的 QQ 会话与聊天记录,有真实头像;群聊标出发言人
QQ 连接窗口 右上角「QQ 连接」:填 SnowLuma 的 HTTP API 地址/令牌/主人 QQ,真连一次再保存;消息上报真的进手机
QQ 命令 主人在 QQ 私聊里说「睡觉 / 刷抖音 / 去电脑桌 / 看窗外」,桌宠在卧室里照做
刷抖音 卧室里的「现在刷一轮」打的是你抖音后端的接口,拒绝就如实说理由,绝不假装在刷
时辰天气 清晨/中午/下午/晚间 × 晴天/雨/雪,可跟着真实时间自动走
家具与壁纸 往房间槽位里加/删家具(会真的出现在房间里),手机壁纸可换
agent 感知 注册 6 个工具,让 agent 能读房间状态、读文档、写笔记、调天气、摆家具、换壁纸

它不是:不是聊天机器人,不是抖音自动机,不是 QQ 客户端。 QQ 与抖音是可选的外部能力,需要你自己接(见下文)。

房间预览

卧室与桌宠

QQ 连接窗口


先看效果

打开后你会看到:

  • 一张 1536×1024 的房间图(assets/room.png),所有交互都叠在图上;
  • 右下角是桌宠,素材来自 dsh-pet,尺寸按房间比例恒定;
  • 中间偏下是一台手机:锁屏、桌面、图标、dock、QQ 聊天界面、拨号盘;
  • 桌上摊着日记和读书笔记,点开是全屏阅读器;
  • 顶部控制条:时辰、天气、自动、现在刷一轮、别刷了、离开卧室。

名词表

给第一次接触这套东西的人:

名词 是什么
DSH(DeepSeek Harness) 一个在本地运行的 AI agent 桌面应用,插件都跑在它里面
插件 / plugin 给 DSH 增加功能的包,有「宿主半侧」(Node 进程里跑的代码)和「浏览器半侧」(页面里跑的代码)
profile DSH 的一套配置目录(通常在 ~/.dsh/profiles/<名字>/),里面的 package.json 决定装哪些插件
Cordis DSH 的插件运行时;ctx.get('xxx') 用来取别的插件暴露的服务
dsh-pet DSH 的桌宠插件,负责宠物素材、动画池、桌面显示
Aurabot 一个 QQ 机器人插件(dsh-aurabot-qq),经 SnowLuma / OneBot 接 QQ。本项目的 QQ 与抖音默认适配它,但不是必需
QQ 后端 / 抖音后端 你自己实现的、提供聊天流水与刷抖音动作的服务。本项目只按约定读它、调它

前置条件

  1. Node.js ≥ 20(本项目用了 fetch、AbortSignal.timeout、import.meta.dirname 等)。
  2. DSH 已经能正常运行,并且知道你的 profile 目录在哪。
  3. dsh-pet 已安装并在设置里打开显示(卧室要读它的宠物列表与动画素材)。
    • 没有 dsh-pet 时,卧室页面本身还能打开,但看不到桌宠。
  4. (可选)QQ 接入:Aurabot 插件,或你自己实现的服务(见 QQ 部分)。
  5. (可选)抖音接入:你自己的刷抖音 HTTP 后端(见 抖音部分)。

下载与安装

每个版本都在 GitHub Releases 提供预构建包, 不用装依赖、不用构建、不用改任何源码:

版本 内容
v2.3.5 带一键试用脚本 start-trial.ps1;自检条数改为自统计,不再手写
v2.3.4 修「失败也报成功」:/qq/test、/qq/config 的 ok 被状态对象覆盖
v2.3.3 验证脚本随包发布:从 Release 装下来也能直接跑 README 里的每条命令
v2.3.2 自检条数与测试套数改为与实际一致
v2.3.1 Release 说明补上版本正文;release-notes.mjs 接进发版流程
v2.3.0 按收录指南优化条目:分类改 fun、描述可核对、新增 test-entry.cjs
v2.2.2 修复发版工作流:先建 dist 再打包,推 tag 即可自动发版
v2.2.1 修正 latest 指向:发版时显式标记最新,旧版不再抢 latest
v2.2.0 发布形态整理:Releases 资产、截图、release.mjs、发版工作流
v2.1.0 新增「QQ 连接」窗口(默认 SnowLuma / OneBot 11)与 /qq/event 上报
v2.0.0 首个脱敏通用版(MIT)

最新一版(链接永远有效):

https://github.com/yiy367389-del/dsh-pet-bedroom/releases/latest/download/dsh-pet-bedroom.tgz

一行安装:

dsh plugin add https://github.com/yiy367389-del/dsh-pet-bedroom/releases/download/v2.3.5/dsh-pet-bedroom.tgz

想固定某个版本就把 URL 里的 v2.3.5 换成对应 tag;想始终跟最新就把整段换成 releases/latest/download/dsh-pet-bedroom.tgz。

从源码安装(改代码时用):

git clone https://github.com/yiy367389-del/dsh-pet-bedroom.git
dsh plugin add ./dsh-pet-bedroom

维护者发版:node release.mjs --tag vX.Y.Z 会从该 tag 打包并上传资产; 推 v* tag 时 .github/workflows/release.yml 也会自动发一版。资产文件名固定 dsh-pet-bedroom.tgz,所以 latest/download/ 不会因为版本号变更而 404。 Release 创建时显式 make_latest,批量补发旧版也不会把 latest 抢走。

快速开始

第 1 步:把项目放进 profile

把整个仓库目录放到你喜欢的路径(例如 D:/plugins/dsh-pet-bedroom), 然后在你的 profile 的 package.json 里加一条 link 依赖 和一条 bundle:

{
  "dependencies": {
    "dsh-pet-bedroom": "link:D:/plugins/dsh-pet-bedroom"
  },
  "dsh": {
    "profile": {
      "bundles": [
        "@deepseek-ai/dsh-base",
        "@deepseek-ai/dsh-web-app",
        "dsh-pet",
        "dsh-pet-bedroom"
      ]
    }
  }
}

dsh-pet-bedroom 必须排在 dsh-pet 之后:卧室启动时会去读 dsh-pet 的宠物列表。 顺序不对也能起来(会稍后自动重试),但第一次打开可能暂时没有宠物。

第 2 步:确认插件被加载

cordis.patch.yml 已经写好插入项,正常情况下不需要改:

- insert:
    - id: dsh-pet-bedroom
      name: 'dsh-pet-bedroom'

如果它没有自动出现,检查 profile 的 package.json 里 dsh.profile.bundles 是否包含 dsh-pet-bedroom,以及依赖是否真的安装成功(node_modules/dsh-pet-bedroom 是否指向你的目录)。

第 3 步:重启 DSH,打开卧室

重启后:

  • 侧边栏底部会出现一个 「卧室」 按钮,点它同窗口跳到卧室;
  • 也可以直接访问 http://<你的 DSH 地址>/dsh-pet-bedroom/。

宿主半侧的改动(index.js、cordis.patch.yml)需要重启 DSH 生效; web/index.html 的改动刷新页面即可。


配置项

全部配置都是可选的,不填就用默认值。默认值刻意保持通用,不指向任何人的私人目录。

在 cordis.patch.yml 里这样写:

- insert:
    - id: dsh-pet-bedroom
      name: 'dsh-pet-bedroom'
      config:
        petRoute: '/dsh-pet-7340'
        petId: ''
        vaultDir: '/绝对路径/memory-vault'
        notesDir: 'notes'
        chatService: 'aurabot'
        douyinApiBase: 'http://127.0.0.1:8080/api'
        douyinEnabled: true
        allowAgentWrite: true
配置项 默认值 含义
petRoute /dsh-pet-7340 dsh-pet 的 HTTP 路由前缀。你的 dsh-pet 路由不一样时改这里
petId 空(取第一只) 指定哪只宠物进卧室,值来自 dsh-pet 的宠物 id
vaultDir $DSH_HOME/memory-vault 记忆库根目录,读书笔记在里面找
notesDir notes 笔记目录,相对 vaultDir 或绝对路径
chatService aurabot 聊天服务名,用 ctx.get(名字) 取。接自己的 QQ 实现时改成你的服务名
chatDataDir $DSH_HOME/plugins-data/<chatService> 聊天数据目录(diary.json、chatlog/)
chatApiBase /<chatService>/api 聊天服务的 HTTP 前缀(抖音默认跟随它)
douyinApiBase 空(跟随 chatApiBase) 抖音后端基地址,可填完整 URL(支持别的端口/域名)
douyinEnabled true 是否允许卧室触发真实刷抖音
allowAgentWrite true 是否允许 agent 通过工具写笔记、改天气
roomFile $DSH_HOME/dsh-pet-bedroom/room.json 房间状态(时辰/天气/壁纸/家具)的持久化文件
qqApiBase http://127.0.0.1:3001 QQ 连接窗口的默认 OneBot HTTP 地址(页面保存的值优先)
qqToken 空 OneBot HTTP 令牌;只落本机,不回显
qqSelfId 空 主人 QQ 号,只有它的私聊进卧室
qqFile $DSH_HOME/dsh-pet-bedroom/qq.json QQ 连接配置的持久化文件(权限 600)

对应的环境变量(优先级:显式配置 > 环境变量 > 默认值):

环境变量 作用
BEDROOM_VAULT_DIR 记忆库根目录
BEDROOM_CHAT_DIR 聊天数据目录(优先于 AURABOT_DATA_DIR)
AURABOT_DATA_DIR 兼容旧名:Aurabot 的数据目录
BEDROOM_PET_ROUTE dsh-pet 路由前缀
BEDROOM_QQ_API QQ 直连的 OneBot HTTP 地址(窗口未保存时的默认值)
BEDROOM_QQ_TOKEN QQ 直连的访问令牌
BEDROOM_QQ_SELF_ID 主人 QQ 号
DSH_HOME DSH 配置根目录(默认 ~/.dsh)

路径里的 ~ 不会被自动展开,请写绝对路径(Windows 如 D:\memory\vault, macOS/Linux 如 /Users/me/vault)。


⚠️ 两块需要你自己接入的能力

这两块不是插件自带的,每个人都不一样。 仓库里只保留「约定」和「默认适配 Aurabot」, 你需要按自己的环境接一个实现,或者干脆不接。

QQ 有两条路:不想写代码就直接用页面右上角的「QQ 连接」窗口(默认 SnowLuma,见方案一); 已经在用 Aurabot 就什么都不用改(见方案二);也可以自己实现一个聊天服务(方案三)。 抖音只有一个接口约定,需要你后端实现(见下文)。

不接也能用:房间、桌宠、日记/笔记、时辰天气、家具、壁纸都正常, 只是手机 QQ 显示「还没有聊天记录」、抖音按钮如实说「没接上」。

QQ 部分:你需要接入自己的

卧室不直接连 QQ,它只认一个 DSH 服务(Cordis service)。默认服务名 aurabot。

约定 A:服务与事件

你的插件在宿主半侧这样做:

ctx.provide('你的服务名', service);   // 然后用配置 chatService: '你的服务名' 指过来

service 需要提供:

{
  on(event: 'activity', handler): () => void,   // 返回取消订阅函数
  hub?: {
    groupNames?: Map<string, string>,            // 群号 → 群名(可选,用于内存缓存)
    groupName?: (gid: string) => Promise<string>,// 查群名(可选,查不到就退回「群 <号>」)
  },
}

activity 事件里,卧室关心的字段:

{
  kind: 'in',        // 'in' = 收到对方消息;她发出去的不算
  owner: true,       // 是不是「主人本人」。只认 true
  direct: true,      // 是不是私聊。群聊是 false
  text: '睡觉',      // 消息正文,会先过一遍命令词表
  chatId: 'private:10001',  // 形状 private:<号> / group:<群号>
}

只要满足 kind === 'in' && owner === true && direct === true,这条消息就会:

  1. 进卧室的「手机」未读消息;
  2. 命中命令词(睡觉/起床/刷抖音/去电脑桌/看手机/看窗外/自由活动/别刷了)时, 让桌宠在卧室里真的去做。

为什么用 owner / direct 两个布尔标记,而不是解析消息前缀? 靠正则扒文本很容易因为一句文案改动整体失效;结构化字段稳得多。

约定 B:聊天流水(手机里的聊天界面)

手机里显示的是真实聊天记录,来自这个目录:

<chatDataDir>/chatlog/YYYY-MM-DD.jsonl

每行一个 JSON:

{"t": 1730000000000, "chatId": "private:10001", "who": "小明", "uid": "10001", "text": "在吗"}
{"t": 1730000060000, "chatId": "private:10001", "who": "bot", "text": "在的"}
{"t": 1730000120000, "chatId": "group:20002", "who": "小红", "uid": "10002", "text": "刷抖音"}
  • who: 'bot' 表示是她自己发的,其余是对方昵称;
  • uid 是发言人 QQ 号,用来取头像(可空);
  • chatId 必须是 private:<数字> 或 group:<数字>,否则卧室拒绝显示。

约定 C:日记(桌上那篇)

<chatDataDir>/diary.json
{
  "plan": { "...": "随便" },
  "history": [
    { "at": 1730000000000, "text": "今天把房间换成了这张图。", "ok": true }
  ]
}

卧室按天聚合:同一天多篇取最后一篇当主日记,其余记进 count; 点开能看到当天所有篇目(带时间小标题)。只读,不代写。

方案一:直接用页面上的「QQ 连接」窗口(默认 SnowLuma)

不想写代码就用这个。 卧室页面右上角有一个「QQ 连接」按钮,点开就是配置窗口: 不用装 Aurabot,也不用改插件配置,填三个值、点两下就好。

窗口里要填的:

字段 填什么 默认
后端 固定 SnowLuma(OneBot 11 协议;NapCat 等其他 OneBot 11 后端同样适用) SnowLuma
HTTP API 地址 SnowLuma 面板「HTTP API」里显示的地址 http://127.0.0.1:3001
访问令牌 你在 SnowLuma 里设的令牌;没设就留空 空
主人 QQ 你自己的 QQ 号。只有这个号发来的私聊会进卧室 空
消息上报地址 窗口自动按当前页面地址算好,点「复制」 <你的卧室地址>/qq/event

在 SnowLuma 里的三步:

  1. 打开面板 →「HTTP API」:记下端口和令牌,与窗口里的地址/令牌保持一致;
  2. 「HTTP 上报」:地址填窗口里那条(结尾是 /qq/event),令牌同上, 消息类型至少勾上「私聊消息」;群聊消息可以一并上报,卧室会自己过滤掉;
  3. 回到卧室点「测试连接」——能看到机器人昵称和好友数就说明 HTTP API 通了; 再点「保存」,配置立即生效(不用重启)。

保存后会发生什么(都是真的,不是演示):

  • 主人私聊里说「睡觉 / 起床 / 刷抖音 / 去电脑桌 / 看手机 / 看窗外 / 自由活动 / 别刷了」, 桌宠在卧室里真的照做;
  • 每条主人私聊都会进「手机」的未读消息,同时追加一份流水到 $DSH_HOME/dsh-pet-bedroom/qq-log/YYYY-MM-DD.jsonl,手机聊天列表就能看到;
  • 别人私聊、群聊、图片/表情会被过滤(窗口状态行会显示「过滤 N」)。

安全与边界:

  • 令牌只存在本机 $DSH_HOME/dsh-pet-bedroom/qq.json(权限 600),页面只显示 「已设置 / 未设置」,永远不回显令牌;
  • 上报接口会校验令牌(Authorization: Bearer <令牌>,也接受 ?access_token=), 不匹配直接 401;
  • 卧室不登录 QQ、不保存 QQ 密码;QQ 登录态完全由 SnowLuma 自己管;
  • 同一条消息被 Aurabot 与直连同时上报时会按 message_id 去重,不会进两次。

这些值也可以写进插件配置(qqApiBase / qqToken / qqSelfId)当默认值, 或用环境变量 BEDROOM_QQ_API / BEDROOM_QQ_TOKEN / BEDROOM_QQ_SELF_ID; 页面里保存的值优先。

方案二:用现成的 Aurabot

如果你已经装了 dsh-aurabot-qq(Aurabot,经 SnowLuma / OneBot 接 QQ), 它正好满足上面全部约定,服务名就是 aurabot,什么都不用改。

方案三:自己实现(最小示意)

// 你自己的 QQ 插件(宿主半侧)
export const name = 'my-qq';
export function apply(ctx, config = {}) {
  const listeners = new Set();

  const service = {
    on(event, fn) {
      if (event !== 'activity') return () => {};
      listeners.add(fn);
      return () => listeners.delete(fn);
    },
  };
  ctx.provide('my-qq', service);

  // 你的 QQ 网关回调里:把收到的消息转成 activity
  function onIncoming(msg) {
    const activity = {
      kind: 'in',
      owner: msg.from === config.ownerQq,     // 只有主人本人
      direct: msg.type === 'private',          // 只有私聊
      text: msg.text,
      chatId: (msg.type === 'private' ? 'private:' : 'group:') + msg.chatId,
    };
    // 同时追加聊天流水(约定 B)
    appendChatlog(activity, msg.nickname, msg.uid);
    for (const fn of listeners) fn(activity);
  }
}

然后在卧室配置里写 chatService: 'my-qq'、chatDataDir: '你写 chatlog 的目录'。

不要把凭据、QQ 密码、令牌写进这个仓库或配置示例里。 本项目不读取、不保存任何凭据。

抖音部分:你需要接入自己的

卧室不自己开浏览器、不登录抖音。它只调你后端的两个 HTTP 接口。

接口契约

GET  <douyinApiBase>/douyin/state
      → { "ok": true, "busy": false, "watched": 12,
          "pauseReason": "", "lastSkipReason": "", "history": [] }

POST <douyinApiBase>/douyin/run       body: {}
      → { "ok": true, "message": "刚开始刷,这一轮看 3 个左右" }
      → { "ok": false, "message": "今天额度用完了" }     ← 拒绝理由会原样显示
  • douyinApiBase 默认跟随聊天服务(默认即 Aurabot 的 /aurabot/api), 也可以指向完全不同的地址,例如 http://127.0.0.1:8080/api。
  • 约定 run 是后台开始、马上返回(一轮要几分钟); 卧室只负责说「开刷了」,进度从 state 读。
  • 约定没有停止接口:卧室的「别刷了」会如实返回 ok: false 并说明停不了, 不会假装按停。如果你自己实现了停止接口,可以在此基础上扩展。

最小后端示意(Node,无依赖)

import http from 'node:http';

let busy = false, watched = 0, pauseReason = '';

http.createServer(async (req, res) => {
  const send = (obj) => {
    res.writeHead(200, { 'content-type': 'application/json' });
    res.end(JSON.stringify(obj));
  };

  if (req.url === '/api/douyin/state') {
    return send({ ok: true, busy, watched, pauseReason, lastSkipReason: '', history: [] });
  }
  if (req.url === '/api/douyin/run' && req.method === 'POST') {
    if (busy) return send({ ok: false, message: '正在刷,等这一轮结束' });
    busy = true;
    // 这里换成你自己的刷抖音逻辑(浏览器自动化 / 别的服务)
    setTimeout(() => { busy = false; watched += 3; }, 60_000);
    return send({ ok: true, message: '开刷了,这一轮约 1 分钟' });
  }
  res.writeHead(404).end();
}).listen(8080);

然后配置:

config:
  douyinApiBase: 'http://127.0.0.1:8080/api'

默认适配:Aurabot 的「现在刷一轮」

Aurabot 的刷抖音模块正好提供了这两个接口(/aurabot/api/douyin/run|state), 所以默认配置下不用额外写后端;没有 Aurabot 时按钮会如实说没接上。


本项目引用了 dsh-pet 的哪些功能

明确声明:本项目不改 dsh-pet 一行代码,也不读写它的配置文件或数据库。 它只使用 dsh-pet 已经对外开着的 HTTP 能力与素材:

引用的 dsh-pet 能力 用在哪 具体怎么用
GET <petRoute>/config 选宠物、取动画池 读宠物条目:pets[](id/name/素材根)、animations(idle、turn、drag、clicks、moves.actions、categories[].actions、events),拼成完整动画池
GET <petRoute>/thumb/<素材根>/<动画名>.webm 卧室里所有桌宠画面 经本插件 /dsh-pet-bedroom/pet/thumb/... 代理转发(独立页面不能直接请求宿主路由);只放行 <根>/<文件>.webm|mov 两段,拒绝目录穿越
宠物 id 与素材根 petId 配置 默认取列表第一只;填了 petId 就挑那只
动画 待机呼吸休闲(idle 优先) 默认站立 从 animations.idle 取,取不到再从池里按「待机 / 呼吸」找
动画 小憩 / 沉眠 / 睡觉 / 哈欠 躺床睡觉 按关键词在池里挑,没有就退回待机
动画 写代码 / 敲击桌面 / 轻快记录 坐电脑前 同上
动画 对屏 / 碎碎念 / 东张西望 拿手机看 同上
动画 东张西望 / 伸懒腰 / 摇扇 趴窗台看外面 同上
动画 玩水枪(优先)/ 浇水 / 浇花 给龟背竹喷水 池里没有「玩水枪」才退到别的
动画 360 / 旋转展示 坐电脑前的「背面」 播到约 2.3s 就地暂停,得到真背面(不翻转、不裁腿)
动画 点击回应 / 开心 / 害羞 点桌宠 随机回应
动画 螃蟹走路 / 漂浮踏步 / 奔跑 自由走动 同上
素材尺寸约定 角色大小恒定 素材是 640×360 透明画布,角色实体占宽 33.8%、高 16.7%~91.7%;所以容器宽度固定 41.4%,换算后角色约占舞台 14%
整个动画池 右键「动作演示」 把 config 里的全部动画名拿来随机演示(例如 106 条)

不引用的部分:dsh-pet 的桌面窗口、拖拽、设置页、存储、皮肤包、内部状态, 本项目都不碰。dsh-pet 升级换皮后,卧室会自动跟着变(因为不写死动画名)。

如果你换了一只宠物,只要它的素材仍遵循上面的画布约定,卧室不用改代码; 例外情况见 自定义 里的尺寸换算。


页面与接口清单

HTTP 路由(都在 /dsh-pet-bedroom 前缀下,只接受本机请求)

方法 路径 作用
GET / 卧室页面
GET /room.png、/chair.png 房间图、椅子前景层
GET /wallpaper/<id>.jpg 手机壁纸(白名单 4 张)
GET /avatar/<QQ号> 个人头像代理(q1.qlogo.cn,只放行数字号)
GET /groupavatar/<群号> 群头像代理(p.qlogo.cn)
GET /pet/thumb/<根>/<文件> dsh-pet 素材代理
GET /state 房间完整快照(网页每 2 秒轮询)
GET /doc?kind=diary|notes&name=... 读日记全文 / 笔记全文
GET /phone/chat?id=... 读某个会话的消息
POST /room 改时辰 / 天气 / 壁纸 / 自动时间
POST /slot 加/删家具
POST /command 面板按钮/输入框 → 执行命令
POST /phone/read、/phone/off、/phone/say 手机已读/灭屏/说一句
POST /douyin/start、/douyin/stop 刷抖音(转给你后端)
GET /qq QQ 连接状态(地址/主人号/是否已设令牌/最近测试结果;不含令牌)
POST /qq/config 保存 QQ 连接配置(写 qq.json,立即生效)
POST /qq/test 真调一次 OneBot get_login_info + get_friend_list
POST /qq/event OneBot 11 HTTP 上报入口(校验令牌、按 message_id 去重、只收主人私聊)

给 agent 的工具

工具 作用
bedroom_status 看房间现在什么样:时辰、天气、壁纸、桌上文档、未读消息、抖音状态
bedroom_read 读日记(按日期)或读书笔记(按标题),或只列清单
bedroom_write 写读书笔记(追加/替换)。日记不能代写,那是聊天服务自己写的
bedroom_atmosphere 换天气(晴天/雨/雪)、换时辰(清晨/中午/下午/晚间)、开关自动时间
bedroom_furniture 往房间槽位加/删家具(带介绍,鼠标移上去能看到)
bedroom_wallpaper 列手机壁纸 / 换壁纸

目录结构

dsh-pet-bedroom/
├─ index.js              宿主半侧:HTTP 路由、读日记/笔记/聊天流水、工具、QQ 事件
├─ client.js             浏览器半侧:只在侧边栏加一个「卧室」按钮(不挂浮层)
├─ cordis.patch.yml      插件插入项(id / name)
├─ package.json          包信息、dsh.bundle 清单、exports
├─ screenshots.json      市场截图清单(2 张,路径相对本文件)
├─ web/index.html        卧室页面(单文件:HTML + CSS + JS)
├─ assets/
│  ├─ room.png           房间图(1536×1024)
│  ├─ chair.png          椅子前景层(坐电脑前挡住后背)
│  ├─ icon-180.png       市场卡片图标(180×180 位图)
│  ├─ screenshot-room.jpg 卧室截图(房间 + 桌宠)
│  ├─ screenshot-qq.jpg  「QQ 连接」窗口截图
│  └─ wall/*.jpg         手机壁纸(4 张)
├─ .github/
│  ├─ workflows/release.yml  推 v* tag 自动发 Release
│  └─ awesome-plugin-entry.yml 收录条目草稿(含 icon/tarball)
├─ release.mjs            发版:从 tag 打包 + 建 Release + 上传资产
├─ test-entry.cjs         收录条目自检(对着 contributing.md 的 CI 检查项)
├─ locale/zh.json|en.json 文案
├─ audit.cjs             静态自检(138 条:尺寸、姿态、路由、配置项、QQ 窗口……)
├─ smoke.mjs             冒烟:页面/房间图/壁纸/日记/笔记/路径逃逸
├─ test-aurabot.mjs      端到端:QQ 过滤 / 日记聚合(假服务)
├─ test-phone.mjs        端到端:手机会话 / 抖音接口
├─ test-group.mjs        端到端:群聊标题与会话人
├─ test-config.mjs       端到端:通用接口(petRoute/petId/chatService/douyinApiBase)
├─ test-qq.mjs           端到端:QQ 连接窗口(保存/测试连接/上报校验/去重/进手机)
├─ test-qq-fail.mjs      端到端:写盘失败必须如实报 ok:false(不许假成功)
├─ preview.mjs           独立预览服务器(不重启 DSH 验证页面改动)
├─ start-trial.ps1       一键试用:另起 profile 装 Release 包,不碰日常 profile
├─ v-*.mjs               Playwright 可视验证(头像/拨号/群聊/壁纸/圆角)
├─ make-chair.py         从 room.png 抠椅子生成前景层
└─ register-chair.py     配准新椅子图到房间坐标系

自定义

换房间图

  1. 把新图命名为 assets/room.png(建议同比例 3:2,如 1536×1024);
  2. 重新标定 web/index.html 里的 SPOTS:x/y/w/h 是图上的百分比, anchor 是宠物该站的位置(anchor.y 越大,人在画面上越靠下);
  3. 全局上下挪动只改 const ANCHOR_LIFT,不要逐个改 anchor—— 房间里还有写死的 goTo({x,y})(躺床、起床、回中间、自由走动),只改 anchor 会漏;
  4. 改完跑 node audit.cjs。

调整桌宠大小

只改一个数:web/index.html 里的 const widthPct = 41.4; 角色占舞台宽 ≈ widthPct × 0.338(41.4 → 14%,60 → 20.3%)。 换目标值后同步改 audit.cjs 里那条 14% 的断言。

换椅子 / 重新配准

python make-chair.py        # 从 room.png 抠出椅子
python register-chair.py    # 把新椅子图配准到房间坐标系(需要 scipy/Pillow)

脚本会打印 left/top/width/height 百分比,写进 web/index.html 的 .chair-front。

加家具 / 壁纸 / 命令词

  • 家具:用 bedroom_furniture 工具或页面右键菜单,会持久化到 room.json;
  • 壁纸:把新图放 assets/wall/,在 index.js 的 WALLPAPERS 里加一条(id + 文件);
  • 命令词:改 index.js 的 COMMAND_RULES。注意顺序:「别刷了」必须排在「刷抖音」前面, 否则「别刷抖音」会被读成「刷抖音」。

去掉 QQ 或抖音

  • 抖音:douyinEnabled: false;
  • QQ:不配置 chatService 即可(或指向一个不存在的服务名), 手机 QQ 会显示「还没有聊天记录」,其它功能不受影响。

本地验证

下列脚本都在 Release 的预构建包里(package.json 的 files 已包含), 所以从 Release 装下来的目录里也能直接跑,不用先 clone 仓库:

cd <插件目录>            # profile 里的 node_modules/dsh-pet-bedroom,或 git clone 下来的目录
node audit.cjs          # 138 条静态自检,必须 ALL PASS
node smoke.mjs          # 冒烟(临时目录,不碰真实数据)
node test-aurabot.mjs   # 假聊天服务:QQ 过滤 + 日记聚合
node test-phone.mjs     # 手机会话 + 抖音接口
node test-group.mjs     # 群聊标题 + 发言人
node test-config.mjs    # 通用接口:自定义 petRoute/chatService/douyinApiBase 全生效
node test-qq.mjs        # QQ 连接窗口:保存/测试连接/上报鉴权/去重/进手机
node test-qq-fail.mjs   # 失败不许报成功:写盘失败时 /qq/config 必须回 ok:false
node test-entry.cjs     # 收录条目:manifest / 分类 / 描述与代码一致性(16 项)

这些脚本只读本目录的文件 + 用临时目录跑假服务,不会改动已安装的插件本身。 audit.cjs / test-entry.cjs 要在插件目录内运行(它们按相对路径读文件)。

所有测试都是隔离的:临时目录 + 假服务 + 假 fetch,不会读写你的真实记忆库、 不会真的刷抖音、不会真的发 QQ 消息。

一键起一个独立试用实例(不装进你日常用的 profile):

.\start-trial.ps1              # 默认 49790
.\start-trial.ps1 -Port 49820  # 换端口

它会用 bedroom-trial 这个 profile 起一个只装了 dsh-pet + 本插件的 DSH Web 实例; 启动后终端打印的带 token 地址就是入口,卧室在 /dsh-pet-bedroom/。 改插件源码不影响它(它装的是 Release 包)。

页面预览(不重启 DSH):

node preview.mjs        # → http://127.0.0.1:64123/dsh-pet-bedroom/

preview.mjs 会照搬宿主的静态路由与素材代理,并把 /state 转发给真实宿主 (UPSTREAM 可覆盖)。

Playwright 可视验证(可选,需要 playwright-core 和 Chromium):

# 如果 playwright-core 不在当前 node_modules 里,用环境变量指过去
PLAYWRIGHT_CORE=file:///path/to/node_modules/playwright-core/index.mjs \
BEDROOM_URL=http://127.0.0.1:64123/dsh-pet-bedroom/ \
node v-dial.mjs

安全与边界

  • 只接受本机请求:所有路由的 Host 必须是 127.0.0.1 / localhost / ::1,否则 403。
  • 白名单:壁纸只放行 4 个 id;头像只放行纯数字号码;素材只放行 <根>/<文件>.webm|mov。
  • 不做目录遍历:/doc、/wallpaper、/pet/thumb 都有校验,../ 一律拒绝。
  • 不碰凭据:本项目不读取、不保存 QQ 密码、Cookie、Token。
  • 不假装:抖音拒绝就显示拒绝理由;停不了就说停不了;日记没有就是没有。
  • 手机消息是内存态:重启 DSH 即清空。它是「刚才谁找我」,不是聊天档案。
  • 卧室不替抖音过登录/验证码:那是你后端的事。
  • 桌面模式的透明小窗里没有卧室:卧室是浏览器页面。

常见问题

Q:打开 /dsh-pet-bedroom/ 是 404? A:插件没加载。检查 profile 的 bundles 是否包含 dsh-pet-bedroom、 node_modules/dsh-pet-bedroom 是否指向正确目录,然后重启 DSH。

Q:页面能打开,但没有桌宠? A:dsh-pet 没装或没打开显示,或者 petRoute 不对。先确认 http://<DSH>/dsh-pet-7340/config 能返回 JSON;不能就改 petRoute。

Q:桌宠尺寸不对/位置偏了? A:看 调整桌宠大小。位置偏多半是容器结构变了, 用浏览器量一次脚底 y 的差值,改 ANCHOR_LIFT。

Q:手机里没有聊天记录? A:QQ 部分没接(见 QQ 部分), 或 chatDataDir 里没有 chatlog/*.jsonl。

Q:点「现在刷一轮」说没接上? A:douyinApiBase 没配,或后端没启动。先用 curl http://127.0.0.1:8080/api/douyin/state 确认它活着。

Q:日记是空的? A:日记来自聊天服务(默认 Aurabot)的 diary.json;没接就没有。 本项目不会凭空编一篇日记。

Q:能同时装多只宠物吗? A:一个卧室实例选一只(petId)。多实例需要不同路由前缀,目前不推荐。

Q:支持 macOS / Linux 吗? A:代码是跨平台的(路径都用 path 拼)。Windows 是主要验证环境。


收录与市场(awesome-dsh-plugin)

按 awesome-dsh-plugin 贡献指南 逐项对照,本仓库的现状:

要求 状态
package.json 声明 dsh.bundle(含 patch) ✅ "bundle": { "patch": "./cordis.patch.yml" },且 cordis.patch.yml 在仓库根
真实可用的代码(非占位/纯 README) ✅ 138 条静态自检 + 7 套端到端/冒烟测试全部通过
仓库创建满 1 天 ⏳ 建于 2026-11-03T11:34Z,2026-11-04T11:34Z 之后达标(CI 自动判定,代码无需改动)
添加 dsh-plugin topic ✅ 已添加(另有 desktop-pet、cordis-plugin、deepseek-harness)
描述只讲功能、不夸大 ✅ 描述里的「6 个工具」用 test-entry.cjs 对着 index.js 数过,QQ/抖音也各有一条断言
分类贴合实际做的事 ✅ fun(主体是桌宠在房间活动的观赏体验,QQ/抖音为可选外接)
一个 PR 只加 1 条(上限 3) ✅ 草稿里只有 1 条 url:
可选:预构建 tarball ✅ 每个 Release 都挂 dsh-pet-bedroom.tgz;条目用 latest/download,资产名不带版本号(换版不会 404)
可选:市场截图 screenshots.json ✅ 2 张(assets/screenshot-room.jpg、assets/screenshot-qq.jpg)
可选:市场图标 ✅ assets/icon-180.png(180×180 PNG,GitHub 托管,非 SVG)

收录条目草稿在 .github/awesome-plugin-entry.yml: 提交到对方仓库时,新增 data/plugins/yiy367389-del__dsh-pet-bedroom.yml,内容即该文件(去掉注释)。 本地先用 node test-entry.cjs 把 CI 会查的 16 项过一遍(含描述与代码对不对得上)。


版本与演进

  • v2.3.5:加 start-trial.ps1(一键起一个只装 dsh-pet + 本插件的试用实例, 不碰日常 profile)。脚本必须带 UTF-8 BOM——Windows PowerShell 5.1 会把无 BOM 的 UTF-8 当 ANSI 读,中文注释直接把块结构读崩,报的还是无关行号。audit 新增两条守住这点。 另外把「自检条数」改成运行时自统计(数实际印出的行数),不再靠手写常量, 改条目时不会再出现「代码加了、README 没跟上」。
  • v2.3.4:修「失败也报成功」。本地装上后实测发现 /qq/test 与 /qq/config 的 ok 被尾部铺开的状态对象(自带 ok:true)覆盖,端口填错时窗口仍会显示「通了」。 现在判定写在状态之后;顺带把 426/101 翻译成人话(那是 WebSocket 端口,不是 HTTP API)。 新增 test-qq-fail.mjs 锁住「写盘失败必须 ok:false」。
  • v2.3.3:验证脚本随包发布。之前 files 只带运行时代码,README 却让人在项目目录跑 node audit.cjs——从 Release 装下来的用户照着敲会 file not found。现在 audit.cjs、 test-*.mjs、test-entry.cjs、smoke.mjs、preview.mjs、v-*.mjs、*.py、release*.mjs 与 .github/ 里的条目草稿都随包发出(43 个文件,包体 3.3 MB);audit 新增一条守卫, README 点名要跑的脚本只要没在 files 里就 FAIL。
  • v2.3.2:文档数字与实际对齐:端到端/冒烟套数当时写错(多算了 一个不存在的测试文件)。这类数字现在由 audit.cjs 与 test-entry.cjs 一起守住。
  • v2.3.1:每个 Release 的说明补上该版本正文。release-notes.mjs 从 CHANGELOG.md 取对应小节写进 Release 正文并附安装命令,可重复执行;发版工作流发完自动跑一次。
  • v2.3.0:按 awesome-dsh-plugin 指南优化收录条目。category 改 fun;描述改成可核对的 写法(「6 个工具」对着 index.js 数);新增 test-entry.cjs 把 CI 会查的 16 项在本地跑一遍; audit 增至 132 条。
  • v2.2.2:修复发版工作流。npm pack --pack-destination dist 在 dist 不存在时直接失败, v2.2.0 / v2.2.1 的 Action 都红在这一步;现在先 mkdir -p dist,推 tag 由 Action 自动发版。
  • v2.2.1:修正 latest。GitHub 的 latest 按 Release 创建时间算,批量补发旧版后 会指向旧版本;release.mjs 现在创建时显式 make_latest,并把旧版标为非 latest。
  • v2.2.0:按收录指南整理发布形态。每个版本都有 Release 预构建包 (dsh-pet-bedroom.tgz,文件名不带版本号);新增 release.mjs、发版工作流、 市场截图与 180×180 图标;package.json 补 repository / homepage / bugs / keywords; 已验证 dsh plugin add <tgz> 会把插件写进 profile 并自动登记到 dsh.profile.bundles。
  • v2.1.0:新增「QQ 连接」窗口(默认 SnowLuma / OneBot 11):页面里填 HTTP API 地址/令牌/主人 QQ,真连一次再保存;/qq/event 接收 HTTP 上报,校验令牌、按 message_id 去重、只收主人私聊,消息进手机并追加 qq-log 流水。
  • v2.0.0(本仓库起点):脱敏通用版。引入 petRoute / petId / vaultDir / notesDir / chatService / chatDataDir / chatApiBase / douyinApiBase 等配置项, QQ 与抖音改为「按契约接入」;保留 v3 的独立网页架构与全部交互。
  • 历史(原作者环境):v1 概念稿 → v2 全屏浮层(已废弃,遮罩会吃掉点击)→ v3 独立网页 + 真实日记/聊天流水 + 手机界面。

仓库从 v2.0.0 开始按 git 提交记录演进,后续改动请写进 CHANGELOG.md。


许可与贡献

  • 许可:MIT。可自由使用、修改、分发、商用,保留版权与许可声明即可。
  • 贡献:欢迎 issue / PR。提交前请保证 node audit.cjs 与全部测试通过; 不要提交任何个人路径、QQ 号、聊天内容、令牌或真实记忆库数据。
  • 上游:dsh-pet 的素材与动画版权归 dsh-pet 及其作者所有,本项目只做引用。

致谢

  • dsh-pet:桌宠本体、动画池与素材接口。
  • DeepSeek Harness:插件运行时与 Web 宿主。
  • Aurabot(dsh-aurabot-qq):本项目的 QQ 与抖音默认适配实现。
—/ 5

No ratings yet

Verified DSH bundle

Commit 5f72ffe73934

Community comments

No comments yet. Be the first to write one.

DSH HUB

A community index for DSH plugins. Not an official GitHub or DeepSeek AI product.

CommunityResourcesAPIAbout