rettiwt-api 2.4.1 → 2.5.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 (133) 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 +118 -60
  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 +1 -1
  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 +21 -17
  26. package/dist/index.js.map +1 -1
  27. package/dist/models/{public → data}/CursoredData.d.ts +5 -3
  28. package/dist/models/{public → data}/CursoredData.js +1 -0
  29. package/dist/models/data/CursoredData.js.map +1 -0
  30. package/dist/{types/public → models/data}/List.d.ts +8 -1
  31. package/dist/models/data/List.js.map +1 -0
  32. package/dist/models/data/Media.d.ts +14 -0
  33. package/dist/models/data/Media.js +19 -0
  34. package/dist/models/data/Media.js.map +1 -0
  35. package/dist/{types/public → models/data}/Tweet.d.ts +26 -8
  36. package/dist/models/{public → data}/Tweet.js +4 -0
  37. package/dist/models/data/Tweet.js.map +1 -0
  38. package/dist/{types/public → models/data}/User.d.ts +8 -1
  39. package/dist/models/data/User.js.map +1 -0
  40. package/dist/models/errors/ApiError.d.ts +17 -0
  41. package/dist/models/errors/ApiError.js +42 -0
  42. package/dist/models/errors/ApiError.js.map +1 -0
  43. package/dist/models/errors/HttpError.d.ts +17 -0
  44. package/dist/models/errors/HttpError.js +42 -0
  45. package/dist/models/errors/HttpError.js.map +1 -0
  46. package/dist/models/errors/RettiwtError.d.ts +8 -0
  47. package/dist/models/errors/RettiwtError.js +34 -0
  48. package/dist/models/errors/RettiwtError.js.map +1 -0
  49. package/dist/models/errors/TimeoutError.d.ts +14 -0
  50. package/dist/models/errors/TimeoutError.js +39 -0
  51. package/dist/models/errors/TimeoutError.js.map +1 -0
  52. package/dist/services/internal/ErrorService.d.ts +85 -0
  53. package/dist/services/internal/ErrorService.js +144 -0
  54. package/dist/services/internal/ErrorService.js.map +1 -0
  55. package/dist/services/internal/FetcherService.d.ts +22 -24
  56. package/dist/services/internal/FetcherService.js +79 -59
  57. package/dist/services/internal/FetcherService.js.map +1 -1
  58. package/dist/services/public/AuthService.d.ts +66 -0
  59. package/dist/services/public/AuthService.js +160 -0
  60. package/dist/services/public/AuthService.js.map +1 -0
  61. package/dist/services/public/TweetService.d.ts +27 -8
  62. package/dist/services/public/TweetService.js +40 -5
  63. package/dist/services/public/TweetService.js.map +1 -1
  64. package/dist/services/public/UserService.d.ts +5 -5
  65. package/dist/services/public/UserService.js.map +1 -1
  66. package/dist/types/ErrorHandler.d.ts +13 -0
  67. package/dist/types/{public/User.js → ErrorHandler.js} +1 -1
  68. package/dist/types/ErrorHandler.js.map +1 -0
  69. package/dist/types/RettiwtConfig.d.ts +32 -0
  70. package/dist/types/RettiwtConfig.js.map +1 -0
  71. package/dist/types/args/TweetMediaArgs.d.ts +15 -0
  72. package/dist/types/{public/CursoredData.js → args/TweetMediaArgs.js} +1 -1
  73. package/dist/types/args/TweetMediaArgs.js.map +1 -0
  74. package/package.json +5 -5
  75. package/src/Rettiwt.ts +10 -5
  76. package/src/cli.ts +1 -1
  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 +23 -18
  82. package/src/models/{public → data}/CursoredData.ts +6 -5
  83. package/src/models/{public → data}/List.ts +14 -4
  84. package/src/models/data/Media.ts +19 -0
  85. package/src/models/{public → data}/Tweet.ts +39 -5
  86. package/src/models/{public → data}/User.ts +28 -4
  87. package/src/models/errors/ApiError.ts +24 -0
  88. package/src/models/errors/HttpError.ts +24 -0
  89. package/src/models/errors/RettiwtError.ts +12 -0
  90. package/src/models/errors/TimeoutError.ts +18 -0
  91. package/src/services/internal/ErrorService.ts +158 -0
  92. package/src/services/internal/FetcherService.ts +94 -80
  93. package/src/services/public/AuthService.ts +97 -0
  94. package/src/services/public/TweetService.ts +45 -10
  95. package/src/services/public/UserService.ts +7 -5
  96. package/src/types/ErrorHandler.ts +13 -0
  97. package/src/types/RettiwtConfig.ts +40 -0
  98. package/src/types/args/TweetMediaArgs.ts +16 -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.map +0 -1
  117. package/dist/types/public/List.js +0 -3
  118. package/dist/types/public/List.js.map +0 -1
  119. package/dist/types/public/Tweet.js +0 -3
  120. package/dist/types/public/Tweet.js.map +0 -1
  121. package/dist/types/public/User.js.map +0 -1
  122. package/src/enums/HTTP.ts +0 -17
  123. package/src/models/internal/RettiwtConfig.ts +0 -26
  124. package/src/types/internal/RettiwtConfig.ts +0 -18
  125. package/src/types/public/CursoredData.ts +0 -24
  126. package/src/types/public/List.ts +0 -27
  127. package/src/types/public/Tweet.ts +0 -86
  128. package/src/types/public/User.ts +0 -48
  129. /package/dist/enums/{ApiErrors.d.ts → Api.d.ts} +0 -0
  130. /package/dist/models/{public → data}/List.js +0 -0
  131. /package/dist/models/{public → data}/User.js +0 -0
  132. /package/dist/types/{internal/RettiwtConfig.js → RettiwtConfig.js} +0 -0
  133. /package/src/enums/{ApiErrors.ts → Api.ts} +0 -0
