rettiwt-api 6.0.8 → 6.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.
Files changed (219) hide show
  1. package/.github/ISSUE_TEMPLATE/bug-report.yml +57 -0
  2. package/.github/ISSUE_TEMPLATE/feature-request.yml +20 -0
  3. package/.github/ISSUE_TEMPLATE/question.yml +15 -0
  4. package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
  5. package/.github/workflows/ci.yml +33 -0
  6. package/.nvmrc +1 -0
  7. package/README.md +30 -6
  8. package/dist/Rettiwt.d.ts +3 -0
  9. package/dist/Rettiwt.js +4 -0
  10. package/dist/Rettiwt.js.map +1 -1
  11. package/dist/cli.js +2 -0
  12. package/dist/cli.js.map +1 -1
  13. package/dist/collections/Extractors.d.ts +24 -0
  14. package/dist/collections/Extractors.js +14 -0
  15. package/dist/collections/Extractors.js.map +1 -1
  16. package/dist/collections/Groups.js +11 -0
  17. package/dist/collections/Groups.js.map +1 -1
  18. package/dist/collections/Requests.js +12 -0
  19. package/dist/collections/Requests.js.map +1 -1
  20. package/dist/commands/DirectMessage.d.ts +10 -0
  21. package/dist/commands/DirectMessage.js +57 -0
  22. package/dist/commands/DirectMessage.js.map +1 -0
  23. package/dist/commands/List.js +44 -3
  24. package/dist/commands/List.js.map +1 -1
  25. package/dist/commands/Tweet.js +29 -1
  26. package/dist/commands/Tweet.js.map +1 -1
  27. package/dist/commands/User.js +39 -1
  28. package/dist/commands/User.js.map +1 -1
  29. package/dist/enums/Data.d.ts +3 -1
  30. package/dist/enums/Data.js +2 -0
  31. package/dist/enums/Data.js.map +1 -1
  32. package/dist/enums/Resource.d.ts +11 -0
  33. package/dist/enums/Resource.js +12 -0
  34. package/dist/enums/Resource.js.map +1 -1
  35. package/dist/enums/raw/Analytics.d.ts +6 -3
  36. package/dist/enums/raw/Analytics.js +5 -2
  37. package/dist/enums/raw/Analytics.js.map +1 -1
  38. package/dist/index.d.ts +15 -0
  39. package/dist/index.js +8 -0
  40. package/dist/index.js.map +1 -1
  41. package/dist/models/args/FetchArgs.d.ts +9 -0
  42. package/dist/models/args/FetchArgs.js +16 -0
  43. package/dist/models/args/FetchArgs.js.map +1 -1
  44. package/dist/models/args/PostArgs.d.ts +1 -0
  45. package/dist/models/args/PostArgs.js +2 -0
  46. package/dist/models/args/PostArgs.js.map +1 -1
  47. package/dist/models/data/Analytics.d.ts +43 -0
  48. package/dist/models/data/Analytics.js +92 -0
  49. package/dist/models/data/Analytics.js.map +1 -0
  50. package/dist/models/data/Conversation.d.ts +93 -0
  51. package/dist/models/data/Conversation.js +293 -0
  52. package/dist/models/data/Conversation.js.map +1 -0
  53. package/dist/models/data/CursoredData.d.ts +2 -1
  54. package/dist/models/data/CursoredData.js +6 -1
  55. package/dist/models/data/CursoredData.js.map +1 -1
  56. package/dist/models/data/DirectMessage.d.ts +105 -0
  57. package/dist/models/data/DirectMessage.js +284 -0
  58. package/dist/models/data/DirectMessage.js.map +1 -0
  59. package/dist/models/data/Inbox.d.ts +44 -0
  60. package/dist/models/data/Inbox.js +106 -0
  61. package/dist/models/data/Inbox.js.map +1 -0
  62. package/dist/models/data/List.d.ts +20 -0
  63. package/dist/models/data/List.js +50 -1
  64. package/dist/models/data/List.js.map +1 -1
  65. package/dist/models/data/Tweet.d.ts +6 -6
  66. package/dist/models/data/Tweet.js +4 -2
  67. package/dist/models/data/Tweet.js.map +1 -1
  68. package/dist/models/data/User.d.ts +2 -0
  69. package/dist/models/data/User.js +6 -0
  70. package/dist/models/data/User.js.map +1 -1
  71. package/dist/requests/DirectMessage.d.ts +28 -0
  72. package/dist/requests/DirectMessage.js +149 -0
  73. package/dist/requests/DirectMessage.js.map +1 -0
  74. package/dist/requests/List.d.ts +10 -0
  75. package/dist/requests/List.js +52 -0
  76. package/dist/requests/List.js.map +1 -1
  77. package/dist/requests/Tweet.d.ts +8 -0
  78. package/dist/requests/Tweet.js +30 -0
  79. package/dist/requests/Tweet.js.map +1 -1
  80. package/dist/requests/User.d.ts +8 -1
  81. package/dist/requests/User.js +67 -8
  82. package/dist/requests/User.js.map +1 -1
  83. package/dist/services/public/DirectMessageService.d.ts +100 -0
  84. package/dist/services/public/DirectMessageService.js +143 -0
  85. package/dist/services/public/DirectMessageService.js.map +1 -0
  86. package/dist/services/public/FetcherService.d.ts +2 -2
  87. package/dist/services/public/FetcherService.js +2 -2
  88. package/dist/services/public/ListService.d.ts +85 -0
  89. package/dist/services/public/ListService.js +111 -0
  90. package/dist/services/public/ListService.js.map +1 -1
  91. package/dist/services/public/TweetService.d.ts +56 -0
  92. package/dist/services/public/TweetService.js +72 -0
  93. package/dist/services/public/TweetService.js.map +1 -1
  94. package/dist/services/public/UserService.d.ts +61 -2
  95. package/dist/services/public/UserService.js +89 -0
  96. package/dist/services/public/UserService.js.map +1 -1
  97. package/dist/types/args/FetchArgs.d.ts +69 -12
  98. package/dist/types/args/PostArgs.d.ts +29 -11
  99. package/dist/types/data/Analytics.d.ts +42 -0
  100. package/dist/types/data/Analytics.js +3 -0
  101. package/dist/types/data/Analytics.js.map +1 -0
  102. package/dist/types/data/Conversation.d.ts +32 -0
  103. package/dist/types/data/Conversation.js +3 -0
  104. package/dist/types/data/Conversation.js.map +1 -0
  105. package/dist/types/data/CursoredData.d.ts +4 -1
  106. package/dist/types/data/DirectMessage.d.ts +25 -0
  107. package/dist/types/data/DirectMessage.js +3 -0
  108. package/dist/types/data/DirectMessage.js.map +1 -0
  109. package/dist/types/data/Inbox.d.ts +18 -0
  110. package/dist/types/data/Inbox.js +3 -0
  111. package/dist/types/data/Inbox.js.map +1 -0
  112. package/dist/types/data/List.d.ts +5 -1
  113. package/dist/types/data/Tweet.d.ts +6 -6
  114. package/dist/types/data/User.d.ts +4 -0
  115. package/dist/types/raw/base/Analytic.d.ts +6 -1
  116. package/dist/types/raw/base/Message.d.ts +16 -0
  117. package/dist/types/raw/base/Message.js +4 -0
  118. package/dist/types/raw/base/Message.js.map +1 -0
  119. package/dist/types/raw/base/Tweet.d.ts +6 -6
  120. package/dist/types/raw/base/User.d.ts +2 -1
  121. package/dist/types/raw/composite/TimelineList.d.ts +9 -0
  122. package/dist/types/raw/composite/TimelineList.js +3 -0
  123. package/dist/types/raw/composite/TimelineList.js.map +1 -0
  124. package/dist/types/raw/dm/Conversation.d.ts +55 -0
  125. package/dist/types/raw/dm/Conversation.js +4 -0
  126. package/dist/types/raw/dm/Conversation.js.map +1 -0
  127. package/dist/types/raw/dm/InboxInitial.d.ts +137 -0
  128. package/dist/types/raw/dm/InboxInitial.js +4 -0
  129. package/dist/types/raw/dm/InboxInitial.js.map +1 -0
  130. package/dist/types/raw/dm/InboxTimeline.d.ts +287 -0
  131. package/dist/types/raw/dm/InboxTimeline.js +4 -0
  132. package/dist/types/raw/dm/InboxTimeline.js.map +1 -0
  133. package/dist/types/raw/dm/UserUpdates.d.ts +41 -0
  134. package/dist/types/raw/dm/UserUpdates.js +4 -0
  135. package/dist/types/raw/dm/UserUpdates.js.map +1 -0
  136. package/dist/types/raw/list/AddMember.d.ts +151 -0
  137. package/dist/types/raw/list/AddMember.js +4 -0
  138. package/dist/types/raw/list/AddMember.js.map +1 -0
  139. package/dist/types/raw/list/Details.d.ts +44 -13
  140. package/dist/types/raw/list/RemoveMember.d.ts +150 -0
  141. package/dist/types/raw/list/RemoveMember.js +4 -0
  142. package/dist/types/raw/list/RemoveMember.js.map +1 -0
  143. package/dist/types/raw/tweet/Bookmark.d.ts +12 -0
  144. package/dist/types/raw/tweet/Bookmark.js +4 -0
  145. package/dist/types/raw/tweet/Bookmark.js.map +1 -0
  146. package/dist/types/raw/tweet/Unbookmark.d.ts +11 -0
  147. package/dist/types/raw/tweet/Unbookmark.js +4 -0
  148. package/dist/types/raw/tweet/Unbookmark.js.map +1 -0
  149. package/dist/types/raw/user/Analytics.d.ts +6 -18
  150. package/dist/types/raw/user/Analytics.js +0 -1
  151. package/dist/types/raw/user/Analytics.js.map +1 -1
  152. package/dist/types/raw/user/Lists.d.ts +319 -0
  153. package/dist/types/raw/user/Lists.js +4 -0
  154. package/dist/types/raw/user/Lists.js.map +1 -0
  155. package/eslint.config.mjs +1 -1
  156. package/package.json +11 -6
  157. package/playground/.env.example +1 -0
  158. package/playground/README.md +53 -0
  159. package/playground/index.js +15 -0
  160. package/playground/package.json +15 -0
  161. package/src/Rettiwt.ts +5 -0
  162. package/src/cli.ts +2 -0
  163. package/src/collections/Extractors.ts +29 -0
  164. package/src/collections/Groups.ts +11 -0
  165. package/src/collections/Requests.ts +20 -0
  166. package/src/commands/DirectMessage.ts +62 -0
  167. package/src/commands/List.ts +44 -3
  168. package/src/commands/Tweet.ts +29 -1
  169. package/src/commands/User.ts +65 -1
  170. package/src/enums/Data.ts +2 -0
  171. package/src/enums/Resource.ts +13 -0
  172. package/src/enums/raw/Analytics.ts +5 -2
  173. package/src/index.ts +15 -0
  174. package/src/models/args/FetchArgs.ts +17 -0
  175. package/src/models/args/PostArgs.ts +2 -0
  176. package/src/models/data/Analytics.ts +97 -0
  177. package/src/models/data/Conversation.ts +344 -0
  178. package/src/models/data/CursoredData.ts +7 -2
  179. package/src/models/data/DirectMessage.ts +335 -0
  180. package/src/models/data/Inbox.ts +124 -0
  181. package/src/models/data/List.ts +60 -1
  182. package/src/models/data/Tweet.ts +10 -8
  183. package/src/models/data/User.ts +6 -0
  184. package/src/requests/DirectMessage.ts +233 -0
  185. package/src/requests/List.ts +58 -0
  186. package/src/requests/Tweet.ts +32 -0
  187. package/src/requests/User.ts +70 -7
  188. package/src/services/public/DirectMessageService.ts +159 -0
  189. package/src/services/public/FetcherService.ts +2 -2
  190. package/src/services/public/ListService.ts +127 -0
  191. package/src/services/public/TweetService.ts +82 -0
  192. package/src/services/public/UserService.ts +110 -2
  193. package/src/types/args/FetchArgs.ts +77 -12
  194. package/src/types/args/PostArgs.ts +31 -11
  195. package/src/types/data/Analytics.ts +58 -0
  196. package/src/types/data/Conversation.ts +44 -0
  197. package/src/types/data/CursoredData.ts +4 -1
  198. package/src/types/data/DirectMessage.ts +33 -0
  199. package/src/types/data/Inbox.ts +23 -0
  200. package/src/types/data/List.ts +7 -1
  201. package/src/types/data/Tweet.ts +6 -6
  202. package/src/types/data/User.ts +6 -0
  203. package/src/types/raw/base/Analytic.ts +7 -1
  204. package/src/types/raw/base/Message.ts +22 -0
  205. package/src/types/raw/base/Tweet.ts +6 -6
  206. package/src/types/raw/base/User.ts +2 -1
  207. package/src/types/raw/composite/TimelineList.ts +10 -0
  208. package/src/types/raw/dm/Conversation.ts +59 -0
  209. package/src/types/raw/dm/InboxInitial.ts +155 -0
  210. package/src/types/raw/dm/InboxTimeline.ts +301 -0
  211. package/src/types/raw/dm/UserUpdates.ts +46 -0
  212. package/src/types/raw/list/AddMember.ts +175 -0
  213. package/src/types/raw/list/Details.ts +52 -13
  214. package/src/types/raw/list/RemoveMember.ts +174 -0
  215. package/src/types/raw/tweet/Bookmark.ts +14 -0
  216. package/src/types/raw/tweet/Unbookmark.ts +14 -0
  217. package/src/types/raw/user/Analytics.ts +6 -22
  218. package/src/types/raw/user/Lists.ts +378 -0
  219. package/tsconfig.json +1 -1
