Greasy Fork is available in English.

添加自定义css和js(广告屏蔽等)

可自定义css选择器屏蔽页面广告,添加js脚本

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install an extension such as Tampermonkey or Violentmonkey to install this script.

You will need to install an extension such as Tampermonkey or Userscripts to install this script.

You will need to install an extension such as Tampermonkey to install this script.

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         添加自定义css和js(广告屏蔽等)
// @description  可自定义css选择器屏蔽页面广告,添加js脚本
// @namespace    _cus_ad_sp
// @version      2.11.2
// @author       vizo
// @license      MIT
// @include      /https?\:\/\/(?!greasyfork).*/
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_addElement
// @grant        GM_registerMenuCommand
// @grant        GM_setClipboard
// @grant        GM_xmlhttpRequest
// @grant        GM_download
// @grant        unsafeWindow
// @connect      *
// @require      https://unpkg.com/[email protected]/dist/jquery.min.js
// @require      https://unpkg.com/[email protected]/dist/vue.min.js
// @require      https://unpkg.com/[email protected]/dist/tiny-oss.min.js
// @require      https://unpkg.com/[email protected]/index.js
// @require      https://unpkg.com/@vizoy/[email protected]/index.js
// @require      https://unpkg.com/@vizoy/[email protected]/sw2.js
// @require      https://unpkg.com/[email protected]/dist/axios.min.js
// @noframes

// ==/UserScript==
Object.assign(TMK, vio)
TMK.sw = sw
unsafeWindow.TMK = TMK
unsafeWindow.$j = $
unsafeWindow.axios = axios

const html = (s) => {
  return s[0]
}

const G = {
  hostIgnore: /\b(taobao|jd|tmall|bilibili|iviewui)\.com|^(192\.|localhost|127\.)\b|\bbaidu\.com\/s\?wd=|quicker/i,
  ifrIgnore: /\b(github|qq|geetest|taobao|aliyundrive|163)\.com|\brecaptcha/i,
  linkIgnore: /\b(github|gitee)\.com/i,
  tmpTime: 0,
  cusStyCnt: '',
  html: html`
    <div id="wp5rh" v-show="dialog1s">
      <div class="mwp_5c" :class="{'expandJS': isExpandJS, 'ld': isLoad}">
        <div class="tit1v">设置</div>
        <div class="tamp-cfg-modal" v-show="isShowTampModal">
          <textarea class="txa-cfg" @change="changeTampCfg" v-model="tampCfgVal" placeholder="oss配置"></textarea>
        </div>
        <div class="c7d-item">
          <p class="stiz">
            <span class="s0l">打开面板快捷键</span>
            <span class="oss-zbtn" @click="hdlTgOssModal">oss</span>
            <span class="view-all-set" @click="hdlViewAllSet">{{ viewSetText }}</span>
          </p>
          <input type="text" class="inpy" v-model="eKey" placeholder="请输入a-z 用逗号隔开">
        </div>
        <div class="c7d-item css-item-xh" v-show="!showAllSet">
          <p class="stiz">
            <span class="s0l">添加css(不含style标签)</span>
            <span class="s0r" :class="{on: disCSS}" @click="hdlTgDisCss">{{ disCssText }}</span>
          </p>
          <textarea class="txtr1z" :class="{'disabled': disCSS}" v-model="texCssVal" :readonly="disCSS" spellcheck="false" placeholder="请输入css代码" @click="hdlExpandJSJs(1)"></textarea>
        </div>
        <div class="c7d-item js-item-xh" v-show="!showAllSet">
          <p class="stiz">
            <span class="s0l">添加js(不含script标签)</span>
            <span class="s0r" :class="{on: disJS}" @click="hdlTgDisJs">{{ disJsText }}</span>
          </p>
          <textarea class="txtr1z" :class="{'disabled': disJS}" v-model="texJsVal" :readonly="disJS" spellcheck="false" placeholder="请输入js代码" @click="hdlExpandJSJs(2)"></textarea>
        </div>
        <div class="c7d-item allset-item" v-show="showAllSet">
          <p class="stiz st1k">
            <span class="s1p">已添加的网站(可删除) {{ addedNum }} 个 </span>
            <span class="s2p imt-c" @click="hdlImportCfg">导入配置</span>
            <span class="s2p ext-c" @click="hdlExportCfg">导出配置</span>
          </p>
          <input type="file" hidden ref="inp_hide" @change="hdlUpFile">  
          <textarea class="txtr1z" v-model="allAddedText"></textarea>
        </div>
        <div class="btn-w">
          <button class="c5kbtn b2" @click="hdlCancel">取消</button>
          <button class="c5kbtn b1" @click="hdlSave">保存</button>
        </div>
      </div>
    </div>
  `,
}

;(function() {
  if (TMK.isMobile()) return
  let k = GM_getValue(`_cfg_${location.host}`) || {}
  k = typeof k === 'string' ? JSON.parse(k) : k
  if (k.css && !k.disCSS) {
    tryAddCusSty(k.css)
  }
})();

tryAddGmSty()

