From 6727da086ab305a5103874949e85dbb6bdb2f6d4 Mon Sep 17 00:00:00 2001 From: nicoco Date: Sat, 10 Feb 2024 07:10:42 +0100 Subject: [PATCH] feat: bridge XMPP mentions as proper whatsapp mentions This has outgoing mentions of participant usernames in MUCs be correctly represented as mentions in official WhatsApp clients. --- slidge_whatsapp/event.go | 1 + slidge_whatsapp/group.py | 24 +++++++++++++++++------- slidge_whatsapp/session.go | 11 +++++++++++ slidge_whatsapp/session.py | 11 ++++++++--- tests/test_whatsapp.py | 17 ++++++++++------- 5 files changed, 47 insertions(+), 17 deletions(-) diff --git a/slidge_whatsapp/event.go b/slidge_whatsapp/event.go index c115f66..80e72d1 100644 --- a/slidge_whatsapp/event.go +++ b/slidge_whatsapp/event.go @@ -148,6 +148,7 @@ type Message struct { ReplyBody string // The full body of the message this message is in reply to, if any. Attachments []Attachment // The list of file (image, video, etc.) attachments contained in this message. Preview Preview // A short description for the URL provided in the message body, if any. + MentionJIDs []string // A list of JIDs mentioned in this message, if any. } // A Attachment represents additional binary data (e.g. images, videos, documents) provided alongside diff --git a/slidge_whatsapp/group.py b/slidge_whatsapp/group.py index 7517052..d152722 100644 --- a/slidge_whatsapp/group.py +++ b/slidge_whatsapp/group.py @@ -3,6 +3,7 @@ from datetime import datetime, timezone from typing import TYPE_CHECKING, Optional from slidge.group import LegacyBookmarks, LegacyMUC, LegacyParticipant, MucType +from slidge.util.types import Mention from slixmpp.exceptions import XMPPError from .generated import whatsapp @@ -96,7 +97,7 @@ class MUC(LegacyMUC[str, str, Participant, str]): participant.role = "moderator" def replace_mentions(self, t: str): - return replace_mentions( + return replace_whatsapp_mentions( t, participants=( { @@ -168,10 +169,19 @@ class Bookmarks(LegacyBookmarks[str, MUC]): return whatsapp_group_id -def replace_mentions(t: str, participants: dict[str, str]): - def match(m: re.Match): - mat = m.group(0) - sub = participants.get(mat.replace("@", "+"), mat) - return sub +def replace_xmpp_mentions(text: str, mentions: list[Mention]): + offset: int = 0 + result: str = "" + for m in mentions: + legacy_id = "@" + m.contact.legacy_id[: m.contact.legacy_id.find("@")] + result = result + text[offset : m.start] + legacy_id + offset = m.end + return result + text[offset:] if offset > 0 else text - return re.sub(r"@\d+", match, t) + +def replace_whatsapp_mentions(text: str, participants: dict[str, str]): + def match(m: re.Match): + group = m.group(0) + return participants.get(group.replace("@", "+"), group) + + return re.sub(r"@\d+", match, text) diff --git a/slidge_whatsapp/session.go b/slidge_whatsapp/session.go index 549c127..3772986 100644 --- a/slidge_whatsapp/session.go +++ b/slidge_whatsapp/session.go @@ -303,6 +303,17 @@ func (s *Session) getMessagePayload(message Message) *proto.Message { } } + // Attach any inline mentions extended metadata. + if len(message.MentionJIDs) > 0 { + if payload == nil { + payload = &proto.Message{ExtendedTextMessage: &proto.ExtendedTextMessage{Text: &message.Body}} + } + if payload.ExtendedTextMessage.ContextInfo == nil { + payload.ExtendedTextMessage.ContextInfo = &proto.ContextInfo{} + } + payload.ExtendedTextMessage.ContextInfo.MentionedJid = message.MentionJIDs + } + if payload == nil { payload = &proto.Message{Conversation: &message.Body} } diff --git a/slidge_whatsapp/session.py b/slidge_whatsapp/session.py index 1d65afe..74f1050 100644 --- a/slidge_whatsapp/session.py +++ b/slidge_whatsapp/session.py @@ -17,6 +17,7 @@ from slidge.contact.roster import ContactIsUser from slidge.util import is_valid_phone_number from slidge.util.types import ( LegacyAttachment, + Mention, MessageReference, PseudoPresenceShow, ResourceDict, @@ -26,7 +27,7 @@ from . import config from .contact import Contact, Roster from .gateway import Gateway from .generated import go, whatsapp -from .group import MUC, Bookmarks +from .group import MUC, Bookmarks, replace_xmpp_mentions from .util import get_bytes_temp MESSAGE_PAIR_SUCCESS = ( @@ -244,7 +245,7 @@ class Session(BaseSession[str, Recipient]): reply_to_msg_id: Optional[str] = None, reply_to_fallback_text: Optional[str] = None, reply_to=None, - mentions=None, + mentions: Optional[list[Mention]] = None, **_, ): """ @@ -253,7 +254,11 @@ class Session(BaseSession[str, Recipient]): message_id = self.whatsapp.GenerateMessageID() message_preview = await self.__get_preview(text) or whatsapp.Preview() message = whatsapp.Message( - ID=message_id, JID=chat.legacy_id, Body=text, Preview=message_preview + ID=message_id, + JID=chat.legacy_id, + Body=replace_xmpp_mentions(text, mentions) if mentions else text, + Preview=message_preview, + MentionJIDs=go.Slice_string([m.contact.legacy_id for m in mentions or []]), ) set_reply_to(chat, message, reply_to_msg_id, reply_to_fallback_text, reply_to) self.whatsapp.SendMessage(message) diff --git a/tests/test_whatsapp.py b/tests/test_whatsapp.py index b84a8d2..1862487 100644 --- a/tests/test_whatsapp.py +++ b/tests/test_whatsapp.py @@ -1,26 +1,29 @@ -from slidge_whatsapp.group import replace_mentions +from slidge_whatsapp.group import replace_whatsapp_mentions -def test_replace_mentions(): +def test_replace_whatsapp_mentions(): text = "Hayo @1234, it's cool in here in with @5678!! @123333" assert ( - replace_mentions( + replace_whatsapp_mentions( text, {"+1234": "bibi", "+5678": "baba"}, ) == "Hayo bibi, it's cool in here in with baba!! @123333" ) - assert replace_mentions(text, {}) == text + assert replace_whatsapp_mentions(text, {}) == text - assert replace_mentions(text, {"+123333": "prout"}) == text.replace( + assert replace_whatsapp_mentions(text, {"+123333": "prout"}) == text.replace( "@123333", "prout" ) - assert replace_mentions("+1234", {"+1234": "bibi", "+5678": "baba"}) == "+1234" + assert ( + replace_whatsapp_mentions("+1234", {"+1234": "bibi", "+5678": "baba"}) + == "+1234" + ) assert ( - replace_mentions("@1234@1234@123", {"+1234": "bibi", "+5678": "baba"}) + replace_whatsapp_mentions("@1234@1234@123", {"+1234": "bibi", "+5678": "baba"}) == "bibibibi@123" )