21 Commits
Author SHA1 Message Date
Git Manager GUI 5eb462e5a7 Upload via Git Manager GUI 2026-07-06 07:40:39 +02:00
Git Manager GUI e5dda9531f Upload via Git Manager GUI 2026-07-06 07:40:37 +02:00
Git Manager GUI b851cbc20b Upload via Git Manager GUI 2026-05-22 11:16:17 +02:00
Git Manager GUI de04e58242 Upload via Git Manager GUI 2026-05-22 11:16:15 +02:00
Git Manager GUI 166bfad1d4 Upload via Git Manager GUI 2026-05-22 11:16:13 +02:00
Git Manager GUI 1de9e4b155 Upload via Git Manager GUI 2026-05-22 11:16:12 +02:00
M_Viper 501be8649a Delete BungeeCord-Chrome/BungeeCord-Chrome.zip via Git Manager GUI 2026-05-22 09:16:02 +00:00
M_Viper 3c80758b12 Minecraft-BungeeCord-Status/minecraft-bungeecord-status.php aktualisiert 2026-04-02 19:38:17 +00:00
Git Manager GUI f7e0dcb23d Upload via Git Manager GUI 2026-04-02 21:13:38 +02:00
Git Manager GUI 65ea93e22d Upload via Git Manager GUI 2026-04-02 20:57:27 +02:00
M_Viper fde1947ae7 README.md aktualisiert 2026-03-01 11:21:36 +00:00
M_Viper 7556521a0e Minecraft-BungeeCord-Status/minecraft-bungeecord-status.php aktualisiert 2026-02-11 19:13:59 +00:00
M_Viper 40c0ed7b0c Minecraft-BungeeCord-Status/minecraft-bungeecord-status.php aktualisiert 2026-02-10 22:31:09 +00:00
M_Viper f407604ff6 Upload manifest.json via GUI 2026-02-09 21:06:02 +00:00
M_Viper 2fce65544b Upload file BungeeCord-Chrome.zip via GUI 2026-02-09 22:06:01 +01:00
M_Viper 1c89b4fdfe Upload background.js via GUI 2026-02-09 21:05:53 +00:00
M_Viper 700e6026b6 Upload popup.js via GUI 2026-02-09 21:05:51 +00:00
M_Viper b4ee8b0564 Upload popup.html via GUI 2026-02-09 21:05:50 +00:00
M_Viper 111158967f Upload popup.css via GUI 2026-02-09 21:05:50 +00:00
M_Viper f7e99f8625 Minecraft-BungeeCord-Status/minecraft-bungeecord-status.php aktualisiert 2026-02-09 20:08:51 +00:00
M_Viper e03ebffb64 README.md aktualisiert 2026-01-08 15:25:50 +00:00
7 changed files with 1546 additions and 908 deletions
+1 -1
View File
@@ -2,7 +2,7 @@
"manifest_version": 3, "manifest_version": 3,
"name": "Minecraft Bungee Status", "name": "Minecraft Bungee Status",
"description": "Zeigt den Live-Status mehrerer Bungee/Proxy-Server an. Fallback auf WordPress AJAX möglich.", "description": "Zeigt den Live-Status mehrerer Bungee/Proxy-Server an. Fallback auf WordPress AJAX möglich.",
"version": "1.1.0", "version": "1.1.1",
"action": { "action": {
"default_popup": "popup.html", "default_popup": "popup.html",
"default_icon": { "default_icon": {
+392 -177
View File
@@ -1,220 +1,435 @@
:root { :root {
--bg: #111827; --bg: #0d1117;
--panel: #0b1220; --panel: #161b27;
--panel2: #111620;
--text: #e6eef8; --text: #e6eef8;
--muted: #9aa7b3; --muted: #5a6a7a;
--muted2: #8899aa;
--accent: #3b82f6; --accent: #3b82f6;
--danger: #ef4444; --radius: 9px;
--radius: 8px;
--online: #22c55e; --online: #22c55e;
--offline: #ef4444; --offline: #ef4444;
--border: rgba(255,255,255,0.07);
} }
* { box-sizing:border-box; } * { 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; } html, body {
header h1 { font-size:16px; margin:0; } margin: 0; padding: 0;
header .actions button { background:transparent; border:1px solid var(--muted); color:var(--text); padding:4px 8px; border-radius:6px; cursor:pointer; } font-family: Inter, Arial, sans-serif;
background: var(--bg);
color: var(--text);
overflow-x: hidden;
}
.root {
width: 560px;
display: flex;
flex-direction: column;
}
/* ── Header ── */
header {
background: var(--panel);
display: flex;
align-items: center;
justify-content: space-between;
padding: 13px 20px;
border-bottom: 1px solid var(--border);
}
header h1 {
font-size: 14px;
font-weight: 700;
margin: 0;
letter-spacing: 0.03em;
color: #fff;
}
header .actions { display: flex; gap: 7px; }
header .actions button {
background: rgba(255,255,255,0.07);
border: 1px solid rgba(255,255,255,0.1);
color: var(--muted2);
border-radius: 7px;
width: 32px; height: 32px;
cursor: pointer;
font-size: 16px;
display: flex; align-items: center; justify-content: center;
transition: background 0.15s, color 0.15s;
}
header .actions button:hover {
background: rgba(255,255,255,0.12);
color: #fff;
}
/* ── Main layout ── */
.main { .main {
display:flex; display: flex;
gap:12px; flex-direction: column;
position: relative;
} }
.server-list { /* ── Settings / add-form panel ── */
width:45%; .settings-panel {
background:var(--panel); background: var(--panel2);
padding:8px; border-bottom: 1px solid var(--border);
border-radius:var(--radius); padding: 14px 20px;
display: flex;
flex-direction: column;
gap: 8px;
}
.settings-panel input {
padding: 8px 12px;
border-radius: 7px;
border: 1px solid rgba(255,255,255,0.09);
background: rgba(0,0,0,0.3);
color: var(--text);
font-size: 13px;
outline: none;
}
.settings-panel input:focus {
border-color: var(--accent);
}
.settings-panel button {
padding: 8px 12px;
border-radius: 7px;
border: none;
background: var(--accent);
color: white;
font-size: 13px;
font-weight: 600;
cursor: pointer;
}
/* ── Server list ── */
.server-list-panel {
background: var(--panel2);
border-bottom: 1px solid var(--border);
padding: 10px 12px;
}
#serversContainer {
list-style: none;
margin: 0; padding: 0;
display: flex;
flex-direction: column;
gap: 4px;
}
.server-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 9px 12px;
border-radius: 8px;
cursor: pointer;
background: transparent;
border: 1px solid transparent;
transition: background 0.15s, border-color 0.15s;
}
.server-item:hover { background: rgba(255,255,255,0.04); }
.server-item.selected { background: rgba(59,130,246,0.08); border-color: rgba(59,130,246,0.25); }
.server-item .meta { display: flex; flex-direction: column; gap: 2px; }
.server-item .meta .name { font-size: 13px; font-weight: 600; color: #fff; }
.server-item .meta .url { font-size: 11px; color: var(--muted); }
.status-bubble {
font-size: 11px;
font-weight: 600;
padding: 3px 10px;
border-radius: 999px;
color: #fff;
background: transparent;
}
/* ── Detail panel ── */
.detail-panel {
padding: 18px 20px;
display: flex;
flex-direction: column;
gap: 16px;
min-height: 200px;
}
.placeholder {
color: var(--muted);
font-size: 13px;
padding: 24px 0;
text-align: center;
}
/* Server identity + online badge */
.srv-identity {
display: flex;
justify-content: space-between;
align-items: flex-start;
}
.srv-name { font-size: 20px; font-weight: 700; color: #fff; margin: 0 0 4px; }
.srv-url { font-size: 12px; color: var(--muted); }
.online-badge {
display: flex;
align-items: center;
gap: 7px;
border-radius: 999px;
padding: 5px 14px;
font-size: 13px;
font-weight: 600;
}
.online-badge.online {
background: rgba(34,197,94,0.12);
border: 1px solid rgba(34,197,94,0.25);
color: #4ade80;
}
.online-badge.offline {
background: rgba(239,68,68,0.1);
border: 1px solid rgba(239,68,68,0.2);
color: #f87171;
}
.pulsing-dot {
width: 7px; height: 7px;
border-radius: 50%;
flex-shrink: 0; 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 { .pulsing-dot.online {
background-color: var(--online); /* Online Grün */ background: var(--online);
animation: pulse-green 2s infinite; animation: pulse-green 2s infinite;
}
.pulsing-dot.offline {
background: var(--offline);
animation: pulse-red 2s infinite;
} }
@keyframes pulse-green { @keyframes pulse-green {
0% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7); } 0%,100% { box-shadow: 0 0 0 0 rgba(34,197,94,0.5); }
70% { box-shadow: 0 0 0 10px rgba(34, 197, 94, 0); } 50% { box-shadow: 0 0 0 5px rgba(34,197,94,0); }
100% { box-shadow: 0 0 0 0 rgba(34, 197, 94, 0); }
} }
@keyframes pulse-red { @keyframes pulse-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.7); } 0%,100% { box-shadow: 0 0 0 0 rgba(239,68,68,0.5); }
70% { box-shadow: 0 0 0 10px rgba(239, 68, 68, 0); } 50% { box-shadow: 0 0 0 5px rgba(239,68,68,0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
} }
/* --- Detail Stats (Zeile 2) --- */ /* MOTD */
.detail-stats { .motd-bar {
display: flex; font-size: 12px;
justify-content: space-between; color: var(--muted2);
align-items: center; background: rgba(255,255,255,0.03);
background: rgba(255,255,255,0.05); border-radius: 8px;
padding: 8px; padding: 8px 14px;
border-radius: var(--radius); border-left: 2px solid rgba(59,130,246,0.35);
margin-bottom: 16px; font-style: italic;
font-size: 13px; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
margin-top: 4px;
} }
/* ---------------------------------- */ /* Stats row – 3 gleich breite Karten */
.stats-row {
.playerList { display: grid;
list-style:none; grid-template-columns: 1fr 1fr 1fr;
padding-left:16px; gap: 10px;
max-height:140px; margin-top: 4px;
overflow:auto;
margin:6px 0;
display:flex;
flex-wrap:wrap;
gap:8px;
} }
.playerList li { .stat-card {
display:flex; background: var(--panel);
align-items:center; border: 1px solid var(--border);
gap:4px; border-radius: var(--radius);
font-size:13px; padding: 12px 16px;
color:var(--text); }
.stat-label {
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.08em;
margin-bottom: 5px;
}
.stat-val {
font-size: 18px;
font-weight: 700;
color: #fff;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.stat-val.small {
font-size: 12px;
padding-top: 4px;
font-weight: 500;
color: var(--muted2);
}
/* Section label */
.section-label {
font-size: 11px;
font-weight: 700;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.1em;
margin-bottom: 9px;
padding-top: 6px;
border-top: 1px solid var(--border);
}
/* Sub-server chips – immer 3 pro Zeile */
.sub-servers {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 8px;
}
.sub-chip {
display: flex;
align-items: center;
gap: 7px;
border-radius: 8px;
padding: 8px 12px;
font-size: 13px;
font-weight: 600;
border: 1px solid var(--border);
background: var(--panel);
color: var(--muted2);
cursor: default;
transition: background 0.15s;
min-width: 0;
}
.sub-chip.active {
background: rgba(59,130,246,0.1);
border-color: rgba(59,130,246,0.3);
color: #93c5fd;
}
.sub-chip-dot {
width: 7px; height: 7px;
border-radius: 50%;
background: #2a3040;
flex-shrink: 0;
}
.sub-chip.active .sub-chip-dot {
background: var(--accent);
}
.sub-chip-name {
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
flex: 1;
min-width: 0;
}
.sub-chip-count {
font-size: 11px;
font-weight: 500;
opacity: 0.65;
flex-shrink: 0;
margin-left: auto;
padding-left: 4px;
}
/* Players grid – 2 Spalten */
.players-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
}
.player-card {
display: flex;
align-items: center;
gap: 12px;
background: var(--panel);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 10px 14px;
transition: background 0.15s;
min-width: 0;
}
.player-card:hover {
background: rgba(255,255,255,0.05);
} }
.player-avatar { .player-avatar {
width:24px; width: 40px; height: 40px;
height:24px; border-radius: 7px;
border-radius:4px; border: 1px solid rgba(255,255,255,0.1);
transition: transform 0.2s ease; flex-shrink: 0;
image-rendering: pixelated;
} }
.player-avatar:hover { transform: scale(1.1); } .player-info { flex: 1; min-width: 0; }
.detailButtons { .player-name {
display:flex; font-size: 14px;
gap:8px; font-weight: 700;
margin-top:8px; color: var(--text);
transition: opacity 0.3s ease; white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
} }
.detailButtons button { .player-prefix {
padding:6px 8px; font-size: 11px;
border-radius:6px; color: var(--muted2);
border:none; margin-bottom: 2px;
cursor:pointer; white-space: nowrap;
transition: opacity 0.2s ease, transform 0.1s ease; overflow: hidden;
text-overflow: ellipsis;
} }
.detailButtons button:hover { opacity: 0.9; transform: translateY(-1px); } .player-server-tag {
.detailButtons button:active { transform: translateY(0); } display: inline-block;
margin-top: 3px;
#btnEdit { background: #f59e0b; color:#000; } font-size: 11px;
#btnDelete { background: var(--danger); color:#fff; } font-weight: 600;
background: rgba(59,130,246,0.18);
#serversContainer::-webkit-scrollbar, color: #93c5fd;
.playerList::-webkit-scrollbar { border-radius: 5px;
width: 6px; padding: 2px 7px;
} }
#serversContainer::-webkit-scrollbar-track, /* Edit/Delete buttons */
.playerList::-webkit-scrollbar-track { .detail-buttons {
background: rgba(255,255,255,0.05); display: flex;
border-radius: 3px; gap: 10px;
padding-top: 10px;
border-top: 1px solid var(--border);
} }
#serversContainer::-webkit-scrollbar-thumb, .detail-buttons button {
.playerList::-webkit-scrollbar-thumb { padding: 7px 16px;
background: var(--muted); border-radius: 7px;
border-radius: 3px; border: none;
cursor: pointer;
font-size: 13px;
font-weight: 600;
} }
#serversContainer::-webkit-scrollbar-thumb:hover, #btnEdit { background: rgba(245,158,11,0.15); color: #fbbf24; border: 1px solid rgba(245,158,11,0.25); }
.playerList::-webkit-scrollbar-thumb:hover { #btnDelete { background: rgba(239,68,68,0.12); color: #f87171; border: 1px solid rgba(239,68,68,0.2); }
background: var(--accent);
} .hidden { display: none !important; }
::-webkit-scrollbar { width: 5px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.08); border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }
+62 -33
View File
@@ -8,60 +8,89 @@
</head> </head>
<body> <body>
<div class="root"> <div class="root">
<header> <header>
<h1>Bungee Status</h1> <h1>Bungee Status</h1>
<div class="actions"> <div class="actions">
<button id="btnRefresh" title="Jetzt aktualisieren">↻</button> <button id="btnRefresh" title="Jetzt aktualisieren">↻</button>
<button id="btnToggleSettings">⚙</button> <button id="btnToggleSettings" title="Einstellungen">⚙</button>
</div> </div>
</header> </header>
<section class="main"> <div 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>
<div id="settingsForm" class="settings-panel hidden">
<h3 id="settingsTitle">Neuen Server hinzufügen</h3>
<input id="inputName" placeholder="Name (z. B. Lobby)" />
<input id="inputUrl" placeholder="URL (z. B. 192.168.1.1:9191)" />
<input id="inputWpSite" placeholder="WP Site (z. B. https://meine-seite.de)" />
<input id="inputWpServerId" placeholder="WP Server ID" />
<button id="btnAddServer">Hinzufügen</button>
</div>
<div id="serverListPanel" class="server-list-panel hidden">
<ul id="serversContainer"></ul> <ul id="serversContainer"></ul>
</aside> </div>
<div class="detail"> <div class="detail-panel">
<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 id="noSelection" class="placeholder">Noch kein Server ausgewählt.</div>
<div class="detail-header">
<div class="server-identity"> <div id="detailContent" class="hidden">
<h2 id="detailName"></h2>
<span id="detailUrlText" class="server-url"></span> <!-- Server Name + Status Badge -->
</div> <div class="srv-identity">
<div class="status-wrapper"> <div>
<div id="detailPulse" class="pulsing-dot"></div> <div class="srv-name" id="detailName"></div>
<span id="detailStatus"></span> <div class="srv-url" id="detailUrlText"></div>
</div> </div>
<div class="online-badge offline" id="statusBadge">
<div class="pulsing-dot offline" id="detailPulse"></div>
<span id="detailStatus">Offline</span>
</div>
</div> </div>
<!-- NEUES LAYOUT ZEILE 2: Spieler, Version, Ping --> <!-- MOTD -->
<div class="detail-stats"> <div id="motdBar" class="motd-bar hidden"></div>
<div><strong>Spieler:</strong> <span id="detailPlayers"></span></div>
<div><strong>Version:</strong> <span id="detailVersion"></span></div> <!-- Stats: Spieler / Version / Ping -->
<div><strong>Ping:</strong> <span id="detailPing">-</span></div> <div class="stats-row">
<div class="stat-card">
<div class="stat-label">Spieler</div>
<div class="stat-val" id="detailPlayers">-</div>
</div>
<div class="stat-card">
<div class="stat-label">Version</div>
<div class="stat-val small" id="detailVersion">-</div>
</div>
<div class="stat-card">
<div class="stat-label">Ping</div>
<div class="stat-val" id="detailPing">-</div>
</div>
</div> </div>
<h3>Online Spieler</h3> <!-- Sub-Server -->
<ul id="detailPlayerList" class="playerList"></ul> <div id="backendSection" class="hidden">
<div class="section-label">Sub-Server</div>
<div class="sub-servers" id="backendServersList"></div>
</div>
<div class="detailButtons"> <!-- Online Spieler -->
<div>
<div class="section-label">Online Spieler</div>
<div class="players-grid" id="detailPlayerList"></div>
</div>
<!-- Edit / Delete -->
<div class="detail-buttons hidden" id="detailButtons">
<button id="btnEdit">Bearbeiten</button> <button id="btnEdit">Bearbeiten</button>
<button id="btnDelete">Löschen</button> <button id="btnDelete">Löschen</button>
</div> </div>
</div> </div>
</div> </div>
</section>
</div>
</div> </div>
<script src="popup.js"></script> <script src="popup.js"></script>
</body> </body>
+276 -230
View File
@@ -1,334 +1,394 @@
// popup.js
const $ = id => document.getElementById(id); const $ = id => document.getElementById(id);
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9); 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 servers = [];
let selectedId = null; let selectedId = null;
let statuses = {}; let statuses = {};
let previousStatuses = {}; let previousStatuses = {};
let settingsVisible = false; let settingsVisible = false;
let editingId = null; // Welcher Server gerade bearbeitet wird
document.addEventListener('DOMContentLoaded', init); document.addEventListener('DOMContentLoaded', init);
btnAdd.addEventListener('click', handleAdd); $('btnAdd')?.addEventListener('click', handleAdd);
btnRefresh.addEventListener('click', manualRefresh); $('btnAddServer')?.addEventListener('click', handleAdd);
btnToggleSettings.addEventListener('click', toggleSettings); $('btnRefresh').addEventListener('click', manualRefresh);
btnEdit.addEventListener('click', handleEdit); $('btnToggleSettings').addEventListener('click', toggleSettings);
btnDelete.addEventListener('click', handleDelete); $('btnEdit').addEventListener('click', handleEdit);
$('btnDelete').addEventListener('click', handleDelete);
async function init() { async function init() {
await loadServersFromStorage(); await loadServersFromStorage();
await loadStatusesFromStorage(); await loadStatusesFromStorage();
await loadSettingsVisibility(); await loadSettingsVisibility();
renderServerList(); renderServerList();
if (servers.length === 1) selectedId = servers[0].id; if (servers.length === 1) selectedId = servers[0].id;
renderDetail(selectedId); renderDetail(selectedId);
adjustDetailLayout();
} }
// --- Settings Visibility --- // ── Minecraft color code parser ──
function parseMinecraftColors(text) {
if (!text) return '';
const map = {
'§0':'#000000','§1':'#0000AA','§2':'#00AA00','§3':'#00AAAA',
'§4':'#AA0000','§5':'#AA00AA','§6':'#FFAA00','§7':'#AAAAAA',
'§8':'#555555','§9':'#5555FF','§a':'#55FF55','§b':'#55FFFF',
'§c':'#FF5555','§d':'#FF55FF','§e':'#FFFF55','§f':'#FFFFFF',
'&0':'#000000','&1':'#0000AA','&2':'#00AA00','&3':'#00AAAA',
'&4':'#AA0000','&5':'#AA00AA','&6':'#FFAA00','&7':'#AAAAAA',
'&8':'#555555','&9':'#5555FF','&a':'#55FF55','&b':'#55FFFF',
'&c':'#FF5555','&d':'#FF55FF','&e':'#FFFF55','&f':'#FFFFFF'
};
let html = text.replace(/[<>]/g, '');
let result = '';
let i = 0;
while (i < html.length) {
const ch = html[i];
if ((ch === '§' || ch === '&') && i + 1 < html.length) {
const code = ch + html[i+1].toLowerCase();
if (map[code]) {
result += `<span style="color:${map[code]}">`;
i += 2;
continue;
}
}
result += html[i];
i++;
}
return result;
}
function get3DAvatarUrl(name, uuid = null) {
return `https://mc-heads.net/head/${encodeURIComponent(name)}/64`;
}
// ── Storage ──
async function loadSettingsVisibility() { async function loadSettingsVisibility() {
const obj = await chrome.storage.local.get(['settingsVisible']); const obj = await chrome.storage.local.get(['settingsVisible']);
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false; settingsVisible = !!obj.settingsVisible;
if (!settingsVisible) settingsForm.classList.add('hidden'); applySettingsVisibility();
else settingsForm.classList.remove('hidden');
} }
async function saveSettingsVisibility() { async function saveSettingsVisibility() {
await chrome.storage.local.set({ settingsVisible }); await chrome.storage.local.set({ settingsVisible });
} }
function applySettingsVisibility() {
$('settingsForm').classList.toggle('hidden', !settingsVisible);
$('serverListPanel').classList.toggle('hidden', !settingsVisible);
$('detailButtons').classList.toggle('hidden', !settingsVisible);
}
async function toggleSettings() { async function toggleSettings() {
settingsVisible = !settingsVisible; settingsVisible = !settingsVisible;
settingsForm.classList.toggle('hidden'); applySettingsVisibility();
await saveSettingsVisibility(); await saveSettingsVisibility();
adjustDetailLayout();
} }
// --- Storage ---
async function loadServersFromStorage() { async function loadServersFromStorage() {
const obj = await chrome.storage.local.get(['servers']); const obj = await chrome.storage.local.get(['servers']);
servers = obj.servers || []; servers = obj.servers || [];
for (const s of servers) if (!s.id) s.id = uid(); for (const s of servers) if (!s.id) s.id = uid();
await chrome.storage.local.set({ servers }); await chrome.storage.local.set({ servers });
} }
async function saveServersToStorage() { async function saveServersToStorage() {
await chrome.storage.local.set({ servers }); await chrome.storage.local.set({ servers });
} }
async function loadStatusesFromStorage() { async function loadStatusesFromStorage() {
const obj = await chrome.storage.local.get(['serverStatuses']); const obj = await chrome.storage.local.get(['serverStatuses']);
statuses = obj.serverStatuses || {}; statuses = obj.serverStatuses || {};
} }
// --- Layout --- // ── Server list ──
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() { function renderServerList() {
serversContainer.innerHTML = ''; const container = $('serversContainer');
container.innerHTML = '';
if (servers.length === 0) { if (servers.length === 0) {
serversContainer.innerHTML = '<div class="placeholder">Noch keine Server hinzugefügt.</div>'; container.innerHTML = '<div class="placeholder" style="padding:8px 0;">Noch kein Server hinzugefügt.</div>';
return; return;
} }
for (const s of servers) { for (const s of servers) {
const item = document.createElement('li'); const li = document.createElement('li');
item.className = 'server-item'; li.className = 'server-item' + (s.id === selectedId ? ' selected' : '');
item.dataset.id = s.id; li.dataset.id = s.id;
const meta = document.createElement('div'); const meta = document.createElement('div');
meta.className = 'meta'; 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)' : ''); const nameEl = document.createElement('div');
listUrl = listUrl.replace(':9191', ''); nameEl.className = 'name';
nameEl.textContent = s.name || '(Kein Name)';
const url = document.createElement('div'); let urlText = s.url || (s.wpSite || '');
url.className = 'url'; urlText = urlText.replace(':9191', '');
url.textContent = listUrl; const urlEl = document.createElement('div');
meta.append(name, url); urlEl.className = 'url';
urlEl.textContent = urlText;
const statusBubble = document.createElement('div'); meta.append(nameEl, urlEl);
statusBubble.className = 'status-bubble';
statusBubble.textContent = '—';
statusBubble.style.backgroundColor = 'transparent';
item.statusBubble = statusBubble;
item.append(meta, statusBubble); const bubble = document.createElement('div');
item.addEventListener('click', () => { bubble.className = 'status-bubble';
bubble.textContent = '—';
li._bubble = bubble;
li.append(meta, bubble);
li.addEventListener('click', () => {
selectedId = s.id; selectedId = s.id;
document.querySelectorAll('.server-item').forEach(el => el.classList.remove('selected'));
li.classList.add('selected');
renderDetail(selectedId); renderDetail(selectedId);
}); });
container.appendChild(li);
serversContainer.appendChild(item);
} }
updateServerListStatuses(false); updateServerListStatuses();
} }
// --- Render Detail --- // ── Detail ──
function renderDetail(id) { function renderDetail(id) {
if (!id) { if (!id) {
noSelection.classList.remove('hidden'); $('noSelection').classList.remove('hidden');
detailContent.classList.add('hidden'); $('detailContent').classList.add('hidden');
return; return;
} }
const srv = servers.find(x => x.id === id); const srv = servers.find(x => x.id === id);
if (!srv) { if (!srv) {
noSelection.classList.remove('hidden'); $('noSelection').classList.remove('hidden');
detailContent.classList.add('hidden'); $('detailContent').classList.add('hidden');
return; return;
} }
noSelection.classList.add('hidden'); $('noSelection').classList.add('hidden');
detailContent.classList.remove('hidden'); $('detailContent').classList.remove('hidden');
detailName.textContent = srv.name || 'Unbenannter Server'; $('detailName').textContent = srv.name || 'Unbenannter Server';
let urlToShow = srv.url || (srv.wpSite || 'Lokal');
let urlToShow = srv.url || (srv.wpSite ? srv.wpSite : 'Lokal');
urlToShow = urlToShow.replace(':9191', ''); urlToShow = urlToShow.replace(':9191', '');
$('detailUrlText').textContent = urlToShow;
detailUrlText.textContent = urlToShow;
updateDetailForServer(srv, true); updateDetailForServer(srv, true);
} }
// --- Update Detail ---
function updateDetailForServer(srv, force = false) { function updateDetailForServer(srv, force = false) {
const st = statuses[srv.id]; const st = statuses[srv.id];
const prevSt = previousStatuses[srv.id]; const prevSt = previousStatuses[srv.id];
const changed = force || JSON.stringify(st) !== JSON.stringify(prevSt);
if (!changed) return;
const statusChanged = force || const badge = $('statusBadge');
!prevSt || const pulse = $('detailPulse');
(prevSt.ok !== st?.ok) ||
(prevSt.data?.online !== st?.data?.online);
if (!st || !st.ok || !st.data) { if (!st || !st.ok || !st.data) {
if (statusChanged) { badge.className = 'online-badge offline';
detailStatus.textContent = 'Offline'; pulse.className = 'pulsing-dot offline';
detailPulse.classList.remove('online'); $('detailStatus').textContent = 'Offline';
detailVersion.textContent = '-'; $('detailVersion').textContent = '-';
detailPlayers.textContent = '-'; $('detailPlayers').textContent = '-';
detailPing.textContent = '-'; $('detailPing').textContent = '-';
updatePlayerList([]); $('motdBar').classList.add('hidden');
} updatePlayerList([]);
updateBackendServers(null);
} else { } else {
const d = st.data; const d = st.data;
const isOnline = !!d.online;
if (statusChanged) { badge.className = 'online-badge ' + (isOnline ? 'online' : 'offline');
detailStatus.textContent = d.online ? 'Online' : 'Offline'; pulse.className = 'pulsing-dot ' + (isOnline ? 'online' : 'offline');
if (d.online) { $('detailStatus').textContent = isOnline ? 'Online' : 'Offline';
detailPulse.classList.add('online'); $('detailVersion').textContent = d.version || 'unknown';
} else {
detailPulse.classList.remove('online');
}
}
const newVersion = d.version || 'unknown'; const count = Array.isArray(d.players) ? d.players.length
if (force || detailVersion.textContent !== newVersion) { : (typeof d.players === 'number' ? d.players : 0);
detailVersion.textContent = newVersion; const max = d.max_players;
} $('detailPlayers').textContent = max && max !== '-1' ? `${count} / ${max}` : String(count);
const playersCount = Array.isArray(d.players) ? d.players.length : (typeof d.players === 'number' ? d.players : 0); const ping = d.ping || d.latency;
const maxPlayers = d.max_players; $('detailPing').textContent = typeof ping === 'number' ? ping + ' ms' : '-';
let newPlayersText = String(playersCount);
if (maxPlayers && maxPlayers !== '-1') {
newPlayersText += ` / ${maxPlayers}`;
}
if (force || detailPlayers.textContent !== newPlayersText) { const motdRaw = d.motd || (d.network && d.network.motd) || '';
detailPlayers.textContent = newPlayersText; if (motdRaw) {
} const motdEl = $('motdBar');
motdEl.innerHTML = parseMinecraftColors(motdRaw);
let pingVal = d.ping || d.latency || '-'; motdEl.classList.remove('hidden');
if (pingVal !== '-' && typeof pingVal === 'number') { } else {
pingVal = pingVal + ' ms'; $('motdBar').classList.add('hidden');
}
if (force || (pingVal !== '-' && detailPing.textContent !== pingVal)) {
detailPing.textContent = pingVal;
} }
const currentPlayers = Array.isArray(d.players) ? d.players : []; const currentPlayers = Array.isArray(d.players) ? d.players : [];
const prevPlayers = (prevSt && Array.isArray(prevSt.data?.players)) ? prevSt.data.players : []; updatePlayerList(currentPlayers);
updateBackendServers(d.network?.backend_servers || null);
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; previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
} }
// --- Spielerliste --- // ── Sub-Server chips ──
function updatePlayerList(players) { function updateBackendServers(list) {
detailPlayerList.innerHTML = ''; const section = $('backendSection');
if (!players || players.length === 0) { const container = $('backendServersList');
detailPlayerList.innerHTML = '<li class="placeholder">Keine Spieler online.</li>'; if (!list || list.length === 0) {
section.classList.add('hidden');
container.innerHTML = '';
return; return;
} }
for (const p of players) { section.classList.remove('hidden');
const li = document.createElement('li'); container.innerHTML = '';
const name = typeof p === 'object' ? p.name || p.username || p.player || '' : String(p); for (const bs of list) {
const count = bs.online_players || 0;
const chip = document.createElement('div');
chip.className = 'sub-chip' + (count > 0 ? ' active' : '');
if (typeof p === 'object' && p.avatar) { const dot = document.createElement('span');
const img = document.createElement('img'); dot.className = 'sub-chip-dot';
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); const nameEl = document.createElement('span');
nameEl.className = 'sub-chip-name';
nameEl.textContent = bs.name;
const countEl = document.createElement('span');
countEl.className = 'sub-chip-count';
countEl.textContent = count > 0 ? count : 'leer';
chip.append(dot, nameEl, countEl);
container.appendChild(chip);
} }
} }
// --- Update Server List Statuses --- // ── Player list ──
function updateServerListStatuses() { function updatePlayerList(players) {
const items = serversContainer.querySelectorAll('.server-item'); const grid = $('detailPlayerList');
items.forEach(item => { grid.innerHTML = '';
const s = servers.find(x => x.id === item.dataset.id);
if (!s) return;
const st = statuses[s.id]; if (!players || players.length === 0) {
const prevSt = previousStatuses[s.id]; grid.innerHTML = '<div class="placeholder">Keine Spieler online.</div>';
return;
}
const statusChanged = !prevSt || for (const p of players) {
(prevSt.ok !== st?.ok) || const card = document.createElement('div');
(prevSt.data?.online !== st?.data?.online); card.className = 'player-card';
if (!statusChanged) return; let name = '', uuid = null, prefix = '', serverName = null;
if (!st || !st.ok || !st.data) { if (typeof p === 'object') {
item.statusBubble.textContent = 'Offline'; name = p.name || p.username || p.player || '';
item.statusBubble.style.backgroundColor = 'var(--offline)'; uuid = p.uuid || null;
} else if (st.data.online) { prefix = p.prefix || p.group || '';
item.statusBubble.textContent = 'Online'; serverName = p.server || p.current_server || p.connected_server || null;
item.statusBubble.style.backgroundColor = 'var(--online)';
} else { } else {
item.statusBubble.textContent = 'Offline'; name = String(p);
item.statusBubble.style.backgroundColor = 'var(--offline)';
} }
previousStatuses[s.id] = st ? JSON.parse(JSON.stringify(st)) : null; const img = document.createElement('img');
img.src = (typeof p === 'object' && p.avatar) ? p.avatar : get3DAvatarUrl(name, uuid);
img.className = 'player-avatar';
img.loading = 'lazy';
img.onerror = function() {
this.onerror = null;
this.src = `https://mc-heads.net/avatar/${encodeURIComponent(name)}/64`;
};
const info = document.createElement('div');
info.className = 'player-info';
if (prefix) {
const prefixEl = document.createElement('div');
prefixEl.className = 'player-prefix';
prefixEl.innerHTML = parseMinecraftColors(prefix);
info.appendChild(prefixEl);
}
const nameEl = document.createElement('div');
nameEl.className = 'player-name';
nameEl.textContent = name;
info.appendChild(nameEl);
if (serverName) {
const tag = document.createElement('span');
tag.className = 'player-server-tag';
tag.textContent = serverName;
info.appendChild(tag);
}
card.append(img, info);
grid.appendChild(card);
}
}
// ── Server list status bubbles ──
function updateServerListStatuses() {
const items = $('serversContainer').querySelectorAll('.server-item');
items.forEach(item => {
const s = servers.find(x => x.id === item.dataset.id);
if (!s || !item._bubble) return;
const st = statuses[s.id];
if (!st || !st.ok || !st.data || !st.data.online) {
item._bubble.textContent = 'Offline';
item._bubble.style.background = 'rgba(239,68,68,0.15)';
item._bubble.style.color = '#f87171';
} else {
item._bubble.textContent = 'Online';
item._bubble.style.background = 'rgba(34,197,94,0.15)';
item._bubble.style.color = '#4ade80';
}
}); });
} }
// --- Add / Edit / Delete --- // ── Edit / Add Mode ──
function enterEditMode(server) {
editingId = server.id;
$('settingsTitle').textContent = 'Server bearbeiten';
$('btnAddServer').textContent = 'Änderungen speichern';
$('inputName').value = server.name || '';
$('inputUrl').value = server.url || '';
$('inputWpSite').value = server.wpSite || '';
$('inputWpServerId').value = server.wpServerId || '';
}
function exitEditMode() {
editingId = null;
$('settingsTitle').textContent = 'Neuen Server hinzufügen';
$('btnAddServer').textContent = 'Hinzufügen';
$('inputName').value = '';
$('inputUrl').value = '';
$('inputWpSite').value = '';
$('inputWpServerId').value = '';
}
// ── Actions ──
async function handleAdd() { async function handleAdd() {
const name = inputName.value.trim(); const name = $('inputName').value.trim();
const url = inputUrl.value.trim(); const url = $('inputUrl').value.trim();
const wpSite = inputWpSite.value.trim(); const wpSite = $('inputWpSite').value.trim();
const wpServerId = inputWpServerId.value.trim(); const wpServerId = $('inputWpServerId').value.trim();
if (!url && !wpSite) { if (!url && !wpSite) {
alert('Bitte URL oder WP Site angeben'); alert('Bitte eine URL oder WordPress-Einstellungen angeben.');
return; return;
} }
const s = { if (editingId) {
id: uid(), // === BEARBEITEN ===
name: name || url || wpSite, const srv = servers.find(s => s.id === editingId);
url: url || null, if (srv) {
wpSite: wpSite || null, srv.name = name || url || wpSite;
wpServerId: wpServerId || null if (url) srv.url = url;
}; if (wpSite) srv.wpSite = wpSite;
if (wpServerId) srv.wpServerId = wpServerId;
}
exitEditMode();
} else {
// === NEU HINZUFÜGEN ===
const s = {
id: uid(),
name: name || url || wpSite,
url: url || null,
wpSite: wpSite || null,
wpServerId: wpServerId || null
};
servers.push(s);
}
servers.push(s);
await saveServersToStorage(); await saveServersToStorage();
inputName.value = '';
inputUrl.value = '';
inputWpSite.value = '';
inputWpServerId.value = '';
renderServerList(); renderServerList();
if (selectedId) renderDetail(selectedId);
} }
async function handleEdit() { async function handleEdit() {
@@ -336,42 +396,28 @@ async function handleEdit() {
const srv = servers.find(s => s.id === selectedId); const srv = servers.find(s => s.id === selectedId);
if (!srv) return; if (!srv) return;
const newName = prompt('Name:', srv.name) || srv.name; settingsVisible = true;
const newUrl = prompt('URL:', srv.url || '') || srv.url; applySettingsVisibility();
const newWpSite = prompt('WP Site:', srv.wpSite || '') || srv.wpSite; await saveSettingsVisibility();
const newWpServerId = prompt('WP Server ID:', srv.wpServerId || '') || srv.wpServerId;
srv.name = newName.trim(); enterEditMode(srv);
srv.url = newUrl.trim();
srv.wpSite = newWpSite.trim();
srv.wpServerId = newWpServerId.trim();
await saveServersToStorage();
renderServerList();
renderDetail(selectedId);
} }
async function handleDelete() { async function handleDelete() {
if (!selectedId) return; if (!selectedId || !confirm('Server wirklich löschen?')) return;
if (!confirm('Server wirklich löschen?')) return;
servers = servers.filter(s => s.id !== selectedId); servers = servers.filter(s => s.id !== selectedId);
selectedId = null; selectedId = null;
await saveServersToStorage(); await saveServersToStorage();
renderServerList(); renderServerList();
renderDetail(null); renderDetail(null);
exitEditMode();
} }
async function manualRefresh() { async function manualRefresh() {
try { try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
chrome.runtime.sendMessage({ cmd: 'refreshNow' });
} catch(e) {
console.error('Refresh fehlgeschlagen:', e);
}
} }
// --- Storage Listener --- // ── Live updates from background ──
chrome.storage.onChanged.addListener((changes, area) => { chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.serverStatuses) { if (area === 'local' && changes.serverStatuses) {
statuses = changes.serverStatuses.newValue || {}; statuses = changes.serverStatuses.newValue || {};
@@ -1,12 +1,21 @@
<?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). Plugin URI:https://git.viper.ipv64.net/M_Viper/Minecraft-BungeeCord-Status
* Tags: minecraft, bungeecord, server status, player list Description: Der ultimative Live-Status für dein BungeeCord Netzwerk.
* Version: 3.6.1 Version: 3.6.6
* Author: M_Viper Author: M_Viper
* Requires at least: 6.0 Author URI: https://m-viper.de
* Requires PHP: 7.4 Requires at least: 6.0
Requires PHP: 7.4
Tested up to: 6.8
PHP Version: 7.4
License: GPL2
License URI: https://www.gnu.org/licenses/gpl-2.0.html
Text Domain: minecraft-bungeecord-status
Tags: minecraft, bungeecord, server status, player list
Support: [Discord Support](https://discord.com/invite/FdRs4BRd8D)
Support: [Telegram Support](https://t.me/M_Viper04)
*/ */
if (!defined('ABSPATH')) exit; if (!defined('ABSPATH')) exit;
@@ -59,144 +68,133 @@ function mcss_format_minecraft_colors($text) {
return $formatted; return $formatted;
} }
/* ---------------- AUTO UPDATE (MCSS) ---------------- */ /* ---------------- HELPER: 3D AVATAR URL ---------------- */
if ( ! class_exists( 'MCSS_Auto_Update' ) ) { function mcss_get_3d_avatar($name, $uuid = null) {
class MCSS_Auto_Update { // Bedrock-Spieler erkennen (Name enthält Punkt oder UUID beginnt mit xuid)
$is_bedrock = strpos($name, '.') !== false || ($uuid && strpos($uuid, 'xuid') === 0);
private $plugin_file; if ($is_bedrock) {
private $repo_owner = 'M_Viper'; // Bedrock: mc-heads.net mit 3D Head (nutze UUID falls vorhanden)
private $repo_name = 'Minecraft-BungeeCord-Status'; if ($uuid && !empty($uuid)) {
private $api_url; return 'https://mc-heads.net/head/' . rawurlencode($uuid) . '/100';
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' ) );
}
} }
return 'https://mc-heads.net/head/' . rawurlencode($name) . '/100';
} else {
// Java: mc-heads.net mit 3D Head
return 'https://mc-heads.net/head/' . rawurlencode($name) . '/100';
}
}
public function check_for_update() {
if ( ! function_exists( 'get_file_data' ) ) { // ===============================
require_once ABSPATH . 'wp-admin/includes/plugin.php'; // MCSS - UPDATE NOTICE SYSTEM
// ===============================
// Plugin-Version aus Header lesen
function mcss_get_plugin_version() {
if ( ! function_exists( 'get_plugin_data' ) ) {
require_once ABSPATH . 'wp-admin/includes/plugin.php';
}
$plugin_data = get_plugin_data( __FILE__ );
return $plugin_data['Version'] ?? '0.0.0';
}
// Cache manuell leeren
function mcss_clear_update_cache() {
if ( isset($_GET['mcss_clear_cache']) && current_user_can('manage_options') ) {
check_admin_referer('mcss_clear_cache_action');
delete_transient('mcss_latest_release');
wp_redirect( admin_url('plugins.php') );
exit;
}
}
add_action('admin_init', 'mcss_clear_update_cache');
// Neueste Release-Infos von Gitea holen
function mcss_get_latest_release_info( $force_refresh = false ) {
$transient_key = 'mcss_latest_release';
if ( $force_refresh ) {
delete_transient( $transient_key );
}
$release_info = get_transient( $transient_key );
if ( false === $release_info ) {
$response = wp_remote_get(
'https://git.viper.ipv64.net/api/v1/repos/M_Viper/Minecraft-BungeeCord-Status/releases/latest',
['timeout' => 10]
);
if ( ! is_wp_error($response) && 200 === wp_remote_retrieve_response_code($response) ) {
$body = wp_remote_retrieve_body($response);
$data = json_decode($body, true);
if ( $data && isset($data['tag_name']) ) {
$tag = ltrim( $data['tag_name'], 'vV' );
$release_info = [
'version' => $tag,
'download_url' => $data['zipball_url'] ?? '',
'notes' => $data['body'] ?? '',
'published_at' => $data['published_at'] ?? '',
];
set_transient( $transient_key, $release_info, 6 * HOUR_IN_SECONDS );
} else {
set_transient( $transient_key, [], HOUR_IN_SECONDS );
} }
} else {
$current = get_file_data( $this->plugin_file, array( 'Version' => 'Version' ), 'plugin' ); set_transient( $transient_key, [], HOUR_IN_SECONDS );
$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__ ); return $release_info;
} }
// Admin-Update-Hinweis anzeigen
function mcss_show_update_notice() {
if ( ! current_user_can('manage_options') ) {
return;
}
$current_version = mcss_get_plugin_version();
$latest_release = mcss_get_latest_release_info();
if ( ! empty($latest_release['version']) && version_compare($current_version, $latest_release['version'], '<') ) {
$refresh_url = wp_nonce_url(
admin_url('plugins.php?mcss_clear_cache=1'),
'mcss_clear_cache_action'
);
?>
<div class="notice notice-warning is-dismissible">
<h3>Minecraft BungeeCord Status – Update verfügbar</h3>
<p>
Installiert: <strong><?php echo esc_html($current_version); ?></strong><br>
Neueste Version: <strong><?php echo esc_html($latest_release['version']); ?></strong>
</p>
<p>
<a href="<?php echo esc_url($latest_release['download_url']); ?>" class="button button-primary" target="_blank">
Update herunterladen
</a>
<a href="https://git.viper.ipv64.net/M_Viper/Minecraft-BungeeCord-Status/releases" class="button" target="_blank">
Release Notes
</a>
<a href="<?php echo esc_url($refresh_url); ?>" class="button">
Jetzt neu prüfen
</a>
</p>
</div>
<?php
}
}
add_action('admin_notices', 'mcss_show_update_notice');
/* ---------------- Assets ---------------- */ /* ---------------- Assets ---------------- */
add_action('admin_enqueue_scripts', function($hook){ add_action('admin_enqueue_scripts', function($hook){
global $pagenow; global $pagenow;
@@ -499,25 +497,89 @@ function mcss_render_row($srv, $i, $font_sizes, $announcement_types) {
} }
/* ---------------- LOGIC: STATUS API FETCHER ---------------- */ /* ---------------- LOGIC: STATUS API FETCHER ---------------- */
function mcss_fetch_data_via_api($host, $port, $timeout = 2) { function mcss_socket_http_json($host, $port, $path = '/', $timeout = 2.0) {
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout); $socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$socket) return false; if (!$socket) {
return false;
}
$out = "GET / HTTP/1.1\r\n"; stream_set_timeout($socket, (int)ceil($timeout));
$out = "GET " . $path . " HTTP/1.1\r\n";
$out .= "Host: " . $host . "\r\n"; $out .= "Host: " . $host . "\r\n";
$out .= "Connection: Close\r\n\r\n"; $out .= "User-Agent: MCSS-WordPress/3.6.6\r\n";
fwrite($socket, $out); $out .= "Accept: application/json\r\n";
$out .= "Connection: close\r\n\r\n";
if (@fwrite($socket, $out) === false) {
@fclose($socket);
return false;
}
$response = ''; $response = '';
while (!feof($socket)) { while (!feof($socket)) {
$response .= fgets($socket, 128); $chunk = @fread($socket, 2048);
if ($chunk === false) {
break;
}
if ($chunk === '') {
$meta = stream_get_meta_data($socket);
if (!empty($meta['timed_out'])) {
break;
}
usleep(10000);
continue;
}
$response .= $chunk;
} }
fclose($socket);
$parts = explode("\r\n\r\n", $response); $meta = stream_get_meta_data($socket);
$body = end($parts); @fclose($socket);
$data = json_decode($body, true);
return $data; if (!empty($meta['timed_out'])) {
return false;
}
$parts = explode("\r\n\r\n", $response, 2);
if (count($parts) !== 2) {
return false;
}
$statusLine = strtok($parts[0], "\r\n");
if (!is_string($statusLine) || stripos($statusLine, '200') === false) {
return false;
}
$data = json_decode($parts[1], true);
return is_array($data) ? $data : false;
}
function mcss_fetch_data_via_api($host, $port, $timeout = 2) {
// Primärer Endpunkt mit Detaildaten
$full = mcss_socket_http_json($host, $port, '/', $timeout);
if (is_array($full) && isset($full['online'])) {
return $full;
}
// Fallback auf Health-Endpunkt (StatusAPI >= hardened build)
$health = mcss_socket_http_json($host, $port, '/health', $timeout);
if (is_array($health) && !empty($health['online'])) {
return [
'online' => true,
'players' => [],
'version' => 'BungeeCord',
'ping' => 1,
'motd' => 'StatusAPI erreichbar (Health)',
];
}
// Ein zweiter kurzer Versuch reduziert False Negatives bei Paketverlust.
$retry = mcss_socket_http_json($host, $port, '/', max(1.0, $timeout));
if (is_array($retry) && isset($retry['online'])) {
return $retry;
}
return false;
} }
function mcss_parse_bungeecord_version($raw_version) { function mcss_parse_bungeecord_version($raw_version) {
@@ -528,16 +590,26 @@ function mcss_parse_bungeecord_version($raw_version) {
} }
function mcss_fetch_server_with_ranks($srv) { function mcss_fetch_server_with_ranks($srv) {
$cache_key = 'mcss_data_' . md5($srv['host']);
$cached = get_transient($cache_key);
if ($cached !== false) return $cached;
$host = $srv['host']; $host = $srv['host'];
$port = $srv['player_port'] ?? 9191; $port = $srv['player_port'] ?? 9191;
$api_data = mcss_fetch_data_via_api($host, $port, 1); $cache_key = 'mcss_data_' . md5($host . ':' . $port);
$last_good_key = 'mcss_last_good_' . md5($host . ':' . $port);
$cached = get_transient($cache_key);
if ($cached !== false) return $cached;
$api_data = mcss_fetch_data_via_api($host, $port, 2);
if (!$api_data || !isset($api_data['online'])) { if (!$api_data || !isset($api_data['online'])) {
$last_good = get_transient($last_good_key);
if (is_array($last_good) && !empty($last_good['online'])) {
$last_good['stale'] = true;
$last_good['stale_reason'] = 'temporary_connection_loss';
set_transient($cache_key, $last_good, 5);
return $last_good;
}
return ['online'=>false,'players'=>[],'version'=>'Offline','ping'=>9999,'motd'=>'Verbindung fehlgeschlagen']; return ['online'=>false,'players'=>[],'version'=>'Offline','ping'=>9999,'motd'=>'Verbindung fehlgeschlagen'];
} }
@@ -547,10 +619,12 @@ function mcss_fetch_server_with_ranks($srv) {
if (is_array($player_data)) { if (is_array($player_data)) {
$name = $player_data['name']; $name = $player_data['name'];
$prefix = $player_data['prefix'] ?? ''; $prefix = $player_data['prefix'] ?? '';
$uuid = $player_data['uuid'] ?? null;
} else { } else {
// Fallback für alte API // Fallback für alte API
$name = $player_data; $name = $player_data;
$prefix = ''; $prefix = '';
$uuid = null;
} }
// 1. Prefix mit Farben konvertieren // 1. Prefix mit Farben konvertieren
@@ -566,9 +640,13 @@ function mcss_fetch_server_with_ranks($srv) {
$display_html = $name_html; $display_html = $name_html;
} }
// 4. 3D Avatar URL generieren (mit Bedrock-Support)
$avatar_url = mcss_get_3d_avatar($name, $uuid);
$players_info[] = [ $players_info[] = [
'name' => $name, 'name' => $name,
'avatar' => "https://mc-heads.net/avatar/" . rawurlencode($name) . "/64", 'uuid' => $uuid,
'avatar' => $avatar_url,
'prefix' => $prefix, 'prefix' => $prefix,
'display_html' => $display_html, 'display_html' => $display_html,
'rank' => $prefix ?: 'Spieler', 'rank' => $prefix ?: 'Spieler',
@@ -579,7 +657,7 @@ function mcss_fetch_server_with_ranks($srv) {
$clean_version = mcss_parse_bungeecord_version($api_data['version'] ?? 'BungeeCord'); $clean_version = mcss_parse_bungeecord_version($api_data['version'] ?? 'BungeeCord');
$result = [ $result = [
'online' => true, 'online' => !empty($api_data['online']),
'players' => $players_info, 'players' => $players_info,
'version' => $clean_version, 'version' => $clean_version,
'ping' => 1, 'ping' => 1,
@@ -588,6 +666,9 @@ function mcss_fetch_server_with_ranks($srv) {
$fast_cache_ttl = max(2, absint($srv['cache_ttl'] ?? 10)); $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);
if (!empty($result['online'])) {
set_transient($last_good_key, $result, 180);
}
return $result; return $result;
} }
@@ -670,7 +751,7 @@ function mcss_shortcode($atts) {
$widget_width = "650px"; $widget_width = "650px";
$widget_padding = "15px"; $widget_padding = "15px";
$logo_size = "70px"; $logo_size = "70px";
$player_head_size = "32px"; $player_head_size = "40px"; // Größer für 3D-Köpfe
$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'])) {
@@ -756,13 +837,13 @@ function mcss_shortcode($atts) {
<!-- PLAYER GRID --> <!-- PLAYER GRID -->
<span style="color:#666;margin-bottom:15px;">Spieler:</span> <span style="color:#666;margin-bottom:15px;">Spieler:</span>
<div id="mcss-player-grid-<?php echo esc_attr($uid); ?>" style="display:flex;flex-wrap:wrap;gap:8px;margin-top:8px;color:#666;justify-content:center;"> <div id="mcss-player-grid-<?php echo esc_attr($uid); ?>" style="display:flex;flex-wrap:wrap;gap:12px;margin-top:8px;color:#666;justify-content:center;">
<?php if (!empty($data['players']) && is_array($data['players'])): ?> <?php if (!empty($data['players']) && is_array($data['players'])): ?>
<?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:6px;box-shadow:0 2px 6px rgba(0,0,0,0.15);" loading="lazy">
<div style="font-size:0.75em;"><?php echo $p['display_html']; ?></div> <div style="font-size:0.75em;margin-top:4px;"><?php echo $p['display_html']; ?></div>
</div> </div>
<?php endforeach; ?> <?php endforeach; ?>
<?php else: ?> <?php else: ?>
@@ -847,8 +928,8 @@ function mcss_shortcode($atts) {
d.players.forEach(function(p){ d.players.forEach(function(p){
var content = p.display_html ? p.display_html : p.name; 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:6px;box-shadow:0 2px 6px rgba(0,0,0,0.15);" loading="lazy">' +
'<div style="font-size:0.75em;">' + content + '</div>' + '<div style="font-size:0.75em;margin-top:4px;">' + content + '</div>' +
'</div>'; '</div>';
}); });
} else { } else {
@@ -875,3 +956,184 @@ function mcss_shortcode($atts) {
<?php return ob_get_clean(); <?php return ob_get_clean();
} }
/* ---------------- SIDEBAR WIDGET: MODERN PILL BADGE (ROBUST INLINE STYLES) ---------------- */
class MCSS_Sidebar_Status_Widget extends WP_Widget {
public function __construct() {
parent::__construct(
'mcss_sidebar_status', // Basis ID
'MC Server Status (Simple)', // Name
array( 'description' => 'Zeigt einen modernen Online/Offline Status in der Sidebar an.' ) // Args
);
}
// Backend: Formular im Widget-Bereich
public function form( $instance ) {
$title = ! empty( $instance['title'] ) ? $instance['title'] : 'Server Status';
$server_id = ! empty( $instance['server_id'] ) ? $instance['server_id'] : '';
// Server-Liste holen
$servers = get_option('mcss_servers', []);
?>
<p>
<label for="<?php echo $this->get_field_id( 'title' ); ?>">Titel:</label>
<input class="widefat" id="<?php echo $this->get_field_id( 'title' ); ?>" name="<?php echo $this->get_field_name( 'title' ); ?>" type="text" value="<?php echo esc_attr( $title ); ?>">
</p>
<p>
<label for="<?php echo $this->get_field_id( 'server_id' ); ?>">Server wählen:</label>
<select class="widefat" id="<?php echo $this->get_field_id( 'server_id' ); ?>" name="<?php echo $this->get_field_name( 'server_id' ); ?>">
<option value="">-- Bitte wählen --</option>
<?php foreach ($servers as $srv): ?>
<option value="<?php echo esc_attr($srv['id'] ?? ''); ?>" <?php selected($server_id, $srv['id'] ?? ''); ?>>
<?php echo esc_html($srv['name'] ?? 'Unbenannt'); ?>
</option>
<?php endforeach; ?>
</select>
</p>
<?php
}
// Backend: Speichern
public function update( $new_instance, $old_instance ) {
$instance = array();
$instance['title'] = ( ! empty( $new_instance['title'] ) ) ? sanitize_text_field( $new_instance['title'] ) : '';
$instance['server_id'] = ( ! empty( $new_instance['server_id'] ) ) ? sanitize_text_field( $new_instance['server_id'] ) : '';
return $instance;
}
// Frontend: Ausgabe
public function widget( $args, $instance ) {
echo $args['before_widget'];
if ( ! empty( $instance['title'] ) ) {
echo $args['before_title'] . apply_filters( 'widget_title', $instance['title'] ) . $args['after_title'];
}
$server_id = $instance['server_id'];
$servers = get_option('mcss_servers', []);
$target_srv = null;
// Server suchen
foreach ($servers as $srv) {
if (($srv['id'] ?? '') === $server_id) { $target_srv = $srv; break; }
}
if ($target_srv) {
// Initiale Daten holen
$data = mcss_fetch_server_with_ranks($target_srv);
$is_online = $data['online'] ?? false;
$uid = md5($target_srv['host'] . 'widget'); // Einzigartige ID für JS
// Farben definieren
$bg_online = '#ecfdf5';
$bg_offline = '#fef2f2';
$text_online = '#059669';
$text_offline = '#dc2626';
$dot_online = '#10b981';
$dot_offline = '#ef4444';
$border_online = 'rgba(16, 185, 129, 0.2)';
$border_offline = 'rgba(239, 68, 68, 0.2)';
$anim_name_online = 'mcss-pulse-modern';
$anim_name_offline = 'mcss-pulse-modern-red';
// Werte für den Initialzustand setzen
$current_bg = $is_online ? $bg_online : $bg_offline;
$current_text = $is_online ? $text_online : $text_offline;
$current_dot = $is_online ? $dot_online : $dot_offline;
$current_border = $is_online ? $border_online : $border_offline;
$current_anim = $is_online ? $anim_name_online : $anim_name_offline;
$status_label = $is_online ? 'Online' : 'Offline';
// Inline Animation Definitionen für Robustheit
?>
<style>
@keyframes mcss-pulse-modern {
0% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(16, 185, 129, 0); }
100% { box-shadow: 0 0 0 0 rgba(16, 185, 129, 0); }
}
@keyframes mcss-pulse-modern-red {
0% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4); }
70% { box-shadow: 0 0 0 6px rgba(239, 68, 68, 0); }
100% { box-shadow: 0 0 0 0 rgba(239, 68, 68, 0); }
}
</style>
<!-- Wrapper mit PUREN Inline Styles -->
<div id="mcss-sw-wrapper-<?php echo esc_attr($uid); ?>"
style="display: flex; align-items: center; justify-content: center; width: 93%; padding: 8px; border-radius: 50px; background-color: <?php echo $current_bg; ?>; border: 1px solid <?php echo $current_border; ?>; transition: all 0.3s ease; font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif; cursor: default; margin: 10px 0;">
<span id="mcss-sw-dot-<?php echo esc_attr($uid); ?>"
style="width: 8px; height: 8px; border-radius: 50%; margin-right: 10px; background-color: <?php echo $current_dot; ?>; animation: <?php echo $current_anim; ?> 2s infinite;"></span>
<span id="mcss-sw-text-<?php echo esc_attr($uid); ?>"
style="font-size: 12px; font-weight: 700; letter-spacing: 0.05em; text-transform: uppercase; color: <?php echo $current_text; ?>;">
<?php echo $status_label; ?>
</span>
</div>
<!-- Kleines Skript für Live-Update (alle 3 Sekunden) -->
<script>
(function(){
var uid = "<?php echo esc_js($uid); ?>";
var serverId = "<?php echo esc_js($target_srv['id']); ?>";
// Konstanten Farben
var cOnlineBg = '<?php echo esc_js($bg_online); ?>';
var cOfflineBg = '<?php echo esc_js($bg_offline); ?>';
var cOnlineText = '<?php echo esc_js($text_online); ?>';
var cOfflineText = '<?php echo esc_js($text_offline); ?>';
var cOnlineDot = '<?php echo esc_js($dot_online); ?>';
var cOfflineDot = '<?php echo esc_js($dot_offline); ?>';
var bOnline = '<?php echo esc_js($border_online); ?>';
var bOffline = '<?php echo esc_js($border_offline); ?>';
var aOnline = '<?php echo esc_js($anim_name_online); ?>';
var aOffline = '<?php echo esc_js($anim_name_offline); ?>';
setInterval(function(){
fetch('<?php echo admin_url('admin-ajax.php'); ?>', {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: 'action=mcss_fetch&server_id=' + serverId
})
.then(r => r.json())
.then(d => {
var wrapper = document.getElementById('mcss-sw-wrapper-' + uid);
var dot = document.getElementById('mcss-sw-dot-' + uid);
var txt = document.getElementById('mcss-sw-text-' + uid);
if(wrapper && dot && txt && d) {
if(d.online) {
wrapper.style.backgroundColor = cOnlineBg;
wrapper.style.borderColor = bOnline;
dot.style.backgroundColor = cOnlineDot;
dot.style.animationName = aOnline;
txt.style.color = cOnlineText;
txt.textContent = 'Online';
} else {
wrapper.style.backgroundColor = cOfflineBg;
wrapper.style.borderColor = bOffline;
dot.style.backgroundColor = cOfflineDot;
dot.style.animationName = aOffline;
txt.style.color = cOfflineText;
txt.textContent = 'Offline';
}
}
});
}, 3000);
})();
</script>
<?php
} else {
echo '<p>Bitte Server in den Einstellungen wählen.</p>';
}
echo $args['after_widget'];
}
}
// Widget registrieren
add_action( 'widgets_init', function(){
register_widget( 'MCSS_Sidebar_Status_Widget' );
});
+92 -6
View File
@@ -2,6 +2,10 @@
StatusAPI zeigt den aktuellen Status deines Minecraft-Servers direkt auf deiner Webseite an – inklusive Online/Offline, Version, Ping, Spieleranzahl und Spieler-Avatare. StatusAPI zeigt den aktuellen Status deines Minecraft-Servers direkt auf deiner Webseite an – inklusive Online/Offline, Version, Ping, Spieleranzahl und Spieler-Avatare.
**StatusAPI Repository:** [https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)
---
## Features ## Features
- Zeigt Serverstatus: **Online / Offline** - Zeigt Serverstatus: **Online / Offline**
@@ -12,26 +16,67 @@ StatusAPI zeigt den aktuellen Status deines Minecraft-Servers direkt auf deiner
- Hinweise/Banner können vom Nutzer geschlossen werden - Hinweise/Banner können vom Nutzer geschlossen werden
- Anpassbare Darstellung für verschiedene Layouts - Anpassbare Darstellung für verschiedene Layouts
---
## Wichtige Hinweise ## Wichtige Hinweise
### BungeeCord Plugin ### BungeeCord Plugin (Voraussetzung)
⚠️ **Die [StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI) MUSS auf deinem BungeeCord-Server installiert sein!**
Das Plugin **StatusAPI.jar** muss in den BungeeCord Plugin-Ordner kopiert werden, damit die API korrekt funktioniert. Das Plugin **StatusAPI.jar** muss in den BungeeCord Plugin-Ordner kopiert werden, damit die API korrekt funktioniert.
```text
BungeeCord/
├─ plugins/
│ └─ StatusAPI.jar ← PFLICHT
└─ config.yml
```
**Download:** [https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)
### WordPress Integration ### WordPress Integration
In WordPress muss der API-Port auf **9191** eingestellt sein, damit die Serverdaten korrekt abgerufen werden. 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. Stelle sicher, dass dein Server und die Webseite auf diesen Port zugreifen können.
Die StatusAPI stellt einen HTTP-Endpunkt bereit (Standard: `http://localhost:9191`), den das WordPress-Plugin abfragt.
### Shortcode für WordPress ### Shortcode für WordPress
Um den Serverstatus auf deiner WordPress-Seite anzuzeigen, füge einfach folgenden Shortcode ein: Um den Serverstatus auf deiner WordPress-Seite anzuzeigen, füge einfach folgenden Shortcode ein:
```html ```html
[bungeecord_status id="Bungeecord"] [bungeecord_status id="Bungeecord"]
``` ```
Ersetze `"Bungeecord"` durch die ID deines Servers, falls mehrere Server eingebunden werden. Ersetze `"Bungeecord"` durch die ID deines Servers, falls mehrere Server eingebunden werden.
---
## Installation
### Schritt 1: StatusAPI auf BungeeCord installieren
1. Lade die **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)** herunter
2. Kopiere die **StatusAPI.jar** in den `plugins`-Ordner deines BungeeCord-Servers
3. Starte den BungeeCord-Server neu
4. Die API läuft nun standardmäßig auf Port **9191**
### Schritt 2: WordPress-Plugin einrichten
1. Installiere das WordPress-Plugin für den BungeeCord-Status
2. Gehe zu den Plugin-Einstellungen
3. Trage die **StatusAPI URL** ein (z.B. `http://localhost:9191`)
4. Speichere die Einstellungen
### Schritt 3: Shortcode einbinden
Füge den Shortcode auf einer beliebigen WordPress-Seite ein:
```html
[bungeecord_status id="Bungeecord"]
```
---
## Beispielanzeige ## Beispielanzeige
**Status:** Online **Status:** Online
@@ -42,17 +87,58 @@ Ersetze `"Bungeecord"` durch die ID deines Servers, falls mehrere Server eingebu
**Avatare:** **Avatare:**
![M_Viper Avatar](https://example.com/avatar/M_Viper.png) <!-- Platzhalter – wird im Plugin dynamisch geladen --> ![M_Viper Avatar](https://example.com/avatar/M_Viper.png) <!-- 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. > Der Abstand zwischen dem Text „Spieler:" und den Avataren ist bewusst etwas größer, um die Übersichtlichkeit zu erhöhen.
---
## Nutzung ## Nutzung
1. Shortcode an der gewünschten Stelle einfügen 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 2. Der Serverstatus aktualisiert sich automatisch, sodass die angezeigten Spieler und der Ping immer aktuell sind
3. Alle Daten werden live von der StatusAPI bezogen
---
## Technische Details
- **API-Port:** 9191 (Standard)
- **Update-Intervall:** 2 Sekunden (automatisch)
- **Protokoll:** HTTP/JSON
- **Datenquelle:** StatusAPI auf BungeeCord
---
## Support ## Support
Bei Problemen überprüfe bitte: Bei Problemen überprüfe bitte:
- Ob das Plugin **StatusAPI.jar** korrekt im BungeeCord Plugin-Ordner liegt - Ob das Plugin **[StatusAPI.jar](https://git.viper.ipv64.net/M_Viper/StatusAPI)** korrekt im BungeeCord Plugin-Ordner liegt
- Ob der API-Port in WordPress korrekt auf **9191** eingestellt ist - Ob der API-Port in WordPress korrekt auf **9191** eingestellt ist
- Ob der Server erreichbar ist und Spieler online sind - Ob der BungeeCord-Server läuft und die StatusAPI erreichbar ist
- Ob keine Firewall den Port 9191 blockiert
- Ob die StatusAPI gültige JSON-Daten zurückliefert
### Häufige Fehler
❌ **"Server offline" obwohl Server läuft**
- StatusAPI ist nicht installiert oder läuft nicht
- Falscher Port in WordPress eingestellt
- Firewall blockiert Port 9191
❌ **Keine Spieler werden angezeigt**
- StatusAPI liefert keine Spielerdaten
- API-Verbindung fehlgeschlagen
- WordPress kann die API-URL nicht erreichen
---
## Weitere Informationen
Für detaillierte Informationen zur Installation, Konfiguration und Fehlerbehebung der StatusAPI:
**[https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**
---
**Copyright © 2026 - M_Viper - Alle Rechte vorbehalten**
Die unbefugte Vervielfältigung, Verbreitung oder Weitergabe dieses Plugins ist strafbar und wird rechtlich verfolgt.