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.
Files changed (130) hide show
  1. package/README.md +81 -31
  2. package/dist/Rettiwt.d.ts +6 -2
  3. package/dist/Rettiwt.js +7 -3
  4. package/dist/Rettiwt.js.map +1 -1
  5. package/dist/cli.js +3 -1
  6. package/dist/cli.js.map +1 -1
  7. package/dist/collections/Extractors.d.ts +15 -2
  8. package/dist/collections/Extractors.js +12 -1
  9. package/dist/collections/Extractors.js.map +1 -1
  10. package/dist/collections/Groups.js +8 -0
  11. package/dist/collections/Groups.js.map +1 -1
  12. package/dist/collections/Requests.js +8 -0
  13. package/dist/collections/Requests.js.map +1 -1
  14. package/dist/commands/Space.d.ts +10 -0
  15. package/dist/commands/Space.js +38 -0
  16. package/dist/commands/Space.js.map +1 -0
  17. package/dist/commands/User.js +139 -0
  18. package/dist/commands/User.js.map +1 -1
  19. package/dist/enums/Resource.d.ts +8 -1
  20. package/dist/enums/Resource.js +8 -0
  21. package/dist/enums/Resource.js.map +1 -1
  22. package/dist/index.d.ts +11 -1
  23. package/dist/index.js +6 -0
  24. package/dist/index.js.map +1 -1
  25. package/dist/models/RettiwtConfig.d.ts +26 -3
  26. package/dist/models/RettiwtConfig.js +68 -3
  27. package/dist/models/RettiwtConfig.js.map +1 -1
  28. package/dist/models/args/FetchArgs.d.ts +3 -0
  29. package/dist/models/args/FetchArgs.js +6 -0
  30. package/dist/models/args/FetchArgs.js.map +1 -1
  31. package/dist/models/args/PostArgs.d.ts +24 -1
  32. package/dist/models/args/PostArgs.js +52 -1
  33. package/dist/models/args/PostArgs.js.map +1 -1
  34. package/dist/models/data/Space.d.ts +70 -0
  35. package/dist/models/data/Space.js +177 -0
  36. package/dist/models/data/Space.js.map +1 -0
  37. package/dist/models/data/UserAbout.d.ts +44 -0
  38. package/dist/models/data/UserAbout.js +129 -0
  39. package/dist/models/data/UserAbout.js.map +1 -0
  40. package/dist/requests/Space.d.ts +15 -0
  41. package/dist/requests/Space.js +74 -0
  42. package/dist/requests/Space.js.map +1 -0
  43. package/dist/requests/Tweet.d.ts +4 -0
  44. package/dist/requests/Tweet.js +57 -0
  45. package/dist/requests/Tweet.js.map +1 -1
  46. package/dist/requests/User.d.ts +21 -0
  47. package/dist/requests/User.js +64 -0
  48. package/dist/requests/User.js.map +1 -1
  49. package/dist/services/internal/AuthService.d.ts +25 -0
  50. package/dist/services/internal/AuthService.js +121 -0
  51. package/dist/services/internal/AuthService.js.map +1 -1
  52. package/dist/services/public/DirectMessageService.js +3 -3
  53. package/dist/services/public/DirectMessageService.js.map +1 -1
  54. package/dist/services/public/FetcherService.d.ts +4 -3
  55. package/dist/services/public/FetcherService.js +22 -16
  56. package/dist/services/public/FetcherService.js.map +1 -1
  57. package/dist/services/public/ListService.js +5 -5
  58. package/dist/services/public/ListService.js.map +1 -1
  59. package/dist/services/public/SpaceService.d.ts +42 -0
  60. package/dist/services/public/SpaceService.js +60 -0
  61. package/dist/services/public/SpaceService.js.map +1 -0
  62. package/dist/services/public/TweetService.js +26 -23
  63. package/dist/services/public/TweetService.js.map +1 -1
  64. package/dist/services/public/UserService.d.ts +79 -0
  65. package/dist/services/public/UserService.js +203 -23
  66. package/dist/services/public/UserService.js.map +1 -1
  67. package/dist/types/RettiwtConfig.d.ts +33 -3
  68. package/dist/types/args/FetchArgs.d.ts +35 -1
  69. package/dist/types/args/PostArgs.d.ts +44 -1
  70. package/dist/types/data/Space.d.ts +89 -0
  71. package/dist/types/data/Space.js +3 -0
  72. package/dist/types/data/Space.js.map +1 -0
  73. package/dist/types/data/UserAbout.d.ts +68 -0
  74. package/dist/types/data/UserAbout.js +3 -0
  75. package/dist/types/data/UserAbout.js.map +1 -0
  76. package/dist/types/raw/base/Space.d.ts +43 -22
  77. package/dist/types/raw/space/AudioSpaceById.d.ts +50 -0
  78. package/dist/types/raw/space/AudioSpaceById.js +4 -0
  79. package/dist/types/raw/space/AudioSpaceById.js.map +1 -0
  80. package/dist/types/raw/space/Details.d.ts +2 -309
  81. package/dist/types/raw/tweet/Post.d.ts +16 -1
  82. package/dist/types/raw/user/About.d.ts +65 -0
  83. package/dist/types/raw/user/About.js +4 -0
  84. package/dist/types/raw/user/About.js.map +1 -0
  85. package/dist/types/raw/user/ChangePassword.d.ts +8 -0
  86. package/dist/types/raw/user/ChangePassword.js +3 -0
  87. package/dist/types/raw/user/ChangePassword.js.map +1 -0
  88. package/dist/types/raw/user/ProfileUpdate.d.ts +1 -0
  89. package/dist/types/raw/user/Settings.d.ts +21 -0
  90. package/dist/types/raw/user/Settings.js +4 -0
  91. package/dist/types/raw/user/Settings.js.map +1 -0
  92. package/package.json +6 -4
  93. package/src/Rettiwt.ts +10 -3
  94. package/src/cli.ts +3 -1
  95. package/src/collections/Extractors.ts +22 -3
  96. package/src/collections/Groups.ts +8 -0
  97. package/src/collections/Requests.ts +11 -0
  98. package/src/commands/Space.ts +46 -0
  99. package/src/commands/User.ts +159 -0
  100. package/src/enums/Resource.ts +9 -0
  101. package/src/index.ts +11 -1
  102. package/src/models/RettiwtConfig.ts +81 -6
  103. package/src/models/args/FetchArgs.ts +6 -0
  104. package/src/models/args/PostArgs.ts +58 -1
  105. package/src/models/data/Space.ts +201 -0
  106. package/src/models/data/UserAbout.ts +161 -0
  107. package/src/requests/Space.ts +76 -0
  108. package/src/requests/Tweet.ts +59 -0
  109. package/src/requests/User.ts +69 -0
  110. package/src/services/internal/AuthService.ts +149 -1
  111. package/src/services/public/DirectMessageService.ts +3 -3
  112. package/src/services/public/FetcherService.ts +25 -18
  113. package/src/services/public/ListService.ts +5 -5
  114. package/src/services/public/SpaceService.ts +65 -0
  115. package/src/services/public/TweetService.ts +27 -24
  116. package/src/services/public/UserService.ts +247 -23
  117. package/src/types/RettiwtConfig.ts +35 -3
  118. package/src/types/args/FetchArgs.ts +41 -1
  119. package/src/types/args/PostArgs.ts +50 -1
  120. package/src/types/data/Space.ts +122 -0
  121. package/src/types/data/UserAbout.ts +87 -0
  122. package/src/types/raw/base/Space.ts +42 -22
  123. package/src/types/raw/space/AudioSpaceById.ts +57 -0
  124. package/src/types/raw/space/Details.ts +3 -352
  125. package/src/types/raw/tweet/Post.ts +19 -1
  126. package/src/types/raw/user/About.ts +77 -0
  127. package/src/types/raw/user/ChangePassword.ts +8 -0
  128. package/src/types/raw/user/ProfileUpdate.ts +1 -0
  129. package/src/types/raw/user/Settings.ts +23 -0
  130. package/tsconfig.json +2 -2
