Files
remnawave-bedolaga-telegram…/app/handlers/admin/polls.py
2025-10-23 05:28:13 +03:00

826 lines
28 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 html
import logging
from decimal import Decimal, InvalidOperation, ROUND_HALF_UP
from typing import Optional
from aiogram import Dispatcher, F, types
from aiogram.fsm.context import FSMContext
from aiogram.fsm.state import State, StatesGroup
from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings
from app.database.crud.poll import (
create_poll,
delete_poll,
get_poll_by_id,
get_poll_statistics,
list_polls,
)
from app.database.models import Poll, User
from app.handlers.admin.messages import (
get_custom_users,
get_custom_users_count,
get_target_display_name,
get_target_users,
get_target_users_count,
)
from app.keyboards.admin import get_admin_communications_submenu_keyboard
from app.localization.texts import get_texts
from app.services.poll_service import send_poll_to_users
from app.utils.decorators import admin_required, error_handler
from app.utils.validators import get_html_help_text, validate_html_tags
logger = logging.getLogger(__name__)
class PollCreationStates(StatesGroup):
waiting_for_title = State()
waiting_for_description = State()
waiting_for_reward = State()
waiting_for_questions = State()
def _build_polls_keyboard(polls: list[Poll], language: str) -> types.InlineKeyboardMarkup:
texts = get_texts(language)
keyboard: list[list[types.InlineKeyboardButton]] = []
for poll in polls[:10]:
keyboard.append(
[
types.InlineKeyboardButton(
text=f"🗳️ {poll.title[:40]}",
callback_data=f"poll_view:{poll.id}",
)
]
)
keyboard.append(
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_CREATE", " Создать опрос"),
callback_data="poll_create",
)
]
)
keyboard.append(
[
types.InlineKeyboardButton(
text=texts.BACK,
callback_data="admin_submenu_communications",
)
]
)
return types.InlineKeyboardMarkup(inline_keyboard=keyboard)
def _format_reward_text(poll: Poll, language: str) -> str:
texts = get_texts(language)
if poll.reward_enabled and poll.reward_amount_kopeks > 0:
return texts.t(
"ADMIN_POLLS_REWARD_ENABLED",
"Награда: {amount}",
).format(amount=settings.format_price(poll.reward_amount_kopeks))
return texts.t("ADMIN_POLLS_REWARD_DISABLED", "Награда отключена")
def _build_poll_details_keyboard(poll_id: int, language: str) -> types.InlineKeyboardMarkup:
texts = get_texts(language)
return types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_SEND", "📤 Отправить"),
callback_data=f"poll_send:{poll_id}",
)
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_STATS", "📊 Статистика"),
callback_data=f"poll_stats:{poll_id}",
)
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_DELETE", "🗑️ Удалить"),
callback_data=f"poll_delete:{poll_id}",
)
],
[types.InlineKeyboardButton(text=texts.t("ADMIN_POLLS_BACK", "⬅️ К списку"), callback_data="admin_polls")],
]
)
def _build_target_keyboard(poll_id: int, language: str) -> types.InlineKeyboardMarkup:
texts = get_texts(language)
return types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_ALL", "👥 Всем"),
callback_data=f"poll_target:{poll_id}:all",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_ACTIVE", "📱 С подпиской"),
callback_data=f"poll_target:{poll_id}:active",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_TRIAL", "🎁 Триал"),
callback_data=f"poll_target:{poll_id}:trial",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_NO_SUB", "❌ Без подписки"),
callback_data=f"poll_target:{poll_id}:no",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_EXPIRING", "⏰ Истекающие"),
callback_data=f"poll_target:{poll_id}:expiring",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_EXPIRED", "🔚 Истекшие"),
callback_data=f"poll_target:{poll_id}:expired",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_ACTIVE_ZERO", "🧊 Активна 0 ГБ"),
callback_data=f"poll_target:{poll_id}:active_zero",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_BROADCAST_TARGET_TRIAL_ZERO", "🥶 Триал 0 ГБ"),
callback_data=f"poll_target:{poll_id}:trial_zero",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_CUSTOM_TARGET", "⚙️ По критериям"),
callback_data=f"poll_custom_menu:{poll_id}",
)
],
[
types.InlineKeyboardButton(
text=texts.BACK,
callback_data=f"poll_view:{poll_id}",
)
],
]
)
def _build_custom_target_keyboard(poll_id: int, language: str) -> types.InlineKeyboardMarkup:
texts = get_texts(language)
return types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_TODAY", "📅 Сегодня"),
callback_data=f"poll_custom_target:{poll_id}:today",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_WEEK", "📅 За неделю"),
callback_data=f"poll_custom_target:{poll_id}:week",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_MONTH", "📅 За месяц"),
callback_data=f"poll_custom_target:{poll_id}:month",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_ACTIVE_TODAY", "⚡ Активные сегодня"),
callback_data=f"poll_custom_target:{poll_id}:active_today",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_INACTIVE_WEEK", "💤 Неактивные 7+ дней"),
callback_data=f"poll_custom_target:{poll_id}:inactive_week",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_INACTIVE_MONTH", "💤 Неактивные 30+ дней"),
callback_data=f"poll_custom_target:{poll_id}:inactive_month",
),
],
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_REFERRALS", "🤝 Через рефералов"),
callback_data=f"poll_custom_target:{poll_id}:referrals",
),
types.InlineKeyboardButton(
text=texts.t("ADMIN_CRITERIA_DIRECT", "🎯 Прямая регистрация"),
callback_data=f"poll_custom_target:{poll_id}:direct",
),
],
[types.InlineKeyboardButton(text=texts.BACK, callback_data=f"poll_send:{poll_id}")],
]
)
def _build_send_confirmation_keyboard(poll_id: int, target: str, language: str) -> types.InlineKeyboardMarkup:
texts = get_texts(language)
return types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_SEND_CONFIRM_BUTTON", "✅ Отправить"),
callback_data=f"poll_send_confirm:{poll_id}:{target}",
)
],
[
types.InlineKeyboardButton(
text=texts.BACK,
callback_data=f"poll_send:{poll_id}",
)
],
]
)
@admin_required
@error_handler
async def show_polls_panel(callback: types.CallbackQuery, db_user: User, db: AsyncSession):
polls = await list_polls(db)
texts = get_texts(db_user.language)
lines = [texts.t("ADMIN_POLLS_LIST_TITLE", "🗳️ <b>Опросы</b>"), ""]
if not polls:
lines.append(texts.t("ADMIN_POLLS_LIST_EMPTY", "Опросов пока нет."))
else:
for poll in polls[:10]:
reward = _format_reward_text(poll, db_user.language)
lines.append(
f"• <b>{html.escape(poll.title)}</b> — "
f"{texts.t('ADMIN_POLLS_QUESTIONS_COUNT', 'Вопросов: {count}').format(count=len(poll.questions))}\n"
f" {reward}"
)
await callback.message.edit_text(
"\n".join(lines),
reply_markup=_build_polls_keyboard(polls, db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def start_poll_creation(
callback: types.CallbackQuery,
db_user: User,
state: FSMContext,
db: AsyncSession,
):
texts = get_texts(db_user.language)
await state.clear()
await state.set_state(PollCreationStates.waiting_for_title)
await state.update_data(questions=[])
await callback.message.edit_text(
texts.t(
"ADMIN_POLLS_CREATION_TITLE_PROMPT",
"🗳️ <b>Создание опроса</b>\n\nВведите заголовок опроса:",
),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def process_poll_title(
message: types.Message,
db_user: User,
state: FSMContext,
db: AsyncSession,
):
if message.text == "/cancel":
await state.clear()
await message.answer(
get_texts(db_user.language).t("ADMIN_POLLS_CREATION_CANCELLED", "❌ Создание опроса отменено."),
reply_markup=get_admin_communications_submenu_keyboard(db_user.language),
)
return
title = message.text.strip()
if not title:
await message.answer("❌ Заголовок не может быть пустым. Попробуйте снова.")
return
await state.update_data(title=title)
await state.set_state(PollCreationStates.waiting_for_description)
texts = get_texts(db_user.language)
await message.answer(
texts.t(
"ADMIN_POLLS_CREATION_DESCRIPTION_PROMPT",
"Введите описание опроса. HTML разрешён.\nОтправьте /skip, чтобы пропустить.",
)
+ f"\n\n{get_html_help_text()}",
parse_mode="HTML",
)
@admin_required
@error_handler
async def process_poll_description(
message: types.Message,
db_user: User,
state: FSMContext,
db: AsyncSession,
):
texts = get_texts(db_user.language)
if message.text == "/cancel":
await state.clear()
await message.answer(
texts.t("ADMIN_POLLS_CREATION_CANCELLED", "❌ Создание опроса отменено."),
reply_markup=get_admin_communications_submenu_keyboard(db_user.language),
)
return
description: Optional[str]
if message.text == "/skip":
description = None
else:
description = message.text.strip()
is_valid, error_message = validate_html_tags(description)
if not is_valid:
await message.answer(
texts.t("ADMIN_POLLS_CREATION_INVALID_HTML", "❌ Ошибка в HTML: {error}").format(error=error_message)
)
return
await state.update_data(description=description)
await state.set_state(PollCreationStates.waiting_for_reward)
await message.answer(
texts.t(
"ADMIN_POLLS_CREATION_REWARD_PROMPT",
"Введите сумму награды в рублях. Отправьте 0 чтобы отключить награду.",
)
)
def _parse_reward_amount(message_text: str) -> int | None:
normalized = message_text.replace(" ", "").replace(",", ".")
try:
value = Decimal(normalized)
except InvalidOperation:
return None
if value < 0:
value = Decimal(0)
kopeks = int((value * 100).quantize(Decimal("1"), rounding=ROUND_HALF_UP))
return max(0, kopeks)
@admin_required
@error_handler
async def process_poll_reward(
message: types.Message,
db_user: User,
state: FSMContext,
db: AsyncSession,
):
texts = get_texts(db_user.language)
if message.text == "/cancel":
await state.clear()
await message.answer(
texts.t("ADMIN_POLLS_CREATION_CANCELLED", "❌ Создание опроса отменено."),
reply_markup=get_admin_communications_submenu_keyboard(db_user.language),
)
return
reward_kopeks = _parse_reward_amount(message.text)
if reward_kopeks is None:
await message.answer(texts.t("ADMIN_POLLS_CREATION_REWARD_INVALID", "❌ Некорректная сумма. Попробуйте ещё раз."))
return
reward_enabled = reward_kopeks > 0
await state.update_data(
reward_enabled=reward_enabled,
reward_amount_kopeks=reward_kopeks,
)
await state.set_state(PollCreationStates.waiting_for_questions)
prompt = texts.t(
"ADMIN_POLLS_CREATION_QUESTION_PROMPT",
(
"Введите вопрос и варианты ответов.\n"
"Каждая строка — отдельный вариант.\n"
"Первая строка — текст вопроса.\n"
"Отправьте /done, когда вопросы будут добавлены."
),
)
await message.answer(prompt)
@admin_required
@error_handler
async def process_poll_question(
message: types.Message,
db_user: User,
state: FSMContext,
db: AsyncSession,
):
texts = get_texts(db_user.language)
if message.text == "/cancel":
await state.clear()
await message.answer(
texts.t("ADMIN_POLLS_CREATION_CANCELLED", "❌ Создание опроса отменено."),
reply_markup=get_admin_communications_submenu_keyboard(db_user.language),
)
return
if message.text == "/done":
data = await state.get_data()
questions = data.get("questions", [])
if not questions:
await message.answer(
texts.t("ADMIN_POLLS_CREATION_NEEDS_QUESTION", "❌ Добавьте хотя бы один вопрос."),
)
return
title = data.get("title")
description = data.get("description")
reward_enabled = data.get("reward_enabled", False)
reward_amount = data.get("reward_amount_kopeks", 0)
poll = await create_poll(
db,
title=title,
description=description,
reward_enabled=reward_enabled,
reward_amount_kopeks=reward_amount,
created_by=db_user.id,
questions=questions,
)
await state.clear()
reward_text = _format_reward_text(poll, db_user.language)
await message.answer(
texts.t(
"ADMIN_POLLS_CREATION_FINISHED",
"✅ Опрос «{title}» создан. Вопросов: {count}. {reward}",
).format(
title=poll.title,
count=len(poll.questions),
reward=reward_text,
),
reply_markup=_build_polls_keyboard(await list_polls(db), db_user.language),
parse_mode="HTML",
)
return
lines = [line.strip() for line in message.text.splitlines() if line.strip()]
if len(lines) < 3:
await message.answer(
texts.t(
"ADMIN_POLLS_CREATION_MIN_OPTIONS",
"❌ Нужен вопрос и минимум два варианта ответа.",
)
)
return
question_text = lines[0]
options = lines[1:]
data = await state.get_data()
questions = data.get("questions", [])
questions.append({"text": question_text, "options": options})
await state.update_data(questions=questions)
await message.answer(
texts.t(
"ADMIN_POLLS_CREATION_ADDED_QUESTION",
"Вопрос добавлен: «{question}». Добавьте следующий вопрос или отправьте /done.",
).format(question=question_text),
parse_mode="HTML",
)
async def _render_poll_details(poll: Poll, language: str) -> str:
texts = get_texts(language)
lines = [f"🗳️ <b>{html.escape(poll.title)}</b>"]
if poll.description:
lines.append(poll.description)
lines.append(_format_reward_text(poll, language))
lines.append(
texts.t("ADMIN_POLLS_QUESTIONS_COUNT", "Вопросов: {count}").format(
count=len(poll.questions)
)
)
if poll.questions:
lines.append("")
lines.append(texts.t("ADMIN_POLLS_QUESTION_LIST_HEADER", "<b>Вопросы:</b>"))
for idx, question in enumerate(sorted(poll.questions, key=lambda q: q.order), start=1):
lines.append(f"{idx}. {html.escape(question.text)}")
for option in sorted(question.options, key=lambda o: o.order):
lines.append(
texts.t("ADMIN_POLLS_OPTION_BULLET", "{option}").format(
option=html.escape(option.text)
)
)
return "\n".join(lines)
@admin_required
@error_handler
async def show_poll_details(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
poll_id = int(callback.data.split(":")[1])
poll = await get_poll_by_id(db, poll_id)
if not poll:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
text = await _render_poll_details(poll, db_user.language)
await callback.message.edit_text(
text,
reply_markup=_build_poll_details_keyboard(poll.id, db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def start_poll_send(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
poll_id = int(callback.data.split(":")[1])
poll = await get_poll_by_id(db, poll_id)
if not poll:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
texts = get_texts(db_user.language)
await callback.message.edit_text(
texts.t("ADMIN_POLLS_SEND_CHOOSE_TARGET", "🎯 Выберите аудиторию для отправки опроса:"),
reply_markup=_build_target_keyboard(poll.id, db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def show_custom_target_menu(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
poll_id = int(callback.data.split(":")[1])
await callback.message.edit_text(
get_texts(db_user.language).t(
"ADMIN_POLLS_CUSTOM_PROMPT",
"Выберите дополнительный критерий аудитории:",
),
reply_markup=_build_custom_target_keyboard(poll_id, db_user.language),
)
await callback.answer()
async def _show_send_confirmation(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
poll_id: int,
target: str,
user_count: int,
):
poll = await get_poll_by_id(db, poll_id)
if not poll:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
audience_name = get_target_display_name(target)
texts = get_texts(db_user.language)
confirmation_text = texts.t(
"ADMIN_POLLS_SEND_CONFIRM",
"📤 Отправить опрос «{title}» аудитории «{audience}»? Пользователей: {count}",
).format(title=poll.title, audience=audience_name, count=user_count)
await callback.message.edit_text(
confirmation_text,
reply_markup=_build_send_confirmation_keyboard(poll_id, target, db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def select_poll_target(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
_, payload = callback.data.split(":", 1)
poll_id_str, target = payload.split(":", 1)
poll_id = int(poll_id_str)
user_count = await get_target_users_count(db, target)
await _show_send_confirmation(callback, db_user, db, poll_id, target, user_count)
@admin_required
@error_handler
async def select_custom_poll_target(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
_, payload = callback.data.split(":", 1)
poll_id_str, criteria = payload.split(":", 1)
poll_id = int(poll_id_str)
user_count = await get_custom_users_count(db, criteria)
await _show_send_confirmation(callback, db_user, db, poll_id, f"custom_{criteria}", user_count)
@admin_required
@error_handler
async def confirm_poll_send(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
_, payload = callback.data.split(":", 1)
poll_id_str, target = payload.split(":", 1)
poll_id = int(poll_id_str)
poll = await get_poll_by_id(db, poll_id)
if not poll:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
if target.startswith("custom_"):
users = await get_custom_users(db, target.replace("custom_", ""))
else:
users = await get_target_users(db, target)
texts = get_texts(db_user.language)
await callback.message.edit_text(
texts.t("ADMIN_POLLS_SENDING", "📤 Запускаю отправку опроса..."),
parse_mode="HTML",
)
result = await send_poll_to_users(callback.bot, db, poll, users)
result_text = texts.t(
"ADMIN_POLLS_SEND_RESULT",
"📤 Отправка завершена\nУспешно: {sent}\nОшибок: {failed}\nПропущено: {skipped}\nВсего: {total}",
).format(**result)
await callback.message.edit_text(
result_text,
reply_markup=_build_poll_details_keyboard(poll.id, db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def show_poll_stats(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
poll_id = int(callback.data.split(":")[1])
poll = await get_poll_by_id(db, poll_id)
if not poll:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
stats = await get_poll_statistics(db, poll_id)
texts = get_texts(db_user.language)
reward_sum = settings.format_price(stats["reward_sum_kopeks"])
lines = [texts.t("ADMIN_POLLS_STATS_HEADER", "📊 <b>Статистика опроса</b>"), ""]
lines.append(
texts.t(
"ADMIN_POLLS_STATS_OVERVIEW",
"Всего приглашено: {total}\nЗавершили: {completed}\nВыплачено наград: {reward}",
).format(
total=stats["total_responses"],
completed=stats["completed_responses"],
reward=reward_sum,
)
)
for question in stats["questions"]:
lines.append("")
lines.append(f"<b>{html.escape(question['text'])}</b>")
for option in question["options"]:
lines.append(
texts.t(
"ADMIN_POLLS_STATS_OPTION_LINE",
"{option}: {count}",
).format(option=html.escape(option["text"]), count=option["count"])
)
await callback.message.edit_text(
"\n".join(lines),
reply_markup=_build_poll_details_keyboard(poll.id, db_user.language),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def confirm_poll_delete(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
poll_id = int(callback.data.split(":")[1])
poll = await get_poll_by_id(db, poll_id)
if not poll:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
texts = get_texts(db_user.language)
await callback.message.edit_text(
texts.t(
"ADMIN_POLLS_CONFIRM_DELETE",
"Вы уверены, что хотите удалить опрос «{title}»?",
).format(title=poll.title),
reply_markup=types.InlineKeyboardMarkup(
inline_keyboard=[
[
types.InlineKeyboardButton(
text=texts.t("ADMIN_POLLS_DELETE", "🗑️ Удалить"),
callback_data=f"poll_delete_confirm:{poll_id}",
)
],
[
types.InlineKeyboardButton(
text=texts.BACK,
callback_data=f"poll_view:{poll_id}",
)
],
]
),
parse_mode="HTML",
)
await callback.answer()
@admin_required
@error_handler
async def delete_poll_handler(
callback: types.CallbackQuery,
db_user: User,
db: AsyncSession,
):
poll_id = int(callback.data.split(":")[1])
success = await delete_poll(db, poll_id)
texts = get_texts(db_user.language)
if success:
await callback.message.edit_text(
texts.t("ADMIN_POLLS_DELETED", "🗑️ Опрос удалён."),
reply_markup=_build_polls_keyboard(await list_polls(db), db_user.language),
)
else:
await callback.answer("❌ Опрос не найден", show_alert=True)
return
await callback.answer()
def register_handlers(dp: Dispatcher):
dp.callback_query.register(show_polls_panel, F.data == "admin_polls")
dp.callback_query.register(start_poll_creation, F.data == "poll_create")
dp.callback_query.register(show_poll_details, F.data.startswith("poll_view:"))
dp.callback_query.register(start_poll_send, F.data.startswith("poll_send:"))
dp.callback_query.register(show_custom_target_menu, F.data.startswith("poll_custom_menu:"))
dp.callback_query.register(select_poll_target, F.data.startswith("poll_target:"))
dp.callback_query.register(select_custom_poll_target, F.data.startswith("poll_custom_target:"))
dp.callback_query.register(confirm_poll_send, F.data.startswith("poll_send_confirm:"))
dp.callback_query.register(show_poll_stats, F.data.startswith("poll_stats:"))
dp.callback_query.register(confirm_poll_delete, F.data.startswith("poll_delete:"))
dp.callback_query.register(delete_poll_handler, F.data.startswith("poll_delete_confirm:"))
dp.message.register(process_poll_title, PollCreationStates.waiting_for_title)
dp.message.register(process_poll_description, PollCreationStates.waiting_for_description)
dp.message.register(process_poll_reward, PollCreationStates.waiting_for_reward)
dp.message.register(process_poll_question, PollCreationStates.waiting_for_questions)