diff --git a/.env.example b/.env.example index 338d79c..921740a 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,6 @@ TELEMT_API_BASE_URL=http://host.docker.internal:9091 TELEMT_API_AUTH_HEADER=Bearer replace_me TELEGRAM_HTTP_TIMEOUT_SECONDS=60 TELEMT_HTTP_TIMEOUT_SECONDS=5 +TELEMT_LINK_HOST=127.0.0.1 +TELEMT_LINK_PORT=9443 +TELEMT_TLS_DOMAIN= diff --git a/README.md b/README.md index 1cf6283..f971d70 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,9 @@ - `TELEMT_API_AUTH_HEADER` - значение заголовка `Authorization` для Telemt API (если включён `auth_header`). - `TELEGRAM_HTTP_TIMEOUT_SECONDS` - timeout запросов к Telegram (по умолчанию `60`). - `TELEMT_HTTP_TIMEOUT_SECONDS` - timeout запросов к Telemt API (по умолчанию `5`). +- `TELEMT_LINK_HOST` - хост для генерации `tg://proxy` ссылок, если API их не вернуло. +- `TELEMT_LINK_PORT` - порт для ссылок (по умолчанию `9443`). +- `TELEMT_TLS_DOMAIN` - TLS-домен для генерации `EE-TLS` ссылки (опционально). ## Быстрый старт (локально, Linux/bash) @@ -47,6 +50,9 @@ services: TELEMT_API_AUTH_HEADER: ${TELEMT_API_AUTH_HEADER} TELEGRAM_HTTP_TIMEOUT_SECONDS: ${TELEGRAM_HTTP_TIMEOUT_SECONDS:-60} TELEMT_HTTP_TIMEOUT_SECONDS: ${TELEMT_HTTP_TIMEOUT_SECONDS:-5} + TELEMT_LINK_HOST: ${TELEMT_LINK_HOST} + TELEMT_LINK_PORT: ${TELEMT_LINK_PORT:-9443} + TELEMT_TLS_DOMAIN: ${TELEMT_TLS_DOMAIN:-} ``` Запуск (Linux/bash): @@ -56,6 +62,9 @@ export TELEGRAM_BOT_TOKEN="xxx:telegram_bot_token" export TELEGRAM_ADMIN_CHAT_ID="123456789" export TELEMT_API_BASE_URL="http://host.docker.internal:9091" export TELEMT_API_AUTH_HEADER="Bearer secret" +export TELEMT_LINK_HOST="172.20.0.3" +export TELEMT_LINK_PORT="9443" +export TELEMT_TLS_DOMAIN="tf.ivx.su" docker compose up -d docker compose logs -f @@ -73,6 +82,9 @@ docker run -d \ -e TELEMT_API_AUTH_HEADER="Bearer secret" \ -e TELEGRAM_HTTP_TIMEOUT_SECONDS="60" \ -e TELEMT_HTTP_TIMEOUT_SECONDS="5" \ + -e TELEMT_LINK_HOST="172.20.0.3" \ + -e TELEMT_LINK_PORT="9443" \ + -e TELEMT_TLS_DOMAIN="tf.ivx.su" \ git.shts.su/denozord/telemt-bot:latest ``` diff --git a/docker-compose.yml b/docker-compose.yml index 27ea8c5..8e322e4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -12,3 +12,6 @@ services: TELEMT_API_AUTH_HEADER: ${TELEMT_API_AUTH_HEADER} TELEGRAM_HTTP_TIMEOUT_SECONDS: ${TELEGRAM_HTTP_TIMEOUT_SECONDS:-60} TELEMT_HTTP_TIMEOUT_SECONDS: ${TELEMT_HTTP_TIMEOUT_SECONDS:-5} + TELEMT_LINK_HOST: ${TELEMT_LINK_HOST} + TELEMT_LINK_PORT: ${TELEMT_LINK_PORT:-9443} + TELEMT_TLS_DOMAIN: ${TELEMT_TLS_DOMAIN:-} diff --git a/main.go b/main.go index ba1da31..d6bce7b 100644 --- a/main.go +++ b/main.go @@ -3,6 +3,7 @@ package main import ( "bytes" "context" + "encoding/hex" "encoding/json" "errors" "fmt" @@ -32,6 +33,10 @@ type config struct { TelemtTimeout time.Duration TelegramTimeout time.Duration + + TelemtLinkHost string + TelemtLinkPort int + TelemtLinkTLSDomain string } type bot struct { @@ -168,6 +173,9 @@ func loadConfig() (config, error) { cfg.TelemtAPIAuth = strings.TrimSpace(os.Getenv("TELEMT_API_AUTH_HEADER")) cfg.TelemtTimeout = parseSecondsEnv("TELEMT_HTTP_TIMEOUT_SECONDS", defaultTelemtTimeoutSeconds) cfg.TelegramTimeout = parseSecondsEnv("TELEGRAM_HTTP_TIMEOUT_SECONDS", defaultTelegramTimeoutSeconds) + cfg.TelemtLinkHost = strings.TrimSpace(os.Getenv("TELEMT_LINK_HOST")) + cfg.TelemtLinkPort = parseIntEnv("TELEMT_LINK_PORT", 9443) + cfg.TelemtLinkTLSDomain = strings.TrimSpace(os.Getenv("TELEMT_TLS_DOMAIN")) return cfg, nil } @@ -184,6 +192,18 @@ func parseSecondsEnv(key string, fallback int) time.Duration { return time.Duration(v) * time.Second } +func parseIntEnv(key string, fallback int) int { + raw := strings.TrimSpace(os.Getenv(key)) + if raw == "" { + return fallback + } + v, err := strconv.Atoi(raw) + if err != nil || v <= 0 { + return fallback + } + return v +} + func (b *bot) run(ctx context.Context) error { for { select { @@ -350,14 +370,7 @@ func (b *bot) handleCreateUser(ctx context.Context, chatID int64, text string) e return b.sendMessage(ctx, chatID, "Ошибка создания пользователя: "+err.Error()) } - links := created.User.Links - if len(links.Classic) == 0 && len(links.Secure) == 0 && len(links.TLS) == 0 { - var fetched userInfo - path := "/v1/users/" + url.PathEscape(created.User.Username) - if err := b.callTelemt(ctx, path, &fetched); err == nil { - links = fetched.Links - } - } + links := b.resolveUserLinks(ctx, created.User.Username, created.Secret, created.User.Links) msg := fmt.Sprintf("Пользователь создан: %s\nsecret: %s", created.User.Username, created.Secret) if linksText := formatUserLinks(links); linksText != "" { @@ -368,6 +381,63 @@ func (b *bot) handleCreateUser(ctx context.Context, chatID int64, text string) e return b.sendMessage(ctx, chatID, msg) } +func hasAnyLinks(links userLinks) bool { + return len(links.Classic) > 0 || len(links.Secure) > 0 || len(links.TLS) > 0 +} + +func (b *bot) resolveUserLinks(ctx context.Context, username, secret string, initial userLinks) userLinks { + if hasAnyLinks(initial) { + return initial + } + + var fetched userInfo + path := "/v1/users/" + url.PathEscape(username) + if err := b.callTelemt(ctx, path, &fetched); err == nil && hasAnyLinks(fetched.Links) { + return fetched.Links + } + + var users []userInfo + if err := b.callTelemt(ctx, "/v1/users", &users); err == nil { + for i := range users { + if users[i].Username == username && hasAnyLinks(users[i].Links) { + return users[i].Links + } + } + } + + users = nil + if err := b.callTelemt(ctx, "/v1/stats/users", &users); err == nil { + for i := range users { + if users[i].Username == username && hasAnyLinks(users[i].Links) { + return users[i].Links + } + } + } + + return b.generateLinksFromSecret(secret) +} + +func (b *bot) generateLinksFromSecret(secret string) userLinks { + host := strings.TrimSpace(b.cfg.TelemtLinkHost) + if host == "" || secret == "" { + return userLinks{} + } + port := b.cfg.TelemtLinkPort + classic := fmt.Sprintf("tg://proxy?server=%s&port=%d&secret=%s", host, port, secret) + dd := fmt.Sprintf("tg://proxy?server=%s&port=%d&secret=dd%s", host, port, secret) + + out := userLinks{ + Classic: []string{classic}, + Secure: []string{dd}, + } + if b.cfg.TelemtLinkTLSDomain != "" { + hexDomain := hex.EncodeToString([]byte(b.cfg.TelemtLinkTLSDomain)) + tls := fmt.Sprintf("tg://proxy?server=%s&port=%d&secret=ee%s%s", host, port, secret, hexDomain) + out.TLS = []string{tls} + } + return out +} + func formatUserLinks(links userLinks) string { var sb strings.Builder appendLinks := func(title string, items []string) {