VoityVIT

Bot_ATM.py

Sep 29th, 2025 (edited)
51
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 7.85 KB | None | 0 0
  1. import asyncio
  2. import os
  3. from dataclasses import dataclass
  4. from datetime import datetime
  5.  
  6. import pytz
  7. from aiogram import Bot, Dispatcher, F, Router
  8. from aiogram.client.default import DefaultBotProperties
  9. from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton, BotCommand
  10. from aiogram.filters import CommandStart, Command, StateFilter
  11. from aiogram.fsm.state import StatesGroup, State
  12. from aiogram.fsm.context import FSMContext
  13. from aiogram.fsm.storage.memory import MemoryStorage
  14.  
  15. TOKEN = os.getenv("BOT_TOKEN", "8198963636:AAG_o5luzCC6t_rEve34T9FiBC4Z6xHTFiE")
  16. FEEDBACK_CHANNEL_ID = int(os.getenv("FEEDBACK_CHANNEL_ID", "-4931351107"))
  17. SUBMITTED: set[int] = set()
  18.  
  19. TZ = pytz.timezone("Europe/Moscow")
  20.  
  21. def has_submitted(user_id: int) -> bool:
  22.     return user_id in SUBMITTED
  23.  
  24. def mark_submitted(user_id: int) -> None:
  25.     SUBMITTED.add(user_id)
  26.  
  27. async def guard_single_feedback(m: Message) -> bool:
  28.     u = m.from_user
  29.     if not u:
  30.         return False
  31.     if has_submitted(u.id):
  32.         await m.answer("Ты уже оставил один отзыв. К сожалению, отправить его второй раз нельзя.")
  33.         return True
  34.     return False
  35.  
  36. RATES = {
  37.     "1": {"emoji": "😞", "label": "Плохо"},
  38.     "2": {"emoji": "😐", "label": "Нормально"},
  39.     "3": {"emoji": "🤩", "label": "Отлично"},
  40. }
  41.  
  42. def rate_kb() -> InlineKeyboardMarkup:
  43.     return InlineKeyboardMarkup(inline_keyboard=[[
  44.         InlineKeyboardButton(text="😞", callback_data="rate:1"),
  45.         InlineKeyboardButton(text="😐", callback_data="rate:2"),
  46.         InlineKeyboardButton(text="🤩", callback_data="rate:3"),
  47.     ]])
  48.  
  49. def skip_kb() -> InlineKeyboardMarkup:
  50.     return InlineKeyboardMarkup(inline_keyboard=[[InlineKeyboardButton(text="Пропустить", callback_data="skip")]])
  51.  
  52. class FB(StatesGroup):
  53.     rating = State()
  54.     liked = State()
  55.     disliked = State()
  56.     extra = State()
  57.  
  58. @dataclass
  59. class Draft:
  60.     rate: str | None = None
  61.     liked: str | None = None
  62.     disliked: str | None = None
  63.     extra: str | None = None
  64.  
  65. router = Router()
  66.  
  67. @router.message(CommandStart())
  68. async def start(m: Message, state: FSMContext):
  69.     await state.clear()
  70.     if await guard_single_feedback(m):
  71.         return
  72.     await state.update_data(
  73.         draft=Draft().__dict__,
  74.         user_id=m.from_user.id if m.from_user else None,
  75.         user_name=m.from_user.full_name if m.from_user else "—",
  76.         user_username=m.from_user.username if m.from_user else None
  77.     )
  78.     await state.set_state(FB.rating)
  79.     await m.answer(
  80.         "Привет! Прошло наше занятие — удели, пожалуйста, пару минут на обратную связь.\n\n"
  81.         "Как тебе занятие? Выбери смайлик:",
  82.         reply_markup=rate_kb()
  83.     )
  84.  
  85. @router.message(Command("feedback"))
  86. async def feedback_cmd(m: Message, state: FSMContext):
  87.     if await guard_single_feedback(m):
  88.         return
  89.     await start(m, state)
  90.  
  91. @router.callback_query(F.data.startswith("rate:"), FB.rating)
  92. async def choose_rate(q: CallbackQuery, state: FSMContext):
  93.     _, val = q.data.split(":", 1)
  94.     data = await state.get_data()
  95.     draft = data.get("draft") or {}
  96.     draft["rate"] = val
  97.     await state.update_data(draft=draft)
  98.     await state.set_state(FB.liked)
  99.     await q.message.answer("Что тебе понравилось?")
  100.     await q.answer()
  101.  
  102. @router.message(StateFilter(FB.liked), F.text)
  103. async def got_liked(m: Message, state: FSMContext):
  104.     try:
  105.         data = await state.get_data()
  106.         draft = data.get("draft") or {}
  107.         draft["liked"] = (m.text or "").strip()
  108.         await state.update_data(draft=draft)
  109.         await state.set_state(FB.disliked)
  110.         await m.answer("Что тебе НЕ понравилось?")
  111.     except Exception:
  112.         await m.answer("Ошибка на шаге «что понравилось». Попробуй ещё раз: /feedback")
  113.  
  114. @router.message(StateFilter(FB.disliked), F.text)
  115. async def got_disliked(m: Message, state: FSMContext):
  116.     try:
  117.         data = await state.get_data()
  118.         draft = data.get("draft") or {}
  119.         draft["disliked"] = (m.text or "").strip()
  120.         await state.update_data(draft=draft)
  121.         await state.set_state(FB.extra)
  122.         await m.answer("Если остались мысли — напиши. Это необязательно.", reply_markup=skip_kb())
  123.     except Exception:
  124.         await m.answer("Ошибка на шаге «что не понравилось». Попробуй ещё раз: /feedback")
  125.  
  126. @router.callback_query(F.data == "skip", FB.extra)
  127. async def extra_skip(q: CallbackQuery, state: FSMContext):
  128.     data = await state.get_data()
  129.     draft = data.get("draft") or {}
  130.     draft["extra"] = ""
  131.     await state.update_data(draft=draft)
  132.     await finalize(q.message, state)
  133.     await q.answer()
  134.  
  135. @router.message(StateFilter(FB.extra), F.text)
  136. async def got_extra(m: Message, state: FSMContext):
  137.     data = await state.get_data()
  138.     draft = data.get("draft") or {}
  139.     draft["extra"] = (m.text or "").strip()
  140.     await state.update_data(draft=draft)
  141.     await finalize(m, state)
  142.  
  143. async def finalize(msg: Message, state: FSMContext):
  144.     try:
  145.         data = await state.get_data()
  146.         d = Draft(**(data.get("draft") or {}))
  147.  
  148.         user_id = data.get("user_id")
  149.         name = data.get("user_name", "—")
  150.         user_username = data.get("user_username")
  151.  
  152.         username = f"@{user_username}" if user_username else f"id:{user_id if user_id else '—'}"
  153.         link = f"tg://user?id={user_id}" if user_id else "—"
  154.  
  155.         rate = RATES.get(d.rate or "2", RATES["2"])
  156.         dt = datetime.now(TZ).strftime("%Y-%m-%d %H:%M")
  157.  
  158.         liked = d.liked.strip() if d.liked else "—"
  159.         disliked = d.disliked.strip() if d.disliked else "—"
  160.         extra = d.extra.strip() if d.extra else "—"
  161.  
  162.         text = (
  163.             "📝 Отзыв о занятии\n\n"
  164.             f"👤 Имя: {name}\n"
  165.             f"🔗 Профиль: {link}\n"
  166.             f"🆔 ID: {user_id if user_id else '—'}\n"
  167.             f"@username: {username}\n"
  168.             f"🗓 {dt} (МСК)\n"
  169.             f"🙂 Оценка: {rate['emoji']} {rate['label']}\n\n"
  170.             f"Что понравилось:\n{liked}\n\n"
  171.             f"Что не понравилось:\n{disliked}\n\n"
  172.             f"Дополнительно:\n{extra}"
  173.         )
  174.  
  175.         try:
  176.             await msg.bot.send_message(chat_id=FEEDBACK_CHANNEL_ID, text=text)
  177.         except Exception:
  178.             await msg.answer("Не удалось отправить отзыв в канал.\n\n" + text)
  179.         else:
  180.             if user_id:
  181.                 mark_submitted(user_id)
  182.             await msg.answer("Спасибо за обратную связь, до новых встреч!")
  183.     except Exception:
  184.         await msg.answer("Не удалось завершить отзыв. Попробуй снова: /feedback")
  185.     finally:
  186.         await state.clear()
  187.  
  188. async def on_startup(bot: Bot):
  189.     try:
  190.         await bot.delete_webhook(drop_pending_updates=True)
  191.     except Exception:
  192.         pass
  193.     try:
  194.         await bot.set_my_commands([
  195.             BotCommand(command="start", description="Начать опрос"),
  196.             BotCommand(command="feedback", description="Оставить отзыв"),
  197.         ])
  198.     except Exception:
  199.         pass
  200.  
  201. async def main():
  202.     bot = Bot(token=TOKEN, default=DefaultBotProperties(parse_mode=None))
  203.     dp = Dispatcher(storage=MemoryStorage())
  204.     dp.include_router(router)
  205.     try:
  206.         await on_startup(bot)
  207.         await dp.start_polling(bot)
  208.     finally:
  209.         await bot.session.close()
  210.  
  211. if __name__ == "__main__":
  212.     asyncio.run(main())
Advertisement
Add Comment
Please, Sign In to add comment