diff --git a/.gitignore b/.gitignore index c34ad9a..98e7c69 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ # Игнорирование локального окружения и пользовательских конфигураций IDE .* +.idea +.venv/ *.iml diff --git a/.idea/PRIMOWORLD.iml b/.idea/PRIMOWORLD.iml index 9e66ae1..63bd207 100644 --- a/.idea/PRIMOWORLD.iml +++ b/.idea/PRIMOWORLD.iml @@ -2,8 +2,17 @@ - + + + + + + + + + + diff --git a/BotCode/__init__.py b/BotCode/__init__.py index dc27354..fe24e97 100644 --- a/BotCode/__init__.py +++ b/BotCode/__init__.py @@ -1,5 +1,6 @@ # BotCode/__init__.py -# Инициализация основного модуля BotCode, для настройки переменных проекта +# Инициализация пакета BotCode, для создания кода проекта # Экспортирование модулей во внешние слои проекта - +from .keyboards import * +from .routers import * diff --git a/BotCode/inline/__init__.py b/BotCode/inline/__init__.py new file mode 100644 index 0000000..37bdd9e --- /dev/null +++ b/BotCode/inline/__init__.py @@ -0,0 +1,4 @@ +# BotCode/inline/__init__.py +# Инициализация модуля inline, для основных роутеров + +# Экспортирование модулей во внешние слои проекта diff --git a/BotCode/keyboards/__init__.py b/BotCode/keyboards/__init__.py new file mode 100644 index 0000000..b10e1c0 --- /dev/null +++ b/BotCode/keyboards/__init__.py @@ -0,0 +1,4 @@ +# BotCode/keyboards/__init__.py +# Инициализация модуля keyboards, для создания клавиатур + +# Экспортирование модулей во внешние слои проекта diff --git a/BotCode/keyboards/inline_kb/__init__.py b/BotCode/keyboards/inline_kb/__init__.py new file mode 100644 index 0000000..2c86324 --- /dev/null +++ b/BotCode/keyboards/inline_kb/__init__.py @@ -0,0 +1,4 @@ +# BotCode/keyboards/inline_kb/__init__.py +# Инициализация модуля inline_kb, для inline-клавиатур + +# Экспортирование модулей во внешние слои проекта diff --git a/BotCode/keyboards/reply_kb/__init__.py b/BotCode/keyboards/reply_kb/__init__.py new file mode 100644 index 0000000..e8c26b2 --- /dev/null +++ b/BotCode/keyboards/reply_kb/__init__.py @@ -0,0 +1,4 @@ +# BotCode/keyboards/reply_kb/__init__.py +# Инициализация модуля reply_kb, для reply-клавиатур + +# Экспортирование модулей во внешние слои проекта diff --git a/BotCode/routers/__init__.py b/BotCode/routers/__init__.py new file mode 100644 index 0000000..46fe7ba --- /dev/null +++ b/BotCode/routers/__init__.py @@ -0,0 +1,4 @@ +# BotCode/routers/__init__.py +# Инициализация модуля routers, для основных роутеров + +# Экспортирование модулей во внешние слои проекта diff --git a/BotLibrary/__init__.py b/BotLibrary/__init__.py index aa61b07..e43fcf3 100644 --- a/BotLibrary/__init__.py +++ b/BotLibrary/__init__.py @@ -1,6 +1,9 @@ -# ProjectsFiles/__init__.py -# Инициализация пакета ProjectsFiles, для настройки переменных проекта +# BotLibrary/__init__.py +# Инициализация пакета BotLibrary, для настройки личных библиотек проекта # Экспортирование модулей во внешние слои проекта -from .bots import * -from .logs import * +from .analytics import * +from .loggers import * +from .system import * +from .timer import * +from .validators import * diff --git a/BotLibrary/analytics/__init__.py b/BotLibrary/analytics/__init__.py new file mode 100644 index 0000000..f64cb57 --- /dev/null +++ b/BotLibrary/analytics/__init__.py @@ -0,0 +1,5 @@ +# BotLibrary/analytics/__init__.py +# Инициализация пакета analytics, для аналитики и проверки типов + +# Экспортирование модулей во внешние слои проекта +from .type_msg import * diff --git a/BotLibrary/analytics/type_msg.py b/BotLibrary/analytics/type_msg.py new file mode 100644 index 0000000..42dc661 --- /dev/null +++ b/BotLibrary/analytics/type_msg.py @@ -0,0 +1,63 @@ +# BotLibrary/analytics/log_type.py +# Определение типа сообщения + +from aiogram.types import ContentType + +def types_message(message): + # Словарь для соответствия типов сообщений + content_types = { + ContentType.TEXT: "Текст", + ContentType.PHOTO: "Фото", + ContentType.STICKER: "Стикер", + ContentType.ANIMATION: "Гиф", + ContentType.VOICE: "Голосовое сообщение", + ContentType.VIDEO_NOTE: "Видео-сообщение", + ContentType.VIDEO: "Видео", + ContentType.AUDIO: "Аудио", + ContentType.DOCUMENT: "Документ", + ContentType.CONTACT: "Контакт", + ContentType.LOCATION: "Локация", + ContentType.VENUE: "Место", + ContentType.DICE: "Бросок кубика", + ContentType.STORY: "История", + ContentType.GAME: "Игра", + ContentType.POLL: "Опрос", + ContentType.FORUM_TOPIC_CREATED: "Создание темы на форуме", + ContentType.FORUM_TOPIC_EDITED: "Редактирование темы форума", + ContentType.FORUM_TOPIC_CLOSED: "Закрытие темы форума", + ContentType.FORUM_TOPIC_REOPENED: "Открытие темы форума", + ContentType.GENERAL_FORUM_TOPIC_HIDDEN: "Скрытие общей темы форума", + ContentType.GENERAL_FORUM_TOPIC_UNHIDDEN: "Раскрытие общей темы форума", + ContentType.GIVEAWAY_CREATED: "Создание розыгрыша", + ContentType.GIVEAWAY: "Розыгрыш", + ContentType.GIVEAWAY_WINNERS: "Победители розыгрыша", + ContentType.GIVEAWAY_COMPLETED: "Розыгрыш завершен", + ContentType.VIDEO_CHAT_SCHEDULED: "Запланированный видеочат", + ContentType.VIDEO_CHAT_STARTED: "Видеочат начат", + ContentType.VIDEO_CHAT_ENDED: "Видеочат завершен", + ContentType.VIDEO_CHAT_PARTICIPANTS_INVITED: "Участники приглашены в видеочат", + ContentType.PINNED_MESSAGE: "Закрепленное сообщение", + ContentType.INVOICE: "Счет", + ContentType.SUCCESSFUL_PAYMENT: "Успешный платеж", + ContentType.REFUNDED_PAYMENT: "Возврат платежа", + ContentType.USERS_SHARED: "Пользователи поделились", + ContentType.CHAT_SHARED: "Чат был передан", + ContentType.CONNECTED_WEBSITE: "Подключенный веб-сайт", + ContentType.WRITE_ACCESS_ALLOWED: "Разрешение на запись", + ContentType.PASSPORT_DATA: "Данные паспорта", + ContentType.PROXIMITY_ALERT_TRIGGERED: "Срабатывание предупреждения о близости", + ContentType.BOOST_ADDED: "Буст чата", + ContentType.CHAT_BACKGROUND_SET: "Установлен фон чата" + } + + # Проверяем тип сообщения и возвращаем описание + if message.pinned_message: # Закрепленное сообщение + return content_types.get(ContentType.PINNED_MESSAGE, "Закрепленное сообщение") + + # Проверка для обычных сообщений + for content_type, description in content_types.items(): + if getattr(message, str(content_type.value)): + return description + + # Если сообщение не соответствует ни одному из типов + return "Неизвестный тип" diff --git a/BotLibrary/loggers/__init__.py b/BotLibrary/loggers/__init__.py new file mode 100644 index 0000000..fe0f819 --- /dev/null +++ b/BotLibrary/loggers/__init__.py @@ -0,0 +1,6 @@ +# BotLibrary/loggers/__init__.py +# Инициализация пакета loggers, для создания логеров + +# Экспортирование модулей во внешние слои проекта +from .logs import * +from .start_info_out import * diff --git a/BotLibrary/logs.py b/BotLibrary/loggers/logs.py similarity index 98% rename from BotLibrary/logs.py rename to BotLibrary/loggers/logs.py index 1e98412..dbb28a8 100644 --- a/BotLibrary/logs.py +++ b/BotLibrary/loggers/logs.py @@ -1,11 +1,10 @@ -# BotLibrary/logs.py +# BotLibrary/system/logs.py # Создание логгеров и их шаблон для проекта import sys from loguru import logger from ProjectsFiles.configs.config import BotLogs - # Создание обычного логгера + логгер в файл async def setup_logger(): logger.remove() # Удаляем все логгеры diff --git a/BotLibrary/loggers/start_info_out.py b/BotLibrary/loggers/start_info_out.py new file mode 100644 index 0000000..1d5ff7b --- /dev/null +++ b/BotLibrary/loggers/start_info_out.py @@ -0,0 +1,43 @@ +# BotLibrary/loggers/start_info_out.py +# Вывод данных бота в консоль для начальной проверки + +from time import sleep +from colorama import Fore + +from ProjectsFiles import Permissions +from .logs import logger +from ..system import BotInfo + + +# Функция для получения информации о боте и выводе ее в консоль и файл +def bot_info_out(): + try: + bot_name = f"Основное имя: {BotInfo.first_name}\n" + bot_post_name = f" Доп. имя: {BotInfo.last_name}\n" + bot_username = f" Юзернейм: @{BotInfo.username}\n" + bot_id = f" ID: {BotInfo.id}\n" + bot_language = f" Языковой код: {BotInfo.language_code}\n" + bot_can_join_groups = f" Может ли вступать в группы: {BotInfo.can_join_groups}\n" + bot_can_read_all_group_messages = f" Чтение всех сообщений: {BotInfo.can_read_all_group_messages}\n" + bot_is_premium = f" Является премиум-ботом: {BotInfo.is_premium}\n" + bot_added_to_attachment_menu = f" Добавлен в меню вложений: {BotInfo.added_to_attachment_menu}\n" + bot_supports_inline_queries = f" Поддерживает инлайн-запросы: {BotInfo.supports_inline_queries}\n" + bot_can_connect_to_business = f" Подключение к бизнес-аккаунтам: {BotInfo.can_connect_to_business}\n" + bot_has_main_web_app = f" Основное веб-приложение: {BotInfo.has_main_web_app}\n" + + # Формируем полный текст с выводом информации о боте + bot_all_info = (f"{bot_name} {bot_post_name} {bot_username} {bot_id} {bot_language} " + f"{bot_can_join_groups} {bot_can_read_all_group_messages} {bot_is_premium} " + f"{bot_added_to_attachment_menu} {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) + + return bot_all_info + + # Проверка на ошибку и ее логирование + except Exception as e: + logger.bind(log_type="INFO", user="Start_INFO").error(f"Ошибка при получении ID пользователя: {e}") diff --git a/BotLibrary/system/__init__.py b/BotLibrary/system/__init__.py new file mode 100644 index 0000000..3deb7e3 --- /dev/null +++ b/BotLibrary/system/__init__.py @@ -0,0 +1,6 @@ +# BotLibrary/system/__init__.py +# Инициализация пакета system, для библиотек запуска + +# Экспортирование модулей во внешние слои проекта +from .bots import * +from .bot_edit import * diff --git a/BotLibrary/system/bot_edit.py b/BotLibrary/system/bot_edit.py new file mode 100644 index 0000000..d6b5d2c --- /dev/null +++ b/BotLibrary/system/bot_edit.py @@ -0,0 +1,90 @@ +# BotLibrary/system/edit_bot.py +# Библиотека установки настроек бота через проект и конфиги + +from aiogram.types import ChatAdministratorRights +from ProjectsFiles import BotEdit +from .bots import bot +from ..loggers import logger + +# Настройка логирования +log_type = "Edit" + +# Функция для выполнения всех настроек, если они не совпадают +async def set_all(): + await set_adm_rights() + await set_bot_name() + await set_bot_description() + await set_bot_short_description() + + +# Функция установки прав администратора +async def set_adm_rights(): + # Применить права администратора для бота + rights = 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 = await bot.get_my_default_administrator_rights() + if current_rights != rights: + await bot.set_my_default_administrator_rights(rights) + + +# Функция установки имени бота с проверкой на ограничения +async def set_bot_name(): + # Получаем текущее имя бота + current_name = (await bot.get_me()).first_name + + # Проверка длины имени + if len(BotEdit.name) < 1 or len(BotEdit.name) > 32: + # Логируем ошибку, если имя не соответствует ограничению + (logger.bind(log_type=log_type, user="NAME_BOT") + .error("Имя бота должно быть от 1 до 32 символов.")) + + # Проверяем, совпадает ли текущее имя с тем, которое мы хотим установить + if current_name != BotEdit.name: + await bot.set_my_name(BotEdit.name) + + +# Функция установки описания бота с проверкой на ограничения +async def set_bot_description(): + # Получаем текущее описание бота + current_description = await bot.get_my_description() + + # Проверка длины описания + if len(BotEdit.description) > 255: + (logger.bind(log_type=log_type, user="DISCRIPT") + .error("Короткое описание бота не может превышать 255 символов.")) + + # Проверяем, совпадает ли текущее описание с тем, которое мы хотим установить + if current_description != BotEdit.description: + await bot.set_my_description(description=BotEdit.description) + + +# Функция установки короткого описания бота с проверкой на ограничения +async def set_bot_short_description(): + # Получаем текущее короткое описание бота + current_short_description = await bot.get_my_short_description() + + # Проверка длины короткого описания + if len(BotEdit.short_description) > 512: + (logger.bind(log_type=log_type, user="SHORT_DISCRIPT") + .error("Описание виджета не может превышать 512 символов.")) + + # Проверяем, совпадает ли текущее короткое описание с тем, которое мы хотим установить + if current_short_description != BotEdit.short_description: + await bot.set_my_short_description(short_description=BotEdit.short_description) diff --git a/BotLibrary/bots.py b/BotLibrary/system/bots.py similarity index 93% rename from BotLibrary/bots.py rename to BotLibrary/system/bots.py index a43dfd7..35191b7 100644 --- a/BotLibrary/bots.py +++ b/BotLibrary/system/bots.py @@ -1,27 +1,29 @@ -# BotLibrary/library/bots.py +# BotLibrary/system/bots.py # Создание и настройка бота в одном файле from aiogram import Dispatcher, Bot, F from aiogram.enums import ParseMode from aiogram.client.default import DefaultBotProperties -from aiogram.utils.keyboard import InlineKeyboardBuilder -from apscheduler.schedulers.asyncio import AsyncIOScheduler +from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder + +from ..timer import * from ProjectsFiles import * # Создание экземпляра диспатчера, строителей кнопок dp = Dispatcher() +rkb = ReplyKeyboardBuilder() ikb = InlineKeyboardBuilder() # Настройка параметров диспатчера -dp["started_at"] = None -dp["started_at_msk"] = None +dp["started_at"] = get_host_time() +dp["started_at_msk"] = get_moscow_time() dp["is_active"] = True # Флаг активности бота dp["logs"] = [] dp["users"] = {} dp["sessions"] = {} dp["task_queue"] = [] -dp["configs"] = {"max_connections": 100, "retry_interval": 5, "time_format": None} +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"] = {} @@ -41,7 +43,6 @@ bot_properties = DefaultBotProperties( show_caption_above_media=False, # Показываем подпись выше медиа ) bot = Bot(token=bot_token, default=bot_properties) # Объявление бота -scheduler = AsyncIOScheduler(timezone=None) # Создание планировщика F_Media = F.photo | F.files | F.video | F.animation | F.voice | F.video_note # Фильтр-медиа F_All = F.text | F.photo | F.files | F.video | F.animation | F.voice | F.video_note # Фильтр на все diff --git a/BotLibrary/timer/__init__.py b/BotLibrary/timer/__init__.py new file mode 100644 index 0000000..19a22f7 --- /dev/null +++ b/BotLibrary/timer/__init__.py @@ -0,0 +1,5 @@ +# BotLibrary/timer/__init__.py +# Инициализация модуля timers, для проверки данных + +# Экспортирование модулей во внешние слои проекта +from .start_time import * diff --git a/BotLibrary/timer/start_time.py b/BotLibrary/timer/start_time.py new file mode 100644 index 0000000..d41be7b --- /dev/null +++ b/BotLibrary/timer/start_time.py @@ -0,0 +1,23 @@ +# BotLibrary/timer/start_time.py +# Получение времени по + +import pytz +from datetime import datetime +from apscheduler.schedulers.asyncio import AsyncIOScheduler +from ProjectsFiles import BotVar + +# Функция получение времени по Московскому времени +def get_moscow_time(): + moscow_tz = pytz.timezone('Europe/Moscow') + moscow_time = datetime.now(moscow_tz) + return moscow_time.strftime(BotVar.time_format) + + +# Функция получение времени хоста +def get_host_time(): + host_time = datetime.now() + return host_time.strftime(BotVar.time_format) + + +# Создание планировщика +scheduler = AsyncIOScheduler(timezone=get_moscow_time()) diff --git a/BotLibrary/validators/__init__.py b/BotLibrary/validators/__init__.py new file mode 100644 index 0000000..1cefde8 --- /dev/null +++ b/BotLibrary/validators/__init__.py @@ -0,0 +1,5 @@ +# BotLibrary/validators/__init__.py +# Инициализация модуля validators, для проверки данных + +# Экспортирование модулей во внешние слои проекта +from .email_valid import * diff --git a/BotLibrary/validators/email_valid.py b/BotLibrary/validators/email_valid.py new file mode 100644 index 0000000..b63e201 --- /dev/null +++ b/BotLibrary/validators/email_valid.py @@ -0,0 +1,16 @@ +# BotLibrary/validators/email_validators.py +# Создание валидации почты для проекта + +from email_validator import validate_email, EmailNotValidError + +# Настройка экспорта из этого модуля +__all__ = ("valid_email",) + + +# Функция проверки почты на корректность +def valid_email(text: str) -> str | None: + try: + email = validate_email(text) + except EmailNotValidError: + return None + return email.normalized \ No newline at end of file diff --git a/ProjectsFiles/configs/config.py b/ProjectsFiles/configs/config.py index 3aafd8a..9b78296 100644 --- a/ProjectsFiles/configs/config.py +++ b/ProjectsFiles/configs/config.py @@ -3,10 +3,16 @@ # Список разрешений для бота class Permissions: - bot_edit = False - logging = True - logging_to_file = False - msg_logging = False + bot_edit = False # Изменение имени, описания и виджета (True) + delete_webhook = True # Удаление веб-хука (True) + + logging = True # Вывод логов в консоль (True) + logging_to_file = False # Вывод логов в файл (True) + msg_logging = False # Логирование сообщений (В разработке) + + start_info_console = True # Вывод информации о боте в начале (True) + + sql_user = True # Регистрирование в базу данных (True) # Имя, описание и виджет бота(при наличии баннера виджета) @@ -15,7 +21,31 @@ class BotEdit: permission = Permissions.bot_edit name = "Стартовый бот" description = "Описание бота" - widget_description = "Описание виджета" + short_description = "Описание виджета" + + is_anonymous=False + manage_chat=True + delete_messages=True + manage_video_chats=True + restrict_members=True + promote_members=True + change_info=True + invite_users=True + post_stories=True + edit_stories=True + delete_stories=True + post_messages=True + edit_messages=True + pin_messages=True + manage_topics=True + + +# Хранение параметров проекта +class BotVar: + encod = "utf-8" + language = "Python3-Aiogram" + time_format = "%Y-%m-%d %H:%M:%S" + prefix = ('$', '!', '.', '%', '&', ':', '|', '+', '-', '/', '~', '?') # Класс создания директорий проекта @@ -23,8 +53,6 @@ class ProjectPath: BotLogs = "BotLogs" - - # Настройки логирования бота class BotLogs: # Разрешение на ведение логов @@ -62,11 +90,3 @@ class BotLogs: "ERROR-{extra[log_type]} | " "{extra[user]} | {message}" ) - - -# Хранение параметров проекта -class BotVar: - encod = "utf-8" - language = "Python3-Aiogram" - time_format = "%Y-%m-%d %H:%M:%S" - prefix = ('$', '!', '.', '%', '&', ':', '|', '+', '-', '/', '~', '?') diff --git a/SQLite3/__init__.py b/SQLite3/__init__.py new file mode 100644 index 0000000..09c15bf --- /dev/null +++ b/SQLite3/__init__.py @@ -0,0 +1,5 @@ +# SQLite3/__init__.py +# Инициализация пакета SQLite3, для базы данных проекта + +# Экспортирование модулей во внешние слои проекта +from .bd import * diff --git a/SQLite3/bd.py b/SQLite3/bd.py new file mode 100644 index 0000000..8e9923b --- /dev/null +++ b/SQLite3/bd.py @@ -0,0 +1,23 @@ +# SQLite3/bd.py +# Файл для работы с базой данных пользователей бота + +import sqlite3 + +# Функция создания базы данных +def create_user_db(bd_name : str = 'bd_user.db'): + db = sqlite3.connect(bd_name) + cursor = db.cursor() + cursor.execute(''' + CREATE TABLE IF NOT EXISTS users ( + user_id INTEGER PRIMARY KEY, + tg_id INTEGER NOT NULL, + username TEXT, + first_name TEXT, + last_name TEXT, + status TEXT, + last_message TEXT, + last_message_time TIMESTAMP, + UNIQUE(tg_id) + );''') + db.commit() + db.close() diff --git a/SQLite3/bd_user.db b/SQLite3/bd_user.db new file mode 100644 index 0000000..ded9a83 Binary files /dev/null and b/SQLite3/bd_user.db differ diff --git a/main.py b/main.py index b9f67cf..435449a 100644 --- a/main.py +++ b/main.py @@ -11,9 +11,14 @@ async def main(): await bot_get_info() logger.bind(log_type="AEP", user="@Console").info(f"Начало запуска бота @{BotInfo.username}...") + + + # Нужно ли удалить веб-хук + if Permissions.delete_webhook: + await bot.delete_webhook() + # Включение опроса бота await dp.start_polling(bot) - await bot.delete_webhook() # Вечная загрузка бота diff --git a/poetry.lock b/poetry.lock index 1a72677..513e07d 100644 --- a/poetry.lock +++ b/poetry.lock @@ -245,6 +245,27 @@ files = [ {file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, ] +[[package]] +name = "dnspython" +version = "2.7.0" +description = "DNS toolkit" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "dnspython-2.7.0-py3-none-any.whl", hash = "sha256:b4c34b7d10b51bcc3a5071e7b8dee77939f1e878477eeecc965e9835f63c6c86"}, + {file = "dnspython-2.7.0.tar.gz", hash = "sha256:ce9c432eda0dc91cf618a5cedf1a4e142651196bbcd2c80e89ed5a907e5cfaf1"}, +] + +[package.extras] +dev = ["black (>=23.1.0)", "coverage (>=7.0)", "flake8 (>=7)", "hypercorn (>=0.16.0)", "mypy (>=1.8)", "pylint (>=3)", "pytest (>=7.4)", "pytest-cov (>=4.1.0)", "quart-trio (>=0.11.0)", "sphinx (>=7.2.0)", "sphinx-rtd-theme (>=2.0.0)", "twine (>=4.0.0)", "wheel (>=0.42.0)"] +dnssec = ["cryptography (>=43)"] +doh = ["h2 (>=4.1.0)", "httpcore (>=1.0.0)", "httpx (>=0.26.0)"] +doq = ["aioquic (>=1.0.0)"] +idna = ["idna (>=3.7)"] +trio = ["trio (>=0.23)"] +wmi = ["wmi (>=1.5.1)"] + [[package]] name = "dotenv" version = "0.9.9" @@ -259,6 +280,22 @@ files = [ [package.dependencies] python-dotenv = "*" +[[package]] +name = "email-validator" +version = "2.2.0" +description = "A robust email address syntax and deliverability validation library." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "email_validator-2.2.0-py3-none-any.whl", hash = "sha256:561977c2d73ce3611850a06fa56b414621e0c8faa9d66f2611407d87465da631"}, + {file = "email_validator-2.2.0.tar.gz", hash = "sha256:cb690f344c617a714f22e66ae771445a1ceb46821152df8e165c5f9a364582b7"}, +] + +[package.dependencies] +dnspython = ">=2.0.0" +idna = ">=2.0.0" + [[package]] name = "frozenlist" version = "1.5.0" @@ -750,6 +787,18 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "pytz" +version = "2025.1" +description = "World timezone definitions, modern and historical" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "pytz-2025.1-py2.py3-none-any.whl", hash = "sha256:89dd22dca55b46eac6eda23b2d72721bf1bdfef212645d81513ef5d03038de57"}, + {file = "pytz-2025.1.tar.gz", hash = "sha256:c2db42be2a2518b28e65f9207c4d05e6ff547d1efa4086469ef855e4ab70178e"}, +] + [[package]] name = "typing-extensions" version = "4.12.2" @@ -893,4 +942,4 @@ propcache = ">=0.2.0" [metadata] lock-version = "2.1" python-versions = ">=3.13" -content-hash = "4660b44e4a2807b09336cd1b1a3b30ac0a28b2ab2827c7360fd420516a5e597a" +content-hash = "8564e3708db591252a9cde80a2aea6de36aa5230bf38e7f06198dab8ef0bfdb8" diff --git a/pyproject.toml b/pyproject.toml index cdcc5bc..b13f035 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -10,7 +10,9 @@ requires-python = ">=3.13" dependencies = [ "aiogram (>=3.18.0,<4.0.0)", "dotenv (>=0.9.9,<0.10.0)", - "apscheduler (>=3.11.0,<4.0.0)" + "apscheduler (>=3.11.0,<4.0.0)", + "email-validator (>=2.2.0,<3.0.0)", + "pytz (>=2025.1,<2026.0)" ]