// ==UserScript==
// @name Fanatical Get Key
// @namespace http://tampermonkey.net/
// @version 0.3
// @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 => {
if(Array.isArray(obj[bundleName])) {
const arr = obj[bundleName]
str += bundleName + (flag ? '\t' : '\n') + arr.reduce((a, b, idx) => a + b.key + (flag ? idx === arr.length - 1 ? '\n' : '\t' : '\n'), '')
} else {
str += (str ? '\n' : '') + '【' + bundleName + '】\n'
Object.keys(obj[bundleName]).forEach(item => {
const arr = obj[bundleName][item]
str += item + (flag ? '\t' : '\n') + arr.reduce((a, b, idx) => a + b.key + (flag ? idx === arr.length - 1 ? '\n' : '\t' : '\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 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(!obj[item.pickAndMix][item.name]) {
obj[item.pickAndMix][item.name] = []
}
gameCount++
obj[item.pickAndMix][item.name].push({
atok,
oid: order_id,
iid: item.iid,
serialId: item.serialId,
key: item.key
})
} else {
if(!obj[item.name]) {
obj[item.name] = item.bundles.length ? {} : []
}
if(item.bundles.length) {
item.bundles.forEach(item2 => {
item2.games.forEach(item3 => {
if(item3.status === 'refunded') return
if(!obj[item.name][item3.name]){
obj[item.name][item3.name] = []
}
item3.bid = item._id
item3.oid = order_id
gameCount++
obj[item.name][item3.name].push({
atok,
bid: item._id,
oid: order_id,
iid: item3.iid,
serialId: item3.serialId,
key: item3.key
})
})
})
} else {
gameCount++
obj[item.name].push({
atok,
oid: order_id,
iid: item.iid,
serialId: item.serialId,
key: item.key
})
}
}
})
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) {
if(isRun) return
const [, order] = this.previousElementSibling.href.match(/orders\/(\w+)/)
if(!order) return alert('order出错')
isRun = true
ele.innerHTML = `一键刮key中...`
getData(await request(`https://www.fanatical.com/api/user/orders/${order}`), this, ele)
}
const init = (list) => {
list.forEach(item => {
if(item.previousElementSibling.innerText === 'COMPLETE' && item.childElementCount === 1) {
item.classList.add('zf-has')
const div = document.createElement('div')
div.innerHTML = '<div class="zf-coustom">刮key(横向序列)</div><div class="zf-coustom">刮key(纵向序列)</div>'
div.firstElementChild.onclick = clickEvent.bind(div, div.firstElementChild)
div.lastElementChild.onclick = clickEvent.bind(div, div.lastElementChild)
item.appendChild(div)
}
})
}
const content = document.querySelector('#root')
const observer = new MutationObserver((mutationsList) => {
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 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;
}
`)
})();