// ==UserScript==// @name         NodeSeek粘贴上传图床并插入Markdown(Lsky/CFBed二选一)// @namespace    https://nodeseek.com/// @version      1.2.0// @description  在 nodeseek.com 论坛中,Ctrl+V 粘贴图片后自动上传图床并插入 Markdown 链接,同时复制直链到剪贴板// @author       baba// @match        https://www.nodeseek.com/*// @match        https://nodeseek.com/*// @run-at       document-start// @grant        GM_xmlhttpRequest// @grant        GM_setClipboard// @connect      *// ==/UserScript==(function () {  'use strict';  // ========== 配置区(按你的实际情况改) ==========  // 后端类型:'lsky'(兰空图床) 或 'cfbed'(Cloudflare ImgBed)  const BACKEND = 'lsky'; // 'lsky' | 'cfbed'  // —— Lsky(兰空图床)参数 ——(示例为你贴的 309999.xyz)  const LSKY = {    api: 'https://xxx/api/v1/upload',          // /api/v1/upload    token: 'Bearer 2|0d9Gmsthmxxxxx8C8MsecIGXSnwZ', // 必填:Bearer 前缀+空格    // 上传成功后优先取 markdown 链接,也会同时把直链复制到剪贴板  };  // —— CFBed 参数(如果你改用 cfbed.sanyue.de/自建站)——  const CFBED = {    base: 'https://xxxxxi', // 不要尾斜杠    // 二选一:推荐 Token;或使用 authCode 查询参数    apiToken: 'imgbed_SkC1xxxxxbkPoIfaucfZ8RE',    authCode: '',      // e.g. 'abcdef'    uploadOptions: {   // 对应文档参数,可按需修改      uploadChannel: 'telegram',  // 'telegram' | 'cfr2' | 's3'      uploadFolder: '',      uploadNameType: 'default',  // 'default' | 'index' | 'origin' | 'short'      autoRetry: 'true',      returnFormat: 'full'        // 'full' 让返回直接是完整URL    }  };  // 插入到编辑器的 Markdown 模板({url} 会被替换)  const MD_TPL = '![image]({url})';  // 尝试把 URL 也插到当前输入框;设为 false 就只写入 CodeMirror  const ALSO_INSERT_IN_ACTIVE_INPUT = true;  // ========== 工具函数 ==========  const toast = (txt, ms = 2000) => {    try {      const bar = document.createElement('div');      bar.textContent = txt;      Object.assign(bar.style, {        position: 'fixed', left: '12px', bottom: '12px',        background: 'rgba(0,0,0,.8)', color: '#fff', padding: '8px 12px',        borderRadius: '8px', zIndex: 999999, fontSize: '12px', maxWidth: '75vw'      });      document.documentElement.appendChild(bar);      setTimeout(() => bar.remove(), ms);    } catch {}  };  const copyText = async (t) => {    try {      if (typeof GM_setClipboard === 'function') GM_setClipboard(t, { type: 'text' });      else await navigator.clipboard.writeText(t);    } catch {}  };  const pickImageFromClipboard = (ev) => {    const items = (ev.clipboardData || ev.originalEvent?.clipboardData)?.items || [];    for (const it of items) {      if (it.kind === 'file' && it.type && it.type.startsWith('image/')) {        return it.getAsFile();      }    }    return null;  };  // 某些来源(微信/企微等)可能只给了 text/html 里的 <img src="data:...">  const tryExtractDataURLImage = (ev) => {    const html = (ev.clipboardData || ev.originalEvent?.clipboardData)?.getData('text/html') || '';    const m = html.match(/src=["'](data:image\/[a-zA-Z0-9+.-]+;base64,[^"']+)["']/);    if (!m) return null;    const dataUrl = m[1];    const [meta, b64] = dataUrl.split(',');    const mime = meta.match(/data:(.*);base64/)[1] || 'image/png';    const bin = atob(b64);    const u8 = new Uint8Array(bin.length);    for (let i = 0; i < bin.length; i++) u8[i] = bin.charCodeAt(i);    return new File([u8], `pasted_${Date.now()}.png`, { type: mime });  };  const insertToCodeMirror = (text) => {    const cmEl = document.querySelector('.CodeMirror');    if (!cmEl) return false;    const cm = cmEl.CodeMirror;    if (!cm) return false;    const cur = cm.getCursor();    cm.replaceRange(text + '\n', cur);    return true;  };  const insertToActiveInput = (text) => {    const el = document.activeElement;    if (!el) return false;    const isInput = el.tagName === 'TEXTAREA' ||      (el.tagName === 'INPUT' && /^(text|search|url|email|tel)$/i.test(el.type));    if (isInput) {      const start = el.selectionStart ?? el.value.length;      const end = el.selectionEnd ?? el.value.length;      const v = el.value;      el.value = v.slice(0, start) + text + v.slice(end);      const newPos = start + text.length;      el.setSelectionRange(newPos, newPos);      el.dispatchEvent(new Event('input', { bubbles: true }));      return true;    }    if (el.isContentEditable) {      const sel = getSelection();      if (!sel || sel.rangeCount === 0) return false;      const range = sel.getRangeAt(0);      range.deleteContents();      range.insertNode(document.createTextNode(text));      range.collapse(false);      return true;    }    return false;  };  // ========== 上传实现 ==========  const uploadToLsky = (file) => new Promise((resolve, reject) => {    const form = new FormData();    form.append('file', file);    GM_xmlhttpRequest({      method: 'POST',      url: LSKY.api,      headers: {        'Authorization': LSKY.token,        'Accept': 'application/json'      },      data: form,      onload: (res) => {        try {          const json = JSON.parse(res.responseText || '{}');          if (res.status === 200 && json?.data) {            // 优先 markdown 链接;兜底使用 url/links.url            const md = json?.data?.links?.markdown;            const url = json?.data?.links?.url || json?.data?.url;            resolve({ md, url });          } else {            reject(new Error('上传成功但响应结构异常'));          }        } catch (e) { reject(e); }      },      onerror: (e) => reject(new Error('网络/跨域失败')),    });  });  const uploadToCFBed = (file) => new Promise((resolve, reject) => {    // 兼容 CFBed:POST /upload?params...    const params = new URLSearchParams({ ...CFBED.uploadOptions });    if (!CFBED.apiToken && CFBED.authCode) params.set('authCode', CFBED.authCode);    const url = `${CFBED.base.replace(/\/+$/,'')}/upload?${params.toString()}`;    GM_xmlhttpRequest({      method: 'POST',      url,      headers: CFBED.apiToken ? { 'Authorization': `Bearer ${CFBED.apiToken}` } : {},      data: (function () { const f = new FormData(); f.append('file', file); return f; })(),      onload: (res) => {        try {          const json = JSON.parse(res.responseText || '[]');          // 可能是 [{src:"..." }] 或 {data:[{src:"..."}]}          let src = null;          if (Array.isArray(json) && json[0]?.src) src = json[0].src;          else if (json?.data && Array.isArray(json.data) && json.data[0]?.src) src = json.data[0].src;          if (!src) return reject(new Error('未获取到图片地址'));          // returnFormat=full 时已是完整URL;否则补域名          let full = src;          if (/^\/[^/]/.test(src)) full = CFBED.base.replace(/\/+$/,'') + src;          // 自构 Markdown          resolve({ md: MD_TPL.replace('{url}', full), url: full });        } catch (e) { reject(e); }      },      onerror: () => reject(new Error('网络/跨域失败')),    });  });  const upload = (file) => BACKEND === 'lsky' ? uploadToLsky(file) : uploadToCFBed(file);  // ========== 主流程:监听粘贴 ==========  document.addEventListener('paste', async (event) => {    try {      // 先看是否有 file/image/*      let file = pickImageFromClipboard(event);      // 回退:尝试从 data URL 抠图      if (!file) file = tryExtractDataURLImage(event);      if (!file) return; // 非图片粘贴,放行      event.preventDefault(); // 阻断默认把图片blob塞编辑器      toast('正在上传图片…');      const { md, url } = await upload(file);      if (!md && !url) throw new Error('上传成功但未拿到链接');      const mdText = md || MD_TPL.replace('{url}', url);      const insertedToCM = insertToCodeMirror(mdText);      if (!insertedToCM && ALSO_INSERT_IN_ACTIVE_INPUT) {        // 如果没有 CodeMirror,就尝试插到当前输入区        insertToActiveInput(mdText);      }      if (url) await copyText(url); // 同时把直链复制到剪贴板      toast('图片已上传并插入,URL已复制');    } catch (err) {      console.error('[NodeSeek Paste Uploader]', err);      toast('上传失败:' + (err?.message || '未知错误'), 3000);    }  }, true);})();

gpt改的,能用,有需要的拿。有问题请大佬指点