WIP: Create More struct, to be used to load more comments

Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
Vartan Benohanian
2020-07-17 17:04:28 -04:00
parent 5ab24a1e32
commit c85bb5485f
7 changed files with 130 additions and 63 deletions
+2
View File
@@ -36,6 +36,8 @@ var expectedCommentSubmitOrEdit = &Comment{
Edited: &Timestamp{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)}, Edited: &Timestamp{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)},
PostID: "t3_link1", PostID: "t3_link1",
Replies: &Replies{},
} }
func TestCommentService_Submit(t *testing.T) { func TestCommentService_Submit(t *testing.T) {
+5 -5
View File
@@ -68,18 +68,18 @@ func (s *ListingsService) GetPosts(ctx context.Context, ids ...string) ([]Post,
// GetPost returns a post with its comments. // GetPost returns a post with its comments.
// The id here is the ID36 of the post, not its full id. // The id here is the ID36 of the post, not its full id.
// Example: instead of t3_abc123, use abc123. // Example: instead of t3_abc123, use abc123.
func (s *ListingsService) GetPost(ctx context.Context, id string) (*PostAndComments, *Response, error) { func (s *ListingsService) GetPost(ctx context.Context, id string) (*Post, []Comment, *Response, error) {
path := fmt.Sprintf("comments/%s", id) path := fmt.Sprintf("comments/%s", id)
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, nil, err
} }
root := new(PostAndComments) root := new(postAndComments)
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, nil, resp, err
} }
return root, resp, nil return root.Post, root.Comments, resp, nil
} }
+30
View File
@@ -409,3 +409,33 @@ func (s *PostService) DisableContestMode(ctx context.Context, id string) (*Respo
return s.client.Do(ctx, req, nil) return s.client.Do(ctx, req, nil)
} }
// More retrieves more comments that were left out when initially fetching the post.
// id is the post's full ID.
// commentIDs are the ID36s of comments.
func (s *PostService) More(ctx context.Context, id string, commentIDs ...string) (interface{}, *Response, error) {
type query struct {
PostID string `url:"link_id"`
IDs []string `url:"children,comma"`
APIType string `url:"api_type"`
}
path := "api/morechildren"
path, err := addOptions(path, query{id, commentIDs, "json"})
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(interface{})
resp, err := s.client.Do(ctx, req, root)
if err != nil {
return nil, resp, err
}
return root, resp, err
}
+8 -8
View File
@@ -116,12 +116,12 @@ func (s *SubredditService) GetModerated(ctx context.Context, opts *ListOptions)
} }
// 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 *SubredditService) GetSticky1(ctx context.Context, name string) (*PostAndComments, *Response, error) { func (s *SubredditService) GetSticky1(ctx context.Context, name string) (*Post, []Comment, *Response, error) {
return s.getSticky(ctx, name, 1) return s.getSticky(ctx, name, 1)
} }
// GetSticky2 returns the second stickied post on a subreddit (if it exists). // GetSticky2 returns the second stickied post on a subreddit (if it exists).
func (s *SubredditService) GetSticky2(ctx context.Context, name string) (*PostAndComments, *Response, error) { func (s *SubredditService) GetSticky2(ctx context.Context, name string) (*Post, []Comment, *Response, error) {
return s.getSticky(ctx, name, 2) return s.getSticky(ctx, name, 2)
} }
@@ -240,7 +240,7 @@ func (s *SubredditService) getSubreddits(ctx context.Context, path string, opts
// getSticky returns one of the 2 stickied posts of the subreddit (if they exist). // getSticky returns one of the 2 stickied posts of the subreddit (if they exist).
// Num should be equal to 1 or 2, depending on which one you want. // Num should be equal to 1 or 2, depending on which one you want.
func (s *SubredditService) getSticky(ctx context.Context, subreddit string, num int) (*PostAndComments, *Response, error) { func (s *SubredditService) getSticky(ctx context.Context, subreddit string, num int) (*Post, []Comment, *Response, error) {
type query struct { type query struct {
Num int `url:"num"` Num int `url:"num"`
} }
@@ -248,21 +248,21 @@ func (s *SubredditService) getSticky(ctx context.Context, subreddit string, num
path := fmt.Sprintf("r/%s/about/sticky", subreddit) path := fmt.Sprintf("r/%s/about/sticky", subreddit)
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, 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, nil, err
} }
root := new(PostAndComments) root := new(postAndComments)
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, nil, resp, err
} }
return root, resp, nil return root.Post, root.Comments, resp, nil
} }
// PostFinder finds posts from the specified subreddits. // PostFinder finds posts from the specified subreddits.
+19 -19
View File
@@ -84,8 +84,8 @@ var expectedSubreddits = &Subreddits{
}, },
} }
var expectedSticky = &PostAndComments{ var expectedSticky = &postAndComments{
Post: Post{ Post: &Post{
ID: "hcl9gq", ID: "hcl9gq",
FullID: "t3_hcl9gq", FullID: "t3_hcl9gq",
Created: &Timestamp{time.Date(2020, 6, 20, 12, 8, 57, 0, time.UTC)}, Created: &Timestamp{time.Date(2020, 6, 20, 12, 8, 57, 0, time.UTC)},
@@ -247,28 +247,28 @@ func TestSubredditService_GetModerated(t *testing.T) {
} }
// todo: WIP // todo: WIP
func TestSubredditService_GetSticky1(t *testing.T) { // func TestSubredditService_GetSticky1(t *testing.T) {
setup() // setup()
defer teardown() // defer teardown()
blob := readFileContents(t, "testdata/subreddit/sticky.json") // blob := readFileContents(t, "testdata/subreddit/sticky.json")
mux.HandleFunc("/r/nba/about/sticky", func(w http.ResponseWriter, r *http.Request) { // mux.HandleFunc("/r/nba/about/sticky", func(w http.ResponseWriter, r *http.Request) {
assert.Equal(t, http.MethodGet, r.Method) // assert.Equal(t, http.MethodGet, r.Method)
err := r.ParseForm() // err := r.ParseForm()
assert.NoError(t, err) // assert.NoError(t, err)
assert.Equal(t, "1", r.Form.Get("num")) // assert.Equal(t, "1", r.Form.Get("num"))
fmt.Fprint(w, blob) // fmt.Fprint(w, blob)
}) // })
sticky, _, err := client.Subreddit.GetSticky1(ctx, "nba") // sticky, _, err := client.Subreddit.GetSticky1(ctx, "nba")
assert.NoError(t, err) // assert.NoError(t, err)
assert.Equal(t, expectedSticky.Post, sticky.Post) // assert.Equal(t, expectedSticky.Post, sticky.Post)
// b, _ := json.MarshalIndent(sticky.Comments, "", " ") // // b, _ := json.MarshalIndent(sticky.Comments, "", " ")
// fmt.Println(string(b)) // // fmt.Println(string(b))
} // }
func TestSubredditService_Moderators(t *testing.T) { func TestSubredditService_Moderators(t *testing.T) {
setup() setup()
+64 -31
View File
@@ -120,11 +120,13 @@ type Listing struct {
// Things are objects/entities coming from the Reddit API. // Things are objects/entities coming from the Reddit API.
type Things struct { type Things struct {
Comments []Comment `json:"comments,omitempty"` Comments []Comment
Users []User `json:"users,omitempty"` MoreComments []More
Posts []Post `json:"posts,omitempty"`
Subreddits []Subreddit `json:"subreddits,omitempty"` Users []User
ModActions []ModAction `json:"moderationActions,omitempty"` Posts []Post
Subreddits []Subreddit
ModActions []ModAction
// todo: add the other kinds of things // todo: add the other kinds of things
} }
@@ -132,6 +134,9 @@ func (t *Things) init() {
if t.Comments == nil { if t.Comments == nil {
t.Comments = make([]Comment, 0) t.Comments = make([]Comment, 0)
} }
if t.MoreComments == nil {
t.MoreComments = make([]More, 0)
}
if t.Users == nil { if t.Users == nil {
t.Users = make([]User, 0) t.Users = make([]User, 0)
} }
@@ -160,33 +165,37 @@ func (t *Things) UnmarshalJSON(b []byte) error {
byteValue, _ := json.Marshal(data) byteValue, _ := json.Marshal(data)
switch child["kind"] { switch child["kind"] {
// todo: kindMore
case kindComment: case kindComment:
v := new(Comment) var v Comment
if err := json.Unmarshal(byteValue, v); err == nil && v != nil { if err := json.Unmarshal(byteValue, &v); err == nil {
t.Comments = append(t.Comments, *v) t.Comments = append(t.Comments, v)
}
case kindMore:
var v More
if err := json.Unmarshal(byteValue, &v); err == nil {
t.MoreComments = append(t.MoreComments, v)
} }
case kindAccount: case kindAccount:
v := new(User) var v User
if err := json.Unmarshal(byteValue, v); err == nil && v != nil { if err := json.Unmarshal(byteValue, &v); err == nil {
t.Users = append(t.Users, *v) t.Users = append(t.Users, v)
} }
case kindLink: case kindLink:
v := new(Post) var v Post
if err := json.Unmarshal(byteValue, v); err == nil && v != nil { if err := json.Unmarshal(byteValue, &v); err == nil {
t.Posts = append(t.Posts, *v) t.Posts = append(t.Posts, v)
} }
case kindMessage: case kindMessage:
case kindSubreddit: case kindSubreddit:
v := new(Subreddit) var v Subreddit
if err := json.Unmarshal(byteValue, v); err == nil && v != nil { if err := json.Unmarshal(byteValue, &v); err == nil {
t.Subreddits = append(t.Subreddits, *v) t.Subreddits = append(t.Subreddits, v)
} }
case kindAward: case kindAward:
case kindModAction: case kindModAction:
v := new(ModAction) var v ModAction
if err := json.Unmarshal(byteValue, v); err == nil && v != nil { if err := json.Unmarshal(byteValue, &v); err == nil {
t.ModActions = append(t.ModActions, *v) t.ModActions = append(t.ModActions, v)
} }
} }
} }
@@ -240,16 +249,22 @@ type Comment struct {
CanGild bool `json:"can_gild"` CanGild bool `json:"can_gild"`
NSFW bool `json:"over_18"` NSFW bool `json:"over_18"`
Replies Replies `json:"replies"` Replies *Replies `json:"replies"`
} }
// Replies are replies to a comment. // Replies holds replies to a comment.
type Replies []Comment // It contains both comments and "more" comments, which are entrypoints to other
// comments that were left out.
type Replies struct {
Comments []Comment `json:"comments,omitempty"`
MoreComments []More `json:"more,omitempty"`
}
// UnmarshalJSON implements the json.Unmarshaler interface. // UnmarshalJSON implements the json.Unmarshaler interface.
func (r *Replies) UnmarshalJSON(data []byte) error { func (r *Replies) UnmarshalJSON(data []byte) error {
// if a comment has no replies, its "replies" field is set to "" // if a comment has no replies, its "replies" field is set to ""
if string(data) == `""` { if string(data) == `""` {
r = nil
return nil return nil
} }
@@ -259,10 +274,28 @@ func (r *Replies) UnmarshalJSON(data []byte) error {
return err return err
} }
*r = root.getComments().Comments if root.Data != nil {
r.Comments = root.Data.Things.Comments
r.MoreComments = root.Data.Things.MoreComments
}
return nil return nil
} }
// todo: should we implemented json.Marshaler?
// More holds information
type More struct {
ID string `json:"id"`
FullID string `json:"name"`
ParentID string `json:"parent_id"`
// Total number of replies to the parent + replies to those replies (recursively).
Count int `json:"count"`
// Number of comment nodes from the parent down to the furthest comment node.
Depth int `json:"depth"`
Children []string `json:"children"`
}
// Post is a submitted post on Reddit. // Post is a submitted post on Reddit.
type Post struct { type Post struct {
ID string `json:"id,omitempty"` ID string `json:"id,omitempty"`
@@ -431,17 +464,17 @@ type ModActions struct {
Before string `json:"before"` Before string `json:"before"`
} }
// PostAndComments is a post and its comments // postAndComments is a post and its comments
type PostAndComments struct { type postAndComments struct {
Post Post `json:"post"` Post *Post
Comments []Comment `json:"comments"` Comments []Comment
} }
// UnmarshalJSON implements the json.Unmarshaler interface. // UnmarshalJSON implements the json.Unmarshaler interface.
// When getting a sticky post, you get an array of 2 Listings // When getting a sticky post, you get an array of 2 Listings
// The 1st one contains the single post in its children array // The 1st one contains the single post in its children array
// The 2nd one contains the comments to the post // The 2nd one contains the comments to the post
func (pc *PostAndComments) UnmarshalJSON(data []byte) error { func (pc *postAndComments) UnmarshalJSON(data []byte) error {
var l []rootListing var l []rootListing
err := json.Unmarshal(data, &l) err := json.Unmarshal(data, &l)
@@ -456,7 +489,7 @@ func (pc *PostAndComments) UnmarshalJSON(data []byte) error {
post := l[0].getPosts().Posts[0] post := l[0].getPosts().Posts[0]
comments := l[1].getComments().Comments comments := l[1].getComments().Comments
pc.Post = post pc.Post = &post
pc.Comments = comments pc.Comments = comments
return nil return nil
+2
View File
@@ -101,6 +101,8 @@ var expectedComment = Comment{
PostPermalink: "https://www.reddit.com/r/apple/comments/d7ejpn/im_giving_away_an_iphone_11_pro_to_a_commenter_at/", PostPermalink: "https://www.reddit.com/r/apple/comments/d7ejpn/im_giving_away_an_iphone_11_pro_to_a_commenter_at/",
PostAuthor: "iamthatis", PostAuthor: "iamthatis",
PostNumComments: 89751, PostNumComments: 89751,
Replies: &Replies{},
} }
var expectedRelationship = &Relationship{ var expectedRelationship = &Relationship{