-
Notifications
You must be signed in to change notification settings - Fork 1
/
slack.py
155 lines (122 loc) · 5.12 KB
/
slack.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
from slackclient import SlackClient
from logger import EdwardLogger
import time
import requests
class SlackWrapper:
DEFAULT_SUPPORTED_FILE_TYPES = ["jpg", "jpeg", "png"]
def __init__(
self,
token=None,
on_file_shared_fn=None,
supported_file_types=DEFAULT_SUPPORTED_FILE_TYPES,
):
"""
SlackWrapper handles communication with the Slack RTM and Web APIs
Params:
`slack_token` : String,
`supported_file_types` a list of supported file types
`on_file_shared_fn` : Fn(string, string) -> None - handle files
"""
self.__token = token
self.__client = SlackClient(token)
self.__should_quit = False
self.__edward_id = None
self.__supported_file_types = supported_file_types
self.__on_file_shared_fn = on_file_shared_fn
def stop(self):
self.__should_quit = True
def start(self):
"""
Connect to Slacks real time messaging client and kick of the main loop
"""
# first things first; let's connect to Slack RTM
if self.__client.rtm_connect(auto_reconnect=True, with_team_state=False):
# once that succeeds, store our own user ID for filtering
self.__edward_id = self.__client.api_call("auth.test")["user_id"]
# kick off main loop; blocking
self.run_main_loop()
def run_main_loop(self):
"""
The main loop is responsible for reading messages off of the queue
and kicking off converting jobs should a message be applicable.
"""
EdwardLogger.info("Ready for action!")
# start looping as long as we're connected
while self.__client.server.connected:
# should we shutdown?
if self.__should_quit:
break
batch = self.__client.rtm_read()
job_params = self.extract_job_params(batch)
if job_params is not None:
# all is OK, spawn handler
download_url, response_channel_id = job_params
# perform the callback containing the job parameters
EdwardLogger.info("File attached and valid")
self.__on_file_shared_fn(download_url, response_channel_id)
# no matter if we scheduled a job or not; wait a bit
time.sleep(1)
def extract_job_params(self, messages):
"""
extract_job_params takes the original slack message and returns either
`None` or a tuple containing the `download_url` and `return_channel_id`:
* download_url is a string containing the download location
* return_channel_id is the Slack channel id on which to reply to
"""
# if there's no messages to be polled, we receive an empty list.
# we should just skip and try again
if len(messages) == 0:
return None
# the messages are returned in an array; grab the first one
# and start validating the payload
message = messages[0]
# if message does not contain required fields, ignore it
if not self.__is_message_valid(message):
return None
# if message author is edward itself, ignore to avoid endless looping
if message["user"] == self.__edward_id:
return None
# if it isn't a file_shared message, just ignore and continue
if not self.__is_file_attached(message):
return None
# if there is a file but it is not supported by Edward
file = message["files"][0]
pretty_type = file["pretty_type"].lower()
if pretty_type not in self.__supported_file_types:
EdwardLogger.info("Attached file not of valid type, skipping")
return None
# if we've reached this point, we encountered a valid message.
# extract the important bits and return a tuple containing job params
return (file["url_private_download"], message["channel"])
def download_image(self, image_download_url):
"""
download_file accepts a file_download_url, provides authorization
and kicks off the request to Slack Web API
"""
return requests.get(image_download_url, headers=self.__get_http_headers())
def upload_image(self, channel_id, bytes):
"""
upload_image uploads the sequence of bytes to the provided slack channel.
It returns either None if all went well, or the error if something blew up
"""
response = self.__client.api_call(
"files.upload", channels=channel_id, file=bytes
)
if response["ok"]:
return None
else:
return response["error"]
def __get_http_headers(self):
return {"Authorization": "Bearer {}".format(self.__token)}
def __is_message_valid(self, message):
return (
"type" in message
and message["type"] == "message"
and "user" in message
)
def __is_file_attached(self, message):
return (
message["type"] == "message"
and "files" in message
and len(message["files"]) > 0
)