Fanatical批量刮key

批量提取整理F站key

  1. // ==UserScript==
  2. // @name Fanatical批量刮key
  3. // @namespace kb1000fx
  4. // @version 0.6
  5. // @description 批量提取整理F站key
  6. // @author kb1000fx
  7. // @include https://www.fanatical.com/*
  8. // @include /https://www\.fanatical\.com/[\S]*redeem-code$/
  9. // @icon https://cdn.fanatical.com/production/icons/favicon-32x32.png
  10. // @grant GM_addStyle
  11. // @grant GM_setClipboard
  12. // @grant unsafeWindow
  13. // @grant window.onload
  14. // @require https://cdn.jsdelivr.net/npm/jquery@3.4.1/dist/jquery.slim.min.js
  15. // ==/UserScript==
  16.  
  17. (function(){
  18. 'use strict';
  19. unsafeWindow.$ = $;
  20. let gameTitles = [];
  21.  
  22. const cbxHTML = `
  23. <div class="checkbox">
  24. <input type="checkbox" name="ordercbx"/>
  25. </div>
  26. `;
  27.  
  28. const textHTML = `
  29. <div id="redeem-input">
  30. <p class="redeem-form-label">批量激活</p>
  31. <div class="input-group">
  32. <textarea class="form-control" rows="10"/>
  33. <div class="input-group-append">
  34. <button id="redeem-code-btn" class="btn btn-primary">批量激活</button>
  35. </div>
  36. </div>
  37. <p class="redeem-form-label">优惠券检验</p>
  38. <div class="input-group">
  39. <input type="text" id="voucher-input" class="form-control">
  40. <div class="input-group-append">
  41. <button id="voucher-check-btn" class="btn btn-primary">优惠券检验</button>
  42. </div>
  43. </div>
  44. </div>
  45. `;
  46.  
  47. const css = `
  48. .table-item{
  49. display: flex;
  50. flex-direction: row;
  51. }
  52. .checkbox{
  53. display: flex;
  54. justify-content:center;
  55. width: 5%;
  56. align-items: center;
  57. }
  58. .table-item a{
  59. width: 95%
  60. }
  61. .tm-panel{
  62. margin-bottom: 24px;
  63. }
  64. .tm-btn{
  65. margin-right: 20px;
  66. }
  67. #redeem-input .input-group{
  68. margin-bottom: 16px;
  69. }
  70. `;
  71.  
  72. const panelHTML = `
  73. <div class="tm-panel">
  74. <button id="redeem-btn" class="btn btn-primary tm-btn">刮Key</button>
  75. <button id="get-btn" class="btn btn-primary tm-btn">提取</button>
  76. <button id="copy-btn" class="btn btn-primary tm-btn">复制</button>
  77. <input type="checkbox" name="showName" checked /> 显示名称
  78. </div>
  79. `;
  80.  
  81. let orderIDList = [];
  82. let unrevealedList = [];
  83. let revealedList = [];
  84. let sortedJson ={};
  85. let str = "";
  86.  
  87. const getRes = async ()=>{
  88. orderIDList = [];
  89. unrevealedList = [];
  90. revealedList = [];
  91. $("input[name='ordercbx']:checked").each(function (i,e){
  92. const orderStr = $(e).parent("div").next().attr("href");
  93. orderIDList.push( orderStr.split("/")[3] );
  94. });
  95. console.log(`已选择 ${orderIDList.length}个订单`);
  96. let gameList = [];
  97. for (let index = 0; index < orderIDList.length; index++) {
  98. const ID = orderIDList[index];
  99. await fetch(`https://www.fanatical.com/api/user/orders/${ID}`, {
  100. method: 'GET',
  101. headers: {
  102. 'anonid': JSON.parse(window.localStorage.bsanonymous).id,
  103. 'authorization': JSON.parse(window.localStorage.bsauth).token,
  104. 'content-type': 'application/json; charset=utf-8'
  105. }
  106. }).then(res => res.json()).then(res => {
  107. console.log(res)
  108. if (res.status == "COMPLETE") {
  109. for (const item of res.items) {
  110. if (item.type == "game") {
  111. gameList.push(item)
  112. } else if (item.type == "bundle") {
  113. for (const tier of item.bundles) {
  114. gameList = [...gameList, ...tier.games]
  115. }
  116. } else {
  117. console.log(item)
  118. }
  119. }
  120. } else {
  121. console.log(`Order ${res._id} is ${res.status}`)
  122. }
  123. })
  124. console.log(gameList)
  125. }
  126. for (let index = 0; index < gameList.length; index++) {
  127. const game = gameList[index];
  128. if (game.status == "revealed") {
  129. revealedList.push({
  130. name: game.name,
  131. key: game.key,
  132. })
  133. } else if (game.status == "fulfilled") {
  134. unrevealedList.push(game)
  135. }
  136. }
  137. };
  138.  
  139. const revealKey = async ()=>{
  140. for (const item of unrevealedList) {
  141. let e = {};
  142. e["name"] = item.name;
  143. await fetch(`https://www.fanatical.com/api/user/orders/redeem`, {
  144. method: 'POST',
  145. body: JSON.stringify(item),
  146. headers: {
  147. 'anonid': JSON.parse(window.localStorage.bsanonymous).id,
  148. 'authorization': JSON.parse(window.localStorage.bsauth).token,
  149. 'content-type': 'application/json; charset=utf-8'
  150. }
  151. }).then(res => res.json()).then(res => {
  152. e["key"] = res.key;
  153. revealedList.push(e);
  154. })
  155. }
  156. };
  157.  
  158. const sortRes = ()=>{
  159. const showName = $("input[name='showName']").prop("checked")
  160. sortedJson ={};
  161. str = "";
  162. let maxLegth = 0;
  163. for (const e of revealedList) {
  164. if(!(e.name in sortedJson)) {
  165. sortedJson[e.name] = [];
  166. };
  167. sortedJson[e.name].push(e.key);
  168. }
  169. console.log(sortedJson);
  170. maxLegth = Math.max.apply(Math, Object.values(sortedJson).map((e)=>e.length))
  171. for (const name in sortedJson) {
  172. const lst = sortedJson[name];
  173. if (showName) {
  174. str += `\n${name}:\n`;
  175. }
  176. for (const key of lst) {
  177. str += `${key}\n`
  178. }
  179. }
  180.  
  181. copyRes();
  182. };
  183.  
  184. const copyRes = ()=>{
  185. console.log(str);
  186. GM_setClipboard(str);
  187. alert("结果已导出至剪切板")
  188. };
  189.  
  190. const redeemCodes = async ()=>{
  191. let succ = 0;
  192. const keys = $("#redeem-input textarea").val().split(/[(\r\n)\r\n]+/).filter(e=>e);
  193. console.log(keys);
  194. for (const key of keys) {
  195. let flag = true;
  196. const res = await fetch(`https://www.fanatical.com/api/user/redeem-code/redeem`, {
  197. method: 'POST',
  198. body: JSON.stringify({
  199. code: key
  200. }),
  201. headers: {
  202. 'anonid': JSON.parse(window.localStorage.bsanonymous).id,
  203. 'authorization': JSON.parse(window.localStorage.bsauth).token,
  204. 'content-type': 'application/json; charset=utf-8'
  205. }
  206. }).then(res => res.json()).catch(e=>{console.log(e);flag = false})
  207. if (flag) {
  208. console.log(res)
  209. if (res._id) {
  210. succ += 1;
  211. }
  212. }
  213. }
  214. alert(`共激活${keys.length}个,成功${succ}个`)
  215. };
  216.  
  217. const checkVoucher = async ()=>{
  218. const voucher = $("#voucher-input").val()
  219. const res = await fetch(`https://www.fanatical.com/api/user/discount`, {
  220. method: 'POST',
  221. body: JSON.stringify({
  222. discountCode: voucher
  223. }),
  224. headers: {
  225. 'anonid': JSON.parse(window.localStorage.bsanonymous).id,
  226. 'authorization': JSON.parse(window.localStorage.bsauth).token,
  227. 'content-type': 'application/json; charset=utf-8'
  228. }
  229. })
  230. if (res.status == 200) {
  231. const rs = await res.json()
  232. console.log(rs)
  233. alert(`名称: ${rs.name}\n优惠券: ${rs.code}\n有效期至: ${rs.valid_until}`)
  234. } else if(res.status == 400){
  235. alert(`${voucher} 已过期`)
  236. }
  237. };
  238.  
  239. const initUI = ()=>{
  240. GM_addStyle(css);
  241. if (window.location.href.match(/https:\/\/www\.fanatical\.com\/[\S]*redeem-code$/)) {
  242. $(".redeem-form").after(textHTML);
  243. } else {
  244. $(".table-item").prepend(cbxHTML);
  245. $(".order-search").after(panelHTML);
  246. replaceDate();
  247. }
  248. };
  249.  
  250. const replaceDate = ()=>{
  251. fetch(`https://www.fanatical.com/api/user/orders`, {
  252. method: 'GET',
  253. headers: {
  254. 'anonid': JSON.parse(window.localStorage.bsanonymous).id,
  255. 'authorization': JSON.parse(window.localStorage.bsauth).token,
  256. 'content-type': 'application/json; charset=utf-8'
  257. }
  258. }).then(res => res.json()).then(res=>{
  259. const els = $(".details-container .date-col");
  260. for (let index = 0; index < els.length; index++) {
  261. $(els[index]).html(new Date(res[index].date).toLocaleString())
  262. }
  263. })
  264. };
  265.  
  266. const addListeners = ()=>{
  267. $("#redeem-btn").click(async ()=>{
  268. await getRes();
  269. await revealKey();
  270. sortRes();
  271. });
  272. $("#get-btn").click(async ()=>{
  273. await getRes();
  274. sortRes();
  275. });
  276. $("#copy-btn").click(()=>{
  277. copyRes();
  278. });
  279. $("#redeem-code-btn").click(()=>{
  280. redeemCodes()
  281. });
  282. $("#voucher-check-btn").click(()=>{
  283. checkVoucher()
  284. });
  285. };
  286.  
  287. (()=>{
  288. const init_timer = setInterval(()=>{
  289. const status1 = $(".table-item").length;
  290. const status2 = $(".redeem-form").length;
  291. if (status1 || status2) {
  292. clearInterval(init_timer);
  293. initUI();
  294. addListeners();
  295. }
  296. }, 200)
  297. })();
  298. })();

QingJ © 2025

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