Displays a text area with game titles and keys so you can copy them out easily.
// ==UserScript==
// @name Humble Bundle Keys Backup
// @namespace Lex@GreasyFork
// @version 0.2.0
// @description Displays a text area with game titles and keys so you can copy them out easily.
// @author Lex
// @match https://www.humblebundle.com/downloads*
// @grant none
// ==/UserScript==
(function() {
'use strict';
function formatGames(games, bundleTitle) {
// Format the output as tab-separated
if (bundleTitle) {
games = games.map(e => (bundleTitle + "\t" + e.title+"\t"+e.key).trim());
} else{
games = games.map(e => (e.title+"\t"+e.key).trim());
}
return games.join("\n");
}
function createNotify() {
const notify = document.createElement("div");
notify.className = "ktt-notify";
return notify;
}
function updateNotify(bundle, message) {
const notify = bundle.querySelector(".ktt-notify");
notify.innerHTML = message;
}
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", true)
);
container.className = "ktt-config-container"
return container;
}
function createArea() {
const area = document.createElement("textarea");
area.className = "key-text-area";
area.style.width = "100%";
area.setAttribute('readonly', true);
return area;
}
// Updates an area if it needs updating, adjusting the height to fit the contents
function updateArea(bundle, updateStr) {
const area = bundle.querySelector(".key-text-area")
if (area.value != updateStr) {
area.value = updateStr;
// Adjust the height so all the contents are visible
area.style.height = "";
area.style.height = area.scrollHeight + 20 + "px";
}
}
function createCopyButton(area) {
const button = document.createElement("button");
button.textContent = "Copy to Clipboard";
button.style.cssText = "display: block; margin: 5px 0; padding: 5px 10px; cursor: pointer;";
button.addEventListener("click", async () => {
await navigator.clipboard.writeText(area.value);
button.textContent = "Copied!";
setTimeout(() => (button.textContent = "Copy to Clipboard"), 1500);
});
return button;
}
// Returns array of the games in the target bundle
function getGames(bundle) {
let games = [];
bundle.querySelectorAll(".key-redeemer").forEach(div => {
let game = {};
game.title = div.querySelector(".heading-text h4").innerText;
const keyfield = div.querySelector(".keyfield");
if (!keyfield) return;
game.key = keyfield.title;
if (game.key.startsWith("Reveal your ")) {
game.key = "";
game.revealed = false;
} else {
game.revealed = true;
}
game.isGift = keyfield.classList.contains("redeemed-gift");
game.isKey = keyfield.classList.contains("redeemed");
games.push(game);
});
return games;
}
function refreshOutput(bundle) {
const gameCount = document.querySelectorAll(".keyfield").length;
const revealedCount = document.querySelectorAll(".redeemed,.redeemed-gift").length;
const color = gameCount == revealedCount ? "" : "tomato";
let notifyHtml = `Found ${gameCount} keyfields. <span style="background:${color}">${revealedCount} are revealed.</span>`;
if (gameCount != revealedCount) {
notifyHtml += " Are some keys not revealed?";
}
if (!bundle.querySelector(".ktt-config-container")) {
const updateCallback = () => refreshOutput(bundle);
const textArea = createArea();
bundle.append(createNotify(), createConfig(updateCallback), textArea, createCopyButton(textArea))
}
updateNotify(bundle, notifyHtml)
let games = getGames(bundle);
const includeTitle = bundle.querySelector(".includeTitle").checked;
const bundleTitle = includeTitle ? $('h1#hibtext')[0].childNodes[2].textContent.trim() : null;
const includeUnrevealed = bundle.querySelector(".includeUnrevealed").checked;
if (!includeUnrevealed) games = games.filter(e => e.key);
const outputText = formatGames(games, bundleTitle);
updateArea(bundle, outputText);
}
function handlePage() {
document.querySelectorAll(".key-container.wrapper").forEach(refreshOutput);
}
function waitForLoad(query, callback) {
if (document.querySelector(query)) {
callback();
} else {
setTimeout(waitForLoad.bind(null, query, callback), 100);
}
}
waitForLoad(".key-redeemer", handlePage);
})();