// ==UserScript== // @name 大别野[米游社频道]右侧成员栏显示频道用户数,标签位置显示频道创建时间 // @namespace http://tampermonkey.net/ // @version 0.3.7 // @description 大别野[米游社频道]右侧成员栏显示频道用户数,标签位置显示频道创建时间。自用 // @author aspen138 // @match https://dby.miyoushe.com/* // @match https://dby.miyoushe.com/chat/* // @icon https://dby.miyoushe.com/favicon.png // @grant GM_addStyle // @run-at document-start // @license MIT // @downloadURL none // ==/UserScript== var ajaxHooker = function() { 'use strict'; const win = window.unsafeWindow || document.defaultView || window; const toString = Object.prototype.toString; const getDescriptor = Object.getOwnPropertyDescriptor; const hookFns = []; const realXhr = win.XMLHttpRequest; const realFetch = win.fetch; const resProto = win.Response.prototype; const xhrResponses = ['response', 'responseText', 'responseXML']; const fetchResponses = ['arrayBuffer', 'blob', 'formData', 'json', 'text']; const fetchInitProps = ['method', 'headers', 'body', 'mode', 'credentials', 'cache', 'redirect', 'referrer', 'referrerPolicy', 'integrity', 'keepalive', 'signal', 'priority' ]; const xhrAsyncEvents = ['readystatechange', 'load', 'loadend']; let filter; function emptyFn() {} function errorFn(err) { console.error(err); } function defineProp(obj, prop, getter, setter) { Object.defineProperty(obj, prop, { configurable: true, enumerable: true, get: getter, set: setter }); } function readonly(obj, prop, value = obj[prop]) { defineProp(obj, prop, () => value, emptyFn); } function writable(obj, prop, value = obj[prop]) { Object.defineProperty(obj, prop, { configurable: true, enumerable: true, writable: true, value: value }); } function shouldFilter(type, url, method, async) { return filter && !filter.find(obj => { switch (true) { case obj.type && obj.type !== type: case toString.call(obj.url) === '[object String]' && !url.includes(obj.url): case toString.call(obj.url) === '[object RegExp]' && !obj.url.test(url): case obj.method && obj.method.toUpperCase() !== method.toUpperCase(): case 'async' in obj && obj.async !== async : return false; } return true; }); } function parseHeaders(obj) { const headers = {}; switch (toString.call(obj)) { case '[object String]': for (const line of obj.trim().split(/[\r\n]+/)) { const parts = line.split(/\s*:\s*/); if (parts.length !== 2) continue; const lheader = parts[0].toLowerCase(); if (lheader in headers) { headers[lheader] += ', ' + parts[1]; } else { headers[lheader] = parts[1]; } } return headers; case '[object Headers]': for (const [key, val] of obj) { headers[key] = val; } return headers; case '[object Object]': return { ...obj }; default: return headers; } } class AHRequest { constructor(request) { this.request = request; this.requestClone = { ...this.request }; this.response = {}; } waitForHookFns() { return Promise.all(hookFns.map(fn => { try { return Promise.resolve(fn(this.request)).then(emptyFn, errorFn); } catch (err) { console.error(err); } })); } waitForResponseFn() { try { return Promise.resolve(this.request.response(this.response)).then(emptyFn, errorFn); } catch (err) { console.error(err); return Promise.resolve(); } } waitForRequestKeys() { if (this.reqPromise) return this.reqPromise; const requestKeys = ['url', 'method', 'abort', 'headers', 'data']; return this.reqPromise = this.waitForHookFns().then(() => Promise.all( requestKeys.map(key => Promise.resolve(this.request[key]).then( val => this.request[key] = val, e => this.request[key] = this.requestClone[key] )) )); } waitForResponseKeys() { if (this.resPromise) return this.resPromise; const responseKeys = this.request.type === 'xhr' ? xhrResponses : fetchResponses; return this.resPromise = this.waitForResponseFn().then(() => Promise.all( responseKeys.map(key => { const descriptor = getDescriptor(this.response, key); if (descriptor && 'value' in descriptor) { return Promise.resolve(descriptor.value).then( val => this.response[key] = val, e => delete this.response[key] ); } else { delete this.response[key]; } }) )); } } class XhrEvents { constructor() { this.events = {}; } add(type, event) { if (type.startsWith('on')) { this.events[type] = typeof event === 'function' ? event : null; } else { this.events[type] = this.events[type] || new Set(); this.events[type].add(event); } } remove(type, event) { if (type.startsWith('on')) { this.events[type] = null; } else { this.events[type] && this.events[type].delete(event); } } _sIP() { this.ajaxHooker_isStopped = true; } trigger(e) { if (e.ajaxHooker_isTriggered || e.ajaxHooker_isStopped) return; e.stopImmediatePropagation = this._sIP; this.events[e.type] && this.events[e.type].forEach(fn => { !e.ajaxHooker_isStopped && fn.call(e.target, e); }); this.events['on' + e.type] && this.events['on' + e.type].call(e.target, e); e.ajaxHooker_isTriggered = true; } clone() { const eventsClone = new XhrEvents(); for (const type in this.events) { if (type.startsWith('on')) { eventsClone.events[type] = this.events[type]; } else { eventsClone.events[type] = new Set([...this.events[type]]); } } return eventsClone; } } const xhrMethods = { readyStateChange(e) { if (e.target.readyState === 4) { e.target.dispatchEvent(new CustomEvent('ajaxHooker_responseReady', { detail: e })); } else { e.target.__ajaxHooker.eventTrigger(e); } }, asyncListener(e) { e.target.__ajaxHooker.eventTrigger(e); }, setRequestHeader(header, value) { const ah = this.__ajaxHooker; ah.originalXhr.setRequestHeader(header, value); if (this.readyState !== 1) return; if (header in ah.headers) { ah.headers[header] += ', ' + value; } else { ah.headers[header] = value; } }, addEventListener(...args) { const ah = this.__ajaxHooker; if (xhrAsyncEvents.includes(args[0])) { ah.proxyEvents.add(args[0], args[1]); } else { ah.originalXhr.addEventListener(...args); } }, removeEventListener(...args) { const ah = this.__ajaxHooker; if (xhrAsyncEvents.includes(args[0])) { ah.proxyEvents.remove(args[0], args[1]); } else { ah.originalXhr.removeEventListener(...args); } }, open(method, url, async = true, ...args) { const ah = this.__ajaxHooker; ah.url = url.toString(); ah.method = method.toUpperCase(); ah.async = !!async; ah.openArgs = args; ah.headers = {}; for (const key of xhrResponses) { ah.proxyProps[key] = { get: () => { const val = ah.originalXhr[key]; ah.originalXhr.dispatchEvent(new CustomEvent('ajaxHooker_readResponse', { detail: { key, val } })); return val; } }; } return ah.originalXhr.open(method, url, ...args); }, sendFactory(realSend) { return function(data) { const ah = this.__ajaxHooker; const xhr = ah.originalXhr; if (xhr.readyState !== 1) return realSend.call(xhr, data); ah.eventTrigger = e => ah.proxyEvents.trigger(e); if (shouldFilter('xhr', ah.url, ah.method, ah.async)) { xhr.addEventListener('ajaxHooker_responseReady', e => { ah.eventTrigger(e.detail); }, { once: true }); return realSend.call(xhr, data); } const request = { type: 'xhr', url: ah.url, method: ah.method, abort: false, headers: ah.headers, data: data, response: null, async: ah.async }; if (!ah.async) { const requestClone = { ...request }; hookFns.forEach(fn => { try { toString.call(fn) === '[object Function]' && fn(request); } catch (err) { console.error(err); } }); for (const key in request) { if (toString.call(request[key]) === '[object Promise]') { request[key] = requestClone[key]; } } xhr.open(request.method, request.url, ah.async, ...ah.openArgs); for (const header in request.headers) { xhr.setRequestHeader(header, request.headers[header]); } data = request.data; xhr.addEventListener('ajaxHooker_responseReady', e => { ah.eventTrigger(e.detail); }, { once: true }); realSend.call(xhr, data); if (toString.call(request.response) === '[object Function]') { const response = { finalUrl: xhr.responseURL, status: xhr.status, responseHeaders: parseHeaders(xhr.getAllResponseHeaders()) }; for (const key of xhrResponses) { defineProp(response, key, () => { return response[key] = ah.originalXhr[key]; }, val => { if (toString.call(val) !== '[object Promise]') { delete response[key]; response[key] = val; } }); } try { request.response(response); } catch (err) { console.error(err); } for (const key of xhrResponses) { ah.proxyProps[key] = { get: () => response[key] }; }; } return; } const req = new AHRequest(request); req.waitForRequestKeys().then(() => { if (request.abort) return; xhr.open(request.method, request.url, ...ah.openArgs); for (const header in request.headers) { xhr.setRequestHeader(header, request.headers[header]); } data = request.data; xhr.addEventListener('ajaxHooker_responseReady', e => { if (typeof request.response !== 'function') return ah.eventTrigger(e.detail); req.response = { finalUrl: xhr.responseURL, status: xhr.status, responseHeaders: parseHeaders(xhr.getAllResponseHeaders()) }; for (const key of xhrResponses) { defineProp(req.response, key, () => { return req.response[key] = ah.originalXhr[key]; }, val => { delete req.response[key]; req.response[key] = val; }); } const resPromise = req.waitForResponseKeys().then(() => { for (const key of xhrResponses) { if (!(key in req.response)) continue; ah.proxyProps[key] = { get: () => { const val = req.response[key]; xhr.dispatchEvent(new CustomEvent('ajaxHooker_readResponse', { detail: { key, val } })); return val; } }; } }); xhr.addEventListener('ajaxHooker_readResponse', e => { const descriptor = getDescriptor(req.response, e.detail.key); if (!descriptor || 'get' in descriptor) { req.response[e.detail.key] = e.detail.val; } }); const eventsClone = ah.proxyEvents.clone(); ah.eventTrigger = event => resPromise.then(() => eventsClone.trigger(event)); ah.eventTrigger(e.detail); }, { once: true }); realSend.call(xhr, data); }); }; } }; function fakeXhr() { const xhr = new realXhr(); let ah = xhr.__ajaxHooker; let xhrProxy = xhr; if (!ah) { const proxyEvents = new XhrEvents(); ah = xhr.__ajaxHooker = { headers: {}, originalXhr: xhr, proxyProps: {}, proxyEvents: proxyEvents, eventTrigger: e => proxyEvents.trigger(e), toJSON: emptyFn // Converting circular structure to JSON }; xhrProxy = new Proxy(xhr, { get(target, prop) { try { if (target === xhr) { if (prop in ah.proxyProps) { const descriptor = ah.proxyProps[prop]; return descriptor.get ? descriptor.get() : descriptor.value; } if (typeof xhr[prop] === 'function') return xhr[prop].bind(xhr); } } catch (err) { console.error(err); } return target[prop]; }, set(target, prop, value) { try { if (target === xhr && prop in ah.proxyProps) { const descriptor = ah.proxyProps[prop]; descriptor.set ? descriptor.set(value) : (descriptor.value = value); } else { target[prop] = value; } } catch (err) { console.error(err); } return true; } }); xhr.addEventListener('readystatechange', xhrMethods.readyStateChange); xhr.addEventListener('load', xhrMethods.asyncListener); xhr.addEventListener('loadend', xhrMethods.asyncListener); for (const evt of xhrAsyncEvents) { const onEvt = 'on' + evt; ah.proxyProps[onEvt] = { get: () => proxyEvents.events[onEvt] || null, set: val => proxyEvents.add(onEvt, val) }; } for (const method of ['setRequestHeader', 'addEventListener', 'removeEventListener', 'open']) { ah.proxyProps[method] = { value: xhrMethods[method] }; } } ah.proxyProps.send = { value: xhrMethods.sendFactory(xhr.send) }; return xhrProxy; } function hookFetchResponse(response, req) { for (const key of fetchResponses) { response[key] = () => new Promise((resolve, reject) => { if (key in req.response) return resolve(req.response[key]); resProto[key].call(response).then(res => { req.response[key] = res; req.waitForResponseKeys().then(() => { resolve(key in req.response ? req.response[key] : res); }); }, reject); }); } } function fakeFetch(url, options = {}) { if (!url) return realFetch.call(win, url, options); let init = { ...options }; if (toString.call(url) === '[object Request]') { init = {}; for (const prop of fetchInitProps) init[prop] = url[prop]; Object.assign(init, options); url = url.url; } url = url.toString(); init.method = init.method || 'GET'; init.headers = init.headers || {}; if (shouldFilter('fetch', url, init.method, true)) return realFetch.call(win, url, init); const request = { type: 'fetch', url: url, method: init.method.toUpperCase(), abort: false, headers: parseHeaders(init.headers), data: init.body, response: null, async: true }; const req = new AHRequest(request); return new Promise((resolve, reject) => { req.waitForRequestKeys().then(() => { if (request.abort) return reject(new DOMException('aborted', 'AbortError')); init.method = request.method; init.headers = request.headers; init.body = request.data; realFetch.call(win, request.url, init).then(response => { if (typeof request.response === 'function') { req.response = { finalUrl: response.url, status: response.status, responseHeaders: parseHeaders(response.headers) }; hookFetchResponse(response, req); response.clone = () => { const resClone = resProto.clone.call(response); hookFetchResponse(resClone, req); return resClone; }; } resolve(response); }, reject); }).catch(err => { console.error(err); resolve(realFetch.call(win, url, init)); }); }); } win.XMLHttpRequest = fakeXhr; Object.keys(realXhr).forEach(key => fakeXhr[key] = realXhr[key]); fakeXhr.prototype = realXhr.prototype; win.fetch = fakeFetch; return { hook: fn => hookFns.push(fn), filter: arr => { filter = Array.isArray(arr) && arr; }, protect: () => { readonly(win, 'XMLHttpRequest', fakeXhr); readonly(win, 'fetch', fakeFetch); }, unhook: () => { writable(win, 'XMLHttpRequest', realXhr); writable(win, 'fetch', realFetch); } }; }(); // 定义一个全局对象,用来包装memberNum var globals = {}; // Define the updateMemberText function const updateMemberText = () => { console.log("触发updateMemberText"); // Use setInterval to periodically check for the elements const intervalId = setInterval(() => { var elements = document.querySelectorAll('#__nuxt > div > div > div:nth-child(3) > div > h2'); console.log(elements); // Only proceed if elements are found if (elements.length > 0) { elements.forEach(targetElement => { if (targetElement && targetElement.textContent.includes('')) { targetElement.childNodes.forEach((child) => { if (child.nodeType === Node.TEXT_NODE && child.textContent.includes('')) { console.log("这个应该后出现 频道人数函数里", globals.memberNum); child.textContent = globals.memberNum + '名频道成员'; } }); } }); // Clear the interval once updates are made clearInterval(intervalId); } }, 100); // Check every 100ms or any other reasonable time interval }; //"退出大别野"文字改为更容易理解的"退出此频道" (function (){ // Function to observe DOM changes const observeDOM = () => { // Options for the observer (which mutations to observe) const config = { childList: true, subtree: true }; // Callback function to execute when mutations are observed const callback = function(mutationsList, observer) { for (let mutation of mutationsList) { if (mutation.type === 'childList') { changeTextContent(); } } }; // Create an observer instance linked to the callback function const observer = new MutationObserver(callback); // Start observing the target node for configured mutations observer.observe(document.body, config); }; // Function to change the text content const changeTextContent = () => { // Select the element based on its inner text let elements = Array.from(document.querySelectorAll('span')); let targetElement = elements.find(el => el.textContent.includes('退出大别野')); // If the element is found, change its text content if (targetElement) { targetElement.textContent = '退出此频道'; } }; // Wait for the DOM to be fully loaded document.addEventListener('DOMContentLoaded', observeDOM); })(); (function() { 'use strict'; function timestampToTime(timestamp) { const milliseconds = timestamp * 1000; const date = new Date(milliseconds); const year = date.getFullYear(); const month = addZero(date.getMonth() + 1); const day = addZero(date.getDate()); const hour = addZero(date.getHours()); const minute = addZero(date.getMinutes()); const second = addZero(date.getSeconds()); return `${ year }-${ month }-${ day } ${ hour }:${ minute }:${ second }`; } function addZero(num) { return num < 10 ? `0${ num }` : `${ num }`; } ajaxHooker.hook(request => { if (request.url.includes("https://bbs-api.miyoushe.com/vila/wapi/villa/v2/getVillaFull") // || request.url.includes("https://bbs-api.miyoushe.com/vila/wapi/home/list") ) { //console.log("成功劫持") //console.log(request) request.response = res => { //console.log('\n== ↓ ↓ ↓ ↓ ↓ == \n', res) var jsonString = res.responseText try { var json = JSON.parse(jsonString); globals.memberNum = json.data.villa_full_info.villa_info.member_num; var createdAt = timestampToTime(Number(json.data.villa_full_info.villa_info.villa_created_at)); console.log("这个应该先出现 频道人数函数里",globals.memberNum) //json.data.villa_full_info.villa_info.tags.push('人数:' + globals.memberNum); setTimeout(updateMemberText(),10*1000); json.data.villa_full_info.villa_info.tags.push('创建时间:' + createdAt); res.responseText = JSON.stringify(json); } catch (e) { console.error("Parsing error:", e); } // GM_addStyle(` // .memberNum { // position: absolute; // left: 0; // bottom: -25px; // color: #999; // } // `); } } }); // Your code here... })();