Add SearchService
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
@@ -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}
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"`
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user