예제 #1
0
    def __init__(
        self,
        private_key: str,
        chain_id: ChainID,
        service_room_suffix: str,
        message_received_callback: Callable[[Message], None],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None] = None,
        servers: List[str] = None,
    ) -> None:
        super().__init__()

        self.private_key = private_key
        self.chain_id = chain_id
        self.service_room_suffix = service_room_suffix
        self.message_received_callback = message_received_callback

        if servers:
            self.available_servers = servers
        else:
            self.available_servers = get_matrix_servers(
                DEFAULT_MATRIX_KNOWN_SERVERS[Environment.
                                             PRODUCTION] if chain_id ==
                1 else DEFAULT_MATRIX_KNOWN_SERVERS[Environment.DEVELOPMENT])

        self.client = make_client(
            servers=self.available_servers,
            http_pool_maxsize=4,
            http_retry_timeout=40,
            http_retry_delay=matrix_http_retry_delay,
        )
        self.broadcast_rooms: List[Room] = []
        self._displayname_cache = DisplayNameCache()
        self._user_manager: Optional[UserAddressManager] = None

        if address_reachability_changed_callback is not None:
            self._user_manager = UserAddressManager(
                client=self.client,
                displayname_cache=self._displayname_cache,
                address_reachability_changed_callback=
                address_reachability_changed_callback,
            )

        self.startup_finished = Event()
        self.rate_limiter = RateLimiter(
            allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES,
            reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL,
        )
예제 #2
0
    def __init__(
        self,
        private_key: str,
        chain_id: ChainID,
        service_room_suffix: str,
        message_received_callback: Callable[[Message], None],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None] = None,
    ) -> None:
        super().__init__()

        self.private_key = private_key
        self.chain_id = chain_id
        self.service_room_suffix = service_room_suffix
        self.message_received_callback = message_received_callback

        self.available_servers = get_matrix_servers(
            DEFAULT_MATRIX_KNOWN_SERVERS[Environment.DEVELOPMENT])

        self.client = make_client(
            servers=self.available_servers,
            http_pool_maxsize=4,
            http_retry_timeout=40,
            http_retry_delay=matrix_http_retry_delay,
        )
        self.broadcast_room: Optional[Room] = None
        self.user_manager: Optional[UserAddressManager] = None

        if address_reachability_changed_callback is not None:
            self.user_manager = UserAddressManager(
                client=self.client,
                get_user_callable=self._get_user,
                address_reachability_changed_callback=
                address_reachability_changed_callback,
            )

        self.startup_finished = Event()
