Forráskód Böngészése

Try generating request IDs that look more like Instagram's

Tulir Asokan 4 éve
szülő
commit
7959801404

+ 1 - 1
mauigpapi/http/thread.py

@@ -88,7 +88,7 @@ class ThreadAPI(BaseAndroidAPI):
 
 
     async def broadcast(self, thread_id: str, item_type: ThreadItemType, signed: bool = False,
     async def broadcast(self, thread_id: str, item_type: ThreadItemType, signed: bool = False,
                         client_context: Optional[str] = None, **kwargs) -> CommandResponse:
                         client_context: Optional[str] = None, **kwargs) -> CommandResponse:
-        client_context = client_context or str(uuid4())
+        client_context = client_context or self.state.gen_client_context()
         form = {
         form = {
             "action": ThreadAction.SEND_ITEM.value,
             "action": ThreadAction.SEND_ITEM.value,
             "send_attribution": "inbox",
             "send_attribution": "inbox",

+ 33 - 55
mauigpapi/mqtt/conn.py

@@ -18,9 +18,10 @@ from typing import (Union, Set, Optional, Any, Dict, Awaitable, Type, List, Type
 from collections import defaultdict
 from collections import defaultdict
 from socket import socket, error as SocketError
 from socket import socket, error as SocketError
 from uuid import uuid4
 from uuid import uuid4
-import logging
 import urllib.request
 import urllib.request
+import logging
 import asyncio
 import asyncio
+import random
 import zlib
 import zlib
 import time
 import time
 import json
 import json
@@ -534,14 +535,13 @@ class AndroidMQTT:
             self._client._keepalive = state.keep_alive_timeout
             self._client._keepalive = state.keep_alive_timeout
 
 
     async def send_command(self, thread_id: str, action: ThreadAction,
     async def send_command(self, thread_id: str, action: ThreadAction,
-                           client_context: Optional[str] = None,
-                           offline_threading_id: Optional[str] = None, **kwargs: Any
+                           client_context: Optional[str] = None, **kwargs: Any
                            ) -> Optional[CommandResponse]:
                            ) -> Optional[CommandResponse]:
-        client_context = client_context or str(uuid4())
+        client_context = client_context or self.state.gen_client_context()
         req = {
         req = {
             "thread_id": thread_id,
             "thread_id": thread_id,
             "client_context": client_context,
             "client_context": client_context,
-            "offline_threading_id": offline_threading_id or client_context,
+            "offline_threading_id": client_context,
             "action": action.value,
             "action": action.value,
             # "device_id": self.state.cookies["ig_did"],
             # "device_id": self.state.cookies["ig_did"],
             **kwargs,
             **kwargs,
@@ -561,97 +561,75 @@ class AndroidMQTT:
             return CommandResponse.parse_json(resp.payload.decode("utf-8"))
             return CommandResponse.parse_json(resp.payload.decode("utf-8"))
 
 
     def send_item(self, thread_id: str, item_type: ThreadItemType, shh_mode: bool = False,
     def send_item(self, thread_id: str, item_type: ThreadItemType, shh_mode: bool = False,
-                  client_context: Optional[str] = None, offline_threading_id: Optional[str] = None,
-                  **kwargs: Any) -> Awaitable[CommandResponse]:
+                  client_context: Optional[str] = None, **kwargs: Any
+                  ) -> Awaitable[CommandResponse]:
         return self.send_command(thread_id, item_type=item_type.value,
         return self.send_command(thread_id, item_type=item_type.value,
                                  is_shh_mode=str(int(shh_mode)), action=ThreadAction.SEND_ITEM,
                                  is_shh_mode=str(int(shh_mode)), action=ThreadAction.SEND_ITEM,
-                                 client_context=client_context,
-                                 offline_threading_id=offline_threading_id, **kwargs)
+                                 client_context=client_context, **kwargs)
 
 
     def send_hashtag(self, thread_id: str, hashtag: str, text: str = "", shh_mode: bool = False,
     def send_hashtag(self, thread_id: str, hashtag: str, text: str = "", shh_mode: bool = False,
-                     client_context: Optional[str] = None,
-                     offline_threading_id: Optional[str] = None) -> Awaitable[CommandResponse]:
+                     client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, text=text, item_id=hashtag, shh_mode=shh_mode,
         return self.send_item(thread_id, text=text, item_id=hashtag, shh_mode=shh_mode,
-                              item_type=ThreadItemType.HASHTAG, client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              item_type=ThreadItemType.HASHTAG, client_context=client_context)
 
 
     def send_like(self, thread_id: str, shh_mode: bool = False,
     def send_like(self, thread_id: str, shh_mode: bool = False,
-                  client_context: Optional[str] = None, offline_threading_id: Optional[str] = None,
-                  ) -> Awaitable[CommandResponse]:
+                  client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, shh_mode=shh_mode, item_type=ThreadItemType.LIKE,
         return self.send_item(thread_id, shh_mode=shh_mode, item_type=ThreadItemType.LIKE,
-                              client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              client_context=client_context)
 
 
     def send_location(self, thread_id: str, venue_id: str, text: str = "",
     def send_location(self, thread_id: str, venue_id: str, text: str = "",
-                      shh_mode: bool = False, client_context: Optional[str] = None,
-                      offline_threading_id: Optional[str] = None,
+                      shh_mode: bool = False, client_context: Optional[str] = None
                       ) -> Awaitable[CommandResponse]:
                       ) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, text=text, item_id=venue_id, shh_mode=shh_mode,
         return self.send_item(thread_id, text=text, item_id=venue_id, shh_mode=shh_mode,
-                              item_type=ThreadItemType.LOCATION, client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              item_type=ThreadItemType.LOCATION, client_context=client_context)
 
 
     def send_media(self, thread_id: str, media_id: str, text: str = "", shh_mode: bool = False,
     def send_media(self, thread_id: str, media_id: str, text: str = "", shh_mode: bool = False,
-                   client_context: Optional[str] = None,
-                   offline_threading_id: Optional[str] = None) -> Awaitable[CommandResponse]:
+                   client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, text=text, media_id=media_id, shh_mode=shh_mode,
         return self.send_item(thread_id, text=text, media_id=media_id, shh_mode=shh_mode,
-                              item_type=ThreadItemType.MEDIA_SHARE, client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              item_type=ThreadItemType.MEDIA_SHARE, client_context=client_context)
 
 
     def send_profile(self, thread_id: str, user_id: str, text: str = "", shh_mode: bool = False,
     def send_profile(self, thread_id: str, user_id: str, text: str = "", shh_mode: bool = False,
-                     client_context: Optional[str] = None,
-                     offline_threading_id: Optional[str] = None) -> Awaitable[CommandResponse]:
+                     client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, text=text, item_id=user_id, shh_mode=shh_mode,
         return self.send_item(thread_id, text=text, item_id=user_id, shh_mode=shh_mode,
-                              item_type=ThreadItemType.PROFILE, client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              item_type=ThreadItemType.PROFILE, client_context=client_context)
 
 
     def send_reaction(self, thread_id: str, emoji: str, item_id: str,
     def send_reaction(self, thread_id: str, emoji: str, item_id: str,
                       reaction_status: ReactionStatus = ReactionStatus.CREATED,
                       reaction_status: ReactionStatus = ReactionStatus.CREATED,
                       target_item_type: ThreadItemType = ThreadItemType.TEXT,
                       target_item_type: ThreadItemType = ThreadItemType.TEXT,
-                      shh_mode: bool = False, client_context: Optional[str] = None,
-                      offline_threading_id: Optional[str] = None) -> Awaitable[CommandResponse]:
+                      shh_mode: bool = False, client_context: Optional[str] = None
+                      ) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, reaction_status=reaction_status.value, node_type="item",
         return self.send_item(thread_id, reaction_status=reaction_status.value, node_type="item",
                               reaction_type="like", target_item_type=target_item_type.value,
                               reaction_type="like", target_item_type=target_item_type.value,
                               emoji=emoji, item_id=item_id, reaction_action_source="double_tap",
                               emoji=emoji, item_id=item_id, reaction_action_source="double_tap",
                               shh_mode=shh_mode, item_type=ThreadItemType.REACTION,
                               shh_mode=shh_mode, item_type=ThreadItemType.REACTION,
-                              client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              client_context=client_context)
 
 
     def send_user_story(self, thread_id: str, media_id: str, text: str = "",
     def send_user_story(self, thread_id: str, media_id: str, text: str = "",
-                        shh_mode: bool = False, client_context: Optional[str] = None,
-                        offline_threading_id: Optional[str] = None) -> Awaitable[CommandResponse]:
+                        shh_mode: bool = False, client_context: Optional[str] = None
+                        ) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, text=text, item_id=media_id, shh_mode=shh_mode,
         return self.send_item(thread_id, text=text, item_id=media_id, shh_mode=shh_mode,
-                              item_type=ThreadItemType.REEL_SHARE, client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              item_type=ThreadItemType.REEL_SHARE, client_context=client_context)
 
 
     def send_text(self, thread_id: str, text: str = "", shh_mode: bool = False,
     def send_text(self, thread_id: str, text: str = "", shh_mode: bool = False,
-                  client_context: Optional[str] = None, offline_threading_id: Optional[str] = None
-                  ) -> Awaitable[CommandResponse]:
+                  client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_item(thread_id, text=text, shh_mode=shh_mode,
         return self.send_item(thread_id, text=text, shh_mode=shh_mode,
-                              item_type=ThreadItemType.TEXT, client_context=client_context,
-                              offline_threading_id=offline_threading_id)
+                              item_type=ThreadItemType.TEXT, client_context=client_context)
 
 
-    def mark_seen(self, thread_id: str, item_id: str, client_context: Optional[str] = None,
-                  offline_threading_id: Optional[str] = None) -> Awaitable[None]:
+    def mark_seen(self, thread_id: str, item_id: str, client_context: Optional[str] = None
+                  ) -> Awaitable[None]:
         return self.send_command(thread_id, item_id=item_id, action=ThreadAction.MARK_SEEN,
         return self.send_command(thread_id, item_id=item_id, action=ThreadAction.MARK_SEEN,
-                                 client_context=client_context,
-                                 offline_threading_id=offline_threading_id)
+                                 client_context=client_context)
 
 
     def mark_visual_item_seen(self, thread_id: str, item_id: str,
     def mark_visual_item_seen(self, thread_id: str, item_id: str,
-                              client_context: Optional[str] = None,
-                              offline_threading_id: Optional[str] = None
-                              ) -> Awaitable[CommandResponse]:
+                              client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_command(thread_id, item_id=item_id,
         return self.send_command(thread_id, item_id=item_id,
                                  action=ThreadAction.MARK_VISUAL_ITEM_SEEN,
                                  action=ThreadAction.MARK_VISUAL_ITEM_SEEN,
-                                 client_context=client_context,
-                                 offline_threading_id=offline_threading_id)
+                                 client_context=client_context)
 
 
     def indicate_activity(self, thread_id: str, activity_status: TypingStatus = TypingStatus.TEXT,
     def indicate_activity(self, thread_id: str, activity_status: TypingStatus = TypingStatus.TEXT,
-                          client_context: Optional[str] = None,
-                          offline_threading_id: Optional[str] = None
-                          ) -> Awaitable[CommandResponse]:
+                          client_context: Optional[str] = None) -> Awaitable[CommandResponse]:
         return self.send_command(thread_id, activity_status=activity_status.value,
         return self.send_command(thread_id, activity_status=activity_status.value,
                                  action=ThreadAction.INDICATE_ACTIVITY,
                                  action=ThreadAction.INDICATE_ACTIVITY,
-                                 client_context=client_context,
-                                 offline_threading_id=offline_threading_id)
+                                 client_context=client_context)
 
 
     # endregion
     # endregion

+ 4 - 0
mauigpapi/state/state.py

@@ -79,6 +79,10 @@ class AndroidState(SerializableAttrs):
     def challenge_path(self, val: str) -> None:
     def challenge_path(self, val: str) -> None:
         self._challenge_path = val
         self._challenge_path = val
 
 
+    @staticmethod
+    def gen_client_context() -> str:
+        return str((int(time.time() * 1000) << 22) + random.randint(10000, 5000000))
+
     def _gen_temp_uuid(self, seed: str, lifetime: int) -> UUID:
     def _gen_temp_uuid(self, seed: str, lifetime: int) -> UUID:
         rand = random.Random(f"{seed}{self.device.id}{round(time.time() * 1000 / lifetime)}")
         rand = random.Random(f"{seed}{self.device.id}{round(time.time() * 1000 / lifetime)}")
         return UUID(int=rand.getrandbits(128), version=4)
         return UUID(int=rand.getrandbits(128), version=4)

+ 14 - 9
mautrix_instagram/portal.py

@@ -16,11 +16,11 @@
 from typing import (Dict, Tuple, Optional, List, Deque, Set, Any, Union, AsyncGenerator,
 from typing import (Dict, Tuple, Optional, List, Deque, Set, Any, Union, AsyncGenerator,
                     Awaitable, NamedTuple, TYPE_CHECKING, cast)
                     Awaitable, NamedTuple, TYPE_CHECKING, cast)
 from collections import deque
 from collections import deque
-from uuid import uuid4
 from io import BytesIO
 from io import BytesIO
 import mimetypes
 import mimetypes
 import asyncio
 import asyncio
 
 
+import asyncpg
 import magic
 import magic
 
 
 from mauigpapi.types import (Thread, ThreadUser, ThreadItem, RegularMediaItem, MediaType,
 from mauigpapi.types import (Thread, ThreadUser, ThreadItem, RegularMediaItem, MediaType,
@@ -172,7 +172,7 @@ class Portal(DBPortal, BasePortal):
         elif not sender.is_connected:
         elif not sender.is_connected:
             await self._send_bridge_error("You're not connected to Instagram", confirmed=True)
             await self._send_bridge_error("You're not connected to Instagram", confirmed=True)
             return
             return
-        request_id = str(uuid4())
+        request_id = sender.state.gen_client_context()
         self._reqid_dedup.add(request_id)
         self._reqid_dedup.add(request_id)
         self.log.debug(f"Handling Matrix message {event_id} from {sender.mxid}/{sender.igpk} "
         self.log.debug(f"Handling Matrix message {event_id} from {sender.mxid}/{sender.igpk} "
                        f"with request ID {request_id}")
                        f"with request ID {request_id}")
@@ -222,11 +222,16 @@ class Portal(DBPortal, BasePortal):
             await self._send_bridge_error(resp.payload.message)
             await self._send_bridge_error(resp.payload.message)
         else:
         else:
             self._msgid_dedup.appendleft(resp.payload.item_id)
             self._msgid_dedup.appendleft(resp.payload.item_id)
-            await DBMessage(mxid=event_id, mx_room=self.mxid, item_id=resp.payload.item_id,
-                            receiver=self.receiver, sender=sender.igpk).insert()
+            try:
+                await DBMessage(mxid=event_id, mx_room=self.mxid, item_id=resp.payload.item_id,
+                                receiver=self.receiver, sender=sender.igpk).insert()
+            except asyncpg.UniqueViolationError as e:
+                self.log.warning(f"Error while persisting {event_id} "
+                                 f"({resp.payload.client_context}) -> {resp.payload.item_id}: {e}")
             self._reqid_dedup.remove(request_id)
             self._reqid_dedup.remove(request_id)
             await self._send_delivery_receipt(event_id)
             await self._send_delivery_receipt(event_id)
-            self.log.debug(f"Handled Matrix message {event_id} -> {resp.payload.item_id}")
+            self.log.debug(f"Handled Matrix message {event_id} ({resp.payload.client_context}) "
+                           f"-> {resp.payload.item_id}")
 
 
     async def handle_matrix_reaction(self, sender: 'u.User', event_id: EventID,
     async def handle_matrix_reaction(self, sender: 'u.User', event_id: EventID,
                                      reacting_to: EventID, emoji: str) -> None:
                                      reacting_to: EventID, emoji: str) -> None:
@@ -568,11 +573,11 @@ class Portal(DBPortal, BasePortal):
             self.log.debug(f"Ignoring message {item.item_id} ({item.client_context}) by "
             self.log.debug(f"Ignoring message {item.item_id} ({item.client_context}) by "
                            f"{item.user_id} as it was sent by us (client_context in dedup queue)")
                            f"{item.user_id} as it was sent by us (client_context in dedup queue)")
         elif item.item_id in self._msgid_dedup:
         elif item.item_id in self._msgid_dedup:
-            self.log.debug(f"Ignoring message {item.item_id} by {item.user_id}"
-                           " as it was already handled (message.id in dedup queue)")
+            self.log.debug(f"Ignoring message {item.item_id} ({item.client_context}) by "
+                           f"{item.user_id} as it was already handled (message.id in dedup queue)")
         elif await DBMessage.get_by_item_id(item.item_id, self.receiver) is not None:
         elif await DBMessage.get_by_item_id(item.item_id, self.receiver) is not None:
-            self.log.debug(f"Ignoring message {item.item_id} by {item.user_id}"
-                           " as it was already handled (message.id found in database)")
+            self.log.debug(f"Ignoring message {item.item_id} ({item.client_context}) by "
+                           f"{item.user_id} as it was already handled (message.id in database)")
         else:
         else:
             self.log.debug(f"Starting handling of message {item.item_id} ({item.client_context}) "
             self.log.debug(f"Starting handling of message {item.item_id} ({item.client_context}) "
                            f"by {item.user_id}")
                            f"by {item.user_id}")