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. Стараемся не разрывать блоки кода 2. Стараемся не разрывать блоки кода
3. Если блок кода не влезает отправляем отдельным сообщением без Markdown 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: if len(text) <= max_length:
has_code = '```' in text has_code, code_opened, code_closed = calc_code_flags(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)] return [(text, has_code, code_opened, code_closed)]
parts = [] parts = []
@ -88,9 +93,6 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
# Текущая позиция в тексте # Текущая позиция в тексте
pos = 0 pos = 0
current = "" current = ""
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:
# Текст до блока кода # Текст до блока кода
@ -103,11 +105,9 @@ 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, 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 = line
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
@ -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 len(current) + len(code_block) + 1 > max_length - RESERVED_FOR_HEADER:
# Отправляем что накопилось # Отправляем что накопилось
if current: 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: if len(code_block) > max_length - RESERVED_FOR_HEADER:
# Отправляем блок кода частями без Markdown # Блок кода включает ```, нужно резать правильно
for i in range(0, len(code_block), max_length - RESERVED_FOR_HEADER): # block_start и block_end включают оба ```
chunk = code_block[i:i + max_length - RESERVED_FOR_HEADER] # Находим позицию открывающего и закрывающего ```
# Первая часть имеет открывающий ```, последняя — закрывающий open_end = code_block.find('```', 3) + 3 # Конец открывающего ```
first_chunk = (i == 0) close_start = code_block.rfind('```') # Начало закрывающего ```
last_chunk = (i + max_length - RESERVED_FOR_HEADER >= len(code_block))
parts.append((chunk, True, first_chunk, last_chunk)) # Содержимое блока без ```
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 = ""
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_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_code_opened = True # Блок содержит открывающий ```
current_code_closed = True # Блок содержит закрывающий ```
pos = block_end 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'): 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, 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 = line
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, 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 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): for i, (part, has_code, code_opened, code_closed) in enumerate(parts):
# Определяем parse_mode для этого сообщения # Определяем 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
# Логика работы с блоками кода между сообщениями: # Логика работы с блоками кода между сообщениями:
# - Если предыдущее сообщение не закрыло блок — открываем в этом need_prepend = False
# - Если текущее не закрывает блок и следующее не имеет кода — закрываем need_append = False
if total > 1 and actual_parse_mode and has_code:
if total > 1 and actual_parse_mode:
# Проверяем нужно ли открыть блок в начале этого сообщения # Проверяем нужно ли открыть блок в начале этого сообщения
if i > 0 and not parts[i-1][3]: # предыдущее не закрыло блок (parts[i-1][3] = code_closed) 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: if total > 1: