rettiwt-api 2.7.1 → 3.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 (139) hide show
  1. package/.eslintrc.js +73 -5
  2. package/.tool-versions +1 -0
  3. package/README.md +87 -20
  4. package/dist/Rettiwt.js +0 -1
  5. package/dist/Rettiwt.js.map +1 -1
  6. package/dist/cli.js +2 -4
  7. package/dist/cli.js.map +1 -1
  8. package/dist/collections/Extractors.d.ts +10 -0
  9. package/dist/collections/Extractors.js +43 -0
  10. package/dist/collections/Extractors.js.map +1 -0
  11. package/dist/collections/Groups.d.ts +19 -0
  12. package/dist/collections/Groups.js +55 -0
  13. package/dist/collections/Groups.js.map +1 -0
  14. package/dist/collections/Requests.d.ts +12 -0
  15. package/dist/collections/Requests.js +46 -0
  16. package/dist/collections/Requests.js.map +1 -0
  17. package/dist/commands/Auth.d.ts +6 -0
  18. package/dist/commands/Auth.js +26 -8
  19. package/dist/commands/Auth.js.map +1 -1
  20. package/dist/commands/Tweet.js +237 -82
  21. package/dist/commands/Tweet.js.map +1 -1
  22. package/dist/commands/User.js +197 -36
  23. package/dist/commands/User.js.map +1 -1
  24. package/dist/enums/Api.d.ts +30 -0
  25. package/dist/enums/Api.js +32 -1
  26. package/dist/enums/Api.js.map +1 -1
  27. package/dist/enums/Data.d.ts +9 -0
  28. package/dist/enums/Data.js +14 -0
  29. package/dist/enums/Data.js.map +1 -0
  30. package/dist/enums/Http.d.ts +1 -1
  31. package/dist/enums/Http.js +1 -1
  32. package/dist/enums/Logging.d.ts +6 -5
  33. package/dist/enums/Logging.js +6 -5
  34. package/dist/enums/Logging.js.map +1 -1
  35. package/dist/enums/Resource.d.ts +33 -0
  36. package/dist/enums/Resource.js +42 -0
  37. package/dist/enums/Resource.js.map +1 -0
  38. package/dist/helper/CliUtils.d.ts +1 -1
  39. package/dist/helper/CliUtils.js.map +1 -1
  40. package/dist/index.d.ts +11 -9
  41. package/dist/index.js +11 -14
  42. package/dist/index.js.map +1 -1
  43. package/dist/models/args/FetchArgs.d.ts +129 -0
  44. package/dist/models/args/FetchArgs.js +263 -0
  45. package/dist/models/args/FetchArgs.js.map +1 -0
  46. package/dist/models/args/PostArgs.d.ts +116 -0
  47. package/dist/models/args/PostArgs.js +232 -0
  48. package/dist/models/args/PostArgs.js.map +1 -0
  49. package/dist/models/data/CursoredData.d.ts +11 -11
  50. package/dist/models/data/CursoredData.js +21 -16
  51. package/dist/models/data/CursoredData.js.map +1 -1
  52. package/dist/models/data/List.d.ts +8 -10
  53. package/dist/models/data/List.js +2 -4
  54. package/dist/models/data/List.js.map +1 -1
  55. package/dist/models/data/Tweet.d.ts +41 -29
  56. package/dist/models/data/Tweet.js +71 -15
  57. package/dist/models/data/Tweet.js.map +1 -1
  58. package/dist/models/data/User.d.ts +36 -20
  59. package/dist/models/data/User.js +69 -7
  60. package/dist/models/data/User.js.map +1 -1
  61. package/dist/models/errors/ApiError.d.ts +1 -3
  62. package/dist/models/errors/ApiError.js +1 -4
  63. package/dist/models/errors/ApiError.js.map +1 -1
  64. package/dist/models/errors/DataValidationError.d.ts +30 -0
  65. package/dist/models/errors/DataValidationError.js +34 -0
  66. package/dist/models/errors/DataValidationError.js.map +1 -0
  67. package/dist/models/errors/HttpError.d.ts +1 -3
  68. package/dist/models/errors/HttpError.js +1 -4
  69. package/dist/models/errors/HttpError.js.map +1 -1
  70. package/dist/models/errors/TimeoutError.d.ts +2 -4
  71. package/dist/models/errors/TimeoutError.js +2 -5
  72. package/dist/models/errors/TimeoutError.js.map +1 -1
  73. package/dist/services/internal/ErrorService.d.ts +45 -35
  74. package/dist/services/internal/ErrorService.js +70 -68
  75. package/dist/services/internal/ErrorService.js.map +1 -1
  76. package/dist/services/internal/LogService.d.ts +7 -5
  77. package/dist/services/internal/LogService.js +28 -9
  78. package/dist/services/internal/LogService.js.map +1 -1
  79. package/dist/services/public/AuthService.d.ts +24 -20
  80. package/dist/services/public/AuthService.js +38 -36
  81. package/dist/services/public/AuthService.js.map +1 -1
  82. package/dist/services/public/FetcherService.d.ts +99 -0
  83. package/dist/services/public/FetcherService.js +254 -0
  84. package/dist/services/public/FetcherService.js.map +1 -0
  85. package/dist/services/public/TweetService.d.ts +213 -94
  86. package/dist/services/public/TweetService.js +405 -208
  87. package/dist/services/public/TweetService.js.map +1 -1
  88. package/dist/services/public/UserService.d.ts +185 -52
  89. package/dist/services/public/UserService.js +333 -103
  90. package/dist/services/public/UserService.js.map +1 -1
  91. package/dist/types/ReturnTypes.d.ts +21 -0
  92. package/dist/types/ReturnTypes.js +3 -0
  93. package/dist/types/ReturnTypes.js.map +1 -0
  94. package/package.json +4 -2
  95. package/src/Rettiwt.ts +0 -3
  96. package/src/cli.ts +2 -4
  97. package/src/collections/Extractors.ts +63 -0
  98. package/src/collections/Groups.ts +54 -0
  99. package/src/collections/Requests.ts +52 -0
  100. package/src/commands/Auth.ts +19 -7
  101. package/src/commands/Tweet.ts +179 -91
  102. package/src/commands/User.ts +118 -25
  103. package/src/enums/Api.ts +31 -0
  104. package/src/enums/Data.ts +9 -0
  105. package/src/enums/Http.ts +1 -1
  106. package/src/enums/Logging.ts +6 -5
  107. package/src/enums/Resource.ts +40 -0
  108. package/src/helper/CliUtils.ts +1 -1
  109. package/src/index.ts +41 -14
  110. package/src/models/args/FetchArgs.ts +296 -0
  111. package/src/models/args/PostArgs.ts +263 -0
  112. package/src/models/data/CursoredData.ts +23 -15
  113. package/src/models/data/List.ts +12 -15
  114. package/src/models/data/Tweet.ts +105 -39
  115. package/src/models/data/User.ts +97 -30
  116. package/src/models/errors/ApiError.ts +1 -4
  117. package/src/models/errors/DataValidationError.ts +44 -0
  118. package/src/models/errors/HttpError.ts +1 -4
  119. package/src/models/errors/TimeoutError.ts +2 -5
  120. package/src/services/internal/ErrorService.ts +76 -75
  121. package/src/services/internal/LogService.ts +20 -10
  122. package/src/services/public/AuthService.ts +39 -38
  123. package/src/services/public/FetcherService.ts +230 -0
  124. package/src/services/public/TweetService.ts +381 -179
  125. package/src/services/public/UserService.ts +314 -86
  126. package/src/types/RettiwtConfig.ts +0 -1
  127. package/src/types/ReturnTypes.ts +24 -0
  128. package/dist/models/args/TweetArgs.d.ts +0 -44
  129. package/dist/models/args/TweetArgs.js +0 -82
  130. package/dist/models/args/TweetArgs.js.map +0 -1
  131. package/dist/models/data/Media.d.ts +0 -14
  132. package/dist/models/data/Media.js +0 -19
  133. package/dist/models/data/Media.js.map +0 -1
  134. package/dist/services/internal/FetcherService.d.ts +0 -106
  135. package/dist/services/internal/FetcherService.js +0 -365
  136. package/dist/services/internal/FetcherService.js.map +0 -1
  137. package/src/models/args/TweetArgs.ts +0 -98
  138. package/src/models/data/Media.ts +0 -19
  139. package/src/services/internal/FetcherService.ts +0 -365