function tryAddGmSty() {
  const isAdd = document.head.querySelector('.sty777rx')
  if (!isAdd) {
    GM_addElement('style', {
      class: 'sty777rx',
      textContent: `
        html body .dn8x {
          display: none !important;
          visibility: hidden !important;
          overflow: hidden !important;
          height: 0 !important;
          width: 0 !important;
          transform: scale(0) !important;
          position: fixed !important;
          top: -99999px !important;
          left: -99999px !important;
          z-index: -100;
        }
        html body .GM-Asd-yisi,
        html body .GM-Asd-certain {
          overflow: hidden !important;
          background-image: none !important;
        }
        html body .adsbygoogle {
          display: none !important;
        }
        .GM-Asd-yisi::before,
        .GM-Asd-certain::before {
          content: attr(fxkasd);
          width: 100% !important;
          height: 100% !important;
          font-size: 16px;
          color: #ddd !important;
          background-color: transparent !important;
          display: flex !important;
          justify-content: center;
          align-items: center;
          font-weight: normal;
          font-style: normal;
          font-family: Arial sans-serif;
          position: absolute !important;
          top: 0;
          left: 0;
          z-index: 1;
        }
        .GM-Asd-certain.imgRx,
        .GM-Asd-certain.ifrIx,
        img.GM-Asd-certain {
          visibility: hidden !important;
          overflow: hidden !important;
          height: 0 !important;
        }
           
        .GM-Asd-certain.bdAsd::before {
          content: '百度广告' !important;
        }
        .GM-Asd-certain.gooAsd::before {
          content: '谷歌广告' !important;
        }
        .GM-Asd-certain.qrc7Box::before {
          content: '二维码';
        }
        .GM-Asd-yisi > *,
        .GM-Asd-certain > * {
          visibility: hidden !important;
          opacity: 0 !important;
        }
        .GM-Asd-yisi:hover > *,
        .GM-Asd-certain:hover > * {
          visibility: visible !important;
          opacity: 0.8 !important;
          animation: anim5z 1.7s both;
        }
        html .rtv8x {
          position: relative !important;
        }
        @keyframes anim5z {
          0% {
            opacity: 0;
          }
          30% {
            opacity: 0;
          }
          100% {
            opacity: 0.8;
          }
        }
        @keyframes anim8z {
          90% {
            visibility: visible;
          }
          100% {
            opacity: 0;
            visibility: hidden;
          }
        }
           
        .GM-Asd-yisi:hover::before,
        .GM-Asd-certain:hover::before {
          animation: anim8z 1.5s both;
        }
        #wp5rh [hidden] {
          display: none !important;
        }
        #wp5rh, #wp5rh * {
          margin: 0;
          padding: 0;
          box-sizing: border-box !important;
        }
        #wp5rh {
          width: 28vw;
          height: 68vh;
          min-width: 440px;
          min-height: 400px;
          padding: 30px;
          background: #fff;
          border-radius: 3px;
          font-family: sans-serif,"HelveticaNeue",Helvetica,"PingFangSC","MicrosoftYaHei","HiraginoSansGB",Arial;
          line-height: 1.5;
          font-size: 12px;
          resize: both;
          box-shadow: 0 0 5px #ccc;
          position: fixed;
          top: 0;
          right: 0;
          bottom: 0;
          left: 0;
          z-index: 50050;
          margin: auto;
        }
        #wp5rh .mwp_5c {
          height: 100%;
          display: flex;
          flex-direction: column;
          position: relative;
        }
        #wp5rh .mwp_5c::before {
          content: '加载中...';
          background: #fff9;
          font-size: 14px;
          color: #999;
          justify-content: center;
          align-items: center;
          position: absolute;
          top: 0;
          right: 0;
          bottom: 0;
          left: 0;
          z-index: 1;
          display: none;
        }
        #wp5rh .mwp_5c.ld::before {
          display: flex;
        }
        #wp5rh .tit1v {
          color: #555;
          font-size: 18px;
          text-align: center;
          margin-bottom: 15px;
        }
        #wp5rh .c7d-item {
          margin-bottom: 10px;
          display: flex;
          flex-direction: column;
          flex-wrap: wrap;
        }
        #wp5rh .allset-item {
          flex: 1;
        }
        #wp5rh .css-item-xh {
          flex: 3;
          transition: flex .3s;
        }
        #wp5rh .js-item-xh {
          flex: 1;
          transition: flex .3s;
        }
        #wp5rh .mwp_5c.expandJS .css-item-xh {
          flex: 1;
        }
        #wp5rh .mwp_5c.expandJS .js-item-xh {
          flex: 3;
        }
        #wp5rh .stiz {
          font-size: 14px;
          color: #555;
          margin-bottom: 3px;
          position: relative;
          text-align: left;
          display: flex;
        }
        #wp5rh .tamp-cfg-modal {
          width: 250px;
          height: 170px;
          padding: 10px;
          border-radius: 2px;
          background: #f1f1f1;
          position: absolute;
          top: -50px;
          right: 0;
          bottom: 0;
          left: 0;
          margin: auto;
          z-index: 2;
        }
        #wp5rh .tamp-cfg-modal .txa-cfg {
          width: 100%;
          height: 100%;
          resize: none;
          color: #777 !important;
          font-family: Consolas;
          font-size: 12px;
          padding: 6px;
          overflow-y: auto;
          border: 1px solid #e6e6e6;
          background: #fafafa;
        }
        #wp5rh .tamp-cfg-modal .txa-cfg::-webkit-input-placeholder {
          color: #ccc !important;
        }
        #wp5rh .stiz .s0l {
          flex: 1;
        }
        #wp5rh .stiz .s0r {
          color: #09e;
          cursor: pointer;
          user-select: none;
          margin-left: 10px;
        }
        #wp5rh .stiz .s0r.on {
          color: #9a9a9a;
        }
        #wp5rh .oss-zbtn {
          color: #c7c7c7;
          cursor: pointer;
          user-select: none;
          margin-right: 10px;
        }
        #wp5rh .view-all-set {
          color: #09e;
          cursor: pointer;
          user-select: none;
        }
        #wp5rh .st1k {
          display: flex;
        }
        #wp5rh .st1k .s1p {
          flex: 1;
        }
        #wp5rh .st1k .s2p {
          width: 65px;
          color: #09e;
          cursor: pointer;
          text-align: right;
          white-space: nowrap;
          overflow: hidden;
        }
        #wp5rh .inpy {
          flex: 0 0 auto;
          height: 32px;
          border: 1px solid #ddd;
          color: #555;
          background: #fff;
          border-radius: 2px;
          padding: 0 10px;
          outline: none;
        }
        #wp5rh .inpy:focus {
          border: 1px solid #c1c1c1;
        }
        #wp5rh .txtr1z {
          width: 100%;
          flex: 1;
          color: #555;
          padding: 6px;
          line-height: 1.4;
          overflow-x: hidden;
          overflow-y: auto;
          border-radius: 2px;
          border: 1px solid #ddd;
          background: #fff;
          font-size: 12px;
          resize: none;
          white-space: pre-line;
          outline: none;
          font-family: Consolas,sans-serif,"Helvetica Neue",Helvetica,"PingFang SC","Microsoft YaHei";
        }
        #wp5rh .txtr1z::-webkit-input-placeholder {
          color: #c5c5c5;
        }
        #wp5rh .txtr1z:focus {
          border: 1px solid #39e;
        }
        #wp5rh .txtr1z.disabled {
          color: #999;
          background: #f5f5f5;
        }
        #wp5rh .txtr1z::-webkit-scrollbar {
          width: 4px;
        }
        #wp5rh .txtr1z::-webkit-scrollbar-corner,
        #wp5rh .txtr1z::-webkit-scrollbar-track {
          background-color: #fff;
        }
        #wp5rh .txtr1z::-webkit-scrollbar-thumb {
          background: #fff;
        }
        #wp5rh .txtr1z:hover::-webkit-scrollbar-thumb {
          background: #e1e1e1;
        }
        #wp5rh .txtr1z:hover::-webkit-scrollbar-corner,
        #wp5rh .txtr1z:hover::-webkit-scrollbar-track {
          background-color: #f7f7f7;
        }
        #wp5rh .btn-w {
          margin-top: 5px;
          display: flex;
          flex-direction: row-reverse;
        }
        #wp5rh .btn-w .c5kbtn {
          width: 90px;
          height: 32px;
          border-radius: 2px;
          margin-right: 10px;
          font-family: sans-serif,"Helvetica Neue",Helvetica,"PingFang SC","Microsoft YaHei" !important;
          cursor: pointer;
          outline: none;
          border: 0;
        }
        #wp5rh .btn-w .c5kbtn.b1 {
          color: #fff !important;
          background: #09e !important;
        }
        #wp5rh .btn-w .c5kbtn.b2 {
          color: #555 !important;
          background: #f1f1f1 !important;
        }
        #wp5rh .btn-w .c5kbtn:first-child {
          margin-right: 0;
        }
        #wp5rh .btn-w .c5kbtn:focus {
          border: 0;
        }
        #wp5rh .btn-w .c5kbtn:hover {
          opacity: 0.9;
        }
      `,
    })
  }
}

const vm = new Vue({
  data() {
    return {
      // 模态框状态
      dialog1s: false,
      // 快捷键名称
      eKey: '',
      showAllSet: false,
      // css代码
      texCssVal: '',
      // js代码
      texJsVal: '',
      // 已添加的网站
      allAddedText: '',
      disCSS: false,
      disJS: false,
      // 是否展开js
      isExpandJS: false,
      isLoad: false,
      // oss配置modal
      isShowTampModal: false,
      tampCfgVal: '',
    }
  },
  computed: {
    viewSetText() {
      return this.showAllSet ? '查看当前网站' : '查看全部网站'
    },
    addedNum() {
      return this.allAddedText
        .split('\n')
        .filter(v => !!v)
        .length
    },
    disCssText() {
      return this.disCSS ? '已禁用' : '禁用css'
    },
    disJsText() {
      return this.disJS ? '已禁用' : '禁用js'
    },
  },
  watch: {
    eKey(nVal) {
      this.eKey = /[a-z\,]/.test(nVal) ? nVal : ''
      GM_setValue('_gus_keyboard', this.eKey)
    },
    dialog1s(nVal) {
      if (!nVal) {
        this.showAllSet = false
        this.isShowTampModal = false
      }
    },
  },
  methods: {
    setGmVal(obj) {
      obj.firstTime = obj.firstTime ?? Math.trunc(Date.now() / 1e3)
      return GM_setValue(`_cfg_${location.host}`, obj)
    },
    getGmVal() {
      let gmVal = GM_getValue(`_cfg_${location.host}`) || {}
      return typeof gmVal === 'string' ? JSON.parse(gmVal) : gmVal
    },
    hdlTgOssModal() {
      this.isShowTampModal = !this.isShowTampModal
    },
    hdlExpandJSJs(type) {
      this.isExpandJS = type === 2
    },
    changeTampCfg() {
      GM_setValue('tampOssCfg7n', this.tampCfgVal)
    },
    async hdlSave() {
      if (!this.showAllSet) {
        this.saveCssAndJs()
        this.initAddedWebToTextArea()
      } else {
        this.updateTextAreaValToGm()
        await this.saveJsonToOss('state')
        await this.saveJsonToOss('cfg')
        location.reload()
      }
      this.dialog1s = false
    },
    hdlCancel() {
      this.dialog1s = false
    },
    // 禁用css
    hdlTgDisCss() {
      this.disCSS = !this.disCSS
    },
    // 禁用js
    hdlTgDisJs() {
      this.disJS = !this.disJS
    },
    async saveCssAndJs() {
      tryAddCusSty(this.texCssVal)
      
      document.querySelectorAll('.cusSty9z1p').forEach((v, i, y) => {
        if (i !== y.length - 1) {
          v.remove()
        } else {
          v.disabled = this.disCSS
        }
      })
      
      const gm = this.getGmVal()
      this.setGmVal({
        ...gm,
        css: this.texCssVal,
        js: this.texJsVal,
        disCSS: this.disCSS,
        disJS: this.disJS,
      })
      
      if (!this.texCssVal && !this.texJsVal) {
        GM_deleteValue(`_cfg_${location.host}`)
      }
      
      // 同步至oss
      if ( !await this.saveJsonToOss('state') ) {
        alert('同步失败, 请导出配置后在其他网站重新导入配置就能同步了')
        return
      }
      await this.saveJsonToOss('cfg')
      
      if (
        gm.disJS !== this.disJS
        || gm.js !== this.texJsVal
        || !this.texCssVal.trim()
        && !this.texJsVal.trim()
      ) {
        await TMK.timeout(500)
        location.reload()
      }
      
    },
    saveJsonToOss(name) {
      let gmCfg = GM_getValue('tampOssCfg7n')
      if (!gmCfg) return Promise.resolve(true)
      gmCfg = typeof gmCfg === 'string' ? JSON.parse(gmCfg) : gmCfg
      const oss = new TinyOSS(gmCfg.ossParams)
      const lastTime = Date.now()
      const data = name === 'state' ? { lastTime } : this.getAllCfg()
      const blob = new Blob([JSON.stringify(data)], { type: 'text/json' })
      const date = TMK.fmt(Date.now(), 'Y-M-D')
      
      GM_setValue('tampCfgUpdateTime', lastTime)
      return new Promise(async (resolve) => {
        try {
          if (name === 'cfg') {
            oss.put(`json/tamp-cfg/cus-cssjs/${date}/${name}.json`, blob)
            await TMK.timeout(300)
          }
          await oss.put(`json/tamp-cfg/cus-cssjs/1-cfg/${name}.json`, blob, {
            onprogress(e) {
              if (e.total > 0) {
                return resolve(true)
              }
            }
          })
        } catch (err) {
          return resolve(false)
        }
      })
    },
    
    GM_req(url) {
      return new Promise((resolve, reject) => {
        GM_xmlhttpRequest({
          url,
          method: 'get',
          responseType: 'json',
          onload: function(xhr) {
            resolve(xhr.response)
          },
        })
      })
    },
    
    async updateCfgFromOss() {
      const gmCfg = this.getOssCfg()
      const gmLastTime = GM_getValue('tampCfgUpdateTime')
      if (gmCfg) {
        try {
          const url1 = `${gmCfg.state}&t=${Date.now()}`
          const url2 = `${gmCfg.cfg}&t=${Date.now()}`
          const res = await this.GM_req(url1)
          const { lastTime } = res
          const now = Date.now()
          
          if (
            !gmLastTime
            || lastTime > gmLastTime
            || now - gmLastTime > 3e4
          ) {
            const rCfg = await this.GM_req(url2)
            this.updateJsonToGm(rCfg)
            // 这里也初始化一次
            this.initAddedWebToTextArea()
            GM_setValue('tampCfgUpdateTime', now)
          }
        } catch (e) {
          TMK.log( e.message )
        }
      }
    },
    
    // 判断是否从远程更新
    tryUpdateCfgFromOss() {
      const gmTime = GM_getValue('pageRefreshTime')
      const now = Date.now()
      if (!gmTime || now - gmTime > 5000) {
        GM_setValue('pageRefreshTime', now)
        return new Promise(async (resolve) => {
          this.isLoad = true
          await this.updateCfgFromOss()
          this.isLoad = false
          resolve()
        })
      }
    },
    
    updateTextAreaValToGm() {
      const gmArr = GM_listValues()
        .filter(v => v.startsWith('_cfg_'))
        .map(v => v.replace(/^_cfg_/, ''))
      const cArr = this.allAddedText.split('\n')
      gmArr.forEach(v => {
        if (!cArr.some(c => v === c)) {
          GM_deleteValue(`_cfg_${v}`)
        }
      })
    },
    
    updateJsonToGm(obj) {
      if (!obj) return
      GM_listValues()
        .filter(v => /^_gus_|^_cfg_/.test(v))
        .forEach(v => {
          GM_deleteValue(v)
        })
      
      // 初始化firstTime
      for (let i in obj) {
        if (!obj[i].firstTime) {
          obj[i].firstTime = Math.trunc(Date.now() / 1e3)
        }
      }
        
      for (let k in obj) {
        GM_setValue(k, obj[k])
      }
    },
    
    tgCfgDialog() {
      this.dialog1s = !this.dialog1s
    },
    hdlViewAllSet() {
      this.showAllSet = !this.showAllSet
    },
    // 导入配置
    hdlImportCfg() {
      this.$refs.inp_hide.click()
    },
    // 导出配置
    hdlExportCfg() {
      const obj = this.getAllCfg()
      TMK.downloadText(JSON.stringify(obj, null, 2), '1.json')
    },
    
    // 获取oss配置
    getOssCfg() {
      let gmCfg = GM_getValue('tampOssCfg7n')
      if (!gmCfg) return
      try {
        return typeof gmCfg === 'string' ? JSON.parse(gmCfg) : gmCfg
      } catch (err) {}
    },
    
    initOssVal() {
      const gmCfg = this.getOssCfg()
      if (gmCfg) {
        this.tampCfgVal = JSON.stringify(gmCfg)
      }
    },
    
    // 获取所有已配置的网站数据
    getAllCfg() {
      const obj = GM_listValues()
        .filter(v => /^_gus_|^_cfg_/.test(v))
        .reduce((acc, v) => {
          let gmVal = GM_getValue(v)
          gmVal = typeof gmVal === 'string' && gmVal > 1 ? JSON.parse(gmVal) : gmVal
          return {
            ...acc,
            [v]: gmVal
          }
        }, {})
      for (let k in obj) {
        if (k.startsWith('_cfg_')) {
          if (!obj[k]?.css.trim() && !obj[k]?.js.trim()) {
            Reflect.deleteProperty(obj, k)
          }
        }
      }
      return obj
    },
    
    hdlUpFile(e) {
      let file = e.target.files[0]
      if (file) {
        let reader = new FileReader()
        reader.readAsText(file, 'utf-8')
        reader.onload = async (evt) => {
          try {
            oUp = JSON.parse(evt.target.result)
            this.updateJsonToGm(oUp)
            
            if (!await this.saveJsonToOss('state')) {
              alert('上传失败, 请选择其他网站重新上传')
              return
            }
            await this.saveJsonToOss('cfg')
            
            this.initCssJsVal()
            this.initAddedWebToTextArea()
            this.initAddedScript()
            
            setTimeout(() => {
              location.reload()
            }, 200)
          } catch (e) {
            // 上传失败
          }
        }
      }
    },
    async resetCss() {
      this.texCssVal = ''
      this.disCSS = false
      this.setGmVal({
        ...this.getGmVal(),
        css: '',
        disCSS: false,
      })
      await this.saveJsonToOss('state')
      await this.saveJsonToOss('cfg')
      location.reload()
    },
    async resetJs() {
      this.texJsVal = ''
      this.disJS = false
      this.setGmVal({
        ...this.getGmVal(),
        js: '',
        disJS: false,
      })
      await this.saveJsonToOss('state')
      await this.saveJsonToOss('cfg')
      location.reload()
    },
    initEvt() {
      document.addEventListener('keydown', e => {
        const el = e.target
        const edt = el.getAttribute('contenteditable')
        const unEdt = edt !== 'true' && edt !== ''
        if (
          !(/text|search|number|password|tel|url|email/.test(el.type))
          && el.tagName !== 'TEXTAREA'
          && unEdt
          && !e.altKey
          && !e.ctrlKey
          && this.eKey.split(',').map(v => v.trim()).includes(e.key)
        ) {
          this.tgCfgDialog()
        }
        if (/esc/i.test(e.key)) {
          this.dialog1s = false
        }
      })
    },
    initEkey() {
      this.eKey = GM_getValue('_gus_keyboard') || ''
    },
    
    initCssJsVal() {
      this.texCssVal = this.getGmVal().css || ''
      this.texJsVal = this.getGmVal().js || ''
      this.disCSS = !!this.getGmVal().disCSS
      this.disJS = !!this.getGmVal().disJS
    },
    
    initAddedWebToTextArea() {
      const sorted = this.sortSiteList(GM_listValues())
      const nArr = sorted
        .filter(v => v.startsWith('_cfg_'))
        .map(v => v.replace(/^_cfg_/, ''))
      this.allAddedText = nArr.join('\n')
    },
    
    initAddedScript() {
      let js = this.getGmVal().js
      let isDisabled = this.getGmVal().disJS
      if (js && !isDisabled) {
        GM_addElement('script', {
          type: 'module',
          textContent: js,
        })
      }
    },
    
    // 网站列表排序, 按时间倒序排列
    sortSiteList(siteList) {
      if (!siteList.length) return []
      const nArr = siteList.map(v => {
        return {
          name: v,
          t: GM_getValue(v)?.firstTime,
        }
      })
      .sort((a, b) => {
        return a.t - b.t > 0 ? -1 : 0
      })
      .map(v => v.name)
      return nArr
    },
    
  },
  async mounted() {
    this.initEvt()
    this.initOssVal()
    this.initEkey()
    await this.tryUpdateCfgFromOss()
    this.initAddedWebToTextArea()
    this.initCssJsVal()
    this.initAddedScript()
  },
})


