Files
scrabble-game/platform/telegram/internal/config/config.go
T
Ilia Denisov e9f836db87
Tests · Go / test (push) Successful in 9s
Tests · Integration / integration (push) Successful in 10s
Tests · UI / test (push) Successful in 20s
Tests · Go / test (pull_request) Successful in 8s
Tests · Integration / integration (pull_request) Successful in 11s
Tests · UI / test (pull_request) Successful in 19s
Stage 15: dual Telegram bots & language-gated variants
Service-agnostic refinement of the owner's idea: the sign-in service returns a
set of supported game languages with the user identity, and the lobby gates the
New Game variant choice by it (en -> English; ru -> Russian + Эрудит).

- Connector hosts two bots in one container (one per service language, each its
  own token + game channel; the same telegram_id spans both). ValidateInitData
  tries each token and returns the validating bot's service_language +
  supported_languages. Per-language config (TELEGRAM_BOT_TOKEN_EN/_RU, channels).
- supported_languages rides the Session (fbs, session-scoped, not persisted); the
  UI offers only the matching variants on New Game — gating only the START of a
  new game (auto-match + friend invite), not accept/open/play; backend does not
  enforce.
- service_language persisted (accounts.service_language, migration 00010, written
  every login, last-login-wins) and routes the user-facing Notify push back
  through the right bot (push-target coalesces with preferred_language).
- Admin SendToUser/SendToGameChannel gain an operator-chosen language selector in
  the console (unrelated to ValidateInitData).
- Non-Telegram logins carry the gateway default set
  (GATEWAY_DEFAULT_SUPPORTED_LANGUAGES, all variants).

Wire (committed regen): ValidateInitDataResponse +service_language
+supported_languages; Session +supported_languages; SendToUser/SendToGameChannel
+language. Docs (ARCHITECTURE/FUNCTIONAL/_ru/READMEs) + PLAN updated; stage marked done.
2026-06-05 09:35:53 +02:00

108 lines
4.0 KiB
Go

// Package config loads the Telegram connector's environment configuration.
package config
import (
"fmt"
"os"
"strconv"
"strings"
pkgtel "scrabble/pkg/telemetry"
)
// Languages is the set of service languages a bot may be tagged with. Each is a
// separate bot (own token + game channel) serving that audience; the same Telegram
// user id spans them all (ARCHITECTURE.md §12).
var Languages = []string{"en", "ru"}
// BotConfig is one language-tagged bot's settings.
type BotConfig struct {
// Token is the Telegram Bot API token (TELEGRAM_BOT_TOKEN_<LANG>). It both
// authenticates the Bot API client and is the HMAC secret for Mini App initData
// validation against this bot.
Token string
// GameChannelID is the chat id of this bot's game channel for SendToGameChannel
// (TELEGRAM_GAME_CHANNEL_ID_<LANG>, optional; 0 disables channel posts).
GameChannelID int64
}
// Config is the Telegram connector's runtime configuration, read from the
// environment. The bot tokens live only in this process (ARCHITECTURE.md §12).
type Config struct {
// Bots maps a service language (one of Languages) to that language's bot
// settings. A language is present when its TELEGRAM_BOT_TOKEN_<LANG> is set; at
// least one bot is required.
Bots map[string]BotConfig
// GRPCAddr is the listen address of the connector gRPC server that gateway and
// backend call (TELEGRAM_GRPC_ADDR, default :9091).
GRPCAddr string
// MiniAppURL is the HTTPS origin of the Mini App registered with BotFather; it
// is the base of every launch button, to which a deep-link adds a startapp
// query parameter (TELEGRAM_MINIAPP_URL, required). It is shared by all bots
// (one gateway origin); initData is signed per bot token.
MiniAppURL string
// APIBaseURL overrides the Bot API host (TELEGRAM_API_BASE_URL, optional;
// default https://api.telegram.org) — used for a local mock or a self-hosted
// Bot API server. Shared by all bots.
APIBaseURL string
// TestEnv routes the Bot API client to Telegram's test environment
// (.../bot<token>/test/METHOD) (TELEGRAM_TEST_ENV=true, default false).
TestEnv bool
// LogLevel is the zap log level (TELEGRAM_LOG_LEVEL, default info).
LogLevel string
// Telemetry configures the OpenTelemetry providers (shared bootstrap).
Telemetry pkgtel.Config
}
// Load reads the connector configuration from the environment, applying defaults
// and validating the required fields.
func Load() (Config, error) {
cfg := Config{
Bots: map[string]BotConfig{},
GRPCAddr: envOr("TELEGRAM_GRPC_ADDR", ":9091"),
MiniAppURL: os.Getenv("TELEGRAM_MINIAPP_URL"),
APIBaseURL: os.Getenv("TELEGRAM_API_BASE_URL"),
TestEnv: os.Getenv("TELEGRAM_TEST_ENV") == "true",
LogLevel: envOr("TELEGRAM_LOG_LEVEL", "info"),
}
for _, lang := range Languages {
suffix := strings.ToUpper(lang)
token := os.Getenv("TELEGRAM_BOT_TOKEN_" + suffix)
if token == "" {
continue
}
bot := BotConfig{Token: token}
if v := strings.TrimSpace(os.Getenv("TELEGRAM_GAME_CHANNEL_ID_" + suffix)); v != "" {
id, err := strconv.ParseInt(v, 10, 64)
if err != nil {
return Config{}, fmt.Errorf("config: TELEGRAM_GAME_CHANNEL_ID_%s %q: %w", suffix, v, err)
}
bot.GameChannelID = id
}
cfg.Bots[lang] = bot
}
tel := pkgtel.DefaultConfig("scrabble-telegram")
tel.ServiceName = envOr("TELEGRAM_SERVICE_NAME", tel.ServiceName)
tel.TracesExporter = envOr("TELEGRAM_OTEL_TRACES_EXPORTER", tel.TracesExporter)
tel.MetricsExporter = envOr("TELEGRAM_OTEL_METRICS_EXPORTER", tel.MetricsExporter)
cfg.Telemetry = tel
if len(cfg.Bots) == 0 {
return Config{}, fmt.Errorf("config: at least one TELEGRAM_BOT_TOKEN_<LANG> (LANG in %v) is required", Languages)
}
if cfg.MiniAppURL == "" {
return Config{}, fmt.Errorf("config: TELEGRAM_MINIAPP_URL is required")
}
if err := cfg.Telemetry.Validate(); err != nil {
return Config{}, fmt.Errorf("config: %w", err)
}
return cfg, nil
}
// envOr returns the environment value for key, or def when it is unset or empty.
func envOr(key, def string) string {
if v := os.Getenv(key); v != "" {
return v
}
return def
}