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 fromc355338010
. Also, this reverts "Profile: don't access removed iphone info endpoint"08327c4117
. This fixes #209.
This commit is contained in:
parent
27a239f07f
commit
26e74bad6a
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user