Greasy Fork is available in English.

躲啥躲

b站PC端视频/番剧等评论归属显形,点击可调整文字颜色

// ==UserScript==
// @name        躲啥躲
// @description b站PC端视频/番剧等评论归属显形,点击可调整文字颜色
// @author      (σ`д′)σ
// @version     1.2.2
// @namespace   https://greasyfork.org/zh-CN/scripts/477707
// @license     GPL-3.0-or-later
// @match       *://www.bilibili.com/video/*
// @match       *://www.bilibili.com/bangumi/play/*
// @grant       GM_addStyle
// @run-at      document-end
// @supportURL  https://greasyfork.org/zh-CN/scripts/477707
// @homepageURL https://github.com/Xli33/odd-monkey
// ==/UserScript==

(() => {
  'use strict';

  const getEl = (name) => document.querySelector(name);

  // comment area
  const elComment = getEl('#commentapp') || getEl('#comment-module');
  if (!elComment) return;

  if (location.pathname.startsWith('/video/')) {
    // observe once
    const observeOnce = (parent, sel, callback, options) => {
      new MutationObserver((mutations, ob) => {
        const el = parent.querySelector(sel);
        if (el) {
          ob.disconnect();
          callback(el);
        }
      }).observe(parent, {
        childList: true,
        // subtree: true,
        ...options
      });
    };

    // watch on need
    observeOnce(elComment, 'bili-comments', (el) => {
      // info to show
      const id = '--' + Math.floor(Math.random() * 10000),
        labelClass = 'com-ip' + id,
        colorClass = labelClass + '-input';
      const getExtraEle = (text) => {
        if (!text) return '';
        const ele = document.createElement('label');
        ele.part = labelClass;
        ele.innerHTML = `${text}<input type="color" part=${colorClass} />`;
        ele.title = '点击调色';
        for (const k in colorEvent) {
          ele.firstElementChild[k] = colorEvent[k];
        }
        return ele;
      };
      const colorEvent = {
        oninput(e) {
          elComment.style.setProperty(id, e.target.value);
        },
        onchange(e) {
          localStorage._ipsv = e.target.value;
        },
        onclick(e) {
          e.target.value = elComment.style.getPropertyValue(id);
        }
      };

      elComment.style.setProperty(id, localStorage._ipsv || '#9499a0');
      GM_addStyle(
        `bili-comments::part(${labelClass}){color:var(${id})}bili-comments::part(${colorClass}){overflow:hidden;width:0;height:0;border:none;padding:0;visibility:hidden;}`
      );

      // by hook
      if (customElements.get('bili-comment-renderer')) {
        const update = (proto, isFooter, key = 'updated') => {
          const refUpdated = proto[key];
          const baseFunc = function (e) {
            refUpdated.call(this, e);
            this.setAttribute('exportparts', labelClass + ',' + colorClass);
          };
          proto[key] = !isFooter
            ? baseFunc
            : function (e) {
                baseFunc.call(this, e);
                if (isFooter) {
                  this.renderRoot.querySelector(`:host>label[part=${labelClass}]`)
                    ? this.renderRoot.firstElementChild.replaceWith(
                        getExtraEle(this.data.reply_control.location)
                      )
                    : this.renderRoot.prepend(getExtraEle(this.data.reply_control.location));
                }
              };
        };
        const map = {
          'bili-comment-action-buttons-renderer': true,
          'bili-comment-thread-renderer': null,
          'bili-comment-renderer': null,
          'bili-comment-replies-renderer': null,
          'bili-comment-reply-renderer': null
        };
        for (const k in map) {
          update(customElements.get(k).prototype, map[k]);
        }
        return;
      }
      // by watcher
      observeOnce(el.shadowRoot, '#feed', (el) => {
        // handle list
        const handleList = (arr) => {
          arr.forEach((e) => {
            e.setAttribute('exportparts', labelClass + ',' + colorClass);
            observeOnce(e.shadowRoot, '#commentapp', (el) => {
              el.setAttribute('exportparts', labelClass + ',' + colorClass);
              observeOnce(el.shadowRoot, '#footer', (fel) => {
                fel.firstElementChild.setAttribute('exportparts', labelClass + ',' + colorClass);
                fel.firstElementChild.shadowRoot.prepend(
                  getExtraEle(fel.firstElementChild.data.reply_control.location)
                );

                // more to handle
                el.nextElementSibling.firstElementChild.setAttribute(
                  'exportparts',
                  labelClass + ',' + colorClass
                );
                const handleList = (arr) => {
                  arr.forEach((e) => {
                    e.setAttribute('exportparts', labelClass + ',' + colorClass);
                    observeOnce(e.shadowRoot, '#footer', (el) => {
                      el.firstElementChild.setAttribute(
                        'exportparts',
                        labelClass + ',' + colorClass
                      );
                      new MutationObserver(() => {
                        el.firstElementChild.shadowRoot.querySelector(`>label[part=${labelClass}]`)
                          ? el.firstElementChild.shadowRoot.firstElementChild.replaceWith(
                              getExtraEle(el.firstElementChild.data.reply_control.location)
                            )
                          : el.firstElementChild.shadowRoot.prepend(
                              getExtraEle(el.firstElementChild.data.reply_control.location)
                            );
                      }).observe(el.previousElementSibling.children[1].shadowRoot, {
                        childList: true,
                        subtree: true
                      });
                    });
                  });
                };

                new MutationObserver((mutations) => {
                  handleList(
                    mutations
                      .filter((e) => e.addedNodes[0]?.nodeName === 'BILI-COMMENT-REPLY-RENDERER')
                      .flatMap((e) => e.addedNodes[0])
                  );
                }).observe(
                  el.nextElementSibling.firstElementChild.shadowRoot.querySelector(
                    '#expander-contents'
                  ),
                  {
                    childList: true
                  }
                );
                handleList(
                  Array.from(
                    el.nextElementSibling.firstElementChild.shadowRoot.querySelectorAll(
                      '#expander-contents>bili-comment-reply-renderer'
                    )
                  )
                );
              });
            });
          });
        };

        new MutationObserver((mutations) => {
          handleList(
            mutations
              .filter((e) => e.addedNodes[0]?.nodeName === 'BILI-COMMENT-THREAD-RENDERER')
              .flatMap((e) => e.addedNodes[0])
          );
        }).observe(el, {
          childList: true
        });
        handleList(Array.from(el.children));
      });
    });

    return;
  }

  // if comments exist
  new MutationObserver((mutations, ob) => {
    const elReplyList = elComment.querySelector('.reply-list');
    if (elReplyList) {
      ob.disconnect();
      watchReply(elReplyList);
    }
  }).observe(elComment, {
    childList: true,
    subtree: true
  });

  const watchReply = (elReplyList) => {
    // 防重复执行mutation
    let flag;
    const { apiData } =
        elComment.firstElementChild.__vue_app__.config.globalProperties.$store.state,
      id = '--' + Math.floor(Math.random() * 10000),
      labelClass = 'com-ip' + id;

    // 要展示的信息
    const getExtraEle = (text) => {
      if (!text) return '';
      const ele = document.createElement('label');
      ele.className = labelClass;
      ele.innerHTML = `${text}<input type="color"/>`;
      ele.title = '点击调色';
      return ele;
    };

    // 处理子级评论
    // 子评论下有新的子评论,也可能是原评论位置变动
    const handleSubReply = (el) => {
      // console.log("%c子评论", "font-size:16px;color:cyan");
      // const { reply_control } = el.__vueParentComponent.ctx.subReply;
      const { rootReplyId, userId } = el.querySelector('.sub-reply-avatar').dataset;
      const { reply_control } = findReply(
        rootReplyId,
        false,
        userId,
        Array.from(el.parentNode.children).indexOf(el)
      );
      el.querySelector('.sub-reply-info').prepend(getExtraEle(reply_control.location));
    };

    // get by rrid...
    const findReply = (rrid, isRoot, subUid, subIndex) => {
      const rootReply = apiData.replyList.res.data.replies.find((e) => e.rpid_str === rrid);
      return (
        (isRoot
          ? rootReply
          : rootReply?.replies
              .filter((e) => !e.invisible)
              .find((e, i) => e.mid_str === subUid && i === subIndex)) ?? {
          reply_control: {}
        }
      );
    };

    // 观察评论区节点并给新评论增加ip等额外信息展示
    new MutationObserver((mutations) => {
      if (flag) {
        flag = null;
        return;
      }
      mutations
        .filter((e) => e.addedNodes.length > 0)
        .forEach((e) => {
          if (e.type !== 'childList' || e.addedNodes[0].nodeType !== 1) return;
          // 根评论下有新的子评论
          if (e.target === elReplyList && e.addedNodes[0].classList.contains('reply-item')) {
            // const { reply_control } =
            // 	e.addedNodes[0].__vueParentComponent.ctx.reply;
            const { reply_control } = findReply(
              e.addedNodes[0].querySelector('.root-reply-avatar').dataset.rootReplyId,
              true
            );
            e.addedNodes[0]
              .querySelector('.reply-info')
              .prepend(getExtraEle(reply_control.location));
            // 处理根评论下的子评论
            e.addedNodes[0].querySelectorAll('.sub-reply-item').forEach((se) => {
              handleSubReply(se);
            });
            flag = true;

            return;
          }
          if (
            e.target.classList.contains('sub-reply-list') &&
            e.addedNodes[0].classList.contains('sub-reply-item') &&
            !e.addedNodes[0].querySelector('.sub-reply-info > .' + labelClass)
          ) {
            handleSubReply(e.addedNodes[0]);
            flag = true;
          }
        });
    }).observe(elReplyList, {
      attributes: false,
      childList: true,
      subtree: true
    });

    // 通过列表代理color input相关事件
    elReplyList.oninput = (e) => {
      if (e.target.parentNode.className === labelClass) {
        elReplyList.style.setProperty(id, e.target.value);
      }
    };
    elReplyList.onchange = (e) => {
      if (e.target.parentNode.className === labelClass) {
        // elReplyList.style.setProperty(id, e.target.value);
        localStorage._ipsv = e.target.value;
      }
    };
    elReplyList.onclick = (e) => {
      if (e.target.nodeName === 'INPUT' && e.target.parentNode.className === labelClass) {
        e.target.value = elReplyList.style.getPropertyValue(id);
      }
    };

    // 添加css
    elReplyList.style.setProperty(id, localStorage._ipsv || '#9499A0');
    GM_addStyle(
      `.${labelClass}{margin-right:10px;color:var(${id})}.${labelClass}>input{overflow:hidden;width:0;height:0;border:none;visibility:hidden;}`
    );
  };
})();