Skip to content

Commit

Permalink
Add support for custom message bridging status events
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Jun 3, 2022
1 parent 9cd6385 commit e677c6d
Show file tree
Hide file tree
Showing 5 changed files with 66 additions and 19 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
* Removed web login interface and added support for logging in inside Matrix.
* The provisioning API is still available, but it has moved from `/login/api`
to `/_matrix/provision/v1`.
* Added error messages and optionally custom status events to detect when
a message fails to bridge.

# v0.3.2 (2022-04-19)

Expand Down
2 changes: 2 additions & 0 deletions mautrix_googlechat/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ def do_update(self, helper: ConfigUpdateHelper) -> None:
copy("bridge.encryption.key_sharing.require_cross_signing")
copy("bridge.encryption.key_sharing.require_verification")
copy("bridge.delivery_receipts")
copy("bridge.delivery_error_reports")
copy("bridge.message_status_events")
copy("bridge.federate_rooms")
copy("bridge.backfill.invite_own_puppet")
copy("bridge.backfill.initial_thread_limit")
Expand Down
4 changes: 4 additions & 0 deletions mautrix_googlechat/example-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,10 @@ bridge:
# Whether or not the bridge should send a read receipt from the bridge bot when a message has
# been sent to Google Chat.
delivery_receipts: false
# Whether or not delivery errors should be reported as messages in the Matrix room.
delivery_error_reports: true
# Whether the bridge should send the message status as a custom com.beeper.message_send_status event.
message_status_events: false
# Whether or not created rooms should have federation enabled.
# If false, created portal rooms will never be federated.
federate_rooms: true
Expand Down
75 changes: 57 additions & 18 deletions mautrix_googlechat/portal.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
from mautrix.bridge import BasePortal, NotificationDisabler, async_getter_lock
from mautrix.errors import IntentError, MatrixError, MForbidden
from mautrix.types import (
BeeperMessageStatusEventContent,
ContentURI,
EncryptionAlgorithm,
EventID,
Expand All @@ -38,8 +39,11 @@
MediaMessageEventContent,
Membership,
MessageEventContent,
MessageStatusReason,
MessageType,
PowerLevelStateEventContent,
RelatesTo,
RelationType,
RoomID,
TextMessageEventContent,
UserID,
Expand Down Expand Up @@ -704,7 +708,7 @@ async def handle_matrix_reaction(
try:
await sender.client.react(target.gc_chat, target.gc_parent_id, target.gcid, reaction)
except Exception as e:
self._rec_error(sender, e, reaction_id, EventType.REACTION)
await self._rec_error(sender, e, reaction_id, EventType.REACTION)
else:
await self._rec_success(sender, reaction_id, EventType.REACTION)

Expand All @@ -719,7 +723,7 @@ async def handle_matrix_redaction(
target.gc_chat, target.gc_parent_id, target.gcid
)
except Exception as e:
self._rec_error(sender, e, redaction_id, EventType.ROOM_REDACTION)
await self._rec_error(sender, e, redaction_id, EventType.ROOM_REDACTION)
else:
await self._rec_success(sender, redaction_id, EventType.ROOM_REDACTION)
return
Expand All @@ -739,7 +743,7 @@ async def handle_matrix_redaction(
remove=True,
)
except Exception as e:
self._rec_error(sender, e, redaction_id, EventType.ROOM_REDACTION)
await self._rec_error(sender, e, redaction_id, EventType.ROOM_REDACTION)
else:
await self._rec_success(sender, redaction_id, EventType.ROOM_REDACTION)
return
Expand Down Expand Up @@ -784,7 +788,7 @@ async def handle_matrix_edit(
)
self._edit_dedup[target.gcid] = resp.message.last_edit_time
except Exception as e:
self._rec_error(sender, e, event_id, EventType.ROOM_MESSAGE, message.msgtype)
await self._rec_error(sender, e, event_id, EventType.ROOM_MESSAGE, message.msgtype)
else:
await self._rec_success(sender, event_id, EventType.ROOM_MESSAGE, message.msgtype)

Expand Down Expand Up @@ -812,9 +816,9 @@ async def handle_matrix_message(
elif message.msgtype.is_media:
resp = await self._handle_matrix_media(sender, message, thread_id, local_id)
else:
raise ValueError(f"Unsupported msgtype {message.msgtype}")
raise NotImplementedError(f"Unsupported msgtype {message.msgtype}")
except Exception as e:
self._rec_error(sender, e, event_id, EventType.ROOM_MESSAGE, message.msgtype)
await self._rec_error(sender, e, event_id, EventType.ROOM_MESSAGE, message.msgtype)
else:
self.log.debug(f"Handled Matrix message {event_id} -> {local_id} -> {resp.gcid}")
await self._rec_success(sender, event_id, EventType.ROOM_MESSAGE, message.msgtype)
Expand Down Expand Up @@ -848,10 +852,11 @@ def _rec_dropped(
room_id=self.mxid,
event_type=evt_type,
message_type=msgtype,
error=Exception(reason),
error=reason,
)
asyncio.create_task(self._send_message_status(event_id, NotImplementedError(reason)))

def _rec_error(
async def _rec_error(
self,
user: u.User,
err: Exception,
Expand All @@ -860,17 +865,14 @@ def _rec_error(
msgtype: MessageType | None = None,
edit: bool = False,
) -> None:
if evt_type == EventType.ROOM_MESSAGE:
if edit:
self.log.exception(f"Failed handling Matrix edit {event_id}", exc_info=err)
else:
self.log.exception(f"Failed handling Matrix message {event_id}", exc_info=err)
type_name = "message"
if evt_type == EventType.ROOM_MESSAGE and edit:
type_name = "edit"
elif evt_type == EventType.ROOM_REDACTION:
self.log.exception(f"Failed handling Matrix redaction {event_id}", exc_info=err)
type_name = "redaction"
elif evt_type == EventType.REACTION:
self.log.exception(f"Failed handling Matrix reaction {event_id}", exc_info=err)
else:
self.log.exception(f"Failed handling unknown Matrix event {event_id}", exc_info=err)
type_name = "reaction"
self.log.exception(f"Failed handling Matrix {type_name} {event_id}", exc_info=err)
user.send_remote_checkpoint(
status=MessageSendCheckpointStatus.PERM_FAILURE,
event_id=event_id,
Expand All @@ -879,6 +881,15 @@ def _rec_error(
message_type=msgtype,
error=err,
)
asyncio.create_task(self._send_message_status(event_id, err))
if self.config["bridge.delivery_error_reports"]:
await self._send_message(
self.main_intent,
TextMessageEventContent(
msgtype=MessageType.NOTICE,
body=f"\u26a0 Your {type_name} may not have been bridged: {err}",
),
)

async def _rec_success(
self,
Expand All @@ -887,14 +898,42 @@ async def _rec_success(
evt_type: EventType,
msgtype: MessageType | None = None,
) -> None:
_ = user.send_remote_checkpoint(
user.send_remote_checkpoint(
status=MessageSendCheckpointStatus.SUCCESS,
event_id=event_id,
room_id=self.mxid,
event_type=evt_type,
message_type=msgtype,
)
await self._send_delivery_receipt(event_id)
asyncio.create_task(self._send_message_status(event_id, err=None))

async def _send_message_status(self, event_id: EventID, err: Exception | None) -> None:
if not self.config["bridge.message_status_events"]:
return
intent = self.az.intent if self.encrypted else self.main_intent
status = BeeperMessageStatusEventContent(
network=self.bridge_info_state_key,
relates_to=RelatesTo(
rel_type=RelationType.REFERENCE,
event_id=event_id,
),
success=err is None,
)
if err:
status.reason = MessageStatusReason.GENERIC_ERROR
status.error = str(err)
status.is_certain = True
status.can_retry = True
if isinstance(err, NotImplementedError):
status.can_retry = False
status.reason = MessageStatusReason.UNSUPPORTED

await intent.send_message_event(
room_id=self.mxid,
event_type=EventType.BEEPER_MESSAGE_STATUS,
content=status,
)

@staticmethod
def _get_send_response(
Expand Down
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@ ruamel.yaml>=0.15.94,<0.18
commonmark>=0.8,<0.10
python-magic>=0.4,<0.5
protobuf>=3,<4
mautrix>=0.16.3,<0.17
mautrix>=0.16.6,<0.17
setuptools

0 comments on commit e677c6d

Please sign in to comment.