kittenbark/tg
A zero-dependency Go library for Telegram bots. The API is a declarative pipeline — updates flow top-to-bottom through filters and handlers, and you wire them together in a single chain.
Every Telegram API method is code-generated from the official schema, so SendMessage, SendPhoto, EditMessageText
and ~200 others are always available, typed, and consistent.
Getting started
go get github.com/kittenbark/tg@latest
Set KBTG_TOKEN=<your token> (or KITTENBARK_TG_TOKEN), then:
package main
import (
"context"
"github.com/kittenbark/tg"
)
func main() {
tg.NewFromEnv().
OnError(tg.OnErrorLog).
Filter(tg.OnPrivate).
Command("/start", tg.CommonTextReply("hello!")).
Branch(tg.OnMessage, func(ctx context.Context, upd *tg.Update) error {
msg := upd.Message
_, err := tg.CopyMessage(ctx, msg.Chat.Id, msg.Chat.Id, msg.MessageId)
return err
}).
Start()
}
Pipeline
The bot is a chain. Each .Branch checks its filter — if it matches, the handler runs and the update is consumed; if
not, it falls through to the next step.
tg.NewFromEnv().
Scheduler(). // auto rate-limit (prevents 429s)
OnError(tg.OnErrorLog).
Filter(tg.OnPrivate). // drop everything not from a DM
Command("/start", greetHandler).
Command("/help", helpHandler).
Branch(tg.OnPhoto, handlePhoto).
Branch(tg.OnDocument, handleDocument).
Branch(tg.OnText, tg.CommonTextReply("unrecognized command")).
Start()
| Method | What it does |
|---|---|
Filter(pred) |
Drop updates that don’t match — nothing below sees them |
Branch(pred, handler) |
Handle matching updates, let others fall through |
Command("/cmd", handler) |
Shorthand for Branch(tg.OnCommand("/cmd"), handler) |
Handle(handler) |
Catch-all — sees every update that reaches it |
Default(handler) |
Same as Handle but conventionally placed last |
Scheduler() |
Attach rate limiter (respects Telegram’s per-chat/global limits) |
OnError(fn) |
Set error handler (tg.OnErrorLog, tg.OnErrorExit, or custom) |
Filters
Filters are plain functions func(ctx, upd) bool, composable with All, Either, Not:
tg.All(tg.OnUrl, myDomainCheck) // AND
tg.Either(tg.OnPhoto, tg.OnVideo) // OR
tg.Not(tg.OnForwarded) // NOT
Content
| Filter | Matches |
|---|---|
OnMessage |
Any message |
OnText |
Text message |
OnPhoto |
Photo |
OnVideo |
Video |
OnDocument |
Document |
OnAudio |
Audio |
OnVoice |
Voice message |
OnAnimation |
GIF/animation |
OnSticker |
Sticker |
OnMedia |
Any media type |
OnUrl |
Message contains a URL |
OnTextRegexp(re) |
Text matches regex |
OnCommand("/cmd") |
Specific command |
OnCallback |
Inline button press |
OnCallbackWithData[T](pred) |
Typed button callback |
Scope
| Filter | Matches |
|---|---|
OnPrivate |
Private chat / DM |
OnPrivateMessage |
Private message specifically |
OnPublicMessage |
Group or channel |
OnChat(ids...) |
Whitelist specific chats |
OnSender(ids...) |
Whitelist specific users |
OnForwarded |
Forwarded message |
OnReply |
Reply to another message |
OnEdited |
Edited message |
OnChance(0.5) |
Random — true with given probability |
Handler combinators
Handlers are plain functions func(ctx, upd) error, composable too:
// Run all in sequence, stop on first error
tg.Chain(logHandler, processHandler, replyHandler)
// Try each, stop on first success (no error)
tg.Fallback(premiumHandler, freeHandler)
// Serialize concurrent access
tg.Synced(handler)
Common helpers
Ready-made handlers for the most frequent patterns:
tg.CommonTextReply("text") // reply with plain text
tg.CommonTextReplyExpiring(5*time.Minute, "text") // reply, auto-delete after duration
tg.CommonReactionReply("👌") // set reaction on message
tg.CommonDeleteMessage // delete the triggering message
tg.CommonRestrictSender() // mute sender
Media
// Upload from disk
tg.SendDocument(ctx, chatId, tg.FromDisk("report.pdf"))
// Reference by file_id or URL
tg.SendPhoto(ctx, chatId, tg.FromCloud(fileId))
// Download to temp file
path, err := tg.GenericDownloadTemp(ctx, fileId)
defer os.Remove(path)
// Albums (grouped media) arrive as separate updates — HandleAlbum batches them:
tg.HandleAlbum(func (ctx context.Context, updates []*tg.Update) error {
// all photos in the album are here
return nil
})
Configuration
NewFromEnv() reads from env vars prefixed KBTG_ or KITTENBARK_TG_:
| Variable | Description |
|---|---|
TOKEN |
Bot token from @BotFather |
TEST_TOKEN |
Fallback token for testing |
API_URL |
Custom Bot API server URL |
TIMEOUT_HANDLE |
Handler timeout in seconds (0 = unlimited) |
ON_ERROR |
log, exit, or ignore |
SYNCED_HANDLE |
true to process updates sequentially |
DOWNLOAD_TYPE |
classic, local_move, or local_copy |
Or construct directly:
tg.New(&tg.Config{
Token: os.Getenv("TOKEN"),
TimeoutHandle: 30 * time.Second,
OnError: tg.OnErrorLog,
})
Full example walkthrough: tg / examples