Пример #1
0
 async def contact_accept(self, body):
     channel = body["channel"]
     request = await self.service.accept(
         contact_request_id=body["contact_request"],
         staff=self.consumer.user)
     if not request:
         await self.consumer.send_error("exhibition.unknown_contact_request"
                                        )
         return
     staff = await self.service.get_staff(
         exhibitor_id=request["exhibitor"]["id"])
     if self.consumer.user.id not in staff:
         await self.consumer.send_error("exhibition.not_staff_member")
         return
     await self.consumer.send_success()
     await self.consumer.channel_layer.group_send(
         GROUP_USER.format(id=request["user"]["id"]),
         {
             "type": "exhibition.contact_accepted",
             "contact_request": request,
             "channel": channel,
         },
     )
     for user_id in staff:
         await self.consumer.channel_layer.group_send(
             GROUP_USER.format(id=str(user_id)),
             {
                 "type": "exhibition.contact_close",
                 "contact_request": request
             },
         )
Пример #2
0
    async def patch(self, body):
        staff = []
        if body["id"] != "":
            staff += await self.service.get_staff(exhibitor_id=body["id"])
        exhibitor = await self.service.patch(exhibitor=body,
                                             world=self.consumer.world)

        for user in exhibitor["staff"]:
            if user["id"] not in staff:
                staff.append(user["id"])

        for user_id in staff:
            data = await database_sync_to_async(
                self.service.get_exhibition_data_for_user)(user_id)
            await self.consumer.channel_layer.group_send(
                GROUP_USER.format(id=str(user_id)),
                {
                    "type": "exhibition.exhibition_data_update",
                    "data": data
                },
            )

        if not exhibitor:
            await self.consumer.send_error("exhibition.unknown_room")
        else:
            await self.consumer.send_success({"exhibitor": exhibitor})
Пример #3
0
 async def dispatch_disconnect(self, close_code):
     if self.consumer.user:
         await self.consumer.channel_layer.group_discard(
             GROUP_USER.format(id=self.consumer.user.id),
             self.consumer.channel_name,
         )
         await self.consumer.channel_layer.group_discard(
             GROUP_WORLD.format(id=self.consumer.world.id),
             self.consumer.channel_name,
         )
Пример #4
0
    async def login(self, body):
        kwargs = {
            "world": self.consumer.world,
        }
        if not body or "token" not in body:
            client_id = body.get("client_id")
            if not client_id:
                await self.consumer.send_error(code="auth.missing_id_or_token")
                return
            kwargs["client_id"] = client_id
        else:
            token = self.consumer.world.decode_token(body["token"])
            if not token:
                await self.consumer.send_error(code="auth.invalid_token")
                return
            kwargs["token"] = token

        login_result = await database_sync_to_async(login)(**kwargs)
        if not login_result:
            await self.consumer.send_error(code="auth.denied")
            return

        self.consumer.user = login_result.user
        if settings.SENTRY_DSN:
            with configure_scope() as scope:
                scope.user = {"id": str(self.consumer.user.id)}

        async with aioredis() as redis:
            redis_read = await redis.hgetall(f"chat:read:{self.consumer.user.id}")
            read_pointers = {k.decode(): int(v.decode()) for k, v in redis_read.items()}

        await self.consumer.send_json(
            [
                "authenticated",
                {
                    "user.config": self.consumer.user.serialize_public(),
                    "world.config": login_result.world_config,
                    "chat.channels": login_result.chat_channels,
                    "chat.read_pointers": read_pointers,
                    "exhibition": login_result.exhibition_data,
                },
            ]
        )

        await self._enforce_connection_limit()

        await self.consumer.channel_layer.group_add(
            GROUP_USER.format(id=self.consumer.user.id), self.consumer.channel_name
        )
        await self.consumer.channel_layer.group_add(
            GROUP_WORLD.format(id=self.consumer.world.id), self.consumer.channel_name
        )

        await ChatService(self.consumer.world).enforce_forced_joins(self.consumer.user)
