Compare commits

...
8 Commits
Author SHA1 Message Date
Alexander Graf dcea0e9672 Release of version 4.0.7 2018-08-05 09:37:18 +02:00
Alexander Graf 809b23a996 doc: Mark Profile.has_higlight_reels as deprecated 2018-08-05 09:13:49 +02:00
Alexander Graf c634f21dcc Fix anon-retry for profiles that blocked viewer
This was broken with 50a5330fec.
2018-08-05 09:01:22 +02:00
Alexander Graf a070ed42ae get_stories(): Fetch in chunks of 100 users
It seems there is a limit of how many user stories can be requested at once.
Now, Instaloader.get_stories() chunks the stories request.

Fixes #157 and fixes #134.
2018-08-05 08:06:53 +02:00
Alexander Graf a34d750152 rate handling: Show %H:%M when request is retried 2018-08-05 08:02:35 +02:00
Lars Lindqvist bfa5d8ed41 Fix :stories
50a5330fec breaks `:stories` if userids is None (non-iterable).
2018-08-05 03:29:24 +02:00
Alexander Graf 7f81985911 _ArbitraryItemFormatter: Allow chaining attributes
Override get_value() rather than get_field(), to allow chaining of item
attributes, such as --post-metadata-txt={owner_profile.profile_pic_url}.

