3 Commits
1.1.0 ... 1.1.1

Author SHA1 Message Date
65e1921da1 mc-player-history.php aktualisiert 2026-02-09 20:11:27 +00:00
7f5efe273d README.md aktualisiert 2026-01-08 15:16:10 +00:00
d6c3faa42c mc-player-history.php aktualisiert 2026-01-06 18:49:36 +00:00
2 changed files with 633 additions and 507 deletions

View File

@@ -11,12 +11,11 @@ Die Daten werden live über eine externe **StatusAPI** aus deinem Minecraft-Netz
### Pflichtvoraussetzung:
- Ein **BungeeCord-Server**
- Das Plugin **StatusAPI**
- Das Plugin **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**
- **StatusAPI MUSS im BungeeCord-Ordner `/plugins` liegen**
**NICHT** auf Spigot / Paper / Velocity
**NUR** im **BungeeCord-Server unter `/plugins`**
```text
BungeeCord/
├─ plugins/
@@ -24,8 +23,12 @@ BungeeCord/
└─ config.yml
```
---
## 🔗 StatusAPI
**Download:** [https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)
Die **StatusAPI** stellt einen HTTP-Endpunkt bereit (z. B. `http://localhost:9191`),
der von diesem WordPress-Plugin **regelmäßig abgefragt** wird, um Spieler- und Online-Daten zu synchronisieren.
@@ -58,21 +61,21 @@ Die StatusAPI **MUSS** auf dem **BungeeCord-Server** installiert sein und sich i
wp-content/plugins/mc-player-history/
```
4. Plugin im WordPress-Admin aktivieren
2. Plugin im WordPress-Admin aktivieren
5. Unter **Einstellungen → MC Player History** die **StatusAPI URL** eintragen
3. Unter **Einstellungen → MC Player History** die **StatusAPI URL** eintragen
Beispiel:
```text
http://localhost:9191
```
6. Einstellungen speichern
4. Einstellungen speichern
---
## 🔗 Verbindung zur StatusAPI
- Die **StatusAPI** läuft auf dem **BungeeCord-Server**
- Die **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)** läuft auf dem **BungeeCord-Server**
- WordPress ruft diese URL per **HTTP** ab
- Standard-Fallback:
```text
@@ -88,7 +91,6 @@ Die StatusAPI **MUSS** auf dem **BungeeCord-Server** installiert sein und sich i
---
## 🧩 Shortcode
```shortcode
[mc_player_history]
```
@@ -102,15 +104,15 @@ Die StatusAPI **MUSS** auf dem **BungeeCord-Server** installiert sein und sich i
| only_online | Nur Online-Spieler anzeigen | false |
### Beispiel
```shortcode
[mc_player_history limit="50" interval="5" only_online="true"]
```
---
## 🎨 Anzeige-Reihenfolge (fix)
Die Struktur ist **fest definiert** und kann **nicht verändert** werden:
```text
Avatar
Prefix
@@ -119,6 +121,8 @@ Name
Status (unten)
```
---
## 🛠 Cron & Caching
- WordPress-Cron läuft alle **2 Minuten**
@@ -131,7 +135,7 @@ Status (unten)
### ❌ Keine Spieler sichtbar?
- StatusAPI nicht erreichbar
- **[StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)** nicht erreichbar
- StatusAPI **nicht im `BungeeCord/plugins/`**
- Falsche URL (z. B. `http://` vergessen)
- Firewall blockiert den API-Port
@@ -141,3 +145,10 @@ Status (unten)
- StatusAPI liefert keine `players`
- BungeeCord-Server nicht gestartet
- Falscher API-Port konfiguriert
---
## 📚 Weitere Informationen
Für detaillierte Informationen zur Installation und Konfiguration der StatusAPI besuche das Repository:
**[https://git.viper.ipv64.net/M_Viper/StatusAPI](https://git.viper.ipv64.net/M_Viper/StatusAPI)**

View File

@@ -1,9 +1,9 @@
<?php
/*
Plugin Name: MC Player History
Description: Strikte Reihenfolge: Bild -> Prefix -> Name -> Status (unten).
Version: 1.1.0
Author: Dein Name
Description: Spielerverlauf deines Minecraft Servers.
Version: 1.1.1
Author: M_Viper
*/
if ( ! defined( 'ABSPATH' ) ) {
@@ -119,10 +119,33 @@ function mcph_sync_from_statusapi() {
foreach ( $players as $p ) {
$name = isset( $p->name ) ? sanitize_text_field( $p->name ) : '';
$prefix = isset( $p->prefix ) ? sanitize_text_field( $p->prefix ) : '';
// UUID aus API übernehmen, falls vorhanden, sonst legacy-Hash
if ( isset( $p->uuid ) && !empty( $p->uuid ) ) {
$uuid = sanitize_text_field( $p->uuid );
} else {
$uuid = 'legacy-' . md5( strtolower( trim( $name ) ) );
}
if ( empty( $name ) ) continue;
// Prüfe ob Spieler bereits existiert (nach UUID ODER Username)
$exists = $wpdb->get_var( $wpdb->prepare( "SELECT id FROM $table_name WHERE uuid = %s", $uuid ) );
// Falls nicht per UUID gefunden, prüfe nach Username (für Migration alter Einträge)
if ( ! $exists ) {
$old_entry = $wpdb->get_row( $wpdb->prepare( "SELECT id, uuid FROM $table_name WHERE username = %s", $name ) );
if ( $old_entry ) {
// Alter Eintrag gefunden - UUID aktualisieren statt neuen Eintrag
$wpdb->update(
$table_name,
array( 'uuid' => $uuid, 'prefix' => $prefix, 'last_seen' => current_time( 'mysql' ), 'is_online' => 1 ),
array( 'id' => $old_entry->id ),
array( '%s', '%s', '%s', '%d' ),
array( '%d' )
);
continue;
}
}
$now = current_time( 'mysql' );
if ( $exists ) {
@@ -181,7 +204,12 @@ function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax
$has_live_data = true;
foreach ( $api_response as $p ) {
if ( isset( $p->name ) ) {
// UUID aus API übernehmen, falls vorhanden, sonst legacy-Hash
if ( isset( $p->uuid ) && !empty( $p->uuid ) ) {
$uuid = sanitize_text_field( $p->uuid );
} else {
$uuid = 'legacy-' . md5( strtolower( trim( $p->name ) ) );
}
$live_online_uuids[ $uuid ] = true;
}
}
@@ -218,7 +246,18 @@ function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax
$username = esc_html( $row->username );
$prefix = mcph_parse_minecraft_colors( $row->prefix );
$avatar = 'https://minotar.net/avatar/' . $username . '/80';
// Avatar-Logik: Moderne 3D-Köpfe (alle in gleiche Richtung)
$avatar = '';
if ( strpos( $username, '.' ) !== false || ( isset($row->uuid) && strpos($row->uuid, 'xuid') === 0 ) ) {
// Bedrock: mc-heads.net mit 3D Head
$avatar = isset($row->uuid) && !empty($row->uuid)
? 'https://mc-heads.net/head/' . esc_attr($row->uuid) . '/100'
: 'https://mc-heads.net/head/' . $username . '/100';
} else {
// Java: mc-heads.net mit 3D Head (gleiche Richtung wie Bedrock)
$avatar = 'https://mc-heads.net/head/' . $username . '/100';
}
$anim_style = '';
if ( ! $is_ajax ) {
@@ -235,7 +274,7 @@ function mcph_generate_player_html( $limit = 500, $only_online = false, $is_ajax
// STRUKTUR: Bild -> Prefix -> Name -> Spacer -> Status
echo '<div class="mc-player-card" style="' . $anim_style . '">';
echo '<img src="' . $avatar . '" class="mc-avatar" alt="' . $username . '">';
echo '<img src="' . $avatar . '" class="mc-avatar" alt="' . $username . '" loading="lazy">';
echo '<div class="mc-info-stack">';
@@ -275,6 +314,33 @@ function mcph_settings_init() {
register_setting( 'mcph_plugin_options', 'mcph_statusapi_url' );
}
function mcph_options_page() {
// Duplikate bereinigen
if ( isset( $_POST['mcph_cleanup_duplicates'] ) && check_admin_referer( 'mcph_cleanup_action' ) ) {
global $wpdb;
$table_name = $wpdb->prefix . 'mc_players';
// Finde Duplikate (gleicher Username, verschiedene UUIDs)
$duplicates = $wpdb->get_results(
"SELECT username, COUNT(*) as count, GROUP_CONCAT(id ORDER BY last_seen DESC) as ids
FROM $table_name
GROUP BY username
HAVING count > 1"
);
$cleaned = 0;
foreach ( $duplicates as $dup ) {
$ids = explode( ',', $dup->ids );
// Behalte den neuesten (ersten), lösche die anderen
array_shift( $ids );
foreach ( $ids as $id ) {
$wpdb->delete( $table_name, array( 'id' => $id ), array( '%d' ) );
$cleaned++;
}
}
echo '<div class="notice notice-success"><p>' . $cleaned . ' Duplikate wurden entfernt.</p></div>';
}
if ( isset( $_POST['mcph_manual_sync'] ) && check_admin_referer( 'mcph_manual_sync_action' ) ) {
mcph_sync_from_statusapi();
delete_transient( 'mcph_api_cache_data' );
@@ -296,6 +362,14 @@ function mcph_options_page() {
</table>
<?php submit_button(); ?>
</form>
<h2>Duplikate bereinigen</h2>
<form method="post">
<?php wp_nonce_field( 'mcph_cleanup_action' ); ?>
<p class="description">Entfernt doppelte Spieler-Einträge aus der Datenbank.</p>
<input type="submit" name="mcph_cleanup_duplicates" class="button button-secondary" value="Duplikate jetzt entfernen" />
</form>
<h2>Manueller Sync</h2>
<form method="post">
<?php wp_nonce_field( 'mcph_manual_sync_action' ); ?>
@@ -379,34 +453,41 @@ function mcph_shortcode( $atts ) {
display: flex;
flex-direction: column;
align-items: center;
gap: 8px; /* Weniger Abstand dazwischen */
gap: 8px;
padding: 20px 10px;
background: #fff;
border: 1px solid #eee;
border-radius: 8px;
transition: all 0.3s ease;
background: linear-gradient(145deg, #ffffff 0%, #f8f9fa 100%);
border: 1px solid #e0e0e0;
border-radius: 12px;
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
cursor: default;
position: relative; /* Fallback */
position: relative;
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
}
.mc-player-card:hover {
transform: translateY(-5px);
box-shadow: 0 10px 20px rgba(0,0,0,0.1);
border-color: #ddd;
transform: translateY(-8px) scale(1.02);
box-shadow: 0 12px 24px rgba(0,0,0,0.15);
border-color: #667eea;
background: linear-gradient(145deg, #ffffff 0%, #f0f2ff 100%);
}
.mc-avatar {
border-radius: 4px;
border: 3px solid #ddd;
background: #fff;
width: 60px;
height: 60px;
transition: border-color 0.3s;
border-radius: 8px;
border: 3px solid #e0e0e0;
background: transparent;
width: 80px;
height: 80px;
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
flex-shrink: 0;
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
object-fit: contain;
padding: 0;
}
.mc-player-card:hover .mc-avatar {
border-color: #bbb;
border-color: #667eea;
transform: scale(1.15) rotateY(10deg);
box-shadow: 0 8px 20px rgba(102, 126, 234, 0.4);
}
/* INFOS BEREICH */
@@ -415,10 +496,10 @@ function mcph_shortcode( $atts ) {
flex-direction: column;
align-items: center;
width: 100%;
flex-grow: 1; /* Füllt den Rest der Karte */
position: relative; /* Referenz für absoluten Status */
padding-bottom: 35px; /* Platz für Status unten */
justify-content: flex-start; /* Oben ausrichten */
flex-grow: 1;
position: relative;
padding-bottom: 35px;
justify-content: flex-start;
}
/* 1. PREFIX */
@@ -429,22 +510,24 @@ function mcph_shortcode( $atts ) {
text-align: center;
margin-bottom: 4px;
line-height: 1.2;
text-shadow: 0 1px 2px rgba(0,0,0,0.1);
}
/* 2. NAME */
.mc-name {
font-weight: bold;
font-size: 1.1em;
color: #333;
color: #2d3748;
word-break: break-word;
display: block;
width: 100%;
text-align: center;
text-shadow: 0 1px 2px rgba(0,0,0,0.05);
}
/* 3. SPACER */
.mc-spacer {
flex-grow: 1; /* Nimmt ALLEN leeren Platz zwischen Name und Status ein */
flex-grow: 1;
}
/* 4. STATUS (UNTEN) */
@@ -460,20 +543,42 @@ function mcph_shortcode( $atts ) {
.mc-status {
display: inline-block;
padding: 4px 12px;
padding: 5px 14px;
border-radius: 20px;
font-size: 0.8em;
font-weight: 600;
transition: all 0.3s ease;
}
.mc-online {
background: linear-gradient(135deg, #d4f8e8 0%, #b8f2d9 100%);
color: #0a7340;
border: 1px solid #81e6b8;
box-shadow: 0 2px 4px rgba(10, 115, 64, 0.1);
}
.mc-offline {
background: linear-gradient(135deg, #fff5f5 0%, #ffe5e5 100%);
color: #c53030;
border: 1px solid #fbb6b6;
box-shadow: 0 2px 4px rgba(197, 48, 48, 0.1);
}
.mc-player-card:hover .mc-status {
transform: scale(1.05);
}
.mc-online { background-color: #e6fffa; color: #28a745; border: 1px solid #b2f5ea; }
.mc-offline { background-color: #fff5f5; color: #e53e3e; border: 1px solid #feb2b2; }
.mc-update-time {
font-size: 0.8em;
color: #999;
font-size: 0.85em;
color: #718096;
text-align: center;
margin-top: 15px;
margin-top: 20px;
font-style: italic;
padding: 8px;
background: rgba(102, 126, 234, 0.05);
border-radius: 6px;
display: inline-block;
width: 100%;
}
@media (min-width: 1200px) {
@@ -481,10 +586,20 @@ function mcph_shortcode( $atts ) {
}
@media (max-width: 600px) {
.mc-grid { grid-template-columns: repeat(2, 1fr); gap: 10px; }
.mc-avatar { width: 50px; height: 50px; }
.mc-name { font-size: 1em; }
.mc-player-card { padding: 10px 5px; }
.mc-grid {
grid-template-columns: repeat(2, 1fr);
gap: 12px;
}
.mc-avatar {
width: 70px;
height: 70px;
}
.mc-name {
font-size: 1em;
}
.mc-player-card {
padding: 15px 8px;
}
}
</style>';