Greasy Fork is available in English.
Displays a text area with game titles and keys so you can copy them out easily.
当前为
// ==UserScript==
// @name Fanatical Keys Backup
// @namespace Lex@GreasyFork
// @version 0.2.5
// @description Displays a text area with game titles and keys so you can copy them out easily.
// @author Lex
// @match https://www.fanatical.com/en/orders*
// @grant none
// ==/UserScript==
(function() {
'use strict';
// Formats games array to a string to be displayed
// Games is an array [ [title, key], ... ]
function formatGames(games) {
// Ignore games which do not have keys revealed
games = games.filter(e => e[1]);
// Format the output as tab-separated
games = games.map(e => e[0]+"\t"+e[1]);
return games.join("\n");
}
function getGames(bundle) {
let is = bundle.querySelectorAll(".new-order-item");
return Array.prototype.map.call(is, i => {
const gameTitleElement = i.getElementsByClassName("game-name");
const gameTitle = gameTitleElement.length > 0 ? gameTitleElement[0].textContent.trim() : "";
const keyElement = i.querySelector("[aria-label='reveal-key']");
const gameKey = keyElement ? keyElement.value : "";
return [gameTitle, gameKey];
});
}
function revealAllKeys(bundle) {
const revealButtons = bundle.querySelectorAll(".key-container button.btn-block");
revealButtons.forEach(b => { b.click() });
this.style.display = "none";
}
function createRevealButton(bundle) {
let btn = document.createElement("button");
btn.type = "button"; // no default behavior
btn.innerText = "Reveal this bundle's keys";
btn.onclick = revealAllKeys.bind(btn, bundle);
return btn;
}
// Adds a textarea to the bottom of the games listing with all the titles and keys
function handleBundle(bundles) {
const bundle = bundles.at(-1);
const bundleName = bundles[0].querySelector(".bundle-name")?.textContent.trim() ?? "No Title";
const games = bundles.flatMap(bundle => getGames(bundle));
const keyCount = games.filter(e => e[1]).length;
const gameStr = formatGames(games);
let notify = bundle.querySelector(".ktt-notify");
if (!notify) {
notify = document.createElement("div");
notify.className = "ktt-notify";
bundle.append(notify);
if (games.length != keyCount) {
const btn = createRevealButton(bundle);
notify.before(btn);
}
}
const color = games.length === keyCount ? "" : "red";
let newInner = `Dumping keys for ${bundleName}: Found ${games.length} items and <span style="background-color:${color}">${keyCount} keys</span>.`;
if (games.length != keyCount) {
newInner += " Are some keys not revealed?";
}
if (notify.innerHTML != newInner) {
notify.innerHTML = newInner;
}
let area = bundle.querySelector(".ktt");
if (!area) {
area = document.createElement("textarea");
area.className = "ktt";
area.style.width = "100%";
area.setAttribute('readonly', true);
bundle.append(area);
}
if (area.value != gameStr) {
area.value = gameStr;
// Adjust the height so all the contents are visible
area.style.height = "";
area.style.height = area.scrollHeight + 20 + "px";
}
}
var loopCount = 0;
function handleOrderPage() {
// There can be more than one bundle in an order
let bundleItemsContainers = [...new Set([...document.querySelectorAll('.new-order-item')].map(item => item.closest('section')))];
// combine bundle groups into their overall bundle header
// this is needed because mystery bundles span multiple bundle containers for some reason
let currentBundleGroup = [];
const bundleGroups = [currentBundleGroup];
for (const bundleItemsContainer of bundleItemsContainers) {
if (bundleItemsContainer.firstChild.className === "bundle-name-container" && currentBundleGroup.length > 0) {
// Indicates a new bundle group is starting
currentBundleGroup = [];
bundleGroups.push(currentBundleGroup);
}
currentBundleGroup.push(bundleItemsContainer);
}
if (bundleGroups.length > 0 && bundleGroups[0].length > 0) {
//console.log(`Found ${bundleGroups.length} bundle(s)`);
bundleGroups.forEach(handleBundle);
if (loopCount++ < 100) {
setTimeout(handleOrderPage, 500);
}
} else {
if (loopCount++ < 100) {
setTimeout(handleOrderPage, 100);
}
}
}
handleOrderPage();
})();