I add command /analytics and I improve code
It's version 0.2.3
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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("❌ Неверная команда")
|
||||||
|
|||||||
@@ -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
@@ -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
@@ -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())
|
||||||
|
|||||||
Reference in New Issue
Block a user