zhiyang3 发表于 4 天前

分享一个粘贴图片自动上传图床的油猴脚本(适用奶昔论坛)

但是这个脚本不支持hostloc,然后由于是油猴脚本都是js.所以用GPT帮忙改了下. 现在能支持hostloc了. 其它类似hostloc 的discuz bbs 理论也能用.你们自己添加// @match 匹配url 页面试试. 我只测试了hostloc.
https://pic.j8.work/1734513011904.png
下一次更新 希望看能不能搞上imgur免费图床.个人还是觉得imgur非常稳定的.
// ==UserScript==
// @name         奶昔论坛 编辑器增强
// @namespace    https://forum.naixi.net/
// @version      0.1.00
// @description为 奶昔论坛 编辑器增加图片上传功能
// @author       Zhiyang
// @match      *://forum.naixi.net/*
// @icon         https://forum.naixi.net/static/image/common/logo.svg
// @grant      GM_xmlhttpRequest
// @license      MPL-2.0 License
// @supportURL   https://forum.naixi.net/thread-2295-1-1.html
// @homepageURLhttps://forum.naixi.net/thread-2295-1-1.html
// ==/UserScript==

/**
*
*
* 当前版本更新日志
* 0.0.11 - 2024.12.18          !!!更新前注意备份您的配置!!!
* - 新增 支持 HOSTLOC
* 0.0.11 - 2024.10.05          !!!更新前注意备份您的配置!!!
* - 新增 支持 0-RTT/telegraph 项目
*/

