rettiwt-api 2.4.2 → 2.5.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.
Files changed (134) hide show
  1. package/.eslintrc.js +5 -0
  2. package/.github/workflows/documentation.yml +1 -1
  3. package/.github/workflows/publish.yml +1 -1
  4. package/.prettierrc +1 -1
  5. package/README.md +69 -19
  6. package/dist/Rettiwt.d.ts +7 -4
  7. package/dist/Rettiwt.js +4 -2
  8. package/dist/Rettiwt.js.map +1 -1
  9. package/dist/cli.js +4 -2
  10. package/dist/cli.js.map +1 -1
  11. package/dist/commands/Auth.d.ts +2 -1
  12. package/dist/commands/Auth.js +10 -17
  13. package/dist/commands/Auth.js.map +1 -1
  14. package/dist/commands/Tweet.js +36 -3
  15. package/dist/commands/Tweet.js.map +1 -1
  16. package/dist/enums/{ApiErrors.js → Api.js} +1 -1
  17. package/dist/enums/Api.js.map +1 -0
  18. package/dist/enums/Http.d.ts +68 -0
  19. package/dist/enums/Http.js +73 -0
  20. package/dist/enums/Http.js.map +1 -0
  21. package/dist/enums/Logging.d.ts +1 -0
  22. package/dist/enums/Logging.js +1 -0
  23. package/dist/enums/Logging.js.map +1 -1
  24. package/dist/index.d.ts +15 -14
  25. package/dist/index.js +22 -17
  26. package/dist/index.js.map +1 -1
  27. package/dist/models/args/TweetArgs.d.ts +44 -0
  28. package/dist/models/args/TweetArgs.js +82 -0
  29. package/dist/models/args/TweetArgs.js.map +1 -0
  30. package/dist/models/{public → data}/CursoredData.d.ts +5 -3
  31. package/dist/models/{public → data}/CursoredData.js +1 -0
  32. package/dist/models/data/CursoredData.js.map +1 -0
  33. package/dist/{types/public → models/data}/List.d.ts +8 -1
  34. package/dist/models/data/List.js.map +1 -0
  35. package/dist/models/data/Media.d.ts +14 -0
  36. package/dist/models/data/Media.js +19 -0
  37. package/dist/models/data/Media.js.map +1 -0
  38. package/dist/{types/public → models/data}/Tweet.d.ts +26 -8
  39. package/dist/models/{public → data}/Tweet.js +4 -0
  40. package/dist/models/data/Tweet.js.map +1 -0
  41. package/dist/{types/public → models/data}/User.d.ts +8 -1
  42. package/dist/models/data/User.js.map +1 -0
  43. package/dist/models/errors/ApiError.d.ts +17 -0
  44. package/dist/models/errors/ApiError.js +42 -0
  45. package/dist/models/errors/ApiError.js.map +1 -0
  46. package/dist/models/errors/HttpError.d.ts +17 -0
  47. package/dist/models/errors/HttpError.js +42 -0
  48. package/dist/models/errors/HttpError.js.map +1 -0
  49. package/dist/models/errors/RettiwtError.d.ts +8 -0
  50. package/dist/models/errors/RettiwtError.js +34 -0
  51. package/dist/models/errors/RettiwtError.js.map +1 -0
  52. package/dist/models/errors/TimeoutError.d.ts +14 -0
  53. package/dist/models/errors/TimeoutError.js +39 -0
  54. package/dist/models/errors/TimeoutError.js.map +1 -0
  55. package/dist/services/internal/ErrorService.d.ts +85 -0
  56. package/dist/services/internal/ErrorService.js +144 -0
  57. package/dist/services/internal/ErrorService.js.map +1 -0
  58. package/dist/services/internal/FetcherService.d.ts +22 -24
  59. package/dist/services/internal/FetcherService.js +79 -59
  60. package/dist/services/internal/FetcherService.js.map +1 -1
  61. package/dist/services/public/AuthService.d.ts +66 -0
  62. package/dist/services/public/AuthService.js +160 -0
  63. package/dist/services/public/AuthService.js.map +1 -0
  64. package/dist/services/public/TweetService.d.ts +27 -8
  65. package/dist/services/public/TweetService.js +45 -8
  66. package/dist/services/public/TweetService.js.map +1 -1
  67. package/dist/services/public/UserService.d.ts +5 -5
  68. package/dist/services/public/UserService.js.map +1 -1
  69. package/dist/types/ErrorHandler.d.ts +13 -0
  70. package/dist/types/{public/User.js → ErrorHandler.js} +1 -1
  71. package/dist/types/ErrorHandler.js.map +1 -0
  72. package/dist/types/RettiwtConfig.d.ts +32 -0
  73. package/dist/types/RettiwtConfig.js.map +1 -0
  74. package/package.json +6 -5
  75. package/src/Rettiwt.ts +10 -5
  76. package/src/cli.ts +4 -2
  77. package/src/commands/Auth.ts +5 -16
  78. package/src/commands/Tweet.ts +56 -3
  79. package/src/enums/Http.ts +68 -0
  80. package/src/enums/Logging.ts +1 -0
  81. package/src/index.ts +25 -18
  82. package/src/models/args/TweetArgs.ts +98 -0
  83. package/src/models/{public → data}/CursoredData.ts +6 -5
  84. package/src/models/{public → data}/List.ts +14 -4
  85. package/src/models/data/Media.ts +19 -0
  86. package/src/models/{public → data}/Tweet.ts +39 -5
  87. package/src/models/{public → data}/User.ts +28 -4
  88. package/src/models/errors/ApiError.ts +24 -0
  89. package/src/models/errors/HttpError.ts +24 -0
  90. package/src/models/errors/RettiwtError.ts +12 -0
  91. package/src/models/errors/TimeoutError.ts +18 -0
  92. package/src/services/internal/ErrorService.ts +158 -0
  93. package/src/services/internal/FetcherService.ts +94 -80
  94. package/src/services/public/AuthService.ts +97 -0
  95. package/src/services/public/TweetService.ts +48 -10
  96. package/src/services/public/UserService.ts +7 -5
  97. package/src/types/ErrorHandler.ts +13 -0
  98. package/src/types/RettiwtConfig.ts +40 -0
  99. package/dist/enums/ApiErrors.js.map +0 -1
  100. package/dist/enums/HTTP.d.ts +0 -17
  101. package/dist/enums/HTTP.js +0 -22
  102. package/dist/enums/HTTP.js.map +0 -1
  103. package/dist/models/internal/RettiwtConfig.d.ts +0 -18
  104. package/dist/models/internal/RettiwtConfig.js +0 -24
  105. package/dist/models/internal/RettiwtConfig.js.map +0 -1
  106. package/dist/models/public/CursoredData.js.map +0 -1
  107. package/dist/models/public/List.d.ts +0 -22
  108. package/dist/models/public/List.js.map +0 -1
  109. package/dist/models/public/Tweet.d.ts +0 -62
  110. package/dist/models/public/Tweet.js.map +0 -1
  111. package/dist/models/public/User.d.ts +0 -29
  112. package/dist/models/public/User.js.map +0 -1
  113. package/dist/types/internal/RettiwtConfig.d.ts +0 -15
  114. package/dist/types/internal/RettiwtConfig.js.map +0 -1
  115. package/dist/types/public/CursoredData.d.ts +0 -22
  116. package/dist/types/public/CursoredData.js +0 -3
  117. package/dist/types/public/CursoredData.js.map +0 -1
  118. package/dist/types/public/List.js +0 -3
  119. package/dist/types/public/List.js.map +0 -1
  120. package/dist/types/public/Tweet.js +0 -3
  121. package/dist/types/public/Tweet.js.map +0 -1
  122. package/dist/types/public/User.js.map +0 -1
  123. package/src/enums/HTTP.ts +0 -17
  124. package/src/models/internal/RettiwtConfig.ts +0 -26
  125. package/src/types/internal/RettiwtConfig.ts +0 -18
  126. package/src/types/public/CursoredData.ts +0 -24
  127. package/src/types/public/List.ts +0 -27
  128. package/src/types/public/Tweet.ts +0 -86
  129. package/src/types/public/User.ts +0 -48
  130. /package/dist/enums/{ApiErrors.d.ts → Api.d.ts} +0 -0
  131. /package/dist/models/{public → data}/List.js +0 -0
  132. /package/dist/models/{public → data}/User.js +0 -0
  133. /package/dist/types/{internal/RettiwtConfig.js → RettiwtConfig.js} +0 -0
  134. /package/src/enums/{ApiErrors.ts → Api.ts} +0 -0
