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, )
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()
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
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