fix: исправлено форматирование блоков кода при разбивке длинных сообщений
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
fbf0edc60a
commit
0a8801afec
|
|
@ -58,87 +58,110 @@ def find_code_blocks(text: str) -> List[Tuple[int, int]]:
|
|||
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 символов.
|
||||
|
||||
Возвращает список кортежей (text, has_markdown):
|
||||
|
||||
Возвращает список кортежей (text, has_code, code_opened, code_closed):
|
||||
- text: текст сообщения
|
||||
- has_markdown: True если сообщение содержит блоки кода
|
||||
|
||||
- has_code: True если сообщение содержит часть блока кода
|
||||
- code_opened: True если блок кода ОТКРЫТ в этом сообщении (есть открывающий ```)
|
||||
- code_closed: True если блок кода ЗАКРЫТ в этом сообщении (есть закрывающий ```)
|
||||
|
||||
Алгоритм:
|
||||
1. Находим все блоки кода
|
||||
2. Стараемся не разрывать блоки кода
|
||||
3. Если блок кода не влезает — отправляем отдельным сообщением без Markdown
|
||||
"""
|
||||
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 = []
|
||||
code_blocks = find_code_blocks(text)
|
||||
|
||||
|
||||
# Текущая позиция в тексте
|
||||
pos = 0
|
||||
current = ""
|
||||
current_has_code = False
|
||||
|
||||
current_code_opened = False
|
||||
current_code_closed = 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:
|
||||
parts.append((current, current_has_code))
|
||||
parts.append((current, current_has_code, current_code_opened, current_code_closed))
|
||||
current = line
|
||||
current_has_code = False
|
||||
current_code_opened = False
|
||||
current_code_closed = False
|
||||
else:
|
||||
current += ('\n' if current else '') + line
|
||||
|
||||
|
||||
# Проверяем влезет ли блок кода
|
||||
if len(current) + len(code_block) + 1 > max_length - RESERVED_FOR_HEADER:
|
||||
# Отправляем что накопилось
|
||||
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:
|
||||
# Отправляем блок кода частями без Markdown
|
||||
for i in range(0, len(code_block), 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_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
|
||||
|
||||
|
||||
# Обрабатываем остаток текста после последнего блока
|
||||
if pos < len(text):
|
||||
remaining = text[pos:]
|
||||
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))
|
||||
parts.append((current, current_has_code, current_code_opened, current_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))
|
||||
|
||||
parts.append((current, current_has_code, current_code_opened, current_code_closed))
|
||||
|
||||
return parts
|
||||
|
||||
|
||||
|
|
@ -150,9 +173,7 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
|
|||
- Блоки кода не разрываются между сообщениями
|
||||
- Если блок кода не влезает — отправляется без Markdown
|
||||
- Нумерация (1/N), (2/N) только если сообщений > 1
|
||||
- Если сообщение содержит блок кода, он закрывается в конце и открывается в начале следующего
|
||||
- КАЖДЫЕ pause_after сообщений — пауза с кнопками "Продолжить" / "Отменить"
|
||||
- После нажатия кнопки — они удаляются
|
||||
- КАЖДЫЕ pause_every сообщений — пауза с кнопками "Продолжить" / "Отменить"
|
||||
|
||||
Args:
|
||||
update: Telegram update
|
||||
|
|
@ -168,7 +189,7 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
|
|||
messages_sent = 0
|
||||
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:
|
||||
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
|
||||
|
||||
# Определяем parse_mode для этого сообщения
|
||||
if parse_mode and has_code:
|
||||
actual_parse_mode = parse_mode
|
||||
elif parse_mode and not has_code:
|
||||
actual_parse_mode = parse_mode
|
||||
else:
|
||||
actual_parse_mode = None
|
||||
actual_parse_mode = parse_mode if parse_mode and has_code else None
|
||||
|
||||
# Если это не первое сообщение и предыдущее имело блок кода — открываем блок
|
||||
# Если это не последнее сообщение и текущее имеет блок кода — закрываем блок
|
||||
if total > 1 and actual_parse_mode:
|
||||
if i > 0 and parts[i-1][1]: # Предыдущее имело блок кода
|
||||
# Логика работы с блоками кода между сообщениями:
|
||||
# - Если предыдущее сообщение не закрыло блок (code_closed=False) и имело код — открываем в этом
|
||||
# - Если текущее сообщение не закрывает блок (code_closed=False) и следующее имеет код — не закрываем
|
||||
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
|
||||
|
||||
if i < total - 1 and has_code: # Следующее будет иметь блок кода
|
||||
# Проверяем нужно ли закрыть блок в конце этого сообщения
|
||||
# Закрываем только если следующее сообщение НЕ имеет кода
|
||||
if i < total - 1 and not code_closed and not parts[i+1][1]: # следующее не имеет кода
|
||||
part = part + "\n```"
|
||||
|
||||
try:
|
||||
|
|
|
|||
Loading…
Reference in New Issue