Fix campaign tracking for users subscribing before /start
This commit is contained in:
+52
-1
@@ -222,12 +222,28 @@ async def _continue_registration_after_language(
|
||||
|
||||
async def cmd_start(message: types.Message, state: FSMContext, db: AsyncSession, db_user=None):
|
||||
logger.info(f"🚀 START: Обработка /start от {message.from_user.id}")
|
||||
|
||||
|
||||
data = await state.get_data() or {}
|
||||
pending_start_payload = data.pop("pending_start_payload", None)
|
||||
|
||||
referral_code = None
|
||||
campaign = None
|
||||
start_args = message.text.split()
|
||||
start_parameter = None
|
||||
|
||||
if len(start_args) > 1:
|
||||
start_parameter = start_args[1]
|
||||
elif pending_start_payload:
|
||||
start_parameter = pending_start_payload
|
||||
logger.info(
|
||||
"📦 START: Используем сохраненный payload '%s'",
|
||||
pending_start_payload,
|
||||
)
|
||||
|
||||
if pending_start_payload is not None:
|
||||
await state.set_data(data)
|
||||
|
||||
if start_parameter:
|
||||
campaign = await get_campaign_by_start_parameter(
|
||||
db,
|
||||
start_parameter,
|
||||
@@ -1409,6 +1425,41 @@ async def required_sub_channel_check(
|
||||
try:
|
||||
state_data = await state.get_data() or {}
|
||||
|
||||
pending_start_payload = state_data.pop("pending_start_payload", None)
|
||||
state_updated = pending_start_payload is not None
|
||||
|
||||
if pending_start_payload:
|
||||
logger.info(
|
||||
"📦 CHANNEL CHECK: Найден сохраненный payload '%s'",
|
||||
pending_start_payload,
|
||||
)
|
||||
|
||||
if "campaign_id" not in state_data and "referral_code" not in state_data:
|
||||
campaign = await get_campaign_by_start_parameter(
|
||||
db,
|
||||
pending_start_payload,
|
||||
only_active=True,
|
||||
)
|
||||
|
||||
if campaign:
|
||||
state_data["campaign_id"] = campaign.id
|
||||
logger.info(
|
||||
"📣 CHANNEL CHECK: Кампания %s восстановлена из payload",
|
||||
campaign.id,
|
||||
)
|
||||
else:
|
||||
state_data["referral_code"] = pending_start_payload
|
||||
logger.info(
|
||||
"🎯 CHANNEL CHECK: Payload интерпретирован как реферальный код",
|
||||
)
|
||||
else:
|
||||
logger.debug(
|
||||
"ℹ️ CHANNEL CHECK: Payload уже обработан ранее, пропускаем восстановление",
|
||||
)
|
||||
|
||||
if state_updated:
|
||||
await state.set_data(state_data)
|
||||
|
||||
user = db_user
|
||||
if not user:
|
||||
user = await get_user_by_telegram_id(db, query.from_user.id)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import logging
|
||||
from typing import Callable, Dict, Any, Awaitable
|
||||
from typing import Callable, Dict, Any, Awaitable, Optional
|
||||
from aiogram import BaseMiddleware, Bot
|
||||
from aiogram.exceptions import TelegramForbiddenError, TelegramBadRequest
|
||||
from aiogram.fsm.context import FSMContext
|
||||
@@ -86,16 +86,16 @@ class ChannelCheckerMiddleware(BaseMiddleware):
|
||||
return await handler(event, data)
|
||||
|
||||
is_required = settings.CHANNEL_IS_REQUIRED_SUB
|
||||
|
||||
|
||||
if not is_required:
|
||||
logger.debug("⚠️ Обязательная подписка отключена, пропускаем проверку")
|
||||
return await handler(event, data)
|
||||
|
||||
channel_link = settings.CHANNEL_LINK
|
||||
|
||||
|
||||
try:
|
||||
member = await bot.get_chat_member(chat_id=channel_id, user_id=telegram_id)
|
||||
|
||||
|
||||
if member.status in self.GOOD_MEMBER_STATUS:
|
||||
return await handler(event, data)
|
||||
elif member.status in self.BAD_MEMBER_STATUS:
|
||||
@@ -104,6 +104,8 @@ class ChannelCheckerMiddleware(BaseMiddleware):
|
||||
if telegram_id:
|
||||
await self._deactivate_trial_subscription(telegram_id)
|
||||
|
||||
await self._capture_start_payload(state, event)
|
||||
|
||||
if isinstance(event, CallbackQuery) and event.data == "sub_channel_check":
|
||||
await event.answer("❌ Вы еще не подписались на канал! Подпишитесь и попробуйте снова.", show_alert=True)
|
||||
return
|
||||
@@ -111,10 +113,12 @@ class ChannelCheckerMiddleware(BaseMiddleware):
|
||||
return await self._deny_message(event, bot, channel_link)
|
||||
else:
|
||||
logger.warning(f"⚠️ Неожиданный статус пользователя {telegram_id}: {member.status}")
|
||||
await self._capture_start_payload(state, event)
|
||||
return await self._deny_message(event, bot, channel_link)
|
||||
|
||||
|
||||
except TelegramForbiddenError as e:
|
||||
logger.error(f"❌ Бот заблокирован в канале {channel_id}: {e}")
|
||||
await self._capture_start_payload(state, event)
|
||||
return await self._deny_message(event, bot, channel_link)
|
||||
except TelegramBadRequest as e:
|
||||
if "chat not found" in str(e).lower():
|
||||
@@ -123,11 +127,45 @@ class ChannelCheckerMiddleware(BaseMiddleware):
|
||||
logger.error(f"❌ Пользователь {telegram_id} не найден: {e}")
|
||||
else:
|
||||
logger.error(f"❌ Ошибка запроса к каналу {channel_id}: {e}")
|
||||
await self._capture_start_payload(state, event)
|
||||
return await self._deny_message(event, bot, channel_link)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Неожиданная ошибка при проверке подписки: {e}")
|
||||
return await handler(event, data)
|
||||
|
||||
async def _capture_start_payload(self, state: Optional[FSMContext], event: TelegramObject) -> None:
|
||||
if not state:
|
||||
return
|
||||
|
||||
message: Optional[Message] = None
|
||||
if isinstance(event, Message):
|
||||
message = event
|
||||
elif isinstance(event, CallbackQuery):
|
||||
message = event.message
|
||||
elif isinstance(event, Update):
|
||||
message = event.message
|
||||
|
||||
if not message or not message.text:
|
||||
return
|
||||
|
||||
text = message.text.strip()
|
||||
if not text.startswith("/start"):
|
||||
return
|
||||
|
||||
parts = text.split(maxsplit=1)
|
||||
if len(parts) < 2 or not parts[1]:
|
||||
return
|
||||
|
||||
payload = parts[1]
|
||||
|
||||
data = await state.get_data() or {}
|
||||
if data.get("pending_start_payload") == payload:
|
||||
return
|
||||
|
||||
data["pending_start_payload"] = payload
|
||||
await state.set_data(data)
|
||||
logger.debug("💾 Сохранен start payload %s для последующей обработки", payload)
|
||||
|
||||
async def _deactivate_trial_subscription(self, telegram_id: int) -> None:
|
||||
async for db in get_db():
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user