Add SearchService

Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
Vartan Benohanian
2020-05-04 20:51:16 -04:00
parent 014ecb6c16
commit dc31cd8dbc
5 changed files with 256 additions and 27 deletions
+2
View File
@@ -92,6 +92,7 @@ type Client struct {
Flair FlairService Flair FlairService
Link LinkService Link LinkService
Listings ListingsService Listings ListingsService
Search SearchService
Subreddit SubredditService Subreddit SubredditService
User UserService User UserService
Vote VoteService Vote VoteService
@@ -120,6 +121,7 @@ func newClient(httpClient *http.Client) *Client {
c.Flair = &FlairServiceOp{client: c} c.Flair = &FlairServiceOp{client: c}
c.Link = &LinkServiceOp{client: c} c.Link = &LinkServiceOp{client: c}
c.Listings = &ListingsServiceOp{client: c} c.Listings = &ListingsServiceOp{client: c}
c.Search = &SearchServiceOp{client: c}
c.Subreddit = &SubredditServiceOp{client: c} c.Subreddit = &SubredditServiceOp{client: c}
c.User = &UserServiceOp{client: c} c.User = &UserServiceOp{client: c}
c.Vote = &VoteServiceOp{client: c} c.Vote = &VoteServiceOp{client: c}
+198
View File
@@ -0,0 +1,198 @@
package geddit
import (
"context"
"fmt"
"net/http"
)
// SearchService handles communication with the search
// related methods of the Reddit API
// IMPORTANT: for searches to include NSFW results, the
// user must check the following in their preferences:
// "include not safe for work (NSFW) search results in searches"
type SearchService interface {
SearchUsers(ctx context.Context, q string, opts *ListOptions) (*Users, *Response, error)
SearchLinksByRelevance(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByHottest(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByTop(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByComments(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByRelevanceInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByHottestInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByTopInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error)
SearchLinksByCommentsInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error)
SearchSubreddits(ctx context.Context, q string, opts *ListOptions) (*Subreddits, *Response, error)
}
// SearchServiceOp implements the VoteService interface
type SearchServiceOp struct {
client *Client
}
var _ SearchService = &SearchServiceOp{}
type searchQuery struct {
ListOptions
Query string `url:"q,omitempty"`
Type string `url:"type,omitempty"`
Sort string `url:"sort,omitempty"`
}
func newSearchQuery(query, _type, sort string, opts *ListOptions) *searchQuery {
if opts == nil {
opts = &ListOptions{}
}
return &searchQuery{
ListOptions: *opts,
Query: query,
Type: _type,
Sort: sort,
}
}
// SearchUsers searches for users
func (s *SearchServiceOp) SearchUsers(ctx context.Context, q string, opts *ListOptions) (*Users, *Response, error) {
query := newSearchQuery(q, "user", "", opts)
root, resp, err := s.search(ctx, "", query)
if err != nil {
return nil, nil, err
}
return root.getUsers(), resp, nil
}
// SearchLinksByRelevance searches for link sorted by relevance to the search query
func (s *SearchServiceOp) SearchLinksByRelevance(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortRelevance], opts)
root, resp, err := s.search(ctx, "", query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByHottest searches for the hottest links
func (s *SearchServiceOp) SearchLinksByHottest(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortHot], opts)
root, resp, err := s.search(ctx, "", query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByTop searches for the top links
func (s *SearchServiceOp) SearchLinksByTop(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortTop], opts)
root, resp, err := s.search(ctx, "", query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByComments searches for links with the highest number of comments
func (s *SearchServiceOp) SearchLinksByComments(ctx context.Context, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortComments], opts)
root, resp, err := s.search(ctx, "", query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByRelevanceInSubreddit searches for link sorted by relevance to the search query in the specified subreddit
func (s *SearchServiceOp) SearchLinksByRelevanceInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortRelevance], opts)
root, resp, err := s.search(ctx, subreddit, query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByHottestInSubreddit searches for the hottest links in the specified subreddit
func (s *SearchServiceOp) SearchLinksByHottestInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortHot], opts)
root, resp, err := s.search(ctx, subreddit, query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByTopInSubreddit searches for the top links in the specified subreddit
func (s *SearchServiceOp) SearchLinksByTopInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortTop], opts)
root, resp, err := s.search(ctx, subreddit, query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchLinksByCommentsInSubreddit searches for links with the highest number of comments in the specified subreddit
func (s *SearchServiceOp) SearchLinksByCommentsInSubreddit(ctx context.Context, subreddit, q string, opts *ListOptions) (*Links, *Response, error) {
query := newSearchQuery(q, "link", sorts[sortComments], opts)
root, resp, err := s.search(ctx, subreddit, query)
if err != nil {
return nil, nil, err
}
return root.getLinks(), resp, nil
}
// SearchSubreddits searches for subreddits
func (s *SearchServiceOp) SearchSubreddits(ctx context.Context, q string, opts *ListOptions) (*Subreddits, *Response, error) {
query := newSearchQuery(q, "sr", "", opts)
root, resp, err := s.search(ctx, "", query)
if err != nil {
return nil, resp, err
}
return root.getSubreddits(), resp, nil
}
func (s *SearchServiceOp) search(ctx context.Context, subreddit string, opts *searchQuery) (*rootListing, *Response, error) {
path := "search"
if subreddit != "" {
path = fmt.Sprintf("r/%s/search?restrict_sr=true", subreddit)
}
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, nil
}
-20
View File
@@ -111,26 +111,6 @@ func (s *SubredditServiceOp) GetMineWhereStreams(ctx context.Context, opts *List
return s.getSubreddits(ctx, "subreddits/mine/contributor", opts) return s.getSubreddits(ctx, "subreddits/mine/contributor", opts)
} }
type sort int
const (
sortHot sort = iota
sortBest
sortNew
sortRising
sortControversial
sortTop
)
var sorts = [...]string{
"hot",
"best",
"new",
"rising",
"controversial",
"top",
}
// GetHotLinks returns the hot links // GetHotLinks returns the hot links
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to // If no subreddit names are provided, then it runs the search against all those the client is subscribed to
// IMPORTANT: for subreddits, this will include the stickied posts (if any) // IMPORTANT: for subreddits, this will include the stickied posts (if any)
+56 -2
View File
@@ -14,6 +14,32 @@ const (
kindMode = "more" kindMode = "more"
) )
type sort int
const (
sortHot sort = iota
sortBest
sortNew
sortRising
sortControversial
sortTop
sortRelevance
sortComments
)
var sorts = [...]string{
"hot",
"best",
"new",
"rising",
"controversial",
"top",
// for search queries
"relevance",
"comments",
}
type root struct { type root struct {
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
Data interface{} `json:"data,omitempty"` Data interface{} `json:"data,omitempty"`
@@ -35,6 +61,7 @@ type Listing struct {
// Things are stuff! // Things are stuff!
type Things struct { type Things struct {
Comments []Comment `json:"comments,omitempty"` Comments []Comment `json:"comments,omitempty"`
Users []User `json:"users,omitempty"`
Links []Link `json:"links,omitempty"` Links []Link `json:"links,omitempty"`
Subreddits []Subreddit `json:"subreddits,omitempty"` Subreddits []Subreddit `json:"subreddits,omitempty"`
// todo: add the other kinds of things // todo: add the other kinds of things
@@ -45,6 +72,11 @@ type commentRoot struct {
Data *Comment `json:"data,omitempty"` Data *Comment `json:"data,omitempty"`
} }
type userRoot struct {
Kind string `json:"kind,omitempty"`
Data *User `json:"data,omitempty"`
}
type linkRoot struct { type linkRoot struct {
Kind string `json:"kind,omitempty"` Kind string `json:"kind,omitempty"`
Data *Link `json:"data,omitempty"` Data *Link `json:"data,omitempty"`
@@ -71,6 +103,10 @@ func (l *Things) UnmarshalJSON(b []byte) error {
l.Comments = append(l.Comments, *root.Data) l.Comments = append(l.Comments, *root.Data)
} }
case kindAccount: case kindAccount:
root := new(userRoot)
if err := json.Unmarshal(byteValue, root); err == nil && root.Data != nil {
l.Users = append(l.Users, *root.Data)
}
case kindLink: case kindLink:
root := new(linkRoot) root := new(linkRoot)
if err := json.Unmarshal(byteValue, root); err == nil && root.Data != nil { if err := json.Unmarshal(byteValue, root); err == nil && root.Data != nil {
@@ -157,8 +193,9 @@ type Link struct {
// If neither, it will be nil // If neither, it will be nil
Likes *bool `json:"likes"` Likes *bool `json:"likes"`
Score int `json:"score"` Score int `json:"score"`
NumberOfComments int `json:"num_comments"` UpvoteRatio float32 `json:"upvote_ratio"`
NumberOfComments int `json:"num_comments"`
SubredditID string `json:"subreddit_id,omitempty"` SubredditID string `json:"subreddit_id,omitempty"`
SubredditName string `json:"subreddit,omitempty"` SubredditName string `json:"subreddit,omitempty"`
@@ -219,6 +256,16 @@ func (rl *rootListing) getComments() *Comments {
return v return v
} }
func (rl *rootListing) getUsers() *Users {
v := new(Users)
if rl != nil && rl.Data != nil {
v.Users = rl.Data.Things.Users
v.After = rl.Data.After
v.Before = rl.Data.Before
}
return v
}
func (rl *rootListing) getLinks() *Links { func (rl *rootListing) getLinks() *Links {
v := new(Links) v := new(Links)
if rl != nil && rl.Data != nil { if rl != nil && rl.Data != nil {
@@ -246,6 +293,13 @@ type Comments struct {
Before string `json:"before"` Before string `json:"before"`
} }
// Users is a list of users
type Users struct {
Users []User `json:"users,omitempty"`
After string `json:"after"`
Before string `json:"before"`
}
// Subreddits is a list of subreddits // Subreddits is a list of subreddits
type Subreddits struct { type Subreddits struct {
Subreddits []Subreddit `json:"subreddits,omitempty"` Subreddits []Subreddit `json:"subreddits,omitempty"`
-5
View File
@@ -59,11 +59,6 @@ type UserServiceOp struct {
var _ UserService = &UserServiceOp{} var _ UserService = &UserServiceOp{}
type userRoot struct {
Kind *string `json:"kind,omitempty"`
Data *User `json:"data,omitempty"`
}
// User represents a Reddit user // User represents a Reddit user
type User struct { type User struct {
// is not the full ID, watch out // is not the full ID, watch out