您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
File naming for rule34
// ==UserScript== // @name Rule34DLNamer (modified version of SankakuDLNamer) // @namespace Rule34DLNamer // @description File naming for rule34 // @author SlimeySlither, sanchan, Dramorian // @match http*://rule34.xxx/index.php?page=post* // @icon https://www.google.com/s2/favicons?sz=64&domain=rule34.xxx // @run-at document-end // @version 1.0 // @grant GM_download // @license MIT // ==/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)); } 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 tagSearchDiv = document.querySelector('div.tag-search'); if (!tagSearchDiv) throw new Error('couldn\'t find .tag-search div'); const li = document.createElement('li'); li.appendChild(el); tagSearchDiv.appendChild(li); } function tagSearchDiv(node, ref_node) { ref_node.parentNode.insertBefore(node, ref_node.nextSibling); } function getPostId() { const urlParams = new URLSearchParams(window.location.search); return urlParams.get('id') || ''; // Return an empty string if 'id' is not found } 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 the second <a> tag within the <li> element const tagLinks = tagItem.getElementsByTagName('a'); if (tagLinks.length > 1) { tag = cleanText(tagLinks[1].innerText); // get text from the second <a> tag } if (tag) { let cat = cleanText(tagItem.className); // insert tag in its category if (!(cat in cats)) { cats[cat] = [tag]; } else { cats[cat].push(tag); } } } return cats; } function getImageData() { const imageLink = document.querySelector('a[href*="/images/"]'); 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 tag']; const copyrights = tags['tag-type-copyright tag']; const artists = tags['tag-type-artist tag']; 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('-'); // Add dash before the hash tokens.push(imageData.hash); } else if (!prefixPostId) { tokens.push('-'); // Add dash before the postId 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(' ') + 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); } })();