Пример #1
0
    async def direct_create(self, body):
        user_ids = set(body.get("users", []))
        user_ids.add(self.consumer.user.id)

        hide = body.get("hide", True)

        channel, created, users = await self.service.get_or_create_direct_channel(
            user_ids=user_ids, hide=hide, hide_except=str(self.consumer.user.id)
        )
        if not channel:
            raise ConsumerException("chat.denied")

        self.channel_id = str(channel.id)
        self.channel = channel

        reply = await self._subscribe()
        if created:
            for user in users:
                event = await self.service.create_event(
                    channel=channel,
                    event_type="channel.member",
                    content={
                        "membership": "join",
                        "user": user.serialize_public(
                            trait_badges_map=self.consumer.world.config.get(
                                "trait_badges_map"
                            )
                        ),
                    },
                    sender=user,
                )
                await self.consumer.channel_layer.group_send(
                    GROUP_CHAT.format(channel=self.channel_id),
                    event,
                )

                if not hide or user == self.consumer.user:
                    await self.service.broadcast_channel_list(
                        user=user, socket_id=self.consumer.socket_id
                    )
                    async with aioredis() as redis:
                        await redis.sadd(
                            f"chat:unread.notify:{self.channel_id}",
                            str(user.id),
                        )
            if not hide:
                async with aioredis() as redis:
                    await redis.setex(
                        f"chat:direct:shownall:{self.channel_id}", 3600 * 24 * 7, "true"
                    )

        reply["id"] = str(channel.id)
        await self.consumer.send_success(reply)
Пример #2
0
async def unregister_connection():
    async with aioredis() as redis:
        await redis.hincrby(
            "connections",
            f"{settings.VENUELESS_COMMIT}.{settings.VENUELESS_ENVIRONMENT}",
            -1,
        )
Пример #3
0
async def ping_connection():
    async with aioredis() as redis:
        await redis.setex(
            f"connections:{settings.VENUELESS_COMMIT}.{settings.VENUELESS_ENVIRONMENT}",
            15,
            "exists",
        )
Пример #4
0
    async def leave(self, body):
        await self._unsubscribe(clean_volatile_membership=False)
        if self.channel.room:
            await self._leave()
            async with aioredis() as redis:
                await redis.srem(f"chat:unread.notify:{self.channel_id}",
                                 str(self.consumer.user.id))
        else:
            await self.service.hide_channel_user(self.channel_id,
                                                 self.consumer.user.id)
            async with aioredis() as redis:
                await redis.delete(f"chat:direct:shownall:{self.channel_id}")
            await self.service.broadcast_channel_list(self.consumer.user,
                                                      self.consumer.socket_id)

        await self.consumer.send_success()
Пример #5
0
async def test_autofix_numbers(chat_room):
    async with world_communicator() as c1, aioredis() as redis:
        await redis.delete("chat.event_id")
        await c1.send_json_to(
            ["chat.join", 123, {
                "channel": str(chat_room.channel.id)
            }])
        await c1.receive_json_from()
        response = await c1.receive_json_from()
        assert response[1]["event_id"] == 1
        await redis.delete("chat.event_id")
        await c1.send_json_to([
            "chat.send",
            123,
            {
                "channel": str(chat_room.channel.id),
                "event_type": "channel.message",
                "content": {
                    "type": "text",
                    "body": "Hello world"
                },
            },
        ])
        await c1.receive_json_from()
        response = await c1.receive_json_from()
        assert response[1]["event_id"] == 3
Пример #6
0
 async def publish_read_pointers(self, body):
     if self.consumer.socket_id != body["socket"]:
         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(["chat.read_pointers", read_pointers])
Пример #7
0
async def get_connections():
    async with aioredis() as redis:
        conns = await redis.hgetall("connections")
        ret = {}
        for k, v in conns.items():
            if v == 0 or await redis.get(f"connections:{k.decode()}") != b"exists":
                await redis.hdel("connections", k)
            else:
                ret[k.decode()] = int(v.decode())
        return ret
Пример #8
0
    async def _set_cache_version(self):
        async with aioredis() as redis:
            await redis.eval(
                SETIFHIGHER,
                [f"{self._cachekey}:version"],
                [self.version],
            )

        cache = caches["process"]
        cache.set(self._cachekey, self, timeout=600)
Пример #9
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)
Пример #10
0
async def register_connection():
    async with aioredis() as redis:
        await redis.hincrby(
            "connections",
            f"{settings.VENUELESS_COMMIT}.{settings.VENUELESS_ENVIRONMENT}",
            1,
        )
        await redis.setex(
            f"connections:{settings.VENUELESS_COMMIT}.{settings.VENUELESS_ENVIRONMENT}",
            15,
            "exists",
        )
Пример #11
0
    async def join(self, body):
        if not self.consumer.user.profile.get("display_name"):
            raise ConsumerException("channel.join.missing_profile")
        reply = await self._subscribe()

        volatile_config = self.module_config.get("volatile", False)
        volatile_client = body.get("volatile", volatile_config)
        if (
            volatile_client != volatile_config
            and await self.consumer.world.has_permission_async(
                user=self.consumer.user,
                room=self.room,
                permission=Permission.ROOM_CHAT_MODERATE,
            )
        ):
            volatile_config = volatile_client

        joined = await self.service.add_channel_user(
            self.channel_id, self.consumer.user, volatile=volatile_config
        )
        if joined:
            event = await self.service.create_event(
                channel=self.channel,
                event_type="channel.member",
                content={
                    "membership": "join",
                    "user": self.consumer.user.serialize_public(
                        trait_badges_map=self.consumer.world.config.get(
                            "trait_badges_map"
                        )
                    ),
                },
                sender=self.consumer.user,
            )
            await self.consumer.channel_layer.group_send(
                GROUP_CHAT.format(channel=self.channel_id),
                event,
            )
            await self.service.broadcast_channel_list(
                self.consumer.user, self.consumer.socket_id
            )
            if not volatile_config:
                async with aioredis() as redis:
                    await redis.sadd(
                        f"chat:unread.notify:{self.channel_id}",
                        str(self.consumer.user.id),
                    )
        await self.consumer.send_success(reply)
Пример #12
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,
         },
     )
Пример #13
0
    async def refresh_from_db_if_outdated(self):
        async with aioredis() as redis:
            latest_version = await redis.get(f"{self._cachekey}:version")
        if latest_version:
            if latest_version == b"deleted":
                raise self.__class__.DoesNotExist
            latest_version = int(latest_version.decode())
        else:
            latest_version = 0

        if latest_version == self.version:
            return

        cache = caches["process"]
        cached_instance = cache.get(self._cachekey)
        if cached_instance and cached_instance.version == latest_version:
            self._refresh_from_cache(cached_instance)
            return

        await database_sync_to_async(self.refresh_from_db)()
        cache.set(self._cachekey, self, timeout=600)
        if latest_version < self.version:
            await self._set_cache_version()
Пример #14
0
    async def send_reaction(self, body):
        reaction = body.get("reaction")
        if reaction not in (
                "+1",
                "clap",
                "heart",
                "open_mouth",
                "rolling_on_the_floor_laughing",
        ):
            raise ConsumerException(code="room.unknown_reaction",
                                    message="Unknown reaction")

        redis_key = f"reactions:{self.consumer.world.id}:{body['room']}"
        redis_debounce_key = f"reactions:{self.consumer.world.id}:{body['room']}:{reaction}:{self.consumer.user.id}"

        # We want to send reactions out to anyone, but we want to aggregate them over short time frames ("ticks") to
        # make sure we do not send 500 messages if 500 people react in the same second, but just one.
        async with aioredis() as redis:
            debounce = await redis.set(redis_debounce_key,
                                       "1",
                                       expire=2,
                                       exist=redis.SET_IF_NOT_EXIST)
            if not debounce:
                # User reacted in the 2 seconds, let's ignore this.
                await self.consumer.send_success({})
                return

            # First, increase the number of reactions
            tr = redis.multi_exec()
            tr.hsetnx(redis_key, "tick", int(time.time()))
            tr.hget(redis_key, "tick")
            tr.hincrby(redis_key, reaction, 1)
            tick_new, tick_start, _ = await tr.execute()

            await self.consumer.send_success({})

            if tick_new or time.time() - int(tick_start.decode()) > 3:
                # We're the first one to react since the last tick! It's our job to wait for the length of a tick, then
                # distribute the value to everyone.
                await asyncio.sleep(1)

                tr = redis.multi_exec()
                tr.hgetall(redis_key)
                tr.delete(redis_key)
                val, _ = await tr.execute()
                if not val:
                    return
                await self.consumer.channel_layer.group_send(
                    GROUP_ROOM.format(id=self.room.pk),
                    {
                        "type": "room.reaction",
                        "reactions": {
                            k.decode(): int(v.decode())
                            for k, v in val.items() if k.decode() != "tick"
                        },
                        "room": str(body["room"]),
                    },
                )
                for k, v in val.items():
                    if k.decode() != "tick":
                        await store_reaction(body["room"], k.decode(),
                                             int(v.decode()))
Пример #15
0
 async def _check_redis(self):
     async with aioredis() as redis:
         await redis.set("healthcheck", "1")
Пример #16
0
 async def _set_cache_deleted(self):
     async with aioredis() as redis:
         await redis.set(
             f"{self._cachekey}:version",
             "deleted",
         )
Пример #17
0
async def clear_redis():
    from venueless.core.utils.redis import aioredis

    async with aioredis() as redis:
        await redis.flushall()
Пример #18
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"],
                })