您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
Greasy Fork is available in English.
NGA表情管理器
当前为
// ==UserScript== // @name NGA Smiles Manager // @namespace http://greasyfork.icu/users/263018 // @version 1.0.0 // @author snyssss // @description NGA表情管理器 // @match *://bbs.nga.cn/* // @match *://ngabbs.com/* // @match *://nga.178.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_deleteValue // @grant GM_addValueChangeListener // @noframes // ==/UserScript== ((ui, poster) => { if (!ui) return; if (!poster) return; // 数据操作 const manager = (() => { const KEY = `NGA_SMILES_MANAGER`; const data = {}; const fetchData = (pid) => new Promise((resolve, reject) => { const api = `/read.php?pid=${pid}`; fetch(api) .then((res) => res.blob()) .then((blob) => { const reader = new FileReader(); reader.onload = async () => { const parser = new DOMParser(); const doc = parser.parseFromString(reader.result, "text/html"); const verify = doc.querySelector("#m_posts"); if (verify) { const subject = doc.querySelector("#postsubject0").innerHTML; const content = doc.querySelector("#postcontent0").innerHTML; const items = content.match(/(?<=\[img\])(.+?)(?=\[\/img\])/g); if (items.length) { resolve({ name: subject, items, }); } else { reject("图楼内容有误"); } } else { reject(doc.title); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { reject("服务器异常"); }); }); const assign = (next) => { Object.getOwnPropertyNames(data).forEach((property) => { delete data[property]; }); Object.getOwnPropertyNames(next).forEach((property) => { data[property] = next[property]; }); }; const save = () => { GM_setValue(KEY, data); }; const load = () => { assign( GM_getValue(KEY) || { [0]: { syncInterval: 3600, }, } ); }; const settings = () => { if (Object.keys(data).length < 1) { load(); } return data[0]; }; const updateSettings = (values) => { const entity = settings(); Object.getOwnPropertyNames(values).forEach((property) => { entity[property] = values[property]; }); save(); }; const list = () => { if (Object.keys(data).length < 1) { load(); } return Object.keys(data) .filter((key) => key > 0) .reduce((root, key) => { return [...root, data[key]]; }, []); }; const get = (pid) => { return list().find((item) => item.pid === pid); }; const set = (pid, values) => { const entity = get(pid); if (entity) { Object.getOwnPropertyNames(values).forEach((property) => { entity[property] = values[property]; }); } else { const index = Math.max(...Object.keys(data), 0) + 1; data[index] = { pid, name: `#${pid}`, error: "", enabled: true, syncDate: null, ...values, }; } save(); }; const sync = async (pid) => { const { syncInterval } = settings(); const syncDate = new Date().getTime(); const entity = get(pid); if ( syncInterval > 0 && entity && entity.syncDate + syncInterval * 1000 > syncDate ) { return false; } try { const { name, items } = await fetchData(pid); set(pid, { name: name || `#${pid}`, error: "", syncDate, }); GM_setValue(pid, items); } catch (error) { set(pid, { error, syncDate, }); return false; } return true; }; const add = async (url) => { const params = new URLSearchParams(url.substring(url.indexOf("?"))); const pid = params.get("pid"); if (pid === null) { alert("图楼地址有误"); return false; } return await sync(pid); }; const remove = (pid) => { GM_deleteValue(pid); Object.keys(data).forEach((key) => { if (data[key].pid === pid) { delete data[key]; } }); save(); }; GM_addValueChangeListener(KEY, function (_, prev, next) { assign(next); }); return { add, set, sync, remove, list, settings, updateSettings, }; })(); // STYLE GM_addStyle(` .s-user-info-container:not(:hover) .ah { display: none !important; } .s-table-wrapper { height: calc((2em + 10px) * 11 + 3px); overflow-y: auto; } .s-table { margin: 0; } .s-table th, .s-table td { position: relative; white-space: nowrap; } .s-table th { position: sticky; top: 2px; z-index: 1; } .s-table input:not([type]), .s-table input[type="text"] { margin: 0; box-sizing: border-box; height: 100%; width: 100%; } .s-input-wrapper { position: absolute; top: 6px; right: 6px; bottom: 6px; left: 6px; } .s-text-ellipsis { display: flex; } .s-text-ellipsis > * { flex: 1; width: 1px; overflow: hidden; text-overflow: ellipsis; } .s-button-group { margin: -.1em -.2em; } `); // UI const u = (() => { const modules = {}; const tabContainer = (() => { const c = document.createElement("div"); c.className = "w100"; c.innerHTML = ` <div class="right_" style="margin-bottom: 5px;"> <table class="stdbtn" cellspacing="0"> <tbody> <tr></tr> </tbody> </table> </div> <div class="clear"></div> `; return c; })(); const tabPanelContainer = (() => { const c = document.createElement("div"); c.style = "width: 80vw;"; return c; })(); const content = (() => { const c = document.createElement("div"); c.append(tabContainer); c.append(tabPanelContainer); return c; })(); const addModule = (() => { const tc = tabContainer.getElementsByTagName("tr")[0]; const cc = tabPanelContainer; return (module) => { const tabBox = document.createElement("td"); tabBox.innerHTML = `<a href="javascript:void(0)" class="nobr silver">${module.name}</a>`; const tab = tabBox.childNodes[0]; const toggle = () => { Object.values(modules).forEach((item) => { if (item.tab === tab) { item.tab.className = "nobr"; item.content.style = "display: block"; item.refresh(); } else { item.tab.className = "nobr silver"; item.content.style = "display: none"; } }); }; tc.append(tabBox); cc.append(module.content); tab.onclick = toggle; modules[module.name] = { ...module, tab, toggle, }; return modules[module.name]; }; })(); return { content, modules, addModule, }; })(); // 列表 (() => { const content = (() => { const c = document.createElement("div"); c.style = "display: none"; c.innerHTML = ` <div class="s-table-wrapper"> <table class="s-table forumbox"> <thead> <tr class="block_txt_c0"> <th class="c1" width="1">标题</th> <th class="c2">异常信息</th> <th class="c3" width="1">同步时间</th> <th class="c4" width="1">是否启用</th> <th class="c5" width="1">操作</th> </tr> </thead> <tbody></tbody> </table> </div> `; return c; })(); const refresh = (() => { const container = content.getElementsByTagName("tbody")[0]; const func = () => { container.innerHTML = ""; Object.values(manager.list()).forEach((item) => { const { pid, name, error, enabled, syncDate } = item; const tc = document.createElement("tr"); tc.className = `row${ (container.querySelectorAll("TR").length % 2) + 1 }`; tc.innerHTML = ` <td class="c1"> <a href="/read.php?pid=${pid}" class="b nobr">${name}</a> </td> <td class="c2"> <span class="nobr">${error}</span> </td> <td class="c3"> <span class="nobr">${new Date(syncDate).toLocaleString()}</span> </td> <td class="c4"> <div style="text-align: center;"> <input type="checkbox" ${ enabled ? `checked="checked"` : "" } /> </div> </td> <td class="c5"> <div class="s-button-group"> <button>同步</button> <button>删除</button> </div> </td> `; const enabledElement = tc.querySelector(`INPUT[type="checkbox"]`); const save = () => { manager.set(pid, { enabled: enabledElement.checked ? 1 : 0, }); }; enabledElement.onchange = save; const actions = tc.getElementsByTagName("button"); actions[0].onclick = async () => { await manager.sync(pid); refresh(); }; actions[1].onclick = () => { if (confirm("是否确认?")) { manager.remove(pid); refresh(); } }; container.appendChild(tc); }); { const tc = document.createElement("tr"); tc.className = `row${ (container.querySelectorAll("TR").length % 2) + 1 }`; tc.innerHTML = ` <td class="c1" colspan="4"> <div class="s-input-wrapper"> <input value="" /> </div> </td> <td class="c5"> <div class="s-button-group"> <button>添加</button> </div> </td> `; const inputElement = tc.querySelector("INPUT"); const actions = tc.getElementsByTagName("button"); actions[0].onclick = async () => { if (inputElement.value) { if (await manager.add(inputElement.value)) { refresh(); } } }; container.appendChild(tc); } }; return func; })(); u.addModule({ name: "列表", content, refresh, }); })(); // 通用设置 (() => { const content = (() => { const c = document.createElement("div"); c.style = "display: none"; return c; })(); const refresh = (() => { const container = content; const func = () => { container.innerHTML = ""; const { syncInterval } = manager.settings(); // 自动同步 { const tc = document.createElement("div"); tc.innerHTML += ` <div>自动同步设置</div> <div></div> `; [ { label: "1小时", value: 3600, }, { label: "1天", value: 3600 * 24, }, { label: "从不", value: 0, }, ].forEach(({ label, value }, index) => { const ele = document.createElement("SPAN"); ele.innerHTML += ` <input id="s-st-${index}" type="radio" name="syncInterval" ${ syncInterval === value && "checked" }> <label for="s-st-${index}" style="cursor: pointer;">${label}</label> `; const items = ele.querySelector("input"); items.onchange = () => { if (items.checked) { manager.updateSettings({ syncInterval: value, }); } }; tc.querySelectorAll("div")[1].append(ele); }); container.appendChild(tc); } }; return func; })(); u.addModule({ name: "设置", content, refresh, }); })(); // 增加菜单项 (() => { const title = "表情管理"; let window; ui.mainMenu.addItemOnTheFly(title, null, () => { if (window === undefined) { window = ui.createCommmonWindow(); } u.modules["列表"].toggle(); window._.addContent(null); window._.addTitle(title); window._.addContent(u.content); window._.show(); }); })(); // 加载表情 const loadSmiles = (loaded) => { if (loaded) return; const { correctAttachUrl } = ui; const tabs = poster.selectSmilesw._.__c.firstElementChild; const contents = poster.selectSmilesw._.__c.lastElementChild; manager.list().forEach((item) => { const { pid, name, enabled } = item; if (enabled) { const tab = document.createElement("BUTTON"); const content = document.createElement("DIV"); tab.className = "block_txt_big"; tab.innerText = name; tab.onclick = async () => { contents.childNodes.forEach((c) => { if (c !== content) { c.style.display = "none"; } else { c.style.display = ""; } }); if (content.childNodes.length === 0) { await manager.sync(pid); const list = GM_getValue(pid) || []; list.forEach((s) => { const smile = document.createElement("IMG"); smile.src = s.indexOf("http") < 0 ? correctAttachUrl(s) : s; smile.style = "max-height: 200px"; smile.onclick = () => { poster.selectSmilesw._.hide(); poster.addText(`[img]${s}[/img]`); }; content.appendChild(smile); }); } }; tabs.appendChild(tab); contents.appendChild(content); } }); }; // 加载脚本 (() => { const hookFunction = (object, functionName, callback) => { ((originalFunction) => { object[functionName] = function () { const returnValue = originalFunction.apply(this, arguments); callback.apply(this, [returnValue, originalFunction, arguments]); return returnValue; }; })(object[functionName]); }; hookFunction(poster, "selectSmiles", (returnValue) => loadSmiles(returnValue) ); })(); })(commonui, postfunc);