@glagan/rettiwt-api 7.0.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 (165) hide show
  1. package/.eslintrc.js +166 -0
  2. package/.gitattributes +3 -0
  3. package/.github/FUNDING.yml +4 -0
  4. package/.github/ISSUE_TEMPLATE/bug-report.yml +57 -0
  5. package/.github/ISSUE_TEMPLATE/feature-request.yml +20 -0
  6. package/.github/ISSUE_TEMPLATE/question.yml +15 -0
  7. package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
  8. package/.github/workflows/ci.yml +32 -0
  9. package/.github/workflows/publish.yml +23 -0
  10. package/.nvmrc +1 -0
  11. package/.prettierignore +3 -0
  12. package/.prettierrc +13 -0
  13. package/LICENSE +21 -0
  14. package/README.md +566 -0
  15. package/dist/cli.js +43 -0
  16. package/eslint.config.mjs +17 -0
  17. package/package.json +50 -0
  18. package/src/Rettiwt.ts +97 -0
  19. package/src/cli.ts +48 -0
  20. package/src/collections/Extractors.ts +155 -0
  21. package/src/collections/Groups.ts +81 -0
  22. package/src/collections/Requests.ts +89 -0
  23. package/src/collections/Tweet.ts +17 -0
  24. package/src/commands/DirectMessage.ts +62 -0
  25. package/src/commands/List.ts +90 -0
  26. package/src/commands/Tweet.ts +437 -0
  27. package/src/commands/User.ts +367 -0
  28. package/src/enums/Api.ts +10 -0
  29. package/src/enums/Authentication.ts +10 -0
  30. package/src/enums/Data.ts +13 -0
  31. package/src/enums/Logging.ts +14 -0
  32. package/src/enums/Media.ts +10 -0
  33. package/src/enums/Notification.ts +12 -0
  34. package/src/enums/Resource.ts +69 -0
  35. package/src/enums/Tweet.ts +8 -0
  36. package/src/enums/raw/Analytics.ts +32 -0
  37. package/src/enums/raw/Media.ts +10 -0
  38. package/src/enums/raw/Notification.ts +11 -0
  39. package/src/enums/raw/Tweet.ts +20 -0
  40. package/src/helper/CliUtils.ts +17 -0
  41. package/src/helper/JsonUtils.ts +70 -0
  42. package/src/index.ts +128 -0
  43. package/src/models/RettiwtConfig.ts +101 -0
  44. package/src/models/args/FetchArgs.ts +169 -0
  45. package/src/models/args/PostArgs.ts +93 -0
  46. package/src/models/args/ProfileArgs.ts +68 -0
  47. package/src/models/auth/AuthCookie.ts +58 -0
  48. package/src/models/auth/AuthCredential.ts +83 -0
  49. package/src/models/data/Analytics.ts +97 -0
  50. package/src/models/data/BookmarkFolder.ts +73 -0
  51. package/src/models/data/Conversation.ts +344 -0
  52. package/src/models/data/CursoredData.ts +64 -0
  53. package/src/models/data/DirectMessage.ts +335 -0
  54. package/src/models/data/Inbox.ts +124 -0
  55. package/src/models/data/List.ts +113 -0
  56. package/src/models/data/Notification.ts +84 -0
  57. package/src/models/data/Tweet.ts +388 -0
  58. package/src/models/data/User.ts +187 -0
  59. package/src/models/errors/TwitterError.ts +65 -0
  60. package/src/models/params/Variables.ts +62 -0
  61. package/src/requests/DirectMessage.ts +229 -0
  62. package/src/requests/List.ts +203 -0
  63. package/src/requests/Media.ts +67 -0
  64. package/src/requests/Tweet.ts +607 -0
  65. package/src/requests/User.ts +1191 -0
  66. package/src/services/internal/AuthService.ts +115 -0
  67. package/src/services/internal/ErrorService.ts +41 -0
  68. package/src/services/internal/LogService.ts +34 -0
  69. package/src/services/public/DirectMessageService.ts +159 -0
  70. package/src/services/public/FetcherService.ts +366 -0
  71. package/src/services/public/ListService.ts +241 -0
  72. package/src/services/public/TweetService.ts +886 -0
  73. package/src/services/public/UserService.ts +1154 -0
  74. package/src/types/ErrorHandler.ts +13 -0
  75. package/src/types/Fetch.ts +3 -0
  76. package/src/types/RettiwtConfig.ts +48 -0
  77. package/src/types/args/FetchArgs.ts +233 -0
  78. package/src/types/args/PostArgs.ts +142 -0
  79. package/src/types/args/ProfileArgs.ts +33 -0
  80. package/src/types/auth/AuthCookie.ts +22 -0
  81. package/src/types/auth/AuthCredential.ts +28 -0
  82. package/src/types/auth/TransactionHeader.ts +8 -0
  83. package/src/types/data/Analytics.ts +58 -0
  84. package/src/types/data/BookmarkFolder.ts +12 -0
  85. package/src/types/data/Conversation.ts +44 -0
  86. package/src/types/data/CursoredData.ts +24 -0
  87. package/src/types/data/DirectMessage.ts +33 -0
  88. package/src/types/data/Inbox.ts +23 -0
  89. package/src/types/data/List.ts +33 -0
  90. package/src/types/data/Notification.ts +26 -0
  91. package/src/types/data/Tweet.ts +99 -0
  92. package/src/types/data/User.ts +54 -0
  93. package/src/types/errors/TwitterError.ts +37 -0
  94. package/src/types/params/Variables.ts +41 -0
  95. package/src/types/raw/base/Analytic.ts +32 -0
  96. package/src/types/raw/base/BookmarkFolder.ts +12 -0
  97. package/src/types/raw/base/Cursor.ts +13 -0
  98. package/src/types/raw/base/Error.ts +38 -0
  99. package/src/types/raw/base/LimitedVisibilityTweet.ts +40 -0
  100. package/src/types/raw/base/List.ts +50 -0
  101. package/src/types/raw/base/Media.ts +53 -0
  102. package/src/types/raw/base/Message.ts +22 -0
  103. package/src/types/raw/base/Notification.ts +66 -0
  104. package/src/types/raw/base/Space.ts +35 -0
  105. package/src/types/raw/base/Tweet.ts +139 -0
  106. package/src/types/raw/base/User.ts +182 -0
  107. package/src/types/raw/composite/DataResult.ts +8 -0
  108. package/src/types/raw/composite/TimelineList.ts +10 -0
  109. package/src/types/raw/composite/TimelineTweet.ts +14 -0
  110. package/src/types/raw/composite/TimelineUser.ts +13 -0
  111. package/src/types/raw/dm/Conversation.ts +59 -0
  112. package/src/types/raw/dm/InboxInitial.ts +155 -0
  113. package/src/types/raw/dm/InboxTimeline.ts +301 -0
  114. package/src/types/raw/dm/UserUpdates.ts +46 -0
  115. package/src/types/raw/generic/Response.ts +10 -0
  116. package/src/types/raw/list/AddMember.ts +175 -0
  117. package/src/types/raw/list/Details.ts +176 -0
  118. package/src/types/raw/list/Members.ts +154 -0
  119. package/src/types/raw/list/RemoveMember.ts +174 -0
  120. package/src/types/raw/list/Tweets.ts +2296 -0
  121. package/src/types/raw/media/FinalizeUpload.ts +20 -0
  122. package/src/types/raw/media/InitalizeUpload.ts +12 -0
  123. package/src/types/raw/media/LiveVideoStream.ts +21 -0
  124. package/src/types/raw/space/Details.ts +359 -0
  125. package/src/types/raw/tweet/Bookmark.ts +14 -0
  126. package/src/types/raw/tweet/Details.ts +210 -0
  127. package/src/types/raw/tweet/DetailsBulk.ts +338 -0
  128. package/src/types/raw/tweet/Like.ts +14 -0
  129. package/src/types/raw/tweet/Likers.ts +200 -0
  130. package/src/types/raw/tweet/Post.ts +150 -0
  131. package/src/types/raw/tweet/Replies.ts +539 -0
  132. package/src/types/raw/tweet/Retweet.ts +31 -0
  133. package/src/types/raw/tweet/Retweeters.ts +208 -0
  134. package/src/types/raw/tweet/Schedule.ts +18 -0
  135. package/src/types/raw/tweet/Search.ts +597 -0
  136. package/src/types/raw/tweet/Unbookmark.ts +14 -0
  137. package/src/types/raw/tweet/Unlike.ts +14 -0
  138. package/src/types/raw/tweet/Unpost.ts +20 -0
  139. package/src/types/raw/tweet/Unretweet.ts +31 -0
  140. package/src/types/raw/tweet/Unschedule.ts +14 -0
  141. package/src/types/raw/user/Affiliates.ts +179 -0
  142. package/src/types/raw/user/Analytics.ts +23 -0
  143. package/src/types/raw/user/BookmarkFolderTweets.ts +53 -0
  144. package/src/types/raw/user/BookmarkFolders.ts +41 -0
  145. package/src/types/raw/user/Bookmarks.ts +637 -0
  146. package/src/types/raw/user/Details.ts +185 -0
  147. package/src/types/raw/user/DetailsBulk.ts +104 -0
  148. package/src/types/raw/user/Follow.ts +280 -0
  149. package/src/types/raw/user/Followed.ts +1942 -0
  150. package/src/types/raw/user/Followers.ts +215 -0
  151. package/src/types/raw/user/Following.ts +215 -0
  152. package/src/types/raw/user/Highlights.ts +1287 -0
  153. package/src/types/raw/user/Likes.ts +1254 -0
  154. package/src/types/raw/user/Lists.ts +378 -0
  155. package/src/types/raw/user/Media.ts +1738 -0
  156. package/src/types/raw/user/Notifications.ts +499 -0
  157. package/src/types/raw/user/ProfileUpdate.ts +80 -0
  158. package/src/types/raw/user/Recommended.ts +2319 -0
  159. package/src/types/raw/user/Scheduled.ts +37 -0
  160. package/src/types/raw/user/Search.ts +230 -0
  161. package/src/types/raw/user/Subscriptions.ts +176 -0
  162. package/src/types/raw/user/Tweets.ts +1254 -0
  163. package/src/types/raw/user/TweetsAndReplies.ts +1254 -0
  164. package/src/types/raw/user/Unfollow.ts +280 -0
  165. package/tsconfig.json +97 -0
