Skip to content

Commit

Permalink
misc: add extra logging to handle_challenge function
Browse files Browse the repository at this point in the history
  • Loading branch information
stanvanrooy committed Apr 30, 2022
1 parent b08f9e5 commit 443a723
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 59 deletions.
6 changes: 3 additions & 3 deletions instauto/api/actions/authentication.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def log_in(self) -> None:
raise e

def change_password(self, new_password: str, current_password: Optional[str] = None) -> requests.Response:
cp = current_password or self._raw_password
cp = current_password or self._plain_password
if cp is None:
raise ValueError("No current password provided")

Expand Down Expand Up @@ -88,7 +88,7 @@ def _encode_password(self, password: Optional[str] = None) -> Optional[str]:
"""Encrypts the raw password into a form that Instagram accepts."""
if not self.state.public_api_key:
return
if not any([password, self._raw_password]):
if not any([password, self._plain_password]):
return

key = Random.get_random_bytes(32)
Expand All @@ -104,7 +104,7 @@ def _encode_password(self, password: Optional[str] = None) -> Optional[str]:
aes = AES.new(key, AES.MODE_GCM, nonce=iv)
aes.update(str(time).encode('utf-8'))

encrypted_password, cipher_tag = aes.encrypt_and_digest(bytes(password or self._raw_password, 'utf-8'))
encrypted_password, cipher_tag = aes.encrypt_and_digest(bytes(password or self._plain_password, 'utf-8'))

encrypted = bytes([1,
int(self.state.public_api_key_id),
Expand Down
16 changes: 12 additions & 4 deletions instauto/api/actions/challenge.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,13 @@
class ChallengeMixin(StubMixin):
def _handle_challenge(self, resp: requests.Response) -> bool:
resp_data = self._json_loads(resp.text)
# pyre-ignore[6]
logger.debug('_handle_challenge -> resp_data: %s', resp_data)

if resp_data['message'] not in ('challenge_required', 'checkpoint_required'):
raise BadResponse("Challenge required, but no URL provided.")
# pyre-ignore[6]

assert 'challenge' in resp_data, f"'challenge' not found in resp_data"
assert 'api_path' in resp_data['challenge'], f"'api_path' not found in resp_data"
api_path = resp_data['challenge']['api_path'][1:]

resp = self._request(
Expand All @@ -37,7 +40,6 @@ def _handle_challenge(self, resp: requests.Response) -> bool:
"post": 1,
}
body = base_body.copy()
# pyre-ignore[16]
body["choice"] = int(data.get("step_data", {}).get("choice", 0))

_ = self._request(endpoint=api_path, method=Method.POST, body=body)
Expand All @@ -51,8 +53,8 @@ def _handle_challenge(self, resp: requests.Response) -> bool:
def _handle_2fa(self, parsed: dict) -> None:
endpoint = "accounts/two_factor_login/"
username = parsed['two_factor_info']['username']
code = self._get_2fa_code(username)

code = input(f"Enter 2fa code for {username}: ") if self._2fa_function is None else self._2fa_function(username)
logger.debug("2fa code is: %s", code)

# 1 = phone verification, 3 = authenticator app verification
Expand All @@ -71,3 +73,9 @@ def _handle_2fa(self, parsed: dict) -> None:
'verification_method': verification_method
}
self._request(endpoint, Method.POST, body=body)

def _get_2fa_code(self, username: str) -> str:
if self._2fa_function:
return self._2fa_function(username)
return input(f"Enter 2fa code for {username}: ")

7 changes: 4 additions & 3 deletions instauto/api/actions/helpers.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Union
from typing import Any, Union

import orjson

Expand Down Expand Up @@ -33,8 +33,9 @@ def _build_default_rupload_params(self, obj, quality: int, is_sidecar: bool) ->
"is_sidecar": str(int(is_sidecar))
}

def _json_loads(self, text: Union[str, bytes, bytearray]) -> Union[dict, list]:
def _json_loads(self, text: Union[bytes, bytearray, memoryview, str]) -> Any:
return orjson.loads(text)

def _json_dumps(self, obj: Union[dict, list]) -> str:
def _json_dumps(self, obj: Any) -> str:
return orjson.dumps(obj).decode()

1 change: 1 addition & 0 deletions instauto/api/actions/post.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,7 @@ def post_carousel(self, posts: List[PostFeed], caption: str, quality: int) -> Di
for i, post in enumerate(posts):
responses[f'post{i}'] = self._upload_image(post, quality, True)[0]

breakpoint()
responses['configure_sidecar'] = self._request('media/configure_sidecar/', Method.POST, body=data, headers=headers, sign_request=True)
return responses

Expand Down
16 changes: 8 additions & 8 deletions instauto/api/actions/stub.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from pathlib import Path
from typing import Callable, Union, Dict
from typing import Any, Callable, Optional, Union, Dict

import requests

Expand Down Expand Up @@ -27,11 +27,11 @@ def __call__(self, obj, quality: int, is_sidecar: bool) -> dict: ...


class _json_loads:
def __call__(self, text: Union[str, bytes, bytearray]) -> Union[dict, list]: ...
def __call__(self, text: Union[bytes, bytearray, memoryview, str]) -> Any: ...


class _json_dumps:
def __call__(self, obj: Union[dict, list]) -> str: ...
def __call__(self, obj: Any) -> str: ...


