Last active
February 6, 2026 09:26
-
-
Save Dobby233Liu/5c3117cc6ce0b5ffe5e8a33f51c93e83 to your computer and use it in GitHub Desktop.
Show liveroom category and play count: Adds a badge to show the category and play count in liveroom cards
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==UserScript== | |
| // @name Show liveroom category and play count | |
| // @namespace io.github.dobby233liu.userscripts.bilibili.liveshowroomarea | |
| // @version 1.1.7c | |
| // @description Adds a badge to show the category and play count in liveroom cards | |
| // @author Liu Wenyuan | |
| // @match https://*.bilibili.com/* | |
| // @exclude *://message.bilibili.com/pages/nav/header_sync* | |
| // @exclude *://message.bilibili.com/pages/nav/index_new_pc_sync* | |
| // @exclude *://s1.hdslb.com/bfs/seed/jinkela/short/cols/* | |
| // @require https://unpkg.com/arrive@2.5.2/minified/arrive.min.js#sha256-tIcpmxEDTbj4LvjrVQOMki7ASpQFVc5GwOuiN/1Y5Ew= | |
| // @grant GM_addStyle | |
| // @run-at document-body | |
| // @icon64 https://i1.hdslb.com/bfs/live/cdf8c5a456de00c456bc6dede3c19569ef2c40bf.png | |
| // ==/UserScript== | |
| (function() { | |
| "use strict"; | |
| const NUMERIC_REGEX = /^[0-9]+$/; | |
| function getRid(link) { | |
| const roomUrl = new URL(link.href); | |
| if (roomUrl.hostname != "live.bilibili.com") { | |
| console.warn("No room id found in", link.href); | |
| return; | |
| } | |
| const rid = roomUrl.pathname.split("/")[1]; | |
| if (!rid || !rid.match(NUMERIC_REGEX)) { | |
| console.warn("No room id found in", link.href); | |
| return; | |
| } | |
| return rid; | |
| } | |
| async function _getInfoByRoom(rid) { | |
| let data = null; | |
| try { | |
| const res = await fetch( | |
| "https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=" + encodeURIComponent(rid), | |
| { | |
| mode: "cors", | |
| credentials: "include" | |
| } | |
| ); | |
| if (!res.ok) throw new Error("Status: " + res.status); | |
| const resObj = await res.json(); | |
| if (resObj.code != 0 || !resObj.data) throw new Error(JSON.stringify(resObj)); | |
| data = resObj.data; | |
| } catch (err) { | |
| console.error("getInfoByRoom for", rid, "failed with:", err); | |
| } | |
| return data; | |
| } | |
| const roomInfoRequests = new Map(); | |
| const ROOM_INFO_EXPIRE_TIMEOUT = (1/4) * 60 * 1000; | |
| setInterval(() => { | |
| const now = Date.now(); | |
| for (const [k, v] of roomInfoRequests.entries()) { | |
| if ((now - v[1]) > ROOM_INFO_EXPIRE_TIMEOUT) { | |
| roomInfoRequests.delete(k); | |
| } | |
| } | |
| }, ROOM_INFO_EXPIRE_TIMEOUT); | |
| function getInfoByRoom(rid) { | |
| let shouldRequest = true; | |
| let ret = roomInfoRequests.get(rid); | |
| if (ret && (Date.now() - ret[1]) <= ROOM_INFO_EXPIRE_TIMEOUT) { | |
| shouldRequest = false; | |
| } else { | |
| roomInfoRequests.delete(rid); | |
| } | |
| if (shouldRequest) { | |
| ret = [_getInfoByRoom(rid), Date.now()]; | |
| roomInfoRequests.set(rid, ret); | |
| } | |
| return ret[0]; | |
| } | |
| function getAreaName({ parent_area_name, area_name }, rid) { | |
| const areaDisplay = [/*parent_area_name?.trim() ?? "",*/ area_name?.trim() ?? ""].join("-").trim(); | |
| if (areaDisplay == "") { | |
| console.warn("No area name for", rid); | |
| return ""; | |
| } | |
| return areaDisplay; | |
| } | |
| function getAreaViewUrl({ parent_area_id, area_id }) { | |
| function isAreaIdNull(id) { return !id || id == "0"; } | |
| const urlObj = new URL("https://live.bilibili.com/p/eden/area-tags"); | |
| if (!isAreaIdNull(parent_area_id)) { | |
| urlObj.searchParams.set("parentAreaId", parent_area_id); | |
| } | |
| if (!isAreaIdNull(area_id)) { | |
| urlObj.searchParams.set("areaId", area_id); | |
| } | |
| if (urlObj.search == "") return; | |
| return urlObj.href; | |
| } | |
| GM_addStyle(` | |
| .living-section-title__text { height: auto; } | |
| .living-section-category-badge-container { | |
| margin-top: 3px; | |
| text-align: right; | |
| width: 100%; | |
| white-space: nowrap; | |
| overflow: hidden; | |
| text-overflow: ellipsis; | |
| line-height: 20px; | |
| } | |
| .living-section-category-badge { | |
| border: 1px var(--brand_pink) solid; | |
| color: var(--brand_pink); | |
| padding: 2px; | |
| border-radius: 4px; | |
| font-size: 12px; transform: scale(0.8); | |
| } | |
| a[href].living-section-category-badge-link:hover { | |
| color: var(--brand_pink_active); | |
| } | |
| a[href].bili-dyn-card-live__desc__category-link:hover { | |
| color: var(--brand_blue_hover); | |
| } | |
| `); | |
| document.body.arrive(".space-main .aside-card .living-section", { existing: true }, async function(card) { | |
| if (card.querySelector(".living-section-tips")) { | |
| console.debug("Room not online", card); | |
| return; | |
| } | |
| const titleContainer = card.querySelector(".living-section-title"); | |
| if (!titleContainer) { | |
| console.warn("No title container element in", card); | |
| return; | |
| } | |
| const link = card.querySelector(".living-section-cover > a[href]"); | |
| if (!link) { | |
| console.warn("No link found for", card); | |
| return; | |
| } | |
| const rid = getRid(link); | |
| if (!rid) return; | |
| const data = await getInfoByRoom(rid); | |
| if (!data) return; | |
| if (!data?.room_info) { | |
| console.warn("No room info data for", rid); | |
| return; | |
| } | |
| const areaDisplay = getAreaName(data.room_info, rid); | |
| if (areaDisplay == "") return; | |
| const watchedDisplay = data.watched_show?.num; | |
| const areaBadgeContainer = titleContainer.appendChild(document.createElement("div")); | |
| areaBadgeContainer.className = "living-section-category-badge-container"; | |
| const areaBadge = areaBadgeContainer.appendChild(document.createElement("span")); | |
| areaBadge.className = "living-section-category-badge"; | |
| const areaLink = areaBadge.appendChild(document.createElement("a")); | |
| areaLink.className = "living-section-category-badge-link"; | |
| areaLink.innerText = areaDisplay; | |
| const areaViewUrl = getAreaViewUrl(data.room_info); | |
| if (areaViewUrl) areaLink.href = areaViewUrl; | |
| areaLink.target = "_blank"; | |
| if (watchedDisplay) { | |
| areaBadge.appendChild(document.createTextNode(" · ")); | |
| const watchedNumIcon = areaBadge.appendChild(document.createElement("i")); | |
| watchedNumIcon.className = "vui_icon sic-BDC-playdata_square_line"; | |
| watchedNumIcon.style.fontVariationSettings = '"strk" 3'; | |
| watchedNumIcon.style.marginRight = "2px"; | |
| areaBadge.appendChild(document.createTextNode(watchedDisplay)); | |
| } | |
| }); | |
| document.body.arrive(".bili-dyn-card-live", { existing: true }, async function(card) { | |
| const rid = getRid(card); | |
| if (!rid) return; | |
| const bottomline = card.querySelector(".bili-dyn-card-live__desc"); | |
| if (!bottomline) { | |
| console.warn("No bottomline found for", card); | |
| return; | |
| } | |
| const data = await getInfoByRoom(rid); | |
| if (!data) return; | |
| if (!data?.room_info) { | |
| console.warn("No room info data for", rid); | |
| return; | |
| } | |
| const areaDisplay = getAreaName(data.room_info, rid); | |
| if (areaDisplay == "") return; | |
| const watchedDisplay = data.watched_show?.num ? `${data.watched_show.num}人看过` : ""; | |
| if (watchedDisplay == "") { | |
| console.warn("No watched num for", rid); | |
| } | |
| bottomline.replaceChildren(); | |
| const areaLink = bottomline.appendChild(document.createElement("a")); | |
| areaLink.className = "bili-dyn-card-live__desc__category-link"; | |
| areaLink.innerText = areaDisplay; | |
| const areaViewUrl = getAreaViewUrl(data.room_info); | |
| if (areaViewUrl) areaLink.href = areaViewUrl; | |
| areaLink.target = "_blank"; | |
| if (watchedDisplay != "") { | |
| bottomline.appendChild(document.createTextNode(" · ")); | |
| bottomline.appendChild(document.createTextNode(watchedDisplay)); | |
| } | |
| }); | |
| })(); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment