Press Tab to open Server Selector.
当前为
// ==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();
}
})();