Вторая часть: Небольшие дополнения и структура

This commit is contained in:
Verum
2025-02-22 08:49:55 +07:00
parent 075a610728
commit 3dcd49a3cc
28 changed files with 435 additions and 34 deletions

2
.gitignore vendored
View File

@@ -4,6 +4,8 @@
# Игнорирование локального окружения и пользовательских конфигураций IDE # Игнорирование локального окружения и пользовательских конфигураций IDE
.* .*
.idea
.venv/
*.iml *.iml

11
.idea/PRIMOWORLD.iml generated
View File

@@ -2,8 +2,17 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/BotCode" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/Documentation" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/Documentation" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/BotCode/inline" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/BotCode/keyboards" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/BotCode/keyboards/inline_kb" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/BotCode/keyboards/reply_kb" isTestSource="true" />
<sourceFolder url="file://$MODULE_DIR$/BotLibrary/analytics" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/BotLibrary/loggers" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/BotLibrary/system" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/BotLibrary/timer" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/BotLibrary/validators" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/SQLite3" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/.venv" /> <excludeFolder url="file://$MODULE_DIR$/.venv" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.13 (PRIMOWORLD)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="Python 3.13 (PRIMOWORLD)" jdkType="Python SDK" />

View File

@@ -1,5 +1,6 @@
# BotCode/__init__.py # BotCode/__init__.py
# Инициализация основного модуля BotCode, для настройки переменных проекта # Инициализация пакета BotCode, для создания кода проекта
# Экспортирование модулей во внешние слои проекта # Экспортирование модулей во внешние слои проекта
from .keyboards import *
from .routers import *

View File

@@ -0,0 +1,4 @@
# BotCode/inline/__init__.py
# Инициализация модуля inline, для основных роутеров
# Экспортирование модулей во внешние слои проекта

View File

@@ -0,0 +1,4 @@
# BotCode/keyboards/__init__.py
# Инициализация модуля keyboards, для создания клавиатур
# Экспортирование модулей во внешние слои проекта

View File

@@ -0,0 +1,4 @@
# BotCode/keyboards/inline_kb/__init__.py
# Инициализация модуля inline_kb, для inline-клавиатур
# Экспортирование модулей во внешние слои проекта

View File

@@ -0,0 +1,4 @@
# BotCode/keyboards/reply_kb/__init__.py
# Инициализация модуля reply_kb, для reply-клавиатур
# Экспортирование модулей во внешние слои проекта

View File

@@ -0,0 +1,4 @@
# BotCode/routers/__init__.py
# Инициализация модуля routers, для основных роутеров
# Экспортирование модулей во внешние слои проекта

View File

@@ -1,6 +1,9 @@
# ProjectsFiles/__init__.py # BotLibrary/__init__.py
# Инициализация пакета ProjectsFiles, для настройки переменных проекта # Инициализация пакета BotLibrary, для настройки личных библиотек проекта
# Экспортирование модулей во внешние слои проекта # Экспортирование модулей во внешние слои проекта
from .bots import * from .analytics import *
from .logs import * from .loggers import *
from .system import *
from .timer import *
from .validators import *

View File

@@ -0,0 +1,5 @@
# BotLibrary/analytics/__init__.py
# Инициализация пакета analytics, для аналитики и проверки типов
# Экспортирование модулей во внешние слои проекта
from .type_msg import *

View File

@@ -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 "Неизвестный тип"

View File

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

View File

@@ -1,11 +1,10 @@
# BotLibrary/logs.py # BotLibrary/system/logs.py
# Создание логгеров и их шаблон для проекта # Создание логгеров и их шаблон для проекта
import sys import sys
from loguru import logger from loguru import logger
from ProjectsFiles.configs.config import BotLogs from ProjectsFiles.configs.config import BotLogs
# Создание обычного логгера + логгер в файл # Создание обычного логгера + логгер в файл
async def setup_logger(): async def setup_logger():
logger.remove() # Удаляем все логгеры logger.remove() # Удаляем все логгеры

View File

@@ -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}")

View File

@@ -0,0 +1,6 @@
# BotLibrary/system/__init__.py
# Инициализация пакета system, для библиотек запуска
# Экспортирование модулей во внешние слои проекта
from .bots import *
from .bot_edit import *

View File

@@ -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)

View File

