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

B站鱼塘 YOYOLnFu/dsh-bili-pond

DeepSeek Harnessv1.3.13
YOYOLnFu4 天前有提交已提供中文文档扫描:含敏感能力

dsh-bili-pond · B站鱼塘 每天派鱼下去 B站 捞几条值得看的上来,写完日报 @ 你。

42.4/100
综合分
18
GitHub 星标
2,480
npm 周下载
—/5
用户评分

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

安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add dsh-bili-pond
npm 源 · dsh-bili-pond 的发布者账号已校验与本仓库一致

状态速览

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

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

验证判据与等级说明 →
社区实装还没有人打卡 · 装过的第一个就是你
安装兼容性检查✓ 自动检查通过
展开查看检查明细 ↓

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

✓npm 包dsh-bili-pond @ 1.3.13
✓Node 引擎要求 >=22 · 基线 Node 22.19 满足
✓dsh CLI 依赖未声明 dsh 版本约束
✓入口文件main/exports/bin 已声明

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/29 08:44:21

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

静态扫描发现插件具备触达本地系统的能力:执行外部命令、读写/删除本地文件、读取剪贴板或密钥类环境变量(至少其一)。这是能力清单,不是危险判定——具体能力项与证据见下方,安装前请自行判断是否需要。

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 184 处,展示前 105 条)
  • 读取本地文件
    index.js:221
    const cf = [join(stateDir, 'bili_credential.json'), join(pluginDir, 'vendor', 'bilibili-mcp', 'bili_credential.json')].find((p) => existsSync(p)) || '
  • 读取本地文件
    index.js:223
    const c = JSON.parse(readFileSync(cf, 'utf8'))
  • 请求外部主机api.bilibili.com
    index.js:226
    const r = await fetch('https://api.bilibili.com/x/web-interface/nav', {
  • 发起网络请求
    index.js:226
    const r = await fetch('https://api.bilibili.com/x/web-interface/nav', {
  • 读取本地文件
    index.js:243
    try { myVer = String(JSON.parse(readFileSync(join(pluginDir, 'package.json'), 'utf8')).version || '') } catch { myVer = '' }
  • 读取本地文件
    index.js:252
    if (existsSync(win)) return win
  • 读取本地文件
    index.js:255
    for (const d of readdirSync(lib)) {
  • 读取本地文件
    index.js:258
    if (existsSync(p)) return p
  • 写入本地文件
    index.js:289
    writeFileSync(overrideFile, JSON.stringify(cur, null, 2), 'utf8')
  • 写入本地文件
    index.js:311
    writeFileSync(syncFailedFile, JSON.stringify(cur, null, 2), 'utf8')
  • 写入本地文件
    index.js:321
    mkdirSync(outDir, { recursive: true })
  • 写入本地文件
    index.js:325
    try { appendFileSync(logFile, line + '\n', 'utf8') } catch { }
  • 写入本地文件
    index.js:1167
    appendFileSync(reportFileOf(),
  • 执行外部命令来自配置/变量
    index.js:1253
    child = spawn(dsh.command, [...dsh.args, ...extraArgs], {
  • 写入本地文件
    index.js:1743
    try { writeFileSync(f, yml.join('\n'), 'utf8'); return f } catch (e) {
  • 执行外部命令来自配置/变量
    index.js:1959
    const child = spawn(dsh.command, args, {
  • 删除本地文件
    index.js:2017
    try { rmSync(guardQueueFile, { force: true }) } catch { }
  • 执行外部命令来自配置/变量
    index.js:2815
    child = spawn(dsh.command, args, {
  • 删除本地文件
    index.js:2905
    finally { if (probe) { try { rmSync(probe, { force: true }) } catch { } } }
  • 执行外部命令固定命令
    index.js:2960
    const c = spawn('uv', ['--version'], { windowsHide: true, stdio: 'ignore' })
  • 执行外部命令来自配置/变量
    index.js:2974
    const c = spawn(cmd, ['--version'], { windowsHide: true, stdio: 'ignore' })
  • 执行外部命令来自配置/变量
    index.js:2983
    try { c = spawn(cmd, args, { cwd: stateDir, windowsHide: true, stdio: ['ignore', 'pipe', 'pipe'], env: pyEnv() }) }
  • 删除本地文件
    index.js:3036
    try { rmSync(bak, { force: true }) } catch { }
  • 删除本地文件
    index.js:3376
    finally { if (probe) { try { rmSync(probe, { force: true }) } catch { } } }
  • 请求外部主机registry.npmjs.org
    index.js:3702
    const r = await fetch('https://registry.npmjs.org/dsh-bili-pond/latest', { signal: ctrl.signal })
  • 发起网络请求
    index.js:3702
    const r = await fetch('https://registry.npmjs.org/dsh-bili-pond/latest', { signal: ctrl.signal })
  • 发起网络请求
    client.js:50
    const jget = async (p) => asJson(await fetch(API + p, { credentials: 'same-origin' }))
  • 发起网络请求
    client.js:51
    const jpost = async (p, body) => asJson(await fetch(API + p, {
  • 写入剪贴板
    client.js:1018
    await navigator.clipboard.writeText(v)
  • 读取本地文件
    lib/hostdeps.mjs:40
    for (const n of readdirSync(join(npm, '@deepseek-ai'))) {
  • 读取本地文件
    lib/hostdeps.mjs:53
    for (const p of readdirSync(join(dshHome, 'profiles'))) {
  • 读取本地文件
    lib/hostdeps.mjs:68
    if (!existsSync(pkgFile)) return null
  • 读取本地文件
    lib/hostdeps.mjs:69
    const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'))
  • 读取本地文件
    lib/hostdeps.mjs:76
    return existsSync(file) ? pathToFileURL(file).href : null
  • 读取本地文件
    lib/mcp.mjs:16
    const PKG = (() => { try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) } catch { return {} } })()
  • 执行外部命令来自配置/变量
    lib/mcp.mjs:36
    this.proc = spawn(command, args, {
  • 读取本地文件
    lib/verify.mjs:44
    if (!existsSync(f)) return null
  • 读取本地文件
    lib/verify.mjs:46
    const c = JSON.parse(readFileSync(f, 'utf8'))
  • 发起网络请求
    lib/verify.mjs:62
    const r = await fetch(url, { headers })
  • 读取本地文件
    lib/verify.mjs:120
    if (!existsSync(seenFile)) return { checked: [], skipped: 0, text: '⚠️ 判重表还没生成,没什么可复核的' }
  • 读取本地文件
    lib/verify.mjs:124
    const rows = parseSeen(readFileSync(seenFile, 'utf8'))
  • 读取本地文件
    lib/verify.mjs:130
    try { state = JSON.parse(readFileSync(stateFile, 'utf8')) } catch { }
  • 写入本地文件
    lib/verify.mjs:152
    try { writeFileSync(stateFile, JSON.stringify(state, null, 2), 'utf8') } catch { }
  • 读取本地文件
    lib/verify.mjs:168
    if (!existsSync(f)) return null
  • 写入本地文件
    lib/verify.mjs:169
    writeFileSync(f, readFileSync(f, 'utf8').replace(/ *$/, '') + '\n' + text + '\n', 'utf8')
  • 写入本地文件
    lib/state.mjs:47
    mkdirSync(dir, { recursive: true })
  • 读取本地文件
    lib/state.mjs:52
    try { return JSON.parse(readFileSync(file, 'utf8')) } catch { return fallback }
  • 写入本地文件
    lib/state.mjs:56
    writeFileSync(file, JSON.stringify(value, null, 2), 'utf8')
  • 读取本地文件
    lib/state.mjs:67
    if (!existsSync(from)) { log(`缺文件:${from}`); continue }
  • 读取本地文件
    lib/state.mjs:68
    if (!overwrite && existsSync(to)) continue
  • 读取本地文件
    lib/state.mjs:69
    if (overwrite && existsSync(to) && readFileSync(from, 'utf8') === readFileSync(to, 'utf8')) continue
  • 读取本地文件
    lib/state.mjs:69
    if (overwrite && existsSync(to) && readFileSync(from, 'utf8') === readFileSync(to, 'utf8')) continue
  • 写入本地文件
    lib/state.mjs:70
    copyFileSync(from, to)
  • 读取本地文件
    lib/state.mjs:82
    try { if (existsSync(home)) return home } catch { }
  • 写入本地文件
    lib/state.mjs:110
    writeFileSync(home, newText, 'utf8'); writeFileSync(ledger, hShip, 'utf8')
  • 写入本地文件
    lib/state.mjs:116
    if (hLast !== hShip) writeFileSync(ledger, hShip, 'utf8')
  • 写入本地文件
    lib/state.mjs:121
    writeFileSync(fresh, newText, 'utf8')
  • 发起网络请求
    lib/login.mjs:45
    const r = await fetch(SPI, { headers: { 'User-Agent': UA } })
  • 发起网络请求
    lib/login.mjs:54
    const r = await fetch(GEN, { headers: { 'User-Agent': UA } })
  • 发起网络请求
    lib/login.mjs:63
    const res = await fetch(pollUrl(key), { headers: { 'User-Agent': UA }, redirect: 'manual' })
  • 写入本地文件
    lib/login.mjs:79
    mkdirSync(stateDir, { recursive: true })
  • 写入本地文件
    lib/login.mjs:81
    writeFileSync(file, JSON.stringify({
  • 执行外部命令来自配置/变量
    lib/login.mjs:97
    child = spawn(pythonPath || 'python', ['-c', code, url, png], { cwd: stateDir, windowsHide: true })
  • 读取本地文件
    lib/login.mjs:99
    const done = () => { try { resolve(existsSync(png) ? png : null) } catch { resolve(null) } }
  • 读取本地文件
    lib/uvfetch.mjs:33
    return createHash('sha256').update(readFileSync(file)).digest('hex')
  • 发起网络请求
    lib/uvfetch.mjs:45
    const r = await fetch(source.url + '.sha256')
  • 读取本地文件
    lib/uvfetch.mjs:55
    try { entries = readdirSync(dir, { withFileTypes: true }) } catch { return null }
  • 执行外部命令来自配置/变量
    lib/uvfetch.mjs:69
    const r = spawnSync(cmd, args, { stdio: 'ignore' })
  • 读取本地文件
    lib/uvfetch.mjs:83
    if (existsSync(target)) return target
  • 写入本地文件
    lib/uvfetch.mjs:85
    mkdirSync(dir, { recursive: true })
  • 删除本地文件递归
    lib/uvfetch.mjs:87
    rmSync(tmp, { recursive: true, force: true })
  • 写入本地文件
    lib/uvfetch.mjs:88
    mkdirSync(tmp, { recursive: true })
  • 请求外部主机pypi.tuna.tsinghua.edu.cn
    lib/uvfetch.mjs:100
    const r = await fetch('https://pypi.tuna.tsinghua.edu.cn/pypi/uv/json')
  • 发起网络请求
    lib/uvfetch.mjs:100
    const r = await fetch('https://pypi.tuna.tsinghua.edu.cn/pypi/uv/json')
  • 发起网络请求
    lib/uvfetch.mjs:124
    const res = await fetch(s.url)
  • 写入本地文件
    lib/uvfetch.mjs:126
    await pipeline(Readable.fromWeb(res.body), createWriteStream(s.file))
  • 读取本地文件
    lib/uvfetch.mjs:127
    const mb = (statSync(s.file).size / 1048576).toFixed(1)
  • 写入本地文件
    lib/uvfetch.mjs:144
    copyFileSync(hit, target)
  • 写入本地文件
    lib/uvfetch.mjs:145
    try { chmodSync(target, 0o755) } catch { }
  • 执行外部命令来自配置/变量
    lib/uvfetch.mjs:147
    const r = spawnSync(target, ['--version'], { stdio: 'ignore' })
  • 删除本地文件递归
    lib/uvfetch.mjs:150
    rmSync(tmp, { recursive: true, force: true })
  • 删除本地文件递归
    lib/uvfetch.mjs:158
    rmSync(tmp, { recursive: true, force: true })
  • 读取本地文件
    lib/history.mjs:60
    const text = readFileSync(mdPath, 'utf8')
  • 读取本地文件
    lib/history.mjs:64
    try { ts = Math.floor(statSync(mdPath).mtimeMs) } catch { ts = Date.now() }
  • 读取本地文件
    lib/history.mjs:265
    if (!existsSync(p)) return []
  • 读取本地文件
    lib/history.mjs:267
    for (const line of readFileSync(p, 'utf8').split(/ ? /)) {
  • 读取本地文件
    lib/history.mjs:287
    if (existsSync(p)) {
  • 读取本地文件
    lib/history.mjs:288
    const size = readFileSync(p).length
  • 写入本地文件
    lib/history.mjs:290
    try { renameSync(p, p + '.1') } catch { }
  • 写入本地文件
    lib/history.mjs:303
    writeFileSync(p, lines)
  • 写入本地文件
    lib/history.mjs:314
    writeFileSync(historyFile(stateDir), list.length ? list.map((e) => JSON.stringify(e)).join('\n') + '\n' : '')
  • 写入本地文件
    lib/history.mjs:324
    try { writeFileSync(cacheFile(stateDir), JSON.stringify(cache)) } catch { }
  • 请求外部主机api.bilibili.com
    lib/history.mjs:337
    const r = await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${e.bvid}`, {
  • 发起网络请求
    lib/history.mjs:337
    const r = await fetch(`https://api.bilibili.com/x/web-interface/view?bvid=${e.bvid}`, {
  • 读取本地文件
    lib/likes.mjs:22
    if (!existsSync(p)) return []
  • 读取本地文件
    lib/likes.mjs:24
    for (const line of readFileSync(p, 'utf8').split(/ ? /)) {
  • 读取本地文件
    lib/likes.mjs:38
    if (existsSync(p) && statSync(p).size > 2 * 1024 * 1024) renameSync(p, p + '.1')
  • 写入本地文件
    lib/likes.mjs:38
    if (existsSync(p) && statSync(p).size > 2 * 1024 * 1024) renameSync(p, p + '.1')
  • 读取本地文件
    lib/likes.mjs:38
    if (existsSync(p) && statSync(p).size > 2 * 1024 * 1024) renameSync(p, p + '.1')
  • 写入本地文件
    lib/likes.mjs:40
    appendFileSync(p, JSON.stringify(ev) + '\n')
  • 写入本地文件
    lib/likes.mjs:82
    writeFileSync(likesFile(stateDir), ok.length ? ok.map((e) => JSON.stringify(e)).join('\n') + '\n' : '')
  • 删除本地文件递归
    selfcheck.mjs:45
    try { rmSync(SANDBOX_HOME, { recursive: true, force: true }) } catch { }
  • 写入本地文件
    selfcheck.mjs:46
    mkdirSync(SANDBOX_HOME, { recursive: true })
  • 读取本地文件
    selfcheck.mjs:58
    const pkg = JSON.parse(readFileSync('./package.json', 'utf8'))
  • 删除本地文件递归
    selfcheck.mjs:118
    try { rmSync(SANDBOX_HOME, { recursive: true, force: true }) } catch { }
扫描于 2026/11/3(0 天前) · 范围 入口闭包 + 全包扫描 · 来源 npm 发布包
涉及外部主机:api.bilibili.com、registry.npmjs.org、pypi.tuna.tsinghua.edu.cn

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

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-tools
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个
v1.3.142026-10-30
· 修:headless 值班时把「模型线路」误报成「未登录 B站」 来自 @Wine90 的 PR #1(已合并)。 · 值班时优先沿用 dsh 界面里的模型线路;没有宿主默认时退回插件设置里的那一条,都拿不到才报「缺凭据 / 线路名不对」 · 「模型线路没通」单独报,不再推着人去重扫 B站 二维码 · 不再直接 import DSH 内部包,改成运行期向宿主取模块,避免版本对不齐 · OAuth 失败后自动清掉过期 token,下一轮正常回到「请扫码登录」 · 密码等敏感值改成运行时才读,dsh plugin export 不会再把它写成明文 · 其它 · 补了 PR #1 的回归校验(test/pr1-selfcheck.mjs):宿主模型线路、插件设置兜底、缺凭据文案、OAuth 过期处理、敏感值是否被静态存储 · 去掉 package.json 与测试文件开头的 BOM(Node 20 会把 JSON 里的 BOM 读成语法错误) 完整版见更新日志里的 1.3.14 一节。
v1.2.22026-10-22
· 值班那一轮,自动沿用你界面里配的模型线路 以前的坑:值班(headless profile)只认「值班侧自己装过」的线路。你在设置页配的自定义线路(providers: 那种,比如自建反代 / 网关)在值班侧根本不存在 —— 下拉里灰着、选不了,还得自己去找包名装过去。 现在:开跑前把宿主 profile 的 cordis.patch.yml 里模型相关的 entry 原样抄进 --patch overlay(dsh --patch 是官方叠加层),线路叫什么、有几条、来自哪个插件都不用管。 · 亮点 · 只抄模型相关的 entry(providers: / models: / baseURL: / apiKeyEnv: 这类),权限、沙箱、UI 配置一概不碰 · 宿主的 agent-default-model 不抄 —— 值班用哪条线路由设置页决定,不会被界面那份悄悄改掉 · 探测结果跟着 overlay 走 —— 下拉里标的「能不能用」是真话 · 认得出「一个插件带多条线路」的写法(providers: 下的映射键,如 workbuddy-local) · 失败一律静默回退 —— 补丁文件读不到、overlay 有问题就照旧开跑,绝不因为这个卡住值班 · 新增设置项 reuseRoutes(默认开) · 修复 · 1.2.1:桌面版宿主进程里拿不到 DSH_PROFILE_DIR(那些 DSH_* 只注入给工具子进程)⇒ 1.2.0 里「从环境变量找界面那份配置」永远返回空,整块功能静默不生效。现在优先从插件自己的位置倒推,env 只作兜底 · 1.2.2:顺着插件位置往上走最多 6 级(兼容 pnpm 的 node_modules/.pnpm/@/node_modules/ 深路径布局),只认 /profiles//cordis.patch.yml 形状的候选,免得撞到包自带的 patch · 没带成时界面会说清楚为什么;/api/bili-pond/models 多返回 routePatch / routeIds / routeCandidates / routeProbe 排错字段 · 升级 dsh plugin --profile desktop add github:YOYOLnFu/dsh-bili-pond 或 npm i dsh-bili-pond@1.2.2 完整变更见 CHANGELOG.md。
数据来自仓库的 GitHub Release,可能与插件实际版本不一致。

README

由 DeepSeek 最新模型翻译生成
dsh-bili-pond · B站鱼塘 蓝色大肥鱼替你看 B 站、写日报、记住你的偏好、写下鱼日记;需要时还能守着评论区替你回话。 自动运行、自动评论默认关闭,要用再开。 DeepSeek Harness 插件。配置一次后,蓝色大肥鱼会自己检索视频、读字幕、本地转写、按你的口味筛选并生成日报。它会用记忆把跨天的偏好和线索串起来,用肥鱼日记记录当天看了什么、为什么挑中,也会把历史视频整理成可按天翻看的封面墙。 它也能拿一个小号守评论区:自己的视频下有人认真提问,就替主人回一句;别人的视频下,只有白名单用户主动 @ 小号时才回。水评、广告、重复评论都不会回。 需要更克制时,可以打开「圈地自萌」:只接视频 UP 主本人的明确邀请,只回楼中楼,不主动起新楼,没有邀请就保持沉默。
展开完整文档(README 剩余 1 节)
dsh-bili-pond · B站鱼塘

蓝色大肥鱼替你看 B 站、写日报、记住你的偏好、写下鱼日记;需要时还能守着评论区替你回话。
自动运行、自动评论默认关闭,要用再开。

DeepSeek Harness 插件。配置一次后,蓝色大肥鱼会自己检索视频、读字幕、本地转写、按你的口味筛选并生成日报。它会用记忆把跨天的偏好和线索串起来,用肥鱼日记记录当天看了什么、为什么挑中,也会把历史视频整理成可按天翻看的封面墙。

它也能拿一个小号守评论区:自己的视频下有人认真提问,就替主人回一句;别人的视频下,只有白名单用户主动 @ 小号时才回。水评、广告、重复评论都不会回。
需要更克制时,可以打开「圈地自萌」:只接视频 UP 主本人的明确邀请,只回楼中楼,不主动起新楼,没有邀请就保持沉默。

| 每天替你看 B 站 | 可配每天几轮与间隔,也能只在谷时跑;手动跑不占自动额度 |
| 按你的口味筛 | 读 AGENTS.md 里的关注方向、排除项和判断标准,不是通用推荐 |
| 真读内容 | 字幕优先;没字幕可本地转写;不靠标题和封面瞎猜 |
| 写过日报 | 渲染 Markdown 日报,并按月份归档,之后还能翻回来查 |
| 看过不重复 | 按视频 BV 号记账;下一轮再搜到同一个视频,直接跳过 |
| 会记忆 | 每轮只留下 2~3 条值得跨天保留的偏好、工具动向和踩坑线索,不把报告抄一遍 |
| 肥鱼日记 | 用大肥鱼自己的语气写当天看了什么、为什么挑中、有什么感受,不是操作日志 |
| 封面墙 | 把历史视频整理成标题、封面、UP、播放量和结论都可见的按天记录 |
| 守评论区跟评 | 自己的视频可代回;别人的视频只回白名单 @;水评和广告跳过 |
| 不刷屏 | 每轮、每天都有上限;同一条评论只回一次,并记录真实发送结果 |
| 圈地自萌 | 只回视频 UP 主本人邀请的楼中楼,普通用户喊话不算,绝不自己起新楼 |
| 模型自选 | 从你 DSH 里真实注册的线路里挑,不写死 |

截图

插件界面就在 DSH 里:可以从「设置 → B站鱼塘」进入,也可以点左侧的「B站鱼塘」图标。
下面几张是实际界面:

| 总览与封面墙 | 记忆 |
| 总览与封面墙 | 记忆 |

| 自动运行与内容筛选 | 封面墙与今日记录 |
| 自动运行与内容筛选 | 封面墙与今日记录 |

1.3.14 新版重点:跟得上你正在用的模型

- 跟随宿主默认模型:设置页没选线路时,值班轮直接抄你在 DSH 里正在用的默认模型(账号登录那条也算),不再硬套 headless 自带的 API Key 线路。
- 不再误报「B站 没登录」:模型线路缺凭据、账号登录没生效这类,现在明确归到「模型线路」,不会让你白扫一次码。
- 谁递的:这条来自社区 PR #1(@Wine90)—— 他把现场报错贴得很清楚,才定位到这一层。

1.3.13 新版重点:会点赞了

- 点赞 / 收藏默认关:要你在设置里亲手打开。打开了也不是「见一个点一个」。
- 必须写清理由:模型得先写一句为什么值得点(至少 6 个字),写不出理由就不许点 —— 工具层直接拒。
- 判据不写死:严格 / 标准 / 宽松三档,外加一行「补充判据」,按你自己的口味调。
- 有额度、有熔断:每轮点赞硬顶 5 条,每天点赞硬顶 10 条、收藏硬顶 8 条;撞上 B站 风控(-352 / -412)自动停 30 分钟,不硬着头皮继续点。
- 能收回来:历史页的卡片上有「已赞」角标,点两次即可撤销(撤销也会真的请求 B 站)。
- 一条视频最多一种表态:点赞和收藏二选一,不做三连、不投币(硬币不可逆)。

1.3.12 新版重点:蓝色大肥鱼开始学会克制

这次更新不只增加功能,也把「什么时候不发言」写进了产品边界:

- 记忆:每轮最多保留 2~3 条真正值得跨天使用的观察,下次筛选时不用从白纸开始。
- 肥鱼日记:一天一篇,用大肥鱼自己的语气写当天看了什么、为什么挑中、有什么感受。
- 封面墙:把日报里的视频变成可翻阅的视觉记录,哪些挑中、跳过、归档和回复过都能看见。
- 圈地自萌:只扫描视频 UP 主本人发出的一级评论,只识别“鱼来”“大肥鱼快来”这类明确邀请;普通观众喊话不算,带“别来 / 不要来 / 别刷”的否定评论直接排除。

圈地模式不会发表新楼。回复固定挂到 UP 邀请评论的楼中楼,同一个账号对同一个 BV + 邀请评论 ID 只回一次;没有邀请就一个字都不发。视频照样看,日报、日记、记忆和私信照常工作,只是不再去没被邀请的评论区里刷存在感。

评论区守护

评论区守护是独立功能,不是圈地自萌。它让一个小号替主人守评论区:

- 自己的视频:有人留下有内容的评论,小号会替主人回一句;不用先 @ 它。
- 别人的视频:只有设置里加入白名单的用户,主动 @ 小号时才回,不会主动去别人评论区串门。
- 不回复什么:纯水、纯表情、哈哈哈、666、前排、广告、加群和链接都会跳过。
- 不会冒充主人:回复里会说明“主人这会儿在忙,我先替他回一句”,并换着句式说。
- 看得见发了什么:设置页有守护记录,扫到什么、跳过什么、真正发出去什么都能核对。

评论区守护默认关闭,要继续打开总开关才会工作。

关于本项目的来源(重要)

本项目基于 dsh-bili-scroll 0.1.10 衍生:

MIT License · Copyright (c) 2026 dsh-bili-duty contributors

但不是 fork,也不跟随上游版本。调度、模型选择、报告渲染、内容筛选、投递逻辑与安全闸均已重写,与原项目不再兼容(配置字段、条目 id、API 路径都变了)。详见「与原版的差异」。

如果你的需求是「大体按原版跑」,请去用原项目;想要下面这些,用这个。

装之前

1. DSH 桌面版(或能跑 dsh 命令的环境),版本 >= 0.1.7-rc.1
2. Node >= 22
3. 一条能用的模型线路:headless 那一轮要调模型。可以用 DSH 账号、API Key,或者 WorkBuddy 这类 provider 插件
4. 装完重启 DSH(插件代码只在启动时加载)

安装

dsh plugin --profile desktop add dsh-bili-pond

装完重启 DSH。

其他装法:

从 GitHub(npm 不可用时)
dsh plugin --profile desktop add github:YOYOLnFu/dsh-bili-pond

从本地目录
dsh plugin --profile desktop add link:/path/to/dsh-bili-pond

第一次使用(3 步)

1. 设置 → B站鱼塘 → 安装运行环境 在插件的数据目录(~/.dsh/bili-duty,见下面「数据都在哪」)里建独立 Python 环境(.venv)并装依赖。几十秒到两分钟。
完成后 pythonPath 会自动指向它。

2. 扫码登录 B站 点「扫码登录」,手机 B站 → 我的 → 扫一扫。
⚠️ 确认时选「记住我 / 长期」,选 24 小时的第二天就得重扫。
凭证只存在本机 <DSH_HOME>/bili-duty/bili_credential.json。

3. 填人设与口味(推荐)设置页「检索与上下文 → 工作区路径」填一个目录,点旁边的「建模板」,它会在那个目录里生成一份 AGENTS.md(我是谁 / 关心什么 / 不关心什么 / 判断标准 / 说话口吻),照着改就行。之后每轮都会把这份文件读进提示词,按它筛内容。
留空 = 按中性语气,不扮演任何人。

然后点「立即运行一轮」看看效果。跑的时候设置页顶部会出现「本轮进度」卡片:当前阶段、已用时、今日已挑几条,运行输出可以展开看。

如果没跑成,卡片里会直接写原因和下一步点哪(比如「模型线路没通 → 去高级设置重选一条」),不用自己去翻日志。

大页面首页还放了常用快捷开关:评论模式(关闭 / 圈地自萌 / 普通评论)、私信、自动运行、评论区守护、自动转写,以及「跑一轮 / 刷新 / 更多设置」。看过、回过、今日 token 和经验也并在这条快捷区里。
评论和私信各自只显示 UID 是否绑定;未绑定私信 UID 时不能直接从首页打开私信,点状态可跳到设置页。
值班、守护和自动刷 B站 使用插件自己的独立会话目录,不会混进 DSH 主界面右侧的「对话任务」。
大页面里的设置采用单列排版,避免不同高度卡片在双列网格里留下空白。左侧只保留顶部图标入口。

设置项

自动运行

| 项 | 默认 | 说明 |
| 自动运行 | 关 | 总开关。关着只能手动跑 |
| 每天最多几轮 | 6 | 0 = 不限 |
| 两轮间隔 | 60 分钟 | 默认 60 ⇒ 每小时最多 1 轮 |
| 只在谷时跑 | 关 | 开启后自动轮只排在谷时窗口(12–14 / 18–09) |

排班规则:五级闸门依次判断 —— 总开关 → 每天挑选配额 → 每天轮数配额 → 两轮间隔 → 谷时窗口。

中午保底:夜间窗口有 15 小时,按间隔能跑 15 轮,容易把额度吃光导致中午轮不到。
所以夜间的最后一轮会扣着留给中午;过了 14:00 中午没跑成则释放,不白等一整天。

手动那一轮不占自动额度(不写窗口标记、不计轮次)。

每轮与每天上限

| 项 | 默认 | 说明 |
| 每轮最多挑几条 | 3 | 一轮里最多挑中几个视频 |
| 每天最多挑几条 | 18 | 全天上限。默认 18 = 6 轮 × 3 条 |
| 每天最多转写几次 | 10 | 熔断。到上限后只挑有字幕的视频 |

⚠️ 默认值必须自洽:每天轮数 × 每轮条数 ≤ 每天上限,否则每天上限先触发,「每天几轮」就成了空头支票。

⚠️ 上限提醒:每天 > 24 条 / 每轮 > 5 条 / 每天 > 12 轮 时卡片顶部会弹警告。
一天 120 条 ≈ 每 12 分钟发一条评论,B站 对高频评论 + 固定 @ 同一人有明确的反垃圾识别,小号很可能当天被封。

内容筛选

| 项 | 默认 | 说明 |
| 只看多长的视频 | 0 ~ 60 分钟 | 0 = 该端不限。长视频转写很慢 |
| 字幕优先 | 开 | 先挑有字幕的,省掉转写 |
| 最多查几条字幕 | 3 | 查完还没字幕,就转写优先级最高的那条(不无限翻页) |

查重规则

- 视频按 BV 号判重:挑中的视频会记进数据目录里的 seen.md。下一轮搜到同一个 BV,直接跳过,不重新读字幕、不重复写日报,也不再次发评论。
- 不是按标题相似度猜:同一个视频以后改了标题,仍然认得出来;换了新 BV,才会当成一条新视频。
- 评论按评论 ID 判重:评论区守护回复过哪一条,就记下对应评论 ID,同一条评论不会重复回。
- “挑中”不等于“已评论”:seen.md 的「看过的视频」只负责视频判重;只有「评论记录」里真正成功的评论,才会阻止后续补评论。
- 记录读不到时不乱猜:如果 seen.md 读取失败,不会把它当成“全部没看过”;这一轮会明确报错并宁可少做,避免重复打扰。
- 想重看某一条:在数据目录的 seen.md 里删掉对应 BV 记录即可。不要随意删除整份文件,否则历史视频可能被重新处理。

普通手动轮和自动值班轮会拿到 bili_seen_read / bili_seen_append;直接运行裸 dsh headless,或运行“守护评论区”轮,不会有这两只工具。

评论区守护

| 项 | 默认 | 说明 |
| 守护评论区 | 关 | 打开后,小号才会替你查看评论 |
| 自己的视频下不用 @ 也回 | 开 | 有内容的评论就代回;关闭后只处理白名单里的人 |
| 每天最多回几条 | 30 | 跨所有视频合计的硬上限,范围 0–500 |
| 白名单 UID | 空 | 只决定“别人的视频下,谁 @ 小号时可以回” |
| 守最近几条视频 | 1 | 最多可调到 10 |
| 每轮最多回几条 | 3 | 防止一次处理过多 |
| 多久扫一次 | 3 分钟 | 调得太短会增加请求和风控压力 |
| 回复带表情 | 开 | 可关闭 |

守护轮只做评论回复,不会顺手搜索视频、写推荐或发私信。回复会精确落到原评论;如果回复失败,下一轮会补,连续失败三次才放弃。

发布与互动

评论和私信是两个独立开关,互不影响 —— 都开就是「评论 + 私信」。

| 项 | 默认 | 说明 |
| 评论模式 | 关闭 | 关闭 / 圈地自萌 / 普通评论 / 普通评论 + 回复楼中楼 |
| 每轮评论上限 | 1 | 0 = 不发评论。上限跟着「每轮最多挑几条」走(挑 3 条就只能设 ≤ 3) |
| 在评论里 @ 谁 | 空 | 填 B站 UID,点「识别」自动带出昵称。留空 = 不发评论 |
| 私信 | 关 | 独立开关。打开后才发私信 |
| 私信收件人 UID | 空 | 只能发给这个人,见下面的安全闸。填完点「识别」可确认发给谁 |

页面上「评论模式」下方会实时写「本轮会做:…」(只生成报告 / 只发公开评论 / 只发私信 / 评论 + 私信),口径与后台判据一致,不用自己推组合。

圈地自萌只认视频 UP 主本人发的一级评论里的明确邀请(例如“鱼来”“大肥鱼快来”),普通观众说同样的话不算。回复固定落到邀请评论的楼中楼,绝不新开楼层;同一账号对同一 (BV, 邀请 rpid) 只回一次,不同账号仍可各自回复同一个邀请。模式开启后,普通评论和 “回复楼中楼”会被同时关掉。

点赞与收藏

两个独立开关,默认都关。打开后,每条被挑中的视频最多给一种表态。

| 项 | 默认 | 说明 |
| 点赞 | 关 | 打开后模型才能点;每次都要带理由 |
| 每轮点赞上限 | 1 | 0~5,硬顶 5 |
| 每天点赞上限 | 3 | 0~10,硬顶 10 |
| 判据档位 | 标准 | 严格 / 标准 / 宽松,选项里各带一句人话 |
| 补充判据 | 空 | 最多 300 字,写在档位之外还想强调的(例如「不赞翻唱」) |
| 收藏 | 关 | 独立开关,收进默认收藏夹 |
| 每天收藏上限 | 2 | 0~8,硬顶 8 |

判断「值不值得点」有两条硬规矩:没读到内容不许点,写不出理由不许点。
点出去之后会记进 likes.jsonl(含撤销历史),面板上能看到「今天已赞 n/cap」,历史页卡片会挂「已赞」角标,点两次可撤销。

高级设置(折叠)

运行模型线路 / 具体模型 / 本地语音转写 / 运行环境 / 数据目录。

安全闸

这个插件会用你的账号说话,所以有几道硬闸:

1. 自动运行、日报评论、评论区守护默认都关,要用再手动打开。
2. 日报评论默认关。要「自动评论」「@ 谁」两个都填了才发。圈地自萌与普通评论硬互斥,开启圈地模式后 MCP 只下发专用扫描 / 楼中楼工具。
3. 私信只能发给设置里那一个 UID。模型传别的 UID 会被直接拒绝(包括视频 UP 主、评论区里的人)。被拒了不许换 UID 重试。
4. 守护评论区默认关。别人的视频下只处理白名单用户的主动 @,不会主动串门。
5. 转写熔断:每天最多 N 次,防止机器一直满负荷。
6. 评论前必须真读内容:字幕、转写原文或足够明确的上下文,不许拿标题简介硬编。
7. 回复有上限、有去重、有回执:每日与每轮都限流,同一条评论只回一次;只有 B 站接口真正发送成功,才会记成“已回复”。
8. 点赞 / 收藏默认关,且必须带理由:理由少于 6 个字、没读到内容、没登录,一律拒绝;被拒的原因会直接回给模型,不是静默失败。
9. 点赞有额度、有熔断、可撤销:每轮 5 条、每天 10 条(收藏 8 条)为硬顶;撞 B站 风控自动冷却 30 分钟;撤销只能撤自己赞过的,点错了能收回来。

跑一轮会发生什么

dsh --profile headless --patch <插件生成的 overlay> <提示词>

那一轮里:

1. 按关键词搜(或按你 AGENTS.md 的口味)
2. 判重(seen.md 按 BV 号记账,看过的不再看)
3. 按时长过滤
4. 字幕优先 → 没有就本地转写(受熔断限制)
5. 判断值不值得 → 挑中 每轮最多挑几条 条
6. 可选:发评论(@ 你)/ 发私信(给你)/ 回复楼中楼 / 点赞或收藏挑中的视频
7. 写日报(markdown)+ 归档(digest/年月.md)

跑的时候能看见进度:设置页顶部会出现「本轮进度」卡片,显示当前阶段(检索 / 读字幕 / 转写 / 写报告 / 发评论)、已用时、今日已挑几条,运行输出默认收起、点开可看。
一轮还没结束时,「立即运行一轮」按钮会变灰并显示已用时 —— 同一时间只允许一轮。

没跑成会说明原因:几秒就退出、模型线路不通、没登录、网络失败这些常见情况,卡片里直接给一句人话和下一步点哪。

工作目录 = 插件的数据目录(~/.dsh/bili-duty,见下一节),那一轮被沙箱关着,只写得进这里。

数据都在哪

插件的运行数据全部集中在一个目录里,下面简称为「数据目录」:

| 系统 | 数据目录 |
| Windows | %USERPROFILE%\.dsh\bili-duty\ |
| macOS / Linux | ~/.dsh/bili-duty/ |

也就是「~/.dsh 这个 DSH 配置文件夹下的 bili-duty 文件夹」。
它跟插件安装位置无关 —— 装插件是在 ~/.dsh/profiles/<profile>/node_modules/ 里,重装或升级插件不会碰数据目录,所以数据不会丢。

那个目录里的东西:

| 文件 | 内容 |
| config.json | 插件自己的配置(以这份为准) |
| duty.md | 值班章程(模型每轮读的规则,你可以改) |
| seen.md | 判重表(看过哪些、发过哪些评论) |
| out/日期.md | 每天的日报 |
| digest/年月.md | 归档(可检索) |
| bili_credential.json | B站 登录凭证(只存本机) |
| duty.log | 事件日志:每轮开始/结束、用时、挑中几条、发布结果(不长) |
| run.log | 最近一轮的原始输出(模型边说边打印的全在里面,每轮清空) |
| .venv | Python 环境 |
| .venv-asr | 转写环境(可选) |
| likes.jsonl | 点赞 / 收藏记账(含撤销历史,按时间追加) |
| like-cooldown.json | 风控冷却(撞上 -352 / -412 后自动生成,到期自愈) |

目录名沿用 bili-duty(从原项目继承)。改目录名会丢失判重表、凭证和历史报告。

与原版的差异

| 方面 | 原版 dsh-bili-scroll | 本项目 |
| 包名 / 条目 id | dsh-bili-scroll / bili-scroll | dsh-bili-pond / bili-pond |
| 自动运行 | 峰谷是总开关,只有窗口内才跑 | 自动是主开关;峰谷降级为可选项 |
| 排班 | 每个窗口跑一次 | 每小时最多 1 轮、可配间隔、中午保底 |
| 配额 | 单一 perDay | 拆成每轮 / 每天两级,默认值自洽 |
| 模型 | 固定跟随 profile | 设置页自选线路与型号,开跑前预检 |
| 时长过滤 | 无 | 有(分钟范围) |
| 字幕优先 | 无 | 有(最多查 N 条) |
| 转写熔断 | 无 | 有(每日上限) |
| 报告 | 纯文本 | markdown 渲染 |
| 日志 | 平铺 | 可折叠 |
| @ 谁 | 填昵称 | 填 UID 自动解析昵称 |
| 私信 | 有配置但工具没放行(发不出去) | 修好 + 只能发给自己 |
| 同步提示 | 回读误报「N 项未能同步」 | 修正(写成功即成功) |

保留的原版部分:vendor/bilibili-mcp/(第三方 B站 MCP,MIT · Copyright (c) 2026 AGI_Ananas)、 lib/ 里的工具函数、以及「插件 + headless 跑一轮」的整体架构。

桌面版 DSH(NEXT / 0.2.x)请注意

桌面版是 Electron 安装包,不是 npm i -g 装的,所以有几处和命令行版不同:

- 跑一轮用的是 Electron 里 spawn 的 node ⇒ 必须带 ELECTRON_RUN_AS_NODE=1,否则它当成"再开一个 Electron 应用"、什么都不干就以 0 退出(插件已处理)。
- dsh CLI 不在 PATH 上 ⇒ 插件会去应用目录里找;找不到会在 duty.log 里明说「请在设置里把 dsh 路径指过去」。
- 改完插件代码要重启 DSH(HMR 不监视 node_modules)。

装不上 / 卡住时

装依赖卡住、或扫码时一直不出二维码

插件会自动下一份 uv 到自己的数据目录(免管理员、不用 PATH),依次试 直连 GitHub → 国内加速代理 → PyPI 镜像,并核对官方 sha256。

如果这条链路全不通:

- 手动放一份 uv.exe 到 %USERPROFILE%\.dsh\bili-duty\bin\ ——插件会用那一份、跳过下载(PATH 里本来就有 uv 的话直接用那个)。
- 或者自己装 Python 3.10+(安装时勾 Add to PATH),再点一次「安装运行环境」。
- 杀软拦了下载:把 %USERPROFILE%\.dsh\bili-duty\bin\ 加白。

装完显示"就绪"但其实跑不起来

pythonPath 没指向 .venv。设置页「高级设置 → 运行环境」里能看到实际路径,应该是 .venv\Scripts\python.exe(Windows)或 .venv/bin/python。

常见问题

「测一下」报 MCP 子进程退出 / No module named 'mcp' 运行环境没装好,或 pythonPath 没指向 .venv。点「安装运行环境」,确认 pythonPath 指向 .venv/Scripts/python.exe。

跑一轮报 MISSING_CREDENTIAL headless profile 没有可用的模型凭证。要么给那个 profile 配好线路,要么在设置页「高级设置 → 运行模型线路」里选一条它真有的(选不到的会置灰)。

一轮跑很久 本地转写很慢(CPU,一条几分钟)。减少关键词、调低每轮条数、或把时长上限设小。

登录失效(发评论返回 -101) B站 cookie 会提前失效。重新扫码即可。

开发

npm run check # 全部四层:双端自检 + 141 条静态断言 + 14 条迁移测试 + 7 条配额钳制测试

拆开跑:

node --import ./tools/register.mjs selfcheck.mjs # 双端预检(不碰真数据目录)
node tools/_check-1016.mjs # 141 条静态断言
node tools/test-migrate.mjs # 老配置迁移(perDay / peakValley / dmMode)
node tools/test-comment-cap.mjs # 评论上限 ≤ 每轮挑中数

npm publish 前会自动跑 npm run check,不过就不发。

改完代码要重启 DSH 才生效(插件代码只在启动时加载,HMR 不监视 node_modules)。

许可

MIT。许可全文见 LICENSE;衍生关系与第三方代码的版权声明见 NOTICE。
内置的 B站 MCP 来自 bilibili-mcp(MIT)。
预览分享卡片长什么样 ↓YOYOLnFu/dsh-bili-pond
分享 · DeepSeek Harness Hub
YOYOLnFu/dsh-bili-pond

dsh-bili-pond · B站鱼塘 每天派鱼下去 B站 捞几条值得看的上来,写完日报 @ 你。

回到顶部