# 哔哩哔哩（B站）视频上传全流程

> 2026-08-18 实战验证：AI编程实战开发 第二集（208MB）定时投稿成功
> 肌肉记忆：`~/Desktop/AgentLimb-muscle/member.bilibili.com.json`（workflow: bilibili-video-publish-full-flow）
> 专用 skill：`~/.workbuddy/skills/bilibili-video-upload/`

## 投稿页

`https://member.bilibili.com/platform/upload/video/frame`（Chrome 需已登录创作中心）

## 流程总览

```
打开投稿页 → 视频分块注入上传 → 标题 → 简介 → 标签(10个上限)
→ 合集 → 封面 → 创作声明 → 定时发布 → 立即投稿 → 「稿件投递成功」
```

## 1. 视频上传（大文件分块注入）

B 站无插件原生上传工具，用 DataTransfer 注入：

1. Python 读 mp4 → base64 → **4MB/块**
2. 逐块经桥接 API `javascript_eval` 执行 `window.__buf.push("<chunk>")`（208MB ≈ 53 块，约 50 秒）
3. 页面内拼装（无分号 IIFE）：

```js
(()=>{
const b64=window.__buf.join('')
const bin=atob(b64)
const arr=new Uint8Array(bin.length)
for(let i=0;i<bin.length;i++)arr[i]=bin.charCodeAt(i)
const file=new File([arr],'video.mp4',{type:'video/mp4'})
const inp=document.querySelector('input[type=file]')
const dt=new DataTransfer()
dt.items.add(file)
inp.files=dt.files
inp.dispatchEvent(new Event('change',{bubbles:true}))
return 'injected size='+arr.length
})()
```

4. 轮询页面出现「上传完成」

现成脚本：`~/.workbuddy/skills/bilibili-video-upload/scripts/video_inject.py`

## 2. 标题 / 简介

React 受控输入，必须原生 setter：

```js
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set
setter.call(inp,'标题')
inp.dispatchEvent(new Event('input',{bubbles:true}))
```

- 标题：`input[placeholder="请输入稿件标题"]`
- 简介：`.ql-editor`（Quill 编辑器，innerHTML 填 `<p>段落</p>` 后 dispatch input）

## 3. 标签

输入框 `input[placeholder="按回车键Enter创建标签"]`。

每个标签循环（**异步脚本必须无分号**）：

```js
(async()=>{
const P=window['Promise']
const wait=ms=>new P(r=>setTimeout(r,ms))
const inp=[...document.querySelectorAll('input[placeholder="按回车键Enter创建标签"]')].find(e=>e.getBoundingClientRect().width>0)
inp.focus()
const setter=Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype,'value').set
setter.call(inp,'标签名')
inp.dispatchEvent(new Event('input',{bubbles:true}))
await wait(300)
for(const type of ['keydown','keypress','keyup']){
inp.dispatchEvent(new KeyboardEvent(type,{key:'Enter',code:'Enter',keyCode:13,which:13,bubbles:true,cancelable:true}))
}
await wait(400)
})()
```

**规则与坑：**
- 上限 **10 个**，系统自动预选「编程」「实战」占坑
- 必选优先填：`UP不断更`、`B站AI创造公开赛`、`workbuddy`
- **过滤大小写敏感**：`WorkBuddy` 被拦，`workbuddy` 通过
- 合成 Enter 必须带 `keyCode:13, which:13`

**推荐标签栏目（第 4 集踩坑）：** 标签输入框**下方「推荐标签」区域有可点击芯片**。像 `B站AI创造公开赛` 这类官方/活动标签**优先点击对应芯片加入**，比手打可靠（第 4 集手打漏加、靠手动补）。脚本化：遍历推荐标签芯片、文本匹配目标标签即派发 `mousedown+mouseup+click`。建议：常规标签手打，活动类标签用推荐栏点击补齐。

## 4. 合集（专辑）

1. 点击文本「加入合集」按钮 → 弹窗 `.batch-add-season`
2. **关键坑**：对 `.season-enter` 必须派发完整 `mousedown+mouseup+click` 三连序列才能展开下拉 `.season-list`（仅 click 无效；`computer` 工具真实点击其中心坐标也可以）
3. 点 `.season-list .season-item`（文本匹配合集名，如「AI编程实战开发｜用AI做一个CAD图纸自动优化工具」）
4. 点弹窗 `button.bcc-button--primary`（确定）
5. 验证：表单 `.video-season`（可见的那个）显示合集名

## 5. 封面

1. 点 `.cover-empty-pill`（「添加封面」）→ 封面制作弹窗出现 `input[type=file][accept*="image/png"]`
2. 封面 png 同样分块 base64（`window.__cbuf`）→ DataTransfer 注入
3. 等裁剪画布加载（`img,canvas` 可见）
4. 点 `.cover-editor` 内叶子文本「完成」（向上找宽度>40 的可点击容器）派发三连点击
5. 验证：`.cover-img` 的 `backgroundImage` 含 `archive.biliimg.com`

现成脚本：`~/.workbuddy/skills/bilibili-video-upload/scripts/cover_inject.py`

## 6. 创作声明

选项就藏在 `.bcc-select` 组件内部（display 隐藏），**不需要展开下拉**——直接对 `li.bcc-option`（文本含「个人观点」）派发 mousedown+mouseup+click。

验证：`.bcc-select input` 的 value ===「个人观点，仅供参考」

## 7. 定时发布

**系列约定：间隔一天发布，固定晚上 20:00**

1. 点 `.switch-container` 开关（开启后背景色 `rgb(0,161,214)`）
2. 点 `.date-picker-date` → 日历弹出 → 点 `[class*=date-picker-body-item]` 中文本为目标日期的格子
3. **时间选择器是 Vue 滚动吸附组件（受控，曾被卡最久，已解出）**：小时/分钟列类 `.time-picker-panel-select-item`，容器 `.time-picker-panel-select-wrp`（高 224）。列**仅「先点 `.date-picker-date` 再点 `.date-picker-timer`」后才渲染**；单独点 `.date-picker-timer` 只开时区下拉。
   - ⚠️ 仅设 `scrollTop` / 合成点击 / `computer` 点击|滚轮|拖拽 都**不提交值**（显示 `p.date-show` 恒为默认当前时间，如 04:00）。
   - ✅ **可行解法**：在**同一段同步 `javascript_eval`** 里对目标列 ① `w.scrollTop = it.offsetTop - (w.clientHeight/2 - it.offsetHeight/2)` 居中 ② `w.dispatchEvent(new Event('scroll',{bubbles:true}))` ③ `it.dispatchEvent(new MouseEvent('click',{bubbles:true,cancelable:true}))`。显示值在 Vue 下个 tick（约 0.5s）后才刷新，要等 0.5s 再读 `p.date-show` 确认。小时设「20」、分钟设「00」，关面板后值固化。
4. 验证：`.date-picker-date p` 与 `.date-picker-timer p` 文本（应为 20:00）

## 8. 提交

- 点 button 文本「立即投稿」（检查非 disabled）
- 出现「**稿件投递成功**」+ 稿件标题 → 完成
- ⚠️ 发布动作前必须先经用户确认

## 已知限制

- WorkBuddy（驼峰）标签被 B 站敏感词过滤拦截 → 用小写 `workbuddy`
- Chrome PNA 策略阻止页面 fetch 本地 HTTP 服务器，传文件只能分块推送
- `page_snapshot` 在部分场景不可用（非 injectable），DOM 检查一律用 `javascript_eval`
