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.
- package/.eslintrc.js +5 -0
- package/.github/workflows/documentation.yml +1 -1
- package/.github/workflows/publish.yml +1 -1
- package/.prettierrc +1 -1
- package/README.md +69 -19
- package/dist/Rettiwt.d.ts +7 -4
- package/dist/Rettiwt.js +4 -2
- package/dist/Rettiwt.js.map +1 -1
- package/dist/cli.js +4 -2
- package/dist/cli.js.map +1 -1
- package/dist/commands/Auth.d.ts +2 -1
- package/dist/commands/Auth.js +10 -17
- package/dist/commands/Auth.js.map +1 -1
- package/dist/commands/Tweet.js +36 -3
- package/dist/commands/Tweet.js.map +1 -1
- package/dist/enums/{ApiErrors.js → Api.js} +1 -1
- package/dist/enums/Api.js.map +1 -0
- package/dist/enums/Http.d.ts +68 -0
- package/dist/enums/Http.js +73 -0
- package/dist/enums/Http.js.map +1 -0
- package/dist/enums/Logging.d.ts +1 -0
- package/dist/enums/Logging.js +1 -0
- package/dist/enums/Logging.js.map +1 -1
- package/dist/index.d.ts +15 -14
- package/dist/index.js +22 -17
- package/dist/index.js.map +1 -1
- package/dist/models/args/TweetArgs.d.ts +44 -0
- package/dist/models/args/TweetArgs.js +82 -0
- package/dist/models/args/TweetArgs.js.map +1 -0
- package/dist/models/{public → data}/CursoredData.d.ts +5 -3
- package/dist/models/{public → data}/CursoredData.js +1 -0
- package/dist/models/data/CursoredData.js.map +1 -0
- package/dist/{types/public → models/data}/List.d.ts +8 -1
- package/dist/models/data/List.js.map +1 -0
- package/dist/models/data/Media.d.ts +14 -0
- package/dist/models/data/Media.js +19 -0
- package/dist/models/data/Media.js.map +1 -0
- package/dist/{types/public → models/data}/Tweet.d.ts +26 -8
- package/dist/models/{public → data}/Tweet.js +4 -0
- package/dist/models/data/Tweet.js.map +1 -0
- package/dist/{types/public → models/data}/User.d.ts +8 -1
- package/dist/models/data/User.js.map +1 -0
- package/dist/models/errors/ApiError.d.ts +17 -0
- package/dist/models/errors/ApiError.js +42 -0
- package/dist/models/errors/ApiError.js.map +1 -0
- package/dist/models/errors/HttpError.d.ts +17 -0
- package/dist/models/errors/HttpError.js +42 -0
- package/dist/models/errors/HttpError.js.map +1 -0
- package/dist/models/errors/RettiwtError.d.ts +8 -0
- package/dist/models/errors/RettiwtError.js +34 -0
- package/dist/models/errors/RettiwtError.js.map +1 -0
- package/dist/models/errors/TimeoutError.d.ts +14 -0
- package/dist/models/errors/TimeoutError.js +39 -0
- package/dist/models/errors/TimeoutError.js.map +1 -0
- package/dist/services/internal/ErrorService.d.ts +85 -0
- package/dist/services/internal/ErrorService.js +144 -0
- package/dist/services/internal/ErrorService.js.map +1 -0
- package/dist/services/internal/FetcherService.d.ts +22 -24
- package/dist/services/internal/FetcherService.js +79 -59
- package/dist/services/internal/FetcherService.js.map +1 -1
- package/dist/services/public/AuthService.d.ts +66 -0
- package/dist/services/public/AuthService.js +160 -0
- package/dist/services/public/AuthService.js.map +1 -0
- package/dist/services/public/TweetService.d.ts +27 -8
- package/dist/services/public/TweetService.js +45 -8
- package/dist/services/public/TweetService.js.map +1 -1
- package/dist/services/public/UserService.d.ts +5 -5
- package/dist/services/public/UserService.js.map +1 -1
- package/dist/types/ErrorHandler.d.ts +13 -0
- package/dist/types/{public/User.js → ErrorHandler.js} +1 -1
- package/dist/types/ErrorHandler.js.map +1 -0
- package/dist/types/RettiwtConfig.d.ts +32 -0
- package/dist/types/RettiwtConfig.js.map +1 -0
- package/package.json +6 -5
- package/src/Rettiwt.ts +10 -5
- package/src/cli.ts +4 -2
- package/src/commands/Auth.ts +5 -16
- package/src/commands/Tweet.ts +56 -3
- package/src/enums/Http.ts +68 -0
- package/src/enums/Logging.ts +1 -0
- package/src/index.ts +25 -18
- package/src/models/args/TweetArgs.ts +98 -0
- package/src/models/{public → data}/CursoredData.ts +6 -5
- package/src/models/{public → data}/List.ts +14 -4
- package/src/models/data/Media.ts +19 -0
- package/src/models/{public → data}/Tweet.ts +39 -5
- package/src/models/{public → data}/User.ts +28 -4
- package/src/models/errors/ApiError.ts +24 -0
- package/src/models/errors/HttpError.ts +24 -0
- package/src/models/errors/RettiwtError.ts +12 -0
- package/src/models/errors/TimeoutError.ts +18 -0
- package/src/services/internal/ErrorService.ts +158 -0
- package/src/services/internal/FetcherService.ts +94 -80
- package/src/services/public/AuthService.ts +97 -0
- package/src/services/public/TweetService.ts +48 -10
- package/src/services/public/UserService.ts +7 -5
- package/src/types/ErrorHandler.ts +13 -0
- package/src/types/RettiwtConfig.ts +40 -0
- package/dist/enums/ApiErrors.js.map +0 -1
- package/dist/enums/HTTP.d.ts +0 -17
- package/dist/enums/HTTP.js +0 -22
- package/dist/enums/HTTP.js.map +0 -1
- package/dist/models/internal/RettiwtConfig.d.ts +0 -18
- package/dist/models/internal/RettiwtConfig.js +0 -24
- package/dist/models/internal/RettiwtConfig.js.map +0 -1
- package/dist/models/public/CursoredData.js.map +0 -1
- package/dist/models/public/List.d.ts +0 -22
- package/dist/models/public/List.js.map +0 -1
- package/dist/models/public/Tweet.d.ts +0 -62
- package/dist/models/public/Tweet.js.map +0 -1
- package/dist/models/public/User.d.ts +0 -29
- package/dist/models/public/User.js.map +0 -1
- package/dist/types/internal/RettiwtConfig.d.ts +0 -15
- package/dist/types/internal/RettiwtConfig.js.map +0 -1
- package/dist/types/public/CursoredData.d.ts +0 -22
- package/dist/types/public/CursoredData.js +0 -3
- package/dist/types/public/CursoredData.js.map +0 -1
- package/dist/types/public/List.js +0 -3
- package/dist/types/public/List.js.map +0 -1
- package/dist/types/public/Tweet.js +0 -3
- package/dist/types/public/Tweet.js.map +0 -1
- package/dist/types/public/User.js.map +0 -1
- package/src/enums/HTTP.ts +0 -17
- package/src/models/internal/RettiwtConfig.ts +0 -26
- package/src/types/internal/RettiwtConfig.ts +0 -18
- package/src/types/public/CursoredData.ts +0 -24
- package/src/types/public/List.ts +0 -27
- package/src/types/public/Tweet.ts +0 -86
- package/src/types/public/User.ts +0 -48
- /package/dist/enums/{ApiErrors.d.ts → Api.d.ts} +0 -0
- /package/dist/models/{public → data}/List.js +0 -0
- /package/dist/models/{public → data}/User.js +0 -0
- /package/dist/types/{internal/RettiwtConfig.js → RettiwtConfig.js} +0 -0
- /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
|
|
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
|
|
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
|
|
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,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
|
-
|
|
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
|
-
|
|
13
|
+
EUploadSteps,
|
|
14
|
+
IMediaUploadInitializeResponse,
|
|
13
15
|
} from 'rettiwt-core';
|
|
14
|
-
import axios, { AxiosRequestConfig,
|
|
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 {
|
|
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 {
|
|
29
|
-
import {
|
|
30
|
-
import {
|
|
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
|
|
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?:
|
|
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:
|
|
158
|
+
private async request<ResType>(config: AxiosRequestConfig): Promise<AxiosResponse<ResType>> {
|
|
186
159
|
// Checking authorization for the requested resource
|
|
187
|
-
this.checkAuthorization(config.
|
|
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
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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
|
-
*
|
|
171
|
+
* If Axios request results in an error, catch it and rethrow a more specific error.
|
|
205
172
|
*/
|
|
206
|
-
return await axios<
|
|
207
|
-
|
|
208
|
-
|
|
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:
|
|
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:
|
|
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:
|
|
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:
|
|
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
|
}
|