Saltar a contenido

💡 Ejemplos Avanzados

Ejemplos completos y patrones recomendados para casos de uso avanzados.

0. Bot Reactivo con EventBus

Usa el Event Bus para código más limpio y reactivo:

from todus import ToDusClient2

client = ToDusClient2(phone_number="5312345678", password="token")

# Handler crítico: bloquear spam
@client.events.on("message", priority=100, contains_keyword="spam")
def block_spam(event):
    print(f"🚫 Bloqueado spam de {event['from']}")
    return True  # ← detiene propagación

# Handler: responder a amigos
@client.events.on("message", from_phone="5387654321", priority=50)
def handle_friend(event):
    print(f"👋 Amigo: {event['body']}")
    # responder automáticamente
    client.send_message("5387654321", "¡Hola!")

# Handler: procesar comandos
@client.events.on("message", regex=r"^!(\w+)", priority=40)
def handle_command(event):
    import re
    match = re.match(r"^!(\w+)", event['body'])
    if match:
        cmd = match.group(1)
        print(f"Comando: {cmd}")

# Handler: logging centralizado
@client.events.on("*", priority=1)  # wildcard
def log_everything(event):
    event_type = event.get("_event_type")
    sender = event.get("from", "?")
    print(f"[{event_type}] {sender}")

# Iniciar escucha
client.login()
print("🤖 Bot reactivo activo...")
client.listen_messages(callback=lambda e: None)

Ventajas: - ✅ Código limpio con decoradores - ✅ Prioridades automáticas - ✅ Filtros declarativos - ✅ Manejo centralizado de eventos


1. Bot Interactivo con Comandos

from todus import ToDusClientWithQueue
from todus.errors import ConnectionLostError, TokenExpiredError
import re
import time

class CommandBot:
    """Bot que responde a comandos."""

    def __init__(self, phone, password):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()
        self.commands = {
            "help": self.cmd_help,
            "ping": self.cmd_ping,
            "status": self.cmd_status,
            "echo": self.cmd_echo,
        }

    def cmd_help(self, args):
        """Mostrar ayuda."""
        return "Comandos: help, ping, status, echo <text>"

    def cmd_ping(self, args):
        """Responder ping."""
        return "🏓 Pong!"

    def cmd_status(self, args):
        """Mostrar estado de la cola."""
        stats = self.client.get_queue_stats()
        return (
            f"📊 Estado de cola:\n"
            f"Pendientes: {stats['pending']}\n"
            f"Entregados: {stats['delivered']}\n"
            f"Fallidos: {stats['failed']}"
        )

    def cmd_echo(self, args):
        """Repetir mensaje."""
        if not args:
            return "Uso: echo <mensaje>"
        return " ".join(args)

    def process_command(self, msg):
        """Procesar comando del mensaje."""
        body = msg.get('body', '').strip()
        if not body.startswith('/'):
            return None

        # Parsear comando
        parts = body[1:].split()
        if not parts:
            return None

        cmd_name = parts[0].lower()
        cmd_args = parts[1:]

        if cmd_name not in self.commands:
            return f"❌ Comando desconocido: {cmd_name}"

        try:
            return self.commands[cmd_name](cmd_args)
        except Exception as e:
            return f"⚠️ Error: {str(e)}"

    def on_message(self, msg):
        """Procesar mensaje entrante."""
        if msg.get('is_group'):
            return  # Ignorar grupos

        sender = msg.get('from', '').split('@')[0]
        body = msg.get('body', '')

        # Procesar comando
        response = self.process_command(msg)
        if response:
            print(f"📤 Respondiendo a {sender}")
            self.client.send_message_queued(sender, response)

    def run(self):
        """Iniciar el bot."""
        print("🤖 Bot iniciado. Esperando mensajes...")
        max_retries = 5
        retry_count = 0

        while retry_count < max_retries:
            try:
                self.client.listen_messages(self.client.token, self.on_message)
            except (ConnectionLostError, TokenExpiredError) as e:
                retry_count += 1
                wait = min(2 ** retry_count, 60)
                print(f"⚠️ {type(e).__name__}. Reintentando en {wait}s...")
                time.sleep(wait)
                self.client.login()
            except KeyboardInterrupt:
                print("\n👋 Bot detenido")
                break
            except Exception as e:
                print(f"❌ Error inesperado: {e}")
                retry_count += 1
                time.sleep(5)

