rettiwt-api 3.1.1 → 4.1.0-alpha.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 (80) hide show
  1. package/.github/FUNDING.yml +4 -0
  2. package/.github/workflows/documentation.yml +5 -0
  3. package/.github/workflows/publish-alpha.yml +29 -0
  4. package/.github/workflows/publish.yml +3 -0
  5. package/.yarnrc.yml +1 -0
  6. package/README.md +50 -10
  7. package/dist/collections/Extractors.d.ts +6 -2
  8. package/dist/collections/Extractors.js +6 -3
  9. package/dist/collections/Extractors.js.map +1 -1
  10. package/dist/collections/Groups.js +4 -1
  11. package/dist/collections/Groups.js.map +1 -1
  12. package/dist/collections/Requests.js +4 -1
  13. package/dist/collections/Requests.js.map +1 -1
  14. package/dist/commands/Tweet.js +73 -41
  15. package/dist/commands/Tweet.js.map +1 -1
  16. package/dist/commands/User.js +42 -19
  17. package/dist/commands/User.js.map +1 -1
  18. package/dist/enums/Data.d.ts +1 -0
  19. package/dist/enums/Data.js +1 -0
  20. package/dist/enums/Data.js.map +1 -1
  21. package/dist/enums/Resource.d.ts +4 -1
  22. package/dist/enums/Resource.js +4 -1
  23. package/dist/enums/Resource.js.map +1 -1
  24. package/dist/helper/CliUtils.d.ts +2 -0
  25. package/dist/helper/CliUtils.js +2 -0
  26. package/dist/helper/CliUtils.js.map +1 -1
  27. package/dist/helper/JsonUtils.d.ts +2 -0
  28. package/dist/helper/JsonUtils.js +3 -1
  29. package/dist/helper/JsonUtils.js.map +1 -1
  30. package/dist/index.d.ts +2 -2
  31. package/dist/models/args/FetchArgs.d.ts +0 -2
  32. package/dist/models/args/FetchArgs.js +138 -10
  33. package/dist/models/args/FetchArgs.js.map +1 -1
  34. package/dist/models/args/PostArgs.d.ts +3 -1
  35. package/dist/models/args/PostArgs.js +62 -24
  36. package/dist/models/args/PostArgs.js.map +1 -1
  37. package/dist/models/data/CursoredData.d.ts +3 -3
  38. package/dist/models/data/CursoredData.js +5 -1
  39. package/dist/models/data/CursoredData.js.map +1 -1
  40. package/dist/models/data/Notification.d.ts +46 -0
  41. package/dist/models/data/Notification.js +69 -0
  42. package/dist/models/data/Notification.js.map +1 -0
  43. package/dist/models/data/Tweet.d.ts +6 -6
  44. package/dist/models/data/Tweet.js +1 -1
  45. package/dist/models/data/Tweet.js.map +1 -1
  46. package/dist/models/data/User.d.ts +3 -3
  47. package/dist/models/data/User.js.map +1 -1
  48. package/dist/services/public/AuthService.d.ts +21 -0
  49. package/dist/services/public/AuthService.js +44 -1
  50. package/dist/services/public/AuthService.js.map +1 -1
  51. package/dist/services/public/FetcherService.d.ts +2 -2
  52. package/dist/services/public/FetcherService.js +5 -6
  53. package/dist/services/public/FetcherService.js.map +1 -1
  54. package/dist/services/public/TweetService.d.ts +55 -32
  55. package/dist/services/public/TweetService.js +96 -55
  56. package/dist/services/public/TweetService.js.map +1 -1
  57. package/dist/services/public/UserService.d.ts +32 -6
  58. package/dist/services/public/UserService.js +52 -7
  59. package/dist/services/public/UserService.js.map +1 -1
  60. package/package.json +3 -2
  61. package/src/collections/Extractors.ts +10 -3
  62. package/src/collections/Groups.ts +4 -1
  63. package/src/collections/Requests.ts +4 -1
  64. package/src/commands/Tweet.ts +43 -18
  65. package/src/commands/User.ts +17 -4
  66. package/src/enums/Data.ts +1 -0
  67. package/src/enums/Resource.ts +4 -1
  68. package/src/helper/CliUtils.ts +2 -0
  69. package/src/helper/JsonUtils.ts +3 -1
  70. package/src/index.ts +5 -1
  71. package/src/models/args/FetchArgs.ts +140 -11
  72. package/src/models/args/PostArgs.ts +65 -24
  73. package/src/models/data/CursoredData.ts +7 -4
  74. package/src/models/data/Notification.ts +91 -0
  75. package/src/models/data/Tweet.ts +6 -7
  76. package/src/models/data/User.ts +3 -3
  77. package/src/services/public/AuthService.ts +51 -1
  78. package/src/services/public/FetcherService.ts +9 -8
  79. package/src/services/public/TweetService.ts +103 -60
  80. package/src/services/public/UserService.ts +51 -7
