diff --git a/Minecraft-Modern-Theme/inc/assistant-ajax.php b/Minecraft-Modern-Theme/inc/assistant-ajax.php new file mode 100644 index 0000000..7efff11 --- /dev/null +++ b/Minecraft-Modern-Theme/inc/assistant-ajax.php @@ -0,0 +1,1988 @@ + 'Bitte gib eine Frage ein.', + 'parts' => [], + ] ); + } + + // "regeln:Kategorie" → direkt zu Rules-Intent, kein Scoring nötig + if ( preg_match( '/^regeln?:/iu', $q ) ) { + $bot = get_option( 'mm_bot_data', [] ); + $result = mm_intent_rules( $q, $bot ); + wp_send_json_success( mm_build_response( [ $result ] ) ); + } + + // "ban:Steve" → direkt zu Ban-Intent mit Spielername + if ( preg_match( '/^ban:(.+)$/iu', $q, $m ) ) { + $bot = get_option( 'mm_bot_data', [] ); + $name = trim( $m[1] ); + $result = mm_intent_ban_check( $q, $name, $bot ); + wp_send_json_success( mm_build_response( [ $result ] ) ); + } + + try { + $bot = get_option( 'mm_bot_data', [] ); + $parts = []; + + // ---- 1. Manuelle Q&A (höchste Priorität) -------------------- + if ( ! empty( $bot['qa'] ) ) { + foreach ( $bot['qa'] as $item ) { + if ( empty( $item['keys'] ) ) continue; + $keys = array_map( 'trim', explode( ',', mb_strtolower( $item['keys'] ) ) ); + foreach ( $keys as $k ) { + if ( $k !== '' && strpos( $q_lc, $k ) !== false ) { + $parts[] = [ + 'source' => 'qa', + 'content' => wp_kses_post( $item['val'] ), + ]; + break 2; + } + } + } + } + + if ( ! empty( $parts ) ) { + wp_send_json_success( mm_build_response( $parts ) ); + } + + // ---- Intent-Scores berechnen -------------------------------- + $intents = mm_score_intents( $q_lc, $q ); + arsort( $intents ); + $active = array_filter( $intents, function( $s ) { return $s > 0; } ); + + $player_name = mm_extract_player_name( $q ); + + foreach ( $active as $intent => $score ) { + // Ban-Intent dominant → Regeln nicht zusätzlich auslösen + if ( $intent === 'rules' && isset( $intents['ban_check'] ) && $intents['ban_check'] >= 25 ) { + continue; + } + // Ban-Intent dominant → Shop/Ticket/Forum nicht zusätzlich auslösen + if ( in_array( $intent, array( 'shop', 'ticket', 'forum' ), true ) + && isset( $intents['ban_check'] ) && $intents['ban_check'] >= 40 ) { + continue; + } + // Player-History dominant → Server-Status nicht zusätzlich auslösen + if ( $intent === 'server_status' + && isset( $intents['player_history'] ) && $intents['player_history'] >= 15 ) { + continue; + } + $result = mm_handle_intent( $intent, $q, $q_lc, $player_name, $bot ); + if ( $result ) { + $parts[] = $result; + } + } + + if ( empty( $parts ) ) { + $parts[] = mm_fallback_response( $bot ); + } + + wp_send_json_success( mm_build_response( $parts ) ); + + } catch ( \Throwable $e ) { + wp_send_json_success( [ + 'reply' => '⚠️ Interner Fehler: ' . esc_html( $e->getMessage() ), + 'parts' => [], + ] ); + } +} + +// ============================================================ +// INTENT SCORING +// Jeder Treffer erhöht den Score; der Intent mit dem höchsten +// Score wird zuerst beantwortet. Mehrere Intents möglich. +// ============================================================ + +function mm_score_intents( $q_lc, $q_raw ) { + $scores = [ + 'server_status' => 0, + 'ban_check' => 0, + 'player_history' => 0, + 'rules' => 0, + 'wiki' => 0, + 'shop' => 0, + 'ticket' => 0, + 'forum' => 0, + 'gallery' => 0, + 'faq' => 0, + ]; + + // Server-Status + foreach ( ['ip', 'adresse', 'join', 'einloggen', 'verbinden', 'server ip', + 'wie komme', 'online spieler', 'wieviele spieler', 'server status', + 'serveradresse', 'spieler online'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['server_status'] += 10; + } + + // Ban / Mute / Kick + foreach ( ['ban', 'gebannt', 'gesperrt', 'mute', 'gemutet', 'stumm', + 'kick', 'gekickt', 'warn', 'verwarnung', 'entban', 'unban', + 'antrag', 'sperre', 'strafe', 'strafen', 'bestraft', + 'meine strafen', 'mein ban', 'sanktion'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['ban_check'] += 15; + } + // Stärker gewichten bei Ich-Bezug + if ( preg_match( '/\b(bin ich|wurde ich|meine|mein)\b.*(gebannt|gesperrt|gemutet|strafe|ban|mute)/i', $q_raw ) ) { + $scores['ban_check'] += 20; + } + if ( preg_match( '/\b(meine strafen|strafen prüfen|mein status)\b/i', $q_raw ) ) { + $scores['ban_check'] += 25; + } + + // Player History + foreach ( ['spielzeit', 'playtime', 'zuletzt online', 'last seen', + 'letzte login', 'wie lang', 'wie lange', 'war online', + 'online status', 'ist online', 'stunden gespielt'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['player_history'] += 15; + } + + // Regeln + foreach ( array( 'regel', 'regeln', 'regelwerk', 'darf ich', 'ist erlaubt', + 'verboten', 'richtlinie', 'grief', 'stehlen', 'pvp', + 'chat regel', 'rule', 'verbote', + 'cheaten', 'cheat', 'hacks', 'hacken', 'modifikation', + 'beleidigung', 'schimpfen', 'respekt' ) as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['rules'] += 12; + } + + // Wiki + foreach ( ['wiki', 'dokumentation', 'guide', 'anleitung', 'howto', + 'how to', 'wie funktioniert', 'erkläre', 'erklärt'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['wiki'] += 12; + } + + // Shop + foreach ( [ + 'shop', 'kaufen', 'item', 'items', 'preis', 'kosten', + 'coins', 'tagesaktion', 'daily deal', 'angebot', 'kategorie', + 'warenkorb', 'bestellen', 'rang kaufen', + 'was kostet', 'wie teuer', 'preis von', 'kosten von', 'preis für', 'wie viel kostet' + ] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['shop'] += 12; + } + + // Ticket / Support + foreach ( ['ticket', 'support', 'hilfe', 'problem melden', + 'bug melden', 'melde', 'anfrage', 'beschwerde', + 'anliegen', 'kontaktier'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['ticket'] += 12; + } + + // Forum + foreach ( ['forum', 'diskussion', 'thread', + 'neuen thread', 'thread erstellen', 'im forum posten', 'im forum schreiben', + 'forum login', 'passwort vergessen', 'forum kategorie', + 'forum nachricht', 'direktnachricht', 'forum profil', + 'forum rang', 'forum level', 'forum mitglieder'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['forum'] += 10; + } + + // Gallery + foreach ( [ + 'galerie', 'gallery', 'bild', 'bilder', 'screenshot', 'screenshots', + 'foto', 'fotos', 'tagesbild', 'bild des tages', + 'hochladen', 'upload', 'uploaden', 'bild teilen', 'bilder teilen', + 'verifizier', 'verify', '/verify', 'token galerie', + 'meine galerie', 'spieler galerie', 'galerie upload', + ] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['gallery'] += 10; + } + // Upload-Kontext stärker gewichten + if ( preg_match( '/\b(wie|kann ich|wie kann|anleitung|schritt)\b.*(bild|foto|screenshot|hochlad|upload)/i', $q_lc ) ) { + $scores['gallery'] += 20; + } + + // FAQ + foreach ( ['faq', 'häufige frage', 'frequently', 'oft gefragt'] as $kw ) { + if ( strpos( $q_lc, $kw ) !== false ) $scores['faq'] += 10; + } + + return $scores; +} + +// ============================================================ +// INTENT HANDLER DISPATCHER +// ============================================================ + +function mm_handle_intent( $intent, $q, $q_lc, $player_name, $bot ) { + switch ( $intent ) { + case 'server_status': return mm_intent_server_status( $bot ); + case 'ban_check': return mm_intent_ban_check( $q, $player_name, $bot ); + case 'player_history': return mm_intent_player_history( $q, $player_name ); + case 'rules': return mm_intent_rules( $q, $bot ); + case 'wiki': return mm_intent_wiki( $q ); + case 'shop': return mm_intent_shop( $q_lc, $bot ); + case 'ticket': return mm_intent_ticket( $q, $bot ); + case 'forum': return mm_intent_forum( $q ); + case 'gallery': return mm_intent_gallery( $bot, $q_lc ); + case 'faq': return mm_intent_faq( $q ); + } + return null; +} + +// ============================================================ +// INTENT: SERVER STATUS (BungeeCord Status Plugin) +// ============================================================ + +function mm_intent_server_status( $bot ) { + $lines = array(); + + // ── 1. Statische Infos aus Bot-Setup ────────────────────── + $ip = $bot['server_ip'] ?? ''; + $ver = $bot['server_ver'] ?? ''; + $specs = $bot['server_specs'] ?? ''; + $discord = $bot['link_discord'] ?? ''; + + if ( $ip ) $lines[] = '🌐 Adresse: ' . esc_html( $ip ) . ''; + if ( $ver ) $lines[] = '🎮 Version: ' . esc_html( $ver ); + if ( $specs ) $lines[] = '🖥️ Server: ' . esc_html( $specs ); + + // ── 2. Live-Daten via BungeeCord-Status-Plugin ───────────── + $servers = get_option( 'mcss_servers', array() ); + + if ( ! empty( $servers ) && function_exists( 'mcss_fetch_server_with_ranks' ) ) { + $srv = $servers[0]; + $cache_key = 'mm_srv_' . md5( ( $srv['host'] ?? '' ) . ( $srv['player_port'] ?? '9191' ) ); + $data = get_transient( $cache_key ); + + // Caching-Zeit erhöht auf 60 Sekunden + if ( false === $data ) { + try { + $data = mcss_fetch_server_with_ranks( $srv ); + } catch ( \Throwable $e ) { + $data = null; + } + set_transient( $cache_key, $data, 60 ); + } + + if ( ! empty( $data ) && isset( $data['online'] ) ) { + $is_online = (bool) $data['online']; + $icon = $is_online ? '🟢' : '🔴'; + $status = $is_online ? 'Online' : 'Offline'; + + // Spieler-Anzahl (players = Array von Objekten) + $player_list = isset( $data['players'] ) && is_array( $data['players'] ) ? $data['players'] : array(); + $player_count = count( $player_list ); + $player_text = $is_online ? ' · ' . $player_count . ' Spieler online' : ''; + + $srv_label = ! empty( $srv['name'] ) ? esc_html( $srv['name'] ) . ': ' : ''; + $lines[] = $icon . ' ' . $srv_label . $status . $player_text; + + // Version vom Server + if ( $is_online && ! empty( $data['version'] ) && empty( $ver ) ) { + $lines[] = '🎮 Version: ' . esc_html( $data['version'] ); + } + + // MOTD + if ( $is_online && ! empty( $data['motd'] ) ) { + $motd = is_array( $data['motd'] ) + ? implode( ' ', $data['motd'] ) + : (string) $data['motd']; + $motd = trim( $motd ); + if ( $motd ) { + $lines[] = '💬 ' . mm_format_motd( $motd ); + } + } + + // Spieler auflisten (bis 8) + if ( $is_online && ! empty( $player_list ) ) { + $names = array(); + foreach ( array_slice( $player_list, 0, 8 ) as $p ) { + if ( is_array( $p ) && ! empty( $p['name'] ) ) { + $names[] = esc_html( $p['name'] ); + } elseif ( is_string( $p ) ) { + $names[] = esc_html( $p ); + } + } + if ( ! empty( $names ) ) { + $lines[] = '👥 Spieler: ' . implode( ', ', $names ) + . ( $player_count > 8 ? ' +' . ( $player_count - 8 ) . ' weitere' : '' ); + } + } + } + + // Weitere konfigurierte Server kurz anzeigen + if ( count( $servers ) > 1 ) { + $extra = array(); + foreach ( array_slice( $servers, 1 ) as $s ) { + if ( empty( $s['name'] ) ) continue; + $ck = 'mm_srv_' . md5( ( $s['host'] ?? '' ) . ( $s['player_port'] ?? '9191' ) ); + $d = get_transient( $ck ); + if ( false === $d && function_exists( 'mcss_fetch_server_with_ranks' ) ) { + try { + $d = mcss_fetch_server_with_ranks( $s ); + } catch ( \Throwable $e ) { + $d = null; + } + set_transient( $ck, $d, 60 ); + } + $on = ! empty( $d['online'] ); + $count = ( $on && ! empty( $d['players'] ) ) ? count( $d['players'] ) : 0; + $extra[] = ( $on ? '🟢' : '🔴' ) . ' ' . esc_html( $s['name'] ) + . ( $on ? ' (' . $count . ')' : '' ); + } + if ( ! empty( $extra ) ) { + $lines[] = '🗺️ Weitere Server: ' . implode( ' · ', $extra ); + } + } + } + + // ── 3. Kein Plugin, keine Daten ─────────────────────────── + if ( empty( $lines ) ) { + return array( + 'source' => 'server_status', + 'title' => '🖥️ Server-Status', + 'content' => 'Server-Infos wurden noch nicht konfiguriert. Bitte im Bot-Setup eintragen.', + ); + } + + // ── 4. Links (nur Discord, kein Wiki-Link mehr) ───────────── + if ( $discord ) { + $lines[] = '💬 Discord'; + } + + return array( + 'source' => 'server_status', + 'title' => '🖥️ Server-Status', + 'content' => implode( '
', $lines ), + ); +} + +// ============================================================ +// HILFSFUNKTION: Minecraft §-Farbcodes → HTML +// Unterstützt §0-§f (Farben) und §l/§o/§n/§m/§r (Format) +// ============================================================ + +function mm_format_motd( $text ) { + if ( empty( $text ) ) return ''; + + $colors = array( + '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', + ); + + // Normalisieren: § und & beide unterstützen + $section = "\xC2\xA7"; // UTF-8 für § + $text = str_replace( '&', $section, $text ); + + // Aufteilen nach §X Segmenten + $parts = preg_split( '/\xC2\xA7([0-9a-fklmnorA-FKLMNOR])/', $text, -1, PREG_SPLIT_DELIM_CAPTURE ); + $output = ''; + $open = 0; + $bold = false; + $italic = false; + + foreach ( $parts as $i => $part ) { + if ( $i % 2 === 0 ) { + // Text-Segment + if ( $part !== '' ) { + $output .= esc_html( $part ); + } + } else { + // Code-Segment + $code = strtolower( $part ); + + // Offene Spans schließen + if ( $open > 0 ) { + $output .= str_repeat( '', $open ); + $open = 0; + } + if ( $bold ) { $output .= ''; $bold = false; } + if ( $italic ) { $output .= ''; $italic = false; } + + if ( isset( $colors[ $code ] ) ) { + $output .= ''; + $open++; + } elseif ( $code === 'l' ) { + $output .= ''; + $bold = true; + } elseif ( $code === 'o' ) { + $output .= ''; + $italic = true; + } elseif ( $code === 'r' ) { + // Reset — alles schon geschlossen oben + } + // §k (obfuscated), §m (strike), §n (underline) ignorieren wir + } + } + + // Offene Tags schließen + if ( $open > 0 ) $output .= str_repeat( '', $open ); + if ( $bold ) $output .= ''; + if ( $italic ) $output .= ''; + + return $output; +} + +// ============================================================ +// INTENT: BAN / MUTE / KICK / WARN (LiteBans Manager) +// ============================================================ + +function mm_intent_ban_check( $q, $player_name, $bot ) { + $dashboard_url = isset( $bot["litebans_dashboard_url"] ) ? $bot["litebans_dashboard_url"] : ""; + + if ( empty( $player_name ) ) { + $html = '🔍 Welchen Spielernamen soll ich prüfen?

'; + $html .= 'Tippe deinen Minecraft-Namen direkt ein:

'; + $html .= '
'; + $html .= 'Format:'; + $html .= ''; + $html .= '
'; + $html .= '
Ersetze DeinName mit deinem echten Minecraft-Namen.
'; + $html .= 'Oder schreibe z.B.: ist Steve gebannt
'; + if ( $dashboard_url ) { + $html .= '

LiteBans Dashboard öffnen'; + } + return array( "source" => "ban_check", "title" => "🔨 Strafen prüfen", "content" => $html ); + } + + $info = mm_litebans_query( $player_name ); + + if ( is_null( $info ) ) { + return array( "source" => "ban_check", "title" => "🔨 Ban-Status", + "content" => "⚠️ LiteBans ist nicht konfiguriert oder die Datenbank ist nicht erreichbar." ); + } + + if ( isset( $info["not_found"] ) ) { + return array( "source" => "ban_check", "title" => "🔨 Ban-Status: " . esc_html( $player_name ), + "content" => "❓ Spieler " . esc_html( $player_name ) . " wurde in der LiteBans-Datenbank nicht gefunden." ); + } + + $lines = array(); + + // Avatar + Name + $lines[] = "
" + . "Avatar" + . "" . esc_html( $info["player"] ) . "" + . "
"; + + // Aktiver Ban + if ( $info["active_ban"] ) { + $b = $info["active_ban"]; + $lines[] = "
"; + $lines[] = "🚫 Aktiver Ban"; + $lines[] = "📋 Grund: " . esc_html( $b["reason"] ); + $lines[] = "👮 Von: " . esc_html( $b["by"] ); + $lines[] = "⏰ Bis: " . esc_html( $b["until"] ) . ""; + $lines[] = "
"; + } else { + $lines[] = "✅ Kein aktiver Ban"; + } + + // Aktiver Mute + if ( $info["active_mute"] ) { + $m = $info["active_mute"]; + $lines[] = "
"; + $lines[] = "🔇 Aktiver Mute"; + $lines[] = "📋 Grund: " . esc_html( $m["reason"] ); + $lines[] = "⏰ Bis: " . esc_html( $m["until"] ) . ""; + $lines[] = "
"; + } else { + $lines[] = "✅ Kein aktiver Mute"; + } + + // Kicks & Warns + $stats = array(); + if ( $info["kicks"] > 0 ) $stats[] = "👢 " . $info["kicks"] . " Kick(s)"; + if ( $info["warnings"] > 0 ) $stats[] = "⚠️ " . $info["warnings"] . " Verwarnung(en)"; + if ( ! empty( $stats ) ) $lines[] = implode( " · ", $stats ); + + // Ban-History + if ( ! empty( $info["ban_history"] ) ) { + $lines[] = "
🕐 Letzte Bestrafungen:"; + foreach ( $info["ban_history"] as $h ) { + $icon = $h["type"] === "ban" ? "🚫" : ( $h["type"] === "mute" ? "🔇" : ( $h["type"] === "kick" ? "👢" : "⚠️" ) ); + $lines[] = $icon . " " . esc_html( $h["date"] ) + . " – " . esc_html( $h["reason"] ) + . " (" . esc_html( $h["by"] ) . ")"; + } + } + + // Entbannungsanträge + if ( ! empty( $info["unban_requests"] ) ) { + $lines[] = "
📋 Entbannungsanträge:"; + foreach ( $info["unban_requests"] as $r ) { + $lines[] = $r["status"] . " – " . esc_html( $r["date"] ); + } + } + + // Links + $link_parts = array(); + if ( $dashboard_url ) { + $link_parts[] = "Dashboard"; + } + if ( $info["active_ban"] && $dashboard_url ) { + $unban_url = add_query_arg( array( + "lb_player" => $info["player"], + "lb_reason" => $info["active_ban"]["reason"], + ), $dashboard_url ); + $link_parts[] = "Entbannungsantrag stellen →"; + } + if ( ! empty( $link_parts ) ) $lines[] = "
" . implode( " · ", $link_parts ); + + $status_icon = $info["active_ban"] ? "🚫" : "✅"; + + return array( + "source" => "ban_check", + "title" => $status_icon . " Ban-Status: " . esc_html( $info["player"] ), + "content" => implode( "
", $lines ), + ); +} + +function mm_litebans_query( $player_name ) { + $settings = get_option( "wp_litebans_pro_settings", array() ); + if ( empty( $settings["db_name"] ) || empty( $settings["db_user"] ) ) return null; + + $prefix = isset( $settings["table_prefix"] ) ? $settings["table_prefix"] : "litebans_"; + $db_host = isset( $settings["db_host"] ) ? $settings["db_host"] : "localhost"; + if ( ! empty( $settings["db_port"] ) ) $db_host .= ":" . intval( $settings["db_port"] ); + + try { + $lbdb = new wpdb( $settings["db_user"], $settings["db_pass"], $settings["db_name"], $db_host ); + $lbdb->suppress_errors( true ); + $lbdb->prefix = $prefix; + } catch ( Exception $e ) { + return null; + } + + $name = sanitize_text_field( $player_name ); + + // UUID über history-Tabelle (wie das Plugin selbst) + $uuid = $lbdb->get_var( $lbdb->prepare( + "SELECT uuid FROM {$prefix}history WHERE name = %s ORDER BY id DESC LIMIT 1", $name + ) ); + + if ( empty( $uuid ) ) { + // Fuzzy-Suche + $row = $lbdb->get_row( $lbdb->prepare( + "SELECT uuid, name FROM {$prefix}history WHERE name LIKE %s ORDER BY id DESC LIMIT 1", + "%" . $lbdb->esc_like( $name ) . "%" + ) ); + if ( ! $row ) return array( "not_found" => true ); + $uuid = $row->uuid; + $name = $row->name; + } + + $info = array( "player" => $name, "active_ban" => null, "active_mute" => null, + "kicks" => 0, "warnings" => 0, "ban_history" => array(), "unban_requests" => array() ); + + // Aktiver Ban (via UUID) + $ban = $lbdb->get_row( $lbdb->prepare( + "SELECT reason, banned_by_name, until, active, removed_by_name FROM {$prefix}bans WHERE uuid = %s AND active = 1 ORDER BY time DESC LIMIT 1", $uuid + ) ); + if ( $ban && intval( $ban->active ) === 1 && empty( $ban->removed_by_name ) ) { + $until = (int) $ban->until; + $info["active_ban"] = array( + "reason" => mm_lb_clean( $ban->reason ), + "by" => $ban->banned_by_name, + "until" => ( $until <= 0 ) ? "Permanent" : date_i18n( "d.m.Y H:i", $until / 1000 ), + ); + } + + // Aktiver Mute (via UUID) + $mute = $lbdb->get_row( $lbdb->prepare( + "SELECT reason, banned_by_name, until, active, removed_by_name FROM {$prefix}mutes WHERE uuid = %s AND active = 1 ORDER BY time DESC LIMIT 1", $uuid + ) ); + if ( $mute && intval( $mute->active ) === 1 && empty( $mute->removed_by_name ) ) { + $until = (int) $mute->until; + $info["active_mute"] = array( + "reason" => mm_lb_clean( $mute->reason ), + "by" => $mute->banned_by_name, + "until" => ( $until <= 0 ) ? "Permanent" : date_i18n( "d.m.Y H:i", $until / 1000 ), + ); + } + + // Kicks & Warnings (UUID) + $info["kicks"] = (int) $lbdb->get_var( $lbdb->prepare( "SELECT COUNT(*) FROM {$prefix}kicks WHERE uuid = %s", $uuid ) ); + if ( $lbdb->get_var( "SHOW TABLES LIKE '{$prefix}warnings'" ) ) { + $info["warnings"] = (int) $lbdb->get_var( $lbdb->prepare( "SELECT COUNT(*) FROM {$prefix}warnings WHERE uuid = %s", $uuid ) ); + } + + // Ban-History (letzte 5 aller Typen) + $history = array(); + foreach ( array( "bans", "mutes", "kicks" ) as $t ) { + $rows = $lbdb->get_results( $lbdb->prepare( + "SELECT reason, banned_by_name, time FROM {$prefix}{$t} WHERE uuid = %s ORDER BY time DESC LIMIT 3", $uuid + ) ); + $type = rtrim( $t, "s" ); // bans→ban, mutes→mute, kicks→kick + foreach ( (array) $rows as $r ) { + $history[] = array( "type" => $type, "date" => date_i18n( "d.m.Y", $r->time / 1000 ), + "reason" => mm_lb_clean( $r->reason ), "by" => $r->banned_by_name, "ts" => $r->time ); + } + } + usort( $history, function( $a, $b ) { return $b["ts"] - $a["ts"]; } ); + $info["ban_history"] = array_slice( $history, 0, 5 ); + + // Entbannungsanträge (WP CPT) + $requests = get_posts( array( "post_type" => "unban_request", "posts_per_page" => 5, + "post_status" => "any", "meta_query" => array( array( "key" => "_lb_player", "value" => $name, "compare" => "=" ) ) ) ); + foreach ( $requests as $r ) { + $status = get_post_meta( $r->ID, "_lb_status", true ) ?: "pending"; + $label = $status === "approved" ? "✅ Angenommen" : ( $status === "rejected" ? "❌ Abgelehnt" : "⏳ Wartend" ); + $info["unban_requests"][] = array( "date" => get_the_date( "d.m.Y", $r->ID ), "status" => $label ); + } + + return $info; +} + +function mm_lb_clean( $text ) { + if ( ! $text ) return "–"; + return preg_replace( "/(?:§|&)[0-9a-fk-orA-FK-OR]/u", "", $text ); +} + +// ============================================================ +// INTENT: PLAYER HISTORY (MC Player History Plugin) +// ============================================================ +// INTENT: PLAYER HISTORY (MC Player History Plugin) +// ============================================================ + +function mm_intent_player_history( $q, $player_name ) { + $bot = get_option( 'mm_bot_data', [] ); + + // Nur aktiv wenn URL im Backend gesetzt + if ( empty( $bot['url_player_history'] ) ) return null; + + $history_url = $bot['url_player_history']; + + // Kein Spielername → Top-Liste + Hinweis + if ( empty( $player_name ) ) { + global $wpdb; + $table = $wpdb->prefix . 'mc_players'; + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) { + return [ + 'source' => 'player_history', + 'title' => '👤 Spieler-History', + 'content' => 'Das MC Player History Plugin ist nicht installiert oder die Tabelle fehlt.', + ]; + } + + // Top 5 nach Spielzeit + $top = $wpdb->get_results( + "SELECT username, prefix, playtime_seconds, is_online + FROM $table WHERE playtime_seconds > 0 + ORDER BY playtime_seconds DESC LIMIT 5" + ); + + $html = 'Nenne mir einen Minecraft-Namen, z.B.: Spielzeit von Steve

