@youmind-openlab/rettiwt-api 1.0.3 → 1.0.4
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/.devcontainer/devcontainer.json +20 -0
- package/README.md +326 -256
- package/dist/collections/Extractors.d.ts +9 -2
- package/dist/collections/Extractors.js +8 -1
- package/dist/collections/Extractors.js.map +1 -1
- package/dist/collections/Groups.js +5 -0
- package/dist/collections/Groups.js.map +1 -1
- package/dist/collections/Requests.js +5 -0
- package/dist/collections/Requests.js.map +1 -1
- package/dist/commands/User.js +126 -0
- package/dist/commands/User.js.map +1 -1
- package/dist/enums/Resource.d.ts +6 -1
- package/dist/enums/Resource.js +5 -0
- package/dist/enums/Resource.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.js.map +1 -1
- package/dist/models/args/PostArgs.d.ts +16 -1
- package/dist/models/args/PostArgs.js +44 -1
- package/dist/models/args/PostArgs.js.map +1 -1
- 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 +25 -0
- package/dist/requests/User.js +59 -0
- package/dist/requests/User.js.map +1 -1
- package/dist/services/public/FetcherService.d.ts +14 -2
- package/dist/services/public/FetcherService.js +21 -6
- package/dist/services/public/FetcherService.js.map +1 -1
- package/dist/services/public/TweetService.js +9 -6
- package/dist/services/public/TweetService.js.map +1 -1
- package/dist/services/public/UserService.d.ts +45 -0
- package/dist/services/public/UserService.js +211 -0
- package/dist/services/public/UserService.js.map +1 -1
- package/dist/types/args/PostArgs.d.ts +44 -1
- package/dist/types/raw/tweet/Post.d.ts +16 -1
- 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 +4 -2
- package/src/collections/Extractors.ts +15 -3
- package/src/collections/Groups.ts +5 -0
- package/src/collections/Requests.ts +6 -0
- package/src/commands/User.ts +146 -0
- package/src/enums/Resource.ts +5 -0
- package/src/index.ts +2 -0
- package/src/models/args/PostArgs.ts +49 -1
- package/src/requests/Tweet.ts +59 -0
- package/src/requests/User.ts +63 -0
- package/src/services/public/FetcherService.ts +27 -7
- package/src/services/public/TweetService.ts +10 -7
- package/src/services/public/UserService.ts +265 -0
- package/src/types/args/PostArgs.ts +50 -1
- package/src/types/raw/tweet/Post.ts +19 -1
- 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/.claude/settings.local.json +0 -9
package/src/commands/User.ts
CHANGED
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { readFileSync } from 'fs';
|
|
2
|
+
import { createInterface } from 'readline/promises';
|
|
3
|
+
import { Writable } from 'stream';
|
|
4
|
+
|
|
1
5
|
import { Command, createCommand } from 'commander';
|
|
2
6
|
|
|
3
7
|
import { RawAnalyticsGranularity, RawAnalyticsMetric } from '../enums/raw/Analytics';
|
|
@@ -353,9 +357,144 @@ function createUserCommand(rettiwt: Rettiwt): Command {
|
|
|
353
357
|
}
|
|
354
358
|
});
|
|
355
359
|
|
|
360
|
+
// Change Password
|
|
361
|
+
user.command('change-password')
|
|
362
|
+
.description('Change your account password')
|
|
363
|
+
.option('--show-new-key', 'Include rotated apiKey in the output')
|
|
364
|
+
.action(async (options?: UserPasswordChangeOptions) => {
|
|
365
|
+
try {
|
|
366
|
+
const initialApiKey = rettiwt.apiKey;
|
|
367
|
+
const currentPassword = await promptHidden('Current password: ');
|
|
368
|
+
const newPassword = await promptHidden('New password: ');
|
|
369
|
+
const confirmPassword = await promptHidden('Confirm new password: ');
|
|
370
|
+
|
|
371
|
+
if (newPassword !== confirmPassword) {
|
|
372
|
+
throw new Error('New password confirmation does not match');
|
|
373
|
+
}
|
|
374
|
+
if (newPassword === currentPassword) {
|
|
375
|
+
throw new Error('New password must be different from current password');
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
const result = await rettiwt.user.changePassword(currentPassword, newPassword);
|
|
379
|
+
const apiKeyUpdated = initialApiKey !== rettiwt.apiKey;
|
|
380
|
+
const response = {
|
|
381
|
+
success: result,
|
|
382
|
+
apiKeyUpdated: result ? apiKeyUpdated : false,
|
|
383
|
+
...(options?.showNewKey ? { apiKey: rettiwt.apiKey } : {}),
|
|
384
|
+
};
|
|
385
|
+
|
|
386
|
+
output(response);
|
|
387
|
+
} catch (error) {
|
|
388
|
+
output(error);
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
|
|
392
|
+
// Change Username
|
|
393
|
+
user.command('change-username')
|
|
394
|
+
.description('Change your username')
|
|
395
|
+
.argument('<username>', 'The new username (with or without @)')
|
|
396
|
+
.action(async (username: string) => {
|
|
397
|
+
try {
|
|
398
|
+
const result = await rettiwt.user.changeUsername(username);
|
|
399
|
+
output(result);
|
|
400
|
+
} catch (error) {
|
|
401
|
+
output(error);
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
|
|
405
|
+
// Update Profile Banner
|
|
406
|
+
user.command('update-profile-banner')
|
|
407
|
+
.description('Update your profile banner from an image file path')
|
|
408
|
+
.argument('<path>', 'The path to the banner image file')
|
|
409
|
+
.action(async (path: string) => {
|
|
410
|
+
try {
|
|
411
|
+
const result = await rettiwt.user.updateProfileBanner(fileToBase64(path));
|
|
412
|
+
output(result);
|
|
413
|
+
} catch (error) {
|
|
414
|
+
output(error);
|
|
415
|
+
}
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
// Update Profile Image
|
|
419
|
+
user.command('update-profile-image')
|
|
420
|
+
.description('Update your profile image from an image file path')
|
|
421
|
+
.argument('<path>', 'The path to the profile image file')
|
|
422
|
+
.action(async (path: string) => {
|
|
423
|
+
try {
|
|
424
|
+
const result = await rettiwt.user.updateProfileImage(fileToBase64(path));
|
|
425
|
+
output(result);
|
|
426
|
+
} catch (error) {
|
|
427
|
+
output(error);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
|
|
356
431
|
return user;
|
|
357
432
|
}
|
|
358
433
|
|
|
434
|
+
/**
|
|
435
|
+
* Reads a file and returns its base64 representation.
|
|
436
|
+
*
|
|
437
|
+
* @param path - The path to the file.
|
|
438
|
+
* @returns The base64 representation of the file contents.
|
|
439
|
+
*/
|
|
440
|
+
function fileToBase64(path: string): string {
|
|
441
|
+
if (path.trim().length === 0) {
|
|
442
|
+
throw new Error('File path cannot be empty');
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
try {
|
|
446
|
+
return readFileSync(path).toString('base64');
|
|
447
|
+
} catch (error) {
|
|
448
|
+
if (error instanceof Error) {
|
|
449
|
+
throw new Error(`Could not read file at '${path}': ${error.message}`);
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
throw new Error(`Could not read file at '${path}'`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
/**
|
|
457
|
+
* Prompts user for hidden input without echoing typed characters.
|
|
458
|
+
*
|
|
459
|
+
* @param query - The prompt text.
|
|
460
|
+
* @returns The provided value.
|
|
461
|
+
*/
|
|
462
|
+
async function promptHidden(query: string): Promise<string> {
|
|
463
|
+
if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
464
|
+
throw new Error('Password prompt requires an interactive terminal');
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
let queryShown = false;
|
|
468
|
+
|
|
469
|
+
const mutedOutput = new Writable({
|
|
470
|
+
write(chunk: Buffer | string, encoding: BufferEncoding, callback: (error?: Error | null) => void): void {
|
|
471
|
+
const text = chunk.toString();
|
|
472
|
+
|
|
473
|
+
if (!queryShown) {
|
|
474
|
+
process.stdout.write(text);
|
|
475
|
+
queryShown = text.includes(query);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
callback();
|
|
479
|
+
},
|
|
480
|
+
});
|
|
481
|
+
|
|
482
|
+
const input = createInterface({
|
|
483
|
+
input: process.stdin,
|
|
484
|
+
output: mutedOutput,
|
|
485
|
+
terminal: true,
|
|
486
|
+
});
|
|
487
|
+
|
|
488
|
+
try {
|
|
489
|
+
const value = await input.question(query);
|
|
490
|
+
process.stdout.write('\n');
|
|
491
|
+
|
|
492
|
+
return value;
|
|
493
|
+
} finally {
|
|
494
|
+
input.close();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
359
498
|
/**
|
|
360
499
|
* The options for fetching user analytics.
|
|
361
500
|
*/
|
|
@@ -377,4 +516,11 @@ type UserProfileUpdateOptions = {
|
|
|
377
516
|
description?: string;
|
|
378
517
|
};
|
|
379
518
|
|
|
519
|
+
/**
|
|
520
|
+
* The options for changing account password.
|
|
521
|
+
*/
|
|
522
|
+
type UserPasswordChangeOptions = {
|
|
523
|
+
showNewKey?: boolean;
|
|
524
|
+
};
|
|
525
|
+
|
|
380
526
|
export default createUserCommand;
|
package/src/enums/Resource.ts
CHANGED
|
@@ -33,6 +33,7 @@ export enum ResourceType {
|
|
|
33
33
|
TWEET_LIKE = 'TWEET_LIKE',
|
|
34
34
|
TWEET_LIKERS = 'TWEET_LIKERS',
|
|
35
35
|
TWEET_POST = 'TWEET_POST',
|
|
36
|
+
TWEET_POST_NOTE = 'TWEET_POST_NOTE',
|
|
36
37
|
TWEET_REPLIES = 'TWEET_REPLIES',
|
|
37
38
|
TWEET_RETWEET = 'TWEET_RETWEET',
|
|
38
39
|
TWEET_RETWEETERS = 'TWEET_RETWEETERS',
|
|
@@ -70,4 +71,8 @@ export enum ResourceType {
|
|
|
70
71
|
USER_TIMELINE_AND_REPLIES = 'USER_TIMELINE_AND_REPLIES',
|
|
71
72
|
USER_UNFOLLOW = 'USER_UNFOLLOW',
|
|
72
73
|
USER_PROFILE_UPDATE = 'USER_PROFILE_UPDATE',
|
|
74
|
+
USER_PROFILE_IMAGE_UPDATE = 'USER_PROFILE_IMAGE_UPDATE',
|
|
75
|
+
USER_PROFILE_BANNER_UPDATE = 'USER_PROFILE_BANNER_UPDATE',
|
|
76
|
+
USER_USERNAME_CHANGE = 'USER_USERNAME_CHANGE',
|
|
77
|
+
USER_PASSWORD_CHANGE = 'USER_PASSWORD_CHANGE',
|
|
73
78
|
}
|
package/src/index.ts
CHANGED
|
@@ -128,6 +128,8 @@ export { IUserTweetsResponse as IRawUserTweetsResponse } from './types/raw/user/
|
|
|
128
128
|
export { IUserTweetsAndRepliesResponse as IRawUserTweetsAndRepliesResponse } from './types/raw/user/TweetsAndReplies';
|
|
129
129
|
export { IUserUnfollowResponse as IRawUserUnfollowResponse } from './types/raw/user/Unfollow';
|
|
130
130
|
export { IUserProfileUpdateResponse as IRawUserProfileUpdateResponse } from './types/raw/user/ProfileUpdate';
|
|
131
|
+
export { IUserSettingsResponse as IRawUserSettingsResponse } from './types/raw/user/Settings';
|
|
132
|
+
export { IUserChangePasswordResponse as IRawUserChangePasswordResponse } from './types/raw/user/ChangePassword';
|
|
131
133
|
export * from './types/ErrorHandler';
|
|
132
134
|
export * from './types/RettiwtConfig';
|
|
133
135
|
export { IConversationTimelineResponse as IRawConversationTimelineResponse } from './types/raw/dm/Conversation';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INewTweet, INewTweetMedia, IPostArgs, IUploadArgs } from '../../types/args/PostArgs';
|
|
1
|
+
import { IChangePasswordArgs, INewTweet, INewTweetMedia, IPostArgs, IUploadArgs } from '../../types/args/PostArgs';
|
|
2
2
|
|
|
3
3
|
import { ProfileUpdateOptions } from './ProfileArgs';
|
|
4
4
|
|
|
@@ -8,12 +8,16 @@ import { ProfileUpdateOptions } from './ProfileArgs';
|
|
|
8
8
|
* @public
|
|
9
9
|
*/
|
|
10
10
|
export class PostArgs implements IPostArgs {
|
|
11
|
+
public changePassword?: ChangePasswordArgs;
|
|
11
12
|
public conversationId?: string;
|
|
12
13
|
public id?: string;
|
|
14
|
+
public profileBanner?: string;
|
|
15
|
+
public profileImage?: string;
|
|
13
16
|
public profileOptions?: ProfileUpdateOptions;
|
|
14
17
|
public tweet?: NewTweet;
|
|
15
18
|
public upload?: UploadArgs;
|
|
16
19
|
public userId?: string;
|
|
20
|
+
public username?: string;
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* @param resource - The resource to be posted.
|
|
@@ -24,8 +28,28 @@ export class PostArgs implements IPostArgs {
|
|
|
24
28
|
this.tweet = args.tweet ? new NewTweet(args.tweet) : undefined;
|
|
25
29
|
this.upload = args.upload ? new UploadArgs(args.upload) : undefined;
|
|
26
30
|
this.userId = args.userId;
|
|
31
|
+
this.username = PostArgs._validateNonEmptyString(args.username, 'Username');
|
|
27
32
|
this.conversationId = args.conversationId;
|
|
28
33
|
this.profileOptions = args.profileOptions ? new ProfileUpdateOptions(args.profileOptions) : undefined;
|
|
34
|
+
this.profileImage = PostArgs._validateNonEmptyString(args.profileImage, 'Profile image');
|
|
35
|
+
this.profileBanner = PostArgs._validateNonEmptyString(args.profileBanner, 'Profile banner');
|
|
36
|
+
this.changePassword = args.changePassword ? new ChangePasswordArgs(args.changePassword) : undefined;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
private static _validateNonEmptyString(value: unknown, fieldName: string): string | undefined {
|
|
40
|
+
if (value === undefined) {
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (typeof value !== 'string') {
|
|
45
|
+
throw new Error(`${fieldName} must be a string`);
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (value.trim().length === 0) {
|
|
49
|
+
throw new Error(`${fieldName} cannot be empty`);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return value;
|
|
29
53
|
}
|
|
30
54
|
}
|
|
31
55
|
|
|
@@ -91,3 +115,27 @@ export class UploadArgs implements IUploadArgs {
|
|
|
91
115
|
this.id = args.id;
|
|
92
116
|
}
|
|
93
117
|
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Validated password change arguments.
|
|
121
|
+
*
|
|
122
|
+
* @public
|
|
123
|
+
*/
|
|
124
|
+
export class ChangePasswordArgs implements IChangePasswordArgs {
|
|
125
|
+
public currentPassword: string;
|
|
126
|
+
public newPassword: string;
|
|
127
|
+
|
|
128
|
+
public constructor(args: IChangePasswordArgs) {
|
|
129
|
+
if (!args.currentPassword || args.currentPassword.trim().length === 0) {
|
|
130
|
+
throw new Error('Current password cannot be empty');
|
|
131
|
+
}
|
|
132
|
+
if (!args.newPassword || args.newPassword.trim().length === 0) {
|
|
133
|
+
throw new Error('New password cannot be empty');
|
|
134
|
+
}
|
|
135
|
+
if (args.newPassword.length < 8) {
|
|
136
|
+
throw new Error('New password must be at least 8 characters long');
|
|
137
|
+
}
|
|
138
|
+
this.currentPassword = args.currentPassword;
|
|
139
|
+
this.newPassword = args.newPassword;
|
|
140
|
+
}
|
|
141
|
+
}
|
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
|
@@ -326,6 +326,43 @@ export class UserRequests {
|
|
|
326
326
|
};
|
|
327
327
|
}
|
|
328
328
|
|
|
329
|
+
/**
|
|
330
|
+
* Returns the request to change the user's password.
|
|
331
|
+
*
|
|
332
|
+
* @param currentPassword - The current password.
|
|
333
|
+
* @param newPassword - The new password.
|
|
334
|
+
*/
|
|
335
|
+
public static changePassword(currentPassword: string, newPassword: string): AxiosRequestConfig {
|
|
336
|
+
return {
|
|
337
|
+
method: 'post',
|
|
338
|
+
url: 'https://x.com/i/api/i/account/change_password.json',
|
|
339
|
+
data: qs.stringify({
|
|
340
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
341
|
+
current_password: currentPassword,
|
|
342
|
+
password: newPassword,
|
|
343
|
+
password_confirmation: newPassword,
|
|
344
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
345
|
+
}),
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/**
|
|
350
|
+
* Returns the request to change the user's username (screen_name).
|
|
351
|
+
*
|
|
352
|
+
* @param newUsername - The new username to set.
|
|
353
|
+
*/
|
|
354
|
+
public static changeUsername(newUsername: string): AxiosRequestConfig {
|
|
355
|
+
return {
|
|
356
|
+
method: 'post',
|
|
357
|
+
url: 'https://x.com/i/api/1.1/account/settings.json',
|
|
358
|
+
data: qs.stringify({
|
|
359
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
360
|
+
screen_name: newUsername,
|
|
361
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
362
|
+
}),
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
329
366
|
/**
|
|
330
367
|
* @param id - The id of the user whose details are to be fetched.
|
|
331
368
|
*/
|
|
@@ -1224,4 +1261,30 @@ export class UserRequests {
|
|
|
1224
1261
|
}),
|
|
1225
1262
|
};
|
|
1226
1263
|
}
|
|
1264
|
+
|
|
1265
|
+
/**
|
|
1266
|
+
* Returns the request to update the user's profile banner.
|
|
1267
|
+
*
|
|
1268
|
+
* @param bannerBase64 - The base64-encoded banner image data.
|
|
1269
|
+
*/
|
|
1270
|
+
public static updateProfileBanner(bannerBase64: string): AxiosRequestConfig {
|
|
1271
|
+
return {
|
|
1272
|
+
method: 'post',
|
|
1273
|
+
url: 'https://x.com/i/api/1.1/account/update_profile_banner.json',
|
|
1274
|
+
data: qs.stringify({ banner: bannerBase64 }),
|
|
1275
|
+
};
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* Returns the request to update the user's profile image.
|
|
1280
|
+
*
|
|
1281
|
+
* @param imageBase64 - The base64-encoded image data.
|
|
1282
|
+
*/
|
|
1283
|
+
public static updateProfileImage(imageBase64: string): AxiosRequestConfig {
|
|
1284
|
+
return {
|
|
1285
|
+
method: 'post',
|
|
1286
|
+
url: 'https://x.com/i/api/1.1/account/update_profile_image.json',
|
|
1287
|
+
data: qs.stringify({ image: imageBase64 }),
|
|
1288
|
+
};
|
|
1289
|
+
}
|
|
1227
1290
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import axios, { AxiosError, isAxiosError } from 'axios';
|
|
1
|
+
import axios, { AxiosError, AxiosResponse, isAxiosError } from 'axios';
|
|
2
2
|
import { Cookie } from 'cookiejar';
|
|
3
3
|
import { JSDOM } from 'jsdom';
|
|
4
4
|
import { ClientTransaction } from 'x-client-transaction-id';
|
|
@@ -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 HTTP response received.
|
|
262
262
|
*
|
|
263
263
|
* @example
|
|
264
264
|
*
|
|
@@ -279,7 +279,10 @@ export class FetcherService {
|
|
|
279
279
|
* });
|
|
280
280
|
* ```
|
|
281
281
|
*/
|
|
282
|
-
|
|
282
|
+
protected async requestWithResponse<T = unknown>(
|
|
283
|
+
resource: ResourceType,
|
|
284
|
+
args: IFetchArgs | IPostArgs,
|
|
285
|
+
): Promise<AxiosResponse<T>> {
|
|
283
286
|
/** The current retry number. */
|
|
284
287
|
let retry = 0;
|
|
285
288
|
|
|
@@ -325,7 +328,8 @@ export class FetcherService {
|
|
|
325
328
|
await this._wait();
|
|
326
329
|
|
|
327
330
|
// Getting the response body
|
|
328
|
-
const
|
|
331
|
+
const response = await axios<T>(config);
|
|
332
|
+
const responseData = response.data;
|
|
329
333
|
|
|
330
334
|
// Check for Twitter API errors in response body
|
|
331
335
|
// Type guard to check if response contains errors
|
|
@@ -348,8 +352,8 @@ export class FetcherService {
|
|
|
348
352
|
throw new TwitterError(axiosError);
|
|
349
353
|
}
|
|
350
354
|
|
|
351
|
-
// Returning the
|
|
352
|
-
return
|
|
355
|
+
// Returning the response
|
|
356
|
+
return response;
|
|
353
357
|
} catch (err) {
|
|
354
358
|
// If it's an error 404, retry
|
|
355
359
|
if (isAxiosError(err) && err.status === 404) {
|
|
@@ -369,4 +373,20 @@ export class FetcherService {
|
|
|
369
373
|
/** If request not successful even after retries, throw the error */
|
|
370
374
|
throw error;
|
|
371
375
|
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Makes an HTTP request according to the given parameters.
|
|
379
|
+
*
|
|
380
|
+
* @param resource - The requested resource.
|
|
381
|
+
* @param args - The args to be used for the request.
|
|
382
|
+
*
|
|
383
|
+
* @typeParam T - The type of the returned response data.
|
|
384
|
+
*
|
|
385
|
+
* @returns The raw data response received.
|
|
386
|
+
*/
|
|
387
|
+
public async request<T = unknown>(resource: ResourceType, args: IFetchArgs | IPostArgs): Promise<T> {
|
|
388
|
+
const response = await this.requestWithResponse<T>(resource, args);
|
|
389
|
+
|
|
390
|
+
return response.data;
|
|
391
|
+
}
|
|
372
392
|
}
|
|
@@ -17,7 +17,7 @@ import { ITweetDetailsResponse } from '../../types/raw/tweet/Details';
|
|
|
17
17
|
import { ITweetDetailsBulkResponse } from '../../types/raw/tweet/DetailsBulk';
|
|
18
18
|
import { ITweetLikeResponse } from '../../types/raw/tweet/Like';
|
|
19
19
|
import { ITweetLikersResponse } from '../../types/raw/tweet/Likers';
|
|
20
|
-
import { ITweetPostResponse } from '../../types/raw/tweet/Post';
|
|
20
|
+
import { ITweetPostNoteResponse, ITweetPostResponse } from '../../types/raw/tweet/Post';
|
|
21
21
|
import { ITweetRepliesResponse } from '../../types/raw/tweet/Replies';
|
|
22
22
|
import { ITweetRetweetResponse } from '../../types/raw/tweet/Retweet';
|
|
23
23
|
import { ITweetRetweetersResponse } from '../../types/raw/tweet/Retweeters';
|
|
@@ -342,15 +342,18 @@ export class TweetService extends FetcherService {
|
|
|
342
342
|
* ```
|
|
343
343
|
*/
|
|
344
344
|
public async post(options: INewTweet): Promise<string | undefined> {
|
|
345
|
-
|
|
345
|
+
// Use CreateNoteTweet endpoint for long-form tweets (X Premium, >280 chars)
|
|
346
|
+
if ((options.text?.length ?? 0) > 280) {
|
|
347
|
+
const response = await this.request<ITweetPostNoteResponse>(ResourceType.TWEET_POST_NOTE, {
|
|
348
|
+
tweet: options,
|
|
349
|
+
});
|
|
346
350
|
|
|
347
|
-
|
|
348
|
-
|
|
351
|
+
return Extractors[ResourceType.TWEET_POST_NOTE](response);
|
|
352
|
+
}
|
|
349
353
|
|
|
350
|
-
|
|
351
|
-
const data = Extractors[resource](response);
|
|
354
|
+
const response = await this.request<ITweetPostResponse>(ResourceType.TWEET_POST, { tweet: options });
|
|
352
355
|
|
|
353
|
-
return
|
|
356
|
+
return Extractors[ResourceType.TWEET_POST](response);
|
|
354
357
|
}
|
|
355
358
|
|
|
356
359
|
/**
|