@@ -0,0 +1,335 @@
1
+ import { IDirectMessage } from '../../types/data/DirectMessage';
2
+ import { IMessage as IRawMessage } from '../../types/raw/base/Message';
3
+ import { IConversationTimelineResponse } from '../../types/raw/dm/Conversation';
4
+ import { IInboxInitialResponse } from '../../types/raw/dm/InboxInitial';
5
+ import { IInboxTimelineResponse } from '../../types/raw/dm/InboxTimeline';
6
+
7
+ /**
8
+ * Type guard to check if the response is an IInboxInitialResponse
9
+ */
10
+ function isInboxInitialResponse(
11
+ response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
12
+ ): response is IInboxInitialResponse {
13
+ return 'inbox_initial_state' in response;
14
+ }
15
+
16
+ /**
17
+ * Type guard to check if the response is an IConversationTimelineResponse
18
+ */
19
+ function isConversationTimelineResponse(
20
+ response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
21
+ ): response is IConversationTimelineResponse {
22
+ return 'conversation_timeline' in response;
23
+ }
24
+
25
+ /**
26
+ * Type guard to check if the response is an IInboxTimelineResponse
27
+ */
28
+ function isInboxTimelineResponse(
29
+ response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
30
+ ): response is IInboxTimelineResponse {
31
+ return 'inbox_timeline' in response;
32
+ }
33
+
34
+ /**
35
+ * The details of a single direct message.
36
+ *
37
+ * @public
38
+ */
39
+ export class DirectMessage implements IDirectMessage {
40
+ /** The raw message details. */
41
+ private readonly _raw: IRawMessage;
42
+
43
+ public conversationId: string;
44
+ public createdAt: string;
45
+ public editCount?: number;
46
+ public id: string;
47
+ public mediaUrls?: string[];
48
+ public read?: boolean;
49
+ public recipientId?: string;
50
+ public senderId: string;
51
+ public text: string;
52
+
53
+ /**
54
+ * @param message - The raw message details from the API response.
55
+ */
56
+ public constructor(message: unknown) {
57
+ this._raw = message as IRawMessage;
58
+
59
+ const parsedData = this._parseMessageData(message);
60
+
61
+ this.id = parsedData.id;
62
+ this.conversationId = parsedData.conversationId;
63
+ this.senderId = parsedData.senderId;
64
+ this.recipientId = parsedData.recipientId;
65
+ this.text = parsedData.text;
66
+ this.createdAt = parsedData.createdAt;
67
+ this.editCount = parsedData.editCount ?? 0;
68
+ this.mediaUrls = this._extractMediaUrls(message);
69
+ this.read = true; // Default to true, can be enhanced later
70
+ }
71
+
72
+ /** The raw message details. */
73
+ public get raw(): IRawMessage {
74
+ return this._raw;
75
+ }
76
+
77
+ /**
78
+ * Extract messages from conversation timeline response
79
+ */
80
+ private static _extractFromConversationTimeline(response: IConversationTimelineResponse): DirectMessage[] {
81
+ const messages: DirectMessage[] = [];
82
+ const entries = response.conversation_timeline?.entries ?? [];
83
+
84
+ for (const entry of entries) {
85
+ if ('message' in entry && entry.message) {
86
+ messages.push(new DirectMessage(entry.message));
87
+ }
88
+ }
89
+
90
+ return messages;
91
+ }
92
+
93
+ /**
94
+ * Extract messages from inbox initial response
95
+ */
96
+ private static _extractFromInboxInitial(response: IInboxInitialResponse): DirectMessage[] {
97
+ const messages: DirectMessage[] = [];
98
+ const entries = response.inbox_initial_state?.entries ?? [];
99
+
100
+ for (const entry of entries) {
101
+ if ('message' in entry && entry.message) {
102
+ messages.push(new DirectMessage(entry.message));
103
+ }
104
+ }
105
+
106
+ return messages;
107
+ }
108
+
109
+ /**
110
+ * Extract messages from inbox timeline response
111
+ */
112
+ private static _extractFromInboxTimeline(response: IInboxTimelineResponse): DirectMessage[] {
113
+ const messages: DirectMessage[] = [];
114
+ const entries = response.inbox_timeline?.entries ?? [];
115
+
116
+ for (const entry of entries) {
117
+ if ('message' in entry && entry.message) {
118
+ messages.push(new DirectMessage(entry.message));
119
+ }
120
+ }
121
+
122
+ return messages;
123
+ }
124
+
125
+ /**
126
+ * Extract media URLs from message attachment data with proper type safety.
127
+ */
128
+ private _extractMediaUrls(message: unknown): string[] | undefined {
129
+ const urls: string[] = [];
130
+ const msg = message as Record<string, unknown>;
131
+ const messageData = msg.message_data as Record<string, unknown> | undefined;
132
+
133
+ // Check for card attachments with images
134
+ const attachment = messageData?.attachment as Record<string, unknown> | undefined;
135
+ const card = attachment?.card as Record<string, unknown> | undefined;
136
+ const bindingValues = card?.binding_values as Record<string, unknown> | undefined;
137
+
138
+ if (bindingValues) {
139
+ // Extract URLs from various image binding values
140
+ const imageBindings = [
141
+ 'thumbnail_image',
142
+ 'photo_image_full_size',
143
+ 'summary_photo_image',
144
+ 'thumbnail_image_original',
145
+ 'summary_photo_image_original',
146
+ 'photo_image_full_size_original',
147
+ ];
148
+
149
+ for (const bindingKey of imageBindings) {
150
+ const imageBinding = bindingValues[bindingKey] as Record<string, unknown> | undefined;
151
+ const imageValue = imageBinding?.image_value as Record<string, unknown> | undefined;
152
+
153
+ if (imageValue?.url && typeof imageValue.url === 'string') {
154
+ urls.push(imageValue.url);
155
+ }
156
+ }
157
+ }
158
+
159
+ // Check for tweet attachments
160
+ const tweet = attachment?.tweet as Record<string, unknown> | undefined;
161
+ if (tweet?.expanded_url && typeof tweet.expanded_url === 'string') {
162
+ urls.push(tweet.expanded_url);
163
+ }
164
+
165
+ return urls.length > 0 ? [...new Set(urls)] : undefined; // Remove duplicates
166
+ }
167
+
168
+ /**
169
+ * Safely extract number value
170
+ */
171
+ private _extractNumberValue(value: unknown): number | undefined {
172
+ if (typeof value === 'number') {
173
+ return value;
174
+ }
175
+ if (typeof value === 'string') {
176
+ const parsed = Number(value);
177
+ return isNaN(parsed) ? undefined : parsed;
178
+ }
179
+ return undefined;
180
+ }
181
+
182
+ /**
183
+ * Safely extract string value with fallback
184
+ */
185
+ private _extractStringValue(...values: unknown[]): string | undefined {
186
+ for (const value of values) {
187
+ if (typeof value === 'string' && value.length > 0) {
188
+ return value;
189
+ }
190
+ }
191
+ return undefined;
192
+ }
193
+
194
+ /**
195
+ * Parse message data with proper type safety
196
+ */
197
+ private _parseMessageData(message: unknown): IDirectMessage {
198
+ const msg = message as Record<string, unknown>;
199
+ const messageData = msg.message_data as Record<string, unknown> | undefined;
200
+
201
+ const id = this._extractStringValue(messageData?.id, msg.id) ?? '';
202
+ const conversationId = this._extractStringValue(msg.conversation_id, messageData?.conversation_id) ?? '';
203
+ const senderId = this._extractStringValue(messageData?.sender_id, msg.sender_id) ?? '';
204
+ const recipientId = this._extractStringValue(messageData?.recipient_id, msg.recipient_id);
205
+ const text = this._extractStringValue(messageData?.text, msg.text) ?? '';
206
+ const createdAt = this._parseTimestamp(this._extractStringValue(messageData?.time, msg.time) ?? '');
207
+ const editCount = this._extractNumberValue(messageData?.edit_count);
208
+
209
+ return {
210
+ id,
211
+ conversationId,
212
+ senderId,
213
+ recipientId,
214
+ createdAt,
215
+ text,
216
+ editCount,
217
+ };
218
+ }
219
+
220
+ /**
221
+ * Parse timestamp with proper validation
222
+ */
223
+ private _parseTimestamp(timestamp: string): string {
224
+ const numericTimestamp = Number(timestamp);
225
+ if (!isNaN(numericTimestamp)) {
226
+ const date = new Date(numericTimestamp);
227
+ if (!isNaN(date.getTime())) {
228
+ return date.toISOString();
229
+ }
230
+ }
231
+ return new Date().toISOString();
232
+ }
233
+
234
+ /**
235
+ * Filter messages by conversation ID
236
+ */
237
+ public static filterByConversation(messages: DirectMessage[], conversationId: string): DirectMessage[] {
238
+ return messages.filter((message) => message.conversationId === conversationId);
239
+ }
240
+
241
+ /**
242
+ * Filter messages by sender ID
243
+ */
244
+ public static filterBySender(messages: DirectMessage[], senderId: string): DirectMessage[] {
245
+ return messages.filter((message) => message.isFromSender(senderId));
246
+ }
247
+
248
+ /**
249
+ * Extracts and deserializes the list of direct messages from the given raw response data.
250
+ *
251
+ * @param response - The raw response data.
252
+ *
253
+ * @returns The deserialized list of direct messages.
254
+ */
255
+ public static list(
256
+ response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
257
+ ): DirectMessage[] {
258
+ const messages: DirectMessage[] = [];
259
+
260
+ if (isInboxInitialResponse(response)) {
261
+ return DirectMessage._extractFromInboxInitial(response);
262
+ } else if (isConversationTimelineResponse(response)) {
263
+ return DirectMessage._extractFromConversationTimeline(response);
264
+ } else if (isInboxTimelineResponse(response)) {
265
+ return DirectMessage._extractFromInboxTimeline(response);
266
+ }
267
+
268
+ return messages;
269
+ }
270
+
271
+ /**
272
+ * Generic method to extract messages from any supported response type
273
+ */
274
+ public static listFromResponse(
275
+ response: IInboxInitialResponse | IConversationTimelineResponse | IInboxTimelineResponse,
276
+ ): DirectMessage[] {
277
+ return DirectMessage.list(response);
278
+ }
279
+
280
+ /**
281
+ * Sort messages by creation time (oldest to newest)
282
+ */
283
+ public static sortByTime(messages: DirectMessage[], ascending = true): DirectMessage[] {
284
+ return [...messages].sort((a, b) => {
285
+ const timeA = new Date(a.createdAt).getTime();
286
+ const timeB = new Date(b.createdAt).getTime();
287
+ return ascending ? timeA - timeB : timeB - timeA;
288
+ });
289
+ }
290
+
291
+ /**
292
+ * Get the age of this message in milliseconds
293
+ */
294
+ public getAgeInMs(): number {
295
+ return Date.now() - new Date(this.createdAt).getTime();
296
+ }
297
+
298
+ /**
299
+ * Check if this message has media attachments
300
+ */
301
+ public hasMedia(): boolean {
302
+ return Boolean(this.mediaUrls && this.mediaUrls.length > 0);
303
+ }
304
+
305
+ /**
306
+ * Check if this message is from a specific sender
307
+ */
308
+ public isFromSender(senderId: string): boolean {
309
+ return this.senderId === senderId;
310
+ }
311
+
312
+ /**
313
+ * @returns A serializable JSON representation of `this` object.
314
+ */
315
+ public toJSON(): IDirectMessage {
316
+ return {
317
+ conversationId: this.conversationId,
318
+ createdAt: this.createdAt,
319
+ editCount: this.editCount,
320
+ id: this.id,
321
+ mediaUrls: this.mediaUrls,
322
+ read: this.read,
323
+ recipientId: this.recipientId,
324
+ senderId: this.senderId,
325
+ text: this.text,
326
+ };
327
+ }
328
+
329
+ /**
330
+ * Check if this message was edited
331
+ */
332
+ public wasEdited(): boolean {
333
+ return Boolean(this.editCount && this.editCount > 0);
334
+ }
335
+ }
@@ -0,0 +1,124 @@
1
+ import { IInbox } from '../../types/data/Inbox';
2
+ import { IInboxInitialResponse } from '../../types/raw/dm/InboxInitial';
3
+ import { IInboxTimelineResponse } from '../../types/raw/dm/InboxTimeline';
4
+
5
+ import { Conversation } from './Conversation';
6
+
7
+ /**
8
+ * Type guard to check if the response is an IInboxInitialResponse
9
+ */
10
+ function isInboxInitialResponse(
11
+ response: IInboxInitialResponse | IInboxTimelineResponse,
12
+ ): response is IInboxInitialResponse {
13
+ return 'inbox_initial_state' in response;
14
+ }
15
+
16
+ /**
17
+ * Type guard to check if the response is an IInboxTimelineResponse
18
+ */
19
+ function isInboxTimelineResponse(
20
+ response: IInboxInitialResponse | IInboxTimelineResponse,
21
+ ): response is IInboxTimelineResponse {
22
+ return 'inbox_timeline' in response;
23
+ }
24
+
25
+ /**
26
+ * The details of a DM inbox containing conversations and metadata.
27
+ *
28
+ * @public
29
+ */
30
+ export class Inbox implements IInbox {
31
+ /** The raw inbox details. */
32
+ private readonly _raw: IInboxInitialResponse | IInboxTimelineResponse;
33
+
34
+ public conversations: Conversation[];
35
+ public cursor: string;
36
+ public lastSeenEventId: string;
37
+ public trustedLastSeenEventId: string;
38
+ public untrustedLastSeenEventId: string;
39
+
40
+ /**
41
+ * @param response - The raw inbox response from the API.
42
+ */
43
+ public constructor(response: IInboxInitialResponse | IInboxTimelineResponse) {
44
+ this._raw = response;
45
+
46
+ // Handle inbox initial state response
47
+ if (isInboxInitialResponse(response)) {
48
+ const inboxState = response.inbox_initial_state;
49
+
50
+ this.cursor = inboxState.cursor ?? '';
51
+ this.lastSeenEventId = inboxState.last_seen_event_id ?? '';
52
+ this.trustedLastSeenEventId = inboxState.trusted_last_seen_event_id ?? '';
53
+ this.untrustedLastSeenEventId = inboxState.untrusted_last_seen_event_id ?? '';
54
+
55
+ // Parse conversations from inbox initial state
56
+ this.conversations = Conversation.listFromInboxInitial(response);
57
+ }
58
+ // Handle inbox timeline response
59
+ else if (isInboxTimelineResponse(response)) {
60
+ const inboxTimeline = response.inbox_timeline;
61
+
62
+ this.cursor = inboxTimeline.min_entry_id ?? '';
63
+ this.lastSeenEventId = '';
64
+ this.trustedLastSeenEventId = '';
65
+ this.untrustedLastSeenEventId = '';
66
+
67
+ // Parse conversations from inbox timeline
68
+ this.conversations = Conversation.listFromInboxTimeline(response);
69
+ } else {
70
+ // Fallback defaults (this should never happen with proper typing)
71
+ this.cursor = '';
72
+ this.lastSeenEventId = '';
73
+ this.trustedLastSeenEventId = '';
74
+ this.untrustedLastSeenEventId = '';
75
+ this.conversations = [];
76
+ }
77
+ }
78
+
79
+ /** The raw inbox details. */
80
+ public get raw(): IInboxInitialResponse | IInboxTimelineResponse {
81
+ return this._raw;
82
+ }
83
+
84
+ /**
85
+ * Get the raw inbox initial state if this inbox was created from one
86
+ */
87
+ public getInitialState(): IInboxInitialResponse | undefined {
88
+ return this.isInitialState() ? (this._raw as IInboxInitialResponse) : undefined;
89
+ }
90
+
91
+ /**
92
+ * Get the raw inbox timeline if this inbox was created from one
93
+ */
94
+ public getTimeline(): IInboxTimelineResponse | undefined {
95
+ return this.isTimeline() ? (this._raw as IInboxTimelineResponse) : undefined;
96
+ }
97
+
98
+ /**
99
+ * Check if this inbox was created from an initial state response
100
+ */
101
+ public isInitialState(): boolean {
102
+ return isInboxInitialResponse(this._raw);
103
+ }
104
+
105
+ /**
106
+ * Check if this inbox was created from a timeline response
107
+ */
108
+ public isTimeline(): boolean {
109
+ return isInboxTimelineResponse(this._raw);
110
+ }
111
+
112
+ /**
113
+ * @returns A serializable JSON representation of `this` object.
114
+ */
115
+ public toJSON(): IInbox {
116
+ return {
117
+ conversations: this.conversations.map((conv) => conv.toJSON()),
118
+ cursor: this.cursor,
119
+ lastSeenEventId: this.lastSeenEventId,
120
+ trustedLastSeenEventId: this.trustedLastSeenEventId,
121
+ untrustedLastSeenEventId: this.untrustedLastSeenEventId,
122
+ };
123
+ }
124
+ }
@@ -1,5 +1,10 @@
1
+ import { LogActions } from '../../enums/Logging';
2
+ import { findByFilter } from '../../helper/JsonUtils';
3
+ import { LogService } from '../../services/internal/LogService';
1
4
  import { IList } from '../../types/data/List';
