Приветствие снова читается одним блоком: фото с короткой подписью и одно сообщение

Первая строка welcome.text уходит подписью к фото, остальное вместе с text2 и text3 — одним сообщением с кнопкой. Так текст не разбит на три части и не упирается в лимит подписи к фото (1024 символа). В редакторе welcome.text появилась подсказка, что первая строка — подпись. Если понадобится больше гибкости, следующим шагом можно сделать последовательность сообщений с картинками.
This commit is contained in:
MpakobecEx 2026-10-25 01:28:56 +05:00
parent 8ae4e8bb6c
commit 34b6574e43
7 changed files with 96 additions and 13 deletions

View File

@ -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 апреля по православную Пасху**

View File

@ -36,6 +36,12 @@
<p class="mt-1 text-xs text-slate-500">
Лимит Telegram — {{ max_length }} символов. Теги подставляет рендерер бота; неизвестный тег — ошибка.
</p>
{% if template.key == 'welcome.text' %}
<p class="mt-2 rounded border border-sky-200 bg-sky-50 p-2 text-xs text-sky-800">
Первая строка этого шаблона уходит подписью к фото, остальное — одним сообщением
вместе с <span class="font-mono">welcome.text2</span> и <span class="font-mono">welcome.text3</span>.
</p>
{% endif %}
</div>
<div class="flex items-center gap-3">

View File

@ -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))

View File

@ -36,3 +36,18 @@ MSG_WELCOME_TEXT3 = """❗️<b>По игре</b>:
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

View File

@ -7,4 +7,4 @@
from __future__ import annotations
__version__ = "0.4.14"
__version__ = "0.4.15"

View File

@ -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"

View File

@ -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() == ("", "")