2.0 Да я ебал это все рассписывать но тут типо новая система логгирования

This commit is contained in:
Verum
2025-03-12 06:44:14 +07:00
parent 0704b2600f
commit 142753dc81
48 changed files with 634 additions and 239 deletions

View File

@@ -4,7 +4,38 @@
# Экспортирование модулей во внешние слои проекта
from .analytics import *
from .loggers import *
from .samples import *
from .system import *
from .timer import *
from .validators import *
from .samples import *
from SQLite3 import create_user_db
from ProjectsFiles import Permissions
# Функция установки
async def setup():
# Запуск логеров
await setup_logger()
# Получение информации о боте
await bot_get_info()
# Вывод сообщение о запуске
Logs.start(text=f"Начало запуска бота @{BotInfo.username}...")
Logs.console()
# Автоматическое создание базы данных при отсутствии
await create_user_db()
# Создание пустых директорий
await setup_directories()
# Нужно ли удалить веб-хук
if Permissions.delete_webhook:
await bot.delete_webhook()
await set_adm_rights()
await set_bot_name()
await set_bot_description()
await set_bot_short_description()

View File

@@ -1,10 +1,13 @@
# BotLibrary/analytics/type_msg.py
# Проверяет тип сообщения
from aiogram.types import ContentType, Message
# Настройка экспорта из модуля
__all__ = ("types_message",)
__all__ = ("type_msg",)
# Функция определения типа сообщения
def types_message(message: Message) -> str:
def type_msg(message: Message) -> str:
"""
Функция для определения типа сообщения на основе его содержимого.
@@ -68,7 +71,7 @@ def types_message(message: Message) -> str:
ContentType.GROUP_CHAT_CREATED: "Создание группового чата",
ContentType.SUPERGROUP_CHAT_CREATED: "Создание супергруппы",
ContentType.CHANNEL_CHAT_CREATED: "Создание канала",
ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED: "Изменение таймера автоудаления сообщения",
ContentType.MESSAGE_AUTO_DELETE_TIMER_CHANGED: "Изменение таймера авто-удаления сообщения",
}
# Получение типа сообщения

View File

@@ -1,8 +1,6 @@
# BotLibrary/loggers/__init__.py
# Инициализация пакета loggers, для создания логеров
# Инициализация под-пакета loggers, для настройки логеров
# Экспортирование модулей во внешние слои проекта
from .logs import *
from .msg_logger import *
from .custom_loggers import *
from .start_info_out import *

View File

@@ -1,10 +1,17 @@
# BotLibrary/loggers/custom_loggers.py
# Кастомные логгеры для проекта, с более стандартизированным использованием
from time import sleep
from colorama import Fore
from loguru import logger
from ..validators import username
from aiogram.types import Message
from BotLibrary.system.bots import BotInfo
from BotLibrary.validators.username import username
from BotLibrary.analytics.type_msg import type_msg
from ProjectsFiles import BotLogs, Permissions, ProjectPath, BotVar, bot_owner
# Настройка экспорта из модуля
__all__ = ("Logs",)
@@ -13,8 +20,10 @@ class Logs:
"""Класс для логирования с разными уровнями через loguru."""
@staticmethod
def start(text: str = "Логирование!", system: str = "PRIMO",
log_type: str = "AEP", user: str = "@Console") -> None:
def start(text: str = "Логирование!",
system: str = "PRIMO",
log_type: str = "AEP",
user: str = "@Console") -> None:
"""
Логирует сообщение на уровне DEBUG.
@@ -22,12 +31,18 @@ class Logs:
:param text: Сообщение для логирования.
:param log_type: Тип лога (например, "Logs").
:param user: Имя пользователя или источник вызова лога.
:return: Вывод сообщения об старте бота
"""
logger.bind(system=system, user=user, log_type=log_type).log("START", text)
@staticmethod
def debug(text: str = "Логирование!", system : str = "DEBUG",
log_type: str = "Logs", user: str = "@Console", message: Message = None) -> None:
def debug(text: str = "Логирование!",
system: str = "DEBUG",
log_type: str = "Logs",
user: str = "@Console",
message: Message = None) -> None:
"""
Логирует сообщение на уровне DEBUG.
@@ -36,14 +51,20 @@ class Logs:
:param log_type: Тип лога (например, "Logs").
:param user: Имя пользователя или источник вызова лога.
:param message: Сообщение от пользователя, если необходимо извлечь имя.
:return: Вывод сообщения об дебаг-информации
"""
if message:
user = username(message)
logger.bind(system=system, log_type=log_type, user=user).debug(text)
@staticmethod
def info(text: str = "Логирование!", system : str = "PRIMO",
log_type: str = "Logs", user: str = "@Console", message: Message = None) -> None:
def info(text: str = "Логирование!",
system: str = "PRIMO",
log_type: str = "Logs",
user: str = "@Console",
message: Message = None) -> None:
"""
Логирует сообщение на уровне INFO.
@@ -52,14 +73,20 @@ class Logs:
:param log_type: Тип лога (например, "Logs").
:param user: Имя пользователя или источник вызова лога.
:param message: Сообщение от пользователя, если необходимо извлечь имя.
:return: Вывод сообщения об некой информации
"""
if message:
user = username(message)
logger.bind(system=system, log_type=log_type, user=user).info(text)
@staticmethod
def warning(text: str = "Логирование!", system : str = "WARNING",
log_type: str = "Logs", user: str = "@Console", message: Message = None) -> None:
def warning(text: str = "Логирование!",
system: str = "WARNING",
log_type: str = "Logs",
user: str = "@Console",
message: Message = None) -> None:
"""
Логирует сообщение на уровне WARNING.
@@ -68,14 +95,20 @@ class Logs:
:param log_type: Тип лога (например, "Logs").
:param user: Имя пользователя или источник вызова лога.
:param message: Сообщение от пользователя, если необходимо извлечь имя.
:return: Вывод сообщения об предупреждении
"""
if message:
user = username(message)
logger.bind(system=system, log_type=log_type, user=user).warning(text)
@staticmethod
def error(text: str = "Логирование!", system : str = "ERROR",
log_type: str = "Logs", user: str = "@Console", message: Message = None) -> None:
def error(text: str = "Логирование!",
system: str = "ERROR",
log_type: str = "Logs",
user: str = "@Console",
message: Message = None) -> None:
"""
Логирует сообщение на уровне ERROR.
@@ -84,7 +117,80 @@ class Logs:
:param log_type: Тип лога (например, "Logs").
:param user: Имя пользователя или источник вызова лога.
:param message: Сообщение от пользователя, если необходимо извлечь имя.
:return: Вывод сообщения об ошибке
"""
if message:
user = username(message)
logger.bind(system=system, log_type=log_type, user=user).error(text)
@staticmethod
def msg(message: Message,
log_type: str = "Message",
permission: bool = BotLogs.permission) -> None:
"""
Логирует сообщение, если оно не обработано.
:param message: Сообщение от пользователя
:param log_type: Тип лога (по умолчанию "Message")
:param permission: Разрешение на логирование (config)
:return: Вывод сообщения об обычном сообщении пользователя
"""
# Получаем username или id пользователя
user: str = f"@{message.from_user.username or message.from_user.id}"
msg_type: str = type_msg(message)
# Логирование только если разрешено
if permission:
# Проверка на наличие текста и его типа
if message.text is None and msg_type not in ("Новые участники чата", "Ушедший участник чата"):
Logs.info(log_type=log_type, user=user, text=f"Получено сообщение из ({message.chat.id}) : {msg_type}")
elif message.text is not None:
Logs.info(log_type=log_type, user=user,
text=f"Получено сообщение из ({message.chat.id}) : {message.text}")
@staticmethod
def console(stop_time: int = 1,
console: bool = Permissions.start_info_console,
file: bool = Permissions.start_info_to_file,
path: str = ProjectPath.bot_info_log_file,) -> None:
"""
Собирает информацию о боте и выводит её в консоль, а также возвращает как строку.
:param stop_time: Количество времени в секундах, после которых выведется информация (1 сек)
:param console: Разрешение на внесение информации в консоль (config)
:param file: Разрешение на внесение информации в файл (config)
:param path: Путь до файла для сохранения информации о боте (config)
:return: Информация о боте в виде строки.
"""
# Собираем данные о боте
bot_name: str = f"Основное имя: {BotInfo.first_name}\n"
bot_post_name: str = f"Владельцы бота: {bot_owner}\n"
bot_username: str = f"Юзернейм: @{BotInfo.username}\n"
bot_id: str = f"ID: {BotInfo.id}\n"
bot_can_join_groups: str = f"Может ли вступать в группы: {BotInfo.can_join_groups}\n"
bot_can_read_all_group_messages: str = f"Чтение всех сообщений: {BotInfo.can_read_all_group_messages}\n"
bot_supports_inline_queries: str = f"Поддерживает инлайн-запросы: {BotInfo.supports_inline_queries}\n"
bot_can_connect_to_business: str = f"Подключение к бизнес-аккаунтам: {BotInfo.can_connect_to_business}\n"
bot_has_main_web_app: str = f"Основное веб-приложение: {BotInfo.has_main_web_app}\n"
# Формируем полный текст с выводом информации о боте
bot_all_info: str = (f"{bot_name} {bot_post_name} {bot_username} {bot_id} "
f"{bot_can_join_groups} {bot_can_read_all_group_messages} "
f"{bot_supports_inline_queries} {bot_can_connect_to_business} "
f"{bot_has_main_web_app}")
# Печатаем всю информацию в консоль с задержкой
if console:
sleep(stop_time)
print(Fore.CYAN + bot_all_info)
# Печатаем всю информацию в файл
if file:
# Преобразуем словарь bot_all_info в строку и записываем в файл
with open(path, 'w', encoding=BotVar.encod) as file:
file.write(str(bot_all_info))

View File

@@ -1,33 +1,45 @@
# BotLibrary/system/logs.py
# BotLibrary/loggers/logs.py
# Создание логгеров и их шаблон для проекта
import sys
from loguru import logger
from ProjectsFiles import BotLogs, ProjectPath
# Настройка экспорта из модуля
__all__ = ("setup_logger",)
# Создание обычного логгера + логгер в файл
async def setup_logger() -> None:
async def setup_logger(logging: bool = BotLogs.permission,
to_file: bool = BotLogs.permission_to_file) -> None:
"""
Настройка логгеров для проекта, выводящих логи в консоль.
Логгеры конфигурируются в зависимости от настроек в BotLogs.
Настройка логгеров для проекта, выводящих логи в консоль и файлы.
Логгеры конфигурируются в зависимости от настроек в конфигах проекта.
Если разрешено логирование, добавляются логи для уровней DEBUG, INFO, WARNING, ERROR.
"""
logger.remove() # Удаляем все логгеры
И кастомные такие, как START, NEW_USER, LEAVE_USER
if BotLogs.permission and BotLogs.permission_to_file:
:param logging: Разрешение на логирование в консоль (config)
:param to_file: Разрешение на логирование в файл (config)
:return: Создание логеров под различные уровни
"""
logger.remove() # Удаляем все стандартные логгеры
# Если есть разрешение, то он создает новые уровни
if logging and BotLogs.permission_to_file:
# Добавляем новый уровень START
logger.level("START", no=25, color="white", icon="🔸")
if BotLogs.permission and BotLogs.permission_new_user:
if logging and BotLogs.permission_new_user:
# Добавляем новый уровень NEW_USER
logger.level("NEW_USER", no=4, color="white", icon="👋")
if BotLogs.permission and BotLogs.permission_leave_user:
if logging and BotLogs.permission_leave_user:
# Добавляем новый уровень LEAVE_USER
logger.level("LEAVE_USER", no=3, color="white", icon="🫰")
# Настройка логирования в консоль для каждого уровня
if BotLogs.permission:
if logging:
logger.add(sys.stderr,
colorize=True,
format=BotLogs.start_text,
@@ -55,8 +67,9 @@ async def setup_logger() -> None:
level="ERROR",
filter=lambda record: record["level"].name == "ERROR")
# Добавление логгера для записи в файл
if BotLogs.permission_to_file:
if to_file:
logger.add(ProjectPath.start_log_file,
rotation=BotLogs.max_size,
format=BotLogs.start_text,

View File

@@ -1,32 +0,0 @@
# BotLibrary/loggers/msg_logger.py
# Логгер для всех не обработанных сообщений
from ProjectsFiles import BotLogs
from .custom_loggers import Logs
from ..analytics.type_msg import types_message
from aiogram.types import Message
# Настройка экспорта из модуля
__all__ = ("logger_msg",)
# Создание функции логирования на обычные сообщения
async def logger_msg(message: Message, log_type: str = "Message") -> None:
"""
Логирует сообщение, если оно не обработано.
:param message: Сообщение от пользователя.
:param log_type: Тип лога (по умолчанию "Message").
"""
# Получаем username или id пользователя
user: str = f"@{message.from_user.username or message.from_user.id}"
msg_type = types_message(message)
# Логирование только если разрешено
if BotLogs.permission:
# Проверка на наличие текста и его типа
if message.text is None and msg_type not in ("Новые участники чата", "Ушедший участник чата"):
Logs.info(log_type=log_type, user=user, text=f"Получено сообщение из ({message.chat.id}) : {msg_type}")
elif message.text is not None:
Logs.info(log_type=log_type, user=user, text=f"Получено сообщение из ({message.chat.id}) : {message.text}")
else:
return

View File

@@ -1,48 +0,0 @@
# BotLibrary/loggers/start_info_out.py
# Вывод данных бота в консоль для начальной проверки
from time import sleep
from colorama import Fore
from ProjectsFiles import Permissions, ProjectPath, BotVar, bot_owner
from .custom_loggers import Logs
from ..system import BotInfo
# Функция для получения информации о боте и выводе ее в консоль и файл
def bot_info_out() -> str:
"""
Собирает информацию о боте и выводит её в консоль, а также возвращает как строку.
:return: Информация о боте в виде строки.
"""
try:
# Собираем данные о боте
bot_name: str = f"Основное имя: {BotInfo.first_name}\n"
bot_post_name: str = f"Владельцы бота: {bot_owner}\n"
bot_username: str = f"Юзернейм: @{BotInfo.username}\n"
bot_id: str = f"ID: {BotInfo.id}\n"
bot_can_join_groups: str = f"Может ли вступать в группы: {BotInfo.can_join_groups}\n"
bot_can_read_all_group_messages: str = f"Чтение всех сообщений: {BotInfo.can_read_all_group_messages}\n"
bot_supports_inline_queries: str = f"Поддерживает инлайн-запросы: {BotInfo.supports_inline_queries}\n"
bot_can_connect_to_business: str = f"Подключение к бизнес-аккаунтам: {BotInfo.can_connect_to_business}\n"
bot_has_main_web_app: str = f"Основное веб-приложение: {BotInfo.has_main_web_app}\n"
# Формируем полный текст с выводом информации о боте
bot_all_info: str = (f"{bot_name} {bot_post_name} {bot_username} {bot_id} "
f"{bot_can_join_groups} {bot_can_read_all_group_messages} "
f"{bot_supports_inline_queries} {bot_can_connect_to_business} "
f"{bot_has_main_web_app}")
# Печатаем все данные в консоль с задержкой в 1 секунду
sleep(1)
if Permissions.start_info_console:
print(Fore.CYAN + bot_all_info)
if Permissions.start_info_to_file:
# Преобразуем словарь bot_all_info в строку и записываем в файл
with open(ProjectPath.bot_info_log_file, 'w', encoding=BotVar.encod) as file:
file.write(str(bot_all_info))
return bot_all_info
except Exception as e:
Logs.error(log_type="SYS", user="Start_INFO", text=f"Ошибка при получении ID пользователя: {e}")

View File

@@ -1,3 +1,6 @@
# BotLibrary/samples/inline_kb_sample.py
# Шаблон для создания инлайн клавиатур
from aiogram.types import InlineKeyboardMarkup, ReplyKeyboardRemove
from aiogram.utils.keyboard import InlineKeyboardBuilder
from typing import List, Tuple, Optional

View File

@@ -1,5 +1,5 @@
# BotCode/keyboards/reply_kb/base_reply_kb.py
# Базовый класс для создания reply-клавиатур с расширенными возможностями и поддержкой row_width
# Базовый класс для создания reply-клавиатур
from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, KeyboardButtonPollType, WebAppInfo, KeyboardButtonRequestUsers, KeyboardButtonRequestChat, KeyboardButtonRequestUser
from aiogram.utils.keyboard import ReplyKeyboardBuilder

View File

@@ -8,7 +8,8 @@ from aiogram.filters import Command
from aiogram.types import InputMediaPhoto, InputMediaVideo, InputMediaDocument
from typing import Optional, Callable
from BotLibrary import Logs, valid_url, username
from ..loggers import Logs
from ..validators import username, valid_url
from ProjectsFiles import BotVar
from SQLite3 import base_sql
@@ -19,7 +20,7 @@ class CommandHandler:
def __init__(self, name: str, keywords: list, func: Optional[list[Callable]] = None, text_msg=None, chat_action: bool = False,
description: str = "Описание команды", tg_links: bool = False,
keyboard=None, prefix=BotVar.prefix, callbackdata: list = None, only_admin: bool = False,
ignore_case: bool = True, activate_keywoards: bool = True,
ignore_case: bool = True, activate_keywords: bool = True, delete_msg: bool = False,
activate_commands: bool = True, activate_callback: bool = True,
media: str = "message", path_to_media=None, parse_mode: str = BotVar.parse_mode,
disable_notification: bool = BotVar.disable_notification, protect: bool = BotVar.protect_content):
@@ -27,6 +28,7 @@ class CommandHandler:
self.name = name
self.log_type = name.capitalize()
self.description = description
self.last_bot_message = {} # {chat_id: message_id}
self.keywords = keywords
self.text_msg = text_msg
@@ -39,6 +41,7 @@ class CommandHandler:
self.protect = protect
self.only_admin = only_admin
self.func = func
self.delete_msg = delete_msg
# Поддержка до 10 медиафайлов через список
if path_to_media is None:
@@ -49,25 +52,30 @@ class CommandHandler:
self.path_to_media = path_to_media[:10] # Ограничение до 10 элементов
self.tg_links = tg_links
if callbackdata == "keywords":
self.callbackdata = keywords
else:
if callbackdata:
self.callbackdata = callbackdata
else:
self.callbackdata = keywords
# Привязываем хэндлер к роутеру
if activate_commands:
self.router.message(Command(*keywords, prefix=prefix, ignore_case=ignore_case))(self.handler)
if activate_keywoards:
if activate_keywords:
self.router.message(F.text.lower().in_(keywords))(self.handler)
if activate_callback and self.callbackdata:
self.router.message(F.text.lower().in_(self.callbackdata))(self.handler)
self.router.callback_query(F.data.in_(self.callbackdata))(self.callback_handler)
async def callback_handler(self, callback: types.CallbackQuery):
"""Обработчик callback-запросов."""
await self.handler(callback.message) # Передаем сообщение в основном обработчике
await callback.answer() # Закрываем callback-запрос
async def handler(self, message: types.Message):
"""Основной хэндлер команды."""
try:
# Извлекаем текст после команды
command_text = message.text[len(message.text.split()[0]):].strip() # Убираем команду из текста
args = command_text.split() # Разделяем команду на аргументы
command_text = (message.text or "").strip() # Защита от NoneType
args = command_text.split() if command_text else [] # Если текст есть — разделяем, иначе пустой список
# Проверка на выполнение дополнительной функции (если она есть)
if self.func: # Проверяем, что функция не None
@@ -115,14 +123,16 @@ class CommandHandler:
if self.tg_links:
text = text.replace("<users>", str(message.from_user.id))
sent_msg = None
if self.media == "message":
await message.reply(
sent_msg = await message.reply(
text=text,
reply_markup=self.keyboard() if self.keyboard else None,
parse_mode=self.parse_mode,
disable_notification=self.disable_notification,
protect_content=self.protect,
)
self.last_bot_message[message.chat.id] = sent_msg.message_id
if self.chat_action:
await message.bot.send_chat_action(
chat_id=message.chat.id,
@@ -142,7 +152,7 @@ class CommandHandler:
media_group[-1].caption = text
media_group[-1].parse_mode = self.parse_mode
await message.reply_media_group(
sent_msg = await message.reply_media_group(
media=media_group,
disable_notification=self.disable_notification,
protect_content=self.protect,
@@ -173,7 +183,7 @@ class CommandHandler:
media_group[-1].caption = text
media_group[-1].parse_mode = self.parse_mode
await message.reply_media_group(
sent_msg = await message.reply_media_group(
media=media_group,
disable_notification=self.disable_notification,
protect_content=self.protect,
@@ -204,7 +214,7 @@ class CommandHandler:
media_group[-1].caption = text
media_group[-1].parse_mode = self.parse_mode
await message.reply_media_group(
sent_msg = await message.reply_media_group(
media=media_group,
disable_notification=self.disable_notification,
protect_content=self.protect,
@@ -229,7 +239,7 @@ class CommandHandler:
if self.media == "photo":
if url:
await message.reply_photo(
sent_msg = await message.reply_photo(
photo=media_path,
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -238,7 +248,7 @@ class CommandHandler:
protect_content=self.protect,
)
else:
await message.reply_photo(
sent_msg = await message.reply_photo(
photo=types.FSInputFile(path=media_path),
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -254,7 +264,7 @@ class CommandHandler:
elif self.media == "gif":
if url:
await message.reply_animation(
sent_msg = await message.reply_animation(
animation=media_path,
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -263,7 +273,7 @@ class CommandHandler:
protect_content=self.protect,
)
else:
await message.reply_animation(
sent_msg = await message.reply_animation(
animation=types.FSInputFile(path=media_path),
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -279,7 +289,7 @@ class CommandHandler:
elif self.media == "video":
if url:
await message.reply_video(
sent_msg = await message.reply_video(
video=media_path,
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -288,7 +298,7 @@ class CommandHandler:
protect_content=self.protect,
)
else:
await message.reply_video(
sent_msg = await message.reply_video(
video=types.FSInputFile(path=media_path),
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -304,7 +314,7 @@ class CommandHandler:
elif self.media == "videonote":
if url:
await message.reply_video_note(
sent_msg = await message.reply_video_note(
video_note=media_path,
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -313,7 +323,7 @@ class CommandHandler:
protect_content=self.protect,
)
else:
await message.reply_video_note(
sent_msg = await message.reply_video_note(
video_note=types.FSInputFile(path=media_path),
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -329,7 +339,7 @@ class CommandHandler:
elif self.media == "audio":
if url:
await message.reply_audio(
sent_msg = await message.reply_audio(
audio=media_path,
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -338,7 +348,7 @@ class CommandHandler:
protect_content=self.protect,
)
else:
await message.reply_audio(
sent_msg = await message.reply_audio(
audio=types.FSInputFile(path=media_path),
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -354,7 +364,7 @@ class CommandHandler:
elif self.media == "file":
if url:
await message.reply_document(
sent_msg = await message.reply_document(
document=media_path,
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -363,7 +373,7 @@ class CommandHandler:
protect_content=self.protect,
)
else:
await message.reply_document(
sent_msg = await message.reply_document(
document=types.FSInputFile(path=media_path),
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -378,7 +388,7 @@ class CommandHandler:
)
elif self.media == "dice":
await message.reply_dice(
sent_msg = await message.reply_dice(
emoji="🎲", # Эмодзи кубика как стандартное значение, если нет URL
caption=text if is_last else None,
reply_markup=self.keyboard() if is_last and self.keyboard else None,
@@ -392,6 +402,16 @@ class CommandHandler:
action=ChatAction.CHOOSE_STICKER,
)
# Сохраняем идентификатор последнего сообщения, если необходимо
if sent_msg:
self.last_bot_message[message.chat.id] = sent_msg.message_id
if self.delete_msg:
await message.bot.delete_message(
chat_id=message.chat.id,
message_id=message.message_id
)
# Проверка на ошибку
except Exception as e:
Logs.error(log_type=self.log_type, user=username(message), text=f"Ошибка команды: {e}")

View File

@@ -1,33 +1,22 @@
# BotLibrary/system/edit_bot.py
# Библиотека установки настроек бота через проект и конфиги
# BotLibrary/system/bot_edit.py
# Под-пакет установки настроек бота
from aiogram.types import ChatAdministratorRights
from ProjectsFiles import BotEdit
from .bots import bot
from ..loggers import Logs
# Настройка логирования
log_type = "Edit"
# Функция для выполнения всех настроек, если они не совпадают
async def set_all() -> None:
"""
Выполняет все необходимые настройки бота, если они не совпадают с текущими значениями.
:return: None
"""
await set_adm_rights()
await set_bot_name()
await set_bot_description()
await set_bot_short_description()
# Настройка экспорта из модуля
__all__ = ("set_adm_rights", "set_bot_name", "set_bot_description", "set_bot_short_description")
# Функция установки прав администратора
async def set_adm_rights() -> None:
"""
Устанавливает права администратора для бота, если они отличаются от текущих.
:return: None
:return: Изменение прав администратора
"""
rights = ChatAdministratorRights(
is_anonymous=BotEdit.is_anonymous,
@@ -54,57 +43,72 @@ async def set_adm_rights() -> None:
# Функция установки имени бота с проверкой на ограничения
async def set_bot_name() -> None:
async def set_bot_name(new_name: str = BotEdit.name) -> None:
"""
Устанавливает имя бота, если оно отличается от текущего и соответствует ограничениям.
:return: None
:param new_name: Новое имя бота (config)
:return: Имя бота
"""
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
# Получаем текущее имя бота
current_name = (await bot.get_me()).first_name
# Проверка длины имени
if len(BotEdit.name) < 1 or len(BotEdit.name) > 32:
if len(new_name) < 1 or len(new_name) > 32:
Logs.error(log_type=log_type, user="NAME_BOT", text="Имя бота должно быть от 1 до 32 символов.")
return # Выходим из функции, если имя некорректно
# Проверяем, совпадает ли текущее имя с тем, которое мы хотим установить
if current_name != BotEdit.name:
await bot.set_my_name(BotEdit.name)
if current_name != new_name:
await bot.set_my_name(new_name)
# Функция установки описания бота с проверкой на ограничения
async def set_bot_description() -> None:
async def set_bot_description(new_description: str = BotEdit.description) -> None:
"""
Устанавливает описание бота, если оно отличается от текущего и соответствует ограничениям.
:return: None
:param new_description: Новое описание для бота (config)
:return: Описание бота
"""
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
# Получаем текущее описание бота
current_description = await bot.get_my_description()
# Проверка длины описания
if len(BotEdit.description) > 255:
Logs.error(log_type=log_type, user="DISCRIPT", text="Короткое описание бота не может превышать 255 символов.")
if len(new_description) > 255 or len(new_description)==0:
Logs.error(log_type=log_type, user="DISCRIPT", text="Короткое описание бота не может превышать 255 символов или быть равно 0.")
return # Выходим из функции, если описание некорректно
# Проверяем, совпадает ли текущее описание с тем, которое мы хотим установить
if current_description != BotEdit.description:
await bot.set_my_description(description=BotEdit.description)
if current_description != new_description:
await bot.set_my_description(description=new_description)
# Функция установки короткого описания бота с проверкой на ограничения
async def set_bot_short_description() -> None:
async def set_bot_short_description(new_short_description: str = BotEdit.short_description) -> None:
"""
Устанавливает короткое описание бота, если оно отличается от текущего и соответствует ограничениям.
:return: None
:param new_short_description: Новое описание виджета для бота (config)
:return: Короткое описание бота
"""
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
# Получаем текущее короткое описание бота
current_short_description = await bot.get_my_short_description()
# Проверка длины короткого описания
if len(BotEdit.short_description) > 512:
Logs.error(log_type=log_type, user="SHORT_DISCRIPT", text="Описание виджета не может превышать 512 символов.")
if len(new_short_description) > 512 or len(new_short_description) == 0:
Logs.error(log_type=log_type, user="SHORT_DISCRIPT", text="Описание виджета не может превышать 512 символов или быть равно 0.")
return # Выходим из функции, если короткое описание некорректно
# Проверяем, совпадает ли текущее короткое описание с тем, которое мы хотим установить
if current_short_description != BotEdit.short_description:
await bot.set_my_short_description(short_description=BotEdit.short_description)
if current_short_description != new_short_description:
await bot.set_my_short_description(short_description=new_short_description)

View File

@@ -5,7 +5,7 @@ from aiogram import Dispatcher, Bot, F
from aiogram.client.default import DefaultBotProperties
from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
from ..timer import get_host_time, get_moscow_time
from ..timer import get_host_time, get_city_time
from ProjectsFiles import bot_token, BotVar
@@ -16,7 +16,7 @@ ikb = InlineKeyboardBuilder()
# Настройка параметров диспатчера
dp = Dispatcher()
dp["started_at"] = get_host_time()
dp["started_at_msk"] = get_moscow_time()
dp["started_at_msk"] = get_city_time()
dp["is_active"] = True # Флаг активности бота
dp["logs"] = []
dp["users"] = {}

View File

@@ -7,31 +7,33 @@ from tzlocal import get_localzone
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from ProjectsFiles import BotVar
# Настройка экспорта из этого модуля
__all__ = ("scheduler", "get_city_time", "get_host_time")
# Создание планировщика для работы с задачами по времени
scheduler = AsyncIOScheduler(timezone=get_localzone().key)
def get_moscow_time() -> str:
# Функция получение иного времени
def get_city_time(city: str = 'Europe/Moscow') -> str:
"""
Получение текущего времени по московскому времени.
Получение текущего времени по иному городскому времени.
:param city: Город, что будет вторым временем
:return: Строка, представляющая время в формате, заданном в BotVar.time_format.
"""
# Устанавливаем временную зону для Москвы
moscow_tz = pytz.timezone('Europe/Moscow')
# Получаем текущее время по московскому времени
moscow_time = datetime.now(moscow_tz)
city_tz = pytz.timezone(city)
# Возвращаем строку с форматом времени
return moscow_time.strftime(BotVar.time_format)
return datetime.now(city_tz).strftime(BotVar.time_format)
# Функция получение времени хоста
def get_host_time() -> str:
"""
Получение текущего времени хоста (локального времени).
:return: Строка, представляющая локальное время в формате, заданном в BotVar.time_format.
"""
# Получаем текущее время на хосте
host_time = datetime.now()
# Возвращаем строку с форматом времени
return host_time.strftime(BotVar.time_format)
return datetime.now().strftime(BotVar.time_format)

View File

@@ -1,24 +1,25 @@
# BotLibrary/validators/email_validators.py
# Создание валидации почты для проекта
from email_validator import validate_email, EmailNotValidError
from typing import Optional
from email_validator import validate_email, EmailNotValidError
# Настройка экспорта из этого модуля
__all__ = ("valid_email",)
# Функция проверки почты на корректность
def valid_email(text: str) -> Optional[str]:
"""
Проверяет корректность почтового адреса.
:param text: Почтовый адрес в виде строки.
# Функция проверки почты на корректность
def valid_email(email: str) -> Optional[str]:
"""
Делает почтовый адрес корректным.
:param email: Почтовый адрес в виде строки.
:return: Нормализованный почтовый адрес, если он валиден, иначе None.
"""
try:
# Проверка и нормализация email
email = validate_email(text)
return email.normalized
except EmailNotValidError:
# Если email невалиден, можно добавить логирование или обработку ошибок
return None
return validate_email(email).normalized
except EmailNotValidError as e:
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
Logs.error(text=f"Ошибка в нормализировании почты: {e}", log_type="NormalEmail")
return None

View File

@@ -1,5 +1,20 @@
# BotLibrary/validators/normal_word.py
# Нормализирует вид слова автоматически
async def normal_words(word : str = "Тестовое слово") -> str:
return word.lower().capitalize()
# Настройка экспорта из этого модуля
__all__ = ("normal_words",)
async def normal_words(word: str) -> str:
"""
Делает слово корректного вида.
:param word: Слово, которое будет приводиться к виду (Тесты).
:return: Нормализованное слово
"""
try:
return word.lower().capitalize()
except Exception as e:
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
Logs.error(text=f"Ошибка в нормализировании слова: {e}", log_type="NormalWord")
return word

View File

@@ -25,5 +25,27 @@ def valid_url(url: str) -> bool:
# Функция, что дает тексту ссылку на HTML
def url_to_text(text: str = "Тест", url: str = "www.google.com") -> str:
return f'<b><a href="{url}">{text}</a></b>'
def url_to_text(text: str, url: str) -> str:
"""
Преобразует текст в HTML ссылку с указанным URL.
Эта функция генерирует HTML-ссылку с переданным текстом и URL, используя тег `<а>`, и делает ссылку жирной.
:param text: Текст, который будет отображаться для ссылки.
:param url: URL, который будет привязан к тексту.
:return: Строка с HTML кодом для ссылки, если URL валиден.
:raises ValueError: Если URL невалиден.
"""
try:
if not valid_url(url): # Проверяем, является ли URL валидным
raise ValueError(f"Переданный URL '{url}' невалиден.")
# Генерация HTML-ссылки
return f'<b><a href="{url}">{text}</a></b>'
except ValueError as e:
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
# Логируем ошибку с использованием Logs.error, как указано
Logs.error(text=f"Ошибка при создании ссылки: {e}", log_type="InvalidURL")
raise e # Перебрасываем ошибку выше для дальнейшей обработки или уведомления

View File

@@ -6,6 +6,7 @@ from aiogram.types import Message
# Настройка экспорта из модуля
__all__ = ("username", "username_to_text")
# Функция получения юзера или ID пользователя
def username(message: Message) -> str:
"""
@@ -13,12 +14,32 @@ def username(message: Message) -> str:
:param message: Объект сообщения из aiogram.
:return: Строка с юзернеймом пользователя или его ID.
:raises ValueError: Если в сообщении отсутствует информация о пользователе.
"""
if message.from_user:
return f"@{message.from_user.username}" if message.from_user.username else f"@{message.from_user.id}"
return "@Unknown_User" # Если from_user отсутствует
try:
if message.from_user:
return f"@{message.from_user.username}" if message.from_user.username else f"@{message.from_user.id}"
raise ValueError("Информация о пользователе отсутствует в сообщении.")
except ValueError as e:
# Логируем ошибку с использованием Logs.error
raise e # Перебрасываем ошибку выше для дальнейшей обработки
# Функция получение имени пользователя + ссылка на него
def username_to_text(message: Message) -> str:
return f'<b><a href="tg://user?id={message.from_user.id}">{message.from_user.full_name}</a></b>'
"""
Преобразует информацию о пользователе в строку с HTML-ссылкой.
:param message: Объект сообщения из aiogram.
:return: Строка с HTML-кодом для ссылки на пользователя.
:raises ValueError: Если в сообщении отсутствует информация о пользователе.
"""
try:
if message.from_user:
return f'<b><a href="tg://user?id={message.from_user.id}">{message.from_user.full_name}</a></b>'
raise ValueError("Информация о пользователе отсутствует в сообщении.")
except ValueError as e:
# Логируем ошибку с использованием Logs.error
raise e # Перебрасываем ошибку выше для дальнейшей обработки