fix: исправлены импорты и добавлен планировщик рассылки
Some checks reported errors
continuous-integration/drone/push Build encountered an error
Some checks reported errors
continuous-integration/drone/push Build encountered an error
- Исправлены импорты: database → src.core.database, config → src.core.config - Заменены async for get_session() на async with async_session_maker() - Добавлен планировщик для пакетной рассылки сообщений (BATCH_SIZE=20, BATCH_DELAY=1.0s) - Исправлено использование is_registered вместо is_active для фильтрации пользователей - Реализована защита от блокировки Telegram при массовой рассылке Изменения: - src/handlers/chat_handlers.py: добавлен broadcast_message_with_scheduler - src/handlers/admin_chat_handlers.py: исправлены импорты и использование сессий
This commit is contained in:
137
docs/CHAT_QUICKSTART.md
Normal file
137
docs/CHAT_QUICKSTART.md
Normal file
@@ -0,0 +1,137 @@
|
||||
# Быстрый старт: Система чата
|
||||
|
||||
## Что реализовано
|
||||
|
||||
✅ **Два режима работы:**
|
||||
- Broadcast: сообщения рассылаются всем пользователям
|
||||
- Forward: сообщения пересылаются в канал/группу
|
||||
|
||||
✅ **7 типов сообщений:** text, photo, video, document, animation, sticker, voice
|
||||
|
||||
✅ **Система банов:**
|
||||
- Личные баны пользователей с причиной
|
||||
- Глобальный бан (закрытие чата для всех кроме админов)
|
||||
|
||||
✅ **Модерация:** удаление сообщений с отслеживанием
|
||||
|
||||
## Быстрая настройка
|
||||
|
||||
### 1. Режим рассылки (broadcast)
|
||||
|
||||
```bash
|
||||
# Админ отправляет команду:
|
||||
/chat_mode
|
||||
# → Нажимает "📢 Рассылка всем"
|
||||
|
||||
# Готово! Теперь сообщения пользователей рассылаются друг другу
|
||||
```
|
||||
|
||||
### 2. Режим пересылки (forward)
|
||||
|
||||
```bash
|
||||
# Шаг 1: Создайте канал и добавьте бота как админа
|
||||
|
||||
# Шаг 2: Узнайте chat_id канала:
|
||||
# - Напишите в канале сообщение
|
||||
# - Перешлите его @userinfobot
|
||||
# - Скопируйте chat_id (например: -1001234567890)
|
||||
|
||||
# Шаг 3: Установите канал
|
||||
/set_forward -1001234567890
|
||||
|
||||
# Шаг 4: Переключите режим
|
||||
/chat_mode
|
||||
# → Нажимает "➡️ Пересылка в канал"
|
||||
|
||||
# Готово! Сообщения пользователей пересылаются в канал
|
||||
```
|
||||
|
||||
## Команды модерации
|
||||
|
||||
```bash
|
||||
# Забанить пользователя (ответ на сообщение)
|
||||
/ban Причина бана
|
||||
|
||||
# Забанить по ID
|
||||
/ban 123456789 Спам
|
||||
|
||||
# Разбанить
|
||||
/unban # (ответ на сообщение)
|
||||
/unban 123456789
|
||||
|
||||
# Список банов
|
||||
/banlist
|
||||
|
||||
# Закрыть/открыть чат для всех
|
||||
/global_ban
|
||||
|
||||
# Удалить сообщение из всех чатов
|
||||
/delete_msg # (ответ на сообщение)
|
||||
|
||||
# Статистика чата
|
||||
/chat_stats
|
||||
```
|
||||
|
||||
## Структура БД
|
||||
|
||||
```
|
||||
chat_settings (1 строка)
|
||||
├── mode: 'broadcast' | 'forward'
|
||||
├── forward_chat_id: ID канала (если forward)
|
||||
└── global_ban: true/false
|
||||
|
||||
banned_users
|
||||
├── telegram_id: ID забаненного
|
||||
├── banned_by: кто забанил
|
||||
├── reason: причина
|
||||
└── is_active: активен ли бан
|
||||
|
||||
chat_messages
|
||||
├── user_id: отправитель
|
||||
├── message_type: тип сообщения
|
||||
├── text: текст или caption
|
||||
├── file_id: ID файла
|
||||
├── forwarded_message_ids: {user_id: msg_id} (JSONB)
|
||||
├── is_deleted: удалено ли
|
||||
└── deleted_by: кто удалил
|
||||
```
|
||||
|
||||
## Файлы
|
||||
|
||||
| Файл | Описание | Строк |
|
||||
|------|----------|-------|
|
||||
| `migrations/versions/005_add_chat_system.py` | Миграция БД | 108 |
|
||||
| `src/core/models.py` | Модели ORM (+67) | - |
|
||||
| `src/core/chat_services.py` | Сервисы | 267 |
|
||||
| `src/handlers/chat_handlers.py` | Обработчики сообщений | 447 |
|
||||
| `src/handlers/admin_chat_handlers.py` | Админ команды | 369 |
|
||||
| `docs/CHAT_SYSTEM.md` | Полная документация | 390 |
|
||||
|
||||
## Следующие шаги
|
||||
|
||||
1. **Тестирование:**
|
||||
- Проверить broadcast режим с разными типами сообщений
|
||||
- Проверить forward режим с каналом
|
||||
- Протестировать баны и разбаны
|
||||
- Проверить удаление сообщений
|
||||
|
||||
2. **Опциональные улучшения:**
|
||||
- Фильтрация контента (мат, спам)
|
||||
- Лимиты сообщений (антиспам)
|
||||
- Ответы на сообщения
|
||||
- Реакции на сообщения
|
||||
- История чата через команду
|
||||
|
||||
## Коммит
|
||||
|
||||
```bash
|
||||
git log --oneline -1
|
||||
# b6c27b7 feat: добавлена система чата с модерацией
|
||||
|
||||
# Ветка: feature/chat-system
|
||||
# Изменений: 7 файлов, 1592 строки добавлено
|
||||
```
|
||||
|
||||
## Полная документация
|
||||
|
||||
Смотрите: [docs/CHAT_SYSTEM.md](./CHAT_SYSTEM.md)
|
||||
@@ -10,8 +10,8 @@ from src.core.chat_services import (
|
||||
ChatMessageService
|
||||
)
|
||||
from src.core.services import UserService
|
||||
from database import get_session
|
||||
from config import ADMIN_IDS
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.config import ADMIN_IDS
|
||||
|
||||
|
||||
router = Router(name='admin_chat_router')
|
||||
@@ -40,7 +40,7 @@ async def cmd_chat_mode(message: Message):
|
||||
await message.answer("❌ У вас нет прав для выполнения этой команды")
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
settings = await ChatSettingsService.get_or_create_settings(session)
|
||||
|
||||
mode_text = "📢 Рассылка всем пользователям" if settings.mode == 'broadcast' else "➡️ Пересылка в канал"
|
||||
@@ -63,7 +63,7 @@ async def process_chat_mode(callback: CallbackQuery):
|
||||
|
||||
mode = callback.data.split(":")[1]
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
settings = await ChatSettingsService.set_mode(session, mode)
|
||||
|
||||
mode_text = "📢 Рассылка всем пользователям" if mode == 'broadcast' else "➡️ Пересылка в канал"
|
||||
@@ -100,7 +100,7 @@ async def cmd_set_forward(message: Message):
|
||||
|
||||
chat_id = args[1].strip()
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
settings = await ChatSettingsService.set_forward_chat(session, chat_id)
|
||||
|
||||
await message.answer(
|
||||
@@ -118,7 +118,7 @@ async def cmd_global_ban(message: Message):
|
||||
await message.answer("❌ У вас нет прав для выполнения этой команды")
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
settings = await ChatSettingsService.get_or_create_settings(session)
|
||||
|
||||
# Переключаем состояние
|
||||
@@ -169,7 +169,7 @@ async def cmd_ban(message: Message):
|
||||
await message.answer("❌ Неверный ID пользователя")
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
# Получаем пользователя
|
||||
user = await UserService.get_user_by_telegram_id(session, target_user_id)
|
||||
|
||||
@@ -228,7 +228,7 @@ async def cmd_unban(message: Message):
|
||||
await message.answer("❌ Неверный ID пользователя")
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
# Разбаниваем
|
||||
success = await BanService.unban_user(session, target_user_id)
|
||||
|
||||
@@ -250,7 +250,7 @@ async def cmd_banlist(message: Message):
|
||||
await message.answer("❌ У вас нет прав для выполнения этой команды")
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
banned_users = await BanService.get_banned_users(session, active_only=True)
|
||||
|
||||
if not banned_users:
|
||||
@@ -290,7 +290,7 @@ async def cmd_delete_message(message: Message):
|
||||
)
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
# Получаем админа
|
||||
admin = await UserService.get_user_by_telegram_id(session, message.from_user.id)
|
||||
|
||||
@@ -345,7 +345,7 @@ async def cmd_chat_stats(message: Message):
|
||||
await message.answer("❌ У вас нет прав для выполнения этой команды")
|
||||
return
|
||||
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
settings = await ChatSettingsService.get_or_create_settings(session)
|
||||
banned_users = await BanService.get_banned_users(session, active_only=True)
|
||||
recent_messages = await ChatMessageService.get_recent_messages(session, limit=100)
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
from aiogram import Router, F
|
||||
from aiogram.types import Message
|
||||
from sqlalchemy.ext.asyncio import AsyncSession
|
||||
import asyncio
|
||||
from typing import List, Dict, Optional
|
||||
|
||||
from src.core.chat_services import (
|
||||
ChatSettingsService,
|
||||
@@ -10,8 +12,8 @@ from src.core.chat_services import (
|
||||
BanService
|
||||
)
|
||||
from src.core.services import UserService
|
||||
from database import get_session
|
||||
from config import ADMIN_IDS
|
||||
from src.core.database import async_session_maker
|
||||
from src.core.config import ADMIN_IDS
|
||||
|
||||
|
||||
def is_admin(user_id: int) -> bool:
|
||||
@@ -21,39 +23,75 @@ def is_admin(user_id: int) -> bool:
|
||||
|
||||
router = Router(name='chat_router')
|
||||
|
||||
# Настройки для планировщика рассылки
|
||||
BATCH_SIZE = 20 # Количество сообщений в пакете
|
||||
BATCH_DELAY = 1.0 # Задержка между пакетами в секундах
|
||||
|
||||
async def get_all_active_users(session: AsyncSession):
|
||||
"""Получить всех активных пользователей для рассылки"""
|
||||
|
||||
async def get_all_active_users(session: AsyncSession) -> List:
|
||||
"""Получить всех зарегистрированных пользователей для рассылки"""
|
||||
users = await UserService.get_all_users(session)
|
||||
return [u for u in users if u.is_active]
|
||||
return [u for u in users if u.is_registered] # Используем is_registered вместо is_active
|
||||
|
||||
|
||||
async def broadcast_message(message: Message, exclude_user_id: int = None):
|
||||
"""Разослать сообщение всем пользователям"""
|
||||
async for session in get_session():
|
||||
async def broadcast_message_with_scheduler(message: Message, exclude_user_id: Optional[int] = None) -> tuple[Dict[str, int], int, int]:
|
||||
"""
|
||||
Разослать сообщение всем пользователям с планировщиком (пакетная отправка).
|
||||
Возвращает: (forwarded_ids, success_count, fail_count)
|
||||
"""
|
||||
async with async_session_maker() as session:
|
||||
users = await get_all_active_users(session)
|
||||
|
||||
if exclude_user_id:
|
||||
users = [u for u in users if u.telegram_id != exclude_user_id]
|
||||
|
||||
forwarded_ids = {}
|
||||
success_count = 0
|
||||
fail_count = 0
|
||||
|
||||
for user in users:
|
||||
if exclude_user_id and user.telegram_id == exclude_user_id:
|
||||
continue
|
||||
# Разбиваем на пакеты
|
||||
for i in range(0, len(users), BATCH_SIZE):
|
||||
batch = users[i:i + BATCH_SIZE]
|
||||
|
||||
try:
|
||||
# Копируем сообщение пользователю
|
||||
sent_msg = await message.copy_to(user.telegram_id)
|
||||
forwarded_ids[str(user.telegram_id)] = sent_msg.message_id
|
||||
success_count += 1
|
||||
except Exception as e:
|
||||
# Отправляем пакет
|
||||
tasks = []
|
||||
for user in batch:
|
||||
tasks.append(_send_message_to_user(message, user.telegram_id))
|
||||
|
||||
# Ждем завершения пакета
|
||||
results = await asyncio.gather(*tasks, return_exceptions=True)
|
||||
|
||||
# Обрабатываем результаты
|
||||
for user, result in zip(batch, results):
|
||||
if isinstance(result, Exception):
|
||||
fail_count += 1
|
||||
print(f"Failed to send message to {user.telegram_id}: {e}")
|
||||
elif result is not None:
|
||||
forwarded_ids[str(user.telegram_id)] = result
|
||||
success_count += 1
|
||||
else:
|
||||
fail_count += 1
|
||||
|
||||
# Задержка между пакетами (если есть еще пакеты)
|
||||
if i + BATCH_SIZE < len(users):
|
||||
await asyncio.sleep(BATCH_DELAY)
|
||||
|
||||
return forwarded_ids, success_count, fail_count
|
||||
|
||||
|
||||
async def forward_to_channel(message: Message, channel_id: str):
|
||||
async def _send_message_to_user(message: Message, user_telegram_id: int) -> Optional[int]:
|
||||
"""
|
||||
Отправить сообщение конкретному пользователю.
|
||||
Возвращает message_id при успехе или None при ошибке.
|
||||
"""
|
||||
try:
|
||||
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 forward_to_channel(message: Message, channel_id: str) -> tuple[bool, Optional[int]]:
|
||||
"""Переслать сообщение в канал/группу"""
|
||||
try:
|
||||
# Пересылаем сообщение в канал
|
||||
@@ -67,7 +105,7 @@ async def forward_to_channel(message: Message, channel_id: str):
|
||||
@router.message(F.text)
|
||||
async def handle_text_message(message: Message):
|
||||
"""Обработчик текстовых сообщений"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
# Проверяем права на отправку
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
@@ -90,8 +128,8 @@ async def handle_text_message(message: Message):
|
||||
|
||||
# Обрабатываем в зависимости от режима
|
||||
if settings.mode == 'broadcast':
|
||||
# Режим рассылки
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
# Режим рассылки с планировщиком
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
# Сохраняем сообщение в историю
|
||||
await ChatMessageService.save_message(
|
||||
@@ -125,7 +163,7 @@ async def handle_text_message(message: Message):
|
||||
telegram_message_id=message.message_id,
|
||||
message_type='text',
|
||||
text=message.text,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
|
||||
await message.answer("✅ Сообщение переслано в канал")
|
||||
@@ -136,10 +174,11 @@ async def handle_text_message(message: Message):
|
||||
@router.message(F.photo)
|
||||
async def handle_photo_message(message: Message):
|
||||
"""Обработчик фото"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
is_admin=is_admin(message.from_user.id)
|
||||
)
|
||||
|
||||
if not can_send:
|
||||
@@ -156,7 +195,7 @@ async def handle_photo_message(message: Message):
|
||||
photo = message.photo[-1]
|
||||
|
||||
if settings.mode == 'broadcast':
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
await ChatMessageService.save_message(
|
||||
session,
|
||||
@@ -182,7 +221,7 @@ async def handle_photo_message(message: Message):
|
||||
message_type='photo',
|
||||
text=message.caption,
|
||||
file_id=photo.file_id,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
await message.answer("✅ Фото переслано в канал")
|
||||
|
||||
@@ -190,10 +229,11 @@ async def handle_photo_message(message: Message):
|
||||
@router.message(F.video)
|
||||
async def handle_video_message(message: Message):
|
||||
"""Обработчик видео"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
is_admin=is_admin(message.from_user.id)
|
||||
)
|
||||
|
||||
if not can_send:
|
||||
@@ -207,7 +247,7 @@ async def handle_video_message(message: Message):
|
||||
return
|
||||
|
||||
if settings.mode == 'broadcast':
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
await ChatMessageService.save_message(
|
||||
session,
|
||||
@@ -233,7 +273,7 @@ async def handle_video_message(message: Message):
|
||||
message_type='video',
|
||||
text=message.caption,
|
||||
file_id=message.video.file_id,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
await message.answer("✅ Видео переслано в канал")
|
||||
|
||||
@@ -241,10 +281,11 @@ async def handle_video_message(message: Message):
|
||||
@router.message(F.document)
|
||||
async def handle_document_message(message: Message):
|
||||
"""Обработчик документов"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
is_admin=is_admin(message.from_user.id)
|
||||
)
|
||||
|
||||
if not can_send:
|
||||
@@ -258,7 +299,7 @@ async def handle_document_message(message: Message):
|
||||
return
|
||||
|
||||
if settings.mode == 'broadcast':
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
await ChatMessageService.save_message(
|
||||
session,
|
||||
@@ -284,7 +325,7 @@ async def handle_document_message(message: Message):
|
||||
message_type='document',
|
||||
text=message.caption,
|
||||
file_id=message.document.file_id,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
await message.answer("✅ Документ переслан в канал")
|
||||
|
||||
@@ -292,10 +333,11 @@ async def handle_document_message(message: Message):
|
||||
@router.message(F.animation)
|
||||
async def handle_animation_message(message: Message):
|
||||
"""Обработчик GIF анимаций"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
is_admin=is_admin(message.from_user.id)
|
||||
)
|
||||
|
||||
if not can_send:
|
||||
@@ -309,7 +351,7 @@ async def handle_animation_message(message: Message):
|
||||
return
|
||||
|
||||
if settings.mode == 'broadcast':
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
await ChatMessageService.save_message(
|
||||
session,
|
||||
@@ -335,7 +377,7 @@ async def handle_animation_message(message: Message):
|
||||
message_type='animation',
|
||||
text=message.caption,
|
||||
file_id=message.animation.file_id,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
await message.answer("✅ Анимация переслана в канал")
|
||||
|
||||
@@ -343,10 +385,11 @@ async def handle_animation_message(message: Message):
|
||||
@router.message(F.sticker)
|
||||
async def handle_sticker_message(message: Message):
|
||||
"""Обработчик стикеров"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
is_admin=is_admin(message.from_user.id)
|
||||
)
|
||||
|
||||
if not can_send:
|
||||
@@ -360,7 +403,7 @@ async def handle_sticker_message(message: Message):
|
||||
return
|
||||
|
||||
if settings.mode == 'broadcast':
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
await ChatMessageService.save_message(
|
||||
session,
|
||||
@@ -384,7 +427,7 @@ async def handle_sticker_message(message: Message):
|
||||
telegram_message_id=message.message_id,
|
||||
message_type='sticker',
|
||||
file_id=message.sticker.file_id,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
await message.answer("✅ Стикер переслан в канал")
|
||||
|
||||
@@ -392,10 +435,11 @@ async def handle_sticker_message(message: Message):
|
||||
@router.message(F.voice)
|
||||
async def handle_voice_message(message: Message):
|
||||
"""Обработчик голосовых сообщений"""
|
||||
async for session in get_session():
|
||||
async with async_session_maker() as session:
|
||||
can_send, reason = await ChatPermissionService.can_send_message(
|
||||
session,
|
||||
message.from_user.id
|
||||
message.from_user.id,
|
||||
is_admin=is_admin(message.from_user.id)
|
||||
)
|
||||
|
||||
if not can_send:
|
||||
@@ -409,7 +453,7 @@ async def handle_voice_message(message: Message):
|
||||
return
|
||||
|
||||
if settings.mode == 'broadcast':
|
||||
forwarded_ids, success, fail = await broadcast_message(message, exclude_user_id=message.from_user.id)
|
||||
forwarded_ids, success, fail = await broadcast_message_with_scheduler(message, exclude_user_id=message.from_user.id)
|
||||
|
||||
await ChatMessageService.save_message(
|
||||
session,
|
||||
@@ -433,6 +477,6 @@ async def handle_voice_message(message: Message):
|
||||
telegram_message_id=message.message_id,
|
||||
message_type='voice',
|
||||
file_id=message.voice.file_id,
|
||||
forwarded_ids={'channel': channel_msg_id}
|
||||
forwarded_ids={'channel': channel_msg_id} if channel_msg_id else None
|
||||
)
|
||||
await message.answer("✅ Голосовое сообщение переслано в канал")
|
||||
|
||||
Reference in New Issue
Block a user