Files

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),
}