"""Command handlers: /start, /help; /start registers user.""" import asyncio import duty_teller.config as config from telegram import Update from telegram.ext import CommandHandler, ContextTypes from duty_teller.db.session import session_scope from duty_teller.db.repository import ( get_or_create_user, set_user_phone, create_calendar_token, ) from duty_teller.i18n import get_lang, t from duty_teller.utils.user import build_full_name async def start(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.message: return user = update.effective_user if not user: return full_name = build_full_name(user.first_name, user.last_name) telegram_user_id = user.id username = user.username first_name = user.first_name last_name = user.last_name def do_get_or_create() -> None: with session_scope(config.DATABASE_URL) as session: get_or_create_user( session, telegram_user_id=telegram_user_id, full_name=full_name, username=username, first_name=first_name, last_name=last_name, ) await asyncio.get_running_loop().run_in_executor(None, do_get_or_create) lang = get_lang(user) text = t(lang, "start.greeting") await update.message.reply_text(text) async def set_phone(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.message or not update.effective_user: return lang = get_lang(update.effective_user) if update.effective_chat and update.effective_chat.type != "private": await update.message.reply_text(t(lang, "set_phone.private_only")) return args = context.args or [] phone = " ".join(args).strip() if args else None telegram_user_id = update.effective_user.id def do_set_phone() -> str | None: with session_scope(config.DATABASE_URL) as session: full_name = build_full_name( update.effective_user.first_name, update.effective_user.last_name ) get_or_create_user( session, telegram_user_id=telegram_user_id, full_name=full_name, username=update.effective_user.username, first_name=update.effective_user.first_name, last_name=update.effective_user.last_name, ) user = set_user_phone(session, telegram_user_id, phone or None) if user is None: return "error" if phone: return "saved" return "cleared" result = await asyncio.get_running_loop().run_in_executor(None, do_set_phone) if result == "error": await update.message.reply_text(t(lang, "set_phone.error")) elif result == "saved": await update.message.reply_text(t(lang, "set_phone.saved", phone=phone or "")) else: await update.message.reply_text(t(lang, "set_phone.cleared")) async def calendar_link(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: """Send personal calendar subscription URL (private chat only, access check).""" if not update.message or not update.effective_user: return lang = get_lang(update.effective_user) if update.effective_chat and update.effective_chat.type != "private": await update.message.reply_text(t(lang, "calendar_link.private_only")) return telegram_user_id = update.effective_user.id username = (update.effective_user.username or "").strip() full_name = build_full_name( update.effective_user.first_name, update.effective_user.last_name ) def do_calendar_link() -> tuple[str | None, str | None]: with session_scope(config.DATABASE_URL) as session: user = get_or_create_user( session, telegram_user_id=telegram_user_id, full_name=full_name, username=update.effective_user.username, first_name=update.effective_user.first_name, last_name=update.effective_user.last_name, ) if not config.can_access_miniapp( username ) and not config.can_access_miniapp_by_phone(user.phone): return (None, "denied") token = create_calendar_token(session, user.id) base = (config.MINI_APP_BASE_URL or "").rstrip("/") url = f"{base}/api/calendar/ical/{token}.ics" if base else None return (url, None) result_url, error = await asyncio.get_running_loop().run_in_executor( None, do_calendar_link ) if error == "denied": await update.message.reply_text(t(lang, "calendar_link.access_denied")) return if not result_url: await update.message.reply_text(t(lang, "calendar_link.error")) return await update.message.reply_text( t(lang, "calendar_link.success", url=result_url) + "\n\n" + t(lang, "calendar_link.help_hint") ) async def help_cmd(update: Update, context: ContextTypes.DEFAULT_TYPE) -> None: if not update.message or not update.effective_user: return lang = get_lang(update.effective_user) lines = [ t(lang, "help.title"), t(lang, "help.start"), t(lang, "help.help"), t(lang, "help.set_phone"), t(lang, "help.calendar_link"), t(lang, "help.pin_duty"), ] if config.is_admin(update.effective_user.username or ""): lines.append(t(lang, "help.import_schedule")) await update.message.reply_text("\n".join(lines)) start_handler = CommandHandler("start", start) help_handler = CommandHandler("help", help_cmd) set_phone_handler = CommandHandler("set_phone", set_phone) calendar_link_handler = CommandHandler("calendar_link", calendar_link)