Douyin Messager | 抖音私信助手

Douyin DMs and video/note comment assistant. 抖音私信与视频/图文评论助手;可读私信、分析评论区,评论/回复等写入前必须确认。

Safety Notice

This listing is from the official public ClawHub registry. Review SKILL.md and referenced scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "Douyin Messager | 抖音私信助手" with this command: npx skills add moroiser/douyin-messager

Douyin Messager | 抖音私信助手

通过浏览器自动化发送抖音私信、获取聊天记录;也支持打开抖音视频/图文链接、读取评论区并做简要分析,在用户明确确认后执行评论/回复等写入操作。

前置要求 | Prerequisites

条件说明
Browser profile必须使用 openclaw profile
登录状态抖音账号已在 openclaw profile 中保持登录态

⚠️ 执行前必须确认

  1. 用户已登录抖音账号(可目测判断)
  2. xdg-open 弹窗:只在 Linux 下存在
    • Linux:询问用户弹窗是否关闭
    • Windows/macOS:跳过

当前能力边界(阶段性总结)

本技能当前已验证可完成四类操作:

  1. 查看私信会话列表:进入私信悬浮面板后,读取可见的私聊/群聊列表,区分会话名称、最新消息预览、时间、未读数和置顶状态。
  2. 进入具体聊天窗口:从会话列表点击指定私聊或群聊,进入聊天详情页,读取当前已加载的聊天记录。
  3. 发送文本消息:在聊天详情底部的 Draft.js 输入框中输入文本,并通过发送按钮完成发送。
  4. 视频/图文评论区处理:可搜索或打开指定视频/图文链接,读取评论区,输出情绪简报;评论、回复、点赞等外部互动写入必须先获得用户明确确认。

需要注意:网页版对视频、图集、点赞、撤回、暂不支持消息等卡片类内容的 DOM 暴露不完整,读取时应保守标注,不应强行推断完整内容或发送者。


核心思路

抖音私信是一个悬浮在页面右侧的动态面板,包含"会话列表"和"聊天详情"两个视图。

会话列表布局(类 QQ/微信):左侧头像,中间分上下两行(上行:用户名/群名;下行:最新消息预览,群聊格式为「发消息的人:消息内容」),右侧时间戳。

聊天详情布局:进入某个会话后,右侧显示聊天记录。普通文本消息通常可提取时间、消息内容,并可根据气泡方向、操作项(如是否有「撤回」)辅助判断是否为本人消息;群聊中他人文本消息有时可从消息文本或引用结构中识别发送者。卡片类消息需谨慎处理。

方法论:不依赖固定 class name,而是通过元素的几何特征 + 内容特征动态查找。具体 class name 仅作参考示例。


获取私信列表流程

获取私信的第一步不是进入某个聊天,而是正确进入私信会话列表

步骤 1:打开私信会话列表

  1. 打开抖音主页或个人主页。
  2. 在顶部导航栏右侧定位「私信」入口(可能显示未读数,如「私信11」)。
  3. 点击后等待右侧悬浮面板出现。
  4. 确认当前处于会话列表视图,而不是某个聊天详情页。

步骤 2:识别每一行会话

会话列表中每一行对应一个私聊或群聊。布局通常为:左侧头像;中间上行为会话名称(用户名/群名,可能带「置顶」标记);中间下行为最新消息预览;右侧为时间戳或未读标记。

⚠️ 会话列表本身是可滚动容器,不要只按当前可见区域判断数量。实测中滚动容器宽约 328px、高约 593px,scrollHeight > clientHeight,可通过 scrollTop 滚到底部;出现「暂时没有更多了」才表示到底。

解析时应优先按行容器处理,而不是直接读取父元素的合并文本。读取每行时分别提取:

字段识别方法
会话名称行内上方文本,通常是用户名或群名;若有「置顶」应作为标记处理,不并入名称
最新消息行内下方文本;群聊常见格式为「发送者:消息内容」
最新发送者若最新消息包含「:」,冒号前通常是群聊中最新消息发送者
时间行右侧或下行末尾的时间文本,如「昨天」「04-25」「01:42」
未读数行右侧 badge 数字;顶部「私信(N)」通常表示总未读数
置顶名称行附近出现「置顶」标记时记录为 pinned=true

步骤 3:区分私聊与群聊

  • 私聊:上行通常是对方昵称;下行多为消息内容本身。
  • 群聊:上行是群名;下行常为「发送者:消息内容」。
  • 不要把上行群名和下行发送者合并为一个名称。

示例参考

上行(会话名称)下行(最新消息)解析结果
示例群A成员甲:分享[视频] · 昨天群名/会话名=示例群A,最新发送者=成员甲
项目交流群成员乙:分享[图集] · 04-25群名=项目交流群,最新发送者=成员乙
联系人A + 置顶收到,谢谢 · 01:42私聊=联系人A,pinned=true

示例:提取可见会话行文本

以下代码用于辅助观察当前可见列表,不应把 class name 当成稳定依赖:

browser action=act request={"kind": "evaluate", "fn": "() => { const rows = []; const all = Array.from(document.querySelectorAll('*')); for (const el of all) { const r = el.getBoundingClientRect(); const text = (el.textContent || '').trim(); if (r.width >= 250 && r.width <= 360 && r.height >= 50 && r.height <= 90 && r.left > window.innerWidth * 0.55 && text && !text.includes('私信(')) { rows.push({top: Math.round(r.top), text}); } } return rows.sort((a,b) => a.top - b.top); }"}

完整发送流程

步骤 1:打开抖音主页

browser action=open profile=openclaw targetUrl=https://www.douyin.com/
browser action=act request={"kind": "wait", "timeMs": 3000}

步骤 2:打开私信悬浮面板

找按钮:在页面顶部导航栏中,找到文字为"私信"(可能带未读数如"私信15")且位于页面最右侧的元素,点击它。

browser action=act request={"kind": "evaluate", "fn": "() => { const all = Array.from(document.querySelectorAll('*')); for(const el of all) { const t = (el.textContent||'').trim(); const r = el.getBoundingClientRect(); if((t === '私信' || t.startsWith('私信')) && r.width > 0 && r.height > 0 && r.width < 100 && r.left > window.innerWidth - 200) { el.click(); return 'clicked: ' + t; } } return 'not found'; }"}
browser action=act request={"kind": "wait", "timeMs": 2000}

找面板:点击后,页面右侧应出现一个约 500-600px 宽、600-700px 高的悬浮面板。

browser action=act request={"kind": "evaluate", "fn": "() => { const all = document.querySelectorAll('[class]'); for(const el of all) { const r = el.getBoundingClientRect(); if(r.width >= 450 && r.width <= 650 && r.height >= 500 && r.height <= 750 && r.left > 0 && r.top > 0 && r.left < 1500) { const text = (el.textContent||'').trim(); if(text.includes('私信')) return 'found panel: ' + r.width + 'x' + r.height + ' at ' + Math.round(r.left); } } return 'panel not found'; }"}

示例参考:实测中面板容器 class 为 vgonMAXk,宽 581px、高 649px,固定在 x:1235 位置。但这是动态的,应以几何定位为主。

步骤 3:进入具体聊天

找目标用户:在面板的会话列表中,找到目标用户名的元素并点击。

browser action=act request={"kind": "evaluate", "fn": "() => { const all = Array.from(document.querySelectorAll('*')); for(const el of all) { const t = (el.textContent||'').trim(); const r = el.getBoundingClientRect(); if(t.includes('<目标用户名>') && r.width > 0 && r.height > 0 && r.left > 0) { el.click(); return 'clicked: ' + t; } } return 'not found'; }"}
browser action=act request={"kind": "wait", "timeMs": 2000}

成功标志:面板从会话列表切换为聊天详情,底部出现输入区域。

示例参考:会话列表区域 class 为 zEpd_aAP(左侧,500px宽);聊天详情区域 class 为 w5duGc5Q(右侧)。

步骤 4:发送消息

找输入框:聊天详情底部有一个 Draft.js 富文本编辑器(contenteditable="true",class 包含 DraftEditor-content)。

browser action=act request={"kind": "evaluate", "fn": "() => { const all = document.querySelectorAll('[contenteditable=\"true\"]'); for(const el of all) { const r = el.getBoundingClientRect(); if(r.width > 200 && r.width < 500 && r.height > 10 && r.height < 100 && r.left > 1000) return 'found input at ' + Math.round(r.left) + ',' + Math.round(r.top); } return 'input not found'; }"}

发送消息:先用 type 方式写入 Draft.js 编辑器,再点击发送按钮。

// 1. 写入文本
browser action=act request={"kind": "type", "selector": "[contenteditable=\"true\"]", "text": "你好,测试消息"}
browser action=act request={"kind": "wait", "timeMs": 500}

