367ddd 发布于4 小时前 发布于4 小时前 (已修改) · 只看该作者 作者: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行。对于不想打开脚本编辑界面的用户,脚本在未检测到相册链接时,也会通过弹窗提醒输入并保存,为了避免过多的网络请求与分析,并不会每次插入都实时检查相册链接内容,当相册内上传了新图片时请点击刷新并耐心等待 使用示例: 剧透 // ==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同盟脚本组目前正在组建中,后续会有新的脚本和信息放出,敬请期待 4 小时前,由367ddd修改 注释 阿露今日也在歌唱 50.00节操 实际实用! 3 1 1
推荐贴
创建帐号或登入才能点评
您必须成为用户才能点评
创建帐号
在我们社区注册个新的帐号。非常简单!
注册新帐号登入
已有帐号? 登入
现在登入