您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Shows if an Itch.io link has been claimed or not
当前为
// ==UserScript== // @name Itch.io Web Integration // @namespace Lex@GreasyFork // @match *://*/* // @grant GM_xmlhttpRequest // @grant GM_getValue // @grant GM_setValue // @version 0.1.8.1 // @author Lex // @description Shows if an Itch.io link has been claimed or not // @connect itch.io // ==/UserScript== (function(){ 'use strict'; const CACHE_VERSION_KEY = "CacheVersion"; const INVALIDATION_TIME = 5*60*60*1000; // 5 hour cache time const ITCH_GAME_CACHE_KEY = 'ItchGameCache'; var ItchGameCache; // Promise wrapper for GM_xmlhttpRequest const Request = details => new Promise((resolve, reject) => { details.onerror = details.ontimeout = reject; details.onload = resolve; GM_xmlhttpRequest(details); }); function versionCacheInvalidator() { const sVersion = v => { if (typeof v !== 'string' || !v.match(/\d+\.\d+/)) return 0; return parseFloat(v.match(/\d+\.\d+/)[0]); } const prev = sVersion(GM_getValue(CACHE_VERSION_KEY, '0.0')); if (prev < 0.1) { console.log(`${GM_info.script.version} > ${prev}`); console.log(`New minor version of ${GM_info.script.name} detected. Invalidating cache.`) _clearItchCache(); } GM_setValue(CACHE_VERSION_KEY, GM_info.script.version); } function _clearItchCache() { ItchGameCache = {}; _saveItchCache(); } function loadItchCache() { ItchGameCache = JSON.parse(GM_getValue(ITCH_GAME_CACHE_KEY, '{}')); } function _saveItchCache() { if (ItchGameCache === undefined) return; GM_setValue(ITCH_GAME_CACHE_KEY, JSON.stringify(ItchGameCache)); } function setItchGameCache(key, game) { loadItchCache(); // refresh our cache in case another tab has edited it ItchGameCache[key] = game; _saveItchCache(); } function deleteItchGameCache(key) { if (key === undefined) return; loadItchCache(); delete ItchGameCache[key]; _saveItchCache(); } function getItchGameCache(link) { if (!ItchGameCache) loadItchCache(); if (Object.prototype.hasOwnProperty.call(ItchGameCache, link)) { return ItchGameCache[link]; } return null; } async function claimGame(url) { const parser = new DOMParser(); const purchase_url = url + "/purchase"; console.log("Getting purchase page: " + purchase_url); const purchase_resp = await Request({method: "GET", url: purchase_url}); const purchase_dom = parser.parseFromString(purchase_resp.responseText, 'text/html'); const download_csrf_token = purchase_dom.querySelector("form.form").csrf_token.value; const download_url_resp = await Request({ method: "POST", url: url + "/download_url", headers: { "Content-Type": "application/x-www-form-urlencoded" }, data: 'csrf_token='+encodeURIComponent(download_csrf_token) }); const downloadUrl = JSON.parse(download_url_resp.responseText).url; console.log("Received download url: " + downloadUrl); const download_resp = await Request({method: "GET", url: downloadUrl}); const dom = parser.parseFromString(download_resp.responseText, 'text/html'); const claimForm = dom.querySelector(".claim_to_download_box form"); const claim_csrf_token = claimForm.csrf_token.value; const claim_key_url = claimForm.action; console.log("Claiming game using " + claim_key_url); const claim_key_resp = await Request({ method: "POST", url: claim_key_url, headers: { "Content-Type": "application/x-www-form-urlencoded" }, data: 'csrf_token='+encodeURIComponent(claim_csrf_token) }); return /You claimed this/.test(claim_key_resp.responseText); } // Parses a DOM into a game object function parsePage(url, dom) { // Gets the inner text of an element if it can be found otherwise returns undefined const txt = query => { const e = dom.querySelector(query); return e && e.innerText.trim(); }; const game = {}; game.cachetime = (new Date()).getTime(); game.url = url; game.isOwned = dom.querySelector(".purchase_banner_inner .key_row .ownership_reason") !== null; game.isClaimable = [...dom.querySelectorAll(".buy_btn")].filter(e => e.innerText == "Download or claim").length > 0; game.isFree = [...dom.querySelectorAll("span[itemprop=price]")].filter(e => e.innerText == "$0.00 USD").length > 0; game.hasFreeDownload = [...dom.querySelectorAll("a.download_btn,a.buy_btn")].filter(e => e.innerText == "Download" || e.innerText == "Download Now").length > 0; game.original_price = txt("span.original_price"); game.price = txt("span[itemprop=price]"); game.saleRate = txt(".sale_rate"); return game; } // Sends an XHR request and parses the results into a game object async function fetchItchGame(url) { const response = await Request({method: "GET", url: url}); if (response.status != 200) { console.log(`Error ${response.status} fetching page ${url}`); return null; } const parser = new DOMParser(); const dom = parser.parseFromString(response.responseText, 'text/html'); return parsePage(url, dom); } // Loads an itch game from cache or fetches the page if needed async function getItchGame(url) { let game = getItchGameCache(url); if (game !== null) { const isExpired = (new Date()).getTime() - game.cachetime > INVALIDATION_TIME; // Expiration checking currently disabled /*if (isExpired) { game = null; }*/ } if (game === null) { game = await fetchItchGame(url); if (game !== null) setItchGameCache(url, game); } return game; } async function claimClicked(a, game) { console.log("Attempting to claim " + game.url); a.innerText += ' ⌛'; a.onclick = null; const success = await claimGame(game.url); if (success === true) { a.style.display = "none"; const ownMark = a.previousElementSibling; ownMark.innerHTML = `<span title="Successfully claimed">✔️</span>`; deleteItchGameCache(game.url); } } // Appends the isOwned tag to an anchor link function appendTags(a, game) { const div = document.createElement("div"); a.after(div); let ownMark = ''; if (game === null) { ownMark = `<span title="Status unknown. Try refreshing.">❓</span>`; } else if (game.isOwned) { ownMark = `<span title="Game is already claimed on itch.io">✔️</span>`; } else { if (!game.isClaimable) { if (game.hasFreeDownload) { ownMark = `<span title="Game is a free download but not claimable">⛔</span>`; } else if (game.price) { ownMark = `<span title="🛒 Game costs ${game.price}">🛒</span>`; } else { ownMark = `<span title="Status unknown">👽</span>`; } } else { const origPrice = game.original_price ? ` 🛒 Original price: ${game.original_price} 💸 Current Price: ${game.price}` : ''; ownMark = `<span title="Game is claimable but you haven't claimed it.${origPrice}">❌</span> <span style="padding: 2px; cursor:pointer; background:rgb(200,200,200); border-radius: 5px" class="ClaimButton">🛄 Claim Game</span>`; } } div.innerHTML = ownMark; div.style.display = "inline-block"; div.childNodes[0].style = "margin-left: 5px; background:rgb(200,200,200); padding: 2px; border-radius: 2px"; const claimBtn = div.querySelector(".ClaimButton"); if (claimBtn) { claimBtn.onclick = function(event) { claimClicked(event.target, game); }; } } function addClickHandler(a) { a.addEventListener('mouseup', event => { deleteItchGameCache(event.target.href); }); } // Handles an itch.io link on a page async function handleLink(a) { addClickHandler(a); const game = await getItchGame(a.href); appendTags(a, game); } function isGameUrl(url) { return /^https:\/\/[^.]+\.itch\.io\/[^/]+$/.test(url); } // Finds all the itch.io links on the current page function getItchLinks() { let links = [...document.querySelectorAll("a[href*='itch.io/']")]; links = links.filter(a => isGameUrl(a.href)); links = links.filter(a => !a.classList.contains("return_link")); links = links.filter(a => { const t = a.textContent.trim(); return t !== "" && t !== "GIF"; }); return links; } function handlePage() { if (isGameUrl(window.location.href)) { const game = parsePage(window.location.href, document); setItchGameCache(window.location.href, game); } const as = getItchLinks(); as.forEach(handleLink); } versionCacheInvalidator(); handlePage(); })();