// ==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 }); })();