转跳到内容

【油猴脚本】便捷文件上传+自定义表情包


推荐贴

发布于 (已修改) · 只看该作者

6u2zvl.webp

作者:SS同盟脚本组

便捷文件上传-脚本安装地址

该脚本利用catbox提供的API,在sstm站内完成文件上传+外链插入的功能,避免同时打开多个窗口来回切换以插入外链文件的困扰,同时支持图片,视频,音频的插入

该脚本需要提供catbox账号hash,创建catbox账号并登陆后,进入https://catbox.moe/user/manage.php可得到当前userhash,写入脚本代码第26行。对于不想打开脚本编辑界面的用户,脚本在未检测到userhash时,也会通过弹窗提醒输入用户hash并保存

使用示例:(基于网络状况和文件大小,使用时可能会产生一定的延迟)

剧透
// ==UserScript==
// @name         sstm catbox uploader
// @namespace    https://sstm.moe/
// @description  先点击文本框确定插入点,再点击按钮,上传媒体文件
// @author       sstm脚本组
// @version      1.0
// @match        https://sstm.moe/topic/*
// @match        https://sstm.moe/profile/*
// @match        https://sstm.moe/messenger/*/*
// @match        https://sstm.moe/forum/*/?do=add
// @icon         https://s.sstmlt.com/board/monthly_2019_04/UNTITLED.png
// @license MIT
// @grant        GM_xmlhttpRequest
// @grant        GM_getValue
// @grant        GM_setValue
// @connect      catbox.moe
// @run-at       document-idle
// ==/UserScript==

