chore: run lint and prettifier

This commit is contained in:
3252a8
2026-05-12 21:54:12 +03:00
parent f31540afdb
commit 11187487b4
174 changed files with 12383 additions and 6688 deletions
+165 -134
View File
@@ -1,29 +1,28 @@
import logging
import asyncio
from datetime import datetime, timezone
from typing import Any, Callable, Dict, Optional
from aiogram import Bot
from aiogram.exceptions import TelegramBadRequest
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from aiogram.utils.text_decorations import html_decoration as hd
from aiogram.exceptions import TelegramBadRequest
from datetime import datetime, timezone
from typing import Optional, Union, Dict, Any, Callable
from config.settings import Settings
from sqlalchemy.orm import sessionmaker
from bot.middlewares.i18n import JsonI18n
from bot.utils.message_queue import get_queue_manager
from bot.utils.text_sanitizer import (
display_name_or_fallback,
username_for_display,
)
from bot.utils.telegram_markup import (
is_profile_link_error,
remove_profile_link_buttons,
)
from bot.utils.text_sanitizer import (
display_name_or_fallback,
username_for_display,
)
from config.settings import Settings
class NotificationService:
"""Enhanced notification service for sending messages to admins and log channels"""
def __init__(self, bot: Bot, settings: Settings, i18n: Optional[JsonI18n] = None):
self.bot = bot
self.settings = settings
@@ -53,23 +52,27 @@ class NotificationService:
"""
buttons = []
if user_id and user_id > 0:
buttons.append([
InlineKeyboardButton(
text=translate("log_open_profile_link"),
url=f"tg://user?id={user_id}",
)
])
buttons.append(
[
InlineKeyboardButton(
text=translate("log_open_profile_link"),
url=f"tg://user?id={user_id}",
)
]
)
if referrer_id and referrer_id > 0:
buttons.append([
InlineKeyboardButton(
text=translate("log_open_referrer_profile_button"),
url=f"tg://user?id={referrer_id}",
)
])
buttons.append(
[
InlineKeyboardButton(
text=translate("log_open_referrer_profile_button"),
url=f"tg://user?id={referrer_id}",
)
]
)
return InlineKeyboardMarkup(inline_keyboard=buttons) if buttons else None
async def _send_to_log_channel(
self,
message: str,
@@ -79,7 +82,7 @@ class NotificationService:
"""Send message to configured log channel/group using message queue"""
if not self.settings.LOG_CHAT_ID:
return
queue_manager = get_queue_manager()
if not queue_manager:
logging.warning("Message queue manager not available, falling back to direct send")
@@ -121,36 +124,36 @@ class NotificationService:
f"Failed to send notification to log channel {self.settings.LOG_CHAT_ID}: {exc}"
)
except Exception:
logging.exception("Failed to send notification to log channel %s.", self.settings.LOG_CHAT_ID)
logging.exception(
"Failed to send notification to log channel %s.", self.settings.LOG_CHAT_ID
)
return
try:
# Use thread_id if provided, otherwise use from settings
final_thread_id = thread_id or self.settings.LOG_THREAD_ID
kwargs = {
"text": message,
"parse_mode": "HTML",
"disable_web_page_preview": True
}
kwargs = {"text": message, "parse_mode": "HTML", "disable_web_page_preview": True}
if reply_markup:
kwargs["reply_markup"] = reply_markup
# Add thread ID for supergroups if specified
if final_thread_id:
kwargs["message_thread_id"] = final_thread_id
# Queue message for sending (groups are rate limited to 15/minute)
await queue_manager.send_message(self.settings.LOG_CHAT_ID, **kwargs)
except Exception:
logging.exception("Failed to queue notification to log channel %s.", self.settings.LOG_CHAT_ID)
logging.exception(
"Failed to queue notification to log channel %s.", self.settings.LOG_CHAT_ID
)
async def _send_to_admins(self, message: str):
"""Send message to all admin users using message queue"""
if not self.settings.ADMIN_IDS:
return
queue_manager = get_queue_manager()
if not queue_manager:
logging.warning("Message queue manager not available, falling back to direct send")
@@ -160,39 +163,40 @@ class NotificationService:
chat_id=admin_id,
text=message,
parse_mode="HTML",
disable_web_page_preview=True
disable_web_page_preview=True,
)
except Exception:
logging.exception("Failed to send notification to admin %s.", admin_id)
return
for admin_id in self.settings.ADMIN_IDS:
try:
await queue_manager.send_message(
chat_id=admin_id,
text=message,
parse_mode="HTML",
disable_web_page_preview=True
chat_id=admin_id, text=message, parse_mode="HTML", disable_web_page_preview=True
)
except Exception:
logging.exception("Failed to queue notification to admin %s.", admin_id)
async def notify_new_user_registration(self, user_id: int, username: Optional[str] = None,
first_name: Optional[str] = None,
referred_by_id: Optional[int] = None):
async def notify_new_user_registration(
self,
user_id: int,
username: Optional[str] = None,
first_name: Optional[str] = None,
referred_by_id: Optional[int] = None,
):
"""Send notification about new user registration"""
if not self.settings.LOG_NEW_USERS:
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
user_display = self._format_user_display(
user_id=user_id,
username=username,
first_name=first_name,
)
referral_text = ""
if referred_by_id:
referrer_link = hd.link(str(referred_by_id), f"tg://user?id={referred_by_id}")
@@ -200,13 +204,13 @@ class NotificationService:
"log_referral_suffix",
referrer_link=referrer_link,
)
message = _(
"log_new_user_registration",
user_id=user_id,
user_display=user_display,
referral_text=referral_text,
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
# Send to log channel
@@ -246,12 +250,16 @@ class NotificationService:
# so we only attach the referrer button when a real referrer is present.
reply_markup: Optional[InlineKeyboardMarkup] = None
if referred_by_id and referred_by_id > 0:
reply_markup = InlineKeyboardMarkup(inline_keyboard=[[
InlineKeyboardButton(
text=_("log_open_referrer_profile_button"),
url=f"tg://user?id={referred_by_id}",
)
]])
reply_markup = InlineKeyboardMarkup(
inline_keyboard=[
[
InlineKeyboardButton(
text=_("log_open_referrer_profile_button"),
url=f"tg://user?id={referred_by_id}",
)
]
]
)
await self._send_to_log_channel(message, reply_markup=reply_markup)
@@ -342,25 +350,31 @@ class NotificationService:
except Exception:
return str(tariff_key)
async def notify_payment_received(self, user_id: int, amount: float, currency: str,
months: int, payment_provider: str,
username: Optional[str] = None,
traffic_gb: Optional[float] = None,
*,
traffic_is_premium: bool = False,
tariff_key: Optional[str] = None):
async def notify_payment_received(
self,
user_id: int,
amount: float,
currency: str,
months: int,
payment_provider: str,
username: Optional[str] = None,
traffic_gb: Optional[float] = None,
*,
traffic_is_premium: bool = False,
tariff_key: Optional[str] = None,
):
"""Send notification about successful payment"""
if not self.settings.LOG_PAYMENTS:
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
user_display = self._format_user_display(
user_id=user_id,
username=username,
)
provider_emoji = {
"yookassa": "💳",
"freekassa": "💳",
@@ -373,14 +387,16 @@ class NotificationService:
if traffic_gb is not None:
traffic_label = self._format_traffic_gb_admin(float(traffic_gb))
traffic_kind = _(
"log_payment_traffic_kind_premium" if traffic_is_premium else "log_payment_traffic_kind_regular",
"log_payment_traffic_kind_premium"
if traffic_is_premium
else "log_payment_traffic_kind_regular",
)
traffic_summary = _(
"log_payment_traffic_purchase_line", gb=traffic_label, kind=traffic_kind
)
traffic_summary = _("log_payment_traffic_purchase_line", gb=traffic_label, kind=traffic_kind)
tariff_name = self._tariff_display_for_log(tariff_key)
tariff_line = (
_("log_payment_tariff_line", name=hd.quote(tariff_name))
if tariff_name
else ""
_("log_payment_tariff_line", name=hd.quote(tariff_name)) if tariff_name else ""
)
message = _(
"log_payment_received_traffic",
@@ -391,7 +407,7 @@ class NotificationService:
traffic_summary=traffic_summary,
tariff_line=tariff_line,
payment_provider=payment_provider,
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
else:
message = _(
@@ -402,81 +418,86 @@ class NotificationService:
currency=currency,
months=months,
payment_provider=payment_provider,
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
# Send to log channel
profile_keyboard = self._build_profile_keyboard(_, user_id)
await self._send_to_log_channel(message, reply_markup=profile_keyboard)
async def notify_promo_activation(self, user_id: int, promo_code: str, bonus_days: int,
username: Optional[str] = None):
async def notify_promo_activation(
self, user_id: int, promo_code: str, bonus_days: int, username: Optional[str] = None
):
"""Send notification about promo code activation"""
if not self.settings.LOG_PROMO_ACTIVATIONS:
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
user_display = self._format_user_display(
user_id=user_id,
username=username,
)
message = _(
"log_promo_activation",
user_display=user_display,
promo_code=promo_code,
bonus_days=bonus_days,
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
# Send to log channel
profile_keyboard = self._build_profile_keyboard(_, user_id)
await self._send_to_log_channel(message, reply_markup=profile_keyboard)
async def notify_trial_activation(self, user_id: int, end_date: datetime,
username: Optional[str] = None):
"""Send notification about trial activation"""
if not self.settings.LOG_TRIAL_ACTIVATIONS:
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
user_display = self._format_user_display(
user_id=user_id,
username=username,
)
message = _(
"log_trial_activation",
user_display=user_display,
end_date=end_date.strftime("%Y-%m-%d %H:%M"),
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")
)
# Send to log channel
profile_keyboard = self._build_profile_keyboard(_, user_id)
await self._send_to_log_channel(message, reply_markup=profile_keyboard)
async def notify_panel_sync(self, status: str, details: str,
users_processed: int, subs_synced: int,
username: Optional[str] = None):
"""Send notification about panel synchronization"""
if not getattr(self.settings, 'LOG_PANEL_SYNC', True):
async def notify_trial_activation(
self, user_id: int, end_date: datetime, username: Optional[str] = None
):
"""Send notification about trial activation"""
if not self.settings.LOG_TRIAL_ACTIVATIONS:
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
user_display = self._format_user_display(
user_id=user_id,
username=username,
)
message = _(
"log_trial_activation",
user_display=user_display,
end_date=end_date.strftime("%Y-%m-%d %H:%M"),
timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S"),
)
# Send to log channel
profile_keyboard = self._build_profile_keyboard(_, user_id)
await self._send_to_log_channel(message, reply_markup=profile_keyboard)
async def notify_panel_sync(
self,
status: str,
details: str,
users_processed: int,
subs_synced: int,
username: Optional[str] = None,
):
"""Send notification about panel synchronization"""
if not getattr(self.settings, "LOG_PANEL_SYNC", True):
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
# Status emoji based on sync result
status_emoji = {
"completed": "",
"completed_with_errors": "⚠️",
"failed": ""
}.get(status, "🔄")
status_emoji = {"completed": "", "completed_with_errors": "⚠️", "failed": ""}.get(
status, "🔄"
)
message = _(
"log_panel_sync",
status_emoji=status_emoji,
@@ -484,22 +505,25 @@ class NotificationService:
users_processed=users_processed,
subs_synced=subs_synced,
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z"),
details=details
details=details,
)
# Send to log channel
await self._send_to_log_channel(message)
async def notify_suspicious_promo_attempt(
self, user_id: int, suspicious_input: str,
username: Optional[str] = None, first_name: Optional[str] = None):
self,
user_id: int,
suspicious_input: str,
username: Optional[str] = None,
first_name: Optional[str] = None,
):
"""Send notification about a suspicious promo code attempt."""
if not self.settings.LOG_SUSPICIOUS_ACTIVITY:
return
admin_lang = self.settings.DEFAULT_LANGUAGE
_ = lambda k, **kw: self.i18n.gettext(
admin_lang, k, **kw) if self.i18n else k
_ = lambda k, **kw: self.i18n.gettext(admin_lang, k, **kw) if self.i18n else k
user_display = self._format_user_display(
user_id=user_id,
@@ -512,18 +536,25 @@ class NotificationService:
user_display=hd.quote(user_display),
user_id=user_id,
suspicious_input=hd.quote(suspicious_input),
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z"))
timestamp=datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S %Z"),
)
# Send to log channel
profile_keyboard = self._build_profile_keyboard(_, user_id)
await self._send_to_log_channel(message, reply_markup=profile_keyboard)
async def send_custom_notification(self, message: str, to_admins: bool = False,
to_log_channel: bool = True, thread_id: Optional[int] = None):
async def send_custom_notification(
self,
message: str,
to_admins: bool = False,
to_log_channel: bool = True,
thread_id: Optional[int] = None,
):
"""Send custom notification message"""
if to_log_channel:
await self._send_to_log_channel(message, thread_id)
if to_admins:
await self._send_to_admins(message)
# Removed legacy helper functions that duplicated NotificationService API