2
5
  import { IList as IRawList } from '../../types/raw/base/List';
6
+ import { ITimelineList } from '../../types/raw/composite/TimelineList';
7
+ import { IListDetailsResponse } from '../../types/raw/list/Details';
3
8
 
4
9
  /**
5
10
  * The details of a single Twitter List.
@@ -14,6 +19,8 @@ export class List implements IList {
14
19
  public createdBy: string;
15
20
  public description?: string;
16
21
  public id: string;
22
+ public isFollowing: boolean;
23
+ public isMember: boolean;
17
24
  public memberCount: number;
18
25
  public name: string;
19
26
  public subscriberCount: number;
@@ -27,9 +34,11 @@ export class List implements IList {
27
34
  this.name = list.name;
28
35
  this.createdAt = new Date(list.created_at).toISOString();
29
36
  this.description = list.description.length ? list.description : undefined;
37
+ this.isFollowing = list.following;
38
+ this.isMember = list.is_member;
30
39
  this.memberCount = list.member_count;
31
40
  this.subscriberCount = list.subscriber_count;
32
- this.createdBy = list.user_results.result.id;
41
+ this.createdBy = list.user_results.result.rest_id;
33
42
  }
34
43
 
35
44
  /** The raw list details. */
@@ -37,6 +46,54 @@ export class List implements IList {
37
46
  return { ...this._raw };
38
47
  }
