您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Redirects removed games from the Steam store to SteamCommunity or SteamDB.
当前为
// ==UserScript== // @name Steam Store Redirector // @namespace https://rafaelgssa.gitlab.io/monkey-scripts // @version 4.1.3 // @author rafaelgssa // @description Redirects removed games from the Steam store to SteamCommunity or SteamDB. // @match *://*/* // @require https://greasemonkey.github.io/gm4-polyfill/gm4-polyfill.js // @require https://greasyfork.org/scripts/405802-monkey-dom/code/Monkey%20DOM.js?version=820314 // @require https://greasyfork.org/scripts/405831-monkey-storage/code/Monkey%20Storage.js?version=820315 // @require https://greasyfork.org/scripts/405813-monkey-utils/code/Monkey%20Utils.js?version=820304 // @require https://greasyfork.org/scripts/405840-monkey-wizard/code/Monkey%20Wizard.js?version=820318 // @run-at document-start // @grant GM.info // @grant GM.setValue // @grant GM.getValue // @grant GM.deleteValue // @grant GM_info // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @noframes // ==/UserScript== /* global DOM, PersistentStorage, SettingsWizard */ /** * @typedef {DestinationMap[keyof DestinationMap]} Destination * * @typedef {Object} DestinationMap * @property {'0'} STEAM_COMMUNITY * @property {'1'} STEAM_DB * * @typedef {'app' | 'sub'} SteamType */ (async () => { 'use strict'; const scriptId = 'ssr'; const scriptName = GM.info.script.name; /** @type {DestinationMap} */ const DESTINATIONS = { STEAM_COMMUNITY: '0', STEAM_DB: '1', }; /** @type {WizardSchema[]} */ const schemas = [ { type: 'multi', id: 'destination', message: 'Where do you want to be redirected to?', defaultValue: DESTINATIONS.STEAM_COMMUNITY, choices: [ { id: '0', template: '"%" for SteamCommunity', value: DESTINATIONS.STEAM_COMMUNITY, }, { id: '1', template: '"%" for SteamDB', value: DESTINATIONS.STEAM_DB, }, ], }, ]; /** @type {MutationObserver} */ let observer; /** * Loads the script. * @returns {Promise<void>} */ const load = async () => { const matches = window.location.href.match( /^https:\/\/store\.steampowered\.com\/#(app|sub)_(\d+)/ ); if (matches) { const [, type, id] = matches; await redirectGame(/** @type {SteamType} */ (type), id); } else { removePageUrlFragment(); checkPageLoaded(); } }; /** * Redirects a game to the appropriate page. * @param {SteamType} type The Steam type of the game. * @param {string} id The Steam ID of the game. * @returns {Promise<void>} */ const redirectGame = async (type, id) => { const destination = /** @type {Destination} */ (await PersistentStorage.getSetting( 'destination' )); const url = getDestinationUrl(destination); window.location.href = `${url}/${type}/${id}`; }; /** * Returns the URL for a destination. * @param {Destination} destination The destination. * @returns {string} The URL for the destination. */ const getDestinationUrl = (destination) => { /** @type {{ [K in Destination]: string }} */ const urls = { [DESTINATIONS.STEAM_COMMUNITY]: 'https://steamcommunity.com', [DESTINATIONS.STEAM_DB]: 'https://steamdb.info', }; return urls[destination]; }; /** * Removes the fragment from the page URL. */ const removePageUrlFragment = () => { if ( window.location.hostname === 'store.steampowered.com' && (window.location.hash.includes('#app_') || window.location.hash.includes('#sub_')) ) { window.history.replaceState( '', document.title, `${window.location.origin}${window.location.pathname}${window.location.search}` ); } }; /** * Checks if the page is fully loaded. */ const checkPageLoaded = () => { document.removeEventListener('pjax:end', checkPageLoaded); document.removeEventListener('turbolinks:load', checkPageLoaded); if (document.readyState === 'loading') { document.addEventListener('DOMContentLoaded', onPageLoad); } else { onPageLoad(); } }; /** * Triggered when the page is fully loaded. */ const onPageLoad = () => { document.removeEventListener('DOMContentLoaded', onPageLoad); addUrlFragments(document.body); if (observer) { observer.disconnect(); } observer = DOM.observeNode(document.body, null, /** @type {NodeCallback} */ (addUrlFragments)); document.addEventListener('pjax:end', checkPageLoaded); document.addEventListener('turbolinks:load', checkPageLoaded); }; /** * Adds the URL fragments to links in a context. * @param {Element} context The context where to add the fragments. */ const addUrlFragments = (context) => { if (!(context instanceof Element)) { return; } let wasAdded = false; const selectors = [ '[href*="store.steampowered.com/app/"]', '[href*="store.steampowered.com/sub/"]', ].join(', '); if (context.matches(selectors)) { wasAdded = addUrlFragment(/** @type {HTMLAnchorElement} */ (context)); } else { /** @type {NodeListOf<HTMLAnchorElement>} */ const elements = context.querySelectorAll(selectors); wasAdded = Array.from(elements).filter(addUrlFragment).length > 0; } if (wasAdded && context === document.body) { // Keep adding until there are no more links without the fragments. window.setTimeout(addUrlFragments, 1000, context); } }; /** * Adds the URL fragment to a link, if not already exists. * @param {HTMLAnchorElement} link The link where to add the fragment. * @returns {boolean} Whether the fragment was added or not. */ const addUrlFragment = (link) => { const url = link.getAttribute('href'); let fragment = link.dataset[scriptId]; if (!url || (fragment && url.includes(fragment))) { return false; } const matches = url.match(/(app|sub)\/(\d+)/); if (!matches) { return false; } const [, type, id] = matches; fragment = `#${type}_${id}`; link.href = `${url.replace(/#.*/, '')}${fragment}`; link.dataset[scriptId] = fragment; return true; }; try { await PersistentStorage.init(scriptId, { settings: { destination: DESTINATIONS.STEAM_COMMUNITY, }, }); await SettingsWizard.init(scriptId, scriptName, schemas); await load(); } catch (err) { console.log(`Failed to load ${scriptName}: `, err); } })();