显示 Steam 游戏在 SteamPY 的 CDKey 价格、余额购价格和代购价格
// ==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 https://greasyfork.org/zh-CN/scripts/518189-steampy-%E4%BB%B7%E6%A0%BC%E6%98%BE%E7%A4%BA/feedback
// @homepageURL https://greasyfork.org/zh-CN/scripts/518189-steampy-%E4%BB%B7%E6%A0%BC%E6%98%BE%E7%A4%BA
// @namespace https://greasyfork.org/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);
})();