Dateien nach "/" hochladen
This commit is contained in:
694
minecraft-server-status.php
Normal file
694
minecraft-server-status.php
Normal file
@@ -0,0 +1,694 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin Name: Minecraft Server Status
|
||||
* Description: Minecraft Serverstatus – Live-Update, PREFIX wird als Rang angezeigt ([Owner], [VIP], etc.), sonst "Spieler"
|
||||
* Version: 1.9.23
|
||||
* Author: M_Viper
|
||||
* Requires at least: 6.7.2
|
||||
* Tested up to: 6.7.2
|
||||
* Requires PHP: 7.4
|
||||
* License: GPL2
|
||||
* Text Domain: wp-multi-mc-server
|
||||
*/
|
||||
|
||||
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'], '1.9.23', true);
|
||||
}
|
||||
});
|
||||
|
||||
add_action('wp_enqueue_scripts', function(){
|
||||
wp_enqueue_style('mcss-style', MCSS_URL . 'css/style.css', [], '1.9.23');
|
||||
wp_enqueue_script('mcss-frontend-js', MCSS_URL . 'js/mcss-frontend.js', ['jquery'], '1.9.23', 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 registrieren ---------------- */
|
||||
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_host', ['sanitize_callback'=>'sanitize_text_field']);
|
||||
register_setting('mcss_settings_group', 'mcss_rcon_port', ['sanitize_callback'=>'intval']);
|
||||
register_setting('mcss_settings_group', 'mcss_rcon_pass', ['sanitize_callback'=>'sanitize_text_field']);
|
||||
register_setting('mcss_settings_group', 'mcss_cache_ttl', ['sanitize_callback'=>'intval']);
|
||||
register_setting('mcss_settings_group', 'mcss_show_motd', ['sanitize_callback'=>'boolval']);
|
||||
register_setting('mcss_settings_group', 'mcss_ranks_json', ['sanitize_callback'=>'mcss_sanitize_ranks']);
|
||||
register_setting('mcss_settings_group', 'mcss_server_logo_id', ['sanitize_callback'=>'absint']);
|
||||
register_setting('mcss_settings_group', 'mcss_server_logo_url', ['sanitize_callback'=>'esc_url_raw']);
|
||||
register_setting('mcss_settings_group', 'mcss_custom_text', ['sanitize_callback'=>'wp_kses_post']);
|
||||
register_setting('mcss_settings_group', 'mcss_ip_color', ['default' => '#1f2937', 'sanitize_callback' => 'sanitize_hex_color']);
|
||||
register_setting('mcss_settings_group', 'mcss_ct_color', ['default' => '#1e293b', 'sanitize_callback' => 'sanitize_hex_color']);
|
||||
register_setting('mcss_settings_group', 'mcss_ip_size', ['default' => '1.5em', 'sanitize_callback' => 'sanitize_text_field']);
|
||||
register_setting('mcss_settings_group', 'mcss_ct_size', ['default' => '1.05em', 'sanitize_callback' => 'sanitize_text_field']);
|
||||
register_setting('mcss_settings_group', 'mcss_hide_port', ['default' => true, 'sanitize_callback' => 'rest_sanitize_boolean']);
|
||||
register_setting('mcss_settings_group', 'mcss_player_port', ['default' => '', 'sanitize_callback' => 'sanitize_text_field']);
|
||||
});
|
||||
|
||||
/* Sanitize Ranks */
|
||||
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() {
|
||||
$logo_id = get_option('mcss_server_logo_id', 0);
|
||||
$logo_url = get_option('mcss_server_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';
|
||||
$custom_text = get_option('mcss_custom_text', '');
|
||||
$ip_color = get_option('mcss_ip_color', '#1f2937');
|
||||
$ct_color = get_option('mcss_ct_color', '#1e293b');
|
||||
$ip_size = get_option('mcss_ip_size', '1.5em');
|
||||
$ct_size = get_option('mcss_ct_size', '1.05em');
|
||||
$hide_port = get_option('mcss_hide_port', true);
|
||||
$player_port = get_option('mcss_player_port', '');
|
||||
|
||||
$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'); ?>
|
||||
<table class="form-table">
|
||||
<tr><th>Server Host</th><td><input name="mcss_host" value="<?php echo esc_attr(get_option('mcss_host','')); ?>" class="regular-text" /></td></tr>
|
||||
<tr><th>RCON Port</th><td><input name="mcss_rcon_port" type="number" value="<?php echo esc_attr(get_option('mcss_rcon_port',25575)); ?>" class="small-text" /></td></tr>
|
||||
<tr><th>RCON Passwort</th><td><input name="mcss_rcon_pass" type="password" value="<?php echo esc_attr(get_option('mcss_rcon_pass','')); ?>" class="regular-text" /></td></tr>
|
||||
<tr><th>Cache TTL (Sekunden)</th><td><input name="mcss_cache_ttl" type="number" value="<?php echo esc_attr(get_option('mcss_cache_ttl',15)); ?>" class="small-text" /></td></tr>
|
||||
<tr><th>MOTD anzeigen?</th><td><input name="mcss_show_motd" type="checkbox" value="1" <?php checked(get_option('mcss_show_motd',true),true); ?> /></td></tr>
|
||||
|
||||
<tr>
|
||||
<th><label for="mcss_player_port"><strong>Spieler-Port (für Kopieren)</strong></label></th>
|
||||
<td>
|
||||
<input name="mcss_player_port" type="text" id="mcss_player_port" value="<?php echo esc_attr($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_hide_port" type="checkbox" value="1" <?php checked($hide_port, true); ?> /> <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_server_logo_id" id="mcss_server_logo_id" value="<?php echo esc_attr($logo_id); ?>">
|
||||
<input type="text" name="mcss_server_logo_url" id="mcss_server_logo_url" value="<?php echo esc_attr($logo_url); ?>" class="regular-text" placeholder="Oder direkte URL...">
|
||||
<button type="button" class="button" id="mcss_upload_logo_button">Hochladen / auswählen</button>
|
||||
<button type="button" class="button" id="mcss_remove_logo_button" <?php echo empty($logo_id) && empty($logo_url) ? 'style="display:none;"' : ''; ?>>Entfernen</button>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th><label for="mcss_custom_text">Zusatztext unter der Adresse</label></th>
|
||||
<td>
|
||||
<input type="text" name="mcss_custom_text" id="mcss_custom_text" value="<?php echo esc_attr($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 type="text" name="mcss_ip_color" value="<?php echo esc_attr($ip_color); ?>" 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_ip_size" style="width:200px;margin-left:8px;">
|
||||
<?php foreach ($font_sizes as $value => $label): ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($ip_size, $value); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<option value="<?php echo esc_attr($ip_size); ?>" <?php echo !array_key_exists($ip_size, $font_sizes) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($ip_size); ?>)</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr>
|
||||
<th>Zusatztext – Schrift</th>
|
||||
<td>
|
||||
<label>Farbe:</label>
|
||||
<input type="text" name="mcss_ct_color" value="<?php echo esc_attr($ct_color); ?>" 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_ct_size" style="width:200px;margin-left:8px;">
|
||||
<?php foreach ($font_sizes as $value => $label): ?>
|
||||
<option value="<?php echo esc_attr($value); ?>" <?php selected($ct_size, $value); ?>><?php echo esc_html($label); ?></option>
|
||||
<?php endforeach; ?>
|
||||
<option value="<?php echo esc_attr($ct_size); ?>" <?php echo !array_key_exists($ct_size, $font_sizes) ? 'selected' : ''; ?>>Benutzerdefiniert (<?php echo esc_html($ct_size); ?>)</option>
|
||||
</select>
|
||||
</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"></div>
|
||||
<textarea id="mcss_ranks_json" name="mcss_ranks_json" style="display:none;"><?php echo esc_textarea(get_option('mcss_ranks_json','[]')); ?></textarea>
|
||||
|
||||
<?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'])): ?>
|
||||
<pre style="background:#fff;padding:12px;border-radius:8px;"><?php echo esc_html(print_r(mcss_test_rcon_now(), true)); ?></pre>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
jQuery(function($){
|
||||
$('.mcss-color-picker').wpColorPicker();
|
||||
});
|
||||
</script>
|
||||
<?php
|
||||
}
|
||||
|
||||
/* ---------------- Hilfsfunktionen ---------------- */
|
||||
function mcss_test_rcon_now() {
|
||||
$host = get_option('mcss_host', '');
|
||||
$port = intval(get_option('mcss_rcon_port', 25575));
|
||||
$pass = get_option('mcss_rcon_pass', '');
|
||||
if (empty($host) || empty($pass)) return ['error' => 'Host oder Passwort nicht gesetzt'];
|
||||
$rcon = new Rcon($host, $port, $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);
|
||||
}
|
||||
|
||||
/* ---------------- LuckPerms helper ---------------- */
|
||||
function mcss_fetch_luckperms_groups() {
|
||||
$host = get_option('mcss_host', '');
|
||||
$port = intval(get_option('mcss_rcon_port', 25575));
|
||||
$pass = get_option('mcss_rcon_pass', '');
|
||||
if (empty($host) || empty($pass)) return [];
|
||||
|
||||
$cache_key = 'mcss_lp_groups_' . md5($host . ':' . $port);
|
||||
$cached = get_transient($cache_key);
|
||||
if ($cached !== false) return $cached;
|
||||
|
||||
$rcon = new Rcon($host, $port, $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;
|
||||
}
|
||||
|
||||
/* ---------------- UUID-Resolver & Avatar ---------------- */
|
||||
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,
|
||||
'user-agent' => 'MCSS-Plugin/1.9'
|
||||
]);
|
||||
|
||||
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', 24 * HOUR_IN_SECONDS);
|
||||
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}";
|
||||
}
|
||||
|
||||
/* ---------------- Hauptfunktion – PREFIX ALS RANG! ---------------- */
|
||||
add_action('wp_ajax_nopriv_mcss_fetch', 'mcss_ajax_fetch');
|
||||
add_action('wp_ajax_mcss_fetch', 'mcss_ajax_fetch');
|
||||
function mcss_ajax_fetch() {
|
||||
wp_send_json(mcss_fetch_server_with_ranks());
|
||||
}
|
||||
|
||||
function mcss_fetch_server_with_ranks() {
|
||||
$host = get_option('mcss_host', '');
|
||||
if (empty($host)) return ['online'=>false,'players'=>[],'address'=>'','version'=>'Unbekannt','ping'=>0,'motd'=>'','last_update'=>time()];
|
||||
|
||||
$port = intval(get_option('mcss_rcon_port', 25575));
|
||||
$pass = get_option('mcss_rcon_pass', '');
|
||||
$ttl = max(5, intval(get_option('mcss_cache_ttl', 15)));
|
||||
$show_motd = (bool)get_option('mcss_show_motd', true);
|
||||
|
||||
$transient_key = 'mcss_data_' . md5($host . ':' . $port);
|
||||
$cached = get_transient($transient_key);
|
||||
if ($cached !== false) {
|
||||
$cached['last_update'] = get_option($transient_key . '_time', time());
|
||||
return $cached;
|
||||
}
|
||||
|
||||
$result = [
|
||||
'online' => false,
|
||||
'players' => [],
|
||||
'address' => $host . ':' . $port,
|
||||
'version' => 'Unbekannt',
|
||||
'ping' => 0,
|
||||
'motd' => '',
|
||||
'last_update' => time()
|
||||
];
|
||||
|
||||
$raw_players = [];
|
||||
|
||||
if (!empty($pass)) {
|
||||
$rcon = new Rcon($host, $port, $pass, 3);
|
||||
if ($rcon->connect()) {
|
||||
$list_raw = $rcon->sendCommand('list');
|
||||
if (preg_match('/: (.*)$/', $list_raw, $m)) {
|
||||
$list = trim($m[1]);
|
||||
$entries = array_filter(array_map('trim', explode(',', $list)));
|
||||
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 = $port !== 25565 ? $host . ':' . $port : $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'])) {
|
||||
$raw_players = [];
|
||||
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);
|
||||
}
|
||||
$result['ping'] = intval($body['debug']['ping'] ?? $body['ping'] ?? 0);
|
||||
if ($show_motd && !empty($body['motd']['clean'])) {
|
||||
$motd = is_array($body['motd']['clean']) ? implode(' ', $body['motd']['clean']) : $body['motd']['clean'];
|
||||
$result['motd'] = $motd;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$lp_groups = mcss_fetch_luckperms_groups();
|
||||
|
||||
$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 extrahieren (z. B. "[Owner] M_Viper" → "[Owner]")
|
||||
$prefix = '';
|
||||
if (preg_match('/^(\[[^\]]+\])/', $raw_entry, $m)) {
|
||||
$prefix = $m[1];
|
||||
}
|
||||
|
||||
// Rang = Prefix oder "Spieler"
|
||||
$rank = $prefix ?: 'Spieler';
|
||||
$color = '#94a3b8'; // Default Spieler-Farbe
|
||||
|
||||
// Farbe aus LuckPerms holen
|
||||
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($transient_key, $result, $ttl);
|
||||
update_option($transient_key . '_time', time());
|
||||
return $result;
|
||||
}
|
||||
|
||||
/* ---------------- Player rank detection (vereinfacht) ---------------- */
|
||||
function mcss_get_player_rank($player, $ranks_map = []) {
|
||||
return ['rank' => 'Spieler', 'color' => '#94a3b8'];
|
||||
}
|
||||
|
||||
/* ---------------- Shortcode ---------------- */
|
||||
add_shortcode('minecraft_status', 'mcss_server_card_shortcode');
|
||||
add_shortcode('minecraft_server_detail', 'mcss_server_card_shortcode');
|
||||
|
||||
function mcss_server_card_shortcode($atts = []) {
|
||||
$data = mcss_fetch_server_with_ranks();
|
||||
|
||||
$logo_id = get_option('mcss_server_logo_id', 0);
|
||||
$logo_url = get_option('mcss_server_logo_url', '');
|
||||
$logo = $logo_id ? wp_get_attachment_image_url($logo_id, 'full') : ($logo_url ?: ($atts['logo'] ?? MCSS_URL . 'img/default-server-logo.png'));
|
||||
|
||||
$custom_text = wp_kses_post(get_option('mcss_custom_text', ''));
|
||||
$ip_color = get_option('mcss_ip_color', '#1f2937');
|
||||
$ip_size = get_option('mcss_ip_size', '1.5em');
|
||||
$ct_color = get_option('mcss_ct_color', '#1e293b');
|
||||
$ct_size = get_option('mcss_ct_size', '1.05em');
|
||||
|
||||
$host = get_option('mcss_host', '');
|
||||
$player_port = trim(get_option('mcss_player_port', ''));
|
||||
$hide_port = (bool)get_option('mcss_hide_port', true);
|
||||
|
||||
$display_address = $host;
|
||||
if (!$hide_port && $player_port !== '') {
|
||||
$display_address .= ':' . $player_port;
|
||||
}
|
||||
|
||||
$copy_address = $host . ($player_port !== '' ? ':' . $player_port : '');
|
||||
|
||||
$user_defined_ranks = json_decode(get_option('mcss_ranks_json','[]'), true);
|
||||
$lp_present = is_array($user_defined_ranks) && count($user_defined_ranks) > 0;
|
||||
$top_rank_name = $lp_present ? ($user_defined_ranks[0]['name'] ?? 'Spieler') : 'Spieler';
|
||||
|
||||
$uid = md5($host . '|' . $player_port);
|
||||
|
||||
ob_start(); ?>
|
||||
<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 id="mcss-widget-content-<?php echo esc_attr($uid); ?>">
|
||||
<div style="padding:28px 32px;background:#f8fafc;border-bottom:1px solid #e2e8f0;display:flex;align-items:center;gap:20px;">
|
||||
<img id="mcss-logo-<?php echo esc_attr($uid); ?>" 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 esc_js(MCSS_URL . 'img/default-server-logo.png'); ?>'" />
|
||||
<div style="flex:1;">
|
||||
<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($ip_size); ?>;color:<?php echo esc_attr($ip_color); ?>;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); ?>" 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($custom_text)): ?>
|
||||
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="font-size:<?php echo esc_attr($ct_size); ?>;color:<?php echo esc_attr($ct_color); ?>;font-weight:600;margin-top:6px;"><?php echo $custom_text; ?></div>
|
||||
<?php else: ?>
|
||||
<div id="mcss-custom-text-<?php echo esc_attr($uid); ?>" style="display:none;"></div>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if (!empty($data['motd']) && get_option('mcss_show_motd', true)): ?>
|
||||
<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 else: ?>
|
||||
<div id="mcss-motd-<?php echo esc_attr($uid); ?>" style="display:none;"></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 ($lp_present): ?>
|
||||
<div><strong>Rang:</strong> <span id="mcss-players-count-<?php echo esc_attr($uid); ?>"><?php echo esc_html($top_rank_name); ?></span></div>
|
||||
<?php else: ?>
|
||||
<div><strong>Spieler:</strong> <span id="mcss-players-count-<?php echo esc_attr($uid); ?>"><?php echo count($data['players']); ?></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'])): ?>
|
||||
<?php 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 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;"><?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; ?>
|
||||
<?php 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>
|
||||
</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');
|
||||
|
||||
fetch(ajaxUrl, { method: 'POST', headers: {'Content-Type':'application/x-www-form-urlencoded'}, body: body.toString() })
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
if(!data) return;
|
||||
|
||||
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)';
|
||||
}
|
||||
if(onlineText) onlineText.textContent = data.online ? 'Online' : 'Offline';
|
||||
|
||||
var verEl = document.getElementById('mcss-version-' + uid);
|
||||
if(verEl) verEl.textContent = (data.version || '');
|
||||
|
||||
var playersCnt = document.getElementById('mcss-players-count-' + uid);
|
||||
if(playersCnt) {
|
||||
var showCount = true;
|
||||
if(Array.isArray(data.players) && data.players.length) {
|
||||
for(var i=0;i<data.players.length;i++){
|
||||
if(data.players[i] && data.players[i].rank && data.players[i].rank !== 'Spieler'){
|
||||
showCount = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if(showCount) playersCnt.textContent = (data.players ? data.players.length : 0);
|
||||
}
|
||||
|
||||
var pingEl = document.getElementById('mcss-ping-' + uid);
|
||||
if(pingEl) pingEl.textContent = (data.ping || 0);
|
||||
|
||||
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';
|
||||
}
|
||||
}
|
||||
|
||||
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 {'&':'&','<':'<','>':'>','"':'"',"'":''','/':'/','=':'=','`':'`'}[s];
|
||||
});
|
||||
}
|
||||
|
||||
setInterval(updateWidgetData, interval);
|
||||
setTimeout(updateWidgetData, 700);
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
return ob_get_clean();
|
||||
}
|
||||
Reference in New Issue
Block a user