@@ -1,20 +1,32 @@
1
- // PACKAGES
2
- import { EResourceType, MediaArgs, TweetFilter } from 'rettiwt-core';
3
-
4
- // SERVICES
5
- import { FetcherService } from '../internal/FetcherService';
6
-
7
- // TYPES
8
- import { IRettiwtConfig } from '../../types/RettiwtConfig';
9
-
10
- // MODELS
1
+ import { statSync } from 'fs';
2
+
3
+ import {
4
+ IInitializeMediaUploadResponse,
5
+ IListTweetsResponse,
6
+ ITweetDetailsResponse,
7
+ ITweetLikeResponse,
8
+ ITweetLikersResponse,
9
+ ITweetPostResponse,
10
+ ITweetRetweetersResponse,
11
+ ITweetRetweetResponse,
12
+ ITweetSearchResponse,
13
+ ITweetUnlikeResponse,
14
+ ITweetUnpostResponse,
15
+ ITweetUnretweetResponse,
16
+ TweetFilter,
17
+ } from 'rettiwt-core';
18
+
19
+ import { EResourceType } from '../../enums/Resource';
20
+ import { TweetArgs } from '../../models/args/PostArgs';
21
+ import { CursoredData } from '../../models/data/CursoredData';
11
22
  import { Tweet } from '../../models/data/Tweet';
12
23
  import { User } from '../../models/data/User';
13
- import { CursoredData } from '../../models/data/CursoredData';
14
- import { TweetArgs, TweetMediaArgs } from '../../models/args/TweetArgs';
24
+ import { IRettiwtConfig } from '../../types/RettiwtConfig';
25
+
26
+ import { FetcherService } from './FetcherService';
15
27
 
