Compare commits

...
13 Commits
Author SHA1 Message Date
Alexander Graf 853e860363 Release of Version 4.9.1 2022-06-08 18:13:38 +02:00
twitter-79 90cfeaf471 Fixed fetching a users profile information (#1561)
* fixed fetching a users profile information

switched to new api/v1/users/web_profile_info/?username=xy endpoint

* fixed non existing users

thanks to @Dibort for fixing this!
2022-06-08 18:12:07 +02:00
Nicolai Weitkemper 5a24064012 Fix incorrect error messages when certain requests fail (#1536)
- handle case where `Profile.from_id` works, but `Profile.from_username` doesn't

- Handle case where `QueryReturnedNotFoundException` lies and `similar_profiles` contains the exact username

- Still raises `ProfileNotExistsException` for compatibility.
2022-05-30 21:29:34 +02:00
MiguelX413 0704602e50 Checks if caption is not None before normalization (#1475) 2022-04-18 09:49:34 +02:00
Alexander Graf 218d88fc6d Release of Version 4.9 2022-03-26 17:18:56 +01:00
Alexander Graf 385c6c8a35 Merge branch 'upcoming/v4.9' 2022-03-26 17:15:09 +01:00
Alexander Graf c0e5d0475b StoryItem.from_mediaid()
Addresses #1238.
2022-03-26 17:03:57 +01:00
Alexander Graf fbd7df2a90 Use resumable iteration to download hashtags
Closes #1457.
2022-03-26 17:02:06 +01:00
JeremyKj87 569808f7a4 Fix for infinity loading if next page is empty (#1465) 2022-03-26 15:57:45 +01:00
Alexander Graf b808843efe Actions: Optimize Publish Package workflow 2022-03-19 17:27:22 +01:00
Alexander Graf f66cdd3e07 Release of Version 4.8.5 2022-03-19 17:15:52 +01:00
Alexander Graf 83dc5f749d Attempt to fix Documentation deployment 2022-03-19 17:02:24 +01:00
Alexander Graf 41de9e5a7c Attempt to fix AUR deployment 2022-03-19 16:53:55 +01:00
8 changed files with 50 additions and 18 deletions
+1 -1
View File
@@ -28,4 +28,4 @@ jobs:
branch: master
repository-name: instaloader/instaloader.github.io
folder: docs/_build/html
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ secrets.PAGES_DEPLOYMENT_TOKEN }}
-4
View File
@@ -22,10 +22,6 @@ jobs:
GITHUB_REF: ${{ github.ref }}
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
shell: bash
- name: "Install Dependencies"
run: |
python -m pip install pipenv==2022.1.8
pipenv sync --dev
- name: "Generate Python Package"
run: python setup.py sdist
- name: "Publish Distribution to PyPI"
+2 -1
View File
@@ -3,7 +3,7 @@
VERSION=${1:1}
# do not deploy pre-releases
#echo $VERSION | grep -qe "[abc]" && exit 0
echo $VERSION | grep -qe "[abc]" && exit 0
cd $(dirname $0)
@@ -12,6 +12,7 @@ openssl aes-256-cbc -K $AUR_KEY -iv $AUR_IV -in id_rsa_AUR.enc -out /tmp/AUR_ope
eval "$(ssh-agent -s)"
chmod 600 /tmp/AUR_openssh
ssh-add /tmp/AUR_openssh
mkdir -p ~/.ssh
ssh-keyscan -t rsa aur.archlinux.org >> ~/.ssh/known_hosts
# clone and modify AUR repo
+2 -1
View File
@@ -258,7 +258,8 @@ How to Download
- Profile posts,
- Profile IGTV posts (:option:`--igtv`),
- Profile tagged posts (:option:`--tagged`),
- Saved posts (``:saved``).
- Saved posts (``:saved``),
- Hashtags.
This feature is enabled by default for targets where it is supported;
:option:`--resume-prefix` only changes the name of the iterator files.
+1 -1
View File
@@ -1,7 +1,7 @@
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
__version__ = '4.9b3'
__version__ = '4.9.1'
try:
+7 -3
View File
@@ -1176,8 +1176,8 @@ class Instaloader:
"""Get Posts associated with a #hashtag.
.. deprecated:: 4.4
Use :meth:`Hashtag.get_posts`."""
return Hashtag.from_name(self.context, hashtag).get_posts()
Use :meth:`Hashtag.get_posts_resumable`."""
return Hashtag.from_name(self.context, hashtag).get_posts_resumable()
def download_hashtag(self, hashtag: Union[Hashtag, str],
max_count: Optional[int] = None,
@@ -1213,7 +1213,7 @@ class Instaloader:
self.download_hashtag_profilepic(hashtag)
if posts:
self.context.log("Retrieving pictures with hashtag #{}...".format(hashtag.name))
self.posts_download_loop(hashtag.get_all_posts(), target, fast_update, post_filter,
self.posts_download_loop(hashtag.get_posts_resumable(), target, fast_update, post_filter,
max_count=max_count)
if self.save_metadata:
json_filename = '{0}/{1}'.format(self.dirname_pattern.format(profile=target,
@@ -1334,6 +1334,10 @@ class Instaloader:
profile_id))
profile_from_id = Profile.from_id(self.context, profile_id)
newname = profile_from_id.username
if profile_name == newname:
self.context.error(
f"Warning: Profile {profile_name} could not be retrieved by its name, but by its ID.")
return profile_from_id
self.context.error("Profile {0} has changed its name to {1}.".format(profile_name, newname))
if latest_stamps is None:
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
+1 -1
View File
@@ -133,7 +133,7 @@ class NodeIterator(Iterator[T]):
return item
if self._data['page_info']['has_next_page']:
query_response = self._query(self._data['page_info']['end_cursor'])
if self._data['edges'] != query_response['edges']:
if self._data['edges'] != query_response['edges'] and len(query_response['edges']) > 0:
page_index, data = self._page_index, self._data
try:
self._page_index = 0
+36 -6
View File
@@ -365,10 +365,16 @@ class Post:
@property
def caption(self) -> Optional[str]:
"""Caption."""
def _normalize(string: Optional[str]) -> Optional[str]:
if string is not None:
return normalize("NFC", string)
else:
return None
if "edge_media_to_caption" in self._node and self._node["edge_media_to_caption"]["edges"]:
return normalize("NFC", self._node["edge_media_to_caption"]["edges"][0]["node"]["text"])
return _normalize(self._node["edge_media_to_caption"]["edges"][0]["node"]["text"])
elif "caption" in self._node:
return normalize("NFC", self._node["caption"])
return _normalize(self._node["caption"])
return None
@property
@@ -758,13 +764,19 @@ class Profile:
def _obtain_metadata(self):
try:
if not self._has_full_metadata:
metadata = self._context.get_json('{}/feed/'.format(self.username), params={})
self._node = metadata['entry_data']['ProfilePage'][0]['graphql']['user']
metadata = self._context.get_iphone_json(f'api/v1/users/web_profile_info/?username={self.username}',
params={})
if metadata['data']['user'] is None:
raise ProfileNotExistsException('Profile {} does not exist.'.format(self.username))
self._node = metadata['data']['user']
self._has_full_metadata = True
except (QueryReturnedNotFoundException, KeyError) as err:
top_search_results = TopSearchResults(self._context, self.username)
similar_profiles = [profile.username for profile in top_search_results.get_profiles()]
if similar_profiles:
if self.username in similar_profiles:
raise ProfileNotExistsException(
f"Profile {self.username} seems to exist, but could not be loaded.") from err
raise ProfileNotExistsException('Profile {} does not exist.\nThe most similar profile{}: {}.'
.format(self.username,
's are' if len(similar_profiles) > 1 else ' is',
@@ -1115,6 +1127,21 @@ class StoryItem:
def __hash__(self) -> int:
return hash(self.mediaid)
@classmethod
def from_mediaid(cls, context: InstaloaderContext, mediaid: int):
"""Create a StoryItem object from a given mediaid.
.. versionadded:: 4.9
"""
pic_json = context.graphql_query(
'2b0673e0dc4580674a88d426fe00ea90',
{'shortcode': Post.mediaid_to_shortcode(mediaid)}
)
shortcode_media = pic_json['data']['shortcode_media']
if shortcode_media is None:
raise BadResponseException("Fetching StoryItem metadata failed.")
return cls(context, shortcode_media)
@property
def _iphone_struct(self) -> Dict[str, Any]:
if not self._context.iphone_support:
@@ -1553,7 +1580,10 @@ class Hashtag:
return self._metadata("media_count")
def get_posts(self) -> Iterator[Post]:
"""Yields the recent posts associated with this hashtag."""
"""Yields the recent posts associated with this hashtag.
.. deprecated:: 4.9
Use :meth:`Hashtag.get_posts_resumable` as this method may return incorrect results (:issue:`1457`)"""
try:
self._metadata("edge_hashtag_to_media", "edges")
self._metadata("edge_hashtag_to_media", "page_info")
@@ -1575,7 +1605,7 @@ class Hashtag:
def get_all_posts(self) -> Iterator[Post]:
"""Yields all posts, i.e. all most recent posts and the top posts, in almost-chronological order."""
sorted_top_posts = iter(sorted(islice(self.get_top_posts(), 9), key=lambda p: p.date_utc, reverse=True))
other_posts = self.get_posts()
other_posts = self.get_posts_resumable()
next_top = next(sorted_top_posts, None)
next_other = next(other_posts, None)
while next_top is not None or next_other is not None: