您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
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.3.0
- // @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, includeUnrevealed, bundleTitle) {
- if (!includeUnrevealed)
- games = games.filter(e => e.gameKey);
- // Format the output as tab-separated
- if (bundleTitle) {
- games = games.map(e => bundleTitle + "\t" + e.gameTitle + "\t" + e.gameKey);
- } else {
- games = games.map(e => e.gameTitle + "\t" + e.gameKey);
- }
- return games.join("\n");
- }
- function revealAllKeys(articles) {
- articles.filter(a => !a.gameKey).forEach(a => {
- a.element.querySelector(".key-container button").click();
- });
- }
- function createRevealButton(bundle) {
- const btn = document.createElement("button");
- btn.type = "button"; // no default behavior
- btn.innerText = "Reveal this bundle's keys";
- btn.addEventListener("click", () => {
- revealAllKeys(bundle.articles);
- btn.style.display = "none";
- })
- return btn;
- }
- function createCopyButton(area) {
- const btn = document.createElement("button");
- btn.type = "button";
- btn.textContent = "Copy to Clipboard";
- btn.style.cssText = "display: block; margin: 5px 0; padding: 5px 10px; cursor: pointer;";
- btn.addEventListener("click", async () => {
- await navigator.clipboard.writeText(area.value);
- btn.textContent = "Copied!";
- setTimeout(() => (btn.textContent = "Copy to Clipboard"), 1500);
- });
- return btn;
- }
- function createConfig(updateCallback) {
- const createCheckbox = (labelText, className, defaultChecked) => {
- const label = document.createElement("label");
- label.style.marginRight = "10px";
- const checkbox = document.createElement("input");
- checkbox.type = "checkbox";
- checkbox.className = className;
- checkbox.checked = defaultChecked;
- checkbox.addEventListener("change", updateCallback);
- label.append(` ${labelText} `, checkbox,);
- return label;
- };
- const container = document.createElement("div");
- container.append(
- createCheckbox("Include Bundle Title", "includeTitle", false),
- createCheckbox("Include Unrevealed", "includeUnrevealed", false)
- );
- container.className = "ktt-config-container"
- return container;
- }
- // Adds a textarea to the bottom of the games listing with all the titles and keys
- function handleBundle(bundle) {
- const games = bundle.articles;
- const keyCount = games.filter(e => e.gameKey).length;
- const lastArticleElement = bundle.articles[bundle.articles.length - 1].element;
- let div = lastArticleElement.nextElementSibling;
- if (!div || div.className !== "ktt-output-container") {
- div = document.createElement("div")
- div.className = "ktt-output-container"
- div.style.width = "100%";
- lastArticleElement.insertAdjacentElement('afterend', div);
- if (games.length != keyCount) {
- div.append(createRevealButton(bundle));
- }
- const notify = document.createElement("div");
- notify.className = "ktt-notify";
- const configCallback = () => { refreshOutput(); };
- const area = document.createElement("textarea");
- area.className = "ktt-area";
- area.style.width = "100%";
- area.setAttribute('readonly', true);
- div.append(notify, createConfig(configCallback), area, createCopyButton(area));
- }
- const color = games.length === keyCount ? "" : "tomato";
- let newInner = `Dumping keys for ${bundle.name}: Found ${games.length} items and <span style="background-color:${color}">${keyCount} keys</span>.`;
- if (games.length != keyCount) {
- newInner += " Are some keys not revealed?";
- }
- const notify = div.querySelector(".ktt-notify");
- if (notify.innerHTML != newInner) {
- notify.innerHTML = newInner;
- }
- const area = div.querySelector(".ktt-area");
- const includeTitle = div.querySelector(".includeTitle").checked;
- const includeUnrevealed = div.querySelector(".includeUnrevealed").checked;
- const gameStr = formatGames(games, includeUnrevealed, includeTitle ? bundle.name : "");
- 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";
- }
- }
- function refreshOutput() {
- let currentBundle = null;
- const bundles = [];
- function traverse(element) {
- if (!element) return;
- if (element.matches("section")) {
- const bundleContainer = element.querySelector(".bundle-name-container");
- if (bundleContainer) {
- const bundleTitle = bundleContainer.textContent.trim();
- if (currentBundle && currentBundle.articles.length === 0) {
- currentBundle.name = bundleTitle;
- } else {
- currentBundle = {
- name: bundleTitle,
- articles: []
- };
- bundles.push(currentBundle);
- }
- }
- }
- if (element.matches("article")) {
- if (!currentBundle) {
- currentBundle = {
- name: "unknown",
- articles: []
- }
- bundles.push(currentBundle)
- }
- currentBundle.articles.push({
- element,
- gameTitle: element.querySelector(".game-name")?.textContent.trim() ?? "",
- gameKey: element.querySelector("[aria-label='reveal-key']")?.value ?? "",
- });
- return; // Stop traversing further inside this article
- }
- for (const child of element.children) {
- if (child)
- traverse(child);
- }
- }
- const container = document.querySelector("section.single-order");
- traverse(container);
- bundles.forEach(handleBundle);
- return bundles;
- }
- let loopCount = 0;
- function handleOrderPage() {
- const bundles = refreshOutput();
- if (bundles.length > 0) {
- if (loopCount++ < 100) {
- setTimeout(handleOrderPage, 500);
- }
- } else {
- if (loopCount++ < 100) {
- setTimeout(handleOrderPage, 100);
- }
- }
- }
- handleOrderPage();
- })();