async def _send_reminder(self, reminder: ReminderInfo) -> None: if len(reminder.users) == 0: self.log.debug( f"Cancelling reminder {reminder}, no users left to remind") return wait = (reminder.date - datetime.now(tz=pytz.UTC)).total_seconds() if wait > 0: self.log.debug(f"Waiting {wait} seconds to send {reminder}") await asyncio.sleep(wait) else: self.log.debug(f"Sending {reminder} immediately") users = " ".join(reminder.users) users_html = " ".join( f"<a href='https://matrix.to/#/{user_id}'>{user_id}</a>" for user_id in reminder.users) content = TextMessageEventContent( msgtype=MessageType.TEXT, body=f"{users}: {reminder.message}", format=Format.HTML, formatted_body=f"{users_html}: {escape(reminder.message)}") content["xyz.maubot.reminder"] = { "id": reminder.id, "message": reminder.message, "targets": list(reminder.users), "reply_to": reminder.reply_to, } if reminder.reply_to: content.set_reply(await self.client.get_event(reminder.room_id, reminder.reply_to)) await self.client.send_message(reminder.room_id, content)
async def handle_signal_message(self, sender: 'p.Puppet', message: MessageData) -> None: if (sender.uuid, message.timestamp) in self._msgts_dedup: self.log.debug( f"Ignoring message {message.timestamp} by {sender.uuid}" " as it was already handled (message.timestamp in dedup queue)" ) return old_message = await DBMessage.get_by_signal_id(sender.uuid, message.timestamp, self.chat_id, self.receiver) if old_message is not None: self.log.debug( f"Ignoring message {message.timestamp} by {sender.uuid}" " as it was already handled (message.id found in database)") return self.log.debug( f"Started handling message {message.timestamp} by {sender.uuid}") self.log.trace(f"Message content: {message}") self._msgts_dedup.appendleft((sender.uuid, message.timestamp)) intent = sender.intent_for(self) await intent.set_typing(self.mxid, False) event_id = None reply_to = await self._find_quote_event_id(message.quote) for attachment in message.all_attachments: content = await self._handle_signal_attachment(intent, attachment) if content: if reply_to and not message.body: # If there's no text, set the first image as the reply content.set_reply(reply_to) reply_to = None event_id = await self._send_message( intent, content, timestamp=message.timestamp) if message.body: content = TextMessageEventContent(msgtype=MessageType.TEXT, body=message.body) if reply_to: content.set_reply(reply_to) event_id = await self._send_message(intent, content, timestamp=message.timestamp) if event_id: msg = DBMessage(mxid=event_id, mx_room=self.mxid, sender=sender.uuid, timestamp=message.timestamp, signal_chat_id=self.chat_id, signal_receiver=self.receiver) await msg.insert() await self._send_delivery_receipt(event_id) self.log.debug( f"Handled Signal message {message.timestamp} -> {event_id}") else: self.log.debug(f"Didn't get event ID for {message.timestamp}")
async def _add_facebook_reply(self, content: TextMessageEventContent, reply: str) -> None: if reply: message = DBMessage.get_by_fbid(reply, self.fb_receiver) if message: evt = await self.main_intent.get_event(message.mx_room, message.mxid) if evt: if isinstance(evt.content, TextMessageEventContent): evt.content.trim_reply_fallback() content.set_reply(evt)
async def _handle_instagram_reel_share( self, source: 'u.User', intent: IntentAPI, item: ThreadItem) -> Optional[EventID]: media = item.reel_share.media prefix_html = None if item.reel_share.type == ReelShareType.REPLY: if item.reel_share.reel_owner_id == source.igpk: prefix = "Replied to your story" else: username = media.user.username prefix = f"Sent @{username}'s story" user_link = f'<a href="https://www.instagram.com/{username}/">@{username}</a>' prefix_html = f"Sent {user_link}'s story" elif item.reel_share.type == ReelShareType.REACTION: prefix = "Reacted to your story" else: self.log.debug( f"Unsupported reel share type {item.reel_share.type}") return None prefix_content = TextMessageEventContent(msgtype=MessageType.NOTICE, body=prefix) if prefix_html: prefix_content.format = Format.HTML prefix_content.formatted_body = prefix_html content = TextMessageEventContent(msgtype=MessageType.TEXT, body=item.reel_share.text) await self._send_message(intent, prefix_content, timestamp=item.timestamp // 1000) if isinstance(media, ExpiredMediaItem): # TODO send message about expired story pass else: fake_item_id = f"fi.mau.instagram.reel_share.{item.user_id}.{media.pk}" existing = await DBMessage.get_by_item_id(fake_item_id, self.receiver) if existing: # If the user already reacted or replied to the same reel share item, # use a Matrix reply instead of reposting the image. content.set_reply(existing.mxid) else: media_event_id = await self._handle_instagram_media( source, intent, item) await DBMessage(mxid=media_event_id, mx_room=self.mxid, item_id=fake_item_id, receiver=self.receiver, sender=media.user.pk).insert() return await self._send_message(intent, content, timestamp=item.timestamp // 1000)
async def _add_reply_header(source: 'AbstractUser', content: TextMessageEventContent, evt: Message, main_intent: IntentAPI): space = (evt.to_id.channel_id if isinstance(evt, Message) and isinstance(evt.to_id, PeerChannel) else source.tgid) msg = DBMessage.get_one_by_tgid(TelegramID(evt.reply_to_msg_id), space) if not msg: return content.relates_to = RelatesTo(rel_type=RelationType.REFERENCE, event_id=msg.mxid) try: event: MessageEvent = await main_intent.get_event(msg.mx_room, msg.mxid) if isinstance(event.content, TextMessageEventContent): event.content.trim_reply_fallback() content.set_reply(event) except MatrixRequestError: pass
async def _add_reply_header(source: 'AbstractUser', content: TextMessageEventContent, evt: Message, main_intent: IntentAPI): space = (evt.peer_id.channel_id if isinstance(evt, Message) and isinstance(evt.peer_id, PeerChannel) else source.tgid) msg = DBMessage.get_one_by_tgid(TelegramID(evt.reply_to.reply_to_msg_id), space) if not msg: return content.relates_to = RelatesTo(rel_type=RelationType.REPLY, event_id=msg.mxid) try: event: MessageEvent = await main_intent.get_event(msg.mx_room, msg.mxid) if isinstance(event.content, TextMessageEventContent): event.content.trim_reply_fallback() puppet = await pu.Puppet.get_by_mxid(event.sender, create=False) content.set_reply(event, displayname=puppet.displayname if puppet else event.sender) except MatrixRequestError: log.exception("Failed to get event to add reply fallback")
async def _add_reply_header(source: au.AbstractUser, content: TextMessageEventContent, evt: Message, main_intent: IntentAPI) -> None: space = (evt.peer_id.channel_id if isinstance(evt, Message) and isinstance(evt.peer_id, PeerChannel) else source.tgid) msg = await DBMessage.get_one_by_tgid( TelegramID(evt.reply_to.reply_to_msg_id), space) if not msg: return try: event = await main_intent.get_event(msg.mx_room, msg.mxid) if event.type == EventType.ROOM_ENCRYPTED and source.bridge.matrix.e2ee: event = await source.bridge.matrix.e2ee.decrypt(event) if isinstance(event.content, TextMessageEventContent): event.content.trim_reply_fallback() puppet = await pu.Puppet.get_by_mxid(event.sender, create=False) content.set_reply( event, displayname=puppet.displayname if puppet else event.sender) except Exception: log.exception("Failed to get event to add reply fallback") content.set_reply(msg.mxid)
async def handle_signal_message(self, source: 'u.User', sender: 'p.Puppet', message: MessageData) -> None: if (sender.address, message.timestamp) in self._msgts_dedup: self.log.debug( f"Ignoring message {message.timestamp} by {sender.uuid}" " as it was already handled (message.timestamp in dedup queue)" ) await self.signal.send_receipt(source.username, sender.address, timestamps=[message.timestamp]) return old_message = await DBMessage.get_by_signal_id(sender.address, message.timestamp, self.chat_id, self.receiver) if old_message is not None: self.log.debug( f"Ignoring message {message.timestamp} by {sender.uuid}" " as it was already handled (message.id found in database)") await self.signal.send_receipt(source.username, sender.address, timestamps=[message.timestamp]) return self.log.debug( f"Started handling message {message.timestamp} by {sender.uuid}") self.log.trace(f"Message content: {message}") self._msgts_dedup.appendleft((sender.address, message.timestamp)) intent = sender.intent_for(self) await intent.set_typing(self.mxid, False) event_id = None reply_to = await self._find_quote_event_id(message.quote) if message.sticker: if not message.sticker.attachment.incoming_filename: self.log.debug( "Downloading sticker from signal, as no incoming filename was defined: %s", message.sticker.attachment) try: async with StickersClient() as client: sticker_data = await client.download_sticker( message.sticker.sticker_id, message.sticker.pack_id, message.sticker.pack_key) path = os.path.join( self.config["signal.outgoing_attachment_dir"], f"{message.sticker.pack_id}_{message.sticker.sticker_id}" ) with open(path, "wb") as file: file.write(sticker_data) message.sticker.attachment.incoming_filename = path except Exception as ex: self.log.warning("Failed to download sticker: %s", ex) if message.sticker.attachment.incoming_filename: content = await self._handle_signal_attachment( intent, message.sticker.attachment) if reply_to: content.set_reply(reply_to) reply_to = None event_id = await self._send_message( intent, content, timestamp=message.timestamp, event_type=EventType.STICKER) for attachment in message.attachments: if not attachment.incoming_filename: self.log.warning( "Failed to bridge attachment, no incoming filename: %s", attachment) continue content = await self._handle_signal_attachment(intent, attachment) if reply_to and not message.body: # If there's no text, set the first image as the reply content.set_reply(reply_to) reply_to = None event_id = await self._send_message(intent, content, timestamp=message.timestamp) if message.body: content = TextMessageEventContent(msgtype=MessageType.TEXT, body=message.body) if reply_to: content.set_reply(reply_to) event_id = await self._send_message(intent, content, timestamp=message.timestamp) if event_id: msg = DBMessage(mxid=event_id, mx_room=self.mxid, sender=sender.address, timestamp=message.timestamp, signal_chat_id=self.chat_id, signal_receiver=self.receiver) await msg.insert() await self.signal.send_receipt(source.username, sender.address, timestamps=[message.timestamp]) await self._send_delivery_receipt(event_id) self.log.debug( f"Handled Signal message {message.timestamp} -> {event_id}") else: self.log.debug(f"Didn't get event ID for {message.timestamp}")