SteamPy PlusPlus

增强购买Steampy密钥的体验,增加筛选功能,添加了好评筛选功能,支持鼠标中键打开Steam页面。大佬没更新,我更新一下获取steam数据的方法而已!

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name            SteamPy PlusPlus
// @name:zh-CN      SteamPy PlusPlus
// @name:en         SteamPy PlusPlus
// @version         6.1
// @description     增强购买Steampy密钥的体验,增加筛选功能,添加了好评筛选功能,支持鼠标中键打开Steam页面。大佬没更新,我更新一下获取steam数据的方法而已!
// @description:en  Enhance the experience of purchasing Steampy keys, add filter functionality, and support opening Steam pages with the middle mouse button.
// @match           https://steampy.com/*
// @grant           GM_setValue
// @grant           GM_getValue
// @grant           GM_xmlhttpRequest
// @grant           GM_registerMenuCommand
// @grant           GM_notification
// @icon            https://steampy.com/logo.ico
// @require         https://scriptcat.org/lib/637/1.4.8/ajaxHooker.js#sha256=dTF50feumqJW36kBpbf6+LguSLAtLr7CEs3oPmyfbiM=
// @require         https://scriptcat.org/lib/513/2.1.0/ElementGetter.js#sha256=aQF7JFfhQ7Hi+weLrBlOsY24Z2ORjaxgZNoni7pAz5U=
// @require         https://scriptcat.org/lib/532/1.0.2/ajax.js#sha384-oDDglpYUiMPlZ/QOkx2727Nl9Pw5b5BEX7IZ/5sEgbiboYYMDfwqHbMAk7X7bo/k
// @require         https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js
// @connect         steampy.com
// @connect         store.steampowered.com
// @run-at          document-start
// @license         MIT
// @namespace https://greasyfork.org/users/683673
// ==/UserScript==

/*global elmGetter,ajaxHooker,$*/

