您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Add a copy button to copy all non-meta tags from major booru sites
// ==UserScript== // @name Universal Booru Tag Copier // @namespace http://tampermonkey.net/ // @version 1.01 // @description Add a copy button to copy all non-meta tags from major booru sites // @author Zelest Carlyone // @match https://danbooru.donmai.us/* // @match https://safebooru.donmai.us/* // @match https://gelbooru.com/* // @match https://*.gelbooru.com/* // @match https://rule34.xxx/* // @match https://e621.net/* // @match https://e926.net/* // @match https://konachan.*/* // @match https://yande.re/* // @match https://*.zerochan.net/* // @match https://*.sankakucomplex.com/* // @match https://safebooru.org/* // @grant none // @license MIT // ==/UserScript== (function () { "use strict"; function cleanTagName(tagName) { // List of face emoticons that should keep their underscores const emoticons = ["o_o", "0_0", "|_|", "._.", "^_^", ">_<", "@_@", ">_@", "+_+", "+_-", "=_=", "<o>_<o>", "<|>_<|>"]; // If it's an emoticon, keep it as-is if (emoticons.includes(tagName)) { return tagName; } // Otherwise, replace underscores with spaces return tagName.replace(/_/g, " "); } // Detect which site we're on function detectSite() { const hostname = window.location.hostname.toLowerCase(); // Danbooru family if (hostname.includes("danbooru") || hostname.includes("safebooru.donmai")) { return "danbooru"; } // Gelbooru family (includes rule34.xxx, safebooru.org, etc.) else if (hostname.includes("gelbooru") || hostname.includes("rule34.xxx") || hostname.includes("safebooru.org")) { return "gelbooru"; } // E621/E926 family else if (hostname.includes("e621") || hostname.includes("e926")) { return "e621"; } // Moebooru family (Konachan, Yande.re, etc.) else if (hostname.includes("konachan") || hostname.includes("yande.re")) { return "moebooru"; } // Sankaku family else if (hostname.includes("sankakucomplex")) { return "sankaku"; } // Zerochan else if (hostname.includes("zerochan")) { return "zerochan"; } return null; } // Get site-specific selectors function getSiteConfig(site) { if (site === "danbooru") { return { tagSection: "#tag-list", categories: [ { selector: "ul.general-tag-list li", name: "general" }, { selector: "ul.character-tag-list li", name: "character" }, { selector: "ul.copyright-tag-list li", name: "copyright" }, { selector: "ul.artist-tag-list li", name: "artist" }, ], getTagName: (item) => { // Try data-tag-name first const dataName = item.getAttribute("data-tag-name"); if (dataName) return dataName; // Fallback: try to get from search link const searchLink = item.querySelector("a.search-tag"); if (searchLink) return searchLink.textContent?.trim(); return null; }, }; } else if (site === "gelbooru") { return { tagSection: "#tag-list, #tag-sidebar", categories: [ { selector: "li.tag-type-general", name: "general" }, { selector: "li.tag-type-character", name: "character" }, { selector: "li.tag-type-copyright", name: "copyright" }, { selector: "li.tag-type-artist", name: "artist" }, ], getTagName: (item) => { const link = item.querySelector('a[href*="tags="]'); return link ? link.textContent?.trim() : null; }, }; } else if (site === "e621") { return { tagSection: "#tag-list", categories: [ { selector: "ul.general-tag-list li.tag-list-item", name: "general" }, { selector: "ul.character-tag-list li.tag-list-item", name: "character" }, { selector: "ul.copyright-tag-list li.tag-list-item", name: "copyright" }, { selector: "ul.artist-tag-list li.tag-list-item", name: "artist" }, ], getTagName: (item) => { const nameAttr = item.getAttribute("data-name"); if (nameAttr) return nameAttr; const nameSpan = item.querySelector(".tag-list-name"); return nameSpan ? nameSpan.textContent?.trim() : null; }, }; } else if (site === "moebooru") { return { tagSection: "#tag-sidebar", categories: [ { selector: "li.tag-type-general", name: "general" }, { selector: "li.tag-type-character", name: "character" }, { selector: "li.tag-type-copyright", name: "copyright" }, { selector: "li.tag-type-artist", name: "artist" }, ], getTagName: (item) => { const nameAttr = item.getAttribute("data-name"); if (nameAttr) return nameAttr; const link = item.querySelector('a[href*="tags="]'); return link ? link.textContent?.trim() : null; }, }; } else if (site === "sankaku") { return { tagSection: "#tag-sidebar, .tag-sidebar", categories: [ { selector: 'li[class*="tag-type-general"]', name: "general" }, { selector: 'li[class*="tag-type-character"]', name: "character" }, { selector: 'li[class*="tag-type-copyright"]', name: "copyright" }, { selector: 'li[class*="tag-type-artist"]', name: "artist" }, ], getTagName: (item) => { const link = item.querySelector("a"); return link ? link.textContent?.trim() : null; }, }; } else if (site === "zerochan") { return { tagSection: "#tags, .tags", categories: [{ selector: 'a[href*="/"]', name: "general" }], getTagName: (item) => item.textContent?.trim(), }; } return null; } // Wait for the page to load and add button function addCopyButton() { const site = detectSite(); if (!site) { console.log("Tag Copier: Unsupported site"); return; } console.log(`Tag Copier: Detected site: ${site}`); const config = getSiteConfig(site); console.log("Tag Copier: Looking for tag list..."); // Try to find the tag list section const tagSection = document.querySelector(config.tagSection); if (!tagSection) { console.log("Tag Copier: No tag section found"); return; } console.log("Tag Copier: Found tag section!"); // Check if button already exists if (document.querySelector("#tag-copy-button")) { console.log("Tag Copier: Button already exists"); return; } console.log("Tag Copier: Adding copy button..."); // Create the copy button const copyButton = document.createElement("button"); copyButton.id = "tag-copy-button"; copyButton.innerHTML = "📋 Copy Tags"; copyButton.style.cssText = ` position: absolute; top: 5px; right: 5px; background: #0073e6; color: white; border: none; padding: 5px 10px; border-radius: 4px; cursor: pointer; font-size: 12px; z-index: 1000; font-family: sans-serif; `; // Add hover effect copyButton.addEventListener("mouseenter", () => { copyButton.style.background = "#005bb5"; }); copyButton.addEventListener("mouseleave", () => { copyButton.style.background = "#0073e6"; }); // Add click handler copyButton.addEventListener("click", () => copyTags(site)); // Make tag section container relative positioned so button positions correctly tagSection.style.position = "relative"; // Add button to the tag section tagSection.appendChild(copyButton); console.log("Tag Copier: Button added successfully!"); } function copyTags(site) { console.log("Tag Copier: Copy button clicked!"); const config = getSiteConfig(site); const tags = []; config.categories.forEach((category) => { console.log(`Tag Copier: Looking for ${category.name} tags...`); // Universal approach: just look for the selector and extract tags const tagItems = document.querySelectorAll(category.selector); console.log(`Tag Copier: Found ${tagItems.length} ${category.name} tags`); tagItems.forEach((item) => { const tagName = config.getTagName(item); if (tagName && tagName.length > 0) { let cleanedTag = cleanTagName(tagName); // Add "artist:" prefix for artist tags if (category.name === "artist") { cleanedTag = "artist:" + cleanedTag; } tags.push(cleanedTag); console.log(`Tag Copier: Added tag: ${tagName} -> ${cleanedTag}`); } }); }); console.log(`Tag Copier: Total tags collected: ${tags.length}`); console.log("Tag Copier: Tags:", tags); // Join tags with commas and copy to clipboard const tagString = tags.join(", "); console.log("Tag Copier: Final tag string:", tagString); if (tagString.length === 0) { console.log("Tag Copier: No tags found to copy!"); return; } // Copy to clipboard if (navigator.clipboard && navigator.clipboard.writeText) { navigator.clipboard .writeText(tagString) .then(() => { console.log("Tag Copier: Successfully copied to clipboard"); showFeedback("✅ Copied!", "#28a745"); }) .catch((err) => { console.error("Tag Copier: Failed to copy tags:", err); fallbackCopy(tagString); }); } else { console.log("Tag Copier: Using fallback copy method"); fallbackCopy(tagString); } } function fallbackCopy(text) { // Fallback for older browsers const textArea = document.createElement("textarea"); textArea.value = text; textArea.style.position = "fixed"; textArea.style.left = "-999999px"; textArea.style.top = "-999999px"; document.body.appendChild(textArea); textArea.focus(); textArea.select(); try { const result = document.execCommand("copy"); if (result) { console.log("Tag Copier: Fallback copy successful"); showFeedback("✅ Copied!", "#28a745"); } else { console.log("Tag Copier: Fallback copy failed"); showFeedback("❌ Copy failed", "#dc3545"); } } catch (err) { console.error("Tag Copier: Fallback copy error:", err); showFeedback("❌ Copy failed", "#dc3545"); } document.body.removeChild(textArea); } function showFeedback(message, color) { const button = document.querySelector("#tag-copy-button"); if (!button) return; const originalText = button.innerHTML; const originalColor = button.style.background; button.innerHTML = message; button.style.background = color; setTimeout(() => { button.innerHTML = originalText; button.style.background = originalColor; }, 1500); } // Initialize when DOM is ready function initialize() { if (document.readyState === "loading") { document.addEventListener("DOMContentLoaded", addCopyButton); } else { addCopyButton(); } // Also run when navigating (for single-page app behavior) let lastUrl = location.href; new MutationObserver(() => { const url = location.href; if (url !== lastUrl) { lastUrl = url; setTimeout(addCopyButton, 500); // Small delay for content to load } }).observe(document, { subtree: true, childList: true }); } // Start the script initialize(); })();