您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
适配于 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: '', // 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(); } })();