// 2. 点击发送按钮(优先用几何位置查找,也可用实测 class 作为参考)
browser action=act request={"kind": "evaluate", "fn": "() => { const btn = Array.from(document.querySelectorAll('*')).find(el => { const r = el.getBoundingClientRect(); const cls = String(el.className || ''); return r.width >= 20 && r.width <= 50 && r.height >= 20 && r.height <= 50 && r.left > window.innerWidth * 0.7 && r.top > window.innerHeight * 0.55 && (cls.includes('send') || cls.includes('PygT7Ced')); }); if(!btn) return 'send button not found'; const r = btn.getBoundingClientRect(); btn.click(); return 'clicked send at ' + Math.round(r.left + r.width/2) + ',' + Math.round(r.top + r.height/2); }"}
browser action=act request={"kind": "wait", "timeMs": 2000}

⚠️ 必须用 type 写入 Draft.js 编辑器。直接操作 DOM 文本(textContentexecCommandclipboard)无法触发 Draft.js 内部状态,发送按钮会保持禁用。

示例参考:实测中输入框 class 为 notranslate public-DraftEditor-contentcontenteditable="true"),宽约 329px,位置 x:1346, y:651。

步骤 5:确认发送成功

browser action=act request={"kind": "evaluate", "fn": "() => { const input = document.querySelector('[contenteditable=\"true\"]'); if(!input) return 'input not found'; return input.textContent.length === 0 ? 'sent ✓' : 'not sent: ' + input.textContent; }"}

发送成功后:输入框被自动清空。


定位方法总结

