3.0 Выпуск в PrimoRU

This commit is contained in:
Verum
2025-04-13 06:50:23 +07:00
parent b8f7ce5b2a
commit 17d10fbf78
51 changed files with 1191 additions and 1611 deletions

View File

@@ -5,37 +5,7 @@
from .analytics import *
from .loggers import *
from .samples import *
from .sql import *
from .system import *
from .timer import *
from .validators 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,25 +1,23 @@
# BotLibrary/analytics/type_chat.py
# Определение типа чата
from aiogram import types
from aiogram.types import Message
# Настройка экспорта в модули
__all__ = ("type_chat",)
# Проверка на тип чата
async def type_chat(message: types.Message) -> str:
async def type_chat(message: Message) -> str:
"""
Преобразует информацию о чате в понятные значения.
Преобразует информацию о чате в его тип на русском языке.
:param message: Объект сообщения из aiogram.
:param message: Объект сообщения из aiogram, содержащий информацию о чате.
:return: Тип чата строкой.
"""
chat_type: str = message.chat.type
if chat_type == "private":
return "Личный"
elif chat_type == "group" or chat_type == "supergroup":
return "Группа"
elif chat_type == "channel":
return "Канал"
else:
return "Неизвестный тип чата."
chat_types: dict[str, str] = {
"private": "Личный",
"group": "Группа",
"supergroup": "Группа",
"channel": "Канал",
}
return chat_types.get(message.chat.type, "Неизвестный тип чата")

View File