(function () {
    'use strict';

    // 图床配置, 默认提供的是这位大佬的 https://www.nodeseek.com/post-38305-1 , 这个图床上传限制 5p / IP / 小时
    // 当 type 为 LskyPro 时以下所有配置项均有用, 为 Chevereto 时 url 和 token 有用, 为 Telegraph / EasyImages 时只有 url 有用
    // Telegraph 官网 https://telegra.ph/ 在大陆被阻断, 可以使用 https://github.com/cf-pages/Telegraph-Image 提供的服务或者自己部署
    // Telegraph2 by @Xiefengshang 使用的是 https://github.com/0-RTT/telegraph 项目(个人考虑到其缓存做的更好所以使用)
    // EasyImages 官网 https://png.cm/ 限制单 ip 每天上传 3 张, 项目地址 https://github.com/icret/EasyImages2.0, 这个图床真烂, 两套接口不统一下, 文档也不写几句话
    const imgHost = {
      type: "Telegraph2", // 图床类型, 支持 LskyPro / Telegraph / Telegraph2 / Chevereto / EasyImages
      url: "https://pic.j8.work", // 图床地址, 带上协议头
      token: null, // 图床 token, 可选, 不填则为游客上传, LskyPro 在 /user/tokens 生成, Chevereto 必填, 在 /settings/api 生成, EasyImages 填写则使用后端接口上传, 不填写则使用前端接口上传
      storageId: null, // 图床存储策略ID, 可选项, 不填则为默认策略, 普通用户可在上传页抓包得到, 管理员可以在后台看到
    };
    const mdImgName = 0; // 0(非 Telegraph): 使用图床返回的原始名称, 其他值则名称固定为该值
    const submitByKey = true; // 是否按下 Ctrl+Enter 后触发发帖动作

    // 页面加载完毕后载入功能
    window.addEventListener('load', initEditorEnhancer, false);

    function initEditorEnhancer() {
      // 监听粘贴事件
      document.addEventListener('paste', (event) => handlePasteEvt(event));

      // 给编辑器绑定拖拽事件
      var dropZone = document.getElementById('code-mirror-editor') || document.getElementById('postbox');
      // 阻止默认行为
      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函数以接受File对象数组而不是DataTransferItemList
            uploadImage(imageFiles.map(file => {
                return {
                  kind: 'file',
                  type: file.type,
                  getAsFile: () => file
                };
            }));
      });

      // 修改图片按钮的行为
      // 图片按钮
      let checkExist = setInterval(function () {
            const oldElement = document.querySelector('.toolbar-item.i-icon.i-icon-pic');
            if (oldElement) {
                clearInterval(checkExist);
                const newElement = oldElement.cloneNode(true);
                oldElement.parentNode.replaceChild(newElement, oldElement);
                newElement.addEventListener('click', handleImgBtnClick);
            }
      }, 200);

      // 监听 Ctrl+Enter 快捷键
      if (submitByKey)
            document.addEventListener('keydown', function (event) {
                if (event.ctrlKey && event.key === 'Enter') {
                  // 获取按钮元素
                  const button = document.querySelector('.submit.btn');
                  // 触发点击事件
                  button.click();

                }
            });

    }

    // 粘贴事件处理
    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) {
      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;
                let formData = new FormData();
                formData.append('file', file);
                if (imgHost.type === 'LskyPro') {
                  if (imgHost.storageId) formData.append('strategy_id', imgHost.storageId);
                  await uploadToLsky(formData);
                } else if (imgHost.type === 'Telegraph') {
                  await uploadToTelegraph(formData);
                } else if (imgHost.type === 'Telegraph2') {
                  await uploadToTelegraph2(formData);
                } else if (imgHost.type === 'Chevereto') {
                  await uploadToChevereto(file);
                } else if (imgHost.type === 'EasyImages') {
                  await uploadToEasyImages(file);
                } else {
                  log(`暂不支持的图床类型: ${imgHost.type}, 取消上传`, 'red');
                  return;
                }
            }

      } else {
            log('你粘贴的内容好像没有图片哦', 'red');
      }
    }

    async function uploadToLsky(formData) {
      return new Promise((resolve, reject) => {
            let headers = {
                'Accept': 'application/json'
            };
            if (imgHost.token)
                headers['Authorization'] = `Bearer ${imgHost.token}`;

            GM_xmlhttpRequest({
                method: 'POST',
                url: `${imgHost.url}/api/v1/upload`,
                headers: headers,
                data: formData,
                onload: (rsp) => {
                  let rspJson = JSON.parse(rsp.responseText);
                  if (rsp.status !== 200) {
                        log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red');
                        reject(rspJson.message);
                  }
                  if (rspJson.status === true) {
                        // 图片上传成功
                        if (rspJson?.data?.links?.markdown)
                            insertToEditor(mdImgName === 0 ? rspJson.data.links.markdown : `![${mdImgName}](${rspJson.data.links.url})`);
                        else {
                            log('图片上传成功, 但接口返回有误, 原始返回已粘贴到编辑器', 'red');
                            insertToEditor(`图片上传成功, 但接口返回有误: ${JSON.stringify(rspJson)})`);
                        }
                  } else
                        log(`图片上传失败: ${rspJson.message}`, 'red');
                  resolve();
                },
                onerror: (error) => {
                  log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                  reject(error);
                }
            });
      });
    }

    async function uploadToTelegraph(formData) {
      return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: `${imgHost.url}/upload`,
                data: formData,
                onload: (rsp) => {
                  let rspJson = JSON.parse(rsp.responseText);
                  rspJson = rspJson;
                  if (rsp.status !== 200) {
                        log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red');
                        reject(rspJson.message);
                  }
                  if (rspJson) {
                        // 图片上传成功
                        if (rspJson?.src)
                            insertToEditor(`![${mdImgName}](${imgHost.url}${rspJson.src})`);
                        else {
                            log('图片上传成功, 但接口返回有误, 原始返回已粘贴到编辑器', 'red');
                            insertToEditor(`图片上传成功, 但接口返回有误: ${JSON.stringify(rspJson)})`);
                        }
                  } else
                        log(`图片上传失败: ${JSON.stringify(rspJson)}`, 'red');
                  resolve();
                },
                onerror: (error) => {
                  log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                  reject(error);
                }
            });
      });
    }

    async function uploadToTelegraph2(formData) {
      return new Promise((resolve, reject) => {
            GM_xmlhttpRequest({
                method: 'POST',
                url: `${imgHost.url}/upload`,
                data: formData,
                onload: (rsp) => {
                  let rspJson = JSON.parse(rsp.responseText);

                  if (rsp.status !== 200 || !rspJson || !rspJson.data) {
                        log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red');
                        reject(rspJson?.message || '图片上传失败,缺少 data 字段');
                  }

                  const result = rspJson.data;

                  // 图片上传成功
                  if (result) {

                        insertToCursor(`${result}`, `${mdImgName}`);
                        log('图片上传成功', 'green');
                        resolve(result);
                  } else {
                        log('图片上传成功, 但接口返回有误, 原始返回已粘贴到编辑器', 'red');

                        insertToCursor(null, null, `图片上传成功, 但接口返回有误: ${JSON.stringify(rspJson)}`)
                        resolve();
                  }
                },
                onerror: (error) => {
                  log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                  reject(error);
                }
            });
      });
    }

    async function uploadToChevereto(file) {
      return new Promise((resolve, reject) => {
            let headers = {
                'Accept': 'application/json'
            };
            if (!imgHost.token) {
                log('Chevereto 图床需配置 token', 'red');
                reject('Chevereto 图床需要 token, 请填写 token 后重试');
                return;
            }
            headers['X-API-Key'] = imgHost.token;
            let formData = new FormData();
            formData.append('source', file);

            GM_xmlhttpRequest({
                method: 'POST',
                url: `${imgHost.url}/api/1/upload`,
                headers: headers,
                data: formData,
                onload: (rsp) => {
                  let rspJson = JSON.parse(rsp.responseText);
                  if (rsp.status !== 200) {
                        log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red');
                        reject(rspJson?.success?.message || rspJson?.error?.message);
                  }
                  if (rspJson.status_code === 200) {
                        // 图片上传成功
                        let imgUrl = rspJson.image.url || rspJson.image.url_viewer || rspJson.image.url_short;
                        if (imgUrl)
                            insertToEditor(mdImgName === 0 ? `![${rspJson.image.filename}](${imgUrl})` : `![${mdImgName}](${imgUrl})`);
                        else {
                            log('图片上传成功, 但接口返回有误, 原始返回已粘贴到编辑器', 'red');
                            insertToEditor(`图片上传成功, 但接口返回有误: ${JSON.stringify(rspJson)})`);
                        }
                  } else
                        log(`图片上传失败: ${rspJson?.success?.message || rspJson?.error?.message}`, 'red');
                  resolve();
                },
                onerror: (error) => {
                  log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                  reject(error);
                }
            });
      });
    }

    async function uploadToEasyImages(file) {
      return new Promise((resolve, reject) => {
            let url = imgHost.url;
            let formData = new FormData();
            if (imgHost.token) {
                // 带token, 使用后端接口上传
                url += '/api/index.php'
                formData.append('token', imgHost.token);
                formData.append('image', file);
            } else {
                // 不带token, 使用前端接口上传
                url += '/app/upload.php'
                formData.append('file', file);
                // 十位时间戳作为sign
                formData.append('sign', Math.floor(Date.now() / 1000));
            }


            GM_xmlhttpRequest({
                method: 'POST',
                url: url,
                data: formData,
                onload: (rsp) => {
                  let rspJson = JSON.parse(rsp.responseText);
                  if (rsp.status !== 200) {
                        log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red');
                        reject(rspJson.result);
                  }
                  if (rspJson.code === 200) {
                        // 图片上传成功
                        if (rspJson?.url)
                            insertToEditor(`![${(mdImgName === 0 ? rspJson.srcName : mdImgName)}](${rspJson.url})`);
                        else {
                            log('图片上传成功, 但接口返回有误, 原始返回已粘贴到编辑器', 'red');
                            insertToEditor(`图片上传成功, 但接口返回有误: ${JSON.stringify(rspJson)})`);
                        }
                  } else
                        log(`图片上传失败: ${JSON.stringify(rspJson)}`, 'red');
                  resolve();
                },
                onerror: (error) => {
                  log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                  reject(error);
                }
            });
      });
    }

    function insertToCursor(sourceUrl, sourceName, errorString) {
      const codeMirrorElement = document.querySelector('.CodeMirror');
      const discuzEditorTextarea = document.querySelector('#e_textarea');


      if (codeMirrorElement) { // nodeseek 使用markdown语法
            const fullLink = `![${sourceName}](${sourceUrl})`
            const codeMirrorInstance = codeMirrorElement.CodeMirror;
            if (codeMirrorInstance) {
                const cursor = codeMirrorInstance.getCursor();

                codeMirrorInstance.replaceRange(`\n${fullLink} \n`, cursor);
            }
      } else if (discuzEditorTextarea) { //discuz 使用 BBS code语法
            const fullLink = `${sourceUrl}`
            const cursorPosition = discuzEditorTextarea.selectionStart; // 获取光标位置
            const textBefore = discuzEditorTextarea.value.substring(0, cursorPosition); // 光标前的文本
            const textAfter = discuzEditorTextarea.value.substring(cursorPosition); // 光标后的文本

            const markdownLink = fullLink; // 你要插入的内容
            discuzEditorTextarea.value = `${textBefore}${markdownLink}${textAfter}`; // 拼接插入的文本

            // 更新光标位置到插入文本的末尾
            discuzEditorTextarea.selectionStart = discuzEditorTextarea.selectionEnd = cursorPosition + markdownLink.length;

            // 让光标保持在文本框中
            discuzEditorTextarea.focus();
      } else {
            log('出现错误: 未找到编辑栏', 'red');
      }

    }

    function insertToEditor(fullLink, sourceName, sourceUrl) {

      const codeMirrorElement = document.querySelector('.CodeMirror');

      if (codeMirrorElement) { // nodeseek 使用markdown语法
            // nodeseek 还是用老逻辑 完整链接
            const codeMirrorInstance = codeMirrorElement.CodeMirror;
            if (codeMirrorInstance) {
                const cursor = codeMirrorInstance.getCursor();
                codeMirrorInstance.replaceRange(`\n${fullLink} \n`, cursor);
            }
      }
      if (fullLink.startsWith('!['))
            log('图片已插入到编辑器~', 'green');
    }

    // 在编辑器打印日志
    function log(message, color = '') {
      if (!document.getElementById('editor-enhance-logs')) {
            initEditorLogDiv();
      }
      const logDiv = document.getElementById('editor-enhance-logs');
      logDiv.innerHTML = `<div${color ? ` style="color: ${color};` : ''}">&nbsp;&nbsp;&nbsp;${message}&nbsp;</div>`;

      console.log(` ${message}`);
    }

    // 初始化显示日志的容器
    function initEditorLogDiv() {
      const logDiv = document.createElement('div');
      logDiv.id = 'editor-enhance-logs';
      logDiv.innerHTML = '';
      document.body.appendChild(logDiv);

      const editorToolbarDiv = document.querySelector('.mde-toolbar') || document.querySelector('#e_controls');
      editorToolbarDiv.appendChild(logDiv);
    }

})();

bxn21583 发表于 4 天前

好东西啊,试试看哈

showd 发表于 3 天前

好东西啊,试试看哈

tomcruise 发表于 3 天前

以后传图片岂不是很方便{:5_127:}

naxi2233 发表于 3 天前

好东西啊,试试看哈

lavalake 发表于 3 天前

好像拖放或者粘贴都无效哈

bin 发表于 3 天前

直接粘贴确实方便
页: [1]
查看完整版本: 分享一个粘贴图片自动上传图床的油猴脚本(适用奶昔论坛)