您需要先安装一个扩展,例如 篡改猴、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或关注我们的公众号极客氢云获取最新地址