""" Implementation of the syndication taxonomy in architecture.md """ from dataclasses import dataclass, asdict, replace from typing import List, Dict, Optional, Tuple, Union import re @dataclass class ContentId: prefix: str id: str def __str__ (self): return self.prefix + self.id @staticmethod def parse (content_id, prefix, id_pattern = '(.+)'): raw_id = content_id[len(prefix):] id_matches = re.fullmatch(id_pattern, raw_id) if id_matches: raw_id = id_matches[0] return ContentId(prefix=prefix, id=raw_id) @dataclass class PublicMetrics: reply_count: Optional[int] = None quote_count: Optional[int] = None retweet_count: Optional[int] = None like_count: Optional[int] = None impression_count: Optional[int] = None bookmark_count: Optional[int] = None # may be video only view_count: Optional[int] = None def items (self): return asdict(self).items() @dataclass class NonPublicMetrics: impression_count: Optional[int] = None user_profile_clicks: Optional[int] = None url_link_clicks: Optional[int] = None def items (self): return asdict(self).items() @dataclass class MediaItem: type: str preview_image_url: str media_key: Optional[str] = None image_url: Optional[str] = None url: Optional[str] = None content_type: Optional[str] = None duration_ms: Optional[int] = None height: Optional[int] = None width: Optional[int] = None size: Optional[int] = None public_metrics: Optional[PublicMetrics] = None @dataclass class Card: display_url: Optional[str] = None source_url: Optional[str] = None content: Optional[str] = None title: Optional[str] = None preview_image_url: Optional[str] = None image_url: Optional[str] = None @dataclass class FeedItemAction: route: str route_params: Dict # intended as external resources, # not first class display. # PDF, chat, embedded image (iframe) @dataclass class FeedItemAttachment: name: str content_type: str url: Optional[str] = None content: Optional[object] = None size: Optional[int] = None @dataclass class UnrepliedSection: feed_item_id: Optional[str] = None description: Optional[str] = None text: Optional[str] = None span: Optional[Tuple[int, int]] = None def text_from (self, feed_item: 'FeedItem') -> str: if not self.span or not feed_item.text: return return feed_item.text[self.span[0]:self.span[1]] ReplyingToSection = UnrepliedSection @dataclass class FeedItem: id: str created_at: str display_name: str handle: str published_by: Optional['FeedServiceUser'] = None title: Optional[str] = None text: Optional[str] = None html: Optional[str] = None author_is_verified: Optional[bool] = None url: Optional[str] = None conversation_id: Optional[str] = None avi_icon_url: Optional[str] = None author_url: Optional[str] = None author_id: Optional[str] = None source_url: Optional[str] = None source_author_url: Optional[str] = None reply_depth: Optional[int] = 0 is_marked: Optional[bool] = None is_bookmarked: Optional[bool] = None card: Optional[Card] = None public_metrics: Optional[PublicMetrics] = None non_public_metrics: Optional[NonPublicMetrics] = None retweeted_tweet_id: Optional[str] = None source_retweeted_by_url: Optional[str] = None retweeted_by: Optional[str] = None retweeted_by_url: Optional[str] = None videos: Optional[List[MediaItem]] = None photos: Optional[List[MediaItem]] = None quoted_tweet_id: Optional[str] = None quoted_tweet: Optional['FeedItem'] = None replied_tweet_id: Optional[str] = None replied_tweet: Optional['FeedItem'] = None note: Optional[str] = None debug_source_data: Optional[Dict] = None attachments: Optional[List[FeedItemAttachment]] = None # At some point we may move to feed_item_actions when the set is known actions: Optional[Dict[str, FeedItemAction]] = None # This is a TBD concept to highlight unreplied parts of a message. unreplied: Optional[List[UnrepliedSection]] = None # This is a TBD concept to highlight parts of a message that are a reply. replying_to: Optional[List[ReplyingToSection]] = None # tm = FeedItem(id="1", text="aa", created_at="fs", display_name="fda", handle="fdsafas") @dataclass class ThreadItem: feed_item: Union[FeedItem,'Collection','RoutedMessage'] children: Optional[List['ThreadItem']] = None parent: Optional['ThreadItem'] = None parents: Optional[List['ThreadItem']] = None actions: Optional[Dict[str, FeedItemAction]] = None @dataclass class FeedServiceUser: id: str url: str name: str # display_name username: str # handle is_verified: Optional[bool] = None is_protected: Optional[bool] = None created_at: Optional[str] = None description: Optional[str] = None preview_image_url: Optional[str] = None # deprecated... rename to avatar_image_url poster_image_url: Optional[str] = None website: Optional[str] = None location: Optional[str] = None actions: Optional[Dict[str, FeedItemAction]] = None source_url: Optional[str] = None raw_user: Optional = None @property def display_name (self) -> str: return self.name @property def handle (self) -> str: return self.username @property def avatar_image_url (self) -> str: return self.preview_image_url @dataclass class Collection: """ Works for both collections and lists for now """ id:str name: str description: Optional[str] = None preview_image_url: Optional[str] = None created_at: Optional[str] = None updated_at: Optional[str] = None owner_id: Optional[str] = None owner: Optional[FeedServiceUser] = None url: Optional[str] = None total_count: Optional[int] = None current_page: Optional['CollectionPage'] = None @dataclass class CollectionItem: item: List[Union[FeedServiceUser,FeedItem,ThreadItem,Collection]] = None sort_order: Optional[int] = None after_id: Optional[str] = None @dataclass class CollectionPage: """ Works for Collections and Lists for now, as well as Threads and nested Collections. Feed is a Collection. """ id: str items: Optional[List[Union[FeedServiceUser,FeedItem,ThreadItem,CollectionItem,Collection]]] = None next_token: Optional[str] = None last_dt: Optional[str] = None total_count: Optional[int] = None includes: Optional[Dict] = None def cleandict(d): if isinstance(d, dict): return {k: cleandict(v) for k, v in d.items() if v is not None} elif isinstance(d, list): return [cleandict(v) for v in d] else: return d