fix: правильная разбивка длинных блоков кода на части

Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
mirivlad 2026-02-27 18:30:10 +08:00
parent 2557440a39
commit 28e671af9c
1 changed files with 71 additions and 42 deletions

View File

@ -73,13 +73,18 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
2. Стараемся не разрывать блоки кода
3. Если блок кода не влезает отправляем отдельным сообщением без Markdown
"""
def calc_code_flags(txt: str) -> Tuple[bool, bool, bool]:
"""Вычислить флаги code для данного текста."""
has_code = '```' in txt
backtick_count = txt.count('```')
# code_opened: есть хотя бы один ```
code_opened = backtick_count >= 1
# code_closed: есть хотя бы 2 ``` (пара) или нечётное количество (открыт и закрыт в одном)
code_closed = backtick_count >= 2
return has_code, code_opened, code_closed
if len(text) <= max_length:
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)
has_code, code_opened, code_closed = calc_code_flags(text)
return [(text, has_code, code_opened, code_closed)]
parts = []
@ -88,9 +93,6 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
# Текущая позиция в тексте
pos = 0
current = ""
current_has_code = False
current_code_opened = False
current_code_closed = False
for block_start, block_end in code_blocks:
# Текст до блока кода
@ -103,11 +105,9 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
for line in before_code.split('\n'):
if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER:
if current:
parts.append((current, current_has_code, current_code_opened, current_code_closed))
has_code, code_opened, code_closed = calc_code_flags(current)
parts.append((current, has_code, code_opened, code_closed))
current = line
current_has_code = False
current_code_opened = False
current_code_closed = False
else:
current += ('\n' if current else '') + line
@ -115,33 +115,51 @@ 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 current:
parts.append((current, current_has_code, current_code_opened, current_code_closed))
has_code, code_opened, code_closed = calc_code_flags(current)
parts.append((current, has_code, code_opened, code_closed))
# Если блок кода слишком длинный — режем его на части
if len(code_block) > max_length - RESERVED_FOR_HEADER:
# Отправляем блок кода частями без Markdown
for i in range(0, len(code_block), max_length - RESERVED_FOR_HEADER):
chunk = code_block[i:i + max_length - RESERVED_FOR_HEADER]
# Первая часть имеет открывающий ```, последняя — закрывающий
first_chunk = (i == 0)
last_chunk = (i + max_length - RESERVED_FOR_HEADER >= len(code_block))
parts.append((chunk, True, first_chunk, last_chunk))
# Блок кода включает ```, нужно резать правильно
# block_start и block_end включают оба ```
# Находим позицию открывающего и закрывающего ```
open_end = code_block.find('```', 3) + 3 # Конец открывающего ```
close_start = code_block.rfind('```') # Начало закрывающего ```
# Содержимое блока без ```
code_content = code_block[3:close_start] # Только содержимое
# Режем содержимое на части
content_max_len = max_length - RESERVED_FOR_HEADER - 3 # -3 для ```
chunks = []
for i in range(0, len(code_content), content_max_len):
chunks.append(code_content[i:i + content_max_len])
# Создаём части с правильными ```
for i, chunk in enumerate(chunks):
if i == 0 and len(chunks) == 1:
# Один чанк — полный блок
full_block = f"```{chunk}```"
parts.append((full_block, True, True, True))
elif i == 0:
# Первая часть — только открывающий ```
first_part = f"```{chunk}"
parts.append((first_part, True, True, False))
elif i == len(chunks) - 1:
# Последняя часть — только закрывающий ```
last_part = f"{chunk}```"
parts.append((last_part, True, False, True))
else:
# Средняя часть — без ```
parts.append((chunk, False, False, False))
current = ""
current_has_code = False
current_code_opened = False
current_code_closed = False
else:
# Блок влезает в следующее сообщение
current = code_block
current_has_code = True
current_code_opened = True # Блок начинается с ```
current_code_closed = True # Блок заканчивается на ```
else:
# Блок кода влезает в текущее сообщение
current += ('\n' if current else '') + code_block
current_has_code = True
current_code_opened = True # Блок содержит открывающий ```
current_code_closed = True # Блок содержит закрывающий ```
pos = block_end
@ -151,16 +169,15 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
for line in remaining.split('\n'):
if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER:
if current:
parts.append((current, current_has_code, current_code_opened, current_code_closed))
has_code, code_opened, code_closed = calc_code_flags(current)
parts.append((current, has_code, code_opened, code_closed))
current = line
current_has_code = False
current_code_opened = False
current_code_closed = False
else:
current += ('\n' if current else '') + line
if current:
parts.append((current, current_has_code, current_code_opened, current_code_closed))
has_code, code_opened, code_closed = calc_code_flags(current)
parts.append((current, has_code, code_opened, code_closed))
return parts
@ -191,19 +208,31 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
for i, (part, has_code, code_opened, code_closed) in enumerate(parts):
# Определяем parse_mode для этого сообщения
actual_parse_mode = parse_mode if parse_mode and has_code else None
actual_parse_mode = parse_mode if parse_mode and (has_code or code_opened or code_closed) else None
# Логика работы с блоками кода между сообщениями:
# - Если предыдущее сообщение не закрыло блок — открываем в этом
# - Если текущее не закрывает блок и следующее не имеет кода — закрываем
if total > 1 and actual_parse_mode and has_code:
need_prepend = False
need_append = False
if total > 1 and actual_parse_mode:
# Проверяем нужно ли открыть блок в начале этого сообщения
if i > 0 and not parts[i-1][3]: # предыдущее не закрыло блок (parts[i-1][3] = code_closed)
part = "```\n" + part
need_prepend = True
# Проверяем нужно ли закрыть блок в конце этого сообщения
if i < total - 1 and not code_closed and not parts[i+1][1]: # следующее не имеет кода
part = part + "\n```"
# Если текущее не закрыло блок и следующее не имеет кода/не закроет
if i < total - 1 and not code_closed:
next_has = parts[i+1][1]
next_opened = parts[i+1][2]
# Закрываем если следующее не имеет кода или не открывает свой блок
if not next_has:
need_append = True
# Применяем модификации
if need_prepend:
part = "```\n" + part
if need_append:
part = part + "\n```"
# Добавляем номер части ПОСЛЕ работы с блоками кода
if total > 1: