Greasy Fork

Greasy Fork is available in English.

牛牛战斗助手

牛牛组队信息、战力排行

// ==UserScript==
// @name         牛牛战斗助手
// @namespace    http://tampermonkey.net/
// @version      0.1.3
// @description  牛牛组队信息、战力排行
// @icon         https://www.milkywayidle.com/favicon.svg
// @author       xiaoshui
// @match        https://www.milkywayidle.com/*
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @connect      niuniu.0xa2c2a.shop
// @license MIT
// ==/UserScript==

(function() {
    if (document.URL.includes("test.milkywayidle.com"))return;

    const host = "https://niuniu.0xa2c2a.shop"
    let config={
        sikll_upload: host + "/api/player",
        party_upload:host + "/api/party",
        api_version:"0.0.2"
    }
    let reporter = null;

        // 拦截WS
    function hookWebSocket() {
        const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
        const oriGet = dataProperty.get;
        dataProperty.get = hookedGet;
        Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
        function hookedGet() {
            const socket = this.currentTarget;
            if (!(socket instanceof WebSocket)) {
                return oriGet.call(this);
            }
            if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
                return oriGet.call(this);
            }
            const message = oriGet.call(this);
            Object.defineProperty(this, "data", { value: message });
            return handleMessage(message);
        }
    }
    hookWebSocket();

        // WS拦截后处理,主进程
    function handleMessage(message,debug=false) {
        let obj = JSON.parse(message);
        if (obj && obj.type === "init_character_data") {
            // 组队情况
            addPartyButton();
            // 上传初始化登录数据
            uploadInitCharacterData(obj);
        }else if (obj && obj.type === "profile_shared"){
            // 上传玩家面板数据
            uploadProfileShared(obj);
        }
        //other
        return message;
    }
        // 上传玩家面板数据
    function uploadProfileShared(obj){
        let data={};
        let profile = obj.profile;
        data.id=profile?.characterSkills?.[0]?.characterID;
        if((data.id === undefined) || (profile?.sharableCharacter?.gameMode !== "standard")) return;

        let isUploadPlayer = true;
        let scriptNiuniu0xa2c2aShop = localStorage.getItem("scriptNiuniu0xa2c2aShop");
        let localPlayer = null;
        let locatParty = null;
        if(scriptNiuniu0xa2c2aShop){
            scriptNiuniu0xa2c2aShop = JSON.parse(scriptNiuniu0xa2c2aShop);
            localPlayer = scriptNiuniu0xa2c2aShop.player;
            locatParty = scriptNiuniu0xa2c2aShop.party;
        }
        if((Date.now() - localPlayer?.[data.id])/60000 < 60){ // 人物信息页面上传间隔60分钟
            isUploadPlayer = false;
            return;
        } else {
            localPlayer = {...localPlayer,[data.id]:Date.now()};
            localStorage.setItem("scriptNiuniu0xa2c2aShop", JSON.stringify({"player":localPlayer,"party":locatParty}));
        }

        data.name=profile.sharableCharacter.name
        data.weapon = profile.wearableItemMap === null ?null:getWeapon(profile?.wearableItemMap);
        let ranged,attack,power,magic = "";
        for( const item of profile.characterSkills){
            if(item.skillHrid === "/skills/ranged"){
                ranged = item.experience.toFixed();
            }else if(item.skillHrid === "/skills/attack"){
                attack = item.experience.toFixed();
            } else if(item.skillHrid === "/skills/power"){
                power = item.experience.toFixed();
            }else if(item.skillHrid === "/skills/magic"){
                magic = item.experience.toFixed();
            }
        }
        data.skill=ranged+","+attack+","+power+","+magic;
        return new Promise((resolve, reject) => {
             GM_xmlhttpRequest({
                 method: 'POST',
                 url: config.sikll_upload,
                 headers: {
                     "Content-Type": "application/json",
                     "reporter":reporter,
                     "apiVersion":config.api_version
                 },
                 data:JSON.stringify(data),
                 onload: function (response) {
                     resolve();
                 },
                 onerror: function (error) {
                     localStorage.removeItem('scriptNiuniu0xa2c2aShop');
                     reject(error);
                 }
             });
         });
    }
    function getWeapon(wearableItemMap){
        let hand = wearableItemMap['/item_locations/main_hand']?.itemHrid
        if(hand === undefined){
            hand = wearableItemMap['/item_locations/two_hand']?.itemHrid
        }
        if(hand === undefined) return null;

        return hand?.substr(7);

    }
        // 上传初始化登录数据
    function uploadInitCharacterData(obj){
        reporter = obj?.character?.id;
        if(obj?.character?.gameMode !== "standard") return;//跳过铁牛
        let isUploadPlayer = true;
        let scriptNiuniu0xa2c2aShop = localStorage.getItem("scriptNiuniu0xa2c2aShop");
        let localPlayer = null;
        let locatParty = null;
        if(scriptNiuniu0xa2c2aShop){
            scriptNiuniu0xa2c2aShop = JSON.parse(scriptNiuniu0xa2c2aShop);
            localPlayer = scriptNiuniu0xa2c2aShop.player;
            locatParty = scriptNiuniu0xa2c2aShop.party;
        }
        if((Date.now() - localPlayer?.[reporter])/60000 < 300){//刷新页面个人信息上传间隔5h
            isUploadPlayer = false;
        } else {
            for(const key in localPlayer){
                if((Date.now() - localPlayer?.[key])/60000 > 300){//清理超过5h的记录
                    delete localPlayer[key];
                }
            }
            localStorage.setItem("scriptNiuniu0xa2c2aShop", JSON.stringify({"player":localPlayer,"party":locatParty}));
        }
        if(isUploadPlayer){
            const fake_profile_shared={}
            fake_profile_shared.type="profile_shared";
            fake_profile_shared.profile={};
            fake_profile_shared.profile.characterSkills=obj.characterSkills;
            fake_profile_shared.profile.combatLevel=obj.combatUnit.combatDetails.combatLevel;
            if(obj.guild){
                fake_profile_shared.profile.guildName=obj.guild.name;
                fake_profile_shared.profile.guildRole=obj.guildCharacterMap[obj.character.id].role;
            }else{
                fake_profile_shared.profile.guildName="";
                fake_profile_shared.profile.guildRole="";
            }
            fake_profile_shared.profile.sharableCharacter=obj.character;
            fake_profile_shared.profile.wearableItemMap={}
            obj.characterItems.forEach(item=>{
                if(item.itemLocationHrid!='/item_locations/inventory'){
                    fake_profile_shared.profile.wearableItemMap[item.itemLocationHrid]=item;
                }
            })
            uploadProfileShared(fake_profile_shared)
        };
        uploadPartyInfo(obj);
    }

        // 上传组队数据
    function uploadPartyInfo(obj){
        let data={};
        if(obj?.partyInfo?.party?.status !== "battling") return;
        const partyInfo = obj?.partyInfo
        data.id=partyInfo.party.id;
        data.map=partyInfo.party.actionHrid.substr(16);

        data.players="";
        for(let k in partyInfo.sharableCharacterMap){
            if(data.players) data.players += ","
            data.players += partyInfo.sharableCharacterMap[k].name
        }

        let isUploadParty = true;
        let scriptNiuniu0xa2c2aShop = localStorage.getItem("scriptNiuniu0xa2c2aShop");
        let localPlayer = null;
        let locatParty = null;
        if(scriptNiuniu0xa2c2aShop){
            scriptNiuniu0xa2c2aShop = JSON.parse(scriptNiuniu0xa2c2aShop);
            localPlayer = scriptNiuniu0xa2c2aShop.player;
            locatParty = scriptNiuniu0xa2c2aShop.party;
        }

        if(isUploadParty && (locatParty !==null)&& (locatParty !== undefined) &&(data.players === locatParty?.players)&&(data.map === locatParty?.map)&& (locatParty?.id === data.id) && ((Date.now() - locatParty?.dateTime)/60000 < 120)){
            isUploadParty = false;
        } else {
            locatParty={"dateTime":Date.now(),...data};
            localStorage.setItem("scriptNiuniu0xa2c2aShop", JSON.stringify({"player":localPlayer,"party":locatParty}));
        }

        if(!isUploadParty) return;

        return new Promise((resolve, reject) => {
             GM_xmlhttpRequest({
                 method: 'POST',
                 url: config.party_upload,
                 headers: {
                     "Content-Type": "application/json",
                     "reporter":reporter,
                     "apiVersion":config.api_version
                 },
                 data:JSON.stringify(data),
                 onload: function (response) {
                     resolve();
                 },
                 onerror: function (error) {
                     localStorage.removeItem('scriptNiuniu0xa2c2aShop');
                     reject(error);
                 }
             });
         });
    }

        //组队情况
    function addPartyButton() {
        const waitForNavi = () => {
            const targetNode = document.querySelector("div.NavigationBar_minorNavigationLinks__dbxh7"); // 确认这个选择器是否适合你的环境
            const navigationLinks = document.querySelectorAll('div.NavigationBar_minorNavigationLink__31K7Y');
            let toolLink;
            for (let link of navigationLinks) {
                if (link.textContent.includes('插件设置')||link.textContent.includes('Script settings')) {
                    toolLink = link;
                    break;
                }
            }
            if (targetNode&&toolLink) {
                let statsButton = document.createElement("div");
                statsButton.setAttribute("class", "NavigationBar_minorNavigationLink__31K7Y");
                statsButton.style.color = toolLink.style.color;
                statsButton.innerHTML = "国人组队情况";
                statsButton.addEventListener("click", () => {
                    window.open("https://niuniu.0xa2c2a.shop/party.html", "_blank");
                });
                // 将按钮添加到目标节点
                targetNode.insertBefore(statsButton, toolLink.nextSibling);
            } else {
                setTimeout(addPartyButton, 200);
            }
        };
        waitForNavi(); // 开始等待目标节点出现
    }

        //菜单
    GM_registerMenuCommand('重置上传间隔', function() {
       localStorage.removeItem('scriptNiuniu0xa2c2aShop');
        alert("重置成功")
    });

})();