通过 OpenTweet REST API 在 X 上发推、排程、发布线程与长文,并附带 AI 生成的图片与视频。
编程
X Dm Auto Chat
试用X (Twitter) DM automated chat end-to-end Skill: scan DM inbox to identify pending-reply conversations, read message history, generate persona-based replies and send; also supports searching users and starting new conversations. Built-in E2E passcode unlock, DM permission filtering, and rate control.
它能做什么
X (Twitter) DM automated chat end-to-end Skill: scan DM inbox to identify pending-reply conversations, read message history, generate persona-based replies and send; also supports searching users and starting new conversations. Built-in E2E passcode unlock, DM permission filtering, and rate control. Use when user mentions X auto-reply DMs, Twitter DM automated chat, auto-handle unread DMs, reply to X private messages with persona, X DM outreach campaign, batch send DMs to Twitter users, auto-process pending DM replies, Twitter DM bot, automated Twitter outreach, X direct message automation.
技能文档
X (Twitter) — DM Auto Chat (End-to-End)
Full X DM automation Skill: inbox scan → conversation read → persona-based reply → send; also supports search-and-outreach. The calling Agent generates reply text based on persona; this Skill handles all mechanical operations.
Language
All process output to user (progress updates, process notifications) follows the user's language.
Objective
Encapsulate "refresh DM list → identify pending replies → read context → reply with persona → send" and "search user → enter chat → send first message" into callable end-to-end capabilities.
Prerequisites
- X account is logged into the browser (
[aria-label="Account menu"]visible on x.com) - The 4-digit DM passcode is provided by the caller before execution starts, if the account has E2E encryption enabled; if no passcode is set on the account, this can be omitted
- Caller has prepared a persona description string (used by the calling Agent to generate replies), e.g.:
"You are a friendly community manager. Tone: warm and concise. End every reply with a question." - Optional: list of target user search queries (for outreach scenario)
Pre-execution Checks
1. Tool Readiness
If browser-act has been confirmed available in the current session → skip this step.
Invoke browser-act via Skill tool to load usage. If installation or configuration issues arise, follow its guidance to resolve then retry.
2. Login Verification
If login status for X has been confirmed in the current session → skip this step.
Discover the browser and verify login state — selection is fully automatic, do not ask the user interactively:
-
browser-act browser list— find browsers withurlcontainingx.com- No X.com browser found → report error and stop: "No browser with X.com open. Please open X.com in a browser first."
- One X.com browser found → record its
idasBROWSER_ID - Multiple X.com browsers found → run
eval "$(python scripts/check-page-state.py)"on each in order; use the first withlogged_in: true. If none are logged in → report error and stop.
-
navigate https://x.com/i/chat→wait stable --timeout 15000 -
eval "$(python scripts/check-page-state.py)"— checklogged_inlogged_in: false→ inform user that login is required; wait; retry this steplogged_in: trueandneed_passcode: true→ run AI Workflow: DM passcode unlock before proceedinglogged_in: trueandneed_passcode: false→ ready for business flow
BROWSER_ID resolved here is used as
--browserprefix for all subsequent browser-act commands in this execution. No fixed session name is used; BROWSER_ID is resolved fresh each execution.
Capability Components
This Skill's operational boundary = what the user can manually do in their browser. It only reads data already displayed to the user on the page, never bypassing authentication or access controls. JS code is encapsulated in Python files under the
scripts/directory, invoked viaeval "$(python scripts/xxx.py {params})".$(...)is bash syntax; it is recommended to use the bash tool for execution. Alleval,navigate,wait,state,input, andscreenshotcommands requirebrowser-act --browserprefix at runtime; commands below omit this prefix for clarity.
Composite: Scan DM inbox (API metadata + DOM preview merged)
eval "$(python scripts/scan-inbox-merged.py)"
Output example:
{
"my_user_id": "123456",
"count": 14,
"unread_count": 3,
"items": [
{
"conversation_id": "123:456",
"conversation_url": "/i/chat/123-456",
"peer_user_id": "456",
"peer_screen_name": "username",
"peer_display_name": "Display Name",
"peer_avatar_url": "https://pbs.twimg.com/...",
"peer_is_blue_verified": false,
"peer_can_dm": true,
"peer_can_dm_reason": "Allowed",
"is_muted": false,
"is_deleted_by_viewer": false,
"latest_message_timestamp": "6:25 PM",
"latest_message_preview": "Hello there",
"latest_message_from_self": false,
"unread": true
}
],
"next_cursor": null,
"message_requests_count": 2
}
API: Fetch DM inbox metadata (with pagination)
eval "$(python scripts/fetch-inbox-api.py --cursor-id '{cursor_id}' --graph-snapshot-id '{snapshot_id}' --limit {N})"
Parameters:
--cursor-id: pagination cursor_id from previous response; omit for first page--graph-snapshot-id: pagination graph_snapshot_id from previous response; omit for first page--limit: conversations per page, default 20
Output example:
{
"my_user_id": "123456",
"count": 20,
"items": [
{
"conversation_id": "123:456",
"is_muted": false,
"is_deleted_by_viewer": false,
"has_more": false,
"peer_user_id": "456",
"peer_screen_name": "username",
"peer_name": "Display Name",
"participants": []
}
],
"next_cursor": {"cursor_id": "abc", "graph_snapshot_id": "xyz"},
"message_requests_count": 2
}
DOM: Read conversation messages
eval "$(python scripts/read-conversation.py)"
Output example:
{
"conversation_id": "123:456",
"url": "https://x.com/i/chat/123-456",
"peer_display_name": "Display Name",
"my_user_id": "123456",
"message_count": 10,
"messages": [
{
"message_id": "abc123",
"direction": "peer",
"text": "Hello there",
"timestamp_text": "6:25 PM",
"links": ["https://t.co/..."],
"images": []
}
]
}
DOM: Scroll to load message history
eval "$(python scripts/scroll-load-history.py)"
Output example:
{
"before_count": 10,
"after_count": 20,
"loaded_more": true,
"reached_top": false
}
DOM: Check composer state
eval "$(python scripts/check-composer.py)"
Output example:
{
"conversation_id": "123:456",
"url": "https://x.com/i/chat/123-456",
"composer_ready": true,
"current_value": "",
"has_send_button": true,
"has_voice_button": true,
"message_count": 10,
"last_message_id": "abc123"
}
DOM: Verify message sent
eval "$(python scripts/verify-sent.py '' --prev-last-id '')"
Parameters:
expected_text: the message text that was sent — pass in single quotes to prevent shell$expansion--prev-last-id:last_message_idrecorded before sending, used to detect the newly appeared message
Output example:
{
"sent": true,
"composer_cleared": true,
"current_message_count": 11
}
API: Search X users (with DM permission)
eval "$(python scripts/search-users.py '')"
Parameters:
query: name, screen_name, or partial search string
Output example:
{
"query": "content creator",
"count": 5,
"users": [
{
"user_id": "456",
"name": "Display Name",
"screen_name": "username",
"avatar_url": "https://pbs.twimg.com/...",
"is_blue_verified": false,
"is_verified_organization": false,
"verified_type": null,
"can_dm": true,
"can_dm_on_xchat": true,
"can_dm_reason": "Allowed",
"protected": false,
"suspended": false
}
]
}
JS: Calculate conversation URL from user_id
eval "$(python scripts/open-conversation-by-user.py '')"
Parameters:
user_id: target user's numeric rest_id
Output example:
{
"my_user_id": "123456",
"peer_user_id": "789",
"conversation_url": "/i/chat/123-789",
"conversation_id": "123:789",
"next_step": "navigate to conversation_url then verify composer_ready"
}
JS: Check page state
eval "$(python scripts/check-page-state.py)"
Output example:
{
"url": "https://x.com/i/chat",
"logged_in": true,
"need_passcode": false,
"on_inbox": true,
"on_conversation": false,
"has_panel": false,
"has_composer": false,
"inbox_count": 14
}
AI Workflow: DM passcode unlock
Triggered when check-page-state returns need_passcode: true. The passcode must have been provided by the caller as a prerequisite:
state— locate 4 consecutiveelements; record their indexes``- Enter each digit:
input ""→input ""→input ""→input ""(must use CDP real keyboard events viabrowser-act input; eval setting value is ignored by X) wait stable --timeout 10000eval "$(python scripts/check-page-state.py)"— verifyneed_passcode: falseandon_inbox: true- 3 consecutive failures still showing
need_passcode: true→ report error and stop: "DM passcode may be incorrect."
AI Workflow: Scenario A — scan unread DMs and reply with persona
Full flow: scan inbox → filter pending-reply conversations → per-conversation: read context → calling Agent generates reply → send → delay
-
eval "$(python scripts/scan-inbox-merged.py)"— getitems[] -
Filter pending-reply conversations: select items where all conditions are met:
unread: trueORlatest_message_from_self: falsepeer_can_dm: trueis_muted !== trueANDis_deleted_by_viewer !== true
-
For each pending-reply conversation (strictly serial, random sleep 8–15s between each):
a.
navigate https://x.com→wait stable --timeout 15000b.
eval "$(python scripts/check-page-state.py)"— ifneed_passcode: true→ run AI Workflow: DM passcode unlock firstc.
eval "$(python scripts/read-conversation.py)"— getmessages[]d. (Optional) If full history needed: loop
eval "$(python scripts/scroll-load-history.py)"untilreached_top: true, then re-read withread-conversation.pye. [AI Intervention] Generate reply: calling Agent combines persona + recent messages (typically last 6) +
peer_display_nameto producereply_text. Max 10,000 characters. Reply content is entirely the caller's decision; this Skill does not participate in generation.f. Send reply:
eval "$(python scripts/check-composer.py)"→ recordlast_message_idstate— find `` or[data-testid="dm-composer-textarea"]indexTA_IDX- Shell-escape
reply_textbefore passing toinput— prevents$N, backtick,!from being expanded by the shell:safe_text = reply_text.replace('\\', '\\\\').replace('$', r'\$').replace('`', r'\`').replace('!', r'\!') input ""(must use CDP real keyboard, cannot use eval)wait --selector '[data-testid="dm-composer-send-button"]' --state attached --timeout 5000eval "document.querySelector('[data-testid=\"dm-composer-send-button\"]').click(); 'clicked'"wait stable --timeout 15000eval "$(python scripts/verify-sent.py '' --prev-last-id '')"— pass originalreply_textin single quotessent: trueandcomposer_cleared: true→ success, record resultsent: false→ record failure, do not retry (prevents duplicate sends); proceed to next
g. Random sleep 8–15s
-
Report batch results (success count / failure count / per-item status)
AI Workflow: Scenario B — search users and send first message
Full flow: search candidates → filter sendable → open conversation → calling Agent generates first message → send
-
For each search query (1–2s interval between searches):
eval "$(python scripts/search-users.py '')" -
Filter:
can_dm: trueANDcan_dm_reason: "Allowed"AND NOTsuspendedAND NOTprotected; skipscreen_namevalues already in send history -
For each target user (strictly serial, random sleep 10–20s between each):
a.
eval "$(python scripts/open-conversation-by-user.py '')"→ getconversation_urlb.
navigate https://x.com→wait stable --timeout 15000c.
eval "$(python scripts/check-page-state.py)"— ifneed_passcode: true→ run AI Workflow: DM passcode unlockd.
eval "$(python scripts/check-composer.py)"— ifcomposer_ready: false→ skip this user; otherwise recordlast_message_ide. [AI Intervention] Generate first message: calling Agent produces
first_textbased on persona + target user info (screen_name,name, verification type). Recommended < 500 characters (shorter first messages reduce spam detection risk).f. Send: follow the same send sub-steps as Scenario A step 3f, substituting
first_textforreply_textg. Random sleep 10–20s
-
Report batch results
Pagination
API Pagination (fetch-inbox-api.py): cursor type, start with empty params. Next page value source: next_cursor.cursor_id + next_cursor.graph_snapshot_id from response. Termination: next_cursor === null.
Success Criteria
- Scenario A:
sent: truerate ≥ 90% across processed pending-reply conversations - Scenario B:
composer_ready: trueconfirmed for each target user before send;sent: truerate ≥ 90% - Per-item failures have a recorded reason (wrong passcode, composer unavailable, 429, etc.)
Known Limitations
- E2E passcode required:
browser-act input(CDP real keyboard) is the only working input method for passcode digits — eval setting value is ignored by X - Message bodies are E2E encrypted in API: GraphQL API returns encrypted binary; plaintext only available from the already-unlocked DOM. This Skill must run in an already-logged-in and unlocked browser
- DM permission enum (
can_dm_reasonobserved values):Allowed= can send;InboxClosed= recipient closed DM; other values treat as cannot send - Non-follower DMs go to Message Requests: first message to a non-follower goes to their Message Requests; they must accept before it moves to Primary
- Send rate (empirical, no official docs): ~5–10 messages/minute max; 8–15s random delay between messages; exceeding triggers HTTP 429 or UI block
- Message length cap: 10,000 characters per message (X official limit)
- Timestamp precision: DOM only gives X display format (
"30m"/"6:25 PM"/"May 8"); no ISO datetime available - Text-only: sending images / GIFs / voice / video / quote tweets not implemented
- Reply content generation not included: persona application and context understanding are entirely the calling Agent's responsibility
- No cross-session state: per-run reply history, blocklists, and progress must be recorded by the caller in external files (JSONL)
- Group conversations:
peer_*fields take only the first non-self member; fine-grained group replies not supported - Message Requests sub-inbox: only scans Primary inbox; Message Requests sub-inbox scanning not implemented
Execution Efficiency
- Batch processing: one run processes one batch then returns; no resident loop — let the caller decide scheduling cadence
- Strictly serial: all DM operations for the same account must be serial — parallel operations accelerate anti-abuse triggering
- No retry on failure: DM send failures are usually permission / rate / network issues; retrying risks duplicate sends — record and skip
- Resume from breakpoint: use JSONL to record
{target, status, timestamp, error?}per item; resume from breakpoint on interruption - Small-scale validation first: before bulk runs, validate the full pipeline with 1–2 items; only then run the full batch
- Reuse browser connection: use the same
--browserthroughout the batch; login state persists within the browser instance, no need to re-unlock for each item
Experience Notes
Path: {working-directory}/browser-act-skill-forge-memories/x-dm-auto-chat-x-dm-auto-chat.memory.md (working directory is determined by the Agent running the Skill)
Before execution: If the file exists, read it first — it records unexpected situations encountered during past executions (e.g., a strategy has become ineffective, a selector changed, a rate threshold discovered); adjust strategy order accordingly.
After execution: If an unexpected situation is encountered (strategy became ineffective, page redesigned, anti-scraping upgraded, better path discovered, new can_dm_reason enum values), append a line:
{YYYY-MM-DD}: {what happened} → {conclusion}
Normal execution does not write to the file. Do not record what keywords were used, which conversations were replied to, or how many messages were sent — those are task outputs, not experience.
相关技能
Searches and reads X (Twitter): profiles, timelines, mentions, followers, tweet search, trends, lists, communities, and Spaces. Publishes posts, likes/unlike...
把笔记、文章、报告等素材改写成符合你语气的 X 推文或线程,并适配字数限制。
X (Twitter) (x.com). Use this skill for ANY X (Twitter) request — reading, creating, updating, and deleting data. Whenever a task involves X (Twitter), use t...
X/Twitter content strategy skill for Chinese creators, indie builders, and expert accounts. Use when Codex needs to decide whether an idea belongs on X, turn...
X / Twitter Automation: Operate a connected X / Twitter account end-to-end from agents and workflows: compose, publish, search, engage, and DM. Use when an a...