365 lines
15 KiB
Python
365 lines
15 KiB
Python
"""Админские обработчики для управления чатом"""
|
||
from aiogram import Router, F
|
||
from aiogram.types import Message, CallbackQuery, InlineKeyboardMarkup, InlineKeyboardButton
|
||
from aiogram.filters import Command
|
||
from sqlalchemy.ext.asyncio import AsyncSession
|
||
|
||
from src.filters.case_insensitive import CaseInsensitiveCommand
|
||
from src.core.chat_services import (
|
||
ChatSettingsService,
|
||
BanService,
|
||
ChatMessageService
|
||
)
|
||
from src.core.services import UserService
|
||
from src.core.database import async_session_maker
|
||
from src.core.config import ADMIN_IDS
|
||
from src.core.permissions import admin_only
|
||
|
||
|
||
router = Router(name='admin_chat_router')
|
||
|
||
|
||
def get_chat_mode_keyboard() -> InlineKeyboardMarkup:
|
||
"""Клавиатура выбора режима чата"""
|
||
return InlineKeyboardMarkup(inline_keyboard=[
|
||
[
|
||
InlineKeyboardButton(text="📢 Рассылка всем", callback_data="chat_mode:broadcast"),
|
||
InlineKeyboardButton(text="➡️ Пересылка в канал", callback_data="chat_mode:forward")
|
||
],
|
||
[InlineKeyboardButton(text="❌ Закрыть", callback_data="close_menu")]
|
||
])
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("chat_mode"))
|
||
@admin_only
|
||
async def cmd_chat_mode(message: Message):
|
||
"""Команда управления режимом чата (регистронезависимо)"""
|
||
|
||
async with async_session_maker() as session:
|
||
settings = await ChatSettingsService.get_or_create_settings(session)
|
||
|
||
mode_text = "📢 Рассылка всем пользователям" if settings.mode == 'broadcast' else "➡️ Пересылка в канал"
|
||
|
||
await message.answer(
|
||
f"🎛 <b>Управление режимом чата</b>\n\n"
|
||
f"Текущий режим: {mode_text}\n\n"
|
||
f"Выберите режим работы:",
|
||
reply_markup=get_chat_mode_keyboard(),
|
||
parse_mode="HTML"
|
||
)
|
||
|
||
|
||
@router.callback_query(F.data.startswith("chat_mode:"))
|
||
async def process_chat_mode(callback: CallbackQuery):
|
||
"""Обработка выбора режима чата"""
|
||
|
||
mode = callback.data.split(":")[1]
|
||
|
||
async with async_session_maker() as session:
|
||
settings = await ChatSettingsService.set_mode(session, mode)
|
||
|
||
mode_text = "📢 Рассылка всем пользователям" if mode == 'broadcast' else "➡️ Пересылка в канал"
|
||
|
||
await callback.message.edit_text(
|
||
f"✅ Режим чата изменен!\n\n"
|
||
f"Новый режим: {mode_text}",
|
||
reply_markup=None
|
||
)
|
||
|
||
await callback.answer("✅ Режим изменен")
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("set_forward"))
|
||
@admin_only
|
||
async def cmd_set_forward(message: Message):
|
||
"""Установить ID канала для пересылки (регистронезависимо)"""
|
||
|
||
args = message.text.split(maxsplit=1)
|
||
if len(args) < 2:
|
||
await message.answer(
|
||
"📝 <b>Использование:</b>\n"
|
||
"/set_forward <chat_id>\n\n"
|
||
"Пример: /set_forward -1001234567890\n\n"
|
||
"💡 Чтобы узнать ID канала/группы:\n"
|
||
"1. Добавьте бота в канал/группу\n"
|
||
"2. Напишите любое сообщение\n"
|
||
"3. Перешлите его боту @userinfobot",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
|
||
chat_id = args[1].strip()
|
||
|
||
async with async_session_maker() as session:
|
||
settings = await ChatSettingsService.set_forward_chat(session, chat_id)
|
||
|
||
await message.answer(
|
||
f"✅ ID канала для пересылки установлен!\n\n"
|
||
f"Chat ID: <code>{chat_id}</code>\n\n"
|
||
f"Теперь переключитесь в режим пересылки командой /chat_mode",
|
||
parse_mode="HTML"
|
||
)
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("global_ban"))
|
||
@admin_only
|
||
async def cmd_global_ban(message: Message):
|
||
"""Включить/выключить глобальный бан чата (регистронезависимо)"""
|
||
|
||
async with async_session_maker() as session:
|
||
settings = await ChatSettingsService.get_or_create_settings(session)
|
||
|
||
# Переключаем состояние
|
||
new_state = not settings.global_ban
|
||
settings = await ChatSettingsService.set_global_ban(session, new_state)
|
||
|
||
if new_state:
|
||
await message.answer(
|
||
"🔇 <b>Глобальный бан включен</b>\n\n"
|
||
"Теперь только администраторы могут отправлять сообщения в чат",
|
||
parse_mode="HTML"
|
||
)
|
||
else:
|
||
await message.answer(
|
||
"🔊 <b>Глобальный бан выключен</b>\n\n"
|
||
"Все пользователи снова могут отправлять сообщения",
|
||
parse_mode="HTML"
|
||
)
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("ban"))
|
||
@admin_only
|
||
async def cmd_ban(message: Message):
|
||
"""Забанить пользователя (регистронезависимо)"""
|
||
|
||
# Проверяем является ли это ответом на сообщение
|
||
if message.reply_to_message:
|
||
target_user_id = message.reply_to_message.from_user.id
|
||
reason = message.text.split(maxsplit=1)[1] if len(message.text.split(maxsplit=1)) > 1 else None
|
||
else:
|
||
args = message.text.split(maxsplit=2)
|
||
if len(args) < 2:
|
||
await message.answer(
|
||
"📝 <b>Использование:</b>\n\n"
|
||
"1. Ответьте на сообщение пользователя: /ban [причина]\n"
|
||
"2. Укажите ID: /ban <user_id> [причина]\n\n"
|
||
"Пример: /ban 123456789 Спам",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
|
||
try:
|
||
target_user_id = int(args[1])
|
||
reason = args[2] if len(args) > 2 else None
|
||
except ValueError:
|
||
await message.answer("❌ Неверный ID пользователя")
|
||
return
|
||
|
||
async with async_session_maker() as session:
|
||
# Получаем пользователя
|
||
user = await UserService.get_user_by_telegram_id(session, target_user_id)
|
||
|
||
if not user:
|
||
await message.answer("❌ Пользователь не найден в базе")
|
||
return
|
||
|
||
# Получаем админа
|
||
admin = await UserService.get_or_create_user(
|
||
session,
|
||
message.from_user.id,
|
||
username=message.from_user.username,
|
||
first_name=message.from_user.first_name,
|
||
last_name=message.from_user.last_name
|
||
)
|
||
|
||
# Баним
|
||
ban = await BanService.ban_user(
|
||
session,
|
||
user_id=user.id,
|
||
telegram_id=target_user_id,
|
||
banned_by=admin.id,
|
||
reason=reason
|
||
)
|
||
|
||
reason_text = f"\n📝 Причина: {reason}" if reason else ""
|
||
|
||
await message.answer(
|
||
f"🚫 <b>Пользователь забанен</b>\n\n"
|
||
f"👤 Пользователь: {user.name or 'Неизвестен'}\n"
|
||
f"🆔 ID: <code>{target_user_id}</code>"
|
||
f"{reason_text}",
|
||
parse_mode="HTML"
|
||
)
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("unban"))
|
||
@admin_only
|
||
async def cmd_unban(message: Message):
|
||
"""Разбанить пользователя (регистронезависимо)"""
|
||
|
||
# Проверяем является ли это ответом на сообщение
|
||
if message.reply_to_message:
|
||
target_user_id = message.reply_to_message.from_user.id
|
||
else:
|
||
args = message.text.split()
|
||
if len(args) < 2:
|
||
await message.answer(
|
||
"📝 <b>Использование:</b>\n\n"
|
||
"1. Ответьте на сообщение пользователя: /unban\n"
|
||
"2. Укажите ID: /unban <user_id>\n\n"
|
||
"Пример: /unban 123456789",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
|
||
try:
|
||
target_user_id = int(args[1])
|
||
except ValueError:
|
||
await message.answer("❌ Неверный ID пользователя")
|
||
return
|
||
|
||
async with async_session_maker() as session:
|
||
# Разбаниваем
|
||
success = await BanService.unban_user(session, target_user_id)
|
||
|
||
if success:
|
||
await message.answer(
|
||
f"✅ <b>Пользователь разбанен</b>\n\n"
|
||
f"🆔 ID: <code>{target_user_id}</code>\n\n"
|
||
f"Теперь пользователь может отправлять сообщения",
|
||
parse_mode="HTML"
|
||
)
|
||
else:
|
||
await message.answer("❌ Пользователь не был забанен")
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("banlist"))
|
||
@admin_only
|
||
async def cmd_banlist(message: Message):
|
||
"""Показать список заблокированных пользователей (регистронезависимо)"""
|
||
|
||
async with async_session_maker() as session:
|
||
banned_users = await BanService.get_banned_users(session, active_only=True)
|
||
|
||
if not banned_users:
|
||
await message.answer("📋 Список банов пуст")
|
||
return
|
||
|
||
text = "🚫 <b>Забаненные пользователи</b>\n\n"
|
||
|
||
for ban in banned_users:
|
||
user = ban.user
|
||
admin = ban.admin
|
||
|
||
text += f"👤 {user.name or 'Неизвестен'} (<code>{ban.telegram_id}</code>)\n"
|
||
text += f"🔨 Забанил: {admin.name if admin else 'Неизвестен'}\n"
|
||
|
||
if ban.reason:
|
||
text += f"📝 Причина: {ban.reason}\n"
|
||
|
||
text += f"📅 Дата: {ban.banned_at.strftime('%d.%m.%Y %H:%M')}\n"
|
||
text += "\n"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("delete_msg"))
|
||
@admin_only
|
||
async def cmd_delete_message(message: Message):
|
||
"""Удалить сообщение из чата (пометить как удаленное) (регистронезависимо)"""
|
||
|
||
if not message.reply_to_message:
|
||
await message.answer(
|
||
"📝 <b>Использование:</b>\n\n"
|
||
"Ответьте на сообщение которое хотите удалить командой /delete_msg",
|
||
parse_mode="HTML"
|
||
)
|
||
return
|
||
|
||
async with async_session_maker() as session:
|
||
# Получаем админа
|
||
admin = await UserService.get_or_create_user(
|
||
session,
|
||
message.from_user.id,
|
||
username=message.from_user.username,
|
||
first_name=message.from_user.first_name,
|
||
last_name=message.from_user.last_name
|
||
)
|
||
|
||
# Находим сообщение в базе по telegram_message_id
|
||
from sqlalchemy import select
|
||
from src.core.models import ChatMessage
|
||
|
||
result = await session.execute(
|
||
select(ChatMessage).where(
|
||
ChatMessage.telegram_message_id == message.reply_to_message.message_id
|
||
)
|
||
)
|
||
chat_message = result.scalar_one_or_none()
|
||
|
||
if not chat_message:
|
||
await message.answer("❌ Сообщение не найдено в базе данных")
|
||
return
|
||
|
||
# Помечаем как удаленное
|
||
success = await ChatMessageService.delete_message(
|
||
session,
|
||
message_id=chat_message.id,
|
||
deleted_by=admin.id
|
||
)
|
||
|
||
if success:
|
||
# Пытаемся удалить сообщение у всех пользователей
|
||
if chat_message.forwarded_message_ids:
|
||
deleted_count = 0
|
||
for user_telegram_id, msg_id in chat_message.forwarded_message_ids.items():
|
||
try:
|
||
await message.bot.delete_message(int(user_telegram_id), msg_id)
|
||
deleted_count += 1
|
||
except Exception as e:
|
||
print(f"Failed to delete message {msg_id} for user {user_telegram_id}: {e}")
|
||
|
||
await message.answer(
|
||
f"✅ <b>Сообщение удалено</b>\n\n"
|
||
f"🗑 Удалено у {deleted_count} пользователей",
|
||
parse_mode="HTML"
|
||
)
|
||
else:
|
||
await message.answer("✅ Сообщение помечено как удаленное")
|
||
else:
|
||
await message.answer("❌ Не удалось удалить сообщение")
|
||
|
||
|
||
@router.message(CaseInsensitiveCommand("chat_stats"))
|
||
@admin_only
|
||
async def cmd_chat_stats(message: Message):
|
||
"""Статистика чата (регистронезависимо)"""
|
||
|
||
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)
|
||
|
||
mode_text = "📢 Рассылка всем" if settings.mode == 'broadcast' else "➡️ Пересылка в канал"
|
||
global_ban_text = "🔇 Включен" if settings.global_ban else "🔊 Выключен"
|
||
|
||
text = (
|
||
f"📊 <b>Статистика чата</b>\n\n"
|
||
f"🎛 Режим: {mode_text}\n"
|
||
f"🚫 Глобальный бан: {global_ban_text}\n"
|
||
f"👥 Забанено пользователей: {len(banned_users)}\n"
|
||
f"💬 Сообщений за последнее время: {len(recent_messages)}\n"
|
||
)
|
||
|
||
if settings.mode == 'forward' and settings.forward_chat_id:
|
||
text += f"\n➡️ ID канала: <code>{settings.forward_chat_id}</code>"
|
||
|
||
await message.answer(text, parse_mode="HTML")
|
||
|
||
|
||
@router.callback_query(F.data == "close_menu")
|
||
async def close_menu(callback: CallbackQuery):
|
||
"""Закрыть меню"""
|
||
await callback.message.delete()
|
||
await callback.answer()
|