8000 < 7FFF meta name="color-scheme" content="light dark" />
Skip to content

Latest commit

 

History

16 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

botx

A Go client library for the Telegram Bot API with auto-generated types and methods.

Built with fasthttp and sonic for high performance.

Features

  • Auto-generated from the official Telegram Bot API spec
  • Fully typed — every API type is a Go struct, every method is a typed function
  • Middleware framework — composable middleware chain with filters
  • Typed handlersOnMessage, OnCallbackQuery, etc.
  • Bound methodsctx.Reply, ctx.ReplyWithPhoto, ctx.EditMessageText, etc.
  • Webhook support — adapters for net/http, Fiber, Gin, Echo
  • Session management — pluggable storage with typed helpers
  • Rate limiting — token bucket, sliding window, fixed window
  • i18n — translations with pluralization support
  • File uploads — supports file_id, URL, and multipart upload
  • Error handling — structured APIError with error codes and retry info
  • High performance — fasthttp + sonic for minimal allocations

Quick Start

package main

import (
	"context"
	"log"
	"os"
	"os/signal"

	"github.com/pageton/botx/tg"
)

func main() {
	bot := tg.NewBot(os.Getenv("BOT_TOKEN"))

	bot.On(tg.OnCommand("start"), func(ctx *tg.Context) {
		ctx.Reply("Welcome!")
	})

	bot.On(tg.OnText(), func(ctx *tg.Context) {
		ctx.Reply("Echo: " + ctx.MessageText())
	})

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

	bot.Start(ctx)
}

Typed Handlers

Use typed handlers for cleaner code with guaranteed non-nil objects:

bot.OnMessage(func(ctx *tg.Context, msg *tg.Message) {
    // msg is guaranteed non-nil
    ctx.Reply("Got message: " + *msg.Text)
})

bot.OnCallbackQuery(func(ctx *tg.Context, cb *tg.CallbackQuery) {
    // cb is guaranteed non-nil
    data := ""
    if cb.Data != nil {
        data = *cb.Data
    }
    ctx.AnswerCallback("You clicked: " + data, false)
})

bot.OnInlineQuery(func(ctx *tg.Context, q *tg.InlineQuery) { ... })
bot.OnPoll(func(ctx *tg.Context, p *tg.Poll) { ... })
bot.OnChatMember(func(ctx *tg.Context, m *tg.ChatMemberUpdated) { ... })

Bound Methods

Context methods that automatically use the current chat/message:

// Messages
ctx.Reply("text")
ctx.ReplyWithPhoto(photo)
ctx.ReplyWithDocument(document)
ctx.ReplyWithVideo(video)
ctx.ReplyWithAudio(audio)
ctx.ReplyWithVoice(voice)
ctx.ReplyWithAnimation(animation)
ctx.ReplyWithVideoNote(videoNote)
ctx.ReplyWithMediaGroup(media)
ctx.ReplyWithLocation(lat, lng)
ctx.ReplyWithVenue(lat, lng, title, address)
ctx.ReplyWithContact(phone, name)
ctx.ReplyWithPoll(question, options)
ctx.ReplyWithDice()
ctx.ReplyWithChatAction("typing")

// Message operations
ctx.ForwardMessage(toChatID, msgID)
ctx.CopyMessage(toChatID, msgID)
ctx.EditMessageText("new text")
ctx.EditMessageCaption(tg.EditCaptionParams{Caption: "new"})
ctx.EditMessageMedia(media)
ctx.EditMessageReplyMarkup(keyboard)
ctx.DeleteMessage()

// Chat management
ctx.BanChatMember(userID)
ctx.UnbanChatMember(userID)
ctx.RestrictChatMember(userID, permissions)
ctx.PromoteChatMember(userID)
ctx.SetChatPermissions(permissions)
ctx.ExportChatInviteLink()
ctx.SetChatPhoto(photo)
ctx.SetChatTitle(title)
ctx.SetChatDescription(desc)
ctx.PinChatMessage(msgID)
ctx.UnpinChatMessage(msgID)
ctx.LeaveChat()
ctx.GetChat()
ctx.GetChatMemberCount()
ctx.GetChatMember(userID)

// Forum topics
ctx.CreateForumTopic(name)
ctx.EditForumTopic(threadID)
ctx.CloseForumTopic(threadID)
ctx.ReopenForumTopic(threadID)
ctx.DeleteForumTopic(threadID)

Context Helpers

ctx.UpdateID()  // update identifier
ctx.ChatID()    // current chat ID
ctx.User()      // *User from any update type
ctx.Message()   // *Message from any message update
ctx.MessageText()

Webhook

net/http

import (
	"net/http"
	"github.com/pageton/botx/tg"
)

bot := tg.NewBot(token)
bot.On(tg.OnCommand("start"), func(ctx *tg.Context) {
    ctx.Reply("Hello!")
})

handler := tg.HTTPHandler(bot, tg.WebhookOptions{
    SecretToken: "my_secret",
})
http.Handle("/webhook", handler)
http.ListenAndServe(":8080", nil)

Fiber

import "github.com/gofiber/fiber/v2"

app := fiber.New()
handler := tg.FiberHandler(bot, tg.WebhookOptions{})
app.Post("/webhook", handler)
app.Listen(":8080")

Gin

import "github.com/gin-gonic/gin"

r := gin.Default()
handler := tg.GinHandler(bot, tg.WebhookOptions{})
r.POST("/webhook", handler)
r.Run(":8080")

Echo

import "github.com/labstack/echo/v4"

e := echo.New()
handler := tg.EchoHandler(bot, tg.WebhookOptions{})
e.POST("/webhook", handler)
e.Start(":8080")

Set Webhook

bot.SetWebhook("https://example.com/webhook", &tg.SetWebhookParams{
    SecretToken: "my_secret",
})

Session

import "github.com/pageton/botx/tg"

// In-memory storage (dev only)
storage := tg.NewMemoryStorage()

bot := tg.NewBot(token)
bot.Use(tg.Session(storage))

type MySession struct {
    Count int    `json:"count"`
    Name  string `json:"name"`
}

bot.On(tg.OnText(), func(ctx *tg.Context) {
    var sess MySession
    tg.GetSession(ctx, &sess)
    
    sess.Count++
    tg.SetSession(ctx, sess)
    
    ctx.Reply(fmt.Sprintf("Count: %d", sess.Count))
})

Session Storage (Sub-modules)

// JSON file storage
import jsonstore "github.com/pageton/botx/ext/session/json"
storage, _ := jsonstore.New("./sessions")

// Redis storage
import redisstore "github.com/pageton/botx/ext/session/redis"
storage := redisstore.New(redisClient, redisstore.WithPrefix("bot:"))

// SQLite storage
import sqlitestore "github.com/pageton/botx/ext/session/sqlite"
storage, _ := sqlitestore.New("./sessions.db")

// MongoDB storage
import mongostore "github.com/pageton/botx/ext/session/mongo"
storage := mongostore.New(mongoClient, "mydb", "sessions")

Custom Session Key

bot.Use(tg.Session(storage, tg.WithSessionKey(func(ctx *tg.Context) string {
    user := ctx.User()
    if user != nil {
        return fmt.Sprintf("user:%d", user.ID)
    }
    return ""
})))

Rate Limiting

import "github.com/pageton/botx/ext/middleware/ratelimit"

// Token bucket: 1 token/sec, burst of 5
limiter := ratelimit.NewTokenBucket(1, 5)

// Sliding window: 10 requests per minute
limiter := ratelimit.NewSlidingWindow(10, time.Minute)

// Fixed window: 30 requests per minute
limiter := ratelimit.NewFixedWindow(30, time.Minute)

// Apply middleware
bot.Use(ratelimit.Middleware(limiter,
    ratelimit.WithKeyFunc(ratelimit.ByUserID()),
    ratelimit.WithOnLimit(func(ctx *tg.Context) {
        ctx.Reply("Slow down!")
    }),
))

i18n

import "github.com/pageton/botx/ext/middleware/i18n"

// Create translator
tr := i18n.New(i18n.WithFallback("en"))

// Load translations
tr.LoadDir("./locales")

// Add middleware
bot.Use(i18n.Middleware(tr, i18n.ByUserLanguage("en")))

// Use in handlers
bot.On(tg.OnCommand("start"), func(ctx *tg.Context) {
    t := i18n.TFunc(ctx)
    ctx.Reply(t("welcome", map[string]string{
        "name": ctx.User().FirstName,
    }))
})

// Pluralization
bot.On(tg.OnCommand("items"), func(ctx *tg.Context) {
    p := i18n.PluralFunc(ctx)
    ctx.Reply(p("items", 5))
})

// Change locale
bot.On(tg.OnCommand("lang"), func(ctx *tg.Context) {
    i18n.SetLocale(ctx, "ru")
    ctx.Reply(i18n.T(ctx, "lang_changed"))
})

Translation Files

locales/en.json:

{
    "welcome": "Hello, {{name}}!",
    "items_zero": "No items",
    "items_one": "{{count}} item",
    "items_other": "{{count}} items"
}

locales/ru.json:

{
    "welcome": "Привет, {{name}}!",
    "items_zero": "Нет элементов",
    "items_one": "{{count}} элемент",
    "items_few": "{{count}} элемента",
    "items_many": "{{count}} элементов"
}

Filters

  • OnMessage() — matches messages
  • OnEditedMessage() — matches edited messages
  • OnChannelPost() — matches channel posts
  • OnCallbackQuery() — matches callback queries
  • OnText() — matches text messages
  • OnCommand("start") — matches /start commands
  • OnCallbackData("btn") — matches callback queries with specific data
  • OnPhoto() — matches photo messages
  • OnInlineQuery() — matches inline queries
  • OnPoll() — matches polls
  • OnChatMember() — matches chat member updates
  • And(f1, f2) — logical AND
  • Or(f1, f2) — logical OR
  • Not(f) — logical NOT

API Client Only

If you don't need the framework, use the API client directly:

client := tg.New("YOUR_BOT_TOKEN")

user, err := client.GetMe()
if err != nil {
    log.Fatal(err)
}

Regenerating Types

make generate

Project Structure

cmd/tg-gen/              CLI tool for code generation
internal/model/          Intermediate representation
internal/parser/         HTML spec parser
internal/generator/      Go code generator
tg/                      Public API: client + generated types/methods + bot framework
ext/
├── session/
│   ├── json/            JSON file session storage
│   ├── redis/           Redis session storage
│   ├── sqlite/          SQLite session storage
│   └── mongo/           MongoDB session storage
└── middleware/
    ├── ratelimit/       Rate limiting middleware
    └── i18n/            Internationalization middleware
examples/                Example bots

Testing

make test

License

MIT

About

A high-performance Go Telegram Bot API client with auto-generated types, middleware framework, session management, and webhook support. Built on fasthttp + sonic.

Topics

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages

0