Protect deliveries to blocked Telegram users
This commit is contained in:
@@ -2,7 +2,6 @@
|
||||
import logging
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
||||
from aiogram.exceptions import TelegramRetryAfter
|
||||
from aiogram.fsm.context import FSMContext
|
||||
from aiogram.fsm.state import State, StatesGroup
|
||||
from aiogram.filters import StateFilter, Command
|
||||
@@ -23,10 +22,10 @@ from src.core.chat_services import (
|
||||
from src.core.services import UserService
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.config import ADMIN_IDS
|
||||
from src.core.delivery import delivery_service
|
||||
from src.utils.account_utils import parse_accounts_from_message
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
MAX_RETRY_AFTER_SECONDS = 30
|
||||
|
||||
|
||||
class ChatStates(StatesGroup):
|
||||
@@ -488,19 +487,20 @@ async def _send_message_to_user(message: Message, user_telegram_id: int, retry:
|
||||
Отправить сообщение конкретному пользователю.
|
||||
Возвращает message_id при успехе или None при ошибке.
|
||||
"""
|
||||
try:
|
||||
sent_msg = await message.copy_to(user_telegram_id)
|
||||
return sent_msg.message_id
|
||||
except TelegramRetryAfter as e:
|
||||
if retry and e.retry_after <= MAX_RETRY_AFTER_SECONDS:
|
||||
logger.warning(f"FloodWait при отправке {user_telegram_id}: {e.retry_after}с")
|
||||
await asyncio.sleep(e.retry_after + 1)
|
||||
return await _send_message_to_user(message, user_telegram_id, retry=False)
|
||||
logger.warning(f"Пропускаем отправку {user_telegram_id}: FloodWait {e.retry_after}с")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось отправить сообщение {user_telegram_id}: {e}")
|
||||
return None
|
||||
result = await delivery_service.send_with_guard(
|
||||
user_telegram_id,
|
||||
lambda: message.copy_to(user_telegram_id),
|
||||
retry=retry,
|
||||
)
|
||||
if result.success:
|
||||
return result.telegram_message.message_id
|
||||
|
||||
logger.warning(
|
||||
"Не удалось отправить сообщение %s: %s",
|
||||
user_telegram_id,
|
||||
result.status,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
async def _send_message_to_user_with_sender(
|
||||
@@ -513,87 +513,79 @@ async def _send_message_to_user_with_sender(
|
||||
Отправить сообщение обычному пользователю с информацией об отправителе.
|
||||
Возвращает message_id при успехе или None при ошибке.
|
||||
"""
|
||||
try:
|
||||
# Формируем текст с информацией об отправителе
|
||||
header = f"📨 <b>{sender_info}:</b>\n\n"
|
||||
|
||||
# Формируем текст с информацией об отправителе
|
||||
header = f"📨 <b>{sender_info}:</b>\n\n"
|
||||
|
||||
async def send_message():
|
||||
if message.text:
|
||||
# Текстовое сообщение
|
||||
sent_msg = await message.bot.send_message(
|
||||
return await message.bot.send_message(
|
||||
user_telegram_id,
|
||||
header + message.text,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.photo:
|
||||
# Фото
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_photo(
|
||||
return await message.bot.send_photo(
|
||||
user_telegram_id,
|
||||
photo=message.photo[-1].file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.video:
|
||||
# Видео
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_video(
|
||||
return await message.bot.send_video(
|
||||
user_telegram_id,
|
||||
video=message.video.file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.document:
|
||||
# Документ
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_document(
|
||||
return await message.bot.send_document(
|
||||
user_telegram_id,
|
||||
document=message.document.file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.animation:
|
||||
# GIF
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_animation(
|
||||
return await message.bot.send_animation(
|
||||
user_telegram_id,
|
||||
animation=message.animation.file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.sticker:
|
||||
# Стикер - сначала отправляем заголовок, потом стикер
|
||||
await message.bot.send_message(user_telegram_id, header, parse_mode="HTML")
|
||||
sent_msg = await message.bot.send_sticker(user_telegram_id, sticker=message.sticker.file_id)
|
||||
return await message.bot.send_sticker(user_telegram_id, sticker=message.sticker.file_id)
|
||||
elif message.voice:
|
||||
# Голосовое сообщение
|
||||
sent_msg = await message.bot.send_voice(
|
||||
return await message.bot.send_voice(
|
||||
user_telegram_id,
|
||||
voice=message.voice.file_id,
|
||||
caption=header,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.video_note:
|
||||
# Видео-кружок
|
||||
await message.bot.send_message(user_telegram_id, header, parse_mode="HTML")
|
||||
sent_msg = await message.bot.send_video_note(user_telegram_id, video_note=message.video_note.file_id)
|
||||
else:
|
||||
# Неизвестный тип - просто копируем
|
||||
await message.bot.send_message(user_telegram_id, header, parse_mode="HTML")
|
||||
sent_msg = await message.copy_to(user_telegram_id)
|
||||
|
||||
return sent_msg.message_id
|
||||
except TelegramRetryAfter as e:
|
||||
if retry and e.retry_after <= MAX_RETRY_AFTER_SECONDS:
|
||||
logger.warning(f"FloodWait при отправке {user_telegram_id}: {e.retry_after}с")
|
||||
await asyncio.sleep(e.retry_after + 1)
|
||||
return await _send_message_to_user_with_sender(
|
||||
message, user_telegram_id, sender_info, retry=False
|
||||
)
|
||||
logger.warning(f"Пропускаем отправку {user_telegram_id}: FloodWait {e.retry_after}с")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось отправить сообщение {user_telegram_id}: {e}")
|
||||
return None
|
||||
return await message.bot.send_video_note(user_telegram_id, video_note=message.video_note.file_id)
|
||||
|
||||
await message.bot.send_message(user_telegram_id, header, parse_mode="HTML")
|
||||
return await message.copy_to(user_telegram_id)
|
||||
|
||||
result = await delivery_service.send_with_guard(
|
||||
user_telegram_id,
|
||||
send_message,
|
||||
retry=retry,
|
||||
)
|
||||
if result.success:
|
||||
return result.telegram_message.message_id
|
||||
|
||||
logger.warning(
|
||||
"Не удалось отправить сообщение %s: %s",
|
||||
user_telegram_id,
|
||||
result.status,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
async def _send_message_to_admin_with_sender(
|
||||
@@ -606,87 +598,79 @@ async def _send_message_to_admin_with_sender(
|
||||
Отправить сообщение админу с информацией об отправителе.
|
||||
Возвращает message_id при успехе или None при ошибке.
|
||||
"""
|
||||
try:
|
||||
# Формируем текст с информацией об отправителе
|
||||
header = f"📨 <b>Сообщение от {sender_info}:</b>\n\n"
|
||||
|
||||
# Формируем текст с информацией об отправителе
|
||||
header = f"📨 <b>Сообщение от {sender_info}:</b>\n\n"
|
||||
|
||||
async def send_message():
|
||||
if message.text:
|
||||
# Текстовое сообщение
|
||||
sent_msg = await message.bot.send_message(
|
||||
return await message.bot.send_message(
|
||||
admin_telegram_id,
|
||||
header + message.text,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.photo:
|
||||
# Фото
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_photo(
|
||||
return await message.bot.send_photo(
|
||||
admin_telegram_id,
|
||||
photo=message.photo[-1].file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.video:
|
||||
# Видео
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_video(
|
||||
return await message.bot.send_video(
|
||||
admin_telegram_id,
|
||||
video=message.video.file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.document:
|
||||
# Документ
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_document(
|
||||
return await message.bot.send_document(
|
||||
admin_telegram_id,
|
||||
document=message.document.file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.animation:
|
||||
# GIF
|
||||
caption = header + (message.caption or "")
|
||||
sent_msg = await message.bot.send_animation(
|
||||
return await message.bot.send_animation(
|
||||
admin_telegram_id,
|
||||
animation=message.animation.file_id,
|
||||
caption=caption,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.sticker:
|
||||
# Стикер - сначала отправляем заголовок, потом стикер
|
||||
await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML")
|
||||
sent_msg = await message.bot.send_sticker(admin_telegram_id, sticker=message.sticker.file_id)
|
||||
return await message.bot.send_sticker(admin_telegram_id, sticker=message.sticker.file_id)
|
||||
elif message.voice:
|
||||
# Голосовое сообщение
|
||||
sent_msg = await message.bot.send_voice(
|
||||
return await message.bot.send_voice(
|
||||
admin_telegram_id,
|
||||
voice=message.voice.file_id,
|
||||
caption=header,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message.video_note:
|
||||
# Видео-кружок
|
||||
await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML")
|
||||
sent_msg = await message.bot.send_video_note(admin_telegram_id, video_note=message.video_note.file_id)
|
||||
else:
|
||||
# Неизвестный тип - просто копируем
|
||||
await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML")
|
||||
sent_msg = await message.copy_to(admin_telegram_id)
|
||||
|
||||
return sent_msg.message_id
|
||||
except TelegramRetryAfter as e:
|
||||
if retry and e.retry_after <= MAX_RETRY_AFTER_SECONDS:
|
||||
logger.warning(f"FloodWait при отправке админу {admin_telegram_id}: {e.retry_after}с")
|
||||
await asyncio.sleep(e.retry_after + 1)
|
||||
return await _send_message_to_admin_with_sender(
|
||||
message, admin_telegram_id, sender_info, retry=False
|
||||
)
|
||||
logger.warning(f"Пропускаем отправку админу {admin_telegram_id}: FloodWait {e.retry_after}с")
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.warning(f"Не удалось отправить сообщение админу {admin_telegram_id}: {e}")
|
||||
return None
|
||||
return await message.bot.send_video_note(admin_telegram_id, video_note=message.video_note.file_id)
|
||||
|
||||
await message.bot.send_message(admin_telegram_id, header, parse_mode="HTML")
|
||||
return await message.copy_to(admin_telegram_id)
|
||||
|
||||
result = await delivery_service.send_with_guard(
|
||||
admin_telegram_id,
|
||||
send_message,
|
||||
retry=retry,
|
||||
)
|
||||
if result.success:
|
||||
return result.telegram_message.message_id
|
||||
|
||||
logger.warning(
|
||||
"Не удалось отправить сообщение админу %s: %s",
|
||||
admin_telegram_id,
|
||||
result.status,
|
||||
)
|
||||
return None
|
||||
|
||||
|
||||
async def forward_to_channel(message: Message, channel_id: str) -> tuple[bool, Optional[int]]:
|
||||
|
||||
@@ -15,6 +15,7 @@ from src.core.services import UserService
|
||||
from src.core.models import User
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.config import ADMIN_IDS
|
||||
from src.core.delivery import delivery_service
|
||||
|
||||
|
||||
router = Router(name='p2p_chat_router')
|
||||
@@ -416,49 +417,58 @@ async def handle_p2p_message(message: Message, state: FSMContext):
|
||||
file_id = message.document.file_id
|
||||
text = message.caption
|
||||
|
||||
# Отправляем сообщение получателю
|
||||
try:
|
||||
async def send_to_recipient():
|
||||
if message_type == "text":
|
||||
sent = await message.bot.send_message(
|
||||
return await message.bot.send_message(
|
||||
recipient_telegram_id,
|
||||
f"<b>{sender_name}</b>\n\n{text}",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message_type == "photo":
|
||||
sent = await message.bot.send_photo(
|
||||
return await message.bot.send_photo(
|
||||
recipient_telegram_id,
|
||||
photo=file_id,
|
||||
caption=f"<b>{sender_name}</b>\n\n{text or ''}" ,
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message_type == "video":
|
||||
sent = await message.bot.send_video(
|
||||
return await message.bot.send_video(
|
||||
recipient_telegram_id,
|
||||
video=file_id,
|
||||
caption=f"<b>{sender_name}</b>\n\n{text or ''}",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
elif message_type == "document":
|
||||
sent = await message.bot.send_document(
|
||||
return await message.bot.send_document(
|
||||
recipient_telegram_id,
|
||||
document=file_id,
|
||||
caption=f"<b>{sender_name}</b>\n\n{text or ''}",
|
||||
parse_mode="HTML"
|
||||
)
|
||||
|
||||
# Сохраняем в БД
|
||||
await P2PMessageService.send_message(
|
||||
session,
|
||||
sender_id=sender.id,
|
||||
recipient_id=recipient_id,
|
||||
message_type=message_type,
|
||||
text=text,
|
||||
file_id=file_id,
|
||||
sender_message_id=message.message_id,
|
||||
recipient_message_id=sent.message_id
|
||||
)
|
||||
|
||||
await message.answer("✅ Сообщение доставлено")
|
||||
|
||||
except Exception as e:
|
||||
await message.answer(f"❌ Не удалось доставить сообщение: {e}")
|
||||
|
||||
return await message.copy_to(recipient_telegram_id)
|
||||
|
||||
delivery = await delivery_service.send_with_guard(
|
||||
recipient_telegram_id,
|
||||
send_to_recipient,
|
||||
)
|
||||
if not delivery.success:
|
||||
if delivery.status in {"blocked_bot", "deactivated", "not_found", "chat_not_found"}:
|
||||
await message.answer("❌ Получатель недоступен: он мог заблокировать бота")
|
||||
else:
|
||||
await message.answer("❌ Не удалось доставить сообщение. Попробуйте позже")
|
||||
return
|
||||
|
||||
# Сохраняем в БД только реально доставленное сообщение.
|
||||
await P2PMessageService.send_message(
|
||||
session,
|
||||
sender_id=sender.id,
|
||||
recipient_id=recipient_id,
|
||||
message_type=message_type,
|
||||
text=text,
|
||||
file_id=file_id,
|
||||
sender_message_id=message.message_id,
|
||||
recipient_message_id=delivery.telegram_message.message_id
|
||||
)
|
||||
|
||||
await message.answer("✅ Сообщение доставлено")
|
||||
|
||||
@@ -13,6 +13,7 @@ from src.core.services import LotteryService
|
||||
from src.core.models import User, Winner
|
||||
from src.core.config import ADMIN_IDS
|
||||
from src.core.permissions import admin_only
|
||||
from src.core.delivery import delivery_service
|
||||
|
||||
|
||||
router = Router()
|
||||
@@ -276,18 +277,26 @@ async def redraw_lottery(message: Message):
|
||||
)]
|
||||
])
|
||||
|
||||
try:
|
||||
await message.bot.send_message(
|
||||
owner.telegram_id,
|
||||
notification_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="Markdown"
|
||||
)
|
||||
|
||||
delivery = await delivery_service.send_with_guard(
|
||||
owner.telegram_id,
|
||||
lambda: message.bot.send_message(
|
||||
owner.telegram_id,
|
||||
notification_message,
|
||||
reply_markup=keyboard,
|
||||
parse_mode="Markdown"
|
||||
),
|
||||
)
|
||||
|
||||
if delivery.success:
|
||||
new_winner.is_notified = True
|
||||
await session.commit()
|
||||
except:
|
||||
pass
|
||||
else:
|
||||
import logging
|
||||
logging.getLogger(__name__).warning(
|
||||
"Не удалось уведомить нового победителя %s: %s",
|
||||
owner.telegram_id,
|
||||
delivery.status,
|
||||
)
|
||||
|
||||
# Формируем отчет для админа
|
||||
text = f"🔄 **Повторный розыгрыш завершен!**\n\n"
|
||||
@@ -416,10 +425,12 @@ async def confirm_winner_callback(callback_query):
|
||||
from aiogram import Bot
|
||||
from src.core.config import BOT_TOKEN
|
||||
bot = Bot(token=BOT_TOKEN)
|
||||
await bot.send_message(admin_id, admin_text, parse_mode="Markdown")
|
||||
await delivery_service.send_with_guard(
|
||||
admin_id,
|
||||
lambda: bot.send_message(admin_id, admin_text, parse_mode="Markdown"),
|
||||
)
|
||||
except Exception as e:
|
||||
import logging
|
||||
logging.getLogger(__name__).error(f"Ошибка отправки админу {admin_id}: {e}")
|
||||
|
||||
await callback_query.answer("✅ Выигрыш подтвержден!", show_alert=True)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user