'; + + if ( ! empty( $top ) ) { + $html .= '🏆 Top-Spieler:
'; + $i = 1; + foreach ( $top as $p ) { + $prefix_html = mm_ph_format_prefix( $p->prefix ); + $time_str = mm_format_playtime( (int) $p->playtime_seconds ); + $online_dot = $p->is_online ? '🟢' : '⚫'; + $html .= $online_dot . ' ' . ( $prefix_html ? $prefix_html . ' ' : '' ) + . '' . esc_html( $p->username ) . '' + . ' – ' . esc_html( $time_str ) . '
'; + $i++; + } + $html .= '
'; + } + + $html .= "→ Alle Spieler ansehen"; + + return [ + 'source' => 'player_history', + 'title' => '👤 Spieler-History', + 'content' => $html, + ]; + } + + // Spieler suchen + global $wpdb; + $table = $wpdb->prefix . 'mc_players'; + + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) !== $table ) { + return [ + 'source' => 'player_history', + 'title' => '👤 Spieler-Info', + 'content' => 'MC Player History Plugin ist nicht aktiv oder Tabelle fehlt.', + ]; + } + + // Exakter Treffer + $row = $wpdb->get_row( $wpdb->prepare( + "SELECT username, prefix, is_online, first_seen, last_seen, playtime_seconds + FROM $table WHERE username = %s LIMIT 1", + $player_name + ) ); + + // Fuzzy-Suche als Fallback + if ( ! $row ) { + $row = $wpdb->get_row( $wpdb->prepare( + "SELECT username, prefix, is_online, first_seen, last_seen, playtime_seconds + FROM $table WHERE username LIKE %s LIMIT 1", + '%' . $wpdb->esc_like( $player_name ) . '%' + ) ); + } + + if ( ! $row ) { + return [ + 'source' => 'player_history', + 'title' => '👤 Spieler: ' . esc_html( $player_name ), + 'content' => '❓ Spieler ' . esc_html( $player_name ) . ' wurde noch nicht auf dem Server gesehen.' + . '

Spieler-Liste öffnen', + ]; + } + + $online_icon = $row->is_online + ? '🟢 Gerade online' + : '⚫ Offline'; + + $playtime = mm_format_playtime( (int) $row->playtime_seconds ); + $last_seen = $row->last_seen + ? wp_date( 'd.m.Y \u\m H:i \U\h\r', strtotime( $row->last_seen ) ) + : 'Unbekannt'; + $first_seen = $row->first_seen + ? wp_date( 'd.m.Y', strtotime( $row->first_seen ) ) + : 'Unbekannt'; + + $lines = [ + '📶 Status: ' . $online_icon, + '⏱️ Spielzeit: ' . esc_html( $playtime ) . '', + '🕐 Zuletzt gesehen: ' . esc_html( $last_seen ) . '', + '📅 Erstmals gesehen: ' . esc_html( $first_seen ) . '', + ]; + + // Rang / Prefix (mit MC-Farben) + if ( ! empty( $row->prefix ) ) { + $prefix_html = mm_ph_format_prefix( $row->prefix ); + if ( $prefix_html !== '' ) { + $lines[] = '🏷️ Rang: ' . $prefix_html; + } + } + + // Avatar + $lines[] = 'Avatar ' + . 'Spieler-Liste öffnen'; + + return [ + 'source' => 'player_history', + 'title' => '👤 Spieler: ' . esc_html( $row->username ), + 'content' => implode( '
', $lines ), + ]; +} + +/** + * Minecraft-Farbcodes in HTML umwandeln. + * Nutzt mcph_parse_minecraft_colors() vom Plugin wenn verfügbar, + * ansonsten eigene Implementierung als Fallback. + */ +function mm_ph_format_prefix( $prefix ) { + if ( empty( $prefix ) ) return ''; + + // Plugin-eigene Funktion bevorzugen + if ( function_exists( 'mcph_parse_minecraft_colors' ) ) { + return mcph_parse_minecraft_colors( $prefix ); + } + + // Fallback: eigene Implementierung + $map = [ + '&0' => '', + '&1' => '', + '&2' => '', + '&3' => '', + '&4' => '', + '&5' => '', + '&6' => '', + '&7' => '', + '&8' => '', + '&9' => '', + '&a' => '', + '&b' => '', + '&c' => '', + '&d' => '', + '&e' => '', + '&f' => '', + '&l' => '', + '&o' => '', + '&n' => '', + '&m' => '', + ]; + + // Anzahl geöffneter Spans zählen für sauberes Schließen + $open = 0; + $text = str_ireplace( array_keys( $map ), array_values( $map ), esc_html( $prefix ) ); + + // &r = alle schließen + $text = preg_replace_callback( '/&r/i', function() use ( &$open ) { + $close = str_repeat( '', $open ); + $open = 0; + return $close; + }, $text ); + + // Spans mitzählen + preg_match_all( '//', $text, $closes ); + $remaining = count( $opens[0] ) - count( $closes[0] ); + if ( $remaining > 0 ) { + $text .= str_repeat( '', $remaining ); + } + + return $text; +} + +/** Sekunden in lesbare Spielzeit umrechnen */ +function mm_format_playtime( $seconds ) { + if ( $seconds <= 0 ) return '0 Minuten'; + $h = floor( $seconds / 3600 ); + $m = floor( ( $seconds % 3600 ) / 60 ); + if ( $h > 0 ) return "{$h}h {$m}min"; + return "{$m} Minuten"; +} + +// ============================================================ +// INTENT: REGELN (Multi Rules Plugin) +// ============================================================ + +function mm_intent_rules( $q, $bot ) { + $rules_url = ! empty( $bot['url_rules'] ) ? $bot['url_rules'] : home_url( '/regeln' ); + $options = get_option( 'mrp_settings' ); + $tabs = isset( $options['tabs'] ) && is_array( $options['tabs'] ) ? $options['tabs'] : array(); + + // ── Schritt 2: Kategorie wurde gewählt → "regeln:Minecraft" ── + if ( preg_match( '/^regeln?:(.+)$/iu', trim( $q ), $m ) ) { + $chosen = trim( $m[1] ); + return mm_rules_show_tab( $chosen, $tabs, $rules_url ); + } + + // ── Spezifische Suche: Suchbegriff vorhanden ────────────── + $q_clean = trim( preg_replace( + '/\b(regel|regeln|regelwerk|zeig|alle|zeige|liste|gibt es|was sind)\b/iu', + '', $q + ) ); + $q_clean = trim( preg_replace( '/\s+/', ' ', $q_clean ) ); + + if ( strlen( $q_clean ) >= 3 ) { + return mm_rules_search( $q_clean, $tabs, $rules_url ); + } + + // ── Schritt 1: Allgemein → Kategorie-Auswahl ───────────── + if ( empty( $tabs ) ) { + return array( + 'source' => 'rules', + 'title' => '📜 Regelwerk', + 'content' => "→ Zum Regelwerk", + ); + } + + $html = 'Wähle eine Kategorie:

'; + $html .= '
'; + + foreach ( $tabs as $tab ) { + $tab_title = $tab['title'] ?? 'Allgemein'; + $rule_count = ! empty( $tab['rules'] ) ? count( $tab['rules'] ) : 0; + // Trigger: "regeln:Minecraft" + $query = 'regeln:' . $tab_title; + $html .= ''; + } + + $html .= '
'; + $html .= '
Alle Regeln im Browser öffnen'; + + return array( + 'source' => 'rules', + 'title' => '📜 Regelwerk', + 'content' => $html, + ); +} + +/** + * Alle Regeln eines bestimmten Tabs anzeigen. + */ +function mm_rules_show_tab( $tab_name, $tabs, $rules_url ) { + $found_tab = null; + + foreach ( $tabs as $tab ) { + if ( mb_strtolower( $tab['title'] ?? '' ) === mb_strtolower( $tab_name ) ) { + $found_tab = $tab; + break; + } + } + + // Fuzzy-Fallback + if ( ! $found_tab ) { + foreach ( $tabs as $tab ) { + if ( strpos( mb_strtolower( $tab['title'] ?? '' ), mb_strtolower( $tab_name ) ) !== false ) { + $found_tab = $tab; + break; + } + } + } + + if ( ! $found_tab ) { + return array( + 'source' => 'rules', + 'title' => '📜 Regelwerk', + 'content' => 'Kategorie ' . esc_html( $tab_name ) . ' nicht gefunden.
' + . "→ Zum Regelwerk", + ); + } + + $rules = isset( $found_tab['rules'] ) && is_array( $found_tab['rules'] ) ? $found_tab['rules'] : array(); + + if ( empty( $rules ) ) { + return array( + 'source' => 'rules', + 'title' => '📜 ' . esc_html( $found_tab['title'] ), + 'content' => 'In dieser Kategorie sind noch keine Regeln hinterlegt.', + ); + } + + $html = ''; + foreach ( $rules as $rule ) { + $title = $rule['rule_title'] ?? ''; + $content = $rule['rule_content'] ?? ''; + + $content_plain = wp_strip_all_tags( $content ); + if ( mb_strlen( $content_plain ) > 300 ) { + $rendered = '

' + . esc_html( mb_substr( $content_plain, 0, 300 ) ) . '…

