From a4e2779b4a474dffe5b78982913b750b1ea1ca43 Mon Sep 17 00:00:00 2001 From: Alekzum <68947902+Alekzum@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:40:25 +0400 Subject: [PATCH 1/4] add .env.example --- .env.example | 14 ++++++++++++++ readme.md | 33 +++++++++------------------------ 2 files changed, 23 insertions(+), 24 deletions(-) create mode 100644 .env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..b4a3e66 --- /dev/null +++ b/.env.example @@ -0,0 +1,14 @@ +TZ=Europe/Moscow # TZ identifier + +BOT_TOKEN=1234343:AABBCCEE # https://botfather.t.me +ADMIN_IDS=77000,77001 # https://myidbot.t.me +CRYPTO_TOKEN=1234343:AABBOOCCEE # https://send.t.me +FLYER_TOKEN=FL-lekLE-lekLE-lekLE-lekLE # https://FlyerServiceBot.t.me + +# Database Configuration +DB_DRIVER=postgresql+asyncpg +DB_HOST=ip +DB_PORT=5432 +DB_NAME=folt_copy +DB_USER=user +DB_PASSWORD="password" \ No newline at end of file diff --git a/readme.md b/readme.md index 45a7f6f..e0d28aa 100644 --- a/readme.md +++ b/readme.md @@ -1,37 +1,22 @@ # CardsBot for funny in Telegram + ## Запуск бота -1. заполнить файл .env -```.env -BOT_TOKEN=1234343:AABBCCEE -ADMIN_IDS=77000,77001 -CRYPTO_TOKEN=1234343:AABBOOCCEE -FLYER_TOKEN=FL-lekLE-lekLE-lekLE-lekLE - -# Database Configuration -DB_DRIVER=postgresql+asyncpg -DB_HOST=ip -DB_PORT=5432 -DB_NAME=folt_copy -DB_USER=user -DB_PASSWORD="password" -``` -2. Нужен чтобы запустился хотя-бы 1 раз дальше для старта использовать из пункта 3 -docker-compose up --build -3. docker-compose up -5. Чтобы остановить контейнер пропишите docker-compose stop +1. Скопировать `.env.example` в `.env` и заполнить файл +2. Для запуска контейнера использовать `docker-compose up` +3. Для остановки контейнера прописать `docker-compose stop` ## Файлы для заполнения -1. data/config.json -2. data/text.py -3. utils/kb.py -4. config.yaml +1. .env +2. data/config.json +3. data/text.py +4. utils/kb.py 5. filters\CardFilter.py 6. filters\ProfileFilter.py -## Дополнительные условия использования +## Дополнительные условия использования Если вы хотите изменить или перераспространить код, вы должны: 1. Создать форк репозитория (fork). 2. Вносить изменения в ваш форк и ссылаться на оригинальный репозиторий. From 32f6d9f43a6ca5624f8b57dd85807dec5d0e1884 Mon Sep 17 00:00:00 2001 From: Alekzum <68947902+Alekzum@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:42:00 +0400 Subject: [PATCH 2/4] trying into docker-compose db-1 | Error: Database is uninitialized and superuser password is not specified. --- database/__init__.py | 2 +- docker-compose.yaml | 41 ++++++++++++++++++++++++++++++----------- dockerfile | 23 +++++++++++++++++++---- init_db.sh | 16 ++++++++++++++++ main.py | 4 ++-- 5 files changed, 68 insertions(+), 18 deletions(-) create mode 100644 init_db.sh diff --git a/database/__init__.py b/database/__init__.py index c8b7913..715ccae 100644 --- a/database/__init__.py +++ b/database/__init__.py @@ -3,6 +3,6 @@ from utils.loader import engine -async def setup_db(): +async def init_db(): async with engine.begin() as conn: await conn.run_sync(Base.metadata.create_all) diff --git a/docker-compose.yaml b/docker-compose.yaml index cf8d0d5..874bc49 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -1,28 +1,47 @@ -version: '3.3' - services: + db: + image: postgres:16 + ports: + - "5432:5432" + restart: always + environment: + - DB_HOST=db + - DB_PORT=${DB_PORT} + - DB_USER=${DB_USER} + - DB_PASSWORD=${DB_PASSWORD} + - DB_NAME=${DB_NAME} + volumes: + - postgres_data:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready --host=${DB_HOST} --port=${DB_PORT} -U ${DB_USER}"] + interval: 5s + timeout: 5s + retries: 5 + networks: + - app_network + app: build: . container_name: cardsbot environment: - - TZ=Europe/Moscow + - TZ=${TZ} - BOT_TOKEN=${BOT_TOKEN} - ADMIN_IDS=${ADMIN_IDS} - CRYPTO_TOKEN=${CRYPTO_TOKEN} - FLYER_TOKEN=${FLYER_TOKEN} - - DB_DRIVER=${DB_DRIVER} - - DB_HOST=${DB_HOST} - - DB_PORT=${DB_PORT} - - DB_NAME=${DB_NAME} - - DB_USER=${DB_USER} - - DB_PASSWORD=${DB_PASSWORD} - networks: - - app_network volumes: - .:/app - /etc/localtime:/etc/localtime:ro - /etc/timezone:/etc/timezone:ro restart: always + depends_on: + - db + networks: + - app_network + networks: app_network: driver: bridge + +volumes: + postgres_data: diff --git a/dockerfile b/dockerfile index b4b9915..f57ae97 100644 --- a/dockerfile +++ b/dockerfile @@ -1,17 +1,32 @@ -# Используем официальный образ Python 3.12 -FROM python:3.12-slim +# Используем официальный образ Python 3.13 +FROM python:3.13-slim # Устанавливаем рабочую директорию WORKDIR /app -# Копируем файл зависимостей в контейнер -COPY requirements.txt . +# Обновляем системные требования +RUN apt-get update && apt-get install -y \ + build-essential \ + libpq-dev \ + postgresql-client \ + && rm -rf /var/lib/apt/lists/* +# Копируем файл зависимостей в контейнер # Устанавливаем все зависимости из requirements.txt +COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt # Копируем весь проект в контейнер COPY . . +# Make init_db.sh executable +# Create a non-root user +RUN chmod +x init_db.sh +RUN useradd -m appuser && chown -R appuser:appuser /app +USER appuser + +# Add app directory to Python path +ENV PYTHONPATH=/app + # Запускаем бота CMD ["python3", "main.py"] diff --git a/init_db.sh b/init_db.sh new file mode 100644 index 0000000..b566e47 --- /dev/null +++ b/init_db.sh @@ -0,0 +1,16 @@ +#!/bin/sh + +echo "Waiting for PostgreSQL to be ready..." +while ! pg_isready -h ${DB_HOST} -p ${DB_PORT} -U ${DB_USER}; do + sleep 1 +done + +echo "Creating database tables..." +python -c " +from database import init_db +import asyncio + +asyncio.run(init_db()) +" + +echo "Database initialization completed!" \ No newline at end of file diff --git a/main.py b/main.py index 2157c20..df6ad27 100644 --- a/main.py +++ b/main.py @@ -11,7 +11,7 @@ import logging from utils import loader -from database import setup_db +from database import init_db from database.cards import parse_cards, parse_limited_cards from handlers import commands_router, premium_router, profile_router, text_triggers_router, shop_router, shop_cards_router from handlers.admin_dialogs import dialogs_router @@ -23,7 +23,7 @@ logger = logging.getLogger(__name__) async def main(): - await setup_db() + await init_db() dp.include_routers(commands_router, profile_router, premium_router, shop_router, dialogs_router, shop_cards_router, From 5d89bd3a9def38ab0be059405ffd0d0f3859b3ee Mon Sep 17 00:00:00 2001 From: Alekzum <68947902+Alekzum@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:42:43 +0400 Subject: [PATCH 3/4] add description to example card with id 4 --- data/config.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/data/config.json b/data/config.json index bedb8ff..65d1721 100644 --- a/data/config.json +++ b/data/config.json @@ -29,7 +29,8 @@ "name": "Карта4", "photo": "https://tinypic.host/", "points": "10000", - "rarity": "Легендарная" + "rarity": "Легендарная", + "description": "Описание" } ] } \ No newline at end of file From f475d4a8dae6156da62e73f43061b0bf1e789c70 Mon Sep 17 00:00:00 2001 From: Alekzum <68947902+Alekzum@users.noreply.github.com> Date: Fri, 6 Jun 2025 23:58:32 +0400 Subject: [PATCH 4/4] formatting (via Prettier), add NotFoundGroupError exceptions at database\group --- data/text.py | 53 ++-- database/bonus_link.py | 47 +-- database/cards.py | 155 ++++++---- database/group.py | 85 ++++-- database/models.py | 55 ++-- database/premium.py | 26 +- database/promo.py | 28 +- database/ref_link.py | 14 +- database/statistic.py | 114 ++++--- database/top.py | 55 ++-- database/user.py | 144 +++++---- filters/CardFilter.py | 18 +- filters/FloodWait.py | 10 +- filters/NotCommentFilter.py | 1 - filters/ProfileFilter.py | 1 - handlers/__init__.py | 2 +- handlers/admin_dialogs/__init__.py | 6 +- .../add_admin_dialogs/add_admin_dialog.py | 39 ++- handlers/admin_dialogs/admin_states.py | 2 +- .../admin_dialogs/ban_dialogs/__init__.py | 2 +- .../admin_dialogs/ban_dialogs/ban_dialog.py | 39 ++- .../admin_dialogs/ban_dialogs/unban_dialog.py | 65 ++-- .../admin_dialogs/base_dialogs/__init__.py | 8 +- .../base_dialogs/admin_dialog.py | 86 ++++-- .../base_dialogs/change_nickname_dialog.py | 39 ++- .../base_dialogs/mailing_dialog.py | 75 +++-- .../base_dialogs/premium_dialog.py | 70 +++-- .../base_dialogs/season_delete_dialog.py | 29 +- .../admin_dialogs/promo_dialogs/__init__.py | 2 +- .../promo_dialogs/create_promo_dialog.py | 83 ++++-- .../promo_dialogs/delete_promo_dialog.py | 21 +- .../ref_link_dialogs/__init__.py | 2 +- .../ref_link_dialogs/ref_links_add_dialog.py | 30 +- .../ref_link_dialogs/ref_links_view_dialog.py | 60 ++-- handlers/commands.py | 119 +++++--- handlers/premium.py | 68 +++-- handlers/profile.py | 282 +++++++++++++----- handlers/shop.py | 129 +++++--- handlers/shopcards.py | 192 ++++++------ handlers/triggers.py | 171 +++++++---- utils/config.py | 2 +- 41 files changed, 1581 insertions(+), 848 deletions(-) diff --git a/data/text.py b/data/text.py index 889cc97..03412ac 100644 --- a/data/text.py +++ b/data/text.py @@ -1,27 +1,27 @@ name_card = "Карта" name_card_rod_padezh = "Карту" -WELCOME_MESSAGE_PRIVATE = f'''👋 Привет! Тут ты можешь собирать уникальные карточки и соревноваться с другими игроками\n\nКак получить карточки?\n
Отправь команду «{name_card}»
\n\nУзнать все функции можно по команде /help''' +WELCOME_MESSAGE_PRIVATE = f"""👋 Привет! Тут ты можешь собирать уникальные карточки и соревноваться с другими игроками\n\nКак получить карточки?\n
Отправь команду «{name_card}»
\n\nУзнать все функции можно по команде /help""" -WELCOME_MESSAGE = f'''👋 Всем привет! Я бот, в котором ты можешь собирать уникальные карточки и соревноваться с другими игроками\n +WELCOME_MESSAGE = f"""👋 Всем привет! Я бот, в котором ты можешь собирать уникальные карточки и соревноваться с другими игроками\n Как начать?\n
Напишите «{name_card}» для получения первой карточки
-\n\nУзнать все функции можно по команде /help''' +\n\nУзнать все функции можно по команде /help""" HELP_MESSAGE = ( "Что это за бот?\n" - f"
Тут ты можешь собирать карточки {name_card} и соревноваться с другими игроками
\n\n" + f"
Тут ты можешь собирать карточки {name_card} и соревноваться с другими игроками
\n\n" "Команды:\n" "
👤 /profile — ваш профиль\n✨ /name [ник] — изменить никнейм\n🃏 /cards — собирать карточки\n🏆 /top — топ игроков\n🚀 /premium — приобрести премиум \n🛍️ /shop — игровой магазин \n🎲 /diceplay — играть в кости\n🏪 /market - биржа уникальных карточек
" f"Для получения карты отправьте любую из команд
{name_card}\nКарта2 \nКарта3 \nКарта7 \nКарта5 \nКарта6
" ) -PRIVACY_MESSAGE = '''Мы обрабатываем данные пользователей строго в целях улучшения функционала нашего бота. +PRIVACY_MESSAGE = """Мы обрабатываем данные пользователей строго в целях улучшения функционала нашего бота. Гарантируем, что данные пользователя, включая идентификатор пользователя (user ID) и имя (first name), не будут переданы третьим лицам или использованы вне контекста улучшения бота. Наш приоритет — обеспечение безопасности и конфиденциальности информации, которую вы нам доверяете.\n\nДля повышения прозрачности нашей работы, мы также обязуемся предоставлять пользователю доступ к информации о том, какие данные собраны и как они используются. В случае изменения политики использования данных, мы своевременно информируем пользователей через обновления нашего пользовательского соглашения. Мы прилагаем все усилия, чтобы наш сервис был максимально безопасным и удобным для -пользователя.''' +пользователя.""" responses = [ "Уберите лапки от чужой кнопки.", @@ -34,17 +34,17 @@ "Ваши лапки попали не туда.", "Лапки в сторону!", "Ваши лапки слишком любопытны!", - "Лапки в сторону, эта кнопка охраняется." + "Лапки в сторону, эта кнопка охраняется.", ] PREMIUM_TEXT = ( - "🚀 Premium\n\n" - "
⌛️ Возможность получать карточки каждые 3 часа вместо 4\n" - "🃏 Повышенная вероятность выпадения легендарных и мифических карт\n" - "🔥 Возможность использовать смайлики в никнейме\n" - "💎 Отображение алмаза в топе карточек\n" - "⚡️ Более быстрая обработка твоих сообщений\n" - "Срок действия • 30 дней
\n\n" + "🚀 Premium\n\n" + "
⌛️ Возможность получать карточки каждые 3 часа вместо 4\n" + "🃏 Повышенная вероятность выпадения легендарных и мифических карт\n" + "🔥 Возможность использовать смайлики в никнейме\n" + "💎 Отображение алмаза в топе карточек\n" + "⚡️ Более быстрая обработка твоих сообщений\n" + "Срок действия • 30 дней
\n\n" ) forbidden_symbols = [ @@ -53,21 +53,20 @@ "\u534d", "1488", "heil", - "hitler" + "hitler", ] -shop_text = ("🛍️ Магазин\n" -"
Выберите нужную категорию:
") +shop_text = "🛍️ Магазин\n" "
Выберите нужную категорию:
" -booster_text = ("⚡️ Бустеры\n" - "
Выберите нужный бустер
") +booster_text = "⚡️ Бустеры\n" "
Выберите нужный бустер
" -coins_text = ("💰 Монеты" - "
Приобрести монеты за звезды
") +coins_text = "💰 Монеты" "
Приобрести монеты за звезды
" -confirmation_payment_text = "🌟Спасибо за покупку, на ваш баланс были начислены монеты. ({quantity})" +confirmation_payment_text = ( + "🌟Спасибо за покупку, на ваш баланс были начислены монеты. ({quantity})" +) luck_message = ( @@ -96,16 +95,12 @@ ) dice_limit = ( - "🎲 Игра «Кости»\n" - "
Будет доступно через {} минут.
" + "🎲 Игра «Кости»\n" "
Будет доступно через {} минут.
" ) settings_chat = ( - "⚙️ Настройки группы\n" - "Выберите один из параметров, который вы хотите изменить" + "⚙️ Настройки группы\n" "Выберите один из параметров, который вы хотите изменить" ) -post_msg = ( - f"Напиши «{name_card}» и я подарю тебе карточку!" -) \ No newline at end of file +post_msg = f"Напиши «{name_card}» и я подарю тебе карточку!" diff --git a/database/bonus_link.py b/database/bonus_link.py index 271ea8a..4ee1711 100644 --- a/database/bonus_link.py +++ b/database/bonus_link.py @@ -9,46 +9,53 @@ def generate_random_string(length=14): - random_string = '' + random_string = "" random_str_seq = string.ascii_letters + string.digits - for i in range(0,length): + for i in range(0, length): if i % length == 0 and i != 0: - random_string += '-' + random_string += "-" random_string += str(random_str_seq[random.randint(0, len(random_str_seq) - 1)]) return random_string -async def create_bonus_link(for_user_id: int, ) -> BonusLink: +async def create_bonus_link( + for_user_id: int, +) -> BonusLink: ref_link: str = generate_random_string() async with AsyncSession(engine, expire_on_commit=False) as session: - ref_link = BonusLink(code=ref_link, for_user_id=for_user_id) - session.add(ref_link) + ref_link_model = BonusLink(code=ref_link, for_user_id=for_user_id) + session.add(ref_link_model) await session.commit() - return ref_link + return ref_link_model async def delete_bonus_link(bonus_link: str) -> None: async with AsyncSession(engine, expire_on_commit=False) as session: - bonus_link = (await session.execute(select(BonusLink).where(BonusLink.code == bonus_link))).scalar_one_or_none() - if bonus_link is None: + bonus_link_model = ( + await session.execute(select(BonusLink).where(BonusLink.code == bonus_link)) + ).scalar_one_or_none() + if bonus_link_model is None: return - await session.delete(bonus_link) + await session.delete(bonus_link_model) await session.commit() -async def get_bonus_link(code: str) -> BonusLink: +async def get_bonus_link(code: str) -> BonusLink | None: async with AsyncSession(engine, expire_on_commit=False) as session: - return (await session.execute( - select(BonusLink).where(BonusLink.code == code) - )).scalar_one_or_none() + return ( + await session.execute(select(BonusLink).where(BonusLink.code == code)) + ).scalar_one_or_none() async def deactivate_bonus_link(code: str): async with AsyncSession(engine, expire_on_commit=False) as session: - bonus = (await session.execute( - select(BonusLink).where(BonusLink.code == code) - )).scalar_one_or_none() + bonus_model = ( + await session.execute(select(BonusLink).where(BonusLink.code == code)) + ).scalar_one_or_none() + + if bonus_model is None: + raise KeyError(f"Didn't found bonus link with code {code}") - if bonus: - bonus.is_active = False - await session.commit() \ No newline at end of file + if bonus_model is not None: + bonus_model.is_active = False # type: ignore + await session.commit() diff --git a/database/cards.py b/database/cards.py index 4e1b08c..4b6bc69 100644 --- a/database/cards.py +++ b/database/cards.py @@ -7,88 +7,111 @@ from .models import Card, LimitedCards -async def parse_cards(filename): +async def parse_cards(filename) -> None: + with open(filename, "r", encoding="utf8") as f: + data = json.load(f) + data = data["cats"] + async with AsyncSession(engine) as session: - with open(filename, 'r', encoding="utf8") as f: - data = json.load(f) - data = data['cats'] - for card in data: - card_id = int(card['id']) - - existing_card = await session.get(Card, card_id) - if existing_card: - existing_card.name = card['name'] - existing_card.points = int(card['points']) - existing_card.rarity = card['rarity'] - existing_card.photo = card['photo'] - existing_card.description = card.get('description', None) - else: - db_card = Card( - id=card_id, - name=card['name'], - points=int(card['points']), - rarity=card['rarity'], - photo=card['photo'], - description=card.get('description', None) - ) - session.add(db_card) + for card in data: + card_id = int(card["id"]) + + existing_card = await session.get(Card, card_id) + if existing_card: + existing_card.name = card["name"] + existing_card.points = int(card["points"]) + existing_card.rarity = card["rarity"] + existing_card.photo = card["photo"] + existing_card.description = card.get("description", None) + continue + + db_card = Card( + id=card_id, + name=card["name"], + points=int(card["points"]), + rarity=card["rarity"], + photo=card["photo"], + description=card.get("description", None), + ) + session.add(db_card) await session.commit() -async def parse_limited_cards(filename): +async def parse_limited_cards(filename) -> None: + with open(filename, "r", encoding="utf8") as f: + data = json.load(f) + data = data["cats"] + async with AsyncSession(engine) as session: - with open(filename, 'r', encoding="utf8") as f: - data = json.load(f) - data = data['cats'] - for card in data: - card_id = int(card['id']) - - existing_card = await session.get(LimitedCards, card_id) - if existing_card: - existing_card.name = card['name'] - existing_card.price = int(card['price']) - existing_card.edition = int(card['edition']) - existing_card.photo = card['photo'] - existing_card.description = card.get('description', None) - else: - db_card = LimitedCards( - id=card_id, - name=card['name'], - price=int(card['price']), - edition=int(card['edition']), - photo=card['photo'], - description=card.get('description', None) - ) - session.add(db_card) + for card in data: + card_id = int(card["id"]) + + existing_card = await session.get(LimitedCards, card_id) + if existing_card: + existing_card.name = card["name"] + existing_card.price = int(card["price"]) + existing_card.edition = int(card["edition"]) + existing_card.photo = card["photo"] + existing_card.description = card.get("description", None) + continue + + db_card = LimitedCards( + id=card_id, + name=card["name"], + price=int(card["price"]), + edition=int(card["edition"]), + photo=card["photo"], + description=card.get("description", None), + ) + session.add(db_card) await session.commit() -async def get_card(card_id: int): +async def get_card(card_id: int) -> Card | None: async with AsyncSession(engine) as session: - card: Card = (await session.execute(select(Card).where(Card.id == card_id))).scalar_one_or_none() + card: Card | None = ( + await session.execute(select(Card).where(Card.id == card_id)) + ).scalar_one_or_none() return card -async def get_all_cards(): +async def get_all_cards() -> list[Card]: async with AsyncSession(engine) as session: cards = (await session.execute(select(Card))).scalars().all() - return cards + return list(cards) + + +async def get_lcard(cat_id: int) -> LimitedCards | None: + async with AsyncSession(engine) as session: + cat: LimitedCards | None = ( + await session.execute(select(LimitedCards).where(LimitedCards.id == cat_id)) + ).scalar_one_or_none() + return cat -async def get_lcard(cat_id: int): - async with AsyncSession(engine) as session: - cat: LimitedCards = (await session.execute(select(LimitedCards).where(LimitedCards.id == cat_id))).scalar_one_or_none() - return cat +async def get_all_lcards() -> list[LimitedCards]: + async with AsyncSession(engine) as session: + cats = (await session.execute(select(LimitedCards))).scalars().all() + return list(cats) -async def get_all_lcards(): - async with AsyncSession(engine) as session: - cats = (await session.execute(select(LimitedCards))).scalars().all() - return cats - async def increment_buy_count(card_id: int): - async with AsyncSession(engine) as session: - card = (await session.execute(select(LimitedCards).where(LimitedCards.id == card_id))).scalar_one_or_none() - if card: - card.buy_count += 1 - await session.commit() \ No newline at end of file + """Increment card's buy count by it's ID + + Args: + card_id (int): Card's id + + Raises: + KeyError: If card didn't found + """ + async with AsyncSession(engine) as session: + card = ( + await session.execute( + select(LimitedCards).where(LimitedCards.id == card_id) + ) + ).scalar_one_or_none() + if card is None: + raise KeyError(f"Didn't found card with id {card_id}") + + card.buy_count += 1 + await session.commit() diff --git a/database/group.py b/database/group.py index 59d45ed..8bfa7fa 100644 --- a/database/group.py +++ b/database/group.py @@ -7,58 +7,93 @@ from utils.loader import engine +class NotFoundGroupError(KeyError): + id: int + def __init__(self, id: int): + self.args = (id, ) + self.message = f"Didn't found group with id {id}" + def __str__(self): + return self.message + + async def create_group(group_id: int, title: str) -> Group: async with AsyncSession(engine) as session: group = Group(group_id=group_id, title=title, comments_on=True) session.add(group) await session.commit() - group = (await session.execute(select(Group).where(Group.group_id == group_id))).scalar_one() + group = ( + await session.execute(select(Group).where(Group.group_id == group_id)) + ).scalar_one() return group - -async def set_comments_active(chat_id: int, to_set: bool) -> None: + +async def set_comments_active(group_id: int, to_set: bool) -> None: async with AsyncSession(engine) as session: - group: Group = (await session.execute(select(Group).where(Group.group_id == chat_id))).scalar_one_or_none() - - if group: - group.comments_on = to_set - await session.commit() - - updated_group = (await session.execute( - select(Group).where(Group.group_id == chat_id) - )).scalar_one_or_none() + group: Group | None = ( + await session.execute(select(Group).where(Group.group_id == group_id)) + ).scalar_one_or_none() + + if group is None: + raise NotFoundGroupError(group_id) + group.comments_on = to_set + await session.commit() + + updated_group = ( + await session.execute(select(Group).where(Group.group_id == group_id)) + ).scalar_one_or_none() async def get_group(group_id: int) -> Group: async with AsyncSession(engine) as session: - group = (await session.execute(select(Group).where(Group.group_id == group_id))).scalar_one_or_none() + group = ( + await session.execute(select(Group).where(Group.group_id == group_id)) + ).scalar_one_or_none() + if group is None: + raise NotFoundGroupError(group_id) return group async def get_group_with_bot_count(): - async with (AsyncSession(engine) as session): - group_count = (await session.execute( - select(func.count(Group.id)) - .where(Group.in_group == True)) - ).scalar_one_or_none() + async with AsyncSession(engine) as session: + group_count = ( + await session.execute( + select(func.count(Group.id)).where(Group.in_group == True) + ) + ).scalar_one_or_none() return group_count async def get_all_groups_ids(offset: int = 0, limit: int = None) -> [Group]: async with AsyncSession(engine) as session: - groups = (await session.execute(select(Group.group_id).limit(limit).offset(offset))).scalars().all() + groups = ( + (await session.execute(select(Group.group_id).limit(limit).offset(offset))) + .scalars() + .all() + ) return groups async def get_all_groups_with_bot_ids() -> [Group]: async with AsyncSession(engine) as session: - groups = (await session.execute(select(Group.group_id).where(Group.in_group == True))).scalars().all() + groups = ( + ( + await session.execute( + select(Group.group_id).where(Group.in_group == True) + ) + ) + .scalars() + .all() + ) return groups async def in_group_change(group_id: int, status: bool) -> None: async with AsyncSession(engine) as session: - group: Group = (await session.execute(select(Group).where(Group.group_id == group_id))).scalar_one_or_none() + group: Group | None = ( + await session.execute(select(Group).where(Group.group_id == group_id)) + ).scalar_one_or_none() + if group is None: + raise NotFoundGroupError(group_id) group.in_group = status await session.commit() @@ -71,13 +106,17 @@ async def get_group_count(): async def update_last_activity_group(telegram_id: int): async with AsyncSession(engine) as session: - group: Group = (await session.execute(select(Group).where(Group.group_id == telegram_id))).scalar_one_or_none() + group: Group = ( + await session.execute(select(Group).where(Group.group_id == telegram_id)) + ).scalar_one_or_none() group.last_activity = datetime.now().date() await session.commit() async def set_group_refer_code(group_id: int, code: str): async with AsyncSession(engine) as session: - group: Group = (await session.execute(select(Group).where(Group.group_id == group_id))).scalar_one_or_none() + group: Group = ( + await session.execute(select(Group).where(Group.group_id == group_id)) + ).scalar_one_or_none() group.from_link = code await session.commit() diff --git a/database/models.py b/database/models.py index 99e2b82..5854c7f 100644 --- a/database/models.py +++ b/database/models.py @@ -1,7 +1,17 @@ import datetime from typing import List -from sqlalchemy import ARRAY, BigInteger, Boolean, Column, Date, DateTime, Integer, String, VARCHAR +from sqlalchemy import ( + ARRAY, + BigInteger, + Boolean, + Column, + Date, + DateTime, + Integer, + String, + VARCHAR, +) from sqlalchemy.ext.asyncio import AsyncAttrs from sqlalchemy.dialects.postgresql import JSONB from sqlalchemy.ext.mutable import MutableList @@ -13,7 +23,7 @@ class Base(AsyncAttrs, DeclarativeBase): class User(Base): - __tablename__ = 'users' + __tablename__ = "users" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) telegram_id: Mapped[int] = mapped_column(BigInteger, unique=True, nullable=False) @@ -39,29 +49,32 @@ class User(Base): Date, nullable=True, default=datetime.datetime.now().date() ) in_pm: Mapped[bool] = mapped_column(Boolean) - status: Mapped[str] = mapped_column(VARCHAR(40), nullable=False, default="USER", server_default="USER") - last_bonus_get: Mapped[datetime.datetime] = mapped_column( - DateTime, nullable=True + status: Mapped[str] = mapped_column( + VARCHAR(40), nullable=False, default="USER", server_default="USER" ) + last_bonus_get: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True) from_link: Mapped[str] = mapped_column(String, nullable=True, default=None) coins: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) luck: Mapped[bool] = mapped_column(Boolean, default=False) - last_dice_play: Mapped[datetime.datetime] = mapped_column( - DateTime, nullable=True - ) + last_dice_play: Mapped[datetime.datetime] = mapped_column(DateTime, nullable=True) def check_promo_expired(self, promo: str) -> bool: - return promo in self.expired_promo_codes if self.expired_promo_codes is not None else False + return ( + promo in self.expired_promo_codes + if self.expired_promo_codes is not None + else False + ) def check_bonus_available(self) -> bool: if self.last_bonus_get is None: return True - return datetime.datetime.now() >= self.last_bonus_get + datetime.timedelta(hours=4) + return datetime.datetime.now() >= self.last_bonus_get + datetime.timedelta( + hours=4 + ) class App(Base): - - __tablename__ = 'app' + __tablename__ = "app" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) yesterday_users_active: Mapped[int] = mapped_column(Integer, nullable=True) @@ -69,12 +82,14 @@ class App(Base): class Group(Base): - __tablename__ = 'groups' + __tablename__ = "groups" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) group_id: Mapped[int] = mapped_column(BigInteger, unique=True, nullable=False) title: Mapped[str] = mapped_column(VARCHAR(100), nullable=False) - added_at: Mapped[datetime.date] = mapped_column(Date, nullable=False, default=datetime.datetime.now().date()) + added_at: Mapped[datetime.date] = mapped_column( + Date, nullable=False, default=datetime.datetime.now().date() + ) last_activity: Mapped[datetime.date] = mapped_column( Date, nullable=False, default=datetime.datetime.now().date() ) @@ -84,7 +99,7 @@ class Group(Base): class Card(Base): - __tablename__ = 'cards' + __tablename__ = "cards" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) name: Mapped[str] = mapped_column(VARCHAR(80), nullable=False) @@ -95,7 +110,7 @@ class Card(Base): class Promo(Base): - __tablename__ = 'promo_codes' + __tablename__ = "promo_codes" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(VARCHAR(80), nullable=False, unique=True) @@ -115,7 +130,7 @@ def is_expiated_time(self): class BonusLink(Base): - __tablename__ = 'bonus_links' + __tablename__ = "bonus_links" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(VARCHAR(80), nullable=False, unique=True) @@ -124,14 +139,14 @@ class BonusLink(Base): class RefLink(Base): - __tablename__ = 'ref_links' + __tablename__ = "ref_links" id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) code: Mapped[str] = mapped_column(VARCHAR(80), nullable=False, unique=True) class LimitedCards(Base): - __tablename__ = 'limited_cards' + __tablename__ = "limited_cards" id: Mapped[int] = mapped_column(Integer, primary_key=True) name: Mapped[str] = mapped_column(String, nullable=False) @@ -139,4 +154,4 @@ class LimitedCards(Base): edition: Mapped[int] = mapped_column(BigInteger, nullable=False) buy_count: Mapped[int] = mapped_column(BigInteger, nullable=False, default=0) description: Mapped[str] = mapped_column(String, nullable=True) - photo: Mapped[str] = mapped_column(String, nullable=False) \ No newline at end of file + photo: Mapped[str] = mapped_column(String, nullable=False) diff --git a/database/premium.py b/database/premium.py index 12c8030..027bf67 100644 --- a/database/premium.py +++ b/database/premium.py @@ -8,29 +8,43 @@ async def check_premium(premium_expire: datetime): - return True if premium_expire is not None and premium_expire.date() > datetime.now().date() else False + return ( + True + if premium_expire is not None and premium_expire.date() > datetime.now().date() + else False + ) async def get_premium_users() -> [User]: async with AsyncSession(engine) as session: users: [User] = ( - await session.execute( - select(User).where(User.premium_expire.is_not(None)).where(User.premium_expire > datetime.now()) + ( + await session.execute( + select(User) + .where(User.premium_expire.is_not(None)) + .where(User.premium_expire > datetime.now()) + ) ) - ).scalars().all() + .scalars() + .all() + ) return users async def premium_from_datetime(telegram_id: int, end_date: datetime): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.premium_expire = end_date await session.commit() async def add_premium(telegram_id: int, days: timedelta): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() if user.premium_expire is None or datetime.now() >= user.premium_expire: user.premium_expire = datetime.now() + days diff --git a/database/promo.py b/database/promo.py index b05f6c1..3a97aee 100644 --- a/database/promo.py +++ b/database/promo.py @@ -18,7 +18,7 @@ async def create_promo( days_add: Optional[int], channel_id: int, activation_limit: int, - expiration_time: datetime + expiration_time: datetime, ) -> Promo: async with AsyncSession(engine) as session: promo = Promo( @@ -28,38 +28,46 @@ async def create_promo( activation_limit=activation_limit, expiration_time=expiration_time, days_add=days_add, - channel_id=channel_id + channel_id=channel_id, ) session.add(promo) await session.commit() - promo = (await session.execute(select(Promo).where(Promo.code == code))).scalar_one() + promo = ( + await session.execute(select(Promo).where(Promo.code == code)) + ).scalar_one() return promo async def delete_promo(code: str) -> None: async with AsyncSession(engine) as session: - promo = (await session.execute(select(Promo).where(Promo.code == code))).scalar_one() + promo = ( + await session.execute(select(Promo).where(Promo.code == code)) + ).scalar_one() await session.delete(promo) await session.commit() async def get_promo(code: str) -> Promo: async with AsyncSession(engine) as session: - promo = (await session.execute(select(Promo).where(Promo.code == code))).scalar_one_or_none() + promo = ( + await session.execute(select(Promo).where(Promo.code == code)) + ).scalar_one_or_none() return promo async def add_activation(code: str) -> None: async with AsyncSession(engine) as session: - promo = (await session.execute(select(Promo).where(Promo.code == code))).scalar_one() + promo = ( + await session.execute(select(Promo).where(Promo.code == code)) + ).scalar_one() promo.activation_counts += 1 await session.commit() async def promo_use(telegram_id: int, promo: Promo) -> None: async with AsyncSession(engine) as session: - user: Optional[User] = (await session.execute( - select(User).where(User.telegram_id == telegram_id)) + user: Optional[User] = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) ).scalar_one_or_none() if not user: @@ -68,7 +76,9 @@ async def promo_use(telegram_id: int, promo: Promo) -> None: user.expired_promo_codes = (user.expired_promo_codes or []) + [promo.code] if promo.action == "reset_cd": - if await check_last_get(user.last_usage, await check_premium(user.premium_expire)): + if await check_last_get( + user.last_usage, await check_premium(user.premium_expire) + ): raise IsAlreadyResetException user.last_usage = datetime.now() - timedelta(hours=3) elif promo.action == "add_premium": diff --git a/database/ref_link.py b/database/ref_link.py index 2719998..022efa7 100644 --- a/database/ref_link.py +++ b/database/ref_link.py @@ -10,10 +10,9 @@ async def get_ref_link(code: str) -> RefLink: async with AsyncSession(engine) as session: - ref_link = (await session.execute( - select(RefLink) - .where(RefLink.code == code) - )).scalar_one_or_none() + ref_link = ( + await session.execute(select(RefLink).where(RefLink.code == code)) + ).scalar_one_or_none() return ref_link @@ -27,10 +26,9 @@ async def create_ref_link(code: str) -> RefLink: async def delete_ref_link(code: str) -> None: async with AsyncSession(engine) as session: - ref_link = (await session.execute( - select(RefLink). - where(RefLink.code == code) - )).scalar_one_or_none() + ref_link = ( + await session.execute(select(RefLink).where(RefLink.code == code)) + ).scalar_one_or_none() if ref_link is None: return await session.delete(ref_link) diff --git a/database/statistic.py b/database/statistic.py index 0bdd654..2f477d8 100644 --- a/database/statistic.py +++ b/database/statistic.py @@ -10,41 +10,51 @@ async def get_users_count_created_by_date(data: datetime.date) -> int: async with AsyncSession(engine) as session: - return (await session.execute( - select(func.count(User.id)) - .where(and_(User.created_at == data, User.in_pm == True)) - )).scalar_one() + return ( + await session.execute( + select(func.count(User.id)).where( + and_(User.created_at == data, User.in_pm == True) + ) + ) + ).scalar_one() async def get_users_count_last_active_today() -> int: async with AsyncSession(engine) as session: - return (await session.execute( - select(func.count(User.id)) - .where(User.last_activity == datetime.datetime.now().date()) - )).scalar_one() + return ( + await session.execute( + select(func.count(User.id)).where( + User.last_activity == datetime.datetime.now().date() + ) + ) + ).scalar_one() async def get_groups_count_created_by_date(data: datetime.date) -> int: async with AsyncSession(engine) as session: - return (await session.execute( - select(func.count(Group.id)) - .where(Group.added_at == data) - )).scalar_one() + return ( + await session.execute( + select(func.count(Group.id)).where(Group.added_at == data) + ) + ).scalar_one() async def get_groups_count_last_active_today() -> int: async with AsyncSession(engine) as session: - return (await session.execute( - select(func.count(Group.id)) - .where(Group.last_activity == datetime.datetime.now().date()) - )).scalar_one() + return ( + await session.execute( + select(func.count(Group.id)).where( + Group.last_activity == datetime.datetime.now().date() + ) + ) + ).scalar_one() async def update_yesterday_last_activities() -> None: async with AsyncSession(engine) as session: - app: Optional[App] = (await session.execute( - select(App).where(App.id == 1) - )).scalar_one_or_none() + app: Optional[App] = ( + await session.execute(select(App).where(App.id == 1)) + ).scalar_one_or_none() if app: app.yesterday_groups_active = await get_groups_count_last_active_today() @@ -54,25 +64,31 @@ async def update_yesterday_last_activities() -> None: async def get_yesterday_groups_active() -> Union[int, str]: async with AsyncSession(engine) as session: - count: Optional[int] = (await session.execute( - select(App.yesterday_groups_active).where(App.id == 1) - )).scalar_one_or_none() + count: Optional[int] = ( + await session.execute( + select(App.yesterday_groups_active).where(App.id == 1) + ) + ).scalar_one_or_none() return count if count is not None else "Вернитесь после 23.59" async def get_yesterday_users_active() -> Union[int, str]: async with AsyncSession(engine) as session: - count: Optional[int] = (await session.execute( - select(App.yesterday_users_active).where(App.id == 1) - )).scalar_one_or_none() - return count if count is not None else "Активность не готова, вернитесь после 23.59" + count: Optional[int] = ( + await session.execute(select(App.yesterday_users_active).where(App.id == 1)) + ).scalar_one_or_none() + return ( + count + if count is not None + else "Активность не готова, вернитесь после 23.59" + ) async def create_app_if_not_exist() -> None: async with AsyncSession(engine) as session: - app: Optional[App] = (await session.execute( - select(App).where(App.id == 1) - )).scalar_one_or_none() + app: Optional[App] = ( + await session.execute(select(App).where(App.id == 1)) + ).scalar_one_or_none() if app is None: app = App(id=1, yesterday_users_active=None, yesterday_groups_active=None) session.add(app) @@ -81,27 +97,43 @@ async def create_app_if_not_exist() -> None: async def get_users_with_link_count(link: str) -> int: async with AsyncSession(engine) as session: - return (await session.execute( - select(func.count(User.id)).where(User.from_link == link) - )).scalar_one() + return ( + await session.execute( + select(func.count(User.id)).where(User.from_link == link) + ) + ).scalar_one() async def get_groups_with_link_count(link: str) -> int: async with AsyncSession(engine) as session: - return (await session.execute( - select(func.count(Group.id)).where(Group.from_link == link) - )).scalar_one() + return ( + await session.execute( + select(func.count(Group.id)).where(Group.from_link == link) + ) + ).scalar_one() async def get_all_groups_with_link(link: str) -> List[int]: async with AsyncSession(engine) as session: - return (await session.execute( - select(Group.group_id).where(Group.from_link == link) - )).scalars().all() + return ( + ( + await session.execute( + select(Group.group_id).where(Group.from_link == link) + ) + ) + .scalars() + .all() + ) async def get_all_users_with_link(link: str) -> List[int]: async with AsyncSession(engine) as session: - return (await session.execute( - select(User.telegram_id).where(User.from_link == link) - )).scalars().all() + return ( + ( + await session.execute( + select(User.telegram_id).where(User.from_link == link) + ) + ) + .scalars() + .all() + ) diff --git a/database/top.py b/database/top.py index 87cd5b7..9ff84ee 100644 --- a/database/top.py +++ b/database/top.py @@ -10,27 +10,35 @@ async def get_top_users_by_cards(): async with AsyncSession(engine) as session: top_users = ( - await session.execute( - select(User).order_by(desc(func.cardinality(User.cards))).filter(func.cardinality(User.cards) > 0) - .limit(10) + ( + await session.execute( + select(User) + .order_by(desc(func.cardinality(User.cards))) + .filter(func.cardinality(User.cards) > 0) + .limit(10) + ) ) - ).scalars().all() + .scalars() + .all() + ) top = [] i = 1 for top_user in top_users: icon = "💎" if await check_premium(top_user.premium_expire) else "" - top += [[i, icon, top_user.nickname, len(top_user.cards), top_user.telegram_id]] + top += [ + [i, icon, top_user.nickname, len(top_user.cards), top_user.telegram_id] + ] i += 1 return top async def get_top_users_by_points(): - async with (AsyncSession(engine) as session): + async with AsyncSession(engine) as session: top_users = ( - await session.execute( - select(User).order_by(desc(User.points)).limit(10) - ) - ).scalars().all() + (await session.execute(select(User).order_by(desc(User.points)).limit(10))) + .scalars() + .all() + ) top = [] i = 1 for top_user in top_users: @@ -41,12 +49,16 @@ async def get_top_users_by_points(): async def get_top_users_by_all_points(): - async with (AsyncSession(engine) as session): + async with AsyncSession(engine) as session: top_users = ( - await session.execute( - select(User).order_by(desc(User.all_points)).limit(10) + ( + await session.execute( + select(User).order_by(desc(User.all_points)).limit(10) + ) ) - ).scalars().all() + .scalars() + .all() + ) top = [] i = 1 for top_user in top_users: @@ -54,11 +66,18 @@ async def get_top_users_by_all_points(): top += [[i, icon, top_user.nickname, top_user.all_points]] i += 1 return top - + async def get_me_on_top(by, telegram_id: int): async with AsyncSession(engine) as session: - position = (await session.execute( - select(1 + count("*")).where(by > select(by).where(User.telegram_id == telegram_id).scalar_subquery()) - )).scalar_one() + position = ( + await session.execute( + select(1 + count("*")).where( + by + > select(by) + .where(User.telegram_id == telegram_id) + .scalar_subquery() + ) + ) + ).scalar_one() return position diff --git a/database/user.py b/database/user.py index 55466a5..65091c9 100644 --- a/database/user.py +++ b/database/user.py @@ -17,31 +17,36 @@ async def create_user(telegram_id: int, username: str, in_pm: bool = False) -> U user = User(telegram_id=telegram_id, in_pm=in_pm) session.add(user) await session.commit() - user = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one() + user = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one() return user async def get_user(telegram_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() return user async def get_user_with_pm_count(): - async with (AsyncSession(engine) as session): - user_count = (await session.execute( - select(func.count(User.id)) - .where(User.in_pm == True)) - ).scalar_one_or_none() + async with AsyncSession(engine) as session: + user_count = ( + await session.execute(select(func.count(User.id)).where(User.in_pm == True)) + ).scalar_one_or_none() return user_count async def set_love_card(telegram_id: int, love_card_id: int, is_limited: bool): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() if user is None: return False - + user.love_card = {"id": love_card_id, "is_limited": is_limited} await session.commit() return True @@ -53,14 +58,18 @@ async def update_last_get(telegram_id: int): async def set_last_get(telegram_id: int, time: datetime): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.last_usage = time await session.commit() async def add_points(telegram_id: int, points: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.points += points user.all_points += points await session.commit() @@ -68,7 +77,9 @@ async def add_points(telegram_id: int, points: int): async def add_card(telegram_id: int, card_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.cards += [card_id] user.card_count += 1 await session.commit() @@ -76,16 +87,16 @@ async def add_card(telegram_id: int, card_id: int): async def change_username(telegram_id: int, username: str): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.nickname = username await session.commit() async def is_nickname_taken(nickname: str) -> bool: async with AsyncSession(engine) as session: - result = await session.execute( - select(User).where(User.nickname == nickname) - ) + result = await session.execute(select(User).where(User.nickname == nickname)) users = result.scalars().all() return len(users) > 0 @@ -103,7 +114,9 @@ async def clear_season(): async def ban_user(telegram_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.is_banned = True await session.commit() return @@ -111,7 +124,9 @@ async def ban_user(telegram_id: int): async def upgrade_user(telegram_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.status = "ADMIN" await session.commit() return @@ -119,7 +134,9 @@ async def upgrade_user(telegram_id: int): async def unban_user(telegram_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.is_banned = False await session.commit() return @@ -127,13 +144,25 @@ async def unban_user(telegram_id: int): async def get_all_users_ids(offset: int = 0, limit: int = None) -> [User]: async with AsyncSession(engine) as session: - users: Dict[User] = (await session.execute(select(User.telegram_id).limit(limit).offset(offset))).scalars().all() + users: Dict[User] = ( + ( + await session.execute( + select(User.telegram_id).limit(limit).offset(offset) + ) + ) + .scalars() + .all() + ) return users async def get_all_users_with_pm_ids() -> [User]: async with AsyncSession(engine) as session: - users: Dict[User] = (await session.execute(select(User.telegram_id).where(User.in_pm == True))).scalars().all() + users: Dict[User] = ( + (await session.execute(select(User.telegram_id).where(User.in_pm == True))) + .scalars() + .all() + ) return users @@ -159,7 +188,9 @@ async def check_last_get(last_get: datetime, is_premium: bool): async def in_pm_change(telegram_id: int, status: bool) -> None: async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.in_pm = status await session.commit() @@ -172,51 +203,59 @@ async def get_user_count(): async def update_last_activity(telegram_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.last_activity = datetime.now().date() await session.commit() async def update_last_bonus_get(telegram_id: int): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.last_bonus_get = datetime.now() await session.commit() async def set_user_refer_code(telegram_id: int, code: str): async with AsyncSession(engine) as session: - user: User = (await session.execute(select(User).where(User.telegram_id == telegram_id))).scalar_one_or_none() + user: User = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() user.from_link = code await session.commit() -async def add_coins(telegram_id: int, coins: int, username: str = None, in_pm: bool = False) -> bool: +async def add_coins( + telegram_id: int, coins: int, username: str = None, in_pm: bool = False +) -> bool: user = await get_user(telegram_id) if user is None: user = await create_user(telegram_id, username or "Гость", in_pm) - + async with AsyncSession(engine) as session: - user = (await session.execute( - select(User).where(User.telegram_id == telegram_id) - )).scalar_one() + user = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one() user.coins += coins await session.commit() return True - + async def get_coins(telegram_id: int) -> int: user = await get_user(telegram_id) if user is None: return 0 - + async with AsyncSession(engine) as session: - user = (await session.execute( - select(User).where(User.telegram_id == telegram_id) - )).scalar_one_or_none() - + user = ( + await session.execute(select(User).where(User.telegram_id == telegram_id)) + ).scalar_one_or_none() + return user.coins if user else 0 - + async def set_luck(telegram_id: int, luck: bool): async with AsyncSession(engine) as session: @@ -228,17 +267,21 @@ async def set_luck(telegram_id: int, luck: bool): async def get_luck(telegram_id: int) -> bool: async with AsyncSession(engine) as session: - user = (await session.execute( - select(User.luck).where(User.telegram_id == telegram_id) - )).scalar_one_or_none() - + user = ( + await session.execute( + select(User.luck).where(User.telegram_id == telegram_id) + ) + ).scalar_one_or_none() + return user if user is not None else False - + async def add_dice_get(telegram_id: int): async with AsyncSession(engine) as session: await session.execute( - update(User).where(User.telegram_id == telegram_id).values(last_dice_play=datetime.now()) + update(User) + .where(User.telegram_id == telegram_id) + .values(last_dice_play=datetime.now()) ) await session.commit() @@ -249,10 +292,9 @@ async def add_limited_card_to_user(user_id: int, card_id: int): update(User) .where(User.telegram_id == user_id) .values( - limited_cards=func.coalesce( - User.limited_cards, - cast([], JSONB) - ).concat(cast([card_id], JSONB)) + limited_cards=func.coalesce(User.limited_cards, cast([], JSONB)).concat( + cast([card_id], JSONB) + ) ) ) await session.commit() @@ -260,9 +302,9 @@ async def add_limited_card_to_user(user_id: int, card_id: int): async def check_user_has_limited_card(user_id: int, card_id: int) -> bool: async with AsyncSession(engine) as session: - user = (await session.execute( - select(User).where(User.telegram_id == user_id) - )).scalar_one_or_none() + user = ( + await session.execute(select(User).where(User.telegram_id == user_id)) + ).scalar_one_or_none() if not user or not user.limited_cards: return False - return card_id in user.limited_cards \ No newline at end of file + return card_id in user.limited_cards diff --git a/filters/CardFilter.py b/filters/CardFilter.py index eed19d1..3cc628d 100644 --- a/filters/CardFilter.py +++ b/filters/CardFilter.py @@ -3,11 +3,17 @@ class CardFilter(BaseFilter): - async def __call__(self, message: Message) -> bool: - if message.text is not None and message.text.casefold() in \ - ["Карта".casefold(), "карта2".casefold(), "карта3".casefold(), "карта4".casefold(), - "карта5".casefold(), "/cards@юзернеймбота".casefold(), "/cards".casefold()]: + + if message.text is not None and message.text.casefold() in [ + "Карта".casefold(), + "карта2".casefold(), + "карта3".casefold(), + "карта4".casefold(), + "карта5".casefold(), + "/cards".casefold(), + f"/cards@{(message.bot and (await message.bot.me()).username or 'юзернеймбота')}".casefold(), + ]: return True - else: - return False \ No newline at end of file + + return False diff --git a/filters/FloodWait.py b/filters/FloodWait.py index d106741..c571261 100644 --- a/filters/FloodWait.py +++ b/filters/FloodWait.py @@ -1,7 +1,6 @@ -import time - from aiogram.filters import BaseFilter from aiogram.types import Message +import time class RateLimitFilter(BaseFilter): @@ -25,7 +24,10 @@ async def __call__(self, message: Message) -> bool: return True def _cleanup_expired(self, current_time: float): - expired_keys = [user_id for user_id, last_time in self.last_request_time.items() - if (current_time - last_time) > self.expiration_time] + expired_keys = [ + user_id + for user_id, last_time in self.last_request_time.items() + if (current_time - last_time) > self.expiration_time + ] for user_id in expired_keys: del self.last_request_time[user_id] diff --git a/filters/NotCommentFilter.py b/filters/NotCommentFilter.py index d63e1af..e9a0cf9 100644 --- a/filters/NotCommentFilter.py +++ b/filters/NotCommentFilter.py @@ -3,7 +3,6 @@ class NotCommentFilter(BaseFilter): - async def check_first_message(self, message: Message) -> bool: if message.reply_to_message is not None: await self.check_first_message(message.reply_to_message) diff --git a/filters/ProfileFilter.py b/filters/ProfileFilter.py index a923bb5..d5e5485 100644 --- a/filters/ProfileFilter.py +++ b/filters/ProfileFilter.py @@ -3,7 +3,6 @@ class ProfileFilter(BaseFilter): - async def __call__(self, message: Message) -> bool: if message.text is not None: if message.text.casefold() in \ diff --git a/handlers/__init__.py b/handlers/__init__.py index cd66184..1afbdfb 100644 --- a/handlers/__init__.py +++ b/handlers/__init__.py @@ -3,4 +3,4 @@ from .profile import profile_router from .triggers import text_triggers_router from .shop import shop_router -from .shopcards import shop_cards_router \ No newline at end of file +from .shopcards import shop_cards_router diff --git a/handlers/admin_dialogs/__init__.py b/handlers/admin_dialogs/__init__.py index 796faa0..be76870 100644 --- a/handlers/admin_dialogs/__init__.py +++ b/handlers/admin_dialogs/__init__.py @@ -10,7 +10,11 @@ dialogs_router = Router() dialogs_router.include_routers( - base_dialogs, ban_dialogs, add_admin_dialogs, promo_dialogs, ref_link_dialogs, + base_dialogs, + ban_dialogs, + add_admin_dialogs, + promo_dialogs, + ref_link_dialogs, ) dialogs_router.message.middleware(AdminMiddleware()) dialogs_router.callback_query.middleware(AdminMiddleware()) diff --git a/handlers/admin_dialogs/add_admin_dialogs/add_admin_dialog.py b/handlers/admin_dialogs/add_admin_dialogs/add_admin_dialog.py index 1f4e397..35b10c1 100644 --- a/handlers/admin_dialogs/add_admin_dialogs/add_admin_dialog.py +++ b/handlers/admin_dialogs/add_admin_dialogs/add_admin_dialog.py @@ -9,25 +9,34 @@ from handlers.admin_dialogs.admin_states import AddAdminSG -async def on_get_id(message: Message, widget, dialog_manager: DialogManager, telegram_id: int): +async def on_get_id( + message: Message, widget, dialog_manager: DialogManager, telegram_id: int +): user = await get_user(int(telegram_id)) if user is not None and user.status == "ADMIN": await dialog_manager.switch_to(AddAdminSG.user_is_banned) elif user is not None: - dialog_manager.dialog_data['user'] = user + dialog_manager.dialog_data["user"] = user await dialog_manager.switch_to(AddAdminSG.accept) else: - dialog_manager.dialog_data['error'] = "Пользователь не найден в базе данных" + dialog_manager.dialog_data["error"] = "Пользователь не найден в базе данных" await dialog_manager.switch_to(AddAdminSG.error) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - user: BotUser = dialog_manager.dialog_data['user'] - return {"username": user.nickname, "user_id": user.telegram_id, } +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + user: BotUser = dialog_manager.dialog_data["user"] + return { + "username": user.nickname, + "user_id": user.telegram_id, + } -async def accept_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): - user: BotUser = manager.dialog_data['user'] +async def accept_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): + user: BotUser = manager.dialog_data["user"] await upgrade_user(user.telegram_id) await manager.switch_to(AddAdminSG.all_ok) @@ -37,30 +46,30 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo Const("Введите айди пользователя которого необходимо повысить"), TextInput(type_factory=int, id="user_id", on_success=on_get_id), Cancel(Const("В меню")), - state=AddAdminSG.get_id + state=AddAdminSG.get_id, ), Window( Const("Желаете повысить пользователя?"), Format("Имя: {username}"), Format("Айди: {user_id}"), Button(Const("Повысить"), id="__upgrade__", on_click=accept_clicked), - Back(Const('Назад')), + Back(Const("Назад")), getter=accept_getter, - state=AddAdminSG.accept + state=AddAdminSG.accept, ), Window( Const("Пользователь успешно повышен"), Cancel(Const("В меню")), - state=AddAdminSG.all_ok + state=AddAdminSG.all_ok, ), Window( Const("Пользователь уже повышен"), Cancel(Const("В меню")), - state=AddAdminSG.user_is_banned + state=AddAdminSG.user_is_banned, ), Window( Format("Ошибка: {dialog_data[error]}"), Cancel(Const("В меню")), - state=AddAdminSG.error - ) + state=AddAdminSG.error, + ), ) diff --git a/handlers/admin_dialogs/admin_states.py b/handlers/admin_dialogs/admin_states.py index 5105654..533990d 100644 --- a/handlers/admin_dialogs/admin_states.py +++ b/handlers/admin_dialogs/admin_states.py @@ -91,4 +91,4 @@ class AddRefLinkSG(StatesGroup): class ViewRefLinkSG(StatesGroup): link_list = State() - one_link = State() \ No newline at end of file + one_link = State() diff --git a/handlers/admin_dialogs/ban_dialogs/__init__.py b/handlers/admin_dialogs/ban_dialogs/__init__.py index ef9b293..af19048 100644 --- a/handlers/admin_dialogs/ban_dialogs/__init__.py +++ b/handlers/admin_dialogs/ban_dialogs/__init__.py @@ -4,4 +4,4 @@ from .unban_dialog import unban_dialog ban_dialogs = Router() -ban_dialogs.include_routers(ban_dialog, unban_dialog) \ No newline at end of file +ban_dialogs.include_routers(ban_dialog, unban_dialog) diff --git a/handlers/admin_dialogs/ban_dialogs/ban_dialog.py b/handlers/admin_dialogs/ban_dialogs/ban_dialog.py index 8999997..6883196 100644 --- a/handlers/admin_dialogs/ban_dialogs/ban_dialog.py +++ b/handlers/admin_dialogs/ban_dialogs/ban_dialog.py @@ -9,25 +9,34 @@ from handlers.admin_dialogs.admin_states import BanSG -async def on_get_id(message: Message, widget, dialog_manager: DialogManager, telegram_id: int): +async def on_get_id( + message: Message, widget, dialog_manager: DialogManager, telegram_id: int +): user = await get_user(telegram_id) if user is not None and user.is_banned: await dialog_manager.switch_to(BanSG.user_is_banned) elif user is not None: - dialog_manager.dialog_data['user'] = user + dialog_manager.dialog_data["user"] = user await dialog_manager.switch_to(BanSG.accept) else: - dialog_manager.dialog_data['error'] = "Пользователь не найден в базе данных" + dialog_manager.dialog_data["error"] = "Пользователь не найден в базе данных" await dialog_manager.switch_to(BanSG.error) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - user: BotUser = dialog_manager.dialog_data['user'] - return {"username": user.nickname, "user_id": user.telegram_id, } +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + user: BotUser = dialog_manager.dialog_data["user"] + return { + "username": user.nickname, + "user_id": user.telegram_id, + } -async def accept_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): - user: BotUser = manager.dialog_data['user'] +async def accept_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): + user: BotUser = manager.dialog_data["user"] await ban_user(user.telegram_id) await manager.switch_to(BanSG.all_ok) @@ -37,30 +46,30 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo Const("Введите айди пользователя которого необходимо забанить"), TextInput(type_factory=int, id="user_id", on_success=on_get_id), Cancel(Const("В меню")), - state=BanSG.get_id + state=BanSG.get_id, ), Window( Const("Желаете забанить пользователя?"), Format("Имя: {username}"), Format("Айди: {user_id}"), Button(Const("Забанить"), id="__ban__", on_click=accept_clicked), - Back(Const('Назад')), + Back(Const("Назад")), getter=accept_getter, - state=BanSG.accept + state=BanSG.accept, ), Window( Const("Пользователь успешно забанен"), Cancel(Const("В меню")), - state=BanSG.all_ok + state=BanSG.all_ok, ), Window( Const("Пользователь уже забанен"), Cancel(Const("В меню")), - state=BanSG.user_is_banned + state=BanSG.user_is_banned, ), Window( Format("Ошибка: {dialog_data[error]}"), Cancel(Const("В меню")), - state=BanSG.error - ) + state=BanSG.error, + ), ) diff --git a/handlers/admin_dialogs/ban_dialogs/unban_dialog.py b/handlers/admin_dialogs/ban_dialogs/unban_dialog.py index 781ff5f..ae40492 100644 --- a/handlers/admin_dialogs/ban_dialogs/unban_dialog.py +++ b/handlers/admin_dialogs/ban_dialogs/unban_dialog.py @@ -9,35 +9,52 @@ from handlers.admin_dialogs.admin_states import ChangeUsernameSG -async def on_get_id(message: Message, widget, dialog_manager: DialogManager, telegram_id: int): +async def on_get_id( + message: Message, widget, dialog_manager: DialogManager, telegram_id: int +): user = await get_user(telegram_id) if user is not None: - dialog_manager.dialog_data['user'] = user + dialog_manager.dialog_data["user"] = user await dialog_manager.switch_to(ChangeUsernameSG.get_new_username) else: - dialog_manager.dialog_data['error'] = "Пользователь не найден в базе данных" + dialog_manager.dialog_data["error"] = "Пользователь не найден в базе данных" await dialog_manager.switch_to(ChangeUsernameSG.error) -async def get_username_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - user: BotUser = dialog_manager.dialog_data['user'] - return {"username": user.nickname, "user_id": user.telegram_id, } +async def get_username_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + user: BotUser = dialog_manager.dialog_data["user"] + return { + "username": user.nickname, + "user_id": user.telegram_id, + } -async def on_get_username(message: Message, widget, dialog_manager: DialogManager, username: str): - dialog_manager.dialog_data['username'] = username +async def on_get_username( + message: Message, widget, dialog_manager: DialogManager, username: str +): + dialog_manager.dialog_data["username"] = username await dialog_manager.switch_to(ChangeUsernameSG.accept) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - user: BotUser = dialog_manager.dialog_data['user'] - username: str = dialog_manager.dialog_data['username'] - return {"old_username": user.nickname, "user_id": user.telegram_id, "new_username": username} +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + user: BotUser = dialog_manager.dialog_data["user"] + username: str = dialog_manager.dialog_data["username"] + return { + "old_username": user.nickname, + "user_id": user.telegram_id, + "new_username": username, + } -async def accept_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): - user: BotUser = manager.dialog_data['user'] - username: str = manager.dialog_data['username'] +async def accept_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): + user: BotUser = manager.dialog_data["user"] + username: str = manager.dialog_data["username"] await change_username(user.telegram_id, username) await manager.switch_to(ChangeUsernameSG.changed) @@ -47,28 +64,30 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo Const("Введите айди пользователя"), TextInput(type_factory=int, id="user_id", on_success=on_get_id), Cancel(Const("В меню")), - state=ChangeUsernameSG.get_id + state=ChangeUsernameSG.get_id, ), Window( Const("Введите новое имя пользователя"), Format("Текущее имя: {username}"), Format("Айди: {user_id}"), TextInput(id="__username__", on_success=on_get_username), - Back(Const('Назад')), + Back(Const("Назад")), getter=get_username_getter, - state=ChangeUsernameSG.get_new_username + state=ChangeUsernameSG.get_new_username, ), Window( - Format("Хотите сменить имя пользователю?\nАйди: {user_id}\nСтарый юзернейм: {old_username}\n" - "Новый юзернейм: {new_username}"), + Format( + "Хотите сменить имя пользователю?\nАйди: {user_id}\nСтарый юзернейм: {old_username}\n" + "Новый юзернейм: {new_username}" + ), Button(Const("Сменить"), id="__accept__", on_click=accept_clicked), Back(Const("В меню")), getter=accept_getter, - state=ChangeUsernameSG.accept + state=ChangeUsernameSG.accept, ), Window( Const("Юзернейм пользователя успешно изменен!"), Cancel(Const("В меню")), - state=ChangeUsernameSG.changed - ) + state=ChangeUsernameSG.changed, + ), ) diff --git a/handlers/admin_dialogs/base_dialogs/__init__.py b/handlers/admin_dialogs/base_dialogs/__init__.py index 6363e89..4e26b8e 100644 --- a/handlers/admin_dialogs/base_dialogs/__init__.py +++ b/handlers/admin_dialogs/base_dialogs/__init__.py @@ -9,5 +9,9 @@ base_dialogs = Router() base_dialogs.include_routers( - admin_dialog, mailing_dialog, premium_dialog, season_delete_dialog, change_nickname_dialog -) \ No newline at end of file + admin_dialog, + mailing_dialog, + premium_dialog, + season_delete_dialog, + change_nickname_dialog, +) diff --git a/handlers/admin_dialogs/base_dialogs/admin_dialog.py b/handlers/admin_dialogs/base_dialogs/admin_dialog.py index 4628286..9e6b03c 100644 --- a/handlers/admin_dialogs/base_dialogs/admin_dialog.py +++ b/handlers/admin_dialogs/base_dialogs/admin_dialog.py @@ -8,18 +8,36 @@ from aiogram_dialog.widgets.text import Const, Format, Multi from database.group import get_all_groups_with_bot_ids, get_group_with_bot_count -from database.statistic import get_groups_count_created_by_date, get_groups_count_last_active_today, \ - get_users_count_created_by_date, \ - get_users_count_last_active_today, get_yesterday_groups_active, get_yesterday_users_active +from database.statistic import ( + get_groups_count_created_by_date, + get_groups_count_last_active_today, + get_users_count_created_by_date, + get_users_count_last_active_today, + get_yesterday_groups_active, + get_yesterday_users_active, +) from database.user import get_all_users_with_pm_ids, get_user_with_pm_count -from handlers.admin_dialogs.admin_states import AddAdminSG, AddRefLinkSG, AdminSG, CreatePromoSG, DelSeasonSG, DeletePromoSG, MailingSG, ViewRefLinkSG +from handlers.admin_dialogs.admin_states import ( + AddAdminSG, + AddRefLinkSG, + AdminSG, + CreatePromoSG, + DelSeasonSG, + DeletePromoSG, + MailingSG, + ViewRefLinkSG, +) -async def message_to_mailing_handler(message: Message, message_input: Message, manager: DialogManager): +async def message_to_mailing_handler( + message: Message, message_input: Message, manager: DialogManager +): await manager.switch_to(AdminSG) -async def export_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): +async def export_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): if button.widget_id == "export_chats": groups_ids = await get_all_groups_with_bot_ids() group_ids = "\n".join(str(group_id) for group_id in groups_ids) @@ -36,13 +54,17 @@ async def export_clicked(callback: CallbackQuery, button: Button, manager: Dialo async def get_statistics(dialog_manager: DialogManager, **kwargs): - created_users_today = await get_users_count_created_by_date(datetime.datetime.now().date()) + created_users_today = await get_users_count_created_by_date( + datetime.datetime.now().date() + ) created_users_yesterday = await get_users_count_created_by_date( datetime.datetime.now().date() - datetime.timedelta(days=1) ) last_active_users_today = await get_users_count_last_active_today() last_active_users_yesterday = await get_yesterday_users_active() - groups_added_today = await get_groups_count_created_by_date(datetime.datetime.now().date()) + groups_added_today = await get_groups_count_created_by_date( + datetime.datetime.now().date() + ) groups_added_yesterday = await get_groups_count_created_by_date( datetime.datetime.now().date() - datetime.timedelta(days=1) ) @@ -61,13 +83,10 @@ async def get_statistics(dialog_manager: DialogManager, **kwargs): "last_active_groups_today": last_active_groups_today, "last_active_groups_yesterday": last_active_groups_yesterday, "total_users": total_users, - "total_groups": total_active_groups + "total_groups": total_active_groups, } - - - admin_dialog = Dialog( Window( Const("Привет админ!"), @@ -80,39 +99,52 @@ async def get_statistics(dialog_manager: DialogManager, **kwargs): SwitchTo(Const("Ссылки"), id="links", state=AdminSG.choose_ref_action), ), Row( - Start(Const("Создать промокод"), id="create_promo", state=CreatePromoSG.get_name), - Start(Const("Удалить промокод"), id="delete_promo", state=DeletePromoSG.get_name), + Start( + Const("Создать промокод"), + id="create_promo", + state=CreatePromoSG.get_name, + ), + Start( + Const("Удалить промокод"), + id="delete_promo", + state=DeletePromoSG.get_name, + ), ), Start(Const("Сбросить сезон"), id="reset_season", state=DelSeasonSG.accept_del), - state=AdminSG.menu, ), Window( Multi( - Format("Сегодня:\n- ЛС: +{created_users_today}\n- Чаты: +{groups_added_today}" - "\n- Актив: 👤 {last_active_users_today} | 👥 {last_active_groups_today}"), - Format("Вчера:\n- ЛС: +{created_users_yesterday}\n- Чаты: +{groups_added_yesterday}" - "\n- Актив: 👤 {last_active_users_yesterday} | 👥 {last_active_groups_yesterday}"), + Format( + "Сегодня:\n- ЛС: +{created_users_today}\n- Чаты: +{groups_added_today}" + "\n- Актив: 👤 {last_active_users_today} | 👥 {last_active_groups_today}" + ), + Format( + "Вчера:\n- ЛС: +{created_users_yesterday}\n- Чаты: +{groups_added_yesterday}" + "\n- Актив: 👤 {last_active_users_yesterday} | 👥 {last_active_groups_yesterday}" + ), Format("За все время:\n- ЛС: {total_users}\n- Чаты: {total_groups}"), - sep="\n\n" + sep="\n\n", ), Next(Const("Экспорт")), - Back(Const('Назад')), + Back(Const("Назад")), getter=get_statistics, - state=AdminSG.statistics + state=AdminSG.statistics, ), Window( Const("Выберите, что нужно экспортировать"), Button(Const("Чаты"), id="export_chats", on_click=export_clicked), Button(Const("Пользователи"), id="export_users", on_click=export_clicked), - Back(Const('Назад')), - state=AdminSG.export + Back(Const("Назад")), + state=AdminSG.export, ), Window( Const("Управление ссылками"), - Start(Const("Просмотр ссылок"), id="check_links", state=ViewRefLinkSG.link_list), + Start( + Const("Просмотр ссылок"), id="check_links", state=ViewRefLinkSG.link_list + ), Start(Const("Добавить ссылку"), id="add_link", state=AddRefLinkSG.get_link), - Back(Const('Назад')), - state=AdminSG.choose_ref_action + Back(Const("Назад")), + state=AdminSG.choose_ref_action, ), ) diff --git a/handlers/admin_dialogs/base_dialogs/change_nickname_dialog.py b/handlers/admin_dialogs/base_dialogs/change_nickname_dialog.py index 7cfd2fd..10837e0 100644 --- a/handlers/admin_dialogs/base_dialogs/change_nickname_dialog.py +++ b/handlers/admin_dialogs/base_dialogs/change_nickname_dialog.py @@ -9,25 +9,34 @@ from handlers.admin_dialogs.admin_states import UnBanSG -async def on_get_id(message: Message, widget, dialog_manager: DialogManager, telegram_id: int): +async def on_get_id( + message: Message, widget, dialog_manager: DialogManager, telegram_id: int +): user = await get_user(telegram_id) if user is not None and not user.is_banned: await dialog_manager.switch_to(UnBanSG.user_not_banned) elif user is not None: - dialog_manager.dialog_data['user'] = user + dialog_manager.dialog_data["user"] = user await dialog_manager.switch_to(UnBanSG.accept) else: - dialog_manager.dialog_data['error'] = "Пользователь не найден в базе данных" + dialog_manager.dialog_data["error"] = "Пользователь не найден в базе данных" await dialog_manager.switch_to(UnBanSG.error) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - user: BotUser = dialog_manager.dialog_data['user'] - return {"username": user.nickname, "user_id": user.telegram_id, } +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + user: BotUser = dialog_manager.dialog_data["user"] + return { + "username": user.nickname, + "user_id": user.telegram_id, + } -async def accept_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): - user: BotUser = manager.dialog_data['user'] +async def accept_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): + user: BotUser = manager.dialog_data["user"] await unban_user(user.telegram_id) await manager.switch_to(UnBanSG.all_ok) @@ -37,30 +46,30 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo Const("Введите айди пользователя которого необходимо разбанить"), TextInput(type_factory=int, id="user_id", on_success=on_get_id), Cancel(Const("В меню")), - state=UnBanSG.get_id + state=UnBanSG.get_id, ), Window( Const("Желаете разбанить пользователя?"), Format("Имя: {username}"), Format("Айди: {user_id}"), Button(Const("Разбанить"), id="__ban__", on_click=accept_clicked), - Back(Const('Назад')), + Back(Const("Назад")), getter=accept_getter, - state=UnBanSG.accept + state=UnBanSG.accept, ), Window( Const("Пользователь успешно разбанен"), Cancel(Const("В меню")), - state=UnBanSG.all_ok + state=UnBanSG.all_ok, ), Window( Const("Пользователь не заблокирован"), Cancel(Const("В меню")), - state=UnBanSG.user_not_banned + state=UnBanSG.user_not_banned, ), Window( Format("Ошибка: {dialog_data[error]}"), Cancel(Const("В меню")), - state=UnBanSG.error - ) + state=UnBanSG.error, + ), ) diff --git a/handlers/admin_dialogs/base_dialogs/mailing_dialog.py b/handlers/admin_dialogs/base_dialogs/mailing_dialog.py index 0deaa75..660a01f 100644 --- a/handlers/admin_dialogs/base_dialogs/mailing_dialog.py +++ b/handlers/admin_dialogs/base_dialogs/mailing_dialog.py @@ -3,18 +3,30 @@ from aiogram.types import Animation, CallbackQuery, Message, PhotoSize, User, Video from aiogram_dialog import ChatEvent, Dialog, DialogManager, Window from aiogram_dialog.widgets.input import MessageInput, TextInput -from aiogram_dialog.widgets.kbd import Back, Button, Cancel, Checkbox, ManagedCheckbox, Next, Row +from aiogram_dialog.widgets.kbd import ( + Back, + Button, + Cancel, + Checkbox, + ManagedCheckbox, + Next, + Row, +) from aiogram_dialog.widgets.text import Const from utils.mailing import mailing from handlers.admin_dialogs.admin_states import MailingSG -async def check_changed(event: ChatEvent, checkbox: ManagedCheckbox, manager: DialogManager): +async def check_changed( + event: ChatEvent, checkbox: ManagedCheckbox, manager: DialogManager +): manager.dialog_data[checkbox.widget.widget_id] = checkbox.is_checked() -async def media_handler(message: Message, message_input: MessageInput, manager: DialogManager): +async def media_handler( + message: Message, message_input: MessageInput, manager: DialogManager +): if message.animation is not None: manager.dialog_data["media"] = message.animation elif message.video is not None: @@ -38,16 +50,24 @@ async def next_clicked(callback: CallbackQuery, button: Button, manager: DialogM await manager.switch_to(MailingSG.get_message) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): media = dialog_manager.dialog_data["media"] message_text = dialog_manager.find("message_text").get_value() if media: if type(media) is Animation: - await bot.send_animation(event_from_user.id, animation=media.file_id, caption=message_text) + await bot.send_animation( + event_from_user.id, animation=media.file_id, caption=message_text + ) elif type(media) is Video: - await bot.send_video(event_from_user.id, video=media.file_id, caption=message_text) + await bot.send_video( + event_from_user.id, video=media.file_id, caption=message_text + ) elif type(media[-1]) is PhotoSize: - await bot.send_photo(event_from_user.id, photo=media[-1].file_id, caption=message_text) + await bot.send_photo( + event_from_user.id, photo=media[-1].file_id, caption=message_text + ) else: await bot.send_message(event_from_user.id, message_text) return {} @@ -66,36 +86,51 @@ async def send_clicked(callback: CallbackQuery, button: Button, manager: DialogM Window( Const("Выберите тип чатов для рассылки"), Row( - Checkbox(Const("✔ ЛС"), Const("❌ ЛС"), id="__private__", - default=False, on_state_changed=check_changed), - Checkbox(Const("✔ Группы"), Const("❌ Группы"), id="__groups__", - default=False, on_state_changed=check_changed), + Checkbox( + Const("✔ ЛС"), + Const("❌ ЛС"), + id="__private__", + default=False, + on_state_changed=check_changed, + ), + Checkbox( + Const("✔ Группы"), + Const("❌ Группы"), + id="__groups__", + default=False, + on_state_changed=check_changed, + ), ), Button(Const("Далее"), id="__next__", on_click=next_clicked), Cancel(Const("Назад")), - state=MailingSG.choose_type + state=MailingSG.choose_type, ), Window( Const("Введите текст сообщение (без медиа)"), TextInput(id="message_text", on_success=Next()), Back(Const("Назад")), - state=MailingSG.get_message + state=MailingSG.get_message, ), Window( - Const("Отправьте медиа или нажмите \"Пропустить\""), - MessageInput(media_handler, content_types=[ContentType.PHOTO, ContentType.VIDEO, ContentType.ANIMATION]), + Const('Отправьте медиа или нажмите "Пропустить"'), + MessageInput( + media_handler, + content_types=[ContentType.PHOTO, ContentType.VIDEO, ContentType.ANIMATION], + ), Button(Const("Пропустить"), id="__skip__", on_click=skip_clicked), Back(Const("Назад")), - state=MailingSG.get_media + state=MailingSG.get_media, ), Window( - Const("Ниже вы увидите как будет выглядеть ваше сообщение, если желаете его разослать нажмите \"Отправить\""), + Const( + 'Ниже вы увидите как будет выглядеть ваше сообщение, если желаете его разослать нажмите "Отправить"' + ), Button(Const("Отправить"), id="__send__", on_click=send_clicked), - state=MailingSG.send_message + state=MailingSG.send_message, ), Window( Const("Пользователь, вы не выбрали не один тип чатов"), Cancel(Const("В меню")), - state=MailingSG.error - ) + state=MailingSG.error, + ), ) diff --git a/handlers/admin_dialogs/base_dialogs/premium_dialog.py b/handlers/admin_dialogs/base_dialogs/premium_dialog.py index 589a34b..9a70a30 100644 --- a/handlers/admin_dialogs/base_dialogs/premium_dialog.py +++ b/handlers/admin_dialogs/base_dialogs/premium_dialog.py @@ -13,50 +13,68 @@ from handlers.admin_dialogs.admin_states import PremiumSG -async def on_get_id(message: Message, widget, dialog_manager: DialogManager, telegram_id: int): +async def on_get_id( + message: Message, widget, dialog_manager: DialogManager, telegram_id: int +): user = await get_user(telegram_id) if user is not None: - dialog_manager.dialog_data['user'] = user + dialog_manager.dialog_data["user"] = user await dialog_manager.switch_to(PremiumSG.premium_get_date) else: - dialog_manager.dialog_data['error'] = "Пользователь не найден в базе данных" + dialog_manager.dialog_data["error"] = "Пользователь не найден в базе данных" await dialog_manager.switch_to(PremiumSG.error) -async def on_date_selected(callback: CallbackQuery, widget, - manager: DialogManager, selected_date: date): +async def on_date_selected( + callback: CallbackQuery, widget, manager: DialogManager, selected_date: date +): now = datetime.now().time() - manager.dialog_data['end_date'] = datetime(year=selected_date.year, month=selected_date.month, - day=selected_date.day, hour=now.hour, minute=now.minute) + manager.dialog_data["end_date"] = datetime( + year=selected_date.year, + month=selected_date.month, + day=selected_date.day, + hour=now.hour, + minute=now.minute, + ) await manager.next() -async def accept_premium_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - user: BotUser = dialog_manager.dialog_data['user'] - end_data = dialog_manager.dialog_data['end_date'] - return {"username": user.nickname, "user_id": user.telegram_id, "premium_end": str(end_data), - "old_premium": user.premium_expire} +async def accept_premium_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + user: BotUser = dialog_manager.dialog_data["user"] + end_data = dialog_manager.dialog_data["end_date"] + return { + "username": user.nickname, + "user_id": user.telegram_id, + "premium_end": str(end_data), + "old_premium": user.premium_expire, + } -async def accept_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): - user: BotUser = manager.dialog_data['user'] - end_date = manager.dialog_data['end_date'] +async def accept_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): + user: BotUser = manager.dialog_data["user"] + end_date = manager.dialog_data["end_date"] await premium_from_datetime(user.telegram_id, end_date) await manager.switch_to(PremiumSG.all_good) premium_dialog = Dialog( Window( - Const("Введите айди телеграм аккаунта пользователя которому необходимо выдать премиум"), + Const( + "Введите айди телеграм аккаунта пользователя которому необходимо выдать премиум" + ), TextInput(type_factory=int, id="user_id", on_success=on_get_id), Cancel(Const("В меню")), - state=PremiumSG.premium_get_id + state=PremiumSG.premium_get_id, ), Window( - Const('Выберите день окончания действия премиума'), - Calendar(id='end_date', on_click=on_date_selected), - Back(Const('Назад')), - state=PremiumSG.premium_get_date + Const("Выберите день окончания действия премиума"), + Calendar(id="end_date", on_click=on_date_selected), + Back(Const("Назад")), + state=PremiumSG.premium_get_date, ), Window( Const("Хотите выдать премиум статус пользователю?"), @@ -65,18 +83,18 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo Format("Дата окончания подписки: {premium_end}"), Format("Старая дата окончания подписки: {old_premium}"), Button(Const("Выдать"), id="give_premium", on_click=accept_clicked), - Back(Const('Назад')), + Back(Const("Назад")), getter=accept_premium_getter, - state=PremiumSG.premium_accept + state=PremiumSG.premium_accept, ), Window( Const("Все вышло!Можно возвращаться в меню"), Cancel(Const("В меню")), - state=PremiumSG.all_good + state=PremiumSG.all_good, ), Window( Format("Ошибка: {dialog_data[error]}"), Cancel(Const("В меню")), - state=PremiumSG.error - ) + state=PremiumSG.error, + ), ) diff --git a/handlers/admin_dialogs/base_dialogs/season_delete_dialog.py b/handlers/admin_dialogs/base_dialogs/season_delete_dialog.py index 72b21fc..f227bec 100644 --- a/handlers/admin_dialogs/base_dialogs/season_delete_dialog.py +++ b/handlers/admin_dialogs/base_dialogs/season_delete_dialog.py @@ -8,9 +8,13 @@ from handlers.admin_dialogs.admin_states import DelSeasonSG -async def accept_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): +async def accept_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): try: - await callback.message.edit_text("Очистка начата", reply_markup=InlineKeyboardBuilder().as_markup()) + await callback.message.edit_text( + "Очистка начата", reply_markup=InlineKeyboardBuilder().as_markup() + ) await clear_season() await manager.switch_to(DelSeasonSG.season_del) except Exception as e: @@ -21,11 +25,8 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo season_delete_dialog = Dialog( Window( Const("Хотите сбросить сезон?"), - Row( - Cancel(Const("Нет")), - Next(Const("Да")) - ), - state=DelSeasonSG.accept_del + Row(Cancel(Const("Нет")), Next(Const("Да"))), + state=DelSeasonSG.accept_del, ), Window( Const("Точно???"), @@ -33,24 +34,20 @@ async def accept_clicked(callback: CallbackQuery, button: Button, manager: Dialo Next(Const("Да")), Cancel(Const("Нет")), ), - state=DelSeasonSG.accept_2 + state=DelSeasonSG.accept_2, ), Window( Const('Что бы сбросить сезон нажмите на кнопку "Сбросить"'), Button(Const("Сбросить"), id="__reset__", on_click=accept_clicked), Cancel(Const("Отмена")), - - state=DelSeasonSG.accept_3 + state=DelSeasonSG.accept_3, ), Window( - Const("Сезон сброшен!"), - Cancel(Const("В меню")), - state=DelSeasonSG.season_del + Const("Сезон сброшен!"), Cancel(Const("В меню")), state=DelSeasonSG.season_del ), Window( Format("Ошибка: {dialog_data[error]}"), Cancel(Const("В меню")), - state=DelSeasonSG.error - ) - + state=DelSeasonSG.error, + ), ) diff --git a/handlers/admin_dialogs/promo_dialogs/__init__.py b/handlers/admin_dialogs/promo_dialogs/__init__.py index dc627bc..e788c9c 100644 --- a/handlers/admin_dialogs/promo_dialogs/__init__.py +++ b/handlers/admin_dialogs/promo_dialogs/__init__.py @@ -4,4 +4,4 @@ from .create_promo_dialog import add_promo_dialog promo_dialogs = Router() -promo_dialogs.include_routers(add_promo_dialog, delete_promo_dialog) \ No newline at end of file +promo_dialogs.include_routers(add_promo_dialog, delete_promo_dialog) diff --git a/handlers/admin_dialogs/promo_dialogs/create_promo_dialog.py b/handlers/admin_dialogs/promo_dialogs/create_promo_dialog.py index c2b0cd3..3093280 100644 --- a/handlers/admin_dialogs/promo_dialogs/create_promo_dialog.py +++ b/handlers/admin_dialogs/promo_dialogs/create_promo_dialog.py @@ -11,31 +11,43 @@ from handlers.admin_dialogs.admin_states import CreatePromoSG -async def get_promo_name(message: Message, widget: ManagedTextInput, dialog_manager: DialogManager, data: str): +async def get_promo_name( + message: Message, widget: ManagedTextInput, dialog_manager: DialogManager, data: str +): promo = await get_promo(dialog_manager.find("name").get_value()) if promo is None: dialog_manager.dialog_data["name"] = data await dialog_manager.switch_to(CreatePromoSG.get_action) else: - await message.answer("Промокод с таким названием уже существует, попробуйте ещё раз") + await message.answer( + "Промокод с таким названием уже существует, попробуйте ещё раз" + ) -async def reset_cd_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): +async def reset_cd_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): manager.dialog_data["action"] = "reset_cd" await manager.switch_to(CreatePromoSG.get_expiration_time) -async def add_premium_clicked(callback: Message, button: Button, manager: DialogManager): +async def add_premium_clicked( + callback: Message, button: Button, manager: DialogManager +): manager.dialog_data["action"] = "add_premium" await manager.switch_to(CreatePromoSG.get_premium_days) -async def get_channel_func(message: Message, message_input: MessageInput, manager: DialogManager): +async def get_channel_func( + message: Message, message_input: MessageInput, manager: DialogManager +): if message.forward_origin is None: await message.answer("Это сообщение не переслано") return try: - channel_bot_member = await message.bot.get_chat_member(message.forward_origin.chat.id, message.bot.id) + channel_bot_member = await message.bot.get_chat_member( + message.forward_origin.chat.id, message.bot.id + ) except Exception: await message.answer("Вероятно бот отсутствует в пересланном канале") return @@ -49,13 +61,17 @@ async def get_channel_func(message: Message, message_input: MessageInput, manage await manager.switch_to(CreatePromoSG.accept) -async def on_date_selected(callback: CallbackQuery, widget, manager: DialogManager, selected_date: date): +async def on_date_selected( + callback: CallbackQuery, widget, manager: DialogManager, selected_date: date +): manager.dialog_data["expiration_str"] = selected_date.strftime("%d.%m.%Y") manager.dialog_data["expiration_date"] = selected_date await manager.switch_to(CreatePromoSG.get_activation_limit) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): promo_name = dialog_manager.find("name").get_value() action = dialog_manager.dialog_data["action"] if action == "add_premium": @@ -75,11 +91,19 @@ async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bo activation_limit = dialog_manager.find("activation_limit").get_value() channel = dialog_manager.dialog_data["channel"] print(expiration_str) - return {"promo_name": promo_name, "action": action_str, "premium_days": premium_days, - "expiration_str": expiration_str, "activation_limit": activation_limit, "channel": channel} - - -async def accept_clicked(callback: CallbackQuery, button: Button, dialog_manager: DialogManager): + return { + "promo_name": promo_name, + "action": action_str, + "premium_days": premium_days, + "expiration_str": expiration_str, + "activation_limit": activation_limit, + "channel": channel, + } + + +async def accept_clicked( + callback: CallbackQuery, button: Button, dialog_manager: DialogManager +): promo_name = dialog_manager.find("name").get_value() action = dialog_manager.dialog_data["action"] if action == "add_premium": @@ -92,8 +116,15 @@ async def accept_clicked(callback: CallbackQuery, button: Button, dialog_manager channel = dialog_manager.dialog_data["channel"] channel_id = dialog_manager.dialog_data["channel_id"] try: - await create_promo(code=promo_name, link=channel, action=action, days_add=premium_days, - activation_limit=activation_limit, expiration_time=expiration_date, channel_id=channel_id) + await create_promo( + code=promo_name, + link=channel, + action=action, + days_add=premium_days, + activation_limit=activation_limit, + expiration_time=expiration_date, + channel_id=channel_id, + ) await dialog_manager.switch_to(CreatePromoSG.all_ok) except Exception as e: await callback.message.answer(f"Ошибка: {e}") @@ -105,38 +136,42 @@ async def accept_clicked(callback: CallbackQuery, button: Button, dialog_manager Format("Введите название промокода"), TextInput(id="name", on_success=get_promo_name), Cancel(Const("В меню")), - state=CreatePromoSG.get_name + state=CreatePromoSG.get_name, ), Window( Format("Выберите действие"), Button(Const("Сброс кд"), id="__reset__", on_click=reset_cd_clicked), - Button(Const("Добавить премиум"), id="__add_premium__", on_click=add_premium_clicked), + Button( + Const("Добавить премиум"), + id="__add_premium__", + on_click=add_premium_clicked, + ), Back(Const("Назад")), - state=CreatePromoSG.get_action + state=CreatePromoSG.get_action, ), Window( Format("Введите количество дней премиума"), TextInput(type_factory=int, id="premium_days", on_success=Next()), Back(Const("Назад")), - state=CreatePromoSG.get_premium_days + state=CreatePromoSG.get_premium_days, ), Window( Format("Введите время действия промокода"), Calendar(id="calendar", on_click=on_date_selected), SwitchTo(Const("Назад"), id="__back__", state=CreatePromoSG.get_action), - state=CreatePromoSG.get_expiration_time + state=CreatePromoSG.get_expiration_time, ), Window( Format("Введите количество активации"), TextInput(id="activation_limit", on_success=Next(), type_factory=int), Back(Const("Назад")), - state=CreatePromoSG.get_activation_limit + state=CreatePromoSG.get_activation_limit, ), Window( Format("Добавьте бота в необходимый канал и перешлите оттуда сообщение"), MessageInput(func=get_channel_func), Back(Const("Назад")), - state=CreatePromoSG.get_channel + state=CreatePromoSG.get_channel, ), Window( Format("Хотите создать промо?"), @@ -148,11 +183,11 @@ async def accept_clicked(callback: CallbackQuery, button: Button, dialog_manager Button(Const("Создать"), id="__accept__", on_click=accept_clicked), Back(Const("Назад")), getter=accept_getter, - state=CreatePromoSG.accept + state=CreatePromoSG.accept, ), Window( Const("Промо успешно создано!"), Cancel(Const("В меню")), - state=CreatePromoSG.all_ok + state=CreatePromoSG.all_ok, ), ) diff --git a/handlers/admin_dialogs/promo_dialogs/delete_promo_dialog.py b/handlers/admin_dialogs/promo_dialogs/delete_promo_dialog.py index f0365bf..cc1985b 100644 --- a/handlers/admin_dialogs/promo_dialogs/delete_promo_dialog.py +++ b/handlers/admin_dialogs/promo_dialogs/delete_promo_dialog.py @@ -10,7 +10,9 @@ from handlers.admin_dialogs.admin_states import DeletePromoSG -async def get_promo_name(message: Message, widget: ManagedTextInput, dialog_manager: DialogManager, data: str): +async def get_promo_name( + message: Message, widget: ManagedTextInput, dialog_manager: DialogManager, data: str +): promo = await get_promo(data) if promo is None: await message.answer("Промокод не найден") @@ -20,12 +22,16 @@ async def get_promo_name(message: Message, widget: ManagedTextInput, dialog_mana await dialog_manager.switch_to(DeletePromoSG.accept) -async def accept_getter(dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs): - promo: Promo = dialog_manager.dialog_data['promo'] +async def accept_getter( + dialog_manager: DialogManager, event_from_user: User, bot: Bot, **kwargs +): + promo: Promo = dialog_manager.dialog_data["promo"] return {"promo": promo.code} -async def accept_clicked(callback: CallbackQuery, button: Button, dialog_manager: DialogManager): +async def accept_clicked( + callback: CallbackQuery, button: Button, dialog_manager: DialogManager +): promo = dialog_manager.dialog_data["promo"] await delete_promo(promo.code) await dialog_manager.switch_to(DeletePromoSG.all_ok) @@ -36,18 +42,17 @@ async def accept_clicked(callback: CallbackQuery, button: Button, dialog_manager Format("Введите название промокода, который хотите удалить"), TextInput(id="name", on_success=get_promo_name), Cancel(Format("В меню")), - state=DeletePromoSG.get_name + state=DeletePromoSG.get_name, ), Window( Format("Хотите удалить промокод {promo}?"), Button(Format("Удалить"), id="__accept__", on_click=accept_clicked), getter=accept_getter, - state=DeletePromoSG.accept + state=DeletePromoSG.accept, ), Window( Format("Промокод успешно удален"), Cancel(Format("В меню")), - state=DeletePromoSG.all_ok + state=DeletePromoSG.all_ok, ), - ) diff --git a/handlers/admin_dialogs/ref_link_dialogs/__init__.py b/handlers/admin_dialogs/ref_link_dialogs/__init__.py index 7071b8d..6c4e835 100644 --- a/handlers/admin_dialogs/ref_link_dialogs/__init__.py +++ b/handlers/admin_dialogs/ref_link_dialogs/__init__.py @@ -4,4 +4,4 @@ from .ref_links_add_dialog import ref_links_add_dialog ref_link_dialogs = Router() -ref_link_dialogs.include_routers(view_links_dialog, ref_links_add_dialog) \ No newline at end of file +ref_link_dialogs.include_routers(view_links_dialog, ref_links_add_dialog) diff --git a/handlers/admin_dialogs/ref_link_dialogs/ref_links_add_dialog.py b/handlers/admin_dialogs/ref_link_dialogs/ref_links_add_dialog.py index 90a72b5..f4297f6 100644 --- a/handlers/admin_dialogs/ref_link_dialogs/ref_links_add_dialog.py +++ b/handlers/admin_dialogs/ref_link_dialogs/ref_links_add_dialog.py @@ -17,12 +17,14 @@ def check_link(link: str) -> str: if len(link) > 16: raise ValueError("Слишком длинная ссылка") - if not re.fullmatch(r'[A-Za-z0-9\-]+', link): + if not re.fullmatch(r"[A-Za-z0-9\-]+", link): raise ValueError("Ссылка содержит недопустимые символы") return link -async def error(message: Message, dialog_: Any, manager: DialogManager, link_error: ValueError): +async def error( + message: Message, dialog_: Any, manager: DialogManager, link_error: ValueError +): await message.answer(f"Произошла ошибка: {link_error.args[0]}") @@ -34,19 +36,31 @@ async def link_created_getter(dialog_manager: DialogManager, bot: Bot, **kwargs) return {"users_link": links["link_user"], "groups_link": links["link_group"]} -async def on_success(message: Message, widget: ManagedTextInput, dialog_manager: DialogManager, data_, **kwargs): +async def on_success( + message: Message, + widget: ManagedTextInput, + dialog_manager: DialogManager, + data_, + **kwargs, +): link = await get_ref_link(widget.get_value()) if link is not None: await dialog_manager.switch_to(AddRefLinkSG.error) else: await dialog_manager.switch_to(AddRefLinkSG.all_ok) + ref_links_add_dialog = Dialog( Window( Const("Введите имя новой ссылки:"), - TextInput(id="link_name", type_factory=check_link, on_success=on_success, on_error=error), + TextInput( + id="link_name", + type_factory=check_link, + on_success=on_success, + on_error=error, + ), Cancel(Const("В меню")), - state=AddRefLinkSG.get_link + state=AddRefLinkSG.get_link, ), Window( Format("Ссылка успешно зарегистрирована!"), @@ -55,12 +69,12 @@ async def on_success(message: Message, widget: ManagedTextInput, dialog_manager: Cancel(Const("В меню")), parse_mode=ParseMode.HTML, getter=link_created_getter, - state=AddRefLinkSG.all_ok + state=AddRefLinkSG.all_ok, ), Window( Jinja("Ошибка! Cсылка уже зарегистрирована!"), Cancel(Const("В меню")), SwitchTo(Const("Создать ссылку"), id="reboot", state=AddRefLinkSG.get_link), - state=AddRefLinkSG.error - ) + state=AddRefLinkSG.error, + ), ) diff --git a/handlers/admin_dialogs/ref_link_dialogs/ref_links_view_dialog.py b/handlers/admin_dialogs/ref_link_dialogs/ref_links_view_dialog.py index 23130e4..907c44a 100644 --- a/handlers/admin_dialogs/ref_link_dialogs/ref_links_view_dialog.py +++ b/handlers/admin_dialogs/ref_link_dialogs/ref_links_view_dialog.py @@ -10,11 +10,17 @@ from database import get_all_groups_with_bot_ids from database.ref_link import delete_ref_link, get_all_links, get_links -from database.statistic import get_all_users_with_link, get_groups_with_link_count, get_users_with_link_count +from database.statistic import ( + get_all_users_with_link, + get_groups_with_link_count, + get_users_with_link_count, +) from handlers.admin_dialogs.admin_states import ViewRefLinkSG -async def error(message: Message, dialog_: Any, manager: DialogManager, link_error: ValueError): +async def error( + message: Message, dialog_: Any, manager: DialogManager, link_error: ValueError +): await message.answer(f"Произошла ошибка: {link_error.args[0]}") @@ -23,7 +29,9 @@ async def all_link_getter(dialog_manager: DialogManager, bot: Bot, **kwargs): return {"links": links} -async def on_provider_selected(callback: CallbackQuery, button: Button, manager: DialogManager, link: str): +async def on_provider_selected( + callback: CallbackQuery, button: Button, manager: DialogManager, link: str +): manager.dialog_data["link_name"] = link await manager.switch_to(ViewRefLinkSG.one_link) @@ -33,18 +41,26 @@ async def link_getter(dialog_manager: DialogManager, bot: Bot, **kwargs): links = await get_links(link, (await bot.get_me()).username) users_from_link = await get_users_with_link_count(link) groups_from_link = await get_groups_with_link_count(link) - return {"link_name": link, "users_link": links["link_user"], "groups_link": links["link_group"], - "users_from_link": users_from_link, "groups_from_link": groups_from_link - } - - -async def on_delete_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): + return { + "link_name": link, + "users_link": links["link_user"], + "groups_link": links["link_group"], + "users_from_link": users_from_link, + "groups_from_link": groups_from_link, + } + + +async def on_delete_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): link = manager.dialog_data["link_name"] await delete_ref_link(link) await manager.switch_to(ViewRefLinkSG.link_list) -async def on_upload_users_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): +async def on_upload_users_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): link = manager.dialog_data["link_name"] users_ids = await get_all_users_with_link(link) group_ids = "\n".join(str(user_id) for user_id in users_ids) @@ -53,7 +69,9 @@ async def on_upload_users_clicked(callback: CallbackQuery, button: Button, manag ) -async def on_upload_groups_clicked(callback: CallbackQuery, button: Button, manager: DialogManager): +async def on_upload_groups_clicked( + callback: CallbackQuery, button: Button, manager: DialogManager +): groups_ids = await get_all_groups_with_bot_ids() group_ids = "\n".join(str(group_id) for group_id in groups_ids) await callback.message.answer_document( @@ -70,7 +88,7 @@ async def on_upload_groups_clicked(callback: CallbackQuery, button: Button, mana id="choose_link_select", item_id_getter=lambda item: item, items="links", - on_click=on_provider_selected + on_click=on_provider_selected, ), width=1, height=5, @@ -78,7 +96,7 @@ async def on_upload_groups_clicked(callback: CallbackQuery, button: Button, mana ), Cancel(Const("В меню")), state=ViewRefLinkSG.link_list, - getter=all_link_getter + getter=all_link_getter, ), Window( Jinja("Статистика ссылки {{link_name}}: \n\n"), @@ -86,12 +104,20 @@ async def on_upload_groups_clicked(callback: CallbackQuery, button: Button, mana Jinja(" -Переходы в группы: {{ groups_from_link }}\n\n"), Jinja("{{ users_link }}\n\n"), Jinja("{{ groups_link }}"), - Button(Const("Выгрузка юзеров"), id="download_users", on_click=on_upload_users_clicked), - Button(Const("Выгрузка групп"), id="download_groups", on_click=on_upload_groups_clicked), + Button( + Const("Выгрузка юзеров"), + id="download_users", + on_click=on_upload_users_clicked, + ), + Button( + Const("Выгрузка групп"), + id="download_groups", + on_click=on_upload_groups_clicked, + ), Button(Const("Удалить"), id="delete_link", on_click=on_delete_clicked), Back(Const("Назад")), parse_mode=ParseMode.HTML, getter=link_getter, - state=ViewRefLinkSG.one_link - ) + state=ViewRefLinkSG.one_link, + ), ) diff --git a/handlers/commands.py b/handlers/commands.py index 5db710f..4d40e61 100644 --- a/handlers/commands.py +++ b/handlers/commands.py @@ -13,19 +13,32 @@ from database.premium import check_premium from database.ref_link import get_ref_link from database.user import check_last_get, set_last_get -from database.user import get_user, in_pm_change, set_user_refer_code, update_last_bonus_get +from database.user import ( + get_user, + in_pm_change, + set_user_refer_code, + update_last_bonus_get, +) from handlers.admin_dialogs.admin_states import AdminSG from handlers.premium import send_payment_method_selection from utils import loader from utils.kb import check_subscribe_keyboard, help_kb, premium_keyboard, start_kb from utils.loader import admins, flyer from utils.states import user_button -from data.text import HELP_MESSAGE, PREMIUM_TEXT, PRIVACY_MESSAGE, WELCOME_MESSAGE, WELCOME_MESSAGE_PRIVATE +from data.text import ( + HELP_MESSAGE, + PREMIUM_TEXT, + PRIVACY_MESSAGE, + WELCOME_MESSAGE, + WELCOME_MESSAGE_PRIVATE, +) commands_router = Router() -@commands_router.message(CommandStart(deep_link=True, magic=F.args.regexp(re.compile(r'bonus_(\w+)')))) +@commands_router.message( + CommandStart(deep_link=True, magic=F.args.regexp(re.compile(r"bonus_(\w+)"))) +) async def handler_bot_start(msg: Message, command: CommandObject): link = await get_bonus_link(command.args.split("_")[-1]) @@ -35,26 +48,36 @@ async def handler_bot_start(msg: Message, command: CommandObject): user_id = link.for_user_id if user_id != msg.from_user.id: - await msg.answer("Это не ваш бонус. Напишите /bonus и перейдите по ссылке для получения бонуса") + await msg.answer( + "Это не ваш бонус. Напишите /bonus и перейдите по ссылке для получения бонуса" + ) return user = await get_user(msg.from_user.id) if user.check_bonus_available(): is_subscribe = await flyer.check(msg.from_user.id, msg.from_user.language_code) if is_subscribe: - if await check_last_get(user.last_usage, await check_premium(user.premium_expire)): - await msg.answer("У вас есть возможность получить карточку сейчас, используйте ее " - "и возвращайтесь за бонусом!") + if await check_last_get( + user.last_usage, await check_premium(user.premium_expire) + ): + await msg.answer( + "У вас есть возможность получить карточку сейчас, используйте ее " + "и возвращайтесь за бонусом!" + ) else: await update_last_bonus_get(msg.from_user.id) - await set_last_get(user.telegram_id, datetime.now() - timedelta(hours=4)) - + await set_last_get( + user.telegram_id, datetime.now() - timedelta(hours=4) + ) + await deactivate_bonus_link(link.code) await msg.answer("Бонус получен. Вы можете снова получить карточку") else: - await msg.answer("После подписки на каналы спонсоров нажмите чтобы получить бонус", - reply_markup=await check_subscribe_keyboard(link.code)) + await msg.answer( + "После подписки на каналы спонсоров нажмите чтобы получить бонус", + reply_markup=await check_subscribe_keyboard(link.code), + ) else: await msg.answer("Упс... 12 часов с последнего бонуса не прошло") @@ -65,25 +88,32 @@ async def handler_bonus_command(msg: Message): await msg.answer("❗️ Данная команда работает только в ЛС") return user = await get_user(msg.from_user.id) - + if not user.check_bonus_available(): await msg.answer("Упс... 4 часа с последнего бонуса не прошло") return is_subscribe = await flyer.check(msg.from_user.id, msg.from_user.language_code) if is_subscribe: - if await check_last_get(user.last_usage, await check_premium(user.premium_expire)): - await msg.answer("У вас есть возможность получить карточку сейчас, используйте ее " - "и возвращайтесь за бонусом!") + if await check_last_get( + user.last_usage, await check_premium(user.premium_expire) + ): + await msg.answer( + "У вас есть возможность получить карточку сейчас, используйте ее " + "и возвращайтесь за бонусом!" + ) else: await update_last_bonus_get(msg.from_user.id) await set_last_get(user.telegram_id, datetime.now() - timedelta(hours=4)) await msg.answer("Бонус получен. Вы можете снова получить карточку") else: - temp_code = ''.join(random.choices('abcdefghijklmnopqrstuvwxyz0123456789', k=10)) - await msg.answer("После подписки на каналы спонсоров нажмите чтобы получить бонус", - reply_markup=await check_subscribe_keyboard(temp_code)) - + temp_code = "".join( + random.choices("abcdefghijklmnopqrstuvwxyz0123456789", k=10) + ) + await msg.answer( + "После подписки на каналы спонсоров нажмите чтобы получить бонус", + reply_markup=await check_subscribe_keyboard(temp_code), + ) @commands_router.message(Command("premium")) @@ -91,43 +121,60 @@ async def premium_command(msg: Message): try: unique_id = str(random.randint(10000, 9999999999)) markup = await premium_keyboard(unique_id) - await msg.bot.send_message(msg.from_user.id, f"{PREMIUM_TEXT} Выберите способ оплаты премиума:", - reply_markup=markup,parse_mode="HTML") + await msg.bot.send_message( + msg.from_user.id, + f"{PREMIUM_TEXT} Выберите способ оплаты премиума:", + reply_markup=markup, + parse_mode="HTML", + ) if msg.chat.type != "private": await msg.answer( f"{str(msg.from_user.first_name)}, " - f"информация о способах оплаты отправлена вам в личные сообщения.") + f"информация о способах оплаты отправлена вам в личные сообщения." + ) except Exception as e: print(e) - await msg.reply("Пожалуйста, напишите боту что-то в личные сообщения, чтобы я смог отправить информацию.") + await msg.reply( + "Пожалуйста, напишите боту что-то в личные сообщения, чтобы я смог отправить информацию." + ) @commands_router.callback_query(F.data.startswith("check_subscribe_")) async def check_subscribe(callback: CallbackQuery, dialog_manager: DialogManager): - link_code = callback.data.split('_')[-1] + link_code = callback.data.split("_")[-1] link = await get_bonus_link(link_code) if link is None or not link.is_active: await callback.message.answer("Бонус не найден или уже использован") await callback.message.delete() return - is_subscribe = await flyer.check(callback.from_user.id, callback.from_user.language_code) + is_subscribe = await flyer.check( + callback.from_user.id, callback.from_user.language_code + ) user = await get_user(callback.from_user.id) if is_subscribe: - if await check_last_get(user.last_usage, await check_premium(user.premium_expire)): - await callback.message.answer("У вас есть возможность получить карточку сейчас, используйте ее " - "и возвращайтесь за бонусом!") + if await check_last_get( + user.last_usage, await check_premium(user.premium_expire) + ): + await callback.message.answer( + "У вас есть возможность получить карточку сейчас, используйте ее " + "и возвращайтесь за бонусом!" + ) else: await update_last_bonus_get(callback.from_user.id) await delete_bonus_link(link_code) await set_last_get(user.telegram_id, datetime.now() - timedelta(hours=4)) - await callback.message.answer("Бонус получен. Вы можете снова получить карточку") + await callback.message.answer( + "Бонус получен. Вы можете снова получить карточку" + ) await callback.message.delete() await callback.message.delete() else: await callback.answer("Подпишитесь что бы получить бонус") -@commands_router.message(CommandStart(deep_link=True, magic=F.args.regexp(re.compile(r'ref_(\w+)')))) +@commands_router.message( + CommandStart(deep_link=True, magic=F.args.regexp(re.compile(r"ref_(\w+)"))) +) async def start_ref(msg: Message, command: CommandObject, created: bool): refer = command.args.split("_")[-1] ref_link = await get_ref_link(refer) @@ -139,6 +186,7 @@ async def start_ref(msg: Message, command: CommandObject, created: bool): elif created and msg.chat.type in ["group", "supergroup"]: await set_group_refer_code(msg.chat.id, refer) + @commands_router.message(CommandStart(deep_link=True, magic=F.args == "premium")) async def start_premium(msg: Message, command: CommandObject): unique_id = str(random.randint(10000, 9999999999)) @@ -153,15 +201,17 @@ async def handler_start_command(msg: Message, command: CommandObject): if user.in_pm is None or user.in_pm is False: await in_pm_change(msg.from_user.id, True) markup = await start_kb(msg) - await msg.answer(WELCOME_MESSAGE_PRIVATE, reply_markup=markup, parse_mode='HTML') + await msg.answer( + WELCOME_MESSAGE_PRIVATE, reply_markup=markup, parse_mode="HTML" + ) else: - await msg.answer(WELCOME_MESSAGE, parse_mode='HTML') + await msg.answer(WELCOME_MESSAGE, parse_mode="HTML") @commands_router.message(Command("help")) async def help_handler(msg: Message, dialog_manager: DialogManager): markup = await help_kb(msg) - await msg.answer(HELP_MESSAGE, reply_markup=markup, parse_mode='HTML') + await msg.answer(HELP_MESSAGE, reply_markup=markup, parse_mode="HTML") @commands_router.message(Command("privacy")) @@ -177,6 +227,7 @@ async def admin_cmd(message: Message, dialog_manager: DialogManager): return await dialog_manager.start(AdminSG.menu) + @commands_router.message(Command("test")) async def test(msg: Message, dialog_manager: DialogManager): if msg.from_user.id not in loader.admins: @@ -184,4 +235,4 @@ async def test(msg: Message, dialog_manager: DialogManager): await msg.answer("Начинаю парс карточек...") await parse_cards("data/config.json") await parse_limited_cards("data/limited_config.json") - await msg.answer("Карточки спаршены!") \ No newline at end of file + await msg.answer("Карточки спаршены!") diff --git a/handlers/premium.py b/handlers/premium.py index ce797c2..25c6e7e 100644 --- a/handlers/premium.py +++ b/handlers/premium.py @@ -3,7 +3,12 @@ from datetime import timedelta from aiogram import F, Router, types -from aiogram.types import CallbackQuery, LabeledPrice, PreCheckoutQuery, SuccessfulPayment +from aiogram.types import ( + CallbackQuery, + LabeledPrice, + PreCheckoutQuery, + SuccessfulPayment, +) from aiogram_dialog import DialogManager from aiogram.utils.text_decorations import html_decoration from aiogram.enums.parse_mode import ParseMode @@ -18,8 +23,12 @@ async def send_payment_method_selection(callback, user_id, unique_id): markup = await premium_keyboard(unique_id) - await callback.bot.send_message(user_id, f"{PREMIUM_TEXT} Выберите способ оплаты премиума:", - reply_markup=markup,parse_mode=ParseMode.HTML) + await callback.bot.send_message( + user_id, + f"{PREMIUM_TEXT} Выберите способ оплаты премиума:", + reply_markup=markup, + parse_mode=ParseMode.HTML, + ) @premium_router.callback_query(F.data.startswith("pay_stars_")) @@ -35,7 +44,9 @@ async def pay_with_stars(callback: CallbackQuery, dialog_manager: DialogManager) currency="XTR", reply_markup=await payment_keyboard(35), ) - await callback.bot.delete_message(callback.message.chat.id, callback.message.message_id) + await callback.bot.delete_message( + callback.message.chat.id, callback.message.message_id + ) except Exception as e: await callback.message.answer(f"Произошла ошибка: {str(e)}") logging.error(f"Error in pay_with_stars: {e}") @@ -48,30 +59,40 @@ async def on_pre_checkout_query( await pre_checkout_query.answer(ok=True) -@premium_router.message(F.successful_payment, lambda message: message.successful_payment.invoice_payload == "komaru_premium") +@premium_router.message( + F.successful_payment, + lambda message: message.successful_payment.invoice_payload == "komaru_premium", +) async def handle_komaru_premium(message: types.Message): successful_payment = message.successful_payment await add_premium(message.from_user.id, timedelta(days=30)) - await message.answer("🌟 Спасибо за покупку Премиума! Наслаждайтесь эксклюзивными преимуществами.") - + await message.answer( + "🌟 Спасибо за покупку Премиума! Наслаждайтесь эксклюзивными преимуществами." + ) @premium_router.callback_query(F.data.startswith("pay_crypto_")) -async def create_and_send_invoice(callback: CallbackQuery, dialog_manager: DialogManager): +async def create_and_send_invoice( + callback: CallbackQuery, dialog_manager: DialogManager +): try: - invoice = await crypto.create_invoice(asset='USDT', amount=0.7) + invoice = await crypto.create_invoice(asset="USDT", amount=0.7) if not invoice: response = "Ошибка при создании инвойса. Попробуйте позже." await callback.bot.send_message(callback.from_user.id, response) return None - markup = await payment_crypto_keyboard(invoice.invoice_id, invoice.bot_invoice_url) + markup = await payment_crypto_keyboard( + invoice.invoice_id, invoice.bot_invoice_url + ) - response = ( - f"Премиум активируется после подтверждения оплаты. Реквизиты: {invoice.bot_invoice_url}\n\nЦена: 0.70 USDT" + response = f"Премиум активируется после подтверждения оплаты. Реквизиты: {invoice.bot_invoice_url}\n\nЦена: 0.70 USDT" + await callback.bot.delete_message( + callback.message.chat.id, callback.message.message_id + ) + await callback.bot.send_message( + callback.from_user.id, response, reply_markup=markup ) - await callback.bot.delete_message(callback.message.chat.id, callback.message.message_id) - await callback.bot.send_message(callback.from_user.id, response, reply_markup=markup) return invoice except Exception as e: error_message = f"Ошибка при создании инвойса: {e}" @@ -81,7 +102,7 @@ async def create_and_send_invoice(callback: CallbackQuery, dialog_manager: Dialo @premium_router.callback_query(F.data.startswith("verify_payment")) async def verify_payment(call, dialog_manager: DialogManager): - parts = call.data.split('_') + parts = call.data.split("_") if len(parts) < 3: await call.bot.send_message(call.message.chat.id, "Ошибка в данных платежа.") return @@ -91,15 +112,22 @@ async def verify_payment(call, dialog_manager: DialogManager): try: print("Invoice ID:", invoice) payment_status = await get_invoice_status(invoice) - if payment_status == 'paid': + if payment_status == "paid": await add_premium(call.from_user.id, timedelta(days=30)) - await call.bot.send_message(call.from_user.id, - "🌟 Спасибо за покупку Премиума! Наслаждайтесь эксклюзивными преимуществами.") + await call.bot.send_message( + call.from_user.id, + "🌟 Спасибо за покупку Премиума! Наслаждайтесь эксклюзивными преимуществами.", + ) await call.bot.delete_message(call.message.chat.id, call.message.message_id) else: - await call.bot.send_message(call.from_user.id, "Оплата не прошла! Попробуйте еще раз.") + await call.bot.send_message( + call.from_user.id, "Оплата не прошла! Попробуйте еще раз." + ) except Exception as e: - await call.bot.send_message(call.from_user.id, f"Произошла ошибка при проверке статуса платежа: {str(e)}") + await call.bot.send_message( + call.from_user.id, + f"Произошла ошибка при проверке статуса платежа: {str(e)}", + ) async def get_invoice_status(invoice_id): diff --git a/handlers/profile.py b/handlers/profile.py index ca44014..71e236d 100644 --- a/handlers/profile.py +++ b/handlers/profile.py @@ -11,11 +11,22 @@ from database.cards import get_all_cards, get_card, get_lcard from database.models import Card, User from database.premium import check_premium -from database.top import get_me_on_top, get_top_users_by_all_points, get_top_users_by_cards, get_top_users_by_points +from database.top import ( + get_me_on_top, + get_top_users_by_all_points, + get_top_users_by_cards, + get_top_users_by_points, +) from database.user import get_user, set_love_card from filters import ProfileFilter from handlers.premium import send_payment_method_selection -from utils.kb import cards_kb, get_card_navigation_keyboard, get_limited_card_navigation_keyboard, profile_kb, top_kb +from utils.kb import ( + cards_kb, + get_card_navigation_keyboard, + get_limited_card_navigation_keyboard, + profile_kb, + top_kb, +) from utils.loader import bot from utils.states import get_dev_titul, get_titul, user_button from data.text import responses @@ -25,30 +36,44 @@ profile_router = Router() -async def send_initial_card_with_navigation(chat_id, user_id, rarity, rarity_cards, card_index): +async def send_initial_card_with_navigation( + chat_id, user_id, rarity, rarity_cards, card_index +): if card_index < len(rarity_cards): card: Card = rarity_cards[card_index] photo_data = card.photo caption = f"{card.name}\nРедкость: {card.rarity}\n\nОчки: {str(card.points)}\n" - markup = await get_card_navigation_keyboard(user_id, card_index, rarity, rarity_cards, card.id) + markup = await get_card_navigation_keyboard( + user_id, card_index, rarity, rarity_cards, card.id + ) - await bot.send_photo(chat_id, photo=photo_data, caption=caption, reply_markup=markup) + await bot.send_photo( + chat_id, photo=photo_data, caption=caption, reply_markup=markup + ) else: logging.error(f"Card index {card_index} out of range for rarity cards") -async def send_card_with_navigation(chat_id, message_id, user_id, rarity, rarity_cards, card_index): +async def send_card_with_navigation( + chat_id, message_id, user_id, rarity, rarity_cards, card_index +): if card_index < len(rarity_cards): card: Card = rarity_cards[card_index] photo_data = card.photo caption = f"{card.name}\nРедкость: {card.rarity}\n\nОчки: {str(card.points)}\n" - markup = await get_card_navigation_keyboard(user_id, card_index, rarity, rarity_cards, card.id) + markup = await get_card_navigation_keyboard( + user_id, card_index, rarity, rarity_cards, card.id + ) media = InputMediaPhoto(media=photo_data) - await bot.edit_message_media(media=media, chat_id=chat_id, message_id=message_id) - await bot.edit_message_caption(caption=caption, chat_id=chat_id, message_id=message_id, reply_markup=markup) + await bot.edit_message_media( + media=media, chat_id=chat_id, message_id=message_id + ) + await bot.edit_message_caption( + caption=caption, chat_id=chat_id, message_id=message_id, reply_markup=markup + ) else: logging.error(f"Card index {card_index} out of range for rarity cards") @@ -62,15 +87,29 @@ async def user_profile(msg: Message, dialog_manager: DialogManager): titul = await get_titul(user.card_count) collected_cards = len(user.cards) total_cards = len(await get_all_cards()) - favorite_card = await get_card(user.love_card['id'] if isinstance(user.love_card, dict) else user.love_card) + favorite_card = await get_card( + user.love_card["id"] if isinstance(user.love_card, dict) else user.love_card + ) if favorite_card is None: favorite_card = "нету" else: favorite_card = favorite_card.name premium_status = await check_premium(user.premium_expire) - premium_message = f"Премиум: активен до {user.premium_expire.date()}" if premium_status else "
Рекомендуем приобрести Premium
" + premium_message = ( + f"Премиум: активен до {user.premium_expire.date()}" + if premium_status + else "
Рекомендуем приобрести Premium
" + ) - if user_id in [6184515646, 1268026433, 5493956779, 1022923020, 851455143, 6794926384, 6679727618]: + if user_id in [ + 6184515646, + 1268026433, + 5493956779, + 1022923020, + 851455143, + 6794926384, + 6679727618, + ]: dev_titul = await get_dev_titul(user_id) dev_titul_message = f"
🪬 Dev Титул: {dev_titul}
" else: @@ -84,7 +123,7 @@ async def user_profile(msg: Message, dialog_manager: DialogManager): photo_cache = file_id else: - photo_cache = 'https://tinypic.host/images/2025/02/14/cat.jpeg' + photo_cache = "https://tinypic.host/images/2025/02/14/cat.jpeg" caption = ( f"Профиль «{html_decoration.bold(html_decoration.quote(user.nickname))}»\n\n" @@ -99,14 +138,23 @@ async def user_profile(msg: Message, dialog_manager: DialogManager): ) markup = await profile_kb(msg) - await bot.send_photo(msg.chat.id, photo=photo_cache, caption=caption, reply_markup=markup, parse_mode="HTML") + await bot.send_photo( + msg.chat.id, + photo=photo_cache, + caption=caption, + reply_markup=markup, + parse_mode="HTML", + ) except Exception as e: if "bot was blocked by the user" in str(e): - await msg.answer("Пожалуйста, разблокируйте бота для доступа к вашему профилю.") + await msg.answer( + "Пожалуйста, разблокируйте бота для доступа к вашему профилю." + ) else: print(e) await msg.answer( - "Произошла ошибка при доступе к вашему профилю. Попробуйте позже.") + "Произошла ошибка при доступе к вашему профилю. Попробуйте позже." + ) @profile_router.message(ProfileFilter() or F.command("profile")) @@ -115,34 +163,54 @@ async def user_profile_comments(msg: Message, dialog_manager: DialogManager): @profile_router.callback_query(F.data.startswith("show_cards")) -async def show_cards_second(callback: types.CallbackQuery, dialog_manager: DialogManager): - unique_id = str(callback.data.split('_')[-1]) - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): +async def show_cards_second( + callback: types.CallbackQuery, dialog_manager: DialogManager +): + unique_id = str(callback.data.split("_")[-1]) + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(text=random.choice(responses), show_alert=True) return user_id = callback.from_user.id user = await get_user(user_id) user_nickname = callback.from_user.first_name - + markup = InlineKeyboardBuilder() - markup.row(types.InlineKeyboardButton(text="ОБЫЧНЫЕ КАРТОЧКИ", callback_data=f"show_usual_{unique_id}")) - markup.row(types.InlineKeyboardButton(text="ЛИМИТИРОВАННЫЕ КАРТОЧКИ", callback_data=f"show_limited_{unique_id}")) - + markup.row( + types.InlineKeyboardButton( + text="ОБЫЧНЫЕ КАРТОЧКИ", callback_data=f"show_usual_{unique_id}" + ) + ) + markup.row( + types.InlineKeyboardButton( + text="ЛИМИТИРОВАННЫЕ КАРТОЧКИ", callback_data=f"show_limited_{unique_id}" + ) + ) + try: - await bot.send_message(user_id, "Выберите тип карточек:", reply_markup=markup.as_markup()) + await bot.send_message( + user_id, "Выберите тип карточек:", reply_markup=markup.as_markup() + ) if callback.message.chat.type in ["supergroup", "group"]: - await bot.send_message(chat_id=callback.message.chat.id, - text=f"{user_nickname}, выбор отправлен вам в личные сообщения!") + await bot.send_message( + chat_id=callback.message.chat.id, + text=f"{user_nickname}, выбор отправлен вам в личные сообщения!", + ) except Exception as e: logging.error(f"Не удалось отправить сообщение: {str(e)}") - await callback.answer("Напишите боту что-то в личные сообщения!", show_alert=True) + await callback.answer( + "Напишите боту что-то в личные сообщения!", show_alert=True + ) @profile_router.callback_query(F.data.startswith("show_usual_")) async def show_usual_cards(callback: types.CallbackQuery): - unique_id = callback.data.split('_')[-1] - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): + unique_id = callback.data.split("_")[-1] + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(text=random.choice(responses), show_alert=True) return @@ -161,22 +229,24 @@ async def show_usual_cards(callback: types.CallbackQuery): builder = InlineKeyboardBuilder() for rarity in rarities: - builder.row(types.InlineKeyboardButton( - text=rarity, - callback_data=f"usual_rarity_{rarity}_{unique_id}" - )) + builder.row( + types.InlineKeyboardButton( + text=rarity, callback_data=f"usual_rarity_{rarity}_{unique_id}" + ) + ) await callback.message.edit_text( - "Выберите редкость карточек:", - reply_markup=builder.as_markup() + "Выберите редкость карточек:", reply_markup=builder.as_markup() ) @profile_router.callback_query(F.data.startswith("usual_rarity_")) async def show_usual_cards_by_rarity(callback: types.CallbackQuery): - _, _, rarity, unique_id = callback.data.split('_', 3) - - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): + _, _, rarity, unique_id = callback.data.split("_", 3) + + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(text=random.choice(responses), show_alert=True) return @@ -194,7 +264,9 @@ async def show_usual_cards_by_rarity(callback: types.CallbackQuery): return card = cards_of_rarity[0] - keyboard = await get_card_navigation_keyboard(user_id, 0, rarity, cards_of_rarity, card.id) + keyboard = await get_card_navigation_keyboard( + user_id, 0, rarity, cards_of_rarity, card.id + ) caption = ( f"🃏 ОБЫЧНАЯ КАРТОЧКА\n" f"{card.name}\n" @@ -207,14 +279,18 @@ async def show_usual_cards_by_rarity(callback: types.CallbackQuery): photo=card.photo, caption=caption, reply_markup=keyboard, - parse_mode="HTML" + parse_mode="HTML", ) @profile_router.callback_query(F.data.startswith("show_limited_")) -async def show_limited_cards(callback: types.CallbackQuery, dialog_manager: DialogManager): - unique_id = callback.data.split('_')[-1] - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): +async def show_limited_cards( + callback: types.CallbackQuery, dialog_manager: DialogManager +): + unique_id = callback.data.split("_")[-1] + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(text=random.choice(responses), show_alert=True) return @@ -230,7 +306,9 @@ async def show_limited_cards(callback: types.CallbackQuery, dialog_manager: Dial if limited_cards: card = limited_cards[0] - keyboard = await get_limited_card_navigation_keyboard(user_id, 0, limited_cards, card.id) + keyboard = await get_limited_card_navigation_keyboard( + user_id, 0, limited_cards, card.id + ) caption = ( f"🎴 ЛИМИТИРОВАННАЯ КАРТОЧКА\n" f"{card.name}\n\n" @@ -246,10 +324,12 @@ async def show_limited_cards(callback: types.CallbackQuery, dialog_manager: Dial photo=card.photo, caption=caption, reply_markup=keyboard, - parse_mode="HTML" + parse_mode="HTML", ) else: - await callback.answer("Произошла ошибка при загрузке карточек", show_alert=True) + await callback.answer( + "Произошла ошибка при загрузке карточек", show_alert=True + ) else: await callback.answer("У вас пока нет лимитированных карточек", show_alert=True) @@ -257,7 +337,7 @@ async def show_limited_cards(callback: types.CallbackQuery, dialog_manager: Dial @profile_router.callback_query(F.data.startswith("navigate_limited_")) async def navigate_limited_cards(callback: types.CallbackQuery): try: - parts = callback.data.split('_') + parts = callback.data.split("_") user_id = int(parts[2]) direction = parts[3] new_index = int(parts[4]) @@ -271,8 +351,10 @@ async def navigate_limited_cards(callback: types.CallbackQuery): if 0 <= new_index < len(limited_cards): card = limited_cards[new_index] - keyboard = await get_limited_card_navigation_keyboard(user_id, new_index, limited_cards, card.id) - + keyboard = await get_limited_card_navigation_keyboard( + user_id, new_index, limited_cards, card.id + ) + caption = ( f"🎴 ЛИМИТИРОВАННАЯ КАРТОЧКА\n" f"{card.name}\n\n" @@ -282,19 +364,21 @@ async def navigate_limited_cards(callback: types.CallbackQuery): if card.description: caption += f"\n{card.description}" - media = InputMediaPhoto(media=card.photo, caption=caption, parse_mode="HTML") + media = InputMediaPhoto( + media=card.photo, caption=caption, parse_mode="HTML" + ) await callback.message.edit_media(media=media, reply_markup=keyboard) else: await callback.answer("Карточка не найдена") except Exception as e: logging.error(f"Error in navigate_limited_cards: {str(e)}") await callback.answer("Произошла ошибка при навигации") - + @profile_router.callback_query(F.data.startswith("navigate_")) async def navigate_cards(callback: types.CallbackQuery): try: - parts = callback.data.split('_') + parts = callback.data.split("_") user_id = int(parts[1]) direction = parts[2] new_index = int(parts[3]) @@ -309,8 +393,10 @@ async def navigate_cards(callback: types.CallbackQuery): if 0 <= new_index < len(cards_of_rarity): card = cards_of_rarity[new_index] - keyboard = await get_card_navigation_keyboard(user_id, new_index, rarity, cards_of_rarity, card.id) - + keyboard = await get_card_navigation_keyboard( + user_id, new_index, rarity, cards_of_rarity, card.id + ) + caption = ( f"🃏 ОБЫЧНАЯ КАРТОЧКА\n" f"{card.name}\n" @@ -318,7 +404,9 @@ async def navigate_cards(callback: types.CallbackQuery): f"Очки: {card.points}\n" ) - media = InputMediaPhoto(media=card.photo, caption=caption, parse_mode="HTML") + media = InputMediaPhoto( + media=card.photo, caption=caption, parse_mode="HTML" + ) await callback.message.edit_media(media=media, reply_markup=keyboard) else: await callback.answer("Карточка не найдена") @@ -329,43 +417,52 @@ async def navigate_cards(callback: types.CallbackQuery): @profile_router.callback_query(F.data.startswith("love_")) async def handle_love_card(callback: types.CallbackQuery): - parts = callback.data.split('_') + parts = callback.data.split("_") user_id, card_id = int(parts[1]), int(parts[2]) card = await get_card(card_id) if card is not None: await set_love_card(user_id, card_id) - await bot.answer_callback_query(callback.id, f"Карточка '{card.name}' теперь ваша любимая!") + await bot.answer_callback_query( + callback.id, f"Карточка '{card.name}' теперь ваша любимая!" + ) else: await bot.answer_callback_query(callback.id, "Не найдено карточек с таким ID.") @profile_router.callback_query(F.data.startswith("top_komaru")) async def top_komaru(callback: types.CallbackQuery): - unique_id = str(callback.data.split('_')[-1]) - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): + unique_id = str(callback.data.split("_")[-1]) + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(random.choice(responses), show_alert=True) return markup = await top_kb(callback, "all_top") await callback.message.answer( - text="Топ 10 пользователей по карточкам. Выберите кнопку:", - reply_markup=markup) - + text="Топ 10 пользователей по карточкам. Выберите кнопку:", reply_markup=markup + ) + @profile_router.message(Command("top")) async def top_komaru_command(msg: Message): markup = await top_kb(msg, "all_top") - await msg.answer("🏆 Топ 10 игроков:\n
Выберите по какому значению показать топ
", reply_markup=markup,parse_mode=ParseMode.HTML) - + await msg.answer( + "🏆 Топ 10 игроков:\n
Выберите по какому значению показать топ
", + reply_markup=markup, + parse_mode=ParseMode.HTML, + ) @profile_router.callback_query(F.data.startswith("top_cards_")) async def cards_top_callback(callback: types.CallbackQuery): - parts = callback.data.split('_') + parts = callback.data.split("_") choice = parts[2] unique_id = str(parts[-1]) - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(random.choice(responses), show_alert=True) return @@ -379,14 +476,18 @@ async def cards_top_callback(callback: types.CallbackQuery): message_text = "🃏 Топ 10 игроков по картам за сезон\n\n" for top_user in top: - message_text += f"{top_user[0]}. {top_user[1]} {top_user[2]}: {top_user[3]} карточек" + message_text += ( + f"{top_user[0]}. {top_user[1]} {top_user[2]}: {top_user[3]} карточек" + ) if user_id == 6184515646: message_text += f" (user_id: {top_user[4]})" message_text += "\n" if user_rank and user_rank > 10: - message_text += (f"\n🎖️ Ваше место • {user_rank}" - f" ({user.nickname}: {len(user.cards)} карточек)") + message_text += ( + f"\n🎖️ Ваше место • {user_rank}" + f" ({user.nickname}: {len(user.cards)} карточек)" + ) markup = await top_kb(callback, "cards") @@ -396,10 +497,14 @@ async def cards_top_callback(callback: types.CallbackQuery): message_text = "🍀 Топ 10 игроков по очкам за этот сезон\n\n" for top_user in top: - message_text += f"{top_user[0]}. {top_user[1]} {top_user[2]}: {top_user[3]} очков\n" + message_text += ( + f"{top_user[0]}. {top_user[1]} {top_user[2]}: {top_user[3]} очков\n" + ) if user_rank and user_rank > 10: - message_text += (f"\n🎖️ Ваше место • {user_rank} " - f"({user.nickname}: {user.points} очков)") + message_text += ( + f"\n🎖️ Ваше место • {user_rank} " + f"({user.nickname}: {user.points} очков)" + ) markup = await top_kb(callback, "point") @@ -409,11 +514,15 @@ async def cards_top_callback(callback: types.CallbackQuery): message_text = "🌎 Топ 10 игроков по очкам за всё время\n\n" for top_user in top: - message_text += f"{top_user[0]}. {top_user[1]} {top_user[2]}: {top_user[3]} очков\n" + message_text += ( + f"{top_user[0]}. {top_user[1]} {top_user[2]}: {top_user[3]} очков\n" + ) if user_rank and user_rank > 10: - message_text += (f"\n🎖️ Ваше место • {user_rank} " - f"({user.nickname}: {user.all_points} очков)") + message_text += ( + f"\n🎖️ Ваше место • {user_rank} " + f"({user.nickname}: {user.all_points} очков)" + ) markup = await top_kb(callback, "all") else: @@ -422,14 +531,20 @@ async def cards_top_callback(callback: types.CallbackQuery): if not message_text: message_text = "Не удалось получить данные. Попробуйте позже." - await bot.edit_message_text(chat_id=callback.message.chat.id, message_id=callback.message.message_id, - text=message_text, reply_markup=markup) + await bot.edit_message_text( + chat_id=callback.message.chat.id, + message_id=callback.message.message_id, + text=message_text, + reply_markup=markup, + ) @profile_router.callback_query(F.data.startswith("premium_callback")) async def handler_premium(callback: types.CallbackQuery): - unique_id = callback.data.split('_')[-1] - if unique_id not in user_button or user_button[unique_id] != str(callback.from_user.id): + unique_id = callback.data.split("_")[-1] + if unique_id not in user_button or user_button[unique_id] != str( + callback.from_user.id + ): await callback.answer(random.choice(responses), show_alert=True) return @@ -438,8 +553,11 @@ async def handler_premium(callback: types.CallbackQuery): if callback.message.chat.type != "private": await callback.message.answer( f"{str(callback.from_user.first_name)}, " - f"информация о способах оплаты отправлена вам в личные сообщения.") + f"информация о способах оплаты отправлена вам в личные сообщения." + ) except Exception as e: print(e) - await callback.answer("Пожалуйста, напишите боту что-то в личные сообщения, чтобы я смог отправить информацию.", - show_alert=True) + await callback.answer( + "Пожалуйста, напишите боту что-то в личные сообщения, чтобы я смог отправить информацию.", + show_alert=True, + ) diff --git a/handlers/shop.py b/handlers/shop.py index 926b47a..32c5a37 100644 --- a/handlers/shop.py +++ b/handlers/shop.py @@ -1,29 +1,60 @@ import asyncio from aiogram import Router, F from aiogram.filters import Command, CommandObject -from aiogram.types import Message, CallbackQuery, LabeledPrice, PreCheckoutQuery, InlineKeyboardButton +from aiogram.types import ( + Message, + CallbackQuery, + LabeledPrice, + PreCheckoutQuery, + InlineKeyboardButton, +) from aiogram.utils.keyboard import InlineKeyboardBuilder from aiogram.exceptions import TelegramBadRequest -from data.text import shop_text, booster_text, coins_text, confirmation_payment_text, luck_message, buy_luck_message, time_booster, dice_game, dice_limit -from utils.kb import shop_keyboard, boost_keyboard, coins_keyboard, payment_keyboard, payment_boost_keyboard -from database.user import add_coins, add_dice_get, get_coins, set_luck, get_luck, get_user, set_last_get, check_last_get +from data.text import ( + shop_text, + booster_text, + coins_text, + confirmation_payment_text, + luck_message, + buy_luck_message, + time_booster, + dice_game, + dice_limit, +) +from utils.kb import ( + shop_keyboard, + boost_keyboard, + coins_keyboard, + payment_keyboard, + payment_boost_keyboard, +) +from database.user import ( + add_coins, + add_dice_get, + get_coins, + set_luck, + get_luck, + get_user, + set_last_get, + check_last_get, +) from datetime import datetime, timedelta from utils.loader import bot shop_router = Router() + + @shop_router.message(Command("shop")) async def shop(msg: Message): if msg.chat.type in ["group", "supergroup", "channel"]: await msg.reply("❗️ Данная команда работает только в ЛС") return await msg.answer( - text=shop_text, - reply_markup=await shop_keyboard(), - parse_mode="HTML" + text=shop_text, reply_markup=await shop_keyboard(), parse_mode="HTML" ) - + @shop_router.callback_query(F.data.startswith("shop:")) async def shop_callback(callback: CallbackQuery): @@ -31,21 +62,15 @@ async def shop_callback(callback: CallbackQuery): data = callback.data.split(":") if action == "boost": await callback.message.edit_text( - text=booster_text, - reply_markup=await boost_keyboard(), - parse_mode="HTML" + text=booster_text, reply_markup=await boost_keyboard(), parse_mode="HTML" ) elif action == "coins": await callback.message.edit_text( - text=coins_text, - reply_markup=await coins_keyboard(), - parse_mode="HTML" + text=coins_text, reply_markup=await coins_keyboard(), parse_mode="HTML" ) elif action == "back": await callback.message.edit_text( - text=shop_text, - reply_markup=await shop_keyboard(), - parse_mode="HTML" + text=shop_text, reply_markup=await shop_keyboard(), parse_mode="HTML" ) elif action == "buy": quantity = int(data[2]) @@ -57,10 +82,11 @@ async def shop_callback(callback: CallbackQuery): prices=prices, provider_token="", payload=f"coins:{quantity}", - currency="XTR", + currency="XTR", reply_markup=await payment_keyboard(cost), ) + @shop_router.callback_query(F.data.startswith("boost:")) async def boost_callback(callback: CallbackQuery): action = callback.data.split(":")[1] @@ -68,13 +94,13 @@ async def boost_callback(callback: CallbackQuery): await callback.message.edit_text( text=luck_message, reply_markup=await payment_boost_keyboard(4, callback.message, "luck"), - parse_mode="HTML" + parse_mode="HTML", ) elif action == "time": await callback.message.edit_text( text=time_booster, reply_markup=await payment_boost_keyboard(3, callback.message, "time"), - parse_mode="HTML" + parse_mode="HTML", ) if action == "buy": to_buy = callback.data.split(":")[2] @@ -97,9 +123,9 @@ async def boost_callback(callback: CallbackQuery): return await add_coins(callback.from_user.id, -15) - + user = await get_user(callback.from_user.id) - + if user and user.last_usage: can_get_now = await check_last_get(user.last_usage, is_premium=False) if can_get_now: @@ -108,41 +134,55 @@ async def boost_callback(callback: CallbackQuery): else: new_time = user.last_usage - timedelta(hours=1) await set_last_get(callback.from_user.id, new_time) - await callback.message.answer("Вы получили бустер на 1 час! Время ожидания сокращено.") + await callback.message.answer( + "Вы получили бустер на 1 час! Время ожидания сокращено." + ) else: await callback.answer("Вы уже можете получить карточку!") + @shop_router.pre_checkout_query() async def on_pre_checkout_query( pre_checkout_query: PreCheckoutQuery, ): await pre_checkout_query.answer(ok=True) -@shop_router.message(F.successful_payment, lambda message: message.successful_payment.invoice_payload.startswith("coins")) + +@shop_router.message( + F.successful_payment, + lambda message: message.successful_payment.invoice_payload.startswith("coins"), +) async def handle_coins_payment(message: Message): successful_payment = message.successful_payment invoice_payload = successful_payment.invoice_payload - + try: quantity_str = invoice_payload.split(":")[1] quantity = int(quantity_str) except Exception as e: - await message.answer("Ошибка обработки платежа: неверный формат payload. Обратитесь в поддержку") + await message.answer( + "Ошибка обработки платежа: неверный формат payload. Обратитесь в поддержку" + ) return - result = await add_coins(message.from_user.id, quantity, username=message.from_user.username, in_pm=True) - + result = await add_coins( + message.from_user.id, quantity, username=message.from_user.username, in_pm=True + ) + if result: builder = InlineKeyboardBuilder().add( - InlineKeyboardButton( - text="🔙 Назад", - callback_data="shop:back" - )) - confirmation_text = confirmation_payment_text.replace("{quantity}", str(quantity)) - await message.answer(text=confirmation_text, parse_mode="HTML", reply_markup=builder.as_markup()) + InlineKeyboardButton(text="🔙 Назад", callback_data="shop:back") + ) + confirmation_text = confirmation_payment_text.replace( + "{quantity}", str(quantity) + ) + await message.answer( + text=confirmation_text, parse_mode="HTML", reply_markup=builder.as_markup() + ) else: await message.answer("Ошибка при добавлении монет. Попробуйте повторить позже.") + @shop_router.message(Command("diceplay")) async def diceplay(msg: Message): user = await get_user(msg.from_user.id) @@ -150,8 +190,12 @@ async def diceplay(msg: Message): time_since_last_throw = datetime.now() - user.last_dice_play cooldown = timedelta(minutes=7) if time_since_last_throw < cooldown: - remaining_time = (cooldown - time_since_last_throw).total_seconds() // 60 # Учитываем всё время - await msg.reply(text=dice_limit.format(int(remaining_time)), parse_mode="HTML") + remaining_time = ( + cooldown - time_since_last_throw + ).total_seconds() // 60 # Учитываем всё время + await msg.reply( + text=dice_limit.format(int(remaining_time)), parse_mode="HTML" + ) return dice = await msg.bot.send_dice( chat_id=msg.chat.id, @@ -160,14 +204,11 @@ async def diceplay(msg: Message): dice_value = dice.dice.value await asyncio.sleep(3) await add_coins( - msg.from_user.id, - dice_value, - msg.from_user.username, - in_pm=(msg.chat.type == "private") + msg.from_user.id, + dice_value, + msg.from_user.username, + in_pm=(msg.chat.type == "private"), ) await add_dice_get(msg.from_user.id) - await msg.reply( - text=dice_game.format(dice_value, dice_value), - parse_mode="HTML" - ) + await msg.reply(text=dice_game.format(dice_value, dice_value), parse_mode="HTML") diff --git a/handlers/shopcards.py b/handlers/shopcards.py index b8f4c5f..8e6ada6 100644 --- a/handlers/shopcards.py +++ b/handlers/shopcards.py @@ -5,69 +5,78 @@ from aiogram.enums import ParseMode from database.cards import get_all_lcards, get_lcard, increment_buy_count -from database.user import add_coins, add_limited_card_to_user, check_user_has_limited_card, get_coins +from database.user import ( + add_coins, + add_limited_card_to_user, + check_user_has_limited_card, + get_coins, +) shop_cards_router = Router() + async def get_card_text(card): - text = ( - f"🎴 {card.name}\n\n" - f"💎 Цена: {card.price:,} монет\n" - f"📦 Тираж: {card.buy_count}/{card.edition}\n" - ) - if card.description: - text += f"\nℹ️ Описание: {card.description}" - - if card.buy_count >= card.edition: - text += "\n\n❌ ТОВАР РАСПРОДАН" - return text + text = ( + f"🎴 {card.name}\n\n" + f"💎 Цена: {card.price:,} монет\n" + f"📦 Тираж: {card.buy_count}/{card.edition}\n" + ) + if card.description: + text += f"\nℹ️ Описание: {card.description}" + + if card.buy_count >= card.edition: + text += "\n\n❌ ТОВАР РАСПРОДАН" + return text + def make_card_keyboard(card_id: int, total_cards: int): builder = InlineKeyboardBuilder() - + nav_buttons = [] if card_id > 1: - nav_buttons.append(types.InlineKeyboardButton(text="⬅️", callback_data=f"market:prev:{card_id}")) + nav_buttons.append( + types.InlineKeyboardButton(text="⬅️", callback_data=f"market:prev:{card_id}") + ) if card_id < total_cards: - nav_buttons.append(types.InlineKeyboardButton(text="➡️", callback_data=f"market:next:{card_id}")) + nav_buttons.append( + types.InlineKeyboardButton(text="➡️", callback_data=f"market:next:{card_id}") + ) if nav_buttons: builder.row(*nav_buttons) - - builder.row(types.InlineKeyboardButton(text="Купить 💎", callback_data=f"market:buy:{card_id}")) - + + builder.row( + types.InlineKeyboardButton( + text="Купить 💎", callback_data=f"market:buy:{card_id}" + ) + ) + return builder.as_markup() async def sort_cards_by_availability(cards): - available = [] - sold_out = [] - for card in cards: - if card.buy_count >= card.edition: - sold_out.append(card) - else: - available.append(card) - return available + sold_out + available = [] + sold_out = [] + for card in cards: + if card.buy_count >= card.edition: + sold_out.append(card) + else: + available.append(card) + return available + sold_out @shop_cards_router.message(Command("market")) async def show_market_menu(message: types.Message): - if message.chat.type != "private": - await message.answer( - "❌ Магазин доступен только в личных сообщениях!" - ) - return - - builder = InlineKeyboardBuilder() - builder.row(types.InlineKeyboardButton( - text="ОТКРЫТЬ", - callback_data="market:open" - )) - - await message.answer( - "🏪 Биржа лимитированных карточек", - reply_markup=builder.as_markup() - ) - + if message.chat.type != "private": + await message.answer("❌ Магазин доступен только в личных сообщениях!") + return + + builder = InlineKeyboardBuilder() + builder.row(types.InlineKeyboardButton(text="ОТКРЫТЬ", callback_data="market:open")) + + await message.answer( + "🏪 Биржа лимитированных карточек", reply_markup=builder.as_markup() + ) + async def get_available_cards(): all_cards = await get_all_lcards() @@ -76,90 +85,103 @@ async def get_available_cards(): @shop_cards_router.callback_query(F.data == "market:open") async def open_market(callback: types.CallbackQuery): - available_cards = await get_available_cards() - if not available_cards: - await callback.message.edit_text("В магазине нет доступных карточек") - return - - first_card = available_cards[0] - keyboard = make_card_keyboard(1, len(available_cards)) - - await callback.message.delete() - await callback.message.answer_photo( - photo=first_card.photo, - caption=await get_card_text(first_card), - reply_markup=keyboard, - parse_mode=ParseMode.HTML - ) - await callback.answer() + available_cards = await get_available_cards() + if not available_cards: + await callback.message.edit_text("В магазине нет доступных карточек") + return + + first_card = available_cards[0] + keyboard = make_card_keyboard(1, len(available_cards)) + + await callback.message.delete() + await callback.message.answer_photo( + photo=first_card.photo, + caption=await get_card_text(first_card), + reply_markup=keyboard, + parse_mode=ParseMode.HTML, + ) + await callback.answer() + @shop_cards_router.callback_query(F.data.startswith("market:")) async def process_shop_callbacks(callback: types.CallbackQuery): action, current_pos = callback.data.split(":")[1:] current_pos = int(current_pos) - available_cards = [card for card in await get_all_lcards() if card.buy_count < card.edition] - + available_cards = [ + card for card in await get_all_lcards() if card.buy_count < card.edition + ] + if action in ["prev", "next"]: new_pos = current_pos - 1 if action == "prev" else current_pos + 1 card = available_cards[new_pos - 1] - + keyboard = make_card_keyboard(new_pos, len(available_cards)) - + await callback.message.edit_media( types.InputMediaPhoto( media=card.photo, caption=await get_card_text(card), - parse_mode=ParseMode.HTML + parse_mode=ParseMode.HTML, ), - reply_markup=keyboard + reply_markup=keyboard, ) - + elif action == "buy": card = available_cards[current_pos - 1] - + if await check_user_has_limited_card(callback.from_user.id, card.id): await callback.answer("У вас уже есть эта карточка!", show_alert=True) return - + user_coins = await get_coins(callback.from_user.id) if user_coins < card.price: - await callback.answer(f"Недостаточно монет! Нужно: {card.price:,}", show_alert=True) + await callback.answer( + f"Недостаточно монет! Нужно: {card.price:,}", show_alert=True + ) return - + builder = InlineKeyboardBuilder() builder.row( - types.InlineKeyboardButton(text="Подтвердить ✅", callback_data=f"market:confirm:{card.id}"), - types.InlineKeyboardButton(text="Отменить ❌", callback_data=f"market:cancel:{current_pos}") + types.InlineKeyboardButton( + text="Подтвердить ✅", callback_data=f"market:confirm:{card.id}" + ), + types.InlineKeyboardButton( + text="Отменить ❌", callback_data=f"market:cancel:{current_pos}" + ), ) - + await callback.message.answer( f"Купить карточку {card.name} за {card.price:,} монет?\nУ вас: {user_coins:,} монет", reply_markup=builder.as_markup(), - parse_mode=ParseMode.HTML + parse_mode=ParseMode.HTML, ) - + elif action == "confirm": card = await get_lcard(int(current_pos)) - + if await check_user_has_limited_card(callback.from_user.id, card.id): await callback.answer("У вас уже есть эта карточка!", show_alert=True) return - + user_coins = await get_coins(callback.from_user.id) if user_coins < card.price: - await callback.answer(f"Недостаточно монет! Нужно: {card.price:,}", show_alert=True) + await callback.answer( + f"Недостаточно монет! Нужно: {card.price:,}", show_alert=True + ) return - - await add_coins(callback.from_user.id, -card.price, callback.from_user.username, True) + + await add_coins( + callback.from_user.id, -card.price, callback.from_user.username, True + ) await increment_buy_count(card.id) await add_limited_card_to_user(callback.from_user.id, card.id) - + await callback.message.edit_text( f"Вы приобрели карточку {card.name}!\nПотрачено: {card.price:,} монет", - parse_mode=ParseMode.HTML + parse_mode=ParseMode.HTML, ) - + elif action == "cancel": await callback.message.delete() - - await callback.answer() \ No newline at end of file + + await callback.answer() diff --git a/handlers/triggers.py b/handlers/triggers.py index 6104869..9f15494 100644 --- a/handlers/triggers.py +++ b/handlers/triggers.py @@ -23,12 +23,25 @@ from aiogram.enums.parse_mode import ParseMode from aiogram.utils.text_decorations import markdown_decoration -sys.path.append(os.path.realpath('.')) +sys.path.append(os.path.realpath(".")) from aiogram.filters import IS_MEMBER, IS_NOT_MEMBER from database.cards import get_all_cards from database.models import Card -from database.user import add_card, add_coins, add_points, change_username, check_last_get, get_coins, get_luck, get_user, \ - in_pm_change, set_luck, update_last_get, is_nickname_taken, IsAlreadyResetException +from database.user import ( + add_card, + add_coins, + add_points, + change_username, + check_last_get, + get_coins, + get_luck, + get_user, + in_pm_change, + set_luck, + update_last_get, + is_nickname_taken, + IsAlreadyResetException, +) from database.premium import check_premium from middlewares import RegisterMiddleware from filters.FloodWait import RateLimitFilter @@ -51,11 +64,11 @@ async def komaru_cards_function(msg: Message, dialog_manager: DialogManager): hours = 3 if is_premium else 4 next_available = user.last_usage + timedelta(hours=hours) remaining = next_available - now - + hours_left = int(remaining.total_seconds() // 3600) minutes_left = int((remaining.total_seconds() % 3600) // 60) seconds_left = int(remaining.total_seconds() % 60) - + time_parts = [] if hours_left > 0: time_parts.append(f"{hours_left} часов") @@ -64,21 +77,25 @@ async def komaru_cards_function(msg: Message, dialog_manager: DialogManager): if seconds_left > 0: time_parts.append(f"{seconds_left} секунд") time_string = " ".join(time_parts) - + await msg.reply( "Осмотревшись, вы не нашли Карту поблизости 🤷‍♂️\n" - f"⏳ Подождите {time_string}, чтобы попробовать снова", - parse_mode=ParseMode.HTML + f"⏳ Подождите {time_string}, чтобы попробовать снова", + parse_mode=ParseMode.HTML, ) return chosen_cat: Card = await random_cat(is_premium, user_id) - description_text = f"\n📜 Описание: {chosen_cat.description}" if chosen_cat.description else "" + description_text = ( + f"\n📜 Описание: {chosen_cat.description}" if chosen_cat.description else "" + ) if user.check_bonus_available(): bonus_message = ( "🎁 Получай карточку раз в 4 часа подписавшись на каналы спонсоров" ) - markup = await get_bonus_keyboard((await msg.bot.get_me()).username, msg.from_user.id) + markup = await get_bonus_keyboard( + (await msg.bot.get_me()).username, msg.from_user.id + ) else: bonus_message = "" markup = None @@ -90,16 +107,21 @@ async def komaru_cards_function(msg: Message, dialog_manager: DialogManager): msg.chat.id, photo=chosen_cat.photo, caption=f"🌟 Карточка «{chosen_cat.name}» уже есть у вас" - f"\n\n💎 Редкость: {chosen_cat.rarity}\n " - f"✨ Очки: +{chosen_cat.points} [{user.points + int(chosen_cat.points)}]\n" - f"💰 Монеты • +{coins} [{coins_db + coins}]\n" - f"{description_text}\n\n" - f"{bonus_message}", + f"\n\n💎 Редкость: {chosen_cat.rarity}\n " + f"✨ Очки: +{chosen_cat.points} [{user.points + int(chosen_cat.points)}]\n" + f"💰 Монеты • +{coins} [{coins_db + coins}]\n" + f"{description_text}\n\n" + f"{bonus_message}", reply_to_message_id=msg.message_id, parse_mode=ParseMode.HTML, - reply_markup=markup + reply_markup=markup, + ) + await add_coins( + user.telegram_id, + int(coins), + username=msg.from_user.username, + in_pm=(msg.chat.type == "private"), ) - await add_coins(user.telegram_id, int(coins), username=msg.from_user.username, in_pm=(msg.chat.type == "private")) else: coins = random.randint(5, 8) coins_db = await get_coins(user.telegram_id) @@ -107,16 +129,21 @@ async def komaru_cards_function(msg: Message, dialog_manager: DialogManager): msg.chat.id, photo=chosen_cat.photo, caption=f"👻 Успех! Карточка «{chosen_cat.name}»" - f"\n\n💎 Редкость: {chosen_cat.rarity}\n " - f"✨ Очки: +{chosen_cat.points} [{user.points + int(chosen_cat.points)}]\n" - f"💰 Монеты • +{coins} [{coins_db + coins}]\n" - f"{description_text}\n" - f"{bonus_message}", + f"\n\n💎 Редкость: {chosen_cat.rarity}\n " + f"✨ Очки: +{chosen_cat.points} [{user.points + int(chosen_cat.points)}]\n" + f"💰 Монеты • +{coins} [{coins_db + coins}]\n" + f"{description_text}\n" + f"{bonus_message}", reply_to_message_id=msg.message_id, parse_mode=ParseMode.HTML, - reply_markup=markup + reply_markup=markup, + ) + await add_coins( + user.telegram_id, + int(coins), + username=msg.from_user.username, + in_pm=(msg.chat.type == "private"), ) - await add_coins(user.telegram_id, int(coins), username=msg.from_user.username, in_pm=(msg.chat.type == "private")) await add_card(user.telegram_id, chosen_cat.id) await update_last_get(user.telegram_id) @@ -131,36 +158,48 @@ async def change_nickname(message: types.Message, dialog_manager: DialogManager) first_name = message.from_user.first_name premium_status = await check_premium(user.premium_expire) - if message.text.startswith('/name'): + if message.text.startswith("/name"): command_parts = message.text.split(maxsplit=1) - if len(command_parts) == 1: + if len(command_parts) == 1: await message.reply("Укажите новый никнейм после команды /name.") return new_nick = command_parts[1].strip() else: - parts = message.text.casefold().split('сменить ник'.casefold(), 1) + parts = message.text.casefold().split("сменить ник".casefold(), 1) if len(parts) > 1 and parts[1].strip(): new_nick = parts[1].strip() else: - await message.reply("Никнейм не может быть пустым. Укажите значение после команды.") + await message.reply( + "Никнейм не может быть пустым. Укажите значение после команды." + ) return if 5 > len(new_nick) or len(new_nick) > 32: - await message.reply("Никнейм не должен быть короче 5 символов и длиннее 32 символов.") + await message.reply( + "Никнейм не должен быть короче 5 символов и длиннее 32 символов." + ) return if any(emoji.is_emoji(char) for char in new_nick): if not premium_status: - await message.reply("Вы не можете использовать эмодзи в нике. Приобретите премиум в профиле!") + await message.reply( + "Вы не можете использовать эмодзи в нике. Приобретите премиум в профиле!" + ) return else: - if not re.match(r'^[\w .,!?#$%^&*()-+=/\]+$|^[\w .,!?#$%^&*()-+=/а-яёА-ЯЁ]+$', new_nick): - await message.reply("Никнейм может содержать только латинские/русские буквы, " - "цифры и базовые символы пунктуации.") + if not re.match( + r"^[\w .,!?#$%^&*()-+=/\]+$|^[\w .,!?#$%^&*()-+=/а-яёА-ЯЁ]+$", new_nick + ): + await message.reply( + "Никнейм может содержать только латинские/русские буквы, " + "цифры и базовые символы пунктуации." + ) return - if '@' in new_nick or validators.url(new_nick) or 't.me' in new_nick: - await message.reply("Никнейм не может содержать символ '@', ссылки или упоминания t.me.") + if "@" in new_nick or validators.url(new_nick) or "t.me" in new_nick: + await message.reply( + "Никнейм не может содержать символ '@', ссылки или упоминания t.me." + ) return if await is_nickname_taken(new_nick): @@ -172,13 +211,13 @@ async def change_nickname(message: types.Message, dialog_manager: DialogManager) except sqlalchemy.exc.IntegrityError as e: await message.reply("Никнейм занят") return - + await message.reply(f"Ваш никнейм был изменён на {new_nick}") @text_triggers_router.message(F.text.casefold().startswith("промо".casefold())) async def activate_promo(message: types.Message, dialog_manager: DialogManager): - promocode = message.text.casefold().split('промо'.casefold(), 1)[1].strip() + promocode = message.text.casefold().split("промо".casefold(), 1)[1].strip() promo = await get_promo(promocode) if promo is None: await message.answer("Промокод не найден") @@ -190,14 +229,23 @@ async def activate_promo(message: types.Message, dialog_manager: DialogManager): await message.answer("Промокод истек.") return try: - channel_member = await message.bot.get_chat_member(promo.channel_id, message.from_user.id) + channel_member = await message.bot.get_chat_member( + promo.channel_id, message.from_user.id + ) except Exception: await message.answer("Возникла ошибки при проверке подписки на канал спонсора") - if channel_member.status not in ["creator", "administrator", "member", "restricted"]: - await message.answer("Вы не подписаны на канал спонсора, подпишитесь", - reply_markup=InlineKeyboardBuilder( - InlineKeyboardButton(text="Канал спонсора", url=promo.link) - ).as_markup()) + if channel_member.status not in [ + "creator", + "administrator", + "member", + "restricted", + ]: + await message.answer( + "Вы не подписаны на канал спонсора, подпишитесь", + reply_markup=InlineKeyboardBuilder( + InlineKeyboardButton(text="Канал спонсора", url=promo.link) + ).as_markup(), + ) return user = await get_user(message.from_user.id) if user.check_promo_expired(promocode): @@ -207,10 +255,14 @@ async def activate_promo(message: types.Message, dialog_manager: DialogManager): await promo_use(user.telegram_id, promo) await message.answer("Промокод активирован успешно") except IsAlreadyResetException: - await message.answer("Таймер уже на нуле, заберите карточку, а затем активируйте промокод.") + await message.answer( + "Таймер уже на нуле, заберите карточку, а затем активируйте промокод." + ) -@text_triggers_router.my_chat_member(ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER)) +@text_triggers_router.my_chat_member( + ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER) +) async def on_bot_added(update: ChatMemberUpdated): if update.chat.type == "private": await in_pm_change(update.from_user.id, True) @@ -229,7 +281,9 @@ async def on_bot_added(update: ChatMemberUpdated): ) -@text_triggers_router.my_chat_member(ChatMemberUpdatedFilter(IS_MEMBER >> IS_NOT_MEMBER)) +@text_triggers_router.my_chat_member( + ChatMemberUpdatedFilter(IS_MEMBER >> IS_NOT_MEMBER) +) async def on_bot_deleted(update: ChatMemberUpdated): if update.chat.type == "private": await in_pm_change(update.from_user.id, False) @@ -291,8 +345,8 @@ async def random_cat(isPro: bool, user_id: int): rare_cats = [cat for cat in cats if cat.rarity == "Редкая"] if rare_cats: return random.choice(rare_cats) - - return 'чиво' + + return "чиво" @text_triggers_router.message(Command("settings")) @@ -303,10 +357,13 @@ async def settings(msg: types.Message, dialog_manager: DialogManager): builder = InlineKeyboardBuilder().add( InlineKeyboardButton( text=f"{status_text} Авто комментарии.", - callback_data=f"settings:toogle:{msg.from_user.id}" + callback_data=f"settings:toogle:{msg.from_user.id}", ), ) - await msg.answer(settings_chat, reply_markup=builder.as_markup(), parse_mode=ParseMode.HTML) + await msg.answer( + settings_chat, reply_markup=builder.as_markup(), parse_mode=ParseMode.HTML + ) + @text_triggers_router.callback_query(F.data.startswith("settings:")) async def settings_callback(callback: types.CallbackQuery): @@ -317,24 +374,24 @@ async def settings_callback(callback: types.CallbackQuery): settings = await get_group(callback.message.chat.id) if settings: - + new_status = not settings.comments_on await set_comments_active(callback.message.chat.id, new_status) updated_settings = await get_group(callback.message.chat.id) - status_text = "✅" if updated_settings.comments_on else "❌" - + status_text = "✅" if updated_settings.comments_on else "❌" + builder = InlineKeyboardBuilder().add( InlineKeyboardButton( text=f"{status_text} Авто комментарии.", - callback_data=f"settings:toogle:{callback.from_user.id}" + callback_data=f"settings:toogle:{callback.from_user.id}", ), ) - + try: await callback.message.edit_text( settings_chat, reply_markup=builder.as_markup(), - parse_mode=ParseMode.HTML + parse_mode=ParseMode.HTML, ) except aiogram.exceptions.TelegramBadRequest as e: if "message is not modified" in str(e): @@ -345,4 +402,4 @@ async def settings_callback(callback: types.CallbackQuery): @text_triggers_router.message() async def on_any_message(msg: types.Message): - pass \ No newline at end of file + pass diff --git a/utils/config.py b/utils/config.py index df351db..2cbe549 100644 --- a/utils/config.py +++ b/utils/config.py @@ -35,7 +35,7 @@ class App: bot: Bot database: Database -def load_config(path: str = None) -> App: +def load_config(path: str | None = None) -> App: env = Env() env.read_env(path)