123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286 |
- """
- 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
|