xfeed 0.1.0
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.
- package/LICENSE +23 -0
- package/README.md +5 -0
- package/package.json +43 -0
- package/src/api/actions.ts +16 -0
- package/src/api/client.test.ts +3370 -0
- package/src/api/client.ts +4319 -0
- package/src/api/query-ids.json +11 -0
- package/src/api/query-ids.test.ts +118 -0
- package/src/api/query-ids.ts +59 -0
- package/src/api/runtime-query-ids.test.ts +926 -0
- package/src/api/runtime-query-ids.ts +389 -0
- package/src/api/types.ts +581 -0
- package/src/app.tsx +664 -0
- package/src/auth/browser-detect.ts +150 -0
- package/src/auth/browser-picker.ts +118 -0
- package/src/auth/check.test.preload.ts +94 -0
- package/src/auth/check.test.ts +388 -0
- package/src/auth/check.ts +220 -0
- package/src/auth/cookies.test.ts +529 -0
- package/src/auth/cookies.ts +299 -0
- package/src/auth/manual-entry.ts +88 -0
- package/src/auth/session.ts +30 -0
- package/src/components/ErrorBanner.tsx +172 -0
- package/src/components/Footer.tsx +90 -0
- package/src/components/Header.tsx +57 -0
- package/src/components/NotificationItem.test.ts +252 -0
- package/src/components/NotificationItem.tsx +80 -0
- package/src/components/NotificationList.test.ts +328 -0
- package/src/components/NotificationList.tsx +157 -0
- package/src/components/PostCard.tsx +186 -0
- package/src/components/PostList.tsx +232 -0
- package/src/components/QuotedPostCard.tsx +55 -0
- package/src/components/ReplyPreviewCard.tsx +80 -0
- package/src/components/ThreadView.prototype.tsx +533 -0
- package/src/components/Toast.tsx +28 -0
- package/src/config/loader.ts +69 -0
- package/src/config/types.ts +27 -0
- package/src/contexts/ModalContext.tsx +227 -0
- package/src/experiments/TimelineScreenExperimental.tsx +202 -0
- package/src/experiments/index.tsx +43 -0
- package/src/experiments/query-client.ts +132 -0
- package/src/experiments/use-bookmark-mutation.ts +342 -0
- package/src/experiments/use-bookmarks-query.ts +166 -0
- package/src/experiments/use-notifications-query.ts +368 -0
- package/src/experiments/use-post-detail-query.ts +187 -0
- package/src/experiments/use-profile-query.ts +162 -0
- package/src/experiments/use-timeline-query.ts +201 -0
- package/src/hooks/.gitkeep +0 -0
- package/src/hooks/useActions.ts +354 -0
- package/src/hooks/useBookmarkFolders.ts +70 -0
- package/src/hooks/useBookmarks.ts +111 -0
- package/src/hooks/useCountdown.ts +75 -0
- package/src/hooks/useListNavigation.test.ts +273 -0
- package/src/hooks/useListNavigation.ts +118 -0
- package/src/hooks/useNavigation.test.ts +340 -0
- package/src/hooks/useNavigation.ts +103 -0
- package/src/hooks/useNotifications.test.ts +377 -0
- package/src/hooks/useNotifications.ts +117 -0
- package/src/hooks/usePaginatedData.ts +217 -0
- package/src/hooks/usePostDetail.ts +137 -0
- package/src/hooks/useThread.prototype.ts +314 -0
- package/src/hooks/useTimeline.ts +136 -0
- package/src/hooks/useUserProfile.ts +142 -0
- package/src/index.tsx +304 -0
- package/src/lib/colors.ts +41 -0
- package/src/lib/format.ts +69 -0
- package/src/lib/media.ts +464 -0
- package/src/lib/result.ts +6 -0
- package/src/lib/text.tsx +76 -0
- package/src/modals/BookmarkFolderSelector.tsx +260 -0
- package/src/modals/ExitConfirmationModal.tsx +131 -0
- package/src/modals/FolderPicker.tsx +281 -0
- package/src/modals/README.md +171 -0
- package/src/modals/SessionExpiredModal.tsx +47 -0
- package/src/modals/index.ts +4 -0
- package/src/screens/.gitkeep +0 -0
- package/src/screens/BookmarksScreen.tsx +168 -0
- package/src/screens/NotificationsScreen.tsx +172 -0
- package/src/screens/PostDetailScreen.tsx +976 -0
- package/src/screens/ProfileScreen.tsx +528 -0
- package/src/screens/SplashScreen.tsx +72 -0
- package/src/screens/ThreadScreen.tsx +81 -0
- package/src/screens/TimelineScreen.tsx +188 -0
- package/vendor/sweet-cookie/LICENSE +22 -0
- package/vendor/sweet-cookie/README.md +29 -0
- package/vendor/sweet-cookie/dist/index.d.ts +3 -0
- package/vendor/sweet-cookie/dist/index.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/index.js +2 -0
- package/vendor/sweet-cookie/dist/index.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chrome.d.ts +10 -0
- package/vendor/sweet-cookie/dist/providers/chrome.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chrome.js +27 -0
- package/vendor/sweet-cookie/dist/providers/chrome.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/crypto.d.ts +11 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/crypto.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/crypto.js +100 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/crypto.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/linuxKeyring.d.ts +25 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/linuxKeyring.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/linuxKeyring.js +104 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/linuxKeyring.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/shared.d.ts +10 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/shared.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/shared.js +293 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/shared.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/windowsDpapi.d.ts +10 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/windowsDpapi.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/windowsDpapi.js +26 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqlite/windowsDpapi.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteLinux.d.ts +7 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteLinux.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteLinux.js +51 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteLinux.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteMac.d.ts +10 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteMac.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteMac.js +118 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteMac.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteWindows.d.ts +7 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteWindows.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteWindows.js +38 -0
- package/vendor/sweet-cookie/dist/providers/chromeSqliteWindows.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/linuxPaths.d.ts +5 -0
- package/vendor/sweet-cookie/dist/providers/chromium/linuxPaths.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/linuxPaths.js +33 -0
- package/vendor/sweet-cookie/dist/providers/chromium/linuxPaths.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/macosKeychain.d.ts +24 -0
- package/vendor/sweet-cookie/dist/providers/chromium/macosKeychain.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/macosKeychain.js +30 -0
- package/vendor/sweet-cookie/dist/providers/chromium/macosKeychain.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/paths.d.ts +11 -0
- package/vendor/sweet-cookie/dist/providers/chromium/paths.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/paths.js +43 -0
- package/vendor/sweet-cookie/dist/providers/chromium/paths.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsMasterKey.d.ts +8 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsMasterKey.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsMasterKey.js +41 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsMasterKey.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsPaths.d.ts +8 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsPaths.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsPaths.js +53 -0
- package/vendor/sweet-cookie/dist/providers/chromium/windowsPaths.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edge.d.ts +8 -0
- package/vendor/sweet-cookie/dist/providers/edge.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edge.js +27 -0
- package/vendor/sweet-cookie/dist/providers/edge.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteLinux.d.ts +7 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteLinux.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteLinux.js +53 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteLinux.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteMac.d.ts +8 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteMac.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteMac.js +60 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteMac.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteWindows.d.ts +7 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteWindows.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteWindows.js +38 -0
- package/vendor/sweet-cookie/dist/providers/edgeSqliteWindows.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/firefoxSqlite.d.ts +6 -0
- package/vendor/sweet-cookie/dist/providers/firefoxSqlite.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/firefoxSqlite.js +257 -0
- package/vendor/sweet-cookie/dist/providers/firefoxSqlite.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/inline.d.ts +8 -0
- package/vendor/sweet-cookie/dist/providers/inline.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/inline.js +71 -0
- package/vendor/sweet-cookie/dist/providers/inline.js.map +1 -0
- package/vendor/sweet-cookie/dist/providers/safariBinaryCookies.d.ts +6 -0
- package/vendor/sweet-cookie/dist/providers/safariBinaryCookies.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/providers/safariBinaryCookies.js +173 -0
- package/vendor/sweet-cookie/dist/providers/safariBinaryCookies.js.map +1 -0
- package/vendor/sweet-cookie/dist/public.d.ts +26 -0
- package/vendor/sweet-cookie/dist/public.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/public.js +197 -0
- package/vendor/sweet-cookie/dist/public.js.map +1 -0
- package/vendor/sweet-cookie/dist/types.d.ts +127 -0
- package/vendor/sweet-cookie/dist/types.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/types.js +2 -0
- package/vendor/sweet-cookie/dist/types.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/base64.d.ts +2 -0
- package/vendor/sweet-cookie/dist/util/base64.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/base64.js +18 -0
- package/vendor/sweet-cookie/dist/util/base64.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/exec.d.ts +8 -0
- package/vendor/sweet-cookie/dist/util/exec.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/exec.js +110 -0
- package/vendor/sweet-cookie/dist/util/exec.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/expire.d.ts +2 -0
- package/vendor/sweet-cookie/dist/util/expire.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/expire.js +32 -0
- package/vendor/sweet-cookie/dist/util/expire.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/fs.d.ts +2 -0
- package/vendor/sweet-cookie/dist/util/fs.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/fs.js +13 -0
- package/vendor/sweet-cookie/dist/util/fs.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/hostMatch.d.ts +2 -0
- package/vendor/sweet-cookie/dist/util/hostMatch.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/hostMatch.js +7 -0
- package/vendor/sweet-cookie/dist/util/hostMatch.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/nodeSqlite.d.ts +5 -0
- package/vendor/sweet-cookie/dist/util/nodeSqlite.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/nodeSqlite.js +58 -0
- package/vendor/sweet-cookie/dist/util/nodeSqlite.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/origins.d.ts +2 -0
- package/vendor/sweet-cookie/dist/util/origins.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/origins.js +27 -0
- package/vendor/sweet-cookie/dist/util/origins.js.map +1 -0
- package/vendor/sweet-cookie/dist/util/runtime.d.ts +2 -0
- package/vendor/sweet-cookie/dist/util/runtime.d.ts.map +1 -0
- package/vendor/sweet-cookie/dist/util/runtime.js +8 -0
- package/vendor/sweet-cookie/dist/util/runtime.js.map +1 -0
- package/vendor/sweet-cookie/package.json +40 -0
package/src/api/types.ts
ADDED
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* X API types for xfeed
|
|
3
|
+
* Consolidated from bird reference implementation
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import type { XCookies } from "@/auth/cookies";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Result of a tweet creation operation
|
|
10
|
+
*/
|
|
11
|
+
export type TweetResult =
|
|
12
|
+
| { success: true; tweetId: string }
|
|
13
|
+
| { success: false; error: string };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Result of a media upload operation
|
|
17
|
+
*/
|
|
18
|
+
export interface UploadMediaResult {
|
|
19
|
+
success: boolean;
|
|
20
|
+
mediaId?: string;
|
|
21
|
+
error?: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Author information for a tweet
|
|
26
|
+
*/
|
|
27
|
+
export interface TweetAuthor {
|
|
28
|
+
username: string;
|
|
29
|
+
name: string;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Media item attached to a tweet (photo, video, or animated GIF)
|
|
34
|
+
*/
|
|
35
|
+
export interface MediaItem {
|
|
36
|
+
/** Unique media ID */
|
|
37
|
+
id: string;
|
|
38
|
+
/** Media type: photo, video, or animated_gif */
|
|
39
|
+
type: "photo" | "video" | "animated_gif";
|
|
40
|
+
/** Direct URL to the media (for photos, this is the display URL) */
|
|
41
|
+
url: string;
|
|
42
|
+
/** Width in pixels */
|
|
43
|
+
width?: number;
|
|
44
|
+
/** Height in pixels */
|
|
45
|
+
height?: number;
|
|
46
|
+
/** Video variants (only for video/animated_gif types) */
|
|
47
|
+
videoVariants?: VideoVariant[];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Video variant with quality/format information
|
|
52
|
+
*/
|
|
53
|
+
export interface VideoVariant {
|
|
54
|
+
/** Bitrate in bits per second (higher = better quality) */
|
|
55
|
+
bitrate?: number;
|
|
56
|
+
/** MIME type (e.g., "video/mp4", "application/x-mpegURL") */
|
|
57
|
+
contentType: string;
|
|
58
|
+
/** Direct URL to the video variant */
|
|
59
|
+
url: string;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* URL entity from tweet text
|
|
64
|
+
*/
|
|
65
|
+
export interface UrlEntity {
|
|
66
|
+
/** Shortened t.co URL as it appears in tweet text */
|
|
67
|
+
url: string;
|
|
68
|
+
/** Full expanded URL */
|
|
69
|
+
expandedUrl: string;
|
|
70
|
+
/** Display-friendly URL (truncated, no protocol) */
|
|
71
|
+
displayUrl: string;
|
|
72
|
+
/** Character indices in tweet text [start, end] */
|
|
73
|
+
indices: [number, number];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @mention entity from tweet text
|
|
78
|
+
*/
|
|
79
|
+
export interface MentionEntity {
|
|
80
|
+
/** @username as it appears in text */
|
|
81
|
+
username: string;
|
|
82
|
+
/** User ID from the API */
|
|
83
|
+
userId?: string;
|
|
84
|
+
/** Display name of the mentioned user */
|
|
85
|
+
name?: string;
|
|
86
|
+
/** Character indices in tweet text [start, end] */
|
|
87
|
+
indices: [number, number];
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Core tweet data structure
|
|
92
|
+
*/
|
|
93
|
+
export interface TweetData {
|
|
94
|
+
id: string;
|
|
95
|
+
text: string;
|
|
96
|
+
author: TweetAuthor;
|
|
97
|
+
authorId?: string;
|
|
98
|
+
createdAt?: string;
|
|
99
|
+
replyCount?: number;
|
|
100
|
+
retweetCount?: number;
|
|
101
|
+
likeCount?: number;
|
|
102
|
+
conversationId?: string;
|
|
103
|
+
inReplyToStatusId?: string;
|
|
104
|
+
/** Quoted tweet data (recursive, depth controlled by quoteDepth option) */
|
|
105
|
+
quotedTweet?: TweetData;
|
|
106
|
+
/** Media attachments (photos, videos, GIFs) */
|
|
107
|
+
media?: MediaItem[];
|
|
108
|
+
/** URL entities parsed from tweet text */
|
|
109
|
+
urls?: UrlEntity[];
|
|
110
|
+
/** @mention entities parsed from tweet text */
|
|
111
|
+
mentions?: MentionEntity[];
|
|
112
|
+
/** Whether the tweet is liked by the current user */
|
|
113
|
+
favorited?: boolean;
|
|
114
|
+
/** Whether the tweet is bookmarked by the current user */
|
|
115
|
+
bookmarked?: boolean;
|
|
116
|
+
/** First nested reply preview (display-only, not navigable) */
|
|
117
|
+
nestedReplyPreview?: TweetData;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Result of fetching a single tweet
|
|
122
|
+
*/
|
|
123
|
+
export interface GetTweetResult {
|
|
124
|
+
success: boolean;
|
|
125
|
+
tweet?: TweetData;
|
|
126
|
+
error?: string;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Result of a search or timeline fetch operation
|
|
131
|
+
*/
|
|
132
|
+
export interface SearchResult {
|
|
133
|
+
success: boolean;
|
|
134
|
+
tweets?: TweetData[];
|
|
135
|
+
error?: string;
|
|
136
|
+
/** Pagination cursor for loading more results (e.g., replies) */
|
|
137
|
+
nextCursor?: string;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Result of a timeline fetch with pagination support
|
|
142
|
+
*/
|
|
143
|
+
export type TimelineResult =
|
|
144
|
+
| { success: true; tweets: TweetData[]; nextCursor?: string }
|
|
145
|
+
| { success: false; error: string };
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* User information
|
|
149
|
+
*/
|
|
150
|
+
export interface UserData {
|
|
151
|
+
id: string;
|
|
152
|
+
username: string;
|
|
153
|
+
name: string;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Extended user profile data with bio and stats
|
|
158
|
+
*/
|
|
159
|
+
export interface UserProfileData {
|
|
160
|
+
id: string;
|
|
161
|
+
username: string;
|
|
162
|
+
name: string;
|
|
163
|
+
description?: string;
|
|
164
|
+
followersCount?: number;
|
|
165
|
+
followingCount?: number;
|
|
166
|
+
isBlueVerified?: boolean;
|
|
167
|
+
/** Profile photo URL */
|
|
168
|
+
profileImageUrl?: string;
|
|
169
|
+
/** Banner/header image URL */
|
|
170
|
+
bannerImageUrl?: string;
|
|
171
|
+
/** User's location string */
|
|
172
|
+
location?: string;
|
|
173
|
+
/** User's website URL (expanded) */
|
|
174
|
+
websiteUrl?: string;
|
|
175
|
+
/** Account creation date (ISO string) */
|
|
176
|
+
createdAt?: string;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* Result of fetching a user profile
|
|
181
|
+
*/
|
|
182
|
+
export interface UserProfileResult {
|
|
183
|
+
success: boolean;
|
|
184
|
+
user?: UserProfileData;
|
|
185
|
+
error?: string;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Result of fetching user tweets
|
|
190
|
+
*/
|
|
191
|
+
export interface UserTweetsResult {
|
|
192
|
+
success: boolean;
|
|
193
|
+
tweets?: TweetData[];
|
|
194
|
+
error?: string;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Result of fetching current user information
|
|
199
|
+
*/
|
|
200
|
+
export interface CurrentUserResult {
|
|
201
|
+
success: boolean;
|
|
202
|
+
user?: UserData;
|
|
203
|
+
error?: string;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Options for creating an XClient instance
|
|
208
|
+
*/
|
|
209
|
+
export interface XClientOptions {
|
|
210
|
+
cookies: XCookies;
|
|
211
|
+
userAgent?: string;
|
|
212
|
+
timeoutMs?: number;
|
|
213
|
+
/** Max depth for quoted tweets (0 disables, default: 1) */
|
|
214
|
+
quoteDepth?: number;
|
|
215
|
+
/** Callback when session expires (401/403 errors) */
|
|
216
|
+
onSessionExpired?: () => void;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Internal GraphQL tweet result structure from X API responses
|
|
221
|
+
*/
|
|
222
|
+
export interface GraphqlTweetResult {
|
|
223
|
+
__typename?: string;
|
|
224
|
+
rest_id?: string;
|
|
225
|
+
legacy?: {
|
|
226
|
+
full_text?: string;
|
|
227
|
+
created_at?: string;
|
|
228
|
+
reply_count?: number;
|
|
229
|
+
retweet_count?: number;
|
|
230
|
+
favorite_count?: number;
|
|
231
|
+
conversation_id_str?: string;
|
|
232
|
+
in_reply_to_status_id_str?: string | null;
|
|
233
|
+
favorited?: boolean;
|
|
234
|
+
bookmarked?: boolean;
|
|
235
|
+
entities?: {
|
|
236
|
+
urls?: Array<{
|
|
237
|
+
url: string;
|
|
238
|
+
expanded_url: string;
|
|
239
|
+
display_url: string;
|
|
240
|
+
indices: [number, number];
|
|
241
|
+
}>;
|
|
242
|
+
media?: Array<{
|
|
243
|
+
url: string;
|
|
244
|
+
indices: [number, number];
|
|
245
|
+
}>;
|
|
246
|
+
user_mentions?: Array<{
|
|
247
|
+
screen_name: string;
|
|
248
|
+
name: string;
|
|
249
|
+
id_str?: string;
|
|
250
|
+
indices: [number, number];
|
|
251
|
+
}>;
|
|
252
|
+
};
|
|
253
|
+
extended_entities?: {
|
|
254
|
+
media?: Array<{
|
|
255
|
+
id_str?: string;
|
|
256
|
+
type: "photo" | "video" | "animated_gif";
|
|
257
|
+
media_url_https: string;
|
|
258
|
+
original_info?: {
|
|
259
|
+
width?: number;
|
|
260
|
+
height?: number;
|
|
261
|
+
};
|
|
262
|
+
video_info?: {
|
|
263
|
+
variants: Array<{
|
|
264
|
+
bitrate?: number;
|
|
265
|
+
content_type: string;
|
|
266
|
+
url: string;
|
|
267
|
+
}>;
|
|
268
|
+
};
|
|
269
|
+
}>;
|
|
270
|
+
};
|
|
271
|
+
};
|
|
272
|
+
core?: {
|
|
273
|
+
user_results?: {
|
|
274
|
+
result?: {
|
|
275
|
+
rest_id?: string;
|
|
276
|
+
id?: string;
|
|
277
|
+
legacy?: {
|
|
278
|
+
screen_name?: string;
|
|
279
|
+
name?: string;
|
|
280
|
+
};
|
|
281
|
+
core?: {
|
|
282
|
+
screen_name?: string;
|
|
283
|
+
name?: string;
|
|
284
|
+
};
|
|
285
|
+
};
|
|
286
|
+
};
|
|
287
|
+
};
|
|
288
|
+
note_tweet?: {
|
|
289
|
+
note_tweet_results?: {
|
|
290
|
+
result?: {
|
|
291
|
+
text?: string;
|
|
292
|
+
richtext?: { text?: string };
|
|
293
|
+
rich_text?: { text?: string };
|
|
294
|
+
content?: {
|
|
295
|
+
text?: string;
|
|
296
|
+
richtext?: { text?: string };
|
|
297
|
+
rich_text?: { text?: string };
|
|
298
|
+
};
|
|
299
|
+
};
|
|
300
|
+
};
|
|
301
|
+
};
|
|
302
|
+
article?: ArticleData;
|
|
303
|
+
/** Quoted tweet data - same structure, recursively nested */
|
|
304
|
+
quoted_status_result?: {
|
|
305
|
+
result?: GraphqlTweetResult;
|
|
306
|
+
};
|
|
307
|
+
/** Visibility wrapper - tweet may be nested here instead of at top level */
|
|
308
|
+
tweet?: GraphqlTweetResult;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Article content structure (for long-form tweets/articles)
|
|
313
|
+
*/
|
|
314
|
+
export interface ArticleData {
|
|
315
|
+
article_results?: {
|
|
316
|
+
result?: ArticleResult;
|
|
317
|
+
};
|
|
318
|
+
title?: string;
|
|
319
|
+
plain_text?: string;
|
|
320
|
+
text?: string;
|
|
321
|
+
richtext?: { text?: string };
|
|
322
|
+
rich_text?: { text?: string };
|
|
323
|
+
body?: TextContent;
|
|
324
|
+
content?: ContentWithItems;
|
|
325
|
+
sections?: Array<{ items?: ContentItem[] }>;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
interface TextContent {
|
|
329
|
+
text?: string;
|
|
330
|
+
richtext?: { text?: string };
|
|
331
|
+
rich_text?: { text?: string };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
interface ContentItem {
|
|
335
|
+
text?: string;
|
|
336
|
+
content?: TextContent;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
interface ContentWithItems extends TextContent {
|
|
340
|
+
items?: ContentItem[];
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
interface ArticleResult {
|
|
344
|
+
title?: string;
|
|
345
|
+
plain_text?: string;
|
|
346
|
+
text?: string;
|
|
347
|
+
richtext?: { text?: string };
|
|
348
|
+
rich_text?: { text?: string };
|
|
349
|
+
body?: TextContent;
|
|
350
|
+
content?: ContentWithItems;
|
|
351
|
+
sections?: Array<{ items?: ContentItem[] }>;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* Internal response structure for tweet creation
|
|
356
|
+
*/
|
|
357
|
+
export interface CreateTweetResponse {
|
|
358
|
+
data?: {
|
|
359
|
+
create_tweet?: {
|
|
360
|
+
tweet_results?: {
|
|
361
|
+
result?: {
|
|
362
|
+
rest_id?: string;
|
|
363
|
+
legacy?: {
|
|
364
|
+
full_text?: string;
|
|
365
|
+
};
|
|
366
|
+
};
|
|
367
|
+
};
|
|
368
|
+
};
|
|
369
|
+
};
|
|
370
|
+
errors?: Array<{ message: string; code?: number }>;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/**
|
|
374
|
+
* GraphQL operation names that use rotating query IDs
|
|
375
|
+
*/
|
|
376
|
+
export type OperationName =
|
|
377
|
+
| "CreateTweet"
|
|
378
|
+
| "CreateRetweet"
|
|
379
|
+
| "FavoriteTweet"
|
|
380
|
+
| "UnfavoriteTweet"
|
|
381
|
+
| "CreateBookmark"
|
|
382
|
+
| "DeleteBookmark"
|
|
383
|
+
| "TweetDetail"
|
|
384
|
+
| "SearchTimeline"
|
|
385
|
+
| "UserArticlesTweets"
|
|
386
|
+
| "Bookmarks"
|
|
387
|
+
| "BookmarkFolderTimeline"
|
|
388
|
+
| "HomeTimeline"
|
|
389
|
+
| "HomeLatestTimeline"
|
|
390
|
+
| "UserByScreenName"
|
|
391
|
+
| "UserTweets"
|
|
392
|
+
| "Likes"
|
|
393
|
+
| "BookmarkFoldersSlice"
|
|
394
|
+
| "bookmarkTweetToFolder"
|
|
395
|
+
| "NotificationsTimeline";
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* Result of an action mutation (like, bookmark, etc.)
|
|
399
|
+
*/
|
|
400
|
+
export type ActionResult =
|
|
401
|
+
| { success: true }
|
|
402
|
+
| { success: false; error: string };
|
|
403
|
+
|
|
404
|
+
/**
|
|
405
|
+
* Bookmark folder data structure
|
|
406
|
+
*/
|
|
407
|
+
export interface BookmarkFolder {
|
|
408
|
+
/** Folder ID (bookmark_collection_id) */
|
|
409
|
+
id: string;
|
|
410
|
+
/** Folder display name */
|
|
411
|
+
name: string;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
/**
|
|
415
|
+
* Result of fetching bookmark folders
|
|
416
|
+
*/
|
|
417
|
+
export type BookmarkFoldersResult =
|
|
418
|
+
| { success: true; folders: BookmarkFolder[] }
|
|
419
|
+
| { success: false; error: string };
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* API error types for different failure modes
|
|
423
|
+
*/
|
|
424
|
+
export type ApiErrorType =
|
|
425
|
+
| "rate_limit"
|
|
426
|
+
| "auth_expired"
|
|
427
|
+
| "network_error"
|
|
428
|
+
| "not_found"
|
|
429
|
+
| "unavailable"
|
|
430
|
+
| "unknown";
|
|
431
|
+
|
|
432
|
+
/**
|
|
433
|
+
* Structured API error with type discrimination
|
|
434
|
+
*/
|
|
435
|
+
export interface ApiError {
|
|
436
|
+
/** Error type for programmatic handling */
|
|
437
|
+
type: ApiErrorType;
|
|
438
|
+
/** Human-readable error message */
|
|
439
|
+
message: string;
|
|
440
|
+
/** HTTP status code if applicable */
|
|
441
|
+
statusCode?: number;
|
|
442
|
+
/** Rate limit reset time (Unix timestamp) for rate_limit errors */
|
|
443
|
+
rateLimitReset?: number;
|
|
444
|
+
/** Retry-After header value in seconds */
|
|
445
|
+
retryAfter?: number;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
/**
|
|
449
|
+
* Timeline result with typed errors
|
|
450
|
+
*/
|
|
451
|
+
export type TimelineResultV2 =
|
|
452
|
+
| { success: true; tweets: TweetData[]; nextCursor?: string }
|
|
453
|
+
| { success: false; error: ApiError };
|
|
454
|
+
|
|
455
|
+
/**
|
|
456
|
+
* Generic fetch result with typed errors
|
|
457
|
+
*/
|
|
458
|
+
export type FetchResult<T> =
|
|
459
|
+
| { success: true; data: T }
|
|
460
|
+
| { success: false; error: ApiError };
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* User-friendly error messages for each error type
|
|
464
|
+
*/
|
|
465
|
+
export const API_ERROR_MESSAGES: Record<ApiErrorType, string> = {
|
|
466
|
+
rate_limit: "Rate limited by X. Please wait before trying again.",
|
|
467
|
+
auth_expired: "Session expired. Please log into x.com and restart xfeed.",
|
|
468
|
+
network_error: "Network error. Check your connection and try again.",
|
|
469
|
+
not_found: "Content not found or has been deleted.",
|
|
470
|
+
unavailable: "This content is temporarily unavailable.",
|
|
471
|
+
unknown: "Something went wrong. Please try again.",
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Check if an error type is retryable
|
|
476
|
+
*/
|
|
477
|
+
export function isRetryableError(errorType: ApiErrorType): boolean {
|
|
478
|
+
return errorType === "network_error" || errorType === "rate_limit";
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Check if an error indicates auth issues requiring re-login
|
|
483
|
+
*/
|
|
484
|
+
export function isAuthError(errorType: ApiErrorType): boolean {
|
|
485
|
+
return errorType === "auth_expired";
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
/**
|
|
489
|
+
* Notification icon types from X API
|
|
490
|
+
*/
|
|
491
|
+
export type NotificationIcon =
|
|
492
|
+
| "heart_icon"
|
|
493
|
+
| "person_icon"
|
|
494
|
+
| "bird_icon"
|
|
495
|
+
| "retweet_icon"
|
|
496
|
+
| "reply_icon";
|
|
497
|
+
|
|
498
|
+
/**
|
|
499
|
+
* Notification data structure
|
|
500
|
+
*/
|
|
501
|
+
export interface NotificationData {
|
|
502
|
+
/** Unique notification ID */
|
|
503
|
+
id: string;
|
|
504
|
+
/** Icon type indicating notification category */
|
|
505
|
+
icon: NotificationIcon;
|
|
506
|
+
/** Human-readable notification message */
|
|
507
|
+
message: string;
|
|
508
|
+
/** URL to navigate to when clicked */
|
|
509
|
+
url: string;
|
|
510
|
+
/** ISO timestamp of the notification */
|
|
511
|
+
timestamp: string;
|
|
512
|
+
/** Sort index for ordering and unread calculation */
|
|
513
|
+
sortIndex: string;
|
|
514
|
+
/** Associated tweet (for likes, retweets, replies) */
|
|
515
|
+
targetTweet?: TweetData;
|
|
516
|
+
/** Users who performed the action */
|
|
517
|
+
fromUsers?: UserData[];
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
/**
|
|
521
|
+
* Result of fetching notifications
|
|
522
|
+
*/
|
|
523
|
+
export type NotificationsResult =
|
|
524
|
+
| {
|
|
525
|
+
success: true;
|
|
526
|
+
notifications: NotificationData[];
|
|
527
|
+
unreadSortIndex?: string;
|
|
528
|
+
topCursor?: string;
|
|
529
|
+
bottomCursor?: string;
|
|
530
|
+
}
|
|
531
|
+
| { success: false; error: ApiError };
|
|
532
|
+
|
|
533
|
+
/**
|
|
534
|
+
* Internal notification timeline instruction types from X API
|
|
535
|
+
*/
|
|
536
|
+
export interface NotificationInstruction {
|
|
537
|
+
type: string;
|
|
538
|
+
sort_index?: string;
|
|
539
|
+
entries?: Array<{
|
|
540
|
+
entryId?: string;
|
|
541
|
+
sortIndex?: string;
|
|
542
|
+
content?: {
|
|
543
|
+
cursorType?: string;
|
|
544
|
+
value?: string;
|
|
545
|
+
itemContent?: {
|
|
546
|
+
itemType?: string;
|
|
547
|
+
id?: string;
|
|
548
|
+
notification_icon?: string;
|
|
549
|
+
rich_message?: {
|
|
550
|
+
text?: string;
|
|
551
|
+
};
|
|
552
|
+
notification_url?: {
|
|
553
|
+
url?: string;
|
|
554
|
+
};
|
|
555
|
+
template?: {
|
|
556
|
+
target_objects?: Array<{
|
|
557
|
+
tweet_results?: {
|
|
558
|
+
result?: GraphqlTweetResult;
|
|
559
|
+
};
|
|
560
|
+
}>;
|
|
561
|
+
from_users?: Array<{
|
|
562
|
+
user_results?: {
|
|
563
|
+
result?: {
|
|
564
|
+
rest_id?: string;
|
|
565
|
+
core?: {
|
|
566
|
+
screen_name?: string;
|
|
567
|
+
name?: string;
|
|
568
|
+
};
|
|
569
|
+
legacy?: {
|
|
570
|
+
screen_name?: string;
|
|
571
|
+
name?: string;
|
|
572
|
+
};
|
|
573
|
+
};
|
|
574
|
+
};
|
|
575
|
+
}>;
|
|
576
|
+
};
|
|
577
|
+
timestamp_ms?: string;
|
|
578
|
+
};
|
|
579
|
+
};
|
|
580
|
+
}>;
|
|
581
|
+
}
|