Use forms for searches

Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
Vartan Benohanian 2020-05-28 21:31:43 -04:00
parent 946318c27b
commit 1d1118517b
4 changed files with 297 additions and 794 deletions

View file

@ -341,3 +341,10 @@ func addOptions(s string, opt interface{}) (string, error) {
origURL.RawQuery = origValues.Encode() origURL.RawQuery = origValues.Encode()
return origURL.String(), nil return origURL.String(), nil
} }
func addQuery(url string, query url.Values) string {
if query == nil || len(query) == 0 {
return url
}
return url + "?" + query.Encode()
}

386
search.go
View file

@ -3,6 +3,8 @@ package geddit
import ( import (
"context" "context"
"fmt" "fmt"
"net/http"
"net/url"
"strings" "strings"
) )
@ -14,9 +16,9 @@ import (
// Note: The "limit" parameter in searches is prone to inconsistent // Note: The "limit" parameter in searches is prone to inconsistent
// behaviour. // behaviour.
type SearchService interface { type SearchService interface {
Posts(query string, opts ...SearchOpt) *PostSearcher Posts(ctx context.Context, query string, subreddits []string, opts ...SearchOptionSetter) (*Links, *Response, error)
Subreddits(query string, opts ...SearchOpt) *SubredditSearcher Subreddits(ctx context.Context, query string, opts ...SearchOptionSetter) (*Subreddits, *Response, error)
Users(query string, opts ...SearchOpt) *UserSearcher Users(ctx context.Context, query string, opts ...SearchOptionSetter) (*Users, *Response, error)
} }
// SearchServiceOp implements the VoteService interface // SearchServiceOp implements the VoteService interface
@ -26,60 +28,96 @@ type SearchServiceOp struct {
var _ SearchService = &SearchServiceOp{} var _ SearchService = &SearchServiceOp{}
// SearchOptions define options used in search queries.
type SearchOptions = url.Values
func newSearchOptions(opts ...SearchOptionSetter) SearchOptions {
searchOptions := make(SearchOptions)
for _, opt := range opts {
opt(searchOptions)
}
return searchOptions
}
// SearchOptionSetter sets values for the options.
type SearchOptionSetter func(opts SearchOptions)
// SetAfter sets the after option.
func SetAfter(v string) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("after", v)
}
}
// SetBefore sets the before option.
func SetBefore(v string) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("before", v)
}
}
// SetLimit sets the limit option.
// Warning: It seems like setting the limit to 1 sometimes returns 0 results.
func SetLimit(v int) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("limit", fmt.Sprint(v))
}
}
// SetSort sets the sort option.
func SetSort(v Sort) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("sort", v.String())
}
}
// SetTimespan sets the timespan option.
func SetTimespan(v Timespan) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("timespan", v.String())
}
}
// setType sets the type option.
func setType(v string) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("type", v)
}
}
// setQuery sets the q option.
func setQuery(v string) SearchOptionSetter {
return func(opts SearchOptions) {
opts.Set("q", v)
}
}
// setRestrict sets the restrict_sr option.
func setRestrict(opts SearchOptions) {
opts.Set("restruct_sr", "true")
}
// Posts searches for posts. // Posts searches for posts.
// By default, it searches for the most relevant posts of all time. // If the list of subreddits is empty, the search is run against r/all.
func (s *SearchServiceOp) Posts(query string, opts ...SearchOpt) *PostSearcher { func (s *SearchServiceOp) Posts(ctx context.Context, query string, subreddits []string, opts ...SearchOptionSetter) (*Links, *Response, error) {
sr := new(PostSearcher) opts = append(opts, setType("link"), setQuery(query))
sr.client = s.client
sr.opts.Query = query
sr.opts.Type = "link"
sr.opts.Sort = SortRelevance.String()
sr.opts.Timespan = TimespanAll.String()
for _, opt := range opts {
opt(sr)
}
return sr
}
// Subreddits searches for subreddits.
func (s *SearchServiceOp) Subreddits(query string, opts ...SearchOpt) *SubredditSearcher {
sr := new(SubredditSearcher)
sr.client = s.client
sr.opts.Query = query
sr.opts.Type = "sr"
for _, opt := range opts {
opt(sr)
}
return sr
}
// Users searches for users.
func (s *SearchServiceOp) Users(query string, opts ...SearchOpt) *UserSearcher {
sr := new(UserSearcher)
sr.client = s.client
sr.opts.Query = query
sr.opts.Type = "user"
for _, opt := range opts {
opt(sr)
}
return sr
}
// PostSearcher helps conducts searches that return posts.
type PostSearcher struct {
clientSearcher
subreddits []string
after string
Results []Link
}
func (s *PostSearcher) search(ctx context.Context) (*Links, *Response, error) {
path := "search" path := "search"
if len(s.subreddits) > 0 { if len(subreddits) > 0 {
path = fmt.Sprintf("r/%s/search", strings.Join(s.subreddits, "+")) path = fmt.Sprintf("r/%s/search", strings.Join(subreddits, "+"))
opts = append(opts, setRestrict)
} }
root, resp, err := s.clientSearcher.Do(ctx, path) 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 { if err != nil {
return nil, resp, err return nil, resp, err
} }
@ -87,232 +125,48 @@ func (s *PostSearcher) search(ctx context.Context) (*Links, *Response, error) {
return root.getLinks(), resp, nil return root.getLinks(), resp, nil
} }
// Search runs the searcher. // Subreddits searches for subreddits.
// The first return value tells the user if there are // The Sort and Timespan options don't affect the results for this search.
// more results that were cut off (due to the limit). func (s *SearchServiceOp) Subreddits(ctx context.Context, query string, opts ...SearchOptionSetter) (*Subreddits, *Response, error) {
func (s *PostSearcher) Search(ctx context.Context) (bool, *Response, error) { opts = append(opts, setType("sr"), setQuery(query))
root, resp, err := s.search(ctx) form := newSearchOptions(opts...)
if err != nil {
return false, resp, err
}
s.Results = root.Links
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// More runs the searcher again and adds to the results.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *PostSearcher) More(ctx context.Context) (bool, *Response, error) {
if s.after == "" {
return s.Search(ctx)
}
s.setAfter(s.after)
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = append(s.Results, root.Links...)
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// All runs the searcher until it yields no more results.
// The limit is set to 100, just to make the least amount
// of requests possible. It is reset to its original value after.
func (s *PostSearcher) All(ctx context.Context) error {
limit := s.opts.Limit
s.setLimit(100)
defer s.setLimit(limit)
var ok = true
var err error
for ok {
ok, _, err = s.More(ctx)
if err != nil {
return err
}
}
return nil
}
// SubredditSearcher helps conducts searches that return subreddits.
type SubredditSearcher struct {
clientSearcher
after string
Results []Subreddit
}
func (s *SubredditSearcher) search(ctx context.Context) (*Subreddits, *Response, error) {
path := "search" path := "search"
root, resp, err := s.clientSearcher.Do(ctx, path) 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 { if err != nil {
return nil, resp, err return nil, resp, err
} }
return root.getSubreddits(), resp, nil return root.getSubreddits(), resp, nil
} }
// Search runs the searcher. // Users searches for users.
// The first return value tells the user if there are // The Sort and Timespan options don't affect the results for this search.
// more results that were cut off (due to the limit). func (s *SearchServiceOp) Users(ctx context.Context, query string, opts ...SearchOptionSetter) (*Users, *Response, error) {
func (s *SubredditSearcher) Search(ctx context.Context) (bool, *Response, error) { opts = append(opts, setType("user"), setQuery(query))
root, resp, err := s.search(ctx) form := newSearchOptions(opts...)
if err != nil {
return false, resp, err
}
s.Results = root.Subreddits
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// More runs the searcher again and adds to the results.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *SubredditSearcher) More(ctx context.Context) (bool, *Response, error) {
if s.after == "" {
return s.Search(ctx)
}
s.setAfter(s.after)
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = append(s.Results, root.Subreddits...)
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// All runs the searcher until it yields no more results.
// The limit is set to 100, just to make the least amount
// of requests possible. It is reset to its original value after.
func (s *SubredditSearcher) All(ctx context.Context) error {
limit := s.opts.Limit
s.setLimit(100)
defer s.setLimit(limit)
var ok = true
var err error
for ok {
ok, _, err = s.More(ctx)
if err != nil {
return err
}
}
return nil
}
// UserSearcher helps conducts searches that return users.
type UserSearcher struct {
clientSearcher
after string
Results []User
}
func (s *UserSearcher) search(ctx context.Context) (*Users, *Response, error) {
path := "search" path := "search"
root, resp, err := s.clientSearcher.Do(ctx, path) 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 { if err != nil {
return nil, resp, err return nil, resp, err
} }
return root.getUsers(), resp, nil return root.getUsers(), resp, nil
} }
// Search runs the searcher.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserSearcher) Search(ctx context.Context) (bool, *Response, error) {
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = root.Users
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// More runs the searcher again and adds to the results.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserSearcher) More(ctx context.Context) (bool, *Response, error) {
if s.after == "" {
return s.Search(ctx)
}
s.setAfter(s.after)
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = append(s.Results, root.Users...)
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// All runs the searcher until it yields no more results.
// The limit is set to 100, just to make the least amount
// of requests possible. It is reset to its original value after.
func (s *UserSearcher) All(ctx context.Context) error {
limit := s.opts.Limit
s.setLimit(100)
defer s.setLimit(limit)
var ok = true
var err error
for ok {
ok, _, err = s.More(ctx)
if err != nil {
return err
}
}
return nil
}

View file

@ -1,134 +0,0 @@
package geddit
import (
"context"
"net/http"
)
// todo: query parameter "show" = "all"
// Searcher defines some parameters common to all requests
// used to conduct searches against the Reddit API.
type Searcher interface {
setAfter(string)
setBefore(string)
setLimit(int)
setSort(Sort)
setTimespan(Timespan)
}
// Contains all options used for searching.
// Not all are used for every search endpoint.
// For example, for getting a user's posts, "q" is not used.
// After/Before are used as the anchor points for subsequent searches.
// Limit is the maximum number of items to be returned (default: 25, max: 100).
// Sort: hot, new, top, controversial, etc.
// Timespan: hour, day, week, month, year, all.
type searchOpts struct {
Query string `url:"q,omitempty"`
Type string `url:"type,omitempty"`
After string `url:"after,omitempty"`
Before string `url:"before,omitempty"`
Limit int `url:"limit,omitempty"`
RestrictSubreddits bool `url:"restrict_sr,omitempty"`
Sort string `url:"sort,omitempty"`
Timespan string `url:"t,omitempty"`
}
type clientSearcher struct {
client *Client
opts searchOpts
}
var _ Searcher = &clientSearcher{}
func (s *clientSearcher) setAfter(v string) {
s.opts.After = v
}
func (s *clientSearcher) setBefore(v string) {
s.opts.Before = v
}
func (s *clientSearcher) setLimit(v int) {
s.opts.Limit = v
}
func (s *clientSearcher) setSort(v Sort) {
s.opts.Sort = v.String()
}
func (s *clientSearcher) setTimespan(v Timespan) {
s.opts.Timespan = v.String()
}
func (s *clientSearcher) Do(ctx context.Context, path string) (*rootListing, *Response, error) {
path, err := addOptions(path, s.opts)
if err != nil {
return nil, nil, err
}
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, resp, nil
}
// SearchOpt sets search options.
type SearchOpt func(s Searcher)
// SetAfter sets the after option.
func SetAfter(v string) SearchOpt {
return func(s Searcher) {
s.setAfter(v)
}
}
// SetBefore sets the before option.
func SetBefore(v string) SearchOpt {
return func(s Searcher) {
s.setBefore(v)
}
}
// SetLimit sets the limit option.
func SetLimit(v int) SearchOpt {
return func(s Searcher) {
s.setLimit(v)
}
}
// SetSort sets the sort option.
func SetSort(v Sort) SearchOpt {
return func(s Searcher) {
s.setSort(v)
}
}
// SetTimespan sets the timespan option.
func SetTimespan(v Timespan) SearchOpt {
return func(s Searcher) {
s.setTimespan(v)
}
}
// FromSubreddits is an option that restricts the
// search to happen in the specified subreddits.
// If none are specified, it's like searching r/all.
// This option is only applicable to the PostSearcher.
func FromSubreddits(subreddits ...string) SearchOpt {
return func(s Searcher) {
if ps, ok := s.(*PostSearcher); ok {
ps.subreddits = subreddits
ps.opts.RestrictSubreddits = len(subreddits) > 0
}
}
}

560
user.go
View file

@ -14,20 +14,20 @@ type UserService interface {
GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error) GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error)
UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) UsernameAvailable(ctx context.Context, username string) (bool, *Response, error)
Overview(opts ...SearchOpt) *UserCommentPostSearcher Overview(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error)
OverviewOf(username string, opts ...SearchOpt) *UserCommentPostSearcher OverviewOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error)
Posts(opts ...SearchOpt) *UserPostSearcher Posts(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
PostsOf(username string, opts ...SearchOpt) *UserPostSearcher PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Response, error)
Comments(opts ...SearchOpt) *UserCommentSearcher Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error)
CommentsOf(username string, opts ...SearchOpt) *UserCommentSearcher CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error)
GetUpvoted(opts ...SearchOpt) *UserPostSearcher Saved(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error)
GetDownvoted(opts ...SearchOpt) *UserPostSearcher Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
GetHidden(opts ...SearchOpt) *UserPostSearcher Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
GetSaved(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
GetGilded(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error)
Friend(ctx context.Context, username string, note string) (interface{}, *Response, error) Friend(ctx context.Context, username string, note string) (interface{}, *Response, error)
Unblock(ctx context.Context, username string) (*Response, error) Unblock(ctx context.Context, username string) (*Response, error)
@ -105,13 +105,13 @@ func (s *UserServiceOp) GetMultipleByID(ctx context.Context, ids ...string) (map
return nil, nil, err return nil, nil, err
} }
root := new(map[string]*UserShort) root := make(map[string]*UserShort)
resp, err := s.client.Do(ctx, req, root) resp, err := s.client.Do(ctx, req, root)
if err != nil { if err != nil {
return nil, resp, err return nil, resp, err
} }
return *root, resp, nil return root, resp, nil
} }
// UsernameAvailable checks whether a username is available for registration // UsernameAvailable checks whether a username is available for registration
@ -142,101 +142,186 @@ func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string)
} }
// Overview returns a list of the client's comments and links // Overview returns a list of the client's comments and links
func (s *UserServiceOp) Overview(opts ...SearchOpt) *UserCommentPostSearcher { func (s *UserServiceOp) Overview(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error) {
return s.OverviewOf(s.client.Username, opts...) return s.OverviewOf(ctx, s.client.Username, opts...)
} }
// OverviewOf returns a list of the user's comments and links // OverviewOf returns a list of the user's comments and links
func (s *UserServiceOp) OverviewOf(username string, opts ...SearchOpt) *UserCommentPostSearcher { func (s *UserServiceOp) OverviewOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error) {
sr := new(UserCommentPostSearcher) form := newSearchOptions(opts...)
sr.client = s.client
sr.username = username path := fmt.Sprintf("user/%s/overview", username)
sr.where = "overview" path = addQuery(path, form)
for _, opt := range opts {
opt(sr) req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, nil, err
} }
return sr
root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, resp, err
}
return root.getLinks(), root.getComments(), resp, nil
} }
// Posts returns a list of the client's posts. // Posts returns a list of the client's posts.
func (s *UserServiceOp) Posts(opts ...SearchOpt) *UserPostSearcher { func (s *UserServiceOp) Posts(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
return s.PostsOf(s.client.Username, opts...) return s.PostsOf(ctx, s.client.Username, opts...)
} }
// PostsOf returns a list of the user's posts. // PostsOf returns a list of the user's posts.
func (s *UserServiceOp) PostsOf(username string, opts ...SearchOpt) *UserPostSearcher { func (s *UserServiceOp) PostsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Links, *Response, error) {
sr := new(UserPostSearcher) form := newSearchOptions(opts...)
sr.client = s.client
sr.username = username path := fmt.Sprintf("user/%s/submitted", username)
sr.where = "submitted" path = addQuery(path, form)
for _, opt := range opts {
opt(sr) req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, err
} }
return sr
root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root.getLinks(), resp, nil
} }
// Comments returns a list of the client's comments. // Comments returns a list of the client's comments.
func (s *UserServiceOp) Comments(opts ...SearchOpt) *UserCommentSearcher { func (s *UserServiceOp) Comments(ctx context.Context, opts ...SearchOptionSetter) (*Comments, *Response, error) {
return s.CommentsOf(s.client.Username, opts...) return s.CommentsOf(ctx, s.client.Username, opts...)
} }
// CommentsOf returns a list of a user's comments. // CommentsOf returns a list of the user's comments.
func (s *UserServiceOp) CommentsOf(username string, opts ...SearchOpt) *UserCommentSearcher { func (s *UserServiceOp) CommentsOf(ctx context.Context, username string, opts ...SearchOptionSetter) (*Comments, *Response, error) {
sr := new(UserCommentSearcher) form := newSearchOptions(opts...)
sr.client = s.client
sr.username = username path := fmt.Sprintf("user/%s/comments", username)
for _, opt := range opts { path = addQuery(path, form)
opt(sr)
} req, err := s.client.NewRequest(http.MethodGet, path, nil)
return sr if err != nil {
return nil, nil, err
} }
// GetUpvoted returns a list of the client's upvoted submissions. root := new(rootListing)
func (s *UserServiceOp) GetUpvoted(opts ...SearchOpt) *UserPostSearcher { resp, err := s.client.Do(ctx, req, root)
sr := new(UserPostSearcher) if err != nil {
sr.client = s.client return nil, resp, err
sr.username = s.client.Username
sr.where = "upvoted"
for _, opt := range opts {
opt(sr)
}
return sr
} }
// GetDownvoted returns a list of the client's downvoted submissions. return root.getComments(), resp, nil
func (s *UserServiceOp) GetDownvoted(opts ...SearchOpt) *UserPostSearcher {
sr := new(UserPostSearcher)
sr.client = s.client
sr.username = s.client.Username
sr.where = "downvoted"
for _, opt := range opts {
opt(sr)
}
return sr
} }
// GetHidden returns a list of the client's hidden submissions. // Saved returns a list of the user's saved posts and comments.
func (s *UserServiceOp) GetHidden(opts ...SearchOpt) *UserPostSearcher { func (s *UserServiceOp) Saved(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Comments, *Response, error) {
sr := new(UserPostSearcher) form := newSearchOptions(opts...)
sr.client = s.client
sr.username = s.client.Username
sr.where = "hidden"
for _, opt := range opts {
opt(sr)
}
return sr
}
// GetSaved returns a list of the client's saved comments and links.
func (s *UserServiceOp) GetSaved(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) {
path := fmt.Sprintf("user/%s/saved", s.client.Username) path := fmt.Sprintf("user/%s/saved", s.client.Username)
return s.getCommentsAndLinks(ctx, path, opts) path = addQuery(path, form)
req, err := s.client.NewRequest(http.MethodGet, path, nil)
if err != nil {
return nil, nil, nil, err
} }
// GetGilded returns a list of the client's gilded comments and links. root := new(rootListing)
func (s *UserServiceOp) GetGilded(ctx context.Context, opts *ListOptions) (*CommentsLinks, *Response, error) { resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, nil, resp, err
}
return root.getLinks(), root.getComments(), resp, nil
}
// Upvoted returns a list of the user's upvoted posts.
func (s *UserServiceOp) Upvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/upvoted", s.client.Username)
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.getLinks(), resp, nil
}
// Downvoted returns a list of the user's downvoted posts.
func (s *UserServiceOp) Downvoted(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/downvoted", s.client.Username)
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.getLinks(), resp, nil
}
// Hidden returns a list of the user's hidden posts.
func (s *UserServiceOp) Hidden(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/hidden", s.client.Username)
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.getLinks(), resp, nil
}
// Gilded returns a list of the user's gilded posts.
func (s *UserServiceOp) Gilded(ctx context.Context, opts ...SearchOptionSetter) (*Links, *Response, error) {
form := newSearchOptions(opts...)
path := fmt.Sprintf("user/%s/gilded", s.client.Username) path := fmt.Sprintf("user/%s/gilded", s.client.Username)
return s.getCommentsAndLinks(ctx, path, 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.getLinks(), resp, nil
} }
// Friend creates or updates a "friend" relationship // Friend creates or updates a "friend" relationship
@ -245,12 +330,12 @@ func (s *UserServiceOp) GetGilded(ctx context.Context, opts *ListOptions) (*Comm
// note: a string no longer than 300 characters // note: a string no longer than 300 characters
func (s *UserServiceOp) Friend(ctx context.Context, username string, note string) (interface{}, *Response, error) { func (s *UserServiceOp) Friend(ctx context.Context, username string, note string) (interface{}, *Response, error) {
type request struct { type request struct {
Name string `url:"name"` Username string `url:"name"`
Note string `url:"note"` Note string `url:"note"`
} }
path := fmt.Sprintf("api/v1/me/friends/%s", username) path := fmt.Sprintf("api/v1/me/friends/%s", username)
body := request{Name: username, Note: note} body := request{Username: username, Note: note}
_, err := s.client.NewRequest(http.MethodPut, path, body) _, err := s.client.NewRequest(http.MethodPut, path, body)
if err != nil { if err != nil {
@ -288,312 +373,3 @@ func (s *UserServiceOp) Unfriend(ctx context.Context, username string) (*Respons
return s.client.Do(ctx, req, nil) return s.client.Do(ctx, req, nil)
} }
func (s *UserServiceOp) getLinks(ctx context.Context, path string, opts *ListOptions) (*Links, *Response, error) {
listing, resp, err := s.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
return listing.getLinks(), resp, nil
}
func (s *UserServiceOp) getComments(ctx context.Context, path string, opts *ListOptions) (*Comments, *Response, error) {
listing, resp, err := s.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
return listing.getComments(), resp, nil
}
func (s *UserServiceOp) getCommentsAndLinks(ctx context.Context, path string, opts *ListOptions) (*CommentsLinks, *Response, error) {
listing, resp, err := s.getListing(ctx, path, opts)
if err != nil {
return nil, resp, err
}
v := new(CommentsLinks)
v.Comments = listing.getComments().Comments
v.Links = listing.getLinks().Links
v.After = listing.getAfter()
v.Before = listing.getBefore()
return v, resp, nil
}
func (s *UserServiceOp) getListing(ctx context.Context, path string, opts *ListOptions) (*rootListing, *Response, error) {
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
}
root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root, resp, err
}
// UserPostSearcher finds the posts of a user.
type UserPostSearcher struct {
clientSearcher
username string
// where can be submitted, upvoted, downvoted, hidden
// https://www.reddit.com/dev/api/#GET_user_{username}_{where}
where string
after string
Results []Link
}
func (s *UserPostSearcher) search(ctx context.Context) (*Links, *Response, error) {
path := fmt.Sprintf("user/%s/%s", s.username, s.where)
root, resp, err := s.clientSearcher.Do(ctx, path)
if err != nil {
return nil, resp, err
}
return root.getLinks(), resp, nil
}
// Search runs the searcher.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserPostSearcher) Search(ctx context.Context) (bool, *Response, error) {
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = root.Links
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// More runs the searcher again and adds to the results.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserPostSearcher) More(ctx context.Context) (bool, *Response, error) {
if s.after == "" {
return s.Search(ctx)
}
s.setAfter(s.after)
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = append(s.Results, root.Links...)
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// All runs the searcher until it yields no more results.
// The limit is set to 100, just to make the least amount
// of requests possible. It is reset to its original value after.
func (s *UserPostSearcher) All(ctx context.Context) error {
limit := s.opts.Limit
s.setLimit(100)
defer s.setLimit(limit)
var ok = true
var err error
for ok {
ok, _, err = s.More(ctx)
if err != nil {
return err
}
}
return nil
}
// UserCommentSearcher finds the comments of a user.
type UserCommentSearcher struct {
clientSearcher
username string
after string
Results []Comment
}
func (s *UserCommentSearcher) search(ctx context.Context) (*Comments, *Response, error) {
path := fmt.Sprintf("user/%s/comments", s.username)
root, resp, err := s.clientSearcher.Do(ctx, path)
if err != nil {
return nil, resp, err
}
return root.getComments(), resp, nil
}
// Search runs the searcher.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserCommentSearcher) Search(ctx context.Context) (bool, *Response, error) {
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = root.Comments
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// More runs the searcher again and adds to the results.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserCommentSearcher) More(ctx context.Context) (bool, *Response, error) {
if s.after == "" {
return s.Search(ctx)
}
s.setAfter(s.after)
root, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results = append(s.Results, root.Comments...)
s.after = root.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// All runs the searcher until it yields no more results.
// The limit is set to 100, just to make the least amount
// of requests possible. It is reset to its original value after.
func (s *UserCommentSearcher) All(ctx context.Context) error {
limit := s.opts.Limit
s.setLimit(100)
defer s.setLimit(limit)
var ok = true
var err error
for ok {
ok, _, err = s.More(ctx)
if err != nil {
return err
}
}
return nil
}
// UserCommentPostSearcher finds the comments and posts of a user.
type UserCommentPostSearcher struct {
clientSearcher
username string
where string
after string
Results struct {
Comments []Comment `json:"comments"`
Posts []Link `json:"posts"`
}
}
func (s *UserCommentPostSearcher) search(ctx context.Context) (*Comments, *Links, *Response, error) {
path := fmt.Sprintf("user/%s/%s", s.username, s.where)
root, resp, err := s.clientSearcher.Do(ctx, path)
if err != nil {
return nil, nil, resp, err
}
return root.getComments(), root.getLinks(), resp, nil
}
// Search runs the searcher.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserCommentPostSearcher) Search(ctx context.Context) (bool, *Response, error) {
rootComments, rootPosts, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results.Comments = rootComments.Comments
s.Results.Posts = rootPosts.Links
s.after = rootComments.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// More runs the searcher again and adds to the results.
// The first return value tells the user if there are
// more results that were cut off (due to the limit).
func (s *UserCommentPostSearcher) More(ctx context.Context) (bool, *Response, error) {
if s.after == "" {
return s.Search(ctx)
}
s.setAfter(s.after)
rootComments, rootPosts, resp, err := s.search(ctx)
if err != nil {
return false, resp, err
}
s.Results.Comments = append(s.Results.Comments, rootComments.Comments...)
s.Results.Posts = append(s.Results.Posts, rootPosts.Links...)
s.after = rootComments.After
// if the "after" value is non-empty, it
// means there are more results to come.
moreResultsExist := s.after != ""
return moreResultsExist, resp, nil
}
// All runs the searcher until it yields no more results.
// The limit is set to 100, just to make the least amount
// of requests possible. It is reset to its original value after.
func (s *UserCommentPostSearcher) All(ctx context.Context) error {
limit := s.opts.Limit
s.setLimit(100)
defer s.setLimit(limit)
var ok = true
var err error
for ok {
ok, _, err = s.More(ctx)
if err != nil {
return err
}
}
return nil
}