如何用 TwexAPI 获取 XChat DM 媒体
很多私信记录不只有文字。客服工单可能依赖一张截图,创作者审核可能包含一段视频,合规归档也需要说明某个文件来自哪个会话。
TwexAPI 的 Get DM Media (XChat v3) 端点可以把一个 XChat attachment 解析成可播放媒体 URL。输入来自 Get DM History (XChat v3) 返回的 attachments:使用 message 对应的 conversation_id、attachment 的 media_hash_key,以及可选但建议传入的 key_version。
这个媒体端点不会自己搜索会话。正确流程是先读取会话历史,保存附件元数据,再针对需要的附件调用 POST /v3/twitter/dm-media。
端点和请求字段
使用 POST /v3/twitter/dm-media 获取一个 XChat 媒体附件。
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
conversation_id | string | 是 | 来自 /v3/twitter/dm-history 的 conversation ID。 |
media_hash_key | string | 是 | DM history attachment 对象里的 media_hash_key。 |
cookie | string | 是 | X 认证 cookie 或 auth_token。只保存在服务端。 |
key_version | string 或 null | 否 | 来自 DM history 的 message key version。能拿到时建议传。 |
type_name | string 或 null | 否 | 可选提示,例如 IMAGE、VIDEO 或 GIF。 |
filename | string 或 null | 否 | 来自 attachment 对象的可选文件名提示。 |
pin | string 或 null | 否 | Identity PIN。API 默认值是 1234。 |
proxy | string 或 null | 否 | 可选 HTTP proxy。 |
基础请求
1curl --request POST \
2 --url https://api.twexapi.io/v3/twitter/dm-media \
3 --header "Authorization: Bearer $TWEXAPI_BEARER_TOKEN" \
4 --header "Content-Type: application/json" \
5 --data '{
6 "conversation_id": "1928096185664843776:1989842918455291904",
7 "media_hash_key": "blRIlZCVIO",
8 "key_version": "3",
9 "type_name": "VIDEO",
10 "filename": "clip.mp4",
11 "cookie": "auth_token=...; ct0=...; twid=...",
12 "pin": "1234"
13 }'响应会返回可播放 URL 和文件元数据。
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "url": "https://api.twexapi.io/media/xchat/...",
6 "path": "/media/xchat/...",
7 "content_type": "video/mp4",
8 "filename": "clip.mp4",
9 "byte_length": 421299,
10 "cached": true
11 }
12}建议把返回的 metadata 和原始 message、attachment 记录存在一起。如果产品需要长期留存,再根据你的隐私和保留策略,把返回的 url 下载到自己的存储里。
从 DM History 获取附件输入
/v3/twitter/dm-history 可能在 message 上返回 attachments。这些字段就是 /v3/twitter/dm-media 的输入。
1{
2 "conversation_id": "1928096185664843776:1989842918455291904",
3 "messages": [
4 {
5 "id": "1234567890123456789",
6 "text": "视频在这里。",
7 "sequence_id": "2076661179334979585",
8 "attachments": [
9 {
10 "attachment_id": "cda5ccdd-af53-48a3-bdd1-5039a73aea25",
11 "conversation_id": "1928096185664843776:1989842918455291904",
12 "filename": "clip.mp4",
13 "filesize_bytes": 421299,
14 "key_version": "3",
15 "media_hash_key": "blRIlZCVIO",
16 "type": "video",
17 "type_name": "VIDEO"
18 }
19 ]
20 }
21 ]
22}如果 attachment 对象里没有 conversation_id,就使用当前页返回的 page-level conversation_id。
Python 媒体解析脚本
这个脚本接收已保存的 DM history attachment 对象,解析可播放 URL,并返回一条可和 message 一起保存的规范化记录。
1import os
2from datetime import datetime, timezone
3from typing import Any
4
5import requests
6
7API_URL = "https://api.twexapi.io/v3/twitter/dm-media"
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
13def build_media_payload(
14 conversation_id: str,
15 attachment: dict[str, Any],
16) -> dict[str, Any]:
17 media_hash_key = attachment.get("media_hash_key")
18 if not media_hash_key:
19 raise ValueError("attachment.media_hash_key is required")
20
21 payload: dict[str, Any] = {
22 "conversation_id": attachment.get("conversation_id") or conversation_id,
23 "media_hash_key": media_hash_key,
24 "cookie": X_COOKIE,
25 "pin": XCHAT_PIN,
26 }
27
28 for field in ("key_version", "type_name", "filename"):
29 if attachment.get(field):
30 payload[field] = attachment[field]
31
32 if PROXY:
33 payload["proxy"] = PROXY
34
35 return payload
36
37def fetch_dm_media(
38 conversation_id: str,
39 attachment: dict[str, Any],
40) -> dict[str, Any]:
41 response = requests.post(
42 API_URL,
43 headers={
44 "Authorization": f"Bearer {BEARER_TOKEN}",
45 "Content-Type": "application/json",
46 "Accept": "application/json",
47 },
48 json=build_media_payload(conversation_id, attachment),
49 timeout=60,
50 )
51 response.raise_for_status()
52 data = response.json()
53
54 if data.get("code") != 200:
55 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
56
57 return data["data"]
58
59def normalize_media_result(
60 *,
61 conversation_id: str,
62 message_id: str,
63 attachment: dict[str, Any],
64 media: dict[str, Any],
65) -> dict[str, Any]:
66 return {
67 "conversation_id": conversation_id,
68 "message_id": message_id,
69 "attachment_id": attachment.get("attachment_id"),
70 "media_hash_key": attachment.get("media_hash_key"),
71 "key_version": attachment.get("key_version"),
72 "type_name": attachment.get("type_name"),
73 "source_filename": attachment.get("filename"),
74 "playable_url": media.get("url"),
75 "api_path": media.get("path"),
76 "content_type": media.get("content_type"),
77 "resolved_filename": media.get("filename"),
78 "byte_length": media.get("byte_length"),
79 "cached": media.get("cached"),
80 "resolved_at": datetime.now(timezone.utc).isoformat(),
81 "raw_attachment": attachment,
82 "raw_media": media,
83 }
84
85if __name__ == "__main__":
86 conversation_id = "1928096185664843776:1989842918455291904"
87 message_id = "1234567890123456789"
88 attachment = {
89 "attachment_id": "cda5ccdd-af53-48a3-bdd1-5039a73aea25",
90 "filename": "clip.mp4",
91 "key_version": "3",
92 "media_hash_key": "blRIlZCVIO",
93 "type_name": "VIDEO",
94 }
95
96 media = fetch_dm_media(conversation_id, attachment)
97 print(
98 normalize_media_result(
99 conversation_id=conversation_id,
100 message_id=message_id,
101 attachment=attachment,
102 media=media,
103 )
104 )先在可信服务端环境里设置 TWEXAPI_BEARER_TOKEN、TWEXAPI_X_COOKIE 和 TWEXAPI_XCHAT_PIN,再运行:
python resolve_xchat_dm_media.py不要在浏览器里调用这个接口。请求需要 X cookie,这类凭据应该留在服务端。
媒体导出工作流
- 读取历史:调用
POST /v3/twitter/dm-history,保存 messages 和其中的attachments。 - 创建附件任务:每个带
media_hash_key的 attachment 创建一个 media job。 - 解析媒体:调用
POST /v3/twitter/dm-media,传入conversation_id、media_hash_key,以及可用时的key_version。 - 保存元数据:保存
url、path、content_type、filename、byte_length、cached和原始响应。 - 谨慎归档:如果要下载文件,请放进你自己的存储,并配置访问控制和保留规则。
这样媒体流水线才可解释:每个文件都能追溯到产生它的 conversation、message、attachment 和 API response。
应该保存哪些字段
| 字段 | 作用 |
|---|---|
conversation_id | 把媒体关联回原始 XChat 会话。 |
message_id | 把文件关联到包含它的 message。 |
attachment_id | 可用时用于 attachment 级别去重。 |
media_hash_key | 解析媒体所需的输入。 |
key_version | 来自 DM history 的推荐输入。 |
type_name | 帮助区分 image、video 和 GIF。 |
url | 媒体端点返回的可播放 URL。 |
path | 随 URL 返回的 API media path。 |
content_type | MIME type,用于渲染和存储决策。 |
filename | 建议文件名。 |
byte_length | 文件大小,单位是 bytes。 |
cached | 说明媒体是否已经可用。 |
| 原始 attachment 和 response JSON | 字段映射变化时可重新处理。 |
不要把完整账号 cookie 存在媒体记录里。保存凭据引用即可。
常见错误
- 没有先保存
/v3/twitter/dm-history的 attachment 元数据,就直接调用/v3/twitter/dm-media。 - 把 message
id或sequence_id当成media_hash_key传入。 - attachment 里有
key_version,但请求时没有带上。 - 把 cookie、可播放 URL 或下载后的文件写进团队无法审计的位置。
- 在需要长期留存的场景里,只保存 media URL,而没有把文件归档到自己的存储。
总结
先用 dm-history 找到 attachment,再调用 POST /v3/twitter/dm-media。请求里传 conversation_id、media_hash_key,有 key_version 时也一起传,然后把返回的可播放 URL 和媒体元数据保存到原始 message 记录旁边。