add support for setting and unsetting Custom Status/Emoji
This change introduces two new methods on *Client for setting and unsetting the
Custom Status and Status Emoji for the currently authenticated User. This also
adds `StatusText` and `StatusEmoji` fields to the `UserProfile` struct. The
Custom Status and Status Emoji functionality was recently added as part of the
User's profile[1].
The first method added is `SetUserCustomStatus()` which takes two string
arguments. This method uses an anonymous struct to generate the JSON required to
update a User's Custom Status and Status Emoji. If both arguments are set to
empty-string (`""`) the Slack API will unset the Custom Status/Emoji.
The second method is `UnsetUserCustomStatus()`, which is a convenience method
that takes zero arguments and wraps `SetUserCustomStatus()`. It unsets the
custom status.
Integration tests were added to ensure that that setting and unsetting the
Custom Status should work as-expected.
Closes #153.
[1] https://slackhq.com/set-your-status-in-slack-28a793914b98
Signed-off-by: Tim Heckman <t@heckman.io>
Tim Heckman
7 years ago
0 | 0 | package slack |
1 | 1 | |
2 | 2 | import ( |
3 | "encoding/json" | |
3 | 4 | "errors" |
4 | 5 | "net/url" |
5 | 6 | ) |
28 | 29 | Title string `json:"title"` |
29 | 30 | BotID string `json:"bot_id,omitempty"` |
30 | 31 | ApiAppID string `json:"api_app_id,omitempty"` |
32 | StatusText string `json:"status_text,omitempty"` | |
33 | StatusEmoji string `json:"status_emoji,omitempty"` | |
31 | 34 | } |
32 | 35 | |
33 | 36 | // User contains all the information of a user |
249 | 252 | } |
250 | 253 | return nil |
251 | 254 | } |
255 | ||
256 | // SetUserCustomStatus will set a custom status and emoji for the currently | |
257 | // authenticated user. If statusEmoji is "" and statusText is not, the Slack API | |
258 | // will automatically set it to ":speech_balloon:". Otherwise, if both are "" | |
259 | // the Slack API will unset the custom status/emoji. | |
260 | func (api *Client) SetUserCustomStatus(statusText, statusEmoji string) error { | |
261 | // XXX(theckman): this anonymous struct is for making requests to the Slack | |
262 | // API for setting and unsetting a User's Custom Status/Emoji. To change | |
263 | // these values we must provide a JSON document as the profile POST field. | |
264 | // | |
265 | // We use an anonymous struct over UserProfile because to unset the values | |
266 | // on the User's profile we cannot use the `json:"omitempty"` tag. This is | |
267 | // because an empty string ("") is what's used to unset the values. Check | |
268 | // out the API docs for more details: | |
269 | // | |
270 | // - https://api.slack.com/docs/presence-and-status#custom_status | |
271 | profile, err := json.Marshal( | |
272 | &struct { | |
273 | StatusText string `json:"status_text"` | |
274 | StatusEmoji string `json:"status_emoji"` | |
275 | }{ | |
276 | StatusText: statusText, | |
277 | StatusEmoji: statusEmoji, | |
278 | }, | |
279 | ) | |
280 | ||
281 | if err != nil { | |
282 | return err | |
283 | } | |
284 | ||
285 | values := url.Values{ | |
286 | "token": {api.config.token}, | |
287 | "profile": {string(profile)}, | |
288 | } | |
289 | ||
290 | response := &userResponseFull{} | |
291 | ||
292 | if err = post("users.profile.set", values, response, api.debug); err != nil { | |
293 | return err | |
294 | } | |
295 | ||
296 | if !response.Ok { | |
297 | return errors.New(response.Error) | |
298 | } | |
299 | ||
300 | return nil | |
301 | } | |
302 | ||
303 | // UnsetUserCustomStatus removes the custom status message for the currently | |
304 | // authenticated user. This is a convenience method that wraps | |
305 | // (*Client).SetUserCustomStatus(). | |
306 | func (api *Client) UnsetUserCustomStatus() error { | |
307 | return api.SetUserCustomStatus("", "") | |
308 | } |
0 | 0 | package slack |
1 | 1 | |
2 | 2 | import ( |
3 | "encoding/json" | |
4 | "fmt" | |
3 | 5 | "net/http" |
4 | 6 | "testing" |
5 | 7 | ) |
34 | 36 | } |
35 | 37 | }`) |
36 | 38 | rw.Write(response) |
39 | } | |
40 | ||
41 | func httpTestErrReply(w http.ResponseWriter, clientErr bool, msg string) { | |
42 | if clientErr { | |
43 | w.WriteHeader(http.StatusBadRequest) | |
44 | } else { | |
45 | w.WriteHeader(http.StatusInternalServerError) | |
46 | } | |
47 | ||
48 | w.Header().Set("Content-Type", "application/json") | |
49 | ||
50 | body, _ := json.Marshal(&SlackResponse{ | |
51 | Ok: false, Error: msg, | |
52 | }) | |
53 | ||
54 | w.Write(body) | |
55 | } | |
56 | ||
57 | func newProfileHandler(up *UserProfile) (setter func(http.ResponseWriter, *http.Request)) { | |
58 | return func(w http.ResponseWriter, r *http.Request) { | |
59 | if up == nil { | |
60 | httpTestErrReply(w, false, "err: UserProfile is nil") | |
61 | return | |
62 | } | |
63 | ||
64 | if err := r.ParseForm(); err != nil { | |
65 | httpTestErrReply(w, true, fmt.Sprintf("err parsing form: %s", err.Error())) | |
66 | return | |
67 | } | |
68 | ||
69 | values := r.Form | |
70 | ||
71 | if len(values["profile"]) == 0 { | |
72 | httpTestErrReply(w, true, `POST data must include a "profile" field`) | |
73 | return | |
74 | } | |
75 | ||
76 | profile := []byte(values["profile"][0]) | |
77 | ||
78 | userProfile := UserProfile{} | |
79 | ||
80 | if err := json.Unmarshal(profile, &userProfile); err != nil { | |
81 | httpTestErrReply(w, true, fmt.Sprintf("err parsing JSON: %s\n\njson: `%s`", err.Error(), profile)) | |
82 | return | |
83 | } | |
84 | ||
85 | *up = userProfile | |
86 | ||
87 | // TODO(theckman): enhance this to return a full User object | |
88 | fmt.Fprint(w, `{"ok":true}`) | |
89 | } | |
37 | 90 | } |
38 | 91 | |
39 | 92 | func TestGetUserIdentity(t *testing.T) { |
75 | 128 | t.Fatal(ErrIncorrectResponse) |
76 | 129 | } |
77 | 130 | } |
131 | ||
132 | func TestUserCustomStatus(t *testing.T) { | |
133 | up := &UserProfile{} | |
134 | ||
135 | setUserProfile := newProfileHandler(up) | |
136 | ||
137 | http.HandleFunc("/users.profile.set", setUserProfile) | |
138 | ||
139 | once.Do(startServer) | |
140 | SLACK_API = "http://" + serverAddr + "/" | |
141 | api := New("testing-token") | |
142 | ||
143 | testSetUserCustomStatus(api, up, t) | |
144 | testUnsetUserCustomStatus(api, up, t) | |
145 | } | |
146 | ||
147 | func testSetUserCustomStatus(api *Client, up *UserProfile, t *testing.T) { | |
148 | const ( | |
149 | statusText = "testStatus" | |
150 | statusEmoji = ":construction:" | |
151 | ) | |
152 | ||
153 | if err := api.SetUserCustomStatus(statusText, statusEmoji); err != nil { | |
154 | t.Fatalf(`SetUserCustomStatus(%q, %q) = %#v, want <nil>`, statusText, statusEmoji, err) | |
155 | } | |
156 | ||
157 | if up.StatusText != statusText { | |
158 | t.Fatalf(`UserProfile.StatusText = %q, want %q`, up.StatusText, statusText) | |
159 | } | |
160 | ||
161 | if up.StatusEmoji != statusEmoji { | |
162 | t.Fatalf(`UserProfile.StatusEmoji = %q, want %q`, up.StatusEmoji, statusEmoji) | |
163 | } | |
164 | } | |
165 | ||
166 | func testUnsetUserCustomStatus(api *Client, up *UserProfile, t *testing.T) { | |
167 | if err := api.UnsetUserCustomStatus(); err != nil { | |
168 | t.Fatalf(`UnsetUserCustomStatus() = %#v, want <nil>`, err) | |
169 | } | |
170 | ||
171 | if up.StatusText != "" { | |
172 | t.Fatalf(`UserProfile.StatusText = %q, want %q`, up.StatusText, "") | |
173 | } | |
174 | ||
175 | if up.StatusEmoji != "" { | |
176 | t.Fatalf(`UserProfile.StatusEmoji = %q, want %q`, up.StatusEmoji, "") | |
177 | } | |
178 | } |