diff --git a/docs/cli-options.rst b/docs/cli-options.rst index 0ac9b12..d40d113 100644 --- a/docs/cli-options.rst +++ b/docs/cli-options.rst @@ -100,7 +100,6 @@ What to Download of each Post Do not xz compress JSON files, rather create pretty formatted JSONs. - What to Download of each Profile ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ @@ -274,6 +273,12 @@ How to Download .. versionadded:: 4.7 +.. option:: --no-iphone + + Do not attempt to download iPhone version of images and videos. + + .. versionadded:: 4.8 + Miscellaneous Options ^^^^^^^^^^^^^^^^^^^^^ diff --git a/instaloader/__main__.py b/instaloader/__main__.py index 5ba7707..da4ede1 100644 --- a/instaloader/__main__.py +++ b/instaloader/__main__.py @@ -206,7 +206,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 or download_posts) and not instaloader.context.is_logged_in: + 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.download_profiles(profiles, download_profile_pic, download_posts, download_tagged, download_igtv, @@ -382,6 +383,8 @@ def main(): g_how.add_argument('--abort-on', type=http_status_code_list, metavar="STATUS_CODES", help='Comma-separated list of HTTP status codes that cause Instaloader to abort, bypassing all ' 'retry logic.') + g_how.add_argument('--no-iphone', action='store_true', + help='Do not attempt to download iPhone version of images and videos.') g_misc = parser.add_argument_group('Miscellaneous Options') g_misc.add_argument('-q', '--quiet', action='store_true', @@ -441,7 +444,8 @@ def main(): resume_prefix=resume_prefix, check_resume_bbd=not args.use_aged_resume_files, slide=args.slide, - fatal_status_codes=args.abort_on) + fatal_status_codes=args.abort_on, + iphone_support=not args.no_iphone) _main(loader, args.profile, username=args.login.lower() if args.login is not None else None, diff --git a/instaloader/exceptions.py b/instaloader/exceptions.py index e4a90ff..271d09b 100644 --- a/instaloader/exceptions.py +++ b/instaloader/exceptions.py @@ -65,6 +65,8 @@ class QueryReturnedNotFoundException(ConnectionException): class TooManyRequestsException(ConnectionException): pass +class IPhoneSupportDisabledException(InstaloaderException): + pass class AbortDownloadException(Exception): """ diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index 589d2c0..d2475af 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -162,6 +162,7 @@ class Instaloader: :param check_resume_bbd: Whether to check the date of expiry of resume files and reject them if expired. :param slide: :option:`--slide` :param fatal_status_codes: :option:`--abort-on` + :param iphone_support: not :option:`--no-iphone` .. attribute:: context @@ -189,10 +190,12 @@ class Instaloader: resume_prefix: Optional[str] = "iterator", check_resume_bbd: bool = True, slide: Optional[str] = None, - fatal_status_codes: Optional[List[int]] = None): + fatal_status_codes: Optional[List[int]] = None, + iphone_support: bool = True): self.context = InstaloaderContext(sleep, quiet, user_agent, max_connection_attempts, - request_timeout, rate_controller, fatal_status_codes) + request_timeout, rate_controller, fatal_status_codes, + iphone_support) # configuration parameters self.dirname_pattern = dirname_pattern or "{target}" @@ -259,7 +262,8 @@ class Instaloader: resume_prefix=self.resume_prefix, check_resume_bbd=self.check_resume_bbd, slide=self.slide, - fatal_status_codes=self.context.fatal_status_codes) + fatal_status_codes=self.context.fatal_status_codes, + iphone_support=self.context.iphone_support) yield new_loader self.context.error_log.extend(new_loader.context.error_log) new_loader.context.error_log = [] # avoid double-printing of errors diff --git a/instaloader/instaloadercontext.py b/instaloader/instaloadercontext.py index 506c7f3..997026e 100644 --- a/instaloader/instaloadercontext.py +++ b/instaloader/instaloadercontext.py @@ -54,7 +54,8 @@ class InstaloaderContext: def __init__(self, sleep: bool = True, quiet: bool = False, user_agent: Optional[str] = None, max_connection_attempts: int = 3, request_timeout: float = 300.0, rate_controller: Optional[Callable[["InstaloaderContext"], "RateController"]] = None, - fatal_status_codes: Optional[List[int]] = None): + fatal_status_codes: Optional[List[int]] = None, + iphone_support: bool = True): self.user_agent = user_agent if user_agent is not None else default_user_agent() self.request_timeout = request_timeout @@ -66,6 +67,7 @@ class InstaloaderContext: self._graphql_page_length = 50 self._root_rhx_gis = None self.two_factor_auth_pending = None + self.iphone_support = iphone_support # error log, filled with error() and printed at the end of Instaloader.main() self.error_log = [] # type: List[str] diff --git a/instaloader/structures.py b/instaloader/structures.py index c1ea78b..c1fffc5 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -168,6 +168,8 @@ class Post: @property def _iphone_struct(self) -> Dict[str, Any]: + 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.") if not self._iphone_struct_: @@ -246,7 +248,7 @@ class Post: @property def url(self) -> str: """URL of the picture / video thumbnail of the post""" - if self.typename == "GraphImage" and self._context.is_logged_in: + if self.typename == "GraphImage" and self._context.iphone_support and self._context.is_logged_in: try: orig_url = self._iphone_struct['image_versions2']['candidates'][0]['url'] url = re.sub(r'([?&])se=\d+&?', r'\1', orig_url).rstrip('&') @@ -304,7 +306,7 @@ class Post: node = edge['node'] is_video = node['is_video'] display_url = node['display_url'] - if not is_video and self._context.is_logged_in: + if not is_video and self._context.iphone_support and self._context.is_logged_in: try: carousel_media = self._iphone_struct['carousel_media'] orig_url = carousel_media[idx]['image_versions2']['candidates'][0]['url'] @@ -372,7 +374,7 @@ class Post: def video_url(self) -> Optional[str]: """URL of the video, or None.""" if self.is_video: - if self._context.is_logged_in: + if self._context.iphone_support and self._context.is_logged_in: try: url = self._iphone_struct['video_versions'][0]['url'] return url @@ -691,6 +693,8 @@ class Profile: @property def _iphone_struct(self) -> Dict[str, Any]: + 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.") if not self._iphone_struct_: @@ -834,7 +838,7 @@ class Profile: .. versionchanged:: 4.2.1 Require being logged in for HD version (as required by Instagram).""" - if self._context.is_logged_in: + if self._context.iphone_support and self._context.is_logged_in: try: return self._iphone_struct['hd_profile_pic_url_info']['url'] except (InstaloaderException, KeyError) as err: @@ -1021,6 +1025,8 @@ class StoryItem: @property def _iphone_struct(self) -> Dict[str, Any]: + 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.") if not self._iphone_struct_: @@ -1079,7 +1085,7 @@ class StoryItem: @property def url(self) -> str: """URL of the picture / video thumbnail of the StoryItem""" - if self.typename == "GraphStoryImage" and self._context.is_logged_in: + if self.typename == "GraphStoryImage" and self._context.iphone_support and self._context.is_logged_in: try: orig_url = self._iphone_struct['image_versions2']['candidates'][0]['url'] url = re.sub(r'([?&])se=\d+&?', r'\1', orig_url).rstrip('&')