mooket

银河奶牛历史价格 show history market data for milkywayidle

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

  1. // ==UserScript==
  2. // @name mooket
  3. // @namespace http://tampermonkey.net/
  4. // @version 2025-03-26
  5. // @description 银河奶牛历史价格 show history market 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.  
  17. (function () {
  18. 'use strict';
  19. let initData_itemDetailMap = null;
  20. if (localStorage.getItem("initClientData")) {
  21. const obj = JSON.parse(localStorage.getItem("initClientData"));
  22. initData_itemDetailMap = obj.itemDetailMap;
  23. }
  24. function hookWS() {
  25. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  26. const oriGet = dataProperty.get;
  27. dataProperty.get = hookedGet;
  28. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  29.  
  30. function hookedGet() {
  31. const socket = this.currentTarget;
  32. if (!(socket instanceof WebSocket)) {
  33. return oriGet.call(this);
  34. }
  35. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  36. return oriGet.call(this);
  37. }
  38. const message = oriGet.call(this);
  39. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  40. return handleMessage(message);
  41. }
  42. }
  43. function handleMessage(message) {
  44. let obj = JSON.parse(message);
  45. if (obj && obj.type === "market_item_order_books_updated") {
  46. requestMarket(obj.marketItemOrderBooks.itemHrid, cur_day);
  47. }
  48. return message;
  49. }
  50.  
  51. hookWS();
  52.  
  53. let cur_day = 1;
  54. let cur_name = null;
  55. let w = "500px";
  56. let h = "280px";
  57. let configStr = localStorage.getItem("mooket_config");
  58. let config = configStr ? JSON.parse(configStr) : { "dayIndex": 0, "visible": true, "filter": { "bid": true, "ask": true, "mean": true } };
  59. cur_day = config.day;//读取设置
  60.  
  61. window.onresize = function () {
  62. checkSize();
  63. };
  64. function checkSize() {
  65. if (window.innerWidth < window.innerHeight) {
  66. w = "280px";
  67. h = "500px";
  68. } else {
  69. w = "500px";
  70. h = "280px";
  71. }
  72. }
  73. checkSize();
  74. // 创建容器元素并设置样式和位置
  75. const container = document.createElement('div');
  76. container.style.border = "1px solid #ccc"; //边框样式
  77. container.style.backgroundColor = "#fff";
  78. container.style.position = "fixed";
  79. container.style.zIndex = 10000;
  80. container.style.top = "50px"; //距离顶部位置
  81. container.style.left = "130px"; //距离左侧位置
  82. container.style.width = w; //容器宽度
  83. container.style.height = h; //容器高度
  84. container.style.resize = "both";
  85. container.style.overflow = "auto";
  86. container.style.display = "flex";
  87. container.style.flexDirection = "column";
  88. container.style.flex = "1";
  89. container.style.minHeight = "33px";
  90. container.style.minWidth = "65px";
  91. container.style.cursor = "move";
  92. container.addEventListener("mousedown", function (e) {
  93. const rect = container.getBoundingClientRect();
  94. if (e.clientX > rect.right - 10 || e.clientY > rect.bottom - 10) {
  95. return;
  96. }
  97. let disX = e.clientX - container.offsetLeft;
  98. let disY = e.clientY - container.offsetTop;
  99. document.onmousemove = function (e) {
  100. let x = e.clientX - disX;
  101. let y = e.clientY - disY;
  102. container.style.left = x + 'px';
  103. container.style.top = y + 'px';
  104. };
  105. document.onmouseup = function () {
  106. document.onmousemove = document.onmouseup = null;
  107. };
  108. });
  109. document.body.appendChild(container);
  110.  
  111. const ctx = document.createElement('canvas');
  112. ctx.id = "myChart";
  113. container.appendChild(ctx);
  114.  
  115. // 创建按钮组并设置样式和位置
  116. let wrapper = document.createElement('div');
  117. wrapper.style.position = 'absolute';
  118. wrapper.style.bottom = '40px';
  119. wrapper.style.right = '15px';
  120. wrapper.style.backgroundColor = '#fff';
  121. wrapper.style.flexShrink = 0;
  122. container.appendChild(wrapper);
  123.  
  124. const days = [1, 3, 7, 30, 180]
  125. const dayTitle = ['1天', '3天', '7天', '30天', '半年']
  126. cur_day = days[config.dayIndex];
  127.  
  128. for (let i = 0; i < 5; i++) {
  129. let btn = document.createElement('input');
  130. btn.id = 'chartType' + i;
  131. btn.type = 'radio';
  132. btn.name = 'chartType';
  133. btn.value = days[i];
  134. btn.style.cursor = 'pointer';
  135. btn.style.verticalAlign = "middle";
  136. btn.checked = i == config.dayIndex;
  137. btn.onclick = function () {
  138. cur_day = this.value;
  139. config.dayIndex = i;
  140. if (cur_name) requestMarket(cur_name, cur_day);
  141. save_config();
  142. }
  143.  
  144. let label = document.createElement('label');
  145. label.innerText = dayTitle[i];
  146. label.style.display = 'inline-block';
  147. label.style.verticalAlign = 'middle';
  148. label.style.textAlign = 'center';
  149. label.htmlFor = btn.id;
  150. label.style.margin = '1px';
  151. wrapper.appendChild(btn);
  152. wrapper.appendChild(label);
  153. }
  154. //添加一个btn隐藏canvas和wrapper
  155. let btn_close = document.createElement('input');
  156. btn_close.type = 'button';
  157. btn_close.value = '📈隐藏';
  158. btn_close.style.textAlign = 'center';
  159. btn_close.style.display = 'inline';
  160. btn_close.style.margin = 0;
  161. btn_close.style.top = '2px';
  162. btn_close.style.left = '2px';
  163. btn_close.style.cursor = 'pointer';
  164. btn_close.style.position = 'absolute';
  165. let lastWidth;
  166. let lastHeight;
  167. btn_close.onclick = toggle;
  168. function toggle() {
  169. if (wrapper.style.display === 'none') {
  170. wrapper.style.display = ctx.style.display = 'block';
  171. btn_close.value = '📈隐藏';
  172. container.style.width = lastWidth;
  173. container.style.height = lastHeight;
  174. config.visible = true;
  175. save_config();
  176. } else {
  177. lastWidth = container.style.width;
  178. lastHeight = container.style.height;
  179. wrapper.style.display = ctx.style.display = 'none';
  180. container.style.width = "auto";
  181. container.style.height = "auto";
  182. btn_close.value = '📈显示';
  183. config.visible = false;
  184. save_config();
  185. }
  186. };
  187.  
  188. container.appendChild(btn_close);
  189.  
  190. let chart = new Chart(ctx, {
  191. type: 'line',
  192. data: {
  193. labels: [],
  194. datasets: [{
  195. label: '市场',
  196. data: [],
  197. backgroundColor: 'rgba(255,99,132,0.2)',
  198. borderColor: 'rgba(255,99,132,1)',
  199. borderWidth: 1
  200. }]
  201. },
  202. options: {
  203. onClick: save_config,
  204. responsive: true,
  205. maintainAspectRatio: false,
  206. scales: {
  207. y: {
  208. beginAtZero: false
  209. }
  210. }
  211. }
  212. });
  213.  
  214. function requestMarket(name, day = 1) {
  215. if (initData_itemDetailMap && initData_itemDetailMap[name]) {
  216. name = initData_itemDetailMap[name].name;
  217. }
  218.  
  219. cur_name = name;
  220. cur_day = day;
  221.  
  222. let time = day * 3600 * 24;
  223. fetch("https://mooket.qi-e.top/market", {
  224. method: "POST",
  225. headers: {
  226. "Content-Type": "application/json",
  227. },
  228. body: JSON.stringify({
  229. name: name,
  230. time: time
  231. })
  232. }).then(res => {
  233. res.json().then(data => updateChart(data, cur_day));
  234. })
  235. }
  236. function formatTime(timestamp, day) {
  237. let date = new Date(timestamp * 1000);
  238. if (day <= 1) {
  239. return date.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' });
  240. }
  241. else if (day <= 3) {
  242. return date.toLocaleTimeString([], { hour: '2-digit' });
  243. } else if (day <= 7) {
  244. return date.toLocaleDateString([], { day: 'numeric' });
  245. } else if (day <= 30) {
  246. return date.toLocaleDateString([], { month: 'short', day: 'numeric' });
  247. } else
  248. return date.toLocaleDateString([], { year: '2-digit', month: 'short' });
  249. }
  250.  
  251. //data={'bid':[{time:1,price:1}],'ask':[{time:1,price:1}]}
  252. function updateChart(data, day) {
  253. //过滤异常元素
  254. for (let i = data.bid.length - 1; i >= 0; i--) {
  255. if (data.bid[i].price < 0 || data.ask[i].price < 0) {
  256. data.bid.splice(i, 1);
  257. data.ask.splice(i, 1);
  258. }
  259. }
  260. //timestamp转日期时间
  261. //根据day输出不同的时间表示,<3天显示时分,<=7天显示日时,<=30天显示月日,>30天显示年月
  262.  
  263. let labels = data.bid.map(x => formatTime(x.time, day));
  264.  
  265. chart.data.labels = labels;
  266.  
  267. chart.data.datasets = [
  268. {
  269. label: '买入',
  270. data: data.bid.map(x => x.price),
  271. backgroundColor: '#ff3300',
  272. borderColor: '#990000',
  273. borderWidth: 1
  274. },
  275. {
  276. label: '卖出',
  277. data: data.ask.map(x => x.price),
  278. backgroundColor: '#00cc00',
  279. borderColor: '#006600',
  280. borderWidth: 1
  281. },
  282. {
  283. label: '均价',
  284. data: data.bid.map(({ price: bidPrice }, index) => {
  285. const { price: askPrice } = data.ask[index];
  286. return (bidPrice + askPrice) / 2;
  287. }),
  288. backgroundColor: '#ff9900',
  289. borderColor: '#996600',
  290. borderWidth: 1
  291. }
  292. ];
  293. chart.setDatasetVisibility(0, config.filter.ask);
  294. chart.setDatasetVisibility(1, config.filter.bid);
  295. chart.setDatasetVisibility(2, config.filter.mean);
  296.  
  297. chart.update()
  298. }
  299. function save_config() {
  300.  
  301. if (chart && chart.data && chart.data.datasets && chart.data.datasets.length == 3) {
  302. config.filter.ask = chart.getDatasetMeta(0).visible;
  303. config.filter.bid = chart.getDatasetMeta(1).visible;
  304. config.filter.mean = chart.getDatasetMeta(2).visible;
  305. }
  306. localStorage.setItem("mooket_config", JSON.stringify(config));
  307. }
  308. //requestMarket('Apple', 1);
  309. toggle();
  310.  
  311. })();

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址