Implemented last Matrix integration features.

- Added presence, typing and seen receipt events.
- Added set display_name and avatar methods.
This commit is contained in:
Fabio Manganiello 2022-08-28 15:17:11 +02:00
parent e479ca7e3e
commit c417d2f692
Signed by untrusted user: blacklight
GPG key ID: D90FBA7F76362774
3 changed files with 172 additions and 24 deletions

View file

@ -217,3 +217,34 @@ class MatrixRoomTopicChangedEvent(MatrixEvent):
:param topic: New room topic. :param topic: New room topic.
""" """
super().__init__(*args, topic=topic, **kwargs) super().__init__(*args, topic=topic, **kwargs)
class MatrixRoomTypingStartEvent(MatrixEvent):
"""
Event triggered when a user in a room starts typing.
"""
class MatrixRoomTypingStopEvent(MatrixEvent):
"""
Event triggered when a user in a room stops typing.
"""
class MatrixRoomSeenReceiptEvent(MatrixEvent):
"""
Event triggered when the last message seen by a user in a room is updated.
"""
class MatrixUserPresenceEvent(MatrixEvent):
"""
Event triggered when a user comes online or goes offline.
"""
def __init__(self, *args, is_active: bool, last_active: datetime | None, **kwargs):
"""
:param is_active: True if the user is currently online.
:param topic: When the user was last active.
"""
super().__init__(*args, is_active=is_active, last_active=last_active, **kwargs)

View file

@ -66,6 +66,8 @@ from nio.client.async_client import client_session
from nio.client.base_client import logged_in from nio.client.base_client import logged_in
from nio.crypto import decrypt_attachment from nio.crypto import decrypt_attachment
from nio.crypto.device import OlmDevice from nio.crypto.device import OlmDevice
from nio.events.ephemeral import ReceiptEvent, TypingNoticeEvent
from nio.events.presence import PresenceEvent
from nio.exceptions import OlmUnverifiedDeviceError from nio.exceptions import OlmUnverifiedDeviceError
from nio.responses import DownloadResponse, RoomMessagesResponse from nio.responses import DownloadResponse, RoomMessagesResponse
@ -86,8 +88,12 @@ from platypush.message.event.matrix import (
MatrixRoomInviteEvent, MatrixRoomInviteEvent,
MatrixRoomJoinEvent, MatrixRoomJoinEvent,
MatrixRoomLeaveEvent, MatrixRoomLeaveEvent,
MatrixRoomSeenReceiptEvent,
MatrixRoomTopicChangedEvent, MatrixRoomTopicChangedEvent,
MatrixRoomTypingStartEvent,
MatrixRoomTypingStopEvent,
MatrixSyncEvent, MatrixSyncEvent,
MatrixUserPresenceEvent,
) )
from platypush.plugins import AsyncRunnablePlugin, action from platypush.plugins import AsyncRunnablePlugin, action
@ -165,6 +171,7 @@ class MatrixClient(AsyncClient):
self._autotrust_users_whitelist = autotrust_users_whitelist or set() self._autotrust_users_whitelist = autotrust_users_whitelist or set()
self._first_sync_performed = asyncio.Event() self._first_sync_performed = asyncio.Event()
self._last_batches_by_room = {} self._last_batches_by_room = {}
self._typing_users_by_room = {}
self._encrypted_attachments_keystore_path = os.path.join( self._encrypted_attachments_keystore_path = os.path.join(
store_path, 'attachment_keys.json' store_path, 'attachment_keys.json'
@ -417,6 +424,9 @@ class MatrixClient(AsyncClient):
self.add_to_device_callback(self._on_key_verification_key, KeyVerificationKey) # type: ignore self.add_to_device_callback(self._on_key_verification_key, KeyVerificationKey) # type: ignore
self.add_to_device_callback(self._on_key_verification_mac, KeyVerificationMac) # type: ignore self.add_to_device_callback(self._on_key_verification_mac, KeyVerificationMac) # type: ignore
self.add_to_device_callback(self._on_key_verification_accept, KeyVerificationAccept) # type: ignore self.add_to_device_callback(self._on_key_verification_accept, KeyVerificationAccept) # type: ignore
self.add_ephemeral_callback(self._on_typing, TypingNoticeEvent) # type: ignore
self.add_ephemeral_callback(self._on_receipt, ReceiptEvent) # type: ignore
self.add_presence_callback(self._on_presence, PresenceEvent) # type: ignore
if self._autojoin_on_invite: if self._autojoin_on_invite:
self.add_event_callback(self._autojoin_room_callback, InviteEvent) # type: ignore self.add_event_callback(self._autojoin_room_callback, InviteEvent) # type: ignore
@ -498,9 +508,9 @@ class MatrixClient(AsyncClient):
return response return response
async def _event_base_args( async def _event_base_args(
self, room: MatrixRoom, event: Event | None = None self, room: MatrixRoom | None, event: Event | None = None
) -> dict: ) -> dict:
sender_id = event.sender if event else None sender_id = getattr(event, 'sender', None)
sender = ( sender = (
await self.get_profile(sender_id) if sender_id else None # type: ignore await self.get_profile(sender_id) if sender_id else None # type: ignore
) )
@ -510,9 +520,15 @@ class MatrixClient(AsyncClient):
'sender_id': sender_id, 'sender_id': sender_id,
'sender_display_name': sender.displayname if sender else None, 'sender_display_name': sender.displayname if sender else None,
'sender_avatar_url': sender.avatar_url if sender else None, 'sender_avatar_url': sender.avatar_url if sender else None,
'room_id': room.room_id, **(
'room_name': room.name, {
'room_topic': room.topic, 'room_id': room.room_id,
'room_name': room.name,
'room_topic': room.topic,
}
if room
else {}
),
'server_timestamp': ( 'server_timestamp': (
datetime.datetime.fromtimestamp(event.server_timestamp / 1000) datetime.datetime.fromtimestamp(event.server_timestamp / 1000)
if event and getattr(event, 'server_timestamp', None) if event and getattr(event, 'server_timestamp', None)
@ -738,16 +754,72 @@ class MatrixClient(AsyncClient):
await self.room_leave(room.room_id) await self.room_leave(room.room_id)
await self.join(event.replacement_room) await self.join(event.replacement_room)
async def _on_typing(self, room: MatrixRoom, event: TypingNoticeEvent):
users = set(event.users)
typing_users = self._typing_users_by_room.get(room.room_id, set())
start_typing_users = users.difference(typing_users)
stop_typing_users = typing_users.difference(users)
for user in start_typing_users:
event.sender = user # type: ignore
get_bus().post(
MatrixRoomTypingStartEvent(
**(await self._event_base_args(room, event)), # type: ignore
sender=user,
)
)
for user in stop_typing_users:
event.sender = user # type: ignore
get_bus().post(
MatrixRoomTypingStopEvent(
**(await self._event_base_args(room, event)), # type: ignore
)
)
self._typing_users_by_room[room.room_id] = users
async def _on_receipt(self, room: MatrixRoom, event: ReceiptEvent):
if self._first_sync_performed.is_set():
for receipt in event.receipts:
event.sender = receipt.user_id # type: ignore
get_bus().post(
MatrixRoomSeenReceiptEvent(
**(await self._event_base_args(room, event)), # type: ignore
)
)
async def _on_presence(self, event: PresenceEvent):
if self._first_sync_performed.is_set():
last_active = (
(
datetime.datetime.now()
- datetime.timedelta(seconds=event.last_active_ago / 1000)
)
if event.last_active_ago
else None
)
event.sender = event.user_id # type: ignore
get_bus().post(
MatrixUserPresenceEvent(
**(await self._event_base_args(None, event)), # type: ignore
is_active=event.currently_active or False,
last_active=last_active,
)
)
async def _on_unknown_encrypted_event( async def _on_unknown_encrypted_event(
self, room: MatrixRoom, event: UnknownEncryptedEvent | MegolmEvent self, room: MatrixRoom, event: UnknownEncryptedEvent | MegolmEvent
): ):
body = getattr(event, 'ciphertext', '') if self._first_sync_performed.is_set():
get_bus().post( body = getattr(event, 'ciphertext', '')
MatrixEncryptedMessageEvent( get_bus().post(
body=body, MatrixEncryptedMessageEvent(
**(await self._event_base_args(room, event)), body=body,
**(await self._event_base_args(room, event)),
)
) )
)
async def _on_unknown_event(self, room: MatrixRoom, event: UnknownEvent): async def _on_unknown_event(self, room: MatrixRoom, event: UnknownEvent):
evt = None evt = None
@ -875,6 +947,14 @@ class MatrixPlugin(AsyncRunnablePlugin):
* :class:`platypush.message.event.matrix.MatrixEncryptedMessageEvent`: * :class:`platypush.message.event.matrix.MatrixEncryptedMessageEvent`:
when a message is received but the client doesn't have the E2E keys when a message is received but the client doesn't have the E2E keys
to decrypt it, or encryption has not been enabled. to decrypt it, or encryption has not been enabled.
* :class:`platypush.message.event.matrix.MatrixRoomTypingStartEvent`:
when a user in a room starts typing.
* :class:`platypush.message.event.matrix.MatrixRoomTypingStopEvent`:
when a user in a room stops typing.
* :class:`platypush.message.event.matrix.MatrixRoomSeenReceiptEvent`:
when the last message seen by a user in a room is updated.
* :class:`platypush.message.event.matrix.MatrixUserPresenceEvent`:
when a user comes online or goes offline.
""" """
@ -1001,6 +1081,8 @@ class MatrixPlugin(AsyncRunnablePlugin):
pass pass
except Exception as e: except Exception as e:
self.logger.exception(e) self.logger.exception(e)
self.logger.info('Waiting 10 seconds before reconnecting')
await asyncio.sleep(10)
finally: finally:
try: try:
await self.client.close() await self.client.close()
@ -1185,7 +1267,7 @@ class MatrixPlugin(AsyncRunnablePlugin):
first returned message will be the oldest and messages will be first returned message will be the oldest and messages will be
returned in ascending order. returned in ascending order.
:param limit: Maximum number of messages to be returned (default: 10). :param limit: Maximum number of messages to be returned (default: 10).
# :return: .. schema:: matrix.MatrixMessagesResponseSchema :return: .. schema:: matrix.MatrixMessagesResponseSchema
""" """
response = self._loop_execute( response = self._loop_execute(
self.client.room_messages( self.client.room_messages(
@ -1570,5 +1652,23 @@ class MatrixPlugin(AsyncRunnablePlugin):
""" """
self._loop_execute(self.client.room_forget(room_id)) self._loop_execute(self.client.room_forget(room_id))
@action
def set_display_name(self, display_name: str):
"""
Set/change the display name for the current user.
:param display_name: New display name.
"""
self._loop_execute(self.client.set_displayname(display_name))
@action
def set_avatar(self, url: str):
"""
Set/change the avatar URL for the current user.
:param url: New avatar URL. It must be a valid ``mxc://`` link.
"""
self._loop_execute(self.client.set_avatar(url))
# vim:sw=4:ts=4:et: # vim:sw=4:ts=4:et:

View file

@ -1,6 +1,7 @@
manifest: manifest:
events: events:
platypush.message.event.matrix.MatrixMessageEvent: when a message is received. platypush.message.event.matrix.MatrixMessageEvent: when a message is
received.
platypush.message.event.matrix.MatrixMessageImageEvent: when a message platypush.message.event.matrix.MatrixMessageImageEvent: when a message
containing an image is received. containing an image is received.
platypush.message.event.matrix.MatrixMessageAudioEvent: when a message platypush.message.event.matrix.MatrixMessageAudioEvent: when a message
@ -11,17 +12,33 @@ manifest:
containing a generic file is received. containing a generic file is received.
platypush.message.event.matrix.MatrixSyncEvent: when the startup platypush.message.event.matrix.MatrixSyncEvent: when the startup
synchronization has been completed and the plugin is ready to use. synchronization has been completed and the plugin is ready to use.
platypush.message.event.matrix.MatrixRoomCreatedEvent: when a room is created. platypush.message.event.matrix.MatrixRoomCreatedEvent: when a room is
platypush.message.event.matrix.MatrixRoomJoinEvent: when a user joins a room. created.
platypush.message.event.matrix.MatrixRoomLeaveEvent: when a user leaves a room. platypush.message.event.matrix.MatrixRoomJoinEvent: when a user joins a
platypush.message.event.matrix.MatrixRoomInviteEvent: when the user is invited to a room. room.
platypush.message.event.matrix.MatrixRoomTopicChangedEvent: when the topic/title of a room changes. platypush.message.event.matrix.MatrixRoomLeaveEvent: when a user leaves a
platypush.message.event.matrix.MatrixCallInviteEvent: when the user is invited to a call. room.
platypush.message.event.matrix.MatrixCallAnswerEvent: when a called user wishes to pick the call. platypush.message.event.matrix.MatrixRoomInviteEvent: when the user is
platypush.message.event.matrix.MatrixCallHangupEvent: when a called user exits the call. invited to a room.
platypush.message.event.matrix.MatrixEncryptedMessageEvent: | platypush.message.event.matrix.MatrixRoomTopicChangedEvent: when the
when a message is received but the client doesn't topic/title of a room changes.
have the E2E keys to decrypt it, or encryption has not been enabled. platypush.message.event.matrix.MatrixCallInviteEvent: when the user is
invited to a call.
platypush.message.event.matrix.MatrixCallAnswerEvent: when a called user
wishes to pick the call.
platypush.message.event.matrix.MatrixCallHangupEvent: when a called user
exits the call.
platypush.message.event.matrix.MatrixEncryptedMessageEvent: when a message
is received but the client doesn't have the E2E keys to decrypt it, or
encryption has not been enabled.
platypush.message.event.matrix.MatrixRoomTypingStartEvent: when a user in a
room starts typing.
platypush.message.event.matrix.MatrixRoomTypingStopEvent: when a user in a
room stops typing.
platypush.message.event.matrix.MatrixRoomSeenReceiptEvent: when the last
message seen by a user in a room is updated.
platypush.message.event.matrix.MatrixUserPresenceEvent: when a user comes
online or goes offline.
apt: apt:
- libolm-devel - libolm-devel
pacman: pacman: