mirror of
https://github.com/instaloader/instaloader.git
synced 2026-11-01 04:55:32 -08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
853e860363 | ||
|
|
90cfeaf471 | ||
|
|
5a24064012 | ||
|
|
0704602e50 | ||
|
|
218d88fc6d | ||
|
|
385c6c8a35 | ||
|
|
c0e5d0475b | ||
|
|
fbd7df2a90 | ||
|
|
569808f7a4 | ||
|
|
b808843efe | ||
|
|
f66cdd3e07 | ||
|
|
83dc5f749d | ||
|
|
41de9e5a7c |
@@ -28,4 +28,4 @@ jobs:
|
|||||||
branch: master
|
branch: master
|
||||||
repository-name: instaloader/instaloader.github.io
|
repository-name: instaloader/instaloader.github.io
|
||||||
folder: docs/_build/html
|
folder: docs/_build/html
|
||||||
token: ${{ secrets.GITHUB_TOKEN }}
|
token: ${{ secrets.PAGES_DEPLOYMENT_TOKEN }}
|
||||||
|
|||||||
@@ -22,10 +22,6 @@ jobs:
|
|||||||
GITHUB_REF: ${{ github.ref }}
|
GITHUB_REF: ${{ github.ref }}
|
||||||
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
|
run: echo ::set-output name=VERSION::${GITHUB_REF/refs\/tags\/v/}
|
||||||
shell: bash
|
shell: bash
|
||||||
- name: "Install Dependencies"
|
|
||||||
run: |
|
|
||||||
python -m pip install pipenv==2022.1.8
|
|
||||||
pipenv sync --dev
|
|
||||||
- name: "Generate Python Package"
|
- name: "Generate Python Package"
|
||||||
run: python setup.py sdist
|
run: python setup.py sdist
|
||||||
- name: "Publish Distribution to PyPI"
|
- name: "Publish Distribution to PyPI"
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
VERSION=${1:1}
|
VERSION=${1:1}
|
||||||
|
|
||||||
# do not deploy pre-releases
|
# do not deploy pre-releases
|
||||||
#echo $VERSION | grep -qe "[abc]" && exit 0
|
echo $VERSION | grep -qe "[abc]" && exit 0
|
||||||
|
|
||||||
cd $(dirname $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)"
|
eval "$(ssh-agent -s)"
|
||||||
chmod 600 /tmp/AUR_openssh
|
chmod 600 /tmp/AUR_openssh
|
||||||
ssh-add /tmp/AUR_openssh
|
ssh-add /tmp/AUR_openssh
|
||||||
|
mkdir -p ~/.ssh
|
||||||
ssh-keyscan -t rsa aur.archlinux.org >> ~/.ssh/known_hosts
|
ssh-keyscan -t rsa aur.archlinux.org >> ~/.ssh/known_hosts
|
||||||
|
|
||||||
# clone and modify AUR repo
|
# clone and modify AUR repo
|
||||||
|
|||||||
@@ -258,7 +258,8 @@ How to Download
|
|||||||
- Profile posts,
|
- Profile posts,
|
||||||
- Profile IGTV posts (:option:`--igtv`),
|
- Profile IGTV posts (:option:`--igtv`),
|
||||||
- Profile tagged posts (:option:`--tagged`),
|
- Profile tagged posts (:option:`--tagged`),
|
||||||
- Saved posts (``:saved``).
|
- Saved posts (``:saved``),
|
||||||
|
- Hashtags.
|
||||||
|
|
||||||
This feature is enabled by default for targets where it is supported;
|
This feature is enabled by default for targets where it is supported;
|
||||||
:option:`--resume-prefix` only changes the name of the iterator files.
|
:option:`--resume-prefix` only changes the name of the iterator files.
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||||
|
|
||||||
|
|
||||||
__version__ = '4.9b3'
|
__version__ = '4.9.1'
|
||||||
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -1176,8 +1176,8 @@ class Instaloader:
|
|||||||
"""Get Posts associated with a #hashtag.
|
"""Get Posts associated with a #hashtag.
|
||||||
|
|
||||||
.. deprecated:: 4.4
|
.. deprecated:: 4.4
|
||||||
Use :meth:`Hashtag.get_posts`."""
|
Use :meth:`Hashtag.get_posts_resumable`."""
|
||||||
return Hashtag.from_name(self.context, hashtag).get_posts()
|
return Hashtag.from_name(self.context, hashtag).get_posts_resumable()
|
||||||
|
|
||||||
def download_hashtag(self, hashtag: Union[Hashtag, str],
|
def download_hashtag(self, hashtag: Union[Hashtag, str],
|
||||||
max_count: Optional[int] = None,
|
max_count: Optional[int] = None,
|
||||||
@@ -1213,7 +1213,7 @@ class Instaloader:
|
|||||||
self.download_hashtag_profilepic(hashtag)
|
self.download_hashtag_profilepic(hashtag)
|
||||||
if posts:
|
if posts:
|
||||||
self.context.log("Retrieving pictures with hashtag #{}...".format(hashtag.name))
|
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)
|
max_count=max_count)
|
||||||
if self.save_metadata:
|
if self.save_metadata:
|
||||||
json_filename = '{0}/{1}'.format(self.dirname_pattern.format(profile=target,
|
json_filename = '{0}/{1}'.format(self.dirname_pattern.format(profile=target,
|
||||||
@@ -1334,6 +1334,10 @@ class Instaloader:
|
|||||||
profile_id))
|
profile_id))
|
||||||
profile_from_id = Profile.from_id(self.context, profile_id)
|
profile_from_id = Profile.from_id(self.context, profile_id)
|
||||||
newname = profile_from_id.username
|
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))
|
self.context.error("Profile {0} has changed its name to {1}.".format(profile_name, newname))
|
||||||
if latest_stamps is None:
|
if latest_stamps is None:
|
||||||
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
if ((format_string_contains_key(self.dirname_pattern, 'profile') or
|
||||||
|
|||||||
@@ -133,7 +133,7 @@ class NodeIterator(Iterator[T]):
|
|||||||
return item
|
return item
|
||||||
if self._data['page_info']['has_next_page']:
|
if self._data['page_info']['has_next_page']:
|
||||||
query_response = self._query(self._data['page_info']['end_cursor'])
|
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
|
page_index, data = self._page_index, self._data
|
||||||
try:
|
try:
|
||||||
self._page_index = 0
|
self._page_index = 0
|
||||||
|
|||||||
@@ -365,10 +365,16 @@ class Post:
|
|||||||
@property
|
@property
|
||||||
def caption(self) -> Optional[str]:
|
def caption(self) -> Optional[str]:
|
||||||
"""Caption."""
|
"""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"]:
|
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:
|
elif "caption" in self._node:
|
||||||
return normalize("NFC", self._node["caption"])
|
return _normalize(self._node["caption"])
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -758,13 +764,19 @@ class Profile:
|
|||||||
def _obtain_metadata(self):
|
def _obtain_metadata(self):
|
||||||
try:
|
try:
|
||||||
if not self._has_full_metadata:
|
if not self._has_full_metadata:
|
||||||
metadata = self._context.get_json('{}/feed/'.format(self.username), params={})
|
metadata = self._context.get_iphone_json(f'api/v1/users/web_profile_info/?username={self.username}',
|
||||||
self._node = metadata['entry_data']['ProfilePage'][0]['graphql']['user']
|
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
|
self._has_full_metadata = True
|
||||||
except (QueryReturnedNotFoundException, KeyError) as err:
|
except (QueryReturnedNotFoundException, KeyError) as err:
|
||||||
top_search_results = TopSearchResults(self._context, self.username)
|
top_search_results = TopSearchResults(self._context, self.username)
|
||||||
similar_profiles = [profile.username for profile in top_search_results.get_profiles()]
|
similar_profiles = [profile.username for profile in top_search_results.get_profiles()]
|
||||||
if similar_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{}: {}.'
|
raise ProfileNotExistsException('Profile {} does not exist.\nThe most similar profile{}: {}.'
|
||||||
.format(self.username,
|
.format(self.username,
|
||||||
's are' if len(similar_profiles) > 1 else ' is',
|
's are' if len(similar_profiles) > 1 else ' is',
|
||||||
@@ -1115,6 +1127,21 @@ class StoryItem:
|
|||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(self.mediaid)
|
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
|
@property
|
||||||
def _iphone_struct(self) -> Dict[str, Any]:
|
def _iphone_struct(self) -> Dict[str, Any]:
|
||||||
if not self._context.iphone_support:
|
if not self._context.iphone_support:
|
||||||
@@ -1553,7 +1580,10 @@ class Hashtag:
|
|||||||
return self._metadata("media_count")
|
return self._metadata("media_count")
|
||||||
|
|
||||||
def get_posts(self) -> Iterator[Post]:
|
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:
|
try:
|
||||||
self._metadata("edge_hashtag_to_media", "edges")
|
self._metadata("edge_hashtag_to_media", "edges")
|
||||||
self._metadata("edge_hashtag_to_media", "page_info")
|
self._metadata("edge_hashtag_to_media", "page_info")
|
||||||
@@ -1575,7 +1605,7 @@ class Hashtag:
|
|||||||
def get_all_posts(self) -> Iterator[Post]:
|
def get_all_posts(self) -> Iterator[Post]:
|
||||||
"""Yields all posts, i.e. all most recent posts and the top posts, in almost-chronological order."""
|
"""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))
|
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_top = next(sorted_top_posts, None)
|
||||||
next_other = next(other_posts, None)
|
next_other = next(other_posts, None)
|
||||||
while next_top is not None or next_other is not None:
|
while next_top is not None or next_other is not None:
|
||||||
|
|||||||
Reference in New Issue
Block a user