⚡ PROTOCOLO OFICIAL · PRO BOTS

Construye bots para
AnarkWorld

Documentación completa para crear bots externos (Pro Bots) con soporte de webhooks, API REST, base de datos persistente, moderación, Mini-Apps y tiempo real.

15+Endpoints API
5Tipos de Evento
7Ops de Storage
10Tipos de Nodo
01

¿Qué es un Bot Externo?

Una aplicación en tu propio servidor que se comunica con AnarkWorld de forma bidireccional.

📥

Webhooks (Entrada)

AnarkWorld envía eventos POST a tu URL cada vez que ocurre algo en los grupos donde tu bot está activo.

📤

Gateway API (Salida)

Tu bot llama a la API REST para enviar mensajes, moderar, guardar datos o lanzar Mini-Apps.

🗄️

Persistencia JSONB

Cada bot tiene su propia base de datos por grupo. Economía, rankings, inventarios — todo sin base de datos propia.

⚡

Socket Bridge

Señales de latencia cero para juegos y apps multiplayer en tiempo real usando los sockets de AnarkWorld.

02

Autenticación

Genera tu token en la Bot Factory (prefijo akw_) y envíalo en cada request.

⚠️ El token no expira automáticamente. Si lo regeneras en la Bot Factory, el anterior queda inválido de inmediato.
HTTP HEADER
Authorization: Bearer akw_tu_token_secreto
// Node.js / Axios
    const axios = require('axios');

    const api = axios.create({
    baseURL: 'https://davcenter.servequake.com/app/api/bot-gateway',
    headers: {
        'Authorization': `Bearer akw_tu_token_secreto`,
        'Content-Type': 'application/json',
        'User-Agent': 'MiBotAwesome/1.0'
    }
    });

    // Prueba de conexión
    api.get('/emojis').then(r => console.log('✅ Conectado', r.data));
# Python / requests
    import requests

    BASE_URL = "https://davcenter.servequake.com/app/api/bot-gateway"
    HEADERS = {
        "Authorization": "Bearer akw_tu_token_secreto",
        "Content-Type": "application/json",
        "User-Agent": "MiBotAwesome/1.0"
    }

    # Prueba de conexión
    r = requests.get(f"{BASE_URL}/emojis", headers=HEADERS)
    print("✅ Conectado", r.json())
03

Recepción de Eventos (Webhooks)

AnarkWorld hace POST a tu Webhook URL configurada en la Bot Factory. Debes responder con HTTP 200 en menos de 3 segundos.

💡 Usa cleanContent del evento new_message para parsear comandos. Este campo ya no tiene etiquetas de mención ni emojis.

Evento: new_message

Se dispara cuando un usuario envía texto, multimedia o un comando en el grupo.

WEBHOOK PAYLOAD — new_message
{
    "event":       "new_message",
    "groupId":     26,
    "groupName":   "Gamer Pro Hub",
    "membersCount":45,
    "groupOwner":  "CapitanZero",
    "messageId":   "temp-1678291002",
    "senderId":    13,
    "senderName":  "TestUser",
    "content":     "Hola bot, revisa a @Isac",
    "cleanContent":"Hola bot, revisa a",   // ← úsalo para parsear comandos
    "targetId":    "50",                     // ← usuario @mencionado
    "targetName":  "Isac",
    "senderRoles": [{ "id": 121, "name": "Moderador Pro" }],
    "botPermissions": [{
        "is_admin":         false,
        "can_send_messages":true,
        "can_send_photos":  true,
        "can_use_music":    true
    }]
    }

Eventos de Sistema

👋 user_joined
Un usuario entra al grupo. Ideal para mensajes de bienvenida y asignación de roles iniciales.
{
    "event":    "user_joined",
    "groupId": 26,
    "userId":  3,
    "username":"Isac ♨️"
    }
🚪 user_left
El usuario salió voluntariamente del grupo.
{
    "event":    "user_left",
    "groupId": 26,
    "userId":  3,
    "username":"Isac ♨️"
    }
🔨 user_kicked
Un admin expulsó a un usuario. Incluye el ID del administrador responsable.
{
    "event":     "user_kicked",
    "groupId":  26,
    "targetId": 102,
    "adminId":  13,
    "adminName":"Test"
    }
🔥 reaction_added
Alguien reaccionó a un mensaje. El bot puede responder o registrar la reacción.
{
    "event":    "reaction_added",
    "groupId": 26,
    "messageId":1450,
    "emoji":   "🔥",
    "userId":  13
    }
04

Mensajería

Base: https://davcenter.servequake.com/app/api/bot-gateway

Método Endpoint Descripción
POST /send-message Envía texto plano o con Rich Tags.
POST /send-media Envía imagen, video o audio (multipart/form-data).
POST /react Añade/quita una reacción (Toggle).
POST /typing Activa el estado "escribiendo..." para humanizar el bot.
// Enviar mensaje con typing humano
    async function enviarConTyping(groupId, texto, parentId = null) {
    // 1. Activar "escribiendo..."
    await api.post('/typing', { groupId, isTyping: true });
    await new Promise(r => setTimeout(r, 1500));

    // 2. Enviar mensaje
    await api.post('/send-message', {
        groupId,
        content: texto,
        ...(parentId && { parentMessageId: parentId })
    });
    }

    // Enviar imagen
    const FormData = require('form-data');
    const fs      = require('fs');

    const form = new FormData();
    form.append('file', fs.createReadStream('banner.jpg'));
    form.append('type', 'IMAGE');
    form.append('groupId', '26');

    await api.post('/send-media', form, {
    headers: form.getHeaders()
    });
import time

    # Enviar mensaje con typing humano
    def enviar_con_typing(group_id, texto, parent_id=None):
        # 1. Activar "escribiendo..."
        requests.post(f"{BASE_URL}/typing",
            json={"groupId": group_id, "isTyping": True},
            headers=HEADERS)
        time.sleep(1.5)

        # 2. Enviar mensaje
        payload = {"groupId": group_id, "content": texto}
        if parent_id:
            payload["parentMessageId"] = parent_id
        requests.post(f"{BASE_URL}/send-message",
            json=payload, headers=HEADERS)

    # Enviar imagen
    with open("banner.jpg", "rb") as f:
        requests.post(f"{BASE_URL}/send-media",
            files={"file": ("banner.jpg", f, "image/jpeg")},
            data={"type": "IMAGE", "groupId": "26"},
            headers={"Authorization": "Bearer akw_tu_token"})
05

Persistencia / Base de Datos

Almacenamiento JSONB ultra-rápido por bot y grupo. Sin necesidad de base de datos propia.

set — Sobrescribir add — Sumar (monedas, XP) sub — Restar (mín: 0) max — Guardar si es mayor (récords) min — Guardar si es menor (tiempos) push — Añadir a lista (inventario) now — Timestamp actual (cooldowns)
// --- GUARDAR / MODIFICAR ---
    await api.post('/storage/set', {
    groupId:   26,
    botId:     108,
    key:       'user_13_monedas',
    value:     '100',
    operation: 'add'   // suma 100 monedas
    });

    // Registrar cooldown (fecha actual)
    await api.post('/storage/set', {
    groupId: 26, botId: 108,
    key: 'cooldown_user_13',
    operation: 'now'   // guarda Date.now()
    });

    // --- LEER ---
    const { data } = await api.get(
    '/storage/get?groupId=26&botId=108&key=user_13_monedas'
    );
    console.log(data.value); // "600"

    // --- LEADERBOARD Top 10 ---
    const { data: ranking } = await api.get(
    '/storage/leaderboard?groupId=26&botId=108&key=user_13_monedas'
    );
    // Devuelve array con nombre y foto de perfil reales
# --- GUARDAR / MODIFICAR ---
    requests.post(f"{BASE_URL}/storage/set",
        json={
            "groupId": 26, "botId": 108,
            "key": "user_13_monedas",
            "value": "100",
            "operation": "add"
        }, headers=HEADERS)

    # Registrar cooldown
    requests.post(f"{BASE_URL}/storage/set",
        json={"groupId": 26, "botId": 108,
            "key": "cooldown_user_13", "operation": "now"},
        headers=HEADERS)

    # --- LEER ---
    r = requests.get(
        f"{BASE_URL}/storage/get",
        params={"groupId": 26, "botId": 108, "key": "user_13_monedas"},
        headers=HEADERS)
    print(r.json()["value"])  # "600"

    # --- LEADERBOARD ---
    ranking = requests.get(
        f"{BASE_URL}/storage/leaderboard",
        params={"groupId": 26, "botId": 108, "key": "user_13_monedas"},
        headers=HEADERS).json()
06

Moderación y Jerarquía

El bot debe tener rango de ADMIN en el grupo para usar estos endpoints.

Método Endpoint Descripción
POST /roles/manage Asigna o quita un rol. Refresca colores en tiempo real.
POST /kick Expulsa a un usuario del grupo.
POST /mute Silencia a un usuario por un tiempo (ej: "8h", "30m").
POST /clear-chat Borra los últimos N mensajes del grupo.
// Asignar rol
    await api.post('/roles/manage', {
    groupId: 26, targetId: 13, roleId: 121, action: 'add'
    });

    // Quitar rol
    await api.post('/roles/manage', {
    groupId: 26, targetId: 13, roleId: 121, action: 'remove'
    });

    // Silenciar 8 horas
    await api.post('/mute', { groupId: 26, targetId: 13, duration: '8h' });

    // Expulsar usuario
    await api.post('/kick', { groupId: 26, targetId: 13 });

    // Limpiar los últimos 20 mensajes
    await api.post('/clear-chat', { groupId: 26, amount: 20 });
# Asignar rol
    requests.post(f"{BASE_URL}/roles/manage",
        json={"groupId": 26, "targetId": 13,
            "roleId": 121, "action": "add"},
        headers=HEADERS)

    # Silenciar 8 horas
    requests.post(f"{BASE_URL}/mute",
        json={"groupId": 26, "targetId": 13, "duration": "8h"},
        headers=HEADERS)

    # Expulsar usuario
    requests.post(f"{BASE_URL}/kick",
        json={"groupId": 26, "targetId": 13},
        headers=HEADERS)

    # Limpiar mensajes
    requests.post(f"{BASE_URL}/clear-chat",
        json={"groupId": 26, "amount": 20},
        headers=HEADERS)
07

Utilidades

Recursos auxiliares: emojis, música y uploads temporales.

Método Endpoint Descripción
GET /emojis Lista de emojis personalizados del servidor (.webp / .json Lottie).
GET /search-music?q=nombre Búsqueda en iTunes. Devuelve metadatos listos para el reproductor.
POST /upload-media Sube un archivo y obtiene URL interna. Se purga cada 7 días.
08

Multiplayer Bridge (Real-Time)

Sincronización instantánea de baja latencia mediante Sockets. Envía datos entre jugadores y bots sin pasar por la base de datos.

📡

Señales de Red

Las señales viajan por la memoria RAM del servidor de sockets, lo que garantiza una respuesta casi instantánea (latencia <50ms) ideal para juegos de acción.

👥

Alcance de Sala

Toda señal enviada a un groupId será retransmitida automáticamente a todos los miembros que tengan la Mini-App abierta en ese momento.

Bot ➔ Jugadores (Gateway)

El bot puede inyectar eventos en los juegos activos (ej: "Aparece un jefe", "Bonus de tiempo").

POST /api/bot-gateway/game-signal
POST /api/bot-gateway/game-signal
                
                            {
                            "groupId": 26,
                            "payload": {
                                "type": "spawn_boss",
                                "x":    500,
                                "hp":   1000
                            }
                            }

Jugador ➔ Grupo (Iframe Bridge)

Usa estos eventos dentro de tu juego para sincronizar movimientos o turnos con otros amigos.

JavaScript — index.html del juego
// Emitir señal de juego
                    await api.post('/game-signal', {
                    groupId: 26,
                    payload: {
                        type: 'player_move',
                        x: 10,
                        y: 20,
                        playerId: 13
                    }
                    });
                    
                    // En el iframe del juego — recibir la señal
                    window.addEventListener('message', (e) => {
                    if (e.data.type === 'REMOTE_GAME_SIGNAL') {
                        const { x, y, playerId } = e.data.payload;
                        moverJugador(playerId, x, y);
                    }
                    });
# Emitir señal de juego
                    requests.post(f"{BASE_URL}/game-signal",
                        json={
                            "groupId": 26,
                            "payload": {
                                "type": "player_move",
                                "x": 10, "y": 20,
                                "playerId": 13
                            }
                        }, headers=HEADERS)
💡 Recomendación: Para evitar saturar el ancho de banda, no envíes más de 10 señales por segundo (intervalos de 100ms). Usa Interpolación (Lerp) en el cliente para suavizar los movimientos.
09

Mini-Apps y Juegos Web

Aloja aplicaciones HTML5 directamente en AnarkWorld y ábrelas como iframe en el chat.

📦

Alojamiento y Logo

Sube un .zip con tu index.html. Si incluyes un archivo icon.png (o jpg) en la raíz, se usará automáticamente como logo en la tarjeta del chat.

📡

Multiplayer Bridge

Usa el sistema de Signals para enviar datos en tiempo real a otros jugadores del grupo (posiciones, turnos, ataques) con latencia cero.

🔄

Sincronización (GET)

No solo puedes guardar, también puedes pedir datos (como el récord anterior) al iniciar el juego para sincronizar el estado del jugador.

🚪

Salida Limpia

Tu app puede ordenar su propio cierre mediante un comando de salida, devolviendo al usuario al chat de forma fluida.

JavaScript — Puente de Comunicación (Bridge)
const BOT_ID = new URLSearchParams(window.location.search).get('botId');

                // 1. PEDIR DATOS AL INICIAR (Ej: Cargar récord)
                window.parent.postMessage({ type: 'GET_BOT_DATA', key: 'puntos', botId: BOT_ID }, '*');
                
                // 2. ENVIAR SEÑAL MULTIPLAYER (A otros jugadores en línea)
    window.parent.postMessage({
                type: 'SEND_GAME_SIGNAL',
                payload: { type: 'move', x: 100, y: 250 }
    }, '*');
    }

                // 3. ESCUCHAR RESPUESTAS Y SEÑALES REMOTAS
                window.addEventListener('message', (event) => {
                if (event.data.type === 'BOT_DATA_RESPONSE') {
                    console.log("Récord recuperado:", event.data.value);
                }
                if (event.data.type === 'REMOTE_GAME_SIGNAL') {
                    console.log("El jugador", event.data.senderId, "se movió");
                }
                });
                
                // 4. CERRAR APP
                function salir() {
                window.parent.postMessage({ type: 'CLOSE_APP' }, '*');
                }
💡 Tip de Formato: El tag [WEBAPP:URL|Título|IconoURL] soporta un tercer parámetro opcional para forzar un icono externo, aunque el sistema ZIP lo detecta automáticamente.
10

Sistema de Rich Text Tags

El lenguaje de AnarkWorld para crear interfaces ricas dentro de mensajes de texto.

Tag Resultado Ejemplo
[M:ID:Nombre] Mención clicable → abre perfil [M:13:TestUser]
[R:ID:Nombre:#HEX] Menciona todos con ese rol (coloreado) [R:121:Mods:#d11f1f]
[E:archivo] Emoji animado Lottie o WebP [E:fuego.json]
[WEBAPP:URL|Título] Tarjeta premium de juego/app [WEBAPP:https://...|Jugar]
[MUSIC_V1]{...} Reproductor de música incrustado [MUSIC_V1]{"title":"Hit",...}
11

Ejemplo Completo — Bot de Bienvenida

Cuando un usuario entra, simula escritura, lo saluda con mención y le asigna el rol "Novato".

const express = require('express');
    const axios   = require('axios');

    const app = express();
    app.use(express.json());

    const API_BASE = 'https://davcenter.servequake.com/app/api/bot-gateway';
    const TOKEN    = 'akw_tu_token_generado';
    const ROLE_NOVATO = 10;

    const api = axios.create({
    baseURL: API_BASE,
    headers: { 'Authorization': `Bearer ${TOKEN}` }
    });

    app.post('/webhook', async (req, res) => {
    res.sendStatus(200); // responder inmediatamente (< 3s)

    const { event, groupId, userId, username } = req.body;

    if (event === 'user_joined') {
        // 1. Simular escritura
        await api.post('/typing', { groupId, isTyping: true });
        await new Promise(r => setTimeout(r, 1500));

        // 2. Bienvenida con mención
        await api.post('/send-message', {
        groupId,
        content: `¡Bienvenido [M:${userId}:${username}] a la comunidad! 🎉`
        });

        // 3. Asignar rol Novato
        await api.post('/roles/manage', {
        groupId, targetId: userId, roleId: ROLE_NOVATO, action: 'add'
        });

        // 4. Registrar en storage (contador de miembros)
        await api.post('/storage/set', {
        groupId, botId: 108,
        key: 'total_bienvenidas',
        value: '1',
        operation: 'add'
        });
    }

    if (event === 'new_message') {
        const { cleanContent, senderId, senderName, messageId } = req.body;

        if (cleanContent.startsWith('/ranking')) {
        const { data } = await api.get(
            `/storage/leaderboard?groupId=${groupId}&botId=108&key=user_${senderId}_xp`
        );
        const top = data.map((u, i) =>
            `${i + 1}. [M:${u.userId}:${u.username}] — ${u.value} XP`
        ).join('\n');
        await api.post('/send-message', {
            groupId,
            content: `🏆 Top Jugadores:\n${top}`,
            parentMessageId: messageId
        });
        }
    }
    });

    app.listen(3000, () => console.log('🤖 Bot activo en :3000'));
from flask import Flask, request, jsonify
    import requests
    import threading, time

    app = Flask(__name__)
    BASE_URL    = "https://davcenter.servequake.com/app/api/bot-gateway"
    HEADERS     = {"Authorization": "Bearer akw_tu_token"}
    ROLE_NOVATO = 10

    def procesar_evento(data):
        event    = data.get("event")
        group_id = data.get("groupId")

        if event == "user_joined":
            user_id  = data["userId"]
            username = data["username"]

            # 1. Typing
            requests.post(f"{BASE_URL}/typing",
                json={"groupId": group_id, "isTyping": True},
                headers=HEADERS)
            time.sleep(1.5)

            # 2. Bienvenida
            requests.post(f"{BASE_URL}/send-message",
                json={
                    "groupId": group_id,
                    "content": f"¡Bienvenido [M:{user_id}:{username}] 🎉!"
                }, headers=HEADERS)

            # 3. Rol Novato
            requests.post(f"{BASE_URL}/roles/manage",
                json={"groupId": group_id, "targetId": user_id,
                    "roleId": ROLE_NOVATO, "action": "add"},
                headers=HEADERS)

        elif event == "new_message":
            cmd      = data.get("cleanContent", "")
            msg_id   = data.get("messageId")
            sender   = data.get("senderId")

            if cmd.startswith("/ranking"):
                r = requests.get(f"{BASE_URL}/storage/leaderboard",
                    params={"groupId": group_id, "botId": 108,
                            "key": f"user_{sender}_xp"},
                    headers=HEADERS).json()
                top = "\n".join(
                    f"{i+1}. [M:{u['userId']}:{u['username']}] — {u['value']} XP"
                    for i, u in enumerate(r))
                requests.post(f"{BASE_URL}/send-message",
                    json={"groupId": group_id,
                        "content": f"🏆 Top:\n{top}",
                        "parentMessageId": msg_id},
                    headers=HEADERS)

    @app.route("/webhook", methods=["POST"])
    def webhook():
        # Responder 200 inmediatamente, procesar en hilo
        threading.Thread(target=procesar_evento,
                        args=(request.json(),)).start()
        return "", 200

    if __name__ == "__main__":
        app.run(port=3000)
        print("🤖 Bot activo en :3000")
12

Template "The God Mode" — Referencia Total

El código definitivo para Mini-Apps. Acceso a identidad, lista de miembros, ranking, persistencia y multiplayer.

<!DOCTYPE html>
                            <html>
                            <body>
                            <script>
                                const urlParams = new URLSearchParams(window.location.search);
                                const BOT_ID = urlParams.get('botId');
                            
                                // 1. PEDIR TODO AL CARGAR
                                window.onload = () => {
                                ask({ type: 'GET_MY_INFO' });      // ¿Quién soy?
                                ask({ type: 'GET_GROUP_CONTEXT' }); // ¿Quiénes están en el grupo?
                                ask({ type: 'GET_LEADERBOARD', key: 'xp', botId: BOT_ID }); // ¿Cómo va el ranking?
                                };
                            
                                // 2. RECIBIR DATOS DE LA APP
                                window.addEventListener('message', (event) => {
                                const d = event.data;
                            
                                if (d.type === 'MY_INFO_RESPONSE') {
                                    console.log("Mi ID:", d.id, "Nombre:", d.name, "Foto:", d.avatar);
                                }
                            
                                if (d.type === 'GROUP_CONTEXT_RESPONSE') {
                                    console.log("Grupo:", d.groupName, "Miembros:", d.members);
                                    // d.members es un array: [{id, name, avatar, isOnline}, ...]
                                }
                            
                                if (d.type === 'LEADERBOARD_DATA') {
                                    console.log("Top 10:", d.data);
                                }
                            
                                if (d.type === 'REMOTE_GAME_SIGNAL') {
                                    console.log("El jugador", d.senderId, "envió señal:", d.payload);
                                }
                                });
                            
                                // 3. FUNCIONES DE ACCIÓN
                                function ask(o) { window.parent.postMessage(o, '*'); }
                            
                                function ganarXP() {
                                ask({ type: 'SET_BOT_DATA', key: 'xp', value: 10, operation: 'add', botId: BOT_ID });
                                }
                            
                                function emitirMovimiento(x, y) {
                                ask({ type: 'SEND_GAME_SIGNAL', payload: { type: 'move', x, y } });
                                }
                            
                                function cerrar() { ask({ type: 'CLOSE_APP' }); }
                            </script>
                            </body>
                            </html>
import requests
                            from flask import Flask, request
                            
                            API_URL = "https://davcenter.servequake.com/app/api/bot-gateway"
                            TOKEN   = "akw_..."
                            
                            def on_message(data):
                                text = data.get("cleanContent", "").lower()
                                gid  = data["groupId"]
                            
                                if text == "/play":
                                    url = "https://tuservidor.com/mini-apps/mi-juego/index.html"
                                    # Enviamos el comando para que todos puedan unirse
                                    requests.post(f"{API_URL}/send-message",
                                        json={
                                            "groupId": gid,
                                            "content": "¡[M:{data['senderId']}:{data['senderName']}] inició una partida!\n[WEBAPP:{url}|Unirse al Juego]"
                                        }, headers={"Authorization": f"Bearer {TOKEN}"})
                            
                            # Tu bot externo recibirá los datos guardados por la Mini-App 
                            # automáticamente en su base de datos de grupo.
13

Límites y Cuotas

Para garantizar la estabilidad del servidor para todos.

120/min
Mensajes
15 MB
Máx por archivo
50 MB
Máx ZIP Mini-App
1 MB
Storage JSONB
7 días
Retención /chat_temp
∞
Mini-Apps (permanentes)
⚠️ Si recibes un HTTP 429, reduce la velocidad de envío. El sistema aplica protección anti-spam automática.
14

Códigos de Respuesta HTTP

Referencia rápida de todos los posibles códigos del Gateway.

200 / 201
OK
Operación exitosa.
401
Unauthorized
Token inválido. Verifica tu akw_token.
403
Forbidden
Bot sin permisos de Admin en el grupo.
404
Not Found
groupId o messageId inexistente.
429
Too Many Requests
Reduce la cadencia de mensajes.
500
Server Error
Contacta al administrador.
N1

Bots por Nodos — Logic Lab

El sistema visual de programación sin código integrado directamente en la app AnarkWorld. Crea flujos lógicos encadenando bloques de acciones que se ejecutan cuando se dispara un comando.

🧠
¡Configuración sin código!
Los Bots por Nodos se gestionan desde Bot Factory → Editor de Lógica. No requieren servidores externos: toda la ejecución corre directamente en AnarkWorld.
🧩

Sin servidor externo

A diferencia de los Pro Bots (Webhook), los bots de nodos corren dentro del propio servidor de AnarkWorld. No necesitas hosting, VPS ni código.

🔗

Cadena de Acciones

Cada Nodo-Comando tiene una lista de acciones que se ejecutan en orden, de arriba a abajo, con soporte para esperas, condiciones y bucles de cooldown.

⚡

Variables dinámicas

El motor sustituye variables como {user}, {id} o {mis_puntos} en tiempo real antes de enviar cualquier mensaje.

🗄️

DB integrada

Accede al mismo almacenamiento JSONB de los Pro Bots. Las claves escritas por un bot de nodos son legibles por un Pro Bot y viceversa.

Arquitectura de ejecución
💬
Mensaje del usuario
Texto enviado en el grupo
🔍
Motor de Triggers
Busca si el texto contiene algún trigger registrado
⛓️
Cadena de Acciones
Ejecuta la lista de acciones del nodo en orden
📤
Respuesta al grupo
El bot envía el mensaje final con variables resueltas
N2

Estructura de un Nodo

Internamente, cada nodo es un objeto JSON dentro del array bot_nodes guardado en la columna de la base de datos del bot.

Así se ve un nodo en el Logic Lab:

⌨️ COMANDO — Trigger
/ranking
triggers: ["/ranking", "top"]
+
📖 DB_GET — Leer DB
Leer puntos
key: user_{id}_xp → {mis_xp}
+
💬 SEND — Responder
Mostrar ranking
"{user} tiene {mis_xp} XP"

Y así es su representación JSON interna:

JSON — bot_nodes[]
{
    "type": "command",
    "triggers": ["/ranking", "top"],
    "actions": [

        // Paso 1: Leer XP del usuario de la DB
        {
        "type":     "db_get",
        "key":      "user_{id}_xp",
        "var_name": "mis_xp"
        },

        // Paso 2: Esperar medio segundo
        {
        "type":  "wait",
        "value": "0.5"
        },

        // Paso 3: Enviar respuesta con variable resuelta
        {
        "type":  "send",
        "value": "{user} tienes {mis_xp} XP acumulados 🏆"
        }
    ]
    }
💡 El campo triggers es un array de strings. El motor busca si el texto limpio (cleanContent) contiene alguno de ellos, sin distinción de mayúsculas/minúsculas. No necesita ser una coincidencia exacta.
N3

Catálogo Completo de Acciones

Cada objeto dentro del array actions tiene un type que define qué hace. Todas las acciones soportan variables {var} en sus campos de texto.

Comunicación
💬send
Envía un mensaje de texto al grupo. Soporta Rich Tags ([M:ID:Nombre], [E:emoji], [WEBAPP:url|titulo]).
value Texto o Rich Tag
✉️send_dm
Envía un mensaje privado (DM) a un usuario específico. Usa {id} para el que escribió el comando o {target_id} para el @mencionado.
target_id value Ej: {id} o 50
💖react
Añade una reacción emoji al mensaje que disparó el comando. Acepta emojis Unicode o una URL de emoji personalizado.
value Ej: 🔥 o https://...
🌐open_webapp
Envía una tarjeta de Mini-App al chat. Genera el tag [WEBAPP:url|titulo] automáticamente. Soporta campo iconUrl opcional.
url title iconUrl (opt)
🕹️start_game
Variante de open_webapp que genera un mensaje de anuncio de partida antes de mostrar la tarjeta. Perfecto para juegos grupales.
game_name url iconUrl (opt)
🧹delete_trigger
Borra el mensaje original que disparó el comando del chat. Útil para comandos de moderación o comandos que no deberían verse.
Sin campos requeridos
Base de Datos
🔍db_get
Lee un valor de la DB del bot en este grupo y lo guarda en una variable local para usar en acciones siguientes. Si la clave no existe, devuelve "0".
key var_name Clave puede usar {id}
💾db_set
Escribe o modifica un valor en la DB. Soporta todas las operaciones del storage: set, add, sub, max, min, push, now.
key value operation
Lógica y Control
⏳wait
Pausa la cadena N segundos antes de ejecutar la siguiente acción. Ideal para simular que el bot "escribe" o para anuncios temporizados.
value En segundos. Ej: "1.5"
🎲random
Elige aleatoriamente una de las opciones separadas por comas en value y la pasa a la siguiente acción de tipo send.
value Ej: "Hola,Buenas,Wagwan"
🔀if_branch
Evalúa una condición y bifurca el flujo. Si la condición es verdadera ejecuta true_val, si es falsa ejecuta false_val. El operador puede ser == (igual exacto) o contains (contiene).
var op target true_val false_val
⏱️cooldown_check
Comprueba si el usuario puede usar el comando (anti-spam, recompensa diaria). Si aún está en cooldown, envía fail_msg y detiene la cadena con break. Si el tiempo pasó, actualiza el timestamp y continúa.
key seconds fail_msg Variable {rem} disponible
🛡️filter_role
Comprueba que el usuario que escribió el comando tenga al menos uno de los roles especificados. Si no los tiene, detiene la cadena silenciosamente.
value IDs de roles separados por comas
Moderación
👑manage_role
Asigna o quita un rol a un usuario del grupo. El bot debe tener permisos de administrador. Emite un evento permissions_updated por socket para refrescar colores en tiempo real.
role_id action "add" o "remove"
Multimedia
🖼️sticker
Envía un sticker al grupo. Puede ser una URL pública de imagen WebP/GIF o una ruta interna de la galería de stickers de AnarkWorld.
value URL del sticker
🏞️imagen
Envía una imagen al chat. Acepta URL pública o ruta interna. Se renderiza como burbuja de foto en la interfaz de la app.
value URL de imagen (JPG/PNG/WEBP)
🎬video
Envía un video al chat. Se renderiza con reproductor incrustado. Acepta URL pública de archivo MP4 o WEBM.
value URL del video (MP4/WEBM)
🎵audio
Envía un mensaje de audio (nota de voz o música). Se renderiza con el reproductor de audio de la app. Soporta WAV, MP3 y OGG.
value URL del audio duration (opt)
🎲azar_multimedia
Elige aleatoriamente una imagen, video o sticker de una lista de URLs separadas por comas y lo envía al grupo. Ideal para respuestas visuales variadas.
value URLs separadas por comas type: IMAGE/VIDEO/STICKER
Interacción Avanzada
📝responder_texto
Envía texto citando visualmente el mensaje que disparó el comando (reply). Equivale a un send pero con parentMessageId automático para anclar la respuesta al mensaje original.
value Texto de respuesta citada
🎙️pedir_entrada
Envía un mensaje al grupo pidiendo al usuario que escriba algo. El siguiente mensaje de ese usuario en el mismo grupo se captura y se almacena en la variable {entrada} para usarla en acciones posteriores.
value Texto de la pregunta timeout (opt, segundos)
🎰probabilidad
Ejecuta la acción siguiente con una probabilidad definida (0–100%). Si el número aleatorio generado supera el umbral, la cadena continúa; si no, se detiene o ejecuta el fail_msg.
value Porcentaje: "30" = 30% fail_msg (opt)
💡 Tip: Las acciones se ejecutan en orden estricto. Si una acción de tipo cooldown_check o filter_role falla, usa break internamente y detiene el resto de la cadena. Ninguna acción posterior se ejecuta.
N4

Listeners — Nodos de Evento

Los Listeners son nodos especiales que se disparan automáticamente ante eventos del sistema, sin que ningún usuario escriba un comando. Su type es "listener" y en lugar de triggers usan un campo event.

🎧 A diferencia de los nodos de comando, los listeners no requieren que nadie escriba nada. Son perfectos para mensajes de bienvenida, despedidas, registros automáticos o respuestas a reacciones.
💬 al_recibir_mensaje
Se dispara con cualquier mensaje enviado al grupo, independientemente de su contenido. Útil para registrar actividad, dar XP automático por escribir o filtrar palabras.
{
    "type":  "listener",
    "event": "al_recibir_mensaje",
    "actions": [...]
    }
🚀 al_unirse_usuario
Se dispara cuando un usuario entra al grupo. Las variables {user} e {id} contienen los datos del usuario recién unido. Ideal para bienvenidas automáticas y asignación de roles iniciales.
{
    "type":  "listener",
    "event": "al_unirse_usuario",
    "actions": [...]
    }
👋 al_salir_usuario
Se dispara cuando un usuario sale del grupo voluntariamente. Puedes enviar una despedida, limpiar sus datos de DB o notificar al staff. Las variables {user} e {id} del saliente están disponibles.
{
    "type":  "listener",
    "event": "al_salir_usuario",
    "actions": [...]
    }
🔨 al_ser_expulsado
Se dispara cuando un administrador expulsa a un usuario. Además de {target_user} y {target_id} (el expulsado), también incluye {user} e {id} del admin que realizó la acción.
{
    "type":  "listener",
    "event": "al_ser_expulsado",
    "actions": [...]
    }
❤️ al_reaccionar_mensaje
Se dispara cuando cualquier usuario reacciona a un mensaje del grupo. Las variables {user} e {id} son del que reaccionó. El emoji usado está disponible como {emoji}.
{
    "type":  "listener",
    "event": "al_reaccionar_mensaje",
    "actions": [...]
    }
Comando vs Listener — Diferencia estructural

Nodo tipo command

COMMAND
{
    "type": "command",
    "triggers": [
        "/hola", "saludar"
    ],
    "actions": [
        { "type": "send",
        "value": "¡Hola {user}!" }
    ]
    }

Se activa cuando el texto contiene un trigger

Nodo tipo listener

LISTENER
{
    "type":  "listener",
    "event": "al_unirse_usuario",

    "actions": [
        { "type": "send",
        "value": "Bienvenido {user}! 🎉" }
    ]
    }

Se activa automáticamente ante un evento del sistema

🚀 Bienvenida automática con rol + XP de inicio Listener
JSON — Listener al_unirse_usuario
{
    "type":  "listener",
    "event": "al_unirse_usuario",
    "actions": [

        // 1. Esperar 1 segundo (efecto humano)
        { "type": "wait", "value": "1" },

        // 2. Dar XP de bienvenida (50 XP)
        {
        "type":      "db_set",
        "key":       "user_{id}_xp",
        "value":     "50",
        "operation": "add"
        },

        // 3. Asignar rol "Novato" (ID: 10)
        { "type": "manage_role", "role_id": 10, "action": "add" },

        // 4. Enviar mensaje de bienvenida con mención
        {
        "type":  "send",
        "value": "👋 ¡Bienvenido [M:{id}:{user}] a {group_name}!\nTienes 50 XP de regalo para empezar 🎁"
        }
    ]
    }
💀 Despedida + borrar datos al salir Listener
JSON — Listener al_salir_usuario
{
    "type":  "listener",
    "event": "al_salir_usuario",
    "actions": [

        // Leer su XP antes de que se vaya (para el mensaje de despedida)
        { "type": "db_get", "key": "user_{id}_xp", "var_name": "xp" },

        // Despedida con datos
        {
        "type":  "send",
        "value": "😢 {user} nos dejó con {xp} XP acumulados. ¡Hasta pronto!"
        },

        // Resetear su XP a 0 (borra el progreso)
        {
        "type":      "db_set",
        "key":       "user_{id}_xp",
        "value":     "0",
        "operation": "set"
        }
    ]
    }
🔨 Notificar expulsión al canal de staff Listener
JSON — Listener al_ser_expulsado
{
    "type":  "listener",
    "event": "al_ser_expulsado",
    "actions": [

        // Informar en el grupo quién expulsó y a quién
        {
        "type":  "send",
        "value": "🔨 [M:{target_id}:{target_user}] fue expulsado por [M:{id}:{user}]."
        },

        // Enviar DM privado al expulsado explicando la situación
        {
        "type":      "send_dm",
        "target_id": "{target_id}",
        "value":     "Has sido expulsado de {group_name}. Contacta con un admin si crees que fue un error."
        }
    ]
    }
💡 Importante: Puedes tener múltiples listeners del mismo evento en el mismo bot. Todos se ejecutarán en orden. Esto te permite separar responsabilidades: un listener de al_unirse_usuario para la bienvenida y otro para el XP inicial.
N5

Variables del Sistema

El motor sustituye estas variables en cualquier campo value, key, target_id o fail_msg antes de ejecutar la acción. Escríbelas con llaves: {nombre_var}.

Usuario que disparó el comando
VariableDescripciónEjemplo
{user}Mención @Nombre del emisor@CapitanZero
{id}ID numérico del emisor13
{user_pic}URL de la foto de perfil del emisor/uploads/profile_images/...
{user_rank}Nombre del rol principal del emisor en el grupoModerador Pro
{msg}Texto limpio del mensaje (sin tags ni emojis)hola bot
Usuario @mencionado (target)
VariableDescripciónEjemplo
{target_id}ID del usuario mencionado con @. Si no hay mención, es el mismo {id}.50
{target_user}@Nombre del mencionado@Isac
{target_pic}URL de la foto del mencionado/uploads/profile_images/...
Grupo y Bot
VariableDescripciónEjemplo
{group_name}Nombre del grupoGamer Pro Hub
{members_count}Número de miembros activos45
{owner}@Nombre del creador del grupo@CapitanZero
{bot_name}Nombre del propio botArkBot
{bot_id}ID del propio bot108
{time}Hora actual del servidor (HH:MM:SS)14:30:22
Mensaje citado (reply)
VariableDescripciónEjemplo
{reply_text}Primeros 50 caracteres del mensaje citadoHola a todos...
{reply_user}@Nombre del autor del mensaje citado@TestUser
{reply_id}ID del autor del mensaje citado13
Variables de cooldown
VariableDescripciónContexto
{rem}Segundos restantes de cooldown. Solo disponible dentro del fail_msg de la acción cooldown_check.Solo en fail_msg
⚠️ Las variables leídas con db_get se añaden al contexto local con el nombre definido en var_name. Por ejemplo, si defines "var_name": "mis_monedas", podrás usar {mis_monedas} en cualquier acción posterior de esa misma cadena.
N6

Cooldowns y Persistencia

El sistema de cooldowns usa la misma DB JSONB del storage para guardar timestamps. La clave interna tiene el formato user_{id}_cd_{key}.

Flujo de cooldown_check:

⌨️ COMANDO
/diario
triggers: ["/diario", "recompensa"]
+
⏱️ COOLDOWN_CHECK
Anti-spam diario
key: "diario" · seconds: 86400
fail_msg: "Vuelve en {rem}s ⏳"
+
💾 DB_SET
Dar monedas
key: user_{id}_coins · op: add · val: 100
+
💬 SEND
Mensaje de éxito
"{user} recibió 100 monedas 🪙"

El JSON completo de este nodo:

JSON — Nodo con Cooldown
{
    "type": "command",
    "triggers": ["/diario", "recompensa"],
    "actions": [

        // 1. Verificar cooldown de 24 horas
        {
        "type":     "cooldown_check",
        "key":      "diario",
        "seconds":  86400,
        "fail_msg": "⏳ Vuelve en {rem}s para tu próxima recompensa."
        },

        // 2. Si pasó el cooldown: sumar monedas
        {
        "type":      "db_set",
        "key":       "user_{id}_coins",
        "value":     "100",
        "operation": "add"
        },

        // 3. Leer el total actualizado
        {
        "type":     "db_get",
        "key":      "user_{id}_coins",
        "var_name": "total_coins"
        },

        // 4. Confirmar al usuario
        {
        "type":  "send",
        "value": "{user} recibió 100 monedas 🪙\nTotal: {total_coins} 🏦"
        }
    ]
    }
✅ La clave interna que genera el sistema es user_13_cd_diario (usando el ID real del usuario). Esto garantiza que cada usuario tenga su propio cooldown independiente, sin que un usuario afecte al otro.
N7

Flujos de Ejemplo

Nodos listos para importar o usar como referencia. Todos siguen la estructura bot_nodes[].

⭐ Sistema de XP — Ganar y consultar experiencia Economy
JSON — 2 Nodos
[
    // NODO 1: Ganar XP (con cooldown de 5 minutos)
    {
        "type": "command",
        "triggers": ["/xp", "ganar xp"],
        "actions": [
        { "type": "cooldown_check", "key": "xp", "seconds": 300, "fail_msg": "⏳ Espera {rem}s para ganar más XP." },
        { "type": "db_set", "key": "user_{id}_xp", "value": "10", "operation": "add" },
        { "type": "db_get", "key": "user_{id}_xp", "var_name": "xp" },
        { "type": "react", "value": "⭐" },
        { "type": "send", "value": "+10 XP para {user} ⭐ — Total: {xp} XP" }
        ]
    },

    // NODO 2: Consultar mi XP
    {
        "type": "command",
        "triggers": ["/mispo", "mis puntos"],
        "actions": [
        { "type": "db_get", "key": "user_{id}_xp", "var_name": "xp" },
        { "type": "send", "value": "📊 {user} tienes {xp} XP acumulados." }
        ]
    }
    ]
👑 Comando protegido por rol + gestión de roles Moderación
JSON — Nodo con filter_role + manage_role
{
    "type": "command",
    "triggers": [".verificar"],
    "actions": [

        // Solo roles 121 (Mod) o 8 (Admin) pueden ejecutar este comando
        { "type": "filter_role", "value": "121, 8" },

        // Borrar el comando para mantener el chat limpio
        { "type": "delete_trigger" },

        // Asignar el rol "Verificado" (ID: 55) al @mencionado
        { "type": "manage_role", "role_id": 55, "action": "add" },

        // Confirmar en el chat
        { "type": "send", "value": "✅ {target_user} ha sido verificado por {user}." }
    ]
    }
🎮 Lanzar juego + guardar récord desde Mini-App Gaming
JSON — Nodo start_game + PostMessage bridge
// BOT NODE: Lanzar el juego
    {
    "type": "command",
    "triggers": ["/jugar", "play"],
    "actions": [
        { "type": "react", "value": "🎮" },
        { "type": "wait", "value": "1" },
        {
        "type":      "start_game",
        "game_name": "Flappy Anark",
        "url":       "https://davcenter.servequake.com/mini-apps/108/app_xyz/index.html",
        "iconUrl":   "https://davcenter.servequake.com/mini-apps/108/app_xyz/icon.png"
        }
    ]
    }

    // MINI-APP (index.html): Guardar récord cuando el jugador pierde
    const BOT_ID = new URLSearchParams(window.location.search).get('botId');

    function onGameOver(puntos) {
    window.parent.postMessage({
        type:      'SET_BOT_DATA',
        key:       'puntos',
        value:     puntos,
        operation: 'max',  // Solo guarda si supera el récord previo
        botId:     BOT_ID
    }, '*');
    }
🎲 Respuesta condicional + aleatoria Fun
JSON — if_branch + random
{
    "type": "command",
    "triggers": ["/8ball", "pregunta"],
    "actions": [

        // Verificar que el mensaje contiene algo (más de solo el comando)
        {
        "type":      "if_branch",
        "var":       "{msg}",
        "op":        "==",
        "target":   "",
        "true_val":  "❌ Escribe tu pregunta después del comando.",
        "false_val": "🎱 Analizando el universo..."
        },

        // Respuesta mágica aleatoria
        {
        "type":  "random",
        "value": "Sí definitivamente 🟢,No lo creo 🔴,Pregunta de nuevo 🔵,Mis fuentes dicen no 🟡,Sin duda 🟢"
        },

        // Usar el resultado del random como mensaje final
        { "type": "send", "value": "{user} preguntó: {msg}\n🎱 La bola dice: {value}" }
    ]
    }
✅ Pro tip: Puedes combinar filter_role + cooldown_check + db_get + if_branch en una sola cadena para crear sistemas de economía, niveles o juegos completos sin escribir una sola línea de código backend.