From 37cbc5d72d00e7dc5c4c7cb827af316e7901a954 Mon Sep 17 00:00:00 2001 From: fireattack Date: Thu, 16 Jun 2022 03:18:59 -0500 Subject: [PATCH 01/16] Handle StoryImage in StoryItem.url (#1558) --- instaloader/structures.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/instaloader/structures.py b/instaloader/structures.py index ba16b0a..5efa541 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -1207,7 +1207,8 @@ class StoryItem: @property def url(self) -> str: """URL of the picture / video thumbnail of the StoryItem""" - if self.typename == "GraphStoryImage" and self._context.iphone_support and self._context.is_logged_in: + if self.typename in ["GraphStoryImage", "StoryImage"] 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('&') From 2d37e1dca282b857a7a3f4be6691b3606c1a00be Mon Sep 17 00:00:00 2001 From: Ziemowit Zabawa Date: Thu, 16 Jun 2022 10:25:43 +0200 Subject: [PATCH 02/16] Make docs explicit about importing multiple argument files (#1548) --- docs/cli-options.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/cli-options.rst b/docs/cli-options.rst index f8a187f..23701ee 100644 --- a/docs/cli-options.rst +++ b/docs/cli-options.rst @@ -329,12 +329,13 @@ Miscellaneous Options Read arguments from file `args.txt`, a shortcut to provide arguments from file rather than command-line. This provides a convenient way to hide login info from CLI, and can also be used to simplify management of long arguments. + You can provide more than one file at once, e.g.: ``+args1.txt +args2.txt``. .. note:: Text file should separate arguments with line breaks. - args.txt example:: + `args.txt` example:: --login=MYUSERNAME --password=MYPASSWORD From 8649a10113047e6ea03f3ac1e68eb23f92bf2dcd Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Thu, 23 Jun 2022 20:43:53 +0200 Subject: [PATCH 03/16] docs: fix link to requests intersphinx inventory --- docs/conf.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/conf.py b/docs/conf.py index 0ab1aff..766faf0 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -48,7 +48,7 @@ autodoc_default_flags = ['show-inheritance', 'members', 'undoc-members'] autodoc_member_order = 'bysource' intersphinx_mapping = {'python': ('https://docs.python.org/3', None), - 'requests': ('https://requests.kennethreitz.org/en/master/', None)} + 'requests': ('https://requests.readthedocs.io/en/latest/', None)} nitpick_ignore = [('py:class', 'typing.Tuple')] From da0dcb106cd1aba7d44d3dcdd133532f730245ed Mon Sep 17 00:00:00 2001 From: Eduardo Kalinowski Date: Mon, 4 Jul 2022 15:02:41 -0300 Subject: [PATCH 04/16] Handle pinned posts with --fast-update and --latest-stamps (#1586) Instead of hard-coding a check for pinned items when storing the first item, allows the creater of NodeIterator to specify how the comparison should be made. This also allows storing the newest post (whatever it's status is), instead of simply storing the newst non-pinned post, which prevents redownloads in case a user adds only a pinned post between runs. --- instaloader/instaloader.py | 7 +++++-- instaloader/nodeiterator.py | 18 +++++++++++++++--- instaloader/structures.py | 25 ++++++++++++++++++++++++- 3 files changed, 44 insertions(+), 6 deletions(-) diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index 950e6ce..15fa555 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -1010,7 +1010,10 @@ class Instaloader: enabled=self.resume_prefix is not None ) as (is_resuming, start_index): for number, post in enumerate(posts, start=start_index + 1): - if (max_count is not None and number > max_count) or not takewhile(post): + should_stop = not takewhile(post) + if should_stop and post.is_pinned: + continue + if (max_count is not None and number > max_count) or should_stop: break if displayed_count is not None: self.context.log("[{0:{w}d}/{1:{w}d}] ".format(number, displayed_count, @@ -1042,7 +1045,7 @@ class Instaloader: except PostChangedException: post_changed = True continue - if fast_update and not downloaded and not post_changed: + if fast_update and not downloaded and not post_changed and not post.is_pinned: # disengage fast_update for first post when resuming if not is_resuming or number > 0: break diff --git a/instaloader/nodeiterator.py b/instaloader/nodeiterator.py index b5fabae..a49656d 100644 --- a/instaloader/nodeiterator.py +++ b/instaloader/nodeiterator.py @@ -76,7 +76,8 @@ class NodeIterator(Iterator[T]): node_wrapper: Callable[[Dict], T], query_variables: Optional[Dict[str, Any]] = None, query_referer: Optional[str] = None, - first_data: Optional[Dict[str, Any]] = None): + first_data: Optional[Dict[str, Any]] = None, + is_first: Optional[Callable[[T], bool]] = None): self._context = context self._query_hash = query_hash self._edge_extractor = edge_extractor @@ -91,6 +92,7 @@ class NodeIterator(Iterator[T]): else: self._data = self._query() self._first_node: Optional[Dict] = None + self._is_first = is_first def _query(self, after: Optional[str] = None) -> Dict: pagination_variables = {'first': NodeIterator._graphql_page_length} # type: Dict[str, Any] @@ -128,8 +130,12 @@ class NodeIterator(Iterator[T]): self._page_index, self._total_index = page_index, total_index raise item = self._node_wrapper(node) - if self._first_node is None: - self._first_node = node + if self._is_first is not None: + if self._is_first(item): + self._first_node = node + else: + if self._first_node is None: + self._first_node = node return item if self._data['page_info']['has_next_page']: query_response = self._query(self._data['page_info']['end_cursor']) @@ -168,7 +174,13 @@ class NodeIterator(Iterator[T]): """ If this iterator has produced any items, returns the first item produced. + It is possible to override what is considered the first item (for example, to consider the + newest item in case items are not in strict chronological order) by passing a callback + function as the `is_first` parameter when creating the class. + .. versionadded:: 4.8 + .. versionchanged:: 4.9.2 + What is considered the first item can be overridden. """ return self._node_wrapper(self._first_node) if self._first_node is not None else None diff --git a/instaloader/structures.py b/instaloader/structures.py index 5efa541..adaa222 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -7,7 +7,7 @@ from contextlib import suppress from datetime import datetime from itertools import islice from pathlib import Path -from typing import Any, Dict, Iterable, Iterator, List, Optional, Tuple, Union +from typing import Any, Callable, Dict, Iterable, Iterator, List, Optional, Tuple, Union from unicodedata import normalize from . import __version__ @@ -644,6 +644,13 @@ class Post: loc.get('lat'), loc.get('lng')) return self._location + @property + def is_pinned(self) -> bool: + """True if this Post has been pinned by at least one user. + + .. versionadded: 4.9.2""" + return 'pinned_for_users' in self._node and bool(self._node['pinned_for_users']) + class Profile: """ @@ -970,6 +977,7 @@ class Profile: {'id': self.userid}, 'https://www.instagram.com/{0}/'.format(self.username), self._metadata('edge_owner_to_timeline_media'), + Profile._make_is_newest_checker() ) def get_saved_posts(self) -> NodeIterator[Post]: @@ -1003,6 +1011,7 @@ class Profile: lambda n: Post(self._context, n, self if int(n['owner']['id']) == self.userid else None), {'id': self.userid}, 'https://www.instagram.com/{0}/'.format(self.username), + is_first=Profile._make_is_newest_checker() ) def get_igtv_posts(self) -> NodeIterator[Post]: @@ -1020,8 +1029,22 @@ class Profile: {'id': self.userid}, 'https://www.instagram.com/{0}/channel/'.format(self.username), self._metadata('edge_felix_video_timeline'), + Profile._make_is_newest_checker() ) + @staticmethod + def _make_is_newest_checker() -> Callable[[Post], bool]: + newest_date: Optional[datetime] = None + def is_newest(p: Post) -> bool: + nonlocal newest_date + post_date = p.date_local + if newest_date is None or post_date > newest_date: + newest_date = post_date + return True + else: + return False + return is_newest + def get_followers(self) -> NodeIterator['Profile']: """ Retrieve list of followers of given profile. From 5e232fd4f521b1700c122827d973fd868c6ae483 Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Mon, 4 Jul 2022 20:18:22 +0200 Subject: [PATCH 05/16] Release of Version 4.9.2 --- instaloader/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instaloader/__init__.py b/instaloader/__init__.py index a5be26e..1a5cb68 100644 --- a/instaloader/__init__.py +++ b/instaloader/__init__.py @@ -1,7 +1,7 @@ """Download pictures (or videos) along with their captions and other metadata from Instagram.""" -__version__ = '4.9.1' +__version__ = '4.9.2' try: From 4bb32e3c5a709f1e200470a63a9e2974b5a43b7f Mon Sep 17 00:00:00 2001 From: fireattack Date: Thu, 14 Jul 2022 14:00:24 -0500 Subject: [PATCH 06/16] write caption in text mode (#1632) --- instaloader/instaloader.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index 15fa555..8754e4e 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -478,9 +478,8 @@ class Instaloader: self.context.log(pcaption, end=' ', flush=True) except UnicodeEncodeError: self.context.log('txt', end=' ', flush=True) - with open(filename, 'wb') as text_file: - with BytesIO(bcaption) as bio: - shutil.copyfileobj(cast(IO, bio), text_file) + with open(filename, 'w', encoding='UTF-8') as fio: + fio.write(caption) os.utime(filename, (datetime.now().timestamp(), mtime.timestamp())) def save_location(self, filename: str, location: PostLocation, mtime: datetime) -> None: From 6a429066978a3fd2249068cf7c35a677786ab7f2 Mon Sep 17 00:00:00 2001 From: fireattack Date: Thu, 11 Aug 2022 12:56:52 -0500 Subject: [PATCH 07/16] don't try to call iPhone api when comparing profile_pic URL (#1681) --- instaloader/instaloader.py | 2 +- instaloader/structures.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index 8754e4e..e34039e 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -562,7 +562,7 @@ class Instaloader: if latest_stamps is None: self.download_profilepic(profile) return - profile_pic_basename = profile.profile_pic_url.split('/')[-1].split('?')[0] + profile_pic_basename = profile.profile_pic_url_no_iphone.split('/')[-1].split('?')[0] saved_basename = latest_stamps.get_profile_pic(profile.username) if saved_basename == profile_pic_basename: return diff --git a/instaloader/structures.py b/instaloader/structures.py index adaa222..2e604e8 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -958,6 +958,13 @@ class Profile: else: return self._metadata("profile_pic_url_hd") + @property + def profile_pic_url_no_iphone(self) -> str: + """Return URL of lower-quality profile picture. + + .. versionadded:: 4.9.3""" + return self._metadata("profile_pic_url_hd") + def get_profile_pic_url(self) -> str: """.. deprecated:: 4.0.3 From 7f9d1618ad457a35fcfd240866e153c96965a71f Mon Sep 17 00:00:00 2001 From: fireattack Date: Thu, 11 Aug 2022 12:59:02 -0500 Subject: [PATCH 08/16] Use api/v1/feed/reels_media endpoint to get story info (#1675) --- instaloader/structures.py | 53 ++++++++++++++++++++++++++++++++------- 1 file changed, 44 insertions(+), 9 deletions(-) diff --git a/instaloader/structures.py b/instaloader/structures.py index 2e604e8..46f6fe2 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -293,7 +293,7 @@ class Post: url = re.sub(r'([?&])se=\d+&?', r'\1', orig_url).rstrip('&') return url except (InstaloaderException, KeyError, IndexError) as err: - self._context.error('{} Unable to fetch high quality image version of {}.'.format(err, self)) + self._context.error(f"Unable to fetch high quality image version of {self}: {err}") return self._node["display_url"] if "display_url" in self._node else self._node["display_src"] @property @@ -357,8 +357,7 @@ class Post: orig_url = carousel_media[idx]['image_versions2']['candidates'][0]['url'] display_url = re.sub(r'([?&])se=\d+&?', r'\1', orig_url).rstrip('&') except (InstaloaderException, KeyError, IndexError) as err: - self._context.error('{} Unable to fetch high quality image version of {}.'.format( - err, self)) + self._context.error(f"Unable to fetch high quality image version of {self}: {err}") yield PostSidecarNode(is_video=is_video, display_url=display_url, video_url=node['video_url'] if is_video else None) @@ -953,7 +952,7 @@ class Profile: 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)) + self._context.error(f"Unable to fetch high quality profile pic: {err}") return self._metadata("profile_pic_url_hd") else: return self._metadata("profile_pic_url_hd") @@ -1179,8 +1178,14 @@ class StoryItem: if not self._context.is_logged_in: 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] + data = self._context.get_iphone_json( + path='api/v1/feed/reels_media/?reel_ids={}'.format(self.owner_id), params={} + ) + self._iphone_struct_ = {} + for item in data['reels'][str(self.owner_id)]['items']: + if item['pk'] == self.mediaid: + self._iphone_struct_ = item + break return self._iphone_struct_ @property @@ -1244,7 +1249,7 @@ class StoryItem: url = re.sub(r'([?&])se=\d+&?', r'\1', orig_url).rstrip('&') return url except (InstaloaderException, KeyError, IndexError) as err: - self._context.error('{} Unable to fetch high quality image version of {}.'.format(err, self)) + self._context.error(f"Unable to fetch high quality image version of {self}: {err}") return self._node['display_resources'][-1]['src'] @property @@ -1319,6 +1324,7 @@ class Story: self._node = node self._unique_id = None # type: Optional[str] self._owner_profile = None # type: Optional[Profile] + self._iphone_struct_ = None # type: Optional[Dict[str, Any]] def __repr__(self): return ''.format(self.owner_username, self.latest_media_utc) @@ -1389,9 +1395,23 @@ class Story: """The story owner's ID.""" return self.owner_profile.userid + def _fetch_iphone_struct(self) -> None: + if self._context.iphone_support and self._context.is_logged_in and not self._iphone_struct_: + data = self._context.get_iphone_json( + path='api/v1/feed/reels_media/?reel_ids={}'.format(self.owner_id), params={} + ) + self._iphone_struct_ = data['reels'][str(self.owner_id)] + def get_items(self) -> Iterator[StoryItem]: """Retrieve all items from a story.""" - yield from (StoryItem(self._context, item, self.owner_profile) for item in reversed(self._node['items'])) + self._fetch_iphone_struct() + for item in reversed(self._node['items']): + if self._iphone_struct_ is not None: + for iphone_struct_item in self._iphone_struct_['items']: + if iphone_struct_item['pk'] == int(item['id']): + item['iphone_struct'] = iphone_struct_item + break + yield StoryItem(self._context, item, self.owner_profile) class Highlight(Story): @@ -1421,6 +1441,7 @@ class Highlight(Story): super().__init__(context, node) self._owner_profile = owner self._items = None # type: Optional[List[Dict[str, Any]]] + self._iphone_struct_ = None # type: Optional[Dict[str, Any]] def __repr__(self): return ''.format(self.owner_username, self.title) @@ -1459,6 +1480,13 @@ class Highlight(Story): "highlight_reel_ids": [str(self.unique_id)], "precomposed_overlay": False})['data']['reels_media'][0]['items'] + def _fetch_iphone_struct(self) -> None: + if self._context.iphone_support and self._context.is_logged_in and not self._iphone_struct_: + data = self._context.get_iphone_json( + path='api/v1/feed/reels_media/?reel_ids=highlight:{}'.format(self.unique_id), params={} + ) + self._iphone_struct_ = data['reels']['highlight:{}'.format(self.unique_id)] + @property def itemcount(self) -> int: """Count of items associated with the :class:`Highlight` instance.""" @@ -1469,8 +1497,15 @@ class Highlight(Story): def get_items(self) -> Iterator[StoryItem]: """Retrieve all associated highlight items.""" self._fetch_items() + self._fetch_iphone_struct() assert self._items is not None - yield from (StoryItem(self._context, item, self.owner_profile) for item in self._items) + for item in self._items: + if self._iphone_struct_ is not None: + for iphone_struct_item in self._iphone_struct_['items']: + if iphone_struct_item['pk'] == int(item['id']): + item['iphone_struct'] = iphone_struct_item + break + yield StoryItem(self._context, item, self.owner_profile) class Hashtag: From e97fd8a27cb297d726f0d485ad0dceb27b45cda2 Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Thu, 11 Aug 2022 20:00:42 +0200 Subject: [PATCH 09/16] Release of Version 4.9.3 --- instaloader/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instaloader/__init__.py b/instaloader/__init__.py index 1a5cb68..54c78f5 100644 --- a/instaloader/__init__.py +++ b/instaloader/__init__.py @@ -1,7 +1,7 @@ """Download pictures (or videos) along with their captions and other metadata from Instagram.""" -__version__ = '4.9.2' +__version__ = '4.9.3' try: From 8debcbb7ab0ce38b639b9e40439d694e1c47cdcc Mon Sep 17 00:00:00 2001 From: Eduardo Kalinowski Date: Wed, 7 Sep 2022 16:01:48 -0300 Subject: [PATCH 10/16] Fixes incorrect latest stamp when download is resumed (#1717) --- instaloader/nodeiterator.py | 4 ++-- instaloader/structures.py | 13 ++----------- 2 files changed, 4 insertions(+), 13 deletions(-) diff --git a/instaloader/nodeiterator.py b/instaloader/nodeiterator.py index a49656d..bd9d4e1 100644 --- a/instaloader/nodeiterator.py +++ b/instaloader/nodeiterator.py @@ -77,7 +77,7 @@ class NodeIterator(Iterator[T]): query_variables: Optional[Dict[str, Any]] = None, query_referer: Optional[str] = None, first_data: Optional[Dict[str, Any]] = None, - is_first: Optional[Callable[[T], bool]] = None): + is_first: Optional[Callable[[T, Optional[T]], bool]] = None): self._context = context self._query_hash = query_hash self._edge_extractor = edge_extractor @@ -131,7 +131,7 @@ class NodeIterator(Iterator[T]): raise item = self._node_wrapper(node) if self._is_first is not None: - if self._is_first(item): + if self._is_first(item, self.first_item): self._first_node = node else: if self._first_node is None: diff --git a/instaloader/structures.py b/instaloader/structures.py index 46f6fe2..109c845 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -1039,17 +1039,8 @@ class Profile: ) @staticmethod - def _make_is_newest_checker() -> Callable[[Post], bool]: - newest_date: Optional[datetime] = None - def is_newest(p: Post) -> bool: - nonlocal newest_date - post_date = p.date_local - if newest_date is None or post_date > newest_date: - newest_date = post_date - return True - else: - return False - return is_newest + def _make_is_newest_checker() -> Callable[[Post, Optional[Post]], bool]: + return lambda post, first: first is None or post.date_local > first.date_local def get_followers(self) -> NodeIterator['Profile']: """ From f1b5e9cdf1701e9203340578105a0a7145a49112 Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Wed, 7 Sep 2022 21:09:46 +0200 Subject: [PATCH 11/16] Increase stale period for pull requests and issues --- .github/workflows/stale.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/stale.yml b/.github/workflows/stale.yml index 6b17c6c..7c88c0f 100644 --- a/.github/workflows/stale.yml +++ b/.github/workflows/stale.yml @@ -18,7 +18,7 @@ jobs: stale-issue-message: 'There has been no activity on this question for an extended period of time. This issue will be closed after further 14 days of inactivity.' stale-issue-label: 'stale' exempt-issue-labels: 'leave open' - days-before-stale: 15 + days-before-stale: 21 days-before-close: -1 remove-stale-when-updated: false - uses: actions/stale@v1 @@ -30,5 +30,5 @@ jobs: stale-pr-label: 'stale' exempt-issue-label: 'leave open' exempt-pr-label: 'leave open' - days-before-stale: 135 + days-before-stale: 189 days-before-close: 14 From 2206a7e5d73210608e378ee5eab8e45866f88057 Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Fri, 16 Sep 2022 19:03:24 +0200 Subject: [PATCH 12/16] Add new sponsor to README.rst We are proud to announce @socialmethod as our new sponsor. --- README.rst | 3 +++ 1 file changed, 3 insertions(+) diff --git a/README.rst b/README.rst index 8f943d2..0f423cf 100644 --- a/README.rst +++ b/README.rst @@ -127,6 +127,9 @@ Supporters .. current-sponsors-start +| Instaloader is proudly sponsored by +| `@socialmethod `__ + See `Alex' GitHub Sponsors `__ page for how you can sponsor the development of Instaloader! From 7857909886a7775f7f0d6edfe753857662ce3f80 Mon Sep 17 00:00:00 2001 From: fireattack Date: Fri, 16 Sep 2022 13:56:01 -0500 Subject: [PATCH 13/16] Handle empty video resource in some old stories in highlights (#1732) --- instaloader/instaloader.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/instaloader/instaloader.py b/instaloader/instaloader.py index e34039e..835ab5c 100644 --- a/instaloader/instaloader.py +++ b/instaloader/instaloader.py @@ -878,13 +878,18 @@ class Instaloader: filename_template = os.path.join(dirname, self.format_filename(item, target=target)) filename = self.__prepare_filename(filename_template, lambda: item.url) downloaded = False - if not item.is_video or self.download_video_thumbnails is True: + video_url_fetch_failed = False + if item.is_video and self.download_videos is True: + video_url = item.video_url + if video_url: + filename = self.__prepare_filename(filename_template, lambda: str(video_url)) + downloaded |= (not _already_downloaded(filename + ".mp4") and + self.download_pic(filename=filename, url=video_url, mtime=date_local)) + else: + video_url_fetch_failed = True + if video_url_fetch_failed or not item.is_video or self.download_video_thumbnails is True: downloaded = (not _already_downloaded(filename + ".jpg") and self.download_pic(filename=filename, url=item.url, mtime=date_local)) - if item.is_video and self.download_videos is True: - filename = self.__prepare_filename(filename_template, lambda: str(item.video_url)) - downloaded |= (not _already_downloaded(filename + ".mp4") and - self.download_pic(filename=filename, url=item.video_url, mtime=date_local)) # Save caption if desired metadata_string = _ArbitraryItemFormatter(item).format(self.storyitem_metadata_txt_pattern).strip() if metadata_string: From 84bb60c155e5494abb2725d5cd2376a747a8aa3c Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Fri, 16 Sep 2022 20:58:23 +0200 Subject: [PATCH 14/16] Release of Version 4.9.4 --- instaloader/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instaloader/__init__.py b/instaloader/__init__.py index 54c78f5..2063778 100644 --- a/instaloader/__init__.py +++ b/instaloader/__init__.py @@ -1,7 +1,7 @@ """Download pictures (or videos) along with their captions and other metadata from Instagram.""" -__version__ = '4.9.3' +__version__ = '4.9.4' try: From ffb0aea5eb79b58ebad1eee57ad097bc2aba0689 Mon Sep 17 00:00:00 2001 From: ILikeToCode <101745538+IL2C@users.noreply.github.com> Date: Mon, 26 Sep 2022 20:37:02 +0100 Subject: [PATCH 15/16] Fixed downloading hashtags (#1749) --- instaloader/structures.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instaloader/structures.py b/instaloader/structures.py index 109c845..a084416 100644 --- a/instaloader/structures.py +++ b/instaloader/structures.py @@ -1550,7 +1550,7 @@ class Hashtag: def _obtain_metadata(self): if not self._has_full_metadata: - self._node = self._query({"__a": 1}) + self._node = self._query({"__a": 1, "__d": "dis"}) self._has_full_metadata = True def _asdict(self): From 3cc29a4ceb3ff4cd04a73dd4b20979b03128f454 Mon Sep 17 00:00:00 2001 From: Alexander Graf <17130992+aandergr@users.noreply.github.com> Date: Mon, 26 Sep 2022 21:38:51 +0200 Subject: [PATCH 16/16] Release of Version 4.9.5 --- instaloader/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/instaloader/__init__.py b/instaloader/__init__.py index 2063778..dbbb5a6 100644 --- a/instaloader/__init__.py +++ b/instaloader/__init__.py @@ -1,7 +1,7 @@ """Download pictures (or videos) along with their captions and other metadata from Instagram.""" -__version__ = '4.9.4' +__version__ = '4.9.5' try: