Skip to content

Commit 7cb88be

Browse files
committed
Add 2FA support.
1 parent 4096109 commit 7cb88be

File tree

3 files changed

+58
-10
lines changed

3 files changed

+58
-10
lines changed

InstagramAPI/base.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@
4444
class AuthenticationError(RuntimeError):
4545
pass
4646

47-
4847
class InstagramAPIBase:
4948

5049
API_URL = 'https://i.instagram.com/api/v1/'
@@ -60,6 +59,11 @@ class InstagramAPIBase:
6059
EXPERIMENTS = 'ig_android_progressive_jpeg,ig_creation_growth_holdout,ig_android_report_and_hide,ig_android_new_browser,ig_android_enable_share_to_whatsapp,ig_android_direct_drawing_in_quick_cam_universe,ig_android_huawei_app_badging,ig_android_universe_video_production,ig_android_asus_app_badging,ig_android_direct_plus_button,ig_android_ads_heatmap_overlay_universe,ig_android_http_stack_experiment_2016,ig_android_infinite_scrolling,ig_fbns_blocked,ig_android_white_out_universe,ig_android_full_people_card_in_user_list,ig_android_post_auto_retry_v7_21,ig_fbns_push,ig_android_feed_pill,ig_android_profile_link_iab,ig_explore_v3_us_holdout,ig_android_histogram_reporter,ig_android_anrwatchdog,ig_android_search_client_matching,ig_android_high_res_upload_2,ig_android_new_browser_pre_kitkat,ig_android_2fac,ig_android_grid_video_icon,ig_android_white_camera_universe,ig_android_disable_chroma_subsampling,ig_android_share_spinner,ig_android_explore_people_feed_icon,ig_explore_v3_android_universe,ig_android_media_favorites,ig_android_nux_holdout,ig_android_search_null_state,ig_android_react_native_notification_setting,ig_android_ads_indicator_change_universe,ig_android_video_loading_behavior,ig_android_black_camera_tab,liger_instagram_android_univ,ig_explore_v3_internal,ig_android_direct_emoji_picker,ig_android_prefetch_explore_delay_time,ig_android_business_insights_qe,ig_android_direct_media_size,ig_android_enable_client_share,ig_android_promoted_posts,ig_android_app_badging_holdout,ig_android_ads_cta_universe,ig_android_mini_inbox_2,ig_android_feed_reshare_button_nux,ig_android_boomerang_feed_attribution,ig_android_fbinvite_qe,ig_fbns_shared,ig_android_direct_full_width_media,ig_android_hscroll_profile_chaining,ig_android_feed_unit_footer,ig_android_media_tighten_space,ig_android_private_follow_request,ig_android_inline_gallery_backoff_hours_universe,ig_android_direct_thread_ui_rewrite,ig_android_rendering_controls,ig_android_ads_full_width_cta_universe,ig_video_max_duration_qe_preuniverse,ig_android_prefetch_explore_expire_time,ig_timestamp_public_test,ig_android_profile,ig_android_dv2_consistent_http_realtime_response,ig_android_enable_share_to_messenger,ig_explore_v3,ig_ranking_following,ig_android_pending_request_search_bar,ig_android_feed_ufi_redesign,ig_android_video_pause_logging_fix,ig_android_default_folder_to_camera,ig_android_video_stitching_7_23,ig_android_profanity_filter,ig_android_business_profile_qe,ig_android_search,ig_android_boomerang_entry,ig_android_inline_gallery_universe,ig_android_ads_overlay_design_universe,ig_android_options_app_invite,ig_android_view_count_decouple_likes_universe,ig_android_periodic_analytics_upload_v2,ig_android_feed_unit_hscroll_auto_advance,ig_peek_profile_photo_universe,ig_android_ads_holdout_universe,ig_android_prefetch_explore,ig_android_direct_bubble_icon,ig_video_use_sve_universe,ig_android_inline_gallery_no_backoff_on_launch_universe,ig_android_image_cache_multi_queue,ig_android_camera_nux,ig_android_immersive_viewer,ig_android_dense_feed_unit_cards,ig_android_sqlite_dev,ig_android_exoplayer,ig_android_add_to_last_post,ig_android_direct_public_threads,ig_android_prefetch_venue_in_composer,ig_android_bigger_share_button,ig_android_dv2_realtime_private_share,ig_android_non_square_first,ig_android_video_interleaved_v2,ig_android_follow_search_bar,ig_android_last_edits,ig_android_video_download_logging,ig_android_ads_loop_count_universe,ig_android_swipeable_filters_blacklist,ig_android_boomerang_layout_white_out_universe,ig_android_ads_carousel_multi_row_universe,ig_android_mentions_invite_v2,ig_android_direct_mention_qe,ig_android_following_follower_social_context'
6160
SIG_KEY_VERSION = '4'
6261