@@ -0,0 +1,388 @@
1
+ import { LogActions } from '../../enums/Logging';
2
+ import { MediaType } from '../../enums/Media';
3
+ import { RawMediaType } from '../../enums/raw/Media';
4
+ import { findByFilter } from '../../helper/JsonUtils';
5
+
6
+ import { LogService } from '../../services/internal/LogService';
7
+
8
+ import { ITweet, ITweetEntities, ITweetMedia } from '../../types/data/Tweet';
9
+ import { ILimitedVisibilityTweet } from '../../types/raw/base/LimitedVisibilityTweet';
10
+ import { IExtendedMedia as IRawExtendedMedia } from '../../types/raw/base/Media';
11
+ import { ITweet as IRawTweet, IEntities as IRawTweetEntities } from '../../types/raw/base/Tweet';
12
+ import { ITimelineTweet } from '../../types/raw/composite/TimelineTweet';
13
+
14
+ import { User } from './User';
15
+
16
+ /**
17
+ * The details of a single tweet.
18
+ *
19
+ * @public
20
+ */
21
+ export class Tweet implements ITweet {
22
+ /** The raw tweet details. */
23
+ private readonly _raw: IRawTweet;
24
+
25
+ public bookmarkCount?: number;
26
+ public conversationId: string;
27
+ public createdAt: string;
28
+ public entities: TweetEntities;
29
+ public fullText: string;
30
+ public id: string;
31
+ public lang: string;
32
+ public likeCount?: number;
33
+ public media?: TweetMedia[];
34
+ public quoteCount?: number;
35
+ public quoted?: Tweet;
36
+ public replyCount?: number;
37
+ public replyTo?: string;
38
+ public retweetCount?: number;
39
+ public retweetedTweet?: Tweet;
40
+ public tweetBy: User;
41
+ public url: string;
42
+ public viewCount?: number;
43
+
44
+ /**
45
+ * @param tweet - The raw tweet details.
46
+ */
47
+ public constructor(tweet: IRawTweet) {
48
+ this._raw = { ...tweet };
49
+ this.id = tweet.rest_id;
50
+ this.conversationId = tweet.legacy.conversation_id_str;
51
+ this.createdAt = new Date(tweet.legacy.created_at).toISOString();
52
+ this.tweetBy = new User(tweet.core.user_results.result);
53
+ this.entities = new TweetEntities(tweet.legacy.entities);
54
+ this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media));
55
+ this.quoted = this._getQuotedTweet(tweet);
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;
59
+ this.replyTo = tweet.legacy.in_reply_to_status_id_str;
60
+ this.lang = tweet.legacy.lang;
61
+ this.quoteCount = tweet.legacy.quote_count;
62
+ this.replyCount = tweet.legacy.reply_count;
63
+ this.retweetCount = tweet.legacy.retweet_count;
64
+ this.likeCount = tweet.legacy.favorite_count;
65
+ this.viewCount = tweet.views?.count ? parseInt(tweet.views.count) : undefined;
66
+ this.bookmarkCount = tweet.legacy.bookmark_count;
67
+ this.retweetedTweet = this._getRetweetedTweet(tweet);
68
+ this.url = `https://x.com/${this.tweetBy.userName}/status/${this.id}`;
69
+ }
70
+
71
+ /** The raw tweet details. */
72
+ public get raw(): IRawTweet {
73
+ return { ...this._raw };
74
+ }
75
+
76
+ /**
77
+ * Extract and deserialize the original quoted tweet from the given raw tweet.
78
+ *
79
+ * @param tweet - The raw tweet.
80
+ *
81
+ * @returns - The deserialized original quoted tweet.
82
+ */
83
+ private _getQuotedTweet(tweet: IRawTweet): Tweet | undefined {
84
+ // If tweet with limited visibility
85
+ if (
86
+ tweet.quoted_status_result &&
87
+ tweet.quoted_status_result?.result?.__typename == 'TweetWithVisibilityResults' &&
88
+ (tweet.quoted_status_result.result as ILimitedVisibilityTweet)?.tweet?.legacy
89
+ ) {
90
+ return new Tweet((tweet.quoted_status_result.result as ILimitedVisibilityTweet).tweet);
91
+ }
92
+ // If normal tweet
93
+ else if ((tweet.quoted_status_result?.result as IRawTweet)?.rest_id) {
94
+ return new Tweet(tweet.quoted_status_result.result as IRawTweet);
95
+ }
96
+ // Else, skip
97
+ else {
98
+ return undefined;
99
+ }
100
+ }
101
+
102
+ /**
103
+ * Extract and deserialize the original retweeted tweet from the given raw tweet.
104
+ *
105
+ * @param tweet - The raw tweet.
106
+ *
107
+ * @returns - The deserialized original retweeted tweet.
108
+ */
109
+ private _getRetweetedTweet(tweet: IRawTweet): Tweet | undefined {
110
+ // If retweet with limited visibility
111
+ if (
112
+ tweet.legacy?.retweeted_status_result &&
113
+ tweet.legacy?.retweeted_status_result?.result?.__typename == 'TweetWithVisibilityResults' &&
114
+ (tweet.legacy?.retweeted_status_result?.result as ILimitedVisibilityTweet)?.tweet?.legacy
115
+ ) {
116
+ return new Tweet((tweet.legacy.retweeted_status_result.result as ILimitedVisibilityTweet).tweet);
117
+ }
118
+ // If normal tweet
119
+ else if ((tweet.legacy?.retweeted_status_result?.result as IRawTweet)?.rest_id) {
120
+ return new Tweet(tweet.legacy.retweeted_status_result.result as IRawTweet);
121
+ }
122
+ // Else, skip
123
+ else {
124
+ return undefined;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Extracts and deserializes multiple target tweets from the given raw response data.
130
+ *
131
+ * @param response - The raw response data.
132
+ * @param ids - The ids of the target tweets.
133
+ *
134
+ * @returns The target deserialized tweets.
135
+ */
136
+ public static multiple(response: NonNullable<unknown>, ids: string[]): Tweet[] {
137
+ let tweets: Tweet[] = [];
138
+
139
+ // Extracting the matching data
140
+ const extract = findByFilter<IRawTweet>(response, '__typename', 'Tweet');
141
+
142
+ // Deserializing valid data
143
+ for (const item of extract) {
144
+ if (item.legacy) {
145
+ // Logging
146
+ LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
147
+
148
+ tweets.push(new Tweet(item));
149
+ } else {
150
+ // Logging
151
+ LogService.log(LogActions.WARNING, {
152
+ action: LogActions.DESERIALIZE,
153
+ message: `Tweet not found, skipping`,
154
+ });
155
+ }
156
+ }
157
+
158
+ // Filtering only required tweets, if required
159
+ if (ids && ids.length) {
160
+ tweets = tweets.filter((tweet) => ids.includes(tweet.id));
161
+ }
162
+
163
+ return tweets;
164
+ }
165
+
166
+ /**
167
+ * Extracts and deserializes a single target tweet from the given raw response data.
168
+ *
169
+ * @param response - The raw response data.
170
+ * @param id - The id of the target tweet.
171
+ *
172
+ * @returns The target deserialized tweet.
173
+ */
174
+ public static single(response: NonNullable<unknown>, id: string): Tweet | undefined {
175
+ const tweets: Tweet[] = [];
176
+
177
+ // Extracting the matching data
178
+ const extract = findByFilter<IRawTweet>(response, 'rest_id', id);
179
+
180
+ // Deserializing valid data
181
+ for (const item of extract) {
182
+ if (item.legacy) {
183
+ // Logging
184
+ LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
185
+
186
+ tweets.push(new Tweet(item));
187
+ } else {
188
+ // Logging
189
+ LogService.log(LogActions.WARNING, {
190
+ action: LogActions.DESERIALIZE,
191
+ message: `Tweet not found, skipping`,
192
+ });
193
+ }
194
+ }
195
+
196
+ return tweets.length ? tweets[0] : undefined;
197
+ }
198
+
199
+ /**
200
+ * Extracts and deserializes the timeline of tweets from the given raw response data.
201
+ *
202
+ * @param response - The raw response data.
203
+ * @param ids - The IDs of specific tweets that need to be extracted.
204
+ *
205
+ * @returns The deserialized timeline of tweets.
206
+ */
207
+ public static timeline(response: NonNullable<unknown>): Tweet[] {
208
+ const tweets: Tweet[] = [];
209
+
210
+ // Extracting the matching data
211
+ const extract = findByFilter<ITimelineTweet>(response, '__typename', 'TimelineTweet');
212
+
213
+ // Deserializing valid data
214
+ for (const item of extract) {
215
+ // If tweet with limited visibility
216
+ if (
217
+ item.tweet_results?.result &&
218
+ item.tweet_results?.result?.__typename == 'TweetWithVisibilityResults' &&
219
+ (item.tweet_results?.result as ILimitedVisibilityTweet)?.tweet?.legacy
220
+ ) {
221
+ tweets.push(new Tweet((item.tweet_results.result as ILimitedVisibilityTweet).tweet));
222
+ }
223
+ // If normal tweet
224
+ else if ((item.tweet_results?.result as IRawTweet)?.legacy) {
225
+ // Logging
226
+ LogService.log(LogActions.DESERIALIZE, { id: (item.tweet_results.result as IRawTweet).rest_id });
227
+
228
+ tweets.push(new Tweet(item.tweet_results.result as IRawTweet));
229
+ }
230
+ // If invalid/unrecognized tweet
231
+ else {
232
+ // Logging
233
+ LogService.log(LogActions.WARNING, {
234
+ action: LogActions.DESERIALIZE,
235
+ message: `Tweet not found, skipping`,
236
+ });
237
+ }
238
+ }
239
+
240
+ return tweets;
241
+ }
242
+
243
+ /**
244
+ * @returns A serializable JSON representation of `this` object.
245
+ */
246
+ public toJSON(): ITweet {
247
+ return {
248
+ bookmarkCount: this.bookmarkCount,
249
+ conversationId: this.conversationId,
250
+ createdAt: this.createdAt,
251
+ entities: this.entities.toJSON(),
252
+ fullText: this.fullText,
253
+ id: this.id,
254
+ lang: this.lang,
255
+ likeCount: this.likeCount,
256
+ media: this.media?.map((item) => item.toJSON()),
257
+ quoteCount: this.quoteCount,
258
+ quoted: this.quoted?.toJSON(),
259
+ replyCount: this.replyCount,
260
+ replyTo: this.replyTo,
261
+ retweetCount: this.retweetCount,
262
+ retweetedTweet: this.retweetedTweet?.toJSON(),
263
+ tweetBy: this.tweetBy.toJSON(),
264
+ url: this.url,
265
+ viewCount: this.viewCount,
266
+ };
267
+ }
268
+ }
269
+
270
+ /**
271
+ * The different types parsed entities like urls, media, mentions, hashtags, etc.
272
+ *
273
+ * @public
274
+ */
275
+ export class TweetEntities implements ITweetEntities {
276
+ /** The list of hashtags mentioned in the tweet. */
277
+ public hashtags: string[] = [];
278
+
279
+ /** The list of IDs of users mentioned in the tweet. */
280
+ public mentionedUsers: string[] = [];
281
+
282
+ /** The list of urls mentioned in the tweet. */
283
+ public urls: string[] = [];
284
+
285
+ /**
286
+ * @param entities - The raw tweet entities.
287
+ */
288
+ public constructor(entities: IRawTweetEntities) {
289
+ // Extracting user mentions
290
+ if (entities.user_mentions) {
291
+ for (const user of entities.user_mentions) {
292
+ this.mentionedUsers.push(user.screen_name);
293
+ }
294
+ }
295
+
296
+ // Extracting urls
297
+ if (entities.urls) {
298
+ for (const url of entities.urls) {
299
+ this.urls.push(url.expanded_url);
300
+ }
301
+ }
302
+
303
+ // Extracting hashtags
304
+ if (entities.hashtags) {
305
+ for (const hashtag of entities.hashtags) {
306
+ this.hashtags.push(hashtag.text);
307
+ }
308
+ }
309
+ }
310
+
311
+ /**
312
+ * @returns A serializable JSON representation of `this` object.
313
+ */
314
+ public toJSON(): ITweetEntities {
315
+ return {
316
+ hashtags: this.hashtags,
317
+ mentionedUsers: this.mentionedUsers,
318
+ urls: this.urls,
319
+ };
320
+ }
321
+ }
322
+
323
+ /**
324
+ * The details of a single media content included in a tweet.
325
+ *
326
+ * @public
327
+ */
328
+ export class TweetMedia implements ITweetMedia {
329
+ /** The ID of the media. */
330
+ public id: string;
331
+
332
+ /** The thumbnail URL for the video content of the tweet. */
333
+ public thumbnailUrl?: string;
334
+
335
+ /** The type of media. */
336
+ public type: MediaType;
337
+
338
+ /** The direct URL to the media. */
339
+ public url = '';
340
+
341
+ /**
342
+ * @param media - The raw media details.
343
+ */
344
+ public constructor(media: IRawExtendedMedia) {
345
+ this.id = media.id_str;
346
+
347
+ // If the media is a photo
348
+ if (media.type == RawMediaType.PHOTO) {
349
+ this.type = MediaType.PHOTO;
350
+ this.url = media.media_url_https;
351
+ }
352
+ // If the media is a gif
353
+ else if (media.type == RawMediaType.GIF) {
354
+ this.type = MediaType.GIF;
355
+ this.url = media.video_info?.variants[0].url as string;
356
+ }
357
+ // If the media is a video
358
+ else {
359
+ this.type = MediaType.VIDEO;
360
+ this.thumbnailUrl = media.media_url_https;
361
+
362
+ /** The highest bitrate of all variants. */
363
+ let highestRate = 0;
364
+
365
+ /**
366
+ * Selecting the URL of the video variant with the highest bitrate.
367
+ */
368
+ media.video_info?.variants.forEach((variant) => {
369
+ if (variant.bitrate > highestRate) {
370
+ highestRate = variant.bitrate;
371
+ this.url = variant.url;
372
+ }
373
+ });
374
+ }
375
+ }
376
+
377
+ /**
378
+ * @returns A serializable JSON representation of `this` object.
379
+ */
380
+ public toJSON(): ITweetMedia {
381
+ return {
382
+ id: this.id,
383
+ thumbnailUrl: this.thumbnailUrl,
384
+ type: this.type,
385
+ url: this.url,
386
+ };
387
+ }
388
+ }
@@ -0,0 +1,187 @@
1
+ import { LogActions } from '../../enums/Logging';
2
+ import { findByFilter } from '../../helper/JsonUtils';
3
+ import { LogService } from '../../services/internal/LogService';
4
+ import { IUser } from '../../types/data/User';
5
+ import { IUser as IRawUser } from '../../types/raw/base/User';
6
+ import { ITimelineUser as IRawTimelineUser } from '../../types/raw/composite/TimelineUser';
7
+
8
+ /**
9
+ * The details of a single user.
10
+ *
11
+ * @public
12
+ */
13
+ export class User implements IUser {
14
+ /** The raw user details. */
15
+ private readonly _raw: IRawUser;
16
+
17
+ public createdAt: string;
18
+ public description?: string;
19
+ public followersCount: number;
20
+ public followingsCount: number;
21
+ public fullName: string;
22
+ public id: string;
23
+ public isFollowed?: boolean;
24
+ public isFollowing?: boolean;
25
+ public isVerified: boolean;
26
+ public likeCount: number;
27
+ public location?: string;
28
+ public pinnedTweet?: string;
29
+ public profileBanner?: string;
30
+ public profileImage: string;
31
+ public statusesCount: number;
32
+ public userName: string;
33
+
34
+ /**
35
+ * @param user - The raw user details.
36
+ */
37
+ public constructor(user: IRawUser) {
38
+ this._raw = { ...user };
39
+ this.id = user.rest_id;
40
+ this.userName = user.core?.screen_name ?? user.legacy.screen_name ?? '';
41
+ this.fullName = user.core?.name ?? user.legacy.name ?? '';
42
+ this.createdAt = new Date(user.core?.created_at ?? user.legacy.created_at ?? 0).toISOString();
43
+ this.description = user.legacy.description.length ? user.legacy.description : undefined;
44
+ this.isFollowed = user.legacy.following;
45
+ this.isFollowing = user.legacy.followed_by;
46
+ this.isVerified = user.is_blue_verified;
47
+ this.likeCount = user.legacy.favourites_count;
48
+ this.followersCount = user.legacy.followers_count;
49
+ this.followingsCount = user.legacy.friends_count;
50
+ this.statusesCount = user.legacy.statuses_count;
51
+ this.location = user.location?.location ?? user.legacy.location ?? undefined;
52
+ this.pinnedTweet = user.legacy.pinned_tweet_ids_str[0];
53
+ this.profileBanner = user.legacy.profile_banner_url;
54
+ this.profileImage = user.avatar?.image_url ?? user.legacy.profile_image_url_https ?? '';
55
+ }
56
+
57
+ /** The raw user details. */
58
+ public get raw(): IRawUser {
59
+ return { ...this._raw };
60
+ }
61
+
62
+ /**
63
+ * Extracts and deserializes multiple target users from the given raw response data.
64
+ *
65
+ * @param response - The raw response data.
66
+ * @param ids - The ids of the target users.
67
+ *
68
+ * @returns The target deserialized users.
69
+ */
70
+ public static multiple(response: NonNullable<unknown>, ids: string[]): User[] {
71
+ let users: User[] = [];
72
+
73
+ // Extracting the matching data
74
+ const extract = findByFilter<IRawUser>(response, '__typename', 'User');
75
+
76
+ // Deserializing valid data
77
+ for (const item of extract) {
78
+ if (item.legacy && (item.core?.created_at || item.legacy.created_at)) {
79
+ // Logging
80
+ LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
81
+
82
+ users.push(new User(item));
83
+ } else {
84
+ // Logging
85
+ LogService.log(LogActions.WARNING, {
86
+ action: LogActions.DESERIALIZE,
87
+ message: `User not found, skipping`,
88
+ });
89
+ }
90
+ }
91
+
92
+ // Filtering only required user, if required
93
+ if (ids && ids.length) {
94
+ users = users.filter((user) => ids.includes(user.id));
95
+ }
96
+
97
+ return users;
98
+ }
99
+
100
+ /**
101
+ * Extracts and deserializes a single target user from the given raw response data.
102
+ *
103
+ * @param response - The raw response data.
104
+ *
105
+ * @returns The target deserialized user.
106
+ */
107
+ public static single(response: NonNullable<unknown>): User | undefined {
108
+ const users: User[] = [];
109
+
110
+ // Extracting the matching data
111
+ const extract = findByFilter<IRawUser>(response, '__typename', 'User');
112
+
113
+ // Deserializing valid data
114
+ for (const item of extract) {
115
+ if (item.legacy && (item.core?.created_at || item.legacy.created_at)) {
116
+ // Logging
117
+ LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
118
+
119
+ users.push(new User(item));
120
+ } else {
121
+ // Logging
122
+ LogService.log(LogActions.WARNING, {
123
+ action: LogActions.DESERIALIZE,
124
+ message: `User not found, skipping`,
125
+ });
126
+ }
127
+ }
128
+
129
+ return users.length ? users[0] : undefined;
130
+ }
131
+
132
+ /**
133
+ * Extracts and deserializes the timeline of users from the given raw response data.
134
+ *
135
+ * @param response - The raw response data.
136
+ *
137
+ * @returns The deserialized timeline of users.
138
+ */
139
+ public static timeline(response: NonNullable<unknown>): User[] {
140
+ const users: User[] = [];
141
+
142
+ // Extracting the matching data
143
+ const extract = findByFilter<IRawTimelineUser>(response, '__typename', 'TimelineUser');
144
+
145
+ // Deserializing valid data
146
+ for (const item of extract) {
147
+ if (item.user_results?.result?.legacy) {
148
+ // Logging
149
+ LogService.log(LogActions.DESERIALIZE, { id: item.user_results.result.rest_id });
150
+
151
+ users.push(new User(item.user_results.result));
152
+ } else {
153
+ // Logging
154
+ LogService.log(LogActions.WARNING, {
155
+ action: LogActions.DESERIALIZE,
156
+ message: `User not found, skipping`,
157
+ });
158
+ }
159
+ }
160
+
161
+ return users;
162
+ }
163
+
164
+ /**
165
+ * @returns A serializable JSON representation of `this` object.
166
+ */
167
+ public toJSON(): IUser {
168
+ return {
169
+ createdAt: this.createdAt,
170
+ description: this.description,
171
+ followersCount: this.followersCount,
172
+ followingsCount: this.followingsCount,
173
+ fullName: this.fullName,
174
+ id: this.id,
175
+ isFollowed: this.isFollowed,
176
+ isFollowing: this.isFollowing,
177
+ isVerified: this.isVerified,
178
+ likeCount: this.likeCount,
179
+ location: this.location,
180
+ pinnedTweet: this.pinnedTweet,
181
+ profileBanner: this.profileBanner,
182
+ profileImage: this.profileImage,
183
+ statusesCount: this.statusesCount,
184
+ userName: this.userName,
185
+ };
186
+ }
187
+ }
@@ -0,0 +1,65 @@
1
+ import { FetchError } from 'ofetch';
2
+
3
+ import { ITwitterError, ITwitterErrorDetails } from '../../types/errors/TwitterError';
4
+ import { IErrorData as IRawErrorData, IErrorDetails as IRawErrorDetails } from '../../types/raw/base/Error';
5
+
6
+ /**
7
+ * The error thrown by Twitter API.
8
+ *
9
+ * @public
10
+ */
11
+ export class TwitterError extends Error implements ITwitterError {
12
+ public details: ITwitterErrorDetails[];
13
+ public message: string;
14
+ public name: string;
15
+ public status: number;
16
+
17
+ /**
18
+ * @param error - The error response received from Twitter.
19
+ */
20
+ public constructor(error: FetchError<IRawErrorData | IRawErrorDetails>) {
21
+ super(error.message);
22
+ this.details = (
23
+ (error.data as IRawErrorData)?.errors
24
+ ? (error.data as IRawErrorData).errors.map((item) => new TwitterErrorDetails(item))
25
+ : [new TwitterErrorDetails(error.data as IRawErrorDetails)]
26
+ ).map((item) => item.toJSON());
27
+ this.message = error.message;
28
+ this.name = 'TWITTER_ERROR';
29
+ this.status = error.status ?? 500;
30
+ }
31
+ }
32
+
33
+ /**
34
+ * The error details.
35
+ *
36
+ * @public
37
+ */
38
+ export class TwitterErrorDetails implements ITwitterErrorDetails {
39
+ public code: number;
40
+ public message: string;
41
+ public name?: string;
42
+ public type?: string;
43
+
44
+ /**
45
+ * @param details - The details of the error.
46
+ */
47
+ public constructor(details: IRawErrorDetails) {
48
+ this.code = details.code;
49
+ this.message = details.message;
50
+ this.name = details.name;
51
+ this.type = details.kind;
52
+ }
53
+
54
+ /**
55
+ * @returns The JSON representation of `this` object.
56
+ */
57
+ public toJSON(): ITwitterErrorDetails {
58
+ return {
59
+ code: this.code,
60
+ message: this.message,
61
+ name: this.message,
62
+ type: this.type,
63
+ };
64
+ }
65
+ }
@@ -0,0 +1,62 @@
1
+ import { IMediaEntityVariable, IMediaVariable, IReplyVariable } from '../../types/params/Variables';
2
+ import { NewTweetMedia } from '../args/PostArgs';
3
+
4
+ /**
5
+ * Media to be sent as payload.
6
+ *
7
+ * @internal
8
+ */
9
+ export class MediaVariable implements IMediaVariable {
10
+ /* eslint-disable @typescript-eslint/naming-convention */
11
+ public media_entities: MediaEntityVariable[];
12
+ public possibly_sensitive: boolean;
13
+ /* eslint-enable @typescript-eslint/naming-convention */
14
+
15
+ /**
16
+ * @param media - The list of NewTweetMedia objects specifying the media items to be sent in the Tweet.
17
+ */
18
+ public constructor(media: NewTweetMedia[]) {
19
+ this.media_entities = media.map((item) => new MediaEntityVariable(item));
20
+ this.possibly_sensitive = false;
21
+ }
22
+ }
23
+
24
+ /**
25
+ * Each media item in the media payload.
26
+ *
27
+ * @internal
28
+ */
29
+ export class MediaEntityVariable implements IMediaEntityVariable {
30
+ /* eslint-disable @typescript-eslint/naming-convention */
31
+ public media_id: string;
32
+ public tagged_users: string[];
33
+ /* eslint-enable @typescript-eslint/naming-convention */
34
+
35
+ /**
36
+ * @param media - The NewTweetMedia object specifying the details of the media item to be included in the payload.
37
+ */
38
+ public constructor(media: NewTweetMedia) {
39
+ this.media_id = media.id;
40
+ this.tagged_users = media.tags ?? [];
41
+ }
42
+ }
43
+
44
+ /**
45
+ * Reply specific details to be sent in payload.
46
+ *
47
+ * @internal
48
+ */
49
+ export class ReplyVariable implements IReplyVariable {
50
+ /* eslint-disable @typescript-eslint/naming-convention */
51
+ public exclude_reply_user_ids: string[];
52
+ public in_reply_to_tweet_id: string;
53
+ /* eslint-enable @typescript-eslint/naming-convention */
54
+
55
+ /**
56
+ * @param replyTo - The id of the Tweet to which this Tweet is a reply.
57
+ */
58
+ public constructor(replyTo: string) {
59
+ this.in_reply_to_tweet_id = replyTo;
60
+ this.exclude_reply_user_ids = [];
61
+ }
62
+ }