// ==UserScript== // @name NGA Likes Support // @namespace https://greasyfork.org/users/263018 // @version 1.4.0 // @author snyssss // @description 显示被点赞和粉丝数量,以及发帖数量、IP属地、曾用名 // @match *://bbs.nga.cn/* // @match *://ngabbs.com/* // @match *://nga.178.com/* // @grant GM_registerMenuCommand // @grant GM_setValue // @grant GM_getValue // @noframes // @downloadURL none // ==/UserScript== (async (ui) => { if (!ui) return; // KEY const SHOW_OLDNAME_ENABLE_KEY = "SHOW_OLDNAME_ENABLE"; const SHOW_POSTNUM_ENABLE_KEY = "SHOW_POSTNUM_ENABLE"; const SHOW_IPLOC_ENABLE_KEY = "SHOW_IPLOC_ENABLE"; // 显示曾用名 const showOldnameEnable = GM_getValue(SHOW_OLDNAME_ENABLE_KEY) || false; // 显示发帖数 const showPostnumEnable = GM_getValue(SHOW_POSTNUM_ENABLE_KEY) || false; // 显示属地 const showIpLocEnable = GM_getValue(SHOW_IPLOC_ENABLE_KEY) || false; // 钩子 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]); }; // IndexedDB 操作 const db = await (async () => { // 常量 const VERSION = 1; const DB_NAME = "NGA_CACHE_IPLOC"; const TABLE_NAME = "ipLoc"; // 是否支持 const support = window.indexedDB !== undefined; // 不支持,直接返回 if (support === false) { return { support, }; } // 获取数据库实例 const instance = await new Promise((resolve) => { // 打开 IndexedDB 数据库 const request = window.indexedDB.open(DB_NAME, VERSION); // 如果数据库不存在则创建 request.onupgradeneeded = (event) => { // 创建表 const store = event.target.result.createObjectStore(TABLE_NAME, { keyPath: null, autoIncrement: true, }); // 创建索引 store.createIndex("uid", "uid"); }; // 成功后返回实例 request.onsuccess = (event) => { resolve(event.target.result); }; }); // 缓存数据 const save = (uid, ipLoc) => new Promise((resolve, reject) => { // 创建事务 const transaction = instance.transaction([TABLE_NAME], "readwrite"); // 获取对象仓库 const store = transaction.objectStore(TABLE_NAME); // 获取索引 const index = store.index("uid"); // 查找最新的数据 const request = index.openCursor(IDBKeyRange.only(uid), "prev"); // 成功后处理数据 request.onsuccess = (event) => { const cursor = event.target.result; // 如果属地没有变化则跳过 if (cursor && cursor.value.ipLoc === ipLoc) { resolve(); return; } // 插入数据 const r = store.put({ uid, ipLoc, timestamp: Date.now(), }); r.onsuccess = () => { resolve(); }; r.onerror = () => { reject(); }; }; // 失败后处理 request.onerror = (event) => { reject(event.target.error); }; }); // 读取数据 const load = (uid, count) => new Promise((resolve, reject) => { // 声明结果 const result = []; // 创建事务 const transaction = instance.transaction([TABLE_NAME], "readwrite"); // 获取对象仓库 const store = transaction.objectStore(TABLE_NAME); // 获取索引 const index = store.index("uid"); // 查找最新的数据 const request = index.openCursor(IDBKeyRange.only(uid), "prev"); // 成功后处理数据 request.onsuccess = (event) => { const cursor = event.target.result; if (cursor && result.length < count) { result.push(cursor.value); cursor.continue(); } else { resolve(result); } }; // 失败后处理 request.onerror = (event) => { reject(event.target.error); }; }); return { support, save, load, }; })(); class UserInfo { execute(task) { task().finally(() => { if (this.waitingQueue.length) { const next = this.waitingQueue.shift(); this.execute(next); } else { this.isRunning = false; } }); } enqueue(task) { if (this.isRunning) { this.waitingQueue.push(task); } else { this.isRunning = true; this.execute(task); } } rearrange() { if (this.data) { const list = Object.values(this.children); for (let i = 0; i < list.length; i++) { if (list[i].source === undefined) { list[i].create(this.data); } Object.entries(this.container).forEach((item) => { list[i].clone(this.data, item); }); } } } reload() { this.enqueue(async () => { this.data = await new Promise((resolve) => { fetch(`/nuke.php?lite=js&__lib=ucp&__act=get&uid=${this.uid}`) .then((res) => res.blob()) .then((blob) => { const reader = new FileReader(); reader.onload = () => { const text = reader.result; const result = JSON.parse( text.replace("window.script_muti_get_var_store=", "") ); resolve(result.data[0]); }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); if (this.data.usernameChanged && showOldnameEnable) { this.data.oldname = await new Promise((resolve) => { fetch(`/nuke.php?lite=js&__lib=ucp&__act=oldname&uid=${this.uid}`) .then((res) => res.blob()) .then((blob) => { const reader = new FileReader(); reader.onload = () => { const text = reader.result; const result = JSON.parse( text.replace("window.script_muti_get_var_store=", "") ); resolve(result.data[0]); }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); } Object.values(this.children).forEach((item) => item.destroy()); this.rearrange(); }); } constructor(id) { this.uid = id; this.waitingQueue = []; this.isRunning = false; this.container = {}; this.children = {}; this.reload(); } } class UserInfoWidget { destroy() { if (this.source) { this.source = undefined; } if (this.target) { Object.values(this.target).forEach((item) => { if (item.parentNode) { item.parentNode.removeChild(item); } }); } } clone(data, [argid, container]) { if (this.source) { if (this.target[argid] === undefined) { this.target[argid] = this.source.cloneNode(true); if (this.callback) { this.callback(data, this.target[argid]); } } const isSmall = container.classList.contains("posterInfoLine"); if (isSmall) { const anchor = container.querySelector(".author ~ br"); if (anchor) { anchor.parentNode.insertBefore(this.target[argid], anchor); } } else { container.appendChild(this.target[argid]); } } } constructor(func, callback) { this.create = (data) => { this.destroy(); this.source = func(data); this.target = {}; }; this.callback = callback; } } ui.sn = ui.sn || {}; ui.sn.userInfo = ui.sn.userInfo || {}; ((info) => { const execute = (argid) => { const args = ui.postArg.data[argid]; if (args.comment) return; const uid = +args.pAid; if (uid > 0) { if (info[uid] === undefined) { info[uid] = new UserInfo(uid); } if (document.contains(info[uid].container[argid]) === false) { info[uid].container[argid] = args.uInfoC.closest("tr").querySelector(".posterInfoLine") || args.uInfoC.querySelector("div"); } info[uid].enqueue(async () => { if (info[uid].children[8] === undefined) { info[uid].children[8] = new UserInfoWidget((data) => { const value = Object.values(data.more_info || {}).find( (item) => item.type === 8 )?.data || 0; const element = document.createElement("SPAN"); element.className = "small_colored_text_btn stxt block_txt_c2 vertmod"; element.style.cursor = "default"; element.innerHTML = ` ${value}`; return element; }); } if (info[uid].children[16] === undefined) { info[uid].children[16] = new UserInfoWidget((data) => { const value = data.follow_by_num || 0; const element = document.createElement("SPAN"); element.className = "small_colored_text_btn stxt block_txt_c2 vertmod"; element.style.cursor = "default"; element.innerHTML = ` ${value}`; return element; }); } info[uid].rearrange(); const container = info[uid].container[argid]; const isSmall = container.classList.contains("posterInfoLine"); // 显示曾用名 if (showOldnameEnable) { if (ui._w.__GP.admincheck) { return; } if (isSmall) { const anchor = [ ...container.querySelectorAll("span.usercol"), ].pop().nextElementSibling; const uInfo = info[uid].data; if (anchor && uInfo && uInfo.oldname) { const element = document.createElement("SPAN"); element.className = "usercol nobr"; element.innerHTML = ` · 曾用名 ${Object.values(uInfo.oldname) .map( (item) => `${item.username}` ) .join(", ")}`; anchor.parentNode.insertBefore(element, anchor); } } else { const anchor = container.parentNode.querySelector( '.stat div[class="clear"]' ).parentNode; const uInfo = info[uid].data; if (anchor && uInfo && uInfo.oldname) { const element = document.createElement("DIV"); element.innerHTML = ` 曾用名: ${Object.values(uInfo.oldname) .map( (item) => `${item.username}` ) .join(", ")}`; anchor.parentNode.appendChild(element, anchor); } } } // 显示发帖数 if (showPostnumEnable) { if (ui._w.__GP.admincheck) { return; } if (isSmall) { const anchor = [ ...container.querySelectorAll("span.usercol"), ].pop().nextElementSibling; const uInfo = ui.userInfo.users[uid]; if (anchor && uInfo) { const element = document.createElement("SPAN"); element.className = "usercol nobr"; element.innerHTML = ` · 发帖 ${uInfo.postnum}`; anchor.parentNode.insertBefore(element, anchor); } } else { const anchor = container.parentNode.querySelector( '.stat div[class="clear"]' ); const uInfo = ui.userInfo.users[uid]; if (anchor && uInfo) { const element = document.createElement("DIV"); element.style = "float:left;margin-right:3px;min-width:49%;*width:49%"; element.innerHTML = ` 发帖: ${uInfo.postnum} `; anchor.parentNode.insertBefore(element, anchor); } } } // 显示属地 if (showIpLocEnable) { if (ui._w.__GP.admincheck) { return; } const data = await (async () => { const uInfo = info[uid].data; if (uInfo) { console.log(uInfo) try { if (db.support) { await db.save(uid, uInfo.ipLoc); return await db.load(uid, 3); } } catch (e) {} return [{ ipLoc: uInfo.ipLoc }]; } return []; })(); if (isSmall) { const anchor = [ ...container.querySelectorAll("span.usercol"), ].pop().nextElementSibling; if (anchor && data.length > 0) { const element = document.createElement("SPAN"); element.className = "usercol nobr"; element.innerHTML = ` · 属地 ${Object.values(data) .map( (item) => `${item.ipLoc}` ) .join(", ")}`; anchor.parentNode.insertBefore(element, anchor); } } else { const anchor = container.parentNode.querySelector( '.stat div[class="clear"]' ); if (anchor && data.length > 0) { const element = document.createElement("DIV"); element.style = "float:left;margin-right:3px;min-width:49%;*width:49%"; element.innerHTML = ` 属地: ${Object.values(data) .map( (item) => `${item.ipLoc}` ) .join(", ")} `; anchor.parentNode.insertBefore(element, anchor); } } } }); } }; const refetch = (arguments) => { const anchor = arguments[0]; const { tid, pid } = arguments[1]; const target = anchor.parentNode.querySelector(".recommendvalue"); if (!target) return; const observer = new MutationObserver(() => { observer.disconnect(); const url = pid ? `/read.php?pid=${pid}` : `/read.php?tid=${tid}`; fetch(url) .then((res) => res.blob()) .then((blob) => { const getLastIndex = (content, position) => { if (position >= 0) { let nextIndex = position + 1; while (nextIndex < content.length) { if (content[nextIndex] === ")") { return nextIndex; } if (content[nextIndex] === "(") { nextIndex = getLastIndex(content, nextIndex); if (nextIndex < 0) { break; } } nextIndex = nextIndex + 1; } } return -1; }; const reader = new FileReader(); reader.onload = async () => { const parser = new DOMParser(); const doc = parser.parseFromString(reader.result, "text/html"); const html = doc.body.innerHTML; const verify = doc.querySelector("#m_posts"); if (verify) { const str = `commonui.postArg.proc( 0`; const index = html.indexOf(str) + str.length; const lastIndex = getLastIndex(html, index); if (lastIndex >= 0) { const matched = html .substring(index, lastIndex) .match(/'\d+,(\d+),(\d+)'/); if (matched) { const score = (matched[1] |= 0); const score_2 = (matched[2] |= 0); const recommend = score - score_2; target.innerHTML = recommend > 0 ? recommend : 0; } } } }; reader.readAsText(blob, "GBK"); }); }); observer.observe(target, { childList: true, }); }; if (ui.postArg) { Object.keys(ui.postArg.data).forEach((i) => execute(i)); } // 绑定事件 (() => { const initialized = { postDisp: false, postScoreAdd: false, }; const hook = () => { if ( Object.values(initialized).findIndex((item) => item === false) < 0 ) { return; } if (ui.postDisp && initialized.postDisp === false) { hookFunction( ui, "postDisp", (returnValue, originalFunction, arguments) => execute(arguments[0]) ); initialized.postDisp = true; } if (ui.postScoreAdd && initialized.postScoreAdd === false) { hookFunction( ui, "postScoreAdd", (returnValue, originalFunction, arguments) => refetch(arguments) ); initialized.postScoreAdd = true; } }; hookFunction(ui, "eval", hook); hook(); })(); })(ui.sn.userInfo); // 菜单项 (() => { // 显示曾用名 if (showOldnameEnable) { GM_registerMenuCommand("显示曾用名:启用", () => { GM_setValue(SHOW_OLDNAME_ENABLE_KEY, false); location.reload(); }); } else { GM_registerMenuCommand("显示曾用名:禁用", () => { GM_setValue(SHOW_OLDNAME_ENABLE_KEY, true); location.reload(); }); } // 显示发帖数 if (showPostnumEnable) { GM_registerMenuCommand("显示发帖数:启用", () => { GM_setValue(SHOW_POSTNUM_ENABLE_KEY, false); location.reload(); }); } else { GM_registerMenuCommand("显示发帖数:禁用", () => { GM_setValue(SHOW_POSTNUM_ENABLE_KEY, true); location.reload(); }); } // 显示属地 if (showIpLocEnable) { GM_registerMenuCommand("显示属地:启用", () => { GM_setValue(SHOW_IPLOC_ENABLE_KEY, false); location.reload(); }); } else { GM_registerMenuCommand("显示属地:禁用", () => { GM_setValue(SHOW_IPLOC_ENABLE_KEY, true); location.reload(); }); } })(); })(commonui);