Skip to content

Instantly share code, notes, and snippets.

@paulera
Last active January 27, 2022 13:28
Show Gist options
  • Save paulera/3c19278b2d2a4f49abfffdca09aa8ad8 to your computer and use it in GitHub Desktop.
Save paulera/3c19278b2d2a4f49abfffdca09aa8ad8 to your computer and use it in GitHub Desktop.
/**
* Mural downloader
*
* Run this script in Google Chrome's console to display buttons for PDF download and a switch for automatic mode.
*
* This requires the extension Ignore X-Frame headers
* https://chrome.google.com/webstore/detail/ignore-x-frame-headers/gleekbfjekiniecknbkamfmkohkpodhe
*
**/
// Default state is automatic download OFF
function autodownload(enable) {
window.autoDownloadEnabled = enable;
if (window.autoDownloadEnabled) {
document.getElementById("autoon").style.display = "block";
document.getElementById("autooff").style.display = "none";
} else {
document.getElementById("autoon").style.display = "none";
document.getElementById("autooff").style.display = "block";
}
console.log ("Autodownload is " + (window.autoDownloadEnabled?"on":"off"));
}
function downloadPDF(button, title, url) {
console.log (`[${title}] 🤖 Processing started`);
// Remember button previous attributes
let oldButtonClickEvent = button.onclick;
let oldButtonClassName = button.className;
let oldButtonInnerHTML = button.innerHTML;
// Use button as status message
button.onclick = function(){};
button.className = "pdf-export ui-button-search";
button.innerHTML = "Loading mural";
// Open url in an iframe
let iframe;
iframe = document.createElement('iframe');
iframe.src = "https://app.mural.co" + url;
iframe.style.display = "none";
iframe.style.width = "700px";
iframe.style.height = "300px";
let header = document.querySelector(".dashboard-layout-header");
header.appendChild(iframe);
console.log (`[${title}] 🎁 Iframe created: ` + iframe.src);
// wait for the iframe document to load. When it finished it doesn't
// mean it is ready yet
iframe.addEventListener('load', () => {
console.log (`[${title}] 👍 Mural loaded`);
// Wait for the export button to be rendered, so it can be clicked
button.innerHTML = "Waiting menus";
console.log (`[${title}] ⏳ Waiting for export button`);
let intervalFindExportButton = setInterval(() => {
try {
// search for the export button
let buttonExport = iframe.contentWindow.document.querySelector("[data-qa='export-mural-menu']");
if (buttonExport) {
console.log (`[${title}] 👍 Export button found`);
clearInterval(intervalFindExportButton); // stop searching
buttonExport.click();
console.log (`[${title}] 👊 Export button clicked`);
// Wait for the PDF button to be rendered, so it can be clicked
console.log (`[${title}] ⏳ Waiting for PDF button`);
let intervalFindPDFButton = setInterval(() => {
// search for the export button
let buttondPDF = iframe.contentWindow.document.querySelector("[data-qa='menu-item-pdf']");
if (buttondPDF) {
console.log (`[${title}] 👍 PDF button found`);
clearInterval(intervalFindPDFButton); // stop searching
buttondPDF.click();
console.log (`[${title}] 👊 PDF button clicked`);
button.innerHTML = "PDF requested...";
// Notifies the end of one download, so another one can
// get ready to start.
window.dispatchEvent(
new CustomEvent(
"PdfExportDone",
{
detail: {
"pdfexportindex": button.getAttribute("pdfexportindex"),
"title": title
}
}
)
);
// Destroy the iframe to release memory and bandwidth, but
// wait a bit so the download can start.
setTimeout(() => {
iframe.remove();
console.log (`[${title}] 💣 Iframe destroyed`);
console.log (`[${title}] 🤖 Processing finished`);
button.onclick = oldButtonClickEvent;
button.className = oldButtonClassName;
button.innerHTML = oldButtonInnerHTML;
}, 10000); // interval to destroy the iframe after start the download
}
},200); // interval to search for the PDF button
}
} catch (error) {
console.log (`[${title}] 🚫 Search for export button aborted`);
clearInterval(intervalFindExportButton);
button.onclick = oldButtonClickEvent;
button.className = oldButtonClassName;
button.innerHTML = oldButtonInnerHTML;
if (error instanceof DOMException) {
console.log("This script requires the extension \"Ignore X-Frame headers\": https://chrome.google.com/webstore/detail/ignore-x-frame-headers/gleekbfjekiniecknbkamfmkohkpodhe");
alert ("This script requires the extension \"Ignore X-Frame headers\" - see console for link");
throw(error);
} else {
throw(error);
}
}
}, 200); // interval to search for the export button
}, true); //addEventListener
}
if (!window.pdfButtonsAdded) {
window.pdfButtonsAdded = true;
document.querySelector("[data-qa='folder-murals-title']").append(
new DOMParser().parseFromString(
"<button id='autoon' onclick='autodownload(false)' style='width:20rem' class='ui-button medium-size'>Automatic download: ON</button>",
'text/html'
).documentElement.querySelector('body').firstChild
);
document.querySelector("[data-qa='folder-murals-title']").append(
new DOMParser().parseFromString(
"<button id='autooff' onclick='autodownload(true)' style='width:20rem' class='ui-button-search'>Automatic download: OFF</button>",
'text/html'
).documentElement.querySelector('body').firstChild
);
document.querySelector("[data-qa='folder-murals-title']").append(
new DOMParser().parseFromString(
"<span>When automatic download is on, start one download manually and the rest will follow automatically.",
'text/html'
).documentElement.querySelector('body').firstChild
);
document.querySelector("[data-qa='folder-murals-title']").append(
new DOMParser().parseFromString(
"<span id='nap' style='display:none'>😴💤 Simulating a nap...</span>",
'text/html'
).documentElement.querySelector('body').firstChild
);
setTimeout(() => {
autodownload(false);
}, 500);
var i = 0;
document.querySelector(
"ul[data-qa='dashboard-room']"
).children.forEach(function(item) {
i++;
const linkNode = item.querySelector("a[data-qa='dashboard-mural-row-item-link']")
const href = linkNode.getAttribute("href");
const title = linkNode.getAttribute("aria-label");
infoNode = item.querySelector(".dashboard-grid-mural-info");
infoNode.style.height="11rem";
button = new DOMParser().parseFromString("<button pdfexportindex=\"" + i + "\" onclick='downloadPDF(this, \"" + title + "\", \"" + href + "\")' type='button' style='margin-top:4px;margin-left:auto; margin-right:auto' class='pdf-export ui-button medium-size'>PDF</button>", 'text/html').documentElement.querySelector('body').firstChild;
infoNode.appendChild(button);
});
}
// Parses events notifying that a download was finished. If the automatic mode
// is on, try to find the next mural and continues with the download.
window.addEventListener("PdfExportDone", function(event) {
window.event = event;
console.log ("🗣 Event dispached: " + event.detail.title + " is done!");
if (window.autoDownloadEnabled) {
// defines a random wait between export requests to not raise suspicion
// on robot detection mechanisms and to not overdo throttling.
minWait = 3000;
maxWait = 10000;
pdfExportIndex = parseInt(event.detail.pdfexportindex);
nextButton = document.querySelector("button[pdfexportindex='" + (pdfExportIndex + 1) + "']");
if (nextButton) {
document.getElementById("nap").style.display = "block";
setTimeout(function() {
nextButton.click();
document.getElementById("nap").style.display = "none";
}, minWait + Math.round(Math.random() * (maxWait - minWait)));
}
}
}, false);
/**
* Some useful code snippets to use in mural.
* If you are here for the PDF export, ignore this part
**/
// Map all board in the current room/folder
let boards = [...document.querySelectorAll('li[data-qa="dashboard-mural-row-item"]')].map( (item) => {
let elementLink = item.querySelector('a[data-qa="dashboard-mural-row-item-link"]');
let elementImage = elementLink.getElementsByClassName('thumb-image')[0];
let urlLocation = new URL(window.location.href);
return {
"title": elementLink.getAttribute('aria-label'),
"id": elementLink.href.split('/')[7],
"href": elementLink.href,
"workspace": urlLocation.pathname.split('/')[2],
"room": urlLocation.pathname.split('/')[4],
"folder": urlLocation.searchParams.get('folderUuid'),
}
});
// Runs a function with all boards mapped
boards.forEach( (board) => {
console.log (board);
});
// List all folders in the current room/folder
console.table([...document.querySelectorAll(".dashboard-folders-link")].map( i => i.innerHTML ));
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment