Merge remote-tracking branch 'upstream/master'
Pavel Kiselev
8 years ago
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | ||
6 | "github.com/nlopes/slack" | |
7 | ) | |
8 | ||
9 | func main() { | |
10 | var ( | |
11 | apiToken string | |
12 | debug bool | |
13 | ) | |
14 | ||
15 | flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token") | |
16 | flag.BoolVar(&debug, "debug", false, "Show JSON output") | |
17 | flag.Parse() | |
18 | ||
19 | api := slack.New(apiToken) | |
20 | if debug { | |
21 | api.SetDebug(true) | |
22 | } | |
23 | ||
24 | var ( | |
25 | postAsUserName string | |
26 | postAsUserID string | |
27 | postToUserName string | |
28 | postToUserID string | |
29 | postToChannelID string | |
30 | ) | |
31 | ||
32 | // Find the user to post as. | |
33 | authTest, err := api.AuthTest() | |
34 | if err != nil { | |
35 | fmt.Printf("Error getting channels: %s\n", err) | |
36 | return | |
37 | } | |
38 | ||
39 | // Post as the authenticated user. | |
40 | postAsUserName = authTest.User | |
41 | postAsUserID = authTest.UserId | |
42 | ||
43 | // Posting to DM with self causes a conversation with slackbot. | |
44 | postToUserName = authTest.User | |
45 | postToUserID = authTest.UserId | |
46 | ||
47 | // Find the channel. | |
48 | _, _, chanID, err := api.OpenIMChannel(postToUserID) | |
49 | if err != nil { | |
50 | fmt.Printf("Error opening IM: %s\n", err) | |
51 | return | |
52 | } | |
53 | postToChannelID = chanID | |
54 | ||
55 | fmt.Printf("Posting as %s (%s) in DM with %s (%s), channel %s\n", postAsUserName, postAsUserID, postToUserName, postToUserID, postToChannelID) | |
56 | ||
57 | // Post a message. | |
58 | postParams := slack.PostMessageParameters{} | |
59 | channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams) | |
60 | if err != nil { | |
61 | fmt.Printf("Error posting message: %s\n", err) | |
62 | return | |
63 | } | |
64 | ||
65 | // Grab a reference to the message. | |
66 | msgRef := slack.NewRefToMessage(channelID, timestamp) | |
67 | ||
68 | // React with :+1: | |
69 | if err := api.AddReaction("+1", msgRef); err != nil { | |
70 | fmt.Printf("Error adding reaction: %s\n", err) | |
71 | return | |
72 | } | |
73 | ||
74 | // React with :-1: | |
75 | if err := api.AddReaction("cry", msgRef); err != nil { | |
76 | fmt.Printf("Error adding reaction: %s\n", err) | |
77 | return | |
78 | } | |
79 | ||
80 | // Get all reactions on the message. | |
81 | msgReactions, err := api.GetReactions(msgRef, slack.NewGetReactionsParameters()) | |
82 | if err != nil { | |
83 | fmt.Printf("Error getting reactions: %s\n", err) | |
84 | return | |
85 | } | |
86 | fmt.Printf("\n") | |
87 | fmt.Printf("%d reactions to message...\n", len(msgReactions)) | |
88 | for _, r := range msgReactions { | |
89 | fmt.Printf(" %d users say %s\n", r.Count, r.Name) | |
90 | } | |
91 | ||
92 | // List all of the users reactions. | |
93 | listReactions, _, err := api.ListReactions(slack.NewListReactionsParameters()) | |
94 | if err != nil { | |
95 | fmt.Printf("Error listing reactions: %s\n", err) | |
96 | return | |
97 | } | |
98 | fmt.Printf("\n") | |
99 | fmt.Printf("All reactions by %s...\n", authTest.User) | |
100 | for _, item := range listReactions { | |
101 | fmt.Printf("%d on a %s...\n", len(item.Reactions), item.Type) | |
102 | for _, r := range item.Reactions { | |
103 | fmt.Printf(" %s (along with %d others)\n", r.Name, r.Count-1) | |
104 | } | |
105 | } | |
106 | ||
107 | // Remove the :cry: reaction. | |
108 | err = api.RemoveReaction("cry", msgRef) | |
109 | if err != nil { | |
110 | fmt.Printf("Error remove reaction: %s\n", err) | |
111 | return | |
112 | } | |
113 | ||
114 | // Get all reactions on the message. | |
115 | msgReactions, err = api.GetReactions(msgRef, slack.NewGetReactionsParameters()) | |
116 | if err != nil { | |
117 | fmt.Printf("Error getting reactions: %s\n", err) | |
118 | return | |
119 | } | |
120 | fmt.Printf("\n") | |
121 | fmt.Printf("%d reactions to message after removing cry...\n", len(msgReactions)) | |
122 | for _, r := range msgReactions { | |
123 | fmt.Printf(" %d users say %s\n", r.Count, r.Name) | |
124 | } | |
125 | } |
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | ||
6 | "github.com/nlopes/slack" | |
7 | ) | |
8 | ||
9 | func main() { | |
10 | var ( | |
11 | apiToken string | |
12 | debug bool | |
13 | ) | |
14 | ||
15 | flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token") | |
16 | flag.BoolVar(&debug, "debug", false, "Show JSON output") | |
17 | flag.Parse() | |
18 | ||
19 | api := slack.New(apiToken) | |
20 | if debug { | |
21 | api.SetDebug(true) | |
22 | } | |
23 | ||
24 | // Get all stars for the usr. | |
25 | params := slack.NewStarsParameters() | |
26 | starredItems, _, err := api.GetStarred(params) | |
27 | if err != nil { | |
28 | fmt.Printf("Error getting stars: %s\n", err) | |
29 | return | |
30 | } | |
31 | for _, s := range starredItems { | |
32 | var desc string | |
33 | switch s.Type { | |
34 | case slack.TYPE_MESSAGE: | |
35 | desc = s.Message.Text | |
36 | case slack.TYPE_FILE: | |
37 | desc = s.File.Name | |
38 | case slack.TYPE_FILE_COMMENT: | |
39 | desc = s.File.Name + " - " + s.Comment.Comment | |
40 | case slack.TYPE_CHANNEL, slack.TYPE_IM, slack.TYPE_GROUP: | |
41 | desc = s.Channel | |
42 | } | |
43 | fmt.Printf("Starred %s: %s\n", s.Type, desc) | |
44 | } | |
45 | } |
0 | package slack | |
1 | ||
2 | const ( | |
3 | TYPE_MESSAGE = "message" | |
4 | TYPE_FILE = "file" | |
5 | TYPE_FILE_COMMENT = "file_comment" | |
6 | TYPE_CHANNEL = "channel" | |
7 | TYPE_IM = "im" | |
8 | TYPE_GROUP = "group" | |
9 | ) | |
10 | ||
11 | // Item is any type of slack message - message, file, or file comment. | |
12 | type Item struct { | |
13 | Type string `json:"type"` | |
14 | Channel string `json:"channel,omitempty"` | |
15 | Message *Message `json:"message,omitempty"` | |
16 | File *File `json:"file,omitempty"` | |
17 | Comment *Comment `json:"comment,omitempty"` | |
18 | } | |
19 | ||
20 | // NewMessageItem turns a message on a channel into a typed message struct. | |
21 | func NewMessageItem(ch string, m *Message) Item { | |
22 | return Item{Type: TYPE_MESSAGE, Channel: ch, Message: m} | |
23 | } | |
24 | ||
25 | // NewFileItem turns a file into a typed file struct. | |
26 | func NewFileItem(f *File) Item { | |
27 | return Item{Type: TYPE_FILE, File: f} | |
28 | } | |
29 | ||
30 | // NewFileCommentItem turns a file and comment into a typed file_comment struct. | |
31 | func NewFileCommentItem(f *File, c *Comment) Item { | |
32 | return Item{Type: TYPE_FILE_COMMENT, File: f, Comment: c} | |
33 | } | |
34 | ||
35 | // NewChannelItem turns a channel id into a typed channel struct. | |
36 | func NewChannelItem(ch string) Item { | |
37 | return Item{Type: TYPE_CHANNEL, Channel: ch} | |
38 | } | |
39 | ||
40 | // NewIMItem turns a channel id into a typed im struct. | |
41 | func NewIMItem(ch string) Item { | |
42 | return Item{Type: TYPE_IM, Channel: ch} | |
43 | } | |
44 | ||
45 | // NewGroupItem turns a channel id into a typed group struct. | |
46 | func NewGroupItem(ch string) Item { | |
47 | return Item{Type: TYPE_GROUP, Channel: ch} | |
48 | } | |
49 | ||
50 | // ItemRef is a reference to a message of any type. One of FileID, | |
51 | // CommentId, or the combination of ChannelId and Timestamp must be | |
52 | // specified. | |
53 | type ItemRef struct { | |
54 | ChannelId string `json:"channel"` | |
55 | Timestamp string `json:"timestamp"` | |
56 | FileId string `json:"file"` | |
57 | CommentId string `json:"file_comment"` | |
58 | } | |
59 | ||
60 | // NewRefToMessage initializes a reference to to a message. | |
61 | func NewRefToMessage(channelID, timestamp string) ItemRef { | |
62 | return ItemRef{ChannelId: channelID, Timestamp: timestamp} | |
63 | } | |
64 | ||
65 | // NewRefToFile initializes a reference to a file. | |
66 | func NewRefToFile(fileID string) ItemRef { | |
67 | return ItemRef{FileId: fileID} | |
68 | } | |
69 | ||
70 | // NewRefToComment initializes a reference to a file comment. | |
71 | func NewRefToComment(commentID string) ItemRef { | |
72 | return ItemRef{CommentId: commentID} | |
73 | } |
0 | package slack | |
1 | ||
2 | import "testing" | |
3 | ||
4 | func TestNewMessageItem(t *testing.T) { | |
5 | c := "C1" | |
6 | m := &Message{} | |
7 | mi := NewMessageItem(c, m) | |
8 | if mi.Type != TYPE_MESSAGE { | |
9 | t.Errorf("want Type %s, got %s", mi.Type, TYPE_MESSAGE) | |
10 | } | |
11 | if mi.Channel != c { | |
12 | t.Errorf("got Channel %s, want %s", mi.Channel, c) | |
13 | } | |
14 | if mi.Message != m { | |
15 | t.Errorf("got Message %v, want %v", mi.Message, m) | |
16 | } | |
17 | } | |
18 | ||
19 | func TestNewFileItem(t *testing.T) { | |
20 | f := &File{} | |
21 | fi := NewFileItem(f) | |
22 | if fi.Type != TYPE_FILE { | |
23 | t.Errorf("got Type %s, want %s", fi.Type, TYPE_FILE) | |
24 | } | |
25 | if fi.File != f { | |
26 | t.Errorf("got File %v, want %v", fi.File, f) | |
27 | } | |
28 | } | |
29 | ||
30 | func TestNewFileCommentItem(t *testing.T) { | |
31 | f := &File{} | |
32 | c := &Comment{} | |
33 | fci := NewFileCommentItem(f, c) | |
34 | if fci.Type != TYPE_FILE_COMMENT { | |
35 | t.Errorf("got Type %s, want %s", fci.Type, TYPE_FILE_COMMENT) | |
36 | } | |
37 | if fci.File != f { | |
38 | t.Errorf("got File %v, want %v", fci.File, f) | |
39 | } | |
40 | if fci.Comment != c { | |
41 | t.Errorf("got Comment %v, want %v", fci.Comment, c) | |
42 | } | |
43 | } | |
44 | ||
45 | func TestNewChannelItem(t *testing.T) { | |
46 | c := "C1" | |
47 | ci := NewChannelItem(c) | |
48 | if ci.Type != TYPE_CHANNEL { | |
49 | t.Errorf("got Type %s, want %s", ci.Type, TYPE_CHANNEL) | |
50 | } | |
51 | if ci.Channel != "C1" { | |
52 | t.Errorf("got Channel %v, want %v", ci.Channel, "C1") | |
53 | } | |
54 | } | |
55 | ||
56 | func TestNewIMItem(t *testing.T) { | |
57 | c := "D1" | |
58 | ci := NewIMItem(c) | |
59 | if ci.Type != TYPE_IM { | |
60 | t.Errorf("got Type %s, want %s", ci.Type, TYPE_IM) | |
61 | } | |
62 | if ci.Channel != "D1" { | |
63 | t.Errorf("got Channel %v, want %v", ci.Channel, "D1") | |
64 | } | |
65 | } | |
66 | ||
67 | func TestNewGroupItem(t *testing.T) { | |
68 | c := "G1" | |
69 | ci := NewGroupItem(c) | |
70 | if ci.Type != TYPE_GROUP { | |
71 | t.Errorf("got Type %s, want %s", ci.Type, TYPE_GROUP) | |
72 | } | |
73 | if ci.Channel != "G1" { | |
74 | t.Errorf("got Channel %v, want %v", ci.Channel, "G1") | |
75 | } | |
76 | } | |
77 | ||
78 | func TestNewRefToMessage(t *testing.T) { | |
79 | ref := NewRefToMessage("chan", "ts") | |
80 | if got, want := ref.ChannelId, "chan"; got != want { | |
81 | t.Errorf("ChannelId got %s, want %s", got, want) | |
82 | } | |
83 | if got, want := ref.Timestamp, "ts"; got != want { | |
84 | t.Errorf("Timestamp got %s, want %s", got, want) | |
85 | } | |
86 | if got, want := ref.FileId, ""; got != want { | |
87 | t.Errorf("FileId got %s, want %s", got, want) | |
88 | } | |
89 | if got, want := ref.CommentId, ""; got != want { | |
90 | t.Errorf("CommentId got %s, want %s", got, want) | |
91 | } | |
92 | } | |
93 | ||
94 | func TestNewRefToFile(t *testing.T) { | |
95 | ref := NewRefToFile("file") | |
96 | if got, want := ref.ChannelId, ""; got != want { | |
97 | t.Errorf("ChannelId got %s, want %s", got, want) | |
98 | } | |
99 | if got, want := ref.Timestamp, ""; got != want { | |
100 | t.Errorf("Timestamp got %s, want %s", got, want) | |
101 | } | |
102 | if got, want := ref.FileId, "file"; got != want { | |
103 | t.Errorf("FileId got %s, want %s", got, want) | |
104 | } | |
105 | if got, want := ref.CommentId, ""; got != want { | |
106 | t.Errorf("CommentId got %s, want %s", got, want) | |
107 | } | |
108 | } | |
109 | ||
110 | func TestNewRefToComment(t *testing.T) { | |
111 | ref := NewRefToComment("file_comment") | |
112 | if got, want := ref.ChannelId, ""; got != want { | |
113 | t.Errorf("ChannelId got %s, want %s", got, want) | |
114 | } | |
115 | if got, want := ref.Timestamp, ""; got != want { | |
116 | t.Errorf("Timestamp got %s, want %s", got, want) | |
117 | } | |
118 | if got, want := ref.FileId, ""; got != want { | |
119 | t.Errorf("FileId got %s, want %s", got, want) | |
120 | } | |
121 | if got, want := ref.CommentId, "file_comment"; got != want { | |
122 | t.Errorf("CommentId got %s, want %s", got, want) | |
123 | } | |
124 | } |
0 | package slack | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "net/url" | |
5 | "strconv" | |
6 | ) | |
7 | ||
8 | // ItemReaction is the reactions that have happened on an item. | |
9 | type ItemReaction struct { | |
10 | Name string `json:"name"` | |
11 | Count int `json:"count"` | |
12 | Users []string `json:"users"` | |
13 | } | |
14 | ||
15 | // ReactedItem is an item that was reacted to, and the details of the | |
16 | // reactions. | |
17 | type ReactedItem struct { | |
18 | Item | |
19 | Reactions []ItemReaction | |
20 | } | |
21 | ||
22 | // GetReactionsParameters is the inputs to get reactions to an item. | |
23 | type GetReactionsParameters struct { | |
24 | Full bool | |
25 | } | |
26 | ||
27 | // NewGetReactionsParameters initializes the inputs to get reactions to an item. | |
28 | func NewGetReactionsParameters() GetReactionsParameters { | |
29 | return GetReactionsParameters{ | |
30 | Full: false, | |
31 | } | |
32 | } | |
33 | ||
34 | type getReactionsResponseFull struct { | |
35 | Type string | |
36 | M struct { | |
37 | Reactions []ItemReaction | |
38 | } `json:"message"` | |
39 | F struct { | |
40 | Reactions []ItemReaction | |
41 | } `json:"file"` | |
42 | FC struct { | |
43 | Reactions []ItemReaction | |
44 | } `json:"comment"` | |
45 | SlackResponse | |
46 | } | |
47 | ||
48 | func (res getReactionsResponseFull) extractReactions() []ItemReaction { | |
49 | switch res.Type { | |
50 | case "message": | |
51 | return res.M.Reactions | |
52 | case "file": | |
53 | return res.F.Reactions | |
54 | case "file_comment": | |
55 | return res.FC.Reactions | |
56 | } | |
57 | return []ItemReaction{} | |
58 | } | |
59 | ||
60 | const ( | |
61 | DEFAULT_REACTIONS_USERID = "" | |
62 | DEFAULT_REACTIONS_COUNT = 100 | |
63 | DEFAULT_REACTIONS_PAGE = 1 | |
64 | DEFAULT_REACTIONS_FULL = false | |
65 | ) | |
66 | ||
67 | // ListReactionsParameters is the inputs to find all reactions by a user. | |
68 | type ListReactionsParameters struct { | |
69 | UserId string | |
70 | Count int | |
71 | Page int | |
72 | Full bool | |
73 | } | |
74 | ||
75 | // NewListReactionsParameters initializes the inputs to find all reactions | |
76 | // performed by a user. | |
77 | func NewListReactionsParameters() ListReactionsParameters { | |
78 | return ListReactionsParameters{ | |
79 | UserId: DEFAULT_REACTIONS_USERID, | |
80 | Count: DEFAULT_REACTIONS_COUNT, | |
81 | Page: DEFAULT_REACTIONS_PAGE, | |
82 | Full: DEFAULT_REACTIONS_FULL, | |
83 | } | |
84 | } | |
85 | ||
86 | type listReactionsResponseFull struct { | |
87 | Items []struct { | |
88 | Type string | |
89 | Channel string | |
90 | M struct { | |
91 | *Message | |
92 | Reactions []ItemReaction | |
93 | } `json:"message"` | |
94 | F struct { | |
95 | *File | |
96 | Reactions []ItemReaction | |
97 | } `json:"file"` | |
98 | FC struct { | |
99 | *Comment | |
100 | Reactions []ItemReaction | |
101 | } `json:"comment"` | |
102 | } | |
103 | Paging `json:"paging"` | |
104 | SlackResponse | |
105 | } | |
106 | ||
107 | func (res listReactionsResponseFull) extractReactedItems() []ReactedItem { | |
108 | items := make([]ReactedItem, len(res.Items)) | |
109 | for i, input := range res.Items { | |
110 | item := ReactedItem{} | |
111 | item.Type = input.Type | |
112 | switch input.Type { | |
113 | case "message": | |
114 | item.Channel = input.Channel | |
115 | item.Message = input.M.Message | |
116 | item.Reactions = input.M.Reactions | |
117 | case "file": | |
118 | item.File = input.F.File | |
119 | item.Reactions = input.F.Reactions | |
120 | case "file_comment": | |
121 | item.File = input.F.File | |
122 | item.Comment = input.FC.Comment | |
123 | item.Reactions = input.FC.Reactions | |
124 | } | |
125 | items[i] = item | |
126 | } | |
127 | return items | |
128 | } | |
129 | ||
130 | // AddReaction adds a reaction emoji to a message, file or file comment. | |
131 | func (api *Slack) AddReaction(name string, item ItemRef) error { | |
132 | values := url.Values{ | |
133 | "token": {api.config.token}, | |
134 | } | |
135 | if name != "" { | |
136 | values.Set("name", name) | |
137 | } | |
138 | if item.ChannelId != "" { | |
139 | values.Set("channel", string(item.ChannelId)) | |
140 | } | |
141 | if item.Timestamp != "" { | |
142 | values.Set("timestamp", string(item.Timestamp)) | |
143 | } | |
144 | if item.FileId != "" { | |
145 | values.Set("file", string(item.FileId)) | |
146 | } | |
147 | if item.CommentId != "" { | |
148 | values.Set("file_comment", string(item.CommentId)) | |
149 | } | |
150 | response := &SlackResponse{} | |
151 | if err := parseResponse("reactions.add", values, response, api.debug); err != nil { | |
152 | return err | |
153 | } | |
154 | if !response.Ok { | |
155 | return errors.New(response.Error) | |
156 | } | |
157 | return nil | |
158 | } | |
159 | ||
160 | // RemoveReaction removes a reaction emoji from a message, file or file comment. | |
161 | func (api *Slack) RemoveReaction(name string, item ItemRef) error { | |
162 | values := url.Values{ | |
163 | "token": {api.config.token}, | |
164 | } | |
165 | if name != "" { | |
166 | values.Set("name", name) | |
167 | } | |
168 | if item.ChannelId != "" { | |
169 | values.Set("channel", string(item.ChannelId)) | |
170 | } | |
171 | if item.Timestamp != "" { | |
172 | values.Set("timestamp", string(item.Timestamp)) | |
173 | } | |
174 | if item.FileId != "" { | |
175 | values.Set("file", string(item.FileId)) | |
176 | } | |
177 | if item.CommentId != "" { | |
178 | values.Set("file_comment", string(item.CommentId)) | |
179 | } | |
180 | response := &SlackResponse{} | |
181 | if err := parseResponse("reactions.remove", values, response, api.debug); err != nil { | |
182 | return err | |
183 | } | |
184 | if !response.Ok { | |
185 | return errors.New(response.Error) | |
186 | } | |
187 | return nil | |
188 | } | |
189 | ||
190 | // GetReactions returns details about the reactions on an item. | |
191 | func (api *Slack) GetReactions(item ItemRef, params GetReactionsParameters) ([]ItemReaction, error) { | |
192 | values := url.Values{ | |
193 | "token": {api.config.token}, | |
194 | } | |
195 | if item.ChannelId != "" { | |
196 | values.Set("channel", string(item.ChannelId)) | |
197 | } | |
198 | if item.Timestamp != "" { | |
199 | values.Set("timestamp", string(item.Timestamp)) | |
200 | } | |
201 | if item.FileId != "" { | |
202 | values.Set("file", string(item.FileId)) | |
203 | } | |
204 | if item.CommentId != "" { | |
205 | values.Set("file_comment", string(item.CommentId)) | |
206 | } | |
207 | if params.Full != DEFAULT_REACTIONS_FULL { | |
208 | values.Set("full", strconv.FormatBool(params.Full)) | |
209 | } | |
210 | response := &getReactionsResponseFull{} | |
211 | if err := parseResponse("reactions.get", values, response, api.debug); err != nil { | |
212 | return nil, err | |
213 | } | |
214 | if !response.Ok { | |
215 | return nil, errors.New(response.Error) | |
216 | } | |
217 | return response.extractReactions(), nil | |
218 | } | |
219 | ||
220 | // ListReactions returns information about the items a user reacted to. | |
221 | func (api *Slack) ListReactions(params ListReactionsParameters) ([]ReactedItem, *Paging, error) { | |
222 | values := url.Values{ | |
223 | "token": {api.config.token}, | |
224 | } | |
225 | if params.UserId != DEFAULT_REACTIONS_USERID { | |
226 | values.Add("user", params.UserId) | |
227 | } | |
228 | if params.Count != DEFAULT_REACTIONS_COUNT { | |
229 | values.Add("count", strconv.Itoa(params.Count)) | |
230 | } | |
231 | if params.Page != DEFAULT_REACTIONS_PAGE { | |
232 | values.Add("page", strconv.Itoa(params.Page)) | |
233 | } | |
234 | if params.Full != DEFAULT_REACTIONS_FULL { | |
235 | values.Add("full", strconv.FormatBool(params.Full)) | |
236 | } | |
237 | response := &listReactionsResponseFull{} | |
238 | err := parseResponse("reactions.list", values, response, api.debug) | |
239 | if err != nil { | |
240 | return nil, nil, err | |
241 | } | |
242 | if !response.Ok { | |
243 | return nil, nil, errors.New(response.Error) | |
244 | } | |
245 | return response.extractReactedItems(), &response.Paging, nil | |
246 | } |
0 | package slack | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "net/http" | |
5 | "reflect" | |
6 | "testing" | |
7 | ) | |
8 | ||
9 | type reactionsHandler struct { | |
10 | gotParams map[string]string | |
11 | response string | |
12 | } | |
13 | ||
14 | func newReactionsHandler() *reactionsHandler { | |
15 | return &reactionsHandler{ | |
16 | gotParams: make(map[string]string), | |
17 | response: `{ "ok": true }`, | |
18 | } | |
19 | } | |
20 | ||
21 | func (rh *reactionsHandler) accumulateFormValue(k string, r *http.Request) { | |
22 | if v := r.FormValue(k); v != "" { | |
23 | rh.gotParams[k] = v | |
24 | } | |
25 | } | |
26 | ||
27 | func (rh *reactionsHandler) handler(w http.ResponseWriter, r *http.Request) { | |
28 | rh.accumulateFormValue("channel", r) | |
29 | rh.accumulateFormValue("count", r) | |
30 | rh.accumulateFormValue("file", r) | |
31 | rh.accumulateFormValue("file_comment", r) | |
32 | rh.accumulateFormValue("full", r) | |
33 | rh.accumulateFormValue("name", r) | |
34 | rh.accumulateFormValue("page", r) | |
35 | rh.accumulateFormValue("timestamp", r) | |
36 | rh.accumulateFormValue("user", r) | |
37 | w.Header().Set("Content-Type", "application/json") | |
38 | w.Write([]byte(rh.response)) | |
39 | } | |
40 | ||
41 | func TestSlack_AddReaction(t *testing.T) { | |
42 | once.Do(startServer) | |
43 | SLACK_API = "http://" + serverAddr + "/" | |
44 | api := New("testing-token") | |
45 | tests := []struct { | |
46 | name string | |
47 | ref ItemRef | |
48 | wantParams map[string]string | |
49 | }{ | |
50 | { | |
51 | "thumbsup", | |
52 | NewRefToMessage("ChannelID", "123"), | |
53 | map[string]string{ | |
54 | "name": "thumbsup", | |
55 | "channel": "ChannelID", | |
56 | "timestamp": "123", | |
57 | }, | |
58 | }, | |
59 | { | |
60 | "thumbsup", | |
61 | NewRefToFile("FileID"), | |
62 | map[string]string{ | |
63 | "name": "thumbsup", | |
64 | "file": "FileID", | |
65 | }, | |
66 | }, | |
67 | { | |
68 | "thumbsup", | |
69 | NewRefToComment("FileCommentID"), | |
70 | map[string]string{ | |
71 | "name": "thumbsup", | |
72 | "file_comment": "FileCommentID", | |
73 | }, | |
74 | }, | |
75 | } | |
76 | var rh *reactionsHandler | |
77 | http.HandleFunc("/reactions.add", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
78 | for i, test := range tests { | |
79 | rh = newReactionsHandler() | |
80 | err := api.AddReaction(test.name, test.ref) | |
81 | if err != nil { | |
82 | t.Fatalf("%d: Unexpected error: %s", i, err) | |
83 | } | |
84 | if !reflect.DeepEqual(rh.gotParams, test.wantParams) { | |
85 | t.Errorf("%d: Got params %#v, want %#v", i, rh.gotParams, test.wantParams) | |
86 | } | |
87 | } | |
88 | } | |
89 | ||
90 | func TestSlack_RemoveReaction(t *testing.T) { | |
91 | once.Do(startServer) | |
92 | SLACK_API = "http://" + serverAddr + "/" | |
93 | api := New("testing-token") | |
94 | tests := []struct { | |
95 | name string | |
96 | ref ItemRef | |
97 | wantParams map[string]string | |
98 | }{ | |
99 | { | |
100 | "thumbsup", | |
101 | NewRefToMessage("ChannelID", "123"), | |
102 | map[string]string{ | |
103 | "name": "thumbsup", | |
104 | "channel": "ChannelID", | |
105 | "timestamp": "123", | |
106 | }, | |
107 | }, | |
108 | { | |
109 | "thumbsup", | |
110 | NewRefToFile("FileID"), | |
111 | map[string]string{ | |
112 | "name": "thumbsup", | |
113 | "file": "FileID", | |
114 | }, | |
115 | }, | |
116 | { | |
117 | "thumbsup", | |
118 | NewRefToComment("FileCommentID"), | |
119 | map[string]string{ | |
120 | "name": "thumbsup", | |
121 | "file_comment": "FileCommentID", | |
122 | }, | |
123 | }, | |
124 | } | |
125 | var rh *reactionsHandler | |
126 | http.HandleFunc("/reactions.remove", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
127 | for i, test := range tests { | |
128 | rh = newReactionsHandler() | |
129 | err := api.RemoveReaction(test.name, test.ref) | |
130 | if err != nil { | |
131 | t.Fatalf("%d: Unexpected error: %s", i, err) | |
132 | } | |
133 | if !reflect.DeepEqual(rh.gotParams, test.wantParams) { | |
134 | t.Errorf("%d: Got params %#v, want %#v", i, rh.gotParams, test.wantParams) | |
135 | } | |
136 | } | |
137 | } | |
138 | ||
139 | func TestSlack_GetReactions(t *testing.T) { | |
140 | once.Do(startServer) | |
141 | SLACK_API = "http://" + serverAddr + "/" | |
142 | api := New("testing-token") | |
143 | tests := []struct { | |
144 | ref ItemRef | |
145 | params GetReactionsParameters | |
146 | wantParams map[string]string | |
147 | json string | |
148 | wantReactions []ItemReaction | |
149 | }{ | |
150 | { | |
151 | NewRefToMessage("ChannelID", "123"), | |
152 | GetReactionsParameters{}, | |
153 | map[string]string{ | |
154 | "channel": "ChannelID", | |
155 | "timestamp": "123", | |
156 | }, | |
157 | `{"ok": true, | |
158 | "type": "message", | |
159 | "message": { | |
160 | "reactions": [ | |
161 | { | |
162 | "name": "astonished", | |
163 | "count": 3, | |
164 | "users": [ "U1", "U2", "U3" ] | |
165 | }, | |
166 | { | |
167 | "name": "clock1", | |
168 | "count": 3, | |
169 | "users": [ "U1", "U2" ] | |
170 | } | |
171 | ] | |
172 | }}`, | |
173 | []ItemReaction{ | |
174 | ItemReaction{Name: "astonished", Count: 3, Users: []string{"U1", "U2", "U3"}}, | |
175 | ItemReaction{Name: "clock1", Count: 3, Users: []string{"U1", "U2"}}, | |
176 | }, | |
177 | }, | |
178 | { | |
179 | NewRefToFile("FileID"), | |
180 | GetReactionsParameters{Full: true}, | |
181 | map[string]string{ | |
182 | "file": "FileID", | |
183 | "full": "true", | |
184 | }, | |
185 | `{"ok": true, | |
186 | "type": "file", | |
187 | "file": { | |
188 | "reactions": [ | |
189 | { | |
190 | "name": "astonished", | |
191 | "count": 3, | |
192 | "users": [ "U1", "U2", "U3" ] | |
193 | }, | |
194 | { | |
195 | "name": "clock1", | |
196 | "count": 3, | |
197 | "users": [ "U1", "U2" ] | |
198 | } | |
199 | ] | |
200 | }}`, | |
201 | []ItemReaction{ | |
202 | ItemReaction{Name: "astonished", Count: 3, Users: []string{"U1", "U2", "U3"}}, | |
203 | ItemReaction{Name: "clock1", Count: 3, Users: []string{"U1", "U2"}}, | |
204 | }, | |
205 | }, | |
206 | { | |
207 | ||
208 | NewRefToComment("FileCommentID"), | |
209 | GetReactionsParameters{}, | |
210 | map[string]string{ | |
211 | "file_comment": "FileCommentID", | |
212 | }, | |
213 | `{"ok": true, | |
214 | "type": "file_comment", | |
215 | "file": {}, | |
216 | "comment": { | |
217 | "reactions": [ | |
218 | { | |
219 | "name": "astonished", | |
220 | "count": 3, | |
221 | "users": [ "U1", "U2", "U3" ] | |
222 | }, | |
223 | { | |
224 | "name": "clock1", | |
225 | "count": 3, | |
226 | "users": [ "U1", "U2" ] | |
227 | } | |
228 | ] | |
229 | }}`, | |
230 | []ItemReaction{ | |
231 | ItemReaction{Name: "astonished", Count: 3, Users: []string{"U1", "U2", "U3"}}, | |
232 | ItemReaction{Name: "clock1", Count: 3, Users: []string{"U1", "U2"}}, | |
233 | }, | |
234 | }, | |
235 | } | |
236 | var rh *reactionsHandler | |
237 | http.HandleFunc("/reactions.get", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
238 | for i, test := range tests { | |
239 | rh = newReactionsHandler() | |
240 | rh.response = test.json | |
241 | got, err := api.GetReactions(test.ref, test.params) | |
242 | if err != nil { | |
243 | t.Fatalf("%d: Unexpected error: %s", i, err) | |
244 | } | |
245 | if !reflect.DeepEqual(got, test.wantReactions) { | |
246 | t.Errorf("%d: Got reaction %#v, want %#v", i, got, test.wantReactions) | |
247 | } | |
248 | if !reflect.DeepEqual(rh.gotParams, test.wantParams) { | |
249 | t.Errorf("%d: Got params %#v, want %#v", i, rh.gotParams, test.wantParams) | |
250 | } | |
251 | } | |
252 | } | |
253 | ||
254 | func TestSlack_ListReactions(t *testing.T) { | |
255 | once.Do(startServer) | |
256 | SLACK_API = "http://" + serverAddr + "/" | |
257 | api := New("testing-token") | |
258 | rh := newReactionsHandler() | |
259 | http.HandleFunc("/reactions.list", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
260 | rh.response = `{"ok": true, | |
261 | "items": [ | |
262 | { | |
263 | "type": "message", | |
264 | "channel": "C1", | |
265 | "message": { | |
266 | "text": "hello", | |
267 | "reactions": [ | |
268 | { | |
269 | "name": "astonished", | |
270 | "count": 3, | |
271 | "users": [ "U1", "U2", "U3" ] | |
272 | }, | |
273 | { | |
274 | "name": "clock1", | |
275 | "count": 3, | |
276 | "users": [ "U1", "U2" ] | |
277 | } | |
278 | ] | |
279 | } | |
280 | }, | |
281 | { | |
282 | "type": "file", | |
283 | "file": { | |
284 | "name": "toy", | |
285 | "reactions": [ | |
286 | { | |
287 | "name": "clock1", | |
288 | "count": 3, | |
289 | "users": [ "U1", "U2" ] | |
290 | } | |
291 | ] | |
292 | } | |
293 | }, | |
294 | { | |
295 | "type": "file_comment", | |
296 | "file": { | |
297 | "name": "toy" | |
298 | }, | |
299 | "comment": { | |
300 | "comment": "cool toy", | |
301 | "reactions": [ | |
302 | { | |
303 | "name": "astonished", | |
304 | "count": 3, | |
305 | "users": [ "U1", "U2", "U3" ] | |
306 | } | |
307 | ] | |
308 | } | |
309 | } | |
310 | ], | |
311 | "paging": { | |
312 | "count": 100, | |
313 | "total": 4, | |
314 | "page": 1, | |
315 | "pages": 1 | |
316 | }}` | |
317 | want := []ReactedItem{ | |
318 | ReactedItem{ | |
319 | Item: NewMessageItem("C1", &Message{Msg: Msg{Text: "hello"}}), | |
320 | Reactions: []ItemReaction{ | |
321 | ItemReaction{Name: "astonished", Count: 3, Users: []string{"U1", "U2", "U3"}}, | |
322 | ItemReaction{Name: "clock1", Count: 3, Users: []string{"U1", "U2"}}, | |
323 | }, | |
324 | }, | |
325 | ReactedItem{ | |
326 | Item: NewFileItem(&File{Name: "toy"}), | |
327 | Reactions: []ItemReaction{ | |
328 | ItemReaction{Name: "clock1", Count: 3, Users: []string{"U1", "U2"}}, | |
329 | }, | |
330 | }, | |
331 | ReactedItem{ | |
332 | Item: NewFileCommentItem(&File{Name: "toy"}, &Comment{Comment: "cool toy"}), | |
333 | Reactions: []ItemReaction{ | |
334 | ItemReaction{Name: "astonished", Count: 3, Users: []string{"U1", "U2", "U3"}}, | |
335 | }, | |
336 | }, | |
337 | } | |
338 | wantParams := map[string]string{ | |
339 | "user": "UserID", | |
340 | "count": "200", | |
341 | "page": "2", | |
342 | "full": "true", | |
343 | } | |
344 | params := NewListReactionsParameters() | |
345 | params.UserId = "UserID" | |
346 | params.Count = 200 | |
347 | params.Page = 2 | |
348 | params.Full = true | |
349 | got, paging, err := api.ListReactions(params) | |
350 | if err != nil { | |
351 | t.Fatalf("Unexpected error: %s", err) | |
352 | } | |
353 | if !reflect.DeepEqual(got, want) { | |
354 | t.Errorf("Got reaction %#v, want %#v", got, want) | |
355 | for i, item := range got { | |
356 | fmt.Printf("Item %d, Type: %s\n", i, item.Type) | |
357 | fmt.Printf("Message %#v\n", item.Message) | |
358 | fmt.Printf("File %#v\n", item.File) | |
359 | fmt.Printf("Comment %#v\n", item.Comment) | |
360 | fmt.Printf("Reactions %#v\n", item.Reactions) | |
361 | } | |
362 | } | |
363 | if !reflect.DeepEqual(rh.gotParams, wantParams) { | |
364 | t.Errorf("Got params %#v, want %#v", rh.gotParams, wantParams) | |
365 | } | |
366 | if reflect.DeepEqual(paging, Paging{}) { | |
367 | t.Errorf("Want paging data, got empty struct") | |
368 | } | |
369 | } |
17 | 17 | Page int |
18 | 18 | } |
19 | 19 | |
20 | // TODO: Verify this. The whole thing is complicated. I don't like the way they mixed things | |
21 | // It also appears to be a bug in parsing the message | |
20 | // StarredItem is an item that has been starred. | |
22 | 21 | type StarredItem struct { |
23 | Type string `json:"type"` | |
24 | ChannelId string `json:"channel"` | |
25 | Message `json:"message,omitempty"` | |
26 | File `json:"file,omitempty"` | |
27 | Comment `json:"comment,omitempty"` | |
22 | Item | |
28 | 23 | } |
29 | 24 | |
30 | 25 | type starsResponseFull struct { |
52 | 47 | // } |
53 | 48 | // } |
54 | 49 | func (api *Slack) GetStarred(params StarsParameters) ([]StarredItem, *Paging, error) { |
55 | response := &starsResponseFull{} | |
56 | 50 | values := url.Values{ |
57 | 51 | "token": {api.config.token}, |
58 | 52 | } |
65 | 59 | if params.Page != DEFAULT_STARS_PAGE { |
66 | 60 | values.Add("page", strconv.Itoa(params.Page)) |
67 | 61 | } |
62 | response := &starsResponseFull{} | |
68 | 63 | err := parseResponse("stars.list", values, response, api.debug) |
69 | 64 | if err != nil { |
70 | 65 | return nil, nil, err |
5 | 5 | "testing" |
6 | 6 | ) |
7 | 7 | |
8 | var starsTests = struct { | |
9 | in []byte | |
10 | outItems []StarredItem | |
11 | outPaging *Paging | |
12 | }{ | |
13 | []byte(`{"ok": true, | |
8 | type starsHandler struct { | |
9 | gotParams map[string]string | |
10 | response string | |
11 | } | |
12 | ||
13 | func newStarsHandler() *starsHandler { | |
14 | return &starsHandler{ | |
15 | gotParams: make(map[string]string), | |
16 | response: `{ "ok": true }`, | |
17 | } | |
18 | } | |
19 | ||
20 | func (sh *starsHandler) accumulateFormValue(k string, r *http.Request) { | |
21 | if v := r.FormValue(k); v != "" { | |
22 | sh.gotParams[k] = v | |
23 | } | |
24 | } | |
25 | ||
26 | func (sh *starsHandler) handler(w http.ResponseWriter, r *http.Request) { | |
27 | sh.accumulateFormValue("user", r) | |
28 | sh.accumulateFormValue("count", r) | |
29 | sh.accumulateFormValue("page", r) | |
30 | w.Header().Set("Content-Type", "application/json") | |
31 | w.Write([]byte(sh.response)) | |
32 | } | |
33 | ||
34 | func TestSlack_GetStarred(t *testing.T) { | |
35 | once.Do(startServer) | |
36 | SLACK_API = "http://" + serverAddr + "/" | |
37 | api := New("testing-token") | |
38 | tests := []struct { | |
39 | params StarsParameters | |
40 | wantParams map[string]string | |
41 | json string | |
42 | starredItems []StarredItem | |
43 | paging *Paging | |
44 | }{ | |
45 | { | |
46 | StarsParameters{ | |
47 | User: "U1", | |
48 | Count: 10, | |
49 | Page: 100, | |
50 | }, | |
51 | map[string]string{ | |
52 | "user": "U1", | |
53 | "count": "10", | |
54 | "page": "100", | |
55 | }, | |
56 | `{"ok": true, | |
14 | 57 | "items": [ |
15 | 58 | { |
16 | 59 | "type": "message", |
17 | "channel": "C2147483705" | |
60 | "channel": "C2147483705", | |
61 | "message": { | |
62 | "text": "hello" | |
63 | } | |
18 | 64 | }, |
19 | 65 | { |
20 | "type": "file" | |
66 | "type": "file", | |
67 | "file": { | |
68 | "name": "toy" | |
69 | } | |
21 | 70 | }, |
22 | 71 | { |
23 | "type": "file_comment" | |
72 | "type": "file_comment", | |
73 | "file": { | |
74 | "name": "toy" | |
75 | }, | |
76 | "comment": { | |
77 | "comment": "nice" | |
78 | } | |
24 | 79 | }, |
25 | 80 | { |
26 | 81 | "type": "channel", |
27 | 82 | "channel": "C2147483705" |
83 | }, | |
84 | { | |
85 | "type": "im", | |
86 | "channel": "D1" | |
87 | }, | |
88 | { | |
89 | "type": "group", | |
90 | "channel": "G1" | |
28 | 91 | } |
29 | ||
30 | 92 | ], |
31 | 93 | "paging": { |
32 | 94 | "count": 100, |
33 | 95 | "total": 4, |
34 | 96 | "page": 1, |
35 | 97 | "pages": 1 |
36 | }}`), | |
37 | []StarredItem{ | |
38 | { | |
39 | Type: "message", | |
40 | ChannelId: "C2147483705", | |
98 | }}`, | |
99 | []StarredItem{ | |
100 | {Item: NewMessageItem("C2147483705", &Message{Msg: Msg{Text: "hello"}})}, | |
101 | {Item: NewFileItem(&File{Name: "toy"})}, | |
102 | {Item: NewFileCommentItem(&File{Name: "toy"}, &Comment{Comment: "nice"})}, | |
103 | {Item: NewChannelItem("C2147483705")}, | |
104 | {Item: NewIMItem("D1")}, | |
105 | {Item: NewGroupItem("G1")}, | |
106 | }, | |
107 | &Paging{ | |
108 | Count: 100, | |
109 | Total: 4, | |
110 | Page: 1, | |
111 | Pages: 1, | |
112 | }, | |
41 | 113 | }, |
42 | { | |
43 | Type: "file", | |
44 | }, | |
45 | { | |
46 | Type: "file_comment", | |
47 | }, | |
48 | { | |
49 | Type: "channel", | |
50 | ChannelId: "C2147483705", | |
51 | }, | |
52 | }, | |
53 | &Paging{ | |
54 | Count: 100, | |
55 | Total: 4, | |
56 | Page: 1, | |
57 | Pages: 1, | |
58 | }, | |
59 | } | |
60 | ||
61 | func getStarredHandler(rw http.ResponseWriter, r *http.Request) { | |
62 | rw.Header().Set("Content-Type", "application/json") | |
63 | // XXX: I stripped the actual content just to test this test Oo | |
64 | // At some point I should add valid content here | |
65 | rw.Write(starsTests.in) | |
66 | } | |
67 | ||
68 | func TestGetStarred(t *testing.T) { | |
69 | http.HandleFunc("/stars.list", getStarredHandler) | |
70 | once.Do(startServer) | |
71 | SLACK_API = "http://" + serverAddr + "/" | |
72 | api := New("testing-token") | |
73 | response_items, response_paging, err := api.GetStarred(NewStarsParameters()) | |
74 | if err != nil { | |
75 | t.Errorf("Unexpected error: %s", err) | |
76 | return | |
77 | 114 | } |
78 | eq := reflect.DeepEqual(response_items, starsTests.outItems) | |
79 | if !eq { | |
80 | t.Errorf("got %v; want %v", response_items, starsTests.outItems) | |
81 | } | |
82 | eq = reflect.DeepEqual(response_paging, starsTests.outPaging) | |
83 | if !eq { | |
84 | t.Errorf("got %v; want %v", response_paging, starsTests.outPaging) | |
115 | var sh *starsHandler | |
116 | http.HandleFunc("/stars.list", func(w http.ResponseWriter, r *http.Request) { sh.handler(w, r) }) | |
117 | for i, test := range tests { | |
118 | sh = newStarsHandler() | |
119 | sh.response = test.json | |
120 | response_items, response_paging, err := api.GetStarred(test.params) | |
121 | if err != nil { | |
122 | t.Fatalf("%d Unexpected error: %s", i, err) | |
123 | } | |
124 | if !reflect.DeepEqual(sh.gotParams, test.wantParams) { | |
125 | t.Errorf("%d got %v; want %v", i, sh.gotParams, test.wantParams) | |
126 | } | |
127 | if !reflect.DeepEqual(response_items, test.starredItems) { | |
128 | t.Errorf("%d got %v; want %v", i, response_items, test.starredItems) | |
129 | } | |
130 | if !reflect.DeepEqual(response_paging, test.paging) { | |
131 | t.Errorf("%d got %v; want %v", i, response_paging, test.paging) | |
132 | } | |
85 | 133 | } |
86 | 134 | } |