1package slack
2
3import (
4	"context"
5	"net/url"
6	"strconv"
7)
8
9// Group contains all the information for a group
10type Group struct {
11	GroupConversation
12	IsGroup bool `json:"is_group"`
13}
14
15type groupResponseFull struct {
16	Group          Group   `json:"group"`
17	Groups         []Group `json:"groups"`
18	Purpose        string  `json:"purpose"`
19	Topic          string  `json:"topic"`
20	NotInGroup     bool    `json:"not_in_group"`
21	NoOp           bool    `json:"no_op"`
22	AlreadyClosed  bool    `json:"already_closed"`
23	AlreadyOpen    bool    `json:"already_open"`
24	AlreadyInGroup bool    `json:"already_in_group"`
25	Channel        Channel `json:"channel"`
26	History
27	SlackResponse
28}
29
30func (api *Client) groupRequest(ctx context.Context, path string, values url.Values) (*groupResponseFull, error) {
31	response := &groupResponseFull{}
32	err := api.postMethod(ctx, path, values, response)
33	if err != nil {
34		return nil, err
35	}
36
37	return response, response.Err()
38}
39
40// ArchiveGroup archives a private group
41func (api *Client) ArchiveGroup(group string) error {
42	return api.ArchiveGroupContext(context.Background(), group)
43}
44
45// ArchiveGroupContext archives a private group
46func (api *Client) ArchiveGroupContext(ctx context.Context, group string) error {
47	values := url.Values{
48		"token":   {api.token},
49		"channel": {group},
50	}
51
52	_, err := api.groupRequest(ctx, "groups.archive", values)
53	return err
54}
55
56// UnarchiveGroup unarchives a private group
57func (api *Client) UnarchiveGroup(group string) error {
58	return api.UnarchiveGroupContext(context.Background(), group)
59}
60
61// UnarchiveGroupContext unarchives a private group
62func (api *Client) UnarchiveGroupContext(ctx context.Context, group string) error {
63	values := url.Values{
64		"token":   {api.token},
65		"channel": {group},
66	}
67
68	_, err := api.groupRequest(ctx, "groups.unarchive", values)
69	return err
70}
71
72// CreateGroup creates a private group
73func (api *Client) CreateGroup(group string) (*Group, error) {
74	return api.CreateGroupContext(context.Background(), group)
75}
76
77// CreateGroupContext creates a private group
78func (api *Client) CreateGroupContext(ctx context.Context, group string) (*Group, error) {
79	values := url.Values{
80		"token": {api.token},
81		"name":  {group},
82	}
83
84	response, err := api.groupRequest(ctx, "groups.create", values)
85	if err != nil {
86		return nil, err
87	}
88	return &response.Group, nil
89}
90
91// CreateChildGroup creates a new private group archiving the old one
92// This method takes an existing private group and performs the following steps:
93//   1. Renames the existing group (from "example" to "example-archived").
94//   2. Archives the existing group.
95//   3. Creates a new group with the name of the existing group.
96//   4. Adds all members of the existing group to the new group.
97func (api *Client) CreateChildGroup(group string) (*Group, error) {
98	return api.CreateChildGroupContext(context.Background(), group)
99}
100
101// CreateChildGroupContext creates a new private group archiving the old one with a custom context
102// For more information see CreateChildGroup
103func (api *Client) CreateChildGroupContext(ctx context.Context, group string) (*Group, error) {
104	values := url.Values{
105		"token":   {api.token},
106		"channel": {group},
107	}
108
109	response, err := api.groupRequest(ctx, "groups.createChild", values)
110	if err != nil {
111		return nil, err
112	}
113	return &response.Group, nil
114}
115
116// GetGroupHistory fetches all the history for a private group
117func (api *Client) GetGroupHistory(group string, params HistoryParameters) (*History, error) {
118	return api.GetGroupHistoryContext(context.Background(), group, params)
119}
120
121// GetGroupHistoryContext fetches all the history for a private group with a custom context
122func (api *Client) GetGroupHistoryContext(ctx context.Context, group string, params HistoryParameters) (*History, error) {
123	values := url.Values{
124		"token":   {api.token},
125		"channel": {group},
126	}
127	if params.Latest != DEFAULT_HISTORY_LATEST {
128		values.Add("latest", params.Latest)
129	}
130	if params.Oldest != DEFAULT_HISTORY_OLDEST {
131		values.Add("oldest", params.Oldest)
132	}
133	if params.Count != DEFAULT_HISTORY_COUNT {
134		values.Add("count", strconv.Itoa(params.Count))
135	}
136	if params.Inclusive != DEFAULT_HISTORY_INCLUSIVE {
137		if params.Inclusive {
138			values.Add("inclusive", "1")
139		} else {
140			values.Add("inclusive", "0")
141		}
142	}
143	if params.Unreads != DEFAULT_HISTORY_UNREADS {
144		if params.Unreads {
145			values.Add("unreads", "1")
146		} else {
147			values.Add("unreads", "0")
148		}
149	}
150
151	response, err := api.groupRequest(ctx, "groups.history", values)
152	if err != nil {
153		return nil, err
154	}
155	return &response.History, nil
156}
157
158// InviteUserToGroup invites a specific user to a private group
159func (api *Client) InviteUserToGroup(group, user string) (*Group, bool, error) {
160	return api.InviteUserToGroupContext(context.Background(), group, user)
161}
162
163// InviteUserToGroupContext invites a specific user to a private group with a custom context
164func (api *Client) InviteUserToGroupContext(ctx context.Context, group, user string) (*Group, bool, error) {
165	values := url.Values{
166		"token":   {api.token},
167		"channel": {group},
168		"user":    {user},
169	}
170
171	response, err := api.groupRequest(ctx, "groups.invite", values)
172	if err != nil {
173		return nil, false, err
174	}
175	return &response.Group, response.AlreadyInGroup, nil
176}
177
178// LeaveGroup makes authenticated user leave the group
179func (api *Client) LeaveGroup(group string) error {
180	return api.LeaveGroupContext(context.Background(), group)
181}
182
183// LeaveGroupContext makes authenticated user leave the group with a custom context
184func (api *Client) LeaveGroupContext(ctx context.Context, group string) (err error) {
185	values := url.Values{
186		"token":   {api.token},
187		"channel": {group},
188	}
189
190	_, err = api.groupRequest(ctx, "groups.leave", values)
191	return err
192}
193
194// KickUserFromGroup kicks a user from a group
195func (api *Client) KickUserFromGroup(group, user string) error {
196	return api.KickUserFromGroupContext(context.Background(), group, user)
197}
198
199// KickUserFromGroupContext kicks a user from a group with a custom context
200func (api *Client) KickUserFromGroupContext(ctx context.Context, group, user string) (err error) {
201	values := url.Values{
202		"token":   {api.token},
203		"channel": {group},
204		"user":    {user},
205	}
206
207	_, err = api.groupRequest(ctx, "groups.kick", values)
208	return err
209}
210
211// GetGroups retrieves all groups
212func (api *Client) GetGroups(excludeArchived bool) ([]Group, error) {
213	return api.GetGroupsContext(context.Background(), excludeArchived)
214}
215
216// GetGroupsContext retrieves all groups with a custom context
217func (api *Client) GetGroupsContext(ctx context.Context, excludeArchived bool) ([]Group, error) {
218	values := url.Values{
219		"token": {api.token},
220	}
221	if excludeArchived {
222		values.Add("exclude_archived", "1")
223	}
224
225	response, err := api.groupRequest(ctx, "groups.list", values)
226	if err != nil {
227		return nil, err
228	}
229	return response.Groups, nil
230}
231
232// GetGroupInfo retrieves the given group
233func (api *Client) GetGroupInfo(group string) (*Group, error) {
234	return api.GetGroupInfoContext(context.Background(), group)
235}
236
237// GetGroupInfoContext retrieves the given group with a custom context
238func (api *Client) GetGroupInfoContext(ctx context.Context, group string) (*Group, error) {
239	values := url.Values{
240		"token":          {api.token},
241		"channel":        {group},
242		"include_locale": {strconv.FormatBool(true)},
243	}
244
245	response, err := api.groupRequest(ctx, "groups.info", values)
246	if err != nil {
247		return nil, err
248	}
249	return &response.Group, nil
250}
251
252// SetGroupReadMark sets the read mark on a private group
253// Clients should try to avoid making this call too often. When needing to mark a read position, a client should set a
254// timer before making the call. In this way, any further updates needed during the timeout will not generate extra
255// calls (just one per channel). This is useful for when reading scroll-back history, or following a busy live
256// channel. A timeout of 5 seconds is a good starting point. Be sure to flush these calls on shutdown/logout.
257func (api *Client) SetGroupReadMark(group, ts string) error {
258	return api.SetGroupReadMarkContext(context.Background(), group, ts)
259}
260
261// SetGroupReadMarkContext sets the read mark on a private group with a custom context
262// For more details see SetGroupReadMark
263func (api *Client) SetGroupReadMarkContext(ctx context.Context, group, ts string) (err error) {
264	values := url.Values{
265		"token":   {api.token},
266		"channel": {group},
267		"ts":      {ts},
268	}
269
270	_, err = api.groupRequest(ctx, "groups.mark", values)
271	return err
272}
273
274// OpenGroup opens a private group
275func (api *Client) OpenGroup(group string) (bool, bool, error) {
276	return api.OpenGroupContext(context.Background(), group)
277}
278
279// OpenGroupContext opens a private group with a custom context
280func (api *Client) OpenGroupContext(ctx context.Context, group string) (bool, bool, error) {
281	values := url.Values{
282		"token":   {api.token},
283		"channel": {group},
284	}
285
286	response, err := api.groupRequest(ctx, "groups.open", values)
287	if err != nil {
288		return false, false, err
289	}
290	return response.NoOp, response.AlreadyOpen, nil
291}
292
293// RenameGroup renames a group
294// XXX: They return a channel, not a group. What is this crap? :(
295// Inconsistent api it seems.
296func (api *Client) RenameGroup(group, name string) (*Channel, error) {
297	return api.RenameGroupContext(context.Background(), group, name)
298}
299
300// RenameGroupContext renames a group with a custom context
301func (api *Client) RenameGroupContext(ctx context.Context, group, name string) (*Channel, error) {
302	values := url.Values{
303		"token":   {api.token},
304		"channel": {group},
305		"name":    {name},
306	}
307
308	// XXX: the created entry in this call returns a string instead of a number
309	// so I may have to do some workaround to solve it.
310	response, err := api.groupRequest(ctx, "groups.rename", values)
311	if err != nil {
312		return nil, err
313	}
314	return &response.Channel, nil
315}
316
317// SetGroupPurpose sets the group purpose
318func (api *Client) SetGroupPurpose(group, purpose string) (string, error) {
319	return api.SetGroupPurposeContext(context.Background(), group, purpose)
320}
321
322// SetGroupPurposeContext sets the group purpose with a custom context
323func (api *Client) SetGroupPurposeContext(ctx context.Context, group, purpose string) (string, error) {
324	values := url.Values{
325		"token":   {api.token},
326		"channel": {group},
327		"purpose": {purpose},
328	}
329
330	response, err := api.groupRequest(ctx, "groups.setPurpose", values)
331	if err != nil {
332		return "", err
333	}
334	return response.Purpose, nil
335}
336
337// SetGroupTopic sets the group topic
338func (api *Client) SetGroupTopic(group, topic string) (string, error) {
339	return api.SetGroupTopicContext(context.Background(), group, topic)
340}
341
342// SetGroupTopicContext sets the group topic with a custom context
343func (api *Client) SetGroupTopicContext(ctx context.Context, group, topic string) (string, error) {
344	values := url.Values{
345		"token":   {api.token},
346		"channel": {group},
347		"topic":   {topic},
348	}
349
350	response, err := api.groupRequest(ctx, "groups.setTopic", values)
351	if err != nil {
352		return "", err
353	}
354	return response.Topic, nil
355}
356