diff --git a/streamer/consumers.py b/streamer/consumers.py index 4947c5e..20785d8 100644 --- a/streamer/consumers.py +++ b/streamer/consumers.py @@ -100,7 +100,9 @@ async def disconnect(self, close_code): await self.leave_party() @action() - async def create(self, part_id, user_name, title="", **kwargs): + async def create( + self, part_id, user_name, title="", user_icon="FaRegUser", **kwargs + ): # create room self.anime_room = await self.database_create_room(part_id=part_id, title=title) # create user @@ -108,6 +110,7 @@ async def create(self, part_id, user_name, title="", **kwargs): user_name=user_name, room_id=self.anime_room, is_host=True, + user_icon=user_icon, ) await self.channel_layer.group_add( str(self.anime_room.room_id), self.channel_name @@ -127,13 +130,16 @@ async def create(self, part_id, user_name, title="", **kwargs): ) @action() - async def join(self, room_id: uuid, user_name: str, **kwargs): + async def join( + self, room_id: uuid, user_name: str, user_icon="FaRegUser", **kwargs + ): """joinを受け取った場合のアクション joinを受け取った場合、ルームが存在していればルームに参加する Args: room_id (uuid): AnimeRoomオブジェクトに存在するroom_id user_name (str): ユーザーが指定する事ができるユーザー名 + user_icon (str): ユーザーが指定する react-icons (FA6) のキー。旧拡張は送らないため既定値あり。 """ # 接続要求されたルームのオブジェクトがあれば取得(deleted_at が入っているものは弾く) self.anime_room = await self.database_get_or_none_room(room_id=room_id) @@ -150,7 +156,7 @@ async def join(self, room_id: uuid, user_name: str, **kwargs): _cancel_pending_room_delete(str(self.anime_room.room_id)) # ルームが存在しているのであればAnimeUserオブジェクトを作成 self.anime_user = await self.database_create_user( - user_name=user_name, room_id=self.anime_room + user_name=user_name, room_id=self.anime_room, user_icon=user_icon ) await self.channel_layer.group_add( str(self.anime_room.room_id), self.channel_name @@ -427,19 +433,26 @@ async def leave_party(self): # control database @database_sync_to_async - def database_create_user(self, user_name: str, room_id, is_host: bool = False): + def database_create_user( + self, + user_name: str, + room_id, + is_host: bool = False, + user_icon: str = "FaRegUser", + ): """データベース上にユーザーを作成する Args: user_name ([type]): ユーザーが任意に指定可能な名前 room_id ([type]): AnimeRoomに存在するID is_host (bool, optional): ホストユーザーの場合はTrueにする + user_icon (str, optional): react-icons (FA6) のキー。未指定なら既定アイコン。 Returns: AnimeUser : 作成したユーザーのオブジェクト """ return AnimeUser.objects.create( - user_name=user_name, room_id=room_id, is_host=is_host + user_name=user_name, room_id=room_id, is_host=is_host, user_icon=user_icon ) @database_sync_to_async @@ -553,7 +566,9 @@ def database_renew_state(self): def database_user_list(self): """ルーム内のユーザーを取得する""" ar = AnimeRoom.objects.get(room_id=self.anime_room.room_id) - user_list = ar.inroom.alive().values("user_name", "user_id", "is_host") + user_list = ar.inroom.alive().values( + "user_name", "user_id", "is_host", "user_icon" + ) return list(user_list) @database_sync_to_async diff --git a/streamer/factories.py b/streamer/factories.py index 861f792..b701273 100644 --- a/streamer/factories.py +++ b/streamer/factories.py @@ -21,6 +21,7 @@ class Meta: model = AnimeUser user_name = factory.Faker("name") + user_icon = "FaRegUser" room_id = factory.SubFactory(AnimeRoomFactory) is_host = False updated_at = factory.Faker("date") diff --git a/streamer/format.py b/streamer/format.py index 22603ce..364f888 100644 --- a/streamer/format.py +++ b/streamer/format.py @@ -22,11 +22,15 @@ class User(BaseModel): Attributes: user_id: UUID identifying the user. user_name: display name chosen by the user. + user_icon: react-icons (Font Awesome 6) key string chosen by the user. + Defaults to ``FaRegUser`` so older clients/data that omit it fall back + to the plain user icon. is_host: whether this participant is the room host (owner). """ user_id: UUID user_name: str + user_icon: str = "FaRegUser" is_host: bool = False diff --git a/streamer/models.py b/streamer/models.py index e352055..f308930 100644 --- a/streamer/models.py +++ b/streamer/models.py @@ -21,6 +21,10 @@ class AnimeRoom(LogicalDeletionMixin): class AnimeUser(LogicalDeletionMixin): user_id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False) user_name = EncryptedCharField(default="user", max_length=20) + # 表示アイコン。フロント(拡張)が react-icons (Font Awesome 6) のキー文字列を送る。 + # アイコン名は PII ではないため平文。旧拡張が送らない場合は既定キーで、現行のシンプルな + # ユーザーアイコン相当(FaRegUser)にフォールバックさせる。 + user_icon = models.CharField(default="FaRegUser", max_length=64) room_id = models.ForeignKey( AnimeRoom, on_delete=models.CASCADE, related_name="inroom" ) diff --git a/streamer/tests.py b/streamer/tests.py index 34d81a6..bceea87 100644 --- a/streamer/tests.py +++ b/streamer/tests.py @@ -29,11 +29,13 @@ async def test_anime_party_consumer_create_ok(self): connected, subprotocol = await communicator.connect() assert connected user_name1 = "user_name1" + user_icon1 = "FaCat" title1 = "鬼滅の刃 - 第1話 - 残酷" await communicator.send_json_to( { "action": "create", "user_name": user_name1, + "user_icon": user_icon1, "part_id": "123456", "title": title1, "request_id": 100, @@ -42,6 +44,8 @@ async def test_anime_party_consumer_create_ok(self): response = await communicator.receive_json_from() assert response["action"] == "create" assert response["user"]["user_name"] == user_name1 + # 送信した user_icon が往復することを確認 + assert response["user"]["user_icon"] == user_icon1 # userがデータベースに作られていることを確認 assert self.anime_user_exist(response["user"]["user_id"]) # roomがデータベースに作られていることを確認 @@ -75,6 +79,8 @@ async def test_anime_party_consumer_join_ok(self): assert response["action"] == "join" # userがデータベースに作られていることを確認 assert self.anime_user_exist(response["user"]["user_id"]) + # user_icon を送らない旧拡張は既定アイコンにフォールバックする(後方互換) + assert response["user"]["user_icon"] == "FaRegUser" await communicator.disconnect() @pytest.mark.django_db(transaction=True) @@ -147,6 +153,10 @@ async def test_anime_party_consumer_video_action(self): hosts = {u["user_name"]: u["is_host"] for u in response["user_list"]} assert hosts[user_name1] is True assert hosts[user_name2] is False + # user_list は表示アイコンのため user_icon も含む(未指定なら既定キー)。 + icons = {u["user_name"]: u["user_icon"] for u in response["user_list"]} + assert icons[user_name1] == "FaRegUser" + assert icons[user_name2] == "FaRegUser" await communicator2.send_json_to( { "action": "video_operation",