From 25b81655477198cc2e63c9cff140398dd3ea18f8 Mon Sep 17 00:00:00 2001 From: Alexander Graf Date: Wed, 11 Apr 2018 10:45:38 +0200 Subject: [PATCH] Various minor fixes * Fix Post.owner_profile in cases where initial owner struct contains id only * Assert node structure is complete enough at Post creation * Fix get_followees() call in _main() * Prefer importing from '.' rather than '.' in __main__ * Fix name of instaloader_unittests ('-' is illegal in module names) * Foreport 5fe2a70374b6e81dc875ad33bf44f1001a4f2fc0 "Fix downloading of hashtags" * Foreport 67ac8f339716c498cddb2f1489eac6104409646c "Enforce being logged in for getting username by id" --- instaloader/__main__.py | 10 ++++----- instaloader/instaloader.py | 22 ++++++++++++++----- instaloader/structures.py | 22 +++++++++++++++++-- ...-unittests.py => instaloader_unittests.py} | 1 + 4 files changed, 42 insertions(+), 13 deletions(-) rename test/{instaloader-unittests.py => instaloader_unittests.py} (98%) diff --git a/instaloader/__main__.py b/instaloader/__main__.py index f8d62c2..20e61ae 100644 --- a/instaloader/__main__.py +++ b/instaloader/__main__.py @@ -6,11 +6,10 @@ import sys from argparse import ArgumentParser, SUPPRESS from typing import Callable, List, Optional -from . import __version__ -from .exceptions import * -from .instaloader import Instaloader, Tristate, get_default_session_filename +from . import (Instaloader, InstaloaderException, InvalidArgumentException, Post, Profile, ProfileNotExistsException, + Tristate, __version__) +from .instaloader import get_default_session_filename from .instaloadercontext import default_user_agent -from .structures import Post def usage_string(): @@ -92,7 +91,8 @@ def _main(instaloader: Instaloader, targetlist: List[str], with instaloader.context.error_catcher(target): if target[0] == '@': instaloader.context.log("Retrieving followees of %s..." % target[1:]) - profiles.update([followee['username'] for followee in instaloader.get_followees(target[1:])]) + followees = instaloader.get_followees(Profile.from_username(instaloader.context, target[1:])) + profiles.update([followee['username'] for followee in followees]) elif target[0] == '#': instaloader.download_hashtag(hashtag=target[1:], max_count=max_count, fast_update=fast_update, filter_func=filter_func) diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index 5156986..c9305b4 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -607,12 +607,18 @@ class Instaloader: def get_hashtag_posts(self, hashtag: str) -> Iterator[Post]: """Get Posts associated with a #hashtag.""" - yield from (Post(self.context, node) - for node in self.context.graphql_node_list("298b92c8d7cad703f7565aa892ede943", - {'tag_name': hashtag}, - 'https://www.instagram.com/explore/tags/{0}/' - .format(hashtag), - lambda d: d['data']['hashtag']['edge_hashtag_to_media'])) + has_next_page = True + end_cursor = None + while has_next_page: + if end_cursor: + params = {'__a': 1, 'max_id': end_cursor} + else: + params = {'__a': 1} + hashtag_data = self.context.get_json('explore/tags/{0}/'.format(hashtag), + params)['graphql']['hashtag']['edge_hashtag_to_media'] + yield from (Post(self.context, edge['node']) for edge in hashtag_data['edges']) + has_next_page = hashtag_data['page_info']['has_next_page'] + end_cursor = hashtag_data['page_info']['end_cursor'] def download_hashtag(self, hashtag: str, max_count: Optional[int] = None, @@ -675,6 +681,10 @@ 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/structures.py b/instaloader/structures.py index 53758c7..87902ca 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -43,7 +43,15 @@ class Post: :param node: Node structure, as returned by Instagram. :param owner_profile: The Profile of the owner, if already known at creation. """ - assert 'shortcode' in node + + # Ensure node contains all the data that is accessed via self._node + assert 'shortcode' in node or 'code' in node + assert 'id' in node + assert 'owner' in node + assert 'date' in node or 'taken_at_timestamp' in node + assert 'display_url' in node or 'display_src' in node + assert 'is_video' in node + self._context = context self._node = node self._owner_profile = owner_profile @@ -109,10 +117,18 @@ class Post: @property def owner_profile(self) -> 'Profile': if not self._owner_profile: - owner_struct = self._field('owner') + if 'username' in self._node['owner']: + owner_struct = self._node['owner'] + 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. + owner_struct = self._full_metadata['owner'] if 'username' in owner_struct: self._owner_profile = Profile(self._context, owner_struct) else: + # Fallback, if we still did not get the owner username self._owner_profile = Profile.from_id(self._context, owner_struct['id']) return self._owner_profile @@ -303,6 +319,8 @@ class Profile: @classmethod def from_id(cls, context: InstaloaderContext, profile_id: int): + 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'] if data: diff --git a/test/instaloader-unittests.py b/test/instaloader_unittests.py similarity index 98% rename from test/instaloader-unittests.py rename to test/instaloader_unittests.py index 1e2690b..8e949a5 100644 --- a/test/instaloader-unittests.py +++ b/test/instaloader_unittests.py @@ -91,6 +91,7 @@ class TestInstaloader(unittest.TestCase): print(f['username']) def test_get_username_by_id(self): + self.L.load_session_from_file(OWN_USERNAME) self.assertEqual(PUBLIC_PROFILE.lower(), instaloader.Profile.from_id(self.L.context, PUBLIC_PROFILE_ID).username)