From e48cd7bccc421c6bafd482ccae93a61008e6bc0e Mon Sep 17 00:00:00 2001 From: Artem Tsyrulnikov Date: Wed, 18 Feb 2026 20:21:38 +0300 Subject: [PATCH] =?UTF-8?q?=D0=BC=D0=B8=D0=BD=D0=B8=20=D1=84=D0=B8=D0=BA?= =?UTF-8?q?=D1=81=D1=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .env.example | 1 + Makefile | 4 +--- docker-compose.yml.example | 1 + shared/logger/logger.py | 33 ++++++++++++++++++++------------ src/main.py | 5 ++--- tg_bot/bot/bot.go | 18 ----------------- tg_bot/main.go | 7 +++++++ tg_bot/screens/channel_parser.go | 8 ++++---- 8 files changed, 37 insertions(+), 40 deletions(-) diff --git a/.env.example b/.env.example index 91998cf..3e860ef 100644 --- a/.env.example +++ b/.env.example @@ -2,6 +2,7 @@ APP__ORIGINS=["http://locahost:3000"] DB__URL=postgres://user:password@localhost:5432/tgex LOGGER__APP_NAME=tgex-backend +LOGGER__APP_VERSION=0.0.0 LOGGER__PRETTY_CONSOLE=true LOGGER__LEVEL=info diff --git a/Makefile b/Makefile index f9bad21..268b616 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ run: uv run uvicorn src.main:app up: - GIT_COMMIT=$$(git rev-parse --short HEAD) docker compose up --build -d --force-recreate + GIT_COMMIT_COUNT=$$(git rev-list --count HEAD 2>/dev/null) docker compose up --build -d --force-recreate docker compose logs -f migrate-init: @@ -35,5 +35,3 @@ clean: @docker network rm $$(docker network ls -q | grep -vE 'bridge|host|none') 2>/dev/null || true @docker system prune --volumes -f @echo "${GREEN}Docker очистка завершена. Базовые образы из registry сохранены.${NC}" - - diff --git a/docker-compose.yml.example b/docker-compose.yml.example index 63467f5..2327201 100644 --- a/docker-compose.yml.example +++ b/docker-compose.yml.example @@ -33,6 +33,7 @@ services: DB__URL: "postgres://user:password@postgres:5432/tgex" S3__ENDPOINT_URL: "http://minio:9000" PARSER__URL: "http://tg_parser:8080" + GIT_COMMIT_COUNT: "${GIT_COMMIT_COUNT:-}" env_file: - .env restart: unless-stopped diff --git a/shared/logger/logger.py b/shared/logger/logger.py index 2523a22..0f74279 100644 --- a/shared/logger/logger.py +++ b/shared/logger/logger.py @@ -1,30 +1,39 @@ import logging import os -import subprocess import sys import pydantic -from pydantic.fields import Field from .console_formatter import ColoredConsoleFormatter from .json_formatter import JSONFormatter -def get_git_commit() -> str: - if commit := os.getenv('GIT_COMMIT'): - return commit - try: - return subprocess.check_output(['git', 'rev-parse', '--short', 'HEAD'], text=True).strip() - except Exception: - return 'unknown' - - class LoggerConfig(pydantic.BaseModel): APP_NAME: str - APP_VERSION: str = Field(default_factory=get_git_commit) + APP_VERSION: str LEVEL: int = logging.INFO PRETTY_CONSOLE: bool = False + @pydantic.field_validator('APP_VERSION') + @classmethod + def normalize_app_version(cls, v: str) -> str: + parts = v.removeprefix('v').split('.') + if len(parts) < 2 or any(not part.isdigit() for part in parts): + raise ValueError('APP_VERSION must look like 1.2.0') + commit_count = _get_git_commit_count() + if not commit_count: + return f'v{".".join(parts)}' + parts[-1] = commit_count + return f'v{".".join(parts)}' + + +def _get_git_commit_count() -> str | None: + commit_count = os.getenv('GIT_COMMIT_COUNT') + if commit_count is None: + return None + normalized = commit_count.strip() + return normalized or None + def init(config: LoggerConfig) -> None: root_logger = logging.getLogger() diff --git a/src/main.py b/src/main.py index 5847929..1de6fce 100644 --- a/src/main.py +++ b/src/main.py @@ -56,9 +56,8 @@ fetch_placement_post_worker = FetchPlacementPostWorker(config=WorkerConfig(INTER app = FastAPI( lifespan=lifespan, version=settings.logger.APP_VERSION, - swagger_ui_parameters={ - 'defaultModelsExpandDepth': 0, # Сворачиваем Schemas - }, + # Сворачиваем Schemas + swagger_ui_parameters={'defaultModelsExpandDepth': 0}, ) app.add_middleware( diff --git a/tg_bot/bot/bot.go b/tg_bot/bot/bot.go index 7385598..4965fc9 100644 --- a/tg_bot/bot/bot.go +++ b/tg_bot/bot/bot.go @@ -20,28 +20,11 @@ type exec struct { } var botUsername string -var botUsernameOnce sync.Once func SetBotUsername(username string) { botUsername = username } func BotUsername() string { return botUsername } -func ensureBotUsername(api echotron.API) { - if botUsername != "" { - return - } - botUsernameOnce.Do(func() { - me, err := api.GetMe() - if err != nil { - log.Error().Err(err).Msg("GetMe failed, bot username will be empty") - return - } - if me.Result != nil { - botUsername = me.Result.Username - } - }) -} - type lastRender struct { textHash string kbHash string @@ -77,7 +60,6 @@ func NewBot(chatID int64, token string, commandRouter func(string) State, backen } api := echotron.NewAPI(token) - ensureBotUsername(api) return &Bot{ ChatID: chatID, diff --git a/tg_bot/main.go b/tg_bot/main.go index 905ccb6..e35674e 100644 --- a/tg_bot/main.go +++ b/tg_bot/main.go @@ -34,6 +34,13 @@ func main() { // Регистрируем команды бота в Telegram api := echotron.NewAPI(botToken) + if me, err := api.GetMe(); err != nil { + log.Error().Err(err).Msg("Failed to get bot profile") + } else if me.Result != nil { + bot.SetBotUsername(me.Result.Username) + log.Info().Str("username", me.Result.Username).Msg("Telegram bot authorized") + } + if _, err := api.SetMyCommands(nil, botCommands...); err != nil { log.Error().Err(err).Msg("Failed to set bot commands") } else { diff --git a/tg_bot/screens/channel_parser.go b/tg_bot/screens/channel_parser.go index b7ccca3..a76525f 100644 --- a/tg_bot/screens/channel_parser.go +++ b/tg_bot/screens/channel_parser.go @@ -18,18 +18,18 @@ type ChannelInput struct { // Парсеры для разных форматов var ( - // Username без @: 5-32 символа, начинается с буквы - channelUsernameRe = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]{4,31}$`) + // Username без @: 4-32 символа, начинается с буквы + channelUsernameRe = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]{3,31}$`) // Invite link: t.me/+xxxxx или t.me/joinchat/xxxxx inviteLinkRe = regexp.MustCompile(`^t\.me/\+[a-zA-Z0-9_-]+$`) inviteLinkJoinRe = regexp.MustCompile(`^t\.me/joinchat/[a-zA-Z0-9_-]+$`) // TGStat: tgstat.ru/channel_name или tgstat.ru/channel/@username - tgstatRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?tgstat\.ru/(?:channel/)?@?([A-Za-z][A-Za-z0-9_.]{4,31})`) + tgstatRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?tgstat\.ru/(?:channel/)?@?([A-Za-z][A-Za-z0-9_.]{3,31})`) // Telemetr: telemetr.io/channel/username или telemetr.io/channel/@username - telemetrRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?telemetr\.io/(?:channel|channels)/@?([A-Za-z][A-Za-z0-9_.]{4,31})`) + telemetrRe = regexp.MustCompile(`^(?:https?://)?(?:www\.)?telemetr\.io/(?:channel|channels)/@?([A-Za-z][A-Za-z0-9_.]{3,31})`) ) // ParseChannelInput парсит ввод пользователя и определяет тип канала