From 34b6574e434a3f3b3092eed7938b18affe0b330e Mon Sep 17 00:00:00 2001 From: MpakobecEx Date: Sun, 25 Oct 2026 01:28:56 +0500 Subject: [PATCH] =?UTF-8?q?=D0=9F=D1=80=D0=B8=D0=B2=D0=B5=D1=82=D1=81?= =?UTF-8?q?=D1=82=D0=B2=D0=B8=D0=B5=20=D1=81=D0=BD=D0=BE=D0=B2=D0=B0=20?= =?UTF-8?q?=D1=87=D0=B8=D1=82=D0=B0=D0=B5=D1=82=D1=81=D1=8F=20=D0=BE=D0=B4?= =?UTF-8?q?=D0=BD=D0=B8=D0=BC=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=BE=D0=BC:=20?= =?UTF-8?q?=D1=84=D0=BE=D1=82=D0=BE=20=D1=81=20=D0=BA=D0=BE=D1=80=D0=BE?= =?UTF-8?q?=D1=82=D0=BA=D0=BE=D0=B9=20=D0=BF=D0=BE=D0=B4=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8C=D1=8E=20=D0=B8=20=D0=BE=D0=B4=D0=BD=D0=BE=20=D1=81=D0=BE?= =?UTF-8?q?=D0=BE=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Первая строка welcome.text уходит подписью к фото, остальное вместе с text2 и text3 — одним сообщением с кнопкой. Так текст не разбит на три части и не упирается в лимит подписи к фото (1024 символа). В редакторе welcome.text появилась подсказка, что первая строка — подпись. Если понадобится больше гибкости, следующим шагом можно сделать последовательность сообщений с картинками. --- README.md | 9 ++++++- admin/templates/messages/edit.html | 6 +++++ bot/handlers/member_events.py | 41 ++++++++++++++++++++++-------- common/texts/welcome.py | 15 +++++++++++ common/version.py | 2 +- tests/test_version.py | 2 +- tests/test_welcome_split.py | 34 +++++++++++++++++++++++++ 7 files changed, 96 insertions(+), 13 deletions(-) create mode 100644 tests/test_welcome_split.py diff --git a/README.md b/README.md index b06c0f9..053b213 100644 --- a/README.md +++ b/README.md @@ -151,7 +151,7 @@ make backup E=test # на домашнем сервере: ./scripts/clickho ## Версионирование -- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.4.14`. +- Единый источник версии — `common/version.py` (`__version__`), текущая — `0.4.15`. - Схема — SemVer-подобная (`MAJOR.MINOR.PATCH`); git-тег = `v` + версия (`v0.1`). - Docker-образы тегируются версией (Блок 2), события ClickHouse получают `bot_version` (Блок 3). - Выпуск: изменить `common/version.py` → коммит → аннотированный тег `vX.Y` → `git push --tags`. @@ -180,6 +180,13 @@ git tag -a vX.Y -m "Release vX.Y" git push origin master --tags ``` +## Приветствие новых участников + +Бот отправляет фото с короткой персональной подписью (первая строка шаблона `welcome.text`), +а затем одно сообщение со всем остальным текстом (`welcome.text` без первой строки + +`welcome.text2` + `welcome.text3`) и кнопкой на канал. Так приветствие остаётся одним +смысловым блоком, но не упирается в лимит подписи к фото (1024 символа). + ## Пасхальное событие «Яички» `/яички` работает только в сезон: по умолчанию с **1 апреля по православную Пасху** diff --git a/admin/templates/messages/edit.html b/admin/templates/messages/edit.html index ad22528..9d006cb 100644 --- a/admin/templates/messages/edit.html +++ b/admin/templates/messages/edit.html @@ -36,6 +36,12 @@

Лимит Telegram — {{ max_length }} символов. Теги подставляет рендерер бота; неизвестный тег — ошибка.

+ {% if template.key == 'welcome.text' %} +

+ Первая строка этого шаблона уходит подписью к фото, остальное — одним сообщением + вместе с welcome.text2 и welcome.text3. +

