kwork-poizonstore/tg_bot/handlers/start.py

134 lines
4.1 KiB
Python

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
-
"""