PageRenderTime 39ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

/util.py

https://gitlab.com/Lett1/SlackDuckBot
Python | 151 lines | 98 code | 13 blank | 40 comment | 7 complexity | 3054a4db10854d9c42996fd1f8a91bd2 MD5 | raw file
  1. """This module holds utility functions used by some modules and the bot itself."""
  2. import config
  3. import requests_cache
  4. import logging
  5. import re
  6. from bs4 import BeautifulSoup
  7. from urllib.request import Request, urlopen
  8. from urllib.error import HTTPError, URLError
  9. import stopit
  10. import shlex
  11. logger = logging.getLogger(__name__)
  12. idregex = re.compile(r'\@([A-Z0-9]{9})', re.UNICODE)
  13. channelregex = re.compile(r'\#([A-Z0-9]{9})', re.UNICODE)
  14. urlregex = re.compile(r'\<(.*?)\>')
  15. def getUserdataForId(userId):
  16. """
  17. Get the json response from the slack api for the specified user id.
  18. API endpoint: https://api.slack.com/methods/users.info
  19. Arguments: userId, slack user id to fetch. Required.
  20. Returns: the json response for the id
  21. """
  22. logging.info("Fetching user data for id %s", userId)
  23. s = requests_cache.CachedSession(cache_name='namelookup_cache', backend='sqlite', expire_after=3600)
  24. payload = {"token": config.SLACK_TOKEN, "user": userId}
  25. req = s.get("https://slack.com/api/users.info", params=payload)
  26. logging.debug("User data for id %s was cached=%s", userId, req.from_cache)
  27. return req.json()
  28. def __replaceUserId(userId):
  29. """
  30. Get the username for the specified id.
  31. Will return the raw id if username could not be found.
  32. """
  33. logging.debug("found id %s", userId.group(1))
  34. data = getUserdataForId(userId.group(1))
  35. if not data["ok"]:
  36. logging.warning("%s for id %s", data["error"], userId.group(1))
  37. return userId.group(0)
  38. else:
  39. return "@" + data["user"]["name"]
  40. def getChanneldataForId(channelId):
  41. """
  42. Get the json response from the slack api for the specified channel id.
  43. API endpoint: https://api.slack.com/methods/channels.info
  44. Arguments: channelId, slack channel id to fetch. Required.
  45. Returns: the json response for the id
  46. """
  47. logging.info("Fetching channel data for id %s", channelId)
  48. s = requests_cache.CachedSession(cache_name='namelookup_cache', backend='sqlite', expire_after=3600)
  49. payload = {"token": config.SLACK_TOKEN, "channel": channelId}
  50. req = s.get("https://slack.com/api/channels.info", params=payload)
  51. logging.debug("Channel data for id %s was cached=%s", channelId, req.from_cache)
  52. return req.json()
  53. def __replaceChannelId(channelId):
  54. """
  55. Gets the channel name for the specified id.
  56. Will return the raw id if channel could not be found.
  57. """
  58. logging.debug("found id %s", channelId.group(1))
  59. data = getChanneldataForId(channelId.group(1))
  60. if not data["ok"]:
  61. logging.warning("%s for id %s", data["error"], channelId.group(1))
  62. return channelId.group(0)
  63. else:
  64. return "#" + data["channel"]["name"]
  65. def replaceIdsInMessage(message):
  66. """
  67. Replaces occurences of slack id's in messages with their human readable counterparts.
  68. """
  69. message = re.sub(channelregex, __replaceChannelId, message)
  70. return re.sub(idregex, __replaceUserId, message)
  71. @stopit.threading_timeoutable(default=None)
  72. def getTitle(url):
  73. """
  74. Fetch the <title> tag of the specified url.
  75. Returns a string if <title> could be found, else returns None.
  76. """
  77. try:
  78. headers = {'User-Agent': 'SlackBot %s V1.0' % config.BOT_NAME}
  79. req = Request(url, None, headers)
  80. response = urlopen(req)
  81. html = response.read()
  82. except HTTPError as e:
  83. print('The server couldn\'t fulfill the request.')
  84. print(('Error code: ', e.code))
  85. except URLError as e:
  86. print('We failed to reach a server.')
  87. print(('Reason: ', e.reason))
  88. except Exception as e:
  89. print(e)
  90. else:
  91. soup = BeautifulSoup(html, "lxml")
  92. if soup.title is not None:
  93. return soup.title.text.strip()
  94. return None
  95. def dict_of_key_value_pairs(arg):
  96. """ parse KEY=val,KEY2=val2 into {'KEY':'val', 'KEY2':'val2'}
  97. Quotes can be used to allow commas in the value
  98. This piece of code has been taken from:
  99. https://github.com/Supervisor/supervisor/blob/e4f3f0cad1697fdb76632dfe8a3935ae4540fbae/supervisor/datatypes.py#L80
  100. """
  101. lexer = shlex.shlex(arg)
  102. lexer.wordchars += '/.+-():'
  103. tokens = list(lexer)
  104. tokens_len = len(tokens)
  105. D = {}
  106. i = 0
  107. while i < tokens_len:
  108. k_eq_v = tokens[i:i+3]
  109. if len(k_eq_v) != 3 or k_eq_v[1] != '=':
  110. raise ValueError("Unexpected end of key/value pairs")
  111. D[k_eq_v[0]] = k_eq_v[2].strip('\'"')
  112. i += 4
  113. return D
  114. def replaceUrlTags(message):
  115. """Strip slacks url formatting out of the message"""
  116. matches = re.findall(urlregex, message)
  117. if matches:
  118. for match in matches:
  119. message = urlregex.sub(match.split("|")[0], message, count=1)
  120. return message