class StubMixin:
Expand All @@ -43,12 +43,12 @@ class StubMixin:
_session: requests.Session
_request_finished_callbacks: list
_handle_challenge: Callable
_2fa_function: Callable[[str], str]
_handle_2fa: Callable[[dict], None]
_2fa_function: Optional[Callable[[str], str]]
_handle_2fa: Optional[Callable[[dict], None]]
_request: _request
_username: str
_raw_password: str
_encoded_password: str
_username: Optional[str]
_plain_password: Optional[str]
_encoded_password: Optional[str]
_gen_uuid: Callable[[], str]
_get_image_type: _get_image_type
_build_default_rupload_params: _build_default_rupload_params
Expand Down
37 changes: 22 additions & 15 deletions instauto/api/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@

from .actions.feed import FeedMixin
from .actions.helpers import HelperMixin
from .structs import IGProfile, DeviceProfile, State, Inbox
from .structs import IGProfile, DeviceProfile, State
from .constants import (DEFAULT_IG_PROFILE, DEFAULT_DEVICE_PROFILE, DEFAULT_STATE)
from .exceptions import StateExpired, NoAuthDetailsProvided, CorruptedSaveData

Expand All @@ -33,32 +33,39 @@
logging.captureWarnings(True)


class ApiClient(ProfileMixin, AuthenticationMixin, PostMixin, RequestMixin, FriendshipsMixin,
SearchMixin, ChallengeMixin, DirectMixin, HelperMixin, FeedMixin, ActivityMixin):
class ApiClient(ProfileMixin, AuthenticationMixin, PostMixin,
RequestMixin, FriendshipsMixin, SearchMixin, ChallengeMixin,
DirectMixin, HelperMixin, FeedMixin, ActivityMixin):
breadcrumb_private_key = "iN4$aGr0m".encode()
bc_hmac = hmac.HMAC(breadcrumb_private_key, digestmod='SHA256')

def __init__(
self, ig_profile: Optional[IGProfile] = None, device_profile: Optional[DeviceProfile] = None,
state: Optional[State] = None, username: Optional[str] = None, password: Optional[str] = None,
session_cookies: Optional[dict] = None, testing=False, _2fa_function: Optional[Callable[[str], str]] = None
self, ig_profile: Optional[IGProfile] = None, device_profile:
Optional[DeviceProfile] = None, state: Optional[State] = None,
username: Optional[str] = None, password: Optional[str] = None,
session_cookies: Optional[dict] = None, testing=False,
_2fa_function: Optional[Callable[[str], str]] = None
) -> None:
"""Initializes all attributes. Can be instantiated with no params.
Needs to be provided with either:
1) state and session_cookies, to resume an old session, in this case all other params are optional
2) username and password, in this case all other params are optional
Needs to be provided with either: 1) state and session_cookies,
to resume an old session, in this case all other params are
optional 2) username and password, in this case all other params
are optional
In the case that the class is initialized without params, or with a few of the params not provided,
they will automatically be filled with default values from constants.py.
In the case that the class is initialized without params, or
with a few of the params not provided, they will automatically
be filled with default values from constants.py.
Using the default values should be fine for pretty much all use cases, but if for some reason you need to use
non-default values, that can be done by creating any of the profiles yourself and passing it in as an argument.
Using the default values should be fine for pretty much all use
cases, but if for some reason you need to use non-default
values, that can be done by creating any of the profiles
yourself and passing it in as an argument.
"""
super().__init__()
self._2fa_function = _2fa_function
self._username = username
self._raw_password = password
self._plain_password = password
self._encoded_password = None

self._init_ig_profile(ig_profile)
Expand All @@ -67,7 +74,7 @@ def __init__(
self._user_agent = self._build_user_agent()

if (username is None or password is None) and (state is None or session_cookies is None) and not testing:
raise NoAuthDetailsProvided("Username, password and state are all not provided.")
raise NoAuthDetailsProvided("Neither a username and username or existing state is provided.")

self._init_session(session_cookies, testing)
self._request_finished_callbacks = [self._update_state_from_headers]
Expand Down
39 changes: 14 additions & 25 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,25 +1,14 @@
APScheduler==3.6.3
Babel==2.9.1
certifi==2020.6.20
cffi==1.14.0
chardet==3.0.4
commonmark==0.9.1
idna==2.10
imagesize==1.2.0
Jinja2==2.11.3
MarkupSafe==1.1.1
numpydoc==1.1.0
packaging==20.4
pycparser==2.20
pycryptodomex==3.9.8
Pygments==2.7.4
pyparsing==2.4.7
pytz==2020.1
recommonmark==0.6.0
requests==2.25.1
six==1.15.0
snowballstemmer==2.0.0
tzlocal==2.1
urllib3==1.26.5
orjson~=3.5.2
setuptools~=51.0.0
APScheduler==3.9.1
certifi==2021.10.8
charset-normalizer==2.0.12
idna==3.3
imagesize==1.3.0
orjson==3.6.8
pycryptodomex==3.14.1
pytz==2022.1
pytz-deprecation-shim==0.1.0.post0
requests==2.27.1
six==1.16.0
tzdata==2022.1
tzlocal==4.2
urllib3==1.26.9
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
'requests',
'apscheduler',
'pycryptodomex',
'imagesize'
'imagesize',
'orjson'
],
classifiers=[
'Development Status :: 5 - Production/Stable',
Expand Down

0 comments on commit 443a723

Please sign in to comment.