refactor: centralize Bot instantiation via create_bot() factory

Replace all ~45 direct Bot() calls across the codebase with a centralized
create_bot() factory function that automatically configures SOCKS5 proxy
session when PROXY_URL is set. This ensures proxy support applies uniformly
to all Telegram API traffic.

Key changes:
- Add app/bot_factory.py with create_bot() factory
- Replace direct Bot() instantiation in 33 files
- Fix session leaks in cloudpayments.py and auth.py (async with)
- Replace 2 direct httpx calls to api.telegram.org with
  bot.create_invoice_link() (balance.py, wheel.py)
- Remove now-unused imports (Bot, DefaultBotProperties, ParseMode, httpx)
This commit is contained in:
Fringg
2026-03-21 02:49:37 +03:00
parent 82b6a8bf70
commit 0a53b85b8a
34 changed files with 180 additions and 314 deletions
+3 -8
View File
@@ -96,23 +96,18 @@ async def setup_bot() -> tuple[Bot, Dispatcher]:
except Exception as e: except Exception as e:
logger.warning('Кеш не инициализирован', error=e) logger.warning('Кеш не инициализирован', error=e)
from aiogram.client.default import DefaultBotProperties from app.bot_factory import create_bot
from aiogram.enums import ParseMode
bot = create_bot()
proxy_url = settings.get_proxy_url() proxy_url = settings.get_proxy_url()
session = None
if proxy_url: if proxy_url:
from urllib.parse import urlparse from urllib.parse import urlparse
from aiogram.client.session.aiohttp import AiohttpSession
session = AiohttpSession(proxy=proxy_url)
parsed = urlparse(proxy_url) parsed = urlparse(proxy_url)
masked = f'{parsed.scheme}://***@{parsed.hostname}:{parsed.port}' if parsed.username else proxy_url masked = f'{parsed.scheme}://***@{parsed.hostname}:{parsed.port}' if parsed.username else proxy_url
logger.info('Proxy configured', proxy_url=masked) logger.info('Proxy configured', proxy_url=masked)
bot = Bot(token=settings.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML), session=session)
maintenance_service.set_bot(bot) maintenance_service.set_bot(bot)
logger.info('Бот установлен в maintenance_service') logger.info('Бот установлен в maintenance_service')
+20
View File
@@ -0,0 +1,20 @@
"""Factory for creating Bot instances with proxy support."""
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from app.config import settings
def create_bot(token: str | None = None, **kwargs) -> Bot:
"""Create a Bot instance with SOCKS5 proxy session if PROXY_URL is configured."""
proxy_url = settings.get_proxy_url()
session = None
if proxy_url:
from aiogram.client.session.aiohttp import AiohttpSession
session = AiohttpSession(proxy=proxy_url)
kwargs.setdefault('default', DefaultBotProperties(parse_mode=ParseMode.HTML))
return Bot(token=token or settings.BOT_TOKEN, session=session, **kwargs)
+4 -6
View File
@@ -227,8 +227,7 @@ async def approve_application(
# Notify user about approval # Notify user about approval
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.services.notification_delivery_service import notification_delivery_service from app.services.notification_delivery_service import notification_delivery_service
@@ -240,7 +239,7 @@ async def approve_application(
tg_message = ( tg_message = (
f'✅ Ваша заявка на партнёрство одобрена!\nКомиссия: {request.commission_percent}%{comment_text}' f'✅ Ваша заявка на партнёрство одобрена!\nКомиссия: {request.commission_percent}%{comment_text}'
) )
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
await notification_delivery_service.notify_partner_approved( await notification_delivery_service.notify_partner_approved(
user=user, user=user,
@@ -280,8 +279,7 @@ async def reject_application(
# Notify user about rejection # Notify user about rejection
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.services.notification_delivery_service import notification_delivery_service from app.services.notification_delivery_service import notification_delivery_service
@@ -291,7 +289,7 @@ async def reject_application(
if user: if user:
comment_text = f'\nПричина: {request.comment}' if request.comment else '' comment_text = f'\nПричина: {request.comment}' if request.comment else ''
tg_message = f'❌ Ваша заявка на партнёрство отклонена.{comment_text}' tg_message = f'❌ Ваша заявка на партнёрство отклонена.{comment_text}'
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
await notification_delivery_service.notify_partner_rejected( await notification_delivery_service.notify_partner_rejected(
user=user, user=user,
+2 -5
View File
@@ -4,14 +4,11 @@ import math
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.bot_factory import create_bot
from app.database.models import PaymentMethod, User from app.database.models import PaymentMethod, User
from app.services.payment_search_service import ( from app.services.payment_search_service import (
MAX_ALL_TIME_DAYS, MAX_ALL_TIME_DAYS,
@@ -550,7 +547,7 @@ async def check_payment_status(
old_is_paid = record.is_paid old_is_paid = record.is_paid
# Run manual check # Run manual check
bot = Bot(token=settings.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) bot = create_bot()
try: try:
payment_service = PaymentService(bot=bot) payment_service = PaymentService(bot=bot)
updated = await run_manual_check(db, payment_method, payment_id, payment_service) updated = await run_manual_check(db, payment_method, payment_id, payment_service)
+2 -7
View File
@@ -5,13 +5,11 @@ from datetime import UTC, datetime
import structlog import structlog
from aiogram import Bot from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select, update from sqlalchemy import func, select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.bot_factory import create_bot
from app.database.models import PinnedMessage, User from app.database.models import PinnedMessage, User
from app.services.pinned_message_service import ( from app.services.pinned_message_service import (
broadcast_pinned_message, broadcast_pinned_message,
@@ -77,10 +75,7 @@ _cached_bot: Bot | None = None
def _get_bot() -> Bot: def _get_bot() -> Bot:
global _cached_bot global _cached_bot
if _cached_bot is None: if _cached_bot is None:
_cached_bot = Bot( _cached_bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
return _cached_bot return _cached_bot
+2 -7
View File
@@ -8,15 +8,13 @@ from typing import Any
import structlog import structlog
from aiogram import Bot from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError from aiogram.exceptions import TelegramBadRequest, TelegramForbiddenError
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from pydantic import BaseModel, Field from pydantic import BaseModel, Field
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.bot_factory import create_bot
from app.database.crud.discount_offer import ( from app.database.crud.discount_offer import (
count_discount_offers, count_discount_offers,
list_discount_offers, list_discount_offers,
@@ -369,10 +367,7 @@ async def list_offers(
def _get_bot() -> Bot: def _get_bot() -> Bot:
"""Create bot instance for sending notifications.""" """Create bot instance for sending notifications."""
return Bot( return create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
def _build_default_promo_message( def _build_default_promo_message(
+1 -3
View File
@@ -511,9 +511,7 @@ async def revoke_role(
from app.database.models import UserRole from app.database.models import UserRole
# Lock the assignment row (FOR UPDATE held until commit) # Lock the assignment row (FOR UPDATE held until commit)
result = await db.execute( result = await db.execute(sa.select(UserRole).where(UserRole.id == assignment_id).with_for_update())
sa.select(UserRole).where(UserRole.id == assignment_id).with_for_update()
)
user_role = result.scalar_one_or_none() user_role = result.scalar_one_or_none()
if not user_role: if not user_role:
raise HTTPException( raise HTTPException(
+2 -7
View File
@@ -479,14 +479,9 @@ async def reply_to_ticket(
# Try to notify user via Telegram # Try to notify user via Telegram
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
from app.handlers.admin.tickets import notify_user_about_ticket_reply from app.handlers.admin.tickets import notify_user_about_ticket_reply
+2 -8
View File
@@ -7,16 +7,13 @@ import time
from datetime import UTC, datetime, timedelta from datetime import UTC, datetime, timedelta
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.types import BufferedInputFile from aiogram.types import BufferedInputFile
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import and_, func, select from sqlalchemy import and_, func, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from app.config import settings from app.bot_factory import create_bot
from app.database.models import Subscription, Transaction, TransactionType, User from app.database.models import Subscription, Transaction, TransactionType, User
from app.services.remnawave_service import RemnaWaveService from app.services.remnawave_service import RemnaWaveService
@@ -680,10 +677,7 @@ async def export_traffic_csv(
filename = f'traffic_usage_{period_label}_{timestamp}.csv' filename = f'traffic_usage_{period_label}_{timestamp}.csv'
try: try:
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
async with bot: async with bot:
await bot.send_document( await bot.send_document(
chat_id=admin.telegram_id, chat_id=admin.telegram_id,
+4 -6
View File
@@ -199,8 +199,7 @@ async def approve_withdrawal(
# Notify user about approval # Notify user about approval
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.services.notification_delivery_service import notification_delivery_service from app.services.notification_delivery_service import notification_delivery_service
@@ -211,7 +210,7 @@ async def approve_withdrawal(
formatted_amount = settings.format_price(withdrawal.amount_kopeks) formatted_amount = settings.format_price(withdrawal.amount_kopeks)
comment_text = f'\n{request.comment}' if request.comment else '' comment_text = f'\n{request.comment}' if request.comment else ''
tg_message = f'✅ Ваш запрос на вывод {formatted_amount} одобрен.{comment_text}' tg_message = f'✅ Ваш запрос на вывод {formatted_amount} одобрен.{comment_text}'
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
await notification_delivery_service.notify_withdrawal_approved( await notification_delivery_service.notify_withdrawal_approved(
user=user, user=user,
@@ -251,8 +250,7 @@ async def reject_withdrawal(
# Notify user about rejection # Notify user about rejection
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.services.notification_delivery_service import notification_delivery_service from app.services.notification_delivery_service import notification_delivery_service
@@ -263,7 +261,7 @@ async def reject_withdrawal(
formatted_amount = settings.format_price(withdrawal.amount_kopeks) formatted_amount = settings.format_price(withdrawal.amount_kopeks)
comment_text = f'\nПричина: {request.comment}' if request.comment else '' comment_text = f'\nПричина: {request.comment}' if request.comment else ''
tg_message = f'❌ Ваш запрос на вывод {formatted_amount} отклонён.{comment_text}' tg_message = f'❌ Ваш запрос на вывод {formatted_amount} отклонён.{comment_text}'
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
await notification_delivery_service.notify_withdrawal_rejected( await notification_delivery_service.notify_withdrawal_rejected(
user=user, user=user,
+10 -15
View File
@@ -196,12 +196,10 @@ async def _process_campaign_bonus(
user.referred_by_id = campaign.partner_user_id user.referred_by_id = campaign.partner_user_id
await db.flush() await db.flush()
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
bot = Bot(token=settings.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) async with create_bot() as bot:
await process_referral_registration(db, user.id, campaign.partner_user_id, bot=bot) await process_referral_registration(db, user.id, campaign.partner_user_id, bot=bot)
logger.info( logger.info(
'Referral set from campaign partner', 'Referral set from campaign partner',
user_id=user.id, user_id=user.id,
@@ -255,12 +253,11 @@ async def _process_referral_code(
return return
user.referred_by_id = referrer.id user.referred_by_id = referrer.id
await db.flush() await db.flush()
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
bot = Bot(token=settings.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) from app.bot_factory import create_bot
await process_referral_registration(db, user.id, referrer.id, bot=bot)
async with create_bot() as bot:
await process_referral_registration(db, user.id, referrer.id, bot=bot)
logger.info('Referral applied from code', user_id=user.id, referrer_id=referrer.id, referral_code=referral_code) logger.info('Referral applied from code', user_id=user.id, referrer_id=referrer.id, referral_code=referral_code)
except Exception as e: except Exception as e:
logger.error('Failed to process referral code', error=e, referral_code=referral_code) logger.error('Failed to process referral code', error=e, referral_code=referral_code)
@@ -937,12 +934,10 @@ async def register_email_standalone(
# Обработать реферальную регистрацию (если есть реферер) # Обработать реферальную регистрацию (если есть реферер)
if referrer: if referrer:
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
bot = Bot(token=settings.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) async with create_bot() as bot:
await process_referral_registration(db, user.id, referrer.id, bot=bot) await process_referral_registration(db, user.id, referrer.id, bot=bot)
logger.info( logger.info(
'Processed referral registration: user_id=, referrer_id', user_id=user.id, referrer_id=referrer.id 'Processed referral registration: user_id=, referrer_id', user_id=user.id, referrer_id=referrer.id
) )
+25 -42
View File
@@ -4,15 +4,12 @@ import math
import time import time
from decimal import ROUND_HALF_UP, Decimal, InvalidOperation from decimal import ROUND_HALF_UP, Decimal, InvalidOperation
import httpx
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import desc, func, select from sqlalchemy import desc, func, select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.database.crud.saved_payment_method import ( from app.database.crud.saved_payment_method import (
deactivate_payment_method, deactivate_payment_method,
@@ -272,50 +269,36 @@ async def create_stars_invoice(
# Create invoice through Telegram Bot API # Create invoice through Telegram Bot API
try: try:
bot_token = settings.BOT_TOKEN from aiogram.types import LabeledPrice
api_url = f'https://api.telegram.org/bot{bot_token}/createInvoiceLink'
async with httpx.AsyncClient() as client: async with create_bot() as bot:
response = await client.post( invoice_url = await bot.create_invoice_link(
api_url, title='Пополнение баланса VPN',
json={ description=f'Пополнение баланса на {normalized_kopeks / 100:.2f} ₽ ({stars_amount} ⭐)',
'title': 'Пополнение баланса VPN', payload=payload,
'description': f'Пополнение баланса на {normalized_kopeks / 100:.2f} ₽ ({stars_amount} ⭐)', provider_token='',
'payload': payload, currency='XTR',
'provider_token': '', # Empty for Stars prices=[LabeledPrice(label='Пополнение баланса', amount=stars_amount)],
'currency': 'XTR',
'prices': [{'label': 'Пополнение баланса', 'amount': stars_amount}],
},
) )
result = response.json() logger.info(
'Created Stars invoice for balance top-up: user=, amount= kopeks, stars',
user_id=user.id,
amount_kopeks=request.amount_kopeks,
stars_amount=stars_amount,
)
if not result.get('ok'): return StarsInvoiceResponse(
logger.error('Telegram API error', result=result) invoice_url=invoice_url,
raise HTTPException( stars_amount=stars_amount,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, amount_kopeks=normalized_kopeks,
detail='Failed to create Stars invoice', )
)
invoice_url = result['result'] except Exception as e:
logger.info( logger.error('Error creating Stars invoice', error=e)
'Created Stars invoice for balance top-up: user=, amount= kopeks, stars',
user_id=user.id,
amount_kopeks=request.amount_kopeks,
stars_amount=stars_amount,
)
return StarsInvoiceResponse(
invoice_url=invoice_url,
stars_amount=stars_amount,
amount_kopeks=normalized_kopeks,
)
except httpx.HTTPError as e:
logger.error('HTTP error creating Stars invoice', error=e)
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Failed to connect to Telegram API', detail='Failed to create Stars invoice',
) )
@@ -1202,7 +1185,7 @@ async def check_payment_status(
old_is_paid = record.is_paid old_is_paid = record.is_paid
# Run manual check # Run manual check
bot = Bot(token=settings.BOT_TOKEN, default=DefaultBotProperties(parse_mode=ParseMode.HTML)) bot = create_bot()
try: try:
payment_service = PaymentService(bot=bot) payment_service = PaymentService(bot=bot)
updated = await run_manual_check(db, payment_method, payment_id, payment_service) updated = await run_manual_check(db, payment_method, payment_id, payment_service)
+4 -4
View File
@@ -306,9 +306,9 @@ async def create_gift_purchase(
else: else:
# 2) Fall back to Bot API (works for public usernames the bot has seen) # 2) Fall back to Bot API (works for public usernames the bot has seen)
try: try:
from aiogram import Bot from app.bot_factory import create_bot
async with Bot(token=settings.BOT_TOKEN) as bot: async with create_bot() as bot:
chat = await asyncio.wait_for(bot.get_chat(chat_id=f'@{tg_username}'), timeout=5.0) chat = await asyncio.wait_for(bot.get_chat(chat_id=f'@{tg_username}'), timeout=5.0)
pre_resolved_telegram_id = chat.id pre_resolved_telegram_id = chat.id
except Exception: except Exception:
@@ -371,9 +371,9 @@ async def create_gift_purchase(
# Stars payments need a Bot instance to create invoice links # Stars payments need a Bot instance to create invoice links
bot = None bot = None
if body.payment_method == 'telegram_stars': if body.payment_method == 'telegram_stars':
from aiogram import Bot from app.bot_factory import create_bot
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
payment_service = PaymentService(bot=bot) payment_service = PaymentService(bot=bot)
payment_result = await payment_service.create_guest_payment( payment_result = await payment_service.create_guest_payment(
+3 -11
View File
@@ -3,13 +3,11 @@
import mimetypes import mimetypes
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.types import BufferedInputFile from aiogram.types import BufferedInputFile
from fastapi import APIRouter, Depends, File, Form, HTTPException, Request, Response, UploadFile, status from fastapi import APIRouter, Depends, File, Form, HTTPException, Request, Response, UploadFile, status
from pydantic import BaseModel from pydantic import BaseModel
from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.database.models import User from app.database.models import User
@@ -98,10 +96,7 @@ async def upload_media(
target_chat_id = _resolve_target_chat_id() target_chat_id = _resolve_target_chat_id()
upload = BufferedInputFile(file_bytes, filename=file.filename or 'upload') upload = BufferedInputFile(file_bytes, filename=file.filename or 'upload')
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
if media_type_normalized == 'photo': if media_type_normalized == 'photo':
@@ -158,10 +153,7 @@ async def download_media(
Download media file by file_id. Download media file by file_id.
Used to display images/documents in ticket messages. Used to display images/documents in ticket messages.
""" """
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
file = await bot.get_file(file_id) file = await bot.get_file(file_id)
+2 -3
View File
@@ -178,12 +178,11 @@ async def apply_for_partner(
# Уведомляем админов о новой заявке # Уведомляем админов о новой заявке
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
await notification_service.send_partner_application_notification( await notification_service.send_partner_application_notification(
+14 -21
View File
@@ -815,12 +815,11 @@ async def purchase_traffic(
# Отправляем уведомление админам # Отправляем уведомление админам
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
old_traffic = subscription.traffic_limit_gb - request.gb old_traffic = subscription.traffic_limit_gb - request.gb
@@ -1043,12 +1042,11 @@ async def purchase_devices_legacy(
# Отправляем уведомление админам # Отправляем уведомление админам
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
await notification_service.send_subscription_update_notification( await notification_service.send_subscription_update_notification(
@@ -1344,12 +1342,11 @@ async def activate_trial(
# Send admin notification about trial activation # Send admin notification about trial activation
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
charged_amount = settings.TRIAL_ACTIVATION_PRICE if requires_payment else None charged_amount = settings.TRIAL_ACTIVATION_PRICE if requires_payment else None
@@ -1763,12 +1760,11 @@ async def submit_purchase(
# Отправляем уведомление админам о покупке подписки # Отправляем уведомление админам о покупке подписки
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
is_new_subscription = result.get('was_trial_conversion') or not context.subscription is_new_subscription = result.get('was_trial_conversion') or not context.subscription
@@ -2161,12 +2157,11 @@ async def purchase_tariff(
# Отправляем уведомление админам о покупке/продлении тарифа # Отправляем уведомление админам о покупке/продлении тарифа
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
# Определяем тип покупки: новая подписка или продление # Определяем тип покупки: новая подписка или продление
@@ -2418,12 +2413,11 @@ async def purchase_devices(
# Отправляем уведомление админам # Отправляем уведомление админам
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
await notification_service.send_subscription_update_notification( await notification_service.send_subscription_update_notification(
@@ -4204,12 +4198,11 @@ async def switch_tariff(
# Отправляем уведомление админам о смене тарифа # Отправляем уведомление админам о смене тарифа
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
await notification_service.send_subscription_purchase_notification( await notification_service.send_subscription_purchase_notification(
+19 -35
View File
@@ -5,7 +5,6 @@ API роуты колеса удачи для пользователей.
import math import math
import time import time
import httpx
import structlog import structlog
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import BaseModel from pydantic import BaseModel
@@ -21,7 +20,6 @@ from app.cabinet.schemas.wheel import (
WheelConfigResponse, WheelConfigResponse,
WheelPrizeDisplay, WheelPrizeDisplay,
) )
from app.config import settings
from app.database.crud.wheel import ( from app.database.crud.wheel import (
get_or_create_wheel_config, get_or_create_wheel_config,
get_user_spin_history, get_user_spin_history,
@@ -251,44 +249,30 @@ async def create_stars_invoice(
# Создаем invoice через Telegram Bot API # Создаем invoice через Telegram Bot API
try: try:
bot_token = settings.BOT_TOKEN from aiogram.types import LabeledPrice
api_url = f'https://api.telegram.org/bot{bot_token}/createInvoiceLink'
async with httpx.AsyncClient() as client: from app.bot_factory import create_bot
response = await client.post(
api_url, async with create_bot() as bot:
json={ invoice_url = await bot.create_invoice_link(
'title': 'Колесо удачи', title='Колесо удачи',
'description': f'Спин колеса удачи ({stars_amount} ⭐)', description=f'Спин колеса удачи ({stars_amount} ⭐)',
'payload': payload, payload=payload,
'provider_token': '', # Пустой для Stars provider_token='',
'currency': 'XTR', currency='XTR',
'prices': [{'label': 'Спин колеса', 'amount': stars_amount}], prices=[LabeledPrice(label='Спин колеса', amount=stars_amount)],
},
) )
result = response.json() logger.info('Created Stars invoice for wheel spin: user=, stars', user_id=user.id, stars_amount=stars_amount)
if not result.get('ok'): return StarsInvoiceResponse(
logger.error('Telegram API error', result=result) invoice_url=invoice_url,
raise HTTPException( stars_amount=stars_amount,
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, )
detail='Ошибка создания инвойса',
)
invoice_url = result['result'] except Exception as e:
logger.info( logger.error('Error creating invoice', error=e)
'Created Stars invoice for wheel spin: user=, stars', user_id=user.id, stars_amount=stars_amount
)
return StarsInvoiceResponse(
invoice_url=invoice_url,
stars_amount=stars_amount,
)
except httpx.HTTPError as e:
logger.error('HTTP error creating invoice', error=e)
raise HTTPException( raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail='Ошибка соединения с Telegram', detail='Ошибка создания инвойса',
) )
+2 -3
View File
@@ -70,12 +70,11 @@ async def create_withdrawal(
# Уведомляем админов о запросе на вывод # Уведомляем админов о запросе на вывод
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN: if getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) and settings.BOT_TOKEN:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
await notification_service.send_withdrawal_request_notification( await notification_service.send_withdrawal_request_notification(
+1 -3
View File
@@ -67,9 +67,7 @@ async def get_cryptobot_payment_by_id(db: AsyncSession, payment_id: int) -> Cryp
return result.scalar_one_or_none() return result.scalar_one_or_none()
async def get_cryptobot_payment_by_invoice_id_for_update( async def get_cryptobot_payment_by_invoice_id_for_update(db: AsyncSession, invoice_id: str) -> CryptoBotPayment | None:
db: AsyncSession, invoice_id: str
) -> CryptoBotPayment | None:
result = await db.execute( result = await db.execute(
select(CryptoBotPayment) select(CryptoBotPayment)
.options(selectinload(CryptoBotPayment.user)) .options(selectinload(CryptoBotPayment.user))
+1 -4
View File
@@ -540,10 +540,7 @@ async def create_invite_message(callback: types.CallbackQuery, db_user: User):
# Compact share text for switch_inline_query (256-char limit) # Compact share text for switch_inline_query (256-char limit)
share_text = invite_text share_text = invite_text
if len(share_text) > 256: if len(share_text) > 256:
share_text = ( share_text = texts.t('REFERRAL_INVITE_TITLE', '🎉 Присоединяйся к VPN сервису!') + f'\n\n👇 {bot_referral_link}'
texts.t('REFERRAL_INVITE_TITLE', '🎉 Присоединяйся к VPN сервису!')
+ f'\n\n👇 {bot_referral_link}'
)
if cabinet_referral_link and len(share_text) + len(cabinet_referral_link) + 5 <= 256: if cabinet_referral_link and len(share_text) + len(cabinet_referral_link) + 5 <= 256:
share_text += f'\n🌐 {cabinet_referral_link}' share_text += f'\n🌐 {cabinet_referral_link}'
share_text = share_text[:256] share_text = share_text[:256]
+11 -18
View File
@@ -46,11 +46,10 @@ async def _send_admin_notification(
if not getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) or not settings.BOT_TOKEN: if not getattr(settings, 'ADMIN_NOTIFICATIONS_ENABLED', False) or not settings.BOT_TOKEN:
return return
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService from app.services.admin_notification_service import AdminNotificationService
async with Bot(token=settings.BOT_TOKEN) as bot: async with create_bot() as bot:
service = AdminNotificationService(bot) service = AdminNotificationService(bot)
await service.send_guest_purchase_notification( await service.send_guest_purchase_notification(
purchase, purchase,
@@ -546,9 +545,9 @@ async def _find_or_create_user(
resolved_telegram_id: int | None = pre_resolved_telegram_id resolved_telegram_id: int | None = pre_resolved_telegram_id
if resolved_telegram_id is None: if resolved_telegram_id is None:
try: try:
from aiogram import Bot from app.bot_factory import create_bot
async with Bot(token=settings.BOT_TOKEN) as bot: async with create_bot() as bot:
chat = await asyncio.wait_for( chat = await asyncio.wait_for(
bot.get_chat(chat_id=f'@{username}'), bot.get_chat(chat_id=f'@{username}'),
timeout=5.0, timeout=5.0,
@@ -656,11 +655,10 @@ async def _send_telegram_gift_notification(
try: try:
import html as html_mod import html as html_mod
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup from aiogram.types import InlineKeyboardButton, InlineKeyboardMarkup
from app.bot_factory import create_bot
gift_from = '' gift_from = ''
if purchase.contact_value: if purchase.contact_value:
safe_name = html_mod.escape(purchase.contact_value) safe_name = html_mod.escape(purchase.contact_value)
@@ -691,10 +689,7 @@ async def _send_telegram_gift_notification(
] ]
) )
async with Bot( async with create_bot() as bot:
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
) as bot:
await bot.send_message( await bot.send_message(
chat_id=user.telegram_id, chat_id=user.telegram_id,
text=text, text=text,
@@ -1205,8 +1200,7 @@ async def _send_stuck_purchase_alert(data: dict, retry_count: int, phase: str) -
try: try:
import html as html_mod import html as html_mod
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService, NotificationCategory from app.services.admin_notification_service import AdminNotificationService, NotificationCategory
amount_rub = data['amount_kopeks'] / 100 amount_rub = data['amount_kopeks'] / 100
@@ -1225,7 +1219,7 @@ async def _send_stuck_purchase_alert(data: dict, retry_count: int, phase: str) -
f'Requires manual investigation.' f'Requires manual investigation.'
) )
async with Bot(token=settings.BOT_TOKEN) as bot: async with create_bot() as bot:
service = AdminNotificationService(bot) service = AdminNotificationService(bot)
await service.send_admin_notification(text, category=NotificationCategory.ERRORS) await service.send_admin_notification(text, category=NotificationCategory.ERRORS)
except Exception: except Exception:
@@ -1244,8 +1238,7 @@ async def _send_amount_mismatch_alert(
try: try:
import html as html_mod import html as html_mod
from aiogram import Bot from app.bot_factory import create_bot
from app.services.admin_notification_service import AdminNotificationService, NotificationCategory from app.services.admin_notification_service import AdminNotificationService, NotificationCategory
text = ( text = (
@@ -1260,7 +1253,7 @@ async def _send_amount_mismatch_alert(
f'Requires manual investigation.' f'Requires manual investigation.'
) )
async with Bot(token=settings.BOT_TOKEN) as bot: async with create_bot() as bot:
service = AdminNotificationService(bot) service = AdminNotificationService(bot)
await service.send_admin_notification(text, category=NotificationCategory.ERRORS) await service.send_admin_notification(text, category=NotificationCategory.ERRORS)
except Exception: except Exception:
+21 -35
View File
@@ -452,18 +452,10 @@ class CloudPaymentsPaymentMixin:
transaction: Any, transaction: Any,
) -> None: ) -> None:
"""Send success notification to user via Telegram.""" """Send success notification to user via Telegram."""
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from app.config import settings from app.bot_factory import create_bot
from app.localization.texts import get_texts from app.localization.texts import get_texts
bot = Bot(
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
# Skip email-only users (no telegram_id) # Skip email-only users (no telegram_id)
if not user.telegram_id: if not user.telegram_id:
logger.debug('Skipping CloudPayments notification for email-only user', user_id=user.id) logger.debug('Skipping CloudPayments notification for email-only user', user_id=user.id)
@@ -492,15 +484,16 @@ class CloudPaymentsPaymentMixin:
if referrer_info: if referrer_info:
message += f'\n\n{referrer_info}' message += f'\n\n{referrer_info}'
try: async with create_bot() as bot:
await bot.send_message( try:
chat_id=user.telegram_id, await bot.send_message(
text=message, chat_id=user.telegram_id,
parse_mode='HTML', text=message,
reply_markup=keyboard, parse_mode='HTML',
) reply_markup=keyboard,
except Exception as error: )
logger.warning('Не удалось отправить уведомление пользователю', telegram_id=user.telegram_id, error=error) except Exception as error:
logger.warning('Не удалось отправить уведомление пользователю', telegram_id=user.telegram_id, error=error)
async def _send_cloudpayments_fail_notification( async def _send_cloudpayments_fail_notification(
self, self,
@@ -508,27 +501,20 @@ class CloudPaymentsPaymentMixin:
message: str, message: str,
) -> None: ) -> None:
"""Send failure notification to user via Telegram.""" """Send failure notification to user via Telegram."""
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from app.config import settings from app.bot_factory import create_bot
bot = Bot(
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
text = f'❌ <b>Оплата не прошла</b>\n\n{message}' text = f'❌ <b>Оплата не прошла</b>\n\n{message}'
try: async with create_bot() as bot:
await bot.send_message( try:
chat_id=telegram_id, await bot.send_message(
text=text, chat_id=telegram_id,
parse_mode='HTML', text=text,
) parse_mode='HTML',
except Exception as error: )
logger.warning('Не удалось отправить уведомление пользователю', telegram_id=telegram_id, error=error) except Exception as error:
logger.warning('Не удалось отправить уведомление пользователю', telegram_id=telegram_id, error=error)
async def get_cloudpayments_payment_status( async def get_cloudpayments_payment_status(
self, self,
+2 -6
View File
@@ -509,14 +509,10 @@ class FreekassaPaymentMixin:
# Lock payment row before finalization to prevent concurrent double-processing # Lock payment row before finalization to prevent concurrent double-processing
locked = await freekassa_crud.get_freekassa_payment_by_id_for_update(db, payment.id) locked = await freekassa_crud.get_freekassa_payment_by_id_for_update(db, payment.id)
if not locked: if not locked:
logger.error( logger.error('Freekassa status check: не удалось заблокировать платёж', payment_id=payment.id)
'Freekassa status check: не удалось заблокировать платёж', payment_id=payment.id
)
elif locked.is_paid: elif locked.is_paid:
# Another concurrent handler already processed — skip # Another concurrent handler already processed — skip
logger.info( logger.info('Freekassa платеж уже оплачен после блокировки', order_id=locked.order_id)
'Freekassa платеж уже оплачен после блокировки', order_id=locked.order_id
)
payment = locked payment = locked
else: else:
payment = locked payment = locked
+2 -6
View File
@@ -488,14 +488,10 @@ class KassaAiPaymentMixin:
# Lock payment row before finalization to prevent concurrent double-processing # Lock payment row before finalization to prevent concurrent double-processing
locked = await kassa_ai_crud.get_kassa_ai_payment_by_id_for_update(db, payment.id) locked = await kassa_ai_crud.get_kassa_ai_payment_by_id_for_update(db, payment.id)
if not locked: if not locked:
logger.error( logger.error('KassaAI status check: не удалось заблокировать платёж', payment_id=payment.id)
'KassaAI status check: не удалось заблокировать платёж', payment_id=payment.id
)
elif locked.is_paid: elif locked.is_paid:
# Another concurrent handler already processed — skip # Another concurrent handler already processed — skip
logger.info( logger.info('KassaAI платеж уже оплачен после блокировки', order_id=locked.order_id)
'KassaAI платеж уже оплачен после блокировки', order_id=locked.order_id
)
payment = locked payment = locked
else: else:
payment = locked payment = locked
+1 -1
View File
@@ -264,7 +264,7 @@ class Pal24PaymentMixin:
payment.callback_payload = callback payment.callback_payload = callback
if payment_id is not None: if payment_id is not None:
payment.payment_id = payment_id payment.payment_id = payment_id
payment.payment_status = (callback.get('Status') or status) payment.payment_status = callback.get('Status') or status
payment.payment_method = ( payment.payment_method = (
callback.get('payment_method') callback.get('payment_method')
or callback.get('PaymentMethod') or callback.get('PaymentMethod')
+2 -3
View File
@@ -1,11 +1,10 @@
from datetime import UTC, datetime from datetime import UTC, datetime
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.database.crud.transaction import get_user_total_spent_kopeks from app.database.crud.transaction import get_user_total_spent_kopeks
from app.database.crud.user import lock_user_for_update from app.database.crud.user import lock_user_for_update
@@ -31,7 +30,7 @@ async def _notify_admins_about_auto_assignment(
logger.debug('BOT_TOKEN не настроен — пропускаем уведомление о промогруппе') logger.debug('BOT_TOKEN не настроен — пропускаем уведомление о промогруппе')
return return
bot = Bot(token=bot_token, default=DefaultBotProperties(parse_mode='HTML')) bot = create_bot(token=bot_token)
try: try:
notification_service = AdminNotificationService(bot) notification_service = AdminNotificationService(bot)
reason = ( reason = (
+1 -2
View File
@@ -251,8 +251,7 @@ async def _warn_if_no_superadmins(
) )
else: else:
logger.warning( logger.warning(
'No active superadmin RBAC roles in DB. ' 'No active superadmin RBAC roles in DB. Legacy config admins (ADMIN_IDS/ADMIN_EMAILS) still have access.',
'Legacy config admins (ADMIN_IDS/ADMIN_EMAILS) still have access.',
) )
+2 -1
View File
@@ -12,6 +12,7 @@ import structlog
from aiogram import Bot from aiogram import Bot
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.database.crud.subscription import ( from app.database.crud.subscription import (
add_subscription_servers, add_subscription_servers,
@@ -337,7 +338,7 @@ async def with_admin_notification_service(
bot: Bot | None = None bot: Bot | None = None
try: try:
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
service = AdminNotificationService(bot) service = AdminNotificationService(bot)
await handler(service) await handler(service)
except Exception as error: # pragma: no cover - defensive logging except Exception as error: # pragma: no cover - defensive logging
+3 -11
View File
@@ -4,9 +4,6 @@ import mimetypes
from typing import Any from typing import Any
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from aiogram.types import BufferedInputFile from aiogram.types import BufferedInputFile
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
@@ -20,6 +17,7 @@ from fastapi import (
status, status,
) )
from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from ..dependencies import require_api_token from ..dependencies import require_api_token
@@ -72,10 +70,7 @@ async def upload_media(
target_chat_id = _resolve_target_chat_id() target_chat_id = _resolve_target_chat_id()
upload = BufferedInputFile(file_bytes, filename=file.filename or 'upload') upload = BufferedInputFile(file_bytes, filename=file.filename or 'upload')
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
if media_type_normalized == 'photo': if media_type_normalized == 'photo':
@@ -121,10 +116,7 @@ async def download_media(
file_id: str, file_id: str,
_: Any = Security(require_api_token), _: Any = Security(require_api_token),
) -> Response: ) -> Response:
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
file = await bot.get_file(file_id) file = await bot.get_file(file_id)
+3 -3
View File
@@ -10,13 +10,13 @@ from typing import Any
from uuid import uuid4 from uuid import uuid4
import structlog import structlog
from aiogram import Bot
from fastapi import APIRouter, Depends, HTTPException, status from fastapi import APIRouter, Depends, HTTPException, status
from pydantic import ValidationError from pydantic import ValidationError
from sqlalchemy import select from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload from sqlalchemy.orm import selectinload
from app.bot_factory import create_bot
from app.config import settings from app.config import settings
from app.database.crud.discount_offer import ( from app.database.crud.discount_offer import (
get_latest_claimed_offer_for_user, get_latest_claimed_offer_for_user,
@@ -928,7 +928,7 @@ async def create_payment_link(
detail='Failed to prepare Stars payment', detail='Failed to prepare Stars payment',
) from exc ) from exc
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
invoice_payload = _build_balance_invoice_payload(user.id, amount_kopeks) invoice_payload = _build_balance_invoice_payload(user.id, amount_kopeks)
try: try:
payment_service = PaymentService(bot) payment_service = PaymentService(bot)
@@ -1399,7 +1399,7 @@ async def create_payment_link(
if not settings.BOT_TOKEN: if not settings.BOT_TOKEN:
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Bot token is not configured') raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, detail='Bot token is not configured')
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
tribute_service = TributeService(bot) tribute_service = TributeService(bot)
payment_url = await tribute_service.create_payment_link( payment_url = await tribute_service.create_payment_link(
+2 -7
View File
@@ -4,13 +4,11 @@ from datetime import UTC, datetime
from typing import Any, Optional from typing import Any, Optional
from aiogram import Bot from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import APIRouter, Depends, HTTPException, Query, status from fastapi import APIRouter, Depends, HTTPException, Query, status
from sqlalchemy import func, select, update from sqlalchemy import func, select, update
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.bot_factory import create_bot
from app.database.models import PinnedMessage from app.database.models import PinnedMessage
from app.services.pinned_message_service import ( from app.services.pinned_message_service import (
broadcast_pinned_message, broadcast_pinned_message,
@@ -52,10 +50,7 @@ def _serialize_pinned_message(msg: PinnedMessage) -> PinnedMessageResponse:
def _get_bot() -> Bot: def _get_bot() -> Bot:
"""Создать экземпляр бота для API операций.""" """Создать экземпляр бота для API операций."""
return Bot( return create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
@router.get('', response_model=PinnedMessageListResponse) @router.get('', response_model=PinnedMessageListResponse)
+2 -8
View File
@@ -2,9 +2,6 @@ from __future__ import annotations
from typing import Any from typing import Any
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import ( from fastapi import (
APIRouter, APIRouter,
Depends, Depends,
@@ -328,12 +325,9 @@ async def send_poll(
total=0, total=0,
) )
from app.config import settings from app.bot_factory import create_bot
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
result = await send_poll_to_users(bot, db, poll, users) result = await send_poll_to_users(bot, db, poll, users)
+3 -12
View File
@@ -4,13 +4,10 @@ from datetime import UTC, datetime
from typing import Any from typing import Any
import structlog import structlog
from aiogram import Bot
from aiogram.client.default import DefaultBotProperties
from aiogram.enums import ParseMode
from fastapi import APIRouter, Depends, HTTPException, Query, Request, Security, status from fastapi import APIRouter, Depends, HTTPException, Query, Request, Security, status
from sqlalchemy.ext.asyncio import AsyncSession from sqlalchemy.ext.asyncio import AsyncSession
from app.config import settings from app.bot_factory import create_bot
from app.database.crud.ticket import TicketCRUD, TicketMessageCRUD from app.database.crud.ticket import TicketCRUD, TicketMessageCRUD
from app.database.models import Ticket, TicketMessage, TicketStatus from app.database.models import Ticket, TicketMessage, TicketStatus
@@ -224,10 +221,7 @@ async def reply_to_ticket(
media_caption=payload.media_caption, media_caption=payload.media_caption,
) )
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
from app.handlers.admin.tickets import notify_user_about_ticket_reply from app.handlers.admin.tickets import notify_user_about_ticket_reply
@@ -286,10 +280,7 @@ async def get_ticket_message_media(
raise HTTPException(status.HTTP_404_NOT_FOUND, 'Media not found for this message') raise HTTPException(status.HTTP_404_NOT_FOUND, 'Media not found for this message')
media_url: str | None = None media_url: str | None = None
bot = Bot( bot = create_bot()
token=settings.BOT_TOKEN,
default=DefaultBotProperties(parse_mode=ParseMode.HTML),
)
try: try:
file = await bot.get_file(message.media_file_id) file = await bot.get_file(message.media_file_id)
if file.file_path: if file.file_path:
+2 -3
View File
@@ -964,11 +964,10 @@ async def _send_crash_notification_on_error(error: Exception) -> None:
return return
try: try:
from aiogram import Bot from app.bot_factory import create_bot
from app.services.startup_notification_service import send_crash_notification from app.services.startup_notification_service import send_crash_notification
bot = Bot(token=settings.BOT_TOKEN) bot = create_bot()
try: try:
traceback_str = traceback.format_exc() traceback_str = traceback.format_exc()
await send_crash_notification(bot, error, traceback_str) await send_crash_notification(bot, error, traceback_str)