def _check_response_for_errors(self, resp: requests.Response) -> None: try: parsed = resp.json() except json.JSONDecodeError: if not resp.ok: if resp.status_code == 404 and '/friendships/' in resp.url: raise InvalidUserId( f"url: {resp.url} is not recognized by Instagram") logger.exception( f"response received: \n{resp.text}\nurl: {resp.url}\nstatus code: {resp.status_code}" ) raise BadResponse("Received a non-200 response from Instagram") return if resp.ok: return if parsed.get('description') == 'invalid password': raise IncorrectLoginDetails( "Instagram does not recognize the provided login details") if parsed.get('message') in ("checkpoint_required", "challenge_required"): if not hasattr(self, '_handle_challenge'): raise BadResponse( "Challenge required. ChallengeMixin is not mixed in.") eh = self._handle_challenge(resp) if eh: return if parsed.get('message') == 'feedback_required': # TODO: implement a handler for this error raise BadResponse( "Something unexpected happened. Please check the IG app.") if parsed.get('message') == 'rate_limit_error': raise TimeoutError("Calm down. Please try again in a few minutes.") raise BadResponse("Received a non-200 response from Instagram")
def _check_response_for_errors(self, resp: requests.Response) -> None: if resp.ok: return try: parsed = resp.json() except json.JSONDecodeError: if resp.status_code == 404 and '/friendships/' in resp.url: raise InvalidUserId(f"account id: {resp.url.split('/')[-2]} is not recognized by Instagram or you do not have a relation with this account.") logger.exception(f"response received: \n{resp.text}\nurl: {resp.url}\nstatus code: {resp.status_code}") raise BadResponse("Received a non-200 response from Instagram") if parsed.get('error_type') == 'bad_password': raise IncorrectLoginDetails("Instagram does not recognize the provided login details") if parsed.get('message') in ("checkpoint_required", "challenge_required"): if not hasattr(self, '_handle_challenge'): raise BadResponse("Challenge required. ChallengeMixin is not mixed in.") eh = self._handle_challenge(resp) if eh: return if parsed.get('message') == 'feedback_required': if os.environ.get("ENABLE_INSTAUTO_USAGE_METRICS", True): # This logs which actions cause limitations on Instagram accounts. # I use this data to focus my development on area's where it's most needed. requests.post('https://instauto.rooy.dev/feedback_required', data={ 'feedback_url': parsed.get('feedback_url'), 'category': parsed.get('category') }) raise BadResponse("Something unexpected happened. Please check the IG app.") if parsed.get('message') == 'rate_limit_error': raise TimeoutError("Calm down. Please try again in a few minutes.") if parsed.get('message') == 'Not authorized to view user': raise AuthorizationError("This is a private user, which you do not follow.") raise BadResponse("Received a non-200 response from Instagram")
def _handle_challenge(self, resp: requests.Response) -> bool: resp_data = self._json_loads(resp.text) # pyre-ignore[6] if resp_data['message'] not in ('challenge_required', 'checkpoint_required'): raise BadResponse("Challenge required, but no URL provided.") # pyre-ignore[6] api_path = resp_data['challenge']['api_path'][1:] resp = self._request(endpoint=api_path, method=Method.GET, query={ "guid": self.state.uuid, "device_id": self.state.android_id }) data = self._json_loads(resp.text) base_body = { "_csrftoken": self._session.cookies['csrftoken'], "_uuid": self.state.uuid, "bloks_versioning_id": self.state.bloks_version_id, "post": 1, } body = base_body.copy() body["choice"] = 1 # body["choice"] = int(data.get("step_data", {}).get("choice", 0)) _ = self._request(endpoint=api_path, method=Method.POST, body=body) security_code = input( "Verification needed. Type verification code here: ") body = base_body.copy() body["security_code"] = security_code _ = self._request(endpoint=api_path, method=Method.POST, body=body) return True
def _handle_challenge(self, resp: requests.Response) -> bool: resp_data = resp.json() if resp_data['message'] not in ('challenge_required', 'checkpoint_required'): raise BadResponse("Challenge required, but no URL provided.") api_path = resp_data['challenge']['api_path'][1:] resp_data2 = self._request(endpoint=api_path, method=Method.GET, query={ "guid": self.state.uuid, "device_id": self.state.android_id }) # TODO: Add support for different kinds of challenges. # Currently, only the verification pin challenge is supported, and other challenges, such as the # 'verify this was you', do not work. resp_json2 = resp_data2.json() if int(resp_json2.get("step_data", {}).get("choice", 0)) == 1: # server only support choice 1, this is requiry some other devices to confirm send verification, like web _ = self._request( endpoint=api_path, method=Method.POST, data={ "choice": 1, # TODO: enum to confirm send verification code. "_csrftoken": self._session.cookies['csrftoken'], "_uuid": self.state.uuid, "bloks_versioning_id": self.state.bloks_version_id, "post": 1 }) logger.warning( "You may should confirm in some other logged device this was me to obtain the verification code." ) else: _ = self._request( endpoint=api_path, method=Method.POST, data={ "choice": 0, # TODO: enum phone/email verification. Which value represent which one? "_csrftoken": self._session.cookies['csrftoken'], "_uuid": self.state.uuid, "bloks_versioning_id": self.state.bloks_version_id, "post": 1 }) security_code = input( "Verification needed. Type verification code here: ") _ = self._request(endpoint=api_path, method=Method.POST, data={ "_csrftoken": self._session.cookies['csrftoken'], "_uuid": self.state.uuid, "bloks_versioning_id": self.state.bloks_version_id, "post": 1, "security_code": security_code }) return True
def _handle_feedback_required(self, parsed: dict) -> None: if os.environ.get("ENABLE_INSTAUTO_USAGE_METRICS", True): # This logs which actions cause limitations on Instagram accounts. # I use this data to focus my development on area's where it's most needed. requests.post('https://instauto.rooy.dev/feedback_required', data={ 'feedback_url': parsed.get('feedback_url'), 'category': parsed.get('category') }) raise BadResponse( "Something unexpected happened. Please check the IG app.")
def _check_response_for_errors(self, resp: requests.Response) -> None: if resp.ok: return try: # pyre-ignore[9]: assume the response is a dictionary parsed: Dict[Any, Any] = self._json_loads(resp.text) except orjson.JSONDecodeError: if resp.status_code == 404 and '/friendships/' in resp.url: raise InvalidUserId( f"account id: {resp.url.split('/')[-2]} is not recognized " f"by Instagram or you do not have a relation with this account." ) logger.exception( f"response received: \n{resp.text}\nurl: {resp.url}\nstatus code: {resp.status_code}" ) raise BadResponse("Received a non-200 response from Instagram") message = parsed.get('message') error_type = parsed.get('error_type') two_factor_required = parsed.get('two_factor_required', False) if two_factor_required: return self._handle_2fa(parsed) if error_type == 'bad_password': raise IncorrectLoginDetails( "Instagram does not recognize the provided login details") if message in ("checkpoint_required", "challenge_required"): if self._handle_checkpoint(resp): return if error_type == 'feedback_required': self._handle_feedback_required(parsed) if error_type == 'rate_limit_error': raise Exception("Calm down. Please try again in a few minutes.") if error_type == 'Not authorized to view user': raise AuthorizationError( "This is a private user, which you do not follow.") raise BadResponse(f"Received a non-200 response from Instagram: \ {message}")
def _check_response_for_errors(self, resp: requests.Response) -> None: if resp.ok: return try: parsed = resp.json() except json.JSONDecodeError: if resp.status_code == 404 and '/friendships/' in resp.url: raise InvalidUserId( f"account id: {resp.url.split('/')[-2]} is not recognized by Instagram or you do not have a relation with this account." ) logger.exception( f"response received: \n{resp.text}\nurl: {resp.url}\nstatus code: {resp.status_code}" ) raise BadResponse("Received a non-200 response from Instagram") if parsed.get('error_type') == 'bad_password': raise IncorrectLoginDetails( "Instagram does not recognize the provided login details") if parsed.get('message') in ("checkpoint_required", "challenge_required"): if not hasattr(self, '_handle_challenge'): raise BadResponse( "Challenge required. ChallengeMixin is not mixed in.") eh = self._handle_challenge(resp) if eh: return if parsed.get('message') == 'feedback_required': # TODO: implement a handler for this error raise BadResponse( "Something unexpected happened. Please check the IG app.") if parsed.get('message') == 'rate_limit_error': raise TimeoutError("Calm down. Please try again in a few minutes.") if parsed.get('message') == 'Not authorized to view user': raise AuthorizationError( "This is a private user, which you do not follow.") raise BadResponse("Received a non-200 response from Instagram")
def _handle_challenge(self, resp: requests.Response) -> bool: resp_data = resp.json() if resp_data['message'] not in ('challenge_required', 'checkpoint_required'): raise BadResponse("Challenge required, but no URL provided.") api_path = resp_data['challenge']['api_path'][1:] _ = self._request(endpoint=api_path, method=Method.GET, query={ "guid": self.state.uuid, "device_id": self.state.device_id }) # TODO: Add support for different kinds of challenges. # Currently, only the verification pin challenge is supported, and other challenges, such as the # 'verify this was you', do not work. _ = self._request( endpoint=api_path, method=Method.POST, data={ "choice": 0, # TODO: enum phone/email verification. Which value represent which one? "_csrftoken": self._session.cookies['csrftoken'], "_uuid": self.state.uuid, "bloks_versioning_id": self.state.bloks_version_id, "post": 1 }) security_code = input( "Verification needed. Type verification code here: ") _ = self._request(endpoint=api_path, method=Method.POST, data={ "_csrftoken": self._session.cookies['csrftoken'], "_uuid": self.state.uuid, "bloks_versioning_id": self.state.bloks_version_id, "post": 1, "security_code": security_code }) return True
def _handle_checkpoint(self, resp: Response) -> bool: if not hasattr(self, '_handle_challenge'): raise BadResponse( "Challenge required. ChallengeMixin is not mixed in.") return self._handle_challenge(resp)