Skip to content

Command Bot Example

A bot that responds to slash commands like /help, /time, /echo, and more.

Overview

This example demonstrates:

  • Command parsing
  • Command routing
  • Help system
  • State management
  • Error messages

Code

csharp
public static void Main(string[] args)
{
    // Configuration
    const string apiUrl = "http://localhost:8080";
    const string botNumber = "+1234567890"; // Replace with your Signal number

    // Create client
    var client = new SignalBotClient(x => x.WithBaseUrl(apiUrl).WithNumber(botNumber));

    // Cancellation token for graceful shutdown
    using var cts = new CancellationTokenSource();

    // Handle Ctrl+C
    Console.CancelKeyPress += (sender, e) =>
    {
        Console.WriteLine("\nStopping bot...");
        e.Cancel = true;
        cts.Cancel();
    };

    Console.WriteLine("Echo Bot is starting...");
    Console.WriteLine($"Bot number: {botNumber}");
    Console.WriteLine("Press Ctrl+C to stop\n");

    // Start receiving messages
    client.StartReceiving(
        updateHandler: HandleMessage,
        errorHandler: HandleError,
        cancellationToken:
        cts.Token
    );

    Console.WriteLine("Bot stopped");
}

public static async Task HandleMessage(ISignalBotClient client, ReceivedMessageEnvelope message, CancellationToken token)
{
    var envelope = message.Envelope!;
    // Get the text message
    var text = envelope.DataMessage?.Message;

    // Ignore empty messages
    if (string.IsNullOrWhiteSpace(text))
    {
        return;
    }

    // Get sender
    var sender = message.Envelope?.SourceNumber ?? string.Empty;

    // Log received message
    Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] From {sender}: {text}");

    // Echo back with a prefix
    var echoMessage = $"You said: {text}";

    try
    {
        // Send the echo
        await client.SendMessageAsync(builder => builder.WithMessage(echoMessage).WithRecipient(sender),
            cancellationToken: token);

        Console.WriteLine($"[{DateTime.Now:HH:mm:ss}] Echoed back to {sender}");
    }
    catch (Exception ex)
    {
        Console.WriteLine($"Error sending echo: {ex.Message}");
    }
}

public static async Task HandleError(ISignalBotClient client, Error err, CancellationToken cancellationToken)
{
    Console.WriteLine($"[ERROR] {err.Exception?.GetType().Name}: {err.Exception?.Message}");

    // Wait a bit before continuing to avoid tight error loops
    await Task.Delay(1000, cancellationToken);
}

Usage Examples

User: /help
Bot: Shows list of available commands

User: /time
Bot: 🕐 Current time: 14:25:30

User: /echo Hello World
Bot: Hello World

User: /ping
Bot: 🏓 Pong!

User: /unknown
Bot: ❓ Unknown command: /unknown. Send /help to see available commands.

Advanced Features

1. Admin-Only Commands

csharp
private static readonly HashSet<string> _adminNumbers = new()
{
    "+9999999999"
};

static async Task HandleMessage(
    SignalBotClient client,
    SignalMessage message,
    CancellationToken cancellationToken)
{
    var text = message.DataMessage?.Message;
    if (string.IsNullOrWhiteSpace(text) || !text.StartsWith("/")) return;

    var sender = message.Source;
    var command = text.Split(' ')[0].ToLower();

    // Check admin commands
    if (command.StartsWith("/admin"))
    {
        if (!_adminNumbers.Contains(sender))
        {
            await SendMessage(sender, "🔒 Access denied: Admin only", cancellationToken);
            return;
        }
        
        await HandleAdminCommand(sender, text, cancellationToken);
        return;
    }

    // ... regular command handling
}

static async Task HandleAdminCommand(string sender, string text, CancellationToken ct)
{
    var parts = text.Split(' ', 2);
    var command = parts[0].ToLower();

    switch (command)
    {
        case "/adminstats":
            await SendAdminStats(sender, ct);
            break;
        case "/adminbroadcast":
            if (parts.Length > 1)
                await BroadcastMessage(parts[1], ct);
            break;
    }
}

2. Command Cooldowns

csharp
private static readonly Dictionary<string, Dictionary<string, DateTime>> _commandCooldowns = new();

static bool IsOnCooldown(string sender, string command, int cooldownSeconds)
{
    if (!_commandCooldowns.ContainsKey(command))
        _commandCooldowns[command] = new Dictionary<string, DateTime>();

    if (_commandCooldowns[command].TryGetValue(sender, out var lastUse))
    {
        var remaining = (lastUse.AddSeconds(cooldownSeconds) - DateTime.Now).TotalSeconds;
        if (remaining > 0)
        {
            return true;
        }
    }

    _commandCooldowns[command][sender] = DateTime.Now;
    return false;
}

// Usage
static async Task HandleExpensiveCommand(string sender, CancellationToken ct)
{
    if (IsOnCooldown(sender, "/expensive", 60))
    {
        await SendMessage(sender, "⏳ Please wait before using this command again", ct);
        return;
    }

    // Process command
    await SendMessage(sender, "✅ Command executed", ct);
}