Первый коммит

This commit is contained in:
2026-02-17 11:24:55 +07:00
commit a06448ca4b
109 changed files with 21165 additions and 0 deletions

View File

@@ -0,0 +1,238 @@
"""
Обработчик команды /listwords - отображение всех правил модерации
"""
from aiogram import Router, F
from aiogram.filters import Command
from aiogram.types import Message, CallbackQuery
from aiogram.utils.keyboard import InlineKeyboardBuilder
from bot.filters.admin import IsAdmin
from configs import settings, COMMANDS
from database import get_manager
from middleware.loggers import logger
from bot.utils.decorators import log_action
__all__ = ("router",)
CMD: str = "list"
router: Router = Router(name="listwords_cmd_router")
def get_refresh_kb(page: int = 0):
"""Клавиатура с кнопкой обновления"""
ikb = InlineKeyboardBuilder()
ikb.button(text="🔄 Обновить", callback_data=f"listwords:refresh:{page}")
ikb.button(text="📊 Статистика", callback_data="stats")
ikb.adjust(2)
return ikb.as_markup()
async def format_banwords_list(page: int = 0) -> str:
"""
Форматирует список всех банвордов с разбивкой по типам.
Args:
page: Номер страницы (для будущей пагинации)
Returns:
Отформатированная строка со всеми правилами
"""
manager = get_manager()
# Получаем все данные из БД
try:
# Используем существующий метод get_all_words_list()
data = await manager.get_all_words_list()
stats = await manager.get_stats()
# Извлекаем данные из словаря
permanent_words = list(data.get('substring', set()))
permanent_lemmas = list(data.get('lemma', set()))
permanent_parts = list(data.get('part', set()))
temp_words = list(data.get('temp_substring', set()))
temp_lemmas = list(data.get('temp_lemma', set()))
conflict_words = list(data.get('conflict_substring', set()))
conflict_lemmas = list(data.get('conflict_lemma', set()))
exceptions = list(data.get('whitelist', set()))
except Exception as e:
logger.error(f"Ошибка получения данных из БД: {e}", log_type="LISTWORDS")
return "❌ <b>Ошибка загрузки данных из базы</b>"
# === ФОРМИРУЕМ ВЫВОД ===
output = "📋 <b>СПИСОК ПРАВИЛ МОДЕРАЦИИ</b>\n\n"
# Статистика
total_count = (
len(permanent_words) + len(permanent_lemmas) + len(permanent_parts) +
len(temp_words) + len(temp_lemmas) +
len(conflict_words) + len(conflict_lemmas)
)
output += f"📊 <b>Общая статистика:</b>\n"
output += f"├─ Всего правил: <code>{total_count}</code>\n"
output += f"├─ Исключений: <code>{len(exceptions)}</code>\n"
output += f"├─ Удалений за всё время: <code>{stats.get('total_deletions', 0)}</code>\n"
output += f"└─ Администраторов: <code>{stats.get('admins', 0)}</code>\n\n"
# === ПОСТОЯННЫЕ ПРАВИЛА ===
if permanent_words or permanent_lemmas or permanent_parts:
output += "🔴 <b>ПОСТОЯННЫЕ ПРАВИЛА:</b>\n\n"
if permanent_words:
output += f"📝 <b>Подстроки</b> ({len(permanent_words)}):\n"
words_str = ', '.join([f"<code>{w}</code>" for w in sorted(permanent_words)[:20]])
if len(permanent_words) > 20:
words_str += f" ... <i>(+{len(permanent_words) - 20} ещё)</i>"
output += f"{words_str}\n\n"
if permanent_lemmas:
output += f"🔤 <b>Леммы</b> ({len(permanent_lemmas)}):\n"
lemmas_str = ', '.join([f"<code>{w}</code>" for w in sorted(permanent_lemmas)[:20]])
if len(permanent_lemmas) > 20:
lemmas_str += f" ... <i>(+{len(permanent_lemmas) - 20} ещё)</i>"
output += f"{lemmas_str}\n\n"
if permanent_parts:
output += f"🧩 <b>Части</b> ({len(permanent_parts)}):\n"
parts_str = ', '.join([f"<code>{w}</code>" for w in sorted(permanent_parts)[:20]])
if len(permanent_parts) > 20:
parts_str += f" ... <i>(+{len(permanent_parts) - 20} ещё)</i>"
output += f"{parts_str}\n\n"
# === ВРЕМЕННЫЕ ПРАВИЛА ===
if temp_words or temp_lemmas:
output += "⏱ <b>ВРЕМЕННЫЕ ПРАВИЛА:</b>\n\n"
if temp_words:
output += f"📝 <b>Временные подстроки</b> ({len(temp_words)}):\n"
# Для временных слов нужна дополнительная информация о времени истечения
# Пока просто выводим список
words_str = ', '.join([f"<code>{w}</code>" for w in sorted(temp_words)[:15]])
if len(temp_words) > 15:
words_str += f" ... <i>(+{len(temp_words) - 15} ещё)</i>"
output += f"{words_str}\n\n"
if temp_lemmas:
output += f"🔤 <b>Временные леммы</b> ({len(temp_lemmas)}):\n"
lemmas_str = ', '.join([f"<code>{w}</code>" for w in sorted(temp_lemmas)[:15]])
if len(temp_lemmas) > 15:
lemmas_str += f" ... <i>(+{len(temp_lemmas) - 15} ещё)</i>"
output += f"{lemmas_str}\n\n"
# === КОНФЛИКТНЫЕ ПРАВИЛА ===
if conflict_words or conflict_lemmas:
output += "⚔️ <b>КОНФЛИКТНЫЕ ПРАВИЛА:</b>\n"
output += "<i>(работают только в режиме /stopconflict)</i>\n\n"
if conflict_words:
output += f"📝 <b>Конфликтные слова</b> ({len(conflict_words)}):\n"
words_str = ', '.join([f"<code>{w}</code>" for w in sorted(conflict_words)[:15]])
if len(conflict_words) > 15:
words_str += f" ... <i>(+{len(conflict_words) - 15} ещё)</i>"
output += f"{words_str}\n\n"
if conflict_lemmas:
output += f"🔤 <b>Конфликтные леммы</b> ({len(conflict_lemmas)}):\n"
lemmas_str = ', '.join([f"<code>{w}</code>" for w in sorted(conflict_lemmas)[:15]])
if len(conflict_lemmas) > 15:
lemmas_str += f" ... <i>(+{len(conflict_lemmas) - 15} ещё)</i>"
output += f"{lemmas_str}\n\n"
# === ИСКЛЮЧЕНИЯ (WHITELIST) ===
if exceptions:
output += f"✅ <b>ИСКЛЮЧЕНИЯ</b> ({len(exceptions)}):\n"
exc_str = ', '.join([f"<code>{exceptions}</code>" for w in sorted(exceptions)[:15]])
if len(exceptions) > 15:
exc_str += f" ... <i>(+{len(exceptions) - 15} ещё)</i>"
output += f"{exc_str}\n\n"
# === АКТИВНЫЕ РЕЖИМЫ ===
active_modes = []
if await manager.is_silence_active():
active_modes.append("🔇 Режим тишины")
if await manager.is_conflict_active():
active_modes.append("⚔️ Режим антиконфликта")
if active_modes:
output += "🔴 <b>АКТИВНЫЕ РЕЖИМЫ:</b>\n"
for mode in active_modes:
output += f"{mode}\n"
output += "\n"
# === ПУСТОЙ СПИСОК ===
if total_count == 0:
output = (
"📋 <b>СПИСОК ПРАВИЛ МОДЕРАЦИИ</b>\n\n"
"⚠️ <i>Правила модерации не настроены</i>\n\n"
"Используйте команды добавления:\n"
"• /addword — добавить подстроку\n"
"• /addlemma — добавить лемму\n"
"• /addpart — добавить часть\n\n"
"📖 Подробнее: /start"
)
# Ограничение длины (Telegram limit 4096)
if len(output) > 4000:
output = output[:3950] + "\n\n<i>... список обрезан, слишком много правил</i>"
return output
@router.callback_query(F.data.startswith("listwords:refresh"))
@router.message(Command(*COMMANDS[CMD], prefix=settings.PREFIX, ignore_case=True), IsAdmin())
@log_action(action_name="LISTWORDS_COMMAND")
async def listwords_cmd(update: Message | CallbackQuery) -> None:
"""
Обработчик команды /listwords.
Отображает список всех правил модерации с разбивкой по категориям.
Доступно только администраторам.
Args:
update: Message или CallbackQuery
"""
# Определяем тип update
if isinstance(update, CallbackQuery):
message = update.message
is_callback = True
# Извлекаем номер страницы из callback_data
try:
page = int(update.data.split(":")[-1])
except:
page = 0
else:
message = update
is_callback = False
page = 0
# Формируем список
try:
text = await format_banwords_list(page)
keyboard = get_refresh_kb(page)
if is_callback:
await message.edit_text(
text=text,
parse_mode="HTML",
reply_markup=keyboard
)
await update.answer("✅ Список обновлён")
else:
await message.answer(
text=text,
parse_mode="HTML",
reply_markup=keyboard
)
except Exception as e:
logger.error(f"Ошибка отправки списка банвордов: {e}", log_type="LISTWORDS")
error_text = "❌ <b>Ошибка загрузки списка</b>\n\nПопробуйте позже"
if is_callback:
await update.answer("❌ Ошибка загрузки", show_alert=True)
else:
await message.answer(error_text, parse_mode="HTML")