本 skill 固化的是我们在 profile=openclaw 环境下实测的一套流程:
> 核心原则:全自动完成互动,输入评论后直接发送,不询问用户确认。只评论不点赞。
profile="openclaw",通过 HTTP 代理访问 X;data-testid="like"/"reply")。browser act + type + slowly: true 模拟真人键盘输入,让 Reply 按钮亮起。当用户出现类似需求时使用本 skill:
默认假设:
N = 3,除非用户另行指定;N:默认 3;```text
https://x.com/home
```
```jsonc
{
"action": "open",
"profile": "openclaw",
"target": "host",
"targetUrl": "https://x.com/home"
}
```
evaluate 抓取候选 tweets(增强版:包含互动数据)在 Home timeline 执行类似脚本,拿前若干条并获取互动数据:
```js
() => {
const N = 15; // 多拉一些以便筛选优质内容
const articles = Array.from(document.querySelectorAll('article[role="article"]'));
const picked = articles.slice(0, N);
const tweets = picked.map(article => {
const linkEl = article.querySelector('a[href*="/status/"]');
const href = linkEl ? linkEl.href : null;
const textEl = article.querySelector('div[data-testid="tweetText"]');
const text = textEl ? textEl.innerText : '';
const authorEl = article.querySelector('div[data-testid="User-Name"] span');
const author = authorEl ? authorEl.innerText : '';
// 获取互动数据(reply/retweet/like 计数)
const replyEl = article.querySelector('[data-testid="reply"]');
const retweetEl = article.querySelector('[data-testid="retweet"]');
const likeEl = article.querySelector('[data-testid="like"]');
const replies = replyEl?.getAttribute('aria-label')?.match(/\d+/)?.[0] || '0';
const retweets = retweetEl?.getAttribute('aria-label')?.match(/\d+/)?.[0] || '0';
const likes = likeEl?.getAttribute('aria-label')?.match(/\d+/)?.[0] || '0';
// 获取发布时间(相对时间)
const timeEl = article.querySelector('time');
const timeAgo = timeEl?.getAttribute('datetime') || '';
return {
href, text, author,
replies: parseInt(replies),
retweets: parseInt(retweets),
likes: parseInt(likes),
timeAgo
};
}).filter(t => t.href && t.text);
return { ok: true, tweets };
}
```
模型侧基于多维度筛选出 N 条最值得互动的推文。
筛选标准(优先级从高到低):
评论质量标准(必须满足至少一项):
格式与长度要求:
API、Docker)互动闭环策略:
负面示例(避免):
❌ 「很有意思,学习了!」(无实质内容)
❌ 「666」「牛逼」(无价值的符号)
❌ 「同意楼上」(重复他人观点)
❌ 纯广告/自我推广(「我们的产品也能做到」)
❌ 过长的技术教程(超过 150 字,应该拆成独立推文)
正面示例(已验证字符数 ≤ 280):
✅ 「这个架构的关键在于状态管理,我们之前用 Redis 做分布式锁遇到过脑裂问题,后来改用 etcd 的 lease 机制才彻底解决。你们是怎么处理的?」(62 字/约 186 字符,提供价值 + 提问)
✅ 「不完全认同"BTC 只是投机工具",真正的价值在于抗审查和去中心化。当然现阶段 90% 的人确实只是炒币,这是两码事。」(53 字/约 159 字符,制造对立但有论据)
✅ 「哈哈上次我也因为忘了清 Docker cache 导致镜像大小暴涨到 2GB,最后发现是 node_modules 被打进去了。多阶段构建真的能救命。」(62 字/约 186 字符,幽默共鸣 + 实践经验)
不再执行点赞动作,直接进入评论流程。
对每条选中的 tweet:
browser.navigate 进入详情页:```jsonc
{
"action": "navigate",
"targetId": "
"url": "https://x.com/
}
```
evaluate 在详情页:article[role="article"];[data-testid="reply"] 打开回复弹窗;div[role="dialog"] div[role="textbox"][contenteditable="true"] 出现。 ⚠️ 重要:必须使用 browser 的原生 type 动作配合 slowly: true,而不是 evaluate 里的 JS 模拟输入!
X 的 Draft.js 编辑器会校验输入事件的来源,只有通过 Playwright/CDP 的真实键盘输入才能让 Reply 按钮亮起。
正确做法:
snapshot 获取回复对话框中 textbox 的 ref:```
browser snapshot labels=false targetId=
```
找到 textbox "Post text" [ref=eXXX]
browser act 的 type 动作,必须带 slowly: true:```jsonc
{
"action": "act",
"profile": "openclaw",
"targetId": "
"request": {
"kind": "type",
"ref": "eXXX",
"text": "你的评论内容",
"slowly": true
}
}
```
错误做法(Reply 按钮会保持灰色):
evaluate + execCommand('insertText', ...)evaluate + 手动 dispatch KeyboardEvent/InputEventtype 但不带 slowly: true验证按钮状态:
输入完成后,可以用 evaluate 检查 Reply 按钮是否亮起:
```js
(() => {
const dialog = document.querySelector('div[role="dialog"]');
const btns = dialog?.querySelectorAll('button');
for (const btn of btns || []) {
if (btn.innerText?.trim() === 'Reply') {
return { disabled: btn.disabled, ariaDisabled: btn.getAttribute('aria-disabled') };
}
}
return { found: false };
})()
```
当 disabled: false 且 ariaDisabled: null 时,按钮可点击。
使用 slowly: true 输入后,Reply 按钮应该已经亮起。默认直接点击发送,不询问用户确认。
发送方式:用 evaluate 点击 Reply 按钮:
```js
(() => {
const dialog = document.querySelector('div[role="dialog"]');
const btns = dialog?.querySelectorAll('button');
for (const btn of btns || []) {
if (btn.innerText?.trim() === 'Reply' && !btn.disabled) {
btn.click();
return 'clicked Reply';
}
}
return 'not found or disabled';
})()
```
发送后等待 2 秒,检查对话框是否关闭(dialogExists: false)来确认发送成功。
完成互动后,向用户做一个结构化汇报,例如:
profile=openclaw,已在托管浏览器中登录 X;示例(自然语言):
> 本轮在 X 首页完成了 2 条高质量互动:
>
> 1. @某 AI 开发者(1 小时前,35 回复)
> 话题:LangChain vs Semantic Kernel
> 评论:「这两个框架的定位其实不同,LangChain 偏快速原型,SK 更适合企业级。我们项目里混用,用 LC 做 POC,SK 做生产。你们有考虑过混合方案吗?」
> 类型:提供价值 + 提问(110 字)
>
> 2. @某加密分析师(30 分钟前,18 回复)
> 话题:BTC 短期波动分析
> 评论:「技术分析在加密市场作用有限,尤其是链上数据和宏观政策更有预测力。上次 ETF 通过前夕就是典型案例,TA 根本没捕捉到。」
> 类型:制造对立(68 字)
为了实现真正的互动闭环,可以在 workspace 中维护一个状态文件:
文件路径:~/.openclaw/workspace/skills/x-comment/interaction-state.json
结构示例:
{
"pendingReplies": [
{
"tweetUrl": "https://x.com/user/status/123456",
"myCommentTime": 1773533956000,
"author": "@某开发者",
"topic": "AI agent 框架",
"checkCount": 0,
"lastChecked": null
}
],
"completedThreads": [
{
"tweetUrl": "https://x.com/user/status/789012",
"rounds": 3,
"finalStatus": "author-replied-back"
}
]
}
跟踪策略:
pendingRepliespendingReplies 中是否有作者回复(通过访问 tweet 详情页,检查作者是否回复了我的评论)completedThreads 标记为 no-author-reply完成所有评论后,关闭浏览器 tab:
{
"action": "close",
"targetId": "<page-id>"
}
这样可以释放资源,避免后台累积过多标签页。
browser act + type + slowly: true,不能用 evaluate 脚本注入btn.click())dialogExists: false)即为发送成功共 1 个版本