Chat API — Ruleta
Backend PHP + MySQL para el sistema de chat del juego de ruleta (Godot 4). Simula tiempo real mediante long-polling, sin WebSockets, compatible con hosting compartido HostGator.
Componentes del sistema
| Componente | Tecnología | Descripción |
|---|---|---|
| Backend API | PHP 7.4+ | Endpoints REST para enviar, recibir y moderar mensajes |
| Base de datos | MySQL 5.7+ | Almacena mensajes y lista de jugadores baneados |
| Chat web | JS Vanilla | Interfaz de chat pública para cualquier usuario |
| Panel admin | JS Vanilla | Logs en tiempo real, moderación y mensajes del sistema |
| Cliente Godot | GDScript 4 | Integración nativa con el juego de ruleta |
Estructura del proyecto
Requisitos
| Componente | Versión mínima | Notas |
|---|---|---|
| PHP | 7.4 | Se usa fn() arrow functions y str_contains() |
| MySQL / MariaDB | 5.7 / 10.2 | Se requiere soporte de tipo JSON |
| Extensión PDO | Cualquiera | Con driver pdo_mysql habilitado |
| mod_rewrite | — | Para las reglas del .htaccess |
Pasos de instalación
-
Crear base de datos en cPanel
Ve a cPanel → MySQL® Databases. Crea una base de datos, un usuario y asígnalo con todos los permisos. Anota el nombre de la BD, usuario y contraseña. -
Importar el schema
En phpMyAdmin, selecciona tu base de datos, ve a la pestaña Importar y subeschema.sql. Esto crea las tablaschat_messagesybanned_players. -
Editar
config.php
Llena las credenciales de BD y genera las API keys con OpenSSL (ver sección Configuración). -
Subir archivos
Sube la carpetachat-api/completa apublic_html/via FTP o el Administrador de archivos de cPanel.public_html/ └── chat-api/ ← aquí
-
Verificar permisos
Asegúrate de queconfig.phptenga permisos644y que.htaccessesté presente en la raíz dechat-api/. -
Probar el endpoint
Abre en el navegador:https://tudominio.com/chat-api/api/get_messages.php?wait=0
Respuesta esperada:{"ok":true,"since_id":0,"messages":[]}
Configuración — config.php
// Base de datos define('DB_HOST', 'localhost'); define('DB_NAME', 'cpanelusr_chat'); define('DB_USER', 'cpanelusr_chatuser'); define('DB_PASS', 'tu_password'); // Genera con: openssl rand -hex 32 define('SYSTEM_API_KEY', 'clave_servidor_juego'); // ← servidor Godot define('ADMIN_API_KEY', 'clave_moderacion'); // ← endpoints REST de moderación // Contraseña del panel web admin define('ADMIN_WEB_PASSWORD', 'contrasena_panel_web'); // Long-polling define('LONG_POLL_TIMEOUT', 25); // segundos máximo de espera define('LONG_POLL_INTERVAL', 0.75); // segundos entre consultas a BD
Generar API keys seguras
# En terminal (Linux/Mac/WSL): openssl rand -hex 32 # Salida de ejemplo: a3f7c2e891b04d56f2a8e3c7b1d94f20e5a6b8c3d7e2f1a4b9c6d8e3f2a1b5c4
SYSTEM_API_KEY ni ADMIN_API_KEY en el cliente exportado de Godot. El cliente Godot solo usa los endpoints públicos. Tampoco subas config.php a repositorios públicos (GitHub, GitLab, etc.).Esquema SQL
Importar schema.sql en phpMyAdmin. Crea dos tablas:
CREATE TABLE chat_messages ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, type VARCHAR(20) NOT NULL, sender VARCHAR(100) NOT NULL, text TEXT NOT NULL, data JSON NULL, created_at DOUBLE NOT NULL, -- microtime(true) PRIMARY KEY (id) ); CREATE TABLE banned_players ( id INT UNSIGNED NOT NULL AUTO_INCREMENT, player_id VARCHAR(100) NOT NULL, reason VARCHAR(255) NULL, banned_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, banned_until DATETIME NULL, -- NULL = permanente PRIMARY KEY (id), UNIQUE KEY (player_id) );
Tablas
chat_messages
| Campo | Tipo | Descripción |
|---|---|---|
id | INT UNSIGNED PK | Autoincremental |
type | VARCHAR(20) | CHAT, SYSTEM, BET, PRIZE_TOTAL, PRIZE_TOP, TIP, ERROR |
sender | VARCHAR(100) | ID o nombre del remitente |
text | TEXT | Contenido visible del mensaje |
data | JSON NULL | Datos estructurados (monto, selección, etc.) |
created_at | DOUBLE | Timestamp Unix con microsegundos (microtime(true)) |
banned_players
| Campo | Tipo | Descripción |
|---|---|---|
id | INT UNSIGNED PK | Autoincremental |
player_id | VARCHAR(100) UNIQUE | ID del jugador silenciado |
reason | VARCHAR(255) NULL | Motivo (opcional) |
banned_at | DATETIME | Fecha del silenciamiento |
banned_until | DATETIME NULL | Fin del baneo. NULL = permanente |
Cómo funciona el long-polling
HostGator no soporta WebSockets en hosting compartido. Esta API los simula manteniendo la conexión HTTP abierta hasta 25 segundos:
El cliente debe reconectarse inmediatamente al recibir cada respuesta, usando el id más alto recibido como nuevo since_id.
Short-polling (alternativa ligera)
Para reducir carga del servidor con muchos jugadores, usa ?wait=0 y llama cada 2-3 segundos:
GET /api/get_messages.php?since_id=42&wait=0
LONG_POLL_TIMEOUT a 10 en config.php o migra a short-polling.Endpoints públicos Sin auth
Long-polling. Devuelve mensajes con id > since_id. Espera hasta timeout segundos si no hay mensajes nuevos.
| Parámetro | Tipo | Default | Descripción |
|---|---|---|---|
since_id | int | 0 | ID del último mensaje recibido |
wait | 0 / 1 | 1 | 0 = responder inmediatamente (short-polling) |
timeout | int | 25 | Segundos máximo de espera (máx. 25) |
// Respuesta { "ok": true, "since_id": 0, "messages": [ { "id": 1, "type": "CHAT", "sender": "Juan", "text": "¡Buena suerte!", "data": {}, "created_at": 1718200000.123 } ] }
Publica un mensaje de chat del jugador. Verifica si el jugador está baneado antes de insertar.
// Body JSON { "player_id": "maria_92", "text": "¡Hola a todos!" }
| Campo | Requerido | Descripción |
|---|---|---|
player_id | Requerido | ID o nickname del jugador |
text | Requerido | Texto del mensaje (máx. 300 caracteres) |
Endpoints del sistema SYSTEM_API_KEY
Todos usan POST a api/system_message.php con el campo action. Requieren el header X-API-Key: SYSTEM_API_KEY.
| action | Campos adicionales | Equivalente Godot |
|---|---|---|
system | text | add_system_message() |
bet | player_id, selection, cash | add_bet_message() |
tip | player_nickname, tip | add_tip_message() |
prize_total | total_prizes | add_prize_total() |
prize_top | player_name, prize | add_top_prize() |
error | text | add_error_message() |
Ejemplos
// Apuesta { "action": "bet", "player_id": "maria_92", "selection": "Rojo", "cash": "100.00" } // Premio destacado { "action": "prize_top", "player_name": "Carlos", "prize": "500.00" } // Total de premios de la ronda { "action": "prize_total", "total_prizes": "1234.50" } // Propina { "action": "tip", "player_nickname": "Pedro", "tip": "25.00" }
Endpoints de moderación ADMIN_API_KEY
| Método | Endpoint | Body | Descripción |
|---|---|---|---|
| POST | api/clear_chat.php | {} | Vacía todo el historial (TRUNCATE) |
| POST | api/delete_message.php | {"id": 42} | Elimina un mensaje por ID |
| POST | api/ban_player.php | {"player_id":"...","reason":"...","minutes":60} | Silencia un jugador (omite minutes para permanente) |
| POST | api/unban_player.php | {"player_id":"..."} | Quita el silencio |
Autenticación
La API key puede enviarse por cualquiera de estas tres vías (en orden de prioridad):
| Método | Ejemplo | Recomendación |
|---|---|---|
| Header HTTP | X-API-Key: tu_clave | Recomendado |
| Query string | ?api_key=tu_clave | Queda en logs del servidor |
| Campo JSON | "api_key": "tu_clave" | Aceptable para POST |
?api_key=) hace que aparezca en los logs de acceso del servidor web. Usa el header X-API-Key siempre que sea posible.Formato de respuestas
Todas las respuestas son JSON con la forma {"ok": true, ...} o {"ok": false, "error": "..."}.
// Éxito { "ok": true, "message": { ... } } // Error { "ok": false, "error": "Descripción del error." }
| HTTP Status | Significado |
|---|---|
200 | OK — operación exitosa |
400 | Bad Request — parámetros inválidos o faltantes |
401 | Unauthorized — API key incorrecta o ausente |
403 | Forbidden — jugador baneado |
404 | Not Found — mensaje o jugador no encontrado |
405 | Method Not Allowed — método HTTP incorrecto |
Chat público web
Acceso: https://tudominio.com/chat-api/web/
- El usuario elige un nickname (guardado en
localStorage) - Recibe mensajes en tiempo real via long-polling
- Los mensajes se auto-eliminan con fade-out al expirar (según
created_atdel servidor) - Indicador visual de estado de conexión (verde / amarillo / rojo)
- Múltiples pestañas del navegador = múltiples "jugadores" para testing
web/index.html en varias pestañas del navegador con diferentes nicknames. Cada uno se conecta de forma independiente.Panel de administración web
Acceso: https://tudominio.com/chat-api/web/admin.html
Contraseña: ADMIN_WEB_PASSWORD definida en config.php
| Pestaña | Funcionalidades |
|---|---|
| Live Feed | Mensajes en tiempo real con colores por tipo · Botón eliminar en cada mensaje · Formulario para enviar mensajes del sistema (apuesta, premio, propina, error…) · Formulario para banear jugadores desde el sidebar |
| Historial | Tabla paginada de todos los mensajes · Filtro por tipo y búsqueda de texto · Eliminar mensajes individuales · Contador total |
| Baneados | Lista de jugadores baneados con motivo y duración · Desbanear con un click · Formulario para banear nuevos jugadores |
web/proxy.php como intermediario: las API keys nunca llegan al navegador. El navegador solo recibe/envía la sesión PHP.Integración con Godot 4
El cliente está en godot_client_example.gd. Añade dos nodos HTTPRequest como hijos: HttpPoll y HttpSend.
# Configuración mínima const API_BASE := "https://tudominio.com/chat-api/api" const PLAYER_ID := "jugador_prueba" # reemplazar con el nickname real # 1. Iniciar el long-polling func _ready() -> void: _http_poll.request_completed.connect(_on_poll_completed) _http_send.request_completed.connect(_on_send_completed) _start_polling() # 2. Loop de long-polling func _poll_once() -> void: var url := "%s/get_messages.php?since_id=%d&wait=1" % [API_BASE, _last_id] _http_poll.request(url) func _on_poll_completed(..., body): var json := JSON.parse_string(body.get_string_from_utf8()) for msg in json.get("messages", []): if msg["id"] > _last_id: _last_id = int(msg["id"]) _apply_message(msg) _poll_once() # reconectar inmediatamente # 3. Enviar mensaje del jugador func send_chat_message(text: String) -> void: var body := JSON.stringify({ "player_id": PLAYER_ID, "text": text }) var headers := PackedStringArray(["Content-Type: application/json"]) _http_send.request(API_BASE + "/send_message.php", headers, HTTPClient.METHOD_POST, body)
Publicar eventos del juego (SYSTEM_API_KEY)
# Apuesta registrada var body := JSON.stringify({ "action": "bet", "player_id": player_id, "selection": "Rojo", "cash": "100.00" }) var headers := PackedStringArray([ "Content-Type: application/json", "X-API-Key: " + SYSTEM_API_KEY ]) http.request(API_BASE + "/system_message.php", headers, HTTPClient.METHOD_POST, body)
Tipos de mensaje y colores
| Tipo | Color | Remitente por defecto |
|---|---|---|
CHAT |
#FFFFFF | player_id |
SYSTEM |
#FFD966 | Sistema |
BET |
#D7FF77 | player_id |
PRIZE_TOTAL |
#FFED00 | Premios |
PRIZE_TOP |
#FFED00 | Premios |
TIP |
#00FF7F | player_nickname |
ERROR |
#EF4F85 | Error |
Pendientes de integración
-
require_player_token()enincludes/auth.php
Actualmente,send_message.phpes público y acepta cualquierplayer_idsin verificación de identidad. Conectar con el sistema real de tokens deRouletteApi(JWT, sesiones PHP, etc.) para validar que el jugador es quien dice ser. -
GameChatMessage::money_format()enincludes/GameChatMessage.php
Ajustar el símbolo de moneda y el formato numérico para que coincida exactamente conCurrencyManager.money_format()del proyecto Godot. -
Remitente "Tú" en el cliente Godot
El backend almacena elplayer_idreal. El cliente debe mostrar "Tú" cuandomsg.sender == PLAYER_ID(ya implementado engodot_client_example.gd). -
life_timecorrecto al recargar historial
Al cargar mensajes previos viasince_id=0, calcular el tiempo de vida restante como:
life_time = max(0.0, 6.0 - (Time.get_unix_time_from_system() - msg.created_at))
en lugar de reiniciar siempre a 6s (ya implementado en el cliente web y en el ejemplo GDScript).
Seguridad
| Medida | Implementación |
|---|---|
| Timing attacks | hash_equals() para comparar API keys (previene ataques de temporización) |
| SQL Injection | PDO con sentencias preparadas en todas las consultas |
| Acceso a configuración | .htaccess bloquea acceso directo a config.php e includes/ |
| Directory listing | Options -Indexes en .htaccess |
| BBCode injection | Escape de [ y ] en el cliente Godot y en el chat web |
| XSS (chat web) | escHtml() en todo el texto dinámico del chat y del admin |
| Panel admin | Sesión PHP con hash_equals(); las API keys nunca salen del servidor |
| Cookies de sesión | httponly: true, samesite: Strict en proxy.php |
Recomendaciones adicionales para producción
- Servir todo bajo HTTPS (certificado SSL gratuito via Let's Encrypt disponible en HostGator)
- Añadir rate limiting por IP en
send_message.phppara evitar spam - Implementar validación de token (
require_player_token()) antes de aceptar mensajes - Rotar las API keys periódicamente
- Mantener
config.phpfuera del repositorio git (.gitignore)