@@ -1,6 +1,8 @@
1
1
  import {
2
2
  ArrayMaxSize,
3
3
  IsArray,
4
+ IsDate,
5
+ IsEmpty,
4
6
  IsNotEmpty,
5
7
  IsNumberString,
6
8
  IsObject,
@@ -8,6 +10,7 @@ import {
8
10
  IsString,
9
11
  Max,
10
12
  MaxLength,
13
+ MinDate,
11
14
  validateSync,
12
15
  } from 'class-validator';
13
16
 
@@ -35,7 +38,15 @@ export class PostArgs {
35
38
  * - {@link EResourceType.USER_FOLLOW}
36
39
  * - {@link EResourceType.USER_UNFOLLOW}
37
40
  */
38
- @IsOptional()
41
+ @IsEmpty({
42
+ groups: [
43
+ EResourceType.MEDIA_UPLOAD_APPEND,
44
+ EResourceType.MEDIA_UPLOAD_FINALIZE,
45
+ EResourceType.MEDIA_UPLOAD_INITIALIZE,
46
+ EResourceType.TWEET_POST,
47
+ EResourceType.TWEET_SCHEDULE,
48
+ ],
49
+ })
39
50
  @IsNotEmpty({
40
51
  groups: [
41
52
  EResourceType.TWEET_LIKE,
@@ -43,6 +54,7 @@ export class PostArgs {
43
54
  EResourceType.TWEET_UNLIKE,
44
55
  EResourceType.TWEET_UNPOST,
45
56
  EResourceType.TWEET_UNRETWEET,
57
+ EResourceType.TWEET_UNSCHEDULE,
46
58
  EResourceType.USER_FOLLOW,
47
59
  EResourceType.USER_UNFOLLOW,
48
60
  ],
@@ -54,6 +66,7 @@ export class PostArgs {
54
66
  EResourceType.TWEET_UNLIKE,
55
67
  EResourceType.TWEET_UNPOST,
56
68
  EResourceType.TWEET_UNRETWEET,
69
+ EResourceType.TWEET_UNSCHEDULE,
57
70
  EResourceType.USER_FOLLOW,
58
71
  EResourceType.USER_UNFOLLOW,
59
72
  ],
@@ -66,9 +79,23 @@ export class PostArgs {
66
79
  * @remarks
67
80
  * Required only when posting a tweet using {@link EResourceType.TWEET_POST}
68
81
  */
69
- @IsOptional()
70
- @IsNotEmpty({ groups: [EResourceType.TWEET_POST] })
71
- @IsObject({ groups: [EResourceType.TWEET_POST] })
82
+ @IsEmpty({
83
+ groups: [
84
+ EResourceType.MEDIA_UPLOAD_APPEND,
85
+ EResourceType.MEDIA_UPLOAD_FINALIZE,
86
+ EResourceType.MEDIA_UPLOAD_INITIALIZE,
87
+ EResourceType.TWEET_LIKE,
88
+ EResourceType.TWEET_RETWEET,
89
+ EResourceType.TWEET_UNLIKE,
90
+ EResourceType.TWEET_UNPOST,
91
+ EResourceType.TWEET_UNRETWEET,
92
+ EResourceType.TWEET_UNSCHEDULE,
93
+ EResourceType.USER_FOLLOW,
94
+ EResourceType.USER_UNFOLLOW,
95
+ ],
96
+ })
97
+ @IsNotEmpty({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
98
+ @IsObject({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
72
99
  public tweet?: TweetArgs;
73
100
 
74
101
  /**
@@ -80,7 +107,13 @@ export class PostArgs {
80
107
  * - {@link EResourceType.MEDIA_UPLOAD_FINALIZE}
81
108
  * - {@link EResourceType.MEDIA_UPLOAD_INITIALIZE}
82
109
  */
83
- @IsOptional()
110
+ @IsEmpty({
111
+ groups: [
112
+ EResourceType.MEDIA_UPLOAD_APPEND,
113
+ EResourceType.MEDIA_UPLOAD_FINALIZE,
114
+ EResourceType.MEDIA_UPLOAD_INITIALIZE,
115
+ ],
116
+ })
84
117
  @IsNotEmpty({
85
118
  groups: [
86
119
  EResourceType.MEDIA_UPLOAD_INITIALIZE,
@@ -103,7 +136,7 @@ export class PostArgs {
103
136
  */
104
137
  public constructor(resource: EResourceType, args: PostArgs) {
105
138
  this.id = args.id;
106
- this.tweet = args.tweet ? new TweetArgs(args.tweet) : undefined;
139
+ this.tweet = args.tweet ? new TweetArgs(resource, args.tweet) : undefined;
107
140
  this.upload = args.upload ? new UploadArgs(resource, args.upload) : undefined;
108
141
 
109
142
  // Validating this object
@@ -128,45 +161,53 @@ export class TweetArgs extends NewTweet {
128
161
  * @remarks
129
162
  * Maximum number of media items that can be posted is 4.
130
163
  */
131
- @IsOptional()
132
- @IsArray()
133
- @ArrayMaxSize(4)
134
- @IsObject({ each: true })
164
+ @IsOptional({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
165
+ @IsArray({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
166
+ @ArrayMaxSize(4, { groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
167
+ @IsObject({ each: true, groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
135
168
  public media?: TweetMediaArgs[];
136
169
 
137
170
  /** The id of the tweet to quote. */
138
- @IsOptional()
139
- @IsNumberString()
171
+ @IsOptional({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
172
+ @IsNumberString(undefined, { groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
140
173
  public quote?: string;
141
174
 
142
175
  /** The id of the tweet to which the given tweet must be a reply. */
143
- @IsOptional()
144
- @IsNumberString()
176
+ @IsOptional({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
177
+ @IsNumberString(undefined, { groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
145
178
  public replyTo?: string;
146
179
 
180
+ /** The date/time at which the tweet must be scheduled to be posted. */
181
+ @IsEmpty({ groups: [EResourceType.TWEET_POST] })
182
+ @IsNotEmpty({ groups: [EResourceType.TWEET_SCHEDULE] })
183
+ @IsDate({ groups: [EResourceType.TWEET_SCHEDULE] })
184
+ @MinDate(() => new Date(), { groups: [EResourceType.TWEET_SCHEDULE] })
185
+ public scheduleFor?: Date;
186
+
147
187
  /**
148
188
  * The text for the tweet to be created.
149
189
  *
150
190
  * @remarks
151
191
  * Length of the tweet must be \<= 280 characters.
152
192
  */
153
- @IsNotEmpty()
154
- @IsString()
155
- @MaxLength(280)
193
+ @IsNotEmpty({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
194
+ @IsString({ groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
195
+ @MaxLength(280, { groups: [EResourceType.TWEET_POST, EResourceType.TWEET_SCHEDULE] })
156
196
  public text: string;
157
197
 
158
198
  /**
159
199
  * @param args - Arguments specifying the tweet to be posted.
160
200
  */
161
- public constructor(args: TweetArgs) {
201
+ public constructor(resource: EResourceType, args: TweetArgs) {
162
202
  super();
163
- this.text = args.text;
164
- this.quote = args.quote;
165
203
  this.media = args.media ? args.media.map((item) => new TweetMediaArgs(item)) : undefined;
204
+ this.quote = args.quote;
166
205
  this.replyTo = args.replyTo;
206
+ this.scheduleFor = args.scheduleFor;
207
+ this.text = args.text;
167
208
 
168
209
  // Validating this object
169
- const validationResult = validateSync(this);
210
+ const validationResult = validateSync(this, { groups: [resource] });
170
211
 
171
212
  // If valiation error occured
172
213
  if (validationResult.length) {
@@ -223,13 +264,13 @@ export class TweetMediaArgs extends NewTweetMedia {
223
264
  */
224
265
  export class UploadArgs {
225
266
  /** The id allocated to the media file to be uploaded. */
226
- @IsOptional()
267
+ @IsEmpty({ groups: [EResourceType.MEDIA_UPLOAD_INITIALIZE] })
227
268
  @IsNotEmpty({ groups: [EResourceType.MEDIA_UPLOAD_APPEND, EResourceType.MEDIA_UPLOAD_FINALIZE] })
228
269
  @IsNumberString(undefined, { groups: [EResourceType.MEDIA_UPLOAD_APPEND, EResourceType.MEDIA_UPLOAD_FINALIZE] })
229
270
  public id?: string;
230
271
 
231
272
  /** The media file to be uploaded. */
232
- @IsOptional()
273
+ @IsEmpty({ groups: [EResourceType.MEDIA_UPLOAD_FINALIZE, EResourceType.MEDIA_UPLOAD_INITIALIZE] })
233
274
  @IsNotEmpty({ groups: [EResourceType.MEDIA_UPLOAD_APPEND] })
234
275
  public media?: string | ArrayBuffer;
235
276
 
@@ -238,7 +279,7 @@ export class UploadArgs {
238
279
  *
239
280
  * @remarks The size must be \<= 5242880 bytes.
240
281
  */
241
- @IsOptional()
282
+ @IsEmpty({ groups: [EResourceType.MEDIA_UPLOAD_APPEND, EResourceType.MEDIA_UPLOAD_FINALIZE] })
242
283
  @IsNotEmpty({ groups: [EResourceType.MEDIA_UPLOAD_INITIALIZE] })
243
284
  @Max(5242880, { groups: [EResourceType.MEDIA_UPLOAD_INITIALIZE] })
244
285
  public size?: number;
@@ -1,9 +1,10 @@
1
- import { ICursor, IResponse } from 'rettiwt-core';
1
+ import { ICursor } from 'rettiwt-core';
2
2
 
3
3
  import { EBaseType } from '../../enums/Data';
4
4
 
5
5
  import { findByFilter } from '../../helper/JsonUtils';
6
6
 
7
+ import { Notification } from './Notification';
7
8
  import { Tweet } from './Tweet';
8
9
  import { User } from './User';
9
10
 
@@ -14,7 +15,7 @@ import { User } from './User';
14
15
  *
15
16
  * @public
16
17
  */
17
- export class CursoredData<T extends Tweet | User> {
18
+ export class CursoredData<T extends Notification | Tweet | User> {
18
19
  /** The batch of data of the given type. */
19
20
  public list: T[] = [];
20
21
 
@@ -25,11 +26,13 @@ export class CursoredData<T extends Tweet | User> {
25
26
  * @param response - The raw response.
26
27
  * @param type - The base type of the data included in the batch.
27
28
  */
28
- public constructor(response: IResponse<unknown>, type: EBaseType) {
29
+ public constructor(response: NonNullable<unknown>, type: EBaseType) {
29
30
  if (type == EBaseType.TWEET) {
30
31
  this.list = Tweet.list(response) as T[];
31
- } else {
32
+ } else if (type == EBaseType.USER) {
32
33
  this.list = User.list(response) as T[];
34
+ } else if (type == EBaseType.NOTIFICATION) {
35
+ this.list = Notification.list(response) as T[];
33
36
  }
34
37
 
35
38
  this.next = new Cursor(findByFilter<ICursor>(response, 'cursorType', 'Bottom')[0]?.value ?? '');
@@ -0,0 +1,91 @@
1
+ import {
2
+ ENotificationType as ENotificationTypeOriginal,
3
+ INotification,
4
+ IUserNotifications as IUserNotificationsResponse,
5
+ } from 'rettiwt-core';
6
+
7
+ import { findKeyByValue } from '../../helper/JsonUtils';
8
+
9
+ /**
10
+ * The different types of notifications.
11
+ *
12
+ * @public
13
+ */
14
+ export enum ENotificationType {
15
+ RECOMMENDATION = 'RECOMMENDATION',
16
+ INFORMATION = 'INFORMATION',
17
+ LIVE = 'LIVE',
18
+ ALERT = 'ALERT',
19
+ UNDEFINED = 'UNDEFINED',
20
+ }
21
+
22
+ /**
23
+ * The details of a single notification.
24
+ *
25
+ * @public
26
+ */
27
+ export class Notification {
28
+ /** The list of id of the users from whom the notification was received. */
29
+ public from: string[];
30
+
31
+ /** The id of the notification. */
32
+ public id: string;
33
+
34
+ /** The text contents of the notification. */
35
+ public message: string;
36
+
37
+ /** The date/time at which the notification was received. */
38
+ public receivedAt: Date;
39
+
40
+ /** The list of id of the target tweet(s) of the notification. */
41
+ public target: string[];
42
+
43
+ /** The type of notification. */
44
+ public type?: ENotificationType;
45
+
46
+ /**
47
+ * @param notification - The raw notification details.
48
+ */
49
+ public constructor(notification: INotification) {
50
+ // Getting the original notification type
51
+ const notificationType: string | undefined = findKeyByValue(ENotificationTypeOriginal, notification.icon.id);
52
+
53
+ this.from = notification.template?.aggregateUserActionsV1?.fromUsers
54
+ ? notification.template.aggregateUserActionsV1.fromUsers.map((item) => item.user.id)
55
+ : [];
56
+ this.id = notification.id;
57
+ this.message = notification.message.text;
58
+ this.receivedAt = new Date(Number(notification.timestampMs));
59
+ this.target = notification.template?.aggregateUserActionsV1?.targetObjects
60
+ ? notification.template.aggregateUserActionsV1.targetObjects.map((item) => item.tweet.id)
61
+ : [];
62
+ this.type = notificationType
63
+ ? ENotificationType[notificationType as keyof typeof ENotificationType]
64
+ : ENotificationType.UNDEFINED;
65
+ }
66
+
67
+ /**
68
+ * Extracts and deserializes the list of notifications from the given raw response data.
69
+ *
70
+ * @param response - The raw response data.
71
+ *
72
+ * @returns The deserialized list of notifications.
73
+ *
74
+ * @internal
75
+ */
76
+ public static list(response: NonNullable<unknown>): Notification[] {
77
+ const notifications: Notification[] = [];
78
+
79
+ // Extracting notifications
80
+ if ((response as IUserNotificationsResponse).globalObjects.notifications) {
81
+ // Iterating over the raw list of notifications
82
+ for (const [, value] of Object.entries(
83
+ (response as IUserNotificationsResponse).globalObjects.notifications,
84
+ )) {
85
+ notifications.push(new Notification(value as INotification));
86
+ }
87
+ }
88
+
89
+ return notifications;
90
+ }
91
+ }
@@ -3,7 +3,6 @@ import {
3
3
  IExtendedMedia as IRawExtendedMedia,
4
4
  ITweet as IRawTweet,
5
5
  IEntities as IRawTweetEntities,
6
- IResponse,
7
6
  ITimelineTweet,
8
7
  ITweet,
9
8
  } from 'rettiwt-core';
@@ -48,8 +47,8 @@ export class Tweet {
48
47
  /** The number of quotes of the tweet. */
49
48
  public quoteCount: number;
50
49
 
51
- /** The rest id of the tweet which is quoted in the tweet. */
52
- public quoted?: string;
50
+ /** The tweet which is quoted in the tweet. */
51
+ public quoted?: Tweet;
53
52
 
54
53
  /** The number of replies to the tweet. */
55
54
  public replyCount: number;
@@ -60,7 +59,7 @@ export class Tweet {
60
59
  /** The number of retweets of the tweet. */
61
60
  public retweetCount: number;
62
61
 
63
- /** The tweet which was retweeted in this tweet (if any). */
62
+ /** The tweet which is retweeted in this tweet (if any). */
64
63
  public retweetedTweet?: Tweet;
65
64
 
66
65
  /** The details of the user who made the tweet. */
@@ -78,7 +77,7 @@ export class Tweet {
78
77
  this.tweetBy = new User(tweet.core.user_results.result);
79
78
  this.entities = new TweetEntities(tweet.legacy.entities);
80
79
  this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media));
81
- this.quoted = tweet.legacy.quoted_status_id_str;
80
+ this.quoted = tweet.quoted_status_result ? new Tweet(tweet.quoted_status_result.result) : undefined;
82
81
  this.fullText = tweet.note_tweet ? tweet.note_tweet.note_tweet_results.result.text : tweet.legacy.full_text;
83
82
  this.replyTo = tweet.legacy.in_reply_to_status_id_str;
84
83
  this.lang = tweet.legacy.lang;
@@ -102,7 +101,7 @@ export class Tweet {
102
101
  *
103
102
  * @internal
104
103
  */
105
- public static list(response: IResponse<unknown>): Tweet[] {
104
+ public static list(response: NonNullable<unknown>): Tweet[] {
106
105
  const tweets: Tweet[] = [];
107
106
 
108
107
  // Extracting the matching data
@@ -137,7 +136,7 @@ export class Tweet {
137
136
  *
138
137
  * @internal
139
138
  */
140
- public static single(response: IResponse<unknown>, id: string): Tweet | undefined {
139
+ public static single(response: NonNullable<unknown>, id: string): Tweet | undefined {
141
140
  const tweets: Tweet[] = [];
142
141
 
143
142
  // Extracting the matching data
@@ -1,4 +1,4 @@
1
- import { IUser as IRawUser, IResponse, ITimelineUser, IUser } from 'rettiwt-core';
1
+ import { IUser as IRawUser, ITimelineUser, IUser } from 'rettiwt-core';
2
2
 
3
3
  import { ELogActions } from '../../enums/Logging';
4
4
  import { findByFilter } from '../../helper/JsonUtils';
@@ -81,7 +81,7 @@ export class User {
81
81
  *
82
82
  * @internal
83
83
  */
84
- public static list(response: IResponse<unknown>): User[] {
84
+ public static list(response: NonNullable<unknown>): User[] {
85
85
  const users: User[] = [];
86
86
 
87
87
  // Extracting the matching data
@@ -115,7 +115,7 @@ export class User {
115
115
  *
116
116
  * @internal
117
117
  */
118
- public static single(response: IResponse<unknown>): User | undefined {
118
+ public static single(response: NonNullable<unknown>): User | undefined {
119
119
  const users: User[] = [];
120
120
 
121
121
  // Extracting the matching data
@@ -1,5 +1,6 @@
1
1
  import { Auth } from 'rettiwt-auth';
2
2
 
3
+ import { EApiErrors } from '../../enums/Api';
3
4
  import { IRettiwtConfig } from '../../types/RettiwtConfig';
4
5
 
5
6
  import { FetcherService } from './FetcherService';
@@ -19,6 +20,55 @@ export class AuthService extends FetcherService {
19
20
  super(config);
20
21
  }
21
22
 
23
+ /**
24
+ * Decodes the encoded cookie string.
25
+ *
26
+ * @param encodedCookies - The encoded cookie string to decode.
27
+ * @returns The decoded cookie string.
28
+ */
29
+ public static decodeCookie(encodedCookies: string): string {
30
+ // Decoding the encoded cookie string
31
+ const decodedCookies: string = Buffer.from(encodedCookies, 'base64').toString('ascii');
32
+
33
+ return decodedCookies;
34
+ }
35
+
36
+ /**
37
+ * Encodes the given cookie string.
38
+ *
39
+ * @param cookieString - The cookie string to encode.
40
+ * @returns The encoded cookie string.
41
+ */
42
+ public static encodeCookie(cookieString: string): string {
43
+ // Encoding the cookie string to base64
44
+ const encodedCookies: string = Buffer.from(cookieString).toString('base64');
45
+
46
+ return encodedCookies;
47
+ }
48
+
49
+ /**
50
+ * Gets the user's id from the given API key.
51
+ *
52
+ * @param apiKey - The API key.
53
+ * @returns The user id associated with the API key.
54
+ */
55
+ public static getUserId(apiKey: string): string {
56
+ // Getting the cookie string from the API key
57
+ const cookieString: string = AuthService.decodeCookie(apiKey);
58
+
59
+ // Searching for the user id in the cookie string
60
+ const searchResults: string[] | null = cookieString.match(/((?<=twid="u=)(.*)(?="))|((?<=twid=u%3D)(.*)(?=;))/);
61
+
62
+ // If user id was found
63
+ if (searchResults) {
64
+ return searchResults[0];
65
+ }
66
+ // If user id was not found
67
+ else {
68
+ throw new Error(EApiErrors.BAD_AUTHENTICATION);
69
+ }
70
+ }
71
+
22
72
  /**
23
73
  * Login to twitter as guest.
24
74
  *
@@ -91,7 +141,7 @@ export class AuthService extends FetcherService {
91
141
  ).toHeader().cookie as string) ?? '';
92
142
 
93
143
  // Converting the credentials to base64 string
94
- apiKey = Buffer.from(apiKey).toString('base64');
144
+ apiKey = AuthService.encodeCookie(apiKey);
95
145
 
96
146
  return apiKey;
97
147
  }
@@ -17,6 +17,8 @@ import { IRettiwtConfig } from '../../types/RettiwtConfig';
17
17
  import { ErrorService } from '../internal/ErrorService';
18
18
  import { LogService } from '../internal/LogService';
19
19
 
20
+ import { AuthService } from './AuthService';
21
+
20
22
  /**
21
23
  * The base service that handles all HTTP requests.
22
24
  *
@@ -32,9 +34,6 @@ export class FetcherService {
32
34
  /** The guest key to use for authenticating against Twitter API as guest. */
33
35
  private readonly guestKey?: string;
34
36
 
35
- /** Whether the instance is authenticated or not. */
36
- private readonly isAuthenticated: boolean;
37
-
38
37
  /** The URL To the proxy server to use for all others. */
39
38
  private readonly proxyUrl?: URL;
40
39
 
@@ -44,6 +43,9 @@ export class FetcherService {
44
43
  /** The URL to the proxy server to use only for authentication. */
45
44
  protected readonly authProxyUrl?: URL;
46
45
 
46
+ /** The id of the authenticated user (if any). */
47
+ protected readonly userId?: string;
48
+
47
49
  /**
48
50
  * @param config - The config object for configuring the Rettiwt instance.
49
51
  */
@@ -51,7 +53,7 @@ export class FetcherService {
51
53
  LogService.enabled = config?.logging ?? false;
52
54
  this.apiKey = config?.apiKey;
53
55
  this.guestKey = config?.guestKey;
54
- this.isAuthenticated = config?.apiKey ? true : false;
56
+ this.userId = config?.apiKey ? AuthService.getUserId(config.apiKey) : undefined;
55
57
  this.authProxyUrl = config?.authProxyUrl ?? config?.proxyUrl;
56
58
  this.proxyUrl = config?.proxyUrl;
57
59
  this.timeout = config?.timeout ?? 0;
@@ -67,10 +69,10 @@ export class FetcherService {
67
69
  */
68
70
  private checkAuthorization(resource: EResourceType): void {
69
71
  // Logging
70
- LogService.log(ELogActions.AUTHORIZATION, { authenticated: this.isAuthenticated });
72
+ LogService.log(ELogActions.AUTHORIZATION, { authenticated: this.userId != undefined });
71
73
 
72
74
  // Checking authorization status
73
- if (!allowGuestAuthentication.includes(resource) && this.isAuthenticated == false) {
75
+ if (!allowGuestAuthentication.includes(resource) && this.userId == undefined) {
74
76
  throw new Error(EApiErrors.RESOURCE_NOT_ALLOWED);
75
77
  }
76
78
  }
@@ -85,8 +87,7 @@ export class FetcherService {
85
87
  // Logging
86
88
  LogService.log(ELogActions.GET, { target: 'USER_CREDENTIAL' });
87
89
 
88
- const cookies = Buffer.from(this.apiKey, 'base64').toString('ascii').split(';');
89
- return new AuthCredential(cookies);
90
+ return new AuthCredential(AuthService.decodeCookie(this.apiKey).split(';'));
90
91
  } else if (this.guestKey) {
91
92
  // Logging
92
93
  LogService.log(ELogActions.GET, { target: 'GUEST_CREDENTIAL' });