您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、显示任务价值、默哀法师助手
当前为
// ==UserScript== // @name Ranged Way Idle // @namespace http://tampermonkey.net/ // @version 2.0 // @description 死亡提醒、强制刷新MWITools的价格、私信提醒音、自动任务排序、显示购买预付金/出售可获金/待领取金额、显示任务价值、默哀法师助手 // @author AlphB // @match https://www.milkywayidle.com/* // @match https://test.milkywayidle.com/* // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com // @grant none // @license CC-BY-NC-SA-4.0 // ==/UserScript== (function () { const config = { notifyDeath: {enable: true, desc: "战斗中角色死亡时发送通知"}, forceUpdateMarketPrice: {enable: true, desc: "进入市场时,强制更新MWITools的市场价格"}, notifyWhisperMessages: {enable: false, desc: "接受到私信时播放提醒音"}, listenKeywordMessages: {enable: false, desc: "中文频道消息含有关键词时播放提醒音"}, autoTaskSort: {enable: true, desc: "自动点击MWI TaskManager的任务排序按钮"}, showMarketListingsFunds: {enable: true, desc: "显示购买预付金/出售可获金/待领取金额"}, mournForMagicWayIdle: {enable: true, desc: "在控制台默哀法师助手"}, showTaskValue: {enable: true, desc: "显示任务代币的价值"}, keywords: [], } const globalVariable = { battleData: { players: null, lastNotifyTime: 0, }, itemDetailMap: JSON.parse(localStorage.getItem("initClientData")).itemDetailMap, whisperAudio: new Audio(`https://upload.thbwiki.cc/d/d1/se_bonus2.mp3`), keywordAudio: new Audio(`https://upload.thbwiki.cc/c/c9/se_pldead00.mp3`), market: { hasFundsElement: false, sellValue: null, buyValue: null, unclaimedValue: null, sellListings: null, buyListings: null }, task: { taskListElement: null, taskTokenValueData: null, hasTaskValueElement: false, taskValueElements: [], tokenValue: { Bid: null, Ask: null } } }; init(); function init() { readConfig(); // 任务代币计算功能需要食用工具 if (!('Edible_Tools' in localStorage)) { config.showTaskValue.enable = false; } // 更新市场价格需要MWITools支持 if (!('MWITools_marketAPI_json' in localStorage)) { config.forceUpdateMarketPrice.enable = false; } globalVariable.whisperAudio.volume = 0.4; globalVariable.keywordAudio.volume = 0.4; let observer = new MutationObserver(function () { if (config.showMarketListingsFunds.enable) showMarketListingsFunds(); if (config.autoTaskSort.enable) autoClickTaskSortButton(); if (config.showTaskValue.enable) showTaskValue(); showConfigMenu(); }); observer.observe(document, {childList: true, subtree: true}); globalVariable.task.taskTokenValueData = getTaskTokenValue(); if (config.mournForMagicWayIdle.enable) { console.log("为法师助手默哀"); } const oriGet = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data").get; function hookedGet() { const socket = this.currentTarget; if (!(socket instanceof WebSocket) || !socket.url || (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); return handleMessage(message); } Object.defineProperty(MessageEvent.prototype, "data", { get: hookedGet, configurable: true, enumerable: true }); } function readConfig() { const localConfig = localStorage.getItem("ranged_way_idle_config"); if (localConfig) { const localConfigObj = JSON.parse(localConfig); for (let key in localConfigObj) { if (config.hasOwnProperty(key) && key !== 'keywords') { config[key].enable = localConfigObj[key]; } } config.keywords = localConfigObj.keywords; } } function saveConfig() { // 仅保存enable开关和keywords const saveConfigObj = {}; const configMenu = document.querySelectorAll("div#ranged_way_idle_config_menu input"); if (configMenu.length === 0) return; for (const checkbox of configMenu) { config[checkbox.id].isTrue = checkbox.checked; saveConfigObj[checkbox.id] = checkbox.checked; } saveConfigObj.keywords = config.keywords; localStorage.setItem("ranged_way_idle_config", JSON.stringify(saveConfigObj)); } function showConfigMenu() { const targetNode = document.querySelector("div.SettingsPanel_profileTab__214Bj"); if (targetNode) { if (!targetNode.querySelector("#ranged_way_idle_config_menu")) { // enable开关部分 targetNode.insertAdjacentHTML("beforeend", `<div id="ranged_way_idle_config_menu"></div>`); const insertElem = targetNode.querySelector("div#ranged_way_idle_config_menu"); insertElem.insertAdjacentHTML( "beforeend", `<div style="float: left;">${ "Ranged Way Idle 设置" }</div></br>` ); for (let key in config) { if (key === 'keywords') continue; insertElem.insertAdjacentHTML( "beforeend", `<div style="float: left;"> <input type="checkbox" id="${key}" ${config[key].enable ? "checked" : ""}>${config[key].desc} </div></br>` ); } insertElem.addEventListener("change", saveConfig); // 控制 keywords 列表 const container = document.createElement('div'); container.style.marginTop = '20px'; const input = document.createElement('input'); input.type = 'text'; input.style.width = '200px'; input.placeholder = 'Ranged Way Idle 监听关键词'; const button = document.createElement('button'); button.textContent = '添加'; const listContainer = document.createElement('div'); listContainer.style.marginTop = '10px'; container.appendChild(input); container.appendChild(button); container.appendChild(listContainer); targetNode.parentNode.insertBefore(container, targetNode.nextSibling); function renderList() { listContainer.innerHTML = ''; config.keywords.forEach((item, index) => { const itemDiv = document.createElement('div'); itemDiv.textContent = item; itemDiv.style.margin = 'auto'; itemDiv.style.width = '200px'; itemDiv.style.cursor = 'pointer'; itemDiv.addEventListener('click', () => { config.keywords.splice(index, 1); renderList(); }); listContainer.appendChild(itemDiv); }); saveConfig(); } renderList(); button.addEventListener('click', () => { const newItem = input.value.trim(); if (newItem) { config.keywords.push(newItem); input.value = ''; saveConfig(); renderList(); } }); } } } function handleMessage(message) { try { const obj = JSON.parse(message); if (!obj) return message; switch (obj.type) { case "init_character_data": globalVariable.market.sellListings = {}; globalVariable.market.buyListings = {}; updateMarketListings(obj.myMarketListings); break; case "market_listings_updated": updateMarketListings(obj.endMarketListings); break; case "new_battle": if (config.notifyDeath.enable) initBattle(obj); break; case "battle_updated": if (config.notifyDeath.enable) checkDeath(obj); break; case "market_item_order_books_updated": if (config.forceUpdateMarketPrice.enable) marketPriceUpdate(obj); break; case "quests_updated": for (let e of globalVariable.task.taskValueElements) { e.remove(); } globalVariable.task.taskValueElements = []; globalVariable.task.hasTaskValueElement = false; break; case "chat_message_received": handleChatMessage(obj); break; } } catch (e) { console.error(e); } return message; } function notifyDeath(name) { // 如果间隔小于60秒,强制不播报 const nowTime = Date.now(); if (nowTime - globalVariable.battleData.lastNotifyTime < 60000) return; globalVariable.battleData.lastNotifyTime = nowTime; new Notification('🎉🎉🎉喜报🎉🎉🎉', {body: `${name} 死了!`}); } function initBattle(obj) { // 处理战斗中各个玩家的角色名,供播报死亡信息 globalVariable.battleData.players = []; for (let player of obj.players) { globalVariable.battleData.players.push({ name: player.name, isAlive: player.currentHitpoints > 0, }); if (player.currentHitpoints === 0) { notifyDeath(player.name); } } } function checkDeath(obj) { // 检查玩家是否死亡 if (!globalVariable.battleData.players) return; for (let key in obj.pMap) { const index = parseInt(key); if (globalVariable.battleData.players[index].isAlive && obj.pMap[key].cHP === 0) { // 角色 活->死 时发送提醒 globalVariable.battleData.players[index].isAlive = false; notifyDeath(globalVariable.battleData.players[index].name); } else if (obj.pMap[key].cHP > 0) { globalVariable.battleData.players[index].isAlive = true; } } } function marketPriceUpdate(obj) { globalVariable.task.taskTokenValueData = getTaskTokenValue(); // 本函数的代码复制自Magic Way Idle let itemDetailMap = globalVariable.itemDetailMap; let itemName = itemDetailMap[obj.marketItemOrderBooks.itemHrid].name; let ask = -1; let bid = -1; // 读取ask最低报价 if (obj.marketItemOrderBooks.orderBooks[0].asks && obj.marketItemOrderBooks.orderBooks[0].asks.length > 0) { ask = obj.marketItemOrderBooks.orderBooks[0].asks[0].price; } // 读取bid最高报价 if (obj.marketItemOrderBooks.orderBooks[0].bids && obj.marketItemOrderBooks.orderBooks[0].bids.length > 0) { bid = obj.marketItemOrderBooks.orderBooks[0].bids[0].price; } // 读取所有物品价格 let jsonObj = JSON.parse(localStorage.getItem("MWITools_marketAPI_json")); // 修改当前查看物品价格 if (jsonObj.market[itemName]) { jsonObj.market[itemName].ask = ask; jsonObj.market[itemName].bid = bid; } // 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改 localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(jsonObj)); } function handleChatMessage(obj) { // 处理聊天信息 if (obj.message.chan === "/chat_channel_types/whisper") { if (config.notifyWhisperMessages.enable) { globalVariable.whisperAudio.play(); } } else if (obj.message.chan === "/chat_channel_types/chinese") { if (config.listenKeywordMessages.enable) { for (let keyword of config.keywords) { if (obj.message.m.includes(keyword)) { globalVariable.keywordAudio.play(); } } } } } function autoClickTaskSortButton() { // 点击MWI TaskManager的任务排序按钮 const targetElement = document.querySelector('#TaskSort'); if (targetElement && targetElement.textContent !== '手动排序') { targetElement.click(); targetElement.textContent = '手动排序'; } } function formatCoinValue(num) { if (num >= 1e13) { return Math.floor(num / 1e12) + "T"; } else if (num >= 1e10) { return Math.floor(num / 1e9) + "B"; } else if (num >= 1e7) { return Math.floor(num / 1e6) + "M"; } else if (num >= 1e4) { return Math.floor(num / 1e3) + "K"; } return num.toString(); } function updateMarketListings(obj) { // 更新市场价格 for (let listing of obj) { if (listing.status === "/market_listing_status/cancelled") { delete globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id]; continue } globalVariable.market[listing.isSell ? "sellListings" : "buyListings"][listing.id] = { itemHrid: listing.itemHrid, price: (listing.orderQuantity - listing.filledQuantity) * (listing.isSell ? Math.ceil(listing.price * 0.98) : listing.price), unclaimedCoinCount: listing.unclaimedCoinCount, } } globalVariable.market.buyValue = 0; globalVariable.market.sellValue = 0; globalVariable.market.unclaimedValue = 0; for (let id in globalVariable.market.buyListings) { const listing = globalVariable.market.buyListings[id]; globalVariable.market.buyValue += listing.price; globalVariable.market.unclaimedValue += listing.unclaimedCoinCount; } for (let id in globalVariable.market.sellListings) { const listing = globalVariable.market.sellListings[id]; globalVariable.market.sellValue += listing.price; globalVariable.market.unclaimedValue += listing.unclaimedCoinCount; } globalVariable.market.hasFundsElement = false; } function showMarketListingsFunds() { // 如果已经存在节点,不必更新 if (globalVariable.market.hasFundsElement) return; const coinStackElement = document.querySelector("div.MarketplacePanel_coinStack__1l0UD"); // 不在市场面板,不必更新 if (coinStackElement) { coinStackElement.style.top = "0px"; coinStackElement.style.left = "0px"; let fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement"); while (fundsElement) { fundsElement.remove(); fundsElement = coinStackElement.parentNode.querySelector("div.fundsElement"); } makeNode("购买预付金", globalVariable.market.buyValue, ["125px", "0px"]); makeNode("出售可获金", globalVariable.market.sellValue, ["125px", "22px"]); makeNode("待领取金额", globalVariable.market.unclaimedValue, ["0px", "22px"]); globalVariable.market.hasFundsElement = true; } function makeNode(text, value, style) { let node = coinStackElement.cloneNode(true); node.classList.add("fundsElement"); const countNode = node.querySelector("div.Item_count__1HVvv"); const textNode = node.querySelector("div.Item_name__2C42x"); if (countNode) countNode.textContent = formatCoinValue(value); if (textNode) textNode.innerHTML = `<span style="color: rgb(102,204,255); font-weight: bold;">${text}</span>`; node.style.left = style[0]; node.style.top = style[1]; coinStackElement.parentNode.insertBefore(node, coinStackElement.nextSibling); } } function getTaskTokenValue() { const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data; const lootsName = ["大陨石舱", "大工匠匣", "大宝箱"]; const bidValueList = [ parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Bid"]), parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Bid"]), parseFloat(chestDropData["Large Treasure Chest"]["期望产出Bid"]), ] const askValueList = [ parseFloat(chestDropData["Large Meteorite Cache"]["期望产出Ask"]), parseFloat(chestDropData["Large Artisan's Crate"]["期望产出Ask"]), parseFloat(chestDropData["Large Treasure Chest"]["期望产出Ask"]), ] const res = { bidValue: Math.max(...bidValueList), askValue: Math.max(...askValueList) } // bid和ask的最佳兑换选项 res.bidLoots = lootsName[bidValueList.indexOf(res.bidValue)]; res.askLoots = lootsName[askValueList.indexOf(res.askValue)]; // bid和ask的任务代币价值 res.bidValue = Math.round(res.bidValue / 30); res.askValue = Math.round(res.askValue / 30); // 小紫牛的礼物的额外价值计算 res.giftValueBid = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Bid"])); res.giftValueAsk = Math.round(parseFloat(chestDropData["Purple's Gift"]["期望产出Ask"])); if (config.forceUpdateMarketPrice.enable) { const marketJSON = JSON.parse(localStorage.getItem("MWITools_marketAPI_json")); marketJSON.market["Task Token"].ask = res.askValue; marketJSON.market["Task Token"].bid = res.bidValue; localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(marketJSON)); } res.rewardValueBid = res.bidValue + res.giftValueBid / 50; res.rewardValueAsk = res.askValue + res.giftValueAsk / 50; return res; } function showTaskValue() { globalVariable.task.taskListElement = document.querySelector("div.TasksPanel_taskList__2xh4k"); // 如果不在任务面板,则销毁显示任务价值的元素 if (!globalVariable.task.taskListElement) { globalVariable.task.taskValueElements = []; globalVariable.task.hasTaskValueElement = false; globalVariable.task.taskListElement = null; return; } // 如果已经存在任务价值的元素,不再更新 if (globalVariable.task.hasTaskValueElement) return; globalVariable.task.hasTaskValueElement = true; const taskNodes = [...globalVariable.task.taskListElement.querySelectorAll("div.RandomTask_randomTask__3B9fA")]; function convertKEndStringToNumber(str) { if (str.endsWith('K') || str.endsWith('k')) { return Number(str.slice(0, -1)) * 1000; } else { return Number(str); } } taskNodes.forEach(function (node) { const reward = node.querySelector("div.RandomTask_rewards__YZk7D"); const coin = convertKEndStringToNumber(reward.querySelectorAll("div.Item_count__1HVvv")[0].innerText); const tokenCount = Number(reward.querySelectorAll("div.Item_count__1HVvv")[1].innerText); const newDiv = document.createElement("div"); newDiv.textContent = `奖励期望收益: ${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueAsk)} / ${formatCoinValue(coin + tokenCount * globalVariable.task.taskTokenValueData.rewardValueBid)}`; newDiv.style.color = "rgb(248,0,248)"; node.querySelector("div.RandomTask_action__3eC6o").appendChild(newDiv); globalVariable.task.taskValueElements.push(newDiv); }); } })();
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址