gs-x-parser 1.0.1 → 1.0.3

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.
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var xhr = require('gs-web-hooks/xhr'), parser = require('./parser.cjs'), event = require('gs-dom/event'), type = require('./type.cjs'), observer = require('gs-dom/observer'), dom = require('./dom.cjs');
3
+ const ListenFlagKey = "__listen-tweet-flag-key";
4
+ class ListenFlag {
5
+ static #flag;
6
+ static get flag() {
7
+ return this.#flag || (this.#flag = self[ListenFlagKey]);
8
+ }
9
+ static isInit(arg) {
10
+ const init = !!this.flag;
11
+ return arg && this.update(arg), init;
12
+ }
13
+ static update(arg) {
14
+ self[ListenFlagKey] ? (arg?.enableTweetDetected && (self[ListenFlagKey].enableTweetDetected = !0), arg?.enableUserDetected && (self[ListenFlagKey].enableUserDetected = !0)) : Object.defineProperty(self, ListenFlagKey, {
15
+ value: { ...arg },
16
+ enumerable: !1,
17
+ configurable: !1,
18
+ writable: !1
19
+ }), this.#flag = self[ListenFlagKey];
20
+ }
21
+ }
22
+ const timelineRegex = /TweetDetail|Timeline|Bookmarks|UserTweets|UserMedia|Likes/, id = "__listen-tweet-interceptor";
23
+ function listenNet() {
24
+ xhr.addXhrInterceptor({
25
+ id,
26
+ before: (url) => timelineRegex.test(url) ? url : void 0,
27
+ after: (text) => {
28
+ const json = JSON.parse(text), { tweets, users, videos } = parser.XParser.parseSimple(json), flag = ListenFlag.flag || {};
29
+ if (flag.enableVideoDetected && videos?.length) try {
30
+ event.trigger(type.ListenTweetEvents.VideoDetected, { detail: videos });
31
+ } catch {
32
+ }
33
+ if (flag.enableTweetDetected && tweets?.length) try {
34
+ event.trigger(type.ListenTweetEvents.UserDetected, { detail: tweets });
35
+ } catch {
36
+ }
37
+ if (flag.enableUserDetected && users?.length) try {
38
+ event.trigger(type.ListenTweetEvents.UserDetected, { detail: users });
39
+ } catch {
40
+ }
41
+ }
42
+ });
43
+ }
44
+ const containerSelector = "main,main *", itemAndInnerSelector = [dom.primaryItemSelector, dom.primaryCellInnerSelector].join(",");
45
+ function addedElements(els) {
46
+ const { enableVideoRendered: vr, enableTweetRendered: tr } = ListenFlag.flag || {};
47
+ if (!vr && !tr)
48
+ return;
49
+ const tweets = [];
50
+ function parseTweetId(el) {
51
+ const id2 = dom.parseTweetIdByProps(el);
52
+ id2 && tweets.push(id2);
53
+ }
54
+ function processTarget(el) {
55
+ el.matches(dom.primaryItemSelector) ? tr && parseTweetId(el) : el.matches(dom.primaryCellSelector) ? (tr && parseTweetId(el), els.push(...el.querySelectorAll(itemAndInnerSelector))) : el.matches(dom.primaryCellInnerSelector) ? tr && parseTweetId(el) : el.matches(containerSelector) && el.clientHeight > 300 && els.push(...el.querySelectorAll(dom.primaryTweetSelector));
56
+ }
57
+ for (let i = 0; i < els.length; i++)
58
+ try {
59
+ processTarget(els[i]);
60
+ } catch (e) {
61
+ console.warn(e);
62
+ }
63
+ tr && tweets.length && event.trigger(type.ListenTweetEvents.TweetRendered, { detail: tweets });
64
+ }
65
+ function observePage() {
66
+ observer.observe({ subtree: !0, addedElements });
67
+ }
68
+ function startListen(option) {
69
+ ListenFlag.isInit(option) || (listenNet(), observePage());
70
+ }
71
+ exports.startListen = startListen;
@@ -0,0 +1,5 @@
1
+ import { IListenTweetOption } from './type.d.ts';
2
+
3
+ declare function startListen(option: IListenTweetOption): void;
4
+
5
+ export { startListen };
@@ -0,0 +1,77 @@
1
+ import { addXhrInterceptor } from 'gs-web-hooks/xhr';
2
+ import { XParser } from './parser.mjs';
3
+ import { trigger } from 'gs-dom/event';
4
+ import { ListenTweetEvents } from './type.mjs';
5
+ import { observe } from 'gs-dom/observer';
6
+ import { primaryItemSelector, primaryCellSelector, primaryCellInnerSelector, primaryTweetSelector, parseTweetIdByProps } from './dom.mjs';
7
+ const ListenFlagKey = "__listen-tweet-flag-key";
8
+ class ListenFlag {
9
+ static #flag;
10
+ static get flag() {
11
+ return this.#flag || (this.#flag = self[ListenFlagKey]);
12
+ }
13
+ static isInit(arg) {
14
+ const init = !!this.flag;
15
+ return arg && this.update(arg), init;
16
+ }
17
+ static update(arg) {
18
+ self[ListenFlagKey] ? (arg?.enableTweetDetected && (self[ListenFlagKey].enableTweetDetected = !0), arg?.enableUserDetected && (self[ListenFlagKey].enableUserDetected = !0)) : Object.defineProperty(self, ListenFlagKey, {
19
+ value: { ...arg },
20
+ enumerable: !1,
21
+ configurable: !1,
22
+ writable: !1
23
+ }), this.#flag = self[ListenFlagKey];
24
+ }
25
+ }
26
+ const timelineRegex = /TweetDetail|Timeline|Bookmarks|UserTweets|UserMedia|Likes/, id = "__listen-tweet-interceptor";
27
+ function listenNet() {
28
+ addXhrInterceptor({
29
+ id,
30
+ before: (url) => timelineRegex.test(url) ? url : void 0,
31
+ after: (text) => {
32
+ const json = JSON.parse(text), { tweets, users, videos } = XParser.parseSimple(json), flag = ListenFlag.flag || {};
33
+ if (flag.enableVideoDetected && videos?.length) try {
34
+ trigger(ListenTweetEvents.VideoDetected, { detail: videos });
35
+ } catch {
36
+ }
37
+ if (flag.enableTweetDetected && tweets?.length) try {
38
+ trigger(ListenTweetEvents.UserDetected, { detail: tweets });
39
+ } catch {
40
+ }
41
+ if (flag.enableUserDetected && users?.length) try {
42
+ trigger(ListenTweetEvents.UserDetected, { detail: users });
43
+ } catch {
44
+ }
45
+ }
46
+ });
47
+ }
48
+ const containerSelector = "main,main *", itemAndInnerSelector = [primaryItemSelector, primaryCellInnerSelector].join(",");
49
+ function addedElements(els) {
50
+ const { enableVideoRendered: vr, enableTweetRendered: tr } = ListenFlag.flag || {};
51
+ if (!vr && !tr)
52
+ return;
53
+ const tweets = [];
54
+ function parseTweetId(el) {
55
+ const id2 = parseTweetIdByProps(el);
56
+ id2 && tweets.push(id2);
57
+ }
58
+ function processTarget(el) {
59
+ el.matches(primaryItemSelector) ? tr && parseTweetId(el) : el.matches(primaryCellSelector) ? (tr && parseTweetId(el), els.push(...el.querySelectorAll(itemAndInnerSelector))) : el.matches(primaryCellInnerSelector) ? tr && parseTweetId(el) : el.matches(containerSelector) && el.clientHeight > 300 && els.push(...el.querySelectorAll(primaryTweetSelector));
60
+ }
61
+ for (let i = 0; i < els.length; i++)
62
+ try {
63
+ processTarget(els[i]);
64
+ } catch (e) {
65
+ console.warn(e);
66
+ }
67
+ tr && tweets.length && trigger(ListenTweetEvents.TweetRendered, { detail: tweets });
68
+ }
69
+ function observePage() {
70
+ observe({ subtree: !0, addedElements });
71
+ }
72
+ function startListen(option) {
73
+ ListenFlag.isInit(option) || (listenNet(), observePage());
74
+ }
75
+ export {
76
+ startListen
77
+ };
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;