// ==UserScript==
// @name Gamer520网站游戏Steam好评率显示器
// @namespace http://tampermonkey.net/
// @version 1.5
// @description 在 gamer520.com 游戏列表和详情页显示 Steam 好评率,替换"免费"标签,仅处理PC PLAY游戏
// @author icescat
// @match https://www.gamer520.com/pcplay*
// @match https://www.gamer520.com/*.html
// @grant GM_xmlhttpRequest
// @grant GM_setValue
// @grant GM_getValue
// @connect store.steampowered.com
// @run-at document-end
// @license MIT
// @downloadURL https://update.greasyfork.icu/scripts/574489/Gamer520%E7%BD%91%E7%AB%99%E6%B8%B8%E6%88%8FSteam%E5%A5%BD%E8%AF%84%E7%8E%87%E6%98%BE%E7%A4%BA%E5%99%A8.user.js
// @updateURL https://update.greasyfork.icu/scripts/574489/Gamer520%E7%BD%91%E7%AB%99%E6%B8%B8%E6%88%8FSteam%E5%A5%BD%E8%AF%84%E7%8E%87%E6%98%BE%E7%A4%BA%E5%99%A8.meta.js
// ==/UserScript==
(function() {
'use strict';
// 调试模式
const DEBUG = true;
function log(...args) {
if (DEBUG) console.log('[SteamRating]', ...args);
}
// 配置
const CONFIG = {
CACHE_DAYS: 7,
REQUEST_DELAY: 500,
MAX_RETRIES: 3,
MIN_SIMILARITY: 0.3
};
// 检查是否为 PC PLAY 游戏(支持列表页和详情页)
function isPCPlayGame(container) {
// 尝试多种选择器
const selectors = [
'.entry-header .meta-category a',
'.entry-meta .meta-category a',
'.meta-category a'
];
for (const selector of selectors) {
const categoryLinks = container.querySelectorAll(selector);
for (const link of categoryLinks) {
if (link.textContent.includes('PC PLAY')) {
return true;
}
}
}
return false;
}
// 检查当前页面是否为详情页
function isDetailPage() {
return window.location.pathname.match(/\/\d+\.html$/) !== null;
}
// 游戏名称清洗规则
function cleanGameName(name) {
if (!name) return '';
let cleaned = name
.replace(/\|?Build\.\d+[^|]*/gi, '')
.replace(/\|?V\d+\.\d+[^|]*/gi, '')
.replace(/\|?Fix-\d+[^|]*/gi, '')
.replace(/\|?豪华中文\|?/g, '')
.replace(/\|?官方中文\|?/g, '')
.replace(/\|?中字-国语\|?/g, '')
.replace(/\|?全DLC[^|]*/g, '')
.replace(/\|?修改器\|?/g, '')
.replace(/\|?解压即撸\|?/g, '')
.replace(/\|?预购[^|]*/g, '')
.replace(/\|?数字豪华版\|?/g, '')
.replace(/\|?典藏版\|?/g, '')
.replace(/\|?放置版\|?/g, '')
.replace(/\|?非虚拟机[^|]*/g, '')
.replace(/\|+/g, ' ')
.trim();
return cleaned;
}
// 提取核心游戏名
function extractCoreName(name) {
const cleaned = cleanGameName(name);
const prefix4 = cleaned.substring(0, 4);
const prefix3 = cleaned.substring(0, 3);
const spaceIndex = cleaned.indexOf(' ');
const firstPart = spaceIndex > 0 ? cleaned.substring(0, spaceIndex) : cleaned;
const prefix5 = cleaned.substring(0, 5);
return [...new Set([cleaned, prefix4, prefix3, firstPart, prefix5])].filter(n => n.length >= 2);
}
// 计算字符串相似度
function calculateSimilarity(str1, str2) {
str1 = str1.toLowerCase().replace(/\s+/g, '');
str2 = str2.toLowerCase().replace(/\s+/g, '');
if (str1.includes(str2) || str2.includes(str1)) {
return 0.8 + (Math.min(str1.length, str2.length) / Math.max(str1.length, str2.length)) * 0.2;
}
let commonLength = 0;
for (let i = 0; i < Math.min(str1.length, str2.length); i++) {
if (str1[i] === str2[i]) {
commonLength++;
} else {
break;
}
}
return commonLength / Math.max(str1.length, str2.length);
}
// 获取缓存
function getCache(key) {
try {
const data = GM_getValue(key, null);
if (!data) return null;
const parsed = JSON.parse(data);
const now = Date.now();
const maxAge = CONFIG.CACHE_DAYS * 24 * 60 * 60 * 1000;
if (now - parsed.timestamp > maxAge) {
return null;
}
return parsed.value;
} catch (e) {
return null;
}
}
// 设置缓存
function setCache(key, value) {
try {
const data = {
timestamp: Date.now(),
value: value
};
GM_setValue(key, JSON.stringify(data));
} catch (e) {
console.error('设置缓存失败:', e);
}
}
// 延迟函数
function delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// 搜索 Steam 游戏
async function searchSteamGame(searchTerm, retries = 0) {
const cacheKey = `search_${searchTerm}`;
const cached = getCache(cacheKey);
if (cached) {
log('使用缓存:', searchTerm);
return cached;
}
try {
log('搜索:', searchTerm);
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://store.steampowered.com/api/storesearch/?term=${encodeURIComponent(searchTerm)}&l=schinese&cc=CN`,
headers: {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Accept-Language': 'zh-CN,zh;q=0.9,en;q=0.8',
'Referer': 'https://store.steampowered.com/',
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
},
onload: (res) => {
if (res.status === 200) {
try {
const data = JSON.parse(res.responseText);
log(`"${searchTerm}" 返回 ${data.total || 0} 个结果`);
resolve(data);
} catch (e) {
reject(new Error('解析JSON失败'));
}
} else {
reject(new Error(`HTTP ${res.status}`));
}
},
onerror: () => reject(new Error('网络请求失败')),
ontimeout: () => reject(new Error('请求超时'))
});
});
setCache(cacheKey, response);
return response;
} catch (error) {
if (retries < CONFIG.MAX_RETRIES) {
await delay(1000 * (retries + 1));
return searchSteamGame(searchTerm, retries + 1);
}
throw error;
}
}
// 智能搜索游戏
async function smartSearchGame(originalName) {
const coreNames = extractCoreName(originalName);
log('原始名称:', originalName);
log('尝试搜索:', coreNames);
let allResults = [];
for (const name of coreNames) {
try {
const result = await searchSteamGame(name);
if (result.items && result.items.length > 0) {
const scoredItems = result.items.map(item => {
const nameSimilarity = calculateSimilarity(originalName, item.name);
const isGame = item.type === 'game' ? 0.1 : 0;
return {
...item,
similarity: nameSimilarity + isGame
};
});
allResults.push(...scoredItems);
}
} catch (error) {
log('搜索失败:', name, error.message);
}
await delay(200);
}
if (allResults.length === 0) {
return { items: [], isUncertain: false };
}
const uniqueResults = [];
const seenIds = new Set();
for (const item of allResults) {
if (!seenIds.has(item.id)) {
seenIds.add(item.id);
uniqueResults.push(item);
}
}
uniqueResults.sort((a, b) => b.similarity - a.similarity);
log('最佳匹配:', uniqueResults[0].name, '相似度:', uniqueResults[0].similarity.toFixed(2));
const isUncertain = uniqueResults[0].similarity < CONFIG.MIN_SIMILARITY;
return {
items: uniqueResults,
isUncertain: isUncertain,
bestMatch: uniqueResults[0]
};
}
// 获取游戏评价
async function getGameReviews(appid, retries = 0) {
const cacheKey = `reviews_${appid}`;
const cached = getCache(cacheKey);
if (cached) return cached;
try {
const response = await new Promise((resolve, reject) => {
GM_xmlhttpRequest({
method: "GET",
url: `https://store.steampowered.com/appreviews/${appid}?json=1&language=schinese`,
headers: {
'Accept': 'application/json',
'Accept-Language': 'zh-CN,zh;q=0.9'
},
onload: (res) => {
if (res.status === 200) {
try {
const data = JSON.parse(res.responseText);
resolve(data);
} catch (e) {
reject(new Error('解析JSON失败'));
}
} else {
reject(new Error(`HTTP ${res.status}`));
}
},
onerror: () => reject(new Error('网络请求失败'))
});
});
setCache(cacheKey, response);
return response;
} catch (error) {
if (retries < CONFIG.MAX_RETRIES) {
await delay(1000 * (retries + 1));
return getGameReviews(appid, retries + 1);
}
throw error;
}
}
// 计算好评率
function calculateRating(positive, negative) {
const total = positive + negative;
if (total === 0) return null;
return Math.round((positive / total) * 100);
}
// 获取颜色样式
function getRatingStyle(rating) {
if (rating === null) return { bg: '#888', text: '#fff' };
if (rating >= 80) return { bg: '#4CAF50', text: '#fff' };
if (rating >= 50) return { bg: '#FFC107', text: '#000' };
return { bg: '#F44336', text: '#fff' };
}
// 创建好评率标签
function createRatingBadge(rating, positive, negative, isUncertain) {
const style = getRatingStyle(rating);
const badge = document.createElement('span');
badge.className = 'steam-rating-badge';
badge.style.cssText = `
background-color: ${style.bg};
color: ${style.text};
padding: 2px 6px;
border-radius: 3px;
font-size: 11px;
font-weight: bold;
display: inline-flex;
align-items: center;
gap: 3px;
cursor: help;
white-space: nowrap;
`;
if (rating === null) {
badge.textContent = '无评价';
} else {
const total = positive + negative;
const uncertainEmoji = isUncertain ? '❓' : '';
badge.innerHTML = `
${uncertainEmoji}${rating}%
(${total})
`;
badge.title = `好评: ${positive} | 差评: ${negative}${isUncertain ? ' (匹配不确定)' : ''}`;
}
return badge;
}
// 处理单个游戏卡片
async function processCard(card, index) {
// 检查是否为 PC PLAY 游戏
if (!isPCPlayGame(card)) {
log('跳过非PC PLAY游戏');
return;
}
const titleElement = card.querySelector('.entry-title a');
if (!titleElement) return;
const fullName = titleElement.textContent || titleElement.getAttribute('title');
const cleanName = cleanGameName(fullName);
if (!cleanName) return;
const priceElement = card.querySelector('.meta-price');
const dateElement = card.querySelector('.meta-date');
if (!priceElement) return;
// 显示加载状态
priceElement.innerHTML = '...';
try {
await delay(index * CONFIG.REQUEST_DELAY);
const searchResult = await smartSearchGame(cleanName);
if (!searchResult.items || searchResult.items.length === 0) {
priceElement.innerHTML = '未找到';
return;
}
const game = searchResult.bestMatch;
const appid = game.id;
const isUncertain = searchResult.isUncertain;
log('选中游戏:', game.name, 'AppID:', appid, '不确定:', isUncertain);
const reviewsResult = await getGameReviews(appid);
if (!reviewsResult.query_summary || reviewsResult.query_summary.total_reviews === 0) {
priceElement.innerHTML = '无评价';
return;
}
const { total_positive, total_negative } = reviewsResult.query_summary;
const rating = calculateRating(total_positive, total_negative);
// 替换价格标签为好评率
const badge = createRatingBadge(rating, total_positive, total_negative, isUncertain);
priceElement.innerHTML = '';
priceElement.appendChild(badge);
// 将时间标签替换为Steam英文名(可点击跳转)
if (dateElement) {
const steamName = document.createElement('li');
steamName.className = 'meta-steam-name';
steamName.style.cssText = `
flex: 1;
text-align: left;
`;
const link = document.createElement('a');
link.href = `https://store.steampowered.com/app/${appid}/`;
link.target = '_blank';
link.style.cssText = `
color: #888;
font-size: 10px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
max-width: 120px;
display: block;
text-decoration: none;
cursor: pointer;
`;
link.title = `${game.name} - 点击打开Steam商店页面`;
link.innerHTML = `${game.name}`;
// 鼠标悬停效果
link.addEventListener('mouseenter', () => {
link.style.color = '#66c0f4';
link.style.textDecoration = 'underline';
});
link.addEventListener('mouseleave', () => {
link.style.color = '#888';
link.style.textDecoration = 'none';
});
steamName.appendChild(link);
// 替换原来的时间元素
dateElement.parentNode.replaceChild(steamName, dateElement);
}
// 让好评率标签也可以点击跳转
if (badge) {
badge.style.cursor = 'pointer';
badge.title = `${badge.title} - 点击打开Steam商店页面`;
badge.addEventListener('click', () => {
window.open(`https://store.steampowered.com/app/${appid}/`, '_blank');
});
}
} catch (error) {
console.error('处理失败:', cleanName, error);
priceElement.innerHTML = '错误';
}
}
// 处理详情页
async function processDetailPage() {
log('处理详情页');
// 尝试多种选择器查找文章容器
let article = document.querySelector('article.post');
if (!article) {
article = document.querySelector('article');
}
if (!article) {
article = document.querySelector('.content-area');
}
if (!article) {
article = document.body;
}
log('使用容器:', article.tagName, article.className);
// 检查是否为 PC PLAY 游戏
if (!isPCPlayGame(article)) {
log('详情页不是 PC PLAY 游戏,跳过');
return;
}
// 获取游戏名称
let titleElement = article.querySelector('h1.entry-title');
if (!titleElement) {
titleElement = document.querySelector('h1.entry-title');
}
if (!titleElement) {
titleElement = document.querySelector('h1');
}
if (!titleElement) {
log('未找到标题');
return;
}
const fullName = titleElement.textContent;
const cleanName = cleanGameName(fullName);
if (!cleanName) return;
log('详情页游戏:', cleanName);
// 查找时间元素(尝试多种选择器)
let dateElement = article.querySelector('.entry-meta .meta-date');
if (!dateElement) {
dateElement = document.querySelector('.entry-meta .meta-date');
}
if (!dateElement) {
dateElement = document.querySelector('.meta-date');
}
if (!dateElement) {
log('未找到时间元素');
return;
}
// 显示加载状态
dateElement.innerHTML = '查询Steam数据中...';
try {
const searchResult = await smartSearchGame(cleanName);
if (!searchResult.items || searchResult.items.length === 0) {
dateElement.innerHTML = '未找到Steam数据';
return;
}
const game = searchResult.bestMatch;
const appid = game.id;
const isUncertain = searchResult.isUncertain;
log('详情页选中:', game.name, 'AppID:', appid);
const reviewsResult = await getGameReviews(appid);
if (!reviewsResult.query_summary || reviewsResult.query_summary.total_reviews === 0) {
// 只显示游戏名
dateElement.innerHTML = `
${game.name} (无评价)
`;
return;
}
const { total_positive, total_negative } = reviewsResult.query_summary;
const rating = calculateRating(total_positive, total_negative);
const style = getRatingStyle(rating);
const uncertainEmoji = isUncertain ? '❓' : '';
// 替换时间元素为 Steam 信息
dateElement.innerHTML = `
${game.name}
${uncertainEmoji}${rating}%
(${total_positive + total_negative})
`;
log('详情页处理完成');
} catch (error) {
console.error('详情页处理失败:', error);
dateElement.innerHTML = '查询失败';
}
}
// 主函数
async function main() {
log('脚本开始运行 v1.5 - PC PLAY专用(支持列表页和详情页)');
// 添加样式
const style = document.createElement('style');
style.textContent = `
.steam-rating-badge {
transition: opacity 0.3s;
}
.steam-rating-badge:hover {
opacity: 0.8;
}
.meta-price {
min-width: 50px;
text-align: right;
}
.meta-steam-name {
flex: 1;
overflow: hidden;
text-align: left !important;
}
.meta-steam-name a {
text-align: left !important;
justify-content: flex-start !important;
}
.post-meta-box {
display: flex;
align-items: center;
justify-content: space-between;
}
`;
document.head.appendChild(style);
// 判断是详情页还是列表页
if (isDetailPage()) {
log('当前为详情页模式');
await processDetailPage();
} else {
log('当前为列表页模式');
const cards = document.querySelectorAll('article.post-grid');
log('找到游戏卡片:', cards.length);
if (cards.length === 0) return;
let pcPlayCount = 0;
for (let i = 0; i < cards.length; i++) {
if (isPCPlayGame(cards[i])) {
pcPlayCount++;
processCard(cards[i], i);
}
}
log('PC PLAY游戏数量:', pcPlayCount);
}
}
// 页面加载完成后执行
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', main);
} else {
main();
}
// 监听页面变化
const observer = new MutationObserver((mutations) => {
let shouldReload = false;
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === 1 && node.matches && node.matches('article.post-grid')) {
shouldReload = true;
}
});
}
});
if (shouldReload) {
setTimeout(main, 500);
}
});
observer.observe(document.body, {
childList: true,
subtree: true
});
})();