概述

此用户脚本来源于picbed(sapic)的一项特有功能,实现在Web中,通过在图片上“按键+右击”显示上传按钮,上传到Web图床!

考虑到其他用户可能有此需求,故将此部分拎出来分享。

要求

  • 你的Web图床有API上传接口且支持url方式上传

    即提交图片url地址,你的图床可以直接下载保存。

  • 匿名上传或允许在Header设置 Authorization: Token <密钥> 作为登录用户上传

使用说明

这是一段用户脚本,依赖于浏览器的Tampermonkey扩展(此类型的其他扩展实现不一定支持,比如暴力猴)。

其效果和限制条件请参考picbed-userscript文档(除效果、限制外,其他是picbed图床程序才有的功能开启设置,忽略它)。

源码

安装好扩展后,添加新脚本,将以下代码复制过去,注意改下 const cfg 的配置内容,后面注释有说明。

// ==UserScript==
// @name        Upload to your picbed
// @version     0.1.0
// @description 上传图片到你的图床
// @author      staugur
// @namespace   https://www.saintic.com/
// @include     http://*
// @include     https://*
// @exclude     https://*.aliyun.com/*
// @run-at      document-start
// @grant       GM_info
// @created     2020-08-14
// @modified    none
// @github      https://github.com/staugur/userscript
// @supportURL  https://github.com/staugur/userscript/issues/
// ==/UserScript==

(function ({
    'use strict';

    const cfg = {
        "hot_key""ctrlKey"// 允许的值: altKey shiftKey
        "api_url"""// 图片上传接口地址
        "token"""// 放在Header的 `Authorization: Token <here>` 用以验证
        "upload_name""file"// 上传字段,按照服务端要求设置
        "id""picbed-menu"// 不用改
    };

    let isObject = v => Object.prototype.toString.call(v) === '[object Object]',
        hasMenu = () => document.getElementById(cfg.id) ? true : false,
        getMenu = () => document.getElementById(cfg.id),
        hasSendingMenu = () => {
            if (hasMenu()) {
                let p = getMenu();
                if (p && p.dataset.sendStatus === "sending") {
                    return true;
                }
            }
            return false;
        },
        setBtnText = text => {
            if (text) getMenu().querySelector("button").textContent = text;
        },
        removeMenu = () => {
            if (hasMenu()) getMenu().remove();
        };

    function upload(src, opts{
        if (!src) return false;
        if (!opts) opts = {};
        if (!isObject(opts)) return false;

        let data = new FormData();
        data.append(cfg.upload_name, src);
        data.append('origin'`userscript/${GM_info.script.version}`);
        Object.assign(opts, {
            url: cfg.api_url,
            method"POST",
            headers: {
                Authorization`Token ${cfg.token}`
            },
            data: data,
        });

        let xhr = new XMLHttpRequest(),
            error = typeof opts.error === 'function' ? opts.error : msg => {
                console.error(msg);
            };
        xhr.responseType = 'json';
        xhr.open(opts.method, opts.url, true);
        for (let key in opts.headers) {
            xhr.setRequestHeader(key, opts.headers[key]);
        }
        xhr.onloadstart = opts.start || null;
        xhr.onload = function ({
            typeof opts.success === 'function' && opts.success(xhr.response);
        };
        xhr.onerror = error("请求错误");
        try {
            xhr.send(opts.data);
        } catch (e) {
            error("网络异常");
        };
    }

    function createMenu(e, onClick{
        if (hasSendingMenu()) return false;
        removeMenu();

        let src = e.target.src;
        let wrapperCss = [
            "position: absolute",
            `left: ${e.pageX}px`,
            `top:${e.pageY}px`,
            "z-index: 9999999",
            "width: 200px",
            "background-color: #fff",
            "color: #000",
            "text-align: center",
            "border: 1px #409eff solid",
            "font-size: 14px",
            "line-height: 24px",
            "overflow: hidden",
            "white-space: nowrap",
            `text-overflow: ".../${src.split("/").slice(-1)[0]}"`,
        ];
        let wrapper = document.createElement('div');
        wrapper.style.cssText = wrapperCss.join(";");
        wrapper.textContent = src;
        wrapper.setAttribute('id', cfg.id);

        let btnCss = [
            'display: block',
            'margin: 0 auto',
            'width: 100%',
            'padding: 5px 10px',
            'font-size: 12px',
            'background-color: #fff',
            'color: #409eff',
            'border: 0px',
            'cursor: pointer',
            'user-select: none',
        ];
        let btn = document.createElement('button');
        btn.style.cssText = btnCss.join(";");
        btn.textContent = "点击上传";
        btn.onclick = onClick;

        wrapper.appendChild(btn);
        document.body.appendChild(wrapper);
    }

    document.addEventListener('mousedown'function (e{
        //恢复默认右键菜单
        document.oncontextmenu = null;
        //在图片上使用 快捷键+鼠标右击 打开自定义菜单
        if (e[cfg.hot_key] === true && e.button === 2) {
            if (e.target.tagName.toLowerCase() === 'img' && e.target.src) {
                //确定选中图片右键,打开上传菜单
                document.oncontextmenu = () => false;
                createMenu(e, () => {
                    let menu = getMenu();
                    menu.querySelector("button").onclick = null;
                    menu.querySelector("button").style.cursor = 'text';
                    if (/^(?:blob:|filesystem:)/.test(e.target.src)) {
                        setBtnText("不支持的图片格式");
                        return false;
                    }
                    upload(e.target.src, {
                        start() => {
                            menu.dataset.sendStatus = "sending";
                            setBtnText("正在上传...");
                        },
                        successres => {
                            if (res.code === 0) {
                                menu.dataset.sendStatus = "success";
                                setBtnText("上传成功");
                            } else {
                                menu.dataset.sendStatus = "fail";
                                setBtnText(res.msg);
                            }
                        },
                        errormsg => {
                            menu.dataset.sendStatus = "error";
                            setBtnText(msg);
                        },
                    });
                });
            }
        } else {
            //非面板内的点击/右击等操作(且无发送中状态)则关闭菜单
            if (e.target.id !== cfg.id && e.target.parentElement.id !== cfg.id) {
                if (!hasSendingMenu()) removeMenu();
            }
        }
    }, true);
})();

·End·