Skip to main content

Getting started

A bot needs an MTProto app identity (AppID / AppHash from my.telegram.org) and a bot token from @BotFather. The app identity is required even for bots — it identifies the application, not the bot, and is not the token.

Your first bot

bot, err := botapi.New(token, botapi.Options{AppID: appID, AppHash: appHash})
if err != nil {
return err
}

bot.OnCommand("start", "Start the bot", func(c *botapi.Context) error {
_, err := c.Reply("Hello!")
return err
})

// Run connects, authorizes as a bot and serves updates until ctx is canceled.
return bot.Run(ctx)

New does no network I/O — it just builds an unconnected bot. Register your handlers, then call Run. Run connects, authorizes the bot with its token, publishes the commands you registered via OnCommand, and then serves updates until the context is canceled.

Cancel cleanly

Wire Run to an interrupt signal so Ctrl-C shuts the bot down:

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

if err := bot.Run(ctx); err != nil {
log.Fatal(err)
}

A complete echo bot

This is the examples/echo bot — it greets on /start and echoes any other text back:

package main

import (
"context"
"os"
"os/signal"
"strconv"
"time"

"go.uber.org/zap"

"github.com/gotd/botapi"
)

func main() {
log, _ := zap.NewDevelopment()
defer func() { _ = log.Sync() }()

appID, err := strconv.Atoi(os.Getenv("APP_ID"))
if err != nil {
log.Fatal("APP_ID must be a number (see https://my.telegram.org)", zap.Error(err))
}

bot, err := botapi.New(os.Getenv("BOT_TOKEN"), botapi.Options{
AppID: appID,
AppHash: os.Getenv("APP_HASH"),
Logger: log,
})
if err != nil {
log.Fatal("Create bot", zap.Error(err))
}

// Middleware applies to every handler.
bot.Use(botapi.Recover(), botapi.Timeout(30*time.Second))

bot.OnCommand("start", "Show the welcome message", func(c *botapi.Context) error {
_, err := c.Reply("Hi! Send me any text and I'll echo it back.")
return err
})

// Any text message that is not a command.
bot.OnMessage(func(c *botapi.Context) error {
_, err := c.Reply(c.Message().Text)
return err
}, botapi.HasText(), botapi.Not(botapi.HasPrefix("/")))

ctx, cancel := signal.NotifyContext(context.Background(), os.Interrupt)
defer cancel()

log.Info("Starting echo bot")
if err := bot.Run(ctx); err != nil {
log.Fatal("Run", zap.Error(err))
}
}

Run it with the three environment variables:

APP_ID=12345 APP_HASH=abcdef BOT_TOKEN=123:abc go run ./examples/echo

Options

botapi.Options configures the bot. Only AppID and AppHash are required:

FieldPurpose
AppID, AppHashMTProto app identity (required).
LoggerA *zap.Logger. Defaults to a no-op logger.
DeviceDevice info reported at session init.
StoragePersists session, peers and update state. In-memory if nil — see Persistence.
OnStartCalled once, after the bot is authorized and gap recovery is live.
FloodWaitTransparently retry FLOOD_WAIT-limited requests — see Errors & resilience.
RequestsPerSecondProactive global rate limit (token bucket).
DisableCommandRegistrationStop Run from publishing OnCommand handlers via SetMyCommands.

Leaving Storage nil keeps the session, peers and update state in memory: nothing survives a restart. That is fine for development; for production, give it a storage.BBoltStorage.

Continue with Sending messages.