// ==UserScript== // @name NodeSeek 编辑器个人图床 + Emoji集成 // @namespace https://www.nodeseek.com/ // @version 0.1.2 // @description 为 NodeSeek 编辑器增加图片上传功能和Emoji选择器,使用个人CloudFlare ImgBed图床,支持上传频率限制 // @author astom // @match *://www.nodeseek.com/* // @icon https://www.nodeseek.com/static/image/favicon/android-chrome-192x192.png // @grant GM_xmlhttpRequest // @license MPL-2.0 License // @downloadURL https://update.greasyfork.icu/scripts/541998/NodeSeek%20%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%AA%E4%BA%BA%E5%9B%BE%E5%BA%8A%20%2B%20Emoji%E9%9B%86%E6%88%90.user.js // @updateURL https://update.greasyfork.icu/scripts/541998/NodeSeek%20%E7%BC%96%E8%BE%91%E5%99%A8%E4%B8%AA%E4%BA%BA%E5%9B%BE%E5%BA%8A%20%2B%20Emoji%E9%9B%86%E6%88%90.meta.js // ==/UserScript== (function () { 'use strict'; // 个人图床配置,先去部署图床,然后改下面前三个就行!!! const imgHost = { url: "https://xxxxx.org/upload", // 图床上传地址,将你的地址替换一下!!! authCode: "xxxxx", // 上传认证码,后台设置的,换成你自己的!!! domain: "https://xxxxx.org", // 图床域名,用于拼接完整链接,换成你自己的!!! uploadChannel: "telegram", // 上传渠道 serverCompress: true, // 服务端压缩 autoRetry: true, // 失败时自动重试 uploadNameType: "default", // 文件命名方式 returnFormat: "default" // 返回链接格式 }; const mdImgName = 0; // 0: 使用图床返回的原始名称, 其他值则名称固定为该值 const submitByKey = true; // 是否按下 Ctrl+Enter 后触发发帖动作 // 上传频率限制配置 let lastUploadTime = 0; // 记录上次上传时间戳 const uploadInterval = 10000; // 上传间隔限制:10秒(毫秒) // Emoji 配置 const emojiConfig = { pickerUrl: 'https://cdn.jsdelivr.net/npm/emoji-picker-element@^1/index.js', width: 380, height: 420 }; // 页面加载完毕后载入功能 window.addEventListener('load', initEditorEnhancer, false); function initEditorEnhancer() { // 监听粘贴事件 document.addEventListener('paste', (event) => handlePasteEvt(event)); // 给编辑器绑定拖拽事件 var dropZone = document.getElementById('code-mirror-editor'); if (dropZone) { // 阻止默认行为 dropZone.addEventListener('dragover', function (e) { e.preventDefault(); e.stopPropagation(); e.dataTransfer.dropEffect = 'copy'; }); // 处理文件拖放 dropZone.addEventListener('drop', function (e) { e.preventDefault(); e.stopPropagation(); log('正在处理拖放内容...'); let imageFiles = []; for (let file of e.dataTransfer.files) { if (/^image\//i.test(file.type)) { imageFiles.push(file); log(`拖放的文件名: ${file.name}`); } } log(`拖放的图片数量: ${imageFiles.length}`); if (imageFiles.length === 0) { log('你拖放的内容好像没有图片哦', 'red'); return; } uploadImage(imageFiles.map(file => { return { kind: 'file', type: file.type, getAsFile: () => file }; })); }); } // 修改图片按钮的行为并添加Emoji按钮 let checkExist = setInterval(function () { const oldElement = document.querySelector('.toolbar-item.i-icon.i-icon-pic[title="图片"]'); if (oldElement) { clearInterval(checkExist); const newElement = oldElement.cloneNode(true); oldElement.parentNode.replaceChild(newElement, oldElement); newElement.addEventListener('click', handleImgBtnClick); // 添加Emoji按钮 addEmojiButton(newElement.parentNode); } }, 200); // 监听 Ctrl+Enter 快捷键 if (submitByKey) { document.addEventListener('keydown', function (event) { if (event.ctrlKey && event.key === 'Enter') { const button = document.querySelector('.submit.btn'); if (button) button.click(); } }); } // 定期检查并确保Emoji按钮存在 setInterval(ensureEmojiButton, 2000); } // 粘贴事件处理 function handlePasteEvt(event) { log('正在处理粘贴内容...'); const items = (event.clipboardData || event.originalEvent.clipboardData).items; if (items.length === 0) { log('你粘贴的内容好像没有图片哦', 'red'); return; } uploadImage(items); } // 图片按钮点击事件处理 function handleImgBtnClick() { const input = document.createElement('input'); input.type = 'file'; input.multiple = true; input.accept = 'image/*'; input.onchange = e => { const files = e.target.files; if (files.length) { const items = [...files].map(file => ({ kind: 'file', type: file.type, getAsFile: () => file })); uploadImage(items); } }; input.click(); } // 处理并上传图片 async function uploadImage(items) { // 检查上传频率限制 const now = Date.now(); const timeSinceLastUpload = now - lastUploadTime; if (lastUploadTime > 0 && timeSinceLastUpload < uploadInterval) { const remainingTime = Math.ceil((uploadInterval - timeSinceLastUpload) / 1000); log(`上传频率限制:请等待 ${remainingTime} 秒后再试`, 'orange'); return; } // 更新上传时间戳 lastUploadTime = now; let imageFiles = []; for (let item of items) { if (item.kind === 'file' && item.type.indexOf('image/') !== -1) { let blob = item.getAsFile(); imageFiles.push(blob); } } if (imageFiles.length > 0) { event.preventDefault(); for (let i = 0; i < imageFiles.length; i++) { if (imageFiles.length > 1) { log(`上传第 ${i + 1} / ${imageFiles.length} 张图片...`); } else { log(`上传图片...`); } let file = imageFiles[i]; await uploadToPersonalImgBed(file); } } else { log('你粘贴的内容好像没有图片哦', 'red'); } } // 上传到个人CloudFlare ImgBed图床 async function uploadToPersonalImgBed(file) { return new Promise((resolve, reject) => { let formData = new FormData(); formData.append('file', file); // 构建URL参数 const params = new URLSearchParams({ authCode: imgHost.authCode, serverCompress: imgHost.serverCompress, uploadChannel: imgHost.uploadChannel, autoRetry: imgHost.autoRetry, uploadNameType: imgHost.uploadNameType, returnFormat: imgHost.returnFormat }); GM_xmlhttpRequest({ method: 'POST', url: `${imgHost.url}?${params.toString()}`, data: formData, onload: (rsp) => { try { let rspJson = JSON.parse(rsp.responseText); if (rsp.status !== 200) { log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red'); reject(rsp.statusText); return; } if (Array.isArray(rspJson) && rspJson.length > 0 && rspJson[0].src) { // 图片上传成功 const imgUrl = `${imgHost.domain}${rspJson[0].src}`; const fileName = mdImgName === 0 ? file.name : mdImgName; insertToEditor(``); log('图片上传成功~', 'green'); // 确保Emoji按钮仍然存在 setTimeout(ensureEmojiButton, 100); } else { log('图片上传失败,接口返回格式异常', 'red'); insertToEditor(`图片上传失败,接口返回: ${JSON.stringify(rspJson)}`); } } catch (e) { log(`图片上传失败,解析响应出错: ${e.message}`, 'red'); reject(e); } resolve(); }, onerror: (error) => { log(`图片上传失败: ${error.status || 'Network Error'} ${error.statusText || ''}`, 'red'); reject(error); } }); }); } // 插入到编辑器 function insertToEditor(content, isEmoji = false) { const codeMirrorElement = document.querySelector('.CodeMirror'); if (codeMirrorElement) { const codeMirrorInstance = codeMirrorElement.CodeMirror; if (codeMirrorInstance) { const cursor = codeMirrorInstance.getCursor(); if (isEmoji) { // 表情符号直接插入,不添加换行符 codeMirrorInstance.replaceRange(content, cursor); } else { // 图片等其他内容保持原有格式(前后换行) codeMirrorInstance.replaceRange(`\n${content} \n`, cursor); } } } } // 在编辑器打印日志 function log(message, color = '') { if (!document.getElementById('editor-enhance-logs')) { initEditorLogDiv(); } const logDiv = document.getElementById('editor-enhance-logs'); logDiv.innerHTML = `