Skip to content

Instantly share code, notes, and snippets.

@FrostBird347
Last active March 29, 2024 08:06
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save FrostBird347/3a42e4084dce3fae325bc9363fe853ac to your computer and use it in GitHub Desktop.
Save FrostBird347/3a42e4084dce3fae325bc9363fe853ac to your computer and use it in GitHub Desktop.
A simple BetterDiscord plugin that adds embed support for Bandcamp links.
/**
* @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('&quot;' + settingsProxy.sideColour + '&quot;:&quot;')[1].split('&quot;')[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);
}
})
}
}
@FrostBird347
Copy link
Author

FrostBird347 commented Jul 27, 2023

Source ↑

Update Log

1.3.6

  • Discord changed the class names of everything

1.3.5

  • Stop discord from crashing occasionally by hiding the original embed instead of directly replacing it
  • Additional checks were added to automatically rehide the original embed when discord decides to randomly remove and add it back (the removing part was causing discord to crash)

1.3.4

  • Updated deprecated BdApi functions

1.3.3

  • Discord changed the class names of everything

1.3.2

  • Albums now display the tracklist by default, however this can be configured in the settings
    • tracklist

1.3.1

  • Added new side colour options "Body", "Link" and fixed "Background" option

1.3.0

  • Added a settings menu
    • "Compact Mode" option reverts embed styling to match version 1.1.1
      • BandcampEmbed
    • "Side Colour" option changes the variable used to pick the edge colour
  • Discord's own embed is now replaced with the custom one
    • Links that are marked to not embed will not have a custom embed either and multiple identical links will only show one custom embed

1.2.0

  • Added some padding around the edges of the iframe, using similar width and heights to embedded youtube videos
  • Make the edge colour match the secondary_text_color value of the page
  • 1.2.0-latest

1.1.1

  • Added the source and update URLS

1.1.0

  • First uploaded version
  • 1.1.0-1.1.1

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment