gs-x-parser 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +77 -0
- package/README.zh.md +77 -0
- package/lib/index.cjs +17 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.mjs +2 -0
- package/lib/parser.cjs +192 -0
- package/lib/parser.d.ts +35 -0
- package/lib/parser.mjs +193 -0
- package/lib/type.cjs +4 -0
- package/lib/type.d.ts +1780 -0
- package/lib/type.mjs +36 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# GS X Parser
|
|
2
|
+
|
|
3
|
+
[中文](README.zh.md)
|
|
4
|
+
|
|
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
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- Complete TypeScript type definitions based on Twitter/X API data structures
|
|
12
|
+
- Support for parsing multiple data types such as users, tweets, media, etc.
|
|
13
|
+
- Provide two parsing modes: simple mode and original mode
|
|
14
|
+
- Support data extraction from various API responses
|
|
15
|
+
- Type safety to ensure reliable data processing
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# Using npm
|
|
21
|
+
npm install gs-x-parser
|
|
22
|
+
|
|
23
|
+
# Using yarn
|
|
24
|
+
yarn add gs-x-parser
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Usage Examples
|
|
28
|
+
|
|
29
|
+
### Parse Simple Mode
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { XParser } from './src/parser/XParser';
|
|
33
|
+
|
|
34
|
+
// Parse API response
|
|
35
|
+
const data = {/* data from API */};
|
|
36
|
+
const result = XParser.parseSimple(data);
|
|
37
|
+
|
|
38
|
+
// Access parsed results
|
|
39
|
+
console.log(result.users); // Simple user list
|
|
40
|
+
console.log(result.tweets); // Simple tweet list
|
|
41
|
+
console.log(result.photos); // Tweets with photos
|
|
42
|
+
console.log(result.videos); // Tweets with videos
|
|
43
|
+
console.log(result.urls); // Tweets with URLs
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### Parse Original Mode
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { XParser } from './src/parser/XParser';
|
|
50
|
+
|
|
51
|
+
// Parse API response
|
|
52
|
+
const data = {/* data from API */};
|
|
53
|
+
const result = XParser.parseOriginal(data);
|
|
54
|
+
|
|
55
|
+
// Access parsed results
|
|
56
|
+
console.log(result.users); // Original user list
|
|
57
|
+
console.log(result.tweets); // Original tweet list
|
|
58
|
+
console.log(result.photos); // Tweets with photos
|
|
59
|
+
console.log(result.videos); // Tweets with videos
|
|
60
|
+
console.log(result.urls); // Tweets with URLs
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Type Definitions
|
|
64
|
+
|
|
65
|
+
The project provides complete type definitions in the `src/type` directory, including:
|
|
66
|
+
|
|
67
|
+
- `IUser` - User type
|
|
68
|
+
- `ITweet` - Tweet type
|
|
69
|
+
- `IMediaEntity` - Media entity type
|
|
70
|
+
- `ISimpleUser` - Simple user type
|
|
71
|
+
- `ISimpleTweet` - Simple tweet type
|
|
72
|
+
- `ISimpleResult` - Simple result type
|
|
73
|
+
- `IOriginalResult` - Original result type
|
|
74
|
+
|
|
75
|
+
## License
|
|
76
|
+
|
|
77
|
+
This project uses the MIT license. For details, please see the [LICENSE](LICENSE) file.
|
package/README.zh.md
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# GS X Parser
|
|
2
|
+
|
|
3
|
+
[English](README.md)
|
|
4
|
+
|
|
5
|
+
## 项目简介
|
|
6
|
+
|
|
7
|
+
GS X Parser 是一个用于解析 Twitter/X API 响应的 TypeScript 库,提供了完整的类型定义和解析功能,支持将复杂的 API 响应转换为简单易用的格式。
|
|
8
|
+
|
|
9
|
+
## 功能特性
|
|
10
|
+
|
|
11
|
+
- 完整的 TypeScript 类型定义,基于 Twitter/X API 数据结构
|
|
12
|
+
- 支持解析用户、推文、媒体等多种数据类型
|
|
13
|
+
- 提供简单模式和原始模式两种解析方式
|
|
14
|
+
- 支持从各种 API 响应中提取数据
|
|
15
|
+
- 类型安全,确保数据处理的可靠性
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
# 使用 npm
|
|
21
|
+
npm install gs-x-parser
|
|
22
|
+
|
|
23
|
+
# 使用 yarn
|
|
24
|
+
yarn add gs-x-parser
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## 使用示例
|
|
28
|
+
|
|
29
|
+
### 解析简单模式
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { XParser } from './src/parser/XParser';
|
|
33
|
+
|
|
34
|
+
// 解析 API 响应
|
|
35
|
+
const data = {/* 从 API 获取的数据 */};
|
|
36
|
+
const result = XParser.parseSimple(data);
|
|
37
|
+
|
|
38
|
+
// 访问解析结果
|
|
39
|
+
console.log(result.users); // 简单用户列表
|
|
40
|
+
console.log(result.tweets); // 简单推文列表
|
|
41
|
+
console.log(result.photos); // 包含图片的推文
|
|
42
|
+
console.log(result.videos); // 包含视频的推文
|
|
43
|
+
console.log(result.urls); // 包含链接的推文
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### 解析原始模式
|
|
47
|
+
|
|
48
|
+
```typescript
|
|
49
|
+
import { XParser } from './src/parser/XParser';
|
|
50
|
+
|
|
51
|
+
// 解析 API 响应
|
|
52
|
+
const data = {/* 从 API 获取的数据 */};
|
|
53
|
+
const result = XParser.parseOriginal(data);
|
|
54
|
+
|
|
55
|
+
// 访问解析结果
|
|
56
|
+
console.log(result.users); // 原始用户列表
|
|
57
|
+
console.log(result.tweets); // 原始推文列表
|
|
58
|
+
console.log(result.photos); // 包含图片的推文
|
|
59
|
+
console.log(result.videos); // 包含视频的推文
|
|
60
|
+
console.log(result.urls); // 包含链接的推文
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## 类型定义
|
|
64
|
+
|
|
65
|
+
项目在 `src/type` 目录下提供了完整的类型定义,包括:
|
|
66
|
+
|
|
67
|
+
- `IUser` - 用户类型
|
|
68
|
+
- `ITweet` - 推文类型
|
|
69
|
+
- `IMediaEntity` - 媒体实体类型
|
|
70
|
+
- `ISimpleUser` - 简单用户类型
|
|
71
|
+
- `ISimpleTweet` - 简单推文类型
|
|
72
|
+
- `ISimpleResult` - 简单结果类型
|
|
73
|
+
- `IOriginalResult` - 原始结果类型
|
|
74
|
+
|
|
75
|
+
## 许可证
|
|
76
|
+
|
|
77
|
+
本项目使用 MIT 许可证,详情请查看 [LICENSE](LICENSE) 文件。
|
package/lib/index.cjs
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var type = require('./type.cjs'), parser = require('./parser.cjs');
|
|
3
|
+
Object.keys(type).forEach(function(k) {
|
|
4
|
+
k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k) && Object.defineProperty(exports, k, {
|
|
5
|
+
enumerable: !0,
|
|
6
|
+
get: function() {
|
|
7
|
+
return type[k];
|
|
8
|
+
}
|
|
9
|
+
});
|
|
10
|
+
}), Object.keys(parser).forEach(function(k) {
|
|
11
|
+
k !== "default" && !Object.prototype.hasOwnProperty.call(exports, k) && Object.defineProperty(exports, k, {
|
|
12
|
+
enumerable: !0,
|
|
13
|
+
get: function() {
|
|
14
|
+
return parser[k];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
});
|
package/lib/index.d.ts
ADDED
package/lib/index.mjs
ADDED
package/lib/parser.cjs
ADDED
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
class XParser {
|
|
3
|
+
/**
|
|
4
|
+
* 解析任意对象为ISimpleResult
|
|
5
|
+
* @param data 任意对象
|
|
6
|
+
* @returns ISimpleResult
|
|
7
|
+
*/
|
|
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;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* 解析任意对象为IOriginalResult
|
|
16
|
+
* @param data 任意对象
|
|
17
|
+
* @returns IOriginalResult
|
|
18
|
+
*/
|
|
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);
|
|
143
|
+
}
|
|
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);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
exports.XParser = XParser;
|
package/lib/parser.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { ISimpleResult, IOriginalResult, IUser, ISimpleUser, ITweet, ISimpleTweet } from './type.d.ts';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* XParser 静态类,用于解析Twitter API响应
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
declare class XParser {
|
|
8
|
+
#private;
|
|
9
|
+
/**
|
|
10
|
+
* 解析任意对象为ISimpleResult
|
|
11
|
+
* @param data 任意对象
|
|
12
|
+
* @returns ISimpleResult
|
|
13
|
+
*/
|
|
14
|
+
static parseSimple(data: any): ISimpleResult;
|
|
15
|
+
/**
|
|
16
|
+
* 解析任意对象为IOriginalResult
|
|
17
|
+
* @param data 任意对象
|
|
18
|
+
* @returns IOriginalResult
|
|
19
|
+
*/
|
|
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;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export { XParser };
|
package/lib/parser.mjs
ADDED
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
class XParser {
|
|
2
|
+
/**
|
|
3
|
+
* 解析任意对象为ISimpleResult
|
|
4
|
+
* @param data 任意对象
|
|
5
|
+
* @returns ISimpleResult
|
|
6
|
+
*/
|
|
7
|
+
static parseSimple(data) {
|
|
8
|
+
const result = {}, users = this.#extractUsers(data);
|
|
9
|
+
users.length > 0 && (result.users = users.map((user) => this.convertToSimpleUser(user)));
|
|
10
|
+
const tweets = this.#extractTweets(data);
|
|
11
|
+
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;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 解析任意对象为IOriginalResult
|
|
15
|
+
* @param data 任意对象
|
|
16
|
+
* @returns IOriginalResult
|
|
17
|
+
*/
|
|
18
|
+
static parseOriginal(data) {
|
|
19
|
+
const result = {}, users = this.#extractUsers(data);
|
|
20
|
+
users.length > 0 && (result.users = users);
|
|
21
|
+
const tweets = this.#extractTweets(data);
|
|
22
|
+
return tweets.length > 0 && (result.tweets = tweets, result.photos = result.tweets.filter((tweet) => {
|
|
23
|
+
const legacy = tweet.legacy;
|
|
24
|
+
return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "photo");
|
|
25
|
+
}), result.videos = result.tweets.filter((tweet) => {
|
|
26
|
+
const legacy = tweet.legacy;
|
|
27
|
+
return legacy && legacy.extended_entities && legacy.extended_entities.media && legacy.extended_entities.media.some((media) => media.type === "video" || media.type === "animated_gif");
|
|
28
|
+
}), result.urls = result.tweets.filter((tweet) => {
|
|
29
|
+
const legacy = tweet.legacy;
|
|
30
|
+
return legacy && legacy.entities && legacy.entities.urls && legacy.entities.urls.length > 0;
|
|
31
|
+
})), this.#extractCursors(data, result), result;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* 将IUser转换为ISimpleUser
|
|
35
|
+
* @param user IUser对象
|
|
36
|
+
* @returns ISimpleUser
|
|
37
|
+
*/
|
|
38
|
+
static convertToSimpleUser(user) {
|
|
39
|
+
return {
|
|
40
|
+
rest_id: user.rest_id,
|
|
41
|
+
name: user.legacy?.name || "",
|
|
42
|
+
screen_name: user.legacy?.screen_name || "",
|
|
43
|
+
profile_image_url_https: user.legacy?.profile_image_url_https || "",
|
|
44
|
+
verified: user.legacy?.verified,
|
|
45
|
+
followers_count: user.legacy?.followers_count,
|
|
46
|
+
friends_count: user.legacy?.friends_count,
|
|
47
|
+
statuses_count: user.legacy?.statuses_count,
|
|
48
|
+
description: user.legacy?.description,
|
|
49
|
+
location: user.legacy?.location,
|
|
50
|
+
url: user.legacy?.url,
|
|
51
|
+
userLabelType: user.affiliates_highlighted_label?.label?.userLabelType,
|
|
52
|
+
verified_type: user.legacy?.verified_type
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 将ITweet转换为ISimpleTweet
|
|
57
|
+
* @param tweet ITweet对象
|
|
58
|
+
* @returns ISimpleTweet
|
|
59
|
+
*/
|
|
60
|
+
static convertToSimpleTweet(tweet) {
|
|
61
|
+
const legacy = tweet.legacy, simpleTweet = {
|
|
62
|
+
rest_id: tweet.rest_id,
|
|
63
|
+
full_text: legacy?.full_text || "",
|
|
64
|
+
created_at: legacy?.created_at || "",
|
|
65
|
+
user_id: legacy?.user_id_str || "",
|
|
66
|
+
user_screen_name: "",
|
|
67
|
+
// 需要从用户信息中获取
|
|
68
|
+
retweet_count: legacy?.retweet_count,
|
|
69
|
+
favorite_count: legacy?.favorite_count,
|
|
70
|
+
reply_count: legacy?.reply_count,
|
|
71
|
+
quote_count: legacy?.quote_count,
|
|
72
|
+
lang: legacy?.lang,
|
|
73
|
+
conversation_id: legacy?.conversation_id_str,
|
|
74
|
+
possibly_sensitive: legacy?.possibly_sensitive,
|
|
75
|
+
is_retweet: !!legacy?.retweeted_status_id_str,
|
|
76
|
+
retweeted_status_id: legacy?.retweeted_status_id_str,
|
|
77
|
+
quoted_status_id: legacy?.quoted_status_id_str
|
|
78
|
+
};
|
|
79
|
+
if (legacy?.extended_entities?.media) {
|
|
80
|
+
const photos = [], videos = [];
|
|
81
|
+
legacy.extended_entities.media.forEach((media) => {
|
|
82
|
+
if (media.type === "photo") {
|
|
83
|
+
let simpleSizes;
|
|
84
|
+
media.sizes && (simpleSizes = {}, media.sizes.thumb && (simpleSizes.thumb = {
|
|
85
|
+
w: media.sizes.thumb.w || 0,
|
|
86
|
+
h: media.sizes.thumb.h || 0,
|
|
87
|
+
resize: media.sizes.thumb.resize || "fit"
|
|
88
|
+
}), media.sizes.small && (simpleSizes.small = {
|
|
89
|
+
w: media.sizes.small.w || 0,
|
|
90
|
+
h: media.sizes.small.h || 0,
|
|
91
|
+
resize: media.sizes.small.resize || "fit"
|
|
92
|
+
}), media.sizes.medium && (simpleSizes.medium = {
|
|
93
|
+
w: media.sizes.medium.w || 0,
|
|
94
|
+
h: media.sizes.medium.h || 0,
|
|
95
|
+
resize: media.sizes.medium.resize || "fit"
|
|
96
|
+
}), media.sizes.large && (simpleSizes.large = {
|
|
97
|
+
w: media.sizes.large.w || 0,
|
|
98
|
+
h: media.sizes.large.h || 0,
|
|
99
|
+
resize: media.sizes.large.resize || "fit"
|
|
100
|
+
}));
|
|
101
|
+
let simpleOriginalInfo;
|
|
102
|
+
media.original_info && (simpleOriginalInfo = {
|
|
103
|
+
width: media.original_info.width || 0,
|
|
104
|
+
height: media.original_info.height || 0
|
|
105
|
+
}), photos.push({
|
|
106
|
+
media_key: media.media_key || "",
|
|
107
|
+
type: "photo",
|
|
108
|
+
media_url_https: media.media_url_https || "",
|
|
109
|
+
display_url: media.display_url || "",
|
|
110
|
+
expanded_url: media.expanded_url || "",
|
|
111
|
+
sizes: simpleSizes,
|
|
112
|
+
original_info: simpleOriginalInfo
|
|
113
|
+
});
|
|
114
|
+
} else if (media.type === "video" || media.type === "animated_gif") {
|
|
115
|
+
let simpleVideoInfo;
|
|
116
|
+
media.video_info && (simpleVideoInfo = {
|
|
117
|
+
aspect_ratio: media.video_info.aspect_ratio,
|
|
118
|
+
duration_millis: media.video_info.duration_millis,
|
|
119
|
+
variants: media.video_info.variants?.map((variant) => ({
|
|
120
|
+
bitrate: variant.bitrate,
|
|
121
|
+
content_type: variant.content_type || "",
|
|
122
|
+
url: variant.url || ""
|
|
123
|
+
}))
|
|
124
|
+
}), videos.push({
|
|
125
|
+
media_key: media.media_key || "",
|
|
126
|
+
type: media.type,
|
|
127
|
+
media_url_https: media.media_url_https || "",
|
|
128
|
+
display_url: media.display_url || "",
|
|
129
|
+
expanded_url: media.expanded_url || "",
|
|
130
|
+
video_info: simpleVideoInfo
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
}), photos.length > 0 && (simpleTweet.photos = photos), videos.length > 0 && (simpleTweet.videos = videos);
|
|
134
|
+
}
|
|
135
|
+
if (legacy?.entities?.urls) {
|
|
136
|
+
const urls = legacy.entities.urls.map((url) => ({
|
|
137
|
+
url: url.url || "",
|
|
138
|
+
expanded_url: url.expanded_url || "",
|
|
139
|
+
display_url: url.display_url || ""
|
|
140
|
+
}));
|
|
141
|
+
urls.length > 0 && (simpleTweet.urls = urls);
|
|
142
|
+
}
|
|
143
|
+
return simpleTweet;
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* 从数据中提取用户
|
|
147
|
+
* @param data 任意对象
|
|
148
|
+
* @returns IUser数组
|
|
149
|
+
*/
|
|
150
|
+
static #extractUsers(data) {
|
|
151
|
+
const users = [], searchUsers = (obj) => {
|
|
152
|
+
if (obj && typeof obj == "object") {
|
|
153
|
+
obj.__typename === "User" && obj.rest_id && users.push(obj);
|
|
154
|
+
for (const key in obj)
|
|
155
|
+
obj.hasOwnProperty(key) && searchUsers(obj[key]);
|
|
156
|
+
} else Array.isArray(obj) && obj.forEach((item) => searchUsers(item));
|
|
157
|
+
};
|
|
158
|
+
return searchUsers(data), users;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* 从数据中提取推文
|
|
162
|
+
* @param data 任意对象
|
|
163
|
+
* @returns ITweet数组
|
|
164
|
+
*/
|
|
165
|
+
static #extractTweets(data) {
|
|
166
|
+
const tweets = [], searchTweets = (obj) => {
|
|
167
|
+
if (obj && typeof obj == "object") {
|
|
168
|
+
obj.__typename === "Tweet" && obj.rest_id && tweets.push(obj);
|
|
169
|
+
for (const key in obj)
|
|
170
|
+
obj.hasOwnProperty(key) && searchTweets(obj[key]);
|
|
171
|
+
} else Array.isArray(obj) && obj.forEach((item) => searchTweets(item));
|
|
172
|
+
};
|
|
173
|
+
return searchTweets(data), tweets;
|
|
174
|
+
}
|
|
175
|
+
/**
|
|
176
|
+
* 从数据中提取游标
|
|
177
|
+
* @param data 任意对象
|
|
178
|
+
* @param result 结果对象
|
|
179
|
+
*/
|
|
180
|
+
static #extractCursors(data, result) {
|
|
181
|
+
const searchCursors = (obj) => {
|
|
182
|
+
if (obj && typeof obj == "object") {
|
|
183
|
+
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-", ""));
|
|
184
|
+
for (const key in obj)
|
|
185
|
+
obj.hasOwnProperty(key) && searchCursors(obj[key]);
|
|
186
|
+
} else Array.isArray(obj) && obj.forEach((item) => searchCursors(item));
|
|
187
|
+
};
|
|
188
|
+
searchCursors(data);
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
export {
|
|
192
|
+
XParser
|
|
193
|
+
};
|
package/lib/type.cjs
ADDED
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const USER_TYPENAMES = ["User", "UserUnavailable"], TWEET_TYPENAMES = ["Tweet", "TextTombstone", "TweetTombstone", "TweetWithVisibilityResults", "ContextualTweetInterstitial"], COMMUNITY_TYPENAMES = ["Community", "CommunityDeleteActionUnavailable", "CommunityUserDefaultModerationState", "CommunityJoinRequestsUnavailable"], TIMELINE_TYPENAMES = ["TimelineTimelineItem", "TimelineMessagePrompt"], TIMELINE_ITEM_TYPENAMES = ["TimelineTweet", "TimelineMessagePrompt"], TIMELINE_INSTRUCTION_TYPES = ["TimelineAddEntries"], TIMELINE_ENTRY_TYPES = ["TimelineTimelineItem", "TimelineTimelineModule", "TimelineTimelineCursor"], TIMELINE_ITEM_TYPES = ["TimelineTweet", "TimelineMessagePrompt"], MEDIA_TYPES = ["photo", "video", "animated_gif"], ELIGIBILITY_TYPES = ["Eligible", "IneligibleUserUnauthorized"], DISPLAY_TYPES = ["Carousel", "Vertical", "VerticalConversation", "Classic", "EntireTweet"], TWEET_DISPLAY_TYPES = ["CondensedTweet", "Tweet", "SelfThread"], USER_DISPLAY_TYPES = ["SubscribableUser", "User"], INJECTION_TYPES = ["ForYouInNetwork", "ForYouPromoted", "WhoToFollow", "creators-only-connect-tab", "CommunityToJoin"], TRANSPARENT_GUIDE_DETAIL_TYPES = ["TimelineEventUrtMetadata"], ELEMENT_TYPES = ["tweet", "user", "event", "feedback", "trend", "users_followed_you", "generic_report_received", "users_liked_your_tweet", "user_quoted_your_tweet", "user_replied_to_your_tweet", "users_retweeted_your_tweet", "user_mentioned_you"], TWEET_QUALITIES = ["HighQuality", "LowQuality", "AbusiveQuality", "RelatedTweet"], COMPONENT_VALUES = ["url", "tweet", "trends", "unified_events", "related_tweet", "suggest_who_to_follow", "for_you_in_network", "for-you-promoted", "alt-text-prompt-injection", "ads-sharing-x-premium-upsell-candidate"], ITEM_TYPES = ["TimelineTweet", "TimelineUser", "TimelineEventSummary", "TimelineMessagePrompt", "TimelineCommunity", "CommunityPinnedTimeline"], CURSOR_TYPES = ["Top", "Bottom", "ShowMore", "ShowMoreThreads"], INSTRUCTION_TYPES = ["TimelineClearCache", "TimelineAddEntries", "TimelineTerminateTimeline", "TimelineShowAlert", "TimelineShowCover"], CONTENT_TYPES = ["TimelineTweetMedia", "TimelineUrl"], VIDEO_CONTENT_TYPES = ["application/x-mpegURL", "video/mp4"], MESSAGE_INBOX_TIMELINE_INDEXES = ["trusted", "untrusted", "untrusted_low_quality"], NOTIFICATION_KEYS = ["clearCache", "addEntries", "clearEntriesUnreadState", "markEntriesUnreadGreaterThanSortIndex"], VERIFIED_TYPES = ["Business", "Government", "Blue", "None"], USER_LABEL_TYPES = ["BusinessLabel", "AutomatedLabel"], URL_TYPES = ["ExternalUrl", "DeepLink", "UrtEndpoint"], VIEW_STATES = ["EnabledWithCount", "Enabled"], LEGACY_CARD_BINDING_KEY_TYPES = ["thumbnail_image", "description", "domain", "thumbnail_image_large", "thumbnail_image_original", "thumbnail_image_small", "thumbnail_image_x_large", "thumbnail_image_color", "summary_photo_image", "summary_photo_image_small", "summary_photo_image_large", "summary_photo_image_x_large", "summary_photo_image_original", "summary_photo_image_color", "photo_image_full_size_color", "vanity_url", "title", "card_url", "creator", "site", "player_image", "player_image_small", "player_image_large", "player_image_x_large", "player_image_original", "player_image_color", "player_url", "player_width", "player_height", "app_name", "app_is_free", "app_star_rating", "app_num_ratings", "app_price_currency", "unified_card", "app_price_amount"], LEGACY_CARD_BINDING_VALUE_TYPES = ["STRING", "IMAGE_COLOR", "IMAGE", "USER"];
|
|
3
|
+
var IdPrefixes = /* @__PURE__ */ ((IdPrefixes2) => (IdPrefixes2.MessagePrompt = "messageprompt-", IdPrefixes2.NormalTweet = "tweet-", IdPrefixes2.PromotedTweet = "promoted-tweet-", IdPrefixes2.User = "user-", IdPrefixes2.CommunityToJoin = "community-to-join-", IdPrefixes2.HomeConversation = "home-conversation-", IdPrefixes2.Conversationthread = "conversationthread-", IdPrefixes2.CommunityConversation = "community-conversation-", IdPrefixes2.CommunityGrid = "communities-grid-", IdPrefixes2.PinnedTweets = "pinned-tweets-", IdPrefixes2.WhoToFollow = "who-to-follow-", IdPrefixes2.CreatorsOnlyConnectTab = "creators-only-connect-tab-", IdPrefixes2.Label = "label-", IdPrefixes2.Guide = "Guide-", IdPrefixes2.ProfileGrid = "profile-grid-", IdPrefixes2.Notification = "notification-", IdPrefixes2.Cursor = "cursor-", IdPrefixes2.CursorTop = "cursor-top-", IdPrefixes2.CursorBottom = "cursor-bottom-", IdPrefixes2.TweetDetailRelatedTweets = "tweetdetailrelatedtweets-", IdPrefixes2.Trends = "trends-", IdPrefixes2))(IdPrefixes || {});
|
|
4
|
+
exports.COMMUNITY_TYPENAMES = COMMUNITY_TYPENAMES, exports.COMPONENT_VALUES = COMPONENT_VALUES, exports.CONTENT_TYPES = CONTENT_TYPES, exports.CURSOR_TYPES = CURSOR_TYPES, exports.DISPLAY_TYPES = DISPLAY_TYPES, exports.ELEMENT_TYPES = ELEMENT_TYPES, exports.ELIGIBILITY_TYPES = ELIGIBILITY_TYPES, exports.INJECTION_TYPES = INJECTION_TYPES, exports.INSTRUCTION_TYPES = INSTRUCTION_TYPES, exports.ITEM_TYPES = ITEM_TYPES, exports.IdPrefixes = IdPrefixes, exports.LEGACY_CARD_BINDING_KEY_TYPES = LEGACY_CARD_BINDING_KEY_TYPES, exports.LEGACY_CARD_BINDING_VALUE_TYPES = LEGACY_CARD_BINDING_VALUE_TYPES, exports.MEDIA_TYPES = MEDIA_TYPES, exports.MESSAGE_INBOX_TIMELINE_INDEXES = MESSAGE_INBOX_TIMELINE_INDEXES, exports.NOTIFICATION_KEYS = NOTIFICATION_KEYS, exports.TIMELINE_ENTRY_TYPES = TIMELINE_ENTRY_TYPES, exports.TIMELINE_INSTRUCTION_TYPES = TIMELINE_INSTRUCTION_TYPES, exports.TIMELINE_ITEM_TYPENAMES = TIMELINE_ITEM_TYPENAMES, exports.TIMELINE_ITEM_TYPES = TIMELINE_ITEM_TYPES, exports.TIMELINE_TYPENAMES = TIMELINE_TYPENAMES, exports.TRANSPARENT_GUIDE_DETAIL_TYPES = TRANSPARENT_GUIDE_DETAIL_TYPES, exports.TWEET_DISPLAY_TYPES = TWEET_DISPLAY_TYPES, exports.TWEET_QUALITIES = TWEET_QUALITIES, exports.TWEET_TYPENAMES = TWEET_TYPENAMES, exports.URL_TYPES = URL_TYPES, exports.USER_DISPLAY_TYPES = USER_DISPLAY_TYPES, exports.USER_LABEL_TYPES = USER_LABEL_TYPES, exports.USER_TYPENAMES = USER_TYPENAMES, exports.VERIFIED_TYPES = VERIFIED_TYPES, exports.VIDEO_CONTENT_TYPES = VIDEO_CONTENT_TYPES, exports.VIEW_STATES = VIEW_STATES;
|