// ==UserScript== // @name Ranged Way Idle // @version 6.3 // @author AlphB // @description 一些超级有用的MWI的QoL功能 // @match https://*.milkywayidle.com/* // @match https://*.milkywayidlecn.com/* // @connect www.milkywayidle.com // @connect test.milkywayidle.com // @connect www.milkywayidlecn.com // @connect test.milkywayidlecn.com // @connect alphb.cn // @grant GM.xmlHttpRequest // @grant GM_notification // @grant GM_getValue // @grant GM_setValue // @icon https://tupian.li/images/2025/09/30/68dae3cf1fa7e.png // @license CC-BY-NC-SA-4.0 // @namespace http://tampermonkey.net/ // @run-at document-start // @downloadURL https://update.greasyfork.icu/scripts/535671/Ranged%20Way%20Idle.user.js // @updateURL https://update.greasyfork.icu/scripts/535671/Ranged%20Way%20Idle.meta.js // ==/UserScript== (function () { const configs = { combatClass: { notifyCombatDeath: { type: "switch", value: true, trigger: ["ws"], listenMessageTypes: ["new_battle", "battle_updated", "init_character_data"] }, minimumNotifyCooldownSeconds: {type: "input_number", value: 5, trigger: [],} }, messageClass: { notifyChatMessages: { type: "switch", value: true, trigger: ["ws", "ob"], listenMessageTypes: ["chat_message_received", "init_character_data"] }, notifyChatMessagesVolume: {type: "input_range", value: 0.5, trigger: [], min: 0, max: 1, step: 0.01}, notifyChatMessagesByRegex: {type: "switch", value: false, trigger: []}, notifyChatMessagesFilterSelf: {type: "switch", value: true, trigger: []}, consoleLogChatMessages: {type: "switch", value: true, trigger: []}, }, gameInfoClass: { initCharacterData: { type: "switch", value: true, trigger: ["ws"], listenMessageTypes: ["init_character_data"], isHidden: true }, updateLocalStorageMarketPrice: { type: "switch", value: true, trigger: ["ws"], listenMessageTypes: ["market_item_order_books_updated"] }, showTaskValue: { type: "switch", value: true, trigger: ["ws", "ob"], listenMessageTypes: ["quests_updated", "init_character_data"] }, showDungeonTokenValue: { type: "switch", value: true, trigger: ["ob"] }, trackLeaderBoardData: {type: "switch", value: true, trigger: ["ob"]}, }, gameUIClass: { autoClickTaskSortButton: {type: "switch", value: true, trigger: ["ob"]}, showMarketAPIUpdateTime: {type: "switch", value: true, trigger: ["ob"]}, forceUpdateAPIButton: {type: "switch", value: true, trigger: ["ob"]}, disableQueueUpgradeButton: {type: "switch", value: false, trigger: ["ob"]}, disableActionQueueBar: {type: "switch", value: false, trigger: ["ob"]}, hideSideBarButton: { type: "switch", value: false, trigger: ["ob", "ws"], listenMessageTypes: ["init_character_data"] }, hideTrainRubbishButton: {type: "switch", value: false, trigger: ["ob"]}, alwaysHideTrainRubbish: {type: "switch", value: false, trigger: []}, addWatermark: {type: "switch", value: false, trigger: ["ob"]}, watermarkText: {type: "input_text", value: "", trigger: []}, quickCopyItemHrid: {type: "switch", value: false, trigger: ["ob"]} }, listingClass: { hookListingInfo: { type: "switch", value: true, trigger: ["ws"], listenMessageTypes: ["market_listings_updated", "init_character_data"], isHidden: true }, saveListingInfoToLocalStorage: {type: "switch", value: true, trigger: []}, saveListingInfoToLocalStorageMaxDays: {type: "input_number", value: 30, trigger: []}, showTotalListingFunds: { type: "switch", value: true, trigger: ["ws", "ob"], listenMessageTypes: ["market_listings_updated"] }, showTotalListingFundsPrecise: {type: "input_number", value: 0, trigger: []}, showListingInfo: { type: "switch", value: true, trigger: ["ws", "ob"], listenMessageTypes: ["market_listings_updated", "market_item_order_books_updated", "init_character_data"] }, mwiOrderHelperCompatible: {type: "switch", value: false, trigger: []}, enableMagicSort: {type: "switch", value: false, trigger: []}, showListingPricePrecise: {type: "input_number", value: 2, trigger: []}, showListingCreateTimeByLifespan: {type: "switch", value: false, trigger: []}, listingSortTools: {type: "switch", value: false, isHidden: true, trigger: ["ob"]}, // TO DO notifyListingFilled: { type: "switch", value: false, trigger: ["ws"], listenMessageTypes: ["market_listings_updated"] }, notifyListingFilledVolume: {type: "input_range", value: 0.5, trigger: [], min: 0, max: 1, step: 0.01}, orderBooksInfo: { type: "switch", value: true, trigger: ["ws", "ob"], listenMessageTypes: ["market_item_order_books_updated"] }, estimateListingCreateTimeColorByAccuracy: {type: "switch", value: false, trigger: []}, estimateListingCreateTimeColorByLifespan: {type: "switch", value: false, trigger: []}, estimateListingCreateTimeByLifespan: {type: "switch", value: false, trigger: []}, }, immemorialMarketClass: { enableImmemorialMarket: { type: "switch", value: false, trigger: ["ob", "ws"], listenMessageTypes: ["init_character_data", "chat_message_received", "market_item_order_books_updated", "market_listings_updated"] }, debugPrintIMWSMessages: {type: "switch", value: false}, }, otherClass: { scriptLanguage: {type: "select", value: "zh-cn", trigger: [], options: ["zh-cn", "en-us"]}, showSponsor: {type: "switch", value: false, trigger: ["ob"]}, mournForMagicWayIdle: {type: "switch", value: true, trigger: ["init"]}, debugPrintWSMessages: {type: "switch", value: false, trigger: [], listenMessageTypes: []}, lazyLoadScript: {type: "switch", value: false, trigger: []}, showConfigMenu: {type: "switch", value: true, trigger: ["ob"], isHidden: true} } }; const globalVariables = { marketAPIUrl: "https://www.milkywayidle.com/game_data/marketplace.json", initCharacterData: null, documentObserver: null, documentObserverFunction: null, webSocketMessageProcessor: null, functionMap: {}, notifyMessageAudio: new Audio("https://upload.thbwiki.cc/d/d1/se_bonus2.mp3"), notifyListingFilledAudio: new Audio("https://upload.thbwiki.cc/f/ff/se_trophy.mp3"), allListings: {}, configs: configs, isIMRealNameOrderEnabled: false, imListingsOwnerMap: {}, imListingsToDeleteSet: new Set(), imListingsCreateTimeData: [], }; unsafeWindow._rwivb = globalVariables; const I18NMap = { "combatClass": {"zh-cn": "战斗功能", "en-us": "Combat Functions"}, "messageClass": {"zh-cn": "聊天功能", "en-us": "Message Functions"}, "gameInfoClass": {"zh-cn": "游戏信息设置", "en-us": "Game Info"}, "gameUIClass": {"zh-cn": "游戏界面设置", "en-us": "Game UI"}, "listingClass": {"zh-cn": "挂单功能设置", "en-us": "Listing Functions"}, "immemorialMarketClass": { "zh-cn": "熙攘市场", "en-us": "Immemorial Market (Do not support english version currently)" }, "otherClass": {"zh-cn": "其他设置", "en-us": "Other Functions"}, "ranged_way_idle_config_menu_title": {"zh-cn": "设置", "en-us": "Config"}, "notifyCombatDeath": {"zh-cn": "战斗中角色死亡时,发出通知", "en-us": "Notify when a character dies in combat"}, "minimumNotifyCooldownSeconds": { "zh-cn": "角色死亡通知冷却时间(秒)", "en-us": "Minimum cooldown time for notifying when a character dies in combat (seconds)" }, "notifyChatMessages": { "zh-cn": "聊天消息含有关键词时,发出声音提醒", "en-us": "Notify when chat messages contain preset keywords" }, "notifyChatMessagesVolume": {"zh-cn": "聊天消息声音提醒音量", "en-us": "Chat message notify sound volume"}, "notifyChatMessagesByRegex": {"zh-cn": "聊天消息采用正则匹配", "en-us": "Use regex to match chat messages"}, "notifyChatMessagesFilterSelf": { "zh-cn": "不提醒自己发送的聊天消息", "en-us": "Filter out chat messages sent by yourself" }, "consoleLogChatMessages": { "zh-cn": "在控制台输出提醒的消息", "en-us": "Log notify chat messages to console" }, "updateLocalStorageMarketPrice": { "zh-cn": "更新localStorage中的市场价格", "en-us": "Update localStorage market price while click in market" }, "showTaskValue": { "zh-cn": "显示任务期望收益(依赖 食用工具)", "en-us": "Show task expected value (requires TaskManager)" }, "showDungeonTokenValue": { "zh-cn": "商店中显示地下城战利品价值", "en-us": "Show dungeon token value at shop" }, "trackLeaderBoardData": {"zh-cn": "跟踪排行榜数据", "en-us": "Track leaderboard data"}, "autoClickTaskSortButton": { "zh-cn": "自动点击任务排序按钮(依赖 MWI TaskManager)", "en-us": "Auto-click task sort button (requires TaskManager)" }, "showMarketAPIUpdateTime": {"zh-cn": "显示市场API更新时间", "en-us": "Show market API update time"}, "forceUpdateAPIButton": {"zh-cn": "强制更新市场API按钮", "en-us": "Force update market API button"}, "disableQueueUpgradeButton": { "zh-cn": "禁用各处队列升级按钮,以防跳转至牛铃商店", "en-us": "Disable queue upgrade buttons to prevent redirect to cowbell shop" }, "disableActionQueueBar": {"zh-cn": "禁用行动队列提示框显示", "en-us": "Disable action queue bar display"}, "hideSideBarButton": {"zh-cn": "隐藏左侧边栏的部分按钮", "en-us": "Hide some buttons in left sidebar"}, "hideTrainRubbishButton": { "zh-cn": "允许隐藏背包里的火车垃圾(无强化等级的奶酪、木制、皮革或布料装备等)", "en-us": "Allow hiding train rubbish in inventory (with no enhancement level)" }, "alwaysHideTrainRubbish": { "zh-cn": "总是自动隐藏背包里的火车垃圾", "en-us": "Always hide train rubbish in inventory" }, "addWatermark": { "zh-cn": "为整个页面添加水印,以防止他人偷图", "en-us": "Add watermark to whole page to prevent stealing your show-off image" }, "watermarkText": { "zh-cn": "水印文字", "en-us": "Watermark text" }, "quickCopyItemHrid": { "zh-cn": "快速复制itemHrid", "en-us": "Quick copy itemHrid" }, "saveListingInfoToLocalStorage": { "zh-cn": "保存挂单信息到localStorage", "en-us": "Save listing info to localStorage" }, "saveListingInfoToLocalStorageMaxDays": { "zh-cn": "挂单信息本地保存时间(天)", "en-us": "Max days to save listing info to localStorage" }, "showTotalListingFunds": { "zh-cn": "显示市场挂单的总购买预付金/出售可获金/待领取金额", "en-us": "Show total listing funds (purchase prepaid coins/sell result coins/unclaimed coins)" }, "showTotalListingFundsPrecise": { "zh-cn": "显示市场挂单的总购买预付金/出售可获金/待领取金额的精度", "en-us": "Precise of total listing funds" }, "showListingInfo": {"zh-cn": "显示各个挂单的价格、创建时间信息", "en-us": "Show listing price/create time"}, "mwiOrderHelperCompatible": { "zh-cn": "尝试兼容Magic Sort。此功能由@adudu完成,暂不对维护开启这个功能后遇到的bug。", "en-us": "Try to be compatible with Magic Sort. This feature is developed by @adudu, and I will not maintain this feature after encountering bugs." }, "enableMagicSort": { "zh-cn": "启用Magic Sort。此功能由@adudu完成,暂不对维护开启这个功能后遇到的bug。", "en-us": "Enable magic sort. This feature is developed by @adudu, and I will not maintain this feature after encountering bugs.", }, "showListingPricePrecise": { "zh-cn": "各个挂单的购买预付金/出售可获金的价格精度", "en-us": "Precise of listing price" }, "showListingCreateTimeByLifespan": { "zh-cn": "显示挂单已存在时长,而非创建的时刻", "en-us": "Show listing lifespan instead of create time" }, "notifyListingFilled": {"zh-cn": "挂单完成时,发出声音提醒", "en-us": "Notify when a listing is filled"}, "notifyListingFilledVolume": {"zh-cn": "挂单完成声音提醒音量", "en-us": "Listing filled notify sound volume"}, "orderBooksInfo": { "zh-cn": "估算挂单创建时间。显示挂单所有者(必须启用熙攘市场的对应功能)", "en-us": "Estimate listing create time. Show listing owner (requires immemorial market feature)" }, "estimateListingCreateTimeColorByAccuracy": { "zh-cn": "依据精度为挂单创建时间着色(越偏向绿色 精度越高)该项为真时,覆盖下一选项设置", "en-us": "Color listing create time by accuracy (green for high accuracy). while this option is true, it overrides the next option setting" }, "estimateListingCreateTimeColorByLifespan": { "zh-cn": "依据存在时间为挂单创建时间着色(越偏向绿色 创建时间越短)", "en-us": "Color listing create time by lifespan (green for short lifespan)" }, "estimateListingCreateTimeByLifespan": { "zh-cn": "估算结果显示为挂单已存在时长,而非创建的时刻", "en-us": "Show estimate listing create time by lifespan" }, "enableImmemorialMarket": { "zh-cn": "启用🌈熙攘市场🌈(启用后请刷新页面)", "en-us": "Enable 🌈Immemorial Market🌈(Please refresh page after enabling this feature)" }, "debugPrintIMWSMessages": { "zh-cn": "打印IMWebSocket消息(不推荐打开)", "en-us": "Print IMWebSocket messages (not recommended)" }, "scriptLanguage": {"zh-cn": "语言 🌏", "en-us": "Language 🌏"}, "showSponsor": {"zh-cn": "赞助作者", "en-us": "Buy me a coffee"}, "mournForMagicWayIdle": {"zh-cn": "在控制台为Magic Way Idle默哀", "en-us": "Mourn for Magic Way Idle"}, "debugPrintWSMessages": { "zh-cn": "打印WebSocket消息(不推荐打开)", "en-us": "Print WebSocket messages (not recommended)" }, "lazyLoadScript": { "zh-cn": "懒加载脚本以提升初始化脚本时的性能,但可能导致部分功能更改开关后,需要手动刷新页面才能生效。", "en-us": "Lazy load script to improve performance during initialization, but may cause some features to not work properly until page refresh." }, "configNoteText": { "zh-cn": "部分设置可能需要刷新页面才能生效。如果完全无效,或者控制台大量报错,请尝试更新本插件或前置插件", "en-us": "Some settings may not take effect until page refresh. If not working, or console is spammed with errors, try updating this script or its pre-requisites." }, "notifyChatMessagesAddRowButton": {"zh-cn": "添加聊天消息监听关键词", "en-us": "Add chat message keyword"}, "taskExpectedValueText": {"zh-cn": "任务期望收益:", "en-us": "Task expected value:"}, "dungeonTokenValueTipText": { "zh-cn": "数字为每代币价值(左一/右一)。绿色为对应地下城的最高价。", "en-us": "Number is the value of each token (Top ask / Top bid). Green stands for the highest value for the corresponding dungeon." }, "trackLeaderBoardDataLeaderboardStoreButton": {"zh-cn": "记录当前排行榜数据", "en-us": "Record current data"}, "trackLeaderBoardDataLeaderboardDeleteButton": {"zh-cn": "删除本地数据", "en-us": "Delete local data"}, "trackLeaderBoardDataLeaderboardRecordTimeText": { "zh-cn": "本地数据记录于:${recordTime}(${timeDelta}小时前)", "en-us": "Local data recorded at: ${recordTime} (${timeDelta} hours ago)" }, "trackLeaderBoardDataLeaderboardNoRecordTimeText": { "zh-cn": "无本地数据记录", "en-us": "No local data recorded" }, "trackLeaderBoardDataNoteText": { "zh-cn": "由于排行榜数据每20分钟记录一次,增速和超越时间有误差,仅供参考。", "en-us": "Due to the leaderboard update every 20 minutes, speed and catchup time may be inaccurate. This is for reference only." }, "trackLeaderBoardDataDifference": {"zh-cn": "增量", "en-us": "Difference"}, "trackLeaderBoardDataSpeed": {"zh-cn": "增速", "en-us": "Speed"}, "trackLeaderBoardDataCatchupTime": {"zh-cn": "超越时间", "en-us": "Catchup time"}, "trackLeaderBoardDataCatchupTimeNow": {"zh-cn": "现在!", "en-us": "Now!"}, "trackLeaderBoardDataNewRecordText": {"zh-cn": "新上榜", "en-us": "New in LB"}, "showMarketAPIUpdateTimeText": {"zh-cn": "市场API更新时间于:", "en-us": "Market API update time:"}, "forceUpdateAPIButtonText": {"zh-cn": "强制更新市场API", "en-us": "Force update market API"}, "forceUpdateAPIButtonTextSuccess": { "zh-cn": "更新成功。市场数据更新于", "en-us": "Update success. Market data updated at:" }, "forceUpdateAPIButtonTextError": { "zh-cn": "更新失败。请稍后重试。", "en-us": "Update failed. Please try again later." }, "forceUpdateAPIButtonTextTimeout": { "zh-cn": "更新超时。请稍后重试。", "en-us": "Update timeout. Please try again later." }, "hideSidebarText": {"zh-cn": "隐藏左侧边栏按钮配置", "en-us": "Hide sidebar buttons config"}, "hideTrainRubbishButtonText": {"zh-cn": "隐藏火车垃圾", "en-us": "Hide train rubbish"}, "showTrainRubbishButtonText": {"zh-cn": "显示火车垃圾", "en-us": "Show train rubbish"}, "quickCopyItemHridButtonText": {"zh-cn": "复制itemHrid", "en-us": "Copy itemHrid"}, "totalUnclaimedCoinsText": {"zh-cn": "待领取金额", "en-us": "Unclaimed"}, "totalPrepaidCoinsText": {"zh-cn": "购买预付金", "en-us": "Purchase prepaid"}, "totalSellResultCoinsText": {"zh-cn": "出售可获金", "en-us": "Sell result"}, "showListingInfoCreateTimeAt": {"zh-cn": "创建于", "en-us": "Created at"}, "showListingInfoCreateTimeLifespan": { "zh-cn": "已存在 ${days}天${hours}时${minutes}分${seconds}秒", "en-us": "Lifespan: ${days}d ${hours}h ${minutes}m ${seconds}s" }, "showListingInfoTopOrderPriceText": {"zh-cn": "左一/右一 价格", "en-us": "Top order price"}, "showListingInfoTotalPriceText": {"zh-cn": "购买预付金/出售可获金", "en-us": "Purchase prepaid / Sell result"}, "estimateListingCreateTimeText": {"zh-cn": "估计创建时间", "en-us": "Estimated create time"}, "realNameOrderText": {"zh-cn": "挂单所有者", "en-us": "Owner"}, "unknownRealName": {"zh-cn": "未知", "en-us": "Unknown"}, "estimateListingCreateTimeLifespan": { "zh-cn": "${days}天${hours}时${minutes}分", "en-us": "${days}d ${hours}h ${minutes}m" }, "sponsorTipText": { "zh-cn": "下列赞助排名按照首字母排序。赞助为自愿性质,不包含额外服务!", "en-us": "Sponsor list sorted by first letter. Sponsorship is voluntary and does not include any additional services!" }, "sponsorText": {"zh-cn": "赞助作者", "en-us": "Buy me a coffee"}, "sponsorAlertText": { "zh-cn": "本赞助为纯自愿捐赠,不包含任何本脚本的额外隐藏功能或服务。如果您愿意,可以在备注中写上你的ID,作者将会把您的名字加入到赞助名单中。忘记备注ID的可以联系AlphB提供支付证明来补充。", "en-us": "This sponsorship is purely voluntary and does not include any additional hidden features or services for this script. If you wish, you can write your ID in the note and the author will add your name to the sponsor list. If you forgot to input your ID, you can contact AlphB to provide payment proof to add it back." }, "zh-cn": {"zh-cn": "中文", "en-us": "中文"}, "en-us": {"zh-cn": "English", "en-us": "English"}, "characterID": {"zh-cn": "角色ID", "en-us": "Character ID"}, "sponsorValue": {"zh-cn": "赞助金额", "en-us": "Sponsor value"}, "IMOpenConfigPanel": {"zh-cn": "打开熙攘市场配置面板", "en-us": "Open Immemorial Market Config Panel"}, "/chat_channel_types/general": {"zh-cn": "英语", "en-us": "English"}, "/chat_channel_types/chinese": {"zh-cn": "中文", "en-us": "Chinese"}, "/chat_channel_types/ironcow": {"zh-cn": "铁牛", "en-us": "Ironcow"}, "/chat_channel_types/trade": {"zh-cn": "交易", "en-us": "Trade"}, "/chat_channel_types/recruit": {"zh-cn": "招募", "en-us": "Recruit"}, "/chat_channel_types/beginner": {"zh-cn": "新手", "en-us": "Beginner"}, "/chat_channel_types/guild": {"zh-cn": "公会", "en-us": "Guild"}, "/chat_channel_types/party": {"zh-cn": "队伍", "en-us": "Party"}, "/chat_channel_types/whisper": {"zh-cn": "私聊", "en-us": "Whisper"}, "/chat_channel_types/moderator": {"zh-cn": "管理员", "en-us": "Moderator"}, "CHAT_CHANNEL_ANY": {"zh-cn": "所有频道", "en-us": "All channels"}, "/chat_channel_types/arabic": {"zh-cn": "العربية", "en-us": "العربية"}, "/chat_channel_types/french": {"zh-cn": "Français", "en-us": "Français"}, "/chat_channel_types/german": {"zh-cn": "Deutsch", "en-us": "Deutsch"}, "/chat_channel_types/hebrew": {"zh-cn": "עברית", "en-us": "עברית"}, "/chat_channel_types/hindi": {"zh-cn": "हिंदी", "en-us": "हिंदी"}, "/chat_channel_types/japanese": {"zh-cn": "日本語", "en-us": "日本語"}, "/chat_channel_types/korean": {"zh-cn": "한국어", "en-us": "한국어"}, "/chat_channel_types/portuguese": {"zh-cn": "Português", "en-us": "Português"}, "/chat_channel_types/russian": {"zh-cn": "Русский", "en-us": "Русский"}, "/chat_channel_types/spanish": {"zh-cn": "Español", "en-us": "Español"}, "/chat_channel_types/vietnamese": {"zh-cn": "Tiếng Việt", "en-us": "Tiếng Việt"}, }; function initScript() { if (document.URL.includes("test.milkywayidle")) { if (document.URL.includes("test.milkywayidle.com")) { globalVariables.marketAPIUrl = "https://test.milkywayidlecn.com/game_data/marketplace.json"; } else { globalVariables.marketAPIUrl = "https://test.milkywayidle.com/game_data/marketplace.json"; } } else { if (document.URL.includes("www.milkywayidle.com")) { globalVariables.marketAPIUrl = "https://www.milkywayidle.com/game_data/marketplace.json"; } else { globalVariables.marketAPIUrl = "https://www.milkywayidlecn.com/game_data/marketplace.json"; } } const localDeletedSet = JSON.parse(localStorage.getItem('ranged_way_idle_deleted_listings') || "[]"); localDeletedSet.forEach(listingId => globalVariables.imListingsToDeleteSet.add(listingId)); const allFunctionsObject = new AllFunctions(); const localConfig = localStorage.getItem("ranged_way_idle_configs"); const lazyLoad = localConfig ? JSON.parse(localConfig)?.otherClass?.lazyLoadScript : false; const otherClass = allFunctionsObject.otherClass(); globalVariables.functionMap.otherClass = { showConfigMenu: otherClass.showConfigMenu() }; globalVariables.functionMap.otherClass.showConfigMenu.loadLocalConfig(); for (const configClass in configs) { if (configClass !== 'otherClass') { globalVariables.functionMap[configClass] = {}; } const functionClassObject = allFunctionsObject[configClass](); for (const configName in configs[configClass]) { if (configs[configClass][configName].type !== 'switch') continue; if (!(configs[configClass][configName]?.trigger?.length > 0)) continue; if (lazyLoad && !configs[configClass][configName].value) continue; if (!functionClassObject[configName]) { console.warn("No function found for config: " + configName); } else if (configName !== 'showConfigMenu') { globalVariables.functionMap[configClass][configName] = functionClassObject[configName](); } } } hookWebSocket(); initDocumentObserver(); for (const configClass in configs) { for (const configName in configs[configClass]) { if (configs[configClass][configName].type === 'switch' && configs[configClass][configName].value && globalVariables.functionMap[configClass][configName] && configs[configClass][configName].trigger.includes("init")) { try { globalVariables.functionMap[configClass][configName].init(); } catch (err) { console.error(err); } } } } function hookWebSocket() { // message processor globalVariables.webSocketMessageProcessor = function (message, type) { const obj = JSON.parse(message); if (configs.otherClass.debugPrintWSMessages.value) console.log(type, obj); if (type !== 'get' || !obj) return; const messageType = obj.type; for (const configClass in configs) { for (const configName in configs[configClass]) { if (configs[configClass][configName].type === 'switch' && configs[configClass][configName].value && globalVariables.functionMap[configClass][configName] && configs[configClass][configName]?.trigger?.includes('ws') && configs[configClass][configName].listenMessageTypes && configs[configClass][configName].listenMessageTypes.includes(messageType)) { try { globalVariables.functionMap[configClass][configName].ws(obj); } catch (err) { console.error(err); } } } } }; // get const oriGet = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data").get; function hookedGet() { const socket = this.currentTarget; if (!(socket instanceof WebSocket) || !socket.url || !socket.url.includes("wss://api.milkywayidle")) { return oriGet.call(this); } const message = oriGet.call(this); try { globalVariables.webSocketMessageProcessor(message, 'get') } catch (err) { console.error(err); } return message; } Object.defineProperty(MessageEvent.prototype, "data", { get: hookedGet, configurable: true, enumerable: true }); // send const originalSend = WebSocket.prototype.send; WebSocket.prototype.send = function (message) { if (!this.url || !this.url.includes("wss://api.milkywayidle")) { return originalSend.call(this, message); } try { globalVariables.webSocketMessageProcessor(message, 'send'); } catch (err) { console.error(err); } return originalSend.call(this, message); }; } function initDocumentObserver() { globalVariables.documentObserverFunction = function documentObserverFunction(mutationsList, observer) { globalVariables.documentObserver.disconnect(); for (const configClass in configs) { for (const configName in configs[configClass]) { const config = configs[configClass][configName]; if (config.type === 'switch' && config.value && config?.trigger?.includes('ob') && globalVariables.functionMap[configClass][configName]) { try { globalVariables.functionMap[configClass][configName].ob(document); } catch (err) { console.error(err); } } } } globalVariables.documentObserver.observe(document, {childList: true, subtree: true}); } globalVariables.documentObserver = new MutationObserver(globalVariables.documentObserverFunction); globalVariables.documentObserver.observe(document, {childList: true, subtree: true}); } } class AllFunctions { combatClass() { function notifyCombatDeath() { const players = []; let lastNotificationTime = 0; function newBattle(obj) { players.length = 0; for (const player of obj.players) { players.push({ name: player.name, isAlive: player.currentHitpoints > 0 }); if (player.currentHitpoints === 0) { new Notification('战斗提醒', {body: `${player.name} 死了!`}); } } } function battleUpdated(obj) { for (const playerIndex in obj.pMap) { const player = players[playerIndex]; if (player.isAlive && obj.pMap[playerIndex].cHP === 0 && Date.now() - lastNotificationTime > 1000 * configs.combatClass.minimumNotifyCooldownSeconds.value) { new Notification('战斗提醒', {body: `${player.name} 死了!`}); lastNotificationTime = Date.now(); } player.isAlive = obj.pMap[playerIndex].cHP > 0; } } function ws(obj) { if (obj.type === "new_battle") { newBattle(obj); } else if (obj.type === "battle_updated") { battleUpdated(obj); } else if (obj.type === "init_character_data") { Notification.requestPermission(); } } return {ws: ws}; } return {notifyCombatDeath: notifyCombatDeath}; } messageClass() { function notifyChatMessages() { const allChannels = ["CHAT_CHANNEL_ANY", "/chat_channel_types/chinese", "/chat_channel_types/general", "/chat_channel_types/ironcow", "/chat_channel_types/trade", "/chat_channel_types/recruit", "/chat_channel_types/beginner", "/chat_channel_types/guild", "/chat_channel_types/party", "/chat_channel_types/whisper", "/chat_channel_types/moderator", "/chat_channel_types/arabic", "/chat_channel_types/french", "/chat_channel_types/german", "/chat_channel_types/hebrew", "/chat_channel_types/hindi", "/chat_channel_types/japanese", "/chat_channel_types/korean", "/chat_channel_types/portuguese", "/chat_channel_types/russian", "/chat_channel_types/spanish", "/chat_channel_types/vietnamese",]; let listenObject = {}; let messageListerMenuRootNode; function createNewRow(selectedChannel = "", inputText = "") { const listenRow = document.createElement("div"); listenRow.classList.add("RangedWayIdleMessageListenRow"); // channel select const selectNode = document.createElement('select'); allChannels.forEach(channel => { const option = document.createElement('option'); option.value = channel; option.textContent = I18N(channel); if (channel === selectedChannel) { option.selected = true; } selectNode.appendChild(option); }); selectNode.addEventListener('change', updateListenObject); // input text const inputNode = document.createElement('input'); inputNode.type = 'text'; inputNode.value = inputText; inputNode.style.width = "15rem"; inputNode.addEventListener('input', updateListenObject); // delete button const deleteButton = document.createElement('button'); deleteButton.textContent = "×"; deleteButton.addEventListener('click', function () { listenRow.remove(); updateListenObject(); }); deleteButton.style.backgroundColor = "#F44444"; // add to row listenRow.appendChild(selectNode); listenRow.appendChild(inputNode); listenRow.appendChild(deleteButton); return listenRow; } function updateListenObject() { const newListenObject = {}; for (const channel of allChannels) { newListenObject[channel] = []; } // collect channel and text from rows for (const row of messageListerMenuRootNode.querySelectorAll('.RangedWayIdleMessageListenRow')) { const channel = row.querySelector('select').value; const text = row.querySelector('input').value.trim(); newListenObject[channel].push(text); } listenObject = newListenObject; localStorage.setItem("ranged_way_idle_listen_chat_messages", JSON.stringify(listenObject)); } function ws(obj) { if (obj.type === "chat_message_received") { if (configs.messageClass.notifyChatMessagesFilterSelf.value && obj.message.cId === globalVariables.initCharacterData.character.id) return; const channel = obj.message.chan; const text = obj.message.m; const toMatchTexts = []; if (listenObject["CHAT_CHANNEL_ANY"]) { for (const listenText of listenObject["CHAT_CHANNEL_ANY"]) { toMatchTexts.push(listenText); } } if (listenObject[channel]) { for (const listenText of listenObject[channel]) { toMatchTexts.push(listenText); } } for (const listenText of toMatchTexts) { if (configs.messageClass.notifyChatMessagesByRegex.value) { const regex = new RegExp(listenText, "g"); if (regex.test(text)) { globalVariables.notifyMessageAudio.volume = configs.messageClass.notifyChatMessagesVolume.value; globalVariables.notifyMessageAudio.play(); if (configs.messageClass.consoleLogChatMessages.value) { console.log(`[${I18N(channel)}] ${obj.message.sName}: ${text}`); } return; } } else { if (text.includes(listenText)) { globalVariables.notifyMessageAudio.volume = configs.messageClass.notifyChatMessagesVolume.value; globalVariables.notifyMessageAudio.play(); if (configs.messageClass.consoleLogChatMessages.value) { console.log(`[${I18N(channel)}] ${obj.message.sName}: ${text}`); } return; } } } } else if (obj.type === "init_character_data") { const localListenObject = localStorage.getItem("ranged_way_idle_listen_chat_messages"); if (localListenObject) { listenObject = JSON.parse(localListenObject); } } } function ob(node) { // add this after config menu const configMenuRootNode = node.querySelector(".RangedWayIdleConfigMenuRoot"); if (!configMenuRootNode) return; if (node.querySelector(".RangedWayIdleMessageListerMenu")) return; messageListerMenuRootNode = document.createElement("div"); messageListerMenuRootNode.classList.add("RangedWayIdleMessageListerMenu"); // new row button const addNewRowButton = document.createElement("button"); addNewRowButton.textContent = I18N("notifyChatMessagesAddRowButton"); addNewRowButton.addEventListener("click", () => { messageListerMenuRootNode.appendChild(createNewRow()); }); addNewRowButton.style.backgroundColor = "#66CCFF"; addNewRowButton.style.color = "#000000"; messageListerMenuRootNode.appendChild(addNewRowButton); // load local listeners for (const channel of allChannels) { if (listenObject[channel]) { for (const text of listenObject[channel]) { messageListerMenuRootNode.appendChild(createNewRow(channel, text)); } } } configMenuRootNode.insertAdjacentElement("afterend", messageListerMenuRootNode); } return {ws: ws, ob: ob}; } return {notifyChatMessages: notifyChatMessages}; } gameInfoClass() { function initCharacterData() { function ws(obj) { globalVariables.initCharacterData = obj; } return {ws: ws}; } function updateLocalStorageMarketPrice() { function ws(obj) { if (obj.type === "market_item_order_books_updated") { const localMarketAPIJson = JSON.parse(localStorage.getItem('MWITools_marketAPI_json')); const itemHrid = obj.marketItemOrderBooks.itemHrid; const orderBooks = obj.marketItemOrderBooks.orderBooks; for (let enhanceLevel = 0; enhanceLevel <= 20; enhanceLevel++) { if (orderBooks[enhanceLevel]) { // 如果左右至少有一个挂单,则需要更新为该价格 let askValue = -1; const ask = orderBooks[enhanceLevel].asks; if (ask && ask.length) { askValue = Math.min(...ask.map(listing => listing.price)); } let bidValue = -1; const bid = orderBooks[enhanceLevel].bids; if (bid && bid.length) { bidValue = Math.max(...bid.map(listing => listing.price)); } if (askValue !== -1 || bidValue !== -1) { localMarketAPIJson.marketData[itemHrid][enhanceLevel] = { a: askValue, b: bidValue }; } } else if (enhanceLevel === 0) { // 左右都没有,强化等级为+0,记录为-1 localMarketAPIJson.marketData[itemHrid][enhanceLevel] = { a: -1, b: -1 } } else { // 左右都没有,强化等级不为+0,删除记录 delete localMarketAPIJson.marketData[itemHrid][enhanceLevel]; } } // 将修改后结果写回marketAPI缓存,完成对marketAPI价格的强制修改 localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(localMarketAPIJson)); } } return {ws: ws}; } function showTaskValue() { let taskValueObject; 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"])); res.rewardValueBid = res.bidValue + res.giftValueBid / 50; res.rewardValueAsk = res.askValue + res.giftValueAsk / 50; return res; } function updateTaskValueNode(node) { const taskListNode = node.querySelector(".TasksPanel_taskList__2xh4k"); if (!taskListNode) return; if (taskListNode.querySelector(".RangedWayIdleTaskValue")) return; for (const taskNode of taskListNode.querySelectorAll(".RandomTask_taskInfo__1uasf")) { const rewardsNode = taskNode.querySelector(".RandomTask_rewards__YZk7D"); let coinCount = 0; let taskTokenCount = 0; for (const itemContainerNode of rewardsNode.querySelectorAll(".Item_itemContainer__x7kH1")) { if (itemContainerNode.querySelector("use").href.baseVal.includes("coin")) { coinCount = parseItemCount(itemContainerNode.querySelector(".Item_count__1HVvv").textContent); } else if (itemContainerNode.querySelector("use").href.baseVal.includes("task_token")) { taskTokenCount = parseItemCount(itemContainerNode.querySelector(".Item_count__1HVvv").textContent); } } const askValue = taskTokenCount * taskValueObject.rewardValueAsk + coinCount; const bidValue = taskTokenCount * taskValueObject.rewardValueBid + coinCount; const taskValueDivNode = document.createElement("div"); taskValueDivNode.classList.add("RangedWayIdleTaskValue"); taskValueDivNode.textContent = I18N("taskExpectedValueText") + `${formatItemCount(askValue)} / ${formatItemCount(bidValue)}`; taskValueDivNode.style.color = "#66CCFF"; taskValueDivNode.style.fontSize = "0.75rem"; taskNode.querySelector(".RandomTask_action__3eC6o").appendChild(taskValueDivNode); } } function updateTaskShopItemValue(node) { const taskShopPanelNode = node.querySelector(".TasksPanel_taskShop__q5sHL"); if (!taskShopPanelNode) return; if (taskShopPanelNode.classList.contains("RangedWayIdleTaskShopValueSet")) return; const chestDropData = JSON.parse(localStorage.getItem("Edible_Tools")).Chest_Drop_Data; taskShopPanelNode.classList.add("RangedWayIdleTaskShopValueSet"); const nameMap = { "large_meteorite_cache": "Large Meteorite Cache", "large_artisans_crate": "Large Artisan's Crate", "large_treasure_chest": "Large Treasure Chest" } for (const taskShopItemNode of taskShopPanelNode.querySelectorAll(".TasksPanel_item__DWSpv")) { const item = taskShopItemNode.querySelector(".TasksPanel_iconContainer__2JGVN use").href.baseVal.split("#")[1]; if (!Object.keys(nameMap).includes(item)) { continue; } const name = nameMap[item]; const askValue = parseFloat(chestDropData[name]["期望产出" + "Ask"]); const bidValue = parseFloat(chestDropData[name]["期望产出" + "Bid"]); const divNode = document.createElement("div"); divNode.textContent = `${formatItemCount(askValue)} / ${formatItemCount(bidValue)}`; divNode.style.color = "#66CCFF"; taskShopItemNode.insertBefore(divNode, taskShopItemNode.lastChild); } } function ws(obj) { if (obj.type === "quests_updated") { // remove old task value nodes document.querySelectorAll(".RangedWayIdleTaskValue").forEach(node => { node.remove(); }); } else if (obj.type === "init_character_data") { taskValueObject = getTaskTokenValue(); if (configs.gameInfoClass.updateLocalStorageMarketPrice.value) { const localMarketAPIJson = JSON.parse(localStorage.getItem("MWITools_marketAPI_json")); localMarketAPIJson.marketData["/items/task_token"] = { "0": { a: taskValueObject.askValue, b: taskValueObject.bidValue } }; localStorage.setItem("MWITools_marketAPI_json", JSON.stringify(localMarketAPIJson)); } } } function ob(node) { // set task expected value updateTaskValueNode(node); // set task shop item value updateTaskShopItemValue(node); } return {ws: ws, ob: ob}; } function showDungeonTokenValue() { const tokenMap = { "chimerical_essence": {tokenType: "D1", tokenCount: 1}, "griffin_leather": {tokenType: "D1", tokenCount: 600}, "manticore_sting": {tokenType: "D1", tokenCount: 1000}, "jackalope_antler": {tokenType: "D1", tokenCount: 1200}, "dodocamel_plume": {tokenType: "D1", tokenCount: 3000}, "griffin_talon": {tokenType: "D1", tokenCount: 3000}, "sinister_essence": {tokenType: "D2", tokenCount: 1}, "acrobats_ribbon": {tokenType: "D2", tokenCount: 2000}, "magicians_cloth": {tokenType: "D2", tokenCount: 2000}, "chaotic_chain": {tokenType: "D2", tokenCount: 3000}, "cursed_ball": {tokenType: "D2", tokenCount: 3000}, "enchanted_essence": {tokenType: "D3", tokenCount: 1}, "royal_cloth": {tokenType: "D3", tokenCount: 2000}, "knights_ingot": {tokenType: "D3", tokenCount: 2000}, "bishops_scroll": {tokenType: "D3", tokenCount: 2000}, "regal_jewel": {tokenType: "D3", tokenCount: 3000}, "sundering_jewel": {tokenType: "D3", tokenCount: 3000}, "pirate_essence": {tokenType: "D4", tokenCount: 1}, "marksman_brooch": {tokenType: "D4", tokenCount: 2000}, "corsair_crest": {tokenType: "D4", tokenCount: 2000}, "damaged_anchor": {tokenType: "D4", tokenCount: 2000}, "maelstrom_plating": {tokenType: "D4", tokenCount: 2000}, "kraken_leather": {tokenType: "D4", tokenCount: 2000}, "kraken_fang": {tokenType: "D4", tokenCount: 3000} }; function ob(node) { const shopContainerNode = node.querySelector(".ShopPanel_tabsComponentContainer__3z6R4 .TabsComponent_tabPanelsContainer__26mzo"); if (!shopContainerNode) return; const shopPanelNode = shopContainerNode.lastChild; if (shopPanelNode.querySelector(".RangedWayIdleDungeonTokenValue")) return; const localMarketAPIJson = JSON.parse(localStorage.getItem("MWITools_marketAPI_json")); const marketData = localMarketAPIJson.marketData; const maxItemPrice = { "D1": {"a": 0, "b": 0}, "D2": {"a": 0, "b": 0}, "D3": {"a": 0, "b": 0}, "D4": {"a": 0, "b": 0} } // match dungeon shop if (shopPanelNode.querySelectorAll(".ShopPanel_shopItem__10Noo").length !== 27) return; for (const itemNode of shopPanelNode.querySelectorAll(".ShopPanel_shopItem__10Noo")) { const itemName = itemNode.querySelector(".ShopPanel_itemContainer__1MlwA use").href.baseVal.split("#")[1]; if (!tokenMap[itemName]) continue; const itemHrid = `/items/${itemName}`; const askValue = marketData[itemHrid][0].a; const bidValue = marketData[itemHrid][0].b; const tokenCount = tokenMap[itemName].tokenCount; const askValueEach = askValue / tokenCount; const bidValueEach = bidValue / tokenCount; maxItemPrice[tokenMap[itemName].tokenType].a = Math.max(maxItemPrice[tokenMap[itemName].tokenType].a, askValueEach); maxItemPrice[tokenMap[itemName].tokenType].b = Math.max(maxItemPrice[tokenMap[itemName].tokenType].b, bidValueEach); const divNode = document.createElement("div"); const textAskNode = document.createElement("span"); textAskNode.classList.add("RangedWayIdleDungeonTokenValue"); textAskNode.textContent = formatItemCount(askValueEach); textAskNode.style.color = "#FF0000"; textAskNode.dataset.tokenType = tokenMap[itemName].tokenType; textAskNode.dataset.value = askValueEach.toString(); textAskNode.dataset.type = "ask"; divNode.appendChild(textAskNode); const splashNode = document.createElement("span"); splashNode.classList.add("RangedWayIdleDungeonTokenValue"); splashNode.textContent = " / "; splashNode.style.color = "#66CCFF"; splashNode.dataset.type = "splash"; divNode.appendChild(splashNode); const textBidNode = document.createElement("span"); textBidNode.classList.add("RangedWayIdleDungeonTokenValue"); textBidNode.textContent = formatItemCount(bidValueEach); textBidNode.style.color = "#FF0000"; textBidNode.dataset.tokenType = tokenMap[itemName].tokenType; textBidNode.dataset.value = bidValueEach.toString(); textBidNode.dataset.type = "bid"; divNode.appendChild(textBidNode); itemNode.insertBefore(divNode, itemNode.lastChild); } for (const textNode of shopPanelNode.querySelectorAll(".RangedWayIdleDungeonTokenValue")) { if (textNode.dataset.type === "ask") { if (textNode.dataset.value === maxItemPrice[textNode.dataset.tokenType].a.toString()) { textNode.style.color = "#00FF00"; } } else if (textNode.dataset.type === "bid") { if (textNode.dataset.value === maxItemPrice[textNode.dataset.tokenType].b.toString()) { textNode.style.color = "#00FF00"; } } } const tipNode = shopPanelNode.insertBefore(document.createElement("div"), shopPanelNode.firstChild); tipNode.textContent = I18N("dungeonTokenValueTipText"); tipNode.style.color = "#66CCFF"; tipNode.style.fontSize = "1.2rem"; } return {ob: ob}; } function trackLeaderBoardData() { function getCurrentKey() { const selectedTabs = document.querySelectorAll(".LeaderboardPanel_tabsComponentContainer__mIgnw .Mui-selected"); if (selectedTabs.length === 0) return; const selectedText = Array.from(selectedTabs).map((tab) => tab.textContent); return selectedText.join("-"); } function createNoteAndButton(noteNode) { const keyString = getCurrentKey(); // store data button const storeButton = document.createElement("button"); storeButton.textContent = I18N("trackLeaderBoardDataLeaderboardStoreButton"); storeButton.style.backgroundColor = "#66CCFF"; storeButton.addEventListener("click", function () { // get data const leaderBoardData = {}; const tableNode = document.querySelector(".LeaderboardPanel_leaderboardTable__3JLvu"); for (const row of tableNode.querySelectorAll("tbody tr")) { const characterNameNode = row.querySelector(".LeaderboardPanel_name__3hpvo").querySelector("span"); const guildNameNode = row.querySelector(".LeaderboardPanel_guildName__2RYcC"); const name = characterNameNode ? characterNameNode.textContent : guildNameNode.textContent; const valueNode1 = row.querySelector(".LeaderboardPanel_valueColumn1__2HFDb"); const valueNode2 = row.querySelector(".LeaderboardPanel_valueColumn2__1ejF2"); const value = Number((valueNode2 ? valueNode2.textContent : valueNode1.textContent).replaceAll(",", "")); leaderBoardData[name] = value || 0; } // store data const localData = JSON.parse(localStorage.getItem("ranged_way_idle_leaderboard_data") || "{}"); localData[keyString] = { data: leaderBoardData, timestamp: new Date().getTime() }; localStorage.setItem("ranged_way_idle_leaderboard_data", JSON.stringify(localData)); }); noteNode.appendChild(storeButton); // delete data button const deleteDataButton = document.createElement("button"); deleteDataButton.textContent = I18N("trackLeaderBoardDataLeaderboardDeleteButton"); deleteDataButton.style.backgroundColor = "#F44444"; deleteDataButton.addEventListener("click", function () { const localData = JSON.parse(localStorage.getItem("ranged_way_idle_leaderboard_data") || "{}"); delete localData[keyString]; localStorage.setItem("ranged_way_idle_leaderboard_data", JSON.stringify(localData)); }); noteNode.appendChild(deleteDataButton); // record time text node const localData = JSON.parse(localStorage.getItem("ranged_way_idle_leaderboard_data") || "{}"); const recordTimeTextNode = document.createElement("div"); if (localData[keyString]) { const recordTime = new Date(localData[keyString].timestamp); const timeDelta = (new Date().getTime() - localData[keyString].timestamp) / 3600000; recordTimeTextNode.textContent = I18N("trackLeaderBoardDataLeaderboardRecordTimeText", { recordTime: recordTime.toLocaleString(), timeDelta: timeDelta.toFixed(2) }); } else { recordTimeTextNode.textContent = I18N("trackLeaderBoardDataLeaderboardNoRecordTimeText"); } noteNode.appendChild(recordTimeTextNode); // hint text node const noteTextNode = document.createElement("div"); noteTextNode.textContent = I18N("trackLeaderBoardDataNoteText"); noteNode.appendChild(noteTextNode); } function showDifference(leaderBoardContentNode) { const keyString = getCurrentKey(); const allStoreData = JSON.parse(localStorage.getItem("ranged_way_idle_leaderboard_data") || "{}"); if (!allStoreData || !allStoreData[keyString]) { return; } // expand panel leaderBoardContentNode.style.maxWidth = '60rem'; // get current data const localData = allStoreData[keyString].data; const timeDelta = (new Date().getTime() - allStoreData[keyString].timestamp) / 1000; const hourDelta = timeDelta / 3600; const tableNode = leaderBoardContentNode.querySelector(".LeaderboardPanel_leaderboardTable__3JLvu"); // head const headNode = tableNode.querySelector("thead").firstChild; const diffNode = document.createElement("th"); diffNode.textContent = I18N("trackLeaderBoardDataDifference"); headNode.appendChild(diffNode); const speedNode = document.createElement("th"); speedNode.textContent = I18N("trackLeaderBoardDataSpeed"); headNode.appendChild(speedNode); const catchupTimeNode = document.createElement("th"); catchupTimeNode.textContent = I18N("trackLeaderBoardDataCatchupTime"); headNode.appendChild(catchupTimeNode); // body let previousRowValue = null; let previousRowSpeed = null; let maxSpeedValue = 0.0; let personalRow = null; let personalName = null; // calculate max speed for set color for (const row of tableNode.querySelectorAll("tbody tr")) { const characterNameNode = row.querySelector(".LeaderboardPanel_name__3hpvo").querySelector("span"); const guildNameNode = row.querySelector(".LeaderboardPanel_guildName__2RYcC"); const name = characterNameNode ? characterNameNode.textContent : guildNameNode.textContent; const valueNode1 = row.querySelector(".LeaderboardPanel_valueColumn1__2HFDb"); const valueNode2 = row.querySelector(".LeaderboardPanel_valueColumn2__1ejF2"); const value = Number((valueNode2 ? valueNode2.textContent : valueNode1.textContent).replaceAll(",", "")); if (localData[name]) { const diffValue = value - localData[name]; maxSpeedValue = Math.max(maxSpeedValue, diffValue / hourDelta); } if (row.classList.contains("LeaderboardPanel_personal__DZ7Nr")) { personalRow = row; personalName = name; } } for (const row of tableNode.querySelectorAll("tbody tr")) { const characterNameNode = row.querySelector(".LeaderboardPanel_name__3hpvo").querySelector("span"); const guildNameNode = row.querySelector(".LeaderboardPanel_guildName__2RYcC"); const name = characterNameNode ? characterNameNode.textContent : guildNameNode.textContent; const valueNode1 = row.querySelector(".LeaderboardPanel_valueColumn1__2HFDb"); const valueNode2 = row.querySelector(".LeaderboardPanel_valueColumn2__1ejF2"); const value = Number((valueNode2 ? valueNode2.textContent : valueNode1.textContent).replaceAll(",", "")); const diffValueNode = document.createElement("td"); diffValueNode.classList.add("RangedWayIdleLeaderBoardDiffValue"); const speedValueNode = document.createElement("td"); speedValueNode.classList.add("RangedWayIdleLeaderBoardSpeedValue"); const catchupTimeValueNode = document.createElement("td"); catchupTimeValueNode.classList.add("RangedWayIdleLeaderBoardCatchupTimeValue"); if (localData[name]) { const diffValue = value - localData[name]; diffValueNode.textContent = diffValue.toLocaleString(); const speedValue = diffValue / hourDelta; speedValueNode.textContent = formatItemCount(speedValue, 2) + "/h"; const k1 = Math.log(1 + (Math.E - 1) * speedValue / maxSpeedValue); diffValueNode.style.color = `rgb(${255 - k1 * 255}, ${k1 * 255}, 0)`; speedValueNode.style.color = `rgb(${255 - k1 * 255}, ${k1 * 255}, 0)`; if (previousRowValue === null || previousRowSpeed === null) { catchupTimeValueNode.textContent = "?????"; catchupTimeValueNode.style.color = "#66CCFF"; } else { const deltaSpeed = speedValue - previousRowSpeed; if (deltaSpeed === 0) { if (previousRowValue === value) { catchupTimeValueNode.textContent = I18N("trackLeaderBoardDataCatchupTimeNow"); catchupTimeValueNode.style.color = "#00FF00"; } else { catchupTimeValueNode.textContent = "∞"; catchupTimeValueNode.style.color = "#FF0000"; } } else { const catchupTimeValue = (previousRowValue - value) / deltaSpeed; if (catchupTimeValue > 0) { catchupTimeValueNode.textContent = formatItemCount(catchupTimeValue, 2) + "h"; const k2 = 10000 / (10000 + catchupTimeValue * catchupTimeValue); catchupTimeValueNode.style.color = `rgb(${255 - k2 * 255}, ${k2 * 255}, 0)`; } else if (catchupTimeValue === 0) { catchupTimeValueNode.textContent = "?????"; catchupTimeValueNode.style.color = "#66CCFF"; } else { catchupTimeValueNode.textContent = "∞"; catchupTimeValueNode.style.color = "#FF0000"; } } } previousRowSpeed = speedValue; } else { diffValueNode.textContent = I18N("trackLeaderBoardDataNewRecordText"); speedValueNode.textContent = I18N("trackLeaderBoardDataNewRecordText"); catchupTimeValueNode.textContent = I18N("trackLeaderBoardDataNewRecordText"); diffValueNode.style.color = "#66CCFF"; speedValueNode.style.color = "#66CCFF"; catchupTimeValueNode.style.color = "#66CCFF"; previousRowSpeed = null; } previousRowValue = value; // personal row if (row.classList.contains("LeaderboardPanel_personal__DZ7Nr")) { previousRowValue = null; previousRowSpeed = null; } row.appendChild(diffValueNode); row.appendChild(speedValueNode); row.appendChild(catchupTimeValueNode); if (personalRow && personalName === name) { personalRow.querySelector(".RangedWayIdleLeaderBoardCatchupTimeValue").textContent = catchupTimeValueNode.textContent; personalRow.querySelector(".RangedWayIdleLeaderBoardCatchupTimeValue").style.color = catchupTimeValueNode.style.color; } } } function ob(node) { const leaderBoardRootNode = node.querySelector(".LeaderboardPanel_leaderboardPanel__19U0W"); if (!leaderBoardRootNode) return; const noteNode = leaderBoardRootNode.querySelector(".LeaderboardPanel_note__z4OpJ"); if (!noteNode) return; // make note and buttons if (noteNode.classList.contains("RangedWayIdleLeaderBoardNote")) return; noteNode.classList.add("RangedWayIdleLeaderBoardNote"); createNoteAndButton(noteNode); // show difference const leaderBoardContentNode = leaderBoardRootNode.querySelector(".LeaderboardPanel_content__p_WNw"); showDifference(leaderBoardContentNode); } return {ob: ob}; } return { initCharacterData: initCharacterData, updateLocalStorageMarketPrice: updateLocalStorageMarketPrice, showTaskValue: showTaskValue, showDungeonTokenValue: showDungeonTokenValue, trackLeaderBoardData: trackLeaderBoardData } } gameUIClass() { function autoClickTaskSortButton() { function ob(node) { const buttonNode = node.querySelector('#TaskSort'); if (!buttonNode || buttonNode.classList.contains("RangedWayIdleAutoClicked")) return; buttonNode.click(); buttonNode.classList.add("RangedWayIdleAutoClicked"); } return {ob: ob}; } function showMarketAPIUpdateTime() { let lastTime = 0; function ob(node) { const buttonContainerNode = node.querySelector(".MarketplacePanel_buttonContainer__vJQud"); if (!buttonContainerNode) return; const nowTime = JSON.parse(localStorage.getItem('MWITools_marketAPI_json')).timestamp; const lastNode = buttonContainerNode.querySelector(".RangedWayIdleShowMarketAPIUpdateTime"); if (nowTime === lastTime) return; if (lastNode) lastNode.remove(); lastTime = nowTime; const divNode = document.createElement("div"); divNode.textContent = I18N("showMarketAPIUpdateTimeText") + " " + new Date(nowTime * 1000).toLocaleString(); divNode.style.color = "rgb(102,204,255)"; divNode.classList.add("RangedWayIdleShowMarketAPIUpdateTime"); buttonContainerNode.insertBefore(divNode, buttonContainerNode.lastChild); } return {ob: ob}; } function forceUpdateAPIButton() { function ob(node) { const listingContainerNode = node.querySelector(".MarketplacePanel_listingCount__3nVY_"); if (!listingContainerNode || !listingContainerNode.querySelector("button")) return; if (listingContainerNode.querySelector(".RangedWayIdleForceUpdateAPIButton")) return; const buttonNode = listingContainerNode.querySelector("button").cloneNode(true); buttonNode.classList.add("RangedWayIdleForceUpdateAPIButton"); buttonNode.textContent = I18N("forceUpdateAPIButtonText"); buttonNode.addEventListener("click", async function () { if (GM && GM.xmlHttpRequest) { GM.xmlHttpRequest({ method: 'GET', url: globalVariables.marketAPIUrl, onload: function (response) { const text = response.responseText; localStorage.setItem("MWITools_marketAPI_json", text); alert(I18N("forceUpdateAPIButtonTextSuccess") + new Date(JSON.parse(text).timestamp * 1000).toLocaleString()); }, onerror: function (err) { alert(I18N("forceUpdateAPIButtonTextError")); console.error(err); }, ontimeout: function () { alert(I18N("forceUpdateAPIButtonTextTimeout")); console.error('timeout'); } }); } else { const resp = await fetch(globalVariable.marketURL); const text = await resp.text(); localStorage.setItem("MWITools_marketAPI_json", text); alert(I18N("forceUpdateAPIButtonTextSuccess") + new Date(JSON.parse(text).timestamp * 1000).toLocaleString()); } }); listingContainerNode.appendChild(buttonNode); } return {ob: ob}; } function disableQueueUpgradeButton() { const disabledButtons = []; function ob(node) { const buttons = node.querySelectorAll("button"); for (const button of buttons) { if ((button.textContent === "Upgrade Queue Capacity" || button.textContent === "升级行动队列") && !button.disabled) { button.disabled = true; disabledButtons.push(button); } } for (let i = disabledButtons.length - 1; i >= 0; i--) { const button = disabledButtons[i]; if (!button.isConnected || (button.textContent !== "Upgrade Queue Capacity" && button.textContent !== "升级行动队列")) { button.disabled = false; disabledButtons.splice(i, 1); } } } return {ob: ob}; } function disableActionQueueBar() { function ob(node) { const actionQueueBarNode = node.querySelector(".QueuedActions_queuedActionsEditMenu__3OoQH"); if (!actionQueueBarNode) return; const buttonNode = node.querySelector(".QueuedActions_queuedActions__2xerL "); buttonNode.click(); } return {ob: ob}; } function hideSideBarButton() { let hideConfigs = null; let hasInit = false; function hideSideBar() { const sideBarRootNode = document.querySelector(".NavigationBar_navigationLinks__1XSSb"); if (!sideBarRootNode) return false; for (const sideBarNode of sideBarRootNode.querySelectorAll(".NavigationBar_navigationLink__3eAHA ")) { for (const useNode of sideBarNode.querySelectorAll("use")) { if (hideConfigs[useNode.href.baseVal] !== undefined) { sideBarNode.style.display = hideConfigs[useNode.href.baseVal] ? "none" : "block"; break; } } } return true; } function showHideSideBarConfigMenu(node) { // add this after config menu const configMenuRootNode = node.querySelector(".RangedWayIdleConfigMenuRoot"); if (!configMenuRootNode) return; if (configMenuRootNode.parentNode.querySelector(".RangedWayIdleHideSidebar")) return; const divRootNode = document.createElement("div"); divRootNode.appendChild(document.createElement("div")); divRootNode.firstChild.textContent = I18N("hideSidebarText"); divRootNode.firstChild.style.fontSize = "1.5rem"; divRootNode.classList.add("RangedWayIdleHideSidebar"); for (const key in hideConfigs) { const svgNode = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); const useNode = document.createElementNS('http://www.w3.org/2000/svg', 'use'); useNode.setAttributeNS('http://www.w3.org/1999/xlink', 'href', key); svgNode.appendChild(useNode); svgNode.style.width = "2.5rem"; svgNode.style.height = "2.5rem"; svgNode.style.opacity = hideConfigs[key] ? "0.25" : "1"; svgNode.onclick = function () { hideConfigs[key] = !hideConfigs[key]; svgNode.style.opacity = hideConfigs[key] ? "0.25" : "1"; localStorage.setItem("ranged_way_idle_hide_sidebar_config", JSON.stringify(hideConfigs)); hideSideBar(); }; divRootNode.appendChild(svgNode); } configMenuRootNode.insertAdjacentElement("afterend", divRootNode); } function ob(node) { // init hide svg if (hideConfigs === null) { const sideBarRootNode = node.querySelector(".NavigationBar_navigationLinks__1XSSb"); if (!sideBarRootNode) return; const localConfigs = JSON.parse(localStorage.getItem("ranged_way_idle_hide_sidebar_config") || "{}"); hideConfigs = {}; for (const sideBarNode of sideBarRootNode.querySelectorAll(".NavigationBar_navigationLink__3eAHA ")) { const useNode = sideBarNode.querySelector("use"); if (useNode.href.baseVal.includes("triangle_")) { // combat const link = "/static/media/misc_sprite.6fa5e97c.svg#combat"; hideConfigs[link] = localConfigs[link] || false; } else if (!useNode.href.baseVal.includes("settings")) { // cannot hide settings hideConfigs[useNode.href.baseVal] = localConfigs[useNode.href.baseVal] || false; } } } showHideSideBarConfigMenu(node); if (!hasInit) hasInit = hideSideBar(); } function ws(obj) { if (obj.type === "init_character_data") { hasInit = false; } } return {ob: ob, ws: ws}; } function hideTrainRubbishButton() { const rubbishNames = []; for (const a of ['cheese', 'verdant', 'azure', 'burble', 'crimson', 'rainbow']) { for (const b of ['brush', 'shears', 'hatchet', 'hammer', 'chisel', 'needle', 'spatula', 'pot', 'alembic', 'enhancer', 'sword', 'spear', 'mace', 'bulwark', 'buckler', 'boots', 'gauntlets', 'helmet', 'plate_legs', 'plate_body',]) { rubbishNames.push(`${a}_${b}`); } } for (const a of ['wooden', 'birch', 'cedar', 'purpleheart', 'ginkgo', 'redwood']) { for (const b of ['crossbow', 'bow', 'water_staff', 'nature_staff', 'fire_staff', 'shield']) { rubbishNames.push(`${a}_${b}`); } } for (const a of ['rough', 'reptile', 'gobo', 'beast']) { for (const b of ['boots', 'bracers', 'hood', 'chaps', 'tunic']) { rubbishNames.push(`${a}_${b}`); } } for (const a of ['cotton', 'linen', 'bamboo', 'silk']) { for (const b of ['boots', 'gloves', 'hat', 'robe_bottoms', 'robe_top']) { rubbishNames.push(`${a}_${b}`); } } function hide(inventoryNode) { for (const itemContainerNode of inventoryNode.querySelectorAll(".Item_itemContainer__x7kH1")) { const itemName = itemContainerNode.querySelector("use").href.baseVal.split("#")[1]; const isNotEnhanced = !itemContainerNode.querySelector(".Item_enhancementLevel__19g-e"); if (rubbishNames.includes(itemName) && isNotEnhanced) { itemContainerNode.style.display = "none"; } } } function show(inventoryNode) { if (configs.gameUIClass.alwaysHideTrainRubbish.value) return; for (const itemContainerNode of inventoryNode.querySelectorAll(".Item_itemContainer__x7kH1")) { itemContainerNode.style.display = "block"; } } function ob(node) { for (const inventoryNode of node.querySelectorAll(".Inventory_inventory__17CH2")) { if (configs.gameUIClass.alwaysHideTrainRubbish.value) hide(inventoryNode); if (inventoryNode.querySelector(".RangedWayIdleHideTrainRubbishButton")) continue; const hideButtonNode = document.createElement("button"); hideButtonNode.textContent = I18N("hideTrainRubbishButtonText"); hideButtonNode.style.backgroundColor = "#66CCFF"; hideButtonNode.classList.add("RangedWayIdleHideTrainRubbishButton"); hideButtonNode.addEventListener("click", () => hide(inventoryNode)); const showButtonNode = document.createElement("button"); showButtonNode.textContent = I18N("showTrainRubbishButtonText"); showButtonNode.style.backgroundColor = "#66CCFF"; showButtonNode.addEventListener("click", () => show(inventoryNode)); inventoryNode.insertBefore(showButtonNode, inventoryNode.firstChild); inventoryNode.insertBefore(hideButtonNode, inventoryNode.firstChild); } } return {ob: ob}; } function addWatermark() { let watermark = null; let lastText = ""; function draw(text) { if (!document || !document.body) return; watermark = document.createElement('div'); watermark.classList.add("RangedWayIdleWatermark"); Object.assign(watermark.style, { position: 'fixed', top: '0', left: '0', width: '100%', height: '100%', pointerEvents: 'none', zIndex: '9999', opacity: '0.3' }); const canvas = document.createElement('canvas'); canvas.width = 100; canvas.height = 100; const ctx = canvas.getContext('2d'); ctx.font = '16px Arial'; ctx.fillStyle = '#999999'; ctx.textAlign = 'center'; ctx.textBaseline = 'middle'; ctx.translate(canvas.width / 2, canvas.height / 2); ctx.rotate(-Math.PI / 6); ctx.fillText(text, 0, 0); lastText = text; watermark.style.backgroundImage = `url(${canvas.toDataURL()})`; watermark.style.backgroundRepeat = 'repeat'; document.body.appendChild(watermark); } function ob(node) { if (configs.gameUIClass.watermarkText.value !== lastText || !configs.gameUIClass.addWatermark.value) { if (watermark) { watermark.remove(); watermark = null; } lastText = ""; } if (!watermark) draw(configs.gameUIClass.watermarkText.value); } return {ob: ob}; } function quickCopyItemHrid() { let lastItemHrid = ""; function marketPanelCopy(node) { const buttonContainerNode = node.querySelector(".MarketplacePanel_marketNavButtonContainer__2QI9I"); if (!buttonContainerNode) return; if (!node.querySelector(".MarketplacePanel_currentItem__3ercC .Item_iconContainer__5z7j4 use")) return; const itemHrid = "/items/" + node.querySelector(".MarketplacePanel_currentItem__3ercC .Item_iconContainer__5z7j4 use").href.baseVal.split("#")[1]; if (itemHrid !== lastItemHrid) { const lastButton = buttonContainerNode.querySelector(".RangedWayIdleQuickCopyItemHridButton"); if (lastButton) lastButton.remove(); lastItemHrid = itemHrid; const buttonNode = buttonContainerNode.firstChild.cloneNode(true); buttonNode.classList.add("RangedWayIdleQuickCopyItemHridButton"); buttonNode.textContent = I18N("quickCopyItemHridButtonText"); buttonNode.addEventListener("click", () => { navigator.clipboard.writeText(itemHrid); }); buttonContainerNode.appendChild(buttonNode); } } function itemActionMenuCopy(node) { const itemActionMenuNode = node.querySelector(".Item_actionMenu__2yUcG"); if (!itemActionMenuNode) return; if (itemActionMenuNode.querySelector(".RangedWayIdleQuickCopyItemHridButton")) return; if (!node.querySelector(".Item_selected__1lIgj use")) return; const itemHrid = "/items/" + node.querySelector(".Item_selected__1lIgj use").href.baseVal.split("#")[1]; const buttonNode = itemActionMenuNode.querySelector(":not(.Button_small__3fqC7).Button_button__1Fe9z").cloneNode(true); buttonNode.classList.add("RangedWayIdleQuickCopyItemHridButton"); buttonNode.textContent = I18N("quickCopyItemHridButtonText"); buttonNode.addEventListener("click", () => { navigator.clipboard.writeText(itemHrid); }); itemActionMenuNode.insertBefore(buttonNode, itemActionMenuNode.lastChild); } function ob(node) { marketPanelCopy(node); itemActionMenuCopy(node); } return {ob: ob}; } return { autoClickTaskSortButton: autoClickTaskSortButton, showMarketAPIUpdateTime: showMarketAPIUpdateTime, forceUpdateAPIButton: forceUpdateAPIButton, disableQueueUpgradeButton: disableQueueUpgradeButton, disableActionQueueBar: disableActionQueueBar, hideSideBarButton: hideSideBarButton, hideTrainRubbishButton: hideTrainRubbishButton, addWatermark: addWatermark, quickCopyItemHrid: quickCopyItemHrid } } listingClass() { function hookListingInfo() { function handleListing(listing) { if (listing.status === "/market_listing_status/cancelled" || (listing.status === "/market_listing_status/filled" && listing.unclaimedItemCount === 0 && listing.unclaimedCoinCount === 0)) { delete globalVariables.allListings[listing.id]; return; } globalVariables.allListings[listing.id] = { id: listing.id, isSell: listing.isSell, itemHrid: listing.itemHrid, enhancementLevel: listing.enhancementLevel, orderQuantity: listing.orderQuantity, filledQuantity: listing.filledQuantity, price: listing.price, coinsAvailable: listing.coinsAvailable, unclaimedItemCount: listing.unclaimedItemCount, unclaimedCoinCount: listing.unclaimedCoinCount, createdTimestamp: listing.createdTimestamp, } } function saveListings() { const obj = JSON.parse(localStorage.getItem('ranged_way_idle_market_listings') || "{}"); const characterId = globalVariables.initCharacterData.character.id; if (!obj[characterId]) obj[characterId] = {}; for (const listingId in globalVariables.allListings) { if (obj[characterId][listingId]) continue; const listing = globalVariables.allListings[listingId]; obj[characterId][listingId] = { id: listing.id, isSell: listing.isSell, itemHrid: listing.itemHrid, enhancementLevel: listing.enhancementLevel, orderQuantity: listing.orderQuantity, filledQuantity: listing.filledQuantity, price: listing.price, createdTimestamp: listing.createdTimestamp, } } const nowTime = new Date().getTime(); for (const listingId in obj[characterId]) { const listing = obj[characterId][listingId]; if (nowTime - new Date(listing.createdTimestamp).getTime() > configs.listingClass.saveListingInfoToLocalStorageMaxDays * 24 * 60 * 60 * 1000) { delete obj[characterId][listingId]; } } localStorage.setItem('ranged_way_idle_market_listings', JSON.stringify(obj)); } function ws(obj) { if (obj.type === "init_character_data") { for (const listing of obj.myMarketListings) { handleListing(listing); } if (configs.listingClass.saveListingInfoToLocalStorage.value) { saveListings(); } } else if (obj.type === "market_listings_updated") { for (const listing of obj.endMarketListings) { handleListing(listing); } if (configs.listingClass.saveListingInfoToLocalStorage.value) { saveListings(); } } } return {ws: ws}; } function showTotalListingFunds() { function ws(obj) { if (obj.type === "market_listings_updated") { document.querySelectorAll(".RangedWayIdleTotalListingFunds").forEach(node => { node.remove(); }); } } function ob(node) { const marketplacePanelNode = node.querySelector(".MarketplacePanel_marketplacePanel__21b7o"); if (!marketplacePanelNode) return; if (marketplacePanelNode.querySelector(".RangedWayIdleTotalListingFunds")) return; let totalUnclaimedCoins = 0; let totalPrepaidCoins = 0; let totalSellResultCoins = 0; for (const listing of Object.values(globalVariables.allListings)) { totalUnclaimedCoins += listing.unclaimedCoinCount; totalPrepaidCoins += listing.coinsAvailable; if (listing.isSell) { const tax = listing.itemHrid === "/items/bag_of_10_cowbells" ? 0.82 : 0.98; totalSellResultCoins += (listing.orderQuantity - listing.filledQuantity) * Math.floor(listing.price * tax) } } const currentCoinNode = marketplacePanelNode.querySelector(".MarketplacePanel_coinStack__1l0UD"); const totalUnclaimedCoinsNode = currentCoinNode.cloneNode(true); const totalPrepaidCoinsNode = currentCoinNode.cloneNode(true); const totalSellResultCoinsNode = currentCoinNode.cloneNode(true); totalUnclaimedCoinsNode.querySelector(".Item_count__1HVvv").textContent = formatItemCount(totalUnclaimedCoins, configs.listingClass.showTotalListingFundsPrecise.value); totalPrepaidCoinsNode.querySelector(".Item_count__1HVvv").textContent = formatItemCount(totalPrepaidCoins, configs.listingClass.showTotalListingFundsPrecise.value); totalSellResultCoinsNode.querySelector(".Item_count__1HVvv").textContent = formatItemCount(totalSellResultCoins, configs.listingClass.showTotalListingFundsPrecise.value); totalUnclaimedCoinsNode.querySelector(".Item_name__2C42x").textContent = I18N("totalUnclaimedCoinsText"); totalPrepaidCoinsNode.querySelector(".Item_name__2C42x").textContent = I18N("totalPrepaidCoinsText"); totalSellResultCoinsNode.querySelector(".Item_name__2C42x").textContent = I18N("totalSellResultCoinsText"); totalUnclaimedCoinsNode.querySelector(".Item_name__2C42x").style.color = "#66CCFF"; totalPrepaidCoinsNode.querySelector(".Item_name__2C42x").style.color = "#66CCFF"; totalSellResultCoinsNode.querySelector(".Item_name__2C42x").style.color = "#66CCFF"; currentCoinNode.style.left = "0rem"; currentCoinNode.style.top = "0rem"; totalUnclaimedCoinsNode.style.left = "0rem"; totalUnclaimedCoinsNode.style.top = "1.5rem"; totalPrepaidCoinsNode.style.left = "8rem"; totalPrepaidCoinsNode.style.top = "0rem"; totalSellResultCoinsNode.style.left = "8rem"; totalSellResultCoinsNode.style.top = "1.5rem"; totalUnclaimedCoinsNode.classList.add("RangedWayIdleTotalListingFunds"); totalPrepaidCoinsNode.classList.add("RangedWayIdleTotalListingFunds"); totalSellResultCoinsNode.classList.add("RangedWayIdleTotalListingFunds"); marketplacePanelNode.insertBefore(totalUnclaimedCoinsNode, currentCoinNode.nextSibling); marketplacePanelNode.insertBefore(totalPrepaidCoinsNode, currentCoinNode.nextSibling); marketplacePanelNode.insertBefore(totalSellResultCoinsNode, currentCoinNode.nextSibling); } return {ws: ws, ob: ob} } function showListingInfo() { const allCreateTimeNodes = []; let intervalId = null; function magicSortInit() { let initialized = false; let buttonsAdded = false; let currentSort = 'time-asc'; let pinUndercutOnTop = false; let isSorting = false; let rowTimestamps = new WeakMap(); let priceCache = new Map(); let marketSearchKeyword = ''; let marketObserver = null; let notificationObserver = null; function getItemName(row) { try { const svg = row.querySelector('svg[aria-label]'); if (svg) return svg.getAttribute('aria-label') || ''; const itemElement = row.querySelector('.Item_item__2De2O'); if (itemElement) return itemElement.textContent ? itemElement.textContent.trim() : ''; return ''; } catch { return ''; } } function parsePrice(priceText) { try { if (!priceText) return 0; const text = priceText.replace(/[^\d.KM]/g, ''); const number = parseFloat(text.replace(/[KM]/g, '')); if (isNaN(number)) return 0; if (text.includes('M')) return number * 1000000; if (text.includes('K')) return number * 1000; return number; } catch { return 0; } } function isCollectableItem(row) { try { const collectButton = row.querySelector('.MarketplacePanel_claimsContainer__29bqh .Button_button__1Fe9z'); const claimsContent = row.querySelector('.MarketplacePanel_claims__WmSFp'); return !!(collectButton && claimsContent && claimsContent.children.length > 0 && collectButton.textContent && collectButton.textContent.includes('收集')); } catch { return false; } } function updatePriceCache(row) { try { const itemName = getItemName(row); const priceElement = row.querySelector('.MarketplacePanel_price__hIzrY span'); const price = priceElement ? priceElement.textContent.trim() : null; if (itemName && price) priceCache.set(itemName, price); } catch { } } function updateAllPriceCache() { try { const tbody = document.querySelector('.MarketplacePanel_myListingsTable__3P1aT tbody'); if (!tbody) return; const rows = Array.from(tbody.children).filter(r => r.tagName === 'TR' && r.querySelector('td')); rows.forEach(updatePriceCache); } catch { } } function initializeTimeTracking() { try { const tbody = document.querySelector('.MarketplacePanel_myListingsTable__3P1aT tbody'); if (!tbody) return; const rows = Array.from(tbody.children).filter(r => r.tagName === 'TR' && r.querySelector('td')); rowTimestamps = new WeakMap(); const base = Date.now(); rows.forEach((row, idx) => { const ts = base - (rows.length - idx) * 1000; rowTimestamps.set(row, ts); updatePriceCache(row); }); } catch { } } function startMarketObserver() { try { const tbody = document.querySelector('.MarketplacePanel_myListingsTable__3P1aT tbody'); if (!tbody) return; if (marketObserver) marketObserver.disconnect(); marketObserver = new MutationObserver(mutations => { let changed = false; for (const m of mutations) { if (m.type === 'childList') { m.addedNodes.forEach(n => { if (n.tagName === 'TR' && n.querySelector('td')) { if (!rowTimestamps.has(n)) rowTimestamps.set(n, Date.now()); updatePriceCache(n); changed = true; } }); if (m.removedNodes.length) changed = true; } } if (changed) updateAllPriceCache(); }); marketObserver.observe(tbody, {childList: true, subtree: false}); updateAllPriceCache(); } catch { } } function stopMarketObserver() { if (marketObserver) { marketObserver.disconnect(); marketObserver = null; } } function startNotificationObserver() { try { const container = document.querySelector('.GamePage_notifications__1xT_i'); if (!container) return; if (notificationObserver) notificationObserver.disconnect(); notificationObserver = new MutationObserver(() => { }); notificationObserver.observe(container, { childList: true, subtree: true, characterData: true }); } catch { } } function stopNotificationObserver() { if (notificationObserver) { notificationObserver.disconnect(); notificationObserver = null; } } function filterMarketItems() { try { const tbody = document.querySelector('.MarketplacePanel_myListingsTable__3P1aT tbody'); if (!tbody) return; const rows = Array.from(tbody.children).filter(r => r.tagName === 'TR' && r.querySelector('td')); rows.forEach(row => { const name = getItemName(row); const show = !marketSearchKeyword || name.toLowerCase().includes(marketSearchKeyword.toLowerCase()); if (show) row.classList.remove('magic-table-row-hidden'); else row.classList.add('magic-table-row-hidden'); }); const clearBtn = document.querySelector('.magic-clear-market-search'); if (clearBtn) clearBtn.style.display = marketSearchKeyword ? 'flex' : 'none'; } catch { } } function sortMarketItems() { if (isSorting) return false; return attemptSort(0); } function attemptSort(retryCount) { if (retryCount >= 3) return false; try { isSorting = true; const tbody = document.querySelector('.MarketplacePanel_myListingsTable__3P1aT tbody'); if (!tbody) { isSorting = false; return false; } const allRows = Array.from(tbody.children).filter(r => r.tagName === 'TR' && r.querySelector('td')); if (!allRows.length) { isSorting = false; return false; } const data = allRows.map((row, index) => { const itemName = getItemName(row); const priceElement = row.querySelector('.MarketplacePanel_price__hIzrY span'); const priceText = priceElement ? priceElement.textContent.trim() : '0'; const price = parsePrice(priceText); const isCollectable = isCollectableItem(row); let isUndercut = false; try { const spans = row.querySelectorAll('.RangedWayIdleShowListingInfo span'); if (spans && spans.length) { const color = getComputedStyle(spans[0]).color; isUndercut = /rgb\(255,\s*0,\s*0\)/i.test(color) || color.toLowerCase() === '#ff0000'; } } catch { } const timestamp = rowTimestamps.get(row) || Date.now(); if (itemName && priceText && priceText !== '0') priceCache.set(itemName, priceText); return { row, itemName, price, priceText, isCollectable, originalIndex: index, timestamp, isUndercut }; }); const sorted = [...data].sort((a, b) => { if (a.isCollectable && !b.isCollectable) return -1; if (!a.isCollectable && b.isCollectable) return 1; if (pinUndercutOnTop) { if (a.isUndercut && !b.isUndercut) return -1; if (!a.isUndercut && b.isUndercut) return 1; } if (currentSort === 'name-asc') return a.itemName.localeCompare(b.itemName, 'zh-CN'); if (currentSort === 'price-asc') return a.price - b.price; return a.timestamp - b.timestamp; }); while (tbody.firstChild) tbody.removeChild(tbody.firstChild); sorted.forEach(({row, isCollectable}) => { row.style.backgroundColor = isCollectable ? 'rgba(76, 175, 80, 0.15)' : ''; tbody.appendChild(row); }); isSorting = false; if (marketSearchKeyword) filterMarketItems(); return true; } catch (e) { isSorting = false; if (retryCount < 2) { setTimeout(() => attemptSort(retryCount + 1), 200); return false; } return false; } } function addMarketSortButton() { try { const buttonContainer = document.querySelector('.MarketplacePanel_buttonContainer__vJQud'); if (!buttonContainer) return; const existingButtons = buttonContainer.querySelectorAll('.magic-sort-button, .magic-refresh-button'); const existingContainers = buttonContainer.querySelectorAll('.magic-search-container, .magic-button-container'); existingButtons.forEach(btn => btn.remove()); existingContainers.forEach(c => c.remove()); if (!document.getElementById('magic-sort-style')) { const style = document.createElement('style'); style.id = 'magic-sort-style'; style.textContent = '.MarketplacePanel_buttonContainer__vJQud{display:flex!important;align-items:center!important;gap:12px!important;flex-wrap:wrap!important}.MarketplacePanel_listingCount__3nVY_{display:flex!important;flex-direction:column!important;align-items:flex-start!important;gap:4px!important;margin-right:auto!important}.magic-button-container{order:-1!important}.magic-search-container{order:0!important;margin-right:auto!important}.magic-table-row-hidden{display:none!important}'; document.head.appendChild(style); } const searchContainer = document.createElement('div'); searchContainer.className = 'magic-search-container'; searchContainer.style.cssText = 'display:flex;align-items:center;gap:8px;margin-right:12px;'; const marketSearchInput = document.createElement('input'); marketSearchInput.className = 'Input_input__2-t98 magic-market-search'; marketSearchInput.type = 'search'; marketSearchInput.placeholder = '🔍 物品搜索'; marketSearchInput.value = marketSearchKeyword; marketSearchInput.style.cssText = 'width:200px;padding:4px 8px;border:1px solid rgba(255,255,255,0.3);border-radius:4px;background:rgba(255,255,255,0.1);color:white;font-size:12px;outline:none;transition:all .2s ease;box-sizing:border-box;'; let marketSearchTimeout; marketSearchInput.addEventListener('input', e => { clearTimeout(marketSearchTimeout); marketSearchTimeout = setTimeout(() => { marketSearchKeyword = e.target.value.trim(); filterMarketItems(); }, 150); }); marketSearchInput.addEventListener('keydown', e => { if (e.key === 'Enter') { e.preventDefault(); marketSearchInput.blur(); } }); const clearMarketSearchBtn = document.createElement('button'); clearMarketSearchBtn.className = 'magic-clear-market-search'; clearMarketSearchBtn.title = '清空搜索'; clearMarketSearchBtn.textContent = '×'; clearMarketSearchBtn.style.cssText = 'background:rgba(255,99,71,.8);border:none;border-radius:4px;color:white;width:24px;height:24px;cursor:pointer;font-size:12px;transition:background .2s;display:' + (marketSearchKeyword ? 'flex' : 'none') + ';align-items:center;justify-content:center;'; clearMarketSearchBtn.addEventListener('click', () => { marketSearchInput.value = ''; marketSearchKeyword = ''; clearMarketSearchBtn.style.display = 'none'; filterMarketItems(); }); searchContainer.appendChild(marketSearchInput); searchContainer.appendChild(clearMarketSearchBtn); const magicButtonContainer = document.createElement('div'); magicButtonContainer.className = 'magic-button-container'; magicButtonContainer.style.cssText = 'display:flex;gap:6px;margin-right:12px;'; const refreshButton = document.createElement('button'); refreshButton.className = 'Button_button__1Fe9z Button_small__3fqC7 magic-refresh-button'; refreshButton.textContent = '刷新'; refreshButton.style.cssText = 'background-color:#4CAF50;color:white;transition:all .2s ease;border:none;font-size:12px;padding:4px 8px;min-width:48px;'; refreshButton.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); refreshMarketListings(refreshButton); }); const sortButton = document.createElement('button'); sortButton.className = 'Button_button__1Fe9z Button_small__3fqC7 magic-sort-button'; const getSortText = () => currentSort === 'name-asc' ? '排序 (名称↑)' : currentSort === 'price-asc' ? '排序 (价格↑)' : '排序 (时间↑)'; sortButton.textContent = getSortText(); sortButton.style.cssText = 'background-color:#2196F3;color:white;transition:all .2s ease;border:none;font-size:12px;padding:4px 8px;min-width:80px;'; sortButton.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); if (currentSort === 'time-asc') currentSort = 'name-asc'; else if (currentSort === 'name-asc') currentSort = 'price-asc'; else currentSort = 'time-asc'; sortButton.textContent = getSortText(); sortMarketItems(); }); const pinButton = document.createElement('button'); pinButton.className = 'Button_button__1Fe9z Button_small__3fqC7 magic-pin-red-button'; const getPinText = () => pinUndercutOnTop ? '取消置顶红价' : '置顶红价'; pinButton.textContent = getPinText(); pinButton.style.cssText = 'background-color:#E91E63;color:white;transition:all .2s ease;border:none;font-size:12px;padding:4px 8px;min-width:72px;'; pinButton.addEventListener('click', e => { e.preventDefault(); e.stopPropagation(); pinUndercutOnTop = !pinUndercutOnTop; pinButton.textContent = getPinText(); sortMarketItems(); }); magicButtonContainer.appendChild(refreshButton); magicButtonContainer.appendChild(pinButton); magicButtonContainer.appendChild(sortButton); buttonContainer.insertBefore(searchContainer, buttonContainer.firstChild); buttonContainer.insertBefore(magicButtonContainer, searchContainer.nextSibling); buttonsAdded = true; } catch { } } function refreshMarketListings(refreshButton) { try { const originalText = refreshButton.textContent; const originalColor = refreshButton.style.backgroundColor; refreshButton.textContent = '刷新中...'; refreshButton.style.backgroundColor = '#FF9800'; refreshButton.disabled = true; setTimeout(() => { try { initializeTimeTracking(); currentSort = 'time-asc'; marketSearchKeyword = ''; const marketSearchInput = document.querySelector('.magic-market-search'); if (marketSearchInput) marketSearchInput.value = ''; const clearBtn = document.querySelector('.magic-clear-market-search'); if (clearBtn) clearBtn.style.display = 'none'; sortMarketItems(); filterMarketItems(); refreshButton.textContent = originalText; refreshButton.style.backgroundColor = originalColor; refreshButton.disabled = false; } catch { refreshButton.textContent = originalText; refreshButton.style.backgroundColor = originalColor; refreshButton.disabled = false; } }, 500); } catch { } } function onMyListingsOpen() { const marketPanel = document.querySelector('.MarketplacePanel_myListings__25wPW'); const table = document.querySelector('.MarketplacePanel_myListingsTable__3P1aT tbody'); if (!marketPanel || !table) return; addMarketSortButton(); if (!initialized) { initializeTimeTracking(); startMarketObserver(); startNotificationObserver(); initialized = true; } sortMarketItems(); filterMarketItems(); } window.MagicSortIntegration = {onMyListingsOpen}; } function formatUTCTime(date) { return I18N("showListingInfoCreateTimeAt") + " " + date.toLocaleString('en-US', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', hour12: false }).replace(/\//g, '-').replace(',', ''); } function formatLifespan(date) { const diffMs = new Date() - date; const seconds = Math.floor(diffMs / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); return I18N("showListingInfoCreateTimeLifespan", { days: days, hours: hours % 24, minutes: minutes % 60, seconds: seconds % 60 }); } function handleTableHead(trNode) { const topOrderPriceNode = document.createElement("th"); topOrderPriceNode.classList.add("RangedWayIdleShowListingInfo"); const totalPriceNode = document.createElement("th"); totalPriceNode.classList.add("RangedWayIdleShowListingInfo"); topOrderPriceNode.textContent = I18N("showListingInfoTopOrderPriceText"); totalPriceNode.textContent = I18N("showListingInfoTotalPriceText"); trNode.insertBefore(topOrderPriceNode, trNode.children[4]); trNode.insertBefore(totalPriceNode, trNode.children[5]); } function addDataToRowsAduduVersion(bodyNode) { // Helper: parse number from display text (tolerant to commas and non-digits) function parseDisplayNumber(text) { if (!text) return NaN; const numStr = String(text).replace(/[^0-9]/g, ""); return numStr ? Number(numStr) : NaN; } // Helper: extract minimal info from a row to match listing function extractRowInfo(trNode) { // itemHrid via SVG
主要用途为与其他安装插件的玩家共享市场的数据信息。
您可以从其他人那里获取到共享的信息,也需要上传自己的信息来分享给他人。
仅打开本功能的初始开关时,您的所有数据都不会上传。各个功能需要在“功能设置”选项卡中手动开启。
为防止恶意上传信息或盗用身份,注册时您需要将您的角色名、角色ID上传,以提供身份验证。具体的注册流程请参见“账号设置”选项卡。
在市场界面,对于自己的挂单,点击挂单所有者处自己的名字,即可隐藏该条挂单,不让其他玩家看到。
本插件方便安装的玩家和谐商量挂单价格,请勿以此人身攻击其他玩家!
喜欢本插件,可以赞助一下作者❤ 在插件的设置页面里可以找到“赞助作者”的功能哦。
若需要删除所有自己上传的信息,退出使用,请联系AlphB。
`; function changeModalTabHint(state) { switch (state) { case"open": { divNode.querySelector("#IM-server-status").textContent = "已连接 " + (hasLogin ? "已登录 权限等级" + permissionLevel : "未登录"); divNode.querySelector("#IM-server-status").style.color = "#00FF00"; break; } case"closed": { divNode.querySelector("#IM-server-status").textContent = "连接失败!等待重连中。长时间连接失败则可能服务器正在维护"; divNode.querySelector("#IM-server-status").style.color = "#FF0000"; break; } case"reconnecting": { divNode.querySelector("#IM-server-status").textContent = "重连中"; divNode.querySelector("#IM-server-status").style.color = "#7F7F7F"; break; } case"closing": { divNode.querySelector("#IM-server-status").textContent = "正在关闭"; divNode.querySelector("#IM-server-status").style.color = "#7F0000"; break; } case"connecting": { divNode.querySelector("#IM-server-status").textContent = "连接中"; divNode.querySelector("#IM-server-status").style.color = "#FFFF00"; break; } case"error": { divNode.querySelector("#IM-server-status").textContent = "连接出错"; divNode.querySelector("#IM-server-status").style.color = "#FF0000"; break; } } } changeModalTabHint(webSukima.currentState); webSukima.onStateChange((newState) => changeModalTabHint(newState)); webSukima.receiveMessage(() => changeModalTabHint("open")); return divNode; }) .addTab('tab2', '账号设置', () => { const divNode = document.createElement("div"); divNode.innerHTML = `为防止恶意上传信息或盗用身份,注册时您需要将您的角色名、角色ID上传,以提供身份验证。目前仅允许标准角色注册,不允许铁牛,以保证一人一号,不浪费服务器资源。
验证时需要提交的个人信息仅包含您的角色名、角色ID。实际上这些数据也是公开的,任何人都可以获取到。因此服务器不会获取您的任何身份隐私信息。
在下面输入您的密码,即可直接登录。未验证的账号无法登录。
注册反馈:
----
登录反馈:
----
修改密码反馈:
----
`; divNode.querySelector("#temp-password").textContent = Math.floor(10000000 + Math.random() * 90000000).toString(); let token = ""; divNode.querySelector("#IM-password").value = imConfigs.password; divNode.querySelector("#IM-password").addEventListener("change", () => { imConfigs.password = divNode.querySelector("#IM-password").value; saveConfig(); }); divNode.querySelector("#IM-register-button").addEventListener("click", async () => { if (divNode.querySelector("#IM-password").value === "") return; webSukima.sendMessage({ "type": "register", "characterId": globalVariables.initCharacterData.character.id, "characterName": globalVariables.initCharacterData.character.name, "password": await sha256(divNode.querySelector("#IM-password").value), }); }); divNode.querySelector("#IM-login-button").addEventListener("click", async () => { webSukima.sendMessage({ "type": "login", "characterId": globalVariables.initCharacterData.character.id, "characterName": globalVariables.initCharacterData.character.name, "password": await sha256(divNode.querySelector("#IM-password").value), }); }); divNode.querySelector("#IM-change-password-button").addEventListener("click", async () => { webSukima.sendMessage({ "type": "change_password", "newPassword": await sha256(divNode.querySelector("#IM-password").value) }); }); webSukima.receiveMessage((obj) => { if (obj.type === "register_reply") { if (obj.success) { token = obj.token; divNode.querySelector("#register-reply").textContent = "验证码:" + token; } else { divNode.querySelector("#register-reply").textContent = "注册失败,原因:" + obj.reason; } } }); webSukima.receiveMessage((obj) => { if (obj.type === "login_reply") { if (obj.success) { divNode.querySelector("#login-reply").textContent = "登录成功。权限等级:" + permissionLevel; } else { divNode.querySelector("#login-reply").textContent = "登录失败,原因:" + obj.reason; } } }); webSukima.receiveMessage((obj) => { if (obj.type === "change_password_reply") { if (obj.success) { divNode.querySelector("#change-password-reply").textContent = "修改成功。下次登录时请输入新密码。"; } else { divNode.querySelector("#change-password-reply").textContent = "修改失败,原因:" + obj.reason; } } }); divNode.querySelector("#IM-copy-token-button").addEventListener("click", () => { navigator.clipboard.writeText("/w ABot " + token); }); return divNode; }) .addTab('tab3', '功能设置', () => { const divNode = document.createElement("div"); divNode.innerHTML = `开启各个功能需要你上传相应的信息。例如想要知道某挂单的所有者,则你必须上传你有哪些挂单。
各个功能下列出了对应的需要上传信息。