diff --git a/comment.go b/comment.go
index 31bc03a..d5baf3d 100644
--- a/comment.go
+++ b/comment.go
@@ -25,61 +25,6 @@ type CommentServiceOp struct {
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
// The after and before fields help decide the anchor point for a subsequent
// call that returns a list
diff --git a/comment_test.go b/comment_test.go
index 1a20770..5a06043 100644
--- a/comment_test.go
+++ b/comment_test.go
@@ -6,6 +6,7 @@ import (
"net/url"
"reflect"
"testing"
+ "time"
)
var expectedCommentSubmitOrEdit = &Comment{
@@ -15,7 +16,6 @@ var expectedCommentSubmitOrEdit = &Comment{
Permalink: "/r/subreddit/comments/test1/some_thread/test2/",
Body: "test comment",
- BodyHTML: "
",
Author: "reddit_username",
AuthorID: "t2_user1",
AuthorFlairText: "Flair",
@@ -27,8 +27,7 @@ var expectedCommentSubmitOrEdit = &Comment{
Score: 1,
Controversiality: 0,
- Created: 1588147787,
- CreatedUTC: 1588118987,
+ Created: &Timestamp{time.Date(2020, 4, 28, 20, 9, 47, 0, time.UTC)},
LinkID: "t3_link1",
}
diff --git a/geddit.go b/geddit.go
index 37a9fc6..5f26b45 100644
--- a/geddit.go
+++ b/geddit.go
@@ -343,7 +343,6 @@ func CheckResponse(r *http.Response) error {
// ListOptions are the optional parameters to the various endpoints that return lists
type ListOptions struct {
- Sort string `url:"sort,omitempty"`
Type string `url:"type,omitempty"` // links or comments
// For getting submissions
@@ -354,7 +353,6 @@ type ListOptions struct {
After string `url:"after,omitempty"`
Before string `url:"before,omitempty"`
Limit int `url:"limit,omitempty"` // default: 25
- Count int `url:"count,omitempty"` // default: 0
}
func addOptions(s string, opt interface{}) (string, error) {
@@ -388,6 +386,61 @@ type root struct {
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 (
kindListing string = "Listing"
kindComment string = "t1"
@@ -397,3 +450,138 @@ const (
kindSubreddit string = "t5"
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,
+ }
+}
diff --git a/listings.go b/listings.go
index 76c6ccb..28a00f5 100644
--- a/listings.go
+++ b/listings.go
@@ -2,7 +2,6 @@ package geddit
import (
"context"
- "encoding/json"
"net/http"
)
@@ -30,14 +29,15 @@ type listingRoot struct {
}
// Listing holds various types of things that all come from the Reddit API
-type Listing struct {
- Links []*Submission `json:"links,omitempty"`
- Comments []*Comment `json:"comments,omitempty"`
- Subreddits []*Subreddit `json:"subreddits,omitempty"`
-}
+// type Listing struct {
+// Links []*Submission `json:"links,omitempty"`
+// Comments []*Comment `json:"comments,omitempty"`
+// Subreddits []*Subreddit `json:"subreddits,omitempty"`
+// }
// Get gets a list of things based on their IDs
// Only links, comments, and subreddits are allowed
+// todo: only links, comments, subreddits
func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) (*Listing, *Response, error) {
type query struct {
IDs []string `url:"id,comma"`
@@ -54,51 +54,13 @@ func (s *ListingsServiceOp) Get(ctx context.Context, ids ...string) (*Listing, *
return nil, nil, err
}
- root := new(listingRoot)
+ root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
- if root.Data == 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
+ return root.Data, resp, nil
}
// todo: do by_id next
diff --git a/private-messages.go b/private-messages.go
new file mode 100644
index 0000000..be4d752
--- /dev/null
+++ b/private-messages.go
@@ -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)
+}
diff --git a/subreddit.go b/subreddit.go
index 0d1b286..668074a 100644
--- a/subreddit.go
+++ b/subreddit.go
@@ -5,7 +5,9 @@ import (
"errors"
"fmt"
"net/http"
+ "net/url"
"strings"
+ "time"
)
// SubredditService handles communication with the subreddit
@@ -23,14 +25,22 @@ type SubredditService interface {
GetMineWhereModerator(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)
- GetNewPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
- GetRisingPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
- GetControversialPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
- GetTopPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error)
+ GetHotLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
+ GetBestLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
+ GetNewLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error)
+ GetRisingLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *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)
- GetSticky2(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)
+
+ 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
@@ -40,42 +50,6 @@ type SubredditServiceOp struct {
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
// The after and before fields help decide the anchor point for a subsequent
// call that returns a list
@@ -85,58 +59,14 @@ type SubredditList struct {
Before string `json:"before,omitempty"`
}
-type submissionRoot struct {
- 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
+// LinkList holds information about a list of links
// The after and before fields help decide the anchor point for a subsequent
// call that returns a list
-type SubmissionList struct {
- Submissions []Submission `json:"submissions,omitempty"`
- After string `json:"after,omitempty"`
- Before string `json:"before,omitempty"`
+// Note: not to be confused with linked lists
+type LinkList struct {
+ Links []Link `json:"submissions,omitempty"`
+ After string `json:"after,omitempty"`
+ Before string `json:"before,omitempty"`
}
// GetByName gets a subreddit by name
@@ -220,44 +150,44 @@ var sorts = [...]string{
"top",
}
-// GetHotPosts returns the hot posts
-// If no subreddit names are provided, then it runs the search against /r/all
+// 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) GetHotPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
- return s.getPosts(ctx, sortHot, opts, names...)
+func (s *SubredditServiceOp) GetHotLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
+ return s.getLinks(ctx, sortHot, opts, names...)
}
-// GetBestPosts returns the best posts
-// If no subreddit names are provided, then it runs the search against /r/all
+// 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) GetBestPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
- return s.getPosts(ctx, sortBest, opts, names...)
+func (s *SubredditServiceOp) GetBestLinks(ctx context.Context, opts *ListOptions, names ...string) (*LinkList, *Response, error) {
+ return s.getLinks(ctx, sortBest, opts, names...)
}
-// GetNewPosts returns the new posts
-// If no subreddit names are provided, then it runs the search against /r/all
-func (s *SubredditServiceOp) GetNewPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
- return s.getPosts(ctx, sortNew, 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) (*LinkList, *Response, error) {
+ return s.getLinks(ctx, sortNew, opts, names...)
}
-// GetRisingPosts returns the rising posts
-// If no subreddit names are provided, then it runs the search against /r/all
-func (s *SubredditServiceOp) GetRisingPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
- return s.getPosts(ctx, sortRising, 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) (*LinkList, *Response, error) {
+ return s.getLinks(ctx, sortRising, opts, names...)
}
-// GetControversialPosts returns the controversial posts
-// If no subreddit names are provided, then it runs the search against /r/all
-func (s *SubredditServiceOp) GetControversialPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
- return s.getPosts(ctx, sortControversial, 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) (*LinkList, *Response, error) {
+ return s.getLinks(ctx, sortControversial, opts, names...)
}
-// GetTopPosts returns the top posts
-// If no subreddit names are provided, then it runs the search against /r/all
-func (s *SubredditServiceOp) GetTopPosts(ctx context.Context, opts *ListOptions, names ...string) (*SubmissionList, *Response, error) {
- return s.getPosts(ctx, sortTop, 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) (*LinkList, *Response, error) {
+ return s.getLinks(ctx, sortTop, opts, names...)
}
type sticky int
@@ -267,14 +197,65 @@ const (
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)
+// // 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)
}
-// 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)
+// 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) (*SubredditList, *Response, error) {
@@ -288,30 +269,24 @@ func (s *SubredditServiceOp) getSubreddits(ctx context.Context, path string, opt
return nil, nil, err
}
- root := new(subredditRootListing)
+ root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
- if root.Data == nil {
- return nil, resp, nil
+ l := new(SubredditList)
+
+ if root.Data != nil {
+ l.Subreddits = root.Data.Things.Subreddits
+ l.After = root.Data.After
+ l.Before = root.Data.Before
}
- sl := new(SubredditList)
- 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
+ return l, 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]
if len(names) > 0 {
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
}
- root := new(submissionRootListing)
+ root := new(rootListing)
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
- if root.Data == nil {
- return nil, resp, nil
+ l := new(LinkList)
+
+ if root.Data != nil {
+ l.Links = root.Data.Things.Links
+ l.After = root.Data.After
+ l.Before = root.Data.Before
}
- sl := new(SubmissionList)
- 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
+ return l, resp, nil
}
// 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 >= 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"`
- // }
+// 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
- // }
+// 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
- // }
+// req, err := s.client.NewRequest(http.MethodGet, path, nil)
+// if err != nil {
+// return nil, nil, err
+// }
- // root := new(submissionRootListing)
- // resp, err := s.client.Do(ctx, req, root)
- // if err != nil {
- // return nil, resp, err
- // }
+// var root []rootListing
+// resp, err := s.client.Do(ctx, req, &root)
+// if err != nil {
+// return nil, resp, err
+// }
- // return nil, resp, nil
- return nil, nil, nil
+// // 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
}
diff --git a/timestamp.go b/timestamp.go
new file mode 100644
index 0000000..4e42308
--- /dev/null
+++ b/timestamp.go
@@ -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)
+}
diff --git a/timestamp_test.go b/timestamp_test.go
new file mode 100644
index 0000000..e4458c1
--- /dev/null
+++ b/timestamp_test.go
@@ -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)
+ }
+ }
+}
diff --git a/user.go b/user.go
index b2bd3aa..6516d18 100644
--- a/user.go
+++ b/user.go
@@ -2,9 +2,9 @@ package geddit
import (
"context"
- "encoding/json"
"fmt"
"net/http"
+ "net/url"
)
// UserService handles communication with the user
@@ -14,8 +14,36 @@ type UserService interface {
GetMultipleByID(ctx context.Context, ids ...string) (map[string]*UserShort, *Response, error)
UsernameAvailable(ctx context.Context, username string) (bool, *Response, error)
- GetComments(ctx context.Context, sort sort, opts *ListOptions) (*CommentList, *Response, error)
- GetCommentsOf(ctx context.Context, username string, sort sort, opts *ListOptions) (*CommentList, *Response, error)
+ // returns the client's links
+ 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
@@ -32,10 +60,10 @@ type userRoot struct {
// User represents a Reddit user
type User struct {
- ID string `json:"id,omitempty"`
- Name string `json:"name,omitempty"`
- Created float64 `json:"created"`
- CreatedUTC float64 `json:"created_utc"`
+ // is not the full ID, watch out
+ ID string `json:"id,omitempty"`
+ Name string `json:"name,omitempty"`
+ Created *Timestamp `json:"created_utc,omitempty"`
LinkKarma int `json:"link_karma"`
CommentKarma int `json:"comment_karma"`
@@ -49,8 +77,8 @@ type User struct {
// UserShort represents a Reddit user, but contains fewer pieces of information
// It is returned from the GET /api/user_data_by_account_ids endpoint
type UserShort struct {
- Name string `json:"name,omitempty"`
- CreatedUTC float64 `json:"created_utc"`
+ Name string `json:"name,omitempty"`
+ Created *Timestamp `json:"created_utc,omitempty"`
LinkKarma int `json:"link_karma"`
CommentKarma int `json:"comment_karma"`
@@ -102,63 +130,8 @@ func (s *UserServiceOp) GetMultipleByID(ctx context.Context, ids ...string) (map
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
// 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) {
type query struct {
User string `url:"user,omitempty"`
@@ -177,9 +150,6 @@ func (s *UserServiceOp) UsernameAvailable(ctx context.Context, username string)
root := new(bool)
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 {
return false, resp, err
}
@@ -208,3 +178,219 @@ func (s *UserServiceOp) Friend(ctx context.Context, username string, note string
// todo: requires gold
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
+}