parent
0dcc912987
commit
91d5d5f867
@ -135,6 +135,11 @@ User Stories
|
||||
.. autoclass:: StoryItem
|
||||
:no-show-inheritance:
|
||||
|
||||
Highlights
|
||||
""""""""""
|
||||
|
||||
.. autoclass:: Highlight
|
||||
|
||||
Profiles
|
||||
""""""""
|
||||
|
||||
|
@ -15,5 +15,5 @@ else:
|
||||
from .exceptions import *
|
||||
from .instaloader import Instaloader
|
||||
from .instaloadercontext import InstaloaderContext
|
||||
from .structures import (Post, PostSidecarNode, PostComment, PostLocation, Profile, Story, StoryItem,
|
||||
from .structures import (Highlight, Post, PostSidecarNode, PostComment, PostLocation, Profile, Story, StoryItem,
|
||||
load_structure_from_file, save_structure_to_file)
|
||||
|
@ -15,7 +15,7 @@ from typing import Any, Callable, Iterator, List, Optional, Set, Union
|
||||
|
||||
from .exceptions import *
|
||||
from .instaloadercontext import InstaloaderContext
|
||||
from .structures import JsonExportable, Post, PostLocation, Profile, Story, StoryItem, save_structure_to_file
|
||||
from .structures import Highlight, JsonExportable, Post, PostLocation, Profile, Story, StoryItem, save_structure_to_file
|
||||
|
||||
|
||||
def get_default_session_filename(username: str) -> str:
|
||||
@ -190,7 +190,7 @@ class Instaloader:
|
||||
|
||||
def update_comments(self, filename: str, post: Post) -> None:
|
||||
def _postcomment_asdict(comment):
|
||||
return {'id': comment.id,
|
||||
return {'id': comment.unique_id,
|
||||
'created_at': int(comment.created_at_utc.replace(tzinfo=timezone.utc).timestamp()),
|
||||
'text': comment.text,
|
||||
'owner': comment.owner._asdict()}
|
||||
@ -488,6 +488,56 @@ class Instaloader:
|
||||
self.context.log()
|
||||
return downloaded
|
||||
|
||||
@_requires_login
|
||||
def get_highlights(self, userid: int) -> Iterator[Highlight]:
|
||||
"""Get all highlights from a user.
|
||||
To use this, one needs to be logged in
|
||||
|
||||
:param userid: ID of the profile whose highlights should get fetched.
|
||||
"""
|
||||
|
||||
data = self.context.graphql_query("7c16654f22c819fb63d1183034a5162f",
|
||||
{"user_id": userid, "include_chaining": False, "include_reel": False,
|
||||
"include_suggested_users": False, "include_logged_out_extras": False,
|
||||
"include_highlight_reels": True})["data"]["user"]['edge_highlight_reels']
|
||||
if data is None:
|
||||
raise BadResponseException('Bad highlights reel JSON.')
|
||||
yield from (Highlight(self.context, edge['node']) for edge in data['edges'])
|
||||
|
||||
@_requires_login
|
||||
def download_highlights(self,
|
||||
userid: int,
|
||||
fast_update: bool = False,
|
||||
filename_target: Optional[str] = None,
|
||||
storyitem_filter: Optional[Callable[[StoryItem], bool]] = None) -> None:
|
||||
"""
|
||||
Download available highlights from a user whose ID is given.
|
||||
To use this, one needs to be logged in
|
||||
|
||||
:param userid: ID of the profile whose highlights should get downloaded.
|
||||
:param fast_update: If true, abort when first already-downloaded picture is encountered
|
||||
:param filename_target: Replacement for {target} in dirname_pattern and filename_pattern
|
||||
or None if profile name and the highlights' titles should be used instead
|
||||
:param storyitem_filter: function(storyitem), which returns True if given StoryItem should be downloaded
|
||||
"""
|
||||
for user_highlight in self.get_highlights(userid):
|
||||
name = user_highlight.owner_username
|
||||
self.context.log("Retrieving highlights \"{}\" from profile {}".format(user_highlight.title, name))
|
||||
totalcount = user_highlight.itemcount
|
||||
count = 1
|
||||
for item in user_highlight.get_items():
|
||||
if storyitem_filter is not None and not storyitem_filter(item):
|
||||
self.context.log("<{} skipped>".format(item), flush=True)
|
||||
continue
|
||||
self.context.log("[%3i/%3i] " % (count, totalcount), end="", flush=True)
|
||||
count += 1
|
||||
with self.context.error_catcher('Download highlights \"{}\" from user {}'.format(user_highlight.title, name)):
|
||||
downloaded = self.download_storyitem(item, filename_target
|
||||
if filename_target
|
||||
else '{}/{}'.format(name, user_highlight.title))
|
||||
if fast_update and not downloaded:
|
||||
break
|
||||
|
||||
@_requires_login
|
||||
def get_feed_posts(self) -> Iterator[Post]:
|
||||
"""Get Posts of the user's feed.
|
||||
|
@ -782,7 +782,7 @@ class Story:
|
||||
# story is a Story object
|
||||
for item in story.get_items():
|
||||
# item is a StoryItem object
|
||||
L.download_storyitem(item, ':stores')
|
||||
L.download_storyitem(item, ':stories')
|
||||
|
||||
This class implements == and is hashable.
|
||||
|
||||
@ -805,7 +805,7 @@ class Story:
|
||||
return NotImplemented
|
||||
|
||||
def __hash__(self) -> int:
|
||||
return hash(self._unique_id)
|
||||
return hash(self.unique_id)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> str:
|
||||
@ -868,6 +868,61 @@ class Story:
|
||||
yield from (StoryItem(self._context, item, self.owner_profile) for item in reversed(self._node['items']))
|
||||
|
||||
|
||||
class Highlight(Story):
|
||||
|
||||
def __init__(self, context: InstaloaderContext, node: Dict[str, Any]):
|
||||
super().__init__(context, node)
|
||||
self._items = None
|
||||
|
||||
def __repr__(self):
|
||||
return '<Highlight by {}: {}>'.format(self.owner_username, self.title)
|
||||
|
||||
@property
|
||||
def unique_id(self) -> int:
|
||||
"""A unique ID identifying this set of highlights."""
|
||||
return int(self._node['id'])
|
||||
|
||||
@property
|
||||
def owner_profile(self) -> Profile:
|
||||
""":class:`Profile` instance of the highlights' owner."""
|
||||
if not self._owner_profile:
|
||||
self._owner_profile = Profile(self._context, self._node['owner'])
|
||||
return self._owner_profile
|
||||
|
||||
@property
|
||||
def title(self) -> str:
|
||||
"""The title of these highlights."""
|
||||
return self._node['title']
|
||||
|
||||
@property
|
||||
def cover_url(self) -> str:
|
||||
"""URL of the highlights' cover."""
|
||||
return self._node['cover_media']['thumbnail_src']
|
||||
|
||||
@property
|
||||
def cover_cropped_url(self) -> str:
|
||||
"""URL of the cropped version of the cover."""
|
||||
return self._node['cover_media_cropped_thumbnail']['url']
|
||||
|
||||
def _fetch_items(self):
|
||||
if not self._items:
|
||||
self._items = self._context.graphql_query("45246d3fe16ccc6577e0bd297a5db1ab",
|
||||
{"reel_ids": [], "tag_names": [], "location_ids": [],
|
||||
"highlight_reel_ids": [str(self.unique_id)],
|
||||
"precomposed_overlay": False})['data']['reels_media'][0]['items']
|
||||
|
||||
@property
|
||||
def itemcount(self) -> int:
|
||||
"""Count of items associated with the :class:`Highlight` instance."""
|
||||
self._fetch_items()
|
||||
return len(self._items)
|
||||
|
||||
def get_items(self) -> Iterator[StoryItem]:
|
||||
"""Retrieve all associated highlight items."""
|
||||
self._fetch_items()
|
||||
yield from (StoryItem(self._context, item, self.owner_profile) for item in self._items)
|
||||
|
||||
|
||||
JsonExportable = Union[Post, Profile, StoryItem]
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user