- Home
- Plugin
- Solo per divertimento
- dsh-lucky-block
dsh-lucky-block
lyhtakeashot/dsh-lucky-block
Adds a draggable lucky-block button to the DeepSeek Harness web UI. Single-click plays zero-cost little performances (popups, a fake blue screen, daily fortune, trivia, cold jokes, music, and a set of visual stunts); a 1.4-second long-press mines the block open and starts a new session to write a random paper, read the news, invent something, or spin a short story. It also appends a "lucky block" choice to the model's multiple-choice questions and rolls a random pick, reversible by saying "rollback". Block appearance and event weights are adjustable in settings.
Installazione
dsh plugin --profile web add github:lyhtakeashot/dsh-lucky-blockREADME
幸运方块 (dsh-lucky-block)
一个 dsh 插件,同时工作在 dsh web 与 dsh desktop。两件事:
- 幸运方块选项 —— AI 向你提选择题时,每个问题的选项末尾会自动多出一个「幸运方块」。
选它,就随机替你挑一个选项、或把几个选项揉成一个新方案,全随机。
不满意?说一句「回滚」(或打
/rollback),立刻回到上一版本。 - 幸运方块按钮 —— 屏幕角落一个可拖动、可锁定的我的世界幸运方块(16×16 原版方块面贴图)。 闲着没事就戳它一下:左键单点零成本,抽弹窗表演 / 假蓝屏 / 今日运气 / 冷知识 / 冷笑话; 长按 1.4 秒挖穿它,伴随裂纹、碎裂粒子和原版 Minecraft 的挖掘声(按当前材质决定是石头、木头还是金属),接着它会在你的 默认工作区自动开一个新会话,全程不需要你做任何事地随机写篇论文 / 扒几条新闻 / 发明个东西。 松手时方块若离屏幕边缘够近(48px 以内)会自动吸附贴边。
它会说话。所有随机事件都套着一层固定的说话腔调:短句、机器口吻、偶尔损你一句。
这套腔调是纯文本的,参数在 lib/persona.js 的 DEFAULT_PERSONA 里,
可以用配置里的 persona 整段换成你自己写的性格。
它从不往会话里写东西,所以绝不会污染你的对话历史。
两个池:单击零成本,长按才花钱
脚本现在分成两个互相独立的池,触发方式不同,抽的东西也不同:
| 触发 | 代价 | 池 |
|---|---|---|
| 左键单点 | 零 token、零延迟、不建会话 | 弹窗 / 蓝屏 / 十一个动效 / 抽签 / 冷知识 / 冷笑话 / 自述 / 音乐 |
| 长按挖满(1.4 秒) | 开新会话、调模型 | 论文 / 新闻 / 发明 / 小说 / 小剧场 / 自我反驳 / 说明书 |
单点是手滑也会发生的动作,所以它不该有成本;长按是明确意图,才值得花 token。 分成两个池而不是一个池加过滤,是为了让两边的权重能独立调节 —— 否则往单击池 里加十条音频,会把长按事件的命中率一起稀释掉。
单击池(22 个事件,权重合计 120)
| 概率 | 事件 | 类型 | 会做什么 |
|---|---|---|---|
| 5.0% | 无害弹窗表演 | 视觉 | 连弹 24 个无意义提示框(三种皮轮转:老式错误框 / Win11 通知卡 / 朴素框),再按创建顺序倒序全部关掉 |
| 5.0% | 蓝屏屏保(60 秒) | 视觉 | 全屏显示仿 Windows 蓝屏,十张脸随机一张(表情 / 配色 / STOP 码都不同),60 秒后自动恢复 |
| 4.2% | 方块自己跑掉了 | 视觉 | 方块窜到离它最远的那个屏幕角落,停一下,再滑回原位 |
| 4.2% | 屏幕上多出几个鼠标 | 视觉 | 五只假光标从屏幕各处游走,不挡点击 |
| 4.2% | 屏幕抖了一下 | 视觉 | 整个页面抖 0.9 秒,然后归位 |
| 3.3% | 停电了(8 秒) | 视觉 | 整个屏幕黑下去,只剩方块还亮着。点击被吃掉(看不见就别乱点),按 ESC 立刻来电 |
| 3.3% | 假警报 | 视觉 | 先播「有东西要来了」—— 真的等 2.6 秒 —— 再说「……没有东西」。两段加起来 5.2 秒 |
| 3.3% | 掀桌 | 视觉 | (╯°□°)╯︵ ┻━┻ 砸在屏幕上,停一下,再换成 ┬─┬ノ(゜-゜ノ) 收走 |
| 3.3% | 手滑(黄油手) | 视觉 | 方块从手里滑出去、直直掉出屏幕,再被捡回来弹一下。整场 1.6 秒,屏幕其余部分一动不动 |
| 2.5% | 彩虹暴走(4 秒) | 视觉 | 整页盖一层流动的彩条,4.2 秒后褪掉。颜色只染在明暗上(overlay 混合),不挡点击 |
| 2.5% | 整页缩水 | 视觉 | 整个页面缩到 80%(可在设置里调到 60%~95%),3 秒后恢复。缩的时候方块不许拖(坐标系变了会写歪坐标) |
| 2.5% | 整页变大 | 视觉 | 整个页面放大到 120%(可在设置里调到 105%~140%),3 秒后恢复。和缩水是同一个动效的两个方向 |
| 2.5% | 低重力(方块飘了) | 视觉 | 方块自己往上飘 6% 屏高、晃两下、再落回原位,3.4 秒。页面其余部分一动不动 |
| 15.0% | 抽今日运气 | 本地 | 抽一档签文:鸿运齐天 / 大吉 / 中吉 / 小吉 / 顺 / 平 / 凶,每档五条文案。不调 AI,附「无科学依据,仅供参考」 |
| 17.5% | 随机冷知识闲聊 | 本地 | 从 818 条本地冷知识库里抽 1 条 |
| 13.3% | 冷笑话专场 | 本地 | 从 353 条本地笑话库里抽 1~2 条 |
| 3.3% | 使用记录自述 | 本地 | 念真实累计数字:「你点了我 217 次,其中 12 次是长按」。不调 AI |
| 1.7% | Never Gonna Give You Up | 本地 | 放 42 秒副歌(唯一不是完整版的一首),带时间轴歌词 |
| 0.8% | Who's Ready for Tomorrow | 本地 | 放完整版 112 秒 |
| 0.8% | I Really Want to Stay at Your House | 本地 | 放完整版 241 秒 |
| 0.8% | My Shot | 本地 | 放完整版 328 秒 |
| 0.8% | Satisfied | 本地 | 放完整版 330 秒 |
音乐合计只占 5%(6 / 120)。五首里 Never Gonna Give You Up 的权重是 2, 其余四首各 1 —— 它多出一倍不是偏心,是唯一带歌词的那首(见下面的「歌词」)。 概率按曲目条数算,不按占用时长:My Shot 有 328 秒,但它和 112 秒的 Who's Ready for Tomorrow 一样只是「五首之一」。
十一个动效事件(跑掉 / 假光标 / 抖动 / 停电 / 假警报 / 掀桌 / 手滑 / 彩虹 / 缩水 / 变大 / 低重力)有个共同点: 它们不产生任何内容。所以除停电之外它们都极短(0.9~5.2 秒),且 ESC 能立刻收场 —— 一个什么都说不出来的表演,多留一秒都是浪费。它们也刻意没有做成 「冷知识的附属特效」:那样冷知识就有 30% 的概率只动不说话。
停电是这条规矩里唯一的故意破例:它黑 8 秒,因为「黑了这么久而你还在等」就是它的
全部内容 —— 短于 6 秒只会像闪了一下,超过 10 秒则真的会有人去关窗口。
它的遮罩还会吃掉点击:在看不见的界面上乱点,代价比什么都不做高得多。
掀桌反过来是最克制的一个:它只旋转自己那两个颜文字,不转整个页面 ——
transform 挂在 body 上会给所有 position:fixed 的后代重建包含块,
DSH 的浮层会跟着错位,那是真 bug,不是玩笑。
手滑是同一条规矩的另一个解法:掉下去的是一份复制出来的方块,真方块只是藏了
一个 class 再弹回来 —— 屏幕其余部分一个像素都不动,所以它连包含块的风险都没有。
彩虹 / 缩水 / 变大 / 低重力是「整页动效」那一组,它们绕开同一条陷阱(transform
或 filter 挂在 body 上会给 position:fixed 的后代重建包含块)用了三种不同的办法:
彩虹的颜色和混合都长在自己那层遮罩上,底下什么都不碰,而且不挡点击;
缩水/变大走 documentElement.style.zoom —— zoom 改的是布局尺度、不重建包含块,
position:fixed 的后代跟着一起缩放,正是要的画面(缩放期间方块禁止拖动:坐标系变了,
拖一下就会把坐标写歪);
低重力只动方块自己,用独立的 translate 属性(不动 transform,因为那个正被忙闲态
和弹回动画占着),飘多高写 vh 而不是问屏幕要像素。
动效现在占单击池 55 / 120(46%),这是刻意的天花板:表演不该超过一半,
下次再加动效,请先从动效里挪权重,别去动站在旁边的抽签和冷知识。
长按池(7 个事件,权重合计 16)
| 概率 | 事件 | 类型 | 会做什么 |
|---|---|---|---|
| 18.8% | 随机选题写论文 | AI | 随机词条拼出题目(如「边缘计算 × 古籍数字化 × 的鲁棒性研究」),写 4 节正文并落盘到 lucky-block-papers/<hash>.md |
| 18.8% | 时事新闻速览 | AI | 联网检索 3 条最近真实新闻,每条一句事实 + 一句点评 + 来源链接;查不到就直说,绝不编 |
| 12.5% | 随机发明一个东西 | AI | 发明一样不存在的东西:起名 + 解决什么问题 + 怎么工作 + 最明显的副作用,像正经产品说明 |
| 12.5% | 随机短篇小说 | AI | 先随机抽三个互不相干的词(冰箱 / 遗嘱 / 台风),要求三个词都真的参与情节,写 800~1200 字 |
| 12.5% | 荒诞小剧场 | AI | 我和一个不可能存在的东西的剧本对话,那个东西完全遵守自己的逻辑,还觉得我才是奇怪的。300 字以内 |
| 12.5% | 自我反驳 | AI | 先立一个看起来有道理的小论点说圆它,再自己找出漏洞并真的反驳掉,最后不给新结论。250 字以内 |
| 12.5% | 无用说明书 | AI | 给一样日常物品写说明书:分条目带编号、有「注意事项」和「常见问题」,每条技术正确但实际没用。250 字以内 |
单击池二十二个全部零 token、不建会话、零延迟 —— 点下去立刻有反应。
全部单击事件都能按 ESC 提前结束
单击池的二十二种表演,回车同效:按一下 ESC,屏幕上正在放的东西立刻收干净 —— 弹窗不再继续弹、蓝屏直接恢复、音乐立刻停、居中长文立刻消失、方块也立刻回位。 单击池里最长的表演是 330 秒的歌,没有这个出口的话,点错一次就得等五分半。
实现上是一张登记表(registerEscape),不是「当前表演」单槽:
一次单击可能同时起好几样(蓝屏 + 方块台词、音乐 + 标题、长文 + 气泡),
单槽会让后起的挤掉先起的,先起的那样就再也关不掉了。
没有表演在放的时候,ESC 完全不拦截 —— 直接放行给 dsh 自己用。 方块头顶那行小气泡故意不登记(它只占一角、6 秒自己消失, 为了它吞掉 ESC 会让用户 6 秒内关不掉 dsh 的任何东西)。
一场没演完,点它不会开第二场
单击的四类表演(弹窗、假蓝屏、音乐、居中播报)都会在屏幕上停十几秒到几分钟。 这段时间里再点方块不会叠出第二场,头顶只提示一句「上一场还没收场。按 ESC 可以提前结束。」—— 「音乐 + 弹窗同时来」的观感是事故现场,而这本来是个让人放松的东西。
想接着点有两种做法:等它自然演完,或者按 ESC 立刻收场。
实现上是一个正在播的表演计数(不是布尔):一次单击可能同时起两样
(蓝屏 + 台词、音乐 + 标题),少一样收场不该就解锁。
计数用 ref 而不是 state,因为只有回调读它、不该触发重渲染;
也没有复用「请求进行中」那个标记 —— 那个只覆盖 HTTP 的几百毫秒,
混在一起会让「还在加载」和「还在演出」互相污染。
四类表演各有一条收场信息(弹窗关完最后一个、蓝屏到点或 ESC、音乐的 ended 或
被掐断、播报的定时到点 / ESC / 点击 / 被下一段顶掉),任何一条漏接
都会让方块永久点不动,所以 tools/show-check.mjs 专门盯着这四条接线。
「抽今日运气」一天只抽一次:当天再点,返回的还是那一签,并在末尾注明
「今天已经抽过了,明天再来吧。」。状态记在
$DSH_HOME/plugins/dsh-lucky-block/fortune-state.json,删掉这个文件就等于当天重抽。
「抽今日运气」的签文权重是刻意偏吉的:吉类合计 87%(鸿运齐天 5% / 大吉 26% / 中吉 33% / 小吉 23%),顺 8%,平 4.9%,凶只有 0.1%; 而且**「凶」的正文是反着写的**(「从某种角度来说,你运气还挺好 —— 今天该踩的坑都提前踩完了」), 抽到下签也能笑一下。这个插件是用来打发时间的,不是用来给人添堵的 —— 所以「凶」的五条也不预告任何具体的坏事,也不写「要是有什么没办成」这种假设句。 最狠的一条干脆不给「凶」找理由,改为报概率:「这一签的概率是 0.1%,是本系统里最稀有的一种。 稀有款都这样:不一定好看,但一定难抽(差评!)」—— 不解释也不取消后果, 只是让抽到这个结果本身变成一件值得说的事。
从好到差是 鸿运齐天 > 大吉 > 中吉 > 小吉 > 顺 > 平 > 凶。 每一档有五条签文,抽到哪一档之后再在那一档里等概率挑一条 —— 所以同一档连抽两天也不会撞到同一句话。
档位权重和文案条数是完全解耦的:drawFortune() 先按权重定档,
再调 withText() 在档内挑文案。把文案摊平成一个个独立条目是很自然的写法,
但那样一来「给凶多写两条」就等于把凶从 0.1% 悄悄提到 0.3% —— 加文案不该改变概率。
「顺」是后加的第五档,插在「小吉」和「平」之间:小吉是有件小事让你高兴, 平是什么都没有,顺是没有好事、但也没有坎。 它比平多一分安心,比小吉少一件好事,所以平从 9.9% 让位到 4.9%。
音频事件
一首歌一条事件,所以每首能单独关掉。事件 id 由文件名派生:
never-give-you-up.ogg → audio-never-give-you-up。想永久关掉某一首,
在面板里取消勾选,或在 config.json 的 events 里写 "audio-never-give-you-up": { "enabled": false }。
用文件名而不是曲名当 id,是因为 id 要进 config 覆盖层、还要过
/^[a-z][a-z0-9-]*$/ 的白名单(曲名里有中文和空格过不了);
这样即使以后改了 title,覆盖配置也不会悄悄失效。
每首歌的权重默认是 1,不写 3:曲目数会随加歌增长,写死 3 的话加满十首
就会把冷知识/冷笑话的命中率稀释掉一半。想单独调某一首,在
lib/audio-tracks.json 那条里写 "weight": 2(包里 never-give-you-up 就是 2)。
lib/audio-tracks.json 里是曲库,往这个 json 填
{ "file": "xxx.ogg", "title": "显示名", "weight": 2 }、
把文件丢进 lib/audio/,事件就自动开始参与抽取 —— 不用改代码。
文件名只接受 [A-Za-z0-9_-] + .ogg|.mp3,防目录穿越。
曲库有两份查找位置,用户目录优先:先看
$DSH_HOME/plugins/dsh-lucky-block/audio-tracks.json,没有再退回随包的
lib/audio-tracks.json。第三方想扩展曲库时不用改插件包本体(插件升级会覆盖回去)。
包里带了五首,除了 Never Gonna Give You Up 是副歌,其余四首都是完整版:
| 曲目 | 时长 | 说明 |
|---|---|---|
| Never Gonna Give You Up | 42 秒 | 副歌(唯一一首切过的) |
| Who's Ready for Tomorrow | 112 秒 | 完整版,去掉结尾 4 秒静音 |
| I Really Want to Stay at Your House | 241 秒 | 完整版,去掉结尾 6 秒静音 |
| My Shot | 328 秒 | 完整版 |
| Satisfied | 330 秒 | 完整版,去掉开头 26 秒黑胶底噪 |
这五首都能按 ESC 立刻停(见上一节),所以完整版不会变成「点一下就得等五分半」。
切法写在 lib/audio/README.md。
歌词
目前只做了 Never Gonna Give You Up 一首 —— 一首歌一份时间轴 JSON, 把喜欢的歌补成歌词是纯数据工作,但二十五首歌里先挑一首试水。
放它的时候,屏幕底部居中会出现当前这一句英文,跟着唱到哪儿走到哪儿,
换句时淡入。它不挡任何点击(pointerEvents: none)—— 听歌的时候还得
能点按钮。要关掉就是按 ESC 停音乐,歌词跟着一起消失。
歌词数据在 lib/lyrics/<文件名去掉后缀>.json:
[
{ "t": 2.42, "text": "Never gonna give you up" },
{ "t": 4.55, "text": "Never gonna let you down" }
]
t 的单位是秒,而且是片段内的秒数,不是原曲秒数 —— 音频文件是剪过的
副歌(原曲 100~142 秒),拿原曲的 LRC 直接粘进来会整首错位。要加一首新歌的
歌词,先确认音频文件是从原曲哪个区间剪的,再把这个偏移减掉。
用户目录下的同名文件优先($DSH_HOME/plugins/dsh-lucky-block/lyrics/),
所以可以不改插件包就给已有的歌换一份歌词。没有歌词文件的歌是常态,
放歌时就是没字幕,不会报错。
骰子会话里你只会看到一行字
开新会话的那 3 个事件,用户消息只有一行(🎲 随机事件:时事新闻速览),
人格设定和任务正文走的是系统提示,不会糊在你眼前。
这样做纯粹是为了观感 —— 之前用户消息里塞着几百字的指令墙,点进会话第一眼就是它。
需要说明的是,宿主没有「发一条不显示的消息」这种能力(PromptContentPart 只有
text / image / file 三种,没有任何可见性字段),所以这里不是把消息藏起来,
而是根本不把它发成消息。
清单可用 node tools/list-events.mjs 随时重新生成 —— 它直接从 lib/index.js
的 buildEvents() 里解析并按池分组,不会和代码漂移。
设置面板(右键方块 → 设置…)
| 开关 | 作用 |
|---|---|
| 长按挖掘音效 | 循环挖掘声 + 挖穿时的碎裂声 + 换镐子那一声。关掉就完全静音 |
面板分四块:
方块外观 —— 大小(24~128 像素)、图案(幸运方块 / 圆石 / 原木 / 铁块 / 命令方块 /
南瓜 / 自定义图片)、圆角、描边宽度与颜色、投影、背景。改动立即生效并落盘,
不用点保存。图片只接受 data:image/ 或 http(s):// 开头的地址——
blob: 在这个缓存域里用不了、file: 会被 CSP 拦下来,两种都会让图片裂开。
「恢复默认」走的和手动改是同一套校验,所以恢复回来的一定是能用的值。
那五种内置图案都是原版材质:从 Minecraft 1.20.1 的资源包(
assets/minecraft/textures/block/)里取的 16×16 贴图,一个像素都没改,
原图留在 lib/textures/ 里(命令方块的正面原图是 16×64 的四帧动画,取了第一帧)。
它们被内联成 data URI 随客户端一起发出去 —— 浏览器读不到插件目录,
而单张 base64 只有三百来个字符。放大后是硬边像素(image-rendering: pixelated),
不是插值出来的一片糊。
外观里还有两个彩蛋开关,都在这一块里:
| 开关 | 默认 | 作用 |
|---|---|---|
| 方块旁边的黄色小字 | 开 | 方块左上方那句斜着的黄色吐槽,每次抽完换一句(28 句真话 + 万圣节另外 4 句)。关掉就只剩方块 |
| 万圣节(10/31)自动变南瓜 | 开 | 只在 10 月 31 日、且你没挑过外观(还是默认的幸运方块)时生效。挑了圆石/原木/铁块/自定义图的人不会被顶掉 |
整页缩放幅度 —— 「整页缩水」和「整页变大」各有一个百分比(5~40,默认 20),
面板上会直接写出效果(缩水 20% 显示成「整页缩到 80%」)。两个方向分开调
是故意的:缩到 60% 只是「小了一号」,放大到 160% 会把标题栏挤出屏幕、逼出横向
滚动条,所以「缩水想调猛、放大想保守」是很正常的要求,一个数字会让这两件事
被迫绑在一起。这对数字不进事件池,保存时走同一条「重建事件池」的路
(幅度在建表时就烘进 spec 了),改完立刻生效;写坏的值被夹回区间而不是让整页
缩到看不见:0 → 5、999 → 40、"abc" → 20。
随机事件 —— 列表分成 「单击池」/「长按池」两页,页签上是各自的事件数。 两个池是分开抽的,所以也分开算账:每行显示的是占本池的比例(分母是本池当前 权重合计,写在列表下方),而不是混在一起拿一个总数去除 —— 单击池 120 份、 长按池 16 份混成一个分母的话,长按事件会显示成 0.x%,那个数字没法用来判断。
页内按事件的出厂权重(defaultWeight)从大到小排,不是按当前权重:
否则你在输入框里改一个数字,那一行就跳到别处去了。占比反过来用当前权重算,
因为那才是下一次抽中它的真实概率。每行一个复选框加一个权重输入框,
关掉一条事件时权重会同时写 0,两个字段说同一件事,不会出现「权重还在但
事件没了」的假象。权重上限 100,下限 0(0 等于关掉)。上限从 25 抬到 100 是被
音乐权重逼的:五首音乐各占 12 份、合计要只占 5%,其余五条就得落在 1633 这一档;
上限卡在 25 时面板会把你填的 33 静默夹回 25,而面板是这个池子唯一能改权重的地方。
扩展接口 —— 只读显示 persona 与 prompts 这两段配置的当前值。
刻意不做成编辑器:JSON 精度要求高,直接粘贴整段更稳;
而且这里显示的是宿主回传的当前值,上一段写坏了重新打开看到的还是那段坏内容,
不会被悄悄换成默认值。
面板最底下有一行「这一份客户端:形状 8 种,事件 22 单击 / 7 长按」。 它不是装饰:这三个数全部由当前加载的代码自己算出来(形状表的长度、事件列表按 trigger 现数),所以它能回答「我看到的到底是不是最新那版」——如果形状写 5 种, 就说明浏览器里跑的还是旧包,不用再猜是缓存还是代码坏了。
改事件权重/开关保存后立刻生效,不用重启。
事件池原本在插件加载时建好一次(用户改完设置得重启才生效),后来改成了
「保存设置时重建一次」:buildEvents() 是「读盘 + 拼装」的纯过程、不带运行期
状态,所以重读配置与热替换代码是两回事,前者安全。
只改音效开关时不重建 —— 音效由客户端自己应用到播放引擎上,重建只是白读一遍 四个语料库。重建失败时事件池保持旧的,不会出现「一半新一半旧」。
保存按钮有回执:点下去立刻变成「保存中…」(按钮同时禁用,连点没有意义), 落盘成功变「已保存 ✓」(绿),失败变「保存失败」(红),1.8 秒后回到待命。 在此之前,按钮在点下去到宿主回包的那几十毫秒里一个字都不变 —— 用户唯一的 判断依据就是手指下面那行字有没有动静,没有动静就会被读成「没按上」,于是再点一次。 失败详情仍然走方块上方的错误气泡,按钮只负责回答「这一下有没有成」。
收起面板有两条路:面板右上角的 ✕,或者再右键一次方块。后者是给 「改完想走」这个动作准备的 —— 面板就贴在方块正下方,手已经在方块上了, 比横过去瞄一个 ✕ 顺手。面板没开时,右键方块照旧开关右键菜单。 保存按钮不自动收面板:常常要连着调几个权重,收早了反而要多点开一次。
有两条由此衍生的约束:
- 代码改了仍然必须重启 dsh。 上面说的是「重读配置文件」,不是「重载
lib/」。 - 面板列出的是没贴覆盖层的完整清单,关掉的事件会以
weight = 0留在面板里。 否则用户一关掉某条,那条就从面板上消失,永远打不开 —— 而「关掉」这个动作 本来就是在面板上做的。
自定义事件(扩展点)
往 $DSH_HOME/plugins/dsh-lucky-block/custom-events.json 写一个数组,
就能加自己的事件。形状和内置事件一致:
[
{ "id": "my-quote", "title": "随机一句书摘", "weight": 2,
"trigger": "click", "kind": "local",
"lines": ["第一句", "第二句"] },
{ "id": "my-summary", "title": "让 AI 总结点什么", "weight": 3,
"trigger": "hold", "kind": "agent",
"prompt": "随便挑一个话题,写三百字。" }
]
kind: 'local' + lines 从里面随机挑一行当正文(零 token);
kind: 'agent' + prompt 把 prompt 当任务发进新会话(走 AI)。
任何一条不合格就整份忽略,原因显示在设置面板底部 —— 半份生效会让「我加的事件怎么没动静」变成一个只能靠读日志排查的问题。 没有这个文件是完全正常的状态(面板会写「0 条」)。
自定义事件的 id 不能和内置事件重名:重名时内置赢, 第三方文件无法覆盖内置行为。原因是内置事件的 prompt 与 prepare 都在代码里写死, 一个只带 id/weight 的 JSON 造不出真的行为。
面板里还列着事件清单,每条事件标着它属于单击还是长按 —— 照着这张表就知道点一下会发生什么、要不要花 token。
冷知识和冷笑话现在固定走本地库,不再有「调不调 AI」的开关: 它们本来就在单击池里,单击不该有成本,给一个勾选框只会让人以为这里能省钱。
| 本地库 | 条数 | 位置 |
|---|---|---|
| 冷笑话 + 点评 | 353 条 + 30 条 | lib/jokes-data.json |
| 冷知识 | 818 条(人体 133 / 动物 139 / 科学 121 / 日常 111 / 历史 111 / 地理 103 / 语言 100) | lib/trivia-data.json |
| 曲库 | 5 首(42 / 112 / 241 / 329 / 330 秒) | lib/audio-tracks.json + lib/audio/ |
冷知识库里分主题存的 topic 只用于统计,永远不显示给用户 —— 显示主题等于
把「这是一条从哪抽的」直接摊开。笑话的 form 字段同理。
两个库的完整内容可以在 lib/语料库总览.md 里逐条翻阅;
那份文件由 node tools/build-corpus-overview.mjs 从运行库生成,改动语料后重跑即可
(--check 用于回归时校验它没过期)。
写进配置也会被忽略并记一条警告。
配置里 events 字段是事件的权重/开关覆盖层(读-改-写,不整份覆盖 ——
model / persona 也在同一个文件里):
{
"sound": true,
"events": {
"trivia": { "weight": 8 },
"paper": { "enabled": false },
"joke": { "weight": 0 }
}
}
只认两个字段,都是可选的:写哪个改哪个,没写的保持原样。
weight 会夹到 [0, 100],0 等价于关掉。
用户侧配置里的事件 id 只接受小写字母/数字/横杠(挡 __proto__ 之类的怪东西),
大写、下划线、点号一律忽略。
设置写在 %DSH_HOME%\plugins\dsh-lucky-block\config.json,是手写与界面共用的同一个文件
(界面保存走读-改-写,不会抹掉你手写的 model / persona):
{
"sound": true,
"zoom": { "shrinkPercent": 20, "growPercent": 20 },
"model": { "provider": "workbuddy", "model": "deepseek-v4.1-flash", "reasoningEffort": "low" }
}
zoom 是「整页缩水 / 整页变大」的幅度(5~40,缺省 20)。两个方向都不写也可以,
缺省即默认;写坏了(0、999、"abc")会被夹回区间,不会让整页缩到看不见。
localOnly 已经没有了 —— 冷知识/冷笑话现在无条件走本地库,见上面「设置面板」一节。
老配置里留着这个字段不会报错,只是被忽略。
安装(本地开发)
# 方式一:直接指向本目录(plugin-manager 支持本地 spec)
# 在插件市场里「安装」填本目录的绝对路径
# 方式二:npm 打包后按包装
npm pack
装完后不需要动 ~/.dsh/profiles/desktop/cordis.patch.yml:宿主侧条目由本包
package.json 的 dsh.bundle.patch 自动插入,profile 的 patch 里再加一条同 id 的
- insert: 反而会造成组装冲突、让插件加载不上(实测踩过)。
只要确认 ~/.dsh/profiles/desktop/package.json 的 dsh.profile.bundles 里有
"dsh-lucky-block"(插件市场安装会自动加;手动装则加这一行)。
"patchReload": "live"(profile 自带)让 cordis.patch.yml 热生效;但改动
bundles 列表必须重启 dsh才生效。
⚠️ 改
lib/下的任何 JS 都必须重启 dsh。 宿主侧插件代码没有热重载 —— 正在跑的进程内存里持有旧模块,代码改了行为不会变。这个坑骗过一整轮排查: 源码里已经搜不到某段字符串,会话里却还在出现它,最后才发现是进程跑的是旧代码。 客户端那半(lib/client.js)刷新页面即可,但桌面端要重启宿主才会重新收集注入行。
可选配置:给骰子会话单独指定模型
默认不启用,骰子会话直接沿用你的默认模型。想让随机事件跑便宜/快的模型时,
在 %DSH_HOME%\plugins\dsh-lucky-block\config.json 写(这个字段只能手写,设置面板里没有):
{
"model": {
"provider": "workbuddy",
"model": "deepseek-v4.1-flash",
"reasoningEffort": "low"
}
}
reasoningEffort 可省略。provider / model 必须精确匹配可用目录里的名字
(目录拿不到时会跳过切换、不影响掷骰子),不确定就先用 /dice 命令观察日志。
⚠️ 为什么默认关着:宿主只提供会话级
sessionController.selectModel,而它 成功后会把这次选择异步写成 profile 的全局默认模型。也就是说开启本配置后, 第一次掷骰子会顺带把你的默认模型也改成这里写的那个。想恢复就把文件删掉, 再在界面里手动把默认模型选回来。
用法
| 想干什么 | 怎么做 |
|---|---|
| 让 AI 给选择题加幸运方块 | 正常提问,选项末尾自动多一项 |
| 抽一次零成本的(弹窗/蓝屏/签/冷知识/笑话/音乐) | 左键单点方块 / 打 /dice |
| 抽一次要花 token 的(论文/新闻/发明等) | 长按方块挖到碎裂 / 打 /dice |
| 不满意,回去 | 说「回滚」或打 /rollback |
| 方块按钮固定住不挡路 | 右键方块 → 锁定(或双击);锁定后仍可单点/长按触发 |
| 让方块贴边 | 把它拖到离屏幕边缘 48px 以内松手,自动吸附 |
| 改设置 | 右键方块 → 设置… |
| 收起设置面板 | 再右键一次方块(或点面板右上角的 ✕) |
单点零成本、长按才花钱**:单点只在单击池里抽(二十二个事件全是本地/视觉, 不建会话、不调模型);长按只在长按池里抽(七个事件都会开新会话调模型)。 单点是手滑也会发生的动作,所以它不该有成本。
长按阈值 1.4 秒(lib/client.js 的 BREAK_MS)。按住时手指挪动超过 5 像素即判为
「在拖动按钮」并取消挖掘——所以拖位置和挖方块不会打架。松开或移开都会取消挖掘,
纹路和挖掘声立刻一起停。
音效从宿主的 /dsh-lucky-block/sound/*.ogg 按需取(24 个原版音效,共 237 KB),
不内联进客户端 bundle —— lib/client.js 是每次页面启动都要下载的入口,
为只在长按瞬间才用到的音效把它撑大 4 倍不划算。挖掘声每 210ms 一个、几个变体随机轮播
(只听一个会明显「卡带」);同时最多 6 个 Audio 元素,页面切到后台自动静音。
挖掘声跟着材质走(圆石 / 原木 / 铁块 / 南瓜),都是 Minecraft 1.20.1 的原版采样:
| 材质 | 挖掘循环(每 210ms) | 挖穿那一下 |
|---|---|---|
| 圆石(以及没有专门分组的脸) | dig1-4.ogg 四个变体轮播 | dig1-4.ogg |
| 原木 | wood1-4.ogg 四个变体轮播 | wood1-4.ogg |
| 铁块 | metal_step1-2.ogg 两个变体轮播 | metal_break1-2.ogg |
| 南瓜 | 同原木(wood1-4.ogg) | wood1-4.ogg |
南瓜与橡木同声不是偷懒:原版南瓜的声音类型就是 WOOD,挖它和挖原木一个声音。
资源镜像里确实有 block/pumpkin/,但里面只有 carve1-2 —— 那是拿剪刀刻南瓜的声音,
不是方块被挖的声音。
挖穿那一下复用同材质的挖掘采样,这不是偷懒:原版 block.<材质>.break 用的就是该材质
dig/<材质>N 的那几段。金属没有用原版 block/metal/*,因为这张资源镜像里根本没有
metal 目录(1.12.2 / 1.16.5 / 1.20.1 / 默认分支全 404),所以改用同样属于金属音色的
铜块采样(block/copper/step* 与 break*)。
anvil_*.ogg 与 wood_click.ogg / pop.ogg 曾经分别被当成「铁块声」和「原木声」,
现在被 material-sound-check.mjs 明确禁止出现在材质组里 —— 铁砧是重物砸地,
点击音是 UI 反馈,两者都不是「敲方块」。
命令方块 / 幸运方块 / 自定义图片都归到石头那一组:前两个在原版里本质是石头类方块, 而自定义图是一张不知道是什么的图,用最中性的声音最不突兀。
TNT 这张脸已经删掉了。 原版 TNT 硬度是 0,拿着镐子也是碰一下就掉,没有挖掘循环 —— 与其为它单独开一条「按下即碎」的特例,不如干脆不留这张脸(用户 m02957「TNT 删掉吧」)。 少一张脸比多一条分支划算:挖掘流程只有一条,声音表也少一个「借石头凑音」的补丁。
挖穿那一刻的声音分两层,对应原版两个不同的音效事件:
| 时机 | 音效 | 原版对应 |
|---|---|---|
| 每一次挖穿 | 按材质(见上表) | block.<材质>.break |
| 每第 59 次挖穿 | 额外叠一声镐子报废(break.ogg) | entity.item.break |
59 是木镐在 Java 版的耐久(基岩版是 60;本插件用的是 1.20.1 Java 版原版音效,取同一口径)。
也就是说你手上一直有一把木镐,挖 59 下就换一把新的 —— 那声「工具碎了」只在换镐子时响,
不是每次挖穿都响(break.ogg 本来是「工具用坏了」的音,把它当成方块碎裂声来用是错的)。
耐久计数存在 localStorage 的 dshLuckyBlock.pickaxe,与方块外观的位置那份分开,
所以调外观不会顺手把镐子修好。
松手吸附(SNAP_THRESHOLD = 48)只在真的拖动过时生效:鼠标只是滑出方块边缘
(会触发 pointerleave)不会吸附,按下就松手也不会。锁定状态下完全不吸附 —— 锁定
就是不建拖动状态,自然短路。吸附有 160ms 的 left/top 过渡,视觉上是滑过去而不是瞬移。
方块按钮位置、外观、锁定状态存在浏览器 localStorage(键 dshLuckyBlock.block),换机器不跟。
重放某一条事件(调试入口)
POST /dsh-lucky-block/roll 的请求体除了 trigger,还可以带 event:
{ "trigger": "click", "event": "audio-never-give-you-up" }
带了 event 就不抽签,直接跑那一条;它的 prepare()、kind 分支、返回值
形状和随机抽中时完全一样,只有「选中哪一条」这一步不同。所以它既是调试入口
(想连看五次蓝屏就指定 bluescreen),也是自动化测试唯一靠得住的写法。
拒绝的两种情况都会回一句能读懂的话:指定的事件不在这个池子里(提示换
trigger 再试)、根本没有这个 id(提示检查配置里的 events 覆盖层是不是把它
关掉了)。
这条路由不设 token 之外的门槛,也不区分「调试」和「正常调用」—— 只在测试里生效的隐藏分支会悄悄腐坏,而这条分支本身没有任何副作用 (它走的完全是生产路径)。
出错了怎么看
方块头顶的气泡只在报错时出现(正常触发的进度、成功、会话 ID 都不写了 —— 有视觉表演的事件自己会演,没表演的由新会话内容承担反馈)。报错时气泡里是宿主 翻译过的人话,点一下可以关掉,例如:
| 情况 | 气泡说什么 |
|---|---|
插件本体没激活(改动 lib/ 后没重启) | 插件没有注册这个接口,通常是插件本体未激活:重启 dsh 后再试 |
| 登录态过期 | 宿主拒绝了请求(未登录或登录已过期),刷新页面后重试 |
| 默认工作区被删/移走 | 默认工作区目录不存在或已被移动。请在 dsh 里重新确认默认工作区路径后再试 |
| 工作区不可写 | 没有权限写入工作区(被系统或沙箱拦下了) |
config.json 里的模型不在目录里 | 模型不可用:config.json 里指定的 provider/model 不在可用目录中 |
| 都不是 | 意外错误:<宿主的原始信息> |
想看完整堆栈就翻宿主日志(dsh 标准输出)——那边始终打的是原始错误。
音效不会报错。拿不到音效文件、浏览器拦截自动播放、没有可用音频设备 —— 这些一律静默忽略。音效是锦上添花,为它弹一个红色报错气泡是本末倒置。
目录
lib/index.js—— 宿主侧(Node):选项注入、答案识别、回滚、幸运方块事件(分单击/长按两个池)、默认工作区会话创建、可选模型切换、人格层与状态机、本地语料库、音频清单、设置读写、音效文件投放、错误翻译。lib/client.js—— 客户端(浏览器):可拖拽幸运方块按钮(含边缘吸附、锁定)+ 单击/长按分流 + 长按挖掘裂纹 / 碎裂粒子 / 音效(按材质分三组)+ 音频播放与歌词字幕 + 弹窗表演 + 蓝屏屏保 + 十一个动效(跑掉 / 假光标 / 抖动 / 停电 / 假警报 / 掀桌 / 手滑 / 彩虹 / 缩水 / 变大 / 低重力)+ 科乐美秘技 + 设置面板 + 台词气泡 + 方块旁边的黄色小字 + 万圣节变南瓜。lib/persona.js—— 随机事件的人格层(纯函数,可单测):默认人格、覆盖合并、人格段拼装、任务正文保护、收尾点评请求。lib/jokes.js—— 本地笑话库加载器(读jokes-data.json,带 BOM 剥离与只读一次缓存)。lib/jokes-data.json/lib/trivia-data.json—— 本地语料库。lib/novel-words.json—— 「随机短篇小说」的关键词池(约 105 个具体词)。 用具体词而不是抽象词是刻意的:抽到「冰箱 / 遗嘱 / 台风」就必须编出一个故事才用得上, 抽到「孤独 / 时间 / 记忆」只会写成散文。lib/audio-tracks.json—— 音频事件清单(用户目录下的同名文件优先)。lib/audio/—— 音频文件目录,由宿主/dsh-lucky-block/audio/<file>投放。lib/lyrics/—— 时间轴歌词,一首歌一份<文件干名字>.json(同样用户目录优先)。 已内含《Never Gonna Give You Up》副歌 42 秒。lib/sounds/—— 24 个原版 Minecraft 音效(.ogg,共 237 KB),由宿主按需投放。cordis.patch.yml—— 把宿主侧插件插进插件树(客户端那半由package.json的dsh.client自动加载)。
素材出处
方块面与裂纹是原版 Minecraft 像素素材,以 base64 data URI 内联在 lib/client.js 里
(静态 bundle 的 require() 只认包 id、不认相对路径,所以不做成独立模块):
| 素材 | 尺寸 | 来源 |
|---|---|---|
| 方块面 | 16×16 | alexsocha/luckyblock mod 的 lucky_block.png |
| 圆石 / 原木 / 铁块 / 命令方块 / 南瓜 | 16×16 ×5 | 原版资源包 InventivetalentDev/minecraft-assets(Minecraft 1.20.1)的 assets/minecraft/textures/block/:cobblestone / oak_log / iron_block / command_block_front(原图 16×64 四帧,取第一帧)/ carved_pumpkin(有脸的那张,不是 pumpkin_side)。原图留在 lib/textures/,node tools/texture-check.mjs --emit 从那里重生成内联表。TNT 那张(tnt_side)在 m02957 随脸一起删了 |
| 挖掘裂纹 10 帧 | 16×16 ×10 | 原版资源包 PixiGeko/Minecraft-default-assets 的 assets/minecraft/textures/block/destroy_stage_0..9.png |
裂纹用 image-rendering: pixelated 保持硬边,长按时按进度切到下一帧,最后一帧之后碎裂。
之所以找原素材而不是手绘:手绘的裂纹再怎么调都像程序生成的线条,和那张像素方块贴图
放在一起会突兀;换成原版的黑褐裂纹 + 白色反光碎屑,才是游戏里挖方块的样子。
音效是 Minecraft 1.20.1 原版 assets,经 tools/build-sounds.mjs 从 mcasset.cloud 镜像取到 lib/sounds/:
挖掘与碎裂 dig/stone1..4、dig/wood1..4、金属 block/copper/step1..2 与 block/copper/break1..2
(原版 block/metal/* 在那张镜像里不存在,见上面「挖掘声跟着材质走」一段)、
奖励叮 random/glass1..3、工具报废 random/break、外加 random/anvil_land(科乐美与旧版残留)。
版权归 Mojang Studios;本插件只作本地开发期素材镜像,运行时不依赖网络。
试水那首《Never Gonna Give You Up》来自 B 站官方 MV(BV1GJ411x7h7),
只取了 100~142 秒的副歌段转成 Vorbis(44100 Hz / 立体声 / 632 KB)。
抓取脚本 tools/.fetch-bv.mjs 与找高潮的 tools/.loudest.mjs 都留在 tools/ 里
(点前缀 = 不进 npm 包),加歌时按 lib/audio/README.md 走一遍即可。
版权归原权利人;运行时不依赖网络,音频随插件包发布。
默认性格是机器客服口吻 + 音量梗 + 人体数据冷知识,靠语气和句式传达。
实现里刻意不提任何作品名、角色名或制造商,也不靠名字塑造性格 ——
persona-check.mjs 有多条断言盯着这一点。
开发期工具(tools/,安装运行不依赖):
host-smoke.mjs 跑宿主侧回归(含音效路由与白名单)、
persona-check.mjs 人格层单测(真 import)、
snap-check.mjs 吸附纯函数(从 client.js 抽源码跑,不会与实现漂移)、
escape-check.mjs ESC 收尾登记表(同样抽源码跑)、
show-check.mjs 同一时刻只播一场表演的计数与四类收场接线(同样抽源码跑),并且逐个扫 12 个动效函数体,不许读组件作用域的变量(nodeRef 这类名字在模块里不存在,读了就是一抽到就报「方块启动失败」的 ReferenceError)、
visual-run-check.mjs 停电 / 假警报 / 掀桌 / 手滑 / 彩虹 / 缩水 / 变大 / 低重力八个动效的真执行验证(假 DOM + 假定时器按时间线推到每个检查点;收场漏删层、漏 unregister、漏 clearTimeout、把方块留在「藏起来」或「正在飘」的状态里、把整页留在缩放状态里,都会红)、
panel-check.mjs 设置面板分页与分组的纯函数(从 client.js 抽源码跑:两个池不许互相漏、排序按出厂权重所以改数字不跳位、占比分母是本池合计、脏数据不产生 NaN、分组的子行摊成参数也要全部渲染)、
zoom-check.mjs 整页缩放幅度的区间与接线(从 index.js 抽源码跑:0/999/"abc" 都被夹回 5~40 而不是让整页消失、两个方向互不影响、配置真的走到了两条事件的 factor 里、改完确实会重建事件池)、
save-feedback-check.mjs 保存按钮的三态回执(从 client.js 抽 panelSaveButton 用假 h() 跑:待命 / 保存中禁用压暗 / 已保存绿 / 失败红、两个按钮的回执互不串台、saving 在发请求之前点亮、ok/fail 1.8 秒后回待命)、
look-check.mjs 外观三件事(小字抽句与不重样、万圣节日期与「该显示哪张脸」的八条分支、四张表彼此对得上 —— 白名单 ↔ 贴图表 ↔ 下拉框 ↔ 材质声音表,外加每个音频文件真的在 lib/sounds/ 下)、
bluescreen-check.mjs 蓝屏十张脸的内容质量(从 index.js 抽数组跑:表情 / 背景色 / STOP 码 / 首句四者两两不重复,每张四行、必有 STOP 行、末行必留 ESC、必说清 60 秒,表情不许带空格 —— 靠空格区分等于没区分)、
texture-check.mjs 五张原版材质真解像素再断言(自己写的最小 PNG 解码器:每张 16×16、圆石是中性灰、原木偏棕且不亮、铁块是亮灰、南瓜是橙的且有刻出来的脸、五张两两不同),并逐字节比对 client.js 里的内联副本与 lib/textures/ 的原图;带 --emit 可从原图重新生成那张表、
lyrics-check.mjs 歌词按时间取句的正确性(同样抽源码跑,含预判窗口的两个边界)、
konami-check.mjs 科乐美秘技的状态机(同样抽源码跑:错一步清零、隔太久清零、不抢按键)、
pickaxe-check.mjs 木镐耐久的边界(同样抽源码跑:第 59 下才换镐子、脏数据不会让镐子永新)、
click-check.mjs 单击判定的边界(同样抽源码跑:右键/中键/未登记的按压一律不触发,左键带拖动的也不触发)、
config-check.mjs 配置读取(含 BOM 坑)、
local-check.mjs 本地语料库与分流、
events-check.mjs 事件覆盖层与第三方事件校验(从 index.js 抽取源码跑)、
verify-sounds.mjs 音效文件 ↔ 客户端清单 ↔ 宿主白名单三方一致性、
material-sound-check.mjs 材质 ↔ 采样族是否真的对得上(木头必须是 dig/wood、金属必须是 copper、南瓜必须与橡木同组、TNT 不许以任何形式回来(材质表 / 白名单 / 下拉框 / instant 分支)、挖穿复用同材质;并明确禁止 anvil_* / wood_click / pop 混进材质组 —— 这三件事只有耳朵能发现,所以写成了断言)、
list-events.mjs 打印事件清单与概率、
merge-trivia.mjs / merge-jokes.mjs 语料合并器(--check 只体检不写盘)、
build-corpus-overview.mjs 重新生成 lib/语料库总览.md(--check 校验是否过期)、
build-sounds.mjs 把原版音效取到 lib/sounds/。
端到端探针(需要先起克隆 profile,流程见 docs/探针流程.md):
probe-sound.mjs、probe-audio.mjs、probe-settings.mjs、probe-visual-ids.mjs、
probe-persona.mjs、probe-local-path.mjs、probe-fortune.mjs、
probe-dice-prompt.mjs。
调研笔记与语料候选文件归档在 docs/research-notes/(考据报告、语料来源、
合并器的候选 JSON)。
已知边界
- 回滚是「fork 到上一版本」:历史会话保留,新分支继续,不会删任何东西。
- 幸运方块开出来的会话不回滚(随机剧本被回滚会变得没意义)。
- 蓝屏屏保只盖 dsh 自己的窗口,不碰系统、不碰其他插件,60 秒或按 ESC 结束。
- 歌词只做了 Never Gonna Give You Up 一首(用户 m18477 点名只做这首)。 其余四首放的时候没有字幕,也不会报错;补齐是纯数据工作,见上面「歌词」一节。
- 方块按钮用 pointer 事件区分两个动作:按下即开始计时(
pointerdown记起点与时间), 挖满 1.4 秒是长按(hold池),中途松手算单击(click池)。 鼠标左键/触摸都行;键盘 Tab 聚焦后 Enter/Space 直接走单击。 单击不含挖掘动画,也不会播放碎裂声。 - 挖掘裂纹用原版
destroy_stage10 帧贴图(透明底),方块碎裂是纯 DOM 的一次白光闪 + 26 个像素碎片;都不受用户主题影响。 - 科乐美秘技:在任意位置连续按
↑ ↑ ↓ ↓ ← → ← → B A,方块会变成金色四秒并播一声碎裂声。 它不占事件池权重(彩蛋不该跟随机事件抢概率),也不吞任何按键 —— 不符合序列的按键一律放行。 - 万圣节:10 月 31 日(本地日期)开盘时方块自己变成南瓜、小字池多出四句, 过了当天自动恢复。只在你没挑过外观时才顶掉默认贴图 —— 选了圆石/原木/铁块/自定义图 的人不会被顶掉;想连默认外观也保住,设置面板里有个开关。它同样不占事件池权重。
- 事件权重/开关的改动保存后立刻生效;方块外观改动也立即生效。
改
lib/下的代码则必须重启 dsh —— 重载配置和重载代码是两件事。
Plugin correlati
dsh-web-ui (dsh-pet)
zhu1090093659/dsh-web-ui
dsh-pet (dsh-pet)
pc2005-cloud/dsh-pet
dsh-ads
nagi-ovo/dsh-ads
dsh-ads
dsh-external/dsh-ads