您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
哔哩哔哩网页版评论区显示用户 IP 归属地
当前为
// ==UserScript== // @name Bilibili Comment User Location // @namespace Hill98 // @description 哔哩哔哩网页版评论区显示用户 IP 归属地 // @version 1.1.0 // @author Hill-98 // @license MIT // @icon https://www.bilibili.com/favicon.ico // @homepageURL https://github.com/Hill-98/userscripts // @supportURL https://github.com/Hill-98/userscripts/issues // @grant none // @match https://www.bilibili.com/* // @run-at document-start // ==/UserScript== const LOG_PREFIX = '[bcul]: '; const console = Object.create(Object.getPrototypeOf(window.console), Object.getOwnPropertyDescriptors(window.console)); const addLocationToReply = function addLocationToReply(rootid, rpid, userid, location) { const el = document.createElement('span'); el.classList.add('reply-location') el.textContent = location; if (window.location.pathname.startsWith('/bangumi/play/')) { // 影视页面 const container = document.querySelector(`.reply-wrap[data-id="${rpid}"]`); const info = container.querySelector('.info'); info.append(el); } else { // 视频页面 const id = rootid === 0 ? rpid : rootid; const containers = document.querySelectorAll(`[data-root-reply-id="${id}"][data-user-id="${userid}"]`); for (let i = 0; i < containers.length; i++) { const container = containers[i]; let parentElement = container.parentElement; const isSub = parentElement.classList.toString().includes('sub-'); if (isSub) { parentElement = parentElement.parentElement; } const info = parentElement.querySelector(isSub ? '.sub-reply-info' : '.reply-info'); if (info && !info.querySelector('.reply-location')) { el.style.marginLeft = '18px'; info.append(el); break; } } } } const handleReplies = function handleReplies(replies) { replies.forEach((reply) => { const control = reply.reply_control; if (control?.location) { try { addLocationToReply(reply.root, reply.rpid, reply.mid, control.location); } catch (ex) { console.error(LOG_PREFIX, ex); } } if (reply.replies) { handleReplies(reply.replies); } }); }; const handleResponse = function handleResponse(url, response) { if (!url.startsWith('https://api.bilibili.com/x/v2/reply')) { return; } try { const json = JSON.parse(response); if (json.code === 0 && json.data?.replies) { setTimeout(handleReplies.bind(this, json.data.replies), 1000); } } catch (ex) { console.error(LOG_PREFIX, ex); } }; const hackResponse = function hackResponse() { if (this.readyState === XMLHttpRequest.DONE && this.status === 200) { handleResponse(this.responseURL, this.response); } }; window.XMLHttpRequest = class XMLHttpRequestHacker extends window.XMLHttpRequest { constructor() { super(); this.addEventListener('readystatechange', hackResponse.bind(this)); } }; const jsonpObserver = new MutationObserver((mutationList) => { mutationList.forEach((mutation) => { mutation.addedNodes.forEach((node) => { if (node.nodeName.toLowerCase() !== 'script') { return; } const u = new URL(node.src); if (u.href.startsWith('https://api.bilibili.com/x/v2/reply')) { const callbackName = u.searchParams.get('callback'); const callback = window[callbackName]; window[callbackName] = function (data) { handleResponse(u.href, JSON.stringify(data)); callback(data); }; } }); }); }); document.addEventListener('DOMContentLoaded', () => { jsonpObserver.observe(document.head, { childList: true, }); });