Fanatical Get Key

F站刮key

  1. // ==UserScript==
  2. // @name Fanatical Get Key
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.8
  5. // @description F站刮key
  6. // @author Ku Mi
  7. // @match https://www.fanatical.com/*
  8. // @icon https://cdn.fanatical.com/production/icons/favicon-32x32.png
  9. // @grant GM_addStyle
  10. // @license MIT
  11. // ==/UserScript==
  12. (function() {
  13. let gameCount = 0
  14. let isRun = false
  15. const render = (obj) => {
  16. let str = ''
  17. Object.keys(obj).forEach(bundleName => {
  18. str += (str ? '\n' : '') + '【' + bundleName + '】\n'
  19. const arr = Object.keys(obj[bundleName])
  20. arr.forEach(item => {
  21. const gameArr = obj[bundleName][item]
  22. str += gameArr.reduce((str, item2) => (str += item + ',' + item2.key + '\n'),'')
  23. })
  24. // if(flag) {
  25. // let longArrName = arr.reduce((a, b) => {
  26. // return obj[bundleName][a].length >= obj[bundleName][b].length ? a : b
  27. // })
  28. // arr.splice(arr.findIndex(item => item === longArrName), 1)
  29. // str += [longArrName, ...arr].join('\t') + '\n'
  30. // const longArr = obj[bundleName][longArrName]
  31. // longArr.forEach((item, index) => {
  32. // const keyArr = []
  33. // keyArr.push(item.key)
  34. // arr.forEach(item2 => {
  35. // const data = obj[bundleName][item2][index]
  36. // if(data) {
  37. // keyArr.push(data.key || '')
  38. // } else {
  39. // keyArr.push('')
  40. // }
  41. // })
  42. // str += keyArr.join('\t') + '\n'
  43. // })
  44. // } else {
  45. // arr.forEach(item => {
  46. // const gameArr = obj[bundleName][item]
  47. // str += gameArr.reduce((str, item2) => (str += item + ',' + item2.key + '\n'),'')
  48. // })
  49. // }
  50. })
  51. const input = document.querySelector('.zf-redeem-text')
  52. input.value = str
  53. }
  54. const myPromise = (item) => {
  55. return new Promise(async (resolve) => {
  56. if(item.key) {
  57. item.key += `(已刮过)`
  58. resolve(item)
  59. } else {
  60. delete item.key
  61. try {
  62. resolve(await request(`https://www.fanatical.com/api/user/orders/redeem`, { method: 'POST', body: JSON.stringify(item) }))
  63. } catch(e) {
  64. resolve({key: '请求失败'})
  65. }
  66. }
  67. })
  68. }
  69. const redeem = async (data, obj, count, ele) => {
  70. const result = await Promise.all(data.map(item => myPromise(item)))
  71. result.forEach((item, index) => {
  72. gameCount--
  73. data[index].key = item.key
  74. if(ele) ele.innerHTML = `一键刮key(${count} / ${count - gameCount})`
  75. })
  76. if(gameCount <= 0) {
  77. render(obj)
  78. gameCount = 0
  79. isRun = false
  80. }
  81. }
  82. const func = (obj, name, item, atok, order_id, bid) => {
  83. if(item.status === 'refunded' || item.type === 'software') return
  84. if(!obj[name][item.name]) {
  85. obj[name][item.name] = []
  86. }
  87. gameCount++
  88. obj[name][item.name].push(Object.assign({
  89. atok,
  90. oid: order_id,
  91. iid: item.iid,
  92. serialId: item.serialId,
  93. key: item.key
  94. }, bid ? {bid} : null))
  95. }
  96. const getData = ({status, _id : order_id, items: orderList}, obj = {}) => {
  97. if(status !== 'COMPLETE') return alert('订单未完成')
  98. const atok = window.localStorage.bsatok
  99. orderList.forEach(item => {
  100. if(item.status === 'refunded') return
  101. if(item.pickAndMix) {
  102. if(!obj[item.pickAndMix]) {
  103. obj[item.pickAndMix] = {}
  104. }
  105. if(item.bundles.length) {
  106. item.bundles.forEach(gameList => {
  107. gameList.games.forEach(item2 => {
  108. func(obj, item.pickAndMix, item2, atok, order_id)
  109. })
  110. })
  111. } else {
  112. func(obj, item.pickAndMix, item, atok, order_id)
  113. }
  114.  
  115. } else {
  116. if(item.bundles.length) {
  117. if(!obj[item.name]) {
  118. obj[item.name] = {}
  119. }
  120. item.bundles.forEach(item2 => {
  121. item2.games.forEach(item3 => {
  122. func(obj, item.name, item3, atok, order_id, item._id)
  123. })
  124. })
  125. } else {
  126. if(!obj['单个游戏']) {
  127. obj['单个游戏'] = {}
  128. }
  129. func(obj, '单个游戏', item, atok, order_id)
  130. }
  131. }
  132. })
  133. return obj
  134. }
  135. const request = async (url, {method = 'GET', body = null} = {}) => {
  136. const result = await fetch(url, {
  137. method,
  138. body,
  139. headers: {
  140. anonid: JSON.parse(window.localStorage.bsanonymous).id,
  141. authorization: JSON.parse(window.localStorage.bsauth).token,
  142. 'content-type': 'application/json; charset=utf-8'
  143. }
  144. })
  145. return await result.json()
  146. }
  147. async function clickEvent(order) {
  148. if(isRun) return
  149. isRun = true
  150. this.innerHTML = `一键刮key中...`
  151. const obj = getData(await request(`https://www.fanatical.com/api/user/orders/${order}`))
  152. this.innerHTML = `一键刮key(${gameCount} / 0)`
  153. let count = gameCount
  154. for(let name in obj) {
  155. for(let item in obj[name]) {
  156. await redeem(obj[name][item], obj, count, this)
  157. }
  158. }
  159. }
  160. const init = (list) => {
  161. setTimeout(() => {
  162. list.forEach(item => {
  163. if(item.previousElementSibling.innerText === 'COMPLETE' && item.childElementCount === 1) {
  164. item.classList.add('zf-has')
  165. const [, order] = item.parentElement.parentElement.href.match(/orders\/(\w+)/)
  166. const me = document.querySelector(`.v-${order}`)
  167. if(me) return
  168. const div = document.createElement('div')
  169. div.className = `zf-wrap v-${order}`
  170. div.style = `position: absolute;right: 20px;top: ${item.offsetTop}px;`
  171. div.innerHTML = '<div class="zf-coustom">一键刮key</div>'
  172. div.firstElementChild.onclick = clickEvent.bind(div.firstElementChild, order)
  173. document.documentElement.appendChild(div)
  174. }
  175. })
  176. }, 1000)
  177. }
  178. const initRedeem = (container) => {
  179. const ele = document.createElement('div')
  180. ele.className = 'zf-redeem-wrap'
  181. ele.innerHTML = `
  182. <textarea class="zf-redeem-text" placeholder="兑换礼品码(适用于整包,单游戏, 一个ip最多10个,有冷却)"></textarea>
  183. <button class="btn btn-primary zf-redeem">兑换</button>
  184. `
  185. container.insertBefore(ele, container.firstElementChild)
  186. ele.querySelector('.zf-redeem').onclick = async () => {
  187. if(isRun) return
  188. const codes = ele.firstElementChild.value.split('\n').filter(item => {
  189. const code = item.trim()
  190. return /\w{14}/.test(code)
  191. }).slice(0, 10)
  192. let flag = window.confirm(`一次最多兑换10个,数量:${codes.length}\n${codes.join('\n')}`)
  193. if(!flag || !codes.length) return
  194. isRun = true
  195. let messageList = []
  196. for(let i = 0; i < codes.length; i++) {
  197. let msg = codes[i]
  198. let id
  199. try {
  200. const { _id, message } = await request('https://www.fanatical.com/api/user/redeem-code/redeem', {method: 'POST', body: JSON.stringify({code: codes[i]})})
  201. if(_id) {
  202. msg += '-----成功'
  203. id =_id
  204. }
  205. if(message) msg += `-----${message}`
  206. } catch(e) {
  207. msg += `-----${e}`
  208. console.error(e)
  209. }
  210. messageList.push({msg, id})
  211. }
  212. flag = window.confirm(`兑换详情:\n是否需要兑换key\n${messageList.map(item => item.msg).join('\n')}`)
  213. if(!flag) return
  214. messageList = messageList.filter(item => item.id)
  215. const obj = {}
  216. for(let i = 0; i < messageList.length; i ++) {
  217. const item = messageList[i]
  218. getData(await request(`https://www.fanatical.com/api/user/orders/${item.id}`), obj)
  219. }
  220. let count = gameCount
  221. for(let name in obj) {
  222. for(let item in obj[name]) {
  223. await redeem(obj[name][item], obj, count)
  224. }
  225. }
  226. }
  227. }
  228. const content = document.querySelector('#root')
  229. const observer = new MutationObserver((mutationsList) => {
  230. if(!/orders\/?$/.test(location.pathname)) {
  231. document.querySelectorAll('.zf-wrap').forEach(item => item.remove())
  232. return
  233. }
  234. for(let mutation of mutationsList) {
  235. if (mutation.type === 'childList' && mutation.addedNodes.length) {
  236. const list = mutation.target.querySelectorAll('.d-none.d-md-block.action-col:not(.zf-has)')
  237. if(list.length) init(list)
  238. const container = mutation.target.querySelector('.account-content.orders-and-keys')
  239. if(!container) continue
  240. if(container.querySelector('.zf-redeem-wrap')) continue
  241. initRedeem(container)
  242. } else if(mutation.type === 'childList' && mutation.removedNodes.length) {
  243. mutation.removedNodes.forEach(item => {
  244. if(item.className = 'table-item' && item.firstElementChild && item.firstElementChild.nodeName === 'A') {
  245. const [, order] = item.firstElementChild.href.match(/orders\/(\w+)/)
  246. const me = document.querySelector(`.v-${order}`)
  247. if(me) me.remove()
  248. }
  249. })
  250. }
  251. }
  252. })
  253. const config = { childList: true, subtree: true }
  254. observer.observe(content, config)
  255. GM_addStyle(`
  256. .zf-coustom {
  257. padding: 5px 15px;
  258. border-radius: 5px;
  259. background-color: #212121;
  260. cursor: pointer;
  261. font-size: 14px;
  262. text-align: center;
  263. color: #fff;
  264. margin-top: 10px;
  265. }
  266. .zf-coustom:hover {
  267. opacity: 0.5;
  268. }
  269. .zf-redeem-wrap {
  270. display: flex;
  271. align-items: flex-end;
  272. }
  273. .zf-redeem-text {
  274. width: 500px;
  275. height: 250px;
  276. margin-right: 20px;
  277. outline: none;
  278. }
  279. `)
  280. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址