Last active
March 29, 2024 08:06
-
-
Save FrostBird347/3a42e4084dce3fae325bc9363fe853ac to your computer and use it in GitHub Desktop.
A simple BetterDiscord plugin that adds embed support for Bandcamp links.
This file contains hidden or 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 BandcampEmbed | |
* @authorLink https://github.com/FrostBird347 | |
* @source https://gist.github.com/FrostBird347/3a42e4084dce3fae325bc9363fe853ac#gistcomment-4643226 | |
* @updateUrl https://gist.githubusercontent.com/FrostBird347/3a42e4084dce3fae325bc9363fe853ac/raw/BandcampEmbed.plugin.js | |
*/ | |
module.exports = class BandcampEmbed { | |
settings = { | |
sideColour: "secondary_text_color", | |
compactMode: false, | |
showTracklist: true | |
}; | |
getName () {return "BandcampEmbed";} | |
getDescription () {return "Adds embed support for Bandcamp links";} | |
getVersion() {return "1.3.6";} | |
getAuthor () {return "FrostBird347";} | |
load() {} | |
stop() {} | |
start() { | |
//Load settings | |
Object.assign(this.settings, BdApi.Data.load("BandcampEmbed", "settings")); | |
//Simple update notifier | |
//I have been unable to figure out how to obtain the plugin's version | |
let CurrentVersion = "1.3.6"; | |
window.fetch("https://gist.githubusercontent.com/FrostBird347/3a42e4084dce3fae325bc9363fe853ac/raw/BandcampEmbed.plugin.js").then(function(response) { | |
response.text().then(function(text) { | |
let Lines = text.split("\t").join("").split("\n"); | |
let RemoteVersion = ""; | |
for (let l = 0; l < Lines.length; l++) { | |
if (RemoteVersion == "" && Lines[l].split(" ").join("").split("\"")[0] == "getVersion(){return") { | |
RemoteVersion = Lines[l].split(" ").join("").split("\"")[1]; | |
} | |
} | |
if (RemoteVersion != CurrentVersion) { | |
window.BdApi.UI.showToast("BandcampEmbed is outdated!\nCurrent: " + CurrentVersion + "\nLatest: " + RemoteVersion, {type: "info", icon:true, timeout: 7500}); | |
} | |
}) | |
}) | |
} | |
getSettingsPanel() { | |
let options = [ | |
{ | |
type: "dropdown", | |
values: ["bg_color", "body_color", "text_color", "secondary_text_color", "link_color"], | |
displayValues: ["Background", "Body", "Text", "Secondary Text", "Link"], | |
optionText: "Side Colour:", | |
settingID: "sideColour" | |
}, | |
{ | |
type: "checkbox", | |
optionText: "Compact Mode", | |
settingID: "compactMode" | |
}, | |
{ | |
type: "checkbox", | |
optionText: "Show Tracklist for Albums", | |
settingID: "showTracklist" | |
} | |
] | |
//Directly accessing "this.settings" will fail later on, safer to refer to a new variable | |
let settingsProxy = this.settings; | |
let settingsPanel = document.createElement("div"); | |
settingsPanel.id = "BandcampEmbedSettings"; | |
settingsPanel.style.color = "var(--text-primary)"; | |
for (let i = 0; i < options.length; i++) { | |
//Don't put a newline at the start | |
if (i != 0) { | |
settingsPanel.appendChild(document.createElement("br")); | |
} | |
switch (options[i].type) { | |
case "dropdown": | |
let newDropdown = document.createElement("fieldset"); | |
newDropdown.style.paddingBlockStart = "0.35em"; | |
newDropdown.style.borderStyle = "hidden"; | |
let newLegend = document.createElement("legend"); | |
newLegend.textContent = options[i].optionText; | |
newLegend.style.fontWeight = "bold"; | |
newDropdown.appendChild(newLegend); | |
for (let iV = 0; iV < options[i].values.length; iV++) { | |
//Don't put a newline at the start | |
if (iV != 0) { | |
newDropdown.appendChild(document.createElement("br")); | |
} | |
let newLabel = document.createElement("label"); | |
newLabel.textContent = options[i].displayValues[iV]; | |
newLabel.style.paddingLeft = "0.75em"; | |
let newRadio = document.createElement("input"); | |
newRadio.type = "radio"; | |
newRadio.name = "BandcampEmbedSettingsOption" + i + "Radio"; | |
newRadio.value = options[i].values[iV]; | |
newRadio.style.margin = "0.4rem"; | |
if (settingsProxy[options[i].settingID] == options[i].values[iV]) { | |
newRadio.checked = true; | |
} | |
newRadio.onchange = function() { | |
settingsProxy[options[i].settingID] = options[i].values[iV]; | |
BdApi.Data.save("BandcampEmbed", "settings", settingsProxy); | |
}; | |
//Put newRadio inside newLabel to make clicking on the text also register | |
newLabel.prepend(newRadio); | |
newDropdown.appendChild(newLabel); | |
} | |
settingsPanel.appendChild(newDropdown); | |
break; | |
case "checkbox": | |
let newLabel = document.createElement("label"); | |
newLabel.textContent = options[i].optionText + ":"; | |
newLabel.style.fontWeight = "bold"; | |
let newCheckbox = document.createElement("input"); | |
newCheckbox.type = "checkbox"; | |
//If the value is not set to something, the checkbox inside label trick does not work for some reason | |
newCheckbox.value = ""; | |
newCheckbox.style.margin = "0.4rem"; | |
if (settingsProxy[options[i].settingID]) { | |
newCheckbox.checked = true; | |
} | |
newCheckbox.onchange = function() { | |
settingsProxy[options[i].settingID] = this.checked; | |
BdApi.Data.save("BandcampEmbed", "settings", settingsProxy); | |
}; | |
//Put newCheckbox inside newLabel to make clicking on the text also register | |
newLabel.appendChild(newCheckbox); | |
settingsPanel.appendChild(newLabel); | |
break; | |
default: | |
throw("Unknown type " + options[i].type + " specified!"); | |
} | |
} | |
return settingsPanel; | |
} | |
observer(changes) { | |
let ElementClasses = { | |
"messageContent": "messageContent_abea64", | |
"isSending": "isSending__93355", | |
"embedWrapper": "embedWrapper__47b23", | |
"embedFull": "embedFull__14919", | |
"embed": "embed_cc6dae", | |
"markup": "markup_a7e664", | |
"embedTitle": "embedTitle__1ac59" | |
}; | |
//Directly accessing "this.settings" will fail later on, safer to refer to a new variable | |
let settingsProxy = this.settings; | |
Array.prototype.forEach.call(document.querySelectorAll("[class*=" + ElementClasses.messageContent + "]:not([class=" + ElementClasses.messageContent + "]) > a:not([class=ProcessedBandcampEmbed])"), async function(el) { | |
try { | |
if (!el.classList.contains("ProcessedBandcampEmbed")) { | |
el.classList.add("ProcessedBandcampEmbed"); | |
let currentURL = el.href; | |
let bandcampRegex = /https:\/\/[^\/\n ?.]+\.bandcamp\.com\/(album|track)\/[^\/\n ?]+/i; | |
let genericRegex = /https:\/\/[^\/\n ?]+\/(album|track)\/[^\/\n ?]+/i; | |
let genericAlbumRegex = /https:\/\/[^\/\n ?]+\/album\/[^\/\n ?]+/i; | |
let extractedDomain = currentURL.split("https://")[1].split("/")[0]; | |
let isGenericPage = false; | |
//Fallback check for custom domains | |
if (!bandcampRegex.test(currentURL) && genericRegex.test(currentURL)) { | |
try { | |
let rawDNSInfo = await window.fetch("https://networkcalc.com/api/dns/lookup/" + encodeURIComponent(extractedDomain)); | |
let parsedDNSInfo = await rawDNSInfo.json(); | |
isGenericPage = (parsedDNSInfo.records.CNAME[0].address == "dom.bandcamp.com"); | |
} catch {} | |
} | |
if (bandcampRegex.test(currentURL) || isGenericPage) { | |
//I couldn't find any other way to bypass cors and I can't get the embed url without reading the page contents | |
window.fetch("https://corsproxy.io/?" + encodeURIComponent(currentURL), {method: "GET", redirect:"error"}).then(function(currentPage) { | |
currentPage.text().then(function(rawPage) { | |
//Find discord's bandcamp embed and get the earliest one | |
//A reverse search is used to prevent errors from to elements being removed during the search | |
let discordsEmbed = undefined; | |
let searchList = el.parentElement.parentElement.nextElementSibling.children; | |
for (let i = searchList.length - 1; i >= 0; i--) { | |
if (searchList[i] != undefined && searchList[i].id != "MARKED_FOR_REMOVAL_BANDCAMP_EMBED") { | |
let foundTitles = searchList[i].getElementsByClassName(ElementClasses.embedTitle); | |
if (foundTitles.length != 0 && foundTitles[0].firstElementChild.href == currentURL) { | |
discordsEmbed = searchList[i]; | |
} | |
} | |
} | |
//If there is no regular bandcamp embed, don't add our custom one | |
//If there is a matching embed, mark it so other async searches above ignore it | |
if (discordsEmbed != undefined) { | |
discordsEmbed.id = "MARKED_FOR_REMOVAL_BANDCAMP_EMBED"; | |
let rawURL = rawPage.split('<meta property="og:video"\n content="')[1].split('">')[0]; | |
let rawLinkColour = window.getComputedStyle(document.documentElement).getPropertyValue("--text-link"); | |
let rawBGColour = window.getComputedStyle(document.documentElement).getPropertyValue("--background-secondary"); | |
let embedSideColour = "#" + rawPage.split('"' + settingsProxy.sideColour + '":"')[1].split('"')[0]; | |
//All this mess to get the current background and link colours (if themes didn't exist I would have just manually specified the colours and avoided this) | |
let tempElem = document.createElement("span"); | |
tempElem.style.color = rawLinkColour; | |
tempElem.style.backgroundColor = rawBGColour; | |
el.parentElement.after(tempElem); | |
let fixedLinkColour = window.getComputedStyle(tempElem).color.split("(")[1].split(")")[0].replaceAll(" ", "").split(","); | |
fixedLinkColour = parseInt(fixedLinkColour[0]).toString(16).padStart(2, "0") + parseInt(fixedLinkColour[1]).toString(16).padStart(2, "0") + parseInt(fixedLinkColour[2]).toString(16).padStart(2, "0"); | |
let fixedBGColour = window.getComputedStyle(tempElem).backgroundColor.split("(")[1].split(")")[0].replaceAll(" ", "").split(","); | |
fixedBGColour = parseInt(fixedBGColour[0]).toString(16).padStart(2, "0") + parseInt(fixedBGColour[1]).toString(16).padStart(2, "0") + parseInt(fixedBGColour[2]).toString(16).padStart(2, "0"); | |
tempElem.remove(); | |
let fixedURL = rawURL + "bgcol=" + fixedBGColour + "/linkcol=" + fixedLinkColour + "/transparent=true/"; | |
if (settingsProxy.showTracklist && genericAlbumRegex.test(currentURL)) { | |
fixedURL = fixedURL.replace("/tracklist=false", "/tracklist=true"); | |
} | |
let embedFrame = document.createElement("iframe"); | |
embedFrame.classList.add(ElementClasses.embedWrapper); | |
embedFrame.classList.add(ElementClasses.embedFull); | |
embedFrame.classList.add(ElementClasses.embed); | |
embedFrame.classList.add(ElementClasses.markup); | |
embedFrame.style.width = "100%"; | |
embedFrame.style.maxWidth = "520px"; | |
if (!settingsProxy.compactMode) { | |
//embedFrame.style.minWidth = "436px"; | |
embedFrame.style.height = "136px"; | |
embedFrame.style.padding = "8px 16px 8px 16px"; | |
} else { | |
//embedFrame.style.minWidth = "404px"; | |
embedFrame.style.height = "120px"; | |
embedFrame.style.padding = "0px"; | |
} | |
if (settingsProxy.showTracklist && genericAlbumRegex.test(currentURL)) { | |
embedFrame.style.height = "350px"; | |
} | |
embedFrame.style.borderColor = embedSideColour; | |
embedFrame.src = fixedURL; | |
//Append info on the new embed | |
embedFrame.dataset.origText = discordsEmbed.textContent; | |
embedFrame.dataset.url = currentURL; | |
//Finally hide discord's own embed now that everything else has been done successfully. | |
discordsEmbed.after(embedFrame); | |
discordsEmbed.style.display = "none"; | |
} | |
}); | |
}); | |
} | |
//Make sure to rehide discord's own embed if it appears again | |
} else { | |
let embedContainer = el.parentElement.parentElement.nextElementSibling; | |
let iframes = embedContainer.getElementsByTagName("iframe"); | |
let articles = embedContainer.getElementsByTagName("article"); | |
for (let iF = 0; iF < iframes.length; iF++) { | |
for (let iA = 0; iA < articles.length; iA++) { | |
if (articles[iA].style.display != "none" && articles[iA].textContent == iframes[iF].dataset.origText) { | |
//Just to be absolutely sure that it wasn't another embed with the same text | |
let titles = articles[iA].getElementsByClassName(ElementClasses.embedTitle); | |
if (titles.length != 0 && titles[0].firstElementChild.href == iframes[iF].dataset.url) { | |
articles[iA].style.display = "none"; | |
} | |
} | |
} | |
} | |
} | |
} catch(err) { | |
console.error(err); | |
} | |
}) | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Source ↑
Update Log
1.3.6
1.3.5
1.3.4
1.3.3
1.3.2
1.3.1
1.3.0
1.2.0
secondary_text_color
value of the page1.1.1
1.1.0