Image Uploader to Markdown to CloudFlare-ImgBed

适配于 CloudFlare-ImgBed 的粘贴上传并生成markdown的脚本, CloudFlare-ImgBed : https://github.com/MarSeventh/CloudFlare-ImgBed

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Image Uploader to Markdown to CloudFlare-ImgBed
// @namespace    http://tampermonkey.net/
// @version      0.4.1-beta
// @description  适配于 CloudFlare-ImgBed 的粘贴上传并生成markdown的脚本, CloudFlare-ImgBed : https://github.com/MarSeventh/CloudFlare-ImgBed
// @author       calg
// @match        *://*/*
// @exclude      *://*.jpg
// @exclude      *://*.jpeg
// @exclude      *://*.png
// @exclude      *://*.gif
// @exclude      *://*.webp
// @exclude      *://*.pdf
// @grant        GM_xmlhttpRequest
// @grant        GM_addStyle
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_registerMenuCommand
// @grant        GM_info
// @grant        GM_unregisterMenuCommand
// @grant        GM_log
// @license      MIT
// @icon         https://raw.githubusercontent.com/MarSeventh/CloudFlare-ImgBed/refs/heads/main/logo.png
// ==/UserScript==

(function() {
    'use strict';

    // 防止重复注入
    if (window.imageUploaderInitialized) {
        return;
    }
    window.imageUploaderInitialized = true;

    // 创建一个唯一的命名空间
    const SCRIPT_NAMESPACE = 'image_uploader_' + GM_info.script.version.replace(/\./g, '_');

    // 存储菜单命令ID
    let menuCommandId = null;

    // 检查是否已经存在事件监听器
    function hasEventListener(element, eventName) {
        const key = `${SCRIPT_NAMESPACE}_${eventName}`;
        return element[key] === true;
    }

    // 标记事件监听器已添加
    function markEventListener(element, eventName) {
        const key = `${SCRIPT_NAMESPACE}_${eventName}`;
        element[key] = true;
    }

    // 默认配置信息
    const DEFAULT_CONFIG = {
        AUTH_CODE: 'AUTH_CODE', // 替换为你的认证码
        SERVER_URL: 'https://SERVER_URL', // 替换为实际的服务器地址
        UPLOAD_PARAMS: {
            serverCompress: true,
            uploadChannel: 'telegram', // 可选 telegram 和 cfr2
            autoRetry: true,
            uploadNameType: 'index', // 可选值为[default, index, origin, short]
            returnFormat: 'full',
            uploadFolder: 'apiupload' // 指定上传目录,用相对路径表示,例如上传到img/test目录需填img/test
        },
        NOTIFICATION_DURATION: 3000, // 通知显示时间(毫秒)
        MARKDOWN_TEMPLATE: '![{filename}]({url})', // Markdown 模板
        AUTO_COPY_URL: false, // 是否自动复制URL到剪贴板
        ALLOWED_HOSTS: ['*'], // 允许在哪些网站上运行,* 表示所有网站
        MAX_FILE_SIZE: 5 * 1024 * 1024 // 最大文件大小(5MB)
    };

    // 获取用户配置并确保所有必需的字段都存在
    const userConfig = GM_getValue('userConfig', {});
    let CONFIG = {};

    // 深度合并配置
    function mergeConfig(target, source) {
        for (const key in source) {
            if (source[key] && typeof source[key] === 'object' && !Array.isArray(source[key])) {
                target[key] = target[key] || {};
                mergeConfig(target[key], source[key]);
            } else {
                target[key] = source[key];
            }
        }
        return target;
    }

    // 确保所有默认配置项都存在
    CONFIG = mergeConfig({...DEFAULT_CONFIG}, userConfig);

    // 验证配置的完整性
    function validateConfig() {
        if (!Array.isArray(CONFIG.ALLOWED_HOSTS)) {
            CONFIG.ALLOWED_HOSTS = DEFAULT_CONFIG.ALLOWED_HOSTS;
        }
        if (typeof CONFIG.NOTIFICATION_DURATION !== 'number') {
            CONFIG.NOTIFICATION_DURATION = DEFAULT_CONFIG.NOTIFICATION_DURATION;
        }
        if (typeof CONFIG.MAX_FILE_SIZE !== 'number') {
            CONFIG.MAX_FILE_SIZE = DEFAULT_CONFIG.MAX_FILE_SIZE;
        }
        if (typeof CONFIG.MARKDOWN_TEMPLATE !== 'string') {
            CONFIG.MARKDOWN_TEMPLATE = DEFAULT_CONFIG.MARKDOWN_TEMPLATE;
        }
        if (typeof CONFIG.AUTO_COPY_URL !== 'boolean') {
            CONFIG.AUTO_COPY_URL = DEFAULT_CONFIG.AUTO_COPY_URL;
        }
    }

    validateConfig();

    // 添加通知样式
    GM_addStyle(`
        .img-upload-notification {
            position: fixed;
            top: 20px;
            right: 20px;
            padding: 15px 20px;
            border-radius: 5px;
            z-index: 9999;
            max-width: 300px;
            font-size: 14px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            transition: all 0.3s ease;
            opacity: 0;
            transform: translateX(20px);
        }
        .img-upload-notification.show {
            opacity: 1;
            transform: translateX(0);
        }
        .img-upload-success {
            background-color: #4caf50;
            color: white;
        }
        .img-upload-error {
            background-color: #f44336;
            color: white;
        }
        .img-upload-info {
            background-color: #2196F3;
            color: white;
        }
        .img-upload-close {
            float: right;
            margin-left: 10px;
            cursor: pointer;
            opacity: 0.8;
        }
        .img-upload-close:hover {
            opacity: 1;
        }

        .img-upload-modal {
            position: fixed;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px;
            border-radius: 8px;
            box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
            z-index: 10000;
            max-width: 600px;
            width: 90%;
            max-height: 80vh;
            overflow-y: auto;
        }
        .img-upload-modal-overlay {
            position: fixed;
            top: 0;
            left: 0;
            right: 0;
            bottom: 0;
            background: rgba(0, 0, 0, 0.5);
            z-index: 9999;
        }
        .img-upload-modal h2 {
            margin: 0 0 20px;
            color: #333;
            font-size: 18px;
        }
        .img-upload-form-group {
            margin-bottom: 20px;
        }
        .img-upload-form-group label {
            display: block;
            margin-bottom: 8px;
            color: #333;
            font-weight: 500;
        }
        .img-upload-help-text {
            margin-top: 4px;
            color: #666;
            font-size: 12px;
        }
        .img-upload-form-group input[type="text"],
        .img-upload-form-group input[type="number"],
        .img-upload-form-group textarea {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            box-sizing: border-box;
        }
        .img-upload-form-group textarea {
            min-height: 100px;
            font-family: monospace;
        }
        .img-upload-form-group input[type="checkbox"] {
            margin-right: 8px;
        }
        .img-upload-buttons {
            display: flex;
            justify-content: flex-end;
            gap: 10px;
            margin-top: 20px;
        }
        .img-upload-button {
            padding: 8px 16px;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 14px;
            transition: background-color 0.2s;
        }
        .img-upload-button-primary {
            background: #2196F3;
            color: white;
        }
        .img-upload-button-secondary {
            background: #e0e0e0;
            color: #333;
        }
        .img-upload-button:hover {
            opacity: 0.9;
        }
        .img-upload-error {
            color: #ffffff;
            font-size: 12px;
            margin-top: 4px;
        }
        .img-upload-info-icon {
            display: inline-block;
            width: 16px;
            height: 16px;
            background: #2196F3;
            color: white;
            border-radius: 50%;
            text-align: center;
            line-height: 16px;
            font-size: 12px;
            margin-left: 4px;
            cursor: help;
        }
        .img-upload-form-group select {
            width: 100%;
            padding: 8px;
            border: 1px solid #ddd;
            border-radius: 4px;
            font-size: 14px;
            background-color: white;
        }
        .img-upload-input-group {
            display: flex;
            align-items: center;
        }
        .img-upload-input-group input {
            flex: 1;
            border-top-right-radius: 0;
            border-bottom-right-radius: 0;
        }
        .img-upload-input-group-text {
            padding: 8px 12px;
            background: #f5f5f5;
            border: 1px solid #ddd;
            border-left: none;
            border-radius: 0 4px 4px 0;
            color: #666;
        }
        .img-upload-checkbox-label {
            display: flex !important;
            align-items: center;
            font-weight: normal !important;
        }
        .img-upload-checkbox-label input {
            margin-right: 8px;
        }

        .img-upload-dropzone {
            display: none;
            position: fixed;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            background: rgba(33, 150, 243, 0.2);
            border: 3px dashed #2196F3;
            z-index: 9998;
            box-sizing: border-box;
        }
        .img-upload-dropzone.active {
            display: block;
        }
        .img-upload-dropzone-message {
            position: absolute;
            top: 50%;
            left: 50%;
            transform: translate(-50%, -50%);
            background: white;
            padding: 20px 40px;
            border-radius: 8px;
            font-size: 18px;
            color: #2196F3;
            box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
        }
    `);

    // 添加日志函数
    function log(message, type = 'info') {
        const prefix = '[Image Uploader]';
        switch(type) {
            case 'error':
                console.error(`${prefix} ❌ ${message}`);
                break;
            case 'warn':
                console.warn(`${prefix} ⚠️ ${message}`);
                break;
            case 'success':
                console.log(`${prefix} ✅ ${message}`);
                break;
            default:
                console.log(`${prefix} ℹ️ ${message}`);
        }
    }

    // 显示通知的函数
    function showNotification(message, type = 'info') {
        const notification = document.createElement('div');
        notification.className = `img-upload-notification img-upload-${type}`;

        const closeBtn = document.createElement('span');
        closeBtn.className = 'img-upload-close';
        closeBtn.textContent = '✕';
        closeBtn.onclick = () => removeNotification(notification);

        const messageSpan = document.createElement('span');
        messageSpan.textContent = message;

        notification.appendChild(closeBtn);
        notification.appendChild(messageSpan);
        document.body.appendChild(notification);

        // 添加显示动画
        setTimeout(() => notification.classList.add('show'), 10);

        // 自动消失
        const timeout = setTimeout(() => removeNotification(notification), CONFIG.NOTIFICATION_DURATION);

        // 鼠标悬停时暂停消失
        notification.addEventListener('mouseenter', () => clearTimeout(timeout));
        notification.addEventListener('mouseleave', () => setTimeout(() => removeNotification(notification), 1000));
    }

    // 移除通知
    function removeNotification(notification) {
        notification.classList.remove('show');
        setTimeout(() => {
            if (notification.parentNode) {
                notification.parentNode.removeChild(notification);
            }
        }, 300);
    }

    // 复制文本到剪贴板
    function copyToClipboard(text) {
        const textarea = document.createElement('textarea');
        textarea.value = text;
        textarea.style.position = 'fixed';
        textarea.style.opacity = '0';
        document.body.appendChild(textarea);
        textarea.select();
        try {
            document.execCommand('copy');
            showNotification('链接已复制到剪贴板!', 'success');
        } catch (err) {
            showNotification('复制失败:' + err.message, 'error');
        }
        document.body.removeChild(textarea);
    }

    // 检查文件大小
    function checkFileSize(file) {
        if (file.size > CONFIG.MAX_FILE_SIZE) {
            showNotification(`文件大小超过限制(${Math.round(CONFIG.MAX_FILE_SIZE/1024/1024)}MB)`, 'error');
            return false;
        }
        return true;
    }

    // 检查当前网站是否允许上传
    function isAllowedHost() {
        const currentHost = window.location.hostname;
        log(`检查当前域名是否允许: ${currentHost}`);

        // 如果允许所有网站
        if (CONFIG.ALLOWED_HOSTS.includes('*')) {
            log('允许所有网站');
            return true;
        }

        // 清理和标准化域名列表
        const allowedHosts = CONFIG.ALLOWED_HOSTS.map(host => {
            // 移除协议前缀
            host = host.replace(/^https?:\/\//, '');
            // 移除路径和查询参数
            host = host.split('/')[0];
            // 移除端口号
            host = host.split(':')[0];
            return host.toLowerCase().trim();
        });

        log(`允许的域名列表: ${JSON.stringify(allowedHosts, null, 2)}`);

        // 检查当前域名是否在允许列表中
        const isAllowed = allowedHosts.some(host => {
            // 完全匹配
            if (host === currentHost) {
                log(`域名完全匹配: ${host}`);
                return true;
            }
            // 通配符匹配(例如 *.example.com)
            if (host.startsWith('*.') && currentHost.endsWith(host.slice(1))) {
                log(`域名通配符匹配: ${host}`);
                return true;
            }
            return false;
        });

        if (!isAllowed) {
            log(`当前域名 ${currentHost} 不在允许列表中`, 'warn');
        }

        return isAllowed;
    }

    // 修改事件监听器添加方式
    function addPasteListener() {
        if (hasEventListener(document, 'paste')) {
            return;
        }

        document.addEventListener('paste', async function(event) {
            if (!isAllowedHost()) return;

            const activeElement = document.activeElement;
            if (!activeElement || !['INPUT', 'TEXTAREA'].includes(activeElement.tagName)) {
                return;
            }

            const items = event.clipboardData.items;
            let hasImage = false;

            for (let item of items) {
                if (item.type.startsWith('image/')) {
                    hasImage = true;
                    event.preventDefault();
                    const blob = item.getAsFile();

                    if (!checkFileSize(blob)) {
                        return;
                    }

                    showNotification('正在上传图片,请稍候...', 'info');
                    await uploadImage(blob, activeElement);
                    break;
                }
            }

            if (!hasImage) {
                return;
            }
        });

        markEventListener(document, 'paste');
    }

    // 上传图片
    async function uploadImage(blob, targetElement) {
        const formData = new FormData();
        const filename = `pasted-image-${Date.now()}.png`;
        formData.append('file', blob, filename);

        log(`开始上传图片: ${filename} (${(blob.size / 1024).toFixed(2)}KB)`);
        log(`上传参数: ${JSON.stringify(CONFIG.UPLOAD_PARAMS, null, 2)}`);

        const queryParams = new URLSearchParams({
            authCode: CONFIG.AUTH_CODE,
            ...CONFIG.UPLOAD_PARAMS
        }).toString();

        try {
            log(`正在发送请求到: ${CONFIG.SERVER_URL}/upload`);
            GM_xmlhttpRequest({
                method: 'POST',
                url: `${CONFIG.SERVER_URL}/upload?${queryParams}`,
                data: formData,
                onload: function(response) {
                    if (response.status === 200) {
                        try {
                            const result = JSON.parse(response.responseText);
                            log(`服务器响应: ${JSON.stringify(result, null, 2)}`);

                            if (result && result.length > 0) {
                                const imageUrl = result[0].src;
                                log(`上传成功,图片URL: ${imageUrl}`, 'success');
                                insertMarkdownImage(imageUrl, targetElement, filename);
                                showNotification('图片上传成功!', 'success');

                                if (CONFIG.AUTO_COPY_URL) {
                                    log('自动复制URL到剪贴板');
                                    copyToClipboard(imageUrl);
                                }
                            } else {
                                const errorMsg = '上传成功但未获取到图片链接,请检查服务器响应';
                                log(errorMsg, 'error');
                                showNotification(errorMsg, 'error');
                            }
                        } catch (e) {
                            const errorMsg = `解析服务器响应失败:${e.message}`;
                            log(errorMsg, 'error');
                            log(`原始响应: ${response.responseText}`, 'error');
                            showNotification(errorMsg, 'error');
                        }
                    } else {
                        let errorMsg = '上传失败';
                        try {
                            const errorResponse = JSON.parse(response.responseText);
                            errorMsg += ':' + (errorResponse.message || response.statusText);
                            log(`上传失败: ${JSON.stringify(errorResponse, null, 2)}`, 'error');
                        } catch (e) {
                            errorMsg += `(状态码:${response.status})`;
                            log(`上传失败: 状态码 ${response.status}`, 'error');
                            log(`原始响应: ${response.responseText}`, 'error');
                        }
                        showNotification(errorMsg, 'error');
                    }
                },
                onerror: function(error) {
                    const errorMsg = '网络错误:无法连接到图床服务器';
                    log(`${errorMsg}: ${error}`, 'error');
                    showNotification(errorMsg, 'error');
                }
            });
        } catch (error) {
            const errorMsg = `上传过程发生错误:${error.message}`;
            log(errorMsg, 'error');
            showNotification(errorMsg, 'error');
        }
    }

    // 在输入框中插入 Markdown 格式的图片链接
    function insertMarkdownImage(imageUrl, element, filename) {
        const markdownImage = CONFIG.MARKDOWN_TEMPLATE
            .replace('{url}', imageUrl)
            .replace('{filename}', filename.replace(/\.[^/.]+$/, '')); // 移除文件扩展名

        const start = element.selectionStart;
        const end = element.selectionEnd;
        const text = element.value;

        element.value = text.substring(0, start) + markdownImage + text.substring(end);
        element.selectionStart = element.selectionEnd = start + markdownImage.length;
        element.focus();
    }

    // 创建配置界面
    function createConfigModal() {
        const overlay = document.createElement('div');
        overlay.className = 'img-upload-modal-overlay';

        const modal = document.createElement('div');
        modal.className = 'img-upload-modal';

        const content = `
            <h2>图床上传配置</h2>
            <form id="img-upload-config-form">
                <div class="img-upload-form-group">
                    <label>认证码</label>
                    <input type="text" name="AUTH_CODE" value="${CONFIG.AUTH_CODE}" required>
                    <div class="img-upload-help-text">用于验证上传请求的密钥</div>
                </div>
                <div class="img-upload-form-group">
                    <label>服务器地址</label>
                    <input type="text" name="SERVER_URL" value="${CONFIG.SERVER_URL}" required>
                    <div class="img-upload-help-text">图床服务器的URL地址</div>
                </div>
                <div class="img-upload-form-group">
                    <label>上传通道</label>
                    <select name="uploadChannel">
                        <option value="cfr2" ${CONFIG.UPLOAD_PARAMS.uploadChannel === 'cfr2' ? 'selected' : ''}>CloudFlare R2</option>
                        <option value="telegram" ${CONFIG.UPLOAD_PARAMS.uploadChannel === 'telegram' ? 'selected' : ''}>Telegram</option>
                    </select>
                    <div class="img-upload-help-text">选择图片上传的存储通道</div>
                </div>
                <div class="img-upload-form-group">
                    <label>文件命名方式</label>
                    <select name="uploadNameType">
                        <option value="default" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'default' ? 'selected' : ''}>默认(前缀_原名)</option>
                        <option value="index" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'index' ? 'selected' : ''}>仅前缀</option>
                        <option value="origin" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'origin' ? 'selected' : ''}>仅原名</option>
                        <option value="short" ${CONFIG.UPLOAD_PARAMS.uploadNameType === 'short' ? 'selected' : ''}>短链接</option>
                    </select>
                    <div class="img-upload-help-text">选择上传后的文件命名方式</div>
                </div>
                <div class="img-upload-form-group">
                    <label>上传目录</label>
                    <input type="text" name="uploadFolder" value="${CONFIG.UPLOAD_PARAMS.uploadFolder}">
                    <div class="img-upload-help-text">指定上传目录,使用相对路径,例如:img/test</div>
                </div>
                <div class="img-upload-form-group">
                    <label>通知显示时间</label>
                    <input type="number" name="NOTIFICATION_DURATION" value="${CONFIG.NOTIFICATION_DURATION}" min="1000" step="500">
                    <div class="img-upload-help-text">通知消息显示的时间(毫秒)</div>
                </div>
                <div class="img-upload-form-group">
                    <label>Markdown模板</label>
                    <input type="text" name="MARKDOWN_TEMPLATE" value="${CONFIG.MARKDOWN_TEMPLATE}">
                    <div class="img-upload-help-text">支持 {filename} 和 {url} 两个变量</div>
                </div>
                <div class="img-upload-form-group">
                    <label>允许的网站</label>
                    <input type="text" name="ALLOWED_HOSTS" value="${CONFIG.ALLOWED_HOSTS.join(',')}">
                    <div class="img-upload-help-text">输入域名,用逗号分隔。例如:nodeseek.com, *.example.com。使用 * 表示允许所有网站。无需输入 http:// 或 https://</div>
                </div>
                <div class="img-upload-form-group">
                    <label>最大文件大小</label>
                    <div class="img-upload-input-group">
                        <input type="number" name="MAX_FILE_SIZE" value="${CONFIG.MAX_FILE_SIZE / 1024 / 1024}" min="1" step="1">
                        <span class="img-upload-input-group-text">MB</span>
                    </div>
                </div>
                <div class="img-upload-form-group">
                    <label class="img-upload-checkbox-label">
                        <input type="checkbox" name="AUTO_COPY_URL" ${CONFIG.AUTO_COPY_URL ? 'checked' : ''}>
                        自动复制URL到剪贴板
                    </label>
                </div>
                <div class="img-upload-buttons">
                    <button type="button" class="img-upload-button img-upload-button-secondary" id="img-upload-cancel">取消</button>
                    <button type="button" class="img-upload-button img-upload-button-secondary" id="img-upload-reset">重置默认值</button>
                    <button type="submit" class="img-upload-button img-upload-button-primary">保存</button>
                </div>
            </form>
        `;

        modal.innerHTML = content;
        document.body.appendChild(overlay);
        document.body.appendChild(modal);

        // 事件处理
        const form = modal.querySelector('#img-upload-config-form');
        const cancelBtn = modal.querySelector('#img-upload-cancel');
        const resetBtn = modal.querySelector('#img-upload-reset');

        function closeModal() {
            document.body.removeChild(overlay);
            document.body.removeChild(modal);
        }

        overlay.addEventListener('click', closeModal);
        cancelBtn.addEventListener('click', closeModal);

        resetBtn.addEventListener('click', () => {
            if (confirm('确定要重置所有配置到默认值吗?')) {
                CONFIG = {...DEFAULT_CONFIG};
                GM_setValue('userConfig', {});
                showNotification('配置已重置为默认值!', 'success');
                closeModal();
            }
        });

        form.addEventListener('submit', (e) => {
            e.preventDefault();
            try {
                const formData = new FormData(form);
                const newConfig = {
                    AUTH_CODE: formData.get('AUTH_CODE'),
                    SERVER_URL: formData.get('SERVER_URL'),
                    UPLOAD_PARAMS: {
                        ...DEFAULT_CONFIG.UPLOAD_PARAMS,
                        uploadChannel: formData.get('uploadChannel'),
                        uploadNameType: formData.get('uploadNameType'),
                        uploadFolder: formData.get('uploadFolder')
                    },
                    NOTIFICATION_DURATION: parseInt(formData.get('NOTIFICATION_DURATION')),
                    MARKDOWN_TEMPLATE: formData.get('MARKDOWN_TEMPLATE'),
                    ALLOWED_HOSTS: formData.get('ALLOWED_HOSTS')
                        .split(',')
                        .map(h => {
                            // 清理域名格式
                            h = h.replace(/^https?:\/\//, '');
                            h = h.split('/')[0];
                            h = h.split(':')[0];
                            return h.toLowerCase().trim();
                        })
                        .filter(h => h), // 移除空值
                    MAX_FILE_SIZE: parseFloat(formData.get('MAX_FILE_SIZE')) * 1024 * 1024,
                    AUTO_COPY_URL: formData.get('AUTO_COPY_URL') === 'on'
                };

                CONFIG = mergeConfig({...DEFAULT_CONFIG}, newConfig);
                GM_setValue('userConfig', CONFIG);
                showNotification('配置已更新!', 'success');
                closeModal();
            } catch (error) {
                showNotification('配置格式错误:' + error.message, 'error');
            }
        });

        // 防止点击模态框时关闭
        modal.addEventListener('click', (e) => e.stopPropagation());
    }

    // 修改注册配置菜单函数
    function registerMenuCommands() {
        // 如果已经存在菜单,先注销
        if (menuCommandId) {
            try {
                GM_unregisterMenuCommand(menuCommandId);
            } catch (e) {
                console.log('Unregister menu failed:', e);
            }
        }

        // 注册新菜单
        try {
            menuCommandId = GM_registerMenuCommand('配置图床参数', createConfigModal);
        } catch (e) {
            console.log('Register menu failed:', e);
            // 如果注册失败,尝试延迟重试
            setTimeout(registerMenuCommands, 1000);
        }
    }

    // 创建拖拽区域
    function createDropZone() {
        const dropZone = document.createElement('div');
        dropZone.className = 'img-upload-dropzone';

        const message = document.createElement('div');
        message.className = 'img-upload-dropzone-message';
        message.textContent = '释放鼠标上传图片';

        dropZone.appendChild(message);
        document.body.appendChild(dropZone);
        return dropZone;
    }

    // 修改拖拽事件监听器添加方式
    function handleDragAndDrop() {
        if (hasEventListener(document, 'drag')) {
            return;
        }

        const dropZone = createDropZone();
        let activeElement = null;

        // 处理拖拽文件
        async function handleFiles(files, targetElement) {
            for (const file of files) {
                if (file.type.startsWith('image/')) {
                    if (!checkFileSize(file)) {
                        continue;
                    }
                    showNotification('正在上传图片,请稍候...', 'info');
                    await uploadImage(file, targetElement);
                } else {
                    showNotification('只能上传图片文件', 'error');
                }
            }
        }

        // 监听拖拽事件
        document.addEventListener('dragenter', (e) => {
            e.preventDefault();
            activeElement = document.activeElement;
            if (activeElement && ['INPUT', 'TEXTAREA'].includes(activeElement.tagName)) {
                dropZone.classList.add('active');
            }
        });

        document.addEventListener('dragover', (e) => {
            e.preventDefault();
        });

        document.addEventListener('dragleave', (e) => {
            e.preventDefault();
            const rect = document.documentElement.getBoundingClientRect();
            if (e.clientX <= rect.left || e.clientX >= rect.right ||
                e.clientY <= rect.top || e.clientY >= rect.bottom) {
                dropZone.classList.remove('active');
            }
        });

        document.addEventListener('drop', async (e) => {
            e.preventDefault();
            dropZone.classList.remove('active');

            if (!isAllowedHost()) return;

            if (activeElement && ['INPUT', 'TEXTAREA'].includes(activeElement.tagName)) {
                const files = Array.from(e.dataTransfer.files);
                await handleFiles(files, activeElement);
            }
        });

        markEventListener(document, 'drag');
    }

    // 修改初始化函数
    function init() {
        // 检查是否已经初始化
        if (document[SCRIPT_NAMESPACE]) {
            log('脚本已经初始化,跳过');
            return;
        }
        document[SCRIPT_NAMESPACE] = true;

        log(`初始化图片上传脚本 v${GM_info.script.version}`);
        log(`当前配置: ${JSON.stringify(CONFIG, null, 2)}`);

        // 清理可能存在的旧实例
        cleanup();

        // 初始化功能
        addPasteListener();
        handleDragAndDrop();

        // 确保菜单注册成功
        const registerMenu = () => {
            if (!menuCommandId) {
                log('注册配置菜单');
                registerMenuCommands();
            }
        };

        // 立即注册一次
        registerMenu();

        // 在不同的时机尝试注册菜单
        window.addEventListener('load', registerMenu);
        document.addEventListener('readystatechange', registerMenu);

        // 定期检查菜单是否存在
        setInterval(registerMenu, 5000);

        log('初始化完成');
    }

    // 添加清理函数
    function cleanup() {
        // 移除可能存在的旧的拖拽区域
        const oldDropZones = document.querySelectorAll('.img-upload-dropzone');
        oldDropZones.forEach(zone => zone.remove());

        // 移除可能存在的旧的通知
        const oldNotifications = document.querySelectorAll('.img-upload-notification');
        oldNotifications.forEach(notification => notification.remove());

        // 注销可能存在的旧菜单
        if (menuCommandId) {
            try {
                GM_unregisterMenuCommand(menuCommandId);
                menuCommandId = null;
            } catch (e) {
                console.log('Cleanup menu failed:', e);
            }
        }
    }

    // 在页面 DOM 加载完成后初始化
    if (document.readyState === 'loading') {
        document.addEventListener('DOMContentLoaded', init);
    } else {
        init();
    }
})();