gs-x-parser 1.0.0 → 1.0.2
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/README.md +1 -4
- package/README.zh.md +2 -4
- package/lib/parser.cjs +270 -175
- package/lib/parser.d.ts +20 -16
- package/lib/parser.mjs +270 -175
- package/lib/type.cjs +2 -2
- package/lib/type.d.ts +336 -40
- package/lib/type.mjs +2 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
# GS X Parser
|
|
2
|
+
GS X Parser is a TypeScript library for parsing Twitter/X API responses, providing complete type definitions and parsing functionality, supporting the conversion of complex API responses into simple and easy-to-use formats.
|
|
2
3
|
|
|
3
4
|
[中文](README.zh.md)
|
|
4
5
|
|
|
5
|
-
## Project Introduction
|
|
6
|
-
|
|
7
|
-
GS X Parser is a TypeScript library for parsing Twitter/X API responses, providing complete type definitions and parsing functionality, supporting the conversion of complex API responses into simple and easy-to-use formats.
|
|
8
|
-
|
|
9
6
|
## Features
|
|
10
7
|
|
|
11
8
|
- Complete TypeScript type definitions based on Twitter/X API data structures
|
package/README.zh.md
CHANGED
package/lib/parser.cjs
CHANGED
|
@@ -1,192 +1,287 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var type = require('./type.cjs');
|
|
3
|
+
function filterTweets(convertedTweets, originalTweets, options) {
|
|
4
|
+
const tweets = [], ads = [], exploreMore = [], recommendations = [];
|
|
5
|
+
return convertedTweets.forEach((tweet, index) => {
|
|
6
|
+
const originalTweet = originalTweets[index], restId = originalTweet?.rest_id || "", entryId = originalTweet?.entryId || "";
|
|
7
|
+
restId.startsWith(type.IdPrefixes.PromotedTweet) || entryId.startsWith(type.IdPrefixes.PromotedTweet) ? (ads.push(tweet), options.includeAds && tweets.push(tweet)) : restId.includes("explore") || entryId.includes("tweetdetailrelatedtweets") ? (exploreMore.push(tweet), options.includeExploreMore && tweets.push(tweet)) : restId.includes("recommendation") || restId.includes("suggestion") || entryId.includes("pinned-tweets") ? (recommendations.push(tweet), options.includeRecommendations && tweets.push(tweet)) : tweets.push(tweet);
|
|
8
|
+
}), { tweets, ads, exploreMore, recommendations };
|
|
9
|
+
}
|
|
10
|
+
function extractUsers(data) {
|
|
11
|
+
const users = [], userIds = /* @__PURE__ */ new Set();
|
|
12
|
+
data?.globalObjects?.users && Object.values(data.globalObjects.users).forEach((user) => {
|
|
13
|
+
user && user.id_str && !userIds.has(user.id_str) && (user.__typename = "User", user.rest_id = user.id_str, userIds.add(user.id_str), users.push(user));
|
|
14
|
+
}), Array.isArray(data) && data.forEach((item) => {
|
|
15
|
+
if (item?.user && item.user.id_str && !userIds.has(item.user.id_str)) {
|
|
16
|
+
const user = item.user;
|
|
17
|
+
user.__typename = "User", user.rest_id = user.id_str, user.ext_is_blue_verified !== void 0 && (user.is_blue_verified = user.ext_is_blue_verified), userIds.add(user.id_str), users.push(user);
|
|
18
|
+
}
|
|
19
|
+
}), data?.data?.user?.result?.timeline?.timeline?.instructions && data.data.user.result.timeline.timeline.instructions.forEach((instruction) => {
|
|
20
|
+
instruction.type === "TimelineAddEntries" && instruction.entries && instruction.entries.forEach((entry) => {
|
|
21
|
+
if (entry.content?.itemContent?.user_results?.result) {
|
|
22
|
+
const user = entry.content.itemContent.user_results.result;
|
|
23
|
+
user && user.rest_id && !userIds.has(user.rest_id) && (user.id_str || (user.id_str = user.rest_id), userIds.add(user.rest_id), users.push(user));
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
}), data?.data?.bookmark_timeline_v2?.timeline?.instructions && data.data.bookmark_timeline_v2.timeline.instructions.forEach((instruction) => {
|
|
27
|
+
instruction.type === "TimelineAddEntries" && instruction.entries && instruction.entries.forEach((entry) => {
|
|
28
|
+
if (entry.content?.itemContent?.tweet_results?.result?.core?.user_results?.result) {
|
|
29
|
+
const user = entry.content.itemContent.tweet_results.result.core.user_results.result;
|
|
30
|
+
user && user.rest_id && !userIds.has(user.rest_id) && (user.id_str || (user.id_str = user.rest_id), userIds.add(user.rest_id), users.push(user));
|
|
31
|
+
}
|
|
32
|
+
});
|
|
33
|
+
}), data?.data?.viewer_v2?.user_results?.result?.notification_timeline?.timeline?.instructions && data.data.viewer_v2.user_results.result.notification_timeline.timeline.instructions.forEach((instruction) => {
|
|
34
|
+
instruction.entries && instruction.entries.forEach((entry) => {
|
|
35
|
+
entry.content?.itemContent?.template?.from_users && entry.content.itemContent.template.from_users.forEach((fromUser) => {
|
|
36
|
+
if (fromUser?.user_results?.result) {
|
|
37
|
+
const user = fromUser.user_results.result;
|
|
38
|
+
user && user.rest_id && !userIds.has(user.rest_id) && (user.id_str || (user.id_str = user.rest_id), userIds.add(user.rest_id), users.push(user));
|
|
39
|
+
}
|
|
40
|
+
}), entry.content?.itemContent?.template?.target_objects && entry.content.itemContent.template.target_objects.forEach((targetObject) => {
|
|
41
|
+
if (targetObject?.tweet_results?.result?.core?.user_results?.result) {
|
|
42
|
+
const user = targetObject.tweet_results.result.core.user_results.result;
|
|
43
|
+
user && user.rest_id && !userIds.has(user.rest_id) && (user.id_str || (user.id_str = user.rest_id), userIds.add(user.rest_id), users.push(user));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
const searchUsers = (obj) => {
|
|
49
|
+
if (obj && typeof obj == "object") {
|
|
50
|
+
const userId = obj.id_str || obj.rest_id;
|
|
51
|
+
obj.__typename === "User" && userId && !userIds.has(userId) && !obj.core?.user_results && !obj.legacy?.full_text && (obj.name || obj.screen_name || obj.legacy) && (obj.__typename = "User", obj.rest_id || (obj.rest_id = userId), obj.id_str || (obj.id_str = userId), obj.ext_is_blue_verified !== void 0 && (obj.is_blue_verified = obj.ext_is_blue_verified), userIds.add(userId), users.push(obj));
|
|
52
|
+
for (const key in obj)
|
|
53
|
+
obj.hasOwnProperty(key) && searchUsers(obj[key]);
|
|
54
|
+
} else Array.isArray(obj) && obj.forEach((item) => searchUsers(item));
|
|
55
|
+
};
|
|
56
|
+
return searchUsers(data), users;
|
|
57
|
+
}
|
|
58
|
+
function extractTweets(data) {
|
|
59
|
+
const tweets = [], tweetIds = /* @__PURE__ */ new Set();
|
|
60
|
+
data?.globalObjects?.tweets && Object.values(data.globalObjects.tweets).forEach((tweet) => {
|
|
61
|
+
tweet && tweet.id_str && !tweetIds.has(tweet.id_str) && (tweet.__typename = "Tweet", tweet.rest_id = tweet.id_str, tweetIds.add(tweet.id_str), tweets.push(tweet));
|
|
62
|
+
}), data?.data?.viewer_v2?.user_results?.result?.notification_timeline?.timeline?.instructions && data.data.viewer_v2.user_results.result.notification_timeline.timeline.instructions.forEach((instruction) => {
|
|
63
|
+
instruction.entries && instruction.entries.forEach((entry) => {
|
|
64
|
+
entry.content?.itemContent?.template?.target_objects && entry.content.itemContent.template.target_objects.forEach((targetObject) => {
|
|
65
|
+
if (targetObject?.tweet_results?.result) {
|
|
66
|
+
const tweet = targetObject.tweet_results.result;
|
|
67
|
+
tweet && tweet.rest_id && !tweetIds.has(tweet.rest_id) && (tweet.__typename = "Tweet", entry.entryId && (tweet.entryId = entry.entryId), entry.content.clientEventInfo && (tweet.clientEventInfo = entry.content.clientEventInfo), tweetIds.add(tweet.rest_id), tweets.push(tweet));
|
|
68
|
+
}
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
const searchTweets = (obj, entryId, clientEventInfo) => {
|
|
73
|
+
if (obj && typeof obj == "object") {
|
|
74
|
+
if (obj.entryId && (entryId = obj.entryId), obj.clientEventInfo && (clientEventInfo = obj.clientEventInfo), (obj.__typename === "Tweet" || !obj.__typename && obj.rest_id && obj.legacy?.full_text) && obj.rest_id && !tweetIds.has(obj.rest_id) && (obj.__typename = "Tweet", entryId && (obj.entryId = entryId), clientEventInfo && (obj.clientEventInfo = clientEventInfo), tweetIds.add(obj.rest_id), tweets.push(obj)), obj.__typename === "TweetWithVisibilityResults" && obj.tweet && obj.tweet.rest_id && !tweetIds.has(obj.tweet.rest_id)) {
|
|
75
|
+
const tweet = obj.tweet;
|
|
76
|
+
tweet.__typename = "Tweet", entryId && (tweet.entryId = entryId), clientEventInfo && (tweet.clientEventInfo = clientEventInfo), tweetIds.add(tweet.rest_id), tweets.push(tweet);
|
|
77
|
+
}
|
|
78
|
+
for (const key in obj)
|
|
79
|
+
obj.hasOwnProperty(key) && searchTweets(obj[key], entryId, clientEventInfo);
|
|
80
|
+
} else Array.isArray(obj) && obj.forEach((item) => searchTweets(item, entryId, clientEventInfo));
|
|
81
|
+
};
|
|
82
|
+
return searchTweets(data), tweets;
|
|
83
|
+
}
|
|
84
|
+
function extractCursors(data, result) {
|
|
85
|
+
const searchCursors = (obj) => {
|
|
86
|
+
if (obj && typeof obj == "object") {
|
|
87
|
+
obj.cursor_top && (result.cursor_top = obj.cursor_top), obj.cursor_bottom && (result.cursor_bottom = obj.cursor_bottom), obj.next_cursor && (result.next_cursor = obj.next_cursor), obj.next_cursor_str && (result.next_cursor_str = obj.next_cursor_str), obj.previous_cursor && (result.previous_cursor = obj.previous_cursor), obj.previous_cursor_str && (result.previous_cursor_str = obj.previous_cursor_str), obj.entryId && (obj.entryId.startsWith("cursor-top-") || obj.entryId.startsWith("cursor-bottom-")) && obj.content && obj.content.value && (obj.entryId.startsWith("cursor-top-") ? result.cursor_top = obj.content.value : result.cursor_bottom = obj.content.value);
|
|
88
|
+
for (const key in obj)
|
|
89
|
+
obj.hasOwnProperty(key) && searchCursors(obj[key]);
|
|
90
|
+
} else Array.isArray(obj) && obj.forEach((item) => searchCursors(item));
|
|
91
|
+
};
|
|
92
|
+
searchCursors(data);
|
|
93
|
+
}
|
|
94
|
+
function convertToSimpleUser(user) {
|
|
95
|
+
return {
|
|
96
|
+
rest_id: user.rest_id,
|
|
97
|
+
name: user.legacy?.name || user.name || user.core?.name || "",
|
|
98
|
+
screen_name: user.legacy?.screen_name || user.screen_name || user.core?.screen_name || "",
|
|
99
|
+
profile_image_url_https: user.legacy?.profile_image_url_https || user.profile_image_url_https || user.avatar?.image_url || "",
|
|
100
|
+
verified: user.legacy?.verified || user.verified || user.verification?.verified,
|
|
101
|
+
blue_verified: user.is_blue_verified,
|
|
102
|
+
followers_count: user.legacy?.followers_count || user.followers_count,
|
|
103
|
+
friends_count: user.legacy?.friends_count || user.friends_count,
|
|
104
|
+
statuses_count: user.legacy?.statuses_count || user.statuses_count,
|
|
105
|
+
description: user.legacy?.description || user.description || user.profile_bio?.description || null,
|
|
106
|
+
location: user.legacy?.location || (typeof user.location == "string" ? user.location : user.location?.location || null),
|
|
107
|
+
url: user.legacy?.url || user.url || null,
|
|
108
|
+
userLabelType: user.affiliates_highlighted_label?.label?.userLabelType,
|
|
109
|
+
verified_type: user.legacy?.verified_type || user.verified_type
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
function getVideoQuality(bitrate) {
|
|
113
|
+
return bitrate >= 25e5 ? "high" : bitrate >= 12e5 ? "medium" : "low";
|
|
114
|
+
}
|
|
115
|
+
function convertToSimpleTweet(tweet, user) {
|
|
116
|
+
const tweetData = tweet.legacy || tweet;
|
|
117
|
+
let tweetQuality;
|
|
118
|
+
tweet.visibility_results?.tweet_visibility_annotations ? tweetQuality = tweet.visibility_results.tweet_visibility_annotations.find(
|
|
119
|
+
(item) => item.tweet_quality
|
|
120
|
+
)?.tweet_quality : tweet.clientEventInfo?.details?.conversationDetails?.conversationSection && (tweetQuality = tweet.clientEventInfo?.details?.conversationDetails?.conversationSection), tweetQuality || (tweetQuality = "HighQuality");
|
|
121
|
+
const simpleTweet = {
|
|
122
|
+
rest_id: tweet.rest_id,
|
|
123
|
+
full_text: tweetData.full_text || "",
|
|
124
|
+
created_at: tweetData.created_at || "",
|
|
125
|
+
user: user ? convertToSimpleUser(user) : {
|
|
126
|
+
rest_id: tweetData.user_id_str || "",
|
|
127
|
+
name: "",
|
|
128
|
+
screen_name: "",
|
|
129
|
+
profile_image_url_https: ""
|
|
130
|
+
},
|
|
131
|
+
retweet_count: tweetData.retweet_count,
|
|
132
|
+
favorite_count: tweetData.favorite_count,
|
|
133
|
+
reply_count: tweetData.reply_count,
|
|
134
|
+
quote_count: tweetData.quote_count,
|
|
135
|
+
lang: tweetData.lang,
|
|
136
|
+
conversation_id: tweetData.conversation_id_str || "",
|
|
137
|
+
possibly_sensitive: tweetData.possibly_sensitive,
|
|
138
|
+
is_retweet: !!tweetData.retweeted_status_id_str,
|
|
139
|
+
retweeted_status_id: tweetData.retweeted_status_id_str,
|
|
140
|
+
quoted_status_id: tweetData.quoted_status_id_str,
|
|
141
|
+
quality: tweetQuality
|
|
142
|
+
}, media = tweetData.extended_entities?.media || tweetData.entities?.media;
|
|
143
|
+
if (media) {
|
|
144
|
+
const photos = [], videos = [], gifs = [];
|
|
145
|
+
media.forEach((mediaItem) => {
|
|
146
|
+
if (mediaItem.type === "photo") {
|
|
147
|
+
let simpleSizes;
|
|
148
|
+
mediaItem.sizes && (simpleSizes = {}, mediaItem.sizes.thumb && (simpleSizes.thumb = {
|
|
149
|
+
w: mediaItem.sizes.thumb.w || 0,
|
|
150
|
+
h: mediaItem.sizes.thumb.h || 0,
|
|
151
|
+
resize: mediaItem.sizes.thumb.resize || "fit"
|
|
152
|
+
}), mediaItem.sizes.small && (simpleSizes.small = {
|
|
153
|
+
w: mediaItem.sizes.small.w || 0,
|
|
154
|
+
h: mediaItem.sizes.small.h || 0,
|
|
155
|
+
resize: mediaItem.sizes.small.resize || "fit"
|
|
156
|
+
}), mediaItem.sizes.medium && (simpleSizes.medium = {
|
|
157
|
+
w: mediaItem.sizes.medium.w || 0,
|
|
158
|
+
h: mediaItem.sizes.medium.h || 0,
|
|
159
|
+
resize: mediaItem.sizes.medium.resize || "fit"
|
|
160
|
+
}), mediaItem.sizes.large && (simpleSizes.large = {
|
|
161
|
+
w: mediaItem.sizes.large.w || 0,
|
|
162
|
+
h: mediaItem.sizes.large.h || 0,
|
|
163
|
+
resize: mediaItem.sizes.large.resize || "fit"
|
|
164
|
+
}));
|
|
165
|
+
let simpleOriginalInfo;
|
|
166
|
+
mediaItem.original_info && (simpleOriginalInfo = {
|
|
167
|
+
width: mediaItem.original_info.width || 0,
|
|
168
|
+
height: mediaItem.original_info.height || 0
|
|
169
|
+
}), photos.push({
|
|
170
|
+
media_key: mediaItem.media_key || "",
|
|
171
|
+
type: "photo",
|
|
172
|
+
media_url_https: mediaItem.media_url_https || "",
|
|
173
|
+
display_url: mediaItem.display_url || "",
|
|
174
|
+
expanded_url: mediaItem.expanded_url || "",
|
|
175
|
+
sizes: simpleSizes,
|
|
176
|
+
original_info: simpleOriginalInfo
|
|
177
|
+
});
|
|
178
|
+
} else if (mediaItem.type === "video") {
|
|
179
|
+
const mp4Videos = mediaItem.video_info?.variants?.filter(
|
|
180
|
+
(item) => item?.content_type === "video/mp4"
|
|
181
|
+
).map((variant) => ({
|
|
182
|
+
bitrate: variant.bitrate,
|
|
183
|
+
url: variant.url || "",
|
|
184
|
+
quality: variant.bitrate ? getVideoQuality(variant.bitrate) : void 0
|
|
185
|
+
})), hlsVideo = mediaItem.video_info?.variants?.find((item) => item?.content_type === "application/x-mpegURL");
|
|
186
|
+
videos.push({
|
|
187
|
+
media_key: mediaItem.media_key || "",
|
|
188
|
+
type: "video",
|
|
189
|
+
media_url_https: mediaItem.media_url_https || "",
|
|
190
|
+
display_url: mediaItem.display_url || "",
|
|
191
|
+
expanded_url: mediaItem.expanded_url || "",
|
|
192
|
+
aspect_ratio: mediaItem.video_info?.aspect_ratio,
|
|
193
|
+
duration_millis: mediaItem.video_info?.duration_millis,
|
|
194
|
+
mp4: mp4Videos,
|
|
195
|
+
hls: hlsVideo?.url
|
|
196
|
+
});
|
|
197
|
+
} else if (mediaItem.type === "animated_gif") {
|
|
198
|
+
const mp4Video = mediaItem.video_info?.variants?.find((item) => item?.content_type === "video/mp4");
|
|
199
|
+
gifs.push({
|
|
200
|
+
media_key: mediaItem.media_key || "",
|
|
201
|
+
type: "animated_gif",
|
|
202
|
+
media_url_https: mediaItem.media_url_https || "",
|
|
203
|
+
display_url: mediaItem.display_url || "",
|
|
204
|
+
expanded_url: mediaItem.expanded_url || "",
|
|
205
|
+
aspect_ratio: mediaItem.video_info?.aspect_ratio,
|
|
206
|
+
duration_millis: mediaItem.video_info?.duration_millis,
|
|
207
|
+
mp4: mp4Video?.url
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}), photos.length > 0 && (simpleTweet.photos = photos), videos.length > 0 && (simpleTweet.videos = videos), gifs.length > 0 && (simpleTweet.gifs = gifs);
|
|
211
|
+
}
|
|
212
|
+
if (tweetData.entities?.urls) {
|
|
213
|
+
const urls = tweetData.entities.urls.map((url) => ({
|
|
214
|
+
url: url.url || "",
|
|
215
|
+
expanded_url: url.expanded_url || "",
|
|
216
|
+
display_url: url.display_url || ""
|
|
217
|
+
}));
|
|
218
|
+
urls.length > 0 && (simpleTweet.urls = urls);
|
|
219
|
+
}
|
|
220
|
+
return simpleTweet;
|
|
221
|
+
}
|
|
2
222
|
class XParser {
|
|
3
223
|
/**
|
|
4
224
|
* 解析任意对象为ISimpleResult
|
|
5
225
|
* @param data 任意对象
|
|
226
|
+
* @param options 解析选项
|
|
6
227
|
* @returns ISimpleResult
|
|
7
228
|
*/
|
|
8
|
-
static parseSimple(data) {
|
|
9
|
-
const result = {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
229
|
+
static parseSimple(data, options = {}) {
|
|
230
|
+
const result = {
|
|
231
|
+
users: [],
|
|
232
|
+
tweets: [],
|
|
233
|
+
ads: [],
|
|
234
|
+
exploreMore: [],
|
|
235
|
+
recommendations: [],
|
|
236
|
+
photos: [],
|
|
237
|
+
videos: [],
|
|
238
|
+
urls: []
|
|
239
|
+
}, users = extractUsers(data), tweets = extractTweets(data), userMap = /* @__PURE__ */ new Map();
|
|
240
|
+
if (users.forEach((user) => {
|
|
241
|
+
userMap.set(user.rest_id, user);
|
|
242
|
+
}), result.users = users.map((user) => convertToSimpleUser(user)), tweets.length > 0) {
|
|
243
|
+
const allTweets = tweets.map((tweet) => {
|
|
244
|
+
const userId = tweet.legacy?.user_id_str || tweet.user_id_str, user = userMap.get(userId || "");
|
|
245
|
+
return convertToSimpleTweet(tweet, user);
|
|
246
|
+
}), { tweets: filteredTweets, ads, exploreMore, recommendations } = filterTweets(allTweets, tweets, options);
|
|
247
|
+
result.tweets = filteredTweets, result.ads = ads, result.exploreMore = exploreMore, result.recommendations = recommendations, result.photos = result.tweets.filter((tweet) => tweet.photos && tweet.photos.length > 0), result.videos = result.tweets.filter((tweet) => tweet.videos && tweet.videos.length > 0), result.gifs = result.tweets.filter((tweet) => tweet.gifs && tweet.gifs.length > 0), result.urls = result.tweets.filter((tweet) => tweet.urls && tweet.urls.length > 0);
|
|
248
|
+
}
|
|
249
|
+
return extractCursors(data, result), result;
|
|
13
250
|
}
|
|
14
251
|
/**
|
|
15
252
|
* 解析任意对象为IOriginalResult
|
|
16
253
|
* @param data 任意对象
|
|
254
|
+
* @param options 解析选项
|
|
17
255
|
* @returns IOriginalResult
|
|
18
256
|
*/
|
|
19
|
-
static parseOriginal(data) {
|
|
20
|
-
const result = {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
verified: user.legacy?.verified,
|
|
46
|
-
followers_count: user.legacy?.followers_count,
|
|
47
|
-
friends_count: user.legacy?.friends_count,
|
|
48
|
-
statuses_count: user.legacy?.statuses_count,
|
|
49
|
-
description: user.legacy?.description,
|
|
50
|
-
location: user.legacy?.location,
|
|
51
|
-
url: user.legacy?.url,
|
|
52
|
-
userLabelType: user.affiliates_highlighted_label?.label?.userLabelType,
|
|
53
|
-
verified_type: user.legacy?.verified_type
|
|
54
|
-
};
|
|
55
|
-
}
|
|
56
|
-
/**
|
|
57
|
-
* 将ITweet转换为ISimpleTweet
|
|
58
|
-
* @param tweet ITweet对象
|
|
59
|
-
* @returns ISimpleTweet
|
|
60
|
-
*/
|
|
61
|
-
static convertToSimpleTweet(tweet) {
|
|
62
|
-
const legacy = tweet.legacy, simpleTweet = {
|
|
63
|
-
rest_id: tweet.rest_id,
|
|
64
|
-
full_text: legacy?.full_text || "",
|
|
65
|
-
created_at: legacy?.created_at || "",
|
|
66
|
-
user_id: legacy?.user_id_str || "",
|
|
67
|
-
user_screen_name: "",
|
|
68
|
-
// 需要从用户信息中获取
|
|
69
|
-
retweet_count: legacy?.retweet_count,
|
|
70
|
-
favorite_count: legacy?.favorite_count,
|
|
71
|
-
reply_count: legacy?.reply_count,
|
|
72
|
-
quote_count: legacy?.quote_count,
|
|
73
|
-
lang: legacy?.lang,
|
|
74
|
-
conversation_id: legacy?.conversation_id_str,
|
|
75
|
-
possibly_sensitive: legacy?.possibly_sensitive,
|
|
76
|
-
is_retweet: !!legacy?.retweeted_status_id_str,
|
|
77
|
-
retweeted_status_id: legacy?.retweeted_status_id_str,
|
|
78
|
-
quoted_status_id: legacy?.quoted_status_id_str
|
|
79
|
-
};
|
|
80
|
-
if (legacy?.extended_entities?.media) {
|
|
81
|
-
const photos = [], videos = [];
|
|
82
|
-
legacy.extended_entities.media.forEach((media) => {
|
|
83
|
-
if (media.type === "photo") {
|
|
84
|
-
let simpleSizes;
|
|
85
|
-
media.sizes && (simpleSizes = {}, media.sizes.thumb && (simpleSizes.thumb = {
|
|
86
|
-
w: media.sizes.thumb.w || 0,
|
|
87
|
-
h: media.sizes.thumb.h || 0,
|
|
88
|
-
resize: media.sizes.thumb.resize || "fit"
|
|
89
|
-
}), media.sizes.small && (simpleSizes.small = {
|
|
90
|
-
w: media.sizes.small.w || 0,
|
|
91
|
-
h: media.sizes.small.h || 0,
|
|
92
|
-
resize: media.sizes.small.resize || "fit"
|
|
93
|
-
}), media.sizes.medium && (simpleSizes.medium = {
|
|
94
|
-
w: media.sizes.medium.w || 0,
|
|
95
|
-
h: media.sizes.medium.h || 0,
|
|
96
|
-
resize: media.sizes.medium.resize || "fit"
|
|
97
|
-
}), media.sizes.large && (simpleSizes.large = {
|
|
98
|
-
w: media.sizes.large.w || 0,
|
|
99
|
-
h: media.sizes.large.h || 0,
|
|
100
|
-
resize: media.sizes.large.resize || "fit"
|
|
101
|
-
}));
|
|
102
|
-
let simpleOriginalInfo;
|
|
103
|
-
media.original_info && (simpleOriginalInfo = {
|
|
104
|
-
width: media.original_info.width || 0,
|
|
105
|
-
height: media.original_info.height || 0
|
|
106
|
-
}), photos.push({
|
|
107
|
-
media_key: media.media_key || "",
|
|
108
|
-
type: "photo",
|
|
109
|
-
media_url_https: media.media_url_https || "",
|
|
110
|
-
display_url: media.display_url || "",
|
|
111
|
-
expanded_url: media.expanded_url || "",
|
|
112
|
-
sizes: simpleSizes,
|
|
113
|
-
original_info: simpleOriginalInfo
|
|
114
|
-
});
|
|
115
|
-
} else if (media.type === "video" || media.type === "animated_gif") {
|
|
116
|
-
let simpleVideoInfo;
|
|
117
|
-
media.video_info && (simpleVideoInfo = {
|
|
118
|
-
aspect_ratio: media.video_info.aspect_ratio,
|
|
119
|
-
duration_millis: media.video_info.duration_millis,
|
|
120
|
-
variants: media.video_info.variants?.map((variant) => ({
|
|
121
|
-
bitrate: variant.bitrate,
|
|
122
|
-
content_type: variant.content_type || "",
|
|
123
|
-
url: variant.url || ""
|
|
124
|
-
}))
|
|
125
|
-
}), videos.push({
|
|
126
|
-
media_key: media.media_key || "",
|
|
127
|
-
type: media.type,
|
|
128
|
-
media_url_https: media.media_url_https || "",
|
|
129
|
-
display_url: media.display_url || "",
|
|
130
|
-
expanded_url: media.expanded_url || "",
|
|
131
|
-
video_info: simpleVideoInfo
|
|
132
|
-
});
|
|
133
|
-
}
|
|
134
|
-
}), photos.length > 0 && (simpleTweet.photos = photos), videos.length > 0 && (simpleTweet.videos = videos);
|
|
135
|
-
}
|
|
136
|
-
if (legacy?.entities?.urls) {
|
|
137
|
-
const urls = legacy.entities.urls.map((url) => ({
|
|
138
|
-
url: url.url || "",
|
|
139
|
-
expanded_url: url.expanded_url || "",
|
|
140
|
-
display_url: url.display_url || ""
|
|
141
|
-
}));
|
|
142
|
-
urls.length > 0 && (simpleTweet.urls = urls);
|
|
257
|
+
static parseOriginal(data, options = {}) {
|
|
258
|
+
const result = {
|
|
259
|
+
users: [],
|
|
260
|
+
tweets: [],
|
|
261
|
+
ads: [],
|
|
262
|
+
exploreMore: [],
|
|
263
|
+
recommendations: [],
|
|
264
|
+
photos: [],
|
|
265
|
+
videos: [],
|
|
266
|
+
urls: []
|
|
267
|
+
}, users = extractUsers(data), tweets = extractTweets(data);
|
|
268
|
+
if (result.users = users, tweets.length > 0) {
|
|
269
|
+
const { tweets: filteredTweets, ads, exploreMore, recommendations } = filterTweets(tweets, tweets, options);
|
|
270
|
+
result.tweets = filteredTweets, result.ads = ads, result.exploreMore = exploreMore, result.recommendations = recommendations, result.photos = result.tweets.filter((tweet) => {
|
|
271
|
+
const legacy = tweet.legacy;
|
|
272
|
+
return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "photo");
|
|
273
|
+
}), result.videos = result.tweets.filter((tweet) => {
|
|
274
|
+
const legacy = tweet.legacy;
|
|
275
|
+
return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "video");
|
|
276
|
+
}), result.gifs = result.tweets.filter((tweet) => {
|
|
277
|
+
const legacy = tweet.legacy;
|
|
278
|
+
return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "animated_gif");
|
|
279
|
+
}), result.urls = result.tweets.filter((tweet) => {
|
|
280
|
+
const legacy = tweet.legacy;
|
|
281
|
+
return legacy && legacy.entities && legacy.entities.urls && legacy.entities.urls.length > 0;
|
|
282
|
+
});
|
|
143
283
|
}
|
|
144
|
-
return
|
|
145
|
-
}
|
|
146
|
-
/**
|
|
147
|
-
* 从数据中提取用户
|
|
148
|
-
* @param data 任意对象
|
|
149
|
-
* @returns IUser数组
|
|
150
|
-
*/
|
|
151
|
-
static #extractUsers(data) {
|
|
152
|
-
const users = [], searchUsers = (obj) => {
|
|
153
|
-
if (obj && typeof obj == "object") {
|
|
154
|
-
obj.__typename === "User" && obj.rest_id && users.push(obj);
|
|
155
|
-
for (const key in obj)
|
|
156
|
-
obj.hasOwnProperty(key) && searchUsers(obj[key]);
|
|
157
|
-
} else Array.isArray(obj) && obj.forEach((item) => searchUsers(item));
|
|
158
|
-
};
|
|
159
|
-
return searchUsers(data), users;
|
|
160
|
-
}
|
|
161
|
-
/**
|
|
162
|
-
* 从数据中提取推文
|
|
163
|
-
* @param data 任意对象
|
|
164
|
-
* @returns ITweet数组
|
|
165
|
-
*/
|
|
166
|
-
static #extractTweets(data) {
|
|
167
|
-
const tweets = [], searchTweets = (obj) => {
|
|
168
|
-
if (obj && typeof obj == "object") {
|
|
169
|
-
obj.__typename === "Tweet" && obj.rest_id && tweets.push(obj);
|
|
170
|
-
for (const key in obj)
|
|
171
|
-
obj.hasOwnProperty(key) && searchTweets(obj[key]);
|
|
172
|
-
} else Array.isArray(obj) && obj.forEach((item) => searchTweets(item));
|
|
173
|
-
};
|
|
174
|
-
return searchTweets(data), tweets;
|
|
175
|
-
}
|
|
176
|
-
/**
|
|
177
|
-
* 从数据中提取游标
|
|
178
|
-
* @param data 任意对象
|
|
179
|
-
* @param result 结果对象
|
|
180
|
-
*/
|
|
181
|
-
static #extractCursors(data, result) {
|
|
182
|
-
const searchCursors = (obj) => {
|
|
183
|
-
if (obj && typeof obj == "object") {
|
|
184
|
-
obj.cursor_top && (result.cursor_top = obj.cursor_top), obj.cursor_bottom && (result.cursor_bottom = obj.cursor_bottom), obj.next_cursor && (result.next_cursor = obj.next_cursor), obj.next_cursor_str && (result.next_cursor_str = obj.next_cursor_str), obj.previous_cursor && (result.previous_cursor = obj.previous_cursor), obj.previous_cursor_str && (result.previous_cursor_str = obj.previous_cursor_str), obj.entryId && (obj.entryId.startsWith("cursor-top-") || obj.entryId.startsWith("cursor-bottom-")) && (obj.entryId.startsWith("cursor-top-") ? result.cursor_top = obj.entryId.replace("cursor-top-", "") : result.cursor_bottom = obj.entryId.replace("cursor-bottom-", ""));
|
|
185
|
-
for (const key in obj)
|
|
186
|
-
obj.hasOwnProperty(key) && searchCursors(obj[key]);
|
|
187
|
-
} else Array.isArray(obj) && obj.forEach((item) => searchCursors(item));
|
|
188
|
-
};
|
|
189
|
-
searchCursors(data);
|
|
284
|
+
return extractCursors(data, result), result;
|
|
190
285
|
}
|
|
191
286
|
}
|
|
192
287
|
exports.XParser = XParser;
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,35 +1,39 @@
|
|
|
1
|
-
import { ISimpleResult, IOriginalResult
|
|
1
|
+
import { ISimpleResult, IOriginalResult } from './type.d.ts';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
4
|
* XParser 静态类,用于解析Twitter API响应
|
|
5
5
|
*/
|
|
6
|
+
interface IXParserOptions {
|
|
7
|
+
/**
|
|
8
|
+
* 是否包含广告推文(包括推广推文)
|
|
9
|
+
*/
|
|
10
|
+
includeAds?: boolean;
|
|
11
|
+
/**
|
|
12
|
+
* 是否包含探索更多内容
|
|
13
|
+
*/
|
|
14
|
+
includeExploreMore?: boolean;
|
|
15
|
+
/**
|
|
16
|
+
* 是否包含各类推荐推文
|
|
17
|
+
*/
|
|
18
|
+
includeRecommendations?: boolean;
|
|
19
|
+
}
|
|
6
20
|
|
|
7
21
|
declare class XParser {
|
|
8
|
-
#private;
|
|
9
22
|
/**
|
|
10
23
|
* 解析任意对象为ISimpleResult
|
|
11
24
|
* @param data 任意对象
|
|
25
|
+
* @param options 解析选项
|
|
12
26
|
* @returns ISimpleResult
|
|
13
27
|
*/
|
|
14
|
-
static parseSimple(data: any): ISimpleResult;
|
|
28
|
+
static parseSimple(data: any, options?: IXParserOptions): ISimpleResult;
|
|
15
29
|
/**
|
|
16
30
|
* 解析任意对象为IOriginalResult
|
|
17
31
|
* @param data 任意对象
|
|
32
|
+
* @param options 解析选项
|
|
18
33
|
* @returns IOriginalResult
|
|
19
34
|
*/
|
|
20
|
-
static parseOriginal(data: any): IOriginalResult;
|
|
21
|
-
/**
|
|
22
|
-
* 将IUser转换为ISimpleUser
|
|
23
|
-
* @param user IUser对象
|
|
24
|
-
* @returns ISimpleUser
|
|
25
|
-
*/
|
|
26
|
-
static convertToSimpleUser(user: IUser): ISimpleUser;
|
|
27
|
-
/**
|
|
28
|
-
* 将ITweet转换为ISimpleTweet
|
|
29
|
-
* @param tweet ITweet对象
|
|
30
|
-
* @returns ISimpleTweet
|
|
31
|
-
*/
|
|
32
|
-
static convertToSimpleTweet(tweet: ITweet): ISimpleTweet;
|
|
35
|
+
static parseOriginal(data: any, options?: IXParserOptions): IOriginalResult;
|
|
33
36
|
}
|
|
34
37
|
|
|
35
38
|
export { XParser };
|
|
39
|
+
export type { IXParserOptions };
|