Sleazy Fork is available in English.

e621/danbooru tag extractor

copy tags from e621

// ==UserScript==
// @name         e621/danbooru tag extractor
// @namespace    http://tampermonkey.net/
// @version      1.2
// @description  copy tags from e621
// @author       NorthPolarise
// @include      https://e621.net/posts/*
// @include      https://danbooru.donmai.us/posts/*
// @grant        none
// @license      GPLv3
// ==/UserScript==

(function() {
    'use strict';

    // 添加 CSS 样式
    const style = document.createElement('style');
    style.textContent = `
        .copy-button {
            background-color: #2e51a2;
            color: white;
            border: none;
            padding: 10px 20px;
            border-radius: 5px;
            cursor: pointer;
            font-size: 14px;
            font-weight: 500;
            position: relative;
            overflow: hidden;
            transition: all 0.3s ease;
            box-shadow: 0 2px 5px rgba(0,0,0,0.2);
        }

        .copy-button:hover {
            transform: translateY(-2px);
            box-shadow: 0 4px 8px rgba(0,0,0,0.2);
        }

        .copy-button:active {
            transform: translateY(0);
        }

        .copy-button::after {
            content: '';
            position: absolute;
            width: 100%;
            height: 100%;
            top: 0;
            left: -100%;
            background: linear-gradient(90deg, transparent, rgba(255,255,255,0.2), transparent);
            transition: 0.5s;
        }

        .copy-button.clicked::after {
            left: 100%;
        }

        .success-animation {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%) scale(0);
            width: 20px;
            height: 20px;
            background-color: #4CAF50;
            border-radius: 50%;
            opacity: 0;
        }

        .copy-button.success .success-animation {
            animation: successPop 0.5s ease-out forwards;
        }

        @keyframes successPop {
            0% {
                transform: translate(-50%, -50%) scale(0);
                opacity: 1;
            }
            50% {
                transform: translate(-50%, -50%) scale(1.2);
                opacity: 0.5;
            }
            100% {
                transform: translate(-50%, -50%) scale(1);
                opacity: 0;
            }
        }
        .tag-row {
            transition: transform 0.2s ease, box-shadow 0.2s ease;
            position: relative;
            z-index: 1;
        }

        .tag-row:hover {
            transform: translateX(5px);
            box-shadow: -2px 2px 5px rgba(0,0,0,0.2);
        }

        .tag-row.dragging {
            opacity: 0.5;
            box-shadow: 0 5px 15px rgba(0,0,0,0.3);
            z-index: 10;
        }

        .tag-row.drag-over {
            transform: translateY(40px);
        }

        .tag-row.drag-over-up {
            transform: translateY(-40px);
        }

        .placeholder {
            height: 40px;
            background-color: rgba(46, 81, 162, 0.2);
            border: 2px dashed #2e51a2;
            border-radius: 3px;
            margin: 5px 0;
            transition: all 0.2s ease;
        }
    `;
    document.head.appendChild(style);

    createInterface();

    // 加载保存的选择状态和顺序
    function loadSavedState(tagList) {
        const savedOrder = JSON.parse(localStorage.getItem('tagOrder') || '[]');
        const savedChecked = JSON.parse(localStorage.getItem('tagChecked') || '{}');

        if (savedOrder.length > 0) {
            // 按保存的顺序重新排列标签
            savedOrder.forEach(tagName => {
                const tagRow = Array.from(tagList.children).find(row =>
                                                                 row.querySelector('span').textContent === tagName
                                                                );
                if (tagRow) {
                    tagList.appendChild(tagRow);
                }
            });
        }

        // 恢复选中状态
        Array.from(tagList.children).forEach(tagRow => {
            const label = tagRow.querySelector('span').textContent;
            const checkbox = tagRow.querySelector('input');
            if (savedChecked[label] !== undefined) {
                checkbox.checked = savedChecked[label];
            }
        });
    }

    // 保存当前状态
    function saveCurrentState(tagList) {
        const currentOrder = Array.from(tagList.children).map(row =>
                                                              row.querySelector('span').textContent
                                                             );
        const currentChecked = {};
        Array.from(tagList.children).forEach(row => {
            const label = row.querySelector('span').textContent;
            currentChecked[label] = row.querySelector('input').checked;
        });

        localStorage.setItem('tagOrder', JSON.stringify(currentOrder));
        localStorage.setItem('tagChecked', JSON.stringify(currentChecked));
    }

    function createInterface() {
        // 创建主容器
        const container = document.createElement('div');
        container.style.position = 'fixed';
        container.style.bottom = '20px';
        container.style.right = '10px';
        container.style.zIndex = '9999';
        container.style.backgroundColor = '#284a81';
        container.style.borderRadius = '5px';
        container.style.boxShadow = '0 2px 5px rgba(0,0,0,0.2)';

        // 创建头部(始终可见的部分)
        const header = document.createElement('div');
        header.style.padding = '10px';
        header.style.borderBottom = '1px solid #2e51a2';
        header.style.display = 'flex';
        header.style.gap = '10px';
        header.style.alignItems = 'center';

        // 创建展开/收起按钮
        const toggleButton = document.createElement('button');
        toggleButton.textContent = '展开';
        toggleButton.className = 'copy-button';
        toggleButton.style.padding = '5px 10px';
        toggleButton.style.backgroundColor = '#2e51a2';
        toggleButton.style.color = 'white';
        toggleButton.style.border = 'none';
        toggleButton.style.borderRadius = '3px';
        toggleButton.style.cursor = 'pointer';

        // 创建复制按钮
        const copyButton = document.createElement('button');
        copyButton.textContent = '复制标签';
        copyButton.className = 'copy-button';
        copyButton.style.padding = '5px 10px';
        copyButton.style.backgroundColor = '#2e51a2';
        copyButton.style.color = 'white';
        copyButton.style.border = 'none';
        copyButton.style.borderRadius = '3px';
        copyButton.style.cursor = 'pointer';

        // 创建标签列表容器
        const content = document.createElement('div');
        content.style.display = 'none';
        content.style.padding = '10px';

        const tagList = document.createElement('div');
        tagList.style.display = 'flex';
        tagList.style.flexDirection = 'column';
        tagList.style.gap = '5px';

        const tagTypes = [
            { id: 'general', name: '一般标签 (General)', class: 'tag-type-0' },
            { id: 'artist', name: '艺术家标签 (Artist)', class: 'tag-type-1' },
            { id: 'character', name: '角色标签 (Character)', class: 'tag-type-4' },
            { id: 'species', name: '物种标签 (Species)', class: 'tag-type-5' },
            { id: 'copyright', name: '版权标签 (Copyright)', class: 'tag-type-3' },
            { id: 'meta', name: '元标签 (Meta)', class: 'tag-type-7' }
        ];

        tagTypes.forEach((type, index) => {
            const tagRow = document.createElement('div');
            tagRow.className = 'tag-row';
            tagRow.draggable = true;
            tagRow.style.display = 'flex';
            tagRow.style.alignItems = 'center';
            tagRow.style.padding = '5px';
            tagRow.style.backgroundColor = '#1d3b8f';
            tagRow.style.borderRadius = '3px';
            tagRow.style.cursor = 'move';
            tagRow.dataset.class = type.class;

            const checkbox = document.createElement('input');
            checkbox.type = 'checkbox';
            checkbox.style.marginRight = '10px';
            // 添加 change 事件监听器
            checkbox.addEventListener('change', () => {
                saveCurrentState(tagList);
            });


            const label = document.createElement('span');
            label.textContent = type.name;
            label.style.color = 'white';
            label.style.flex = '1';

            tagRow.appendChild(checkbox);
            tagRow.appendChild(label);
            tagList.appendChild(tagRow);

            // 添加拖拽事件
            tagRow.addEventListener('dragstart', handleDragStart);
            tagRow.addEventListener('dragover', handleDragOver);
            tagRow.addEventListener('dragend', handleDragEnd)
            tagRow.addEventListener('drop', handleDrop);
            tagRow.addEventListener('dragenter', (e) => e.preventDefault());
        });

        // 事件处理
        toggleButton.addEventListener('click', function() {
            const isHidden = content.style.display === 'none';
            content.style.display = isHidden ? 'block' : 'none';
            toggleButton.textContent = isHidden ? '收起' : '展开';
        });

        copyButton.addEventListener('click', function() {
            extractTags();
            copyButton.style.backgroundColor = '#1d3b8f';
            setTimeout(() => {
                copyButton.style.backgroundColor = '#2e51a2';
            }, 200);
        });

        // 加载保存的状态
        loadSavedState(tagList);

        // 组装界面
        header.appendChild(toggleButton);
        header.appendChild(copyButton);
        content.appendChild(tagList);
        container.appendChild(header);
        container.appendChild(content);
        document.body.appendChild(container);
    }

    let dragSrcElement = null;
    let placeholder = null;

    function createPlaceholder() {
        const div = document.createElement('div');
        div.className = 'placeholder';
        return div;
    }

    function handleDragStart(e) {
        dragSrcElement = this;
        e.dataTransfer.effectAllowed = 'move';
        e.dataTransfer.setData('text/html', this.outerHTML);

        // 创建占位符
        placeholder = createPlaceholder();

        // 添加拖动样式
        this.classList.add('dragging');

        // 延迟添加占位符,使动画更流畅
        requestAnimationFrame(() => {
            this.parentNode.insertBefore(placeholder, this);
            this.style.display = 'none';
        });
    }

    function handleDragOver(e) {
        e.preventDefault();
        e.dataTransfer.dropEffect = 'move';

        const tagRow = this;
        if (tagRow === dragSrcElement || tagRow === placeholder) {
            return;
        }

        // 获取鼠标在元素上的相对位置
        const rect = tagRow.getBoundingClientRect();
        const midpoint = rect.top + (rect.height / 2);
        const moveUp = e.clientY < midpoint;

        // 移除所有其他元素的过渡类
        document.querySelectorAll('.tag-row').forEach(row => {
            row.classList.remove('drag-over', 'drag-over-up');
        });

        // 移动占位符
        if (placeholder) {
            if (moveUp) {
                tagRow.parentNode.insertBefore(placeholder, tagRow);
            } else {
                tagRow.parentNode.insertBefore(placeholder, tagRow.nextSibling);
            }
        }

        return false;
    }

    function handleDragEnd(e) {
        this.classList.remove('dragging');
        this.style.display = '';

        // 移除所有过渡类
        document.querySelectorAll('.tag-row').forEach(row => {
            row.classList.remove('drag-over', 'drag-over-up');
        });

        // 移除占位符
        if (placeholder && placeholder.parentNode) {
            placeholder.parentNode.insertBefore(this, placeholder);
            placeholder.remove();
        }

        placeholder = null;
        dragSrcElement = null;
    }

    function handleDrop(e) {
        e.preventDefault();
        e.stopPropagation();

        if (dragSrcElement !== this) {
            // 位置已经由 dragover 事件处理,这里只需确保元素可见
            dragSrcElement.style.display = '';
        }

        // 保存当前状态
    const tagList = this.parentNode;

    saveCurrentState(tagList);

    return false;
    }

    // 修改复制函数
    function extractTags() {
        const tagContainer = document.querySelector('#tag-list');
        if (!tagContainer) {
            alert('未找到标签容器!');
            return;
        }

        let allTags = [];
        const tagClasses = {
            'general': '.general-tag-list .search-tag',
            'artist': '.artist-tag-list .search-tag',
            'character': '.character-tag-list .search-tag',
            'species': '.species-tag-list .search-tag',
            'copyright': '.copyright-tag-list .search-tag',
            'meta': '.meta-tag-list .search-tag'
        };

        const tagRows = document.querySelectorAll('.tag-row');
        tagRows.forEach(row => {
            if (row.querySelector('input').checked) {
                const label = row.querySelector('span').textContent;
                let type = '';

                if (label.includes('一般标签')) type = 'general';
                else if (label.includes('艺术家标签')) type = 'artist';
                else if (label.includes('角色标签')) type = 'character';
                else if (label.includes('物种标签')) type = 'species';
                else if (label.includes('版权标签')) type = 'copyright';
                else if (label.includes('元标签')) type = 'meta';

                if (type && tagClasses[type]) {
                    const tags = Array.from(document.querySelectorAll(tagClasses[type]))
                    .map(tag => tag.textContent.trim())
                    .filter(tag => tag)
                    .sort();
                    allTags = allTags.concat(tags);
                }
            }
        });

        if (allTags.length === 0) {
            alert('请至少选择一个标签类型!');
            return;
        }

        const tagsText = allTags.join(',') + ',';

        navigator.clipboard.writeText(tagsText).then(() => {
            const copyButton = document.querySelector('#copy-button');
            if (copyButton) {
                // 添加点击动画效果
                copyButton.classList.add('clicked');
                copyButton.classList.add('success');

                // 创建成功动画元素
                const successAnim = document.createElement('div');
                successAnim.className = 'success-animation';
                copyButton.appendChild(successAnim);

                // 重置动画状态
                setTimeout(() => {
                    copyButton.classList.remove('clicked');
                    copyButton.classList.remove('success');
                    if (successAnim.parentNode === copyButton) {
                        copyButton.removeChild(successAnim);
                    }
                }, 1000);
            }
        }).catch(err => {
            console.error('复制失败:', err);
            alert('复制失败,请手动复制:\n' + tagsText);
        });
    }

    // 创建按钮
    function createCopyButton() {
        const button = document.createElement('button');
        button.id = 'copy-button';
        button.className = 'copy-button';
        button.textContent = '复制标签';
        button.onclick = extractTags;
        return button;
    }
})();