LINUX SB 快照站

【插件】响应咱们站长,写了一个图床插件,不知道该不该发.

原帖: linux.sb/topic/11762 · 共 16 楼 · 标题快照 2026-08-12 15:42:17

楼主
发帖 2026-08-12 15:36:14 · 快照 2026-08-12 15:42:17

看到站长的帖子:
image
正好手头有现成的图床插件.非常的短小简单.
用法:
1.把代码加入油猴.
2.复制图片,在编辑器直接粘贴
提示上传:
image
然后上传会非常慢,因为脚本内置的服务器是示例,非常慢小,建议替换成你的图床.
image
3.注意:脚本把其他途径的撇了,只有简单图床的.然后里面内置的图床是我临时搭建的示例.很慢很慢有时候还不成功,换成自己的图床就好了.还有就是如果图片有外链帖子会被强制审核吧所以这个插件不知道该不该发.
4.肯定有很多错误,后续可能有上传失败的情况,可以自己任意修改和发布,本人放弃所有权限只为linux.sb更好发展

// ==UserScript==
// @name         图床linuxdoissb
// @namespace    https://linux.sb/
// @connect      22.2222222.best
// @version      0.9.0
// @description  为linux.sb专门写的图床插件,提供粘贴/拖放/按钮上传到 EasyImages
// @author       大帅哥
// @match        *://linux.sb/*
// @match        *://hostloc.com/*
// @icon         https://linux.sb/app/assets/index.svg
// @grant        GM_xmlhttpRequest
// @license      MPL-1.0 License
// ==/UserScript==



