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 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
@@ -1,11 +1,9 @@
1
1
  # GS X Parser
2
2
 
3
- [English](README.md)
4
-
5
- ## 项目简介
6
-
7
3
  GS X Parser 是一个用于解析 Twitter/X API 响应的 TypeScript 库,提供了完整的类型定义和解析功能,支持将复杂的 API 响应转换为简单易用的格式。
8
4
 
5
+ [English](README.md)
6
+
9
7
  ## 功能特性
10
8
 
11
9
  - 完整的 TypeScript 类型定义,基于 Twitter/X API 数据结构
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 = {}, users = this.#extractUsers(data);
10
- users.length > 0 && (result.users = users.map((user) => this.convertToSimpleUser(user)));
11
- const tweets = this.#extractTweets(data);
12
- return tweets.length > 0 && (result.tweets = tweets.map((tweet) => this.convertToSimpleTweet(tweet)), 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.urls = result.tweets.filter((tweet) => tweet.urls && tweet.urls.length > 0)), this.#extractCursors(data, result), result;
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 = {}, users = this.#extractUsers(data);
21
- users.length > 0 && (result.users = users);
22
- const tweets = this.#extractTweets(data);
23
- return tweets.length > 0 && (result.tweets = tweets, result.photos = result.tweets.filter((tweet) => {
24
- const legacy = tweet.legacy;
25
- return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "photo");
26
- }), result.videos = result.tweets.filter((tweet) => {
27
- const legacy = tweet.legacy;
28
- return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "video" || media.type === "animated_gif");
29
- }), result.urls = result.tweets.filter((tweet) => {
30
- const legacy = tweet.legacy;
31
- return legacy && legacy.entities && legacy.entities.urls && legacy.entities.urls.length > 0;
32
- })), this.#extractCursors(data, result), result;
33
- }
34
- /**
35
- * 将IUser转换为ISimpleUser
36
- * @param user IUser对象
37
- * @returns ISimpleUser
38
- */
39
- static convertToSimpleUser(user) {
40
- return {
41
- rest_id: user.rest_id,
42
- name: user.legacy?.name || "",
43
- screen_name: user.legacy?.screen_name || "",
44
- profile_image_url_https: user.legacy?.profile_image_url_https || "",
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 simpleTweet;
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, IUser, ISimpleUser, ITweet, ISimpleTweet } from './type.d.ts';
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 };