MCPcopy Index your code
hub / github.com/enetx/tg

github.com/enetx/tg @v1.0.2

Chat with this repo
repository ↗ · DeepWiki ↗ · release v1.0.2 ↗ · + Follow
5,132 symbols 37,690 edges 646 files 2,688 documented · 52%
What it actually does AI analysis from the code graph — generated when you open this
loading…
README

image

TG - Telegram Bot Framework for Go

Go Reference Go Report Card Coverage Status Go Ask DeepWiki Mentioned in Awesome Go [![Telegram Bot API Version][TelegramVersionBadge]][TelegramLastVersion]

Modern and elegant wrapper around gotgbot with convenient API and functional programming style support.

Video Demo

TG Framework Demo

Features

  • 🚀 Simple and intuitive API with method chaining support
  • 🎯 Type-safe event handlers for all Telegram update types
  • 🔧 Flexible bot configuration through Builder pattern
  • 📝 Rich functionality for messages, media, keyboards and payments
  • 🎮 Built-in FSM support (finite state machines) for complex dialogues
  • 💰 Telegram Payments and Stars support with refunds
  • 🎲 Full support for all Telegram content types and features
  • 🔧 Middleware system for request filtering and processing
  • 📂 Advanced file handling with metadata and thumbnails
  • 🌐 Webhook support with security features

Quick Start

Installation

go mod init your-bot
go get github.com/enetx/tg

Simple Echo Bot

package main

import (
    "github.com/enetx/g"
    "github.com/enetx/tg/bot"
    "github.com/enetx/tg/ctx"
)

func main() {
    token := "token"
    b := bot.New(token).Build().Unwrap()

    // Handle text messages
    b.On.Message.Text(func(ctx *ctx.Context) error {
        return ctx.Reply("Echo: " + g.String(ctx.EffectiveMessage.Text)).Send().Err()
    })

    // Handle /start command
    b.Command("start", func(ctx *ctx.Context) error {
        return ctx.Reply("Welcome to the bot!").Send().Err()
    })

    b.Polling().Start()
}

Message Handlers

Handle different types of Telegram messages:

// Text messages
b.On.Message.Text(func(ctx *ctx.Context) error {
    return ctx.Reply("Text received").Send().Err()
})

// Media messages
b.On.Message.Photo(func(ctx *ctx.Context) error {
    return ctx.Reply("Photo received").Send().Err()
})

b.On.Message.Voice(func(ctx *ctx.Context) error {
    return ctx.Reply("Voice received").Send().Err()
})

b.On.Message.Video(func(ctx *ctx.Context) error {
    return ctx.Reply("Video received").Send().Err()
})

b.On.Message.Document(func(ctx *ctx.Context) error {
    return ctx.Reply("Document received").Send().Err()
})

// Contact and location
b.On.Message.Contact(func(ctx *ctx.Context) error {
    contact := ctx.EffectiveMessage.Contact
    return ctx.Reply("Thanks for sharing your contact!").Send().Err()
})

b.On.Message.Location(func(ctx *ctx.Context) error {
    location := ctx.EffectiveMessage.Location
    return ctx.Reply("Location received").Send().Err()
})

Commands

Register commands with advanced options:

// Basic command
b.Command("start", func(ctx *ctx.Context) error {
    return ctx.SendMessage("Start command triggered!").Send().Err()
})

// Command with custom triggers and options
b.Command("help", func(ctx *ctx.Context) error {
    return ctx.Reply("Help message").Send().Err()
}).
    Triggers('!', '.').  // Allow !help and .help
    AllowEdited().       // Handle edited messages
    AllowChannel().      // Work in channels
    Register()

// Commands work automatically, but you can customize them further

Inline Keyboards

Create interactive inline keyboards:

// Basic inline keyboard
b.Command("menu", func(ctx *ctx.Context) error {
    markup := keyboard.Inline().
        Row().
        Text("Option 1", "opt1").
        Text("Option 2", "opt2").
        Row().
        URL("Visit Site", "https://example.com").
        WebApp("Open App", "https://webapp.com")

    return ctx.Reply("Choose an option:").Markup(markup).Send().Err()
})

// Handle button presses
b.On.Callback.Equal("opt1", func(ctx *ctx.Context) error {
    return ctx.AnswerCallbackQuery("You chose option 1!").Send().Err()
})

b.On.Callback.Prefix("opt", func(ctx *ctx.Context) error {
    data := ctx.Update.CallbackQuery.Data
    return ctx.AnswerCallbackQuery("You clicked: " + g.String(data)).Alert().Send().Err()
})

Dynamic Keyboard Editing

b.On.Callback.Equal("edit", func(ctx *ctx.Context) error {
    // Edit existing keyboard
    markup := keyboard.Inline(ctx.EffectiveMessage.ReplyMarkup).
        Edit(func(btn *keyboard.Button) {
            switch btn.Get.Callback() {
            case "opt1":
                btn.Text("Modified Option 1")
            case "remove":
                btn.Delete()
            }
        })

    return ctx.EditMessageReplyMarkup(markup).Send().Err()
})

Reply Keyboards

Create custom reply keyboards:

b.Command("keyboard", func(ctx *ctx.Context) error {
    markup := keyboard.Reply().
        Row().
        Text("Regular Button").
        Contact("📞 Share Phone").
        Row().
        Location("📍 Send Location").
        WebApp("🌐 Web App", "https://webapp.com").
        Row().
        Poll("📊 Create Poll")

    return ctx.Reply("Use the keyboard below:").Markup(markup).Send().Err()
})

File Handling

Send various types of media files:

// Photo
b.Command("photo", func(ctx *ctx.Context) error {
    return ctx.SendPhoto("photo.png").
        Caption("Beautiful photo").
        Send().Err()
})

// Document with advanced options
b.Command("doc", func(ctx *ctx.Context) error {
    return ctx.SendDocument("document.pdf").
        Caption("Important document").
        Reply(reply.New(ctx.EffectiveMessage.MessageId)).
        Send().Err()
})

// Video with metadata
b.Command("video", func(ctx *ctx.Context) error {
    return ctx.SendVideo("video.mp4").
        Caption("Cool video").
        Spoiler().
        Timeout(3 * time.Minute). // Custom timeout
        ApplyMetadata().          // Extract video info (ffprobe)
        GenerateThumbnail().      // Auto-generate thumbnail (ffmpeg)
        Send().Err()
})

// Audio with metadata
b.Command("audio", func(ctx *ctx.Context) error {
    return ctx.SendAudio("song.mp3").
        Title("Song Title").
        Performer("Artist Name").
        Duration(180 * time.Second).
        Send().Err()
})

Finite State Machine (FSM)

Create complex multi-step conversations:

import (
    "github.com/enetx/fsm"
    "github.com/enetx/g"
    "github.com/enetx/tg/bot"
    "github.com/enetx/tg/ctx"
)

// Define states
const (
    StateGetEmail = "get_email"
    StateGetName  = "get_name"
    StateSummary  = "summary"
)

// Store FSM instances per user
var fsmStore = g.NewMapSafe[int64, *fsm.SyncFSM]()

func main() {
    b := bot.New(token).Build().Unwrap()

    // Create FSM template
    template := fsm.New(StateGetEmail).
        Transition(StateGetEmail, "next", StateGetName).
        Transition(StateGetName, "next", StateSummary)

    // Define state handlers
    template.OnEnter(StateGetEmail, func(fctx *fsm.Context) error {
        tgctx := fctx.Meta.Get("tgctx").Some().(*ctx.Context)
        return tgctx.Reply("Enter your email:").Send().Err()
    })

    template.OnEnter(StateGetName, func(fctx *fsm.Context) error {
        email := fctx.Input.(string)
        fctx.Data.Set("email", email)

        tgctx := fctx.Meta.Get("tgctx").Some().(*ctx.Context)
        return tgctx.Reply("Enter your name:").Send().Err()
    })

    template.OnEnter(StateSummary, func(fctx *fsm.Context) error {
        name := fctx.Input.(string)
        email := fctx.Data.Get("email").UnwrapOr("<no email>")

        tgctx := fctx.Meta.Get("tgctx").Some().(*ctx.Context)
        defer fsmStore.Delete(tgctx.EffectiveUser.Id)

        return tgctx.Reply(g.Format("Got name: {} and email: {}", name, email)).Send().Err()
    })

    // Start FSM
    b.Command("register", func(ctx *ctx.Context) error {
        fsm := fsmStore.Entry(ctx.EffectiveUser.Id).OrInsertWith(template.Clone().Sync)

        fsm.SetState(StateGetEmail)
        fsm.Context().Meta.Set("tgctx", ctx)
        return fsm.CallEnter(StateGetEmail)
    })

    // Handle FSM input
    b.On.Message.Text(func(ctx *ctx.Context) error {
        opt := fsmStore.Get(ctx.EffectiveUser.Id)
        if opt.IsNone() {
            return nil // No active FSM
        }

        fsm := opt.Some()
        fsm.Context().Meta.Set("tgctx", ctx)
        return fsm.Trigger("next", ctx.EffectiveMessage.Text)
    })

    b.Polling().Start()
}

Payments with Telegram Stars

Handle payments using Telegram's Stars system:

// Create invoice
b.Command("buy", func(ctx *ctx.Context) error {
    if ctx.EffectiveChat.Type != "private" {
        return nil
    }

    return ctx.SendInvoice("Premium Access", "Get premium features", "premium_123", "XTR").
        Price("Premium Plan", 100).  // 100 stars
        Protect().                   // Content protection
        Send().Err()
})

// Handle pre-checkout (validation)
b.On.PreCheckout.Any(func(ctx *ctx.Context) error {
    // Validate payment here if needed
    return ctx.AnswerPreCheckoutQuery().Ok().Send().Err()
})

// Handle successful payment
b.On.Message.SuccessfulPayment(func(ctx *ctx.Context) error {
    user := ctx.EffectiveUser
    payment := ctx.EffectiveMessage.SuccessfulPayment
    chargeID := payment.TelegramPaymentChargeId

    // Grant premium access here
    g.Println("User {1.FirstName} ({1.Id}) paid {2.TotalAmount} {2.Currency} with payload {2.InvoicePayload}",
        user, payment)

    return ctx.SendMessage(g.Format("Payment complete! Thank you, {}!\nChargeID:\n{}", user.FirstName, chargeID)).
        Send().Err()
})

// Handle refunds
b.Command("refund", func(ctx *ctx.Context) error {
    chargeID := ctx.Args().Get(0).Some()

    if result := ctx.RefundStarPayment(chargeID).Send(); result.IsErr() {
        err := g.String(result.Err().Error())
        if err.Contains("CHARGE_ALREADY_REFUNDED") {
            return ctx.Reply("This payment was already refunded.").Send().Err()
        }
        return ctx.Reply("Refund failed.").Send().Err()
    }

    return ctx.Reply("Refund processed successfully.").Send().Err()
})

Middleware

Add middleware for request processing:

// Global middleware
b.Use(func(ctx *ctx.Context) error {
    // Log all updates
    fmt.Println("Update from user:", ctx.EffectiveUser.Id)
    return nil // Continue processing
})

// Admin-only middleware
adminMiddleware := func(ctx *ctx.Context) error {
    admin := ctx.IsAdmin()
    if admin.IsErr() {
        return admin.Err()
    }

    if !admin.Ok() {
        return ctx.AnswerCallbackQuery("Access restricted to admins only!").Alert().Send().Err()
    }

    return nil // Continue
}

// Apply middleware to specific handlers
b.On.Callback.Prefix("admin_", adminMiddleware)

Webhook Mode

Set up webhook instead of polling:

import (
    "net/http"
    "io"

    "github.com/enetx/tg/bot"
    "github.com/enetx/tg/types/updates"
)

func main() {
    b := bot.New(token).Build().Unwrap()

    // Register webhook
    result := b.Webhook().
        Domain("https://yourdomain.com").
        Path("/webhook").
        SecretToken("your-secret").
        AllowedUpdates(updates.Message, updates.CallbackQuery).
        Register()
    if result.IsErr() {
        panic(result.Err())
    }

    // Setup HTTP server
    http.HandleFunc("/webhook", func(w http.ResponseWriter, r *http.Request) {
        // Verify secret token
        if r.Header.Get("X-Telegram-Bot-Api-Secret-Token") != "your-secret" {
            http.Error(w, "Unauthorized", http.StatusUnauthorized)
            return
        }

        body, _ := io.ReadAll(r.Body)
        b.HandleWebhook(body)
        w.WriteHeader(http.StatusOK)
    })

    http.ListenAndServe(":8080", nil)
}

Business Account API

Handle business account connections and messages:

// Handle business connection updates
b.On.BusinessConnection.Enabled(func(ctx *ctx.Context) error {
    conn := ctx.Update.BusinessConnection

    // Configure business account
    return ctx.Business(g.String(conn.Id)).SetName("My Business").
        LastName("LLC").
        Send().Err()
})

// Handle business messages
b.On.Message.Business(func(ctx *ctx.Context) error {
    return ctx.Reply("Business message received!").Send().Err()
})

// Handle deleted business messages
b.On.BusinessMessagesDeleted.Any(func(ctx *ctx.Context) error {
    deleted := ctx.Update.DeletedBusinessMessages
    // Process message deletions
    return nil
})

// Manage business account settings
b.Command("business_setup", func(ctx *ctx.Context) error {
    connectionId := g.String("your_connection_id")

    // Set profile information
    err := ctx.Business(connectionId).
        SetBio("Professional business account").
        Send().Err()

    if err != nil {
        return err
    }

    // Check star balance
    balance := ctx.Business(connectionId).Balance().GetStarBalance().Send()
    if balance.IsOk() {
        return ctx.Reply(g.Format("Stars balance: {}", balance.Ok().Amount)).Send().Err()
    }

    return ctx.Reply("Business account configured").Send().Err()
})

Text Entities and Formatting

Format text messages with various entities:

```go import ( "github.com/enetx/g" "github.com/enetx/tg/entities" )

// Basic text formatting b.Command("format", func(ctx *ctx.Context) error { text := g.String("Hello bold italic code")

e := entities.New(text).
    Bold("bold").     // Make "bold" bold
    Italic("italic"). // Make "italic" italic
    Code("cod

Extension points exported contracts — how you extend this code

Bot (Interface)
Bot defines the minimal interface required to perform business account operations. [7 implementers]
ctx/business/bot.go
QueryResult (Interface)
QueryResult represents an interface for all inline query result builders. [47 implementers]
inline/query_result.go
Media (Interface)
Media represents an interface for all input media builders. [47 implementers]
input/builder.go
Keyboard (Interface)
Keyboard represents any type that can produce a gotgbot.ReplyMarkup. [50 implementers]
keyboard/keyboard.go
BotAPI (Interface)
(no doc) [5 implementers]
core/types.go
Handler (FuncType)
Handler is a function type that handles bot events and returns an error if processing fails.
handlers/handler.go
BusinessMessagesDeleted (FuncType)
BusinessMessagesDeleted is a filter function that determines whether to handle deleted business messages.
handlers/filters/filters.go
MessageContent (Interface)
MessageContent represents an interface for all input message content builders. [47 implementers]
input/builder.go

Core symbols most depended-on inside this repo

Error
called by 4087
ctx/answer_shipping_query.go
String
called by 3429
types/chat/chat.go
Send
called by 1430
ctx/reply.go
New
called by 1122
ctx/context.go
Std
called by 920
areas/areas.go
Timeout
called by 683
ctx/reply.go
APIURL
called by 633
ctx/reply.go
Build
called by 544
input/builder.go

Shape

Function 2,583
Method 2,243
Struct 279
TypeAlias 13
Interface 12
FuncType 2

Languages

Go100%

Modules by API surface

ctx/context.go150 symbols
handlers/message.go82 symbols
tests/handlers/message_test.go50 symbols
examples/advanced/interactive_keyboards_advanced/interactive_keyboards_advanced.go42 symbols
ctx/send_poll.go41 symbols
tests/ctx/send_poll_test.go37 symbols
tests/keyboard/inline_keyboard_test.go34 symbols
tests/ctx/send_video_test.go34 symbols
ctx/send_video.go32 symbols
ctx/send_invoice.go30 symbols
bot/bot.go29 symbols
tests/handlers/inline_query_test.go28 symbols

For agents

$ claude mcp add tg \
  -- python -m otcore.mcp_server <graph>

⬇ download graph artifact