function tryAddCusSty(styCnt) {
  const clsName = 'cusSty9z1p'
  if (document.querySelector('.' + clsName)) {
    return
  }
  if (styCnt) {
    G.cusStyCnt = styCnt
  } else {
    styCnt = G.cusStyCnt
  }
  GM_addElement('style', {
    class: clsName,
    textContent: styCnt
  })
}

function tryAppendWp5() {
  const wp5 = document.getElementById('wp5rh')
  if (!wp5) {
    document.body.insertAdjacentHTML('beforeend', G.html)
  }
}

// MKS
// ==== 规则 start gvz =======================
function isIgnHost() {
  return G.hostIgnore.test(location.host + location.pathname)
}

function isIgnLink(linkUrl) {
  try {
    const lnk = new URL(linkUrl.replace(/^\/\//, 'https://'))
    return G.linkIgnore.test(lnk.host + lnk.pathname)
  } catch {
    return false
  }
}

function isIgnIfr(url) {
  try {
    const ifr = new URL(url.replace(/^\/\//, 'https://'))
    return G.ifrIgnore.test(ifr.host + ifr.pathname)
  } catch {
    return false
  }
}

// 是否是广告商
function isAdvertiser(str) {
  return /\b(cpu\.baidu|pos\.baidu|google(sy|ad)|mediav)\b|adsbygoogle|adx\.php/.test(str)
}

function isBlank(el) {
  return /_blank/i.test(el.target)
}

function isHtmlOrBody(el) {
  return /^(body|html)$/i.test(el.nodeName)
}

function imgSrcYsAd(src) {
  if (!src || src.includes('data:image/')) {
    return false
  }
  const str = src.slice(0, 80) + src.slice(-120)
  return /(\b|_)(ad[sv]?|close|adve\w+)[-_]?\d*\.(png|jpg|gif|webp)/i.test(str)
}

function adTxt(s) {
  if (!s) return false
  return /(?<=[^个有打是癣很的多小种])广告(?=[^好太很多不有真比也是还])/.test(s.slice(0, 2000))
}

function adm(s) {
  if (!s || !s.length) return false
  if (typeof s !== 'string' || s.includes('data:image/')) {
    return false
  }
  if (s.length > 2000) {
    s = s.slice(0, 800) + s.slice(-800)
  }
  return (
    /(\b|_)ad[sv]?(ver)?[-_]?\d{0,10}(\b|_)|Adver|\badsense|(\b|_)ad[sv]?[_-]\w+/i.test(s)
    || /(\b|_)ad[A-Z][a-z]{2,6}\d{0,4}\b|[a-z]{4}Ad(\b|_)|(\b|_)sinaad|topAd/.test(s)
    || /[\b_]ave[-_]{1,6}/.test(s)
    || isAdvertiser(s)
  ) && !hasUUID(s)
}

// sgn
function hasAdSign(el, rmTxt = false) {
  if (!el) return false
  const attArr = [...el.attributes].filter(v => v.nodeName !== 'style')
  
  return (adTxt(el.textContent) && !rmTxt)
    || hasAdTextInBeAf(el)
    || attArr.some(v => adm(v.nodeValue) || adTxt(v.nodeValue))
}

function hasSibling(el) {
  return TMK.getSiblings(el).length > 1
}

function isGif(str) {
  if (!str) return false
  return /(\.(gif|php|jsp|asp)(\b|_))|^data:image\/gif;/i.test(str)
}

function ysGifAd(el) {
  const lazy = attr(el, 'data-src')
  const src = attr(el, 'src')
  return isGif(src)
    && !lazy
    && !isSwiper(el.parentElement)
    && likeAdSize(el)
    && !isSmallSize(el)
}

function ysVideoAd(el) {
  if (el.tagName !== 'VIDEO') {
    return
  }
  const loop = attr(el, 'loop')
  const autoplay = attr(el, 'autoplay')
  const muted = attr(el, 'muted')
  return hasAdSign(el)
    || (loop && autoplay && muted && likeAdSize(el))
}

function lnkEqImgUrl(link, imgUrl) {
  return link === imgUrl
}

function isAbs(url) {
  return /^https?|^\/\//i.test(url)
}

function isSwiper(el) {
  const pEl = el.parentElement
  const gEl = pEl.parentElement
  const inc = (ex) => {
    const p = attr(ex, 'class')
    return p ? p.includes('swiper') : false
  }
  return inc(el) || inc(pEl) || inc(gEl)
}

// 大小是否符合广告的尺寸(面积)
function likeAdSize(el) {
  return (
    !isSmallSize(el)
    && !isLargerSize(el)
    && bcr(el).height < 710
  )
}

function isLargerSize(el) {
  return bcr(el).width * bcr(el).height >= 900 * 600
    && !biggerRatio(el)
}

function isSmallSize(el) {
  const w = bcr(el).width
  const h = bcr(el).height
  return (
    (w * h) <= (80 * 40)
    || w < 65
    || h < 30
  )
  && w > 0
  && h > 0
}

function siblingHasAd(el) {
  const sib = TMK.getSiblings(el).filter(v => !/style|script/i.test(v.nodeName))
  return sib.some(v => hasAdSign(v))
}

// 判断是否在页面边角吗角落
function isCorner(el) {
  const winW = window.innerWidth
  const winH = window.innerHeight
  const x = bcr(el).x
  const y = bcr(el).y
  const w = bcr(el).width
  const h = bcr(el).height
  const p = 40
  return (
    (x - p <= 0 && y - p <= 0)
    || (x - p <= 0 && y + h + p >= winH)
    || (x + w + p >= winW && y - p <= 0)
    || (x + w + p >= winW && y + h + p >= winH)
  )
  && (w > 0 && w < 580)
  && (h > 0 && h < 620)
  && isFixed(el)
}

// 靠近顶部
function isNearTop(el) {
  const y = bcr(el).top
  const ht = bcr(document.documentElement).top
  return y >= 0
    && y <= 60
    && ht > -10
    && bcr(el).width > 30
    && bcr(el).height > 30
}

function likeLogo(el) {
  return isNearTop(el)
    && bcr(el).left < 800
    && bcr(el).width < 330
}

function hasUUID(str) {
  return /[a-f\d]{4}(?:[a-f\d]{4}-){4}[a-f\d]{12}/i.test(str)
}

function hasScript(el) {
  return el.querySelectorAll('script').length > 0
}

function hasIframe(el) {
  return el.querySelectorAll('iframe').length > 0
}

function hasAdTextInBeAf(el) {
  const bf = (str) => {
    const cnt = getComputedStyle(el, str).getPropertyValue('content')
    return adTxt(cnt)
  }
  return bf('::before')|| bf('::after')
}

function likeQrcSize(el) {
  const iw = bcr(el).width
  const ih = bcr(el).height
  return iw > 80 && ih > 80 &&
    iw < 385 && ih < 390 &&
    iw / ih > 0.7 && iw / ih < 1.1
}

function isFixed(el) {
  return getComputedStyle(el).position === 'fixed'
}

function isFxk(el) {
  const arr = TMK.pEls(el)
  return !!attr(el, 'fxkasd') || arr.some(v => !!attr(v, 'fxkasd'))
}

// 宽高比例疑似
function ysAdRatio(el) {
  const w = bcr(el).width
  const h = bcr(el).height
  return ( (w / h) > 3.6 || (h / w) > 3.6 )
    && w > 30
    && h > 30
}

function biggerRatio(el) {
  const w = bcr(el).width
  const h = bcr(el).height
  return (w / h) > 10
    && w > 30
    && h > 30
}

function ifrHasIfr(el) {
  try {
    const doc = el.contentWindow?.document
    return [...doc.querySelectorAll('iframe')].length > 0
  } catch {
    return false
  }
}

// MKS
// ===== 规则 end =========================================

function bcr(el) {
  return el.getBoundingClientRect()
}

function setFxK(el, str) {
  el.setAttribute('fxkasd', str.toUpperCase())
}

function attr(el, p) {
  return el ? el.getAttribute(p) : ''
}

// 内联a标签转换为inline-block
function setInlineBlock(link) {
  if (getComputedStyle(link).display === 'inline') {
    link.style.display = 'inline-block'
  }
}

// 如果是static则设置为relative
function setEleAsRelative(el) {
  if (getComputedStyle(el).position === 'static') {
    el.style.position = 'relative'
    el.classList.add('rtv8x')
  }
}

function linkHost(url) {
  return TMK.isAbsUrl(url) ? new URL(url).host : null
}

function compareTwoBox(el1, el2) {
  const w = bcr(el1).width
  const h = bcr(el1).height
  const pw = bcr(el2).width
  const ph = bcr(el2).height
  const cs = getComputedStyle
  const pTop = Number(cs(el1).paddingTop.slice(0, -2))
  const pRight = Number(cs(el1).paddingRight.slice(0, -2))
  const pBom = Number(cs(el1).paddingBottom.slice(0, -2))
  const pLeft = Number(cs(el1).paddingLeft.slice(0, -2))
  return (Math.abs(pw + pLeft + pRight - w) < 32
    && Math.abs(ph + pTop + pBom - h) < 32
    && pw > 36
    && ph > 36)
    || (w < 6 || h < 6)
}

function setCertainCls(el, cls = '') {
  const hasFk = (fk) => {
    return !!el?.src?.includes(fk) || [...el.querySelectorAll('iframe')]?.some(ifr => ifr?.src?.includes(fk))
  }
  const bdCls = hasFk('pos.baidu') ? 'bdAsd' : ''
  const ggCls = hasFk('googlead') ? 'gooAsd' : ''
  const clsStr = `GM-Asd-certain ${bdCls} ${ggCls} ${cls}`.trim().replace(/\s{2,}/, ' ').split(' ')
  el.classList.add(...clsStr)
}

// 删除添加的屏蔽class和标记
function removeAsdMk(el, isRecursive) {
  let dArr = [el]
  if (isRecursive) {
    dArr = [...dArr, ...TMK.pEls(el)]
  }
  dArr.forEach(v => {
    v.classList.remove(
      'GM-Asd-yisi',
      'GM-Asd-certain',
      'bdAsd',
      'gooAsd',
      'qrc7Box',
      'x8x',
    )
    v.removeAttribute('fxkasd')
  })
}

function tryBkSiblingAndReturnRes(el) {
  const sib = TMK.getSiblings(el).filter(v => !/style|script/i.test(v.nodeName))
  let isSuc = false
  sib.forEach(v => {
    if (hasAdSign(v)) {
      isSuc = true
      trySetMkRecursive(v, 'sib01', 'sib02')
    }
  })
  return isSuc
}

function imgIsLargeSize(img) {
  if (img.nodeName !== 'IMG') {
    return false
  }
  return isLargerSize(img)
}

function trySetMkRecursive(el, mk1, mk2, clsStr = '') {
  if (
    !el
    || isFxk(el)
    || el.id === 'wp5rh'
  ) {
    return
  }
  
  const isForce = clsStr.includes('dn8x')
  
  el.classList.add('x8x')
  setEleAsRelative(el)
  
  const zArr = [...[el], ...TMK.pEls(el)].reverse()
  const laEl = zArr[zArr.length - 1]
  
  let isAdd = false
  
  for (let pl of zArr) {
    // 避免Dom嵌套过深
    if (zArr.length > 20) {
      break
    }
    if (compareTwoBox(pl, laEl) && pl !== el) {
      if (
        !isHtmlOrBody(pl)
        && (likeAdSize(laEl) || isForce)
      ) {
        setEleAsRelative(pl)
        setCertainCls(pl, clsStr)
        setFxK(pl, mk1)
        isAdd = true
      }
      break
    }
  }
  
  const ipl = el.parentElement
  if (!likeAdSize(el) && !isForce) {
    return
  }
  
  if (
    !isAdd
    && !isHtmlOrBody(el)
  ) {
    setCertainCls(el, clsStr)
    setFxK(el, mk2)
  } else if (
    !isHtmlOrBody(ipl)
    && (
      !isAdd
      || imgIsLargeSize(el)
    )
  ) {
    setEleAsRelative(ipl)
    setCertainCls(ipl, clsStr + ' imgRx')
    setFxK(ipl, mk2)
  }
}

// 屏蔽body直接子元素疑似AD bda
function blockBodyCdYsAd() {
  document.querySelectorAll('body > *, body > * > *, div[class*="fixed"], div[id*="fixed"]').forEach(el => {
    const isLnk = el.tagName === 'A'
    
    const largesAd = isLnk
      && isLargerSize(el)
      && document.querySelectorAll('*').length > 800
      
    const largerZdx = Number(getComputedStyle(el).zIndex) > 9980
    const imgInLnk = el.querySelectorAll('a > img').length > 0
    const hasVdo = el.querySelectorAll('video').length > 0
    
    if (
      largesAd
      || (
        (
          largerZdx
          || hasScript(el)
          || imgInLnk
          || hasVdo
        )
        && isCorner(el)
        && likeAdSize(el)
      )
    ) {
      el.classList.add('dn8x')
      el.style.display = 'none'
      return
    }
    
  })
}

// 屏蔽div中含有script的疑似AD spt
function blockScriptInDiv() {
  document.querySelectorAll('body script').forEach(el => {
    const pEl = el.parentElement
    
    if (isLargerSize(pEl)) {
      removeAsdMk(el, true)
      return
    }
    
    if (isFxk(pEl)) {
      return
    }
    
    let prevNum = 0
    let prevEl = el.previousElementSibling
    while (prevEl) {
      if (prevEl.nodeName === 'SCRIPT') {
        prevNum++
      }
      prevEl = prevEl.previousElementSibling
    }
    
    if (!prevNum && tryBkSiblingAndReturnRes(el)) {
      return
    }
    
    if (hasAdSign(el) || hasAdSign(pEl)) {
      trySetMkRecursive(pEl, 's01', 's02')
    }
  })
}

// 屏蔽疑似ad的box  sltad
function blockYsSlt() {
  const sArr = ['div[class*="-ad" i]', 'div[class*="ad-" i]', 'div[class*="_ad" i]', 'div[class*="ad_" i]',
  'div[id*="-ad" i]', 'div[id*="ad-" i]', 'div[id*="_ad" i]', 'div[id*="ad_" i]',
  'li[class*="-ad" i]', 'li[class*="ad-" i]', 'li[class*="_ad" i]', 'li[class*="ad_" i]',
  'li[id*="-ad" i]', 'li[id*="ad-" i]', 'li[id*="_ad" i]', 'li[id*="ad_" i]',
  'li[class*="mediav" i]',
  'div[class*="mediav" i]',
  'div[data-spm*="ad-" i]', 'div[class*="ave" i]', 'div[aria-label*="ad-" i]', 'div[id*="ave" i]', 'li[class*="ave" i]', 'li[id*="ave" i]', 'li[data-spm*="ave" i]', 'ins[class*="ad" i]', 'ins[id*="ad" i]',
  ]
  document.querySelectorAll(sArr.join(',')).forEach(el => {
    if (isLargerSize(el)) {
      removeAsdMk(el, true)
      return
    }
    
    if (isFxk(el)) {
      return
    }
    
    if (adm(el.id) || adm(el.className)) {
      if (
        hasAdSign(el)
        && (likeAdSize(el) || isFixed(el))
      ) {
        trySetMkRecursive(el, 's51', 's52')
      }
    }
    
  })
}

// 屏蔽注释中包含广告的下一元素  cmt
function blockCommentsHasAd() {
  let dgLmt = 12
  const cdArr = []
  let cdNds = Array.from(document.body?.childNodes || [])
  while (--dgLmt && cdNds.length) {
    cdArr.push(...cdNds)
    cdNds = cdNds.map(v => [...v.childNodes]).flat(1)
  }
  
  const cmtArr = cdArr.filter(v => {
    return v.nodeType === 8
      && v.nodeValue.length < 50
      && /廣告|广告|\bAD\b|\badsense|\badv/.test(v.nodeValue)
      && !/结束|end/i.test(v.nodeValue)
  })
  
  cmtArr.forEach(v => {
    let nextEl = v.nextElementSibling
    while (nextEl && /style|script/i.test(nextEl.nodeName)) {
      nextEl = nextEl.nextElementSibling
    }
    trySetMkRecursive(nextEl, 'C01', 'C02')
  })
}

// 屏蔽iframe广告 ifs
function blockIfrAd() {
  document.querySelectorAll('iframe').forEach(v => {
    const src = attr(v, 'src')
    
    if (isIgnIfr(src)) {
      return
    }
    
    if (isLargerSize(v)) {
      removeAsdMk(v, true)
      return
    }
    
    if (isAdvertiser(src)) {
      trySetMkRecursive(v, 'f01', 'f02', 'ifrIx dn8x')
      return
    }
    
    const pEl = v.parentElement
    const rLen = () => {
      return [
        hasAdSign(v),
        hasAdSign(pEl),
        ysAdRatio(v),
      ].filter(v => !!v).length
    }
    
    if (
      rLen() >= 1
      || (
        ifrHasIfr(v)
        && ysAdRatio(v)
      )
      || (
        isNearTop(v)
        && ysAdRatio(v)
      )
    ) {
      trySetMkRecursive(v, 'f01', 'f02', 'ifrIx')
    }
  })
}

function blockYsVideoAd() {
  document.querySelectorAll('video').forEach(vdo => {
    const rLen = () => {
      return [
        ysVideoAd(vdo),
      ]
        .filter(v => !!v)
        .length
    }
    
    if (
      rLen() >= 1
      || ysAdRatio(vdo)
    ) {
      trySetMkRecursive(vdo, 'vdo01', 'vdo02', 'vdoRb')
    }
    
  })
}

// ims
function blockYsImgInLink() {
  Array.from(document.querySelectorAll('a > img, a > * > img, a > * > * > img')).forEach(el => {
    const link = el.closest('a')
    const pEl = link.parentElement
    const lnkUrl = attr(link, 'href')
     
    if (isIgnLink(lnkUrl)) {
      return
    }
     
    const rArrLen = () => {
      return [
        isBlank(link),
        ysGifAd(el),
        imgSrcYsAd(attr(el, 'src')),
        hasAdSign(link),
        hasAdSign(pEl),
        hasAdSign(el),
        siblingHasAd(link),
        siblingHasAd(el),
        isNearTop(el),
        biggerRatio(el),
      ].filter(v => !!v).length
    }
    
    if (
      (
        rArrLen() >= 2
        && !isSmallSize(el)
        && !likeLogo(el)
      ) || (
        isCorner(el)
        && ysGifAd(el)
      )
    ) {
      trySetMkRecursive(el, 'i01', 'i02')
    }
     
  })
}

function wchDom() {
  TMK.watchDom('body', TMK.throttle(() => {
    tryAddCusSty()
    tryAddGmSty()
  }, 500))
}

function regMainEvt() {
  ['mousemove', 'scroll'].forEach(evt => {
    document.addEventListener(evt, insMainAdFn)
  })
}

function insMainAdFn() {
  const pnow = performance.now()
  let now = Date.now()
  if (now - G.tmpTime > 800 || pnow < 6000) {
    G.tmpTime = now
    if (!isIgnHost()) {
      
      // 链接下的图片
      blockYsImgInLink()
      blockBodyCdYsAd()
      blockYsSlt()
      blockCommentsHasAd()
      // 确认iframe广告
      blockIfrAd()
      blockScriptInDiv()
      // 视频广告
      blockYsVideoAd()
      
    }
  }
}

function initTimer() {
  insMainAdFn()
  setTimeout(initTimer, performance.now() < 6000 ? 350 : 8000)
}

async function initFunc() {
  if (TMK.isMobile()) return
  
  setTimeout(() => {
    if (!isIgnHost()) {
      regMainEvt()
    }
  }, 5e3)
  
  initTimer()
  
  await TMK.loadEl('body')
  tryAppendWp5()
  vm.$mount('#wp5rh')
  GM_registerMenuCommand('打开设置面板', vm.tgCfgDialog)
  GM_registerMenuCommand('清空当前网站添加的CSS', vm.resetCss)
  GM_registerMenuCommand('清空当前网站添加的JS', vm.resetJs)
  wchDom()
}

initFunc()