2020-07-11 13:49:07 -04:00
|
|
|
package reddit
|
2020-04-23 22:57:47 -04:00
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net/http"
|
2020-05-03 17:31:35 -04:00
|
|
|
"net/url"
|
2020-04-29 15:59:18 -04:00
|
|
|
"strings"
|
2020-04-23 22:57:47 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
// SubredditService handles communication with the subreddit
|
2020-06-27 23:53:59 -04:00
|
|
|
// related methods of the Reddit API.
|
|
|
|
//
|
|
|
|
// Reddit API docs: https://www.reddit.com/dev/api/#section_subreddits
|
2020-07-21 23:05:24 -04:00
|
|
|
type SubredditService struct {
|
|
|
|
client *Client
|
|
|
|
}
|
2020-04-23 22:57:47 -04:00
|
|
|
|
2020-07-12 22:53:19 -04:00
|
|
|
type rootSubreddit struct {
|
|
|
|
Kind string `json:"kind,omitempty"`
|
|
|
|
Data *Subreddit `json:"data,omitempty"`
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
type rootSubredditInfoList struct {
|
|
|
|
Subreddits []*SubredditInfo `json:"subreddits,omitempty"`
|
2020-05-16 21:46:16 -04:00
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
// SubredditInfo represents minimal information about a subreddit.
|
|
|
|
type SubredditInfo struct {
|
2020-05-16 21:46:16 -04:00
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Subscribers int `json:"subscriber_count"`
|
|
|
|
ActiveUsers int `json:"active_user_count"`
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
type rootSubredditNames struct {
|
|
|
|
Names []string `json:"names,omitempty"`
|
|
|
|
}
|
|
|
|
|
2020-07-11 23:26:14 -04:00
|
|
|
type rootModeratorList struct {
|
|
|
|
Kind string `json:"kind,omitempty"`
|
|
|
|
Data struct {
|
|
|
|
Moderators []Moderator `json:"children"`
|
|
|
|
} `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
// Moderator is a user who moderates a subreddit.
|
|
|
|
type Moderator struct {
|
|
|
|
ID string `json:"id,omitempty"`
|
|
|
|
Name string `json:"name,omitempty"`
|
|
|
|
Permissions []string `json:"mod_permissions"`
|
|
|
|
}
|
|
|
|
|
2020-07-25 00:08:28 -04:00
|
|
|
func (s *SubredditService) getPosts(ctx context.Context, sort Sort, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) {
|
|
|
|
path := sort.String()
|
|
|
|
if len(subreddits) > 0 {
|
|
|
|
path = fmt.Sprintf("r/%s/%s", strings.Join(subreddits, "+"), sort.String())
|
|
|
|
}
|
|
|
|
|
|
|
|
form := newSearchOptions(opts...)
|
|
|
|
path = addQuery(path, form)
|
|
|
|
|
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(rootListing)
|
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.getPosts(), resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Hot returns the hottest posts from the specified subreddits.
|
|
|
|
// If none are defined, it returns the ones from your subscribed subreddits.
|
|
|
|
// Note: when looking for hot posts in a subreddit, it will include the stickied
|
|
|
|
// posts (if any) PLUS posts from the limit parameter (25 by default).
|
|
|
|
func (s *SubredditService) Hot(ctx context.Context, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) {
|
|
|
|
return s.getPosts(ctx, SortHot, subreddits, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// New returns the newest posts from the specified subreddits.
|
|
|
|
// If none are defined, it returns the ones from your subscribed subreddits.
|
|
|
|
func (s *SubredditService) New(ctx context.Context, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) {
|
|
|
|
return s.getPosts(ctx, SortNew, subreddits, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Rising returns the rising posts from the specified subreddits.
|
|
|
|
// If none are defined, it returns the ones from your subscribed subreddits.
|
|
|
|
func (s *SubredditService) Rising(ctx context.Context, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) {
|
|
|
|
return s.getPosts(ctx, SortRising, subreddits, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Controversial returns the most controversial posts from the specified subreddits.
|
|
|
|
// If none are defined, it returns the ones from your subscribed subreddits.
|
|
|
|
func (s *SubredditService) Controversial(ctx context.Context, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) {
|
|
|
|
return s.getPosts(ctx, SortControversial, subreddits, opts...)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Top returns the top posts from the specified subreddits.
|
|
|
|
// If none are defined, it returns the ones from your subscribed subreddits.
|
|
|
|
func (s *SubredditService) Top(ctx context.Context, subreddits []string, opts ...SearchOptionSetter) (*Posts, *Response, error) {
|
|
|
|
return s.getPosts(ctx, SortTop, subreddits, opts...)
|
2020-05-16 22:34:01 -04:00
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
// Get gets a subreddit by name.
|
|
|
|
func (s *SubredditService) Get(ctx context.Context, name string) (*Subreddit, *Response, error) {
|
|
|
|
if name == "" {
|
|
|
|
return nil, nil, errors.New("name: must not be empty")
|
2020-04-23 22:57:47 -04:00
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
path := fmt.Sprintf("r/%s/about", name)
|
2020-04-23 22:57:47 -04:00
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-12 22:53:19 -04:00
|
|
|
root := new(rootSubreddit)
|
2020-04-23 22:57:47 -04:00
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.Data, resp, nil
|
|
|
|
}
|
2020-04-29 15:59:18 -04:00
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetPopular returns popular subreddits.
|
2020-07-27 22:26:29 -04:00
|
|
|
// todo: use search opts for this
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetPopular(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/popular", opts)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetNew returns new subreddits.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetNew(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/new", opts)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetGold returns gold subreddits.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetGold(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/gold", opts)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetDefault returns default subreddits.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetDefault(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/default", opts)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetSubscribed returns the list of subreddits the client is subscribed to.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetSubscribed(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/mine/subscriber", opts)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetApproved returns the list of subreddits the client is an approved user in.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetApproved(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/mine/contributor", opts)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetModerated returns the list of subreddits the client is a moderator of.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) GetModerated(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-06-21 00:13:17 -04:00
|
|
|
return s.getSubreddits(ctx, "subreddits/mine/moderator", opts)
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetSticky1 returns the first stickied post on a subreddit (if it exists).
|
2020-07-19 22:03:37 -04:00
|
|
|
func (s *SubredditService) GetSticky1(ctx context.Context, name string) (*Post, []*Comment, *Response, error) {
|
2020-06-22 21:57:00 -04:00
|
|
|
return s.getSticky(ctx, name, 1)
|
2020-05-16 11:38:47 -04:00
|
|
|
}
|
2020-05-03 17:31:35 -04:00
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// GetSticky2 returns the second stickied post on a subreddit (if it exists).
|
2020-07-19 22:03:37 -04:00
|
|
|
func (s *SubredditService) GetSticky2(ctx context.Context, name string) (*Post, []*Comment, *Response, error) {
|
2020-06-22 21:57:00 -04:00
|
|
|
return s.getSticky(ctx, name, 2)
|
2020-05-16 11:38:47 -04:00
|
|
|
}
|
2020-05-03 17:31:35 -04:00
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
func (s *SubredditService) handleSubscription(ctx context.Context, form url.Values) (*Response, error) {
|
|
|
|
path := "api/subscribe"
|
|
|
|
|
|
|
|
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return s.client.Do(ctx, req, nil)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// Subscribe subscribes to subreddits based on their names.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) Subscribe(ctx context.Context, subreddits ...string) (*Response, error) {
|
2020-05-03 17:31:35 -04:00
|
|
|
form := url.Values{}
|
|
|
|
form.Set("action", "sub")
|
2020-05-16 11:38:47 -04:00
|
|
|
form.Set("sr_name", strings.Join(subreddits, ","))
|
2020-05-03 17:31:35 -04:00
|
|
|
return s.handleSubscription(ctx, form)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// SubscribeByID subscribes to subreddits based on their id.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) SubscribeByID(ctx context.Context, ids ...string) (*Response, error) {
|
2020-05-03 17:31:35 -04:00
|
|
|
form := url.Values{}
|
|
|
|
form.Set("action", "sub")
|
|
|
|
form.Set("sr", strings.Join(ids, ","))
|
|
|
|
return s.handleSubscription(ctx, form)
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// Unsubscribe unsubscribes from subreddits based on their names.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) Unsubscribe(ctx context.Context, subreddits ...string) (*Response, error) {
|
2020-05-03 17:31:35 -04:00
|
|
|
form := url.Values{}
|
|
|
|
form.Set("action", "unsub")
|
2020-05-16 11:38:47 -04:00
|
|
|
form.Set("sr_name", strings.Join(subreddits, ","))
|
2020-05-03 17:31:35 -04:00
|
|
|
return s.handleSubscription(ctx, form)
|
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// UnsubscribeByID unsubscribes from subreddits based on their id.
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) UnsubscribeByID(ctx context.Context, ids ...string) (*Response, error) {
|
2020-05-03 17:31:35 -04:00
|
|
|
form := url.Values{}
|
|
|
|
form.Set("action", "unsub")
|
|
|
|
form.Set("sr", strings.Join(ids, ","))
|
|
|
|
return s.handleSubscription(ctx, form)
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
// Search searches for subreddits with names beginning with the query provided.
|
|
|
|
// They hold a very minimal amount of info.
|
|
|
|
func (s *SubredditService) Search(ctx context.Context, query string) ([]*SubredditInfo, *Response, error) {
|
|
|
|
path := "api/search_subreddits"
|
2020-05-16 21:46:16 -04:00
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
form := url.Values{}
|
|
|
|
form.Set("query", query)
|
|
|
|
|
|
|
|
req, err := s.client.NewRequestWithForm(http.MethodPost, path, form)
|
2020-05-16 21:46:16 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
root := new(rootSubredditInfoList)
|
2020-05-16 21:46:16 -04:00
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
return root.Subreddits, resp, nil
|
2020-05-16 21:46:16 -04:00
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
// SearchNames searches for subreddits with names beginning with the query provided.
|
|
|
|
func (s *SubredditService) SearchNames(ctx context.Context, query string) ([]string, *Response, error) {
|
|
|
|
path := fmt.Sprintf("api/search_reddit_names?query=%s", query)
|
2020-05-16 21:46:16 -04:00
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
2020-05-16 21:46:16 -04:00
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
root := new(rootSubredditNames)
|
2020-05-16 21:46:16 -04:00
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
return root.Names, resp, nil
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-06-27 23:53:59 -04:00
|
|
|
func (s *SubredditService) getSubreddits(ctx context.Context, path string, opts *ListOptions) (*Subreddits, *Response, error) {
|
2020-04-29 15:59:18 -04:00
|
|
|
path, err := addOptions(path, opts)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
2020-05-03 17:31:35 -04:00
|
|
|
root := new(rootListing)
|
2020-04-29 15:59:18 -04:00
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
2020-07-21 23:59:53 -04:00
|
|
|
return root.getSubreddits(), resp, nil
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-06-21 00:13:17 -04:00
|
|
|
// getSticky returns one of the 2 stickied posts of the subreddit (if they exist).
|
|
|
|
// Num should be equal to 1 or 2, depending on which one you want.
|
2020-07-19 22:03:37 -04:00
|
|
|
func (s *SubredditService) getSticky(ctx context.Context, subreddit string, num int) (*Post, []*Comment, *Response, error) {
|
2020-05-16 22:34:01 -04:00
|
|
|
type query struct {
|
2020-06-22 21:57:00 -04:00
|
|
|
Num int `url:"num"`
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-05-16 22:34:01 -04:00
|
|
|
path := fmt.Sprintf("r/%s/about/sticky", subreddit)
|
|
|
|
path, err := addOptions(path, query{num})
|
2020-04-29 15:59:18 -04:00
|
|
|
if err != nil {
|
2020-07-17 17:04:28 -04:00
|
|
|
return nil, nil, nil, err
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
2020-07-17 17:04:28 -04:00
|
|
|
return nil, nil, nil, err
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-07-17 17:04:28 -04:00
|
|
|
root := new(postAndComments)
|
2020-04-29 15:59:18 -04:00
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
2020-07-17 17:04:28 -04:00
|
|
|
return nil, nil, resp, err
|
2020-04-29 15:59:18 -04:00
|
|
|
}
|
|
|
|
|
2020-07-17 17:04:28 -04:00
|
|
|
return root.Post, root.Comments, resp, nil
|
2020-05-16 22:34:01 -04:00
|
|
|
}
|
2020-04-29 15:59:18 -04:00
|
|
|
|
2020-07-11 23:26:14 -04:00
|
|
|
// Moderators returns the moderators of a subreddit.
|
|
|
|
func (s *SubredditService) Moderators(ctx context.Context, subreddit string) (interface{}, *Response, error) {
|
|
|
|
path := fmt.Sprintf("r/%s/about/moderators", subreddit)
|
|
|
|
|
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(rootModeratorList)
|
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
return root.Data.Moderators, resp, nil
|
|
|
|
}
|
2020-07-20 21:03:57 -04:00
|
|
|
|
|
|
|
// todo: sr_detail's NSFW indicator is over_18 instead of over18
|
|
|
|
func (s *SubredditService) random(ctx context.Context, nsfw bool) (*Subreddit, *Response, error) {
|
|
|
|
path := "r/random"
|
|
|
|
if nsfw {
|
|
|
|
path = "r/randnsfw"
|
|
|
|
}
|
|
|
|
|
|
|
|
type query struct {
|
|
|
|
ExpandSubreddit bool `url:"sr_detail"`
|
|
|
|
Limit int `url:"limit,omitempty"`
|
|
|
|
}
|
|
|
|
|
|
|
|
path, err := addOptions(path, query{true, 1})
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
type rootResponse struct {
|
|
|
|
Data struct {
|
|
|
|
Children []struct {
|
|
|
|
Data struct {
|
|
|
|
Subreddit *Subreddit `json:"sr_detail"`
|
|
|
|
} `json:"data"`
|
|
|
|
} `json:"children"`
|
|
|
|
} `json:"data"`
|
|
|
|
}
|
|
|
|
|
|
|
|
root := new(rootResponse)
|
|
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
|
|
if err != nil {
|
|
|
|
return nil, resp, err
|
|
|
|
}
|
|
|
|
|
|
|
|
var sr *Subreddit
|
|
|
|
if len(root.Data.Children) > 0 {
|
|
|
|
sr = root.Data.Children[0].Data.Subreddit
|
|
|
|
}
|
|
|
|
|
|
|
|
return sr, resp, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Random returns a random SFW subreddit.
|
|
|
|
func (s *SubredditService) Random(ctx context.Context) (*Subreddit, *Response, error) {
|
|
|
|
return s.random(ctx, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
// RandomNSFW returns a random NSFW subreddit.
|
|
|
|
func (s *SubredditService) RandomNSFW(ctx context.Context) (*Subreddit, *Response, error) {
|
|
|
|
return s.random(ctx, true)
|
|
|
|
}
|