Florr.io Server Selector

Press Tab to open Server Selector.

目前为 2020-07-02 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Florr.io Server Selector
  3. // @namespace Violentmonkey Scripts
  4. // @version 2.2.5
  5. // @author ash & Guava_Thrower
  6. // @match *://florr.io/*
  7. // @grant unsafeWindow
  8. // @grant GM_addStyle
  9. // @grant GM_info
  10. // @description Press Tab to open Server Selector.
  11. // ==/UserScript==
  12.  
  13. // Styles by @Guava_Thrower, ash
  14.  
  15. GM_addStyle(`
  16. .container {
  17. display: block;
  18. width: 100%;
  19. height: 100%;
  20. position: fixed;
  21. top: 0;
  22. left: 0;
  23. pointer-events: none;
  24. font-family: Ubuntu;
  25. color: #fff;
  26. font-size: 16px;
  27. text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000, 0 0 2px #000;
  28. }
  29.  
  30. .active {
  31. pointer-events: auto;
  32. }
  33.  
  34. .hidden {
  35. display: none;
  36. }
  37.  
  38. .btn, .modal {
  39. background: #5a9fdb;
  40. border: 4px solid #4981b1;
  41. border-radius: 6px;
  42. pointer-events: auto;
  43. }
  44.  
  45. .btn {
  46. position: absolute;
  47. height: 34px;
  48. right: 6px;
  49. bottom: 6px;
  50. padding-left: 16px;
  51. padding-right: 16px;
  52. cursor: pointer;
  53. outline: 0;
  54. font-family: Ubuntu;
  55. color: #fff;
  56. font-size: 16px;
  57. font-weight: bold;
  58. text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000, 0 0 2px #000;
  59. }
  60.  
  61. .container:not(.active) .btn:hover {
  62. background: #65aeef;
  63. }
  64.  
  65. .active .btn {
  66. background: #518fc5;
  67. }
  68.  
  69. .modal {
  70. position: absolute;
  71. bottom: 48px;
  72. right: 6px;
  73. padding: 8px;
  74. width: 300px;
  75. max-height: 400px;
  76. transition: 0.2s;
  77. transform: translate(0, calc(100% + 60px));
  78. }
  79.  
  80. .active .modal {
  81. transform: translate(0, 0);
  82. }
  83.  
  84. .modal-title {
  85. font-size: 1.6em;
  86. text-align: center;
  87. margin-top: 0;
  88. }
  89.  
  90. .modal-close-btn {
  91. position: absolute;
  92. right: 5px;
  93. top: 5px;
  94. width: 30px;
  95. height: 30px;
  96. border-radius: 4px;
  97. background: #bb5555;
  98. border: 5px solid #974545;
  99. cursor: pointer;
  100. outline: 0;
  101. }
  102.  
  103. .modal-close-btn:hover {
  104. background: #da6868;
  105. }
  106.  
  107. .modal-close-btn:after, .modal-close-btn:before {
  108. content: "";
  109. position: absolute;
  110. left: 50%;
  111. top: 50%;
  112. width: 5px;
  113. height: 90%;
  114. background: #cccccc;
  115. border-radius: 4px;
  116. }
  117.  
  118. .modal-close-btn:after {
  119. transform: translate(-50%, -50%) rotate(45deg);
  120. }
  121.  
  122. .modal-close-btn:before {
  123. transform: translate(-50%, -50%) rotate(-45deg);
  124. }
  125.  
  126. .modal-element {
  127. background: #4981b1;
  128. padding: 10px;
  129. border-radius: 5px;
  130. margin-bottom: 5px;
  131. cursor: pointer;
  132. }
  133.  
  134. .modal-element:last-child {
  135. margin-bottom: 0;
  136. }
  137.  
  138. .modal-element-active {
  139. box-shadow: inset 0 0 0 2px #376185;
  140. }
  141.  
  142. .modal-loader {
  143. display: flex;
  144. flex-direction: column;
  145. align-items: center;
  146. padding: 10px;
  147. }
  148.  
  149. .spinner {
  150. width: 40px;
  151. height: 40px;
  152. border: 10px solid rgba(0, 0, 0, 0.5);
  153. border-top-color: rgba(0, 0, 0, 0.8);
  154. border-radius: 50%;
  155. animation: spin 0.5s infinite;
  156. }
  157.  
  158. .spinner-text {
  159. margin-top: 5px;
  160. font-size: 1.25em;
  161. }
  162.  
  163. .modal-info {
  164. position: absolute;
  165. right: 40px;
  166. top: 5px;
  167. font-size: 1.20em;
  168. padding: 4px 5px;
  169. }
  170.  
  171. .modal-info:hover .modal-info-content {
  172. opacity: 1;
  173. pointer-events: inherit;
  174. }
  175.  
  176. .modal-info-content {
  177. position: absolute;
  178. bottom: 150%;
  179. right: 0;
  180. background: rgba(0, 0, 0, 0.5);
  181. border-radius: 4px;
  182. opacity: 0;
  183. padding: 6px;
  184. font-size: 0.85rem;
  185. width: 250px;
  186. transition: 0.2s;
  187. pointer-events: none;
  188. }
  189.  
  190. .modal-info-title {
  191. margin: 0;
  192. margin-bottom: 5px;
  193. font-size: 1.40rem;
  194. }
  195.  
  196. @keyframes spin {
  197. from {
  198. transform: rotate(0deg);
  199. }
  200. to {
  201. transform: rotate(360deg);
  202. }
  203. }
  204. `);
  205.  
  206. (() => {
  207. 'use strict';
  208.  
  209. // UI component by @Guava_Thrower, ash
  210.  
  211. const html = `
  212. <button class="btn">Servers</button>
  213. <div class="modal">
  214. <h1 class="modal-title">Servers</h1>
  215. <button class="modal-close-btn"></button>
  216. <span class="modal-info">
  217. ?
  218. <div class="modal-info-content">
  219. <div class="modal-info-title">Developers</div>
  220. <div>UI - Guava_Thrower, ash</div>
  221. <div>Server list - ash</div>
  222. <br>
  223. <div>v${GM_info.script.version}</div>
  224. </div>
  225. </span>
  226. <div class="modal-element modal-loader">
  227. <div class="spinner"></div>
  228. <div class="spinner-text">Fetching...</div>
  229. </div>
  230. </div>
  231. `;
  232.  
  233. const container = document.createElement("div");
  234. container.classList.add('container');
  235. container.innerHTML = html;
  236. document.body.appendChild(container);
  237.  
  238. const modalEl = document.querySelector(".modal");
  239. const btnEl = document.querySelector(".btn");
  240. const closeBtnEl = document.querySelector(".modal-close-btn");
  241. const loaderEl = document.querySelector(".modal-loader");
  242.  
  243. btnEl.onclick = () => {
  244. container.classList.toggle("active");
  245. }
  246.  
  247. closeBtnEl.onclick = () => {
  248. closeModal();
  249. }
  250.  
  251. const closeModal = () => {
  252. container.classList.remove("active");
  253. }
  254. window.addEventListener('click', (evt) => {
  255. if (evt.target === container) {
  256. closeModal();
  257. }
  258. });
  259. window.addEventListener('keydown', (evt) => {
  260. if (evt.code === 'Tab') {
  261. container.classList.toggle('hidden');
  262. if (container.classList.contains('active')) {
  263. container.classList.remove('active');
  264. }
  265. }
  266. });
  267.  
  268. // listing servers by @ash
  269.  
  270. const Config = {
  271. script: {
  272. m28nOverride: false,
  273. socket: null,
  274. currentId: '',
  275. ids: []
  276. }
  277. };
  278.  
  279. const fetchServers = async () => {
  280. let url = "https://api.n.m28.io";
  281.  
  282. try {
  283. let response = await fetch(`${url}/endpoint/florrio/findEach/`);
  284. let body = await response.json();
  285.  
  286. if (body.hasOwnProperty("servers")) {
  287. loaderEl.style.display = "none";
  288. Object.entries(body.servers).forEach(([key, val]) => {
  289. if (!Config.script.ids.includes(val.id)) {
  290.  
  291. let el = document.createElement("div");
  292. el.classList.add("modal-element");
  293. el.innerHTML = key;
  294.  
  295. Config.script.ids.push(val.id);
  296.  
  297. el.setAttribute('data-value', JSON.stringify(val));
  298. if (val.id === Config.script.currentId) {
  299. el.classList.add("modal-element-active");
  300. }
  301.  
  302. el.onclick = () => {
  303. connectServer();
  304. let activeEl = document.querySelector(".modal-element-active");
  305. activeEl.classList.remove("modal-element-active");
  306. el.classList.add("modal-element-active");
  307. closeModal();
  308. }
  309.  
  310. modalEl.appendChild(el);
  311.  
  312. }
  313. });
  314. }
  315. } catch (err) {
  316. console.error(err);
  317. }
  318. }
  319.  
  320. const findServerPreferenceProxy = new Proxy(unsafeWindow.m28n.findServerPreference, {
  321. apply(_target, _thisArgs, args) {
  322. if (Config.script.m28nOverride) {
  323. var el = document.querySelector(".modal-element-active");
  324. args[1](null, [JSON.parse(el.getAttribute("data-value"))]);
  325. return;
  326. }
  327. return Reflect.apply(...arguments);
  328. }
  329. })
  330.  
  331. unsafeWindow.m28n.findServerPreference = findServerPreferenceProxy;
  332.  
  333. const WebSocketProxy = new Proxy(unsafeWindow.WebSocket, {
  334. construct(Target, args) {
  335. const instance = Reflect.construct(...arguments);
  336.  
  337. const messageHandler = (e) => {
  338. let buffer = new DataView(e.data);
  339. if (buffer.getUint8(0) === 1) {
  340. instance.removeEventListener("message", messageHandler);
  341. Config.script.socket = instance;
  342. Config.script.currentId = instance.url.match(/wss:\/\/(\w{4})\./)[1];
  343. fetchServers();
  344. }
  345. }
  346.  
  347. instance.addEventListener("message", messageHandler);
  348.  
  349. return instance;
  350. }
  351. });
  352.  
  353. unsafeWindow.WebSocket = WebSocketProxy;
  354.  
  355. const connectServer = () => {
  356. Config.script.m28nOverride = true;
  357. Config.script.socket.close();
  358. }
  359. })();