Пример #5
0
    async def _enforce_connection_limit(self):
        connection_limit = self.consumer.world.config.get("connection_limit")
        if not connection_limit:
            return

        message = {"type": "connection.replaced"}

        channel_names = []
        group = GROUP_USER.format(id=self.consumer.user.id)
        cl = self.consumer.channel_layer
        key = cl._group_key(group)
        async with cl.connection(cl.consistent_hash(group)) as connection:
            # Discard old channels based on group_expiry
            await connection.zremrangebyscore(
                key, min=0, max=int(time.time()) - cl.group_expiry
            )
            channel_names += [
                x.decode("utf8") for x in await connection.zrange(key, 0, -1)
            ]

        if len(channel_names) < connection_limit:
            return

        if connection_limit == 1:
            channels_to_drop = channel_names
        else:
            channels_to_drop = channel_names[: -1 * (connection_limit - 1)]

        (
            connection_to_channel_keys,
            channel_keys_to_message,
            channel_keys_to_capacity,
        ) = cl._map_channel_keys_to_connection(channels_to_drop, message)

        for connection_index, channel_redis_keys in connection_to_channel_keys.items():
            group_send_lua = (
                """ for i=1,#KEYS do
                            redis.call('LPUSH', KEYS[i], ARGV[i])
                            redis.call('EXPIRE', KEYS[i], %d)
                        end
                        """
                % cl.expiry
            )

            args = [
                channel_keys_to_message[channel_key]
                for channel_key in channel_redis_keys
            ]
            async with cl.connection(connection_index) as connection:
                await connection.eval(
                    group_send_lua, keys=channel_redis_keys, args=args
                )
Пример #6
0
 async def delete(self, body):
     staff = await self.service.get_staff(exhibitor_id=body["exhibitor"])
     if not await self.service.delete(exhibitor_id=body["exhibitor"]):
         await self.consumer.send_error("exhibition.unknown_exhibitor")
     else:
         for user_id in staff:
             data = await database_sync_to_async(
                 self.service.get_exhibition_data_for_user)(user_id)
             await self.consumer.channel_layer.group_send(
                 GROUP_USER.format(id=str(user_id)),
                 {
                     "type": "exhibition.exhibition_data_update",
                     "data": data
                 },
             )
         await self.consumer.send_success({})
Пример #7
0
 async def mark_read(self, body):
     if not body.get("id"):
         raise ConsumerException("chat.invalid_body")
     async with aioredis() as redis:
         await redis.hset(f"chat:read:{self.consumer.user.id}",
                          self.channel_id, body.get("id"))
         await redis.sadd(f"chat:unread.notify:{self.channel_id}",
                          str(self.consumer.user.id))
     await self.consumer.send_success()
     await self.consumer.channel_layer.group_send(
         GROUP_USER.format(id=self.consumer.user.id),
         {
             "type": "chat.read_pointers",
             "socket": self.consumer.socket_id,
         },
     )
Пример #8
0
 async def silence(self, body):
     if body.get("id") == str(self.consumer.user.id):
         await self.consumer.send_error(code="user.silence.self")
         return
     ok = await set_user_silenced(self.consumer.world,
                                  body.get("id"),
                                  by_user=self.consumer.user)
     if ok:
         await self.consumer.send_success({})
         # Force user browser to reload instead of drop to kick out of e.g. BBB sessions
         await self.consumer.channel_layer.group_send(
             GROUP_USER.format(id=body.get("id")),
             {"type": "connection.reload"},
         )
     else:
         await self.consumer.send_error(code="user.not_found")
Пример #9
0
 async def contact(self, body):
     exhibitor = await self.service.get_exhibitor(
         exhibitor_id=body["exhibitor"])
     if not exhibitor:
         await self.consumer.send_error("exhibition.unknown_exhibitor")
         return
     request = await self.service.contact(exhibitor_id=exhibitor["id"],
                                          user=self.consumer.user)
     await self.consumer.send_success({"contact_request": request})
     for staff_member in exhibitor["staff"]:
         await self.consumer.channel_layer.group_send(
             GROUP_USER.format(id=str(staff_member["id"])),
             {
                 "type": "exhibition.contact_request",
                 "contact_request": request,
             },
         )
