Better SteamPY

提供Steampy界面美化,功能增强,如库中已有游戏标记(支持家庭库及愿望单)、标记资料受限游戏等功能

当前为 2024-10-16 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Better SteamPY
// @namespace    https://space.bilibili.com/93654843
// @version      20241016
// @description  提供Steampy界面美化,功能增强,如库中已有游戏标记(支持家庭库及愿望单)、标记资料受限游戏等功能
// @author       FiNNiER
// @match        *://steampy.com/*
// @icon         https://steampy.com/img/logo.63413a4f.png
// @grant        GM_xmlhttpRequest
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_addStyle
// @grant        GM_getResourceText
// @resource     listdata https://gitee.com/Finnier/getSteamAppListWithType/raw/main/data/Listwithnogame.json
// @connect      gitee.com
// @connect      api.steampowered.com
// @connect      store.steampowered.com
// @run-at       document-body
// ==/UserScript==
var Saves = {
  wishlist: [],
  ownedApps: [],
  familygameList: [],
  lastupdatetime: 0,
};
var limitedApps = [];
var noDlc = false;
var noownedGames = false;
var noRestrictedGames = false;
const listDataText = GM_getResourceText('listdata');
const listData = JSON.parse(listDataText);

(function () {
  'use strict';
  load();
  observePageChanges();
})();

//读取个人库存及愿望单并储存
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} 个非受限游戏`,
        });
        console.log(GM_getValue('limitedApps'));
        resolve(limitedGames);
      },
    });
  });
}

//获取游戏类型
iview.Notice.success({
  title: `Better Steampy`,
  desc: `已加载 ${Object.keys(listData).length} 个DLC及原声带`,
});
function isAppIdInList(appid) {
  return listData.hasOwnProperty(appid);
}

//初始化脚本配置菜单
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>
        </Modal>
      </div>
  `;
  const filter = document.createElement('div');
  filter.innerHTML = `
  <div id="filter" class="ml-20-rem">
  <Space direction="vertical" size="large">
        <div id="filter">
          <Checkbox-Group v-model="filter"  @on-change="filterChange">
            <Checkbox label="noOwnedGames" border>不显示已拥有游戏</Checkbox>
            <Checkbox label="noRestrictedGames" border >不显示资料受限游戏</Checkbox>
            <Checkbox label="noDlc" border>不显示DLC及原声带</Checkbox>
          </Checkbox-Group>
    </Space>
      `;
  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'],
      };
    },
    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;
      },
      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);
          });
        } 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: '重载完毕',
        });
      },
      async reloadLimitedSaves() {
        this.$Notice.info({
          title: '正在加载受限游戏列表',
        });
        this.reloadLimitedSaves_loading = true;
        await getLimitedGamesList();
        limitedApps = GM_getValue('limitedApps');
        this.updateValues();
        this.reloadLimitedSaves_loading = false;
        this.$Notice.success({
          title: '加载完毕',
        });
      },
      clearSaves() {
        this.$Notice.info({
          title: '存档已清除',
        });
        let nullSaves = {
          wishlist: [],
          ownedApps: [],
          familygameList: [],
          lastupdatetime: 0,
        };
        Saves = nullSaves;
        GM_setValue('Saves', nullSaves);
        this.updateValues();
      },
    },
  });
  new Vue({
    el: '#filter',
    data() {
      return {
        filter: [],
      };
    },
    methods: {
      filterChange() {
        noownedGames = this.filter.includes('noOwnedGames');
        noRestrictedGames = this.filter.includes('noRestrictedGames');
        noDlc = this.filter.includes('noDlc');
        const elements = document.querySelectorAll('.cdkGameIcon');
        elements.forEach((element) => {
          cdkeyGameChecker(element);
        });
      },
    },
  });
  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 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');
  if (appId != 0) {
    element.parentElement.parentElement.style.display = 'block';
    if (noDlc) {
      if (isAppIdInList(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();
      }
      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();
  }
  //自动更新
  if (new Date().getTime() - Saves.lastupdatetime > 86400000) {
    iview.Notice.info({
      title: '存档自动更新中',
    });
    getOwnAndWish();
    if (JSON.parse(localStorage.getItem('isInfamily'))) {
      getFamilyGame();
    }
    getLimitedGamesList();
  }
}

//监听页面变化
function observePageChanges() {
  const config = {
    childList: true,
    subtree: true,
    attributes: true,
    attributeFilter: ['data-src'],
  };
  let hasExecuted = false;
  const callback = function (mutationsList, observer) {
    for (let mutation of mutationsList) {
      if (
        mutation.type === 'attributes' &&
        mutation.attributeName === 'data-src'
      ) {
        const targetElement = mutation.target;
        if (targetElement.classList.contains('cdkGameIcon')) {
          cdkeyGameChecker(targetElement);
        }
      }

      if (!hasExecuted && mutation.type === 'childList') {
        const balanceTitleElement = document.querySelector(
          '.balanceTitle > div'
        );
        if (balanceTitleElement) {
          init();
          hasExecuted = true;
        }
      }
    }
  };

  const observer = new MutationObserver(callback);
  observer.observe(document.body, config);
}
//CSS样式
const style = document.createElement('style');
style.innerHTML = `
    .ProfileFeaturesLimited {
    width: .65rem;
    height: .3rem;
    background: #ed4014;
    position: absolute;
    top: 0;
    color: #fff;
    text-align: center;
    line-height: .3rem;
    font-size: .12rem;
}
`;
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');

//Todo list:
//夜间模式
//侧栏收放
//中键快捷控制标签页
//左右翻页