Files
Minecraft-Modern-Theme/Minecraft-Modern-Theme/inc/assistant-ajax.php
2026-03-29 22:30:22 +02:00

1988 lines
87 KiB
PHP
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<?php
/**
* Assistant AJAX Handler Komplette Überarbeitung
*
* Integriert alle Plugins:
* ✔ LiteBans Manager (Ban / Mute / Kick / Warn)
* ✔ MC Player History (Spielzeit, Login, Online-Status)
* ✔ BungeeCord Status (Server-IP, Online-Spieler, Sub-Server)
* ✔ Multi Rules (Regelwerk, Tab-Suche)
* ✔ WP Multi Wiki (Wiki-Artikel-Suche)
* ✔ WP Ingame Shop Pro (Items, Kategorien, Daily Deal)
* ✔ WP Multi Ticket Pro (Ticket erstellen / nachschlagen)
* ✔ WP Business Forum (Threads, Kategorien)
* ✔ MC MultiServer Gallery PRO (Galerie-Links)
* ✔ FAQ (Custom Post Type) (FAQ-Suche)
* ✔ Manuelles Q&A (Bot-Setup im Backend)
*
* Verbesserungen:
* → Intent-Scoring : Mehrere Absichten pro Nachricht erkannt
* → Multi-Antworten : Alle passenden Ergebnisse werden kombiniert
* → Spieler-Extraktion: Robuste Regex für Minecraft-Namen
* → Caching : Transients für teure DB-Abfragen
* → Fallback-Kette : Graceful Degradation wenn Plugin fehlt
*
* @version 3.1
* @author M_Viper
*/
if ( ! defined( 'ABSPATH' ) ) exit;
// Doppel-Load verhindern (falls alte und neue Datei gleichzeitig eingebunden)
if ( defined( 'MM_ASSISTANT_AJAX_LOADED' ) ) return;
define( 'MM_ASSISTANT_AJAX_LOADED', true );
// ============================================================
// AJAX Hooks
// ============================================================
add_action( 'wp_ajax_mm_assistant_query', 'mm_assistant_query' );
add_action( 'wp_ajax_nopriv_mm_assistant_query', 'mm_assistant_query' );
// ============================================================
// HAUPTFUNKTION
// ============================================================
function mm_assistant_query() {
// Sicherheit
if ( ! isset( $_POST['nonce'] ) || ! wp_verify_nonce(
sanitize_text_field( wp_unslash( $_POST['nonce'] ) ),
'mm_bot_nonce'
) ) {
wp_send_json_error( 'Sicherheitsfehler.' );
}
$raw = sanitize_text_field( wp_unslash( $_POST['q'] ?? '' ) );
$q = trim( $raw );
$q_lc = mb_strtolower( $q );
if ( strlen( $q ) < 2 ) {
wp_send_json_success( [
'reply' => '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[] = '🌐 <b>Adresse:</b> <code>' . esc_html( $ip ) . '</code>';
if ( $ver ) $lines[] = '🎮 <b>Version:</b> ' . esc_html( $ver );
if ( $specs ) $lines[] = '🖥️ <b>Server:</b> ' . 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 ? '<b>Online</b>' : '<b>Offline</b>';
// 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 ? ' · <b>' . $player_count . '</b> 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[] = '🎮 <b>Version:</b> ' . 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[] = '👥 <b>Spieler:</b> ' . implode( ', ', $names )
. ( $player_count > 8 ? ' <small>+' . ( $player_count - 8 ) . ' weitere</small>' : '' );
}
}
}
// 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[] = '🗺️ <b>Weitere Server:</b> ' . 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[] = '💬 <a href="' . esc_url( $discord ) . '" target="_blank">Discord</a>';
}
return array(
'source' => 'server_status',
'title' => '🖥️ Server-Status',
'content' => implode( '<br>', $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( '</span>', $open );
$open = 0;
}
if ( $bold ) { $output .= '</b>'; $bold = false; }
if ( $italic ) { $output .= '</i>'; $italic = false; }
if ( isset( $colors[ $code ] ) ) {
$output .= '<span style="color:' . $colors[ $code ] . ';">';
$open++;
} elseif ( $code === 'l' ) {
$output .= '<b>';
$bold = true;
} elseif ( $code === 'o' ) {
$output .= '<i>';
$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( '</span>', $open );
if ( $bold ) $output .= '</b>';
if ( $italic ) $output .= '</i>';
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 = '🔍 <b>Welchen Spielernamen soll ich prüfen?</b><br><br>';
$html .= '<small style="opacity:.7;">Tippe deinen Minecraft-Namen direkt ein:</small><br><br>';
$html .= '<div style="display:flex;gap:6px;align-items:center;flex-wrap:wrap;">';
$html .= '<span style="opacity:.6;font-size:12px;">Format:</span>';
$html .= '<button class="mm-quick-btn" data-q="ban:DeinName" type="button" '
. 'style="font-family:monospace;letter-spacing:.5px;">ban:DeinName</button>';
$html .= '</div>';
$html .= '<br><small style="opacity:.6;">Ersetze <b>DeinName</b> mit deinem echten Minecraft-Namen.<br>';
$html .= 'Oder schreibe z.B.: <b>ist Steve gebannt</b></small>';
if ( $dashboard_url ) {
$html .= '<br><br>→ <a href="' . esc_url( $dashboard_url ) . '" target="_blank">LiteBans Dashboard öffnen</a>';
}
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 <b>" . esc_html( $player_name ) . "</b> wurde in der LiteBans-Datenbank nicht gefunden." );
}
$lines = array();
// Avatar + Name
$lines[] = "<div style='display:flex;align-items:center;gap:10px;margin-bottom:6px;'>"
. "<img src='https://minotar.net/avatar/" . esc_attr( $info["player"] ) . "/40' "
. "style='border-radius:4px;width:40px;height:40px;' alt='Avatar'>"
. "<b style='font-size:15px;'>" . esc_html( $info["player"] ) . "</b>"
. "</div>";
// Aktiver Ban
if ( $info["active_ban"] ) {
$b = $info["active_ban"];
$lines[] = "<div style='background:rgba(239,68,68,.15);border-left:3px solid #ef4444;padding:8px 10px;border-radius:0 6px 6px 0;margin:4px 0;'>";
$lines[] = "🚫 <b>Aktiver Ban</b>";
$lines[] = "📋 Grund: " . esc_html( $b["reason"] );
$lines[] = "👮 Von: " . esc_html( $b["by"] );
$lines[] = "⏰ Bis: <b>" . esc_html( $b["until"] ) . "</b>";
$lines[] = "</div>";
} else {
$lines[] = "✅ <b>Kein aktiver Ban</b>";
}
// Aktiver Mute
if ( $info["active_mute"] ) {
$m = $info["active_mute"];
$lines[] = "<div style='background:rgba(245,158,11,.15);border-left:3px solid #f59e0b;padding:8px 10px;border-radius:0 6px 6px 0;margin:4px 0;'>";
$lines[] = "🔇 <b>Aktiver Mute</b>";
$lines[] = "📋 Grund: " . esc_html( $m["reason"] );
$lines[] = "⏰ Bis: <b>" . esc_html( $m["until"] ) . "</b>";
$lines[] = "</div>";
} else {
$lines[] = "✅ <b>Kein aktiver Mute</b>";
}
// Kicks & Warns
$stats = array();
if ( $info["kicks"] > 0 ) $stats[] = "👢 <b>" . $info["kicks"] . "</b> Kick(s)";
if ( $info["warnings"] > 0 ) $stats[] = "⚠️ <b>" . $info["warnings"] . "</b> Verwarnung(en)";
if ( ! empty( $stats ) ) $lines[] = implode( " · ", $stats );
// Ban-History
if ( ! empty( $info["ban_history"] ) ) {
$lines[] = "<br><b>🕐 Letzte Bestrafungen:</b>";
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"] )
. " <small style='opacity:.6;'>(" . esc_html( $h["by"] ) . ")</small>";
}
}
// Entbannungsanträge
if ( ! empty( $info["unban_requests"] ) ) {
$lines[] = "<br><b>📋 Entbannungsanträge:</b>";
foreach ( $info["unban_requests"] as $r ) {
$lines[] = $r["status"] . " " . esc_html( $r["date"] );
}
}
// Links
$link_parts = array();
if ( $dashboard_url ) {
$link_parts[] = "<a href='" . esc_url( $dashboard_url ) . "' target='_blank'>Dashboard</a>";
}
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[] = "<a href='" . esc_url( $unban_url ) . "' target='_blank'><b>Entbannungsantrag stellen →</b></a>";
}
if ( ! empty( $link_parts ) ) $lines[] = "<br>" . implode( " · ", $link_parts );
$status_icon = $info["active_ban"] ? "🚫" : "";
return array(
"source" => "ban_check",
"title" => $status_icon . " Ban-Status: " . esc_html( $info["player"] ),
"content" => implode( "<br>", $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.: <b>Spielzeit von Steve</b><br><br>';
if ( ! empty( $top ) ) {
$html .= '🏆 <b>Top-Spieler:</b><br>';
$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 . ' ' : '' )
. '<b>' . esc_html( $p->username ) . '</b>'
. ' ' . esc_html( $time_str ) . '<br>';
$i++;
}
$html .= '<br>';
}
$html .= "→ <a href='" . esc_url( $history_url ) . "' target='_blank'><b>Alle Spieler ansehen</b></a>";
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 <b>' . esc_html( $player_name ) . '</b> wurde noch nicht auf dem Server gesehen.'
. '<br><br>→ <a href="' . esc_url( $history_url ) . '" target="_blank">Spieler-Liste öffnen</a>',
];
}
$online_icon = $row->is_online
? '🟢 <b>Gerade online</b>'
: '⚫ 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: <b>' . esc_html( $playtime ) . '</b>',
'🕐 Zuletzt gesehen: <b>' . esc_html( $last_seen ) . '</b>',
'📅 Erstmals gesehen: <b>' . esc_html( $first_seen ) . '</b>',
];
// 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[] = '<img src="https://mc-heads.net/avatar/' . esc_attr( $row->username ) . '/32" '
. 'style="border-radius:4px;vertical-align:middle;margin-right:4px;" alt="Avatar"> '
. '<small><a href="' . esc_url( $history_url ) . '" target="_blank">Spieler-Liste öffnen</a></small>';
return [
'source' => 'player_history',
'title' => '👤 Spieler: ' . esc_html( $row->username ),
'content' => implode( '<br>', $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' => '<span style="color:#000000">',
'&1' => '<span style="color:#0000AA">',
'&2' => '<span style="color:#00AA00">',
'&3' => '<span style="color:#00AAAA">',
'&4' => '<span style="color:#AA0000">',
'&5' => '<span style="color:#AA00AA">',
'&6' => '<span style="color:#FFAA00">',
'&7' => '<span style="color:#AAAAAA">',
'&8' => '<span style="color:#555555">',
'&9' => '<span style="color:#5555FF">',
'&a' => '<span style="color:#55FF55">',
'&b' => '<span style="color:#55FFFF">',
'&c' => '<span style="color:#FF5555">',
'&d' => '<span style="color:#FF55FF">',
'&e' => '<span style="color:#FFFF55">',
'&f' => '<span style="color:#FFFFFF">',
'&l' => '<span style="font-weight:bold">',
'&o' => '<span style="font-style:italic">',
'&n' => '<span style="text-decoration:underline">',
'&m' => '<span style="text-decoration:line-through">',
];
// 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( '</span>', $open );
$open = 0;
return $close;
}, $text );
// Spans mitzählen
preg_match_all( '/<span/', $text, $opens );
preg_match_all( '/<\/span>/', $text, $closes );
$remaining = count( $opens[0] ) - count( $closes[0] );
if ( $remaining > 0 ) {
$text .= str_repeat( '</span>', $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' => "→ <a href='" . esc_url( $rules_url ) . "' target='_blank'><b>Zum Regelwerk</b></a>",
);
}
$html = '<small style="opacity:.7;">Wähle eine Kategorie:</small><br><br>';
$html .= '<div class="mm-bot-quick" style="flex-direction:column;gap:6px;">';
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 .= '<button class="mm-quick-btn" data-q="' . esc_attr( $query ) . '" type="button">'
. '📂 ' . esc_html( $tab_title )
. ' <small style="opacity:.6;">(' . $rule_count . ')</small>'
. '</button>';
}
$html .= '</div>';
$html .= '<br><small>→ <a href="' . esc_url( $rules_url ) . '" target="_blank">Alle Regeln im Browser öffnen</a></small>';
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 <b>' . esc_html( $tab_name ) . '</b> nicht gefunden.<br>'
. "→ <a href='" . esc_url( $rules_url ) . "' target='_blank'>Zum Regelwerk</a>",
);
}
$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 = '<p style="margin:4px 0;font-size:13px;opacity:.9;">'
. esc_html( mb_substr( $content_plain, 0, 300 ) ) . '…</p>';
} else {
$rendered = '<div style="font-size:13px;line-height:1.6;margin-top:4px;opacity:.9;">'
. wp_kses_post( $content )
. '</div>';
}
$html .= '<div style="margin-bottom:8px;padding:9px 12px;'
. 'background:rgba(255,255,255,.05);'
. 'border-left:3px solid #0099ff;'
. 'border-radius:0 6px 6px 0;">';
$html .= '📌 <b>' . esc_html( $title ) . '</b>';
$html .= $rendered;
$html .= '</div>';
}
// Zurück-Button zur Kategorieauswahl
$html .= '<br><div style="display:flex;gap:8px;flex-wrap:wrap;">';
$html .= '<button class="mm-quick-btn" data-q="regeln" type="button">← Zurück</button>';
$html .= '<a href="' . esc_url( $rules_url ) . '" target="_blank" '
. 'style="font-size:12px;color:#5bc0eb;align-self:center;">'
. 'Im Browser öffnen</a>';
$html .= '</div>';
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 <b>' . esc_html( $q_clean ) . '</b> gefunden.<br><br>';
$html .= '<small style="opacity:.7;">Wähle eine Kategorie:</small><br><br>';
$html .= '<div class="mm-bot-quick" style="flex-direction:column;gap:6px;">';
foreach ( $tabs as $tab ) {
$tab_title = $tab['title'] ?? 'Allgemein';
$html .= '<button class="mm-quick-btn" data-q="' . esc_attr( 'regeln:' . $tab_title ) . '" type="button">'
. '📂 ' . esc_html( $tab_title ) . '</button>';
}
$html .= '</div>';
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 = '<p style="margin:4px 0;font-size:13px;opacity:.9;">'
. esc_html( mb_substr( $content_plain, 0, 300 ) ) . '…</p>';
} else {
$rendered = '<div style="font-size:13px;line-height:1.6;margin-top:4px;opacity:.9;">'
. wp_kses_post( $r['content'] )
. '</div>';
}
$html .= '<div style="margin-bottom:8px;padding:9px 12px;'
. 'background:rgba(255,255,255,.05);'
. 'border-left:3px solid #0099ff;'
. 'border-radius:0 6px 6px 0;">';
$html .= '📌 <b>' . esc_html( $r['title'] ) . '</b>';
$html .= ' <small style="opacity:.6;">(' . esc_html( $r['tab'] ) . ')</small>';
$html .= $rendered;
$html .= '</div>';
}
if ( count( $matches ) > 3 ) {
$html .= '<small style="opacity:.7;">' . ( count( $matches ) - 3 ) . ' weitere Treffer vorhanden.</small><br>';
}
$html .= '<br><div style="display:flex;gap:8px;flex-wrap:wrap;">';
$html .= '<button class="mm-quick-btn" data-q="regeln" type="button">← Alle Kategorien</button>';
$html .= '<a href="' . esc_url( $rules_url ) . '" target="_blank" '
. 'style="font-size:12px;color:#5bc0eb;align-self:center;">Im Browser öffnen</a>';
$html .= '</div>';
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 '→ <a href="' . esc_url( get_permalink( $w->ID ) ) . '">'
. esc_html( $w->post_title ) . '</a>';
}, $wikis );
return [
'source' => 'wiki',
'title' => '📖 Wiki',
'content' => 'Verfügbare Wikis:<br>' . implode( '<br>', $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 ? " → <a href='" . esc_url( $url ) . "' target='_blank'>Wiki öffnen</a>" : '';
return [
'source' => 'wiki',
'title' => '📖 Wiki',
'content' => 'Kein Wiki-Artikel zu &bdquo;' . esc_html( $search ) . '&ldquo; gefunden.' . $link,
];
}
$html = [];
foreach ( $results as $post ) {
$excerpt = get_the_excerpt( $post->ID );
$html[] = '📄 <a href="' . esc_url( get_permalink( $post->ID ) ) . '"><b>'
. esc_html( $post->post_title ) . '</b></a>'
. ( $excerpt ? '<br><small>' . wp_trim_words( $excerpt, 25, '…' ) . '</small>' : '' );
}
return [
'source' => 'wiki',
'title' => '📖 Wiki-Treffer',
'content' => implode( '<br><br>', $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[] = '<div style="width:100%;max-width:none;background:#40444b;color:#dcddde;padding:0;margin:0;border-radius:0;text-align:center;">';
// ── 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[] = '<div style="text-align:center; margin:8px 0 12px 0;">'
. '🔥 <b>Tagesaktion:</b><br>'
. '<img src="' . esc_url($img) . '" alt="' . esc_attr($deal->name) . '" style="width:64px;height:64px;display:block;margin:0 auto 6px auto;">'
. '<div style="font-size:1.1em;font-weight:bold;margin-bottom:2px;">' . esc_html( $deal->name ) . '</div>'
. '<span style="font-size:1.1em;">'
. '<b>' . esc_html( (string) $offer ) . ' ' . esc_html( $currency ) . '</b>'
. ( $deal->price != $offer ? ' <s>' . esc_html( (string) $deal->price ) . '</s>' : '' )
. '</span>'
. ( $item_link ? '<br>→ <a href="' . $item_link . '" target="_blank">Zum Item im Shop</a>' : '' )
. '</div>';
}
}
// ── 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 = '<div style="font-size:0.97em;color:#888;margin-top:6px;">Verfügbar auf: <span style=\'color:#444;\'>' . esc_html(implode(', ', $servers)) . '</span></div>';
}
}
$lines[] = '<div style="max-width:320px;margin:18px auto 12px auto;padding:14px 16px 12px 16px;background:#ececf1;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,0.08);display:flex;flex-direction:column;align-items:center;">'
. '<div style="font-size:1.05em;font-weight:600;color:#6b7280;margin-bottom:10px;text-align:center;">🛒 Shop-Item:</div>'
. '<img src="' . esc_url($img) . '" alt="' . esc_attr($item_display_name) . '" style="width:56px;height:56px;border-radius:8px;background:#e5e7eb;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:12px;">'
. '<div style="font-size:1.13em;font-weight:700;color:#23272e;margin-bottom:6px;text-align:center;">' . esc_html($item_display_name) . '</div>'
. '<div style="font-size:1.12em;font-weight:700;color:#22c55e;margin-bottom:2px;text-align:center;">' . esc_html((string)$item->price) . ' ' . esc_html($currency) . '</div>'
. $server_list
. '</div>';
} else {
$lines[] = '🛒 <b>Gefundene Items:</b>';
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[] = '<img src="' . esc_url($img) . '" alt="' . esc_attr($item->item_title) . '" style="width:48px;height:48px;vertical-align:middle;margin-right:8px;">'
. '<b>' . esc_html($item->item_title) . '</b>'
. ' ' . esc_html((string)$item->price) . ' ' . esc_html($currency)
. ($item_link ? ' <a href="' . $item_link . '" target="_blank">[Shop]</a>' : '');
}
}
} 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[] = '<div style="display:flex;align-items:center;gap:10px;margin-bottom:6px;">'
. '<img src="' . esc_url($img) . '" alt="' . esc_attr($item->name) . '" style="width:48px;height:48px;">'
. '<div><b>' . esc_html($item->name) . '</b><br>'
. 'Preis: <b>' . esc_html((string)$item->price) . ' ' . esc_html($currency) . '</b>'
. ($item_link ? ' <a href="' . $item_link . '" target="_blank">[Shop]</a>' : '')
. '</div></div>';
} 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 = '<div style="font-size:0.97em;color:#888;margin-top:6px;">Verfügbar auf: <span style=\'color:#444;\'>' . esc_html(implode(', ', $servers)) . '</span></div>';
}
}
$lines[] = '<div style="max-width:320px;margin:18px auto 12px auto;padding:14px 16px 12px 16px;background:#ececf1;border-radius:16px;box-shadow:0 2px 12px rgba(0,0,0,0.08);display:flex;flex-direction:column;align-items:center;">'
. '<div style="font-size:1.05em;font-weight:600;color:#6b7280;margin-bottom:10px;text-align:center;">🛒 Shop-Item:</div>'
. '<img src="' . esc_url($img) . '" alt="' . esc_attr($item_display_name) . '" style="width:56px;height:56px;border-radius:8px;background:#e5e7eb;box-shadow:0 1px 4px rgba(0,0,0,0.06);margin-bottom:12px;">'
. '<div style="font-size:1.13em;font-weight:700;color:#23272e;margin-bottom:6px;text-align:center;">' . esc_html($item_display_name) . '</div>'
. '<div style="font-size:1.12em;font-weight:700;color:#22c55e;margin-bottom:2px;text-align:center;">' . esc_html((string)$fuzzy->price) . ' ' . esc_html($currency) . '</div>'
. $server_list
. '</div>';
} else {
$lines[] = 'Kein Item mit &bdquo;' . esc_html($search) . '&ldquo; gefunden.';
}
}
}
} else {
// Übersicht: Kategorien + Item-Anzahl
$total = (int) $wpdb->get_var( "SELECT COUNT(*) FROM $t_items WHERE status = 'publish'" );
$lines[] = '🛒 <b>Shop-Übersicht:</b> <b>' . $total . ' Items</b> 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[] = '📂 <b>Kategorien:</b>';
foreach ( $cats as $cat ) {
$lines[] = '&nbsp;• ' . 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: <b>' . esc_html( $cheapest->item_title ) . '</b> ' . esc_html( (string) $cheapest->price ) . ' ' . esc_html( $currency );
if ( $priciest ) $lines[] = '💎 Teuerstes: <b>' . esc_html( $priciest->item_title ) . '</b> ' . esc_html( (string) $priciest->price ) . ' ' . esc_html( $currency );
}
if ( $shop_url ) {
$lines[] = "<div style='text-align:center;margin:10px 0 0 0;'><a href='" . esc_url( $shop_url ) . "' target='_blank' style='color:#2563eb;font-size:1em;text-decoration:underline;font-weight:500;'>→ Zum Item im Shop</a></div>";
}
return [
'source' => 'shop',
'title' => '🛒 Shop',
'content' => implode( '<br>', $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 = '<br>💬 Letzte Nachricht: <i>'
. esc_html( wp_trim_words( $msg->message, 20, '…' ) )
. '</i> <small>(' . wp_date( 'd.m.Y H:i', strtotime( $msg->created_at ) ) . ')</small>';
}
}
$lines = [
'🎫 Ticket <b>#' . $ticket->id . '</b>',
'📋 Betreff: <b>' . esc_html( $ticket->subject ) . '</b>',
'📌 Status: <b>' . esc_html( $ticket->status ) . '</b>',
'🏷️ 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( '<br>', $lines ),
];
} else {
return [
'source' => 'ticket',
'title' => '🎫 Ticket nicht gefunden',
'content' => 'Ticket <b>#' . $ticket_id . '</b> 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: <b>' . $total . '</b> Tickets · Offen: <b>' . $open . '</b> · Geschlossen: <b>' . $closed . '</b>';
// 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[] = '📂 <b>Kategorien:</b> ' . implode( ' · ', $cat_parts );
}
}
$ticket_url = $bot['url_tickets'] ?? '';
if ( $ticket_url ) {
$lines[] = "→ <a href='" . esc_url( $ticket_url ) . "' target='_blank'><b>Neues Ticket erstellen</b></a>";
$lines[] = '<small>Oder gib eine Ticket-Nummer ein, z.B.: <b>#1234</b></small>';
} else {
$lines[] = 'Bitte nutze das Support-Formular auf unserer Website.';
}
return [
'source' => 'ticket',
'title' => '🎫 Support-Ticket',
'content' => implode( '<br>', $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[] = '📝 <b>Registrierung:</b>';
if ( $reg_mode === 'disabled' ) {
$lines[] = '❌ Die Registrierung ist derzeit <b>deaktiviert</b>.';
} elseif ( $reg_mode === 'invite' ) {
$lines[] = '🔒 Das Forum nutzt <b>Einladungscodes</b>. Du benötigst einen gültigen Code.';
} else {
$lines[] = '✅ Die Registrierung ist <b>offen</b> — jederzeit möglich.';
}
if ( $member_count > 0 ) {
$lines[] = '<small>👥 Bereits <b>' . $member_count . '</b> Mitglieder dabei.</small>';
}
} catch ( \Throwable $e ) {
$lines[] = '📝 Registrierung verfügbar.';
}
} else {
$lines[] = '📝 <b>Registrierung:</b>';
$lines[] = 'Erstelle deinen Account direkt im Forum.';
}
$lines[] = "→ <a href='{$forum_url}' target='_blank'><b>Zum Forum &amp; Registrierung</b></a>";
return [
'source' => 'forum',
'title' => '💬 Forum-Registrierung',
'content' => implode( '<br>', $lines ),
];
}
// ── Sub-Intent: Login / Passwort ──────────────────────────
if ( preg_match( '/\b(login|einloggen|passwort vergessen|passwort.?reset|zugangsdaten)\b/i', $q ) ) {
$lines = [];
$lines[] = '🔑 <b>Forum-Login:</b>';
$lines[] = "→ <a href='{$forum_url}' target='_blank'><b>Zum Forum &amp; Login</b></a>";
if ( preg_match( '/passwort/i', $q ) ) {
$lines[] = '<br>🔓 <b>Passwort vergessen?</b>';
$lines[] = 'Klicke auf der Login-Seite auf <em>„Passwort vergessen"</em>.';
}
return [
'source' => 'forum',
'title' => '💬 Forum-Login',
'content' => implode( '<br>', $lines ),
];
}
// ── Sub-Intent: Profil ────────────────────────────────────
if ( preg_match( '/\b(profil|mein konto|mein account|avatar|signatur|forum rang|forum level)\b/i', $q ) ) {
$lines = [];
$lines[] = '👤 <b>Dein Forum-Profil</b> enthält:';
$lines[] = '&nbsp;• Anzeigename, Avatar &amp; Signatur';
if ( class_exists( 'WBF_Levels' ) && WBF_Levels::is_enabled() ) {
$lines[] = '&nbsp;• Level-System (basiert auf Beitragsanzahl)';
}
$lines[] = '&nbsp;• Rollen-Badge, Beitragsanzahl &amp; Registrierungsdatum';
$lines[] = "→ <a href='{$forum_url}' target='_blank'><b>Zum Forum → Profil öffnen</b></a>";
return [
'source' => 'forum',
'title' => '💬 Forum-Profil',
'content' => implode( '<br>', $lines ),
];
}
// ── Sub-Intent: Nachrichten / DM ──────────────────────────
if ( preg_match( '/\b(direktnachricht|forum nachricht|forum inbox|pn schreiben|privat.?nachricht)\b/i', $q ) ) {
$lines = [];
$lines[] = '✉️ <b>Direktnachrichten</b> im Forum:';
$lines[] = 'Du kannst eingeloggten Mitgliedern direkt Nachrichten schreiben.';
$lines[] = "→ <a href='{$forum_url}' target='_blank'><b>Forum öffnen → Postfach</b></a>";
return [
'source' => 'forum',
'title' => '💬 Forum-Nachrichten',
'content' => implode( '<br>', $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[] = '✏️ <b>Neuen Thread erstellen:</b>';
$lines[] = '① Melde dich im Forum an.';
$lines[] = '② Wähle die passende Kategorie.';
$lines[] = '③ Klicke auf <em>„Neuen Thread"</em> und fülle Titel &amp; Inhalt aus.';
$lines[] = "→ <a href='{$forum_url}' target='_blank'><b>Forum öffnen</b></a>";
return [
'source' => 'forum',
'title' => '💬 Thread erstellen',
'content' => implode( '<br>', $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 .= '<div style="margin-bottom:6px;padding:7px 10px;background:rgba(255,255,255,.04);border-left:2px solid #0099ff;border-radius:0 5px 5px 0;">';
$html .= $pin . '<a href="' . esc_url( $t_url ) . '" target="_blank"><b>' . esc_html( $title_short ) . '</b></a>';
if ( $t->cat_name ) $html .= ' <small style="opacity:.6;">· ' . esc_html( $t->cat_name ) . '</small>';
if ( ! empty( $meta ) ) $html .= '<br><small style="opacity:.55;">' . implode( ' · ', $meta ) . '</small>';
$html .= '</div>';
}
$html .= "<br>→ <a href='{$forum_url}' target='_blank'>Alle Threads im Forum</a>";
return [
'source' => 'forum',
'title' => '💬 Forum ' . count( $threads ) . ' Treffer',
'content' => $html,
];
}
return [
'source' => 'forum',
'title' => '💬 Forum',
'content' => 'Kein Thread zu <b>' . esc_html( $search ) . '</b> gefunden.<br>'
. "→ <a href='{$forum_url}' target='_blank'>Forum öffnen &amp; selbst suchen</a>",
];
} 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[] = '<b>' . $total_threads . '</b> Threads';
if ( $total_posts > 0 ) $stat_parts[] = '<b>' . $total_posts . '</b> Beiträge';
if ( $total_members > 0 ) $stat_parts[] = '<b>' . $total_members . '</b> Mitglieder';
if ( ! empty( $stat_parts ) ) {
$lines[] = '📊 ' . implode( ' · ', $stat_parts );
}
$online_icon = $online_count > 0 ? '🟢' : '⚫';
$lines[] = $online_icon . ' ' . ( $online_count > 0
? '<b>' . $online_count . '</b> 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[] = '<br>📂 <b>Kategorien:</b>';
foreach ( $cats as $cat ) {
$c_url = $forum_url . '?cat=' . esc_attr( $cat->slug );
$count = $cat->thread_count > 0 ? ' <small style="opacity:.55;">(' . (int) $cat->thread_count . ')</small>' : '';
$lines[] = '&nbsp;→ <a href="' . esc_url( $c_url ) . '" target="_blank">'
. esc_html( $cat->name ) . '</a>' . $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[] = '<br>🆕 <b>Neueste Threads:</b>';
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 ? ' <small style="opacity:.55;">(' . (int) $r->reply_count . ' Antw.)</small>' : '';
$lines[] = '&nbsp;→ <a href="' . esc_url( $t_url ) . '" target="_blank">'
. esc_html( $title_short ) . '</a>' . $replies;
}
}
} catch ( \Throwable $e ) {
$lines[] = '⚠️ Forum-Daten konnten nicht geladen werden.';
}
}
$lines[] = ( ! empty( $lines ) ? '<br>' : '' )
. "→ <a href='{$forum_url}' target='_blank'><b>Forum öffnen</b></a>";
return [
'source' => 'forum',
'title' => '💬 Forum',
'content' => implode( '<br>', $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[] = "📷 → <a href='" . esc_url( $gallery_url ) . "' target='_blank'><b>Galerie öffnen</b></a>";
// ── 2. Upload-Anleitung (bei Upload-Frage oder allgemein) ─
if ( $is_upload_query ) {
$lines[] = '<hr style="border:0;border-top:1px solid rgba(255,255,255,.1);margin:8px 0">';
$lines[] = '📤 <b>Bilder hochladen so geht\'s:</b>';
$lines[] = '① <b>Galerie öffnen</b> und auf <em>„Bilder hochladen"</em> klicken.';
$lines[] = '② Deinen <b>Minecraft-Namen</b> eingeben und den Server wählen → <em>Session starten</em>.';
$lines[] = '③ Im Spiel den Befehl <code>/verify [token]</code> eingeben, um dich zu verifizieren.';
$lines[] = '④ <b>Screenshots auswählen</b> (optional Album anlegen) → hochladen ✓';
$lines[] = '<small>💡 Der Verify-Token ist 5 Minuten gültig. Du musst dazu online auf dem Server sein.</small>';
} 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 = ' <small>von <b>' . esc_html( $player ) . '</b></small>';
}
}
if ( $img ) {
$lines[] = '<hr style="border:0;border-top:1px solid rgba(255,255,255,.1);margin:8px 0">';
$lines[] = '🌟 <b>Bild des Tages' . $uploader . ':</b>';
$lines[] = "<a href='" . esc_url( $full ? $full[0] : $img[0] ) . "' target='_blank'>"
. "<img src='" . esc_url( $img[0] ) . "' "
. "style='max-width:100%;border-radius:8px;margin-top:4px;cursor:pointer;' "
. "alt='Bild des Tages'>"
. "</a>";
}
} else {
// Kein Tagesbild gecacht → Hinweis
$lines[] = '<small>📅 Heute noch kein Bild des Tages verfügbar.</small>';
}
// Galerie-Statistik
$count = wp_count_posts( 'mc_gallery' );
if ( $count && $count->publish > 0 ) {
$lines[] = '<small>📁 ' . intval( $count->publish ) . ' Galerie(n) auf dem Server.</small>';
}
}
return [
'source' => 'gallery',
'title' => $is_upload_query ? '📤 Galerie & Upload' : '📷 Galerie',
'content' => implode( '<br>', $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[] = '❓ <b>' . esc_html( $post->post_title ) . '</b>'
. ( $excerpt ? '<br>' . wp_trim_words( $excerpt, 40, '…' ) : '' );
}
return [
'source' => 'faq',
'title' => '❓ FAQ',
'content' => implode( '<br><br>', $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 = [
'🖥️ <b>Server</b> z.B. "Wie ist die Server-IP?"',
];
if ( ! empty( $bot['url_rules'] ) ) $hints[] = '📜 <b>Regeln</b> z.B. "Ist PvP erlaubt?"';
if ( ! empty( $bot['url_player_history'] ) ) $hints[] = '👤 <b>Spieler-Info</b> z.B. "Spielzeit von Steve"';
if ( ! empty( $bot['litebans_dashboard_url'] ) || ! empty( get_option( 'wp_litebans_pro_settings', [] )['db_name'] ) )
$hints[] = '🔨 <b>Ban-Status</b> z.B. "Ist Steve gebannt?"';
if ( ! empty( $bot['url_wiki'] ) ) $hints[] = '📖 <b>Wiki</b> z.B. "Wiki Befehle"';
if ( ! empty( $bot['url_gallery'] ) ) $hints[] = '📷 <b>Galerie</b> z.B. "Zeig die Galerie"';
if ( ! empty( $bot['url_shop'] ) ) $hints[] = '🛒 <b>Shop</b> z.B. "Was kostet ein Diamantschwert?"';
if ( ! empty( $bot['url_tickets'] ) ) $hints[] = '🎫 <b>Ticket</b> z.B. "Ticket erstellen"';
return [
'source' => 'fallback',
'title' => '❓ Ich habe dazu keine Antwort',
'content' => 'Versuche es mit einem dieser Themen:<br>' . implode( '<br>', $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'] )
? "<b>{$p['title']}</b><br>{$p['content']}"
: $p['content'],
'parts' => $parts,
];
}
$blocks = [];
foreach ( $parts as $p ) {
$block = isset( $p['title'] ) ? "<b>{$p['title']}</b><br>" : '';
$block .= $p['content'];
$blocks[] = $block;
}
return [
'reply' => implode( '<hr style="margin:10px 0;border:0;border-top:1px solid rgba(255,255,255,.15)">', $blocks ),
'parts' => $parts,
];
}