Пример #10
0
 async def contact_cancel(self, body):
     request = await self.service.missed(
         contact_request_id=body["contact_request"])
     if not request:
         await self.consumer.send_error("exhibition.unknown_contact_request"
                                        )
         return
     await self.consumer.send_success()
     staff = await self.service.get_staff(
         exhibitor_id=request["exhibitor"]["id"])
     for user_id in staff:
         await self.consumer.channel_layer.group_send(
             GROUP_USER.format(id=str(user_id)),
             {
                 "type": "exhibition.contact_close",
                 "contact_request": request,
             },
         )
Пример #11
0
    async def send(self, body):
        content = body["content"]
        event_type = body["event_type"]
        if event_type != "channel.message":
            raise ConsumerException("chat.unsupported_event_type")
        if not (content.get("type") == "text" or
                (content.get("type") == "deleted" and "replaces" in body) or
                (content.get("type") == "call" and not self.channel.room)):
            raise ConsumerException("chat.unsupported_content_type")

        if body.get("replaces"):
            other_message = await self.service.get_event(
                pk=body["replaces"],
                channel_id=self.channel_id,
            )
            if self.consumer.user.id != other_message.sender_id:
                # Users may only edit messages by other users if they are mods,
                # and even then only delete them
                is_moderator = (self.channel.room and await
                                self.consumer.world.has_permission_async(
                                    user=self.consumer.user,
                                    room=self.channel.room,
                                    permission=Permission.ROOM_CHAT_MODERATE,
                                ))
                if body["content"]["type"] != "deleted" or not is_moderator:
                    raise ConsumerException("chat.denied")
            await self.service.update_event(other_message.id,
                                            new_content=content)

        if content.get("type") == "text" and not content.get("body"):
            raise ConsumerException("chat.empty")

        if await self.consumer.user.is_blocked_in_channel_async(self.channel):
            raise ConsumerException("chat.denied")

        if self.consumer.user.is_silenced:
            # In regular channels, this is already prevented by room permissions, but we need to check for DMs
            raise ConsumerException("chat.denied")

        # Re-open direct messages. If a user hid a direct message channel, it should re-appear once they get a message
        if not self.channel.room:
            async with aioredis() as redis:
                all_visible = await redis.exists(
                    f"chat:direct:shownall:{self.channel_id}")
            if not all_visible:
                users = await self.service.show_channels_to_hidden_users(
                    self.channel_id)
                for user in users:
                    await self.service.broadcast_channel_list(
                        user, self.consumer.socket_id)
                    async with aioredis() as redis:
                        await redis.sadd(
                            f"chat:unread.notify:{self.channel_id}",
                            str(user.id),
                        )
            async with aioredis() as redis:
                await redis.setex(f"chat:direct:shownall:{self.channel_id}",
                                  3600 * 24 * 7, "true")

        event = await self.service.create_event(
            channel=self.channel,
            event_type=event_type,
            content=content,
            sender=self.consumer.user,
            replaces=body.get("replaces", None),
        )
        await self.consumer.send_success(
            {"event": {k: v
                       for k, v in event.items() if k != "type"}})
        await self.consumer.channel_layer.group_send(
            GROUP_CHAT.format(channel=self.channel_id), event)

        # Unread notifications
        # We pop user IDs from the list of users to notify, because once they've been notified they don't need a
        # notification again until they sent a new read pointer.
        async with aioredis() as redis:
            batch_size = 100
            while True:
                users = await redis.spop(
                    f"chat:unread.notify:{self.channel_id}", 100)

                for user in users:
                    await self.consumer.channel_layer.group_send(
                        GROUP_USER.format(id=user.decode()),
                        {
                            "type": "chat.notification_pointers",
                            "data": {
                                self.channel_id: event["event_id"]
                            },
                        },
                    )

                if len(users) < batch_size:
                    break

        if content.get("type") == "text":
            match = re.search(r"(?P<url>https?://[^\s]+)", content.get("body"))
            if match:
                await asgiref.sync.sync_to_async(
                    retrieve_preview_information.apply_async
                )(kwargs={
                    "world": str(self.consumer.world.id),
                    "event_id": event["event_id"],
                })