Florr.io Server Selector

Press Tab to open Server Selector.

目前為 2020-07-02 提交的版本,檢視 最新版本

// ==UserScript==
// @name         Florr.io Server Selector
// @namespace    Violentmonkey Scripts
// @version      2.2.5
// @author       ash & Guava_Thrower
// @match        *://florr.io/*
// @grant        unsafeWindow
// @grant        GM_addStyle
// @grant	 GM_info
// @description Press Tab to open Server Selector.
// ==/UserScript==

// Styles by @Guava_Thrower, ash

GM_addStyle(`
  .container {
    display: block;
    width: 100%;
    height: 100%;
    position: fixed;
    top: 0;
    left: 0;
    pointer-events: none;
    font-family: Ubuntu;
    color: #fff;
		font-size: 16px;
    text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000, 0 0 2px #000;
  }

  .active {
    pointer-events: auto;
  }

  .hidden {
    display: none;
  }

  .btn, .modal {
		background: #5a9fdb;
		border: 4px solid #4981b1;
		border-radius: 6px;
    pointer-events: auto;
	}

	.btn {
		position: absolute;
    height: 34px;
		right: 6px; 
		bottom: 6px; 
    padding-left: 16px;
    padding-right: 16px;
		cursor: pointer;
		outline: 0;
    font-family: Ubuntu;
    color: #fff;
		font-size: 16px;
    font-weight: bold;
    text-shadow: 0 0 2px #000, 0 0 2px #000, 0 0 2px #000, 0 0 2px #000;
	}

	.container:not(.active) .btn:hover {
		background: #65aeef;
	}

	.active .btn {
		background: #518fc5;
	}

	.modal {
		position: absolute;
		bottom: 48px;
		right: 6px;
		padding: 8px;
		width: 300px;
		max-height: 400px;
		transition: 0.2s;
		transform: translate(0, calc(100% + 60px));
	}

	.active .modal {
		transform: translate(0, 0);
	}

	.modal-title {
		font-size: 1.6em;
		text-align: center;
		margin-top: 0;
	}

	.modal-close-btn {
		position: absolute;
		right: 5px;
		top: 5px;
		width: 30px;
		height: 30px;
		border-radius: 4px;
		background: #bb5555;
		border: 5px solid #974545;
		cursor: pointer;
		outline: 0;
	}

	.modal-close-btn:hover {
		background: #da6868;
	}

	.modal-close-btn:after, .modal-close-btn:before {
		content: "";
		position: absolute;
		left: 50%;
		top: 50%;
		width: 5px;
		height: 90%;
		background: #cccccc;
		border-radius: 4px;
	}

	.modal-close-btn:after {
		transform: translate(-50%, -50%) rotate(45deg);
	}

	.modal-close-btn:before {
		transform: translate(-50%, -50%) rotate(-45deg);
	}

	.modal-element {
		background: #4981b1;
		padding: 10px;
		border-radius: 5px;
		margin-bottom: 5px;
		cursor: pointer;
	}

	.modal-element:last-child {
		margin-bottom: 0;
	}

	.modal-element-active {
		box-shadow: inset 0 0 0 2px #376185;
	}

	.modal-loader {
		display: flex;
		flex-direction: column;
		align-items: center;
		padding: 10px;
	}

	.spinner {
		width: 40px;
		height: 40px;
		border: 10px solid rgba(0, 0, 0, 0.5);
		border-top-color: rgba(0, 0, 0, 0.8);
		border-radius: 50%;
		animation: spin 0.5s infinite;
	}

	.spinner-text {
		margin-top: 5px;
		font-size: 1.25em;
	}

  .modal-info {
		position: absolute;
		right: 40px;
		top: 5px;
		font-size: 1.20em;
		padding: 4px 5px;
	}

	.modal-info:hover .modal-info-content {
		opacity: 1;
		pointer-events: inherit;
	}

	.modal-info-content {
		position: absolute;
		bottom: 150%;
		right: 0;
		background: rgba(0, 0, 0, 0.5);
		border-radius: 4px;
		opacity: 0;
		padding: 6px;
		font-size: 0.85rem;
		width: 250px;
		transition: 0.2s;
		pointer-events: none;
	}

	.modal-info-title {
		margin: 0;
		margin-bottom: 5px;
		font-size: 1.40rem;
	}

	@keyframes spin {
		from {
			transform: rotate(0deg);
		}
		to {
			transform: rotate(360deg);
		}
	}
`);

