您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
同步客户端关注功能
当前为
// ==UserScript== // @name NGA Watcher // @namespace https://gf.qytechs.cn/users/263018 // @version 1.1.0 // @author snyssss // @description 同步客户端关注功能 // @match *://bbs.nga.cn/* // @match *://ngabbs.com/* // @match *://nga.178.com/* // @grant GM_addStyle // @grant GM_setValue // @grant GM_getValue // @grant GM_addValueChangeListener // @grant GM_registerMenuCommand // @noframes // ==/UserScript== ((ui, self) => { if (!ui) return; // 钩子 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]); }; // STYLE GM_addStyle(` .s-user-info-container:not(:hover) .ah { display: none !important; } .s-table { border: 1px solid #ead5bc; border-left: none; border-bottom: none; width: 99.95%; } .s-table thead { background-color: #591804; color: #fff8e7; } .s-table tbody tr { background-color: #fff0cd; } .s-table tbody tr:nth-of-type(odd) { background-color: #fff8e7; } .s-table td { border: 1px solid #ead5bc; border-top: none; border-right: none; padding: 6px; white-space: nowrap; } .s-table input:not([type]) { margin: 0; width: 100%; box-sizing: border-box; } `); // 用户信息 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(); }); }); 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]); } } 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 follow = (uid) => new Promise((resolve, reject) => { fetch( `/nuke.php?lite=js&__lib=follow_v2&__act=follow&id=${uid}&type=1`, { method: "post", } ) .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=", "") ); if (result.data) { resolve(result.data[0]); } else { reject(result.error[0]); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); // 取消关注 const un_follow = (uid) => new Promise((resolve, reject) => { fetch( `/nuke.php?lite=js&__lib=follow_v2&__act=follow&id=${uid}&type=8`, { method: "post", } ) .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=", "") ); if (result.data) { resolve(result.data[0]); } else { reject(result.error[0]); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); // 移除粉丝 const un_follow_fans = (uid) => new Promise((resolve, reject) => { fetch( `/nuke.php?lite=js&__lib=follow_v2&__act=follow&id=${uid}&type=256`, { method: "post", } ) .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=", "") ); if (result.data) { resolve(result.data[0]); } else { reject(result.error[0]); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); // 获取关注列表 const follow_list = (page) => new Promise((resolve, reject) => { fetch( `/nuke.php?lite=js&__lib=follow_v2&__act=get_follow&page=${page}`, { method: "post", } ) .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=", "") ); if (result.data) { resolve(result.data[0]); } else { reject(result.error[0]); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); // 获取粉丝列表 const follow_by_list = (page) => new Promise((resolve, reject) => { fetch( `/nuke.php?lite=js&__lib=follow_v2&__act=get_follow_by&page=${page}`, { method: "post", } ) .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=", "") ); if (result.data) { resolve(result.data[0]); } else { reject(result.error[0]); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); // 获取关注动态 const follow_dymanic_list = () => new Promise((resolve, reject) => { fetch(`/nuke.php?lite=js&__lib=follow_v2&__act=get_push_list`, { method: "post", }) .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=", "") ); if (result.data) { resolve(result.data); } else { reject(result.error[0]); } }; reader.readAsText(blob, "GBK"); }) .catch(() => { resolve(); }); }); // UI const u = (() => { const modules = {}; const createView = () => { 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: 40vw;"; 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.visible = true; } else { item.tab.className = "nobr silver"; item.content.style = "display: none"; item.visible = false; } }); module.refresh(); }; tc.append(tabBox); cc.append(module.content); tab.onclick = toggle; modules[module.name] = { ...module, tab, toggle, visible: false, }; return modules[module.name]; }; })(); return { content, modules, addModule, }; }; const refresh = () => { Object.values(modules) .find((item) => item.visible) ?.refresh(); }; return { createView, refresh, }; })(); // 我的关注 { const name = "我的关注"; const content = (() => { const c = document.createElement("div"); c.style.display = "none"; c.innerHTML = ` <div style="max-height: 400px; overflow: auto;"> <table class="s-table"> <tbody></tbody> </table> </div> `; return c; })(); let page = 0; let lastSize = -1; let isFetching = false; const box = content.querySelector("DIV"); const list = content.querySelector("TBODY"); const fetchData = () => { isFetching = true; follow_list(page) .then((res) => { lastSize = Object.keys(res).length; for (let i in res) { const { uid, username } = res[i]; const name = `s-follow-${uid}`; if (list.querySelector(`#${name}`)) { continue; } const item = document.createElement("TR"); item.id = name; item.innerHTML = ` <td> <a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a> </td> <td width="1"> <button>移除</button> </td> `; const action = item.querySelector("BUTTON"); action.onclick = () => { if (confirm("取消关注?")) { un_follow(uid).then(() => { info[uid].reload(); u.refresh(); }); } }; list.appendChild(item); } }) .finally(() => { isFetching = false; }); }; box.onscroll = () => { if (isFetching || lastSize === 0) { return; } if (box.scrollHeight - box.scrollTop - box.clientHeight <= 40) { page = page + 1; fetchData(); } }; const refresh = () => { list.innerHTML = ""; page = 1; lastSize = -1; fetchData(); }; hookFunction(u, "createView", (view) => { view.addModule({ name, content, refresh, }); }); } // 我的粉丝 { const name = "我的粉丝"; const content = (() => { const c = document.createElement("div"); c.style.display = "none"; c.innerHTML = ` <div style="max-height: 400px; overflow: auto;"> <table class="s-table"> <tbody></tbody> </table> </div> `; return c; })(); let page = 0; let lastSize = -1; let isFetching = false; const box = content.querySelector("DIV"); const list = content.querySelector("TBODY"); const fetchData = () => { isFetching = true; follow_by_list(page) .then((res) => { lastSize = Object.keys(res).length; for (let i in res) { const { uid, username } = res[i]; const name = `s-fans-${uid}`; if (list.querySelector(`#${name}`)) { continue; } const item = document.createElement("TR"); item.id = name; item.innerHTML = ` <td> <a href="/nuke.php?func=ucp&uid=${uid}" class="b nobr">[@${username}]</a> </td> <td width="1"> <button>移除</button> </td> `; const action = item.querySelector("BUTTON"); action.onclick = () => { if (confirm("移除粉丝?")) { un_follow_fans(uid).then(() => { u.refresh(); }); } }; list.appendChild(item); } }) .finally(() => { isFetching = false; }); }; box.onscroll = () => { if (isFetching || lastSize === 0) { return; } if (box.scrollHeight - box.scrollTop - box.clientHeight <= 40) { page = page + 1; fetchData(); } }; const refresh = () => { list.innerHTML = ""; page = 1; lastSize = -1; fetchData(); }; hookFunction(u, "createView", (view) => { view.addModule({ name, content, refresh, }); }); } // 关注动态 { const name = "关注动态"; const content = (() => { const c = document.createElement("div"); c.style.display = "none"; c.innerHTML = ` <div style="max-height: 400px; overflow: auto;"> <table class="s-table"> <tbody></tbody> </table> </div> `; return c; })(); let page = 0; let lastSize = -1; let isFetching = false; const box = content.querySelector("DIV"); const list = content.querySelector("TBODY"); const fetchData = () => { isFetching = true; follow_dymanic_list(page) .then((res) => { if (res[1] === res[2]) { lastSize = 0; } else { lastSize = -1; } return res[0]; }) .then((res) => { for (let i in res) { const id = res[i][0]; const time = res[i][6]; const summary = res[i]["summary"]; const name = `s-follow-dymanic-${id}`; if (list.querySelector(`#${name}`)) { continue; } const parsedSummary = summary .replace( /\[uid=(\d+)\](.+)\[\/uid\]/, `<a href="/nuke.php?func=ucp&uid=$1" class="b nobr">$2</a>` ) .replace( /\[pid=(\d+)\](.+)\[\/pid\]/, `<a href="/read.php?pid=$1" class="b nobr">回复</a>` ) .replace(/\[tid=(\d+)\](.+)\[\/tid\]/, function ($0, $1, $2) { let s = ui.cutstrbylen($2, 19); if (s.length < $2.length) { s += "..."; } return `<a href="/read.php?tid=${$1}" class="b nobr">${s}</a>`; }); const item = document.createElement("TR"); item.id = name; item.innerHTML = ` <td width="100"> ${ui.time2dis(time)} </td> <td> ${parsedSummary} </td> `; list.appendChild(item); } }) .finally(() => { isFetching = false; }); }; box.onscroll = () => { if (isFetching || lastSize === 0) { return; } if (box.scrollHeight - box.scrollTop - box.clientHeight <= 40) { page = page + 1; fetchData(); } }; const refresh = () => { list.innerHTML = ""; page = 1; lastSize = -1; fetchData(); }; hookFunction(u, "createView", (view) => { view.addModule({ name, content, refresh, }); }); } // 打开菜单 const showMenu = (() => { let view, window; return () => { if (view === undefined) { view = u.createView(); } view.modules["关注动态"].toggle(); if (window === undefined) { window = ui.createCommmonWindow(); } window._.addContent(null); window._.addTitle(`关注`); window._.addContent(view.content); window._.show(); }; })(); // 增加菜单项 if (document.querySelector(`[name="unisearchinput"]`)) { const anchor = document.querySelector("#mainmenu .td:last-child"); const button = document.createElement("DIV"); button.className = `td`; button.innerHTML = `<a class="mmdefault" href="javascript: void(0);" style="white-space: nowrap;">关注</a>`; button.onclick = showMenu; anchor.before(button); } let popover; 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.querySelector( "[name=uid]" ).parentNode; } info[uid].enqueue(async () => { args.uInfoC.className = args.uInfoC.className + " s-user-info-container"; if (info[uid].children[16]) { info[uid].children[16].destroy(); } info[uid].children[16] = new UserInfoWidget( (data) => { const value = data.follow_by_num || 0; const element = document.createElement("SPAN"); if (uid === self || data.follow) { element.className = "small_colored_text_btn stxt block_txt_c2 vertmod"; } else { element.className = "small_colored_text_btn stxt block_txt_c2 vertmod ah"; } 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>`; element.style.cursor = "pointer"; return element; }, (data, element) => { if (!self) return; const handleClose = () => { if (popover) { popover.style.display = "none"; } }; const handleSwitchFollow = () => { if (data.follow) { if (confirm("取消关注?")) { un_follow(data.uid).then(() => { info[uid].reload(); u.refresh(); }); } } else { follow(data.uid).then(() => { info[uid].reload(); u.refresh(); }); } handleClose(); }; element.onclick = (e) => { if (uid === self) { showMenu(); return; } if (!popover) { popover = document.createElement("SPAN"); popover.className = "urltip2 urltip3 ah"; popover.style = "textAlign: left; margin: 0;"; } if (element.parentNode !== popover.parentNode) { element.parentNode.appendChild(popover); } if (data.follow) { if (popover.type !== 1) { popover.type = 1; popover.innerHTML = `<nobr> <a href="javascript: void(0);">[已关注]</a> <a href="javascript: void(0);">[关闭]</a> </nobr>`; const buttons = popover.getElementsByTagName("A"); buttons[0].onclick = handleSwitchFollow; buttons[1].onclick = handleClose; } } else { if (popover.type !== 2) { popover.type = 2; popover.innerHTML = `<nobr> <a href="javascript: void(0);">[关注]</a> <a href="javascript: void(0);">[关闭]</a> </nobr>`; const buttons = popover.getElementsByTagName("A"); buttons[0].onclick = handleSwitchFollow; buttons[1].onclick = handleClose; } } popover.style.left = `${e.pageX}px`; popover.style.top = `${e.pageY}px`; popover.style.display = "block"; }; } ); info[uid].rearrange(); }); } }; if (ui.postArg) { Object.keys(ui.postArg.data).forEach((i) => execute(i)); } let initialized = false; hookFunction(ui, "eval", () => { if (initialized) return; if (ui.postDisp) { hookFunction( ui, "postDisp", (returnValue, originalFunction, arguments) => execute(arguments[0]) ); initialized = true; } }); })(ui.sn.userInfo); })(commonui, __CURRENT_UID);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址