// ==UserScript== // @name Better SteamPYPlus // @namespace https://space.bilibili.com/93654843 // @version 1.0.2 // @description Better SteamPY基础上增加了绝版游戏筛选、自动翻页、慈善包筛选功能 // @author FiNNiER、ZY // @match *://steampy.com/* // @icon https://steampy.com/img/logo.63413a4f.png // @grant GM_xmlhttpRequest // @grant GM_setValue // @grant GM_getValue // @grant GM_addStyle // @connect gitee.com // @connect api.steampowered.com // @connect store.steampowered.com // @connect steam-tracker.com // @connect barter.vg // @connect bartervg.com // @run-at document-body // @downloadURL https://update.greasyfork.icu/scripts/562523/Better%20SteamPYPlus.user.js // @updateURL https://update.greasyfork.icu/scripts/562523/Better%20SteamPYPlus.meta.js // ==/UserScript== var Saves = { wishlist: [], ownedApps: [], familygameList: [], lastupdatetime: 0, }; var limitedApps = []; var noGameList = []; var delistedGamesData = null; // 存储下架游戏数据 var noDlc = false; var noownedGames = false; var noRestrictedGames = false; var onlyDelistedGames = false; // 只显示下架游戏 var delistedTypes = []; // 选中的下架类型 var autoSkipTimer = null; // 用于防抖的计时器 var isAutoSkipping = false; // 防止重复触发翻页 var lastGameIds = ''; // 记录上一次的游戏ID列表,用于检测数据是否更新 var autoSkipTargetPage = 0; // 自动跳页的目标页码 var isFastScanning = false; // 快速扫描进行中 var fastScanAbort = false; // 中止快速扫描 var blacklist = []; // 黑名单 [{appId, name, addedTime}] var noBlacklistGames = false; // 不显示黑名单游戏 var noBundledGames = false; // 不显示进过慈善包的游戏 var bundledAppsData = null; // barter.vg 慈善包数据 {appid: bundleCount} (function () { 'use strict'; load(); observePageChanges(); // 强制加载下架游戏数据 setTimeout(() => { if (!delistedGamesData || !delistedGamesData.removed_apps) { console.log('[Better Steampy] 强制加载下架游戏数据'); getDelistedGamesList(); } }, 2000); })(); // 判断单个 appId 是否符合当前筛选条件(纯数据判断,不依赖 DOM) function doesAppPassFilter(appId) { appId = Number(appId); if (!appId) return false; // 下架游戏筛选 if (onlyDelistedGames) { if (!delistedGamesData || !delistedGamesData.removed_apps) return false; const gameData = delistedGamesData.removed_apps[appId.toString()]; if (!gameData) return false; const categoryMap = { 'Purchase disabled': 'purchase disabled', 'Delisted': 'delisted', 'Delisted video': 'delisted', 'F2P': 'f2p', 'Unreleased': 'unreleased', 'Test app': 'test app', 'Retail only': 'retail only', 'Pre-order exclusive': 'pre-order exclusive', 'Banned': 'banned' }; const mappedType = categoryMap[gameData.category] || (gameData.category || '').toLowerCase(); if (!delistedTypes.includes(mappedType)) return false; } // 不显示 DLC if (noDlc && noGameList.includes(appId)) return false; // 不显示已拥有 if (noownedGames && Saves.ownedApps.includes(appId)) return false; // 不显示资料受限(不在 limitedApps 列表中 = 受限) if (noRestrictedGames && localStorage.getItem('IsProfileFeatureLimited') && !limitedApps.includes(appId)) return false; // 不显示黑名单 if (noBlacklistGames && blacklist.some(b => b.appId === appId)) return false; // 不显示进过慈善包的游戏 if (noBundledGames && bundledAppsData) { const bundleCount = bundledAppsData[appId.toString()]; if (bundleCount && bundleCount > 0) return false; } return true; } // 黑名单操作 function addToBlacklist(appId, name) { appId = Number(appId); if (blacklist.some(b => b.appId === appId)) return; blacklist.push({ appId, name: name || ('App ' + appId), addedTime: Date.now() }); GM_setValue('Blacklist', blacklist); } function removeFromBlacklist(appId) { appId = Number(appId); blacklist = blacklist.filter(b => b.appId !== appId); GM_setValue('Blacklist', blacklist); } function isInBlacklist(appId) { return blacklist.some(b => b.appId === Number(appId)); } // 获取 Vue 分页父组件及翻页方法 function getPageComponent() { try { const pageComp = document.querySelector('.ivu-page').__vue__; for (let i = 1; i <= 6; i++) { let comp = pageComp; for (let j = 0; j < i; j++) { comp = comp.$parent; if (!comp) break; } if (comp && (comp.pageHandler || comp.changePage)) { return comp; } } } catch (e) {} return null; } // 获取当前页码和总页数 function getPageInfo() { const activePageEl = document.querySelector('.zpagenav .page-ul li.active'); let currentPage = 1, maxPage = 1; if (activePageEl) { currentPage = parseInt(activePageEl.textContent) || 1; const allPageNums = Array.from(document.querySelectorAll('.zpagenav .page-ul li')) .map(li => parseInt(li.textContent)) .filter(num => !isNaN(num)); maxPage = Math.max(...allPageNums, 1); } return { currentPage, maxPage }; } // 跳转到指定页码 function jumpToPage(pageNum) { const comp = getPageComponent(); if (comp) { if (comp.pageHandler) { comp.pageHandler(pageNum); } else if (comp.changePage) { comp.changePage(pageNum); } return true; } // fallback: 点击按钮 const nextButton = document.querySelector('.ivu-page-next:not(.ivu-page-disabled)'); if (nextButton) { nextButton.click(); return true; } return false; } // 从当前 DOM 中提取所有游戏的 appId function extractCurrentPageAppIds() { const icons = document.querySelectorAll('.cdkGameIcon'); return Array.from(icons).map(icon => { const src = icon.getAttribute('data-src') || ''; const match = src.match(/\/apps\/(\d+)\//); return match ? Number(match[1]) : 0; }).filter(id => id > 0); } // 生成当前页数据的指纹 function getPageDataFingerprint() { const icons = document.querySelectorAll('.cdkGameIcon'); return Array.from(icons).map(icon => icon.getAttribute('data-src') || '').join('|'); } // 事件驱动等待:用 MutationObserver 监听 data-src 变化,比轮询快得多 function waitForPageData(expectedPage, prevFingerprint, timeout = 8000) { return new Promise((resolve) => { let settled = false; let timeoutId = null; let observer = null; const cleanup = () => { if (settled) return; settled = true; if (observer) observer.disconnect(); if (timeoutId) clearTimeout(timeoutId); }; const tryResolve = () => { if (settled) return; if (fastScanAbort) { cleanup(); resolve(false); return; } const { currentPage } = getPageInfo(); const fp = getPageDataFingerprint(); const icons = document.querySelectorAll('.cdkGameIcon'); if (currentPage === expectedPage && fp !== prevFingerprint && fp.length > 0 && icons.length > 0) { cleanup(); resolve(true); } }; // 超时保底 timeoutId = setTimeout(() => { if (!settled) { console.log('[Better Steampy] 第', expectedPage, '页等待超时'); cleanup(); resolve(false); } }, timeout); // 先立即检查(可能数据已经到了) tryResolve(); if (settled) return; // MutationObserver 监听 data-src 变化——Vue 更新 DOM 时立即触发 observer = new MutationObserver(() => { tryResolve(); }); const container = document.querySelector('.cdkGameIcon')?.closest('.gameblock')?.parentElement?.parentElement || document.body; observer.observe(container, { attributes: true, attributeFilter: ['data-src'], subtree: true, childList: true }); }); } // 创建/获取扫描进度浮窗(单例,固定位置,只更新文字) function getScanOverlay() { let el = document.getElementById('bsp-scan-overlay'); if (!el) { el = document.createElement('div'); el.id = 'bsp-scan-overlay'; el.style.cssText = 'position:fixed;top:16px;right:16px;z-index:99999;background:rgba(0,0,0,0.78);color:#fff;padding:12px 22px;border-radius:8px;font-size:14px;pointer-events:auto;display:none;box-shadow:0 2px 12px rgba(0,0,0,0.3);max-width:320px;line-height:1.6;'; // 停止按钮 const btn = document.createElement('span'); btn.id = 'bsp-scan-stop'; btn.textContent = ' ✕ 停止'; btn.style.cssText = 'cursor:pointer;margin-left:10px;color:#ff6b6b;font-weight:bold;'; btn.onclick = () => { fastScanAbort = true; }; el.appendChild(document.createElement('span')); // 文字容器 el.appendChild(btn); document.body.appendChild(el); } return el; } function showScanProgress(text) { const el = getScanOverlay(); el.style.display = 'block'; el.querySelector('span:first-child').textContent = text; } function hideScanOverlay() { const el = document.getElementById('bsp-scan-overlay'); if (el) el.style.display = 'none'; } // 尝试从 Vue 组件直接读取游戏列表数据(比 DOM 快,不需要等渲染) function tryGetVueGameList() { try { const comp = getPageComponent(); if (!comp) return null; // 遍历组件及其子组件,查找包含游戏列表的 data const searchData = (vm) => { if (!vm) return null; const d = vm.$data || vm._data || {}; for (const key of Object.keys(d)) { const val = d[key]; if (Array.isArray(val) && val.length > 0 && val[0] && (val[0].appid || val[0].appId || val[0].app_id || val[0].icon)) { return val; } } // 搜索子组件 if (vm.$children) { for (const child of vm.$children) { const result = searchData(child); if (result) return result; } } return null; }; // 从分页组件向上找到根列表组件 let target = comp; for (let i = 0; i < 3; i++) { const result = searchData(target); if (result) return result; if (target.$parent) target = target.$parent; else break; } } catch (e) {} return null; } // 从 Vue 数据中提取 appId function extractAppIdsFromVueData(list) { return list.map(item => { // 尝试各种可能的字段名 const id = item.appid || item.appId || item.app_id || 0; if (id) return Number(id); // 从 icon URL 提取 const icon = item.icon || item.img || ''; const match = icon.match(/\/apps\/(\d+)\//); return match ? Number(match[1]) : 0; }).filter(id => id > 0); } // 快速扫描:跳过不符合条件的页面,直接定位到有结果的页面 async function fastScanPages(startPage, maxPage) { if (isFastScanning) return; isFastScanning = true; fastScanAbort = false; showScanProgress(`快速扫描中... ${startPage} / ${maxPage}`); let foundPage = -1; for (let page = startPage; page <= maxPage; page++) { if (fastScanAbort) { console.log('[Better Steampy] 快速扫描被中止'); break; } showScanProgress(`快速扫描中... ${page} / ${maxPage}`); // 记录跳转前的数据指纹 const prevFingerprint = getPageDataFingerprint(); jumpToPage(page); // 等待数据更新 const loaded = await waitForPageData(page, prevFingerprint, 8000); if (!loaded) { console.log('[Better Steampy] 第', page, '页加载超时,跳过'); continue; } // 优先从 Vue 数据读取(更快更可靠),fallback 到 DOM let appIds; const vueList = tryGetVueGameList(); if (vueList) { appIds = extractAppIdsFromVueData(vueList); } if (!appIds || appIds.length === 0) { appIds = extractCurrentPageAppIds(); } const hasMatch = appIds.some(id => doesAppPassFilter(id)); if (hasMatch) { foundPage = page; console.log('[Better Steampy] 在第', page, '页找到符合条件的游戏'); break; } else { console.log('[Better Steampy] 第', page, '页无符合条件的游戏,继续扫描'); } } hideScanOverlay(); if (foundPage > 0) { const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); iview.Notice.success({ title: 'Better Steampy', desc: `已跳转到第 ${foundPage} 页(跳过了 ${foundPage - startPage} 页无结果页面)`, duration: 3 }); } else if (!fastScanAbort) { iview.Notice.warning({ title: 'Better Steampy', desc: `第 ${startPage} ~ ${maxPage} 页均无符合条件的游戏`, duration: 4 }); if (startPage > 1) { jumpToPage(startPage); } } isFastScanning = false; isAutoSkipping = false; autoSkipTargetPage = 0; } // 检测当前页是否为空,如果为空则启动快速扫描 function checkAndAutoSkip() { if (autoSkipTimer) clearTimeout(autoSkipTimer); if (isFastScanning) return; autoSkipTimer = setTimeout(() => { const cdkGameIcons = document.querySelectorAll('.cdkGameIcon'); const allGames = Array.from(cdkGameIcons).map(icon => icon.closest('.gameblock')).filter(el => el !== null); const visibleGames = allGames.filter(el => el.style.display !== 'none'); // 获取当前页面游戏的ID列表 const currentGameIds = Array.from(cdkGameIcons).map(icon => { const src = icon.getAttribute('data-src') || ''; const match = src.match(/\/apps\/(\d+)\//); return match ? match[1] : ''; }).join(','); const dataChanged = currentGameIds !== lastGameIds && lastGameIds !== ''; if (isAutoSkipping) { if (!dataChanged) return; isAutoSkipping = false; } else if (dataChanged) { autoSkipTargetPage = 0; } lastGameIds = currentGameIds; // 当前页有游戏但全部被隐藏 if (allGames.length > 0 && visibleGames.length === 0) { const { currentPage, maxPage } = getPageInfo(); if (currentPage >= maxPage) { iview.Notice.warning({ title: 'Better Steampy', desc: '当前页无符合条件的游戏,已到最后一页', duration: 3 }); return; } // 启动快速扫描,从下一页开始 fastScanPages(currentPage + 1, maxPage); } else { isAutoSkipping = false; autoSkipTargetPage = 0; } }, 500); } // 重置自动跳页状态(用于手动翻页时调用) function resetAutoSkipState() { isAutoSkipping = false; autoSkipTargetPage = 0; // 如果正在快速扫描,中止它 if (isFastScanning) { fastScanAbort = true; } } //读取个人库存及愿望单并储存 function getOwnAndWish() { return new Promise((resolve, reject) => { var wishlist = []; var ownedApps = []; GM_xmlhttpRequest({ method: 'GET', url: 'https://store.steampowered.com/dynamicstore/userdata/?t=' + Math.trunc(Date.now() / 1000), responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); wishlist = data.rgWishlist; ownedApps = data.rgOwnedApps; let previousSaves = GM_getValue('Saves'); let newSave = { wishlist: wishlist, ownedApps: ownedApps, familygameList: previousSaves.familygameList, lastupdatetime: new Date().getTime(), }; GM_setValue('Saves', newSave); Saves = newSave; iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${ownedApps.length} 个库存游戏及DLC,${wishlist.length} 个愿望单游戏`, }); resolve(newSave); }, }); }); } //读取家庭库并储存 function getFamilyGame() { return new Promise((resolve, reject) => { var access_token; var family_groupid; var familygameList = []; GM_xmlhttpRequest({ method: 'GET', url: 'https://store.steampowered.com/pointssummary/ajaxgetasyncconfig', responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); access_token = data.data.webapi_token; // access_token GM_xmlhttpRequest({ method: 'GET', url: `https://api.steampowered.com/IFamilyGroupsService/GetFamilyGroupForUser/v1/?access_token=${access_token}`, responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); family_groupid = data.response.family_groupid; // family_groupid GM_xmlhttpRequest({ method: 'GET', url: `https://api.steampowered.com/IFamilyGroupsService/GetSharedLibraryApps/v1/?access_token=${access_token}&family_groupid=${family_groupid}&include_own=true`, responseType: 'json', onload: function (response) { let data = JSON.parse(response.responseText); data.response.apps.forEach((app) => { if (app.exclude_reason == 0) { familygameList.push(app.appid); } }); let previousSaves = GM_getValue('Saves'); let newSave = { wishlist: previousSaves.wishlist, ownedApps: previousSaves.ownedApps, familygameList: familygameList, lastupdatetime: new Date().getTime(), }; GM_setValue('Saves', newSave); Saves = newSave; iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${familygameList.length} 个家庭库游戏`, }); resolve(familygameList); }, }); }, }); }, }); }); } //获取受限游戏列表 function getLimitedGamesList() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://gitee.com/Finnier/getSteamRestrictedGameLIst/raw/main/data/normalist.json', responseType: 'json', onload: function (response) { var data = JSON.parse(response.responseText); var limitedGames = data; GM_setValue('limitedApps', limitedGames); iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${limitedGames.length} 个非受限游戏`, }); resolve(limitedGames); }, }); }); } //获取非游戏列表 function getNogameList() { return new Promise((resolve, reject) => { GM_xmlhttpRequest({ method: 'GET', url: 'https://gitee.com/Finnier/getSteamAppListWithType/raw/main/data/Listwithnogame.json', responseType: 'json', onload: function (response) { var data = JSON.parse(response.responseText); var nogamelistdata = Object.keys(data).map(Number); GM_setValue('NoGameList', nogamelistdata); noGameList = nogamelistdata; iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${nogamelistdata.length} 个DLC及原声带`, }); resolve(nogamelistdata); }, }); }); } //获取下架游戏列表(尝试从 SWI 插件缓存读取) function getDelistedGamesList() { return new Promise((resolve, reject) => { console.log('[Better Steampy] 开始获取下架游戏数据...'); // 尝试从 chrome.storage 读取 SWI 插件的缓存数据 if (typeof chrome !== 'undefined' && chrome.storage && chrome.storage.local) { chrome.storage.local.get(['swi_decommissioned'], function(result) { if (result.swi_decommissioned && result.swi_decommissioned.removed_apps) { console.log('[Better Steampy] 从 SWI 插件缓存读取下架游戏数据'); delistedGamesData = result.swi_decommissioned; GM_setValue('DelistedGamesData', delistedGamesData); const count = Object.keys(delistedGamesData.removed_apps).length; console.log('[Better Steampy] 成功加载下架游戏数据,共', count, '个'); iview.Notice.success({ title: `Better Steampy`, desc: `已从 SWI 插件加载 ${count} 个下架游戏数据`, }); resolve(delistedGamesData); } else { // SWI 插件没有缓存,尝试从 API 获取 fetchFromAPI(); } }); } else { // 不支持 chrome.storage,直接从 API 获取 fetchFromAPI(); } function fetchFromAPI() { GM_xmlhttpRequest({ method: 'GET', url: 'https://steam-tracker.com/api?action=GetAppListV3', responseType: 'json', timeout: 10000, onload: function (response) { try { console.log('[Better Steampy] API 响应状态:', response.status); var data = JSON.parse(response.responseText); console.log('[Better Steampy] 解析后的数据结构:', Object.keys(data)); // 转换数组格式为对象格式,方便查询 if (data.removed_apps && Array.isArray(data.removed_apps)) { const removed_apps_obj = {}; data.removed_apps.forEach(app => { removed_apps_obj[app.appid] = { name: app.name, category: app.category, type: app.type }; }); const convertedData = { removed_apps: removed_apps_obj }; GM_setValue('DelistedGamesData', convertedData); delistedGamesData = convertedData; const count = Object.keys(removed_apps_obj).length; console.log('[Better Steampy] 成功加载下架游戏数据,共', count, '个'); iview.Notice.success({ title: `Better Steampy`, desc: `已加载 ${count} 个下架游戏数据`, }); resolve(convertedData); } else { throw new Error('数据格式不正确'); } } catch (e) { console.error('[Better Steampy] 解析下架游戏数据失败:', e); iview.Notice.error({ title: `Better Steampy`, desc: `加载下架游戏数据失败: ${e.message}`, }); reject(e); } }, onerror: function(error) { console.error('[Better Steampy] 请求下架游戏数据失败:', error); iview.Notice.error({ title: `Better Steampy`, desc: `请求下架游戏数据失败,请检查网络连接`, }); reject(error); }, ontimeout: function() { console.error('[Better Steampy] 请求下架游戏数据超时'); iview.Notice.error({ title: `Better Steampy`, desc: `请求下架游戏数据超时`, }); reject(new Error('timeout')); } }); } }); } //获取慈善包数据(从 barter.vg) function getBundledGamesList() { return new Promise((resolve, reject) => { console.log('[Better Steampy] 开始获取慈善包数据...'); GM_xmlhttpRequest({ method: 'GET', url: 'https://bartervg.com/browse/bundles/json/', responseType: 'json', timeout: 30000, onload: function (response) { try { var data = typeof response.response === 'object' ? response.response : JSON.parse(response.responseText); // data 格式: { "appid": { bundles: N }, ... } var bundleMap = {}; var count = 0; for (var sku in data) { if (data[sku] && data[sku].bundles && data[sku].bundles > 0) { bundleMap[sku] = data[sku].bundles; count++; } } bundledAppsData = bundleMap; GM_setValue('BundledAppsData', bundleMap); GM_setValue('BundledAppsDataTime', Date.now()); console.log('[Better Steampy] 成功加载慈善包数据,共', count, '个进过包的游戏'); iview.Notice.success({ title: 'Better Steampy', desc: `已加载 ${count} 个进过慈善包的游戏数据`, }); resolve(bundleMap); } catch (e) { console.error('[Better Steampy] 解析慈善包数据失败:', e); reject(e); } }, onerror: function(error) { console.error('[Better Steampy] 请求慈善包数据失败:', error); reject(error); }, ontimeout: function() { console.error('[Better Steampy] 请求慈善包数据超时'); reject(new Error('timeout')); } }); }); } //初始化脚本配置菜单 function init() { const settings = document.createElement('div'); settings.innerHTML = `
脚本设置