@@ -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';
@@ -80,7 +80,7 @@ export class TweetService extends FetcherService {
80
80
  });
81
81
 
82
82
  // Deserializing response
83
- const data = Extractors[resource](response) ?? false;
83
+ const data = Extractors[resource](response.data) ?? false;
84
84
 
85
85
  return data;
86
86
  }
@@ -146,7 +146,7 @@ export class TweetService extends FetcherService {
146
146
  const response = await this.request<ITweetRepliesResponse>(resource, { id: id });
147
147
 
148
148
  // Deserializing response
149
- const data = Extractors[resource](response, id);
149
+ const data = Extractors[resource](response.data, id);
150
150
 
151
151
  return data as T extends string ? Tweet | undefined : Tweet[];
152
152
  }
@@ -158,7 +158,7 @@ export class TweetService extends FetcherService {
158
158
  const response = await this.request<ITweetDetailsBulkResponse>(resource, { ids: id });
159
159
 
160
160
  // Deserializing response
161
- const data = Extractors[resource](response, id);
161
+ const data = Extractors[resource](response.data, id);
162
162
 
163
163
  return data as T extends string ? Tweet | undefined : Tweet[];
164
164
  }
@@ -170,7 +170,7 @@ export class TweetService extends FetcherService {
170
170
  const response = await this.request<ITweetDetailsResponse>(resource, { id: String(id) });
171
171
 
172
172
  // Deserializing response
173
- const data = Extractors[resource](response, String(id));
173
+ const data = Extractors[resource](response.data, String(id));
174
174
 
175
175
  return data as T extends string ? Tweet | undefined : Tweet[];
176
176
  }
