[银河奶牛]钉钉

在公会页面显示更详细的数据

当前为 2024-07-15 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name [Mwi]Guild Details Display
  3. // @name:zh-CN [银河奶牛]钉钉
  4. // @name:zh-TW [银河奶牛]公会数据显示
  5. // @namespace http://tampermonkey.net/
  6. // @version 1.01
  7. // @description Display more detailed data on the guild page.
  8. // @description:zh-cn 在公会页面显示更详细的数据
  9. // @description:zh-tw 在公会页面显示更详细的数据
  10. // @author Truth_Light
  11. // @icon https://www.google.com/s2/favicons?sz=64&domain=milkywayidle.com
  12. // @match https://www.milkywayidle.com/game?characterId=*
  13. // @grant none
  14. // ==/UserScript==
  15.  
  16. (function() {
  17. 'use strict';
  18. const userLanguage = navigator.language || navigator.userLanguage;
  19. const isZH = userLanguage.startsWith("zh");
  20. const updataDealy = 24*60*60*1000; //数据更新时限
  21. let rateXPDayMap = {};
  22. function formatPrice(value) {
  23. const isNegative = value < 0;
  24. value = Math.abs(value);
  25.  
  26. if (value >= 1000000) {
  27. return (isNegative ? '-' : '') + (value / 1000000).toFixed(1) + 'M';
  28. } else if (value >= 1000) {
  29. return (isNegative ? '-' : '') + (value / 1000).toFixed(1) + 'K';
  30. } else {
  31. return (isNegative ? '-' : '') + value.toFixed(0).toString();
  32. }
  33. }
  34.  
  35. function hookWS() {
  36. const dataProperty = Object.getOwnPropertyDescriptor(MessageEvent.prototype, "data");
  37. const oriGet = dataProperty.get;
  38.  
  39. dataProperty.get = hookedGet;
  40. Object.defineProperty(MessageEvent.prototype, "data", dataProperty);
  41.  
  42. function hookedGet() {
  43. const socket = this.currentTarget;
  44. if (!(socket instanceof WebSocket)) {
  45. return oriGet.call(this);
  46. }
  47. if (socket.url.indexOf("api.milkywayidle.com/ws") <= -1 && socket.url.indexOf("api-test.milkywayidle.com/ws") <= -1) {
  48. return oriGet.call(this);
  49. }
  50.  
  51. const message = oriGet.call(this);
  52. Object.defineProperty(this, "data", { value: message }); // Anti-loop
  53.  
  54. return handleMessage(message);
  55. }
  56. }
  57.  
  58. function handleMessage(message) {
  59. try {
  60. let obj = JSON.parse(message);
  61. let timeDifference = null;
  62. let newUpdatedAt = null;
  63. if (obj && obj.type === "guild_updated") {
  64. const Guild_ID = obj.guild.id;
  65. let storedData = JSON.parse(localStorage.getItem("Guild_Data")) || {};
  66.  
  67. // 判断是否已经存在旧数据
  68. if (storedData[Guild_ID] && storedData[Guild_ID].guild_updated && storedData[Guild_ID].guild_updated.old.updatedAt) {
  69. const oldUpdatedAt = new Date(storedData[Guild_ID].guild_updated.new.updatedAt);
  70. newUpdatedAt = new Date(obj.guild.updatedAt);
  71.  
  72. // 计算时间差(单位:毫秒)
  73. timeDifference = newUpdatedAt - oldUpdatedAt;
  74.  
  75. if (timeDifference >= updataDealy) {
  76. // 更新老数据为新数据
  77. storedData[Guild_ID].guild_updated.old = storedData[Guild_ID].guild_updated.new;
  78. // 更新新数据为当前数据
  79. storedData[Guild_ID].guild_updated.new = {
  80. experience: obj.guild.experience,
  81. level: obj.guild.level,
  82. updatedAt: obj.guild.updatedAt
  83. };
  84. } else {
  85. // 仅更新新数据
  86. storedData[Guild_ID].guild_updated.new = {
  87. experience: obj.guild.experience,
  88. level: obj.guild.level,
  89. updatedAt: obj.guild.updatedAt
  90. };
  91. }
  92. //计算Δ
  93. const Delta = {
  94. Delta_Xp: storedData[Guild_ID].guild_updated.new.experience - storedData[Guild_ID].guild_updated.old.experience,
  95. Delta_Level: storedData[Guild_ID].guild_updated.new.level - storedData[Guild_ID].guild_updated.old.level,
  96. Delta_Time: (newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000, // 转换为秒
  97. Rate_XP_Hours: (3600*(obj.guild.experience - storedData[Guild_ID].guild_updated.old.experience)/((newUpdatedAt - new Date(storedData[Guild_ID].guild_updated.old.updatedAt)) / 1000)).toFixed(2)
  98. };
  99. storedData[Guild_ID].guild_updated.Delta = Delta;
  100.  
  101. const Guild_TotalXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[1];
  102. if (Guild_TotalXp_div) {
  103. const xpText = isZH ? "经验值 / 小时" : "XP / Hour";
  104.  
  105. Guild_TotalXp_div.insertAdjacentHTML(
  106. "afterend",
  107. `<div>${formatPrice(Delta.Rate_XP_Hours)} ${xpText}</div>`
  108. );
  109. const Guild_NeedXp_div = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2];
  110. if (Guild_NeedXp_div) {
  111. const Guild_NeedXp = document.querySelectorAll(".GuildPanel_value__Hm2I9")[2].textContent.replace(/,/g, '');
  112. const Time = TimeReset(Guild_NeedXp/Delta.Rate_XP_Hours);
  113. Guild_NeedXp_div.insertAdjacentHTML(
  114. "afterend", // 使用 "afterend" 在元素的后面插入内容
  115. `<div>${Time}</div>`
  116. );
  117. }
  118. }
  119. } else {
  120. // 如果没有旧数据,则直接添加新数据
  121. storedData[Guild_ID] = {
  122. guild_name: obj.guild.name,
  123. guild_updated: {
  124. old: {
  125. experience: obj.guild.experience,
  126. level: obj.guild.level,
  127. updatedAt: obj.guild.updatedAt
  128. },
  129. new: {},
  130. }
  131. };
  132. }
  133.  
  134. // 存储更新后的数据到 localStorage
  135. localStorage.setItem("Guild_Data", JSON.stringify(storedData));
  136. } else if (obj && obj.type === "guild_characters_updated") {
  137. let storedData = JSON.parse(localStorage.getItem("Guild_Data")) || {};
  138. for (const key in obj.guildSharableCharacterMap) {
  139. if (obj.guildSharableCharacterMap.hasOwnProperty(key)) {
  140. const Guild_ID = obj.guildCharacterMap[key].guildID;
  141. const name = obj.guildSharableCharacterMap[key].name;
  142. storedData[Guild_ID].guild_player = storedData[Guild_ID].guild_player || {};
  143. if (storedData[Guild_ID] && storedData[Guild_ID].guild_player && storedData[Guild_ID].guild_player[name] && storedData[Guild_ID].guild_player[name].old) {
  144. if (timeDifference >= updataDealy) {
  145. // 更新老数据为新数据
  146. storedData[Guild_ID].guild_player[name].old = storedData[Guild_ID].guild_player[name].new;
  147. // 更新新数据为当前数据
  148. storedData[Guild_ID].guild_player[name].new = {
  149. id: key,
  150. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  151. guildExperience: obj.guildCharacterMap[key].guildExperience
  152. };
  153. } else {
  154. // 仅更新新数据
  155. storedData[Guild_ID].guild_player[name].new = {
  156. id: key,
  157. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  158. guildExperience: obj.guildCharacterMap[key].guildExperience
  159. };
  160. }
  161. //计算Δ
  162. const Delta = {
  163. Delta_Xp: storedData[Guild_ID].guild_player[name].new.guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience,
  164. Rate_XP_Day: (24*3600*(obj.guildCharacterMap[key].guildExperience - storedData[Guild_ID].guild_player[name].old.guildExperience)/(storedData[Guild_ID].guild_updated.Delta.Delta_Time)).toFixed(2)
  165. };
  166. storedData[Guild_ID].guild_player[name].Delta = Delta;
  167. rateXPDayMap[name] = Delta.Rate_XP_Day;
  168. }else {
  169.  
  170. storedData[Guild_ID].guild_player[name] = {
  171. old: {
  172. id: key,
  173. gameMode: obj.guildSharableCharacterMap[key].gameMode,
  174. guildExperience: obj.guildCharacterMap[key].guildExperience
  175. },
  176. new:{}
  177. };
  178. }
  179. }
  180.  
  181. }
  182. //console.log("测试数据",storedData);
  183. //console.log("guild_characters_updated", obj);
  184. updateExperienceDisplay(rateXPDayMap);
  185. localStorage.setItem("Guild_Data", JSON.stringify(storedData));
  186. }
  187.  
  188.  
  189.  
  190.  
  191. } catch (error) {
  192. console.error("Error processing message:", error);
  193. }
  194. return message;
  195. }
  196.  
  197.  
  198. function TimeReset(hours) {
  199. const totalMinutes = hours * 60;
  200. const days = Math.floor(totalMinutes / (24 * 60));
  201. const yudays = totalMinutes % (24 * 60);
  202. const hrs = Math.floor(yudays / 60);
  203. const minutes = Math.floor(yudays % 60);
  204. const dtext = isZH ? "天" : "d";
  205. const htext = isZH ? "时" : "h";
  206. const mtext = isZH ? "分" : "m";
  207. return `${days}${dtext} ${hrs}${htext} ${minutes}${mtext}`;
  208. }
  209.  
  210. function updateExperienceDisplay(rateXPDayMap) {
  211. const trElements = document.querySelectorAll(".GuildPanel_membersTable__1NwIX tbody tr");
  212.  
  213. // 将 rateXPDayMap 转换为数组,便于排序和排名计算
  214. const sortedMembers = Object.entries(rateXPDayMap)
  215. .map(([name, XPdata]) => ({ name, XPdata }))
  216. .sort((a, b) => b.XPdata - a.XPdata); // 按 XPdata 降序排序
  217.  
  218. for (let { name, XPdata } of sortedMembers) {
  219. for (let tr of trElements) {
  220. const nameElement = tr.querySelector(".CharacterName_name__1amXp");
  221.  
  222. if (nameElement && nameElement.textContent.trim() === name) {
  223. const experienceElement = tr.querySelector("td:nth-child(3) > div");
  224.  
  225. if (experienceElement) {
  226. const newDiv = document.createElement('div');
  227. newDiv.textContent = `${formatPrice(XPdata)}/天`;
  228.  
  229. // 计算颜色:根据排名计算渐变颜色,排名越高越绿,排名越低越红
  230. const hue = 120 - (sortedMembers.findIndex(member => member.name === name) * (120 / (sortedMembers.length - 1)));
  231. newDiv.style.color = `hsl(${hue}, 100%, 50%)`;
  232.  
  233. experienceElement.insertAdjacentElement('afterend', newDiv);
  234.  
  235. break;
  236. }
  237. }
  238. }
  239. }
  240. }
  241.  
  242.  
  243. hookWS();
  244.  
  245. })();