如何用 TwexAPI 向 XChat Group 发送 DM
有些私信流程不是一对一。客服团队可能需要在已有客户群里跟进,创作者运营可能要向 campaign 小组同步素材,运营团队也可能需要把一条已审批更新发到既有 XChat 会话里。
TwexAPI 的 Send DM (XChat v3) 端点支持这种场景:把 recipient 设置为已有 group ID。这个 group ID 必须以 g... 开头;接口负责向已有群组发送消息,但不会创建新群,也不会添加成员。
群组 DM 比一对一 DM 风险更高。一条错误消息会同时影响多个人,所以更稳的实现是:确认 group、审批文案、只发送一次,并保存返回的 message object。
端点和 Group Recipient
使用 POST /v3/twitter/send-dm,并把 recipient 设为已有 XChat group ID。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
recipient | string | 是 | 以 g... 开头的已有 group ID。 |
text | string | 是 | 群消息正文。内容应和该群组用途匹配。 |
cookie | string | 是 | 发送账号的 X 认证 cookie 或 auth_token。 |
media_urls | string array 或 null | 否 | 一张图片的公开 URL。最多 1 个,不能和 video_url 同时使用。 |
video_url | string 或 null | 否 | 一个视频的公开 URL。不能和 media_urls 同时使用。 |
pin | string 或 null | 否 | Identity PIN。API 默认值是 1234。 |
proxy | string 或 null | 否 | 可选的发送会话 HTTP proxy。 |
文档标注该端点为 $0.0025 每次调用。真正重要的不是调用成本,而是确保群消息是有意发送、可审计、可追踪的。
基础群消息请求
curl --request POST \
--url https://api.twexapi.io/v3/twitter/send-dm \
--header "Authorization: Bearer $TWEXAPI_BEARER_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"recipient": "g1234567890",
"text": "Launch group 更新:导出修复已经上线。请先重新跑最新报告,再对外分享截图。",
"cookie": "auth_token=...; ct0=...; twid=...",
"pin": "1234"
}'成功响应会在 data 中返回 message object。
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "id": "1234567890123456789",
6 "text": "Launch group 更新:导出修复已经上线。",
7 "time": "2024-01-01T12:00:00Z",
8 "attachment": null,
9 "attachments": null,
10 "sequence_id": "2076661179334979585",
11 "sender_id": "1234567890123456789",
12 "recipient_id": "g1234567890"
13 }
14}建议保存返回的 id、sequence_id、sender_id、recipient_id、time 和原始响应。对于群组流程,还应保存内部 group label,方便复核人员知道这个 g... ID 对应哪个群。
Python 群消息发送脚本
下面的脚本要求先有 group 映射和审批状态。它不会允许调用方从表单里随便传一个 group ID 就立刻发送。
1import os
2from datetime import datetime, timezone
3from typing import Any
4
5import requests
6
7API_URL = "https://api.twexapi.io/v3/twitter/send-dm"
8BEARER_TOKEN = os.environ["TWEXAPI_BEARER_TOKEN"]
9X_COOKIE = os.environ["TWEXAPI_X_COOKIE"]
10XCHAT_PIN = os.environ.get("TWEXAPI_XCHAT_PIN", "1234")
11PROXY = os.environ.get("TWEXAPI_PROXY")
12
13GROUPS = {
14 "launch_ops": "g1234567890",
15 "creator_review": "g9876543210",
16}
17
18def resolve_group_id(group_key: str) -> str:
19 group_id = GROUPS.get(group_key)
20 if not group_id:
21 raise ValueError(f"Unknown group key: {group_key}")
22 if not group_id.startswith("g"):
23 raise ValueError("XChat group recipient must start with 'g'")
24 return group_id
25
26def validate_group_message(text: str) -> str:
27 cleaned = text.strip()
28 if not cleaned:
29 raise ValueError("text is required")
30 return cleaned
31
32def send_group_dm(
33 group_key: str,
34 text: str,
35 *,
36 media_urls: list[str] | None = None,
37 video_url: str | None = None,
38) -> dict[str, Any]:
39 if media_urls and video_url:
40 raise ValueError("media_urls and video_url are mutually exclusive")
41 if media_urls and len(media_urls) > 1:
42 raise ValueError("media_urls supports at most one image")
43
44 payload: dict[str, Any] = {
45 "recipient": resolve_group_id(group_key),
46 "text": validate_group_message(text),
47 "cookie": X_COOKIE,
48 "pin": XCHAT_PIN,
49 }
50
51 if media_urls:
52 payload["media_urls"] = media_urls
53 if video_url:
54 payload["video_url"] = video_url
55 if PROXY:
56 payload["proxy"] = PROXY
57
58 response = requests.post(
59 API_URL,
60 headers={
61 "Authorization": f"Bearer {BEARER_TOKEN}",
62 "Content-Type": "application/json",
63 "Accept": "application/json",
64 },
65 json=payload,
66 timeout=60,
67 )
68 response.raise_for_status()
69 data = response.json()
70
71 if data.get("code") != 200:
72 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
73
74 return data
75
76def normalize_group_send(
77 draft: dict[str, Any],
78 response: dict[str, Any],
79) -> dict[str, Any]:
80 message = response.get("data") or {}
81 return {
82 "draft_id": draft["draft_id"],
83 "group_key": draft["group_key"],
84 "group_id": resolve_group_id(draft["group_key"]),
85 "message_id": message.get("id"),
86 "sequence_id": message.get("sequence_id"),
87 "sender_id": message.get("sender_id"),
88 "recipient_id": message.get("recipient_id"),
89 "sent_at": message.get("time") or datetime.now(timezone.utc).isoformat(),
90 "api_code": response.get("code"),
91 "api_msg": response.get("msg"),
92 "raw_response": response,
93 }
94
95if __name__ == "__main__":
96 draft = {
97 "draft_id": "launch-group-update-2026-07-16",
98 "group_key": "launch_ops",
99 "text": "Launch group 更新:导出修复已经上线。请先重新跑最新报告,再对外分享截图。",
100 "status": "approved",
101 }
102
103 if draft["status"] != "approved":
104 raise ValueError("Group DM draft must be approved before sending")
105
106 result = send_group_dm(draft["group_key"], draft["text"])
107 print(normalize_group_send(draft, result))在可信服务端环境运行:
TWEXAPI_BEARER_TOKEN="your_twexapi_bearer_token" \
TWEXAPI_X_COOKIE="auth_token=...; ct0=...; twid=..." \
TWEXAPI_XCHAT_PIN="1234" \
python send_xchat_group_dm.pyGROUPS 映射要刻意保持显式。生产环境里可以从数据库读取,但只允许已审批的内部 group 记录进入发送流程。
附加一张图片或一个视频
群组 DM 使用同一个端点,媒体规则也一样:通过 media_urls 发送一张图片,或通过 video_url 发送一个视频,二者不能同时使用。
image_result = send_group_dm(
"launch_ops",
"这是最终 launch checklist 截图。",
media_urls=["https://example.com/checklist.jpg"],
)video_result = send_group_dm(
"creator_review",
"这是更新后 brief 的简短 walkthrough。",
video_url="https://example.com/brief-walkthrough.mp4",
)使用稳定的公开 URL,并避免在日志里记录完整签名 URL。如果媒体内容敏感,除非安全模型明确允许,否则不要通过公开链接发送。
群组 DM 工作流
群消息应比一对一消息有更严格的复核路径。
- Group registry:保存每个已批准 group ID 的内部名称、负责人、用途和允许发送账号。
- 草稿:针对 group key 创建消息,而不是直接使用用户输入的
g...值。 - 复核:确认群组用途、消息正文、媒体选择和发送账号。
- 发送一次:只对已审批草稿调用一次
POST /v3/twitter/send-dm。 - 记录:保存返回的 message object、group key、group ID、发送账号和原始响应。
- 跟进:把消息关联到项目、工单、campaign 或客户记录。
这能避免最常见的群消息事故:消息本身正确,但发到了错误的群。
保存 Group Send 状态
| 字段 | 作用 |
|---|---|
draft_id | 把已发送消息关联回已审批草稿。 |
group_key | 内部可读的群组标签。 |
group_id | XChat 的 g... recipient 值。 |
sender_account_id | 标识哪个 X 账号发送了消息。 |
message_text | 保存已审批的正文。 |
attachment_kind | 跟踪无媒体、图片或视频。 |
message_id | 返回的 XChat message ID。 |
sequence_id | 后续会话分页时可能用到。 |
sent_at | 支持审计和时间线复盘。 |
status | 跟踪 approved、sent、failed 或 needs_review。 |
raw_response | 保存原始 API 证据。 |
不要在这张表里保存完整账号 cookie。保存凭据引用即可。
常见错误
- 把
POST /v3/twitter/send-dm当成创建群组端点。它只能向已有群组发送。 - 从未复核表单直接接收原始
g...ID 并立即发送。 - 发送和该群用途不匹配的泛化广播消息。
- 忘记
media_urls只支持一张图片。 - 同时传入
media_urls和video_url。 - 未确认群里是否已经收到消息,就对未知 timeout 自动重试。
遇到未知失败时,应先检查群会话或消息历史再决定是否重试。第二次发送可能造成重复更新。
总结
要用 TwexAPI 向 XChat group 发送 DM,调用 POST /v3/twitter/send-dm,并把 recipient 设置为已有 g... group ID。更稳的生产实现是:维护 group registry,要求消息审批,每个已审批草稿只发送一次,并持久化保存返回的 message object。
这样可以让群消息自动化有用,但不会变成不可控的广播。