v2_functions #3

Merged
trevor merged 2 commits from v2_functions into master 2026-02-11 09:41:26 +00:00
7 changed files with 839 additions and 112 deletions
Showing only changes of commit ca0c63a89c - Show all commits

View File

@@ -6,7 +6,7 @@ from aiogram.fsm.state import State, StatesGroup
from aiogram.filters import StateFilter, Command
from sqlalchemy.ext.asyncio import AsyncSession
import asyncio
from typing import List, Dict, Optional, Set
from typing import List, Dict, Optional, Set, Any
from collections import deque
import time
@@ -130,18 +130,21 @@ async def get_all_active_users(session: AsyncSession) -> List:
async def broadcast_message_with_scheduler(
message: Message,
sender_user: Any, # User model object
exclude_user_id: Optional[int] = None,
admin_only: bool = False,
sender_info: Optional[str] = None
admin_only: bool = False
) -> tuple[Dict[str, int], int, int]:
"""
Разослать сообщение всем пользователям с планировщиком (пакетная отправка).
Подписи формируются динамически в зависимости от получателя:
- Админы видят: nickname (карта: XXXX)
- Обычные пользователи видят: nickname (от пользователя) или "Админ" (от админа)
Args:
message: Сообщение для рассылки
sender_user: Объект User отправителя
exclude_user_id: ID пользователя для исключения
admin_only: Рассылать только админам
sender_info: Информация об отправителе (для показа админам)
Возвращает: (forwarded_ids, success_count, fail_count)
"""
@@ -165,12 +168,29 @@ async def broadcast_message_with_scheduler(
# Отправляем пакет
tasks = []
for user in batch:
# Для админов добавляем информацию об отправителе, если сообщение от обычного пользователя
if sender_info and user.telegram_id in ADMIN_IDS:
tasks.append(_send_message_to_admin_with_sender(message, user.telegram_id, sender_info))
for recipient_user in batch:
# Формируем подпись в зависимости от получателя
if recipient_user.telegram_id in ADMIN_IDS:
# Админы видят полную информацию: nickname (карта: XXXX)
sender_name = sender_user.nickname if sender_user.nickname else (
f"@{sender_user.username}" if sender_user.username else sender_user.first_name
)
if sender_user.club_card_number:
sender_name += f" (карта: {sender_user.club_card_number})"
sender_info = sender_name
tasks.append(_send_message_to_admin_with_sender(message, recipient_user.telegram_id, sender_info))
else:
tasks.append(_send_message_to_user(message, user.telegram_id))
# Обычные пользователи видят:
# - "Админ" если отправитель - админ
# - nickname если отправитель - обычный пользователь
if sender_user.telegram_id in ADMIN_IDS:
sender_info = "Админ"
tasks.append(_send_message_to_user_with_sender(message, recipient_user.telegram_id, sender_info))
else:
sender_info = sender_user.nickname if sender_user.nickname else (
f"@{sender_user.username}" if sender_user.username else sender_user.first_name
)
tasks.append(_send_message_to_user_with_sender(message, recipient_user.telegram_id, sender_info))
# Ждем завершения пакета
results = await asyncio.gather(*tasks, return_exceptions=True)
@@ -205,6 +225,85 @@ async def _send_message_to_user(message: Message, user_telegram_id: int) -> Opti
return None
async def _send_message_to_user_with_sender(message: Message, user_telegram_id: int, sender_info: str) -> Optional[int]:
"""
Отправить сообщение обычному пользователю с информацией об отправителе.
Возвращает message_id при успехе или None при ошибке.
"""
try:
# Формируем текст с информацией об отправителе
header = f"📨 <b>{sender_info}:</b>\n\n"
if message.text:
# Текстовое сообщение
sent_msg = 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(
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(
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(
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(
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)
elif message.voice:
# Голосовое сообщение
sent_msg = 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 Exception as e:
print(f"Failed to send message to {user_telegram_id}: {e}")
return None
async def _send_message_to_admin_with_sender(message: Message, admin_telegram_id: int, sender_info: str) -> Optional[int]:
"""
Отправить сообщение админу с информацией об отправителе.
@@ -443,20 +542,12 @@ async def handle_text_message(message: Message, state: FSMContext):
# Обрабатываем в зависимости от режима
if settings.mode == 'broadcast':
# Режим рассылки с планировщиком
# Формируем информацию об отправителе для админов (если это не админ)
sender_info = None
if not is_admin(message.from_user.id):
# Используем nickname, если есть, иначе fallback на username или first_name
sender_name = user.nickname if user.nickname else (f"@{user.username}" if user.username else user.first_name)
if user.club_card_number:
sender_name += f" (карта: {user.club_card_number})"
sender_info = sender_name
# Передаем объект user для динамического формирования подписей
# ВСЕГДА исключаем отправителя - он не должен получать своё же сообщение
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
message,
exclude_user_id=message.from_user.id,
sender_info=sender_info
message,
sender_user=user,
exclude_user_id=message.from_user.id
)
# Сохраняем сообщение в историю
@@ -532,20 +623,11 @@ async def handle_photo_message(message: Message, state: FSMContext):
photo = message.photo[-1]
if settings.mode == 'broadcast':
# Формируем информацию об отправителе для админов (если это не админ)
sender_info = None
if not is_admin(message.from_user.id):
# Используем nickname, если есть, иначе fallback на username или first_name
sender_name = user.nickname if user.nickname else (f"@{user.username}" if user.username else user.first_name)
if user.club_card_number:
sender_name += f" (карта: {user.club_card_number})"
sender_info = sender_name
# Рассылаем фото - ВСЕГДА исключаем отправителя
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
message,
exclude_user_id=message.from_user.id,
sender_info=sender_info
sender_user=user,
exclude_user_id=message.from_user.id
)
await ChatMessageService.save_message(
@@ -607,19 +689,11 @@ async def handle_video_message(message: Message, state: FSMContext):
)
if settings.mode == 'broadcast':
# Формируем информацию об отправителе для админов (если это не админ)
sender_info = None
if not is_admin(message.from_user.id):
# Используем nickname, если есть, иначе fallback на username или first_name
sender_name = user.nickname if user.nickname else (f"@{user.username}" if user.username else user.first_name)
if user.club_card_number:
sender_name += f" (карта: {user.club_card_number})"
sender_info = sender_name
# Рассылаем видео
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
message,
exclude_user_id=message.from_user.id,
sender_info=sender_info
sender_user=user,
exclude_user_id=message.from_user.id
)
await ChatMessageService.save_message(
@@ -681,19 +755,11 @@ async def handle_document_message(message: Message, state: FSMContext):
)
if settings.mode == 'broadcast':
# Формируем информацию об отправителе для админов (если это не админ)
sender_info = None
if not is_admin(message.from_user.id):
# Используем nickname, если есть, иначе fallback на username или first_name
sender_name = user.nickname if user.nickname else (f"@{user.username}" if user.username else user.first_name)
if user.club_card_number:
sender_name += f" (карта: {user.club_card_number})"
sender_info = sender_name
# Рассылаем документ
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
message,
exclude_user_id=message.from_user.id,
sender_info=sender_info
sender_user=user,
exclude_user_id=message.from_user.id
)
await ChatMessageService.save_message(
@@ -755,19 +821,11 @@ async def handle_animation_message(message: Message, state: FSMContext):
)
if settings.mode == 'broadcast':
# Формируем информацию об отправителе для админов (если это не админ)
sender_info = None
if not is_admin(message.from_user.id):
# Используем nickname, если есть, иначе fallback на username или first_name
sender_name = user.nickname if user.nickname else (f"@{user.username}" if user.username else user.first_name)
if user.club_card_number:
sender_name += f" (карта: {user.club_card_number})"
sender_info = sender_name
# Рассылаем анимацию
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
message,
exclude_user_id=message.from_user.id,
sender_info=sender_info
sender_user=user,
exclude_user_id=message.from_user.id
)
await ChatMessageService.save_message(
@@ -829,19 +887,11 @@ async def handle_sticker_message(message: Message, state: FSMContext):
)
if settings.mode == 'broadcast':
# Формируем информацию об отправителе для админов (если это не админ)
sender_info = None
if not is_admin(message.from_user.id):
# Используем nickname, если есть, иначе fallback на username или first_name
sender_name = user.nickname if user.nickname else (f"@{user.username}" if user.username else user.first_name)
if user.club_card_number:
sender_name += f" (карта: {user.club_card_number})"
sender_info = sender_name
# Рассылаем стикер
forwarded_ids, success, fail = await broadcast_message_with_scheduler(
message,
exclude_user_id=message.from_user.id,
sender_info=sender_info
sender_user=user,
exclude_user_id=message.from_user.id
)
await ChatMessageService.save_message(