Motivated by #151.
2018-08-04 11:41:19 +02:00
Lars Lindqvist b5aa268344 Fix owner_profile for Profile.get_tagged_posts() 2018-08-04 09:41:23 +02:00
5 changed files with 58 additions and 28 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
__version__ = '4.0.6'
__version__ = '4.0.7'
try:
+33 -14
View File
@@ -90,6 +90,7 @@ def _main(instaloader: Instaloader, targetlist: List[str],
instaloader.context.log("Logged in as %s." % username)
# Try block for KeyboardInterrupt (save session on ^C)
profiles = set()
anonymous_retry_profiles = set()
try:
# Generate set of profiles, already downloading non-profile targets
for target in targetlist:
@@ -136,7 +137,28 @@ def _main(instaloader: Instaloader, targetlist: List[str],
instaloader.download_saved_posts(fast_update=fast_update, max_count=max_count,
post_filter=post_filter)
else:
profiles.add(instaloader.check_profile_id(target))
try:
profile = instaloader.check_profile_id(target)
if instaloader.context.is_logged_in and profile.has_blocked_viewer:
if not stories_only and not profile.is_private:
raise ProfileNotExistsException("{} blocked you; But she's public, "
"so we just download her anonymously.".format(target))
else:
instaloader.context.error("{} blocked you.".format(target))
else:
profiles.add(profile)
except ProfileNotExistsException as err:
# Not only our profile.has_blocked_viewer condition raises ProfileNotExistsException,
# check_profile_id() also does, since access to blocked profile may be responded with 404.
if instaloader.context.is_logged_in and not stories_only:
instaloader.context.log(err)
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
with instaloader.anonymous_copy() as anonymous_loader:
with instaloader.context.error_catcher():
anonymous_retry_profiles.add(anonymous_loader.check_profile_id(target))
instaloader.context.log("Looks good.")
else:
raise
if len(profiles) > 1:
instaloader.context.log("Downloading {} profiles: {}".format(len(profiles),
' '.join([p.username for p in profiles])))
@@ -144,19 +166,16 @@ def _main(instaloader: Instaloader, targetlist: List[str],
# Iterate through profiles list and download them
for target in profiles:
with instaloader.context.error_catcher(target):
try:
instaloader.download_profile(target, profile_pic, profile_pic_only,
fast_update, post_filter=post_filter)
except ProfileNotExistsException as err:
if instaloader.context.is_logged_in and not stories_only:
instaloader.context.log(err)
instaloader.context.log("Trying again anonymously, helps in case you are just blocked.")
with instaloader.anonymous_copy() as anonymous_loader:
with instaloader.context.error_catcher():
anonymous_loader.download_profile(target, profile_pic, profile_pic_only,
fast_update, post_filter=post_filter)
else:
raise
instaloader.download_profile(target, profile_pic, profile_pic_only,
fast_update, post_filter=post_filter)
if anonymous_retry_profiles:
instaloader.context.log("Downloading anonymously: {}"
.format(' '.join([p.username for p in anonymous_retry_profiles])))
with instaloader.anonymous_copy() as anonymous_loader:
for target in anonymous_retry_profiles:
with instaloader.context.error_catcher(target):
anonymous_loader.download_profile(target, profile_pic, profile_pic_only,
fast_update, post_filter=post_filter)
if stories or stories_only:
with instaloader.context.error_catcher("Download stories"):
instaloader.context.log("Downloading stories")
+16 -9
View File
@@ -49,11 +49,11 @@ class _ArbitraryItemFormatter(string.Formatter):
def __init__(self, item: Any):
self._item = item
def get_field(self, field_name, args, kwargs):
def get_value(self, key, args, kwargs):
"""Override to substitute {ATTRIBUTE} by attributes of our _item."""
if hasattr(self._item, field_name):
return self._item.__getattribute__(field_name), None
return super().get_field(field_name, args, kwargs)
if hasattr(self._item, key):
return getattr(self._item, key)
return super().get_value(key, args, kwargs)
def format_field(self, value, format_spec):
"""Override :meth:`string.Formatter.format_field` to have our
@@ -396,17 +396,22 @@ class Instaloader:
:param userids: List of user IDs to be processed in terms of downloading their stories, or None.
"""
if userids is None:
if not userids:
data = self.context.graphql_query("d15efd8c0c5b23f0ef71f18bf363c704",
{"only_stories": True})["data"]["user"]
if data is None:
raise BadResponseException('Bad stories reel JSON.')
userids = list(edge["node"]["id"] for edge in data["feed_reels_tray"]["edge_reels_tray_to_reel"]["edges"])
stories = self.context.graphql_query("bf41e22b1c4ba4c9f31b844ebb7d9056",
{"reel_ids": userids, "precomposed_overlay": False})["data"]
def _userid_chunks():
userids_per_query = 100
for i in range(0, len(userids), userids_per_query):
yield userids[i:i + userids_per_query]
yield from (Story(self.context, media) for media in stories['reels_media'])
for userid_chunk in _userid_chunks():
stories = self.context.graphql_query("bf41e22b1c4ba4c9f31b844ebb7d9056",
{"reel_ids": userid_chunk, "precomposed_overlay": False})["data"]
yield from (Story(self.context, media) for media in stories['reels_media'])
@_requires_login
def download_stories(self,
@@ -428,8 +433,10 @@ class Instaloader:
if not userids:
self.context.log("Retrieving all visible stories...")
else:
userids = [p if isinstance(p, int) else p.userid for p in userids]
for user_story in self.get_stories([p if isinstance(p, int) else p.userid for p in userids]):
for user_story in self.get_stories(userids):
name = user_story.owner_username
self.context.log("Retrieving stories from profile {}.".format(name))
totalcount = user_story.itemcount
+5 -2
View File
@@ -9,6 +9,7 @@ import textwrap
import time
import urllib.parse
from contextlib import contextmanager
from datetime import datetime, timedelta
from typing import Any, Callable, Dict, Iterator, Optional
import requests
@@ -246,7 +247,8 @@ class InstaloaderContext:
if is_graphql_query and not query_not_limited:
waittime = graphql_query_waittime()
if waittime > 0:
self.log('\nToo many queries in the last time. Need to wait {} seconds.'.format(waittime))
self.log('\nToo many queries in the last time. Need to wait {} seconds, until {:%H:%M}.'
.format(waittime, datetime.now() + timedelta(seconds=waittime)))
time.sleep(waittime)
if self.query_timestamps is not None:
self.query_timestamps.append(time.monotonic())
@@ -301,7 +303,8 @@ class InstaloaderContext:
if is_graphql_query:
waittime = graphql_query_waittime(untracked_queries=True)
if waittime > 0:
self.log('The request will be retried in {} seconds.'.format(waittime))
self.log('The request will be retried in {} seconds, at {:%H:%M}.'
.format(waittime, datetime.now() + timedelta(seconds=waittime)))
time.sleep(waittime)
self._sleep()
return self.get_json(path=path, params=params, host=host, session=sess, _attempt=_attempt + 1)
+3 -2
View File
@@ -526,7 +526,8 @@ class Profile:
@property
def has_highlight_reels(self) -> bool:
"""
Always returns `True` since :issue:`153`.
.. deprecated:: 4.0.6
Always returns `True` since :issue:`153`.
Before broken, this indicated whether the :class:`Profile` had available stories.
"""
@@ -613,7 +614,7 @@ class Profile:
def get_tagged_posts(self) -> Iterator[Post]:
"""Retrieve all posts where a profile is tagged."""
self._obtain_metadata()
yield from (Post(self._context, node, self) for node in
yield from (Post(self._context, node, self if int(node['owner']['id']) == self.userid else None) for node in
self._context.graphql_node_list("e31a871f7301132ceaab56507a66bbb7",
{'id': self.userid},
'https://www.instagram.com/{0}/'.format(self.username),