'; + } else { + $rendered = '
' + . wp_kses_post( $content ) + . '
'; + } + + $html .= '
'; + $html .= '📌 ' . esc_html( $title ) . ''; + $html .= $rendered; + $html .= '
'; + } + + // Zurück-Button zur Kategorieauswahl + $html .= '
'; + $html .= ''; + $html .= '' + . 'Im Browser öffnen'; + $html .= '
'; + + return array( + 'source' => 'rules', + 'title' => '📜 ' . esc_html( $found_tab['title'] ) . ' – ' . count( $rules ) . ' Regeln', + 'content' => $html, + ); +} + +/** + * Volltext-Suche über alle Tabs + Regeln. + */ +function mm_rules_search( $q_clean, $tabs, $rules_url ) { + $q_lc = mb_strtolower( $q_clean ); + $words = array_filter( preg_split( '/\s+/', $q_lc ), function( $w ) { + $stop = array( 'regel', 'regeln', 'ist', 'darf', 'ich', 'erlaubt', 'verboten', 'wie', 'was', 'kann' ); + return strlen( $w ) > 2 && ! in_array( $w, $stop, true ); + } ); + + $matches = array(); + + foreach ( $tabs as $tab ) { + $tab_title = $tab['title'] ?? 'Allgemein'; + if ( empty( $tab['rules'] ) || ! is_array( $tab['rules'] ) ) continue; + + foreach ( $tab['rules'] as $rule ) { + $r_title = mb_strtolower( $rule['rule_title'] ?? '' ); + $r_content = mb_strtolower( wp_strip_all_tags( $rule['rule_content'] ?? '' ) ); + $t_lc = mb_strtolower( $tab_title ); + $score = 0; + + if ( strpos( $r_title, $q_lc ) !== false ) $score += 30; + if ( strpos( $r_content, $q_lc ) !== false ) $score += 15; + if ( strpos( $t_lc, $q_lc ) !== false ) $score += 10; + + foreach ( $words as $w ) { + if ( strpos( $r_title, $w ) !== false ) $score += 12; + if ( strpos( $r_content, $w ) !== false ) $score += 5; + if ( strpos( $t_lc, $w ) !== false ) $score += 4; + } + + if ( $score > 0 ) { + $matches[] = array( + 'score' => $score, + 'tab' => $tab_title, + 'title' => $rule['rule_title'] ?? '', + 'content' => $rule['rule_content'] ?? '', + ); + } + } + } + + if ( empty( $matches ) ) { + // Keine Treffer → direkt zur Kategorieauswahl + $html = 'Keine Regel zu ' . esc_html( $q_clean ) . ' gefunden.

'; + $html .= 'Wähle eine Kategorie:

'; + $html .= '
'; + foreach ( $tabs as $tab ) { + $tab_title = $tab['title'] ?? 'Allgemein'; + $html .= ''; + } + $html .= '
'; + + return array( + 'source' => 'rules', + 'title' => '📜 Regelwerk', + 'content' => $html, + ); + } + + usort( $matches, function( $a, $b ) { return $b['score'] - $a['score']; } ); + $top = array_slice( $matches, 0, 3 ); + $html = ''; + + foreach ( $top as $r ) { + $content_plain = wp_strip_all_tags( $r['content'] ); + if ( mb_strlen( $content_plain ) > 300 ) { + $rendered = '

' + . esc_html( mb_substr( $content_plain, 0, 300 ) ) . '…

'; + } else { + $rendered = '
' + . wp_kses_post( $r['content'] ) + . '
'; + } + + $html .= '
'; + $html .= '📌 ' . esc_html( $r['title'] ) . ''; + $html .= ' (' . esc_html( $r['tab'] ) . ')'; + $html .= $rendered; + $html .= '
'; + } + + if ( count( $matches ) > 3 ) { + $html .= '' . ( count( $matches ) - 3 ) . ' weitere Treffer vorhanden.
'; + } + + $html .= '
'; + $html .= ''; + $html .= 'Im Browser öffnen'; + $html .= '
'; + + return array( + 'source' => 'rules', + 'title' => '📜 Regelwerk – ' . count( $matches ) . ' Treffer', + 'content' => $html, + ); +} + +// ============================================================ +// INTENT: WIKI (WP Multi Wiki Plugin) +// ============================================================ + +function mm_intent_wiki( $q ) { + if ( ! post_type_exists( 'wmw_article' ) ) return null; + + // Suchbegriff bereinigen + $search = preg_replace( '/\b(wiki|anleitung|guide|howto|how to|dokument|erkläre?|zeig mir)\b/i', '', $q ); + $search = trim( preg_replace( '/\s+/', ' ', $search ) ); + + if ( strlen( $search ) < 2 ) { + // Alle Wikis auflisten + if ( function_exists( 'wmw_get_wikis' ) ) { + $wikis = wmw_get_wikis(); + if ( ! empty( $wikis ) ) { + $links = array_map( function( $w ) { + return '→ ' + . esc_html( $w->post_title ) . ''; + }, $wikis ); + return [ + 'source' => 'wiki', + 'title' => '📖 Wiki', + 'content' => 'Verfügbare Wikis:
' . implode( '
', $links ), + ]; + } + } + return null; + } + + $args = [ + 'post_type' => 'wmw_article', + 'post_status' => 'publish', + 's' => $search, + 'posts_per_page' => 4, + 'orderby' => 'relevance', + ]; + $results = get_posts( $args ); + + if ( empty( $results ) ) { + // Fallback: alle Wiki-Artikel + $bot = get_option( 'mm_bot_data', [] ); + $url = ! empty( $bot['url_wiki'] ) ? $bot['url_wiki'] : ''; + $link = $url ? " → Wiki öffnen" : ''; + return [ + 'source' => 'wiki', + 'title' => '📖 Wiki', + 'content' => 'Kein Wiki-Artikel zu „' . esc_html( $search ) . '“ gefunden.' . $link, + ]; + } + + $html = []; + foreach ( $results as $post ) { + $excerpt = get_the_excerpt( $post->ID ); + $html[] = '📄 ' + . esc_html( $post->post_title ) . '' + . ( $excerpt ? '
' . wp_trim_words( $excerpt, 25, '…' ) . '' : '' ); + } + + return [ + 'source' => 'wiki', + 'title' => '📖 Wiki-Treffer', + 'content' => implode( '

