1
0
mirror of https://github.com/instaloader/instaloader.git synced 2024-11-20 17:22:31 +01:00

Cache Profiles on creation and create one at test_login()

This commit is contained in:
André Koch-Kramer 2020-06-09 13:18:50 +02:00
parent 57393f619d
commit 195569410b
3 changed files with 44 additions and 18 deletions

View File

@ -470,7 +470,13 @@ class Instaloader:
def test_login(self) -> Optional[str]: def test_login(self) -> Optional[str]:
"""Returns the Instagram username to which given :class:`requests.Session` object belongs, or None.""" """Returns the Instagram username to which given :class:`requests.Session` object belongs, or None."""
return self.context.test_login() data = self.context.graphql_query("d6f4427fbe92d846298cf93df0b937d3", {}).get('data', {}).get('user')
if data is not None:
profile = Profile.from_node(self.context, {'id': data.get('id'),
'username': data.get('username'),
'profile_pic_url_hd': data.get('profile_pic_url')})
return profile.username
return None
def login(self, user: str, passwd: str) -> None: def login(self, user: str, passwd: str) -> None:
"""Log in to instagram with given username and password and internally store session object. """Log in to instagram with given username and password and internally store session object.

View File

@ -78,6 +78,9 @@ class InstaloaderContext:
# Cache profile from id (mapping from id to Profile) # Cache profile from id (mapping from id to Profile)
self.profile_id_cache = dict() # type: Dict[int, Any] self.profile_id_cache = dict() # type: Dict[int, Any]
# Cache profile from name (mapping from username to Profile)
self.profile_name_cache = dict() # type: Dict[str, Any]
@contextmanager @contextmanager
def anonymous_copy(self): def anonymous_copy(self):
session = self._session session = self._session
@ -185,7 +188,12 @@ class InstaloaderContext:
self.username = username self.username = username
def test_login(self) -> Optional[str]: def test_login(self) -> Optional[str]:
"""Not meant to be used directly, use :meth:`Instaloader.test_login`.""" """Not meant to be used directly, use :meth:`Instaloader.test_login`.
.. deprecated:: 4.5
:meth:`Instaloader.test_login` now calls :meth:`InstaloaderContext.graphql_query` directly
without using this function.
"""
data = self.graphql_query("d6f4427fbe92d846298cf93df0b937d3", {}) data = self.graphql_query("d6f4427fbe92d846298cf93df0b937d3", {})
return data["data"]["user"]["username"] if data["data"]["user"] is not None else None return data["data"]["user"]["username"] if data["data"]["user"] is not None else None

View File

@ -196,7 +196,7 @@ class Post:
# is contained. # is contained.
# Note that we cannot use Profile.from_id() here since that would lead us into a recursion. # Note that we cannot use Profile.from_id() here since that would lead us into a recursion.
owner_struct = self._full_metadata['owner'] owner_struct = self._full_metadata['owner']
self._owner_profile = Profile(self._context, owner_struct) self._owner_profile = Profile.from_node(self._context, owner_struct)
return self._owner_profile return self._owner_profile
@property @property
@ -387,7 +387,7 @@ class Post:
return PostCommentAnswer(id=int(node['id']), return PostCommentAnswer(id=int(node['id']),
created_at_utc=datetime.utcfromtimestamp(node['created_at']), created_at_utc=datetime.utcfromtimestamp(node['created_at']),
text=node['text'], text=node['text'],
owner=Profile(self._context, node['owner']), owner=Profile.from_node(self._context, node['owner']),
likes_count=node.get('edge_liked_by', {}).get('count', 0)) likes_count=node.get('edge_liked_by', {}).get('count', 0))
def _postcommentanswers(node): def _postcommentanswers(node):
@ -442,13 +442,13 @@ class Post:
likes_edges = self._field('edge_media_preview_like', 'edges') likes_edges = self._field('edge_media_preview_like', 'edges')
if self.likes == len(likes_edges): if self.likes == len(likes_edges):
# If the Post's metadata already contains all likes, don't do GraphQL requests to obtain them # If the Post's metadata already contains all likes, don't do GraphQL requests to obtain them
yield from (Profile(self._context, like['node']) for like in likes_edges) yield from (Profile.from_node(self._context, like['node']) for like in likes_edges)
return return
yield from NodeIterator( yield from NodeIterator(
self._context, self._context,
'1cb6ec562846122743b61e492c85999f', '1cb6ec562846122743b61e492c85999f',
lambda d: d['data']['shortcode_media']['edge_liked_by'], lambda d: d['data']['shortcode_media']['edge_liked_by'],
lambda n: Profile(self._context, n), lambda n: Profile.from_node(self._context, n),
{'shortcode': self.shortcode}, {'shortcode': self.shortcode},
'https://www.instagram.com/p/{0}/'.format(self.shortcode), 'https://www.instagram.com/p/{0}/'.format(self.shortcode),
) )
@ -474,7 +474,7 @@ class Post:
.. versionadded:: 4.4 .. versionadded:: 4.4
""" """
return ([] if not self.is_sponsored else return ([] if not self.is_sponsored else
[Profile(self._context, edge['node']['sponsor']) for edge in [Profile.from_node(self._context, edge['node']['sponsor']) for edge in
self._field('edge_media_to_sponsor_user', 'edges')]) self._field('edge_media_to_sponsor_user', 'edges')])
@property @property
@ -537,6 +537,17 @@ class Profile:
# if loaded from JSON with load_structure_from_file() # if loaded from JSON with load_structure_from_file()
self._iphone_struct_ = node['iphone_struct'] self._iphone_struct_ = node['iphone_struct']
@classmethod
def from_node(cls, context: InstaloaderContext, node: Dict[str, Any]) -> 'Profile':
assert 'username' in node
if node['username'] in context.profile_name_cache:
return context.profile_name_cache[node['username']]
profile = cls(context, node)
if 'id' in node:
context.profile_id_cache[node['id']] = profile
context.profile_name_cache[profile.username] = profile
return profile
@classmethod @classmethod
def from_username(cls, context: InstaloaderContext, username: str): def from_username(cls, context: InstaloaderContext, username: str):
"""Create a Profile instance from a given username, raise exception if it does not exist. """Create a Profile instance from a given username, raise exception if it does not exist.
@ -548,8 +559,11 @@ class Profile:
:raises: :class:`ProfileNotExistsException` :raises: :class:`ProfileNotExistsException`
""" """
# pylint:disable=protected-access # pylint:disable=protected-access
profile = cls(context, {'username': username.lower()}) if username.lower() in context.profile_name_cache:
return context.profile_name_cache[username.lower()]
profile = cls.from_node(context, {'username': username.lower()})
profile._obtain_metadata() # to raise ProfileNotExistException now in case username is invalid profile._obtain_metadata() # to raise ProfileNotExistException now in case username is invalid
context.profile_id_cache[profile.userid] = profile
return profile return profile
@classmethod @classmethod
@ -572,12 +586,10 @@ class Profile:
'include_highlight_reels': False}, 'include_highlight_reels': False},
rhx_gis=context.root_rhx_gis)['data']['user'] rhx_gis=context.root_rhx_gis)['data']['user']
if data: if data:
profile = cls(context, data['reel']['owner']) return cls.from_node(context, data['reel']['owner'])
else: else:
raise ProfileNotExistsException("No profile found, the user may have blocked you (ID: " + raise ProfileNotExistsException("No profile found, the user may have blocked you (ID: " +
str(profile_id) + ").") str(profile_id) + ").")
context.profile_id_cache[profile_id] = profile
return profile
def _asdict(self): def _asdict(self):
json_node = self._node.copy() json_node = self._node.copy()
@ -860,7 +872,7 @@ class Profile:
self._context, self._context,
'37479f2b8209594dde7facb0d904896a', '37479f2b8209594dde7facb0d904896a',
lambda d: d['data']['user']['edge_followed_by'], lambda d: d['data']['user']['edge_followed_by'],
lambda n: Profile(self._context, n), lambda n: Profile.from_node(self._context, n),
{'id': str(self.userid)}, {'id': str(self.userid)},
'https://www.instagram.com/{0}/'.format(self.username), 'https://www.instagram.com/{0}/'.format(self.username),
) )
@ -879,7 +891,7 @@ class Profile:
self._context, self._context,
'58712303d941c6855d4e888c5f0cd22f', '58712303d941c6855d4e888c5f0cd22f',
lambda d: d['data']['user']['edge_follow'], lambda d: d['data']['user']['edge_follow'],
lambda n: Profile(self._context, n), lambda n: Profile.from_node(self._context, n),
{'id': str(self.userid)}, {'id': str(self.userid)},
'https://www.instagram.com/{0}/'.format(self.username), 'https://www.instagram.com/{0}/'.format(self.username),
) )
@ -894,7 +906,7 @@ class Profile:
if not self._context.is_logged_in: if not self._context.is_logged_in:
raise LoginRequiredException("--login required to get a profile's similar accounts.") raise LoginRequiredException("--login required to get a profile's similar accounts.")
self._obtain_metadata() self._obtain_metadata()
yield from (Profile(self._context, edge["node"]) for edge in yield from (Profile.from_node(self._context, edge["node"]) for edge in
self._context.graphql_query("ad99dd9d3646cc3c0dda65debcd266a7", self._context.graphql_query("ad99dd9d3646cc3c0dda65debcd266a7",
{"user_id": str(self.userid), "include_chaining": True}, {"user_id": str(self.userid), "include_chaining": True},
"https://www.instagram.com/{0}/" "https://www.instagram.com/{0}/"
@ -1099,7 +1111,7 @@ class Story:
def owner_profile(self) -> Profile: def owner_profile(self) -> Profile:
""":class:`Profile` instance of the story owner.""" """:class:`Profile` instance of the story owner."""
if not self._owner_profile: if not self._owner_profile:
self._owner_profile = Profile(self._context, self._node['user']) self._owner_profile = Profile.from_node(self._context, self._node['user'])
return self._owner_profile return self._owner_profile
@property @property
@ -1157,7 +1169,7 @@ class Highlight(Story):
def owner_profile(self) -> Profile: def owner_profile(self) -> Profile:
""":class:`Profile` instance of the highlights' owner.""" """:class:`Profile` instance of the highlights' owner."""
if not self._owner_profile: if not self._owner_profile:
self._owner_profile = Profile(self._context, self._node['owner']) self._owner_profile = Profile.from_node(self._context, self._node['owner'])
return self._owner_profile return self._owner_profile
@property @property
@ -1388,7 +1400,7 @@ class TopSearchResults:
user_node = user['user'] user_node = user['user']
if 'pk' in user_node: if 'pk' in user_node:
user_node['id'] = user_node['pk'] user_node['id'] = user_node['pk']
yield Profile(self._context, user_node) yield Profile.from_node(self._context, user_node)
def get_prefixed_usernames(self) -> Iterator[str]: def get_prefixed_usernames(self) -> Iterator[str]:
""" """
@ -1481,7 +1493,7 @@ def load_structure_from_file(context: InstaloaderContext, filename: str) -> Json
if node_type == "Post": if node_type == "Post":
return Post(context, json_structure['node']) return Post(context, json_structure['node'])
elif node_type == "Profile": elif node_type == "Profile":
return Profile(context, json_structure['node']) return Profile.from_node(context, json_structure['node'])
elif node_type == "StoryItem": elif node_type == "StoryItem":
return StoryItem(context, json_structure['node']) return StoryItem(context, json_structure['node'])
elif node_type == "Hashtag": elif node_type == "Hashtag":