Добавлено экранирование Markdown в сообщениях бота и обработчик кнопки для очистки истории диалога. Обновлено приветственное сообщение с инструкциями по форматированию.
This commit is contained in:
parent
d2af1e23de
commit
c9feb61ceb
102
bot.py
102
bot.py
@ -4,6 +4,7 @@ import logging
|
|||||||
import json
|
import json
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
from typing import Dict, List, Any, Optional
|
from typing import Dict, List, Any, Optional
|
||||||
|
import re
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher, types, F
|
from aiogram import Bot, Dispatcher, types, F
|
||||||
from aiogram.filters import Command
|
from aiogram.filters import Command
|
||||||
@ -11,6 +12,7 @@ from aiogram.types import Message
|
|||||||
from dotenv import load_dotenv
|
from dotenv import load_dotenv
|
||||||
from ollama import AsyncClient
|
from ollama import AsyncClient
|
||||||
import asyncpg
|
import asyncpg
|
||||||
|
from aiogram.types import InlineKeyboardMarkup, InlineKeyboardButton
|
||||||
|
|
||||||
# Настройка логирования
|
# Настройка логирования
|
||||||
logging.basicConfig(level=logging.INFO)
|
logging.basicConfig(level=logging.INFO)
|
||||||
@ -151,6 +153,50 @@ async def keep_typing(chat_id: int):
|
|||||||
logging.error(f"Ошибка в keep_typing: {e}")
|
logging.error(f"Ошибка в keep_typing: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
def escape_markdown(text: str) -> str:
|
||||||
|
"""Экранирует специальные символы Markdown, если они не являются частью форматирования"""
|
||||||
|
# Регулярное выражение для поиска уже отформатированного текста
|
||||||
|
md_patterns = [
|
||||||
|
r'`[^`]+`', # Код
|
||||||
|
r'\*\*[^*]+\*\*', # Жирный
|
||||||
|
r'\*[^*]+\*', # Курсив
|
||||||
|
r'_[^_]+_', # Курсив (альтернативный)
|
||||||
|
r'__[^_]+__', # Жирный (альтернативный)
|
||||||
|
r'\[[^\]]+\]\([^)]+\)', # Ссылки
|
||||||
|
]
|
||||||
|
|
||||||
|
# Собираем все совпадения с шаблонами
|
||||||
|
matches = []
|
||||||
|
for pattern in md_patterns:
|
||||||
|
matches.extend([(m.start(), m.end()) for m in re.finditer(pattern, text)])
|
||||||
|
|
||||||
|
# Сортируем по началу совпадения
|
||||||
|
matches.sort()
|
||||||
|
|
||||||
|
# Если нет совпадений, просто экранируем все специальные символы
|
||||||
|
if not matches:
|
||||||
|
return re.sub(r'([_*\[\]()~`>#+-=|{}.!\\])', r'\\\1', text)
|
||||||
|
|
||||||
|
# Иначе экранируем только те символы, которые не входят в уже отформатированный текст
|
||||||
|
result = ""
|
||||||
|
last_pos = 0
|
||||||
|
|
||||||
|
for start, end in matches:
|
||||||
|
# Экранируем символы до начала форматирования
|
||||||
|
if start > last_pos:
|
||||||
|
result += re.sub(r'([_*\[\]()~`>#+-=|{}.!\\])', r'\\\1', text[last_pos:start])
|
||||||
|
|
||||||
|
# Добавляем отформатированный текст без изменений
|
||||||
|
result += text[start:end]
|
||||||
|
last_pos = end
|
||||||
|
|
||||||
|
# Экранируем символы после последнего форматирования
|
||||||
|
if last_pos < len(text):
|
||||||
|
result += re.sub(r'([_*\[\]()~`>#+-=|{}.!\\])', r'\\\1', text[last_pos:])
|
||||||
|
|
||||||
|
return result
|
||||||
|
|
||||||
|
|
||||||
@dp.message(Command("start"))
|
@dp.message(Command("start"))
|
||||||
async def cmd_start(message: Message):
|
async def cmd_start(message: Message):
|
||||||
"""Обработчик команды /start"""
|
"""Обработчик команды /start"""
|
||||||
@ -165,7 +211,22 @@ async def cmd_start(message: Message):
|
|||||||
# Если есть, сохраняем их в кэше
|
# Если есть, сохраняем их в кэше
|
||||||
user_messages[user_id] = messages
|
user_messages[user_id] = messages
|
||||||
|
|
||||||
await message.answer("Привет! Я бот на базе Gemma. Отправьте мне сообщение или фото, и я отвечу вам.")
|
welcome_message = (
|
||||||
|
"Привет! Я бот на базе Gemma. Отправьте мне сообщение или фото, и я отвечу вам.\n\n"
|
||||||
|
"Поддерживается *Markdown* форматирование:\n"
|
||||||
|
"• *жирный текст* (звездочки)\n"
|
||||||
|
"• _курсив_ (нижнее подчеркивание)\n"
|
||||||
|
"• `код` (обратные кавычки)\n"
|
||||||
|
"• [ссылки](https://example.com)\n\n"
|
||||||
|
"Используйте /clear для очистки контекста диалога."
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем инлайн-кнопку для очистки истории
|
||||||
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="🗑 Очистить историю сообщений", callback_data="clear_history")]
|
||||||
|
])
|
||||||
|
|
||||||
|
await message.answer(welcome_message, parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard)
|
||||||
|
|
||||||
|
|
||||||
@dp.message(Command("clear"))
|
@dp.message(Command("clear"))
|
||||||
@ -245,8 +306,14 @@ async def handle_photo(message: Message):
|
|||||||
# Сохраняем ответ в БД
|
# Сохраняем ответ в БД
|
||||||
await save_message_to_db(user_id, assistant_message)
|
await save_message_to_db(user_id, assistant_message)
|
||||||
|
|
||||||
# Отправляем ответ пользователю
|
# Создаем инлайн-кнопку для очистки истории
|
||||||
await message.answer(answer)
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="🗑 Очистить историю сообщений", callback_data="clear_history")]
|
||||||
|
])
|
||||||
|
|
||||||
|
# Отправляем ответ пользователю с Markdown-форматированием и кнопкой
|
||||||
|
safe_answer = escape_markdown(answer)
|
||||||
|
await message.answer(safe_answer, parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Останавливаем задачу обновления статуса печати в случае ошибки
|
# Останавливаем задачу обновления статуса печати в случае ошибки
|
||||||
@ -305,8 +372,14 @@ async def handle_text(message: Message):
|
|||||||
# Сохраняем ответ в БД
|
# Сохраняем ответ в БД
|
||||||
await save_message_to_db(user_id, assistant_message)
|
await save_message_to_db(user_id, assistant_message)
|
||||||
|
|
||||||
# Отправляем ответ пользователю
|
# Создаем инлайн-кнопку для очистки истории
|
||||||
await message.answer(answer)
|
keyboard = InlineKeyboardMarkup(inline_keyboard=[
|
||||||
|
[InlineKeyboardButton(text="🗑 Очистить историю сообщений", callback_data="clear_history")]
|
||||||
|
])
|
||||||
|
|
||||||
|
# Отправляем ответ пользователю с Markdown-форматированием и кнопкой
|
||||||
|
safe_answer = escape_markdown(answer)
|
||||||
|
await message.answer(safe_answer, parse_mode=types.ParseMode.MARKDOWN, reply_markup=keyboard)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
# Останавливаем задачу обновления статуса печати в случае ошибки
|
# Останавливаем задачу обновления статуса печати в случае ошибки
|
||||||
@ -324,6 +397,25 @@ async def handle_other(message: Message):
|
|||||||
await message.answer("Я могу обрабатывать только текст и фотографии. Пожалуйста, отправьте текст или одну фотографию.")
|
await message.answer("Я могу обрабатывать только текст и фотографии. Пожалуйста, отправьте текст или одну фотографию.")
|
||||||
|
|
||||||
|
|
||||||
|
# Добавляем обработчик нажатия на кнопку очистки истории
|
||||||
|
@dp.callback_query(F.data == "clear_history")
|
||||||
|
async def clear_history_callback(callback: types.CallbackQuery):
|
||||||
|
"""Обработчик кнопки очистки истории"""
|
||||||
|
user_id = callback.from_user.id
|
||||||
|
|
||||||
|
# Очищаем контекст в памяти
|
||||||
|
user_messages[user_id] = []
|
||||||
|
|
||||||
|
# Очищаем контекст в БД
|
||||||
|
await clear_messages_from_db(user_id)
|
||||||
|
|
||||||
|
# Уведомляем пользователя
|
||||||
|
await callback.answer("Контекст диалога очищен!")
|
||||||
|
|
||||||
|
# Отправляем сообщение в чат
|
||||||
|
await callback.message.answer("Контекст диалога очищен. Вы можете начать новый разговор.")
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
"""Запуск бота"""
|
"""Запуск бота"""
|
||||||
# Инициализируем подключение к базе данных
|
# Инициализируем подключение к базе данных
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user