diff --git a/docs/as-module.rst b/docs/as-module.rst index 6062d65..e838901 100644 --- a/docs/as-module.rst +++ b/docs/as-module.rst @@ -74,7 +74,10 @@ metadata of a Profile. :class:`Profile` instances can be created with: profile = Profile.from_username(L.context, USERNAME) - :meth:`Profile_from_userid` - given its User ID (currently requires to be logged in). + given its User ID. This allows to easily lookup a Profile's username given + its ID:: + + Profile.from_id(L.context, USERID).username - :meth:`Profile.get_followees` Profiles that are followed by given user. diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index 4c54883..88cac31 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -629,10 +629,6 @@ class Instaloader: else: self.context.log("Trying to find profile {0} using its unique ID {1}.".format(profile_name, profile_id)) - if not self.context.is_logged_in: - self.context.error("Profile {} changed its name. " - "If you use --login=USERNAME, I can find out the new name.") - raise LoginRequiredException("--login=USERNAME required to obtain profile name from its ID number") profile_from_id = Profile.from_id(self.context, profile_id) newname = profile_from_id.username self.context.log("Profile {0} has changed its name to {1}.".format(profile_name, newname)) diff --git a/instaloader/instaloadercontext.py b/instaloader/instaloadercontext.py index c01c58a..f7ac2ff 100644 --- a/instaloader/instaloadercontext.py +++ b/instaloader/instaloadercontext.py @@ -56,6 +56,7 @@ class InstaloaderContext: self.quiet = quiet self.max_connection_attempts = max_connection_attempts self._graphql_page_length = 50 + self._root_rhx_gis = None # error log, filled with error() and printed at the end of Instaloader.main() self.error_log = [] @@ -376,3 +377,14 @@ class InstaloaderContext: except KeyboardInterrupt: self.error("[skipped by user]", repeat_at_end=False) raise ConnectionException(error_string) from err + + @property + def root_rhx_gis(self) -> Optional[str]: + """rhx_gis string returned in the / query.""" + if self.is_logged_in: + # At the moment, rhx_gis seems to be required for anonymous requests only. By returning None when logged + # in, we can save the root_rhx_gis lookup query. + return None + if not self._root_rhx_gis: + self._root_rhx_gis = self.get_json('', {})['rhx_gis'] + return self._root_rhx_gis diff --git a/instaloader/structures.py b/instaloader/structures.py index 10b6a59..ce56ce5 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -145,8 +145,8 @@ class Post: else: # Sometimes, the 'owner' structure does not contain the username, only the user's ID. In that case, # this call triggers downloading of the complete Post metadata struct, where the owner username - # is contained. This is better than to get the username by user ID, since it is possible anonymously - # and gives us other information that is more likely to be usable. + # is contained. This is better than to get the username by user ID, since it + # gives us other information that is more likely to be usable. owner_struct = self._full_metadata['owner'] if 'username' in owner_struct: self._owner_profile = Profile(self._context, owner_struct) @@ -379,17 +379,16 @@ class Profile: @classmethod def from_id(cls, context: InstaloaderContext, profile_id: int): - """If logged in, create a Profile instance from a given userid. If possible, use :meth:`Profile.from_username` - or constructor directly rather than this method, since does many requests. + """Create a Profile instance from a given userid. If possible, use :meth:`Profile.from_username` + or constructor directly rather than this method, since it does many requests. :param context: :attr:`Instaloader.context` :param profile_id: userid - :raises: :class:`ProfileNotExistsException`, :class:`LoginRequiredException`, :class:`ProfileHasNoPicsException` + :raises: :class:`ProfileNotExistsException`, :class:`ProfileHasNoPicsException` """ - if not context.is_logged_in: - raise LoginRequiredException("--login=USERNAME required to obtain profile metadata from its ID number.") data = context.graphql_query("472f257a40c653c64c666ce877d59d2b", - {'id': str(profile_id), 'first': 1})['data']['user'] + {'id': str(profile_id), 'first': 1}, + rhx_gis=context.root_rhx_gis)['data']['user'] if data: data = data["edge_owner_to_timeline_media"] else: @@ -400,7 +399,7 @@ class Profile: raise ProfileHasNoPicsException("Profile with ID {0}: no pics found.".format(str(profile_id))) else: raise LoginRequiredException("Login required to determine username (ID: " + str(profile_id) + ").") - username = Post.from_mediaid(context, int(data['edges'][0]["node"]["id"])).owner_username + username = Post.from_shortcode(context, data['edges'][0]["node"]["shortcode"]).owner_username return cls(context, {'username': username.lower(), 'id': profile_id}) def _asdict(self): diff --git a/test/instaloader_unittests.py b/test/instaloader_unittests.py index 57b11d8..f193706 100644 --- a/test/instaloader_unittests.py +++ b/test/instaloader_unittests.py @@ -54,6 +54,10 @@ class TestInstaloaderAnonymously(unittest.TestCase): self.assertEqual(PUBLIC_PROFILE_ID, instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).userid) + def test_get_username_by_id(self): + self.assertEqual(PUBLIC_PROFILE.lower(), + instaloader.Profile.from_id(self.L.context, PUBLIC_PROFILE_ID).username) + def test_post_from_mediaid(self): for post in instaloader.Profile.from_username(self.L.context, PUBLIC_PROFILE).get_posts(): post2 = instaloader.Post.from_mediaid(self.L.context, post.mediaid)