dsh-douyin-publish(视频发布器 · 抖音 / B站)
把一段本地视频,一路送到投稿页的"待发布"状态。
打开投稿页 → 版式自检 → 把文件直接塞进页面自己的上传控件 → 填标题 → 填文案/话题 → 设可见性 → 默认停手。
这是九个作品里唯一的"功能"(其余多是工具):它不止给 AI 加一个动作, 而是替使用者跑完一条链 —— 开页面、交文件、填表、判断该不该发。 也正因为如此,它的默认值必须保守。
0.4.2:B站真正吃文件的是那个"藏着"的 input(2026-10-17 晚,真机)
第二次真机跑,卡在"等了 61 秒编辑表单没出来"。查下来:B站投稿页有三个 input[type=file] ——
一个隐藏的(外面包着 .bcc-upload-wrapper)、一个看得见的按钮里的、一个给字幕的 .txt。
真正驱动上传的是隐藏的那个;塞进看得见的那个,页面一动不动(文件在 input 里,但没人接)。
旧代码按"可见优先"挑,正好挑错。现在:
- 候选按 DOM 先后排,不再按可见性;
- 塞完先看页面有没有往前走(编辑表单出现),没走就换下一个候选试,最多 3 个;
- 回执里写明"塞进了第 N 个上传控件"。
0.4.1:真机上遇到的两个"不是改版"的情况(2026-10-17 晚)
第一次拿真 B站账号跑 v0.4.0,卡在"版式自检"——不是选择器坏了,是两件真事:
- 上传按钮比
input[type=file]晚渲染。旧代码只要看到 file input 就去自检, 而 B站的上传区还在画 → 误报layout-changed。现在会等上传区真的出现(最多 15 秒)。 - B站会提示"本地浏览器存在 N 个未提交的视频 → 继续编辑 / 不用了"。自动化不该接管别人的旧草稿:
现在看到这个提示会点「不用了」,从新稿开始(旧稿原样留着),并在回执里记一笔。
如果页面直接把旧稿恢复成编辑态(没给"不用了"),会明确报
resumed-draft, 让你先去处理那条旧稿——而不是猜成"改版"。
配乐:让用户自己在平台曲库里挑(v0.6.0)
不是本地音乐库。抖音自己的曲库里有搜歌、试听、使用那一套,那是平台的东西, 我们不去复制它 —— 我们只负责把那个面板打开、再往前送一步:
douyin_upload({ ..., pickMusic: true }) // 打开面板 + 聚焦搜索框
douyin_upload({ ..., pickMusic: true, musicSearch: "轻快" }) // 打开面板 + 顺手搜「轻快」
流程是:填完标题/文案/话题/封面 → 点右侧「添加音乐」标签 → 等「选择音乐」侧边面板长出来 → 把「搜索音乐」框聚焦 → 停。剩下的打字/试听/点「使用」由人在浏览器窗口里做 (平台会自己把歌混进视频)。
真机核对的细节(2026-10-18):入口不是文案「添加音乐」,而是右侧那颗 div[class*=preview-button]
标签;点开后的面板里有一个 input[placeholder*="搜索音乐"],它外面盖着遮罩,点不可靠、el.focus() 可靠。
mode=publish 时不打开面板(避免和发布撞在一起);B站的曲库面板还没核对,传了会标 warn 跳过。
musicSearch 是实验性的:我在这台机器上没能稳定把词打进去 —— 脚本改 value(native setter、
execCommand)都能改值,但抖音的搜索只在真实键盘事件时才触发;而插件会话里的按键投递时好时坏。
所以它默认只做"打开面板 + 聚焦",词打不进去时回执会如实说"请你手动在搜索框里输",不会假装成功。
不想点的话,用下面的 bgmPath 走本地混音。
另一条路(可选):bgmPath 给一段本地音频,它会先用 ffmpeg 混好再上传(原视频不动),
bgmVolume 默认 0.35 —— 适合"我就要这一首,别让我点"的场景。
两条默认值是刻意的
| 参数 | 默认 | 要改成别的必须显式说 |
|---|---|---|
visibility |
private(仅自己可见) |
visibility: 'public' / 'friends' |
mode |
draft(不点投稿) |
mode: 'publish' |
发布是不可逆且对外可见的动作。所以这个插件默认只做到"表填好、停投稿前",
你要发,就再说一次。想先看看会发生什么,用 dryRun: true。
平台没有你要的可见性时,插件直接拒绝(visibility-unsupported),
不会"默认当你没说过"然后按公开发出去。
文件是怎么交给页面的(v0.4 的关键改动)
以前靠"真点上传按钮 → 等系统『打开文件』对话框 → 用 UI 自动化填路径", 那条路要碰另一个进程的窗口、会挡住使用者、还得起 PowerShell, 而且它出过一次"看起来成功、其实没交上"的事故(见下)。
现在默认走注入:
- 本机在
127.0.0.1的一个随机端口起一个只读、只服务这一个文件、带随机令牌的小服务; - 页面自己
fetch回来,用DataTransfer造一个真File,塞进页面的input[type=file], 派发input/change; - 之后就是页面自己的上传逻辑在跑 —— 和"人手选文件"完全一样,但不弹对话框、不挡人。
安全边界写死:只绑回环地址、只认一条路由(令牌不对一律 404)、不列目录、用完就关(另有空闲超时兜底)。
什么时候退回系统对话框:文件超过 maxInjectBytes(默认 512MB)、
或注入被页面拦下(CSP/CORS)。退回时会回读校验,读不回来就判失败 —— 不点确认、不假装成功。
它做什么(每一步都留证据)
| 步 | 动作 | 细节 |
|---|---|---|
| ① | 检查视频 | 大小 / 分辨率 / 时长 / 编码(ffprobe,拿不到也不拦) |
| ①' | 配乐(可选) | 给了 bgmPath 就先用 ffmpeg 混一段新视频:原视频有声→与原声混合(bgmVolume 默认 0.35)、无声→BGM 铺满;原视频不动,混音失败直接报错不硬发 |
| ② | 借浏览器会话 | 用自己的会话键,和别的任务不串台 |
| ③ | 打开投稿页 | 平台档案里的地址(抖音 / B站) |
| ④ | 等上传控件 | 轮询到 input[type=file] 出现为止 |
| ④' | 版式自检(投稿页) | 上传控件 / 「上传视频」区域缺一个 → 立刻报 layout-changed 并点名,不硬着头皮跑 |
| ⑤ | 交文件 | 注入(默认);大文件或注入失败才退回系统对话框 |
| ⑥ | 等编辑页 | 按平台的"编辑控件是否出现"判(B站不换 URL,URL 只当旁证) |
| ⑥' | 版式自检(编辑页) | 标题框 / 投稿按钮缺一个 → 同样当场报 layout-changed |
| ⑦ | 填标题 | 整段写入后回读(B站会预填文件名,所以要覆盖而不是追加) |
| ⑦' | 填文案 + 代填话题 | 抖音:文案与 #话题 都进简介,打完回读;B站:文案进简介,话题进「标签」并回车确认 |
| ⑧ | 设可见性 | 抖音按中文标签点 checkbox;B站展开「更多设置 → 可见范围」再选,回读选中态 |
| ⑧' | 设封面帧 | 只有抖音做:打开「选择封面」→ 时间轴挪到 coverTime(默认第 1 秒,第 0 帧常是黑场);设不上只标 warn |
| ⑨ | 投稿,或故意不投稿 | mode=draft 时这一步记 skipped |
失败会分类说清:no-browser / platform-unsupported / visibility-unsupported /
upload-page-timeout / edit-page-timeout / layout-changed / handover / visibility / bgm。
平台
| 平台 | 状态 | 差异 |
|---|---|---|
| 抖音 | ✅ 真机核对过 | 话题写进简介;可见性三档(公开/好友/仅自己可见);封面帧可设 |
| B站 | ✅ 真机核对过(2026-10-17) | 不换 URL就切到编辑态;标题预填文件名;话题进「标签」(回车);可见范围在「更多设置」里(公开可见/仅自己可见,没有好友);封面帧未核对 |
| 小红书 / X | ⛔ 还没核对 | 传 platform="xiaohongshu" 之类会明确拒绝(platform-unsupported),不拿别的平台的选择器去瞎点 |
要扩到别的平台,前提是先真机把那个平台的投稿页走一遍(登录态、上传入口、标题/简介框、
话题落地方式、可见性入口、投稿按钮),再往 lib/platforms.js 补一份档案 ——
这是"给别人用"的底线,不能拿猜的选择器充数。
它依赖什么
- 浏览器插件(借宿主的
ctx.browser服务)—— 没有时明确报错,不会假装能用。 - 可选:
ffprobe(没有也能发,只是少了元信息)。 - 大文件才需要的退路:自带
lib/handover.ps1(与作者的dsh-file-handover同源, 但各作品独立成仓,不互相依赖)。
安装
市场收录后在 DSH 里搜 dsh-douyin-publish 安装。手动安装:
New-Item -ItemType Junction -Path "$env:USERPROFILE\.dsh\profiles\desktop\node_modules\dsh-douyin-publish" -Target "D:\dsh\projects\dsh-douyin-publish"
# profile 的 package.json:dependencies 加 "dsh-douyin-publish": "link:D:/dsh/projects/dsh-douyin-publish"
# 并把 "dsh-douyin-publish" 加进 dsh.profile.bundles(漏了不加载)→ 重启 DSH
发布前先做这两件事:① 装好浏览器插件;② 在浏览器窗口里登录对应平台的创作中心 (登录态存在浏览器 profile 里,一次登录可跨重启复用)。
用法
douyin_upload videoPath=D:\videos\a.mp4 [platform=douyin|bilibili] title="标题" \
[description="文案"] [topics="猫,狗"] [coverTime=1] [visibility=private] [mode=draft|publish] [dryRun]
典型流程:dryRun: true 看一眼 → 正式跑一次(停投稿前)→ 到浏览器里核对 → mode: 'publish' 真发。
已知限制
- 版式绑定:选择器基于真机实测(抖音 2026-10-16、B站 2026-10-17);
平台改版后需要同步更新。插件先自检再动手:关键控件缺一个就当场报
layout-changed并点名缺什么。 - 注入会把文件读进浏览器渲染进程:所以设了
maxInjectBytes(默认 512MB)上限,超过自动退回系统对话框。 - 抖音话题是"联想式"的:代填是把
#话题打字进简介框,是否被转成真标签由页面决定; 所以填完一定回读,读不齐就明说"请在页面上看一眼",不假装成功。 - B站标签上限 20 字:超长的话题会被页面截断,回执里会提醒。
- 封面是尽力而为(仅抖音):靠封面弹窗里的时间轴定位;弹窗里没有时间轴时只标 warn、保留默认封面。
- 验证码 / 风控不硬试:检测到就停下请人到浏览器窗口处理,再继续。
- 系统对话框那条退路仅 Windows:注入这条路是跨平台的。
- 不做内容创作:剪片、封面图、文案是内容管线的事(用 ffmpeg 等),这个插件只负责"送上去"。
自测
node test/smoke.mjs # 165 项:假浏览器 + 假 spawn + 真起一个回环小服务;断言编排序列、默认值、失败分类、版式自检、话题/标签、可见性、平台拒绝
License
MIT
No comments yet. Be the first to write one.