message.py 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. from __future__ import unicode_literals
  2. import datetime
  3. import logging
  4. import emoji
  5. class Message(object):
  6. _DEFAULT_USER_ICON_SIZE = 72
  7. def __init__(self, formatter, message):
  8. self._formatter = formatter
  9. self._message = message
  10. ##############
  11. # Properties #
  12. ##############
  13. @property
  14. def user_id(self):
  15. if "user" in self._message:
  16. return self._message["user"]
  17. elif "bot_id" in self._message:
  18. return self._message["bot_id"]
  19. else:
  20. logging.error("No user ID on %s", self._message)
  21. @property
  22. def user(self):
  23. return self._formatter.find_user(self._message)
  24. @property
  25. def username(self):
  26. try:
  27. return self.user.display_name
  28. except KeyError:
  29. # In case this is a bot or something, we fallback to "username"
  30. if "username" in self._message:
  31. return self._message["username"]
  32. elif "user" in self._message:
  33. return self.user_id
  34. elif "bot_id" in self._message:
  35. return self._message["bot_id"]
  36. else:
  37. return None
  38. @property
  39. def time(self):
  40. # Handle this: "ts": "1456427378.000002"
  41. tsepoch = float(self._message["ts"].split(".")[0])
  42. return str(datetime.datetime.fromtimestamp(tsepoch)).split('.')[0]
  43. @property
  44. def attachments(self):
  45. return [ LinkAttachment("ATTACHMENT", entry, self._formatter)
  46. for entry in self._message.get("attachments", []) ]
  47. @property
  48. def files(self):
  49. if "file" in self._message: # this is probably an outdated case
  50. allfiles = [self._message["file"]]
  51. else:
  52. allfiles = self._message.get("files", [])
  53. return [ LinkAttachment("FILE", entry, self._formatter) for entry in allfiles ]
  54. @property
  55. def msg(self):
  56. text = self._message.get("text")
  57. if text:
  58. text = self._formatter.render_text(text)
  59. return text
  60. def user_message(self, user_id):
  61. return {"user": user_id}
  62. def usernames(self, reaction):
  63. return [
  64. self._formatter.find_user(self.user_message(user_id)).display_name
  65. for user_id
  66. in reaction.get("users")
  67. if self._formatter.find_user(self.user_message(user_id))
  68. ]
  69. @property
  70. def reactions(self):
  71. reactions = self._message.get("reactions", [])
  72. return [
  73. {
  74. "usernames": self.usernames(reaction),
  75. "name": emoji.emojize(
  76. self._formatter.slack_to_accepted_emoji(':{}:'.format(reaction.get("name"))),
  77. language='alias'
  78. )
  79. }
  80. for reaction in reactions
  81. ]
  82. @property
  83. def img(self):
  84. try:
  85. return self.user.image_url(self._DEFAULT_USER_ICON_SIZE)
  86. except KeyError:
  87. return ""
  88. @property
  89. def id(self):
  90. return self.time
  91. @property
  92. def subtype(self):
  93. return self._message.get("subtype")
  94. class LinkAttachment(object):
  95. """
  96. Wrapper class for entries in either the "files" or "attachments" arrays.
  97. """
  98. _DEFAULT_THUMBNAIL_SIZE = 360
  99. # Fields that need to be processed for markup (and possibly markdown)
  100. _TEXT_FIELDS = {"pretext", "text", "footer"}
  101. def __init__(self, attachment_type, raw, formatter):
  102. self._type = attachment_type
  103. self._raw = raw
  104. self._formatter = formatter
  105. def __getitem__(self, key):
  106. content = self._raw[key]
  107. if content and key in self._TEXT_FIELDS:
  108. process_markdown = (key in self._raw.get("mrkdwn_in", []))
  109. content = self._formatter.render_text(content, process_markdown)
  110. return content
  111. def thumbnail(self, size=None):
  112. size = size if size else self._DEFAULT_THUMBNAIL_SIZE
  113. # ATTACHMENT type
  114. if "image_url" in self._raw:
  115. logging.debug("image_url path")
  116. return {
  117. "src": self._raw["image_url"],
  118. "width": self._raw.get("image_width"),
  119. "height": self._raw.get("image_height"),
  120. }
  121. else: # FILE type
  122. thumb_key = "thumb_{}".format(size)
  123. logging.debug("thumb path" + thumb_key)
  124. if thumb_key not in self._raw:
  125. # let's try some fallback logic
  126. thumb_key = "thumb_{}".format(self._raw.get("filetype"))
  127. if thumb_key not in self._raw:
  128. # pick the first one that shows up in the iterator
  129. candidates = [k for k in self._raw.keys()
  130. if k.startswith("thumb_") and not k.endswith(("_w","_h"))]
  131. if candidates:
  132. thumb_key = candidates[0]
  133. logging.info("Fell back to thumbnail key %s for [%s]",
  134. thumb_key, self._raw.get("title"))
  135. if thumb_key in self._raw:
  136. return {
  137. "src": self._raw[thumb_key],
  138. "width": self._raw.get(thumb_key + "_w"),
  139. "height": self._raw.get(thumb_key + "_h"),
  140. }
  141. else:
  142. logging.info("No thumbnail found for [%s]", self._raw.get("title"))
  143. @property
  144. def is_image(self):
  145. return self._raw.get("mimetype", "").startswith("image/")
  146. @property
  147. def link(self):
  148. if "from_url" in self._raw:
  149. return self._raw["from_url"]
  150. else:
  151. return self._raw.get("url_private")
  152. @property
  153. def fields(self):
  154. """
  155. Fetch the "fields" list, and process the text within each field, including markdown
  156. processing if the message indicates that the fields contain markdown.
  157. Only present on attachments, not files--this abstraction isn't 100% awesome.'
  158. """
  159. process_markdown = ("fields" in self._raw.get("mrkdwn_in", []))
  160. fields = self._raw.get("fields", [])
  161. if fields:
  162. logging.debug("Rendering with markdown markdown %s for %s", process_markdown, fields)
  163. return [
  164. {"title": e["title"], "short": e.get("short", False), "value": self._formatter.render_text(e["value"], process_markdown)}
  165. for e in fields
  166. ]