Browse Source

Catch checkpoint errors when connecting

Tulir Asokan 3 years ago
parent
commit
04a0f5796c
3 changed files with 49 additions and 6 deletions
  1. 18 5
      mauigpapi/types/challenge.py
  2. 1 1
      mautrix_instagram/__main__.py
  3. 30 0
      mautrix_instagram/user.py

+ 18 - 5
mauigpapi/types/challenge.py

@@ -17,7 +17,7 @@ from typing import Optional
 
 
 from attr import dataclass
 from attr import dataclass
 
 
-from mautrix.types import SerializableAttrs
+from mautrix.types import SerializableAttrs, field
 
 
 from .login import LoginResponseUser
 from .login import LoginResponseUser
 
 
@@ -38,6 +38,15 @@ class ChallengeStateData(SerializableAttrs):
     form_type: Optional[str] = None
     form_type: Optional[str] = None
 
 
 
 
+@dataclass
+class ChallengeContext(SerializableAttrs):
+    step_name: Optional[str] = None
+    challenge_type_enum: Optional[str] = None
+    cni: Optional[int] = None
+    is_stateless: bool = False
+    present_as_modal: bool = False
+
+
 @dataclass(kw_only=True)
 @dataclass(kw_only=True)
 class ChallengeStateResponse(SerializableAttrs):
 class ChallengeStateResponse(SerializableAttrs):
     # TODO enum?
     # TODO enum?
@@ -50,7 +59,11 @@ class ChallengeStateResponse(SerializableAttrs):
     action: Optional[str] = None
     action: Optional[str] = None
     status: str
     status: str
 
 
-    # flow_render_type: int
-    # bloks_action: str
-    # challenge_context: str
-    # challenge_type_enum_str: str
+    flow_render_type: Optional[int] = None
+    bloks_action: Optional[str] = None
+    challenge_context_str: Optional[str] = field(default=None, json="challenge_context")
+    challenge_type_enum_str: Optional[str] = None
+
+    @property
+    def challenge_context(self) -> ChallengeContext:
+        return ChallengeContext.parse_json(self.challenge_context_str)

+ 1 - 1
mautrix_instagram/__main__.py

@@ -116,7 +116,7 @@ class InstagramBridge(Bridge):
                 return
                 return
             log.info("Executing periodic reconnections")
             log.info("Executing periodic reconnections")
             for user in User.by_igpk.values():
             for user in User.by_igpk.values():
-                if not user.is_connected and not always_reconnect:
+                if not user.client or (not user.is_connected and not always_reconnect):
                     log.debug("Not reconnecting %s: not connected", user.mxid)
                     log.debug("Not reconnecting %s: not connected", user.mxid)
                     continue
                     continue
                 log.debug("Executing periodic reconnect for %s", user.mxid)
                 log.debug("Executing periodic reconnect for %s", user.mxid)

+ 30 - 0
mautrix_instagram/user.py

@@ -22,6 +22,7 @@ import time
 
 
 from mauigpapi import AndroidAPI, AndroidMQTT, AndroidState
 from mauigpapi import AndroidAPI, AndroidMQTT, AndroidState
 from mauigpapi.errors import (
 from mauigpapi.errors import (
+    IGCheckpointError,
     IGNotLoggedInError,
     IGNotLoggedInError,
     IGUserIDNotFoundError,
     IGUserIDNotFoundError,
     IrisSubscribeError,
     IrisSubscribeError,
@@ -63,6 +64,8 @@ BridgeState.human_readable_errors.update(
     {
     {
         "ig-connection-error": "Instagram disconnected unexpectedly",
         "ig-connection-error": "Instagram disconnected unexpectedly",
         "ig-auth-error": "Authentication error from Instagram: {message}",
         "ig-auth-error": "Authentication error from Instagram: {message}",
+        "ig-checkpoint": "Instagram checkpoint error. Please check Instagram website.",
+        "ig-checkpoint-locked": "Instagram checkpoint error. Please check Instagram website.",
         "ig-disconnected": None,
         "ig-disconnected": None,
         "ig-no-mqtt": "You're not connected to Instagram",
         "ig-no-mqtt": "You're not connected to Instagram",
         "logged-out": "You're not logged into Instagram",
         "logged-out": "You're not logged into Instagram",
@@ -176,6 +179,9 @@ class User(DBUser, BaseUser):
                 self.log.warning(f"Failed to connect to Instagram: {e}, logging out")
                 self.log.warning(f"Failed to connect to Instagram: {e}, logging out")
                 await self.logout(error=e)
                 await self.logout(error=e)
                 return
                 return
+            except IGCheckpointError as e:
+                await self._handle_checkpoint(e, on="connect", client=client)
+                return
         self.client = client
         self.client = client
         self._is_logged_in = True
         self._is_logged_in = True
         self.igpk = user.pk
         self.igpk = user.pk
@@ -327,6 +333,9 @@ class User(DBUser, BaseUser):
                     except IGNotLoggedInError as e:
                     except IGNotLoggedInError as e:
                         self.log.exception("Got not logged in error while syncing for refresh")
                         self.log.exception("Got not logged in error while syncing for refresh")
                         await self.logout(error=e)
                         await self.logout(error=e)
+                    except IGCheckpointError as e:
+                        await self._handle_checkpoint(e, on="refresh")
+                        return
                     except Exception:
                     except Exception:
                         if retry_count >= 4 and minutes < 5:
                         if retry_count >= 4 and minutes < 5:
                             minutes += 1
                             minutes += 1
@@ -342,6 +351,27 @@ class User(DBUser, BaseUser):
         finally:
         finally:
             self._is_refreshing = False
             self._is_refreshing = False
 
 
+    async def _handle_checkpoint(
+        self, e: IGCheckpointError, on: str, client: AndroidAPI | None = None
+    ) -> None:
+        self.log.warning(f"Got checkpoint error on {on}: {e.body.serialize()}")
+        client = client or self.client
+        error_code = "ig-checkpoint"
+        try:
+            resp = await client.challenge_reset()
+            self.log.debug(f"Challenge state: {resp.serialize()}")
+            if resp.challenge_context.challenge_type_enum == "HACKED_LOCK":
+                error_code = "ig-checkpoint-locked"
+        except Exception:
+            self.log.exception("Error resetting challenge state")
+        await self.push_bridge_state(BridgeStateEvent.BAD_CREDENTIALS, error=error_code)
+        self.client = None
+        self.mqtt = None
+        # if on == "connect":
+        #     await self.connect()
+        # else:
+        #     await self.sync()
+
     async def _sync_thread(self, thread: Thread, min_active_at: int) -> None:
     async def _sync_thread(self, thread: Thread, min_active_at: int) -> None:
         portal = await po.Portal.get_by_thread(thread, self.igpk)
         portal = await po.Portal.get_by_thread(thread, self.igpk)
         if portal.mxid:
         if portal.mxid: