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

抖音值班助手 kdcadmin/dsh_douyin_tools

DeepSeek Harness
kdcadmin3 天前有提交已提供中文文档扫描:含敏感能力

deepseek自动刷抖音的dsh插件,能读评论,@你的账号,点赞视频和发布视频

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

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

自动兼容检查有告警:未发布到 npm registry,仅可从源码安装。
安装命令 · 需先装 dsh CLI 引擎
dsh plugin --profile web add kdcadmin/dsh_douyin_tools
该包不可直接 npm 安装,改从 GitHub 源安装(明细见下方安装检查)

状态速览

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

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

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

未发布到 npm registry,仅可从源码安装

展开查看检查明细 ↓

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

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

验证方式:npm registry 存在性 + package.json 静态校验 · 最后检查 2026/10/28 20:43:59

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

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

可执行外部命令读写/删除本地文件写入剪贴板(不计风险)发起外部网络请求
查看扫描证据(共 256 处,展示前 75 条)
  • 读取本地文件
    index.js:71
    local = JSON.parse(readFileSync(join(PLUGIN_DIR, 'package.json'), 'utf8')).version || ''
  • 请求外部主机api.github.com
    index.js:77
    const res = await fetch('https://api.github.com/repos/kdcadmin/dsh_douyin_tools/contents/package.json', {
  • 发起网络请求
    index.js:77
    const res = await fetch('https://api.github.com/repos/kdcadmin/dsh_douyin_tools/contents/package.json', {
  • 写入本地文件
    index.js:267
    try { mkdirSync(outDir, { recursive: true }) } catch { }
  • 写入本地文件
    index.js:272
    try { appendFileSync(logFile, line + '\n', 'utf8') } catch { }
  • 读取本地文件
    index.js:277
    const st = statSync(logFile)
  • 写入本地文件
    index.js:279
    try { renameSync(logFile, logFile + '.1') } catch { }
  • 读取本地文件
    index.js:552
    if (existsSync(v)) return { command: v, args: [], via: 'venv' }
  • 读取本地文件
    index.js:574
    const hasCaptchaFlag = () => { try { return existsSync(captchaFlagFile) } catch { return false } }
  • 读取本地文件
    index.js:577
    const hasLoginBrowserFlag = () => { try { return existsSync(loginBrowserFlagFile) } catch { return false } }
  • 读取本地文件
    index.js:578
    const mcpKeyNow = () => { const p = pythonNow(); return `${p.command}|${p.args.join(' ')}|${scriptPath}|${cfg.cdpUrl}|${(hasCaptchaFlag() || hasLoginB
  • 写入本地文件
    index.js:587
    try { mkdirSync(join(stateDir, '.tmp'), { recursive: true }) } catch { }
  • 写入本地文件
    index.js:627
    writeFileSync(dmSentFile, JSON.stringify({ items: items.slice(-300) }, null, 1), 'utf8')
  • 写入本地文件
    index.js:1001
    try { writeFileSync(temp, [...new Set(ids)].join('\n') + (ids.length ? '\n' : ''), 'utf8'); renameSync(temp, replyIndexFile) }
  • 删除本地文件
    index.js:1002
    finally { try { rmSync(temp, { force: true }) } catch {} }
  • 发起网络请求
    index.js:1516
    const res = await fetch(base + '/json/version', { signal: AbortSignal.timeout(timeoutMs) })
  • 执行外部命令来自配置/变量
    index.js:1577
    const child = spawn(exe.path, args, { detached: true, stdio: 'ignore', windowsHide: false })
  • 发起网络请求
    index.js:1628
    const res = await fetch(url, { ...init, signal: AbortSignal.timeout(timeoutMs) })
  • 删除本地文件
    index.js:1967
    try { rmSync(join(stateDir, 'headless-overlay.yml'), { force: true }) } catch {}
  • 执行外部命令来自配置/变量
    index.js:2023
    const child = spawn(dsh.command, args, {
  • 发起网络请求
    index.js:2160
    const res = await fetch(a.base + '/chat/completions', {
  • 发起网络请求
    index.js:2193
    const res = await fetch(String(url), { signal: AbortSignal.timeout(8000) })
  • 删除本地文件
    index.js:3486
    rmSync(full, { force: true })
  • 删除本地文件
    index.js:3513
    try { if (existsSync(mine)) { rmSync(mine, { force: true }); n += 1 } } catch { keep.push(it); continue }
  • 删除本地文件递归
    index.js:3535
    for (const f of readdirSync(cache)) { try { rmSync(join(cache, f), { recursive: true, force: true }); n += 1 } catch { } }
  • 删除本地文件
    index.js:3840
    try { writeFileSync(probe, String(Date.now()), 'utf8'); return true } catch { return false } finally { try { rmSync(probe, { force: true }) } catch {}
  • 发起网络请求
    client.js:26
    const jget = async (p) => asJson(await fetch(API + p, { credentials: 'same-origin' }))
  • 发起网络请求
    client.js:27
    const jpost = async (p, body) => asJson(await fetch(API + p, {
  • 写入剪贴板
    client.js:966
    h('button', { 'data-dy-btn': '', onClick: () => { try { navigator.clipboard && navigator.clipboard.writeText(String(st.report || '')); setMsg('报告路径已复制
  • 发起网络请求
    client.js:1044
    fetch('/api/douyin-duty/log', {
  • 发起网络请求
    client.js:1102
    fetch('/api/douyin-duty/log', { method: 'POST', credentials: 'same-origin',
  • 发起网络请求
    client.js:1280
    fetch('/api/douyin-duty/memory', { credentials: 'same-origin' })
  • 发起网络请求
    client.js:1289
    const r = await fetch('/api/douyin-duty/persona', {
  • 读取本地文件
    lib/hostdeps.mjs:41
    for (const n of readdirSync(join(npm, '@deepseek-ai'))) {
  • 读取本地文件
    lib/hostdeps.mjs:54
    for (const p of readdirSync(join(dshHome, 'profiles'))) {
  • 读取本地文件
    lib/hostdeps.mjs:69
    if (!existsSync(pkgFile)) return null
  • 读取本地文件
    lib/hostdeps.mjs:70
    const pkg = JSON.parse(readFileSync(pkgFile, 'utf8'))
  • 读取本地文件
    lib/hostdeps.mjs:77
    return existsSync(file) ? pathToFileURL(file).href : null
  • 写入本地文件
    lib/actions.mjs:26
    mkdirSync(dir)
  • 写入本地文件
    lib/actions.mjs:27
    try { writeFileSync(owner, JSON.stringify({ pid: process.pid }), { mode: 0o600 }) }
  • 删除本地文件
    lib/actions.mjs:28
    catch (e) { try { rmdirSync(dir) } catch {}; throw e }
  • 读取本地文件
    lib/actions.mjs:33
    const pid = JSON.parse(readFileSync(owner, 'utf8')).pid
  • 删除本地文件
    lib/actions.mjs:36
    if (err.code === 'ESRCH') { unlinkSync(owner); rmdirSync(dir); continue }
  • 删除本地文件
    lib/actions.mjs:36
    if (err.code === 'ESRCH') { unlinkSync(owner); rmdirSync(dir); continue }
  • 删除本地文件
    lib/actions.mjs:46
    if (acquired) { try { unlinkSync(owner); rmdirSync(dir) } catch {} }
  • 删除本地文件
    lib/actions.mjs:46
    if (acquired) { try { unlinkSync(owner); rmdirSync(dir) } catch {} }
  • 执行外部命令来自配置/变量
    lib/process.mjs:4
    const proc = spawn(command, args, { cwd, env, shell: false, windowsHide: true, stdio: ['ignore','pipe','pipe'] })
  • 读取本地文件
    lib/dsh-cli.mjs:10
    if (!existsSync(executable) || !existsSync(join(root,'app.asar')) || !existsSync(shim)) continue
  • 读取本地文件
    lib/dsh-cli.mjs:11
    const text = readFileSync(shim,'utf8')
  • 读取本地文件
    lib/mcp.mjs:17
    const PKG = (() => { try { return JSON.parse(readFileSync(new URL('../package.json', import.meta.url), 'utf8')) } catch { return {} } })()
  • 执行外部命令来自配置/变量
    lib/mcp.mjs:40
    this.proc = spawn(command, args, {
  • 写入本地文件
    lib/state.mjs:49
    try { mkdirSync(dir, { recursive: true }) } catch { }
  • 读取本地文件
    lib/state.mjs:54
    try { return JSON.parse(readFileSync(file, 'utf8')) } catch { return fallback }
  • 写入本地文件
    lib/state.mjs:60
    writeFileSync(temp, JSON.stringify(value, null, 2), { encoding: 'utf8', mode: 0o600 })
  • 写入本地文件
    lib/state.mjs:61
    renameSync(temp, file)
  • 删除本地文件
    lib/state.mjs:62
    } finally { try { unlinkSync(temp) } catch {} }
  • 读取本地文件
    lib/state.mjs:81
    if (!existsSync(from)) { log(`缺文件:${from}(Python MCP 还没放进来,先跳过)`); return { copied: false, reason: 'missing-vendor' } }
  • 读取本地文件
    lib/state.mjs:82
    const incoming = readFileSync(from, 'utf8')
  • 读取本地文件
    lib/state.mjs:83
    if (!existsSync(to)) {
  • 写入本地文件
    lib/state.mjs:84
    copyFileSync(from, to)
  • 写入本地文件
    lib/state.mjs:85
    writeFileSync(hashFile, sha(incoming))
  • 读取本地文件
    lib/state.mjs:89
    const onDisk = readFileSync(to, 'utf8')
  • 写入本地文件
    lib/state.mjs:90
    if (onDisk === incoming) { writeFileSync(hashFile, sha(incoming)); return { copied: false, reason: 'same' } }
  • 读取本地文件
    lib/state.mjs:91
    const previous = existsSync(hashFile) ? readFileSync(hashFile, 'utf8').trim() : ''
  • 读取本地文件
    selfcheck.mjs:94
    check('视频理解原型 run.py 在包内', existsSync(join(pluginDir, 'video-understanding-lab', 'run.py')),
  • 读取本地文件
    selfcheck.mjs:99
    const yml = readFileSync(join(pluginDir, 'cordis.patch.yml'), 'utf8')
  • 读取本地文件
    selfcheck.mjs:108
    const src = readFileSync(join(pluginDir, 'index.js'), 'utf8')
  • 读取本地文件
    selfcheck.mjs:231
    check('家目录建出来了', existsSync(homeDir), homeDir)
  • 读取本地文件
    selfcheck.mjs:232
    check('config.json 落了盘', existsSync(join(homeDir, 'config.json')))
  • 读取本地文件
    selfcheck.mjs:233
    check('duty.md 从 prompts/ 铺过来了', existsSync(join(homeDir, 'duty.md')))
  • 写入本地文件
    selfcheck.mjs:265
    mkdirSync(fakeComfy, { recursive: true })
  • 写入本地文件
    selfcheck.mjs:266
    writeFileSync(join(fakeComfy, 'old.mp4'), '')
  • 写入本地文件
    selfcheck.mjs:268
    writeFileSync(join(fakeComfy, 'newest.mp4'), '')
  • 写入本地文件
    selfcheck.mjs:286
    writeFileSync(fakeCli,
  • 删除本地文件递归
    selfcheck.mjs:383
    try { rmSync(home, { recursive: true, force: true }) } catch { }
扫描于 2026/11/2(1 天前) · 范围 入口闭包 + 发布文件清单 · 来源 GitHub 仓库
涉及外部主机:api.github.com

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

依赖的 DSH / Cordis 模块
@deepseek-ai/schemastery@deepseek-ai/dsh-tools
给这个插件打分还没有人投票↓
用户评分
还没有人投票,来当第一个

README

由 DeepSeek 最新模型翻译生成
dsh-douyin-life(刷抖音) 封面 DSH 插件。每天替使用者刷抖音:刷推荐流 → 看片(转写)→ 读评论 → 挑片写日报,顺手做点赞、评论(可 @ 你)、转发视频给你(附一句话 + 一个对味表情包)、续火花、回私信(含陌生人);需要发片时,也可以把本地渲染好的成片发到抖音(本地可选配置,不绑定任何渲染工具)。 登录:设置页点「浏览器登录」→ 直接弹出带界面的 Chrome 打开抖音登录页,在里面扫码或收短信验证码,登录成功后自动切回后台无头运行(也可以继续用「截图登录」拿二维码)。 装法:从插件市场 / npm 装,或把源码目录 link 进 profile。设置页有「刷抖音」一页(日常 / 刷视频 / 互动 / 私信 / 表情包 / 高级 六个 tab),左侧还有「记忆」页(插件自带记忆 + 人设,不依赖任何记忆插件)。 接口契约见 SPEC.md。发布说明见 PUBLISH.md。 装完先让 agent 做的 5 步(写给模型看的) 这段是给 agent 的执行清单:不跑完这几步,插件「装了但用不了」。按顺序做,别跳。 1. 装 Python 依赖(MCP) —— 插件自带 Python MCP,依赖在 vendor/douyin-mcp/requirements.txt(mcp<2、playwright、faster-whisper 等)。
展开完整文档(README 剩余 1 节)
dsh-douyin-life(刷抖音)

封面

DSH 插件。每天替使用者刷抖音:刷推荐流 → 看片(转写)→ 读评论 → 挑片写日报,顺手做点赞、评论(可 @ 你)、转发视频给你(附一句话 + 一个对味表情包)、续火花、回私信(含陌生人);需要发片时,也可以把本地渲染好的成片发到抖音(本地可选配置,不绑定任何渲染工具)。

登录:设置页点「浏览器登录」→ 直接弹出带界面的 Chrome 打开抖音登录页,在里面扫码或收短信验证码,登录成功后自动切回后台无头运行(也可以继续用「截图登录」拿二维码)。

装法:从插件市场 / npm 装,或把源码目录 link 进 profile。设置页有「刷抖音」一页(日常 / 刷视频 / 互动 / 私信 / 表情包 / 高级 六个 tab),左侧还有「记忆」页(插件自带记忆 + 人设,不依赖任何记忆插件)。

接口契约见 SPEC.md。发布说明见 PUBLISH.md。

装完先让 agent 做的 5 步(写给模型看的)

这段是给 agent 的执行清单:不跑完这几步,插件「装了但用不了」。按顺序做,别跳。

1. 装 Python 依赖(MCP) —— 插件自带 Python MCP,依赖在 vendor/douyin-mcp/requirements.txt(mcp<2、playwright、faster-whisper 等)。
设置页「高级」有一键装(建 venv → 装依赖 → 自检);命令行等价:
python -m venv <家>/.venv
<家>/.venv/Scripts/python -m pip install -r vendor/douyin-mcp/requirements.txt

然后把设置里的 pythonPath 指到 <家>/.venv/Scripts/python.exe。
2. 确认 Chrome —— 用本机已装的 Chrome(插件用 channel=chrome 拉起持久 profile,不额外下载浏览器);没有 Chrome 先装。
3. 登录 —— 让主人点设置页「浏览器登录」(会弹带界面 Chrome,扫码 / 短信验证码都在窗口里完成,成功后自动切回无头);也可以用 dy_login_start 出二维码。
4. 配模型 / 出片目录 —— DSH 里把模型配成能用的(默认 deepseek-account / deepseek-flash);headlessModel 留空=跟随默认模型。要发本地成片才填成片目录(默认空,不填只影响发片)。
5. 自检 + 闸门 —— 跑 dy_selfcheck,ok:true 再用;autoLike / autoComment / autoPublish / autoReply 默认全关,主人明确要才打开。

第一次用:把这份交给你的 agent

装好后别自己在设置页瞎点。把下面这句发给你的 AI:

读一下 dsh-douyin-life 的 docs/AGENT_ONBOARDING.md(在你本机 DSH 的插件目录 node_modules/dsh-douyin-life/docs/ 里),按它把登录、记忆/人设、私信和表情包配好。

那份文档是写给 agent 看的:Chrome 怎么检测/安装/自动打开、扫码和短信验证码怎么登、默认记忆/人设怎么用(不用装记忆插件)、收藏表情包怎么巡检、没有收藏时怎么用关键字兜底、哪些写动作默认关着不许碰、出问题怎么排查。

0.2.1 修复版(2026-10-27)

这次是本地源码修复,未发布 npm、未代替你重启宿主。完整结果见 修复报告,历史问题见 审查记录。

- 所有写动作经过宿主的授权、额度、串行锁及持久化动作记录;未确认回执返回 unknown,保留额度且不自动重发。
- headless 的 Python 工具通过带临时凭证的回环代理调用宿主,复用浏览器与账本。当前桌面 CLI 可自动发现,不再依赖旧安装路径,也不强制放开沙箱。
- 选择数量来自结构化 picks;日报关闭或被编辑不再改变额度。state.count 表示轮数,pickedToday 表示挑片数。
- 新安装默认关闭普通评论和私信回复;已有显式设置保留。私信需要稳定的对方、会话、新入站消息标识。
- 本机模式使用转写摘录模板;自由人设只用于模型模式。本机自动投稿与 @豆包暂未接线,界面明确标注;草稿转正式发布由使用者在创作者中心完成。
- 本轮没有向真实抖音账号发送测试内容。DOM 选择器、消息 ID、平台回执仍需重载后的真机验收。

开发回归:源码目录运行 npm test。回环代理测试用 npm run test:proxy、npm run test:headless;真实 MCP、模型、CLI 验证命令见修复报告。测试不依赖真实账号。

界面

设置页在 DSH → 设置 → 刷抖音,六个 tab:日常 / 刷视频 / 互动 / 私信 / 表情包 / 高级。

记忆和人设是插件自带的,不依赖任何记忆插件:默认记忆写在 <家>/memory/YYYY-MM.md,人设是 <家>/persona.md(+ personaPrompt),下一轮开头会带上最近记忆。
需要的话可以接入你自己的记忆系统(见 docs/AGENT_ONBOARDING.md 的「记忆与人设」一节);不接也完全不影响使用。

日常

日常

刷视频

刷视频

互动

互动上

互动下

私信

私信

表情包

表情包

高级

高级上

高级下

1. 装

npm 上还没有这个包,dsh plugin add dsh-douyin-life 会 404。
用插件目录的绝对路径装,装完重启一次 dsh。
dsh plugin --profile desktop add link:<插件目录的绝对路径>

首次启用后:先 dy_chrome_open 拉起一个带调试端口的 Chrome(默认 http://127.0.0.1:9222),首次在里面扫码 / 验证码登录;连不上就退回插件自己的 profile,用 dy_login_start 出二维码 PNG 扫码。完整步骤见 docs/AGENT_ONBOARDING.md。
登录态只落在本机,不上传任何地方。

「高级」页可以一键把运行环境装好:装主依赖(建 venv) → 装 mcp<2 + playwright(用系统 Chrome,不下载浏览器)→ 环境自检。

2. 家目录

%USERPROFILE%\.dsh\douyin-duty\(或 $DSH_HOME/douyin-duty/)

| 文件 / 目录 | 说明 |
| config.json | 插件自己那份配置(面板/工具写) |
| state.json | {day,count,picked,loggedIn,windows,winTries,commentFails,...} |
| duty.log | 一行一件事(每次工具调用、每轮结果都有一行) |
| out/YYYY-MM-DD.md | 每轮日报(按 ### <视频id> 记) |
| ledger.json | 账本:回过的评论 cid、发过的评论、转发过的视频、私信次数 |
| memory/YYYY-MM.md | 默认记忆(视频观察、私信对话、续火花…) |
| dm-log/YYYY-MM-DD.json | 私信聊天记录(谁、说了什么、回没回) |
| .need-captcha | 验证码标记:有它时浏览器切「带界面」 |
| replied-cids.txt / replied-users.txt | 去重索引(评论 cid / 已回过的用户) |
| thumbs/ | 缩略图(卡片用,30 天自动清) |
| media/ | 看片时抽的音轨 + 转写缓存 .json(音轨每天清,json 留着省得重下) |
| duty.md | 值班章程(用户改过就保留,新版落 duty.md.new) |
| persona.md | 人设(personaPrompt 也在这里) |
| mcp_server.py | Python MCP(从 vendor/ 铺过来) |
| browser/ | Playwright 持久化 profile(登录态在这里) |
| .venv/ | Python 依赖环境(pythonPath 指过来) |
| headless-overlay.yml | 跑一轮时临时叠给 headless 的补丁 |

⚠️ 凭证 / 登录态 / 报告一律落在家里,绝不写进插件包(装进 node_modules 后只读、升级会覆盖)。

3. 工具(35 个 dy_)

读 / 看

| 工具 | 干什么 |
| dy_status | 今天跑没跑、挑了几条、额度、窗口、登录态、CDP、报告路径 |
| dy_config | 读 / 改配置(不给 patch 就是读) |
| dy_whoami | 抖音登录的是谁 |
| dy_feed | 刷推荐流(打开精选→点推荐→拦 /module/feed/),不搜索 |
| dy_hot / dy_hot_videos | 热榜 / 某热点下的视频(不用登录) |
| dy_search | 关键词搜视频 |
| dy_video | 单条视频信息(标题/作者/时长/点赞) |
| dy_comments | 评论(接口优先,图文作品直接 DOM 抠;不编数据) |
| dy_my_posts / dy_my_videos | 我自己发的作品(按天过滤)/ 投稿账本 |
| dy_thumb | 给视频截缩略图 |
| dy_remember | 往 <家>/memory/YYYY-MM.md 记一行 |

动手(都有闸门)

| 工具 | 干什么 | 闸门 |
| dy_like | 点赞 / 取消 | autoLike 或 force: true |
| dy_comment | 发评论 / 回复(可 mention 真人 @;图文作品用 reply_index 按序号回) | autoComment+mentionName / autoCommentPlain,或 force: true |
| dy_share | 转发视频给某人(悬停出分享面板→点那行的「分享」),随后同一会话发「一句话 + 一个表情包」 | 名字在「可以主动发消息的人」里,或 force: true |
| dy_chat_send | 私信发一句(续火花用) | 同上 |
| dy_chat_sticker | 私信发表情包(关键词联想,可 flip 换一批) | 同上 |
| dy_dm_inbox | 只读私信收件箱(谁发的、内容预览) | — |
| dy_dm_poll | 手动跑一次后台秒回巡检(有新的就回一句) | dmAutoReply |
| dy_dm_round | 旧的「看一遍私信回一句」:模板短句 + 每日额度 | dmReply |
| dy_reply_round | 回我自己视频下的新评论 | autoReply |
| dy_reply | 回复某一条指定评论 | autoReply |
| dy_publish | 投稿(video_path 或 latest: true);自动勾「内容由 AI 生成」,默认存草稿 | autoPublish 或 force: true |

维护

| 工具 | 干什么 |
| dy_run | 立刻跑一轮(默认本机直跑,不 spawn headless;mode: "headless" 才起子会话) |
| dy_selfcheck | 真跑一次环境自检(家目录 / Python / MCP + 一次登录检查) |
| dy_cleanup | 立刻清一次:临时垃圾、老缩略图、老日报、日志轮转、媒体音轨、浏览器 Cache |
| dy_chrome_open | 拉起「本人已登录的 Chrome」并开 CDP 端口(之后所有动作都走它) |
| dy_login_start / dy_login_poll | 扫码登录 |
| dy_login_sms / dy_login_sms_submit | 短信验证码登录 |

4. 一轮到底做了什么

谷时窗口(或手动「现在跑一轮」) └─ dy_feed 刷一屏推荐(不搜索) └─ 按时长筛:> 划走阈值 直接跳过;> 跳着看阈值 标记 skim └─ 打分排序 → 一轮只挑「一次挑几条」 └─ 动作轮转(保证一轮里既有转发也有评论,一条视频只做一件事):
· 评论 @ 你 (每天限额 commentPerDay)
· 普通评论(不@) (每天限额 plainCommentPerDay)
· 转发给某人 (每天限额 sharePerDay → 分享 + 一句话 + 一个表情包,同一会话)
· 只点赞 └─ 要「动手」的那几条先真看片:dy_watch 抽音轨 → faster-whisper 转写 └─ 写日报 out/YYYY-MM-DD.md(### <视频id> + 标题/作者/数据/为什么) └─ 动作结果(成功/失败原因)也写进日报,不假装成功

同一巡检定时器(每隔多少分钟看一次评论和私信,默认 30 分钟、±20% 抖动)到点会:

1. 续火花(每天 1 次)
2. 看私信 → 谁发来新消息就回一句(每人 1 条/天、合计 5 条/天)
3. 看评论 → 回我自己视频下的新评论(回评论看几天内的作品,默认 3 天)

5. 看片(转写)

- 页面 currentSrc 或详情接口提供可信 HTTPS 媒体地址;未知时长/长视频不整条下载,短片下载有 64MB 上限。
- ffmpeg 输出 16kHz 单声道 PCM16。短片截取、长片分段共享 watchSeconds 总预算,返回 actual sampledRanges 和 partial。
- 音轨在独立临时目录内生成、合并、转写,任务结束清理。Whisper worker 最多运行 180 秒,超时和取消都会终止进程。
- 缓存路径为 media/<id>.<秒数>.v2.json,读取时校验 schema 与模型。DOUYIN_WHISPER_MODEL 可选择 tiny/base/small;HF_ENDPOINT 可覆盖镜像,模型缓存放在家目录 models。
- 默认 watchBeforeAct 开启时,转写失败就跳过依赖内容的评论/转发,不会假装看完。partial=true 的文案明确写采样。
- 真实 ffmpeg 与缓存 base 模型的本地静音测试已通过;真实抖音取流、页面选择器和发布回执仍需重载后验收。

6. 设置字段(六个 tab)

日常

| 字段 | 默认 | 说明 |
| perDay | 5 | 每天最多挑几条(0 = 不自动刷) |
| peakValley / noon / night | true | 谷时自动刷总开关 / 中午 12–14 / 夜间 00:30–08:30 |
| pickPerRound | 2 | 一次挑几条(别一口气刷完) |
| skipOverMin | 60 | 超过多少分钟就划走 |
| skimOverMin | 5 | 超过多少分钟就跳着看(评论/附言会写明) |

偏好

| 字段 | 默认 | 说明 |
| keywords | [] | 关注关键词(空 = 用章程默认) |
| reportLevel | full | full / brief / off |
| personaPrompt | '' | 模型模式人设;本机模式使用转写摘录模板 |
| memory | true | 是否写记忆 |

互动

| 字段 | 默认 | 说明 |
| autoReply | false | 自动回我自己视频下的评论 |
| replyPerRun / replyPerDay | 3 / 10 | 每次回几条 / 每天最多 |
| replyRandom | true | 随机挑(关 = 按顺序) |
| replyOncePerUser | false | 同一个人只回一次 |
| replyIntervalMin | 30 | 每隔多少分钟看一次评论和私信(±20% 抖动) |
| replyVideoIds | '' | 要回的自己的视频 id(一次性/长期看 replyOneShot) |
| replyWorksDays | 3 | 回评论看几天内的作品 |
| skipLive | true | 遇到直播就跳过 |
| autoLike | false | 自动点赞 |
| autoComment + mentionName | false / '' | 评论并 @ 你 |
| commentPerDay | 10 | 每天最多几条 @ 评论 |
| autoCommentPlain / plainCommentPerDay | false / 10 | 自动评论(不 @ 人)/ 每天最多几条 |
| shareToNick / sharePerDay | '' / 10 | 转发给谁 / 每天最多转发几条(与评论互斥) |
| shareNote | '' | 转发时附带的一句话(空 = 按看片内容/类型自动生成) |
| msgAllowTo | '' | 可以主动发消息的人(昵称逗号分隔;空 = 谁都不主动发) |
| dmReplyOnlyTo | '' | 只回谁的私信(昵称,逗号分隔,可多人;空 = 谁发来都回) |
| dmReply | false | 自动回陌生人私信(模板短句) |
| dmAutoReply / dmAutoIntervalSec | true / 15 | 后台秒回白名单私信 / 多少秒看一次 |
| dmReplyPerContact / dmReplyPerDay | 1 / 5 | 每个联系人每天回几条 / 每天合计 |
| streakNick / streakPerDay | '' / 1 | 续火花发给谁 / 每天几次 |
| askDoubao | false | 保留配置;当前不执行,等授权接线 |
| autoPublish / publishPerDay | false / 1 | 允许手动工具与 headless 投稿 / 每天最多提交几次;本机轮次不自动投稿 |
| publishTitle / publishTags | '' | 标题模板({date} 会替换)/ 默认话题 |
| publishDraft | true | 投稿默认存草稿 |

高级

| 字段 | 默认 | 说明 |
| localRound | true | 「跑一轮」用本机直跑(不 spawn headless) |
| watchBeforeAct / watchSeconds | true / 90 | 动手前先真看片 / 总取样秒数预算 |
| autoClean | true | 每天自动清理一次 |
| pythonPath | python | 显式解释器 / uv → 家目录 .venv → PATH python |
| cdpUrl / cdpPort / chromePath | 9222 / 9222 / '' | 复用「本人已登录的 Chrome」 |
| headlessProfile / headlessModel / overlayMcp / workspace | headless / '' / true / '' | 起子会话时的参数 |
| dshBin | '' | dsh 的 bin.js(空 = 自动探测,含 app.asar 路径) |

每个字段都带 .extra('volatile', true)(否则 settings.update() 报 has no volatile fields)。
新增字段必须同时改四处:Config(schema)、DEFAULTS、clampPatch、client.js 的 load/save,少一处就是「填了没用」——selfcheck.mjs 有断言盯着这件事。

7. HTTP(设置页用)

| 方法 | 路径 | 说明 |
| GET | /api/douyin-duty/status | 状态(config / windows / nextWindow / gates / cdp / doing) |
| GET / POST | /api/douyin-duty/config | 读 / 写配置 |
| POST | /api/douyin-duty/run | 立刻跑一轮(默认本机直跑;返回 picked / actions / error) |
| GET | /api/douyin-duty/thumb?id= | 缩略图(没有就现截) |
| GET | /api/douyin-duty/watched | 刷过的视频(按日期分组,卡片用) |
| GET | /api/douyin-duty/report/latest | 最近一份日报 |
| POST | /api/douyin-duty/login/qr · /login/poll | 扫码登录 |

8. 环境问题怎么降级

| 情况 | 表现 |
| headless 子进程写系统 Temp 被拒(EPERM mkdtemp) | 插件把 TEMP/TMP/TMPDIR 指到 <家>/.tmp;MCP 子进程也带同样变量 |
| 沙箱不让子进程开命名管道(Playwright WinError 5) | headless 经回环代理调用宿主浏览器;回环连接也被限制时明确失败,改用本机模式或配置宿主允许的连接 |
| Chrome 没带调试端口 | dy_status.cdp.ok=false + 提示;dy_chrome_open 拉起来 |
| MCP 起不来 / Python 缺 | 工具返回 {ok:false, error, hint},不抛异常 |
| 抖音没登录 | {ok:false, needLogin:true, hint} |
| 评论发不出去 | 回读确认不到就报失败;连续失败 2 次当轮停止评论(点赞/转发照做) |

加载路径每一步都包 try/catch:插件加载失败比少个功能糟得多。

9. 自检

node selfcheck.mjs # 55 条断言:宿主能加载 / 工具齐(35 个)/ 路由通 / 结构对 / 闸门默认关

10. 最近更新(增量说明 · 2026-10-25 ~ 10-26)

只做补充说明,前面章节的结构和内容没动。

节奏:谷时那一轮并入巡检定时器(只有一个间隔参数)

改成共用 replyIntervalMin(默认 30 分钟,±20% 抖动 → 实际 24~36 分钟)。到点依次做:

谷时窗口内 → 刷一轮(一次挑几条)→ 续火花(每天 1 次)→ 看私信 → 看评论

不再有单独的「每 60 秒查一次表」谷时定时器;不在窗口/额度满时 windowTick() 自己跳过,不会多刷。
设置页那一行也改名为 「每隔多少分钟巡检一次」。

私信:只回指定的人 + 按人设说话

- 新增 dmReplyOnlyTo(设置里叫 「只回谁的私信(可填多个)」):昵称逗号分隔,可填一个或多个;留空 = 谁发来都回。
- 回复不再是那四句硬编码(收到啦[憨笑] 之类),改成人设短句库:问候 / 被夸 / 被凶 / 提问 / 表情 / 晚安 / 兜底 七类,每类 3~4 句随机,单句 ≤20 字、带表情、不写客服话。
- 限额不变:每个联系人每天 1 条、合计每天 10 条(dmReplyPerContact / dmReplyPerDay)。

图文作品:只读不回

图文(note)作品的评论接口不抛数据,靠 DOM 抠;页面上「回复」不是可点元素,所以评论只读、不回复,并在结果里写明理由、不占额度。视频作品正常回复。

稳定性(都是实测踩出来的)

- _goto 容忍抖音常见的 ERR_ABORTED / 连接被重置:重试 2 次,最后用 commit 兜底;
- 共用 Chrome 时会泄漏标签页(实测堆到 39 个)→ 每次动作前 _sweep_tabs 收掉多余标签;
- 私信发送改成最小实现(原内联 JS 里有 const 重复声明,会抛 Assignment to constant variable.);
- 定时器里 maybeClean / dmRound 曾因块级作用域报 is not defined,现在统一「先声明后赋值 + typeof 防御」。

配置字段的两条老规矩(又踩了一次)

新增字段必须同时改四处:Config(schema) / DEFAULTS / clampPatch / client.js 的 load&save。
少写 schema 会这样:设置页保存时弹「已存到插件自己那份(宿主设置层没写成)」,日志里是 Config field "xxx" is not volatile —— 本地照常生效,但设置页刷新会回到旧值。

补充:0.2.1 修复验证(2026-10-27 重启后)

只补充本次修复,不改前面已有说明。

- 重启后自检通过:mode=profile、已登录、搜索正常、ffmpeg + whisper 模型就绪、video-understanding-lab 在位。
- 新装默认走自有 profile(cdpUrl 空);显式 CDP 连不上自动退回;想复用日常 Chrome 才 dy_chrome_open。
- 看片:lab 随包发布;watchSeconds 真正截断(partial=true);缺 lab 时明确报错并跳过依赖看片的互动。
- 私信:只有平台确认的发送才记「已回复」;没回执标 unknown、最多重试一次;秒回按 dmReplyOnlyTo 在 msgAllowTo 内再筛。
- 记忆页:没装可选记忆插件时显示本地「原始记录 + 人设」,不再 404。
- 配置保存失败以本地为准;perDay=0 只停自动刷,手动可跑;多实例端口按家目录派生(DOUYIN_PROFILE_PORT);requirements 补 pillow。
- 验收边界:只读/离线已验;点赞/评论/私信/发布未做真实账号验收,别对外说「全部通过」。
预览分享卡片长什么样 ↓kdcadmin/dsh_douyin_tools
分享 · DeepSeek Harness Hub
kdcadmin/dsh_douyin_tools

deepseek自动刷抖音的dsh插件,能读评论,@你的账号,点赞视频和发布视频

这是你的仓库?认领条目

回到顶部