@@ -0,0 +1,19 @@
1
+ // PACKAGES
2
+ import { IMediaUploadInitializeResponse } from 'rettiwt-core';
3
+
4
+ /**
5
+ * The details of a single media file.
6
+ *
7
+ * @public
8
+ */
9
+ export class Media {
10
+ /** The id of the media. */
11
+ public id: string;
12
+
13
+ /**
14
+ * @param media - The raw media data.
15
+ */
16
+ public constructor(media: IMediaUploadInitializeResponse) {
17
+ this.id = media.media_id_string;
18
+ }
19
+ }
@@ -6,9 +6,6 @@ import {
6
6
  EMediaType,
7
7
  } from 'rettiwt-core';
8
8
 
9
- // TYPES
10
- import { ITweet, ITweetEntities } from '../../types/public/Tweet';
11
-
12
9
  // MODELS
13
10
  import { User } from './User';
14
11
 
@@ -20,21 +17,50 @@ import { normalizeText } from '../../helper/JsonUtils';
20
17
  *
21
18
  * @public
22
19
  */
23
- export class Tweet implements ITweet {
20
+ export class Tweet {
21
+ /** The rest id of the tweet. */
24
22
  public id: string;
23
+
24
+ /** The details of the user who made the tweet. */
25
25
  public tweetBy: User;
26
+
27
+ /** The date and time of creation of the tweet, in UTC string format. */
26
28
  public createdAt: string;
29
+
30
+ /** Additional tweet entities like urls, mentions, etc. */
27
31
  public entities: TweetEntities;
32
+
33
+ /** The urls of the media contents of the tweet (if any). */
28
34
  public media: TweetMedia[];
35
+
36
+ /** The rest id of the tweet which is quoted in the tweet. */
29
37
  public quoted: string;
38
+
39
+ /** The full text content of the tweet. */
30
40
  public fullText: string;
41
+
42
+ /** The rest id of the user to which the tweet is a reply. */
31
43
  public replyTo: string;
44
+
45
+ /** The language in which the tweet is written. */
32
46
  public lang: string;
47
+
48
+ /** The number of quotes of the tweet. */
33
49
  public quoteCount: number;
50
+
51
+ /** The number of replies to the tweet. */
34
52
  public replyCount: number;
53
+
54
+ /** The number of retweets of the tweet. */
35
55
  public retweetCount: number;
56
+
57
+ /** The number of likes of the tweet. */
36
58
  public likeCount: number;
59
+
60
+ /** The number of views of a tweet. */
37
61
  public viewCount: number;
62
+
63
+ /** The number of bookmarks of a tweet. */
38
64
  public bookmarkCount: number;
39
65
 
40
66
  /**
@@ -66,9 +92,14 @@ export class Tweet implements ITweet {
66
92
  *
67
93
  * @public
68
94
  */
69
- export class TweetEntities implements ITweetEntities {
95
+ export class TweetEntities {
96
+ /** The list of hashtags mentioned in the tweet. */
70
97
  public hashtags: string[] = [];
98
+
99
+ /** The list of urls mentioned in the tweet. */
71
100
  public urls: string[] = [];
101
+
102
+ /** The list of IDs of users mentioned in the tweet. */
72
103
  public mentionedUsers: string[] = [];
73
104
 
74
105
  /**
@@ -106,7 +137,10 @@ export class TweetEntities implements ITweetEntities {
106
137
  * @public
107
138
  */
108
139
  export class TweetMedia {
140
+ /** The type of media. */
109
141
  public type: EMediaType;
142
+
143
+ /** The direct URL to the media. */
110
144
  public url: string = '';
111
145
 
112
146
  /**
@@ -1,28 +1,52 @@
1
1
  // PACKAGES
2
2
  import { IUser as IRawUser } from 'rettiwt-core';
3
3
 
4
- // TYPES
5
- import { IUser } from '../../types/public/User';
6
-
7
4
  /**
8
5
  * The details of a single user.
9
6
  *
10
7
  * @public
11
8
  */
12
- export class User implements IUser {
9
+ export class User {
10
+ /** The rest id of the user. */
13
11
  public id: string;
12
+
13
+ /** The username/screenname of the user. */
14
14
  public userName: string;
15
+
16
+ /** The full name of the user. */
15
17
  public fullName: string;
18
+
19
+ /** The creation date of user's account. */
16
20
  public createdAt: string;
21
+
22
+ /** The user's description. */
17
23
  public description: string;
24
+
25
+ /** Whether the account is verified or not. */
18
26
  public isVerified: boolean;
27
+
28
+ /** The number of tweets liked by the user. */
19
29
  public favouritesCount: number;
30
+
31
+ /** The number of followers of the user. */
20
32
  public followersCount: number;
33
+
34
+ /** The number of following of the user. */
21
35
  public followingsCount: number;
36
+
37
+ /** The number of tweets made by the user. */
22
38
  public statusesCount: number;
39
+
40
+ /** The location of user as provided by user. */
23
41
  public location: string;
42
+
43
+ /** The rest id of the tweet pinned in the user's profile. */
24
44
  public pinnedTweet: string;
45
+
46
+ /** The url of the profile banner image. */
25
47
  public profileBanner: string;
48
+
49
+ /** The url of the profile image. */
26
50
  public profileImage: string;
27
51
 
28
52
  /**
@@ -0,0 +1,24 @@
1
+ // ERRORS
2
+ import { RettiwtError } from './RettiwtError';
3
+
4
+ /**
5
+ * Represents an error that is thrown by Twitter API.
6
+ *
7
+ * @internal
8
+ */
9
+ export class ApiError extends RettiwtError {
10
+ /** The error code thrown by Twitter API. */
11
+ public code: number;
12
+
13
+ /**
14
+ * Initializes a new ApiError based on the given error details.
15
+ *
16
+ * @param errorCode - The error code thrown by Twitter API.
17
+ * @param message - Any additional error message.
18
+ */
19
+ public constructor(errorCode: number, message?: string) {
20
+ super(message);
21
+
22
+ this.code = errorCode;
23
+ }
24
+ }
@@ -0,0 +1,24 @@
1
+ // ERRORS
2
+ import { RettiwtError } from './RettiwtError';
3
+
4
+ /**
5
+ * Represents an HTTP error that occues while making a request to Twitter API.
6
+ *
7
+ * @internal
8
+ */
9
+ export class HttpError extends RettiwtError {
10
+ /** The HTTP status code. */
11
+ public status: number;
12
+
13
+ /**
14
+ * Initializes a new HttpError based on the given error details.
15
+ *
16
+ * @param httpStatus - The HTTP status code received upon making the request
17
+ * @param message - Any additional error message.
18
+ */
19
+ public constructor(httpStatus: number, message?: string) {
20
+ super(message);
21
+
22
+ this.status = httpStatus;
23
+ }
24
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Represents an error that arises inside the package.
3
+ *
4
+ * @internal
5
+ */
6
+ export class RettiwtError extends Error {
7
+ public constructor(message?: string) {
8
+ super(message);
9
+
10
+ Object.setPrototypeOf(this, RettiwtError.prototype);
11
+ }
12
+ }
@@ -0,0 +1,18 @@
1
+ // ERRORS
2
+ import { RettiwtError } from './RettiwtError';
3
+
4
+ /**
5
+ * Represents an HTTP error that occues while making a request to Twitter API.
6
+ *
7
+ * @internal
8
+ */
9
+ export class TimeoutError extends RettiwtError {
10
+ /**
11
+ * Initializes a new TimeoutError based on the given error details.
12
+ *
13
+ * @param message - Error message with the configured timeout.
14
+ */
15
+ public constructor(message?: string) {
16
+ super(message);
17
+ }
18
+ }
@@ -0,0 +1,158 @@
1
+ // PACKAGES
2
+ import axios, { AxiosError, AxiosResponse } from 'axios';
3
+ import { findKeyByValue } from '../../helper/JsonUtils';
4
+
5
+ // TYPES
6
+ import { IErrorHandler } from '../../types/ErrorHandler';
7
+
8
+ // ENUMS
9
+ import { EApiErrors } from '../../enums/Api';
10
+ import { EHttpStatus } from '../../enums/Http';
11
+ import { EErrorCodes } from 'rettiwt-core';
12
+
13
+ // ERRORS
14
+ import { ApiError } from '../../models/errors/ApiError';
15
+ import { HttpError } from '../../models/errors/HttpError';
16
+ import { TimeoutError } from '../../models/errors/TimeoutError';
17
+
18
+ // TODO Refactor and document this module
19
+
20
+ /**
21
+ * The base service that handles any errors.
22
+ *
23
+ * @public
24
+ */
25
+ export class ErrorService implements IErrorHandler {
26
+ /**
27
+ * Error message used when the specific error type is not defined in the required enums.
28
+ */
29
+ protected static readonly DEFAULT_ERROR_MESSAGE = 'Unknown error';
30
+
31
+ /**
32
+ * The method called when an error response is received from Twitter API.
33
+ *
34
+ * @param error - The error caught while making HTTP request to Twitter API.
35
+ */
36
+ public handle(error: unknown): void {
37
+ if (!axios.isAxiosError(error)) {
38
+ throw error;
39
+ }
40
+
41
+ this.handleTimeoutError(error);
42
+
43
+ const axiosResponse = this.getAxiosResponse(error);
44
+ this.handleApiError(axiosResponse);
45
+ this.handleHttpError(axiosResponse);
46
+ }
47
+
48
+ /**
49
+ * Handles exceeded timeout, configured in RettiwtConfig.
50
+ *
51
+ * @param error - The error object.
52
+ * @throws An error if the configured request timeout has been exceeded.
53
+ */
54
+ protected handleTimeoutError(error: AxiosError): void {
55
+ if (error.code === 'ECONNABORTED') {
56
+ throw new TimeoutError(error.message);
57
+ }
58
+ }
59
+
60
+ /**
61
+ * Retrieves the response data from the given error.
62
+ *
63
+ * @param error - The error object.
64
+ * @returns The response data.
65
+ * @throws The original error if it is not an HTTP error with a response.
66
+ */
67
+ protected getAxiosResponse(error: AxiosError): AxiosResponse {
68
+ if (error.response) {
69
+ return error.response;
70
+ }
71
+
72
+ throw error;
73
+ }
74
+
75
+ /**
76
+ * Handles HTTP error in a response.
77
+ *
78
+ * @param response - The response object received.
79
+ * @throws An error with the corresponding HTTP status text if any HTTP-related error has occurred.
80
+ */
81
+ protected handleHttpError(response: AxiosResponse): void {
82
+ throw this.createHttpError(response.status);
83
+ }
84
+
85
+ /**
86
+ * Handles API error in a response.
87
+ *
88
+ * @param response - The response object received.
89
+ * @throws An error with the corresponding API error message if any API-related error has occurred.
90
+ */
91
+ protected handleApiError(response: AxiosResponse): void {
92
+ const errorCode = this.getErrorCode(response);
93
+
94
+ if (errorCode === undefined) {
95
+ return;
96
+ }
97
+
98
+ throw this.createApiError(errorCode);
99
+ }
100
+
101
+ /**
102
+ * Creates an HTTP error instance based on the provided HTTP status.
103
+ *
104
+ * @param httpStatus - The HTTP status code.
105
+ * @returns An HTTP error instance.
106
+ */
107
+ protected createHttpError(httpStatus: number): HttpError {
108
+ return new HttpError(httpStatus, this.getHttpErrorMessage(httpStatus));
109
+ }
110
+
111
+ /**
112
+ * Retrieves the HTTP error message based on the provided HTTP status.
113
+ *
114
+ * @param httpStatus - The HTTP status code.
115
+ * @returns The HTTP error message.
116
+ */
117
+ protected getHttpErrorMessage(httpStatus: number): string {
118
+ return Object.values(EHttpStatus).includes(httpStatus)
119
+ ? EHttpStatus[httpStatus]
120
+ : ErrorService.DEFAULT_ERROR_MESSAGE;
121
+ }
122
+
123
+ /**
124
+ * Retrieves the API error code from the Axios response data.
125
+ *
126
+ * @param response - The response object received.
127
+ * @returns The error code, or undefined if not found.
128
+ */
129
+ protected getErrorCode(response: AxiosResponse): number | undefined {
130
+ const errors = (response.data as { errors: { code: number }[] }).errors;
131
+
132
+ return !!errors && errors.length ? errors[0].code : undefined;
133
+ }
134
+
135
+ /**
136
+ * Creates an API error instance based on the provided error code.
137
+ *
138
+ * @param errorCode - The error code.
139
+ * @returns An API error instance.
140
+ */
141
+ protected createApiError(errorCode: number): ApiError {
142
+ return new ApiError(errorCode, this.getApiErrorMessage(errorCode));
143
+ }
144
+
145
+ /**
146
+ * Retrieves the API error message based on the provided error code.
147
+ *
148
+ * @param errorCode - The error code.
149
+ * @returns The API error message.
150
+ */
151
+ protected getApiErrorMessage(errorCode: number): string {
152
+ const errorCodeKey = findKeyByValue(EErrorCodes, errorCode.toString());
153
+
154
+ return !!errorCodeKey && errorCodeKey in EApiErrors
155
+ ? EApiErrors[errorCodeKey as keyof typeof EApiErrors]
156
+ : ErrorService.DEFAULT_ERROR_MESSAGE;
157
+ }
158
+ }
@@ -1,7 +1,8 @@
1
1
  // PACKAGES
2
2
  import {
3
3
  Request,
4
- Args,
4
+ FetchArgs,
5
+ PostArgs,
5
6
  EResourceType,
6
7
  ICursor as IRawCursor,
7
8
  ITweet as IRawTweet,
@@ -9,29 +10,34 @@ import {
9
10
  ITimelineTweet,
10
11
  ITimelineUser,
11
12
  IResponse,
12
- EErrorCodes,
13
+ EUploadSteps,
14
+ IMediaUploadInitializeResponse,
13
15
  } from 'rettiwt-core';
14
- import axios, { AxiosRequestConfig, AxiosRequestHeaders, AxiosResponse } from 'axios';
16
+ import axios, { AxiosRequestConfig, AxiosResponse } from 'axios';
15
17
  import https, { Agent } from 'https';
16
18
  import { AuthCredential, Auth } from 'rettiwt-auth';
17
19
  import { HttpsProxyAgent } from 'https-proxy-agent';
18
20
 
19
21
  // SERVICES
22
+ import { ErrorService } from './ErrorService';
20
23
  import { LogService } from './LogService';
21
24
 
25
+ // TYPES
26
+ import { IRettiwtConfig } from '../../types/RettiwtConfig';
27
+ import { IErrorHandler } from '../../types/ErrorHandler';
28
+
22
29
  // ENUMS
23
- import { EHttpStatus } from '../../enums/HTTP';
24
- import { EApiErrors } from '../../enums/ApiErrors';
30
+ import { EApiErrors } from '../../enums/Api';
25
31
  import { ELogActions } from '../../enums/Logging';
26
32
 
27
33
  // MODELS
28
- import { RettiwtConfig } from '../../models/internal/RettiwtConfig';
29
- import { CursoredData } from '../../models/public/CursoredData';
30
- import { Tweet } from '../../models/public/Tweet';
31
- import { User } from '../../models/public/User';
34
+ import { CursoredData } from '../../models/data/CursoredData';
35
+ import { Tweet } from '../../models/data/Tweet';
36
+ import { User } from '../../models/data/User';
32
37
 
33
38
  // HELPERS
34
- import { findByFilter, findKeyByValue } from '../../helper/JsonUtils';
39
+ import { findByFilter } from '../../helper/JsonUtils';
40
+ import { statSync } from 'fs';
35
41
 
36
42
  /**
37
43
  * The base service that handles all HTTP requests.
@@ -45,16 +51,25 @@ export class FetcherService {
45
51
  /** Whether the instance is authenticated or not. */
46
52
  private readonly isAuthenticated: boolean;
47
53
 
54
+ /** The URL to the proxy server to use for authentication. */
55
+ protected readonly authProxyUrl?: URL;
56
+
48
57
  /** The HTTPS Agent to use for requests to Twitter API. */
49
58
  private readonly httpsAgent: Agent;
50
59
 
60
+ /** The max wait time for a response. */
61
+ private readonly timeout: number;
62
+
51
63
  /** The log service instance to use to logging. */
52
64
  private readonly logger: LogService;
53
65
 
66
+ /** The service used to handle HTTP and API errors */
67
+ private readonly errorHandler: IErrorHandler;
68
+
54
69
  /**
55
70
  * @param config - The config object for configuring the Rettiwt instance.
56
71
  */
57
- public constructor(config?: RettiwtConfig) {
72
+ public constructor(config?: IRettiwtConfig) {
58
73
  // If API key is supplied
59
74
  if (config?.apiKey) {
60
75
  this.cred = this.getAuthCredential(config.apiKey);
@@ -68,8 +83,11 @@ export class FetcherService {
68
83
  this.cred = undefined;
69
84
  }
70
85
  this.isAuthenticated = config?.apiKey ? true : false;
86
+ this.authProxyUrl = config?.authProxyUrl ?? config?.proxyUrl;
71
87
  this.httpsAgent = this.getHttpsAgent(config?.proxyUrl);
88
+ this.timeout = config?.timeout ?? 0;
72
89
  this.logger = new LogService(config?.logging);
90
+ this.errorHandler = config?.errorHandler ?? new ErrorService();
73
91
  }
74
92
 
75
93
  /**
@@ -92,9 +110,6 @@ export class FetcherService {
92
110
  * @returns The generated AuthCredential.
93
111
  */
94
112
  private getGuestCredential(guestKey: string): AuthCredential {
95
- // Converting guestKey from base64 to string
96
- guestKey = Buffer.from(guestKey).toString('ascii');
97
-
98
113
  return new AuthCredential(undefined, guestKey);
99
114
  }
100
115
 
@@ -133,79 +148,33 @@ export class FetcherService {
133
148
  return new https.Agent();
134
149
  }
135
150
 
136
- /**
137
- * The middleware for handling any http error.
138
- *
139
- * @param res - The response object received.
140
- * @returns The received response, if no HTTP errors are found.
141
- * @throws An error if any HTTP-related error has occured.
142
- */
143
- private handleHttpError(res: AxiosResponse<IResponse<unknown>>): AxiosResponse<IResponse<unknown>> {
144
- /**
145
- * If the status code is not 200 =\> the HTTP request was not successful. hence throwing error
146
- */
147
- if (res.status != 200 && res.status in EHttpStatus) {
148
- throw new Error(EHttpStatus[res.status]);
149
- }
150
-
151
- return res;
152
- }
153
-
154
- /**
155
- * The middleware for handling any Twitter API-level errors.
156
- *
157
- * @param res - The response object received.
158
- * @returns The received response, if no API errors are found.
159
- * @throws An error if any API-related error has occured.
160
- */
161
- private handleApiError(res: AxiosResponse<IResponse<unknown>>): AxiosResponse<IResponse<unknown>> {
162
- // If error exists
163
- if (res.data.errors && res.data.errors.length) {
164
- // Getting the error code
165
- const code: number = res.data.errors[0].code;
166
-
167
- // Getting the error message
168
- const message: string = EApiErrors[
169
- findKeyByValue(EErrorCodes, `${code}`) as keyof typeof EApiErrors
170
- ] as string;
171
-
172
- // Throw the error
173
- throw new Error(message);
174
- }
175
-
176
- return res;
177
- }
178
-
179
151
  /**
180
152
  * Makes an HTTP request according to the given parameters.
181
153
  *
154
+ * @typeParam ResType - The type of the returned response data.
182
155
  * @param config - The request configuration.
183
156
  * @returns The response received.
184
157
  */
185
- private async request(config: Request): Promise<AxiosResponse<IResponse<unknown>>> {
158
+ private async request<ResType>(config: AxiosRequestConfig): Promise<AxiosResponse<ResType>> {
186
159
  // Checking authorization for the requested resource
187
- this.checkAuthorization(config.endpoint);
160
+ this.checkAuthorization(config.url as EResourceType);
188
161
 
189
162
  // If not authenticated, use guest authentication
190
- this.cred = this.cred ?? (await new Auth().getGuestCredential());
163
+ this.cred = this.cred ?? (await new Auth({ proxyUrl: this.authProxyUrl }).getGuestCredential());
191
164
 
192
- /**
193
- * Creating axios request configuration from the input configuration.
194
- */
195
- const axiosRequest: AxiosRequestConfig = {
196
- url: config.url,
197
- method: config.type,
198
- data: config.payload,
199
- headers: JSON.parse(JSON.stringify(this.cred.toHeader())) as AxiosRequestHeaders,
200
- httpsAgent: this.httpsAgent,
201
- };
165
+ // Setting additional request parameters
166
+ config.headers = { ...config.headers, ...this.cred.toHeader() };
167
+ config.httpAgent = this.httpsAgent;
168
+ config.timeout = this.timeout;
202
169
 
203
170
  /**
204
- * After making the request, the response is then passed to HTTP error handling middleware for HTTP error handling.
171
+ * If Axios request results in an error, catch it and rethrow a more specific error.
205
172
  */
206
- return await axios<IResponse<unknown>>(axiosRequest)
207
- .then((res) => this.handleHttpError(res))
208
- .then((res) => this.handleApiError(res));
173
+ return await axios<ResType>(config).catch((error: unknown) => {
174
+ this.errorHandler.handle(error);
175
+
176
+ throw error;
177
+ });
209
178
  }
210
179
 
211
180
  /**
@@ -308,16 +277,16 @@ export class FetcherService {
308
277
  */
309
278
  protected async fetch<OutType extends Tweet | User>(
310
279
  resourceType: EResourceType,
311
- args: Args,
280
+ args: FetchArgs,
312
281
  ): Promise<CursoredData<OutType>> {
313
282
  // Logging
314
283
  this.logger.log(ELogActions.FETCH, { resourceType: resourceType, args: args });
315
284
 
316
285
  // Preparing the HTTP request
317
- const request: Request = new Request(resourceType, args);
286
+ const request: AxiosRequestConfig = new Request(resourceType, args).toAxiosRequestConfig();
318
287
 
319
288
  // Getting the raw data
320
- const res = await this.request(request).then((res) => res.data);
289
+ const res = await this.request<IResponse<unknown>>(request).then((res) => res.data);
321
290
 
322
291
  // Extracting data
323
292
  const extractedData = this.extractData(res, resourceType);
@@ -335,16 +304,61 @@ export class FetcherService {
335
304
  * @param args - Resource specific arguments.
336
305
  * @returns Whether posting was successful or not.
337
306
  */
338
- protected async post(resourceType: EResourceType, args: Args): Promise<boolean> {
307
+ protected async post(resourceType: EResourceType, args: PostArgs): Promise<boolean> {
339
308
  // Logging
340
309
  this.logger.log(ELogActions.POST, { resourceType: resourceType, args: args });
341
310
 
342
311
  // Preparing the HTTP request
343
- const request: Request = new Request(resourceType, args);
312
+ const request: AxiosRequestConfig = new Request(resourceType, args).toAxiosRequestConfig();
344
313
 
345
314
  // Posting the data
346
- await this.request(request);
315
+ await this.request<unknown>(request);
347
316
 
348
317
  return true;
349
318
  }
319
+
320
+ /**
321
+ * Uploads the given media file to Twitter
322
+ *
323
+ * @param media - The path to the media file to upload.
324
+ * @returns The id of the uploaded media.
325
+ */
326
+ protected async upload(media: string): Promise<string> {
327
+ // INITIALIZE
328
+
329
+ // Logging
330
+ this.logger.log(ELogActions.UPLOAD, { step: EUploadSteps.INITIALIZE });
331
+
332
+ const id: string = (
333
+ await this.request<IMediaUploadInitializeResponse>(
334
+ new Request(EResourceType.MEDIA_UPLOAD, {
335
+ upload: { step: EUploadSteps.INITIALIZE, size: statSync(media).size },
336
+ }).toAxiosRequestConfig(),
337
+ )
338
+ ).data.media_id_string;
339
+
340
+ // APPEND
341
+
342
+ // Logging
343
+ this.logger.log(ELogActions.UPLOAD, { step: EUploadSteps.APPEND });
344
+
345
+ await this.request<unknown>(
346
+ new Request(EResourceType.MEDIA_UPLOAD, {
347
+ upload: { step: EUploadSteps.APPEND, id: id, media: media },
348
+ }).toAxiosRequestConfig(),
349
+ );
350
+
351
+ // FINALIZE
352
+
353
+ // Logging
354
+ this.logger.log(ELogActions.UPLOAD, { step: EUploadSteps.APPEND });
355
+
356
+ await this.request<unknown>(
357
+ new Request(EResourceType.MEDIA_UPLOAD, {
358
+ upload: { step: EUploadSteps.FINALIZE, id: id },
359
+ }).toAxiosRequestConfig(),
360
+ );
361
+
362
+ return id;
363
+ }
350
364
  }