(function () {
    'use strict';

    // EasyImages 配置,你要配置的2项
    const imgHost = {
        type: 'EasyImages',
        url: 'https://22.2222222.best', // 图床地址,改为你自己的
        token: '4ed5cc908146b9318849e586dc0280f1', // 你的图床apikey,没有就留空
    };
    const mdImgName = 'image'; // 插入 markdown 时的 alt 文本
    const submitByKey = true; // Ctrl+Enter 触发提交

    const EDITOR_SELECTOR = 'textarea#markdown-textarea-1, textarea[name="body"]';
    const TOOLBAR_SELECTOR = '.markdown-textarea[data-markdown-textarea], .markdown-textarea';
    const IMAGE_BTN_SELECTOR = 'button[data-markdown-action="image"]';

    window.addEventListener('load', initEditorEnhancer, false);

    function getEditor() {
        return document.querySelector(EDITOR_SELECTOR);
    }

    function getToolbar() {
        return document.querySelector(TOOLBAR_SELECTOR);
    }

    function initEditorEnhancer() {
        // 粘贴上传
        document.addEventListener('paste', handlePasteEvt);

 
        bindDropZone();
        const dropTimer = setInterval(() => {
            if (bindDropZone()) clearInterval(dropTimer);
        }, 300);
        setTimeout(() => clearInterval(dropTimer), 15000);


        const btnTimer = setInterval(() => {
            const oldBtn = document.querySelector(IMAGE_BTN_SELECTOR);
            if (!oldBtn) return;
            clearInterval(btnTimer);
            const newBtn = oldBtn.cloneNode(true);
            oldBtn.parentNode.replaceChild(newBtn, oldBtn);
            newBtn.addEventListener('click', (e) => {
                e.preventDefault();
                e.stopPropagation();
                handleImgBtnClick();
            });
        }, 200);
        setTimeout(() => clearInterval(btnTimer), 15000);


        if (submitByKey) {
            document.addEventListener('keydown', (event) => {
                if (!(event.ctrlKey && event.key === 'Enter')) return;
                const button =
                    document.querySelector('.submit.btn') ||
                    document.querySelector('button[type="submit"]');
                if (button) button.click();
            });
        }
    }

    function bindDropZone() {
        const dropZone = getEditor();
        if (!dropZone || dropZone.dataset.easyimgDropBound === '1') return !!dropZone;
        dropZone.dataset.easyimgDropBound = '1';

        dropZone.addEventListener('dragover', (e) => {
            e.preventDefault();
            e.stopPropagation();
            e.dataTransfer.dropEffect = 'copy';
        });

        dropZone.addEventListener('drop', (e) => {
            e.preventDefault();
            e.stopPropagation();
            log('正在处理拖放内容...');
            const imageFiles = [];
            for (const 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(
                imageFiles.map((file) => ({
                    kind: 'file',
                    type: file.type,
                    getAsFile: () => file,
                }))
            );
        });
        return true;
    }

    function handlePasteEvt(event) {
        const items = (event.clipboardData || event.originalEvent.clipboardData).items;
        if (!items || items.length === 0) return;

        let hasImage = false;
        for (const item of items) {
            if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
                hasImage = true;
                break;
            }
        }
        if (!hasImage) return;

 
        event.preventDefault();
        log('正在处理粘贴内容...');
        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 || !files.length) return;
            const items = [...files].map((file) => ({
                kind: 'file',
                type: file.type,
                getAsFile: () => file,
            }));
            uploadImage(items);
        };
        input.click();
    }

    async function uploadImage(items) {
        const imageFiles = [];
        for (const item of items) {
            if (item.kind === 'file' && item.type.indexOf('image/') !== -1) {
                imageFiles.push(item.getAsFile());
            }
        }
        if (imageFiles.length === 0) {
            log('你粘贴的内容好像没有图片哦', 'red');
            return;
        }

        for (let i = 0; i < imageFiles.length; i++) {
            if (imageFiles.length > 1) log(`上传第 ${i + 1} / ${imageFiles.length} 张图片...`);
            else log('上传图片...');
            await uploadToEasyImages(imageFiles[i]);
        }
    }

    async function uploadToEasyImages(file) {
        return new Promise((resolve, reject) => {
            let url = imgHost.url;
            const formData = new FormData();

            if (imgHost.token) {

                url += '/api/index.php';
                formData.append('token', imgHost.token);
                formData.append('image', file);
            } else {

                url += '/app/upload.php';
                formData.append('file', file);
                formData.append('sign', Math.floor(Date.now() / 1000));
            }

            GM_xmlhttpRequest({
                method: 'POST',
                url: url,
                data: formData,
                onload: (rsp) => {
                    let rspJson;
                    try {
                        rspJson = JSON.parse(rsp.responseText);

                    } catch (err) {
                        
                        log(`图片上传失败: 响应不是 JSON`, 'red');
                        reject(err);
                        return;
                    }

                    if (rsp.status !== 200) {
                        log(`图片上传失败: ${rsp.status} ${rsp.statusText}`, 'red');
                        reject(rspJson.result);
                        return;
                    }

                    if (rspJson.code === 200 && rspJson.url) {
                        log('图片上传成功', 'green');
                        insertToEditor(`![${mdImgName}](${rspJson.url})`);
                    } else if (rspJson.code === 200) {
                        log('图片上传成功, 但接口返回有误', 'red');
                        insertToEditor(`图片上传成功, 但接口返回有误: ${JSON.stringify(rspJson)}`);
                    } else {
                        log(`图片上传失败: ${JSON.stringify(rspJson)}`, 'red');
                    }
                    resolve();
                },
                onerror: (error) => {
                    log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                    reject(error);
                },
            });
        });
    }


    function insertToEditor(text) {
        const ta =
            getEditor() ||
            document.querySelector('#e_textarea') ||
            document.querySelector('#fastpostmessage');

        if (!ta) {
            log('出现错误: 未找到编辑栏 (#markdown-textarea-1)', 'red');
            return;
        }

        const start = typeof ta.selectionStart === 'number' ? ta.selectionStart : ta.value.length;
        const end = typeof ta.selectionEnd === 'number' ? ta.selectionEnd : start;
        const before = ta.value.substring(0, start);
        const after = ta.value.substring(end);
        const insert = `\n${text}\n`;

        ta.value = before + insert + after;
        const pos = start + insert.length;
        ta.selectionStart = ta.selectionEnd = pos;
        ta.focus();


        ta.dispatchEvent(new Event('input', { bubbles: true }));
        ta.dispatchEvent(new Event('change', { bubbles: true }));

        if (text.startsWith('![')) log('图片已插入到编辑器~', 'green');
    }

    function log(message, color = '') {
        if (!document.getElementById('editor-enhance-logs')) {
            initEditorLogDiv();
        }
        const logDiv = document.getElementById('editor-enhance-logs');
        if (logDiv) {
            logDiv.innerHTML = `<div${color ? ` style="color:${color};"` : ''}>&nbsp;&nbsp;&nbsp;${message}&nbsp;</div>`;
        }
        console.log(`[Editor-EasyImages] ${message}`);
    }

    function initEditorLogDiv() {
        let logDiv = document.getElementById('editor-enhance-logs');
        if (!logDiv) {
            logDiv = document.createElement('div');
            logDiv.id = 'editor-enhance-logs';
            document.body.appendChild(logDiv);
        }
        const toolbar = getToolbar();
        if (toolbar && !toolbar.contains(logDiv)) {
            toolbar.appendChild(logDiv);
        }
    }
})();

