Install
openclaw skills install douyin-send-dmSend 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, message, or 发私信 to someone on Douyin / 抖音 / douyin.com — even when they only describe the action ("tell @xyz hi", "ping that creator on Douyin"). Works for the web client only and only when sender and recipient are mutual followers (相互关注). Surface ban/block tips honestly instead of claiming success.
openclaw skills install douyin-send-dmSend 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).
Trigger on any of:
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.
127.0.0.1:9222.
The user can launch Chrome with --remote-debugging-port=9222, or open chrome://inspect/#remote-debugging, click Configure…, and follow the in-page steps until they see Server running at: 127.0.0.1:9222.user browser profile (built-in: driver=existing-session, transport=chrome-mcp). No extra config needed — browser action=start profile=user will spawn chrome-devtools-mcp and attach.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.
Navigate the chosen tab to a user search:
browser action=navigate profile=user targetId=<id>
url=https://www.douyin.com/search/<URL-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.
Critical: there are two 私信 controls on the page.
私信 (top-right, around x≈1542) opens the global DM panel with no conversation selected.私信 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=<profile-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<200 && r.height>20 && r.height<60 && 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=<id>
request={"kind":"wait","timeMs":1500}
Verify the right conversation is selected by reading .RightPanelHeadertitle:
browser action=act profile=user targetId=<id>
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.
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=<id>
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,'<MESSAGE TEXT>'); return ed.innerText; }"}
Replace <MESSAGE TEXT> with the user's message. Escape single quotes (' → \\'). The returned innerText should match the message (a trailing zero-width char is fine).
browser action=act profile=user targetId=<id>
request={"kind":"press","key":"Enter"}
Inspect the DOM for both signals:
browser action=act profile=user targetId=<id>
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.
browser action=screenshot fails with ENOENT … openclaw-chrome-mcp-…. Known existing-session driver quirk. Skip screenshots; rely on DOM inspection (evaluate) for verification..RightPanelHeadertitle matches the target.evaluate blocks accidentally clicking page-wide containers. Constrain queries with getBoundingClientRect size filters (e.g. width<400 && height<100).act:type errors with "type requires ref or selector". Existing-session driver doesn't accept coords-only focus. Use the execCommand('insertText', …) evaluate pattern in step 4.Chrome MCP existing-session attach … timed out). Verify curl -s http://127.0.0.1:9222/json/version returns JSON. If 404, Chrome's chrome://inspect proxy is on but the actual DevTools endpoint isn't — relaunch Chrome with --remote-debugging-port=9222.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 : " — 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.