fix: polling подход для ожидания кнопок вместо asyncio.Event
Co-authored-by: Qwen-Coder <qwen-coder@alibabacloud.com>
This commit is contained in:
parent
42e1043f28
commit
d20092730e
|
|
@ -62,12 +62,6 @@ async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
await query.delete_message()
|
await query.delete_message()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось удалить сообщение с кнопками: {e}")
|
logger.warning(f"Не удалось удалить сообщение с кнопками: {e}")
|
||||||
# Устанавливаем event чтобы разблокировать send_long_message
|
|
||||||
if state.output_continue_event:
|
|
||||||
logger.info("callback: устанавливаем continue_event")
|
|
||||||
state.output_continue_event.set()
|
|
||||||
else:
|
|
||||||
logger.warning("callback: output_continue_event не найден!")
|
|
||||||
await query.answer()
|
await query.answer()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
@ -82,12 +76,6 @@ async def menu_callback(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||||
await query.delete_message()
|
await query.delete_message()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.warning(f"Не удалось удалить сообщение с кнопками: {e}")
|
logger.warning(f"Не удалось удалить сообщение с кнопками: {e}")
|
||||||
# Устанавливаем event чтобы разблокировать send_long_message
|
|
||||||
if state.output_continue_event:
|
|
||||||
logger.info("callback: устанавливаем continue_event (отмена)")
|
|
||||||
state.output_continue_event.set()
|
|
||||||
else:
|
|
||||||
logger.warning("callback: output_continue_event не найден!")
|
|
||||||
await query.answer()
|
await query.answer()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -89,7 +89,6 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
|
||||||
lines = text.split('\n')
|
lines = text.split('\n')
|
||||||
current = ""
|
current = ""
|
||||||
in_code_block = False # Состояние: внутри блока кода или нет
|
in_code_block = False # Состояние: внутри блока кода или нет
|
||||||
code_block_started_in_current = False # Блок кода был открыт в текущей части
|
|
||||||
|
|
||||||
for line in lines:
|
for line in lines:
|
||||||
# Проверяем, содержит ли строка ```
|
# Проверяем, содержит ли строка ```
|
||||||
|
|
@ -108,10 +107,8 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
|
||||||
# code_closed=False потому что блок продолжится
|
# code_closed=False потому что блок продолжится
|
||||||
parts.append((current, has_code, code_opened, False))
|
parts.append((current, has_code, code_opened, False))
|
||||||
current = line
|
current = line
|
||||||
code_block_started_in_current = False
|
|
||||||
else:
|
else:
|
||||||
current = test_line
|
current = test_line
|
||||||
code_block_started_in_current = True
|
|
||||||
in_code_block = False
|
in_code_block = False
|
||||||
else:
|
else:
|
||||||
# Были снаружи — эта строка открывает блок
|
# Были снаружи — эта строка открывает блок
|
||||||
|
|
@ -121,10 +118,8 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
|
||||||
has_code, code_opened, code_closed = calc_code_flags(current)
|
has_code, code_opened, code_closed = calc_code_flags(current)
|
||||||
parts.append((current, has_code, code_opened, code_closed))
|
parts.append((current, has_code, code_opened, code_closed))
|
||||||
current = line
|
current = line
|
||||||
code_block_started_in_current = True
|
|
||||||
else:
|
else:
|
||||||
current = test_line
|
current = test_line
|
||||||
code_block_started_in_current = True
|
|
||||||
in_code_block = True
|
in_code_block = True
|
||||||
else:
|
else:
|
||||||
# Строка не меняет состояние
|
# Строка не меняет состояние
|
||||||
|
|
@ -136,7 +131,6 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
|
||||||
code_closed = not in_code_block
|
code_closed = not in_code_block
|
||||||
parts.append((current, has_code, code_opened, code_closed))
|
parts.append((current, has_code, code_opened, code_closed))
|
||||||
current = line
|
current = line
|
||||||
code_block_started_in_current = in_code_block
|
|
||||||
else:
|
else:
|
||||||
current = test_line
|
current = test_line
|
||||||
|
|
||||||
|
|
@ -151,22 +145,9 @@ def split_message(text: str, max_length: int = MAX_MESSAGE_LENGTH) -> List[Tuple
|
||||||
async def send_long_message(update: Update, text: str, parse_mode: str = None, pause_every: int = 3):
|
async def send_long_message(update: Update, text: str, parse_mode: str = None, pause_every: int = 3):
|
||||||
"""
|
"""
|
||||||
Отправить длинный текст, разбив на несколько сообщений.
|
Отправить длинный текст, разбив на несколько сообщений.
|
||||||
|
Использует polling для ожидания нажатия кнопки (не блокирует event loop).
|
||||||
Умная разбивка:
|
|
||||||
- Блоки кода не разрываются между сообщениями
|
|
||||||
- Если блок кода не влезает — отправляется без Markdown
|
|
||||||
- Нумерация (1/N), (2/N) только если сообщений > 1
|
|
||||||
- КАЖДЫЕ pause_every сообщений — пауза с кнопками "Продолжить" / "Отменить"
|
|
||||||
- Ждём нажатия кнопки бесконечно, не продолжаем автоматически
|
|
||||||
|
|
||||||
Args:
|
|
||||||
update: Telegram update
|
|
||||||
text: Текст для отправки
|
|
||||||
parse_mode: Режим парсинга (Markdown)
|
|
||||||
pause_every: Каждые сколько сообщений делать паузу (0 = без паузы)
|
|
||||||
"""
|
"""
|
||||||
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
|
||||||
import asyncio
|
|
||||||
|
|
||||||
parts = split_message(text)
|
parts = split_message(text)
|
||||||
total = len(parts)
|
total = len(parts)
|
||||||
|
|
@ -175,27 +156,18 @@ 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 для этого сообщения
|
||||||
# Используем parse_mode если сообщение имеет код ИЛИ если мы внутри блока кода
|
|
||||||
# Мы внутри блока кода если: предыдущее не закрыло ИЛИ (текущее открыто и не закрыто)
|
|
||||||
prev_closed = parts[i-1][3] if i > 0 else True
|
prev_closed = parts[i-1][3] if i > 0 else True
|
||||||
in_code_block = not prev_closed or (code_opened and not code_closed)
|
in_code_block = not prev_closed or (code_opened and not code_closed)
|
||||||
actual_parse_mode = parse_mode if parse_mode and (has_code or in_code_block) else None
|
actual_parse_mode = parse_mode if parse_mode and (has_code or in_code_block) else None
|
||||||
|
|
||||||
# Логика работы с блоками кода между сообщениями:
|
# Логика работы с блоками кода между сообщениями
|
||||||
# Если предыдущее сообщение не закрыло блок — нужно открыть в этом
|
|
||||||
# Если текущее сообщение не закрыло блок — нужно закрыть в следующем
|
|
||||||
if total > 1 and actual_parse_mode:
|
if total > 1 and actual_parse_mode:
|
||||||
# Проверяем нужно ли открыть блок в начале этого сообщения
|
|
||||||
if i > 0 and not parts[i-1][3]: # предыдущее не закрыло блок
|
if i > 0 and not parts[i-1][3]: # предыдущее не закрыло блок
|
||||||
part = "```\n" + part
|
part = "```\n" + part
|
||||||
|
if i < total - 1 and not code_closed and not parts[i+1][2]: # следующее не открывает блок
|
||||||
|
part = part + "\n```"
|
||||||
|
|
||||||
# Проверяем нужно ли закрыть блок в конце этого сообщения
|
# Добавляем номер части
|
||||||
if i < total - 1 and not code_closed:
|
|
||||||
# Следующее сообщение не начинается с ``` — закрываем блок
|
|
||||||
if not parts[i+1][2]: # следующее не открывает блок
|
|
||||||
part = part + "\n```"
|
|
||||||
|
|
||||||
# Добавляем номер части ПОСЛЕ работы с блоками кода
|
|
||||||
if total > 1:
|
if total > 1:
|
||||||
header = f"({i+1}/{total}) "
|
header = f"({i+1}/{total}) "
|
||||||
if len(header) + len(part) <= MAX_MESSAGE_LENGTH:
|
if len(header) + len(part) <= MAX_MESSAGE_LENGTH:
|
||||||
|
|
@ -204,16 +176,13 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
|
||||||
try:
|
try:
|
||||||
await update.message.reply_text(part, parse_mode=actual_parse_mode)
|
await update.message.reply_text(part, parse_mode=actual_parse_mode)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Фоллбэк: отправляем без разметки
|
|
||||||
logger.debug(f"Ошибка Markdown, отправляем без разметки: {e}")
|
logger.debug(f"Ошибка Markdown, отправляем без разметки: {e}")
|
||||||
await update.message.reply_text(part)
|
await update.message.reply_text(part)
|
||||||
|
|
||||||
messages_sent += 1
|
messages_sent += 1
|
||||||
|
|
||||||
# Небольшая пауза между сообщениями
|
|
||||||
await asyncio.sleep(0.1)
|
await asyncio.sleep(0.1)
|
||||||
|
|
||||||
# КАЖДЫЕ pause_every сообщений — спрашиваем продолжать ли
|
# КАЖДЫЕ pause_every сообщений — спрашивать продолжать ли
|
||||||
if pause_every > 0 and messages_sent % pause_every == 0 and i < total - 1:
|
if pause_every > 0 and messages_sent % pause_every == 0 and i < total - 1:
|
||||||
remaining = total - (i + 1)
|
remaining = total - (i + 1)
|
||||||
keyboard = InlineKeyboardMarkup([
|
keyboard = InlineKeyboardMarkup([
|
||||||
|
|
@ -231,47 +200,49 @@ async def send_long_message(update: Update, text: str, parse_mode: str = None, p
|
||||||
reply_markup=keyboard
|
reply_markup=keyboard
|
||||||
)
|
)
|
||||||
|
|
||||||
# Ждём ответа пользователя через asyncio.Event (не блокируем event loop)
|
# Ждём через polling (короткие паузы дают event loop обработать callback)
|
||||||
from bot.config import state_manager
|
from bot.config import state_manager
|
||||||
import asyncio
|
|
||||||
|
|
||||||
user_id = update.effective_user.id
|
user_id = update.effective_user.id
|
||||||
|
|
||||||
# Создаём Event для этого пользователя
|
|
||||||
continue_event = asyncio.Event()
|
|
||||||
state = state_manager.get(user_id)
|
state = state_manager.get(user_id)
|
||||||
state.waiting_for_output_control = True
|
state.waiting_for_output_control = True
|
||||||
state.output_remaining = remaining
|
state.output_remaining = remaining
|
||||||
state.output_wait_message = wait_msg
|
state.output_wait_message = wait_msg
|
||||||
state.output_continue_event = continue_event
|
state.continue_output = None # None = ещё не решил
|
||||||
|
|
||||||
logger.info(f"send_long_message: ждём нажатия кнопки (user_id={user_id}, remaining={remaining})")
|
logger.info(f"send_long_message: ждём нажатия кнопки (user_id={user_id}, remaining={remaining})")
|
||||||
|
|
||||||
# Ждём пока callback handler не установит event
|
# Polling с короткими паузами (даём event loop обработать callback)
|
||||||
await continue_event.wait()
|
for _ in range(600): # Максимум 600 * 0.5 = 300 секунд = 5 минут
|
||||||
|
await asyncio.sleep(0.5)
|
||||||
|
state = state_manager.get(user_id)
|
||||||
|
if state.continue_output is not None:
|
||||||
|
# Пользователь нажал кнопку
|
||||||
|
break
|
||||||
|
|
||||||
logger.info(f"send_long_message: кнопка нажата, continue_output={state.continue_output}")
|
logger.info(f"send_long_message: кнопка нажата, continue_output={state.continue_output}")
|
||||||
|
|
||||||
# Пользователь ответил
|
# Проверяем решение пользователя
|
||||||
if state.continue_output:
|
if not state.continue_output:
|
||||||
# Продолжаем - удаляем кнопки
|
# Отменил
|
||||||
try:
|
try:
|
||||||
await wait_msg.delete()
|
await wait_msg.delete()
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
else:
|
state.waiting_for_output_control = False
|
||||||
# Отменил - удаляем сообщение с кнопками
|
state.output_remaining = None
|
||||||
try:
|
state.output_wait_message = None
|
||||||
await wait_msg.delete()
|
return
|
||||||
except:
|
|
||||||
pass
|
# Продолжаем
|
||||||
return # Прерываем вывод
|
try:
|
||||||
|
await wait_msg.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
# Очищаем состояние
|
|
||||||
state.waiting_for_output_control = False
|
state.waiting_for_output_control = False
|
||||||
state.output_remaining = None
|
state.output_remaining = None
|
||||||
state.output_wait_message = None
|
state.output_wait_message = None
|
||||||
state.output_continue_event = None
|
|
||||||
|
|
||||||
|
|
||||||
def format_long_output(text: str, max_lines: int = 100, head_lines: int = 50, tail_lines: int = 50) -> str:
|
def format_long_output(text: str, max_lines: int = 100, head_lines: int = 50, tail_lines: int = 50) -> str:
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue