view_model.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224
  1. """
  2. Implementation of the syndication taxonomy in architecture.md
  3. """
  4. from dataclasses import dataclass, asdict, replace
  5. from typing import List, Dict, Optional, Tuple
  6. @dataclass
  7. class PublicMetrics:
  8. reply_count: Optional[int] = None
  9. quote_count: Optional[int] = None
  10. retweet_count: Optional[int] = None
  11. like_count: Optional[int] = None
  12. # may be video only
  13. view_count: Optional[int] = None
  14. def items (self):
  15. return asdict(self).items()
  16. @dataclass
  17. class NonPublicMetrics:
  18. impression_count: Optional[int] = None
  19. user_profile_clicks: Optional[int] = None
  20. url_link_clicks: Optional[int] = None
  21. def items (self):
  22. return asdict(self).items()
  23. @dataclass
  24. class MediaItem:
  25. type: str
  26. preview_image_url: str
  27. media_key: Optional[str] = None
  28. image_url: Optional[str] = None
  29. url: Optional[str] = None
  30. content_type: Optional[str] = None
  31. duration_ms: Optional[int] = None
  32. height: Optional[int] = None
  33. width: Optional[int] = None
  34. size: Optional[int] = None
  35. public_metrics: Optional[PublicMetrics] = None
  36. @dataclass
  37. class Card:
  38. display_url: Optional[str] = None
  39. source_url: Optional[str] = None
  40. content: Optional[str] = None
  41. title: Optional[str] = None
  42. preview_image_url: Optional[str] = None
  43. image_url: Optional[str] = None
  44. @dataclass
  45. class FeedItemAction:
  46. route: str
  47. route_params: Dict
  48. # intended as external resources,
  49. # not first class display.
  50. # PDF, chat, embedded image (iframe)
  51. @dataclass
  52. class FeedItemAttachment:
  53. name: str
  54. content_type: str
  55. url: Optional[str] = None
  56. content: Optional[object] = None
  57. size: Optional[int] = None
  58. @dataclass
  59. class UnrepliedSection:
  60. feed_item_id: Optional[str] = None
  61. description: Optional[str] = None
  62. text: Optional[str] = None
  63. span: Optional[Tuple[int, int]] = None
  64. def text_from (self, feed_item: 'FeedItem') -> str:
  65. if not self.span or not feed_item.text:
  66. return
  67. return feed_item.text[self.span[0]:self.span[1]]
  68. ReplyingToSection = UnrepliedSection
  69. @dataclass
  70. class FeedItem:
  71. id: str
  72. created_at: str
  73. display_name: str
  74. handle: str
  75. published_by: Optional['FeedServiceUser'] = None
  76. text: Optional[str] = None
  77. html: Optional[str] = None
  78. author_is_verified: Optional[bool] = None
  79. url: Optional[str] = None
  80. conversation_id: Optional[str] = None
  81. avi_icon_url: Optional[str] = None
  82. author_url: Optional[str] = None
  83. author_id: Optional[str] = None
  84. source_url: Optional[str] = None
  85. source_author_url: Optional[str] = None
  86. reply_depth: Optional[int] = 0
  87. is_marked: Optional[bool] = None
  88. card: Optional[Card] = None
  89. public_metrics: Optional[PublicMetrics] = None
  90. non_public_metrics: Optional[NonPublicMetrics] = None
  91. retweeted_tweet_id: Optional[str] = None
  92. source_retweeted_by_url: Optional[str] = None
  93. retweeted_by: Optional[str] = None
  94. retweeted_by_url: Optional[str] = None
  95. videos: Optional[List[MediaItem]] = None
  96. photos: Optional[List[MediaItem]] = None
  97. quoted_tweet_id: Optional[str] = None
  98. quoted_tweet: Optional['FeedItem'] = None
  99. replied_tweet_id: Optional[str] = None
  100. replied_tweet: Optional['FeedItem'] = None
  101. note: Optional[str] = None
  102. debug_source_data: Optional[Dict] = None
  103. attachments: Optional[List[FeedItemAttachment]] = None
  104. # At some point we may move to feed_item_actions when the set is known
  105. actions: Optional[Dict[str, FeedItemAction]] = None
  106. # This is a TBD concept to highlight unreplied parts of a message.
  107. unreplied: Optional[List[UnrepliedSection]] = None
  108. # This is a TBD concept to highlight parts of a message that are a reply.
  109. replying_to: Optional[List[ReplyingToSection]] = None
  110. # tm = FeedItem(id="1", text="aa", created_at="fs", display_name="fda", handle="fdsafas")
  111. @dataclass
  112. class ThreadItem:
  113. feed_item: FeedItem
  114. children: Optional[List['ThreadItem']] = None
  115. parent: Optional['ThreadItem'] = None
  116. parents: Optional[List['ThreadItem']] = None
  117. actions: Optional[Dict[str, FeedItemAction]] = None
  118. @dataclass
  119. class FeedServiceUser:
  120. id: str
  121. url: str
  122. name: str # display_name
  123. username: str # handle
  124. is_verified: Optional[bool] = None
  125. is_protected: Optional[bool] = None
  126. created_at: Optional[str] = None
  127. description: Optional[str] = None
  128. preview_image_url: Optional[str] = None # deprecated... rename to avatar_image_url
  129. poster_image_url: Optional[str] = None
  130. website: Optional[str] = None
  131. location: Optional[str] = None
  132. actions: Optional[Dict[str, FeedItemAction]] = None
  133. source_url: Optional[str] = None
  134. raw_user: Optional = None
  135. @property
  136. def display_name (self) -> str:
  137. return self.name
  138. @property
  139. def handle (self) -> str:
  140. return self.username
  141. @property
  142. def avatar_image_url (self) -> str:
  143. return self.preview_image_url
  144. @dataclass
  145. class CollectionPage:
  146. """
  147. Feed is a collection.
  148. """
  149. id: str
  150. items: Optional[List[FeedServiceUser|FeedItem|ThreadItem]] = None
  151. next_token: Optional[str] = None
  152. last_dt: Optional[str] = None
  153. def cleandict(d):
  154. if isinstance(d, dict):
  155. return {k: cleandict(v) for k, v in d.items() if v is not None}
  156. elif isinstance(d, list):
  157. return [cleandict(v) for v in d]
  158. else:
  159. return d