@@ -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
  }
@@ -0,0 +1,97 @@
1
+ // PACKAGES
2
+ import { Auth } from 'rettiwt-auth';
3
+
4
+ // SERVICES
5
+ import { FetcherService } from '../internal/FetcherService';
6
+
7
+ // TYPES
8
+ import { IRettiwtConfig } from '../../types/RettiwtConfig';
9
+
10
+ /**
11
+ * Handles authentication.
12
+ *
13
+ * @public
14
+ */
15
+ export class AuthService extends FetcherService {
16
+ /**
17
+ * @param config - The config object for configuring the Rettiwt instance.
18
+ *
19
+ * @internal
20
+ */
21
+ public constructor(config?: IRettiwtConfig) {
22
+ super(config);
23
+ }
24
+
25
+ /**
26
+ * Login to twitter using account credentials.
27
+ *
28
+ * @param email - The email id associated with the Twitter account.
29
+ * @param userName - The username associated with the Twitter account.
30
+ * @param password - The password to the Twitter account.
31
+ * @returns The API_KEY for the Twitter account.
32
+ *
33
+ * @example
34
+ * ```
35
+ * import { Rettiwt } from 'rettiwt-api';
36
+ *
37
+ * // Creating a new Rettiwt instance
38
+ * const rettiwt = new Rettiwt();
39
+ *
40
+ * // Logging in an getting the API_KEY
41
+ * rettiwt.auth.login("email@domain.com", "username", "password")
42
+ * .then(apiKey => {
43
+ * // Use the API_KEY
44
+ * ...
45
+ * })
46
+ * .catch(err => {
47
+ * console.log(err);
48
+ * });
49
+ * ```
50
+ */
51
+ public async login(email: string, userName: string, password: string): Promise<string> {
52
+ // Logging in and getting the credentials
53
+ let apiKey: string =
54
+ ((
55
+ await new Auth({ proxyUrl: this.authProxyUrl }).getUserCredential({
56
+ email: email,
57
+ userName: userName,
58
+ password: password,
59
+ })
60
+ ).toHeader().cookie as string) ?? '';
61
+
62
+ // Converting the credentials to base64 string
63
+ apiKey = Buffer.from(apiKey).toString('base64');
64
+
65
+ return apiKey;
66
+ }
67
+
68
+ /**
69
+ * Login to twitter as guest.
70
+ *
71
+ * @returns A new guest key.
72
+ *
73
+ * @example
74
+ * ```
75
+ * import { Rettiwt } from 'rettiwt-api';
76
+ *
77
+ * // Creating a new Rettiwt instance
78
+ * const rettiwt = new Rettiwt();
79
+ *
80
+ * // Logging in an getting a new guest key
81
+ * rettiwt.auth.guest()
82
+ * .then(guestKey => {
83
+ * // Use the guest key
84
+ * ...
85
+ * })
86
+ * .catch(err => {
87
+ * console.log(err);
88
+ * });
89
+ * ```
90
+ */
91
+ public async guest(): Promise<string> {
92
+ // Getting a new guest key
93
+ const guestKey: string = (await new Auth().getGuestCredential()).guestToken ?? '';
94
+
95
+ return guestKey;
96
+ }
97
+ }
@@ -1,14 +1,17 @@
1
1
  // PACKAGES
2
- import { EResourceType, TweetFilter } from 'rettiwt-core';
2
+ import { EResourceType, MediaArgs, TweetFilter } from 'rettiwt-core';
3
3
 
4
4
  // SERVICES
5
5
  import { FetcherService } from '../internal/FetcherService';
6
6
 
7
+ // TYPES
8
+ import { IRettiwtConfig } from '../../types/RettiwtConfig';
9
+
7
10
  // MODELS
