Greasy Fork

Greasy Fork is available in English.

去广告&关键词屏蔽

去除“全部关注”和“最新微博”列表中的广告&屏蔽包含设置的关键词的微博

当前为 2021-07-16 提交的版本,查看 最新版本

// ==UserScript==
// @name              去广告&关键词屏蔽
// @name:zh           去广告&关键词屏蔽
// @namespace         Violentmonkey Scripts
// @match             *://*.weibo.com/*
// @include           *://weibo.com/*
// @exclude           *://weibo.com/tv*
// @grant             none
// @version           2.3
// @author            fbz
// @description       去除“全部关注”和“最新微博”列表中的广告&屏蔽包含设置的关键词的微博
// @description:zh    去除“全部关注”和“最新微博”列表中的广告&屏蔽包含设置的关键词的微博
// @require           https://unpkg.com/[email protected]/dist/ajaxhook.js
// ==/UserScript==
(function() {
  /*添加样式*/
  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;
      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: ;
    }

    .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;
    }

    .ng_item:not(:first-child) {
      margin-left: 12px;
    }

    .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;
    }
  `
  /*添加样式*/
  function addStyle(css) {
    if (!css) return
    var head = document.querySelector('head');
    var style = document.createElement("style");
    style.type = 'text/css'
    style.innerHTML = css
    head.appendChild(style)
  }

  /*dialog模板*/
  const dialog_temp = `
    <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. 关键词保存在本地的local storage中。 2. 更改关键词后刷新页面生效。</p>
      </div>
      <div class="my-dialog__footer"></div>
    </div>
  `
  /*按钮模板*/
  const btn_temp = `
    <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>
  `

  /*生成添加屏蔽关键词的按钮*/
  function createngListBtn() {
    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', function() {
      showDialog()
    })
  }

  const NgListKey = 'NgList'

  // 获取屏蔽词列表
  function getNgList() {
    return JSON.parse(localStorage.getItem(NgListKey))
  }

  // 设置屏蔽词值
  function setNgList(list) {
    return localStorage.setItem(NgListKey, JSON.stringify(list))
  }

  // 初始化屏蔽词
  function initNgList() {
    setNgList([])
  }

  // 初始化dialog
  function initDialog() {
    const wrapper = document.createElement('div')
    wrapper.classList.add('my-dialog__wrapper')
    wrapper.innerHTML = dialog_temp
    document.body.appendChild(wrapper)

    /*初始化事件*/
    document.querySelector('.my-dialog__headerbtn').addEventListener('click', function() {
      // 关闭按钮点击事件
      hideDialog()
    })
    document.querySelector('#add_btn').addEventListener('click', function() {
      // 添加关键词按钮点击事件
      const ngWord_input = document.querySelector('#ngWord_input')

      if (ngWord_input) {
        const value = ngWord_input.value
        data.ngList = ngList.concat([value])
        ngWord_input.value = ''
      }
    })
  }
  // 显示dialog
  function showDialog() {
    data.ngList = data.ngList
    document.querySelector('.my-dialog__wrapper').style.display = 'initial'
  }
  // 隐藏dialog
  function hideDialog() {
    document.querySelector('.my-dialog__wrapper').style.display = ''
  }

  // 把屏蔽词列表添加到弹窗中
  function setNgListToDom(list) {
    let nodeStr = ''
    for (let [i, item] of list.entries()) {
      nodeStr += (`<span class="ng_item">${item}<i class="close-icon" data-index=${i}></i></span>`)
    }
    const ngListNode = document.querySelector('#ngList')
    if (ngListNode) {
      ngListNode.innerHTML = nodeStr

      const onDel = (i) => {
        // 删除关键词
        const arr = [...data.ngList]
        arr.splice(i, 1)
        data.ngList = arr || []
      }
      const delBtnList = ngListNode.querySelectorAll('.close-icon')
      for (const [i, node] of delBtnList.entries()) {
        node.addEventListener('click', function(el) {
          onDel(Number(el.target.dataset.index))
        })
      }
    }
  }

  addStyle(css) // 添加样式
  createngListBtn() // 生成按钮
  initDialog() // 初始化弹窗

  const data = {
    ngList: []
  }

  Object.defineProperty(data, 'ngList', {
    // 简易双向绑定
    get: function() {
      return ngList
    },
    set: function(value) {
      ngList = value || []
      setNgList(ngList)
      setNgListToDom(ngList)
    }
  })

  let ngList = getNgList() // 屏蔽词列表
  if (!ngList) {
    initNgList()
    ngList = getNgList()
  }
  data.ngList = ngList

  ah.proxy({
    // 请求发起前进入
    onRequest: (config, handler) => {
      handler.next(config)
    },
    // 请求发生错误时进入,比如超时;注意,不包括http状态码错误,如404仍然会认为请求成功
    onError: (err, handler) => {
      handler.next(err)
    },
    // 请求成功后进入
    onResponse: (response, handler) => {
      const url = response.config.url
      let res = response.response
      if (url.includes('friendstimeline') && res) {
        res = JSON.parse(res)
        res.statuses = res.statuses.reduce((acc, cur) => {
          // 过滤未关注的用户&屏蔽关键词
          if (cur.user.following || cur.screen_name_suffix_new) {
            const retweeted_status = cur // 转推的原推
            const myText = cur.text // 本人推的内容

            const ngWordInMyText = data.ngList.some(word => myText.includes(word))

            if (ngWordInMyText) return acc

            if (retweeted_status) {
              // 如果是转推,判断原博是否包含屏蔽关键词
              const oriText = retweeted_status.text
              const ngWordInOriText = data.ngList.some(word => oriText.includes(word))
              if (ngWordInOriText) return acc
            }
            acc.push(cur)
          }
          return acc
        }, [])

        response.response = JSON.stringify(res)
      }
      handler.next(response)
    }
  })
})()