311 lines
12 KiB
Python
311 lines
12 KiB
Python
"""Обработчики для работы с шаблонами."""
|
||
import logging
|
||
import logging
|
||
import re
|
||
import logging
|
||
from typing import Optional, Dict, Any, cast
|
||
from telegram import Update, Message, CallbackQuery
|
||
from telegram.ext import ContextTypes, ConversationHandler
|
||
from telegram.error import BadRequest
|
||
|
||
from app.bots.editor.states import BotStates
|
||
from app.bots.editor.session import get_session_store
|
||
from ..keyboards import (
|
||
template_type_keyboard,
|
||
get_templates_keyboard,
|
||
get_preview_keyboard
|
||
)
|
||
from ..utils.parsers import parse_key_value_lines
|
||
from ..utils.validation import validate_template_name
|
||
from app.services.users import get_or_create_user
|
||
from app.services.template import list_templates as get_user_templates_list
|
||
from app.services.template import create_template
|
||
|
||
logger = logging.getLogger(__name__)
|
||
|
||
async def start_template_creation(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Начало создания шаблона."""
|
||
if not update.message:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
message = update.message
|
||
await message.reply_text(
|
||
"Выберите тип шаблона:",
|
||
reply_markup=template_type_keyboard()
|
||
)
|
||
return BotStates.TPL_TYPE
|
||
|
||
async def handle_template_type(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка выбора типа шаблона."""
|
||
if not update.callback_query:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
query = update.callback_query
|
||
await query.answer()
|
||
|
||
tpl_type = query.data
|
||
user_id = query.from_user.id
|
||
|
||
session_store = get_session_store()
|
||
session = session_store.get_or_create(user_id)
|
||
session.type = tpl_type
|
||
|
||
await query.message.edit_text("Введите название шаблона:")
|
||
return BotStates.TPL_NAME
|
||
|
||
async def handle_template_name(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка ввода имени шаблона."""
|
||
if not update.message:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
message = update.message
|
||
user_id = message.from_user.id
|
||
name = message.text.strip()
|
||
|
||
if not validate_template_name(name):
|
||
await message.reply_text(
|
||
"Некорректное имя шаблона. Используйте только буквы, цифры и знаки - _"
|
||
)
|
||
return BotStates.TPL_NAME
|
||
|
||
session = get_session_store().get_or_create(user_id)
|
||
session.template_name = name
|
||
|
||
await message.reply_text("Введите текст шаблона:")
|
||
return BotStates.TPL_TEXT
|
||
|
||
async def handle_template_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка текста шаблона."""
|
||
if not update.message:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
message = update.message
|
||
user_id = message.from_user.id
|
||
text = message.text.strip()
|
||
|
||
session = get_session_store().get_or_create(user_id)
|
||
session.text = text
|
||
|
||
await message.reply_text(
|
||
"Введите клавиатуру в формате:\n"
|
||
"текст кнопки = ссылка\n\n"
|
||
"Или отправьте 'skip' чтобы пропустить"
|
||
)
|
||
return BotStates.TPL_NEW_KB
|
||
|
||
async def handle_template_keyboard(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка клавиатуры шаблона."""
|
||
if not update.message:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
message = update.message
|
||
user_id = message.from_user.id
|
||
kb_text = message.text.strip()
|
||
|
||
session = get_session_store().get_or_create(user_id)
|
||
|
||
if kb_text != "skip":
|
||
try:
|
||
keyboard = parse_key_value_lines(kb_text)
|
||
session.keyboard = keyboard
|
||
except ValueError as e:
|
||
await message.reply_text(f"Ошибка разбора клавиатуры: {e}")
|
||
return BotStates.TPL_NEW_KB
|
||
|
||
try:
|
||
template_data = {
|
||
"owner_id": user_id,
|
||
"name": session.template_name,
|
||
"title": session.template_name,
|
||
"content": session.text,
|
||
"type": session.type,
|
||
"parse_mode": session.parse_mode or "HTML",
|
||
"keyboard_tpl": session.keyboard
|
||
}
|
||
await create_template(template_data)
|
||
await message.reply_text("Шаблон успешно создан")
|
||
|
||
# Очищаем сессию
|
||
get_session_store().drop(user_id)
|
||
return BotStates.CONVERSATION_END
|
||
|
||
except ValueError as e:
|
||
await message.reply_text(f"Ошибка создания шаблона: {e}")
|
||
return BotStates.TPL_NEW_KB
|
||
except Exception as e:
|
||
logger.error(f"Неожиданная ошибка при создании шаблона: {e}")
|
||
await message.reply_text("Произошла непредвиденная ошибка при создании шаблона")
|
||
return BotStates.TPL_NEW_KB
|
||
|
||
def extract_template_vars(content: str) -> list[str]:
|
||
"""Извлекает переменные из текста шаблона."""
|
||
import re
|
||
pattern = r'\{([^}]+)\}'
|
||
return list(set(re.findall(pattern, content)))
|
||
|
||
async def list_templates(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Список шаблонов."""
|
||
if not update.message or not update.message.from_user:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
try:
|
||
message = update.message
|
||
tg_user = message.from_user
|
||
|
||
# Получаем или создаем пользователя
|
||
user = await get_or_create_user(tg_user_id=tg_user.id, username=tg_user.username)
|
||
|
||
# Получаем шаблоны пользователя
|
||
templates = await get_user_templates_list(owner_id=user.id)
|
||
|
||
if not templates:
|
||
await message.reply_text("Нет доступных шаблонов")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
# Сохраняем шаблоны в контексте
|
||
context.user_data['templates'] = {str(t.id): t for t in templates}
|
||
|
||
# Создаем клавиатуру с шаблонами
|
||
keyboard = get_templates_keyboard(templates)
|
||
|
||
await message.reply_text(
|
||
"Выберите шаблон для использования:",
|
||
reply_markup=keyboard
|
||
)
|
||
return BotStates.SELECT_TEMPLATE
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка загрузки шаблонов: {e}", exc_info=True)
|
||
if update.message:
|
||
await update.message.reply_text(f"Ошибка загрузки шаблонов: {e}")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
async def handle_template_selection(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка выбора шаблона."""
|
||
query = update.callback_query
|
||
if not query:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
await query.answer()
|
||
|
||
try:
|
||
template_id = query.data.split(":")[1]
|
||
template = context.user_data['templates'].get(template_id)
|
||
|
||
if not template:
|
||
await query.message.edit_text("Шаблон не найден")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
# Сохраняем выбранный шаблон
|
||
context.user_data['current_template'] = template
|
||
|
||
# Извлекаем переменные из шаблона
|
||
vars_needed = extract_template_vars(template.content)
|
||
|
||
if not vars_needed:
|
||
# Если переменных нет, сразу показываем предпросмотр
|
||
rendered_text = template.content
|
||
keyboard = get_preview_keyboard()
|
||
await query.message.edit_text(
|
||
f"Предпросмотр:\n\n{rendered_text}",
|
||
reply_markup=keyboard,
|
||
parse_mode=template.parse_mode
|
||
)
|
||
return BotStates.PREVIEW_CONFIRM
|
||
|
||
# Сохраняем список необходимых переменных
|
||
context.user_data['vars_needed'] = vars_needed
|
||
context.user_data['template_vars'] = {}
|
||
|
||
# Запрашиваем первую переменную
|
||
await query.message.edit_text(
|
||
f"Введите значение для переменной {vars_needed[0]}:"
|
||
)
|
||
return BotStates.TEMPLATE_VARS
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при выборе шаблона: {e}", exc_info=True)
|
||
await query.message.edit_text(f"Произошла ошибка: {e}")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
async def handle_template_vars(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка ввода значений переменных шаблона."""
|
||
if not update.message or not update.message.text:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
try:
|
||
vars_needed = context.user_data.get('vars_needed', [])
|
||
template_vars = context.user_data.get('template_vars', {})
|
||
template = context.user_data.get('current_template')
|
||
|
||
if not vars_needed or not template:
|
||
await update.message.reply_text("Ошибка: данные шаблона потеряны")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
# Сохраняем введенное значение
|
||
current_var = vars_needed[len(template_vars)]
|
||
template_vars[current_var] = update.message.text
|
||
context.user_data['template_vars'] = template_vars
|
||
|
||
# Проверяем, все ли переменные заполнены
|
||
if len(template_vars) == len(vars_needed):
|
||
# Формируем предпросмотр
|
||
rendered_text = template.content
|
||
for var, value in template_vars.items():
|
||
rendered_text = rendered_text.replace(f"{{{var}}}", value)
|
||
|
||
keyboard = get_preview_keyboard()
|
||
await update.message.reply_text(
|
||
f"Предпросмотр:\n\n{rendered_text}",
|
||
reply_markup=keyboard,
|
||
parse_mode=template.parse_mode
|
||
)
|
||
return BotStates.PREVIEW_CONFIRM
|
||
|
||
# Запрашиваем следующую переменную
|
||
next_var = vars_needed[len(template_vars)]
|
||
await update.message.reply_text(
|
||
f"Введите значение для переменной {next_var}:"
|
||
)
|
||
return BotStates.TEMPLATE_VARS
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при обработке переменных: {e}", exc_info=True)
|
||
await update.message.reply_text(f"Произошла ошибка: {e}")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
async def handle_preview_confirmation(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||
"""Обработка подтверждения предпросмотра."""
|
||
query = update.callback_query
|
||
if not query:
|
||
return BotStates.CONVERSATION_END
|
||
|
||
await query.answer()
|
||
|
||
try:
|
||
action = query.data.split(":")[1]
|
||
|
||
if action == "cancel":
|
||
await query.message.edit_text("Отправка отменена")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
if action == "send":
|
||
template = context.user_data.get('current_template')
|
||
template_vars = context.user_data.get('template_vars', {})
|
||
|
||
if not template:
|
||
await query.message.edit_text("Ошибка: данные шаблона потеряны")
|
||
return BotStates.CONVERSATION_END
|
||
|
||
# TODO: Добавить логику отправки в канал
|
||
await query.message.edit_text(
|
||
"Пост успешно отправлен\n\n" +
|
||
"(Здесь будет реальная отправка в канал)"
|
||
)
|
||
return BotStates.CONVERSATION_END
|
||
|
||
except Exception as e:
|
||
logger.error(f"Ошибка при подтверждении: {e}", exc_info=True)
|
||
await query.message.edit_text(f"Произошла ошибка: {e}")
|
||
return BotStates.CONVERSATION_END
|