diff --git a/geddit.go b/geddit.go index 1c1827b..201d492 100644 --- a/geddit.go +++ b/geddit.go @@ -98,6 +98,7 @@ type Client struct { Comment *CommentService Flair *FlairService Listings *ListingsService + Multi *MultiService Post *PostService Search *SearchService Subreddit *SubredditService @@ -133,6 +134,7 @@ func newClient(httpClient *http.Client) *Client { c.Comment = (*CommentService)(&c.common) c.Flair = (*FlairService)(&c.common) c.Listings = (*ListingsService)(&c.common) + c.Multi = (*MultiService)(&c.common) c.Post = (*PostService)(&c.common) c.Search = (*SearchService)(&c.common) c.Subreddit = (*SubredditService)(&c.common) diff --git a/multi.go b/multi.go new file mode 100644 index 0000000..32fb106 --- /dev/null +++ b/multi.go @@ -0,0 +1,223 @@ +package geddit + +import ( + "context" + "encoding/json" + "errors" + "fmt" + "net/http" + "net/url" +) + +// MultiService handles communication with the multireddit +// related methods of the Reddit API. +// +// Reddit API docs: https://www.reddit.com/dev/api#section_multis +type MultiService service + +type multiRoot struct { + Kind string `json:"kind,omitempty"` + Data *Multi `json:"data,omitempty"` +} + +// Multi is a multireddit, i.e. a customizable group of subreddits. +// Users can create multis for custom navigation, instead of browsing +// one subreddit or all subreddits at a time. +type Multi struct { + Name string `json:"name,omitempty"` + DisplayName string `json:"display_name,omitempty"` + Path string `json:"path,omitempty"` + Description string `json:"description_md,omitempty"` + Subreddits SubredditNames `json:"subreddits"` + CopedFrom *string `json:"copied_from"` + + Owner string `json:"owner,omitempty"` + OwnerID string `json:"ownerID,omitempty"` + Created *Timestamp `json:"created_utc,omitempty"` + + NumberOfSubscribers int `json:"num_subscribers"` + Visibility string `json:"visibility,omitempty"` + Subscribed bool `json:"is_subscriber"` + Favorite bool `json:"is_favorited"` + CanEdit bool `json:"can_edit"` + NSFW bool `json:"over_18"` +} + +// SubredditNames is a list of subreddit names. +type SubredditNames []string + +// UnmarshalJSON implements the json.Unmarshaler interface. +func (n *SubredditNames) UnmarshalJSON(data []byte) error { + var subreddits []map[string]string + + err := json.Unmarshal(data, &subreddits) + if err != nil { + return err + } + + for _, subreddit := range subreddits { + name, ok := subreddit["name"] + if !ok { + continue + } + *n = append(*n, name) + } + + return nil +} + +// MultiCopyRequest represents a request to copy a multireddit. +type MultiCopyRequest struct { + From string + To string + // Raw markdown text. + Description string + // No longer than 50 characters. + DisplayName string +} + +// Form parameterizes the fields and returns the form. +func (r *MultiCopyRequest) Form() url.Values { + form := url.Values{} + form.Set("from", r.From) + form.Set("to", r.To) + form.Set("description_md", r.Description) + form.Set("display_name", r.DisplayName) + return form +} + +// MultiCreateRequest represents a request to create a multireddit. +type MultiCreateRequest struct { + Description string `json:"description_md,omitempty"` + DisplayName string `json:"display_name,omitempty"` + Subreddits []string `json:"subreddits"` + Visibility string `json:"visibility,omitempty"` +} + +// Form parameterizes the fields and returns the form. +func (r *MultiCreateRequest) Form() url.Values { + byteValue, _ := json.Marshal(r) + form := url.Values{} + form.Set("model", string(byteValue)) + return form +} + +// Get gets information about the multireddit from its url path. +func (s *MultiService) Get(ctx context.Context, multiPath string) (*Multi, *Response, error) { + path := fmt.Sprintf("api/multi/%s", multiPath) + + req, err := s.client.NewRequest(http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(multiRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Data, resp, nil +} + +// Mine returns your multireddits. +func (s *MultiService) Mine(ctx context.Context) ([]Multi, *Response, error) { + path := "api/multi/mine" + + req, err := s.client.NewRequest(http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + var root []multiRoot + resp, err := s.client.Do(ctx, req, &root) + if err != nil { + return nil, resp, err + } + + multis := make([]Multi, 0) + for _, multi := range root { + multis = append(multis, *multi.Data) + } + + return multis, resp, nil +} + +// Of returns the user's public multireddits. +func (s *MultiService) Of(ctx context.Context, username string) ([]Multi, *Response, error) { + path := fmt.Sprintf("api/multi/user/%s", username) + + req, err := s.client.NewRequest(http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + var root []multiRoot + resp, err := s.client.Do(ctx, req, &root) + if err != nil { + return nil, resp, err + } + + multis := make([]Multi, 0) + for _, multi := range root { + multis = append(multis, *multi.Data) + } + + return multis, resp, nil +} + +// Copy copies a multireddit. +func (s *MultiService) Copy(ctx context.Context, copyRequest *MultiCopyRequest) (*Multi, *Response, error) { + if copyRequest == nil { + return nil, nil, errors.New("copyRequest cannot be nil") + } + + path := fmt.Sprintf("api/multi/copy") + + req, err := s.client.NewPostForm(path, copyRequest.Form()) + if err != nil { + return nil, nil, err + } + + root := new(multiRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Data, resp, nil +} + +// Create creates a multireddit. +func (s *MultiService) Create(ctx context.Context, createRequest *MultiCreateRequest) (*Multi, *Response, error) { + if createRequest == nil { + return nil, nil, errors.New("createRequest cannot be nil") + } + + path := fmt.Sprintf("api/multi/copy") + + req, err := s.client.NewRequest(http.MethodPost, path, createRequest) + if err != nil { + return nil, nil, err + } + + root := new(multiRoot) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root.Data, resp, nil +} + +// Delete deletes a multireddit. +func (s *MultiService) Delete(ctx context.Context, multiPath string) (*Response, error) { + path := fmt.Sprintf("api/multi/%s", multiPath) + + req, err := s.client.NewRequest(http.MethodDelete, path, nil) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} diff --git a/subreddit_test.go b/subreddit_test.go index 8803314..a087285 100644 --- a/subreddit_test.go +++ b/subreddit_test.go @@ -45,6 +45,7 @@ var expectedSubreddits = &Subreddits{ Subscribers: 15336, NSFW: false, UserIsMod: false, + Favorite: false, }, { ID: "2qh1i", @@ -61,6 +62,7 @@ var expectedSubreddits = &Subreddits{ Subscribers: 28449174, NSFW: false, UserIsMod: false, + Favorite: true, }, { ID: "2qh0u", @@ -77,6 +79,7 @@ var expectedSubreddits = &Subreddits{ Subscribers: 24987753, NSFW: false, UserIsMod: false, + Favorite: false, }, }, } diff --git a/things.go b/things.go index 8204f05..dd48e46 100644 --- a/things.go +++ b/things.go @@ -337,6 +337,7 @@ type Subreddit struct { ActiveUserCount *int `json:"active_user_count,omitempty"` NSFW bool `json:"over18"` UserIsMod bool `json:"user_is_moderator"` + Favorite bool `json:"user_has_favorited"` } func (rl *rootListing) getAfter() string {