-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathclient.rkt
207 lines (178 loc) · 8.35 KB
/
client.rkt
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
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
#lang racket
(require racket/class racket/include)
(include "./server.rkt")
(include "./util.rkt")
(provide SlackClient%)
(define SlackClient%
(class object%
#|
The SlackClient makes API Calls to the `Slack Web API
<https://api.slack.com/web>`_ as well as managing connections to the
`Real-time Messaging API via websocket <https://api.slack.com/rtm>`_
It also manages some of the Client state for Channels that the associated
token (User or Bot) is associated with.
For more information, check out the `Slack API Docs
<https://api.slack.com/>`_
Init:
:Args:
token (str): Your Slack Authentication token. You can find or
generate a test token `here
<https://api.slack.com/docs/oauth-test-tokens>`_
Note: Be `careful with your token
<https://api.slack.com/docs/oauth-safety>`_
proxies (dict): Proxies to use when create websocket or api
calls, declare http and websocket proxies using
{'http': 'http://127.0.0.1'}, and https proxy
using {'https': 'https://127.0.0.1:443'}
|#
(super-new)
(init-field clientToken [clientProxies #f])
(define token clientToken)
(define server (new Server%
[serverToken token]
[serverConnect? #f]
[serverProxies clientProxies]))
(define/public (append-user-agent name versn)
(send server append-user-agent name versn))
(define/public (rtm-connect #:withTeamStat [withTeamStat #t]
#:postData [**kwargs (hash)])
#|
Connects to the RTM Websocket
:Args:
with_team_state (bool): Connect via `rtm.start` to pull
workspace state information.
`False` connects via `rtm.connect`,
which is lighter weight and better
for very large teams.
:Returns:
False on exceptions
|#
(with-handlers ([exn:fail? (lambda (e) #f)])
(send server rtm-connect #:useRtmStart withTeamStat #:postData **kwargs)
#t))
(define/public (api-call method [timeout #f] #:postData [**kwargs (hash)])
#|
Call the Slack Web API as documented here: https://api.slack.com/web
:Args:
method (str): The API Method to call. See `the full list
here <https://api.slack.com/methods>`_
:Kwargs:
(optional) kwargs: any arguments passed here will be bundled and sent
to the api requester as post_data and will be
passed along to the API.
Example::
sc.server.api_call("channels.setPurpose",
channel="CABC12345",
purpose="Writing some code!")
:Returns:
str -- returns the text of the HTTP response.
Examples::
u'{"ok":true,"purpose":"Testing bots"}'
or
u'{"ok":false,"error":"channel_not_found"}'
See here for more information on responses: https://api.slack.com/web
|#
(define rBody (send server api-call method timeout #:postData **kwargs))
(let ([okInBody (hash-ref rBody 'ok #f)])
(cond
[(string=? method "im.open")
(when okInBody
(send server attach-channel
(hash-ref **kwargs 'user)
(hash-ref (hash-ref rBody 'channel) 'id)))]
[(member method '("mpim.open" "groups.create" "groups.createchild"))
(when okInBody
(send server attach-channel
(hash-ref (hash-ref rBody 'group) 'name)
(hash-ref (hash-ref rBody 'group) 'id)
(hash-ref (hash-ref rBody 'group) 'members)))]
[(member method '("channels.create" "channels.join"))
(when okInBody
(send server attach-channel
(hash-ref (hash-ref rBody 'channel) 'name)
(hash-ref (hash-ref rBody 'channel) 'id)
(hash-ref
(hash-ref rBody 'channel)
'members)))]))
rBody)
(define/public (rtm-read)
#|
Reads from the RTM Websocket stream then calls
`self.process_changes(item)` for each line in the returned data.
Multiple events may be returned, always returns a list
[], which is empty if there are no incoming messages.
:Args:
None
:Returns:
data (json) - The server response. For example::
[{u'presence': u'active',
u'type': u'presence_change',
u'user': u'UABC1234'}]
:Raises:
SlackNotConnected if self.server is not defined.
|#
;; in the future, this should handle some events internally
;; i.e. channel creation
(map
(lambda (line)
(let ([lineHash (string->jsexpr line)])
(send this process-changes lineHash)
lineHash))
(string-split (send server websocket-safe-read) "\n")))
(define/public (rtm-send-message channel message
[aThread #f] [replyBroadcast #f])
#|
Sends a message to a given channel.
:Args:
channel (str) - the string identifier for a channel or
channel name (e.g. 'C1234ABC',
'bot-test', or
'#bot-test')
message (message) - the string you'd like to
send to the channel
thread (str or None) - the parent message ID, if
sending to a thread
replyBroadcast (boolean) - if messaging a thread, whether to also
send the message back to the
channel
:Returns:
None
|#
;; The `channel` argument can be a channel name or an ID. At first its
;; assumed to be a name and an attempt is made to find the ID in the
;; workspace state cache. If that lookup fails, the argument is used as
;; the channel ID.
(send server rtm-send-message
(let ([foundChannels (find-channel
channel
(send server get-channels))])
(if (= (length foundChannels) 1)
(send (car foundChannels) get-id)
channel))
message
aThread
replyBroadcast))
(define/public (process-changes data)
#|
Internal method which processes RTM events and
modifies the local data store accordingly.
Stores new channels when joining a group
(Multi-party DM), IM (DM) or channel.
Stores user data on a team join event.
|#
(when (member 'type (hash-keys data))
(let ([type (hash-ref data 'type)])
(when (member type '("channel_created" "group_joined"))
(let ([channel (hash-ref data 'channel)])
(send server attach-channel
(hash-ref channel 'name)
(hash-ref channel 'id)
'())))
(when (string=? type "im_created")
(let ([channel (hash-ref data 'channel)])
(send server attach-channel
(hash-ref channel 'user)
(hash-ref channel 'id)
'())))
(when (string=? type "team_join")
(send server parse-user-data (list (hash-ref data 'user)))))))))