', $html ), + ]; +} + +// ============================================================ +// INTENT: SHOP (WP Ingame Shop Pro) +// ============================================================ + +function mm_intent_shop( $q_lc, $bot ) { + global $wpdb; + $t_items = $wpdb->prefix . 'wis_items'; + $t_cats = $wpdb->prefix . 'wis_categories'; + + if ( $wpdb->get_var( "SHOW TABLES LIKE '$t_items'" ) !== $t_items ) return null; + + // DEBUG: Suchbegriff und Item-Anzahl loggen + if (!function_exists('mm_debug_log')) { + function mm_debug_log($msg) { + if (defined('WP_DEBUG') && WP_DEBUG) { + error_log('[MM SHOP] ' . $msg); + } + } + } + + mm_debug_log('Suchbegriff: ' . $q_lc); + + $currency = get_option( 'wis_currency_name', 'Coins' ); + $lines = []; + $img_base = get_option( 'wis_image_base_url', '' ); + $shop_url = $bot['url_shop'] ?? ''; + // Shop-Antwort-Box (grau, Card-Layout) + // $lines[] = '
'; + + // ── Tagesaktion nur anzeigen, wenn KEINE Preisabfrage ───────────────── + $is_price_query = false; + $search = $q_lc; + if (preg_match('/\b(was kostet|preis von|wie teuer|wie viel kostet|kosten von|preis für|preis)\b/i', $search)) { + $is_price_query = true; + } + if (!$is_price_query) { + $deal = $wpdb->get_row( "SELECT * FROM $t_items WHERE is_daily_deal = 1 AND status = 'publish' LIMIT 1" ); + if ( $deal ) { + $offer = isset( $deal->offer_price ) && $deal->offer_price > 0 ? $deal->offer_price : $deal->price; + $img = $img_base . str_replace(':', '_', $deal->item_id) . '.png'; + $item_link = $shop_url ? esc_url($shop_url) . '#item-' . urlencode($deal->item_id) : ''; + $lines[] = '
' + . '🔥 Tagesaktion:
' + . '' . esc_attr($deal->name) . '' + . '
' . esc_html( $deal->name ) . '
' + . '' + . '' . esc_html( (string) $offer ) . ' ' . esc_html( $currency ) . '' + . ( $deal->price != $offer ? ' ' . esc_html( (string) $deal->price ) . '' : '' ) + . '' + . ( $item_link ? '
Zum Item im Shop' : '' ) + . '
'; + } + } + + // ── Suchbegriff extrahieren (auch für Preisabfragen) ──────────────── + // (is_price_query wurde oben schon gesetzt) + $search = $q_lc; + if ($is_price_query) { + // Nur das Item extrahieren + $search = preg_replace('/\b(shop|kaufen|kosten|preis|items?|angebot|tagesaktion|daily deal|kategorie|rang|was gibt|zeig|liste|was kostet|preis von|wie teuer|wie viel kostet|kosten von|preis für)\b/i', '', $search); + } else { + $search = preg_replace('/\b(shop|kaufen|kosten|preis|items?|angebot|tagesaktion|daily deal|kategorie|rang|was gibt|zeig|liste)\b/i', '', $search); + } + $search = trim(preg_replace('/\s+/', ' ', $search)); + // Satzzeichen am Anfang/Ende entfernen + $search = preg_replace('/^[^\p{L}\p{N}]+|[^\p{L}\p{N}]+$/u', '', $search); + + if (strlen($search) >= 3) { + mm_debug_log('Suchbegriff nach Extraktion: ' . $search); + // Spezifische Suche (auch für Preisabfragen) + $items = $wpdb->get_results($wpdb->prepare( + "SELECT * FROM $t_items + WHERE status = 'publish' AND (item_title LIKE %s OR name LIKE %s OR item_id LIKE %s) + ORDER BY price ASC LIMIT 8", + '%' . $wpdb->esc_like($search) . '%', + '%' . $wpdb->esc_like($search) . '%', + '%' . $wpdb->esc_like($search) . '%' + )); + mm_debug_log('LIKE-Query: ' . $search . ' | Treffer: ' . count($items)); + + if (!empty($items)) { + mm_debug_log('LIKE-Query erstes Item: ' . (isset($items[0]) ? json_encode($items[0]) : 'n/a')); + // Preisabfrage: Nur das erste Item als Preisantwort + if ($is_price_query) { + $item = $items[0]; + $img = $img_base . str_replace(':', '_', $item->item_id) . '.png'; + $item_link = $shop_url ? esc_url($shop_url) . '#item-' . urlencode($item->item_id) : ''; + $item_display_name = $item->name ?: ($item->item_title ?: $item->item_id); + // Serverliste aufbereiten + $server_list = ''; + if (!empty($item->servers)) { + $servers = json_decode($item->servers, true); + if (is_array($servers) && count($servers) > 0) { + $server_list = '
Verfügbar auf: ' . esc_html(implode(', ', $servers)) . '
'; + } + } + $lines[] = '
' + . '
🛒 Shop-Item:
' + . '' . esc_attr($item_display_name) . '' + . '
' . esc_html($item_display_name) . '
' + . '
' . esc_html((string)$item->price) . ' ' . esc_html($currency) . '
' + . $server_list + . '
'; + } else { + $lines[] = '🛒 Gefundene Items:'; + foreach ($items as $item) { + $img = $img_base . str_replace(':', '_', $item->item_id) . '.png'; + $item_link = $shop_url ? esc_url($shop_url) . '#item-' . urlencode($item->item_id) : ''; + $lines[] = '' . esc_attr($item->item_title) . '' + . '' . esc_html($item->item_title) . '' + . ' – ' . esc_html((string)$item->price) . ' ' . esc_html($currency) + . ($item_link ? ' [Shop]' : ''); + } + } + } else { + // Exakte Suche, falls LIKE nichts findet + $item = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $t_items WHERE status = 'publish' AND (name = %s OR item_id = %s OR item_title = %s) LIMIT 1", + $search, $search, $search + )); + mm_debug_log('Exakte Suche: ' . ($item ? json_encode($item) : 'kein Treffer')); + // Case-insensitive Fallback, falls nötig + if (!$item && !empty($search)) { + $item = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $t_items WHERE status = 'publish' AND (name COLLATE utf8_general_ci = %s COLLATE utf8_general_ci OR item_id COLLATE utf8_general_ci = %s COLLATE utf8_general_ci OR item_title COLLATE utf8_general_ci = %s COLLATE utf8_general_ci) LIMIT 1", + $search, $search, $search + )); + mm_debug_log('Case-insensitive Suche: ' . ($item ? json_encode($item) : 'kein Treffer')); + } + // Fallback: Suchbegriff als Minecraft-ID mit Prefix testen + if (!$item && !empty($search) && strpos($search, 'minecraft:') === false) { + $mc_id = 'minecraft:' . strtolower(str_replace([' ', '_'], ['_', '_'], $search)); + $item = $wpdb->get_row($wpdb->prepare( + "SELECT * FROM $t_items WHERE status = 'publish' AND (item_id = %s OR item_id COLLATE utf8_general_ci = %s COLLATE utf8_general_ci) LIMIT 1", + $mc_id, $mc_id + )); + mm_debug_log('MC-ID Fallback: ' . $mc_id . ' | ' . ($item ? json_encode($item) : 'kein Treffer')); + } + if ($item) { + mm_debug_log('Gefundenes Item (exakt/fallback): ' . json_encode($item)); + $img = $img_base . str_replace(':', '_', $item->item_id) . '.png'; + $item_link = $shop_url ? esc_url($shop_url) . '#item-' . urlencode($item->item_id) : ''; + $lines[] = '
' + . '' . esc_attr($item->name) . '' + . '
' . esc_html($item->name) . '
' + . 'Preis: ' . esc_html((string)$item->price) . ' ' . esc_html($currency) . '' + . ($item_link ? ' [Shop]' : '') + . '
'; + } else { + // Aggressive Fuzzy-Suche als letzter Versuch + $all_items = $wpdb->get_results("SELECT * FROM $t_items WHERE status = 'publish'"); + mm_debug_log('Fuzzy-Suche über alle Items (' . count($all_items) . '): ' . $search); + $search_lc = mb_strtolower($search); + $fuzzy = null; + foreach ($all_items as $it) { + if (strpos(mb_strtolower($it->name), $search_lc) !== false + || strpos(mb_strtolower($it->item_title), $search_lc) !== false + || strpos(mb_strtolower($it->item_id), $search_lc) !== false) { + $fuzzy = $it; + break; + } + } + mm_debug_log('Fuzzy-Suche Treffer: ' . ($fuzzy ? json_encode($fuzzy) : 'kein Treffer')); + if ($fuzzy) { + $img = $img_base . str_replace(':', '_', $fuzzy->item_id) . '.png'; + $item_link = $shop_url ? esc_url($shop_url) . '#item-' . urlencode($fuzzy->item_id) : ''; + $item_display_name = $fuzzy->name ?: ($fuzzy->item_title ?: $fuzzy->item_id); + // Serverliste aufbereiten + $server_list = ''; + if (!empty($fuzzy->servers)) { + $servers = json_decode($fuzzy->servers, true); + if (is_array($servers) && count($servers) > 0) { + $server_list = '
Verfügbar auf: ' . esc_html(implode(', ', $servers)) . '
'; + } + } + $lines[] = '
' + . '
🛒 Shop-Item:
' + . '' . esc_attr($item_display_name) . '' + . '
' . esc_html($item_display_name) . '
' + . '
' . esc_html((string)$fuzzy->price) . ' ' . esc_html($currency) . '
' + . $server_list + . '
'; + } else { + $lines[] = 'Kein Item mit „' . esc_html($search) . '“ gefunden.'; + } + } + } + } else { + // Übersicht: Kategorien + Item-Anzahl + $total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $t_items WHERE status = 'publish'" ); + $lines[] = '🛒 Shop-Übersicht: ' . $total . ' Items verfügbar.'; + + $cats = $wpdb->get_results( + "SELECT c.name, COUNT(i.id) as cnt + FROM $t_cats c + LEFT JOIN $t_items i ON i.category_id = c.id AND i.status = 'publish' + GROUP BY c.id ORDER BY cnt DESC LIMIT 8" + ); + if ( ! empty( $cats ) ) { + $lines[] = '📂 Kategorien:'; + foreach ( $cats as $cat ) { + $lines[] = ' • ' . esc_html( $cat->name ) . ' (' . intval( $cat->cnt ) . ' Items)'; + } + } + + // Günstigste & teuerste Items + $cheapest = $wpdb->get_row( "SELECT item_title, price FROM $t_items WHERE status = 'publish' AND price > 0 ORDER BY price ASC LIMIT 1" ); + $priciest = $wpdb->get_row( "SELECT item_title, price FROM $t_items WHERE status = 'publish' ORDER BY price DESC LIMIT 1" ); + if ( $cheapest ) $lines[] = '💚 Günstigstes: ' . esc_html( $cheapest->item_title ) . ' – ' . esc_html( (string) $cheapest->price ) . ' ' . esc_html( $currency ); + if ( $priciest ) $lines[] = '💎 Teuerstes: ' . esc_html( $priciest->item_title ) . ' – ' . esc_html( (string) $priciest->price ) . ' ' . esc_html( $currency ); + } + + if ( $shop_url ) { + $lines[] = ""; + } + + return [ + 'source' => 'shop', + 'title' => '🛒 Shop', + 'content' => implode( '
', $lines ), + ]; +} + +// ============================================================ +// INTENT: TICKET (WP Multi Ticket Pro) +// ============================================================ + +function mm_intent_ticket( $q, $bot ) { + global $wpdb; + $table = $wpdb->prefix . 'wmt_tickets'; + $table_msg = $wpdb->prefix . 'wmt_messages'; + $exists = $wpdb->get_var( "SHOW TABLES LIKE '$table'" ) === $table; + + // ── Ticket per Nummer nachschlagen ──────────────────────── + if ( preg_match( '/#?(\d{3,6})\b/', $q, $m ) && $exists ) { + $ticket_id = intval( $m[1] ); + $ticket = $wpdb->get_row( $wpdb->prepare( + "SELECT id, subject, status, category, department, created_at, guest_name, guest_email + FROM $table WHERE id = %d", + $ticket_id + ) ); + + if ( $ticket ) { + // Letzte Nachricht + $last_msg = ''; + if ( $wpdb->get_var( "SHOW TABLES LIKE '$table_msg'" ) === $table_msg ) { + $msg = $wpdb->get_row( $wpdb->prepare( + "SELECT message, created_at FROM $table_msg WHERE ticket_id = %d ORDER BY created_at DESC LIMIT 1", + $ticket_id + ) ); + if ( $msg ) { + $last_msg = '
💬 Letzte Nachricht: ' + . esc_html( wp_trim_words( $msg->message, 20, '…' ) ) + . ' (' . wp_date( 'd.m.Y H:i', strtotime( $msg->created_at ) ) . ')'; + } + } + + $lines = [ + '🎫 Ticket #' . $ticket->id . '', + '📋 Betreff: ' . esc_html( $ticket->subject ) . '', + '📌 Status: ' . esc_html( $ticket->status ) . '', + '🏷️ Kategorie: ' . esc_html( $ticket->category ?: '–' ), + ]; + if ( ! empty( $ticket->department ) ) { + $lines[] = '🏢 Abteilung: ' . esc_html( $ticket->department ); + } + if ( ! empty( $ticket->guest_name ) ) { + $lines[] = '👤 Von: ' . esc_html( $ticket->guest_name ); + } + $lines[] = '📅 Erstellt: ' . wp_date( 'd.m.Y \u\m H:i', strtotime( $ticket->created_at ) ); + if ( $last_msg ) $lines[] = $last_msg; + + return [ + 'source' => 'ticket', + 'title' => '🎫 Ticket #' . $ticket->id, + 'content' => implode( '
', $lines ), + ]; + } else { + return [ + 'source' => 'ticket', + 'title' => '🎫 Ticket nicht gefunden', + 'content' => 'Ticket #' . $ticket_id . ' existiert nicht.', + ]; + } + } + + // ── Allgemeine Ticket-Übersicht ──────────────────────────── + $lines = []; + + if ( $exists ) { + $open = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE status NOT LIKE %s", '%Geschlossen%' ) ); + $closed = (int) $wpdb->get_var( $wpdb->prepare( "SELECT COUNT(*) FROM $table WHERE status LIKE %s", '%Geschlossen%' ) ); + $total = $open + $closed; + + $lines[] = '📊 Gesamt: ' . $total . ' Tickets · Offen: ' . $open . ' · Geschlossen: ' . $closed . ''; + + // Kategorien anzeigen + $cats = $wpdb->get_results( + "SELECT COALESCE(category, 'Keine Kategorie') as cat, COUNT(*) as cnt + FROM $table GROUP BY category ORDER BY cnt DESC LIMIT 5" + ); + if ( ! empty( $cats ) ) { + $cat_parts = []; + foreach ( $cats as $c ) { + $cat_parts[] = esc_html( $c->cat ) . ' (' . intval( $c->cnt ) . ')'; + } + $lines[] = '📂 Kategorien: ' . implode( ' · ', $cat_parts ); + } + } + + $ticket_url = $bot['url_tickets'] ?? ''; + if ( $ticket_url ) { + $lines[] = "→ Neues Ticket erstellen"; + $lines[] = 'Oder gib eine Ticket-Nummer ein, z.B.: #1234'; + } else { + $lines[] = 'Bitte nutze das Support-Formular auf unserer Website.'; + } + + return [ + 'source' => 'ticket', + 'title' => '🎫 Support-Ticket', + 'content' => implode( '
', $lines ), + ]; +} + +// ============================================================ +// INTENT: FORUM (WP Business Forum) +// ============================================================ + +function mm_intent_forum( $q ) { + // Nur aktiv wenn WBF installiert UND URL im Backend gesetzt + $bot = get_option( 'mm_bot_data', [] ); + if ( ! class_exists( 'WBF_DB' ) ) return null; + if ( empty( $bot['url_forum'] ) ) return null; + + global $wpdb; + + $forum_url = esc_url( $bot['url_forum'] ); + $q_lc = mb_strtolower( $q ); + + // Tabellen prüfen — fehlen sie, früh abbrechen statt hängen + $t_tbl = $wpdb->prefix . 'forum_threads'; + $p_tbl = $wpdb->prefix . 'forum_posts'; + $u_tbl = $wpdb->prefix . 'forum_users'; + $c_tbl = $wpdb->prefix . 'forum_categories'; + + $tables_ok = ( + $wpdb->get_var( "SHOW TABLES LIKE '$t_tbl'" ) === $t_tbl && + $wpdb->get_var( "SHOW TABLES LIKE '$u_tbl'" ) === $u_tbl && + $wpdb->get_var( "SHOW TABLES LIKE '$c_tbl'" ) === $c_tbl + ); + + // ── Sub-Intent: Registrierung ───────────────────────────── + if ( preg_match( '/\b(registrier(en|ung)?|konto erstellen|account erstellen|sign.?up|einschreiben)\b/i', $q ) ) { + $lines = []; + if ( $tables_ok ) { + try { + $reg_mode = get_option( 'wbf_settings', [] )['registration_mode'] ?? 'open'; + $member_count = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $u_tbl WHERE role != 'banned'" ); + + $lines[] = '📝 Registrierung:'; + if ( $reg_mode === 'disabled' ) { + $lines[] = '❌ Die Registrierung ist derzeit deaktiviert.'; + } elseif ( $reg_mode === 'invite' ) { + $lines[] = '🔒 Das Forum nutzt Einladungscodes. Du benötigst einen gültigen Code.'; + } else { + $lines[] = '✅ Die Registrierung ist offen — jederzeit möglich.'; + } + if ( $member_count > 0 ) { + $lines[] = '👥 Bereits ' . $member_count . ' Mitglieder dabei.'; + } + } catch ( \Throwable $e ) { + $lines[] = '📝 Registrierung verfügbar.'; + } + } else { + $lines[] = '📝 Registrierung:'; + $lines[] = 'Erstelle deinen Account direkt im Forum.'; + } + $lines[] = "→ Zum Forum & Registrierung"; + return [ + 'source' => 'forum', + 'title' => '💬 Forum-Registrierung', + 'content' => implode( '
', $lines ), + ]; + } + + // ── Sub-Intent: Login / Passwort ────────────────────────── + if ( preg_match( '/\b(login|einloggen|passwort vergessen|passwort.?reset|zugangsdaten)\b/i', $q ) ) { + $lines = []; + $lines[] = '🔑 Forum-Login:'; + $lines[] = "→ Zum Forum & Login"; + if ( preg_match( '/passwort/i', $q ) ) { + $lines[] = '
🔓 Passwort vergessen?'; + $lines[] = 'Klicke auf der Login-Seite auf „Passwort vergessen".'; + } + return [ + 'source' => 'forum', + 'title' => '💬 Forum-Login', + 'content' => implode( '
', $lines ), + ]; + } + + // ── Sub-Intent: Profil ──────────────────────────────────── + if ( preg_match( '/\b(profil|mein konto|mein account|avatar|signatur|forum rang|forum level)\b/i', $q ) ) { + $lines = []; + $lines[] = '👤 Dein Forum-Profil enthält:'; + $lines[] = ' • Anzeigename, Avatar & Signatur'; + if ( class_exists( 'WBF_Levels' ) && WBF_Levels::is_enabled() ) { + $lines[] = ' • Level-System (basiert auf Beitragsanzahl)'; + } + $lines[] = ' • Rollen-Badge, Beitragsanzahl & Registrierungsdatum'; + $lines[] = "→ Zum Forum → Profil öffnen"; + return [ + 'source' => 'forum', + 'title' => '💬 Forum-Profil', + 'content' => implode( '
', $lines ), + ]; + } + + // ── Sub-Intent: Nachrichten / DM ────────────────────────── + if ( preg_match( '/\b(direktnachricht|forum nachricht|forum inbox|pn schreiben|privat.?nachricht)\b/i', $q ) ) { + $lines = []; + $lines[] = '✉️ Direktnachrichten im Forum:'; + $lines[] = 'Du kannst eingeloggten Mitgliedern direkt Nachrichten schreiben.'; + $lines[] = "→ Forum öffnen → Postfach"; + return [ + 'source' => 'forum', + 'title' => '💬 Forum-Nachrichten', + 'content' => implode( '
', $lines ), + ]; + } + + // ── Sub-Intent: Thread erstellen ────────────────────────── + if ( preg_match( '/\b(neuen thread|thread erstellen|beitrag erstellen|topic erstellen|im forum schreiben|im forum posten)\b/i', $q ) ) { + $lines = []; + $lines[] = '✏️ Neuen Thread erstellen:'; + $lines[] = '① Melde dich im Forum an.'; + $lines[] = '② Wähle die passende Kategorie.'; + $lines[] = '③ Klicke auf „Neuen Thread" und fülle Titel & Inhalt aus.'; + $lines[] = "→ Forum öffnen"; + return [ + 'source' => 'forum', + 'title' => '💬 Thread erstellen', + 'content' => implode( '
', $lines ), + ]; + } + + // ── Suchbegriff extrahieren ──────────────────────────────── + $search = preg_replace( '/\b(forum|diskussion|thread|thema|beitrag|post|kategorie|suche?)\b/i', '', $q ); + $search = trim( preg_replace( '/\s+/', ' ', $search ) ); + + // ── Thread-Suche ────────────────────────────────────────── + if ( strlen( $search ) >= 3 && $tables_ok ) { + try { + $threads = $wpdb->get_results( $wpdb->prepare( + "SELECT t.id, t.title, t.slug, t.reply_count, t.view_count, t.is_pinned, + c.name as cat_name + FROM {$t_tbl} t + LEFT JOIN {$c_tbl} c ON t.category_id = c.id + WHERE t.title LIKE %s AND t.deleted_at IS NULL + ORDER BY t.is_pinned DESC, t.created_at DESC LIMIT 5", + '%' . $wpdb->esc_like( $search ) . '%' + ) ); + + if ( ! empty( $threads ) ) { + $html = ''; + foreach ( $threads as $t ) { + $t_url = $forum_url . '?thread=' . esc_attr( $t->slug ); + $pin = $t->is_pinned ? '📌 ' : ''; + $meta = []; + if ( $t->reply_count > 0 ) $meta[] = $t->reply_count . ' Antworten'; + if ( $t->view_count > 0 ) $meta[] = $t->view_count . ' Aufrufe'; + $title_short = mb_strlen( $t->title ) > 50 ? mb_substr( $t->title, 0, 50 ) . '…' : $t->title; + $html .= '
'; + $html .= $pin . '' . esc_html( $title_short ) . ''; + if ( $t->cat_name ) $html .= ' · ' . esc_html( $t->cat_name ) . ''; + if ( ! empty( $meta ) ) $html .= '
' . implode( ' · ', $meta ) . ''; + $html .= '
'; + } + $html .= "
Alle Threads im Forum"; + return [ + 'source' => 'forum', + 'title' => '💬 Forum – ' . count( $threads ) . ' Treffer', + 'content' => $html, + ]; + } + + return [ + 'source' => 'forum', + 'title' => '💬 Forum', + 'content' => 'Kein Thread zu ' . esc_html( $search ) . ' gefunden.
' + . "→ Forum öffnen & selbst suchen", + ]; + } catch ( \Throwable $e ) { + // Bei DB-Fehler: einfach Übersicht zeigen + } + } + + // ── Allgemeine Übersicht mit Live-Statistiken ────────────── + $lines = []; + + if ( $tables_ok ) { + try { + $total_threads = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $t_tbl WHERE deleted_at IS NULL" ); + $total_posts = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $p_tbl WHERE deleted_at IS NULL" ); + $total_members = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $u_tbl WHERE role != 'banned'" ); + $online_count = (int) $wpdb->get_var( + "SELECT COUNT(*) FROM $u_tbl WHERE last_active >= DATE_SUB(NOW(), INTERVAL 5 MINUTE)" + ); + + $stat_parts = []; + if ( $total_threads > 0 ) $stat_parts[] = '' . $total_threads . ' Threads'; + if ( $total_posts > 0 ) $stat_parts[] = '' . $total_posts . ' Beiträge'; + if ( $total_members > 0 ) $stat_parts[] = '' . $total_members . ' Mitglieder'; + if ( ! empty( $stat_parts ) ) { + $lines[] = '📊 ' . implode( ' · ', $stat_parts ); + } + + $online_icon = $online_count > 0 ? '🟢' : '⚫'; + $lines[] = $online_icon . ' ' . ( $online_count > 0 + ? '' . $online_count . ' Mitglied' . ( $online_count > 1 ? 'er' : '' ) . ' gerade online' + : 'Aktuell niemand aktiv' ); + + // Kategorien + $cats = $wpdb->get_results( + "SELECT c.name, c.slug, COUNT(t.id) as thread_count + FROM {$c_tbl} c + LEFT JOIN {$t_tbl} t ON t.category_id = c.id AND t.deleted_at IS NULL + WHERE c.parent_id = 0 + GROUP BY c.id ORDER BY c.sort_order ASC LIMIT 6" + ); + if ( ! empty( $cats ) ) { + $lines[] = '
📂 Kategorien:'; + foreach ( $cats as $cat ) { + $c_url = $forum_url . '?cat=' . esc_attr( $cat->slug ); + $count = $cat->thread_count > 0 ? ' (' . (int) $cat->thread_count . ')' : ''; + $lines[] = ' → ' + . esc_html( $cat->name ) . '' . $count; + } + } + + // Neueste Threads + $recent = $wpdb->get_results( + "SELECT t.title, t.slug, t.reply_count + FROM {$t_tbl} t + WHERE t.deleted_at IS NULL AND t.is_pinned = 0 + ORDER BY t.created_at DESC LIMIT 3" + ); + if ( ! empty( $recent ) ) { + $lines[] = '
🆕 Neueste Threads:'; + foreach ( $recent as $r ) { + $t_url = $forum_url . '?thread=' . esc_attr( $r->slug ); + $title_short = mb_strlen( $r->title ) > 50 ? mb_substr( $r->title, 0, 50 ) . '…' : $r->title; + $replies = $r->reply_count > 0 ? ' (' . (int) $r->reply_count . ' Antw.)' : ''; + $lines[] = ' → ' + . esc_html( $title_short ) . '' . $replies; + } + } + } catch ( \Throwable $e ) { + $lines[] = '⚠️ Forum-Daten konnten nicht geladen werden.'; + } + } + + $lines[] = ( ! empty( $lines ) ? '
' : '' ) + . "→ Forum öffnen"; + + return [ + 'source' => 'forum', + 'title' => '💬 Forum', + 'content' => implode( '
', $lines ), + ]; +} + +// ============================================================ +// INTENT: GALERIE (MC MultiServer Gallery PRO) +// ============================================================ + +function mm_intent_gallery( $bot, $q_lc = '' ) { + $gallery_url = ! empty( $bot['url_gallery'] ) ? $bot['url_gallery'] : home_url( '/galerie' ); + + // ── Erkennen ob Upload-Frage ────────────────────────────── + $is_upload_query = (bool) preg_match( + '/hochlad|upload|uploaden|verifizier|verify|token|bild teilen|bilder teilen|wie.*bild|anleitung.*galerie/i', + $q_lc + ); + + $lines = []; + + // ── 1. Galerie-Link ────────────────────────────────────── + $lines[] = "📷 → Galerie öffnen"; + + // ── 2. Upload-Anleitung (bei Upload-Frage oder allgemein) ─ + if ( $is_upload_query ) { + $lines[] = '
'; + $lines[] = '📤 Bilder hochladen – so geht\'s:'; + $lines[] = '① Galerie öffnen und auf „Bilder hochladen" klicken.'; + $lines[] = '② Deinen Minecraft-Namen eingeben und den Server wählen → Session starten.'; + $lines[] = '③ Im Spiel den Befehl /verify [token] eingeben, um dich zu verifizieren.'; + $lines[] = '④ Screenshots auswählen (optional Album anlegen) → hochladen ✓'; + $lines[] = '💡 Der Verify-Token ist 5 Minuten gültig. Du musst dazu online auf dem Server sein.'; + } else { + // Kurz-Hinweis auf Upload auch bei allgemeiner Galerie-Frage + $lines[] = "🖼️ Du kannst eigene Screenshots direkt auf der Galerie-Seite hochladen."; + } + + // ── 3. Bild des Tages ──────────────────────────────────── + if ( post_type_exists( 'mc_gallery' ) ) { + $today_key = 'mc_daily_image_' . gmdate( 'Y-m-d' ); + $image_id = get_transient( $today_key ); + + if ( $image_id && wp_attachment_is_image( $image_id ) ) { + $img = wp_get_attachment_image_src( $image_id, 'medium' ); + $full = wp_get_attachment_image_src( $image_id, 'full' ); + $meta = get_post( $image_id ); + $uploader = ''; + + // Spielername aus Galerie-Post-Meta holen + if ( $meta && $meta->post_parent ) { + $player = get_post_meta( $meta->post_parent, 'mc_player', true ); + if ( $player ) { + $uploader = ' von ' . esc_html( $player ) . ''; + } + } + + if ( $img ) { + $lines[] = '
'; + $lines[] = '🌟 Bild des Tages' . $uploader . ':'; + $lines[] = "" + . "" + . ""; + } + } else { + // Kein Tagesbild gecacht → Hinweis + $lines[] = '📅 Heute noch kein Bild des Tages verfügbar.'; + } + + // Galerie-Statistik + $count = wp_count_posts( 'mc_gallery' ); + if ( $count && $count->publish > 0 ) { + $lines[] = '📁 ' . intval( $count->publish ) . ' Galerie(n) auf dem Server.'; + } + } + + return [ + 'source' => 'gallery', + 'title' => $is_upload_query ? '📤 Galerie & Upload' : '📷 Galerie', + 'content' => implode( '
', $lines ), + ]; +} + +// ============================================================ +// INTENT: FAQ (Custom Post Type 'faq') +// ============================================================ + +function mm_intent_faq( $q ) { + if ( ! post_type_exists( 'faq' ) ) return null; + + $search = preg_replace( '/\b(faq|häufig|frage|oft gefragt)\b/i', '', $q ); + $search = trim( $search ); + + $results = get_posts( [ + 'post_type' => 'faq', + 'post_status' => 'publish', + 's' => strlen( $search ) >= 3 ? $search : '', + 'posts_per_page' => 4, + 'orderby' => 'relevance', + ] ); + + if ( empty( $results ) ) return null; + + $html = []; + foreach ( $results as $post ) { + $excerpt = get_the_excerpt( $post->ID ); + $html[] = '❓ ' . esc_html( $post->post_title ) . '' + . ( $excerpt ? '
' . wp_trim_words( $excerpt, 40, '…' ) : '' ); + } + + return [ + 'source' => 'faq', + 'title' => '❓ FAQ', + 'content' => implode( '

', $html ), + ]; +} + +// ============================================================ +// SPIELER-NAME EXTRAHIEREN +// ============================================================ + +function mm_extract_player_name( $q ) { + // Explizit: "von Steve", "spieler Steve", "für Steve" + if ( preg_match( '/(?:von|spieler|name|für|player|ban.*?von|bann.*?von)[:\s]+([A-Za-z0-9_]{3,16})/iu', $q, $m ) ) { + return $m[1]; + } + // "Steve ist gebannt", "ist Steve gebannt" + if ( preg_match( '/\b([A-Za-z0-9_]{3,16})\s+ist\s+(gebannt|gesperrt|online|offline)/i', $q, $m ) ) { + return $m[1]; + } + // "bin ich" → kein Name (eingeloggter User, kein Minecraft-Login hier) + if ( preg_match( '/\b(bin ich|ich bin|mein ban|mein status)\b/i', $q ) ) { + return ''; + } + // Allgemein: letztes kapitalisiertes Wort als Spielername (MC-Namen beginnen meist groß) + if ( preg_match_all( '/\b([A-Z][A-Za-z0-9_]{2,15})\b/', $q, $m ) ) { + $stop = [ 'Minecraft', 'Wiki', 'Forum', 'FAQ', 'Shop', 'Discord', 'Ban', 'Mute', + 'Kick', 'Warn', 'Spielzeit', 'Playtime', 'Galerie', 'Ticket', 'Support' ]; + foreach ( array_reverse( $m[1] ) as $candidate ) { + if ( ! in_array( $candidate, $stop, true ) ) { + return $candidate; + } + } + } + return ''; +} + +// ============================================================ +// FALLBACK +// ============================================================ + +function mm_fallback_response( $bot ) { + $hints = [ + '🖥️ Server – z.B. "Wie ist die Server-IP?"', + ]; + if ( ! empty( $bot['url_rules'] ) ) $hints[] = '📜 Regeln – z.B. "Ist PvP erlaubt?"'; + if ( ! empty( $bot['url_player_history'] ) ) $hints[] = '👤 Spieler-Info – z.B. "Spielzeit von Steve"'; + if ( ! empty( $bot['litebans_dashboard_url'] ) || ! empty( get_option( 'wp_litebans_pro_settings', [] )['db_name'] ) ) + $hints[] = '🔨 Ban-Status – z.B. "Ist Steve gebannt?"'; + if ( ! empty( $bot['url_wiki'] ) ) $hints[] = '📖 Wiki – z.B. "Wiki Befehle"'; + if ( ! empty( $bot['url_gallery'] ) ) $hints[] = '📷 Galerie – z.B. "Zeig die Galerie"'; + if ( ! empty( $bot['url_shop'] ) ) $hints[] = '🛒 Shop – z.B. "Was kostet ein Diamantschwert?"'; + if ( ! empty( $bot['url_tickets'] ) ) $hints[] = '🎫 Ticket – z.B. "Ticket erstellen"'; + + return [ + 'source' => 'fallback', + 'title' => '❓ Ich habe dazu keine Antwort', + 'content' => 'Versuche es mit einem dieser Themen:
' . implode( '
', $hints ), + ]; +} + +// ============================================================ +// RESPONSE BUILDER +// Alle Part-Arrays zu einem einzigen formatierten HTML-String +// zusammenführen. +// ============================================================ + +function mm_build_response( $parts ) { + if ( count( $parts ) === 1 ) { + $p = $parts[0]; + return [ + 'reply' => isset( $p['title'] ) + ? "{$p['title']}
{$p['content']}" + : $p['content'], + 'parts' => $parts, + ]; + } + + $blocks = []; + foreach ( $parts as $p ) { + $block = isset( $p['title'] ) ? "{$p['title']}
" : ''; + $block .= $p['content']; + $blocks[] = $block; + } + + return [ + 'reply' => implode( '
', $blocks ), + 'parts' => $parts, + ]; +} \ No newline at end of file diff --git a/Minecraft-Modern-Theme/inc/assistant-widget.php b/Minecraft-Modern-Theme/inc/assistant-widget.php new file mode 100644 index 0000000..5142cb4 --- /dev/null +++ b/Minecraft-Modern-Theme/inc/assistant-widget.php @@ -0,0 +1,854 @@ + 'mm_bot_sanitize_settings', + ] ); +} ); + +function mm_bot_sanitize_settings( $input ) { + $clean = []; + + $text_fields = [ 'server_ip', 'server_ver', 'server_specs', 'bot_name', 'welcome' ]; + $url_fields = [ + 'url_wiki', 'url_rules', 'url_tickets', 'url_faq', + 'url_team', 'url_shop', 'url_gallery', 'url_player_history', + 'url_forum', 'link_discord', 'litebans_dashboard_url', + ]; + + foreach ( $text_fields as $f ) { + $clean[ $f ] = isset( $input[ $f ] ) ? sanitize_text_field( $input[ $f ] ) : ''; + } + foreach ( $url_fields as $f ) { + $clean[ $f ] = isset( $input[ $f ] ) ? esc_url_raw( $input[ $f ] ) : ''; + } + + $clean['qa'] = []; + if ( ! empty( $input['qa'] ) && is_array( $input['qa'] ) ) { + foreach ( $input['qa'] as $item ) { + if ( empty( $item['keys'] ) ) continue; + $clean['qa'][] = [ + 'keys' => sanitize_text_field( $item['keys'] ), + 'val' => wp_kses_post( $item['val'] ), + ]; + } + } + + return $clean; +} + +// ========================================================================= +// 2. ADMIN-SEITE +// ========================================================================= + +function mm_render_bot_admin() { + $data = get_option( 'mm_bot_data', [] ); + ?> +
+

Bot-Zentrale

+ + + +
+ + + +
+

1. Server-Infos

+ + + + + + + + + + + + + +
+ +

Wird bei Fragen nach der Server-IP angezeigt.

+
+ +

Wird bei Fragen nach der Server-Hardware angezeigt.

+
+
+ + +
+

2. Bot-Einstellungen

+ + + + + + + + + + + + + +
+ + +

UUID / Name wird im Theme-Customizer unter "Assistent" gesetzt.

+
+ +

Erster Text der beim Öffnen des Assistenten angezeigt wird.

+
+
+ + +
+

3. Wichtige Links

+

+ 💡 Wenn eine Seite mit dem passenden Shortcode gefunden wird, erscheint ein „Vorschlag übernehmen"-Button. +

+ + prepare( 'post_content LIKE %s', '%[' . $wpdb->esc_like( $sc ) . '%' ); + } + if ( empty( $conditions ) ) return ''; + $where = implode( ' OR ', $conditions ); + $page = $wpdb->get_row( + "SELECT ID FROM {$wpdb->posts} + WHERE post_status = 'publish' + AND post_type IN ('page','post') + AND ({$where}) + LIMIT 1" + ); + return $page ? get_permalink( $page->ID ) : ''; + } + + $autodetect = [ + 'url_wiki' => mm_find_page_by_shortcode( ['wmw_wiki', 'wmw_search', 'wmw_article'] ), + 'url_rules' => mm_find_page_by_shortcode( ['mrp_rules', 'mc_rules', 'multi_rules'] ), + 'url_tickets' => mm_find_page_by_shortcode( ['wmtp_tickets', 'wm_tickets', 'multi_ticket'] ), + 'url_shop' => mm_find_page_by_shortcode( ['wis_shop', 'ingame_shop', 'wis_items'] ), + 'url_gallery' => mm_find_page_by_shortcode( ['mc_gallery', 'mc_gallery_overview', 'mc_gallery_upload', 'mc_gallery_all_albums'] ), + 'url_faq' => mm_find_page_by_shortcode( ['faq_list', 'faq', 'faq_page'] ), + 'url_player_history' => mm_find_page_by_shortcode( ['mc_player_history', 'mc_players', 'player_history'] ), + 'url_forum' => mm_find_page_by_shortcode( ['business_forum'] ), + 'litebans_dashboard_url' => mm_find_page_by_shortcode( ['litebans_dashboard', 'litebans', 'wp_litebans'] ), + ]; + + $links = [ + 'url_wiki' => [ 'label' => '📖 Wiki-URL', 'plugin' => 'WP Multi Wiki', 'active' => post_type_exists( 'wmw_article' ) ], + 'url_rules' => [ 'label' => '📜 Regelwerk-URL', 'plugin' => 'Multi Rules', 'active' => function_exists( 'mrp_get_plugin_version' ) ], + 'url_tickets' => [ 'label' => '🎫 Ticket/Support-URL', 'plugin' => 'WP Multi Ticket Pro', 'active' => class_exists( 'WP_Multi_Ticket_Pro' ) ], + 'url_shop' => [ 'label' => '🛒 Shop-URL', 'plugin' => 'WP Ingame Shop Pro', 'active' => class_exists( 'WIS_Activator' ) ], + 'url_gallery' => [ 'label' => '📷 Galerie-URL', 'plugin' => 'MC MultiServer Gallery PRO', 'active' => post_type_exists( 'mc_gallery' ) ], + 'url_faq' => [ 'label' => '❓ FAQ-URL', 'plugin' => 'FAQ Post Type', 'active' => post_type_exists( 'faq' ) ], + 'url_player_history' => [ 'label' => '👤 Spieler-History-URL', 'plugin' => 'MC Player History', 'active' => function_exists( 'mcph_get_plugin_version' ) ], + 'url_forum' => [ 'label' => '💬 Forum-URL', 'plugin' => 'WP Business Forum', 'active' => class_exists( 'WBF_DB' ) ], + 'url_team' => [ 'label' => '👥 Team-URL', 'plugin' => '', 'active' => true ], + 'link_discord' => [ 'label' => '💬 Discord-Einladung', 'plugin' => '', 'active' => true ], + 'litebans_dashboard_url' => [ 'label' => '🔨 LiteBans Dashboard-URL', 'plugin' => 'LiteBans Manager', 'active' => class_exists( 'WP_LiteBans_Pro' ) ], + ]; + ?> + + + + + + + + + $cfg ) : + $badge = ''; + if ( $cfg['plugin'] ) { + $cls = $cfg['active'] ? 'badge' : 'badge inactive'; + $txt = $cfg['active'] ? 'aktiv' : 'inaktiv'; + $badge = '' . esc_html( $cfg['plugin'] ) . ' ' . $txt . ''; + } + $saved = $data[ $key ] ?? ''; + $suggested = $autodetect[ $key ] ?? ''; + $show_suggest = $suggested && $suggested !== $saved; + ?> + + + + + +
+
+ + + + + + + + ✔ Erkannt & gesetzt + +
+
+
+ + +
+

4. Individuelle Q&A

+

+ Schlüsselwörter (kommagetrennt) → Antwort. Hat höchste Priorität vor allen Plugin-Abfragen. +

+ + + + + + +
SchlüsselwörterAntwort (HTML erlaubt)
+
+ $item ) : + if ( empty( $item['keys'] ) ) continue; + ?> +
+ + + +
+ +
+ +
+ + +
+
+ + + '🖥️ Server-Status', 'q' => 'server status' ]; + } + + // Regeln – nur wenn URL im Backend gesetzt + if ( ! empty( $data['url_rules'] ) ) { + $quick[] = [ 'label' => '📜 Regelwerk', 'q' => 'regeln' ]; + } + + // Wiki – nur wenn URL im Backend gesetzt + if ( ! empty( $data['url_wiki'] ) ) { + $quick[] = [ 'label' => '📖 Wiki', 'q' => 'wiki' ]; + } + + // Shop – nur wenn URL im Backend gesetzt + global $wpdb; + if ( ! empty( $data['url_shop'] ) ) { + $quick[] = [ 'label' => '🛒 Shop', 'q' => 'shop' ]; + } + + // Ticket / Support – nur wenn URL im Backend gesetzt + if ( ! empty( $data['url_tickets'] ) ) { + $quick[] = [ 'label' => '🎫 Support-Ticket', 'q' => 'ticket erstellen' ]; + } + + // Forum + if ( class_exists( 'WBF_DB' ) && ! empty( $data['url_forum'] ) ) { + $quick[] = [ 'label' => '💬 Forum', 'q' => 'forum' ]; + } + + // Galerie – nur wenn URL im Backend gesetzt + if ( ! empty( $data['url_gallery'] ) ) { + $quick[] = [ 'label' => '📷 Galerie', 'q' => 'galerie' ]; + } + + // Ban-Status + $lb = get_option( 'wp_litebans_pro_settings', [] ); + if ( ! empty( $lb['db_name'] ) ) { + $quick[] = [ 'label' => '🔨 Strafen prüfen', 'q' => 'meine strafen' ]; + } + + // Spieler-History – nur wenn URL im Backend gesetzt + if ( ! empty( $data['url_player_history'] ) ) { + $quick[] = [ 'label' => '⏱️ Spielzeit', 'q' => 'spielzeit' ]; + } + + // Discord + if ( ! empty( $data['link_discord'] ) ) { + $quick[] = [ 'label' => '💬 Discord', 'q' => 'discord' ]; + } + ?> + +
+ + + + + + +
+ + + + +

- +

+
+ 📦 Was wird gesichert:
+ ✓ Customizer-Einstellungen (Farben, Social Links, Menü-Design, etc.)
+ ✓ Livestream API Keys (YouTube, Twitch)
+ ✓ Homepage-Seite (Titel, Inhalt, Highlight-Bild)
+ ✓ Navigation Menüs inkl. aller Items & Struktur
+ ✓ Widget-Konfigurationen
+ ✓ Team-Mitglieder (mit UUID, Avatar, Banner)
+ ✓ FAQ-Einträge & Kategorien
+ ✓ Custom CSS
+ ✓ Announcement-Bar Einstellungen +
+ @@ -87,6 +100,33 @@ endif; // Customizer Register // ========================================================================= function minecraft_modern_customize_register( $wp_customize ) { + // ========================================================================= + // 9. Virtueller Assistent + // ========================================================================= + $wp_customize->add_section( 'assistant_settings', array( + 'title' => __('Virtueller Assistent', 'minecraft-modern-theme'), + 'priority' => 80, + 'description' => __('Steuert den virtuellen Assistenten im Frontend. Avatar basiert auf Minecraft-UUID.', 'minecraft-modern-theme'), + ) ); + $wp_customize->add_setting( 'assistant_enabled', array( + 'default' => false, + 'sanitize_callback' => 'wp_validate_boolean', + ) ); + $wp_customize->add_control( 'assistant_enabled', array( + 'label' => __('Virtuellen Assistenten aktivieren', 'minecraft-modern-theme'), + 'section' => 'assistant_settings', + 'type' => 'checkbox', + ) ); + $wp_customize->add_setting( 'assistant_minecraft_uuid', array( + 'default' => '', + 'sanitize_callback' => 'sanitize_text_field', + ) ); + $wp_customize->add_control( 'assistant_minecraft_uuid', array( + 'label' => __('Minecraft UUID für Avatar', 'minecraft-modern-theme'), + 'section' => 'assistant_settings', + 'type' => 'text', + 'description' => __('Gib die Minecraft-UUID für den Avatar des Assistenten ein.', 'minecraft-modern-theme'), + ) ); // ========================================================================= // 1. HEADER SLIDER @@ -235,18 +275,18 @@ function minecraft_modern_customize_register( $wp_customize ) { // ========================================================================= $wp_customize->add_section( 'social_links', array( 'title' => __('Social Media Links', 'minecraft-modern-theme'), 'priority' => 40 ) ); $social_platforms = array( - 'discord' => 'Discord', 'youtube' => 'YouTube', 'twitter' => 'Twitter (X)', + 'bluesky' => 'BlueSky', 'discord' => 'Discord', 'youtube' => 'YouTube', 'twitter' => 'Twitter (X)', 'facebook' => 'Facebook', 'instagram' => 'Instagram', 'tiktok' => 'TikTok', 'twitch' => 'Twitch', 'steam' => 'Steam', 'github' => 'GitHub', 'linkedin' => 'LinkedIn', 'pinterest' => 'Pinterest', 'reddit' => 'Reddit', - 'teamspeak' => 'Teamspeak', 'spotify' => 'Spotify', + 'mastodon' => 'Mastodon', 'threads' => 'Threads', 'kickstarter' => 'Kickstarter', + 'teamspeak' => 'Teamspeak', 'spotify' => 'Spotify', 'stoat' => 'Stoat', ); foreach ( $social_platforms as $key => $label ) { $wp_customize->add_setting( 'social_' . $key, array( 'sanitize_callback' => 'esc_url_raw' ) ); $wp_customize->add_control( 'social_' . $key, array( 'label' => $label . ' URL', 'section' => 'social_links', 'type' => 'url' ) ); } - // ========================================================================= // 6. FOOTER // ========================================================================= @@ -311,6 +351,60 @@ function minecraft_modern_customize_register( $wp_customize ) { ) ); + // ========================================================================= + // 8.5. VIDEO / LIVESTREAM EINSTELLUNGEN + // ========================================================================= + $wp_customize->add_section( 'video_livestream_settings', array( + 'title' => __('Video & Livestream', 'minecraft-modern-theme'), + 'description' => __('Einstellungen für YouTube Livestream-Erkennung. Hauptmethode: Livestream-Posts unter "Livestreams" erstellen. Optional: Zusätzlichen Hauptkanal hier eintragen.', 'minecraft-modern-theme'), + 'priority' => 75, + ) ); + + $wp_customize->add_setting( 'youtube_api_key', array( + 'default' => '', + 'sanitize_callback' => 'sanitize_text_field', + 'transport' => 'refresh', + ) ); + $wp_customize->add_control( 'youtube_api_key', array( + 'label' => __('YouTube API Key', 'minecraft-modern-theme'), + 'description' => __('Erforderlich für automatische YouTube Live-Erkennung. Hier API Key erstellen', 'minecraft-modern-theme'), + 'section' => 'video_livestream_settings', + 'type' => 'text', + 'input_attrs' => array( + 'placeholder' => 'AIzaSyD...', + ), + ) ); + + $wp_customize->add_setting( 'twitch_client_id', array( + 'default' => '', + 'sanitize_callback' => 'sanitize_text_field', + 'transport' => 'refresh', + ) ); + $wp_customize->add_control( 'twitch_client_id', array( + 'label' => __('Twitch Client ID', 'minecraft-modern-theme'), + 'description' => __('Erforderlich für Twitch Live-Erkennung. Hier App erstellen', 'minecraft-modern-theme'), + 'section' => 'video_livestream_settings', + 'type' => 'text', + 'input_attrs' => array( + 'placeholder' => 'xxxxxxxxxxxxxx', + ), + ) ); + + $wp_customize->add_setting( 'twitch_client_secret', array( + 'default' => '', + 'sanitize_callback' => 'sanitize_text_field', + 'transport' => 'refresh', + ) ); + $wp_customize->add_control( 'twitch_client_secret', array( + 'label' => __('Twitch Client Secret', 'minecraft-modern-theme'), + 'description' => __('Nur für Live-Prüfung. Wird serverseitig genutzt.', 'minecraft-modern-theme'), + 'section' => 'video_livestream_settings', + 'type' => 'text', + 'input_attrs' => array( + 'placeholder' => 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx', + ), + ) ); + // ========================================================================= // 9. EXPORT / IMPORT // ========================================================================= diff --git a/Minecraft-Modern-Theme/inc/theme-updater.php b/Minecraft-Modern-Theme/inc/theme-updater.php index dbad110..1863f67 100644 --- a/Minecraft-Modern-Theme/inc/theme-updater.php +++ b/Minecraft-Modern-Theme/inc/theme-updater.php @@ -1,7 +1,8 @@ theme_slug = get_template(); + + add_action( 'admin_notices', [ $this, 'display_update_notice' ] ); add_action( 'wp_dashboard_setup', [ $this, 'add_dashboard_widget' ] ); - - // Refresh Logik - add_action( 'admin_init', [ $this, 'handle_refresh_request' ] ); + add_action( 'admin_init', [ $this, 'handle_refresh_request' ] ); } /** - * Holt die API-Daten von Gitea + * Holt die API-Daten von Gitea (mit Transient-Cache). */ private function get_latest_release() { $update_data = get_transient( $this->transient_key ); if ( false === $update_data ) { - $api_url = "https://git.viper.ipv64.net/api/v1/repos/{$this->repo}/releases/latest"; + $api_url = "https://git.viper.ipv64.net/api/v1/repos/{$this->repo}/releases/latest"; $response = wp_remote_get( $api_url, [ 'timeout' => 10 ] ); if ( is_wp_error( $response ) || wp_remote_retrieve_response_code( $response ) !== 200 ) { @@ -41,12 +42,12 @@ class Minecraft_Modern_Theme_Manager { return [ 'error' => true ]; } - $data = json_decode( wp_remote_retrieve_body( $response ) ); + $data = json_decode( wp_remote_retrieve_body( $response ) ); $new_version = ltrim( $data->tag_name, 'vV' ); - + $update_data = [ 'version' => $new_version, - 'url' => "https://git.viper.ipv64.net/{$this->repo}/releases" + 'url' => "https://git.viper.ipv64.net/{$this->repo}/releases", ]; set_transient( $this->transient_key, $update_data, 12 * HOUR_IN_SECONDS ); @@ -56,7 +57,7 @@ class Minecraft_Modern_Theme_Manager { } /** - * Zeigt die gelbe Info-Box oben im Admin-Bereich + * Zeigt die gelbe Info-Box oben im Admin-Bereich an, wenn ein Update verfügbar ist. */ public function display_update_notice() { if ( ! current_user_can( 'update_themes' ) ) return; @@ -65,14 +66,18 @@ class Minecraft_Modern_Theme_Manager { if ( isset( $latest['error'] ) ) return; $current_version = wp_get_theme( $this->theme_slug )->get( 'Version' ); + if ( ! $current_version ) return; if ( version_compare( $current_version, $latest['version'], '<' ) ) { ?>

- Minecraft Modern Update verfügbar: Version ist bereit zum Download. - Zum Download → + Minecraft Modern Update verfügbar: + Version ist bereit zum Download. + + Zum Download → +

theme_slug )->get( 'Version' ); - $latest = $this->get_latest_release(); - + $latest = $this->get_latest_release(); + echo '
'; - echo '

Installiert: ' . esc_html( $current_version ) . '

'; + echo '

Installiert: ' . esc_html( $current_version ?: '–' ) . '

'; if ( isset( $latest['version'] ) ) { echo '

Aktuellste: ' . esc_html( $latest['version'] ) . '

'; - - if ( version_compare( $current_version, $latest['version'], '<' ) ) { + + if ( $current_version && version_compare( $current_version, $latest['version'], '<' ) ) { echo '
'; echo '

Update verfügbar!

'; echo 'Download ZIP von Gitea'; @@ -122,7 +127,7 @@ class Minecraft_Modern_Theme_Manager { } /** - * Verarbeitet den Klick auf "Jetzt prüfen" + * Verarbeitet den Klick auf "Update-Cache jetzt leeren". */ public function handle_refresh_request() { if ( isset( $_GET['mm_refresh_check'] ) && check_admin_referer( 'mm_refresh_action' ) ) { @@ -133,5 +138,4 @@ class Minecraft_Modern_Theme_Manager { } } -// Initialisierung new Minecraft_Modern_Theme_Manager(); \ No newline at end of file