(function () {
    'use strict';

    /* ========= 用户配置 ========= */

    // 留空时:优先读取 GM 存储;若仍为空,则首次运行时弹窗要求输入
    const HARDCODED_USER_HASH = "";

    /* ============================ */

    const GM_USER_HASH_KEY = "catbox_user_hash";
    const API_URL = "https://catbox.moe/user/api.php";

    let USER_HASH = "";
    let lastActiveEditor = null;

    function isValidUserHash(value) {
        return typeof value === "string" && value.trim().length > 0;
    }

    async function resolveUserHash() {
        // 1. 优先使用硬编码值
        if (isValidUserHash(HARDCODED_USER_HASH)) {
            const fixed = HARDCODED_USER_HASH.trim();
            await GM_setValue(GM_USER_HASH_KEY, fixed);
            console.log("[catbox] use hardcoded USER_HASH");
            return fixed;
        }

        // 2. 再读 GM 存储
        const saved = await GM_getValue(GM_USER_HASH_KEY, "");
        if (isValidUserHash(saved)) {
            console.log("[catbox] use saved USER_HASH");
            return saved.trim();
        }

        // 3. 都没有时弹窗要求输入
        const input = window.prompt(
            "未检测到 Catbox USER_HASH,请输入你的 user hash:",
            ""
        );

        if (!input) {
            console.warn("[catbox] 用户未输入 USER_HASH,脚本停止初始化");
            alert("未提供 Catbox USER_HASH,脚本不会继续运行。");
            return "";
        }

        const trimmed = input.trim();
        if (!isValidUserHash(trimmed)) {
            alert("输入的 USER_HASH 无效,脚本不会继续运行。");
            return "";
        }

        await GM_setValue(GM_USER_HASH_KEY, trimmed);
        console.log("[catbox] USER_HASH saved");
        return trimmed;
    }

    function rememberEditorFromNode(node) {
        if (!node || !(node instanceof Element)) return;

        const editor = node.closest(".cke_wysiwyg_div");
        if (editor) {
            lastActiveEditor = editor;
            console.log("[catbox] remember editor", editor);
        }
    }

    function getTargetEditor() {
        if (lastActiveEditor && document.body.contains(lastActiveEditor)) {
            return lastActiveEditor;
        }

        const active = document.activeElement;
        if (active && active.classList && active.classList.contains("cke_wysiwyg_div")) {
            lastActiveEditor = active;
            return active;
        }

        const visibleEditors = Array.from(document.querySelectorAll(".cke_wysiwyg_div"))
            .filter(el => el.offsetParent !== null);

        if (visibleEditors.length > 0) {
            lastActiveEditor = visibleEditors[0];
            return visibleEditors[0];
        }

        return document.querySelector(".cke_wysiwyg_div");
    }

    /* 插入按钮 */

    function createButton() {
        const mainFunction = () => {
            if (!USER_HASH) {
                alert("未设置 USER_HASH");
                return;
            }

            const input = document.createElement("input");
            input.type = "file";
            input.multiple = false;
            input.style.display = "none";
            document.body.appendChild(input);

            input.onchange = () => {
                if (input.files.length > 0) uploadFile(input.files[0]);
                input.remove();
            };

            input.click();
        };

        const observer = new MutationObserver(function (mutations) {
            mutations.forEach((record) => {
                if (!record.addedNodes.length) return;

                const menuClassTop = ['ipsMenu', 'ipsMenu_auto', 'ipsHide', 'ipsMenu_topCenter'];
                const menuClassBottom = ['ipsMenu', 'ipsMenu_auto', 'ipsHide', 'ipsMenu_bottomCenter'];

                for (const node of record.addedNodes) {
                    if (!node.tagName || node.tagName.toLowerCase() !== 'ul') continue;

                    const inEditor = node.closest('[id^="elEditor"]');
                    if (!inEditor) continue;

                    if (menuClassTop.every(cls => node.classList.contains(cls))) {
                        if (!node.querySelector("#catbox_upload_option_top")) {
                            const li = document.createElement('li');
                            li.classList.add('ipsMenu_item');
                            li.innerHTML = '<a id="catbox_upload_option_top">上传文件到 Catbox</a>';
                            li.addEventListener("click", mainFunction);
                            node.appendChild(li);
                            console.log('已插入 topCenter Catbox 按钮');
                        }
                    }

                    if (menuClassBottom.every(cls => node.classList.contains(cls))) {
                        if (!node.querySelector("#catbox_upload_option_bottom")) {
                            const li = document.createElement('li');
                            li.classList.add('ipsMenu_item');
                            li.innerHTML = '<a id="catbox_upload_option_bottom">上传文件到 Catbox</a>';
                            li.addEventListener("click", mainFunction);
                            node.appendChild(li);
                            console.log('已插入 bottomCenter Catbox 按钮');
                        }
                    }
                }
            });
        });

        observer.observe(document.body, { childList: true, subtree: true });
        console.log('已启动observer');

        window.addEventListener('beforeunload', function () {
            observer.disconnect();
        });
    }

    /* ========= 上传文件 ========= */

    function randomFilename(extension = "") {
        let chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
        let name = "";
        for (let i = 0; i < 10; i++) {
            name += chars.charAt(Math.floor(Math.random() * chars.length));
        }
        if (extension) name += "." + extension;
        return name;
    }

    function uploadFile(file) {
        const boundary = "----WebKitFormBoundary" + Math.random().toString(36).substr(2);
        const CRLF = "\r\n";

        let body = "";
        const appendField = (name, value) => {
            body += `--${boundary}${CRLF}`;
            body += `Content-Disposition: form-data; name="${name}"${CRLF}${CRLF}`;
            body += value + CRLF;
        };

        appendField("reqtype", "fileupload");
        appendField("userhash", USER_HASH || "");

        const ext = file.name.includes('.') ? file.name.split('.').pop() : "";
        const randomName = randomFilename(ext);

        body += `--${boundary}${CRLF}`;
        body += `Content-Disposition: form-data; name="fileToUpload"; filename="${randomName}"${CRLF}`;
        body += `Content-Type: ${file.type || "application/octet-stream"}${CRLF}${CRLF}`;

        const reader = new FileReader();
        reader.onload = () => {
            const fileData = new Uint8Array(reader.result);
            let binary = "";
            for (let i = 0; i < fileData.length; i++) {
                binary += String.fromCharCode(fileData[i]);
            }

            const finalBody = body + binary + CRLF + `--${boundary}--${CRLF}`;

            GM_xmlhttpRequest({
                method: "POST",
                url: API_URL,
                headers: {
                    "Content-Type": "multipart/form-data; boundary=" + boundary
                },
                data: finalBody,
                binary: true,
                onload: function (res) {
                    const url = res.responseText.trim();

                    if (!url.startsWith("https://files.catbox.moe/")) {
                        alert("上传失败: " + url + "\n请检查 USER_HASH 是否正确,或文件名是否存在非法字符。");
                        console.warn("Catbox 上传异常返回:", url);
                        return;
                    }

                    console.log("上传完成:", url);
                    insertMedia(url, file.type || "");
                },
                onerror: function (err) {
                    alert("上传失败: " + err.status);
                }
            });
        };

        reader.readAsArrayBuffer(file);
    }

    /* 插入内容 */

    function insertMedia(url, type) {
        const editor = getTargetEditor();

        if (!editor) {
            alert("未找到编辑器");
            return;
        }

        editor.focus();

        let html = "";

        if (type.startsWith("image")) {
            html = `
<img data-cke-saved-src="${url}" src="${url}" alt="" class="ipsImage" width="auto" height="auto">
`;
        } else if (type.startsWith("video")) {
            html = `
<video id="media_insert" controls style="width: 90%; height: auto;">
<source type="${type}" src="${url}">
</video>`;
        } else if (type.startsWith("audio")) {
            html = `
<video id="media_insert" controls style="width: 80%; height: 50px;">
<source type="${type}" src="${url}">
</video>`;
        } else {
            html = `<a href="${url}" target="_blank">${url}</a>`;
        }

        insertAtCursor(html);
    }

    /* 光标插入 */

    function insertAtCursor(html) {
        const sel = window.getSelection();

        if (!sel.rangeCount) {
            document.execCommand("insertHTML", false, html);
            return;
        }

        const range = sel.getRangeAt(0);
        range.deleteContents();

        const div = document.createElement("div");
        div.innerHTML = html;

        const frag = document.createDocumentFragment();
        let node, lastNode;

        while ((node = div.firstChild)) {
            lastNode = frag.appendChild(node);
        }

        range.insertNode(frag);

        if (lastNode) {
            range.setStartAfter(lastNode);
            range.collapse(true);
            sel.removeAllRanges();
            sel.addRange(range);
        }
    }

    /* 初始化 */

    async function init() {
        USER_HASH = await resolveUserHash();
        if (!USER_HASH) return;

        document.addEventListener("focusin", function (e) {
            rememberEditorFromNode(e.target);
        }, true);

        document.addEventListener("mousedown", function (e) {
            rememberEditorFromNode(e.target);
        }, true);

        createButton();
        console.log("[catbox] init done");
    }

    init();

})();

 

