Skip to content

SQLite Storage Bot

This example demonstrates using SQLite as the storage backend for sessions, conversations, and peer caching. The bot persists notes, lists cached peers, and demonstrates conversation management.

Full Code

go
package main

import (
	"context"
	"fmt"
	"log"
	"os"
	"os/signal"
	"strings"
	"syscall"
	"time"

	"github.com/mtgo-labs/mtgo/telegram"
	"github.com/mtgo-labs/mtgo/telegram/types"
	extstorage "github.com/mtgo-labs/storage"
	"github.com/mtgo-labs/storage/sqlite"
)

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

	client, err := telegram.NewClient(mustAtoi(apiID), apiHash, &telegram.Config{
		BotToken:    botToken,
		SessionName: "storage_bot.session",
		SavePeers:   true,
		Storage:     sqlite.New(),
	})
	if err != nil {
		log.Fatalf("new client: %v", err)
	}

	// /start
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		ctx.Reply(
			"<b>Storage Bot</b>\n\n"+
				"Commands:\n"+
				"• /note &lt;text&gt; — save a note\n"+
				"• /notes — list your notes\n"+
				"• /peers — show cached peers\n"+
				"• /clear — delete your notes",
		)
	}, telegram.Command("start"))

	// /note <text> — save a note using conversation storage
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		text := msg.Text
		if len(text) <= 5 {
			ctx.Reply("Usage: /note <text>")
			return
		}
		body := text[6:]
		conv := &extstorage.Conversation{
			ChatID:    msg.ChatID,
			UserID:    msg.FromID,
			Name:      "note:" + body,
			Step:      0,
			UpdatedAt: time.Now().Unix(),
		}
		if err := client.SaveConversation(conv); err != nil {
			ctx.Reply(fmt.Sprintf("save error: %v", err))
			return
		}
		ctx.Reply("Note saved!")
	}, telegram.Command("note"))

	// /notes — retrieve saved notes
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		conv, err := client.LoadConversation(msg.ChatID, msg.FromID)
		if err != nil {
			ctx.Reply(fmt.Sprintf("error: %v", err))
			return
		}
		if conv == nil {
			ctx.Reply("No notes found. Use /note <text> to save one.")
			return
		}
		ctx.Reply(fmt.Sprintf("Your note: %s", conv.Name))
	}, telegram.Command("notes"))

	// /clear — delete saved notes
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		if err := client.DeleteConversation(msg.ChatID, msg.FromID); err != nil {
			ctx.Reply(fmt.Sprintf("error: %v", err))
			return
		}
		ctx.Reply("Notes cleared!")
	}, telegram.Command("clear"))

	// /peers — show cached peers from storage
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		peers, err := client.LoadPeers()
		if err != nil {
			ctx.Reply(fmt.Sprintf("error: %v", err))
			return
		}
		if len(peers) == 0 {
			ctx.Reply("No cached peers yet.")
			return
		}
		var text strings.Builder
		fmt.Fprintf(&text, "Cached peers (%d):\n", len(peers))
		for i, p := range peers {
			if i >= 20 {
				text.WriteString("...")
				break
			}
			name := p.FirstName
			if p.Username != "" {
				name += " @" + p.Username
			}
			fmt.Fprintf(&text, "• %s (id=%d, %s)\n", name, p.ID, p.Type)
		}
		ctx.Reply(text.String())
	}, telegram.Command("peers"))

	// Auto-save peers on every message
	client.OnMessage(func(ctx *telegram.Context, msg *types.Message) {
		if msg.FromID == 0 {
			return
		}
		peer := &extstorage.Peer{
			ID:          msg.FromID,
			Type:        extstorage.PeerTypeUser,
			LastUpdated: time.Now().Unix(),
		}
		client.SavePeer(peer)
	})

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

	shutdownCtx, stopNotify := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
	defer stopNotify()
	go func() {
		<-shutdownCtx.Done()
		client.Stop()
	}()

	bot, err := client.GetMe(context.Background())
	if err != nil {
		log.Fatalf("get me: %v", err)
	}

	fmt.Println("=== Storage Bot ===")
	fmt.Printf("  Bot: %s (@%s)\n", bot.FirstName, bot.Username)
	fmt.Println("  Commands: /start /note /notes /peers /clear")
	fmt.Println("─────────────────────")
	fmt.Println("storage bot running, press Ctrl+C to stop")

	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) int32 {
	var n int32
	if _, err := fmt.Sscanf(s, "%d", &n); err != nil {
		log.Fatalf("invalid integer %q: %v", s, err)
	}
	return n
}

How It Works

Configuring SQLite Storage

go
client, err := telegram.NewClient(apiID, apiHash, &telegram.Config{
    SessionName: "storage_bot.session",
    Storage:     sqlite.New(),
})

Setting Storage: sqlite.New() enables SQLite as the persistent backend. The session file name is used as the database file name. Without this, the client uses in-memory storage that is lost on restart.

Conversation Storage

Conversations are key-value records keyed by (chatID, userID):

go
// Save
conv := &extstorage.Conversation{
    ChatID:    msg.ChatID,
    UserID:    msg.FromID,
    Name:      "note:" + body,
    Step:      0,
    UpdatedAt: time.Now().Unix(),
}
client.SaveConversation(conv)

// Load
conv, err := client.LoadConversation(chatID, userID)

// Delete
client.DeleteConversation(chatID, userID)

Use Name and Step to track multi-step conversation state. The conversations plugin builds on this primitive.

Peer Caching

Peers are resolved users/channels stored for fast lookup:

go
// Save a peer
peer := &extstorage.Peer{
    ID:          msg.FromID,
    Type:        extstorage.PeerTypeUser,
    LastUpdated: time.Now().Unix(),
}
client.SavePeer(peer)

// Load all peers
peers, err := client.LoadPeers()

With SavePeers: true in the config, the client automatically persists resolved peers from incoming updates. You can also save peers manually for custom caching logic.

Graceful Shutdown

go
shutdownCtx, stopNotify := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stopNotify()
go func() {
    <-shutdownCtx.Done()
    client.Stop()
}()

Using signal.NotifyContext with a goroutine ensures the client stops cleanly on Ctrl+C or SIGTERM, flushing any pending writes to SQLite.

Storage Backends

MTGo supports multiple storage backends:

BackendPackageUse for
Memorybuilt-inDevelopment, testing
SQLitegithub.com/mtgo-labs/storage/sqliteSingle-server production
PostgreSQLgithub.com/mtgo-labs/storage/postgresDistributed production
MongoDBgithub.com/mtgo-labs/storage/mongodbDocument-oriented storage
Redisgithub.com/mtgo-labs/storage/redisHigh-performance caching

All backends implement the same Storage interface, so switching is a one-line change.

Running

bash
export API_ID=12345
export API_HASH=your_api_hash
export BOT_TOKEN=your_bot_token

go run main.go

Try /note Buy groceries, /notes, /peers, and /clear. Data persists across restarts in the SQLite database file.

Released under the Apache-2.0 License.