@@ -210,7 +210,7 @@ export class TweetService extends FetcherService {
210
210
  });
211
211
 
212
212
  // Deserializing response
213
- const data = Extractors[resource](response) ?? false;
213
+ const data = Extractors[resource](response.data) ?? false;
214
214
 
215
215
  return data;
216
216
  }
@@ -253,7 +253,7 @@ export class TweetService extends FetcherService {
253
253
  });
254
254
 
255
255
  // Deserializing response
256
- const data = Extractors[resource](response);
256
+ const data = Extractors[resource](response.data);
257
257
 
258
258
  return data;
259
259
  }
@@ -342,15 +342,18 @@ export class TweetService extends FetcherService {
342
342
  * ```
343
343
  */
344
344
  public async post(options: INewTweet): Promise<string | undefined> {
345
- const resource = ResourceType.TWEET_POST;
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
- // Posting the tweet
348
- const response = await this.request<ITweetPostResponse>(resource, { tweet: options });
351
+ return Extractors[ResourceType.TWEET_POST_NOTE](response.data);
352
+ }
349
353
 
350
- // Deserializing response
351
- const data = Extractors[resource](response);
354
+ const response = await this.request<ITweetPostResponse>(ResourceType.TWEET_POST, { tweet: options });
352
355
 
353
- return data;
356
+ return Extractors[ResourceType.TWEET_POST](response.data);
354
357
  }
355
358
 
356
359
  /**
@@ -403,7 +406,7 @@ export class TweetService extends FetcherService {
403
406
  });
404
407
 
405
408
  // Deserializing response
406
- const data = Extractors[resource](response);
409
+ const data = Extractors[resource](response.data);
407
410
 
408
411
  return data;
409
412
  }
@@ -440,7 +443,7 @@ export class TweetService extends FetcherService {
440
443
  const response = await this.request<ITweetRetweetResponse>(resource, { id: id });
441
444
 
442
445
  // Deserializing response
443
- const data = Extractors[resource](response) ?? false;
446
+ const data = Extractors[resource](response.data) ?? false;
444
447
 
445
448
  return data;
446
449
  }
@@ -483,7 +486,7 @@ export class TweetService extends FetcherService {
483
486
  });
484
487
 
485
488
  // Deserializing response
486
- const data = Extractors[resource](response);
489
+ const data = Extractors[resource](response.data);
487
490
 
488
491
  return data;
489
492
  }
@@ -525,7 +528,7 @@ export class TweetService extends FetcherService {
525
528
  const response = await this.request<ITweetScheduleResponse>(resource, { tweet: options });
526
529
 
527
530
  // Deserializing response
528
- const data = Extractors[resource](response);
531
+ const data = Extractors[resource](response.data);
529
532
 
530
533
  return data;
531
534
  }
@@ -573,7 +576,7 @@ export class TweetService extends FetcherService {
573
576
  });
574
577
 
575
578
  // Deserializing response
576
- const data = Extractors[resource](response);
579
+ const data = Extractors[resource](response.data);
577
580
 
578
581
  // Sorting the tweets by date, from recent to oldest
579
582
  data.list.sort((a, b) => new Date(b.createdAt).valueOf() - new Date(a.createdAt).valueOf());
@@ -682,7 +685,7 @@ export class TweetService extends FetcherService {
682
685
  const response = await this.request<ITweetUnbookmarkResponse>(resource, { id: id });
683
686
 
684
687
  // Deserializing the response
685
- const data = Extractors[resource](response) ?? false;
688
+ const data = Extractors[resource](response.data) ?? false;
686
689
 
687
690
  return data;
688
691
  }
@@ -719,7 +722,7 @@ export class TweetService extends FetcherService {
719
722
  const response = await this.request<ITweetUnlikeResponse>(resource, { id: id });
720
723
 
721
724
  // Deserializing the response
722
- const data = Extractors[resource](response) ?? false;
725
+ const data = Extractors[resource](response.data) ?? false;
723
726
 
724
727
  return data;
725
728
  }
@@ -756,7 +759,7 @@ export class TweetService extends FetcherService {
756
759
  const response = await this.request<ITweetUnpostResponse>(resource, { id: id });
757
760
 
758
761
  // Deserializing the response
759
- const data = Extractors[resource](response) ?? false;
762
+ const data = Extractors[resource](response.data) ?? false;
760
763
 
761
764
  return data;
762
765
  }
@@ -793,7 +796,7 @@ export class TweetService extends FetcherService {
793
796
  const response = await this.request<ITweetUnretweetResponse>(resource, { id: id });
794
797
 
795
798
  // Deserializing the response
796
- const data = Extractors[resource](response) ?? false;
799
+ const data = Extractors[resource](response.data) ?? false;
797
800
 
798
801
  return data;
799
802
  }
@@ -830,7 +833,7 @@ export class TweetService extends FetcherService {
830
833
  const response = await this.request<ITweetUnscheduleResponse>(resource, { id: id });
831
834
 
832
835
  // Deserializing the response
833
- const data = Extractors[resource](response) ?? false;
836
+ const data = Extractors[resource](response.data) ?? false;
834
837
 
835
838
  return data;
836
839
  }
@@ -873,7 +876,7 @@ export class TweetService extends FetcherService {
873
876
  await this.request<IMediaInitializeUploadResponse>(ResourceType.MEDIA_UPLOAD_INITIALIZE, {
874
877
  upload: { size: size },
875
878
  })
876
- ).media_id_string;
879
+ ).data.media_id_string;
877
880
 
878
881
  // APPEND
879
882
  await this.request<unknown>(ResourceType.MEDIA_UPLOAD_APPEND, { upload: { id: id, media: media } });
@@ -9,13 +9,16 @@ import { List } from '../../models/data/List';
9
9
  import { Notification } from '../../models/data/Notification';
10
10
  import { Tweet } from '../../models/data/Tweet';
11
11
  import { User } from '../../models/data/User';
12
+ import { UserAbout } from '../../models/data/UserAbout';
12
13
  import { RettiwtConfig } from '../../models/RettiwtConfig';
13
14
  import { IProfileUpdateOptions } from '../../types/args/ProfileArgs';
15
+ import { IUserAboutResponse } from '../../types/raw/user/About';
14
16
  import { IUserAffiliatesResponse } from '../../types/raw/user/Affiliates';
15
17
  import { IUserAnalyticsResponse } from '../../types/raw/user/Analytics';
16
18
  import { IUserBookmarkFoldersResponse } from '../../types/raw/user/BookmarkFolders';
17
19
  import { IUserBookmarkFolderTweetsResponse } from '../../types/raw/user/BookmarkFolderTweets';
18
20
  import { IUserBookmarksResponse } from '../../types/raw/user/Bookmarks';
21
+ import { IUserChangePasswordResponse } from '../../types/raw/user/ChangePassword';
19
22
  import { IUserDetailsResponse } from '../../types/raw/user/Details';
20
23
  import { IUserDetailsBulkResponse } from '../../types/raw/user/DetailsBulk';
21
24
  import { IUserFollowResponse } from '../../types/raw/user/Follow';
@@ -30,11 +33,14 @@ import { IUserNotificationsResponse } from '../../types/raw/user/Notifications';
30
33
  import { IUserProfileUpdateResponse } from '../../types/raw/user/ProfileUpdate';
31
34
  import { IUserRecommendedResponse } from '../../types/raw/user/Recommended';
32
35
  import { IUserSearchResponse } from '../../types/raw/user/Search';
36
+ import { IUserSettingsResponse } from '../../types/raw/user/Settings';
33
37
  import { IUserSubscriptionsResponse } from '../../types/raw/user/Subscriptions';
34
38
  import { IUserTweetsResponse } from '../../types/raw/user/Tweets';
35
39
  import { IUserTweetsAndRepliesResponse } from '../../types/raw/user/TweetsAndReplies';
36
40
  import { IUserUnfollowResponse } from '../../types/raw/user/Unfollow';
37
41
 
42
+ import { AuthService } from '../internal/AuthService';
43
+
38
44
  import { FetcherService } from './FetcherService';
39
45
 
40
46
  /**
@@ -52,6 +58,97 @@ export class UserService extends FetcherService {
52
58
  super(config);
53
59
  }
54
60
 
61
+ /**
62
+ * Gets the size in bytes of a base64 string.
63
+ *
64
+ * @param base64Data - The base64 data show size is required.
65
+ *
66
+ * @returns The size in bytes of the data.
67
+ */
68
+ private _base64ByteSize(base64Data: string): number {
69
+ const paddingMatch = base64Data.match(/=+$/);
70
+ const paddingLength = paddingMatch ? paddingMatch[0].length : 0;
71
+
72
+ return (base64Data.length * 3) / 4 - paddingLength;
73
+ }
74
+
75
+ /**
76
+ * Normalizes base64 data into just the raw base64 string.
77
+ *
78
+ * @param payload - The data to normalize.
79
+ *
80
+ * @returns The raw base64 part of the data.
81
+ */
82
+ private _normalizeBase64(payload: string): string {
83
+ const trimmedPayload = payload.trim();
84
+ const lowerCasePayload = trimmedPayload.toLowerCase();
85
+ const base64Marker = ';base64,';
86
+
87
+ if (lowerCasePayload.startsWith('data:')) {
88
+ const markerIndex = lowerCasePayload.indexOf(base64Marker);
89
+ if (markerIndex !== -1) {
90
+ return trimmedPayload.slice(markerIndex + base64Marker.length).trim();
91
+ }
92
+ }
93
+
94
+ return trimmedPayload;
95
+ }
96
+
97
+ private _validateBase64Payload(payload: string, fieldName: string): string {
98
+ const normalizedPayload = this._normalizeBase64(payload).replace(/\s+/g, '');
99
+
100
+ if (normalizedPayload.length === 0) {
101
+ throw new Error(`${fieldName} cannot be empty`);
102
+ }
103
+
104
+ if (!/^[A-Za-z0-9+/]*={0,2}$/.test(normalizedPayload)) {
105
+ throw new Error(`${fieldName} must be valid base64`);
106
+ }
107
+
108
+ return normalizedPayload;
109
+ }
110
+
111
+ /**
112
+ * Get the about profile of a user.
113
+ *
114
+ * @param userName - The username/screenname of the target user.
115
+ *
116
+ * @returns The about profile of the user.
117
+ *
118
+ * @example
119
+ *
120
+ * ```ts
121
+ * import { Rettiwt } from 'rettiwt-api';
122
+ *
123
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
124
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
125
+ *
126
+ * // Fetching the about profile of the User with username 'user1' or '@user1'
127
+ * rettiwt.user.about('user1') // or @user1
128
+ * .then(res => {
129
+ * console.log(res);
130
+ * })
131
+ * .catch(err => {
132
+ * console.log(err);
133
+ * });
134
+ * ```
135
+ */
136
+ public async about(userName: string): Promise<UserAbout | undefined> {
137
+ const resource = ResourceType.USER_ABOUT_BY_USERNAME;
138
+
139
+ if (userName.startsWith('@')) {
140
+ userName = userName.slice(1);
141
+ }
142
+
143
+ // Fetching raw about profile
144
+ const response = await this.request<IUserAboutResponse>(resource, { id: userName });
145
+
146
+ // Deserializing response
147
+ const data = Extractors[resource](response.data);
148
+
149
+ return data;
150
+ }
151
+
55
152
  /**
56
153
  * Get the list affiliates of a user.
57
154
  *
@@ -90,7 +187,7 @@ export class UserService extends FetcherService {
90
187
  });
91
188
 
92
189
  // Deserializing response
93
- const data = Extractors[resource](response);
190
+ const data = Extractors[resource](response.data);
94
191
 
95
192
  return data;
96
193
  }
@@ -148,7 +245,7 @@ export class UserService extends FetcherService {
148
245
  showVerifiedFollowers,
149
246
  });
150
247
 
151
- const data = Extractors[resource](response);
248
+ const data = Extractors[resource](response.data);
152
249
 
153
250
  return data;
154
251
  }
@@ -191,7 +288,7 @@ export class UserService extends FetcherService {
191
288
  });
192
289
 
193
290
  // Deserializing response
194
- const data = Extractors[resource](response);
291
+ const data = Extractors[resource](response.data);
195
292
 
196
293
  return data;
197
294
  }
@@ -230,7 +327,7 @@ export class UserService extends FetcherService {
230
327
  });
231
328
 
232
329
  // Deserializing response
233
- const data = Extractors[resource](response);
330
+ const data = Extractors[resource](response.data);
234
331
 
235
332
  return data;
236
333
  }
@@ -271,11 +368,86 @@ export class UserService extends FetcherService {
271
368
  });
272
369
 
273
370
  // Deserializing response
274
- const data = Extractors[resource](response);
371
+ const data = Extractors[resource](response.data);
372
+
373
+ return data;
374
+ }
375
+
376
+ /**
377
+ * Changes the password of the authenticated user.
378
+ *
379
+ * @param currentPassword - The current account password.
380
+ * @param newPassword - The new password to set.
381
+ * @returns Whether the password was changed successfully.
382
+ *
383
+ * @remarks
384
+ * After a successful password change, this method attempts to rotate the current
385
+ * `apiKey` using cookies returned by Twitter. If rotation is not possible, you
386
+ * must re-authenticate and obtain a new `apiKey` to continue making authenticated
387
+ * requests.
388
+ */
389
+ public async changePassword(currentPassword: string, newPassword: string): Promise<boolean> {
390
+ const resource = ResourceType.USER_PASSWORD_CHANGE;
391
+
392
+ // Changing the password
393
+ const response = await this.request<IUserChangePasswordResponse>(resource, {
394
+ changePassword: { currentPassword, newPassword },
395
+ });
396
+
397
+ // Getting if password change was successful or not
398
+ const data = Extractors[resource](response.data) ?? false;
399
+
400
+ // If password change was successful
401
+ if (data === true) {
402
+ // Getting the new API key
403
+ const newApiKey = AuthService.getApiKeyFromReponse(response);
404
+
405
+ // If new API key is generated, update current API key
406
+ if (newApiKey !== undefined) {
407
+ this.config.apiKey = newApiKey;
408
+ }
409
+
410
+ // Getting the new CSRF token and updating current API key
411
+ await AuthService.refreshCsrfToken(this.config);
412
+ }
275
413
 
276
414
  return data;
277
415
  }
278
416
 
417
+ /**
418
+ * Changes the username (screen_name) of the authenticated user.
419
+ *
420
+ * @param newUsername - The new username (with or without `@`).
421
+ * @returns Whether the username was changed successfully.
422
+ */
423
+ public async changeUsername(newUsername: string): Promise<boolean> {
424
+ const resource = ResourceType.USER_USERNAME_CHANGE;
425
+
426
+ // Strip @ prefix if present
427
+ const username = newUsername.startsWith('@') ? newUsername.slice(1) : newUsername;
428
+
429
+ // Username validation
430
+ if (username.length < 4) {
431
+ throw new Error('Username must be at least 4 characters long');
432
+ }
433
+ if (username.length > 15) {
434
+ throw new Error('Username cannot exceed 15 characters');
435
+ }
436
+ if (!/^[A-Za-z0-9_]+$/.test(username)) {
437
+ throw new Error('Username can only contain letters, numbers, and underscores');
438
+ }
439
+
440
+ // Changing the username
441
+ const response = await this.request<IUserSettingsResponse>(resource, {
442
+ username,
443
+ });
444
+
445
+ // Getting the updated username
446
+ const updatedUsername = Extractors[resource](response.data);
447
+
448
+ return updatedUsername?.toLowerCase() === username.toLowerCase();
449
+ }
450
+
279
451
  /**
280
452
  * Get the details of the logged in user.
281
453
  *
@@ -386,7 +558,7 @@ export class UserService extends FetcherService {
386
558
  const response = await this.request<IUserDetailsBulkResponse>(resource, { ids: id });
387
559
 
388
560
  // Deserializing response
389
- const data = Extractors[resource](response, id);
561
+ const data = Extractors[resource](response.data, id);
390
562
 
391
563
  return data;
392
564
  }
@@ -413,7 +585,7 @@ export class UserService extends FetcherService {
413
585
  const response = await this.request<IUserDetailsResponse>(resource, { id: id ?? this.config.userId });
414
586
 
415
587
  // Deserializing response
416
- const data = Extractors[resource](response);
588
+ const data = Extractors[resource](response.data);
417
589
 
418
590
  return data;
419
591
  }
@@ -453,7 +625,7 @@ export class UserService extends FetcherService {
453
625
  const response = await this.request<IUserFollowResponse>(ResourceType.USER_FOLLOW, { id: id });
454
626
 
455
627
  // Deserializing the response
456
- const data = Extractors[resource](response) ?? false;
628
+ const data = Extractors[resource](response.data) ?? false;
457
629
 
458
630
  return data;
459
631
  }
@@ -494,7 +666,7 @@ export class UserService extends FetcherService {
494
666
  });
495
667
 
496
668
  // Deserializing response
497
- const data = Extractors[resource](response);
669
+ const data = Extractors[resource](response.data);
498
670
 
499
671
  return data;
500
672
  }
@@ -537,7 +709,7 @@ export class UserService extends FetcherService {
537
709
  });
538
710
 
539
711
  // Deserializing response
540
- const data = Extractors[resource](response);
712
+ const data = Extractors[resource](response.data);
541
713
 
542
714
  return data;
543
715
  }
@@ -580,7 +752,7 @@ export class UserService extends FetcherService {
580
752
  });
581
753
 
582
754
  // Deserializing response
583
- const data = Extractors[resource](response);
755
+ const data = Extractors[resource](response.data);
584
756
 
585
757
  return data;
586
758
  }
@@ -623,7 +795,7 @@ export class UserService extends FetcherService {
623
795
  });
624
796
 
625
797
  // Deserializing response
626
- const data = Extractors[resource](response);
798
+ const data = Extractors[resource](response.data);
627
799
 
628
800
  return data;
629
801
  }
@@ -665,7 +837,7 @@ export class UserService extends FetcherService {
665
837
  });
666
838
 
667
839
  // Deserializing response
668
- const data = Extractors[resource](response);
840
+ const data = Extractors[resource](response.data);
669
841
 
670
842
  return data;
671
843
  }
@@ -707,7 +879,7 @@ export class UserService extends FetcherService {
707
879
  });
708
880
 
709
881
  // Deserializing response
710
- const data = Extractors[resource](response);
882
+ const data = Extractors[resource](response.data);
711
883
 
712
884
  return data;
713
885
  }
@@ -750,7 +922,7 @@ export class UserService extends FetcherService {
750
922
  });
751
923
 
752
924
  // Deserializing response
753
- const data = Extractors[resource](response);
925
+ const data = Extractors[resource](response.data);
754
926
 
755
927
  return data;
756
928
  }
@@ -807,7 +979,7 @@ export class UserService extends FetcherService {
807
979
  });
808
980
 
809
981
  // Deserializing response
810
- const notifications = Extractors[resource](response);
982
+ const notifications = Extractors[resource](response.data);
811
983
 
812
984
  // Sorting the notifications by time, from oldest to recent
813
985
  notifications.list.sort((a, b) => new Date(a.receivedAt).valueOf() - new Date(b.receivedAt).valueOf());
@@ -864,7 +1036,7 @@ export class UserService extends FetcherService {
864
1036
  });
865
1037
 
866
1038
  // Deserializing response
867
- const data = Extractors[resource](response);
1039
+ const data = Extractors[resource](response.data);
868
1040
 
869
1041
  return data;
870
1042
  }
@@ -911,7 +1083,7 @@ export class UserService extends FetcherService {
911
1083
  });
912
1084
 
913
1085
  // Deserializing response
914
- const data = Extractors[resource](response);
1086
+ const data = Extractors[resource](response.data);
915
1087
 
916
1088
  return data;
917
1089
  }
@@ -954,7 +1126,7 @@ export class UserService extends FetcherService {
954
1126
  });
955
1127
 
956
1128
  // Deserializing response
957
- const data = Extractors[resource](response);
1129
+ const data = Extractors[resource](response.data);
958
1130
 
959
1131
  return data;
960
1132
  }
@@ -997,7 +1169,7 @@ export class UserService extends FetcherService {
997
1169
  });
998
1170
 
999
1171
  // Deserializing response
1000
- const data = Extractors[resource](response);
1172
+ const data = Extractors[resource](response.data);
1001
1173
 
1002
1174
  return data;
1003
1175
  }
@@ -1045,7 +1217,7 @@ export class UserService extends FetcherService {
1045
1217
  });
1046
1218
 
1047
1219
  // Deserializing response
1048
- const data = Extractors[resource](response);
1220
+ const data = Extractors[resource](response.data);
1049
1221
 
1050
1222
  return data;
1051
1223
  }
@@ -1082,7 +1254,7 @@ export class UserService extends FetcherService {
1082
1254
  const response = await this.request<IUserUnfollowResponse>(ResourceType.USER_UNFOLLOW, { id: id });
1083
1255
 
1084
1256
  // Deserializing the response
1085
- const data = Extractors[resource](response) ?? false;
1257
+ const data = Extractors[resource](response.data) ?? false;
1086
1258
 
1087
1259
  return data;
1088
1260
  }
@@ -1147,7 +1319,59 @@ export class UserService extends FetcherService {
1147
1319
  const response = await this.request<IUserProfileUpdateResponse>(resource, { profileOptions: validatedOptions });
1148
1320
 
1149
1321
  // Deserializing the response
1150
- const data = Extractors[resource](response) ?? false;
1322
+ const data = Extractors[resource](response.data) ?? false;
1323
+
1324
+ return data;
1325
+ }
1326
+
1327
+ /**
1328
+ * Updates the profile banner of the authenticated user.
1329
+ *
1330
+ * @param bannerBase64 - The base64-encoded banner image data.
1331
+ * @returns Whether the profile banner was updated successfully.
1332
+ */
1333
+ public async updateProfileBanner(bannerBase64: string): Promise<boolean> {
1334
+ const resource = ResourceType.USER_PROFILE_BANNER_UPDATE;
1335
+
1336
+ const validatedBanner = this._validateBase64Payload(bannerBase64, 'Profile banner');
1337
+
1338
+ // Banner size validation (max 5 MB)
1339
+ const bannerSizeBytes = this._base64ByteSize(validatedBanner);
1340
+ if (bannerSizeBytes > 5 * 1024 * 1024) {
1341
+ throw new Error('Profile banner cannot exceed 5 MB');
1342
+ }
1343
+
1344
+ const response = await this.request<IUserProfileUpdateResponse>(resource, {
1345
+ profileBanner: validatedBanner,
1346
+ });
1347
+
1348
+ const data = Extractors[resource](response.data) ?? false;
1349
+
1350
+ return data;
1351
+ }
1352
+
1353
+ /**
1354
+ * Updates the profile image of the authenticated user.
1355
+ *
1356
+ * @param imageBase64 - The base64-encoded image data.
1357
+ * @returns Whether the profile image was updated successfully.
1358
+ */
1359
+ public async updateProfileImage(imageBase64: string): Promise<boolean> {
1360
+ const resource = ResourceType.USER_PROFILE_IMAGE_UPDATE;
1361
+
1362
+ const validatedImage = this._validateBase64Payload(imageBase64, 'Profile image');
1363
+
1364
+ // Image size validation (max 2 MB)
1365
+ const imageSizeBytes = this._base64ByteSize(validatedImage);
1366
+ if (imageSizeBytes > 2 * 1024 * 1024) {
1367
+ throw new Error('Profile image cannot exceed 2 MB');
1368
+ }
1369
+
1370
+ const response = await this.request<IUserProfileUpdateResponse>(resource, {
1371
+ profileImage: validatedImage,
1372
+ });
1373
+
1374
+ const data = Extractors[resource](response.data) ?? false;
1151
1375
 
1152
1376
  return data;
1153
1377
  }
@@ -1,3 +1,5 @@
1
+ import { AxiosProxyConfig, AxiosResponse } from 'axios';
2
+
1
3
  import { IErrorHandler } from './ErrorHandler';
2
4
 
3
5
  /**
@@ -10,11 +12,34 @@ export interface IRettiwtConfig {
10
12
  apiKey?: string;
11
13
 
12
14
  /**
13
- * Optional URL to proxy server to use for requests to Twitter API.
15
+ * The proxy to use.
16
+ *
17
+ * @remarks
18
+ * <br>
19
+ * - If set to anything besides `undefined`, disables Axios' built-in environment variable-set proxy.
20
+ *
21
+ * @example
22
+ * ```
23
+ * // Use custom proxy config via config object
24
+ * {
25
+ * proxy: {
26
+ * host: '127.0.0.1',
27
+ * port: 8080
28
+ * }
29
+ * }
30
+ *
31
+ * // Use custom proxy config via URL
32
+ * {
33
+ * proxy: 'https://127.0.0.1:8080'
34
+ * }
14
35
  *
15
- * @remarks When deploying to cloud platforms, if setting {@link IRettiwtConfig.authProxyUrl} does not resolve Error 429, then this might be required.
36
+ * // Use Axios environment variable for proxy
37
+ * {
38
+ * proxy: undefined
39
+ * }
40
+ * ```
16
41
  */
17
- proxyUrl?: URL;
42
+ proxy?: AxiosProxyConfig | string | null;
18
43
 
19
44
  /** The max wait time (in milli-seconds) for a response; if not set, Twitter server timeout is used. */
20
45
  timeout?: number;
@@ -22,6 +47,13 @@ export interface IRettiwtConfig {
22
47
  /** Whether to write logs to console or not. */
23
48
  logging?: boolean;
24
49
 
50
+ /**
51
+ * Optional response middleware to be executed on obtaining a successful response.
52
+ *
53
+ * @param response - The raw `AxiosReponse` object.
54
+ */
55
+ responseMiddleware?: (response: AxiosResponse) => void | Promise<void>;
56
+
25
57
  /** Optional custom error handler to define error conditions and process API/HTTP errors in responses. */
26
58
  errorHandler?: IErrorHandler;
27
59