197 lines
6.2 KiB
Python
197 lines
6.2 KiB
Python
import logging
|
|
from datetime import datetime, timezone
|
|
from typing import Any, Optional
|
|
|
|
from aiogram import Bot
|
|
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from bot.middlewares.i18n import JsonI18n
|
|
from config.settings import Settings
|
|
from db.dal import user_dal
|
|
from db.models import User
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
TELEGRAM_NOTIFICATIONS_UNKNOWN = "unknown"
|
|
TELEGRAM_NOTIFICATIONS_ENABLED = "enabled"
|
|
TELEGRAM_NOTIFICATIONS_NEEDS_START = "needs_start"
|
|
TELEGRAM_NOTIFICATIONS_BLOCKED = "blocked"
|
|
TELEGRAM_NOTIFICATION_STATUSES = {
|
|
TELEGRAM_NOTIFICATIONS_UNKNOWN,
|
|
TELEGRAM_NOTIFICATIONS_ENABLED,
|
|
TELEGRAM_NOTIFICATIONS_NEEDS_START,
|
|
TELEGRAM_NOTIFICATIONS_BLOCKED,
|
|
}
|
|
|
|
|
|
def normalize_telegram_notification_status(value: Optional[str]) -> str:
|
|
status = str(value or "").strip().lower()
|
|
return status if status in TELEGRAM_NOTIFICATION_STATUSES else TELEGRAM_NOTIFICATIONS_UNKNOWN
|
|
|
|
|
|
def telegram_notifications_enabled(user: Optional[User]) -> bool:
|
|
return (
|
|
bool(getattr(user, "telegram_id", None))
|
|
and normalize_telegram_notification_status(
|
|
getattr(user, "telegram_notifications_status", None)
|
|
)
|
|
== TELEGRAM_NOTIFICATIONS_ENABLED
|
|
)
|
|
|
|
|
|
def telegram_notifications_need_prompt(user: Optional[User]) -> bool:
|
|
status = normalize_telegram_notification_status(
|
|
getattr(user, "telegram_notifications_status", None)
|
|
)
|
|
return bool(getattr(user, "telegram_id", None)) and status in {
|
|
TELEGRAM_NOTIFICATIONS_NEEDS_START,
|
|
TELEGRAM_NOTIFICATIONS_BLOCKED,
|
|
}
|
|
|
|
|
|
def telegram_notifications_start_link(bot_username: Optional[str]) -> Optional[str]:
|
|
username = str(bot_username or "").strip().lstrip("@")
|
|
if not username or username == "your_bot_username":
|
|
return None
|
|
return f"https://t.me/{username}?start=notifications"
|
|
|
|
|
|
def telegram_notification_status_from_error(exc: Exception) -> Optional[str]:
|
|
if isinstance(exc, TelegramForbiddenError):
|
|
return TELEGRAM_NOTIFICATIONS_BLOCKED
|
|
if not isinstance(exc, TelegramBadRequest):
|
|
return None
|
|
|
|
message = str(exc).lower()
|
|
if any(
|
|
token in message
|
|
for token in (
|
|
"bot was blocked",
|
|
"user is deactivated",
|
|
"forbidden",
|
|
)
|
|
):
|
|
return TELEGRAM_NOTIFICATIONS_BLOCKED
|
|
if any(
|
|
token in message
|
|
for token in (
|
|
"chat not found",
|
|
"bot can't initiate conversation",
|
|
"bot can't initiate",
|
|
"user not found",
|
|
)
|
|
):
|
|
return TELEGRAM_NOTIFICATIONS_NEEDS_START
|
|
return None
|
|
|
|
|
|
async def mark_telegram_notifications_status(
|
|
session: AsyncSession,
|
|
user_id: int,
|
|
status: str,
|
|
*,
|
|
telegram_id: Optional[int] = None,
|
|
checked_at: Optional[datetime] = None,
|
|
) -> Optional[User]:
|
|
normalized = normalize_telegram_notification_status(status)
|
|
now = checked_at or datetime.now(timezone.utc)
|
|
update_data: dict[str, Any] = {
|
|
"telegram_notifications_status": normalized,
|
|
"telegram_notifications_checked_at": now,
|
|
}
|
|
if telegram_id:
|
|
update_data["telegram_id"] = int(telegram_id)
|
|
if normalized == TELEGRAM_NOTIFICATIONS_ENABLED:
|
|
update_data["telegram_notifications_enabled_at"] = now
|
|
update_data["telegram_notifications_blocked_at"] = None
|
|
elif normalized == TELEGRAM_NOTIFICATIONS_BLOCKED:
|
|
update_data["telegram_notifications_blocked_at"] = now
|
|
return await user_dal.update_user(session, user_id, update_data)
|
|
|
|
|
|
async def mark_telegram_notifications_enabled_for_telegram_user(
|
|
session: AsyncSession,
|
|
telegram_id: int,
|
|
) -> Optional[User]:
|
|
db_user = await user_dal.get_user_by_telegram_id(session, telegram_id)
|
|
if not db_user:
|
|
db_user = await user_dal.get_user_by_id(session, telegram_id)
|
|
if not db_user:
|
|
return None
|
|
return await mark_telegram_notifications_status(
|
|
session,
|
|
int(db_user.user_id),
|
|
TELEGRAM_NOTIFICATIONS_ENABLED,
|
|
telegram_id=telegram_id,
|
|
)
|
|
|
|
|
|
async def probe_telegram_notifications(
|
|
*,
|
|
session: AsyncSession,
|
|
bot: Bot,
|
|
settings: Settings,
|
|
i18n: Optional[JsonI18n],
|
|
user: User,
|
|
bot_username: Optional[str] = None,
|
|
force: bool = False,
|
|
) -> dict[str, Any]:
|
|
telegram_id = getattr(user, "telegram_id", None)
|
|
if not telegram_id:
|
|
return {
|
|
"ok": False,
|
|
"status": TELEGRAM_NOTIFICATIONS_UNKNOWN,
|
|
"start_link": telegram_notifications_start_link(bot_username),
|
|
}
|
|
|
|
current_status = normalize_telegram_notification_status(
|
|
getattr(user, "telegram_notifications_status", None)
|
|
)
|
|
if current_status == TELEGRAM_NOTIFICATIONS_ENABLED and not force:
|
|
return {
|
|
"ok": True,
|
|
"status": TELEGRAM_NOTIFICATIONS_ENABLED,
|
|
"start_link": telegram_notifications_start_link(bot_username),
|
|
}
|
|
|
|
try:
|
|
await bot.get_chat(int(telegram_id))
|
|
except Exception as exc:
|
|
status = telegram_notification_status_from_error(exc)
|
|
if status:
|
|
await mark_telegram_notifications_status(session, int(user.user_id), status)
|
|
return {
|
|
"ok": False,
|
|
"status": status,
|
|
"start_link": telegram_notifications_start_link(bot_username),
|
|
}
|
|
logger.warning(
|
|
"Telegram notification chat probe failed for user %s / telegram %s: %s",
|
|
user.user_id,
|
|
telegram_id,
|
|
exc,
|
|
)
|
|
await mark_telegram_notifications_status(
|
|
session,
|
|
int(user.user_id),
|
|
TELEGRAM_NOTIFICATIONS_UNKNOWN,
|
|
)
|
|
return {
|
|
"ok": False,
|
|
"status": TELEGRAM_NOTIFICATIONS_UNKNOWN,
|
|
"start_link": telegram_notifications_start_link(bot_username),
|
|
}
|
|
|
|
await mark_telegram_notifications_status(
|
|
session,
|
|
int(user.user_id),
|
|
TELEGRAM_NOTIFICATIONS_ENABLED,
|
|
telegram_id=int(telegram_id),
|
|
)
|
|
return {
|
|
"ok": True,
|
|
"status": TELEGRAM_NOTIFICATIONS_ENABLED,
|
|
"start_link": telegram_notifications_start_link(bot_username),
|
|
}
|