39
48
 
49
+ /**
50
+ * Extracts and deserializes a single target list from the given raw response data.
51
+ *
52
+ * @param response - The raw response data.
53
+ * @param id - The id of the target list.
54
+ *
55
+ * @returns The target deserialized list.
56
+ */
57
+ public static single(response: IListDetailsResponse, id: string): List | undefined {
58
+ // If list found
59
+ if (response.data.list.id_str === id) {
60
+ return new List(response.data.list as unknown as IRawList);
61
+ }
62
+ // If not found
63
+ else {
64
+ return undefined;
65
+ }
66
+ }
67
+
68
+ /**
69
+ * Extracts and deserializes the timeline of lists followed from the given raw response data.
70
+ *
71
+ * @param response - The raw response data.
72
+ *
73
+ * @returns The deserialized timeline of lists.
74
+ */
75
+ public static timeline(response: NonNullable<unknown>): List[] {
76
+ const lists: List[] = [];
77
+
78
+ // Extracting the matching data
79
+ const extract = findByFilter<ITimelineList>(response, '__typename', 'TimelineTwitterList').map(
80
+ (item) => item.list,
81
+ );
82
+
83
+ // Deserializing valid data
84
+ for (const item of extract) {
85
+ // If valid list
86
+ if (item !== undefined && item.id !== undefined && item.following === true) {
87
+ // Logging
88
+ LogService.log(LogActions.DESERIALIZE, { id: item.id });
89
+
90
+ lists.push(new List(item));
91
+ }
92
+ }
93
+
94
+ return lists;
95
+ }
96
+
40
97
  /**
41
98
  * @returns A serializable JSON representation of `this` object.
42
99
  */
