Fanatical Get Key

F站刮key

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

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

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Fanatical Get Key
// @namespace    http://tampermonkey.net/
// @version      0.8
// @description  F站刮key
// @author       Ku Mi
// @match        https://www.fanatical.com/*
// @icon         https://cdn.fanatical.com/production/icons/favicon-32x32.png
// @grant        GM_addStyle
// @license MIT
// ==/UserScript==
(function() {
    let gameCount = 0
    let isRun = false
    const render = (obj) => {
      let str = ''
      Object.keys(obj).forEach(bundleName => {
          str += (str ? '\n' : '') + '【' + bundleName + '】\n'
          const arr = Object.keys(obj[bundleName])
          arr.forEach(item => {
            const gameArr = obj[bundleName][item]
            str += gameArr.reduce((str, item2) => (str += item + ',' + item2.key + '\n'),'')
        })
        //   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]
        //           str += gameArr.reduce((str, item2) => (str += item + ',' + item2.key + '\n'),'')
        //       })
        //   }
      })
      const input = document.querySelector('.zf-redeem-text')
      input.value = str
    }
    const myPromise = (item) => {
        return new Promise(async (resolve) => {
            if(item.key) {
                item.key += `(已刮过)`
                resolve(item)
            } else {
                delete item.key
                try {
                    resolve(await request(`https://www.fanatical.com/api/user/orders/redeem`, { method: 'POST', body: JSON.stringify(item) }))
                } catch(e) {
                    resolve({key: '请求失败'})
                }
            }
        })
    }
    const redeem = async (data, obj, count, ele) => {
        const result = await Promise.all(data.map(item => myPromise(item)))
        result.forEach((item, index) => {
            gameCount--
            data[index].key = item.key
            if(ele) ele.innerHTML = `一键刮key(${count} / ${count - gameCount})`
        })
        if(gameCount <= 0) {
            render(obj)
            gameCount = 0
            isRun = false
        }
    }
    const func = (obj, name, item, atok, order_id, bid) => {
        if(item.status === 'refunded' || item.type === 'software') 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 = ({status, _id : order_id, items: orderList}, obj = {}) => {
      if(status !== 'COMPLETE') return alert('订单未完成')
      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)
              }
          }
      })
      return obj
    }
    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(order) {
        if(isRun) return
        isRun = true
        this.innerHTML = `一键刮key中...`
        const obj = getData(await request(`https://www.fanatical.com/api/user/orders/${order}`))
        this.innerHTML = `一键刮key(${gameCount} / 0)`
        let count = gameCount
        for(let name in obj) {
            for(let item in obj[name]) {
                await redeem(obj[name][item], obj, count, this)
            }
        }
    }
      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.firstElementChild.onclick = clickEvent.bind(div.firstElementChild, order)
                      document.documentElement.appendChild(div)
                  }
              })
          }, 1000)
      }
      const initRedeem = (container) => {
          const ele = document.createElement('div')
          ele.className = 'zf-redeem-wrap'
          ele.innerHTML = `
           <textarea class="zf-redeem-text" placeholder="兑换礼品码(适用于整包,单游戏, 一个ip最多10个,有冷却)"></textarea>
           <button class="btn btn-primary zf-redeem">兑换</button>
         `
          container.insertBefore(ele, container.firstElementChild)
          ele.querySelector('.zf-redeem').onclick = async () => {
              if(isRun) return
              const codes = ele.firstElementChild.value.split('\n').filter(item => {
                const code = item.trim()
                return /\w{14}/.test(code)
              }).slice(0, 10)
              let flag = window.confirm(`一次最多兑换10个,数量:${codes.length}\n${codes.join('\n')}`)
              if(!flag || !codes.length) return
              isRun = true
              let messageList = []
              for(let i = 0; i < codes.length; i++) {
                  let msg = codes[i]
                  let id
                  try {
                      const { _id, message } = await request('https://www.fanatical.com/api/user/redeem-code/redeem', {method: 'POST', body: JSON.stringify({code: codes[i]})})
                      if(_id) {
                       msg += '-----成功'
                       id =_id
                      }
                      if(message) msg += `-----${message}`
                  } catch(e) {
                      msg += `-----${e}`
                      console.error(e)
                  }
                  messageList.push({msg, id})
              }
              flag = window.confirm(`兑换详情:\n是否需要兑换key\n${messageList.map(item => item.msg).join('\n')}`)
              if(!flag) return
              messageList = messageList.filter(item => item.id)
              const obj = {}
              for(let i = 0; i < messageList.length; i ++) {
                 const item = messageList[i]
                 getData(await request(`https://www.fanatical.com/api/user/orders/${item.id}`), obj)
              }
              let count = gameCount
              for(let name in obj) {
                  for(let item in obj[name]) {
                   await redeem(obj[name][item], obj, count)
                  }
              }
          }
      }
        const content = document.querySelector('#root')
        const observer = new MutationObserver((mutationsList) => {
            if(!/orders\/?$/.test(location.pathname)) {
              document.querySelectorAll('.zf-wrap').forEach(item => item.remove())
              return
            }
            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)
                    const container = mutation.target.querySelector('.account-content.orders-and-keys')
                    if(!container) continue
                    if(container.querySelector('.zf-redeem-wrap')) continue
                    initRedeem(container)
                } else if(mutation.type === 'childList' && mutation.removedNodes.length) {
                   mutation.removedNodes.forEach(item => {
                     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;
  }
  .zf-redeem-wrap {
   display: flex;
   align-items: flex-end;
  }
  .zf-redeem-text {
    width: 500px;
    height: 250px;
    margin-right: 20px;
    outline: none;
  }
  `)
  })();