feat: ask about previous mobile email access before showing instructions

This commit is contained in:
Денис Кривоченко
2026-07-30 17:21:32 +07:00
parent 3e98471e21
commit e550e657fe
3 changed files with 103 additions and 61 deletions
+44 -13
View File
@@ -19,6 +19,7 @@ from utils.texts import (
INSTRUCT_TRUECONF_WITH_AD, INSTRUCT_TRUECONF_WITH_AD,
INSTRUCT_TRUECONF_PENDING_TEXT, INSTRUCT_TRUECONF_PENDING_TEXT,
INSTRUCT_EMAIL_AVAILABLE, INSTRUCT_EMAIL_AVAILABLE,
INSTRUCT_EMAIL_HISTORY_ASK,
INSTRUCT_EMAIL_UNAVAILABLE, INSTRUCT_EMAIL_UNAVAILABLE,
INSTRUCT_ERROR_MSG, INSTRUCT_ERROR_MSG,
) )
@@ -446,19 +447,10 @@ async def instruct_router_handler(msg: Message):
ad_info = get_ad_user_info(user_id) ad_info = get_ad_user_info(user_id)
raw_mail = ad_info.get("raw_mail") if ad_info else None raw_mail = ad_info.get("raw_mail") if ad_info else None
if raw_mail: if raw_mail:
text = INSTRUCT_EMAIL_AVAILABLE(EMOJI_DIGITS, cn, raw_mail) # Пользователь с доступом к почте — спрашиваем про прошлый доступ
# Отправляем текст сообщения set_state(user_id, "INSTRUCT_EMAIL_HISTORY")
text = INSTRUCT_EMAIL_HISTORY_ASK(EMOJI_DIGITS, cn)
await msg.answer(text, parse_mode="html") await msg.answer(text, parse_mode="html")
# Затем отправляем оба документа отдельно
for instruct_path, file_name in [
(GMAIL_ANDROID_INSTRUCT_PATH, "Инструкция Android.docx"),
(APPLE_INSTRUCT_PATH, "Инструкция Apple.docx"),
]:
if os.path.isfile(instruct_path):
await msg.bot.send_document(
chat_id=msg.chat_id,
file=FSInputFile(instruct_path, file_name=file_name),
)
else: else:
text = INSTRUCT_EMAIL_UNAVAILABLE(EMOJI_DIGITS, cn) text = INSTRUCT_EMAIL_UNAVAILABLE(EMOJI_DIGITS, cn)
await msg.answer(text, parse_mode="html") await msg.answer(text, parse_mode="html")
@@ -489,6 +481,45 @@ async def instruct_router_handler(msg: Message):
else: else:
await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html") await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
elif current_state == "INSTRUCT_EMAIL_HISTORY":
msg.handled = True
if cmd == "0":
clear_state(user_id)
from utils.menu import MENU_TEXT
await msg.answer(MENU_TEXT, parse_mode="html")
elif cmd == "9":
set_state(user_id, "INSTRUCT_MODE")
await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html")
elif cmd == "1":
# Да, был доступ — показываем инструкции
log_menu_stats(user_id, "Инструкции", "Почта (был доступ)")
cn = get_user_cn(user_id)
if cn is None:
cn = user_id
set_state(user_id, "INSTRUCT_EMAIL_VIEW")
text = INSTRUCT_EMAIL_AVAILABLE(EMOJI_DIGITS, cn, "")
await msg.answer(text, parse_mode="html")
for instruct_path, file_name in [
(GMAIL_ANDROID_INSTRUCT_PATH, "Инструкция Android.docx"),
(APPLE_INSTRUCT_PATH, "Инструкция Apple.docx"),
]:
if os.path.isfile(instruct_path):
await msg.bot.send_document(
chat_id=msg.chat_id,
file=FSInputFile(instruct_path, file_name=file_name),
)
elif cmd == "2":
# Нет, не было доступа — показываем текст про заявку
log_menu_stats(user_id, "Инструкции", "Почта (не было доступа)")
cn = get_user_cn(user_id)
if cn is None:
cn = user_id
set_state(user_id, "INSTRUCT_EMAIL_VIEW")
text = INSTRUCT_EMAIL_UNAVAILABLE(EMOJI_DIGITS, cn)
await msg.answer(text, parse_mode="html")
else:
await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
elif current_state in ["INSTRUCT_TRUECONF_VIEW", "INSTRUCT_EMAIL_VIEW"]: elif current_state in ["INSTRUCT_TRUECONF_VIEW", "INSTRUCT_EMAIL_VIEW"]:
msg.handled = True msg.handled = True
if cmd == "0": if cmd == "0":
@@ -499,4 +530,4 @@ async def instruct_router_handler(msg: Message):
set_state(user_id, "INSTRUCT_MODE") set_state(user_id, "INSTRUCT_MODE")
await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html") await msg.answer(INSTRUCT_MAIN_MENU_TEXT, parse_mode="html")
else: else:
await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html") await msg.answer(UNKNOWN_MAIN_CMD_TEXT, parse_mode="html")
+48 -47
View File
@@ -1,47 +1,48 @@
# /opt/trueconf_bot/utils/states.py # /opt/trueconf_bot/utils/states.py
import time import time
import config.config as config import config.config as config
user_states = {} user_states = {}
tc_bot = None # Общая переменная для экземпляра бота tc_bot = None # Общая переменная для экземпляра бота
# --- Константы состояний --- # --- Константы состояний ---
PHOTO_MODE = "PHOTO_MODE" PHOTO_MODE = "PHOTO_MODE"
SD_MODE = "SD_MODE" SD_MODE = "SD_MODE"
TRANSCRIPTION_MODE = "TRANSCRIPTION_MODE" TRANSCRIPTION_MODE = "TRANSCRIPTION_MODE"
SEARCH_MODE = "SEARCH_MODE" SEARCH_MODE = "SEARCH_MODE"
INSTRUCT_EMAIL_HISTORY = "INSTRUCT_EMAIL_HISTORY"
def set_state(user_id, state):
"""Фиксирует новое состояние пользователя и обновляет метку времени активности""" def set_state(user_id, state):
user_states[user_id] = { """Фиксирует новое состояние пользователя и обновляет метку времени активности"""
"state": state, user_states[user_id] = {
"last_active": time.time() "state": state,
} "last_active": time.time()
}
def get_state(user_id):
""" def get_state(user_id):
Возвращает текущее состояние пользователя. """
Если время неактивности превысило лимит из config.py, сессия сбрасывается. Возвращает текущее состояние пользователя.
""" Если время неактивности превысило лимит из config.py, сессия сбрасывается.
if user_id in user_states: """
session = user_states[user_id] if user_id in user_states:
session = user_states[user_id]
# Динамически забираем значение таймаута из конфига (дефолт — 1800 сек / 30 мин)
session_timeout = getattr(config, 'SESSION_TIMEOUT', 1800) # Динамически забираем значение таймаута из конфига (дефолт — 1800 сек / 30 мин)
session_timeout = getattr(config, 'SESSION_TIMEOUT', 1800)
# Проверяем, сколько секунд прошло с момента последнего сообщения
if time.time() - session["last_active"] > session_timeout: # Проверяем, сколько секунд прошло с момента последнего сообщения
# Время вышло! Аппаратно сносим сессию навигации if time.time() - session["last_active"] > session_timeout:
del user_states[user_id] # Время вышло! Аппаратно сносим сессию навигации
return None del user_states[user_id]
return None
# Если время не вышло, обновляем таймер (пользователь проявил активность)
session["last_active"] = time.time() # Если время не вышло, обновляем таймер (пользователь проявил активность)
return session["state"] session["last_active"] = time.time()
return session["state"]
return None
return None
def clear_state(user_id):
"""Принудительно очищает состояние пользователя (выход в корень)""" def clear_state(user_id):
if user_id in user_states: """Принудительно очищает состояние пользователя (выход в корень)"""
del user_states[user_id] if user_id in user_states:
del user_states[user_id]
+11 -1
View File
@@ -633,6 +633,15 @@ def _instruct_email_available(emojis: dict, cn: str, mail: str) -> str:
f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>" f"<i>{emojis['9']} — Назад\n{emojis['0']} — В главное меню</i>"
) )
def _instruct_email_history_ask(emojis: dict, cn: str) -> str:
"""Вопрос о наличии ранее доступа к мобильной почте."""
return (
f"📧 <b>Был ли у Вас ранее доступ к мобильной почте?</b>\n\n"
f"Нажмите {emojis['1']} — да, был / {emojis['2']} — нет, не было\n\n"
f"<i>{emojis['0']} — В главное меню</i>"
)
def _instruct_email_unavailable(emojis: dict, cn: str) -> str: def _instruct_email_unavailable(emojis: dict, cn: str) -> str:
"""Текст когда почта недоступна.""" """Текст когда почта недоступна."""
return ( return (
@@ -649,7 +658,8 @@ INSTRUCT_TRUECONF_WITH_AD = _instruct_trueconf_with_ad
INSTRUCT_TRUECONF_PENDING_TEXT = _instruct_trueconf_pending INSTRUCT_TRUECONF_PENDING_TEXT = _instruct_trueconf_pending
INSTRUCT_TRUECONF_NO_TICKET_TEXT = _instruct_trueconf_no_ticket INSTRUCT_TRUECONF_NO_TICKET_TEXT = _instruct_trueconf_no_ticket
INSTRUCT_EMAIL_AVAILABLE = _instruct_email_available INSTRUCT_EMAIL_AVAILABLE = _instruct_email_available
INSTRUCT_EMAIL_HISTORY_ASK = _instruct_email_history_ask
INSTRUCT_EMAIL_UNAVAILABLE = _instruct_email_unavailable INSTRUCT_EMAIL_UNAVAILABLE = _instruct_email_unavailable
# Единая стандартная заглушка системной ошибки для модуля инструкций # Единая стандартная заглушка системной ошибки для модуля инструкций
INSTRUCT_ERROR_MSG = system_error_text(EMOJI_DIGITS, show_back=True) INSTRUCT_ERROR_MSG = system_error_text(EMOJI_DIGITS, show_back=True)