目标定位策略实测示例(参考)
「私信」按钮文字="私信"或"私信数字",位于页面最右侧(r.left > innerWidth - 200文字="私信15",viewport x:1708, y:28
私信面板宽 450-650px、高 500-750px、固定在页面右侧,内容含"私信"class=vgonMAXk,581×649px,x:1235
会话列表面板左侧区域class=zEpd_aAP(宽~81px)
聊天详情面板右侧区域class=w5duGc5Q(宽~500px)
输入框contenteditable="true",宽 200-500px,位于页面右侧class=notranslate public-DraftEditor-content,329×22px
发送按钮输入框右侧;写入文本后点击class 示例:PygT7Ced e2e-send-msg-btn

注意:以上尺寸和位置基于典型 1920px 宽屏幕实测。class name 会随抖音版本变化,几何特征定位是更稳定的方法


读取具体聊天记录

进入某个私聊或群聊后,可以读取聊天详情页中当前已加载的消息块。读取时建议按消息块容器处理,而不是直接读取整个聊天面板的合并文本。

可识别字段

字段说明
时间消息块上方或附近的时间文本,如「刚刚」「2026-04-04 19:07」
消息内容普通文本消息可从气泡文本中提取
是否本人消息可结合气泡位置、是否出现「撤回」操作等特征辅助判断
群聊发送者普通文本或引用消息中可能出现发送者名称;若 DOM 未明确暴露,应标注为未知/推断
卡片类内容视频、图集、点赞、撤回、不支持类型等可能只显示占位文本,不应过度解析

示例:读取当前已加载消息块

browser action=act request={"kind": "evaluate", "fn": "() => { const area = document.querySelector('.IRB0Sra6') || document.querySelector('.z1iI1SFY'); if(!area) return 'message area not found'; const blocks = Array.from(area.querySelectorAll('.mM66nPpS')); return blocks.map(block => { const time = block.querySelector('.mA74174G')?.textContent?.trim() || ''; const text = (block.querySelector('.G3hOMUUp') || block.querySelector('.J3X6BOUb') || block).textContent.trim(); const mine = block.textContent.includes('撤回'); return { time, mine, text }; }); }"}

上述 class name 是实测示例;正式逻辑应优先用消息区域的几何位置和消息块尺寸筛选,再结合文本特征解析。


获取对方回复

滚动到最新消息

browser action=act request={"kind": "evaluate", "fn": "() => { const all = Array.from(document.querySelectorAll('*')); for(const el of all) { const r = el.getBoundingClientRect(); if(r.width >= 400 && r.width <= 600 && r.height >= 300 && r.height <= 800 && r.left > 800) { el.scrollTop = el.scrollHeight; return 'scrolled'; } } return 'not found'; }"}
browser action=act request={"kind": "wait", "timeMs": 1000}

截图确认

browser action=screenshot

视频搜索与评论区操作(阶段性验证)

当前已验证可完成以下只读/可定位操作:

  1. 按关键词搜索视频:可直接打开 https://www.douyin.com/search/<关键词>?type=video,从搜索结果中提取 /video/<id> 链接、标题、作者、时间和互动数字。
  2. 打开指定视频/图文:可直接导航到 https://www.douyin.com/video/<id> 或用户给出的分享链接。图文/笔记类内容可能会规范化跳转为 /note/<id>,个人页弹窗链接中的 modal_id=<id> 也可作为目标内容 ID 使用。
  3. 读取视频信息:可从页面文本中读取标题、作者、发布时间、点赞/评论/收藏/分享等可见数字(具体字段需按页面布局解析)。
  4. 读取评论区:视频页评论区在页面下方,需滚动到「全部评论」区域;可读取可见评论的昵称、内容、时间/地区、点赞数、分享/回复入口,并可继续向下滚动加载更多评论。
  5. 定位发评论输入框:点击「留下你的精彩评论吧」后,会出现 Draft.js 输入框:contenteditable="true" 且 class 包含 public-DraftEditor-content
  6. 定位回复输入框:点击某条评论的「回复」后,会在该评论下方出现 Draft.js 输入框,placeholder 形如「回复@用户名」。
  7. 定位发送按钮:输入文字后,评论框右侧会出现图标按钮区,最右侧圆形上箭头按钮为发送/发布入口。
  8. 定位评论点赞入口:每条评论的操作区包含点赞数、分享、回复;点赞图标/数字区域可通过评论块几何位置定位。
  9. 评论区情绪简报:基于已加载评论文本,按正向/中性/负向/争议或信息不足分类,输出样本量、主要情绪、典型主题和置信度;样本少时必须注明限制。

⚠️ 发评论、回复评论、点赞、分享等都属于外部互动写入操作,必须先获得用户明确确认;测试时可以定位输入框和按钮,但不要擅自提交。

搜索并提取视频链接示例

browser action=navigate profile=openclaw targetUrl="https://www.douyin.com/search/OpenClaw?type=video"
browser action=act request={"kind":"wait","timeMs":3000}
browser action=act request={"kind":"evaluate","fn":"() => Array.from(document.querySelectorAll('a[href*=\"/video/\"]')).map(a => ({ href: a.href, text: (a.innerText || a.textContent || '').trim() })).slice(0, 20)"}

打开视频并滚动到评论区

browser action=navigate profile=openclaw targetUrl="https://www.douyin.com/video/<video_id>"
browser action=act request={"kind":"wait","timeMs":5000}
browser action=act request={"kind":"evaluate","fn":"() => { const el = Array.from(document.querySelectorAll('*')).find(el => (el.innerText || el.textContent || '').includes('全部评论') && el.getBoundingClientRect().y > 100); if (el) { el.scrollIntoView({block:'start'}); return 'scrolled to comments'; } return 'comments not found'; }"}

从个人页弹窗链接打开目标内容

用户可能发送形如 https://www.douyin.com/user/self?...&modal_id=<id>&showTab=like 的链接。此类链接可直接打开;若页面进入个人页弹窗,也可提取 modal_id 后直接尝试:

browser action=navigate profile=openclaw targetUrl="https://www.douyin.com/video/<modal_id>"
browser action=act request={"kind":"wait","timeMs":5000}
// 页面可能自动跳转为 /note/<modal_id>,属于正常情况。

读取可见评论示例

browser action=act request={"kind":"evaluate","fn":"() => Array.from(document.querySelectorAll('*')).map(el => { const r = el.getBoundingClientRect(); const text = (el.innerText || el.textContent || '').trim(); return { r, text, cls: String(el.className || '') }; }).filter(o => o.r.width > 300 && o.r.height > 60 && o.text.includes('回复') && o.text.includes('·')).slice(0, 20).map(o => o.text)"}

图文/笔记页的评论区可能在右侧栏,通过「评论(N)」标签切换;即使初始显示 评论(0),点击后也可能加载真实评论。读取时以当前 DOM 文本为准,并检查是否出现「暂时没有更多评论」。

评论区情绪简报模板

评论区情绪简报:
- 样本量:已加载 N 条评论
- 整体情绪:正向 / 中性 / 负向 / 混合 / 信息不足
- 主要主题:……
- 风险/争议点:……
- 置信度:高 / 中 / 低(说明原因)

评论/回复输入框定位

// 点击「留下你的精彩评论吧」或某条评论的「回复」后:
browser action=act request={"kind":"evaluate","fn":"() => { const input = document.querySelector('[contenteditable=\"true\"].public-DraftEditor-content'); if (!input) return 'input not found'; const r = input.getBoundingClientRect(); return { x: Math.round(r.left), y: Math.round(r.top), w: Math.round(r.width), h: Math.round(r.height), text: input.innerText }; }"}

常见问题

Q: 点击「私信」按钮没反应? A: 确认点击的是正确的按钮(顶栏最右侧,带"私信"文字)。也可先导航到 /user/self 页面再点击。

Q: 私信面板找不到? A: 确认点击后有等待足够时间(wait 2000ms)。面板宽约 500px、高约 650px,固定在页面右侧。

Q: 输入框找不到? A: 必须先在会话列表中点击一个具体用户,进入聊天详情模式,输入框才会出现。

Q: 消息输入了但发送不成功? A: 先用 kind: 'type' 写入 Draft.js 输入框,再点击发送按钮。直接操作 DOM 文本(textContent赋值、execCommandclipboard paste)无法触发 Draft.js 状态。

Q: 怎么确认发送成功了? A: 发送成功后输入框被自动清空(textContent 变为空字符串)。


完整示例

// 1. 打开抖音
browser action=open profile=openclaw targetUrl=https://www.douyin.com/
browser action=act request={"kind": "wait", "timeMs": 3000}

// 2. 点击「私信」按钮
browser action=act request={"kind": "evaluate", "fn": "() => { const all = Array.from(document.querySelectorAll('*')); for(const el of all) { const t = (el.textContent||'').trim(); const r = el.getBoundingClientRect(); if((t === '私信' || t.startsWith('私信')) && r.width > 0 && r.height > 0 && r.width < 100 && r.left > window.innerWidth - 200) { el.click(); return 'clicked: ' + t; } } return 'not found'; }"}
browser action=act request={"kind": "wait", "timeMs": 2000}

// 3. 点击目标用户
browser action=act request={"kind": "evaluate", "fn": "() => { const all = Array.from(document.querySelectorAll('*')); for(const el of all) { const t = (el.textContent||'').trim(); const r = el.getBoundingClientRect(); if(t.includes('<目标用户名>') && r.width > 0 && r.height > 0) { el.click(); return 'clicked: ' + t; } } return 'not found'; }"}
browser action=act request={"kind": "wait", "timeMs": 2000}

// 4. 写入消息并点击发送
browser action=act request={"kind": "type", "selector": "[contenteditable=\"true\"]", "text": "你好,这是测试消息"}
browser action=act request={"kind": "wait", "timeMs": 500}
browser action=act request={"kind": "evaluate", "fn": "() => { const btn = Array.from(document.querySelectorAll('*')).find(el => { const r = el.getBoundingClientRect(); const cls = String(el.className || ''); return r.width >= 20 && r.width <= 50 && r.height >= 20 && r.height <= 50 && r.left > window.innerWidth * 0.7 && r.top > window.innerHeight * 0.55 && (cls.includes('send') || cls.includes('PygT7Ced')); }); if(!btn) return 'send button not found'; btn.click(); return 'clicked send'; }"}
browser action=act request={"kind": "wait", "timeMs": 2000}

// 5. 确认发送
browser action=act request={"kind": "evaluate", "fn": "() => { const input = document.querySelector('[contenteditable=\"true\"]'); return input ? (input.textContent.length === 0 ? 'sent ✓' : 'not sent: ' + input.textContent) : 'input not found'; }"}

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

Wangdongjie Cfo Skill

基于王东杰26年实战经验,提供A+H双市场IPO操盘、资本杠杆设计、业财融合和AI数字化风控咨询。

Registry SourceRecently Updated
General

Hk Stock Morning Report

Generate HK stock market morning report (股市晨報) for Chinese bank trading desk. Use when user asks "生成晨报", "股市晨报", "今日股市", "港股晨報", or any similar HK stock mark...

Registry SourceRecently Updated
General

Nansen Mpp Payment

Pay-per-call access to the Nansen API via MPP (Tempo). Use when a user wants anonymous Nansen access without an API key and without managing their own Base/S...

Registry SourceRecently Updated
General

Etsy Autolist

Auto-create and manage digital product listings on Etsy. Creates listings from existing digital product files (PDFs, templates, spreadsheets) using Etsy Open...

Registry SourceRecently Updated