@@ -46,6 +103,8 @@ export class List implements IList {
46
103
  createdBy: this.createdBy,
47
104
  description: this.description,
48
105
  id: this.id,
106
+ isFollowing: this.isFollowing,
107
+ isMember: this.isMember,
49
108
  memberCount: this.memberCount,
50
109
  name: this.name,
51
110
  subscriberCount: this.subscriberCount,
@@ -22,24 +22,24 @@ export class Tweet implements ITweet {
22
22
  /** The raw tweet details. */
23
23
  private readonly _raw: IRawTweet;
24
24
 
25
- public bookmarkCount: number;
25
+ public bookmarkCount?: number;
26
26
  public conversationId: string;
27
27
  public createdAt: string;
28
28
  public entities: TweetEntities;
29
29
  public fullText: string;
30
30
  public id: string;
31
31
  public lang: string;
32
- public likeCount: number;
32
+ public likeCount?: number;
33
33
  public media?: TweetMedia[];
34
- public quoteCount: number;
34
+ public quoteCount?: number;
35
35
  public quoted?: Tweet;
36
- public replyCount: number;
36
+ public replyCount?: number;
37
37
  public replyTo?: string;
38
- public retweetCount: number;
38
+ public retweetCount?: number;
39
39
  public retweetedTweet?: Tweet;
40
40
  public tweetBy: User;
41
41
  public url: string;
42
- public viewCount: number;
42
+ public viewCount?: number;
43
43
 
44
44
  /**
45
45
  * @param tweet - The raw tweet details.
@@ -53,14 +53,16 @@ export class Tweet implements ITweet {
53
53
  this.entities = new TweetEntities(tweet.legacy.entities);
54
54
  this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media));
55
55
  this.quoted = this._getQuotedTweet(tweet);
56
- this.fullText = tweet.note_tweet ? tweet.note_tweet.note_tweet_results.result.text : tweet.legacy.full_text;
56
+ this.fullText = tweet.note_tweet?.note_tweet_results?.result?.text
57
+ ? tweet.note_tweet.note_tweet_results.result.text
58
+ : tweet.legacy.full_text;
57
59
  this.replyTo = tweet.legacy.in_reply_to_status_id_str;
58
60
  this.lang = tweet.legacy.lang;
59
61
  this.quoteCount = tweet.legacy.quote_count;
60
62
  this.replyCount = tweet.legacy.reply_count;
61
63
  this.retweetCount = tweet.legacy.retweet_count;
62
64
  this.likeCount = tweet.legacy.favorite_count;
63
- this.viewCount = tweet.views.count ? parseInt(tweet.views.count) : 0;
65
+ this.viewCount = tweet.views?.count ? parseInt(tweet.views.count) : undefined;
64
66
  this.bookmarkCount = tweet.legacy.bookmark_count;
65
67
  this.retweetedTweet = this._getRetweetedTweet(tweet);
66
68
  this.url = `https://x.com/${this.tweetBy.userName}/status/${this.id}`;
@@ -20,6 +20,8 @@ export class User implements IUser {
20
20
  public followingsCount: number;
21
21
  public fullName: string;
22
22
  public id: string;
23
+ public isFollowed?: boolean;
24
+ public isFollowing?: boolean;
23
25
  public isVerified: boolean;
24
26
  public likeCount: number;
25
27
  public location?: string;
@@ -39,6 +41,8 @@ export class User implements IUser {
39
41
  this.fullName = user.legacy.name;
40
42
  this.createdAt = new Date(user.legacy.created_at).toISOString();
41
43
  this.description = user.legacy.description.length ? user.legacy.description : undefined;
44
+ this.isFollowed = user.legacy.following;
45
+ this.isFollowing = user.legacy.followed_by;
42
46
  this.isVerified = user.is_blue_verified;
43
47
  this.likeCount = user.legacy.favourites_count;
44
48
  this.followersCount = user.legacy.followers_count;
@@ -168,6 +172,8 @@ export class User implements IUser {
168
172
  followingsCount: this.followingsCount,
169
173
  fullName: this.fullName,
170
174
  id: this.id,
175
+ isFollowed: this.isFollowed,
176
+ isFollowing: this.isFollowing,
171
177
  isVerified: this.isVerified,
172
178
  likeCount: this.likeCount,
173
179
  location: this.location,