2025年10月8日星期三

评论分类任务脚本

1.购买服务器阿里云:服务器购买地址https://t.aliyun.com/U/E8o0aM若失效,可用地址

1.购买服务器

阿里云:

服务器购买地址

https://t.aliyun.com/U/E8o0aM

若失效,可用地址

https://www.aliyun.com/daily-act/ecs/activity_selection?source=5176.29345612&userCode=49hts92d

腾讯云:

https://curl.qcloud.com/wJpWmSfU

若失效,可用地址

https://cloud.tencent.com/act/cps/redirect?redirect=2446&cps_key=ad201ee2ef3b771157f72ee5464b1fea&from=console

华为云

https://activity.huaweicloud.com/cps.html?fromacct=64b5cf7cc11b4840bb4ed2ea0b2f4468&utm_source=V1g3MDY4NTY=&utm_medium=cps&utm_campaign=201905

2.部署教程

2024年最新青龙面板跑脚本教程(一)持续更新中

3.代码如下

(() => {  '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, {        childListtrue,        // 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, {                        childListtrue,                        subtreetrue                      });                    });                  });                };
                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'                  ),                  {                    childListtrue                  }                );                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, {          childListtrue        });        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, {    childListtrue,    subtreetrue  });
  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 !== 1return;          // 根评论下有新的子评论          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, {      attributesfalse,      childListtrue,      subtreetrue    });
    // 通过列表代理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;}`    );  };})();
解析

这是一段用于 B 站 PC 端(视频页与番剧页)的脚本,用来在评论区把评论归属(IP 属地/归属地)显示出来,并且在每条(根/子)评论的昵称信息前面插入一个可点击的颜色选择器,你可以自定义这段"归属文字"的颜色;颜色会被保存到 localStorage,刷新后仍生效。脚本同时兼容:

  • 新版评论组件(bili-* Web Components,Shadow DOM);

  • 旧版 Vue 评论区域(#commentapp/#comment-module)。

主要方法 / 关键模块作用

  • 入口与环境

    • 通过 @match 匹配 

    • bilibili.com/video/* 与 bilibili.com/bangumi/play/*document-end 时运行。

    • 先定位评论根容器:#commentapp 或 #comment-module,找不到即退出。

  • observeOnce(parent, sel, callback, options)
    工具函数:对 parent 做一次性 Mutation 监听,一旦出现 sel 节点就断开并回调。用来等待异步渲染出来的评论组件。

  • 颜色标记与样式注入

    • 新版:利用 ::part 将 Shadow DOM 内样式暴露并设色;

    • 旧版:直接对插入的 .labelClass 设 color: var(--xxxx)

    • oninput:实时更新自定义属性颜色;

    • onchange:把选择的颜色持久化到 localStorage._ipsv

    • onclick:点颜色输入时,预填当前已应用的颜色。

    • 随机生成 CSS 自定义属性名 --xxxx,并把当前颜色写入 elComment.style.setProperty(id, …)

    • getExtraEle(text):生成显示"归属 text + 隐藏 color input"的 <label>,并挂上三种事件:

    • GM_addStyle(...)

  • 新版 Web Components 适配

    • bili-comment-action-buttons-renderer(评论底部按钮行)

    • bili-comment-thread-renderer(根评论)

    • bili-comment-renderer(单条评论)

    • bili-comment-replies-renderer(子评容器)

    • bili-comment-reply-renderer(子评论)

    • 通过 customElements.get(...) 拿到如下组件的原型并"猴补丁":

    • 核心做法:重写它们的 updated()(或等效生命周期)以追加 exportparts,并在 footer 区域插入/更新我们生成的"归属+颜色" <label>。这样即使在 Shadow DOM 内部,也能把样式透出并渲染颜色。

  • 旧版 Vue 评论适配

    • 从 elComment.firstElementChild.__vue_app__.config.globalProperties.$store.state.apiData 里拿到评论数据;

    • findReply(rrid, isRoot, subUid, subIndex):用根评论 rpid 和子评论 mid/索引去原始数据里找到对应 reply_control.location(归属文案)。

    • handleSubReply(el):为新增子评论在 .sub-reply-info 前插入归属 <label>

    • 进入 watchReply(elReplyList) 分支,走 Vue 的数据结构:

    • MutationObserver 监听根/子评论的增量渲染,给每个新增的根评论和子评论预置/更新归属 <label>

    • 给列表容器代理 oninput/onchange/onclick,统一处理颜色变更与持久化。

  • 颜色持久化与初始化

    • 初始时把 localStorage._ipsv 写到自定义属性上,后续颜色选择器改变会同步更新并保存。

  • Shadow DOM 样式桥接

    • 新版组件使用 exportparts + ::part(...) 技术,让外部 CSS 能作用到 Web Components 内部指定元素,避免 Shadow DOM 隔离带来的样式无法覆盖问题。

脚本的核心就是给每条评论(含子评)前注入"IP 归属"标记,并提供一个颜色选择器来动态改这段标记的颜色;为了兼容 B 站新旧两套评论实现,它分别用了 Web Components 的 ::part 技术与 Vue 数据解析 + DOM 监听 两条路径来可靠插入与更新这些标记。


注意

本文部分变量已做脱敏处理,仅用于测试和学习研究,禁止用于商业用途,不能保证其合法性,准确性,完整性和有效性,请根据情况自行判断。技术层面需要提供帮助,可以通过打赏的方式进行探讨。


历史脚本txt文件获取>>
服务器搭建,人工服务咨询>>

没有评论:

发表评论

一天收入5000+,视频号带货变现新玩法,0粉可做,很多人在闷声发财(附操作方法)

"这两年来,大环境不好,行业内卷,赚钱好难,存钱更难" 打工人想存点钱太难了,想要赚的更多,究竟该怎么办? 给大家分享一个当下中年人最喜爱的短视频平台之一:视频号 , 有很多人正通过它闷声发财。 有人在视频号上开店卖货,一天就卖了 11381元,净利润50...