from telebot import types from telebot.async_telebot import Handler, AsyncTeleBot from telebot.asyncio_handler_backends import StatesGroup, State, ContinueHandling from telebot.types import ReplyKeyboardRemove from account.models import User from tg_bot.messages import TGCoreMessage from tg_bot.keyboards import TGKeyboards from tg_bot.tasks import send_tg_message from tg_bot.utils import extract_deep_link # TODO: step handlers (https://github.com/eternnoir/pyTelegramBotAPI/blob/master/examples/step_example.py) # TODO: state filters class BotStates(StatesGroup): idle = State() send_phone = State() # Default message handler async def default_handler(message: types.Message, bot: AsyncTeleBot): cid = message.from_user.id # Set default state for sender if await bot.get_state(cid) is None: await bot.set_state(cid, BotStates.idle) return ContinueHandling() # === Command /start === async def start_handler(message: types.Message, bot: AsyncTeleBot): cid = message.from_user.id await bot.set_state(cid, BotStates.idle) if not await is_tg_bound_to_user(cid): # Send welcome message await bot.send_message(cid, TGCoreMessage.START) referral_code = extract_deep_link(message.text) # Save referral code in chat context for later usage # TODO: use base64-encoded hash here for additional data maybe? async with bot.retrieve_data(cid) as data: data['referral_code'] = referral_code # Ask for phone number to bind to User await request_phone(cid, TGCoreMessage.SHARE_PHONE, bot) else: # TODO: if user is signed in, send help text or account info instead await bot.send_message(cid, TGCoreMessage.ALREADY_AUTH) # TODO: implement sometime in the future # === Command /add_phone === # async def add_phone_handler(message: types.Message, bot: AsyncTeleBot): # cid = message.from_user.id # await request_phone(cid, bot, TGCoreMessage.SHARE_PHONE) async def is_tg_bound_to_user(tg_user_id): return await User.objects.filter(tg_user_id=tg_user_id, phone__isnull=False).aexists() async def request_phone(user_id, text, bot: AsyncTeleBot): """ Ask for phone number to bind to User """ reply_markup = TGKeyboards.SHARE_PHONE await bot.set_state(user_id, BotStates.send_phone) await bot.send_message(user_id, text, reply_markup=reply_markup) def request_phone_sync(user_id, text): state = BotStates.send_phone reply_markup = TGKeyboards.SHARE_PHONE send_tg_message(user_id, text, state=state.name, reply_markup=reply_markup) async def contact_handler(message: types.Message, bot: AsyncTeleBot): """ User has sent his phone number to authenticate in bot """ cid = message.from_user.id async with bot.retrieve_data(cid) as data: await User.objects.bind_tg_user( tg_user_id=cid, phone=message.contact.phone_number, referral_code=data.pop('referral_code', None) ) await bot.send_message(cid, TGCoreMessage.AUTH_SUCCESS, reply_markup=ReplyKeyboardRemove()) await bot.set_state(cid, BotStates.idle) def get_handlers(): return [ Handler(callback=default_handler), Handler(callback=start_handler, commands=['start'], state=BotStates.idle), # Handler(callback=add_phone_handler, commands=['add_phone']), Handler(callback=contact_handler, content_types=['contact'], state=BotStates.send_phone, func=lambda m: m.from_user.id == m.contact.user_id # Disallow contact forwarding ), ] """ - backend: try to find User with tg_user_id of Message - no User found -> ask for phone Possible situations: 1) No user in db: - tg: ask for phone - backend: create draft user - web: login via tg -> 2) User exists in DB, but has no phone (phone deleted in web) - tg: ask for phone - find User with tg_user_id & add phone 3) User exists in DB, but no tg_user_id or phone ??? """ """ Options: - login via telegram -> find User with tg_user_id -> if found, login - send contact from bot -> find user with phone -> add tg_user_id to User - """