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

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

目前为 2022-01-14 提交的版本。查看 最新版本

// ==UserScript==
// @name         添加自定义css和js(广告屏蔽等)
// @description  可自定义css选择器屏蔽页面广告,添加js脚本
// @namespace    _cus_ad_sp
// @version      2.8.11
// @author       vizo
// @license      MIT
// updateUrl     http://dwz.win/ac4h
// @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
// @connect      *
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/tiny-oss.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/index.min.js
// @require      https://cdn.jsdelivr.net/npm/@vizoy/[email protected]/index.min.js
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jsQR.min.js
// @noframes

// ==/UserScript==

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

GM_addStyle(`
  .GM-Asd-yisi,
  .GM-Asd-certain {
    background-image: none !important;
  }
  .GM-Asd-yisi::before,
  .GM-Asd-certain::before {
    content: '疑似广告';
    width: 100%;
    height: 100%;
    font-size: 16px;
    color: #ddd;
    display: flex;
    justify-content: center;
    align-items: center;
    font-weight: normal;
    font-style: normal;
    font-family: Arial sans-serif;
    position: absolute;
    top: 0;
    left: 0;
    z-index: 1;
  }
  .GM-Asd-certain::before {
    content: '已屏蔽广告';
  }
  .GM-Asd-certain.baidu::before {
    content: '百度广告';
  }
  .GM-Asd-certain.google::before {
    content: '谷歌广告';
  }
  .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;
  }
  @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;
  }
  #wp5sn [hidden] {
    display: none !important;
  }
  #wp5sn, #wp5sn * {
    margin: 0;
    padding: 0;
    box-sizing: border-box !important;
  }
  #wp5sn {
    width: 28vw;
    height: 68vh;
    min-width: 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;
  }
  #wp5sn .mwp_5c {
    height: 100%;
    display: flex;
    flex-direction: column;
    position: relative;
  }
  #wp5sn .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;
  }
  #wp5sn .mwp_5c.ld::before {
    display: flex;
  }
  #wp5sn .tit1v {
    color: #555;
    font-size: 18px;
    text-align: center;
    margin-bottom: 15px;
  }
  #wp5sn .c7d-item {
    margin-bottom: 10px;
    display: flex;
    flex-direction: column;
    flex-wrap: wrap;
  }
  #wp5sn .allset-item {
    flex: 1;
  }
  #wp5sn .css-item-xh {
    flex: 3;
    transition: flex .3s;
  }
  #wp5sn .js-item-xh {
    flex: 1;
    transition: flex .3s;
  }
  #wp5sn .mwp_5c.expandJS .css-item-xh {
    flex: 1;
  }
  #wp5sn .mwp_5c.expandJS .js-item-xh {
    flex: 3;
  }
  #wp5sn .stiz {
    font-size: 14px;
    color: #555;
    margin-bottom: 3px;
    position: relative;
    text-align: left;
    display: flex;
  }
  #wp5sn .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;
  }
  #wp5sn .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;
  }
  #wp5sn .tamp-cfg-modal .txa-cfg::-webkit-input-placeholder {
    color: #ccc !important;
  }
  #wp5sn .stiz .s0l {
    flex: 1;
  }
  #wp5sn .stiz .s0r {
    color: #09e;
    cursor: pointer;
    user-select: none;
    margin-left: 10px;
  }
  #wp5sn .stiz .s0r.on {
    color: #9a9a9a;
  }
  #wp5sn .oss-zbtn {
    color: #c7c7c7;
    cursor: pointer;
    user-select: none;
    margin-right: 10px;
  }
  #wp5sn .view-all-set {
    color: #09e;
    cursor: pointer;
    user-select: none;
  }
  #wp5sn .st1k {
    display: flex;
  }
  #wp5sn .st1k .s1p {
    flex: 1;
  }
  #wp5sn .st1k .s2p {
    width: 65px;
    color: #09e;
    cursor: pointer;
    text-align: right;
    white-space: nowrap;
    overflow: hidden;
  }
  #wp5sn .inpy {
    flex: 0 0 auto;
    height: 32px;
    border: 1px solid #ddd;
    color: #555;
    background: #fff;
    border-radius: 2px;
    padding: 0 10px;
    outline: none;
  }
  #wp5sn .inpy:focus {
    border: 1px solid #c1c1c1;
  }
  #wp5sn .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";
  }
  #wp5sn .txtr1z::-webkit-input-placeholder {
    color: #c5c5c5;
  }
  #wp5sn .txtr1z:focus {
    border: 1px solid #39e;
  }
  #wp5sn .txtr1z.disabled {
    color: #999;
    background: #f5f5f5;
  }
  #wp5sn .txtr1z::-webkit-scrollbar {
    width: 4px;
  }
  #wp5sn .txtr1z::-webkit-scrollbar-corner,
  #wp5sn .txtr1z::-webkit-scrollbar-track {
    background-color: #fff;
  }
  #wp5sn .txtr1z::-webkit-scrollbar-thumb {
    background: #fff;
  }
  #wp5sn .txtr1z:hover::-webkit-scrollbar-thumb {
    background: #e1e1e1;
  }
  #wp5sn .txtr1z:hover::-webkit-scrollbar-corner,
  #wp5sn .txtr1z:hover::-webkit-scrollbar-track {
    background-color: #f7f7f7;
  }
  #wp5sn .btn-w {
    margin-top: 5px;
    display: flex;
    flex-direction: row-reverse;
  }
  #wp5sn .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;
  }
  #wp5sn .btn-w .c5kbtn.b1 {
    color: #fff !important;
    background: #09e !important;
  }
  #wp5sn .btn-w .c5kbtn.b2 {
    color: #555 !important;
    background: #f1f1f1 !important;
  }
  #wp5sn .btn-w .c5kbtn:first-child {
    margin-right: 0;
  }
  #wp5sn .btn-w .c5kbtn:focus {
    border: 0;
  }
  #wp5sn .btn-w .c5kbtn:hover {
    opacity: 0.9;
  }
`)

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

const G = {
  hostIgnore: /(taobao|jd|tmall|baidu|163|qq)\.com|jsdelivr|npmjs|192\.168|github|fastgit|mozilla|gov\.cn|edu\.cn|mil\.cn/iu,
  ifrIgnore: /(qq|geetest|taobao|aliyundrive|163)\.com|recaptcha/iu,
  linkIgnore: /(github)\.com/iu,
  html: html`
    <div id="wp5sn" 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>
  `,
}
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() {
      let css = `<style class="sty9z1p52">${this.texCssVal}</style>`
      document.head.insertAdjacentHTML('beforeend', css)
      
      Array.from(document.querySelectorAll('.sty9z1p52')).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()
      ) {
        if (this.disJS) {
          localStorage.removeItem('TMK_INIT_FUNC')
        }
        await vio.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 = vio.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 vio.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 )
        }
      }
    },
    
    // 判断是否从远程更新
    judgeIsUpdateCfgFromOss() {
      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 res = this.getAllCfg()
      vio.downloadText(JSON.stringify(res, 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() {
      return 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
          }
        }, {})
    },
    
    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()
            this.initGMStyleClass()
            
            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() {
      window.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
    },
    
    initGMStyleClass() {
      document.querySelectorAll('style').forEach(v => {
        if (!/\w{8}(-\w{4}){3}-\w{12}/.test(v.id)) {
          return
        }
        if (v.textContent.includes('sty9z1p52')) {
          v.classList.add('sty9z1p52')
        }
      })
    },
    
  },
  async mounted() {
    this.initEvt()
    this.initOssVal()
    this.initEkey()
    await this.judgeIsUpdateCfgFromOss()
    this.initAddedWebToTextArea()
    this.initCssJsVal()
    this.initAddedScript()
    this.initGMStyleClass()
  },
})

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

function isIgnLink(linkUrl) {
  return G.ifrIgnore.test(linkUrl)
}

function isIgnIfr(url) {
  return G.ifrIgnore.test(url)
}

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

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

function isStaticStart(linkUrl) {
  return linkHost(linkUrl)?.startsWith('static')
}

function isGif(el) {
  return !!el?.src.endsWith('.gif')
}

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

function H1InH2(url) {
  if (!url) return false
  url = url.replace(/^\/\/.+$/, `${location.protocol}$&`)
  const f_host = location.host.replace(/^([\w-]+\.[\w-]+)$/, 'www.$1')
  const H1 = (f_host.match(/(?<=[\w-]+\.).+/) || [])[0]
  const linkHost = TMK.isUrl(url) ? new URL(url).host : null
  const H2 = linkHost ? linkHost.replace(/^([\w-]+\.[\w-]+)$/, 'www.$1').match(/(?<=[\w-]+\.).+/)[0] : ''
  return H2.includes(H1)
}

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

function isLightbox(link) {
  return Array.from(link.attributes).some(v => v.nodeName.includes('lightbox'))
}
function isSwiper(el) {
  const pEl = el.parentElement
  const gEl = pEl.parentElement
  const inc = (el) => el.className.includes('swiper')
  return inc(el) || inc(pEl) || inc(gEl)
}

// 是否大小看起来不像Ad
function unLikeAdSize(el) {
  if (!TMK.isVisible(el)) {
    return true
  }
  const iw = Number(getComputedStyle(el).width.slice(0, -2))
  const ih = Number(getComputedStyle(el).height.slice(0, -2))
  if (!iw || !ih) {
    return true
  }
  return iw < 150 && ih < 50 ||
    iw < 50 && ih < 150 ||
    iw < 91 && ih < 91 ||
    iw > 180 && ih > 480 ||
    iw > 800 && ih > 140 ||
    iw > 345 && ih > 345 ||
    iw < 10 || ih < 10
}

// 判断是否在页面边角
function isCorner(el) {
  if (!TMK.isVisible(el)) {
    return false
  }
  const dcW = document.body.clientWidth
  const crt = el.getBoundingClientRect()
  const elX = crt.x
  const elW = crt.width
  const elH = crt.height
  const elT = crt.top
  return (elX - 20 <= 0 || elX + elW + 20 >= dcW) && elW > 0 && elH > 0 && elT > 250
}

function hasImgFlag(el) {
  return el.matches('.added_fk')
}

// 内联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'
  }
}

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

function sltAd(el) {
  const regx = /\bA[Dd]s?|A[Dd]s?(\b|$)|\b[Aa]ds?[-_\d]|[-_][Aa]ds?(\b|$)|adver/
  const lbe = el.getAttribute('aria-label')
  return (regx.test(el.id) && !hasUUID(el.id)) ||
      (regx.test(el.className) && !hasUUID(el.className)) ||
      (regx.test(lbe) && !hasUUID(lbe))
     
}

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

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

function hasAdText(el) {
  const isCntAd = (str) => getComputedStyle(el, str).getPropertyValue('content').includes('广告')
  return /广告(\b|$)/m.test(el.textContent) || isCntAd('::before') || isCntAd('::after')
}

function yisiAdLink(el) {
  if (!TMK.isVisible(el)) {
    return false
  }
  const links = Array.from(el.querySelectorAll('a'))
  const aImg = links.some(ex => {
    const imgSize = [...ex.childNodes].some(node => {
      return node.nodeName === 'IMG' &&
        !unLikeAdSize(node) && 
        TMK.isVisible(node)
    })
    return isBlank(ex) &&
      imgSize &&
      !H1InH2(ex.getAttribute('href')) &&
      TMK.isVisible(ex)
  })
  const backImg = links.some(ex => {
    return /^url/i.test(getComputedStyle(ex).backgroundImage) &&
      isBlank(ex) &&
      !H1InH2(ex.getAttribute('href')) &&
      TMK.isVisible(ex)
  })
  return aImg || backImg
}

function likeQrcSize(el) {
  const iw = Number(getComputedStyle(el).width.slice(0, -2))
  const ih = Number(getComputedStyle(el).height.slice(0, -2))
  return iw > 80 && ih > 80 &&
    iw < 385 && ih < 390 &&
    iw / ih > 0.7 && iw / ih < 1.1
}

