Greasy Fork

Greasy Fork is available in English.

图片/视频上传助手(智能命名版)

根据标签内容智能命名文件,支持图片/视频上传到指定 API,含悬浮、菜单、剪贴板上传功能

当前为 2025-10-17 提交的版本,查看 最新版本

// ==UserScript==
// @name         图片/视频上传助手(智能命名版)
// @namespace    https://example.com/
// @version      1.2
// @description  根据标签内容智能命名文件,支持图片/视频上传到指定 API,含悬浮、菜单、剪贴板上传功能
// @author       云空陆
// @license MIT
// @match        *://*/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @connect      your.domain
// ==/UserScript==

(function () {
    'use strict';

      /*********** 配置区域 ***********/
    const UPLOAD_API = "";  // 上传 API直接填写api地址
    const AUTH_CODE = "";           // 上传认证码或者apitoken(可为空)
    const SERVER_COMPRESS = "false";     // true/false/空(空使用默认 true)
    const UPLOAD_CHANNEL = "";      // telegram/cfr2/s3/空(空使用默认 telegram)
    const AUTO_RETRY = "";          // true/false/空(空使用默认 true)
    const UPLOAD_NAME_TYPE = "";    // default/index/origin/short/空(空使用默认 default)
    const RETURN_FORMAT = "";       // default/full/空(空使用默认 default)
    const UPLOAD_FOLDER = "";       // 相对目录,空使用默认
    /********************************/

    let currentFile = null;

    const uploadBtn = document.createElement("div");
    uploadBtn.innerText = "📤";
    Object.assign(uploadBtn.style, {
        position: "fixed",
        padding: "6px 10px",
        background: "#f13535ff",
        color: "#fff",
        borderRadius: "4px",
        fontSize: "12px",
        cursor: "pointer",
        zIndex: "999999",
        display: "none",
        boxShadow: "0 2px 6px rgba(0,0,0,0.3)",
        transition: "opacity 0.2s",
    });
    document.body.appendChild(uploadBtn);

    let hideUploadTimer = null;

    function getFileSrc(el) {
        return el.getAttribute('data-original') || el.getAttribute('data-src') || el.src || '';
    }

    // 获取节点中可读标题作为文件名
    function getReadableName(el) {
        const nameAttr = el.getAttribute("alt") ||
            el.getAttribute("title") ||
            el.getAttribute("aria-label") ||
            el.getAttribute("data-name") ||
            el.getAttribute("data-title") || "";
        if (nameAttr && nameAttr.trim()) return sanitizeFilename(nameAttr.trim());
        return "";
    }

    // 清理非法文件名字符
    function sanitizeFilename(name) {
        return name.replace(/[\\/:*?"<>|]/g, "_").slice(0, 50);
    }

    // 根据 MIME 类型或 URL 自动获取扩展名
    function guessExtension(mime, url = "") {
        if (mime) {
            if (mime.startsWith("image/")) return mime.split("/")[1];
            if (mime.startsWith("video/")) return mime.split("/")[1];
        }
        const m = url.match(/\.(jpg|jpeg|png|webp|gif|mp4|mov|avi|mkv|webm|svg|bmp|heic|heif)(\?|#|$)/i);
        if (m) return m[1].toLowerCase();
        return "dat";
    }

    // ===== 鼠标悬浮显示按钮 =====
    document.addEventListener("mouseover", (e) => {
        let el = e.target;
        if ((el.tagName === "IMG" || el.tagName === "VIDEO") && getFileSrc(el)) {
            currentFile = el;
            const rect = el.getBoundingClientRect();
            uploadBtn.style.left = `${rect.right - 160}px`;
            uploadBtn.style.top = `${rect.top + 10}px`;
            uploadBtn.style.display = "block";
            uploadBtn.style.opacity = "1";
            clearTimeout(hideUploadTimer);
        }
    });

    document.addEventListener("mouseout", (e) => {
        if (!uploadBtn.contains(e.relatedTarget) && (!currentFile || !currentFile.contains(e.relatedTarget))) {
            clearTimeout(hideUploadTimer);
            hideUploadTimer = setTimeout(() => {
                uploadBtn.style.opacity = "0";
                setTimeout(() => { uploadBtn.style.display = "none"; }, 300);
            }, 500);
        }
    });

    uploadBtn.addEventListener("mouseenter", () => clearTimeout(hideUploadTimer));

    // ===== 上传核心逻辑 =====
    async function uploadFileByUrl(fileUrl, index = 1, total = 1, el = null) {
        if (!fileUrl) { showTip("❌ 未找到文件链接", true); return; }

        showTip(`📤 上传中(${index}/${total})\n${fileUrl}`, false);

        try {
            const resp = await fetch(fileUrl);
            const blob = await resp.blob();

            // 智能生成文件名
            let baseName = el ? getReadableName(el) : "";
            const ext = guessExtension(blob.type, fileUrl);
            if (!baseName) {
                const fromUrl = decodeURIComponent(fileUrl.split("/").pop().split("?")[0]);
                baseName = sanitizeFilename(fromUrl.replace(/\.[a-z0-9]+$/i, "")) || `file_${Date.now()}`;
            }
            const filename = `${baseName}.${ext}`;

            const formData = new FormData();
            formData.append("file", blob, filename);

            const queryParams = new URLSearchParams();
            if (AUTH_CODE) queryParams.append("authCode", AUTH_CODE);
            if (SERVER_COMPRESS) queryParams.append("serverCompress", SERVER_COMPRESS);
            if (UPLOAD_CHANNEL) queryParams.append("uploadChannel", UPLOAD_CHANNEL);
            if (AUTO_RETRY) queryParams.append("autoRetry", AUTO_RETRY);
            if (UPLOAD_NAME_TYPE) queryParams.append("uploadNameType", UPLOAD_NAME_TYPE);
            if (RETURN_FORMAT) queryParams.append("returnFormat", RETURN_FORMAT);
            if (UPLOAD_FOLDER) queryParams.append("uploadFolder", UPLOAD_FOLDER);

            GM_xmlhttpRequest({
                method: "POST",
                url: `${UPLOAD_API}?${queryParams.toString()}`,
                data: formData,
                responseType: "json",
                onload: function (res) {
                    let result;
                    try { result = res.response || JSON.parse(res.responseText); }
                    catch (e) { showTip("❌ 上传响应解析失败", true); return; }

                    if (Array.isArray(result) && result[0]?.src) {
                        const fullUrl = `${UPLOAD_API.replace(/\/upload$/, "")}${result[0].src}`;
                        showTip(`✅ 上传成功(${index}/${total})\n${filename}\n链接: ${fullUrl}`, true, fullUrl);
                        console.log("上传成功:", fullUrl);
                    } else {
                        showTip(`❌ 上传失败(${index}/${total}): 未返回有效链接`, true);
                        console.error(result);
                    }
                },
                onerror: function () { showTip("❌ 网络请求失败", true); }
            });
        } catch (err) {
            console.error(err);
            showTip("❌ 文件加载或处理失败", true);
        }
    }

    uploadBtn.addEventListener("click", async () => {
        if (!currentFile) return;
        const url = getFileSrc(currentFile);
        await uploadFileByUrl(url, 1, 1, currentFile);
    });

    // ===== 提示框 =====
    let tipDiv = null, autoCloseTimer = null;
    function showTip(msg, showControls = false, copyText = "") {
        if (!tipDiv) {
            tipDiv = document.createElement("div");
            Object.assign(tipDiv.style, {
                position: "fixed", top: "20px", right: "20px",
                background: "rgba(0,0,0,0.55)", color: "#fff",
                padding: "10px 15px", borderRadius: "6px",
                zIndex: "999999", fontSize: "13px",
                whiteSpace: "pre-wrap", wordBreak: "break-word",
                maxWidth: "320px", maxHeight: "250px", overflowY: "auto"
            });
            document.body.appendChild(tipDiv);
        }
        tipDiv.innerHTML = "";
        const textNode = document.createElement("div");
        textNode.innerText = msg;
        tipDiv.appendChild(textNode);

        if (showControls) {
            const btnContainer = document.createElement("div");
            Object.assign(btnContainer.style, { marginTop: "6px", textAlign: "right" });

            if (copyText) {
                const copyBtn = document.createElement("button");
                copyBtn.innerText = "复制链接";
                Object.assign(copyBtn.style, {
                    marginRight: "5px",
                    cursor: "pointer",
                    color: "#fff",
                    background: "#f13535ff",
                    border: "none",
                    borderRadius: "4px",
                    padding: "3px 8px",
                    fontSize: "12px"
                });
                copyBtn.onclick = () => {
                    navigator.clipboard.writeText(copyText).then(() => {
                        copyBtn.innerText = "已复制";
                        setTimeout(() => copyBtn.innerText = "复制链接", 1500);
                    });
                };
                btnContainer.appendChild(copyBtn);
            }

            const closeBtn = document.createElement("button");
            closeBtn.innerText = "关闭";
            Object.assign(closeBtn.style, {
                cursor: "pointer",
                color: "#fff",
                background: "#f13535ff",
                border: "none",
                borderRadius: "4px",
                padding: "3px 8px",
                fontSize: "12px"
            });
            closeBtn.onclick = () => { tipDiv.style.display = "none"; clearTimeout(autoCloseTimer); };
            btnContainer.appendChild(closeBtn);
            tipDiv.appendChild(btnContainer);
        }

        tipDiv.style.display = "block";
        clearTimeout(autoCloseTimer);
        autoCloseTimer = setTimeout(() => { tipDiv.style.display = "none"; }, 10000);
    }

    // ===== 菜单上传 =====
    GM_registerMenuCommand("📥 手动输入链接上传", async () => {
        const input = prompt("请输入文件链接(可输入多个链接,用换行或逗号分隔):");
        if (!input) return;
        const urls = input.split(/\n|,|;|\s+/).map(u => u.trim()).filter(Boolean);
        const total = urls.length;
        for (let i = 0; i < total; i++) {
            await uploadFileByUrl(urls[i], i + 1, total);
            await new Promise(r => setTimeout(r, 1200));
        }
        showTip(`✅ 全部上传完成,共 ${total} 个文件`, true);
    });

    // ===== 剪贴板上传按钮 =====
    const clipBtn = document.createElement("div");
    clipBtn.innerText = "📋剪贴图床";
    Object.assign(clipBtn.style, {
        position: "fixed",
        right: localStorage.getItem("clipBtn_right") || "20px",
        bottom: localStorage.getItem("clipBtn_bottom") || "80px",
        background: "#f13535ff",
        color: "#fff",
        padding: "8px 12px",
        borderRadius: "20px",
        fontSize: "13px",
        cursor: "pointer",
        boxShadow: "0 2px 6px rgba(0,0,0,0.3)",
        zIndex: "999999",
        opacity: "0.9",
        userSelect: "none",
    });
    document.body.appendChild(clipBtn);

    // ===== 拖拽逻辑 =====
    let isDragging = false, offsetX = 0, offsetY = 0;
    clipBtn.addEventListener("mousedown", (e) => {
        isDragging = true;
        offsetX = e.clientX - clipBtn.getBoundingClientRect().left;
        offsetY = e.clientY - clipBtn.getBoundingClientRect().top;
        clipBtn.style.transition = "none";
        e.preventDefault();
    });
    document.addEventListener("mousemove", (e) => {
        if (!isDragging) return;
        let left = e.clientX - offsetX;
        let top = e.clientY - offsetY;
        left = Math.max(0, Math.min(window.innerWidth - clipBtn.offsetWidth, left));
        top = Math.max(0, Math.min(window.innerHeight - clipBtn.offsetHeight, top));
        clipBtn.style.left = left + "px";
        clipBtn.style.top = top + "px";
        clipBtn.style.right = "auto";
        clipBtn.style.bottom = "auto";
    });
    document.addEventListener("mouseup", () => {
        if (isDragging) {
            isDragging = false;
            clipBtn.style.transition = "opacity 0.2s";
            localStorage.setItem("clipBtn_left", clipBtn.style.left);
            localStorage.setItem("clipBtn_top", clipBtn.style.top);
        }
    });

    // ===== 剪贴板上传逻辑 =====
    clipBtn.addEventListener("click", async () => {
        if (isDragging) return;
        try {
            const items = await navigator.clipboard.read();
            for (const item of items) {
                for (const type of item.types) {
                    if (type.startsWith("image/") || type.startsWith("video/")) {
                        const blob = await item.getType(type);
                        const blobUrl = URL.createObjectURL(blob);
                        showTip("📋 检测到文件,正在上传...", false);
                        await uploadFileByUrl(blobUrl);
                        return;
                    }
                }
            }
            const text = await navigator.clipboard.readText();
            if (/^https?:\/\/[^\s]+/i.test(text.trim())) {
                showTip("📋 检测到文件链接,正在上传...", false);
                await uploadFileByUrl(text.trim());
            } else {
                showTip("⚠️ 剪贴板中未检测到文件或链接", true);
            }
        } catch (err) {
            console.error(err);
            showTip("❌ 无法访问剪贴板,请检查浏览器权限", true);
        }
    });

})();