tgaml is a Go framework for building Telegram bots where dialogue structure,
texts, and keyboards are declared in YAML. Business logic that requires Go code
is wired through a simple handler registration API.
go get github.com/shumdude/tgaml| Package | Import path | Description |
|---|---|---|
config |
github.com/shumdude/tgaml/pkg/config |
YAML types, Load(fs.FS), cross-reference validator |
i18n |
github.com/shumdude/tgaml/pkg/i18n |
Dotted-key resolver, {var} templating |
telegram |
github.com/shumdude/tgaml/pkg/telegram |
ChatIDFromUpdate helper |
session |
github.com/shumdude/tgaml/pkg/session |
FSMBackend interface, per-user Session |
engine |
github.com/shumdude/tgaml/pkg/engine |
Update router, render engine, widget registries |
// 1. Load config from an embedded FS
//go:embed config
var configFS embed.FS
sub, _ := fs.Sub(configFS, "config")
cfg, _ := config.Load(sub)
// 2. Create engine
f := /* your fsm.FSM implementation satisfying session.FSMBackend */
eng := engine.New(cfg, f)
// 3. Register show_if evaluators (optional, for conditional buttons)
eng.RegisterShowIf("registered", func(s *session.Session) bool {
return s.GetStr("reg", "name") != "" && s.GetStr("reg", "email") != ""
})
// 4. Register Go handlers
eng.Register("handle_my_feature", myHandler(eng))
// 5. Create bot
b, _ := bot.New(token, eng.BotOptions()...)
// 6. Register widgets (after bot.New)
dp := datepicker.New(b, onSelect, ...)
eng.RegisterDatepicker("my_datepicker", dp)
// 7. Run
b.Start(ctx)messages:
menu:
title: "Welcome!"
registration:
ask_name: "What is your name?"
buttons:
register: "Register"
faq: "FAQ"keyboards:
main_menu:
- - id: btn_register
text: buttons.register
action: goto
target: reg_ask_name
show_if: unregistered
- - id: btn_faq
text: buttons.faq
action: handler
target: handle_faqscenes:
- id: menu
text: messages.menu.title
text_registered: messages.menu.title_registered
keyboard: main_menu
render_mode: UPDATE_MESSAGE
keyboard_policy: KEEP_AS_IS
commands:
- command: /start
scene: menu
dialogs:
- id: dialog_faq
prefix: faq:
nodes:
- id: root
text: messages.faq.title
buttons:
- text: buttons.faq_q1
next: q1
- id: q1
text: messages.faq.q1func myHandler(e *engine.Engine) engine.HandlerFunc {
return func(ctx context.Context, b *bot.Bot, u *models.Update, s *session.Session) (nextScene string, err error) {
// nextScene == "" -> engine does nothing (handler manages state)
// nextScene != "" -> engine transitions FSM (no auto-render)
return "", nil
}
}Register named evaluators via eng.RegisterShowIf(name, fn). The framework
calls the evaluator when building a keyboard for a user. Unknown names are
treated as "show" (fail-open); nil session is treated as "hide" (fail-safe).
name := sess.GetStr("reg", "name")
sess.SetStr("reg", "name", "Alice")
sess.SetStrings("reg", map[string]string{"name": "Alice", "email": "a@b.c"})
sess.ClearNamespace("reg")
type Prefs struct{ Lang string }
session.SetTyped(sess, "app", "prefs", Prefs{Lang: "en"})
prefs, _ := session.GetTyped[Prefs](sess, "app", "prefs")
sess.Transition(ctx, "menu")
scene, _ := sess.CurrentScene()Run the framework test suite from a fresh clone:
go test ./...AGENTS.mdfor repository-specific change constraints and test expectations.