package main import ( "context" "fmt" "log" ) type Handler interface { Handle(ctx context.Context, update Update, bot Bot) error } type Bot struct { Slug string Token string Secret string RequireAuth bool Handler Handler } type Registry struct { bots map[string]Bot } // NewRegistry builds the bot routing map from the parsed YAML config + the // per-bot secrets (already merged into BotConfig from env). It receives the // shared TelegramClient and Auth so per-handler instances can use them. // `auth` may be nil (no AUTH_SECRET set) ; in that case any bot configured // with `handler: auth` or `requireAuth: true` is a fatal config error. func NewRegistry(cfg *Config, tg *TelegramClient, auth *Auth) (*Registry, error) { if len(cfg.Bots) == 0 { return nil, fmt.Errorf("no bots configured") } bots := make(map[string]Bot, len(cfg.Bots)) for slug, b := range cfg.Bots { token := b.Token secret := b.Secret if token == "" { return nil, fmt.Errorf("bot %s: token missing", slug) } if secret == "" { return nil, fmt.Errorf("bot %s: secret missing", slug) } // Default requireAuth = true (secure by default). Explicit // `requireAuth: false` is required to expose a bot publicly. requireAuth := true if b.RequireAuth != nil { requireAuth = *b.RequireAuth } // Chicken-and-egg : the auth handler itself can't be gated, otherwise // no one could ever authenticate. Force off and warn loudly. if b.Handler == "auth" { if requireAuth { log.Printf("bot %s: handler=auth implies requireAuth=false (otherwise unreachable) — overriding", slug) } requireAuth = false } if requireAuth && auth == nil { return nil, fmt.Errorf("bot %s has requireAuth: true (default) but AUTH_SECRET is unset; either set AUTH_SECRET or add `requireAuth: false` to the bot config", slug) } var h Handler switch b.Handler { case "echo": h = &EchoHandler{tg: tg} case "auth": if auth == nil { return nil, fmt.Errorf("bot %s uses handler=auth but AUTH_SECRET is unset", slug) } h = &AuthHandler{tg: tg, auth: auth} case "http": if b.HTTP == nil { return nil, fmt.Errorf("bot %s uses handler=http but no `http:` config block was provided", slug) } hh, err := NewHTTPHandler(tg, *b.HTTP) if err != nil { return nil, fmt.Errorf("bot %s: %w", slug, err) } h = hh case "": return nil, fmt.Errorf("bot %s: handler missing", slug) default: return nil, fmt.Errorf("bot %s: unknown handler %q", slug, b.Handler) } bots[slug] = Bot{ Slug: slug, Token: token, Secret: secret, RequireAuth: requireAuth, Handler: h, } } return &Registry{bots: bots}, nil } func (r *Registry) Get(slug string) (Bot, bool) { b, ok := r.bots[slug] return b, ok } func (r *Registry) Count() int { return len(r.bots) } func (r *Registry) Slugs() []string { out := make([]string, 0, len(r.bots)) for s := range r.bots { out = append(out, s) } return out }