/install douyin-send-dm
Douyin: Send a DM via Chrome
Send a text DM to a Douyin user from the user's signed-in Chrome session, then verify the message actually delivered (Douyin will silently render bubbles even when DMs are banned — never claim success without checking).
When to use
Trigger on any of:
- "send a message to \x3Cname> on Douyin / 抖音"
- "DM \x3Cname> on douyin.com"
- "tell \x3Cname> '\x3Ctext>' on Douyin"
- 抖音 / 私信 / 发私信 mentioned alongside a target user
If the user wants to control the desktop app (抖音聊天.app / Douyin Chat for Mac) rather than the website, this skill does not apply — that requires macOS Accessibility automation, not browser control.
Prerequisites (one-time setup)
- Chrome 144+ exposing CDP on
127.0.0.1:9222. The user can launch Chrome with--remote-debugging-port=9222, or openchrome://inspect/#remote-debugging, click Configure…, and follow the in-page steps until they seeServer running at: 127.0.0.1:9222. - OpenClaw
userbrowser profile (built-in:driver=existing-session,transport=chrome-mcp). No extra config needed —browser action=start profile=userwill spawnchrome-devtools-mcpand attach. - User logged into douyin.com in that Chrome.
- Sender and recipient must be mutual followers (相互关注). Douyin web blocks DMs to non-mutuals. If the target's profile shows only "关注" (follow) instead of "相互关注", stop and tell the user.
- Sender's DM privilege not banned. If banned, the message bubble still renders locally with a tip "私信功能已被封禁" — see Verify step.
Workflow
1. Attach to Chrome
browser action=start profile=user
browser action=tabs profile=user
Pick an empty tab from tabs, or be prepared to open a new one. Save its targetId — pass it on every subsequent call to keep the same tab.
2. Find the target user
Navigate the chosen tab to a user search:
browser action=navigate profile=user targetId=\x3Cid>
url=https://www.douyin.com/search/\x3CURL-encoded query>?type=user
Snapshot with refs="aria" and pick the result whose card shows "相互关注" and whose 抖音号 / bio matches what the user described. If multiple plausible results exist, ask the user to confirm before proceeding.
Click that result link by ref — it opens the profile in a new tab. Snapshot or browser action=tabs again to find the new targetId (URL pattern: https://www.douyin.com/user/MS4wLj…). Switch to that tab for the rest of the flow.
3. Open the DM panel from the profile
Critical: there are two 私信 controls on the page.
- Header
私信(top-right, around x≈1542) opens the global DM panel with no conversation selected. - Profile-page
私信button (next to "相互关注", around x≈1562 y≈158) opens the panel with the right conversation already selected.
Always click the profile-page button. Find it via DOM filter, not by ref (the ref tree sometimes returns the wrong element):
browser action=act profile=user targetId=\x3Cprofile-tab>
request={"kind":"evaluate","fn":"() => { const btns=[...document.querySelectorAll('button')].filter(b=>(b.innerText||'').trim()==='私信' && b.offsetParent!==null); for (const b of btns){ const r=b.getBoundingClientRect(); if (r.width>30 && r.width\x3C200 && r.height>20 && r.height\x3C60 && r.x>200){ b.click(); return JSON.stringify({clicked:true,x:Math.round(r.x),y:Math.round(r.y)}); } } return JSON.stringify({found:btns.length}); }"}
Then wait for the panel to render:
browser action=act profile=user targetId=\x3Cid>
request={"kind":"wait","timeMs":1500}
Verify the right conversation is selected by reading .RightPanelHeadertitle:
browser action=act profile=user targetId=\x3Cid>
request={"kind":"evaluate","fn":"() => { const h=document.querySelector('.RightPanelHeadertitle'); return h?h.innerText:null; }"}
The result must equal the target user's display name. If null or wrong, the DM panel didn't open or the wrong row got selected — retry the click.
4. Type the message
The message editor is a [contenteditable="true"] with placeholder 发送消息. Standard act:type rejects pure-coords focus on existing-session driver, so write the text via execCommand:
browser action=act profile=user targetId=\x3Cid>
request={"kind":"evaluate","fn":"() => { const ed=[...document.querySelectorAll('[contenteditable=\"true\"]')].find(e=>{const r=e.getBoundingClientRect();return r.width>100 && r.height>20;}); if(!ed) return 'no-editor'; ed.focus(); const sel=window.getSelection(); const range=document.createRange(); range.selectNodeContents(ed); range.collapse(false); sel.removeAllRanges(); sel.addRange(range); document.execCommand('insertText',false,'\x3CMESSAGE TEXT>'); return ed.innerText; }"}
Replace \x3CMESSAGE TEXT> with the user's message. Escape single quotes (' → \\'). The returned innerText should match the message (a trailing zero-width char is fine).
5. Send
browser action=act profile=user targetId=\x3Cid>
request={"kind":"press","key":"Enter"}
6. Verify delivery — do not skip
Inspect the DOM for both signals:
browser action=act profile=user targetId=\x3Cid>
request={"kind":"evaluate","fn":"() => { const ed=[...document.querySelectorAll('[contenteditable=\"true\"]')].find(e=>{const r=e.getBoundingClientRect();return r.width>100 && r.height>20;}); const editorText=ed?ed.innerText.trim():null; const list=document.querySelector('.messageMessageListwrapper'); const listText=list?list.innerText.slice(0,400):''; const banned=/私信功能已被封禁|对方拒收|不是Ta的好友|无法发送/.test(listText); return JSON.stringify({editorEmpty:!editorText||editorText==='\u200b'||editorText==='', listText, banned}); }"}
Outcome matrix:
| editorEmpty | banned | Result |
|---|---|---|
| true | false | ✅ delivered — confirm to user |
| true | true | ⚠️ Sent locally but Douyin blocked it. Tell the user the bubble appears but the recipient won't receive it; they should check Douyin notifications for the ban reason. |
| false | – | ❌ Send didn't fire (Enter consumed by IME, no focus, etc.). Retry steps 4–5. |
Always quote the exact tip text Douyin shows when reporting a ban — don't paraphrase.
Failure modes & recovery
- Search returns multiple "相互关注" matches. Ask the user to disambiguate by 抖音号 or bio before clicking.
- No 私信 button on profile, only "关注" / "相互关注". Not mutuals. Tell the user — Douyin web doesn't allow DMs to non-mutuals.
browser action=screenshotfails withENOENT … openclaw-chrome-mcp-…. Known existing-session driver quirk. Skip screenshots; rely on DOM inspection (evaluate) for verification.- Header 私信 click instead of profile button. Closes / reopens the global panel without selecting a chat. Always re-check
.RightPanelHeadertitlematches the target. - Massive
evaluateblocks accidentally clicking page-wide containers. Constrain queries withgetBoundingClientRectsize filters (e.g.width\x3C400 && height\x3C100). act:typeerrors with "type requires ref or selector". Existing-session driver doesn't accept coords-only focus. Use theexecCommand('insertText', …)evaluate pattern in step 4.- Chrome MCP attach times out (
Chrome MCP existing-session attach … timed out). Verifycurl -s http://127.0.0.1:9222/json/versionreturns JSON. If404, Chrome'schrome://inspectproxy is on but the actual DevTools endpoint isn't — relaunch Chrome with--remote-debugging-port=9222.
Reuse on another machine
Copy this SKILL.md (and parent directory) to that machine's skills root (e.g. ~/.openclaw/workspace/skills/douyin-send-dm/ or ~/.agents/skills/douyin-send-dm/). Then just say "send a Douyin DM to \x3Cuser>: \x3Ctext>" — OpenClaw's skill discovery will load it.
If the target user is not the same person as on this machine, redo step 2 (search + confirm 相互关注) before sending.
Honest reporting rules
- Never say "message sent" based only on the bubble appearing. Always run the verify step.
- If banned/blocked, surface Douyin's exact tip in your reply and recommend the user check their Douyin notifications.
- If the user asks "did it actually send?", re-run the verify-step query and report the live DOM state.
- Make sure OpenClaw is installed (local or Docker)
- Run the install command in chat:
/install douyin-send-dm - After installation, invoke the skill by name or use
/douyin-send-dm - Provide required inputs per the skill's parameter spec and get structured output
What is Douyin Send DM?
Send a direct message (私信) to a specific Douyin user via the user's logged-in Chrome through OpenClaw's browser tool. Use whenever the user asks to send, DM,... It is an AI Agent Skill for Claude Code / OpenClaw, with 22 downloads so far.
How do I install Douyin Send DM?
Run "/install douyin-send-dm" in the OpenClaw or Claude Code chat to install it in one step — no extra setup required.
Is Douyin Send DM free?
Yes, Douyin Send DM is completely free, licensed under MIT-0. You can download, install and use it at no cost.
Which platforms does Douyin Send DM support?
Douyin Send DM is cross-platform and runs anywhere OpenClaw / Claude Code is available (cross-platform).
Who created Douyin Send DM?
It is built and maintained by FindData (@scs001); the current version is v0.1.0.