bilibili 评论折跃小助手

复制评论区的折跃链接

  1. // ==UserScript==
  2. // @name bilibili 评论折跃小助手
  3. // @version 0.1.1
  4. // @description 复制评论区的折跃链接
  5. // @author as042971
  6. // @author Sparanoid
  7. // @license AGPL
  8. // @include *://www.bilibili.com/video/av*
  9. // @include *://www.bilibili.com/video/BV*
  10. // @icon https://experiments.sparanoid.net/favicons/v2/www.bilibili.com.ico
  11. // @grant none
  12. // @run-at document-start
  13. // @namespace https://gf.qytechs.cn/users/865651
  14. // ==/UserScript==
  15.  
  16. window.addEventListener('load', () => {
  17. const DEBUG = false;
  18. const NAMESPACE = 'bilibili-comment-wrap';
  19.  
  20. console.log(`${NAMESPACE} loaded`);
  21. function debug(description = '', msg = '', force = false) {
  22. if (DEBUG || force) {
  23. console.log(`${NAMESPACE}: ${description}`, msg)
  24. }
  25. }
  26.  
  27. function attachEl(item) {
  28. let injectWrap = item.querySelector('.con .info');
  29.  
  30. // .text - comment content
  31. // .text-con - reply content
  32. let content = item.querySelector('.con .text') || item.querySelector('.reply-con .text-con');
  33. let id = item.dataset.id;
  34. let avID = window.aid;
  35.  
  36. // Simple way to attach element on replies initially loaded with comment
  37. // which wouldn't trigger mutation inside observeComments
  38. let replies = item.querySelectorAll('.con .reply-box .reply-item');
  39. if (replies.length > 0) {
  40. [...replies].map(reply => {
  41. attachEl(reply);
  42. });
  43. }
  44.  
  45. if (injectWrap.querySelector('.comment-wrap')) {
  46. debug('already loaded for this comment');
  47. } else {
  48. // Insert wrap check button
  49. let wrapEl = document.createElement('span');
  50.  
  51. wrapEl.classList.add('comment-wrap', 'btn-hover', 'btn-highlight');
  52. wrapEl.innerHTML = '复制折跃地址';
  53. wrapEl.addEventListener('click', e => {
  54. let link = 'https://www.bilibili.com/video/av'+avID+'/#reply'+id;
  55. let aux = document.createElement("input");
  56. aux.setAttribute("value", link);
  57. document.body.appendChild(aux);
  58. aux.select();
  59. document.execCommand("copy");
  60. document.body.removeChild(aux);
  61. }, false);
  62.  
  63. injectWrap.append(wrapEl);
  64. }
  65. }
  66.  
  67. function observeComments(wrapper) {
  68. // .comment-list - general list for video, zhuanlan, and dongtai
  69. // .reply-box - replies attached to specific comment
  70. let commentLists = wrapper ? wrapper.querySelectorAll('.comment-list, .reply-box') : document.querySelectorAll('.comment-list, .reply-box');
  71.  
  72. if (commentLists) {
  73.  
  74. [...commentLists].map(commentList => {
  75.  
  76. // Directly attach elements for pure static server side rendered comments
  77. // and replies list. Used by zhuanlan posts with reply hash in URL.
  78. // TODO: need a better solution
  79. [...commentList.querySelectorAll('.list-item, .reply-item')].map(item => {
  80. attachEl(item);
  81. });
  82.  
  83. const observer = new MutationObserver((mutationsList, observer) => {
  84.  
  85. for (const mutation of mutationsList) {
  86.  
  87. if (mutation.type === 'childList') {
  88.  
  89. debug('observed mutations', [...mutation.addedNodes].length);
  90.  
  91. [...mutation.addedNodes].map(item => {
  92. attachEl(item);
  93.  
  94. // Check if the comment has replies
  95. // I check replies here to make sure I can disable subtree option for
  96. // MutationObserver to get better performance.
  97. let replies = item.querySelectorAll('.con .reply-box .reply-item');
  98.  
  99. if (replies.length > 0) {
  100. observeComments(item)
  101. debug(item.dataset.id + ' has rendered reply(ies)', replies.length);
  102. }
  103. })
  104. }
  105. }
  106. });
  107. observer.observe(commentList, { attributes: false, childList: true, subtree: false });
  108. });
  109. }
  110. }
  111.  
  112. // .bb-comment loads directly for zhuanlan post. So load it directly
  113. observeComments();
  114.  
  115. // .bb-comment loads dynamcially for dontai and videos. So observe it first
  116. const wrapperObserver = new MutationObserver((mutationsList, observer) => {
  117.  
  118. for (const mutation of mutationsList) {
  119.  
  120. if (mutation.type === 'childList') {
  121.  
  122. [...mutation.addedNodes].map(item => {
  123. debug('mutation wrapper added', item);
  124.  
  125. if (item.classList?.contains('bb-comment')) {
  126. debug('mutation wrapper added (found target)', item);
  127.  
  128. observeComments(item);
  129.  
  130. // Stop observing
  131. // TODO: when observer stops it won't work for dynamic homepage ie. https://space.bilibili.com/703007996/dynamic
  132. // so disable it here. This may have some performance impact on low-end machines.
  133. // wrapperObserver.disconnect();
  134. }
  135. })
  136. }
  137. }
  138. });
  139. wrapperObserver.observe(document.body, { attributes: false, childList: true, subtree: true });
  140.  
  141. }, false);

QingJ © 2025

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