view_model.py 4.8 KB

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