Beispiel #1
0
    def _handle_choose_winner(self, content: dict):
        try:
            round_id = RoundID(UUID(hex=content["round"]))
        except (KeyError, ValueError):
            raise InvalidRequest("invalid round")
        try:
            winner_id = WhiteCardID(UUID(hex=content["winner"]))
        except (KeyError, ValueError):
            raise InvalidRequest("invalid winner")

        self.user.game.choose_winner(round_id, winner_id)
Beispiel #2
0
    def _handle_chat(self, content: dict):
        # TODO rate limiting, spam blocking, blacklist
        try:
            text = content["text"]
            if not (isinstance(text, str)
                    and config.chat.is_valid_message(text)):
                # TODO graceful handling for disallowed text
                raise InvalidRequest("invalid text")
        except KeyError:
            raise InvalidRequest("invalid text")

        self.user.game.send_event({
            "type": "chat_message",
            "player": self.user.player.to_event_json(),
            "text": text,
        })
Beispiel #3
0
    def _handle_join_game(self, content: dict):
        try:
            game_code = GameCode(content["code"])
            if not isinstance(game_code, str):
                raise InvalidRequest("invalid code")
        except KeyError:
            raise InvalidRequest("invalid code")
        try:
            game = self.server.games.find_by("code", game_code)
        except KeyError:
            raise GameError("game_not_found", "game not found")

        if game.options.password:
            password = content.get("password", "")
            if not password:
                raise GameError("password_required",
                                "a password is required to join the game")
            if game.options.password.upper() != password.upper():
                raise GameError("password_incorrect", "incorrect password")

        game.add_player(self.user)
Beispiel #4
0
    def _handle_authenticate(self, content: dict):
        if self.user:
            raise GameError("already_authenticated", "already authenticated")
        if "id" in content and "token" in content:
            try:
                user_id = UUID(hex=content["id"])
            except (KeyError, ValueError):
                raise InvalidRequest("invalid id")
            try:
                user = self.server.users.find_by("id", user_id)
            except KeyError:
                raise GameError("user_not_found", "user not found")
            if user.token != content["token"]:
                raise GameError("invalid_token", "invalid token")
            self.user = user
            self.user.reconnected(self)
        elif "name" in content:
            name = content["name"]
            if not isinstance(
                    name,
                    str) or not config.users.username.is_valid_name(name):
                raise InvalidRequest("invalid name")
            if self.server.users.exists("name", name.lower()):
                raise GameError("name_in_use", "name already in use")
            user = User(name, self.server, self)
            self.server.add_user(user)
            self.user = user
        else:
            raise InvalidRequest("missing id/token or name")

        LOGGER.info("%s authenticated as %s", self.remote_addr, self.user)
        result = {
            "id": str(user.id),
            "token": user.token,
            "name": user.name,
            "in_game": user.game is not None
        }
        if user.game:
            user.game.send_updates(full_resync=True, to=user.player)
        return result
Beispiel #5
0
    def _handle_play_white(self, content: dict):
        cards: List[Tuple[WhiteCardID, Optional[str]]] = []
        try:
            round_id = RoundID(UUID(hex=content["round"]))
        except (KeyError, ValueError):
            raise InvalidRequest("invalid round")
        try:
            input_cards = content["cards"]
            for input_card in input_cards:
                if not isinstance(input_card, dict):
                    raise InvalidRequest("invalid cards")
                slot_id = WhiteCardID(UUID(hex=input_card["id"]))
                text = input_card.get("text")
                if text is not None:
                    if not (isinstance(text, str)
                            and config.game.blank_cards.is_valid_text(text)):
                        # TODO graceful handling for disallowed text
                        raise InvalidRequest("invalid cards")
                    text = text.strip()
                cards.append((slot_id, text))
        except (KeyError, ValueError):
            raise InvalidRequest("invalid cards")

        self.user.game.play_white_cards(round_id, self.user.player, cards)
Beispiel #6
0
    def _handle_kick_player(self, content: dict):
        try:
            user_id = UserID(UUID(hex=content["user"]))
        except (KeyError, ValueError):
            raise InvalidRequest("invalid user")

        if user_id == self.user.id:
            raise InvalidGameState("self_kick", "can't kick yourself")
        try:
            player = self.user.game.players.find_by("id", user_id)
        except KeyError:
            raise InvalidGameState("player_not_in_game",
                                   "the player is not in the game")

        self.user.game.remove_player(player, LeaveReason.host_kick)
Beispiel #7
0
 def _handle_game_options(self, content: dict):
     changes = {}
     for field in fields(GameOptions):
         if field.name in content:
             if self.user.game.game_running and field.name not in GameOptions.updateable_ingame:
                 raise InvalidGameState(
                     "option_locked",
                     f"{field.name} can't be changed while the game is ongoing"
                 )
             value = content[field.name]
             if field.name == "card_packs":
                 try:
                     value = tuple(
                         self.server.card_packs.find_by(
                             "id", CardPackID(UUID(uuid)))
                         for uuid in value)
                 except (TypeError, ValueError, KeyError):
                     raise InvalidRequest("invalid card_packs list")
             changes[field.name] = value
     try:
         new_options = replace(self.user.game.options, **changes)
         self.user.game.update_options(new_options)
     except ConfigError as ex:
         raise GameError("invalid_options", str(ex)) from None
Beispiel #8
0
    def _handle_request(self, action: ApiAction,
                        call_id: Union[str, int, float], content: dict):
        # noinspection PyBroadException
        try:
            if not self.user and action != ApiAction.authenticate:
                raise GameError("not_authenticated", "must authenticate first")

            try:
                handler = self.handlers[action]
            except KeyError:
                raise InvalidRequest("invalid action")

            call_result = handler(self, content) or {}
            return {"call_id": call_id, "error": None, **call_result}
        except GameError as ex:
            # force a full resync if that will likely be useful
            if isinstance(ex,
                          InvalidGameState) and self.user and self.user.game:
                LOGGER.error("%s hit an error likely caused by desync",
                             self.user,
                             exc_info=True)
                self.user.game.send_updates(full_resync=True,
                                            to=self.user.player)
            return {
                "call_id": call_id,
                "error": ex.code,
                "description": ex.description
            }
        except Exception:
            LOGGER.error("Internal uncaught exception in WebSocket handler.",
                         exc_info=True)
            return {
                "call_id": call_id,
                "error": "internal_error",
                "description": "internal error"
            }
Beispiel #9
0
    async def receive_json_from_client(self, message: Union[str, bytes]):
        """Called by subclasses when they receive a JSON message from the client."""
        if not isinstance(message, str):
            raise InvalidRequest("only text JSON messages allowed")
        try:
            parsed = json.loads(message)
        except JSONDecodeError:
            raise InvalidRequest("invalid JSON")
        if not isinstance(parsed, dict):
            raise InvalidRequest("only JSON objects allowed")

        if not self.handshaked:
            try:
                if parsed["version"] == UI_VERSION:
                    await self.send_json_to_client(
                        {"config": self.server.config_json()})
                    self.handshaked = True
                else:
                    await self.send_json_to_client(
                        {"error": "incorrect_version"}, close=True)
                return
            except KeyError:
                raise InvalidRequest("invalid handshake")

        try:
            action = ApiAction[parsed["action"]]
            call_id = parsed["call_id"]
        except KeyError:
            raise InvalidRequest("action or call_id missing or invalid")
        if not isinstance(action, ApiAction) or not isinstance(
                call_id, (str, int, float)):
            raise InvalidRequest("action or call_id missing or invalid")

        result = self._handle_request(action, call_id, parsed)

        await self.send_json_to_client(result)