- Telegram integration for customer statistics - MCP server implementation with rate limiting - Cache system for performance optimization - Multi-language support - RESTful API endpoints 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
159 lines
4.0 KiB
Go
159 lines
4.0 KiB
Go
package app
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
tgconfig "funstatmcp/internal/telegram"
|
|
)
|
|
|
|
type Config struct {
|
|
Telegram tgconfig.Config
|
|
Host string
|
|
Port int
|
|
RequireSession bool
|
|
}
|
|
|
|
func FromEnv() (Config, error) {
|
|
var cfg Config
|
|
|
|
telegramCfg, err := loadTelegramConfig()
|
|
if err != nil {
|
|
return cfg, err
|
|
}
|
|
|
|
cfg.Telegram = telegramCfg
|
|
cfg.Host = getEnvDefault("FUNSTAT_HOST", "127.0.0.1")
|
|
|
|
portStr := getEnvDefault("FUNSTAT_PORT", "8091")
|
|
port, err := strconv.Atoi(portStr)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("invalid FUNSTAT_PORT: %w", err)
|
|
}
|
|
cfg.Port = port
|
|
|
|
cfg.RequireSession = parseBool(getEnvDefault("FUNSTAT_REQUIRE_SESSION", "false"))
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func loadTelegramConfig() (tgconfig.Config, error) {
|
|
var cfg tgconfig.Config
|
|
|
|
apiIDStr := os.Getenv("TELEGRAM_API_ID")
|
|
if apiIDStr == "" {
|
|
return cfg, fmt.Errorf("TELEGRAM_API_ID is required")
|
|
}
|
|
apiID, err := strconv.Atoi(apiIDStr)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("invalid TELEGRAM_API_ID: %w", err)
|
|
}
|
|
cfg.APIID = apiID
|
|
|
|
cfg.APIHash = strings.TrimSpace(os.Getenv("TELEGRAM_API_HASH"))
|
|
if cfg.APIHash == "" {
|
|
return cfg, fmt.Errorf("TELEGRAM_API_HASH is required")
|
|
}
|
|
|
|
cfg.BotUsername = getEnvDefault("FUNSTAT_BOT_USERNAME", "@openaiw_bot")
|
|
cfg.SessionString = strings.TrimSpace(os.Getenv("TELEGRAM_SESSION_STRING"))
|
|
sessionStringFile := strings.TrimSpace(os.Getenv("TELEGRAM_SESSION_STRING_FILE"))
|
|
if cfg.SessionString == "" && sessionStringFile != "" {
|
|
data, err := os.ReadFile(expandPath(sessionStringFile))
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("read TELEGRAM_SESSION_STRING_FILE: %w", err)
|
|
}
|
|
cfg.SessionString = strings.TrimSpace(string(data))
|
|
}
|
|
|
|
sessionPath := strings.TrimSpace(os.Getenv("TELEGRAM_SESSION_PATH"))
|
|
if sessionPath != "" {
|
|
if !strings.HasSuffix(sessionPath, ".session") {
|
|
sessionPath = sessionPath + ".session"
|
|
}
|
|
cfg.SessionStorage = expandPath(sessionPath)
|
|
} else {
|
|
cfg.SessionStorage = defaultSessionPath()
|
|
}
|
|
|
|
if value := strings.TrimSpace(os.Getenv("FUNSTAT_RATE_LIMIT_PER_SECOND")); value != "" {
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("invalid FUNSTAT_RATE_LIMIT_PER_SECOND: %w", err)
|
|
}
|
|
cfg.RateLimit = parsed
|
|
}
|
|
|
|
if value := strings.TrimSpace(os.Getenv("FUNSTAT_RATE_LIMIT_WINDOW")); value != "" {
|
|
duration, err := time.ParseDuration(value)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("invalid FUNSTAT_RATE_LIMIT_WINDOW: %w", err)
|
|
}
|
|
cfg.RateLimitWindow = duration
|
|
}
|
|
|
|
if value := strings.TrimSpace(os.Getenv("FUNSTAT_CACHE_TTL")); value != "" {
|
|
duration, err := time.ParseDuration(value)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("invalid FUNSTAT_CACHE_TTL: %w", err)
|
|
}
|
|
cfg.CacheTTL = duration
|
|
}
|
|
|
|
proxyHost := strings.TrimSpace(os.Getenv("FUNSTAT_PROXY_HOST"))
|
|
proxyPort := strings.TrimSpace(os.Getenv("FUNSTAT_PROXY_PORT"))
|
|
if proxyHost != "" && proxyPort != "" {
|
|
port, err := strconv.Atoi(proxyPort)
|
|
if err != nil {
|
|
return cfg, fmt.Errorf("invalid FUNSTAT_PROXY_PORT: %w", err)
|
|
}
|
|
cfg.Proxy = &tgconfig.ProxyConfig{
|
|
Type: getEnvDefault("FUNSTAT_PROXY_TYPE", "socks5"),
|
|
Host: proxyHost,
|
|
Port: port,
|
|
Username: strings.TrimSpace(os.Getenv("FUNSTAT_PROXY_USERNAME")),
|
|
Password: strings.TrimSpace(os.Getenv("FUNSTAT_PROXY_PASSWORD")),
|
|
}
|
|
}
|
|
|
|
return cfg, nil
|
|
}
|
|
|
|
func getEnvDefault(key, fallback string) string {
|
|
if value := strings.TrimSpace(os.Getenv(key)); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func parseBool(value string) bool {
|
|
switch strings.ToLower(strings.TrimSpace(value)) {
|
|
case "1", "true", "yes", "on":
|
|
return true
|
|
default:
|
|
return false
|
|
}
|
|
}
|
|
|
|
func defaultSessionPath() string {
|
|
home, err := os.UserHomeDir()
|
|
if err != nil {
|
|
return filepath.Join(os.TempDir(), "funstatmcp", "session.json")
|
|
}
|
|
return filepath.Join(home, ".funstatmcp", "session.json")
|
|
}
|
|
|
|
func expandPath(path string) string {
|
|
if strings.HasPrefix(path, "~") {
|
|
home, err := os.UserHomeDir()
|
|
if err == nil {
|
|
return filepath.Join(home, strings.TrimPrefix(path, "~"))
|
|
}
|
|
}
|
|
return path
|
|
}
|