- // ==UserScript==
- // @name mooket
- // @namespace http://tampermonkey.net/
- // @version 20250413.36065
- // @description 银河奶牛历史价格 show history market data for milkywayidle
- // @author IOMisaka
- // @match https://www.milkywayidle.com/*
- // @match https://test.milkywayidle.com/*
- // @connect mooket.qi-e.top
- // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
- // @grant none
- // @require https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js
- // @require https://update.greasyfork.org/scripts/532609/1570245/MWICore.js
- // @run-at document-start
- // @license MIT
- // ==/UserScript==
-
-
- (function () {
- 'use strict';
- //等待window.mwi.game加载完成
- new Promise(resolve => {
- const interval = setInterval(() => {
- if (window?.mwi?.inited && document.body) {
- clearInterval(interval);
- resolve();
- }
- }, 500);
- }).then(() => {
- window.mwi.hookCallback(window.mwi.game, "handleMessageMarketItemOrderBooksUpdated", (_, obj) => {
- requestItemPrice(obj.marketItemOrderBooks.itemHrid, cur_day);
- });
- window.mwi.hookCallback(window.mwi.game, "handleMessageMarketListingsUpdated", (_, obj) => {
- obj.endMarketListings.forEach(order => {
- if (order.filledQuantity == 0) return;//没有成交的订单不记录
- let key = order.itemHrid + "_" + order.enhancementLevel;
-
- let tradeItem = trade_history[key] || {}
- if (order.isSell) {
- tradeItem.sell = order.price;
- } else {
- tradeItem.buy = order.price;
- }
- trade_history[key] = tradeItem;
- });
- localStorage.setItem("mooket_trade_history", JSON.stringify(trade_history));//保存挂单数据
- });
-
-
- let trade_history = JSON.parse(localStorage.getItem("mooket_trade_history") || "{}");
-
- let cur_day = 1;
- let curHridName = null;
- let curShowItemName = null;
- let w = "500";
- let h = "280";
-
- let configStr = localStorage.getItem("mooket_config");
- let config = configStr ? JSON.parse(configStr) : { "dayIndex": 0, "visible": true, "filter": { "bid": true, "ask": true, "mean": true } };
- cur_day = config.day;//读取设置
-
- window.onresize = function () {
- checkSize();
- };
- function checkSize() {
- if (window.innerWidth < window.innerHeight) {
- w = "250";
- h = "400";
- } else {
- w = "400";
- h = "250";
- }
- }
- checkSize();
-
- // 创建容器元素并设置样式和位置
- const container = document.createElement('div');
- container.style.border = "1px solid #ccc"; //边框样式
- container.style.backgroundColor = "#fff";
- container.style.position = "fixed";
- container.style.zIndex = 10000;
- container.style.top = `${Math.max(0, Math.min(config.y || 0, window.innerHeight - 50))}px`; //距离顶部位置
- container.style.left = `${Math.max(0, Math.min(config.x || 0, window.innerWidth - 50))}px`; //距离左侧位置
- container.style.width = `${Math.max(0, Math.min(config.w || w, window.innerWidth - 50))}px`; //容器宽度
- container.style.height = `${Math.max(0, Math.min(config.h || h, window.innerHeight - 50))}px`; //容器高度
- container.style.resize = "both";
- container.style.overflow = "auto";
- container.style.display = "flex";
- container.style.flexDirection = "column";
- container.style.flex = "1";
- container.style.minHeight = "33px";
- container.style.minWidth = "68px";
- container.style.cursor = "move";
- container.style.userSelect = "none";
-
- let mouseDragging = false;
- let touchDragging = false;
- let offsetX, offsetY;
-
- let resizeEndTimer = null;
- container.addEventListener("resize", () => {
- if (resizeEndTimer) clearTimeout(resizeEndTimer);
- resizeEndTimer = setTimeout(save_config, 1000);
- });
- container.addEventListener("mousedown", function (e) {
- if (mouseDragging || touchDragging) return;
- const rect = container.getBoundingClientRect();
- if (container.style.resize === "both" && (e.clientX > rect.right - 10 || e.clientY > rect.bottom - 10)) return;
- mouseDragging = true;
- offsetX = e.clientX - container.offsetLeft;
- offsetY = e.clientY - container.offsetTop;
- });
-
- document.addEventListener("mousemove", function (e) {
- if (mouseDragging) {
- var newX = e.clientX - offsetX;
- var newY = e.clientY - offsetY;
- container.style.left = newX + "px";
- container.style.top = newY + "px";
- }
- });
-
- document.addEventListener("mouseup", function () {
- mouseDragging = false;
- save_config();
- });
-
- container.addEventListener("touchstart", function (e) {
- if (mouseDragging || touchDragging) return;
- const rect = container.getBoundingClientRect();
- let touch = e.touches[0];
- if (container.style.resize === "both" && (e.clientX > rect.right - 10 || e.clientY > rect.bottom - 10)) return;
- touchDragging = true;
- offsetX = touch.clientX - container.offsetLeft;
- offsetY = touch.clientY - container.offsetTop;
- });
-
- document.addEventListener("touchmove", function (e) {
- if (touchDragging) {
- let touch = e.touches[0];
- var newX = touch.clientX - offsetX;
- var newY = touch.clientY - offsetY;
- container.style.left = newX + "px";
- container.style.top = newY + "px";
- }
- });
-
- document.addEventListener("touchend", function () {
- touchDragging = false;
- save_config();
- });
- document.body.appendChild(container);
-
- const ctx = document.createElement('canvas');
- ctx.id = "myChart";
- container.appendChild(ctx);
-
-
-
- // 创建下拉菜单并设置样式和位置
- let wrapper = document.createElement('div');
- wrapper.style.position = 'absolute';
- wrapper.style.top = '5px';
- wrapper.style.right = '16px';
- wrapper.style.fontSize = '14px';
-
- //wrapper.style.backgroundColor = '#fff';
- wrapper.style.flexShrink = 0;
- container.appendChild(wrapper);
-
- const days = [1, 3, 7, 14, 30, 180, 360];
- const dayTitle = ['1天', '3天', '1周', '2周', '1月', '半年', '一年'];
- cur_day = days[config.dayIndex];
-
- let select = document.createElement('select');
- select.style.cursor = 'pointer';
- select.style.verticalAlign = 'middle';
- select.onchange = function () {
- cur_day = this.value;
- config.dayIndex = days.indexOf(parseInt(this.value));
- if (curHridName) requestItemPrice(curHridName, cur_day);
- save_config();
- };
-
- for (let i = 0; i < days.length; i++) {
- let option = document.createElement('option');
- option.value = days[i];
- option.text = dayTitle[i];
- if (i === config.dayIndex) option.selected = true;
- select.appendChild(option);
- }
-
- wrapper.appendChild(select);
-
- // 创建一个容器元素并设置样式和位置
- const leftContainer = document.createElement('div');
- leftContainer.style.padding = '2px'
- leftContainer.style.display = 'flex';
- leftContainer.style.flexDirection = 'row';
- leftContainer.style.alignItems = 'center'
- container.appendChild(leftContainer);
-
- //添加一个btn隐藏canvas和wrapper
- let btn_close = document.createElement('input');
- btn_close.type = 'button';
- btn_close.value = '📈隐藏';
- btn_close.style.margin = 0;
- btn_close.style.cursor = 'pointer';
-
- leftContainer.appendChild(btn_close);
-
-
- //一个固定的文本显示买入卖出历史价格
- let price_info = document.createElement('div');
-
- price_info.style.fontSize = '14px';
- price_info.title = "我的最近买/卖价格"
- price_info.style.width = "max-content";
- price_info.style.whiteSpace = "nowrap";
- price_info.style.lineHeight = '25px';
- price_info.style.display = 'none';
- price_info.style.marginLeft = '5px';
-
- let buy_price = document.createElement('span');
- let sell_price = document.createElement('span');
- price_info.appendChild(buy_price);
- price_info.appendChild(sell_price);
- buy_price.style.color = 'red';
- sell_price.style.color = 'green';
-
- leftContainer.appendChild(price_info);
-
- let lastWidth;
- let lastHeight;
- btn_close.onclick = toggle;
- function toggle() {
- if (wrapper.style.display === 'none') {
- wrapper.style.display = ctx.style.display = 'block';
- container.style.resize = "both";
- btn_close.value = '📈隐藏';
- leftContainer.style.position = 'absolute'
- leftContainer.style.top = '1px';
- leftContainer.style.left = '1px';
- container.style.width = lastWidth;
- container.style.height = lastHeight;
- config.visible = true;
- save_config();
- } else {
- lastWidth = container.style.width;
- lastHeight = container.style.height;
- wrapper.style.display = ctx.style.display = 'none';
- container.style.resize = "none";
- container.style.width = "auto";
- container.style.height = "auto";
-
-
- btn_close.value = '📈显示';
- leftContainer.style.position = 'relative'
- leftContainer.style.top = 0;
- leftContainer.style.left = 0;
-
- config.visible = false;
- save_config();
- }
- };
-
- let chart = new Chart(ctx, {
- type: 'line',
- data: {
- labels: [],
- datasets: [{
- label: '市场',
- data: [],
- backgroundColor: 'rgba(255,99,132,0.2)',
- borderColor: 'rgba(255,99,132,1)',
- borderWidth: 1
- }]
- },
- options: {
- onClick: save_config,
- responsive: true,
- maintainAspectRatio: false,
- pointRadius: 0,
- pointHitRadius: 20,
- scales: {
- y: {
- beginAtZero: false,
- ticks: {
- // 自定义刻度标签格式化
- callback: showNumber
- }
- }
- },
- plugins: {
- title: {
- display: true,
- text: "",
- }
- }
- }
- });
-
- function requestItemPrice(itemHridName, day = 1) {
- curHridName = itemHridName;
- cur_day = day;
-
- let itemNameEN = window.mwi.game.state.itemDetailDict[itemHridName].name;
-
-
- curShowItemName = localStorage.getItem("i18nextLng")?.startsWith("zh") ?
- window.mwi.lang.zh.translation.itemNames[itemHridName] : window.mwi.lang.en.translation.itemNames[itemHridName];
-
-
- let time = day * 3600 * 24;
- fetch("https://mooket.qi-e.top/market", {
- method: "POST",
- headers: {
- "Content-Type": "application/json",
- },
- body: JSON.stringify({
- name: itemNameEN,
- time: time
- })
- }).then(res => {
- res.json().then(data => updateChart(data, cur_day));
- })
- }
-
- function formatTime(timestamp, range) {
- const date = new Date(timestamp * 1000);
- const pad = n => n.toString().padStart(2, '0');
-
- // 获取各时间组件
- const hours = pad(date.getHours());
- const minutes = pad(date.getMinutes());
- const day = pad(date.getDate());
- const month = pad(date.getMonth() + 1);
- const shortYear = date.getFullYear().toString().slice(-2);
-
- // 根据时间范围选择格式
- switch (parseInt(range)) {
- case 1: // 1天:只显示时间
- return `${hours}:${minutes}`;
-
- case 3: // 3天:日+时段
- return `${hours}:${minutes}`;
-
- case 7: // 7天:月/日 + 时段
- return `${day}.${hours}`;
- case 14: // 14天:月/日 + 时段
- return `${day}.${hours}`;
- case 30: // 30天:月/日
- return `${month}/${day}`;
-
- default: // 180天:年/月
- return `${shortYear}/${month}`;
- }
- }
-
- function showNumber(num) {
- if (isNaN(num)) return num;
- if (num === 0) return "0"; // 单独处理0的情况
-
- const absNum = Math.abs(num);
-
- //num保留一位小数
- if (num < 1) return num.toFixed(2);
-
- return absNum >= 1e10 ? `${(num / 1e9).toFixed(1)}B` :
- absNum >= 1e7 ? `${(num / 1e6).toFixed(1)}M` :
- absNum >= 1e4 ? `${Math.floor(num / 1e3)}K` :
- `${Math.floor(num)}`;
- }
- //data={'bid':[{time:1,price:1}],'ask':[{time:1,price:1}]}
- function updateChart(data, day) {
- //过滤异常元素
- for (let i = data.bid.length - 1; i >= 0; i--) {
- if (data.bid[i].price < 0 || data.ask[i].price < 0) {
- data.bid.splice(i, 1);
- data.ask.splice(i, 1);
- }
- }
- //timestamp转日期时间
- //根据day输出不同的时间表示,<3天显示时分,<=7天显示日时,<=30天显示月日,>30天显示年月
-
- //显示历史价格
- let enhancementLevel = document.querySelector(".MarketplacePanel_infoContainer__2mCnh .Item_enhancementLevel__19g-e")?.textContent.replace("+", "") || "0";
- let tradeName = curHridName + "_" + parseInt(enhancementLevel);
- if (trade_history[tradeName]) {
- let buy = trade_history[tradeName].buy || "无";
- let sell = trade_history[tradeName].sell || "无";
- price_info.style.display = "inline-block";
- let levelStr = enhancementLevel > 0 ? "(+" + enhancementLevel + ")" : "";
- price_info.innerHTML = `<span style="color:red">${showNumber(buy)}</span>/<span style="color:green">${showNumber(sell)}</span>${levelStr}`;
- container.style.minWidth = price_info.clientWidth + 70 + "px";
-
- } else {
- price_info.style.display = "none";
- container.style.minWidth = "68px";
- }
-
- let labels = data.bid.map(x => formatTime(x.time, day));
-
- chart.data.labels = labels;
-
- let sma = [];
- let sma_size = 6;
- let sma_window = [];
- for (let i = 0; i < data.bid.length; i++) {
- sma_window.push((data.bid[i].price + data.ask[i].price) / 2);
- if (sma_window.length > sma_size) sma_window.shift();
- sma.push(sma_window.reduce((a, b) => a + b, 0) / sma_window.length);
- }
- chart.options.plugins.title.text = curShowItemName
- chart.data.datasets = [
- {
- label: '买入',
- data: data.bid.map(x => x.price),
- borderColor: '#ff3300',
- backgroundColor: '#ff3300',
- borderWidth: 1.5
- },
- {
- label: '卖出',
- data: data.ask.map(x => x.price),
- borderColor: '#00cc00',
- backgroundColor: '#00cc00',
- borderWidth: 1.5
- },
- {
- label: '均线',
- data: sma,
- borderColor: '#ff9900',
- borderWidth: 3,
- tension: 0.5,
- fill: true
- }
- ];
- chart.setDatasetVisibility(0, config.filter.ask);
- chart.setDatasetVisibility(1, config.filter.bid);
- chart.setDatasetVisibility(2, config.filter.mean);
-
- chart.update()
- }
- function save_config() {
-
- if (chart && chart.data && chart.data.datasets && chart.data.datasets.length == 3) {
- config.filter.ask = chart.getDatasetMeta(0).visible;
- config.filter.bid = chart.getDatasetMeta(1).visible;
- config.filter.mean = chart.getDatasetMeta(2).visible;
- }
- config.x = Math.max(0, Math.min(container.getBoundingClientRect().x, window.innerWidth - 50));
- config.y = Math.max(0, Math.min(container.getBoundingClientRect().y, window.innerHeight - 50));
- if (container.style.width != "auto") {
- config.w = container.clientWidth;
- config.h = container.clientHeight;
- }
-
- localStorage.setItem("mooket_config", JSON.stringify(config));
- }
- setInterval(() => {
- if (document.querySelector(".MarketplacePanel_marketplacePanel__21b7o")?.checkVisibility()) {
- container.style.display = "block"
- } else {
- container.style.display = "none"
- }
- }, 1000);
- toggle();
- });
- })();