Files
Minecraft-Server-Status/minecraft-server-status.php

951 lines
53 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Plugin Name: Minecraft Server Status Multi-Server Edition
* Description: Die ultimative Live-Status-Anzeige für deine Minecraft Server mit Echtzeit-Updates für beliebig viele Server.
* Tags: minecraft, server status, player list, multi-server, rcon, luckperms, widget, shortcode, gaming, customizable, live, ping, maintenance, gameserver
* Version: 2.0.1
* Author: M_Viper
* Plugin URI: https://git.viper.ipv64.net/M_Viper/Minecraft-Server-Status
* Author URI: https://m-viper.de
* Requires at least: 6.7.2
* Tested up to: 6.7.2
* Requires PHP: 7.4
* License: GPL2
* Text Domain: wp-multi-mc-server
* Support: [Telegram Support](https://t.me/M_Viper04)
*/
if (!defined('ABSPATH')) exit;
define('MCSS_DIR', plugin_dir_path(__FILE__));
define('MCSS_URL', plugin_dir_url(__FILE__));
require_once MCSS_DIR . 'rcon/Rcon.php';
/* ---------------- Enqueue assets ---------------- */
add_action('admin_enqueue_scripts', function($hook){
global $pagenow;
if ($pagenow === 'admin.php' && isset($_GET['page']) && $_GET['page'] === 'mcss-settings') {
wp_enqueue_media();
wp_enqueue_style('wp-color-picker');
wp_enqueue_script('wp-color-picker');
wp_enqueue_script('mcss-admin-js', MCSS_URL . 'js/admin.js', ['jquery', 'wp-color-picker'], '4.0.1', true);
}
});
add_action('wp_enqueue_scripts', function(){
wp_enqueue_style('mcss-style', MCSS_URL . 'css/style.css', [], '4.0.1');
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '4.0.1', true);
wp_localize_script('mcss-frontend-js', 'mcss_ajax_object', [
'ajax_url' => admin_url('admin-ajax.php'),
'refresh_interval' => max(10, intval(get_option('mcss_cache_ttl', 15))) * 1000
]);
});
/* ---------------- Settings ---------------- */
add_action('admin_menu', function(){
add_options_page('Minecraft Server', 'Minecraft Server', 'manage_options', 'mcss-settings', 'mcss_settings_page');
});
add_action('admin_init', function(){
register_setting('mcss_settings_group', 'mcss_servers', ['sanitize_callback'=>'mcss_sanitize_servers']);
});
function mcss_sanitize_servers($input) {
if (!is_array($input)) return [];
$clean = [];
foreach ($input as $srv) {
if (!is_array($srv)) continue;
$clean[] = [
'id' => sanitize_key($srv['id'] ?? 'srv_'.wp_rand()),
'name' => sanitize_text_field($srv['name'] ?? 'Server'),
'host' => sanitize_text_field($srv['host'] ?? ''),
'query_port' => absint($srv['query_port'] ?? 25565),
'rcon_port' => absint($srv['rcon_port'] ?? 25575),
'rcon_pass' => sanitize_text_field($srv['rcon_pass'] ?? ''),
'player_port' => sanitize_text_field($srv['player_port'] ?? ''),
'hide_port' => !empty($srv['hide_port']),
'show_motd' => !empty($srv['show_motd']),
'cache_ttl' => max(5, absint($srv['cache_ttl'] ?? 15)),
'logo_id' => absint($srv['logo_id'] ?? 0),
'logo_url' => esc_url_raw($srv['logo_url'] ?? ''),
'custom_text' => wp_kses_post($srv['custom_text'] ?? ''),
'ip_color' => sanitize_hex_color($srv['ip_color'] ?? '#1f2937'),
'ct_color' => sanitize_hex_color($srv['ct_color'] ?? '#1e293b'),
'ip_size' => sanitize_text_field($srv['ip_size'] ?? '1.5em'),
'ct_size' => sanitize_text_field($srv['ct_size'] ?? '1.05em'),
'name_color' => sanitize_hex_color($srv['name_color'] ?? '#1e293b'),
'name_size' => sanitize_text_field($srv['name_size'] ?? '1.8em'),
'maintenance_mode' => !empty($srv['maintenance_mode']),
'maintenance_message' => wp_kses_post($srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!'),
'ranks_json' => mcss_sanitize_ranks($srv['ranks_json'] ?? '[]'),
];
}
return $clean;
}
function mcss_sanitize_ranks($input) {
$decoded = json_decode($input, true);
if (!is_array($decoded)) return '[]';
$out = [];
foreach ($decoded as $r) {
if (!is_array($r) || empty($r['name'])) continue;
$out[] = [
'name' => sanitize_text_field($r['name']),
'groups' => sanitize_text_field($r['groups'] ?? ''),
'color' => sanitize_text_field($r['color'] ?? '#6c5ce7')
];
}
return wp_json_encode($out);
}
/* ---------------- Settings Page ---------------- */
function mcss_settings_page() {
$servers = get_option('mcss_servers', []);
if (empty($servers)) {
$servers = [[
'id' => 'default', 'name' => 'Hauptserver', 'host' => '', 'query_port' => 25565,
'rcon_port' => 25575, 'cache_ttl' => 15, 'hide_port' => true, 'show_motd' => true
]];
}
$font_sizes = [
'0.7em' => 'Sehr klein','0.85em' => 'Klein','1em' => 'Normal','1.2em' => 'Etwas größer',
'1.4em' => 'Groß','1.5em' => 'Sehr groß (Standard)','1.7em' => 'Extra groß','2em' => 'Riesig',
'2.5em' => 'Enorm','3em' => 'Gigantisch',
];
?>
<div class="wrap">
<h1>Minecraft Server Einstellungen</h1>
<form method="post" action="options.php">
<?php settings_fields('mcss_settings_group'); ?>
<div id="mcss-servers-repeater">
<?php foreach ($servers as $i => $srv): mcss_render_server_row($srv, $i, $font_sizes); endforeach; ?>
</div>
<p><button type="button" class="button button-primary" id="mcss-add-server">+ Neuen Server hinzufügen</button></p>
<?php submit_button(); ?>
</form>
<h2>Tools</h2>
<p><a class="button" href="<?php echo esc_url(add_query_arg('mcss_test_connection','1', admin_url('options-general.php?page=mcss-settings'))); ?>">RCON testen</a></p>
<?php if (isset($_GET['mcss_test_connection']) && !empty($servers[0])): ?>
<pre style="background:#fff;padding:12px;border-radius:8px;"><?php echo esc_html(print_r(mcss_test_rcon_now($servers[0]), true)); ?></pre>
<?php endif; ?>
</div>
<script>
jQuery(function($){
let counter = <?php echo count($servers); ?>;
// Button-Event-Handler
$('#mcss-add-server').on('click', function(e){
e.preventDefault();
// Neue Server-Zeile erstellen
const newId = 'new_' + counter;
const newIndex = counter;
// HTML für neue Server-Zeile
const newServerHtml = `
<div class="mcss-server-row" style="border:2px solid #ddd;border-radius:12px;padding:20px;margin:20px 0;background:#fdfdfd;position:relative;">
<button type="button" class="notice-dismiss mcss-remove-server" style="position:absolute;top:10px;right:10px;"></button>
<h3>Server ${newIndex + 1}: Neuer Server</h3>
<table class="form-table">
<tr><th>Name (intern)</th><td><input name="mcss_servers[${newIndex}][name]" value="Neuer Server" class="regular-text server-name-input" required></td></tr>
<tr>
<th>Name Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[${newIndex}][name_color]" value="#1e293b" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
<label style="margin-left:20px;">Größe:</label>
<select name="mcss_servers[${newIndex}][name_size]" style="width:200px;margin-left:8px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr><th>Server Host</th><td><input name="mcss_servers[${newIndex}][host]" value="" class="regular-text" required></td></tr>
<tr><th>RCON Port</th><td><input name="mcss_servers[${newIndex}][rcon_port]" type="number" value="25575" class="small-text"></td></tr>
<tr><th>RCON Passwort</th><td><input name="mcss_servers[${newIndex}][rcon_pass]" type="password" value="" class="regular-text"></td></tr>
<tr><th>Cache TTL (Sekunden)</th><td><input name="mcss_servers[${newIndex}][cache_ttl]" type="number" value="15" class="small-text"></td></tr>
<tr><th>MOTD anzeigen?</th><td><input name="mcss_servers[${newIndex}][show_motd]" type="checkbox" value="1" checked /></td></tr>
<tr>
<th><label for="mcss_player_port_${newIndex}"><strong>Spieler-Port (für Kopieren)</strong></label></th>
<td>
<input name="mcss_servers[${newIndex}][player_port]" id="mcss_player_port_${newIndex}" type="text" value="" class="regular-text" style="width:180px;" placeholder="z. B. 25565" />
<p class="description" style="margin-top:8px;">Wird <strong>nicht angezeigt</strong>, aber beim Klick mitkopiert.<br>Leer = kein Port wird kopiert.</p>
</td>
</tr>
<tr>
<th>Port in Adresse ausblenden?</th>
<td><input name="mcss_servers[${newIndex}][hide_port]" type="checkbox" value="1" checked /> <label>Ja zeige nur den Host an</label></td>
</tr>
<tr>
<th><label>Server Logo</label></th>
<td>
<div id="mcss-logo-preview_${newIndex}" style="margin-bottom:12px;">
<img src="<?php echo MCSS_URL; ?>img/default-server-logo.png" style="max-width:150px;max-height:150px;border-radius:8px;" />
</div>
<input type="hidden" name="mcss_servers[${newIndex}][logo_id]" value="0">
<input type="text" name="mcss_servers[${newIndex}][logo_url]" value="" class="regular-text" placeholder="Oder direkte URL...">
<button type="button" class="button mcss-upload-logo-button" id="mcss_upload_logo_button_${newIndex}">Hochladen / auswählen</button>
<button type="button" class="button mcss-remove-logo-button" id="mcss_remove_logo_button_${newIndex}" style="display:none;">Entfernen</button>
</td>
</tr>
<tr>
<th><label>Zusatztext unter der Adresse</label></th>
<td>
<input name="mcss_servers[${newIndex}][custom_text]" value="" class="large-text" placeholder="1.21 Survival • Whitelist aktiv • Discord" />
<p class="description">HTML + Emojis erlaubt.</p>
</td>
</tr>
<tr>
<th>IP-Adresse Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[${newIndex}][ip_color]" value="#1f2937" class="mcss-color-picker" data-default-color="#1f2937" style="width:100px;margin-left:8px;" />
<label style="margin-left:20px;">Größe:</label>
<select name="mcss_servers[${newIndex}][ip_size]" style="width:200px;margin-left:8px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th>Zusatztext Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[${newIndex}][ct_color]" value="#1e293b" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
<label style="margin-left:20px;">Größe:</label>
<select name="mcss_servers[${newIndex}][ct_size]" style="width:200px;margin-left:8px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>"><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
</select>
</td>
</tr>
<tr>
<th><label>Wartungsmodus</label></th>
<td>
<input name="mcss_servers[${newIndex}][maintenance_mode]" type="checkbox" value="1" />
<label>Wartungsmodus aktivieren</label>
<p class="description" style="margin-top:8px;">Wenn aktiviert, wird eine Wartungsnachricht anstelle der Server-Informationen angezeigt.</p>
</td>
</tr>
<tr>
<th><label>Wartungsnachricht</label></th>
<td>
<textarea name="mcss_servers[${newIndex}][maintenance_message]" class="large-text" rows="4" placeholder="Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!">Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!</textarea>
<p class="description">HTML + Emojis erlaubt. Diese Nachricht wird angezeigt, wenn der Wartungsmodus aktiv ist.</p>
</td>
</tr>
</table>
<h2>Ränge (LuckPerms Zuordnung)</h2>
<p>Die Ränge werden automatisch über LuckPerms per RCON abgerufen.</p>
<div id="mcss-ranks-editor_${newIndex}" class="mcss-ranks-editor"></div>
<textarea id="mcss_ranks_json_${newIndex}" name="mcss_servers[${newIndex}][ranks_json]" class="mcss-ranks-json" style="display:none;">[]</textarea>
<input type="hidden" name="mcss_servers[${newIndex}][id]" value="${newId}">
</div>
`;
// HTML zum DOM hinzufügen
$('#mcss-servers-repeater').append(newServerHtml);
counter++;
// Farbpicker für neue Zeile initialisieren
$('#mcss-servers-repeater .mcss-server-row:last .mcss-color-picker').wpColorPicker();
});
// Event-Handler für das Entfernen von Servern
$(document).on('click', '.mcss-remove-server', function(){
$(this).closest('.mcss-server-row').remove();
});
// Farbpicker für bestehende Zeilen initialisieren
$('.mcss-color-picker').wpColorPicker();
});
</script>
<?php
}
function mcss_render_server_row($srv, $i, $font_sizes) {
$logo_id = $srv['logo_id'] ?? 0;
$logo_url = $srv['logo_url'] ?? '';
$current_logo = $logo_id ? wp_get_attachment_image_url($logo_id, 'medium') : $logo_url;
if (!$current_logo) $current_logo = MCSS_URL . 'img/default-server-logo.png';
?>
<div class="mcss-server-row" style="border:2px solid #ddd;border-radius:12px;padding:20px;margin:20px 0;background:#fdfdfd;position:relative;">
<button type="button" class="notice-dismiss mcss-remove-server" style="position:absolute;top:10px;right:10px;"></button>
<h3>Server <?php echo $i+1; ?>: <span class="server-name-display"><?php echo esc_html($srv['name'] ?? ''); ?></span></h3>
<table class="form-table">
<tr><th>Name (intern)</th><td><input name="mcss_servers[<?php echo $i; ?>][name]" value="<?php echo esc_attr($srv['name'] ?? ''); ?>" class="regular-text server-name-input" required></td></tr>
<tr>
<th>Name Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[<?php echo $i; ?>][name_color]" value="<?php echo esc_attr($srv['name_color'] ?? '#1e293b'); ?>" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
<label style="margin-left:20px;">Größe:</label>
<select name="mcss_servers[<?php echo $i; ?>][name_size]" style="width:200px;margin-left:8px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['name_size'] ?? '1.8em', $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
<option value="<?php echo esc_attr($srv['name_size'] ?? '1.8em'); ?>" <?php echo !isset($font_sizes[$srv['name_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['name_size'] ?? '1.8em'); ?>)</option>
</select>
</td>
</tr>
<tr><th>Server Host</th><td><input name="mcss_servers[<?php echo $i; ?>][host]" value="<?php echo esc_attr($srv['host'] ?? ''); ?>" class="regular-text" required></td></tr>
<tr><th>RCON Port</th><td><input name="mcss_servers[<?php echo $i; ?>][rcon_port]" type="number" value="<?php echo esc_attr($srv['rcon_port'] ?? 25575); ?>" class="small-text"></td></tr>
<tr><th>RCON Passwort</th><td><input name="mcss_servers[<?php echo $i; ?>][rcon_pass]" type="password" value="<?php echo esc_attr($srv['rcon_pass'] ?? ''); ?>" class="regular-text"></td></tr>
<tr><th>Cache TTL (Sekunden)</th><td><input name="mcss_servers[<?php echo $i; ?>][cache_ttl]" type="number" value="<?php echo esc_attr($srv['cache_ttl'] ?? 15); ?>" class="small-text"></td></tr>
<tr><th>MOTD anzeigen?</th><td><input name="mcss_servers[<?php echo $i; ?>][show_motd]" type="checkbox" value="1" <?php checked(!empty($srv['show_motd'])); ?> /></td></tr>
<tr>
<th><label for="mcss_player_port"><strong>Spieler-Port (für Kopieren)</strong></label></th>
<td>
<input name="mcss_servers[<?php echo $i; ?>][player_port]" id="mcss_player_port" type="text" value="<?php echo esc_attr($srv['player_port'] ?? ''); ?>" class="regular-text" style="width:180px;" placeholder="z. B. 25565" />
<p class="description" style="margin-top:8px;">Wird <strong>nicht angezeigt</strong>, aber beim Klick mitkopiert.<br>Leer = kein Port wird kopiert.</p>
</td>
</tr>
<tr>
<th>Port in Adresse ausblenden?</th>
<td><input name="mcss_servers[<?php echo $i; ?>][hide_port]" type="checkbox" value="1" <?php checked(!empty($srv['hide_port'])); ?> /> <label>Ja zeige nur den Host an</label></td>
</tr>
<tr>
<th><label>Server Logo</label></th>
<td>
<div id="mcss-logo-preview" style="margin-bottom:12px;">
<img src="<?php echo esc_url($current_logo); ?>" style="max-width:150px;max-height:150px;border-radius:8px;" />
</div>
<input type="hidden" name="mcss_servers[<?php echo $i; ?>][logo_id]" value="<?php echo esc_attr($logo_id); ?>">
<input type="text" name="mcss_servers[<?php echo $i; ?>][logo_url]" value="<?php echo esc_attr($logo_url); ?>" class="regular-text" placeholder="Oder direkte URL...">
<button type="button" class="button mcss-upload-logo-button" id="mcss_upload_logo_button">Hochladen / auswählen</button>
<button type="button" class="button mcss-remove-logo-button" id="mcss_remove_logo_button" <?php echo empty($logo_id) && empty($logo_url) ? 'style="display:none;"' : ''; ?>>Entfernen</button>
</td>
</tr>
<tr>
<th><label>Zusatztext unter der Adresse</label></th>
<td>
<input name="mcss_servers[<?php echo $i; ?>][custom_text]" value="<?php echo esc_attr($srv['custom_text'] ?? ''); ?>" class="large-text" placeholder="1.21 Survival • Whitelist aktiv • Discord" />
<p class="description">HTML + Emojis erlaubt.</p>
</td>
</tr>
<tr>
<th>IP-Adresse Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[<?php echo $i; ?>][ip_color]" value="<?php echo esc_attr($srv['ip_color'] ?? '#1f2937'); ?>" class="mcss-color-picker" data-default-color="#1f2937" style="width:100px;margin-left:8px;" />
<label style="margin-left:20px;">Größe:</label>
<select name="mcss_servers[<?php echo $i; ?>][ip_size]" style="width:200px;margin-left:8px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['ip_size'] ?? '1.5em', $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
<option value="<?php echo esc_attr($srv['ip_size'] ?? '1.5em'); ?>" <?php echo !isset($font_sizes[$srv['ip_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['ip_size'] ?? '1.5em'); ?>)</option>
</select>
</td>
</tr>
<tr>
<th>Zusatztext Schrift</th>
<td>
<label>Farbe:</label>
<input name="mcss_servers[<?php echo $i; ?>][ct_color]" value="<?php echo esc_attr($srv['ct_color'] ?? '#1e293b'); ?>" class="mcss-color-picker" data-default-color="#1e293b" style="width:100px;margin-left:8px;" />
<label style="margin-left:20px;">Größe:</label>
<select name="mcss_servers[<?php echo $i; ?>][ct_size]" style="width:200px;margin-left:8px;">
<?php foreach ($font_sizes as $value => $label): ?>
<option value="<?php echo esc_attr($value); ?>" <?php selected($srv['ct_size'] ?? '1.05em', $value); ?>><?php echo esc_html($label); ?></option>
<?php endforeach; ?>
<option value="<?php echo esc_attr($srv['ct_size'] ?? '1.05em'); ?>" <?php echo !isset($font_sizes[$srv['ct_size'] ?? '']) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($srv['ct_size'] ?? '1.05em'); ?>)</option>
</select>
</td>
</tr>
<tr>
<th><label>Wartungsmodus</label></th>
<td>
<input name="mcss_servers[<?php echo $i; ?>][maintenance_mode]" type="checkbox" value="1" <?php checked(!empty($srv['maintenance_mode'])); ?> />
<label>Wartungsmodus aktivieren</label>
<p class="description" style="margin-top:8px;">Wenn aktiviert, wird eine Wartungsnachricht anstelle der Server-Informationen angezeigt.</p>
</td>
</tr>
<tr>
<th><label>Wartungsnachricht</label></th>
<td>
<textarea name="mcss_servers[<?php echo $i; ?>][maintenance_message]" class="large-text" rows="4" placeholder="Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!"><?php echo esc_textarea($srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!'); ?></textarea>
<p class="description">HTML + Emojis erlaubt. Diese Nachricht wird angezeigt, wenn der Wartungsmodus aktiv ist.</p>
</td>
</tr>
</table>
<h2>Ränge (LuckPerms Zuordnung)</h2>
<p>Die Ränge werden automatisch über LuckPerms per RCON abgerufen.</p>
<div id="mcss-ranks-editor" class="mcss-ranks-editor"></div>
<textarea id="mcss_ranks_json" name="mcss_servers[<?php echo $i; ?>][ranks_json]" class="mcss-ranks-json" style="display:none;"><?php echo esc_textarea($srv['ranks_json'] ?? '[]'); ?></textarea>
<input type="hidden" name="mcss_servers[<?php echo $i; ?>][id]" value="<?php echo esc_attr($srv['id'] ?? ''); ?>">
</div>
<?php
}
/* ---------------- Funktionen ---------------- */
function mcss_test_rcon_now($srv) {
if (empty($srv['host']) || empty($srv['rcon_pass'])) return ['error' => 'Host oder Passwort nicht gesetzt'];
$rcon = new Rcon($srv['host'], $srv['rcon_port'], $srv['rcon_pass'], 3);
$out = ['connected' => false];
if ($rcon->connect()) {
$out['connected'] = true;
$out['list'] = $rcon->sendCommand('list');
$out['version_raw'] = $rcon->sendCommand('version');
$out['lp_listgroups'] = $rcon->sendCommand('lp listgroups');
$rcon->disconnect();
} else $out['error'] = 'RCON Verbindung fehlgeschlagen';
return $out;
}
function mcss_normalize_version($raw) {
$raw = trim((string)$raw);
if ($raw === '') return 'Unbekannt';
$raw = preg_replace('/^\s*v\s*/i', '', $raw);
$softwares = ['spigot','paper','bukkit'];
foreach ($softwares as $soft) {
if (stripos($raw, $soft) !== false) {
if (preg_match('/git-' . preg_quote($soft, '/') . '-(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return ucfirst($soft) . ' ' . $m[1];
if (preg_match('/\b' . preg_quote($soft, '/') . '\b[^\d]{0,10}?(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return ucfirst($soft) . ' ' . $m[1];
if (preg_match('/(\d+\.\d+(?:\.\d+)?)/', $raw, $m)) return ucfirst($soft) . ' ' . $m[1];
return ucfirst($soft) . ' Unbekannt';
}
}
if (preg_match('/\(mc:\s*(\d+\.\d+(?:\.\d+)?)\)/i', $raw, $m)) return 'Vanilla ' . $m[1];
if (preg_match('/minecraft\s+(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return 'Vanilla ' . $m[1];
if (preg_match('/git-(spigot|paper|bukkit)-(\d+\.\d+(?:\.\d+)?)/i', $raw, $m)) return ucfirst($m[1]) . ' ' . $m[2];
if (preg_match('/(\d+\.\d+(?:\.\d+)?)/', $raw, $m)) return $m[1];
return trim($raw);
}
function mcss_fetch_luckperms_groups($srv) {
if (empty($srv['host']) || empty($srv['rcon_pass'])) return [];
$cache_key = 'mcss_lp_groups_' . md5($srv['host'].':'.$srv['rcon_port']);
$cached = get_transient($cache_key);
if ($cached !== false) return $cached;
$rcon = new Rcon($srv['host'], $srv['rcon_port'], $srv['rcon_pass'], 3);
if (!$rcon->connect()) return [];
$groups = [];
$out = $rcon->sendCommand('lp listgroups');
if ($out) {
if (preg_match('/(?:Groups?:\s*)(.+)/i', $out, $m)) {
$names = array_map('trim', explode(',', $m[1]));
foreach ($names as $n) if ($n !== '') $groups[] = $n;
} else {
$lines = preg_split("/\r?\n/", $out);
foreach ($lines as $ln) {
$ln = trim(preg_replace('/^[\-\d\.\s]+/', '', $ln));
if ($ln !== '') $groups[] = $ln;
}
}
}
if (empty($groups)) {
$out2 = $rcon->sendCommand('lp info');
if ($out2 && preg_match_all('/Groups:\s*(.+)/i', $out2, $mm)) {
$names = array_map('trim', explode(',', implode(',', $mm[1])));
foreach ($names as $n) if ($n !== '') $groups[] = $n;
}
}
$groups = array_values(array_unique(array_filter($groups)));
$group_infos = [];
foreach ($groups as $g) {
$info_raw = $rcon->sendCommand('lp group ' . $g . ' info');
$weight = 0; $prefix = null;
if ($info_raw) {
if (preg_match('/weight[:\s]*([\-]?\d+)/i', $info_raw, $wm)) $weight = intval($wm[1]);
elseif (preg_match('/priority[:\s]*([\-]?\d+)/i', $info_raw, $wm2)) $weight = intval($wm2[1]);
if (preg_match('/prefix[:\s]*([^\r\n]+)/i', $info_raw, $pm)) $prefix = trim($pm[1]);
elseif (preg_match('/display name[:\s]*([^\r\n]+)/i', $info_raw, $pm2)) $prefix = trim($pm2[1]);
}
$color = '#6c5ce7';
if ($prefix) { $c = mcss_color_from_prefix($prefix); if ($c) $color = $c; }
$group_infos[] = ['group'=>$g,'weight'=>$weight,'prefix'=>$prefix,'color'=>$color];
}
$rcon->disconnect();
usort($group_infos, fn($a,$b) => $b['weight'] <=> $a['weight']);
$ranks = [];
foreach ($group_infos as $gi) {
$ranks[] = ['name'=>$gi['group'],'groups'=>$gi['group'],'color'=>$gi['color'],'prefix'=>$gi['prefix']];
}
set_transient($cache_key, $ranks, 12 * HOUR_IN_SECONDS);
return $ranks;
}
function mcss_color_from_prefix($prefix) {
if (!$prefix) return null;
if (preg_match('/[§&]([0-9a-fk-or])/i', $prefix, $m)) {
$code = strtolower($m[1]);
$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'];
if (isset($map[$code])) return $map[$code];
}
if (preg_match('/#([0-9a-f]{6})/i', $prefix, $m2)) return '#' . $m2[1];
return null;
}
function mcss_get_uuid_from_name($name) {
if (empty($name) || !preg_match('/^[a-zA-Z0-9_]{3,16}$/', $name)) return false;
$key = 'mcss_uuid_' . strtolower($name);
$cached = get_transient($key);
if ($cached !== false && $cached !== 'invalid') return $cached;
$response = wp_remote_get("https://api.mojang.com/users/profiles/minecraft/" . rawurlencode($name), ['timeout'=>6]);
if (is_wp_error($response) || wp_remote_retrieve_response_code($response) !== 200) {
set_transient($key, 'invalid', 60);
return false;
}
$body = json_decode(wp_remote_retrieve_body($response), true);
if (empty($body['id'])) {
set_transient($key, 'invalid', 86400);
return false;
}
$uuid = $body['id'];
set_transient($key, $uuid, 30*DAY_IN_SECONDS);
return $uuid;
}
function mcss_avatar($input, $size = 64) {
$size = intval($size);
if (preg_match('/^[0-9a-f]{32}$/i', $input) || preg_match('/^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i', $input)) {
$uuid = preg_replace('/-/', '', $input);
} else {
$uuid = mcss_get_uuid_from_name($input);
if (!$uuid) return "https://mc-heads.net/avatar/" . rawurlencode($input) . "/{$size}";
}
return "https://mc-heads.net/avatar/{$uuid}/{$size}";
}
/* ---------------- FUNKTION FÜR PING-MESSUNG ---------------- */
function mcss_ping_server($host, $port = 25565, $timeout = 2) {
$startTime = microtime(true);
// Versuche, eine TCP-Verbindung zum Server herzustellen
$socket = @fsockopen($host, $port, $errno, $errstr, $timeout);
if (!$socket) {
return false; // Server ist nicht erreichbar
}
// Schließe die Verbindung
fclose($socket);
// Berechne die Ping-Zeit in Millisekunden
$endTime = microtime(true);
$ping = round(($endTime - $startTime) * 1000);
return $ping;
}
/* ---------------- Fetch & AJAX ---------------- */
add_action('wp_ajax_mcss_fetch', 'mcss_ajax_fetch');
add_action('wp_ajax_nopriv_mcss_fetch', 'mcss_ajax_fetch');
function mcss_ajax_fetch() {
$id = sanitize_text_field($_POST['server_id'] ?? '');
$servers = get_option('mcss_servers', []);
foreach ($servers as $srv) {
if (($srv['id'] ?? '') === $id) {
wp_send_json(mcss_fetch_server_with_ranks($srv));
}
}
wp_send_json(['online'=>false,'players'=>[],'address'=>'','version'=>'Unbekannt','ping'=>0,'motd'=>'']);
}
function mcss_fetch_server_with_ranks($srv) {
$cache_key = 'mcss_data_' . md5($srv['host'].':'.$srv['rcon_port']);
$cached = get_transient($cache_key);
if ($cached !== false) {
$cached['last_update'] = get_option($cache_key.'_time', time());
return $cached;
}
$result = ['online'=>false,'players'=>[],'address'=>$srv['host'].':'.$srv['rcon_port'],'version'=>'Unbekannt','ping'=>0,'motd'=>'','last_update'=>time()];
$raw_players = [];
if (!empty($srv['rcon_pass'])) {
$rcon = new Rcon($srv['host'], $srv['rcon_port'], $srv['rcon_pass'], 3);
if ($rcon->connect()) {
$list_raw = $rcon->sendCommand('list');
if (preg_match('/: (.*)$/', $list_raw, $m)) {
$entries = array_filter(array_map('trim', explode(',', $m[1])));
foreach ($entries as $entry) {
if (preg_match('/\s+([a-zA-Z0-9_]{3,16})$/', $entry, $n)) {
$clean_name = $n[1];
} else {
$clean_name = trim(preg_replace('/^[^a-zA-Z0-9_]*/', '', $entry));
$clean_name = preg_replace('/[^a-zA-Z0-9_].*$/', '', $clean_name);
}
if ($clean_name && preg_match('/^[a-zA-Z0-9_]{3,16}$/', $clean_name)) {
$raw_players[] = ['name' => $clean_name, 'raw_entry' => $entry];
}
}
}
$result['online'] = true;
$result['version'] = mcss_normalize_version($rcon->sendCommand('version') ?? '');
$rcon->disconnect();
}
}
$api_host = $srv['query_port'] != 25565 ? $srv['host'].':'.$srv['query_port'] : $srv['host'];
$resp = wp_remote_get('https://api.mcsrvstat.us/2/'.rawurlencode($api_host), ['timeout'=>7]);
if (!is_wp_error($resp) && wp_remote_retrieve_response_code($resp)==200) {
$body = json_decode(wp_remote_retrieve_body($resp), true);
if (!empty($body['online'])) {
$result['online'] = true;
if (empty($raw_players) && !empty($body['players']['list']) && is_array($body['players']['list'])) {
foreach ($body['players']['list'] as $pl) {
if (!is_array($pl)) continue;
$name = $pl['name'] ?? '';
$uuid = $pl['uuid'] ?? '';
if ($name === '') continue;
$raw_players[] = ['name' => $name, 'uuid' => $uuid, 'raw_entry' => $name];
}
}
if ($result['version'] === 'Unbekannt') {
$candidate = $body['version'] ?? $body['software'] ?? '';
if ($candidate) $result['version'] = mcss_normalize_version($candidate);
}
if (!empty($srv['show_motd']) && !empty($body['motd']['clean'])) {
$motd = is_array($body['motd']['clean']) ? implode(' ', $body['motd']['clean']) : $body['motd']['clean'];
$result['motd'] = $motd;
}
}
}
// PING-MESSUNG
$ping_port = !empty($srv['player_port']) ? $srv['player_port'] : $srv['query_port'];
$ping_result = mcss_ping_server($srv['host'], $ping_port, 2);
if ($ping_result !== false) {
$result['ping'] = $ping_result;
} else {
// Wenn Ping-Messung fehlschlägt, versuche den Ping aus der API zu verwenden
if (!empty($body['debug']['ping'])) {
$result['ping'] = intval($body['debug']['ping']);
} elseif (!empty($body['ping'])) {
$result['ping'] = intval($body['ping']);
}
}
$lp_groups = mcss_fetch_luckperms_groups($srv);
$players_info = [];
foreach ($raw_players as $p) {
$name = $p['name'];
$uuid = $p['uuid'] ?? mcss_get_uuid_from_name($name);
$raw_entry = $p['raw_entry'] ?? $name;
$prefix = '';
if (preg_match('/^(\[[^\]]+\])/', $raw_entry, $m)) {
$prefix = $m[1];
}
$rank = $prefix ?: 'Spieler';
$color = '#94a3b8';
foreach ($lp_groups as $g) {
if ($g['prefix'] && stripos($prefix, $g['prefix']) !== false) {
$color = $g['color'];
break;
}
}
$players_info[] = [
'name' => $name,
'avatar' => mcss_avatar($uuid ?: $name, 64),
'rank' => $rank,
'color' => $color,
];
}
$result['players'] = $players_info;
$result['version'] = preg_replace('/^\s*v/i', '', trim($result['version']));
set_transient($cache_key, $result, $srv['cache_ttl']);
update_option($cache_key.'_time', time());
return $result;
}
/* ---------------- Shortcode ---------------- */
add_shortcode('minecraft_status', 'mcss_server_card_shortcode');
add_shortcode('minecraft_server_detail', 'mcss_server_card_shortcode');
function mcss_server_card_shortcode($atts = []) {
$atts = shortcode_atts(['id' => ''], $atts);
if (empty($atts['id'])) return 'Fehler: id fehlt';
$servers = get_option('mcss_servers', []);
$srv = null;
foreach ($servers as $s) {
if (($s['id'] ?? '') === $atts['id'] || ($s['name'] ?? '') === $atts['id']) {
$srv = $s; break;
}
}
if (!$srv) return '<p style="color:red;">Server nicht gefunden</p>';
// Prüfen, ob der Wartungsmodus aktiviert ist
$maintenance_mode = !empty($srv['maintenance_mode']);
$maintenance_message = $srv['maintenance_message'] ?? 'Der Server befindet sich derzeit im Wartungsmodus. Wir sind bald wieder für dich da!';
// Wenn Wartungsmodus aktiv ist, nur die Wartungsansicht anzeigen
if ($maintenance_mode) {
$uid = md5($srv['host'] . '|' . ($srv['player_port'] ?? ''));
$logo = $srv['logo_id'] ? wp_get_attachment_image_url($srv['logo_id'], 'full') : ($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png');
ob_start(); ?>
<style>
/* Pulsierende Animation für den Wartungs-Statuspunkt */
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(251, 146, 60, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(251, 146, 60, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(251, 146, 60, 0);
}
}
.mcss-status-maintenance {
animation: pulse 2s infinite;
}
</style>
<div id="mcss-server-widget-<?php echo esc_attr($uid); ?>" class="mcss-server-widget" style="max-width:920px;margin:30px auto;background:#fff;border-radius:20px;overflow:hidden;box-shadow:0 16px 40px rgba(0,0,0,0.12);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;border:1px solid #e2e8f0;position:relative;">
<div style="padding:28px 32px;background:#fef3c7;border-bottom:1px solid #fbbf24;display:flex;align-items:center;gap:20px;">
<img src="<?php echo esc_url($logo); ?>" alt="Logo" loading="lazy" style="width:80px;height:80px;border-radius:16px;box-shadow:0 8px 20px rgba(0,0,0,0.15);" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
<div style="flex:1;">
<h2 style="margin:0 0 8px 0;font-size:<?php echo esc_attr($srv['name_size'] ?? '1.8em'); ?>;color:<?php echo esc_attr($srv['name_color'] ?? '#1e293b'); ?>;font-weight:800;"><?php echo esc_html($srv['name'] ?? 'Minecraft Server'); ?></h2>
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
<span class="mcss-status-maintenance" style="width:14px;height:14px;border-radius:50%;background:#fb923c;box-shadow:0 0 16px rgba(251, 146, 60, 0.5);"></span>
<span style="font-weight:600;color:#92400e;font-size:1em;">Wartungsmodus</span>
</div>
</div>
</div>
<div style="padding:24px 32px;background:#fef3c7;">
<div style="margin-bottom:16px;font-weight:700;color:#92400e;font-size:1.05em;">Wartungshinweis:</div>
<div style="font-size:1.1em;color:#78350f;line-height:1.6;"><?php echo wp_kses_post($maintenance_message); ?></div>
<div style="margin-top:20px;font-size:0.9em;color:#92400e;">
<p>Wir arbeiten daran, den Server so schnell wie möglich wieder verfügbar zu machen. Vielen Dank für deine Geduld!</p>
</div>
</div>
</div>
<?php
return ob_get_clean();
}
// Normaler Modus (keine Wartung)
$data = mcss_fetch_server_with_ranks($srv);
$uid = md5($srv['host'] . '|' . ($srv['player_port'] ?? ''));
$display_address = $srv['host'];
if (!$srv['hide_port'] && !empty($srv['player_port'])) $display_address .= ':'.$srv['player_port'];
$copy_address = $srv['host'] . (!empty($srv['player_port']) ? ':'.$srv['player_port'] : '');
$logo = $srv['logo_id'] ? wp_get_attachment_image_url($srv['logo_id'], 'full') : ($srv['logo_url'] ?: MCSS_URL.'img/default-server-logo.png');
$user_defined_ranks = json_decode($srv['ranks_json'] ?? '', true);
if (!is_array($user_defined_ranks)) {
$user_defined_ranks = [];
}
$lp_present = !empty($user_defined_ranks);
$show_player_count = empty($lp_present);
ob_start(); ?>
<style>
/* Pulsierende Animation für den Online-Statuspunkt */
@keyframes pulse {
0% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0.7);
}
70% {
transform: scale(1);
box-shadow: 0 0 0 10px rgba(16, 185, 129, 0);
}
100% {
transform: scale(0.95);
box-shadow: 0 0 0 0 rgba(16, 185, 129, 0);
}
}
.mcss-status-online {
animation: pulse 2s infinite;
}
</style>
<div id="mcss-server-widget-<?php echo esc_attr($uid); ?>" class="mcss-server-widget" style="max-width:920px;margin:30px auto;background:#fff;border-radius:20px;overflow:hidden;box-shadow:0 16px 40px rgba(0,0,0,0.12);font-family:-apple-system,BlinkMacSystemFont,'Segoe UI',Roboto,sans-serif;border:1px solid #e2e8f0;position:relative;">
<div id="mcss-copy-toast-<?php echo esc_attr($uid); ?>" style="position:absolute;top:20px;right:20px;background:#10b981;color:#fff;padding:12px 24px;border-radius:12px;font-weight:600;font-size:0.95em;opacity:0;transform:translateY(-20px);transition:all .4s ease;z-index:100;box-shadow:0 8px 20px rgba(16,185,129,0.4);">Adresse kopiert!</div>
<div style="padding:28px 32px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;gap:20px;">
<img src="<?php echo esc_url($logo); ?>" alt="Logo" loading="lazy" style="width:80px;height:80px;border-radius:16px;box-shadow:0 8px 20px rgba(0,0,0,0.15);" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
<div style="flex:1;">
<h2 style="margin:0 0 8px 0;font-size:<?php echo esc_attr($srv['name_size'] ?? '1.8em'); ?>;color:<?php echo esc_attr($srv['name_color'] ?? '#1e293b'); ?>;font-weight:800;"><?php echo esc_html($srv['name'] ?? 'Minecraft Server'); ?></h2>
<div style="display:flex;align-items:center;gap:12px;margin-bottom:8px;">
<span id="mcss-address-<?php echo esc_attr($uid); ?>" class="mcss-address" style="font-weight:800;font-size:<?php echo esc_attr($srv['ip_size'] ?? '1.5em'); ?>;color:<?php echo esc_attr($srv['ip_color'] ?? '#1f2937'); ?>;cursor:pointer;user-select:none;" onclick="mcss_copy_address_<?php echo esc_attr($uid); ?>('<?php echo esc_js($copy_address); ?>')">
<?php echo esc_html($display_address); ?>
</span>
<span id="mcss-status-dot-<?php echo esc_attr($uid); ?>" class="<?php echo $data['online'] ? 'mcss-status-online' : ''; ?>" style="width:14px;height:14px;border-radius:50%;background:<?php echo $data['online']?'#10b981':'#ef4444'; ?>;box-shadow:0 0 16px <?php echo $data['online']?'rgba(16,185,129,0.5)':'rgba(239,68,68,0.5)'; ?>;"></span>
<span id="mcss-online-text-<?php echo esc_attr($uid); ?>" style="font-weight:600;color:#475569;font-size:1em;"><?php echo $data['online']?'Online':'Offline'; ?></span>
</div>
<?php if (!empty($srv['custom_text'])): ?>
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="font-size:<?php echo esc_attr($srv['ct_size'] ?? '1.05em'); ?>;color:<?php echo esc_attr($srv['ct_color'] ?? '#1e293b'); ?>;font-weight:600;margin-top:6px;"><?php echo wp_kses_post($srv['custom_text']); ?></div>
<?php endif; ?>
<?php if (!empty($data['motd']) && !empty($srv['show_motd'])): ?>
<div id="mcss-motd-<?php echo esc_attr($uid); ?>" style="margin-top:10px;font-size:0.95em;color:#64748b;"><?php echo esc_html($data['motd']); ?></div>
<?php endif; ?>
</div>
</div>
<div style="padding:18px 32px;background:#f1f5f9;display:flex;justify-content:space-around;flex-wrap:wrap;gap:20px;font-size:0.98em;color:#374151;border-bottom:1px solid #e2e8f0;">
<div><strong>Version:</strong> <span id="mcss-version-<?php echo esc_attr($uid); ?>"><?php echo esc_html($data['version']); ?></span></div>
<?php if ($show_player_count): ?>
<div><strong>Spieler:</strong> <span id="mcss-players-count-<?php echo esc_attr($uid); ?>"><?php echo count($data['players']); ?></span></div>
<?php else: ?>
<div><strong>Rang:</strong> <span id="mcss-players-count-<?php echo esc_attr($uid); ?>"><?php echo esc_html($user_defined_ranks[0]['name'] ?? 'Spieler'); ?></span></div>
<?php endif; ?>
<div><strong>Ping:</strong> <span id="mcss-ping-<?php echo esc_attr($uid); ?>"><?php echo intval($data['ping']); ?></span> ms</div>
</div>
<div style="padding:24px 32px;">
<div style="margin-bottom:16px;font-weight:700;color:#1f2937;font-size:1.05em;">Spieler online:</div>
<div id="mcss-player-grid-<?php echo esc_attr($uid); ?>" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(80px,1fr));gap:16px;">
<?php if (!empty($data['players'])): foreach ($data['players'] as $p): ?>
<div style="text-align:center;">
<img src="<?php echo esc_url($p['avatar']); ?>" alt="<?php echo esc_attr($p['name']); ?>" loading="lazy" style="width:56px;height:56px;border-radius:12px;margin-bottom:8px;" onerror="this.src='<?php echo MCSS_URL.'img/default-server-logo.png'; ?>'" />
<div style="font-weight:600;font-size:0.88em;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;"><?php echo esc_html($p['name']); ?></div>
<div style="font-size:0.8em;color:<?php echo esc_attr($p['color']); ?>;margin-top:4px;"><?php echo esc_html($p['rank']); ?></div>
</div>
<?php endforeach; else: ?>
<div style="grid-column:1/-1;text-align:center;color:#94a3b8;padding:30px 0;font-size:1.1em;">Keine Spieler online</div>
<?php endif; ?>
</div>
</div>
</div>
<script>
function mcss_copy_address_<?php echo esc_attr($uid); ?>(text) {
navigator.clipboard.writeText(text).then(function(){
var toast = document.getElementById('mcss-copy-toast-<?php echo esc_attr($uid); ?>');
if(!toast) return;
toast.style.opacity = '1';
toast.style.transform = 'translateY(0)';
setTimeout(function(){
toast.style.opacity = '0';
toast.style.transform = 'translateY(-20px)';
}, 2000);
}).catch(function(){});
}
(function(){
var uid = '<?php echo esc_js($uid); ?>';
var ajaxUrl = (typeof mcss_ajax_object !== 'undefined' && mcss_ajax_object.ajax_url) ? mcss_ajax_object.ajax_url : '<?php echo admin_url('admin-ajax.php'); ?>';
var interval = (typeof mcss_ajax_object !== 'undefined' && mcss_ajax_object.refresh_interval) ? parseInt(mcss_ajax_object.refresh_interval,10) : 15000;
interval = Math.max(10000, interval);
function updateWidgetData() {
var body = new URLSearchParams();
body.append('action', 'mcss_fetch');
body.append('server_id', '<?php echo esc_js($srv['id']); ?>');
fetch(ajaxUrl, {
method: 'POST',
headers: {'Content-Type':'application/x-www-form-urlencoded'},
body: body.toString()
})
.then(r => r.json())
.then(data => {
if(!data) return;
// Status-Dot + Online-Text
var dot = document.getElementById('mcss-status-dot-' + uid);
var onlineText = document.getElementById('mcss-online-text-' + uid);
if(dot) {
dot.style.background = data.online ? '#10b981' : '#ef4444';
dot.style.boxShadow = data.online ? '0 0 16px rgba(16,185,129,0.5)' : '0 0 16px rgba(239,68,68,0.5)';
// Animation basierend auf dem Online-Status hinzufügen/entfernen
if(data.online) {
dot.classList.add('mcss-status-online');
} else {
dot.classList.remove('mcss-status-online');
}
}
if(onlineText) onlineText.textContent = data.online ? 'Online' : 'Offline';
// Version
var verEl = document.getElementById('mcss-version-' + uid);
if(verEl) verEl.textContent = (data.version || '');
// Spieleranzahl / Rang
var playersCnt = document.getElementById('mcss-players-count-' + uid);
if(playersCnt) {
<?php if ($show_player_count): ?>
playersCnt.textContent = (data.players ? data.players.length : 0);
<?php else: ?>
playersCnt.textContent = data.players?.[0]?.rank ?? 'Spieler';
<?php endif; ?>
}
// Ping
var pingEl = document.getElementById('mcss-ping-' + uid);
if(pingEl) pingEl.textContent = (data.ping || 0);
// MOTD
var motdEl = document.getElementById('mcss-motd-' + uid);
if(motdEl) {
if(data.motd && data.motd.length) {
motdEl.style.display = '';
motdEl.textContent = data.motd;
} else {
motdEl.style.display = 'none';
}
}
// Spieler-Grid neu bauen
var grid = document.getElementById('mcss-player-grid-' + uid);
if(grid) {
if(data.players && data.players.length) {
var html = '';
data.players.forEach(function(p){
var safeAvatar = p.avatar || '';
var safeName = p.name || '';
var safeRank = p.rank || '';
var safeColor = p.color || '#6c5ce7';
html += '<div style="text-align:center;">' +
'<img src="'+ safeAvatar +'" alt="'+ safeName +'" loading="lazy" style="width:56px;height:56px;border-radius:12px;margin-bottom:8px;" onerror="this.src=\'<?php echo esc_js(MCSS_URL . 'img/default-server-logo.png'); ?>\'" />' +
'<div style="font-weight:600;font-size:0.88em;color:#1f2937;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;">'+ escapeHtml(safeName) +'</div>' +
'<div style="font-size:0.8em;color:'+ safeColor +';margin-top:4px;">'+ escapeHtml(safeRank) +'</div>' +
'</div>';
});
grid.innerHTML = html;
} else {
grid.innerHTML = '<div style="grid-column:1/-1;text-align:center;color:#94a3b8;padding:30px 0;font-size:1.1em;">Keine Spieler online</div>';
}
}
})
.catch(() => {});
}
function escapeHtml(text) {
if(!text) return '';
return text.replace(/[&<>"'=\/]/g, function(s) {
return {'&':'&amp;','<':'&lt;','>':'&gt;','"':'&quot;',"'":'&#39;','/':'&#x2F;','=':'&#x3D;','':'&#x60;'}[s];
});
}
setInterval(updateWidgetData, interval);
setTimeout(updateWidgetData, 700);
})();
</script>
<?php
return ob_get_clean();
}