qb-fix-bilibili

inQ_Beta wants to fix some of bilibili problem

目前为 2023-12-30 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name qb-fix-bilibili
  3. // @version 0.0.22
  4. // @description inQ_Beta wants to fix some of bilibili problem
  5. // @license Apache-2.0
  6. // @author inQ_Beta
  7. // @match https://*.bilibili.com/*
  8. // @grant GM_addStyle
  9. // @namespace no1xsyzy
  10. // ==/UserScript==
  11.  
  12. (function() {
  13. "use strict";
  14. function trace(description, center) {
  15. return center;
  16. }
  17. const $ = (x) => document.querySelector(x);
  18. function betterSelector(parentNode2, selector2) {
  19. const className = /^\.([\w_-]+)$/.exec(selector2);
  20. if (className) {
  21. return {
  22. select: () => trace(`betterSelector("${selector2}").select#class`, parentNode2.getElementsByClassName(className[1])[0]),
  23. selectAll: () => trace(
  24. `betterSelector("${selector2}").selectAll#class`,
  25. Array.from(parentNode2.getElementsByClassName(className[1]))
  26. )
  27. };
  28. }
  29. const elementID = /^#([\w_-]+)$/.exec(selector2);
  30. if (elementID) {
  31. return {
  32. select: () => trace(`betterSelector("${selector2}").select#id=${elementID[1]}`, document.getElementById(elementID[1])),
  33. selectAll: () => trace(`betterSelector("${selector2}").selectAll#id=${elementID[1]}`, [
  34. document.getElementById(elementID[1])
  35. ])
  36. };
  37. }
  38. const tagName = /^([\w_-]+)$/.exec(selector2);
  39. if (tagName) {
  40. return {
  41. select: () => trace(`betterSelector("${selector2}").select#tag`, parentNode2.getElementsByTagName(tagName[1])[0]),
  42. selectAll: () => trace(
  43. `betterSelector("${selector2}").selectAll#tag`,
  44. Array.from(parentNode2.getElementsByTagName(tagName[1]))
  45. )
  46. };
  47. }
  48. return {
  49. select: () => trace(`betterSelector("${selector2}").select#qs`, parentNode2.querySelector(selector2)),
  50. selectAll: () => trace(`betterSelector("${selector2}").selectAll#qs`, Array.from(parentNode2.querySelectorAll(selector2)))
  51. };
  52. }
  53. function launchObserver({
  54. parentNode: parentNode2,
  55. selector: selector2,
  56. failCallback = null,
  57. successCallback = null,
  58. stopWhenSuccess = true,
  59. config = {
  60. childList: true,
  61. subtree: true
  62. }
  63. }) {
  64. if (!parentNode2) {
  65. parentNode2 = document;
  66. }
  67. const { select, selectAll } = betterSelector(parentNode2, selector2);
  68. let _connected = false;
  69. const off = () => {
  70. if (_connected) {
  71. observer.takeRecords();
  72. observer.disconnect();
  73. _connected = false;
  74. }
  75. };
  76. const on = () => {
  77. if (!_connected) {
  78. observer.observe(parentNode2, config);
  79. _connected = true;
  80. }
  81. };
  82. const connected = () => _connected;
  83. const reroot = (newParentNode) => {
  84. parentNode2 = newParentNode;
  85. };
  86. const wrapped = { on, off, connected, reroot };
  87. const observeFunc = (mutationList) => {
  88. const selected = select();
  89. if (!selected) {
  90. if (failCallback) {
  91. failCallback({ ...wrapped, mutationList });
  92. }
  93. return;
  94. }
  95. if (stopWhenSuccess) {
  96. off();
  97. }
  98. if (successCallback) {
  99. const maybePromise = successCallback({
  100. ...wrapped,
  101. selected,
  102. selectAll,
  103. mutationList
  104. });
  105. if (maybePromise instanceof Promise) {
  106. maybePromise.then(() => {
  107. });
  108. }
  109. }
  110. };
  111. const observer = new MutationObserver(observeFunc);
  112. on();
  113. return wrapped;
  114. }
  115. function attrChange({
  116. node,
  117. attributeFilter,
  118. callback,
  119. once = true
  120. }) {
  121. let _connected = false;
  122. let _resolve;
  123. const promise = new Promise((resolve) => {
  124. _resolve = resolve;
  125. });
  126. const wrapped = {
  127. on() {
  128. if (_connected)
  129. return;
  130. _connected = true;
  131. observer.observe(node, { attributeFilter, attributeOldValue: true });
  132. },
  133. off() {
  134. if (!_connected)
  135. return;
  136. _connected = false;
  137. observer.disconnect();
  138. },
  139. connected() {
  140. return _connected;
  141. },
  142. reroot(x) {
  143. if (_connected) {
  144. wrapped.off();
  145. node = x;
  146. wrapped.on();
  147. } else {
  148. node = x;
  149. }
  150. },
  151. then(onfulfill, onrejected) {
  152. return promise.then(onfulfill, onrejected);
  153. }
  154. };
  155. const observer = new MutationObserver((mutationList) => {
  156. if (once) {
  157. wrapped.off();
  158. }
  159. callback(mutationList, wrapped);
  160. _resolve(mutationList[0].attributeName);
  161. });
  162. wrapped.on();
  163. return wrapped;
  164. }
  165. function elementEmerge(selector2, parentNode2, subtree = true) {
  166. const g = betterSelector(parentNode2 ?? document, selector2).select();
  167. if (g)
  168. return Promise.resolve(g);
  169. return new Promise((resolve) => {
  170. launchObserver({
  171. parentNode: parentNode2,
  172. selector: selector2,
  173. successCallback: ({ selected }) => {
  174. resolve(selected);
  175. },
  176. config: { subtree, childList: true }
  177. });
  178. });
  179. }
  180. const waitAppBodyMount = async function() {
  181. const appBody = betterSelector(document, `.app-body`).select();
  182. if (!appBody) {
  183. throw new Error("activity page");
  184. }
  185. await new Promise((resolve) => {
  186. launchObserver({
  187. parentNode: appBody,
  188. selector: `#sections-vm`,
  189. successCallback: ({ selected }) => {
  190. resolve(null);
  191. },
  192. config: { childList: true }
  193. });
  194. });
  195. return appBody;
  196. }();
  197. async function 关注栏尺寸() {
  198. GM_addStyle(`
  199. .section-content-cntr{height:calc(100vh - 250px)!important;}
  200. .follow-cntr{height:calc(100vh - 150px)!important;}
  201. .follow-cntr>.anchor-list{height:auto!important;}
  202. .follow-cntr>.anchor-list>.three-anchor{height:auto!important;}
  203. `);
  204. const sidebarVM = await (async () => {
  205. if (location.pathname === "/") {
  206. return betterSelector(document, `.flying-vm`).select();
  207. } else if (location.pathname === "/p/eden/area-tags") {
  208. return betterSelector(document, `#area-tags`).select();
  209. } else if (/^(?:\/blanc)?\/(\d+)$/.exec(location.pathname)) {
  210. const appBody = await waitAppBodyMount;
  211. return betterSelector(appBody, `#sidebar-vm`).select();
  212. }
  213. })();
  214. const sidebarPopup = await elementEmerge(`.side-bar-popup-cntr`, sidebarVM);
  215. launchObserver({
  216. parentNode: sidebarPopup,
  217. selector: `*`,
  218. successCallback: ({ mutationList }) => {
  219. if (sidebarPopup.style.height !== "0px") {
  220. sidebarPopup.style.bottom = "75px";
  221. sidebarPopup.style.height = "calc(100vh - 150px)";
  222. }
  223. setTimeout(() => $(`.side-bar-popup-cntr.ts-dot-4 .ps`)?.dispatchEvent(new Event("scroll")), 2e3);
  224. },
  225. stopWhenSuccess: false,
  226. config: {
  227. attributes: true,
  228. attributeFilter: ["class"]
  229. }
  230. });
  231. }
  232. const makeTitle$1 = () => `${($(`#area-tags header img+div`) || $(`#area-tags header h2`)).innerText} - 分区列表 - 哔哩哔哩直播`;
  233. const parentNode$3 = $(`#area-tags`);
  234. const selector$4 = `header`;
  235. function 分区标题() {
  236. launchObserver({
  237. parentNode: parentNode$3,
  238. selector: selector$4,
  239. successCallback: () => {
  240. document.title = makeTitle$1();
  241. },
  242. stopWhenSuccess: false
  243. });
  244. document.title = makeTitle$1();
  245. }
  246. const followersTextClass = (followers) => {
  247. if (followers > 1e6) {
  248. return [`${Math.round(followers / 1e5) / 10}m★`, "followers-m"];
  249. } else if (followers > 1e3) {
  250. return [`${Math.round(followers / 100) / 10}k★`, "followers-k"];
  251. } else {
  252. return [`${followers}★`, "followers-1"];
  253. }
  254. };
  255. function defaultCacheStorageFactory(id) {
  256. let store = [];
  257. const get = (key) => store.filter(([k, t, v]) => k === key).map(([k, t, v]) => [t, v])[0] ?? [0, void 0];
  258. const set = (key, time, value) => {
  259. const i = store.findIndex(([k, t, v]) => k === key);
  260. if (i === -1) {
  261. store.push([key, time, value]);
  262. } else {
  263. store[i] = [key, time, value];
  264. }
  265. };
  266. const cleanup = (ttl, now) => {
  267. store = store.filter(([k, t]) => t + ttl > now);
  268. };
  269. return { get, set, cleanup };
  270. }
  271. function timedLRU(func, {
  272. id,
  273. version = 1,
  274. ttl = 10 * 60 * 1e3,
  275. cleanupInterval = 60 * 1e3,
  276. cacheStorageFactory = defaultCacheStorageFactory
  277. }) {
  278. const cacheStorage = cacheStorageFactory(id, version);
  279. let timeout = null;
  280. const cleanup = () => {
  281. if (timeout !== null) {
  282. clearTimeout(timeout);
  283. }
  284. cacheStorage.cleanup(ttl, (/* @__PURE__ */ new Date()).getTime());
  285. timeout = setTimeout(cleanup, cleanupInterval);
  286. };
  287. setTimeout(cleanup, cleanupInterval / 10);
  288. const wrapped = async (k) => {
  289. const t = (/* @__PURE__ */ new Date()).getTime();
  290. let [_, v] = cacheStorage.get(k);
  291. if (v === void 0) {
  292. v = await func(k);
  293. }
  294. cacheStorage.set(k, t, v);
  295. return v;
  296. };
  297. wrapped.cleanup = cleanup;
  298. return wrapped;
  299. }
  300. function localStorageCacheStorageFactory(id, version) {
  301. const get = (key) => JSON.parse(localStorage.getItem(`cacheStore__${id}__${version}__${key}`)) ?? [0, void 0];
  302. const set = (key, time, value) => {
  303. localStorage.setItem(`cacheStore__${id}__${version}__${key}`, JSON.stringify([time, value]));
  304. };
  305. const cleanup = (ttl, now) => {
  306. for (let i = 0; i < localStorage.length; i++) {
  307. const k = localStorage.key(i);
  308. if (!k.startsWith(`cacheStore__${id}__`)) {
  309. continue;
  310. }
  311. if (!k.startsWith(`cacheStore__${id}__${version}__`))
  312. ;
  313. const [t, _] = JSON.parse(localStorage.getItem(k));
  314. if (t + ttl < now) {
  315. localStorage.removeItem(k);
  316. }
  317. }
  318. };
  319. return { get, set, cleanup };
  320. }
  321. const getCard = timedLRU(
  322. async (uid) => {
  323. const json = await (await fetch(`https://api.bilibili.com/x/web-interface/card?mid=${uid}`, {
  324. // credentials: 'include',
  325. headers: {
  326. Accept: "application/json"
  327. },
  328. method: "GET",
  329. mode: "cors"
  330. })).json();
  331. if (json.code !== 0) {
  332. throw json.message;
  333. }
  334. const {
  335. data: {
  336. card: { fans, sex }
  337. }
  338. } = json;
  339. return { fans, gender: sex };
  340. },
  341. {
  342. id: "getCard",
  343. version: 2,
  344. ttl: 86400 * 1e3,
  345. cacheStorageFactory: localStorageCacheStorageFactory
  346. }
  347. );
  348. const getFansCount = async (uid) => {
  349. return (await getCard(uid)).fans;
  350. };
  351. const getSexTag = async (uid) => {
  352. const { gender } = await getCard(uid);
  353. switch (gender) {
  354. case "男":
  355. return "♂";
  356. case "女":
  357. return "♀";
  358. default:
  359. return "〼";
  360. }
  361. };
  362. const getInfoByRoom = timedLRU(
  363. async (roomid) => {
  364. const json = await (await fetch(`https://api.live.bilibili.com/xlive/web-room/v1/index/getInfoByRoom?room_id=${roomid}`, {
  365. // credentials: 'include',
  366. headers: {
  367. Accept: "application/json"
  368. },
  369. method: "GET",
  370. mode: "cors"
  371. })).json();
  372. if (json.code !== 0) {
  373. throw json.message;
  374. }
  375. const followers = json.data.anchor_info.relation_info.attention;
  376. return { followers };
  377. },
  378. {
  379. id: "getInfoByRoom",
  380. version: 2,
  381. ttl: 86400 * 1e3,
  382. cacheStorageFactory: localStorageCacheStorageFactory
  383. }
  384. );
  385. const getRoomFollowers = async (roomid) => {
  386. return (await getInfoByRoom(roomid)).followers;
  387. };
  388. async function* getDynamicFeed({
  389. timezone = -480,
  390. type = "all"
  391. }) {
  392. let page = 1;
  393. let json = await (await fetch(
  394. `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=${timezone}&type=${type}&page=${page}`,
  395. {
  396. credentials: "include",
  397. headers: {
  398. Accept: "application/json"
  399. },
  400. method: "GET",
  401. mode: "cors"
  402. }
  403. )).json();
  404. if (json.code === 0) {
  405. for (const item of json.data.items) {
  406. yield item;
  407. }
  408. } else {
  409. throw json.message;
  410. }
  411. while (json.data.has_more) {
  412. page += 1;
  413. json = await (await fetch(
  414. `https://api.bilibili.com/x/polymer/web-dynamic/v1/feed/all?timezone_offset=${timezone}&type=${type}&offset=${json.data.offset}&page=${page}`,
  415. {
  416. credentials: "include",
  417. headers: {
  418. Accept: "application/json"
  419. },
  420. method: "GET",
  421. mode: "cors"
  422. }
  423. )).json();
  424. if (json.code === 0) {
  425. for (const item of json.data.items) {
  426. yield item;
  427. }
  428. } else {
  429. throw json.message;
  430. }
  431. }
  432. }
  433. function compareDynamicID(a, b) {
  434. if (a === b)
  435. return 0;
  436. if (a.length < b.length)
  437. return -1;
  438. if (a.length > b.length)
  439. return 1;
  440. if (a < b)
  441. return -1;
  442. if (a > b)
  443. return 1;
  444. }
  445. function recordDynamicFeed(spec) {
  446. const registry = [];
  447. const gen = getDynamicFeed(spec);
  448. const extend = async (n = 1) => {
  449. for (let i = 0; i < n; i++) {
  450. let v;
  451. try {
  452. v = (await gen.next()).value;
  453. } catch (err) {
  454. }
  455. if (v) {
  456. registry.push(v);
  457. } else {
  458. return false;
  459. }
  460. }
  461. return true;
  462. };
  463. const getByIndex = async (i) => {
  464. while (registry.length < i) {
  465. if (!await extend())
  466. break;
  467. }
  468. if (registry.length > i) {
  469. return registry[i];
  470. }
  471. };
  472. const lastVisibleDynamic = () => {
  473. for (let i = registry.length - 1; i >= 0; i--) {
  474. if (registry[i].visible) {
  475. return registry[i];
  476. }
  477. }
  478. return null;
  479. };
  480. const getByDynamicID = async (did) => {
  481. if (!registry.length && !await extend()) {
  482. return null;
  483. }
  484. do {
  485. if (registry[registry.length - 1].id_str === did) {
  486. return registry[registry.length - 1];
  487. }
  488. if (compareDynamicID(lastVisibleDynamic().id_str, did) < 0) {
  489. for (const dyn of registry) {
  490. if (dyn.id_str === did) {
  491. return dyn;
  492. }
  493. }
  494. return null;
  495. }
  496. } while (await extend());
  497. };
  498. const getByBVID = async (bvid) => {
  499. if (spec.type === "article") {
  500. return null;
  501. }
  502. for (const dyn of registry) {
  503. if (dyn.modules.module_dynamic.major.archive.bvid === bvid) {
  504. return dyn;
  505. }
  506. }
  507. do {
  508. if (lastVisibleDynamic()?.modules.module_dynamic.major.archive.bvid === bvid) {
  509. return lastVisibleDynamic();
  510. }
  511. } while (await extend());
  512. return null;
  513. };
  514. return { getByIndex, getByDynamicID, getByBVID };
  515. }
  516. const CARDCLS$1 = "Item_1EohdhbR";
  517. const NAMECLS = "Item_QAOnosoB";
  518. const parentNode$2 = $(`#area-tag-list`);
  519. const selector$3 = `.${CARDCLS$1}`;
  520. GM_addStyle(`
  521. .${NAMECLS}.processed::after {
  522. content: attr(data-followers);
  523. }
  524.  
  525. .${NAMECLS}.processed.followers-m {
  526. color: purple !important;
  527. }
  528.  
  529. .${NAMECLS}.processed.followers-k {
  530. color: grey !important;
  531. }
  532.  
  533. .${NAMECLS}.processed.followers-1 {
  534. color: red !important;
  535. }
  536. `);
  537. function 分区添加粉丝数() {
  538. launchObserver({
  539. parentNode: parentNode$2,
  540. selector: selector$3,
  541. successCallback: ({ selectAll }) => {
  542. for (const card of selectAll()) {
  543. (async () => {
  544. const nametag = card.querySelector(`.${NAMECLS}`);
  545. if (nametag.classList.contains("processed")) {
  546. return;
  547. }
  548. const followers = await getRoomFollowers(card.pathname.slice(1));
  549. const [txt, cls] = followersTextClass(followers);
  550. nametag.dataset.followers = txt;
  551. nametag.title = txt;
  552. nametag.classList.add("processed");
  553. nametag.classList.add(cls);
  554. })();
  555. }
  556. },
  557. stopWhenSuccess: false
  558. });
  559. }
  560. const CARDCLS = "Item_1EohdhbR";
  561. const TITLECLS = "Item_2GEmdhg6";
  562. const parentNode$1 = $(`#area-tag-list`);
  563. const selector$2 = `.${CARDCLS}`;
  564. function 分区卡片直播间标题指向() {
  565. launchObserver({
  566. parentNode: parentNode$1,
  567. selector: selector$2,
  568. successCallback: ({ selectAll }) => {
  569. for (const card of selectAll()) {
  570. (async () => {
  571. const titletag = card.querySelector(`.${TITLECLS}`);
  572. titletag.title = titletag.textContent.trim();
  573. })();
  574. }
  575. },
  576. stopWhenSuccess: false
  577. });
  578. }
  579. function 分区() {
  580. 关注栏尺寸();
  581. 分区标题();
  582. 分区添加粉丝数();
  583. 分区卡片直播间标题指向();
  584. }
  585. function liveStatus() {
  586. const liveStatus2 = betterSelector(document, `.live-status`).select().innerText;
  587. switch (liveStatus2) {
  588. case "直播":
  589. return "▶️";
  590. case "闲置":
  591. return "⏹️";
  592. case "轮播":
  593. return "🔁";
  594. default:
  595. return `【${liveStatus2}】`;
  596. }
  597. }
  598. const liveTitle = () => betterSelector(document, `.live-title`).select().innerText;
  599. const liveHost = () => betterSelector(document, `.room-owner-username`).select().innerText;
  600. const makeTitle = () => `${liveStatus()} ${liveTitle()} - ${liveHost()} - 哔哩哔哩直播`;
  601. const selector$1 = `.live-title`;
  602. async function 直播间标题() {
  603. launchObserver({
  604. parentNode: await elementEmerge(`#head-info-vm .left-header-area`),
  605. selector: selector$1,
  606. successCallback: () => {
  607. document.title = makeTitle();
  608. },
  609. stopWhenSuccess: false
  610. });
  611. document.title = makeTitle();
  612. }
  613. function 通用表情框尺寸修复() {
  614. GM_addStyle(`
  615. #control-panel-ctnr-box > .border-box.top-left[style^="transform-origin: 249px "],
  616. #control-panel-ctnr-box > .border-box.top-left[style^="transform-origin: 251px "]
  617. {
  618. height: 500px
  619. }
  620. `);
  621. }
  622. const parentNode = betterSelector(document, `#chat-items`).select();
  623. const selector = `.user-name`;
  624. GM_addStyle(`.infoline::before{
  625. content: attr(data-infoline);
  626. color: white;
  627. }
  628. .infoline.followers-m::before{
  629. color: purple;
  630. }
  631. .infoline.followers-k::before{
  632. color: red;
  633. }
  634. `);
  635. const append = async (un) => {
  636. const uid = un.parentNode.parentNode.dataset.uid;
  637. const fans = await getFansCount(uid);
  638. const [txt, cls] = followersTextClass(fans);
  639. const sextag = await getSexTag(uid);
  640. un.dataset.infoline = `${sextag} ${txt} `;
  641. if (cls !== "")
  642. un.classList.add(cls);
  643. };
  644. function 直播间留言者显示粉丝数() {
  645. launchObserver({
  646. parentNode,
  647. selector,
  648. successCallback: ({ selectAll }) => {
  649. for (const un of selectAll()) {
  650. if (!un.classList.contains("infoline")) {
  651. un.classList.add("infoline");
  652. append(un);
  653. }
  654. }
  655. },
  656. stopWhenSuccess: false
  657. });
  658. }
  659. async function 自动刷新崩溃直播间() {
  660. await new Promise((resolve) => setTimeout(resolve, 5e3));
  661. const player = betterSelector(document, `#live-player`).select();
  662. const video = elementEmerge(`video`, player, false).then((x) => void 0);
  663. const endingPanel = elementEmerge(`.web-player-ending-panel`, player, false).then(
  664. (x) => void 0
  665. );
  666. const errorPanel = elementEmerge(`.web-player-error-panel`, player, false).then((x) => void 0);
  667. const last = await Promise.race([video, endingPanel, errorPanel]);
  668. if (last.tagName === "VIDEO")
  669. ;
  670. else if (last.classList.contains("web-player-error-panel")) {
  671. location.reload();
  672. }
  673. }
  674. function 直播间() {
  675. 关注栏尺寸();
  676. 直播间标题();
  677. 直播间留言者显示粉丝数();
  678. 通用表情框尺寸修复();
  679. 自动刷新崩溃直播间();
  680. }
  681. function 直播主页() {
  682. 关注栏尺寸();
  683. }
  684. async function 分离视频类型() {
  685. GM_addStyle(`
  686. .qfb__subselect_list {display: none;}
  687.  
  688. .bili-dyn-list-tabs__item.active:nth-child(2) ~ .qfb__subselect_list {
  689. display: flex;
  690. }
  691.  
  692. #qfb_upload, #qfb_live_replay {
  693. display: none;
  694. }
  695.  
  696. #qfb_upload:not(:checked) ~ .bili-dyn-list-tabs .qfb_upload::before {content:"☐"}
  697. #qfb_upload:checked ~ .bili-dyn-list-tabs .qfb_upload::before {content:"☑"}
  698. #qfb_upload:not(:checked) ~ .bili-dyn-list .qfb_upload {
  699. display: none;
  700. }
  701.  
  702. #qfb_live_replay:not(:checked) ~ .bili-dyn-list-tabs .qfb_live_replay::before {content:"☐"}
  703. #qfb_live_replay:checked ~ .bili-dyn-list-tabs .qfb_live_replay::before {content:"☑"}
  704. #qfb_live_replay:not(:checked) ~ .bili-dyn-list .qfb_live_replay {
  705. display: none;
  706. }
  707. `);
  708. const listTabs = await elementEmerge(`.bili-dyn-list-tabs`);
  709. const cmbUpload = document.createElement("input");
  710. cmbUpload.setAttribute("type", "checkbox");
  711. cmbUpload.setAttribute("id", "qfb_upload");
  712. cmbUpload.checked = true;
  713. listTabs.before(cmbUpload);
  714. const cmbLiveReplay = document.createElement("input");
  715. cmbLiveReplay.setAttribute("type", "checkbox");
  716. cmbLiveReplay.setAttribute("id", "qfb_live_replay");
  717. cmbLiveReplay.checked = true;
  718. listTabs.before(cmbLiveReplay);
  719. const listTabsUpload = await elementEmerge(`.bili-dyn-list-tabs__item:nth-child(2)`, listTabs);
  720. const subSelect = document.createElement("div");
  721. subSelect.classList.add("bili-dyn-list-tabs");
  722. subSelect.classList.add("qfb__subselect_list");
  723. subSelect.innerHTML = `
  724. <div class="bili-dyn-list-tabs__list">
  725. <label class="bili-dyn-list-tabs__item qfb_upload" for="qfb_upload">
  726. 投稿视频
  727. </label>
  728. <label class="bili-dyn-list-tabs__item qfb_live_replay" for="qfb_live_replay">
  729. 直播回放
  730. </label>
  731. </div>
  732. `;
  733. listTabs.after(subSelect);
  734. attrChange({
  735. node: listTabsUpload,
  736. attributeFilter: ["class"],
  737. callback: () => {
  738. if (listTabsUpload.classList.contains("active")) {
  739. subSelect.style.display = "flex";
  740. } else {
  741. subSelect.style.display = "none";
  742. }
  743. },
  744. once: false
  745. });
  746. listTabs.addEventListener("click", (e) => {
  747. listTabsUpload.classList.contains("");
  748. if (e.target === listTabsUpload) {
  749. subSelect.style.display = "flex";
  750. } else {
  751. subSelect.style.display = "none";
  752. }
  753. });
  754. const dynList = await elementEmerge(`.bili-dyn-list__items`);
  755. launchObserver({
  756. parentNode: dynList,
  757. selector: `.bili-dyn-list__item`,
  758. successCallback: ({ selectAll }) => {
  759. for (const div of selectAll()) {
  760. if (div.classList.contains("processed"))
  761. continue;
  762. const type = div.getElementsByClassName(`bili-dyn-card-video__badge`)[0]?.textContent.trim();
  763. switch (type) {
  764. case "直播回放":
  765. div.classList.add("qfb_live_replay");
  766. break;
  767. case "合作视频":
  768. case "投稿视频":
  769. div.classList.add("qfb_upload");
  770. break;
  771. }
  772. div.classList.add("processed");
  773. }
  774. },
  775. config: {
  776. childList: true
  777. }
  778. });
  779. }
  780. async function 动态首页联合投稿具名() {
  781. const record = recordDynamicFeed({ type: "video" });
  782. launchObserver({
  783. parentNode: document.body,
  784. selector: `.bili-dyn-item`,
  785. successCallback: async ({ selectAll }) => {
  786. for (const dynItem of selectAll()) {
  787. if (dynItem.dataset.qfb_expanded_did === "processing") {
  788. return;
  789. }
  790. if (dynItem.dataset.qfb_expanded_did && !dynItem.querySelector(`.bili-dyn-item-fold`)) {
  791. dynItem.dataset.qfb_expanded_did = "processing";
  792. const dyn = await record.getByDynamicID(dynItem.querySelector(`.bili-dyn-card-video`).getAttribute("dyn-id"));
  793. const timediv = dynItem.querySelector(`.bili-dyn-time`);
  794. timediv.innerHTML = `${dyn.modules.module_author.pub_time} · ${dyn.modules.module_author.pub_action}`;
  795. delete dynItem.dataset.qfb_expanded_did;
  796. } else if (!dynItem.dataset.qfb_expanded_did && dynItem.querySelector(`.bili-dyn-item-fold`)) {
  797. dynItem.dataset.qfb_expanded_did = "processing";
  798. const dyn = await record.getByDynamicID(dynItem.querySelector(`.bili-dyn-card-video`).getAttribute("dyn-id"));
  799. const timediv = dynItem.querySelector(`.bili-dyn-time`);
  800. if (!dyn.modules.module_fold)
  801. return;
  802. const description = (await Promise.all(dyn.modules.module_fold.ids.map((did) => record.getByDynamicID(did)))).map((dyn2) => `<a href="${dyn2.modules.module_author.jump_url}">${dyn2.modules.module_author.name}</a>`).join(`、`);
  803. timediv.innerHTML = `${dyn.modules.module_author.pub_time} · ${description}联合创作`;
  804. dynItem.dataset.qfb_expanded_did = dyn.id_str;
  805. }
  806. }
  807. },
  808. stopWhenSuccess: false
  809. });
  810. }
  811. function 动态页面() {
  812. 动态首页联合投稿具名();
  813. 分离视频类型();
  814. }
  815. async function 单条动态页面$1() {
  816. const dyn = await elementEmerge(`.bili-rich-text`, document);
  817. const uname = $(`.bili-dyn-title>span`).textContent.trim();
  818. const dtime = $(`.bili-dyn-time`).textContent.trim();
  819. const dcont = dyn.textContent.trim();
  820. const ddisp = dcont.length > 23 ? dcont.slice(0, 20) + "..." : dcont;
  821. document.title = `${uname} ${dtime} 说道:『${ddisp}』-哔哩哔哩`;
  822. }
  823. async function opus$1() {
  824. const uname = $(`.opus-module-author__name`).textContent.trim();
  825. const dtime = $(`.opus-module-author__pub__text`).textContent.trim();
  826. const dcont = $(`.opus-module-content`).textContent.trim();
  827. const ddisp = dcont.length > 23 ? dcont.slice(0, 20) + "..." : dcont;
  828. document.title = `${uname} ${dtime} 说道:『${ddisp}』-哔哩哔哩`;
  829. }
  830. function 单条动态页面() {
  831. 单条动态页面$1();
  832. }
  833. function opus() {
  834. opus$1();
  835. }
  836. async function 排序粉丝勋章() {
  837. const list = await elementEmerge(`.medalList .list`);
  838. Array.from(list.children).sort(
  839. (a, b) => betterSelector(b, ".btn").selectAll().length - betterSelector(a, ".btn").selectAll().length || betterSelector(b, ".living").selectAll().length - betterSelector(a, ".living").selectAll().length || +betterSelector(b, ".m-medal__fans-medal-level").select().textContent - +betterSelector(a, ".m-medal__fans-medal-level").select().textContent
  840. ).forEach((node) => list.appendChild(node));
  841. }
  842. function 粉丝勋章页() {
  843. 排序粉丝勋章();
  844. }
  845. if (location.host === "live.bilibili.com") {
  846. if (location.pathname === "/") {
  847. 直播主页();
  848. } else if (location.pathname === "/p/eden/area-tags") {
  849. 分区();
  850. } else if (location.pathname === "/p/html/live-fansmedal-wall/") {
  851. 粉丝勋章页();
  852. } else if (/^(?:\/blanc)?\/(\d+)$/.exec(location.pathname) && $(`.app-body`)) {
  853. 直播间();
  854. } else
  855. ;
  856. } else if (location.host === "space.bilibili.com")
  857. ;
  858. else if (location.host === "t.bilibili.com") {
  859. if (/\/topic\/name\/[^/]+\/feed/.exec(location.pathname))
  860. ;
  861. else if (/\/\d+/.exec(location.pathname)) {
  862. 单条动态页面();
  863. } else {
  864. 动态页面();
  865. }
  866. } else if (location.host === "www.bilibili.com") {
  867. if (/^\/opus\//.exec(location.pathname)) {
  868. opus();
  869. }
  870. } else
  871. ;
  872. })();