Files
postbot/app/bots/editor/handlers/templates.py
2025-08-19 05:13:16 +09:00

311 lines
12 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""Обработчики для работы с шаблонами."""
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