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',
];
?>
Server :
Ränge (LuckPerms Zuordnung)
Die Ränge werden automatisch über LuckPerms per RCON abgerufen.
'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 'Server nicht gefunden
';
// 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(); ?>