TwexAPI で XChat グループ DM 履歴を取得する方法
グループ DM の履歴は、1:1 の export よりも少し丁寧な構造が必要です。1 つの message stream に customer、support owner、campaign manager、reviewer が混在するため、text だけでなく group ID、sender ID、timestamp、attachments、archive がどこで止まったかを示す pagination cursor も残す必要があります。
TwexAPI の Get DM History (XChat v3) endpoint は、POST /v3/twitter/dm-history で XChat history を取得します。group conversation では、g... で始まる既存 group ID を recipient に渡します。
この endpoint は既存 conversation を読むためのものです。group 作成、member 追加、message 送信は行いません。
Endpoint と Group Request Fields
既知の XChat group ID を使って POST /v3/twitter/dm-history を呼びます。
| Field | Type | Required | Notes |
|---|---|---|---|
recipient | string | Yes | g... で始まる既存 group ID。 |
cookie | string | Yes | X authentication cookie または auth_token。server-side に保管します。 |
pin | string or null | No | Identity PIN。API default は 1234。 |
count | integer or null | No | Page size は 10 から 200。default は 50。 |
all | boolean or null | No | true の場合、has_more をたどって全ページを返します。大きい group history では慎重に使います。 |
before | string or null | No | Manual pagination では、前 page の最古 message sequence_id を渡します。 |
proxy | string or null | No | Optional HTTP proxy。 |
Basic Group History Request
curl --request POST \
--url https://api.twexapi.io/v3/twitter/dm-history \
--header "Authorization: Bearer $TWEXAPI_BEARER_TOKEN" \
--header "Content-Type: application/json" \
--data '{
"recipient": "g1234567890",
"cookie": "auth_token=...; ct0=...; twid=...",
"pin": "1234",
"count": 50,
"all": false
}'response は conversation_id、has_more、messages を含みます。
1{
2 "code": 200,
3 "msg": "success",
4 "data": {
5 "conversation_id": "g1234567890",
6 "has_more": true,
7 "messages": [
8 {
9 "id": "1234567890123456789",
10 "text": "Launch report is ready for review.",
11 "time": "2024-01-01T12:00:00Z",
12 "sequence_id": "2076661179334979585",
13 "sender_id": "1928096185664843776",
14 "recipient_id": "g1234567890",
15 "attachment": null,
16 "attachments": null
17 }
18 ]
19 }
20}has_more が true の場合は、現在 page の最古 message の sequence_id を取り、次の request の before として渡します。
Python Group History Exporter
未確認の user input から raw g... value を直接受け取るのではなく、group registry を使います。これにより export が明確な business purpose と結びつきます。
1import os
2from datetime import datetime, timezone
3from typing import Any
4
5import requests
6
7API_URL = "https://api.twexapi.io/v3/twitter/dm-history"
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 fetch_group_history_page(
27 group_key: str,
28 *,
29 before: str | None = None,
30 count: int = 50,
31) -> dict[str, Any]:
32 if count < 10 or count > 200:
33 raise ValueError("count must be between 10 and 200")
34
35 payload: dict[str, Any] = {
36 "recipient": resolve_group_id(group_key),
37 "cookie": X_COOKIE,
38 "pin": XCHAT_PIN,
39 "count": count,
40 "all": False,
41 }
42 if before:
43 payload["before"] = before
44 if PROXY:
45 payload["proxy"] = PROXY
46
47 response = requests.post(
48 API_URL,
49 headers={
50 "Authorization": f"Bearer {BEARER_TOKEN}",
51 "Content-Type": "application/json",
52 "Accept": "application/json",
53 },
54 json=payload,
55 timeout=60,
56 )
57 response.raise_for_status()
58 data = response.json()
59
60 if data.get("code") != 200:
61 raise RuntimeError(data.get("msg", "TwexAPI returned an error"))
62
63 return data["data"]
64
65def normalize_group_message(
66 group_key: str,
67 group_id: str,
68 conversation_id: str,
69 message: dict[str, Any],
70) -> dict[str, Any]:
71 return {
72 "group_key": group_key,
73 "group_id": group_id,
74 "conversation_id": conversation_id,
75 "message_id": message["id"],
76 "text": message.get("text"),
77 "time": message.get("time"),
78 "sequence_id": message.get("sequence_id"),
79 "sender_id": message.get("sender_id"),
80 "recipient_id": message.get("recipient_id"),
81 "attachment": message.get("attachment"),
82 "attachments": message.get("attachments"),
83 "fetched_at": datetime.now(timezone.utc).isoformat(),
84 "raw": message,
85 }
86
87def export_group_history(
88 group_key: str,
89 *,
90 max_pages: int = 10,
91) -> list[dict[str, Any]]:
92 before = None
93 rows: list[dict[str, Any]] = []
94 group_id = resolve_group_id(group_key)
95
96 for _ in range(max_pages):
97 page = fetch_group_history_page(group_key, before=before)
98 conversation_id = page["conversation_id"]
99 messages = page.get("messages") or []
100
101 rows.extend(
102 normalize_group_message(group_key, group_id, conversation_id, msg)
103 for msg in messages
104 )
105
106 if not page.get("has_more") or not messages:
107 break
108
109 oldest = min(
110 messages,
111 key=lambda msg: int(msg.get("sequence_id") or 0),
112 )
113 before = oldest.get("sequence_id")
114 if not before:
115 break
116
117 return rows
118
119if __name__ == "__main__":
120 exported = export_group_history("launch_ops", max_pages=5)
121 print(f"Exported {len(exported)} group messages")production では、次 page を取得する前に現在 page を保存します。3 page 目の後に export が失敗した場合は、最後に保存した before から再開し、全 archive を再取得しません。
Group History Workflow
- Register the group: 許可された
g...ID、internal key、owner、purpose、allowed reader account を保存します。 - Request a page:
POST /v3/twitter/dm-historyを呼び、all: false、page size 10〜200 を使います。 - Persist the page: checkpoint 更新前に、normalized rows と raw message JSON を保存します。
- Advance the cursor: 現在 page の最古 message
sequence_idを次のbeforeにします。 - Stop deliberately:
has_moreが false、message が空、または configured page limit に達したら停止します。
Page limit は意図的なものです。Group history は長くなるため、bounded job のほうが monitor と retry が簡単です。
When To Use all=true
all=true は、小さな group conversation を 1 回の call で完全 export したい場合に便利です。operations log、customer evidence、compliance-style archive では、page-level recovery がなくなるため向いていません。
Recurring export では all: false を維持し、cursor を保存しながら 1 page ずつ進めます。
Fields To Store
| Field | Why it matters |
|---|---|
group_key | Human-readable internal group label。 |
group_id | request で使った XChat g... recipient value。 |
conversation_id | API が返す conversation key。 |
message_id | Deduplication と lookup。 |
sequence_id | Pagination checkpoint と ordering signal。 |
sender_id | どの participant が書いた message かを示します。 |
recipient_id | message が expected group に属することを確認します。 |
time | Conversation timeline。 |
text | Search、ticket notes、review context。 |
attachment and attachments | Media evidence と XChat v3 attachment metadata。 |
fetched_at | Snapshot capture time。 |
| Raw message JSON | Schema が変わったときに reprocess できます。 |
Full account cookie を archive に保存しないでください。Credential reference だけを保存し、secret manager に secret を置きます。
Common Mistakes
- Group workflow のはずなのに user ID や
@handleを渡す。 - Export form で未確認の raw
g...ID を入力させる。 - Newest message の
sequence_idをbeforeに使う。 - 長い group history に
all=trueを使い、checkpoint recovery を失う。 - 最初の page が text だけだったため attachment fields を捨てる。
Wrap-up
POST /v3/twitter/dm-history に g... recipient を渡すと、既存 XChat group history を取得できます。信頼できる pattern は、group registry、最古 sequence_id による manual pagination、raw JSON storage、clean resume できる checkpoint です。