自定义表情包-脚本安装地址

该脚本允许将imgbox的gallery用作表情包,并插入到论坛的表情包组件中,本质上仍是将imgbox的外链图片插入,但是简化了其步骤

类似的,可以将用作表情包的链接写入脚本代码第26行。对于不想打开脚本编辑界面的用户,脚本在未检测到相册链接时,也会通过弹窗提醒输入并保存,为了避免过多的网络请求与分析,并不会每次插入都实时检查相册链接内容,当相册内上传了新图片时请点击刷新并耐心等待

使用示例:

i39vfa6j_o.png0QKWiwwq_o.png

剧透
// ==UserScript==
// @name Imgbox 表情包
// @namespace https://sstm.moe/
// @version 1.0
// @description  将imgbox相册解析为表情包
// @author       sstm脚本组
// @match        https://sstm.moe/topic/*
// @match        https://sstm.moe/profile/*
// @match        https://sstm.moe/messenger/*/*
// @match        https://sstm.moe/forum/*/?do=add
// @icon         https://s.sstmlt.com/board/monthly_2019_04/UNTITLED.png
// @license MIT
// @grant GM_xmlhttpRequest
// @grant GM_getValue
// @grant GM_setValue
// @connect imgbox.com
// @run-at document-idle
// @downloadURL https://update.greasyfork.org/scripts/570127/Imgbox%20%E8%A1%A8%E6%83%85%E5%8C%85.user.js
// @updateURL https://update.greasyfork.org/scripts/570127/Imgbox%20%E8%A1%A8%E6%83%85%E5%8C%85.meta.js
// ==/UserScript==

