@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,366 @@
1
+ import { Cookie } from 'cookiejar';
2
+ import { JSDOM } from 'jsdom';
3
+ import { FetchError, ofetch } from 'ofetch';
4
+ import { ClientTransaction } from 'x-client-transaction-id';
5
+
6
+ import { AllowGuestAuthenticationGroup, FetchResourcesGroup, PostResourcesGroup } from '../../collections/Groups';
7
+ import { Requests } from '../../collections/Requests';
8
+ import { ApiErrors } from '../../enums/Api';
9
+ import { LogActions } from '../../enums/Logging';
10
+ import { ResourceType } from '../../enums/Resource';
11
+ import { FetchArgs } from '../../models/args/FetchArgs';
12
+ import { PostArgs } from '../../models/args/PostArgs';
13
+ import { AuthCredential } from '../../models/auth/AuthCredential';
14
+ import { TwitterError } from '../../models/errors/TwitterError';
15
+ import { RettiwtConfig } from '../../models/RettiwtConfig';
16
+ import { IFetchArgs } from '../../types/args/FetchArgs';
17
+ import { IPostArgs } from '../../types/args/PostArgs';
18
+ import { ITransactionHeader } from '../../types/auth/TransactionHeader';
19
+ import { IErrorHandler } from '../../types/ErrorHandler';
20
+ import { IErrorData } from '../../types/raw/base/Error';
21
+
22
+ import { AuthService } from '../internal/AuthService';
23
+ import { ErrorService } from '../internal/ErrorService';
24
+ import { LogService } from '../internal/LogService';
25
+
26
+ /**
27
+ * The base service that handles all HTTP requests.
28
+ *
29
+ * @public
30
+ */
31
+ export class FetcherService {
32
+ /** The AuthService instance to use. */
33
+ private readonly _auth: AuthService;
34
+
35
+ /** The delay/delay function to use (ms). */
36
+ private readonly _delay?: number | (() => number | Promise<number>);
37
+
38
+ /** The service used to handle HTTP and API errors */
39
+ private readonly _errorHandler: IErrorHandler;
40
+
41
+ /** The max wait time for a response. */
42
+ private readonly _timeout: number;
43
+
44
+ /** The config object. */
45
+ protected readonly config: RettiwtConfig;
46
+
47
+ /**
48
+ * @param config - The config object for configuring the Rettiwt instance.
49
+ */
50
+ public constructor(config: RettiwtConfig) {
51
+ LogService.enabled = config.logging ?? false;
52
+ this.config = config;
53
+ this._delay = config.delay;
54
+ this._errorHandler = config.errorHandler ?? new ErrorService();
55
+ this._timeout = config.timeout ?? 0;
56
+ this._auth = new AuthService(config);
57
+ }
58
+
59
+ /**
60
+ * Checks the authorization status based on the requested resource.
61
+ *
62
+ * @param resource - The requested resource.
63
+ *
64
+ * @throws An error if not authorized to access the requested resource.
65
+ */
66
+ private _checkAuthorization(resource: ResourceType): void {
67
+ // Logging
68
+ LogService.log(LogActions.AUTHORIZATION, { authenticated: this.config.userId != undefined });
69
+
70
+ // Checking authorization status
71
+ if (!AllowGuestAuthenticationGroup.includes(resource) && this.config.userId == undefined) {
72
+ throw new Error(ApiErrors.RESOURCE_NOT_ALLOWED);
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Returns the AuthCredentials based on the type of key present.
78
+ *
79
+ * @returns The generated AuthCredential
80
+ */
81
+ private async _getCredential(): Promise<AuthCredential> {
82
+ if (this.config.apiKey) {
83
+ // Logging
84
+ LogService.log(LogActions.GET, { target: 'USER_CREDENTIAL' });
85
+
86
+ return new AuthCredential(
87
+ AuthService.decodeCookie(this.config.apiKey)
88
+ .split(';')
89
+ .map((item) => new Cookie(item)),
90
+ );
91
+ } else {
92
+ // Logging
93
+ LogService.log(LogActions.GET, { target: 'NEW_GUEST_CREDENTIAL' });
94
+
95
+ return this._auth.guest();
96
+ }
97
+ }
98
+
99
+ /**
100
+ * Generates the header for the transaction ID.
101
+ *
102
+ * @param method - The target method.
103
+ * @param url - The target URL.
104
+ *
105
+ * @returns The header containing the transaction ID.
106
+ */
107
+ private async _getTransactionHeader(method: string, url: string): Promise<ITransactionHeader> {
108
+ // Get the X homepage HTML document (using utility function)
109
+ const document = await this._handleXMigration();
110
+
111
+ // Create and initialize ClientTransaction instance
112
+ const transaction = await ClientTransaction.create(document);
113
+
114
+ // Getting the URL path excluding all params
115
+ const path = new URL(url).pathname.split('?')[0].trim();
116
+
117
+ // Generating the transaction ID
118
+ const tid = await transaction.generateTransactionId(method.toUpperCase(), path);
119
+
120
+ return {
121
+ /* eslint-disable @typescript-eslint/naming-convention */
122
+ 'x-client-transaction-id': tid,
123
+ /* eslint-enable @typescript-eslint/naming-convention */
124
+ };
125
+ }
126
+
127
+ private async _handleXMigration(): Promise<Document> {
128
+ // Fetch X.com homepage
129
+ const homePageResponse = await ofetch<string>('https://x.com', {
130
+ headers: this.config.headers,
131
+ agent: this.config.httpsAgent,
132
+ });
133
+
134
+ // Parse HTML using linkedom
135
+ let dom = new JSDOM(homePageResponse);
136
+ let document = dom.window.document;
137
+
138
+ // Check for migration redirection links
139
+ const migrationRedirectionRegex = new RegExp(
140
+ '(http(?:s)?://(?:www\\.)?(twitter|x){1}\\.com(/x)?/migrate([/?])?tok=[a-zA-Z0-9%\\-_]+)+',
141
+ 'i',
142
+ );
143
+
144
+ const metaRefresh = document.querySelector("meta[http-equiv='refresh']");
145
+ const metaContent = metaRefresh ? metaRefresh.getAttribute('content') || '' : '';
146
+
147
+ const migrationRedirectionUrl =
148
+ migrationRedirectionRegex.exec(metaContent) || migrationRedirectionRegex.exec(homePageResponse);
149
+
150
+ if (migrationRedirectionUrl) {
151
+ // Follow redirection URL
152
+ const redirectResponse = await ofetch<string>(migrationRedirectionUrl[0], {
153
+ agent: this.config.httpsAgent,
154
+ });
155
+
156
+ dom = new JSDOM(redirectResponse);
157
+ document = dom.window.document;
158
+ }
159
+
160
+ // Handle migration form if present
161
+ const migrationForm =
162
+ document.querySelector("form[name='f']") ||
163
+ document.querySelector("form[action='https://x.com/x/migrate']");
164
+
165
+ if (migrationForm) {
166
+ const url = migrationForm.getAttribute('action') || 'https://x.com/x/migrate';
167
+ const method = migrationForm.getAttribute('method') || 'POST';
168
+
169
+ // Collect form input fields
170
+ const requestPayload = new FormData();
171
+
172
+ const inputFields = migrationForm.querySelectorAll('input');
173
+ for (const element of Array.from(inputFields)) {
174
+ const name = element.getAttribute('name');
175
+ const value = element.getAttribute('value');
176
+ if (name && value) {
177
+ requestPayload.append(name, value);
178
+ }
179
+ }
180
+
181
+ // Submit form using POST request
182
+ const formResponse = await ofetch<string>(url, {
183
+ method: method,
184
+ body: requestPayload,
185
+ headers: {
186
+ /* eslint-disable @typescript-eslint/naming-convention */
187
+
188
+ 'Content-Type': 'multipart/form-data',
189
+ ...this.config.headers,
190
+
191
+ /* eslint-enable @typescript-eslint/naming-convention */
192
+ },
193
+ agent: this.config.httpsAgent,
194
+ });
195
+
196
+ dom = new JSDOM(formResponse);
197
+ document = dom.window.document;
198
+ }
199
+
200
+ // Return final DOM document
201
+ return document;
202
+ }
203
+
204
+ /**
205
+ * Validates the given args against the given resource.
206
+ *
207
+ * @param resource - The resource against which validation is to be done.
208
+ * @param args - The args to be validated.
209
+ *
210
+ * @returns The validated args.
211
+ */
212
+ private _validateArgs(resource: ResourceType, args: IFetchArgs | IPostArgs): FetchArgs | PostArgs | undefined {
213
+ if (FetchResourcesGroup.includes(resource)) {
214
+ // Logging
215
+ LogService.log(LogActions.VALIDATE, { target: 'FETCH_ARGS' });
216
+
217
+ return new FetchArgs(args);
218
+ } else if (PostResourcesGroup.includes(resource)) {
219
+ // Logging
220
+ LogService.log(LogActions.VALIDATE, { target: 'POST_ARGS' });
221
+
222
+ return new PostArgs(args);
223
+ }
224
+ }
225
+
226
+ /**
227
+ * Introduces a delay using the configured delay/delay function.
228
+ */
229
+ private async _wait(): Promise<void> {
230
+ // If no delay is set, skip
231
+ if (this._delay == undefined) {
232
+ return;
233
+ }
234
+
235
+ /** The delay (in ms) to use. */
236
+ let delay = 0;
237
+
238
+ // Getting the delay
239
+ if (this._delay && typeof this._delay == 'number') {
240
+ delay = this._delay;
241
+ } else if (this._delay && typeof this._delay == 'function') {
242
+ delay = await this._delay();
243
+ }
244
+
245
+ // Awaiting for the delay time
246
+ await new Promise((resolve) => setTimeout(resolve, delay));
247
+ }
248
+
249
+ /**
250
+ * Makes an HTTP request according to the given parameters.
251
+ *
252
+ * @param resource - The requested resource.
253
+ * @param config - The request configuration.
254
+ *
255
+ * @typeParam T - The type of the returned response data.
256
+ *
257
+ * @returns The raw data response received.
258
+ *
259
+ * @example
260
+ *
261
+ * #### Fetching the raw details of a single user, using their username
262
+ * ```ts
263
+ * import { FetcherService, ResourceType } from 'rettiwt-api';
264
+ *
265
+ * // Creating a new FetcherService instance using the given 'API_KEY'
266
+ * const fetcher = new FetcherService({ apiKey: API_KEY });
267
+ *
268
+ * // Fetching the details of the User with username 'user1'
269
+ * fetcher.request(ResourceType.USER_DETAILS_BY_USERNAME, { id: 'user1' })
270
+ * .then(res => {
271
+ * console.log(res);
272
+ * })
273
+ * .catch(err => {
274
+ * console.log(err);
275
+ * });
276
+ * ```
277
+ */
278
+ public async request<T = unknown>(resource: ResourceType, args: IFetchArgs | IPostArgs): Promise<T> {
279
+ /** The current retry number. */
280
+ let retry = 0;
281
+
282
+ /** The error, if any. */
283
+ let error: unknown = undefined;
284
+
285
+ // Logging
286
+ LogService.log(LogActions.REQUEST, { resource: resource, args: args });
287
+
288
+ // Checking authorization for the requested resource
289
+ this._checkAuthorization(resource);
290
+
291
+ // Validating args
292
+ args = this._validateArgs(resource, args)!;
293
+
294
+ // Getting credentials from key
295
+ const cred: AuthCredential = await this._getCredential();
296
+
297
+ // Getting request configuration
298
+ const config = Requests[resource](args);
299
+
300
+ // Setting additional request parameters
301
+ config.headers = {
302
+ ...config.headers,
303
+ ...cred.toHeader(),
304
+ ...this.config.headers,
305
+ };
306
+ config.agent = this.config.httpsAgent;
307
+ config.timeout = this._timeout;
308
+
309
+ // Using retries for error 404
310
+ do {
311
+ // Sending the request
312
+ try {
313
+ // Getting and appending transaction information
314
+ config.headers = {
315
+ ...(await this._getTransactionHeader(config.method ?? '', config.url ?? '')),
316
+ ...config.headers,
317
+ };
318
+
319
+ // Introducing a delay
320
+ await this._wait();
321
+
322
+ // Getting the response body
323
+ const responseData = await ofetch<T>(config.url, { ...config, responseType: undefined });
324
+
325
+ // Check for Twitter API errors in response body
326
+ // Type guard to check if response contains errors
327
+ const potentialErrorResponse = responseData as unknown as Partial<IErrorData>;
328
+ if (
329
+ potentialErrorResponse.errors &&
330
+ Array.isArray(potentialErrorResponse.errors) &&
331
+ (potentialErrorResponse.data === undefined ||
332
+ JSON.stringify(potentialErrorResponse.data) === JSON.stringify({}))
333
+ ) {
334
+ // Throw TwitterError using existing error class
335
+ throw new TwitterError({
336
+ data: { errors: potentialErrorResponse.errors },
337
+ response: {
338
+ status: 200,
339
+ },
340
+ message: potentialErrorResponse.errors[0]?.message ?? 'Twitter API Error',
341
+ status: 200,
342
+ } as FetchError<IErrorData>);
343
+ }
344
+
345
+ // Returning the reponse body
346
+ return responseData;
347
+ } catch (err) {
348
+ // If it's an error 404, retry
349
+ if (err instanceof FetchError && err.status === 404) {
350
+ error = err;
351
+ continue;
352
+ }
353
+ // Else, delegate error handling
354
+ else {
355
+ this._errorHandler.handle(err);
356
+ }
357
+ } finally {
358
+ // Incrementing the number of retries done
359
+ retry++;
360
+ }
361
+ } while (retry < this.config.maxRetries);
362
+
363
+ /** If request not successful even after retries, throw the error */
364
+ throw error;
365
+ }
366
+ }
@@ -0,0 +1,241 @@
1
+ import { Extractors } from '../../collections/Extractors';
2
+ import { ResourceType } from '../../enums/Resource';
3
+ import { CursoredData } from '../../models/data/CursoredData';
4
+ import { List } from '../../models/data/List';
5
+ import { Tweet } from '../../models/data/Tweet';
6
+ import { User } from '../../models/data/User';
7
+ import { RettiwtConfig } from '../../models/RettiwtConfig';
8
+ import { IListMemberAddResponse } from '../../types/raw/list/AddMember';
9
+ import { IListDetailsResponse } from '../../types/raw/list/Details';
10
+ import { IListMembersResponse } from '../../types/raw/list/Members';
11
+ import { IListMemberRemoveResponse } from '../../types/raw/list/RemoveMember';
12
+ import { IListTweetsResponse } from '../../types/raw/list/Tweets';
13
+
14
+ import { FetcherService } from './FetcherService';
15
+
16
+ export class ListService extends FetcherService {
17
+ /**
18
+ * @param config - The config object for configuring the Rettiwt instance.
19
+ *
20
+ * @internal
21
+ */
22
+ public constructor(config: RettiwtConfig) {
23
+ super(config);
24
+ }
25
+
26
+ /**
27
+ * Add a user as a member of a list.
28
+ *
29
+ * @param listId - The ID of the target list.
30
+ * @param userId - The ID of the target user to be added as a member.
31
+ *
32
+ * @returns The new member count of the list. If adding was unsuccessful, return `undefined`.
33
+ *
34
+ * @example
35
+ *
36
+ * ```ts
37
+ * import { Rettiwt } from 'rettiwt-api';
38
+ *
39
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
40
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
41
+ *
42
+ * // Adding a user with ID '123456789' as a member to the list with ID '987654321'
43
+ * rettiwt.list.addMember('987654321', '123456789')
44
+ * .then(res => {
45
+ * console.log(res);
46
+ * })
47
+ * .catch(err => {
48
+ * console.log(err);
49
+ * });
50
+ * ```
51
+ */
52
+ public async addMember(listId: string, userId: string): Promise<number | undefined> {
53
+ const resource: ResourceType = ResourceType.LIST_MEMBER_ADD;
54
+
55
+ // Adding the user as a member
56
+ const response = await this.request<IListMemberAddResponse>(resource, {
57
+ id: listId,
58
+ userId: userId,
59
+ });
60
+
61
+ // Deserializing response
62
+ const data = Extractors[resource](response);
63
+
64
+ return data;
65
+ }
66
+
67
+ /**
68
+ * Get the details of a list.
69
+ *
70
+ * @param id - The ID of the target list.
71
+ *
72
+ * @returns
73
+ * The details of the target list.
74
+ *
75
+ * If list not found, returns undefined.
76
+ *
77
+ * @example
78
+ *
79
+ * #### Fetching the details of a list
80
+ * ```ts
81
+ * import { Rettiwt } from 'rettiwt-api';
82
+ *
83
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
84
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
85
+ *
86
+ * // Fetching the details of the list with the id '1234567890'
87
+ * rettiwt.list.details('1234567890')
88
+ * .then(res => {
89
+ * console.log(res);
90
+ * })
91
+ * .catch(err => {
92
+ * console.log(err);
93
+ * });
94
+ * ```
95
+ */
96
+ public async details(id: string): Promise<List | undefined> {
97
+ const resource: ResourceType = ResourceType.LIST_DETAILS;
98
+
99
+ // Getting the details of the list
100
+ const response = await this.request<IListDetailsResponse>(resource, { id: id });
101
+
102
+ // Deserializing response
103
+ const data = Extractors[resource](response, id);
104
+
105
+ return data;
106
+ }
107
+
108
+ /**
109
+ * Get the list of members of a tweet list.
110
+ *
111
+ * @param id - The ID of target list.
112
+ * @param count - The number of members to fetch, must be \<= 100.
113
+ * @param cursor - The cursor to the batch of members to fetch.
114
+ *
115
+ * @returns The list tweets in the given list.
116
+ *
117
+ * @example
118
+ *
119
+ * ```ts
120
+ * import { Rettiwt } from 'rettiwt-api';
121
+ *
122
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
123
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
124
+ *
125
+ * // Fetching the first 100 members of the Twitter list with id '1234567890'
126
+ * rettiwt.list.members('1234567890')
127
+ * .then(res => {
128
+ * console.log(res);
129
+ * })
130
+ * .catch(err => {
131
+ * console.log(err);
132
+ * });
133
+ * ```
134
+ *
135
+ * @remarks Due a bug in Twitter API, the count is ignored when no cursor is provided and defaults to 100.
136
+ */
137
+ public async members(id: string, count?: number, cursor?: string): Promise<CursoredData<User>> {
138
+ const resource: ResourceType = ResourceType.LIST_MEMBERS;
139
+
140
+ // Fetching the raw list of members
141
+ const response = await this.request<IListMembersResponse>(resource, {
142
+ id: id,
143
+ count: count,
144
+ cursor: cursor,
145
+ });
146
+
147
+ // Deserializing response
148
+ const data = Extractors[resource](response);
149
+
150
+ return data;
151
+ }
152
+
153
+ /**
154
+ * Remove a member from a list.
155
+ *
156
+ * @param listId - The ID of the target list.
157
+ * @param userId - The ID of the target user to removed from the members.
158
+ *
159
+ * @returns The new member count of the list. If removal was unsuccessful, return `undefined`.
160
+ *
161
+ * @example
162
+ *
163
+ * ```ts
164
+ * import { Rettiwt } from 'rettiwt-api';
165
+ *
166
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
167
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
168
+ *
169
+ * // Removing a user with ID '123456789' from the member of the list with ID '987654321'
170
+ * rettiwt.list.removeMember('987654321', '123456789')
171
+ * .then(res => {
172
+ * console.log(res);
173
+ * })
174
+ * .catch(err => {
175
+ * console.log(err);
176
+ * });
177
+ * ```
178
+ */
179
+ public async removeMember(listId: string, userId: string): Promise<number | undefined> {
180
+ const resource: ResourceType = ResourceType.LIST_MEMBER_REMOVE;
181
+
182
+ // Removing the member
183
+ const response = await this.request<IListMemberRemoveResponse>(resource, {
184
+ id: listId,
185
+ userId: userId,
186
+ });
187
+
188
+ // Deserializing response
189
+ const data = Extractors[resource](response);
190
+
191
+ return data;
192
+ }
193
+
194
+ /**
195
+ * Get the list of tweets from a tweet list.
196
+ *
197
+ * @param id - The ID of target list.
198
+ * @param count - The number of tweets to fetch, must be \<= 100.
199
+ * @param cursor - The cursor to the batch of tweets to fetch.
200
+ *
201
+ * @returns The list tweets in the given list.
202
+ *
203
+ * @example
204
+ *
205
+ * ```ts
206
+ * import { Rettiwt } from 'rettiwt-api';
207
+ *
208
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
209
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
210
+ *
211
+ * // Fetching the most recent 100 tweets of the Twitter list with id '1234567890'
212
+ * rettiwt.list.tweets('1234567890')
213
+ * .then(res => {
214
+ * console.log(res);
215
+ * })
216
+ * .catch(err => {
217
+ * console.log(err);
218
+ * });
219
+ * ```
220
+ *
221
+ * @remarks Due a bug in Twitter API, the count is ignored when no cursor is provided and defaults to 100.
222
+ */
223
+ public async tweets(id: string, count?: number, cursor?: string): Promise<CursoredData<Tweet>> {
224
+ const resource = ResourceType.LIST_TWEETS;
225
+
226
+ // Fetching raw list tweets
227
+ const response = await this.request<IListTweetsResponse>(resource, {
228
+ id: id,
229
+ count: count,
230
+ cursor: cursor,
231
+ });
232
+
233
+ // Deserializing response
234
+ const data = Extractors[resource](response);
235
+
236
+ // Sorting the tweets by date, from recent to oldest
237
+ data.list.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
238
+
239
+ return data;
240
+ }
241
+ }