-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathmoxfield-export-spreadsheet.js
189 lines (160 loc) · 6.25 KB
/
moxfield-export-spreadsheet.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
// ==UserScript==
// @name Moxfield - Export decklist to Spreadsheet
// @namespace http://tampermonkey.net/
// @version 2025-02-02
// @description Adds an "Export to Spreadsheet" button to Moxfield decklist page, does just that
// @author Cambysses
// @match *://moxfield.com/decks/*
// @icon https://www.google.com/s2/favicons?sz=64&domain=moxfield.com
// @grant none
// @run-at document-idle
// ==/UserScript==
(function () {
'use strict';
let done = false;
function checkForElement(observer) {
if (done) {
return;
}
let targetElement = document.querySelector(".LFQILNJUd0WkLwZH_6IA");
if (targetElement) {
done = true;
insertExportOption();
observer.disconnect();
}
}
function convertObjectArrayToCsv(data) {
// Extract the headers (keys from the first object)
let headers = Object.keys(data[0]);
// Map each object to a CSV row
let rows = data.map(obj =>
headers.map(header => JSON.stringify(obj[header] ?? "")).join(",")
);
// Combine headers and rows into a single CSV string
return [headers.join(","), ...rows].join("\n");
}
function promptDownload(data, filename) {
let csvString = convertObjectArrayToCsv(data);
// Create a Blob from the CSV string
let blob = new Blob([csvString], { type: "text/csv" });
// Create a temporary <a> element for downloading the file
let a = document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = filename;
// Append the <a> to the document, trigger the download, and remove the element
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
}
function showErrorMessage() {
alert("Could not generate file. Make sure you have \"Mana Cost\", \"Price\", and \"Set Symbol\" enabled under \"View Options\".");
}
function getCards() {
let cards = [];
try {
document.querySelectorAll(".XIi4jFys2lGhYwseGpBo").forEach((cardElement) => {
// Get count
let count = cardElement.querySelector(".dcD5V3uk1cjCKIMHR5hC input").value;
// Get name
let name = "";
cardElement.querySelectorAll(".table-deck-row-link span").forEach((nameElement) => {
if (nameElement.children.length === 0) {
name += nameElement.innerText;
}
});
// Get mana cost
let manaCost = "";
cardElement.querySelectorAll(".mana").forEach((manaElement) => {
manaCost += manaElement.classList[1].split("-")[1].toUpperCase();
});
// Get price
let price = cardElement.querySelector(".text-end.text-monospace").innerText;
// Get set
let set = cardElement.querySelector(".zNzCIkFbLmQ5PAHzbwZa").getAttribute("title");
// Get rarity
let rarity = cardElement.querySelector(".zNzCIkFbLmQ5PAHzbwZa").classList[1].split("-")[1];
// Get type
let cardType = makeCardTypeSingular(cardElement.parentElement.querySelector(".QplEGzdpUY3yofYwhhX0 a .d-inline-block.me-1").innerText);
// Create scryfall search link
let scryFallLink = "https://scryfall.com/search?q=" + name.replaceAll(" ", "+");
cards.push({
count,
name,
manaCost,
price,
set,
rarity,
cardType,
scryFallLink
});
});
return cards;
}
catch (e) {
showErrorMessage();
return;
}
}
function getDeckTitle() {
return document.querySelector(".deckheader-name").innerText;
}
function insertExportOption() {
let menu = document.querySelector(".LFQILNJUd0WkLwZH_6IA .col-sm-auto.d-flex");
let menuItem = document.createElement("span");
menuItem.classList.add("me-5");
menu.insertBefore(menuItem, menu.lastElementChild);
let menuItemLink = document.createElement("a");
menuItemLink.classList.add("xQ0_bw2aqoYGKBWqhsjz");
menuItemLink.classList.add("text-nowrap");
menuItemLink.classList.add("cursor-pointer");
menuItemLink.classList.add("no-outline");
menuItem.appendChild(menuItemLink);
menuItemLink.onclick = () => {
promptDownload(getCards(), `${getDeckTitle()}.xlsx`);
};
let icon = document.createElement("i");
icon.classList.add("fa-light");
icon.classList.add("fa-file-export");
icon.classList.add("me-sm-2");
menuItemLink.appendChild(icon);
let menuItemLinkText = document.createElement("span");
menuItemLinkText.classList.add("d-sm-inline");
menuItemLinkText.innerText = "Export to Spreadsheet";
menuItemLink.appendChild(menuItemLinkText);
}
function makeCardTypeSingular(cardType) {
switch (cardType) {
case "Creatures":
return "Creature";
case "Artifacts":
return "Artifact";
case "Enchantments":
return "Enchantment";
case "Instants":
return "Instant";
case "Sorceries":
return "Sorcery";
case "Planeswalkers":
return "Planeswalker";
case "Lands":
return "Land";
case "Battles":
return "Battle";
case "Commanders":
return "Commander";
default:
return cardType;
}
}
function main() {
const observer = new MutationObserver((mutationsList) => {
for (let mutation of mutationsList) {
if (mutation.type === 'childList') {
checkForElement(observer);
}
}
});
observer.observe(document.body, { childList: true, subtree: true });
}
main();
})();