+ {% endif %}
diff --git a/bot/handlers/member_events.py b/bot/handlers/member_events.py index 8caf68b..8e8916e 100644 --- a/bot/handlers/member_events.py +++ b/bot/handlers/member_events.py @@ -52,18 +52,39 @@ async def welcome_handler(event: ChatMemberUpdated) -> None: builder = InlineKeyboardBuilder() builder.button(text=welcome.MSG_WELCOME_BUTTON_CAPTION, url=welcome.MSG_WELCOME_BUTTON_URL) + markup = builder.as_markup() - await event.bot.send_photo( - chat_id=event.chat.id, - photo=FSInputFile("images/welcome_image.jpg"), - caption=render(await get_text("welcome.text"), {"username": user.username}), + caption, body = welcome.split_welcome( + await get_text("welcome.text"), + await get_text("welcome.text2"), + await get_text("welcome.text3"), ) - await event.bot.send_message( - chat_id=event.chat.id, - text=await get_text("welcome.text2"), - reply_markup=builder.as_markup(), - ) - await event.bot.send_message(chat_id=event.chat.id, text=await get_text("welcome.text3")) + caption = render(caption, {"username": user.username}) + body = render(body, {"username": user.username}) + + # Подпись к фото ограничена 1024 символами; если не влезла — уйдёт в тело. + if len(caption) > 1024: + body = caption + (f"\n\n{body}" if body else "") + caption = "" + + if body: + await event.bot.send_photo( + chat_id=event.chat.id, + photo=FSInputFile("images/welcome_image.jpg"), + caption=caption or None, + ) + await event.bot.send_message( + chat_id=event.chat.id, + text=body, + reply_markup=markup, + ) + else: + await event.bot.send_photo( + chat_id=event.chat.id, + photo=FSInputFile("images/welcome_image.jpg"), + caption=caption or None, + reply_markup=markup, + ) @router.chat_member(ChatMemberUpdatedFilter(member_status_changed=LEAVE_TRANSITION)) diff --git a/common/texts/welcome.py b/common/texts/welcome.py index eb4ef1e..e875655 100644 --- a/common/texts/welcome.py +++ b/common/texts/welcome.py @@ -36,3 +36,18 @@ MSG_WELCOME_TEXT3 = """❗️По игре: MSG_WELCOME_BUTTON_CAPTION = "Канал" MSG_WELCOME_BUTTON_URL = "https://t.me/+DQk8MyN2y6QzNTM6" + + +def split_welcome(*parts: str) -> tuple[str, str]: + """Разделить приветствие на подпись к фото и одно общее сообщение. + + Первая строка первого шаблона становится подписью (персональное приветствие), + всё остальное (включая ``text2``/``text3``) — одним сообщением по порядку. + """ + if not parts: + return "", "" + + caption, _, rest = parts[0].partition("\n") + body_parts = [rest.rstrip(), *parts[1:]] + body = "\n\n".join(part.strip() for part in body_parts if part and part.strip()) + return caption.strip(), body diff --git a/common/version.py b/common/version.py index 621672c..7510ebe 100644 --- a/common/version.py +++ b/common/version.py @@ -7,4 +7,4 @@ from __future__ import annotations -__version__ = "0.4.14" +__version__ = "0.4.15" diff --git a/tests/test_version.py b/tests/test_version.py index 9491810..c50f862 100644 --- a/tests/test_version.py +++ b/tests/test_version.py @@ -7,4 +7,4 @@ from common import __version__ def test_version_is_set(): assert __version__ - assert __version__ == "0.4.14" + assert __version__ == "0.4.15" diff --git a/tests/test_welcome_split.py b/tests/test_welcome_split.py new file mode 100644 index 0000000..7b520a3 --- /dev/null +++ b/tests/test_welcome_split.py @@ -0,0 +1,34 @@ +"""Тесты сборки приветствия: подпись к фото + одно общее сообщение.""" + +from __future__ import annotations + +from common.texts.welcome import split_welcome + + +def test_first_line_is_caption_rest_joins_body() -> None: + caption, body = split_welcome( + "💠{username}, добро пожаловать в Dispel Evil!\n\n❗️Правила клана\n1. …", + "Часть 2", + "Часть 3", + ) + + assert caption == "💠{username}, добро пожаловать в Dispel Evil!" + assert body == "❗️Правила клана\n1. …\n\nЧасть 2\n\nЧасть 3" + + +def test_without_newline_all_text_is_caption() -> None: + caption, body = split_welcome("Привет", "Второй", "Третий") + + assert caption == "Привет" + assert body == "Второй\n\nТретий" + + +def test_empty_parts_are_skipped() -> None: + caption, body = split_welcome("Привет\n\nТело", "", " ") + + assert caption == "Привет" + assert body == "Тело" + + +def test_no_parts() -> None: + assert split_welcome() == ("", "")