x-reader.js 1.74 KB
/**
 * 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 };