feat: реализован MVP Telegram-бота для записи
* настроена структура проекта * подключены handlers и routers * реализовано главное меню * добавлены разделы: профиль, контакты, о компании, мои записи * реализована FSM-машина состояний для записи * добавлен сервисный слой booking_service * реализовано сохранение записей в памяти * добавлено подтверждение записи * реализована отмена записи * исправлены конфликты FSM и кнопок меню * добавлена отдельная клавиатура во время записи * выполнен рефакторинг импортов и структуры проекта * улучшен пользовательский сценарий записи
This commit is contained in:
+2
-1
@@ -1 +1,2 @@
|
|||||||
BOOKINGS = {}
|
users = {}
|
||||||
|
bookings = []
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
@router.message(F.text == "ℹ️ О компании")
|
||||||
|
async def about(message: Message, state: FSMContext):
|
||||||
|
await state.clear() # 🔥 СБРОС FSM
|
||||||
|
|
||||||
|
await message.answer("Мы demo SPA бот 💆♂️")
|
||||||
@@ -0,0 +1,122 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
from aiogram.filters import StateFilter
|
||||||
|
|
||||||
|
from app.states.booking import BookingState
|
||||||
|
from app.services.booking_service import create_booking
|
||||||
|
from app.keyboards.menu import menu, cancel_menu
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
|
||||||
|
# отмена записи на любом шаге
|
||||||
|
@router.message(F.text.lower() == "отмена")
|
||||||
|
async def cancel(message: Message, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
await message.answer(
|
||||||
|
"❌ Запись отменена",
|
||||||
|
reply_markup=menu
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# старт записи
|
||||||
|
@router.message(F.text == "📅 Записаться")
|
||||||
|
async def start_booking(message: Message, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await state.set_state(BookingState.service)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"Выберите услугу:\n"
|
||||||
|
"1. Массаж\n"
|
||||||
|
"2. SPA\n"
|
||||||
|
"3. Косметология",
|
||||||
|
reply_markup=cancel_menu
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# выбор услуги
|
||||||
|
@router.message(StateFilter(BookingState.service))
|
||||||
|
async def choose_service(message: Message, state: FSMContext):
|
||||||
|
|
||||||
|
forbidden = [
|
||||||
|
"📅 Записаться",
|
||||||
|
"👤 Профиль",
|
||||||
|
"📖 Мои записи",
|
||||||
|
"ℹ️ О компании",
|
||||||
|
"📞 Контакты"
|
||||||
|
]
|
||||||
|
|
||||||
|
if message.text in forbidden:
|
||||||
|
await message.answer(
|
||||||
|
"⚠️ Сначала завершите запись или напишите 'отмена'"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(service=message.text)
|
||||||
|
|
||||||
|
await state.set_state(BookingState.date)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"Введите дату (например 20.06)"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# выбор даты
|
||||||
|
@router.message(StateFilter(BookingState.date))
|
||||||
|
async def choose_date(message: Message, state: FSMContext):
|
||||||
|
|
||||||
|
forbidden = [
|
||||||
|
"📅 Записаться",
|
||||||
|
"👤 Профиль",
|
||||||
|
"📖 Мои записи",
|
||||||
|
"ℹ️ О компании",
|
||||||
|
"📞 Контакты"
|
||||||
|
]
|
||||||
|
|
||||||
|
if message.text in forbidden:
|
||||||
|
await message.answer(
|
||||||
|
"⚠️ Сначала завершите запись или напишите 'отмена'"
|
||||||
|
)
|
||||||
|
return
|
||||||
|
|
||||||
|
await state.update_data(date=message.text)
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
await state.set_state(BookingState.confirm)
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
f"Услуга: {data['service']}\n"
|
||||||
|
f"Дата: {data['date']}\n\n"
|
||||||
|
f"Напишите 'да' для подтверждения"
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
# подтверждение
|
||||||
|
@router.message(StateFilter(BookingState.confirm))
|
||||||
|
async def confirm_booking(message: Message, state: FSMContext):
|
||||||
|
|
||||||
|
if message.text.lower() != "да":
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"❌ Запись отменена",
|
||||||
|
reply_markup=menu
|
||||||
|
)
|
||||||
|
|
||||||
|
data = await state.get_data()
|
||||||
|
|
||||||
|
create_booking(
|
||||||
|
user_id=message.from_user.id,
|
||||||
|
service=data["service"],
|
||||||
|
date=data["date"]
|
||||||
|
)
|
||||||
|
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"✅ Вы успешно записаны!",
|
||||||
|
reply_markup=menu
|
||||||
|
)
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
@router.message(F.text == "📞 Контакты")
|
||||||
|
async def contacts(message: Message, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
await message.answer("📍 Москва\n☎ +7 999 999 99 99")
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
|
from app.db.memory import bookings
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
@router.message(F.text == "📖 Мои записи")
|
||||||
|
async def my_bookings(message: Message, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
user_id = message.from_user.id
|
||||||
|
|
||||||
|
user_bookings = [
|
||||||
|
b for b in bookings if b["user_id"] == user_id
|
||||||
|
]
|
||||||
|
|
||||||
|
if not user_bookings:
|
||||||
|
await message.answer("У вас нет записей")
|
||||||
|
return
|
||||||
|
|
||||||
|
text = "📖 Ваши записи:\n\n"
|
||||||
|
|
||||||
|
for b in user_bookings:
|
||||||
|
text += f"💆 {b['service']} — 📅 {b['date']}\n"
|
||||||
|
|
||||||
|
await message.answer(text)
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
from aiogram import Router, F
|
||||||
|
from aiogram.types import Message
|
||||||
|
from aiogram.fsm.context import FSMContext
|
||||||
|
|
||||||
|
from app.db.memory import users
|
||||||
|
|
||||||
|
router = Router()
|
||||||
|
|
||||||
|
@router.message(F.text == "👤 Профиль")
|
||||||
|
async def profile(message: Message, state: FSMContext):
|
||||||
|
await state.clear()
|
||||||
|
|
||||||
|
user = users.get(message.from_user.id)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
await message.answer("Профиль не найден")
|
||||||
|
return
|
||||||
|
|
||||||
|
await message.answer(f"👤 Имя: {user['name']}")
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
from aiogram import Router
|
||||||
|
router = Router()
|
||||||
+14
-3
@@ -1,8 +1,19 @@
|
|||||||
from aiogram import Router, F
|
from aiogram import Router
|
||||||
|
from aiogram.filters import CommandStart
|
||||||
from aiogram.types import Message
|
from aiogram.types import Message
|
||||||
|
|
||||||
|
from app.keyboards.menu import menu
|
||||||
|
from app.db.memory import users
|
||||||
|
|
||||||
router = Router()
|
router = Router()
|
||||||
|
|
||||||
@router.message(F.text == "/start")
|
@router.message(CommandStart())
|
||||||
async def start(message: Message):
|
async def start(message: Message):
|
||||||
await message.answer("👋 Бот запущен (чистый старт)")
|
users[message.from_user.id] = {
|
||||||
|
"name": message.from_user.full_name
|
||||||
|
}
|
||||||
|
|
||||||
|
await message.answer(
|
||||||
|
"👋 Добро пожаловать в Demo Bot",
|
||||||
|
reply_markup=menu
|
||||||
|
)
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton
|
||||||
|
|
||||||
|
# Главное меню
|
||||||
|
menu = ReplyKeyboardMarkup(
|
||||||
|
keyboard=[
|
||||||
|
[KeyboardButton(text="📅 Записаться")],
|
||||||
|
[
|
||||||
|
KeyboardButton(text="👤 Профиль"),
|
||||||
|
KeyboardButton(text="📖 Мои записи")
|
||||||
|
],
|
||||||
|
[
|
||||||
|
KeyboardButton(text="ℹ️ О компании"),
|
||||||
|
KeyboardButton(text="📞 Контакты")
|
||||||
|
]
|
||||||
|
],
|
||||||
|
resize_keyboard=True
|
||||||
|
)
|
||||||
|
|
||||||
|
# Меню во время записи
|
||||||
|
cancel_menu = ReplyKeyboardMarkup(
|
||||||
|
keyboard=[
|
||||||
|
[KeyboardButton(text="Отмена")]
|
||||||
|
],
|
||||||
|
resize_keyboard=True
|
||||||
|
)
|
||||||
+33
@@ -0,0 +1,33 @@
|
|||||||
|
import asyncio
|
||||||
|
|
||||||
|
from aiogram import Bot, Dispatcher
|
||||||
|
|
||||||
|
from app.config import BOT_TOKEN
|
||||||
|
|
||||||
|
from app.handlers.start import router as start_router
|
||||||
|
from app.handlers.booking import router as booking_router
|
||||||
|
from app.handlers.profile import router as profile_router
|
||||||
|
from app.handlers.mybookings import router as mybooking_router
|
||||||
|
from app.handlers.about import router as about_router
|
||||||
|
from app.handlers.contacts import router as contacts_router
|
||||||
|
|
||||||
|
|
||||||
|
async def main():
|
||||||
|
bot = Bot(token=BOT_TOKEN)
|
||||||
|
dp = Dispatcher()
|
||||||
|
|
||||||
|
# подключаем все роутеры
|
||||||
|
dp.include_router(start_router)
|
||||||
|
dp.include_router(booking_router)
|
||||||
|
dp.include_router(profile_router)
|
||||||
|
dp.include_router(mybooking_router)
|
||||||
|
dp.include_router(about_router)
|
||||||
|
dp.include_router(contacts_router)
|
||||||
|
|
||||||
|
print("Bot started...")
|
||||||
|
|
||||||
|
await dp.start_polling(bot)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
asyncio.run(main())
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
from app.db.memory import bookings
|
||||||
|
|
||||||
|
def create_booking(user_id: int, service: str, date: str):
|
||||||
|
booking = {
|
||||||
|
"user_id": user_id,
|
||||||
|
"service": service,
|
||||||
|
"date": date,
|
||||||
|
"status": "active"
|
||||||
|
}
|
||||||
|
|
||||||
|
bookings.append(booking)
|
||||||
|
return booking
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
from aiogram.fsm.state import StatesGroup, State
|
||||||
|
|
||||||
|
class BookingState(StatesGroup):
|
||||||
|
service = State()
|
||||||
|
date = State()
|
||||||
|
confirm = State()
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import asyncio
|
|
||||||
import logging
|
|
||||||
|
|
||||||
from aiogram import Bot, Dispatcher
|
|
||||||
from aiogram.fsm.storage.memory import MemoryStorage
|
|
||||||
|
|
||||||
from app.config import BOT_TOKEN
|
|
||||||
from app.handlers import start
|
|
||||||
|
|
||||||
logging.basicConfig(level=logging.INFO)
|
|
||||||
|
|
||||||
|
|
||||||
async def main():
|
|
||||||
bot = Bot(token=BOT_TOKEN)
|
|
||||||
dp = Dispatcher(storage=MemoryStorage())
|
|
||||||
|
|
||||||
dp.include_router(start.router)
|
|
||||||
|
|
||||||
await dp.start_polling(bot)
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
|
||||||
asyncio.run(main())
|
|
||||||
Reference in New Issue
Block a user