(() => {
    'use strict';

	// UI component by @Guava_Thrower, ash

	const html = `
    <button class="btn">Servers</button>
    <div class="modal">
      <h1 class="modal-title">Servers</h1>
      <button class="modal-close-btn"></button>
      <span class="modal-info">
        ?
        <div class="modal-info-content">
          <div class="modal-info-title">Developers</div>
          <div>UI - Guava_Thrower, ash</div>
          <div>Server list - ash</div>
          <br>
          <div>v${GM_info.script.version}</div>
        </div>
      </span>
      <div class="modal-element modal-loader">
        <div class="spinner"></div>
        <div class="spinner-text">Fetching...</div>
      </div>
    </div>
  `;

	const container = document.createElement("div");
  container.classList.add('container');
	container.innerHTML = html;
	document.body.appendChild(container);

	const modalEl = document.querySelector(".modal");
	const btnEl = document.querySelector(".btn");
	const closeBtnEl = document.querySelector(".modal-close-btn");
	const loaderEl = document.querySelector(".modal-loader");

	btnEl.onclick = () => {
	    container.classList.toggle("active");
	}

	closeBtnEl.onclick = () => {
	    closeModal();
	}

	const closeModal = () => {
		container.classList.remove("active");
	}
  
  window.addEventListener('click', (evt) => {
      if (evt.target === container) {
          closeModal();
      }
  });
  
  window.addEventListener('keydown', (evt) => {
      if (evt.code === 'Tab') {
          container.classList.toggle('hidden');
        
          if (container.classList.contains('active')) {
            container.classList.remove('active');
          }
      }
  });

	// listing servers by @ash

	const Config = {
	    script: {
	        m28nOverride: false,
	        socket: null,
	        currentId: '',
	        ids: []
	    }
	};

	const fetchServers = async () => {
	    let url = "https://api.n.m28.io";

	    try {
	        let response = await fetch(`${url}/endpoint/florrio/findEach/`);
	        let body = await response.json();

	        if (body.hasOwnProperty("servers")) {
	        	loaderEl.style.display = "none";
	            Object.entries(body.servers).forEach(([key, val]) => {
	                if (!Config.script.ids.includes(val.id)) {

	                    let el = document.createElement("div");
	                    el.classList.add("modal-element");
	                    el.innerHTML = key;

	                    Config.script.ids.push(val.id);

	                    el.setAttribute('data-value', JSON.stringify(val));
	                    if (val.id === Config.script.currentId) {
	                        el.classList.add("modal-element-active");
	                    }

	                    el.onclick = () => {
	                        connectServer();
	                        let activeEl = document.querySelector(".modal-element-active");
	                        activeEl.classList.remove("modal-element-active");
	                        el.classList.add("modal-element-active");
                          closeModal();
	                    }

	                    modalEl.appendChild(el);

	                }
	            });
	        }
	    } catch (err) {
	        console.error(err);
	    }
	}

	const findServerPreferenceProxy = new Proxy(unsafeWindow.m28n.findServerPreference, {
	    apply(_target, _thisArgs, args) {
	        if (Config.script.m28nOverride) {
	            var el = document.querySelector(".modal-element-active");
	            args[1](null, [JSON.parse(el.getAttribute("data-value"))]);
	            return;
	        }
	        return Reflect.apply(...arguments);
	    }
	})

	unsafeWindow.m28n.findServerPreference = findServerPreferenceProxy;

	const WebSocketProxy = new Proxy(unsafeWindow.WebSocket, {
	    construct(Target, args) {
	        const instance = Reflect.construct(...arguments);

	        const messageHandler = (e) => {
	            let buffer = new DataView(e.data);
	            if (buffer.getUint8(0) === 1) {
	                instance.removeEventListener("message", messageHandler);
	                Config.script.socket = instance;
	                Config.script.currentId = instance.url.match(/wss:\/\/(\w{4})\./)[1];
	                fetchServers();
	            }
	        }

	        instance.addEventListener("message", messageHandler);

	        return instance;
	    }
	});

	unsafeWindow.WebSocket = WebSocketProxy;

	const connectServer = () => {
	    Config.script.m28nOverride = true;
	    Config.script.socket.close();
	}
})();

QingJ © 2025

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