- // ==UserScript==
- // @name NGA Likes Support
- // @namespace https://greasyfork.org/users/263018
- // @version 1.4.2
- // @author snyssss
- // @description 显示被点赞和粉丝数量,以及发帖数量、IP属地、曾用名
-
- // @match *://bbs.nga.cn/*
- // @match *://ngabbs.com/*
- // @match *://nga.178.com/*
-
- // @grant GM_registerMenuCommand
- // @grant GM_setValue
- // @grant GM_getValue
- // @grant GM_xmlhttpRequest
- // @noframes
- // ==/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 && cursor.value) {
- if (
- result.length < count &&
- result.findIndex((item) => item.ipLoc === cursor.value.ipLoc) < 0
- ) {
- 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}`, {
- credentials: "omit",
- })
- .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 = `<span class="white"><span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">⯅</span> ${value}</span>`;
-
- 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 = `<span class="white"><span style="font-family: comm_glyphs; -webkit-font-smoothing: antialiased; line-height: 1em;">★</span> ${value}</span>`;
-
- 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 = `
- <span> · 曾用名 ${Object.values(uInfo.oldname)
- .map(
- (item) =>
- `<span class="userval" title="${ui.time2dis(
- item.time
- )}">${item.username}</span>`
- )
- .join(", ")}</span>`;
-
- 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 = `
- <span>曾用名: ${Object.values(uInfo.oldname)
- .map(
- (item) =>
- `<span class="userval" title="${ui.time2dis(
- item.time
- )}">${item.username}</span>`
- )
- .join(", ")}</span>`;
-
- 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 = `
- <span> · 发帖 <span class="${
- uInfo.postnum > 9999 ? "numeric" : "numericl"
- } userval">${uInfo.postnum}</span></span>`;
-
- 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 = `
- <nobr>
- <span>发帖: <span class="${
- uInfo.postnum > 9999 ? "numeric" : "numericl"
- } userval">${uInfo.postnum}</span></span>
- </nobr>`;
-
- anchor.parentNode.insertBefore(element, anchor);
- }
- }
- }
-
- // 显示属地
- if (showIpLocEnable) {
- if (ui._w.__GP.admincheck) {
- return;
- }
-
- const data = await (async () => {
- const uInfo = info[uid].data;
-
- if (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 = `
- <span> · 属地 ${Object.values(data)
- .map(
- (item) =>
- `<span class="userval" title="${
- item.timestamp
- ? ui.time2dis(item.timestamp / 1000)
- : ""
- }">${item.ipLoc}</span>`
- )
- .join(", ")}</span>`;
-
- 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 = `
- <nobr>
- <span>属地: ${Object.values(data)
- .map(
- (item) =>
- `<span class="userval" title="${
- item.timestamp
- ? ui.time2dis(item.timestamp / 1000)
- : ""
- }">${item.ipLoc}</span>`
- )
- .join(", ")}</span>
- </nobr>`;
-
- 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);