Better SteamPYPlus

Better SteamPY基础上增加了绝版游戏筛选、自动翻页功能

スクリプトをインストールするには、Tampermonkey, GreasemonkeyViolentmonkey のような拡張機能のインストールが必要です。

You will need to install an extension such as Tampermonkey to install this script.

スクリプトをインストールするには、TampermonkeyViolentmonkey のような拡張機能のインストールが必要です。

スクリプトをインストールするには、TampermonkeyUserscripts のような拡張機能のインストールが必要です。

このスクリプトをインストールするには、Tampermonkeyなどの拡張機能をインストールする必要があります。

このスクリプトをインストールするには、ユーザースクリプト管理ツールの拡張機能をインストールする必要があります。

(ユーザースクリプト管理ツールは設定済みなのでインストール!)

このスタイルをインストールするには、Stylusなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus などの拡張機能をインストールする必要があります。

このスタイルをインストールするには、Stylus tなどの拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

このスタイルをインストールするには、ユーザースタイル管理用の拡張機能をインストールする必要があります。

(ユーザースタイル管理ツールは設定済みなのでインストール!)

このスクリプトの質問や評価の投稿はこちら通報はこちらへお寄せください
// ==UserScript==
// @name         Better SteamPYPlus
// @namespace    https://space.bilibili.com/93654843
// @version      1.0.1
// @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
// @run-at       document-body
// ==/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; // 不显示黑名单游戏


(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;

  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'));
        }
      });
    }
  });
}

