即梦下载无水印原图

为即梦添加按钮,可以直接下载无水印原图

// ==UserScript==
// @name         即梦下载无水印原图
// @namespace    http://tampermonkey.net/
// @version      1.3
// @description  为即梦添加按钮,可以直接下载无水印原图
// @author       psdoc烛光
// @match        https://jimeng.jianying.com/*
// @icon         https://favicon.im/jimeng.jianying.com?larger=true&t=1755764200795
// @grant        GM_download
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';

    // 全局变量,存储观察者实例
    let globalObserver = null;
    let retryCount = 0;
    const MAX_RETRY = 5;
    const RETRY_DELAY = 100; // 重试间隔

    /**
     * 等待DOM加载完成后解析
     * @returns {Promise} - 返回一个Promise,表示DOM加载完成的状态
     */
    function waitForDOM() {
        return new Promise(resolve => {
            if (document.readyState === 'complete' || document.readyState === 'interactive') {
                resolve();
            } else {
                document.addEventListener('DOMContentLoaded', resolve);
            }
        });
    }

    /**
     * 等待目标容器出现
     * @param {string} selector - 目标容器的CSS选择器
     * @param {number} timeout - 超时时间(毫秒)
     * @returns {Promise<Element>} - 返回找到的元素
     */
    function waitForElement(selector, timeout = 1000) {
        return new Promise((resolve, reject) => {
            const element = document.querySelector(selector);
            if (element) {
                resolve(element);
                return;
            }

            const observer = new MutationObserver((mutations) => {
                const targetElement = document.querySelector(selector);
                if (targetElement) {
                    observer.disconnect();
                    resolve(targetElement);
                }
            });

            observer.observe(document.body, {
                childList: true,
                subtree: true
            });

            // 设置超时
            setTimeout(() => {
                observer.disconnect();
                reject(new Error(`等待元素 ${selector} 超时`));
            }, timeout);
        });
    }

    /**
     * 创建操作区域内的下载原图按钮
     * @returns {HTMLDivElement} - 返回创建好的下载按钮元素
     */
    function createOperationAreaDownloadButton() {
        const button = document.createElement('div');
        button.tabIndex = 0;
        button.className = 'operation-button-PU0Wce';
        button.innerHTML = `<svg width="1em" height="1em" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" fill="none" role="presentation" xmlns="http://www.w3.org/2000/svg" class="operation-icon-q9NsDV"><g><path data-follow-fill="currentColor" d="M12 2a1 1 0 0 1 1 1v10.312l4.023-4.021a1 1 0 0 1 1.414 1.414l-5.73 5.728a1 1 0 0 1-1.414 0l-5.73-5.728A1 1 0 1 1 6.977 9.29L11 13.312V3a1 1 0 0 1 1-1ZM3 20.002a1 1 0 0 1 1-1L20 19a1 1 0 0 1 0 2l-16 .002a1 1 0 0 1-1-1Z" clip-rule="evenodd" fill-rule="evenodd" fill="currentColor"></path></g></svg>
            <span>下载原图</span>`;

        // 添加点击事件
        button.addEventListener('click', function() {
            downloadOriginalImage();
        });

        return button;
    }

    /**
     * 创建原有的下载原图按钮
     * @returns {HTMLDivElement} - 返回创建好的下载按钮元素
     */
    function createDownloadButton() {
        const button = document.createElement('div');
        button.tabIndex = 0;
        button.className = 'operation-button-PU0Wce';
        button.innerHTML = `<svg width="1em" height="1em" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" fill="none" role="presentation" xmlns="http://www.w3.org/2000/svg" class="operation-icon-q9NsDV"><g><path data-follow-fill="currentColor" d="M12 2a1 1 0 0 1 1 1v10.312l4.023-4.021a1 1 0 0 1 1.414 1.414l-5.73 5.728a1 1 0 0 1-1.414 0l-5.73-5.728A1 1 0 1 1 6.977 9.29L11 13.312V3a1 1 0 0 1 1-1ZM3 20.002a1 1 0 0 1 1-1L20 19a1 1 0 0 1 0 2l-16 .002a1 1 0 0 1-1-1Z" clip-rule="evenodd" fill-rule="evenodd" fill="currentColor"></path></g></svg>
            <span>下载原图</span>`;

        // 添加点击事件
        button.addEventListener('click', function() {
            downloadOriginalImage();
        });

        return button;
    }

    /**
     * 获取当前选中的图片元素
     * @returns {HTMLImageElement|null} - 返回当前选中的图片元素,如果找不到则返回null
     */
    function getCurrentSelectedImage() {
        // 首先尝试查找当前激活的图片容器中的图片
        const activeImageContainer = document.querySelector('.image-container.active, .image-item.selected, [class*="active"], [class*="selected"]');
        if (activeImageContainer) {
            const imageInContainer = activeImageContainer.querySelector('img[crossorigin="anonymous"]');
            // 检查图片是否在avatar容器内,如果是则跳过
            if (imageInContainer && !imageInContainer.closest('.avatar-container-UQ4YgV')) {
                console.log('通过活动容器找到图片:', imageInContainer.src);
                return imageInContainer;
            }
        }

        // 备用方案:查找所有符合条件的图片,排除avatar容器内的图片
        const allImages = document.querySelectorAll('img[crossorigin="anonymous"]');
        const filteredImages = Array.from(allImages).filter(img => !img.closest('.avatar-container-UQ4YgV'));

        if (filteredImages.length > 0) {
            // 优先查找带有特定属性的图片
            const specificImage = filteredImages.find(img => img.hasAttribute('data-apm-action') && img.getAttribute('data-apm-action') === 'ai-generated-image-detail-card');
            if (specificImage) {
                console.log('找到特定属性图片:', specificImage.src);
                return specificImage;
            }

            // 如果没有特定属性的图片,返回第一个符合条件的图片
            console.log('使用第一个符合条件的图片:', filteredImages[0].src);
            return filteredImages[0];
        }

        console.log('未找到任何符合条件的图片');
        return null;
    }

    /**
     * 创建在新标签页打开原图的按钮
     * @returns {HTMLDivElement} - 返回创建好的按钮元素
     */
    function createEmptyButton() {
        const button = document.createElement('div');
        button.tabIndex = 0;
        button.className = 'operation-button-PU0Wce';
        button.innerHTML = `<svg width="1em" height="1em" viewBox="0 0 24 24" preserveAspectRatio="xMidYMid meet" fill="none" role="presentation" xmlns="http://www.w3.org/2000/svg" class="operation-icon-q9NsDV"><g><path data-follow-fill="currentColor" fill-rule="evenodd" clip-rule="evenodd" d="M12 2C6.477 2 2 6.477 2 12c0 5.523 4.477 10 10 10s10-4.477 10-10c0-5.523-4.477-10-10-10zm0 18c-4.411 0-8-3.589-8-8s3.589-8 8-8 8 3.589 8 8-3.589 8-8 8z" fill="currentColor"></path></g></svg>
            <span>打开原图</span>`;

        // 添加点击事件,在新标签页打开原图
        button.addEventListener('click', function() {
            const targetImage = getCurrentSelectedImage();

            if (targetImage && targetImage.src) {
                console.log('找到目标图片,URL:', targetImage.src);
                // 在新标签页打开图片
                window.open(targetImage.src, '_blank');
            } else {
                alert('未找到可查看的图片');
            }
        });

        return button;
    }

    /**
     * 下载原图功能
     */
    function downloadOriginalImage() {
        const targetImage = getCurrentSelectedImage();

        if (targetImage && targetImage.src) {
            console.log('找到目标图片,准备下载,URL:', targetImage.src);
            const imageUrl = targetImage.src;
            const imageName = imageUrl.split('/').pop().split('?')[0] || 'downloaded_image';

            try {
                // 使用GM_download下载图片
                GM_download({
                    url: imageUrl,
                    name: imageName,
                    saveAs: true,
                    onerror: function(error) {
                        console.error('下载失败:', error);
                        alert('下载失败,请重试');
                    },
                    onprogress: function(progress) {
                        console.log('下载进度:', progress);
                    }
                });
            } catch (error) {
                console.error('GM_download调用失败:', error);
                alert('下载功能不可用,请检查Tampermonkey设置');
            }
        } else {
            alert('未找到可下载的图片');
        }
    }

    /**
     * 将按钮添加到目标容器
     * @param {HTMLDivElement} button - 要添加的按钮元素
     * @param {string} targetSelector - 目标容器的CSS选择器
     * @returns {boolean} - 添加成功返回true,否则返回false
     */
    function addButtonToTarget(button, targetSelector) {
        const targetContainer = document.querySelector(targetSelector);

        if (targetContainer) {
            // 检查是否已经存在相同的按钮
            const existingButtons = targetContainer.querySelectorAll('.operation-button-PU0Wce');
            for (let existingBtn of existingButtons) {
                if (existingBtn.textContent === button.textContent) {
                    console.log('按钮已存在,跳过添加');
                    return true;
                }
            }

            targetContainer.appendChild(button);
            console.log('成功添加按钮到容器');
            return true;
        } else {
            console.log(`未找到目标容器 ${targetSelector}`);
            return false;
        }
    }

    /**
     * 修改目标容器样式为两行布局
     * @param {string} targetSelector - 目标容器的CSS选择器
     */
    function modifyContainerLayout(targetSelector) {
        const targetContainer = document.querySelector(targetSelector);

        if (targetContainer) {
            // 添加内联样式使容器变成两行布局,宽度自适应
            targetContainer.style.display = 'flex';
            targetContainer.style.flexWrap = 'wrap';
            targetContainer.style.maxHeight = '80px'; // 根据需要调整高度
            targetContainer.style.width = 'fit-content'; // 宽度自适应
            targetContainer.style.alignItems = 'flex-start';

            console.log('成功修改容器布局为两行');
        } else {
            console.log(`未找到目标容器 ${targetSelector}`);
        }
    }

    /**
     * 在操作区域添加下载按钮
     * @param {string} operationAreaSelector - 操作区域的CSS选择器
     * @returns {boolean} - 添加成功返回true,否则返回false
     */
    async function addButtonToOperationArea(operationAreaSelector) {
        try {
            // 等待操作区域出现
            const operationArea = await waitForElement(operationAreaSelector);

            if (operationArea) {
                // 检查是否已经存在相同的按钮
                const existingButtons = operationArea.querySelectorAll('.operation-button-PU0Wce');
                for (let existingBtn of existingButtons) {
                    if (existingBtn.textContent.trim() === '下载原图') {
                        console.log('操作区域下载按钮已存在,跳过添加');
                        return true;
                    }
                }

                // 创建下载按钮
                const operationAreaButton = createOperationAreaDownloadButton();

                // 根据容器内元素的排列顺序找到合适的插入位置
                const publishButton = operationArea.querySelector('.publish-button-LkMPnt');
                const operationButtons = operationArea.querySelector('.operation-buttons-H5Rhlq');

                // 隐藏发布按钮
                if (publishButton) {
                    publishButton.style.display = 'none';
                    console.log('已隐藏发布按钮');
                }

                if (publishButton && operationButtons) {
                    // 如果发布按钮和操作按钮容器都存在,将下载按钮插入到它们之间
                    operationArea.insertBefore(operationAreaButton, operationButtons);
                    console.log('成功在发布按钮和操作按钮容器之间插入下载按钮');
                } else if (publishButton) {
                    // 如果只有发布按钮,将下载按钮插入到发布按钮之后
                    publishButton.parentNode.insertBefore(operationAreaButton, publishButton.nextSibling);
                    console.log('成功在发布按钮之后插入下载按钮');
                } else if (operationButtons) {
                    // 如果只有操作按钮容器,将下载按钮插入到操作按钮容器之前
                    operationArea.insertBefore(operationAreaButton, operationButtons);
                    console.log('成功在操作按钮容器之前插入下载按钮');
                } else {
                    // 如果都没有,直接添加到容器末尾
                    operationArea.appendChild(operationAreaButton);
                    console.log('成功在操作区域末尾添加下载按钮');
                }

                return true;
            }
        } catch (error) {
            console.error('在操作区域添加按钮失败:', error);
            return false;
        }
    }

    /**
     * 初始化按钮和布局
     * @param {string} targetSelector - 目标容器的CSS选择器
     * @param {string} operationAreaSelector - 操作区域的CSS选择器
     */
    async function initializeButtons(targetSelector, operationAreaSelector) {
        try {
            // 等待目标容器出现
            await waitForElement(targetSelector);
            console.log('目标容器已找到,开始初始化');

            // 修改容器布局
            modifyContainerLayout(targetSelector);

            // 创建并添加按钮
            const downloadButton = createDownloadButton();
            const emptyButton = createEmptyButton();

            const addedDownload = addButtonToTarget(downloadButton, targetSelector);
            const addedEmpty = addButtonToTarget(emptyButton, targetSelector);

            // 在操作区域添加下载按钮
            const addedOperationAreaButton = await addButtonToOperationArea(operationAreaSelector);

            if (addedDownload && addedEmpty && addedOperationAreaButton) {
                console.log('所有按钮初始化成功');
                retryCount = 0; // 重置重试计数
                return true;
            } else {
                console.log('部分按钮初始化失败');
                return false;
            }
        } catch (error) {
            console.error('初始化按钮失败:', error);
            return false;
        }
    }

    /**
     * 设置全局MutationObserver来监控DOM变化
     * 用于处理单页应用的页面切换
     * @param {string} targetSelector - 目标容器的CSS选择器
     * @param {string} operationAreaSelector - 操作区域的CSS选择器
     */
    function setupGlobalObserver(targetSelector, operationAreaSelector) {
        // 如果已经存在观察者,先断开
        if (globalObserver) {
            globalObserver.disconnect();
        }

        globalObserver = new MutationObserver((mutations) => {
            // 检查是否有目标容器的变化
            const targetContainer = document.querySelector(targetSelector);
            if (targetContainer) {
                // 检查容器中是否已经有我们的按钮
                const existingButtons = targetContainer.querySelectorAll('.operation-button-PU0Wce');
                if (existingButtons.length === 0) {
                    console.log('检测到容器变化但缺少按钮,重新初始化');
                    initializeButtons(targetSelector, operationAreaSelector);
                }
            }

            // 检查操作区域是否缺少按钮
            const operationArea = document.querySelector(operationAreaSelector);
            if (operationArea) {
                const operationAreaButtons = operationArea.querySelectorAll('.operation-button-PU0Wce');
                let hasDownloadButton = false;
                for (let btn of operationAreaButtons) {
                    if (btn.textContent.trim() === '下载原图') {
                        hasDownloadButton = true;
                        break;
                    }
                }
                if (!hasDownloadButton) {
                    console.log('检测到操作区域变化但缺少下载按钮,重新添加');
                    addButtonToOperationArea(operationAreaSelector);
                }
            }
        });

        // 开始观察DOM变化
        globalObserver.observe(document.body, {
            childList: true,
            subtree: true,
            attributes: false,
            characterData: false
        });

        console.log('全局DOM观察者已启动');
    }

    /**
     * 重试初始化机制
     * @param {string} targetSelector - 目标容器的CSS选择器
     * @param {string} operationAreaSelector - 操作区域的CSS选择器
     */
    async function retryInitialization(targetSelector, operationAreaSelector) {
        if (retryCount >= MAX_RETRY) {
            console.log('已达到最大重试次数,停止重试');
            return;
        }

        retryCount++;
        console.log(`第 ${retryCount} 次重试初始化`);

        setTimeout(async () => {
            const success = await initializeButtons(targetSelector, operationAreaSelector);
            if (!success) {
                retryInitialization(targetSelector, operationAreaSelector);
            }
        }, RETRY_DELAY);
    }

    // 主函数
    /**
     * 脚本主函数,协调整个功能的执行
     */
    async function main() {
        await waitForDOM();

        // 目标容器选择器
        const targetSelector = '.action-buttons-wrapper-v1aogC';
        // 操作区域选择器(红框位置)
        const operationAreaSelector = '.operation-area-Kuc_sT';

        console.log('脚本开始执行');

        // 首次初始化
        const success = await initializeButtons(targetSelector, operationAreaSelector);

        if (!success) {
            console.log('首次初始化失败,启动重试机制');
            retryInitialization(targetSelector, operationAreaSelector);
        }

        // 设置全局观察者
        setupGlobalObserver(targetSelector, operationAreaSelector);

        // 监听路由变化(针对单页应用)
        let lastUrl = location.href;
        new MutationObserver(() => {
            const url = location.href;
            if (url !== lastUrl) {
                lastUrl = url;
                console.log('检测到URL变化,重新初始化按钮');
                setTimeout(() => {
                    initializeButtons(targetSelector, operationAreaSelector);
                }, 1000); // 延迟1秒等待DOM更新
            }
        }).observe(document, { subtree: true, childList: true });

        console.log('脚本初始化完成');
    }

    // 执行主函数
    main();
})();