@@ -1,27 +1,29 @@
# BotLibrary/library/bots.py # BotLibrary/system/bots.py
# Создание и настройка бота в одном файле # Создание и настройка бота в одном файле
from aiogram import Dispatcher, Bot, F from aiogram import Dispatcher, Bot, F
from aiogram.enums import ParseMode from aiogram.enums import ParseMode
from aiogram.client.default import DefaultBotProperties from aiogram.client.default import DefaultBotProperties
from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.utils.keyboard import InlineKeyboardBuilder, ReplyKeyboardBuilder
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from ..timer import *
from ProjectsFiles import * from ProjectsFiles import *
# Создание экземпляра диспатчера, строителей кнопок # Создание экземпляра диспатчера, строителей кнопок
dp = Dispatcher() dp = Dispatcher()
rkb = ReplyKeyboardBuilder()
ikb = InlineKeyboardBuilder() ikb = InlineKeyboardBuilder()
# Настройка параметров диспатчера # Настройка параметров диспатчера
dp["started_at"] = None dp["started_at"] = get_host_time()
dp["started_at_msk"] = None dp["started_at_msk"] = get_moscow_time()
dp["is_active"] = True # Флаг активности бота dp["is_active"] = True # Флаг активности бота
dp["logs"] = [] dp["logs"] = []
dp["users"] = {} dp["users"] = {}
dp["sessions"] = {} dp["sessions"] = {}
dp["task_queue"] = [] 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["metrics"] = {"messages_received": 0, "messages_sent": 0, "errors": 0}
dp["modules"] = {} dp["modules"] = {}
dp["state"] = {} dp["state"] = {}
@@ -41,7 +43,6 @@ bot_properties = DefaultBotProperties(
show_caption_above_media=False, # Показываем подпись выше медиа show_caption_above_media=False, # Показываем подпись выше медиа
) )
bot = Bot(token=bot_token, default=bot_properties) # Объявление бота 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_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 # Фильтр на все F_All = F.text | F.photo | F.files | F.video | F.animation | F.voice | F.video_note # Фильтр на все

View File

@@ -0,0 +1,5 @@
# BotLibrary/timer/__init__.py
# Инициализация модуля timers, для проверки данных
# Экспортирование модулей во внешние слои проекта
from .start_time import *

View File

@@ -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())

View File

@@ -0,0 +1,5 @@
# BotLibrary/validators/__init__.py
# Инициализация модуля validators, для проверки данных
# Экспортирование модулей во внешние слои проекта
from .email_valid import *

View File

@@ -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

View File

@@ -3,10 +3,16 @@
# Список разрешений для бота # Список разрешений для бота
class Permissions: class Permissions:
bot_edit = False bot_edit = False # Изменение имени, описания и виджета (True)
logging = True delete_webhook = True # Удаление веб-хука (True)
logging_to_file = False
msg_logging = False 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 permission = Permissions.bot_edit
name = "Стартовый бот" name = "Стартовый бот"
description = "Описание бота" 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" BotLogs = "BotLogs"
# Настройки логирования бота # Настройки логирования бота
class BotLogs: class BotLogs:
# Разрешение на ведение логов # Разрешение на ведение логов
@@ -62,11 +90,3 @@ class BotLogs:
"<bold>ERROR-{extra[log_type]}</bold> | " "<bold>ERROR-{extra[log_type]}</bold> | "
"{extra[user]} | {message}</level>" "{extra[user]} | {message}</level>"
) )
# Хранение параметров проекта
class BotVar:
encod = "utf-8"
language = "Python3-Aiogram"
time_format = "%Y-%m-%d %H:%M:%S"
prefix = ('$', '!', '.', '%', '&', ':', '|', '+', '-', '/', '~', '?')

5
SQLite3/__init__.py Normal file
View File

@@ -0,0 +1,5 @@
# SQLite3/__init__.py
# Инициализация пакета SQLite3, для базы данных проекта
# Экспортирование модулей во внешние слои проекта
from .bd import *

23
SQLite3/bd.py Normal file
View File

@@ -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()

BIN
SQLite3/bd_user.db Normal file

Binary file not shown.

View File

@@ -11,9 +11,14 @@ async def main():
await bot_get_info() await bot_get_info()
logger.bind(log_type="AEP", user="@Console").info(f"Начало запуска бота @{BotInfo.username}...") 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 dp.start_polling(bot)
await bot.delete_webhook()
# Вечная загрузка бота # Вечная загрузка бота

51
poetry.lock generated
View File

@@ -245,6 +245,27 @@ files = [
{file = "certifi-2025.1.31.tar.gz", hash = "sha256:3d5da6925056f6f18f119200434a4780a94263f10d1c21d032a6f6b2baa20651"}, {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]] [[package]]
name = "dotenv" name = "dotenv"
version = "0.9.9" version = "0.9.9"
@@ -259,6 +280,22 @@ files = [
[package.dependencies] [package.dependencies]
python-dotenv = "*" 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]] [[package]]
name = "frozenlist" name = "frozenlist"
version = "1.5.0" version = "1.5.0"
@@ -750,6 +787,18 @@ files = [
[package.extras] [package.extras]
cli = ["click (>=5.0)"] 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]] [[package]]
name = "typing-extensions" name = "typing-extensions"
version = "4.12.2" version = "4.12.2"
@@ -893,4 +942,4 @@ propcache = ">=0.2.0"
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.13" python-versions = ">=3.13"
content-hash = "4660b44e4a2807b09336cd1b1a3b30ac0a28b2ab2827c7360fd420516a5e597a" content-hash = "8564e3708db591252a9cde80a2aea6de36aa5230bf38e7f06198dab8ef0bfdb8"

View File

@@ -10,7 +10,9 @@ requires-python = ">=3.13"
dependencies = [ dependencies = [
"aiogram (>=3.18.0,<4.0.0)", "aiogram (>=3.18.0,<4.0.0)",
"dotenv (>=0.9.9,<0.10.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)"
] ]