附带一个骂始皇的示例图:

image

#1
发帖 2026-08-12 15:42:06 · 快照 2026-08-12 15:45:41

开源!传 Greasy Fork 👍

#1 · 历史 (1)
发帖 2026-08-12 15:42:36 · 快照 2026-08-17 20:54:54

不错,顶!

#3 · 历史 (1)
发帖 2026-08-12 15:50:47 · 快照 2026-08-17 20:54:54

非常好

#4 · 历史 (1)
发帖 2026-08-13 08:54:49 · 快照 2026-08-17 20:54:54

我以为大水哥只会水🥺

#7 · 历史 (1)
发帖 2026-08-13 12:27:17 · 快照 2026-08-17 20:54:54

太棒了 以前还得自己到外面找图床 现在好了

#8 · 历史 (1)
发帖 2026-08-13 15:48:48 · 快照 2026-08-17 20:54:54

@一拳超人 #7 你这个脚本好像有问题,会导致ctrl+enter直接回首页。编辑完回复内容,一按,直接回首页了,搞了我好几次脑子。刚刚让codex查出来是你脚本的bug。我自己修复了。

#10 · 历史 (1)
发帖 2026-08-13 15:53:41 · 快照 2026-08-17 20:54:54

@douzi #10 这是我自己本地cockpit的号池啊,其中的号是我P的,没有那么多20x

#11 · 历史 (1)
发帖 2026-08-13 15:54:16 · 快照 2026-08-17 20:54:54

这是修复完ctrl+enter的版本:

// ==UserScript==
// @name         图床linuxdoissb
// @namespace    https://linux.sb/
// @connect      22.2222222.best
// @version      0.9.1
// @description  为 linux.sb 专门写的图床插件,支持粘贴、拖放、按钮上传到 EasyImages,并修复 Ctrl+Enter 错误提交搜索框的问题
// @author       大帅哥
// @match        *://linux.sb/*
// @match        *://hostloc.com/*
// @icon         https://linux.sb/app/assets/index.svg
// @grant        GM_xmlhttpRequest
// @license      MPL-1.0 License
// @downloadURL  http://192.168.1.1/jiaoben/%E5%9B%BE%E5%BA%8Alinuxdoissb.user.js
// @updateURL    http://192.168.1.1/jiaoben/%E5%9B%BE%E5%BA%8Alinuxdoissb.user.js
// ==/UserScript==

