I add command /analytics and I improve code

It's version 0.2.3
This commit is contained in:
Niken
2025-10-05 23:16:19 +03:00
parent 3ef1327b67
commit cce8c7dc70
7 changed files with 156 additions and 86 deletions
+1
View File
@@ -12,3 +12,4 @@ storage/__pycache__/
utils/__pycache__/ utils/__pycache__/
storage/message.txt storage/message.txt
/addons/dowloadmp4_to_youtube/gettoken.py /addons/dowloadmp4_to_youtube/gettoken.py
/drevo.py
-1
View File
@@ -9,7 +9,6 @@ import sys
import io import io
import unicodedata import unicodedata
import uuid import uuid
from urllib.parse import unquote
from config import Config from config import Config
# Настройка кодировки для всего приложения # Настройка кодировки для всего приложения
+2 -2
View File
@@ -16,8 +16,8 @@ class TelegramBot:
from handlers import admin, schedule#, media, common from handlers import admin, schedule#, media, common
# Регистрируем обработчики из разных модулей # Регистрируем обработчики из разных модулей
admin.AdminHandlers.register(self) admin.register_handlers(self.dp, self.state, self.bot)
schedule.register_handlers(self.dp, self.state, self.bot) schedule.register_handlers(self.dp, self.state)
#media.register_handlers(self.dp, self.state, self.bot) #media.register_handlers(self.dp, self.state, self.bot)
#common.register_handlers(self.dp, self.state, self.bot) #common.register_handlers(self.dp, self.state, self.bot)
+77 -66
View File
@@ -1,88 +1,99 @@
from aiogram import types from aiogram import types, Dispatcher, Bot
from aiogram.types import Message from aiogram.types import Message
from aiogram.filters import Command from aiogram.filters import Command
from config import Config from config import Config
from models.state import BotState
from utils.antispam import admin_required from utils.antispam import admin_required
from services.watcher_service import WatcherService from services.watcher_service import WatcherService
from storage.message_storage import load_messages, save_message, clear_messages from storage.message_storage import load_messages, save_message, clear_messages
from logging import getLogger from logging import getLogger
from utils.analytics import create_statistics_text
logger = getLogger(__name__) 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")) def register_handlers(dp: Dispatcher, state: BotState, bot: Bot):
@admin_required(3) @dp.message(Command("log"))
async def send_status(message: Message): @admin_required(3)
from utils.analytics import analyze_bot_logs async def send_log(message: Message):
from utils.mac_metrics import get_macbook_battery_level try:
try: log_file = types.FSInputFile(Config.LOG_FILE)
stats = analyze_bot_logs(Config.LOG_FILE) await message.answer_document(log_file, caption="📑 Логи бота")
batt = await get_macbook_battery_level() except FileNotFoundError:
status_text = ( await message.answer("Файл логов пока не создан.")
"🤖 СТАТУС БОТА\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)}")
@self.dp.message(Command("del")) @dp.message(Command("status"))
@admin_required(1) @admin_required(3)
async def delete_all_messages(message: Message): async def send_status(message: Message):
messages = load_messages() from utils.analytics import analyze_bot_logs
if not messages: from utils.mac_metrics import get_macbook_battery_level, get_process_usage
sent = await message.answer("📭 Нет сохранённых сообщений для удаления.", try:
reply_to_message_id=message.message_id) stats = analyze_bot_logs(Config.LOG_FILE)
save_message(sent.chat.id, sent.message_id) batt = await get_macbook_battery_level()
return 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 @dp.message(Command("analytics"))
for chat_id, msg_id in messages: @admin_required(1)
try: async def stat(message: Message):
await self.bot.delete_message(chat_id, msg_id) from utils.analytics import analyze_bot_logs
deleted += 1 stats = analyze_bot_logs(Config.LOG_FILE)
except Exception as e: await message.answer(create_statistics_text(stats), reply_to_message_id=message.message_id)
logger.warning(f"Не удалось удалить {msg_id} в чате {chat_id}: {e}")
clear_messages() @dp.message(Command("del"))
sent = await message.answer(f"✅ Удалено {deleted} сообщений (включая /rasp).", @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) reply_to_message_id=message.message_id)
save_message(sent.chat.id, sent.message_id) save_message(sent.chat.id, sent.message_id)
return
@self.dp.message(Command("power")) deleted = 0
@admin_required(2) for chat_id, msg_id in messages:
async def power_control(message: types.Message): try:
args = message.text.split() await bot.delete_message(chat_id, msg_id)
if len(args) < 2: deleted += 1
status = "включена" if self.state.watcher_work else "выключена" except Exception as e:
await message.answer(f"⏱️ Слежка расписания: {status}") logger.warning(f"Не удалось удалить {msg_id} в чате {chat_id}: {e}")
return
command = args[1].lower() clear_messages()
watcher_service = WatcherService(self.state, self.bot) 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: @dp.message(Command("power"))
await watcher_service.start() @admin_required(2)
await message.answer("✅ Слежка расписания включена") async def power_control(message: types.Message):
elif command == "off" and self.state.watcher_work: args = message.text.split()
await watcher_service.stop() if len(args) < 2:
await message.answer("❌ Слежка расписания выключена") status = "включена" if state.watcher_work else "выключена"
else: await message.answer(f"⏱️ Слежка расписания: {status}")
await message.answer("❌ Неверная команда") 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("❌ Неверная команда")
+2 -2
View File
@@ -1,4 +1,4 @@
from aiogram import types, Dispatcher, Bot from aiogram import types, Dispatcher
from aiogram.filters import Command from aiogram.filters import Command
from models.state import BotState from models.state import BotState
from services.schedule_service import ScheduleService from services.schedule_service import ScheduleService
@@ -6,7 +6,7 @@ from utils.antispam import is_chat_spam
from storage.message_storage import save_message 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")) @dp.message(Command("rasp"))
async def send_schedule(message: types.Message): async def send_schedule(message: types.Message):
"""Отправка расписания""" """Отправка расписания"""
+26 -6
View File
@@ -1,6 +1,7 @@
from collections import Counter from collections import Counter
import re import re
from datetime import datetime from datetime import datetime
import statistics
import tempfile import tempfile
import json import json
@@ -198,7 +199,7 @@ def calculate_schedule_success_rate(stats):
def create_statistics_text(stats): def create_statistics_text(stats):
"""Создает текстовый отчет статистики""" """Создает текстовый отчет статистики с расширенными метриками"""
if 'error' in stats: if 'error' in stats:
return f"❌ Ошибка анализа логов: {stats['error']}" return f"❌ Ошибка анализа логов: {stats['error']}"
@@ -212,15 +213,28 @@ def create_statistics_text(stats):
text += f"• Строк в логе: {stats['total_lines']:,}\n\n" 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 += "⚡ ПРОИЗВОДИТЕЛЬНОСТЬ:\n"
text += f"• Среднее время: {stats['performance']['avg_handling_time']:.0f} мс\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['performance']['handling_count']}\n"
text += f"• Успешных операций: {stats['success_rate']}%\n\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 += "🔄 СТАТУС РАБОТЫ:\n"
text += f"• Перезапусков: {stats['restarts']}\n" text += f"• Перезапусков: {restarts}\n"
text += f"• Uptime: {stats['uptime_percentage']}%\n\n" text += f"• Uptime: {stats['uptime_percentage']}%\n"
text += f"• MTBF (среднее время между перезапусками): {mtbf} ч\n"
text += f"• Ошибок в час: {errors_per_hour}\n\n"
# Расписание # Расписание
text += "📅 РАСПИСАНИЕ:\n" text += "📅 РАСПИСАНИЕ:\n"
@@ -231,17 +245,23 @@ def create_statistics_text(stats):
# Ошибки # Ошибки
text += "🚨 ОШИБКИ:\n" text += "🚨 ОШИБКИ:\n"
text += f"• Всего ошибок: {sum(stats['errors'].values())}\n" text += f"• Всего ошибок: {errors_total}\n"
text += f"• Предупреждений: {sum(stats['warnings'].values())}\n" text += f"• Предупреждений: {sum(stats['warnings'].values())}\n"
text += f"• Сетевых: {stats['network_errors']}\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 += "👥 АКТИВНОСТЬ:\n"
text += f"• Команд: {sum(stats['user_commands'].values())}\n" text += f"• Команд: {sum(stats['user_commands'].values())}\n"
text += f"• Групп: {len(stats['groups'])}\n" text += f"• Групп: {len(stats['groups'])}\n"
# Топ групп
if stats['groups']: if stats['groups']:
text += "• Топ групп:\n" text += "• Топ групп:\n"
for group, count in stats['groups'].most_common(3): for group, count in stats['groups'].most_common(3):
+48 -9
View File
@@ -1,27 +1,66 @@
import asyncio import asyncio
import os
async def get_macbook_battery_level(): async def get_macbook_battery_level():
"""
Асинхронная функция для получения уровня заряда батареи на macOS.
Использует утилиту pmset.
"""
# Запускаем команду pmset -g batt
process = await asyncio.create_subprocess_exec( process = await asyncio.create_subprocess_exec(
"pmset", "-g", "batt", "pmset", "-g", "batt",
stdout=asyncio.subprocess.PIPE, stdout=asyncio.subprocess.PIPE,
stderr=asyncio.subprocess.PIPE stderr=asyncio.subprocess.PIPE
) )
stdout, stderr = await process.communicate() stdout, stderr = await process.communicate()
if process.returncode != 0: if process.returncode != 0:
raise RuntimeError(f"Ошибка при выполнении pmset: {stderr.decode().strip()}") raise RuntimeError(f"Ошибка при выполнении pmset: {stderr.decode().strip()}")
output = stdout.decode().strip() output = stdout.decode().strip()
# Пример строки: " - InternalBattery-0 (id=1234567) 85%; charging; ..."
for part in output.splitlines(): for part in output.splitlines():
if "%" in part: if "%" in part:
percent_str = part.split("\t")[-1].split(";")[0].strip() percent_str = part.split("\t")[-1].split(";")[0].strip()
return int(percent_str.replace("%", "")) return int(percent_str.replace("%", ""))
raise ValueError("Не удалось определить уровень заряда")
raise ValueError("Не удалось определить уровень заряда")
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())