@@ -6,7 +6,6 @@ from aiogram.types import ContentType, Message
# Настройка экспорта из модуля
__all__ = ("type_msg",)
# Функция определения типа сообщения
def type_msg(message: Message) -> str:
"""
Функция для определения типа сообщения на основе его содержимого.
@@ -50,7 +49,7 @@ def type_msg(message: Message) -> str:
ContentType.INVOICE: "Счет",
ContentType.SUCCESSFUL_PAYMENT: "Успешный платеж",
ContentType.REFUNDED_PAYMENT: "Возврат платежа",
ContentType.USERS_SHARED: "Пользователи поделились",
ContentType.USERS_SHARED: "Пользователь поделился",
ContentType.CHAT_SHARED: "Чат был передан",
ContentType.CONNECTED_WEBSITE: "Подключенный веб-сайт",
ContentType.WRITE_ACCESS_ALLOWED: "Разрешение на запись",
@@ -77,9 +76,20 @@ def type_msg(message: Message) -> str:
# Получение типа сообщения
message_type: str = message.content_type
# Если это контакт, добавляем номер телефона
if message_type == ContentType.CONTACT and message.contact:
return f"{content_types.get(message_type, 'Контакт')}: {message.contact.phone_number}"
# Если это пользователи, добавляем их ID
if message_type == ContentType.USERS_SHARED and message.users_shared:
user_ids = ", ".join(map(str, message.users_shared.user_ids))
return f"{content_types.get(message_type, 'Пользователь поделился')}: {user_ids}"
# Если это переданный чат, добавляем его ID
if message_type == ContentType.CHAT_SHARED and message.chat_shared:
return f"{content_types.get(message_type, 'Чат был передан')}: {message.chat_shared.chat_id}"
# Возвращаем описание типа сообщения, если оно есть в словаре, иначе "Неизвестный тип"
return content_types.get(message_type, "Неизвестный тип")

View File

@@ -3,4 +3,3 @@
# Экспортирование модулей во внешние слои проекта
from .logs import *
from .custom_loggers import *

View File

@@ -1,207 +0,0 @@
# BotLibrary/loggers/custom_loggers.py
# Кастомные логгеры для проекта, с более стандартизированным использованием
from time import sleep
from aiogram import types
from colorama import Fore
from loguru import logger
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",)
class Logs:
"""Класс для логирования с разными уровнями через loguru."""
@staticmethod
def start(text: str = "Логирование!",
system: str = "PRIMO",
log_type: str = "AEP",
user: str = "@Console") -> None:
"""
Логирует сообщение на уровне START.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
:param log_type: Тип лога (например, "Help").
: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:
"""
Логирует сообщение на уровне DEBUG.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
:param log_type: Тип лога (например, "Help").
: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:
"""
Логирует сообщение на уровне INFO.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
: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:
"""
Логирует сообщение на уровне WARNING.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
: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:
"""
Логирует сообщение на уровне ERROR.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
: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: types.Message,
log_type: str = "Message",
user: str = None,
msg_type: str = None,
permission: bool = BotLogs.permission) -> None:
"""
Логирует сообщение, если оно не обработано.
:param message: Сообщение от пользователя.
:param log_type: Тип лога (по умолчанию "Message").
:param permission: Разрешение на логирование (config).
:param user: Получение пользователя (автоматически).
:param msg_type: Получение типа сообщения (автоматически).
.
:return: Вывод сообщения об обычном сообщении пользователя.
"""
# Получаем айди чата
chat_id = message.chat.id
# Получаем username или id пользователя
if user is None:
user: str = f"@{message.from_user.username or message.from_user.id}"
if msg_type is None:
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"Получено сообщение из ({chat_id}) : {msg_type}")
elif message.text is not None:
Logs.info(log_type=log_type, user=user,
text=f"Получено сообщение из ({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,107 +1,295 @@
# BotLibrary/loggers/logs.py
# Создание логгеров и их шаблон для проекта
# Кастомные логгеры для проекта, с более стандартизированным использованием
import sys
from loguru import logger
from ProjectsFiles import BotLogs, ProjectPath
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__ = ("setup_logger",)
# Создание обычного логгера + логгер в файл
async def setup_logger(logging: bool = BotLogs.permission,
to_file: bool = BotLogs.permission_to_file) -> None:
"""
Настройка логгеров для проекта, выводящих логи в консоль и файлы.
Логгеры конфигурируются в зависимости от настроек в конфигах проекта.
Если разрешено логирование, добавляются логи для уровней DEBUG, INFO, WARNING, ERROR.
И кастомные такие, как START, NEW_USER, LEAVE_USER
:param logging: Разрешение на логирование в консоль (config)
:param to_file: Разрешение на логирование в файл (config)
:return: Создание логеров под различные уровни
"""
logger.remove() # Удаляем все стандартные логгеры
__all__ = ("Logs",)
# Если есть разрешение, то он создает новые уровни
if logging and BotLogs.permission_to_file:
# Добавляем новый уровень START
logger.level("START", no=25, color="white", icon="🔸")
if logging and BotLogs.permission_new_user:
# Добавляем новый уровень NEW_USER
logger.level("NEW_USER", no=4, color="white", icon="👋")
if logging and BotLogs.permission_leave_user:
# Добавляем новый уровень LEAVE_USER
logger.level("LEAVE_USER", no=3, color="white", icon="🫰")
class Logs:
"""Класс для логирования с разными уровнями через loguru."""
@staticmethod
def setup(logging: bool = BotLogs.permission,
to_file: bool = BotLogs.permission_to_file) -> None:
"""
Настройка логгеров для проекта, выводящих логи в консоль и файлы.
Логгеры конфигурируются в зависимости от настроек в конфигах проекта.
Если разрешено логирование, добавляются логи для уровней DEBUG, INFO, WARNING, ERROR.
И кастомные такие, как START, NEW_USER, LEAVE_USER
: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 logging and BotLogs.permission_new_user:
# Добавляем новый уровень NEW_USER
logger.level("NEW_USER", no=4, color="white", icon="👋")
if logging and BotLogs.permission_leave_user:
# Добавляем новый уровень LEAVE_USER
logger.level("LEAVE_USER", no=3, color="white", icon="🫰")
# Настройка логирования в консоль для каждого уровня
if logging:
from sys import stderr
logger.add(stderr,
colorize=True,
format=BotLogs.start_text,
level="START",
filter=lambda record: record["level"].name == "START"
)
logger.add(stderr,
colorize=True,
format=BotLogs.debug_text,
level="DEBUG",
filter=lambda record: record["level"].name == "DEBUG")
logger.add(stderr,
colorize=True,
format=BotLogs.info_text,
level="INFO",
filter=lambda record: record["level"].name == "INFO")
logger.add(stderr,
colorize=True,
format=BotLogs.warning_text,
level="WARNING",
filter=lambda record: record["level"].name == "WARNING")
logger.add(stderr,
colorize=True,
format=BotLogs.error_text,
level="ERROR",
filter=lambda record: record["level"].name == "ERROR")
# Добавление логгера для записи в файл
if to_file:
logger.add(ProjectPath.start_log_file,
rotation=BotLogs.max_size,
format=BotLogs.start_text,
backtrace=True,
diagnose=True,
level="START",
filter=lambda record: record["level"].name == "START")
logger.add(ProjectPath.debug_log_file,
rotation=BotLogs.max_size,
format=BotLogs.debug_text,
backtrace=True,
diagnose=True,
level="DEBUG",
filter=lambda record: record["level"].name == "DEBUG")
logger.add(ProjectPath.info_log_file,
rotation=BotLogs.max_size,
format=BotLogs.info_text,
backtrace=True,
diagnose=True,
level="INFO",
filter=lambda record: record["level"].name == "INFO")
logger.add(ProjectPath.warning_log_file,
rotation=BotLogs.max_size,
format=BotLogs.warning_text,
backtrace=True,
diagnose=True,
level="WARNING",
filter=lambda record: record["level"].name == "WARNING")
logger.add(ProjectPath.error_log_file,
rotation=BotLogs.max_size,
format=BotLogs.error_text,
backtrace=True,
diagnose=True,
level="ERROR",
filter=lambda record: record["level"].name == "ERROR")
# Настройка логирования в консоль для каждого уровня
if logging:
logger.add(sys.stderr,
colorize=True,
format=BotLogs.start_text,
level="START",
filter=lambda record: record["level"].name == "START"
)
logger.add(sys.stderr,
colorize=True,
format=BotLogs.debug_text,
level="DEBUG",
filter=lambda record: record["level"].name == "DEBUG")
logger.add(sys.stderr,
colorize=True,
format=BotLogs.info_text,
level="INFO",
filter=lambda record: record["level"].name == "INFO")
logger.add(sys.stderr,
colorize=True,
format=BotLogs.warning_text,
level="WARNING",
filter=lambda record: record["level"].name == "WARNING")
logger.add(sys.stderr,
colorize=True,
format=BotLogs.error_text,
level="ERROR",
filter=lambda record: record["level"].name == "ERROR")
@staticmethod
def start(text: str = "Логирование!",
system: str = "PRIMO",
log_type: str = "AEP",
user: str = "@Console") -> None:
"""
Логирует сообщение на уровне START.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
:param log_type: Тип лога (например, "Help").
:param user: Имя пользователя или источник вызова лога.
:return: Вывод сообщения об старте бота
"""
logger.bind(system=system, user=user, log_type=log_type).log("START", text)
# Добавление логгера для записи в файл
if to_file:
logger.add(ProjectPath.start_log_file,
rotation=BotLogs.max_size,
format=BotLogs.start_text,
backtrace=True,
diagnose=True,
level="START",
filter=lambda record: record["level"].name == "START")
logger.add(ProjectPath.debug_log_file,
rotation=BotLogs.max_size,
format=BotLogs.debug_text,
backtrace=True,
diagnose=True,
level="DEBUG",
filter=lambda record: record["level"].name == "DEBUG")
logger.add(ProjectPath.info_log_file,
rotation=BotLogs.max_size,
format=BotLogs.info_text,
backtrace=True,
diagnose=True,
level="INFO",
filter=lambda record: record["level"].name == "INFO")
logger.add(ProjectPath.warning_log_file,
rotation=BotLogs.max_size,
format=BotLogs.warning_text,
backtrace=True,
diagnose=True,
level="WARNING",
filter=lambda record: record["level"].name == "WARNING")
logger.add(ProjectPath.error_log_file,
rotation=BotLogs.max_size,
format=BotLogs.error_text,
backtrace=True,
diagnose=True,
level="ERROR",
filter=lambda record: record["level"].name == "ERROR")
@staticmethod
def debug(text: str = "Логирование!",
system: str = "DEBUG",
log_type: str = "Logs",
user: str = "@Console",
message: Message = None) -> None:
"""
Логирует сообщение на уровне DEBUG.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
:param log_type: Тип лога (например, "Help").
: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:
"""
Логирует сообщение на уровне INFO.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
: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:
"""
Логирует сообщение на уровне WARNING.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
: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:
"""
Логирует сообщение на уровне ERROR.
:param text: Сообщение для логирования.
:param system: Тип системы логирования.
: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",
user: str = None,
msg_type: str = None,
permission: bool = BotLogs.permission) -> None:
"""
Логирует сообщение, если оно не обработано.
:param message: Сообщение от пользователя.
:param log_type: Тип лога (по умолчанию "Message").
:param permission: Разрешение на логирование (config).
:param user: Получение пользователя (автоматически).
:param msg_type: Получение типа сообщения (автоматически).
.
:return: Вывод сообщения об обычном сообщении пользователя.
"""
# Получаем айди чата
chat_id = message.chat.id
# Получаем username или id пользователя
if user is None:
user: str = f"@{message.from_user.username or message.from_user.id}"
if msg_type is None:
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"Получено сообщение из ({chat_id}) : {msg_type}")
elif message.text is not None:
Logs.info(log_type=log_type, user=user,
text=f"Получено сообщение из ({chat_id}) : {message.text}")
@staticmethod
def console(console: bool = Permissions.start_info_console,
file: bool = Permissions.start_info_to_file,
path: str = ProjectPath.bot_info_log_file) -> None:
"""
Собирает информацию о боте и выводит её в консоль, а также возвращает как строку.
: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:
from colorama import Fore
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,10 +1,13 @@
# BotLibrary/samples/inline_kb_sample.py
# Шаблон для создания инлайн клавиатур
from aiogram.types import InlineKeyboardMarkup, ReplyKeyboardRemove
from aiogram.types import InlineKeyboardMarkup
from aiogram.utils.keyboard import InlineKeyboardBuilder
from typing import List, Tuple, Optional
# Настройка экспорта в модули
__all__ = ("BaseInlineKeyboard",)
class BaseInlineKeyboard:
def __init__(self, buttons: List[Tuple[str, Optional[str], Optional[str]]], row_width: int = 1):
"""
@@ -17,7 +20,7 @@ class BaseInlineKeyboard:
def get_keyboard(self) -> InlineKeyboardMarkup:
"""
Создаёт инлайн-клавиатуру и возвращает её вместе с объектом для удаления reply-клавиатуры.
:return: кортеж (InlineKeyboardMarkup, ReplyKeyboardRemove)
:return: кортеж InlineKeyboardMarkup
"""
ikb = InlineKeyboardBuilder()
for text, url, callback_data in self.buttons:

View File

@@ -5,6 +5,9 @@ from aiogram.types import ReplyKeyboardMarkup, KeyboardButton, KeyboardButtonPol
from aiogram.utils.keyboard import ReplyKeyboardBuilder
from typing import List, Union, Tuple, Optional, Dict, Any
# Настройка экспорта в модули
__all__ = ("BaseReplyKeyboard",)
class BaseReplyKeyboard:
def __init__(
self,
@@ -100,4 +103,4 @@ class BaseReplyKeyboard:
buttons.append(KeyboardButton(text=button))
rkb.row(*buttons)
return rkb.as_markup(resize_keyboard=self.resize_keyboard, one_time_keyboard=self.one_time_keyboard)
return rkb.as_markup(resize_keyboard=self.resize_keyboard, one_time_keyboard=self.one_time_keyboard)

View File

@@ -11,16 +11,47 @@ from typing import Optional, Callable
from ..loggers import Logs
from ..validators import username, valid_url
from ProjectsFiles import BotVar
from SQLite3 import base_sql
from ..sql import db
# Настройки экспорта в модули
__all__ = ("CommandHandler",)
class CommandHandler:
"""
Класс для создания и управления командами Telegram-бота.
Этот класс позволяет создавать команды с различными настройками, включая
текстовые сообщения, медиафайлы, клавиатуры, выполнение функций и обработку
callback-запросов.
:param name: Название команды.
:param keywords: Список ключевых слов, вызывающих команду.
:param func: Список функций, которые выполняются при вызове команды.
:param text_msg: Текстовое сообщение, которое отправляется в ответ на команду.
:param chat_action: Флаг отправки анимации набора текста перед ответом.
:param description: Описание команды.
:param tg_links: Флаг обработки ссылок на пользователей в тексте.
:param keyboard: Клавиатура, привязанная к команде.
:param prefix: Префикс команды (по умолчанию `BotVar.prefix`).
:param callbackdata: Список callback-данных, связанных с командой.
:param only_admin: Флаг, разрешающий использование команды только администраторам.
:param ignore_case: Флаг игнорирования регистра в ключевых словах.
:param activate_keywords: Флаг активации команды по ключевым словам.
:param delete_msg: Флаг удаления исходного сообщения после обработки команды.
:param activate_commands: Флаг активации команды через стандартный обработчик команд.
:param activate_callback: Флаг активации обработки callback-запросов.
:param media: Тип медиафайла, который отправляется (`"message"`, `"photo"`, `"video"`, и т. д.).
:param path_to_media: Путь к медиафайлу (или список путей).
:param parse_mode: Форматирование текста (`Markdown`, `HTML` и т. д.).
:param disable_notification: Флаг отключения уведомлений при отправке сообщений.
:param protect: Флаг защиты контента сообщений.
:return: Готовый шаблон для команды.
"""
def __init__(self, name: str,
keywords: list,
func: Optional[list[Callable]] = None,
text_msg=None,
text_msg = None,
chat_action: bool = False,
description: str = "Описание команды",
tg_links: bool = False,
@@ -34,15 +65,16 @@ class CommandHandler:
activate_commands: bool = True,
activate_callback: bool = True,
media: str = "message",
path_to_media=None,
path_to_media = None,
parse_mode: str = BotVar.parse_mode,
disable_notification: bool = BotVar.disable_notification,
protect: bool = BotVar.protect_content):
self.router = Router(name=f"{name}_router")
self.name = name
self.log_type = name.capitalize()
self.description = description
self.last_bot_message = {} # {chat_id: message_id}
self.last_bot_message = {}
self.keywords = keywords
self.text_msg = text_msg
@@ -125,7 +157,10 @@ class CommandHandler:
text = text.replace("<users>", str(message.from_user.id))
Logs.info(log_type=self.log_type, user=username(message), text=f"использовал(а) команду /{self.name}")
await base_sql(message)
# Работа с базами данных
db.update_user(message)
db.update_user_messages(message)
# Обрабатываем текстовое сообщение
if callable(self.text_msg):

View File

@@ -0,0 +1,12 @@
# BotLibrary/system/__init__.py
# Инициализация пакета system, для библиотек запуска
# Экспортирование модулей во внешние слои проекта
from .db_class import *
from ProjectsFiles import BotVar
# Создание экземпляра класса
db = Database(BotVar.bd_names)
# Создание базы данных
db.create_db()

563
BotLibrary/sql/db_class.py Normal file
View File

@@ -0,0 +1,563 @@
# BotLibrary/system/db_class.py
# Создание базы данных
import os
import sqlite3
from datetime import datetime, timedelta, timezone
from aiogram import types
from typing import Optional, List, Tuple
from ProjectsFiles import BotVar
# Настройка экспорта в модули
__all__ = ("Database",)
class Database:
"""Класс для управления базой данных пользователей чата с использованием SQLite3."""
def __init__(self, db_name: str = BotVar.bd_path) -> None:
"""Инициализация класса с именем базы данных."""
self.db_name = db_name
# --- Основные методы из предоставленных функций ---
def create_db(self) -> None:
"""Создание базы данных и таблиц с начальными данными."""
# Создание директории, если её нет
db_directory = os.path.dirname(self.db_name)
if db_directory and not os.path.exists(db_directory):
os.makedirs(db_directory)
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
# Таблица пользователей
cursor.execute('''
CREATE TABLE IF NOT EXISTS users (
user_id INTEGER PRIMARY KEY,
tg_id INTEGER NOT NULL UNIQUE,
username TEXT,
first_name TEXT,
last_name TEXT,
role TEXT DEFAULT NULL,
status TEXT DEFAULT 'active',
user TEXT DEFAULT 'user'
);''')
# Таблица сообщений пользователей
cursor.execute('''
CREATE TABLE IF NOT EXISTS user_messages (
user_id INTEGER PRIMARY KEY,
last_message TEXT,
last_message_id INTEGER,
last_message_time TEXT,
messages_per_day INTEGER DEFAULT 0,
messages_per_week INTEGER DEFAULT 0,
messages_per_month INTEGER DEFAULT 0,
total_messages INTEGER DEFAULT 0,
FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE CASCADE
);''')
# Таблица персонажей Genshin Impact
cursor.execute('''
CREATE TABLE IF NOT EXISTS characters (
id INTEGER PRIMARY KEY AUTOINCREMENT,
region TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT DEFAULT 'Свободно',
user_id INTEGER DEFAULT NULL,
comment TEXT DEFAULT '',
FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE SET NULL
);''')
# Таблица персонажей Honkai: Star Rail
cursor.execute('''
CREATE TABLE IF NOT EXISTS characters_hsr (
id INTEGER PRIMARY KEY AUTOINCREMENT,
region TEXT NOT NULL,
name TEXT NOT NULL,
status TEXT DEFAULT 'Свободно',
user_id INTEGER DEFAULT NULL,
comment TEXT DEFAULT '',
FOREIGN KEY (user_id) REFERENCES users (user_id) ON DELETE SET NULL
);''')
# Начальные данные для characters (Genshin Impact)
characters_genshin: list[tuple[str, str, str, str]] = [
# Мондштадт
("Мондштадт", "Венти", "Свободно", ""),
("Мондштадт", "Кэйа", "Свободно", ""),
("Мондштадт", "Альбедо", "Свободно", ""),
("Мондштадт", "Дилюк", "Свободно", ""),
("Мондштадт", "Мика", "Свободно", ""),
("Мондштадт", "Беннет", "Свободно", ""),
("Мондштадт", "Рэйзор", "Свободно", ""),
("Мондштадт", "Эола", "Свободно", ""),
("Мондштадт", "Мона", "Свободно", ""),
("Мондштадт", "Джинн", "Свободно", ""),
("Мондштадт", "Диона", "Свободно", ""),
("Мондштадт", "Лиза", "Свободно", ""),
("Мондштадт", "Ноэлль", "Свободно", ""),
("Мондштадт", "Сахароза", "Свободно", ""),
("Мондштадт", "Розария", "Свободно", ""),
("Мондштадт", "Эмбер", "Свободно", ""),
("Мондштадт", "Фишль", "Свободно", ""),
("Мондштадт", "Барбара", "Свободно", ""),
# Ли Юэ
("Ли Юэ", "Чжун Ли", "Свободно", ""),
("Ли Юэ", "Сяо", "Свободно", ""),
("Ли Юэ", "Син Цю", "Свободно", ""),
("Ли Юэ", "Чун Юнь", "Свободно", ""),
("Ли Юэ", "Бай Чжу", "Свободно", ""),
("Ли Юэ", "Е Лань", "Свободно", ""),
("Ли Юэ", "Шень Хэ", "Свободно", ""),
("Ли Юэ", "Гань Юй", "Свободно", ""),
("Ли Юэ", "Ци Ци", "Свободно", ""),
("Ли Юэ", "Кэ Цин", "Свободно", ""),
("Ли Юэ", "Янь Фэй", "Свободно", ""),
("Ли Юэ", "Нин Гуан", "Свободно", ""),
("Ли Юэ", "Бэй Доу", "Свободно", ""),
("Ли Юэ", "Яо Яо", "Свободно", ""),
("Ли Юэ", "Ка Мин", "Свободно", ""),
("Ли Юэ", "Сянь Юнь", "Свободно", ""),
("Ли Юэ", "Юнь Цзинь", "Свободно", ""),
("Ли Юэ", "Ху Тао", "Свободно", ""),
("Ли Юэ", "Лань Янь", "Свободно", ""),
# Инадзума
("Инадзума", "Итто", "Свободно", ""),
("Инадзума", "Горо", "Свободно", ""),
("Инадзума", "Аято", "Свободно", ""),
("Инадзума", "Хэйдзо", "Свободно", ""),
("Инадзума", "Тома", "Свободно", ""),
("Инадзума", "Кадзуха", "Свободно", ""),
("Инадзума", "Кирара", "Свободно", ""),
("Инадзума", "Кудзё Сара", "Свободно", ""),
("Инадзума", "Ёимия", "Свободно", ""),
("Инадзума", "Аяка", "Свободно", ""),
("Инадзума", "Сангономия Кокоми", "Свободно", ""),
("Инадзума", "Яэ Мико", "Свободно", ""),
("Инадзума", "Райдэн Эи", "Свободно", ""),
("Инадзума", "Саю", "Свободно", ""),
("Инадзума", "Куки Синобу", "Свободно", ""),
("Инадзума", "Мидзуки", "Свободно", ""),
# Сумеру
("Сумеру", "Аль-Хайтам", "Свободно", ""),
("Сумеру", "Кавех", "Свободно", ""),
("Сумеру", "Сайно", "Свободно", ""),
("Сумеру", "Тигнари", "Свободно", ""),
("Сумеру", "Сетос", "Свободно", ""),
("Сумеру", "Нилу", "Свободно", ""),
("Сумеру", "Нахида", "Свободно", ""),
("Сумеру", "Лайла", "Свободно", ""),
("Сумеру", "Кандакия", "Свободно", ""),
("Сумеру", "Дори", "Свободно", ""),
("Сумеру", "Дэхья", "Свободно", ""),
("Сумеру", "Коллеи", "Свободно", ""),
("Сумеру", "Фарузан", "Свободно", ""),
# Фонтейн
("Фонтейн", "Лини", "Свободно", ""),
("Фонтейн", "Ризли", "Свободно", ""),
("Фонтейн", "Невиллет", "Свободно", ""),
("Фонтейн", "Фремине", "Свободно", ""),
("Фонтейн", "Линетт", "Свободно", ""),
("Фонтейн", "Эмилия", "Свободно", ""),
("Фонтейн", "Клоринда", "Свободно", ""),
("Фонтейн", "Навия", "Свободно", ""),
("Фонтейн", "Шарлотта", "Свободно", ""),
("Фонтейн", "Фурина", "Свободно", ""),
("Фонтейн", "Тиори", "Свободно", ""),
("Фонтейн", "Сиджвин", "Свободно", ""),
# Натлан
("Натлан", "Кинич", "Свободно", ""),
("Натлан", "Оророн", "Свободно", ""),
("Натлан", "Муалани", "Свободно", ""),
("Натлан", "Ситлали", "Свободно", ""),
("Натлан", "Шилонен", "Свободно", ""),
("Натлан", "Иансан", "Свободно", ""),
("Натлан", "Мавуика", "Свободно", ""),
("Натлан", "Часка", "Свободно", ""),
# Фатуи
("Фатуи", "Тарталья", "Свободно", ""),
("Фатуи", "Панталоне", "Свободно", ""),
("Фатуи", "Дотторе", "Свободно", ""),
("Фатуи", "Капитано", "Свободно", ""),
("Фатуи", "Пьеро", "Свободно", ""),
("Фатуи", "Пульничелла", "Свободно", ""),
("Фатуи", "Синьора", "Свободно", ""),
("Фатуи", "Арлекино", "Свободно", ""),
("Фатуи", "Коломбина", "Свободно", ""),
("Фатуи", "Царица", "Свободно", ""),
("Фатуи", "Странник", "Свободно", ""),
# Иные персонажи
("Иные персонажи", "Итэр", "Свободно", ""),
("Иные персонажи", "Люмин", "Свободно", ""),
("Иные персонажи", "Элой", "Свободно", ""),
("Иные персонажи", "Паймон", "Свободно", ""),
("Иные персонажи", "Дайнслейф", "Свободно", ""),
]
# Начальные данные для characters_hsr (Honkai: Star Rail)
characters_hsr: list[tuple[str, str, str, str]] = [
# Ярило-6
("Ярило-6", "Броня", "Свободно", ""),
("Ярило-6", "Гепард", "Свободно", ""),
("Ярило-6", "Зеле", "Свободно", ""),
("Ярило-6", "Клара", "Свободно", ""),
("Ярило-6", "Лука", "Свободно", ""),
("Ярило-6", "Наташа", "Свободно", ""),
("Ярило-6", "Пела", "Свободно", ""),
("Ярило-6", "Рысь", "Свободно", ""),
("Ярило-6", "Сампо", "Свободно", ""),
("Ярило-6", "Сервал", "Свободно", ""),
("Ярило-6", "Хук", "Свободно", ""),
# Станция «Герта»
("Станция «Герта»", "Арлан", "Свободно", ""),
("Станция «Герта»", "Аста", "Свободно", ""),
("Станция «Герта»", "Великая Герта", "Свободно", ""),
("Станция «Герта»", "Кукла «Герта»", "Свободно", ""),
("Станция «Герта»", "Жуань Мэй", "Свободно", ""),
# Пенакония
("Пенакония", "Авантюрин", "Свободно", ""),
("Пенакония", "Ахерон", "Свободно", ""),
("Пенакония", "Галлахер", "Свободно", ""),
("Пенакония", "Зарянка", "Свободно", ""),
("Пенакония", "Миша", "Свободно", ""),
("Пенакония", "Мистер Река", "Свободно", ""),
("Пенакония", "Раппа", "Свободно", ""),
("Пенакония", "Чёрный Лебедь", "Свободно", ""),
("Пенакония", "Яшма", "Свободно", ""),
("Пенакония", "Воскресенье", "Свободно", ""),
# Звёздный Экспресс
("Звёздный Экспресс", "Вельт", "Свободно", ""),
("Звёздный Экспресс", "Келус", "Свободно", ""),
("Звёздный Экспресс", "Стелла", "Свободно", ""),
("Звёздный Экспресс", "Дань Хэн", "Свободно", ""),
("Звёздный Экспресс", "Март 7", "Свободно", ""),
("Звёздный Экспресс", "Химеко", "Свободно", ""),
("Звёздный Экспресс", "Пом Пом", "Свободно", ""),
# Галактика
("Галактика", "Аргенти", "Свободно", ""),
("Галактика", "Блэйд", "Свободно", ""),
("Галактика", "Бутхилл", "Свободно", ""),
("Галактика", "Доктор Рацио", "Свободно", ""),
("Галактика", "Кафка", "Свободно", ""),
("Галактика", "Светлячок", "Свободно", ""),
("Галактика", "Искорка", "Свободно", ""),
("Галактика", "Серебряный Волк", "Свободно", ""),
("Галактика", "Топаз", "Свободно", ""),
# Амфореус
("Амфореус", "Аглая", "Свободно", ""),
("Амфореус", "Мидей", "Свободно", ""),
("Амфореус", "Трибби", "Свободно", ""),
# Альянс Сяньчжоу
("Альянс Сяньчжоу", "Байлу", "Свободно", ""),
("Альянс Сяньчжоу", "Гуйнайфей", "Свободно", ""),
("Альянс Сяньчжоу", "Линша", "Свободно", ""),
("Альянс Сяньчжоу", "Лоча", "Свободно", ""),
("Альянс Сяньчжоу", "Моцзэ", "Свободно", ""),
("Альянс Сяньчжоу", "Пожиратель Луны", "Свободно", ""),
("Альянс Сяньчжоу", "Сушан", "Свободно", ""),
("Альянс Сяньчжоу", "Сюзи", "Свободно", ""),
("Альянс Сяньчжоу", "Фуга", "Свободно", ""),
("Альянс Сяньчжоу", "Фэйсяо", "Свободно", ""),
("Альянс Сяньчжоу", "Ханья", "Свободно", ""),
("Альянс Сяньчжоу", "Хохо", "Свободно", ""),
("Альянс Сяньчжоу", "Цзин Юань", "Свободно", ""),
("Альянс Сяньчжоу", "Цзиннлю", "Свободно", ""),
("Альянс Сяньчжоу", "Цзяоцю", "Свободно", ""),
("Альянс Сяньчжоу", "Цинцюэ", "Свободно", ""),
("Альянс Сяньчжоу", "Юйкун", "Свободно", ""),
("Альянс Сяньчжоу", "Юньли", "Свободно", ""),
("Альянс Сяньчжоу", "Яньцин", "Свободно", ""),
]
# Заполнение таблиц начальными данными, если они пусты
cursor.execute("SELECT COUNT(*) FROM characters")
if cursor.fetchone()[0] == 0:
cursor.executemany('INSERT INTO characters (region, name, status, comment) VALUES (?, ?, ?, ?)',
characters_genshin)
cursor.execute("SELECT COUNT(*) FROM characters_hsr")
if cursor.fetchone()[0] == 0:
cursor.executemany('INSERT INTO characters_hsr (region, name, status, comment) VALUES (?, ?, ?, ?)',
characters_hsr)
db.commit()
def add_user(self, tg_id: int, username: str, first_name: str, last_name: str, role: str, status: str,
user: str) -> None:
"""Добавление нового пользователя в базу данных."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
# Проверка на существование пользователя
cursor.execute("SELECT user_id FROM users WHERE tg_id = ?", (tg_id,))
if cursor.fetchone():
return # Пользователь уже существует
# Определение нового user_id
cursor.execute("SELECT MAX(user_id) FROM users")
max_id = cursor.fetchone()[0]
new_user_id = 1 if max_id is None else max_id + 1
# Вставка пользователя
cursor.execute('''
INSERT INTO users (user_id, tg_id, username, first_name, last_name, role, status, user)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (new_user_id, tg_id, username, first_name, last_name, role, status, user))
# Создание записи в user_messages
cursor.execute('INSERT INTO user_messages (user_id) VALUES (?)', (new_user_id,))
db.commit()
def get_user(self, tg_id: int) -> Optional[Tuple]:
"""Получение информации о пользователе по tg_id."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("SELECT * FROM users WHERE tg_id = ?", (tg_id,))
return cursor.fetchone()
def update_user(self, tg_id: int, username: Optional[str] = None, first_name: Optional[str] = None,
last_name: Optional[str] = None, role: Optional[str] = None, user: Optional[str] = None) -> None:
"""Обновление данных о пользователе."""
updates = []
params = []
if username:
updates.append("username = ?")
params.append(username)
if first_name:
updates.append("first_name = ?")
params.append(first_name)
if last_name:
updates.append("last_name = ?")
params.append(last_name)
if role:
updates.append("role = ?")
params.append(role)
if user:
updates.append("user = ?")
params.append(user)
if updates:
query = f"UPDATE users SET {', '.join(updates)} WHERE tg_id = ?"
params.append(tg_id)
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute(query, params)
db.commit()
def update_user_messages(self, message: types.Message) -> None:
"""Обновление статистики сообщений пользователя."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
tg_id = message.from_user.id
# Получение user_id
cursor.execute("SELECT user_id FROM users WHERE tg_id = ?", (tg_id,))
result = cursor.fetchone()
if not result:
return # Пользователь не найден
user_id = result[0]
# Проверка существующей записи в user_messages
cursor.execute(
"SELECT last_message_time, messages_per_day, messages_per_week, messages_per_month, total_messages "
"FROM user_messages WHERE user_id = ?", (user_id,))
result = cursor.fetchone()
# Время сообщения в московском часовом поясе
now = message.date.astimezone(timezone(timedelta(hours=3)))
today = now.date()
start_of_week = today - timedelta(days=today.weekday())
current_month = now.month
current_year = now.year
last_message = message.text or "Н" # Замените на type_msg(message) при необходимости
last_message_id = message.message_id
if result:
last_message_time, messages_per_day, messages_per_week, messages_per_month, total_messages = result
if last_message_time:
last_message_time = datetime.fromisoformat(last_message_time).astimezone(
timezone(timedelta(hours=3)))
last_date = last_message_time.date()
last_week = last_date - timedelta(days=last_date.weekday())
last_month = last_message_time.month
last_year = last_message_time.year
# Обнуление счетчиков при смене периода
if last_date != today:
messages_per_day = 0
if last_week != start_of_week:
messages_per_week = 0
if last_month != current_month or last_year != current_year:
messages_per_month = 0
else:
messages_per_day, messages_per_week, messages_per_month = 0, 0, 0
# Увеличение счетчиков
messages_per_day += 1
messages_per_week += 1
messages_per_month += 1
total_messages += 1
else:
messages_per_day, messages_per_week, messages_per_month, total_messages = 1, 1, 1, 1
cursor.execute('''
INSERT INTO user_messages (user_id, last_message, last_message_id, last_message_time,
messages_per_day, messages_per_week, messages_per_month, total_messages)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
''', (user_id, last_message, last_message_id, now.isoformat(), messages_per_day,
messages_per_week, messages_per_month, total_messages))
db.commit()
return
# Обновление записи
cursor.execute('''
UPDATE user_messages
SET last_message = ?, last_message_id = ?, last_message_time = ?,
messages_per_day = ?, messages_per_week = ?, messages_per_month = ?,
total_messages = ?
WHERE user_id = ?
''', (last_message, last_message_id, now.isoformat(), messages_per_day,
messages_per_week, messages_per_month, total_messages, user_id))
db.commit()
def get_user_status(self, message: types.Message) -> str:
"""Получение статуса пользователя."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("SELECT user FROM users WHERE tg_id = ?", (message.from_user.id,))
row = cursor.fetchone()
status_map = {
"ban": "Забанен",
"user": "Пользователь",
"moderator": "Модератор",
"admin": "Администратор",
"so-owner": "Совладелец",
"owner": "Владелец",
}
return status_map.get(row[0], "Ошибка!") if row else "Пользователь не найден"
# --- Новые и улучшенные методы ---
def delete_user(self, tg_id: int) -> None:
"""Удаление пользователя из базы данных."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("DELETE FROM users WHERE tg_id = ?", (tg_id,))
db.commit()
def get_all_users(self) -> List[Tuple]:
"""Получение списка всех пользователей."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("SELECT * FROM users")
return cursor.fetchall()
def get_user_messages(self, tg_id: int) -> Optional[Tuple]:
"""Получение статистики сообщений пользователя."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("SELECT user_id FROM users WHERE tg_id = ?", (tg_id,))
result = cursor.fetchone()
if not result:
return None
user_id = result[0]
cursor.execute("SELECT * FROM user_messages WHERE user_id = ?", (user_id,))
return cursor.fetchone()
def ban_user(self, tg_id: int) -> None:
"""Блокировка пользователя."""
self.update_user(tg_id, user="ban")
def unban_user(self, tg_id: int) -> None:
"""Разблокировка пользователя."""
self.update_user(tg_id, user="user")
def promote_user(self, tg_id: int, new_role: str) -> None:
"""Изменение роли пользователя."""
valid_roles = ["user", "moderator", "admin", "so-owner", "owner"]
if new_role in valid_roles:
self.update_user(tg_id, user=new_role)
def get_top_active_users(self, limit: int = 10) -> List[Tuple]:
"""Получение самых активных пользователей по количеству сообщений."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute('''
SELECT u.tg_id, u.username, um.total_messages
FROM users u
JOIN user_messages um ON u.user_id = um.user_id
ORDER BY um.total_messages DESC
LIMIT ?
''', (limit,))
return cursor.fetchall()
def assign_character(self, tg_id: int, character_id: int, game: str = "genshin") -> bool:
"""Назначение персонажа пользователю."""
table = "characters" if game == "genshin" else "characters_hsr"
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute(f"SELECT user_id FROM {table} WHERE id = ?", (character_id,))
result = cursor.fetchone()
if result and result[0] is not None:
return False # Персонаж уже занят
cursor.execute("SELECT user_id FROM users WHERE tg_id = ?", (tg_id,))
user_id = cursor.fetchone()
if not user_id:
return False # Пользователь не найден
cursor.execute(f"UPDATE {table} SET user_id = ?, status = 'Занят' WHERE id = ?", (user_id[0], character_id))
db.commit()
return True
def free_character(self, character_id: int, game: str = "genshin") -> None:
"""Освобождение персонажа."""
table = "characters" if game == "genshin" else "characters_hsr"
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute(f"UPDATE {table} SET user_id = NULL, status = 'Свободно' WHERE id = ?", (character_id,))
db.commit()
def get_user_characters(self, tg_id: int, game: str = "genshin") -> List[Tuple]:
"""Получение списка персонажей пользователя."""
table = "characters" if game == "genshin" else "characters_hsr"
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("SELECT user_id FROM users WHERE tg_id = ?", (tg_id,))
user_id = cursor.fetchone()
if not user_id:
return []
cursor.execute(f"SELECT * FROM {table} WHERE user_id = ?", (user_id[0],))
return cursor.fetchall()
def search_users(self, query: str) -> List[Tuple]:
"""Поиск пользователей по имени или юзернейму."""
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute("""
SELECT * FROM users
WHERE username LIKE ? OR first_name LIKE ? OR last_name LIKE ?
""", (f"%{query}%", f"%{query}%", f"%{query}%"))
return cursor.fetchall()
def get_character_by_name(self, name: str, game: str = "genshin") -> Optional[Tuple]:
"""Поиск персонажа по имени."""
table = "characters" if game == "genshin" else "characters_hsr"
with sqlite3.connect(self.db_name) as db:
cursor = db.cursor()
cursor.execute(f"SELECT * FROM {table} WHERE name = ?", (name,))
return cursor.fetchone()

View File

@@ -1,153 +1,115 @@
# BotLibrary/system/bot_edit.py
# Под-пакет установки настроек бота
from aiogram.types import ChatAdministratorRights
from aiogram import Bot
from ProjectsFiles import BotEdit
from .bots import bot
# Настройка логирования
log_type = "Edit"
# Настройка экспорта из модуля
__all__ = ("set_adm_rights", "set_bot_name", "set_bot_description", "set_bot_short_description")
__all__ = ("BotRights",)
# Функция установки прав администратора
async def set_adm_rights(anonym: bool = BotEdit.is_anonymous,
manage_chat: bool = BotEdit.manage_chat,
delete_msg: bool = BotEdit.delete_messages,
manage_video_chats: bool = BotEdit.manage_video_chats,
restrict_members: bool = BotEdit.restrict_members,
promote_members: bool = BotEdit.promote_members,
change_info: bool = BotEdit.change_info,
invite_users: bool = BotEdit.invite_users,
post_stories: bool = BotEdit.post_stories,
edit_stories: bool = BotEdit.edit_stories,
delete_stories: bool = BotEdit.delete_stories,
post_messages: bool = BotEdit.post_messages,
edit_messages: bool = BotEdit.edit_messages,
pin_messages: bool = BotEdit.pin_messages,
manage_topics: bool = BotEdit.manage_topics,) -> None:
class BotRights:
"""
Устанавливает права администратора для бота, если они отличаются от текущих.
Все через конфиги!!!
:param anonym: Позволяет ли боту быть анонимным.
:param manage_chat: Разрешение на управление чатом.
:param delete_msg: Разрешение на удаление сообщений.
:param manage_video_chats: Разрешение на управление видеочатами.
:param restrict_members: Разрешение на ограничение участников (мут, бан).
:param promote_members: Разрешение на назначение администраторов.
:param change_info: Разрешение на изменение информации о группе/канале.
:param invite_users: Разрешение на приглашение новых участников.
:param post_stories: Разрешение на публикацию историй.
:param edit_stories: Разрешение на редактирование историй.
:param delete_stories: Разрешение на удаление историй.
:param post_messages: Разрешение на публикацию сообщений (только для каналов).
:param edit_messages: Разрешение на редактирование сообщений (только для каналов).
:param pin_messages: Разрешение на закрепление сообщений.
:param manage_topics: Разрешение на управление темами (в супергруппах).
:return: Изменение прав администратора
Класс для установки прав администратора и метаинформации бота (имя, описания).
"""
rights = ChatAdministratorRights(
is_anonymous=anonym,
can_manage_chat=manage_chat,
can_delete_messages=delete_msg,
can_manage_video_chats=manage_video_chats,
can_restrict_members=restrict_members,
can_promote_members=promote_members,
can_change_info=change_info,
can_invite_users=invite_users,
can_post_stories=post_stories,
can_edit_stories=edit_stories,
can_delete_stories=delete_stories,
can_post_messages=post_messages,
can_edit_messages=edit_messages,
can_pin_messages=pin_messages,
can_manage_topics=manage_topics,
)
@staticmethod
async def set_administrator_rights(bot: Bot) -> None:
"""
Установка прав администратора в чатах.
# Применяем права только в случае изменения
current_rights = await bot.get_my_default_administrator_rights()
if current_rights != rights:
await bot.set_my_default_administrator_rights(rights)
else:
return
:param bot: Базовый объект бота.
:return: Измененные права по конфигу.
"""
from aiogram.types import ChatAdministratorRights
rights: ChatAdministratorRights = ChatAdministratorRights(
is_anonymous=BotEdit.is_anonymous,
can_manage_chat=BotEdit.manage_chat,
can_delete_messages=BotEdit.delete_messages,
can_manage_video_chats=BotEdit.manage_video_chats,
can_restrict_members=BotEdit.restrict_members,
can_promote_members=BotEdit.promote_members,
can_change_info=BotEdit.change_info,
can_invite_users=BotEdit.invite_users,
can_post_stories=BotEdit.post_stories,
can_edit_stories=BotEdit.edit_stories,
can_delete_stories=BotEdit.delete_stories,
can_post_messages=BotEdit.post_messages,
can_edit_messages=BotEdit.edit_messages,
can_pin_messages=BotEdit.pin_messages,
can_manage_topics=BotEdit.manage_topics,
)
current_rights: ChatAdministratorRights = await bot.get_my_default_administrator_rights()
if current_rights != rights:
await bot.set_my_default_administrator_rights(rights)
# Функция установки имени бота с проверкой на ограничения
async def set_bot_name(new_name: str = BotEdit.name) -> None:
"""
Устанавливает имя бота, если оно отличается от текущего и соответствует ограничениям.
@staticmethod
async def set_name(bot: Bot) -> None:
"""
Установка имени бота.
:param new_name: Новое имя бота (config)
:return: Имя бота
"""
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
:param bot: Базовый объект бота.
:return: Измененное имя бота.
"""
current_name: str = (await bot.get_me()).first_name
new_name: str = str(BotEdit.name)
# Получаем текущее имя бота
current_name = (await bot.get_me()).first_name
if not (1 <= len(new_name) <= 32):
from ..loggers import Logs
Logs.error(log_type="SET_NAME", user="BOT", text="Имя бота должно быть от 1 до 32 символов.")
return
# Проверка длины имени
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 != new_name:
await bot.set_my_name(new_name)
# Проверяем, совпадает ли текущее имя с тем, которое мы хотим установить
if current_name != new_name:
await bot.set_my_name(new_name)
else:
return
@staticmethod
async def set_description(bot: Bot) -> None:
"""
Установка описания бота.
:param bot: Базовый объект бота.
:return: Измененное описание бота.
"""
from aiogram.types import BotDescription
current_description: BotDescription = await bot.get_my_description()
new_description: str = str(BotEdit.description)
# Функция установки описания бота с проверкой на ограничения
async def set_bot_description(new_description: str = BotEdit.description) -> None:
"""
Устанавливает описание бота, если оно отличается от текущего и соответствует ограничениям.
if not (0 < len(new_description) <= 255):
from ..loggers import Logs
Logs.error(log_type="SET_DESCRIPTION", user="BOT", text="Описание должно быть от 1 до 255 символов.")
return
:param new_description: Новое описание для бота (config)
:return: Описание бота
"""
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
if current_description != new_description:
await bot.set_my_description(description=new_description)
# Получаем текущее описание бота
current_description = await bot.get_my_description()
@staticmethod
async def set_short_description(bot: Bot) -> None:
"""
Установка описания виджета.
# Проверка длины описания
if len(new_description) > 255 or len(new_description)==0:
Logs.error(log_type=log_type, user="DISCRIPT", text="Короткое описание бота не может превышать 255 символов или быть равно 0.")
return # Выходим из функции, если описание некорректно
:param bot: Базовый объект бота.
:return: Измененное описание виджета бота.
"""
from aiogram.types import BotShortDescription
current_short_description: BotShortDescription = await bot.get_my_short_description()
new_short_description: str = str(BotEdit.short_description)
# Проверяем, совпадает ли текущее описание с тем, которое мы хотим установить
if current_description != new_description:
await bot.set_my_description(description=new_description)
else:
return
if not (0 < len(new_short_description) <= 512):
from ..loggers import Logs
Logs.error(log_type="SET_SHORT_DESCRIPTION", user="BOT", text="Короткое описание должно быть от 1 до 512 символов.")
return
if current_short_description != new_short_description:
await bot.set_my_short_description(short_description=new_short_description)
# Функция установки короткого описания бота с проверкой на ограничения
async def set_bot_short_description(new_short_description: str = BotEdit.short_description) -> None:
"""
Устанавливает короткое описание бота, если оно отличается от текущего и соответствует ограничениям.
@staticmethod
async def all(bot: Bot) -> 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(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 != new_short_description:
await bot.set_my_short_description(short_description=new_short_description)
else:
return
:param bot: Базовый объект бота.
:return: Изменение всех основных параметров бота.
"""
await BotRights.set_administrator_rights(bot)
await BotRights.set_name(bot)
await BotRights.set_description(bot)
await BotRights.set_short_description(bot)

View File

@@ -17,23 +17,14 @@ dp = Dispatcher()
dp["started_at"] = get_host_time()
dp["started_at_city"] = get_city_time()
dp["is_active"] = True # Флаг активности бота
dp["logs"] = []
dp["users"] = {}
dp["admins"] = {}
dp["sessions"] = {}
dp["task_queue"] = []
dp["configs"] = {"max_connections": 100, "retry_interval": 5, "time_format": BotVar.time_format}
dp["metrics"] = {"messages_received": 0, "messages_sent": 0, "errors": 0}
dp["modules"] = {}
dp["state"] = {}
dp["scheduler"] = []
dp["handlers"] = {"on_message": [], "on_error": []}
dp["storage"] = {}
dp["database"] = "SQLite3"
# Создание экземпляра бота и его настройка
bot = Bot(token=bot_token, default=DefaultBotProperties(
bot: Bot = Bot(token=bot_token, default=DefaultBotProperties(
parse_mode=BotVar.parse_mode,
disable_notification=BotVar.disable_notification,
protect_content=BotVar.protect_content,
@@ -57,27 +48,31 @@ class BotInfo:
"""
id: int = None
first_name: str = None
bot_owner: str = BotVar
last_name: str = None
username: str = None
description: str = None
short_description: str = None
can_join_groups: bool = False
can_read_all_group_messages: bool = False
description: str = ''
short_description: str = ''
language_code: str = BotVar.language
prefix: str = BotVar.prefix
bot_owner: str = BotVar
is_premium: bool = False
added_to_attachment_menu: bool = False
supports_inline_queries: bool = False
can_connect_to_business: bool = False
has_main_web_app: bool = False
can_join_groups: bool = False
can_read_all_group_messages: bool = False
@classmethod
def update(cls, bot_info) -> None:
async def info(cls, bots: Bot = bot) -> dict:
"""
Обновляет данные о боте.
:param bot_info: Объект с данными о боте, полученные через API Telegram.
Получает информацию о боте через API и обновляет данные в классе.
:param bots: Объект бота
:return: Словарь с данными о боте
"""
bot_info = await bots.get_me()
cls.id = bot_info.id
cls.first_name = bot_info.first_name
cls.last_name = bot_info.last_name
@@ -93,31 +88,28 @@ class BotInfo:
cls.can_join_groups = getattr(bot_info, 'can_join_groups', False)
cls.can_read_all_group_messages = getattr(bot_info, 'can_read_all_group_messages', False)
return cls.to_dict()
# Функция получения информации о боте
async def bot_get_info(bots: Bot = bot) -> dict:
"""
Получает информацию о боте и обновляет данные в классе BotInfo.
:param bots: Получение объекта бота в функцию.
:return: Словарь с данными о боте.
"""
bot_info_data = await bots.get_me()
BotInfo.update(bot_info_data)
return {
'bot_info': bot_info_data,
'id': bot_info_data.id,
'first_name': bot_info_data.first_name,
'last_name': bot_info_data.last_name,
'username': bot_info_data.username,
'description': getattr(bot_info_data, 'description', ''),
'short_description': getattr(bot_info_data, 'short_description', ''),
'language_code': bot_info_data.language_code,
'prefix': BotVar.prefix,
'is_premium': bot_info_data.is_premium,
'added_to_attachment_menu': bot_info_data.added_to_attachment_menu,
'supports_inline_queries': bot_info_data.supports_inline_queries,
'can_connect_to_business': bot_info_data.can_connect_to_business,
'has_main_web_app': bot_info_data.has_main_web_app,
'can_join_groups': getattr(bot_info_data, 'can_join_groups', False),
'can_read_all_group_messages': getattr(bot_info_data, 'can_read_all_group_messages', False),
}
@classmethod
def to_dict(cls) -> dict:
"""
Возвращает текущие данные в виде словаря.
"""
return {
'id': cls.id,
'first_name': cls.first_name,
'last_name': cls.last_name,
'username': cls.username,
'description': cls.description,
'short_description': cls.short_description,
'language_code': cls.language_code,
'prefix': cls.prefix,
'bot_owner': cls.bot_owner,
'is_premium': cls.is_premium,
'added_to_attachment_menu': cls.added_to_attachment_menu,
'supports_inline_queries': cls.supports_inline_queries,
'can_connect_to_business': cls.can_connect_to_business,
'has_main_web_app': cls.has_main_web_app,
'can_join_groups': cls.can_join_groups,
'can_read_all_group_messages': cls.can_read_all_group_messages,
}

View File

@@ -2,51 +2,47 @@
# Создание пустых директорий при первом запуске
import os
from ProjectsFiles import ProjectPath, TypeDirectory
from typing import List
from ProjectsFiles import ProjectPath, TypeDirectory
# Настройка экспорта из модуля
__all__ = ("create_directories", "setup_directories", "create_directory")
__all__ = ("Directory",)
class Directory:
@staticmethod
async def create_directory(directory: str) -> None:
"""
Создает директории, если они еще не существуют.
# Функция создания директории
async def create_directory(directory : str) -> None:
"""
Создает директории, если они еще не существуют.
:param directory: Путь к базовой директории.
:return: Создание директорий по определенному пути.
"""
os.makedirs(directory, exist_ok=True)
:param directory: Путь к базовой директории.
:return: Создание директорий по определенному пути.
"""
os.makedirs(directory)
@staticmethod
async def create_directories(base_directory: str, subdirectories: List[str]) -> None:
"""
Создает указанные поддиректории в указанной базовой директории.
:param base_directory: Путь к базовой директории.
:param subdirectories: Список поддиректорий, которые необходимо создать.
:return: Создание директорий по определенному пути.
"""
# Создание директорий и файлов в каждой из них
for subdirectory in subdirectories:
directory_path = os.path.join(base_directory, subdirectory)
# Функция создания поддиректорий
async def create_directories(base_directory: str, subdirectories: List[str]) -> None:
"""
Создает указанные поддиректории в указанной базовой директории.
# Проверка, существует ли директория, если нет - создаём
os.makedirs(directory_path, exist_ok=True)
:param base_directory: Путь к базовой директории.
:param subdirectories: Список поддиректорий, которые необходимо создать.
:return: Создание директорий по определенному пути.
"""
# Создание директорий и файлов в каждой из них
for subdirectory in subdirectories:
directory_path = os.path.join(base_directory, subdirectory)
@staticmethod
async def setup() -> None:
"""
Настройка начальных пустых директорий для проекта.
# Проверка, существует ли директория, если нет - создаём
if not os.path.exists(directory_path):
os.makedirs(directory_path)
# Функция установки начальных директорий
async def setup_directories() -> None:
"""
Настройка начальных пустых директорий для проекта.
:return: Создание системы директорий по определенному пути.
"""
# Создание директорий для медиа файлов
await create_directories(ProjectPath.personal_media, TypeDirectory.media_directories)
await create_directories(ProjectPath.received_media, TypeDirectory.media_directories)
await create_directories(ProjectPath.received_avatars, TypeDirectory.avatar_directories)
# await create_directories(ProjectPath.msg, TypeDirectory.msg_directories)
:return: Создание системы директорий по определенному пути.
"""
# Создание директорий для медиа файлов
await Directory.create_directories(ProjectPath.personal_media, TypeDirectory.media_directories)
await Directory.create_directories(ProjectPath.received_media, TypeDirectory.media_directories)
await Directory.create_directories(ProjectPath.received_avatars, TypeDirectory.avatar_directories)

View File

@@ -1,7 +1,6 @@
# BotLibrary/timer/start_time.py
# Получение времени по разным часовым поясам
import pytz
from datetime import datetime
from tzlocal import get_localzone
from apscheduler.schedulers.asyncio import AsyncIOScheduler
@@ -14,7 +13,6 @@ __all__ = ("scheduler", "get_city_time", "get_host_time")
scheduler = AsyncIOScheduler(timezone=get_localzone().key)
# Функция получение иного времени
def get_city_time(city: str = 'Europe/Moscow',
time_format: str = BotVar.time_format) -> str:
"""
@@ -24,13 +22,13 @@ def get_city_time(city: str = 'Europe/Moscow',
:param time_format: Шаблон форматирования времени (конфиг).
:return: Строка, представляющая время в формате, заданном в BotVar.time_format.
"""
from pytz import timezone
# Устанавливаем временную зону для Москвы
city_tz = pytz.timezone(city)
city_tz = timezone(city)
# Возвращаем строку с форматом времени
return datetime.now(city_tz).strftime(time_format)
# Функция получение времени хоста
def get_host_time(time_format: str = BotVar.time_format) -> str:
"""
Получение текущего времени хоста (локального времени).

View File

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

View File

@@ -13,8 +13,9 @@ async def normal_words(word: str) -> str:
"""
try:
return word.lower().capitalize()
except Exception as e:
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
from ..loggers.logs import Logs
Logs.error(text=f"Ошибка в нормализировании слова: {e}", log_type="NormalWord")
return word

View File

@@ -1,13 +1,9 @@
# BotLibrary/validators/url_valid.py
# Валидатор ссылок на регулярных выражениях
import re
# Настройка экспорта из этого модуля
__all__ = ("valid_url", "url_to_text")
# Функция определения является ли строка ссылкой
def valid_url(url: str) -> bool:
"""
Проверяет, является ли строка валидной ссылкой (URL).
@@ -15,7 +11,8 @@ def valid_url(url: str) -> bool:
:param url: Строка для проверки.
:return: True, если строка является валидным URL, иначе False.
"""
url_pattern = re.compile(
from re import compile
url_pattern = compile(
r'^(https?://)?' # Протокол (http или https, необязателен)
r'([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}' # Домен
r'(:\d+)?' # Порт (необязателен)
@@ -24,7 +21,6 @@ def valid_url(url: str) -> bool:
return bool(url_pattern.match(url))
# Функция, что дает тексту ссылку на HTML
def url_to_text(text: str, url: str) -> str:
"""
Преобразует текст в HTML ссылку с указанным URL.
@@ -45,7 +41,7 @@ def url_to_text(text: str, url: str) -> str:
except ValueError as e:
# Импортируем Logs внутри функции, чтобы избежать циклического импорта
from ..loggers.custom_loggers import Logs
from ..loggers.logs import Logs
# Логируем ошибку с использованием Logs.error, как указано
Logs.error(text=f"Ошибка при создании ссылки: {e}", log_type="InvalidURL")
raise e # Перебрасываем ошибку выше для дальнейшей обработки или уведомления

View File

@@ -7,7 +7,6 @@ from aiogram.types import Message
__all__ = ("username", "username_to_text")
# Функция получения юзера или ID пользователя
def username(message: Message) -> str:
"""
Возвращает юзернейм пользователя из сообщения, или ID, если юзернейм не указан.
@@ -26,7 +25,6 @@ def username(message: Message) -> str:
raise e # Перебрасываем ошибку выше для дальнейшей обработки
# Функция получение имени пользователя + ссылка на него
def username_to_text(message: Message) -> str:
"""
Преобразует информацию о пользователе в строку с HTML-ссылкой.