(function () {
    'use strict';

    const imgHost = {
        type: 'EasyImages',
        url: 'https://22.2222222.best',
        token: '4ed5cc908146b9318849e586dc0280f1',
    };

    const mdImgName = 'image';
    const submitByKey = true;
    const EDITOR_SELECTOR = 'textarea#markdown-textarea-1, textarea[name="body"]';
    const TOOLBAR_SELECTOR = '.markdown-textarea[data-markdown-textarea], .markdown-textarea';
    const IMAGE_BTN_SELECTOR = 'button[data-markdown-action="image"]';

    window.addEventListener('load', initEditorEnhancer, false);

    function getEditor() {
        return document.querySelector(EDITOR_SELECTOR);
    }

    function getToolbar() {
        return document.querySelector(TOOLBAR_SELECTOR);
    }

    function initEditorEnhancer() {
        document.addEventListener('paste', handlePasteEvt);

        bindDropZone();
        const dropTimer = setInterval(() => {
            if (bindDropZone()) clearInterval(dropTimer);
        }, 300);
        setTimeout(() => clearInterval(dropTimer), 15000);

        const btnTimer = setInterval(() => {
            const oldBtn = document.querySelector(IMAGE_BTN_SELECTOR);
            if (!oldBtn) return;
            clearInterval(btnTimer);

            const newBtn = oldBtn.cloneNode(true);
            oldBtn.parentNode.replaceChild(newBtn, oldBtn);
            newBtn.addEventListener('click', (event) => {
                event.preventDefault();
                event.stopPropagation();
                handleImgBtnClick();
            });
        }, 200);
        setTimeout(() => clearInterval(btnTimer), 15000);

        if (submitByKey) {
            document.addEventListener('keydown', handleCtrlEnter);
        }
    }

    function handleCtrlEnter(event) {
        if (!(event.ctrlKey && event.key === 'Enter')) return;

        const editor = event.target instanceof Element
            ? event.target.closest(EDITOR_SELECTOR)
            : null;
        if (!editor) return;

        const form = editor.closest('form.ajax-reply-form, form[action="/reply_edit"]');
        if (!form) return;

        const submitButton = form.querySelector('button[type="submit"], input[type="submit"]');
        if (!submitButton || submitButton.disabled) return;

        event.preventDefault();
        event.stopPropagation();
        form.requestSubmit(submitButton);
    }

    function bindDropZone() {
        const dropZone = getEditor();
        if (!dropZone || dropZone.dataset.easyimgDropBound === '1') return Boolean(dropZone);
        dropZone.dataset.easyimgDropBound = '1';

        dropZone.addEventListener('dragover', (event) => {
            event.preventDefault();
            event.stopPropagation();
            event.dataTransfer.dropEffect = 'copy';
        });

        dropZone.addEventListener('drop', (event) => {
            event.preventDefault();
            event.stopPropagation();
            log('正在处理拖放内容...');

            const imageFiles = [];
            for (const file of event.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(imageFiles.map((file) => ({
                kind: 'file',
                type: file.type,
                getAsFile: () => file,
            })));
        });
        return true;
    }

    function handlePasteEvt(event) {
        const clipboard = event.clipboardData || event.originalEvent?.clipboardData;
        const items = clipboard?.items;
        if (!items || items.length === 0) return;

        const hasImage = Array.from(items).some(
            (item) => item.kind === 'file' && item.type.startsWith('image/')
        );
        if (!hasImage) return;

        event.preventDefault();
        log('正在处理粘贴内容...');
        uploadImage(items);
    }

    function handleImgBtnClick() {
        const input = document.createElement('input');
        input.type = 'file';
        input.multiple = true;
        input.accept = 'image/*';
        input.onchange = (event) => {
            const files = event.target.files;
            if (!files?.length) return;
            uploadImage([...files].map((file) => ({
                kind: 'file',
                type: file.type,
                getAsFile: () => file,
            })));
        };
        input.click();
    }

    async function uploadImage(items) {
        const imageFiles = [];
        for (const item of items) {
            if (item.kind === 'file' && item.type.startsWith('image/')) {
                imageFiles.push(item.getAsFile());
            }
        }

        if (imageFiles.length === 0) {
            log('你粘贴的内容好像没有图片哦', 'red');
            return;
        }

        for (let index = 0; index < imageFiles.length; index += 1) {
            log(imageFiles.length > 1
                ? `上传第 ${index + 1} / ${imageFiles.length} 张图片...`
                : '上传图片...');
            await uploadToEasyImages(imageFiles[index]);
        }
    }

    function uploadToEasyImages(file) {
        return new Promise((resolve, reject) => {
            let url = imgHost.url;
            const formData = new FormData();

            if (imgHost.token) {
                url += '/api/index.php';
                formData.append('token', imgHost.token);
                formData.append('image', file);
            } else {
                url += '/app/upload.php';
                formData.append('file', file);
                formData.append('sign', Math.floor(Date.now() / 1000));
            }

            GM_xmlhttpRequest({
                method: 'POST',
                url,
                data: formData,
                onload: (response) => {
                    let data;
                    try {
                        data = JSON.parse(response.responseText);
                    } catch (error) {
                        log('图片上传失败: 响应不是 JSON', 'red');
                        reject(error);
                        return;
                    }

                    if (response.status !== 200) {
                        log(`图片上传失败: ${response.status} ${response.statusText}`, 'red');
                        reject(data.result);
                        return;
                    }

                    if (data.code === 200 && data.url) {
                        log('图片上传成功', 'green');
                        insertToEditor(`![${mdImgName}](${data.url})`);
                    } else if (data.code === 200) {
                        log('图片上传成功, 但接口返回有误', 'red');
                        insertToEditor(`图片上传成功, 但接口返回有误: ${JSON.stringify(data)}`);
                    } else {
                        log(`图片上传失败: ${JSON.stringify(data)}`, 'red');
                    }
                    resolve();
                },
                onerror: (error) => {
                    log(`图片上传失败: ${error.status} ${error.statusText}`, 'red');
                    reject(error);
                },
            });
        });
    }

    function insertToEditor(text) {
        const textarea = getEditor()
            || document.querySelector('#e_textarea')
            || document.querySelector('#fastpostmessage');

        if (!textarea) {
            log('出现错误: 未找到编辑栏', 'red');
            return;
        }

        const start = typeof textarea.selectionStart === 'number'
            ? textarea.selectionStart
            : textarea.value.length;
        const end = typeof textarea.selectionEnd === 'number'
            ? textarea.selectionEnd
            : start;
        const insert = `\n${text}\n`;

        textarea.value = textarea.value.substring(0, start)
            + insert
            + textarea.value.substring(end);
        const position = start + insert.length;
        textarea.selectionStart = textarea.selectionEnd = position;
        textarea.focus();
        textarea.dispatchEvent(new Event('input', { bubbles: true }));
        textarea.dispatchEvent(new Event('change', { bubbles: true }));

        if (text.startsWith('![')) log('图片已插入到编辑器~', 'green');
    }

    function log(message, color = '') {
        if (!document.getElementById('editor-enhance-logs')) initEditorLogDiv();
        const logDiv = document.getElementById('editor-enhance-logs');
        if (logDiv) {
            logDiv.innerHTML = `<div${color ? ` style="color:${color};"` : ''}>&nbsp;&nbsp;&nbsp;${message}&nbsp;</div>`;
        }
        console.log(`[Editor-EasyImages] ${message}`);
    }

    function initEditorLogDiv() {
        let logDiv = document.getElementById('editor-enhance-logs');
        if (!logDiv) {
            logDiv = document.createElement('div');
            logDiv.id = 'editor-enhance-logs';
            document.body.appendChild(logDiv);
        }
        const toolbar = getToolbar();
        if (toolbar && !toolbar.con