Fanatical批量刮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批量刮key
// @namespace    kb1000fx
// @version      0.6
// @description  批量提取整理F站key
// @author       kb1000fx
// @include      https://www.fanatical.com/*
// @include      /https://www\.fanatical\.com/[\S]*redeem-code$/
// @icon         https://cdn.fanatical.com/production/icons/favicon-32x32.png
// @grant        GM_addStyle
// @grant        GM_setClipboard
// @grant        unsafeWindow
// @grant        window.onload
// @require      https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.slim.min.js
// ==/UserScript==

(function(){
    'use strict';
    unsafeWindow.$ = $;
    let gameTitles = [];

    const cbxHTML = `
        <div class="checkbox">
            <input type="checkbox" name="ordercbx"/>        
        </div>
    `;

    const textHTML = `
        <div id="redeem-input">
            <p class="redeem-form-label">批量激活</p>
            <div class="input-group">
                <textarea class="form-control" rows="10"/>
                <div class="input-group-append">
                    <button id="redeem-code-btn" class="btn btn-primary">批量激活</button>
                </div>
            </div>
            <p class="redeem-form-label">优惠券检验</p>
            <div class="input-group">
                <input type="text" id="voucher-input" class="form-control">
                <div class="input-group-append">
                    <button id="voucher-check-btn" class="btn btn-primary">优惠券检验</button>
                </div>
            </div>
        </div>
    `;

    const css = `
        .table-item{
            display: flex;
            flex-direction: row;
        }
        .checkbox{
            display: flex;
            justify-content:center; 
            width: 5%;
            align-items: center;
        }
        .table-item a{
            width: 95%
        }
        .tm-panel{
            margin-bottom: 24px;
        }
        .tm-btn{
            margin-right: 20px;
        }
        #redeem-input .input-group{
            margin-bottom: 16px;
        }
    `;

    const panelHTML = `
        <div class="tm-panel">
            <button id="redeem-btn" class="btn btn-primary tm-btn">刮Key</button>
            <button id="get-btn" class="btn btn-primary tm-btn">提取</button>
            <button id="copy-btn" class="btn btn-primary tm-btn">复制</button>
            <input type="checkbox" name="showName" checked /> 显示名称
        </div>
    `;

    let orderIDList = [];
    let unrevealedList = [];
    let revealedList = [];
    let sortedJson ={};
    let str = "";

    const getRes = async ()=>{
        orderIDList = [];
        unrevealedList = [];
        revealedList = [];
        $("input[name='ordercbx']:checked").each(function (i,e){
            const orderStr = $(e).parent("div").next().attr("href");
            orderIDList.push( orderStr.split("/")[3] ); 
        });        
        console.log(`已选择 ${orderIDList.length}个订单`);
        let gameList = [];
        for (let index = 0; index < orderIDList.length; index++) {
            const ID = orderIDList[index];
            await fetch(`https://www.fanatical.com/api/user/orders/${ID}`, {
                method: 'GET',
                headers: {
                    'anonid': JSON.parse(window.localStorage.bsanonymous).id,
                    'authorization': JSON.parse(window.localStorage.bsauth).token,
                    'content-type': 'application/json; charset=utf-8'
                }
            }).then(res => res.json()).then(res => {
                console.log(res)
                if (res.status == "COMPLETE") { 
                    for (const item of res.items) {
                        if (item.type == "game") {
                            gameList.push(item)
                        } else if (item.type == "bundle") {
                            for (const tier of item.bundles) {
                                gameList = [...gameList, ...tier.games]
                            }
                        } else {
                            console.log(item)
                        }
                    }
                } else {
                    console.log(`Order ${res._id} is ${res.status}`)
                }
            })
            console.log(gameList)
        }
        for (let index = 0; index < gameList.length; index++) {
                const game = gameList[index];
                if (game.status == "revealed") {
                    revealedList.push({
                        name: game.name,
                        key: game.key,
                    })
                } else if (game.status == "fulfilled") {
                    unrevealedList.push(game)
                }
       }
    };

    const revealKey = async ()=>{
        for (const item of unrevealedList) {
            let e = {};
            e["name"] = item.name;    
            await fetch(`https://www.fanatical.com/api/user/orders/redeem`, {
                method: 'POST',
                body: JSON.stringify(item),
                headers: {
                    'anonid': JSON.parse(window.localStorage.bsanonymous).id,
                    'authorization': JSON.parse(window.localStorage.bsauth).token,
                    'content-type': 'application/json; charset=utf-8'
                }
            }).then(res => res.json()).then(res => {
                e["key"] = res.key;
                revealedList.push(e);
            })
        }
    };

    const sortRes = ()=>{
        const showName = $("input[name='showName']").prop("checked")
        sortedJson ={};
        str = "";
        let maxLegth = 0;
        for (const e of revealedList) {
            if(!(e.name in sortedJson)) {
                sortedJson[e.name] = [];             
            };
            sortedJson[e.name].push(e.key);
        }
        console.log(sortedJson); 
        maxLegth = Math.max.apply(Math, Object.values(sortedJson).map((e)=>e.length))
        for (const name in sortedJson) {
            const lst = sortedJson[name];
            if (showName) {
              str += `\n${name}:\n`;
            }           
            for (const key of lst) {
                str += `${key}\n`
            }
         }           

        copyRes();
    };

    const copyRes = ()=>{
        console.log(str);
        GM_setClipboard(str);
        alert("结果已导出至剪切板")
    };

    const redeemCodes = async ()=>{
        let succ = 0;
        const keys = $("#redeem-input textarea").val().split(/[(\r\n)\r\n]+/).filter(e=>e);
        console.log(keys);
        for (const key of keys) {
            let flag = true;
            const res = await fetch(`https://www.fanatical.com/api/user/redeem-code/redeem`, {
                method: 'POST',
	            body: JSON.stringify({
                    code: key
                }),
                headers: {
                    'anonid': JSON.parse(window.localStorage.bsanonymous).id,
                    'authorization': JSON.parse(window.localStorage.bsauth).token,
                    'content-type': 'application/json; charset=utf-8'
                }
            }).then(res => res.json()).catch(e=>{console.log(e);flag = false})
            if (flag) {
                console.log(res)
                if (res._id) {
                    succ += 1;
                }
            }
        }
        alert(`共激活${keys.length}个,成功${succ}个`)
    };

    const checkVoucher = async ()=>{
        const voucher = $("#voucher-input").val()
        const res = await fetch(`https://www.fanatical.com/api/user/discount`, {
            method: 'POST',
	        body: JSON.stringify({
                discountCode: voucher
            }),
            headers: {
                'anonid': JSON.parse(window.localStorage.bsanonymous).id,
                'authorization': JSON.parse(window.localStorage.bsauth).token,
                'content-type': 'application/json; charset=utf-8'
            }
        })
        if (res.status == 200) {
            const rs = await res.json()
            console.log(rs)
            alert(`名称: ${rs.name}\n优惠券: ${rs.code}\n有效期至: ${rs.valid_until}`)
        } else if(res.status == 400){
            alert(`${voucher} 已过期`)
        }
    };

    const initUI = ()=>{
        GM_addStyle(css);
        if (window.location.href.match(/https:\/\/www\.fanatical\.com\/[\S]*redeem-code$/)) {
            $(".redeem-form").after(textHTML);
        } else {
            $(".table-item").prepend(cbxHTML);
            $(".order-search").after(panelHTML);  
            replaceDate();
        }
    };

    const replaceDate = ()=>{
        fetch(`https://www.fanatical.com/api/user/orders`, {
            method: 'GET',
            headers: {
                'anonid': JSON.parse(window.localStorage.bsanonymous).id,
                'authorization': JSON.parse(window.localStorage.bsauth).token,
                'content-type': 'application/json; charset=utf-8'
            }
        }).then(res => res.json()).then(res=>{
            const els = $(".details-container .date-col");
            for (let index = 0; index < els.length; index++) {
                $(els[index]).html(new Date(res[index].date).toLocaleString())
            }
        })
    };

    const addListeners = ()=>{
        $("#redeem-btn").click(async ()=>{
            await getRes();
            await revealKey();
            sortRes();
        });
        $("#get-btn").click(async ()=>{
            await getRes();
            sortRes();
        });
        $("#copy-btn").click(()=>{
            copyRes();
        });
        $("#redeem-code-btn").click(()=>{
            redeemCodes()
        });
        $("#voucher-check-btn").click(()=>{
            checkVoucher()
        });
    };

    (()=>{
        const init_timer = setInterval(()=>{
            const status1 = $(".table-item").length;
            const status2 = $(".redeem-form").length;
            if (status1 || status2) {
                clearInterval(init_timer);
                initUI();
                addListeners();    
            }
        }, 200)
    })();
})();