package main import ( "encoding/json" "fmt" "log" "net/http" "strings" ) type Server struct { registry *Registry } func NewServer(r *Registry) *Server { return &Server{registry: r} } func (s *Server) Routes() http.Handler { mux := http.NewServeMux() mux.HandleFunc("/healthz", s.health) mux.HandleFunc("/readyz", s.ready) mux.HandleFunc("/bot/", s.botWebhook) return chain(mux, recoverMW, accessLogMW) } func (s *Server) health(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprint(w, "OK") } func (s *Server) ready(w http.ResponseWriter, _ *http.Request) { w.WriteHeader(http.StatusOK) _, _ = fmt.Fprint(w, "OK") } func (s *Server) botWebhook(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { http.Error(w, "method not allowed", http.StatusMethodNotAllowed) return } slug := strings.TrimPrefix(r.URL.Path, "/bot/") slug = strings.Trim(slug, "/") if slug == "" || strings.Contains(slug, "/") { http.Error(w, "bot slug missing or malformed", http.StatusBadRequest) return } bot, ok := s.registry.Get(slug) if !ok { http.Error(w, "unknown bot", http.StatusNotFound) return } if !verifyTelegramSecret(r.Header.Get("X-Telegram-Bot-Api-Secret-Token"), bot.Secret) { http.Error(w, "unauthorized", http.StatusUnauthorized) return } var update Update // NOTE: pas de DisallowUnknownFields — Telegram ajoute des champs // (entities, sticker, photo, forum_topic…) au fil du temps. On reste // tolérant et on n'extrait que ce dont on a besoin. if err := json.NewDecoder(r.Body).Decode(&update); err != nil { http.Error(w, "bad update payload", http.StatusBadRequest) return } if err := bot.Handler.Handle(r.Context(), update, bot); err != nil { log.Printf("bot=%s update=%d handler error: %v", slug, update.UpdateID, err) } w.WriteHeader(http.StatusOK) _, _ = fmt.Fprint(w, "{}") }