diff --git a/shvatka/core/services/player.py b/shvatka/core/services/player.py index 7b99eeb6..e6c6ab2e 100644 --- a/shvatka/core/services/player.py +++ b/shvatka/core/services/player.py @@ -102,6 +102,12 @@ async def join_team( await dao.commit() raise await dao.commit() + logger.info( + "Captain %s added to team %s player %s", + manager.id, + team.id, + player.id, + ) async def get_checked_player_on_team( diff --git a/shvatka/tgbot/__main__.py b/shvatka/tgbot/__main__.py index 2f020f38..ef046bc5 100644 --- a/shvatka/tgbot/__main__.py +++ b/shvatka/tgbot/__main__.py @@ -69,7 +69,9 @@ async def main(): logger.info("started") try: - await dp.start_polling(bot) + await dp.start_polling( + bot, allowed_updates=dp.resolve_used_update_types(skip_events={"aiogd_update"}) + ) finally: await engine.dispose() logger.info("stopped") diff --git a/shvatka/tgbot/handlers/team/manage.py b/shvatka/tgbot/handlers/team/manage.py index 2a5ee4df..0e5b274d 100644 --- a/shvatka/tgbot/handlers/team/manage.py +++ b/shvatka/tgbot/handlers/team/manage.py @@ -3,23 +3,30 @@ from aiogram import Bot, F, Router from aiogram.enums import ChatType from aiogram.exceptions import TelegramBadRequest -from aiogram.filters import Command, CommandObject, or_f -from aiogram.types import Message +from aiogram.filters import ( + Command, + CommandObject, + or_f, + ChatMemberUpdatedFilter, + IS_NOT_MEMBER, + IS_MEMBER, +) +from aiogram.types import Message, ChatMemberUpdated, CallbackQuery from aiogram.utils.text_decorations import html_decoration as hd from shvatka.core.models import dto -from shvatka.core.services.player import join_team, get_team_players +from shvatka.core.services.player import get_team_players, get_player_by_id, join_team from shvatka.core.services.team import create_team +from shvatka.core.utils import exceptions from shvatka.core.utils.defaults_constants import DEFAULT_ROLE from shvatka.core.utils.exceptions import ( TeamError, PlayerAlreadyInTeam, AnotherTeamInChat, - PlayerRestoredInTeam, - PermissionsError, ) from shvatka.core.views.game import GameLogWriter from shvatka.infrastructure.db.dao.holder import HolderDao +from shvatka.tgbot import keyboards as kb from shvatka.tgbot import states from shvatka.tgbot.filters.has_target import HasTargetFilter from shvatka.tgbot.filters.is_admin import is_admin_filter @@ -87,8 +94,18 @@ async def cmd_create_team_group(message: Message, user: dto.User, chat: dto.Chat await message.reply(NOT_SUPERGROUP_ERROR) +async def user_join_chat_with_team( + event: ChatMemberUpdated, team: dto.Team, player: dto.Player, bot: Bot +) -> None: + await bot.send_message( + chat_id=event.chat.id, + text=f"Принять {hd.quote(player.name_mention)} в команду {hd.quote(team.name)}?", + reply_markup=kb.get_join_team_kb(team, player), + ) + + async def cmd_add_in_team( - message: Message, + _: Message, team: dto.Team, target: dto.Player, player: dto.Player, @@ -96,6 +113,7 @@ async def cmd_add_in_team( command: CommandObject, dao: HolderDao, ): + role = command.args or DEFAULT_ROLE logger.info( "Captain %s try to add %s in team %s", player.id, @@ -105,41 +123,131 @@ async def cmd_add_in_team( try: chat_member = await bot.get_chat_member(team.get_chat_id(), target.get_chat_id()) except TelegramBadRequest: - return await message.reply("Не могу найти этого пользователя (его нет в чате?)") + return await bot.send_message( + chat_id=team.get_chat_id(), text="Не могу найти этого пользователя (его нет в чате?)" + ) if chat_member.user.is_bot: return if chat_member.status == "left": - return await message.reply("Не могу добавить этого пользователя. Вероятно его нет в чате") - role = command.args or DEFAULT_ROLE + return await bot.send_message( + chat_id=team.get_chat_id(), + text="Не могу добавить этого пользователя. Вероятно его нет в чате", + ) try: await join_team(target, team, player, dao.team_player, role) except PlayerAlreadyInTeam as e: - return await message.reply( - f"Игрок {hd.quote(target.name_mention)} уже находится в команде " - f"({hd.quote(e.team.name)}).\n" # type: ignore + return await bot.send_message( + chat_id=team.get_chat_id(), + text=f"Игрок {hd.quote(target.name_mention)} уже находится в команде " + f"({hd.quote(e.team.name)}).\n", # type: ignore ) - except PlayerRestoredInTeam: - return await message.reply("Игрок возвращён в команду, я сделаю вид что и не покидал") - except PermissionsError: - return await message.reply( - "У тебя нет прав добавлять игроков в команду. Обратись к капитану" + except exceptions.PlayerRestoredInTeam: + return await bot.send_message( + chat_id=team.get_chat_id(), + text="Игрок возвращён в команду, я сделаю вид что и не покидал", + ) + except exceptions.PermissionsError: + return await bot.send_message( + chat_id=team.get_chat_id(), + text="У тебя нет прав добавлять игроков в команду. Обратись к капитану", ) - await message.answer( - "В команду {team} добавлен игрок " - "{player} в качестве роли указано: {role}".format( + await bot.send_message( + chat_id=team.get_chat_id(), + text="В команду {team} добавлен игрок {player} в качестве роли указано: {role}".format( team=hd.bold(team.name), player=hd.bold(target.name_mention), role=hd.italic(role) - ) + ), ) + + +async def button_join( + callback_query: CallbackQuery, + callback_data: kb.JoinToTeamRequestCD, + team: dto.Team, + player: dto.Player, + team_player: dto.TeamPlayer, + bot: Bot, + dao: HolderDao, +): + if team.id != callback_data.team_id: + raise exceptions.SHDataBreach( + f"asked about team_id {callback_data.team_id} but in team {team.id}" + ) + if team_player.team_id != team.id: + return await callback_query.answer("Ты состоишь в другой команде!", show_alert=True) + await callback_query.answer() + target = await get_player_by_id(callback_data.player_id, dao.player) logger.info( - "Captain %s add to team %s player %s with role %s", + "Captain %s try to add %s in team %s", player.id, - team.id, target.id, - role, + team.id, + ) + chat_member = await bot.get_chat_member(team.get_chat_id(), target.get_chat_id()) + if chat_member.user.is_bot: + return + if chat_member.status == "left": + return await bot.send_message( + chat_id=team.get_chat_id(), + text="Не могу добавить этого пользователя. Вероятно его уже нет в чате", + ) + assert callback_query.message + try: + await join_team(target, team, player, dao.team_player) + except PlayerAlreadyInTeam as e: + return await bot.edit_message_text( + chat_id=team.get_chat_id(), + message_id=callback_query.message.message_id, + text=f"Игрок {hd.quote(target.name_mention)} уже находится в команде " + f"({hd.quote(e.team.name)}).\n", # type: ignore + ) + except exceptions.PlayerRestoredInTeam: + return await bot.edit_message_text( + chat_id=team.get_chat_id(), + message_id=callback_query.message.message_id, + text="Игрок возвращён в команду, я сделаю вид что и не покидал", + ) + except exceptions.PermissionsError: + return await callback_query.answer( + text="У тебя нет прав добавлять игроков в команду. Обратись к капитану", + show_alert=True, + ) + await bot.edit_message_text( + chat_id=team.get_chat_id(), + message_id=callback_query.message.message_id, + text="В команду {team} добавлен игрок {player}".format( + team=hd.bold(team.name), player=hd.bold(target.name_mention) + ), ) +async def button_join_no( + callback_query: CallbackQuery, + callback_data: kb.JoinToTeamRequestCD, + team: dto.Team, + player: dto.Player, + team_player: dto.TeamPlayer, + dao: HolderDao, +): + if team.id != callback_data.team_id: + raise exceptions.SHDataBreach( + f"asked about team_id {callback_data.team_id} but in team {team.id}" + ) + if team_player.team_id != team.id: + return await callback_query.answer("Ты состоишь в другой команде!", show_alert=True) + await callback_query.answer() + target = await get_player_by_id(callback_data.player_id, dao.player) + assert callback_query.message + await callback_query.message.edit_text( + f"{hd.quote(player.name_mention)} не стал принимать " + f"игрока {hd.quote(target.name_mention)} в команду" + ) + + +async def answer_not_enough_rights(callback_query: CallbackQuery): + await callback_query.answer("Недостаточно прав", show_alert=True) + + async def cmd_team(message: Message, team: dto.Team): await message.answer( text=render_team_card(team), @@ -158,6 +266,7 @@ async def cmd_players(message: Message, team: dto.Team, dao: HolderDao): def setup() -> Router: router = Router(name=__name__) router.message.outer_middleware.register(TeamPlayerMiddleware()) + router.callback_query.outer_middleware.register(TeamPlayerMiddleware()) disable_router_on_game(router) router.message.register( @@ -187,6 +296,27 @@ def setup() -> Router: Command(commands=PLAYERS_COMMAND), IsTeamFilter(), ) + router.chat_member.register( + user_join_chat_with_team, + ChatMemberUpdatedFilter(IS_NOT_MEMBER >> IS_MEMBER), + IsTeamFilter(), + ) + router.callback_query.register( + button_join, + kb.JoinToTeamRequestCD.filter(F.y_n), + or_f(TeamPlayerFilter(is_captain=True), TeamPlayerFilter(can_add_players=True)), + IsTeamFilter(), + ) + router.callback_query.register( + button_join_no, + kb.JoinToTeamRequestCD.filter(~F.y_n), + or_f(TeamPlayerFilter(is_captain=True), TeamPlayerFilter(can_add_players=True)), + IsTeamFilter(), + ) + router.callback_query.register( + answer_not_enough_rights, + kb.JoinToTeamRequestCD.filter(), + ) register_start_handler( Command(commands=MANAGE_TEAM_COMMAND), F.chat.type == ChatType.PRIVATE, diff --git a/shvatka/tgbot/keyboards/__init__.py b/shvatka/tgbot/keyboards/__init__.py index ed7ae72b..c712950f 100644 --- a/shvatka/tgbot/keyboards/__init__.py +++ b/shvatka/tgbot/keyboards/__init__.py @@ -18,6 +18,10 @@ get_kb_agree_promotion, AgreePromotionCD, ) +from .team import ( + JoinToTeamRequestCD, + get_join_team_kb, +) from .waiver import ( get_kb_waivers, get_kb_manage_waivers, diff --git a/shvatka/tgbot/keyboards/team.py b/shvatka/tgbot/keyboards/team.py new file mode 100644 index 00000000..a7855a44 --- /dev/null +++ b/shvatka/tgbot/keyboards/team.py @@ -0,0 +1,24 @@ +from aiogram.filters.callback_data import CallbackData +from aiogram.utils.keyboard import InlineKeyboardBuilder + +from shvatka.core.models import dto + + +class JoinToTeamRequestCD(CallbackData, prefix="join_to_team"): + team_id: int + player_id: int + y_n: bool + + +def get_join_team_kb(team: dto.Team, player: dto.Player): + builder = InlineKeyboardBuilder() + builder.button( + text="Принять", + callback_data=JoinToTeamRequestCD(team_id=team.id, player_id=player.id, y_n=True), + ) + builder.button( + text="Отказать", + callback_data=JoinToTeamRequestCD(team_id=team.id, player_id=player.id, y_n=False), + ) + builder.adjust(2) + return builder.as_markup()