From 4b69ee03107d90f69368412164d2c9654d6ff223 Mon Sep 17 00:00:00 2001 From: mirivlad Date: Fri, 27 Feb 2026 19:06:40 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=D0=BF=D0=B5=D1=80=D0=B5=D0=BF=D0=B8?= =?UTF-8?q?=D1=81=D0=B0=D0=BD=20split=5Fmessage=20=D1=81=20=D0=BE=D1=82?= =?UTF-8?q?=D1=81=D0=BB=D0=B5=D0=B6=D0=B8=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5?= =?UTF-8?q?=D0=BC=20=D1=81=D0=BE=D1=81=D1=82=D0=BE=D1=8F=D0=BD=D0=B8=D1=8F?= =?UTF-8?q?=20=D0=B1=D0=BB=D0=BE=D0=BA=D0=B0=20=D0=BA=D0=BE=D0=B4=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Qwen-Coder --- bot/utils/formatters.py | 162 +++++++++++++++------------------------- 1 file changed, 59 insertions(+), 103 deletions(-) diff --git a/bot/utils/formatters.py b/bot/utils/formatters.py index 8d78fcf..d0425dc 100644 --- a/bot/utils/formatters.py +++ b/bot/utils/formatters.py @@ -69,18 +69,16 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple - code_closed: True если блок кода ЗАКРЫТ в этом сообщении (есть закрывающий ```) Алгоритм: - 1. Находим все блоки кода - 2. Стараемся не разрывать блоки кода - 3. Если блок кода не влезает — отправляем отдельным сообщением без Markdown + 1. Разбиваем текст на строки + 2. Накапливаем строки до достижения лимита + 3. Отслеживаем состояние блока кода (внутри/снаружи) """ 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 + code_closed = backtick_count >= 2 or (backtick_count == 1 and txt.rstrip().endswith('```')) return has_code, code_opened, code_closed if len(text) <= max_length: @@ -88,95 +86,63 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple return [(text, has_code, code_opened, code_closed)] parts = [] - code_blocks = find_code_blocks(text) - - # Текущая позиция в тексте - pos = 0 + lines = text.split('\n') current = "" + in_code_block = False # Состояние: внутри блока кода или нет + code_block_started_in_current = False # Блок кода был открыт в текущей части - for block_start, block_end in code_blocks: - # Текст до блока кода - before_code = text[pos:block_start] - - # Сам блок кода (включая ```) - code_block = text[block_start:block_end] - - # Обрабатываем текст до блока - for line in before_code.split('\n'): - if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER: - if current: - has_code, code_opened, code_closed = calc_code_flags(current) - parts.append((current, has_code, code_opened, code_closed)) - current = line + for line in lines: + # Проверяем, содержит ли строка ``` + backticks_in_line = line.count('```') + + # Если строка содержит нечётное количество ```, она меняет состояние + if backticks_in_line % 2 == 1: + # Эта строка содержит ``` который меняет состояние + if in_code_block: + # Были внутри блока — эта строка закрывает его + test_line = current + ('\n' if current else '') + line + if len(test_line) > max_length - RESERVED_FOR_HEADER: + # Сначала отправляем текущее (блок не закрыт в этой части!) + if current: + has_code, code_opened, _ = calc_code_flags(current) + # code_closed=False потому что блок продолжится + parts.append((current, has_code, code_opened, False)) + current = line + code_block_started_in_current = False + else: + current = test_line + code_block_started_in_current = True + in_code_block = False else: - current += ('\n' if current else '') + line - - # Проверяем влезет ли блок кода - if len(current) + len(code_block) + 1 > max_length - RESERVED_FOR_HEADER: - # Отправляем что накопилось - if current: - 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: - # Блок кода включает ```, нужно резать правильно - # 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 = "" - else: - # Блок влезает в следующее сообщение - current = code_block + # Были снаружи — эта строка открывает блок + test_line = current + ('\n' if current else '') + line + if len(test_line) > max_length - RESERVED_FOR_HEADER: + if current: + has_code, code_opened, code_closed = calc_code_flags(current) + parts.append((current, has_code, code_opened, code_closed)) + current = line + code_block_started_in_current = True + else: + current = test_line + code_block_started_in_current = True + in_code_block = True else: - # Блок кода влезает в текущее сообщение - current += ('\n' if current else '') + code_block - - pos = block_end - - # Обрабатываем остаток текста после последнего блока - if pos < len(text): - remaining = text[pos:] - for line in remaining.split('\n'): - if len(current) + len(line) + 1 > max_length - RESERVED_FOR_HEADER: + # Строка не меняет состояние + test_line = current + ('\n' if current else '') + line + if len(test_line) > max_length - RESERVED_FOR_HEADER: if current: - has_code, code_opened, code_closed = calc_code_flags(current) + has_code, code_opened, _ = calc_code_flags(current) + # Если мы внутри блока кода — block не закрыт + code_closed = not in_code_block parts.append((current, has_code, code_opened, code_closed)) current = line + code_block_started_in_current = in_code_block else: - current += ('\n' if current else '') + line + current = test_line if current: - has_code, code_opened, code_closed = calc_code_flags(current) + has_code, code_opened, _ = calc_code_flags(current) + code_closed = not in_code_block parts.append((current, has_code, code_opened, code_closed)) return parts @@ -208,31 +174,21 @@ 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 or code_opened or code_closed) else None + actual_parse_mode = parse_mode if parse_mode and has_code else None # Логика работы с блоками кода между сообщениями: - 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) - need_prepend = True + if i > 0 and not parts[i-1][3]: # предыдущее не закрыло блок + part = "```\n" + part # Проверяем нужно ли закрыть блок в конце этого сообщения - # Если текущее не закрыло блок и следующее не имеет кода/не закроет 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 not parts[i+1][2]: # следующее не открывает блок + part = part + "\n```" # Добавляем номер части ПОСЛЕ работы с блоками кода if total > 1: