Add pinning support
Norberto Lopes
8 years ago
0 | package main | |
1 | ||
2 | import ( | |
3 | "flag" | |
4 | "fmt" | |
5 | ||
6 | "github.com/nlopes/slack" | |
7 | ) | |
8 | ||
9 | /* | |
10 | WARNING: This example is destructive in the sense that it create a channel called testpinning | |
11 | */ | |
12 | func main() { | |
13 | var ( | |
14 | apiToken string | |
15 | debug bool | |
16 | ) | |
17 | ||
18 | flag.StringVar(&apiToken, "token", "YOUR_TOKEN_HERE", "Your Slack API Token") | |
19 | flag.BoolVar(&debug, "debug", false, "Show JSON output") | |
20 | flag.Parse() | |
21 | ||
22 | api := slack.New(apiToken) | |
23 | if debug { | |
24 | api.SetDebug(true) | |
25 | } | |
26 | ||
27 | var ( | |
28 | postAsUserName string | |
29 | postAsUserID string | |
30 | postToChannelID string | |
31 | ) | |
32 | ||
33 | // Find the user to post as. | |
34 | authTest, err := api.AuthTest() | |
35 | if err != nil { | |
36 | fmt.Printf("Error getting channels: %s\n", err) | |
37 | return | |
38 | } | |
39 | ||
40 | channelName := "testpinning" | |
41 | ||
42 | // Post as the authenticated user. | |
43 | postAsUserName = authTest.User | |
44 | postAsUserID = authTest.UserID | |
45 | ||
46 | // Create a temporary channel | |
47 | channel, err := api.CreateChannel(channelName) | |
48 | ||
49 | if err != nil { | |
50 | // If the channel exists, that means we just need to unarchive it | |
51 | if err.Error() == "name_taken" { | |
52 | err = nil | |
53 | channels, err := api.GetChannels(false) | |
54 | if err != nil { | |
55 | fmt.Println("Could not retrieve channels") | |
56 | return | |
57 | } | |
58 | for _, archivedChannel := range channels { | |
59 | if archivedChannel.Name == channelName { | |
60 | if archivedChannel.IsArchived { | |
61 | err = api.UnarchiveChannel(archivedChannel.ID) | |
62 | if err != nil { | |
63 | fmt.Printf("Could not unarchive %s: %s\n", archivedChannel.ID, err) | |
64 | return | |
65 | } | |
66 | } | |
67 | channel = &archivedChannel | |
68 | break | |
69 | } | |
70 | } | |
71 | } | |
72 | if err != nil { | |
73 | fmt.Printf("Error setting test channel for pinning: %s\n", err) | |
74 | return | |
75 | } | |
76 | } | |
77 | postToChannelID = channel.ID | |
78 | ||
79 | fmt.Printf("Posting as %s (%s) in channel %s\n", postAsUserName, postAsUserID, postToChannelID) | |
80 | ||
81 | // Post a message. | |
82 | postParams := slack.PostMessageParameters{} | |
83 | channelID, timestamp, err := api.PostMessage(postToChannelID, "Is this any good?", postParams) | |
84 | if err != nil { | |
85 | fmt.Printf("Error posting message: %s\n", err) | |
86 | return | |
87 | } | |
88 | ||
89 | // Grab a reference to the message. | |
90 | msgRef := slack.NewRefToMessage(channelID, timestamp) | |
91 | ||
92 | // Add message pin to channel | |
93 | if err := api.AddPin(channelID, msgRef); err != nil { | |
94 | fmt.Printf("Error adding pin: %s\n", err) | |
95 | return | |
96 | } | |
97 | ||
98 | // List all of the users pins. | |
99 | listPins, _, err := api.ListPins(channelID, slack.NewListPinsParameters()) | |
100 | if err != nil { | |
101 | fmt.Printf("Error listing pins: %s\n", err) | |
102 | return | |
103 | } | |
104 | fmt.Printf("\n") | |
105 | fmt.Printf("All pins by %s...\n", authTest.User) | |
106 | for _, item := range listPins { | |
107 | fmt.Printf(" > Item type: %s\n", item.Type) | |
108 | } | |
109 | ||
110 | // Remove the pin. | |
111 | err = api.RemovePin(channelID, msgRef) | |
112 | if err != nil { | |
113 | fmt.Printf("Error remove pin: %s\n", err) | |
114 | return | |
115 | } | |
116 | ||
117 | if err = api.ArchiveChannel(channelID); err != nil { | |
118 | fmt.Printf("Error archiving channel: %s\n", err) | |
119 | return | |
120 | } | |
121 | ||
122 | } |
0 | package slack | |
1 | ||
2 | import ( | |
3 | "errors" | |
4 | "net/url" | |
5 | "strconv" | |
6 | ) | |
7 | ||
8 | const ( | |
9 | DEFAULT_PINS_COUNT = 100 | |
10 | DEFAULT_PINS_PAGE = 1 | |
11 | ) | |
12 | ||
13 | // ListPinsParameters contains all the optional parameters for the pins.list call | |
14 | type ListPinsParameters struct { | |
15 | Count int | |
16 | Page int | |
17 | } | |
18 | ||
19 | // NewListPinsParameters initializes the inputs to find all pins | |
20 | // performed by a user. | |
21 | func NewListPinsParameters() ListPinsParameters { | |
22 | return ListPinsParameters{ | |
23 | Count: DEFAULT_PINS_COUNT, | |
24 | Page: DEFAULT_PINS_PAGE, | |
25 | } | |
26 | } | |
27 | ||
28 | type listPinsResponseFull struct { | |
29 | Items []Item | |
30 | Paging `json:"paging"` | |
31 | SlackResponse | |
32 | } | |
33 | ||
34 | // AddPin pins an item in a channel | |
35 | func (api *Client) AddPin(channel string, item ItemRef) error { | |
36 | values := url.Values{ | |
37 | "channel": {channel}, | |
38 | "token": {api.config.token}, | |
39 | } | |
40 | if item.Timestamp != "" { | |
41 | values.Set("timestamp", string(item.Timestamp)) | |
42 | } | |
43 | if item.File != "" { | |
44 | values.Set("file", string(item.File)) | |
45 | } | |
46 | if item.Comment != "" { | |
47 | values.Set("file_comment", string(item.Comment)) | |
48 | } | |
49 | response := &SlackResponse{} | |
50 | if err := post("pins.add", values, response, api.debug); err != nil { | |
51 | return err | |
52 | } | |
53 | if !response.Ok { | |
54 | return errors.New(response.Error) | |
55 | } | |
56 | return nil | |
57 | } | |
58 | ||
59 | // RemovePin un-pins an item from a channel | |
60 | func (api *Client) RemovePin(channel string, item ItemRef) error { | |
61 | values := url.Values{ | |
62 | "channel": {channel}, | |
63 | "token": {api.config.token}, | |
64 | } | |
65 | if item.Timestamp != "" { | |
66 | values.Set("timestamp", string(item.Timestamp)) | |
67 | } | |
68 | if item.File != "" { | |
69 | values.Set("file", string(item.File)) | |
70 | } | |
71 | if item.Comment != "" { | |
72 | values.Set("file_comment", string(item.Comment)) | |
73 | } | |
74 | response := &SlackResponse{} | |
75 | if err := post("pins.remove", values, response, api.debug); err != nil { | |
76 | return err | |
77 | } | |
78 | if !response.Ok { | |
79 | return errors.New(response.Error) | |
80 | } | |
81 | return nil | |
82 | } | |
83 | ||
84 | // ListPins returns information about the items a user reacted to. | |
85 | func (api *Client) ListPins(channel string, params ListPinsParameters) ([]Item, *Paging, error) { | |
86 | values := url.Values{ | |
87 | "channel": {channel}, | |
88 | "token": {api.config.token}, | |
89 | } | |
90 | if params.Count != DEFAULT_PINS_COUNT { | |
91 | values.Add("count", strconv.Itoa(params.Count)) | |
92 | } | |
93 | if params.Page != DEFAULT_PINS_PAGE { | |
94 | values.Add("page", strconv.Itoa(params.Page)) | |
95 | } | |
96 | response := &listPinsResponseFull{} | |
97 | err := post("pins.list", values, response, api.debug) | |
98 | if err != nil { | |
99 | return nil, nil, err | |
100 | } | |
101 | if !response.Ok { | |
102 | return nil, nil, errors.New(response.Error) | |
103 | } | |
104 | return response.Items, &response.Paging, nil | |
105 | } |
0 | package slack | |
1 | ||
2 | import ( | |
3 | "fmt" | |
4 | "net/http" | |
5 | "reflect" | |
6 | "testing" | |
7 | ) | |
8 | ||
9 | type pinsHandler struct { | |
10 | gotParams map[string]string | |
11 | response string | |
12 | } | |
13 | ||
14 | func newPinsHandler() *pinsHandler { | |
15 | return &pinsHandler{ | |
16 | gotParams: make(map[string]string), | |
17 | response: `{ "ok": true }`, | |
18 | } | |
19 | } | |
20 | ||
21 | func (rh *pinsHandler) accumulateFormValue(k string, r *http.Request) { | |
22 | if v := r.FormValue(k); v != "" { | |
23 | rh.gotParams[k] = v | |
24 | } | |
25 | } | |
26 | ||
27 | func (rh *pinsHandler) 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("page", r) | |
33 | rh.accumulateFormValue("timestamp", r) | |
34 | w.Header().Set("Content-Type", "application/json") | |
35 | w.Write([]byte(rh.response)) | |
36 | } | |
37 | ||
38 | func TestSlack_AddPin(t *testing.T) { | |
39 | once.Do(startServer) | |
40 | SLACK_API = "http://" + serverAddr + "/" | |
41 | api := New("testing-token") | |
42 | tests := []struct { | |
43 | channel string | |
44 | ref ItemRef | |
45 | wantParams map[string]string | |
46 | }{ | |
47 | { | |
48 | "ChannelID", | |
49 | NewRefToMessage("ChannelID", "123"), | |
50 | map[string]string{ | |
51 | "channel": "ChannelID", | |
52 | "timestamp": "123", | |
53 | }, | |
54 | }, | |
55 | { | |
56 | "ChannelID", | |
57 | NewRefToFile("FileID"), | |
58 | map[string]string{ | |
59 | "channel": "ChannelID", | |
60 | "file": "FileID", | |
61 | }, | |
62 | }, | |
63 | { | |
64 | "ChannelID", | |
65 | NewRefToComment("FileCommentID"), | |
66 | map[string]string{ | |
67 | "channel": "ChannelID", | |
68 | "file_comment": "FileCommentID", | |
69 | }, | |
70 | }, | |
71 | } | |
72 | var rh *pinsHandler | |
73 | http.HandleFunc("/pins.add", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
74 | for i, test := range tests { | |
75 | rh = newPinsHandler() | |
76 | err := api.AddPin(test.channel, test.ref) | |
77 | if err != nil { | |
78 | t.Fatalf("%d: Unexpected error: %s", i, err) | |
79 | } | |
80 | if !reflect.DeepEqual(rh.gotParams, test.wantParams) { | |
81 | t.Errorf("%d: Got params %#v, want %#v", i, rh.gotParams, test.wantParams) | |
82 | } | |
83 | } | |
84 | } | |
85 | ||
86 | func TestSlack_RemovePin(t *testing.T) { | |
87 | once.Do(startServer) | |
88 | SLACK_API = "http://" + serverAddr + "/" | |
89 | api := New("testing-token") | |
90 | tests := []struct { | |
91 | channel string | |
92 | ref ItemRef | |
93 | wantParams map[string]string | |
94 | }{ | |
95 | { | |
96 | "ChannelID", | |
97 | NewRefToMessage("ChannelID", "123"), | |
98 | map[string]string{ | |
99 | "channel": "ChannelID", | |
100 | "timestamp": "123", | |
101 | }, | |
102 | }, | |
103 | { | |
104 | "ChannelID", | |
105 | NewRefToFile("FileID"), | |
106 | map[string]string{ | |
107 | "channel": "ChannelID", | |
108 | "file": "FileID", | |
109 | }, | |
110 | }, | |
111 | { | |
112 | "ChannelID", | |
113 | NewRefToComment("FileCommentID"), | |
114 | map[string]string{ | |
115 | "channel": "ChannelID", | |
116 | "file_comment": "FileCommentID", | |
117 | }, | |
118 | }, | |
119 | } | |
120 | var rh *pinsHandler | |
121 | http.HandleFunc("/pins.remove", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
122 | for i, test := range tests { | |
123 | rh = newPinsHandler() | |
124 | err := api.RemovePin(test.channel, test.ref) | |
125 | if err != nil { | |
126 | t.Fatalf("%d: Unexpected error: %s", i, err) | |
127 | } | |
128 | if !reflect.DeepEqual(rh.gotParams, test.wantParams) { | |
129 | t.Errorf("%d: Got params %#v, want %#v", i, rh.gotParams, test.wantParams) | |
130 | } | |
131 | } | |
132 | } | |
133 | ||
134 | func TestSlack_ListPins(t *testing.T) { | |
135 | once.Do(startServer) | |
136 | SLACK_API = "http://" + serverAddr + "/" | |
137 | api := New("testing-token") | |
138 | rh := newPinsHandler() | |
139 | http.HandleFunc("/pins.list", func(w http.ResponseWriter, r *http.Request) { rh.handler(w, r) }) | |
140 | rh.response = `{"ok": true, | |
141 | "items": [ | |
142 | { | |
143 | "type": "message", | |
144 | "channel": "C1", | |
145 | "message": { | |
146 | "text": "hello", | |
147 | "reactions": [ | |
148 | { | |
149 | "name": "astonished", | |
150 | "count": 3, | |
151 | "users": [ "U1", "U2", "U3" ] | |
152 | }, | |
153 | { | |
154 | "name": "clock1", | |
155 | "count": 3, | |
156 | "users": [ "U1", "U2" ] | |
157 | } | |
158 | ] | |
159 | } | |
160 | }, | |
161 | { | |
162 | "type": "file", | |
163 | "file": { | |
164 | "name": "toy", | |
165 | "reactions": [ | |
166 | { | |
167 | "name": "clock1", | |
168 | "count": 3, | |
169 | "users": [ "U1", "U2" ] | |
170 | } | |
171 | ] | |
172 | } | |
173 | }, | |
174 | { | |
175 | "type": "file_comment", | |
176 | "file": { | |
177 | "name": "toy" | |
178 | }, | |
179 | "comment": { | |
180 | "comment": "cool toy", | |
181 | "reactions": [ | |
182 | { | |
183 | "name": "astonished", | |
184 | "count": 3, | |
185 | "users": [ "U1", "U2", "U3" ] | |
186 | } | |
187 | ] | |
188 | } | |
189 | } | |
190 | ], | |
191 | "paging": { | |
192 | "count": 100, | |
193 | "total": 4, | |
194 | "page": 1, | |
195 | "pages": 1 | |
196 | }}` | |
197 | want := []Item{ | |
198 | NewMessageItem("C1", &Message{Msg: Msg{Text: "hello"}}), | |
199 | NewFileItem(&File{Name: "toy"}), | |
200 | NewFileCommentItem(&File{Name: "toy"}, &Comment{Comment: "cool toy"}), | |
201 | } | |
202 | wantParams := map[string]string{ | |
203 | "channel": "ChannelID", | |
204 | "count": "200", | |
205 | "page": "2", | |
206 | } | |
207 | params := NewListPinsParameters() | |
208 | params.Count = 200 | |
209 | params.Page = 2 | |
210 | got, paging, err := api.ListPins("ChannelID", params) | |
211 | if err != nil { | |
212 | t.Fatalf("Unexpected error: %s", err) | |
213 | } | |
214 | if !reflect.DeepEqual(got, want) { | |
215 | t.Errorf("Got Pins %#v, want %#v", got, want) | |
216 | for i, item := range got { | |
217 | fmt.Printf("Item %d, Type: %s\n", i, item.Type) | |
218 | fmt.Printf("Message %#v\n", item.Message) | |
219 | fmt.Printf("File %#v\n", item.File) | |
220 | fmt.Printf("Comment %#v\n", item.Comment) | |
221 | } | |
222 | } | |
223 | if !reflect.DeepEqual(rh.gotParams, wantParams) { | |
224 | t.Errorf("Got params %#v, want %#v", rh.gotParams, wantParams) | |
225 | } | |
226 | if reflect.DeepEqual(paging, Paging{}) { | |
227 | t.Errorf("Want paging data, got empty struct") | |
228 | } | |
229 | } |
387 | 387 | "file_comment_edited": FileCommentEditedEvent{}, |
388 | 388 | "file_comment_deleted": FileCommentDeletedEvent{}, |
389 | 389 | |
390 | "pin_added": PinAddedEvent{}, | |
391 | "pin_removed": PinRemovedEvent{}, | |
392 | ||
390 | 393 | "star_added": StarAddedEvent{}, |
391 | 394 | "star_removed": StarRemovedEvent{}, |
392 | 395 |
0 | package slack | |
1 | ||
2 | type pinEvent struct { | |
3 | Type string `json:"type"` | |
4 | User string `json:"user"` | |
5 | Item Item `json:"item"` | |
6 | Channel string `json:"channel_id"` | |
7 | EventTimestamp JSONTimeString `json:"event_ts"` | |
8 | HasPins bool `json:"has_pins,omitempty"` | |
9 | } | |
10 | ||
11 | // PinAddedEvent represents the Pin added event | |
12 | type PinAddedEvent pinEvent | |
13 | ||
14 | // PinRemovedEvent represents the Pin removed event | |
15 | type PinRemovedEvent pinEvent |