SankakuDLNamer

Help with DL naming

Fra og med 11.05.2024. Se den nyeste version.

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name        SankakuDLNamer
// @namespace   SankakuDLNamer
// @description Help with DL naming
// @author      SlimeySlither, sanchan
// @match       http*://chan.sankakucomplex.com/*posts/*
// @match       http*://idol.sankakucomplex.com/*posts/*
// @match       http*://beta.sankakucomplex.com/*posts/*
// @run-at      document-end
// @version     1.4.0
// @grant       GM_download
// ==/UserScript==

(function() {
	'use strict';

	const usePostId = false; // replaces hash with post ID if true
	const prefixPostId = false; // put post ID in front if true
	const maxEntries = 4;
	const showCopyFilenameButton = false; // workaround in case GM_download fails
	const debug = false;
	const tagDelimiter = ', ';

	function main() {
		const tags = getSidebarTags();
		if (debug) console.debug('tags', tags);

		const imageData = getImageData();
		if (debug) console.debug('imageData', imageData);

		const postId = getPostId();
		if (debug) console.debug('postId', postId);

		const downloadName = generateFilename(tags, imageData, postId);
		if (debug) console.debug('downloadName', downloadName);

		const details = getDLDetails(imageData, downloadName);
		if (debug) console.debug(details);

		if (showCopyFilenameButton) insertUnderDetails(createCopyFilenameButton(downloadName));
		insertUnderDetails(createDownloadButton(details));

         // Convert text links in <li> elements to lowercase
        convertTextLinksToLowercase();
	}

     function convertTextLinksToLowercase() {
    const sidebar = document.getElementById('tag-sidebar');
    if (!sidebar) return; // Exit if tag-sidebar element is not found

    const liElements = sidebar.querySelectorAll('ul > li');
    liElements.forEach(li => {
        const textLinks = li.querySelectorAll('a');
        textLinks.forEach(link => {
            link.textContent = link.textContent.toLowerCase();
        });
    });
}


	function createDownloadButton(details) {
		const a = document.createElement('a');
		a.href = '#';
		a.innerText = 'Download';
		a.onclick = function() {
			console.log('downloading...');
			details.onload = () => { console.log('download complete'); };
			details.ontimeout = () => { console.error('download timeout'); };
			details.onerror = (error, errorDetails) => {
				console.error('download failed', error, errorDetails);
				alert('download failed with ' + error);
			};
			details.onprogress = () => { if (debug) console.debug('.'); };
			GM_download(details);
			return false;
		};

		return a;
	}

	function createCopyFilenameButton(downloadName) {
		const a = document.createElement('a');
		a.href = '#';
		a.innerText = 'Copy Filename';
		a.onclick = function() {
			navigator.clipboard.writeText(downloadName);
			return false;
		};

		return a;
	}

	function insertUnderDetails(el) {
		const imageLink = document.getElementById('highres');
		if (!imageLink) throw new Error('couldn\'t find image link');

		const li = document.createElement('li');
		li.appendChild(el);

		insertNodeAfter(li, imageLink.parentNode);
	}

	function insertNodeAfter(node, ref_node) {
		ref_node.parentNode.insertBefore(node, ref_node.nextSibling);
	}

	function getPostId() {
		const pathname = window.location.pathname;
		const temp = pathname.endsWith('/') ? pathname.slice(0, -1) : pathname;
		return temp.substring(temp.lastIndexOf('/') + 1);
	}

	function cleanText(text) {
		// replace illegal filename characters https://stackoverflow.com/a/42210346
		return text.replaceAll(/[/\\?%*:|"<>]/g, '-');
	}

	function getSidebarTags() {
    const tagSidebar = document.getElementById('tag-sidebar');
    if (!tagSidebar) throw new Error('couldn\'t find tag-sidebar');

    const cats = {}; // category -> [tags]
    for (const tagItem of tagSidebar.getElementsByTagName('li')) {
        let tag;

        // find tag
        for (const tagLink of tagItem.getElementsByTagName('a')) {
            if (tagLink.hasAttribute('id')) {
                tag = cleanText(tagLink.innerText);
                break;
            }
        }

        if (tag) {
            let cat = cleanText(tagItem.className);

            // Convert category name to lowercase if it's 'tag-type-general'
            if (cat === 'tag-type-general') {
                cat = cat.toLowerCase();
            }

            // insert tag in its category
            if (!(cat in cats)) {
                cats[cat] = [tag];
            } else {
                cats[cat].push(tag);
            }
        }
    }

    return cats;
}

	function getImageData() {
		const imageLink = document.getElementById('highres');
		if (!imageLink) throw new Error('couldn\'t find image link');

		const url = new URL(imageLink.getAttribute('href'), document.baseURI);
		if (debug) console.log('image url', url);

		const filename = url.pathname.substring(url.pathname.lastIndexOf('/') + 1);
		if (debug) console.log('filename', filename);

		const j = filename.lastIndexOf('.');
		const hash = filename.substring(0, j);
		const extension = filename.substring(j); // including '.'

		return { url, hash, extension };
	}

	function sortAndShortenTagList(tags) {
		if (!tags) return;

		tags.sort();

		if (tags.length > maxEntries) {
			tags.splice(maxEntries);
			tags.push('...');
		}
	}

	function generateFilename(tags, imageData, postId) {
    let characters = tags['tag-type-character'];
    const copyrights = tags['tag-type-copyright'];
    const artists = tags['tag-type-artist'];

    if (characters) {
        // Remove round brackets from character tags
        for (let i = 0; i < characters.length; i++) {
            let j = characters[i].indexOf('(');
            if (j > 0) {
                if ([' ', '_'].includes(characters[i][j - 1])) j--;
                characters[i] = characters[i].substring(0, j);
            }
        }

        // Deduplicate
        characters = [...new Set(characters)];
    }

    sortAndShortenTagList(characters);
    sortAndShortenTagList(copyrights);
    sortAndShortenTagList(artists);

    const tokens = [];

    if (usePostId && prefixPostId) {
        tokens.push(postId);
        tokens.push('-');
    }

    if (characters) tokens.push(characters.join(tagDelimiter));
    if (copyrights) tokens.push('(' + copyrights.join(tagDelimiter) + ')');
    if (artists) {
        tokens.push('drawn by');
        tokens.push(artists.join(tagDelimiter));
    }

    if (!usePostId) {
        tokens.push(imageData.hash);
    } else if (!prefixPostId) {
        tokens.push(postId);
    }

    // Remove '-' if there's nothing after it
    if (tokens[tokens.length - 1] === '-') {
        tokens.splice(-1);
    }

    // Join tokens into a filename string, convert to lowercase, and append extension
    const filename = tokens.join(' ').toLowerCase() + imageData.extension;

    return filename;
}

	function getDLDetails(imageData, downloadName) {
		return {
			url: imageData.url.href,
			name: downloadName,
			saveAs: true,
		};
	}

	if (document.readyState === 'complete' || document.readyState === 'loaded' || document.readyState === 'interactive') {
		main();
	} else {
		document.addEventListener('DOMContentLoaded', main, false);
	}

})();