(function () {
    'use strict';

    // add vue updated hook
    function addUpdatedHook(el, callback) {
        const options = el.__vue__.$options;
        if (!options.updated) {
            options.updated = [];
        } else if (!Array.isArray(options.updated)) {
            options.updated = [options.updated];
        }

        options.updated.push(function (...args) {
            callback.apply(this, args);
        });
    }

    const GameManager = {
        saveState(state) {
            GM_setValue('steamGameList', JSON.stringify(state));
        },
        loadState() {
            const saved = JSON.parse(GM_getValue('steamGameList', null));
            if (!saved) {
                return {
                    own: [],
                    wish: [],
                    sub: []
                };
            }
            return saved
        }
    }

    async function syncSteamOwnGameData() {
        try {
            console.log('开始同步Steam数据...');
            
            // 尝试使用GM_xmlhttpRequest直接请求,确保正确传递cookie
            return new Promise((resolve, reject) => {
                GM_xmlhttpRequest({
                    method: 'GET',
                    url: 'https://store.steampowered.com/dynamicstore/userdata/',
                    responseType: 'json',
                    headers: {
                        'Content-Type': 'application/json',
                        'Accept': 'application/json, text/javascript, */*; q=0.01'
                    },
                    onload: function(response) {
                        console.log('Steam API响应状态:', response.status);
                        console.log('Steam API响应头:', response.responseHeaders);
                        
                        try {
                            const data = JSON.parse(response.responseText);
                            console.log('Steam API返回数据:', data);
                            
                            if (data) {
                                // 检查数据结构是否正确
                                var own = data.rgOwnedApps || [];
                                var wish = data.rgWishlist || [];
                                var sub = data.rgOwnedPackages || [];
                                
                                // 确保数据是数组格式
                                if (!Array.isArray(own)) own = [];
                                if (!Array.isArray(wish)) wish = [];
                                if (!Array.isArray(sub)) sub = [];
                                
                                console.log('处理后的数据:', { own: own.length, wish: wish.length, sub: sub.length });
                                
                                if (own.length + wish.length + sub.length > 0) {
                                    GameManager.saveState({
                                        own: own,
                                        wish: wish,
                                        sub: sub
                                    });

                                    GM_notification({
                                        title: "SteamPy Plus",
                                        text: "同步Steam数据成功",
                                        timeout: 3000
                                    });
                                    resolve(true);
                                } else {
                                    console.log('未获取到Steam数据,可能需要重新登录Steam');
                                    GM_notification({
                                        title: "SteamPy Plus",
                                        text: "未获取到Steam数据,请确保已登录Steam",
                                        timeout: 3000
                                    });
                                    resolve(false);
                                }
                            } else {
                                console.error('获取Steam数据失败:返回数据为空');
                                GM_notification({
                                    title: "SteamPy Plus",
                                    text: "获取Steam数据失败,请检查网络连接",
                                    timeout: 3000
                                });
                                resolve(false);
                            }
                        } catch (parseError) {
                            console.error('解析Steam数据时发生错误:', parseError);
                            console.error('原始响应:', response.responseText);
                            GM_notification({
                                title: "SteamPy Plus",
                                text: "解析Steam数据时发生错误,请检查控制台",
                                timeout: 3000
                            });
                            resolve(false);
                        }
                    },
                    onerror: function(error) {
                        console.error('请求Steam API时发生错误:', error);
                        GM_notification({
                            title: "SteamPy Plus",
                            text: "请求Steam API时发生错误,请检查网络连接",
                            timeout: 3000
                        });
                        resolve(false);
                    },
                    ontimeout: function() {
                        console.error('请求Steam API超时');
                        GM_notification({
                            title: "SteamPy Plus",
                            text: "请求Steam API超时,请检查网络连接",
                            timeout: 3000
                        });
                        resolve(false);
                    }
                });
            });
        } catch (error) {
            console.error('同步Steam数据时发生错误:', error);
            GM_notification({
                title: "SteamPy Plus",
                text: "同步Steam数据时发生错误,请检查控制台",
                timeout: 3000
            });
            return false;
        }
    }

    GM_registerMenuCommand('同步Steam数据', syncSteamOwnGameData);

    const steamGameList = GameManager.loadState();

    // 状态管理
    const StateManager = {
        saveState(state) {
            GM_setValue('steamPriceFilterState', JSON.stringify(state));
        },
        loadState() {
            const saved = GM_getValue('steamPriceFilterState', null);
            return saved ? JSON.parse(saved) : {
                minPrice: 0,
                maxPrice: 9999,
                minRating: 0,
                isActive: false,
                ratingFilterActive: false
            };
        }
    };

    let filterState = StateManager.loadState();

    function getAccessToken() {
        return window.localStorage.getItem('accessToken');
    }

    const listSaleUrl = "xboot/steamKeySale/listSale"

    const steampyUrl = "https://steampy.com/";

    function requestApi(url, method, data) {

        return ajax(url, {
            method: method,
            data: data,
            responseType: 'json',
            headers: {
                Accesstoken: getAccessToken()
            },
            _nocatch: true
        })
    }

    const cacheKey = `${listSaleUrl}_listSaleCache`;
    function getSaleList(gameId) {
        const cached = GM_getValue(cacheKey, {});
        const cachedData = cached[gameId];
        if (cachedData && cachedData.expireTime > Date.now()) {
            return Promise.resolve(cachedData.data);
        }
        return requestApi(`${steampyUrl}${listSaleUrl}`, 'GET', { gameId: gameId, pageNumber: 1, pageSize: 20, sort: "keyPrice", order: "asc", startDate: '', endDate: '' })
            .then(data => {
                const expireTime = Date.now() + 12 * 60 * 60 * 1000; // 12h
                const newCache = Object.assign({}, cached, { [gameId]: { data, expireTime } });
                GM_setValue(cacheKey, newCache);
                return data;
            });
    }


    function getSteamAppId(gameBlock) {
        // 尝试从多个可能的位置获取游戏ID
        
        // 1. 首先尝试从图片URL中提取(原有方法)
        const iconImg = gameBlock.querySelector('.cdkGameIcon') || gameBlock.querySelector('.gameIcon');
        if (iconImg) {
            let imgUrl = iconImg.dataset.src || iconImg.src;
            // 移除可能存在的反引号和前后空格
            imgUrl = imgUrl.replace(/[`']/g, '').trim();
            // 从图片地址中匹配游戏ID
            // 兼容多种格式: 
            // steam/apps/1651560/header
            // shared.akamai.steamstatic.com/store_item_assets/steam/apps/3357650/e32e168b25ed68a0cf6264c220c07e96c2abfb56/header.jpg
            // media.st.dl.eccdnx.com/steam/bundles/32470/5htp7gjbzhjufq5c/header_586x192_schinese.jpg
            let match = imgUrl.match(/steam\/apps\/(\d+)/);
            if (match) {
                return parseInt(match[1]);
            }
            
            // 尝试从bundle URL中提取ID
            match = imgUrl.match(/steam\/bundles\/(\d+)/);
            if (match) {
                return parseInt(match[1]);
            }
        }
        
        // 2. 尝试从游戏块的其他属性中获取
        const gameIdAttr = gameBlock.dataset.gameId || gameBlock.getAttribute('data-game-id');
        if (gameIdAttr) {
            return parseInt(gameIdAttr);
        }
        
        // 3. 尝试从链接中提取
        const gameLink = gameBlock.querySelector('a[href*="steam"]');
        if (gameLink) {
            const linkMatch = gameLink.href.match(/steam\/apps\/(\d+)/);
            if (linkMatch) {
                return parseInt(linkMatch[1]);
            }
        }
        
        // 4. 尝试从游戏名称或其他元素中提取
        const gameName = gameBlock.querySelector('.gameName');
        if (gameName) {
            // 这里可以添加更多的提取方法,根据Steam网站的新结构
        }
        
        console.error('[getSteamAppId] 无法从游戏块中提取Steam App ID:', gameBlock);
        return null;
    }

    // 游戏数据存储
    const TempDataStore = {
        steamGameData: null,
        saleGameData: null, // /xboot/steamGame/getOne?
        setGameData(data) {
            this.steamGameData = data;
        },
        getGameData() {
            return this.steamGameData || {
                result: {
                    content: []
                }
            };
        },
        getRatingByAppId(appId) {
            const gameList = this.getGameData().result.content;
            const appIdNum = Number(appId);
            const targetGame = gameList.find(game => Number(game.appId) === appIdNum);
            return targetGame && targetGame.rating ? targetGame.rating : 0;
        },
        getRatingCountByAppId(appId) {
            const gameList = this.getGameData().result.content;
            const appIdNum = Number(appId);
            const targetGame = gameList.find(game => Number(game.appId) === appIdNum);
            return targetGame && targetGame.ratingCount ? targetGame.ratingCount : 0;
        },
    };

    // 接口拦截
    ajaxHooker.hook(request => {
        // 处理原有接口
        if (request.url.includes('/xboot/steamGame/keyHot')) {
            request.response = (res) => {
                try {
                    const originalData = JSON.parse(res.responseText);
                    TempDataStore.setGameData(originalData);
                    res.responseText = JSON.stringify(originalData);
                } catch (e) {
                    console.error('keyHot接口数据处理失败:', e);
                }
            };
        }
        else if (request.url.includes('/xboot/steamGame/getOne')) {
            request.response = (res) => {
                try {
                    const originalData = JSON.parse(res.responseText);
                    if (originalData.code === 200 && originalData.success === true) {
                        const data = originalData.result;
                        const showArea = $(".market-content > .market-detail > div:nth-child(3)")
                        showArea.append(`<div data-v-3911ff65="" class="ht100 mt-50" style="flex-wrap: wrap;"><span class="f20-rem mt-20-rem ml-20-rem">历史销售数量 ${data.keyTx}</span></div> `)
                    } else {
                        console.log('getOne接口数据处理失败:', originalData);
                    }

                } catch (e) {
                    console.error('getOne接口数据处理失败:', e);
                }
            };
        }
        return request;
    });

    // 单个游戏评分更新(使用Steam风格文本描述)
    function updateGameRating(gameBlock) {
        if (!gameBlock) return;

        const appId = getSteamAppId(gameBlock);
        if (!appId) {
            console.error('[updateGameRating] 找不到 appid', gameBlock);
            return;
        }
        
        // 处理愿望单标记
        if (steamGameList.wish.includes(appId)) {
            const gameName = gameBlock.querySelector('.gameName');
            if (gameName) {
                gameName.classList.add('bg-blue');
            }
        }
        
        // 尝试找到游戏头部元素
        let gameHead = gameBlock.querySelector('.gameHead');
        // 如果找不到.gameHead,尝试其他可能的容器
        if (!gameHead) {
            gameHead = gameBlock.querySelector('.game-header') || 
                      gameBlock.querySelector('.game-info') || 
                      gameBlock;
        }

        // 只有存在有效ID和容器时才处理评分
        if (appId && gameHead) {
            const rating = TempDataStore.getRatingByAppId(appId);
            let ratingEl = gameHead.querySelector('.gameRating');

            // 有评分数据
            if (rating > 0) {
                // 计算百分比并映射到Steam评分等级
                const ratingPercent = Math.round(rating * 100);
                let ratingText, ratingClass;
                const ratingCount = TempDataStore.getRatingCountByAppId(appId);

                // Steam风格评分标准
                if (ratingPercent >= 90) {
                    ratingText = "好评如潮";
                    ratingClass = "overwhelmingly-positive";
                } else if (ratingPercent >= 80) {
                    ratingText = "特别好评";
                    ratingClass = "very-positive";
                } else if (ratingPercent >= 70) {
                    ratingText = "多半好评";
                    ratingClass = "positive";
                } else if (ratingPercent >= 40) {
                    ratingText = "褒贬不一";
                    ratingClass = "mixed";
                } else if (ratingPercent >= 20) {
                    ratingText = "多半差评";
                    ratingClass = "negative";
                } else {
                    ratingText = "特别差评";
                    ratingClass = "very-negative";
                }

                // 添加评价数量
                const fullRatingText = ratingCount > 0 ? `${ratingText} (${ratingCount})` : ratingText;

                if (ratingEl) {
                    // 只在内容变化时更新
                    if (ratingEl.textContent !== fullRatingText) {
                        ratingEl.textContent = fullRatingText;
                    }

                    // 更新评分等级类名
                    if (!ratingEl.classList.contains(ratingClass)) {
                        ratingEl.classList.remove(
                            'overwhelmingly-positive',
                            'very-positive',
                            'positive',
                            'mixed',
                            'negative',
                            'very-negative'
                        );
                        ratingEl.classList.add(ratingClass);
                    }
                } else {
                    // 创建新评分标签
                    const newRatingEl = document.createElement('div');
                    newRatingEl.className = `gameRating ${ratingClass}`;
                    newRatingEl.textContent = fullRatingText;
                    gameHead.appendChild(newRatingEl);
                }
            }
            // 无评分数据则移除标签
            else {
                console.error('[updateGameRating] 没有评分数据 appid:', appId);
                if (ratingEl) {
                    ratingEl.remove();
                }
            }
        }
    }

    // 同步更新评分样式
    function injectRatingStyle() {
        const existingStyle = document.getElementById('ratingStyle');
        if (existingStyle) {
            existingStyle.remove();
        }

        const style = document.createElement('style');
        style.id = 'ratingStyle';
        style.textContent = `
        .gameHead .gameRating {
            padding: 0 8px !important;
            height: .3rem !important;
            position: absolute !important;
            top: 0 !important;
            left: 0 !important;
            color: #fff !important;
            text-align: center !important;
            line-height: .3rem !important;
            border-radius: .09rem 0 0 0 !important;
            font-size: .12rem !important;
            font-weight: bold !important;
            z-index: 10 !important;
            white-space: nowrap !important;
        }
        /* Steam风格评分颜色 */
        .gameRating.overwhelmingly-positive { background: #4CAF50 !important; } /* 好评如潮 - 深绿 */
        .gameRating.very-positive { background: #8BC34A !important; } /* 特别好评 - 中绿 */
        .gameRating.positive { background: #CDDC39 !important; color: #333 !important; } /* 多半好评 - 浅绿 */
        .gameRating.mixed { background: #FFC107 !important; color: #333 !important; } /* 褒贬不一 - 黄色 */
        .gameRating.negative { background: #FF9800 !important; } /* 多半差评 - 橙色 */
        .gameRating.very-negative { background: #F44336 !important; } /* 特别差评 - 红色 */
    `;
        document.head.appendChild(style);
    }


    // 筛选UI
    function createFilterUI() {
        const ui = document.createElement('div');
        ui.id = 'priceFilterContainer';
        ui.className = 'ml-5-rem flex-row align-items-center'; // 复用网站现有类
        ui.style.cssText = `
            font-family:Arial,sans-serif;
            font-size:.13rem; /* 13px → 0.13rem */
            gap:.08rem; /* 8px → 0.08rem */
            padding:.08rem; /* 8px → 0.08rem */
            border-radius:.04rem; /* 4px → 0.04rem,与网站按钮一致 */
            height:.25rem; /* 与网站.tagBtn系列高度一致 */
            box-sizing:border-box; /* 确保padding不影响高度 */
        `;

        // 标题(复用网站标签样式)
        const title = document.createElement('span');
        title.className = 'tag-titleOne ml-3-rem'; // 复用网站标题类
        title.textContent = '价格筛选';
        title.style.fontWeight = 'bold';
        ui.appendChild(title);

        // 预设按钮容器
        const presetContainer = document.createElement('div');
        presetContainer.className = 'flex-row jc-space-flex-start align-items-center pr5-rem'; // 复用网站布局类
        presetContainer.style.gap = '.08rem'; // 8px → 0.08rem

        // 预设按钮配置
        const presets = [{
            text: '0-20元',
            min: 0,
            max: 20
        }
        ];

        presets.forEach(p => {
            const btn = document.createElement('div');
            btn.className = 'tagBtn'; // 复用网站按钮类
            btn.dataset.min = p.min;
            btn.dataset.max = p.max;
            btn.textContent = p.text;

            // 基础样式(与网站.tagBtn保持一致)
            const baseStyle = `
                padding:.04rem .1rem; /* 4px 10px → 0.04rem 0.1rem */
                border-radius:.04rem; /* 与网站一致 */
                cursor:pointer;
                font-size:.13rem; /* 13px → 0.13rem */
                border:1px solid #ddd;
                color:#666;
                background:transparent;
                transition:all 0.2s;
                box-sizing:border-box;
                height:.25rem; /* 与网站按钮高度一致 */
                line-height:.17rem; /* 高度 - 2*padding = 0.25-0.08=0.17rem */
            `;

            btn.style.cssText = baseStyle;

            // 激活状态样式(匹配网站高亮风格)
            if (filterState.isActive && filterState.minPrice === p.min && filterState.maxPrice === p.max) {
                btn.style.cssText = `
                    ${baseStyle}
                    border:1px solid #409EFF;
                    color:#fff;
                    background:#409EFF;
                `;
            }

            btn.onclick = () => {
                filterState.minPrice = p.min;
                filterState.maxPrice = p.max;
                filterState.isActive = true;
                StateManager.saveState(filterState);
                syncInputValues();
                applyFilterGameList();
                updatePresetHighlights();
            };
            presetContainer.appendChild(btn);
        });
        ui.appendChild(presetContainer);

        // 输入框容器
        const inputContainer = document.createElement('div');
        inputContainer.className = 'flex-row align-items-center'; // 复用网站布局类
        inputContainer.style.gap = '.08rem'; // 8px → 0.08rem

        // 最低价格输入框
        const minInp = document.createElement('input');
        minInp.id = 'priceFilterMin';
        minInp.type = 'number';
        minInp.placeholder = '最低价';
        minInp.min = 0;
        minInp.style.cssText = `
            width:.7rem; /* 70px → 0.7rem */
            height:.28rem; /* 28px → 0.28rem */
            padding:0 .08rem; /* 0 8px → 0 0.08rem */
            border:1px solid #ccc;
            border-radius:.04rem; /* 与网站一致 */
            box-sizing:border-box;
            font-size:.13rem; /* 13px → 0.13rem */
            line-height:.12rem; /* 确保文本垂直居中 */
        `;
        minInp.addEventListener('input', (e) => {
            filterState.minPrice = parseFloat(e.target.value) || 0;
            filterState.isActive = true;
            StateManager.saveState(filterState);
        });

        // 分隔符
        const separator = document.createTextNode('-');
        separator.nodeValue = '-';

        // 最高价格输入框
        const maxInp = document.createElement('input');
        maxInp.id = 'priceFilterMax';
        maxInp.type = 'number';
        maxInp.placeholder = '最高价';
        maxInp.min = 0;
        maxInp.style.cssText = minInp.style.cssText; // 与最低价输入框样式一致
        maxInp.addEventListener('input', (e) => {
            filterState.maxPrice = parseFloat(e.target.value) || 9999;
            filterState.isActive = true;
            StateManager.saveState(filterState);
        });

        // 筛选按钮(匹配网站按钮风格)
        const filterBtn = document.createElement('button');
        filterBtn.className = 'ivu-btn ivu-btn-default ivu-btn-sm'; // 复用网站IVUE按钮类
        filterBtn.textContent = '筛选';
        filterBtn.style.cssText = `
            margin-left:.04rem; /* 4px → 0.04rem */
            padding:.04rem .12rem; /* 4px 12px → 0.04rem 0.12rem */
            cursor:pointer;
            background:#409EFF;
            color:white;
            border:1px solid #409EFF;
            border-radius:.04rem; /* 与网站一致 */
            font-size:.13rem; /* 13px → 0.13rem */
            height:.28rem; /* 与输入框高度一致 */
            line-height:.2rem; /* 高度 - 2*padding = 0.28-0.08=0.2rem */
            box-sizing:border-box;
            border:none; /* 清除IVUE默认边框 */
        `;
        filterBtn.onclick = () => {
            applyFilterGameList();
            updatePresetHighlights(false);
        };

        inputContainer.append(minInp, separator, maxInp, filterBtn);
        ui.appendChild(inputContainer);

        // 好评筛选UI
        const ratingFilterContainer = document.createElement('div');
        ratingFilterContainer.className = 'ml-5-rem flex-row align-items-center';
        ratingFilterContainer.style.cssText = `
            font-family:Arial,sans-serif;
            font-size:.13rem; /* 13px → 0.13rem */
            gap:.08rem; /* 8px → 0.08rem */
            padding:.08rem; /* 8px → 0.08rem */
            border-radius:.04rem; /* 4px → 0.04rem,与网站按钮一致 */
            height:.25rem; /* 与网站.tagBtn系列高度一致 */
            box-sizing:border-box; /* 确保padding不影响高度 */
        `;

        // 好评筛选标题
        const ratingTitle = document.createElement('span');
        ratingTitle.className = 'tag-titleOne ml-3-rem';
        ratingTitle.textContent = '好评筛选';
        ratingTitle.style.fontWeight = 'bold';
        ratingFilterContainer.appendChild(ratingTitle);

        // 好评筛选预设按钮
        const ratingPresets = [
            { text: '全部', min: 0 },
            { text: '特好以上', min: 0.8 },
            { text: '好评如潮', min: 0.9 }
        ];

        ratingPresets.forEach(p => {
            const btn = document.createElement('div');
            btn.className = 'tagBtn';
            btn.dataset.min = p.min;
            btn.textContent = p.text;

            // 基础样式
            const baseStyle = `
                padding:.04rem .1rem; /* 4px 10px → 0.04rem 0.1rem */
                border-radius:.04rem; /* 与网站一致 */
                cursor:pointer;
                font-size:.13rem; /* 13px → 0.13rem */
                border:1px solid #ddd;
                color:#666;
                background:transparent;
                transition:all 0.2s;
                box-sizing:border-box;
                height:.25rem; /* 与网站按钮高度一致 */
                line-height:.17rem; /* 高度 - 2*padding = 0.25-0.08=0.17rem */
            `;

            btn.style.cssText = baseStyle;

            // 激活状态样式
            if (filterState.ratingFilterActive && filterState.minRating === p.min) {
                btn.style.cssText = `
                    ${baseStyle}
                    border:1px solid #409EFF;
                    color:#fff;
                    background:#409EFF;
                `;
            }

            btn.onclick = () => {
                filterState.minRating = p.min;
                filterState.ratingFilterActive = p.min > 0;
                StateManager.saveState(filterState);
                applyFilterGameList();
                updateRatingPresetHighlights();
            };
            ratingFilterContainer.appendChild(btn);
        });

        ui.appendChild(ratingFilterContainer);

        return ui;
    }

    function insertFilterUI() {
        if (document.getElementById('priceFilterContainer')) return;
        const ui = createFilterUI();
        const targetContainer = document.querySelector('.tag.flex-row.align-items-center');
        if (targetContainer) {
            targetContainer.appendChild(ui);
            syncInputValues();
        }
        if (filterState.isActive) {
            applyFilterGameList();
        }
    }

    // 更新预设按钮高亮状态
    function updatePresetHighlights(shouldHighlight = true) {
        const baseStyle = `
            padding:.04rem .1rem;
            border-radius:.04rem;
            cursor:pointer;
            font-size:.13rem;
            border:1px solid #ddd;
            color:#666;
            background:transparent;
            transition:all 0.2s;
            box-sizing:border-box;
            height:.25rem;
            line-height:.17rem;
        `;

        document.querySelectorAll('.tagBtn[data-min]').forEach(btn => {
            const btnMin = parseFloat(btn.dataset.min);
            const btnMax = parseFloat(btn.dataset.max);
            const isMatch = filterState.isActive && filterState.minPrice === btnMin && filterState.maxPrice === btnMax;

            btn.style.cssText = shouldHighlight && isMatch ?
                `
                    ${baseStyle}
                    border:1px solid #409EFF;
                    color:#fff;
                    background:#409EFF;
                ` :
                baseStyle;
        });
    }

    // 更新好评筛选预设按钮高亮状态
    function updateRatingPresetHighlights() {
        const baseStyle = `
            padding:.04rem .1rem;
            border-radius:.04rem;
            cursor:pointer;
            font-size:.13rem;
            border:1px solid #ddd;
            color:#666;
            background:transparent;
            transition:all 0.2s;
            box-sizing:border-box;
            height:.25rem;
            line-height:.17rem;
        `;

        document.querySelectorAll('.tagBtn[data-min]').forEach(btn => {
            if (!btn.dataset.max) { // 好评筛选按钮没有max属性
                const btnMin = parseFloat(btn.dataset.min);
                const isMatch = filterState.ratingFilterActive && filterState.minRating === btnMin;

                btn.style.cssText = isMatch ?
                    `
                        ${baseStyle}
                        border:1px solid #409EFF;
                        color:#fff;
                        background:#409EFF;
                    ` :
                    baseStyle;
            }
        });
    }

    // 价格筛选核心逻辑
    function syncInputValues() {
        const minInp = document.getElementById('priceFilterMin');
        const maxInp = document.getElementById('priceFilterMax');
        if (minInp && filterState.isActive) minInp.value = filterState.minPrice;
        if (maxInp && filterState.isActive) maxInp.value = filterState.maxPrice;
    }


    function processGameOpen(gameBlock) {
        if (gameBlock.dataset.filterProcessed) return;
        gameBlock.dataset.filterProcessed = 'true';

        gameBlock.addEventListener('mousedown', e => {
            if (e.button === 1 && !e.ctrlKey && !e.shiftKey) {
                const appId = getSteamAppId(gameBlock);
                if (appId) {
                    e.preventDefault();
                    window.open(`https://store.steampowered.com/app/${appId}/`, '_blank');
                }
            }
        });
    }


    function $dom(selector) {
        return $(selector).get(0);
    }

    function $vue(selector) {
        return $dom(selector).__vue__;
    }


    function applyFilterGameList() {
        const vueElement = $dom(".game_layout .game_layout");
        if (!vueElement || !vueElement.__vue__) {
            console.log('Vue实例未找到,无法应用筛选');
            return;
        }
        const vueData = vueElement.__vue__;
        if (!vueData.gameList) {
            console.log('游戏列表未找到,无法应用筛选');
            return;
        }
        const gameList = vueData.gameList

        const filterGameList = gameList.filter(game => {
            // 确保price是数字类型
            const price = parseFloat(game.keyPrice) || 0;

            // 价格筛选
            const priceShouldShow = !filterState.isActive ||
                (price >= filterState.minPrice && price <= filterState.maxPrice);
            
            // 好评筛选
            const appId = game.appId;
            const appIdInt = parseInt(appId);
            const rating = TempDataStore.getRatingByAppId(appIdInt);
            const ratingShouldShow = !filterState.ratingFilterActive || rating >= filterState.minRating;
            
            // 已拥有游戏筛选
            if (steamGameList && steamGameList.own && steamGameList.own.includes(appIdInt)) {
                return false
            }
            
            return priceShouldShow && ratingShouldShow
        })

        if (filterGameList.length !== gameList.length) {
            vueData.gameList = filterGameList
        }
    }

    async function startContentMonitor() {
        await elmGetter.get('div.ivu-tabs-content  div.flex-row.jc-space-flex-start.flex-wrap.w-auto');

        addUpdatedHook($dom(".ivu-tabs-tabpane"), function () {
            applyFilterGameList();

            const gameBlocks = this.$el.querySelectorAll('.gameblock');
            // 遍历每个.gameblock执行处理
            gameBlocks.forEach(gameBlock => {
                // 应用筛选和评分更新
                updateGameRating(gameBlock);
                // 处理游戏块核心逻辑
                processGameOpen(gameBlock);
            });
        })

        // 初始加载后执行一次
        setTimeout(() => {
            applyFilterGameList();
            // 初始加载时对所有可见游戏更新评分
            document.querySelectorAll('.gameblock')
                .forEach(gameBlock => updateGameRating(gameBlock));
        }, 600);
    }


    async function startSellListListener() {
        d("startSellListListener");

        const el = await elmGetter.get("#main > div.main > div.single-page-con > div.single-page > div:has(.cdkTrade-layout)")
        d("found sell list div", el)
        const vm = el[0].__vue__
        vm.$watch('sellList', function (newVal) {
            console.debug('sellList 已更新');
            if (newVal === undefined && vm.sellList === undefined) {
                d('newVal === undefined && vm.sellList === undefined')
                return
            }
            this.$nextTick(async () => {
                console.log(await elmGetter.get('.orderOne.bg-white .list-item'));
                $('.orderOne.bg-white .list-item').each(async function (index, item) {
                    const minPriceElement = item.querySelector('div:nth-child(7)');

                    const data = vm.sellList[index];
                    const selfPrice = data.keyPrice;
                    minPriceElement.innerText = `${selfPrice}`;
                    minPriceElement.classList.remove('color-red');
                    if (data.stock === 0) return

                    const gameId = data.gameId;
                    const sellerData = await getSaleList(gameId);
                    const combinedData = {
                        gameName: data.steamGame.gameName,
                        sellerData: sellerData,
                        keyData: data
                    };
                    d(combinedData);

                    if (sellerData.code !== 200) {
                        console.error(sellerData.msg);
                        return
                    }

                    const sellerList = sellerData.result && sellerData.result.content ? sellerData.result.content : [];
                    let order = 1
                    let minPrice = sellerList[0].keyPrice

                    if (minPrice >= selfPrice) {
                        d(`minPrice >= selfPrice ${minPrice} >= ${selfPrice}`)

                        return
                    }
                    for (const seller of sellerList) {
                        if (seller.saleId === data.sellerId) {
                            break
                        }
                        if (seller.keyPrice < selfPrice) {
                            order += seller.stock
                        }
                    }
                    d("order", order)
                    if (order !== 1) {

                        if (minPriceElement) {
                            minPriceElement.classList.add('color-red');
                            const rawText = minPriceElement.innerText;

                            minPriceElement.innerText = `${selfPrice} 最低价${minPrice}`;
                            minPriceElement.setAttribute('data-rawtext', rawText);

                        };
                    } else {

                        d("already min price")
                    }
                })

            })
        }, { immediate: true })
    }

    async function startModalListener() {
        const vm = $dom("#main > div.main > div.single-page-con > div > div").__vue__
        const originalGoToChoose = vm.goToChoose;

        vm.goToChoose = function (e) {
            originalGoToChoose.call(this, e);
            this.$nextTick(() => {
                console.debug('DOM 已更新,当前选择的游戏ID:', this.gameId);
                const $modal = $('.ivu-modal').filter(':visible');
                addHisPriceToModal($modal, this.modalGamList[e], this); // 此时 modal 已完成渲染
            });
        };

        console.log('seller 弹窗监听器已启动');
    }

    function addHisPriceToModal(modal, gameData, vm) {
        console.debug(gameData);

        // 1. 从传入的modal中获取当前最低价格标签(使用jQuery选择器)
        const $currentMinPriceLabel = modal.find('.mt-15.f15.fw500 .color-red.f12-rem');
        if (!$currentMinPriceLabel.length) {
            console.log('弹窗内未找到当前最低价格标签');
            return;
        }
        // 3. 从已就绪的数据中获取历史最低价
        const hisPrice = gameData.hisPrice
        if (hisPrice === null) {
            console.log(`【${currentGameName}】无历史价格数据`);
            return;
        }

        // 4. 防重复:检查当前弹窗内是否已添加过历史价格标签(仅在当前modal内查找)
        if (modal.find('.his-price-tag').length) {
            return;
        }

        // 5. 创建并插入历史价格标签(样式优化:与原价格区分)
        const hisPriceSpan = document.createElement('span');
        hisPriceSpan.className = 'his-price-tag color-blue f12-rem ml-10'; // 蓝色+左间距
        hisPriceSpan.textContent = ` 历史最低价格: ¥${hisPrice.toFixed(2)}`; // 保留2位小数,格式统一

        $currentMinPriceLabel.after(hisPriceSpan);

        // 价格是 xx.x 格式(1位小数),放大10倍转为整数(如 12.3 → 123)
        const keyPriceInt = Math.round(Number(gameData.keyPrice) * 10);
        // 减去 0.1(即 1/10,对应整数减 1)
        const cdkPriceInt = keyPriceInt - 1;
        // 转回一位小数
        vm.cdkPrice = cdkPriceInt / 10;
    }

    async function addQuantitySort() {
        try {
            // 1. 等待并获取父容器(用jQuery选择器,兼容动态加载元素)
            // 若elmGetter是自定义元素等待工具,可保留await;若无需等待,直接用 $(selector) 即可
            const $parent = await elmGetter.get('.flex-row > .c-point.flex-row.align-items-center');

            if (!$parent.length) {
                console.warn('未找到按钮父容器,无法添加"数量"排序按钮');
                return;
            }

            // 2. 查找所有目标按钮(jQuery find 简化选择器)
            const $targetBtns = $parent.find('.ml-5-rem.c-point.tagBtn');
            if (!$targetBtns.length) {
                console.warn('未找到目标按钮(.ml-5-rem.c-point.tagBtn),无法添加"数量"排序按钮');
                return;
            }

            // 3. 关键:从现有目标按钮提取Vue scoped CSS的data-v属性(支持多个data-v属性)
            const $sampleBtn = $targetBtns.first(); // 取任意一个现有按钮当"样本"
            const vueDataAttrs = {}; // 存储提取的data-v属性(键:data-v-xxx,值:属性值)
            // 遍历样本按钮的所有属性,筛选出data-v-开头的属性
            $.each($sampleBtn[0].attributes, (i, attr) => {
                if (attr.name.startsWith('data-v-')) {
                    vueDataAttrs[attr.name] = attr.value; // 保存属性名和值(如data-v-e7c25b08: "")
                }
            });

            // 3. 取最后一个目标按钮(jQuery last() 直接获取,无需计算索引)
            const $lastTargetBtn = $targetBtns.last();

            // 4. jQuery链式创建按钮(避免多次createElement,语法更简洁)
            const $quantityBtn = $('<div>')
                .addClass('ml-5-rem c-point tagBtn') // 批量添加类名
                .attr(vueDataAttrs)
                .append(
                    $('<span>')
                        .addClass('tag-title')
                        .text('数量').attr(vueDataAttrs) // 设置文本
                );

            const form = await elmGetter.get("#main > div.main > div.single-page-con > div > div")
            const formVue = form[0].__vue__

            const handleSortClick = function () {
                // 找到**所有排序按钮**(包括原有和新添加的"数量"按钮)
                const $allSortBtns = $parent.find('.ml-5-rem.c-point.tagBtn');
                // 移除所有按钮的active状态
                $allSortBtns.removeClass('active');
                // 给当前点击的按钮添加active
                $(this).addClass('active');
                formVue.sellForm.sort = "stock"
                formVue.sellForm.pageNumber = 1
                formVue.getSellData()
            };

            // 6. 给新按钮绑定通用点击事件
            $quantityBtn.on('click', handleSortClick);
            $targetBtns.on('click', handleSortClick);

            // 6. 插入新按钮(jQuery after() 直接插入,支持jQuery对象)
            $lastTargetBtn.after($quantityBtn);

            console.log('成功添加"数量"排序按钮');
        } catch (error) {
            console.error('添加"数量"排序按钮失败:', error);
        }
    }

    // 路径处理
    const TARGET_PATH = '/cdKey/cdKey';
    const SELLER_CDKEY_PATH = '/pyUserInfo/sellerCDKey'; // 新增路径
    const CdkDeatil_PATH = '/cdkDetail';
    let isInitialized = false;
    let isSellerInitialized = false; // 新增状态标识

    function isTargetPath() {
        return window.location.pathname.startsWith(TARGET_PATH);
    }

    // 新增:检查是否为卖家CDKey路径
    function isSellerCDKeyPath() {
        return window.location.pathname.startsWith(SELLER_CDKEY_PATH);
    }

    function cleanUp() {
        if (!isInitialized) return;
        isInitialized = false;
    }

    function sellerCleanUp() {
        if (!isSellerInitialized) return;
        isSellerInitialized = false;
        d('sellerCleanUp')
    }

    function handlePathChange() {
        // 处理原有路径逻辑
        if (isTargetPath() && !isInitialized) {
            console.log("run script in cdKey path");
            buyInit();
        } else if (!isTargetPath() && isInitialized) {
            cleanUp();
        }

        // 新增:处理卖家CDKey路径逻辑
        if (isSellerCDKeyPath() && !isSellerInitialized) {
            console.log("run script in sellerCDKey path");
            sellerInit(); // 调用新增的初始化函数
        } else if (!isSellerCDKeyPath() && isSellerInitialized) {
            sellerCleanUp();
        }
        // https://steampy.com/cdkDetail?name=cn&gameId=815763235650146304
        if (window.location.pathname.startsWith(CdkDeatil_PATH)) {
            console.log("run script in cdkDetail path");
            buyKeyInit();
        }
    }

    async function buyKeyInit() {

    }

    async function buyInit() {
        if (isInitialized) return;
        injectRatingStyle();
        await elmGetter.get('.tag.flex-row.align-items-center')
        insertFilterUI()
        await startContentMonitor();
        isInitialized = true;
    }

    function d(...args) {
        console.debug(...args);
    }

    async function sellerInit() {
        if (isSellerInitialized) { d("seller already initialized"); return };
        await elmGetter.get("div.main > div.single-page-con > div > div", $dom("#main"))
        d('sellerInit')
        startModalListener()
        addQuantitySort()
        startSellListListener()
        isSellerInitialized = true;
    }

    // 监听历史变化
    let lastPath = location.pathname + location.search;
    const originalPushState = history.pushState;
    const originalReplaceState = history.replaceState;

    history.pushState = function (...args) {
        originalPushState.apply(this, args);
        const newPath = location.pathname + location.search;
        if (newPath !== lastPath) {
            lastPath = newPath;
            handlePathChange();
        }
    };

    history.replaceState = function (...args) {
        originalReplaceState.apply(this, args);
        const newPath = location.pathname + location.search;
        if (newPath !== lastPath) {
            lastPath = newPath;
            handlePathChange();
        }
    };

    window.addEventListener('popstate', handlePathChange);
    window.addEventListener('hashchange', handlePathChange);

    elmGetter.selector($)
    console.log(elmGetter.selector($));

    handlePathChange();
})();