16
28
  /**
17
- * Handles fetching of data related to tweets.
29
+ * Handles interacting with resources related to tweets.
18
30
  *
19
31
  * @public
20
32
  */
@@ -32,7 +44,10 @@ export class TweetService extends FetcherService {
32
44
  * Get the details of a tweet.
33
45
  *
34
46
  * @param id - The id of the target tweet.
35
- * @returns The details of a single tweet with the given tweet id.
47
+ *
48
+ * @returns
49
+ * The details of the tweet with the given id.
50
+ * If no tweet matches the given id, returns `undefined`.
36
51
  *
37
52
  * @example
38
53
  * ```
@@ -41,8 +56,8 @@ export class TweetService extends FetcherService {
41
56
  * // Creating a new Rettiwt instance using the given 'API_KEY'
42
57
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
43
58
  *
44
- * // Fetching the details of the tweet with the id '12345678'
45
- * rettiwt.tweet.details('12345678')
59
+ * // Fetching the details of the tweet with the id '1234567890'
60
+ * rettiwt.tweet.details('1234567890')
46
61
  * .then(res => {
47
62
  * console.log(res);
48
63
  * })
@@ -50,23 +65,25 @@ export class TweetService extends FetcherService {
50
65
  * console.log(err);
51
66
  * });
52
67
  * ```
53
- *
54
- * @public
55
68
  */
56
- public async details(id: string): Promise<Tweet> {
57
- // Fetching the requested data
58
- const data = await this.fetch<Tweet>(EResourceType.TWEET_DETAILS, { id: id });
69
+ public async details(id: string): Promise<Tweet | undefined> {
70
+ const resource = EResourceType.TWEET_DETAILS;
59
71
 
60
- return data.list[0];
72
+ // Fetching raw tweet details
73
+ const response = await this.request<ITweetDetailsResponse>(resource, { id: id });
74
+
75
+ // Deserializing response
76
+ const data = this.extract<Tweet>(response, resource);
77
+
78
+ return data;
61
79
  }
62
80
 
63
81
  /**
64
- * Search for tweets using a query.
82
+ * Like a tweet.
65
83
  *
66
- * @param query - The query be used for searching the tweets.
67
- * @param count - The number of tweets to fetch, must be \<= 20.
68
- * @param cursor - The cursor to the batch of tweets to fetch.
69
- * @returns The list of tweets that match the given filter.
84
+ * @param id - The id of the tweet to be liked.
85
+ *
86
+ * @returns Whether liking was successful or not.
70
87
  *
71
88
  * @example
72
89
  * ```
@@ -75,8 +92,8 @@ export class TweetService extends FetcherService {
75
92
  * // Creating a new Rettiwt instance using the given 'API_KEY'
76
93
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
77
94
  *
78
- * // Fetching the most recent 5 tweets from user 'user1'
79
- * rettiwt.tweet.search({ fromUsers: ['user1'] }, 5)
95
+ * // Liking the Tweet with id '1234567890'
96
+ * rettiwt.tweet.like('1234567890')
80
97
  * .then(res => {
81
98
  * console.log(res);
82
99
  * })
@@ -84,31 +101,27 @@ export class TweetService extends FetcherService {
84
101
  * console.log(err);
85
102
  * });
86
103
  * ```
87
- *
88
- * @remarks For details about available filters, refer to {@link TweetFilter}
89
- *
90
- * @public
91
104
  */
92
- public async search(query: TweetFilter, count?: number, cursor?: string): Promise<CursoredData<Tweet>> {
93
- // Fetching the requested data
94
- const data = await this.fetch<Tweet>(EResourceType.TWEET_SEARCH, {
95
- filter: query,
96
- count: count,
97
- cursor: cursor,
105
+ public async like(id: string): Promise<boolean> {
106
+ // Favoriting the tweet
107
+ const response = await this.request<ITweetLikeResponse>(EResourceType.TWEET_LIKE, {
108
+ id: id,
98
109
  });
99
110
 
100
- // Sorting the tweets by date, from recent to oldest
101
- data.list.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
111
+ // Deserializing response
112
+ const data = this.extract<boolean>(response, EResourceType.TWEET_LIKE) ?? false;
102
113
 
103
114
  return data;
104
115
  }
105
116
 
106
117
  /**
107
- * Stream tweets in pseudo real-time using a filter.
118
+ * Get the list of users who liked a tweet.
108
119
  *
109
- * @param filter - The filter to be used for searching the tweets.
110
- * @param pollingInterval - The interval in milliseconds to poll for new tweets. Default interval is 60000 ms.
111
- * @returns An async generator that yields matching tweets as they are found.
120
+ * @param id - The id of the target tweet.
121
+ * @param count - The number of likers to fetch, must be \<= 100.
122
+ * @param cursor - The cursor to the batch of likers to fetch.
123
+ *
124
+ * @returns The list of users who liked the given tweet.
112
125
  *
113
126
  * @example
114
127
  * ```
@@ -117,64 +130,40 @@ export class TweetService extends FetcherService {
117
130
  * // Creating a new Rettiwt instance using the given 'API_KEY'
118
131
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
119
132
  *
120
- * // Streaming all upcoming tweets from user 'user1'
121
- * (async () => {
122
- * try {
123
- * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] }, 1000)) {
124
- * console.log(tweet.fullText);
125
- * }
126
- * }
127
- * catch (err) {
128
- * console.log(err);
129
- * }
130
- * })();
133
+ * // Fetching the most recent 100 likers of the Tweet with id '1234567890'
134
+ * rettiwt.tweet.likers('1234567890')
135
+ * .then(res => {
136
+ * console.log(res);
137
+ * })
138
+ * .catch(err => {
139
+ * console.log(err);
140
+ * });
131
141
  * ```
132
- *
133
- * @public
134
142
  */
135
- public async *stream(filter: TweetFilter, pollingInterval: number = 60000): AsyncGenerator<Tweet> {
136
- const startDate = new Date();
137
-
138
- let cursor: string | undefined = undefined;
139
- let sinceId: string | undefined = undefined;
140
- let nextSinceId: string | undefined = undefined;
141
-
142
- while (true) {
143
- // Pause execution for the specified polling interval before proceeding to the next iteration
144
- await new Promise((resolve) => setTimeout(resolve, pollingInterval));
143
+ public async likers(id: string, count?: number, cursor?: string): Promise<CursoredData<User>> {
144
+ const resource = EResourceType.TWEET_LIKERS;
145
145
 
146
- // Search for tweets
147
- const tweets = await this.search({ ...filter, startDate: startDate, sinceId: sinceId }, undefined, cursor);
148
-
149
- // Yield the matching tweets
150
- for (const tweet of tweets.list) {
151
- yield tweet;
152
- }
146
+ // Fetching raw likers
147
+ const response = await this.request<ITweetLikersResponse>(resource, {
148
+ id: id,
149
+ count: count,
150
+ cursor: cursor,
151
+ });
153
152
 
154
- // Store the most recent tweet ID from this batch
155
- if (tweets.list.length > 0 && cursor === undefined) {
156
- nextSinceId = tweets.list[0].id;
157
- }
153
+ // Deserializing response
154
+ const data = this.extract<CursoredData<User>>(response, resource)!;
158
155
 
159
- // If there are more tweets to fetch, adjust the cursor value
160
- if (tweets.list.length > 0 && tweets.next) {
161
- cursor = tweets.next.value;
162
- }
163
- // Else, start the next iteration from this batch's most recent tweet
164
- else {
165
- sinceId = nextSinceId;
166
- cursor = undefined;
167
- }
168
- }
156
+ return data;
169
157
  }
170
158
 
171
159
  /**
172
- * Get the tweets from the tweet list with the given id.
160
+ * Get the list of tweets from a tweet list.
173
161
  *
174
- * @param listId - The id of list from where the tweets are to be fetched.
162
+ * @param id - The id of target list.
175
163
  * @param count - The number of tweets to fetch, must be \<= 100.
176
164
  * @param cursor - The cursor to the batch of tweets to fetch.
177
- * @returns The list tweets present in the given list.
165
+ *
166
+ * @returns The list tweets in the given list.
178
167
  *
179
168
  * @example
180
169
  * ```
@@ -183,8 +172,8 @@ export class TweetService extends FetcherService {
183
172
  * // Creating a new Rettiwt instance using the given 'API_KEY'
184
173
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
185
174
  *
186
- * // Fetching the most recent 100 tweets of the Twitter list with id '12345678'
187
- * rettiwt.tweet.list('12345678')
175
+ * // Fetching the most recent 100 tweets of the Twitter list with id '1234567890'
176
+ * rettiwt.tweet.list('1234567890')
188
177
  * .then(res => {
189
178
  * console.log(res);
190
179
  * })
@@ -195,14 +184,19 @@ export class TweetService extends FetcherService {
195
184
  *
196
185
  * @remarks Due a bug in Twitter API, the count is ignored when no cursor is provided and defaults to 100.
197
186
  */
198
- public async list(listId: string, count?: number, cursor?: string): Promise<CursoredData<Tweet>> {
199
- // Fetching the requested data
200
- const data = await this.fetch<Tweet>(EResourceType.LIST_TWEETS, {
201
- id: listId,
187
+ public async list(id: string, count?: number, cursor?: string): Promise<CursoredData<Tweet>> {
188
+ const resource = EResourceType.LIST_TWEETS;
189
+
190
+ // Fetching raw list tweets
191
+ const response = await this.request<IListTweetsResponse>(resource, {
192
+ id: id,
202
193
  count: count,
203
194
  cursor: cursor,
204
195
  });
205
196
 
197
+ // Deserializing response
198
+ const data = this.extract<CursoredData<Tweet>>(response, resource)!;
199
+
206
200
  // Sorting the tweets by date, from recent to oldest
207
201
  data.list.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
208
202
 
@@ -210,22 +204,22 @@ export class TweetService extends FetcherService {
210
204
  }
211
205
 
212
206
  /**
213
- * Get the list of users who liked a tweet.
207
+ * Post a tweet.
214
208
  *
215
- * @param tweetId - The rest id of the target tweet.
216
- * @param count - The number of favoriters to fetch, must be \<= 100.
217
- * @param cursor - The cursor to the batch of favoriters to fetch.
218
- * @returns The list of users who liked the given tweet.
209
+ * @param options - The options describing the tweet to be posted. Check {@link TweetArgs} for available options.
210
+ *
211
+ * @returns Whether posting was successful or not.
219
212
  *
220
213
  * @example
214
+ * Posting a simple text
221
215
  * ```
222
216
  * import { Rettiwt } from 'rettiwt-api';
223
217
  *
224
218
  * // Creating a new Rettiwt instance using the given 'API_KEY'
225
219
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
226
220
  *
227
- * // Fetching the most recent 100 likers of the Tweet with id '12345678'
228
- * rettiwt.tweet.favoriters('12345678')
221
+ * // Posting a tweet to twitter
222
+ * rettiwt.tweet.post({ text: 'Hello World!' })
229
223
  * .then(res => {
230
224
  * console.log(res);
231
225
  * })
@@ -234,15 +228,104 @@ export class TweetService extends FetcherService {
234
228
  * });
235
229
  * ```
236
230
  *
237
- * @public
231
+ * @example
232
+ * Posting a tweet with an image that has been already uploaded
233
+ * ```
234
+ * import { Rettiwt } from 'rettiwt-api';
235
+ *
236
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
237
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
238
+ *
239
+ * // Posting a tweet, containing an image called 'mountains.jpg', to twitter
240
+ * rettiwt.tweet.post({ text: 'What a nice view!', media: [{ id: '1234567890' }] })
241
+ * .then(res => {
242
+ * console.log(res);
243
+ * })
244
+ * .catch(err => {
245
+ * console.log(err);
246
+ * });
247
+ * ```
248
+ *
249
+ * @example
250
+ * Posting a reply to a tweet
251
+ * ```
252
+ * import { Rettiwt } from 'rettiwt-api';
253
+ *
254
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
255
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
256
+ *
257
+ * // Posting a simple text reply, to a tweet with id "1234567890"
258
+ * rettiwt.tweet.post({ text: 'Hello!', replyTo: "1234567890" })
259
+ * .then(res => {
260
+ * console.log(res);
261
+ * })
262
+ * .catch(err => {
263
+ * console.log(err);
264
+ * });
265
+ * ```
266
+ *
267
+ * * @example
268
+ * Posting a tweet that quotes another tweet
269
+ * ```
270
+ * import { Rettiwt } from 'rettiwt-api';
271
+ *
272
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
273
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
274
+ *
275
+ * // Posting a simple text tweet, quoting a tweet with id "1234567890"
276
+ * rettiwt.tweet.post({ text: 'Hello!', quote: "1234567890" })
277
+ * .then(res => {
278
+ * console.log(res);
279
+ * })
280
+ * .catch(err => {
281
+ * console.log(err);
282
+ * });
283
+ * ```
238
284
  */
239
- public async favoriters(tweetId: string, count?: number, cursor?: string): Promise<CursoredData<User>> {
240
- // Fetching the requested data
241
- const data = await this.fetch<User>(EResourceType.TWEET_FAVORITERS, {
242
- id: tweetId,
243
- count: count,
244
- cursor: cursor,
245
- });
285
+ public async post(options: TweetArgs): Promise<string | undefined> {
286
+ const resource = EResourceType.TWEET_POST;
287
+
288
+ // Posting the tweet
289
+ const response = await this.request<ITweetPostResponse>(resource, { tweet: options });
290
+
291
+ // Deserializing response
292
+ const data = this.extract<string>(response, resource);
293
+
294
+ return data;
295
+ }
296
+
297
+ /**
298
+ * Retweet a tweet.
299
+ *
300
+ * @param id - The id of the target tweet.
301
+ *
302
+ * @returns Whether retweeting was successful or not.
303
+ *
304
+ * @example
305
+ * ```
306
+ * import { Rettiwt } from 'rettiwt-api';
307
+ *
308
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
309
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
310
+ *
311
+ * // Retweeting the Tweet with id '1234567890'
312
+ * rettiwt.tweet.retweet('1234567890')
313
+ * .then(res => {
314
+ * console.log(res);
315
+ * })
316
+ * .catch(err => {
317
+ * console.log(err);
318
+ * });
319
+ * ```
320
+ */
321
+ public async retweet(id: string): Promise<boolean> {
322
+ const resource = EResourceType.TWEET_RETWEET;
323
+
324
+ // Retweeting the tweet
325
+ const response = await this.request<ITweetRetweetResponse>(resource, { id: id });
326
+
327
+ // Deserializing response
328
+ const data = this.extract<boolean>(response, resource) ?? false;
246
329
 
247
330
  return data;
248
331
  }
@@ -250,9 +333,10 @@ export class TweetService extends FetcherService {
250
333
  /**
251
334
  * Get the list of users who retweeted a tweet.
252
335
  *
253
- * @param tweetId - The rest id of the target tweet.
336
+ * @param id - The id of the target tweet.
254
337
  * @param count - The number of retweeters to fetch, must be \<= 100.
255
338
  * @param cursor - The cursor to the batch of retweeters to fetch.
339
+ *
256
340
  * @returns The list of users who retweeted the given tweet.
257
341
  *
258
342
  * @example
@@ -262,8 +346,8 @@ export class TweetService extends FetcherService {
262
346
  * // Creating a new Rettiwt instance using the given 'API_KEY'
263
347
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
264
348
  *
265
- * // Fetching the most recent 100 retweeters of the Tweet with id '12345678'
266
- * rettiwt.tweet.retweeters('12345678')
349
+ * // Fetching the most recent 100 retweeters of the Tweet with id '1234567890'
350
+ * rettiwt.tweet.retweeters('1234567890')
267
351
  * .then(res => {
268
352
  * console.log(res);
269
353
  * })
@@ -271,37 +355,41 @@ export class TweetService extends FetcherService {
271
355
  * console.log(err);
272
356
  * });
273
357
  * ```
274
- *
275
- * @public
276
358
  */
277
- public async retweeters(tweetId: string, count?: number, cursor?: string): Promise<CursoredData<User>> {
278
- // Fetching the requested data
279
- const data = await this.fetch<User>(EResourceType.TWEET_RETWEETERS, {
280
- id: tweetId,
359
+ public async retweeters(id: string, count?: number, cursor?: string): Promise<CursoredData<User>> {
360
+ const resource = EResourceType.TWEET_RETWEETERS;
361
+
362
+ // Fetching raw list of retweeters
363
+ const response = await this.request<ITweetRetweetersResponse>(resource, {
364
+ id: id,
281
365
  count: count,
282
366
  cursor: cursor,
283
367
  });
284
368
 
369
+ // Deserializing response
370
+ const data = this.extract<CursoredData<User>>(response, resource)!;
371
+
285
372
  return data;
286
373
  }
287
374
 
288
375
  /**
289
- * Post a tweet.
376
+ * Search for tweets using a filter.
290
377
  *
291
- * @param text - The text to be posted, length must be \<= 280 characters.
292
- * @param media - The list of media to post in the tweet, max number of media must be \<= 4.
293
- * @param replyTo - The id of the tweet to which the reply is to be made.
294
- * @returns Whether posting was successful or not.
378
+ * @param filter - The filter to be used for searching the tweets.
379
+ * @param count - The number of tweets to fetch, must be \<= 20.
380
+ * @param cursor - The cursor to the batch of tweets to fetch.
295
381
  *
296
- * @example Posting a simple text
382
+ * @returns The list of tweets that match the given filter.
383
+ *
384
+ * @example
297
385
  * ```
298
386
  * import { Rettiwt } from 'rettiwt-api';
299
387
  *
300
388
  * // Creating a new Rettiwt instance using the given 'API_KEY'
301
389
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
302
390
  *
303
- * // Posting a tweet to twitter
304
- * rettiwt.tweet.tweet('Hello World!')
391
+ * // Fetching the most recent 5 tweets from user 'user1'
392
+ * rettiwt.tweet.search({ fromUsers: ['user1'] }, 5)
305
393
  * .then(res => {
306
394
  * console.log(res);
307
395
  * })
@@ -310,15 +398,107 @@ export class TweetService extends FetcherService {
310
398
  * });
311
399
  * ```
312
400
  *
313
- * @example Posting a tweet with an image
401
+ * @remarks For details about available filters, refer to {@link TweetFilter}
402
+ */
403
+ public async search(filter: TweetFilter, count?: number, cursor?: string): Promise<CursoredData<Tweet>> {
404
+ const resource = EResourceType.TWEET_SEARCH;
405
+
406
+ // Fetching raw list of filtered tweets
407
+ const response = await this.request<ITweetSearchResponse>(resource, {
408
+ filter: filter,
409
+ count: count,
410
+ cursor: cursor,
411
+ });
412
+
413
+ // Deserializing response
414
+ const data = this.extract<CursoredData<Tweet>>(response, resource)!;
415
+
416
+ // Sorting the tweets by date, from recent to oldest
417
+ data.list.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
418
+
419
+ return data;
420
+ }
421
+
422
+ /**
423
+ * Stream tweets in pseudo real-time using a filter.
424
+ *
425
+ * @param filter - The filter to be used for searching the tweets.
426
+ * @param pollingInterval - The interval in milliseconds to poll for new tweets. Default interval is 60000 ms.
427
+ *
428
+ * @returns An async generator that yields matching tweets as they are found.
429
+ *
430
+ * @example
314
431
  * ```
315
432
  * import { Rettiwt } from 'rettiwt-api';
316
433
  *
317
434
  * // Creating a new Rettiwt instance using the given 'API_KEY'
318
435
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
319
436
  *
320
- * // Posting a tweet, containing an image called 'mountains.jpg', to twitter
321
- * rettiwt.tweet.tweet('What a nice view!', [{ path: 'mountains.jpg' }])
437
+ * // Streaming all upcoming tweets from user 'user1'
438
+ * (async () => {
439
+ * try {
440
+ * for await (const tweet of rettiwt.tweet.stream({ fromUsers: ['user1'] }, 1000)) {
441
+ * console.log(tweet.fullText);
442
+ * }
443
+ * }
444
+ * catch (err) {
445
+ * console.log(err);
446
+ * }
447
+ * })();
448
+ * ```
449
+ */
450
+ public async *stream(filter: TweetFilter, pollingInterval: number = 60000): AsyncGenerator<Tweet> {
451
+ const startDate = new Date();
452
+
453
+ let cursor: string | undefined = undefined;
454
+ let sinceId: string | undefined = undefined;
455
+ let nextSinceId: string | undefined = undefined;
456
+
457
+ while (true) {
458
+ // Pause execution for the specified polling interval before proceeding to the next iteration
459
+ await new Promise((resolve) => setTimeout(resolve, pollingInterval));
460
+
461
+ // Search for tweets
462
+ const tweets = await this.search({ ...filter, startDate: startDate, sinceId: sinceId }, undefined, cursor);
463
+
464
+ // Yield the matching tweets
465
+ for (const tweet of tweets.list) {
466
+ yield tweet;
467
+ }
468
+
469
+ // Store the most recent tweet ID from this batch
470
+ if (tweets.list.length > 0 && cursor === undefined) {
471
+ nextSinceId = tweets.list[0].id;
472
+ }
473
+
474
+ // If there are more tweets to fetch, adjust the cursor value
475
+ if (tweets.list.length > 0 && tweets.next) {
476
+ cursor = tweets.next.value;
477
+ }
478
+ // Else, start the next iteration from this batch's most recent tweet
479
+ else {
480
+ sinceId = nextSinceId;
481
+ cursor = undefined;
482
+ }
483
+ }
484
+ }
485
+
486
+ /**
487
+ * Unlike a tweet.
488
+ *
489
+ * @param id - The id of the target tweet.
490
+ *
491
+ * @returns Whether unliking was successful or not.
492
+ *
493
+ * @example
494
+ * ```
495
+ * import { Rettiwt } from 'rettiwt-api';
496
+ *
497
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
498
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
499
+ *
500
+ * // Unliking the Tweet with id '1234567890'
501
+ * rettiwt.tweet.unlike('1234567890')
322
502
  * .then(res => {
323
503
  * console.log(res);
324
504
  * })
@@ -326,16 +506,35 @@ export class TweetService extends FetcherService {
326
506
  * console.log(err);
327
507
  * });
328
508
  * ```
509
+ */
510
+ public async unlike(id: string): Promise<boolean> {
511
+ const resource = EResourceType.TWEET_UNLIKE;
512
+
513
+ // Unliking the tweet
514
+ const response = await this.request<ITweetUnlikeResponse>(resource, { id: id });
515
+
516
+ // Deserializing the response
517
+ const data = this.extract<boolean>(response, resource) ?? false;
518
+
519
+ return data;
520
+ }
521
+
522
+ /**
523
+ * Unpost a tweet.
329
524
  *
330
- * @example Posting a reply to a tweet
525
+ * @param id - The id of the target tweet.
526
+ *
527
+ * @returns Whether unposting was successful or not.
528
+ *
529
+ * @example
331
530
  * ```
332
531
  * import { Rettiwt } from 'rettiwt-api';
333
532
  *
334
533
  * // Creating a new Rettiwt instance using the given 'API_KEY'
335
534
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
336
535
  *
337
- * // Posting a simple text reply, to a tweet with id "1234567890"
338
- * rettiwt.tweet.tweet('Hello!', undefined, "1234567890")
536
+ * // Unposting the Tweet with id '1234567890'
537
+ * rettiwt.tweet.unpost('1234567890')
339
538
  * .then(res => {
340
539
  * console.log(res);
341
540
  * })
@@ -343,40 +542,25 @@ export class TweetService extends FetcherService {
343
542
  * console.log(err);
344
543
  * });
345
544
  * ```
346
- *
347
- * @public
348
545
  */
349
- public async tweet(text: string, media?: TweetMediaArgs[], replyTo?: string): Promise<boolean> {
350
- // Converting JSON args to object
351
- const tweet: TweetArgs = new TweetArgs({ text: text, media: media });
352
-
353
- /** Stores the list of media that has been uploaded */
354
- const uploadedMedia: MediaArgs[] = [];
546
+ public async unpost(id: string): Promise<boolean> {
547
+ const resource = EResourceType.TWEET_UNPOST;
355
548
 
356
- // If tweet includes media, upload the media items
357
- if (tweet.media) {
358
- for (const item of tweet.media) {
359
- // Uploading the media item and getting it's allocated id
360
- const id: string = await this.upload(item.path);
361
-
362
- // Storing the uploaded media item
363
- uploadedMedia.push(new MediaArgs({ id: id, tags: item.tags }));
364
- }
365
- }
549
+ // Unposting the tweet
550
+ const response = await this.request<ITweetUnpostResponse>(resource, { id: id });
366
551
 
367
- // Posting the tweet
368
- const data = await this.post(EResourceType.CREATE_TWEET, {
369
- tweet: { text: text, media: uploadedMedia, replyTo: replyTo },
370
- });
552
+ // Deserializing the response
553
+ const data = this.extract<boolean>(response, resource) ?? false;
371
554
 
372
555
  return data;
373
556
  }
374
557
 
375
558
  /**
376
- * Favorite the tweet with the given id.
559
+ * Unretweet a tweet.
560
+ *
561
+ * @param id - The id of the target tweet.
377
562
  *
378
- * @param tweetId - The id of the tweet to be favorited.
379
- * @returns Whether favoriting was successful or not.
563
+ * @returns Whether unretweeting was successful or not.
380
564
  *
381
565
  * @example
382
566
  * ```
@@ -385,8 +569,8 @@ export class TweetService extends FetcherService {
385
569
  * // Creating a new Rettiwt instance using the given 'API_KEY'
386
570
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
387
571
  *
388
- * // Liking the Tweet with id '12345678'
389
- * rettiwt.tweet.favorite('12345678')
572
+ * // Unretweeting the Tweet with id '1234567890'
573
+ * rettiwt.tweet.unretweet('1234567890')
390
574
  * .then(res => {
391
575
  * console.log(res);
392
576
  * })
@@ -394,21 +578,25 @@ export class TweetService extends FetcherService {
394
578
  * console.log(err);
395
579
  * });
396
580
  * ```
397
- *
398
- * @public
399
581
  */
400
- public async favorite(tweetId: string): Promise<boolean> {
401
- // Favoriting the tweet
402
- const data = await this.post(EResourceType.FAVORITE_TWEET, { id: tweetId });
582
+ public async unretweet(id: string): Promise<boolean> {
583
+ const resource = EResourceType.TWEET_UNRETWEET;
584
+
585
+ // Unretweeting the tweet
586
+ const response = await this.request<ITweetUnretweetResponse>(resource, { id: id });
587
+
588
+ // Deserializing the response
589
+ const data = this.extract<boolean>(response, resource) ?? false;
403
590
 
404
591
  return data;
405
592
  }
406
593
 
407
594
  /**
408
- * Retweet the tweet with the given id.
595
+ * Upload a media file to Twitter.
409
596
  *
410
- * @param tweetId - The id of the tweet with the given id.
411
- * @returns Whether retweeting was successful or not.
597
+ * @param media - The path or ArrayBuffer to the media file to upload.
598
+ *
599
+ * @returns The id of the uploaded media.
412
600
  *
413
601
  * @example
414
602
  * ```
@@ -417,8 +605,8 @@ export class TweetService extends FetcherService {
417
605
  * // Creating a new Rettiwt instance using the given 'API_KEY'
418
606
  * const rettiwt = new Rettiwt({ apiKey: API_KEY });
419
607
  *
420
- * // Retweeting the Tweet with id '12345678'
421
- * rettiwt.tweet.retweet('12345678')
608
+ * // Uploading a file called mountains.jpg
609
+ * rettiwt.tweet.upload('mountains.jpg')
422
610
  * .then(res => {
423
611
  * console.log(res);
424
612
  * })
@@ -427,12 +615,26 @@ export class TweetService extends FetcherService {
427
615
  * });
428
616
  * ```
429
617
  *
430
- * @public
618
+ * @remarks
619
+ * - The uploaded media exists for 24 hrs within which it can be included in a tweet to be posted.
620
+ * If not posted in a tweet within this period, the uploaded media is removed.
621
+ * - Instead of a path to the media, an ArrayBuffer containing the media can also be uploaded.
431
622
  */
432
- public async retweet(tweetId: string): Promise<boolean> {
433
- // Retweeting the tweet
434
- const data = await this.post(EResourceType.CREATE_RETWEET, { id: tweetId });
435
-
436
- return data;
623
+ public async upload(media: string | ArrayBuffer): Promise<string> {
624
+ // INITIALIZE
625
+ const size = typeof media == 'string' ? statSync(media).size : media.byteLength;
626
+ const id: string = (
627
+ await this.request<IInitializeMediaUploadResponse>(EResourceType.MEDIA_UPLOAD_INITIALIZE, {
628
+ upload: { size: size },
629
+ })
630
+ ).media_id_string;
631
+
632
+ // APPEND
633
+ await this.request<unknown>(EResourceType.MEDIA_UPLOAD_APPEND, { upload: { id: id, media: media } });
634
+
635
+ // FINALIZE
636
+ await this.request<unknown>(EResourceType.MEDIA_UPLOAD_FINALIZE, { upload: { id: id } });
637
+
638
+ return id;
437
639
  }
438
640
  }