Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- import asyncio
- import os
- from dataclasses import dataclass
- from datetime import datetime
- import pytz
- from aiogram import Bot, Dispatcher, F, Router
- from aiogram.client.default import DefaultBotProperties
- from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, BotCommand
- from aiogram.filters import CommandStart, Command, StateFilter
- from aiogram.fsm.state import StatesGroup, State
- from aiogram.fsm.context import FSMContext
- from aiogram.fsm.storage.memory import MemoryStorage
- TOKEN = os.getenv("BOT_TOKEN", "8198963636:AAG_o5luzCC6t_rEve34T9FiBC4Z6xHTFiE")
- FEEDBACK_CHANNEL_ID = int(os.getenv("FEEDBACK_CHANNEL_ID", "-4931351107"))
- SUBMITTED: set[int] = set()
- TZ = pytz.timezone("Europe/Moscow")
- def has_submitted(user_id: int) -> bool:
- return user_id in SUBMITTED
- def mark_submitted(user_id: int) -> None:
- SUBMITTED.add(user_id)
- async def guard_single_feedback(m: Message) -> bool:
- u = m.from_user
- if not u:
- return False
- if has_submitted(u.id):
- await m.answer("Ты уже оставил один отзыв. К сожалению, отправить его второй раз нельзя.")
- return True
- return False
- RATES = {
- "1": {"emoji": "😞", "label": "Плохо"},
- "2": {"emoji": "😐", "label": "Нормально"},
- "3": {"emoji": "🤩", "label": "Отлично"},
- }
- def rate_kb() -> InlineKeyboardMarkup:
- return InlineKeyboardMarkup(inline_keyboard=[[
- InlineKeyboardButton(text="😞", callback_data="rate:1"),
- InlineKeyboardButton(text="😐", callback_data="rate:2"),
- InlineKeyboardButton(text="🤩", callback_data="rate:3"),
- ]])
- def skip_kb() -> InlineKeyboardMarkup:
- return InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Пропустить", callback_data="skip")]])
- class FB(StatesGroup):
- rating = State()
- liked = State()
- disliked = State()
- extra = State()
- @dataclass
- class Draft:
- rate: str | None = None
- liked: str | None = None
- disliked: str | None = None
- extra: str | None = None
- router = Router()
- @router.message(CommandStart())
- async def start(m: Message, state: FSMContext):
- await state.clear()
- if await guard_single_feedback(m):
- return
- await state.update_data(
- draft=Draft().__dict__,
- user_id=m.from_user.id if m.from_user else None,
- user_name=m.from_user.full_name if m.from_user else "—",
- user_username=m.from_user.username if m.from_user else None
- )
- await state.set_state(FB.rating)
- await m.answer(
- "Привет! Прошло наше занятие — удели, пожалуйста, пару минут на обратную связь.\n\n"
- "Как тебе занятие? Выбери смайлик:",
- reply_markup=rate_kb()
- )
- @router.message(Command("feedback"))
- async def feedback_cmd(m: Message, state: FSMContext):
- if await guard_single_feedback(m):
- return
- await start(m, state)
- @router.callback_query(F.data.startswith("rate:"), FB.rating)
- async def choose_rate(q: CallbackQuery, state: FSMContext):
- _, val = q.data.split(":", 1)
- data = await state.get_data()
- draft = data.get("draft") or {}
- draft["rate"] = val
- await state.update_data(draft=draft)
- await state.set_state(FB.liked)
- await q.message.answer("Что тебе понравилось?")
- await q.answer()
- @router.message(StateFilter(FB.liked), F.text)
- async def got_liked(m: Message, state: FSMContext):
- try:
- data = await state.get_data()
- draft = data.get("draft") or {}
- draft["liked"] = (m.text or "").strip()
- await state.update_data(draft=draft)
- await state.set_state(FB.disliked)
- await m.answer("Что тебе НЕ понравилось?")
- except Exception:
- await m.answer("Ошибка на шаге «что понравилось». Попробуй ещё раз: /feedback")
- @router.message(StateFilter(FB.disliked), F.text)
- async def got_disliked(m: Message, state: FSMContext):
- try:
- data = await state.get_data()
- draft = data.get("draft") or {}
- draft["disliked"] = (m.text or "").strip()
- await state.update_data(draft=draft)
- await state.set_state(FB.extra)
- await m.answer("Если остались мысли — напиши. Это необязательно.", reply_markup=skip_kb())
- except Exception:
- await m.answer("Ошибка на шаге «что не понравилось». Попробуй ещё раз: /feedback")
- @router.callback_query(F.data == "skip", FB.extra)
- async def extra_skip(q: CallbackQuery, state: FSMContext):
- data = await state.get_data()
- draft = data.get("draft") or {}
- draft["extra"] = ""
- await state.update_data(draft=draft)
- await finalize(q.message, state)
- await q.answer()
- @router.message(StateFilter(FB.extra), F.text)
- async def got_extra(m: Message, state: FSMContext):
- data = await state.get_data()
- draft = data.get("draft") or {}
- draft["extra"] = (m.text or "").strip()
- await state.update_data(draft=draft)
- await finalize(m, state)
- async def finalize(msg: Message, state: FSMContext):
- try:
- data = await state.get_data()
- d = Draft(**(data.get("draft") or {}))
- user_id = data.get("user_id")
- name = data.get("user_name", "—")
- user_username = data.get("user_username")
- username = f"@{user_username}" if user_username else f"id:{user_id if user_id else '—'}"
- link = f"tg://user?id={user_id}" if user_id else "—"
- rate = RATES.get(d.rate or "2", RATES["2"])
- dt = datetime.now(TZ).strftime("%Y-%m-%d %H:%M")
- liked = d.liked.strip() if d.liked else "—"
- disliked = d.disliked.strip() if d.disliked else "—"
- extra = d.extra.strip() if d.extra else "—"
- text = (
- "📝 Отзыв о занятии\n\n"
- f"👤 Имя: {name}\n"
- f"🔗 Профиль: {link}\n"
- f"🆔 ID: {user_id if user_id else '—'}\n"
- f"@username: {username}\n"
- f"🗓 {dt} (МСК)\n"
- f"🙂 Оценка: {rate['emoji']} {rate['label']}\n\n"
- f"Что понравилось:\n{liked}\n\n"
- f"Что не понравилось:\n{disliked}\n\n"
- f"Дополнительно:\n{extra}"
- )
- try:
- await msg.bot.send_message(chat_id=FEEDBACK_CHANNEL_ID, text=text)
- except Exception:
- await msg.answer("Не удалось отправить отзыв в канал.\n\n" + text)
- else:
- if user_id:
- mark_submitted(user_id)
- await msg.answer("Спасибо за обратную связь, до новых встреч!")
- except Exception:
- await msg.answer("Не удалось завершить отзыв. Попробуй снова: /feedback")
- finally:
- await state.clear()
- async def on_startup(bot: Bot):
- try:
- await bot.delete_webhook(drop_pending_updates=True)
- except Exception:
- pass
- try:
- await bot.set_my_commands([
- BotCommand(command="start", description="Начать опрос"),
- BotCommand(command="feedback", description="Оставить отзыв"),
- ])
- except Exception:
- pass
- async def main():
- bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=None))
- dp = Dispatcher(storage=MemoryStorage())
- dp.include_router(router)
- try:
- await on_startup(bot)
- await dp.start_polling(bot)
- finally:
- await bot.session.close()
- if __name__ == "__main__":
- asyncio.run(main())
Advertisement
Add Comment
Please, Sign In to add comment