:art: Hygiene, Mostly Related To ID
Joe Fitzgerald
8 years ago
28 | 28 | return |
29 | 29 | } |
30 | 30 | for _, group := range groups { |
31 | fmt.Printf("Id: %s, Name: %s\n", group.Id, group.Name) | |
31 | fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name) | |
32 | 32 | } |
33 | 33 | } |
34 | 34 | |
47 | 47 | fmt.Printf("%s\n", err) |
48 | 48 | return |
49 | 49 | } |
50 | fmt.Printf("Id: %s, Fullname: %s, Email: %s\n", user.Id, user.Profile.RealName, user.Profile.Email) | |
50 | fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email) | |
51 | 51 | } |
52 | 52 | |
53 | 53 | ## Why? |
60 | 60 | Anyone is welcome to contribute. Either open a PR or create an issue. |
61 | 61 | |
62 | 62 | ## License |
63 | BSD 2 Clause license⏎ | |
63 | BSD 2 Clause license |
24 | 24 | return adminResponse, nil |
25 | 25 | } |
26 | 26 | |
27 | // InviteGuest invites a user to Slack as a single-channel guest | |
27 | 28 | func (api *Slack) InviteGuest( |
28 | 29 | teamName string, |
29 | channelID string, | |
30 | channel string, | |
30 | 31 | firstName string, |
31 | 32 | lastName string, |
32 | 33 | emailAddress string, |
33 | 34 | ) error { |
34 | 35 | values := url.Values{ |
35 | 36 | "email": {emailAddress}, |
36 | "channels": {channelID}, | |
37 | "channels": {channel}, | |
37 | 38 | "first_name": {firstName}, |
38 | 39 | "last_name": {lastName}, |
39 | 40 | "ultra_restricted": {"1"}, |
50 | 51 | return nil |
51 | 52 | } |
52 | 53 | |
54 | // InviteRestricted invites a user to Slack as a restricted account | |
53 | 55 | func (api *Slack) InviteRestricted( |
54 | 56 | teamName string, |
55 | channelID string, | |
57 | channel string, | |
56 | 58 | firstName string, |
57 | 59 | lastName string, |
58 | 60 | emailAddress string, |
59 | 61 | ) error { |
60 | 62 | values := url.Values{ |
61 | 63 | "email": {emailAddress}, |
62 | "channels": {channelID}, | |
64 | "channels": {channel}, | |
63 | 65 | "first_name": {firstName}, |
64 | 66 | "last_name": {lastName}, |
65 | 67 | "restricted": {"1"}, |
0 | package slack | |
1 | ||
2 | // AttachmentField contains information for an attachment field | |
3 | // An Attachment can contain multiple of these | |
4 | type AttachmentField struct { | |
5 | Title string `json:"title"` | |
6 | Value string `json:"value"` | |
7 | Short bool `json:"short"` | |
8 | } | |
9 | ||
10 | // Attachment contains all the information for an attachment | |
11 | type Attachment struct { | |
12 | Color string `json:"color,omitempty"` | |
13 | Fallback string `json:"fallback"` | |
14 | ||
15 | AuthorName string `json:"author_name,omitempty"` | |
16 | AuthorSubname string `json:"author_subname,omitempty"` | |
17 | AuthorLink string `json:"author_link,omitempty"` | |
18 | AuthorIcon string `json:"author_icon,omitempty"` | |
19 | ||
20 | Title string `json:"title,omitempty"` | |
21 | TitleLink string `json:"title_link,omitempty"` | |
22 | Pretext string `json:"pretext,omitempty"` | |
23 | Text string `json:"text"` | |
24 | ||
25 | ImageURL string `json:"image_url,omitempty"` | |
26 | ThumbURL string `json:"thumb_url,omitempty"` | |
27 | ||
28 | Fields []AttachmentField `json:"fields,omitempty"` | |
29 | MarkdownIn []string `json:"mrkdwn_in,omitempty"` | |
30 | } |
29 | 29 | LastSet JSONTime `json:"last_set"` |
30 | 30 | } |
31 | 31 | |
32 | type BaseChannel struct { | |
33 | Id string `json:"id"` | |
34 | Created JSONTime `json:"created"` | |
35 | IsOpen bool `json:"is_open"` | |
36 | LastRead string `json:"last_read,omitempty"` | |
37 | Latest Message `json:"latest,omitempty"` | |
38 | UnreadCount int `json:"unread_count,omitempty"` | |
39 | UnreadCountDisplay int `json:"unread_count_display,omitempty"` | |
32 | type baseChannel struct { | |
33 | ID string `json:"id"` | |
34 | Created JSONTime `json:"created"` | |
35 | IsOpen bool `json:"is_open"` | |
36 | LastRead string `json:"last_read,omitempty"` | |
37 | Latest Message `json:"latest,omitempty"` | |
38 | UnreadCount int `json:"unread_count,omitempty"` | |
39 | UnreadCountDisplay int `json:"unread_count_display,omitempty"` | |
40 | 40 | } |
41 | 41 | |
42 | 42 | // Channel contains information about the channel |
43 | 43 | type Channel struct { |
44 | BaseChannel | |
45 | Name string `json:"name"` | |
46 | IsChannel bool `json:"is_channel"` | |
47 | Creator string `json:"creator"` | |
48 | IsArchived bool `json:"is_archived"` | |
49 | IsGeneral bool `json:"is_general"` | |
50 | Members []string `json:"members"` | |
51 | Topic ChannelTopic `json:"topic"` | |
52 | Purpose ChannelPurpose `json:"purpose"` | |
53 | IsMember bool `json:"is_member"` | |
54 | LastRead string `json:"last_read,omitempty"` | |
55 | Latest *Message `json:"latest,omitempty"` | |
56 | UnreadCount int `json:"unread_count,omitempty"` | |
57 | NumMembers int `json:"num_members,omitempty"` | |
44 | baseChannel | |
45 | Name string `json:"name"` | |
46 | IsChannel bool `json:"is_channel"` | |
47 | Creator string `json:"creator"` | |
48 | IsArchived bool `json:"is_archived"` | |
49 | IsGeneral bool `json:"is_general"` | |
50 | Members []string `json:"members"` | |
51 | Topic ChannelTopic `json:"topic"` | |
52 | Purpose ChannelPurpose `json:"purpose"` | |
53 | IsMember bool `json:"is_member"` | |
54 | LastRead string `json:"last_read,omitempty"` | |
55 | Latest *Message `json:"latest,omitempty"` | |
56 | UnreadCount int `json:"unread_count,omitempty"` | |
57 | NumMembers int `json:"num_members,omitempty"` | |
58 | 58 | } |
59 | 59 | |
60 | 60 | func channelRequest(path string, values url.Values, debug bool) (*channelResponseFull, error) { |
70 | 70 | } |
71 | 71 | |
72 | 72 | // ArchiveChannel archives the given channel |
73 | func (api *Slack) ArchiveChannel(channelId string) error { | |
74 | values := url.Values{ | |
75 | "token": {api.config.token}, | |
76 | "channel": {channelId}, | |
73 | func (api *Slack) ArchiveChannel(channel string) error { | |
74 | values := url.Values{ | |
75 | "token": {api.config.token}, | |
76 | "channel": {channel}, | |
77 | 77 | } |
78 | 78 | _, err := channelRequest("channels.archive", values, api.debug) |
79 | 79 | if err != nil { |
83 | 83 | } |
84 | 84 | |
85 | 85 | // UnarchiveChannel unarchives the given channel |
86 | func (api *Slack) UnarchiveChannel(channelId string) error { | |
87 | values := url.Values{ | |
88 | "token": {api.config.token}, | |
89 | "channel": {channelId}, | |
86 | func (api *Slack) UnarchiveChannel(channel string) error { | |
87 | values := url.Values{ | |
88 | "token": {api.config.token}, | |
89 | "channel": {channel}, | |
90 | 90 | } |
91 | 91 | _, err := channelRequest("channels.unarchive", values, api.debug) |
92 | 92 | if err != nil { |
109 | 109 | } |
110 | 110 | |
111 | 111 | // GetChannelHistory retrieves the channel history |
112 | func (api *Slack) GetChannelHistory(channelId string, params HistoryParameters) (*History, error) { | |
113 | values := url.Values{ | |
114 | "token": {api.config.token}, | |
115 | "channel": {channelId}, | |
112 | func (api *Slack) GetChannelHistory(channel string, params HistoryParameters) (*History, error) { | |
113 | values := url.Values{ | |
114 | "token": {api.config.token}, | |
115 | "channel": {channel}, | |
116 | 116 | } |
117 | 117 | if params.Latest != DEFAULT_HISTORY_LATEST { |
118 | 118 | values.Add("latest", params.Latest) |
138 | 138 | } |
139 | 139 | |
140 | 140 | // GetChannelInfo retrieves the given channel |
141 | func (api *Slack) GetChannelInfo(channelId string) (*Channel, error) { | |
142 | values := url.Values{ | |
143 | "token": {api.config.token}, | |
144 | "channel": {channelId}, | |
141 | func (api *Slack) GetChannelInfo(channel string) (*Channel, error) { | |
142 | values := url.Values{ | |
143 | "token": {api.config.token}, | |
144 | "channel": {channel}, | |
145 | 145 | } |
146 | 146 | response, err := channelRequest("channels.info", values, api.debug) |
147 | 147 | if err != nil { |
151 | 151 | } |
152 | 152 | |
153 | 153 | // InviteUserToChannel invites a user to a given channel and returns a *Channel |
154 | func (api *Slack) InviteUserToChannel(channelId, userId string) (*Channel, error) { | |
155 | values := url.Values{ | |
156 | "token": {api.config.token}, | |
157 | "channel": {channelId}, | |
158 | "user": {userId}, | |
154 | func (api *Slack) InviteUserToChannel(channel, user string) (*Channel, error) { | |
155 | values := url.Values{ | |
156 | "token": {api.config.token}, | |
157 | "channel": {channel}, | |
158 | "user": {user}, | |
159 | 159 | } |
160 | 160 | response, err := channelRequest("channels.invite", values, api.debug) |
161 | 161 | if err != nil { |
178 | 178 | } |
179 | 179 | |
180 | 180 | // LeaveChannel makes the authenticated user leave the given channel |
181 | func (api *Slack) LeaveChannel(channelId string) (bool, error) { | |
182 | values := url.Values{ | |
183 | "token": {api.config.token}, | |
184 | "channel": {channelId}, | |
181 | func (api *Slack) LeaveChannel(channel string) (bool, error) { | |
182 | values := url.Values{ | |
183 | "token": {api.config.token}, | |
184 | "channel": {channel}, | |
185 | 185 | } |
186 | 186 | response, err := channelRequest("channels.leave", values, api.debug) |
187 | 187 | if err != nil { |
194 | 194 | } |
195 | 195 | |
196 | 196 | // KickUserFromChannel kicks a user from a given channel |
197 | func (api *Slack) KickUserFromChannel(channelId, userId string) error { | |
198 | values := url.Values{ | |
199 | "token": {api.config.token}, | |
200 | "channel": {channelId}, | |
201 | "user": {userId}, | |
197 | func (api *Slack) KickUserFromChannel(channel, user string) error { | |
198 | values := url.Values{ | |
199 | "token": {api.config.token}, | |
200 | "channel": {channel}, | |
201 | "user": {user}, | |
202 | 202 | } |
203 | 203 | _, err := channelRequest("channels.kick", values, api.debug) |
204 | 204 | if err != nil { |
227 | 227 | // timer before making the call. In this way, any further updates needed during the timeout will not generate extra calls |
228 | 228 | // (just one per channel). This is useful for when reading scroll-back history, or following a busy live channel. A |
229 | 229 | // timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. |
230 | func (api *Slack) SetChannelReadMark(channelId, ts string) error { | |
231 | values := url.Values{ | |
232 | "token": {api.config.token}, | |
233 | "channel": {channelId}, | |
230 | func (api *Slack) SetChannelReadMark(channel, ts string) error { | |
231 | values := url.Values{ | |
232 | "token": {api.config.token}, | |
233 | "channel": {channel}, | |
234 | 234 | "ts": {ts}, |
235 | 235 | } |
236 | 236 | _, err := channelRequest("channels.mark", values, api.debug) |
241 | 241 | } |
242 | 242 | |
243 | 243 | // RenameChannel renames a given channel |
244 | func (api *Slack) RenameChannel(channelId, name string) (*Channel, error) { | |
245 | values := url.Values{ | |
246 | "token": {api.config.token}, | |
247 | "channel": {channelId}, | |
244 | func (api *Slack) RenameChannel(channel, name string) (*Channel, error) { | |
245 | values := url.Values{ | |
246 | "token": {api.config.token}, | |
247 | "channel": {channel}, | |
248 | 248 | "name": {name}, |
249 | 249 | } |
250 | 250 | // XXX: the created entry in this call returns a string instead of a number |
259 | 259 | |
260 | 260 | // SetChannelPurpose sets the channel purpose and returns the purpose that was |
261 | 261 | // successfully set |
262 | func (api *Slack) SetChannelPurpose(channelId, purpose string) (string, error) { | |
263 | values := url.Values{ | |
264 | "token": {api.config.token}, | |
265 | "channel": {channelId}, | |
262 | func (api *Slack) SetChannelPurpose(channel, purpose string) (string, error) { | |
263 | values := url.Values{ | |
264 | "token": {api.config.token}, | |
265 | "channel": {channel}, | |
266 | 266 | "purpose": {purpose}, |
267 | 267 | } |
268 | 268 | response, err := channelRequest("channels.setPurpose", values, api.debug) |
273 | 273 | } |
274 | 274 | |
275 | 275 | // SetChannelTopic sets the channel topic and returns the topic that was successfully set |
276 | func (api *Slack) SetChannelTopic(channelId, topic string) (string, error) { | |
277 | values := url.Values{ | |
278 | "token": {api.config.token}, | |
279 | "channel": {channelId}, | |
276 | func (api *Slack) SetChannelTopic(channel, topic string) (string, error) { | |
277 | values := url.Values{ | |
278 | "token": {api.config.token}, | |
279 | "channel": {channel}, | |
280 | 280 | "topic": {topic}, |
281 | 281 | } |
282 | 282 | response, err := channelRequest("channels.setTopic", values, api.debug) |
20 | 20 | ) |
21 | 21 | |
22 | 22 | type chatResponseFull struct { |
23 | ChannelId string `json:"channel"` | |
23 | Channel string `json:"channel"` | |
24 | 24 | Timestamp string `json:"ts"` |
25 | 25 | Text string `json:"text"` |
26 | 26 | SlackResponse |
27 | } | |
28 | ||
29 | // AttachmentField contains information for an attachment field | |
30 | // An Attachment can contain multiple of these | |
31 | type AttachmentField struct { | |
32 | Title string `json:"title"` | |
33 | Value string `json:"value"` | |
34 | Short bool `json:"short"` | |
35 | } | |
36 | ||
37 | // Attachment contains all the information for an attachment | |
38 | type Attachment struct { | |
39 | Fallback string `json:"fallback"` | |
40 | ||
41 | Color string `json:"color,omitempty"` | |
42 | ||
43 | Pretext string `json:"pretext,omitempty"` | |
44 | ||
45 | AuthorName string `json:"author_name,omitempty"` | |
46 | AuthorLink string `json:"author_link,omitempty"` | |
47 | AuthorIcon string `json:"author_icon,omitempty"` | |
48 | ||
49 | Title string `json:"title,omitempty"` | |
50 | TitleLink string `json:"title_link,omitempty"` | |
51 | ||
52 | Text string `json:"text"` | |
53 | ||
54 | ImageURL string `json:"image_url,omitempty"` | |
55 | ThumbURL string `json:"thumb_url,omitempty"` | |
56 | ||
57 | Fields []AttachmentField `json:"fields,omitempty"` | |
58 | ||
59 | MarkdownIn []string `json:"mrkdwn_in,omitempty"` | |
60 | 27 | } |
61 | 28 | |
62 | 29 | // PostMessageParameters contains all the parameters necessary (including the optional ones) for a PostMessage() request |
105 | 72 | } |
106 | 73 | |
107 | 74 | // DeleteMessage deletes a message in a channel |
108 | func (api *Slack) DeleteMessage(channelId, messageTimestamp string) (string, string, error) { | |
75 | func (api *Slack) DeleteMessage(channel, messageTimestamp string) (string, string, error) { | |
109 | 76 | values := url.Values{ |
110 | 77 | "token": {api.config.token}, |
111 | "channel": {channelId}, | |
78 | "channel": {channel}, | |
112 | 79 | "ts": {messageTimestamp}, |
113 | 80 | } |
114 | 81 | response, err := chatRequest("chat.delete", values, api.debug) |
115 | 82 | if err != nil { |
116 | 83 | return "", "", err |
117 | 84 | } |
118 | return response.ChannelId, response.Timestamp, nil | |
85 | return response.Channel, response.Timestamp, nil | |
119 | 86 | } |
120 | 87 | |
121 | 88 | func escapeMessage(message string) string { |
130 | 97 | |
131 | 98 | // PostMessage sends a message to a channel |
132 | 99 | // Message is escaped by default according to https://api.slack.com/docs/formatting |
133 | func (api *Slack) PostMessage(channelId string, text string, params PostMessageParameters) (channel string, timestamp string, err error) { | |
100 | func (api *Slack) PostMessage(channel string, text string, params PostMessageParameters) (string, string, error) { | |
134 | 101 | if params.EscapeText { |
135 | 102 | text = escapeMessage(text) |
136 | 103 | } |
137 | 104 | values := url.Values{ |
138 | 105 | "token": {api.config.token}, |
139 | "channel": {channelId}, | |
106 | "channel": {channel}, | |
140 | 107 | "text": {text}, |
141 | 108 | } |
142 | 109 | if params.Username != DEFAULT_MESSAGE_USERNAME { |
178 | 145 | if err != nil { |
179 | 146 | return "", "", err |
180 | 147 | } |
181 | return response.ChannelId, response.Timestamp, nil | |
148 | return response.Channel, response.Timestamp, nil | |
182 | 149 | } |
183 | 150 | |
184 | 151 | // UpdateMessage updates a message in a channel |
185 | func (api *Slack) UpdateMessage(channelId, timestamp, text string) (string, string, string, error) { | |
152 | func (api *Slack) UpdateMessage(channel, timestamp, text string) (string, string, string, error) { | |
186 | 153 | values := url.Values{ |
187 | 154 | "token": {api.config.token}, |
188 | "channel": {channelId}, | |
155 | "channel": {channel}, | |
189 | 156 | "text": {escapeMessage(text)}, |
190 | 157 | "ts": {timestamp}, |
191 | 158 | } |
193 | 160 | if err != nil { |
194 | 161 | return "", "", "", err |
195 | 162 | } |
196 | return response.ChannelId, response.Timestamp, response.Text, nil | |
163 | return response.Channel, response.Timestamp, response.Text, nil | |
197 | 164 | } |
0 | package slack | |
1 | ||
2 | // Comment contains all the information relative to a comment | |
3 | type Comment struct { | |
4 | ID string `json:"id,omitempty"` | |
5 | Created JSONTime `json:"created,omitempty"` | |
6 | Timestamp JSONTime `json:"timestamp,omitempty"` | |
7 | User string `json:"user,omitempty"` | |
8 | Comment string `json:"comment,omitempty"` | |
9 | } |
6 | 6 | ) |
7 | 7 | |
8 | 8 | type imChannel struct { |
9 | Id string `json:"id"` | |
9 | ID string `json:"id"` | |
10 | 10 | } |
11 | 11 | |
12 | 12 | type imResponseFull struct { |
21 | 21 | |
22 | 22 | // IM contains information related to the Direct Message channel |
23 | 23 | type IM struct { |
24 | BaseChannel | |
25 | IsIM bool `json:"is_im"` | |
26 | UserId string `json:"user"` | |
27 | IsUserDeleted bool `json:"is_user_deleted"` | |
24 | baseChannel | |
25 | IsIM bool `json:"is_im"` | |
26 | User string `json:"user"` | |
27 | IsUserDeleted bool `json:"is_user_deleted"` | |
28 | 28 | } |
29 | 29 | |
30 | 30 | func imRequest(path string, values url.Values, debug bool) (*imResponseFull, error) { |
40 | 40 | } |
41 | 41 | |
42 | 42 | // CloseIMChannel closes the direct message channel |
43 | func (api *Slack) CloseIMChannel(channelId string) (bool, bool, error) { | |
43 | func (api *Slack) CloseIMChannel(channel string) (bool, bool, error) { | |
44 | 44 | values := url.Values{ |
45 | 45 | "token": {api.config.token}, |
46 | "channel": {channelId}, | |
46 | "channel": {channel}, | |
47 | 47 | } |
48 | 48 | response, err := imRequest("im.close", values, api.debug) |
49 | 49 | if err != nil { |
53 | 53 | } |
54 | 54 | |
55 | 55 | // OpenIMChannel opens a direct message channel to the user provided as argument |
56 | // Returns some status and the channelId | |
57 | func (api *Slack) OpenIMChannel(userId string) (bool, bool, string, error) { | |
56 | // Returns some status and the channel | |
57 | func (api *Slack) OpenIMChannel(user string) (bool, bool, string, error) { | |
58 | 58 | values := url.Values{ |
59 | 59 | "token": {api.config.token}, |
60 | "user": {userId}, | |
60 | "user": {user}, | |
61 | 61 | } |
62 | 62 | response, err := imRequest("im.open", values, api.debug) |
63 | 63 | if err != nil { |
64 | 64 | return false, false, "", err |
65 | 65 | } |
66 | return response.NoOp, response.AlreadyOpen, response.Channel.Id, nil | |
66 | return response.NoOp, response.AlreadyOpen, response.Channel.ID, nil | |
67 | 67 | } |
68 | 68 | |
69 | 69 | // MarkIMChannel sets the read mark of a direct message channel to a specific point |
70 | func (api *Slack) MarkIMChannel(channelId, ts string) (err error) { | |
70 | func (api *Slack) MarkIMChannel(channel, ts string) (err error) { | |
71 | 71 | values := url.Values{ |
72 | 72 | "token": {api.config.token}, |
73 | "channel": {channelId}, | |
73 | "channel": {channel}, | |
74 | 74 | "ts": {ts}, |
75 | 75 | } |
76 | 76 | _, err = imRequest("im.mark", values, api.debug) |
81 | 81 | } |
82 | 82 | |
83 | 83 | // GetIMHistory retrieves the direct message channel history |
84 | func (api *Slack) GetIMHistory(channelId string, params HistoryParameters) (*History, error) { | |
84 | func (api *Slack) GetIMHistory(channel string, params HistoryParameters) (*History, error) { | |
85 | 85 | values := url.Values{ |
86 | 86 | "token": {api.config.token}, |
87 | "channel": {channelId}, | |
87 | "channel": {channel}, | |
88 | 88 | } |
89 | 89 | if params.Latest != DEFAULT_HISTORY_LATEST { |
90 | 90 | values.Add("latest", params.Latest) |
20 | 20 | } |
21 | 21 | fmt.Printf("Name: %s, Url: %s\n", file.Name, file.URL) |
22 | 22 | |
23 | err = api.DeleteFile(file.Id) | |
23 | err = api.DeleteFile(file.ID) | |
24 | 24 | if err != nil { |
25 | 25 | fmt.Printf("%s\n", err) |
26 | 26 | return |
16 | 16 | return |
17 | 17 | } |
18 | 18 | for _, group := range groups { |
19 | fmt.Printf("Id: %s, Name: %s\n", group.Id, group.Name) | |
19 | fmt.Printf("ID: %s, Name: %s\n", group.ID, group.Name) | |
20 | 20 | } |
21 | 21 | } |
22 | 22 | */ |
23 | 23 | } |
24 | 24 | params.Attachments = []slack.Attachment{attachment} |
25 | channelId, timestamp, err := api.PostMessage("CHANNEL_ID", "Some text", params) | |
25 | channelID, timestamp, err := api.PostMessage("CHANNEL_ID", "Some text", params) | |
26 | 26 | if err != nil { |
27 | 27 | fmt.Printf("%s\n", err) |
28 | 28 | return |
29 | 29 | } |
30 | fmt.Printf("Message successfully sent to channel %s at %s", channelId, timestamp) | |
30 | fmt.Printf("Message successfully sent to channel %s at %s", channelID, timestamp) | |
31 | 31 | } |
12 | 12 | fmt.Printf("%s\n", err) |
13 | 13 | return |
14 | 14 | } |
15 | fmt.Printf("Id: %s, Fullname: %s, Email: %s\n", user.Id, user.Profile.RealName, user.Profile.Email) | |
15 | fmt.Printf("ID: %s, Fullname: %s, Email: %s\n", user.ID, user.Profile.RealName, user.Profile.Email) | |
16 | 16 | } |
8 | 8 | |
9 | 9 | const ( |
10 | 10 | // Add here the defaults in the siten |
11 | DEFAULT_FILES_USERID = "" | |
11 | DEFAULT_FILES_USER = "" | |
12 | 12 | DEFAULT_FILES_TS_FROM = 0 |
13 | 13 | DEFAULT_FILES_TS_TO = -1 |
14 | 14 | DEFAULT_FILES_TYPES = "all" |
16 | 16 | DEFAULT_FILES_PAGE = 1 |
17 | 17 | ) |
18 | 18 | |
19 | // Comment contains all the information relative to a comment | |
20 | type Comment struct { | |
21 | Id string `json:"id"` | |
22 | Timestamp JSONTime `json:"timestamp"` | |
23 | UserId string `json:"user"` | |
24 | Comment string `json:"comment"` | |
25 | Created JSONTime `json:"created,omitempty"` | |
26 | } | |
27 | ||
28 | 19 | // File contains all the information for a file |
29 | 20 | type File struct { |
30 | Id string `json:"id"` | |
21 | ID string `json:"id"` | |
31 | 22 | Created JSONTime `json:"created"` |
32 | 23 | Timestamp JSONTime `json:"timestamp"` |
33 | 24 | |
36 | 27 | Mimetype string `json:"mimetype"` |
37 | 28 | Filetype string `json:"filetype"` |
38 | 29 | PrettyType string `json:"pretty_type"` |
39 | UserId string `json:"user"` | |
30 | User string `json:"user"` | |
40 | 31 | |
41 | 32 | Mode string `json:"mode"` |
42 | 33 | Editable bool `json:"editable"` |
86 | 77 | |
87 | 78 | // GetFilesParameters contains all the parameters necessary (including the optional ones) for a GetFiles() request |
88 | 79 | type GetFilesParameters struct { |
89 | UserId string | |
80 | User string | |
90 | 81 | TimestampFrom JSONTime |
91 | 82 | TimestampTo JSONTime |
92 | 83 | Types string |
106 | 97 | // NewGetFilesParameters provides an instance of GetFilesParameters with all the sane default values set |
107 | 98 | func NewGetFilesParameters() GetFilesParameters { |
108 | 99 | return GetFilesParameters{ |
109 | UserId: DEFAULT_FILES_USERID, | |
100 | User: DEFAULT_FILES_USER, | |
110 | 101 | TimestampFrom: DEFAULT_FILES_TS_FROM, |
111 | 102 | TimestampTo: DEFAULT_FILES_TS_TO, |
112 | 103 | Types: DEFAULT_FILES_TYPES, |
128 | 119 | } |
129 | 120 | |
130 | 121 | // GetFileInfo retrieves a file and related comments |
131 | func (api *Slack) GetFileInfo(fileId string, count, page int) (*File, []Comment, *Paging, error) { | |
132 | values := url.Values{ | |
133 | "token": {api.config.token}, | |
134 | "file": {fileId}, | |
122 | func (api *Slack) GetFileInfo(fileID string, count, page int) (*File, []Comment, *Paging, error) { | |
123 | values := url.Values{ | |
124 | "token": {api.config.token}, | |
125 | "file": {fileID}, | |
135 | 126 | "count": {strconv.Itoa(count)}, |
136 | 127 | "page": {strconv.Itoa(page)}, |
137 | 128 | } |
147 | 138 | values := url.Values{ |
148 | 139 | "token": {api.config.token}, |
149 | 140 | } |
150 | if params.UserId != DEFAULT_FILES_USERID { | |
151 | values.Add("user", params.UserId) | |
141 | if params.User != DEFAULT_FILES_USER { | |
142 | values.Add("user", params.User) | |
152 | 143 | } |
153 | 144 | // XXX: this is broken. fix it with a proper unix timestamp |
154 | 145 | if params.TimestampFrom != DEFAULT_FILES_TS_FROM { |
216 | 207 | } |
217 | 208 | |
218 | 209 | // DeleteFile deletes a file |
219 | func (api *Slack) DeleteFile(fileId string) error { | |
220 | values := url.Values{ | |
221 | "token": {api.config.token}, | |
222 | "file": {fileId}, | |
210 | func (api *Slack) DeleteFile(fileID string) error { | |
211 | values := url.Values{ | |
212 | "token": {api.config.token}, | |
213 | "file": {fileID}, | |
223 | 214 | } |
224 | 215 | _, err := fileRequest("files.delete", values, api.debug) |
225 | 216 | if err != nil { |
7 | 7 | |
8 | 8 | // Group contains all the information for a group |
9 | 9 | type Group struct { |
10 | BaseChannel | |
10 | baseChannel | |
11 | 11 | Name string `json:"name"` |
12 | 12 | IsGroup bool `json:"is_group"` |
13 | 13 | Creator string `json:"creator"` |
51 | 51 | } |
52 | 52 | |
53 | 53 | // ArchiveGroup archives a private group |
54 | func (api *Slack) ArchiveGroup(groupId string) error { | |
55 | values := url.Values{ | |
56 | "token": {api.config.token}, | |
57 | "channel": {groupId}, | |
54 | func (api *Slack) ArchiveGroup(group string) error { | |
55 | values := url.Values{ | |
56 | "token": {api.config.token}, | |
57 | "channel": {group}, | |
58 | 58 | } |
59 | 59 | _, err := groupRequest("groups.archive", values, api.debug) |
60 | 60 | if err != nil { |
64 | 64 | } |
65 | 65 | |
66 | 66 | // UnarchiveGroup unarchives a private group |
67 | func (api *Slack) UnarchiveGroup(groupId string) error { | |
68 | values := url.Values{ | |
69 | "token": {api.config.token}, | |
70 | "channel": {groupId}, | |
67 | func (api *Slack) UnarchiveGroup(group string) error { | |
68 | values := url.Values{ | |
69 | "token": {api.config.token}, | |
70 | "channel": {group}, | |
71 | 71 | } |
72 | 72 | _, err := groupRequest("groups.unarchive", values, api.debug) |
73 | 73 | if err != nil { |
95 | 95 | // 2. Archives the existing group. |
96 | 96 | // 3. Creates a new group with the name of the existing group. |
97 | 97 | // 4. Adds all members of the existing group to the new group. |
98 | func (api *Slack) CreateChildGroup(groupId string) (*Group, error) { | |
99 | values := url.Values{ | |
100 | "token": {api.config.token}, | |
101 | "channel": {groupId}, | |
98 | func (api *Slack) CreateChildGroup(group string) (*Group, error) { | |
99 | values := url.Values{ | |
100 | "token": {api.config.token}, | |
101 | "channel": {group}, | |
102 | 102 | } |
103 | 103 | response, err := groupRequest("groups.createChild", values, api.debug) |
104 | 104 | if err != nil { |
108 | 108 | } |
109 | 109 | |
110 | 110 | // CloseGroup closes a private group |
111 | func (api *Slack) CloseGroup(groupId string) (bool, bool, error) { | |
112 | values := url.Values{ | |
113 | "token": {api.config.token}, | |
114 | "channel": {groupId}, | |
111 | func (api *Slack) CloseGroup(group string) (bool, bool, error) { | |
112 | values := url.Values{ | |
113 | "token": {api.config.token}, | |
114 | "channel": {group}, | |
115 | 115 | } |
116 | 116 | response, err := imRequest("groups.close", values, api.debug) |
117 | 117 | if err != nil { |
121 | 121 | } |
122 | 122 | |
123 | 123 | // GetGroupHistory retrieves message history for a give group |
124 | func (api *Slack) GetGroupHistory(groupId string, params HistoryParameters) (*History, error) { | |
125 | values := url.Values{ | |
126 | "token": {api.config.token}, | |
127 | "channel": {groupId}, | |
124 | func (api *Slack) GetGroupHistory(group string, params HistoryParameters) (*History, error) { | |
125 | values := url.Values{ | |
126 | "token": {api.config.token}, | |
127 | "channel": {group}, | |
128 | 128 | } |
129 | 129 | if params.Latest != DEFAULT_HISTORY_LATEST { |
130 | 130 | values.Add("latest", params.Latest) |
150 | 150 | } |
151 | 151 | |
152 | 152 | // InviteUserToGroup invites a user to a group |
153 | func (api *Slack) InviteUserToGroup(groupId, userId string) (*Group, bool, error) { | |
154 | values := url.Values{ | |
155 | "token": {api.config.token}, | |
156 | "channel": {groupId}, | |
157 | "user": {userId}, | |
153 | func (api *Slack) InviteUserToGroup(group, user string) (*Group, bool, error) { | |
154 | values := url.Values{ | |
155 | "token": {api.config.token}, | |
156 | "channel": {group}, | |
157 | "user": {user}, | |
158 | 158 | } |
159 | 159 | response, err := groupRequest("groups.invite", values, api.debug) |
160 | 160 | if err != nil { |
164 | 164 | } |
165 | 165 | |
166 | 166 | // LeaveGroup makes authenticated user leave the group |
167 | func (api *Slack) LeaveGroup(groupId string) error { | |
168 | values := url.Values{ | |
169 | "token": {api.config.token}, | |
170 | "channel": {groupId}, | |
167 | func (api *Slack) LeaveGroup(group string) error { | |
168 | values := url.Values{ | |
169 | "token": {api.config.token}, | |
170 | "channel": {group}, | |
171 | 171 | } |
172 | 172 | _, err := groupRequest("groups.leave", values, api.debug) |
173 | 173 | if err != nil { |
177 | 177 | } |
178 | 178 | |
179 | 179 | // KickUserFromGroup kicks a user from a group |
180 | func (api *Slack) KickUserFromGroup(groupId, userId string) error { | |
181 | values := url.Values{ | |
182 | "token": {api.config.token}, | |
183 | "channel": {groupId}, | |
184 | "user": {userId}, | |
180 | func (api *Slack) KickUserFromGroup(group, user string) error { | |
181 | values := url.Values{ | |
182 | "token": {api.config.token}, | |
183 | "channel": {group}, | |
184 | "user": {user}, | |
185 | 185 | } |
186 | 186 | _, err := groupRequest("groups.kick", values, api.debug) |
187 | 187 | if err != nil { |
206 | 206 | } |
207 | 207 | |
208 | 208 | // GetGroupInfo retrieves the given group |
209 | func (api *Slack) GetGroupInfo(groupId string) (*Group, error) { | |
210 | values := url.Values{ | |
211 | "token": {api.config.token}, | |
212 | "channel": {groupId}, | |
209 | func (api *Slack) GetGroupInfo(group string) (*Group, error) { | |
210 | values := url.Values{ | |
211 | "token": {api.config.token}, | |
212 | "channel": {group}, | |
213 | 213 | } |
214 | 214 | response, err := groupRequest("groups.info", values, api.debug) |
215 | 215 | if err != nil { |
223 | 223 | // timer before making the call. In this way, any further updates needed during the timeout will not generate extra |
224 | 224 | // calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live |
225 | 225 | // channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout. |
226 | func (api *Slack) SetGroupReadMark(groupId, ts string) error { | |
227 | values := url.Values{ | |
228 | "token": {api.config.token}, | |
229 | "channel": {groupId}, | |
226 | func (api *Slack) SetGroupReadMark(group, ts string) error { | |
227 | values := url.Values{ | |
228 | "token": {api.config.token}, | |
229 | "channel": {group}, | |
230 | 230 | "ts": {ts}, |
231 | 231 | } |
232 | 232 | _, err := groupRequest("groups.mark", values, api.debug) |
237 | 237 | } |
238 | 238 | |
239 | 239 | // OpenGroup opens a private group |
240 | func (api *Slack) OpenGroup(groupId string) (bool, bool, error) { | |
240 | func (api *Slack) OpenGroup(group string) (bool, bool, error) { | |
241 | 241 | values := url.Values{ |
242 | 242 | "token": {api.config.token}, |
243 | "user": {groupId}, | |
243 | "user": {group}, | |
244 | 244 | } |
245 | 245 | response, err := groupRequest("groups.open", values, api.debug) |
246 | 246 | if err != nil { |
252 | 252 | // RenameGroup renames a group |
253 | 253 | // XXX: They return a channel, not a group. What is this crap? :( |
254 | 254 | // Inconsistent api it seems. |
255 | func (api *Slack) RenameGroup(groupId, name string) (*Channel, error) { | |
256 | values := url.Values{ | |
257 | "token": {api.config.token}, | |
258 | "channel": {groupId}, | |
255 | func (api *Slack) RenameGroup(group, name string) (*Channel, error) { | |
256 | values := url.Values{ | |
257 | "token": {api.config.token}, | |
258 | "channel": {group}, | |
259 | 259 | "name": {name}, |
260 | 260 | } |
261 | 261 | // XXX: the created entry in this call returns a string instead of a number |
269 | 269 | } |
270 | 270 | |
271 | 271 | // SetGroupPurpose sets the group purpose |
272 | func (api *Slack) SetGroupPurpose(groupId, purpose string) (string, error) { | |
273 | values := url.Values{ | |
274 | "token": {api.config.token}, | |
275 | "channel": {groupId}, | |
272 | func (api *Slack) SetGroupPurpose(group, purpose string) (string, error) { | |
273 | values := url.Values{ | |
274 | "token": {api.config.token}, | |
275 | "channel": {group}, | |
276 | 276 | "purpose": {purpose}, |
277 | 277 | } |
278 | 278 | response, err := groupRequest("groups.setPurpose", values, api.debug) |
283 | 283 | } |
284 | 284 | |
285 | 285 | // SetGroupTopic sets the group topic |
286 | func (api *Slack) SetGroupTopic(groupId, topic string) (string, error) { | |
287 | values := url.Values{ | |
288 | "token": {api.config.token}, | |
289 | "channel": {groupId}, | |
286 | func (api *Slack) SetGroupTopic(group, topic string) (string, error) { | |
287 | values := url.Values{ | |
288 | "token": {api.config.token}, | |
289 | "channel": {group}, | |
290 | 290 | "topic": {topic}, |
291 | 291 | } |
292 | 292 | response, err := groupRequest("groups.setTopic", values, api.debug) |
105 | 105 | |
106 | 106 | // UserDetails contains user details coming in the initial response from StartRTM |
107 | 107 | type UserDetails struct { |
108 | Id string `json:"id"` | |
108 | ID string `json:"id"` | |
109 | 109 | Name string `json:"name"` |
110 | 110 | Created JSONTime `json:"created"` |
111 | 111 | ManualPresence string `json:"manual_presence"` |
123 | 123 | |
124 | 124 | // Team contains details about a team |
125 | 125 | type Team struct { |
126 | Id string `json:"id"` | |
126 | ID string `json:"id"` | |
127 | 127 | Name string `json:"name"` |
128 | 128 | Domain string `json:"name"` |
129 | 129 | } |
135 | 135 | |
136 | 136 | // Bot contains information about a bot |
137 | 137 | type Bot struct { |
138 | Id string `json:"id"` | |
138 | ID string `json:"id"` | |
139 | 139 | Name string `json:"name"` |
140 | 140 | Deleted bool `json:"deleted"` |
141 | 141 | Icons Icons `json:"icons"` |
159 | 159 | SlackWSResponse |
160 | 160 | } |
161 | 161 | |
162 | // GetBotById returns a bot given a bot id | |
163 | func (info Info) GetBotById(botId string) *Bot { | |
162 | // GetBotByID returns a bot given a bot id | |
163 | func (info Info) GetBotByID(botID string) *Bot { | |
164 | 164 | for _, bot := range info.Bots { |
165 | if bot.Id == botId { | |
165 | if bot.ID == botID { | |
166 | 166 | return &bot |
167 | 167 | } |
168 | 168 | } |
169 | 169 | return nil |
170 | 170 | } |
171 | 171 | |
172 | // GetUserById returns a user given a user id | |
173 | func (info Info) GetUserById(userId string) *User { | |
172 | // GetUserByID returns a user given a user id | |
173 | func (info Info) GetUserByID(userID string) *User { | |
174 | 174 | for _, user := range info.Users { |
175 | if user.Id == userId { | |
175 | if user.ID == userID { | |
176 | 176 | return &user |
177 | 177 | } |
178 | 178 | } |
179 | 179 | return nil |
180 | 180 | } |
181 | 181 | |
182 | // GetChannelById returns a channel given a channel id | |
183 | func (info Info) GetChannelById(channelId string) *Channel { | |
182 | // GetChannelByID returns a channel given a channel id | |
183 | func (info Info) GetChannelByID(channelID string) *Channel { | |
184 | 184 | for _, channel := range info.Channels { |
185 | if channel.Id == channelId { | |
185 | if channel.ID == channelID { | |
186 | 186 | return &channel |
187 | 187 | } |
188 | 188 | } |
11 | 11 | } |
12 | 12 | |
13 | 13 | // GetOAuthToken retrieves an AccessToken |
14 | func GetOAuthToken(clientId, clientSecret, code, redirectURI string, debug bool) (accessToken string, scope string, err error) { | |
14 | func GetOAuthToken(clientID, clientSecret, code, redirectURI string, debug bool) (accessToken string, scope string, err error) { | |
15 | 15 | values := url.Values{ |
16 | "client_id": {clientId}, | |
16 | "client_id": {clientID}, | |
17 | 17 | "client_secret": {clientSecret}, |
18 | 18 | "code": {code}, |
19 | 19 | "redirect_uri": {redirectURI}, |
22 | 22 | } |
23 | 23 | |
24 | 24 | type CtxChannel struct { |
25 | Id string `json:"id"` | |
25 | ID string `json:"id"` | |
26 | 26 | Name string `json:"name"` |
27 | 27 | } |
28 | 28 | |
29 | 29 | type CtxMessage struct { |
30 | UserId string `json:"user"` | |
30 | User string `json:"user"` | |
31 | 31 | Username string `json:"username"` |
32 | 32 | Text string `json:"text"` |
33 | 33 | Timestamp string `json:"ts"` |
37 | 37 | type SearchMessage struct { |
38 | 38 | Type string `json:"type"` |
39 | 39 | Channel CtxChannel `json:"channel"` |
40 | UserId string `json:"user"` | |
40 | User string `json:"user"` | |
41 | 41 | Username string `json:"username"` |
42 | 42 | Timestamp string `json:"ts"` |
43 | 43 | Text string `json:"text"` |
19 | 19 | Url string `json:"url"` |
20 | 20 | Team string `json:"team"` |
21 | 21 | User string `json:"user"` |
22 | TeamId string `json:"team_id"` | |
23 | UserId string `json:"user_id"` | |
22 | TeamID string `json:"team_id"` | |
23 | UserID string `json:"user_id"` | |
24 | 24 | } |
25 | 25 | |
26 | 26 | type authTestResponseFull struct { |
6 | 6 | ) |
7 | 7 | |
8 | 8 | const ( |
9 | DEFAULT_STARS_USERID = "" | |
10 | DEFAULT_STARS_COUNT = 100 | |
11 | DEFAULT_STARS_PAGE = 1 | |
9 | DEFAULT_STARS_USER = "" | |
10 | DEFAULT_STARS_COUNT = 100 | |
11 | DEFAULT_STARS_PAGE = 1 | |
12 | 12 | ) |
13 | 13 | |
14 | 14 | type StarsParameters struct { |
30 | 30 | |
31 | 31 | func NewStarsParameters() StarsParameters { |
32 | 32 | return StarsParameters{ |
33 | User: DEFAULT_STARS_USERID, | |
33 | User: DEFAULT_STARS_USER, | |
34 | 34 | Count: DEFAULT_STARS_COUNT, |
35 | 35 | Page: DEFAULT_STARS_PAGE, |
36 | 36 | } |
50 | 50 | values := url.Values{ |
51 | 51 | "token": {api.config.token}, |
52 | 52 | } |
53 | if params.User != DEFAULT_STARS_USERID { | |
53 | if params.User != DEFAULT_STARS_USER { | |
54 | 54 | values.Add("user", params.User) |
55 | 55 | } |
56 | 56 | if params.Count != DEFAULT_STARS_COUNT { |
24 | 24 | |
25 | 25 | // User contains all the information of a user |
26 | 26 | type User struct { |
27 | Id string `json:"id"` | |
27 | ID string `json:"id"` | |
28 | 28 | Name string `json:"name"` |
29 | 29 | Deleted bool `json:"deleted"` |
30 | 30 | Color string `json:"color"` |
74 | 74 | } |
75 | 75 | |
76 | 76 | // GetUserPresence will retrieve the current presence status of given user. |
77 | func (api *Slack) GetUserPresence(userId string) (*UserPresence, error) { | |
77 | func (api *Slack) GetUserPresence(user string) (*UserPresence, error) { | |
78 | 78 | values := url.Values{ |
79 | 79 | "token": {api.config.token}, |
80 | "user": {userId}, | |
80 | "user": {user}, | |
81 | 81 | } |
82 | 82 | response, err := userRequest("users.getPresence", values, api.debug) |
83 | 83 | if err != nil { |
87 | 87 | } |
88 | 88 | |
89 | 89 | // GetUserInfo will retrive the complete user information |
90 | func (api *Slack) GetUserInfo(userId string) (*User, error) { | |
90 | func (api *Slack) GetUserInfo(user string) (*User, error) { | |
91 | 91 | values := url.Values{ |
92 | 92 | "token": {api.config.token}, |
93 | "user": {userId}, | |
93 | "user": {user}, | |
94 | 94 | } |
95 | 95 | response, err := userRequest("users.info", values, api.debug) |
96 | 96 | if err != nil { |
112 | 112 | api.mutex.Lock() |
113 | 113 | defer api.mutex.Unlock() |
114 | 114 | api.messageId++ |
115 | msg := &Ping{Id: api.messageId, Type: "ping"} | |
115 | msg := &Ping{ID: api.messageId, Type: "ping"} | |
116 | 116 | if err := websocket.JSON.Send(api.conn, msg); err != nil { |
117 | 117 | return err |
118 | 118 | } |
6 | 6 | } |
7 | 7 | |
8 | 8 | type ChannelCreatedInfo struct { |
9 | Id string `json:"id"` | |
9 | ID string `json:"id"` | |
10 | 10 | IsChannel bool `json:"is_channel"` |
11 | 11 | Name string `json:"name"` |
12 | 12 | Created JSONTimeString `json:"created"` |
24 | 24 | // channel_archive |
25 | 25 | // channel_unarchive |
26 | 26 | Type string `json:"type"` |
27 | ChannelId string `json:"channel"` | |
28 | UserId string `json:"user,omitempty"` | |
27 | Channel string `json:"channel"` | |
28 | User string `json:"user,omitempty"` | |
29 | 29 | Timestamp *JSONTimeString `json:"ts,omitempty"` |
30 | 30 | } |
31 | 31 | |
35 | 35 | } |
36 | 36 | |
37 | 37 | type ChannelRenameInfo struct { |
38 | Id string `json:"id"` | |
38 | ID string `json:"id"` | |
39 | 39 | Name string `json:"name"` |
40 | 40 | Created JSONTimeString `json:"created"` |
41 | 41 | } |
1 | 1 | |
2 | 2 | type IMCreatedEvent struct { |
3 | 3 | Type string `json:"type"` |
4 | UserId string `json:"user"` | |
4 | User string `json:"user"` | |
5 | 5 | Channel ChannelCreatedInfo `json:"channel"` |
6 | 6 | } |
7 | 7 |
3 | 3 | Type string `json:"type"` |
4 | 4 | EventTimestamp JSONTimeString `json:"event_ts"` |
5 | 5 | File File `json:"file"` |
6 | // FileId is used for FileDeletedEvent | |
7 | FileId string `json:"file_id,omitempty"` | |
6 | // FileID is used for FileDeletedEvent | |
7 | FileID string `json:"file_id,omitempty"` | |
8 | 8 | } |
9 | 9 | |
10 | 10 | type FileCreatedEvent fileActionEvent |
27 | 27 | |
28 | 28 | type FileCommentDeletedEvent struct { |
29 | 29 | fileActionEvent |
30 | CommentId string `json:"comment"` | |
30 | Comment string `json:"comment"` | |
31 | 31 | } |
1 | 1 | |
2 | 2 | type GroupCreatedEvent struct { |
3 | 3 | Type string `json:"type"` |
4 | UserId string `json:"user"` | |
4 | User string `json:"user"` | |
5 | 5 | Channel ChannelCreatedInfo `json:"channel"` |
6 | 6 | } |
7 | 7 |
11 | 11 | type PresenceChangeEvent struct { |
12 | 12 | Type string `json:"type"` |
13 | 13 | Presence string `json:"presence"` |
14 | UserId string `json:"user"` | |
14 | User string `json:"user"` | |
15 | 15 | } |
16 | 16 | |
17 | 17 | type UserTypingEvent struct { |
18 | Type string `json:"type"` | |
19 | UserId string `json:"user"` | |
20 | ChannelId string `json:"channel"` | |
18 | Type string `json:"type"` | |
19 | User string `json:"user"` | |
20 | Channel string `json:"channel"` | |
21 | 21 | } |
22 | 22 | |
23 | 23 | type LatencyReport struct { |