Image Uploader to Markdown to CloudFlare-ImgBed

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

// ==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();
    }
})();