rettiwt-api 6.3.0-alpha.1 → 7.0.1
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/User.d.ts +1 -1
- package/dist/models/data/User.js +3 -3
- package/dist/models/data/User.js.map +1 -1
- 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/User.d.ts +2 -2
- 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/base/User.d.ts +1 -1
- 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 +5 -3
- 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/User.ts +3 -3
- 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/User.ts +2 -2
- package/src/types/data/UserAbout.ts +87 -0
- package/src/types/raw/base/Space.ts +42 -22
- package/src/types/raw/base/User.ts +1 -1
- 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,161 @@
|
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
|
+
import { LogService } from '../../services/internal/LogService';
|
|
3
|
+
import {
|
|
4
|
+
IUserAbout,
|
|
5
|
+
IUserAboutProfile,
|
|
6
|
+
IUserAboutUsernameChanges,
|
|
7
|
+
IUserAboutVerificationInfo,
|
|
8
|
+
} from '../../types/data/UserAbout';
|
|
9
|
+
import { IUserAboutResponse, IUserAboutResult } from '../../types/raw/user/About';
|
|
10
|
+
|
|
11
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
12
|
+
type IRawUsernameChanges = {
|
|
13
|
+
count?: string;
|
|
14
|
+
last_changed_at_msec?: string;
|
|
15
|
+
};
|
|
16
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* The about profile details of a single user.
|
|
20
|
+
*
|
|
21
|
+
* @public
|
|
22
|
+
*/
|
|
23
|
+
export class UserAbout implements IUserAbout {
|
|
24
|
+
/** The raw about profile details. */
|
|
25
|
+
private readonly _raw: IUserAboutResult;
|
|
26
|
+
|
|
27
|
+
public aboutProfile?: IUserAboutProfile;
|
|
28
|
+
public createdAt: string;
|
|
29
|
+
public fullName: string;
|
|
30
|
+
public id: string;
|
|
31
|
+
public isProtected?: boolean;
|
|
32
|
+
public isVerified: boolean;
|
|
33
|
+
public profileImage: string;
|
|
34
|
+
public profileImageShape?: string;
|
|
35
|
+
public userName: string;
|
|
36
|
+
public verificationInfo?: IUserAboutVerificationInfo;
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param user - The raw about profile details.
|
|
40
|
+
*/
|
|
41
|
+
public constructor(user: IUserAboutResult) {
|
|
42
|
+
this._raw = { ...user };
|
|
43
|
+
|
|
44
|
+
this.id = user.rest_id ?? user.id ?? '';
|
|
45
|
+
this.userName = user.core?.screen_name ?? '';
|
|
46
|
+
this.fullName = user.core?.name ?? '';
|
|
47
|
+
this.createdAt = new Date(user.core?.created_at ?? 0).toISOString();
|
|
48
|
+
this.profileImage = user.avatar?.image_url ?? '';
|
|
49
|
+
this.profileImageShape = user.profile_image_shape;
|
|
50
|
+
this.isVerified = user.is_blue_verified ?? false;
|
|
51
|
+
this.isProtected = user.privacy?.protected;
|
|
52
|
+
this.aboutProfile = UserAbout._buildAboutProfile(user);
|
|
53
|
+
this.verificationInfo = UserAbout._buildVerificationInfo(user);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** The raw about profile details. */
|
|
57
|
+
public get raw(): IUserAboutResult {
|
|
58
|
+
return { ...this._raw };
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
private static _buildAboutProfile(user: IUserAboutResult): IUserAboutProfile | undefined {
|
|
62
|
+
const profile = user.about_profile;
|
|
63
|
+
|
|
64
|
+
if (!profile) {
|
|
65
|
+
return undefined;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const usernameChanges = UserAbout._buildUsernameChanges(profile.username_changes);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
createdCountryAccurate: profile.created_country_accurate,
|
|
72
|
+
accountBasedIn: profile.account_based_in,
|
|
73
|
+
locationAccurate: profile.location_accurate,
|
|
74
|
+
learnMoreUrl: profile.learn_more_url,
|
|
75
|
+
source: profile.source,
|
|
76
|
+
usernameChanges: usernameChanges,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
private static _buildUsernameChanges(changes?: IRawUsernameChanges): IUserAboutUsernameChanges | undefined {
|
|
81
|
+
if (!changes) {
|
|
82
|
+
return undefined;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return {
|
|
86
|
+
count: UserAbout._toNumber(changes.count),
|
|
87
|
+
lastChangedAt: UserAbout._toIsoFromMsec(changes.last_changed_at_msec),
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
private static _buildVerificationInfo(user: IUserAboutResult): IUserAboutVerificationInfo | undefined {
|
|
92
|
+
const info = user.verification_info;
|
|
93
|
+
|
|
94
|
+
if (!info) {
|
|
95
|
+
return undefined;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
isIdentityVerified: info.is_identity_verified,
|
|
100
|
+
verifiedSince: UserAbout._toIsoFromMsec(info.reason?.verified_since_msec),
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
private static _toIsoFromMsec(value?: string | number): string | undefined {
|
|
105
|
+
const parsed = UserAbout._toNumber(value);
|
|
106
|
+
|
|
107
|
+
return parsed === undefined ? undefined : new Date(parsed).toISOString();
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
private static _toNumber(value?: string | number): number | undefined {
|
|
111
|
+
if (value === undefined || value === null) {
|
|
112
|
+
return undefined;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const parsed = typeof value === 'number' ? value : Number(value);
|
|
116
|
+
|
|
117
|
+
return Number.isFinite(parsed) ? parsed : undefined;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Extracts and deserializes a single target user about profile from the given raw response data.
|
|
122
|
+
*
|
|
123
|
+
* @param response - The raw response data.
|
|
124
|
+
*
|
|
125
|
+
* @returns The target deserialized user about profile.
|
|
126
|
+
*/
|
|
127
|
+
public static single(response: NonNullable<unknown>): UserAbout | undefined {
|
|
128
|
+
const result = (response as IUserAboutResponse)?.data?.user_result_by_screen_name?.result;
|
|
129
|
+
|
|
130
|
+
if (!result || result.__typename !== 'User') {
|
|
131
|
+
LogService.log(LogActions.WARNING, {
|
|
132
|
+
action: LogActions.DESERIALIZE,
|
|
133
|
+
message: `User not found, skipping`,
|
|
134
|
+
});
|
|
135
|
+
return undefined;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Logging
|
|
139
|
+
LogService.log(LogActions.DESERIALIZE, { id: result.rest_id ?? result.id });
|
|
140
|
+
|
|
141
|
+
return new UserAbout(result);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* @returns A serializable JSON representation of `this` object.
|
|
146
|
+
*/
|
|
147
|
+
public toJSON(): IUserAbout {
|
|
148
|
+
return {
|
|
149
|
+
id: this.id,
|
|
150
|
+
userName: this.userName,
|
|
151
|
+
fullName: this.fullName,
|
|
152
|
+
createdAt: this.createdAt,
|
|
153
|
+
profileImage: this.profileImage,
|
|
154
|
+
profileImageShape: this.profileImageShape,
|
|
155
|
+
isVerified: this.isVerified,
|
|
156
|
+
isProtected: this.isProtected,
|
|
157
|
+
aboutProfile: this.aboutProfile,
|
|
158
|
+
verificationInfo: this.verificationInfo,
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
@@ -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
|
}
|