瀏覽代碼

Fix handling reactions

Tulir Asokan 2 年之前
父節點
當前提交
98a476fefb
共有 5 個文件被更改,包括 76 次插入19 次删除
  1. 20 10
      mauigpapi/mqtt/conn.py
  2. 1 0
      mauigpapi/types/__init__.py
  3. 11 4
      mauigpapi/types/thread_item.py
  4. 37 4
      mautrix_instagram/portal.py
  5. 7 1
      mautrix_instagram/user.py

+ 20 - 10
mauigpapi/mqtt/conn.py

@@ -47,9 +47,11 @@ from ..types import (
     LiveVideoCommentPayload,
     MessageSyncEvent,
     MessageSyncMessage,
+    Operation,
     PubsubEvent,
     PubsubPayload,
     ReactionStatus,
+    ReactionType,
     RealtimeDirectEvent,
     RealtimeZeroProvisionPayload,
     ThreadAction,
@@ -314,12 +316,9 @@ class AndroidMQTT:
                 additional["has_seen"] = int(rest[1])
             elif subitem_key == "items":
                 additional["item_id"] = rest[1]
-                # TODO wtf is this?
-                #      it has something to do with reactions
-                if len(rest) > 4:
-                    additional[rest[2]] = {
-                        rest[3]: rest[4],
-                    }
+                if len(rest) > 4 and rest[2] == "reactions":
+                    additional["reaction_type"] = ReactionType(rest[3])
+                    additional["reaction_user_id"] = int(rest[4])
             elif subitem_key in "admin_user_ids":
                 additional["admin_user_id"] = int(rest[1])
             elif subitem_key == "activity_indicator_id":
@@ -335,10 +334,21 @@ class AndroidMQTT:
                 **self._parse_direct_thread_path(part.path),
             }
             try:
-                raw_message = {
-                    **raw_message,
-                    **json.loads(part.value),
-                }
+                json_value = json.loads(part.value)
+                if "reaction_type" in raw_message:
+                    self.log.trace("Treating %s as new reaction data", json_value)
+                    raw_message["new_reaction"] = json_value
+                    json_value["sender_id"] = raw_message.pop("reaction_user_id")
+                    json_value["type"] = raw_message.pop("reaction_type")
+                    json_value["client_context"] = parsed_item.mutation_token
+                    if part.op == Operation.REMOVE:
+                        json_value["emoji"] = None
+                        json_value["timestamp"] = None
+                else:
+                    raw_message = {
+                        **raw_message,
+                        **json_value,
+                    }
             except (json.JSONDecodeError, TypeError):
                 raw_message["value"] = part.value
             message = MessageSyncMessage.deserialize(raw_message)

+ 1 - 0
mauigpapi/types/__init__.py

@@ -81,6 +81,7 @@ from .thread_item import (
     MediaType,
     Reaction,
     Reactions,
+    ReactionType,
     ReelMediaShareItem,
     ReelShareItem,
     ReelShareReactionInfo,

+ 11 - 4
mauigpapi/types/thread_item.py

@@ -379,13 +379,19 @@ class AnimatedMediaItem(SerializableAttrs):
     images: AnimatedMediaImages
 
 
+class ReactionType(SerializableEnum):
+    LIKES = "likes"
+    EMOJIS = "emojis"
+
+
 @dataclass
 class Reaction(SerializableAttrs):
     sender_id: int
-    timestamp: int
-    client_context: Optional[str]
-    emoji: str = "❤️"
+    timestamp: Optional[int]
+    emoji: Optional[str] = "❤️"
     super_react_type: Optional[str] = None
+    client_context: Optional[str] = None
+    type: Optional[ReactionType] = None
 
     @property
     def timestamp_ms(self) -> int:
@@ -511,6 +517,7 @@ class ThreadItem(SerializableAttrs):
     timestamp: int = 0
     item_type: Optional[ThreadItemType] = None
     is_shh_mode: bool = False
+    new_reaction: Optional[Reaction] = None
 
     text: Optional[str] = None
     client_context: Optional[str] = None
@@ -546,7 +553,7 @@ class ThreadItem(SerializableAttrs):
         try:
             return _dict_to_attrs(cls, data)
         except SerializerError:
-            log.debug("Failed to deserialize ThreadItem %s", data)
+            log.debug("Failed to deserialize ThreadItem %s", data, exc_info=True)
             return Obj(**data)
 
     @property

+ 37 - 4
mautrix_instagram/portal.py

@@ -131,7 +131,6 @@ class Portal(DBPortal, BasePortal):
     backfill_lock: SimpleLock
     _msgid_dedup: deque[str]
     _reqid_dedup: set[str]
-    _reaction_dedup: deque[tuple[str, int, str]]
 
     _last_participant_update: set[int]
     _reaction_lock: asyncio.Lock
@@ -166,7 +165,6 @@ class Portal(DBPortal, BasePortal):
         self._create_room_lock = asyncio.Lock()
         self.log = self.log.getChild(thread_id)
         self._msgid_dedup = deque(maxlen=100)
-        self._reaction_dedup = deque(maxlen=100)
         self._reqid_dedup = set()
         self._last_participant_update = set()
 
@@ -614,8 +612,6 @@ class Portal(DBPortal, BasePortal):
         if existing and existing.reaction == emoji:
             return
 
-        dedup_id = (message.item_id, sender.igpk, emoji)
-        self._reaction_dedup.appendleft(dedup_id)
         async with self._reaction_lock:
             resp = await sender.mqtt.send_reaction(
                 self.thread_id, item_id=message.item_id, emoji=emoji
@@ -1393,6 +1389,43 @@ class Portal(DBPortal, BasePortal):
                 await self.main_intent.redact(self.mxid, message.mxid)
             self.log.debug(f"Redacted {message.mxid} after Instagram unsend")
 
+    async def handle_instagram_reaction(
+        self, source: u.User, sender: p.Puppet, item: ThreadItem, remove: bool
+    ) -> None:
+        message = await DBMessage.get_by_item_id(item.item_id, self.receiver)
+        if not message:
+            self.log.debug(f"Dropping reaction by {sender.pk} to unknown message {item.item_id}")
+            return
+        emoji = item.new_reaction.emoji
+        async with self._reaction_lock:
+            existing = await DBReaction.get_by_item_id(item.item_id, self.receiver, sender.pk)
+            if not existing and remove:
+                self.log.debug(
+                    f"Ignoring duplicate reaction removal by {sender.pk} to {item.item_id}"
+                )
+                return
+            elif not remove and existing and existing.reaction == emoji:
+                self.log.debug(f"Ignoring duplicate reaction by {sender.pk} to {item.item_id}")
+                return
+            intent = sender.intent_for(self)
+            if remove:
+                await existing.delete()
+                await intent.redact(self.mxid, existing.mxid)
+                self.log.debug(
+                    f"Removed {sender.pk}'s reaction to {item.item_id} (redacted {existing.mxid})"
+                )
+            else:
+                timestamp = item.new_reaction.timestamp_ms
+                reaction_event_id = await intent.react(
+                    self.mxid, message.mxid, key=emoji, timestamp=timestamp
+                )
+                await self._upsert_reaction(
+                    existing, intent, reaction_event_id, message, sender, emoji, timestamp
+                )
+                self.log.debug(
+                    f"Handled {sender.pk}'s reaction to {item.item_id} -> {reaction_event_id}"
+                )
+
     async def _handle_instagram_reactions(
         self, message: DBMessage, reactions: list[Reaction], is_backfill: bool = False
     ) -> None:

+ 7 - 1
mautrix_instagram/user.py

@@ -679,8 +679,14 @@ class User(DBUser, BaseUser):
                 )
                 return
         self.log.trace(f"Received message sync event {evt.message}")
-        sender = await pu.Puppet.get_by_pk(evt.message.user_id) if evt.message.user_id else None
         await portal.backfill_lock.wait(f"{evt.message.op} {evt.message.item_id}")
+        if evt.message.new_reaction:
+            sender = await pu.Puppet.get_by_pk(evt.message.new_reaction.sender_id)
+            await portal.handle_instagram_reaction(
+                self, sender, evt.message, remove=evt.message.op == Operation.REMOVE
+            )
+            return
+        sender = await pu.Puppet.get_by_pk(evt.message.user_id) if evt.message.user_id else None
         if evt.message.op == Operation.ADD:
             if not sender:
                 # I don't think we care about adds with no sender