parent
0dcc912987
commit
91d5d5f867
@ -135,6 +135,11 @@ User Stories
|
|||||||
.. autoclass:: StoryItem
|
.. autoclass:: StoryItem
|
||||||
:no-show-inheritance:
|
:no-show-inheritance:
|
||||||
|
|
||||||
|
Highlights
|
||||||
|
""""""""""
|
||||||
|
|
||||||
|
.. autoclass:: Highlight
|
||||||
|
|
||||||
Profiles
|
Profiles
|
||||||
""""""""
|
""""""""
|
||||||
|
|
||||||
|
@ -15,5 +15,5 @@ else:
|
|||||||
from .exceptions import *
|
from .exceptions import *
|
||||||
from .instaloader import Instaloader
|
from .instaloader import Instaloader
|
||||||
from .instaloadercontext import InstaloaderContext
|
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)
|
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 .exceptions import *
|
||||||
from .instaloadercontext import InstaloaderContext
|
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:
|
def get_default_session_filename(username: str) -> str:
|
||||||
@ -190,7 +190,7 @@ class Instaloader:
|
|||||||
|
|
||||||
def update_comments(self, filename: str, post: Post) -> None:
|
def update_comments(self, filename: str, post: Post) -> None:
|
||||||
def _postcomment_asdict(comment):
|
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()),
|
'created_at': int(comment.created_at_utc.replace(tzinfo=timezone.utc).timestamp()),
|
||||||
'text': comment.text,
|
'text': comment.text,
|
||||||
'owner': comment.owner._asdict()}
|
'owner': comment.owner._asdict()}
|
||||||
@ -488,6 +488,56 @@ class Instaloader:
|
|||||||
self.context.log()
|
self.context.log()
|
||||||
return downloaded
|
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
|
@_requires_login
|
||||||
def get_feed_posts(self) -> Iterator[Post]:
|
def get_feed_posts(self) -> Iterator[Post]:
|
||||||
"""Get Posts of the user's feed.
|
"""Get Posts of the user's feed.
|
||||||
|
@ -782,7 +782,7 @@ class Story:
|
|||||||
# story is a Story object
|
# story is a Story object
|
||||||
for item in story.get_items():
|
for item in story.get_items():
|
||||||
# item is a StoryItem object
|
# item is a StoryItem object
|
||||||
L.download_storyitem(item, ':stores')
|
L.download_storyitem(item, ':stories')
|
||||||
|
|
||||||
This class implements == and is hashable.
|
This class implements == and is hashable.
|
||||||
|
|
||||||
@ -805,7 +805,7 @@ class Story:
|
|||||||
return NotImplemented
|
return NotImplemented
|
||||||
|
|
||||||
def __hash__(self) -> int:
|
def __hash__(self) -> int:
|
||||||
return hash(self._unique_id)
|
return hash(self.unique_id)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def unique_id(self) -> str:
|
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']))
|
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]
|
JsonExportable = Union[Post, Profile, StoryItem]
|
||||||
|
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user