def user_followers_gql_chunk( self, user_id: str, max_amount: int = 0, end_cursor: str = None) -> Tuple[List[UserShort], str]: """ Get user's followers information by Public Graphql API and end_cursor Parameters ---------- user_id: str User id of an instagram account max_amount: int, optional Maximum number of media to return, default is 0 - Inf end_cursor: str, optional The cursor from which it is worth continuing to receive the list of followers Returns ------- Tuple[List[UserShort], str] List of objects of User type with cursor """ user_id = str(user_id) users = [] variables = { "id": user_id, "include_reel": True, "fetch_mutual": False, "first": 12 } self.inject_sessionid_to_public() while True: if end_cursor: variables["after"] = end_cursor data = self.public_graphql_request( variables, query_hash="5aefa9893005572d237da5068082d8d5") if not data["user"] and not users: raise UserNotFound(user_id=user_id, **data) page_info = json_value(data, "user", "edge_followed_by", "page_info", default={}) edges = json_value(data, "user", "edge_followed_by", "edges", default=[]) for edge in edges: users.append(extract_user_short(edge["node"])) end_cursor = page_info.get("end_cursor") if not page_info.get("has_next_page") or not end_cursor: break if max_amount and len(users) >= max_amount: break return users, end_cursor
def user_following_gql(self, user_id: str, amount: int = 0) -> List[UserShort]: """ Get user's following information by Public Graphql API Parameters ---------- user_id: str User id of an instagram account amount: int, optional Maximum number of media to return, default is 0 Returns ------- List[UserShort] List of objects of User type """ user_id = str(user_id) end_cursor = None users = [] variables = { "id": user_id, "include_reel": True, "fetch_mutual": False, "first": 24, } self.inject_sessionid_to_public() while True: if end_cursor: variables["after"] = end_cursor data = self.public_graphql_request( variables, query_hash="e7e2f4da4b02303f74f0841279e52d76") if not data["user"] and not users: raise UserNotFound(user_id=user_id, **data) page_info = json_value(data, "user", "edge_follow", "page_info", default={}) edges = json_value(data, "user", "edge_follow", "edges", default=[]) for edge in edges: users.append(extract_user_short(edge["node"])) end_cursor = page_info.get("end_cursor") if not page_info.get("has_next_page") or not end_cursor: break if amount and len(users) >= amount: break # time.sleep(sleep) if amount: users = users[:amount] return users
def usertag_medias_gql(self, user_id: int, amount: int = 0, sleep: int = 2) -> List[Media]: """ Get medias where a user is tagged (by Public GraphQL API) Parameters ---------- user_id: int amount: int, optional Maximum number of media to return, default is 0 (all medias) sleep: int, optional Timeout between pages iterations, default is 2 Returns ------- List[Media] A list of objects of Media """ amount = int(amount) user_id = int(user_id) medias = [] end_cursor = None variables = { "id": user_id, "first": 50 if not amount or amount > 50 else amount, # These are Instagram restrictions, you can only specify <= 50 } while True: if end_cursor: variables["after"] = end_cursor data = self.public_graphql_request( variables, query_hash="be13233562af2d229b008d2976b998b5") page_info = json_value(data, "user", "edge_user_to_photos_of_you", "page_info", default={}) edges = json_value(data, "user", "edge_user_to_photos_of_you", "edges", default=[]) for edge in edges: medias.append(edge["node"]) end_cursor = page_info.get("end_cursor") if not page_info.get("has_next_page") or not end_cursor: break if amount and len(medias) >= amount: break time.sleep(sleep) if amount: medias = medias[:amount] return [extract_media_gql(media) for media in medias]
def user_medias_gql(self, user_id: int, amount: int = 50, sleep: int = 2) -> List[Media]: """ Get a user's media by Public Graphql API Parameters ---------- user_id: int amount: int, optional Maximum number of media to return, default is 50 sleep: int, optional Timeout between pages iterations, default is 2 Returns ------- List[Media] A list of objects of Media """ amount = int(amount) user_id = int(user_id) medias = [] end_cursor = None variables = { "id": user_id, "first": 50 if amount > 50 else amount, # These are Instagram restrictions, you can only specify <= 50 } while True: if end_cursor: variables["after"] = end_cursor data = self.public_graphql_request( variables, query_hash="e7e2f4da4b02303f74f0841279e52d76") page_info = json_value(data, "user", "edge_owner_to_timeline_media", "page_info", default={}) edges = json_value(data, "user", "edge_owner_to_timeline_media", "edges", default=[]) for edge in edges: medias.append(edge["node"]) end_cursor = page_info.get("end_cursor") if not page_info.get("has_next_page") or not end_cursor: break if len(medias) >= amount: break time.sleep(sleep) return [extract_media_gql(media) for media in medias[:amount]]
def user_medias_paginated_gql(self, user_id: int, amount: int = 0, sleep: int = 2, end_cursor=None) -> Tuple[List[Media], str]: """ Get a page of a user's media by Public Graphql API Parameters ---------- user_id: int amount: int, optional Maximum number of media to return, default is 0 (all medias) sleep: int, optional Timeout between pages iterations, default is 2 end_cursor: str, optional Cursor value to start at, obtained from previous call to this method Returns ------- Tuple[List[Media], str] A tuple containing a list of medias and the next end_cursor value """ amount = int(amount) user_id = int(user_id) medias = [] variables = { "id": user_id, "first": 50 if not amount or amount > 50 else amount, # These are Instagram restrictions, you can only specify <= 50 } variables["after"] = end_cursor data = self.public_graphql_request( variables, query_hash="e7e2f4da4b02303f74f0841279e52d76") page_info = json_value(data, "user", "edge_owner_to_timeline_media", "page_info", default={}) edges = json_value(data, "user", "edge_owner_to_timeline_media", "edges", default=[]) for edge in edges: medias.append(edge["node"]) end_cursor = page_info.get("end_cursor") if amount: medias = medias[:amount] return ([extract_media_gql(media) for media in medias], end_cursor)
def insights_account(self) -> Dict: """ Get insights for account Returns ------- Dict A dictionary of response from the call """ assert self.user_id, "Login required" data = { "surface": "account", "doc_id": 2449243051851783, "locale": "en_US", "vc_policy": "insights_policy", "strip_nulls": False, "strip_defaults": False, } query_params = { "IgInsightsGridMediaImage_SIZE": 360, "activityTab": True, "audienceTab": True, "contentTab": True, "query_params": {"access_token": "", "id": self.user_id}, } result = self.private_request( "ads/graphql/", self.with_query_params(data, query_params), ) res = json_value(result, "data", "shadow_instagram_user", "business_manager") if not res: raise UserError("Account is not business account", **self.last_json) return res
def close_friend_remove(self, user_id: str): """ Remove from Close Friends List Parameters ---------- user_id: str Unique identifier of a User Returns ------- bool A boolean value """ assert self.user_id, "Login required" user_id = str(user_id) data = { "block_on_empty_thread_creation": "false", "module": "CLOSE_FRIENDS_V2_SEARCH", "source": "audience_manager", "_uid": self.user_id, "_uuid": self.uuid, "remove": [user_id], "add": [] } result = self.private_request("friendships/set_besties/", data) return json_value(result, "friendship_statuses", user_id, "is_bestie") == False
def public_a1_request(self, endpoint, data=None, params=None, headers=None): url = self.PUBLIC_API_URL + endpoint.lstrip("/") params = params or {} params.update({"__a": 1, '__d': 'dis'}) response = self.public_request(url, data=data, params=params, headers=headers, return_json=True) try: return response["graphql"] except KeyError as e: error_type = response.get("error_type") if error_type == "generic_request_error": raise GenericRequestError( json_value(response, "errors", "error", 0, default=error_type), **response) raise e
def track_info_by_canonical_id(self, music_canonical_id: str) -> Track: """ Get Track by music_canonical_id Parameters ---------- music_canonical_id: str Unique identifier of the track Returns ------- Track An object of Track type """ data = { "tab_type": "clips", "referrer_media_id": "", "_uuid": self.uuid, "music_canonical_id": str(music_canonical_id), } result = self.private_request("clips/music/", data) track = json_value(result, "metadata", "music_info", "music_asset_info") return extract_track(track)
def insights_media_feed_all( self, post_type: str = "ALL", time_frame: str = "TWO_YEARS", data_ordering: str = "REACH_COUNT", count: int = 0, sleep: int = 2, ) -> List[Dict]: """ Get insights for all medias from feed with page iteration with cursor and sleep timeout Parameters ---------- post_type: str, optional Types of posts, default is "ALL" Options: ("ALL", "CAROUSEL_V2", "IMAGE", "SHOPPING", "VIDEO") time_frame: str, optional Time frame to pull media insights, default is "TWO_YEARS" Options: ("ONE_WEEK", "ONE_MONTH", "THREE_MONTHS", "SIX_MONTHS", "ONE_YEAR", "TWO_YEARS") data_ordering: str, optional Ordering strategy for the data, default is "REACH_COUNT" Options: ("REACH_COUNT", "LIKE_COUNT", "FOLLOW", "SHARE_COUNT", "BIO_LINK_CLICK", "COMMENT_COUNT", "IMPRESSION_COUNT", "PROFILE_VIEW", "VIDEO_VIEW_COUNT", "SAVE_COUNT"...) count: int, optional Max media count for retrieving, default is 0 sleep: int, optional Timeout between pages iterations, default is 2 Returns ------- List[Dict] List of dictionaries of response from the call """ assert self.user_id, "Login required" supported_post_types = ("ALL", "CAROUSEL_V2", "IMAGE", "SHOPPING", "VIDEO") supported_time_frames = ( "ONE_WEEK", "ONE_MONTH", "THREE_MONTHS", "SIX_MONTHS", "ONE_YEAR", "TWO_YEARS", ) assert post_type in supported_post_types, "Unsupported post type" assert time_frame in supported_time_frames, "Unsupported time frame" medias = [] cursor = None data = { "surface": "post_grid", "doc_id": 2345520318892697, "locale": "en_US", "vc_policy": "insights_policy", "strip_nulls": False, "strip_defaults": False, } query_params = { "IgInsightsGridMediaImage_SIZE": 480, "count": 200, # TODO Try to detect max allowed value # "cursor": "0", "dataOrdering": data_ordering, "postType": post_type, "timeframe": time_frame, "search_base": "USER", "is_user": "******", "queryParams": {"access_token": "", "id": self.user_id}, } while True: if cursor: query_params["cursor"] = cursor result = self.private_request( "ads/graphql/", self.with_query_params(data, query_params), ) if not json_value( result, "data", "shadow_instagram_user", "business_manager", default=None, ): raise UserError("Account is not business account", **self.last_json) stats = result["data"]["shadow_instagram_user"]["business_manager"][ "top_posts_unit" ]["top_posts"] cursor = stats["page_info"]["end_cursor"] medias.extend(stats["edges"]) if not stats["page_info"]["has_next_page"]: break if count and len(medias) >= count: break time.sleep(sleep) if count: medias = medias[:count] return medias
def insights_media_feed_all( self, post_type: POST_TYPE = "ALL", time_frame: TIME_FRAME = "TWO_YEARS", data_ordering: DATA_ORDERING = "REACH_COUNT", count: int = 0, sleep: int = 2, ) -> List[Dict]: """ Get insights for all medias from feed with page iteration with cursor and sleep timeout Parameters ---------- post_type: str, optional Types of posts, default is "ALL" time_frame: str, optional Time frame to pull media insights, default is "TWO_YEARS" data_ordering: str, optional Ordering strategy for the data, default is "REACH_COUNT" count: int, optional Max media count for retrieving, default is 0 sleep: int, optional Timeout between pages iterations, default is 2 Returns ------- List[Dict] List of dictionaries of response from the call """ assert post_type in POST_TYPES, \ f'Unsupported post_type="{post_type}" {POST_TYPES}' assert time_frame in TIME_FRAMES, \ f'Unsupported time_frame="{time_frame}" {TIME_FRAMES}' assert data_ordering in DATA_ORDERS, \ f'Unsupported data_ordering="{data_ordering}" {DATA_ORDERS}' assert self.user_id, "Login required" medias = [] cursor = None data = { "surface": "post_grid", "doc_id": 2345520318892697, "locale": "en_US", "vc_policy": "insights_policy", "strip_nulls": False, "strip_defaults": False, } query_params = { "IgInsightsGridMediaImage_SIZE": 480, "count": 200, # TODO Try to detect max allowed value # "cursor": "0", "dataOrdering": data_ordering, "postType": post_type, "timeframe": time_frame, "search_base": "USER", "is_user": "******", "queryParams": {"access_token": "", "id": self.user_id}, } while True: if cursor: query_params["cursor"] = cursor result = self.private_request( "ads/graphql/", self.with_query_params(data, query_params), ) if not json_value( result, "data", "shadow_instagram_user", "business_manager", default=None, ): raise UserError("Account is not business account", **self.last_json) stats = json_value( result, "data", "shadow_instagram_user", "business_manager", "top_posts_unit", "top_posts" ) cursor = stats["page_info"]["end_cursor"] medias.extend(stats["edges"]) if not stats["page_info"]["has_next_page"]: break if count and len(medias) >= count: break time.sleep(sleep) if count: medias = medias[:count] return medias