8
- import { RettiwtConfig } from '../../models/internal/RettiwtConfig';
9
- import { Tweet } from '../../models/public/Tweet';
10
- import { User } from '../../models/public/User';
11
- import { CursoredData } from '../../models/public/CursoredData';
11
+ import { Tweet } from '../../models/data/Tweet';
12
+ import { User } from '../../models/data/User';
13
+ import { CursoredData } from '../../models/data/CursoredData';
14
+ import { ITweetMediaArgs } from '../../types/args/TweetMediaArgs';
12
15
 
13
16
  /**
14
17
  * Handles fetching of data related to tweets.
@@ -21,7 +24,7 @@ export class TweetService extends FetcherService {
21
24
  *
22
25
  * @internal
23
26
  */
24
- public constructor(config?: RettiwtConfig) {
27
+ public constructor(config?: IRettiwtConfig) {
25
28
  super(config);
26
29
  }
27
30
 
@@ -220,10 +223,11 @@ export class TweetService extends FetcherService {
220
223
  /**
221
224
  * Post a tweet.
222
225
  *
223
- * @param tweetText - The text to be posted, length must be \<= 280 characters.
226
+ * @param text - The text to be posted, length must be \<= 280 characters.
227
+ * @param media - The list of media to post in the tweet.
224
228
  * @returns Whether posting was successful or not.
225
229
  *
226
- * @example
230
+ * @example Posting a simple text
227
231
  * ```
228
232
  * import { Rettiwt } from 'rettiwt-api';
229
233
  *
@@ -240,11 +244,42 @@ export class TweetService extends FetcherService {
240
244
  * });
241
245
  * ```
242
246
  *
247
+ * @example Posting a tweet with an image
248
+ * ```
249
+ * import { Rettiwt } from 'rettiwt-api';
250
+ *
251
+ * // Creating a new Rettiwt instance using the given 'API_KEY'
252
+ * const rettiwt = new Rettiwt({ apiKey: API_KEY });
253
+ *
254
+ * // Posting a tweet, containing an image called 'mountains.jpg', to twitter
255
+ * rettiwt.tweet.tweet('What a nice view!', [{ path: 'mountains.jpg' }])
256
+ * .then(res => {
257
+ * console.log(res);
258
+ * })
259
+ * .catch(err => {
260
+ * console.log(err);
261
+ * });
262
+ * ```
263
+ *
243
264
  * @public
244
265
  */
245
- public async tweet(tweetText: string): Promise<boolean> {
266
+ public async tweet(text: string, media?: ITweetMediaArgs[]): Promise<boolean> {
267
+ /** Stores the list of media that has been uploaded */
268
+ const uploadedMedia: MediaArgs[] = [];
269
+
270
+ // If tweet includes media, upload the media items
271
+ if (media) {
272
+ for (const item of media) {
273
+ // Uploading the media item and getting it's allocated id
274
+ const id: string = await this.upload(item.path);
275
+
276
+ // Storing the uploaded media item
277
+ uploadedMedia.push({ id: id, tags: item.tags });
278
+ }
279
+ }
280
+
246
281
  // Posting the tweet
247
- const data = await this.post(EResourceType.CREATE_TWEET, { tweetText: tweetText });
282
+ const data = await this.post(EResourceType.CREATE_TWEET, { tweet: { text: text, media: uploadedMedia } });
248
283
 
249
284
  return data;
250
285
  }
@@ -4,13 +4,15 @@ import { EResourceType } from 'rettiwt-core';
4
4
  // SERVICES
5
5
  import { FetcherService } from '../internal/FetcherService';
6
6
 
7
+ // TYPES
8
+ import { IRettiwtConfig } from '../../types/RettiwtConfig';
9
+
7
10
  // MODELS
8
- import { RettiwtConfig } from '../../models/internal/RettiwtConfig';
9
- import { User } from '../../models/public/User';
10
- import { Tweet } from '../../models/public/Tweet';
11
+ import { User } from '../../models/data/User';
12
+ import { Tweet } from '../../models/data/Tweet';
11
13
 
12
14
  // TYPES
13
- import { CursoredData } from '../../models/public/CursoredData';
15
+ import { CursoredData } from '../../models/data/CursoredData';
14
16
 
15
17
  /**
16
18
  * Handles fetching of data related to user account
@@ -23,7 +25,7 @@ export class UserService extends FetcherService {
23
25
  *
24
26
  * @internal
25
27
  */
26
- public constructor(config?: RettiwtConfig) {
28
+ public constructor(config?: IRettiwtConfig) {
27
29
  super(config);
28
30
  }
29
31
 
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Defines the error handler that processes API/HTTP errors in the responses.
3
+ *
4
+ * @public
5
+ */
6
+ export interface IErrorHandler {
7
+ /**
8
+ * The method called when an error response is received from Twitter API.
9
+ *
10
+ * @param error - The error caught while making request to Twitter API.
11
+ */
12
+ handle(error: unknown): void;
13
+ }