mooket

显示市场历史价格 银河奶牛 show market history data for milkywayidle

目前为 2025-03-20 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name mooket
  3. // @namespace http://tampermonkey.net/
  4. // @version 2025-03-18
  5. // @description 显示市场历史价格 银河奶牛 show market history data for milkywayidle
  6. // @author IOMisaka
  7. // @match https://www.milkywayidle.com/*
  8. // @match https://test.milkywayidle.com/*
  9. // @connect mooket.qi-e.top
  10. // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==
  11. // @grant none
  12. // @require https://cdn.jsdelivr.net/npm/chart.js@4.4.8/dist/chart.umd.min.js
  13. // @license MIT
  14. // ==/UserScript==
  15.  
  16. (function () {
  17. 'use strict';
  18. function hookWS() {
  19. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  20. const oriGet = dataProperty.get;
  21. dataProperty.get = hookedGet;
  22. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  23.  
  24. function hookedGet() {
  25. const socket = this.currentTarget;
  26. if (!(socket instanceof WebSocket)) {
  27. return oriGet.call(this);
  28. }
  29. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  30. return oriGet.call(this);
  31. }
  32. const message = oriGet.call(this);
  33. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  34. return handleMessage(message);
  35. }
  36. }
  37. function handleMessage(message) {
  38. let obj = JSON.parse(message);
  39. if (obj && obj.type === "market_item_order_books_updated") {
  40. requestMarket(obj.marketItemOrderBooks.itemHrid);
  41. }
  42. return message;
  43. }
  44.  
  45. hookWS();
  46.  
  47. let cur_day = 1;
  48. let cur_name = null;
  49.  
  50. // 创建容器元素并设置样式和位置
  51. const container = document.createElement('div');
  52. container.style.border = "1px solid #ccc"; //边框样式
  53. container.style.backgroundColor = "#fff";
  54. container.style.position = "fixed";
  55. container.style.zIndex = 10000;
  56. container.style.top = "50px"; //距离顶部位置
  57. container.style.left = "50px"; //距离左侧位置
  58. container.style.width = "600px"; //容器宽度
  59. container.style.height = "330px"; //容器高度
  60. container.style.cursor = "move";
  61. container.addEventListener("mousedown", function (e) {
  62. let disX = e.clientX - container.offsetLeft;
  63. let disY = e.clientY - container.offsetTop;
  64. document.onmousemove = function (e) {
  65. let x = e.clientX - disX;
  66. let y = e.clientY - disY;
  67. container.style.left = x + 'px';
  68. container.style.top = y + 'px';
  69. };
  70. document.onmouseup = function () {
  71. document.onmousemove = document.onmouseup = null;
  72. };
  73. });
  74. document.body.appendChild(container);
  75.  
  76. const ctx = document.createElement('canvas');
  77. ctx.id = "myChart";
  78. container.appendChild(ctx);
  79.  
  80. // 创建按钮组并设置样式和位置
  81. let wrapper = document.createElement('div');
  82. wrapper.style.display = 'inline-block';
  83. container.appendChild(wrapper);
  84.  
  85. const days = [1, 3, 7, 30, 180]
  86. const dayTitle = ['1天', '3天', '7天', '30天', '半年']
  87. for (let i = 0; i < 5; i++) {
  88. let btn = document.createElement('input');
  89. btn.id = 'chartType' + i;
  90. btn.type = 'radio';
  91. btn.name = 'chartType';
  92. btn.value = days[i];
  93. btn.style.cursor = 'pointer';
  94. btn.style.verticalAlign = "middle";
  95. btn.checked = i == 0;
  96. btn.onclick = function () { cur_day = this.value; requestMarket(cur_name, cur_day); };
  97.  
  98. let label = document.createElement('label');
  99. label.innerText = dayTitle[i];
  100. label.style.display = 'inline-block';
  101. label.style.verticalAlign = 'middle';
  102. label.style.textAlign = 'center';
  103. label.htmlFor = btn.id;
  104. label.style.margin = '1px';
  105. wrapper.appendChild(btn);
  106. wrapper.appendChild(label);
  107. }
  108. //添加一个btn隐藏canvas和wrapper
  109. let btn_close = document.createElement('input');
  110. btn_close.type = 'button';
  111. btn_close.value = '📈隐藏';
  112. btn_close.style.textAlign = 'center';
  113. btn_close.style.display = 'inline';
  114. btn_close.style.margin = 0;
  115. btn_close.style.top = '1px';
  116. btn_close.style.left = '1px';
  117. btn_close.style.cursor = 'pointer';
  118. btn_close.style.position = 'absolute';
  119. btn_close.onclick = function () {
  120. if (wrapper.style.display === 'none') {
  121. wrapper.style.display = ctx.style.display = 'block';
  122. btn_close.value = '📈隐藏';
  123. container.style.width = "600px";
  124. container.style.height = "330px";
  125. } else {
  126. wrapper.style.display = ctx.style.display = 'none';
  127. container.style.width = "63px";
  128. container.style.height = "25px";
  129. btn_close.value = '📈显示';
  130. }
  131. };
  132.  
  133. container.appendChild(btn_close);
  134.  
  135.  
  136. let chart = new Chart(ctx, {
  137. type: 'line',
  138. data: {
  139. labels: ['一月', '二月', '三月'],
  140. datasets: [{
  141. label: '销量',
  142. data: [12, 19, 3],
  143. backgroundColor: 'rgba(255,99,132,0.2)',
  144. borderColor: 'rgba(255,99,132,1)',
  145. borderWidth: 1
  146. }]
  147. },
  148. options: {
  149. scales: {
  150. y: {
  151. beginAtZero: true
  152. }
  153. }
  154. }
  155. });
  156.  
  157. function requestMarket(name, day = 1) {
  158. cur_name = name;
  159. cur_day = day;
  160.  
  161. let time = day * 3600 * 24;
  162. fetch("https://mooket.qi-e.top/market", {
  163. method: "POST",
  164. headers: {
  165. "Content-Type": "application/json",
  166. },
  167. body: JSON.stringify({
  168. name: name,
  169. time: time
  170. })
  171. }).then(res => {
  172. res.json().then(data => updateChart(data, day));
  173. })
  174. }
  175. function formatTime(timestamp, day) {
  176. let date = new Date(timestamp * 1000);
  177. if (day <= 1) {
  178. return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  179. }
  180. else if (day <= 3) {
  181. return date.toLocaleDateString([], { day: 'numeric', hour: '2-digit' });
  182. } else if (day <= 7) {
  183. return date.toLocaleDateString([], { day: 'numeric', hour: '2-digit' });
  184. } else if (day <= 30) {
  185. return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
  186. } else
  187. return date.toLocaleDateString([], { year: 'numeric', month: 'short' });
  188. }
  189.  
  190. //data={'bid':[{time:1,price:1}],'ask':[{time:1,price:1}]}
  191. function updateChart(data, day) {
  192. //timestamp转日期时间
  193. //根据day输出不同的时间表示,<3天显示时分,<=7天显示日时,<=30天显示月日,>30天显示年月
  194.  
  195. let labels = data.bid.map(x => formatTime(x.time, day));
  196.  
  197. chart.data.labels = labels;
  198.  
  199. chart.data.datasets = [
  200. {
  201. label: '买入',
  202. data: data.bid.map(x => x.price),
  203. backgroundColor: '#ff3300',
  204. borderColor: '#990000',
  205. borderWidth: 1
  206. },
  207. {
  208. label: '卖出',
  209. data: data.ask.map(x => x.price),
  210. backgroundColor: '#00cc00',
  211. borderColor: '#006600',
  212. borderWidth: 1
  213. },
  214. {
  215. label: '均价',
  216. data: data.bid.map(({ price: bidPrice }, index) => {
  217. const { price: askPrice } = data.ask[index];
  218. return (bidPrice + askPrice) / 2;
  219. }),
  220. backgroundColor: '#ff9900',
  221. borderColor: '#996600',
  222. borderWidth: 1
  223. }
  224. ];
  225. chart.update()
  226. }
  227. requestMarket('Apple', 1);
  228.  
  229. })();