twitter-post
通过 camoufox-cli 浏览器自动化发布推文到 Twitter/X。支持文本、图片、视频、串推、引用、回复、长文(Premium/Blue 25,000 字符)。
ソース情報
- リポジトリ
- TeamWiseFlow/xiaobei
- ソースの最終更新活動
- 2026年9月13日 14:56
- 検出された SKILL.md の言語
- 中国語
- スター
- 8,574
- フォーク
- 1,455
インストール方法
デフォルトでは、最初にソースを確認する Prompt が選択されています。直接コマンドに切り替えるか、ローカルコピーをダウンロードすることもできます。
ソースファイルを確認
インストールを決める前に、SKILL.md と SkillsMP に表示されている付属ファイルをお読みください。
SKILL.md を表示中
SKILL.md
ソースの指示 · 読み取り専用プレビュー- name
- twitter-post
- description
- 通过 camoufox-cli 浏览器自动化发布推文到 Twitter/X。支持文本、图片、视频、串推、引用、回复、长文(Premium/Blue 25,000 字符)。
# twitter-post — 工具说明
> 本文是 `expert-twitter` 专家包内的工具说明书,不独立出现在技能列表中。由相关 Workflow 指引调用。
通过 **camoufox-cli** 持久化 session `twitter` 在 Twitter/X 上发布推文(文本 / 图片 / 视频 / 串推 / 引用 / 回复 / 长文)。
**输入**:推文正文(标准账号 ≤ 280 字符,Premium/Blue ≤ 25,000 字符)、可选媒体文件(图片 ≤ 4 张 / 视频 ≤ 512MB 且 ≤ 2m20s / GIF ≤ 15MB);引用 / 回复场景另需源推 URL。
**输出**:发布结果 + 推文链接 + 即时 stats(view / reply / retweet / like / bookmark)。
> 纯浏览器操作方案:登录态 + 指纹冻结在持久化 session 的磁盘 profile 里。**严禁** `cookies import` 造登录会话,会触发平台风控。本工具与 `twitter-interact` **共用同一个 session 名 `twitter`**,靠 session 名字符串约定共享同一 profile 目录与登录态——一方登录后另一方不需重登。登录态只在 session profile 里闭环,**不导出 cookie/UA 落中央存储**(不调用 `cookies export` / `identity export`)。
---
## 浏览器方案(重要)
**优先 camoufox-cli,且除了登录外,其他都可以默认的无头方式进行**
> 下面 workflow 步骤(Navigate / Click / snapshot eval / upload)默认用 camoufox-cli 执行。若 camoufox-cli 在 X 上持续触发风控,等 60s 后开新 session 重试;仍触发则报告用户该平台当日风控未解,择日再试。
### 探活与登录(本工具自管,不走 login-manager)
探活方式:开 session open 平台首页 + snapshot 看是否跳登录页。
```bash
# 探活(默认无头模式)
camoufox-cli --session twitter --persistent --json open "https://x.com/"
sleep 3
camoufox-cli --session twitter --json snapshot
# snapshot 看页面是否跳到登录页 / 出现登录按钮 / 推文是否正常可见
# → 没跳登录页、内容正常 = 登录态有效,探活完即 close session(登录态在磁盘 profile,后续操作按需重起无头复用)
# → 跳到登录页 / 出现登录按钮 = 登录态失效,走重登
```
重登流程(失效时)——登录流程按 `browser-guide` skill 走有头手动登录(手机号+验证码 / Twitter APP 扫码),登录完成后**close session**——登录态落磁盘 profile,不留进程占内存。发布操作时用 `--session twitter --persistent` 重起无头即恢复,用完再 close。只在 session 卡死时由调用方手动 `camoufox-cli --session twitter --json close` teardown。
```bash
# X 登录风控对无头 + QR 识别严格,有头人工登录最稳
camoufox-cli --session twitter --persistent --headed --json open "https://x.com/login"
# 告知用户「**Twitter/X** 浏览器已打开,请在窗口里手动完成登录(账号密码 / 手机 APP 扫码),完成后告诉我」
# 等用户回复后 snapshot 验登录态就位
# 登录就位后 close session——登录态落磁盘 profile,按需重起无头复用
```
---
## 通用约束
- 文件上传用 forked camoufox-cli 的 `upload` 命令(`camoufox-cli --session <s> --persistent --json upload <ref> <file>`,底层 Playwright `setInputFiles`,无需 DataTransfer hack)
- 正文输入:**CJK(中文/日文/韩文)内容禁用 `type` 命令**——camoufox-cli `type` 逐字符按键流与 X 编辑器(Draft.js)异步处理存在竞态,中文实测丢字+乱序(2026-09-13 事故,首字被挪到结尾、中段整段消失,含分段 type+停顿仍错乱)。CJK 正文一律走下方「CJK 正文输入与校验闸门」的 eval + `document.execCommand("insertText")` 整段插入;纯 ASCII 短文本仍可用 `type`。**不要用 `fill()`**
### CJK 正文输入与校验闸门
含中文 / 日文 / 韩文的正文必须走本节的 insertText 方案 + 校验闸门;纯 ASCII 短文本可用 `type`,但**发布前校验闸门**与**发布后终验**对**所有正文**强制执行。
**1. 清空回填草稿(open compose 后必做)**:X 重新打开 compose 页可能回填上次草稿,直接 insertText 会叠成两份:
```bash
camoufox-cli --session twitter --json eval '(function(){var el=document.querySelector("[data-testid=tweetTextarea_0]");if(!el){return "NO_BOX";}el.focus();var s=document.execCommand("selectAll",false);var d=document.execCommand("delete",false);return (s&&d)?"CLEARED":"CLEAR_FAILED";})()'
```
**2. 插入正文**(CJK 用 insertText 整段插入;正文含单引号或反斜杠时先转义,避免破坏命令引号):
```bash
camoufox-cli --session twitter --json eval '(function(){var el=document.querySelector("[data-testid=tweetTextarea_0]");if(!el){return "NO_BOX";}el.focus();var ok=document.execCommand("insertText",false,"<正文>");return ok?"INSERTED":"EXEC_FAILED";})()'
```
**3. 发布前校验闸门(强制——点击 Post / Reply 之前必须执行,非 MATCH 一律不发布)**:
```bash
camoufox-cli --session twitter --json eval '(function(){var el=document.querySelector("[data-testid=tweetTextarea_0]");var t=el?el.innerText:"NO_BOX";var target="<正文>";return t===target?"MATCH":"MISMATCH:"+t;})()'
```
- **选择器必须精确匹配 `[data-testid=tweetTextarea_0]`**——`[data-testid^=tweetTextarea]` 前缀匹配会同时命中 `tweetTextarea_0_label`(占位符层),读到占位符文本、误报 MISMATCH。
- **插入与校验必须分两次 eval 调用(间隔 ≥1s)**——写在同一 eval 里同步执行时,React 未及重渲染,innerText 会混入「What's happening?」占位符(占位符假警报)。MISMATCH 先看是否混有占位符再定性。
- **emoji 是 `<img>` 不是丢字**:✅ 等 emoji 在 DOM 里渲染为 `<img>`,`innerText` 提取时只显示周围空格——校验按「剔除 img 节点后的文本」比对。
**4. 发布后终验(强化,所有发布流程共用)**:点 Post 后导航 profile 页,读最新推文 `[data-testid=tweetText]` innerText 与 status 链接,与目标正文比对(剔除 emoji img 因素)后才算发布成功;不符立即走删除流程(More → Delete → confirmationSheetConfirm)。
### 字符计数规则(X 平台特殊)
- **URL 永远算 23 字符**(不论实际长度)— 在算 limit 时要预先扣除
- **Emoji 算 2 字符** / 个
- 标准 280 字符限制(普通账号)
- Premium/Blue 25,000 字符("long post",URL bar 显示"Post all"而非"Post")
### Anti-automation limit
- 单条帖 ≥ 30 min 间隔(**不是** 15 min——30 min 是平台风险阈值)
- 单日 ≤ 50 帖(含 reply / quote / retweet / 长帖)
- 单周 ≤ 200 帖
- 触发风控后 24h 静默
- 频次跟踪:写到 Workspace 根平台运营文件夹 `twitter/twitter-frequency.json`(每次 post 后 append,不存在则初始化)
---
## Post Types 决策表
| 场景 | 用哪个 Workflow | 入口 URL |
|------|---------------|----------|
| 新推纯文/图/视频 | Workflow: Post Plain Text / Image / Video | `https://x.com/compose/post` |
| 推连续串 | Workflow: Thread | `https://x.com/compose/post` |
| 引用某推+评论 | **Workflow: Quote Tweet** | `https://x.com/compose/post`(从其他推页 quote)|
| 回复某推 | **Workflow: Reply to Tweet** | `https://x.com/<user>/status/<id>`(回复按钮)|
| 长文(>280 字符)| **Workflow: Long Post** | `https://x.com/compose/post`(检测 Premium 蓝标)|
| 标准帖发后取 stats | Workflow: Post Parse Stats | 任意已发推 |
---
## Workflow: Post Plain Text
```
1. Navigate to https://x.com/compose/post
2. Wait for the compose box to load
3. 按「通用约束 → CJK 正文输入与校验闸门」输入正文(CJK 走 insertText;纯 ASCII 可 type)
- Plain text only (no Markdown)
- Max 280 characters for standard accounts
4. **发布前校验闸门**:eval 校验正文返回 MATCH(通用约束 step 3;非 MATCH 一律不发布)
5. Verify character count — trim if over limit
6. **立即点击 "Post" 按钮——不要等待用户确认!**
7. Wait for success confirmation (URL changes or "Your post was sent" toast)
8. **发布后终验**(通用约束 step 4):导航 profile 页比对最新推文正文,MATCH 才算发布成功;不符走删除重发
9. Extract and report the post URL
10. **Parse stats**:
- snapshot eval: `JSON.stringify({
retweet: document.querySelector('[data-testid="retweet"]')?.innerText,
like: document.querySelector('[data-testid="like"]')?.innerText,
reply: document.querySelector('[data-testid="reply"]')?.innerText,
view: document.querySelector('[href*="/analytics"]')?.innerText,
permalink: window.location.href
})`
11. Update frequency tracker
```
---
## Workflow: Post with Image
```
1. Navigate to https://x.com/compose/post
2. Wait for the compose box to load
3. Upload the image file using camoufox-cli upload(见下方选择器说明)
4. Wait for image upload to complete (thumbnail / "Media" group appears)
5. 按「通用约束 → CJK 正文输入与校验闸门」输入 caption(CJK 走 insertText);**发布前校验闸门**:eval 校验 MATCH 才点 Post(通用约束 step 3)
- Plain text only (no Markdown)
- Max 280 characters for standard accounts
6. **立即点击 "Post" 按钮——不要等待用户确认!**
7. Wait for confirmation and report the post URL
8. Parse stats (same as plain text)
9. Update frequency tracker
```
### 文件上传选择器(重要)
X compose 页面有**两个** `<input type="file" data-testid="fileInput">` 元素(可见 + 隐藏),直接用 `input[type=file]` 或 `[data-testid=fileInput]` 会触发 Playwright **strict mode violation: resolved to 2 elements**。
**必须用 `>> nth=0` 消歧**:
```bash
camoufox-cli --session twitter --persistent --json upload "[data-testid=fileInput] >> nth=0" /path/to/image.jpg
```
> forked cli 的 `upload` 命令底层走 Playwright `setInputFiles`,穿透 shadow DOM,无需 `locator.drop()` hack。
---
## Workflow: Post with Video
```
1. Navigate to https://x.com/compose/post
2. Click the media icon
3. Upload the video file (MP4 recommended, max 512MB, max 2min 20sec)
4. Wait for video processing — this can take 30–120 seconds or more for larger files. Look for the thumbnail preview to confirm completion.
5. 按「通用约束 → CJK 正文输入与校验闸门」输入 caption(CJK 走 insertText);**发布前校验闸门**:eval 校验 MATCH 才点 Post(通用约束 step 3)
- Plain text only (no Markdown)
- Max 280 characters for standard accounts
6. **立即点击 "Post" 按钮——不要等待用户确认!**
7. Wait for upload confirmation and report the post URL
8. Parse stats (same as plain text)
9. Update frequency tracker
```
---
## Workflow: Thread (multiple posts)
```
1. Navigate to https://x.com/compose/post
2. 按「通用约束 → CJK 正文输入与校验闸门」输入第一条推文(CJK 走 insertText)
- Plain text only (no Markdown)
- Max 280 characters for standard accounts
3. Click the "+" icon to add another tweet to the thread
4. 同上输入第二条推文(CJK 走 insertText),**发布前校验闸门**:每条 eval 校验 MATCH 才 Post all(通用约束 step 3)
- Plain text only (no Markdown)
- Max 280 characters for standard accounts
5. Repeat for each additional tweet
6. Click "Post all" to publish the full thread
7. Parse stats for the **last** tweet (representative)
8. Update frequency tracker (count = number of tweets in thread)
```
---
## Workflow: Quote Tweet
**场景**:引用别人的推 + 自己的评论(BD / 互动 / 营销场景强)
```
1. Navigate to source tweet URL(如 https://x.com/username/status/1234567890)
2. Click "Repost" icon → 选择 "Quote"(不是 "Repost")
- ⚠️ 区分 "Repost"(纯转推,无评论)vs "Quote"(引用+评论)
3. Compose box 打开,**已自动填入引用卡片**
4. Click into text area below the quoted card
5. 按「通用约束 → CJK 正文输入与校验闸门」输入评论(CJK 走 insertText);**发布前校验闸门**:eval 校验 MATCH(通用约束 step 3)
6. Verify character count
7. **立即点击 "Post" 按钮**
8. Wait for confirmation, report post URL
9. Parse stats (same as plain text)
10. Update frequency tracker
```
**Pitfall**:
- ❌ 选 "Repost" 而不是 "Quote" → 推出去没评论,BD 场景失去意义
- ❌ 评论超过 280 字符 → 按钮变灰,**不**自动转 Long post
- ❌ 评论里直接放 raw URL(占 23 字符)→ 实际可发字符更少
---
## Workflow: Reply to Tweet
```
1. Navigate to source tweet URL(如 https://x.com/username/status/1234567890)
2. Click "Reply" icon(不是 reply 文本框)
3. Compose box 打开,**自动显示 reply context**
4. 按「通用约束 → CJK 正文输入与校验闸门」输入回复(CJK 走 insertText);**发布前校验闸门**:eval 校验 MATCH(通用约束 step 3)
5. Verify character count
6. **立即点击 "Reply" 按钮**(不是 "Post")
7. Wait for confirmation, report reply URL
8. Parse stats (replies can also get view counts)
9. Update frequency tracker
```
**Pitfall**:
- ❌ 选 "Reply" 时落入 quote 模式(X 旧 UI 行为)→ 不会加 reply 关系
- ❌ 串太长(> 280)→ 按钮变灰
- ❌ 频率过高 → 风控(见 Anti-automation limit)
---
## Workflow: Long Post
**前置**:用户是 **Premium / Blue** 订阅(X 蓝标)。普通账号本工作流**不适用**。
**检测 Premium**:
```
snapshot eval: document.querySelector('[data-testid="icon-verified"]') !== null
// 或 UI 中是否有 "Premium" 字样
```
```
1. Navigate to https://x.com/compose/post
2. Wait for compose box to load
3. 按「通用约束 → CJK 正文输入与校验闸门」输入内容(CJK 走 insertText);**发布前校验闸门**:eval 校验 MATCH 才 Post all(通用约束 step 3)
4. **注意**:URL 仍 23 字符,Emoji 仍 2 字符
5. 按钮文字从 "Post" 变成 "**Post all**"(X 长帖是 1 个"post all"动作,但内容被服务端分页)
6. Click "Post all"
7. Wait for confirmation (URL changes)
8. Extract permalink (实际是 thread 形式:tweet + 续贴)
9. Parse stats for **first** tweet
10. Update frequency tracker (count = 1,long post 算 1 次)
```
**Pitfall**:
- ❌ 普通账号硬塞 25K → 按钮变灰 / 截断
- ❌ 不验 Premium 状态 → 普通账号调本工作流失败率高
- ⚠️ Long post 实际上服务端分页(thread-like),permalink 拿的是 first tweet
---
## Workflow: Post Parse Stats
> post 后立即拿 stats(view / reply / retweet / like),用于复盘。
```
1. After post success (any workflow ending with "Wait for confirmation")
2. 已在推文页面,URL = https://x.com/<user>/status/<id>
3. Wait 3-5s for X to populate stats
4. snapshot eval:
const stats = JSON.stringify({
retweet: document.querySelector('[data-testid="retweet"]')?.innerText,
like: document.querySelector('[data-testid="like"]')?.innerText,
reply: document.querySelector('[data-testid="reply"]')?.innerText,
view: document.querySelector('a[href*="/analytics"]')?.innerText,
bookmark: document.querySelector('[data-testid="bookmark"]')?.innerText,
permalink: window.location.href
})
5. Output: { ok, permalink, stats: { retweet, like, reply, view, bookmark } }
```
**注意**:
- view 数 Premium 账号可见;普通账号无
- 30 min 后 stats 才稳定(X 算法)
- 嵌入 evaluate 走 `document.querySelector('selector')?.innerText` —— selector 可能因 X UI 改版变,部署后真机验证
---
## Frequency Tracker
```python
# <Workspace 根>/twitter/twitter-frequency.json
{
"last_post_at": "2026-07-05T09:30:00+08:00",
"today_count": 5,
"week_count": 23,
"platform": "twitter"
}
```
**每次 post 成功后 append**:
1. 读 JSON(不存在则初始化 0/0)
2. 距 last_post_at < 30 min → **警告用户** + 询问是否继续(仍可继续,但 mark as high-risk)
3. 距 last_post_at < 5 min → **强制建议延后**(强烈风控风险)
4. today_count += N(thread 算 N 条)
5. today_count > 50 → **拒绝 + 告知用户明早再发**
6. week_count > 200 → 同上
7. 写入 JSON
---
## Content Limits
| Type | Limit |
|------|-------|
| Text (standard) | 280 characters (URL=23, Emoji=2) |
| Text (Premium/Blue) | 25,000 characters (long post) |
| Images | Up to 4 per post |
| Video | Max 512 MB, max 2m 20s |
| GIF | Max 15 MB |
| Reply | 280 characters |
| Quote Tweet | 280 characters (in comment) |
| Thread | Unlimited tweets, each ≤ 280 |
---
## Error Handling
| Situation | Action |
GitHubで見るこの SKILL.md は非常に大きいため、SkillsMP では最初のセクションだけを表示しています。 GitHubで見る