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
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.
108 lines
4.0 KiB
Go
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
|
|
}
|