Change download of stories when not using :stories

If using --stories or --stories-only the stories got donwloaded along
with the profiles one by one. Now, the stories get downloaded in a
similar aproach like when using the :stories target, i.e.
download_stories() gets only called once.
Profile.has_highlight_reels is broken and now always returns true.
This fixes #153.
This commit is contained in:
André Koch-Kramer 2018-08-02 16:20:27 +02:00
parent 1739844758
commit 50a5330fec
3 changed files with 64 additions and 43 deletions

View File

@ -121,8 +121,9 @@ def _main(instaloader: Instaloader, targetlist: List[str],
if target[0] == '@': if target[0] == '@':
instaloader.context.log("Retrieving followees of %s..." % target[1:]) instaloader.context.log("Retrieving followees of %s..." % target[1:])
profile = Profile.from_username(instaloader.context, target[1:]) profile = Profile.from_username(instaloader.context, target[1:])
followees = profile.get_followees() for followee in profile.get_followees():
profiles.update([followee.username for followee in followees]) instaloader.save_profile_id(followee)
profiles.add(followee)
elif target[0] == '#': elif target[0] == '#':
instaloader.download_hashtag(hashtag=target[1:], max_count=max_count, fast_update=fast_update, instaloader.download_hashtag(hashtag=target[1:], max_count=max_count, fast_update=fast_update,
post_filter=post_filter) post_filter=post_filter)
@ -135,26 +136,32 @@ def _main(instaloader: Instaloader, targetlist: List[str],
instaloader.download_saved_posts(fast_update=fast_update, max_count=max_count, instaloader.download_saved_posts(fast_update=fast_update, max_count=max_count,
post_filter=post_filter) post_filter=post_filter)
else: else:
profiles.add(target) profiles.add(instaloader.check_profile_id(target))
if len(profiles) > 1: if len(profiles) > 1:
instaloader.context.log("Downloading {} profiles: {}".format(len(profiles), ' '.join(profiles))) instaloader.context.log("Downloading {} profiles: {}".format(len(profiles),
# Iterate through profiles list and download them ' '.join([p.username for p in profiles])))
for target in profiles: if not stories_only:
with instaloader.context.error_catcher(target): # Iterate through profiles list and download them
try: for target in profiles:
instaloader.download_profile(target, profile_pic, profile_pic_only, fast_update, with instaloader.context.error_catcher(target):
stories, stories_only, post_filter=post_filter, try:
storyitem_filter=storyitem_filter) instaloader.download_profile(target, profile_pic, profile_pic_only,
except ProfileNotExistsException as err: fast_update, post_filter=post_filter)
if instaloader.context.is_logged_in: except ProfileNotExistsException as err:
instaloader.context.log(err) if instaloader.context.is_logged_in and not stories_only:
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.") instaloader.context.log(err)
with instaloader.anonymous_copy() as anonymous_loader: instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
with instaloader.context.error_catcher(): with instaloader.anonymous_copy() as anonymous_loader:
anonymous_loader.download_profile(target, profile_pic, profile_pic_only, with instaloader.context.error_catcher():
fast_update, post_filter=post_filter) anonymous_loader.download_profile(target, profile_pic, profile_pic_only,
else: fast_update, post_filter=post_filter)
raise else:
raise
if stories or stories_only:
with instaloader.context.error_catcher("Download stories"):
instaloader.context.log("Downloading stories")
instaloader.download_stories(userids=list(profiles), fast_update=fast_update,
filename_target=None, storyitem_filter=storyitem_filter)
except KeyboardInterrupt: except KeyboardInterrupt:
print("\nInterrupted by user.", file=sys.stderr) print("\nInterrupted by user.", file=sys.stderr)
# Save session if it is useful # Save session if it is useful

View File

@ -11,7 +11,7 @@ from contextlib import contextmanager, suppress
from datetime import datetime, timezone from datetime import datetime, timezone
from functools import wraps from functools import wraps
from io import BytesIO from io import BytesIO
from typing import Callable, Iterator, List, Optional, Any from typing import Any, Callable, Iterator, List, Optional, Union
from .exceptions import * from .exceptions import *
from .instaloadercontext import InstaloaderContext from .instaloadercontext import InstaloaderContext
@ -410,25 +410,26 @@ class Instaloader:
@_requires_login @_requires_login
def download_stories(self, def download_stories(self,
userids: Optional[List[int]] = None, userids: Optional[List[Union[int, Profile]]] = None,
fast_update: bool = False, fast_update: bool = False,
filename_target: str = ':stories', filename_target: Optional[str] = ':stories',
storyitem_filter: Optional[Callable[[StoryItem], bool]] = None) -> None: storyitem_filter: Optional[Callable[[StoryItem], bool]] = None) -> None:
""" """
Download available stories from user followees or all stories of users whose ID are given. Download available stories from user followees or all stories of users whose ID are given.
Does not mark stories as seen. Does not mark stories as seen.
To use this, one needs to be logged in To use this, one needs to be logged in
:param userids: List of user IDs to be processed in terms of downloading their stories :param userids: List of user IDs or Profiles to be processed in terms of downloading their stories
:param fast_update: If true, abort when first already-downloaded picture is encountered :param fast_update: If true, abort when first already-downloaded picture is encountered
:param filename_target: Replacement for {target} in dirname_pattern and filename_pattern :param filename_target: Replacement for {target} in dirname_pattern and filename_pattern
or None if profile name should be used instead
:param storyitem_filter: function(storyitem), which returns True if given StoryItem should be downloaded :param storyitem_filter: function(storyitem), which returns True if given StoryItem should be downloaded
""" """
if not userids: if not userids:
self.context.log("Retrieving all visible stories...") self.context.log("Retrieving all visible stories...")
for user_story in self.get_stories(userids): for user_story in self.get_stories([p if isinstance(p, int) else p.userid for p in userids]):
name = user_story.owner_username name = user_story.owner_username
self.context.log("Retrieving stories from profile {}.".format(name)) self.context.log("Retrieving stories from profile {}.".format(name))
totalcount = user_story.itemcount totalcount = user_story.itemcount
@ -440,7 +441,7 @@ class Instaloader:
self.context.log("[%3i/%3i] " % (count, totalcount), end="", flush=True) self.context.log("[%3i/%3i] " % (count, totalcount), end="", flush=True)
count += 1 count += 1
with self.context.error_catcher('Download story from user {}'.format(name)): with self.context.error_catcher('Download story from user {}'.format(name)):
downloaded = self.download_storyitem(item, filename_target) downloaded = self.download_storyitem(item, filename_target if filename_target else name)
if fast_update and not downloaded: if fast_update and not downloaded:
break break
@ -611,6 +612,24 @@ class Instaloader:
if fast_update and not downloaded: if fast_update and not downloaded:
break break
def _get_id_filename(self, profile_name: str) -> str:
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
format_string_contains_key(self.dirname_pattern, 'target'))):
return '{0}/id'.format(self.dirname_pattern.format(profile=profile_name.lower(),
target=profile_name.lower()))
else:
return '{0}/{1}_id'.format(self.dirname_pattern.format(), profile_name.lower())
def save_profile_id(self, profile: Profile):
"""
Store ID of profile locally.
"""
os.makedirs(self.dirname_pattern.format(profile=profile.username,
target=profile.username), exist_ok=True)
with open(self._get_id_filename(profile.username), 'w') as text_file:
text_file.write(str(profile.userid) + "\n")
self.context.log("Stored ID {0} for profile {1}.".format(profile.userid, profile.username))
def check_profile_id(self, profile_name: str) -> Profile: def check_profile_id(self, profile_name: str) -> Profile:
""" """
Consult locally stored ID of profile with given name, check whether ID matches and whether name Consult locally stored ID of profile with given name, check whether ID matches and whether name
@ -623,12 +642,7 @@ class Instaloader:
with suppress(ProfileNotExistsException): with suppress(ProfileNotExistsException):
profile = Profile.from_username(self.context, profile_name) profile = Profile.from_username(self.context, profile_name)
profile_exists = profile is not None profile_exists = profile is not None
if ((format_string_contains_key(self.dirname_pattern, 'profile') or id_filename = self._get_id_filename(profile_name)
format_string_contains_key(self.dirname_pattern, 'target'))):
id_filename = '{0}/id'.format(self.dirname_pattern.format(profile=profile_name.lower(),
target=profile_name.lower()))
else:
id_filename = '{0}/{1}_id'.format(self.dirname_pattern.format(), profile_name.lower())
try: try:
with open(id_filename, 'rb') as id_file: with open(id_filename, 'rb') as id_file:
profile_id = int(id_file.read()) profile_id = int(id_file.read())
@ -657,15 +671,11 @@ class Instaloader:
except (FileNotFoundError, ValueError): except (FileNotFoundError, ValueError):
pass pass
if profile_exists: if profile_exists:
os.makedirs(self.dirname_pattern.format(profile=profile_name.lower(), self.save_profile_id(profile)
target=profile_name.lower()), exist_ok=True)
with open(id_filename, 'w') as text_file:
text_file.write(str(profile.userid) + "\n")
self.context.log("Stored ID {0} for profile {1}.".format(profile.userid, profile_name))
return profile return profile
raise ProfileNotExistsException("Profile {0} does not exist.".format(profile_name)) raise ProfileNotExistsException("Profile {0} does not exist.".format(profile_name))
def download_profile(self, profile_name: str, def download_profile(self, profile_name: Union[str, Profile],
profile_pic: bool = True, profile_pic_only: bool = False, profile_pic: bool = True, profile_pic_only: bool = False,
fast_update: bool = False, fast_update: bool = False,
download_stories: bool = False, download_stories_only: bool = False, download_stories: bool = False, download_stories_only: bool = False,
@ -676,7 +686,10 @@ class Instaloader:
# Get profile main page json # Get profile main page json
# check if profile does exist or name has changed since last download # check if profile does exist or name has changed since last download
# and update name and json data if necessary # and update name and json data if necessary
profile = self.check_profile_id(profile_name.lower()) if isinstance(profile_name, str):
profile = self.check_profile_id(profile_name.lower())
else:
profile = profile_name
profile_name = profile.username profile_name = profile.username

View File

@ -526,10 +526,11 @@ class Profile:
@property @property
def has_highlight_reels(self) -> bool: def has_highlight_reels(self) -> bool:
""" """
This becomes `True` if the :class:`Profile` has any stories currently available, Always returns `True` since :issue:`153`.
even if not viewable by the viewer.
Before broken, this indicated whether the :class:`Profile` had available stories.
""" """
return self._iphone_struct['has_highlight_reels'] return True
@property @property
def has_public_story(self) -> bool: def has_public_story(self) -> bool: