forked from platypush/platypush
Implemented last Matrix integration features.
- Added presence, typing and seen receipt events. - Added set display_name and avatar methods.
This commit is contained in:
parent
e479ca7e3e
commit
c417d2f692
3 changed files with 172 additions and 24 deletions
|
@ -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)
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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:
|
||||||
|
|
Loading…
Reference in a new issue