bot works as echo (adding templates s functional)

tpl_list dont
This commit is contained in:
2025-08-19 05:13:16 +09:00
parent a8d860ed87
commit 18a92ca526
7 changed files with 274 additions and 47 deletions

View File

@@ -1,13 +1,27 @@
"""Обработчики для работы с шаблонами."""
from typing import Optional, Dict, Any
from telegram import Update, Message
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
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:
"""Начало создания шаблона."""
@@ -123,24 +137,174 @@ async def handle_template_keyboard(update: Update, context: ContextTypes.DEFAULT
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:
if not update.message or not update.message.from_user:
return BotStates.CONVERSATION_END
message = update.message
user_id = message.from_user.id
templates = await get_user_templates(user_id)
if not templates:
await message.reply_text("У вас пока нет шаблонов")
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
page = context.user_data.get("tpl_page", 0)
keyboard = get_templates_keyboard(templates, page)
await query.answer()
await message.reply_text(
"Выберите шаблон:",
reply_markup=keyboard
)
return BotStates.TPL_SELECT
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

View File

@@ -10,7 +10,27 @@ def template_type_keyboard() -> InlineKeyboardMarkup:
def get_templates_keyboard(templates: List[Any], page: int = 0) -> InlineKeyboardMarkup:
"""Возвращает клавиатуру со списком шаблонов."""
return KbBuilder.get_templates_keyboard(templates, page)
keyboard = []
for template in templates:
keyboard.append([
InlineKeyboardButton(
template.name,
callback_data=f"template:{template.id}"
)
])
return InlineKeyboardMarkup(keyboard)
def get_preview_keyboard() -> InlineKeyboardMarkup:
"""Возвращает клавиатуру для предпросмотра."""
keyboard = [
[
InlineKeyboardButton("✅ Отправить", callback_data="preview:send"),
InlineKeyboardButton("❌ Отмена", callback_data="preview:cancel")
]
]
return InlineKeyboardMarkup(keyboard)
class KbBuilder:

View File

@@ -12,7 +12,10 @@ from .handlers.templates import (
handle_template_name,
handle_template_text,
handle_template_keyboard,
list_templates
list_templates,
handle_template_selection,
handle_template_vars,
handle_preview_confirmation
)
from .handlers.posts import (
newpost,
@@ -41,7 +44,10 @@ def register_handlers(app: Application) -> None:
# Шаблоны
template_handler = ConversationHandler(
entry_points=[CommandHandler("newtemplate", start_template_creation)],
entry_points=[
CommandHandler("newtemplate", start_template_creation),
CommandHandler("tpl_list", list_templates)
],
states={
BotStates.TPL_TYPE: [
CallbackQueryHandler(handle_template_type)
@@ -54,6 +60,15 @@ def register_handlers(app: Application) -> None:
],
BotStates.TPL_NEW_KB: [
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_template_keyboard)
],
BotStates.SELECT_TEMPLATE: [
CallbackQueryHandler(handle_template_selection, pattern=r"^template:")
],
BotStates.TEMPLATE_VARS: [
MessageHandler(filters.TEXT & ~filters.COMMAND, handle_template_vars)
],
BotStates.PREVIEW_CONFIRM: [
CallbackQueryHandler(handle_preview_confirmation, pattern=r"^preview:")
]
},
fallbacks=[CommandHandler("cancel", cancel)]

View File

@@ -23,7 +23,7 @@ class BotStates(IntEnum):
TEMPLATE_PREVIEW = 19
TEMPLATE_VARS = 20
# Состояния создания поста
# Состояния работы с шаблонами и создания поста
CREATE_POST = 30
CHOOSE_CHANNEL = 31 # Выбор канала
CHOOSE_TYPE = 32 # Выбор типа поста (текст/фото/видео/gif)
@@ -33,9 +33,9 @@ class BotStates(IntEnum):
EDIT_KEYBOARD = 36 # Редактирование клавиатуры
CONFIRM_SEND = 37 # Подтверждение отправки
ENTER_SCHEDULE = 38 # Ввод времени для отложенной публикации
SELECT_TEMPLATE = 39 # Выбор шаблона
SELECT_TEMPLATE = 25 # Выбор шаблона
PREVIEW_VARS = 40 # Ввод значений для переменных
PREVIEW_CONFIRM = 41 # Подтверждение предпросмотра
PREVIEW_CONFIRM = 26 # Подтверждение предпросмотра
# Состояния работы с каналами
CHANNEL_NAME = 50

View File

@@ -29,7 +29,7 @@ DEFAULT_TTL = 3600 # 1 час
# Настройка логирования
logger = logging.getLogger(__name__)
async def main():
async def run_bot():
"""Запуск бота."""
app = Application.builder().token(settings.editor_bot_token).build()
@@ -39,23 +39,17 @@ async def main():
# Запуск бота
await app.run_polling(allowed_updates=Update.ALL_TYPES)
if __name__ == "__main__":
import asyncio
def main():
"""Основная функция запуска."""
try:
asyncio.run(main())
asyncio.run(run_bot())
except KeyboardInterrupt:
logger.info("Бот остановлен")
except Exception as e:
logger.error(f"Ошибка: {e}", exc_info=True)
finally:
# Убедимся, что все циклы закрыты
loop = asyncio.get_event_loop()
if loop.is_running():
loop.stop()
if not loop.is_closed():
loop.close()
except Exception as e:
logger.error(f"Ошибка: {e}", exc_info=True)
if __name__ == "__main__":
main()
from app.core.config import settings
from .editor.session import SessionStore
@@ -67,12 +61,7 @@ from .editor.handlers.templates import (
handle_template_keyboard,
list_templates
)
from .editor.handlers.posts import (
newpost,
handle_post_template,
handle_post_channel,
handle_post_schedule
)
from .editor.handlers.posts import newpost
from .editor.states import BotStates
# Настройка логирования
@@ -624,7 +613,7 @@ async def tpl_new_content(update: Update, context: Context) -> int:
"title": session.template_name,
"content": content,
"type": session.type,
"owner_id": user.id,
"tg_user_id": user.id, # Передаем telegram user id вместо owner_id
"parse_mode": session.parse_mode,
"keyboard_tpl": session.keyboard
}
@@ -633,8 +622,9 @@ async def tpl_new_content(update: Update, context: Context) -> int:
await create_template(template_data)
await message.reply_text("Шаблон успешно сохранен")
return ConversationHandler.END
except ValueError as e:
await message.reply_text(f"Ошибка создания шаблона: {e}")
except Exception as e:
logger.error(f"Ошибка создания шаблона: {e}", exc_info=True)
await message.reply_text(f"Ошибка создания шаблона. Пожалуйста, попробуйте позже.")
return BotStates.TPL_NEW_CONTENT
async def list_user_templates(update: Update, context: Context) -> None: