mirror of
https://github.com/instaloader/instaloader.git
synced 2026-10-17 14:12:17 -07:00
Compare commits
9
Commits
v4.15
...
rel/4.15.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
297a88f252 | ||
|
|
db2ae98928 | ||
|
|
b79a0a1e8b | ||
|
|
d3cd639789 | ||
|
|
618c23144e | ||
|
|
66165a9d5d | ||
|
|
27e42f661e | ||
|
|
78bbd697cd | ||
|
|
e4524e119a |
Binary file not shown.
|
After Width: | Height: | Size: 2.9 KiB |
+20
-1
@@ -124,11 +124,30 @@ Supporters
|
||||
.. current-sponsors-start
|
||||
|
||||
| Instaloader is proudly sponsored by
|
||||
| `@rocketapi-io <https://github.com/rocketapi-io>`__
|
||||
|
||||
.. list-table::
|
||||
:align: center
|
||||
|
||||
* - |socialapis|
|
||||
* - `SocialAPIs <https://socialapis.io/?utm_source=github&utm_medium=sponsor&utm_campaign=instaloader>`__
|
||||
|
||||
|
|
||||
|
||||
.. list-table::
|
||||
:align: center
|
||||
|
||||
* - `@rocketapi-io <https://github.com/rocketapi-io>`__
|
||||
|
||||
|
|
||||
|
||||
See `Alex' GitHub Sponsors <https://github.com/sponsors/aandergr>`__ page for
|
||||
how you can sponsor the development of Instaloader!
|
||||
|
||||
.. |socialapis| image:: ./.sponsor-logos/socialapis.png
|
||||
:alt: SocialAPIs
|
||||
:target: https://socialapis.io/?utm_source=github&utm_medium=sponsor&utm_campaign=instaloader
|
||||
:height: 96px
|
||||
|
||||
.. current-sponsors-end
|
||||
|
||||
It is a pleasure for us to share our Instaloader to the world, and we are proud
|
||||
|
||||
Symlink
+1
@@ -0,0 +1 @@
|
||||
../.sponsor-logos
|
||||
@@ -1,7 +1,7 @@
|
||||
"""Download pictures (or videos) along with their captions and other metadata from Instagram."""
|
||||
|
||||
|
||||
__version__ = '4.15'
|
||||
__version__ = '4.15.2'
|
||||
|
||||
|
||||
try:
|
||||
|
||||
@@ -82,7 +82,7 @@ def filterstr_to_filterfunc(filter_str: str, item_type: type):
|
||||
return filterfunc
|
||||
|
||||
|
||||
def get_cookies_from_instagram(domain, browser, cookie_file='', cookie_name=''):
|
||||
def get_cookies_from_instagram(domain: str, browser: str, cookie_file: Optional[str] = None):
|
||||
supported_browsers = {
|
||||
"brave": browser_cookie3.brave,
|
||||
"chrome": browser_cookie3.chrome,
|
||||
@@ -114,13 +114,10 @@ def get_cookies_from_instagram(domain, browser, cookie_file='', cookie_name=''):
|
||||
raise LoginException(f"No cookies found for Instagram in {browser}, "
|
||||
f"Are you logged in successfully in {browser}?")
|
||||
|
||||
if cookie_name:
|
||||
return cookies.get(cookie_name, {})
|
||||
else:
|
||||
return cookies
|
||||
return cookies
|
||||
|
||||
|
||||
def import_session(browser, instaloader, cookiefile):
|
||||
def import_session(browser: str, instaloader: Instaloader, cookiefile: Optional[str]):
|
||||
cookie = get_cookies_from_instagram('instagram', browser, cookiefile)
|
||||
if cookie is not None:
|
||||
instaloader.context.update_cookies(cookie)
|
||||
|
||||
@@ -7,7 +7,7 @@ from datetime import datetime, timedelta
|
||||
from lzma import LZMAError
|
||||
from typing import Any, Callable, Dict, Iterable, Iterator, NamedTuple, Optional, Tuple, TypeVar
|
||||
|
||||
from .exceptions import AbortDownloadException, InvalidArgumentException
|
||||
from .exceptions import InvalidArgumentException
|
||||
from .instaloadercontext import InstaloaderContext
|
||||
|
||||
class FrozenNodeIterator(NamedTuple):
|
||||
@@ -318,7 +318,7 @@ def resumable_iteration(context: InstaloaderContext,
|
||||
context.error("Warning: Not resuming from {}: {}".format(resume_file_path, exc))
|
||||
try:
|
||||
yield is_resuming, start_index
|
||||
except (KeyboardInterrupt, AbortDownloadException):
|
||||
except (Exception, KeyboardInterrupt):
|
||||
if os.path.dirname(resume_file_path):
|
||||
os.makedirs(os.path.dirname(resume_file_path), exist_ok=True)
|
||||
save(iterator.freeze(), resume_file_path)
|
||||
|
||||
+67
-11
@@ -319,9 +319,11 @@ class Post:
|
||||
|
||||
def _obtain_metadata(self):
|
||||
if not self._full_metadata_dict:
|
||||
pic_json = self._context.doc_id_graphql_query(
|
||||
response = self._context.doc_id_graphql_query(
|
||||
"8845758582119845", {"shortcode": self.shortcode}
|
||||
)["data"]["xdt_shortcode_media"]
|
||||
)
|
||||
data = (response or {}).get("data")
|
||||
pic_json = data.get("xdt_shortcode_media") if data else None
|
||||
if pic_json is None:
|
||||
raise BadResponseException("Fetching Post metadata failed.")
|
||||
try:
|
||||
@@ -889,7 +891,8 @@ class Profile:
|
||||
Also, this class implements == and is hashable.
|
||||
"""
|
||||
def __init__(self, context: InstaloaderContext, node: Dict[str, Any]):
|
||||
assert 'username' in node
|
||||
assert "username" in node
|
||||
assert "id" in node or "pk" in node
|
||||
self._context = context
|
||||
self._has_public_story: Optional[bool] = None
|
||||
self._node = node
|
||||
@@ -909,10 +912,13 @@ class Profile:
|
||||
:param username: Username
|
||||
:raises: :class:`ProfileNotExistsException`
|
||||
"""
|
||||
# pylint:disable=protected-access
|
||||
profile = cls(context, {'username': username.lower()})
|
||||
profile._obtain_metadata() # to raise ProfileNotExistsException now in case username is invalid
|
||||
return profile
|
||||
data = context.doc_id_graphql_query("26347858941511777", {"hasQuery": True, "query": username})["data"]
|
||||
if data:
|
||||
for user in data["xdt_api__v1__fbsearch__non_profiled_serp"]["users"]:
|
||||
if user["username"].lower() == username.lower():
|
||||
return cls(context, user)
|
||||
|
||||
raise ProfileNotExistsException("Profile {} does not exist.".format(username))
|
||||
|
||||
@classmethod
|
||||
def from_id(cls, context: InstaloaderContext, profile_id: int):
|
||||
@@ -979,11 +985,23 @@ class Profile:
|
||||
def _obtain_metadata(self):
|
||||
try:
|
||||
if not self._has_full_metadata:
|
||||
metadata = self._context.get_iphone_json(f'api/v1/users/web_profile_info/?username={self.username}',
|
||||
params={})
|
||||
if metadata['data']['user'] is None:
|
||||
user_id = self._node.get('id') or self._node.get('pk')
|
||||
variables = {
|
||||
"id": str(user_id),
|
||||
"render_surface": "PROFILE",
|
||||
"__relay_internal__pv__PolarisCannesGuardianExperienceEnabledrelayprovider": True,
|
||||
"__relay_internal__pv__PolarisCASB976ProfileEnabledrelayprovider": False,
|
||||
"__relay_internal__pv__PolarisRepostsConsumptionEnabledrelayprovider": False,
|
||||
"__relay_internal__pv__PolarisWebSchoolsEnabledrelayprovider": False,
|
||||
"enable_integrity_filters": True,
|
||||
}
|
||||
data = self._context.doc_id_graphql_query('27937681195819736', variables)
|
||||
if data is None:
|
||||
raise QueryReturnedNotFoundException('GraphQL query returned None')
|
||||
user_data = data.get('data', {}).get('user')
|
||||
if user_data is None:
|
||||
raise ProfileNotExistsException('Profile {} does not exist.'.format(self.username))
|
||||
self._node = metadata['data']['user']
|
||||
self._node = self._normalize_profile_data(user_data)
|
||||
self._has_full_metadata = True
|
||||
except (QueryReturnedNotFoundException, KeyError) as err:
|
||||
top_search_results = TopSearchResults(self._context, self.username)
|
||||
@@ -998,6 +1016,44 @@ class Profile:
|
||||
', '.join(similar_profiles[0:5]))) from err
|
||||
raise ProfileNotExistsException('Profile {} does not exist.'.format(self.username)) from err
|
||||
|
||||
def _normalize_profile_data(self, user_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Normalize PolarisProfilePageContentQuery response to match legacy format."""
|
||||
normalized = user_data.copy()
|
||||
if 'id' not in normalized and 'pk' in normalized:
|
||||
normalized['id'] = normalized['pk']
|
||||
if 'edge_owner_to_timeline_media' not in normalized and 'media_count' in normalized:
|
||||
normalized['edge_owner_to_timeline_media'] = {'count': normalized['media_count']}
|
||||
if 'edge_felix_video_timeline' not in normalized:
|
||||
normalized['edge_felix_video_timeline'] = {'count': 0}
|
||||
if 'edge_followed_by' not in normalized and 'follower_count' in normalized:
|
||||
normalized['edge_followed_by'] = {'count': normalized['follower_count']}
|
||||
if 'edge_follow' not in normalized and 'following_count' in normalized:
|
||||
normalized['edge_follow'] = {'count': normalized['following_count']}
|
||||
if 'is_business_account' not in normalized and 'is_business' in normalized:
|
||||
normalized['is_business_account'] = normalized['is_business']
|
||||
if 'business_category_name' not in normalized and 'category' in normalized:
|
||||
normalized['business_category_name'] = normalized['category']
|
||||
friendship = normalized.get('friendship_status', {}) or {}
|
||||
if 'followed_by_viewer' not in normalized:
|
||||
normalized['followed_by_viewer'] = friendship.get('following', False)
|
||||
if 'follows_viewer' not in normalized:
|
||||
normalized['follows_viewer'] = friendship.get('followed_by', False)
|
||||
if 'blocked_by_viewer' not in normalized:
|
||||
normalized['blocked_by_viewer'] = friendship.get('blocking', False)
|
||||
if 'has_blocked_viewer' not in normalized:
|
||||
normalized['has_blocked_viewer'] = False
|
||||
if 'has_requested_viewer' not in normalized:
|
||||
normalized['has_requested_viewer'] = friendship.get('incoming_request', False)
|
||||
if 'requested_by_viewer' not in normalized:
|
||||
normalized['requested_by_viewer'] = friendship.get('outgoing_request', False)
|
||||
if 'profile_pic_url_hd' not in normalized:
|
||||
hd_info = normalized.get('hd_profile_pic_url_info')
|
||||
if hd_info and 'url' in hd_info:
|
||||
normalized['profile_pic_url_hd'] = hd_info['url']
|
||||
elif 'profile_pic_url' in normalized:
|
||||
normalized['profile_pic_url_hd'] = normalized['profile_pic_url']
|
||||
return normalized
|
||||
|
||||
def _metadata(self, *keys) -> Any:
|
||||
try:
|
||||
d = self._node
|
||||
|
||||
Reference in New Issue
Block a user