62+
class _2FA_Required(Exception):
63+
""" Raised and caught internally to the class to indicate Two-Factor Authentication is turned on for login."""
64+
def __init__(self, two_factor_info):
65+
self.two_factor_info = two_factor_info
66+
6367
def __init__(self, username, password):
6468
self._loggedinuserid = ''
6569
self._ranktoken = ''
@@ -170,7 +174,14 @@ def _sendrequest(self, endpoint, post=None, login=False, headers=None):
170174

171175
try:
172176
response.raise_for_status()
173-
except requests.RequestException:
177+
except requests.RequestException as re:
178+
# Special case for 2FA response:
179+
if isinstance(re, requests.HTTPError) and response.status_code == 400:
180+
json_dict = json.loads(response.text)
181+
if "two_factor_required" in json_dict and json_dict["two_factor_required"]:
182+
LOGGER.info("2FA required.")
183+
raise self._2FA_Required(two_factor_info=json_dict["two_factor_info"])
184+
174185
LOGGER.info("Instagram returned HTTP Error Code %s: (%s)",
175186
response.status_code, response.text)
176187
raise

InstagramAPI/endpoints.py

Lines changed: 43 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
import copy
2020
import math
2121
import sys
22-
from .base import InstagramAPIBase
22+
from .base import InstagramAPIBase, AuthenticationError
2323

2424
LOGGER = logging.getLogger('InstagramAPI')
2525

@@ -58,8 +58,17 @@ class InstagramAPIEndPoints(InstagramAPIBase):
5858
5959
"""
6060

61-
def __init__(self, username, password):
61+
def __init__(self, username, password, two_factor_callback=None):
62+
"""
63+
:param two_factor_callback: a function that takes a dictionary of "two_factor_info", and returns an
64+
verification string for logging in. Typically, this function would be prompt the user to enter the text
65+
sent via SMS.
66+
two_factor_info parameter typically contains the user name, parts of the phone number, a unique
67+
identifier, and details about how frequently the requests may be attempted without triggering robocalls
68+
or other consequences.
69+
"""
6270
InstagramAPIBase.__init__(self, username, password)
71+
self._two_factor_callback = two_factor_callback
6372

6473
def auto_complete_user_list(self):
6574
return self._sendrequest('friendships/autocomplete_user_list/')
@@ -366,6 +375,13 @@ def like(self, media_id):
366375
return self._sendrequest('media/' + str(media_id) + '/like/', self._generatesignature(data))
367376

368377
def login(self, force=False):
378+
"""
379+
Authenticate this API instance.
380+
381+
If already logged in (and not later logged out) does nothing (unless forced).
382+
:param force: if true, will attempt to log in even if already logged in.
383+
:return: dictionary of responses.
384+
"""
369385
if not self._isloggedin or force:
370386
self._session = requests.Session()
371387
# if you need proxy make something like this:
@@ -382,10 +398,31 @@ def login(self, force=False):
382398
'password': self._password,
383399
'login_attempt_count': '0'}
384400

385-
full_response = self._sendrequest(
386-
'accounts/login/',
387-
post=self._generatesignature(json.dumps(data)),
388-
login=True)
401+
try:
402+
full_response = self._sendrequest(
403+
'accounts/login/',
404+
post=self._generatesignature(json.dumps(data)),
405+
login=True)
406+
except InstagramAPIBase._2FA_Required as exception:
407+
# In order to login, need to provide the second factor (i.e. SMS code or backup code).
408+
# Use call-back to get this string.
409+
if not self._two_factor_callback:
410+
raise AuthenticationError("This account requires support for Two-Factor Authentication")
411+
two_factor_info = exception.two_factor_info = exception.two_factor_info
412+
verification_string = self._two_factor_callback(two_factor_info)
413+
data = {
414+
'verification_code': verification_string,
415+
'two_factor_identifier': two_factor_info['two_factor_identifier'],
416+
'_csrftoken': full_response.cookies['csrftoken'],
417+
'username': self._username,
418+
'device_id': self._deviceid,
419+
'password': self._password,
420+
}
421+
422+
full_response = self._sendrequest(
423+
'accounts/two_factor_login/',
424+
post=self._generatesignature(json.dumps(data)),
425+
login=True)
389426

390427
self._isloggedin = True
391428
decoded_text = json.loads(full_response.text)

InstagramAPI/instagram_api.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ class InstagramAPI(InstagramAPIEndPoints):
1717
# Make visible to clients for ease of reference.
1818
AuthenticationError = AuthenticationError
1919

20-
def __init__(self, username, password):
21-
InstagramAPIEndPoints.__init__(self, username, password)
20+
def __init__(self, username, password, two_factor_callback=None):
21+
InstagramAPIEndPoints.__init__(self, username, password, two_factor_callback)
2222

2323
# Helper functions to gather complete lists/deal with pagination.
2424

0 commit comments

Comments
 (0)