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

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

目前為 2021-01-14 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

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

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         添加自定义css和js(广告屏蔽等)
// @description  可自定义css选择器屏蔽页面广告,添加js脚本
// @namespace    _cus_ad_sp
// @version      1.1.0
// @author       vizo
// updateUrl     hhttp://dwz.win/ac4h
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/vue.min.js
// @require      https://cdn.jsdelivr.net/npm/vio-utils/utils.js
// @include      /https?\:\/\/(?!localhost|127.0.0.1|192.168|greasyfork).*/
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_deleteValue
// @grant        GM_listValues
// @grant        GM_registerMenuCommand
// @noframes

// ==/UserScript==

;(function() {
  if (vio.isMobile()) return
  let k = GM_getValue(`cus_${location.host}`) || {}
  if (k.css && !k.disCSS) {
    GM_addStyle(`sty9z1p52{}\n${k.css}\n`)
  }
})()

GM_addStyle(`
  #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;
  }
  #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 .css-item-xh,
  #wp5sn .js-item-xh,
  #wp5sn .allset-item {
    flex: 1;
  }
  #wp5sn .stiz {
    font-size: 14px;
    color: #555;
    margin-bottom: 3px;
    position: relative;
    display: flex;
  }
  #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 .view-all-set {
    color: #09e;
    cursor: pointer;
    user-select: none;
    position: absolute;
    top: 0;
    right: 0;
  }
  #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;
    border-radius: 2px;
    padding: 0 10px;
    outline: none;
  }
  #wp5sn .inpy:focus {
    border: 1px solid #c1c1c1;
  }
  #wp5sn .txtr1z {
    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 #c1c1c1;
  }
  #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;
    color: #555;
    background-color: #f1f1f1;
    cursor: pointer;
    outline: none;
    border: 0;
  }
  #wp5sn .btn-w .c5kbtn.b1 {
    color: #fff;
    background-color: #09e;
  }
  #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 G = {
  html: `
    <div id="wp5sn" v-show="dialog1s">
      <div class="mwp_5c">
        <div class="tit1v">设置</div>
        <div class="c7d-item">
          <p class="stiz">打开面板快捷键 <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" placeholder="请输入css代码"></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" placeholder="请输入js代码"></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="allAddedStr"></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: '',
      // 已添加的网站
      allAddedStr: '',
      disCSS: false,
      disJS: false,
    }
  },
  computed: {
    viewSetText() {
      return this.showAllSet ? '查看当前网站' : '查看全部网站'
    },
    addedNum() {
      return this.allAddedStr
        .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.slice(0, 1) : ''
      GM_setValue('cusBKEkey', this.eKey)
    },
    dialog1s(nVal) {
      if (!nVal) {
        this.showAllSet = false
      }
    },
  },
  methods: {
    setGmVal(val) {
      return GM_setValue(`cus_${location.host}`, val)
    },
    getGmVal() {
      return GM_getValue(`cus_${location.host}`) || {}
    },
    hdlSave() {
      if (!this.showAllSet) {
        this.saveCss()
        this.saveJs()
        this.initAllAdded()
      } else {
        this.updateAllAdded()
      }
      this.dialog1s = false
    },
    hdlCancel() {
      this.dialog1s = false
    },
    // 禁用css
    hdlTgDisCss() {
      this.disCSS = !this.disCSS
    },
    // 禁用js
    hdlTgDisJs() {
      this.disJS = !this.disJS
    },
    saveCss() {
      if ( !this.texCssVal.trim() && !Object.keys(this.getGmVal()).length ) {
        return
      }
      let css = `<style class="sty9z1p52">${this.texCssVal}</style>`
      document.head.insertAdjacentHTML('beforeend', css)
      
      this.setGmVal({
        ...this.getGmVal(),
        css: this.texCssVal,
        disCSS: this.disCSS,
      })
      
      Array.from(document.querySelectorAll('.sty9z1p52')).forEach((v, i, y) => {
        if (i !== y.length - 1) {
          v.remove()
        } else {
          v.disabled = this.disCSS
        }
      })
    },
    saveJs() {
      const gm = this.getGmVal()
      if (!!gm.disJS === this.disJS && gm.js === this.texJsVal && gm !== undefined) {
        return
      }
      this.setGmVal({
        ...gm,
        js: this.texJsVal,
        disJS: this.disJS,
      })
      location.reload()
    },
    updateAllAdded() {
      const gmArr = GM_listValues()
        .filter(v => v.includes('cus_'))
        .map(v => v.slice(4))
      const cArr = this.allAddedStr.split('\n')
      gmArr.forEach(v => {
        if (!cArr.some(c => v === c)) {
          GM_deleteValue(`cus_${v}`)
        }
      })
    },
    
    tgCfgDialog() {
      this.dialog1s = !this.dialog1s
    },
    hdlViewAllSet() {
      this.showAllSet = !this.showAllSet
    },
    // 导入配置
    hdlImportCfg() {
      this.$refs.inp_hide.click()
    },
    // 导出配置
    hdlExportCfg() {
      let res = GM_listValues().reduce((acc, v) => {
        return {
          ...acc,
          [v]: GM_getValue(v)
        }
      }, {})
      vio.downloadText(JSON.stringify(res, null, 2), '1.json')
    },
    
    hdlUpFile(e) {
      let file = e.target.files[0]
      if (file) {
        let reader = new FileReader()
        reader.readAsText(file, 'utf-8')
        reader.onload = (evt) => {
          let res = null
          try {
            oUp = JSON.parse(evt.target.result)
            const upKeys = Object.keys(oUp)
            const gmKeys = GM_listValues()
            
            gmKeys.forEach(v => {
              GM_deleteValue(v)
            })
            
            upKeys.forEach(v => {
              GM_setValue(v, oUp[v])
            })
            // 上传成功
            this.initCssJsVal()
            this.initAllAdded()
            this.initAddedScript()
            this.initGMStyleClass()
            setTimeout(() => {
              location.reload()
            }, 200)
          } catch (e) {
            // 上传失败
          }
        }
      }
    },
    resetCss() {
      this.texCssVal = ''
      this.disCSS = false
      this.setGmVal({
        ...this.getGmVal(),
        css: '',
        disCSS: false,
      })
      location.reload()
    },
    resetJs() {
      this.texJsVal = ''
      this.disJS = false
      this.setGmVal({
        ...this.getGmVal(),
        js: '',
        disJS: false,
      })
      location.reload()
    },
    initEvt() {
      window.addEventListener('keydown', e => {
        if (
          !(/text|search|number|password|tel|url|email/.test(e.target.type)) &&
          e.target.tagName !== 'TEXTAREA' &&
          !e.altKey && 
          !e.ctrlKey &&
          e.key === this.eKey
        ) {
          this.tgCfgDialog()
        }
        if (/esc/i.test(e.key)) {
          this.dialog1s = false
        }
      })
    },
    initEkey() {
      this.eKey = GM_getValue('cusBKEkey') || ''
    },
    initCssJsVal() {
      this.texCssVal = this.getGmVal().css || ''
      this.texJsVal = this.getGmVal().js || ''
      this.disCSS = this.getGmVal().disCSS
      this.disJS = !!this.getGmVal().disJS
    },
    initAllAdded() {
      let gmArr = GM_listValues()
        .filter(v => v.includes('cus_'))
        .map(v => v.slice(4))
      this.allAddedStr = gmArr.join('\n')
    },
    initAddedScript() {
      let js = this.getGmVal().js
      let isDisabled = this.getGmVal().disJS
      if (js && !isDisabled) {
        let st = document.createElement('script')
        st.textContent = js
        document.body.appendChild(st)
      }
    },
    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')
        }
      })
    },
  },
  mounted() {
    this.initEvt()
    this.initEkey()
    this.initCssJsVal()
    this.initAllAdded()
    this.initAddedScript()
    this.initGMStyleClass()
  },
})

const initFunc = () => {
  if (vio.isMobile()) return
  document.addEventListener('DOMContentLoaded', () => {
    document.body.insertAdjacentHTML('beforeend', G.html)
    vm.$mount('#wp5sn')
    GM_registerMenuCommand('打开设置面板', vm.tgCfgDialog)
    GM_registerMenuCommand('清空当前网站添加的CSS', vm.resetCss)
    GM_registerMenuCommand('清空当前网站添加的JS', vm.resetJs)
  }, false)
}

initFunc()