9 Commits
3.6.2 ... 3.6.4

7 changed files with 961 additions and 731 deletions

Binary file not shown.

View File

@@ -11,17 +11,46 @@
}
* { 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 {
margin: 0;
padding: 0;
font-family: Inter, Arial, sans-serif;
background: var(--bg);
color: var(--text);
overflow-x: hidden;
}
.root {
width: 520px;
padding: 12px;
display: flex;
flex-direction: column;
}
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; }
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;
align-items: flex-start;
}
.server-list {
@@ -50,12 +79,17 @@ header .actions button { background:transparent; border:1px solid var(--muted);
.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; }
#serversContainer {
list-style: none;
margin: 0;
padding: 0;
}
.server-item {
display: flex;
align-items: center;
justify-content: space-between;
padding:6px;
padding: 8px;
margin-bottom: 6px;
border-radius: 6px;
cursor: pointer;
@@ -64,157 +98,120 @@ header .actions button { background:transparent; border:1px solid var(--muted);
}
.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 .meta .url { font-size: 11px; color: var(--muted); }
.server-item .status-bubble {
font-size:12px;
padding:3px 6px;
font-size: 11px;
padding: 2px 8px;
border-radius: 999px;
color: #fff;
transition: background-color 0.3s ease;
}
.placeholder { color:var(--muted); padding:12px; }
.placeholder { color: var(--muted); padding: 12px; font-size: 13px; }
.hidden { display: none; }
/* --- Detail Header (Zeile 1) --- */
.detail-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 8px;
margin-bottom: 12px;
}
.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;
}
.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;
font-size: 13px;
}
/* --- Pulsierender Punkt Animation --- */
.pulsing-dot {
width: 10px;
height: 10px;
border-radius: 50%;
background-color: var(--offline); /* Standard Offline Rot */
width: 10px; height: 10px; border-radius: 50%;
background-color: var(--offline);
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 */
background-color: var(--online);
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-green { 0% {box-shadow: 0 0 0 0 rgba(34, 197, 94, 0.7);} 70% {box-shadow: 0 0 0 8px 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 8px rgba(239, 68, 68, 0);} 100% {box-shadow: 0 0 0 0 rgba(239, 68, 68, 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;
padding: 10px;
border-radius: var(--radius);
margin-bottom: 16px;
margin-bottom: 12px;
font-size: 13px;
}
/* ---------------------------------- */
.playerList {
list-style: none;
padding-left:16px;
max-height:140px;
overflow:auto;
margin:6px 0;
padding: 10px 0;
margin: 0;
display: flex;
flex-wrap: wrap;
gap:8px;
gap: 12px;
justify-content: center; /* Das hier setzt die Köpfe wieder in die Mitte */
}
.playerList li {
.player-item {
position: relative;
display: flex;
flex-direction: column;
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;
width: 42px; height: 42px;
border-radius: 6px;
border:none;
cursor:pointer;
transition: opacity 0.2s ease, transform 0.1s ease;
transition: transform 0.2s ease;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
border: 2px solid rgba(255,255,255,0.1);
}
.detailButtons button:hover { opacity: 0.9; transform: translateY(-1px); }
.detailButtons button:active { transform: translateY(0); }
.player-avatar:hover {
transform: scale(1.1);
border-color: var(--accent);
}
.player-hover-info {
position: absolute;
bottom: -28px;
background: rgba(0, 0, 0, 0.95);
color: white;
padding: 3px 10px;
border-radius: 4px;
font-size: 11px;
white-space: nowrap;
pointer-events: none;
opacity: 0;
transform: translateY(5px);
transition: all 0.2s ease;
z-index: 100;
border: 1px solid rgba(255,255,255,0.2);
}
.player-item:hover .player-hover-info {
opacity: 1;
transform: translateY(0);
}
.detailButtons { display: flex; gap: 8px; margin-top: 15px; border-top: 1px solid rgba(255,255,255,0.05); padding-top: 10px; }
.detailButtons button { padding: 6px 12px; border-radius: 6px; border: none; cursor: pointer; font-size: 12px; font-weight: 600; }
#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);
}
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: rgba(255,255,255,0.1); border-radius: 10px; }
::-webkit-scrollbar-thumb:hover { background: var(--accent); }

View File

@@ -33,7 +33,6 @@
<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>
@@ -45,7 +44,6 @@
</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>

View File

@@ -1,3 +1,4 @@
//popup.js
const $ = id => document.getElementById(id);
const uid = () => 'srv_' + Math.random().toString(36).slice(2,9);
@@ -50,7 +51,37 @@ async function init() {
adjustDetailLayout();
}
// --- Settings Visibility ---
// --- NEU: Minecraft Color Parser (Eingebaut ohne Löschung) ---
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'
};
let html = text.replace(/[<>]/g, '');
Object.keys(map).forEach(code => {
const color = map[code];
const regex = new RegExp(code, 'g');
html = html.replace(regex, `</span><span style="color: ${color}">`);
});
return `<span>${html}</span>`.replace(/&l/g, '<b>').replace(/&r/g, '</b>');
}
// --- 3D Avatar URL Generator ---
function get3DAvatarUrl(playerName, uuid = null) {
const isBedrock = playerName.includes('.') || (uuid && uuid.startsWith('xuid'));
if (isBedrock) {
if (uuid && uuid.length > 0) {
return `https://mc-heads.net/head/${encodeURIComponent(uuid)}/64`;
}
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
} else {
return `https://mc-heads.net/head/${encodeURIComponent(playerName)}/64`;
}
}
async function loadSettingsVisibility() {
const obj = await chrome.storage.local.get(['settingsVisible']);
settingsVisible = obj.settingsVisible !== undefined ? obj.settingsVisible : false;
@@ -69,7 +100,6 @@ async function toggleSettings() {
adjustDetailLayout();
}
// --- Storage ---
async function loadServersFromStorage() {
const obj = await chrome.storage.local.get(['servers']);
servers = obj.servers || [];
@@ -86,7 +116,6 @@ async function loadStatusesFromStorage() {
statuses = obj.serverStatuses || {};
}
// --- Layout ---
function adjustDetailLayout() {
const settingsHidden = settingsForm.classList.contains('hidden');
detailContent.classList.toggle('full-width', settingsHidden);
@@ -95,51 +124,44 @@ function adjustDetailLayout() {
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);
updateServerListStatuses();
}
// --- Render Detail ---
// --- Fortsetzung popup.js ---
function renderDetail(id) {
if (!id) {
noSelection.classList.remove('hidden');
@@ -154,26 +176,17 @@ function renderDetail(id) {
}
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);
const statusChanged = force || !prevSt || (prevSt.ok !== st?.ok) || (prevSt.data?.online !== st?.data?.online);
if (!st || !st.ok || !st.data) {
if (statusChanged) {
@@ -186,105 +199,80 @@ function updateDetailForServer(srv, force = false) {
}
} 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');
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;
}
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;
}
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;
}
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);
let playersChanged = force || JSON.stringify(currentPlayers) !== JSON.stringify(prevPlayers);
if (playersChanged) updatePlayerList(currentPlayers);
}
if (playersChanged) {
updatePlayerList(currentPlayers);
}
}
previousStatuses[srv.id] = st ? JSON.parse(JSON.stringify(st)) : null;
}
// --- Spielerliste ---
// --- Spielerliste mit Prefix-Hover und Farbsupport ---
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);
li.className = 'player-item'; // CSS Klasse für den Hover-Container
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);
let name = '';
let uuid = null;
let prefix = '';
if (typeof p === 'object') {
name = p.name || p.username || p.player || '';
uuid = p.uuid || null;
prefix = p.prefix || '';
} 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);
name = String(p);
}
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`;
};
// Das Hover-Element mit farbigem Prefix + Name
const info = document.createElement('div');
info.className = 'player-hover-info';
info.innerHTML = `${parseMinecraftColors(prefix)} ${name}`.trim();
li.appendChild(img);
li.appendChild(info);
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)';
@@ -295,39 +283,19 @@ function updateServerListStatuses() {
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
};
if (!url && !wpSite) 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 = '';
inputName.value = inputUrl.value = inputWpSite.value = inputWpServerId.value = '';
renderServerList();
}
@@ -335,43 +303,26 @@ 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;
if (!selectedId || !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);
}
try { chrome.runtime.sendMessage({ cmd: 'refreshNow' }); } catch(e) {}
}
// --- Storage Listener ---
chrome.storage.onChanged.addListener((changes, area) => {
if (area === 'local' && changes.serverStatuses) {
statuses = changes.serverStatuses.newValue || {};

View File

@@ -3,7 +3,7 @@
* Plugin Name: Minecraft BungeeCord Status Network Edition
* Description: Der ultimative Live-Status für dein BungeeCord Netzwerk (Border None Fix).
* Tags: minecraft, bungeecord, server status, player list
* Version: 3.6.1
* Version: 3.6.4
* Author: M_Viper
* Requires at least: 6.0
* Requires PHP: 7.4
@@ -59,6 +59,23 @@ function mcss_format_minecraft_colors($text) {
return $formatted;
}
/* ---------------- HELPER: 3D AVATAR URL ---------------- */
function mcss_get_3d_avatar($name, $uuid = null) {
// Bedrock-Spieler erkennen (Name enthält Punkt oder UUID beginnt mit xuid)
$is_bedrock = strpos($name, '.') !== false || ($uuid && strpos($uuid, 'xuid') === 0);
if ($is_bedrock) {
// Bedrock: mc-heads.net mit 3D Head (nutze UUID falls vorhanden)
if ($uuid && !empty($uuid)) {
return 'https://mc-heads.net/head/' . rawurlencode($uuid) . '/100';
}
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';
}
}
/* ---------------- AUTO UPDATE (MCSS) ---------------- */
if ( ! class_exists( 'MCSS_Auto_Update' ) ) {
class MCSS_Auto_Update {
@@ -547,10 +564,12 @@ function mcss_fetch_server_with_ranks($srv) {
if (is_array($player_data)) {
$name = $player_data['name'];
$prefix = $player_data['prefix'] ?? '';
$uuid = $player_data['uuid'] ?? null;
} else {
// Fallback für alte API
$name = $player_data;
$prefix = '';
$uuid = null;
}
// 1. Prefix mit Farben konvertieren
@@ -566,9 +585,13 @@ function mcss_fetch_server_with_ranks($srv) {
$display_html = $name_html;
}
// 4. 3D Avatar URL generieren (mit Bedrock-Support)
$avatar_url = mcss_get_3d_avatar($name, $uuid);
$players_info[] = [
'name' => $name,
'avatar' => "https://mc-heads.net/avatar/" . rawurlencode($name) . "/64",
'uuid' => $uuid,
'avatar' => $avatar_url,
'prefix' => $prefix,
'display_html' => $display_html,
'rank' => $prefix ?: 'Spieler',
@@ -670,7 +693,7 @@ function mcss_shortcode($atts) {
$widget_width = "650px";
$widget_padding = "15px";
$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'];
if (empty($srv['hide_port']) && !empty($srv['player_port_copy'])) {
@@ -756,13 +779,13 @@ function mcss_shortcode($atts) {
<!-- PLAYER GRID -->
<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 foreach ($data['players'] as $p): ?>
<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;">
<div style="font-size:0.75em;"><?php echo $p['display_html']; ?></div>
<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;margin-top:4px;"><?php echo $p['display_html']; ?></div>
</div>
<?php endforeach; ?>
<?php else: ?>
@@ -847,8 +870,8 @@ function mcss_shortcode($atts) {
d.players.forEach(function(p){
var content = p.display_html ? p.display_html : p.name;
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;">' +
'<div style="font-size:0.75em;">' + content + '</div>' +
'<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;margin-top:4px;">' + content + '</div>' +
'</div>';
});
} else {
@@ -875,3 +898,184 @@ function mcss_shortcode($atts) {
<?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' );
});

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 Repository:** [https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)
---
## Features
- 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
- Anpassbare Darstellung für verschiedene Layouts
---
## 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.
```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
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.
Die StatusAPI stellt einen HTTP-Endpunkt bereit (Standard: `http://localhost:9191`), den das WordPress-Plugin abfragt.
### 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.
---
## 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
**Status:** Online
@@ -42,17 +87,52 @@ Ersetze `"Bungeecord"` durch die ID deines Servers, falls mehrere Server eingebu
**Avatare:**
![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
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
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
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 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)**