// ==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("重置成功")
});
})();