Greasy Fork

Greasy Fork is available in English.

牛牛战斗助手

牛牛组队信息、战力排行

当前为 2025-05-19 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         牛牛战斗助手
// @namespace    http://tampermonkey.net/
// @version      0.1.0
// @description  牛牛组队信息、战力排行
// @icon         https://www.milkywayidle.com/favicon.svg
// @author       xiaoshui
// @match        https://www.milkywayidle.com/*
// @grant        GM_xmlhttpRequest
// @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.name=profile.sharableCharacter.name
        if(profile.sharableCharacter.gameMode !== "standard")return;
        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();
                data.id=item.characterID;
            }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((localPlayer !== null) && (localPlayer !== undefined) && ((Date.now() - localPlayer)/60000 < 300)){
            isUploadPlayer = false;
        } else {
            localPlayer = Date.now();
            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(); // 开始等待目标节点出现
    }

})();