// - 规则 end-------------------------------

function linkHost(url) {
  return TMK.isUrl(url) ? new URL(url).host : null
}
function compareTwoBox(el1, el2) {
  const gs = getComputedStyle
  const w = gs(el1).width.replace('px', '') - 0
  const h = gs(el1).height.replace('px', '') - 0
  const pw = gs(el2).width.replace('px', '') - 0
  const ph = gs(el2).height.replace('px', '') - 0
  return 36 >= Math.abs(pw - w) && 36 >= Math.abs(ph - h)
}

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

// MKS
// 屏蔽疑似站外广告(a标签下的图片或背景图片)
function inspectYiSiLink() {
  Array.from(document.querySelectorAll('a > img')).forEach(el => {
    const link = el.parentElement
    const linkUrl = link.getAttribute('href')
    
    if (
      H1InH2(linkUrl) &&
      !isGif(el) ||
      isIgnLink(linkUrl) ||
      (!isGif(el) && !isAbsolutePath(linkUrl)) ||
      isLightbox(link) ||
      !isGif(el)
    ) {
      el.classList.remove('added_fk')
    }
    
    if (
      lnkEqImgUrl(linkUrl, el.src) ||
      isIgnLink(linkUrl) ||
      unLikeAdSize(el) && !isGif(el) ||
      hasImgFlag(el) ||
      H1InH2(linkUrl) &&
      !isGif(el) &&
      !sltAd(link)
    ) {
      return
    }
    
    if (
      (
        isBlank(link) &&
        !H1InH2(linkUrl) &&
        isAbsolutePath(linkUrl)
      ) || 
      (
        isBlank(link) && 
        (isGif(el) || sltAd(link))
      )
    ) {
      el.classList.add('added_fk')
      setInlineBlock(link)
      const arr = TMK.getParentEls(el).reverse()
      let isAdd = false
      for (let v of arr) {
        if (compareTwoBox(v, el)) {
          if (!isSwiper(v)) {
            v.classList.add('GM-Asd-yisi')
            setEleAsRelative(v)
            isAdd = true
          }
          break
        }
      }
      if (!isAdd) {
        link.classList.add('GM-Asd-yisi')
        setEleAsRelative(link)
      }
    }
    
  })
}

// 屏蔽body直接子元素疑似AD
function bodyCdYisiAd() {
  document.querySelectorAll('body > *').forEach(el => {
    const cs = getComputedStyle
    const posz = cs(el).position === 'fixed' && (cs(el).zIndex > 90 || cs(el).zIndex === 'auto')
    if (
      posz &&
      !unLikeAdSize(el) &&
      isCorner(el) &&
      yisiAdLink(el) ||
      sltAd(el)
    ) {
      setCertainCls(el)
    }
  })
}

// 屏蔽div中含有script的疑似AD
function scriptInDiv() {
  document.querySelectorAll('script').forEach(el => {
    const pEl = el.parentElement
    if (
      !/html|head|body/i.test(pEl.nodeName) &&
      !unLikeAdSize(pEl) &&
      yisiAdLink(pEl)) {
      setEleAsRelative(pEl)
      setCertainCls(pEl, 'GM-has-script')
    }
  })
}

// 屏蔽包含广告文字或script的box
function hideContainAdTextOrScript() {
  document.querySelectorAll('*[class*="ad" i],*[id*="ad" i],*[aria-label*="ad" i]').forEach(el => {
    if (
      !sltAd(el) ||
      unLikeAdSize(el)
    ) {
      return
    }
    if (hasAdText(el) || hasScript(el) || yisiAdLink(el)) {
      setCertainCls(el)
      setEleAsRelative(el)
    }
  })
}

