Skip to content

Instantly share code, notes, and snippets.

@ihatecsv
Last active September 3, 2024 19:21
Show Gist options
  • Save ihatecsv/09f3a9df30d13f6bcdd945069f4e51c6 to your computer and use it in GitHub Desktop.
Save ihatecsv/09f3a9df30d13f6bcdd945069f4e51c6 to your computer and use it in GitHub Desktop.
Scryfall Forge MTG and Mage buttons
// ==UserScript==
// @name Scryfall to Forge MTG and Mage
// @namespace http://tampermonkey.net/
// @version 1.0.11
// @description Fetch Forge MTG and Mage card data on Scryfall card pages.
// @match https://scryfall.com/card/*
// @grant GM_xmlhttpRequest
// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js
// @require https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/languages/java.min.js
// @resource HLJS_CSS https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/default.min.css
// @grant GM_addStyle
// @grant GM_getResourceText
// @author ihatecsv
// ==/UserScript==
(function () {
"use strict";
// Add highlight.js CSS
GM_addStyle(GM_getResourceText("HLJS_CSS"));
const STATE = {
forgeData: "",
mageData: "",
buttonContainer: null,
forgeToggleButton: null,
mageToggleButton: null,
githubButton: null,
cardName: "",
modal: null,
modalContent: null,
closeButton: null,
forgeSize: 0,
mageSize: 0,
forgeRetryState: 0, // 0: letter folders, 1: upcoming, 2: rebalanced
};
function formatFileSize(bytes) {
return bytes < 1024 ? `${bytes} B` : `${(bytes / 1024).toFixed(1)} KB`;
}
function getGithubUrl(source, raw = false) {
const { cardName } = STATE;
let url;
if (source === "forge") {
const firstLetter = cardName.charAt(0).toLowerCase();
let formattedCardName;
if (STATE.forgeRetryState === 2) {
// For rebalanced cards, keep the 'a-' prefix and replace other non-alphanumeric characters with underscores
formattedCardName =
cardName.slice(0, 2) + cardName.slice(2).replace(/[^a-z0-9]/g, "_");
} else {
formattedCardName = cardName.toLowerCase().replace(/[^a-z0-9]/g, "_");
}
if (STATE.forgeRetryState === 0) {
url = `https://github.com/Card-Forge/forge/blob/master/forge-gui/res/cardsfolder/${firstLetter}/${formattedCardName}.txt`;
} else if (STATE.forgeRetryState === 1) {
url = `https://github.com/Card-Forge/forge/blob/master/forge-gui/res/cardsfolder/upcoming/${formattedCardName}.txt`;
} else {
url = `https://github.com/Card-Forge/forge/blob/master/forge-gui/res/cardsfolder/rebalanced/${formattedCardName}.txt`;
}
} else {
const basicLands = ["plains", "island", "swamp", "mountain", "forest"];
if (basicLands.includes(cardName.toLowerCase())) {
const capitalizedCardName =
cardName.charAt(0).toUpperCase() + cardName.slice(1).toLowerCase();
url = `https://github.com/magefree/mage/blob/master/Mage/src/main/java/mage/cards/basiclands/${capitalizedCardName}.java`;
} else {
const camelCaseCardName = cardName
.split("-")
.map(
(word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()
)
.join("");
url = `https://github.com/magefree/mage/blob/master/Mage.Sets/src/mage/cards/${camelCaseCardName
.charAt(0)
.toLowerCase()}/${camelCaseCardName}.java`;
}
}
return raw
? url
.replace("github.com", "raw.githubusercontent.com")
.replace("/blob", "")
: url;
}
function fetchCardData(source) {
const url = getGithubUrl(source, true);
const button =
source === "forge" ? STATE.forgeToggleButton : STATE.mageToggleButton;
const dataKey = `${source}Data`;
const sizeKey = `${source}Size`;
button.disabled = true;
button.style.cursor = "wait";
GM_xmlhttpRequest({
method: "GET",
url: url,
onload: function (response) {
if (response.status === 200) {
STATE[dataKey] = response.responseText;
STATE[sizeKey] = response.responseText.length;
button.textContent = `${
source === "forge" ? "Forge" : "XMage"
} (${formatFileSize(STATE[sizeKey])})`;
button.style.backgroundColor = "#343242";
button.disabled = false;
button.style.cursor = "pointer";
} else if (source === "forge" && STATE.forgeRetryState < 2) {
// Retry with the next folder for Forge
STATE.forgeRetryState++;
fetchCardData("forge");
} else {
console.log(`Card not found in ${source} repo`);
button.style.backgroundColor = "#870000";
button.disabled = true;
button.style.cursor = "not-allowed";
}
},
onerror: function (error) {
console.error(`Error fetching ${source} data:`, error);
button.style.backgroundColor = "#c85309";
button.disabled = true;
button.style.cursor = "not-allowed";
},
});
}
function createButton(text, onClick, styles = {}) {
const button = document.createElement("button");
button.textContent = text;
button.addEventListener("click", onClick);
Object.assign(button.style, {
color: "#fff",
backgroundColor: "#808080",
border: "none",
cursor: "wait",
padding: "0",
transition: "all 0.3s ease",
...styles,
});
button.disabled = true;
return button;
}
function addToggleButtons() {
const cardTextOracle = document.querySelector(".prints");
if (cardTextOracle && !STATE.forgeToggleButton && !STATE.mageToggleButton) {
STATE.buttonContainer = document.createElement("div");
Object.assign(STATE.buttonContainer.style, {
display: "flex",
justifyContent: "space-between",
marginBottom: "25px",
});
STATE.forgeToggleButton = createButton(
"Forge",
() => showModal("forge"),
{ width: "49%", height: "24px" }
);
STATE.mageToggleButton = createButton("XMage", () => showModal("mage"), {
width: "49%",
height: "24px",
});
STATE.buttonContainer.appendChild(STATE.forgeToggleButton);
STATE.buttonContainer.appendChild(STATE.mageToggleButton);
cardTextOracle.insertBefore(
STATE.buttonContainer,
cardTextOracle.firstChild
);
fetchCardData("forge");
fetchCardData("mage");
}
}
function createModal() {
STATE.modal = document.createElement("div");
STATE.modalContent = document.createElement("div");
STATE.closeButton = document.createElement("span");
Object.assign(STATE.modal.style, {
display: "none",
position: "fixed",
zIndex: "1000",
left: "0",
top: "0",
width: "100%",
height: "100%",
backgroundColor: "rgba(0,0,0,0.4)",
overflow: "auto",
});
Object.assign(STATE.modalContent.style, {
backgroundColor: "#fefefe",
margin: "5% auto",
padding: "20px",
border: "1px solid #888",
width: "80%",
maxWidth: "800px",
maxHeight: "80%",
overflowY: "auto",
position: "relative",
borderRadius: "15px",
});
Object.assign(STATE.closeButton.style, {
color: "#aaa",
float: "right",
fontSize: "28px",
fontWeight: "bold",
cursor: "pointer",
});
STATE.closeButton.textContent = "×";
STATE.closeButton.onclick = closeModal;
STATE.modalContent.appendChild(STATE.closeButton);
STATE.modal.appendChild(STATE.modalContent);
document.body.appendChild(STATE.modal);
STATE.modal.onclick = function (event) {
if (event.target === STATE.modal) {
closeModal();
}
};
}
function showModal(source) {
if (!STATE.modal) {
createModal();
}
const data = source === "forge" ? STATE.forgeData : STATE.mageData;
const size = source === "forge" ? STATE.forgeSize : STATE.mageSize;
const header = document.createElement("h2");
header.textContent = `${
source === "forge" ? "Forge" : "XMage"
} Data (${formatFileSize(size)})`;
header.style.marginTop = "0";
header.style.marginBottom = "15px";
const preElement = document.createElement("pre");
Object.assign(preElement.style, {
margin: "0",
whiteSpace: "no-wrap",
wordBreak: "break-word",
overflowX: "auto",
maxHeight: "400px",
backgroundColor: "#f0f0f0",
padding: "8px",
borderRadius: "8px",
marginTop: "10px",
border: "1px solid #ccc",
});
preElement.textContent = data;
if (source === "mage") {
preElement.className = "language-java";
hljs.highlightElement(preElement);
}
STATE.modalContent.innerHTML = "";
STATE.modalContent.appendChild(STATE.closeButton);
STATE.modalContent.appendChild(header);
STATE.modalContent.appendChild(preElement);
STATE.githubButton = document.createElement("a");
STATE.githubButton.textContent = "View on GitHub";
STATE.githubButton.href = getGithubUrl(source);
STATE.githubButton.target = "_blank";
Object.assign(STATE.githubButton.style, {
display: "inline-block",
padding: "8px 16px",
backgroundColor: "#24292e",
color: "#fff",
textDecoration: "none",
borderRadius: "4px",
fontSize: "14px",
marginTop: "10px",
textAlign: "center",
cursor: "pointer",
});
STATE.modalContent.appendChild(STATE.githubButton);
STATE.modal.style.display = "block";
document.body.style.overflow = "hidden";
}
function closeModal() {
if (STATE.modal) {
STATE.modal.style.display = "none";
document.body.style.overflow = "auto";
}
}
STATE.cardName = window.location.pathname.split("/").pop();
addToggleButtons();
})();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment