Greasy Fork

Greasy Fork is available in English.

SteamPY 价格显示

显示 Steam 游戏在 SteamPY 的 CDKey 价格、余额购价格和代购价格

当前为 2025-03-16 提交的版本,查看 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         SteamPY 价格显示
// @version      1.2
// @description  显示 Steam 游戏在 SteamPY 的 CDKey 价格、余额购价格和代购价格
// @author       Li
// @match        https://store.steampowered.com/app/*
// @grant        GM_xmlhttpRequest
// @connect      steampy.com
// @connect      steampowered.com
// @run-at       document-end
// @icon         https://steampy.com/m_logo.ico
// @license      MIT
// @supportURL   http://greasyfork.icu/zh-CN/scripts/518189-steampy-%E4%BB%B7%E6%A0%BC%E6%98%BE%E7%A4%BA/feedback
// @homepageURL  http://greasyfork.icu/zh-CN/scripts/518189-steampy-%E4%BB%B7%E6%A0%BC%E6%98%BE%E7%A4%BA
// @namespace http://greasyfork.icu/users/
// ==/UserScript==

(function () {
    'use strict';

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

    const API_ENDPOINTS = {
        gameInfo: (subId, appId, type) => `${BASE_URL}xboot/common/plugIn/getGame?subId=${subId}&appId=${appId}&type=${type}`,
        cdkDetail: (id) => `${BASE_URL}cdkDetail?name=cn&gameId=${id}`,
        balanceBuyDetail: (id) => `${BASE_URL}balanceBuyDetail?data=cn&gameId=${id}`,
        hotGameDetail: (id) => `${BASE_URL}hotGameDetail?gameId=${id}`,
    };

    const getAppId = () => {
        try {
            const link = document.querySelector('.apphub_OtherSiteInfo a');
            const appIdMatch = link?.href?.match(/\d+$/);
            return appIdMatch ? appIdMatch[0] : null;
        } catch (err) {
            console.error("获取 AppID 失败:", err);
            return null;
        }
    };

    const getSubIdElements = () => [...document.querySelectorAll('.game_area_purchase_game_wrapper')];

    const createPlaceholder = (parent) => {
        const placeholder = document.createElement('div');
        placeholder.className = 'price-box';
        placeholder.innerHTML = `<div class="loading-text">加载中...</div>`;
        parent.appendChild(placeholder);
        return placeholder;
    };

    const updatePlaceholder = (placeholder, content, isError = false) => {
        placeholder.innerHTML = isError
            ? `<div class="error-text">${content}</div>`
            : content;
    };

    const displayPrices = (res, placeholder) => {
        if (!res.success) {
            updatePlaceholder(placeholder, `加载失败:${res.message || "API 返回错误"}`, true);
            return;
        }
        const { keyPrice, marketPrice, daiPrice, id } = res.result;
        updatePlaceholder(
            placeholder,
            `
                <a href="${API_ENDPOINTS.cdkDetail(id)}" target="_blank" class="price-link">
                    CDKey 价格:¥${keyPrice || "未知"}
                </a>
                <a href="${API_ENDPOINTS.balanceBuyDetail(id)}" target="_blank" class="price-link">
                    余额购价格:¥${marketPrice || "未知"}
                </a>
                <a href="${API_ENDPOINTS.hotGameDetail(id)}" target="_blank" class="price-link">
                    代购价格:¥${daiPrice || "未知"}
                </a>
            `
        );
    };

    const appId = getAppId();
    if (!appId) {
        console.error("无法获取 AppID,页面结构可能已更改。");
        return;
    }

    const subIdElements = getSubIdElements();
    subIdElements.forEach((element) => {
        try {
            const input = element.querySelector('input[name="subid"], input[name="bundleid"]');
            if (!input) return;

            const subId = input.value;
            const type = input.name;
            const apiUrl = API_ENDPOINTS.gameInfo(subId, appId, type);

            const placeholder = createPlaceholder(element);

            GM_xmlhttpRequest({
                method: "GET",
                url: apiUrl,
                onload: (response) => {
                    try {
                        if (response.status !== 200) {
                            updatePlaceholder(placeholder, `加载失败:HTTP ${response.status} (${response.statusText || "无状态信息"})`, true);
                            return;
                        }
                        const responseText = response.responseText;
                        try {
                            const result = JSON.parse(responseText);
                            displayPrices(result, placeholder);
                        } catch (jsonErr) {
                            console.error("JSON 解析失败:", jsonErr, "响应文本:", responseText.slice(0, 200));
                            updatePlaceholder(placeholder, `加载失败:数据解析错误 (${jsonErr.message})`, true);
                        }
                    } catch (err) {
                        console.error("处理响应失败:", err);
                        updatePlaceholder(placeholder, `加载失败:内部错误 (${err.message})`, true);
                    }
                },
                onerror: (error) => {
                    console.error("请求失败:", error);
                    updatePlaceholder(placeholder, `加载失败:网络请求错误 (${error.status || "无状态信息"})`, true);
                }
            });
        } catch (err) {
            console.error("处理 SubID 失败:", err);
        }
    });

    const style = document.createElement('style');
    style.innerHTML = `
        .price-box {
            font-size: 14px;
            margin-top: 8px;
            padding: 8px;
            background-color: rgba(0, 0, 0, 0.3);
            border-radius: 4px;
            color: #f0f0f0;
            display: flex;
            flex-wrap: wrap;
            gap: 8px;
            box-sizing: border-box;
        }
        .price-link {
            color: #ffffff;
            text-decoration: none;
            font-size: 13px;
            white-space: nowrap;
        }
        .price-link:hover {
            color: #cccccc;
        }
        @media screen and (max-width: 767px) {
            .price-box {
                flex-direction: column;
                gap: 4px;
            }
        }
    `;
    document.head.appendChild(style);
})();