SMMO游戏助手

https://web.simple-mmo.com/,web端游戏功能辅助工具。本助手致力于:不帮你玩游戏,但是可以让你更好地玩游戏。

  1. // ==UserScript==
  2. // @name SMMO游戏助手
  3. // @namespace bmqy.net
  4. // @version 1.1.1
  5. // @author bmqy
  6. // @description https://web.simple-mmo.com/,web端游戏功能辅助工具。本助手致力于:不帮你玩游戏,但是可以让你更好地玩游戏。
  7. // @icon https://web.simple-mmo.com/apple-touch-icon.png
  8. // @match https://web.simple-mmo.com/*
  9. // @grant GM_getValue
  10. // @grant GM_info
  11. // @grant GM_setValue
  12. // @grant GM_xmlhttpRequest
  13. // ==/UserScript==
  14.  
  15. (function () {
  16. 'use strict';
  17.  
  18. var _GM_getValue = /* @__PURE__ */ (() => typeof GM_getValue != "undefined" ? GM_getValue : void 0)();
  19. var _GM_info = /* @__PURE__ */ (() => typeof GM_info != "undefined" ? GM_info : void 0)();
  20. var _GM_setValue = /* @__PURE__ */ (() => typeof GM_setValue != "undefined" ? GM_setValue : void 0)();
  21. var _GM_xmlhttpRequest = /* @__PURE__ */ (() => typeof GM_xmlhttpRequest != "undefined" ? GM_xmlhttpRequest : void 0)();
  22. const SMMOHelper = {
  23. storageKey: {
  24. player: "player",
  25. playerId: "playerId",
  26. levelStep: "levelStep",
  27. token: "csrfToken"
  28. },
  29. getValue(key) {
  30. try {
  31. return _GM_getValue(key, "");
  32. } catch (error) {
  33. return localStorage.getItem(key) || "";
  34. }
  35. },
  36. setValue(key, val) {
  37. try {
  38. return _GM_setValue(key, val);
  39. } catch (error) {
  40. localStorage.setItem(key, val);
  41. }
  42. },
  43. init() {
  44. this.fakeFetch();
  45. this.searchPlayerId();
  46. this.resetDonateButton();
  47. this.resetJobsButton();
  48. this.addHeadTravelBtn();
  49. this.showFooter();
  50. this.onUserClickBtn();
  51. },
  52. searchPlayerId() {
  53. setTimeout(() => {
  54. let a = document.querySelector('a[href^="/user/view/"]');
  55. let id = a.href.match(/\d+/)[0];
  56. if (id)
  57. this.setValue(this.storageKey.playerId, id);
  58. }, 0);
  59. },
  60. // 顶部导航增加旅行按钮
  61. addHeadTravelBtn() {
  62. if (location.href.indexOf("/travel") > -1 && location.href.indexOf("/travel/party") === -1)
  63. return false;
  64. if (document.querySelector("#btnToTravel"))
  65. return false;
  66. let $box = document.querySelector(".ml-4.flex.items-center.relative");
  67. let $a = document.createElement("a");
  68. $a.id = "btnToTravel";
  69. $a.innerHTML = `<a href="/travel">
  70. <button class="relative lg:hidden flex-shrink-0 bg-white p-1 text-indigo-600 dark:text-indigo-700 dark:bg-indigo-950 rounded-full hover:text-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500 mr-4">
  71. <span class="sr-only">View Friends</span>
  72. <!-- Heroicon name: outline/bell -->
  73. <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
  74. <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 9l3 3m0 0l-3 3m3-3H8m13 0a9 9 0 11-18 0 9 9 0 0118 0z"></path>
  75. </svg>
  76. </button>
  77. </a>`;
  78. $box.insertBefore($a, $box.childNodes[0]);
  79. },
  80. // 增加脚本设置项
  81. addSettingsItem() {
  82. if (location.href.indexOf("/preferences/customisation") === -1)
  83. return false;
  84. document.querySelector(".flex.items-center.justify-between.rounded-lg").parentNode;
  85. let $newItem = document.createElement("div");
  86. $newItem.className = "flex items-center justify-between bg-white rounded-lg px-2 py-3 mt-2";
  87. $newItem.innerHTML = `<span class="flex-grow flex flex-col ml-2">
  88. <span class="text-xs sm:text-sm font-medium text-gray-900" id="smmo-helper-label">工作次数</span>
  89. <span class="text-xs sm:text-sm text-gray-500" id="smmo-helper-description">是否设置工作次数默认10次</span>
  90. </span>
  91. <button type="button" class="bg-indigo-600 relative inline-flex flex-shrink-0 h-6 w-11 border-2 border-transparent rounded-full cursor-pointer transition-colors ease-in-out duration-200 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500" role="switch" aria-checked="false" aria-labelledby="smmo-helper-label" aria-describedby="smmo-helper-description">
  92. <span aria-hidden="true" class="translate-x-5 pointer-events-none inline-block h-5 w-5 rounded-full bg-white shadow transform ring-0 transition ease-in-out duration-200"></span>
  93. </button>`;
  94. },
  95. // 脚本底部信息
  96. showFooter() {
  97. let $div = document.createElement("div");
  98. $div.style.position = "fixed";
  99. $div.style.left = "10px";
  100. $div.style.bottom = "10px";
  101. $div.style.fontSize = "14px";
  102. $div.style.color = "#999";
  103. $div.innerHTML = `${_GM_info["script"]["name"]} v${_GM_info["script"]["version"]}`;
  104. document.body.appendChild($div);
  105. },
  106. // 监听事件动作,更新Bio
  107. onUserClickBtn() {
  108. if (location.href.indexOf("/travel") > -1) {
  109. let btns = document.querySelectorAll("button");
  110. btns.forEach((e, i) => {
  111. if (e.id.indexOf("step_btn_") > -1) {
  112. e.addEventListener("click", () => {
  113. this.getPlayerBio();
  114. console.log("旅行");
  115. });
  116. }
  117. });
  118. } else if (location.href.indexOf("/npcs/attack/") > -1) {
  119. let btns = document.querySelectorAll("button");
  120. btns.forEach((e, i) => {
  121. if (e.innerText === "Attack") {
  122. e.addEventListener("click", () => {
  123. console.log("攻击");
  124. });
  125. }
  126. });
  127. } else if (location.href.indexOf("/crafting/material/") > -1) {
  128. let btns = document.querySelectorAll("button");
  129. btns.forEach((e, i) => {
  130. if (e.id === "crafting_button") {
  131. e.addEventListener("click", () => {
  132. console.log("采集");
  133. });
  134. }
  135. });
  136. } else if (location.href.indexOf("/quests/view/") > -1) {
  137. let btns = document.querySelectorAll("button");
  138. btns.forEach((e, i) => {
  139. if (e.id === "questButton") {
  140. e.addEventListener("click", () => {
  141. console.log("任务");
  142. });
  143. }
  144. });
  145. } else if (location.href.indexOf("/crafting/menu") > -1) {
  146. let btns = document.querySelectorAll("button");
  147. btns.forEach((e, i) => {
  148. if (e.innerText === "Claim Items") {
  149. e.addEventListener("click", () => {
  150. console.log("制作");
  151. });
  152. }
  153. });
  154. }
  155. },
  156. // 重置Donate
  157. resetDonateButton() {
  158. document.querySelectorAll("button[type=button]").forEach((e) => {
  159. if (e.innerText === "Gold") {
  160. e.addEventListener("click", function() {
  161. setTimeout(() => {
  162. document.querySelector(".swal2-input").value = 2e3;
  163. }, 0);
  164. });
  165. }
  166. });
  167. },
  168. // 重置Jobs
  169. resetJobsButton() {
  170. document.querySelectorAll("button[type=button]").forEach((e) => {
  171. if (e.innerText === "Start working") {
  172. e.addEventListener("click", function() {
  173. setTimeout(() => {
  174. document.querySelector("input[type=range]").value = 10;
  175. document.querySelector("output").innerHTML = 10;
  176. }, 0);
  177. });
  178. }
  179. });
  180. },
  181. // 拦截请求
  182. fakeFetch() {
  183. const originFetch = window.fetch;
  184. window.fetch = (url, options) => {
  185. return originFetch(url, options).then(async (response) => {
  186. if (url === "https://api.simple-mmo.com/api/action/travel/4") {
  187. let resRaw = response.clone();
  188. let resF = await resRaw.json();
  189. this.checkPlayerLevel(resF.level) && this.updatePlayerBio(resF.level);
  190. this.setValue(this.storageKey.player, resF);
  191. return response;
  192. } else {
  193. return response;
  194. }
  195. });
  196. };
  197. },
  198. http(options) {
  199. return new Promise((resolve, reject) => {
  200. _GM_xmlhttpRequest({
  201. method: options.methods || "GET",
  202. url: options.url,
  203. responseType: options.responseType || null,
  204. headers: options.headers || null,
  205. data: options.data || "",
  206. onload: function(xhr) {
  207. if (xhr.status == 200) {
  208. resolve(xhr.response);
  209. } else {
  210. reject(xhr.response);
  211. }
  212. },
  213. onerror: function(xhr) {
  214. reject(xhr.response);
  215. }
  216. });
  217. });
  218. },
  219. // 检测等级变化
  220. checkPlayerLevel(level) {
  221. let playerData = this.getValue(this.storageKey.player, {});
  222. if (!playerData.level)
  223. return false;
  224. let levelStep = this.getValue("levelStep", 100);
  225. if (playerData.level != level && level % levelStep === 0) {
  226. return true;
  227. }
  228. return false;
  229. },
  230. getNewBio(level, old) {
  231. let news = "";
  232. let oldArr = [];
  233. if (old.indexOf("===== LEVEL UP =====") === -1) {
  234. news = `${old}
  235. ===== LEVEL UP =====
  236. ${this.getDate()} LV${level}
  237. ===== UPDATE =====`;
  238. } else {
  239. let oldStr = /\=\=\=\=\= LEVEL UP \=\=\=\=\=\n([a-zA-Z0-9 \-\r\n:]+)\n(?=\=\=\=\=\= UPDATE \=\=\=\=\=)/ig.exec(old)[1];
  240. oldArr = oldStr.split(/\n/);
  241. let newLine = `${this.getDate()} LV${level}`;
  242. if (oldArr.indexOf(newLine) === -1) {
  243. oldArr.push(newLine);
  244. }
  245. news = old.replace(/(\=\=\=\=\= LEVEL UP \=\=\=\=\=\n)([a-zA-Z0-9 \-\r\n:]+)(\n\=\=\=\=\= UPDATE \=\=\=\=\=)/ig, `$1${oldArr.join("\n")}$3`);
  246. }
  247. return news;
  248. },
  249. getDate() {
  250. let now = /* @__PURE__ */ new Date();
  251. let y = now.getFullYear();
  252. let m = ("0" + (now.getMonth() + 1)).slice(-2);
  253. let d = ("0" + now.getDate()).slice(-2);
  254. return `${y}-${m}-${d}`;
  255. },
  256. // 更新Bio
  257. async updatePlayerBio(level) {
  258. let playerId = this.getValue(this.storageKey.playerId);
  259. let csrfToken = this.getValue(this.storageKey.token);
  260. if (!playerId || !csrfToken)
  261. return false;
  262. let url = `https://web.simple-mmo.com/user/character/${playerId}/bio/submit`;
  263. let old = await this.getPlayerBio();
  264. let newBio = await this.getNewBio(level, old);
  265. this.http({
  266. url,
  267. methods: "POST",
  268. headers: {
  269. "Content-Type": "application/x-www-form-urlencoded"
  270. },
  271. data: `_token=${csrfToken}&content=${newBio}`
  272. });
  273. },
  274. // 获取Bio
  275. async getPlayerBio() {
  276. let playerId = this.getValue(this.storageKey.playerId);
  277. if (!playerId)
  278. return false;
  279. let url = `https://web.simple-mmo.com/user/view/${playerId}/bio?new_page_refresh=true`;
  280. let response = await this.http({
  281. url
  282. });
  283. let domParser = new DOMParser();
  284. let dom = domParser.parseFromString(response, "text/html");
  285. let csrfToken = document.querySelector("meta[name=csrf-token]").content;
  286. this.setValue(this.storageKey.token, csrfToken);
  287. return dom.querySelector("textarea#content").innerHTML;
  288. }
  289. };
  290. window.addEventListener("load", function() {
  291. SMMOHelper.init();
  292. });
  293.  
  294. })();