# Uso
if __name__ == "__main__":
    bot = CommandBot("5312345678", "password")
    bot.run()

2. Distribuidor de Mensajes (Newsletter)

from todus import ToDusClientWithQueue
import json
from typing import List

class NewsletterBot:
    """Bot para enviar newsletters a múltiples usuarios."""

    def __init__(self, phone, password, contacts_file="contacts.json"):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()
        self.contacts = self.load_contacts(contacts_file)

    def load_contacts(self, file_path: str) -> List[str]:
        """Cargar lista de contactos."""
        try:
            with open(file_path, 'r') as f:
                return json.load(f)
        except FileNotFoundError:
            print(f"Archivo {file_path} no encontrado")
            return []

    def save_contacts(self, file_path: str):
        """Guardar lista de contactos."""
        with open(file_path, 'w') as f:
            json.dump(self.contacts, f, indent=2)

    def add_contact(self, phone: str):
        """Agregar contacto."""
        from todus.util import normalize_phone
        phone = normalize_phone(phone)
        if phone not in self.contacts:
            self.contacts.append(phone)
            self.save_contacts("contacts.json")

    def send_newsletter(self, message: str, title: str = "📰 Newsletter"):
        """Enviar newsletter a todos."""
        print(f"📤 Enviando {title} a {len(self.contacts)} contactos...")

        successful = 0
        failed = 0

        full_message = f"{title}\n\n{message}"

        for phone in self.contacts:
            try:
                msg_id = self.client.send_message_queued(phone, full_message)
                successful += 1
                print(f"✅ {phone}: {msg_id}")
            except Exception as e:
                failed += 1
                print(f"❌ {phone}: {e}")

        print(f"\n📊 Resultados: {successful} exitosos, {failed} fallidos")
        return {"successful": successful, "failed": failed}

    def send_with_delay(self, message: str, delay_seconds: int = 5):
        """Enviar con retraso entre mensajes."""
        import time
        print(f"📤 Enviando con retraso de {delay_seconds}s entre mensajes...")

        for i, phone in enumerate(self.contacts, 1):
            try:
                self.client.send_message_queued(phone, message)
                print(f"{i}/{len(self.contacts)}{phone}")
                if i < len(self.contacts):
                    time.sleep(delay_seconds)
            except Exception as e:
                print(f"{i}/{len(self.contacts)}{phone}: {e}")

# Uso
if __name__ == "__main__":
    bot = NewsletterBot("5312345678", "password")

    # Agregar contactos
    bot.add_contact("5351234567")
    bot.add_contact("5387654321")

    # Enviar newsletter simple
    bot.send_newsletter(
        message="Aquí va el contenido del newsletter",
        title="🔔 Noticia Importante"
    )

    # Enviar con retraso para evitar rate limiting
    # bot.send_with_delay("Mensaje importante", delay_seconds=3)

3. Monitoreo de Conversaciones

from todus import ToDusClientWithQueue
from datetime import datetime
import json

class ConversationMonitor:
    """Registra y analiza conversaciones."""

    def __init__(self, phone, password):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()
        self.conversations = {}

    def log_message(self, msg):
        """Registrar mensaje en log."""
        sender = msg.get('from', 'unknown').split('@')[0]
        body = msg.get('body', '')
        msg_id = msg.get('id', 'unknown')
        timestamp = datetime.now().isoformat()

        if sender not in self.conversations:
            self.conversations[sender] = []

        self.conversations[sender].append({
            "timestamp": timestamp,
            "id": msg_id,
            "body": body,
            "type": msg.get('type', 'normal'),
            "is_group": msg.get('is_group', False),
        })

        print(f"📝 {sender}: {body[:50]}...")

    def get_stats(self, phone: str = None):
        """Obtener estadísticas."""
        if phone:
            msgs = self.conversations.get(phone, [])
            return {
                "contact": phone,
                "message_count": len(msgs),
                "first_message": msgs[0]['timestamp'] if msgs else None,
                "last_message": msgs[-1]['timestamp'] if msgs else None,
            }
        else:
            return {
                "total_contacts": len(self.conversations),
                "total_messages": sum(len(m) for m in self.conversations.values()),
                "contacts": list(self.conversations.keys()),
            }

    def export_conversations(self, file_path: str):
        """Exportar conversaciones a JSON."""
        with open(file_path, 'w') as f:
            json.dump(self.conversations, f, indent=2, ensure_ascii=False)
        print(f"✅ Conversaciones exportadas a {file_path}")

    def on_message(self, msg):
        """Procesar mensaje."""
        self.log_message(msg)

    def run(self):
        """Iniciar monitoreo."""
        print("👁️ Iniciando monitoreo de conversaciones...")
        self.client.listen_messages(self.client.token, self.on_message)

# Uso
if __name__ == "__main__":
    monitor = ConversationMonitor("5312345678", "password")

    # Mostrar estadísticas antes de iniciar
    print(monitor.get_stats())

    # Iniciar monitoreo
    # monitor.run()

    # Exportar resultados
    # monitor.export_conversations("conversations.json")

4. Sistema de Persistencia con Base de Datos

from todus import ToDusClientWithQueue
import sqlite3
from datetime import datetime

class DatabasedBot:
    """Bot con persistencia en SQLite."""

    def __init__(self, phone, password, db_path="bot.db"):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()
        self.db_path = db_path
        self.init_database()

    def init_database(self):
        """Crear tablas si no existen."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()

        cursor.execute('''
            CREATE TABLE IF NOT EXISTS messages (
                id INTEGER PRIMARY KEY,
                from_phone TEXT,
                to_phone TEXT,
                body TEXT,
                timestamp TEXT,
                msg_id TEXT UNIQUE,
                direction TEXT
            )
        ''')

        cursor.execute('''
            CREATE TABLE IF NOT EXISTS users (
                phone TEXT PRIMARY KEY,
                first_seen TEXT,
                last_seen TEXT,
                message_count INTEGER DEFAULT 0
            )
        ''')

        conn.commit()
        conn.close()

    def save_message(self, msg, direction="received"):
        """Guardar mensaje en base de datos."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()

        from_phone = msg.get('from', '').split('@')[0]
        to_phone = msg.get('to', '').split('@')[0]
        body = msg.get('body', '')
        msg_id = msg.get('id')

        if not msg_id:
            return

        try:
            cursor.execute('''
                INSERT INTO messages 
                (from_phone, to_phone, body, timestamp, msg_id, direction)
                VALUES (?, ?, ?, ?, ?, ?)
            ''', (from_phone, to_phone, body, datetime.now().isoformat(), msg_id, direction))

            # Actualizar tabla de usuarios
            cursor.execute('''
                INSERT INTO users (phone, first_seen, last_seen, message_count)
                VALUES (?, ?, ?, 1)
                ON CONFLICT(phone) DO UPDATE SET
                    last_seen = ?,
                    message_count = message_count + 1
            ''', (from_phone, datetime.now().isoformat(), datetime.now().isoformat()))

            conn.commit()
        except sqlite3.IntegrityError:
            pass  # Mensaje duplicado
        finally:
            conn.close()

    def get_user_history(self, phone: str, limit: int = 10):
        """Obtener histórico de usuario."""
        conn = sqlite3.connect(self.db_path)
        cursor = conn.cursor()

        cursor.execute('''
            SELECT * FROM messages 
            WHERE from_phone = ? OR to_phone = ?
            ORDER BY timestamp DESC
            LIMIT ?
        ''', (phone, phone, limit))

        results = cursor.fetchall()
        conn.close()
        return results

    def on_message(self, msg):
        """Procesar y guardar mensaje."""
        self.save_message(msg)

# Uso
if __name__ == "__main__":
    bot = DatabasedBot("5312345678", "password")

    # Ver histórico
    # history = bot.get_user_history("5351234567", limit=5)
    # for record in history:
    #     print(record)

    # Iniciar escucha
    # bot.client.listen_messages(bot.client.token, bot.on_message)

5. Sincronización con Webhook

from todus import ToDusClientWithQueue
import requests
import json

class WebhookSync:
    """Envía eventos a un webhook remoto."""

    def __init__(self, phone, password, webhook_url: str):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()
        self.webhook_url = webhook_url

    def send_to_webhook(self, event_type: str, data: dict):
        """Enviar evento a webhook."""
        payload = {
            "event": event_type,
            "timestamp": datetime.now().isoformat(),
            "data": data,
        }

        try:
            response = requests.post(
                self.webhook_url,
                json=payload,
                timeout=5
            )
            print(f"✅ Webhook: {response.status_code}")
        except Exception as e:
            print(f"❌ Webhook error: {e}")

    def on_message(self, msg):
        """Sincronizar mensaje con webhook."""
        self.send_to_webhook("message", {
            "from": msg.get('from'),
            "body": msg.get('body'),
            "timestamp": datetime.now().isoformat(),
        })

# Uso con servidor Flask
from flask import Flask, request

app = Flask(__name__)
bot = WebhookSync(
    "5312345678",
    "password",
    webhook_url="https://mi-servidor.com/webhook"
)

@app.route('/webhook', methods=['POST'])
def webhook():
    """Recibir eventos del bot."""
    data = request.json
    print(f"Evento recibido: {data['event']}")
    return {'status': 'ok'}, 200

# Iniciar bot
# threading.Thread(
#     target=bot.client.listen_messages,
#     args=(bot.client.token, bot.on_message),
#     daemon=True
# ).start()

# app.run(port=5000)

6. Análisis de Sentimiento

from todus import ToDusClientWithQueue
from textblob import TextBlob  # pip install textblob

class SentimentBot:
    """Analiza sentimiento de mensajes."""

    def __init__(self, phone, password):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()

    def analyze_sentiment(self, text: str) -> dict:
        """Analizar sentimiento del texto."""
        blob = TextBlob(text)
        polarity = blob.sentiment.polarity  # -1 a 1
        subjectivity = blob.sentiment.subjectivity  # 0 a 1

        if polarity > 0.1:
            sentiment = "😊 Positivo"
        elif polarity < -0.1:
            sentiment = "😢 Negativo"
        else:
            sentiment = "😐 Neutral"

        return {
            "sentiment": sentiment,
            "polarity": polarity,
            "subjectivity": subjectivity,
        }

    def on_message(self, msg):
        """Analizar y responder."""
        body = msg.get('body', '')
        sender = msg.get('from', '').split('@')[0]

        result = self.analyze_sentiment(body)

        # Responder según sentimiento
        response = f"Detecté un mensaje {result['sentiment'].lower()}"

        print(f"📊 {sender}: {result}")
        self.client.send_message_queued(sender, response)

# Uso
if __name__ == "__main__":
    bot = SentimentBot("5312345678", "password")
    # bot.client.listen_messages(bot.client.token, bot.on_message)

7. Rate Limiting y Throttling

from todus import ToDusClientWithQueue
import time
from collections import deque

class ThrottledBot:
    """Bot con control de rate limiting."""

    def __init__(self, phone, password, max_messages_per_minute=30):
        self.client = ToDusClientWithQueue(phone, password)
        self.client.login()
        self.max_per_minute = max_messages_per_minute
        self.message_times = deque(maxlen=max_messages_per_minute)

    def wait_if_needed(self):
        """Esperar si se alcanzó el límite de tasa."""
        if len(self.message_times) < self.max_per_minute:
            return

        oldest = self.message_times[0]
        elapsed = time.time() - oldest
        wait_time = 60 - elapsed

        if wait_time > 0:
            print(f"⏳ Esperando {wait_time:.1f}s...")
            time.sleep(wait_time)

        self.message_times.clear()

    def send_throttled(self, phone: str, message: str):
        """Enviar con throttling."""
        self.wait_if_needed()
        self.message_times.append(time.time())
        return self.client.send_message_queued(phone, message)

# Uso
if __name__ == "__main__":
    bot = ThrottledBot("5312345678", "password", max_messages_per_minute=10)

    # Enviar muchos mensajes sin ser bloqueado
    for i in range(20):
        bot.send_throttled("5387654321", f"Mensaje {i+1}")

Más ejemplos disponibles en el repositorio: toDus-API/examples