From 253144203b8b8499e5eee43a1718d4588b93a026 Mon Sep 17 00:00:00 2001 From: Eduardo Kalinowski Date: Sat, 18 May 2024 11:13:44 -0300 Subject: [PATCH] Download stories when --load-cookies is used (#2263) --- docs/basic-usage.rst | 16 +++++++-------- docs/cli-options.rst | 16 +++++++++------ docs/troubleshooting.rst | 2 +- instaloader/__main__.py | 34 +++++++++++++++---------------- instaloader/instaloader.py | 4 ++-- instaloader/instaloadercontext.py | 2 +- instaloader/structures.py | 22 ++++++++++---------- 7 files changed, 49 insertions(+), 47 deletions(-) diff --git a/docs/basic-usage.rst b/docs/basic-usage.rst index 34958a8..ef8515b 100644 --- a/docs/basic-usage.rst +++ b/docs/basic-usage.rst @@ -75,7 +75,7 @@ What to Download Instaloader supports the following targets: - ``profile`` - Public profile, or private profile with :option:`--login`. + Public profile, or private profile with :ref:`login`. If an already-downloaded profile has been renamed, Instaloader automatically finds it by its unique ID and renames the folder accordingly. @@ -102,23 +102,23 @@ Instaloader supports the following targets: Posts tagged with a given location; the location ID is the numerical ID Instagram labels a location with (e.g. \https://www.instagram.com/explore/locations/**362629379**/plymouth-naval-memorial/). - Requires :option:`--login`. + Requires :ref:`login`. .. versionadded:: 4.2 - ``:stories`` The currently-visible **stories** of your followees (requires - :option:`--login`). + :ref:`login`). - ``:feed`` - Your **feed** (requires :option:`--login`). + Your **feed** (requires :ref:`login`). - ``:saved`` - Posts which are marked as **saved** (requires :option:`--login`). + Posts which are marked as **saved** (requires :ref:`login`). - ``@profile`` All profiles that are followed by ``profile``, i.e. the *followees* of - ``profile`` (requires :option:`--login`). + ``profile`` (requires :ref:`login`). - ``-post`` Replace **post** with the post's shortcode to download single post. Must be preceded by ``--`` in @@ -140,7 +140,7 @@ downloads the pictures and videos and their captions. You can specify - :option:`--geotags` **download geotags** of each post and save them as - Google Maps link (requires :option:`--login`), + Google Maps link (requires :ref:`login`), For a reference of all supported command line options, see :ref:`command-line-options`. @@ -255,7 +255,7 @@ Id est, the following attributes can be used with both As :option:`--post-filter`, the following attributes can be used additionally: - :attr:`~Post.viewer_has_liked` (bool) - Whether user (with :option:`--login`) has liked given post. To download the + Whether user (with :ref:`login`) has liked given post. To download the pictures from your feed that you have liked:: instaloader --login=your_username --post-filter=viewer_has_liked :feed diff --git a/docs/cli-options.rst b/docs/cli-options.rst index 2f8b7d3..753a85f 100644 --- a/docs/cli-options.rst +++ b/docs/cli-options.rst @@ -61,13 +61,13 @@ What to Download of each Post Download geotags when available. Geotags are stored as a text file with the location's name and a Google Maps link. This requires an additional - request to the Instagram server for each picture. Requires :option:`--login`. + request to the Instagram server for each picture. Requires :ref:`login`. .. option:: --comments, -C Download and update comments for each post. This requires an additional request to the Instagram server for each post, which is why it is disabled by - default. Requires :option:`--login`. + default. Requires :ref:`login`. .. option:: --no-captions @@ -116,12 +116,12 @@ What to Download of each Profile .. option:: --stories, -s Also download stories of each profile that is downloaded. Requires - :option:`--login`. + :ref:`login`. .. option:: --highlights Also download highlights of each profile that is downloaded. Requires - :option:`--login`. + :ref:`login`. .. versionadded:: 4.1 @@ -183,6 +183,7 @@ Which Posts to Download Do not attempt to download more than COUNT posts. Applies to ``#hashtag``, ``%location_id``, ``:feed``, and ``:saved``. +.. _login: Login (Download Private Profiles) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -192,6 +193,9 @@ profiles. To login, pass the :option:`--login` option. Your session cookie (not password!) will be saved to a local file to be reused next time you want Instaloader to login. +Instead of :option:`--login`, it is possible to use +:option:`--load-cookies` to import a session from a browser. + .. option:: --login YOUR-USERNAME, -l YOUR-USERNAME Login name (profile name) for your Instagram account. @@ -204,8 +208,8 @@ Instaloader to login. Incompatible with :option:`--login` due to potential username mismatch between user input and browser login. Supported browsers: Chrome, Firefox, Edge, Opera, Safari, Brave. - After loading the cookies run the :option:`--login` option as it is required to download high quality media - and to make full use of Instaloader's features. + In subsequent runs, you can just use :option:`--login` to reuse the + same session, which is saved by Instaloader. .. versionadded:: 4.11 diff --git a/docs/troubleshooting.rst b/docs/troubleshooting.rst index a30ff5e..e5d9541 100644 --- a/docs/troubleshooting.rst +++ b/docs/troubleshooting.rst @@ -33,7 +33,7 @@ happen under normal conditions, consider adjusting the There have been observations that services, that in their nature offer promiscuous IP addresses, such as cloud, VPN and public proxy services, might be subject to significantly stricter limits for anonymous access. However, -logged-in accesses (see :option:`--login`) do not seem to be affected. +:ref:`logged-in accesses` do not seem to be affected. Instaloader allows to adjust the rate controlling behavior by overriding :class:`instaloader.RateController`. diff --git a/instaloader/__main__.py b/instaloader/__main__.py index 5a8e5ba..46c27b5 100644 --- a/instaloader/__main__.py +++ b/instaloader/__main__.py @@ -5,7 +5,6 @@ import datetime import os import re import sys -import textwrap from argparse import ArgumentParser, ArgumentTypeError, SUPPRESS from typing import List, Optional @@ -115,9 +114,7 @@ def import_session(browser, instaloader, cookiefile): raise SystemExit(f"Not logged in. Are you logged in successfully in {browser}?") instaloader.context.username = username print(f"{username} has been successfully logged in.") - next_step_text = (f"Next: Run instaloader --login={username} as it is required to download high quality media " - "and to make full use of instaloader's features.") - print(textwrap.fill(next_step_text)) + print(f"Next time use --login={username} to reuse the same session.") def _main(instaloader: Instaloader, targetlist: List[str], @@ -185,7 +182,7 @@ def _main(instaloader: Instaloader, targetlist: List[str], instaloader.context.log("Logged in as %s." % username) # since 4.2.9 login is required for geotags if instaloader.download_geotags and not instaloader.context.is_logged_in: - instaloader.context.error("Warning: Use --login to download geotags of posts.") + instaloader.context.error("Warning: Login is required to download geotags of posts.") # Try block for KeyboardInterrupt (save session on ^C) profiles = set() anonymous_retry_profiles = set() @@ -279,7 +276,7 @@ def _main(instaloader: Instaloader, targetlist: List[str], ' '.join([p.username for p in profiles]))) if instaloader.context.iphone_support and profiles and (download_profile_pic or download_posts) and \ not instaloader.context.is_logged_in: - instaloader.context.log("Hint: Use --login to download higher-quality versions of pictures.") + instaloader.context.log("Hint: Login to download higher-quality versions of pictures.") instaloader.download_profiles(profiles, download_profile_pic, download_posts, download_tagged, download_igtv, download_highlights, download_stories, @@ -322,17 +319,17 @@ def main(): help="Download profile. If an already-downloaded profile has been renamed, Instaloader " "automatically finds it by its unique ID and renames the folder likewise.") g_targets.add_argument('_at_profile', nargs='*', metavar="@profile", - help="Download all followees of profile. Requires --login. " + help="Download all followees of profile. Requires login. " "Consider using :feed rather than @yourself.") g_targets.add_argument('_hashtag', nargs='*', metavar='"#hashtag"', help="Download #hashtag.") g_targets.add_argument('_location', nargs='*', metavar='%location_id', - help="Download %%location_id. Requires --login.") + help="Download %%location_id. Requires login.") g_targets.add_argument('_feed', nargs='*', metavar=":feed", - help="Download pictures from your feed. Requires --login.") + help="Download pictures from your feed. Requires login.") g_targets.add_argument('_stories', nargs='*', metavar=":stories", - help="Download the stories of your followees. Requires --login.") + help="Download the stories of your followees. Requires login.") g_targets.add_argument('_saved', nargs='*', metavar=":saved", - help="Download the posts that you marked as saved. Requires --login.") + help="Download the posts that you marked as saved. Requires login.") g_targets.add_argument('_singlepost', nargs='*', metavar="-- -shortcode", help="Download the post with the given shortcode") g_targets.add_argument('_json', nargs='*', metavar="filename.json[.xz]", @@ -363,11 +360,11 @@ def main(): help='Download geotags when available. Geotags are stored as a ' 'text file with the location\'s name and a Google Maps link. ' 'This requires an additional request to the Instagram ' - 'server for each picture. Requires --login.') + 'server for each picture. Requires login.') g_post.add_argument('-C', '--comments', action='store_true', help='Download and update comments for each post. ' 'This requires an additional request to the Instagram ' - 'server for each post, which is why it is disabled by default. Requires --login.') + 'server for each post, which is why it is disabled by default. Requires login.') g_post.add_argument('--no-captions', action='store_true', help='Do not create txt files.') g_post.add_argument('--post-metadata-txt', action='append', @@ -381,11 +378,11 @@ def main(): g_post.add_argument('--no-compress-json', action='store_true', help='Do not xz compress JSON files, rather create pretty formatted JSONs.') g_prof.add_argument('-s', '--stories', action='store_true', - help='Also download stories of each profile that is downloaded. Requires --login.') + help='Also download stories of each profile that is downloaded. Requires login.') g_prof.add_argument('--stories-only', action='store_true', help=SUPPRESS) g_prof.add_argument('--highlights', action='store_true', - help='Also download highlights of each profile that is downloaded. Requires --login.') + help='Also download highlights of each profile that is downloaded. Requires login.') g_prof.add_argument('--tagged', action='store_true', help='Also download posts where each profile is tagged.') g_prof.add_argument('--igtv', action='store_true', @@ -417,7 +414,8 @@ def main(): 'Instaloader can login to Instagram. This allows downloading private profiles. ' 'To login, pass the --login option. Your session cookie (not your password!) ' 'will be saved to a local file to be reused next time you want Instaloader ' - 'to login.') + 'to login. Instead of --login, the --load-cookies option can be used to ' + 'import a session from a browser.') g_login.add_argument('-l', '--login', metavar='YOUR-USERNAME', help='Login name (profile name) for your Instagram account.') g_login.add_argument('-b', '--load-cookies', metavar='BROWSER-NAME', @@ -484,8 +482,8 @@ def main(): args = parser.parse_args() try: - if args.login is None and (args.stories or args.stories_only): - print("--login=USERNAME required to download stories.", file=sys.stderr) + if (args.login is None and args.load_cookies is None) and (args.stories or args.stories_only): + print("Login is required to download stories.", file=sys.stderr) args.stories = False if args.stories_only: raise SystemExit(1) diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index e854360..51e4cdb 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -77,7 +77,7 @@ def _requires_login(func: Callable) -> Callable: @wraps(func) def call(instaloader, *args, **kwargs): if not instaloader.context.is_logged_in: - raise LoginRequiredException("--login=USERNAME required.") + raise LoginRequiredException("Login required.") return func(instaloader, *args, **kwargs) return call @@ -1460,7 +1460,7 @@ class Instaloader: if tagged or igtv or highlights or posts: if (not self.context.is_logged_in and profile.is_private): - raise LoginRequiredException("--login=USERNAME required.") + raise LoginRequiredException("Login required.") if (self.context.username != profile.username and profile.is_private and not profile.followed_by_viewer): diff --git a/instaloader/instaloadercontext.py b/instaloader/instaloadercontext.py index 5efd08a..a7b00a3 100644 --- a/instaloader/instaloadercontext.py +++ b/instaloader/instaloadercontext.py @@ -392,7 +392,7 @@ class InstaloaderContext: if (redirect_url.startswith('https://www.instagram.com/accounts/login') or redirect_url.startswith('https://i.instagram.com/accounts/login')): if not self.is_logged_in: - raise LoginRequiredException("Redirected to login page. Use --login.") + raise LoginRequiredException("Redirected to login page. Use --login or --load-cookies.") raise AbortDownloadException("Redirected to login page. You've been logged out, please wait " + "some time, recreate the session and try again") if redirect_url.startswith('https://{}/'.format(host)): diff --git a/instaloader/structures.py b/instaloader/structures.py index c0dc373..3670df4 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -330,7 +330,7 @@ class Post: if not self._context.iphone_support: raise IPhoneSupportDisabledException("iPhone support is disabled.") if not self._context.is_logged_in: - raise LoginRequiredException("--login required to access iPhone media info endpoint.") + raise LoginRequiredException("Login required to access iPhone media info endpoint.") if not self._iphone_struct_: data = self._context.get_iphone_json(path='api/v1/media/{}/info/'.format(self.mediaid), params={}) self._iphone_struct_ = data['items'][0] @@ -686,7 +686,7 @@ class Post: Change return type to ``Iterable``. """ if not self._context.is_logged_in: - raise LoginRequiredException("--login required to access comments of a post.") + raise LoginRequiredException("Login required to access comments of a post.") def _postcommentanswer(node): return PostCommentAnswer(id=int(node['id']), @@ -752,7 +752,7 @@ class Post: Require being logged in (as required by Instagram). """ if not self._context.is_logged_in: - raise LoginRequiredException("--login required to access likes of a post.") + raise LoginRequiredException("Login required to access likes of a post.") if self.likes == 0: # Avoid doing additional requests if there are no comments return @@ -929,7 +929,7 @@ class Profile: .. versionadded:: 4.5.2""" if not context.is_logged_in: - raise LoginRequiredException("--login required to access own profile.") + raise LoginRequiredException("Login required to access own profile.") return cls(context, context.graphql_query("d6f4427fbe92d846298cf93df0b937d3", {})["data"]["user"]) def _asdict(self): @@ -983,7 +983,7 @@ class Profile: if not self._context.iphone_support: raise IPhoneSupportDisabledException("iPhone support is disabled.") if not self._context.is_logged_in: - raise LoginRequiredException("--login required to access iPhone profile info endpoint.") + 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'] @@ -1190,7 +1190,7 @@ class Profile: :rtype: NodeIterator[Post]""" if self.username != self._context.username: - raise LoginRequiredException("--login={} required to get that profile's saved posts.".format(self.username)) + raise LoginRequiredException(f"Login as {self.username} required to get that profile's saved posts.") return NodeIterator( self._context, @@ -1250,7 +1250,7 @@ class Profile: .. versionadded:: 4.10 """ if not self._context.is_logged_in: - raise LoginRequiredException("--login required to get a profile's followers.") + raise LoginRequiredException("Login required to get a profile's followers.") self._obtain_metadata() return NodeIterator( self._context, @@ -1269,7 +1269,7 @@ class Profile: :rtype: NodeIterator[Profile] """ if not self._context.is_logged_in: - raise LoginRequiredException("--login required to get a profile's followers.") + raise LoginRequiredException("Login required to get a profile's followers.") self._obtain_metadata() return NodeIterator( self._context, @@ -1288,7 +1288,7 @@ class Profile: :rtype: NodeIterator[Profile] """ if not self._context.is_logged_in: - raise LoginRequiredException("--login required to get a profile's followees.") + raise LoginRequiredException("Login required to get a profile's followees.") self._obtain_metadata() return NodeIterator( self._context, @@ -1307,7 +1307,7 @@ class Profile: .. versionadded:: 4.4 """ 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() yield from (Profile(self._context, edge["node"]) for edge in self._context.graphql_query("ad99dd9d3646cc3c0dda65debcd266a7", @@ -1386,7 +1386,7 @@ class StoryItem: if not self._context.iphone_support: raise IPhoneSupportDisabledException("iPhone support is disabled.") if not self._context.is_logged_in: - raise LoginRequiredException("--login required to access iPhone media info endpoint.") + raise LoginRequiredException("Login required to access iPhone media info endpoint.") if not self._iphone_struct_: data = self._context.get_iphone_json( path='api/v1/feed/reels_media/?reel_ids={}'.format(self.owner_id), params={}