view_model.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  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. @dataclass
  44. class FeedItemAttachment:
  45. name: str
  46. content_type: str
  47. url: Optional[str] = None
  48. content: Optional[object] = None
  49. size: Optional[int] = None
  50. @dataclass
  51. class UnrepliedSection:
  52. feed_item_id: Optional[str] = None
  53. description: Optional[str] = None
  54. text: Optional[str] = None
  55. span: Optional[Tuple[int, int]] = None
  56. def text_from (self, feed_item: 'FeedItem') -> str:
  57. if not self.span or not feed_item.text:
  58. return
  59. return feed_item.text[self.span[0]:self.span[1]]
  60. ReplyingToSection = UnrepliedSection
  61. @dataclass
  62. class FeedItem:
  63. id: str
  64. created_at: str
  65. display_name: str
  66. handle: str
  67. text: Optional[str] = None
  68. html: Optional[str] = None
  69. author_is_verified: Optional[bool] = None
  70. url: Optional[str] = None
  71. conversation_id: Optional[str] = None
  72. avi_icon_url: Optional[str] = None
  73. author_url: Optional[str] = None
  74. author_id: Optional[str] = None
  75. source_url: Optional[str] = None
  76. source_author_url: Optional[str] = None
  77. reply_depth: Optional[int] = 0
  78. is_marked: Optional[bool] = None
  79. card: Optional[Card] = None
  80. public_metrics: Optional[PublicMetrics] = None
  81. non_public_metrics: Optional[NonPublicMetrics] = None
  82. retweeted_tweet_id: Optional[str] = None
  83. source_retweeted_by_url: Optional[str] = None
  84. retweeted_by: Optional[str] = None
  85. retweeted_by_url: Optional[str] = None
  86. videos: Optional[List[MediaItem]] = None
  87. photos: Optional[List[MediaItem]] = None
  88. quoted_tweet_id: Optional[str] = None
  89. quoted_tweet: Optional['FeedItem'] = None
  90. replied_tweet_id: Optional[str] = None
  91. replied_tweet: Optional['FeedItem'] = None
  92. note: Optional[str] = None
  93. debug_source_data: Optional[Dict] = None
  94. attachments: Optional[List[FeedItemAttachment]] = None
  95. # At some point we may move to feed_item_actions when the set is known
  96. actions: Optional[Dict[str, FeedItemAction]] = None
  97. # This is a TBD concept to highlight unreplied parts of a message.
  98. unreplied: Optional[List[UnrepliedSection]] = None
  99. # This is a TBD concept to highlight parts of a message that are a reply.
  100. replying_to: Optional[List[ReplyingToSection]] = None
  101. # tm = FeedItem(id="1", text="aa", created_at="fs", display_name="fda", handle="fdsafas")
  102. @dataclass
  103. class ThreadItem:
  104. feed_item: FeedItem
  105. children: Optional[List['ThreadItem']] = None
  106. parent: Optional['ThreadItem'] = None
  107. parents: Optional[List['ThreadItem']] = None
  108. actions: Optional[Dict[str, FeedItemAction]] = None
  109. @dataclass
  110. class FeedServiceUser:
  111. id: str
  112. url: str
  113. name: str # display_name
  114. username: str # handle
  115. created_at: Optional[str] = None
  116. description: Optional[str] = None
  117. preview_image_url: Optional[str] = None
  118. website: Optional[str] = None
  119. actions: Optional[Dict[str, FeedItemAction]] = None
  120. source_url: Optional[str] = None
  121. @property
  122. def display_name (self) -> str:
  123. return self.name
  124. @property
  125. def handle (self) -> str:
  126. return self.username
  127. def cleandict(d):
  128. if isinstance(d, dict):
  129. return {k: cleandict(v) for k, v in d.items() if v is not None}
  130. elif isinstance(d, list):
  131. return [cleandict(v) for v in d]
  132. else:
  133. return d