gs-x-parser 1.0.1 → 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/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/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 };
|