Last active
December 26, 2024 17:18
-
-
Save thde/12c8e6f692c6fe6dbc38b37ef27dfa2b to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// @name Bandcamp Purchase Export | |
// @description Allows to export Bandcamp purchases as a CSV file. | |
const main = async () => { | |
const currentRates = rates("CHF"); | |
await loadAllPurchases(); | |
const allPurchases = purchases(await currentRates); | |
if (allPurchases.length == 0) { | |
alert( | |
"No purchase has been found on the current page!\nEnsure you're on Bandcamp's purchases page, before running this bookmarklet.", | |
); | |
return; | |
} | |
download("bandcamp-purchases.csv", convertToCSV(allPurchases)); | |
}; | |
const loadAllPurchases = async (timeoutMS = 1000) => { | |
const allButton = document.querySelector(".view-all-button"); | |
if (allButton != null) { | |
allButton.click(); | |
await sleep(timeoutMS); | |
} | |
// scroll to bottom of page to trigger loading of additional purchases | |
while (window.innerHeight + window.scrollY < document.body.offsetHeight - 2) { | |
window.scrollTo(0, document.body.scrollHeight, { behavior: "smooth" }); | |
await sleep(timeoutMS); | |
} | |
}; | |
const purchases = (rates) => | |
[...document.querySelectorAll(".purchases-item")].map((e) => { | |
const s = e | |
.querySelector(".purchases-item-total strong:last-child") | |
.innerText.split(" "); | |
const purchaseDate = new Date( | |
e.querySelector(".purchases-item-date").innerText.trim(), | |
); | |
const currency = s[1].trim(); | |
const amount = parseFloat(s[0].replace(/[^\d.-]/g, "")); | |
return { | |
date: purchaseDate.toLocaleDateString("de-CH"), | |
currency: rates.base, | |
amount: | |
currency == rates.base | |
? amount | |
: (amount * rates.rates[currency]).toFixed(2), | |
originalCurrency: currency, | |
originalAmount: amount, | |
title: e.querySelector(".purchases-item-title").innerText.trim(), | |
}; | |
}); | |
const convertToCSV = (elements, separator = ",") => | |
[Object.keys(elements[0]), ...elements.map((line) => Object.values(line))] | |
.map((line) => | |
line | |
.map((element) => { | |
if (typeof element !== "string") return element; | |
return `"` + element.replaceAll(`"`, `""`) + `"`; | |
}) | |
.join(separator), | |
) | |
.join("\n"); | |
const download = (filename, text) => { | |
const element = document.createElement("a"); | |
element.setAttribute( | |
"href", | |
"data:text/csv;charset=utf-8," + encodeURIComponent(text), | |
); | |
element.setAttribute("download", filename); | |
element.style.display = "none"; | |
document.body.appendChild(element); | |
element.click(); | |
document.body.removeChild(element); | |
}; | |
const rates = async (base) => { | |
const resp = await fetch(`https://api.frankfurter.app/latest?from=${base}`); | |
return await resp.json(); | |
}; | |
const sleep = (ms) => new Promise((r) => setTimeout(r, ms)); | |
main(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment