Greasy Fork

Greasy Fork is available in English.

EPIC游戏库存导出

修复日期提取问题,精确提取EPIC游戏订单信息,基于HTML表格结构。

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         EPIC游戏库存导出
// @namespace    http://tampermonkey.net/
// @version      1.5
// @license PaperTiger
// @description  修复日期提取问题,精确提取EPIC游戏订单信息,基于HTML表格结构。
// @author       Paper Tiger
// @match         *://*.epicgames.com/account/*
// @require      https://cdnjs.cloudflare.com/ajax/libs/xlsx/0.17.0/xlsx.full.min.js
// @grant        none
// ==/UserScript==

/* global XLSX */

(function() {
    'use strict';

    let isProcessing = false;
    let currentPage = 0;
    let allOrderData = [];
    let processedOrderIds = new Set(); // 用于跟踪已处理的订单ID

    function createExportButton() {
        const button = document.createElement('button');
        button.textContent = '开始导出';
        button.id = 'epic-export-btn';
        button.style.position = 'fixed';
        button.style.top = '10px';
        button.style.right = '400px';
        button.style.zIndex = '99999';
        button.style.padding = '10px';
        button.style.backgroundColor = 'rgba(40, 167, 69, 1)';
        button.style.color = 'white';
        button.style.border = '2px solid red';
        button.style.borderRadius = '5px';
        button.style.cursor = 'pointer';
        document.body.appendChild(button);

        // 创建进度显示区域
        const progressDiv = document.createElement('div');
        progressDiv.id = 'epic-progress';
        progressDiv.style.position = 'fixed';
        progressDiv.style.top = '50px';
        progressDiv.style.right = '400px';
        progressDiv.style.zIndex = '99999';
        progressDiv.style.padding = '10px';
        progressDiv.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
        progressDiv.style.color = 'white';
        progressDiv.style.borderRadius = '5px';
        progressDiv.style.display = 'none';
        progressDiv.style.maxWidth = '300px';
        document.body.appendChild(progressDiv);

        button.addEventListener('click', function() {
            if (!isProcessing) {
                isProcessing = true;
                button.textContent = '处理中...';
                button.disabled = true;
                allOrderData = []; // 清空之前的数据
                processedOrderIds.clear(); // 清空已处理的订单ID
                currentPage = 0;
                clickTransactions();
            }
        });
    }

    function updateProgress(message) {
        const progressDiv = document.getElementById('epic-progress');
        if (progressDiv) {
            progressDiv.style.display = 'block';
            progressDiv.innerHTML = message;
        }
    }

    function clickTransactions() {
        const transactionsButton = document.querySelector('#nav-link-transactions');
        if (transactionsButton) {
            transactionsButton.click();
            console.log('点击了交易按钮');
            updateProgress('正在加载交易页面...');
            // 等待页面加载后开始提取数据
            setTimeout(extractCurrentPageData, 2000);
        } else {
            console.log("找不到交易按钮。");
            updateProgress('找不到交易按钮,请确保在正确的页面上。');
            resetButton();
        }
    }

    function extractCurrentPageData() {
        currentPage++;
        updateProgress(`正在处理第 ${currentPage} 页...`);

        // 查找所有订单行 - 使用更精确的选择器
        const orderRows = document.querySelectorAll('tr[data-orderid]');
        console.log(`第 ${currentPage} 页找到 ${orderRows.length} 个订单`);

        let pageOrderCount = 0;
        orderRows.forEach((row, index) => {
            try {
                const orderData = extractOrderDataFromRow(row);
                if (orderData && orderData['订单ID']) {
                    // 检查订单ID是否已经处理过
                    if (!processedOrderIds.has(orderData['订单ID'])) {
                        processedOrderIds.add(orderData['订单ID']);
                        allOrderData.push(orderData);
                        pageOrderCount++;
                        console.log(`提取订单: ${orderData['订单ID']} - ${orderData['订单日期']} - ${orderData['游戏名称']} - ${orderData['付款金额']}`);
                    } else {
                        console.log(`跳过重复订单ID: ${orderData['订单ID']}`);
                    }
                } else {
                    console.warn(`第 ${index + 1} 个订单数据无效或缺少订单ID`);
                }
            } catch (error) {
                console.error(`提取第 ${index + 1} 个订单数据时出错:`, error);
            }
        });

        console.log(`第 ${currentPage} 页提取了 ${pageOrderCount} 个新订单,总计 ${allOrderData.length} 个订单`);

        // 检查是否有下一页
        setTimeout(checkNextPage, 1000);
    }

    function extractOrderDataFromRow(row) {
        try {
            // 获取订单ID - 确保从正确的属性获取
            const orderId = row.getAttribute('data-orderid');
            if (!orderId) {
                console.warn('订单行缺少data-orderid属性');
                return null;
            }

            // 获取订单信息单元格 (第二个td)
            const infoCell = row.querySelector('td:nth-child(2)');
            if (!infoCell) {
                console.warn('找不到订单信息单元格');
                return null;
            }

            // 提取日期 - 修复日期提取逻辑
            let orderDate = '';
            const mainDiv = infoCell.querySelector('.am-yd8sa2');
            if (mainDiv) {
                // 获取第一个文本节点,这应该是日期
                const firstChild = mainDiv.firstChild;
                if (firstChild && firstChild.nodeType === Node.TEXT_NODE) {
                    orderDate = firstChild.textContent.trim();
                } else {
                    // 如果没有直接的文本节点,尝试其他方法
                    const dateText = mainDiv.textContent;
                    // 使用正则表达式提取日期格式
                    const dateMatch = dateText.match(/(\d{4}年\d{1,2}月\d{1,2}日)/);
                    if (dateMatch) {
                        orderDate = dateMatch[1];
                    }
                }
            }

            // 提取游戏名称 - 查找包含游戏名称的span元素
            let gameName = '';
            const gameNameElements = infoCell.querySelectorAll('.am-hoct6b');
            if (gameNameElements.length > 0) {
                gameName = gameNameElements[0].textContent.trim();
            }

            // 提取价格信息 - 查找包含"价格"文本的元素
            let price = '0.00';
            const priceElements = infoCell.querySelectorAll('.am-brjg0');
            for (let element of priceElements) {
                if (element.textContent.includes('价格')) {
                    const priceContainer = element.nextElementSibling;
                    if (priceContainer && priceContainer.classList.contains('am-1v0j95h')) {
                        const priceText = priceContainer.textContent.trim();
                        // 提取价格数字,处理 "¥0.00" 或 "- ¥0.00" 格式
                        const priceMatch = priceText.match(/[¥$]?([\d.,]+)/);
                        if (priceMatch) {
                            price = priceMatch[1];
                        }
                        break;
                    }
                }
            }

            // 提取商城信息
            let marketplace = '';
            for (let element of priceElements) {
                if (element.textContent.includes('商城')) {
                    const marketplaceContainer = element.nextElementSibling;
                    if (marketplaceContainer && marketplaceContainer.classList.contains('am-1v0j95h')) {
                        marketplace = marketplaceContainer.textContent.trim();
                        break;
                    }
                }
            }

            // 提取说明信息
            let description = '';
            for (let element of priceElements) {
                if (element.textContent.includes('说明')) {
                    const descContainer = element.nextElementSibling;
                    if (descContainer && descContainer.classList.contains('am-1v0j95h')) {
                        const descElement = descContainer.querySelector('.am-1rqw9bo');
                        if (descElement) {
                            description = descElement.textContent.trim();
                        }
                        break;
                    }
                }
            }

            // 验证数据完整性
            if (!orderId || !orderDate) {
                console.warn(`订单数据不完整: ID=${orderId}, 日期=${orderDate}`);
                return null;
            }

            // 调试信息
            console.log(`调试 - 订单ID: ${orderId}, 日期: "${orderDate}", 游戏: "${gameName}"`);

            return {
                '订单ID': orderId,
                '订单日期': orderDate,
                '游戏名称': gameName,
                '说明': description,
                '付款金额': price,
                '商城': marketplace,
                '页面': currentPage
            };
        } catch (error) {
            console.error('提取订单数据时出错:', error);
            return null;
        }
    }

    function checkNextPage() {
        const nextButton = document.querySelector('#next-btn');
        if (nextButton && !nextButton.disabled) {
            nextButton.click();
            console.log('点击了下一页按钮');
            updateProgress(`正在加载第 ${currentPage + 1} 页...`);
            // 等待页面加载后继续提取
            setTimeout(extractCurrentPageData, 2000);
        } else {
            console.log("没有更多页面可以加载。");
            updateProgress(`处理完成!共提取 ${allOrderData.length} 个唯一订单。正在生成Excel文件...`);
            // 等待最后一页数据完全加载
            setTimeout(exportData, 2000);
        }
    }

    function exportData() {
        if (allOrderData.length === 0) {
            alert('没有提取到任何订单数据,请检查页面是否正确加载。');
            resetButton();
            return;
        }

        console.log(`准备导出 ${allOrderData.length} 条唯一订单数据`);

        const workbook = XLSX.utils.book_new();
        const worksheet = XLSX.utils.json_to_sheet(allOrderData);
        XLSX.utils.book_append_sheet(workbook, worksheet, '订单历史');
        XLSX.writeFile(workbook, `EPIC游戏订单历史_${new Date().toISOString().slice(0, 10)}.xlsx`);

        alert(`导出完成!共导出 ${allOrderData.length} 条唯一订单数据。`);
        resetButton();
    }

    function resetButton() {
        const button = document.getElementById('epic-export-btn');
        const progressDiv = document.getElementById('epic-progress');

        if (button) {
            button.textContent = '开始导出';
            button.disabled = false;
        }

        if (progressDiv) {
            progressDiv.style.display = 'none';
        }

        isProcessing = false;
    }

    createExportButton();
})();