🏠 返回首頁 

Greasy Fork is available in English.

B站共同关注快速查看

快速查看与特定用户的共同关注(视频播放页、动态页、用户空间、直播间)


安装此脚本?
作者推荐脚本

您可能也喜欢B站封面获取


安装此脚本
  1. // ==UserScript==
  2. // @name B站共同关注快速查看
  3. // @version 1.14.0.20240827
  4. // @namespace laster2800
  5. // @author Laster2800
  6. // @description 快速查看与特定用户的共同关注(视频播放页、动态页、用户空间、直播间)
  7. // @icon https://www.bilibili.com/favicon.ico
  8. // @homepageURL https://greasyfork.org/zh-CN/scripts/428453
  9. // @supportURL https://greasyfork.org/zh-CN/scripts/428453/feedback
  10. // @license LGPL-3.0
  11. // @include *://www.bilibili.com/*
  12. // @include *://t.bilibili.com/*
  13. // @include *://space.bilibili.com/*
  14. // @include /https?:\/\/live\.bilibili\.com\/(blanc\/)?\d+([/?]|$)/
  15. // @exclude *://www.bilibili.com/watchlater/
  16. // @exclude *://www.bilibili.com/correspond/*
  17. // @exclude *://www.bilibili.com/page-proxy/*
  18. // @exclude *://t.bilibili.com/*/*
  19. // @require https://update.greasyfork.org/scripts/409641/1435266/UserscriptAPI.js
  20. // @require https://update.greasyfork.org/scripts/432002/1161015/UserscriptAPIWait.js
  21. // @require https://update.greasyfork.org/scripts/432003/1381253/UserscriptAPIWeb.js
  22. // @grant GM_notification
  23. // @grant GM_xmlhttpRequest
  24. // @grant GM_setValue
  25. // @grant GM_getValue
  26. // @grant GM_deleteValue
  27. // @grant GM_listValues
  28. // @grant GM_registerMenuCommand
  29. // @grant GM_unregisterMenuCommand
  30. // @connect api.bilibili.com
  31. // @compatible edge 版本不小于 93
  32. // @compatible chrome 版本不小于 93
  33. // @compatible firefox 版本不小于 92
  34. // ==/UserScript==
  35. (function() {
  36. 'use strict'
  37. const gm = {
  38. id: 'gm428453',
  39. configVersion: GM_getValue('configVersion'),
  40. configUpdate: 20210928,
  41. config: {
  42. dispMessage: true,
  43. dispInReverse: false,
  44. dispInText: false,
  45. dispRelation: true,
  46. userSpace: true,
  47. rareCard: false,
  48. },
  49. configMap: {
  50. dispMessage: { default: true, name: '无共同关注或查询失败时提示信息', needNotReload: true },
  51. dispInReverse: { default: false, name: '以目标 [最新关注 → 最早关注] 排序', needNotReload: true },
  52. dispInText: { default: false, name: '以纯文本形式显示', needNotReload: true },
  53. dispRelation: { default: true, name: '显示目标与本账号的关系', needNotReload: true },
  54. userSpace: { default: true, name: '在用户空间启用' },
  55. rareCard: { default: false, name: '在非常规用户卡片启用' },
  56. },
  57. url: {
  58. api_sameFollowings: uid => `https://api.bilibili.com/x/relation/same/followings?vmid=${uid}`,
  59. api_relation: uid => `https://api.bilibili.com/x/space/acc/relation?mid=${uid}`,
  60. page_space: uid => `https://space.bilibili.com/${uid}`,
  61. gm_changelog: 'https://gitee.com/liangjiancang/userscript/blob/master/script/BilibiliSameFollowing/changelog.md',
  62. },
  63. regex: {
  64. page_videoNormalMode: /\.com\/video([#/?]|$)/,
  65. page_videoWatchlaterMode: /\.com\/medialist\/play\/(watchlater|ml\d+)([#/?]|$)/,
  66. page_listMode: /\.com\/list\/.+/,
  67. page_dynamic: /\/t\.bilibili\.com(\/|$)/,
  68. page_dynamicDetail: /\/t\.bilibili\.com\/\d+([#/?]|$)/,
  69. page_article: /\.com\/read\/cv\d+([#/?]|$)/,
  70. page_space: /space\.bilibili\.com\/\d+([#/?]|$)/,
  71. page_live: /live\.bilibili\.com\/(blanc\/)?\d+([#/?]|$)/, // 只含具体的直播间页面
  72. },
  73. const: {
  74. noticeTimeout: 5600,
  75. },
  76. }
  77. /* global UserscriptAPI */
  78. const api = new UserscriptAPI({
  79. id: gm.id,
  80. label: GM_info.script.name,
  81. })
  82. /** @type {Script} */
  83. let script = null
  84. /** @type {Webpage} */
  85. let webpage = null
  86. /**
  87. * 脚本运行的抽象,为脚本本身服务的核心功能
  88. */
  89. class Script {
  90. /** 通用方法 */
  91. method = {
  92. /**
  93. * 重置脚本
  94. */
  95. reset() {
  96. const gmKeys = GM_listValues()
  97. for (const gmKey of gmKeys) {
  98. GM_deleteValue(gmKey)
  99. }
  100. },
  101. }
  102. /**
  103. * 初始化脚本
  104. */
  105. init() {
  106. try {
  107. this.updateVersion()
  108. for (const [name, item] of Object.entries(gm.configMap)) {
  109. const v = GM_getValue(name)
  110. const dv = item.default
  111. gm.config[name] = typeof v === typeof dv ? v : dv
  112. }
  113. } catch (e) {
  114. api.logger.error(e)
  115. api.message.confirm('初始化错误!是否彻底清空内部数据以重置脚本?').then(r###lt => {
  116. if (r###lt) {
  117. this.method.reset()
  118. location.reload()
  119. }
  120. })
  121. }
  122. }
  123. /**
  124. * 初始化脚本菜单
  125. */
  126. initScriptMenu() {
  127. const _self = this
  128. const cfgName = id => `[ ${config[id] ? '✓' : '✗'} ] ${configMap[id].name}`
  129. const { config, configMap } = gm
  130. const menuMap = {}
  131. for (const id of Object.keys(config)) {
  132. menuMap[id] = createMenuItem(id)
  133. }
  134. // 其他菜单
  135. menuMap.reset = GM_registerMenuCommand('初始化脚本', () => this.resetScript())
  136. function createMenuItem(id) {
  137. return GM_registerMenuCommand(cfgName(id), () => {
  138. config[id] = !config[id]
  139. GM_setValue(id, config[id])
  140. GM_notification({
  141. text: `已${config[id] ? '开启' : '关闭'}「${configMap[id].name}」功能${configMap[id].needNotReload ? '' : ',刷新页面以生效(点击通知以刷新)'}。`,
  142. timeout: gm.const.noticeTimeout,
  143. onclick: configMap[id].needNotReload ? null : () => location.reload(),
  144. })
  145. clearMenu()
  146. _self.initScriptMenu()
  147. })
  148. }
  149. function clearMenu() {
  150. for (const menuId of Object.values(menuMap)) {
  151. GM_unregisterMenuCommand(menuId)
  152. }
  153. }
  154. }
  155. /**
  156. * 版本更新处理
  157. */
  158. updateVersion() {
  159. if (gm.configVersion >= 20210829) { // 1.5.0.20210829
  160. if (gm.configVersion < gm.configUpdate) {
  161. // 必须按从旧到新的顺序写
  162. // 内部不能使用 gm.configUpdate,必须手写更新后的配置版本号!
  163. // 1.8.0.20210928
  164. if (gm.configVersion < 20210928) {
  165. GM_deleteValue('live')
  166. GM_deleteValue('commonCard')
  167. }
  168. // 功能性更新后更新此处配置版本
  169. if (gm.configVersion < 0) {
  170. GM_notification({
  171. text: '功能性更新完毕,你可能需要重新设置脚本。点击查看更新日志。',
  172. onclick: () => window.open(gm.url.gm_changelog),
  173. })
  174. }
  175. }
  176. if (gm.configVersion !== gm.configUpdate) {
  177. gm.configVersion = gm.configUpdate
  178. GM_setValue('configVersion', gm.configVersion)
  179. }
  180. } else {
  181. this.method.reset()
  182. gm.configVersion = gm.configUpdate
  183. GM_setValue('configVersion', gm.configVersion)
  184. }
  185. }
  186. /**
  187. * 初始化脚本
  188. */
  189. async resetScript() {
  190. const r###lt = await api.message.confirm('是否要初始化脚本?')
  191. if (r###lt) {
  192. const gmKeys = GM_listValues()
  193. for (const gmKey of gmKeys) {
  194. GM_deleteValue(gmKey)
  195. }
  196. gm.configVersion = gm.configUpdate
  197. GM_setValue('configVersion', gm.configVersion)
  198. location.reload()
  199. }
  200. }
  201. }
  202. /**
  203. * 页面处理的抽象,脚本围绕网站的特化部分
  204. */
  205. class Webpage {
  206. /** 通用方法 */
  207. method = {
  208. /**
  209. * 从 URL 中获取 UID
  210. * @param {string} [url=location.href] URL
  211. * @returns {string} UID
  212. */
  213. getUid(url = location.href) {
  214. return /\/(\d+)([#/?]|$)/.exec(url)?.[1] ?? null
  215. },
  216. /**
  217. * 获取指定用户与你的关系
  218. * @param {string} uid UID
  219. * @returns {Promise<{code: number, special: boolean}>} `{code, special}`
  220. * @see {@link https://github.com/SocialSisterYi/bilibili-API-collect/blob/master/user/relation.md#查询用户与自己关系_互相 查询用户与自己关系_互相}
  221. */
  222. async getRelation(uid) {
  223. const resp = await api.web.request({
  224. url: gm.url.api_relation(uid),
  225. }, { check: r => r.code === 0 })
  226. const relation = resp.data.be_relation
  227. return { code: relation.attribute, special: relation.special === 1 }
  228. },
  229. /**
  230. * 判断用户是否为自己
  231. * @param {string | number} uid UID
  232. * @returns {boolean} 用户是否为自己
  233. */
  234. isUserSelf(uid) {
  235. const selfUid = document.cookie.replace(/(?:(?:^|.*;\s*)DedeUserID\s*=\s*([^;]*).*$)|^.*$/, '$1')
  236. return selfUid ? String(uid) === selfUid : false
  237. },
  238. }
  239. /**
  240. * 卡片处理逻辑
  241. * @param {Object} options 选项
  242. * @param {string | { querySelector: Function }} [options.container] 卡片父元素(选择器),缺省时取 `document.body`
  243. * @param {string} options.card 卡片元素选择器
  244. * @param {string} options.user 用户元素选择器
  245. * @param {string} options.info 信息元素选择器
  246. * @param {boolean} [options.lazy=true] 卡片内容是否为懒加载
  247. * @param {boolean} [options.ancestor] 将 `container` 视为祖先元素而非父元素
  248. * @param {string} [options.before] 将信息显示元素插入到信息元素内部哪个元素之前,以 CSS 选择器定义,缺省时插入到信息元素最后
  249. */
  250. async cardLogic(options) {
  251. options = { lazy: true, ancestor: false, ...options }
  252. let container = null
  253. if (options?.container?.querySelector instanceof Function) {
  254. container = options.container
  255. } else {
  256. container = options.container ? await api.wait.$(options.container) : (document.body ?? await api.wait.$('body'))
  257. }
  258. api.wait.executeAfterElementLoaded({
  259. selector: options.card,
  260. base: container,
  261. subtree: options.ancestor,
  262. repeat: true,
  263. timeout: 0,
  264. callback: async card => {
  265. let userLink = null
  266. // 存在两种情况,不能简单套用一个 waitQuerySelector 来处理,否则可能会引入无效等待
  267. if (options.lazy) {
  268. // 情况 1:往「正在加载」状态的 card 中添加元素,使其转化为「已完成」状态
  269. userLink = await api.wait.$(options.user, card)
  270. } else {
  271. // 情况 2:将「正在加载」状态的 card 移除,然后将「已完成」状态的 card 添加到 DOM 中
  272. userLink = card.querySelector(options.user)
  273. }
  274. if (userLink) {
  275. const info = await api.wait.$(options.info, card)
  276. const before = options.before && await api.wait.$(options.before, info)
  277. await this.generalLogic({
  278. uid: this.method.getUid(userLink.href),
  279. target: info,
  280. className: `${gm.id} card-same-followings`,
  281. before,
  282. })
  283. }
  284. },
  285. })
  286. }
  287. /**
  288. * 通用处理逻辑
  289. * @param {Object} options 选项
  290. * @param {string | number} options.uid 用户 ID
  291. * @param {HTMLElement} options.target 指定目标元素
  292. * @param {string} [options.className=''] 显示元素的类名;若 `target` 的子孙元素中有对应元素则直接使用,否则创建之
  293. * @param {HTMLElement} [options.before] 将信息显示元素插入哪个元素之前,该元素须为目标元素的子元素;缺省时插入到目标元素最后
  294. */
  295. async generalLogic(options) {
  296. const { uid, target, before } = options
  297. if (webpage.method.isUserSelf(uid)) return
  298. let dispEl = target.sameFollowings ?? (options.className ? target.querySelector(options.className.replaceAll(/(^|\s+)(?=\w)/g, '.')) : null)
  299. if (dispEl) {
  300. dispEl.textContent = ''
  301. } else {
  302. dispEl = document.createElement('div')
  303. if (before && target.contains(before)) {
  304. before.before(dispEl)
  305. } else {
  306. target.append(dispEl)
  307. }
  308. dispEl.className = options.className || ''
  309. target.sameFollowings = dispEl
  310. }
  311. dispEl.style.display = 'none'
  312. try {
  313. let resp = await api.web.request({
  314. url: gm.url.api_sameFollowings(uid),
  315. })
  316. if (resp.code === 0) {
  317. const { data } = resp
  318. if (data.list) {
  319. const { total } = data
  320. const totalPages = Math.ceil(total / 50)
  321. for (let i = 2; i <= totalPages; i++) {
  322. resp = await api.web.request({
  323. url: `${gm.url.api_sameFollowings(uid)}&pn=${i}`,
  324. })
  325. if (resp.code !== 0 || !resp.data.list) break
  326. data.list.push(...resp.data.list)
  327. }
  328. }
  329. let sameFollowings = null
  330. sameFollowings = (gm.config.dispInText ? data.list?.map(item => item.uname) : data.list) ?? []
  331. if (sameFollowings.length > 0 || gm.config.dispMessage) {
  332. if (sameFollowings.length > 0) {
  333. if (!gm.config.dispInReverse) {
  334. sameFollowings.reverse()
  335. }
  336. if (gm.config.dispInText) {
  337. dispEl.innerHTML = `<div class="gm-pre">共同关注</div><div class="same-following">${sameFollowings.join(',&nbsp;')}</div>`
  338. } else {
  339. const highlights = this.getHighlightFollowings()
  340. let innerHTML = `<div class="gm-pre" title="加粗:本账号特别关注\n下划线:与本账号互粉\n目标的关注时间:${gm.config.dispInReverse ? '最新关注 → 最早关注' : '最早关注 → 最新关注'}">共同关注</div><div>`
  341. for (const item of sameFollowings) {
  342. const { uname, sign } = item
  343. const mid = String(item.mid)
  344. let className = 'same-following'
  345. if (item.special === 1) { // 特别关注
  346. className += ' gm-special'
  347. }
  348. if (item.attribute === 6) { // 互粉
  349. className += ' gm-mutual'
  350. }
  351. for (const highlight of highlights) { // 高亮显示
  352. if (highlight === mid || highlight === uname) {
  353. className += ' gm-highlight'
  354. }
  355. }
  356. const mtime = new Date(item.mtime * 1000)
  357. const myFollowingTime = `${mtime.toLocaleDateString()} ${mtime.toLocaleTimeString()}`
  358. const verify = item.official_verify?.desc
  359. let title = ''
  360. if (verify) title += `认证:${verify}\n`
  361. if (sign) title += `签名:${sign}\n`
  362. title += `本账号关注时间:${myFollowingTime}`
  363. innerHTML += `<a href="${gm.url.page_space(mid)}" target="_blank" class="${className}" title="${title}">${uname}</a><span>,&nbsp;</span>`
  364. }
  365. dispEl.innerHTML = innerHTML.slice(0, -'<span>,&nbsp;</span>'.length) + '</div>'
  366. }
  367. } else if (gm.config.dispMessage) {
  368. dispEl.innerHTML = '<div class="gm-pre">共同关注</div><div class="same-following">[ 无 ]</div>'
  369. }
  370. }
  371. } else {
  372. if (gm.config.dispMessage && resp.message) {
  373. const message = resp.code === 22115 ? '关注列表不可见' : resp.message
  374. dispEl.innerHTML = `<div class="gm-pre">共同关注</div><div title="查询失败 [ code: ${resp.code}, message: ${resp.message} ]" class="same-following">[ ${message} ]</div>`
  375. }
  376. const msg = [resp.code, resp.message]
  377. if (resp.code > 0) {
  378. api.logger.info(msg)
  379. } else {
  380. api.logger.error(msg)
  381. }
  382. }
  383. } catch (e) {
  384. if (gm.config.dispMessage) {
  385. dispEl.innerHTML = '<div class="gm-pre">共同关注</div><div class="same-following">[ 网络请求错误 ]</div>'
  386. }
  387. api.logger.error(e)
  388. }
  389. if (gm.config.dispRelation) {
  390. try {
  391. const relation = await this.method.getRelation(uid)
  392. const desc = (relation.special ? {
  393. 1: '对方悄悄关注并特别关注了你', // impossible
  394. 2: '对方特别关注了你',
  395. 6: '对方与你互粉并特别关注了你',
  396. 128: '对方已将你拉黑,但特别关注了你', // impossible
  397. } : {
  398. 1: '对方悄悄关注了你',
  399. 2: '对方关注了你',
  400. 6: '对方与你互粉',
  401. 128: '对方已将你拉黑',
  402. })[relation.code]
  403. if (desc) {
  404. dispEl.insertAdjacentHTML('afterbegin', `<div class="gm-relation">${desc}</div>`)
  405. }
  406. } catch (e) {
  407. api.logger.error(e)
  408. }
  409. }
  410. if (dispEl.textContent) {
  411. dispEl.style.display = ''
  412. }
  413. }
  414. /**
  415. * 获取当前页面高亮关注
  416. *
  417. * @returns {string[]} 高亮关注(数组元素为 uid 或用户名)
  418. */
  419. getHighlightFollowings() {
  420. const highlights = []
  421. if (api.base.urlMatch(gm.regex.page_videoNormalMode)) {
  422. const uploader = '#v_upinfo .u-face a.u-face__avatar' // 单独投稿(新旧版一致)
  423. const oldGroup = '.members-info .up-card a.avatar' // 旧版集体投稿
  424. const newGroup = '.members-info-v1 .up-card a.avatar' // 新版集体投稿
  425. const highlightEls = document.querySelectorAll(`${uploader},${oldGroup},${newGroup}`)
  426. for (const el of highlightEls) {
  427. const uid = this.method.getUid(el)
  428. uid && highlights.push(uid)
  429. }
  430. } else if (api.base.urlMatch(gm.regex.page_dynamicDetail)) {
  431. const author = '.card .bili-dyn-item .bili-dyn-title' // 动态所有者
  432. const origAuthor = '.card .bili-dyn-item .dyn-orig-author' // 原动态所有者(对于转发动态)
  433. const highlightEls = document.querySelectorAll(`${author},${origAuthor}`)
  434. for (const el of highlightEls) {
  435. const text = el.textContent.replace(/^\s*/, '').replace(/\s*$/, '')
  436. highlights.push(text)
  437. }
  438. } else if (api.base.urlMatch(gm.regex.page_article)) {
  439. const el = document.querySelector('.article-up-info .avatar-container a')
  440. const uid = this.method.getUid(el)
  441. uid && highlights.push(uid)
  442. }
  443. return highlights
  444. }
  445. /**
  446. * 初始化 <bili-user-profile> 处理
  447. */
  448. async initBiliUserProfile() {
  449. const bup = await api.wait.$('bili-user-profile')
  450. webpage.cardLogic({
  451. container: bup.shadowRoot,
  452. card: '#view',
  453. user: '#avatar',
  454. info: '#content',
  455. })
  456. webpage.addStyle(bup.shadowRoot)
  457. }
  458. /**
  459. * 初始化动态页
  460. *
  461. * 针对 (左方「正在直播」 + 动态所有者 + 被转发动态所有者) 的用户卡片。
  462. */
  463. async initDynamic() {
  464. const container = await api.wait.waitForElementLoaded({
  465. selector: '.bili-user-profile',
  466. base: document.body,
  467. subtree: false,
  468. timeout: 0,
  469. })
  470. // 此处B站的用户卡片更新方式比较奇葩
  471. // 查看未查看过的用户时:直接改部分元素的 textContent 来达成效果
  472. // 查看已查看过用户时:通过其他方式,如 setAttribute() 来达成效果
  473. let userLink = null
  474. // 处理查看未查看过的用户,用 wait API 中的黑科技解决
  475. api.wait.executeAfterElementLoaded({
  476. selector: '.bili-user-profile-view__avatar',
  477. base: container,
  478. repeat: true,
  479. timeout: 0,
  480. callback: el => {
  481. userLink = el
  482. update()
  483. // 处理查看已查看过用户
  484. const ob = new MutationObserver(update)
  485. ob.observe(userLink, { attributeFilter: ['href'] })
  486. },
  487. })
  488. async function update() {
  489. const uid = webpage.method.getUid(userLink.href)
  490. if (uid) {
  491. webpage.generalLogic({
  492. uid,
  493. target: await api.wait.$('.bili-user-profil1e__info__body', container),
  494. className: `${gm.id} card-same-followings`,
  495. })
  496. }
  497. }
  498. }
  499. /**
  500. * 初始化直播间
  501. *
  502. * 处理点击弹幕弹出的信息卡片。
  503. */
  504. async initLive() {
  505. const frame = self !== top
  506. const container = await api.wait.$('.danmaku-menu')
  507. const usernameEl = await api.wait.$('.username', container)
  508. container.style.width = 'auto'
  509. container.style.maxWidth = frame ? '264px' : '300px'
  510. api.base.addStyle(`
  511. .danmaku-menu .none-select > * {
  512. padding: 4px 10px !important;
  513. }
  514. `)
  515. const ob = new MutationObserver(() => {
  516. const uid = container.__vue__.info?.uid
  517. if (uid) {
  518. webpage.generalLogic({
  519. uid,
  520. target: container,
  521. className: `${gm.id} live-same-followings`,
  522. })
  523. // 若在 frame 中,container 右边会被 frame 边界挡住使得宽度受限,用 transform 左移也无法突破
  524. // 故不能直接用一个 transform 来解决,须动态计算
  525. // 说是动态计算,也不要根据宽度增量来算偏移了,一是官方自己的位置就不科学;二是想精确计算,必须得等到卡片
  526. // 注入文字之后,那么偏移的时间点就晚了,会造成视觉上非常强烈的不适感,综合显示效果还不如现在这样
  527. container.style.left = frame ? '76vw' : '72vw'
  528. }
  529. })
  530. ob.observe(usernameEl, { childList: true, subtree: true })
  531. }
  532. addStyle(doc = document) {
  533. api.base.addStyle(`
  534. .${gm.id} > * {
  535. display: inline-block;
  536. }
  537. .${gm.id} > *,
  538. .${gm.id} .same-following {
  539. color: inherit;
  540. text-decoration: none;
  541. outline: none;
  542. margin: 0;
  543. padding: 0;
  544. border: 0;
  545. vertical-align: baseline;
  546. white-space: pre-wrap;
  547. word-break: break-all;
  548. }
  549. .${gm.id} a.same-following:hover,
  550. .${gm.id} .gm-highlight {
  551. color: #00a1d6;
  552. }
  553. .${gm.id} .same-following.gm-highlight:hover {
  554. color: #3f51b5;
  555. }
  556. .${gm.id} .gm-relation {
  557. display: block;
  558. font-weight: bold;
  559. }
  560. .${gm.id} .gm-special {
  561. font-weight: bold;
  562. }
  563. .${gm.id} .gm-mutual {
  564. text-decoration: underline;
  565. }
  566. .${gm.id}.card-same-followings {
  567. color: #99a2aa;
  568. padding: 1em 0 0;
  569. font-size: 12px;
  570. }
  571. .${gm.id}.card-same-followings .gm-pre {
  572. position: absolute;
  573. margin-left: -5em;
  574. line-height: unset;
  575. }
  576. .${gm.id}.space-same-followings {
  577. margin-bottom: 0.5em;
  578. padding: 0.5em 1.6em;
  579. background: #fff;
  580. box-shadow: 0 0 0 1px #eee;
  581. border-radius: 0 0 4px 4px;
  582. }
  583. .${gm.id}.space-same-followings .gm-pre {
  584. font-weight: bold;
  585. padding-right: 1em;
  586. }
  587. .${gm.id}.live-same-followings {
  588. margin: 1em;
  589. }
  590. .${gm.id}.live-same-followings > * {
  591. display: block;
  592. }
  593. .${gm.id}.live-same-followings > :first-child {
  594. margin-bottom: 0.5em;
  595. }
  596. .${gm.id}.live-same-followings .gm-pre {
  597. font-weight: bold;
  598. }
  599. .z-aside-area {
  600. z-index: 11;
  601. }
  602. `, doc)
  603. }
  604. }
  605. document.readyState !== 'complete' ? window.addEventListener('load', main) : main()
  606. async function main() {
  607. script = new Script()
  608. webpage = new Webpage()
  609. script.init()
  610. script.initScriptMenu()
  611. webpage.addStyle()
  612. // 遍布全站的常规用户卡片,如视频评论区、动态评论区、用户空间评论区……
  613. // 旧版用户卡片
  614. webpage.cardLogic({
  615. card: '.user-card',
  616. user: '.face',
  617. info: '.info',
  618. lazy: false,
  619. })
  620. // 2022 版用户卡片
  621. webpage.cardLogic({
  622. card: '.user-card',
  623. user: '.card-user-name',
  624. info: '.card-content',
  625. before: '.card-btn-warp',
  626. lazy: false,
  627. })
  628. // 2024 下半年版用户卡片
  629. webpage.initBiliUserProfile()
  630. if (api.base.urlMatch([gm.regex.page_videoNormalMode, gm.regex.page_listMode])) {
  631. // 常规播放页中的UP主头像
  632. // 旧版播放页: .user-card-m
  633. // 2022 版播放页: .user-card-m-exp
  634. webpage.cardLogic({
  635. card: '.user-card-m, .user-card-m-exp',
  636. user: '.face',
  637. info: '.info',
  638. before: '.btn-box',
  639. ancestor: true,
  640. })
  641. } else if (api.base.urlMatch(gm.regex.page_videoWatchlaterMode)) {
  642. // 稍后再看播放页中的UP主头像
  643. webpage.cardLogic({
  644. container: '#app #app', // 这是什么阴间玩意?
  645. card: '.user-card-m',
  646. user: '.face',
  647. info: '.info',
  648. before: '.btn-box',
  649. })
  650. } else if (api.base.urlMatch(gm.regex.page_dynamic)) {
  651. // 动态页中,(左方「正在直播」 + 动态所有者 + 被转发动态所有者) 的用户卡片
  652. webpage.initDynamic()
  653. } else if (api.base.urlMatch(gm.regex.page_space)) {
  654. if (gm.config.userSpace) {
  655. // 用户空间顶部显示
  656. webpage.generalLogic({
  657. uid: webpage.method.getUid(),
  658. target: await api.wait.$('.h .wrapper'),
  659. className: `${gm.id} space-same-followings`,
  660. })
  661. }
  662. if (gm.config.rareCard) {
  663. // 用户空间的动态中,被转发动态的所有者的用户卡片
  664. webpage.cardLogic({
  665. card: '.userinfo-wrapper',
  666. user: '.face',
  667. info: '.info',
  668. ancestor: true,
  669. })
  670. // 用户空间右侧充电中的用户卡片
  671. webpage.cardLogic({
  672. card: '#id-card',
  673. user: '.idc-avatar-container',
  674. info: '.idc-info',
  675. })
  676. }
  677. } else if (api.base.urlMatch(gm.regex.page_live)) {
  678. // 直播间点击弹幕弹出的信息卡片
  679. webpage.initLive()
  680. }
  681. }
  682. })()