fix: исправлено форматирование блоков кода при разбивке длинных сообщений

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
mirivlad 2026-02-27 18:11:46 +08:00
parent fbf0edc60a
commit 0a8801afec
1 changed files with 57 additions and 37 deletions

View File

@ -58,13 +58,15 @@ def find_code_blocks(text: str) -> List[Tuple[int, int]]:
return blocks return blocks
def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple[str, bool]]: def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple[str, bool, bool, bool]]:
""" """
Умно разбить длинный текст на сообщения <= max_length символов. Умно разбить длинный текст на сообщения <= max_length символов.
Возвращает список кортежей (text, has_markdown): Возвращает список кортежей (text, has_code, code_opened, code_closed):
- text: текст сообщения - text: текст сообщения
- has_markdown: True если сообщение содержит блоки кода - has_code: True если сообщение содержит часть блока кода
- code_opened: True если блок кода ОТКРЫТ в этом сообщении (есть открывающий ```)
- code_closed: True если блок кода ЗАКРЫТ в этом сообщении (есть закрывающий ```)
Алгоритм: Алгоритм:
1. Находим все блоки кода 1. Находим все блоки кода
@ -72,7 +74,13 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
3. Если блок кода не влезает отправляем отдельным сообщением без Markdown 3. Если блок кода не влезает отправляем отдельным сообщением без Markdown
""" """
if len(text) <= max_length: if len(text) <= max_length:
return [(text, '```' in text)] has_code = '```' in text
# Считаем количество ``` для определения открыт/закрыт
backtick_count = text.count('```')
# Если 1 или нечётное количество — блок открыт но не закрыт (или наоборот)
code_opened = backtick_count >= 1 and (backtick_count % 2 == 1)
code_closed = backtick_count >= 2 and (backtick_count % 2 == 0)
return [(text, has_code, code_opened, code_closed)]
parts = [] parts = []
code_blocks = find_code_blocks(text) code_blocks = find_code_blocks(text)
@ -81,6 +89,8 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
pos = 0 pos = 0
current = "" current = ""
current_has_code = False current_has_code = False
current_code_opened = False
current_code_closed = False
for block_start, block_end in code_blocks: for block_start, block_end in code_blocks:
# Текст до блока кода # Текст до блока кода
@ -93,9 +103,11 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
for line in before_code.split('\n'): for line in before_code.split('\n'):
if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER: if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER:
if current: if current:
parts.append((current, current_has_code)) parts.append((current, current_has_code, current_code_opened, current_code_closed))
current = line current = line
current_has_code = False current_has_code = False
current_code_opened = False
current_code_closed = False
else: else:
current += ('\n' if current else '') + line current += ('\n' if current else '') + line
@ -103,24 +115,33 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
if len(current) + len(code_block) + 1 > max_length - RESERVED_FOR_HEADER: if len(current) + len(code_block) + 1 > max_length - RESERVED_FOR_HEADER:
# Отправляем что накопилось # Отправляем что накопилось
if current: if current:
parts.append((current, current_has_code)) parts.append((current, current_has_code, current_code_opened, current_code_closed))
# Если блок кода слишком длинный — режем его на части # Если блок кода слишком длинный — режем его на части
if len(code_block) > max_length - RESERVED_FOR_HEADER: if len(code_block) > max_length - RESERVED_FOR_HEADER:
# Отправляем блок кода частями без Markdown # Отправляем блок кода частями без Markdown
for i in range(0, len(code_block), max_length - RESERVED_FOR_HEADER): for i in range(0, len(code_block), max_length - RESERVED_FOR_HEADER):
chunk = code_block[i:i + max_length - RESERVED_FOR_HEADER] chunk = code_block[i:i + max_length - RESERVED_FOR_HEADER]
parts.append((chunk, False)) # Без Markdown! # Первая часть имеет открывающий ```, последняя — закрывающий
first_chunk = (i == 0)
last_chunk = (i + max_length - RESERVED_FOR_HEADER >= len(code_block))
parts.append((chunk, True, first_chunk, last_chunk))
current = "" current = ""
current_has_code = False current_has_code = False
current_code_opened = False
current_code_closed = False
else: else:
# Блок влезает в следующее сообщение # Блок влезает в следующее сообщение
current = code_block current = code_block
current_has_code = True current_has_code = True
current_code_opened = True # Блок начинается с ```
current_code_closed = True # Блок заканчивается на ```
else: else:
# Блок кода влезает в текущее сообщение # Блок кода влезает в текущее сообщение
current += ('\n' if current else '') + code_block current += ('\n' if current else '') + code_block
current_has_code = True current_has_code = True
current_code_opened = True # Блок содержит открывающий ```
current_code_closed = True # Блок содержит закрывающий ```
pos = block_end pos = block_end
@ -130,14 +151,16 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
for line in remaining.split('\n'): for line in remaining.split('\n'):
if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER: if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER:
if current: if current:
parts.append((current, current_has_code)) parts.append((current, current_has_code, current_code_opened, current_code_closed))
current = line current = line
current_has_code = False current_has_code = False
current_code_opened = False
current_code_closed = False
else: else:
current += ('\n' if current else '') + line current += ('\n' if current else '') + line
if current: if current:
parts.append((current, current_has_code)) parts.append((current, current_has_code, current_code_opened, current_code_closed))
return parts return parts
@ -150,9 +173,7 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
- Блоки кода не разрываются между сообщениями - Блоки кода не разрываются между сообщениями
- Если блок кода не влезает отправляется без Markdown - Если блок кода не влезает отправляется без Markdown
- Нумерация (1/N), (2/N) только если сообщений > 1 - Нумерация (1/N), (2/N) только если сообщений > 1
- Если сообщение содержит блок кода, он закрывается в конце и открывается в начале следующего - КАЖДЫЕ pause_every сообщений пауза с кнопками "Продолжить" / "Отменить"
- КАЖДЫЕ pause_after сообщений пауза с кнопками "Продолжить" / "Отменить"
- После нажатия кнопки они удаляются
Args: Args:
update: Telegram update update: Telegram update
@ -168,7 +189,7 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
messages_sent = 0 messages_sent = 0
wait_msg = None wait_msg = None
for i, (part, has_code) in enumerate(parts): for i, (part, has_code, code_opened, code_closed) in enumerate(parts):
# Добавляем номер части если их несколько # Добавляем номер части если их несколько
if total > 1: if total > 1:
header = f"({i+1}/{total}) " header = f"({i+1}/{total}) "
@ -176,20 +197,19 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
part = header + part part = header + part
# Определяем parse_mode для этого сообщения # Определяем parse_mode для этого сообщения
if parse_mode and has_code: actual_parse_mode = parse_mode if parse_mode and has_code else None
actual_parse_mode = parse_mode
elif parse_mode and not has_code:
actual_parse_mode = parse_mode
else:
actual_parse_mode = None
# Если это не первое сообщение и предыдущее имело блок кода — открываем блок # Логика работы с блоками кода между сообщениями:
# Если это не последнее сообщение и текущее имеет блок кода — закрываем блок # - Если предыдущее сообщение не закрыло блок (code_closed=False) и имело код — открываем в этом
if total > 1 and actual_parse_mode: # - Если текущее сообщение не закрывает блок (code_closed=False) и следующее имеет код — не закрываем
if i > 0 and parts[i-1][1]: # Предыдущее имело блок кода if total > 1 and actual_parse_mode and has_code:
# Проверяем нужно ли открыть блок в начале этого сообщения
if i > 0 and not parts[i-1][3]: # предыдущее не закрыло блок (parts[i-1][3] = code_closed)
part = "```\n" + part part = "```\n" + part
if i < total - 1 and has_code: # Следующее будет иметь блок кода # Проверяем нужно ли закрыть блок в конце этого сообщения
# Закрываем только если следующее сообщение НЕ имеет кода
if i < total - 1 and not code_closed and not parts[i+1][1]: # следующее не имеет кода
part = part + "\n```" part = part + "\n```"
try: try: