Init project
This commit is contained in:
7
birthday_pool_bot/telegram_bot/ui/__init__.py
Normal file
7
birthday_pool_bot/telegram_bot/ui/__init__.py
Normal file
@@ -0,0 +1,7 @@
|
||||
from .dispatcher import setup_dispatcher
|
||||
|
||||
|
||||
__all__ = (
|
||||
# dispatcher
|
||||
"setup_dispatcher",
|
||||
)
|
||||
47
birthday_pool_bot/telegram_bot/ui/callback_data.py
Normal file
47
birthday_pool_bot/telegram_bot/ui/callback_data.py
Normal file
@@ -0,0 +1,47 @@
|
||||
import enum
|
||||
import uuid
|
||||
from typing import Any
|
||||
|
||||
from aiogram.filters.callback_data import CallbackData
|
||||
from pydantic import Field
|
||||
|
||||
|
||||
class PaginatorCallbackData(CallbackData, prefix=""):
|
||||
page: int = 1
|
||||
|
||||
|
||||
class MenuCallbackData(CallbackData, prefix="menu"):
|
||||
pass
|
||||
|
||||
|
||||
class SubscriptionsCallbackData(PaginatorCallbackData, prefix="subscriptions"):
|
||||
pass
|
||||
|
||||
|
||||
class AddSubscriptionCallbackData(CallbackData, prefix="subscription_add"):
|
||||
pass
|
||||
|
||||
|
||||
class ConfirmAnswerEnum(str, enum.Enum):
|
||||
YES = "yes"
|
||||
NO = "no"
|
||||
|
||||
|
||||
class AddSubscriptionConfirmCallbackData(CallbackData, prefix="subscription_add_confirm"):
|
||||
answer: ConfirmAnswerEnum
|
||||
|
||||
|
||||
class SubscriptionActionEnum(str, enum.Enum):
|
||||
SHOW = "show"
|
||||
DELETE = "delete"
|
||||
EDIT = "edit"
|
||||
BACK = "back"
|
||||
|
||||
|
||||
class SubscriptionCallbackData(CallbackData, prefix="subscription"):
|
||||
to_user_id: uuid.UUID
|
||||
action: SubscriptionActionEnum = SubscriptionActionEnum.SHOW
|
||||
|
||||
|
||||
class AddSubscriptionPoolsCallbackData(PaginatorCallbackData, prefix="subscription_add_pool"):
|
||||
to_user_id: uuid.UUID
|
||||
26
birthday_pool_bot/telegram_bot/ui/constants.py
Normal file
26
birthday_pool_bot/telegram_bot/ui/constants.py
Normal file
@@ -0,0 +1,26 @@
|
||||
from birthday_pool_bot.dto import BankEnum
|
||||
|
||||
|
||||
BANKS_MAP = {
|
||||
"Т-Банк": BankEnum.T_BANK,
|
||||
"Сбер": BankEnum.SBER,
|
||||
"Альфа-Банк": BankEnum.ALFA_BANK,
|
||||
}
|
||||
BANKS_TITLE_MAP = {value: key for key, value in BANKS_MAP.items()}
|
||||
|
||||
PROFILE_BUTTON = "Профиль"
|
||||
SUBSCRIPTIONS_BUTTON = "Мои подписки"
|
||||
|
||||
SET_PROFILE_NAME = "Изменить имя"
|
||||
SET_PROFILE_PHONE = "Изменить номер телефона"
|
||||
SET_PROFILE_GIFT_PAYMENT_DATA_BUTTON = "Изменить банковские данные для подарка"
|
||||
SET_PROFILE_BIRTHDATE_BUTTON = "Изменить день рождения"
|
||||
SHARE_CONTACT_BUTTON = "Поделиться контактом"
|
||||
CREATE_POOL_BUTTON = "Хочу создать сбор денег"
|
||||
JOIN_EXISTING_POOL_BUTTON = "Хочу присоединиться к сборам"
|
||||
DECLINE_POOL_BUTTON = "Не хочу участвовать в сборе денег"
|
||||
USE_PROFILE_GIFT_PAYMENT_DATA = "Использовать данные из профиля"
|
||||
SKIP_BUTTON = "Пропустить"
|
||||
BACK_BUTTON = "Назад"
|
||||
|
||||
ERROR_MESSAGE = "Какой-то сбой, попробуйте ещё раз"
|
||||
311
birthday_pool_bot/telegram_bot/ui/dispatcher.py
Normal file
311
birthday_pool_bot/telegram_bot/ui/dispatcher.py
Normal file
@@ -0,0 +1,311 @@
|
||||
import aiogram
|
||||
from aiogram.filters import Command
|
||||
from aiogram_dialog import DialogManager
|
||||
from birthday_pool_bot.repositories import RepositoriesContainer
|
||||
|
||||
from . import constants, handlers
|
||||
from .callback_data import (
|
||||
AddSubscriptionCallbackData,
|
||||
AddSubscriptionConfirmCallbackData,
|
||||
ConfirmAnswerEnum,
|
||||
MenuCallbackData,
|
||||
SubscriptionActionEnum,
|
||||
SubscriptionCallbackData,
|
||||
SubscriptionsCallbackData,
|
||||
)
|
||||
from .middlewares import (
|
||||
AuthMiddleware,
|
||||
DependsMiddleware,
|
||||
TypingMiddleware,
|
||||
)
|
||||
from .states import (
|
||||
AddSubscriptionPoolState,
|
||||
AddSubscriptionState,
|
||||
MenuState,
|
||||
SetProfileBirthdayState,
|
||||
SetProfileGiftPaymentDataState,
|
||||
SetProfileNameState,
|
||||
SetProfilePhoneState,
|
||||
)
|
||||
|
||||
|
||||
def setup_dispatcher(
|
||||
dispatcher: aiogram.Dispatcher,
|
||||
repositories_container: RepositoriesContainer,
|
||||
):
|
||||
typing_middleware = TypingMiddleware()
|
||||
auth_middleware = AuthMiddleware(users_repository=repositories_container.users)
|
||||
users_repository_middleware = DependsMiddleware(
|
||||
name="users_repository",
|
||||
object_=repositories_container.users,
|
||||
)
|
||||
pools_repository_middleware = DependsMiddleware(
|
||||
name="pools_repository",
|
||||
object_=repositories_container.pools,
|
||||
)
|
||||
subscriptions_repository_middleware = DependsMiddleware(
|
||||
name="subscriptions_repository",
|
||||
object_=repositories_container.subscriptions,
|
||||
)
|
||||
middlewares = [
|
||||
# typing_middleware,
|
||||
auth_middleware,
|
||||
users_repository_middleware,
|
||||
pools_repository_middleware,
|
||||
subscriptions_repository_middleware,
|
||||
]
|
||||
|
||||
router = aiogram.Router()
|
||||
for middleware in middlewares:
|
||||
dispatcher.update.outer_middleware(middleware)
|
||||
router.callback_query.middleware(middleware)
|
||||
router.message.middleware(middleware)
|
||||
|
||||
router = setup_router(router=router)
|
||||
dispatcher.include_router(router)
|
||||
|
||||
|
||||
def setup_router(router: aiogram.Router) -> aiogram.Router:
|
||||
# Menu
|
||||
router.message.register(handlers.menu_message_handler, Command("start"))
|
||||
router.message.register(handlers.menu_message_handler, Command("menu"))
|
||||
router.message.register(
|
||||
handlers.profile_message_handler,
|
||||
MenuState.MENU,
|
||||
aiogram.F.text == constants.PROFILE_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.subscriptions_message_handler,
|
||||
MenuState.MENU,
|
||||
aiogram.F.text == constants.SUBSCRIPTIONS_BUTTON,
|
||||
)
|
||||
|
||||
## Profile
|
||||
router.message.register(
|
||||
handlers.ask_profile_name_message_handler,
|
||||
MenuState.PROFILE,
|
||||
aiogram.F.text == constants.SET_PROFILE_NAME,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_profile_phone_message_handler,
|
||||
MenuState.PROFILE,
|
||||
aiogram.F.text == constants.SET_PROFILE_PHONE,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_profile_birthdate_message_handler,
|
||||
MenuState.PROFILE,
|
||||
aiogram.F.text == constants.SET_PROFILE_BIRTHDATE_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_profile_gift_payment_phone_message_handler,
|
||||
MenuState.PROFILE,
|
||||
aiogram.F.text == constants.SET_PROFILE_GIFT_PAYMENT_DATA_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.menu_message_handler,
|
||||
MenuState.PROFILE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
|
||||
### Set profile name
|
||||
router.message.register(
|
||||
handlers.profile_message_handler,
|
||||
SetProfileNameState.WAITING_FOR_NAME,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_profile_name_message_handler,
|
||||
SetProfileNameState.WAITING_FOR_NAME,
|
||||
)
|
||||
|
||||
### Set profile phone
|
||||
router.message.register(
|
||||
handlers.profile_message_handler,
|
||||
SetProfilePhoneState.WAITING_FOR_PHONE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_profile_phone_message_handler,
|
||||
SetProfilePhoneState.WAITING_FOR_PHONE,
|
||||
)
|
||||
|
||||
### Set profile birthday
|
||||
router.message.register(
|
||||
handlers.profile_message_handler,
|
||||
SetProfileBirthdayState.WAITING_FOR_DATE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_profile_birthdate_message_handler,
|
||||
SetProfileBirthdayState.WAITING_FOR_DATE,
|
||||
)
|
||||
|
||||
### Set profile gift payment data
|
||||
router.message.register(
|
||||
handlers.profile_message_handler,
|
||||
SetProfileGiftPaymentDataState.WAITING_FOR_PHONE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_profile_gift_payment_phone_message_handler,
|
||||
SetProfileGiftPaymentDataState.WAITING_FOR_PHONE,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_profile_gift_payment_phone_message_handler,
|
||||
SetProfileGiftPaymentDataState.WAITING_FOR_BANK,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_profile_gift_payment_bank_message_handler,
|
||||
SetProfileGiftPaymentDataState.WAITING_FOR_BANK,
|
||||
aiogram.F.text.in_(constants.BANKS_MAP),
|
||||
)
|
||||
|
||||
## Subscriptions
|
||||
router.callback_query.register(
|
||||
handlers.subscription_callback_handler,
|
||||
MenuState.SUBSCRIPTIONS,
|
||||
SubscriptionCallbackData.filter(
|
||||
aiogram.F.action == SubscriptionActionEnum.SHOW,
|
||||
),
|
||||
)
|
||||
router.callback_query.register(
|
||||
handlers.subscriptions_callback_handler,
|
||||
MenuState.SUBSCRIPTIONS,
|
||||
SubscriptionsCallbackData.filter(),
|
||||
)
|
||||
router.callback_query.register(
|
||||
handlers.add_subscription_callback_handler,
|
||||
MenuState.SUBSCRIPTIONS,
|
||||
AddSubscriptionCallbackData.filter(),
|
||||
)
|
||||
router.callback_query.register(
|
||||
handlers.menu_callback_handler,
|
||||
MenuState.SUBSCRIPTIONS,
|
||||
MenuCallbackData.filter(),
|
||||
)
|
||||
|
||||
### Subscription interaction
|
||||
router.callback_query.register(
|
||||
handlers.delete_subscription_callback,
|
||||
MenuState.SUBSCRIPTIONS,
|
||||
SubscriptionCallbackData.filter(
|
||||
aiogram.F.action == SubscriptionActionEnum.DELETE,
|
||||
),
|
||||
)
|
||||
router.callback_query.register(
|
||||
handlers.subscriptions_callback_handler,
|
||||
MenuState.SUBSCRIPTIONS,
|
||||
SubscriptionCallbackData.filter(
|
||||
aiogram.F.action == SubscriptionActionEnum.BACK,
|
||||
),
|
||||
)
|
||||
|
||||
### Add new subscription
|
||||
router.message.register(
|
||||
handlers.add_subscription_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_PHONE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_user_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_PHONE,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_user_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_DATE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_user_birthday_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_DATE,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_user_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_NAME,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_name_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_NAME,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_pool_description_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_POOL_DECISION,
|
||||
aiogram.F.text == constants.CREATE_POOL_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.show_add_subscription_pools_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_POOL_DECISION,
|
||||
aiogram.F.text == constants.JOIN_EXISTING_POOL_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_confirmation_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_POOL_DECISION,
|
||||
aiogram.F.text == constants.DECLINE_POOL_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_name_message_handler,
|
||||
AddSubscriptionState.WAITING_FOR_POOL_DECISION,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.callback_query.register(
|
||||
handlers.confirm_add_subscription_callback_handler,
|
||||
AddSubscriptionState.WAITING_FOR_CONFIRMATION,
|
||||
AddSubscriptionConfirmCallbackData.filter(
|
||||
aiogram.F.answer == ConfirmAnswerEnum.YES,
|
||||
),
|
||||
)
|
||||
router.callback_query.register(
|
||||
handlers.subscriptions_callback_handler,
|
||||
AddSubscriptionState.WAITING_FOR_CONFIRMATION,
|
||||
AddSubscriptionConfirmCallbackData.filter(
|
||||
aiogram.F.answer == ConfirmAnswerEnum.NO,
|
||||
),
|
||||
)
|
||||
|
||||
#### Add new subscription pool
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_pool_payment_phone_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_DESCRIPTION,
|
||||
aiogram.F.text == constants.SKIP_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_pool_decision_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_DESCRIPTION,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_pool_description_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_DESCRIPTION,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_pool_payment_data_from_profile_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE,
|
||||
aiogram.F.text == constants.USE_PROFILE_GIFT_PAYMENT_DATA,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.ask_add_subscription_pool_description_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_pool_payment_phone_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_pool_payment_phone_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_PAYMENT_BANK,
|
||||
aiogram.F.text == constants.BACK_BUTTON,
|
||||
)
|
||||
router.message.register(
|
||||
handlers.set_add_subscription_pool_payment_bank_message_handler,
|
||||
AddSubscriptionPoolState.WAITING_FOR_PAYMENT_BANK,
|
||||
)
|
||||
|
||||
#### Choose pool for subscription
|
||||
|
||||
# Fallback
|
||||
router.message.register(handlers.fallback_message_handler)
|
||||
|
||||
return router
|
||||
6
birthday_pool_bot/telegram_bot/ui/exceptions.py
Normal file
6
birthday_pool_bot/telegram_bot/ui/exceptions.py
Normal file
@@ -0,0 +1,6 @@
|
||||
class TelegramBotUIError(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FlowInternalError(TelegramBotUIError):
|
||||
pass
|
||||
894
birthday_pool_bot/telegram_bot/ui/handlers.py
Normal file
894
birthday_pool_bot/telegram_bot/ui/handlers.py
Normal file
@@ -0,0 +1,894 @@
|
||||
import asyncio
|
||||
import uuid
|
||||
|
||||
from aiogram import types, F, Router
|
||||
from aiogram.enums import MessageOriginType, ParseMode
|
||||
from aiogram.filters import Command
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
|
||||
from pydantic_core import PydanticCustomError
|
||||
from pydantic_filters import PagePagination
|
||||
from pydantic_extra_types.phone_numbers import PhoneNumber
|
||||
|
||||
from birthday_pool_bot.dto import (
|
||||
BankEnum,
|
||||
PaymentData,
|
||||
Pool,
|
||||
Subscription,
|
||||
SubscriptionFilter,
|
||||
User,
|
||||
)
|
||||
from birthday_pool_bot.repositories.repositories import (
|
||||
PoolsRepository,
|
||||
SubscriptionsRepository,
|
||||
UsersRepository,
|
||||
)
|
||||
from . import constants, logic
|
||||
from .callback_data import (
|
||||
AddSubscriptionCallbackData,
|
||||
AddSubscriptionConfirmCallbackData,
|
||||
ConfirmAnswerEnum,
|
||||
MenuCallbackData,
|
||||
SubscriptionCallbackData,
|
||||
SubscriptionsCallbackData,
|
||||
)
|
||||
from .exceptions import FlowInternalError
|
||||
from .states import (
|
||||
AddSubscriptionPoolState,
|
||||
AddSubscriptionState,
|
||||
MenuState,
|
||||
SetProfileBirthdayState,
|
||||
SetProfileGiftPaymentDataState,
|
||||
SetProfileNameState,
|
||||
SetProfilePhoneState,
|
||||
)
|
||||
from .utils import get_telegram_user_full_name, parse_date
|
||||
|
||||
|
||||
async def fallback_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
|
||||
await logic.show_menu(message=message)
|
||||
await state.clear()
|
||||
await state.set_state(MenuState.MENU)
|
||||
|
||||
|
||||
async def menu_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.show_menu(message=message)
|
||||
await state.set_state(MenuState.MENU)
|
||||
|
||||
|
||||
async def menu_callback_handler(
|
||||
callback_query: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.show_menu(message=callback_query.message)
|
||||
await state.set_state(MenuState.MENU)
|
||||
|
||||
|
||||
async def profile_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
):
|
||||
await logic.show_profile(message=message, user=user)
|
||||
await state.set_state(MenuState.PROFILE)
|
||||
|
||||
|
||||
async def ask_profile_name_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_profile_name(message=message)
|
||||
await state.set_state(SetProfileNameState.WAITING_FOR_NAME)
|
||||
|
||||
|
||||
async def set_profile_name_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
user.name = message.text
|
||||
async with users_repository.transaction():
|
||||
user = await users_repository.update_user(user=user)
|
||||
|
||||
await logic.show_profile(message=message, user=user)
|
||||
await state.set_state(MenuState.PROFILE)
|
||||
|
||||
|
||||
async def ask_profile_phone_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_profile_phone(message=message)
|
||||
await state.set_state(SetProfilePhoneState.WAITING_FOR_PHONE)
|
||||
|
||||
|
||||
async def set_profile_phone_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
if message.contact is None:
|
||||
await message.reply(
|
||||
text=(
|
||||
"Некорректный ответ, пожалуйста, отправьте свой контакт нажатием на кнопку "
|
||||
"`Поделиться контактом`"
|
||||
),
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
)
|
||||
await logic.ask_profile_phone(message=message)
|
||||
await state.set_state(SetProfilePhoneState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
phone_number = message.contact.phone_number
|
||||
try:
|
||||
PhoneNumber._validate(phone_number, None)
|
||||
except PydanticCustomError:
|
||||
await message.reply(
|
||||
text="Некорректный номер телефон, попробуйте ещё раз",
|
||||
)
|
||||
await state.set_state(SetProfilePhoneState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
user.phone = phone_number
|
||||
async with users_repository.transaction():
|
||||
user = await users_repository.update_user(user=user)
|
||||
|
||||
await logic.show_profile(message=message, user=user)
|
||||
await state.set_state(MenuState.PROFILE)
|
||||
|
||||
|
||||
async def ask_profile_birthdate_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_profile_birthdate(message=message)
|
||||
await state.set_state(SetProfileBirthdayState.WAITING_FOR_DATE)
|
||||
|
||||
|
||||
async def set_profile_birthdate_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
birthday = parse_date(message=message)
|
||||
if birthday is None:
|
||||
await logic.ask_profile_birthdate(message=message)
|
||||
await state.set_state(SetProfileBirthdayState.WAITING_FOR_DATE)
|
||||
return
|
||||
|
||||
user.birthday = birthday
|
||||
async with users_repository.transaction():
|
||||
await users_repository.update_user(user=user)
|
||||
|
||||
await logic.show_profile(message=message, user=user)
|
||||
await state.set_state(MenuState.PROFILE)
|
||||
|
||||
|
||||
async def ask_profile_gift_payment_phone_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_profile_gift_payment_phone(message=message)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_PHONE)
|
||||
|
||||
|
||||
async def set_profile_gift_payment_phone_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
phone_number = None
|
||||
if message.contact is not None:
|
||||
phone_number = message.contact.phone_number
|
||||
elif message.text is not None:
|
||||
phone_number = message.text
|
||||
else:
|
||||
await logic.ask_profile_gift_payment_phone(message=message)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
try:
|
||||
PhoneNumber._validate(phone_number, None)
|
||||
except PydanticCustomError:
|
||||
await message.reply(
|
||||
text="Некорректный номер телефон, попробуйте ещё раз",
|
||||
)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.update_data(gift_payment_phone=phone_number)
|
||||
|
||||
await logic.ask_profile_gift_payment_bank(
|
||||
message=message,
|
||||
)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_BANK)
|
||||
|
||||
|
||||
async def ask_profile_gift_payment_bank_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_profile_gift_payment_bank(message=message)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_BANK)
|
||||
|
||||
|
||||
async def set_profile_gift_payment_bank_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
data = await state.get_data()
|
||||
if "gift_payment_phone" not in data:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_profile_gift_payment_phone(message=message)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
phone = data["gift_payment_phone"]
|
||||
bank_name = message.text
|
||||
if bank_name not in constants.BANKS_MAP:
|
||||
await message.reply(text="Неизвестный банк, попробуйте ещё раз")
|
||||
await logic.ask_profile_gift_payment_bank(message=message)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_BANK)
|
||||
|
||||
bank = constants.BANKS_MAP[bank_name]
|
||||
user.gift_payment_data = PaymentData(
|
||||
phone=phone,
|
||||
bank=bank,
|
||||
)
|
||||
async with users_repository.transaction():
|
||||
user = await users_repository.update_user(user=user)
|
||||
|
||||
await logic.show_profile(message=message, user=user)
|
||||
await state.set_state(MenuState.PROFILE)
|
||||
|
||||
|
||||
async def subscriptions_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
):
|
||||
await logic.show_subscriptions(
|
||||
user=user,
|
||||
subscriptions_repository=subscriptions_repository,
|
||||
message=message,
|
||||
)
|
||||
await state.set_state(MenuState.SUBSCRIPTIONS)
|
||||
|
||||
|
||||
async def subscriptions_callback_handler(
|
||||
callback_query: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
):
|
||||
await logic.show_subscriptions(
|
||||
user=user,
|
||||
subscriptions_repository=subscriptions_repository,
|
||||
message=callback_query.message,
|
||||
)
|
||||
await state.set_state(MenuState.SUBSCRIPTIONS)
|
||||
|
||||
|
||||
async def subscription_callback_handler(
|
||||
callback_query: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
):
|
||||
callback_data = SubscriptionCallbackData.unpack(callback_query.data)
|
||||
|
||||
await logic.show_subscription(
|
||||
from_user_id=user.id,
|
||||
to_user_id=callback_data.to_user_id,
|
||||
subscriptions_repository=subscriptions_repository,
|
||||
callback_query=callback_query,
|
||||
)
|
||||
await state.set_state(MenuState.SUBSCRIPTIONS)
|
||||
|
||||
|
||||
async def delete_subscription_callback(
|
||||
callback_query: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
):
|
||||
callback_data = SubscriptionCallbackData.unpack(callback_query.data)
|
||||
|
||||
await logic.delete_subscription(
|
||||
from_user_id=user.id,
|
||||
to_user_id=callback_data.to_user_id,
|
||||
subscriptions_repository=subscriptions_repository,
|
||||
callback_query=callback_query,
|
||||
)
|
||||
|
||||
await logic.show_subscriptions(
|
||||
user=user,
|
||||
subscriptions_repository=subscriptions_repository,
|
||||
message=callback_query.message,
|
||||
)
|
||||
await state.set_state(MenuState.SUBSCRIPTIONS)
|
||||
|
||||
|
||||
async def add_subscription_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
|
||||
|
||||
async def add_subscription_callback_handler(
|
||||
callback_query: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_add_subscription_user(callback_query=callback_query)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
|
||||
|
||||
async def ask_add_subscription_user_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
|
||||
|
||||
async def set_add_subscription_user_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
users_repository: UsersRepository,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
):
|
||||
user_name, user_phone, user_telegram_id = None, None, None
|
||||
|
||||
async with users_repository.transaction():
|
||||
if message.contact is not None:
|
||||
subscription_users = await users_repository.get_users_by_primary_keys(
|
||||
phone=message.contact.phone_number,
|
||||
telegram_id=message.contact.user_id,
|
||||
)
|
||||
subscription_user = await users_repository.merge_users(*subscription_users)
|
||||
user_name = get_telegram_user_full_name(message.contact)
|
||||
user_phone = message.contact.phone_number
|
||||
user_telegram_id = message.contact.user_id
|
||||
elif message.forward_origin is not None:
|
||||
if message.forward_origin.type == MessageOriginType.HIDDEN_USER:
|
||||
await message.reply(
|
||||
text="Пользователь скрыл свои данные, попробуйте отправить номер телефона или его контакт",
|
||||
)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
if message.forward_origin.type != MessageOriginType.USER:
|
||||
await message.reply(
|
||||
text="Это сообщение не от пользователя, попробуйте ещё раз",
|
||||
)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
user_name = get_telegram_user_full_name(message.forward_origin.sender_user)
|
||||
user_telegram_id = message.forward_origin.sender_user.id
|
||||
subscription_user = await users_repository.get_user_by_telegram_id(
|
||||
telegram_id=user_telegram_id,
|
||||
)
|
||||
elif message.text:
|
||||
user_phone = message.text
|
||||
try:
|
||||
PhoneNumber._validate(user_phone, None)
|
||||
except PydanticCustomError:
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
subscription_user = await users_repository.get_user_by_phone(phone=user_phone)
|
||||
|
||||
if subscription_user is None:
|
||||
subscription_user = User(
|
||||
name=user_name,
|
||||
phone=user_phone,
|
||||
telegram_id=user_telegram_id,
|
||||
)
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.create_user(user=subscription_user)
|
||||
|
||||
if subscription_user.id == user.id:
|
||||
await message.reply("Нельзя подписаться на самого себя")
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
async with subscriptions_repository.transaction():
|
||||
subscription = await subscriptions_repository.get_subscription(
|
||||
from_user_id=user.id,
|
||||
to_user_id=subscription_user.id,
|
||||
)
|
||||
|
||||
if subscription is not None:
|
||||
await message.reply(text="У вас уже есть подписка на этого человека")
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.update_data(
|
||||
subscription_user_id=subscription_user.id,
|
||||
)
|
||||
|
||||
if subscription_user.birthday is None:
|
||||
await logic.ask_add_subscription_user_birthday(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_DATE)
|
||||
return
|
||||
|
||||
await logic.ask_add_subscription_name(
|
||||
message=message,
|
||||
users_repository=users_repository,
|
||||
subscription_user_id=subscription_user.id,
|
||||
)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_NAME)
|
||||
|
||||
|
||||
async def ask_add_subscription_user_birthday_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_add_subscription_user_birthday(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_DATE)
|
||||
|
||||
|
||||
async def set_add_subscription_user_birthday_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
birthday = parse_date(message=message)
|
||||
if birthday is None:
|
||||
await logic.ask_add_subscription_user_birthday(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_DATE)
|
||||
return
|
||||
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
if subscription_user_id is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(user_id=subscription_user_id)
|
||||
if subscription_user is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
subscription_user.birthday = birthday
|
||||
await users_repository.update_user(user=subscription_user)
|
||||
|
||||
await logic.ask_add_subscription_name(
|
||||
message=message,
|
||||
users_repository=users_repository,
|
||||
subscription_user_id=subscription_user.id,
|
||||
)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_NAME)
|
||||
|
||||
|
||||
async def ask_add_subscription_name_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
if subscription_user_id is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
try:
|
||||
await logic.ask_add_subscription_name(
|
||||
message=message,
|
||||
users_repository=users_repository,
|
||||
subscription_user_id=subscription_user_id,
|
||||
)
|
||||
except FlowInternalError:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_NAME)
|
||||
|
||||
|
||||
async def set_add_subscription_name_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
pools_repository: PoolsRepository,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
if subscription_user_id is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(user_id=subscription_user_id)
|
||||
if subscription_user is None or subscription_user.birthday is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.update_data(subscription_name=message.text)
|
||||
await logic.ask_add_subscription_pool_decision(
|
||||
message=message,
|
||||
pools_repository=pools_repository,
|
||||
users_repository=users_repository,
|
||||
subscription_user_id=subscription_user_id,
|
||||
)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_POOL_DECISION)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_decision_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
pools_repository: PoolsRepository,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
if subscription_user_id is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
try:
|
||||
await logic.ask_add_subscription_pool_decision(
|
||||
message=message,
|
||||
pools_repository=pools_repository,
|
||||
users_repository=users_repository,
|
||||
subscription_user_id=subscription_user_id,
|
||||
)
|
||||
except FlowInternalError:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_POOL_DECISION)
|
||||
|
||||
|
||||
async def show_add_subscription_pools_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
pools_repository: PoolsRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
if subscription_user_id is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
try:
|
||||
await logic.show_add_subscription_pools(
|
||||
user=user,
|
||||
pools_repository=pools_repository,
|
||||
message=message,
|
||||
)
|
||||
except FlowInternalError:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await logic.ask_add_subscription_pool_description(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_DESCRIPTION)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_description_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_add_subscription_pool_description(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_DESCRIPTION)
|
||||
|
||||
|
||||
async def set_add_subscription_pool_description_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
description = message.text
|
||||
await state.update_data(subscription_pool_description=description)
|
||||
|
||||
await logic.ask_add_subscription_pool_payment_phone(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_payment_phone_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
):
|
||||
await logic.ask_add_subscription_pool_payment_phone(message=message, user=user)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE)
|
||||
|
||||
|
||||
async def set_add_subscription_pool_payment_phone_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
phone_number = None
|
||||
if message.contact is not None:
|
||||
phone_number = message.contact.phone_number
|
||||
elif message.text is not None:
|
||||
phone_number = message.text
|
||||
else:
|
||||
await message.reply(text="Некорректный номер телефона, попробуйте ещё раз")
|
||||
await logic.ask_add_subscription_pool_payment_phone(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE)
|
||||
return
|
||||
|
||||
try:
|
||||
PhoneNumber._validate(phone_number, None)
|
||||
except PydanticCustomError:
|
||||
await message.reply(
|
||||
text="Некорректный номер телефона, попробуйте ещё раз",
|
||||
)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE)
|
||||
return
|
||||
|
||||
await state.update_data(subscription_pool_phone=phone_number)
|
||||
|
||||
await logic.ask_add_subscription_pool_payment_bank(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_BANK)
|
||||
|
||||
|
||||
async def set_add_subscription_pool_payment_data_from_profile_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
users_repository: UsersRepository,
|
||||
pools_repository: PoolsRepository,
|
||||
):
|
||||
if user.gift_payment_data is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_pool_payment_phone(message=message, user=user)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_PHONE)
|
||||
return
|
||||
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
subscription_name = state_data.get("subscription_name")
|
||||
subscription_pool_description = state_data.get("subscription_pool_description")
|
||||
if not all((subscription_user_id, subscription_name)):
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.update_data(
|
||||
subscription_pool_phone=user.gift_payment_data.phone,
|
||||
subscription_pool_bank=user.gift_payment_data.bank,
|
||||
)
|
||||
await logic.ask_add_subscription_confirmation(
|
||||
message=message,
|
||||
users_repository=users_repository,
|
||||
pools_repository=pools_repository,
|
||||
subscription_name=subscription_name,
|
||||
subscription_user_id=subscription_user_id,
|
||||
subscription_pool_description=subscription_pool_description,
|
||||
subscription_pool_phone=user.gift_payment_data.phone,
|
||||
subscription_pool_bank=user.gift_payment_data.bank,
|
||||
)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_CONFIRMATION)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_payment_bank_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
):
|
||||
await logic.ask_add_subscription_pool_payment_bank(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_PAYMENT_BANK)
|
||||
|
||||
|
||||
async def set_add_subscription_pool_payment_bank_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
users_repository: UsersRepository,
|
||||
pools_repository: PoolsRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
subscription_name = state_data.get("subscription_name")
|
||||
subscription_pool_description = state_data.get("subscription_pool_description")
|
||||
subscription_pool_phone = state_data.get("subscription_pool_phone")
|
||||
if not all((subscription_user_id, subscription_name)):
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
if subscription_pool_phone is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_pool_decision(message=message)
|
||||
await state.set_state(AddSubscriptionPoolState.WAITING_FOR_DESCRIPTION)
|
||||
return
|
||||
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(
|
||||
user_id=subscription_user_id,
|
||||
)
|
||||
if subscription_user is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
bank_name = message.text
|
||||
if bank_name not in constants.BANKS_MAP:
|
||||
await message.reply(text="Неизвестный банк, попробуйте ещё раз")
|
||||
await logic.ask_profile_gift_payment_bank(message=message)
|
||||
await state.set_state(SetProfileGiftPaymentDataState.WAITING_FOR_BANK)
|
||||
|
||||
bank = constants.BANKS_MAP[bank_name]
|
||||
await state.update_data(subscription_pool_bank=bank)
|
||||
|
||||
await logic.ask_add_subscription_confirmation(
|
||||
message=message,
|
||||
users_repository=users_repository,
|
||||
pools_repository=pools_repository,
|
||||
subscription_name=subscription_name,
|
||||
subscription_user_id=subscription_user_id,
|
||||
subscription_pool_description=subscription_pool_description,
|
||||
subscription_pool_phone=subscription_pool_phone,
|
||||
subscription_pool_bank=bank,
|
||||
)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_CONFIRMATION)
|
||||
|
||||
|
||||
async def ask_add_subscription_confirmation_message_handler(
|
||||
message: types.Message,
|
||||
state: FSMContext,
|
||||
users_repository: UsersRepository,
|
||||
pools_repository: PoolsRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
subscription_name = state_data.get("subscription_name")
|
||||
subscription_pool_id = state_data.get("subscription_pool_id")
|
||||
subscription_pool_description = state_data.get("subscription_pool_description")
|
||||
subscription_pool_phone = state_data.get("subscription_pool_phone")
|
||||
subscription_pool_bank = state_data.get("subscription_pool_bank")
|
||||
if not all((subscription_user_id, subscription_name)):
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(
|
||||
user_id=subscription_user_id,
|
||||
)
|
||||
if subscription_user is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
try:
|
||||
await logic.ask_add_subscription_confirmation(
|
||||
message=message,
|
||||
users_repository=users_repository,
|
||||
pools_repository=pools_repository,
|
||||
subscription_name=subscription_name,
|
||||
subscription_user_id=subscription_user_id,
|
||||
subscription_pool_id=subscription_pool_id,
|
||||
subscription_pool_description=subscription_pool_description,
|
||||
subscription_pool_phone=subscription_pool_phone,
|
||||
subscription_pool_bank=subscription_pool_bank,
|
||||
)
|
||||
except FlowInternalError:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_CONFIRMATION)
|
||||
|
||||
|
||||
async def confirm_add_subscription_callback_handler(
|
||||
callback_query: types.CallbackQuery,
|
||||
state: FSMContext,
|
||||
user: User,
|
||||
pools_repository: PoolsRepository,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
users_repository: UsersRepository,
|
||||
):
|
||||
state_data = await state.get_data()
|
||||
subscription_user_id = state_data.get("subscription_user_id")
|
||||
subscription_name = state_data.get("subscription_name")
|
||||
subscription_pool_id = state_data.get("subscription_pool_id")
|
||||
subscription_pool_description = state_data.get("subscription_pool_description")
|
||||
subscription_pool_phone = state_data.get("subscription_pool_phone")
|
||||
subscription_pool_bank = state_data.get("subscription_pool_bank")
|
||||
if not all((subscription_user_id, subscription_name)):
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(
|
||||
user_id=subscription_user_id,
|
||||
)
|
||||
if subscription_user is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
subscription_pool = None
|
||||
if subscription_pool_id is not None:
|
||||
async with pools_repository.transaction():
|
||||
subscription_pool = await pools_repository.get_pool_by_id(
|
||||
pool_id=subscription_pool_id,
|
||||
)
|
||||
if subscription_pool is None:
|
||||
await message.reply(text=constants.ERROR_MESSAGE)
|
||||
await logic.ask_add_subscription_user(message=message)
|
||||
await state.set_state(AddSubscriptionState.WAITING_FOR_PHONE)
|
||||
return
|
||||
|
||||
if subscription_pool is None and all((subscription_pool_phone, subscription_pool_bank)):
|
||||
subscription_pool = Pool(
|
||||
owner_id=user.id,
|
||||
birthday_user_id=subscription_user.id,
|
||||
description=subscription_pool_description,
|
||||
payment_data=PaymentData(
|
||||
phone=subscription_pool_phone,
|
||||
bank=subscription_pool_bank,
|
||||
),
|
||||
)
|
||||
async with pools_repository.transaction():
|
||||
subscription_pool = await pools_repository.create_pool(pool=subscription_pool)
|
||||
subscription_pool_id = subscription_pool.id
|
||||
|
||||
subscription = Subscription(
|
||||
from_user_id=user.id,
|
||||
to_user_id=subscription_user_id,
|
||||
name=subscription_name,
|
||||
pool_id=subscription_pool_id,
|
||||
)
|
||||
async with subscriptions_repository.transaction():
|
||||
await subscriptions_repository.create_subscription(
|
||||
subscription=subscription,
|
||||
)
|
||||
|
||||
await logic.show_subscriptions(
|
||||
user=user,
|
||||
subscriptions_repository=subscriptions_repository,
|
||||
message=callback_query.message,
|
||||
)
|
||||
await state.clear()
|
||||
await state.set_state(MenuState.SUBSCRIPTIONS)
|
||||
683
birthday_pool_bot/telegram_bot/ui/logic.py
Normal file
683
birthday_pool_bot/telegram_bot/ui/logic.py
Normal file
@@ -0,0 +1,683 @@
|
||||
from aiogram import types
|
||||
from aiogram.enums import ParseMode
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
|
||||
from pydantic_filters import PagePagination
|
||||
|
||||
from birthday_pool_bot.dto import User
|
||||
from birthday_pool_bot.repositories.repositories import UsersRepository
|
||||
from . import constants
|
||||
from .callback_data import (
|
||||
AddSubscriptionCallbackData,
|
||||
AddSubscriptionConfirmCallbackData,
|
||||
ConfirmAnswerEnum,
|
||||
MenuCallbackData,
|
||||
SubscriptionActionEnum,
|
||||
SubscriptionCallbackData,
|
||||
SubscriptionsCallbackData,
|
||||
)
|
||||
from .exceptions import FlowInternalError
|
||||
|
||||
|
||||
async def show_menu(message: types.Message) -> None:
|
||||
text = (
|
||||
"Привет! Я – Birthday Pool Bot. "
|
||||
"Я помогаю собирать подарки на дни рождения.\n"
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.PROFILE_BUTTON)
|
||||
keyboard.button(text=constants.SUBSCRIPTIONS_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def show_profile(
|
||||
message: types.Message,
|
||||
user: User,
|
||||
):
|
||||
birthday_str = user.birthday.strftime("%d.%m.%Y") if user.birthday else 'Не указано'
|
||||
text = (
|
||||
f"👤 *Имя*: {user.name or 'Не указано'}\n\n"
|
||||
f"📱 *Телефон*: {user.phone}\n\n"
|
||||
f"🎂 *День рождения*: {birthday_str}\n\n"
|
||||
)
|
||||
if user.gift_payment_data is not None:
|
||||
bank_title = constants.BANKS_TITLE_MAP[user.gift_payment_data.bank]
|
||||
text += (
|
||||
"💳️ *Получать подарки на*:\n"
|
||||
f"🏦 _Банк_: {bank_title}\n"
|
||||
f"📱 _Телефон_: {user.gift_payment_data.phone}\n\n"
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.SET_PROFILE_NAME)
|
||||
keyboard.button(text=constants.SET_PROFILE_PHONE)
|
||||
keyboard.button(text=constants.SET_PROFILE_BIRTHDATE_BUTTON)
|
||||
keyboard.button(text=constants.SET_PROFILE_GIFT_PAYMENT_DATA_BUTTON)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(2)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_profile_name(message: types.Message):
|
||||
text = "👤 Напишите своё имя"
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_profile_phone(message: types.Message):
|
||||
text = "📱 Отправь свой контакт для подтверждения телефона"
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.SHARE_CONTACT_BUTTON, request_contact=True)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_profile_gift_payment_phone(message: types.Message):
|
||||
text = (
|
||||
"*Введите номер телефона для получения подарков*\n"
|
||||
"Пример: `+79123456789`\n\n"
|
||||
"ИЛИ\n\n"
|
||||
"*Просто поделитесь своим контактом*"
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.SHARE_CONTACT_BUTTON, request_contact=True)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_profile_gift_payment_bank(
|
||||
message: types.Message,
|
||||
):
|
||||
text = "🏦 Выберите банк для приёма платежей"
|
||||
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
for title, type_ in constants.BANKS_MAP.items():
|
||||
keyboard.button(text=title)
|
||||
keyboard.adjust(3)
|
||||
keyboard.row(types.KeyboardButton(text=constants.BACK_BUTTON))
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_profile_birthdate(message: types.Message):
|
||||
text = (
|
||||
"🎂 Отправьте свою дату рождения в формате dd.mm.yyyy\n"
|
||||
"Пример: `26.05.1995`."
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def show_subscriptions(
|
||||
user: User,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
message: types.Message | None = None,
|
||||
callback_query: types.CallbackQuery | None = None,
|
||||
):
|
||||
page = 1
|
||||
per_page = 5
|
||||
if callback_query is not None:
|
||||
callback_data = SubscriptionsCallbackData.unpack(callback_query.data)
|
||||
page = callback_data.page
|
||||
|
||||
async with subscriptions_repository.transaction():
|
||||
total = await subscriptions_repository.get_user_subscriptions_count(user_id=user.id)
|
||||
pages_count = total // per_page + int(bool(total % per_page))
|
||||
subscriptions = [
|
||||
subscription
|
||||
async for subscription in subscriptions_repository.get_user_subscriptions(
|
||||
user_id=user.id,
|
||||
pagination=PagePagination(page=page, per_page=per_page),
|
||||
)
|
||||
]
|
||||
|
||||
text = "Мои подписки:" if subscriptions else "Нет подписок"
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
for subscription in subscriptions:
|
||||
keyboard.button(
|
||||
text=subscription.name,
|
||||
callback_data=SubscriptionCallbackData(
|
||||
to_user_id=subscription.to_user_id,
|
||||
).pack(),
|
||||
)
|
||||
navigation_row = []
|
||||
if page > 1:
|
||||
navigation_row.append(types.InlineKeyboardButton(
|
||||
text="<",
|
||||
callback_data=SubscriptionsCallbackData(page=page - 1).pack(),
|
||||
))
|
||||
if pages_count > 1:
|
||||
navigation_row.append(types.InlineKeyboardButton(
|
||||
text=f"{page}/{pages_count}",
|
||||
callback_data="null",
|
||||
))
|
||||
if page < pages_count:
|
||||
navigation_row.append(types.InlineKeyboardButton(
|
||||
text=">",
|
||||
callback_data=SubscriptionsCallbackData(page=page + 1).pack(),
|
||||
))
|
||||
keyboard.row(*navigation_row)
|
||||
keyboard.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="Добавить",
|
||||
callback_data=AddSubscriptionCallbackData().pack(),
|
||||
),
|
||||
types.InlineKeyboardButton(
|
||||
text="Назад",
|
||||
callback_data=MenuCallbackData().pack(),
|
||||
),
|
||||
)
|
||||
reply_markup = keyboard.as_markup()
|
||||
|
||||
if message is not None:
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
elif callback_query is not None:
|
||||
await callback_query.message.edit_text(text=text)
|
||||
await callback_query.message.edit_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def show_subscription(
|
||||
from_user_id: uuid.UUID,
|
||||
to_user_id: uuid.UUID,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
callback_query: types.CallbackQuery | None = None,
|
||||
message: types.Message | None = None,
|
||||
):
|
||||
async with subscriptions_repository.transaction():
|
||||
subscription = await subscriptions_repository.get_subscription(
|
||||
from_user_id=from_user_id,
|
||||
to_user_id=to_user_id,
|
||||
with_to_user=True,
|
||||
with_pool=True,
|
||||
with_pool_owner=True,
|
||||
)
|
||||
if subscription is None:
|
||||
raise FlowInternalError()
|
||||
|
||||
birthday_str = subscription.to_user.birthday.strftime("%d.%m.%Y")
|
||||
text = (
|
||||
f"*Имя*: {subscription.name}\n\n"
|
||||
f"🎂 *День рождения*: {birthday_str}\n\n"
|
||||
)
|
||||
if subscription.pool is not None:
|
||||
if subscription.pool.owner_id == from_user_id:
|
||||
text += "Вы собираете деньги\n\n"
|
||||
else:
|
||||
text += "Вы участвуете в сборе денег\n\n"
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
# keyboard.button(
|
||||
# text="Изменить",
|
||||
# callback_data=SubscriptionCallbackData(
|
||||
# to_user_id=subscription.to_user_id,
|
||||
# action=SubscriptionActionEnum.EDIT,
|
||||
# ).pack(),
|
||||
# )
|
||||
keyboard.button(
|
||||
text="Удалить",
|
||||
callback_data=SubscriptionCallbackData(
|
||||
to_user_id=subscription.to_user_id,
|
||||
action=SubscriptionActionEnum.DELETE,
|
||||
).pack(),
|
||||
)
|
||||
keyboard.button(
|
||||
text="Назад",
|
||||
callback_data=SubscriptionsCallbackData().pack(),
|
||||
)
|
||||
keyboard.adjust(2)
|
||||
reply_markup = keyboard.as_markup()
|
||||
|
||||
if message is not None:
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
return
|
||||
elif callback_query is not None:
|
||||
await callback_query.message.edit_text(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
)
|
||||
await callback_query.message.edit_reply_markup(reply_markup=reply_markup)
|
||||
return
|
||||
|
||||
|
||||
async def delete_subscription(
|
||||
from_user_id: uuid.UUID,
|
||||
to_user_id: uuid.UUID,
|
||||
subscriptions_repository: SubscriptionsRepository,
|
||||
callback_query: types.CallbackQuery | None = None,
|
||||
message: types.Message | None = None,
|
||||
):
|
||||
async with subscriptions_repository.transaction():
|
||||
await subscriptions_repository.delete_subscription(
|
||||
from_user_id=from_user_id,
|
||||
to_user_id=to_user_id,
|
||||
)
|
||||
|
||||
if message is not None:
|
||||
await message.reply(text="Подписка успешно удалена")
|
||||
elif callback_query is not None:
|
||||
await callback_query.answer(text="Подписка успешно удалена")
|
||||
|
||||
|
||||
async def ask_add_subscription_user(
|
||||
callback_query: types.CallbackQuery | None = None,
|
||||
message: types.Message | None = None,
|
||||
):
|
||||
text = (
|
||||
"*Введите номер телефона будущего именниника*\n"
|
||||
"Пример: `+79123456789`\n\n"
|
||||
"ИЛИ\n\n"
|
||||
"*Перешлите сообщение пользователя*\n\n"
|
||||
"ИЛИ\n\n"
|
||||
"*Просто поделитесь его контактом*"
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
if message is not None:
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
elif callback_query is not None:
|
||||
await callback_query.message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_user_birthday(
|
||||
message: types.Message,
|
||||
):
|
||||
text = (
|
||||
"Отправьте дату рождения пользователя в формате dd.mm.yyyy\n"
|
||||
"Пример: `26.05.1995`."
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_name(
|
||||
message: types.Message,
|
||||
users_repository: UsersRepository,
|
||||
subscription_user_id: uuid.UUID,
|
||||
):
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(user_id=subscription_user_id)
|
||||
if subscription_user is None or subscription_user.birthday is None:
|
||||
raise FlowInternalError()
|
||||
|
||||
text = (
|
||||
"Введите имя человека, на которого хотите подписаться\n\n"
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
if subscription_user.name is not None:
|
||||
text += (
|
||||
"ИЛИ\n\n"
|
||||
f"Используйте имя из профиля пользователя: `{subscription_user.name}`\n\n"
|
||||
)
|
||||
keyboard.button(text=subscription_user.name)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_decision(
|
||||
message: types.Message,
|
||||
pools_repository: PoolsRepository,
|
||||
users_repository: UsersRepository,
|
||||
subscription_user_id: uuid.UUID,
|
||||
):
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(user_id=subscription_user_id)
|
||||
if subscription_user is None or subscription_user.birthday is None:
|
||||
raise FlowInternalError()
|
||||
|
||||
async with pools_repository.transaction():
|
||||
pools_count = await pools_repository.get_pools_by_birthday_user_id_count(
|
||||
birthday_user_id=subscription_user_id,
|
||||
)
|
||||
|
||||
text = "Хотите ли вы участвовать в сборе денег для этого пользователя?"
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.CREATE_POOL_BUTTON)
|
||||
if pools_count > 0:
|
||||
keyboard.button(text=constants.JOIN_EXISTING_POOL_BUTTON)
|
||||
keyboard.button(text=constants.DECLINE_POOL_BUTTON)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def show_add_subscription_pools(
|
||||
user: User,
|
||||
pools_repository: PoolsRepository,
|
||||
message: types.Message | None = None,
|
||||
callback_query: types.CallbackQuery | None = None,
|
||||
):
|
||||
page = 1
|
||||
per_page = 5
|
||||
if callback_query is not None:
|
||||
callback_data = SubscriptionsCallbackData.unpack(callback_query.data)
|
||||
page = callback_data.page
|
||||
|
||||
async with subscriptions_repository.transaction():
|
||||
total = await subscriptions_repository.get_user_subscriptions_count(user_id=user.id)
|
||||
pages_count = total // per_page + int(bool(total % per_page))
|
||||
subscriptions = [
|
||||
subscription
|
||||
async for subscription in subscriptions_repository.get_user_subscriptions(
|
||||
user_id=user.id,
|
||||
pagination=PagePagination(page=page, per_page=per_page),
|
||||
)
|
||||
]
|
||||
|
||||
text = "Сборы:" if subscriptions else "Нет сборов"
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
for subscription in subscriptions:
|
||||
keyboard.button(
|
||||
text=subscription.name,
|
||||
callback_data=SubscriptionCallbackData(
|
||||
to_user_id=subscription.to_user_id,
|
||||
).pack(),
|
||||
)
|
||||
navigation_row = []
|
||||
if page > 1:
|
||||
navigation_row.append(types.InlineKeyboardButton(
|
||||
text="<",
|
||||
callback_data=SubscriptionsCallbackData(page=page - 1).pack(),
|
||||
))
|
||||
if pages_count > 1:
|
||||
navigation_row.append(types.InlineKeyboardButton(
|
||||
text=f"{page}/{pages_count}",
|
||||
callback_data="null",
|
||||
))
|
||||
if page < pages_count:
|
||||
navigation_row.append(types.InlineKeyboardButton(
|
||||
text=">",
|
||||
callback_data=SubscriptionsCallbackData(page=page + 1).pack(),
|
||||
))
|
||||
keyboard.row(*navigation_row)
|
||||
keyboard.row(
|
||||
types.InlineKeyboardButton(
|
||||
text="Добавить",
|
||||
callback_data=AddSubscriptionCallbackData().pack(),
|
||||
),
|
||||
types.InlineKeyboardButton(
|
||||
text="Назад",
|
||||
callback_data=MenuCallbackData().pack(),
|
||||
),
|
||||
)
|
||||
reply_markup = keyboard.as_markup()
|
||||
|
||||
if message is not None:
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
elif callback_query is not None:
|
||||
await callback_query.message.edit_text(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
)
|
||||
await callback_query.message.edit_reply_markup(
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_description(
|
||||
message: types.Message,
|
||||
):
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.SKIP_BUTTON)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text="Введи описание для сбора",
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_payment_phone(
|
||||
message: types.Message,
|
||||
user: User,
|
||||
):
|
||||
text = (
|
||||
"*Введите номер телефона, на который будете принимать сбор денег*\n\n"
|
||||
"ИЛИ\n\n"
|
||||
"*Отправьте свой контакт*\n\n"
|
||||
)
|
||||
if user.gift_payment_data is not None:
|
||||
bank_title = constants.BANKS_TITLE_MAP[user.gift_payment_data.bank]
|
||||
text += (
|
||||
"ИЛИ\n\n"
|
||||
"*Используйте данные из своего профиля*:\n"
|
||||
f"📱 _Телефон_: {user.gift_payment_data.phone}\n"
|
||||
f"🏦 _Банк_: {bank_title}\n\n"
|
||||
)
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
keyboard.button(text=constants.SHARE_CONTACT_BUTTON, request_contact=True)
|
||||
if user.gift_payment_data is not None:
|
||||
keyboard.button(text=constants.USE_PROFILE_GIFT_PAYMENT_DATA)
|
||||
keyboard.button(text=constants.BACK_BUTTON)
|
||||
keyboard.adjust(1)
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_pool_payment_bank(
|
||||
message: types.Message,
|
||||
):
|
||||
text = "Выберите банк для приёма платежей"
|
||||
|
||||
keyboard = ReplyKeyboardBuilder()
|
||||
for title, type_ in constants.BANKS_MAP.items():
|
||||
keyboard.button(text=title)
|
||||
keyboard.adjust(3)
|
||||
keyboard.row(types.KeyboardButton(text=constants.BACK_BUTTON))
|
||||
reply_markup = keyboard.as_markup(
|
||||
resize_keyboard=True,
|
||||
one_time_keyboard=True,
|
||||
)
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
|
||||
|
||||
async def ask_add_subscription_confirmation(
|
||||
message: types.Message,
|
||||
users_repository: UsersRepository,
|
||||
pools_repository: PoolsRepository,
|
||||
subscription_name: str,
|
||||
subscription_user_id: uuid.UUID,
|
||||
subscription_pool_id: uuid.UUID | None = None,
|
||||
subscription_pool_description: str | None = None,
|
||||
subscription_pool_phone: str | None = None,
|
||||
subscription_pool_bank: BankEnum | None = None,
|
||||
):
|
||||
async with users_repository.transaction():
|
||||
subscription_user = await users_repository.get_user_by_id(
|
||||
user_id=subscription_user_id,
|
||||
)
|
||||
if subscription_user is None:
|
||||
raise FlowInternalError()
|
||||
|
||||
subscription_pool = None
|
||||
if subscription_pool_id is not None:
|
||||
async with pools_repository.transaction():
|
||||
subscription_pool = await pools_repository.get_pool_by_id(
|
||||
pool_id=subscription_pool_id,
|
||||
with_owner=True,
|
||||
)
|
||||
if subscription_pool is None:
|
||||
raise FlowInternalError()
|
||||
|
||||
text = (
|
||||
f"Вы хотите подписаться на пользователя {subscription_name}?\n\n"
|
||||
f"🎂 *День рождения*: {subscription_user.birthday.strftime('%d.%m.%Y')}\n\n"
|
||||
)
|
||||
if all((subscription_pool_description, subscription_pool_phone, subscription_pool_bank)):
|
||||
bank_title = constants.BANKS_TITLE_MAP[subscription_pool_bank]
|
||||
text += (
|
||||
"💳️ *Вы собираете деньги на*:\n"
|
||||
f"📱 _Телефон_: {subscription_pool_phone}\n"
|
||||
f"🏦 _Банк_: {bank_title}\n\n"
|
||||
)
|
||||
if subscription_pool_description:
|
||||
text += (
|
||||
"_Описание_:\n"
|
||||
"```\n"
|
||||
f"{subscription_pool_description}"
|
||||
"```\n\n"
|
||||
)
|
||||
elif subscription_pool is not None:
|
||||
text += "💳️ ️️️*Вы участвуете в сборе денег*\n\n"
|
||||
|
||||
keyboard = InlineKeyboardBuilder()
|
||||
keyboard.button(
|
||||
text="Да",
|
||||
callback_data=AddSubscriptionConfirmCallbackData(
|
||||
answer=ConfirmAnswerEnum.YES,
|
||||
).pack(),
|
||||
)
|
||||
keyboard.button(
|
||||
text="Нет",
|
||||
callback_data=AddSubscriptionConfirmCallbackData(
|
||||
answer=ConfirmAnswerEnum.NO,
|
||||
).pack(),
|
||||
)
|
||||
keyboard.adjust(2)
|
||||
reply_markup = keyboard.as_markup()
|
||||
|
||||
await message.reply(
|
||||
text=text,
|
||||
parse_mode=ParseMode.MARKDOWN,
|
||||
reply_markup=reply_markup,
|
||||
)
|
||||
13
birthday_pool_bot/telegram_bot/ui/middlewares/__init__.py
Normal file
13
birthday_pool_bot/telegram_bot/ui/middlewares/__init__.py
Normal file
@@ -0,0 +1,13 @@
|
||||
from .auth import AuthMiddleware
|
||||
from .depends import DependsMiddleware
|
||||
from .typing import TypingMiddleware
|
||||
|
||||
|
||||
__all__ = (
|
||||
# auth
|
||||
"AuthMiddleware",
|
||||
# depends
|
||||
"DependsMiddleware",
|
||||
# typing
|
||||
"TypingMiddleware",
|
||||
)
|
||||
47
birthday_pool_bot/telegram_bot/ui/middlewares/auth.py
Normal file
47
birthday_pool_bot/telegram_bot/ui/middlewares/auth.py
Normal file
@@ -0,0 +1,47 @@
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
import aiogram
|
||||
from aiogram.types import TelegramObject, User as TelegramUser
|
||||
|
||||
from birthday_pool_bot.dto import User
|
||||
from birthday_pool_bot.repositories.repositories import UsersRepository
|
||||
from birthday_pool_bot.telegram_bot.ui.utils import get_telegram_user_full_name
|
||||
|
||||
|
||||
class AuthMiddleware(aiogram.BaseMiddleware):
|
||||
def __init__(self, users_repository: UsersRepository):
|
||||
self._users_repository = users_repository
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: dict[str, Any],
|
||||
):
|
||||
telegram_user = self.get_telegram_user(event=event)
|
||||
if telegram_user is None:
|
||||
return await handler(event, data)
|
||||
|
||||
async with self._users_repository.transaction():
|
||||
user = await self._users_repository.get_user_by_telegram_id(
|
||||
telegram_id=telegram_user.id,
|
||||
)
|
||||
if user is None:
|
||||
user = User(
|
||||
name=get_telegram_user_full_name(user=telegram_user),
|
||||
telegram_id=telegram_user.id,
|
||||
)
|
||||
user = await self._users_repository.create_user(user=user)
|
||||
|
||||
data["user"] = user
|
||||
return await handler(event, data)
|
||||
|
||||
def get_telegram_user(self, event: TelegramObject) -> TelegramUser | None:
|
||||
if hasattr(event, "from_user") and isinstance(event.from_user, TelegramUser):
|
||||
return event.from_user
|
||||
|
||||
if hasattr(event, "user") and isinstance(event.user, TelegramUser):
|
||||
return event.user
|
||||
|
||||
if hasattr(event, "new_chat_member") and event.new_chat_member:
|
||||
return event.new_chat_member.user
|
||||
19
birthday_pool_bot/telegram_bot/ui/middlewares/depends.py
Normal file
19
birthday_pool_bot/telegram_bot/ui/middlewares/depends.py
Normal file
@@ -0,0 +1,19 @@
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
import aiogram
|
||||
from aiogram.types import TelegramObject
|
||||
|
||||
|
||||
class DependsMiddleware(aiogram.BaseMiddleware):
|
||||
def __init__(self, name: str, object_: Any):
|
||||
self._name = name
|
||||
self._object = object_
|
||||
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||
event: TelegramObject,
|
||||
data: dict[str, Any],
|
||||
):
|
||||
data[self._name] = self._object
|
||||
return await handler(event, data)
|
||||
36
birthday_pool_bot/telegram_bot/ui/middlewares/typing.py
Normal file
36
birthday_pool_bot/telegram_bot/ui/middlewares/typing.py
Normal file
@@ -0,0 +1,36 @@
|
||||
import asyncio
|
||||
import contextlib
|
||||
from typing import Any, Awaitable, Callable
|
||||
|
||||
import aiogram
|
||||
from aiogram import types
|
||||
|
||||
|
||||
class TypingMiddleware(aiogram.BaseMiddleware):
|
||||
async def __call__(
|
||||
self,
|
||||
handler: Callable[[types.TelegramObject, dict[str, Any]], Awaitable[Any]],
|
||||
event: types.TelegramObject,
|
||||
data: dict[str, Any],
|
||||
):
|
||||
if not isinstance(event, types.Message):
|
||||
return await handler(event, data)
|
||||
|
||||
bot = data.get("bot")
|
||||
chat_id = event.chat.id
|
||||
async with send_typing_action(bot=bot, chat_id=chat_id):
|
||||
return await handler(event, data)
|
||||
|
||||
|
||||
@contextlib.asynccontextmanager
|
||||
async def send_typing_action(bot: aiogram.Bot, chat_id: int):
|
||||
async def background_task():
|
||||
while True:
|
||||
await bot.send_chat_action(chat_id=chat_id, action="typing")
|
||||
await asyncio.sleep(3)
|
||||
|
||||
task = asyncio.create_task(background_task())
|
||||
try:
|
||||
yield
|
||||
finally:
|
||||
task.cancel()
|
||||
38
birthday_pool_bot/telegram_bot/ui/states.py
Normal file
38
birthday_pool_bot/telegram_bot/ui/states.py
Normal file
@@ -0,0 +1,38 @@
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
|
||||
|
||||
class MenuState(StatesGroup):
|
||||
MENU = State()
|
||||
PROFILE = State()
|
||||
SUBSCRIPTIONS = State()
|
||||
|
||||
|
||||
class SetProfileNameState(StatesGroup):
|
||||
WAITING_FOR_NAME = State()
|
||||
|
||||
|
||||
class SetProfilePhoneState(StatesGroup):
|
||||
WAITING_FOR_PHONE = State()
|
||||
|
||||
|
||||
class SetProfileBirthdayState(StatesGroup):
|
||||
WAITING_FOR_DATE = State()
|
||||
|
||||
|
||||
class SetProfileGiftPaymentDataState(StatesGroup):
|
||||
WAITING_FOR_PHONE = State()
|
||||
WAITING_FOR_BANK = State()
|
||||
|
||||
|
||||
class AddSubscriptionState(StatesGroup):
|
||||
WAITING_FOR_PHONE = State()
|
||||
WAITING_FOR_DATE = State()
|
||||
WAITING_FOR_NAME = State()
|
||||
WAITING_FOR_POOL_DECISION = State()
|
||||
WAITING_FOR_CONFIRMATION = State()
|
||||
|
||||
|
||||
class AddSubscriptionPoolState(StatesGroup):
|
||||
WAITING_FOR_DESCRIPTION = State()
|
||||
WAITING_FOR_PAYMENT_PHONE = State()
|
||||
WAITING_FOR_PAYMENT_BANK = State()
|
||||
25
birthday_pool_bot/telegram_bot/ui/utils.py
Normal file
25
birthday_pool_bot/telegram_bot/ui/utils.py
Normal file
@@ -0,0 +1,25 @@
|
||||
import datetime
|
||||
import re
|
||||
|
||||
from aiogram import types
|
||||
from aiogram.types import User as TelegramUser
|
||||
|
||||
|
||||
BIRTHDATE_REGEXP = re.compile("^(?P<day>\d{2})\.(?P<month>\d{2})\.(?P<year>\d{4})$")
|
||||
|
||||
|
||||
def parse_date(message: types.Message) -> datetime.date:
|
||||
if (re_match := BIRTHDATE_REGEXP.match(message.text)) is None:
|
||||
return
|
||||
|
||||
return datetime.date(
|
||||
year=int(re_match.group("year")),
|
||||
month=int(re_match.group("month")),
|
||||
day=int(re_match.group("day")),
|
||||
)
|
||||
|
||||
|
||||
def get_telegram_user_full_name(user: TelegramUser) -> str:
|
||||
name_parts = (user.first_name, user.last_name)
|
||||
return " ".join(filter(None, name_parts))
|
||||
|
||||
Reference in New Issue
Block a user