Download stories when --load-cookies is used (#2263)

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