diff --git a/instaloader/structures.py b/instaloader/structures.py index df5c60c..1f4ca60 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -889,7 +889,8 @@ class Profile: Also, this class implements == and is hashable. """ def __init__(self, context: InstaloaderContext, node: Dict[str, Any]): - assert 'username' in node + assert "username" in node + assert "id" in node or "pk" in node self._context = context self._has_public_story: Optional[bool] = None self._node = node @@ -909,10 +910,13 @@ class Profile: :param username: Username :raises: :class:`ProfileNotExistsException` """ - # pylint:disable=protected-access - profile = cls(context, {'username': username.lower()}) - profile._obtain_metadata() # to raise ProfileNotExistsException now in case username is invalid - return profile + data = context.doc_id_graphql_query("26347858941511777", {"hasQuery": True, "query": username})["data"] + if data: + for user in data["xdt_api__v1__fbsearch__non_profiled_serp"]["users"]: + if user["username"].lower() == username.lower(): + return cls(context, user) + + raise ProfileNotExistsException("Profile {} does not exist.".format(username)) @classmethod def from_id(cls, context: InstaloaderContext, profile_id: int): @@ -979,11 +983,21 @@ class Profile: def _obtain_metadata(self): try: if not self._has_full_metadata: - metadata = self._context.get_iphone_json(f'api/v1/users/web_profile_info/?username={self.username}', - params={}) - if metadata['data']['user'] is None: + user_id = self._node.get('id') or self._node.get('pk') + variables = { + "id": str(user_id), + "render_surface": "PROFILE", + "__relay_internal__pv__PolarisCannesGuardianExperienceEnabledrelayprovider": True, + "__relay_internal__pv__PolarisCASB976ProfileEnabledrelayprovider": False, + "__relay_internal__pv__PolarisRepostsConsumptionEnabledrelayprovider": False, + } + data = self._context.doc_id_graphql_query('25980296051578533', variables) + if data is None: + raise QueryReturnedNotFoundException('GraphQL query returned None') + user_data = data.get('data', {}).get('user') + if user_data is None: raise ProfileNotExistsException('Profile {} does not exist.'.format(self.username)) - self._node = metadata['data']['user'] + self._node = self._normalize_profile_data(user_data) self._has_full_metadata = True except (QueryReturnedNotFoundException, KeyError) as err: top_search_results = TopSearchResults(self._context, self.username) @@ -998,6 +1012,44 @@ class Profile: ', '.join(similar_profiles[0:5]))) from err raise ProfileNotExistsException('Profile {} does not exist.'.format(self.username)) from err + def _normalize_profile_data(self, user_data: Dict[str, Any]) -> Dict[str, Any]: + """Normalize PolarisProfilePageContentQuery response to match legacy format.""" + normalized = user_data.copy() + if 'id' not in normalized and 'pk' in normalized: + normalized['id'] = normalized['pk'] + if 'edge_owner_to_timeline_media' not in normalized and 'media_count' in normalized: + normalized['edge_owner_to_timeline_media'] = {'count': normalized['media_count']} + if 'edge_felix_video_timeline' not in normalized: + normalized['edge_felix_video_timeline'] = {'count': 0} + if 'edge_followed_by' not in normalized and 'follower_count' in normalized: + normalized['edge_followed_by'] = {'count': normalized['follower_count']} + if 'edge_follow' not in normalized and 'following_count' in normalized: + normalized['edge_follow'] = {'count': normalized['following_count']} + if 'is_business_account' not in normalized and 'is_business' in normalized: + normalized['is_business_account'] = normalized['is_business'] + if 'business_category_name' not in normalized and 'category' in normalized: + normalized['business_category_name'] = normalized['category'] + friendship = normalized.get('friendship_status', {}) or {} + if 'followed_by_viewer' not in normalized: + normalized['followed_by_viewer'] = friendship.get('following', False) + if 'follows_viewer' not in normalized: + normalized['follows_viewer'] = friendship.get('followed_by', False) + if 'blocked_by_viewer' not in normalized: + normalized['blocked_by_viewer'] = friendship.get('blocking', False) + if 'has_blocked_viewer' not in normalized: + normalized['has_blocked_viewer'] = False + if 'has_requested_viewer' not in normalized: + normalized['has_requested_viewer'] = friendship.get('incoming_request', False) + if 'requested_by_viewer' not in normalized: + normalized['requested_by_viewer'] = friendship.get('outgoing_request', False) + if 'profile_pic_url_hd' not in normalized: + hd_info = normalized.get('hd_profile_pic_url_info') + if hd_info and 'url' in hd_info: + normalized['profile_pic_url_hd'] = hd_info['url'] + elif 'profile_pic_url' in normalized: + normalized['profile_pic_url_hd'] = normalized['profile_pic_url'] + return normalized + def _metadata(self, *keys) -> Any: try: d = self._node