Internet Content Filterer

block users' posts and comments whom you blocked

目前為 2024-08-13 提交的版本,檢視 最新版本

function _classPrivateMethodInitSpec(e, a) { _checkPrivateRedeclaration(e, a), a.add(e); }
function _applyDecoratedDescriptor(i, e, r, n, l) { var a = {}; return Object.keys(n).forEach(function (i) { a[i] = n[i]; }), a.enumerable = !!a.enumerable, a.configurable = !!a.configurable, ("value" in a || a.initializer) && (a.writable = !0), a = r.slice().reverse().reduce(function (r, n) { return n(i, e, r) || r; }, a), l && void 0 !== a.initializer && (a.value = a.initializer ? a.initializer.call(l) : void 0, a.initializer = void 0), void 0 === a.initializer ? (Object.defineProperty(i, e, a), null) : a; }
function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; }
function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == typeof i ? i : i + ""; }
function _toPrimitive(t, r) { if ("object" != typeof t || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != typeof i) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); }
function _classPrivateFieldInitSpec(e, t, a) { _checkPrivateRedeclaration(e, t), t.set(e, a); }
function _checkPrivateRedeclaration(e, t) { if (t.has(e)) throw new TypeError("Cannot initialize the same private elements twice on an object"); }
function _classPrivateFieldGet(s, a) { return s.get(_assertClassBrand(s, a)); }
function _classPrivateFieldSet(s, a, r) { return s.set(_assertClassBrand(s, a), r), r; }
function _assertClassBrand(e, t, n) { if ("function" == typeof e ? e === t : e.has(t)) return arguments.length < 3 ? t : n; throw new TypeError("Private element is not present on this object"); }
// ==UserScript==
// @name              Internet Content Filterer
// @name:zh           网络内容过滤器
// @namespace         Violentmonkey Scripts
// @match             *://*.weibo.com/*
// @match             *://*.weibo.cn/*
// @match             *://weibo.com/*
// @match             *://m.hupu.com/*
// @match             *://tieba.baidu.com/*
// @match             *://www.zhihu.com/search*
// @exclude           *://weibo.com/tv*
// @grant             GM.getValue
// @grant             GM.setValue
// @grant             GM.deleteValue
// @version           3.8
// @author            fbz
// @description       block users' posts and comments whom you blocked
// @description:zh    屏蔽特定用户的帖子和评论
// @run-at            document-start
// @license           MIT
// @require           https://unpkg.com/[email protected]/dist/ajaxhook.js
// ==/UserScript==
/* eslint-disable max-classes-per-file,no-param-reassign,no-console */
/* jshint esversion: 6 */
// eslint-disable-next-line func-names
(async function (_dec, _dec2, _dec3, _class, _WeiboFilter, _apiBlackList, _dec4, _class2, _HupuFilter, _dec5, _dec6, _dec7, _class3, _TiebaFilter, _DomainStore) {
  /* 添加样式 */

  const BlockButtonClass = 'block-button';
  const cssByCls = cls => `.${cls}`;
  const css = `
      #add_ngList_btn {
        position: fixed;
        bottom: 2rem;
        left: 1rem;
        width: 2rem;
        height: 2rem;
        border-radius: 50%;
        border: 1px solid rgba(0, 0, 0, 0.5);
        background: white;
        display: flex;
        align-items: center;
        justify-content: center;
        cursor: pointer !important;
        z-index: 100;
      }

      #add_ngList_btn::before {
        content: '';
        position: absolute;
        width: 16px;
        height: 2px;
        background: rgba(0, 0, 0, 0.5);
        top: calc(50% - 1px);
        left: calc(50% - 8px);
      }

      #add_ngList_btn::after {
        content: '';
        position: absolute;
        height: 16px;
        width: 2px;
        background: rgba(0, 0, 0, 0.5);
        top: calc(50% - 8px);
        left: calc(50% - 1px);
      }

      .my-dialog__wrapper {
        position: fixed;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        overflow: auto;
        margin: 0;
        z-index: 10000;
        background: rgba(0, 0, 0, 0.3);
        display: none;
      }

      .my-dialog {
        position: relative;
        background: #FFFFFF;
        border-radius: 2px;
        box-shadow: 0 1px 3px rgb(0 0 0 / 30%);
        box-sizing: border-box;
        width: 50%;
        transform: none;
        left: 0;
        margin: 0 auto;
      }

      .my-dialog .my-dialog__header {
        border-bottom: 1px solid #e4e4e4;
        padding: 14px 16px 10px 16px;
      }

      .my-dialog__title {
        line-height: 24px;
        font-size: 18px;
        color: #303133;
      }

      .my-dialog__headerbtn {
        position: absolute;
        top: 20px;
        right: 20px;
        padding: 0;
        background: transparent;
        border: none;
        outline: none;
        cursor: pointer;
        font-size: 16px;
        width: 12px;
        height: 12px;
        transform: rotateZ(45deg);
      }

      .my-dialog .my-dialog__header .my-dialog__headerbtn {
        right: 16px;
        top: 16px;
      }

      .my-dialog__headerbtn .my-dialog__close::before {
        content: '';
        position: absolute;
        width: 12px;
        height: 1.5px;
        background: #909399;
        top: calc(50% - 0.75px);
        left: calc(50% - 6px);
        border-radius: 2px;
      }

      .my-dialog__headerbtn:hover .my-dialog__close::before {
        background: #1890ff;
      }

      .my-dialog__headerbtn .my-dialog__close::after {
        content: '';
        position: absolute;
        height: 12px;
        width: 1.5px;
        background: #909399;
        top: calc(50% - 6px);
        left: calc(50% - 0.75px);
        border-radius: 2px;
      }

      .my-dialog__headerbtn:hover .my-dialog__close::after {
        background: #1890ff;
      }

      .my-dialog__body {
        padding: 30px 20px;
        color: #606266;
        font-size: 14px;
        word-break: break-all;
      }

      .my-dialog__footer {
        padding: 20px;
        padding-top: 10px;
        text-align: right;
        box-sizing: border-box;
      }

      .my-dialog .my-dialog__footer {
        padding: 0px 16px 24px 16px;
        margin-top: 40px;
      }

      #ngList {
        display: flex;
        flex-wrap: wrap;
        justify-content: flex-start;
        max-height: 480px;
        overflow-y: scroll;
      }

      ${cssByCls(BlockButtonClass)} {
        cursor: pointer;
        height: 12px;
        width: 12px;
        margin-left: 1px;
        float: inherit;
        background: white;
        border-width: 0;
        padding: 0;
        line-height:0px;
        transition: transform 300ms cubic-bezier(0.4, 0, 0.2, 1);
        transformOrigin: '50% bottom;
      }

      ${cssByCls(BlockButtonClass)}:hover {
        transform: translateY(0) scale(1.5);
      }

      .close-icon {
        width: 12px;
        height: 12px;
        border-radius: 50%;
        display: inline-block;
        position: relative;
        transform: rotateZ(45deg);
        margin-left: 8px;
        cursor: pointer;
      }

      .close-icon:hover {
        background: #409eff;
      }

      .close-icon::before {
        content: '';
        position: absolute;
        width: 8px;
        height: 2px;
        background: #409eff;
        top: calc(50% - 1px);
        left: calc(50% - 4px);
        border-radius: 2px;
      }

      .close-icon:hover::before {
        background: #fff;
      }

      .close-icon::after {
        content: '';
        position: absolute;
        height: 8px;
        width: 2px;
        background: #409eff;
        top: calc(50% - 4px);
        left: calc(50% - 1px);
        border-radius: 2px;
      }

      .close-icon:hover::after {
        background: #fff;
      }

      .ng_item {
        background-color: #ecf5ff;
        display: inline-flex;
        align-items: center;
        padding: 0 10px;
        font-size: 12px;
        color: #409eff;
        border: 1px solid #d9ecff;
        border-radius: 4px;
        box-sizing: border-box;
        white-space: nowrap;
        height: 28px;
        line-height: 26px;
        margin-left: 12px;
        margin-top: 8px;
      }


      .input_container {
        display: flex;
        align-items: center;
        margin-bottom: 12px;
      }

      .el-input {
        position: relative;
        font-size: 14px;
        display: inline-block;
        width: 100%;
      }

      .el-input__inner {
        -webkit-appearance: none;
        background-color: #fff;
        background-image: none;
        border-radius: 4px;
        border: 1px solid #dcdfe6;
        box-sizing: border-box;
        color: #606266;
        display: inline-block;
        font-size: inherit;
        height: 40px;
        line-height: 40px;
        outline: none;
        padding: 0 15px;
        transition: border-color .2s cubic-bezier(.645, .045, .355, 1);
        width: 100%;
        cursor: pointer;
        font-family: inherit;
      }

      .el-button {
        display: inline-block;
        line-height: 1;
        white-space: nowrap;
        cursor: pointer;
        background: #fff;
        border: 1px solid #dcdfe6;
        color: #606266;
        -webkit-appearance: none;
        text-align: center;
        box-sizing: border-box;
        outline: none;
        margin: 0;
        transition: .1s;
        font-weight: 500;
        -moz-user-select: none;
        -webkit-user-select: none;
        -ms-user-select: none;
        padding: 12px 20px;
        font-size: 14px;
        border-radius: 4px;
      }

      .el-button:focus,
      .el-button:hover {
        color: #409eff;
        border-color: #c6e2ff;
        background-color: #ecf5ff;
      }

      .el-button:active {
        color: #3a8ee6;
        border-color: #3a8ee6;
        outline: none;
      }

      .input_container .el-input {
        margin-right: 12px;
      }

      .tips {
        margin-top: 24px;
        font-size: 12px;
        color: #F56C6C;
      }
    `;

  /* 按钮模板 */
  const btnTemp = `
      <span class="Configs_alink_2Yg6L" yawf-component-tag="woo-box">
        <div class="woo-box-flex woo-box-alignCenter woo-pop-item-main" role="button" tabindex="0" data-focus-visible="true" yawf-component-tag="woo-pop-item woo-box">
          屏蔽词设置
        </div>
      </span>
    `;
  const svgIcon = `
      <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
          <circle cx="12" cy="12" r="10" />
          <line x1="4" y1="4" x2="20" y2="20" />
      </svg>
    `;

  /* 添加样式 */
  function addStyle(cssStyle) {
    if (!cssStyle) return;
    const head = document.querySelector('head');
    const style = document.createElement('style');
    style.type = 'text/css';
    style.innerHTML = cssStyle;
    head.appendChild(style);
  }
  const defaultNameFn = elem => elem.innerText || elem.getAttribute('aria-label');
  function createButtonElement(name) {
    // eslint-disable-next-line
    name = name.trim().replace(/^@/, '').replace(':', '');
    const wrapper = document.createElement('div');
    wrapper.innerHTML = svgIcon;
    wrapper.className = BlockButtonClass;
    wrapper.setAttribute('user-name', name);
    return wrapper;
  }
  function newListAndUser(list, user) {
    return {
      listSelector: list,
      userSelector: user
    };
  }
  function injectButton({
    listSelector,
    userSelector,
    elementButtonFunc,
    doc = document,
    subListSelector,
    subUserSelector,
    nameFn = defaultNameFn
  }) {
    const list = doc.querySelectorAll(listSelector);
    list.forEach(element => {
      if (subListSelector && subUserSelector) {
        injectButton({
          listSelector: subListSelector,
          userSelector: subUserSelector,
          doc: element
        });
      }
      if (elementButtonFunc) elementButtonFunc(element);
      let user = element.querySelector(userSelector);
      if (!user) {
        return;
      }
      if (element.querySelector(`${cssByCls(BlockButtonClass)}`)) {
        return;
      }
      const btn = createButtonElement(nameFn(user));
      while (user.parentNode) {
        if (user.parentNode.childElementCount > 1) {
          user.parentNode.appendChild(btn);
          break;
        }
        user = user.parentNode;
      }
    });
  }
  var _filters = /*#__PURE__*/new WeakMap();
  var _proxyOpt = /*#__PURE__*/new WeakMap();
  var _registered = /*#__PURE__*/new WeakMap();
  class WebsiteFilter {
    /**
     * @param {Array.<{listSelector: String, userSelector:String}>} selectors
     * @param {Document | Element} root
     * @param nameFn
     */
    removeElements(selectors, root = document, nameFn = defaultNameFn) {
      if (selectors.length < 1) {
        return;
      }
      const selector = selectors[0];
      const list = root.querySelectorAll(selector.listSelector);
      list.forEach(element => {
        const user = element.querySelector(selector.userSelector);
        if (!user) {
          return;
        }
        const poster = nameFn(user);
        if (this.inBlackList(poster)) {
          element.parentNode.removeChild(element);
        } else {
          this.removeElements(selectors.slice(1), element);
        }
      });
    }
    inBlackList(name) {
      return this.store.hasUser(name);
    }

    /**
     *
     * @param {DomainStore} store
     */
    constructor(store) {
      _classPrivateFieldInitSpec(this, _filters, []);
      _classPrivateFieldInitSpec(this, _proxyOpt, {});
      _classPrivateFieldInitSpec(this, _registered, false);
      /**
       * @type DomainStore
       */
      _defineProperty(this, "store", void 0);
      this.store = store;
      this.addHook(...Object.getOwnPropertyNames(Object.getPrototypeOf(this)).filter(name => Object.getPrototypeOf(this)[name].hookMeta).reduce((hooks, name) => {
        const fn = this[name].bind(this);
        fn.hookMeta = this[name].hookMeta;
        hooks.push(fn);
        return hooks;
      }, []));
      _classPrivateFieldSet(_filters, this, Object.getOwnPropertyNames(Object.getPrototypeOf(this)).reduce((fns, name) => {
        const method = this[name];
        if (method.filterMeta) {
          const fn = method.bind(this);
          fn.hookMeta = this[name].hookMeta;
          fns.push(fn);
        }
        return fns;
      }, []));
    }
    addHook(...hooks) {
      _classPrivateFieldSet(_proxyOpt, this, {
        ..._classPrivateFieldGet(_proxyOpt, this),
        onError: (err, handler) => {
          console.log(err);
          handler.next(err);
        },
        onResponse: this.handleResponseFunc(...hooks)
      });
    }
    handleResponseFunc(...hooks) {
      return (response, handler) => {
        const {
          url
        } = response.config;
        if (typeof url !== 'string') {
          handler.next(response);
          return;
        }
        for (const hooker of hooks) {
          const pattern = hooker.hookMeta;
          if (url.includes(pattern)) {
            let res = response.response;
            if (res) {
              try {
                res = JSON.parse(res);
                const ret = hooker(url, res);
                res = ret || res;
                response.response = JSON.stringify(res);
              } catch (err) {
                /* empty */
              }
            }
            break;
          }
        }
        handler.next(response);
      };
    }
    withProxyOpt(o) {
      _classPrivateFieldSet(_proxyOpt, this, {
        ..._classPrivateFieldGet(_proxyOpt, this),
        ...o
      });
    }
    render() {
      if (!_classPrivateFieldGet(_registered, this)) {
        const opt = _classPrivateFieldGet(_proxyOpt, this);
        ah.proxy(opt, unsafeWindow);
        _classPrivateFieldSet(_registered, this, true);
      }
      _classPrivateFieldGet(_filters, this).forEach(f => {
        f();
      });
    }
  }
  function filterFunc(target, name, descriptor) {
    const fn = descriptor.value;
    fn.filterMeta = true;
    return descriptor;
  }
  function hookFunc(pattern) {
    return (target, name, descriptor) => {
      descriptor.value.hookMeta = pattern;
      return descriptor;
    };
  }
  let WeiboFilter = (_dec = hookFunc('/friendstimeline'), _dec2 = hookFunc('/searchBand'), _dec3 = hookFunc('/buildComments'), (_class = (_apiBlackList = /*#__PURE__*/new WeakMap(), (_WeiboFilter = class WeiboFilter extends WebsiteFilter {
    // 接口黑名单

    constructor(store) {
      super(store);
      _classPrivateFieldInitSpec(this, _apiBlackList, ['/female_version.mp3', '/intake/v2/rum/events']);
      this.withProxyOpt({
        onRequest: (config, handler) => {
          if (!_classPrivateFieldGet(_apiBlackList, this).some(item => config.url.toString().includes(item))) {
            // 不在接口黑名单里的请求才放行
            handler.next(config);
          }
        }
      });
      this.hideTrends();
    }
    async filterSearchResults() {
      const selector = {
        cards: 'div.card-wrap',
        cardUser: 'div.info a.name',
        retweetedCards: 'div.card-wrap div.card-comment',
        retweetedCardUser: 'div.con a.name',
        comments: 'div.card-review',
        commentUser: 'div.content a.name'
      };
      injectButton({
        listSelector: selector.cards,
        userSelector: selector.cardUser,
        elementButtonFunc: WeiboFilter.createCommentButton
      });
      injectButton({
        listSelector: selector.retweetedCards,
        userSelector: selector.retweetedCardUser
      });
      this.removeElements([{
        listSelector: selector.cards,
        userSelector: selector.cardUser
      }, {
        listSelector: selector.comments,
        userSelector: selector.commentUser
      }]);
      this.removeElements([{
        listSelector: selector.cards,
        userSelector: selector.retweetedCardUser
      }]);
    }
    async filterFeeds() {
      const selector = {
        cardListSelector: 'div.vue-recycle-scroller__item-view',
        cardUserSelector: 'div.Feed_body_3R0rO a.ALink_default_2ibt1',
        commentListSelector: 'div.wbpro-list',
        commentUserSelector: 'div.con1.woo-box-item-flex div.text a.ALink_default_2ibt1',
        replyListSelector: 'div.item2',
        replyUserSelector: 'div.con2 a.ALink_default_2ibt1'
      };
      this.removeElements([newListAndUser(selector.cardListSelector, selector.cardUserSelector), newListAndUser(selector.commentListSelector, selector.commentUserSelector), newListAndUser(selector.replyListSelector, selector.replyUserSelector)]);
      injectButton({
        listSelector: selector.cardListSelector,
        userSelector: selector.cardUserSelector
      });
      const feedWrappers = document.querySelectorAll(selector.cardListSelector);
      feedWrappers.forEach(feed => {
        injectButton({
          listSelector: selector.commentListSelector,
          userSelector: selector.commentUserSelector,
          doc: feed,
          subListSelector: selector.replyListSelector,
          subUserSelector: selector.replyUserSelector
        });
      });
    }
    async filterReplies() {
      const reply = document.querySelector('div.ReplyModal_scroll3_2kADQ');
      if (!reply) {
        return;
      }
      const selector = {
        commentListSelector: 'div.wbpro-list',
        commentUserSelector: 'div.con1.woo-box-item-flex a.ALink_default_2ibt1',
        root: reply,
        replyListSelector: 'div.vue-recycle-scroller__item-view',
        replyUserSelector: 'div.con2 a.ALink_default_2ibt1'
      };
      this.removeElements([{
        listSelector: selector.commentListSelector,
        userSelector: selector.commentUserSelector
      }, {
        listSelector: selector.replyListSelector,
        userSelector: selector.replyUserSelector
      }], reply);
      injectButton({
        listSelector: selector.commentListSelector,
        userSelector: selector.commentUserSelector,
        doc: reply,
        subListSelector: selector.replyListSelector,
        subUserSelector: selector.replyUserSelector
      });
    }
    static async createCommentButton(card) {
      injectButton({
        listSelector: 'div.card-together div.list div.card-review',
        userSelector: 'a.name',
        doc: card
      });
    }
    async filterDetailComments() {
      const selector = {
        commentListSelector: 'div.vue-recycle-scroller__item-view',
        commentUserSelector: 'div.con1.woo-box-item-flex div.text a.ALink_default_2ibt1',
        replyListSelector: 'div.item2',
        replyUserSelector: 'div.con2 a.ALink_default_2ibt1'
      };
      this.removeElements([{
        listSelector: selector.commentListSelector,
        userSelector: selector.commentUserSelector
      }, {
        listSelector: selector.replyListSelector,
        userSelector: selector.replyUserSelector
      }]);
      injectButton({
        listSelector: selector.commentListSelector,
        userSelector: selector.commentUserSelector,
        subListSelector: selector.replyListSelector,
        subUserSelector: selector.replyUserSelector
      });
    }
    async hideTrends() {
      let trend = document.querySelector('div.main-side');
      if (trend) trend.style.display = 'none';
      if (trend = document.querySelector('div.Main_side_i7Vti')) trend.style.display = 'none';
    }
    async createRetweetButton() {
      this.removeElements([newListAndUser('div.vue-recycle-scroller__item-view', 'div.retweet.Feed_retweet_JqZJb a.ALink_default_2ibt1')]);
      injectButton({
        listSelector: 'div.retweet.Feed_retweet_JqZJb',
        userSelector: 'a.ALink_default_2ibt1'
      });
    }

    /**
     *@param {Array.<{user: {screen_name: string}, text: string}>} comments
     * */
    filterComments(comments) {
      return comments.reduce((filtered, comment) => {
        var _comment$user;
        const myText = comment.text || '';
        const ngWordInMyText = this.inBlackList(myText) || ((_comment$user = comment.user) === null || _comment$user === void 0 ? void 0 : _comment$user.screen_name) && this.inBlackList(comment.user.screen_name);
        if (!ngWordInMyText) {
          filtered.push(comment);
        }
        return filtered;
      }, []);
    }

    /**
     * @typedef Status
     * @type {object}
     * @property {?User} user
     * @property {string} text
     * @property {?Status} retweeted_status
     *
     * @typedef User
     * @type {object}
     * @property {boolean} following
     * @property {string} screen_name
     *
     *
     * @param {Status[]} statuses
     * */
    filterStatuses(statuses) {
      return statuses.reduce((acc, cur) => {
        if (cur.user.following) {
          var _cur$user;
          const myText = cur.text || '';
          const ngWordInMyText = this.inBlackList(myText) || ((_cur$user = cur.user) === null || _cur$user === void 0 ? void 0 : _cur$user.screen_name) && this.inBlackList(cur.user.screen_name);
          if (!ngWordInMyText) {
            if (cur.retweeted_status) {
              var _cur$retweeted_status;
              const oriText = cur.retweeted_status.text || '';
              const ngWordInOriText = this.inBlackList(oriText) || ((_cur$retweeted_status = cur.retweeted_status) === null || _cur$retweeted_status === void 0 || (_cur$retweeted_status = _cur$retweeted_status.user) === null || _cur$retweeted_status === void 0 ? void 0 : _cur$retweeted_status.screen_name) && this.inBlackList(cur.retweeted_status.user.screen_name);
              if (ngWordInOriText) return acc;
            }
            acc.push(cur);
          }
        }
        return acc;
      }, []);
    }
    filterSearchBand(searchBands) {
      return searchBands.reduce((acc, cur) => {
        if (!this.inBlackList(cur.word)) {
          acc.push(cur);
        }
        return acc;
      }, []);
    }
    onFriendTimeline(url, res) {
      if (url.includes('m.weibo.cn')) {
        res.data.statuses = this.filterStatuses(res.data.statuses);
      } else {
        res.statuses = this.filterStatuses(res.statuses);
        console.log(`filtered url: ${url}, users: ${res.statuses.reduce((pre, cur) => {
          pre.push(cur.user.screen_name);
          return pre;
        }, [])}`);
      }
    }
    onSearchBand(url, res) {
      res.data.realtime = this.filterSearchBand(res.data.realtime);
    }
    hook_buildComments(url, res) {
      res.data = this.filterComments(res.data);
    }
  }, _defineProperty(_WeiboFilter, "host", 'weibo.com'), _WeiboFilter)), (_applyDecoratedDescriptor(_class.prototype, "filterSearchResults", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterSearchResults"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterFeeds", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterFeeds"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterReplies", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterReplies"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "filterDetailComments", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "filterDetailComments"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hideTrends", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "hideTrends"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "createRetweetButton", [filterFunc], Object.getOwnPropertyDescriptor(_class.prototype, "createRetweetButton"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onFriendTimeline", [_dec], Object.getOwnPropertyDescriptor(_class.prototype, "onFriendTimeline"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "onSearchBand", [_dec2], Object.getOwnPropertyDescriptor(_class.prototype, "onSearchBand"), _class.prototype), _applyDecoratedDescriptor(_class.prototype, "hook_buildComments", [_dec3], Object.getOwnPropertyDescriptor(_class.prototype, "hook_buildComments"), _class.prototype)), _class));
  let HupuFilter = (_dec4 = hookFunc('/bbs-reply-detail'), (_class2 = (_HupuFilter = class HupuFilter extends WebsiteFilter {
    async filterHupuComments() {
      const selector = {
        commentsSelector: 'div.index_discuss-card__Nd4MK.hp-m-discuss-card.post-card',
        userSelector: 'p.discuss-card__user span.discuss-card__username',
        repliesSelector: 'div.index_discuss-card__Nd4MK.hp-m-discuss-card.discuss-card',
        quotesSelector: 'div.discuss-card__quote-container-quote',
        quoteUserSelector: 'div.discuss-card__quote-container-quote span.discuss-card__quote-container-discusser'
      };
      this.removeElements([{
        listSelector: selector.commentsSelector,
        userSelector: selector.userSelector
      }]);
      injectButton({
        listSelector: selector.commentsSelector,
        userSelector: selector.userSelector
      });
      this.removeElements([newListAndUser(selector.commentsSelector, selector.quoteUserSelector)]);
      injectButton({
        listSelector: selector.quotesSelector,
        userSelector: selector.quoteUserSelector
      });
      this.removeElements([newListAndUser(selector.repliesSelector, selector.userSelector)]);
      injectButton({
        listSelector: selector.repliesSelector,
        userSelector: selector.userSelector
      });
    }
    hupuRouter(url, res) {
      function filterHupuReplies(replies) {
        return replies.reduce((filtered, reply) => {
          const user = reply.user.username;
          if (!this.inBlackList(user)) {
            filtered.push(reply);
          }
          return filtered;
        }, []);
      }
      res.data.replies = filterHupuReplies(res.data.replies);
    }
  }, _defineProperty(_HupuFilter, "host", 'hupu.com'), _HupuFilter), (_applyDecoratedDescriptor(_class2.prototype, "filterHupuComments", [filterFunc], Object.getOwnPropertyDescriptor(_class2.prototype, "filterHupuComments"), _class2.prototype), _applyDecoratedDescriptor(_class2.prototype, "hupuRouter", [_dec4], Object.getOwnPropertyDescriptor(_class2.prototype, "hupuRouter"), _class2.prototype)), _class2));
  let TiebaFilter = (_dec5 = hookFunc('/p/totalComment'), _dec6 = hookFunc('/topicList'), _dec7 = hookFunc('/suggestion'), (_class3 = (_TiebaFilter = class TiebaFilter extends WebsiteFilter {
    constructor(store) {
      super(store);
      this.hideLoginPopup();
    }
    async hideLoginPopup() {
      let login = null;
      while (!login) {
        login = document.querySelector('div.tieba-custom-pass-login');
        if (login) {
          const ob = new MutationObserver(mutations => {
            const record = mutations[0];
            if (record.target.style.display === 'block') {
              login.style.display = 'none';
            }
          });
          ob.observe(login, {
            attributeFilter: ['style']
          });
          break;
        }
        await sleep(10);
      }
    }
    async filterTiebaThreadListComments() {
      const selector = {
        threadsSelector: 'li.j_thread_list.clearfix.thread_item_box',
        threadUserSelector: 'span.tb_icon_author'
      };
      const fn = user => {
        var _user$getAttribute;
        return (_user$getAttribute = user.getAttribute('title')) === null || _user$getAttribute === void 0 ? void 0 : _user$getAttribute.replace('主题作者: ', '');
      };
      this.removeElements([newListAndUser(selector.threadsSelector, selector.threadUserSelector)], document, fn);
      injectButton({
        listSelector: selector.threadsSelector,
        userSelector: selector.threadUserSelector,
        nameFn: fn
      });
    }
    async filterTiebaThreadComments() {
      const selector = {
        commentsSelector: 'div.l_post.l_post_bright.j_l_post.clearfix',
        commentUserSelector: 'div.d_author li.d_name a.p_author_name.j_user_card',
        repliesSelector: 'div.j_lzl_c_b_a.core_reply_content li.lzl_single_post.j_lzl_s_p',
        replyUserSelector: 'div.lzl_cnt a.at.j_user_card'
      };
      this.removeElements([newListAndUser(selector.commentsSelector, selector.commentUserSelector), newListAndUser(selector.repliesSelector, selector.replyUserSelector)]);
      injectButton({
        listSelector: selector.commentsSelector,
        userSelector: selector.commentUserSelector,
        subListSelector: selector.repliesSelector,
        subUserSelector: selector.replyUserSelector
      });
    }
    async hideRightBar() {
      const rightBar = document.querySelector('div.right_section.right_bright');
      if (!rightBar) {
        return;
      }
      rightBar.parentNode.removeChild(rightBar);
    }
    hookReplies(url, res) {
      res.data = filterTiebaReplies(res.data);
    }

    /**
     * @typedef Topic
     * @type {object}
     * @property {string[]} topic_list
     *
     * @param {string} url
     * @param {object} res
     * @param {{user_his_topic: Topic, sug_topic: Topic, bang_topic: Topic, manual_topic: Topic}} res.data
     *
     * */
    hookTopicList(url, res) {
      res.data.user_his_topic.topic_list = [];
      res.data.sug_topic.topic_list = [];
      res.data.bang_topic.topic_list = [];
      res.data.manual_topic.topic_list = [];
    }

    /**
     * @param {string} url
     * @param {{hottopic_list: object}} res
     * */
    hookSuggestion(url, res) {
      res.hottopic_list.search_data = [];
    }
  }, _defineProperty(_TiebaFilter, "host", 'tieba.baidu.com'), _TiebaFilter), (_applyDecoratedDescriptor(_class3.prototype, "hideLoginPopup", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "hideLoginPopup"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "filterTiebaThreadListComments", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "filterTiebaThreadListComments"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "filterTiebaThreadComments", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "filterTiebaThreadComments"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hideRightBar", [filterFunc], Object.getOwnPropertyDescriptor(_class3.prototype, "hideRightBar"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookReplies", [_dec5], Object.getOwnPropertyDescriptor(_class3.prototype, "hookReplies"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookTopicList", [_dec6], Object.getOwnPropertyDescriptor(_class3.prototype, "hookTopicList"), _class3.prototype), _applyDecoratedDescriptor(_class3.prototype, "hookSuggestion", [_dec7], Object.getOwnPropertyDescriptor(_class3.prototype, "hookSuggestion"), _class3.prototype)), _class3));
  /**
   * @param {Object} data
   * @property {{user_nickname_v2: string}[]} user_list
   * @property {{comment_info: Object[]}[]} comment_list
   * @property {{show_nickname: string}} comment_info
   * */
  function filterTiebaReplies(data) {
    const userMap = new Map();
    for (const user of Object.values(data.user_list)) {
      userMap.set(user.user_nickname_v2, true);
    }
    const comments = data.comment_list;
    data.comment_list = Object.entries(comments).reduce((filtered, [postId, comment]) => {
      let replies = comment.comment_info;
      replies = replies.filter(reply => !this.inBlackList(reply.show_nickname));
      comment.comment_list_num = replies.length;
      comment.comment_info = replies;
      if (comment.comment_list_num > 0) {
        filtered = {
          ...filtered,
          [postId]: comment
        };
      }
      return filtered;
    }, {});
    return data;
  }
  var _blackUsers = /*#__PURE__*/new WeakMap();
  var _DomainStore_brand = /*#__PURE__*/new WeakSet();
  class DomainStore {
    static async init() {
      let list = await this.loadBlackList();
      return new DomainStore(list);
    }

    /**
     * @type {Map.<string, boolean>}
     * */

    get userList() {
      return _classPrivateFieldGet(_blackUsers, this).keys();
    }
    hasUser(name) {
      return _classPrivateFieldGet(_blackUsers, this).has(name);
    }
    addUser(name) {
      _classPrivateFieldGet(_blackUsers, this).set(name, true);
      _assertClassBrand(_DomainStore_brand, this, _flush).call(this);
    }
    removeUser(name) {
      _classPrivateFieldGet(_blackUsers, this).delete(name);
      _assertClassBrand(_DomainStore_brand, this, _flush).call(this);
    }
    /**
     * @param {string[]} list
     * */
    constructor(list) {
      _classPrivateMethodInitSpec(this, _DomainStore_brand);
      _classPrivateFieldInitSpec(this, _blackUsers, void 0);
      _classPrivateFieldSet(_blackUsers, this, list.reduce((map, user) => {
        return map.set(user, true);
      }, new Map()));
    }

    // 获取屏蔽词列表
    static async loadBlackList() {
      let value = await GM.getValue(DomainStore.domainKeyPrefix);
      if (!value) return [];
      const ret = JSON.parse(String(value));
      console.log(`gm value: ${ret}`);
      return ret;
    }
  }
  _DomainStore = DomainStore;
  async function _flush() {
    await GM.setValue(_DomainStore.domainKeyPrefix, JSON.stringify(_classPrivateFieldGet(_blackUsers, this).keys()));
  }
  _defineProperty(DomainStore, "NgListKey", 'NgList');
  (() => {
    const domain = document.location.host;
    let segs = domain.split('.');
    if (segs.length > 2) segs = segs.slice(1);
    _DomainStore.domainKeyPrefix = `${segs.join('.')}:${_DomainStore.NgListKey}`;
  })();
  var _store = /*#__PURE__*/new WeakMap();
  var _filter = /*#__PURE__*/new WeakMap();
  class MainView {
    /**
     *
     * @param {DomainStore} store
     * @param {WebsiteFilter} filter
     */
    constructor({
      store,
      filter
    }) {
      /**
       * @member {DomainStore} #store
       * */
      _classPrivateFieldInitSpec(this, _store, void 0);
      /**
       * @member {WebsiteFilter} #filter
       */
      _classPrivateFieldInitSpec(this, _filter, void 0);
      _classPrivateFieldSet(_store, this, store);
      _classPrivateFieldSet(_filter, this, filter);
      addStyle(css); // 添加样式
      window.addEventListener('load', () => {
        // 屏蔽视频播放后的弱智三连语音
        appObserverInit();
      });
    }
    dialogElement() {
      return document.querySelector(MainView.DialogSelector);
    }
    render() {
      setInterval(() => {
        this.renderSettingButton();
        this.renderSettingPanel();
        _classPrivateFieldGet(_filter, this).render();
        this.renderBlockButtons();
      }, 1000);
    }
    async renderBlockButtons() {
      document.querySelectorAll(`${cssByCls(BlockButtonClass)}`).forEach(button => {
        // 点击按钮展示弹窗
        button.addEventListener('click', ev => {
          ev.stopPropagation();
          let name = button.getAttribute('user-name');
          _classPrivateFieldGet(_store, this).addUser(name);
        });
      });
    }

    /* 生成添加屏蔽关键词的按钮 */
    async renderSettingButton() {
      if (!document.body) {
        return;
      }
      if (document.body.querySelector('#add_ngList_btn')) {
        return;
      }
      const btn = document.createElement('div');
      btn.title = '添加屏蔽关键词';
      const span = document.createElement('span');
      span.innerText = '';
      btn.appendChild(span);
      btn.id = 'add_ngList_btn';
      document.body.appendChild(btn);

      // 点击按钮展示弹窗
      btn.addEventListener('click', () => {
        this.renderBlockedUsers();
        this.showDialog();
      });
    }
    async renderSettingPanel() {
      /* dialog模板 */
      const dialogTemplate = `
      <div class="my-dialog" style="margin-top: 15vh; width: 40%;">
        <div class="my-dialog__header">
          <span class="my-dialog__title">屏蔽词列表</span>
          <button type="button" aria-label="Close" class="my-dialog__headerbtn">
            <i class="my-dialog__close"></i>
          </button>
        </div>
        <div class="my-dialog__body">
          <div class="input_container">
            <div class="el-input">
              <input id="ngWord_input" class="el-input__inner" type="text" />
            </div>
            <button type="button" class="el-button" id="add_btn">
              <span>添加</span>
            </button>
          </div>
          <div id="ngList"></div>
          <p class="tips">注:1. 可过滤包含屏蔽词的用户、微博、评论、热搜。 2. 关键词保存在本地的local storage中。 3. 更改关键词后刷新页面生效(不刷新页面的情况下,只有之后加载的微博才会生效)。</p>
        </div>
        <div class="my-dialog__footer"></div>
      </div>
    `;
      if (!document.body) {
        return;
      }
      if (document.body.querySelector('.my-dialog__wrapper')) {
        return;
      }
      const wrapper = document.createElement('div');
      wrapper.classList.add('my-dialog__wrapper');
      wrapper.innerHTML = dialogTemplate;
      document.body.appendChild(wrapper);

      /* 初始化事件 */
      document.querySelector('.my-dialog__headerbtn').addEventListener('click', () => {
        // 关闭按钮点击事件
        this.hideDialog();
      });
      document.querySelector('#add_btn').addEventListener('click', () => {
        // 添加关键词按钮点击事件
        const ngWord_input = document.querySelector('#ngWord_input');
        if (ngWord_input && ngWord_input.value) {
          _classPrivateFieldGet(_store, this).addUser(ngWord_input.value);
          ngWord_input.value = '';
          this.renderBlockedUsers();
        }
      });
    }
    showDialog() {
      this.dialogElement().style.display = 'initial';
    }
    hideDialog() {
      this.dialogElement().style.display = 'none';
    }
    renderBlockedUsers() {
      let blockedUsersHTML = '';
      const users = [..._classPrivateFieldGet(_store, this).userList];
      for (const [i, item] of users.entries()) {
        blockedUsersHTML += `<span class="ng_item">${item}<i class="close-icon" data-index=${i}></i></span>`;
      }
      const ngListNode = document.querySelector('#ngList');
      if (ngListNode) {
        ngListNode.innerHTML = blockedUsersHTML;
        const buttons = ngListNode.querySelectorAll('.close-icon');
        for (const button of buttons) {
          button.addEventListener('click', () => {
            const name = button.textContent;
            _classPrivateFieldGet(_store, this).removeUser(name);
            this.renderBlockedUsers();
          });
        }
      }
    }
  }

  // 创建观察器
  _defineProperty(MainView, "DialogSelector", '.my-dialog__wrapper');
  function appObserverInit() {
    const targetNode = document.getElementById('app');
    if (!targetNode) {
      return;
    }
    // 观察器的配置(需要观察什么变动)
    const config = {
      childList: true,
      subtree: true
    };
    // 当观察到变动时执行的回调函数
    const callback = function () {
      const audioList = document.querySelectorAll('.AfterPatch_bg_34rqc');
      for (const audio of audioList) {
        audio.remove();
        console.log('移除了弱智三连');
      }
    };

    // 创建一个观察器实例并传入回调函数
    const observer = new MutationObserver(callback);

    // 以上述配置开始观察目标节点
    observer.observe(targetNode, config);
  }
  function sleep(ms) {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  const store = await DomainStore.init();
  let filter = null;
  const {
    host
  } = document.location;
  filter = host.includes(WeiboFilter.host) ? new WeiboFilter(store) : host.includes(HupuFilter.host) ? new HupuFilter(store) : host.includes(TiebaFilter.host) ? new TiebaFilter(store) : null;
  const controller = new MainView({
    store,
    filter
  });
  controller.render();
})();

QingJ © 2025

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