From 33f4f18f001e6cde102821984a949fdcc06a7e71 Mon Sep 17 00:00:00 2001 From: Markov Andrey Date: Tue, 30 Jun 2026 09:07:59 +0000 Subject: [PATCH] Add new file --- core/commands/stats.py | 166 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 166 insertions(+) create mode 100644 core/commands/stats.py diff --git a/core/commands/stats.py b/core/commands/stats.py new file mode 100644 index 0000000..c5f2c05 --- /dev/null +++ b/core/commands/stats.py @@ -0,0 +1,166 @@ +# -*- coding: utf-8 -*- +""" +Административные команды: !stats, !status, !clean. +- !stats – статистика работы бота (сообщения, файлы, AI-запросы, ошибки, uptime). +- !status – проверка состояния сервисов (GigaChat, PostgreSQL, Qdrant, диск). +- !clean – очистка временной директории. + +Изменения для PostgreSQL: +- Замена `conn.ping()` на `conn.execute("SELECT 1")` в команде !status. +- Обновлены тексты сообщений (MariaDB → PostgreSQL). +""" + +import os +import shutil +import logging +from datetime import datetime + +from core.commands.base import Command + +logger = logging.getLogger(__name__) + + +class StatsCommand(Command): + """Показывает статистику работы бота (только для администраторов).""" + + name = "!stats" + aliases = ["/stats", "!статистика", "/статистика", "!стат", "/стат"] + permission = "admin" + + async def execute(self, msg, args: str, user_jid: str, room_jid: str = None): + """ + Выводит: + - uptime бота + - количество обработанных сообщений + - количество AI-запросов + - количество обработанных файлов + - количество ошибок + - количество документов в базе знаний (с учётом контекста) + """ + if not self.check_permission(user_jid): + reply = msg.reply("⛔ Нет прав.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение о недостатке прав (stats)") + return + + # Время работы бота + uptime = datetime.now() - self.bot.start_time + + # Получение метрик из MetricsHandler + stats = self.bot.metrics_handler.get() + + # Количество доступных документов в БЗ (личные/комнатные + глобальные) + sources = await self.bot.kb.get_available_sources(user_jid, room_jid) + + response = ( + f"📊 **Статистика бота {self.bot.config.name}**\n" + f"⏱ Uptime: {str(uptime).split('.')[0]}\n" + f"💬 Сообщений: {stats.get('messages_total', 0)}\n" + f"🤖 AI запросов: {stats.get('ai_requests', 0)}\n" + f"📁 Файлов обработано: {stats.get('files_processed', 0)}\n" + f"❌ Ошибок: {stats.get('errors_total', 0)}\n" + f"📚 Документов в БЗ: {len(sources)}" + ) + reply = msg.reply(response) + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить статистику") + + def get_help(self) -> str: + return "Показывает статистику работы бота (админ)." + + +class StatusCommand(Command): + """Проверяет состояние всех внешних сервисов (административная команда).""" + + name = "!status" + aliases = ["/status", "!статус", "/статус"] + permission = "admin" + + async def execute(self, msg, args: str, user_jid: str, room_jid: str = None): + if not self.check_permission(user_jid): + reply = msg.reply("⛔ Нет прав.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение о недостатке прав (stats)") + return + + # --- GigaChat --- + try: + # Простой запрос для проверки доступности API + await self.bot.giga.chat([], "ping", system_prompt=None, file_id=None, temperature=0.1) + ai_status = "✅ GigaChat Online" + except Exception as e: + ai_status = f"❌ GigaChat Offline: {e}" + + # --- PostgreSQL (asyncpg) --- + try: + async with self.bot.db.pool.acquire() as conn: + # В asyncpg нет метода ping(), используем SELECT 1 + await conn.execute("SELECT 1") + db_status = "✅ PostgreSQL Online" + except Exception as e: + db_status = f"❌ PostgreSQL Error: {e}" + + # --- Qdrant --- + try: + self.bot.qdrant.client.get_collection(self.bot.qdrant.collection_name) + qd_status = "✅ Qdrant Online" + except Exception as e: + qd_status = f"❌ Qdrant Error: {e}" + + # --- Дисковое пространство (временная директория) --- + total, used, free = shutil.disk_usage(self.bot.config.temp_dir) + disk_info = f"💾 Диск: {used // (2**20)}MB / {total // (2**20)}MB" + + full_status = f"🛰 **СТАТУС БОТА**\n{ai_status}\n{db_status}\n{qd_status}\n{disk_info}" + reply = msg.reply(full_status) + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить статус") + + def get_help(self) -> str: + return "Проверяет состояние сервисов (GigaChat, PostgreSQL, Qdrant, диск) – админ." + + +class CleanCommand(Command): + """Очищает временную директорию бота (temp_dir).""" + + name = "!clean" + aliases = ["/clean", "!очистка", "/очистка"] + permission = "admin" + + async def execute(self, msg, args: str, user_jid: str, room_jid: str = None): + if not self.check_permission(user_jid): + reply = msg.reply("⛔ Нет прав.") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить сообщение о недостатке прав (stats)") + return + + count = 0 + temp_dir = self.bot.config.temp_dir + for f in os.listdir(temp_dir): + try: + p = os.path.join(temp_dir, f) + if os.path.isfile(p): + os.unlink(p) + elif os.path.isdir(p): + shutil.rmtree(p) + count += 1 + except Exception: + pass + reply = msg.reply(f"🧹 Удалено объектов из temp_dir: {count}") + if reply: + reply.send() # исправлено + else: + logger.error("Не удалось отправить отчёт об очистке") + + def get_help(self) -> str: + return "Очищает временную директорию бота (админ)." \ No newline at end of file