bot rafactor and bugfix
This commit is contained in:
0
app/bots/editor/handlers/__init__.py
Normal file
0
app/bots/editor/handlers/__init__.py
Normal file
43
app/bots/editor/handlers/base.py
Normal file
43
app/bots/editor/handlers/base.py
Normal file
@@ -0,0 +1,43 @@
|
||||
"""Базовые обработчики."""
|
||||
from telegram import Update
|
||||
from telegram.ext import ContextTypes, ConversationHandler
|
||||
|
||||
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработчик команды /start."""
|
||||
if not update.message:
|
||||
return ConversationHandler.END
|
||||
|
||||
await update.message.reply_text(
|
||||
"Привет! Я бот для управления постами.\n"
|
||||
"Для создания шаблона используйте /newtemplate\n"
|
||||
"Для создания поста используйте /newpost\n"
|
||||
"Для просмотра шаблонов /templates\n"
|
||||
"Для помощи /help"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
async def help_command(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработчик команды /help."""
|
||||
if not update.message:
|
||||
return ConversationHandler.END
|
||||
|
||||
await update.message.reply_text(
|
||||
"Доступные команды:\n"
|
||||
"/start - начать работу с ботом\n"
|
||||
"/newtemplate - создать новый шаблон\n"
|
||||
"/templates - просмотреть существующие шаблоны\n"
|
||||
"/newpost - создать новый пост\n"
|
||||
"/cancel - отменить текущую операцию"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
async def cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Отмена текущей операции."""
|
||||
if not update.message:
|
||||
return ConversationHandler.END
|
||||
|
||||
await update.message.reply_text(
|
||||
"Операция отменена.",
|
||||
reply_markup=None
|
||||
)
|
||||
return ConversationHandler.END
|
||||
744
app/bots/editor/handlers/posts.py
Normal file
744
app/bots/editor/handlers/posts.py
Normal file
@@ -0,0 +1,744 @@
|
||||
"""Обработчики для работы с постами."""
|
||||
from datetime import datetime
|
||||
from logging import getLogger
|
||||
import re
|
||||
from typing import Dict, Any, Optional, cast, Union
|
||||
|
||||
from telegram import (
|
||||
Update,
|
||||
Message,
|
||||
CallbackQuery,
|
||||
ReplyKeyboardMarkup,
|
||||
InlineKeyboardMarkup,
|
||||
InlineKeyboardButton
|
||||
)
|
||||
from telegram.ext import (
|
||||
ContextTypes,
|
||||
ConversationHandler
|
||||
)
|
||||
from telegram.helpers import escape_markdown
|
||||
from telegram.constants import ChatAction, ParseMode
|
||||
from telegram.error import BadRequest, Forbidden, TelegramError
|
||||
|
||||
from ..session import UserSession, SessionStore
|
||||
from ..states import BotStates
|
||||
from ..keyboards import KbBuilder
|
||||
from app.models.post import Post, PostType
|
||||
from app.services.template import TemplateService
|
||||
from app.services.channels import ChannelService
|
||||
from app.services.telegram import PostService
|
||||
from ..messages import MessageType
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
def parse_key_value_lines(text: str) -> Dict[str, str]:
|
||||
"""Парсит строки в формате 'ключ = значение' в словарь."""
|
||||
if not text:
|
||||
return {}
|
||||
|
||||
result = {}
|
||||
for line in text.split('\n'):
|
||||
if '=' not in line:
|
||||
continue
|
||||
key, value = map(str.strip, line.split('=', 1))
|
||||
if key:
|
||||
result[key] = value
|
||||
return result
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
logger = getLogger(__name__)
|
||||
|
||||
async def newpost(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Начало создания нового поста."""
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
|
||||
if not message or not user:
|
||||
return ConversationHandler.END
|
||||
|
||||
try:
|
||||
# Создаем новую сессию
|
||||
session = SessionStore.get_instance().get(user.id)
|
||||
session.clear()
|
||||
|
||||
# Загружаем список каналов пользователя
|
||||
channels = await ChannelService.get_user_channels(user.id)
|
||||
if not channels:
|
||||
await message.reply_text(
|
||||
"У вас нет добавленных каналов. Используйте /add_channel чтобы добавить."
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
kb = KbBuilder.channels(channels)
|
||||
await message.reply_text(
|
||||
"Выберите канал для публикации:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.CHOOSE_CHANNEL
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in newpost: {e}")
|
||||
await message.reply_text("Произошла ошибка при создании поста")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def choose_channel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка выбора канала."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return ConversationHandler.END
|
||||
|
||||
channel_id = int(query.data.replace("channel:", ""))
|
||||
|
||||
# Проверяем существование канала
|
||||
channel = await ChannelService.get_channel(channel_id)
|
||||
if not channel:
|
||||
await message.edit_text("Канал не найден")
|
||||
return ConversationHandler.END
|
||||
|
||||
session = SessionStore.get_instance().get(query.from_user.id)
|
||||
session.channel_id = channel_id
|
||||
|
||||
kb = KbBuilder.post_types()
|
||||
await message.edit_text(
|
||||
"Выберите тип поста:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.CHOOSE_TYPE
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_channel: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text("Произошла ошибка при выборе канала")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def choose_type(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка выбора типа поста."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return ConversationHandler.END
|
||||
|
||||
post_type = PostType(query.data.replace("type:", ""))
|
||||
|
||||
session = SessionStore.get_instance().get(query.from_user.id)
|
||||
session.type = post_type
|
||||
|
||||
kb = KbBuilder.parse_modes()
|
||||
await message.edit_text(
|
||||
"Выберите формат текста:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.CHOOSE_FORMAT
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_type: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text("Произошла ошибка при выборе типа поста")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def choose_format(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка выбора формата текста."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return ConversationHandler.END
|
||||
|
||||
parse_mode = query.data.replace("fmt:", "")
|
||||
if parse_mode not in [ParseMode.HTML, ParseMode.MARKDOWN_V2]:
|
||||
await message.edit_text("Неизвестный формат текста")
|
||||
return ConversationHandler.END
|
||||
|
||||
session = SessionStore.get_instance().get(query.from_user.id)
|
||||
session.parse_mode = parse_mode
|
||||
|
||||
kb = KbBuilder.text_input_options()
|
||||
await message.edit_text(
|
||||
"Введите текст поста или выберите шаблон:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_format: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text("Произошла ошибка при выборе формата")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def enter_text(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка ввода текста поста."""
|
||||
message = update.effective_message
|
||||
user = update.effective_user
|
||||
|
||||
if not message or not user:
|
||||
return ConversationHandler.END
|
||||
|
||||
text = message.text
|
||||
if not text:
|
||||
await message.reply_text("Пожалуйста, введите текст поста")
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
try:
|
||||
session = SessionStore.get_instance().get(user.id)
|
||||
session.text = text
|
||||
|
||||
if session.type == MessageType.TEXT:
|
||||
await message.reply_text(
|
||||
"Введите клавиатуру в формате:\n"
|
||||
"текст кнопки = ссылка\n\n"
|
||||
"Или отправьте 'skip' чтобы пропустить"
|
||||
)
|
||||
return BotStates.EDIT_KEYBOARD
|
||||
|
||||
await message.reply_text(
|
||||
"Отправьте фото/видео/gif для поста"
|
||||
)
|
||||
return BotStates.ENTER_MEDIA
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in enter_text: {e}")
|
||||
await message.reply_text("Произошла ошибка при сохранении текста")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def choose_template_open(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Открытие выбора шаблона."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
user_id = query.from_user.id
|
||||
|
||||
templates = await TemplateService.list_user_templates(user_id)
|
||||
if not templates:
|
||||
await message.edit_text(
|
||||
"У вас нет шаблонов. Создайте новый с помощью /newtemplate"
|
||||
)
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
total = len(templates)
|
||||
if total == 0:
|
||||
await message.edit_text("Список шаблонов пуст")
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
user_data = context.user_data
|
||||
if not user_data:
|
||||
user_data = {}
|
||||
context.user_data = user_data
|
||||
|
||||
page = user_data.get("tpl_page", 0)
|
||||
items = templates[page * KbBuilder.PAGE_SIZE:(page + 1) * KbBuilder.PAGE_SIZE]
|
||||
|
||||
kb = KbBuilder.templates_list(items, page, total)
|
||||
await message.edit_text(
|
||||
"Выберите шаблон:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_template_open: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text("Произошла ошибка при загрузке шаблонов")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def choose_template_apply(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Применение выбранного шаблона."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
template_id = query.data.replace("tpluse:", "")
|
||||
|
||||
template = await TemplateService.get_template(template_id)
|
||||
if not template:
|
||||
await message.edit_text("Шаблон не найден")
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
session = SessionStore.get_instance().get(query.from_user.id)
|
||||
session.template_id = template_id
|
||||
session.text = template.content
|
||||
|
||||
if "{" in template.content and "}" in template.content:
|
||||
# Шаблон содержит переменные
|
||||
await message.edit_text(
|
||||
"Введите значения для переменных в формате:\n"
|
||||
"переменная = значение"
|
||||
)
|
||||
return BotStates.PREVIEW_VARS
|
||||
|
||||
# Нет переменных, можно сразу показать предпросмотр
|
||||
kb = KbBuilder.preview_confirm()
|
||||
|
||||
post_data = session.to_dict()
|
||||
await PostService.preview_post(message, post_data)
|
||||
|
||||
await message.reply_text(
|
||||
"Предпросмотр поста. Выберите действие:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_template_apply: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text(
|
||||
"Произошла ошибка при применении шаблона"
|
||||
)
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при применении шаблона: {e}")
|
||||
await query.message.edit_text(
|
||||
f"Ошибка при применении шаблона: {str(e)}"
|
||||
)
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
async def choose_template_preview(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Предпросмотр шаблона."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
template_id = query.data.replace("tplprev:", "")
|
||||
template = await TemplateService.get_template(template_id)
|
||||
|
||||
if not template:
|
||||
await message.edit_text("Шаблон не найден")
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
await message.edit_text(
|
||||
f"Предпросмотр шаблона:\n\n{template.content}",
|
||||
parse_mode=template.parse_mode
|
||||
)
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_template_preview: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text(
|
||||
"Произошла ошибка при предпросмотре шаблона"
|
||||
)
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
async def choose_template_navigate(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Навигация по страницам шаблонов."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
# Получаем номер страницы
|
||||
page = int(query.data.replace("tplpage:", ""))
|
||||
|
||||
user_data = context.user_data
|
||||
if not user_data:
|
||||
user_data = {}
|
||||
context.user_data = user_data
|
||||
user_data["tpl_page"] = page
|
||||
|
||||
# Перестраиваем список для новой страницы
|
||||
templates = await TemplateService.list_user_templates(query.from_user.id)
|
||||
total = len(templates)
|
||||
items = templates[page * KbBuilder.PAGE_SIZE:(page + 1) * KbBuilder.PAGE_SIZE]
|
||||
|
||||
kb = KbBuilder.templates_list(items, page, total)
|
||||
await message.edit_reply_markup(reply_markup=kb)
|
||||
return BotStates.SELECT_TEMPLATE
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in choose_template_navigate: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text("Произошла ошибка при смене страницы")
|
||||
return ConversationHandler.END
|
||||
|
||||
async def choose_template_cancel(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Отмена выбора шаблона."""
|
||||
query = update.callback_query
|
||||
if not query:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
await query.message.edit_text(
|
||||
"Введите текст поста:"
|
||||
)
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
async def preview_collect_vars(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Сбор значений переменных для шаблона."""
|
||||
message = update.message
|
||||
if not message or not message.from_user or not message.text:
|
||||
return ConversationHandler.END
|
||||
|
||||
try:
|
||||
variables = parse_key_value_lines(message.text)
|
||||
session = SessionStore.get_instance().get(message.from_user.id)
|
||||
if not session.template_id:
|
||||
await message.reply_text("Шаблон не выбран")
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
template = await TemplateService.get_template(session.template_id)
|
||||
if not template:
|
||||
await message.reply_text("Шаблон не найден")
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
# Подставляем значения переменных
|
||||
text = template.content
|
||||
for var, value in variables.items():
|
||||
text = text.replace(f"{{{var}}}", value)
|
||||
|
||||
session.text = text
|
||||
post_data = session.to_dict()
|
||||
|
||||
kb = KbBuilder.preview_confirm()
|
||||
await PostService.preview_post(message, post_data)
|
||||
|
||||
await message.reply_text(
|
||||
"Предпросмотр поста. Выберите действие:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
except ValueError as e:
|
||||
await message.reply_text(
|
||||
f"Ошибка в формате переменных: {str(e)}\n"
|
||||
"Используйте формат:\n"
|
||||
"переменная = значение"
|
||||
)
|
||||
return BotStates.PREVIEW_VARS
|
||||
except Exception as e:
|
||||
logger.error(f"Error in preview_collect_vars: {e}")
|
||||
await message.reply_text(
|
||||
"Произошла ошибка при обработке переменных"
|
||||
)
|
||||
return BotStates.PREVIEW_VARS
|
||||
|
||||
async def preview_confirm(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Подтверждение предпросмотра поста."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
action = query.data.replace("pv:", "")
|
||||
if action == "edit":
|
||||
await message.edit_text(
|
||||
"Введите текст поста:"
|
||||
)
|
||||
return BotStates.ENTER_TEXT
|
||||
|
||||
session = SessionStore.get_instance().get(query.from_user.id)
|
||||
|
||||
if not session.type:
|
||||
await message.edit_text("Ошибка: не выбран тип поста")
|
||||
return ConversationHandler.END
|
||||
|
||||
if session.type == MessageType.TEXT:
|
||||
await message.edit_text(
|
||||
"Введите клавиатуру в формате:\n"
|
||||
"текст кнопки = ссылка\n\n"
|
||||
"Или отправьте 'skip' чтобы пропустить"
|
||||
)
|
||||
return BotStates.EDIT_KEYBOARD
|
||||
|
||||
await message.edit_text(
|
||||
"Отправьте фото/видео/gif для поста"
|
||||
)
|
||||
return BotStates.ENTER_MEDIA
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in preview_confirm: {e}")
|
||||
if query.message:
|
||||
await query.message.edit_text(
|
||||
"Произошла ошибка при обработке предпросмотра"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
async def enter_media(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка медиафайла."""
|
||||
message = update.message
|
||||
if not message or not message.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
try:
|
||||
session = SessionStore.get_instance().get(message.from_user.id)
|
||||
|
||||
if message.photo:
|
||||
session.media_file_id = message.photo[-1].file_id
|
||||
elif message.video:
|
||||
session.media_file_id = message.video.file_id
|
||||
elif message.animation:
|
||||
session.media_file_id = message.animation.file_id
|
||||
elif message.document:
|
||||
session.media_file_id = message.document.file_id
|
||||
else:
|
||||
await message.reply_text(
|
||||
"Пожалуйста, отправьте фото, видео или GIF"
|
||||
)
|
||||
return BotStates.ENTER_MEDIA
|
||||
|
||||
# Показываем предпросмотр
|
||||
kb = KbBuilder.preview_confirm()
|
||||
post_data = session.to_dict()
|
||||
await PostService.preview_post(message, post_data)
|
||||
|
||||
await message.reply_text(
|
||||
"Предпросмотр поста. Выберите действие:",
|
||||
reply_markup=kb
|
||||
)
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in enter_media: {e}")
|
||||
await message.reply_text(
|
||||
"Произошла ошибка при обработке файла"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
session.media_id = message.animation.file_id
|
||||
|
||||
await message.reply_text(
|
||||
"Введите клавиатуру в формате:\n"
|
||||
"текст кнопки = ссылка\n\n"
|
||||
"Или отправьте 'skip' чтобы пропустить"
|
||||
)
|
||||
return BotStates.EDIT_KEYBOARD
|
||||
|
||||
async def edit_keyboard(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка клавиатуры поста."""
|
||||
message = update.message
|
||||
if not message or not message.from_user or not message.text:
|
||||
return ConversationHandler.END
|
||||
|
||||
try:
|
||||
kb_text = message.text.strip()
|
||||
session = SessionStore.get_instance().get(message.from_user.id)
|
||||
|
||||
if kb_text.lower() != "skip":
|
||||
keyboard = parse_key_value_lines(kb_text)
|
||||
session.keyboard = {"rows": []}
|
||||
for text, url in keyboard.items():
|
||||
session.keyboard["rows"].append([{"text": text, "url": url}])
|
||||
|
||||
# Показываем предпросмотр поста
|
||||
post_data = session.to_dict()
|
||||
await PostService.preview_post(message, post_data)
|
||||
|
||||
keyboard = InlineKeyboardMarkup([
|
||||
[
|
||||
InlineKeyboardButton("Отправить", callback_data="send:now"),
|
||||
InlineKeyboardButton("Отложить", callback_data="send:schedule")
|
||||
]
|
||||
])
|
||||
|
||||
await message.reply_text(
|
||||
"Выберите действие:",
|
||||
reply_markup=keyboard
|
||||
)
|
||||
return BotStates.CONFIRM_SEND
|
||||
|
||||
except ValueError as e:
|
||||
await message.reply_text(
|
||||
f"Ошибка в формате клавиатуры: {e}\n"
|
||||
"Используйте формат:\n"
|
||||
"текст кнопки = ссылка"
|
||||
)
|
||||
return BotStates.EDIT_KEYBOARD
|
||||
except Exception as e:
|
||||
logger.error(f"Error in edit_keyboard: {e}")
|
||||
await message.reply_text(
|
||||
"Произошла ошибка при обработке клавиатуры"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
|
||||
async def confirm_send(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Подтверждение отправки поста."""
|
||||
query = update.callback_query
|
||||
if not query or not query.from_user:
|
||||
return ConversationHandler.END
|
||||
|
||||
await query.answer()
|
||||
|
||||
try:
|
||||
message = cast(Message, query.message)
|
||||
if not query.data:
|
||||
await message.edit_text("Неверный формат данных")
|
||||
return BotStates.CONFIRM_SEND
|
||||
|
||||
action = query.data.replace("send:", "")
|
||||
if action == "schedule":
|
||||
await message.edit_text(
|
||||
"Введите дату и время для отложенной публикации в формате:\n"
|
||||
"ДД.ММ.ГГГГ ЧЧ:ММ"
|
||||
)
|
||||
return BotStates.ENTER_SCHEDULE
|
||||
|
||||
session = SessionStore.get_instance().get(query.from_user.id)
|
||||
|
||||
# Отправляем пост сейчас
|
||||
post_data = session.to_dict()
|
||||
|
||||
message = query.message
|
||||
if not message:
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
if not session.channel_id:
|
||||
await context.bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text="Канал не выбран",
|
||||
reply_markup=KbBuilder.go_back()
|
||||
)
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
post = await PostService.create_post(context.bot, session.channel_id, post_data)
|
||||
if post:
|
||||
await context.bot.edit_message_text(
|
||||
chat_id=message.chat.id,
|
||||
message_id=message.message_id,
|
||||
text="Пост успешно отправлен!"
|
||||
)
|
||||
SessionStore.get_instance().drop(query.from_user.id)
|
||||
return ConversationHandler.END
|
||||
|
||||
await context.bot.send_message(
|
||||
chat_id=message.chat.id,
|
||||
text="Ошибка при отправке поста. Попробуйте позже.",
|
||||
reply_markup=KbBuilder.go_back()
|
||||
)
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in confirm_send: {e}")
|
||||
message = query.message
|
||||
if message:
|
||||
await context.bot.edit_message_text(
|
||||
chat_id=message.chat.id,
|
||||
message_id=message.message_id,
|
||||
text="Произошла ошибка при отправке поста"
|
||||
)
|
||||
return BotStates.PREVIEW_CONFIRM
|
||||
return ConversationHandler.END
|
||||
|
||||
session = get_session_store().get_or_create(query.from_user.id)
|
||||
|
||||
try:
|
||||
# Отправляем пост
|
||||
await schedule_post(session, schedule_time=None)
|
||||
await query.message.edit_text("Пост успешно отправлен!")
|
||||
|
||||
# Очищаем сессию
|
||||
get_session_store().drop(query.from_user.id)
|
||||
return ConversationHandler.END
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Ошибка при отправке поста: {e}")
|
||||
await query.message.edit_text(
|
||||
f"Ошибка при отправке поста: {str(e)}"
|
||||
)
|
||||
return BotStates.CONFIRM_SEND
|
||||
|
||||
async def enter_schedule(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||
"""Обработка времени для отложенной публикации."""
|
||||
message = update.message
|
||||
if not message or not message.from_user or not message.text:
|
||||
return ConversationHandler.END
|
||||
|
||||
try:
|
||||
schedule_text = message.text.strip()
|
||||
if not schedule_text:
|
||||
await message.reply_text(
|
||||
"Некорректный формат даты.\n"
|
||||
"Используйте формат: ДД.ММ.ГГГГ ЧЧ:ММ"
|
||||
)
|
||||
return BotStates.ENTER_SCHEDULE
|
||||
|
||||
try:
|
||||
schedule_time = datetime.strptime(schedule_text, "%d.%m.%Y %H:%M")
|
||||
if schedule_time <= datetime.now():
|
||||
await message.reply_text(
|
||||
"Нельзя указать время в прошлом.\n"
|
||||
"Введите время в будущем."
|
||||
)
|
||||
return BotStates.ENTER_SCHEDULE
|
||||
|
||||
session = SessionStore.get_instance().get(message.from_user.id)
|
||||
if not session.channel_id:
|
||||
await message.reply_text("Не выбран канал для публикации")
|
||||
return ConversationHandler.END
|
||||
|
||||
# Отправляем отложенный пост
|
||||
post_data = session.to_dict()
|
||||
post_data["schedule_time"] = schedule_time
|
||||
await PostService.create_post(context.bot, session.channel_id, post_data)
|
||||
await message.reply_text("Пост запланирован!")
|
||||
|
||||
# Очищаем сессию
|
||||
SessionStore.get_instance().drop(message.from_user.id)
|
||||
return ConversationHandler.END
|
||||
|
||||
except ValueError:
|
||||
await message.reply_text(
|
||||
"Некорректный формат даты.\n"
|
||||
"Используйте формат: ДД.ММ.ГГГГ ЧЧ:ММ"
|
||||
)
|
||||
return BotStates.ENTER_SCHEDULE
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in enter_schedule: {e}")
|
||||
await message.reply_text(
|
||||
"Произошла ошибка при обработке времени публикации"
|
||||
)
|
||||
return ConversationHandler.END
|
||||
146
app/bots/editor/handlers/templates.py
Normal file
146
app/bots/editor/handlers/templates.py
Normal file
@@ -0,0 +1,146 @@
|
||||
"""Обработчики для работы с шаблонами."""
|
||||
from typing import Optional, Dict, Any
|
||||
from telegram import Update, Message
|
||||
from telegram.ext import ContextTypes, ConversationHandler
|
||||
|
||||
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 ..utils.parsers import parse_key_value_lines
|
||||
from ..utils.validation import validate_template_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
|
||||
|
||||
async def list_templates(update: Update, context: ContextTypes.DEFAULT_TYPE) -> BotStates:
|
||||
"""Список шаблонов."""
|
||||
if not update.message:
|
||||
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("У вас пока нет шаблонов")
|
||||
return BotStates.CONVERSATION_END
|
||||
|
||||
page = context.user_data.get("tpl_page", 0)
|
||||
keyboard = get_templates_keyboard(templates, page)
|
||||
|
||||
await message.reply_text(
|
||||
"Выберите шаблон:",
|
||||
reply_markup=keyboard
|
||||
)
|
||||
return BotStates.TPL_SELECT
|
||||
Reference in New Issue
Block a user