Handle listings better by using custom unmarshaling
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
parent
460554e19e
commit
7922711d51
9 changed files with 988 additions and 345 deletions
55
comment.go
55
comment.go
|
@ -25,61 +25,6 @@ type CommentServiceOp struct {
|
||||||
|
|
||||||
var _ CommentService = &CommentServiceOp{}
|
var _ CommentService = &CommentServiceOp{}
|
||||||
|
|
||||||
type commentRoot struct {
|
|
||||||
Kind *string `json:"kind,omitempty"`
|
|
||||||
Data *Comment `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type commentRootListing struct {
|
|
||||||
Kind *string `json:"kind,omitempty"`
|
|
||||||
Data *struct {
|
|
||||||
Dist int `json:"dist"`
|
|
||||||
Roots []commentRoot `json:"children,omitempty"`
|
|
||||||
After string `json:"after,omitempty"`
|
|
||||||
Before string `json:"before,omitempty"`
|
|
||||||
} `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Comment is a comment posted by a user
|
|
||||||
type Comment struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
FullID string `json:"name,omitempty"`
|
|
||||||
ParentID string `json:"parent_id,omitempty"`
|
|
||||||
Permalink string `json:"permalink,omitempty"`
|
|
||||||
|
|
||||||
Body string `json:"body,omitempty"`
|
|
||||||
BodyHTML string `json:"body_html,omitempty"`
|
|
||||||
Author string `json:"author,omitempty"`
|
|
||||||
AuthorID string `json:"author_fullname,omitempty"`
|
|
||||||
AuthorFlairText string `json:"author_flair_text,omitempty"`
|
|
||||||
|
|
||||||
Subreddit string `json:"subreddit,omitempty"`
|
|
||||||
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
|
|
||||||
SubredditID string `json:"subreddit_id,omitempty"`
|
|
||||||
|
|
||||||
Score int `json:"score"`
|
|
||||||
Controversiality int `json:"controversiality"`
|
|
||||||
|
|
||||||
Created float64 `json:"created"`
|
|
||||||
CreatedUTC float64 `json:"created_utc"`
|
|
||||||
|
|
||||||
LinkID string `json:"link_id,omitempty"`
|
|
||||||
|
|
||||||
// These don't appear when submitting a comment
|
|
||||||
LinkTitle string `json:"link_title,omitempty"`
|
|
||||||
LinkPermalink string `json:"link_permalink,omitempty"`
|
|
||||||
LinkAuthor string `json:"link_author,omitempty"`
|
|
||||||
LinkNumComments int `json:"num_comments"`
|
|
||||||
|
|
||||||
IsSubmitter bool `json:"is_submitter"`
|
|
||||||
ScoreHidden bool `json:"score_hidden"`
|
|
||||||
Saved bool `json:"saved"`
|
|
||||||
Stickied bool `json:"stickied"`
|
|
||||||
Locked bool `json:"locked"`
|
|
||||||
CanGild bool `json:"can_gild"`
|
|
||||||
NSFW bool `json:"over_18"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommentList holds information about a list of comments
|
// CommentList holds information about a list of comments
|
||||||
// The after and before fields help decide the anchor point for a subsequent
|
// The after and before fields help decide the anchor point for a subsequent
|
||||||
// call that returns a list
|
// call that returns a list
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"reflect"
|
"reflect"
|
||||||
"testing"
|
"testing"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
var expectedCommentSubmitOrEdit = &Comment{
|
var expectedCommentSubmitOrEdit = &Comment{
|
||||||
|
@ -15,7 +16,6 @@ var expectedCommentSubmitOrEdit = &Comment{
|
||||||
Permalink: "/r/subreddit/comments/test1/some_thread/test2/",
|
Permalink: "/r/subreddit/comments/test1/some_thread/test2/",
|
||||||
|
|
||||||
Body: "test comment",
|
Body: "test comment",
|
||||||
BodyHTML: "<div class=\"md\"><p>test comment</p>\n</div>",
|
|
||||||
Author: "reddit_username",
|
Author: "reddit_username",
|
||||||
AuthorID: "t2_user1",
|
AuthorID: "t2_user1",
|
||||||
AuthorFlairText: "Flair",
|
AuthorFlairText: "Flair",
|
||||||
|
@ -27,8 +27,7 @@ var expectedCommentSubmitOrEdit = &Comment{
|
||||||
Score: 1,
|
Score: 1,
|
||||||
Controversiality: 0,
|
Controversiality: 0,
|
||||||
|
|
||||||
Created: 1588147787,
|
Created: &Timestamp{time.Date(2020, 4, 28, 20, 9, 47, 0, time.UTC)},
|
||||||
CreatedUTC: 1588118987,
|
|
||||||
|
|
||||||
LinkID: "t3_link1",
|
LinkID: "t3_link1",
|
||||||
}
|
}
|
||||||
|
|
192
geddit.go
192
geddit.go
|
@ -343,7 +343,6 @@ func CheckResponse(r *http.Response) error {
|
||||||
|
|
||||||
// ListOptions are the optional parameters to the various endpoints that return lists
|
// ListOptions are the optional parameters to the various endpoints that return lists
|
||||||
type ListOptions struct {
|
type ListOptions struct {
|
||||||
Sort string `url:"sort,omitempty"`
|
|
||||||
Type string `url:"type,omitempty"` // links or comments
|
Type string `url:"type,omitempty"` // links or comments
|
||||||
|
|
||||||
// For getting submissions
|
// For getting submissions
|
||||||
|
@ -354,7 +353,6 @@ type ListOptions struct {
|
||||||
After string `url:"after,omitempty"`
|
After string `url:"after,omitempty"`
|
||||||
Before string `url:"before,omitempty"`
|
Before string `url:"before,omitempty"`
|
||||||
Limit int `url:"limit,omitempty"` // default: 25
|
Limit int `url:"limit,omitempty"` // default: 25
|
||||||
Count int `url:"count,omitempty"` // default: 0
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addOptions(s string, opt interface{}) (string, error) {
|
func addOptions(s string, opt interface{}) (string, error) {
|
||||||
|
@ -388,6 +386,61 @@ type root struct {
|
||||||
Data interface{} `json:"data,omitempty"`
|
Data interface{} `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type rootListing struct {
|
||||||
|
Kind string `json:"kind,omitempty"`
|
||||||
|
Data *Listing `json:"data"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Listing holds things coming from the Reddit API
|
||||||
|
// It also contains the after/before anchors useful for subsequent requests
|
||||||
|
type Listing struct {
|
||||||
|
Things Things `json:"children"`
|
||||||
|
After string `json:"after"`
|
||||||
|
Before string `json:"before"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Things are stuff!
|
||||||
|
type Things struct {
|
||||||
|
Comments []Comment `json:"comments,omitempty"`
|
||||||
|
Links []Link `json:"links,omitempty"`
|
||||||
|
Subreddits []Subreddit `json:"subreddits,omitempty"`
|
||||||
|
// todo: add the other kinds of things
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (l *Things) UnmarshalJSON(b []byte) error {
|
||||||
|
var children []map[string]interface{}
|
||||||
|
if err := json.Unmarshal(b, &children); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, child := range children {
|
||||||
|
byteValue, _ := json.Marshal(child)
|
||||||
|
switch child["kind"] {
|
||||||
|
case kindComment:
|
||||||
|
root := new(commentRoot)
|
||||||
|
if err := json.Unmarshal(byteValue, root); err == nil && root.Data != nil {
|
||||||
|
l.Comments = append(l.Comments, *root.Data)
|
||||||
|
}
|
||||||
|
case kindAccount:
|
||||||
|
case kindLink:
|
||||||
|
root := new(linkRoot)
|
||||||
|
if err := json.Unmarshal(byteValue, root); err == nil && root.Data != nil {
|
||||||
|
l.Links = append(l.Links, *root.Data)
|
||||||
|
}
|
||||||
|
case kindMessage:
|
||||||
|
case kindSubreddit:
|
||||||
|
root := new(subredditRoot)
|
||||||
|
if err := json.Unmarshal(byteValue, root); err == nil && root.Data != nil {
|
||||||
|
l.Subreddits = append(l.Subreddits, *root.Data)
|
||||||
|
}
|
||||||
|
case kindAward:
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
kindListing string = "Listing"
|
kindListing string = "Listing"
|
||||||
kindComment string = "t1"
|
kindComment string = "t1"
|
||||||
|
@ -397,3 +450,138 @@ const (
|
||||||
kindSubreddit string = "t5"
|
kindSubreddit string = "t5"
|
||||||
kindAward string = "t6"
|
kindAward string = "t6"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type commentRoot struct {
|
||||||
|
Kind string `json:"kind,omitempty"`
|
||||||
|
Data *Comment `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type linkRoot struct {
|
||||||
|
Kind string `json:"kind,omitempty"`
|
||||||
|
Data *Link `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type subredditRoot struct {
|
||||||
|
Kind string `json:"kind,omitempty"`
|
||||||
|
Data *Subreddit `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comment is a comment posted by a user
|
||||||
|
type Comment struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
FullID string `json:"name,omitempty"`
|
||||||
|
ParentID string `json:"parent_id,omitempty"`
|
||||||
|
Permalink string `json:"permalink,omitempty"`
|
||||||
|
|
||||||
|
Body string `json:"body,omitempty"`
|
||||||
|
Author string `json:"author,omitempty"`
|
||||||
|
AuthorID string `json:"author_fullname,omitempty"`
|
||||||
|
AuthorFlairText string `json:"author_flair_text,omitempty"`
|
||||||
|
AuthorFlairID string `json:"author_flair_template_id,omitempty"`
|
||||||
|
|
||||||
|
Subreddit string `json:"subreddit,omitempty"`
|
||||||
|
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
|
||||||
|
SubredditID string `json:"subreddit_id,omitempty"`
|
||||||
|
|
||||||
|
Score int `json:"score"`
|
||||||
|
Controversiality int `json:"controversiality"`
|
||||||
|
|
||||||
|
Created *Timestamp `json:"created_utc,omitempty"`
|
||||||
|
Edited *Timestamp `json:"edited,omitempty"`
|
||||||
|
|
||||||
|
LinkID string `json:"link_id,omitempty"`
|
||||||
|
|
||||||
|
// These don't appear when submitting a comment
|
||||||
|
LinkTitle string `json:"link_title,omitempty"`
|
||||||
|
LinkPermalink string `json:"link_permalink,omitempty"`
|
||||||
|
LinkAuthor string `json:"link_author,omitempty"`
|
||||||
|
LinkNumComments int `json:"num_comments"`
|
||||||
|
|
||||||
|
IsSubmitter bool `json:"is_submitter"`
|
||||||
|
ScoreHidden bool `json:"score_hidden"`
|
||||||
|
Saved bool `json:"saved"`
|
||||||
|
Stickied bool `json:"stickied"`
|
||||||
|
Locked bool `json:"locked"`
|
||||||
|
CanGild bool `json:"can_gild"`
|
||||||
|
NSFW bool `json:"over_18"`
|
||||||
|
|
||||||
|
// If a comment has no replies, its "replies" value is "",
|
||||||
|
// which the unmarshaler doesn't like
|
||||||
|
// So we capture this varying field in RepliesRaw, and then
|
||||||
|
// fill it in Replies
|
||||||
|
RepliesRaw json.RawMessage `json:"replies,omitempty"`
|
||||||
|
Replies []commentRoot `json:"-"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link is a submitted post on Reddit
|
||||||
|
type Link struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
FullID string `json:"name,omitempty"`
|
||||||
|
Created *Timestamp `json:"created_utc,omitempty"`
|
||||||
|
Edited *Timestamp `json:"edited,omitempty"`
|
||||||
|
|
||||||
|
Permalink string `json:"permalink,omitempty"`
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
Body string `json:"selftext,omitempty"`
|
||||||
|
|
||||||
|
Score int `json:"score"`
|
||||||
|
NumberOfComments int `json:"num_comments"`
|
||||||
|
|
||||||
|
SubredditID string `json:"subreddit_id,omitempty"`
|
||||||
|
SubredditName string `json:"subreddit,omitempty"`
|
||||||
|
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
|
||||||
|
|
||||||
|
AuthorID string `json:"author_fullname,omitempty"`
|
||||||
|
AuthorName string `json:"author,omitempty"`
|
||||||
|
|
||||||
|
Spoiler bool `json:"spoiler"`
|
||||||
|
Locked bool `json:"locked"`
|
||||||
|
NSFW bool `json:"over_18"`
|
||||||
|
IsSelfPost bool `json:"is_self"`
|
||||||
|
Saved bool `json:"saved"`
|
||||||
|
Stickied bool `json:"stickied"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Subreddit holds information about a subreddit
|
||||||
|
type Subreddit struct {
|
||||||
|
ID string `json:"id,omitempty"`
|
||||||
|
FullID string `json:"name,omitempty"`
|
||||||
|
Created *Timestamp `json:"created_utc,omitempty"`
|
||||||
|
|
||||||
|
URL string `json:"url,omitempty"`
|
||||||
|
Name string `json:"display_name,omitempty"`
|
||||||
|
NamePrefixed string `json:"display_name_prefixed,omitempty"`
|
||||||
|
Title string `json:"title,omitempty"`
|
||||||
|
PublicDescription string `json:"public_description,omitempty"`
|
||||||
|
Type string `json:"subreddit_type,omitempty"`
|
||||||
|
SuggestedCommentSort string `json:"suggested_comment_sort,omitempty"`
|
||||||
|
|
||||||
|
Subscribers int `json:"subscribers"`
|
||||||
|
ActiveUserCount *int `json:"active_user_count,omitempty"`
|
||||||
|
NSFW bool `json:"over18"`
|
||||||
|
UserIsMod bool `json:"user_is_moderator"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *rootListing) getLinks() *LinkList {
|
||||||
|
if rl == nil || rl.Data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &LinkList{
|
||||||
|
Links: rl.Data.Things.Links,
|
||||||
|
After: rl.Data.After,
|
||||||
|
Before: rl.Data.Before,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rl *rootListing) getComments() *CommentList {
|
||||||
|
if rl == nil || rl.Data == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return &CommentList{
|
||||||
|
Comments: rl.Data.Things.Comments,
|
||||||
|
After: rl.Data.After,
|
||||||
|
Before: rl.Data.Before,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
54
listings.go
54
listings.go
|
@ -2,7 +2,6 @@ package geddit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -30,14 +29,15 @@ type listingRoot struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Listing holds various types of things that all come from the Reddit API
|
// Listing holds various types of things that all come from the Reddit API
|
||||||
type Listing struct {
|
// type Listing struct {
|
||||||
Links []*Submission `json:"links,omitempty"`
|
// Links []*Submission `json:"links,omitempty"`
|
||||||
Comments []*Comment `json:"comments,omitempty"`
|
// Comments []*Comment `json:"comments,omitempty"`
|
||||||
Subreddits []*Subreddit `json:"subreddits,omitempty"`
|
// Subreddits []*Subreddit `json:"subreddits,omitempty"`
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Get gets a list of things based on their IDs
|
// Get gets a list of things based on their IDs
|
||||||
// Only links, comments, and subreddits are allowed
|
// Only links, comments, and subreddits are allowed
|
||||||
|
// todo: only links, comments, subreddits
|
||||||
func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) (*Listing, *Response, error) {
|
func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) (*Listing, *Response, error) {
|
||||||
type query struct {
|
type query struct {
|
||||||
IDs []string `url:"id,comma"`
|
IDs []string `url:"id,comma"`
|
||||||
|
@ -54,51 +54,13 @@ func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) (*Listing, *
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := new(listingRoot)
|
root := new(rootListing)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.Data == nil {
|
return root.Data, resp, nil
|
||||||
return nil, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
l := new(Listing)
|
|
||||||
|
|
||||||
for _, result := range root.Data.Children {
|
|
||||||
kind, ok1 := result["kind"].(string)
|
|
||||||
data, ok2 := result["data"]
|
|
||||||
|
|
||||||
if ok1 && ok2 {
|
|
||||||
byteValue, err := json.Marshal(data)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
var v interface{}
|
|
||||||
switch kind {
|
|
||||||
case kindComment:
|
|
||||||
v = new(Comment)
|
|
||||||
l.Comments = append(l.Comments, v.(*Comment))
|
|
||||||
case kindLink:
|
|
||||||
v = new(Submission)
|
|
||||||
l.Links = append(l.Links, v.(*Submission))
|
|
||||||
case kindSubreddit:
|
|
||||||
v = new(Subreddit)
|
|
||||||
l.Subreddits = append(l.Subreddits, v.(*Subreddit))
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(byteValue, v)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return l, resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: do by_id next
|
// todo: do by_id next
|
||||||
|
|
34
private-messages.go
Normal file
34
private-messages.go
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
package geddit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/url"
|
||||||
|
)
|
||||||
|
|
||||||
|
// PrivateMessageService handles communication with the private message
|
||||||
|
// related methods of the Reddit API
|
||||||
|
type PrivateMessageService interface {
|
||||||
|
BlockUser(ctx context.Context, messageID string) (*Response, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivateMessageServiceOp implements the PrivateMessageService interface
|
||||||
|
type PrivateMessageServiceOp struct {
|
||||||
|
client *Client
|
||||||
|
}
|
||||||
|
|
||||||
|
var _ PrivateMessageService = &PrivateMessageServiceOp{}
|
||||||
|
|
||||||
|
// BlockUser blocks a user based on the ID of the private message
|
||||||
|
func (s *PrivateMessageServiceOp) BlockUser(ctx context.Context, messageID string) (*Response, error) {
|
||||||
|
path := "api/block"
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Set("id", messageID)
|
||||||
|
|
||||||
|
req, err := s.client.NewPostForm(path, form)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.Do(ctx, req, nil)
|
||||||
|
}
|
439
subreddit.go
439
subreddit.go
|
@ -5,7 +5,9 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
"strings"
|
"strings"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SubredditService handles communication with the subreddit
|
// SubredditService handles communication with the subreddit
|
||||||
|
@ -23,14 +25,22 @@ type SubredditService interface {
|
||||||
GetMineWhereModerator(ctx context.Context, opts *ListOptions) (*SubredditList, *Response, error)
|
GetMineWhereModerator(ctx context.Context, opts *ListOptions) (*SubredditList, *Response, error)
|
||||||
GetMineWhereStreams(ctx context.Context, opts *ListOptions) (*SubredditList, *Response, error)
|
GetMineWhereStreams(ctx context.Context, opts *ListOptions) (*SubredditList, *Response, error)
|
||||||
|
|
||||||
GetHotPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
|
GetHotLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
|
||||||
GetNewPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
|
GetBestLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
|
||||||
GetRisingPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
|
GetNewLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
|
||||||
GetControversialPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
|
GetRisingLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
|
||||||
GetTopPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
|
GetControversialLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
|
||||||
|
GetTopLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
|
||||||
|
|
||||||
GetSticky1(ctx context.Context, name string) (interface{}, *Response, error)
|
// GetSticky1(ctx context.Context, name string) (interface{}, *Response, error)
|
||||||
GetSticky2(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
|
// SubredditServiceOp implements the SubredditService interface
|
||||||
|
@ -40,42 +50,6 @@ type SubredditServiceOp struct {
|
||||||
|
|
||||||
var _ SubredditService = &SubredditServiceOp{}
|
var _ SubredditService = &SubredditServiceOp{}
|
||||||
|
|
||||||
type subredditRoot struct {
|
|
||||||
Kind *string `json:"kind,omitempty"`
|
|
||||||
Data *Subreddit `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type subredditRootListing struct {
|
|
||||||
Kind *string `json:"kind,omitempty"`
|
|
||||||
Data *struct {
|
|
||||||
Dist int `json:"dist"`
|
|
||||||
Roots []subredditRoot `json:"children,omitempty"`
|
|
||||||
After string `json:"after,omitempty"`
|
|
||||||
Before string `json:"before,omitempty"`
|
|
||||||
} `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subreddit holds information about a subreddit
|
|
||||||
type Subreddit struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
FullID string `json:"name,omitempty"`
|
|
||||||
Created float64 `json:"created"`
|
|
||||||
CreatedUTC float64 `json:"created_utc"`
|
|
||||||
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
DisplayName string `json:"display_name,omitempty"`
|
|
||||||
DisplayNamePrefixed string `json:"display_name_prefixed,omitempty"`
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
PublicDescription string `json:"public_description,omitempty"`
|
|
||||||
Type string `json:"subreddit_type,omitempty"`
|
|
||||||
SuggestedCommentSort string `json:"suggested_comment_sort,omitempty"`
|
|
||||||
|
|
||||||
Subscribers int `json:"subscribers"`
|
|
||||||
ActiveUserCount *int `json:"active_user_count,omitempty"`
|
|
||||||
NSFW bool `json:"over18"`
|
|
||||||
UserIsMod bool `json:"user_is_moderator"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubredditList holds information about a list of subreddits
|
// SubredditList holds information about a list of subreddits
|
||||||
// The after and before fields help decide the anchor point for a subsequent
|
// The after and before fields help decide the anchor point for a subsequent
|
||||||
// call that returns a list
|
// call that returns a list
|
||||||
|
@ -85,58 +59,14 @@ type SubredditList struct {
|
||||||
Before string `json:"before,omitempty"`
|
Before string `json:"before,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type submissionRoot struct {
|
// LinkList holds information about a list of links
|
||||||
Kind *string `json:"kind,omitempty"`
|
|
||||||
Data *Submission `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type submissionRootListing struct {
|
|
||||||
Kind *string `json:"kind,omitempty"`
|
|
||||||
Data *struct {
|
|
||||||
Dist int `json:"dist"`
|
|
||||||
Roots []submissionRoot `json:"children,omitempty"`
|
|
||||||
After string `json:"after,omitempty"`
|
|
||||||
Before string `json:"before,omitempty"`
|
|
||||||
} `json:"data,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// Submission is a submitted post on Reddit
|
|
||||||
type Submission struct {
|
|
||||||
ID string `json:"id,omitempty"`
|
|
||||||
FullID string `json:"name,omitempty"`
|
|
||||||
Created float64 `json:"created"`
|
|
||||||
CreatedUTC float64 `json:"created_utc"`
|
|
||||||
|
|
||||||
Permalink string `json:"permalink,omitempty"`
|
|
||||||
URL string `json:"url,omitempty"`
|
|
||||||
|
|
||||||
Title string `json:"title,omitempty"`
|
|
||||||
Body string `json:"selftext,omitempty"`
|
|
||||||
Score int `json:"score"`
|
|
||||||
NumberOfComments int `json:"num_comments"`
|
|
||||||
|
|
||||||
SubredditID string `json:"t5_2qo4s,omitempty"`
|
|
||||||
SubredditName string `json:"subreddit,omitempty"`
|
|
||||||
SubredditNamePrefixed string `json:"subreddit_name_prefixed,omitempty"`
|
|
||||||
|
|
||||||
AuthorID string `json:"author_fullname,omitempty"`
|
|
||||||
AuthorName string `json:"author,omitempty"`
|
|
||||||
|
|
||||||
Spoiler bool `json:"spoiler"`
|
|
||||||
Locked bool `json:"locked"`
|
|
||||||
NSFW bool `json:"over_18"`
|
|
||||||
IsSelfPost bool `json:"is_self"`
|
|
||||||
Saved bool `json:"saved"`
|
|
||||||
Stickied bool `json:"stickied"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// SubmissionList holds information about a list of subreddits
|
|
||||||
// The after and before fields help decide the anchor point for a subsequent
|
// The after and before fields help decide the anchor point for a subsequent
|
||||||
// call that returns a list
|
// call that returns a list
|
||||||
type SubmissionList struct {
|
// Note: not to be confused with linked lists
|
||||||
Submissions []Submission `json:"submissions,omitempty"`
|
type LinkList struct {
|
||||||
After string `json:"after,omitempty"`
|
Links []Link `json:"submissions,omitempty"`
|
||||||
Before string `json:"before,omitempty"`
|
After string `json:"after,omitempty"`
|
||||||
|
Before string `json:"before,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetByName gets a subreddit by name
|
// GetByName gets a subreddit by name
|
||||||
|
@ -220,44 +150,44 @@ var sorts = [...]string{
|
||||||
"top",
|
"top",
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetHotPosts returns the hot posts
|
// GetHotLinks returns the hot links
|
||||||
// If no subreddit names are provided, then it runs the search against /r/all
|
// 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)
|
||||||
// PLUS the number of posts from the limit parameter (which is 25 by default)
|
// PLUS the number of posts from the limit parameter (which is 25 by default)
|
||||||
func (s *SubredditServiceOp) GetHotPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) GetHotLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
return s.getPosts(ctx, sortHot, opts, names...)
|
return s.getLinks(ctx, sortHot, opts, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetBestPosts returns the best posts
|
// GetBestLinks returns the best links
|
||||||
// If no subreddit names are provided, then it runs the search against /r/all
|
// 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)
|
||||||
// PLUS the number of posts from the limit parameter (which is 25 by default)
|
// PLUS the number of posts from the limit parameter (which is 25 by default)
|
||||||
func (s *SubredditServiceOp) GetBestPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) GetBestLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
return s.getPosts(ctx, sortBest, opts, names...)
|
return s.getLinks(ctx, sortBest, opts, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNewPosts returns the new posts
|
// GetNewLinks returns the new links
|
||||||
// If no subreddit names are provided, then it runs the search against /r/all
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
||||||
func (s *SubredditServiceOp) GetNewPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) GetNewLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
return s.getPosts(ctx, sortNew, opts, names...)
|
return s.getLinks(ctx, sortNew, opts, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRisingPosts returns the rising posts
|
// GetRisingLinks returns the rising links
|
||||||
// If no subreddit names are provided, then it runs the search against /r/all
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
||||||
func (s *SubredditServiceOp) GetRisingPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) GetRisingLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
return s.getPosts(ctx, sortRising, opts, names...)
|
return s.getLinks(ctx, sortRising, opts, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetControversialPosts returns the controversial posts
|
// GetControversialLinks returns the controversial links
|
||||||
// If no subreddit names are provided, then it runs the search against /r/all
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
||||||
func (s *SubredditServiceOp) GetControversialPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) GetControversialLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
return s.getPosts(ctx, sortControversial, opts, names...)
|
return s.getLinks(ctx, sortControversial, opts, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetTopPosts returns the top posts
|
// GetTopLinks returns the top links
|
||||||
// If no subreddit names are provided, then it runs the search against /r/all
|
// If no subreddit names are provided, then it runs the search against all those the client is subscribed to
|
||||||
func (s *SubredditServiceOp) GetTopPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) GetTopLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
return s.getPosts(ctx, sortTop, opts, names...)
|
return s.getLinks(ctx, sortTop, opts, names...)
|
||||||
}
|
}
|
||||||
|
|
||||||
type sticky int
|
type sticky int
|
||||||
|
@ -267,14 +197,65 @@ const (
|
||||||
sticky2
|
sticky2
|
||||||
)
|
)
|
||||||
|
|
||||||
// GetSticky1 returns the first stickied post on a subreddit (if it exists)
|
// // GetSticky1 returns the first stickied post on a subreddit (if it exists)
|
||||||
func (s *SubredditServiceOp) GetSticky1(ctx context.Context, name string) (interface{}, *Response, error) {
|
// func (s *SubredditServiceOp) GetSticky1(ctx context.Context, name string) (interface{}, *Response, error) {
|
||||||
return s.getSticky(ctx, name, sticky1)
|
// 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)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetSticky2 returns the second stickied post on a subreddit (if it exists)
|
// SubscribeByID subscribes to subreddits based on their id
|
||||||
func (s *SubredditServiceOp) GetSticky2(ctx context.Context, name string) (interface{}, *Response, error) {
|
// Returns {} on success
|
||||||
return s.getSticky(ctx, name, sticky2)
|
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) (*SubredditList, *Response, error) {
|
func (s *SubredditServiceOp) getSubreddits(ctx context.Context, path string, opts *ListOptions) (*SubredditList, *Response, error) {
|
||||||
|
@ -288,30 +269,24 @@ func (s *SubredditServiceOp) getSubreddits(ctx context.Context, path string, opt
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := new(subredditRootListing)
|
root := new(rootListing)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.Data == nil {
|
l := new(SubredditList)
|
||||||
return nil, resp, nil
|
|
||||||
|
if root.Data != nil {
|
||||||
|
l.Subreddits = root.Data.Things.Subreddits
|
||||||
|
l.After = root.Data.After
|
||||||
|
l.Before = root.Data.Before
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := new(SubredditList)
|
return l, resp, nil
|
||||||
var subreddits []Subreddit
|
|
||||||
|
|
||||||
for _, child := range root.Data.Roots {
|
|
||||||
subreddits = append(subreddits, *child.Data)
|
|
||||||
}
|
|
||||||
sl.Subreddits = subreddits
|
|
||||||
sl.After = root.Data.After
|
|
||||||
sl.Before = root.Data.Before
|
|
||||||
|
|
||||||
return sl, resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *SubredditServiceOp) getPosts(ctx context.Context, sort sort, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
|
func (s *SubredditServiceOp) getLinks(ctx context.Context, sort sort, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
|
||||||
path := sorts[sort]
|
path := sorts[sort]
|
||||||
if len(names) > 0 {
|
if len(names) > 0 {
|
||||||
path = fmt.Sprintf("r/%s/%s", strings.Join(names, "+"), sorts[sort])
|
path = fmt.Sprintf("r/%s/%s", strings.Join(names, "+"), sorts[sort])
|
||||||
|
@ -327,27 +302,21 @@ func (s *SubredditServiceOp) getPosts(ctx context.Context, sort sort, opts *List
|
||||||
return nil, nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
root := new(submissionRootListing)
|
root := new(rootListing)
|
||||||
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
|
||||||
}
|
}
|
||||||
|
|
||||||
if root.Data == nil {
|
l := new(LinkList)
|
||||||
return nil, resp, nil
|
|
||||||
|
if root.Data != nil {
|
||||||
|
l.Links = root.Data.Things.Links
|
||||||
|
l.After = root.Data.After
|
||||||
|
l.Before = root.Data.Before
|
||||||
}
|
}
|
||||||
|
|
||||||
sl := new(SubmissionList)
|
return l, resp, nil
|
||||||
var submissions []Submission
|
|
||||||
|
|
||||||
for _, child := range root.Data.Roots {
|
|
||||||
submissions = append(submissions, *child.Data)
|
|
||||||
}
|
|
||||||
sl.Submissions = submissions
|
|
||||||
sl.After = root.Data.After
|
|
||||||
sl.Before = root.Data.Before
|
|
||||||
|
|
||||||
return sl, resp, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// getSticky returns one of the 2 stickied posts of the subreddit
|
// getSticky returns one of the 2 stickied posts of the subreddit
|
||||||
|
@ -355,28 +324,154 @@ func (s *SubredditServiceOp) getPosts(ctx context.Context, sort sort, opts *List
|
||||||
// If it's <= 1, it's 1
|
// If it's <= 1, it's 1
|
||||||
// If it's >= 2, it's 2
|
// If it's >= 2, it's 2
|
||||||
// todo
|
// todo
|
||||||
func (s *SubredditServiceOp) getSticky(ctx context.Context, name string, num sticky) (interface{}, *Response, error) {
|
// func (s *SubredditServiceOp) getSticky(ctx context.Context, name string, num sticky) (interface{}, *Response, error) {
|
||||||
// type query struct {
|
// type query struct {
|
||||||
// Num sticky `url:"num"`
|
// Num sticky `url:"num"`
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// path := fmt.Sprintf("r/%s/about/sticky", name)
|
// path := fmt.Sprintf("r/%s/about/sticky", name)
|
||||||
// path, err := addOptions(path, query{num})
|
// path, err := addOptions(path, query{num})
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return nil, nil, err
|
// return nil, nil, err
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
// req, err := s.client.NewRequest(http.MethodGet, path, nil)
|
||||||
// if err != nil {
|
// if err != nil {
|
||||||
// return nil, nil, err
|
// return nil, nil, err
|
||||||
// }
|
// }
|
||||||
|
|
||||||
// root := new(submissionRootListing)
|
// var root []rootListing
|
||||||
// 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 nil, resp, nil
|
// // test, _ := json.MarshalIndent(root, "", " ")
|
||||||
return nil, nil, nil
|
// // 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
|
||||||
}
|
}
|
||||||
|
|
52
timestamp.go
Normal file
52
timestamp.go
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
package geddit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Timestamp represents a time that can be unmarshalled from a JSON string
|
||||||
|
// formatted as either an RFC3339 or Unix timestamp.
|
||||||
|
type Timestamp struct {
|
||||||
|
time.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (t *Timestamp) MarshalJSON() ([]byte, error) {
|
||||||
|
if t == nil || t.Time.IsZero() {
|
||||||
|
return []byte(`false`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
parsed := t.Time.Format(time.RFC3339)
|
||||||
|
return []byte(`"` + parsed + `"`), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
// Time is expected in RFC3339 or Unix format.
|
||||||
|
func (t *Timestamp) UnmarshalJSON(data []byte) (err error) {
|
||||||
|
str := string(data)
|
||||||
|
|
||||||
|
// "edited" for comments and links is either false, or a timestamp
|
||||||
|
if str == "false" {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
f, err := strconv.ParseFloat(str, 64)
|
||||||
|
if err == nil {
|
||||||
|
t.Time = time.Unix(int64(f), 0).UTC()
|
||||||
|
} else {
|
||||||
|
t.Time, err = time.Parse(`"`+time.RFC3339+`"`, str)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Equal reports whether t and u are equal based on time.Equal
|
||||||
|
func (t Timestamp) Equal(u Timestamp) bool {
|
||||||
|
return t.Time.Equal(u.Time)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Before reports whether u is before t based on time.Before
|
||||||
|
func (t Timestamp) Before(u Timestamp) bool {
|
||||||
|
return t.Time.Before(u.Time)
|
||||||
|
}
|
182
timestamp_test.go
Normal file
182
timestamp_test.go
Normal file
|
@ -0,0 +1,182 @@
|
||||||
|
package geddit
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
emptyTimeStr = `"0001-01-01T00:00:00Z"`
|
||||||
|
referenceTimeStr = `"2006-01-02T15:04:05Z"`
|
||||||
|
referenceUnixTimeStr = `1136214245`
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
referenceTime = time.Date(2006, time.January, 02, 15, 04, 05, 0, time.UTC)
|
||||||
|
unixOrigin = time.Unix(0, 0).In(time.UTC)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestTimestamp_Marshal(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
data Timestamp
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
equal bool
|
||||||
|
}{
|
||||||
|
{"Reference", Timestamp{referenceTime}, referenceTimeStr, false, true},
|
||||||
|
{"Empty", Timestamp{}, emptyTimeStr, false, true},
|
||||||
|
{"Mismatch", Timestamp{}, referenceTimeStr, false, false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
out, err := json.Marshal(tc.data)
|
||||||
|
if gotErr := err != nil; gotErr != tc.wantErr {
|
||||||
|
t.Fatalf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
|
||||||
|
}
|
||||||
|
got := string(out)
|
||||||
|
equal := got == tc.want
|
||||||
|
if (got == tc.want) != tc.equal {
|
||||||
|
t.Fatalf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimestamp_Unmarshal(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
data string
|
||||||
|
want Timestamp
|
||||||
|
wantErr bool
|
||||||
|
equal bool
|
||||||
|
}{
|
||||||
|
{"Reference", referenceTimeStr, Timestamp{referenceTime}, false, true},
|
||||||
|
{"ReferenceUnix", referenceUnixTimeStr, Timestamp{referenceTime}, false, true},
|
||||||
|
{"Empty", emptyTimeStr, Timestamp{}, false, true},
|
||||||
|
{"UnixStart", `0`, Timestamp{unixOrigin}, false, true},
|
||||||
|
{"Mismatch", referenceTimeStr, Timestamp{}, false, false},
|
||||||
|
{"MismatchUnix", `0`, Timestamp{}, false, false},
|
||||||
|
{"Invalid", `"asdf"`, Timestamp{referenceTime}, true, false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
var got Timestamp
|
||||||
|
err := json.Unmarshal([]byte(tc.data), &got)
|
||||||
|
if gotErr := err != nil; gotErr != tc.wantErr {
|
||||||
|
t.Fatalf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
equal := got.Equal(tc.want)
|
||||||
|
if equal != tc.equal {
|
||||||
|
t.Fatalf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestTimstamp_MarshalReflexivity(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
data Timestamp
|
||||||
|
}{
|
||||||
|
{"Reference", Timestamp{referenceTime}},
|
||||||
|
{"Empty", Timestamp{}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
data, err := json.Marshal(tc.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: Marshal err=%v", tc.desc, err)
|
||||||
|
}
|
||||||
|
var got Timestamp
|
||||||
|
err = json.Unmarshal(data, &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: Unmarshal err=%v", tc.desc, err)
|
||||||
|
}
|
||||||
|
if !got.Equal(tc.data) {
|
||||||
|
t.Fatalf("%s: %+v != %+v", tc.desc, got, data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type WrappedTimestamp struct {
|
||||||
|
A int
|
||||||
|
Time Timestamp
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedTimstamp_Marshal(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
data WrappedTimestamp
|
||||||
|
want string
|
||||||
|
wantErr bool
|
||||||
|
equal bool
|
||||||
|
}{
|
||||||
|
{"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, true},
|
||||||
|
{"Empty", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, emptyTimeStr), false, true},
|
||||||
|
{"Mismatch", WrappedTimestamp{}, fmt.Sprintf(`{"A":0,"Time":%s}`, referenceTimeStr), false, false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
out, err := json.Marshal(tc.data)
|
||||||
|
if gotErr := err != nil; gotErr != tc.wantErr {
|
||||||
|
t.Fatalf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
|
||||||
|
}
|
||||||
|
got := string(out)
|
||||||
|
equal := got == tc.want
|
||||||
|
if equal != tc.equal {
|
||||||
|
t.Fatalf("%s: got=%s, want=%s, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedTimstamp_Unmarshal(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
data string
|
||||||
|
want WrappedTimestamp
|
||||||
|
wantErr bool
|
||||||
|
equal bool
|
||||||
|
}{
|
||||||
|
{"Reference", referenceTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true},
|
||||||
|
{"ReferenceUnix", referenceUnixTimeStr, WrappedTimestamp{0, Timestamp{referenceTime}}, false, true},
|
||||||
|
{"Empty", emptyTimeStr, WrappedTimestamp{0, Timestamp{}}, false, true},
|
||||||
|
{"UnixStart", `0`, WrappedTimestamp{0, Timestamp{unixOrigin}}, false, true},
|
||||||
|
{"Mismatch", referenceTimeStr, WrappedTimestamp{0, Timestamp{}}, false, false},
|
||||||
|
{"MismatchUnix", `0`, WrappedTimestamp{0, Timestamp{}}, false, false},
|
||||||
|
{"Invalid", `"asdf"`, WrappedTimestamp{0, Timestamp{referenceTime}}, true, false},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
var got Timestamp
|
||||||
|
err := json.Unmarshal([]byte(tc.data), &got)
|
||||||
|
if gotErr := err != nil; gotErr != tc.wantErr {
|
||||||
|
t.Fatalf("%s: gotErr=%v, wantErr=%v, err=%v", tc.desc, gotErr, tc.wantErr, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
equal := got.Time.Equal(tc.want.Time.Time)
|
||||||
|
if equal != tc.equal {
|
||||||
|
t.Fatalf("%s: got=%#v, want=%#v, equal=%v, want=%v", tc.desc, got, tc.want, equal, tc.equal)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestWrappedTimstamp_MarshalReflexivity(t *testing.T) {
|
||||||
|
testCases := []struct {
|
||||||
|
desc string
|
||||||
|
data WrappedTimestamp
|
||||||
|
}{
|
||||||
|
{"Reference", WrappedTimestamp{0, Timestamp{referenceTime}}},
|
||||||
|
{"Empty", WrappedTimestamp{0, Timestamp{}}},
|
||||||
|
}
|
||||||
|
for _, tc := range testCases {
|
||||||
|
bytes, err := json.Marshal(tc.data)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: Marshal err=%v", tc.desc, err)
|
||||||
|
}
|
||||||
|
var got WrappedTimestamp
|
||||||
|
err = json.Unmarshal(bytes, &got)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("%s: Unmarshal err=%v", tc.desc, err)
|
||||||
|
}
|
||||||
|
if !got.Time.Equal(tc.data.Time) {
|
||||||
|
t.Fatalf("%s: %+v != %+v", tc.desc, got, tc.data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
320
user.go
320
user.go
|
@ -2,9 +2,9 @@ package geddit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"net/url"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UserService handles communication with the user
|
// UserService handles communication with the user
|
||||||
|
@ -14,8 +14,36 @@ 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)
|
||||||
|
|
||||||
GetComments(ctx context.Context, sort sort, opts *ListOptions) (*CommentList, *Response, error)
|
// returns the client's links
|
||||||
GetCommentsOf(ctx context.Context, username string, sort sort, opts *ListOptions) (*CommentList, *Response, error)
|
GetHotLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetNewLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetTopLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetControversialLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
|
||||||
|
// returns the links of the user with the username
|
||||||
|
GetHotLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetNewLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetTopLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetControversialLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
|
||||||
|
GetUpvoted(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetDownvoted(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
GetHidden(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error)
|
||||||
|
|
||||||
|
// returns the client's comments
|
||||||
|
GetHotComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
GetNewComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
GetTopComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
GetControversialComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
|
||||||
|
// returns the comments of the user with the username
|
||||||
|
GetHotCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
GetNewCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
GetTopCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
GetControversialCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error)
|
||||||
|
|
||||||
|
Unblock(ctx context.Context, username string) (*Response, error)
|
||||||
|
Unfriend(ctx context.Context, username string) (*Response, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// UserServiceOp implements the UserService interface
|
// UserServiceOp implements the UserService interface
|
||||||
|
@ -32,10 +60,10 @@ type userRoot struct {
|
||||||
|
|
||||||
// User represents a Reddit user
|
// User represents a Reddit user
|
||||||
type User struct {
|
type User struct {
|
||||||
ID string `json:"id,omitempty"`
|
// is not the full ID, watch out
|
||||||
Name string `json:"name,omitempty"`
|
ID string `json:"id,omitempty"`
|
||||||
Created float64 `json:"created"`
|
Name string `json:"name,omitempty"`
|
||||||
CreatedUTC float64 `json:"created_utc"`
|
Created *Timestamp `json:"created_utc,omitempty"`
|
||||||
|
|
||||||
LinkKarma int `json:"link_karma"`
|
LinkKarma int `json:"link_karma"`
|
||||||
CommentKarma int `json:"comment_karma"`
|
CommentKarma int `json:"comment_karma"`
|
||||||
|
@ -49,8 +77,8 @@ type User struct {
|
||||||
// UserShort represents a Reddit user, but contains fewer pieces of information
|
// UserShort represents a Reddit user, but contains fewer pieces of information
|
||||||
// It is returned from the GET /api/user_data_by_account_ids endpoint
|
// It is returned from the GET /api/user_data_by_account_ids endpoint
|
||||||
type UserShort struct {
|
type UserShort struct {
|
||||||
Name string `json:"name,omitempty"`
|
Name string `json:"name,omitempty"`
|
||||||
CreatedUTC float64 `json:"created_utc"`
|
Created *Timestamp `json:"created_utc,omitempty"`
|
||||||
|
|
||||||
LinkKarma int `json:"link_karma"`
|
LinkKarma int `json:"link_karma"`
|
||||||
CommentKarma int `json:"comment_karma"`
|
CommentKarma int `json:"comment_karma"`
|
||||||
|
@ -102,63 +130,8 @@ func (s *UserServiceOp) GetMultipleByID(ctx context.Context, ids ...string) (map
|
||||||
return *root, resp, nil
|
return *root, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetComments returns a list of the client's comments
|
|
||||||
func (s *UserServiceOp) GetComments(ctx context.Context, sort sort, opts *ListOptions) (*CommentList, *Response, error) {
|
|
||||||
return s.GetCommentsOf(ctx, s.client.Username, sort, opts)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetCommentsOf returns a list of the user's comments
|
|
||||||
func (s *UserServiceOp) GetCommentsOf(ctx context.Context, username string, sort sort, opts *ListOptions) (*CommentList, *Response, error) {
|
|
||||||
path := fmt.Sprintf("user/%s/comments?sort=%s", username, 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(commentRootListing)
|
|
||||||
resp, err := s.client.Do(ctx, req, root)
|
|
||||||
if err != nil {
|
|
||||||
return nil, resp, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if root.Data == nil {
|
|
||||||
return nil, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
cl := new(CommentList)
|
|
||||||
var comments []Comment
|
|
||||||
|
|
||||||
for _, child := range root.Data.Roots {
|
|
||||||
comments = append(comments, *child.Data)
|
|
||||||
}
|
|
||||||
cl.Comments = comments
|
|
||||||
cl.After = root.Data.After
|
|
||||||
cl.Before = root.Data.Before
|
|
||||||
|
|
||||||
return cl, resp, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// UsernameAvailable checks whether a username is available for registration
|
// UsernameAvailable checks whether a username is available for registration
|
||||||
// If a valid username is provided, this endpoint returns a body with just "true" or "false"
|
// If a valid username is provided, this endpoint returns a body with just "true" or "false"
|
||||||
// If an invalid username is provided, it returns the following:
|
|
||||||
/*
|
|
||||||
{
|
|
||||||
"json": {
|
|
||||||
"errors": [
|
|
||||||
[
|
|
||||||
"BAD_USERNAME",
|
|
||||||
"invalid username",
|
|
||||||
"user"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) {
|
func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string) (bool, *Response, error) {
|
||||||
type query struct {
|
type query struct {
|
||||||
User string `url:"user,omitempty"`
|
User string `url:"user,omitempty"`
|
||||||
|
@ -177,9 +150,6 @@ func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string)
|
||||||
|
|
||||||
root := new(bool)
|
root := new(bool)
|
||||||
resp, err := s.client.Do(ctx, req, root)
|
resp, err := s.client.Do(ctx, req, root)
|
||||||
if _, ok := err.(*json.UnmarshalTypeError); ok {
|
|
||||||
return false, resp, fmt.Errorf("must provide username conforming to Reddit's criteria; username provided: %q", username)
|
|
||||||
}
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return false, resp, err
|
return false, resp, err
|
||||||
}
|
}
|
||||||
|
@ -208,3 +178,219 @@ func (s *UserServiceOp) Friend(ctx context.Context, username string, note string
|
||||||
// todo: requires gold
|
// todo: requires gold
|
||||||
return nil, nil, nil
|
return nil, nil, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Unblock unblocks a user
|
||||||
|
func (s *UserServiceOp) Unblock(ctx context.Context, username string) (*Response, error) {
|
||||||
|
path := "api/unfriend"
|
||||||
|
|
||||||
|
form := url.Values{}
|
||||||
|
form.Set("name", username)
|
||||||
|
form.Set("type", "enemy")
|
||||||
|
form.Set("container", "todo: this should be the current user's full id")
|
||||||
|
|
||||||
|
req, err := s.client.NewPostForm(path, form)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.Do(ctx, req, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Unfriend unfriends a user
|
||||||
|
func (s *UserServiceOp) Unfriend(ctx context.Context, username string) (*Response, error) {
|
||||||
|
path := fmt.Sprintf("api/v1/me/friends/%s", username)
|
||||||
|
req, err := s.client.NewRequest(http.MethodDelete, path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return s.client.Do(ctx, req, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUpvoted returns a list of the client's upvoted submissions
|
||||||
|
func (s *UserServiceOp) GetUpvoted(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
path := fmt.Sprintf("user/%s/upvoted", s.client.Username)
|
||||||
|
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.getLinks(), resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetDownvoted returns a list of the client's downvoted submissions
|
||||||
|
func (s *UserServiceOp) GetDownvoted(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
path := fmt.Sprintf("user/%s/downvoted", s.client.Username)
|
||||||
|
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.getLinks(), resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHidden returns a list of the client's hidden submissions
|
||||||
|
func (s *UserServiceOp) GetHidden(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
path := fmt.Sprintf("user/%s/hidden", s.client.Username)
|
||||||
|
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.getLinks(), resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHotLinks returns a list of the client's hottest submissions
|
||||||
|
func (s *UserServiceOp) GetHotLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, s.client.Username, sortHot, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewLinks returns a list of the client's newest submissions
|
||||||
|
func (s *UserServiceOp) GetNewLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, s.client.Username, sortNew, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopLinks returns a list of the client's top submissions
|
||||||
|
func (s *UserServiceOp) GetTopLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, s.client.Username, sortTop, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetControversialLinks returns a list of the client's most controversial submissions
|
||||||
|
func (s *UserServiceOp) GetControversialLinks(ctx context.Context, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, s.client.Username, sortControversial, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHotLinksOf returns a list of the user's hottest submissions
|
||||||
|
func (s *UserServiceOp) GetHotLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, username, sortHot, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewLinksOf returns a list of the user's newest submissions
|
||||||
|
func (s *UserServiceOp) GetNewLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, username, sortNew, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopLinksOf returns a list of the user's top submissions
|
||||||
|
func (s *UserServiceOp) GetTopLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, username, sortTop, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetControversialLinksOf returns a list of the user's most controversial submissions
|
||||||
|
func (s *UserServiceOp) GetControversialLinksOf(ctx context.Context, username string, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
return s.getLinks(ctx, username, sortControversial, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHotComments returns a list of the client's hottest comments
|
||||||
|
func (s *UserServiceOp) GetHotComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, s.client.Username, sortHot, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewComments returns a list of the client's newest comments
|
||||||
|
func (s *UserServiceOp) GetNewComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, s.client.Username, sortNew, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopComments returns a list of the client's top comments
|
||||||
|
func (s *UserServiceOp) GetTopComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, s.client.Username, sortTop, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetControversialComments returns a list of the client's most controversial comments
|
||||||
|
func (s *UserServiceOp) GetControversialComments(ctx context.Context, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, s.client.Username, sortControversial, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetHotCommentsOf returns a list of the user's hottest comments
|
||||||
|
func (s *UserServiceOp) GetHotCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, username, sortHot, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetNewCommentsOf returns a list of the user's newest comments
|
||||||
|
func (s *UserServiceOp) GetNewCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, username, sortNew, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTopCommentsOf returns a list of the user's top comments
|
||||||
|
func (s *UserServiceOp) GetTopCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, username, sortTop, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetControversialCommentsOf returns a list of the user's most controversial comments
|
||||||
|
func (s *UserServiceOp) GetControversialCommentsOf(ctx context.Context, username string, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
return s.getComments(ctx, username, sortControversial, opts)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserServiceOp) getLinks(ctx context.Context, username string, sort sort, opts *ListOptions) (*LinkList, *Response, error) {
|
||||||
|
path := fmt.Sprintf("user/%s/submitted?sort=%s", username, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.getLinks(), resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *UserServiceOp) getComments(ctx context.Context, username string, sort sort, opts *ListOptions) (*CommentList, *Response, error) {
|
||||||
|
path := fmt.Sprintf("user/%s/comments?sort=%s", username, 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
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.getComments(), resp, nil
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue