Skip to content

MTGo Echo Bot ​

This is a complete, runnable echo bot that replies to every private message it receives. Walk through each section to understand how MTGo bots work.

Full Code ​

go
package main

import (
    "context"
    "fmt"
    "log"
    "os"

    tg "github.com/mtgo-labs/mtgo/telegram"
    "github.com/mtgo-labs/mtgo/telegram/types"
)

func main() {
    apiID := mustAtoi(mustEnv("API_ID"))
    apiHash := mustEnv("API_HASH")
    botToken := mustEnv("BOT_TOKEN")

    client, err := tg.NewClient(apiID, apiHash, &tg.Config{
        BotToken:    botToken,
        SessionName: "echo_bot",
        SavePeers:   true,
    })
    if err != nil {
        log.Fatalf("new client: %v", err)
    }

    client.OnMessage(func(client *tg.Client, msg *types.Message) {
        if msg == nil || msg.Text == "" {
            return
        }
        _, err := msg.Reply(msg.Text)
        if err != nil {
            log.Printf("reply error: %v", err)
        }
    }, tg.Private)

    if err := client.Connect(0); err != nil {
        log.Fatalf("connect: %v", err)
    }
    defer client.Stop()

    bot, err := client.GetMe(context.Background())
    if err != nil {
        log.Fatalf("get me: %v", err)
    }
    fmt.Printf("Bot @%s is running\n", bot.Username)

    client.Idle()
}

func mustEnv(key string) string {
    v := os.Getenv(key)
    if v == "" {
        log.Fatalf("environment variable %s is required", key)
    }
    return v
}

func mustAtoi(s string) int {
    var n int
    if _, err := fmt.Sscanf(s, "%d", &n); err != nil {
        log.Fatalf("invalid integer %q: %v", s, err)
    }
    return n
}

Walkthrough ​

Environment Variables ​

go
apiID := mustAtoi(mustEnv("API_ID"))
apiHash := mustEnv("API_HASH")
botToken := mustEnv("BOT_TOKEN")

Three required environment variables:

  • API_ID — your application's API ID from my.telegram.org
  • API_HASH — your application's API hash
  • BOT_TOKEN — the bot token from @BotFather

mustEnv and mustAtoi are helper functions that exit with a clear error message if a variable is missing or invalid.

Creating the Client ​

go
client, err := tg.NewClient(apiID, apiHash, &tg.Config{
    BotToken:    botToken,
    SessionName: "echo_bot",
    SavePeers:   true,
})
  • BotToken — authenticates as a bot (no phone/SMS flow needed)
  • SessionName — names the session file (echo_bot.session)
  • SavePeers: true — persists resolved peers to disk so they survive restarts

At this point, no network connection has been made yet. The client is configured but idle.

Registering the Message Handler ​

go
client.OnMessage(func(client *tg.Client, msg *types.Message) {
    if msg == nil || msg.Text == "" {
        return
    }
    _, err := msg.Reply(msg.Text)
    if err != nil {
        log.Printf("reply error: %v", err)
    }
}, tg.Private)
  • OnMessage — registers a callback that fires for every incoming message
  • tg.Private — filter: only handle private (1-on-1) messages. Other options: tg.Group, tg.All
  • msg.Text — the text content of the message. Empty for media-only messages
  • msg.Reply(msg.Text) — sends a reply to the same chat with the same text (echo)

The nil check on msg is defensive—some updates may not include a full message object.

Connecting ​

go
if err := client.Connect(0); err != nil {
    log.Fatalf("connect: %v", err)
}
defer client.Stop()

Connect(0) performs the full connection sequence:

  1. Loads session from storage (or creates a new one)
  2. Establishes TCP connection to the nearest DC
  3. Performs key exchange if needed
  4. Authenticates using the bot token
  5. Starts receiving updates

The argument 0 means "use the default DC." client.Stop() in the defer ensures a clean shutdown.

Getting Bot Info ​

go
bot, err := client.GetMe(context.Background())
if err != nil {
    log.Fatalf("get me: %v", err)
}
fmt.Printf("Bot @%s is running\n", bot.Username)

GetMe calls the users.GetFullUser RPC to fetch the bot's own profile. This confirms authentication succeeded and gives you the bot's username for logging.

Keeping the Process Alive ​

go
client.Idle()

Idle() blocks the main goroutine indefinitely. It listens for OS signals (SIGINT, SIGTERM) and cleanly shuts down when received. Without this, the program would exit immediately after connecting.

Running the Bot ​

bash
# Set environment variables
export API_ID=12345
export API_HASH=your_api_hash
export BOT_TOKEN=your_bot_token

# Run
go run main.go

Output:

Bot @my_echo_bot is running

Now send any text message to your bot in a private chat, and it will reply with the same text.

Released under the Apache-2.0 License.