bilibili 成分查询

bilibili 共同关注一键查询(本地查询版)

当前为 2025-10-10 提交的版本,查看 最新版本

// ==UserScript==
// @name         bilibili 成分查询
// @namespace    https://github.com/sparanoid/userscript
// @supportURL   https://github.com/sparanoid/userscript/issues
// @version      0.1.14
// @description  bilibili 共同关注一键查询(本地查询版)
// @author       Sparanoid
// @license      AGPL
// @compatible   chrome 80 or later
// @compatible   edge 80 or later
// @compatible   firefox 74 or later
// @compatible   safari 13.1 or later
// @match        https://*.bilibili.com/*
// @icon         https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico
// @grant        none
// @run-at       document-start
// ==/UserScript==

// Debugging pages:
// - https://t.bilibili.com/594017148390748345
// - https://www.bilibili.com/read/cv13871002
// - https://space.bilibili.com/703007996/fans/follow
// - https://www.bilibili.com/video/BV1Ar4y1C77P
// - https://www.bilibili.com/video/BV1KL411g7om (colab)

window.addEventListener(
	"load",
	() => {
		const DEBUG = true;
		const NAMESPACE = "bilibili-social-check";
		const apiBase = "https://api.bilibili.com";
		const feedbackUrl = "https://t.bilibili.com/545085157213602473";
		const conclusion = [
			"🎤谁啊,真不熟", // 0
			"纯路人了属于是", // 1
			"有点共同爱好了", // 2
			"共同兴趣还不少", // 3
			"共同兴趣还挺多", // 4
			"怎么会事呢", // 5
			"很难不是好兄弟", // 6
			"一家人了属于是", // 7
			"很难不狂暴鸿儒", // 8
			"我擦我不好说", //9
			"克隆人是吧?", // 10
		];

		console.log(`${NAMESPACE} loaded`);

		async function fetchResult(url = "", data = {}) {
			const response = await fetch(url, {
				credentials: "include",
			});
			return response.json();
		}

		function debug(description = "", msg = "", force = false) {
			if (DEBUG || force) {
				console.log(`${NAMESPACE}: ${description}`, msg);
			}
		}

		function formatDate(timestamp) {
			const date =
				timestamp.toString().length === 10
					? new Date(+timestamp * 1000)
					: new Date(+timestamp);
			return `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`;
		}

		function rateColor(percent) {
			return `hsl(${100 - percent}, 70%, 45%)`;
		}

		function percentDisplay(num) {
			return num.toFixed(2).replace(".00", "");
		}

		function sanitize(string) {
			const map = {
				"&": "&",
				"<": "&lt;",
				">": "&gt;",
				'"': "&quot;",
				"'": "&#x27;",
				"/": "&#x2F;",
			};
			const reg = /[&<>"'/]/gi;
			return string.replace(reg, (match) => map[match]);
		}

		function insertAfter(referenceNode, newNode) {
			referenceNode.parentNode.insertBefore(newNode, referenceNode.nextSibling);
		}

		function attachEl(wrapper, output) {
			const content = document.createElement("div");
			content.innerHTML = output;

			wrapper.append(content);
		}

		function processFollowings(wrapper, id, output, iteration, following) {
			let outputlist = "";

			fetchResult(
				`${apiBase}/x/relation/same/followings?vmid=${id}&pn=${iteration}`,
			).then((data) => {
				debug("data returned", data);

				if (data.code !== 0) {
					outputlist = data.message;
					attachEl(wrapper, outputlist);
				} else {
					const result = data.data;
					const total = result.total;
					const items = result.list;

					if (items.length > 0) {
						items.map((item) => {
							const name = item.uname;
							const status = item.attribute;
							const uid = item.mid;
							const userSince = item.mtime;
							const userSign = item.sign;
							const avatar = item.face;
							const tag = item.tag;
							const verify = item.official_verify;
							let verifyColor = "#000";
							const vip = item.vip;
							let desc = `我的关注时间:${formatDate(userSince)}\n`;

							if (verify?.type === 0) {
								verifyColor = "#ff8d00";
							} else if (verify?.type === 1) {
								verifyColor = "#30a8fd";
							}

							if (verify?.type !== -1) {
								desc += `认证:${verify.desc}\n`;
							}

							// Remove extra the trailling new line
							desc = desc.trim();

							outputlist += `<div>
<a href="https://space.bilibili.com/${uid}" target="_blank" style="display: flex; align-items: center; margin-bottom: 5px; gap: 5px; color: inherit;">
  <img src="${avatar}" style="width: 24px; height: 24px; border-radius: 2px;" />
  <span style="color: ${verifyColor};" title="${desc}">${name}</span>
  ${item.attribute === 6 ? `<span style="border-radius: 2px; background: #5963d6; color: #fff; width: 12px; height: 12px; font-size: 10px; font-weight: bold; text-align: center; line-height: 1;" title="已互粉">⇄</span>` : ""}
  ${vip?.vipType !== 0 && vip?.vipStatus === 1 ? `<span title="${vip.label.text}\n会员有效期:${formatDate(vip.vipDueDate)}"><img src="${vip.avatar_subscript_url}" style="display: block; width: 12px; height: 12px;" /></span>` : ""}
  <span style="opacity: .6; overflow: hidden; text-overflow: ellipsis; white-space: pre; flex: 1;" title="${sanitize(userSign)}" >${sanitize(userSign.replace(/(?:\r\n|\r|\n)/g, ""))}</span>
</a></div>`;
						});

						debug("try next page", iteration + 1);

						const nextPageRequest = setTimeout(
							() => {
								processFollowings(
									wrapper,
									id,
									output,
									iteration + 1,
									following,
								);
							},
							800 + Math.floor(Math.random() * 600),
						);
					} else {
						debug("loop finished");
						// Attach stats
						attachEl(
							wrapper.querySelector("div"),
							`共同关注:${total}\n相似比:${percentDisplay((total / following) * 100)}%(${conclusion[Math.round((total / following) * 10)]})`,
						);
					}

					attachEl(wrapper, outputlist);
				}
			});
		}

		function processCard(wrapper) {
			const iteration = 1;
			const resultContent = "";
			const idEl =
				wrapper.querySelector(".face") ||
				wrapper.querySelector(".idc-avatar-container") ||
				wrapper.querySelector(".card-user-name");
			const followingEl =
				wrapper.querySelector(".info .social span") ||
				wrapper.querySelector(".info .social .like") ||
				wrapper.querySelector(".idc-content .idc-meta .idc-meta-item") ||
				wrapper.querySelector(".card-social-info .card-user-attention span");
			let id = "";
			const wrapPadding = "1rem";

			if (idEl) {
				id = idEl.href.match(/\/\/space\.bilibili\.com\/(\d+)/)[1];
			}

			// ensure user id exists
			debug("passed wrapper", wrapper);
			debug("current uid", id);

			if (id) {
				// Create output wrapper and limit height
				const injectWrap = wrapper;
				const contentWrap = document.createElement("div");

				contentWrap.classList.add(`${NAMESPACE}-wrap`);
				contentWrap.style.overflowY = "auto";
				contentWrap.style.maxHeight = "300px";
				contentWrap.style.padding = wrapPadding;
				contentWrap.style.paddingTop = ".5rem";
				contentWrap.style.marginTop = "1rem";
				contentWrap.style.borderTop = "1px solid #eee";

				const banner = document.createElement("div");
				banner.style.paddingBottom = ".5rem";
				banner.style.marginBottom = ".5rem";
				banner.style.borderBottom = "1px solid #eee";
				banner.style.whiteSpace = "pre";
				banner.innerHTML =
					`成分查询-本地查询版(<a href="${feedbackUrl}" target="_blank">问题反馈</a>)` +
					`\n外部查询:<a href="https://laplace.live/user/${id}" target="_blank">laplace</a> / <a href="https://danmakus.com/user/${id}" target="_blank">danmakus</a> / <a href="https://space.bilibili.ooo/${id}" target="_blank">ooo</a>` +
					`\n查询时间:${formatDate(Date.now())}`;
				contentWrap.append(banner);

				// Process followingSum when id is available
				const totalFollowing = followingEl.innerText.match(/(\d+)/)[1];
				debug("following element", followingEl);

				// Inject prepared wrapper
				injectWrap.append(contentWrap);

				processFollowings(
					contentWrap,
					id,
					resultContent,
					iteration,
					totalFollowing,
				);
			}
		}

		// .user-card loads dynamcially. So observe it first
		const wrapperObserver = new MutationObserver((mutationsList, observer) => {
			for (const mutation of mutationsList) {
				if (mutation.type === "childList") {
					[...mutation.addedNodes].map((item) => {
						debug("mutation wrapper added", item);

						// Normal card, global, comments avatar, comment mentions, and etc.
						if (item.classList?.contains("user-card")) {
							debug("mutation card detected (global card)", item);
							processCard(item);
						}

						// Following/follower list
						if (item.classList?.contains("idc-info")) {
							const parent = item.parentNode;

							if (parent.getAttribute("id") === "id-card") {
								debug("mutation card detected (following/follower list)", item);
								processCard(parent);
							}
						}

						// Cards in dongtai mentions
						// NOTE: deprecated since Oct 2021. Will fallback to global card
						if (item.classList?.contains("face")) {
							const parent = item.parentNode;

							if (parent.classList?.contains("userinfo-content")) {
								debug("mutation card detected (dynamic dongtai)", item);
								processCard(parent);
							}
						}

						// Cards in author area in video page
						if (item.classList?.contains("user-info-wrapper")) {
							const parent = item.parentNode;

							if (parent.classList?.contains("user-card-m-exp")) {
								debug("mutation card detected (dynamic dongtai)", item);
								processCard(parent);
							}
						}
					});
				}
			}
		});
		wrapperObserver.observe(document.body, {
			attributes: false,
			childList: true,
			subtree: true,
		});
	},
	false,
);

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址