diff --git a/.gitignore b/.gitignore index 81b150b..1a0db67 100644 --- a/.gitignore +++ b/.gitignore @@ -12,3 +12,4 @@ storage/__pycache__/ utils/__pycache__/ storage/message.txt /addons/dowloadmp4_to_youtube/gettoken.py +/drevo.py diff --git a/addons/dowloadmp4_to_youtube/dowmp4.py b/addons/dowloadmp4_to_youtube/dowmp4.py index 4d77eb7..446ec96 100644 --- a/addons/dowloadmp4_to_youtube/dowmp4.py +++ b/addons/dowloadmp4_to_youtube/dowmp4.py @@ -9,7 +9,6 @@ import sys import io import unicodedata import uuid -from urllib.parse import unquote from config import Config # Настройка кодировки для всего приложения diff --git a/bot/core.py b/bot/core.py index 9219b24..4c148cc 100644 --- a/bot/core.py +++ b/bot/core.py @@ -16,8 +16,8 @@ class TelegramBot: from handlers import admin, schedule#, media, common # Регистрируем обработчики из разных модулей - admin.AdminHandlers.register(self) - schedule.register_handlers(self.dp, self.state, self.bot) + admin.register_handlers(self.dp, self.state, self.bot) + schedule.register_handlers(self.dp, self.state) #media.register_handlers(self.dp, self.state, self.bot) #common.register_handlers(self.dp, self.state, self.bot) diff --git a/handlers/admin.py b/handlers/admin.py index 56bbde7..7c98177 100644 --- a/handlers/admin.py +++ b/handlers/admin.py @@ -1,88 +1,99 @@ -from aiogram import types +from aiogram import types, Dispatcher, Bot from aiogram.types import Message from aiogram.filters import Command from config import Config +from models.state import BotState from utils.antispam import admin_required from services.watcher_service import WatcherService from storage.message_storage import load_messages, save_message, clear_messages from logging import getLogger +from utils.analytics import create_statistics_text logger = getLogger(__name__) -class AdminHandlers: - def register(self): - """Регистрирует все хендлеры этого класса""" - @self.dp.message(Command("log")) - @admin_required(3) - async def send_log(message: Message): - try: - log_file = types.FSInputFile(Config.LOG_FILE) - await message.answer_document(log_file, caption="📑 Логи бота") - except FileNotFoundError: - await message.answer("Файл логов пока не создан.") - @self.dp.message(Command("status")) - @admin_required(3) - async def send_status(message: Message): - from utils.analytics import analyze_bot_logs - from utils.mac_metrics import get_macbook_battery_level - try: - stats = analyze_bot_logs(Config.LOG_FILE) - batt = await get_macbook_battery_level() - status_text = ( - "🤖 СТАТУС БОТА\n" - "══════════════\n" - f"✅ Uptime: {stats.get('uptime_percentage', 0)}%\n" - f"⏱️ Слежка расписания: {'ВКЛ' if self.state.watcher_work else 'ВЫКЛ'}\n" - f"🔋 Уровень заряда: {batt}%" - ) - await message.answer(status_text) - except Exception as e: - await message.answer(f"❌ Ошибка при проверке статуса: {str(e)}") +def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): + @dp.message(Command("log")) + @admin_required(3) + async def send_log(message: Message): + try: + log_file = types.FSInputFile(Config.LOG_FILE) + await message.answer_document(log_file, caption="📑 Логи бота") + except FileNotFoundError: + await message.answer("Файл логов пока не создан.") - @self.dp.message(Command("del")) - @admin_required(1) - async def delete_all_messages(message: Message): - messages = load_messages() - if not messages: - sent = await message.answer("📭 Нет сохранённых сообщений для удаления.", - reply_to_message_id=message.message_id) - save_message(sent.chat.id, sent.message_id) - return + @dp.message(Command("status")) + @admin_required(3) + async def send_status(message: Message): + from utils.analytics import analyze_bot_logs + from utils.mac_metrics import get_macbook_battery_level, get_process_usage + try: + stats = analyze_bot_logs(Config.LOG_FILE) + batt = await get_macbook_battery_level() + usage = await get_process_usage() + status_text = ( + "🤖 СТАТУС БОТА\n" + "══════════════\n" + f"✅ Uptime: {stats.get('uptime_percentage', 0)}%\n" + f"⏱️ Слежка расписания: {'ВКЛ' if state.watcher_work else 'ВЫКЛ'}\n" + f"🔋 Уровень заряда: {batt}%\n" + f"🖥️ Загрузка цп: {usage["cpu_percent"]}\n" + f"🧠 Загрузка оперативки: {usage["rss_mb"]:.2f} MB\n" + ) + await message.answer(status_text) + except Exception as e: + await message.answer(f"❌ Ошибка при проверке статуса: {str(e)}") - deleted = 0 - for chat_id, msg_id in messages: - try: - await self.bot.delete_message(chat_id, msg_id) - deleted += 1 - except Exception as e: - logger.warning(f"Не удалось удалить {msg_id} в чате {chat_id}: {e}") + @dp.message(Command("analytics")) + @admin_required(1) + async def stat(message: Message): + from utils.analytics import analyze_bot_logs + stats = analyze_bot_logs(Config.LOG_FILE) + await message.answer(create_statistics_text(stats), reply_to_message_id=message.message_id) - clear_messages() - sent = await message.answer(f"✅ Удалено {deleted} сообщений (включая /rasp).", + @dp.message(Command("del")) + @admin_required(1) + async def delete_all_messages(message: Message): + messages = load_messages() + if not messages: + sent = await message.answer("📭 Нет сохранённых сообщений для удаления.", reply_to_message_id=message.message_id) save_message(sent.chat.id, sent.message_id) + return - @self.dp.message(Command("power")) - @admin_required(2) - async def power_control(message: types.Message): - args = message.text.split() - if len(args) < 2: - status = "включена" if self.state.watcher_work else "выключена" - await message.answer(f"⏱️ Слежка расписания: {status}") - return + deleted = 0 + for chat_id, msg_id in messages: + try: + await bot.delete_message(chat_id, msg_id) + deleted += 1 + except Exception as e: + logger.warning(f"Не удалось удалить {msg_id} в чате {chat_id}: {e}") - command = args[1].lower() - watcher_service = WatcherService(self.state, self.bot) + clear_messages() + sent = await message.answer(f"✅ Удалено {deleted} сообщений (включая /rasp).", + reply_to_message_id=message.message_id) + save_message(sent.chat.id, sent.message_id) - if command == "on" and not self.state.watcher_work: - await watcher_service.start() - await message.answer("✅ Слежка расписания включена") - elif command == "off" and self.state.watcher_work: - await watcher_service.stop() - await message.answer("❌ Слежка расписания выключена") - else: - await message.answer("❌ Неверная команда") + @dp.message(Command("power")) + @admin_required(2) + async def power_control(message: types.Message): + args = message.text.split() + if len(args) < 2: + status = "включена" if state.watcher_work else "выключена" + await message.answer(f"⏱️ Слежка расписания: {status}") + return + + command = args[1].lower() + watcher_service = WatcherService(state, bot) + + if command == "on" and not state.watcher_work: + await watcher_service.start() + await message.answer("✅ Слежка расписания включена") + elif command == "off" and state.watcher_work: + await watcher_service.stop() + await message.answer("❌ Слежка расписания выключена") + else: + await message.answer("❌ Неверная команда") diff --git a/handlers/schedule.py b/handlers/schedule.py index f8f6c80..92848cd 100644 --- a/handlers/schedule.py +++ b/handlers/schedule.py @@ -1,4 +1,4 @@ -from aiogram import types, Dispatcher, Bot +from aiogram import types, Dispatcher from aiogram.filters import Command from models.state import BotState from services.schedule_service import ScheduleService @@ -6,7 +6,7 @@ from utils.antispam import is_chat_spam from storage.message_storage import save_message -def register_handlers(dp: Dispatcher, state: BotState, bot: Bot): +def register_handlers(dp: Dispatcher, state: BotState): @dp.message(Command("rasp")) async def send_schedule(message: types.Message): """Отправка расписания""" diff --git a/utils/analytics.py b/utils/analytics.py index ca243cb..9afe6a2 100644 --- a/utils/analytics.py +++ b/utils/analytics.py @@ -1,6 +1,7 @@ from collections import Counter import re from datetime import datetime +import statistics import tempfile import json @@ -198,7 +199,7 @@ def calculate_schedule_success_rate(stats): def create_statistics_text(stats): - """Создает текстовый отчет статистики""" + """Создает текстовый отчет статистики с расширенными метриками""" if 'error' in stats: return f"❌ Ошибка анализа логов: {stats['error']}" @@ -212,15 +213,28 @@ def create_statistics_text(stats): text += f"• Строк в логе: {stats['total_lines']:,}\n\n" # Производительность + handling_times = stats.get("handling_times", []) # сохрани список в analyze_bot_logs + median_time = statistics.median(handling_times) if handling_times else 0 + text += "⚡ ПРОИЗВОДИТЕЛЬНОСТЬ:\n" text += f"• Среднее время: {stats['performance']['avg_handling_time']:.0f} мс\n" + text += f"• Медиана времени: {median_time:.0f} мс\n" text += f"• Сообщений обработано: {stats['performance']['handling_count']}\n" text += f"• Успешных операций: {stats['success_rate']}%\n\n" # Статус работы + duration = stats['time_period'].get('duration_hours', 0) + errors_total = sum(stats['errors'].values()) + errors_per_hour = round(errors_total / duration, 2) if duration else 0 + + restarts = stats['restarts'] + mtbf = round(duration / restarts, 2) if restarts else duration + text += "🔄 СТАТУС РАБОТЫ:\n" - text += f"• Перезапусков: {stats['restarts']}\n" - text += f"• Uptime: {stats['uptime_percentage']}%\n\n" + text += f"• Перезапусков: {restarts}\n" + text += f"• Uptime: {stats['uptime_percentage']}%\n" + text += f"• MTBF (среднее время между перезапусками): {mtbf} ч\n" + text += f"• Ошибок в час: {errors_per_hour}\n\n" # Расписание text += "📅 РАСПИСАНИЕ:\n" @@ -231,17 +245,23 @@ def create_statistics_text(stats): # Ошибки text += "🚨 ОШИБКИ:\n" - text += f"• Всего ошибок: {sum(stats['errors'].values())}\n" + text += f"• Всего ошибок: {errors_total}\n" text += f"• Предупреждений: {sum(stats['warnings'].values())}\n" text += f"• Сетевых: {stats['network_errors']}\n" - text += f"• Браузера: {stats['browser_errors']}\n\n" + text += f"• Браузера: {stats['browser_errors']}\n" + + # Топ-3 ошибок + if stats['errors']: + text += "• Топ ошибок:\n" + for err, count in stats['errors'].most_common(3): + text += f" - {err} ({count})\n" + text += "\n" # Пользователи text += "👥 АКТИВНОСТЬ:\n" text += f"• Команд: {sum(stats['user_commands'].values())}\n" text += f"• Групп: {len(stats['groups'])}\n" - # Топ групп if stats['groups']: text += "• Топ групп:\n" for group, count in stats['groups'].most_common(3): diff --git a/utils/mac_metrics.py b/utils/mac_metrics.py index 2b82130..453f738 100644 --- a/utils/mac_metrics.py +++ b/utils/mac_metrics.py @@ -1,27 +1,66 @@ import asyncio +import os async def get_macbook_battery_level(): - """ - Асинхронная функция для получения уровня заряда батареи на macOS. - Использует утилиту pmset. - """ - # Запускаем команду pmset -g batt process = await asyncio.create_subprocess_exec( "pmset", "-g", "batt", stdout=asyncio.subprocess.PIPE, stderr=asyncio.subprocess.PIPE ) - stdout, stderr = await process.communicate() - if process.returncode != 0: raise RuntimeError(f"Ошибка при выполнении pmset: {stderr.decode().strip()}") output = stdout.decode().strip() - # Пример строки: " - InternalBattery-0 (id=1234567) 85%; charging; ..." for part in output.splitlines(): if "%" in part: percent_str = part.split("\t")[-1].split(";")[0].strip() return int(percent_str.replace("%", "")) + raise ValueError("Не удалось определить уровень заряда") - raise ValueError("Не удалось определить уровень заряда") \ No newline at end of file + +async def get_process_usage(pid=None): + """ + Возвращает CPU, MEM% и RSS в мегабайтах для указанного процесса. + По умолчанию берёт текущий процесс (os.getpid()). + """ + if pid is None: + pid = os.getpid() + + process = await asyncio.create_subprocess_exec( + "ps", "-p", str(pid), "-o", "%cpu,%mem,rss,comm", + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.PIPE + ) + stdout, stderr = await process.communicate() + if process.returncode != 0: + raise RuntimeError(f"Ошибка ps: {stderr.decode().strip()}") + + lines = stdout.decode().strip().splitlines() + if len(lines) < 2: + return None + + cpu, mem_percent, rss_kb, comm = lines[1].split(None, 3) + + return { + "pid": pid, + "command": comm, + "cpu_percent": float(cpu), + "mem_percent": float(mem_percent), + "rss_mb": int(rss_kb) / 1024 # переводим КБ → МБ + } + + + + + +async def main(): + battery = await get_macbook_battery_level() + usage = await get_process_usage() + + print(f"🔋 Батарея: {battery}%") + print(f"🖥 CPU: {usage['cpu_percent']}% | MEM: {usage['mem_percent']}% | RSS: {usage['rss_mb']:.2f} MB") + + +if __name__ == "__main__": + asyncio.run(main())