x-reader.js
1.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
/**
* XReader — 解析 X (Twitter) 官方导出的 tweets.js 文件
*
* 文件格式:window.YTD.tweets.part0 = [{tweet: {...}}, ...]
* 提取字段:id_str, created_at, full_text, entities.hashtags[]
*/
const fs = require('fs');
class XReader {
constructor(filePath) {
this.filePath = filePath;
}
/**
* 解析 tweets.js,返回推文数组
* @returns {Array<{id, createdAt, text, hashtags}>}
*/
parseTweets() {
const raw = fs.readFileSync(this.filePath, 'utf-8');
// 去掉 window.YTD.tweets.part0 = 前缀,取出 JSON 数组部分
const jsonStart = raw.indexOf('[');
if (jsonStart === -1) {
throw new Error('tweets.js 格式错误:找不到 JSON 数组起始符 [');
}
let jsonStr = raw.slice(jsonStart).trim();
// 某些导出版本末尾有分号
if (jsonStr.endsWith(';')) {
jsonStr = jsonStr.slice(0, -1);
}
let rawTweets;
try {
rawTweets = JSON.parse(jsonStr);
} catch (e) {
throw new Error('tweets.js JSON 解析失败:' + e.message);
}
const tweets = [];
for (const item of rawTweets) {
const t = item.tweet || item; // 兼容两种格式
if (!t) continue;
const hashtags = (t.entities?.hashtags || []).map(h =>
typeof h === 'string' ? h : (h.text || h.tag || '')
).filter(Boolean);
tweets.push({
id: t.id_str || t.id || '',
createdAt: t.created_at || '',
text: t.full_text || t.text || '',
hashtags,
});
}
return tweets;
}
}
module.exports = { XReader };