(function(){
    'use strict';

    const HARDCODED_ALBUM_URL = "";
    const GM_ALBUM_URL_KEY = "imgbox_album_url";
    const CACHE="imgbox_album_cache";
    let albumUrl = "";
    let emojiDOM=null;
    let lastActiveEditor = null;
    const insertedPanels = new WeakSet();
    console.log("[imgbox] start");

    function isValidAlbumUrl(url) {
        return typeof url === "string"
            && /^https?:\/\/(?:www\.)?imgbox\.com\/g\/[A-Za-z0-9_-]+/.test(url.trim());
    }

    async function resolveAlbumUrl() {
        if (isValidAlbumUrl(HARDCODED_ALBUM_URL)) {
            const fixed = HARDCODED_ALBUM_URL.trim();
            await GM_setValue(GM_ALBUM_URL_KEY, fixed);
            console.log("[imgbox] use hardcoded album url:", fixed);
            return fixed;
        }

        const saved = await GM_getValue(GM_ALBUM_URL_KEY, "");
        if (isValidAlbumUrl(saved)) {
            console.log("[imgbox] use saved GM album url:", saved);
            return saved.trim();
        }

        const input = window.prompt(
            "Imgbox 表情包:未检测到相册链接,请输入 imgbox 相册地址(例如 https://imgbox.com/g/xxxxxxx )",
            ""
        );

        if (!input) {
            console.warn("[imgbox] 用户未输入相册链接,脚本停止初始化");
            alert("未提供 Imgbox 相册链接,脚本不会继续运行。");
            return "";
        }

        const trimmed = input.trim();
        if (!isValidAlbumUrl(trimmed)) {
            console.warn("[imgbox] 输入的相册链接无效:", trimmed);
            alert("输入的 Imgbox 相册链接无效,脚本不会继续运行。");
            return "";
        }

        await GM_setValue(GM_ALBUM_URL_KEY, trimmed);
        console.log("[imgbox] saved album url to GM storage:", trimmed);
        return trimmed;
    }

    async function refreshCache(){
        console.log("[imgbox] refresh start 开始刷新");
        const html=await gmFetch(albumUrl);
        localStorage.setItem(CACHE,html);
        const list=parseAlbum(html);
        emojiDOM=buildDOM(list);
        document.querySelectorAll("[data-imgbox]").forEach(n=>{
            n.replaceWith(emojiDOM.cloneNode(true));
        });
        console.log("[imgbox] refresh done");
    }

    function gmFetch(url){
        return new Promise((resolve,reject)=>{
            GM_xmlhttpRequest({method:"GET",url,onload:r=>resolve(r.responseText),onerror:reject});
        });
    }

    function parseAlbum(html){
        const doc=new DOMParser().parseFromString(html,"text/html");
        const list=[];
        doc.querySelectorAll("#gallery-view-content img").forEach(img=>{
            list.push({thumb:img.src});
        });
        console.log("[imgbox] parsed:",list.length);
        return list;
    }

    function thumbToOrigin(url){
        return url.replace("thumbs","images").replace("_b.","_o.");
    }

    function rememberEditorFromNode(node) {
        if (!node || !(node instanceof Element)) return;

        const editor = node.closest(".cke_wysiwyg_div");
        if (editor) {
            lastActiveEditor = editor;
            console.log("[imgbox] remember editor", editor);
        }
    }

    function getTargetEditor(){
        if (lastActiveEditor && document.body.contains(lastActiveEditor)) {
            return lastActiveEditor;
        }

        const active = document.activeElement;
        if (active && active.classList && active.classList.contains("cke_wysiwyg_div")) {
            lastActiveEditor = active;
            return active;
        }

        const visibleEditors = Array.from(document.querySelectorAll(".cke_wysiwyg_div"))
            .filter(el => el.offsetParent !== null);

        if (visibleEditors.length > 0) {
            lastActiveEditor = visibleEditors[0];
            return visibleEditors[0];
        }

        return document.querySelector(".cke_wysiwyg_div");
    }

    function insertMedia(url){
        const editor=getTargetEditor();
        if(!editor){alert("未找到编辑器");return;}
        editor.focus();
        const html=`<img src="${url}" class="ipsImage" style="width: 200px; height: auto;">`;
        insertAtCursor(html);
    }

    function insertAtCursor(html){
        const sel=window.getSelection();
        if(!sel.rangeCount){document.execCommand("insertHTML",false,html);return;}
        const range=sel.getRangeAt(0);
        range.deleteContents();
        const div=document.createElement("div");
        div.innerHTML=html;
        const frag=document.createDocumentFragment();
        let node,lastNode;
        while(node=div.firstChild){lastNode=frag.appendChild(node);}
        range.insertNode(frag);
        if(lastNode){
            range.setStartAfter(lastNode);
            range.collapse(true);
            sel.removeAllRanges();
            sel.addRange(range);
        }
    }

    function buildDOM(list){
        const container=document.createElement("div");
        container.dataset.imgbox="1";
        const title=document.createElement("div");
        title.className="ipsAreaBackground_light ipsPad_half";
        title.style.display="flex";
        title.style.justifyContent="space-between";
        title.style.alignItems="center";

        const label=document.createElement("strong");
        label.textContent="自定义表情包";

        const btn=document.createElement("button");
        btn.textContent="刷新";
        btn.dataset.imgboxRefresh="1";
        btn.style.fontSize="12px";
        btn.style.cursor="pointer";

        title.appendChild(label);
        title.appendChild(btn);
        container.appendChild(title);
        const cat = document.createElement("div");
        cat.className = "ipsEmoticons_category";

        let row = document.createElement("div");
        row.className = "ipsEmoticons_row ipsEmoji";

        list.forEach((item, index) => {
            const div = document.createElement("div");
            div.className = "ipsEmoticons_item";
            div.dataset.thumb = item.thumb;

            const img = document.createElement("img");
            img.src = item.thumb;
            img.loading = "lazy";

            div.appendChild(img);
            row.appendChild(div);

            if ((index + 1) % 8 === 0) {
                cat.appendChild(row);
                row = document.createElement("div");
                row.className = "ipsEmoticons_row ipsEmoji";
            }
        });

        if (row.children.length > 0) {
            cat.appendChild(row);
        }

        container.appendChild(cat);
        return container;
    }

    function insertPanel(panel){
        if(panel.dataset.imgboxDone) return;

        setTimeout(() => {
            if (!document.body.contains(panel)) return;
            if (insertedPanels.has(panel)) return;
            insertedPanels.add(panel);
            const node = emojiDOM.cloneNode(true);
            panel.prepend(node);
            console.log("[imgbox] 延迟插入成功", node);
        }, 300);
    }

    const observer=new MutationObserver(mutations=>{
        for(const m of mutations){
            for(const node of m.addedNodes){
                if(node.nodeType!==1) continue;

                let panel=null;

                if(node.matches?.(".ipsEmoticons_content")){
                    panel=node;
                }else{
                    panel=node.querySelector?.(".ipsEmoticons_content");
                }

                if(panel){
                    requestAnimationFrame(()=>{
                        insertPanel(panel);
                    });
                }
            }
        }
    });

    // 关键:记录用户最后真正操作的编辑器
    document.addEventListener("focusin", function(e){
        rememberEditorFromNode(e.target);
    }, true);

    document.addEventListener("mousedown", function(e){
        rememberEditorFromNode(e.target);

        const refresh=e.target.closest("[data-imgbox-refresh]");
        if(refresh){
            e.preventDefault();
            e.stopPropagation();
            console.log("[imgbox] refresh click");
            refreshCache();
            return;
        }

        const item=e.target.closest(".ipsEmoticons_item[data-thumb]");
        if(item){
            e.preventDefault();
            e.stopPropagation();
            const origin=thumbToOrigin(item.dataset.thumb);
            console.log("[imgbox] click:",origin);
            insertMedia(origin);
        }

    }, true);

    async function init(){
        albumUrl = await resolveAlbumUrl();
        if (!albumUrl) return;

        let html=localStorage.getItem(CACHE);
        if(!html){
            console.log("[imgbox] fetching album 没有缓存,直接获取");
            html=await gmFetch(albumUrl);
            localStorage.setItem(CACHE,html);
        }
        const list=parseAlbum(html);
        emojiDOM=buildDOM(list);
        observer.observe(document.body,{
            childList:true,
            subtree:true
        });
    }

    init();

})();

 

SS同盟脚本组目前正在组建中,后续会有新的脚本和信息放出,敬请期待:1a64f5f1867e53ba24b96f710a24db78:

,由367ddd修改
注释
阿露今日也在歌唱 阿露今日也在歌唱 50.00节操 实际实用!
  • 喜欢(+1) 3
  • 感谢(+1) 1
  • 顶(+1) 1

创建帐号或登入才能点评

您必须成为用户才能点评

创建帐号

在我们社区注册个新的帐号。非常简单!

注册新帐号

登入

已有帐号? 登入

现在登入
×
×
  • 新建...

重要消息

为使您更好地使用该站点,请仔细阅读以下内容: 使用条款