rettiwt-api 6.3.0-alpha.0 → 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.
- package/README.md +81 -31
- package/dist/Rettiwt.d.ts +6 -2
- package/dist/Rettiwt.js +7 -3
- package/dist/Rettiwt.js.map +1 -1
- package/dist/cli.js +3 -1
- package/dist/cli.js.map +1 -1
- package/dist/collections/Extractors.d.ts +15 -2
- package/dist/collections/Extractors.js +12 -1
- package/dist/collections/Extractors.js.map +1 -1
- package/dist/collections/Groups.js +8 -0
- package/dist/collections/Groups.js.map +1 -1
- package/dist/collections/Requests.js +8 -0
- package/dist/collections/Requests.js.map +1 -1
- package/dist/commands/Space.d.ts +10 -0
- package/dist/commands/Space.js +38 -0
- package/dist/commands/Space.js.map +1 -0
- package/dist/commands/User.js +139 -0
- package/dist/commands/User.js.map +1 -1
- package/dist/enums/Resource.d.ts +8 -1
- package/dist/enums/Resource.js +8 -0
- package/dist/enums/Resource.js.map +1 -1
- package/dist/index.d.ts +11 -1
- package/dist/index.js +6 -0
- package/dist/index.js.map +1 -1
- package/dist/models/RettiwtConfig.d.ts +26 -3
- package/dist/models/RettiwtConfig.js +68 -3
- package/dist/models/RettiwtConfig.js.map +1 -1
- package/dist/models/args/FetchArgs.d.ts +3 -0
- package/dist/models/args/FetchArgs.js +6 -0
- package/dist/models/args/FetchArgs.js.map +1 -1
- package/dist/models/args/PostArgs.d.ts +24 -1
- package/dist/models/args/PostArgs.js +52 -1
- package/dist/models/args/PostArgs.js.map +1 -1
- package/dist/models/data/Space.d.ts +70 -0
- package/dist/models/data/Space.js +177 -0
- package/dist/models/data/Space.js.map +1 -0
- package/dist/models/data/UserAbout.d.ts +44 -0
- package/dist/models/data/UserAbout.js +129 -0
- package/dist/models/data/UserAbout.js.map +1 -0
- package/dist/requests/Space.d.ts +15 -0
- package/dist/requests/Space.js +74 -0
- package/dist/requests/Space.js.map +1 -0
- package/dist/requests/Tweet.d.ts +4 -0
- package/dist/requests/Tweet.js +57 -0
- package/dist/requests/Tweet.js.map +1 -1
- package/dist/requests/User.d.ts +21 -0
- package/dist/requests/User.js +64 -0
- package/dist/requests/User.js.map +1 -1
- package/dist/services/internal/AuthService.d.ts +25 -0
- package/dist/services/internal/AuthService.js +121 -0
- package/dist/services/internal/AuthService.js.map +1 -1
- package/dist/services/public/DirectMessageService.js +3 -3
- package/dist/services/public/DirectMessageService.js.map +1 -1
- package/dist/services/public/FetcherService.d.ts +4 -3
- package/dist/services/public/FetcherService.js +22 -16
- package/dist/services/public/FetcherService.js.map +1 -1
- package/dist/services/public/ListService.js +5 -5
- package/dist/services/public/ListService.js.map +1 -1
- package/dist/services/public/SpaceService.d.ts +42 -0
- package/dist/services/public/SpaceService.js +60 -0
- package/dist/services/public/SpaceService.js.map +1 -0
- package/dist/services/public/TweetService.js +26 -23
- package/dist/services/public/TweetService.js.map +1 -1
- package/dist/services/public/UserService.d.ts +79 -0
- package/dist/services/public/UserService.js +203 -23
- package/dist/services/public/UserService.js.map +1 -1
- package/dist/types/RettiwtConfig.d.ts +33 -3
- package/dist/types/args/FetchArgs.d.ts +35 -1
- package/dist/types/args/PostArgs.d.ts +44 -1
- package/dist/types/data/Space.d.ts +89 -0
- package/dist/types/data/Space.js +3 -0
- package/dist/types/data/Space.js.map +1 -0
- package/dist/types/data/UserAbout.d.ts +68 -0
- package/dist/types/data/UserAbout.js +3 -0
- package/dist/types/data/UserAbout.js.map +1 -0
- package/dist/types/raw/base/Space.d.ts +43 -22
- package/dist/types/raw/space/AudioSpaceById.d.ts +50 -0
- package/dist/types/raw/space/AudioSpaceById.js +4 -0
- package/dist/types/raw/space/AudioSpaceById.js.map +1 -0
- package/dist/types/raw/space/Details.d.ts +2 -309
- package/dist/types/raw/tweet/Post.d.ts +16 -1
- package/dist/types/raw/user/About.d.ts +65 -0
- package/dist/types/raw/user/About.js +4 -0
- package/dist/types/raw/user/About.js.map +1 -0
- package/dist/types/raw/user/ChangePassword.d.ts +8 -0
- package/dist/types/raw/user/ChangePassword.js +3 -0
- package/dist/types/raw/user/ChangePassword.js.map +1 -0
- package/dist/types/raw/user/ProfileUpdate.d.ts +1 -0
- package/dist/types/raw/user/Settings.d.ts +21 -0
- package/dist/types/raw/user/Settings.js +4 -0
- package/dist/types/raw/user/Settings.js.map +1 -0
- package/package.json +6 -4
- package/src/Rettiwt.ts +10 -3
- package/src/cli.ts +3 -1
- package/src/collections/Extractors.ts +22 -3
- package/src/collections/Groups.ts +8 -0
- package/src/collections/Requests.ts +11 -0
- package/src/commands/Space.ts +46 -0
- package/src/commands/User.ts +159 -0
- package/src/enums/Resource.ts +9 -0
- package/src/index.ts +11 -1
- package/src/models/RettiwtConfig.ts +81 -6
- package/src/models/args/FetchArgs.ts +6 -0
- package/src/models/args/PostArgs.ts +58 -1
- package/src/models/data/Space.ts +201 -0
- package/src/models/data/UserAbout.ts +161 -0
- package/src/requests/Space.ts +76 -0
- package/src/requests/Tweet.ts +59 -0
- package/src/requests/User.ts +69 -0
- package/src/services/internal/AuthService.ts +149 -1
- package/src/services/public/DirectMessageService.ts +3 -3
- package/src/services/public/FetcherService.ts +25 -18
- package/src/services/public/ListService.ts +5 -5
- package/src/services/public/SpaceService.ts +65 -0
- package/src/services/public/TweetService.ts +27 -24
- package/src/services/public/UserService.ts +247 -23
- package/src/types/RettiwtConfig.ts +35 -3
- package/src/types/args/FetchArgs.ts +41 -1
- package/src/types/args/PostArgs.ts +50 -1
- package/src/types/data/Space.ts +122 -0
- package/src/types/data/UserAbout.ts +87 -0
- package/src/types/raw/base/Space.ts +42 -22
- package/src/types/raw/space/AudioSpaceById.ts +57 -0
- package/src/types/raw/space/Details.ts +3 -352
- package/src/types/raw/tweet/Post.ts +19 -1
- package/src/types/raw/user/About.ts +77 -0
- package/src/types/raw/user/ChangePassword.ts +8 -0
- package/src/types/raw/user/ProfileUpdate.ts +1 -0
- package/src/types/raw/user/Settings.ts +23 -0
- package/tsconfig.json +2 -2
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import { AxiosRequestConfig } from 'axios';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Collection of requests related to spaces.
|
|
5
|
+
*
|
|
6
|
+
* @public
|
|
7
|
+
*/
|
|
8
|
+
export class SpaceRequests {
|
|
9
|
+
/**
|
|
10
|
+
* @param id - The id of the space whose details are to be fetched.
|
|
11
|
+
* @param withReplays - Whether to include replay information.
|
|
12
|
+
* @param withListeners - Whether to include listeners information.
|
|
13
|
+
* @param isMetatagsQuery - Whether the request is a metatags query.
|
|
14
|
+
*/
|
|
15
|
+
public static details(
|
|
16
|
+
id: string,
|
|
17
|
+
withReplays?: boolean,
|
|
18
|
+
withListeners?: boolean,
|
|
19
|
+
isMetatagsQuery?: boolean,
|
|
20
|
+
): AxiosRequestConfig {
|
|
21
|
+
return {
|
|
22
|
+
method: 'get',
|
|
23
|
+
url: 'https://x.com/i/api/graphql/rR7CQrr8kxb6fatlUaB61Q/AudioSpaceById',
|
|
24
|
+
params: {
|
|
25
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
26
|
+
variables: JSON.stringify({
|
|
27
|
+
id: id,
|
|
28
|
+
isMetatagsQuery: isMetatagsQuery ?? false,
|
|
29
|
+
withReplays: withReplays ?? true,
|
|
30
|
+
withListeners: withListeners ?? false,
|
|
31
|
+
}),
|
|
32
|
+
features: JSON.stringify({
|
|
33
|
+
spaces_2022_h2_spaces_communities: true,
|
|
34
|
+
spaces_2022_h2_clipping: true,
|
|
35
|
+
creator_subscriptions_tweet_preview_api_enabled: true,
|
|
36
|
+
profile_label_improvements_pcf_label_in_post_enabled: true,
|
|
37
|
+
responsive_web_profile_redirect_enabled: false,
|
|
38
|
+
rweb_tipjar_consumption_enabled: false,
|
|
39
|
+
verified_phone_label_enabled: false,
|
|
40
|
+
premium_content_api_read_enabled: false,
|
|
41
|
+
communities_web_enable_tweet_community_results_fetch: true,
|
|
42
|
+
c9s_tweet_anatomy_moderator_badge_enabled: true,
|
|
43
|
+
responsive_web_grok_analyze_button_fetch_trends_enabled: false,
|
|
44
|
+
responsive_web_grok_analyze_post_followups_enabled: true,
|
|
45
|
+
responsive_web_jetfuel_frame: true,
|
|
46
|
+
responsive_web_grok_share_attachment_enabled: true,
|
|
47
|
+
responsive_web_grok_annotations_enabled: false,
|
|
48
|
+
articles_preview_enabled: true,
|
|
49
|
+
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
|
|
50
|
+
responsive_web_edit_tweet_api_enabled: true,
|
|
51
|
+
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
|
|
52
|
+
view_counts_everywhere_api_enabled: true,
|
|
53
|
+
longform_notetweets_consumption_enabled: true,
|
|
54
|
+
responsive_web_twitter_article_tweet_consumption_enabled: true,
|
|
55
|
+
tweet_awards_web_tipping_enabled: false,
|
|
56
|
+
responsive_web_grok_show_grok_translated_post: false,
|
|
57
|
+
responsive_web_grok_analysis_button_from_backend: true,
|
|
58
|
+
post_ctas_fetch_enabled: true,
|
|
59
|
+
creator_subscriptions_quote_tweet_preview_enabled: false,
|
|
60
|
+
freedom_of_speech_not_reach_fetch_enabled: true,
|
|
61
|
+
standardized_nudges_misinfo: true,
|
|
62
|
+
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
|
|
63
|
+
longform_notetweets_rich_text_read_enabled: true,
|
|
64
|
+
longform_notetweets_inline_media_enabled: true,
|
|
65
|
+
responsive_web_grok_image_annotation_enabled: true,
|
|
66
|
+
responsive_web_grok_imagine_annotation_enabled: true,
|
|
67
|
+
responsive_web_graphql_timeline_navigation_enabled: true,
|
|
68
|
+
responsive_web_grok_community_note_auto_translation_is_enabled: false,
|
|
69
|
+
responsive_web_enhance_cards_enabled: false,
|
|
70
|
+
}),
|
|
71
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
72
|
+
},
|
|
73
|
+
paramsSerializer: { encode: encodeURIComponent },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
package/src/requests/Tweet.ts
CHANGED
|
@@ -287,6 +287,65 @@ export class TweetRequests {
|
|
|
287
287
|
};
|
|
288
288
|
}
|
|
289
289
|
|
|
290
|
+
/**
|
|
291
|
+
* @param args - The configuration object for the long-form tweet to be posted (X Premium only).
|
|
292
|
+
*/
|
|
293
|
+
public static postNote(args: INewTweet): AxiosRequestConfig {
|
|
294
|
+
// Parsing the args
|
|
295
|
+
const parsedArgs = new NewTweet(args);
|
|
296
|
+
|
|
297
|
+
return {
|
|
298
|
+
method: 'post',
|
|
299
|
+
url: 'https://x.com/i/api/graphql/_eeuQKX1-VyRP_ROM-GN7g/CreateNoteTweet',
|
|
300
|
+
data: {
|
|
301
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
302
|
+
variables: {
|
|
303
|
+
tweet_text: parsedArgs.text,
|
|
304
|
+
media: parsedArgs.media ? new MediaVariable(parsedArgs.media) : undefined,
|
|
305
|
+
semantic_annotation_ids: [],
|
|
306
|
+
disallowed_reply_options: null,
|
|
307
|
+
},
|
|
308
|
+
features: {
|
|
309
|
+
premium_content_api_read_enabled: false,
|
|
310
|
+
communities_web_enable_tweet_community_results_fetch: true,
|
|
311
|
+
c9s_tweet_anatomy_moderator_badge_enabled: true,
|
|
312
|
+
responsive_web_grok_analyze_button_fetch_trends_enabled: false,
|
|
313
|
+
responsive_web_grok_analyze_post_followups_enabled: true,
|
|
314
|
+
responsive_web_jetfuel_frame: true,
|
|
315
|
+
responsive_web_grok_share_attachment_enabled: true,
|
|
316
|
+
responsive_web_grok_annotations_enabled: true,
|
|
317
|
+
responsive_web_edit_tweet_api_enabled: true,
|
|
318
|
+
graphql_is_translatable_rweb_tweet_is_translatable_enabled: true,
|
|
319
|
+
view_counts_everywhere_api_enabled: true,
|
|
320
|
+
longform_notetweets_consumption_enabled: true,
|
|
321
|
+
responsive_web_twitter_article_tweet_consumption_enabled: true,
|
|
322
|
+
tweet_awards_web_tipping_enabled: false,
|
|
323
|
+
content_disclosure_indicator_enabled: true,
|
|
324
|
+
content_disclosure_ai_generated_indicator_enabled: true,
|
|
325
|
+
responsive_web_grok_show_grok_translated_post: true,
|
|
326
|
+
responsive_web_grok_analysis_button_from_backend: true,
|
|
327
|
+
post_ctas_fetch_enabled: true,
|
|
328
|
+
longform_notetweets_rich_text_read_enabled: true,
|
|
329
|
+
longform_notetweets_inline_media_enabled: false,
|
|
330
|
+
profile_label_improvements_pcf_label_in_post_enabled: true,
|
|
331
|
+
responsive_web_profile_redirect_enabled: false,
|
|
332
|
+
rweb_tipjar_consumption_enabled: false,
|
|
333
|
+
verified_phone_label_enabled: false,
|
|
334
|
+
articles_preview_enabled: true,
|
|
335
|
+
responsive_web_grok_community_note_auto_translation_is_enabled: false,
|
|
336
|
+
responsive_web_graphql_skip_user_profile_image_extensions_enabled: false,
|
|
337
|
+
freedom_of_speech_not_reach_fetch_enabled: true,
|
|
338
|
+
standardized_nudges_misinfo: true,
|
|
339
|
+
tweet_with_visibility_results_prefer_gql_limited_actions_policy_enabled: true,
|
|
340
|
+
responsive_web_grok_image_annotation_enabled: true,
|
|
341
|
+
responsive_web_grok_imagine_annotation_enabled: true,
|
|
342
|
+
responsive_web_graphql_timeline_navigation_enabled: true,
|
|
343
|
+
responsive_web_enhance_cards_enabled: false,
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
290
349
|
/**
|
|
291
350
|
* @param id - The id of the tweet whose replies are to be fetched.
|
|
292
351
|
* @param cursor - The cursor to the batch of replies to fetch.
|
package/src/requests/User.ts
CHANGED
|
@@ -11,6 +11,20 @@ import { IProfileUpdateOptions } from '../types/args/ProfileArgs';
|
|
|
11
11
|
* @public
|
|
12
12
|
*/
|
|
13
13
|
export class UserRequests {
|
|
14
|
+
/**
|
|
15
|
+
* @param userName - The username of the user whose about profile is to be fetched.
|
|
16
|
+
*/
|
|
17
|
+
public static aboutByUsername(userName: string): AxiosRequestConfig {
|
|
18
|
+
return {
|
|
19
|
+
method: 'get',
|
|
20
|
+
url: 'https://x.com/i/api/graphql/zs_jFPFT78rBpXv9Z3U2YQ/AboutAccountQuery',
|
|
21
|
+
params: {
|
|
22
|
+
variables: JSON.stringify({ screenName: userName }),
|
|
23
|
+
},
|
|
24
|
+
paramsSerializer: { encode: encodeURIComponent },
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
14
28
|
/**
|
|
15
29
|
* @param id - The id of the user whose affiliates are to be fetched.
|
|
16
30
|
* @param count - The number of affiliates to fetch. Only works as a lower limit when used with a cursor.
|
|
@@ -311,6 +325,39 @@ export class UserRequests {
|
|
|
311
325
|
};
|
|
312
326
|
}
|
|
313
327
|
|
|
328
|
+
/**
|
|
329
|
+
* @param currentPassword - The current password.
|
|
330
|
+
* @param newPassword - The new password.
|
|
331
|
+
*/
|
|
332
|
+
public static changePassword(currentPassword: string, newPassword: string): AxiosRequestConfig {
|
|
333
|
+
return {
|
|
334
|
+
method: 'post',
|
|
335
|
+
url: 'https://x.com/i/api/i/account/change_password.json',
|
|
336
|
+
data: qs.stringify({
|
|
337
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
338
|
+
current_password: currentPassword,
|
|
339
|
+
password: newPassword,
|
|
340
|
+
password_confirmation: newPassword,
|
|
341
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
342
|
+
}),
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
/**
|
|
347
|
+
* @param newUsername - The new username to set.
|
|
348
|
+
*/
|
|
349
|
+
public static changeUsername(newUsername: string): AxiosRequestConfig {
|
|
350
|
+
return {
|
|
351
|
+
method: 'post',
|
|
352
|
+
url: 'https://x.com/i/api/1.1/account/settings.json',
|
|
353
|
+
data: qs.stringify({
|
|
354
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
355
|
+
screen_name: newUsername,
|
|
356
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
357
|
+
}),
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
314
361
|
/**
|
|
315
362
|
* @param id - The id of the user whose details are to be fetched.
|
|
316
363
|
*/
|
|
@@ -1209,4 +1256,26 @@ export class UserRequests {
|
|
|
1209
1256
|
}),
|
|
1210
1257
|
};
|
|
1211
1258
|
}
|
|
1259
|
+
|
|
1260
|
+
/**
|
|
1261
|
+
* @param bannerBase64 - The base64-encoded banner image data.
|
|
1262
|
+
*/
|
|
1263
|
+
public static updateProfileBanner(bannerBase64: string): AxiosRequestConfig {
|
|
1264
|
+
return {
|
|
1265
|
+
method: 'post',
|
|
1266
|
+
url: 'https://x.com/i/api/1.1/account/update_profile_banner.json',
|
|
1267
|
+
data: qs.stringify({ banner: bannerBase64 }),
|
|
1268
|
+
};
|
|
1269
|
+
}
|
|
1270
|
+
|
|
1271
|
+
/**
|
|
1272
|
+
* @param imageBase64 - The base64-encoded image data.
|
|
1273
|
+
*/
|
|
1274
|
+
public static updateProfileImage(imageBase64: string): AxiosRequestConfig {
|
|
1275
|
+
return {
|
|
1276
|
+
method: 'post',
|
|
1277
|
+
url: 'https://x.com/i/api/1.1/account/update_profile_image.json',
|
|
1278
|
+
data: qs.stringify({ image: imageBase64 }),
|
|
1279
|
+
};
|
|
1280
|
+
}
|
|
1212
1281
|
}
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
-
import axios from 'axios';
|
|
1
|
+
import axios, { AxiosResponse } from 'axios';
|
|
2
|
+
|
|
3
|
+
import { Cookie } from 'cookiejar';
|
|
2
4
|
|
|
3
5
|
import { ApiErrors } from '../../enums/Api';
|
|
4
6
|
import { AuthCredential } from '../../models/auth/AuthCredential';
|
|
@@ -20,6 +22,21 @@ export class AuthService {
|
|
|
20
22
|
this._config = config;
|
|
21
23
|
}
|
|
22
24
|
|
|
25
|
+
/**
|
|
26
|
+
* Splits the cookie header into a list of key=value pairs.
|
|
27
|
+
*
|
|
28
|
+
* @param cookieHeader - The value of the cookie header.
|
|
29
|
+
*
|
|
30
|
+
* @returns The list of key=value pairs in the cookies.
|
|
31
|
+
*/
|
|
32
|
+
private static _splitCookieHeader(cookieHeader: string | string[]): string[] {
|
|
33
|
+
if (Array.isArray(cookieHeader)) {
|
|
34
|
+
return cookieHeader;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
return cookieHeader.split(/,(?=\s*[^;,]+=)/g);
|
|
38
|
+
}
|
|
39
|
+
|
|
23
40
|
/**
|
|
24
41
|
* Decodes the encoded cookie string.
|
|
25
42
|
*
|
|
@@ -46,6 +63,91 @@ export class AuthService {
|
|
|
46
63
|
return encodedCookies;
|
|
47
64
|
}
|
|
48
65
|
|
|
66
|
+
/**
|
|
67
|
+
* Gets a new API key from an HTTP response.
|
|
68
|
+
*
|
|
69
|
+
* @param response - The HTTP response received.
|
|
70
|
+
* @param config - The current Rettiwt config.
|
|
71
|
+
*
|
|
72
|
+
* @returns The new API key.
|
|
73
|
+
*/
|
|
74
|
+
public static getApiKeyFromReponse(response: AxiosResponse, config?: RettiwtConfig): string | undefined {
|
|
75
|
+
// If new cookies not returned or user not authenticated, terminate
|
|
76
|
+
if (response.headers['set-cookie'] === undefined || config?.apiKey === undefined) {
|
|
77
|
+
return;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/** The collection of required cookie names. */
|
|
81
|
+
const requiredCookieNames = new Set(['auth_token', 'ct0', 'kdt', 'twid']);
|
|
82
|
+
|
|
83
|
+
/** The current cookie string. */
|
|
84
|
+
const currentCookieString = AuthService.decodeCookie(config.apiKey);
|
|
85
|
+
|
|
86
|
+
/** The cookie key=value pairs from the response. */
|
|
87
|
+
const cookies = AuthService._splitCookieHeader(response.headers['set-cookie']);
|
|
88
|
+
|
|
89
|
+
/** The map from cookie key to value. */
|
|
90
|
+
const cookiesMap = new Map<string, string>();
|
|
91
|
+
|
|
92
|
+
for (const cookieEntry of currentCookieString.split(';')) {
|
|
93
|
+
const trimmedEntry = cookieEntry.trim();
|
|
94
|
+
const separatorIndex = trimmedEntry.indexOf('=');
|
|
95
|
+
|
|
96
|
+
if (!trimmedEntry || separatorIndex < 1) {
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const key = trimmedEntry.slice(0, separatorIndex).trim();
|
|
101
|
+
const value = trimmedEntry.slice(separatorIndex + 1).trim();
|
|
102
|
+
if (!key || !value || !requiredCookieNames.has(key)) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
cookiesMap.set(key, value);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
let hasUpdate = false;
|
|
110
|
+
for (const cookie of cookies) {
|
|
111
|
+
const cookieValuePair = cookie.split(';', 1)[0]?.trim();
|
|
112
|
+
const separatorIndex = cookieValuePair?.indexOf('=') ?? -1;
|
|
113
|
+
|
|
114
|
+
if (!cookieValuePair || separatorIndex < 1) {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
const key = cookieValuePair.slice(0, separatorIndex).trim();
|
|
119
|
+
const value = cookieValuePair.slice(separatorIndex + 1).trim();
|
|
120
|
+
if (!key || !value || !requiredCookieNames.has(key)) {
|
|
121
|
+
continue;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
cookiesMap.set(key, value);
|
|
125
|
+
hasUpdate = true;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (!hasUpdate || !cookiesMap.has('twid')) {
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
let mergedCookieString = '';
|
|
133
|
+
for (const [key, value] of cookiesMap.entries()) {
|
|
134
|
+
mergedCookieString += `${key}=${value};`;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!mergedCookieString) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
try {
|
|
142
|
+
// Encoding the new cookies into an API key
|
|
143
|
+
const newApiKey = AuthService.encodeCookie(mergedCookieString);
|
|
144
|
+
|
|
145
|
+
return newApiKey;
|
|
146
|
+
} catch {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
49
151
|
/**
|
|
50
152
|
* Gets the user's id from the given API key.
|
|
51
153
|
*
|
|
@@ -71,6 +173,50 @@ export class AuthService {
|
|
|
71
173
|
}
|
|
72
174
|
}
|
|
73
175
|
|
|
176
|
+
/**
|
|
177
|
+
* Fetches a fresh CSRF from Twitter by making a lightweight
|
|
178
|
+
* authenticated request, then rotates the apiKey with the updated cookie.
|
|
179
|
+
*
|
|
180
|
+
* @param config - The current Rettiwt config.
|
|
181
|
+
*/
|
|
182
|
+
public static async refreshCsrfToken(config: RettiwtConfig): Promise<void> {
|
|
183
|
+
// If unauthenticated, skip
|
|
184
|
+
if (config.apiKey === undefined) {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
try {
|
|
189
|
+
const cred = new AuthCredential(
|
|
190
|
+
AuthService.decodeCookie(config.apiKey)
|
|
191
|
+
.split(';')
|
|
192
|
+
.map((item) => new Cookie(item)),
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
const refreshResponse = await axios.get('https://x.com/i/api/1.1/account/verify_credentials.json', {
|
|
196
|
+
headers: {
|
|
197
|
+
...cred.toHeader(),
|
|
198
|
+
authorization:
|
|
199
|
+
'Bearer AAAAAAAAAAAAAAAAAAAAANRILgAAAAAAnNwIzUejRCOuH5E6I8xnZz4puTs%3D1Zv7ttfk8LF81IUq16cHjhLTvJu4FA33AGWWjCpTnA',
|
|
200
|
+
},
|
|
201
|
+
httpAgent: config.httpAgent,
|
|
202
|
+
httpsAgent: config.httpsAgent,
|
|
203
|
+
proxy: config.axiosProxyConfig,
|
|
204
|
+
validateStatus: () => true,
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
// Getting the new API key
|
|
208
|
+
const newApiKey = AuthService.getApiKeyFromReponse(refreshResponse, config);
|
|
209
|
+
|
|
210
|
+
// If new API key is generated, update current API key
|
|
211
|
+
if (newApiKey !== undefined) {
|
|
212
|
+
config.apiKey = newApiKey;
|
|
213
|
+
}
|
|
214
|
+
} catch {
|
|
215
|
+
// If ct0 refresh fails, leave apiKey as-is
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
74
220
|
/**
|
|
75
221
|
* Login to twitter as guest.
|
|
76
222
|
*
|
|
@@ -106,7 +252,9 @@ export class AuthService {
|
|
|
106
252
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
107
253
|
}>('https://api.twitter.com/1.1/guest/activate.json', undefined, {
|
|
108
254
|
headers: cred.toHeader(),
|
|
255
|
+
httpAgent: this._config.httpAgent,
|
|
109
256
|
httpsAgent: this._config.httpsAgent,
|
|
257
|
+
proxy: this._config.axiosProxyConfig,
|
|
110
258
|
})
|
|
111
259
|
.then((res) => {
|
|
112
260
|
cred.guestToken = res.data.guest_token;
|
|
@@ -64,7 +64,7 @@ export class DirectMessageService extends FetcherService {
|
|
|
64
64
|
});
|
|
65
65
|
|
|
66
66
|
// Deserializing response
|
|
67
|
-
const data = Extractors[resource](response);
|
|
67
|
+
const data = Extractors[resource](response.data);
|
|
68
68
|
|
|
69
69
|
return data;
|
|
70
70
|
}
|
|
@@ -139,7 +139,7 @@ export class DirectMessageService extends FetcherService {
|
|
|
139
139
|
});
|
|
140
140
|
|
|
141
141
|
// Deserializing response
|
|
142
|
-
const data = Extractors[resource](response);
|
|
142
|
+
const data = Extractors[resource](response.data);
|
|
143
143
|
|
|
144
144
|
return data;
|
|
145
145
|
}
|
|
@@ -151,7 +151,7 @@ export class DirectMessageService extends FetcherService {
|
|
|
151
151
|
const response = await this.request<IInboxInitialResponse>(resource, {});
|
|
152
152
|
|
|
153
153
|
// Deserializing response
|
|
154
|
-
const data = Extractors[resource](response);
|
|
154
|
+
const data = Extractors[resource](response.data);
|
|
155
155
|
|
|
156
156
|
return data;
|
|
157
157
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import axios, { AxiosError, isAxiosError } from 'axios';
|
|
1
|
+
import axios, { AxiosError, AxiosResponse, isAxiosError } from 'axios';
|
|
2
2
|
import { Cookie } from 'cookiejar';
|
|
3
|
-
import {
|
|
3
|
+
import { parseHTML } from 'linkedom';
|
|
4
4
|
import { ClientTransaction } from 'x-client-transaction-id';
|
|
5
5
|
|
|
6
6
|
import { AllowGuestAuthenticationGroup, FetchResourcesGroup, PostResourcesGroup } from '../../collections/Groups';
|
|
@@ -128,13 +128,13 @@ export class FetcherService {
|
|
|
128
128
|
// Fetch X.com homepage
|
|
129
129
|
const homePageResponse = await axios.get<string>('https://x.com', {
|
|
130
130
|
headers: this.config.headers,
|
|
131
|
-
httpAgent: this.config.
|
|
131
|
+
httpAgent: this.config.httpAgent,
|
|
132
132
|
httpsAgent: this.config.httpsAgent,
|
|
133
|
+
proxy: this.config.axiosProxyConfig,
|
|
133
134
|
});
|
|
134
135
|
|
|
135
136
|
// Parse HTML using linkedom
|
|
136
|
-
let
|
|
137
|
-
let document = dom.window.document;
|
|
137
|
+
let document = parseHTML(homePageResponse.data).document;
|
|
138
138
|
|
|
139
139
|
// Check for migration redirection links
|
|
140
140
|
const migrationRedirectionRegex = new RegExp(
|
|
@@ -151,12 +151,12 @@ export class FetcherService {
|
|
|
151
151
|
if (migrationRedirectionUrl) {
|
|
152
152
|
// Follow redirection URL
|
|
153
153
|
const redirectResponse = await axios.get<string>(migrationRedirectionUrl[0], {
|
|
154
|
-
httpAgent: this.config.
|
|
154
|
+
httpAgent: this.config.httpAgent,
|
|
155
155
|
httpsAgent: this.config.httpsAgent,
|
|
156
|
+
proxy: this.config.axiosProxyConfig,
|
|
156
157
|
});
|
|
157
158
|
|
|
158
|
-
|
|
159
|
-
document = dom.window.document;
|
|
159
|
+
document = parseHTML(redirectResponse.data).document;
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
// Handle migration form if present
|
|
@@ -193,12 +193,12 @@ export class FetcherService {
|
|
|
193
193
|
|
|
194
194
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
195
195
|
},
|
|
196
|
-
httpAgent: this.config.
|
|
196
|
+
httpAgent: this.config.httpAgent,
|
|
197
197
|
httpsAgent: this.config.httpsAgent,
|
|
198
|
+
proxy: this.config.axiosProxyConfig,
|
|
198
199
|
});
|
|
199
200
|
|
|
200
|
-
|
|
201
|
-
document = dom.window.document;
|
|
201
|
+
document = parseHTML(formResponse.data).document;
|
|
202
202
|
}
|
|
203
203
|
|
|
204
204
|
// Return final DOM document
|
|
@@ -254,11 +254,11 @@ export class FetcherService {
|
|
|
254
254
|
* Makes an HTTP request according to the given parameters.
|
|
255
255
|
*
|
|
256
256
|
* @param resource - The requested resource.
|
|
257
|
-
* @param
|
|
257
|
+
* @param args - The args to be used for the request.
|
|
258
258
|
*
|
|
259
259
|
* @typeParam T - The type of the returned response data.
|
|
260
260
|
*
|
|
261
|
-
* @returns The raw
|
|
261
|
+
* @returns The raw Axios response.
|
|
262
262
|
*
|
|
263
263
|
* @example
|
|
264
264
|
*
|
|
@@ -279,7 +279,7 @@ export class FetcherService {
|
|
|
279
279
|
* });
|
|
280
280
|
* ```
|
|
281
281
|
*/
|
|
282
|
-
public async request<T = unknown>(resource: ResourceType, args: IFetchArgs | IPostArgs): Promise<T
|
|
282
|
+
public async request<T = unknown>(resource: ResourceType, args: IFetchArgs | IPostArgs): Promise<AxiosResponse<T>> {
|
|
283
283
|
/** The current retry number. */
|
|
284
284
|
let retry = 0;
|
|
285
285
|
|
|
@@ -307,8 +307,9 @@ export class FetcherService {
|
|
|
307
307
|
...cred.toHeader(),
|
|
308
308
|
...this.config.headers,
|
|
309
309
|
};
|
|
310
|
-
config.httpAgent = this.config.
|
|
310
|
+
config.httpAgent = this.config.httpAgent;
|
|
311
311
|
config.httpsAgent = this.config.httpsAgent;
|
|
312
|
+
config.proxy = this.config.axiosProxyConfig;
|
|
312
313
|
config.timeout = this._timeout;
|
|
313
314
|
|
|
314
315
|
// Using retries for error 404
|
|
@@ -325,7 +326,8 @@ export class FetcherService {
|
|
|
325
326
|
await this._wait();
|
|
326
327
|
|
|
327
328
|
// Getting the response body
|
|
328
|
-
const
|
|
329
|
+
const response = await axios<T>(config);
|
|
330
|
+
const responseData = response.data;
|
|
329
331
|
|
|
330
332
|
// Check for Twitter API errors in response body
|
|
331
333
|
// Type guard to check if response contains errors
|
|
@@ -348,8 +350,13 @@ export class FetcherService {
|
|
|
348
350
|
throw new TwitterError(axiosError);
|
|
349
351
|
}
|
|
350
352
|
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
+
// Calling the request middleware, if configured
|
|
354
|
+
if (this.config.responseMiddleware !== undefined) {
|
|
355
|
+
await this.config.responseMiddleware(response);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Returning the response
|
|
359
|
+
return response;
|
|
353
360
|
} catch (err) {
|
|
354
361
|
// If it's an error 404, retry
|
|
355
362
|
if (isAxiosError(err) && err.status === 404) {
|
|
@@ -59,7 +59,7 @@ export class ListService extends FetcherService {
|
|
|
59
59
|
});
|
|
60
60
|
|
|
61
61
|
// Deserializing response
|
|
62
|
-
const data = Extractors[resource](response);
|
|
62
|
+
const data = Extractors[resource](response.data);
|
|
63
63
|
|
|
64
64
|
return data;
|
|
65
65
|
}
|
|
@@ -100,7 +100,7 @@ export class ListService extends FetcherService {
|
|
|
100
100
|
const response = await this.request<IListDetailsResponse>(resource, { id: id });
|
|
101
101
|
|
|
102
102
|
// Deserializing response
|
|
103
|
-
const data = Extractors[resource](response, id);
|
|
103
|
+
const data = Extractors[resource](response.data, id);
|
|
104
104
|
|
|
105
105
|
return data;
|
|
106
106
|
}
|
|
@@ -145,7 +145,7 @@ export class ListService extends FetcherService {
|
|
|
145
145
|
});
|
|
146
146
|
|
|
147
147
|
// Deserializing response
|
|
148
|
-
const data = Extractors[resource](response);
|
|
148
|
+
const data = Extractors[resource](response.data);
|
|
149
149
|
|
|
150
150
|
return data;
|
|
151
151
|
}
|
|
@@ -186,7 +186,7 @@ export class ListService extends FetcherService {
|
|
|
186
186
|
});
|
|
187
187
|
|
|
188
188
|
// Deserializing response
|
|
189
|
-
const data = Extractors[resource](response);
|
|
189
|
+
const data = Extractors[resource](response.data);
|
|
190
190
|
|
|
191
191
|
return data;
|
|
192
192
|
}
|
|
@@ -231,7 +231,7 @@ export class ListService extends FetcherService {
|
|
|
231
231
|
});
|
|
232
232
|
|
|
233
233
|
// Deserializing response
|
|
234
|
-
const data = Extractors[resource](response);
|
|
234
|
+
const data = Extractors[resource](response.data);
|
|
235
235
|
|
|
236
236
|
// Sorting the tweets by date, from recent to oldest
|
|
237
237
|
data.list.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { Extractors } from '../../collections/Extractors';
|
|
2
|
+
import { ResourceType } from '../../enums/Resource';
|
|
3
|
+
import { Space } from '../../models/data/Space';
|
|
4
|
+
import { RettiwtConfig } from '../../models/RettiwtConfig';
|
|
5
|
+
import { ISpaceDetailsOptions } from '../../types/args/FetchArgs';
|
|
6
|
+
import { IAudioSpaceByIdResponse } from '../../types/raw/space/AudioSpaceById';
|
|
7
|
+
|
|
8
|
+
import { FetcherService } from './FetcherService';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Handles interacting with resources related to spaces.
|
|
12
|
+
*
|
|
13
|
+
* @public
|
|
14
|
+
*/
|
|
15
|
+
export class SpaceService extends FetcherService {
|
|
16
|
+
/**
|
|
17
|
+
* @param config - The config object for configuring the Rettiwt instance.
|
|
18
|
+
*
|
|
19
|
+
* @internal
|
|
20
|
+
*/
|
|
21
|
+
public constructor(config: RettiwtConfig) {
|
|
22
|
+
super(config);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Get the details of a space.
|
|
27
|
+
*
|
|
28
|
+
* @param id - The ID of the target space.
|
|
29
|
+
* @param options - Additional options for the fetch.
|
|
30
|
+
*
|
|
31
|
+
* @returns The details of the space with the given ID.
|
|
32
|
+
*
|
|
33
|
+
* @example
|
|
34
|
+
*
|
|
35
|
+
* ```ts
|
|
36
|
+
* import { Rettiwt } from 'rettiwt-api';
|
|
37
|
+
*
|
|
38
|
+
* const rettiwt = new Rettiwt({ apiKey: API_KEY });
|
|
39
|
+
*
|
|
40
|
+
* rettiwt.space.details('1YqJDNEzvoVKV', { withListeners: true })
|
|
41
|
+
* .then(res => {
|
|
42
|
+
* console.log(res);
|
|
43
|
+
* })
|
|
44
|
+
* .catch(err => {
|
|
45
|
+
* console.log(err);
|
|
46
|
+
* });
|
|
47
|
+
* ```
|
|
48
|
+
*/
|
|
49
|
+
public async details(id: string, options?: ISpaceDetailsOptions): Promise<Space | undefined> {
|
|
50
|
+
const resource = ResourceType.SPACE_DETAILS;
|
|
51
|
+
|
|
52
|
+
// Fetching raw space details
|
|
53
|
+
const response = await this.request<IAudioSpaceByIdResponse>(resource, {
|
|
54
|
+
id: id,
|
|
55
|
+
withReplays: options?.withReplays,
|
|
56
|
+
withListeners: options?.withListeners,
|
|
57
|
+
isMetatagsQuery: options?.isMetatagsQuery,
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Deserializing response
|
|
61
|
+
const data = Extractors[resource](response.data);
|
|
62
|
+
|
|
63
|
+
return data;
|
|
64
|
+
}
|
|
65
|
+
}
|