白鲸台 · dsh-whale-panel

一个带预算观念的 B站 策展助手:按概率认识你,按半衰期遗忘,按峰谷算钱, 只在你划定的额度内替你挑内容。
它不替你经营账号:不刷播放量、不自动点赞投币、不发正文以外的社交动作。 它不和你的账号抢:用的是单独的发言号(扫码登录的那个),你本号照常刷 B站, 两边同时用不会互踢、不会触发异地校验;它看过的片子会在本机台账里标成
source: plugin, 记录页一眼能看出「哪条是它看的」。 它不越界发言:政治 / 政府政策 / 前沿科学三类话题,写死三层闸门,一律不评价。
当前版本:1.26.1 · 许可证:MIT · 宿主:DeepSeek Harness 0.2.x
三句话看懂它:
- 兴趣是学的,而且会衰减——不用你维护关键词,旧兴趣自己淡出。
- 花的是 token 额度,不是「次数」——金额只是上限,时段决定汇率。
- 评论默认关;开了也是「起草→你确认」,且有硬禁区三层拦截。
它不干什么(这几条比功能列表更该先看):
| 它不做 | 为什么 |
|---|---|
| 不刷播放量、不自动点赞投币 | 这些是不可逆的社交痕迹,不该替你做 |
| 不替你经营账号 | 定位是「潜在水下替你看,值得才探头叫主人」,不是「替你涨粉」 |
| 不承诺站外什么都能播 | 受 UP 主嵌入开关与防盗链限制,这里只保证状态可观测 |
| 不评价政治 / 政府 / 前沿科学 | 三层闸门写死在代码里(见「评论条例」一节) |
截图(欢迎 PR)
📷 我这边不方便录屏,所以先留位:
docs/screenshots/已开好,欢迎社区提交截图 / 录屏。 想看的样子:学习页画像的字号变化、运行页的「金额 → token」实时换算、 监测台事件流、标题栏总开关拨到「已关闭」、记录页「它看过的 N 条」。
怎么截、遮哪些隐私、命名规则见 docs/screenshots/README.md。
截图请务必打码 UID / 昵称 / 二维码——那是账号,不是演示素材。
📖 面向使用者的完整说明(安装、五个页签、五个公式、接口清单、已知边界、B站 接口实测) 见
docs/使用手册.md;开发踩坑见docs/dev-notes.md。 本文件偏工程。
和你的账号是什么关系
这是装之前最该问清楚的,先说结论:插件和你本人不抢同一个会话。
- 两个号分工:插件全程用发言号(就是你扫码登录的那个)去听、去看、去评论; 主人号只被 @ 提醒,插件不会拿它做任何写操作。
- 不互踢:登录拿的是普通 cookie(
SESSDATA/bili_jct),不是设备级独占会话。 你用发言号同时刷 B站 也不会被踢下线,插件也不会因为你在看而任务失败。 - 请求走真实 session:选片、看片、上报都是这个账号的正常请求特征,不是高频接口扫射; 而且插件自己还有「一轮跑完隔 N 秒」的节奏限制。
- ⚠️ 诚实说清代价:插件看过的片子会进发言号的观看历史(这是
history/report的结果, 好处是「看过的不会再推给你」)。B站 服务端的历史我们改不了、也不会去改; 但插件在本机记了一份台账(watchLog,source: plugin), 「记录」页第一条就是「它看过的 N 条」,随时能对上账。 - 不同账号时:你要是拿另一个号登录发言,插件就跟着换,不会串号;状态文件里 cookie 只存当前这一份。
一、它解决什么
| 你的痛点 | 它的做法 |
|---|---|
| 想看点什么,但不想每天自己刷 | 每轮运行按兴趣词去搜 + 混入热门,去重后给你 10–20 条 |
| 关键词是死配置,写一次就过时 | 兴趣画像是学的,而且会衰减:旧的自动淡出,新的顶上来 |
| 不想一直手动点“开始学习” | 每次运行按概率自己决定学不学(概率你定,默认 35%) |
| 想让它在评论区接话,但怕号废 | 评论区默认关;开启后是“起草→你确认”或“自检→少量自主”,有日上限 |
| 怕它偷偷烧钱 | 日上限 + 单次上限两级,超了直接拦;进度条常驻底栏 |
| 用久了没人告诉你它还缺什么 | 留言区会自己攒“我觉得还能加什么”,等你采纳/驳回 |
二、安装与打开
# 在 DSH 会话里让它装(推荐)
plugin_manager install_bundle D:\plugins\dsh-whale-panel
装好后刷新页面,右下角出现圆形「鲸」按钮,点开就是浮窗;拖标题栏可移动,位置和页签会记住。
- 收起后是那个圆按钮,不占版面
- 标题栏从左到右:Logo、标题、登录状态、「N 条待回」、主题切换、收起
三、五个页签
1)学习
- 主人号 UID:只填数字,不需要登录。旁边「查」会拉一次用户卡片确认是谁
- 手动话题:逗号分隔,权重最高(每个 +6),是你直接下命令的那条线
- 补充视频链接:粘 BV 号或链接,最多 20 个,自动解析标题和标签
- 每轮自动去学的概率:0–100%,默认 35%。运行时才触发,不需要手动点
- 兴趣画像:词的大小和透明度按权重走,越重要越大越实;悬停看得见权重和“最近一次出现”
- 旧兴趣淡出的半衰期:默认 7 天,0.5–60 可调;权重掉到地板 1 以下自动删除
- 评论区语气 · 实时:每轮运行自动读两条视频的评论区,算平均字数、表情率、高频词
2)运行
- 平台:B站已接入;小红书/抖音是占位(选了会明说未接入,不会假装干活)
- 发言号:扫码登录,只用于后续写操作;登录态存本地文件
- 峰谷模式:自动判断北京时间是否在 00:30–08:30 谷时。谷时按折扣单价折算,同样的钱能换到更多 token,于是每轮目标条数自动变多;关掉就按原价算
- 预算:输入的是金额,但真正分配的是 token——日上限、单次上限都实时换算成 token 额度显示,已用多少也按 token 记
- 每轮目标条数 / 最多续接次数:目标没跑够会挂起,下次先补缺口;续接到上限就放弃重开
- 评论区:滑动开关;开后有档位——自动发 / 起草后我确认 / 自检 → 少量自主(可设每天 N 条)
- 草稿由模型拟:结合视频标题/简介/UP、兴趣画像、评论区语气统计生成一条完整评论,模型不可用时回退模板;发送前可在文本框改
- @ 主人号概率:默认 50%,只对“兴趣”来源的条目逐条掷骰子
- 刷一轮并挑推荐:主按钮;结果列表点标题会跳到监测台的「预览」
3)监测
- 干了什么:宿主事件流,每秒拉一次,带时间戳,按 info/ok/err 着色
- 预览:封面 + 标题 + UP + 时长 + 播放量(公开接口),一个「在 B站打开」按钮
- 试播:默认不加载 iframe;点「试播(可能受限)」才挂官方播放器,6 秒后给出受限提示
- 开关关掉就不发请求、不占资源;画面与事件流互不影响
站外能否播放由 UP 主开关与防盗链决定。这里不承诺“什么都能播”,只保证状态可观测。
4)留言
- 顶部输入框可以直接留言(Enter 或点「留」)
- 每条留言有 采纳 / 先不用 / ×,下面一行「我的想法…」失焦即存
- 系统会自己往里追加建议(跑满 3 轮提定时、读到评论提语气、画像有了提纠偏)
四、核心机制
4.1 兴趣画像怎么长出来
四条线并行,互不阻塞,各自失败不影响其它:
| 线 | 来源 | 依赖 |
|---|---|---|
| A 关注动态 | feed/all 里主人的投稿 |
发言号登录且已关注主人号 |
| B 公开投稿 | space/wbi/arc/search(WBI 签名) |
无,但常被 -799/-352 限频 |
| C 手动话题 | 你自己写的词 | 无 |
| D 手动链接 | 你粘的 BV 号 → view + tag/archive/tags |
无 |
样本补完标签后加权:标签 ×3、标题分词 ×1、手动话题 ×6。
4.2 衰减公式
旧权重' = 旧权重 × 0.5 ^ (距上次出现的天数 / 半衰期)
新画像 = 旧权重' 与本次观察相加,低于地板(默认 1)的丢弃,按权重取前 14
所以“最近一直在看什么”会迅速占满前列,三个月前的那点兴趣会自然消失,不需要谁去手动清理。
4.3 @ 提醒(单独成条)
正文评论里不带 @。正文发出之后,再按 mentionChance 掷一次骰子,命中就另发一条只表达“想让你看看这个”的短评论(约 10 秒后发,避免同账号连发),并带上 at_name 让被 @ 的人真的收到通知。
两条评论在「记录」页分开打标:@ 提醒带 @ 提醒 标记,接回复流程只处理正文评论下的回复。
额度不够时只发正文、不硬发提醒。
4.4 预算 → token 换算
金额只是上限,真正分配的是 token:
混合单价 = 输入价 × 0.3 + 输出价 × 0.7 (¥/百万 token)
有效单价 = 混合单价 × (峰谷模式开启且处于谷时 ? 折扣 : 1)
日额度 = 日上限 ÷ 有效单价
单次额度 = 单次上限 ÷ 有效单价
每轮目标 = min(目标条数, 单次额度 ÷ 每条估算 tokens)
默认值:输入 ¥2/百万、输出 ¥8/百万、谷时折扣 0.5、每条推荐估算 1200 tokens。
这些都可在 state.json 里调,属于估算模型,不是真实账单。
日上限不要求跑满,剩下的不结转也不浪费;只有累计用量碰到天花板才会拦。
4.5 续接
一轮没跑够目标条数时,把缺口记进 pending(含已续接次数)。
下次运行先补这个缺口,最多续接 maxResume 次(默认 2);到顶就丢弃缺口、重新开始,
避免为了补齐几条而无限来回消耗。
4.6 连播与自愈
「连播」打开后,一趟跑完只隔 cruiseGapSec(默认 45 秒)就自己开下一趟,不再整小时空等;
运行页和监测台都会显示“下一条 N 秒后开跑”。
防卡死五件套:
- 运行锁:进程内锁 +
runningSince落盘,上一轮没跑完时新触发直接返回,不会再叠加两轮。 - 看门狗:
runningSince超过 6 分钟没清,判定卡死并自动回收重来。 - 收尾兜底:
run在finally里清锁、写nextRunAt,并把停在「选片中/连播中」的阶段强行拉回「空闲」。 - 超时:自请求 180 秒、所有 B站 接口 15 秒,网络挂起不会再把整轮无限期拖住。
- 额度/未登录:token 额度用完只把下一趟推迟(不关连播),补上额度立刻恢复;评论日上限用完则整轮都不开、连播睡到次日 00:00;未登录停在「等登录」,扫码后自己继续。
4.7 视频内容取材(1.11.0)
写评论前先弄清楚「这条视频到底在讲什么」,按可信度从高到低取材:
| 层 | 手段 | 接口 | 实测 |
|---|---|---|---|
| ① | B站 字幕(ai-zh 优先) |
x/player/wbi/v2 → data.subtitle.subtitles[].subtitle_url |
接口通(code 0),但未登录/匿名时字幕列表基本是空的,登录后才有内容 |
| ② | 弹幕池 | x/v1/dm/list.so?oid=<cid> |
稳定可用,一条视频能拿到 58–364 条;观众把重点和梗都打在屏幕上 |
| ③ | 关键帧抽帧(多模态) | x/player/videoshot 的雪碧图整张丢给模型 |
弹幕长时间静默时的兜底,一条视频最多 4 张 |
| ④ | 评论区前排 | x/v2/reply |
代表「整体看法」,所以放最后 |
| ⑤ | 只有标题 + 简介 | — | 上面都空时 |
取材结果记在草稿的 material 字段(字幕 / 弹幕 / 画面 / 评论区),监测台会打一行「取材:弹幕 N 条」。
不做本地转写——不为了一条评论让人下 500MB 的 Whisper 模型。
4.8 风格闸(1.11.0)
发出去之前先照镜子,拦的是「一看就不是真人」的写法(全部本地规则,不花 token):
- 套话开头:不得不说 / 说实话 / 总而言之 / 在我看来……
- 总结升华式结尾:「值得每个人深思」「不仅是…更是…」
- AI 写作词堆砌:不仅、更是、体现、传达、态度、内涵……一条里出现两个以上
- 四句等长排比、署名式破折号结尾、超长(> 60 字)
命中就让模型按理由改写一次;改写后再验一遍,不过就这轮不发,压成草稿并写明原因。 开关:评论区设置里「发之前拦 AI 腔」。
4.9 可见性复核(1.11.0)
code: 0 只代表 B站 收下了,不代表别人看得见——新号/敏感内容会被折叠或送审,而且不公开时连通知都不会推(@ 了也白 @)。
发出后 2 分钟用只读接口回查:顶层走 x/v2/reply/main?mode=2&ps=20(按时间第一页),楼中楼走 x/v2/reply/reply?root=。
记录标 public / hidden / unknown;查不到一律说「没查到」,不说「没发出去」(翻页和热度排序都会漏)。
4.10 选片时间窗(1.11.0)
搜索改 order=pubdate + time_start / time_end,交给 B站 服务端筛,而不是拉一页回来本地砍
(按播放排序返回的天然是历史爆款,本地砍完常常一条不剩,表现为「这轮空手」)。
设置项「只挑最近几天的片子」默认 7,0 = 不限。
⚠️ time_range 参数筛的是视频时长不是发布时间,别混。
4.12 真自动播放(1.12.0)
「自动播放」开关(监测台顶部,默认开)打开后:
- 一有新的「正在看」就自动切到试播页、静音开播——不用手点;
- 播完 2.5 秒自动调
next.video换下一条,形成连播; - 界面没开 / 不在这一页时,宿主自己的节奏器按
dwellSec(默认 300 秒 = 5 分钟)往下换,保证一直在刷; - 播放器空态有「从头再放一次」。
⚠️ 诚实说明:画面是真的在放(player.bilibili.com 静音自动播),但进度条仍是本地计时估算——
播放器是跨域的,拿不到内部 currentTime。所以「上报观看进度」也是按这个估算值报的。
4.13 材料分层与抽帧看画面(1.13.0)
写评论/判断兴趣时,材料按可靠性排,四段一起给模型,不再「有字幕就忽略弹幕」:
| 顺序 | 来源 | 代表什么 | 权重 |
|---|---|---|---|
| ① | 字幕 | 视频本人说了什么,最可靠 | 最高 |
| ② | 弹幕 | 现场实时反应,大家笑什么、吐槽什么 | 中高 |
| ③ | 画面抽帧 | 主播不开字幕也不放弹幕时的补充 | 中 |
| ④ | 评论区 | 观众对这条的整体看法,放最后当参照 | 参照 |
抽帧兜底:!subtitle && 弹幕 < 5 条 时触发。
- 调
x/player/videoshot?aid=&cid=&index=1拿故事板 —— 一张 10×10 的雪碧图,每格 480×270,实测约 172 KB; - 直接整张图丢给多模态模型(不用解码、不装 sharp/pngjs),让它 2–4 句说清「这条在讲什么」;
- 宿主没有
attachments、或llm.resolveModelInfo()说这个模型不支持读图 → 明确跳过并写日志,不静默降级; - 模型回「看不出」也算跳过;开关
visionFallback(默认开)。
4.14 契合度与 @ 的门槛(1.13.0)
fitScore() 算一条内容跟主人画像的契合度(0–1):
命中 = 画像词出现在 标题/简介/字幕/画面描述/弹幕 里(包含式匹配)
✗ 不能直接拿 tokenize 切词比 —— 「原神新角色PV」会被切成一个词,跟「原神」永远对不上
raw = Σ min(画像权重, 12) × min(1, 来源权重/2)
score= 1 − e^(−raw/6)
mentionThreshold(默认 0.7):低于它连骰子都不掷。实测分层——
标题命中 0.81 ✓ 字幕命中 0.71 ✓ 只有弹幕 0.39 ✗ 纯画面 0.57 ✗ 完全无关 0.00 ✗
offTopicShare(默认 0.3):发言号每轮选片里允许有多少「跟主人兴趣不沾边」的。
选片时相关的优先、无关的按配额放行,超了丢掉 —— 既不会只刷一种,也不会飘成见啥看啥。
无画像时 fitScore 返回 null(无从判断),退回原来的纯概率行为。
4.11 状态文件安全
state.json 先写临时文件再原子替换,读的时候只有“文件真不存在”才回落默认值,
其余情况重试后报错——登录态和画像不会被一次并发读写成默认值冲掉(1.10.0 修复的事故)。
评论条例(硬规定,写死在代码里)
这几类话题一律不评价、不站队、不调侃:
| 领域 | 举例 | 处理 |
|---|---|---|
| 政治敏感 | 政治体制、意识形态、派系、维稳、审查 | 不评论 |
| 政府话题 | 政府/政策/官方表态、两会、人大、外交 | 不评论 |
| 前沿科学 | 量子计算、核聚变、基因编辑、大模型/AGI、脑机接口、超导 | 不评价研究结论 |
三层拦截,不靠自觉:
- 写之前:
COMMENT_SYSTEM里写明硬性禁区,模型一开始就不往那边写; - 发之前(本地词表):
noCommentScope()用关键词表兜一遍 —— 命中就改写一次,改不掉直接held不发。 这一步不花 token、不受模型心情影响,是真正兜底的; - 自检:
CHECK_SYSTEM也把这三类列为 FAIL 条件,模型再过一道。
另外 视频本身就属于这三类时(标题/简介命中),videoOutOfScope() 会让它整个视频都不评论,
只记录一条「涉「X」不评价」到 scopeSkipped,不往评论区凑。
为什么这么严:这几类话题一旦说错收不回来,而且是别人账号的社交资产。宁可这一条不发。
五、技术构成
dsh-whale-panel/
├─ package.json bundle 清单 + dsh.client(platform/immediately/inject)
├─ cordis.patch.yml 插入一行:id=whale-panel, name=dsh-whale-panel
├─ index.js Host 半边(Node ESM)
├─ client.js Client 半边(浏览器模块,注册到 shell.overlay)
├─ locale/{zh,en}.json 插件市场的标题与描述
└─ icon.svg 插件图标
Host 半边负责:状态持久化、B站 扫码登录、WBI 签名、抓取、画像合并与衰减、评论学习、事件流、HTTP 桥。
Client 半边负责:浮窗渲染(shell.overlay 里的一张卡片)、五页签、主题、拖拽、轮询。
桥:POST /whale-panel/api,action 分发;只接受同源请求(Origin 必须指向 127.0.0.1/localhost),挡掉其它网站的跨站调用。
依赖的宿主服务:webServer(注册路由)。其余全部用标准库 + 全局 fetch。
HTTP 接口
| action | 作用 |
|---|---|
state |
读全量状态(顺带触发一次自动建议生成) |
save |
改配置(白名单字段 + 数值夹取) |
learn |
跑一次学习(四条线并行 + 合并衰减) |
comments.learn |
读最近 2 条视频的评论区,算语气特征 |
master.lookup |
查主人号用户卡片 |
interest.clear |
清空画像 |
login.start / login.poll / logout |
发言号扫码登录 |
run |
选片 + 概率自动学习 + 刷新语气 + 记账 |
monitor |
增量拉事件流(since 游标) |
notes.add / notes.update / notes.remove |
留言区增删改 |
状态文件
%DSH_HOME%\whale-panel\state.json(默认 <DSH_HOME>\whale-panel\state.json),权限 0600。
⚠️ cookie 是明文存的。登录 B站 拿到的
SESSDATA等直接落在这个 JSON 里。 单人本机使用问题不大,但别把这个文件同步到网盘或仓库。
六、已知边界
| 边界 | 说明 |
|---|---|
| 预算不是真实账单 | 按「单价 × 每条估算 tokens」折算,单价与估算值都可配,但不是运营商账单 |
| 峰谷靠系统时间 | 只按北京时间 00:30–08:30 判断,不查询 API 实际计费状态 |
| 站外播放受限 | UP 主可关闭嵌入、防盗链会降级、未登录锁画质;iframe 跨域读不到内部状态,所以只做“试播 + 提示”,不承诺能播 |
| 跨账号读历史不行 | B站 观看历史只有号主能读;跨账号只能靠关注动态/公开投稿/手动 |
| B站 风控 | card/navnum 通;space/wbi/arc/search 常 -799/-352;fav -400;dynamic 412 |
| 事件流只存内存 | 宿主重启即清空,刻意不落盘 |
| 平台只接了 B站 | 小红书/抖音是占位 |
七、开发笔记(踩过的坑)
改了工作目录却看不到变化、面板白屏、状态被并发写坏、B站 返回 -111 ……
这些坑连同根因和正确姿势都整理在 docs/dev-notes.md,共 17 条。
只想装插件的不用看;要改代码,先看那份再动手,能省一整晚。
八、参考与致谢
这个插件里有一部分思路来自社区里两个同类 DSH 插件。代码全部独立实现,没有复制它们的任何源文件:
| 项目 | 协议 | 参考了什么 |
|---|---|---|
| c1overzz/dsh-bili-scroll | MIT | 风格闸(不合口味就拦下不发)、评论发出后复核可见性、真实 @ 通知、改过就保留的「章程」升级 |
| acttk/dsh-bili-interactor | MIT | 先读字幕再说话、daysBack 服务端时间窗筛选、常驻状态条 |
两者都是 MIT:MIT 约束的是代码复制(要求保留版权声明),不约束思路与功能。 本项目没有引用它们的代码,因此不继承其版权声明义务;但把来源写在这里,是应该的。
刻意没有学的三件事:上报观看刷播放量、自动点赞投币(不可逆的社交痕迹)、拉黑/维护黑名单。 这个插件的定位是「潜在水下替你看,值得才探头叫主人」,不是「替你经营账号」。
如果你认为某处实现与上面两个项目过于接近,欢迎开 issue 指出,我会改。
九、许可
MIT。B站 相关接口属于其平台资产,使用请遵守其用户协议;自动化写入操作的风险由使用者自负。
No comments yet. Be the first to write one.