diff --git a/.gitignore b/.gitignore index 7351200..ddcb89f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,22 @@ +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### PyCharm ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + # User-specific stuff .idea/**/workspace.xml .idea/**/tasks.xml @@ -276,6 +295,9 @@ poetry.toml # ruff .ruff_cache/ +# LSP config files +pyrightconfig.json + ### venv ### # Virtualenv # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/ diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/READ_INSTRUCTION.txt b/READ_INSTRUCTION.txt index 8fafab8..f49f103 100644 --- a/READ_INSTRUCTION.txt +++ b/READ_INSTRUCTION.txt @@ -4,10 +4,15 @@ 3) Начать игру! - начинается игра 3) Остановить игру! - игра останавливается, выводятся оставшиеся пользователи 4) Последняя игра! - выводится победитель из последней игры, если он есть - +5) Следующий раунд! +6) Исключить name +7) Моя статистика! +8) Статистика! Механизм игры такой: РЕЖИМ: ВСЕ ПРОТИВ ВСЕХ! - все сражаются против друг друга, пока не останется 1 или 0 пользователей. В случае если остался 1 - выводится победитель, в случае, если никто не остался - выводится соответствующее сообщение +ВАЖНО!! Подгружать фотографии можно с сохраненок либо ваших фотографий с телефона. Фотографии со страницы нельзя! + diff --git a/alembic/env.py b/alembic/env.py index 3def7dd..1e737bd 100644 --- a/alembic/env.py +++ b/alembic/env.py @@ -5,7 +5,7 @@ from sqlalchemy import pool from sqlalchemy.engine import Connection from sqlalchemy.ext.asyncio import AsyncEngine -from app.store.models.model import ParticipantsModel +from app.store.models.model import ParticipantsModel, GameModel from alembic import context diff --git a/alembic/versions/1d6d7e9045d3_participants.py b/alembic/versions/1d6d7e9045d3_participants.py deleted file mode 100644 index c61490e..0000000 --- a/alembic/versions/1d6d7e9045d3_participants.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Participants - -Revision ID: 1d6d7e9045d3 -Revises: -Create Date: 2023-02-27 20:20:14.025321 - -""" -from alembic import op -import sqlalchemy as sa - - -# revision identifiers, used by Alembic. -revision = '1d6d7e9045d3' -down_revision = None -branch_labels = None -depends_on = None - - -def upgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.create_table('participants', - sa.Column('id', sa.BigInteger(), nullable=False), - sa.Column('name', sa.Text(), nullable=False), - sa.Column('wins', sa.BigInteger(), nullable=True), - sa.Column('chat_id', sa.BigInteger(), nullable=False), - sa.Column('owner_id', sa.BigInteger(), nullable=True), - sa.Column('photo_id', sa.BigInteger(), nullable=True), - sa.Column('access_key', sa.Text(), nullable=True), - sa.PrimaryKeyConstraint('id') - ) - # ### end Alembic commands ### - - -def downgrade() -> None: - # ### commands auto generated by Alembic - please adjust! ### - op.drop_table('participants') - # ### end Alembic commands ### diff --git a/alembic/versions/e0fb8c912758_add_gamemodel.py b/alembic/versions/e0fb8c912758_add_gamemodel.py new file mode 100644 index 0000000..6838a7e --- /dev/null +++ b/alembic/versions/e0fb8c912758_add_gamemodel.py @@ -0,0 +1,63 @@ +"""Add GameModel + +Revision ID: e0fb8c912758 +Revises: +Create Date: 2023-03-04 19:24:14.714777 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'e0fb8c912758' +down_revision = None +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.create_table('game_session', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('chat_id', sa.BigInteger(), nullable=True), + sa.PrimaryKeyConstraint('id'), + sa.UniqueConstraint('chat_id') + ) + op.create_table('game', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('chat_id', sa.BigInteger(), nullable=True), + sa.Column('users', postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column('state_photo', sa.Boolean(), nullable=True), + sa.Column('state_in_game', sa.Boolean(), nullable=True), + sa.Column('state_wait_votes', sa.Boolean(), nullable=True), + sa.Column('new_pair', postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column('first_votes', sa.BigInteger(), nullable=True), + sa.Column('second_votes', sa.BigInteger(), nullable=True), + sa.Column('state_send_photo', sa.Boolean(), nullable=True), + sa.Column('voters', postgresql.JSONB(astext_type=sa.Text()), nullable=True), + sa.Column('amount_users', sa.BigInteger(), nullable=True), + sa.Column('last_winner', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['chat_id'], ['game_session.chat_id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + op.create_table('participants', + sa.Column('id', sa.BigInteger(), nullable=False), + sa.Column('name', sa.Text(), nullable=False), + sa.Column('wins', sa.BigInteger(), nullable=True), + sa.Column('chat_id', sa.BigInteger(), nullable=True), + sa.Column('owner_id', sa.BigInteger(), nullable=True), + sa.Column('photo_id', sa.BigInteger(), nullable=True), + sa.Column('access_key', sa.Text(), nullable=True), + sa.ForeignKeyConstraint(['chat_id'], ['game_session.chat_id'], ondelete='CASCADE'), + sa.PrimaryKeyConstraint('id') + ) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_table('participants') + op.drop_table('game') + op.drop_table('game_session') + # ### end Alembic commands ### diff --git a/alembic/versions/eb47d650c4bf_add_gamemodel.py b/alembic/versions/eb47d650c4bf_add_gamemodel.py new file mode 100644 index 0000000..02468e9 --- /dev/null +++ b/alembic/versions/eb47d650c4bf_add_gamemodel.py @@ -0,0 +1,28 @@ +"""Add GameModel + +Revision ID: eb47d650c4bf +Revises: e0fb8c912758 +Create Date: 2023-03-07 21:26:04.620744 + +""" +from alembic import op +import sqlalchemy as sa +from sqlalchemy.dialects import postgresql + +# revision identifiers, used by Alembic. +revision = 'eb47d650c4bf' +down_revision = 'e0fb8c912758' +branch_labels = None +depends_on = None + + +def upgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.add_column('game', sa.Column('kicked_users', postgresql.JSONB(astext_type=sa.Text()), nullable=True)) + # ### end Alembic commands ### + + +def downgrade() -> None: + # ### commands auto generated by Alembic - please adjust! ### + op.drop_column('game', 'kicked_users') + # ### end Alembic commands ### diff --git a/app/bot_vk.py b/app/bot_vk.py new file mode 100644 index 0000000..500f60e --- /dev/null +++ b/app/bot_vk.py @@ -0,0 +1,27 @@ +import asyncio + +from app.store.bot.sender import VKSender +from app.store.vk_api.poller import Poller +from app.store.bot.manager import BotManager +from app.store import Store +from app.web.app import app + + +class Bot: + def __init__(self, n): + self.queue = asyncio.Queue() + self.out_queue = asyncio.Queue() + self.store = Store(app) + self.poller = Poller(self.queue, self.store) + self.worker = BotManager(self.queue, self.out_queue, app, n) + self.sender = VKSender(self.out_queue, app) + + async def start(self): + await self.poller.start(app) + await self.worker.start() + await self.sender.start() + + async def stop(self): + await self.poller.stop() + await self.worker.stop() + await self.sender.stop() diff --git a/app/store/__init__.py b/app/store/__init__.py index 97b1fb3..5bfe100 100644 --- a/app/store/__init__.py +++ b/app/store/__init__.py @@ -1,3 +1,4 @@ +import asyncio import typing from app.store.database.database import Database @@ -8,15 +9,17 @@ class Store: def __init__(self, app: "Application"): - from app.store.bot.manager import BotManager from app.store.vk_api.accessor import VkApiAccessor + from app.store.bot.manager import BotManager + from app.store.bot.sender import VKSender + self.queue = asyncio.Queue() + self.out_queue = asyncio.Queue() self.vk_api = VkApiAccessor(app) - self.bots_manager = BotManager(app) + self.bot_manager = BotManager(self.queue, self.out_queue, app, 1) + self.vk_sender = VKSender(self.out_queue, app) def setup_store(app: "Application"): app.database = Database(app) - app.on_startup.append(app.database.connect) - app.on_cleanup.append(app.database.disconnect) app.store = Store(app) diff --git a/app/store/bot/admins_id.txt b/app/store/bot/admins_id.txt new file mode 100644 index 0000000..d1058f0 --- /dev/null +++ b/app/store/bot/admins_id.txt @@ -0,0 +1 @@ +582423336 \ No newline at end of file diff --git a/app/store/bot/bot_runner.py b/app/store/bot/bot_runner.py new file mode 100644 index 0000000..fea8477 --- /dev/null +++ b/app/store/bot/bot_runner.py @@ -0,0 +1,18 @@ +import asyncio +import datetime + + +from app.bot_vk import Bot + + +def run(): + loop = asyncio.get_event_loop() + bot = Bot(3) + try: + print("Bot has been started") + loop.create_task(bot.start()) + loop.run_forever() + except KeyboardInterrupt: + print("\nstopping", datetime.datetime.now()) + loop.run_until_complete(bot.stop()) + print("Bot has been stopped", datetime.datetime.now()) diff --git a/app/store/bot/dataclassess.py b/app/store/bot/dataclassess.py deleted file mode 100644 index 61f6abd..0000000 --- a/app/store/bot/dataclassess.py +++ /dev/null @@ -1,20 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class UpdateObject: - id: int - user_id: int - body: str - - -@dataclass -class Update: - type: str - object: UpdateObject - - -@dataclass -class Message: - user_id: int - text: str diff --git a/app/store/bot/lexicon.py b/app/store/bot/lexicon.py new file mode 100644 index 0000000..8d3f2dc --- /dev/null +++ b/app/store/bot/lexicon.py @@ -0,0 +1,46 @@ +commands_for_users = { + "«Регистрация!»": "регистрирует всех участников игры.", + "«Загрузить фотографии!»": "после написания данной команды, скидываете фотографию, которая будет участвовать в конкурсе.", + "«Начать игру!»": "запускает игровой процесс.", + "«Остановить игру!»": "останавливает игровую сессию и выводит всех оставшихся участников.", + "«Моя статистика!»": "выводит персональную статистику человека.", + "«Последняя игра!»": "выводит информацию о последней игре.", +} + +commands_for_admins = { + "«Стастика!»": "выводит статистику всех игроков.", + "«Замолчать!»": "запрещает выбранному участнику говорить.", + "«Говорить!»": "убирает у выбранного участника блокировку чата.", + "«Выгнать!»": "удаляет выбранного участника из чата.", +} + + +lexicon_for_messages = { + "DUR_GAME": "Данная команда недоступна во время игры!", + "NO_REG": "Вы не прошли регистрацию!", + "SUCC_REG": "Регистрация прошла успешно!", + "WINNER": "Победил", + "NO_WINNERS": "Никто не победил!", + "STATISTIC_PLAYER": "Статистика игрока", + "AMOUNT_WINS": "Кол-во побед", + "LAST_WINNER": "Последний победитель", + "NO_LAST_WINNER": "Игр еще не было или они закончились ничьей!", + "WELCOME_PHRASE": "Здравствуйте, я бот-викторина. Пожалуйста, для игры сделайте меня администратором!", + "SUCC_PHOTO": "Фотографии успешно загружены!", + "LITTLE_PEOPLE": "Необходимо минимум два человека!", + "START_GAME": "Игра начинается через", + "LETS_GO": "Поехали!", + "NOT_ENOUGH_PHOTO": "Не все пользователи загрузили фотографии!", + "GAME_GO": "Игра уже идет!", + "CHOOSE": "Выбирай!", + "ALR_VOTED": "Вы уже отдали свой голос!", + "FIRST_WIN": "И в текущем сражении победителем стал обладатель первой картинки!", + "SECOND_WIN": "И в текущем сражении победителем стал обладатель второй картинки!", + "DRAW": "Никто не победил - следовательно оба вылетают.", + "RANDOM_WIN": "Никто не проголосовал, поэтому победитель определяется случайным образом.", + "REMAIN": "Оставшиеся пользователи", + "GAME_STOP": "Игра остановлена!", + "GAME_NO_EXIST": "Игровая сессия не запущена!", + "ADMIN_COMMAND": "Данная команда вам недоступна!", + "USER_KICKED": "Пользователь исключен!", +} diff --git a/app/store/bot/manager.py b/app/store/bot/manager.py index c85b2b2..1d4cdae 100644 --- a/app/store/bot/manager.py +++ b/app/store/bot/manager.py @@ -1,193 +1,607 @@ +import asyncio import typing from logging import getLogger -from time import sleep, time +from time import sleep from random import choice -from sqlalchemy.sql import select, update as refresh -from app.store.bot.services import make_grid, check_winner -from app.store.vk_api.dataclasses import Message, Update, Attachment, UpdateObject +from sqlalchemy import insert +from sqlalchemy.sql import select, update as refresh, delete +from app.store.bot.lexicon import ( + commands_for_users, + commands_for_admins, + lexicon_for_messages, +) +from app.store.bot.services import make_grid, check_winner, check_kicked from app.web.app import app -from app.store.models.model import ParticipantsModel +from app.store.models.model import ParticipantsModel, GameModel, GameSession if typing.TYPE_CHECKING: from app.web.app import Application -class SM: - def __init__(self): - self.state_photo = False - self.state_in_game = False - self.state_wait_votes = False - self.users = None - self.new_pair = None - self.voters_dict = {} - self.voters = [] - self.state_send_photo = False - self.amount_users = None - self.last_winner = None - - def reset_values(self): - self.state_photo = False - self.state_in_game = False - self.state_wait_votes = False - self.users = None - self.new_pair = None - self.voters_dict = {} - self.voters = [] - self.state_send_photo = False - self.amount_users = None - - class BotManager: - def __init__(self, app: "Application"): + def __init__( + self, in_queue, out_queue, app: "Application", concurrent_workers + ): self.app = app self.bot = None self.logger = getLogger("handler") self.active_chats = {} + self._tasks: typing.List[asyncio.Task] = [] + self.in_queue = in_queue + self.out_queue = out_queue + self.concurrent_workerks = concurrent_workers self.time_end = {} self.storage = {} + self.reader = open( + "/home/olred/PycharmProjects/kts_project_template/app/store/bot/admins_id.txt", + "r", + encoding="utf-8", + ) + self.actions = { + "chat_kick_user": self.command_kick, + "chat_invite_user": self.command_invite, + } + self.commands = { + "Регистрация!": self.command_registery, + "Начать игру!": self.command_start_game, + "Остановить игру!": self.command_stop_game, + "Последняя игра!": self.command_last_game, + "Моя статистика!": self.command_my_statistic, + "Команды!": self.command_list_of_commands, + "Следующий раунд!": self.command_next_round, + "Статистика!": self.command_general_statistic, + } - async def handle_updates(self, updates: list[Update]): - for i in self.storage.keys(): - if time() - self.storage[i][0] > 30 and self.storage[i][1]: - updates.append( - Update( - type="time_out", - object=UpdateObject( - chat_id=i, - id=-1, - body="time_out", - ), + async def handle_updates(self, update): + if ( + hasattr(update.object, "member_id") + and update.object.member_id == -207946988 + and update.object.type == "chat_invite_user" + ): + await self.actions.get(update.object.type)(update) + elif ( + update.object.type in self.actions.keys() + and not update.object.type == "chat_invite_user" + ): + await self.actions.get(update.object.type)(update) + game = await self.get_game(update) + users = await self.get_users(update) + self.reader = list(map(int, self.reader)) + if update.object.body in self.commands: + await self.commands[update.object.body](update, game) + if update.object.body == "Загрузить фотографии!" or game["state_photo"]: + if len(users) == 0: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["NO_REG"], ) ) - for update in updates: - if update.object.chat_id not in self.active_chats.keys(): - temp = SM() - self.active_chats[update.object.chat_id] = temp - this_chat = self.active_chats[update.object.chat_id] else: - this_chat = self.active_chats[update.object.chat_id] - if update.object.body == "Регистрация!": - await self.command_registery(update) - if update.object.body == "Загрузить фотографии!" or this_chat.state_photo: - if not this_chat.state_in_game: - this_chat.state_photo = True - await self.command_download_photo(update, this_chat) + if not game["state_in_game"]: + if not game["state_photo"]: + await self.set_state_photo(update, True) + else: + await self.command_download_photo(update) else: - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Нельзя загружать фотографии во время игры!", + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], ) ) - if update.object.body == "Начать игру!": - if not this_chat.state_in_game: - this_chat.reset_values() - await self.command_start_game(update, this_chat) - else: - await self.app.store.vk_api.send_message( - Message(chat_id=update.object.chat_id, text=f"Игра уже идет!") + if ( + len(update.object.body.split()) == 2 + and "Исключить" in update.object.body.split() + ): + await self.command_kick_from_game( + update.object.body.split()[1], update, game + ) + + if game["state_send_photo"]: + await self.command_send_photo(update, game) + if game["state_wait_votes"]: + await self.command_write_answers(update, game) + if game["state_in_game"] and ((not game["state_wait_votes"])): + await self.command_send_preresult(update, game) + if self.check_users(game): + game["state_in_game"] = False + game["state_send_photo"] = False + await self.reset_all_states(update) + if len(game["users"]["participants"]) == 1: + game["last_winner"] = list( + game["users"]["participants"][-1].keys() + )[0] + await self.set_last_winner(update, game["last_winner"]) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + f"{lexicon_for_messages['WINNER']} {game['last_winner']}", + ) ) - if update.object.body == "Остановить игру!": - if this_chat.state_in_game: - await self.command_stop_game(this_chat, update) + game["kicked_users"]["kicked"] = [] + await self.set_kicked(update, game["kicked_users"], game) + await self.new_win(update, game) else: - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Игровая сессия не запущена!", + game["kicked_users"]["kicked"] = [] + await self.set_kicked(update, game["kicked_users"], game) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["NO_WINNERS"], ) ) - if update.object.body == "Последняя игра!": - if not this_chat.state_in_game: - if this_chat.last_winner is not None: - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Последний победитель: {this_chat.last_winner}", - ) - ) - else: - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, text="Игр еще не было!" - ) - ) - else: - await self.app.store.vk_api.send_message( - Message( + elif len(game["users"]["participants"]) > 1: + await self.command_send_photo(update, game) + + @staticmethod + def post_init(game): + game["users"] = {} + game["new_pair"] = {} + game["voters"] = {} + + async def command_registery(self, update, game): + if not game["state_in_game"]: + await self.app.database.connect() + async with self.app.database.session.begin() as session: + result = await app.store.vk_api.make_userlist( + update.object.chat_id, self.app + ) + for k, v in result: + users_exists_select = select( + ParticipantsModel.__table__.c.chat_id, + ParticipantsModel.__table__.c.name, + ).where( + ParticipantsModel.__table__.columns.chat_id + == update.object.chat_id, + ParticipantsModel.__table__.c.name == k, + ) + result = await session.execute(users_exists_select) + if not ((update.object.chat_id, k) in result.fetchall()): + new_user = ParticipantsModel( + name=k, + wins=0, chat_id=update.object.chat_id, - text="Данная команда недоступна во время игры!", + owner_id=v, + photo_id=None, + access_key=None, ) + session.add(new_user) + await session.commit() + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["SUCC_REG"], ) - if this_chat.state_send_photo: - await self.command_send_photo(update, this_chat) - if this_chat.state_wait_votes: - await self.command_write_answers(update, this_chat) - if this_chat.state_in_game and ( - (not this_chat.state_wait_votes) - or time() - self.storage[update.object.chat_id][0] > 30 - ): - await self.command_send_preresult(update, this_chat) - if self.check_users(this_chat): - if len(this_chat.users) == 1: - this_chat.last_winner = this_chat.users[-1][0] - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Победил {this_chat.users[0][0]}!", - ) - ) - else: - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, text=f"Никто не победил!" - ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], + ) + ) + + async def command_next_round(self, update, game): + if game["state_in_game"]: + game["state_wait_votes"] = False + game["voters"]["already_voted"] = [] + await self.set_state_wait_votes(update, game["state_wait_votes"]) + await self.set_voters(update, [], game) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["GAME_NO_EXIST"], + ) + ) + + async def command_list_of_commands(self, update, game): + if not game["state_in_game"]: + if update.object.id in self.reader: + result = "" + for i in commands_for_users.items(): + result += f"{i[0]}: {i[1]}%0A" + for i in commands_for_admins.items(): + result += f"{i[0]}: {i[1]}%0A" + self.out_queue.put_nowait( + ("message", update.object.chat_id, result[:-3]) + ) + else: + result = "" + for i in commands_for_users.items(): + result += f"{i[0]}: {i[1]}%0A" + self.out_queue.put_nowait( + ("message", update.object.chat_id, result[:-3]) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], + ) + ) + + async def command_my_statistic(self, update, game): + if not game["state_in_game"]: + result = await self.get_statistics(update) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + f"{lexicon_for_messages['STATISTIC_PLAYER']} {result[-1][1]}:%0A{lexicon_for_messages['AMOUNT_WINS']}: {result[-1][0]}", + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], + ) + ) + + async def command_general_statistic(self, update, game): + if not game["state_in_game"]: + if update.object.id in self.reader: + result = await self.get_all_statistics(update) + for i in result: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + f"{lexicon_for_messages['STATISTIC_PLAYER']} {i[1]}:%0A{lexicon_for_messages['AMOUNT_WINS']}: {i[0]}", ) - elif len(this_chat.users) > 1: - await self.command_send_photo(update, this_chat) - this_chat.state_send_photo = False + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["ADMIN_COMMAND"], + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], + ) + ) + + async def command_last_game(self, update, game): + if not game["state_in_game"]: + if game["last_winner"] is not None: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + f"{lexicon_for_messages['LAST_WINNER']}: {game['last_winner']}", + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["NO_LAST_WINNER"], + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], + ) + ) - async def command_registery(self, update): + async def get_game(self, update): await self.app.database.connect() async with self.app.database.session.begin() as session: - result = await app.store.vk_api.make_userlist(update.object.chat_id) - for k, v in result: - users_exists_select = select( - ParticipantsModel.__table__.c.chat_id, - ParticipantsModel.__table__.c.name, - ).where( - ParticipantsModel.__table__.columns.chat_id + game_state = select(GameModel.__table__).where( + GameModel.__table__.c.chat_id == update.object.chat_id + ) + temp = await session.execute(game_state) + temp = temp.fetchall() + result = { + "chat_id": temp[0][1], + "users": temp[0][2], + "state_photo": temp[0][3], + "state_in_game": temp[0][4], + "state_wait_votes": temp[0][5], + "new_pair": temp[0][6], + "first_votes": temp[0][7], + "second_votes": temp[0][8], + "state_send_photo": temp[0][9], + "voters": temp[0][10], + "amount_users": temp[0][11], + "last_winner": temp[0][12], + "kicked_users": temp[0][13], + } + return result + + async def get_users(self, update): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + users_info = select(ParticipantsModel.__table__).where( + ParticipantsModel.__table__.c.chat_id == update.object.chat_id + ) + result = await session.execute(users_info) + return result.fetchall() + + async def set_last_winner(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_state_photo = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(last_winner=value) + ) + await session.execute(query_state_photo) + await session.commit() + + async def set_state_photo(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_state_photo = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(state_photo=value) + ) + await session.execute(query_state_photo) + await session.commit() + + async def set_state_send_photo(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_state_send_photo = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(state_send_photo=value) + ) + await session.execute(query_state_send_photo) + await session.commit() + + async def set_amount_users(self, update, amount): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_amount_users = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(amount_users=amount) + ) + await session.execute(query_amount_users) + await session.commit() + + async def set_participants(self, chat_id, users): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + users_write_in_game = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == chat_id, + ) + .values(users=users) + ) + await session.execute(users_write_in_game) + await session.commit() + + async def set_new_pair(self, update, new_pair): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_new_pair = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(new_pair=new_pair) + ) + await session.execute(query_new_pair) + await session.commit() + + async def set_state_in_game(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_state_in_game = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(state_in_game=value) + ) + await session.execute(query_state_in_game) + await session.commit() + + async def set_state_wait_votes(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_state_wait_votes = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(state_wait_votes=value) + ) + await session.execute(query_state_wait_votes) + await session.commit() + + async def set_first_votes(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_first_votes = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(first_votes=value) + ) + await session.execute(query_first_votes) + await session.commit() + + async def set_second_votes(self, update, value): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_second_votes = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(second_votes=value) + ) + await session.execute(query_second_votes) + await session.commit() + + async def set_voters(self, update, value, game): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + if value is None: + game["voters"]["already_voted"] = [] + query_second_votes = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(voters=game["voters"]) + ) + await session.execute(query_second_votes) + await session.commit() + + async def set_kicked(self, update, value, game): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + users_kicked_from_game = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values(kicked_users=value) + ) + await session.execute(users_kicked_from_game) + await session.commit() + + async def reset_all_states(self, update): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + query_state_photo = ( + refresh(GameModel.__table__) + .where( + GameModel.__table__.c.chat_id == update.object.chat_id, + ) + .values( + users=None, + state_in_game=None, + state_wait_votes=None, + new_pair=None, + first_votes=0, + second_votes=0, + state_send_photo=None, + amount_users=None, + voters=None, + ) + ) + await session.execute(query_state_photo) + await session.commit() + + async def new_win(self, update, game): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + user_new_win = ( + refresh(ParticipantsModel.__table__) + .where( + ParticipantsModel.__table__.c.name == game["last_winner"], + ParticipantsModel.__table__.c.chat_id == update.object.chat_id, - ParticipantsModel.__table__.c.name == k, - ) - result = await session.execute(users_exists_select) - if not ((update.object.chat_id, k) in result.fetchall()): - new_user = ParticipantsModel( - name=k, - wins=0, - chat_id=update.object.chat_id, - owner_id=v, - photo_id=None, - access_key=None, - ) - session.add(new_user) - await session.commit() - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, text="Регистрация прошла успешно!" ) + .values( + wins=ParticipantsModel.__table__.c.wins + 1, + ) + ) + await session.execute(user_new_win) + await session.commit() + + async def get_statistics(self, update): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + user_check_wins = select( + ParticipantsModel.__table__.c.wins, + ParticipantsModel.__table__.c.name, + ).where( + ParticipantsModel.__table__.columns.chat_id + == update.object.chat_id, + ParticipantsModel.__table__.c.owner_id == update.object.id, ) + result = await session.execute(user_check_wins) + return result.fetchall() - def check_users(self, this_chat): - if len(this_chat.users) <= 1: - this_chat.state_send_photo = False - this_chat.state_wait_votes = False - this_chat.state_in_game = False + async def get_all_statistics(self, update): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + user_check_wins = select( + ParticipantsModel.__table__.c.wins, + ParticipantsModel.__table__.c.name, + ).where( + ParticipantsModel.__table__.columns.chat_id + == update.object.chat_id, + ) + result = await session.execute(user_check_wins) + return result.fetchall() + + async def command_kick(self, update): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + user_check_wins = delete(ParticipantsModel.__table__).where( + ParticipantsModel.__table__.columns.chat_id + == update.object.chat_id, + ParticipantsModel.__table__.c.owner_id == update.object.id, + ) + await session.execute(user_check_wins) + await session.commit() + + async def command_invite(self, update): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + user_newsession_wins = insert(GameSession.__table__).values( + chat_id=update.object.chat_id, + ) + await session.execute(user_newsession_wins) + user_newchat_wins = insert(GameModel.__table__).values( + chat_id=update.object.chat_id, + ) + await session.execute(user_newchat_wins) + await session.commit() + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["WELCOME_PHRASE"], + ) + ) + + def check_users(self, game): + if len(game["users"]["participants"]) <= 1: return 1 return 0 - async def command_download_photo(self, update, this_chat): + async def command_download_photo(self, update): if hasattr(update.object, "type") and update.object.type == "photo": await self.app.database.connect() async with self.app.database.session.begin() as session: @@ -196,137 +610,324 @@ async def command_download_photo(self, update, this_chat): .where( ParticipantsModel.__table__.c.owner_id == update.object.owner_id, - ParticipantsModel.__table__.c.chat_id == update.object.chat_id, + ParticipantsModel.__table__.c.chat_id + == update.object.chat_id, ) .values( photo_id=update.object.photo_id, access_key=update.object.access_key, ) ) + await self.set_state_photo(update, False) await session.execute(users_add_photos) await session.commit() - this_chat.state_photo = False - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text="Фотографии успешно загружены!", + + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["SUCC_PHOTO"], ) ) - async def command_start_game(self, update, this_chat): - this_chat.users = await app.store.vk_api.proccess_start_game( - update.object.chat_id - ) - this_chat.amount_users = len(this_chat.users) - if len(this_chat.users) == 0: - await self.app.store.vk_api.send_message( - Message(chat_id=update.object.chat_id, text="Вы не прошли регистрацию!") - ) + async def proccess_start_game(self, chat_id): + await self.app.database.connect() + async with self.app.database.session.begin() as session: + users_exists_select = select( + ParticipantsModel.__table__.c.name, + ParticipantsModel.__table__.c.owner_id, + ParticipantsModel.__table__.c.photo_id, + ParticipantsModel.__table__.c.access_key, + ).where(ParticipantsModel.__table__.c.chat_id == chat_id) + temp = await session.execute(users_exists_select) + temp = temp.fetchall() + result = {"participants": []} + for i in temp: + result["participants"] = result.get("participants", []) + [ + {i[0]: [i[1], i[2], i[3]]} + ] + return result + + async def command_kick_from_game(self, kicked_name, update, game): + if not game["state_in_game"]: + if update.object.id in self.reader: + user_name = kicked_name[kicked_name.find("|") + 1 : -1] + if game["kicked_users"] is None: + game["kicked_users"] = {} + game["kicked_users"]["kicked"] = game["kicked_users"].get( + "kicked", [] + ) + [user_name] + await self.set_kicked(update, game["kicked_users"], game) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["USER_KICKED"], + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["ADMIN_COMMAND"], + ) + ) else: - for i in range(3, 0, -1): - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Игра начинается через {i}.", + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["DUR_GAME"], + ) + ) + + async def command_start_game(self, update, game): + if not game["state_in_game"]: + self.post_init(game) + await self.reset_all_states(update) + game["users"] = await self.proccess_start_game( + update.object.chat_id + ) + if game["kicked_users"] is None: + game["kicked_users"] = {} + game["kicked_users"]["kicked"] = [] + await self.set_kicked(update, game["kicked_users"], game) + game["users"] = check_kicked( + game["kicked_users"]["kicked"], game["users"] + ) + await self.set_participants(update.object.chat_id, game["users"]) + if len( + list( + filter( + lambda x: all( + j is not None for i in x.values() for j in i + ), + game["users"]["participants"], + ) + ) + ) == len(game["users"]["participants"]): + game["amount_users"] = len(game["users"]["participants"]) + await self.set_amount_users(update, game["amount_users"]) + if len(game["users"]["participants"]) == 0: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["NO_REG"], + ) + ) + elif len(game["users"]["participants"]) == 1: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["LITTLE_PEOPLE"], + ) + ) + else: + for i in range(3, 0, -1): + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + f"{lexicon_for_messages['START_GAME']} {i}.", + ) + ) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["LETS_GO"], + ) ) + await self.set_state_send_photo(update, True) + game["state_send_photo"] = True + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["NOT_ENOUGH_PHOTO"], + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["GAME_GO"], ) - sleep(1) - await self.app.store.vk_api.send_message( - Message(chat_id=update.object.chat_id, text=f"Поехали!") ) - this_chat.state_send_photo = True - async def command_send_photo(self, update, this_chat): - this_chat.new_pair = make_grid(this_chat.users) - attach_pair = [i[1:] for i in this_chat.new_pair] - await self.app.store.vk_api.send_photo( - Attachment( - chat_id=update.object.chat_id, attachment=attach_pair, text="Выбирай!" + async def command_send_photo(self, update, game): + game["new_pair"] = make_grid(game["users"]["participants"]) + await self.set_new_pair(update, game["new_pair"]) + self.out_queue.put_nowait( + ( + "photo", + update.object.chat_id, + lexicon_for_messages["CHOOSE"], + [ + list(game["new_pair"][0].values())[0], + list(game["new_pair"][1].values())[0], + ], ) ) - this_chat.state_in_game = True - ( - this_chat.voters_dict[this_chat.new_pair[0]], - this_chat.voters_dict[this_chat.new_pair[1]], - ) = (0, 0) - this_chat.state_wait_votes = True - self.storage[update.object.chat_id] = [time(), this_chat.state_wait_votes] - - async def command_write_answers(self, update, this_chat): - if update.object.id not in this_chat.voters: - this_chat.state_send_photo = False + game["state_in_game"] = True + await self.set_state_in_game(update, game["state_in_game"]) + game["first_votes"], game["second_votes"] = 0, 0 + await self.set_first_votes(update, game["first_votes"]) + await self.set_second_votes(update, game["second_votes"]) + game["state_wait_votes"] = True + await self.set_state_wait_votes(update, game["state_wait_votes"]) + game["state_send_photo"] = False + await self.set_state_send_photo(update, game["state_send_photo"]) + + async def command_write_answers(self, update, game): + if game["voters"] is None: + game["voters"] = {} + if update.object.id not in game["voters"].get("already_voted", []): + game["state_send_photo"] = False + await self.set_state_send_photo(update, game["state_send_photo"]) if update.object.body == "1": - this_chat.voters_dict[this_chat.new_pair[0]] += 1 - this_chat.voters.append(update.object.id) - if len(this_chat.voters) == this_chat.amount_users: - this_chat.state_wait_votes = False - self.storage[update.object.chat_id][1] = this_chat.state_wait_votes - this_chat.voters = [] + game["first_votes"] += 1 + await self.set_first_votes(update, game["first_votes"]) + game["voters"]["already_voted"] = game["voters"].get( + "already_voted", [] + ) + [update.object.id] + await self.set_voters(update, update.object.id, game) + if len(game["voters"]["already_voted"]) == game["amount_users"]: + game["state_wait_votes"] = False + await self.set_state_wait_votes(update, False) + game["voters"]["already_voted"] = [] + await self.set_voters(update, [], game) + elif update.object.body == "2": - this_chat.voters_dict[this_chat.new_pair[1]] += 1 - this_chat.voters.append(update.object.id) - if len(this_chat.voters) == this_chat.amount_users: - this_chat.state_wait_votes = False - self.storage[update.object.chat_id][1] = this_chat.state_wait_votes - this_chat.voters = [] + game["second_votes"] += 1 + await self.set_second_votes(update, game["second_votes"]) + game["voters"]["already_voted"] = game["voters"].get( + "already_voted", [] + ) + [update.object.id] + await self.set_voters(update, update.object.id, game) + if len(game["voters"]["already_voted"]) == game["amount_users"]: + game["state_wait_votes"] = False + await self.set_state_wait_votes( + update, game["state_wait_votes"] + ) + game["voters"]["already_voted"] = [] + await self.set_voters(update, [], game) elif update.object.id == -1: - this_chat.state_wait_votes = False - self.storage[update.object.chat_id][1] = this_chat.state_wait_votes - this_chat.voters = [] - elif update.object.id in this_chat.voters and update.object.body in ("1", "2"): - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, text=f"Вы уже отдали свой голос!" + game["state_wait_votes"] = False + await self.set_state_wait_votes(update, False) + game["voters"]["already_voted"] = [] + await self.set_voters(update, [], game) + elif update.object.id in game["voters"][ + "already_voted" + ] and update.object.body in ( + "1", + "2", + ): + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["ALR_VOTED"], ) ) - async def command_send_preresult(self, update, this_chat): - check = check_winner(this_chat.voters_dict, this_chat.new_pair) + async def command_send_preresult(self, update, game): + check = check_winner(game) if check == 1: - this_chat.users.remove(this_chat.new_pair[1]) - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"И в текущем сражении победителем стал обладатель первой картинки", + game["users"]["participants"].remove(game["new_pair"][1]) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["FIRST_WIN"], ) ) elif check == 2: - this_chat.users.remove(this_chat.new_pair[0]) - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"И в текущем сражении победителем стал обладатель второй картинки", + game["users"]["participants"].remove(game["new_pair"][0]) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["SECOND_WIN"], ) ) elif not check: - this_chat.users.remove(this_chat.new_pair[0]) - this_chat.users.remove(this_chat.new_pair[1]) - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Никто не победил - следовательно оба вылетают.", - ) + game["users"]["participants"].remove(game["new_pair"][0]) + game["users"]["participants"].remove(game["new_pair"][1]) + self.out_queue.put_nowait( + ("message", update.object.chat_id, lexicon_for_messages["DRAW"]) ) elif update.object.id == -1: - this_chat.users.remove(choice(this_chat.new_pair)) - await self.app.store.vk_api.send_message( - Message( - chat_id=update.object.chat_id, - text=f"Никто не проголосовал, поэтому победитель определяется случайным образом.", + game["users"]["participants"].remove( + choice( + game["new_pair"]["first_partic"] + + game["new_pair"]["second_partic"] + ) + ) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["RANDOM_WIN"], ) ) - this_chat.state_send_photo = True + game["state_send_photo"] = True + await self.set_participants(update.object.chat_id, game["users"]) + await self.set_state_send_photo(update, True) - async def command_stop_game(self, this_chat, update): - await self.app.store.vk_api.send_message( - Message(chat_id=update.object.chat_id, text="Оставшиеся пользователи:") - ) - for i in this_chat.users: - await self.app.store.vk_api.send_message( - Message(chat_id=update.object.chat_id, text=f"{i[0]}") - ) - this_chat.reset_values() - self.storage[update.object.chat_id][1] = False - await self.app.store.vk_api.send_message( - Message(chat_id=update.object.chat_id, text=f"Игра остановлена!") - ) + async def command_stop_game(self, update, game): + if game["state_in_game"]: + game["kicked_users"]["kicked"] = [] + await self.set_kicked(update, game["kicked_users"], game) + game["state_in_game"] = False + await self.set_state_in_game(update, game["state_in_game"]) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["REMAIN"], + ) + ) + for i in game["users"]["participants"]: + self.out_queue.put_nowait( + ("message", update.object.chat_id, f"{list(i.keys())[0]}") + ) + await self.reset_all_states(update) + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["GAME_STOP"], + ) + ) + else: + self.out_queue.put_nowait( + ( + "message", + update.object.chat_id, + lexicon_for_messages["GAME_NO_EXIST"], + ) + ) + + async def _worker(self): + while True: + try: + upd = await self.in_queue.get() + await self.handle_updates(upd) + finally: + self.in_queue.task_done() + + async def start(self): + for _ in range(self.concurrent_workerks): + asyncio.create_task(self._worker()) + + async def stop(self): + await self.in_queue.join() + self.reader.close() + for t in self._tasks: + t.cancel() diff --git a/app/store/bot/poller.py b/app/store/bot/poller.py deleted file mode 100644 index 94ba589..0000000 --- a/app/store/bot/poller.py +++ /dev/null @@ -1,25 +0,0 @@ -import asyncio -from asyncio import Task -from typing import Optional - -from app.store import Store - - -class Poller: - def __init__(self, store: Store): - self.store = store - self.is_running = False - self.poll_task: Optional[Task] = None - - async def start(self): - self.is_running = True - self.poll_task = asyncio.create_task(self.poll()) - - async def stop(self): - self.is_running = False - await self.poll_task - - async def poll(self): - while self.is_running: - updates = await self.store.vk_api.poll() - await self.store.bots_manager.handle_updates(updates) diff --git a/app/store/bot/sender.py b/app/store/bot/sender.py new file mode 100644 index 0000000..876b6cd --- /dev/null +++ b/app/store/bot/sender.py @@ -0,0 +1,45 @@ +import asyncio +import typing + +from app.store.vk_api.dataclasses import Message, Attachment + +if typing.TYPE_CHECKING: + from app.web.app import Application + + +class VKSender: + def __init__(self, out_queue, app: "Application"): + self.app = app + self._tasks: typing.List[asyncio.Task] = [] + self.out_queue = out_queue + + async def send_vk(self, upd): + if upd[0] == "message": + await self.app.store.vk_api.send_message( + Message(chat_id=upd[1], text=upd[2]), self.app + ) + if upd[0] == "photo": + await self.app.store.vk_api.send_photo( + Attachment( + chat_id=upd[1], + attachment=upd[3], + text=upd[2], + ), + self.app, + ) + + async def _worker(self): + while True: + try: + upd = await self.out_queue.get() + await self.send_vk(upd) + finally: + self.out_queue.task_done() + + async def start(self): + asyncio.create_task(self._worker()) + + async def stop(self): + await self.out_queue.join() + for t in self._tasks: + t.cancel() diff --git a/app/store/bot/services.py b/app/store/bot/services.py index 88399da..9d818e3 100644 --- a/app/store/bot/services.py +++ b/app/store/bot/services.py @@ -10,9 +10,22 @@ def make_grid(data_users: list): return [participant_1, participant_2] -def check_winner(game: dict, pair: list): - if game[pair[0]] > game[pair[1]]: +def check_winner(game): + if game["first_votes"] > game["second_votes"]: return 1 - if game[pair[0]] < game[pair[1]]: + if game["first_votes"] < game["second_votes"]: return 2 return 0 + + +def check_kicked(kicked_users: list, active_users: dict): + i = 0 + while i != len(active_users["participants"]): + if list(active_users["participants"][i].keys())[-1] in kicked_users: + active_users["participants"] = ( + active_users["participants"][:i] + + active_users["participants"][i + 1 :] + ) + else: + i += 1 + return active_users diff --git a/app/store/database/database.py b/app/store/database/database.py index 8161d11..4830b70 100644 --- a/app/store/database/database.py +++ b/app/store/database/database.py @@ -1,5 +1,9 @@ from typing import Optional, TYPE_CHECKING, Any -from sqlalchemy.ext.asyncio import AsyncEngine, AsyncSession, create_async_engine +from sqlalchemy.ext.asyncio import ( + AsyncEngine, + AsyncSession, + create_async_engine, +) from sqlalchemy.orm import declarative_base, sessionmaker from app.store.database.sqlalchemy_base import db @@ -17,11 +21,17 @@ def __init__(self, app: "Application"): async def connect(self, *_: list, **__: dict) -> None: self._db = db - self._engine = create_async_engine("postgresql+asyncpg://kts_user:kts_pass@localhost:5432/kts", echo=True) - self.session = sessionmaker(self._engine, expire_on_commit=False, future=True, class_=AsyncSession) - - + self._engine = create_async_engine( + "postgresql+asyncpg://kts_user:kts_pass@localhost:5432/kts", + echo=True, + ) + self.session = sessionmaker( + self._engine, + expire_on_commit=False, + future=True, + class_=AsyncSession, + ) async def disconnect(self, *_: list, **__: dict) -> None: if self._engine: - await self._engine.dispose() \ No newline at end of file + await self._engine.dispose() diff --git a/app/store/models/model.py b/app/store/models/model.py index 1996d61..42c09bb 100644 --- a/app/store/models/model.py +++ b/app/store/models/model.py @@ -1,10 +1,35 @@ +from sqlalchemy.orm import relationship + from app.store.database.sqlalchemy_base import db -from sqlalchemy import ( - Column, - Text, - BigInteger, -) +from sqlalchemy import Column, Text, BigInteger, ForeignKey, Boolean +from sqlalchemy.dialects.postgresql import JSONB + + +class GameModel(db): + __tablename__ = "game" + id = Column(BigInteger, primary_key=True) + chat_id = Column( + BigInteger, ForeignKey("game_session.chat_id", ondelete="CASCADE") + ) + users = Column(JSONB) + state_photo = Column(Boolean) + state_in_game = Column(Boolean) + state_wait_votes = Column(Boolean) + new_pair = Column(JSONB) + first_votes = Column(BigInteger, default=0) + second_votes = Column(BigInteger, default=0) + state_send_photo = Column(Boolean) + voters = Column(JSONB) + amount_users = Column(BigInteger) + last_winner = Column(Text) + kicked_users = Column(JSONB) + + +class GameSession(db): + __tablename__ = "game_session" + id = Column(BigInteger, primary_key=True) + chat_id = Column(BigInteger, unique=True) class ParticipantsModel(db): @@ -12,7 +37,9 @@ class ParticipantsModel(db): id = Column(BigInteger, primary_key=True) name = Column(Text, nullable=False) wins = Column(BigInteger) - chat_id = Column(BigInteger, nullable=False) + chat_id = Column( + BigInteger, ForeignKey("game_session.chat_id", ondelete="CASCADE") + ) owner_id = Column(BigInteger) photo_id = Column(BigInteger) access_key = Column(Text) diff --git a/app/store/vk_api/accessor.py b/app/store/vk_api/accessor.py index 7b840cd..49b09fa 100644 --- a/app/store/vk_api/accessor.py +++ b/app/store/vk_api/accessor.py @@ -2,9 +2,7 @@ import typing from typing import Optional from aiohttp import TCPConnector -from sqlalchemy import select from aiohttp.client import ClientSession -from app.store.models.model import ParticipantsModel from app.base.base_accessor import BaseAccessor from app.store.vk_api.dataclasses import ( Message, @@ -12,6 +10,7 @@ UpdateObject, UpdatePhoto, Attachment, + UpdateAction, ) from app.store.vk_api.poller import Poller @@ -36,9 +35,7 @@ async def connect(self, app: "Application"): await self._get_long_poll_service() except Exception as e: self.logger.error("Exception", exc_info=e) - self.poller = Poller(app.store) self.logger.info("start polling") - await self.poller.start() async def disconnect(self, app: "Application"): if self.session: @@ -74,7 +71,8 @@ async def _get_long_poll_service(self): self.ts = data["ts"] self.logger.info(self.server) - async def poll(self): + async def poll(self, app): + await self.connect(app) async with self.session.get( self._build_query( host=self.server, @@ -83,7 +81,7 @@ async def poll(self): "act": "a_check", "key": self.key, "ts": self.ts, - "wait": 1, + "wait": 10, }, ) ) as resp: @@ -101,9 +99,13 @@ async def poll(self): Update( type=update["type"], object=UpdatePhoto( - chat_id=update["object"]["message"]["peer_id"], + chat_id=update["object"]["message"][ + "peer_id" + ], id=update["object"]["message"]["id"], - body=update["object"]["message"]["text"], + body=update["object"]["message"][ + "text" + ], type=i["type"], owner_id=i["photo"]["owner_id"], photo_id=i["photo"]["id"], @@ -112,20 +114,44 @@ async def poll(self): ) ) else: - updates.append( - Update( - type=update["type"], - object=UpdateObject( - chat_id=update["object"]["message"]["peer_id"], - id=update["object"]["message"]["from_id"], - body=update["object"]["message"]["text"], - ), + try: + updates.append( + Update( + type=update["type"], + object=UpdateAction( + chat_id=update["object"]["message"][ + "peer_id" + ], + id=update["object"]["message"]["from_id"], + body=update["object"]["message"]["text"], + type=update["object"]["message"]["action"][ + "type" + ], + member_id=update["object"]["message"][ + "action" + ]["member_id"], + ), + ) ) - ) - - await self.app.store.bots_manager.handle_updates(updates) + except KeyError: + updates.append( + Update( + type=update["type"], + object=UpdateObject( + chat_id=update["object"]["message"][ + "peer_id" + ], + id=update["object"]["message"]["from_id"], + body=update["object"]["message"]["text"], + type="other_type", + ), + ) + ) + await self.disconnect(app) + return updates - async def send_message(self, message: Message) -> None: + async def send_message(self, message: Message, app) -> None: + await self.connect(app) async with self.session.get( self._build_query( API_PATH, @@ -140,6 +166,7 @@ async def send_message(self, message: Message) -> None: ) as resp: data = await resp.json() self.logger.info(data) + await self.disconnect(app) @staticmethod def _build_attachment(attach_mass: list[str]): @@ -149,7 +176,8 @@ def _build_attachment(attach_mass: list[str]): spisok.append(stroka) return ",".join(spisok) - async def send_photo(self, attachment: Attachment) -> None: + async def send_photo(self, attachment: Attachment, app) -> None: + await self.connect(app) attachments = self._build_attachment(attachment.attachment) print(attachments) async with self.session.get( @@ -167,8 +195,10 @@ async def send_photo(self, attachment: Attachment) -> None: ) as resp: data = await resp.json() self.logger.info(data) + await self.disconnect(app) - async def make_userlist(self, chat_id): + async def make_userlist(self, chat_id, app): + await self.connect(app) async with self.session.get( self._build_query( API_PATH, @@ -185,16 +215,5 @@ async def make_userlist(self, chat_id): full_name = f'@{data["response"]["profiles"][i]["screen_name"]}' id_profile = data["response"]["profiles"][i]["id"] participants.append((full_name, id_profile)) + await self.disconnect(app) return participants - - async def proccess_start_game(self, chat_id): - await self.app.database.connect() - async with self.app.database.session.begin() as session: - users_exists_select = select( - ParticipantsModel.__table__.c.name, - ParticipantsModel.__table__.c.owner_id, - ParticipantsModel.__table__.c.photo_id, - ParticipantsModel.__table__.c.access_key, - ).where(ParticipantsModel.__table__.c.chat_id == chat_id) - result = await session.execute(users_exists_select) - return result.fetchall() diff --git a/app/store/vk_api/dataclasses.py b/app/store/vk_api/dataclasses.py index a6f544f..631bfa9 100644 --- a/app/store/vk_api/dataclasses.py +++ b/app/store/vk_api/dataclasses.py @@ -6,20 +6,25 @@ class UpdateObject: chat_id: int id: int body: str + type: str @dataclass class UpdatePhoto(UpdateObject): - type: str owner_id: int photo_id: int access_key: str +@dataclass +class UpdateAction(UpdateObject): + member_id: int + + @dataclass class Update: type: str - object: UpdateObject | UpdatePhoto + object: UpdateObject | UpdatePhoto | UpdateAction @dataclass diff --git a/app/store/vk_api/poller.py b/app/store/vk_api/poller.py index c97f3c1..3a8b2c1 100644 --- a/app/store/vk_api/poller.py +++ b/app/store/vk_api/poller.py @@ -5,22 +5,22 @@ class Poller: - def __init__(self, store: Store): + def __init__(self, in_queue, store: Store): self.store = store self.is_running = False self.poll_task: Optional[Task] = None + self.in_queue = in_queue - async def start(self): + async def start(self, app): self.is_running = True - self.poll_task = asyncio.create_task(self.poll()) - await asyncio.gather(self.poll_task) + self.poll_task = asyncio.create_task(self.poll(app)) async def stop(self): self.is_running = False - await self.poll_task + self.poll_task.cancel() - async def poll(self): + async def poll(self, app): while self.is_running: - updates = await self.store.vk_api.poll() - if not (updates is None): - await self.store.bots_manager.handle_updates(updates) + updates = await self.store.vk_api.poll(app) + for u in updates: + self.in_queue.put_nowait(u) diff --git a/app/web/app.py b/app/web/app.py index e6f5fb0..2520f47 100644 --- a/app/web/app.py +++ b/app/web/app.py @@ -2,8 +2,6 @@ from aiohttp.web import ( Application as AiohttpApplication, - Request as AiohttpRequest, - View as AiohttpView, ) from app.store import Store, setup_store diff --git a/app/web/mw.py b/app/web/mw.py deleted file mode 100644 index e36ca6b..0000000 --- a/app/web/mw.py +++ /dev/null @@ -1,8 +0,0 @@ -from aiohttp import web -from aiohttp.abc import Request - - -@web.middleware -async def example_mw(request: Request, handler): - - return await handler(request) diff --git a/app/web/urls.py b/app/web/urls.py deleted file mode 100644 index b21877e..0000000 --- a/app/web/urls.py +++ /dev/null @@ -1,10 +0,0 @@ -from aiohttp.web_app import Application -from aiohttp_cors import CorsConfig - -__all__ = ("register_urls",) - - -def register_urls(application: Application, cors: CorsConfig): - import app.users.urls - - app.users.urls.register_urls(application, cors) diff --git a/main.py b/main.py deleted file mode 100644 index 8f1f171..0000000 --- a/main.py +++ /dev/null @@ -1,13 +0,0 @@ -import os - -from app.web.app import setup_app -from aiohttp.web import run_app - -if __name__ == "__main__": - run_app( - setup_app( - config_path=os.path.join( - os.path.dirname(os.path.realpath(__file__)), "config.yml" - ) - ) - ) \ No newline at end of file diff --git a/pytest.ini b/pytest.ini index 2ad7400..58cc523 100644 --- a/pytest.ini +++ b/pytest.ini @@ -1,3 +1,3 @@ [pytest] -env = - D:CONFIG=tests/config.yaml +filterwarnings = ignore::DeprecationWarning +asyncio_mode = auto diff --git a/requirements.txt b/requirements.txt index c3ef3b3..5a84433 100644 --- a/requirements.txt +++ b/requirements.txt @@ -37,3 +37,4 @@ SQLAlchemy==2.0.3 typing_extensions==4.4.0 webargs==5.5.3 yarl==1.8.2 +docker-compose=1.29.2-1 diff --git a/run_bot.py b/run_bot.py new file mode 100644 index 0000000..1c90e20 --- /dev/null +++ b/run_bot.py @@ -0,0 +1,13 @@ +import os + +from app.web.app import setup_app +from app.store.bot.bot_runner import run + + +if __name__ == "__main__": + setup_app( + config_path=os.path.join( + os.path.dirname(os.path.realpath(__file__)), "config.yml" + ) + ) + run() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/bot/__init__.py b/tests/bot/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/bot/test_bot.py b/tests/bot/test_bot.py new file mode 100644 index 0000000..61531af --- /dev/null +++ b/tests/bot/test_bot.py @@ -0,0 +1,351 @@ +from app.store.models.model import ParticipantsModel, GameModel +from app.store.bot.lexicon import lexicon_for_messages +from app.store.vk_api.dataclasses import ( + Message, + Update, + UpdateObject, + UpdatePhoto, +) +from sqlalchemy import select + + +class TestManager: + async def test_registration_command(self, store, cli, create_base): + upd = Update( + type="message_new", + object=UpdateObject( + id=1, chat_id=1, body="Регистрация!", type="test_type" + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + break + message: Message = store.vk_api.send_message.mock_calls[0].args[0] + assert store.vk_api.make_userlist.call_count == 1 + assert message.chat_id == 1 + assert message.text == lexicon_for_messages["SUCC_REG"] + + async def test_no_auth_download_command( + self, store, cli, no_auth_create_base + ): + upd = Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Загрузить фотографии!", + type="test_type", + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + break + message: Message = store.vk_api.send_message.mock_calls[0].args[0] + assert message.chat_id == 1 + assert message.text == lexicon_for_messages["NO_REG"] + + async def test_auth_download_command(self, store, cli, create_base): + upd = Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Загрузить фотографии!", + type="test_type", + ), + ) + await store.bot_manager.handle_updates(upd) + upd = Update( + type="message_new", + object=UpdatePhoto( + id=1, + chat_id=1, + body="", + type="photo", + owner_id=1, + photo_id=1, + access_key="1", + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + break + message: Message = store.vk_api.send_message.mock_calls[0].args[0] + assert message.chat_id == 1 + assert message.text == lexicon_for_messages["SUCC_PHOTO"] + + async def test_no_auth_start_command(self, store, cli, no_auth_create_base): + upd = Update( + type="message_new", + object=UpdateObject( + id=1, chat_id=1, body="Начать игру!", type="test_type" + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + break + message: Message = store.vk_api.send_message.mock_calls[0].args[0] + assert message.chat_id == 1 + assert message.text == lexicon_for_messages["NO_REG"] + + async def test_stop_no_game_command(self, store, cli, create_base): + upd = Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Остановить игру!", + type="test_type", + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + break + message: Message = store.vk_api.send_message.mock_calls[0].args[0] + assert message.chat_id == 1 + assert message.text == lexicon_for_messages["GAME_NO_EXIST"] + + async def test_statistics_command(self, store, cli, create_base): + upd = Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Моя статистика!", + type="test_type", + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + break + message: Message = store.vk_api.send_message.mock_calls[-1].args[0] + assert message.chat_id == 1 + assert ( + message.text + == f"{lexicon_for_messages['STATISTIC_PLAYER']} olred:%0A{lexicon_for_messages['AMOUNT_WINS']}: 0" + ) + + async def test_left_command( + self, store, cli, create_base, db_session, connection + ): + await store.bot_manager.handle_updates( + Update( + type="message_new", + object=UpdateObject( + id=1, chat_id=1, body="", type="chat_kick_user" + ), + ) + ) + async with db_session.begin() as session: + users_exists_select = select( + ParticipantsModel.__table__.c.chat_id, + ParticipantsModel.__table__.c.name, + ).where( + ParticipantsModel.__table__.columns.chat_id == 1, + ParticipantsModel.__table__.c.name == "olred", + ) + result = await session.execute(users_exists_select) + assert len(result.fetchall()) == 0 + + async def test_statistics_admin_command( + self, store, connection, db_session, cli, no_auth_create_base + ): + async with db_session.begin() as session: + for i in range(2): + new_user = ParticipantsModel( + name=f"olred{i}", + wins=0, + chat_id=1, + owner_id=582423336 + i, + photo_id=1, + access_key="dasda", + ) + session.add(new_user) + await session.commit() + upd = Update( + type="message_new", + object=UpdateObject( + id=582423336, chat_id=1, body="Статистика!", type="test_type" + ), + ) + await store.bot_manager.handle_updates(upd) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + message_1: Message = store.vk_api.send_message.mock_calls[0].args[0] + message_2: Message = store.vk_api.send_message.mock_calls[1].args[0] + assert message_1.chat_id == 1 + assert ( + message_1.text + == f"{lexicon_for_messages['STATISTIC_PLAYER']} olred0:%0A{lexicon_for_messages['AMOUNT_WINS']}: 0" + ) + assert ( + message_2.text + == f"{lexicon_for_messages['STATISTIC_PLAYER']} olred1:%0A{lexicon_for_messages['AMOUNT_WINS']}: 0" + ) + + async def test_in_game_commands( + self, store, connection, db_session, cli, no_auth_create_base + ): + async with db_session.begin() as session: + for i in range(2): + new_user = ParticipantsModel( + name=f"olred{i}", + wins=0, + chat_id=1, + owner_id=1 + i, + photo_id=1, + access_key="dasda", + ) + session.add(new_user) + await session.commit() + await store.bot_manager.handle_updates( + Update( + type="message_new", + object=UpdateObject( + id=1, chat_id=1, body="Начать игру!", type="test_type" + ), + ) + ) + await store.bot_manager.handle_updates( + Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Загрузить фотографии!", + type="test_type", + ), + ) + ) + await store.bot_manager.handle_updates( + Update( + type="message_new", + object=UpdateObject( + id=1, chat_id=1, body="Начать игру!", type="test_type" + ), + ) + ) + await store.bot_manager.handle_updates( + Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Моя статистика!", + type="test_type", + ), + ) + ) + await store.bot_manager.handle_updates( + Update( + type="message_new", + object=UpdateObject( + id=1, + chat_id=1, + body="Остановить игру!", + type="test_type", + ), + ) + ) + while not store.out_queue.empty(): + try: + upd = await store.out_queue.get() + await store.vk_sender.send_vk(upd) + finally: + store.out_queue.task_done() + message_1: Message = store.vk_api.send_message.mock_calls[3].args[0] + message_2: Message = store.vk_api.send_message.mock_calls[-1].args[0] + message_3: Message = store.vk_api.send_message.mock_calls[4].args[0] + message_4: Message = store.vk_api.send_message.mock_calls[5].args[0] + message_5: Message = store.vk_api.send_message.mock_calls[6].args[0] + assert message_2.chat_id == 1 + assert message_1.text == lexicon_for_messages["LETS_GO"] + assert message_2.text == lexicon_for_messages["GAME_STOP"] + assert message_3.text == lexicon_for_messages["DUR_GAME"] + assert message_4.text == lexicon_for_messages["GAME_GO"] + assert message_5.text == lexicon_for_messages["DUR_GAME"] + + async def test_kick_from_game_admin_command( + self, store, connection, db_session, cli, no_auth_create_base + ): + async with db_session.begin() as session: + for i in range(2): + new_user = ParticipantsModel( + name=f"@olred{i}", + wins=0, + chat_id=1, + owner_id=1 + i, + photo_id=1, + access_key="dasda", + ) + session.add(new_user) + await session.commit() + upd = Update( + type="message_new", + object=UpdateObject( + id=582423336, + chat_id=1, + body="Исключить @olred0", + type="test_type", + ), + ) + await store.bot_manager.handle_updates(upd) + upd = Update( + type="message_new", + object=UpdateObject( + id=582423336, chat_id=1, body="Начать игру!", type="test_type" + ), + ) + await store.bot_manager.handle_updates(upd) + upd = Update( + type="message_new", + object=UpdateObject( + id=582423336, + chat_id=1, + body="Остановить игру!", + type="test_type", + ), + ) + async with db_session.begin() as session: + users_exists_select = select(GameModel.__table__.c.users) + result = await session.execute(users_exists_select) + massiv_keys = [] + for i in result.fetchall()[-1][-1]["participants"]: + key_value = list(i.keys())[-1] + if key_value != "@olred0": + massiv_keys.append(key_value) + assert "@olred0" not in massiv_keys diff --git a/tests/config.yaml b/tests/config.yaml deleted file mode 100644 index 45a4ab5..0000000 --- a/tests/config.yaml +++ /dev/null @@ -1,4 +0,0 @@ -debug: true - -security: - token: token diff --git a/tests/conftest.py b/tests/conftest.py index 5871ed8..d4bb209 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -1 +1 @@ -import pytest +from .fixtures import * diff --git a/tests/fixtures.py b/tests/fixtures.py deleted file mode 100644 index 6b8b688..0000000 --- a/tests/fixtures.py +++ /dev/null @@ -1,2 +0,0 @@ -import pytest -from tests.conftest import * diff --git a/tests/fixtures/__init__.py b/tests/fixtures/__init__.py new file mode 100644 index 0000000..55e5f84 --- /dev/null +++ b/tests/fixtures/__init__.py @@ -0,0 +1 @@ +from .common import * diff --git a/tests/fixtures/common.py b/tests/fixtures/common.py new file mode 100644 index 0000000..48adb8a --- /dev/null +++ b/tests/fixtures/common.py @@ -0,0 +1,113 @@ +import logging +import os +from unittest.mock import AsyncMock + +import pytest +from aiohttp.test_utils import loop_context, TestClient +from sqlalchemy.ext.asyncio import AsyncSession +from sqlalchemy import text +from app.store import Database +from app.store import Store +from app.store.models.model import GameSession, GameModel, ParticipantsModel +from app.web.app import setup_app + + +@pytest.fixture(scope="session") +def event_loop(): + with loop_context() as _loop: + yield _loop + + +@pytest.fixture(autouse=True, scope="session") +def server(): + app = setup_app( + config_path=os.path.join( + os.path.abspath(os.path.dirname(__file__)), "..", "config.yml" + ) + ) + app.store.vk_api.make_userlist = AsyncMock() + app.store.vk_api.send_message = AsyncMock() + app.store.vk_api.send_photo = AsyncMock() + + app.database = Database(app) + # make_grid = AsyncMock(return_values=[]) + + return app + + +@pytest.fixture +def store(server) -> Store: + return server.store + + +@pytest.fixture +async def connection(server): + await server.database.connect() + + +@pytest.fixture +def db_session(server): + return server.database.session + + +@pytest.fixture +async def create_base(server, connection, db_session): + async with db_session.begin() as session: + new_game_session = GameSession(chat_id=1) + session.add(new_game_session) + await session.commit() + async with db_session.begin() as session: + new_user = ParticipantsModel( + name="olred", + wins=0, + chat_id=1, + owner_id=1, + photo_id=None, + access_key=None, + ) + new_game = GameModel(chat_id=1) + session.add(new_user) + session.add(new_game) + await session.commit() + + +@pytest.fixture +async def no_auth_create_base(server, connection, db_session): + async with db_session.begin() as session: + new_game_session = GameSession(chat_id=1) + session.add(new_game_session) + await session.commit() + await session.commit() + async with db_session.begin() as session: + new_game = GameModel(chat_id=1) + session.add(new_game) + await session.commit() + + +@pytest.fixture(autouse=True, scope="function") +def clear_send(server): + server.store.vk_api.send_message.reset_mock() + + +@pytest.fixture(autouse=True) +def cli(aiohttp_client, event_loop, server) -> TestClient: + return event_loop.run_until_complete(aiohttp_client(server)) + + +@pytest.fixture(autouse=True, scope="function") +async def clear_db(server): + yield + try: + session = AsyncSession(server.database._engine) + connection = session.connection() + for table in server.database._db.metadata.tables: + await session.execute(text(f"TRUNCATE {table} CASCADE")) + await session.execute( + text(f"ALTER SEQUENCE {table}_id_seq RESTART WITH 1") + ) + + await session.commit() + connection.close() + + except Exception as err: + logging.warning(err) diff --git a/tests/test_common.py b/tests/test_common.py deleted file mode 100644 index dd1f6dc..0000000 --- a/tests/test_common.py +++ /dev/null @@ -1,13 +0,0 @@ -import pytest - - -class TestCommon: - async def test_404(self, cli): - response = await cli.get("/") - assert response.status == 404 - assert await response.json() == { - "status": "error", - "data": {}, - "code": None, - "message": "404: Not Found", - }