예제 #3
0
class MatrixListener(gevent.Greenlet):
    # pylint: disable=too-many-instance-attributes
    def __init__(
        self,
        private_key: str,
        chain_id: ChainID,
        service_room_suffix: str,
        message_received_callback: Callable[[Message], None],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None] = None,
    ) -> None:
        super().__init__()

        self.private_key = private_key
        self.chain_id = chain_id
        self.service_room_suffix = service_room_suffix
        self.message_received_callback = message_received_callback

        self.available_servers = get_matrix_servers(
            DEFAULT_MATRIX_KNOWN_SERVERS[Environment.DEVELOPMENT])

        self.client = make_client(
            servers=self.available_servers,
            http_pool_maxsize=4,
            http_retry_timeout=40,
            http_retry_delay=matrix_http_retry_delay,
        )
        self.broadcast_room: Optional[Room] = None
        self.user_manager: Optional[UserAddressManager] = None

        if address_reachability_changed_callback is not None:
            self.user_manager = UserAddressManager(
                client=self.client,
                get_user_callable=self._get_user,
                address_reachability_changed_callback=
                address_reachability_changed_callback,
            )

        self.startup_finished = Event()

    def listen_forever(self) -> None:
        self.startup_finished.wait()
        self.client.listen_forever()

    def _run(self) -> None:  # pylint: disable=method-hidden
        self._start_client()

        self.client.start_listener_thread()
        self.client.sync_thread.get()

    def stop(self) -> None:
        self.client.stop_listener_thread()

    def _start_client(self) -> None:
        try:
            login_or_register(
                self.client,
                signer=LocalSigner(private_key=decode_hex(self.private_key)))
        except (MatrixRequestError, ValueError):
            raise ConnectionError("Could not login/register to matrix.")

        try:
            room_name = make_room_alias(self.chain_id,
                                        self.service_room_suffix)
            self.broadcast_room = join_global_room(
                client=self.client,
                name=room_name,
                servers=self.available_servers)
        except (MatrixRequestError, TransportError):
            raise ConnectionError(
                "Could not join monitoring broadcasting room.")

        self.broadcast_room.add_listener(self._handle_message,
                                         "m.room.message")

        # Signal that startup is finished
        self.startup_finished.set()

    def follow_address_presence(self,
                                address: Address,
                                refresh: bool = False) -> None:
        if self.user_manager:
            self.user_manager.add_address(address)

            if refresh:
                self.user_manager.populate_userids_for_address(address)
                self.user_manager.refresh_address_presence(address)

            log.debug(
                "Tracking address",
                address=to_checksum_address(address),
                current_presence=self.user_manager.get_address_reachability(
                    address),
                refresh=refresh,
            )

    def _get_user(self, user: Union[User, str]) -> User:
        """Creates an User from an user_id, if none, or fetch a cached User """
        user_id: str = getattr(user, "user_id", user)
        if (self.broadcast_room and user_id in self.broadcast_room._members  # pylint: disable=protected-access
            ):
            duser: User = self.broadcast_room._members[user_id]  # pylint: disable=protected-access

            # if handed a User instance with displayname set, update the discovery room cache
            if getattr(user, "displayname", None):
                assert isinstance(user, User)
                duser.displayname = user.displayname
            user = duser
        elif not isinstance(user, User):
            user = self.client.get_user(user_id)

        return user

    def _handle_message(self, room: Any, event: dict) -> bool:
        """ Handle text messages sent to listening rooms """
        if event["type"] != "m.room.message" or event["content"][
                "msgtype"] != "m.text":
            # Ignore non-messages and non-text messages
            return False

        sender_id = event["sender"]
        user = self._get_user(sender_id)
        peer_address = validate_userid_signature(user)

        if not peer_address:
            log.debug(
                "Message from invalid user displayName signature",
                peer_user=user.user_id,
                room=room,
            )
            return False

        data = event["content"]["body"]
        if not isinstance(data, str):
            log.warning(
                "Received message body not a string",
                peer_user=user.user_id,
                peer_address=to_checksum_address(peer_address),
                room=room,
            )
            return False

        messages = deserialize_messages(data, peer_address)
        if not messages:
            return False

        for message in messages:
            log.debug("Message received",
                      message=message,
                      sender=to_checksum_address(message.sender))
            self.message_received_callback(message)

        return True
