rettiwt-api 5.0.1 → 6.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.
- package/.eslintrc.js +13 -8
- package/README.md +8 -1
- package/dist/cli.js +17 -14
- package/dist/cli.js.map +1 -1
- package/dist/collections/Extractors.d.ts +1 -1
- package/dist/collections/Extractors.js +21 -21
- package/dist/collections/Extractors.js.map +1 -1
- package/dist/collections/Groups.d.ts +4 -4
- package/dist/collections/Groups.js +45 -45
- package/dist/collections/Groups.js.map +1 -1
- package/dist/collections/Requests.d.ts +3 -3
- package/dist/collections/Requests.js +3 -3
- package/dist/collections/Requests.js.map +1 -1
- package/dist/collections/Tweet.d.ts +4 -4
- package/dist/collections/Tweet.js +5 -5
- package/dist/collections/Tweet.js.map +1 -1
- package/dist/commands/Tweet.js +48 -12
- package/dist/commands/Tweet.js.map +1 -1
- package/dist/enums/Api.d.ts +1 -1
- package/dist/enums/Api.js +7 -7
- package/dist/enums/Api.js.map +1 -1
- package/dist/enums/Authentication.d.ts +1 -1
- package/dist/enums/Authentication.js +7 -7
- package/dist/enums/Authentication.js.map +1 -1
- package/dist/enums/Data.d.ts +1 -1
- package/dist/enums/Data.js +7 -7
- package/dist/enums/Data.js.map +1 -1
- package/dist/enums/Logging.d.ts +1 -1
- package/dist/enums/Logging.js +11 -11
- package/dist/enums/Logging.js.map +1 -1
- package/dist/enums/Media.d.ts +1 -1
- package/dist/enums/Media.js +7 -7
- package/dist/enums/Media.js.map +1 -1
- package/dist/enums/Notification.d.ts +1 -1
- package/dist/enums/Notification.js +9 -9
- package/dist/enums/Notification.js.map +1 -1
- package/dist/enums/Resource.d.ts +1 -1
- package/dist/enums/Resource.js +42 -42
- package/dist/enums/Resource.js.map +1 -1
- package/dist/enums/Tweet.d.ts +1 -1
- package/dist/enums/Tweet.js +7 -7
- package/dist/enums/Tweet.js.map +1 -1
- package/dist/enums/raw/Analytics.d.ts +2 -2
- package/dist/enums/raw/Analytics.js +21 -21
- package/dist/enums/raw/Analytics.js.map +1 -1
- package/dist/enums/raw/Media.d.ts +1 -1
- package/dist/enums/raw/Media.js +7 -7
- package/dist/enums/raw/Media.js.map +1 -1
- package/dist/enums/raw/Notification.d.ts +1 -1
- package/dist/enums/raw/Notification.js +8 -8
- package/dist/enums/raw/Notification.js.map +1 -1
- package/dist/enums/raw/Tweet.d.ts +2 -2
- package/dist/enums/raw/Tweet.js +12 -12
- package/dist/enums/raw/Tweet.js.map +1 -1
- package/dist/models/RettiwtConfig.d.ts +3 -2
- package/dist/models/RettiwtConfig.js +7 -5
- package/dist/models/RettiwtConfig.js.map +1 -1
- package/dist/models/args/FetchArgs.d.ts +7 -5
- package/dist/models/args/FetchArgs.js +15 -9
- package/dist/models/args/FetchArgs.js.map +1 -1
- package/dist/models/auth/AuthCredential.d.ts +2 -2
- package/dist/models/auth/AuthCredential.js +3 -3
- package/dist/models/auth/AuthCredential.js.map +1 -1
- package/dist/models/data/CursoredData.d.ts +2 -2
- package/dist/models/data/CursoredData.js +3 -3
- package/dist/models/data/CursoredData.js.map +1 -1
- package/dist/models/data/Notification.d.ts +2 -2
- package/dist/models/data/Notification.js +3 -3
- package/dist/models/data/Notification.js.map +1 -1
- package/dist/models/data/Tweet.d.ts +4 -4
- package/dist/models/data/Tweet.js +18 -18
- package/dist/models/data/Tweet.js.map +1 -1
- package/dist/models/data/User.js +9 -9
- package/dist/models/data/User.js.map +1 -1
- package/dist/requests/Tweet.d.ts +2 -2
- package/dist/requests/Tweet.js +2 -2
- package/dist/requests/Tweet.js.map +1 -1
- package/dist/requests/User.d.ts +2 -2
- package/dist/requests/User.js.map +1 -1
- package/dist/services/internal/AuthService.js +1 -1
- package/dist/services/internal/AuthService.js.map +1 -1
- package/dist/services/internal/ErrorService.d.ts +2 -2
- package/dist/services/internal/ErrorService.js +4 -4
- package/dist/services/internal/ErrorService.js.map +1 -1
- package/dist/services/internal/LogService.d.ts +2 -2
- package/dist/services/internal/LogService.js.map +1 -1
- package/dist/services/internal/TidService.d.ts +4 -4
- package/dist/services/internal/TidService.js +10 -10
- package/dist/services/internal/TidService.js.map +1 -1
- package/dist/services/public/FetcherService.d.ts +7 -7
- package/dist/services/public/FetcherService.js +79 -35
- package/dist/services/public/FetcherService.js.map +1 -1
- package/dist/services/public/ListService.js +4 -4
- package/dist/services/public/ListService.js.map +1 -1
- package/dist/services/public/TweetService.d.ts +3 -3
- package/dist/services/public/TweetService.js +35 -35
- package/dist/services/public/TweetService.js.map +1 -1
- package/dist/services/public/UserService.js +37 -37
- package/dist/services/public/UserService.js.map +1 -1
- package/dist/types/RettiwtConfig.d.ts +6 -0
- package/dist/types/args/FetchArgs.d.ts +14 -6
- package/dist/types/auth/AuthCredential.d.ts +2 -2
- package/dist/types/data/Notification.d.ts +2 -2
- package/dist/types/data/Tweet.d.ts +2 -2
- package/dist/types/raw/base/Media.d.ts +2 -2
- package/dist/types/raw/base/Notification.d.ts +2 -2
- package/package.json +18 -18
- package/src/cli.ts +20 -14
- package/src/collections/Extractors.ts +21 -21
- package/src/collections/Groups.ts +45 -45
- package/src/collections/Requests.ts +4 -4
- package/src/collections/Tweet.ts +6 -6
- package/src/commands/Tweet.ts +49 -12
- package/src/enums/Api.ts +1 -1
- package/src/enums/Authentication.ts +1 -1
- package/src/enums/Data.ts +1 -1
- package/src/enums/Logging.ts +1 -1
- package/src/enums/Media.ts +1 -1
- package/src/enums/Notification.ts +1 -1
- package/src/enums/Resource.ts +1 -1
- package/src/enums/Tweet.ts +1 -1
- package/src/enums/raw/Analytics.ts +2 -2
- package/src/enums/raw/Media.ts +1 -1
- package/src/enums/raw/Notification.ts +1 -1
- package/src/enums/raw/Tweet.ts +2 -2
- package/src/models/RettiwtConfig.ts +7 -5
- package/src/models/args/FetchArgs.ts +17 -11
- package/src/models/auth/AuthCredential.ts +5 -5
- package/src/models/data/CursoredData.ts +5 -5
- package/src/models/data/Notification.ts +6 -6
- package/src/models/data/Tweet.ts +22 -22
- package/src/models/data/User.ts +10 -10
- package/src/requests/Tweet.ts +4 -4
- package/src/requests/User.ts +3 -3
- package/src/services/internal/AuthService.ts +2 -2
- package/src/services/internal/ErrorService.ts +4 -4
- package/src/services/internal/LogService.ts +2 -2
- package/src/services/internal/TidService.ts +11 -11
- package/src/services/public/FetcherService.ts +54 -38
- package/src/services/public/ListService.ts +6 -6
- package/src/services/public/TweetService.ts +39 -39
- package/src/services/public/UserService.ts +40 -40
- package/src/types/RettiwtConfig.ts +7 -0
- package/src/types/args/FetchArgs.ts +18 -8
- package/src/types/auth/AuthCredential.ts +2 -2
- package/src/types/data/Notification.ts +2 -2
- package/src/types/data/Tweet.ts +2 -2
- package/src/types/raw/base/Media.ts +2 -2
- package/src/types/raw/base/Notification.ts +2 -2
- package/.tool-versions +0 -1
|
@@ -2,7 +2,7 @@ import { AxiosHeaders, AxiosRequestHeaders } from 'axios';
|
|
|
2
2
|
|
|
3
3
|
import { Cookie } from 'cookiejar';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { AuthenticationType } from '../../enums/Authentication';
|
|
6
6
|
import { IAuthCredential } from '../../types/auth/AuthCredential';
|
|
7
7
|
|
|
8
8
|
import { AuthCookie } from './AuthCookie';
|
|
@@ -19,7 +19,7 @@ import { AuthCookie } from './AuthCookie';
|
|
|
19
19
|
*/
|
|
20
20
|
export class AuthCredential implements IAuthCredential {
|
|
21
21
|
public authToken?: string;
|
|
22
|
-
public authenticationType?:
|
|
22
|
+
public authenticationType?: AuthenticationType;
|
|
23
23
|
public cookies?: string;
|
|
24
24
|
public csrfToken?: string;
|
|
25
25
|
public guestToken?: string;
|
|
@@ -34,7 +34,7 @@ export class AuthCredential implements IAuthCredential {
|
|
|
34
34
|
// If guest credentials given
|
|
35
35
|
if (!cookies && guestToken) {
|
|
36
36
|
this.guestToken = guestToken;
|
|
37
|
-
this.authenticationType =
|
|
37
|
+
this.authenticationType = AuthenticationType.GUEST;
|
|
38
38
|
}
|
|
39
39
|
// If login credentials given
|
|
40
40
|
else if (cookies && guestToken) {
|
|
@@ -43,7 +43,7 @@ export class AuthCredential implements IAuthCredential {
|
|
|
43
43
|
|
|
44
44
|
this.cookies = parsedCookie.toString();
|
|
45
45
|
this.guestToken = guestToken;
|
|
46
|
-
this.authenticationType =
|
|
46
|
+
this.authenticationType = AuthenticationType.LOGIN;
|
|
47
47
|
}
|
|
48
48
|
// If user credentials given
|
|
49
49
|
else if (cookies && !guestToken) {
|
|
@@ -52,7 +52,7 @@ export class AuthCredential implements IAuthCredential {
|
|
|
52
52
|
|
|
53
53
|
this.cookies = parsedCookie.toString();
|
|
54
54
|
this.csrfToken = parsedCookie.ct0;
|
|
55
|
-
this.authenticationType =
|
|
55
|
+
this.authenticationType = AuthenticationType.USER;
|
|
56
56
|
}
|
|
57
57
|
}
|
|
58
58
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { BaseType } from '../../enums/Data';
|
|
2
2
|
|
|
3
3
|
import { findByFilter } from '../../helper/JsonUtils';
|
|
4
4
|
|
|
@@ -24,18 +24,18 @@ export class CursoredData<T extends Notification | Tweet | User> implements ICur
|
|
|
24
24
|
* @param response - The raw response.
|
|
25
25
|
* @param type - The base type of the data included in the batch.
|
|
26
26
|
*/
|
|
27
|
-
public constructor(response: NonNullable<unknown>, type:
|
|
27
|
+
public constructor(response: NonNullable<unknown>, type: BaseType) {
|
|
28
28
|
// Initializing defaults
|
|
29
29
|
this.list = [];
|
|
30
30
|
this.next = '';
|
|
31
31
|
|
|
32
|
-
if (type ==
|
|
32
|
+
if (type == BaseType.TWEET) {
|
|
33
33
|
this.list = Tweet.timeline(response) as T[];
|
|
34
34
|
this.next = findByFilter<IRawCursor>(response, 'cursorType', 'Bottom')[0]?.value ?? '';
|
|
35
|
-
} else if (type ==
|
|
35
|
+
} else if (type == BaseType.USER) {
|
|
36
36
|
this.list = User.timeline(response) as T[];
|
|
37
37
|
this.next = findByFilter<IRawCursor>(response, 'cursorType', 'Bottom')[0]?.value ?? '';
|
|
38
|
-
} else if (type ==
|
|
38
|
+
} else if (type == BaseType.NOTIFICATION) {
|
|
39
39
|
this.list = Notification.list(response) as T[];
|
|
40
40
|
this.next = findByFilter<IRawCursor>(response, 'cursorType', 'Top')[0]?.value ?? '';
|
|
41
41
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { NotificationType } from '../../enums/Notification';
|
|
2
|
+
import { RawNotificationType } from '../../enums/raw/Notification';
|
|
3
3
|
import { findKeyByValue } from '../../helper/JsonUtils';
|
|
4
4
|
import { INotification } from '../../types/data/Notification';
|
|
5
5
|
import { INotification as IRawNotification } from '../../types/raw/base/Notification';
|
|
@@ -19,7 +19,7 @@ export class Notification implements INotification {
|
|
|
19
19
|
public message: string;
|
|
20
20
|
public receivedAt: string;
|
|
21
21
|
public target: string[];
|
|
22
|
-
public type?:
|
|
22
|
+
public type?: NotificationType;
|
|
23
23
|
|
|
24
24
|
/**
|
|
25
25
|
* @param notification - The raw notification details.
|
|
@@ -28,7 +28,7 @@ export class Notification implements INotification {
|
|
|
28
28
|
this._raw = { ...notification };
|
|
29
29
|
|
|
30
30
|
// Getting the original notification type
|
|
31
|
-
const notificationType: string | undefined = findKeyByValue(
|
|
31
|
+
const notificationType: string | undefined = findKeyByValue(RawNotificationType, notification.icon.id);
|
|
32
32
|
|
|
33
33
|
this.from = notification.template?.aggregateUserActionsV1?.fromUsers
|
|
34
34
|
? notification.template.aggregateUserActionsV1.fromUsers.map((item) => item.user.id)
|
|
@@ -40,8 +40,8 @@ export class Notification implements INotification {
|
|
|
40
40
|
? notification.template.aggregateUserActionsV1.targetObjects.map((item) => item.tweet.id)
|
|
41
41
|
: [];
|
|
42
42
|
this.type = notificationType
|
|
43
|
-
?
|
|
44
|
-
:
|
|
43
|
+
? NotificationType[notificationType as keyof typeof NotificationType]
|
|
44
|
+
: NotificationType.UNDEFINED;
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
/** The raw notification details. */
|
package/src/models/data/Tweet.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
|
+
import { MediaType } from '../../enums/Media';
|
|
3
|
+
import { RawMediaType } from '../../enums/raw/Media';
|
|
4
4
|
import { findByFilter } from '../../helper/JsonUtils';
|
|
5
5
|
|
|
6
6
|
import { LogService } from '../../services/internal/LogService';
|
|
@@ -52,7 +52,7 @@ export class Tweet implements ITweet {
|
|
|
52
52
|
this.tweetBy = new User(tweet.core.user_results.result);
|
|
53
53
|
this.entities = new TweetEntities(tweet.legacy.entities);
|
|
54
54
|
this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media));
|
|
55
|
-
this.quoted = this.
|
|
55
|
+
this.quoted = this._getQuotedTweet(tweet);
|
|
56
56
|
this.fullText = tweet.note_tweet ? tweet.note_tweet.note_tweet_results.result.text : tweet.legacy.full_text;
|
|
57
57
|
this.replyTo = tweet.legacy.in_reply_to_status_id_str;
|
|
58
58
|
this.lang = tweet.legacy.lang;
|
|
@@ -62,7 +62,7 @@ export class Tweet implements ITweet {
|
|
|
62
62
|
this.likeCount = tweet.legacy.favorite_count;
|
|
63
63
|
this.viewCount = tweet.views.count ? parseInt(tweet.views.count) : 0;
|
|
64
64
|
this.bookmarkCount = tweet.legacy.bookmark_count;
|
|
65
|
-
this.retweetedTweet = this.
|
|
65
|
+
this.retweetedTweet = this._getRetweetedTweet(tweet);
|
|
66
66
|
this.url = `https://x.com/${this.tweetBy.userName}/status/${this.id}`;
|
|
67
67
|
}
|
|
68
68
|
|
|
@@ -78,7 +78,7 @@ export class Tweet implements ITweet {
|
|
|
78
78
|
*
|
|
79
79
|
* @returns - The deserialized original quoted tweet.
|
|
80
80
|
*/
|
|
81
|
-
private
|
|
81
|
+
private _getQuotedTweet(tweet: IRawTweet): Tweet | undefined {
|
|
82
82
|
// If tweet with limited visibility
|
|
83
83
|
if (
|
|
84
84
|
tweet.quoted_status_result &&
|
|
@@ -104,7 +104,7 @@ export class Tweet implements ITweet {
|
|
|
104
104
|
*
|
|
105
105
|
* @returns - The deserialized original retweeted tweet.
|
|
106
106
|
*/
|
|
107
|
-
private
|
|
107
|
+
private _getRetweetedTweet(tweet: IRawTweet): Tweet | undefined {
|
|
108
108
|
// If retweet with limited visibility
|
|
109
109
|
if (
|
|
110
110
|
tweet.legacy?.retweeted_status_result &&
|
|
@@ -141,13 +141,13 @@ export class Tweet implements ITweet {
|
|
|
141
141
|
for (const item of extract) {
|
|
142
142
|
if (item.legacy) {
|
|
143
143
|
// Logging
|
|
144
|
-
LogService.log(
|
|
144
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
145
145
|
|
|
146
146
|
tweets.push(new Tweet(item));
|
|
147
147
|
} else {
|
|
148
148
|
// Logging
|
|
149
|
-
LogService.log(
|
|
150
|
-
action:
|
|
149
|
+
LogService.log(LogActions.WARNING, {
|
|
150
|
+
action: LogActions.DESERIALIZE,
|
|
151
151
|
message: `Tweet not found, skipping`,
|
|
152
152
|
});
|
|
153
153
|
}
|
|
@@ -179,13 +179,13 @@ export class Tweet implements ITweet {
|
|
|
179
179
|
for (const item of extract) {
|
|
180
180
|
if (item.legacy) {
|
|
181
181
|
// Logging
|
|
182
|
-
LogService.log(
|
|
182
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
183
183
|
|
|
184
184
|
tweets.push(new Tweet(item));
|
|
185
185
|
} else {
|
|
186
186
|
// Logging
|
|
187
|
-
LogService.log(
|
|
188
|
-
action:
|
|
187
|
+
LogService.log(LogActions.WARNING, {
|
|
188
|
+
action: LogActions.DESERIALIZE,
|
|
189
189
|
message: `Tweet not found, skipping`,
|
|
190
190
|
});
|
|
191
191
|
}
|
|
@@ -221,15 +221,15 @@ export class Tweet implements ITweet {
|
|
|
221
221
|
// If normal tweet
|
|
222
222
|
else if ((item.tweet_results?.result as IRawTweet)?.legacy) {
|
|
223
223
|
// Logging
|
|
224
|
-
LogService.log(
|
|
224
|
+
LogService.log(LogActions.DESERIALIZE, { id: (item.tweet_results.result as IRawTweet).rest_id });
|
|
225
225
|
|
|
226
226
|
tweets.push(new Tweet(item.tweet_results.result as IRawTweet));
|
|
227
227
|
}
|
|
228
228
|
// If invalid/unrecognized tweet
|
|
229
229
|
else {
|
|
230
230
|
// Logging
|
|
231
|
-
LogService.log(
|
|
232
|
-
action:
|
|
231
|
+
LogService.log(LogActions.WARNING, {
|
|
232
|
+
action: LogActions.DESERIALIZE,
|
|
233
233
|
message: `Tweet not found, skipping`,
|
|
234
234
|
});
|
|
235
235
|
}
|
|
@@ -328,7 +328,7 @@ export class TweetMedia {
|
|
|
328
328
|
public thumbnailUrl?: string;
|
|
329
329
|
|
|
330
330
|
/** The type of media. */
|
|
331
|
-
public type:
|
|
331
|
+
public type: MediaType;
|
|
332
332
|
|
|
333
333
|
/** The direct URL to the media. */
|
|
334
334
|
public url = '';
|
|
@@ -338,18 +338,18 @@ export class TweetMedia {
|
|
|
338
338
|
*/
|
|
339
339
|
public constructor(media: IRawExtendedMedia) {
|
|
340
340
|
// If the media is a photo
|
|
341
|
-
if (media.type ==
|
|
342
|
-
this.type =
|
|
341
|
+
if (media.type == RawMediaType.PHOTO) {
|
|
342
|
+
this.type = MediaType.PHOTO;
|
|
343
343
|
this.url = media.media_url_https;
|
|
344
344
|
}
|
|
345
345
|
// If the media is a gif
|
|
346
|
-
else if (media.type ==
|
|
347
|
-
this.type =
|
|
346
|
+
else if (media.type == RawMediaType.GIF) {
|
|
347
|
+
this.type = MediaType.GIF;
|
|
348
348
|
this.url = media.video_info?.variants[0].url as string;
|
|
349
349
|
}
|
|
350
350
|
// If the media is a video
|
|
351
351
|
else {
|
|
352
|
-
this.type =
|
|
352
|
+
this.type = MediaType.VIDEO;
|
|
353
353
|
this.thumbnailUrl = media.media_url_https;
|
|
354
354
|
|
|
355
355
|
/** The highest bitrate of all variants. */
|
package/src/models/data/User.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
2
|
import { findByFilter } from '../../helper/JsonUtils';
|
|
3
3
|
import { LogService } from '../../services/internal/LogService';
|
|
4
4
|
import { IUser } from '../../types/data/User';
|
|
@@ -73,13 +73,13 @@ export class User implements IUser {
|
|
|
73
73
|
for (const item of extract) {
|
|
74
74
|
if (item.legacy && item.legacy.created_at) {
|
|
75
75
|
// Logging
|
|
76
|
-
LogService.log(
|
|
76
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
77
77
|
|
|
78
78
|
users.push(new User(item));
|
|
79
79
|
} else {
|
|
80
80
|
// Logging
|
|
81
|
-
LogService.log(
|
|
82
|
-
action:
|
|
81
|
+
LogService.log(LogActions.WARNING, {
|
|
82
|
+
action: LogActions.DESERIALIZE,
|
|
83
83
|
message: `User not found, skipping`,
|
|
84
84
|
});
|
|
85
85
|
}
|
|
@@ -110,13 +110,13 @@ export class User implements IUser {
|
|
|
110
110
|
for (const item of extract) {
|
|
111
111
|
if (item.legacy && item.legacy.created_at) {
|
|
112
112
|
// Logging
|
|
113
|
-
LogService.log(
|
|
113
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
114
114
|
|
|
115
115
|
users.push(new User(item));
|
|
116
116
|
} else {
|
|
117
117
|
// Logging
|
|
118
|
-
LogService.log(
|
|
119
|
-
action:
|
|
118
|
+
LogService.log(LogActions.WARNING, {
|
|
119
|
+
action: LogActions.DESERIALIZE,
|
|
120
120
|
message: `User not found, skipping`,
|
|
121
121
|
});
|
|
122
122
|
}
|
|
@@ -142,13 +142,13 @@ export class User implements IUser {
|
|
|
142
142
|
for (const item of extract) {
|
|
143
143
|
if (item.user_results?.result?.legacy) {
|
|
144
144
|
// Logging
|
|
145
|
-
LogService.log(
|
|
145
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.user_results.result.rest_id });
|
|
146
146
|
|
|
147
147
|
users.push(new User(item.user_results.result));
|
|
148
148
|
} else {
|
|
149
149
|
// Logging
|
|
150
|
-
LogService.log(
|
|
151
|
-
action:
|
|
150
|
+
LogService.log(LogActions.WARNING, {
|
|
151
|
+
action: LogActions.DESERIALIZE,
|
|
152
152
|
message: `User not found, skipping`,
|
|
153
153
|
});
|
|
154
154
|
}
|
package/src/requests/Tweet.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { AxiosRequestConfig } from 'axios';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { RawTweetRepliesSortType, RawTweetSearchResultType } from '../enums/raw/Tweet';
|
|
4
4
|
import { TweetFilter } from '../models/args/FetchArgs';
|
|
5
5
|
import { NewTweet } from '../models/args/PostArgs';
|
|
6
6
|
import { MediaVariable, ReplyVariable } from '../models/params/Variables';
|
|
@@ -266,7 +266,7 @@ export class TweetRequests {
|
|
|
266
266
|
* @param id - The id of the tweet whose replies are to be fetched.
|
|
267
267
|
* @param cursor - The cursor to the batch of replies to fetch.
|
|
268
268
|
*/
|
|
269
|
-
public static replies(id: string, cursor?: string, sortBy?:
|
|
269
|
+
public static replies(id: string, cursor?: string, sortBy?: RawTweetRepliesSortType): AxiosRequestConfig {
|
|
270
270
|
return {
|
|
271
271
|
method: 'get',
|
|
272
272
|
url: 'https://x.com/i/api/graphql/_8aYOgEDz35BrBcBal1-_w/TweetDetail',
|
|
@@ -277,7 +277,7 @@ export class TweetRequests {
|
|
|
277
277
|
cursor: cursor,
|
|
278
278
|
referrer: 'tweet',
|
|
279
279
|
with_rux_injections: false,
|
|
280
|
-
rankingMode: sortBy ??
|
|
280
|
+
rankingMode: sortBy ?? RawTweetRepliesSortType.RELEVACE,
|
|
281
281
|
includePromotedContent: true,
|
|
282
282
|
withCommunity: true,
|
|
283
283
|
withQuickPromoteEligibilityTweetFields: true,
|
|
@@ -451,7 +451,7 @@ export class TweetRequests {
|
|
|
451
451
|
count: count,
|
|
452
452
|
cursor: cursor,
|
|
453
453
|
querySource: 'typed_query',
|
|
454
|
-
product: parsedFilter.top ?
|
|
454
|
+
product: parsedFilter.top ? RawTweetSearchResultType.TOP : RawTweetSearchResultType.LATEST,
|
|
455
455
|
withAuxiliaryUserLabels: false,
|
|
456
456
|
withArticleRichContentState: false,
|
|
457
457
|
withArticlePlainText: false,
|
package/src/requests/User.ts
CHANGED
|
@@ -2,7 +2,7 @@ import qs from 'querystring';
|
|
|
2
2
|
|
|
3
3
|
import { AxiosRequestConfig } from 'axios';
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { RawAnalyticsGranularity, RawAnalyticsMetric } from '../enums/raw/Analytics';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Collection of requests related to users.
|
|
@@ -77,8 +77,8 @@ export class UserRequests {
|
|
|
77
77
|
public static analytics(
|
|
78
78
|
fromTime: Date,
|
|
79
79
|
toTime: Date,
|
|
80
|
-
granularity:
|
|
81
|
-
requestedMetrics:
|
|
80
|
+
granularity: RawAnalyticsGranularity,
|
|
81
|
+
requestedMetrics: RawAnalyticsMetric[],
|
|
82
82
|
): AxiosRequestConfig {
|
|
83
83
|
return {
|
|
84
84
|
method: 'get',
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { ApiErrors } from '../../enums/Api';
|
|
4
4
|
import { AuthCredential } from '../../models/auth/AuthCredential';
|
|
5
5
|
import { RettiwtConfig } from '../../models/RettiwtConfig';
|
|
6
6
|
|
|
@@ -67,7 +67,7 @@ export class AuthService {
|
|
|
67
67
|
}
|
|
68
68
|
// If user id was not found
|
|
69
69
|
else {
|
|
70
|
-
throw new Error(
|
|
70
|
+
throw new Error(ApiErrors.BAD_AUTHENTICATION);
|
|
71
71
|
}
|
|
72
72
|
}
|
|
73
73
|
|
|
@@ -15,14 +15,14 @@ export class ErrorService implements IErrorHandler {
|
|
|
15
15
|
*
|
|
16
16
|
* @param error - The error response received from Twitter.
|
|
17
17
|
*/
|
|
18
|
-
private
|
|
18
|
+
private _handleAxiosError(error: AxiosError<IRawErrorData | IRawErrorDetails>): void {
|
|
19
19
|
throw new TwitterError(error);
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* Handle unknown error.
|
|
24
24
|
*/
|
|
25
|
-
private
|
|
25
|
+
private _handleUnknownError(): void {
|
|
26
26
|
throw new Error('Unknown error');
|
|
27
27
|
}
|
|
28
28
|
|
|
@@ -33,9 +33,9 @@ export class ErrorService implements IErrorHandler {
|
|
|
33
33
|
*/
|
|
34
34
|
public handle(error: unknown): void {
|
|
35
35
|
if (isAxiosError(error)) {
|
|
36
|
-
this.
|
|
36
|
+
this._handleAxiosError(error as AxiosError<IRawErrorData | IRawErrorDetails>);
|
|
37
37
|
} else {
|
|
38
|
-
this.
|
|
38
|
+
this._handleUnknownError();
|
|
39
39
|
}
|
|
40
40
|
}
|
|
41
41
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* Handles logging of data for debug purpose.
|
|
@@ -16,7 +16,7 @@ export class LogService {
|
|
|
16
16
|
*
|
|
17
17
|
* @param data - The data to be logged.
|
|
18
18
|
*/
|
|
19
|
-
public static log(action:
|
|
19
|
+
public static log(action: LogActions, data: NonNullable<unknown>): void {
|
|
20
20
|
// Proceed to log only if logging is enabled
|
|
21
21
|
if (this.enabled) {
|
|
22
22
|
// Preparing the log message
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import axios from 'axios';
|
|
2
2
|
import * as htmlParser from 'node-html-parser';
|
|
3
3
|
|
|
4
|
-
import {
|
|
4
|
+
import { LogActions } from '../../enums/Logging';
|
|
5
5
|
|
|
6
6
|
import { calculateClientTransactionIdHeader } from '../../helper/TidUtils';
|
|
7
7
|
|
|
@@ -34,16 +34,16 @@ export class TidService implements ITidProvider {
|
|
|
34
34
|
*
|
|
35
35
|
* @returns The new dynamic args.
|
|
36
36
|
*/
|
|
37
|
-
private async
|
|
38
|
-
const html = await this.
|
|
37
|
+
private async _getDynamicArgs(): Promise<ITidDynamicArgs> {
|
|
38
|
+
const html = await this._getHomepageHtml();
|
|
39
39
|
const root = htmlParser.parse(html);
|
|
40
40
|
const keyElement = root.querySelector("[name='twitter-site-verification']");
|
|
41
41
|
const frameElements = root.querySelectorAll("[id^='loading-x-anim']");
|
|
42
42
|
|
|
43
43
|
return {
|
|
44
44
|
verificationKey: keyElement?.getAttribute('content') ?? '',
|
|
45
|
-
frames: frameElements.map((el) => this.
|
|
46
|
-
indices: await this.
|
|
45
|
+
frames: frameElements.map((el) => this._parseFrameElement(el)),
|
|
46
|
+
indices: await this._getKeyBytesIndices(html),
|
|
47
47
|
};
|
|
48
48
|
}
|
|
49
49
|
|
|
@@ -52,7 +52,7 @@ export class TidService implements ITidProvider {
|
|
|
52
52
|
*
|
|
53
53
|
* @returns The stringified HTML content of the homepage.
|
|
54
54
|
*/
|
|
55
|
-
private async
|
|
55
|
+
private async _getHomepageHtml(): Promise<string> {
|
|
56
56
|
const response = await axios.get<string>('https://x.com', {
|
|
57
57
|
headers: this._config.headers,
|
|
58
58
|
httpAgent: this._config.httpsAgent,
|
|
@@ -62,10 +62,10 @@ export class TidService implements ITidProvider {
|
|
|
62
62
|
return response.data;
|
|
63
63
|
}
|
|
64
64
|
|
|
65
|
-
private async
|
|
65
|
+
private async _getKeyBytesIndices(html: string): Promise<number[]> {
|
|
66
66
|
const ondemandFileMatch = html.match(/ondemand\.s":"([^"]+)"/);
|
|
67
67
|
if (!ondemandFileMatch || !ondemandFileMatch[1]) {
|
|
68
|
-
LogService.log(
|
|
68
|
+
LogService.log(LogActions.WARNING, { message: 'ondemand.s file not found' });
|
|
69
69
|
|
|
70
70
|
return [0, 0, 0, 0];
|
|
71
71
|
}
|
|
@@ -80,7 +80,7 @@ export class TidService implements ITidProvider {
|
|
|
80
80
|
return Array.from(match).map((m) => Number(m[2]));
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
private
|
|
83
|
+
private _parseFrameElement(element: htmlParser.HTMLElement): number[][] {
|
|
84
84
|
const pathElement = element.children[0].children[1];
|
|
85
85
|
const value = pathElement.getAttribute('d');
|
|
86
86
|
if (!value) {
|
|
@@ -122,7 +122,7 @@ export class TidService implements ITidProvider {
|
|
|
122
122
|
extraByte: 3,
|
|
123
123
|
});
|
|
124
124
|
} catch (err) {
|
|
125
|
-
LogService.log(
|
|
125
|
+
LogService.log(LogActions.WARNING, {
|
|
126
126
|
message: 'Failed to generated transaction token. Request may or may not work',
|
|
127
127
|
error: err,
|
|
128
128
|
});
|
|
@@ -135,6 +135,6 @@ export class TidService implements ITidProvider {
|
|
|
135
135
|
* Refreshes the dynamic args from the homepage.
|
|
136
136
|
*/
|
|
137
137
|
public async refreshDynamicArgs(): Promise<void> {
|
|
138
|
-
this._dynamicArgs = await this.
|
|
138
|
+
this._dynamicArgs = await this._getDynamicArgs();
|
|
139
139
|
}
|
|
140
140
|
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
1
|
+
import axios, { isAxiosError } from 'axios';
|
|
2
2
|
import { Cookie } from 'cookiejar';
|
|
3
3
|
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
4
|
+
import { AllowGuestAuthenticationGroup, FetchResourcesGroup, PostResourcesGroup } from '../../collections/Groups';
|
|
5
|
+
import { Requests } from '../../collections/Requests';
|
|
6
|
+
import { ApiErrors } from '../../enums/Api';
|
|
7
|
+
import { LogActions } from '../../enums/Logging';
|
|
8
|
+
import { ResourceType } from '../../enums/Resource';
|
|
9
9
|
import { FetchArgs } from '../../models/args/FetchArgs';
|
|
10
10
|
import { PostArgs } from '../../models/args/PostArgs';
|
|
11
11
|
import { AuthCredential } from '../../models/auth/AuthCredential';
|
|
@@ -65,13 +65,13 @@ export class FetcherService {
|
|
|
65
65
|
*
|
|
66
66
|
* @throws An error if not authorized to access the requested resource.
|
|
67
67
|
*/
|
|
68
|
-
private
|
|
68
|
+
private _checkAuthorization(resource: ResourceType): void {
|
|
69
69
|
// Logging
|
|
70
|
-
LogService.log(
|
|
70
|
+
LogService.log(LogActions.AUTHORIZATION, { authenticated: this.config.userId != undefined });
|
|
71
71
|
|
|
72
72
|
// Checking authorization status
|
|
73
|
-
if (!
|
|
74
|
-
throw new Error(
|
|
73
|
+
if (!AllowGuestAuthenticationGroup.includes(resource) && this.config.userId == undefined) {
|
|
74
|
+
throw new Error(ApiErrors.RESOURCE_NOT_ALLOWED);
|
|
75
75
|
}
|
|
76
76
|
}
|
|
77
77
|
|
|
@@ -80,10 +80,10 @@ export class FetcherService {
|
|
|
80
80
|
*
|
|
81
81
|
* @returns The generated AuthCredential
|
|
82
82
|
*/
|
|
83
|
-
private async
|
|
83
|
+
private async _getCredential(): Promise<AuthCredential> {
|
|
84
84
|
if (this.config.apiKey) {
|
|
85
85
|
// Logging
|
|
86
|
-
LogService.log(
|
|
86
|
+
LogService.log(LogActions.GET, { target: 'USER_CREDENTIAL' });
|
|
87
87
|
|
|
88
88
|
return new AuthCredential(
|
|
89
89
|
AuthService.decodeCookie(this.config.apiKey)
|
|
@@ -92,7 +92,7 @@ export class FetcherService {
|
|
|
92
92
|
);
|
|
93
93
|
} else {
|
|
94
94
|
// Logging
|
|
95
|
-
LogService.log(
|
|
95
|
+
LogService.log(LogActions.GET, { target: 'NEW_GUEST_CREDENTIAL' });
|
|
96
96
|
|
|
97
97
|
return this._auth.guest();
|
|
98
98
|
}
|
|
@@ -106,7 +106,7 @@ export class FetcherService {
|
|
|
106
106
|
*
|
|
107
107
|
* @returns The header containing the transaction ID.
|
|
108
108
|
*/
|
|
109
|
-
private async
|
|
109
|
+
private async _getTransactionHeader(method: string, url: string): Promise<ITidHeader | undefined> {
|
|
110
110
|
// Getting the URL path excluding all params
|
|
111
111
|
const path = new URL(url).pathname.split('?')[0].trim();
|
|
112
112
|
|
|
@@ -132,15 +132,15 @@ export class FetcherService {
|
|
|
132
132
|
*
|
|
133
133
|
* @returns The validated args.
|
|
134
134
|
*/
|
|
135
|
-
private
|
|
136
|
-
if (
|
|
135
|
+
private _validateArgs(resource: ResourceType, args: IFetchArgs | IPostArgs): FetchArgs | PostArgs | undefined {
|
|
136
|
+
if (FetchResourcesGroup.includes(resource)) {
|
|
137
137
|
// Logging
|
|
138
|
-
LogService.log(
|
|
138
|
+
LogService.log(LogActions.VALIDATE, { target: 'FETCH_ARGS' });
|
|
139
139
|
|
|
140
140
|
return new FetchArgs(args);
|
|
141
|
-
} else if (
|
|
141
|
+
} else if (PostResourcesGroup.includes(resource)) {
|
|
142
142
|
// Logging
|
|
143
|
-
LogService.log(
|
|
143
|
+
LogService.log(LogActions.VALIDATE, { target: 'POST_ARGS' });
|
|
144
144
|
|
|
145
145
|
return new PostArgs(args);
|
|
146
146
|
}
|
|
@@ -149,7 +149,7 @@ export class FetcherService {
|
|
|
149
149
|
/**
|
|
150
150
|
* Introduces a delay using the configured delay/delay function.
|
|
151
151
|
*/
|
|
152
|
-
private async
|
|
152
|
+
private async _wait(): Promise<void> {
|
|
153
153
|
// If no delay is set, skip
|
|
154
154
|
if (this._delay == undefined) {
|
|
155
155
|
return;
|
|
@@ -198,44 +198,60 @@ export class FetcherService {
|
|
|
198
198
|
* });
|
|
199
199
|
* ```
|
|
200
200
|
*/
|
|
201
|
-
public async request<T = unknown>(resource:
|
|
201
|
+
public async request<T = unknown>(resource: ResourceType, args: IFetchArgs | IPostArgs): Promise<T> {
|
|
202
|
+
/** The error, if any. */
|
|
203
|
+
let error: unknown = undefined;
|
|
204
|
+
|
|
202
205
|
// Logging
|
|
203
|
-
LogService.log(
|
|
206
|
+
LogService.log(LogActions.REQUEST, { resource: resource, args: args });
|
|
204
207
|
|
|
205
208
|
// Checking authorization for the requested resource
|
|
206
|
-
this.
|
|
209
|
+
this._checkAuthorization(resource);
|
|
207
210
|
|
|
208
211
|
// Validating args
|
|
209
|
-
args = this.
|
|
212
|
+
args = this._validateArgs(resource, args)!;
|
|
210
213
|
|
|
211
214
|
// Getting credentials from key
|
|
212
|
-
const cred: AuthCredential = await this.
|
|
215
|
+
const cred: AuthCredential = await this._getCredential();
|
|
213
216
|
|
|
214
217
|
// Getting request configuration
|
|
215
|
-
const config =
|
|
218
|
+
const config = Requests[resource](args);
|
|
216
219
|
|
|
217
220
|
// Setting additional request parameters
|
|
218
221
|
config.headers = {
|
|
219
222
|
...config.headers,
|
|
220
223
|
...cred.toHeader(),
|
|
221
|
-
...(await this.
|
|
224
|
+
...(await this._getTransactionHeader(config.method ?? '', config.url ?? '')),
|
|
222
225
|
...this.config.headers,
|
|
223
226
|
};
|
|
224
227
|
config.httpAgent = this.config.httpsAgent;
|
|
225
228
|
config.httpsAgent = this.config.httpsAgent;
|
|
226
229
|
config.timeout = this._timeout;
|
|
227
230
|
|
|
228
|
-
//
|
|
229
|
-
|
|
230
|
-
//
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
231
|
+
// Using retries for error 404
|
|
232
|
+
for (let retry = 1; retry <= this.config.maxRetries; retry++) {
|
|
233
|
+
// Sending the request
|
|
234
|
+
try {
|
|
235
|
+
// Introducing a delay
|
|
236
|
+
await this._wait();
|
|
237
|
+
|
|
238
|
+
// Returning the reponse body
|
|
239
|
+
return (await axios<T>(config)).data;
|
|
240
|
+
} catch (err) {
|
|
241
|
+
// If it's an error 404, retry
|
|
242
|
+
if (isAxiosError(err) && err.status === 404) {
|
|
243
|
+
error = err;
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
// Else, delegate error handling
|
|
247
|
+
else {
|
|
248
|
+
this._errorHandler.handle(err);
|
|
249
|
+
throw err;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
239
252
|
}
|
|
253
|
+
|
|
254
|
+
/** If request not successful even after retries, throw the error */
|
|
255
|
+
throw error;
|
|
240
256
|
}
|
|
241
257
|
}
|