Fanatical Get Key

F站刮key

目前為 2021-09-21 提交的版本,檢視 最新版本

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Fanatical Get Key
// @namespace    http://tampermonkey.net/
// @version      0.5
// @description  F站刮key
// @author       Ku Mi
// @match        https://www.fanatical.com/*
// @icon         https://cdn.fanatical.com/production/icons/favicon-32x32.png
// @grant        GM_addStyle
// ==/UserScript==
(function() {
  let gameCount = 0
  let isRun = false
  const render = (obj, flag) => {
    let str = ''
    Object.keys(obj).forEach(bundleName => {
        str += (str ? '\n' : '') + '【' + bundleName + '】\n'
        const arr = Object.keys(obj[bundleName])
        if(flag) {
            let longArrName = arr.reduce((a, b) => {
                return obj[bundleName][a].length >= obj[bundleName][b].length ? a : b
            })
            arr.splice(arr.findIndex(item => item === longArrName), 1)
            str += [longArrName, ...arr].join('\t') + '\n'
            const longArr = obj[bundleName][longArrName]
            longArr.forEach((item, index) => {
                const keyArr = []
                keyArr.push(item.key)
                arr.forEach(item2 => {
                    const data = obj[bundleName][item2][index]
                    if(data) {
                        keyArr.push(data.key || '')
                    } else {
                        keyArr.push('')
                    }
                })
                str += keyArr.join('\t') + '\n'
            })
        } else {
          arr.forEach(item => {
            const gameArr = obj[bundleName][item]
            const temp = [item]
            gameArr.forEach(item2 => temp.push(item2.key))
            str += temp.join('\n') + '\n'
        })
        }
    })
    const input = document.createElement('textarea')
    document.documentElement.appendChild(input)
    input.value = str
    input.select()
    document.execCommand('copy')
    input.remove()
    alert('复制成功')
  }
  const myPromise = (item) => {
    return new Promise((resolve) => {
      if(item.key) {
        item.key += `(已刮过)`
        resolve(item)
      }else {
        delete item.key
        resolve(request(`https://www.fanatical.com/api/user/orders/redeem`, { method: 'POST', body: JSON.stringify(item) }))
      }
    })
  }
  const redeem = (data, obj, div, count, ele) => {
      data.forEach(item => {
          myPromise(item).then(res => {
              item.key = res.key
          }).catch(() => (item.key = '请求失败')).finally(() => {
            ele.innerHTML = `一键刮key(${count} / ${count - --gameCount})`
            if(gameCount <= 0) {
              render(obj, div.firstElementChild === ele)
              gameCount = 0
              isRun = false
            }
          })
      })
  }
  const func = (obj, name, item, atok, order_id, bid) => {
      if(item.status === 'refunded') return
      if(!obj[name][item.name]) {
          obj[name][item.name] = []
      }
      gameCount++
      obj[name][item.name].push(Object.assign({
          atok,
          oid: order_id,
          iid: item.iid,
          serialId: item.serialId,
          key: item.key
      }, bid ? {bid} : null))
  }
  const getData = async ({status, _id : order_id, items: orderList}, div, ele) => {
    if(status !== 'COMPLETE') return alert('订单未完成')
    const obj = {}
    const atok = window.localStorage.bsatok
    orderList.forEach(item => {
        if(item.status === 'refunded') return
        if(item.pickAndMix) {
            if(!obj[item.pickAndMix]) {
                obj[item.pickAndMix] = {}
            }
            if(item.bundles.length) {
                item.bundles.forEach(gameList => {
                    gameList.games.forEach(item2 => {
                        func(obj, item.pickAndMix, item2, atok, order_id)
                    })
                })
            } else {
                func(obj, item.pickAndMix, item, atok, order_id)
            }

        } else {
            if(item.bundles.length) {
                if(!obj[item.name]) {
                    obj[item.name] = {}
                }
                item.bundles.forEach(item2 => {
                    item2.games.forEach(item3 => {
                        func(obj, item.name, item3, atok, order_id, item._id)
                    })
                })
            } else {
                if(!obj['单个游戏']) {
                    obj['单个游戏'] = {}
                }
                func(obj, '单个游戏', item, atok, order_id)
            }
        }
    })
    ele.innerHTML = `一键刮key(${gameCount} / 0)`
    Object.keys(obj).forEach(name => {
      if(Array.isArray(obj[name])) {
        redeem(obj[name], obj, div, gameCount, ele)
      } else {
         Object.keys(obj[name]).forEach(item => {
           redeem(obj[name][item], obj, div, gameCount, ele)
         })
      }
    })
  }
  const request = async (url, {method = 'GET', body = null} = {}) => {
      const result = await fetch(url, {
          method,
          body,
          headers: {
              anonid: JSON.parse(window.localStorage.bsanonymous).id,
              authorization: JSON.parse(window.localStorage.bsauth).token,
              'content-type': 'application/json; charset=utf-8'
          }
      })
      return await result.json()
  }
  async function clickEvent(ele, item, order) {
      if(isRun) return
      isRun = true
      ele.innerHTML = `一键刮key中...`
      getData(await request(`https://www.fanatical.com/api/user/orders/${order}`), this, ele)
   }
  const init = (list) => {
      setTimeout(() => {
          list.forEach(item => {
              if(item.previousElementSibling.innerText === 'COMPLETE' && item.childElementCount === 1) {
                  item.classList.add('zf-has')
                  const [, order] = item.parentElement.parentElement.href.match(/orders\/(\w+)/)
                  const me = document.querySelector(`.v-${order}`)
                  if(me) return
                  const div = document.createElement('div')
                  div.className = `zf-wrap v-${order}`
                  div.style = `position: absolute;right: 20px;top: ${item.offsetTop}px;`
                  div.innerHTML = '<div class="zf-coustom">刮key(横向序列)</div><div class="zf-coustom">刮key(纵向序列)</div>'
                  div.firstElementChild.onclick = clickEvent.bind(div, div.firstElementChild, item, order)
                  div.lastElementChild.onclick = clickEvent.bind(div, div.lastElementChild, item, order)
                  document.documentElement.appendChild(div)
              }
          })
      }, 1000)
  }
      const content = document.querySelector('#root')
      const observer = new MutationObserver((mutationsList) => {
          if(/orders\/\w+/.test(location.pathname)) {
            document.querySelectorAll('.zf-wrap').forEach(item => item.remove())
          }
          for(let mutation of mutationsList) {
              if (mutation.type === 'childList' && mutation.addedNodes.length) {
                  const list = mutation.target.querySelectorAll('.d-none.d-md-block.action-col:not(.zf-has)')
                  if(list.length) init(list)
              } else if(mutation.type === 'childList' && mutation.removedNodes.length) {
                 mutation.removedNodes.forEach(item => {
                     console.log(item.firstElementChild)
                   if(item.className = 'table-item' && item.firstElementChild && item.firstElementChild.nodeName === 'A') {
                     const [, order] = item.firstElementChild.href.match(/orders\/(\w+)/)
                     const me = document.querySelector(`.v-${order}`)
                     if(me) me.remove()
                   }
                 })
              }
          }
      })
      const config = { childList: true, subtree: true }
      observer.observe(content, config)
GM_addStyle(`
.zf-coustom {
 padding: 5px 15px;
 border-radius: 5px;
 background-color: #212121;
 cursor: pointer;
 font-size: 14px;
 text-align: center;
 color: #fff;
 margin-top: 10px;
}
.zf-coustom:hover {
 opacity: 0.5;
}
`)
})();