@glagan/rettiwt-api 7.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.js +166 -0
- package/.gitattributes +3 -0
- package/.github/FUNDING.yml +4 -0
- package/.github/ISSUE_TEMPLATE/bug-report.yml +57 -0
- package/.github/ISSUE_TEMPLATE/feature-request.yml +20 -0
- package/.github/ISSUE_TEMPLATE/question.yml +15 -0
- package/.github/PULL_REQUEST_TEMPLATE.md +32 -0
- package/.github/workflows/ci.yml +32 -0
- package/.github/workflows/publish.yml +23 -0
- package/.nvmrc +1 -0
- package/.prettierignore +3 -0
- package/.prettierrc +13 -0
- package/LICENSE +21 -0
- package/README.md +566 -0
- package/dist/cli.js +43 -0
- package/eslint.config.mjs +17 -0
- package/package.json +50 -0
- package/src/Rettiwt.ts +97 -0
- package/src/cli.ts +48 -0
- package/src/collections/Extractors.ts +155 -0
- package/src/collections/Groups.ts +81 -0
- package/src/collections/Requests.ts +89 -0
- package/src/collections/Tweet.ts +17 -0
- package/src/commands/DirectMessage.ts +62 -0
- package/src/commands/List.ts +90 -0
- package/src/commands/Tweet.ts +437 -0
- package/src/commands/User.ts +367 -0
- package/src/enums/Api.ts +10 -0
- package/src/enums/Authentication.ts +10 -0
- package/src/enums/Data.ts +13 -0
- package/src/enums/Logging.ts +14 -0
- package/src/enums/Media.ts +10 -0
- package/src/enums/Notification.ts +12 -0
- package/src/enums/Resource.ts +69 -0
- package/src/enums/Tweet.ts +8 -0
- package/src/enums/raw/Analytics.ts +32 -0
- package/src/enums/raw/Media.ts +10 -0
- package/src/enums/raw/Notification.ts +11 -0
- package/src/enums/raw/Tweet.ts +20 -0
- package/src/helper/CliUtils.ts +17 -0
- package/src/helper/JsonUtils.ts +70 -0
- package/src/index.ts +128 -0
- package/src/models/RettiwtConfig.ts +101 -0
- package/src/models/args/FetchArgs.ts +169 -0
- package/src/models/args/PostArgs.ts +93 -0
- package/src/models/args/ProfileArgs.ts +68 -0
- package/src/models/auth/AuthCookie.ts +58 -0
- package/src/models/auth/AuthCredential.ts +83 -0
- package/src/models/data/Analytics.ts +97 -0
- package/src/models/data/BookmarkFolder.ts +73 -0
- package/src/models/data/Conversation.ts +344 -0
- package/src/models/data/CursoredData.ts +64 -0
- package/src/models/data/DirectMessage.ts +335 -0
- package/src/models/data/Inbox.ts +124 -0
- package/src/models/data/List.ts +113 -0
- package/src/models/data/Notification.ts +84 -0
- package/src/models/data/Tweet.ts +388 -0
- package/src/models/data/User.ts +187 -0
- package/src/models/errors/TwitterError.ts +65 -0
- package/src/models/params/Variables.ts +62 -0
- package/src/requests/DirectMessage.ts +229 -0
- package/src/requests/List.ts +203 -0
- package/src/requests/Media.ts +67 -0
- package/src/requests/Tweet.ts +607 -0
- package/src/requests/User.ts +1191 -0
- package/src/services/internal/AuthService.ts +115 -0
- package/src/services/internal/ErrorService.ts +41 -0
- package/src/services/internal/LogService.ts +34 -0
- package/src/services/public/DirectMessageService.ts +159 -0
- package/src/services/public/FetcherService.ts +366 -0
- package/src/services/public/ListService.ts +241 -0
- package/src/services/public/TweetService.ts +886 -0
- package/src/services/public/UserService.ts +1154 -0
- package/src/types/ErrorHandler.ts +13 -0
- package/src/types/Fetch.ts +3 -0
- package/src/types/RettiwtConfig.ts +48 -0
- package/src/types/args/FetchArgs.ts +233 -0
- package/src/types/args/PostArgs.ts +142 -0
- package/src/types/args/ProfileArgs.ts +33 -0
- package/src/types/auth/AuthCookie.ts +22 -0
- package/src/types/auth/AuthCredential.ts +28 -0
- package/src/types/auth/TransactionHeader.ts +8 -0
- package/src/types/data/Analytics.ts +58 -0
- package/src/types/data/BookmarkFolder.ts +12 -0
- package/src/types/data/Conversation.ts +44 -0
- package/src/types/data/CursoredData.ts +24 -0
- package/src/types/data/DirectMessage.ts +33 -0
- package/src/types/data/Inbox.ts +23 -0
- package/src/types/data/List.ts +33 -0
- package/src/types/data/Notification.ts +26 -0
- package/src/types/data/Tweet.ts +99 -0
- package/src/types/data/User.ts +54 -0
- package/src/types/errors/TwitterError.ts +37 -0
- package/src/types/params/Variables.ts +41 -0
- package/src/types/raw/base/Analytic.ts +32 -0
- package/src/types/raw/base/BookmarkFolder.ts +12 -0
- package/src/types/raw/base/Cursor.ts +13 -0
- package/src/types/raw/base/Error.ts +38 -0
- package/src/types/raw/base/LimitedVisibilityTweet.ts +40 -0
- package/src/types/raw/base/List.ts +50 -0
- package/src/types/raw/base/Media.ts +53 -0
- package/src/types/raw/base/Message.ts +22 -0
- package/src/types/raw/base/Notification.ts +66 -0
- package/src/types/raw/base/Space.ts +35 -0
- package/src/types/raw/base/Tweet.ts +139 -0
- package/src/types/raw/base/User.ts +182 -0
- package/src/types/raw/composite/DataResult.ts +8 -0
- package/src/types/raw/composite/TimelineList.ts +10 -0
- package/src/types/raw/composite/TimelineTweet.ts +14 -0
- package/src/types/raw/composite/TimelineUser.ts +13 -0
- package/src/types/raw/dm/Conversation.ts +59 -0
- package/src/types/raw/dm/InboxInitial.ts +155 -0
- package/src/types/raw/dm/InboxTimeline.ts +301 -0
- package/src/types/raw/dm/UserUpdates.ts +46 -0
- package/src/types/raw/generic/Response.ts +10 -0
- package/src/types/raw/list/AddMember.ts +175 -0
- package/src/types/raw/list/Details.ts +176 -0
- package/src/types/raw/list/Members.ts +154 -0
- package/src/types/raw/list/RemoveMember.ts +174 -0
- package/src/types/raw/list/Tweets.ts +2296 -0
- package/src/types/raw/media/FinalizeUpload.ts +20 -0
- package/src/types/raw/media/InitalizeUpload.ts +12 -0
- package/src/types/raw/media/LiveVideoStream.ts +21 -0
- package/src/types/raw/space/Details.ts +359 -0
- package/src/types/raw/tweet/Bookmark.ts +14 -0
- package/src/types/raw/tweet/Details.ts +210 -0
- package/src/types/raw/tweet/DetailsBulk.ts +338 -0
- package/src/types/raw/tweet/Like.ts +14 -0
- package/src/types/raw/tweet/Likers.ts +200 -0
- package/src/types/raw/tweet/Post.ts +150 -0
- package/src/types/raw/tweet/Replies.ts +539 -0
- package/src/types/raw/tweet/Retweet.ts +31 -0
- package/src/types/raw/tweet/Retweeters.ts +208 -0
- package/src/types/raw/tweet/Schedule.ts +18 -0
- package/src/types/raw/tweet/Search.ts +597 -0
- package/src/types/raw/tweet/Unbookmark.ts +14 -0
- package/src/types/raw/tweet/Unlike.ts +14 -0
- package/src/types/raw/tweet/Unpost.ts +20 -0
- package/src/types/raw/tweet/Unretweet.ts +31 -0
- package/src/types/raw/tweet/Unschedule.ts +14 -0
- package/src/types/raw/user/Affiliates.ts +179 -0
- package/src/types/raw/user/Analytics.ts +23 -0
- package/src/types/raw/user/BookmarkFolderTweets.ts +53 -0
- package/src/types/raw/user/BookmarkFolders.ts +41 -0
- package/src/types/raw/user/Bookmarks.ts +637 -0
- package/src/types/raw/user/Details.ts +185 -0
- package/src/types/raw/user/DetailsBulk.ts +104 -0
- package/src/types/raw/user/Follow.ts +280 -0
- package/src/types/raw/user/Followed.ts +1942 -0
- package/src/types/raw/user/Followers.ts +215 -0
- package/src/types/raw/user/Following.ts +215 -0
- package/src/types/raw/user/Highlights.ts +1287 -0
- package/src/types/raw/user/Likes.ts +1254 -0
- package/src/types/raw/user/Lists.ts +378 -0
- package/src/types/raw/user/Media.ts +1738 -0
- package/src/types/raw/user/Notifications.ts +499 -0
- package/src/types/raw/user/ProfileUpdate.ts +80 -0
- package/src/types/raw/user/Recommended.ts +2319 -0
- package/src/types/raw/user/Scheduled.ts +37 -0
- package/src/types/raw/user/Search.ts +230 -0
- package/src/types/raw/user/Subscriptions.ts +176 -0
- package/src/types/raw/user/Tweets.ts +1254 -0
- package/src/types/raw/user/TweetsAndReplies.ts +1254 -0
- package/src/types/raw/user/Unfollow.ts +280 -0
- package/tsconfig.json +97 -0
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
|
+
import { MediaType } from '../../enums/Media';
|
|
3
|
+
import { RawMediaType } from '../../enums/raw/Media';
|
|
4
|
+
import { findByFilter } from '../../helper/JsonUtils';
|
|
5
|
+
|
|
6
|
+
import { LogService } from '../../services/internal/LogService';
|
|
7
|
+
|
|
8
|
+
import { ITweet, ITweetEntities, ITweetMedia } from '../../types/data/Tweet';
|
|
9
|
+
import { ILimitedVisibilityTweet } from '../../types/raw/base/LimitedVisibilityTweet';
|
|
10
|
+
import { IExtendedMedia as IRawExtendedMedia } from '../../types/raw/base/Media';
|
|
11
|
+
import { ITweet as IRawTweet, IEntities as IRawTweetEntities } from '../../types/raw/base/Tweet';
|
|
12
|
+
import { ITimelineTweet } from '../../types/raw/composite/TimelineTweet';
|
|
13
|
+
|
|
14
|
+
import { User } from './User';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* The details of a single tweet.
|
|
18
|
+
*
|
|
19
|
+
* @public
|
|
20
|
+
*/
|
|
21
|
+
export class Tweet implements ITweet {
|
|
22
|
+
/** The raw tweet details. */
|
|
23
|
+
private readonly _raw: IRawTweet;
|
|
24
|
+
|
|
25
|
+
public bookmarkCount?: number;
|
|
26
|
+
public conversationId: string;
|
|
27
|
+
public createdAt: string;
|
|
28
|
+
public entities: TweetEntities;
|
|
29
|
+
public fullText: string;
|
|
30
|
+
public id: string;
|
|
31
|
+
public lang: string;
|
|
32
|
+
public likeCount?: number;
|
|
33
|
+
public media?: TweetMedia[];
|
|
34
|
+
public quoteCount?: number;
|
|
35
|
+
public quoted?: Tweet;
|
|
36
|
+
public replyCount?: number;
|
|
37
|
+
public replyTo?: string;
|
|
38
|
+
public retweetCount?: number;
|
|
39
|
+
public retweetedTweet?: Tweet;
|
|
40
|
+
public tweetBy: User;
|
|
41
|
+
public url: string;
|
|
42
|
+
public viewCount?: number;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param tweet - The raw tweet details.
|
|
46
|
+
*/
|
|
47
|
+
public constructor(tweet: IRawTweet) {
|
|
48
|
+
this._raw = { ...tweet };
|
|
49
|
+
this.id = tweet.rest_id;
|
|
50
|
+
this.conversationId = tweet.legacy.conversation_id_str;
|
|
51
|
+
this.createdAt = new Date(tweet.legacy.created_at).toISOString();
|
|
52
|
+
this.tweetBy = new User(tweet.core.user_results.result);
|
|
53
|
+
this.entities = new TweetEntities(tweet.legacy.entities);
|
|
54
|
+
this.media = tweet.legacy.extended_entities?.media?.map((media) => new TweetMedia(media));
|
|
55
|
+
this.quoted = this._getQuotedTweet(tweet);
|
|
56
|
+
this.fullText = tweet.note_tweet?.note_tweet_results?.result?.text
|
|
57
|
+
? tweet.note_tweet.note_tweet_results.result.text
|
|
58
|
+
: tweet.legacy.full_text;
|
|
59
|
+
this.replyTo = tweet.legacy.in_reply_to_status_id_str;
|
|
60
|
+
this.lang = tweet.legacy.lang;
|
|
61
|
+
this.quoteCount = tweet.legacy.quote_count;
|
|
62
|
+
this.replyCount = tweet.legacy.reply_count;
|
|
63
|
+
this.retweetCount = tweet.legacy.retweet_count;
|
|
64
|
+
this.likeCount = tweet.legacy.favorite_count;
|
|
65
|
+
this.viewCount = tweet.views?.count ? parseInt(tweet.views.count) : undefined;
|
|
66
|
+
this.bookmarkCount = tweet.legacy.bookmark_count;
|
|
67
|
+
this.retweetedTweet = this._getRetweetedTweet(tweet);
|
|
68
|
+
this.url = `https://x.com/${this.tweetBy.userName}/status/${this.id}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/** The raw tweet details. */
|
|
72
|
+
public get raw(): IRawTweet {
|
|
73
|
+
return { ...this._raw };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Extract and deserialize the original quoted tweet from the given raw tweet.
|
|
78
|
+
*
|
|
79
|
+
* @param tweet - The raw tweet.
|
|
80
|
+
*
|
|
81
|
+
* @returns - The deserialized original quoted tweet.
|
|
82
|
+
*/
|
|
83
|
+
private _getQuotedTweet(tweet: IRawTweet): Tweet | undefined {
|
|
84
|
+
// If tweet with limited visibility
|
|
85
|
+
if (
|
|
86
|
+
tweet.quoted_status_result &&
|
|
87
|
+
tweet.quoted_status_result?.result?.__typename == 'TweetWithVisibilityResults' &&
|
|
88
|
+
(tweet.quoted_status_result.result as ILimitedVisibilityTweet)?.tweet?.legacy
|
|
89
|
+
) {
|
|
90
|
+
return new Tweet((tweet.quoted_status_result.result as ILimitedVisibilityTweet).tweet);
|
|
91
|
+
}
|
|
92
|
+
// If normal tweet
|
|
93
|
+
else if ((tweet.quoted_status_result?.result as IRawTweet)?.rest_id) {
|
|
94
|
+
return new Tweet(tweet.quoted_status_result.result as IRawTweet);
|
|
95
|
+
}
|
|
96
|
+
// Else, skip
|
|
97
|
+
else {
|
|
98
|
+
return undefined;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Extract and deserialize the original retweeted tweet from the given raw tweet.
|
|
104
|
+
*
|
|
105
|
+
* @param tweet - The raw tweet.
|
|
106
|
+
*
|
|
107
|
+
* @returns - The deserialized original retweeted tweet.
|
|
108
|
+
*/
|
|
109
|
+
private _getRetweetedTweet(tweet: IRawTweet): Tweet | undefined {
|
|
110
|
+
// If retweet with limited visibility
|
|
111
|
+
if (
|
|
112
|
+
tweet.legacy?.retweeted_status_result &&
|
|
113
|
+
tweet.legacy?.retweeted_status_result?.result?.__typename == 'TweetWithVisibilityResults' &&
|
|
114
|
+
(tweet.legacy?.retweeted_status_result?.result as ILimitedVisibilityTweet)?.tweet?.legacy
|
|
115
|
+
) {
|
|
116
|
+
return new Tweet((tweet.legacy.retweeted_status_result.result as ILimitedVisibilityTweet).tweet);
|
|
117
|
+
}
|
|
118
|
+
// If normal tweet
|
|
119
|
+
else if ((tweet.legacy?.retweeted_status_result?.result as IRawTweet)?.rest_id) {
|
|
120
|
+
return new Tweet(tweet.legacy.retweeted_status_result.result as IRawTweet);
|
|
121
|
+
}
|
|
122
|
+
// Else, skip
|
|
123
|
+
else {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* Extracts and deserializes multiple target tweets from the given raw response data.
|
|
130
|
+
*
|
|
131
|
+
* @param response - The raw response data.
|
|
132
|
+
* @param ids - The ids of the target tweets.
|
|
133
|
+
*
|
|
134
|
+
* @returns The target deserialized tweets.
|
|
135
|
+
*/
|
|
136
|
+
public static multiple(response: NonNullable<unknown>, ids: string[]): Tweet[] {
|
|
137
|
+
let tweets: Tweet[] = [];
|
|
138
|
+
|
|
139
|
+
// Extracting the matching data
|
|
140
|
+
const extract = findByFilter<IRawTweet>(response, '__typename', 'Tweet');
|
|
141
|
+
|
|
142
|
+
// Deserializing valid data
|
|
143
|
+
for (const item of extract) {
|
|
144
|
+
if (item.legacy) {
|
|
145
|
+
// Logging
|
|
146
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
147
|
+
|
|
148
|
+
tweets.push(new Tweet(item));
|
|
149
|
+
} else {
|
|
150
|
+
// Logging
|
|
151
|
+
LogService.log(LogActions.WARNING, {
|
|
152
|
+
action: LogActions.DESERIALIZE,
|
|
153
|
+
message: `Tweet not found, skipping`,
|
|
154
|
+
});
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Filtering only required tweets, if required
|
|
159
|
+
if (ids && ids.length) {
|
|
160
|
+
tweets = tweets.filter((tweet) => ids.includes(tweet.id));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
return tweets;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Extracts and deserializes a single target tweet from the given raw response data.
|
|
168
|
+
*
|
|
169
|
+
* @param response - The raw response data.
|
|
170
|
+
* @param id - The id of the target tweet.
|
|
171
|
+
*
|
|
172
|
+
* @returns The target deserialized tweet.
|
|
173
|
+
*/
|
|
174
|
+
public static single(response: NonNullable<unknown>, id: string): Tweet | undefined {
|
|
175
|
+
const tweets: Tweet[] = [];
|
|
176
|
+
|
|
177
|
+
// Extracting the matching data
|
|
178
|
+
const extract = findByFilter<IRawTweet>(response, 'rest_id', id);
|
|
179
|
+
|
|
180
|
+
// Deserializing valid data
|
|
181
|
+
for (const item of extract) {
|
|
182
|
+
if (item.legacy) {
|
|
183
|
+
// Logging
|
|
184
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
185
|
+
|
|
186
|
+
tweets.push(new Tweet(item));
|
|
187
|
+
} else {
|
|
188
|
+
// Logging
|
|
189
|
+
LogService.log(LogActions.WARNING, {
|
|
190
|
+
action: LogActions.DESERIALIZE,
|
|
191
|
+
message: `Tweet not found, skipping`,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return tweets.length ? tweets[0] : undefined;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Extracts and deserializes the timeline of tweets from the given raw response data.
|
|
201
|
+
*
|
|
202
|
+
* @param response - The raw response data.
|
|
203
|
+
* @param ids - The IDs of specific tweets that need to be extracted.
|
|
204
|
+
*
|
|
205
|
+
* @returns The deserialized timeline of tweets.
|
|
206
|
+
*/
|
|
207
|
+
public static timeline(response: NonNullable<unknown>): Tweet[] {
|
|
208
|
+
const tweets: Tweet[] = [];
|
|
209
|
+
|
|
210
|
+
// Extracting the matching data
|
|
211
|
+
const extract = findByFilter<ITimelineTweet>(response, '__typename', 'TimelineTweet');
|
|
212
|
+
|
|
213
|
+
// Deserializing valid data
|
|
214
|
+
for (const item of extract) {
|
|
215
|
+
// If tweet with limited visibility
|
|
216
|
+
if (
|
|
217
|
+
item.tweet_results?.result &&
|
|
218
|
+
item.tweet_results?.result?.__typename == 'TweetWithVisibilityResults' &&
|
|
219
|
+
(item.tweet_results?.result as ILimitedVisibilityTweet)?.tweet?.legacy
|
|
220
|
+
) {
|
|
221
|
+
tweets.push(new Tweet((item.tweet_results.result as ILimitedVisibilityTweet).tweet));
|
|
222
|
+
}
|
|
223
|
+
// If normal tweet
|
|
224
|
+
else if ((item.tweet_results?.result as IRawTweet)?.legacy) {
|
|
225
|
+
// Logging
|
|
226
|
+
LogService.log(LogActions.DESERIALIZE, { id: (item.tweet_results.result as IRawTweet).rest_id });
|
|
227
|
+
|
|
228
|
+
tweets.push(new Tweet(item.tweet_results.result as IRawTweet));
|
|
229
|
+
}
|
|
230
|
+
// If invalid/unrecognized tweet
|
|
231
|
+
else {
|
|
232
|
+
// Logging
|
|
233
|
+
LogService.log(LogActions.WARNING, {
|
|
234
|
+
action: LogActions.DESERIALIZE,
|
|
235
|
+
message: `Tweet not found, skipping`,
|
|
236
|
+
});
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return tweets;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
/**
|
|
244
|
+
* @returns A serializable JSON representation of `this` object.
|
|
245
|
+
*/
|
|
246
|
+
public toJSON(): ITweet {
|
|
247
|
+
return {
|
|
248
|
+
bookmarkCount: this.bookmarkCount,
|
|
249
|
+
conversationId: this.conversationId,
|
|
250
|
+
createdAt: this.createdAt,
|
|
251
|
+
entities: this.entities.toJSON(),
|
|
252
|
+
fullText: this.fullText,
|
|
253
|
+
id: this.id,
|
|
254
|
+
lang: this.lang,
|
|
255
|
+
likeCount: this.likeCount,
|
|
256
|
+
media: this.media?.map((item) => item.toJSON()),
|
|
257
|
+
quoteCount: this.quoteCount,
|
|
258
|
+
quoted: this.quoted?.toJSON(),
|
|
259
|
+
replyCount: this.replyCount,
|
|
260
|
+
replyTo: this.replyTo,
|
|
261
|
+
retweetCount: this.retweetCount,
|
|
262
|
+
retweetedTweet: this.retweetedTweet?.toJSON(),
|
|
263
|
+
tweetBy: this.tweetBy.toJSON(),
|
|
264
|
+
url: this.url,
|
|
265
|
+
viewCount: this.viewCount,
|
|
266
|
+
};
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* The different types parsed entities like urls, media, mentions, hashtags, etc.
|
|
272
|
+
*
|
|
273
|
+
* @public
|
|
274
|
+
*/
|
|
275
|
+
export class TweetEntities implements ITweetEntities {
|
|
276
|
+
/** The list of hashtags mentioned in the tweet. */
|
|
277
|
+
public hashtags: string[] = [];
|
|
278
|
+
|
|
279
|
+
/** The list of IDs of users mentioned in the tweet. */
|
|
280
|
+
public mentionedUsers: string[] = [];
|
|
281
|
+
|
|
282
|
+
/** The list of urls mentioned in the tweet. */
|
|
283
|
+
public urls: string[] = [];
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @param entities - The raw tweet entities.
|
|
287
|
+
*/
|
|
288
|
+
public constructor(entities: IRawTweetEntities) {
|
|
289
|
+
// Extracting user mentions
|
|
290
|
+
if (entities.user_mentions) {
|
|
291
|
+
for (const user of entities.user_mentions) {
|
|
292
|
+
this.mentionedUsers.push(user.screen_name);
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Extracting urls
|
|
297
|
+
if (entities.urls) {
|
|
298
|
+
for (const url of entities.urls) {
|
|
299
|
+
this.urls.push(url.expanded_url);
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// Extracting hashtags
|
|
304
|
+
if (entities.hashtags) {
|
|
305
|
+
for (const hashtag of entities.hashtags) {
|
|
306
|
+
this.hashtags.push(hashtag.text);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* @returns A serializable JSON representation of `this` object.
|
|
313
|
+
*/
|
|
314
|
+
public toJSON(): ITweetEntities {
|
|
315
|
+
return {
|
|
316
|
+
hashtags: this.hashtags,
|
|
317
|
+
mentionedUsers: this.mentionedUsers,
|
|
318
|
+
urls: this.urls,
|
|
319
|
+
};
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
/**
|
|
324
|
+
* The details of a single media content included in a tweet.
|
|
325
|
+
*
|
|
326
|
+
* @public
|
|
327
|
+
*/
|
|
328
|
+
export class TweetMedia implements ITweetMedia {
|
|
329
|
+
/** The ID of the media. */
|
|
330
|
+
public id: string;
|
|
331
|
+
|
|
332
|
+
/** The thumbnail URL for the video content of the tweet. */
|
|
333
|
+
public thumbnailUrl?: string;
|
|
334
|
+
|
|
335
|
+
/** The type of media. */
|
|
336
|
+
public type: MediaType;
|
|
337
|
+
|
|
338
|
+
/** The direct URL to the media. */
|
|
339
|
+
public url = '';
|
|
340
|
+
|
|
341
|
+
/**
|
|
342
|
+
* @param media - The raw media details.
|
|
343
|
+
*/
|
|
344
|
+
public constructor(media: IRawExtendedMedia) {
|
|
345
|
+
this.id = media.id_str;
|
|
346
|
+
|
|
347
|
+
// If the media is a photo
|
|
348
|
+
if (media.type == RawMediaType.PHOTO) {
|
|
349
|
+
this.type = MediaType.PHOTO;
|
|
350
|
+
this.url = media.media_url_https;
|
|
351
|
+
}
|
|
352
|
+
// If the media is a gif
|
|
353
|
+
else if (media.type == RawMediaType.GIF) {
|
|
354
|
+
this.type = MediaType.GIF;
|
|
355
|
+
this.url = media.video_info?.variants[0].url as string;
|
|
356
|
+
}
|
|
357
|
+
// If the media is a video
|
|
358
|
+
else {
|
|
359
|
+
this.type = MediaType.VIDEO;
|
|
360
|
+
this.thumbnailUrl = media.media_url_https;
|
|
361
|
+
|
|
362
|
+
/** The highest bitrate of all variants. */
|
|
363
|
+
let highestRate = 0;
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Selecting the URL of the video variant with the highest bitrate.
|
|
367
|
+
*/
|
|
368
|
+
media.video_info?.variants.forEach((variant) => {
|
|
369
|
+
if (variant.bitrate > highestRate) {
|
|
370
|
+
highestRate = variant.bitrate;
|
|
371
|
+
this.url = variant.url;
|
|
372
|
+
}
|
|
373
|
+
});
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* @returns A serializable JSON representation of `this` object.
|
|
379
|
+
*/
|
|
380
|
+
public toJSON(): ITweetMedia {
|
|
381
|
+
return {
|
|
382
|
+
id: this.id,
|
|
383
|
+
thumbnailUrl: this.thumbnailUrl,
|
|
384
|
+
type: this.type,
|
|
385
|
+
url: this.url,
|
|
386
|
+
};
|
|
387
|
+
}
|
|
388
|
+
}
|
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { LogActions } from '../../enums/Logging';
|
|
2
|
+
import { findByFilter } from '../../helper/JsonUtils';
|
|
3
|
+
import { LogService } from '../../services/internal/LogService';
|
|
4
|
+
import { IUser } from '../../types/data/User';
|
|
5
|
+
import { IUser as IRawUser } from '../../types/raw/base/User';
|
|
6
|
+
import { ITimelineUser as IRawTimelineUser } from '../../types/raw/composite/TimelineUser';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* The details of a single user.
|
|
10
|
+
*
|
|
11
|
+
* @public
|
|
12
|
+
*/
|
|
13
|
+
export class User implements IUser {
|
|
14
|
+
/** The raw user details. */
|
|
15
|
+
private readonly _raw: IRawUser;
|
|
16
|
+
|
|
17
|
+
public createdAt: string;
|
|
18
|
+
public description?: string;
|
|
19
|
+
public followersCount: number;
|
|
20
|
+
public followingsCount: number;
|
|
21
|
+
public fullName: string;
|
|
22
|
+
public id: string;
|
|
23
|
+
public isFollowed?: boolean;
|
|
24
|
+
public isFollowing?: boolean;
|
|
25
|
+
public isVerified: boolean;
|
|
26
|
+
public likeCount: number;
|
|
27
|
+
public location?: string;
|
|
28
|
+
public pinnedTweet?: string;
|
|
29
|
+
public profileBanner?: string;
|
|
30
|
+
public profileImage: string;
|
|
31
|
+
public statusesCount: number;
|
|
32
|
+
public userName: string;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param user - The raw user details.
|
|
36
|
+
*/
|
|
37
|
+
public constructor(user: IRawUser) {
|
|
38
|
+
this._raw = { ...user };
|
|
39
|
+
this.id = user.rest_id;
|
|
40
|
+
this.userName = user.core?.screen_name ?? user.legacy.screen_name ?? '';
|
|
41
|
+
this.fullName = user.core?.name ?? user.legacy.name ?? '';
|
|
42
|
+
this.createdAt = new Date(user.core?.created_at ?? user.legacy.created_at ?? 0).toISOString();
|
|
43
|
+
this.description = user.legacy.description.length ? user.legacy.description : undefined;
|
|
44
|
+
this.isFollowed = user.legacy.following;
|
|
45
|
+
this.isFollowing = user.legacy.followed_by;
|
|
46
|
+
this.isVerified = user.is_blue_verified;
|
|
47
|
+
this.likeCount = user.legacy.favourites_count;
|
|
48
|
+
this.followersCount = user.legacy.followers_count;
|
|
49
|
+
this.followingsCount = user.legacy.friends_count;
|
|
50
|
+
this.statusesCount = user.legacy.statuses_count;
|
|
51
|
+
this.location = user.location?.location ?? user.legacy.location ?? undefined;
|
|
52
|
+
this.pinnedTweet = user.legacy.pinned_tweet_ids_str[0];
|
|
53
|
+
this.profileBanner = user.legacy.profile_banner_url;
|
|
54
|
+
this.profileImage = user.avatar?.image_url ?? user.legacy.profile_image_url_https ?? '';
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/** The raw user details. */
|
|
58
|
+
public get raw(): IRawUser {
|
|
59
|
+
return { ...this._raw };
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Extracts and deserializes multiple target users from the given raw response data.
|
|
64
|
+
*
|
|
65
|
+
* @param response - The raw response data.
|
|
66
|
+
* @param ids - The ids of the target users.
|
|
67
|
+
*
|
|
68
|
+
* @returns The target deserialized users.
|
|
69
|
+
*/
|
|
70
|
+
public static multiple(response: NonNullable<unknown>, ids: string[]): User[] {
|
|
71
|
+
let users: User[] = [];
|
|
72
|
+
|
|
73
|
+
// Extracting the matching data
|
|
74
|
+
const extract = findByFilter<IRawUser>(response, '__typename', 'User');
|
|
75
|
+
|
|
76
|
+
// Deserializing valid data
|
|
77
|
+
for (const item of extract) {
|
|
78
|
+
if (item.legacy && (item.core?.created_at || item.legacy.created_at)) {
|
|
79
|
+
// Logging
|
|
80
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
81
|
+
|
|
82
|
+
users.push(new User(item));
|
|
83
|
+
} else {
|
|
84
|
+
// Logging
|
|
85
|
+
LogService.log(LogActions.WARNING, {
|
|
86
|
+
action: LogActions.DESERIALIZE,
|
|
87
|
+
message: `User not found, skipping`,
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Filtering only required user, if required
|
|
93
|
+
if (ids && ids.length) {
|
|
94
|
+
users = users.filter((user) => ids.includes(user.id));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return users;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Extracts and deserializes a single target user from the given raw response data.
|
|
102
|
+
*
|
|
103
|
+
* @param response - The raw response data.
|
|
104
|
+
*
|
|
105
|
+
* @returns The target deserialized user.
|
|
106
|
+
*/
|
|
107
|
+
public static single(response: NonNullable<unknown>): User | undefined {
|
|
108
|
+
const users: User[] = [];
|
|
109
|
+
|
|
110
|
+
// Extracting the matching data
|
|
111
|
+
const extract = findByFilter<IRawUser>(response, '__typename', 'User');
|
|
112
|
+
|
|
113
|
+
// Deserializing valid data
|
|
114
|
+
for (const item of extract) {
|
|
115
|
+
if (item.legacy && (item.core?.created_at || item.legacy.created_at)) {
|
|
116
|
+
// Logging
|
|
117
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.rest_id });
|
|
118
|
+
|
|
119
|
+
users.push(new User(item));
|
|
120
|
+
} else {
|
|
121
|
+
// Logging
|
|
122
|
+
LogService.log(LogActions.WARNING, {
|
|
123
|
+
action: LogActions.DESERIALIZE,
|
|
124
|
+
message: `User not found, skipping`,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
return users.length ? users[0] : undefined;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Extracts and deserializes the timeline of users from the given raw response data.
|
|
134
|
+
*
|
|
135
|
+
* @param response - The raw response data.
|
|
136
|
+
*
|
|
137
|
+
* @returns The deserialized timeline of users.
|
|
138
|
+
*/
|
|
139
|
+
public static timeline(response: NonNullable<unknown>): User[] {
|
|
140
|
+
const users: User[] = [];
|
|
141
|
+
|
|
142
|
+
// Extracting the matching data
|
|
143
|
+
const extract = findByFilter<IRawTimelineUser>(response, '__typename', 'TimelineUser');
|
|
144
|
+
|
|
145
|
+
// Deserializing valid data
|
|
146
|
+
for (const item of extract) {
|
|
147
|
+
if (item.user_results?.result?.legacy) {
|
|
148
|
+
// Logging
|
|
149
|
+
LogService.log(LogActions.DESERIALIZE, { id: item.user_results.result.rest_id });
|
|
150
|
+
|
|
151
|
+
users.push(new User(item.user_results.result));
|
|
152
|
+
} else {
|
|
153
|
+
// Logging
|
|
154
|
+
LogService.log(LogActions.WARNING, {
|
|
155
|
+
action: LogActions.DESERIALIZE,
|
|
156
|
+
message: `User not found, skipping`,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
return users;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* @returns A serializable JSON representation of `this` object.
|
|
166
|
+
*/
|
|
167
|
+
public toJSON(): IUser {
|
|
168
|
+
return {
|
|
169
|
+
createdAt: this.createdAt,
|
|
170
|
+
description: this.description,
|
|
171
|
+
followersCount: this.followersCount,
|
|
172
|
+
followingsCount: this.followingsCount,
|
|
173
|
+
fullName: this.fullName,
|
|
174
|
+
id: this.id,
|
|
175
|
+
isFollowed: this.isFollowed,
|
|
176
|
+
isFollowing: this.isFollowing,
|
|
177
|
+
isVerified: this.isVerified,
|
|
178
|
+
likeCount: this.likeCount,
|
|
179
|
+
location: this.location,
|
|
180
|
+
pinnedTweet: this.pinnedTweet,
|
|
181
|
+
profileBanner: this.profileBanner,
|
|
182
|
+
profileImage: this.profileImage,
|
|
183
|
+
statusesCount: this.statusesCount,
|
|
184
|
+
userName: this.userName,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { FetchError } from 'ofetch';
|
|
2
|
+
|
|
3
|
+
import { ITwitterError, ITwitterErrorDetails } from '../../types/errors/TwitterError';
|
|
4
|
+
import { IErrorData as IRawErrorData, IErrorDetails as IRawErrorDetails } from '../../types/raw/base/Error';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* The error thrown by Twitter API.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
export class TwitterError extends Error implements ITwitterError {
|
|
12
|
+
public details: ITwitterErrorDetails[];
|
|
13
|
+
public message: string;
|
|
14
|
+
public name: string;
|
|
15
|
+
public status: number;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* @param error - The error response received from Twitter.
|
|
19
|
+
*/
|
|
20
|
+
public constructor(error: FetchError<IRawErrorData | IRawErrorDetails>) {
|
|
21
|
+
super(error.message);
|
|
22
|
+
this.details = (
|
|
23
|
+
(error.data as IRawErrorData)?.errors
|
|
24
|
+
? (error.data as IRawErrorData).errors.map((item) => new TwitterErrorDetails(item))
|
|
25
|
+
: [new TwitterErrorDetails(error.data as IRawErrorDetails)]
|
|
26
|
+
).map((item) => item.toJSON());
|
|
27
|
+
this.message = error.message;
|
|
28
|
+
this.name = 'TWITTER_ERROR';
|
|
29
|
+
this.status = error.status ?? 500;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The error details.
|
|
35
|
+
*
|
|
36
|
+
* @public
|
|
37
|
+
*/
|
|
38
|
+
export class TwitterErrorDetails implements ITwitterErrorDetails {
|
|
39
|
+
public code: number;
|
|
40
|
+
public message: string;
|
|
41
|
+
public name?: string;
|
|
42
|
+
public type?: string;
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* @param details - The details of the error.
|
|
46
|
+
*/
|
|
47
|
+
public constructor(details: IRawErrorDetails) {
|
|
48
|
+
this.code = details.code;
|
|
49
|
+
this.message = details.message;
|
|
50
|
+
this.name = details.name;
|
|
51
|
+
this.type = details.kind;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* @returns The JSON representation of `this` object.
|
|
56
|
+
*/
|
|
57
|
+
public toJSON(): ITwitterErrorDetails {
|
|
58
|
+
return {
|
|
59
|
+
code: this.code,
|
|
60
|
+
message: this.message,
|
|
61
|
+
name: this.message,
|
|
62
|
+
type: this.type,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { IMediaEntityVariable, IMediaVariable, IReplyVariable } from '../../types/params/Variables';
|
|
2
|
+
import { NewTweetMedia } from '../args/PostArgs';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Media to be sent as payload.
|
|
6
|
+
*
|
|
7
|
+
* @internal
|
|
8
|
+
*/
|
|
9
|
+
export class MediaVariable implements IMediaVariable {
|
|
10
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
11
|
+
public media_entities: MediaEntityVariable[];
|
|
12
|
+
public possibly_sensitive: boolean;
|
|
13
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param media - The list of NewTweetMedia objects specifying the media items to be sent in the Tweet.
|
|
17
|
+
*/
|
|
18
|
+
public constructor(media: NewTweetMedia[]) {
|
|
19
|
+
this.media_entities = media.map((item) => new MediaEntityVariable(item));
|
|
20
|
+
this.possibly_sensitive = false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Each media item in the media payload.
|
|
26
|
+
*
|
|
27
|
+
* @internal
|
|
28
|
+
*/
|
|
29
|
+
export class MediaEntityVariable implements IMediaEntityVariable {
|
|
30
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
31
|
+
public media_id: string;
|
|
32
|
+
public tagged_users: string[];
|
|
33
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @param media - The NewTweetMedia object specifying the details of the media item to be included in the payload.
|
|
37
|
+
*/
|
|
38
|
+
public constructor(media: NewTweetMedia) {
|
|
39
|
+
this.media_id = media.id;
|
|
40
|
+
this.tagged_users = media.tags ?? [];
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Reply specific details to be sent in payload.
|
|
46
|
+
*
|
|
47
|
+
* @internal
|
|
48
|
+
*/
|
|
49
|
+
export class ReplyVariable implements IReplyVariable {
|
|
50
|
+
/* eslint-disable @typescript-eslint/naming-convention */
|
|
51
|
+
public exclude_reply_user_ids: string[];
|
|
52
|
+
public in_reply_to_tweet_id: string;
|
|
53
|
+
/* eslint-enable @typescript-eslint/naming-convention */
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* @param replyTo - The id of the Tweet to which this Tweet is a reply.
|
|
57
|
+
*/
|
|
58
|
+
public constructor(replyTo: string) {
|
|
59
|
+
this.in_reply_to_tweet_id = replyTo;
|
|
60
|
+
this.exclude_reply_user_ids = [];
|
|
61
|
+
}
|
|
62
|
+
}
|