1
0
mirror of https://github.com/instaloader/instaloader.git synced 2024-09-11 16:22:24 +02:00

Download HD profile picture if logged in

Unfortunately, it is now required to be logged in to access the HD version of
profile pictures. When attempting to download profile pictures without --login,
a warning message is printed once, and the lower-quality versions are obtained.

For backwards compatibility, already-downloaded profile pictures are
overwritten if the now-obtainable version is assumed to be of better quality
than the existing one (determined by file size vs Content-Length).

The iPhone endpoint is accessed with code exhumed from
c355338010. Also, this reverts "Profile: don't
access removed iphone info endpoint" 08327c4117.

This fixes #209.
This commit is contained in:
Alexander Graf 2019-01-17 16:36:59 +01:00
parent 27a239f07f
commit 26e74bad6a
4 changed files with 51 additions and 4 deletions

View File

@ -179,6 +179,8 @@ def _main(instaloader: Instaloader, targetlist: List[str],
if len(profiles) > 1:
instaloader.context.log("Downloading {} profiles: {}".format(len(profiles),
' '.join([p.username for p in profiles])))
if profiles and download_profile_pic and not instaloader.context.is_logged_in:
instaloader.context.error("Warning: Use --login to download HD version of profile pictures.")
instaloader.download_profiles(profiles,
download_profile_pic, download_posts, download_tagged, download_highlights,
download_stories, fast_update, post_filter, storyitem_filter)

View File

@ -309,7 +309,9 @@ class Instaloader:
else:
filename = '{0}/{1}_{2}_profile_pic.{3}'.format(self.dirname_pattern.format(), profile.username.lower(),
profile_pic_identifier, profile_pic_extension)
if os.path.isfile(filename):
content_length = profile_pic_response.headers.get('Content-Length', None)
if os.path.isfile(filename) and (not self.context.is_logged_in or
content_length is not None and os.path.getsize(filename) >= int(content_length)):
self.context.log(filename + ' already exists')
return None
self.context.write_raw(profile_pic_bytes if profile_pic_bytes else profile_pic_response, filename)

View File

@ -429,6 +429,24 @@ class InstaloaderContext:
data = _query()
yield from (edge['node'] for edge in data['edges'])
def get_iphone_json(self, path: str, params: Dict[str, Any]) -> Dict[str, Any]:
"""JSON request to ``i.instagram.com``.
:param path: URL, relative to ``i.instagram.com/``
:param params: GET parameters
:return: Decoded response dictionary
:raises QueryReturnedBadRequestException: When the server responds with a 400.
:raises QueryReturnedNotFoundException: When the server responds with a 404.
:raises ConnectionException: When query repeatedly failed.
.. versionadded:: 4.2.1"""
tempsession = copy_session(self._session)
tempsession.headers['User-Agent'] = 'Instagram 10.3.2 (iPhone7,2; iPhone OS 9_3_3; en_US; en-US; ' \
'scale=2.00; 750x1334) AppleWebKit/420+'
for header in ['Host', 'Origin', 'X-Instagram-AJAX', 'X-Requested-With']:
tempsession.headers.pop(header, None)
return self.get_json(path, params, 'i.instagram.com', tempsession)
def write_raw(self, resp: Union[bytes, requests.Response], filename: str) -> None:
"""Write raw response data into a file."""
self.log(filename, end=' ', flush=True)

View File

@ -373,6 +373,10 @@ class Profile:
self._has_public_story = None
self._node = node
self._rhx_gis = None
self._iphone_struct_ = None
if 'iphone_struct' in node:
# if loaded from JSON with load_structure_from_file()
self._iphone_struct_ = node['iphone_struct']
@classmethod
def from_username(cls, context: InstaloaderContext, username: str):
@ -423,6 +427,8 @@ class Profile:
json_node.pop('edge_media_collections', None)
json_node.pop('edge_owner_to_timeline_media', None)
json_node.pop('edge_saved_media', None)
if self._iphone_struct_:
json_node['iphone_struct'] = self._iphone_struct_
return json_node
def _obtain_metadata(self):
@ -447,6 +453,15 @@ class Profile:
d = d[key]
return d
@property
def _iphone_struct(self) -> Dict[str, Any]:
if not self._context.is_logged_in:
raise LoginRequiredException("--login required to access iPhone profile info endpoint.")
if not self._iphone_struct_:
data = self._context.get_iphone_json(path='api/v1/users/{}/info/'.format(self.userid), params={})
self._iphone_struct_ = data['user']
return self._iphone_struct_
@property
def userid(self) -> int:
"""User ID"""
@ -563,10 +578,20 @@ class Profile:
@property
def profile_pic_url(self) -> str:
"""Return URL of profile picture
"""Return URL of profile picture. If logged in, the HD version is returned, otherwise a lower-quality version.
.. versionadded:: 4.0.3"""
return self._metadata("profile_pic_url_hd")
.. versionadded:: 4.0.3
.. versionchanged:: 4.2.1
Require being logged in for HD version (as required by Instagram)."""
if self._context.is_logged_in:
try:
return self._iphone_struct['hd_profile_pic_url_info']['url']
except (InstaloaderException, KeyError) as err:
self._context.error('{} Unable to fetch high quality profile pic.'.format(err))
return self._metadata("profile_pic_url_hd")
else:
return self._metadata("profile_pic_url_hd")
def get_profile_pic_url(self) -> str:
""".. deprecated:: 4.0.3