Greasy Fork

Greasy Fork is available in English.

bilibili三连

推荐投币收藏一键三连

当前为 2020-12-09 提交的版本,查看 最新版本

// ==UserScript==
// @name         bilibili三连
// @version      0.0.12
// @include      https://www.bilibili.com/video/av*
// @include      https://www.bilibili.com/video/BV*
// @description  推荐投币收藏一键三连
// @grant        GM_getValue
// @grant        GM_setValue
// @run-at       document-idle
// @namespace    http://greasyfork.icu/users/164996
// ==/UserScript==
const click = s => {
  if (!s) return
  if (s instanceof HTMLElement) s.click()
  else {
    const n = document.querySelector(s)
    if (!n) return
    n.click()
  }
  return true
}
const waitForAll = (selectors, delay = 500, timeout = 50000) =>
  new Promise(resolve => {
    let max_times = 1 + timeout / delay
    let times = 0
    let nodes
    const f = () => {
      nodes = selectors.map(i => document.querySelector(i))
      times = times + 1
      if (Object.values(nodes).every(v => v != null)) {
        resolve(nodes)
      } else if (times >= max_times) {
        resolve([])
      } else {
        setTimeout(f, delay)
      }
    }
    f()
  })
;(async () => {
  const [position, app, _] = await waitForAll([
    '#arc_toolbar_report span.collect',
    'div#app>div.v-wrap',
    'span.bilibili-player-video-info-people-text'
  ])
  if (!position) return
  // state
  let like = GM_getValue('like', true)
  let coin = GM_getValue('coin', 0)
  let collect = GM_getValue('collect', true)
  let collection = GM_getValue('collection', '输入收藏夹名')
  // style
  const css = `
    span#sanlian > div {
      display: none;
      position: absolute;
      color: SlateGray;
      background: white;
      border: 1px solid #e5e9ef;
      box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.14);
      border-radius: 2px;
      padding: 1em;
      cursor: default;
      z-index: 2;
    }
    span#sanlian span[id^='sanlian_'] * {
      color: SlateGrey;
      cursor: pointer;
    }
    span#sanlian span[id^='sanlian_'].sanlian_on * {
      color: SlateBlue;
    }
    span#sanlian span[id^='sanlian_']:hover * {
      color: DarkSlateBlue;
    }
    span#sanlian > div > input {
      border: 0;
      border-bottom: 1px solid;
    }
    span#sanlian span#sanlian_coin i {
      margin: 0;
    }
    .video-toolbar .ops > span {
      width: 88px;
    }
    .bili-dialog-m {
      display: block;
    }
  `
  const style = document.createElement('style')
  style.type = 'text/css'
  style.appendChild(document.createTextNode(css))
  document.head.appendChild(style)
  // remove leading space of coin text
  const coin_text = document.querySelector('#arc_toolbar_report span.coin > i')
    .nextSibling
  if (coin_text.nodeType == Node.TEXT_NODE) {
    coin_text.textContent = coin_text.textContent.trim()
  }
  position.insertAdjacentHTML(
    'afterend',
    `<span id=sanlian title=推荐硬币收藏><i class=van-icon-tuodong></i>三连
    <div>
        <span id=sanlian_like class=${like ? 'sanlian_on' : ''}>
            <i class=van-icon-videodetails_like ></i>
        </span>
        <span id=sanlian_coin class=${coin > 0 ? 'sanlian_on' : ''}>
            <i class=van-icon-videodetails_throw></i><span>x${coin}</span>
        </span>
        <span id=sanlian_collect class=${collect ? 'sanlian_on' : ''}>
            <i class=van-icon-videodetails_collec></i>
        </span>
        <input type="text" value=${collection}>
      </div>
    </span>`
  )
  const s = document.querySelector('#sanlian')
  const i = document.querySelector('#sanlian>i')
  const x = document.querySelector('#sanlian>div')
  const like_btn = document.querySelector('#sanlian_like')
  const coin_btn = document.querySelector('#sanlian_coin')
  const coin_value = document.querySelector('#sanlian_coin span')
  const collect_btn = document.querySelector('#sanlian_collect')
  const collect_value = document.querySelector('#sanlian input')
  let dialog_style = style.sheet.rules
  dialog_style = dialog_style[dialog_style.length - 1].style
  like_btn.addEventListener('click', function() {
    const c = this.classList
    like = !like
    c.toggle('sanlian_on')
    GM_setValue('like', like)
  })
  coin_btn.addEventListener('click', function() {
    const c = this.classList
    coin = (coin + 1) % 3
    coin_value.innerHTML = 'x' + coin
    if (coin !== 2) c.toggle('sanlian_on')
    GM_setValue('coin', coin)
  })
  coin_btn.addEventListener('contextmenu', function(e) {
    e.preventDefault()
    const c = this.classList
    coin = (coin + 2) % 3
    coin_value.innerHTML = 'x' + coin
    if (coin !== 1) c.toggle('sanlian_on')
    GM_setValue('coin', coin)
  })
  collect_btn.addEventListener('click', function() {
    const c = this.classList
    collect = !collect
    c.toggle('sanlian_on')
    GM_setValue('collect', collect)
  })
  collect_value.addEventListener('keyup', function() {
    collection = collect_value.value
    GM_setValue('collection', collection)
  })
  s.addEventListener('mouseover', () => {
    x.style.display = 'block'
  })
  s.addEventListener('mouseout', () => {
    x.style.display = 'none'
  })
  s.addEventListener('click', async e => {
    let t
    // http request timeout
    const timeout = 3500
    if (![s, i].includes(e.target)) return
    dialog_style.display = 'none'
    let dt = setTimeout(() => {
      dialog_style.display = 'block'
    }, timeout)
    // like
    if (like) click('#arc_toolbar_report span.like:not(.on)')
    // coin
    if (coin > 0 && click('#arc_toolbar_report span.coin:not(.on)')) {
      await new Promise(resolve => {
        new MutationObserver(function() {
          this.disconnect()
          if (coin === 1) click('.mc-box.left-con')
          else click('.mc-box.right-con')
          t = setTimeout(() => {
            click('div.bili-dialog-m div.coin-operated-m i.close')
          }, timeout)
          new MutationObserver(function() {
            this.disconnect()
            clearTimeout(t)
            resolve()
          }).observe(app, { childList: true })
          setTimeout(() => {
            click('div.coin-bottom > span')
          }, 0)
        }).observe(app, { childList: true, subtree: true })
      })
    }
    // collect
    if (collect && click('#arc_toolbar_report span.collect')) {
      await new Promise(resolve => {
        new MutationObserver(function(e) {
          if (e[0].target.nodeName !== 'UL') return
          this.disconnect()
          t = document.querySelectorAll(
            'div.collection-m div.group-list input+i'
          )
          // match or first
          t =
            [...t].find(
              i => i.nextElementSibling.textContent.trim() === collection
            ) || t[0]

          // already collect
          if (!t || t.previousElementSibling.checked || !click(t)) {
            click('i.close')
            return resolve()
          }
          // offline fallback
          t = setTimeout(() => {
            click('i.close')
          }, timeout)
          // wait for dialog close
          new MutationObserver(function() {
            this.disconnect()
            clearTimeout(t)
            resolve()
          }).observe(app, { childList: true })
          const b = document.querySelector(
            'div.collection-m button.submit-move'
          )
          if (b.hasAttribute('disabled')) {
            new MutationObserver(function() {
              this.disconnect()
              click(b)
            }).observe(b, { attributes: true })
          } else click(b)
        }).observe(app, { childList: true, subtree: true })
      })
    }
    clearTimeout(dt)
    dialog_style.display = 'block'
  })
})()