// 屏蔽iframe疑似广告
function hideIfrYiSiAd() {
  document.querySelectorAll('*[class*="ad" i],*[id*="ad" i], *[aria-label*="ad" i]').forEach(el => {
    const ifrSrcArr = Array.from(el.querySelectorAll('iframe'), el => el.src)
    const isIgnoreIfrSrc = ifrSrcArr.some(c => isIgnIfr(c))
    if (
      isIgnoreIfrSrc ||
      !sltAd(el) ||
      !hasIframe(el)
    ) {
      return
    }
    setEleAsRelative(el)
    el.classList.add('GM-Asd-yisi')
  })
}

// 屏蔽iframe广告
function hideIfrCertainAd() {
  document.querySelectorAll('iframe').forEach(v => {
    if (isIgnIfr(v?.src)) return
    const pEl = v.parentElement
    if (
      Array.from(v.attributes).some(e2 => /baidu|google/i.test(e2.value)) || 
      (
        !unLikeAdSize(v) &&
        yisiAdLink(pEl)
      )
    ) {
      if (!/body|html/i.test(pEl.tagName)) {
        setEleAsRelative(pEl)
        setCertainCls(pEl)
      }
    }
  })
}

// 屏蔽二维码
function judgeQrCode() {
  const imgs = Array.from(document.querySelectorAll('img'))
  const eachCount = imgs.reduce((acc, v) => {
    return acc += likeQrcSize(v) ? 1 : 0
  }, 0)
  
  if (eachCount > 30) return
  
  imgs.forEach(el => {
    if (el.classList.contains('qrcimg')) return
    if (!likeQrcSize(el)) return
    if (!el.src) return
    
    const pEl = el.parentElement
    const cav = document.createElement('canvas')
    const ctx = cav.getContext('2d')
    
    el.classList.add('qrcimg')
    GM_xmlhttpRequest({
      url: el.src,
      method: 'get',
      responseType: 'blob',
      onload: function(xhr) {
        try {
          const bloburl = URL.createObjectURL(xhr.response)
          const img = new Image()
          img.crossOrigin = 'anonymous'
          img.src = bloburl
          img.onload = () => {
            const w = img.width
            const h = img.height
            cav.width = w
            cav.height = h
            ctx.drawImage(img, 0, 0)
            const imgdata = ctx.getImageData(0, 0, w, h)
            const res = jsQR(imgdata.data, w, h)
            if (res) {
              if (compareTwoBox(pEl, el)) {
                setCertainCls(pEl, 'qrc7Box')
                setEleAsRelative(pEl)
              } else {
                TMK.wrap(el, '<div class="qrc7Box GM-Asd-certain" style="position:relative;"></div>')
              }
            }
            URL.revokeObjectURL(bloburl)
          }
        } catch (e) {}
      },
    })
    
  })
}

function initBlockFunc() {
  if (!isIgnHost()) {
    // 屏蔽疑似站外广告
    inspectYiSiLink()
    bodyCdYisiAd()
    // 屏蔽包含广告文字的box
    hideContainAdTextOrScript()
    // 屏蔽疑似iframe广告
    hideIfrYiSiAd()
    // 屏蔽iframe广告
    hideIfrCertainAd()
    scriptInDiv()
  }
  vio.deferCall(initBlockFunc, 200, 80)
}

function initFunc() {
  if (!vio.isMobile()) {
    const funcStr = localStorage.getItem('TMK_INIT_FUNC')
    if (funcStr) {
      try {
        vio.evalFunc(funcStr, TMK.gm())
      } catch(e) {
        TMK.log( e.message )
      }
    }
    
    document.addEventListener('DOMContentLoaded', () => {
      const wp5 = document.getElementById('wp5sn')
      if (!wp5) {
        document.body.insertAdjacentHTML('beforeend', G.html)
      }
      vm.$mount('#wp5sn')
      GM_registerMenuCommand('打开设置面板', vm.tgCfgDialog)
      GM_registerMenuCommand('清空当前网站添加的CSS', vm.resetCss)
      GM_registerMenuCommand('清空当前网站添加的JS', vm.resetJs)
      initBlockFunc()
    }, false)
    
    window.addEventListener('mousemove', vio.throttle(() => {
      if (!isIgnHost()) {
        inspectYiSiLink()
      }
    }, 3000))
  }
}

initFunc()

QingJ © 2025

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