1
0
mirror of https://github.com/instaloader/instaloader.git synced 2024-07-14 23:00:06 +02:00

Add class and functions for downloading highlights

Requested in #162.
This commit is contained in:
André Koch-Kramer 2018-08-11 07:25:33 +02:00
parent 0dcc912987
commit 91d5d5f867
4 changed files with 115 additions and 5 deletions

View File

@ -135,6 +135,11 @@ User Stories
.. autoclass:: StoryItem
:no-show-inheritance:
Highlights
""""""""""
.. autoclass:: Highlight
Profiles
""""""""

View File

@ -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)

View 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.

View File

@ -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]