예제 #4
0
class MatrixListener(gevent.Greenlet):
    # pylint: disable=too-many-instance-attributes
    def __init__(
        self,
        private_key: str,
        chain_id: ChainID,
        service_room_suffix: str,
        message_received_callback: Callable[[Message], None],
        address_reachability_changed_callback: Callable[
            [Address, AddressReachability], None
        ] = None,
    ) -> None:
        super().__init__()

        self.private_key = private_key
        self.chain_id = chain_id
        self.service_room_suffix = service_room_suffix
        self.message_received_callback = message_received_callback

        self.available_servers = get_matrix_servers(
            DEFAULT_MATRIX_KNOWN_SERVERS[Environment.PRODUCTION]
            if chain_id == 1
            else DEFAULT_MATRIX_KNOWN_SERVERS[Environment.DEVELOPMENT]
        )

        self.client = make_client(
            servers=self.available_servers,
            http_pool_maxsize=4,
            http_retry_timeout=40,
            http_retry_delay=matrix_http_retry_delay,
        )
        self.broadcast_rooms: List[Room] = []
        self.user_manager: Optional[UserAddressManager] = None

        if address_reachability_changed_callback is not None:
            self.user_manager = UserAddressManager(
                client=self.client,
                get_user_callable=self._get_user,
                address_reachability_changed_callback=address_reachability_changed_callback,
            )

        self.startup_finished = Event()
        self.rate_limiter = RateLimiter(
            allowed_bytes=MATRIX_RATE_LIMIT_ALLOWED_BYTES,
            reset_interval=MATRIX_RATE_LIMIT_RESET_INTERVAL,
        )

    def listen_forever(self) -> None:
        self.startup_finished.wait()
        self.client.listen_forever()

    def _run(self) -> None:  # pylint: disable=method-hidden
        self._start_client()

        self.client.start_listener_thread()
        assert self.client.sync_thread
        self.client.sync_thread.get()

    def stop(self) -> None:
        if self.user_manager:
            self.user_manager.stop()
        self.client.stop_listener_thread()

    def _start_client(self) -> None:
        try:
            if self.user_manager:
                self.user_manager.start()

            login_or_register(
                self.client, signer=LocalSigner(private_key=decode_hex(self.private_key))
            )
        except (MatrixRequestError, ValueError):
            raise ConnectionError("Could not login/register to matrix.")

        try:
            self.join_global_rooms(client=self.client, available_servers=self.available_servers)
        except (MatrixRequestError, TransportError):
            raise ConnectionError("Could not join monitoring broadcasting room.")

        # Add listener for global rooms
        for broadcast_room in self.broadcast_rooms:
            broadcast_room.add_listener(self._handle_message, "m.room.message")

        # Signal that startup is finished
        self.startup_finished.set()

    def follow_address_presence(self, address: Address, refresh: bool = False) -> None:
        if self.user_manager:
            self.user_manager.add_address(address)

            if refresh:
                self.user_manager.populate_userids_for_address(address)
                self.user_manager.refresh_address_presence(address)

            log.debug(
                "Tracking address",
                address=to_checksum_address(address),
                current_presence=self.user_manager.get_address_reachability(address),
                refresh=refresh,
            )

    def _get_user(self, user: Union[User, str]) -> User:
        """Creates an User from an user_id, if none, or fetch a cached User """
        user_id: str = getattr(user, "user_id", user)
        for broadcast_room in self.broadcast_rooms:
            if user_id in broadcast_room._members:  # pylint: disable=protected-access
                duser: User = broadcast_room._members[user_id]  # pylint: disable=protected-access

                # if handed a User instance with displayname set, update the discovery room cache
                if getattr(user, "displayname", None):
                    assert isinstance(user, User)
                    duser.displayname = user.displayname
                user = duser
                break
        else:
            user = self.client.get_user(user_id)

        return user

    def _handle_message(self, room: Any, event: dict) -> bool:
        """ Handle text messages sent to listening rooms """
        if event["type"] != "m.room.message" or event["content"]["msgtype"] != "m.text":
            # Ignore non-messages and non-text messages
            return False

        sender_id = event["sender"]
        user = self._get_user(sender_id)
        peer_address = validate_userid_signature(user)

        if not peer_address:
            log.debug(
                "Message from invalid user displayName signature",
                peer_user=user.user_id,
                room=room,
            )
            return False

        data = event["content"]["body"]
        if not isinstance(data, str):
            log.warning(
                "Received message body not a string",
                peer_user=user.user_id,
                peer_address=to_checksum_address(peer_address),
                room=room,
            )
            return False

        messages = deserialize_messages(data, peer_address, self.rate_limiter)
        if not messages:
            return False

        for message in messages:
            assert message.sender, "Message has no sender"
            self.message_received_callback(message)

        return True

    def join_global_rooms(
        self, client: GMatrixClient, available_servers: Sequence[str] = ()
    ) -> None:
        """Join or create a global public room with given name on all available servers.
        If can't, create a public room with the name on each server.

        Params:
            client: matrix-python-sdk client instance
            servers: optional: sequence of known/available servers to try to find the room in
        """
        room_alias_prefix = make_room_alias(self.chain_id, self.service_room_suffix)

        parsed_servers = [
            urlparse(s).netloc for s in available_servers if urlparse(s).netloc not in {None, ""}
        ]

        for server in parsed_servers:
            room_alias_full = f"#{room_alias_prefix}:{server}"
            log.debug("Trying to join pathfinding room", room_alias_full=room_alias_full)
            try:
                broadcast_room = client.join_room(room_alias_full)
                log.debug("Joined pathfinding room", room=broadcast_room)
                self.broadcast_rooms.append(broadcast_room)
            except MatrixRequestError as ex:
                if ex.code is not 404:
                    log.debug(
                        "Could not join pathfinding room, trying to create one",
                        room_alias_full=room_alias_full,
                    )
                    try:
                        global_room = client.create_room(room_alias_full, is_public=True)
                        log.debug("Created pathfinding room", room=broadcast_room)
                        self.broadcast_rooms.append(global_room)
                    except MatrixRequestError:
                        log.debug(
                            "Could neither join nor create a global room",
                            room_alias_full=room_alias_full,
                        )
                        TransportError("Could neither join nor create a global room")
                        raise
                else:
                    log.debug(
                        "Could not join global room",
                        room_alias_full=room_alias_full,
                        _exception=ex,
                    )
                    raise