暂时不支持捆绑包标记

上次更新于 (每24小时执行一次自动更新)

已加载 {{ownedApps}} 个库存游戏及DLC

已加载 {{wishlist}} 个愿望单游戏

已加载 {{familygameList}} 个家庭库游戏

是否加入了家庭组:

重载存档 清除存档
数据来源于https://github.com/F1NN1ER/getSteamRestrictedGameLIst 数据每日更新,可能尚有部分未及时标记
是否启用受限游戏标注:

目前共加载{{limitedApps}}个非受限游戏(跟随拥有状态自动更新)

刷新
已拥有
在愿望单中
在家庭库中
未拥有
是否关闭网页右下方推广侧栏:
点击游戏卡片上的 🚫 按钮可将游戏加入黑名单,在筛选中勾选"不显示黑名单游戏"即可隐藏

当前黑名单共 {{blacklistCount}} 个游戏

{{item.name}} ({{item.appId}}) 移除
黑名单为空
清空黑名单
`; const filter = document.createElement('div'); filter.innerHTML = `
筛选: 隐藏已拥有 隐藏受限 隐藏DLC 隐藏黑名单 隐藏慈善包
仅下架
Purchase Disabled Delisted F2P Unreleased Test App Retail Only Pre-order Banned
`; const targetElement = document.querySelector('.balanceTitle > div'); targetElement.appendChild(settings); targetElement.appendChild(filter); new Vue({ el: '#settings', data() { return { reloadLimitedSaves_loading: false, refershSaves_loading: false, modal: false, lastUpdateTime: Saves.lastupdatetime, ownedApps: Saves.ownedApps.length, wishlist: Saves.wishlist.length, familygameList: Saves.familygameList.length, limitedApps: limitedApps.length, isInFamilyGroup: JSON.parse(localStorage.getItem('isInfamily')), checkIsProfileFeatureLimited: JSON.parse( localStorage.getItem('IsProfileFeatureLimited') ), isSuspensionOff: JSON.parse(localStorage.getItem('isSuspensionOff')), ownedAppsColor: localStorage.getItem('ownedColor'), wishlistColor: localStorage.getItem('wishlistColor'), familygameColor: localStorage.getItem('familygameColor'), unownedColor: localStorage.getItem('unownedColor'), defaultcolors: ['#0c8918', '#177cb0', '#ff8936', '#ff2e63'], blacklistItems: [...blacklist], blacklistCount: blacklist.length, }; }, methods: { updateValues() { this.ownedApps = Saves.ownedApps.length; this.wishlist = Saves.wishlist.length; this.familygameList = Saves.familygameList.length; this.limitedApps = limitedApps.length; this.lastUpdateTime = Saves.lastupdatetime; this.blacklistItems = [...blacklist]; this.blacklistCount = blacklist.length; }, isInFamilyGroup_change(status) { if (status) { localStorage.setItem('isInfamily', JSON.stringify(true)); } else { localStorage.removeItem('isInfamily'); } }, checkIsProfileFeatureLimited_change(status) { if (status) { localStorage.setItem('IsProfileFeatureLimited', JSON.stringify(true)); Saves = GM_getValue('Saves'); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); checkAndAutoSkip(); // 添加自动跳页检测 } else { localStorage.removeItem('IsProfileFeatureLimited'); const elements = document.querySelectorAll('.ProfileFeaturesLimited'); elements.forEach((element) => { element.parentNode.removeChild(element); }); } }, isSuspensionOff_change(status) { if (status) { localStorage.setItem('isSuspensionOff', JSON.stringify(true)); GM_addStyle('.suspension{display:none}'); } else { GM_addStyle('.suspension{display:block}'); localStorage.removeItem('isSuspensionOff'); } }, ownedAppsColor_change(color) { ownedColor = color; localStorage.setItem('ownedColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, wishlistColor_change(color) { wishlistColor = color; localStorage.setItem('wishlistColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, familygameColor_change(color) { familygameColor = color; localStorage.setItem('familygameColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, unownedColor_change(color) { unownedColor = color; localStorage.setItem('unownedColor', color); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, async reloadSaves() { this.$Notice.info({ title: '正在重载存档', }); this.refershSaves_loading = true; await Promise.all([ getOwnAndWish(), this.isInFamilyGroup ? getFamilyGame() : Promise.resolve(), ]); Saves = GM_getValue('Saves'); const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); this.updateValues(); this.refershSaves_loading = false; this.$Notice.success({ title: '重载完毕', }); checkAndAutoSkip(); // 添加自动跳页检测 }, async reloadLimitedSaves() { this.$Notice.info({ title: '正在加载受限游戏列表', }); this.reloadLimitedSaves_loading = true; await getLimitedGamesList(); await getNogameList(); limitedApps = GM_getValue('limitedApps'); this.updateValues(); this.reloadLimitedSaves_loading = false; this.$Notice.success({ title: '加载完毕', }); checkAndAutoSkip(); // 添加自动跳页检测 }, clearSaves() { this.$Notice.info({ title: '存档已清除', }); let nullSaves = { wishlist: [], ownedApps: [], familygameList: [], lastupdatetime: 0, }; Saves = nullSaves; GM_setValue('Saves', nullSaves); this.updateValues(); }, removeBlacklistItem(appId) { removeFromBlacklist(appId); this.blacklistItems = [...blacklist]; this.blacklistCount = blacklist.length; // 刷新页面显示 const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); }, clearBlacklist() { blacklist = []; GM_setValue('Blacklist', blacklist); this.blacklistItems = []; this.blacklistCount = 0; const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); this.$Notice.success({ title: '黑名单已清空' }); }, }, }); new Vue({ el: '#filter', data() { return { filter: [], delistedTypes: ['purchase disabled', 'delisted', 'f2p', 'unreleased', 'test app', 'retail only', 'pre-order exclusive', 'banned'], showDelistedTypes: false, delistedTypesPanelExpanded: false, }; }, computed: {}, methods: { filterChange() { noownedGames = this.filter.includes('noOwnedGames'); noRestrictedGames = this.filter.includes('noRestrictedGames'); noDlc = this.filter.includes('noDlc'); noBlacklistGames = this.filter.includes('noBlacklistGames'); noBundledGames = this.filter.includes('noBundledGames'); onlyDelistedGames = this.filter.includes('onlyDelistedGames'); this.showDelistedTypes = onlyDelistedGames; if (onlyDelistedGames) { this.delistedTypesPanelExpanded = true; } delistedTypes = this.delistedTypes; const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); checkAndAutoSkip(); }, delistedTypesChange() { delistedTypes = this.delistedTypes; const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); checkAndAutoSkip(); }, toggleDelistedTypesPanel() { this.delistedTypesPanelExpanded = !this.delistedTypesPanelExpanded; }, }, }); if (localStorage.getItem('isSuspensionOff') === 'true') { GM_addStyle('.suspension{display:none}'); } } //游戏状态标记-CDKEY function cdkeyGameChecker(element) { const isAppOwned = (appId) => Saves.ownedApps.includes(appId); const isAppinwishlist = (appId) => Saves.wishlist.includes(appId); const isAppShared = (appId) => Saves.familygameList.includes(appId); const isNotLimited = (appId) => !limitedApps.includes(appId); // 恢复原来的判断方式 const isDLC = (appId) => noGameList.includes(appId); const getAppId = (url) => (url.match(/\/apps\/(\d+)\//) || [])[1] || null; const getBundleId = (url) =>(url.match(/\/bundles\/(\d+)\//) || [])[1] || null; const appId = Number(getAppId(element.getAttribute('data-src'))); const gameNameElement = element .closest('.gameblock') .querySelector('.gameName'); const gameBlock = element.closest('.gameblock'); // 检测游戏是否下架以及下架类型(使用 Steam-Tracker 数据) const getDelistedType = () => { if (!delistedGamesData || !delistedGamesData.removed_apps) { return null; } const appIdStr = appId.toString(); const gameData = delistedGamesData.removed_apps[appIdStr]; if (!gameData) { // 不是下架游戏 return null; } // Steam-Tracker 的 category 字段对应下架类型 const category = gameData.category; if (!category) { return null; } // 映射 Steam-Tracker 的 category 到我们的类型 const categoryMap = { 'Purchase disabled': 'purchase disabled', 'Delisted': 'delisted', 'Delisted video': 'delisted', 'F2P': 'f2p', 'Unreleased': 'unreleased', 'Test app': 'test app', 'Retail only': 'retail only', 'Pre-order exclusive': 'pre-order exclusive', 'Banned': 'banned' }; const mappedType = categoryMap[category] || category.toLowerCase(); return mappedType; }; if (appId != 0) { element.parentElement.parentElement.style.display = 'block'; // 添加黑名单按钮(如果还没有) if (gameBlock && !gameBlock.querySelector('.bsp-blacklist-btn')) { const btn = document.createElement('div'); btn.className = 'bsp-blacklist-btn'; btn.title = isInBlacklist(appId) ? '已在黑名单(点击移除)' : '加入黑名单'; btn.textContent = isInBlacklist(appId) ? '✅' : '🚫'; btn.style.cssText = 'position:absolute;top:4px;left:4px;z-index:100;cursor:pointer;font-size:18px;line-height:1;background:rgba(0,0,0,0.5);border-radius:50%;width:26px;height:26px;display:flex;align-items:center;justify-content:center;opacity:0.7;transition:opacity 0.2s;'; btn.onmouseenter = () => { btn.style.opacity = '1'; }; btn.onmouseleave = () => { btn.style.opacity = '0.7'; }; btn.onclick = (e) => { e.stopPropagation(); e.preventDefault(); const gameName = gameNameElement ? gameNameElement.textContent.trim() : ''; if (isInBlacklist(appId)) { removeFromBlacklist(appId); btn.textContent = '🚫'; btn.title = '加入黑名单'; iview.Notice.info({ title: 'Better Steampy', desc: `已将「${gameName}」移出黑名单`, duration: 2 }); } else { addToBlacklist(appId, gameName); btn.textContent = '✅'; btn.title = '已在黑名单(点击移除)'; iview.Notice.info({ title: 'Better Steampy', desc: `已将「${gameName}」加入黑名单`, duration: 2 }); } // 如果启用了黑名单筛选,立即刷新显示 if (noBlacklistGames) { const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((el) => { cdkeyGameChecker(el); }); checkAndAutoSkip(); } }; // 插入到 gameBlock 中(相对定位的容器) const imgWrap = gameBlock.querySelector('.cdkGameImg') || gameBlock; imgWrap.style.position = 'relative'; imgWrap.appendChild(btn); } else if (gameBlock) { // 更新已有按钮状态 const existingBtn = gameBlock.querySelector('.bsp-blacklist-btn'); if (existingBtn) { existingBtn.textContent = isInBlacklist(appId) ? '✅' : '🚫'; existingBtn.title = isInBlacklist(appId) ? '已在黑名单(点击移除)' : '加入黑名单'; } } // 黑名单筛选 if (noBlacklistGames && isInBlacklist(appId)) { element.parentElement.parentElement.style.display = 'none'; return; } // 慈善包筛选 if (noBundledGames && bundledAppsData) { const bundleCount = bundledAppsData[appId.toString()]; if (bundleCount && bundleCount > 0) { element.parentElement.parentElement.style.display = 'none'; return; } } // 下架游戏筛选逻辑 if (onlyDelistedGames) { const delistedType = getDelistedType(); if (!delistedType || !delistedTypes.includes(delistedType)) { // 不是下架游戏,或者不在选中的下架类型中,隐藏 element.parentElement.parentElement.style.display = 'none'; return; } } if (noDlc) { if (isDLC(appId)) { element.parentElement.parentElement.style.display = 'none'; } } if (isAppOwned(appId)) { if (noownedGames) { element.parentElement.parentElement.style.display = 'none'; } else { gameNameElement.style.color = ownedColor; } } else if (isAppShared(appId)) { gameNameElement.style.color = familygameColor; } else if (isAppinwishlist(appId)) { gameNameElement.style.color = wishlistColor; } else { gameNameElement.style.color = unownedColor; } if (localStorage.getItem('IsProfileFeatureLimited')) { const existingDiscountDiv = element.parentElement.querySelector( '.ProfileFeaturesLimited' ); if (existingDiscountDiv) { existingDiscountDiv.remove(); } // 恢复使用 limitedApps 列表判断(不在列表中=受限) if (isNotLimited(appId)) { if (noRestrictedGames) { element.parentElement.parentElement.style.display = 'none'; } else { const discountDiv = document.createElement('div'); discountDiv.className = 'ProfileFeaturesLimited'; discountDiv.textContent = '资料受限'; element.parentElement.appendChild(discountDiv); } } } } } //加载存档 function load() { var previousSave = GM_getValue('Saves'); if (previousSave !== undefined) { Saves = GM_getValue('Saves'); } else { GM_setValue('Saves', Saves); } var previousLimitedApps = GM_getValue('limitedApps'); if (previousLimitedApps !== undefined) { limitedApps = GM_getValue('limitedApps'); } else { getLimitedGamesList(); } var previousNoGameList = GM_getValue('NoGameList'); if (previousNoGameList !== undefined) { noGameList = GM_getValue('NoGameList'); } else { getNogameList(); } var previousDelistedGamesData = GM_getValue('DelistedGamesData'); if (previousDelistedGamesData !== undefined && previousDelistedGamesData !== null && previousDelistedGamesData.removed_apps) { delistedGamesData = GM_getValue('DelistedGamesData'); console.log('[Better Steampy] 从缓存加载下架游戏数据,共', Object.keys(delistedGamesData.removed_apps).length, '个'); } else { console.log('[Better Steampy] 缓存无效,重新获取下架游戏数据'); getDelistedGamesList(); } // 加载黑名单 var previousBlacklist = GM_getValue('Blacklist'); if (previousBlacklist !== undefined && Array.isArray(previousBlacklist)) { blacklist = previousBlacklist; console.log('[Better Steampy] 从缓存加载黑名单,共', blacklist.length, '个'); } // 加载慈善包数据 var previousBundledAppsData = GM_getValue('BundledAppsData'); if (previousBundledAppsData !== undefined && previousBundledAppsData !== null && typeof previousBundledAppsData === 'object') { bundledAppsData = previousBundledAppsData; console.log('[Better Steampy] 从缓存加载慈善包数据,共', Object.keys(bundledAppsData).length, '个'); } else { getBundledGamesList(); } //自动更新 if (new Date().getTime() - Saves.lastupdatetime > 86400000) { iview.Notice.info({ title: '存档自动更新中', }); getOwnAndWish(); if (JSON.parse(localStorage.getItem('isInfamily'))) { getFamilyGame(); } getLimitedGamesList(); getNogameList(); getDelistedGamesList(); getBundledGamesList(); } } //监听页面变化 function observePageChanges() { const config = { childList: true, subtree: true, attributes: true, attributeFilter: ['data-src', 'class', 'title'], // 添加 class 和 title 监听,捕获 SWI 插件的标记 }; let hasExecuted = false; let pageChangeTimer = null; // 用于检测页面数据加载完成 let paginationListenerAdded = false; // 是否已添加分页监听 const callback = function (mutationsList, observer) { let hasGameIconChange = false; let hasSWIMarkChange = false; // 检测 SWI 插件标记变化 for (let mutation of mutationsList) { if ( mutation.type === 'attributes' && mutation.attributeName === 'data-src' ) { const targetElement = mutation.target; if (targetElement.classList.contains('cdkGameIcon')) { cdkeyGameChecker(targetElement); hasGameIconChange = true; } } // 检测 SWI 插件添加的标记(星星、垃圾桶图标) if (mutation.type === 'childList') { for (let node of mutation.addedNodes) { if (node.nodeType === 1) { // 元素节点 // 检测是否是 SWI 插件添加的图标 if (node.classList && (node.classList.contains('swi') || node.querySelector && node.querySelector('.swi'))) { hasSWIMarkChange = true; break; } } } } if (!hasExecuted && mutation.type === 'childList') { const balanceTitleElement = document.querySelector( '.balanceTitle > div' ); if (balanceTitleElement) { init(); hasExecuted = true; } } } // 添加分页按钮点击监听(只添加一次) if (!paginationListenerAdded) { const pagination = document.querySelector('.ivu-page'); if (pagination) { pagination.addEventListener('click', function(e) { // 检测是否点击了分页按钮(不是由脚本触发的自动翻页) if (!isAutoSkipping) { resetAutoSkipState(); } }); paginationListenerAdded = true; } } // 如果有游戏图标变化,说明页面数据在加载 if (hasGameIconChange) { if (pageChangeTimer) { clearTimeout(pageChangeTimer); } // 等待游戏数据加载完成后检测 pageChangeTimer = setTimeout(() => { // 只有在启用了过滤选项时才检测自动跳页 if (noownedGames || noRestrictedGames || noDlc || onlyDelistedGames || noBlacklistGames || noBundledGames) { checkAndAutoSkip(); } }, 800); // 减少等待时间,因为不需要等待 SWI 插件渲染 } // 如果检测到 SWI 插件标记变化,重新检查所有游戏 if (hasSWIMarkChange) { if (pageChangeTimer) { clearTimeout(pageChangeTimer); } pageChangeTimer = setTimeout(() => { const elements = document.querySelectorAll('.cdkGameIcon'); elements.forEach((element) => { cdkeyGameChecker(element); }); // 检测是否需要自动跳页 if (noownedGames || noRestrictedGames || noDlc || onlyDelistedGames || noBlacklistGames || noBundledGames) { checkAndAutoSkip(); } }, 500); // SWI 标记已经渲染,可以快速检测 } }; const observer = new MutationObserver(callback); observer.observe(document.body, config); } //CSS样式 const style = document.createElement('style'); style.innerHTML = ` .ProfileFeaturesLimited { height: .26rem; background: #ed4014; position: absolute; top: 4px; left: 34px; color: #fff; text-align: center; line-height: .26rem; font-size: .11rem; padding: 0 6px; border-radius: 3px; white-space: nowrap; z-index: 99; } #filter > .ivu-checkbox-group > .ivu-checkbox-wrapper { margin-right: 0 !important; font-size: 0; } #filter > .ivu-checkbox-group > div > .ivu-checkbox-wrapper { margin-right: 0 !important; font-size: 0; } #filter > .ivu-checkbox-group > .ivu-checkbox-wrapper > .ivu-checkbox, #filter > .ivu-checkbox-group > div > .ivu-checkbox-wrapper > .ivu-checkbox { display: none !important; } #filter > .ivu-checkbox-group > .ivu-checkbox-wrapper > .ivu-checkbox + span, #filter > .ivu-checkbox-group > div > .ivu-checkbox-wrapper > .ivu-checkbox + span { padding: 0 !important; } #filter .ivu-tag-default { border: 1px solid #b8becc !important; color: #515a6e !important; background: #f8f8f9 !important; } #filter .ivu-tag-default:hover { border-color: #2d8cf0 !important; color: #2d8cf0 !important; } #filter .ivu-tag-primary { box-shadow: 0 1px 4px rgba(45,140,240,0.3); } #filter .ivu-tag-warning { box-shadow: 0 1px 4px rgba(255,153,0,0.3); } .bsp-cp-btn { cursor: pointer; padding: 4px 10px; border: 1px solid #dcdee2; border-radius: 4px; font-size: 13px; transition: all 0.15s; } .bsp-cp-btn:hover { border-color: #2d8cf0; color: #2d8cf0; } .bsp-cp-active { background: #2d8cf0 !important; color: #fff !important; border-color: #2d8cf0 !important; } `; document.head.appendChild(style); //默认颜色 if (!localStorage.getItem('ownedColor')) { localStorage.setItem('ownedColor', '#0c8918'); localStorage.setItem('wishlistColor', '#177cb0'); localStorage.setItem('familygameColor', '#ff8936'); localStorage.setItem('unownedColor', '#ff2e63'); } var ownedColor = localStorage.getItem('ownedColor'); var wishlistColor = localStorage.getItem('wishlistColor'); var familygameColor = localStorage.getItem('familygameColor'); var unownedColor = localStorage.getItem('unownedColor');