您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Displays tag categories of games on itch.io
// ==UserScript== // @name Itchio Show Categories // @version 0.0.3 // @author Dillon Regimbal // @namespace https://dillonr.com // @description Displays tag categories of games on itch.io // @match *://itch.io/* // @match *://*.itch.io/* // @run-at document-idle // @grant GM_addStyle // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @icon https://itch.io/favicon.ico // @noframes // ==/UserScript== // Since 2020-06-12 // https://greasyfork.org/en/users/420789-dillon-regimbal // https://greasyfork.org/en/scripts/405228-itchio-show-categories // https://github.com/dregimbal/UserScripts/blob/master/ItchioShowCategories.user.js (function () { 'use strict' if (window !== window.parent) { // https://developer.mozilla.org/en-US/docs/Web/API/Window/parent // Don't run inside of a frame } // #region Config and Variables let btn_category_class = 'dr_category_button' let btn_category_id = 'dr_markCategory' let btn_category_text = 'Checked 0/0' let game_store_link_selector = '.game_cell_data a.game_link, .bundle_game_grid_widget .game_cell a.title' let category_text_class = 'dr_category_text' let meta_tag_class = 'meta_tag' let game_cell_class = 'game_cell' let game_cell_data_class = 'game_cell_data' let categoryMap = [ { container: '#wrapper', categories: [ { title: 'Co-op', searchStrings: ['co-op', ' coop '] } ] }, { container: '.game_info_panel_widget', categories: [ { title: 'Local Multiplayer', searchStrings: ['Local Multiplayer'] }, { title: 'Networked', searchStrings: ['Networked Multiplayer'] }, { title: 'Controller', searchStrings: ['Gamepad', 'Xbox Controller', 'Joystick', 'Playstation Controller', 'Joy-Con', 'Wiimote'] }, { title: 'Phone Control', searchStrings: ['Smartphone'] }, { title: 'Physical', searchStrings: ['Physical Game', 'Tabletop'] } ] } ] let gamesToCheck = new Map() // #endregion // #region Create button and styles let divButton = document.createElement('div') divButton.classList.add(btn_category_class) divButton.id = btn_category_id let eleA = document.createElement('a') eleA.setAttribute('onclick', 'return false;') eleA.textContent = btn_category_text divButton.appendChild(eleA) GM_addStyle(` .${btn_category_class} { border-radius: 2px; border: medium none; padding: 10px; display: inline-block; cursor: pointer; background: #67C1F5 none repeat scroll 0% 0%; width: 120px; text-align: center; } .${btn_category_class} a { text-decoration: none !important; color: #FFF !important; padding: 0px 2px; } .${btn_category_class}:hover a { color: #0079BF !important; } .${btn_category_class}, .${btn_category_class} a { font-family: Verdana; font-size: 12px; line-height: 16px; } .${game_cell_class} .${game_cell_data_class} a.${category_text_class}.${meta_tag_class}, .${game_cell_class} a.${category_text_class}.${meta_tag_class} { padding: 3px; margin: 2px; font-size: 14px; color: #ffffff; background-color: #17199d; } #${btn_category_id} { position: fixed; right: 20px; bottom: 65px; z-index: 33; } .scrolling_outer { height: auto !important; } `) // #endregion function queueCheckingGames() { let storePageLinkElements = document.querySelectorAll(game_store_link_selector) for (let storelink of storePageLinkElements) { // Don't search bundle pages for game details if (!storelink.href.includes('/b/')) { if (!gamesToCheck.has(storelink.href)) { // New link gamesToCheck.set(storelink.href, { link: storelink.href, elements: new Set([storelink.parentElement]), checked: false, categories: new Set() }) } else { // Existing link gamesToCheck.get(storelink.href).elements.add(storelink.parentElement) } // Update the count on the button eleA.innerText = getNumberOfCheckedGames() } } return } async function checkGameLinks() { for (let game of gamesToCheck.values()) { await fetchGameCategories(game.link) .then(() => { for (let element of game.elements) { let nextSibling = element.nextElementSibling if (nextSibling !== null && nextSibling.classList.contains(category_text_class)) { // console.log('Categories already added') } else { for (let category of game.categories) { addCategoryText(element, category) } } } }) eleA.innerText = getNumberOfCheckedGames() } return } function getNumberOfCheckedGames() { return `Checked ${Array.from(gamesToCheck.values()).reduce((acc, game) => { if (game.checked) { // eslint-disable-next-line no-param-reassign acc++ } return acc }, 0)}/${gamesToCheck.size}` } /** * Checks a page for game categories * @param {string} storePageUrl The store page that contains the categories * @returns {Promise} The categories of the game */ function fetchGameCategories(storePageUrl) { return new Promise((resolve, reject) => { let game = gamesToCheck.get(storePageUrl) if (game.checked) { resolve(game.categories) } else { GM_xmlhttpRequest({ method: 'GET', url: game.link, onload: function (response) { console.assert(response.status === 200, [ response.status, response.statusText, response.readyState, response.responseHeaders, response.responseText, response.finalUrl ].join(' - ')) let parser = new DOMParser() let storePage = parser.parseFromString(response.responseText, 'text/html') let validCategories = new Set() for (let scope of categoryMap) { let scopedElements = storePage.querySelectorAll(scope.container) console.assert(scopedElements.length > 0, `No elements matching "${scope.container}" found on ${game.link}`) for (let scopedElement of scopedElements) { for (let category of scope.categories) { for (let searchString of category.searchStrings) { if (scopedElement.textContent.toLowerCase().includes(searchString.toLowerCase())) { validCategories.add(category.title) } } } } } game.checked = true game.categories = validCategories resolve(validCategories) } }) } }) } /** * @description Add text after an element * @param {HTMLElement} element the element to add the text to * @param {string} text the contents of the text * @returns {undefined} */ function addCategoryText(element, text) { if (typeof element !== 'undefined' && element !== null) { let categoryText = document.createElement('a') categoryText.classList.add(meta_tag_class) categoryText.classList.add(category_text_class) categoryText.innerText = text element.parentNode.insertBefore(categoryText, element.nextSibling) // console.log(`Adding ${text}`) } else { console.log(`Element null, cannot add: ${text}`) } return } let url = document.documentURI let checking = false if (url.includes('/my-collections') || url.includes('/my-purchases') || url.includes('/games') || url.includes('/s/') || url.includes('/c/') || url.includes('/b/')) { divButton.addEventListener('click', async () => { if (!checking) { checking = true queueCheckingGames() await checkGameLinks() checking = false } else { console.log('Wait a second, eh') } }) document.body.appendChild(divButton) queueCheckingGames() } }())