//初始化脚本配置菜单
function init() {
  const settings = document.createElement('div');
  settings.innerHTML = `
      <div id="settings" class="ml-20-rem">
      <div class="withdraw" @click="modal=true, updateValues()">脚本设置</div>
        <Modal v-model="modal">
          <br />
          <Card>
            <template #title><h2>拥有状态标记</h2></template>
            <Alert type="warning" show-icon>暂时不支持捆绑包标记</Alert>
            <p>
              上次更新于
              <i-time :time="lastUpdateTime" :interval="1"></i-time>
              (每24小时执行一次自动更新)
            </p>
            <p>已加载 {{ownedApps}} 个库存游戏及DLC</p>
            <p>已加载 {{wishlist}} 个愿望单游戏</p>
            <p>已加载 {{familygameList}} 个家庭库游戏</p>
            <div>
              是否加入了家庭组:<i-Switch
                v-model="isInFamilyGroup"
                @on-change="isInFamilyGroup_change"
              />
            </div>
            <br />
            <button-group size="large" shape="circle">
              <i-Button @click="reloadSaves" :loading="refershSaves_loading"
                >重载存档</i-Button
              >
              <i-Button @click="clearSaves">清除存档</i-Button>
            </button-group>
          </Card>
          <Card>
            <template #title><h2>个人资料功能受限标记</h2></template>
            <Alert show-icon
              >数据来源于https://github.com/F1NN1ER/getSteamRestrictedGameLIst</Alert
            >
            <Alert show-icon>数据每日更新,可能尚有部分未及时标记</Alert>
            <div>
              是否启用受限游戏标注:<i-Switch
                v-model="checkIsProfileFeatureLimited"
                @on-change="checkIsProfileFeatureLimited_change"
              />
            </div>
            <p>目前共加载{{limitedApps}}个非受限游戏(跟随拥有状态自动更新)</p>
            <i-Button
              @click="reloadLimitedSaves"
              :loading="reloadLimitedSaves_loading"
              >刷新</i-Button
            >
          </Card>
          <Card>
            <template #title><h2>标记颜色设置</h2></template>
            <div>
              已拥有
              <Color-Picker
                v-model="ownedAppsColor"
                size="small"
                :colors="defaultcolors"
                @on-change="ownedAppsColor_change"
              />
            </div>
            <div>
              在愿望单中
              <Color-Picker
                v-model="wishlistColor"
                size="small"
                :colors="defaultcolors"
                @on-change="wishlistColor_change"
              />
            </div>
            <div>
              在家庭库中
              <Color-Picker
                v-model="familygameColor"
                size="small"
                :colors="defaultcolors"
                @on-change="familygameColor_change"
              />
            </div>
            <div>
              未拥有
              <Color-Picker
                v-model="unownedColor"
                size="small"
                :colors="defaultcolors"
                @on-change="unownedColor_change"
              />
            </div>
          </Card>
          <Card>
            <template #title><h2>网页优化</h2></template>
            <div>
              是否关闭网页右下方推广侧栏:<i-Switch
                v-model="isSuspensionOff"
                @on-change="isSuspensionOff_change"
              />
            </div>
          </Card>
          <Card>
            <template #title><h2>黑名单管理</h2></template>
            <Alert show-icon>点击游戏卡片上的 🚫 按钮可将游戏加入黑名单,在筛选中勾选"不显示黑名单游戏"即可隐藏</Alert>
            <p>当前黑名单共 {{blacklistCount}} 个游戏</p>
            <div v-if="blacklistItems.length > 0" style="max-height:300px;overflow-y:auto;margin:8px 0;">
              <div v-for="item in blacklistItems" :key="item.appId" style="display:flex;align-items:center;justify-content:space-between;padding:6px 8px;border-bottom:1px solid #f0f0f0;">
                <span>
                  <a :href="'https://store.steampowered.com/app/' + item.appId" target="_blank" style="color:#2d8cf0;">{{item.name}}</a>
                  <span style="color:#999;font-size:12px;margin-left:6px;">({{item.appId}})</span>
                </span>
                <i-Button size="small" type="error" ghost @click="removeBlacklistItem(item.appId)">移除</i-Button>
              </div>
            </div>
            <div v-else style="color:#999;padding:12px 0;">黑名单为空</div>
            <i-Button type="error" size="small" @click="clearBlacklist" v-if="blacklistItems.length > 0" style="margin-top:8px;">清空黑名单</i-Button>
          </Card>
        </Modal>
      </div>
  `;
  const filter = document.createElement('div');
  filter.innerHTML = `
  <div id="filter" class="ml-20-rem">
        <div id="filter" style="position: relative; display: flex; align-items: center; flex-wrap: wrap; gap: 6px;">
          <span style="color:#515a6e;font-size:13px;margin-right:4px;font-weight:500;">筛选:</span>
          <Checkbox-Group v-model="filter" @on-change="filterChange" style="display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px;">
            <Checkbox label="noOwnedGames"><Tag :color="filter.includes('noOwnedGames')?'primary':'default'" size="medium" style="cursor:pointer;margin:0;font-size:13px;padding:0 12px;height:28px;line-height:28px;">隐藏已拥有</Tag></Checkbox>
            <Checkbox label="noRestrictedGames"><Tag :color="filter.includes('noRestrictedGames')?'primary':'default'" size="medium" style="cursor:pointer;margin:0;font-size:13px;padding:0 12px;height:28px;line-height:28px;">隐藏受限</Tag></Checkbox>
            <Checkbox label="noDlc"><Tag :color="filter.includes('noDlc')?'primary':'default'" size="medium" style="cursor:pointer;margin:0;font-size:13px;padding:0 12px;height:28px;line-height:28px;">隐藏DLC</Tag></Checkbox>
            <Checkbox label="noBlacklistGames"><Tag :color="filter.includes('noBlacklistGames')?'primary':'default'" size="medium" style="cursor:pointer;margin:0;font-size:13px;padding:0 12px;height:28px;line-height:28px;">隐藏黑名单</Tag></Checkbox>
            <div style="display: inline-flex; align-items: center; position: relative;">
              <Checkbox label="onlyDelistedGames"><Tag :color="filter.includes('onlyDelistedGames')?'warning':'default'" size="medium" style="cursor:pointer;margin:0;font-size:13px;padding:0 12px;height:28px;line-height:28px;">仅下架</Tag></Checkbox>
              <span
                v-if="showDelistedTypes"
                @click.stop.prevent="toggleDelistedTypesPanel"
                style="cursor:pointer;padding:0 4px;transition:transform 0.2s;display:inline-block;user-select:none;font-size:10px;color:#808695;"
                :style="{ transform: delistedTypesPanelExpanded ? 'rotate(90deg)' : 'rotate(0deg)' }">▼</span>
              <div v-if="showDelistedTypes && delistedTypesPanelExpanded"
                   style="position:absolute;left:100%;top:-4px;margin-left:6px;background:#fff;border:1px solid #dcdee2;border-radius:4px;padding:6px 10px;box-shadow:0 2px 8px rgba(0,0,0,0.15);z-index:1000;min-width:160px;">
                <Checkbox-Group v-model="delistedTypes" @on-change="delistedTypesChange" style="display:flex;flex-direction:column;gap:4px;font-size:12px;">
                  <Checkbox label="purchase disabled" style="margin:0;">Purchase Disabled</Checkbox>
                  <Checkbox label="delisted" style="margin:0;">Delisted</Checkbox>
                  <Checkbox label="f2p" style="margin:0;">F2P</Checkbox>
                  <Checkbox label="unreleased" style="margin:0;">Unreleased</Checkbox>
                  <Checkbox label="test app" style="margin:0;">Test App</Checkbox>
                  <Checkbox label="retail only" style="margin:0;">Retail Only</Checkbox>
                  <Checkbox label="pre-order exclusive" style="margin:0;">Pre-order</Checkbox>
                  <Checkbox label="banned" style="margin:0;">Banned</Checkbox>
                </Checkbox-Group>
              </div>
            </div>
          </Checkbox-Group>
      `;
  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');
        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 (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, '个');
  }
  //自动更新
  if (new Date().getTime() - Saves.lastupdatetime > 86400000) {
    iview.Notice.info({
      title: '存档自动更新中',
    });
    getOwnAndWish();
    if (JSON.parse(localStorage.getItem('isInfamily'))) {
      getFamilyGame();
    }
    getLimitedGamesList();
    getNogameList();
    getDelistedGamesList();
  }
}

//监听页面变化
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) {
          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) {
          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');