c897c14b23
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
458 lines
14 KiB
Go
458 lines
14 KiB
Go
package geddit
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"net/http"
|
|
"net/url"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
// SubredditService handles communication with the subreddit
|
|
// related methods of the Reddit API
|
|
type SubredditService interface {
|
|
GetByName(ctx context.Context, name string) (*Subreddit, *Response, error)
|
|
|
|
GetPopular(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
GetNew(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
GetGold(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
GetDefault(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
|
|
GetMineWhereSubscriber(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
GetMineWhereContributor(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
GetMineWhereModerator(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
GetMineWhereStreams(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error)
|
|
|
|
GetHotLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error)
|
|
GetBestLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error)
|
|
GetNewLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error)
|
|
GetRisingLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error)
|
|
GetControversialLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error)
|
|
GetTopLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error)
|
|
|
|
// GetSticky1(ctx context.Context, name string) (interface{}, *Response, error)
|
|
// GetSticky2(ctx context.Context, name string) (interface{}, *Response, error)
|
|
|
|
Subscribe(ctx context.Context, names ...string) (*Response, error)
|
|
SubscribeByID(ctx context.Context, ids ...string) (*Response, error)
|
|
Unsubscribe(ctx context.Context, names ...string) (*Response, error)
|
|
UnsubscribeByID(ctx context.Context, ids ...string) (*Response, error)
|
|
|
|
StreamLinks(ctx context.Context, names ...string) (<-chan Link, chan<- bool, error)
|
|
}
|
|
|
|
// SubredditServiceOp implements the SubredditService interface
|
|
type SubredditServiceOp struct {
|
|
client *Client
|
|
}
|
|
|
|
var _ SubredditService = &SubredditServiceOp{}
|
|
|
|
// GetByName gets a subreddit by name
|
|
func (s *SubredditServiceOp) GetByName(ctx context.Context, name string) (*Subreddit, *Response, error) {
|
|
if name == "" {
|
|
return nil, nil, errors.New("empty subreddit name provided")
|
|
}
|
|
|
|
path := fmt.Sprintf("r/%s/about", name)
|
|
req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
root := new(subredditRoot)
|
|
resp, err := s.client.Do(ctx, req, root)
|
|
if err != nil {
|
|
return nil, resp, err
|
|
}
|
|
|
|
return root.Data, resp, nil
|
|
}
|
|
|
|
// GetPopular returns popular subreddits
|
|
func (s *SubredditServiceOp) GetPopular(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/popular", opts)
|
|
}
|
|
|
|
// GetNew returns new subreddits
|
|
func (s *SubredditServiceOp) GetNew(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/new", opts)
|
|
}
|
|
|
|
// GetGold returns gold subreddits
|
|
func (s *SubredditServiceOp) GetGold(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/gold", opts)
|
|
}
|
|
|
|
// GetDefault returns default subreddits
|
|
func (s *SubredditServiceOp) GetDefault(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/default", opts)
|
|
}
|
|
|
|
// GetMineWhereSubscriber returns the list of subreddits the client is subscribed to
|
|
func (s *SubredditServiceOp) GetMineWhereSubscriber(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/mine/subscriber", opts)
|
|
}
|
|
|
|
// GetMineWhereContributor returns the list of subreddits the client is a contributor to
|
|
func (s *SubredditServiceOp) GetMineWhereContributor(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/mine/contributor", opts)
|
|
}
|
|
|
|
// GetMineWhereModerator returns the list of subreddits the client is a moderator in
|
|
func (s *SubredditServiceOp) GetMineWhereModerator(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
return s.getSubreddits(ctx, "subreddits/mine/contributor", opts)
|
|
}
|
|
|
|
// GetMineWhereStreams returns the list of subreddits the client is subscribed to and has hosted videos in
|
|
func (s *SubredditServiceOp) GetMineWhereStreams(ctx context.Context, opts *ListOptions) (*Subreddits, *Response, error) {
|
|
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
|
|
// 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)
|
|
// PLUS the number of posts from the limit parameter (which is 25 by default)
|
|
func (s *SubredditServiceOp) GetHotLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
return s.getLinks(ctx, sortHot, opts, names...)
|
|
}
|
|
|
|
// GetBestLinks returns the best links
|
|
// 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)
|
|
// PLUS the number of posts from the limit parameter (which is 25 by default)
|
|
func (s *SubredditServiceOp) GetBestLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
return s.getLinks(ctx, sortBest, opts, names...)
|
|
}
|
|
|
|
// GetNewLinks returns the new links
|
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
|
func (s *SubredditServiceOp) GetNewLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
return s.getLinks(ctx, sortNew, opts, names...)
|
|
}
|
|
|
|
// GetRisingLinks returns the rising links
|
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
|
func (s *SubredditServiceOp) GetRisingLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
return s.getLinks(ctx, sortRising, opts, names...)
|
|
}
|
|
|
|
// GetControversialLinks returns the controversial links
|
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
|
func (s *SubredditServiceOp) GetControversialLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
return s.getLinks(ctx, sortControversial, opts, names...)
|
|
}
|
|
|
|
// GetTopLinks returns the top links
|
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
|
func (s *SubredditServiceOp) GetTopLinks(ctx context.Context, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
return s.getLinks(ctx, sortTop, opts, names...)
|
|
}
|
|
|
|
type sticky int
|
|
|
|
const (
|
|
sticky1 sticky = iota + 1
|
|
sticky2
|
|
)
|
|
|
|
// // GetSticky1 returns the first stickied post on a subreddit (if it exists)
|
|
// func (s *SubredditServiceOp) GetSticky1(ctx context.Context, name string) (interface{}, *Response, error) {
|
|
// return s.getSticky(ctx, name, sticky1)
|
|
// }
|
|
|
|
// // GetSticky2 returns the second stickied post on a subreddit (if it exists)
|
|
// func (s *SubredditServiceOp) GetSticky2(ctx context.Context, name string) (interface{}, *Response, error) {
|
|
// return s.getSticky(ctx, name, sticky2)
|
|
// }
|
|
|
|
// Subscribe subscribes to subreddits based on their name
|
|
// Returns {} on success
|
|
func (s *SubredditServiceOp) Subscribe(ctx context.Context, names ...string) (*Response, error) {
|
|
form := url.Values{}
|
|
form.Set("action", "sub")
|
|
form.Set("sr_name", strings.Join(names, ","))
|
|
return s.handleSubscription(ctx, form)
|
|
}
|
|
|
|
// SubscribeByID subscribes to subreddits based on their id
|
|
// Returns {} on success
|
|
func (s *SubredditServiceOp) SubscribeByID(ctx context.Context, ids ...string) (*Response, error) {
|
|
form := url.Values{}
|
|
form.Set("action", "sub")
|
|
form.Set("sr", strings.Join(ids, ","))
|
|
return s.handleSubscription(ctx, form)
|
|
}
|
|
|
|
// Unsubscribe unsubscribes from subreddits
|
|
// Returns {} on success
|
|
func (s *SubredditServiceOp) Unsubscribe(ctx context.Context, names ...string) (*Response, error) {
|
|
form := url.Values{}
|
|
form.Set("action", "unsub")
|
|
form.Set("sr_name", strings.Join(names, ","))
|
|
return s.handleSubscription(ctx, form)
|
|
}
|
|
|
|
// UnsubscribeByID unsubscribes from subreddits based on their id
|
|
// Returns {} on success
|
|
func (s *SubredditServiceOp) UnsubscribeByID(ctx context.Context, ids ...string) (*Response, error) {
|
|
form := url.Values{}
|
|
form.Set("action", "unsub")
|
|
form.Set("sr", strings.Join(ids, ","))
|
|
return s.handleSubscription(ctx, form)
|
|
}
|
|
|
|
func (s *SubredditServiceOp) handleSubscription(ctx context.Context, form url.Values) (*Response, error) {
|
|
path := "api/subscribe"
|
|
req, err := s.client.NewPostForm(path, form)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
resp, err := s.client.Do(ctx, req, nil)
|
|
if err != nil {
|
|
return resp, err
|
|
}
|
|
|
|
return resp, nil
|
|
}
|
|
|
|
func (s *SubredditServiceOp) getSubreddits(ctx context.Context, path string, opts *ListOptions) (*Subreddits, *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
|
|
}
|
|
|
|
l := new(Subreddits)
|
|
|
|
if root.Data != nil {
|
|
l.Subreddits = root.Data.Things.Subreddits
|
|
l.After = root.Data.After
|
|
l.Before = root.Data.Before
|
|
}
|
|
|
|
return l, resp, nil
|
|
}
|
|
|
|
func (s *SubredditServiceOp) getLinks(ctx context.Context, sort sort, opts *ListOptions, names ...string) (*Links, *Response, error) {
|
|
path := sorts[sort]
|
|
if len(names) > 0 {
|
|
path = fmt.Sprintf("r/%s/%s", strings.Join(names, "+"), sorts[sort])
|
|
}
|
|
|
|
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
|
|
}
|
|
|
|
l := new(Links)
|
|
|
|
if root.Data != nil {
|
|
l.Links = root.Data.Things.Links
|
|
l.After = root.Data.After
|
|
l.Before = root.Data.Before
|
|
}
|
|
|
|
return l, resp, nil
|
|
}
|
|
|
|
// getSticky returns one of the 2 stickied posts of the subreddit
|
|
// Num should be equal to 1 or 2, depending on which one you want
|
|
// If it's <= 1, it's 1
|
|
// If it's >= 2, it's 2
|
|
// todo
|
|
// func (s *SubredditServiceOp) getSticky(ctx context.Context, name string, num sticky) (interface{}, *Response, error) {
|
|
// type query struct {
|
|
// Num sticky `url:"num"`
|
|
// }
|
|
|
|
// path := fmt.Sprintf("r/%s/about/sticky", name)
|
|
// path, err := addOptions(path, query{num})
|
|
// if err != nil {
|
|
// return nil, nil, err
|
|
// }
|
|
|
|
// req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
|
// if err != nil {
|
|
// return nil, nil, err
|
|
// }
|
|
|
|
// var root []rootListing
|
|
// resp, err := s.client.Do(ctx, req, &root)
|
|
// if err != nil {
|
|
// return nil, resp, err
|
|
// }
|
|
|
|
// // test, _ := json.MarshalIndent(root, "", " ")
|
|
// // fmt.Println(string(test))
|
|
|
|
// linkRoot := new(linkRoot)
|
|
|
|
// link := root[0].Data.Children[0]
|
|
// byteValue, err := json.Marshal(link)
|
|
// if err != nil {
|
|
// return nil, resp, err
|
|
// }
|
|
|
|
// err = json.Unmarshal(byteValue, linkRoot)
|
|
// if err != nil {
|
|
// return nil, resp, err
|
|
// }
|
|
|
|
// // these are all the comments in the post
|
|
// comments := root[1].Data.Children
|
|
|
|
// var commentsRoot []commentRoot
|
|
// byteValue, err = json.Marshal(comments)
|
|
// if err != nil {
|
|
// return nil, resp, err
|
|
// }
|
|
|
|
// err = json.Unmarshal(byteValue, &commentsRoot)
|
|
// if err != nil {
|
|
// return nil, resp, err
|
|
// }
|
|
|
|
// test, _ := json.MarshalIndent(commentsRoot, "", " ")
|
|
// fmt.Println(string(test))
|
|
|
|
// for _, comment := range commentsRoot {
|
|
// if string(comment.Data.RepliesRaw) == `""` {
|
|
// comment.Data.Replies = nil
|
|
// continue
|
|
// }
|
|
|
|
// // var
|
|
// }
|
|
|
|
// return commentsRoot, resp, nil
|
|
// }
|
|
|
|
// func handleComments(comments []commentRoot) {
|
|
// for _, comment := range comments {
|
|
// if string(comment.Data.RepliesRaw) == `""` {
|
|
// comment.Data.Replies = nil
|
|
// continue
|
|
// }
|
|
|
|
// }
|
|
// }
|
|
|
|
// StreamLinks returns a channel that receives new submissions from the subreddits
|
|
// To stop the stream, simply send a bool value to the stop channel
|
|
func (s *SubredditServiceOp) StreamLinks(ctx context.Context, names ...string) (<-chan Link, chan<- bool, error) {
|
|
if len(names) == 0 {
|
|
return nil, nil, errors.New("must specify at least one subreddit")
|
|
}
|
|
|
|
submissionCh := make(chan Link)
|
|
stop := make(chan bool, 1)
|
|
|
|
go func() {
|
|
// todo: if the post with the before gets deleted, you keep getting 0 posts
|
|
var last *Timestamp
|
|
for {
|
|
select {
|
|
case <-stop:
|
|
close(submissionCh)
|
|
return
|
|
default:
|
|
sl, _, err := s.GetNewLinks(ctx, nil, names...)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
|
|
var newest *Timestamp
|
|
for i, submission := range sl.Links {
|
|
if i == 0 {
|
|
newest = submission.Created
|
|
}
|
|
if last == nil {
|
|
submissionCh <- submission
|
|
continue
|
|
}
|
|
if last.Before(*submission.Created) {
|
|
submissionCh <- submission
|
|
}
|
|
}
|
|
last = newest
|
|
}
|
|
<-time.After(time.Second * 3)
|
|
fmt.Println()
|
|
}
|
|
}()
|
|
|
|
// go func() {
|
|
// var before string
|
|
// for {
|
|
// select {
|
|
// case <-stop:
|
|
// close(submissionCh)
|
|
// return
|
|
// default:
|
|
// sl, _, err := s.GetSubmissions(ctx, SortNew, &ListOptions{Before: before}, names...)
|
|
// if err != nil {
|
|
// continue
|
|
// }
|
|
// fmt.Printf("Received %d posts\n", len(sl.Submissions))
|
|
|
|
// if len(sl.Submissions) == 0 {
|
|
// continue
|
|
// }
|
|
|
|
// for _, submission := range sl.Submissions {
|
|
// submissionCh <- submission
|
|
// }
|
|
// before = sl.Submissions[0].FullID
|
|
// }
|
|
// <-time.After(time.Second * 5)
|
|
// fmt.Println()
|
|
// }
|
|
// }()
|
|
|
|
return submissionCh, stop, nil
|
|
}
|