Compare commits
20 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 056a98b6a9 | |||
| 045eb29b18 | |||
| 059a0d23f6 | |||
| b2e1338597 | |||
| 0ec7710840 | |||
| 3a7534c4eb | |||
| 3dc4ad4bb4 | |||
| 954bc4d622 | |||
| 187abfcbf5 | |||
| 5adb7f5752 | |||
| 81165484b8 | |||
| 280b0647a0 | |||
| 88d22d8d08 | |||
| 522551fc76 | |||
| 3be6b0b9a4 | |||
| f0e197ee8a | |||
| 519bb5161e | |||
| 395a26f023 | |||
| cc1b6115ce | |||
| 6915b8d807 |
187
BungeeCord-Chrome/background.js
Normal file
187
BungeeCord-Chrome/background.js
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
// background.js
|
||||||
|
const POLL_INTERVAL_SEC = 2;
|
||||||
|
|
||||||
|
// Alarm erstellen
|
||||||
|
chrome.runtime.onInstalled.addListener(() => {
|
||||||
|
chrome.alarms.create('periodicRefresh', { periodInMinutes: POLL_INTERVAL_SEC / 60 });
|
||||||
|
});
|
||||||
|
|
||||||
|
chrome.alarms.onAlarm.addListener((alarm) => {
|
||||||
|
if (alarm.name === 'periodicRefresh') {
|
||||||
|
refreshAllServers().catch(console.error);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Message Listener
|
||||||
|
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
|
||||||
|
if (msg && msg.cmd === 'refreshNow') {
|
||||||
|
refreshAllServers()
|
||||||
|
.then(statuses => sendResponse({ ok: true, statuses }))
|
||||||
|
.catch(err => sendResponse({ ok: false, error: String(err) }));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function refreshAllServers() {
|
||||||
|
const obj = await chrome.storage.local.get('servers');
|
||||||
|
const servers = obj.servers || [];
|
||||||
|
if (servers.length === 0) {
|
||||||
|
await chrome.storage.local.set({ serverStatuses: {} });
|
||||||
|
chrome.action.setBadgeText({ text: '' });
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = {};
|
||||||
|
let totalOnlinePlayers = 0;
|
||||||
|
|
||||||
|
const tasks = servers.map(async (srv) => {
|
||||||
|
try {
|
||||||
|
const data = await fetchServerStatus(srv, 2000);
|
||||||
|
results[srv.id] = { ok: true, server: srv, fetched: Date.now(), data };
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
if (Array.isArray(data.players)) totalOnlinePlayers += data.players.length;
|
||||||
|
else if (typeof data.players === 'number') totalOnlinePlayers += data.players;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
results[srv.id] = { ok: false, server: srv, fetched: Date.now(), error: String(e) };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.all(tasks);
|
||||||
|
await chrome.storage.local.set({ serverStatuses: results });
|
||||||
|
|
||||||
|
const badgeText = totalOnlinePlayers > 0 ? String(totalOnlinePlayers) : '';
|
||||||
|
chrome.action.setBadgeText({ text: badgeText });
|
||||||
|
chrome.action.setBadgeBackgroundColor({ color: '#3b82f6' });
|
||||||
|
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchServerStatus(server, timeoutMs = 2000) {
|
||||||
|
let serverObj = typeof server === 'string' ? { id: 'legacy', url: server } : server;
|
||||||
|
let fetchUrl = serverObj.url || '';
|
||||||
|
|
||||||
|
// 1. Direkte URL-Abfrage
|
||||||
|
if (fetchUrl) {
|
||||||
|
if (!/^https?:\/\//i.test(fetchUrl)) fetchUrl = 'http://' + fetchUrl;
|
||||||
|
try {
|
||||||
|
const startTime = performance.now();
|
||||||
|
const controller = new AbortController();
|
||||||
|
const id = setTimeout(() => controller.abort(), timeoutMs);
|
||||||
|
|
||||||
|
const resp = await fetch(fetchUrl, { method: 'GET', signal: controller.signal });
|
||||||
|
clearTimeout(id);
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const latency = Math.round(endTime - startTime);
|
||||||
|
|
||||||
|
if (resp.ok) {
|
||||||
|
let text = await resp.text();
|
||||||
|
const lastBrace = text.lastIndexOf('}');
|
||||||
|
if (lastBrace !== -1) text = text.substring(0, lastBrace + 1);
|
||||||
|
text = text.trim();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(text);
|
||||||
|
if (parsed && (typeof parsed.online !== 'undefined' || Array.isArray(parsed.players) || parsed.version)) {
|
||||||
|
parsed.ping = latency; // Ping hinzufügen
|
||||||
|
return parsed;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Parsing fehlgeschlagen → Fallback-Objekt mit Ping
|
||||||
|
return {
|
||||||
|
online: false,
|
||||||
|
players: [],
|
||||||
|
max_players: 0,
|
||||||
|
version: 'unknown',
|
||||||
|
motd: '',
|
||||||
|
ping: latency > timeoutMs ? null : latency // bei Timeout kein sinnvoller Ping
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// HTTP nicht ok → Offline mit gemessener Latenz (falls unter Timeout)
|
||||||
|
return {
|
||||||
|
online: false,
|
||||||
|
players: [],
|
||||||
|
max_players: 0,
|
||||||
|
version: 'unknown',
|
||||||
|
motd: '',
|
||||||
|
ping: latency > timeoutMs ? null : latency
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
// Timeout oder Netzwerkfehler
|
||||||
|
return {
|
||||||
|
online: false,
|
||||||
|
players: [],
|
||||||
|
max_players: 0,
|
||||||
|
version: 'unknown',
|
||||||
|
motd: '',
|
||||||
|
ping: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. WordPress AJAX-Abfrage
|
||||||
|
const wpSite = serverObj.wpSite ? String(serverObj.wpSite).replace(/\/$/, '') : null;
|
||||||
|
const wpServerId = serverObj.wpServerId ? String(serverObj.wpServerId) : null;
|
||||||
|
|
||||||
|
if (wpSite && wpServerId) {
|
||||||
|
try {
|
||||||
|
const ajaxUrl = wpSite + '/wp-admin/admin-ajax.php';
|
||||||
|
const body = 'action=mcss_fetch&server_id=' + encodeURIComponent(wpServerId);
|
||||||
|
|
||||||
|
const startTime = performance.now();
|
||||||
|
const controller2 = new AbortController();
|
||||||
|
const id2 = setTimeout(() => controller2.abort(), timeoutMs);
|
||||||
|
|
||||||
|
const resp2 = await fetch(ajaxUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||||
|
body,
|
||||||
|
signal: controller2.signal
|
||||||
|
});
|
||||||
|
clearTimeout(id2);
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
const latency = Math.round(endTime - startTime);
|
||||||
|
|
||||||
|
if (!resp2.ok) throw new Error('WP AJAX HTTP ' + resp2.status);
|
||||||
|
|
||||||
|
const json = await resp2.json();
|
||||||
|
if (json && (typeof json.online !== 'undefined' || Array.isArray(json.players) || json.version)) {
|
||||||
|
json.ping = latency; // Ping hinzufügen
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
// JSON nicht im erwarteten Format
|
||||||
|
return {
|
||||||
|
online: false,
|
||||||
|
players: [],
|
||||||
|
max_players: 0,
|
||||||
|
version: 'unknown',
|
||||||
|
motd: '',
|
||||||
|
ping: latency > timeoutMs ? null : latency
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
return {
|
||||||
|
online: false,
|
||||||
|
players: [],
|
||||||
|
max_players: 0,
|
||||||
|
version: 'unknown',
|
||||||
|
motd: '',
|
||||||
|
ping: null
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keine gültige Konfiguration
|
||||||
|
return {
|
||||||
|
online: false,
|
||||||
|
players: [],
|
||||||
|
max_players: 0,
|
||||||
|
version: 'unknown',
|
||||||
|
motd: '',
|
||||||
|
ping: null
|
||||||
|
};
|
||||||
|
}
|
||||||
BIN
BungeeCord-Chrome/icons/icon128.png
Normal file
BIN
BungeeCord-Chrome/icons/icon128.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
BungeeCord-Chrome/icons/icon16.png
Normal file
BIN
BungeeCord-Chrome/icons/icon16.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
BIN
BungeeCord-Chrome/icons/icon48.png
Normal file
BIN
BungeeCord-Chrome/icons/icon48.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 72 KiB |
25
BungeeCord-Chrome/manifest.json
Normal file
25
BungeeCord-Chrome/manifest.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"manifest_version": 3,
|
||||||
|
"name": "Minecraft Bungee Status",
|
||||||
|
"description": "Zeigt den Live-Status mehrerer Bungee/Proxy-Server an. Fallback auf WordPress AJAX möglich.",
|
||||||
|
"version": "1.1.0",
|
||||||
|
"action": {
|
||||||
|
"default_popup": "popup.html",
|
||||||
|
"default_icon": {
|
||||||
|
"16": "icons/icon16.png",
|
||||||
|
"48": "icons/icon48.png",
|
||||||
|
"128": "icons/icon128.png"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"background": {
|
||||||
|
"service_worker": "background.js"
|
||||||
|
},
|
||||||
|
"permissions": [
|
||||||
|
"storage",
|
||||||
|
"alarms"
|
||||||
|
],
|
||||||
|
"host_permissions": [
|
||||||
|
"http://*/*",
|
||||||
|
"https://*/*"
|
||||||
|
]
|
||||||
|
}
|
||||||
220
BungeeCord-Chrome/popup.css
Normal file
220
BungeeCord-Chrome/popup.css
Normal file
@@ -0,0 +1,220 @@
|
|||||||
|
:root {
|
||||||
|
--bg: #111827;
|
||||||
|
--panel: #0b1220;
|
||||||
|
--text: #e6eef8;
|
||||||
|
--muted: #9aa7b3;
|
||||||
|
--accent: #3b82f6;
|
||||||
|
--danger: #ef4444;
|
||||||
|
--radius: 8px;
|
||||||
|
--online: #22c55e;
|
||||||
|
--offline: #ef4444;
|
||||||
|
}
|
||||||
|
|
||||||
|
* { box-sizing:border-box; }
|
||||||
|
html,body { margin:0; padding:0; font-family:Inter, Arial, sans-serif; background:var(--bg); color:var(--text);}
|
||||||
|
.root { width:520px; max-width:520px; padding:12px; }
|
||||||
|
|
||||||
|
header { display:flex; align-items:center; justify-content:space-between; margin-bottom:8px; }
|
||||||
|
header h1 { font-size:16px; margin:0; }
|
||||||
|
header .actions button { background:transparent; border:1px solid var(--muted); color:var(--text); padding:4px 8px; border-radius:6px; cursor:pointer; }
|
||||||
|
|
||||||
|
.main {
|
||||||
|
display:flex;
|
||||||
|
gap:12px;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-list {
|
||||||
|
width:45%;
|
||||||
|
background:var(--panel);
|
||||||
|
padding:8px;
|
||||||
|
border-radius:var(--radius);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail {
|
||||||
|
flex:1;
|
||||||
|
background:var(--panel);
|
||||||
|
padding:8px;
|
||||||
|
border-radius:var(--radius);
|
||||||
|
min-height:220px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail.full-width {
|
||||||
|
width: 100%;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-form { display:flex; flex-direction:column; gap:6px; margin-bottom:8px; }
|
||||||
|
.add-form input { padding:6px 8px; border-radius:6px; border:1px solid #21303b; background:#071019; color:var(--text); }
|
||||||
|
.add-form button { padding:6px 8px; border-radius:6px; border:none; background:var(--accent); color:white; cursor:pointer; }
|
||||||
|
|
||||||
|
#serversContainer { list-style:none; margin:0; padding:0; max-height:260px; overflow:auto; }
|
||||||
|
.server-item {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
justify-content:space-between;
|
||||||
|
padding:6px;
|
||||||
|
margin-bottom:6px;
|
||||||
|
border-radius:6px;
|
||||||
|
cursor:pointer;
|
||||||
|
background: rgba(255,255,255,0.02);
|
||||||
|
transition: background 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-item:hover { background: rgba(255,255,255,0.05); }
|
||||||
|
|
||||||
|
.server-item .meta { display:flex; flex-direction:column; }
|
||||||
|
.server-item .meta .name { font-weight:600; }
|
||||||
|
.server-item .meta .url { font-size:12px; color:var(--muted); }
|
||||||
|
.server-item .status-bubble {
|
||||||
|
font-size:12px;
|
||||||
|
padding:3px 6px;
|
||||||
|
border-radius:999px;
|
||||||
|
color:#fff;
|
||||||
|
transition: background-color 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.placeholder { color:var(--muted); padding:12px; }
|
||||||
|
.hidden { display:none; }
|
||||||
|
|
||||||
|
/* --- Detail Header (Zeile 1) --- */
|
||||||
|
.detail-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-identity h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.server-url {
|
||||||
|
display: block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: var(--muted);
|
||||||
|
margin-top: 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-wrapper {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 8px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Pulsierender Punkt Animation --- */
|
||||||
|
.pulsing-dot {
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 50%;
|
||||||
|
background-color: var(--offline); /* Standard Offline Rot */
|
||||||
|
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7);
|
||||||
|
animation: pulse-red 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
.pulsing-dot.online {
|
||||||
|
background-color: var(--online); /* Online Grün */
|
||||||
|
animation: pulse-green 2s infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-green {
|
||||||
|
0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); }
|
||||||
|
70% { box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); }
|
||||||
|
100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes pulse-red {
|
||||||
|
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); }
|
||||||
|
70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); }
|
||||||
|
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* --- Detail Stats (Zeile 2) --- */
|
||||||
|
.detail-stats {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
padding: 8px;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------------------------- */
|
||||||
|
|
||||||
|
.playerList {
|
||||||
|
list-style:none;
|
||||||
|
padding-left:16px;
|
||||||
|
max-height:140px;
|
||||||
|
overflow:auto;
|
||||||
|
margin:6px 0;
|
||||||
|
display:flex;
|
||||||
|
flex-wrap:wrap;
|
||||||
|
gap:8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.playerList li {
|
||||||
|
display:flex;
|
||||||
|
align-items:center;
|
||||||
|
gap:4px;
|
||||||
|
font-size:13px;
|
||||||
|
color:var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-avatar {
|
||||||
|
width:24px;
|
||||||
|
height:24px;
|
||||||
|
border-radius:4px;
|
||||||
|
transition: transform 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.player-avatar:hover { transform: scale(1.1); }
|
||||||
|
|
||||||
|
.detailButtons {
|
||||||
|
display:flex;
|
||||||
|
gap:8px;
|
||||||
|
margin-top:8px;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailButtons button {
|
||||||
|
padding:6px 8px;
|
||||||
|
border-radius:6px;
|
||||||
|
border:none;
|
||||||
|
cursor:pointer;
|
||||||
|
transition: opacity 0.2s ease, transform 0.1s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detailButtons button:hover { opacity: 0.9; transform: translateY(-1px); }
|
||||||
|
.detailButtons button:active { transform: translateY(0); }
|
||||||
|
|
||||||
|
#btnEdit { background: #f59e0b; color:#000; }
|
||||||
|
#btnDelete { background: var(--danger); color:#fff; }
|
||||||
|
|
||||||
|
#serversContainer::-webkit-scrollbar,
|
||||||
|
.playerList::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serversContainer::-webkit-scrollbar-track,
|
||||||
|
.playerList::-webkit-scrollbar-track {
|
||||||
|
background: rgba(255,255,255,0.05);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serversContainer::-webkit-scrollbar-thumb,
|
||||||
|
.playerList::-webkit-scrollbar-thumb {
|
||||||
|
background: var(--muted);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#serversContainer::-webkit-scrollbar-thumb:hover,
|
||||||
|
.playerList::-webkit-scrollbar-thumb:hover {
|
||||||
|
background: var(--accent);
|
||||||
|
}
|
||||||
68
BungeeCord-Chrome/popup.html
Normal file
68
BungeeCord-Chrome/popup.html
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="de">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||||
|
<title>Bungee Status</title>
|
||||||
|
<link rel="stylesheet" href="popup.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="root">
|
||||||
|
<header>
|
||||||
|
<h1>Bungee Status</h1>
|
||||||
|
<div class="actions">
|
||||||
|
<button id="btnRefresh" title="Jetzt aktualisieren">↻</button>
|
||||||
|
<button id="btnToggleSettings">⚙</button>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<section class="main">
|
||||||
|
<aside class="server-list">
|
||||||
|
<div class="add-form" id="settingsForm">
|
||||||
|
<input id="inputName" placeholder="Name (z. B. Lobby)" />
|
||||||
|
<input id="inputUrl" placeholder="URL (127.0.0.1:9191)" />
|
||||||
|
<input id="inputWpSite" placeholder="WP Site" />
|
||||||
|
<input id="inputWpServerId" placeholder="WP Server ID" />
|
||||||
|
<button id="btnAddServer">Hinzufügen</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ul id="serversContainer"></ul>
|
||||||
|
</aside>
|
||||||
|
|
||||||
|
<div class="detail">
|
||||||
|
<div id="noSelection" class="placeholder">Wähle einen Server aus der linken Liste.</div>
|
||||||
|
<div id="detailContent" class="detailContent hidden">
|
||||||
|
|
||||||
|
<!-- NEUES LAYOUT ZEILE 1: Name, URL, Status (mit Pulsierendem Punkt) -->
|
||||||
|
<div class="detail-header">
|
||||||
|
<div class="server-identity">
|
||||||
|
<h2 id="detailName"></h2>
|
||||||
|
<span id="detailUrlText" class="server-url"></span>
|
||||||
|
</div>
|
||||||
|
<div class="status-wrapper">
|
||||||
|
<div id="detailPulse" class="pulsing-dot"></div>
|
||||||
|
<span id="detailStatus"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- NEUES LAYOUT ZEILE 2: Spieler, Version, Ping -->
|
||||||
|
<div class="detail-stats">
|
||||||
|
<div><strong>Spieler:</strong> <span id="detailPlayers"></span></div>
|
||||||
|
<div><strong>Version:</strong> <span id="detailVersion"></span></div>
|
||||||
|
<div><strong>Ping:</strong> <span id="detailPing">-</span></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3>Online Spieler</h3>
|
||||||
|
<ul id="detailPlayerList" class="playerList"></ul>
|
||||||
|
|
||||||
|
<div class="detailButtons">
|
||||||
|
<button id="btnEdit">Bearbeiten</button>
|
||||||
|
<button id="btnDelete">Löschen</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
<script src="popup.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
384
BungeeCord-Chrome/popup.js
Normal file
384
BungeeCord-Chrome/popup.js
Normal file
@@ -0,0 +1,384 @@
|
|||||||
|
const $ = id => document.getElementById(id);
|
||||||
|
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
|
||||||
|
|
||||||
|
const inputName = $('inputName');
|
||||||
|
const inputUrl = $('inputUrl');
|
||||||
|
const inputWpSite = $('inputWpSite');
|
||||||
|
const inputWpServerId = $('inputWpServerId');
|
||||||
|
const btnAdd = $('btnAddServer');
|
||||||
|
const serversContainer = $('serversContainer');
|
||||||
|
const serverListPanel = document.querySelector('.server-list');
|
||||||
|
const btnRefresh = $('btnRefresh');
|
||||||
|
const btnToggleSettings = $('btnToggleSettings');
|
||||||
|
const settingsForm = $('settingsForm');
|
||||||
|
|
||||||
|
const noSelection = $('noSelection');
|
||||||
|
const detailContent = $('detailContent');
|
||||||
|
const detailName = $('detailName');
|
||||||
|
const detailUrlText = $('detailUrlText');
|
||||||
|
const detailStatus = $('detailStatus');
|
||||||
|
const detailPulse = $('detailPulse');
|
||||||
|
const detailVersion = $('detailVersion');
|
||||||
|
const detailPlayers = $('detailPlayers');
|
||||||
|
const detailPing = $('detailPing');
|
||||||
|
const detailPlayerList = $('detailPlayerList');
|
||||||
|
const btnEdit = $('btnEdit');
|
||||||
|
const btnDelete = $('btnDelete');
|
||||||
|
|
||||||
|
let servers = [];
|
||||||
|
let selectedId = null;
|
||||||
|
let statuses = {};
|
||||||
|
let previousStatuses = {};
|
||||||
|
let settingsVisible = false;
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', init);
|
||||||
|
btnAdd.addEventListener('click', handleAdd);
|
||||||
|
btnRefresh.addEventListener('click', manualRefresh);
|
||||||
|
btnToggleSettings.addEventListener('click', toggleSettings);
|
||||||
|
btnEdit.addEventListener('click', handleEdit);
|
||||||
|
btnDelete.addEventListener('click', handleDelete);
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
await loadServersFromStorage();
|
||||||
|
await loadStatusesFromStorage();
|
||||||
|
await loadSettingsVisibility();
|
||||||
|
renderServerList();
|
||||||
|
|
||||||
|
if (servers.length === 1) selectedId = servers[0].id;
|
||||||
|
renderDetail(selectedId);
|
||||||
|
|
||||||
|
adjustDetailLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Settings Visibility ---
|
||||||
|
async function loadSettingsVisibility() {
|
||||||
|
const obj = await chrome.storage.local.get(['settingsVisible']);
|
||||||
|
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false;
|
||||||
|
if (!settingsVisible) settingsForm.classList.add('hidden');
|
||||||
|
else settingsForm.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSettingsVisibility() {
|
||||||
|
await chrome.storage.local.set({ settingsVisible });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function toggleSettings() {
|
||||||
|
settingsVisible = !settingsVisible;
|
||||||
|
settingsForm.classList.toggle('hidden');
|
||||||
|
await saveSettingsVisibility();
|
||||||
|
adjustDetailLayout();
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Storage ---
|
||||||
|
async function loadServersFromStorage() {
|
||||||
|
const obj = await chrome.storage.local.get(['servers']);
|
||||||
|
servers = obj.servers || [];
|
||||||
|
for (const s of servers) if (!s.id) s.id = uid();
|
||||||
|
await chrome.storage.local.set({ servers });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveServersToStorage() {
|
||||||
|
await chrome.storage.local.set({ servers });
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadStatusesFromStorage() {
|
||||||
|
const obj = await chrome.storage.local.get(['serverStatuses']);
|
||||||
|
statuses = obj.serverStatuses || {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Layout ---
|
||||||
|
function adjustDetailLayout() {
|
||||||
|
const settingsHidden = settingsForm.classList.contains('hidden');
|
||||||
|
detailContent.classList.toggle('full-width', settingsHidden);
|
||||||
|
serverListPanel.classList.toggle('hidden', settingsHidden);
|
||||||
|
btnEdit.style.display = settingsHidden ? 'none' : 'inline-block';
|
||||||
|
btnDelete.style.display = settingsHidden ? 'none' : 'inline-block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Render Server List ---
|
||||||
|
function renderServerList() {
|
||||||
|
serversContainer.innerHTML = '';
|
||||||
|
if (servers.length === 0) {
|
||||||
|
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const s of servers) {
|
||||||
|
const item = document.createElement('li');
|
||||||
|
item.className = 'server-item';
|
||||||
|
item.dataset.id = s.id;
|
||||||
|
|
||||||
|
const meta = document.createElement('div');
|
||||||
|
meta.className = 'meta';
|
||||||
|
const name = document.createElement('div');
|
||||||
|
name.className = 'name';
|
||||||
|
name.textContent = s.name || '(Kein Name)';
|
||||||
|
|
||||||
|
let listUrl = s.url || (s.wpSite ? s.wpSite + ' (WP)' : '');
|
||||||
|
listUrl = listUrl.replace(':9191', '');
|
||||||
|
|
||||||
|
const url = document.createElement('div');
|
||||||
|
url.className = 'url';
|
||||||
|
url.textContent = listUrl;
|
||||||
|
meta.append(name, url);
|
||||||
|
|
||||||
|
const statusBubble = document.createElement('div');
|
||||||
|
statusBubble.className = 'status-bubble';
|
||||||
|
statusBubble.textContent = '—';
|
||||||
|
statusBubble.style.backgroundColor = 'transparent';
|
||||||
|
item.statusBubble = statusBubble;
|
||||||
|
|
||||||
|
item.append(meta, statusBubble);
|
||||||
|
item.addEventListener('click', () => {
|
||||||
|
selectedId = s.id;
|
||||||
|
renderDetail(selectedId);
|
||||||
|
});
|
||||||
|
|
||||||
|
serversContainer.appendChild(item);
|
||||||
|
}
|
||||||
|
updateServerListStatuses(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Render Detail ---
|
||||||
|
function renderDetail(id) {
|
||||||
|
if (!id) {
|
||||||
|
noSelection.classList.remove('hidden');
|
||||||
|
detailContent.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const srv = servers.find(x => x.id === id);
|
||||||
|
if (!srv) {
|
||||||
|
noSelection.classList.remove('hidden');
|
||||||
|
detailContent.classList.add('hidden');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
noSelection.classList.add('hidden');
|
||||||
|
detailContent.classList.remove('hidden');
|
||||||
|
|
||||||
|
detailName.textContent = srv.name || 'Unbenannter Server';
|
||||||
|
|
||||||
|
let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal');
|
||||||
|
urlToShow = urlToShow.replace(':9191', '');
|
||||||
|
|
||||||
|
detailUrlText.textContent = urlToShow;
|
||||||
|
|
||||||
|
updateDetailForServer(srv, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Update Detail ---
|
||||||
|
function updateDetailForServer(srv, force = false) {
|
||||||
|
const st = statuses[srv.id];
|
||||||
|
const prevSt = previousStatuses[srv.id];
|
||||||
|
|
||||||
|
const statusChanged = force ||
|
||||||
|
!prevSt ||
|
||||||
|
(prevSt.ok !== st?.ok) ||
|
||||||
|
(prevSt.data?.online !== st?.data?.online);
|
||||||
|
|
||||||
|
if (!st || !st.ok || !st.data) {
|
||||||
|
if (statusChanged) {
|
||||||
|
detailStatus.textContent = 'Offline';
|
||||||
|
detailPulse.classList.remove('online');
|
||||||
|
detailVersion.textContent = '-';
|
||||||
|
detailPlayers.textContent = '-';
|
||||||
|
detailPing.textContent = '-';
|
||||||
|
updatePlayerList([]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
const d = st.data;
|
||||||
|
|
||||||
|
if (statusChanged) {
|
||||||
|
detailStatus.textContent = d.online ? 'Online' : 'Offline';
|
||||||
|
if (d.online) {
|
||||||
|
detailPulse.classList.add('online');
|
||||||
|
} else {
|
||||||
|
detailPulse.classList.remove('online');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const newVersion = d.version || 'unknown';
|
||||||
|
if (force || detailVersion.textContent !== newVersion) {
|
||||||
|
detailVersion.textContent = newVersion;
|
||||||
|
}
|
||||||
|
|
||||||
|
const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0);
|
||||||
|
const maxPlayers = d.max_players;
|
||||||
|
let newPlayersText = String(playersCount);
|
||||||
|
if (maxPlayers && maxPlayers !== '-1') {
|
||||||
|
newPlayersText += ` / ${maxPlayers}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force || detailPlayers.textContent !== newPlayersText) {
|
||||||
|
detailPlayers.textContent = newPlayersText;
|
||||||
|
}
|
||||||
|
|
||||||
|
let pingVal = d.ping || d.latency || '-';
|
||||||
|
if (pingVal !== '-' && typeof pingVal === 'number') {
|
||||||
|
pingVal = pingVal + ' ms';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) {
|
||||||
|
detailPing.textContent = pingVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentPlayers = Array.isArray(d.players) ? d.players : [];
|
||||||
|
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : [];
|
||||||
|
|
||||||
|
let playersChanged = false;
|
||||||
|
if (force) {
|
||||||
|
playersChanged = true;
|
||||||
|
} else {
|
||||||
|
playersChanged = JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (playersChanged) {
|
||||||
|
updatePlayerList(currentPlayers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Spielerliste ---
|
||||||
|
function updatePlayerList(players) {
|
||||||
|
detailPlayerList.innerHTML = '';
|
||||||
|
if (!players || players.length === 0) {
|
||||||
|
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (const p of players) {
|
||||||
|
const li = document.createElement('li');
|
||||||
|
const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p);
|
||||||
|
|
||||||
|
if (typeof p === 'object' && p.avatar) {
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = p.avatar;
|
||||||
|
img.className = 'player-avatar';
|
||||||
|
img.title = name;
|
||||||
|
li.appendChild(img);
|
||||||
|
} else {
|
||||||
|
// Falls kein Avatar vorhanden, generiere einen von mc-heads.net
|
||||||
|
const img = document.createElement('img');
|
||||||
|
img.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/32`;
|
||||||
|
img.className = 'player-avatar';
|
||||||
|
img.title = name;
|
||||||
|
li.appendChild(img);
|
||||||
|
}
|
||||||
|
|
||||||
|
detailPlayerList.appendChild(li);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Update Server List Statuses ---
|
||||||
|
function updateServerListStatuses() {
|
||||||
|
const items = serversContainer.querySelectorAll('.server-item');
|
||||||
|
items.forEach(item => {
|
||||||
|
const s = servers.find(x => x.id === item.dataset.id);
|
||||||
|
if (!s) return;
|
||||||
|
|
||||||
|
const st = statuses[s.id];
|
||||||
|
const prevSt = previousStatuses[s.id];
|
||||||
|
|
||||||
|
const statusChanged = !prevSt ||
|
||||||
|
(prevSt.ok !== st?.ok) ||
|
||||||
|
(prevSt.data?.online !== st?.data?.online);
|
||||||
|
|
||||||
|
if (!statusChanged) return;
|
||||||
|
|
||||||
|
if (!st || !st.ok || !st.data) {
|
||||||
|
item.statusBubble.textContent = 'Offline';
|
||||||
|
item.statusBubble.style.backgroundColor = 'var(--offline)';
|
||||||
|
} else if (st.data.online) {
|
||||||
|
item.statusBubble.textContent = 'Online';
|
||||||
|
item.statusBubble.style.backgroundColor = 'var(--online)';
|
||||||
|
} else {
|
||||||
|
item.statusBubble.textContent = 'Offline';
|
||||||
|
item.statusBubble.style.backgroundColor = 'var(--offline)';
|
||||||
|
}
|
||||||
|
|
||||||
|
previousStatuses[s.id] = st ? JSON.parse(JSON.stringify(st)) : null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Add / Edit / Delete ---
|
||||||
|
async function handleAdd() {
|
||||||
|
const name = inputName.value.trim();
|
||||||
|
const url = inputUrl.value.trim();
|
||||||
|
const wpSite = inputWpSite.value.trim();
|
||||||
|
const wpServerId = inputWpServerId.value.trim();
|
||||||
|
|
||||||
|
if (!url && !wpSite) {
|
||||||
|
alert('Bitte URL oder WP Site angeben');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const s = {
|
||||||
|
id: uid(),
|
||||||
|
name: name || url || wpSite,
|
||||||
|
url: url || null,
|
||||||
|
wpSite: wpSite || null,
|
||||||
|
wpServerId: wpServerId || null
|
||||||
|
};
|
||||||
|
|
||||||
|
servers.push(s);
|
||||||
|
await saveServersToStorage();
|
||||||
|
|
||||||
|
inputName.value = '';
|
||||||
|
inputUrl.value = '';
|
||||||
|
inputWpSite.value = '';
|
||||||
|
inputWpServerId.value = '';
|
||||||
|
|
||||||
|
renderServerList();
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleEdit() {
|
||||||
|
if (!selectedId) return;
|
||||||
|
const srv = servers.find(s => s.id === selectedId);
|
||||||
|
if (!srv) return;
|
||||||
|
|
||||||
|
const newName = prompt('Name:', srv.name) || srv.name;
|
||||||
|
const newUrl = prompt('URL:', srv.url || '') || srv.url;
|
||||||
|
const newWpSite = prompt('WP Site:', srv.wpSite || '') || srv.wpSite;
|
||||||
|
const newWpServerId = prompt('WP Server ID:', srv.wpServerId || '') || srv.wpServerId;
|
||||||
|
|
||||||
|
srv.name = newName.trim();
|
||||||
|
srv.url = newUrl.trim();
|
||||||
|
srv.wpSite = newWpSite.trim();
|
||||||
|
srv.wpServerId = newWpServerId.trim();
|
||||||
|
|
||||||
|
await saveServersToStorage();
|
||||||
|
renderServerList();
|
||||||
|
renderDetail(selectedId);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete() {
|
||||||
|
if (!selectedId) return;
|
||||||
|
if (!confirm('Server wirklich löschen?')) return;
|
||||||
|
|
||||||
|
servers = servers.filter(s => s.id !== selectedId);
|
||||||
|
selectedId = null;
|
||||||
|
|
||||||
|
await saveServersToStorage();
|
||||||
|
renderServerList();
|
||||||
|
renderDetail(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function manualRefresh() {
|
||||||
|
try {
|
||||||
|
chrome.runtime.sendMessage({ cmd: 'refreshNow' });
|
||||||
|
} catch(e) {
|
||||||
|
console.error('Refresh fehlgeschlagen:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- Storage Listener ---
|
||||||
|
chrome.storage.onChanged.addListener((changes, area) => {
|
||||||
|
if (area === 'local' && changes.serverStatuses) {
|
||||||
|
statuses = changes.serverStatuses.newValue || {};
|
||||||
|
updateServerListStatuses();
|
||||||
|
if (selectedId) {
|
||||||
|
const srv = servers.find(s => s.id === selectedId);
|
||||||
|
if (srv) updateDetailForServer(srv);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
<?php
|
<?php
|
||||||
/**
|
/*
|
||||||
* Plugin Name: Minecraft BungeeCord Status – Network Edition
|
* Plugin Name: Minecraft BungeeCord Status – Network Edition
|
||||||
* Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix).
|
* Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix).
|
||||||
* Tags: minecraft, bungeecord, server status, player list
|
* Tags: minecraft, bungeecord, server status, player list
|
||||||
* Version: 3.6.0 (Final Border Fix)
|
* Version: 3.6.1
|
||||||
* Author: M_Viper
|
* Author: M_Viper
|
||||||
* Requires at least: 6.0
|
* Requires at least: 6.0
|
||||||
* Requires PHP: 7.4
|
* Requires PHP: 7.4
|
||||||
@@ -16,6 +16,187 @@ define('MCSS_URL', plugin_dir_url(__FILE__));
|
|||||||
|
|
||||||
require_once MCSS_DIR . 'rcon/Rcon.php';
|
require_once MCSS_DIR . 'rcon/Rcon.php';
|
||||||
|
|
||||||
|
/* ---------------- HELPER: MINECRAFT COLORS ---------------- */
|
||||||
|
function mcss_format_minecraft_colors($text) {
|
||||||
|
if (empty($text)) return '';
|
||||||
|
|
||||||
|
// Minecraft Color Map
|
||||||
|
$color_map = [
|
||||||
|
'0' => '#000000', // Black
|
||||||
|
'1' => '#0000AA', // Dark Blue
|
||||||
|
'2' => '#00AA00', // Dark Green
|
||||||
|
'3' => '#00AAAA', // Dark Aqua
|
||||||
|
'4' => '#AA0000', // Dark Red
|
||||||
|
'5' => '#AA00AA', // Dark Purple
|
||||||
|
'6' => '#FFAA00', // Gold
|
||||||
|
'7' => '#AAAAAA', // Gray
|
||||||
|
'8' => '#555555', // Dark Gray
|
||||||
|
'9' => '#5555FF', // Blue
|
||||||
|
'a' => '#55FF55', // Green
|
||||||
|
'b' => '#55FFFF', // Aqua
|
||||||
|
'c' => '#FF5555', // Red
|
||||||
|
'd' => '#FF55FF', // Light Purple
|
||||||
|
'e' => '#FFFF55', // Yellow
|
||||||
|
'f' => '#FFFFFF' // White
|
||||||
|
];
|
||||||
|
|
||||||
|
// Base Wrapper
|
||||||
|
$formatted = '<span style="color:#AAAAAA;">'; // Standard Grau
|
||||||
|
|
||||||
|
// Ersetze Farben: &c -> </span><span style="color:#HEX">
|
||||||
|
foreach ($color_map as $char => $hex) {
|
||||||
|
$text = str_replace("&" . $char, "</span><span style=\"color:$hex;\">", $text);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Formatierungen
|
||||||
|
$text = str_replace("&l", "</span><span style=\"font-weight:bold;\">", $text); // Bold
|
||||||
|
$text = str_replace("&o", "</span><span style=\"font-style:italic;\">", $text); // Italic
|
||||||
|
$text = str_replace("&n", "</span><span style=\"text-decoration:underline;\">", $text); // Underline
|
||||||
|
$text = str_replace("&m", "</span><span style=\"text-decoration:line-through;\">", $text); // Strikethrough
|
||||||
|
$text = str_replace("&r", "</span><span style=\"color:#AAAAAA;\">", $text); // Reset to Gray
|
||||||
|
|
||||||
|
$formatted .= $text . '</span>';
|
||||||
|
return $formatted;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* ---------------- AUTO UPDATE (MCSS) ---------------- */
|
||||||
|
if ( ! class_exists( 'MCSS_Auto_Update' ) ) {
|
||||||
|
class MCSS_Auto_Update {
|
||||||
|
|
||||||
|
private $plugin_file;
|
||||||
|
private $repo_owner = 'M_Viper';
|
||||||
|
private $repo_name = 'Minecraft-BungeeCord-Status';
|
||||||
|
private $api_url;
|
||||||
|
private $transient_key;
|
||||||
|
|
||||||
|
public function __construct( $plugin_file ) {
|
||||||
|
$this->plugin_file = $plugin_file;
|
||||||
|
$this->api_url = 'https://git.viper.ipv64.net/api/v1/repos/' . rawurlencode( $this->repo_owner ) . '/' . rawurlencode( $this->repo_name ) . '/releases';
|
||||||
|
$this->transient_key = 'mcss_update_check_' . md5( $this->repo_owner . '/' . $this->repo_name );
|
||||||
|
|
||||||
|
if ( is_admin() ) {
|
||||||
|
add_action( 'admin_init', array( $this, 'check_for_update' ) );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function check_for_update() {
|
||||||
|
if ( ! function_exists( 'get_file_data' ) ) {
|
||||||
|
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||||
|
}
|
||||||
|
|
||||||
|
$current = get_file_data( $this->plugin_file, array( 'Version' => 'Version' ), 'plugin' );
|
||||||
|
$current_version = isset( $current['Version'] ) ? $current['Version'] : '0.0.0';
|
||||||
|
|
||||||
|
$latest = $this->get_latest_release_info();
|
||||||
|
|
||||||
|
if ( $latest && ! empty( $latest['version'] ) && ! empty( $latest['url'] ) ) {
|
||||||
|
if ( version_compare( $latest['version'], $current_version, '>' ) ) {
|
||||||
|
add_action( 'admin_notices', function() use ( $latest, $current_version ) {
|
||||||
|
?>
|
||||||
|
<div class="notice notice-warning is-dismissible">
|
||||||
|
<p>
|
||||||
|
<strong>Minecraft BungeeCord Status – Update verfügbar</strong><br>
|
||||||
|
Neue Version: <strong><?php echo esc_html( $latest['version'] ); ?></strong><br>
|
||||||
|
Installiert: <strong><?php echo esc_html( $current_version ); ?></strong><br>
|
||||||
|
<a href="<?php echo esc_url( $latest['url'] ); ?>" class="button button-primary" target="_blank" rel="noreferrer noopener">Direkter Download (ZIP)</a>
|
||||||
|
<a href="<?php echo esc_url( 'https://git.viper.ipv64.net/' . rawurlencode( $this->repo_owner ) . '/' . rawurlencode( $this->repo_name ) . '/releases' ); ?>" class="button" target="_blank" style="margin-left:8px;" rel="noreferrer noopener">Releases ansehen</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<?php
|
||||||
|
} );
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private function get_latest_release_info() {
|
||||||
|
$cached = get_transient( $this->transient_key );
|
||||||
|
if ( false !== $cached && is_array( $cached ) && ! empty( $cached['version'] ) ) {
|
||||||
|
return $cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = false;
|
||||||
|
|
||||||
|
$response = wp_remote_get( $this->api_url, array(
|
||||||
|
'timeout' => 8,
|
||||||
|
'headers' => array(
|
||||||
|
'Accept' => 'application/json',
|
||||||
|
'User-Agent' => 'MCSS-Update-Checker/1.0',
|
||||||
|
),
|
||||||
|
) );
|
||||||
|
|
||||||
|
if ( is_wp_error( $response ) ) {
|
||||||
|
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$code = wp_remote_retrieve_response_code( $response );
|
||||||
|
if ( 200 !== (int) $code ) {
|
||||||
|
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$body = wp_remote_retrieve_body( $response );
|
||||||
|
$json = json_decode( $body, true );
|
||||||
|
|
||||||
|
if ( ! is_array( $json ) || empty( $json ) ) {
|
||||||
|
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), HOUR_IN_SECONDS );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ( $json as $release ) {
|
||||||
|
$tag = '';
|
||||||
|
if ( ! empty( $release['tag_name'] ) ) {
|
||||||
|
$tag = ltrim( (string) $release['tag_name'], 'vV' );
|
||||||
|
} elseif ( ! empty( $release['name'] ) ) {
|
||||||
|
$tag = ltrim( (string) $release['name'], 'vV' );
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( ! empty( $release['assets'] ) && is_array( $release['assets'] ) ) {
|
||||||
|
foreach ( $release['assets'] as $asset ) {
|
||||||
|
if ( empty( $asset['name'] ) || empty( $asset['browser_download_url'] ) ) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( strtolower( $asset['name'] ) === 'minecraft-bungeecord-status.zip' ) {
|
||||||
|
$version = $tag ?: $this->extract_version_from_string( $asset['name'] . ' ' . ( $release['name'] ?? '' ) );
|
||||||
|
if ( $version ) {
|
||||||
|
$result = array(
|
||||||
|
'version' => $version,
|
||||||
|
'url' => $asset['browser_download_url'],
|
||||||
|
);
|
||||||
|
break 2;
|
||||||
|
} else {
|
||||||
|
$result = array(
|
||||||
|
'version' => ( $tag ?: (string) ( $release['name'] ?? '' ) ),
|
||||||
|
'url' => $asset['browser_download_url'],
|
||||||
|
);
|
||||||
|
break 2;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ( $result ) {
|
||||||
|
set_transient( $this->transient_key, $result, 12 * HOUR_IN_SECONDS );
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
set_transient( $this->transient_key, array( 'version' => '', 'url' => '' ), 6 * HOUR_IN_SECONDS );
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function extract_version_from_string( $str ) {
|
||||||
|
if ( preg_match( '/([0-9]+\.[0-9]+(?:\.[0-9]+)?)/', $str, $m ) ) {
|
||||||
|
return $m[1];
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new MCSS_Auto_Update( __FILE__ );
|
||||||
|
}
|
||||||
|
|
||||||
/* ---------------- Assets ---------------- */
|
/* ---------------- Assets ---------------- */
|
||||||
add_action('admin_enqueue_scripts', function($hook){
|
add_action('admin_enqueue_scripts', function($hook){
|
||||||
global $pagenow;
|
global $pagenow;
|
||||||
@@ -30,8 +211,8 @@ add_action('admin_enqueue_scripts', function($hook){
|
|||||||
});
|
});
|
||||||
|
|
||||||
add_action('wp_enqueue_scripts', function(){
|
add_action('wp_enqueue_scripts', function(){
|
||||||
wp_enqueue_style('mcss-style', MCSS_URL . 'css/style.css', [], '3.6.0');
|
wp_enqueue_style('mcss-style', MCSS_URL . 'css/style.css', [], '3.8.1');
|
||||||
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '3.6.0', true);
|
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '3.8.1', true);
|
||||||
});
|
});
|
||||||
|
|
||||||
/* ---------------- Settings ---------------- */
|
/* ---------------- Settings ---------------- */
|
||||||
@@ -62,8 +243,8 @@ function mcss_sanitize_servers($input) {
|
|||||||
'host' => sanitize_text_field($srv['host'] ?? ''),
|
'host' => sanitize_text_field($srv['host'] ?? ''),
|
||||||
'rcon_port' => absint($srv['rcon_port'] ?? 25577),
|
'rcon_port' => absint($srv['rcon_port'] ?? 25577),
|
||||||
'rcon_pass' => sanitize_text_field($srv['rcon_pass'] ?? ''),
|
'rcon_pass' => sanitize_text_field($srv['rcon_pass'] ?? ''),
|
||||||
'player_port' => sanitize_text_field($srv['player_port'] ?? '9191'), // API Port
|
'player_port' => sanitize_text_field($srv['player_port'] ?? '9191'),
|
||||||
'player_port_copy' => sanitize_text_field($srv['player_port_copy'] ?? ''), // Display Port
|
'player_port_copy' => sanitize_text_field($srv['player_port_copy'] ?? ''),
|
||||||
'copy_address' => sanitize_text_field($srv['copy_address'] ?? ''),
|
'copy_address' => sanitize_text_field($srv['copy_address'] ?? ''),
|
||||||
'hide_port' => !empty($srv['hide_port']),
|
'hide_port' => !empty($srv['hide_port']),
|
||||||
'show_motd' => !empty($srv['show_motd']),
|
'show_motd' => !empty($srv['show_motd']),
|
||||||
@@ -71,14 +252,12 @@ function mcss_sanitize_servers($input) {
|
|||||||
'logo_id' => absint($srv['logo_id'] ?? 0),
|
'logo_id' => absint($srv['logo_id'] ?? 0),
|
||||||
'logo_url' => esc_url_raw($srv['logo_url'] ?? ''),
|
'logo_url' => esc_url_raw($srv['logo_url'] ?? ''),
|
||||||
'custom_text' => wp_kses_post($srv['custom_text'] ?? ''),
|
'custom_text' => wp_kses_post($srv['custom_text'] ?? ''),
|
||||||
// Styles
|
|
||||||
'ip_color' => sanitize_hex_color($srv['ip_color'] ?? '#1f2937'),
|
'ip_color' => sanitize_hex_color($srv['ip_color'] ?? '#1f2937'),
|
||||||
'ct_color' => sanitize_hex_color($srv['ct_color'] ?? '#1e293b'),
|
'ct_color' => sanitize_hex_color($srv['ct_color'] ?? '#1e293b'),
|
||||||
'ip_size' => sanitize_text_field($srv['ip_size'] ?? '1.5em'),
|
'ip_size' => sanitize_text_field($srv['ip_size'] ?? '1.5em'),
|
||||||
'ct_size' => sanitize_text_field($srv['ct_size'] ?? '1.05em'),
|
'ct_size' => sanitize_text_field($srv['ct_size'] ?? '1.05em'),
|
||||||
'name_color' => sanitize_hex_color($srv['name_color'] ?? '#333333'),
|
'name_color' => sanitize_hex_color($srv['name_color'] ?? '#333333'),
|
||||||
'name_size' => sanitize_text_field($srv['name_size'] ?? '1.8em'),
|
'name_size' => sanitize_text_field($srv['name_size'] ?? '1.8em'),
|
||||||
// Events & Maintenance
|
|
||||||
'maintenance_mode' => !empty($srv['maintenance_mode']),
|
'maintenance_mode' => !empty($srv['maintenance_mode']),
|
||||||
'maintenance_message' => wp_kses_post($srv['maintenance_message'] ?? 'Wartung'),
|
'maintenance_message' => wp_kses_post($srv['maintenance_message'] ?? 'Wartung'),
|
||||||
'announcement_enabled' => !empty($srv['announcement_enabled']),
|
'announcement_enabled' => !empty($srv['announcement_enabled']),
|
||||||
@@ -86,7 +265,6 @@ function mcss_sanitize_servers($input) {
|
|||||||
'announcement_start' => sanitize_text_field($srv['announcement_start'] ?? ''),
|
'announcement_start' => sanitize_text_field($srv['announcement_start'] ?? ''),
|
||||||
'announcement_end' => sanitize_text_field($srv['announcement_end'] ?? ''),
|
'announcement_end' => sanitize_text_field($srv['announcement_end'] ?? ''),
|
||||||
'announcement_type' => sanitize_text_field($srv['announcement_type'] ?? 'info'),
|
'announcement_type' => sanitize_text_field($srv['announcement_type'] ?? 'info'),
|
||||||
// Ranks
|
|
||||||
'ranks_json' => mcss_sanitize_ranks($srv['ranks_json'] ?? '[]'),
|
'ranks_json' => mcss_sanitize_ranks($srv['ranks_json'] ?? '[]'),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
@@ -136,7 +314,6 @@ function mcss_settings_page() {
|
|||||||
$servers = [['id'=>'default', 'name'=>'Mein Netzwerk', 'host'=>'127.0.0.1', 'player_port'=>'9191', 'cache_ttl'=>10, 'hide_port'=>true, 'show_motd'=>true]];
|
$servers = [['id'=>'default', 'name'=>'Mein Netzwerk', 'host'=>'127.0.0.1', 'player_port'=>'9191', 'cache_ttl'=>10, 'hide_port'=>true, 'show_motd'=>true]];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Vollständige Liste
|
|
||||||
$font_sizes = [
|
$font_sizes = [
|
||||||
'0.7em'=>'Sehr klein','0.85em'=>'Klein','1em'=>'Normal','1.2em'=>'Etwas größer',
|
'0.7em'=>'Sehr klein','0.85em'=>'Klein','1em'=>'Normal','1.2em'=>'Etwas größer',
|
||||||
'1.4em'=>'Groß','1.5em'=>'Sehr groß','1.7em'=>'Extra groß','2em'=>'Riesig',
|
'1.4em'=>'Groß','1.5em'=>'Sehr groß','1.7em'=>'Extra groß','2em'=>'Riesig',
|
||||||
@@ -149,7 +326,7 @@ function mcss_settings_page() {
|
|||||||
<div class="wrap">
|
<div class="wrap">
|
||||||
<h1>BungeeCord Netzwerk Einstellungen</h1>
|
<h1>BungeeCord Netzwerk Einstellungen</h1>
|
||||||
<p style="color:#d97706;background:#fef3c7;padding:10px;border-radius:5px;">
|
<p style="color:#d97706;background:#fef3c7;padding:10px;border-radius:5px;">
|
||||||
<strong>Final:</strong> Expliziter `border: none` im Inline-Style für Wartungsmodus.
|
<strong>Wichtig:</strong> Bitte das Plugin StatusAPI.jar im Bungeecord Installieren.
|
||||||
</p>
|
</p>
|
||||||
<form method="post" action="options.php">
|
<form method="post" action="options.php">
|
||||||
<?php settings_fields('mcss_settings_group'); ?>
|
<?php settings_fields('mcss_settings_group'); ?>
|
||||||
@@ -365,17 +542,37 @@ function mcss_fetch_server_with_ranks($srv) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
$players_info = [];
|
$players_info = [];
|
||||||
$user_defined_ranks = json_decode($srv['ranks_json'] ?? '[]', true);
|
|
||||||
if (!is_array($user_defined_ranks)) $user_defined_ranks = [];
|
|
||||||
|
|
||||||
foreach ($api_data['players'] as $name) {
|
foreach ($api_data['players'] as $player_data) {
|
||||||
$rank = 'Spieler';
|
if (is_array($player_data)) {
|
||||||
$color = '#566d8dff';
|
$name = $player_data['name'];
|
||||||
|
$prefix = $player_data['prefix'] ?? '';
|
||||||
|
} else {
|
||||||
|
// Fallback für alte API
|
||||||
|
$name = $player_data;
|
||||||
|
$prefix = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Prefix mit Farben konvertieren
|
||||||
|
$prefix_html = mcss_format_minecraft_colors($prefix);
|
||||||
|
|
||||||
|
// 2. Namen immer Schwarz darstellen (überschreibt eventuelle Farben aus dem Prefix)
|
||||||
|
$name_html = '<span style="color:black;">' . esc_html($name) . '</span>';
|
||||||
|
|
||||||
|
// 3. Zusammenbauen (Nur Abstand, wenn Prefix existiert)
|
||||||
|
if (!empty($prefix_html)) {
|
||||||
|
$display_html = $prefix_html . ' ' . $name_html;
|
||||||
|
} else {
|
||||||
|
$display_html = $name_html;
|
||||||
|
}
|
||||||
|
|
||||||
$players_info[] = [
|
$players_info[] = [
|
||||||
'name' => $name,
|
'name' => $name,
|
||||||
'avatar' => "https://mc-heads.net/avatar/" . rawurlencode($name) . "/64",
|
'avatar' => "https://mc-heads.net/avatar/" . rawurlencode($name) . "/64",
|
||||||
'rank' => $rank,
|
'prefix' => $prefix,
|
||||||
'color' => $color,
|
'display_html' => $display_html,
|
||||||
|
'rank' => $prefix ?: 'Spieler',
|
||||||
|
'color' => '#566d8dff',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -389,7 +586,7 @@ function mcss_fetch_server_with_ranks($srv) {
|
|||||||
'motd' => is_array($api_data['motd']) ? implode(' ', $api_data['motd']) : $api_data['motd']
|
'motd' => is_array($api_data['motd']) ? implode(' ', $api_data['motd']) : $api_data['motd']
|
||||||
];
|
];
|
||||||
|
|
||||||
$fast_cache_ttl = min(2, $srv['cache_tl'] ?? $srv['cache_ttl']);
|
$fast_cache_ttl = max(2, absint($srv['cache_ttl'] ?? 10));
|
||||||
set_transient($cache_key, $result, $fast_cache_ttl);
|
set_transient($cache_key, $result, $fast_cache_ttl);
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
@@ -421,7 +618,7 @@ function mcss_shortcode($atts) {
|
|||||||
}
|
}
|
||||||
if (!$srv) return 'Server nicht gefunden';
|
if (!$srv) return 'Server nicht gefunden';
|
||||||
|
|
||||||
// MAINTENANCE MODE (Rich Style - No Border)
|
// MAINTENANCE MODE
|
||||||
$maintenance_mode = !empty($srv['maintenance_mode']);
|
$maintenance_mode = !empty($srv['maintenance_mode']);
|
||||||
$maintenance_message = $srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!';
|
$maintenance_message = $srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!';
|
||||||
if ($maintenance_mode) {
|
if ($maintenance_mode) {
|
||||||
@@ -439,10 +636,8 @@ function mcss_shortcode($atts) {
|
|||||||
}
|
}
|
||||||
.mcss-status-maintenance { animation: pulse 2s infinite; }
|
.mcss-status-maintenance { animation: pulse 2s infinite; }
|
||||||
</style>
|
</style>
|
||||||
<!-- FIX: border:none hinzugefügt -->
|
|
||||||
<div id="mcss-widget-<?php echo esc_attr($uid); ?>" style="max-width:650px;margin:30px auto;padding:0; background:#fef3c7;border:none;border-radius:20px; overflow:hidden;box-shadow:0 16px 40px rgba(0,0,0,0.12); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
<div id="mcss-widget-<?php echo esc_attr($uid); ?>" style="max-width:650px;margin:30px auto;padding:0; background:#fef3c7;border:none;border-radius:20px; overflow:hidden;box-shadow:0 16px 40px rgba(0,0,0,0.12); font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;">
|
||||||
|
|
||||||
<!-- Rich Header -->
|
|
||||||
<div style="padding:28px 32px;background:#fef3c7;border-bottom:1px solid #fbbf24;display:flex;align-items:center;gap:20px;">
|
<div style="padding:28px 32px;background:#fef3c7;border-bottom:1px solid #fbbf24;display:flex;align-items:center;gap:20px;">
|
||||||
<img src="<?php echo esc_url($logo); ?>" alt="Logo" loading="lazy" style="width:60px;height:60px;border-radius:12px;box-shadow:0 8px 20px rgba(0,0,0,0.15);" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
|
<img src="<?php echo esc_url($logo); ?>" alt="Logo" loading="lazy" style="width:60px;height:60px;border-radius:12px;box-shadow:0 8px 20px rgba(0,0,0,0.15);" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
|
||||||
<div style="flex:1;">
|
<div style="flex:1;">
|
||||||
@@ -453,7 +648,6 @@ function mcss_shortcode($atts) {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- Rich Body -->
|
|
||||||
<div style="padding:24px 32px;background:#fef3c7;">
|
<div style="padding:24px 32px;background:#fef3c7;">
|
||||||
<div style="margin-bottom:16px;font-weight:700;color:#92400e;font-size:1.05em;">Wartungshinweis:</div>
|
<div style="margin-bottom:16px;font-weight:700;color:#92400e;font-size:1.05em;">Wartungshinweis:</div>
|
||||||
<div style="font-size:1.1em;color:#78350f;line-height:1.6;"><?php echo wp_kses_post($maintenance_message); ?></div>
|
<div style="font-size:1.1em;color:#78350f;line-height:1.6;"><?php echo wp_kses_post($maintenance_message); ?></div>
|
||||||
@@ -465,11 +659,10 @@ function mcss_shortcode($atts) {
|
|||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
// NORMAL MODE (No Border)
|
// NORMAL MODE
|
||||||
$data = mcss_fetch_server_with_ranks($srv);
|
$data = mcss_fetch_server_with_ranks($srv);
|
||||||
$uid = md5($srv['host']);
|
$uid = md5($srv['host']);
|
||||||
|
|
||||||
// STYLES
|
|
||||||
$name_color = $srv['name_color'] ?? '#333333';
|
$name_color = $srv['name_color'] ?? '#333333';
|
||||||
$name_size = $srv['name_size'] ?? '1.3em';
|
$name_size = $srv['name_size'] ?? '1.3em';
|
||||||
$ct_color = $srv['ct_color'] ?? '#555555';
|
$ct_color = $srv['ct_color'] ?? '#555555';
|
||||||
@@ -479,13 +672,11 @@ function mcss_shortcode($atts) {
|
|||||||
$logo_size = "70px";
|
$logo_size = "70px";
|
||||||
$player_head_size = "32px";
|
$player_head_size = "32px";
|
||||||
|
|
||||||
// Copy Logic (player_port_copy)
|
|
||||||
$copy_addr = !empty($srv['copy_address']) ? $srv['copy_address'] : $srv['host'];
|
$copy_addr = !empty($srv['copy_address']) ? $srv['copy_address'] : $srv['host'];
|
||||||
if (empty($srv['hide_port']) && !empty($srv['player_port_copy'])) {
|
if (empty($srv['hide_port']) && !empty($srv['player_port_copy'])) {
|
||||||
$copy_addr .= ':' . $srv['player_port_copy'];
|
$copy_addr .= ':' . $srv['player_port_copy'];
|
||||||
}
|
}
|
||||||
|
|
||||||
// ANNOUNCEMENT CHECK
|
|
||||||
$show_announcement = mcss_should_show_announcement($srv);
|
$show_announcement = mcss_should_show_announcement($srv);
|
||||||
$announcement_text = $srv['announcement_text'] ?? '';
|
$announcement_text = $srv['announcement_text'] ?? '';
|
||||||
$announcement_type = $srv['announcement_type'] ?? 'info';
|
$announcement_type = $srv['announcement_type'] ?? 'info';
|
||||||
@@ -499,7 +690,6 @@ function mcss_shortcode($atts) {
|
|||||||
.mcss-announcement { animation: slideDown 0.5s ease-out; }
|
.mcss-announcement { animation: slideDown 0.5s ease-out; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- FIX: border:none hinzugefügt -->
|
|
||||||
<div id="mcss-widget-<?php echo esc_attr($uid); ?>" style="max-width:<?php echo $widget_width; ?>;margin:20px auto;padding:<?php echo $widget_padding; ?>;background:white;border:none;border-radius:10px;box-shadow:0 4px 15px rgba(0,0,0,0.1);font-family:sans-serif;position:relative;">
|
<div id="mcss-widget-<?php echo esc_attr($uid); ?>" style="max-width:<?php echo $widget_width; ?>;margin:20px auto;padding:<?php echo $widget_padding; ?>;background:white;border:none;border-radius:10px;box-shadow:0 4px 15px rgba(0,0,0,0.1);font-family:sans-serif;position:relative;">
|
||||||
|
|
||||||
<!-- POPUP / TOAST -->
|
<!-- POPUP / TOAST -->
|
||||||
@@ -518,7 +708,6 @@ function mcss_shortcode($atts) {
|
|||||||
<div style="display:flex;align-items:center;gap:12px;border-bottom:1px solid #eee;padding-bottom:12px;">
|
<div style="display:flex;align-items:center;gap:12px;border-bottom:1px solid #eee;padding-bottom:12px;">
|
||||||
<img src="<?php echo esc_url($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png'); ?>" style="width:<?php echo $logo_size; ?>;height:<?php echo $logo_size; ?>;border-radius:6px;">
|
<img src="<?php echo esc_url($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png'); ?>" style="width:<?php echo $logo_size; ?>;height:<?php echo $logo_size; ?>;border-radius:6px;">
|
||||||
|
|
||||||
<!-- GEÄNDERTER HEADER: Name -> IP + Status -> Zusatztext -->
|
|
||||||
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
|
<div style="flex:1;display:flex;flex-direction:column;gap:6px;">
|
||||||
|
|
||||||
<!-- NAME -->
|
<!-- NAME -->
|
||||||
@@ -531,13 +720,10 @@ function mcss_shortcode($atts) {
|
|||||||
|
|
||||||
<!-- IP + STATUS -->
|
<!-- IP + STATUS -->
|
||||||
<div style="display:flex;align-items:center;gap:10px;font-size:0.9em;font-weight:600;">
|
<div style="display:flex;align-items:center;gap:10px;font-size:0.9em;font-weight:600;">
|
||||||
|
|
||||||
<!-- IP -->
|
|
||||||
<span style="color:#374151;" id="mcss-ip-<?php echo esc_attr($uid); ?>">
|
<span style="color:#374151;" id="mcss-ip-<?php echo esc_attr($uid); ?>">
|
||||||
<?php echo esc_html($copy_addr); ?>
|
<?php echo esc_html($copy_addr); ?>
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- PULSIERENDER STATUSPUNKT -->
|
|
||||||
<span id="mcss-status-dot-<?php echo esc_attr($uid); ?>"
|
<span id="mcss-status-dot-<?php echo esc_attr($uid); ?>"
|
||||||
class="<?php echo $data['online'] ? 'mcss-status-online' : ''; ?>"
|
class="<?php echo $data['online'] ? 'mcss-status-online' : ''; ?>"
|
||||||
style="width:10px;height:10px;border-radius:50%;
|
style="width:10px;height:10px;border-radius:50%;
|
||||||
@@ -545,14 +731,13 @@ function mcss_shortcode($atts) {
|
|||||||
box-shadow:0 0 8px <?php echo $data['online'] ? 'rgba(16,185,129,.7)' : 'rgba(239,68,68,.7)'; ?>;">
|
box-shadow:0 0 8px <?php echo $data['online'] ? 'rgba(16,185,129,.7)' : 'rgba(239,68,68,.7)'; ?>;">
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<!-- STATUS TEXT (EINDEUTIG) -->
|
|
||||||
<span id="mcss-status-text-<?php echo esc_attr($uid); ?>"
|
<span id="mcss-status-text-<?php echo esc_attr($uid); ?>"
|
||||||
style="color:<?php echo $data['online'] ? '#10b981' : '#ef4444'; ?>;">
|
style="color:<?php echo $data['online'] ? '#10b981' : '#ef4444'; ?>;">
|
||||||
<?php echo $data['online'] ? 'Online' : 'Offline'; ?>
|
<?php echo $data['online'] ? 'Online' : 'Offline'; ?>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ZUSATZTEXT (wird NICHT von JS angefasst) -->
|
<!-- ZUSATZTEXT -->
|
||||||
<?php if (!empty($srv['custom_text'])): ?>
|
<?php if (!empty($srv['custom_text'])): ?>
|
||||||
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="font-size:<?php echo $ct_size; ?>;color:<?php echo $ct_color; ?>;font-weight:500;">
|
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="font-size:<?php echo $ct_size; ?>;color:<?php echo $ct_color; ?>;font-weight:500;">
|
||||||
<?php echo wp_kses_post($srv['custom_text']); ?>
|
<?php echo wp_kses_post($srv['custom_text']); ?>
|
||||||
@@ -577,7 +762,7 @@ function mcss_shortcode($atts) {
|
|||||||
<?php foreach ($data['players'] as $p): ?>
|
<?php foreach ($data['players'] as $p): ?>
|
||||||
<div style="text-align:center;">
|
<div style="text-align:center;">
|
||||||
<img src="<?php echo esc_url($p['avatar']); ?>" style="width:<?php echo $player_head_size; ?>;height:<?php echo $player_head_size; ?>;border-radius:4px;">
|
<img src="<?php echo esc_url($p['avatar']); ?>" style="width:<?php echo $player_head_size; ?>;height:<?php echo $player_head_size; ?>;border-radius:4px;">
|
||||||
<div style="font-size:0.75em;"><?php echo esc_html($p['name']); ?></div>
|
<div style="font-size:0.75em;"><?php echo $p['display_html']; ?></div>
|
||||||
</div>
|
</div>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
<?php else: ?>
|
<?php else: ?>
|
||||||
@@ -605,11 +790,9 @@ function mcss_shortcode($atts) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// SET CLICK ON NAME
|
|
||||||
const nameEl = document.getElementById('mcss-name-<?php echo esc_attr($uid); ?>');
|
const nameEl = document.getElementById('mcss-name-<?php echo esc_attr($uid); ?>');
|
||||||
if(nameEl) nameEl.addEventListener('click', mcss_copy_<?php echo esc_attr($uid); ?>);
|
if(nameEl) nameEl.addEventListener('click', mcss_copy_<?php echo esc_attr($uid); ?>);
|
||||||
|
|
||||||
// CLOSE ANNOUNCEMENT FUNCTION
|
|
||||||
document.querySelectorAll('.mcss-announcement-close').forEach(function(btn){
|
document.querySelectorAll('.mcss-announcement-close').forEach(function(btn){
|
||||||
btn.addEventListener('click', function(e){
|
btn.addEventListener('click', function(e){
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -662,9 +845,10 @@ function mcss_shortcode($atts) {
|
|||||||
var html = '';
|
var html = '';
|
||||||
if (d.players && Array.isArray(d.players) && d.players.length > 0) {
|
if (d.players && Array.isArray(d.players) && d.players.length > 0) {
|
||||||
d.players.forEach(function(p){
|
d.players.forEach(function(p){
|
||||||
|
var content = p.display_html ? p.display_html : p.name;
|
||||||
html += '<div style="text-align:center;">' +
|
html += '<div style="text-align:center;">' +
|
||||||
'<img src="' + (p.avatar ? p.avatar : '') + '" style="width:<?php echo $player_head_size; ?>;height:<?php echo $player_head_size; ?>;border-radius:4px;">' +
|
'<img src="' + (p.avatar ? p.avatar : '') + '" style="width:<?php echo $player_head_size; ?>;height:<?php echo $player_head_size; ?>;border-radius:4px;">' +
|
||||||
'<div style="font-size:0.75em;">' + (p.name ? p.name : '') + '</div>' +
|
'<div style="font-size:0.75em;">' + content + '</div>' +
|
||||||
'</div>';
|
'</div>';
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
@@ -691,4 +875,3 @@ function mcss_shortcode($atts) {
|
|||||||
|
|
||||||
<?php return ob_get_clean();
|
<?php return ob_get_clean();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
58
README.md
58
README.md
@@ -1,2 +1,58 @@
|
|||||||
# Minecraft-BungeeCord-Status
|
# Minecraft BungeeCord Status
|
||||||
|
|
||||||
|
StatusAPI zeigt den aktuellen Status deines Minecraft-Servers direkt auf deiner Webseite an – inklusive Online/Offline, Version, Ping, Spieleranzahl und Spieler-Avatare.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Zeigt Serverstatus: **Online / Offline**
|
||||||
|
- Zeigt Version und Ping
|
||||||
|
- Zeigt die Anzahl der Spieler und ihre Avatare
|
||||||
|
- Klick auf Servername kopiert die Adresse automatisch
|
||||||
|
- Spielerübersicht wird automatisch alle **2 Sekunden** aktualisiert
|
||||||
|
- Hinweise/Banner können vom Nutzer geschlossen werden
|
||||||
|
- Anpassbare Darstellung für verschiedene Layouts
|
||||||
|
|
||||||
|
## Wichtige Hinweise
|
||||||
|
|
||||||
|
### BungeeCord Plugin
|
||||||
|
Das Plugin **StatusAPI.jar** muss in den BungeeCord Plugin-Ordner kopiert werden, damit die API korrekt funktioniert.
|
||||||
|
|
||||||
|
### WordPress Integration
|
||||||
|
In WordPress muss der API-Port auf **9191** eingestellt sein, damit die Serverdaten korrekt abgerufen werden.
|
||||||
|
Stelle sicher, dass dein Server und die Webseite auf diesen Port zugreifen können.
|
||||||
|
|
||||||
|
### Shortcode für WordPress
|
||||||
|
|
||||||
|
Um den Serverstatus auf deiner WordPress-Seite anzuzeigen, füge einfach folgenden Shortcode ein:
|
||||||
|
|
||||||
|
```html
|
||||||
|
[bungeecord_status id="Bungeecord"]
|
||||||
|
```
|
||||||
|
|
||||||
|
|
||||||
|
Ersetze `"Bungeecord"` durch die ID deines Servers, falls mehrere Server eingebunden werden.
|
||||||
|
|
||||||
|
## Beispielanzeige
|
||||||
|
|
||||||
|
**Status:** Online
|
||||||
|
**Version:** 1.8 - 1.21
|
||||||
|
**Ping:** 1 ms
|
||||||
|
**Spieler:** 1
|
||||||
|
|
||||||
|
**Avatare:**
|
||||||
|
 <!-- Platzhalter – wird im Plugin dynamisch geladen -->
|
||||||
|
|
||||||
|
> Der Abstand zwischen dem Text „Spieler:“ und den Avataren ist bewusst etwas größer, um die Übersichtlichkeit zu erhöhen.
|
||||||
|
|
||||||
|
## Nutzung
|
||||||
|
|
||||||
|
1. Shortcode an der gewünschten Stelle einfügen
|
||||||
|
2. Der Serverstatus aktualisiert sich automatisch, sodass die angezeigten Spieler und der Ping immer aktuell sind
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
Bei Problemen überprüfe bitte:
|
||||||
|
|
||||||
|
- Ob das Plugin **StatusAPI.jar** korrekt im BungeeCord Plugin-Ordner liegt
|
||||||
|
- Ob der API-Port in WordPress korrekt auf **9191** eingestellt ist
|
||||||
|
- Ob der Server erreichbar ist und Spieler online sind
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
<groupId>net.viper.bungee</groupId>
|
<groupId>net.viper.bungee</groupId>
|
||||||
<artifactId>StatusAPI</artifactId>
|
<artifactId>StatusAPI</artifactId>
|
||||||
<version>1.0</version>
|
<version>3.6.2</version>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>StatusAPI</name>
|
<name>StatusAPI</name>
|
||||||
@@ -20,13 +20,22 @@
|
|||||||
</properties>
|
</properties>
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<!-- BungeeCord API (lokal installiert) -->
|
<!-- BungeeCord API -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.md-5</groupId>
|
<groupId>net.md-5</groupId>
|
||||||
<artifactId>bungeecord-api</artifactId>
|
<artifactId>bungeecord-api</artifactId>
|
||||||
<version>1.20</version>
|
<version>1.20</version>
|
||||||
<scope>provided</scope>
|
<scope>provided</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- LuckPerms API (Optional) -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.luckperms</groupId>
|
||||||
|
<artifactId>api</artifactId>
|
||||||
|
<version>5.4</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
59
StatusAPI/src/main/java/net/viper/status/FileDownloader.java
Normal file
59
StatusAPI/src/main/java/net/viper/status/FileDownloader.java
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package net.viper.status;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.io.BufferedInputStream;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* FileDownloader: Lädt Dateien asynchron herunter (CMILib Style).
|
||||||
|
*/
|
||||||
|
public class FileDownloader {
|
||||||
|
|
||||||
|
private final Plugin plugin;
|
||||||
|
|
||||||
|
public FileDownloader(Plugin plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Lädt eine Datei herunter.
|
||||||
|
* @param urlString Die Download URL
|
||||||
|
* @param destination Die Zieldatei
|
||||||
|
* @param onSuccess Callback, der im Hauptthread ausgeführt wird, wenn fertig.
|
||||||
|
*/
|
||||||
|
public void downloadFile(String urlString, File destination, Runnable onSuccess) {
|
||||||
|
plugin.getProxy().getScheduler().runAsync(plugin, () -> {
|
||||||
|
BufferedInputStream bufferedInputStream = null;
|
||||||
|
FileOutputStream fileOutputStream = null;
|
||||||
|
try {
|
||||||
|
URL url = new URL(urlString);
|
||||||
|
bufferedInputStream = new BufferedInputStream(url.openStream());
|
||||||
|
fileOutputStream = new FileOutputStream(destination);
|
||||||
|
byte[] buffer = new byte[1024];
|
||||||
|
int count;
|
||||||
|
while ((count = bufferedInputStream.read(buffer, 0, 1024)) != -1) {
|
||||||
|
fileOutputStream.write(buffer, 0, count);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Schließen
|
||||||
|
fileOutputStream.close();
|
||||||
|
bufferedInputStream.close();
|
||||||
|
|
||||||
|
// Callback im Main Thread
|
||||||
|
plugin.getProxy().getScheduler().schedule(plugin, onSuccess, 1, java.util.concurrent.TimeUnit.MILLISECONDS);
|
||||||
|
|
||||||
|
} catch (Throwable e) {
|
||||||
|
plugin.getLogger().warning("Download fehlgeschlagen: " + e.getMessage());
|
||||||
|
if (destination.exists()) destination.delete();
|
||||||
|
} finally {
|
||||||
|
if (fileOutputStream != null) try { fileOutputStream.close(); } catch (IOException ignored) {}
|
||||||
|
if (bufferedInputStream != null) try { bufferedInputStream.close(); } catch (IOException ignored) {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,28 +2,63 @@ package net.viper.status;
|
|||||||
|
|
||||||
import net.md_5.bungee.api.ProxyServer;
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
import net.md_5.bungee.api.config.ListenerInfo;
|
import net.md_5.bungee.api.config.ListenerInfo;
|
||||||
|
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||||
import net.md_5.bungee.api.plugin.Plugin;
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintWriter;
|
||||||
import java.net.ServerSocket;
|
import java.net.ServerSocket;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.HashMap;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class StatusAPI extends Plugin implements Runnable {
|
public class StatusAPI extends Plugin implements Runnable {
|
||||||
|
|
||||||
private Thread thread;
|
private Thread thread;
|
||||||
private int port = 9191;
|
private int port = 9191;
|
||||||
|
private UpdateChecker updateChecker;
|
||||||
|
private FileDownloader fileDownloader;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
getLogger().info("StatusAPI wird aktiviert...");
|
getLogger().info("StatusAPI wird aktiviert...");
|
||||||
getLogger().info("Starte Web-Server auf Port " + port + "...");
|
getLogger().info("Starte Web-Server auf Port " + port + "...");
|
||||||
|
|
||||||
thread = new Thread(this);
|
// Start HTTP server thread
|
||||||
|
thread = new Thread(this, "StatusAPI-HTTP-Server");
|
||||||
thread.start();
|
thread.start();
|
||||||
|
|
||||||
|
String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0";
|
||||||
|
updateChecker = new UpdateChecker(this, currentVersion, 6); // 6 Stunden Intervall
|
||||||
|
fileDownloader = new FileDownloader(this);
|
||||||
|
|
||||||
|
File pluginFile = getFile();
|
||||||
|
File backupFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.bak");
|
||||||
|
|
||||||
|
// --- AUTOMATISCHES BACKUP-CLEANUP ---
|
||||||
|
// Falls ein altes Update (.bak) im Ordner liegt, löschen wir es nach 1 Minute Startzeit.
|
||||||
|
// Wenn der Server kurz crashen würde, hast du noch 60 Sekunden Zeit, ihn zu stoppen,
|
||||||
|
// damit das Backup erhalten bleibt. Läuft er stabil, wird der Platz freigegeben.
|
||||||
|
if (backupFile.exists()) {
|
||||||
|
ProxyServer.getInstance().getScheduler().schedule(this, () -> {
|
||||||
|
if (backupFile.exists()) {
|
||||||
|
if (backupFile.delete()) {
|
||||||
|
getLogger().info("Altes Backup (.bak) wurde erfolgreich gelöscht.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, 1, TimeUnit.MINUTES);
|
||||||
|
}
|
||||||
|
// ---------------------------------------
|
||||||
|
|
||||||
|
// Sofortiger Start-Check
|
||||||
|
checkAndMaybeUpdate();
|
||||||
|
|
||||||
|
// Regelmäßiger Check (alle 6 Stunden)
|
||||||
|
ProxyServer.getInstance().getScheduler().schedule(this, this::checkAndMaybeUpdate, 6, 6, TimeUnit.HOURS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -31,6 +66,99 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
getLogger().info("Stoppe Web-Server...");
|
getLogger().info("Stoppe Web-Server...");
|
||||||
if (thread != null) {
|
if (thread != null) {
|
||||||
thread.interrupt();
|
thread.interrupt();
|
||||||
|
try { thread.join(1000); } catch (InterruptedException ignored) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prüft Update und startet Download falls nötig.
|
||||||
|
*/
|
||||||
|
private void checkAndMaybeUpdate() {
|
||||||
|
try {
|
||||||
|
updateChecker.checkNow();
|
||||||
|
String currentVersion = getDescription() != null ? getDescription().getVersion() : "0.0.0";
|
||||||
|
|
||||||
|
if (updateChecker.isUpdateAvailable(currentVersion)) {
|
||||||
|
String newVersion = updateChecker.getLatestVersion();
|
||||||
|
String url = updateChecker.getLatestUrl();
|
||||||
|
getLogger().warning("----------------------------------------");
|
||||||
|
getLogger().warning("Neue Version verfügbar: " + newVersion);
|
||||||
|
getLogger().warning("Starte automatisches Update...");
|
||||||
|
getLogger().warning("----------------------------------------");
|
||||||
|
|
||||||
|
// Download Starten
|
||||||
|
File pluginFile = getFile();
|
||||||
|
File newFile = new File(pluginFile.getParentFile(), "StatusAPI.jar.new");
|
||||||
|
|
||||||
|
fileDownloader.downloadFile(url, newFile, () -> {
|
||||||
|
// Callback: Wenn Download erfolgreich
|
||||||
|
triggerUpdateScript(pluginFile, newFile);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().severe("Fehler beim Update-Check: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Erstellt ein externes Batch-Skript, startet es und stoppt den Server.
|
||||||
|
* Das Skript führt den Datei-Tausch durch, wenn der Server weg ist.
|
||||||
|
*/
|
||||||
|
private void triggerUpdateScript(File currentFile, File newFile) {
|
||||||
|
try {
|
||||||
|
File pluginsFolder = currentFile.getParentFile();
|
||||||
|
// Wir legen das Skript neben die Haupt-JAR (root) damit es nicht im Plugin-Ordner liegt
|
||||||
|
File rootFolder = pluginsFolder.getParentFile();
|
||||||
|
File batFile = new File(rootFolder, "StatusAPI_Update_" + System.currentTimeMillis() + ".bat");
|
||||||
|
|
||||||
|
// Batch Inhalt
|
||||||
|
// 1. Wartet 5 Sekunden (Server fährt runter)
|
||||||
|
// 2. Geht in den Plugin Ordner
|
||||||
|
// 3. Sichert .jar zu .bak
|
||||||
|
// 4. Benennt .new zu .jar um
|
||||||
|
// 5. Löscht sich selbst
|
||||||
|
String batContent = "@echo off\n" +
|
||||||
|
"echo Bitte warten, der Server f\"ahrt herunter...\n" +
|
||||||
|
"timeout /t 5 /nobreak >nul\n" +
|
||||||
|
"cd /d \"" + pluginsFolder.getAbsolutePath().replace("\\", "/") + "\"\n" +
|
||||||
|
"echo Fuehre Datei-Tausch durch...\n" +
|
||||||
|
"if exist StatusAPI.jar.bak del StatusAPI.jar.bak\n" +
|
||||||
|
"if exist StatusAPI.jar (\n" +
|
||||||
|
" ren StatusAPI.jar StatusAPI.jar.bak\n" +
|
||||||
|
")\n" +
|
||||||
|
"if exist StatusAPI.new.jar (\n" +
|
||||||
|
" ren StatusAPI.new.jar StatusAPI.jar\n" +
|
||||||
|
" echo Update erfolgreich!\n" +
|
||||||
|
") else (\n" +
|
||||||
|
" echo FEHLER: StatusAPI.new.jar nicht gefunden!\n" +
|
||||||
|
" pause\n" +
|
||||||
|
")\n" +
|
||||||
|
"del \"%~f0\"";
|
||||||
|
|
||||||
|
try (PrintWriter out = new PrintWriter(batFile)) {
|
||||||
|
out.println(batContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Spieler kicken
|
||||||
|
try {
|
||||||
|
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||||
|
p.disconnect("§cServer f\"ahrt f\"ur ein Update neu herunter. Bitte etwas warten.");
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
|
||||||
|
// Skript starten
|
||||||
|
getLogger().info("Starte Update-Skript im Hintergrund...");
|
||||||
|
try {
|
||||||
|
Runtime.getRuntime().exec("cmd /c start \"Update_Proc\" \"" + batFile.getAbsolutePath() + "\"");
|
||||||
|
} catch (IOException e) {
|
||||||
|
getLogger().warning("Konnte Skript nicht starten. Update wird manuell ben\"otigt.");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Server stoppen
|
||||||
|
ProxyServer.getInstance().stop();
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
getLogger().severe("Fehler beim Vorbereiten des Updates: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -38,80 +166,93 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
public void run() {
|
public void run() {
|
||||||
try (ServerSocket serverSocket = new ServerSocket(port)) {
|
try (ServerSocket serverSocket = new ServerSocket(port)) {
|
||||||
serverSocket.setSoTimeout(1000);
|
serverSocket.setSoTimeout(1000);
|
||||||
|
|
||||||
while (!Thread.interrupted()) {
|
while (!Thread.interrupted()) {
|
||||||
try {
|
try {
|
||||||
Socket clientSocket = serverSocket.accept();
|
Socket clientSocket = serverSocket.accept();
|
||||||
handleConnection(clientSocket);
|
handleConnection(clientSocket);
|
||||||
} catch (java.net.SocketTimeoutException e) {
|
} catch (java.net.SocketTimeoutException e) {}
|
||||||
// Loop Check
|
catch (IOException e) {
|
||||||
} catch (IOException e) {
|
getLogger().warning("Fehler beim Akzeptieren einer Verbindung: " + e.getMessage());
|
||||||
// Ignorieren
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
getLogger().severe("Konnte ServerSocket nicht starten auf Port " + port);
|
getLogger().severe("Konnte ServerSocket nicht starten auf Port " + port + ": " + e.getMessage());
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleConnection(Socket clientSocket) {
|
private void handleConnection(Socket clientSocket) {
|
||||||
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
|
try (BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream(), "UTF-8"));
|
||||||
PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true)) {
|
OutputStream out = clientSocket.getOutputStream()) {
|
||||||
|
|
||||||
String inputLine = in.readLine();
|
String inputLine = in.readLine();
|
||||||
|
|
||||||
if (inputLine != null && inputLine.startsWith("GET")) {
|
if (inputLine != null && inputLine.startsWith("GET")) {
|
||||||
|
Map<String, Object> data = new LinkedHashMap<>();
|
||||||
Map<String, Object> data = new HashMap<>();
|
|
||||||
data.put("online", true);
|
data.put("online", true);
|
||||||
|
|
||||||
// --- VERSION CLEANUP START ---
|
// Version
|
||||||
String versionRaw = ProxyServer.getInstance().getVersion();
|
String versionRaw = ProxyServer.getInstance().getVersion();
|
||||||
String versionClean = versionRaw; // Fallback
|
String versionClean = versionRaw;
|
||||||
|
if (versionRaw != null && versionRaw.contains(":")) {
|
||||||
// Regex um die saubere Version zu extrahieren (z.B. 1.21-R0.5-SNAPSHOT)
|
|
||||||
// Aus: git:BungeeCord-Bootstrap:1.21-R0.5-SNAPSHOT:36e6154:2012
|
|
||||||
if (versionRaw.matches("git-BungeeCord-Bootstrap:.*")) {
|
|
||||||
String[] parts = versionRaw.split(":");
|
String[] parts = versionRaw.split(":");
|
||||||
if(parts.length > 2) {
|
if (parts.length >= 3) versionClean = parts[2].trim();
|
||||||
versionClean = parts[2];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
data.put("version", versionClean);
|
data.put("version", versionClean);
|
||||||
// --- VERSION CLEANUP ENDE ---
|
data.put("max_players", String.valueOf(ProxyServer.getInstance().getConfig().getPlayerLimit()));
|
||||||
|
|
||||||
data.put("max_players", ProxyServer.getInstance().getConfig().getPlayerLimit());
|
|
||||||
|
|
||||||
|
// Motd
|
||||||
String motd = "BungeeCord";
|
String motd = "BungeeCord";
|
||||||
try {
|
try {
|
||||||
ListenerInfo listener = ProxyServer.getInstance().getConfig().getListeners().iterator().next();
|
Iterator<ListenerInfo> it = ProxyServer.getInstance().getConfig().getListeners().iterator();
|
||||||
if (listener != null) {
|
if (it.hasNext()) {
|
||||||
motd = listener.getMotd();
|
ListenerInfo listener = it.next();
|
||||||
|
if (listener != null && listener.getMotd() != null) motd = listener.getMotd();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception ignored) {}
|
||||||
// Fallback
|
|
||||||
}
|
|
||||||
data.put("motd", motd);
|
data.put("motd", motd);
|
||||||
|
|
||||||
List<String> playerNames = new ArrayList<>();
|
// LuckPerms (Optional)
|
||||||
for (net.md_5.bungee.api.connection.ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
boolean luckPermsEnabled = ProxyServer.getInstance().getPluginManager().getPlugin("LuckPerms") != null;
|
||||||
playerNames.add(p.getName());
|
List<Map<String, String>> playersList = new ArrayList<>();
|
||||||
|
for (ProxiedPlayer p : ProxyServer.getInstance().getPlayers()) {
|
||||||
|
Map<String, String> playerInfo = new LinkedHashMap<>();
|
||||||
|
playerInfo.put("name", p.getName());
|
||||||
|
String prefix = "";
|
||||||
|
if (luckPermsEnabled) {
|
||||||
|
try {
|
||||||
|
Class<?> providerClass = Class.forName("net.luckperms.api.LuckPermsProvider");
|
||||||
|
Object luckPermsApi = providerClass.getMethod("get").invoke(null);
|
||||||
|
Object userManager = luckPermsApi.getClass().getMethod("getUserManager").invoke(luckPermsApi);
|
||||||
|
Object user = userManager.getClass().getMethod("getUser", UUID.class).invoke(userManager, p.getUniqueId());
|
||||||
|
if (user != null) {
|
||||||
|
Class<?> queryOptionsClass = Class.forName("net.luckperms.api.query.QueryOptions");
|
||||||
|
Object queryOptions = queryOptionsClass.getMethod("defaultContextualOptions").invoke(null);
|
||||||
|
Object cachedData = user.getClass().getMethod("getCachedData").invoke(user);
|
||||||
|
Object metaData = cachedData.getClass().getMethod("getMetaData", queryOptionsClass).invoke(cachedData, queryOptions);
|
||||||
|
Object result = metaData.getClass().getMethod("getPrefix").invoke(metaData);
|
||||||
|
if (result != null) prefix = (String) result;
|
||||||
|
}
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
playerInfo.put("prefix", prefix);
|
||||||
|
playersList.add(playerInfo);
|
||||||
}
|
}
|
||||||
data.put("players", playerNames);
|
data.put("players", playersList);
|
||||||
|
|
||||||
|
// Response
|
||||||
String json = buildJsonString(data);
|
String json = buildJsonString(data);
|
||||||
|
byte[] jsonBytes = json.getBytes("UTF-8");
|
||||||
out.println("HTTP/1.1 200 OK");
|
StringBuilder response = new StringBuilder();
|
||||||
out.println("Content-Type: application/json");
|
response.append("HTTP/1.1 200 OK\r\n");
|
||||||
out.println("Access-Control-Allow-Origin: *");
|
response.append("Content-Type: application/json; charset=UTF-8\r\n");
|
||||||
out.println("Content-Length: " + json.length());
|
response.append("Access-Control-Allow-Origin: *\r\n");
|
||||||
out.println("Connection: close");
|
response.append("Content-Length: ").append(jsonBytes.length).append("\r\n");
|
||||||
out.println();
|
response.append("Connection: close\r\n\r\n");
|
||||||
out.println(json);
|
out.write(response.toString().getBytes("UTF-8"));
|
||||||
|
out.write(jsonBytes);
|
||||||
|
out.flush();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
getLogger().severe("Fehler beim Verarbeiten der Anfrage: " + e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -121,26 +262,32 @@ public class StatusAPI extends Plugin implements Runnable {
|
|||||||
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
for (Map.Entry<String, Object> entry : data.entrySet()) {
|
||||||
if (!first) sb.append(",");
|
if (!first) sb.append(",");
|
||||||
first = false;
|
first = false;
|
||||||
sb.append("\"").append(entry.getKey()).append("\":");
|
sb.append("\"").append(escapeJson(entry.getKey())).append("\":").append(valueToString(entry.getValue()));
|
||||||
|
|
||||||
Object value = entry.getValue();
|
|
||||||
if (value instanceof List) {
|
|
||||||
sb.append("[");
|
|
||||||
List<?> list = (List<?>) value;
|
|
||||||
for (int i = 0; i < list.size(); i++) {
|
|
||||||
if (i > 0) sb.append(",");
|
|
||||||
sb.append("\"").append(list.get(i)).append("\"");
|
|
||||||
}
|
|
||||||
sb.append("]");
|
|
||||||
} else if (value instanceof String) {
|
|
||||||
sb.append("\"").append(((String) value).replace("\"", "\\\"")).append("\"");
|
|
||||||
} else if (value instanceof Boolean) {
|
|
||||||
sb.append(value);
|
|
||||||
} else {
|
|
||||||
sb.append("\"").append(value).append("\"");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
sb.append("}");
|
sb.append("}");
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String valueToString(Object value) {
|
||||||
|
if (value == null) return "null";
|
||||||
|
else if (value instanceof Boolean) return value.toString();
|
||||||
|
else if (value instanceof List) {
|
||||||
|
StringBuilder sb = new StringBuilder("[");
|
||||||
|
List<?> list = (List<?>) value;
|
||||||
|
for (int i = 0; i < list.size(); i++) {
|
||||||
|
if (i > 0) sb.append(",");
|
||||||
|
Object item = list.get(i);
|
||||||
|
if (item instanceof Map) sb.append(buildJsonString((Map<String, Object>) item));
|
||||||
|
else sb.append("\"").append(escapeJson(String.valueOf(item))).append("\"");
|
||||||
|
}
|
||||||
|
sb.append("]");
|
||||||
|
return sb.toString();
|
||||||
|
}
|
||||||
|
else return "\"" + escapeJson(String.valueOf(value)) + "\"";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String escapeJson(String s) {
|
||||||
|
if (s == null) return "";
|
||||||
|
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "\\r");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
151
StatusAPI/src/main/java/net/viper/status/UpdateChecker.java
Normal file
151
StatusAPI/src/main/java/net/viper/status/UpdateChecker.java
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
package net.viper.status;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.util.logging.Level;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
public class UpdateChecker {
|
||||||
|
|
||||||
|
private final Plugin plugin;
|
||||||
|
private final String currentVersion;
|
||||||
|
private final int intervalHours;
|
||||||
|
|
||||||
|
private final String apiUrl = "https://git.viper.ipv64.net/api/v1/repos/M_Viper/Minecraft-BungeeCord-Status/releases";
|
||||||
|
|
||||||
|
private volatile String latestVersion = "";
|
||||||
|
private volatile String latestUrl = "";
|
||||||
|
|
||||||
|
// Pattern für Dateinamen im Assets-Array
|
||||||
|
private static final Pattern ASSET_NAME_PATTERN = Pattern.compile("\"name\"\\s*:\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE);
|
||||||
|
// Pattern für Download-URL
|
||||||
|
private static final Pattern DOWNLOAD_PATTERN = Pattern.compile("\"browser_download_url\"\\s*:\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE);
|
||||||
|
// Pattern für Tag Version (WICHTIG: Wir suchen global, um das Haupt-Release zu finden)
|
||||||
|
private static final Pattern TAG_NAME_PATTERN = Pattern.compile("\"tag_name\"\\s*:\\s*\"([^\"]+)\"", Pattern.CASE_INSENSITIVE);
|
||||||
|
|
||||||
|
public UpdateChecker(Plugin plugin, String currentVersion, int intervalHours) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
this.currentVersion = currentVersion != null ? currentVersion : "0.0.0";
|
||||||
|
this.intervalHours = Math.max(1, intervalHours);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void checkNow() {
|
||||||
|
try {
|
||||||
|
HttpURLConnection conn = (HttpURLConnection) new URL(apiUrl).openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setRequestProperty("Accept", "application/json");
|
||||||
|
conn.setRequestProperty("User-Agent", "StatusAPI-UpdateChecker/2.0");
|
||||||
|
conn.setConnectTimeout(5000);
|
||||||
|
conn.setReadTimeout(5000);
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code != 200) {
|
||||||
|
plugin.getLogger().warning("Gitea API nicht erreichbar (HTTP " + code + ")");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), "UTF-8"))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) sb.append(line).append("\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
String body = sb.toString();
|
||||||
|
|
||||||
|
// 1. Die LATEST Version (Tag) finden
|
||||||
|
// Wir suchen das erste "tag_name" im gesamten JSON, das ist meistens das neueste Release.
|
||||||
|
String foundVersion = null;
|
||||||
|
Matcher tagM = TAG_NAME_PATTERN.matcher(body);
|
||||||
|
if (tagM.find()) {
|
||||||
|
foundVersion = tagM.group(1).trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundVersion == null) {
|
||||||
|
plugin.getLogger().warning("Keine Version (Tag) im Release gefunden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Version säubern (v vorne entfernen)
|
||||||
|
if (foundVersion.startsWith("v") || foundVersion.startsWith("V")) {
|
||||||
|
foundVersion = foundVersion.substring(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Download URL für StatusAPI.jar finden
|
||||||
|
// Wir suchen das Asset "StatusAPI.jar"
|
||||||
|
String foundUrl = null;
|
||||||
|
|
||||||
|
Pattern releasePattern = Pattern.compile("(?s)\\{.*?\\}");
|
||||||
|
Matcher releaseMatcher = releasePattern.matcher(body);
|
||||||
|
while (releaseMatcher.find()) {
|
||||||
|
String block = releaseMatcher.group();
|
||||||
|
java.util.List<String> names = new java.util.ArrayList<>();
|
||||||
|
java.util.List<String> downloads = new java.util.ArrayList<>();
|
||||||
|
|
||||||
|
Matcher nm = ASSET_NAME_PATTERN.matcher(block);
|
||||||
|
while (nm.find()) names.add(nm.group(1));
|
||||||
|
|
||||||
|
Matcher dm = DOWNLOAD_PATTERN.matcher(block);
|
||||||
|
while (dm.find()) downloads.add(dm.group(1));
|
||||||
|
|
||||||
|
int pairs = Math.min(names.size(), downloads.size());
|
||||||
|
for (int i = 0; i < pairs; i++) {
|
||||||
|
String name = names.get(i);
|
||||||
|
String dl = downloads.get(i);
|
||||||
|
if ("StatusAPI.jar".equalsIgnoreCase(name.trim())) {
|
||||||
|
foundUrl = dl;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (foundUrl != null) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (foundUrl == null) {
|
||||||
|
plugin.getLogger().warning("Keine JAR-Datei für dieses Release gefunden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
plugin.getLogger().info("Gefundene Version: " + foundVersion + " (Aktuell: " + currentVersion + ")");
|
||||||
|
|
||||||
|
latestVersion = foundVersion;
|
||||||
|
latestUrl = foundUrl;
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().log(Level.SEVERE, "Fehler beim Update-Check: " + e.getMessage(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLatestVersion() {
|
||||||
|
return latestVersion != null ? latestVersion : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLatestUrl() {
|
||||||
|
return latestUrl != null ? latestUrl : "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdateAvailable(String currentVer) {
|
||||||
|
String lv = getLatestVersion();
|
||||||
|
if (lv.isEmpty()) return false;
|
||||||
|
return compareVersions(lv, currentVer) > 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private int compareVersions(String a, String b) {
|
||||||
|
try {
|
||||||
|
String[] aa = a.split("\\.");
|
||||||
|
String[] bb = b.split("\\.");
|
||||||
|
int len = Math.max(aa.length, bb.length);
|
||||||
|
for (int i = 0; i < len; i++) {
|
||||||
|
int ai = i < aa.length ? Integer.parseInt(aa[i].replaceAll("\\D","")) : 0;
|
||||||
|
int bi = i < bb.length ? Integer.parseInt(bb[i].replaceAll("\\D","")) : 0;
|
||||||
|
if (ai != bi) return Integer.compare(ai, bi);
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (Exception ex) {
|
||||||
|
return a.compareTo(b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,13 @@
|
|||||||
name: StatusAPI
|
name: StatusAPI
|
||||||
version: 1.0
|
|
||||||
main: net.viper.status.StatusAPI
|
main: net.viper.status.StatusAPI
|
||||||
|
version: 3.6.2
|
||||||
author: M_Viper
|
author: M_Viper
|
||||||
|
description: StatusAPI für BungeeCord inkl. Update-Checker
|
||||||
|
|
||||||
|
softdepend:
|
||||||
|
- LuckPerms
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
statusapi.update.notify:
|
||||||
|
description: 'Erlaubt Update-Benachrichtigungen'
|
||||||
|
default: op
|
||||||
Reference in New Issue
Block a user