DeepDanbooru 魔法串生成器

基于DeepDanbooru生成nai魔法串

当前为 2025-06-11 提交的版本,查看 最新版本

// ==UserScript==
// @name         DeepDanbooru 魔法串生成器
// @namespace    http://tampermonkey.net/
// @version      1.01
// @description  基于DeepDanbooru生成nai魔法串
// @author       a1606
// @license      MIT
// @match        http://dev.kanotype.net:8003/deepdanbooru/view/general/*
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_setClipboard
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    const STATE = {
        roles: [],
        presets: [],
        artists: [],
        ignore: [],
        selectedRoles: new Set(),
        selectedPresets: new Set(),
        selectedArtists: new Set(),
        selectedTags: new Set()
    };

    async function loadStorage() {
        STATE.roles = await GM_getValue('custom_roles', []);
        STATE.presets = await GM_getValue('custom_presets', []);
        STATE.artists = await GM_getValue('custom_artists', []);
        STATE.ignore = await GM_getValue('ignored_tags', []);
        STATE.selectedRoles = new Set(await GM_getValue('selected_roles', []));
        STATE.selectedPresets = new Set(await GM_getValue('selected_presets', []));
        STATE.selectedArtists = new Set(await GM_getValue('selected_artists', []));
    }

    async function saveStorage() {
        await GM_setValue('custom_roles', STATE.roles);
        await GM_setValue('custom_presets', STATE.presets);
        await GM_setValue('custom_artists', STATE.artists);
        await GM_setValue('ignored_tags', STATE.ignore);
        await GM_setValue('selected_roles', [...STATE.selectedRoles]);
        await GM_setValue('selected_presets', [...STATE.selectedPresets]);
        await GM_setValue('selected_artists', [...STATE.selectedArtists]);
    }

    function createButtonBar(tagTable) {
        const wrapper = document.createElement('div');
        wrapper.style.margin = '10px 0';

        const copyBtn = document.createElement('button');
        copyBtn.textContent = '🧙 生成魔法串';
        copyBtn.style.marginRight = '10px';

        const settingBtn = document.createElement('button');
        settingBtn.textContent = '⚙️ 设置';

        copyBtn.onclick = () => generateMagic(tagTable);
        settingBtn.onclick = openSettingsPanel;

        wrapper.append(copyBtn, settingBtn);
        tagTable.parentNode.insertBefore(wrapper, tagTable);

        const rows = tagTable.querySelectorAll('tr');
        rows.forEach(row => {
            const cells = row.querySelectorAll('td');
            if (cells.length >= 2) {
                const tag = cells[0].textContent.trim();
                const checkbox = document.createElement('input');
                checkbox.type = 'checkbox';
                checkbox.checked = true;
                checkbox.style.marginRight = '6px';
                cells[0].prepend(checkbox);
                checkbox.dataset.tag = tag;
            }
        });
    }

    function splitNoteAndContent(item) {
        const idx = item.indexOf('::');
        return idx !== -1 ? [item.slice(0, idx), item.slice(idx + 2)] : [null, item];
    }

    function generateMagic(tagTable) {
        const rows = tagTable.querySelectorAll('tr');
        const tagList = [];

        rows.forEach(row => {
            const cells = row.querySelectorAll('td');
            if (cells.length >= 2) {
                const tag = cells[0].textContent.trim();
                const checkbox = cells[0].querySelector('input[type="checkbox"]');
                const score = parseFloat(cells[1].textContent.trim()).toFixed(2);
                if (
                    checkbox?.checked &&
                    !tag.includes(':') &&
                    !STATE.ignore.includes(tag)
                ) {
                    tagList.push(`${tag}:${score}`);
                }
            }
        });

        const parts = [];
        if (STATE.selectedRoles.size > 0)
            parts.push([...STATE.selectedRoles].map(e => splitNoteAndContent(e)[1]).join(' '));

        if (STATE.selectedPresets.size > 0)
            parts.push([...STATE.selectedPresets].map(e => splitNoteAndContent(e)[1]).join(' '));

        parts.push(tagList.join(', '));

        if (STATE.selectedArtists.size > 0)
            parts.push([...STATE.selectedArtists].map(e => splitNoteAndContent(e)[1]).join(' '));

        const final = parts.join('\n');
        GM_setClipboard(final);
        alert('✅ 魔法串已复制!');
    }

    function enableDragSorting(wrapper, list, selectedSet, isTextOnly, key) {
        let draggingElem;

        wrapper.addEventListener('dragstart', e => {
            if (e.target.classList.contains('draggable-item')) {
                draggingElem = e.target;
                e.dataTransfer.effectAllowed = 'move';
            }
        });

        wrapper.addEventListener('dragover', e => {
            e.preventDefault();
            const target = e.target.closest('.draggable-item');
            if (target && target !== draggingElem) {
                const rect = target.getBoundingClientRect();
                const next = (e.clientY - rect.top) > rect.height / 2;
                target.parentNode.insertBefore(draggingElem, next ? target.nextSibling : target);
            }
        });

        wrapper.addEventListener('drop', async () => {
            const items = [...wrapper.querySelectorAll('.draggable-item')].map(row => {
                const span = row.querySelector('span');
                const textarea = row.querySelector('textarea');
                return textarea ? `${span.textContent}::${textarea.value}` : span.textContent;
            });
            list.splice(0, list.length, ...items);
            await saveStorage();
        });
    }

    function createManager(label, key, list, selectedSet, isTextOnly = false) {
        const wrapper = document.createElement('div');
        wrapper.style.margin = '10px 0';

        const row1 = document.createElement('div');
        row1.style.display = 'flex';
        row1.style.alignItems = 'center';
        row1.style.marginBottom = '4px';

        const row2 = document.createElement('div');
        row2.style.display = 'flex';
        row2.style.alignItems = 'center';

        const noteInput = document.createElement('input');
        noteInput.placeholder = `备注`;
        noteInput.style.marginRight = '5px';
        noteInput.style.flex = '1';

        const addBtn = document.createElement('button');
        addBtn.textContent = '添加';
        addBtn.style.width = '50px';
        addBtn.style.height = '30px';
        addBtn.onclick = async () => {
            const note = noteInput.value.trim();
            const content = contentInput.value.trim();
            const final = note ? `${note}::${content}` : content;
            if (content && !list.includes(final)) {
                list.push(final);
                if (selectedSet) selectedSet.add(final);
                await saveStorage();
                wrapper.append(renderItemRow(final, selectedSet, list, isTextOnly));
                noteInput.value = '';
                contentInput.value = '';
            }
        };

        const contentInput = document.createElement('textarea');
        contentInput.placeholder = `内容(将作为输出 tag)`;
        contentInput.style.flex = '1';
        contentInput.style.height = '96px';
        contentInput.style.overflowY = 'scroll';
        contentInput.style.resize = 'none';

        row1.append(noteInput, addBtn);
        row2.append(contentInput);
        wrapper.append(row1, row2);

        list.forEach(item => wrapper.append(renderItemRow(item, selectedSet, list, isTextOnly)));

        enableDragSorting(wrapper, list, selectedSet, isTextOnly, key);

        return wrapper;
    }

    function renderItemRow(item, selectedSet, list, isTextOnly) {
        const row = document.createElement('div');
        row.className = 'draggable-item';
        row.draggable = true;
        row.style.display = 'flex';
        row.style.alignItems = 'center';
        row.style.marginTop = '4px';
        row.style.flexDirection = 'column';
        row.style.border = '1px dashed #ccc';
        row.style.padding = '2px';

        const [note, content] = item.includes('::') ? item.split('::') : [null, item];

        const top = document.createElement('div');
        top.style.display = 'flex';
        top.style.alignItems = 'center';
        top.style.width = '100%';

        const checkbox = document.createElement('input');
        checkbox.type = 'checkbox';
        checkbox.checked = selectedSet?.has(item);
        checkbox.disabled = isTextOnly;
        checkbox.onchange = async () => {
            if (checkbox.checked) selectedSet.add(item);
            else selectedSet.delete(item);
            await saveStorage();
        };

        const label = document.createElement('span');
        label.textContent = note || content;
        label.style.margin = '0 5px';
        label.style.flex = '1';

        const toggleBtn = document.createElement('button');
        toggleBtn.textContent = '▸';
        toggleBtn.style.border = 'none';
        toggleBtn.style.background = 'transparent';
        toggleBtn.style.cursor = 'pointer';
        toggleBtn.style.height = '24px';
        toggleBtn.style.width = '24px';
        toggleBtn.style.padding = '0';
        toggleBtn.onclick = () => {
            const expanded = toggleBtn.textContent === '▾';
            toggleBtn.textContent = expanded ? '▸' : '▾';
            detailBox.style.display = expanded ? 'none' : 'block';
        };

        const delBtn = document.createElement('button');
        delBtn.textContent = '✖';
        delBtn.style.border = 'none';
        delBtn.style.background = 'transparent';
        delBtn.style.fontSize = '10px';
        delBtn.style.width = '24px';
        delBtn.style.height = '24px';
        delBtn.onclick = async () => {
            if (!confirm(`是否要删除${note || content}?`)) return;
            const idx = list.indexOf(item);
            if (idx > -1) list.splice(idx, 1);
            selectedSet?.delete(item);
            await saveStorage();
            row.remove();
        };

        const detailBox = document.createElement('textarea');
        detailBox.style.display = 'none';
        detailBox.style.fontSize = '12px';
        detailBox.style.color = '#333';
        detailBox.style.margin = '5px 0 0 20px';
        detailBox.style.width = '90%';
        detailBox.style.height = '96px';
        detailBox.style.overflowY = 'scroll';
        detailBox.style.resize = 'none';
        detailBox.value = content;

        top.append(checkbox, label, toggleBtn, delBtn);
        row.append(top, detailBox);

        return row;
    }

    function openSettingsPanel() {
        let panel = document.querySelector('#magic-settings');
        if (panel) return panel.style.display = 'block';

        panel = document.createElement('div');
        panel.id = 'magic-settings';
        panel.style.position = 'fixed';
        panel.style.top = '50px';
        panel.style.left = '10px';
        panel.style.background = 'white';
        panel.style.border = '1px solid gray';
        panel.style.padding = '10px';
        panel.style.zIndex = '9999';
        panel.style.width = '320px';

        const closeBtn = document.createElement('button');
        closeBtn.textContent = '关闭';
        closeBtn.onclick = () => { panel.style.display = 'none'; };

        const tabMenu = document.createElement('div');
        tabMenu.style.display = 'flex';
        tabMenu.style.gap = '10px';
        tabMenu.style.marginBottom = '10px';

        const contentWrapper = document.createElement('div');

        const tabs = [
            { label: '角色', key: 'custom_roles', list: STATE.roles, selected: STATE.selectedRoles },
            { label: '预设', key: 'custom_presets', list: STATE.presets, selected: STATE.selectedPresets },
            { label: '艺术家', key: 'custom_artists', list: STATE.artists, selected: STATE.selectedArtists },
            { label: '忽略Tag', key: 'ignored_tags', list: STATE.ignore, selected: null, isTextOnly: true }
        ];

        tabs.forEach((tab, idx) => {
            const btn = document.createElement('button');
            btn.textContent = tab.label;
            btn.style.padding = '2px 6px';
            btn.onclick = () => {
                [...tabMenu.children].forEach(b => b.style.background = '');
                btn.style.background = '#def';
                contentWrapper.innerHTML = '';
                contentWrapper.append(createManager(tab.label, tab.key, tab.list, tab.selected, tab.isTextOnly));
            };
            if (idx === 0) btn.style.background = '#def';
            tabMenu.appendChild(btn);
        });

        contentWrapper.append(createManager(tabs[0].label, tabs[0].key, tabs[0].list, tabs[0].selected));

        panel.append(tabMenu, contentWrapper, closeBtn);
        document.body.appendChild(panel);
    }

    async function init() {
        const container = document.querySelector('.container');
        const tagTable = container?.querySelector('table');
        if (!tagTable) return setTimeout(init, 500);
        await loadStorage();
        createButtonBar(tagTable);
    }

    init();
})();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址