您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
在 nodeloc.com 的评论框旁添加图片上传功能,支持粘贴/拖拽上传,提示在右下角
// ==UserScript== // @name Nodeloc 自建图床快捷上传 // @namespace http://tampermonkey.net/ // @version 1.0 // @description 在 nodeloc.com 的评论框旁添加图片上传功能,支持粘贴/拖拽上传,提示在右下角 // @author BreezeZhang // @match https://*.nodeloc.com/* // @grant GM_setValue // @grant GM_getValue // @grant unsafeWindow // @connect nodeloc.com // @connect * // @license GPL-3.0 // ==/UserScript== (function() { 'use strict'; let config = { EASYIMAGE_API_URL: GM_getValue('EASYIMAGE_API_URL', ''), EASYIMAGE_TOKEN: GM_getValue('EASYIMAGE_TOKEN', ''), LANKONG_API_URL: GM_getValue('LANKONG_API_URL', ''), LANKONG_EMAIL: GM_getValue('LANKONG_EMAIL', ''), LANKONG_PASSWORD: GM_getValue('LANKONG_PASSWORD', ''), UPLOAD_TYPE: GM_getValue('UPLOAD_TYPE', 'easyimage') }; if (!config.EASYIMAGE_API_URL || !config.EASYIMAGE_TOKEN) { setTimeout(showConfigModal, 0); return; } setTimeout(initScript, 0); function extractDomain(url) { try { return new URL(url).hostname; } catch (e) { return ''; } } function showConfigModal(isUpdate = false) { if (document.querySelector('.easyimage-config-modal')) return; const modal = document.createElement('div'); modal.className = 'easyimage-config-modal'; modal.style.cssText = 'position:fixed;top:0;left:0;width:100%;height:100%;background-color:rgba(0,0,0,0.5);display:flex;justify-content:center;align-items:center;z-index:10000;'; const modalContent = document.createElement('div'); modalContent.style.cssText = 'background-color:#fff;padding:20px;border-radius:8px;width:350px;box-shadow:0 4px 6px rgba(0,0,0,0.1);display:flex;flex-direction:column;'; const title = document.createElement('h3'); title.textContent = isUpdate ? '更新配置' : '初始配置'; title.style.cssText = 'margin-bottom:20px;text-align:center;color:#333;font-size:1.2em;'; modalContent.appendChild(title); const uploadTypeLabel = document.createElement('label'); uploadTypeLabel.textContent = '选择图床类型:'; uploadTypeLabel.style.cssText = 'display:block;margin-bottom:5px;color:#666;font-size:0.9em;'; modalContent.appendChild(uploadTypeLabel); const uploadTypeSelect = document.createElement('select'); uploadTypeSelect.style.cssText = 'width:100%;padding:8px;margin-bottom:15px;border:1px solid #ddd;border-radius:4px;font-size:1em;'; ['easyimage', 'lankong'].forEach(type => { const option = document.createElement('option'); option.value = type; option.textContent = type === 'easyimage' ? '简单图床' : '兰空图床'; uploadTypeSelect.appendChild(option); }); uploadTypeSelect.value = config.UPLOAD_TYPE; modalContent.appendChild(uploadTypeSelect); const apiLabel = document.createElement('label'); apiLabel.textContent = 'API URL:'; apiLabel.style.cssText = 'display:block;margin-bottom:5px;color:#666;font-size:0.9em;'; modalContent.appendChild(apiLabel); const apiInput = document.createElement('input'); apiInput.type = 'text'; apiInput.value = config.UPLOAD_TYPE === 'easyimage' ? config.EASYIMAGE_API_URL : config.LANKONG_API_URL; apiInput.style.cssText = 'width:100%;padding:8px;margin-bottom:15px;border:1px solid #ddd;border-radius:4px;font-size:1em;'; modalContent.appendChild(apiInput); const tokenLabel = document.createElement('label'); tokenLabel.textContent = 'Token:'; tokenLabel.style.cssText = apiLabel.style.cssText; modalContent.appendChild(tokenLabel); const tokenInput = document.createElement('input'); tokenInput.type = 'text'; tokenInput.value = config.UPLOAD_TYPE === 'easyimage' ? config.EASYIMAGE_TOKEN : ''; tokenInput.style.cssText = apiInput.style.cssText; modalContent.appendChild(tokenInput); const emailLabel = document.createElement('label'); emailLabel.textContent = '邮箱:'; emailLabel.style.cssText = apiLabel.style.cssText; modalContent.appendChild(emailLabel); const emailInput = document.createElement('input'); emailInput.type = 'text'; emailInput.value = config.UPLOAD_TYPE === 'lankong' ? config.LANKONG_EMAIL : ''; emailInput.style.cssText = apiInput.style.cssText; modalContent.appendChild(emailInput); const passwordLabel = document.createElement('label'); passwordLabel.textContent = '密码:'; passwordLabel.style.cssText = apiLabel.style.cssText; modalContent.appendChild(passwordLabel); const passwordInput = document.createElement('input'); passwordInput.type = 'password'; passwordInput.value = config.UPLOAD_TYPE === 'lankong' ? config.LANKONG_PASSWORD : ''; passwordInput.style.cssText = apiInput.style.cssText; modalContent.appendChild(passwordInput); const buttonContainer = document.createElement('div'); buttonContainer.style.cssText = 'display:flex;justify-content:flex-end;margin-top:20px;'; const confirmBtn = document.createElement('button'); confirmBtn.textContent = '保存'; confirmBtn.style.cssText = 'padding:10px 20px;background-color:#4CAF50;color:white;border:none;border-radius:4px;cursor:pointer;font-size:1em;margin-right:10px;transition:background-color 0.3s ease;'; confirmBtn.addEventListener('click', () => { const apiUrl = apiInput.value.trim(); const token = tokenInput.value.trim(); const email = emailInput.value.trim(); const password = passwordInput.value.trim(); const uploadType = uploadTypeSelect.value; if (!apiUrl || (uploadType === 'easyimage' && !token) || (uploadType === 'lankong' && (!email || !password))) { alert('请填写所有必填字段!'); return; } config.UPLOAD_TYPE = uploadType; GM_setValue('UPLOAD_TYPE', config.UPLOAD_TYPE); if (uploadType === 'easyimage') { config.EASYIMAGE_API_URL = apiUrl; config.EASYIMAGE_TOKEN = token; GM_setValue('EASYIMAGE_API_URL', config.EASYIMAGE_API_URL); GM_setValue('EASYIMAGE_TOKEN', config.EASYIMAGE_TOKEN); } else if (uploadType === 'lankong') { config.LANKONG_API_URL = apiUrl; config.LANKONG_EMAIL = email; config.LANKONG_PASSWORD = password; GM_setValue('LANKONG_API_URL', config.LANKONG_API_URL); GM_setValue('LANKONG_EMAIL', config.LANKONG_EMAIL); GM_setValue('LANKONG_PASSWORD', config.LANKONG_PASSWORD); } document.body.removeChild(modal); if (isUpdate) alert('配置更新成功!'); initScript(); }); buttonContainer.appendChild(confirmBtn); const cancelBtn = document.createElement('button'); cancelBtn.textContent = '取消'; cancelBtn.style.cssText = 'padding:10px 20px;background-color:#f44336;color:white;border:none;border-radius:4px;cursor:pointer;font-size:1em;transition:background-color 0.3s ease;'; cancelBtn.addEventListener('click', () => { document.body.removeChild(modal); if (!isUpdate) alert('配置未完成,脚本将无法运行!'); }); buttonContainer.appendChild(cancelBtn); modalContent.appendChild(buttonContainer); modal.appendChild(modalContent); document.body.appendChild(modal); modal.addEventListener('click', e => { if (e.target === modal) document.body.removeChild(modal); }); confirmBtn.addEventListener('mouseover', () => confirmBtn.style.backgroundColor = '#45a049'); confirmBtn.addEventListener('mouseout', () => confirmBtn.style.backgroundColor = '#4CAF50'); cancelBtn.addEventListener('mouseover', () => cancelBtn.style.backgroundColor = '#da190b'); cancelBtn.addEventListener('mouseout', () => cancelBtn.style.backgroundColor = '#f44336'); uploadTypeSelect.addEventListener('change', () => { const uploadType = uploadTypeSelect.value; if (uploadType === 'easyimage') { apiLabel.textContent = 'API URL:'; tokenLabel.textContent = 'Token:'; [emailLabel, emailInput, passwordLabel, passwordInput].forEach(el => el.style.display = 'none'); apiInput.value = config.EASYIMAGE_API_URL || 'https://example.com/api/index.php'; tokenInput.value = config.EASYIMAGE_TOKEN || ''; } else if (uploadType === 'lankong') { apiLabel.textContent = '兰空图床域名:'; tokenLabel.textContent = 'Token:'; [emailLabel, emailInput, passwordLabel, passwordInput].forEach(el => el.style.display = 'block'); apiInput.value = config.LANKONG_API_URL || 'https://你的兰空图床域名'; tokenInput.value = ''; emailInput.value = config.LANKONG_EMAIL || 'YOUR_EMAIL_HERE'; passwordInput.value = config.LANKONG_PASSWORD || 'YOUR_PASSWORD_HERE'; } }); uploadTypeSelect.dispatchEvent(new Event('change')); } function initScript() { const checkComposerFooter = () => { const composerFooter = document.querySelector('.Composer-footer'); if (composerFooter && !composerFooter.querySelector('.easyimage-upload-container')) { createUploadButton(); setupPasteAndDrop(); addPlaceholder(); } else { setTimeout(checkComposerFooter, 100); } }; checkComposerFooter(); const observer = new MutationObserver(checkComposerFooter); observer.observe(document.body, { childList: true, subtree: true }); } function createUploadButton() { const composerFooter = document.querySelector('.Composer-footer'); if (!composerFooter || composerFooter.querySelector('.easyimage-upload-container')) return false; const uploadContainer = document.createElement('li'); uploadContainer.className = 'item-easyimage easyimage-upload-container'; uploadContainer.style.cssText = 'display:inline-block'; const uploadButton = document.createElement('button'); uploadButton.className = 'Button Button--icon Button--link hasIcon'; uploadButton.setAttribute('type', 'button'); uploadButton.setAttribute('title', ''); uploadButton.setAttribute('aria-label', '上传图片'); uploadButton.setAttribute('data-original-title', '上传图片'); const uploadIcon = document.createElement('i'); uploadIcon.className = 'icon fas fa-image Button-icon'; uploadIcon.setAttribute('aria-hidden', 'true'); const uploadLabel = document.createElement('span'); uploadLabel.className = 'Button-label'; uploadLabel.textContent = '上传图片'; uploadButton.appendChild(uploadIcon); uploadButton.appendChild(uploadLabel); const updateButton = document.createElement('button'); updateButton.className = 'Button Button--icon Button--link hasIcon'; updateButton.setAttribute('type', 'button'); updateButton.setAttribute('title', ''); updateButton.setAttribute('aria-label', '更新配置'); updateButton.setAttribute('data-original-title', '更新配置'); const updateIcon = document.createElement('i'); updateIcon.className = 'icon fas fa-cog Button-icon'; updateIcon.setAttribute('aria-hidden', 'true'); const updateLabel = document.createElement('span'); updateLabel.className = 'Button-label'; updateLabel.textContent = '更新配置'; updateButton.appendChild(updateIcon); updateButton.appendChild(updateLabel); const fileInput = document.createElement('input'); fileInput.type = 'file'; fileInput.accept = 'image/*'; fileInput.style.display = 'none'; uploadContainer.appendChild(uploadButton); uploadContainer.appendChild(updateButton); uploadContainer.appendChild(fileInput); composerFooter.insertBefore(uploadContainer, composerFooter.lastChild); uploadButton.addEventListener('click', () => fileInput.click()); fileInput.addEventListener('change', () => { if (fileInput.files.length === 0) return; const file = fileInput.files[0]; uploadButton.textContent = '正在上传...'; uploadButton.disabled = true; uploadImage(file, (success, result) => { uploadButton.textContent = '上传图片'; uploadButton.disabled = false; if (success) insertMarkdown(result); else alert('上传失败:' + result); }); }); updateButton.addEventListener('click', () => showConfigModal(true)); return true; } function setupPasteAndDrop() { const textEditor = document.querySelector('.TextEditor-editor'); if (!textEditor || textEditor.dataset.pasteDropSetup) return; textEditor.dataset.pasteDropSetup = 'true'; textEditor.addEventListener('paste', event => { const items = (event.clipboardData || window.clipboardData).items; for (let i = 0; i < items.length; i++) { if (items[i].type.indexOf('image') === 0) { event.preventDefault(); uploadImage(items[i].getAsFile(), (success, result) => { if (success) insertMarkdown(result); else alert('上传失败:' + result); }); break; } } }); let uploadInProgress = false; textEditor.addEventListener('dragover', event => { event.preventDefault(); event.dataTransfer.dropEffect = 'copy'; }); textEditor.addEventListener('drop', event => { event.preventDefault(); event.stopPropagation(); if (uploadInProgress) return; const files = event.dataTransfer.files; if (files.length > 0 && files[0].type.indexOf('image') === 0) { uploadInProgress = true; uploadImage(files[0], (success, result) => { uploadInProgress = false; if (success) insertMarkdown(result); else alert('上传失败:' + result); }); } }); } function addPlaceholder() { const textEditor = document.querySelector('.TextEditor-editor'); if (!textEditor || textEditor.querySelector('.easyimage-placeholder')) return; const placeholder = document.createElement('div'); placeholder.className = 'easyimage-placeholder'; placeholder.textContent = '拖拽或粘贴图片可以上传图片'; placeholder.style.cssText = 'position:absolute;bottom:10px;right:10px;color:#aaa;font-size:12px;pointer-events:none;z-index:1'; textEditor.parentElement.appendChild(placeholder); textEditor.addEventListener('input', () => { placeholder.style.display = textEditor.value.trim() ? 'none' : 'block'; }); } function uploadImage(file, callback) { const fileKey = `${file.name}-${file.size}-${file.lastModified}`; if (window.uploadedFiles && window.uploadedFiles[fileKey]) { return callback(true, window.uploadedFiles[fileKey]); } if (config.UPLOAD_TYPE === 'easyimage') { const formData = new FormData(); formData.append('image', file); formData.append('token', config.EASYIMAGE_TOKEN); fetchWithTimeout(config.EASYIMAGE_API_URL, { method: 'POST', body: formData }, 10000) .then(response => response.json()) .then(result => { if (result.result === 'success' && result.code === 200) { window.uploadedFiles = window.uploadedFiles || {}; window.uploadedFiles[fileKey] = result.url; callback(true, result.url); } else { callback(false, result.message || '服务器返回错误'); } }) .catch(error => callback(false, '网络错误:' + error.message)); } else if (config.UPLOAD_TYPE === 'lankong') { fetchWithTimeout(`${config.LANKONG_API_URL}/api/v1/tokens`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ email: config.LANKONG_EMAIL, password: config.LANKONG_PASSWORD }) }, 10000) .then(response => response.json()) .then(data => { if (data.status) { const token = data.data.token; const formData = new FormData(); formData.append('image', file); fetchWithTimeout(`${config.LANKONG_API_URL}/api/v1/upload`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}` }, body: formData }, 10000) .then(response => response.json()) .then(result => { if (result.status) { window.uploadedFiles = window.uploadedFiles || {}; window.uploadedFiles[fileKey] = result.data.url; callback(true, result.data.url); } else { callback(false, result.message || '上传失败'); } }) .catch(error => callback(false, '网络错误:' + error.message)); } else { callback(false, data.message || '获取Token失败'); } }) .catch(error => callback(false, '网络错误:' + error.message)); } } function insertMarkdown(url) { const textEditor = document.querySelector('.TextEditor-editor'); if (textEditor) { const markdown = ``; const start = textEditor.selectionStart; const end = textEditor.selectionEnd; textEditor.value = textEditor.value.substring(0, start) + markdown + textEditor.value.substring(end); textEditor.focus(); textEditor.setSelectionRange(start + markdown.length, start + markdown.length); showSuccessMessage(); } } function showSuccessMessage() { const composerContent = document.querySelector('.Composer-content'); if (!composerContent) return; const successMsg = document.createElement('div'); successMsg.textContent = '🎉 图片上传成功!'; successMsg.style.cssText = 'color:#4CAF50;margin:5px 0'; composerContent.insertBefore(successMsg, composerContent.firstChild); setTimeout(() => { if (successMsg.parentNode) { successMsg.parentNode.removeChild(successMsg); } }, 2000); } function fetchWithTimeout(resource, options = {}, timeout = 10000) { return Promise.race([ fetch(resource, options), new Promise((_, reject) => setTimeout(() => reject(new Error('请求超时')), timeout)) ]); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址