Sauvegarde / Artname

// ==UserScript==
// @name		Artname
// @namespace	about:blank
// @description	Parse "art" / artist name to create an appropriate filename and auto-select it for convenience.
// @include		https://*.deviantart.com*
// @include		https://www.furaffinity.net/view/*
// @include		https://www.furaffinity.net/full/*
// @include 	https://www.hentai-foundry.com/pictures/*
// @include		https://inkbunny.net/s/*
// @icon		http://icons.iconarchive.com/icons/designbolts/cute-social-media/48/Deviantart-icon.png
// @grant       GM_xmlhttpRequest
// @grant		GM_download
// @connect     api-da.wixmp.com
// @run-at      document-start
// @version 	3.0
// @updateURL   https://openuserjs.org/meta/Sauvegarde/Artname.meta.js
// @downloadURL https://openuserjs.org/install/Sauvegarde/Artname.user.js
// @supportURL  https://openuserjs.org/scripts/Sauvegarde/Artname/issues
// @copyright   2017, Sauvegarde (https://openuserjs.org/users/Sauvegarde)
// @license		MIT
// ==/UserScript==

/**
 * Generate a corrected title in the form of "$author - $picture".
 * All special characters not supported by Windows' filesystem are removed.
 */
function parseName(name) {
	const author = name.replace(/(^.*) by (.*?$)/g, "$2");
	const picture = name.replace(/(^.*) by (.*?$)/g, "$1");
	let title = author + " - " + picture;
	title = title.replace(/[?.*_~=`"]/g, " ");		// forbidden characters
	title = title.replace(/[\/\\]/g, "-");			// slashes
	title = title.replace(/:/g, " - ");				// colon
	title = title.replace(/\-+\s+\-+/g, "-");		// redundant dashes
	title = title.replace(/\s+/g, " ");				// redundant spaces
	title = title.replace(/^\s|\s$/g, "");			// start/end spaces
	title = title.replace(/^\-+\s+|\s+\-+$/g, "");	// start/end dashes
	return title;
}

/**
 * Highlight all the text in the element so you can ctrl+c the corrected name.
 */
function selectText(element) {
	if(document.body.createTextRange) {
		const range = document.body.createTextRange();
		range.moveToElementText(element);
		range.select();
	}
	else if(window.getSelection) {
		const selection = window.getSelection();
		const range = document.createRange();
		range.selectNodeContents(element);
		selection.removeAllRanges();
		selection.addRange(range);
	}
}

/**
 * Open the "Save As" dialog with the corrected filename.
 * https://www.tampermonkey.net/documentation.php#GM_download
 */
function createSaveAsButton(url, name) {
    const btn = document.createElement("button");
    btn.type = "button";
    btn.innerText = "Save as";
    btn.style.display = "none";
    if(GM_download) {
        const type = /\.(\w{3,4})\?|\.(\w{3,4})$/;
        if(type.test(url)) {
			// For some reason, there sometimes 'undefined' in matched groups...
            const ext = type.exec(url).filter(el => el !== undefined)[1];
            assignClick(btn, url, name, ext);
        } else if(GM_xmlhttpRequest) {
            getExtensionThenAssignClick(btn, url, name);
        }
    }
    return btn;

    /**
     * Give the button its purpose.
     */
    function assignClick(btn, url, name, ext) {
        btn.addEventListener("click", () => GM_download({
            url: url,
            name: name + "." + ext,
            saveAs: true
        }));
        // The button is revealed as its construction is finished
        if(url && ext) {
            btn.style.display = "";
        }
    }

    /**
      * Use a convoluted XmlHttpRequest in order to get the image's extension when its not in the URL.
      * Usually needs a cross-scripting permission to access the CDN.
      * It also means the button will take time to appear.
      * https://www.tampermonkey.net/documentation.php#GM_xmlhttpRequest
      */
    function getExtensionThenAssignClick(btn, url, name) {
        GM_xmlhttpRequest({
            method: "get",
            url: url,
            onload: (data) => {
                const mimeType = /content-type: image\/(\w+)/;
                const headers = data.responseHeaders;
                if(mimeType.test(headers)) {
                    const ext = mimeType.exec(headers)[1].replace("jpeg", "jpg");
                    assignClick(btn, url, name, ext);
                }
            }
        });
    }
}

function processDeviantArt() {
    // DA does not refresh the whole page, use that instead
    const miniBrowse = document.querySelector(".minibrowse-container");
    const standardOutput = document.querySelector("#output");
    const context = miniBrowse || standardOutput;
	if(context) {
        const pic = context.querySelector(".dev-view-deviation");
        if(pic) {
            const page = document.title.substr(0, document.title.length - 14);
            const name = parseName(page);
            const dlcn = "dev-page-button dev-page-button-with-text dev-page-download";
            let dlbt = context.getElementsByClassName(dlcn)[0];
            if(dlbt) {
                dlbt.innerHTML = "<i></i><span class='label'>" + name + "</span>";
            }
            else {
                // The usual DA button is absent so we will recreate it
                const navzone = context.querySelector(".dev-meta-actions");
                const fullpic = context.querySelector(".dev-content-full");
                dlbt = document.createElement("a");
                dlbt.className = dlcn;
                dlbt.title = name;
                dlbt.href = fullpic.src;
                dlbt.style = "margin-top: -12px;";
                dlbt.innerHTML = "<i></i><span>" + name + "</span>";
                // Replicates normal button's behaviour: direct download
                // I suspect they don't show the button because the "download" attribute
                //     does nothing when it's on another domain.
                dlbt.addEventListener("click", event => {
                    event.preventDefault();
                    GM_download({
                        url: dlbt.href,
                        name: name
                    });
                });
                const devPage = document.createElement("div");
                devPage.className = "dev-page-view";
                devPage.appendChild(dlbt);
                navzone.appendChild(devPage);
            }
            selectText(dlbt.childNodes[1].firstChild);
            const sabt = createSaveAsButton(dlbt.href, name);
            dlbt.parentElement.appendChild(sabt);
        }
        // Thumbnail hopping support
        document.querySelectorAll(".thumb")
            .forEach(el => el.addEventListener("click", () => {
            setTimeout(() => processDeviantArt(), 1000);
        }));
	}
}

function processFuraffinity() {
	const actions = document.querySelector("#page-submission .actions");
	if(actions) {
		const page = document.title.substr(0, document.title.length - 26);
		const name = parseName(page);
		for(let i = 0 ; i < actions.childNodes.length ; ++i) {
			if(actions.childNodes[i].textContent.match("Download")) {
				const dlbt = actions.childNodes[i].childNodes[0];
				dlbt.title = name;
				dlbt.innerHTML = name;
				selectText(dlbt);
				const sabt = createSaveAsButton(dlbt.href, name);
				dlbt.parentElement.parentElement.appendChild(sabt);
				break;
			}
		}
	}
}

function processHentaiFoundry() {
	const boxfooter = document.querySelector("#picBox .boxfooter");
	if(boxfooter) {
		const page = document.title.substr(0, document.title.length - 17);
		const name = parseName(page);
		const title = document.createElement("span");
		title.style = "float: left; margin-right: 5px;";
		title.innerHTML = name;
		boxfooter.insertBefore(title, boxfooter.firstChild);
		const img = document.querySelector(".boxbody img.center");
		const url = img.onclick ? "https:" + /src='(.*?)'/.exec(img.onclick.toSource())[1] : img.src;
		const sabt = createSaveAsButton(url, name);
		boxfooter.parentElement.appendChild(sabt);
		selectText(title);
	}
}

function processInkbunny() {
    const pictop = document.querySelector("#pictop");
    if(pictop) {
        const page = document.title.substr(0, document.title.length - 49);
		const name = parseName(page);
        const title = pictop.querySelector("h1");
        title.innerText = name;
		selectText(title);
        const link = document.querySelector("#size_container div+a");
        // If there is no download link that means the image is already full size
        const url = link ? link.href : document.querySelector("#magicbox").src;
        const sabt = createSaveAsButton(url, name);
        title.parentElement.appendChild(sabt);
    }
}

window.addEventListener("load", function() {
	processDeviantArt();
	processFuraffinity();
	processHentaiFoundry();
	processInkbunny();
});