tweetapi 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,63 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from ..client import TweetAPI
7
+
8
+
9
+ class UnencryptedDMResource:
10
+ def __init__(self, client: TweetAPI) -> None:
11
+ self._client = client
12
+
13
+ def send_dm(self, *, auth_token: str, conversation_id: str, text: str, proxy: str, request_id: Optional[str] = None, media: Optional[list[Any]] = None) -> dict[str, Any]:
14
+ """Send an unencrypted direct message."""
15
+ return self._client._post("/tw-v2/interaction/send-dm", {
16
+ "authToken": auth_token, "conversationId": conversation_id,
17
+ "text": text, "proxy": proxy, "requestId": request_id, "media": media,
18
+ })
19
+
20
+ def get_dm_permissions(self, *, auth_token: str, recipient_ids: str, proxy: Optional[str] = None) -> dict[str, Any]:
21
+ """Check DM permissions for recipients."""
22
+ return self._client._get("/tw-v2/interaction/dm-permissions", {
23
+ "authToken": auth_token, "recipientIds": recipient_ids, "proxy": proxy,
24
+ })
25
+
26
+ def get_inbox_initial_state(self, *, auth_token: str, proxy: Optional[str] = None) -> dict[str, Any]:
27
+ """Get initial inbox state."""
28
+ return self._client._get("/tw-v2/interaction/inbox-initial-state", {
29
+ "authToken": auth_token, "proxy": proxy,
30
+ })
31
+
32
+ def get_inbox_trusted(self, *, auth_token: str, cursor: str, filter_low_quality: Optional[bool] = None, proxy: Optional[str] = None) -> dict[str, Any]:
33
+ """Get trusted inbox conversations."""
34
+ return self._client._get("/tw-v2/interaction/inbox-timeline-trusted", {
35
+ "authToken": auth_token, "cursor": cursor,
36
+ "filterLowQuality": filter_low_quality, "proxy": proxy,
37
+ })
38
+
39
+ def get_inbox_untrusted(self, *, auth_token: str, cursor: str, filter_low_quality: Optional[bool] = None, proxy: Optional[str] = None) -> dict[str, Any]:
40
+ """Get untrusted (message requests) inbox conversations."""
41
+ return self._client._get("/tw-v2/interaction/inbox-timeline-untrusted", {
42
+ "authToken": auth_token, "cursor": cursor,
43
+ "filterLowQuality": filter_low_quality, "proxy": proxy,
44
+ })
45
+
46
+ def get_conversation(self, *, auth_token: str, conversation_id: str, cursor: Optional[str] = None, proxy: Optional[str] = None) -> dict[str, Any]:
47
+ """Get messages in a conversation."""
48
+ return self._client._get("/tw-v2/interaction/conversation", {
49
+ "authToken": auth_token, "conversationId": conversation_id,
50
+ "cursor": cursor, "proxy": proxy,
51
+ })
52
+
53
+ def get_dm_user_updates(self, *, auth_token: str, cursor: str, proxy: Optional[str] = None) -> dict[str, Any]:
54
+ """Get DM user updates."""
55
+ return self._client._get("/tw-v2/interaction/dm-user-updates", {
56
+ "authToken": auth_token, "cursor": cursor, "proxy": proxy,
57
+ })
58
+
59
+ def accept_conversation(self, *, auth_token: str, conversation_id: str, proxy: Optional[str] = None) -> dict[str, Any]:
60
+ """Accept a conversation request."""
61
+ return self._client._post("/tw-v2/interaction/accept-conversation", {
62
+ "authToken": auth_token, "conversationId": conversation_id, "proxy": proxy,
63
+ })
@@ -0,0 +1,75 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from ..client import TweetAPI
7
+
8
+
9
+ class UserResource:
10
+ def __init__(self, client: TweetAPI) -> None:
11
+ self._client = client
12
+
13
+ def get_by_username(self, *, username: str) -> dict[str, Any]:
14
+ """Get a user profile by username."""
15
+ return self._client._get("/tw-v2/user/by-username", {"username": username})
16
+
17
+ def get_by_usernames(self, *, usernames: str) -> dict[str, Any]:
18
+ """Get multiple user profiles by usernames (comma-separated)."""
19
+ return self._client._get("/tw-v2/user/by-usernames", {"usernames": usernames})
20
+
21
+ def get_by_user_id(self, *, user_id: str) -> dict[str, Any]:
22
+ """Get a user profile by user ID."""
23
+ return self._client._get("/tw-v2/user/by-id", {"userId": user_id})
24
+
25
+ def get_by_user_ids(self, *, user_ids: str) -> dict[str, Any]:
26
+ """Get multiple user profiles by user IDs (comma-separated)."""
27
+ return self._client._get("/tw-v2/user/by-ids", {"userIds": user_ids})
28
+
29
+ def get_tweets(self, *, user_id: str, cursor: Optional[str] = None) -> dict[str, Any]:
30
+ """Get a user's tweets."""
31
+ return self._client._get("/tw-v2/user/tweets", {"userId": user_id, "cursor": cursor})
32
+
33
+ def get_tweets_and_replies(self, *, user_id: str, cursor: Optional[str] = None) -> dict[str, Any]:
34
+ """Get a user's tweets and replies."""
35
+ return self._client._get("/tw-v2/user/tweets-and-replies", {"userId": user_id, "cursor": cursor})
36
+
37
+ def get_following(self, *, user_id: str, cursor: Optional[str] = None) -> dict[str, Any]:
38
+ """Get who a user follows (v2 — full user objects)."""
39
+ return self._client._get("/tw-v2/user/following", {"userId": user_id, "cursor": cursor})
40
+
41
+ def get_followers(self, *, user_id: str, cursor: Optional[str] = None) -> dict[str, Any]:
42
+ """Get a user's followers (v2 — full user objects)."""
43
+ return self._client._get("/tw-v2/user/followers", {"userId": user_id, "cursor": cursor})
44
+
45
+ def get_verified_followers(self, *, user_id: str, cursor: Optional[str] = None) -> dict[str, Any]:
46
+ """Get a user's verified followers."""
47
+ return self._client._get("/tw-v2/user/verified-followers", {"userId": user_id, "cursor": cursor})
48
+
49
+ def get_subscriptions(self, *, user_id: str, cursor: Optional[str] = None) -> dict[str, Any]:
50
+ """Get a user's subscriptions."""
51
+ return self._client._get("/tw-v2/user/subscriptions", {"userId": user_id, "cursor": cursor})
52
+
53
+ def get_following_v1(self, *, user_id: str, count: Optional[str] = None, cursor: Optional[str] = None) -> dict[str, Any]:
54
+ """Get who a user follows (v1 — supports count)."""
55
+ return self._client._get("/tw-v2/user/following-list", {"userId": user_id, "count": count, "cursor": cursor})
56
+
57
+ def get_followers_v1(self, *, user_id: str, count: Optional[str] = None, cursor: Optional[str] = None) -> dict[str, Any]:
58
+ """Get a user's followers (v1 — supports count)."""
59
+ return self._client._get("/tw-v2/user/followers-list", {"userId": user_id, "count": count, "cursor": cursor})
60
+
61
+ def get_following_ids(self, *, user_id: str, count: Optional[str] = None, cursor: Optional[str] = None) -> dict[str, Any]:
62
+ """Get IDs of accounts a user is following."""
63
+ return self._client._get("/tw-v2/user/following-ids", {"userId": user_id, "count": count, "cursor": cursor})
64
+
65
+ def get_followers_ids(self, *, user_id: str, count: Optional[str] = None, cursor: Optional[str] = None) -> dict[str, Any]:
66
+ """Get IDs of a user's followers."""
67
+ return self._client._get("/tw-v2/user/followers-ids", {"userId": user_id, "count": count, "cursor": cursor})
68
+
69
+ def check_follow(self, *, subject_id: str, target_id: str) -> dict[str, Any]:
70
+ """Check the follow relationship between two users."""
71
+ return self._client._get("/tw-v2/user/friendship", {"subjectId": subject_id, "targetId": target_id})
72
+
73
+ def about_account(self, *, username: str) -> dict[str, Any]:
74
+ """Get account creation details and transparency information."""
75
+ return self._client._get("/tw-v2/user/about-account", {"username": username})
@@ -0,0 +1,44 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any, Optional, TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from ..client import TweetAPI
7
+
8
+
9
+ class XChatResource:
10
+ def __init__(self, client: TweetAPI) -> None:
11
+ self._client = client
12
+
13
+ def setup(self, *, auth_token: str, user_id: str, pin: str, proxy: Optional[str] = None) -> dict[str, Any]:
14
+ """Initialize X Chat (encrypted DMs) for your account."""
15
+ return self._client._post("/tw-v2/xchat/setup", {
16
+ "authToken": auth_token, "userId": user_id, "pin": pin, "proxy": proxy,
17
+ })
18
+
19
+ def get_conversations(self, *, auth_token: str, cursor: Optional[str] = None, graph_snapshot_id: Optional[str] = None, limit: Optional[int] = None, proxy: Optional[str] = None) -> dict[str, Any]:
20
+ """List encrypted DM conversations."""
21
+ return self._client._post("/tw-v2/xchat/conversations", {
22
+ "authToken": auth_token, "cursor": cursor,
23
+ "graphSnapshotId": graph_snapshot_id, "limit": limit, "proxy": proxy,
24
+ })
25
+
26
+ def send(self, *, auth_token: str, recipient_id: str, message: str, proxy: Optional[str] = None) -> dict[str, Any]:
27
+ """Send an encrypted message."""
28
+ return self._client._post("/tw-v2/xchat/send", {
29
+ "authToken": auth_token, "recipientId": recipient_id,
30
+ "message": message, "proxy": proxy,
31
+ })
32
+
33
+ def get_history(self, *, auth_token: str, conversation_id: str, cursor: Optional[str] = None, limit: Optional[int] = None, proxy: Optional[str] = None) -> dict[str, Any]:
34
+ """Get encrypted conversation history."""
35
+ return self._client._post("/tw-v2/xchat/history", {
36
+ "authToken": auth_token, "conversationId": conversation_id,
37
+ "cursor": cursor, "limit": limit, "proxy": proxy,
38
+ })
39
+
40
+ def can_dm(self, *, auth_token: str, user_ids: list[str], proxy: Optional[str] = None) -> dict[str, Any]:
41
+ """Check if you can send encrypted DMs to users."""
42
+ return self._client._post("/tw-v2/xchat/can-dm", {
43
+ "authToken": auth_token, "userIds": user_ids, "proxy": proxy,
44
+ })
tweetapi/types.py ADDED
@@ -0,0 +1,484 @@
1
+ """Response types for TweetAPI.
2
+
3
+ Uses TypedDict for lightweight, JSON-compatible type hints.
4
+ All types mirror the API's JSON response shapes exactly.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ from typing import Any, List as TypingList, Optional, TypedDict
10
+
11
+
12
+ # ─── Response Wrappers ───────────────────────────────────────────────────────
13
+
14
+
15
+ class ApiResponse(TypedDict):
16
+ data: Any
17
+
18
+
19
+ class Pagination(TypedDict):
20
+ nextCursor: Optional[str]
21
+ prevCursor: Optional[str]
22
+
23
+
24
+ class PaginatedResponse(TypedDict):
25
+ data: list[Any]
26
+ pagination: Pagination
27
+
28
+
29
+ class SearchMeta(TypedDict):
30
+ query: str
31
+ resultType: str
32
+ resultCount: int
33
+ completedIn: int
34
+
35
+
36
+ class SearchResponse(TypedDict):
37
+ data: list[Any]
38
+ pagination: Pagination
39
+ meta: SearchMeta
40
+
41
+
42
+ class ActionData(TypedDict, total=False):
43
+ id: str
44
+ action: str
45
+ timestamp: str
46
+ success: bool
47
+ message: str
48
+ metadata: dict[str, Any]
49
+
50
+
51
+ class ActionResponse(TypedDict):
52
+ data: ActionData
53
+
54
+
55
+ # ─── User ────────────────────────────────────────────────────────────────────
56
+
57
+
58
+ class Professional(TypedDict, total=False):
59
+ type: Optional[str]
60
+ category: list[str]
61
+ restId: Optional[str]
62
+
63
+
64
+ class BusinessAccount(TypedDict):
65
+ affiliatesCount: int
66
+
67
+
68
+ class HighlightsInfo(TypedDict):
69
+ canHighlight: bool
70
+ highlightedTweetsCount: str
71
+
72
+
73
+ class User(TypedDict, total=False):
74
+ id: str
75
+ username: str
76
+ name: str
77
+ bio: str
78
+ location: Optional[str]
79
+ website: Optional[str]
80
+ pinnedTweetIds: list[str]
81
+ avatar: Optional[str]
82
+ banner: Optional[str]
83
+ profileImageShape: Optional[str]
84
+ verified: bool
85
+ isBlueVerified: bool
86
+ verifiedType: Optional[str]
87
+ verifiedSince: Optional[str]
88
+ isIdentityVerified: bool
89
+ isProtected: bool
90
+ possiblySensitive: bool
91
+ profileInterstitialType: Optional[str]
92
+ withheldInCountries: list[str]
93
+ professional: Optional[Professional]
94
+ businessAccount: Optional[BusinessAccount]
95
+ creatorSubscriptionsCount: int
96
+ hasHiddenSubscriptions: bool
97
+ highlightsInfo: Optional[HighlightsInfo]
98
+ hasGraduatedAccess: bool
99
+ isProfileTranslatable: bool
100
+ hasCustomTimelines: bool
101
+ isTranslator: bool
102
+ affiliatesHighlightedLabel: Optional[dict[str, Any]]
103
+ defaultProfile: bool
104
+ defaultProfileImage: bool
105
+ followerCount: int
106
+ followingCount: int
107
+ tweetCount: int
108
+ listedCount: int
109
+ mediaCount: int
110
+ favoritesCount: int
111
+ createdAt: Optional[str]
112
+
113
+
114
+ class UserRelationship(TypedDict, total=False):
115
+ sourceId: str
116
+ targetId: str
117
+ following: bool
118
+ followedBy: bool
119
+ blocking: bool
120
+ blockedBy: bool
121
+ muting: bool
122
+ notificationsEnabled: bool
123
+ canDm: bool
124
+ canMediaTag: bool
125
+ wantRetweets: bool
126
+ markedSpam: bool
127
+ followRequestSent: bool
128
+ followRequestReceived: bool
129
+ allReplies: bool
130
+
131
+
132
+ class UserAnalytics(TypedDict, total=False):
133
+ userId: str
134
+ period: str
135
+ impressions: int
136
+ engagements: int
137
+ engagementRate: float
138
+ linkClicks: int
139
+ profileVisits: int
140
+ mentions: int
141
+ newFollowers: int
142
+ topTweet: Optional[str]
143
+ detailedMetrics: dict[str, int]
144
+
145
+
146
+ # ─── Media ───────────────────────────────────────────────────────────────────
147
+
148
+
149
+ class MediaSize(TypedDict, total=False):
150
+ width: int
151
+ height: int
152
+ resize: str
153
+ url: str
154
+
155
+
156
+ class VideoVariant(TypedDict, total=False):
157
+ bitrate: Optional[int]
158
+ contentType: str
159
+ url: str
160
+
161
+
162
+ class MediaAvailability(TypedDict):
163
+ status: str
164
+ reason: Optional[str]
165
+
166
+
167
+ class Media(TypedDict, total=False):
168
+ id: str
169
+ key: str
170
+ type: str # "photo" | "video" | "animated_gif"
171
+ url: str
172
+ displayUrl: str
173
+ expandedUrl: str
174
+ thumbnailUrl: Optional[str]
175
+ width: int
176
+ height: int
177
+ aspectRatio: list[int]
178
+ sizes: dict[str, MediaSize]
179
+ duration: Optional[float]
180
+ bitrate: Optional[int]
181
+ videoInfo: Optional[dict[str, Any]]
182
+ altText: Optional[str]
183
+ sensitiveMedia: bool
184
+ mediaAvailability: MediaAvailability
185
+ allowDownload: bool
186
+ mediaStats: Optional[dict[str, int]]
187
+ sourceStatusId: Optional[str]
188
+ sourceUserId: Optional[str]
189
+
190
+
191
+ # ─── Poll ────────────────────────────────────────────────────────────────────
192
+
193
+
194
+ class PollOption(TypedDict):
195
+ position: int
196
+ label: str
197
+ voteCount: int
198
+ percentage: float
199
+
200
+
201
+ class Poll(TypedDict, total=False):
202
+ id: str
203
+ options: list[PollOption]
204
+ endDatetime: str
205
+ durationMinutes: int
206
+ votingStatus: str
207
+ totalVotes: int
208
+
209
+
210
+ # ─── Card ────────────────────────────────────────────────────────────────────
211
+
212
+
213
+ class CardBindingValues(TypedDict, total=False):
214
+ title: str
215
+ description: str
216
+ domain: str
217
+ thumbnailImageUrl: Optional[str]
218
+ thumbnailImageColor: Optional[str]
219
+ playerUrl: Optional[str]
220
+ playerWidth: Optional[int]
221
+ playerHeight: Optional[int]
222
+ appId: Optional[str]
223
+ appName: Optional[str]
224
+
225
+
226
+ class Card(TypedDict, total=False):
227
+ name: str
228
+ url: str
229
+ cardType: str
230
+ type: str
231
+ bindingValues: CardBindingValues
232
+ vanityUrl: Optional[str]
233
+
234
+
235
+ # ─── Place ───────────────────────────────────────────────────────────────────
236
+
237
+
238
+ class Place(TypedDict, total=False):
239
+ id: str
240
+ fullName: str
241
+ name: str
242
+ country: str
243
+ countryCode: str
244
+ placeType: str
245
+ url: str
246
+
247
+
248
+ # ─── Tweet ───────────────────────────────────────────────────────────────────
249
+
250
+
251
+ class ReplyTo(TypedDict):
252
+ tweetId: str
253
+ userId: str
254
+ username: str
255
+
256
+
257
+ class Mention(TypedDict):
258
+ id: str
259
+ username: str
260
+ name: str
261
+
262
+
263
+ class EditControl(TypedDict, total=False):
264
+ editTweetIds: list[str]
265
+ editableUntil: str
266
+ isEditEligible: bool
267
+ editsRemaining: int
268
+
269
+
270
+ class BirdwatchPivot(TypedDict, total=False):
271
+ calloutText: str
272
+ shortTitle: str
273
+ noteId: str
274
+ iconType: str
275
+ destinationUrl: str
276
+
277
+
278
+ class ConversationControl(TypedDict, total=False):
279
+ policy: str
280
+ allowedUserIds: list[str]
281
+
282
+
283
+ class Tweet(TypedDict, total=False):
284
+ id: str
285
+ conversationId: Optional[str]
286
+ text: str
287
+ displayTextRange: list[int]
288
+ author: User
289
+ source: Optional[str]
290
+ type: str # "tweet" | "reply" | "quote" | "retweet" | "thread"
291
+ replyTo: Optional[ReplyTo]
292
+ quotedTweet: Optional[Any] # recursive Tweet
293
+ retweetedTweet: Optional[Any]
294
+ likeCount: int
295
+ retweetCount: int
296
+ replyCount: int
297
+ quoteCount: int
298
+ bookmarkCount: int
299
+ viewCount: Optional[int]
300
+ media: Optional[list[Media]]
301
+ poll: Optional[Poll]
302
+ card: Optional[Card]
303
+ hashtags: list[str]
304
+ mentions: list[Mention]
305
+ urls: list[str]
306
+ symbols: list[str]
307
+ possiblySensitive: bool
308
+ limitedActions: Optional[str]
309
+ hasAIGeneratedMedia: bool
310
+ isPaidPromotion: bool
311
+ isEdited: bool
312
+ editControl: Optional[EditControl]
313
+ isTranslatable: bool
314
+ lang: str
315
+ translatedText: Optional[str]
316
+ hasBirdwatchNotes: bool
317
+ birdwatchPivot: Optional[BirdwatchPivot]
318
+ conversationControl: Optional[ConversationControl]
319
+ isPromoted: bool
320
+ communityId: Optional[str]
321
+ createdAt: Optional[str]
322
+ place: Optional[Place]
323
+
324
+
325
+ class TweetTranslation(TypedDict):
326
+ text: str
327
+ lang: str
328
+ sourceLanguage: str
329
+ destinationLanguage: str
330
+ translationSource: str
331
+
332
+
333
+ # ─── List ────────────────────────────────────────────────────────────────────
334
+
335
+
336
+ class List(TypedDict, total=False):
337
+ id: str
338
+ name: str
339
+ description: str
340
+ mode: str # "public" | "private"
341
+ owner: User
342
+ bannerUrl: Optional[str]
343
+ facepileUrls: list[str]
344
+ memberCount: int
345
+ subscriberCount: int
346
+ createdAt: Optional[str]
347
+ slug: str
348
+ uri: str
349
+
350
+
351
+ # ─── Community ───────────────────────────────────────────────────────────────
352
+
353
+
354
+ class CommunityRule(TypedDict):
355
+ id: str
356
+ name: str
357
+ description: str
358
+ order: int
359
+ createdAt: str
360
+
361
+
362
+ class Community(TypedDict, total=False):
363
+ id: str
364
+ name: str
365
+ description: str
366
+ bannerUrl: Optional[str]
367
+ avatarUrl: Optional[str]
368
+ rules: list[CommunityRule]
369
+ memberCount: int
370
+ moderatorCount: int
371
+ adminCount: int
372
+ isPrivate: bool
373
+ pinnedTweetId: Optional[str]
374
+ createdAt: Optional[str]
375
+ isMember: bool
376
+ isAdmin: bool
377
+ isModerator: bool
378
+ canPost: bool
379
+
380
+
381
+ class CommunityMember(TypedDict, total=False):
382
+ user: User
383
+ role: str # "member" | "moderator" | "admin"
384
+ joinedAt: str
385
+
386
+
387
+ class CommunitySearchResult(TypedDict, total=False):
388
+ id: str
389
+ name: str
390
+ memberCount: int
391
+ topic: Optional[str]
392
+ isNsfw: bool
393
+ bannerUrl: Optional[str]
394
+ defaultBannerUrl: Optional[str]
395
+ membersFacepile: list[str]
396
+
397
+
398
+ # ─── Space ───────────────────────────────────────────────────────────────────
399
+
400
+
401
+ class SpaceParticipant(TypedDict, total=False):
402
+ periscopeUserId: str
403
+ twitterUserId: str
404
+ username: str
405
+ displayName: str
406
+ avatarUrl: str
407
+ isVerified: bool
408
+ isBlueVerified: bool
409
+
410
+
411
+ class SpaceTopic(TypedDict):
412
+ id: str
413
+ name: str
414
+
415
+
416
+ class SpaceParticipants(TypedDict, total=False):
417
+ admins: list[SpaceParticipant]
418
+ speakers: list[SpaceParticipant]
419
+ listeners: list[SpaceParticipant]
420
+ total: int
421
+
422
+
423
+ class Space(TypedDict, total=False):
424
+ id: str
425
+ title: str
426
+ state: str # "Running" | "Ended" | "Scheduled" | "Canceled"
427
+ mediaKey: str
428
+ createdAt: int
429
+ scheduledStart: Optional[int]
430
+ startedAt: Optional[int]
431
+ endedAt: Optional[int]
432
+ updatedAt: Optional[int]
433
+ creator: Optional[User]
434
+ totalLiveListeners: int
435
+ totalReplayWatched: int
436
+ participants: SpaceParticipants
437
+ isAvailableForReplay: bool
438
+ topics: list[SpaceTopic]
439
+ tweetId: Optional[str]
440
+
441
+
442
+ class SpaceStreamInfo(TypedDict, total=False):
443
+ hlsUrl: str
444
+ status: str
445
+ streamType: str
446
+ shareUrl: Optional[str]
447
+
448
+
449
+ # ─── Notification ────────────────────────────────────────────────────────────
450
+
451
+
452
+ class Notification(TypedDict, total=False):
453
+ id: str
454
+ type: str
455
+ createdAt: str
456
+ message: str
457
+ icon: str
458
+ fromUsers: list[str]
459
+ targetTweetId: Optional[str]
460
+ targetUserId: Optional[str]
461
+ seen: bool
462
+
463
+
464
+ # ─── Auth ────────────────────────────────────────────────────────────────────
465
+
466
+
467
+ class LoginCookies(TypedDict):
468
+ auth_token: str
469
+ ct0: str
470
+ twid: str
471
+ kdt: str
472
+ __cf_bm: str
473
+
474
+
475
+ class LoginUser(TypedDict):
476
+ id: str
477
+ username: str
478
+ name: str
479
+
480
+
481
+ class LoginResponse(TypedDict):
482
+ cookies: LoginCookies
483
+ user: LoginUser
484
+ timestamp: str