1// Copyright 2016 The go-github AUTHORS. All rights reserved.
2//
3// Use of this source code is governed by a BSD-style
4// license that can be found in the LICENSE file.
5
6package github
7
8import (
9	"context"
10	"fmt"
11	"net/http"
12)
13
14// ReactionsService provides access to the reactions-related functions in the
15// GitHub API.
16//
17// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/
18type ReactionsService service
19
20// Reaction represents a GitHub reaction.
21type Reaction struct {
22	// ID is the Reaction ID.
23	ID     *int64  `json:"id,omitempty"`
24	User   *User   `json:"user,omitempty"`
25	NodeID *string `json:"node_id,omitempty"`
26	// Content is the type of reaction.
27	// Possible values are:
28	//     "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
29	Content *string `json:"content,omitempty"`
30}
31
32// Reactions represents a summary of GitHub reactions.
33type Reactions struct {
34	TotalCount *int    `json:"total_count,omitempty"`
35	PlusOne    *int    `json:"+1,omitempty"`
36	MinusOne   *int    `json:"-1,omitempty"`
37	Laugh      *int    `json:"laugh,omitempty"`
38	Confused   *int    `json:"confused,omitempty"`
39	Heart      *int    `json:"heart,omitempty"`
40	Hooray     *int    `json:"hooray,omitempty"`
41	Rocket     *int    `json:"rocket,omitempty"`
42	Eyes       *int    `json:"eyes,omitempty"`
43	URL        *string `json:"url,omitempty"`
44}
45
46func (r Reaction) String() string {
47	return Stringify(r)
48}
49
50// ListCommentReactionOptions specifies the optional parameters to the
51// ReactionsService.ListCommentReactions method.
52type ListCommentReactionOptions struct {
53	// Content restricts the returned comment reactions to only those with the given type.
54	// Omit this parameter to list all reactions to a commit comment.
55	// Possible values are: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
56	Content string `url:"content,omitempty"`
57
58	ListOptions
59}
60
61// ListCommentReactions lists the reactions for a commit comment.
62//
63// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#list-reactions-for-a-commit-comment
64func (s *ReactionsService) ListCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListCommentReactionOptions) ([]*Reaction, *Response, error) {
65	u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id)
66	u, err := addOptions(u, opts)
67	if err != nil {
68		return nil, nil, err
69	}
70
71	req, err := s.client.NewRequest("GET", u, nil)
72	if err != nil {
73		return nil, nil, err
74	}
75
76	// TODO: remove custom Accept headers when APIs fully launch.
77	req.Header.Set("Accept", mediaTypeReactionsPreview)
78
79	var m []*Reaction
80	resp, err := s.client.Do(ctx, req, &m)
81	if err != nil {
82		return nil, resp, err
83	}
84
85	return m, resp, nil
86}
87
88// CreateCommentReaction creates a reaction for a commit comment.
89// Note that if you have already created a reaction of type content, the
90// previously created reaction will be returned with Status: 200 OK.
91// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
92//
93// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#create-reaction-for-a-commit-comment
94func (s *ReactionsService) CreateCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) {
95	u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions", owner, repo, id)
96
97	body := &Reaction{Content: String(content)}
98	req, err := s.client.NewRequest("POST", u, body)
99	if err != nil {
100		return nil, nil, err
101	}
102
103	// TODO: remove custom Accept headers when APIs fully launch.
104	req.Header.Set("Accept", mediaTypeReactionsPreview)
105
106	m := &Reaction{}
107	resp, err := s.client.Do(ctx, req, m)
108	if err != nil {
109		return nil, resp, err
110	}
111
112	return m, resp, nil
113}
114
115// DeleteCommentReaction deletes the reaction for a commit comment.
116//
117// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-a-commit-comment-reaction
118func (s *ReactionsService) DeleteCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) {
119	u := fmt.Sprintf("repos/%v/%v/comments/%v/reactions/%v", owner, repo, commentID, reactionID)
120
121	return s.deleteReaction(ctx, u)
122}
123
124// DeleteCommentReactionByID deletes the reaction for a commit comment by repository ID.
125//
126// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-a-commit-comment-reaction
127func (s *ReactionsService) DeleteCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) {
128	u := fmt.Sprintf("repositories/%v/comments/%v/reactions/%v", repoID, commentID, reactionID)
129
130	return s.deleteReaction(ctx, u)
131}
132
133// ListIssueReactions lists the reactions for an issue.
134//
135// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#list-reactions-for-an-issue
136func (s *ReactionsService) ListIssueReactions(ctx context.Context, owner, repo string, number int, opts *ListOptions) ([]*Reaction, *Response, error) {
137	u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number)
138	u, err := addOptions(u, opts)
139	if err != nil {
140		return nil, nil, err
141	}
142
143	req, err := s.client.NewRequest("GET", u, nil)
144	if err != nil {
145		return nil, nil, err
146	}
147
148	// TODO: remove custom Accept headers when APIs fully launch.
149	req.Header.Set("Accept", mediaTypeReactionsPreview)
150
151	var m []*Reaction
152	resp, err := s.client.Do(ctx, req, &m)
153	if err != nil {
154		return nil, resp, err
155	}
156
157	return m, resp, nil
158}
159
160// CreateIssueReaction creates a reaction for an issue.
161// Note that if you have already created a reaction of type content, the
162// previously created reaction will be returned with Status: 200 OK.
163// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
164//
165// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#create-reaction-for-an-issue
166func (s *ReactionsService) CreateIssueReaction(ctx context.Context, owner, repo string, number int, content string) (*Reaction, *Response, error) {
167	u := fmt.Sprintf("repos/%v/%v/issues/%v/reactions", owner, repo, number)
168
169	body := &Reaction{Content: String(content)}
170	req, err := s.client.NewRequest("POST", u, body)
171	if err != nil {
172		return nil, nil, err
173	}
174
175	// TODO: remove custom Accept headers when APIs fully launch.
176	req.Header.Set("Accept", mediaTypeReactionsPreview)
177
178	m := &Reaction{}
179	resp, err := s.client.Do(ctx, req, m)
180	if err != nil {
181		return nil, resp, err
182	}
183
184	return m, resp, nil
185}
186
187// DeleteIssueReaction deletes the reaction to an issue.
188//
189// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-an-issue-reaction
190func (s *ReactionsService) DeleteIssueReaction(ctx context.Context, owner, repo string, issueNumber int, reactionID int64) (*Response, error) {
191	url := fmt.Sprintf("repos/%v/%v/issues/%v/reactions/%v", owner, repo, issueNumber, reactionID)
192
193	return s.deleteReaction(ctx, url)
194}
195
196// DeleteIssueReactionByID deletes the reaction to an issue by repository ID.
197//
198// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-an-issue-reaction
199func (s *ReactionsService) DeleteIssueReactionByID(ctx context.Context, repoID, issueNumber int, reactionID int64) (*Response, error) {
200	url := fmt.Sprintf("repositories/%v/issues/%v/reactions/%v", repoID, issueNumber, reactionID)
201
202	return s.deleteReaction(ctx, url)
203}
204
205// ListIssueCommentReactions lists the reactions for an issue comment.
206//
207// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#list-reactions-for-an-issue-comment
208func (s *ReactionsService) ListIssueCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) {
209	u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id)
210	u, err := addOptions(u, opts)
211	if err != nil {
212		return nil, nil, err
213	}
214
215	req, err := s.client.NewRequest("GET", u, nil)
216	if err != nil {
217		return nil, nil, err
218	}
219
220	// TODO: remove custom Accept headers when APIs fully launch.
221	req.Header.Set("Accept", mediaTypeReactionsPreview)
222
223	var m []*Reaction
224	resp, err := s.client.Do(ctx, req, &m)
225	if err != nil {
226		return nil, resp, err
227	}
228
229	return m, resp, nil
230}
231
232// CreateIssueCommentReaction creates a reaction for an issue comment.
233// Note that if you have already created a reaction of type content, the
234// previously created reaction will be returned with Status: 200 OK.
235// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
236//
237// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#create-reaction-for-an-issue-comment
238func (s *ReactionsService) CreateIssueCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) {
239	u := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions", owner, repo, id)
240
241	body := &Reaction{Content: String(content)}
242	req, err := s.client.NewRequest("POST", u, body)
243	if err != nil {
244		return nil, nil, err
245	}
246
247	// TODO: remove custom Accept headers when APIs fully launch.
248	req.Header.Set("Accept", mediaTypeReactionsPreview)
249
250	m := &Reaction{}
251	resp, err := s.client.Do(ctx, req, m)
252	if err != nil {
253		return nil, resp, err
254	}
255
256	return m, resp, nil
257}
258
259// DeleteIssueCommentReaction deletes the reaction to an issue comment.
260//
261// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-an-issue-comment-reaction
262func (s *ReactionsService) DeleteIssueCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) {
263	url := fmt.Sprintf("repos/%v/%v/issues/comments/%v/reactions/%v", owner, repo, commentID, reactionID)
264
265	return s.deleteReaction(ctx, url)
266}
267
268// DeleteIssueCommentReactionByID deletes the reaction to an issue comment by repository ID.
269//
270// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-an-issue-comment-reaction
271func (s *ReactionsService) DeleteIssueCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) {
272	url := fmt.Sprintf("repositories/%v/issues/comments/%v/reactions/%v", repoID, commentID, reactionID)
273
274	return s.deleteReaction(ctx, url)
275}
276
277// ListPullRequestCommentReactions lists the reactions for a pull request review comment.
278//
279// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#list-reactions-for-a-pull-request-review-comment
280func (s *ReactionsService) ListPullRequestCommentReactions(ctx context.Context, owner, repo string, id int64, opts *ListOptions) ([]*Reaction, *Response, error) {
281	u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id)
282	u, err := addOptions(u, opts)
283	if err != nil {
284		return nil, nil, err
285	}
286
287	req, err := s.client.NewRequest("GET", u, nil)
288	if err != nil {
289		return nil, nil, err
290	}
291
292	// TODO: remove custom Accept headers when APIs fully launch.
293	req.Header.Set("Accept", mediaTypeReactionsPreview)
294
295	var m []*Reaction
296	resp, err := s.client.Do(ctx, req, &m)
297	if err != nil {
298		return nil, resp, err
299	}
300
301	return m, resp, nil
302}
303
304// CreatePullRequestCommentReaction creates a reaction for a pull request review comment.
305// Note that if you have already created a reaction of type content, the
306// previously created reaction will be returned with Status: 200 OK.
307// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
308//
309// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#create-reaction-for-a-pull-request-review-comment
310func (s *ReactionsService) CreatePullRequestCommentReaction(ctx context.Context, owner, repo string, id int64, content string) (*Reaction, *Response, error) {
311	u := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions", owner, repo, id)
312
313	body := &Reaction{Content: String(content)}
314	req, err := s.client.NewRequest("POST", u, body)
315	if err != nil {
316		return nil, nil, err
317	}
318
319	// TODO: remove custom Accept headers when APIs fully launch.
320	req.Header.Set("Accept", mediaTypeReactionsPreview)
321
322	m := &Reaction{}
323	resp, err := s.client.Do(ctx, req, m)
324	if err != nil {
325		return nil, resp, err
326	}
327
328	return m, resp, nil
329}
330
331// DeletePullRequestCommentReaction deletes the reaction to a pull request review comment.
332//
333// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-a-pull-request-comment-reaction
334func (s *ReactionsService) DeletePullRequestCommentReaction(ctx context.Context, owner, repo string, commentID, reactionID int64) (*Response, error) {
335	url := fmt.Sprintf("repos/%v/%v/pulls/comments/%v/reactions/%v", owner, repo, commentID, reactionID)
336
337	return s.deleteReaction(ctx, url)
338}
339
340// DeletePullRequestCommentReactionByID deletes the reaction to a pull request review comment by repository ID.
341//
342// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-a-pull-request-comment-reaction
343func (s *ReactionsService) DeletePullRequestCommentReactionByID(ctx context.Context, repoID, commentID, reactionID int64) (*Response, error) {
344	url := fmt.Sprintf("repositories/%v/pulls/comments/%v/reactions/%v", repoID, commentID, reactionID)
345
346	return s.deleteReaction(ctx, url)
347}
348
349// ListTeamDiscussionReactions lists the reactions for a team discussion.
350//
351// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#list-reactions-for-a-team-discussion-legacy
352func (s *ReactionsService) ListTeamDiscussionReactions(ctx context.Context, teamID int64, discussionNumber int, opts *ListOptions) ([]*Reaction, *Response, error) {
353	u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber)
354	u, err := addOptions(u, opts)
355	if err != nil {
356		return nil, nil, err
357	}
358
359	req, err := s.client.NewRequest("GET", u, nil)
360	if err != nil {
361		return nil, nil, err
362	}
363
364	req.Header.Set("Accept", mediaTypeReactionsPreview)
365
366	var m []*Reaction
367	resp, err := s.client.Do(ctx, req, &m)
368	if err != nil {
369		return nil, resp, err
370	}
371
372	return m, resp, nil
373}
374
375// CreateTeamDiscussionReaction creates a reaction for a team discussion.
376// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
377//
378// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#create-reaction-for-a-team-discussion-legacy
379func (s *ReactionsService) CreateTeamDiscussionReaction(ctx context.Context, teamID int64, discussionNumber int, content string) (*Reaction, *Response, error) {
380	u := fmt.Sprintf("teams/%v/discussions/%v/reactions", teamID, discussionNumber)
381
382	body := &Reaction{Content: String(content)}
383	req, err := s.client.NewRequest("POST", u, body)
384	if err != nil {
385		return nil, nil, err
386	}
387
388	req.Header.Set("Accept", mediaTypeReactionsPreview)
389
390	m := &Reaction{}
391	resp, err := s.client.Do(ctx, req, m)
392	if err != nil {
393		return nil, resp, err
394	}
395
396	return m, resp, nil
397}
398
399// DeleteTeamDiscussionReaction deletes the reaction to a team discussion.
400//
401// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-team-discussion-reaction
402func (s *ReactionsService) DeleteTeamDiscussionReaction(ctx context.Context, org, teamSlug string, discussionNumber int, reactionID int64) (*Response, error) {
403	url := fmt.Sprintf("orgs/%v/teams/%v/discussions/%v/reactions/%v", org, teamSlug, discussionNumber, reactionID)
404
405	return s.deleteReaction(ctx, url)
406}
407
408// DeleteTeamDiscussionReactionByOrgIDAndTeamID deletes the reaction to a team discussion by organization ID and team ID.
409//
410// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-team-discussion-reaction
411func (s *ReactionsService) DeleteTeamDiscussionReactionByOrgIDAndTeamID(ctx context.Context, orgID, teamID, discussionNumber int, reactionID int64) (*Response, error) {
412	url := fmt.Sprintf("organizations/%v/team/%v/discussions/%v/reactions/%v", orgID, teamID, discussionNumber, reactionID)
413
414	return s.deleteReaction(ctx, url)
415}
416
417// ListTeamDiscussionCommentReactions lists the reactions for a team discussion comment.
418//
419// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#list-reactions-for-a-team-discussion-comment-legacy
420func (s *ReactionsService) ListTeamDiscussionCommentReactions(ctx context.Context, teamID int64, discussionNumber, commentNumber int, opts *ListOptions) ([]*Reaction, *Response, error) {
421	u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber)
422	u, err := addOptions(u, opts)
423	if err != nil {
424		return nil, nil, err
425	}
426
427	req, err := s.client.NewRequest("GET", u, nil)
428	if err != nil {
429		return nil, nil, err
430	}
431
432	req.Header.Set("Accept", mediaTypeReactionsPreview)
433
434	var m []*Reaction
435	resp, err := s.client.Do(ctx, req, &m)
436	if err != nil {
437		return nil, nil, err
438	}
439	return m, resp, nil
440}
441
442// CreateTeamDiscussionCommentReaction creates a reaction for a team discussion comment.
443// The content should have one of the following values: "+1", "-1", "laugh", "confused", "heart", "hooray", "rocket", or "eyes".
444//
445// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#create-reaction-for-a-team-discussion-comment-legacy
446func (s *ReactionsService) CreateTeamDiscussionCommentReaction(ctx context.Context, teamID int64, discussionNumber, commentNumber int, content string) (*Reaction, *Response, error) {
447	u := fmt.Sprintf("teams/%v/discussions/%v/comments/%v/reactions", teamID, discussionNumber, commentNumber)
448
449	body := &Reaction{Content: String(content)}
450	req, err := s.client.NewRequest("POST", u, body)
451	if err != nil {
452		return nil, nil, err
453	}
454
455	req.Header.Set("Accept", mediaTypeReactionsPreview)
456
457	m := &Reaction{}
458	resp, err := s.client.Do(ctx, req, m)
459	if err != nil {
460		return nil, resp, err
461	}
462
463	return m, resp, nil
464}
465
466// DeleteTeamDiscussionCommentReaction deletes the reaction to a team discussion comment.
467//
468// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-team-discussion-comment-reaction
469func (s *ReactionsService) DeleteTeamDiscussionCommentReaction(ctx context.Context, org, teamSlug string, discussionNumber, commentNumber int, reactionID int64) (*Response, error) {
470	url := fmt.Sprintf("orgs/%v/teams/%v/discussions/%v/comments/%v/reactions/%v", org, teamSlug, discussionNumber, commentNumber, reactionID)
471
472	return s.deleteReaction(ctx, url)
473}
474
475// DeleteTeamDiscussionCommentReactionByOrgIDAndTeamID deletes the reaction to a team discussion comment by organization ID and team ID.
476//
477// GitHub API docs: https://docs.github.com/en/free-pro-team@latest/rest/reference/reactions/#delete-team-discussion-comment-reaction
478func (s *ReactionsService) DeleteTeamDiscussionCommentReactionByOrgIDAndTeamID(ctx context.Context, orgID, teamID, discussionNumber, commentNumber int, reactionID int64) (*Response, error) {
479	url := fmt.Sprintf("organizations/%v/team/%v/discussions/%v/comments/%v/reactions/%v", orgID, teamID, discussionNumber, commentNumber, reactionID)
480
481	return s.deleteReaction(ctx, url)
482}
483
484func (s *ReactionsService) deleteReaction(ctx context.Context, url string) (*Response, error) {
485	req, err := s.client.NewRequest(http.MethodDelete, url, nil)
486	if err != nil {
487		return nil, err
488	}
489
490	// TODO: remove custom Accept headers when APIs fully launch.
491	req.Header.Set("Accept", mediaTypeReactionsPreview)
492
493	return s.client.Do(ctx, req, nil)
494}
495