From 112f7f03707d45a20fc2692293897ba51d19df5b Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Wed, 29 Jul 2020 14:11:06 -0400 Subject: [PATCH] WIP: load more comments for a post Signed-off-by: Vartan Benohanian --- comment.go | 67 ++++++++++++++++++++++++++++++++++++++++++++++++++++ post.go | 46 +++++++++++++++--------------------- subreddit.go | 2 +- things.go | 41 ++++++++++++++++++++++---------- 4 files changed, 115 insertions(+), 41 deletions(-) diff --git a/comment.go b/comment.go index 52edd3a..16a4d63 100644 --- a/comment.go +++ b/comment.go @@ -2,6 +2,7 @@ package reddit import ( "context" + "errors" "net/http" "net/url" ) @@ -63,3 +64,69 @@ func (s *CommentService) Edit(ctx context.Context, id string, text string) (*Com return root, resp, nil } + +// LoadMoreReplies retrieves more replies that were left out when initially fetching the comment. +func (s *CommentService) LoadMoreReplies(ctx context.Context, comment *Comment) (*Response, error) { + if comment == nil { + return nil, errors.New("comment: must not be nil") + } + + if !comment.hasMore() { + return nil, nil + } + + postID := comment.PostID + commentIDs := comment.Replies.MoreComments.Children + + 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{postID, commentIDs, "json"}) + if err != nil { + return nil, err + } + + req, err := s.client.NewRequest(http.MethodGet, path, nil) + if err != nil { + return nil, err + } + + type rootResponse struct { + JSON struct { + Data struct { + Things Things `json:"things"` + } `json:"data"` + } `json:"json"` + } + + root := new(rootResponse) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return resp, err + } + + comments := root.JSON.Data.Things.Comments + for _, c := range comments { + addCommentToReplies(comment, c) + } + + comment.Replies.MoreComments = nil + return resp, nil +} + +// addCommentToReplies traverses the comment tree to find the one +// that the 2nd comment is replying to. It then adds it to its replies. +func addCommentToReplies(parent *Comment, comment *Comment) { + if parent.FullID == comment.ParentID { + parent.Replies.Comments = append(parent.Replies.Comments, comment) + return + } + + for _, reply := range parent.Replies.Comments { + addCommentToReplies(reply, comment) + } +} diff --git a/post.go b/post.go index 405100c..e1615b4 100644 --- a/post.go +++ b/post.go @@ -65,20 +65,20 @@ type SubmitLinkOptions struct { // Get returns a post with its comments. // id is the ID36 of the post, not its full id. // Example: instead of t3_abc123, use abc123. -func (s *PostService) Get(ctx context.Context, id string) (*Post, []*Comment, *Response, error) { +func (s *PostService) Get(ctx context.Context, id string) (*PostAndComments, *Response, error) { path := fmt.Sprintf("comments/%s", id) req, err := s.client.NewRequest(http.MethodGet, path, nil) if err != nil { - return nil, nil, nil, err + return nil, nil, err } - root := new(postAndComments) + root := new(PostAndComments) resp, err := s.client.Do(ctx, req, root) if err != nil { - return nil, nil, resp, err + return nil, resp, err } - return root.Post, root.Comments, resp, nil + return root, resp, nil } func (s *PostService) submit(ctx context.Context, v interface{}) (*Submitted, *Response, error) { @@ -433,24 +433,18 @@ func (s *PostService) DisableContestMode(ctx context.Context, id string) (*Respo 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, comment *Comment) (*Response, error) { - if comment == nil { - return nil, errors.New("comment: must not be nil") +// LoadMoreComments retrieves more comments that were left out when initially fetching the post. +func (s *PostService) LoadMoreComments(ctx context.Context, pc *PostAndComments) (*Response, error) { + if pc == nil { + return nil, errors.New("pc: must not be nil") } - if comment.Replies.MoreComments == nil { + if !pc.hasMore() { return nil, nil } - postID := comment.PostID - commentIDs := comment.Replies.MoreComments.Children - - if len(commentIDs) == 0 { - return nil, nil - } + postID := pc.Post.FullID + commentIDs := pc.moreComments.Children type query struct { PostID string `url:"link_id"` @@ -485,22 +479,20 @@ func (s *PostService) More(ctx context.Context, comment *Comment) (*Response, er comments := root.JSON.Data.Things.Comments for _, c := range comments { - addCommentToReplies(comment, c) + addCommentToTree(pc, c) } - comment.Replies.MoreComments = nil + pc.moreComments = nil return resp, nil } -// addCommentToReplies traverses the comment tree to find the one -// that the 2nd comment is replying to. It then adds it to its replies. -func addCommentToReplies(parent *Comment, comment *Comment) { - if parent.FullID == comment.ParentID { - parent.Replies.Comments = append(parent.Replies.Comments, comment) +func addCommentToTree(pc *PostAndComments, comment *Comment) { + if pc.Post.FullID == comment.ParentID { + pc.Comments = append(pc.Comments, comment) return } - for _, reply := range parent.Replies.Comments { + for _, reply := range pc.Comments { addCommentToReplies(reply, comment) } } @@ -516,7 +508,7 @@ func (s *PostService) random(ctx context.Context, subreddits ...string) (*Post, return nil, nil, nil, err } - root := new(postAndComments) + root := new(PostAndComments) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, nil, resp, err diff --git a/subreddit.go b/subreddit.go index 3855476..9eb67f9 100644 --- a/subreddit.go +++ b/subreddit.go @@ -294,7 +294,7 @@ func (s *SubredditService) getSticky(ctx context.Context, subreddit string, num return nil, nil, nil, err } - root := new(postAndComments) + root := new(PostAndComments) resp, err := s.client.Do(ctx, req, root) if err != nil { return nil, nil, resp, err diff --git a/things.go b/things.go index d0983d6..c0a23fa 100644 --- a/things.go +++ b/things.go @@ -58,7 +58,7 @@ type Listing struct { // Things are objects/entities coming from the Reddit API. type Things struct { Comments []*Comment - MoreComments []*More + MoreComments *More Users []*User Posts []*Post @@ -71,9 +71,6 @@ func (t *Things) init() { if t.Comments == nil { t.Comments = make([]*Comment, 0) } - if t.MoreComments == nil { - t.MoreComments = make([]*More, 0) - } if t.Users == nil { t.Users = make([]*User, 0) } @@ -110,7 +107,7 @@ func (t *Things) UnmarshalJSON(b []byte) error { case kindMore: v := new(More) if err := json.Unmarshal(byteValue, v); err == nil { - t.MoreComments = append(t.MoreComments, v) + t.MoreComments = v } case kindAccount: v := new(User) @@ -190,6 +187,10 @@ type Comment struct { Replies Replies `json:"replies"` } +func (c *Comment) hasMore() bool { + return c.Replies.MoreComments != nil && len(c.Replies.MoreComments.Children) > 0 +} + // Replies holds replies to a comment. // It contains both comments and "more" comments, which are entrypoints to other // comments that were left out. @@ -214,9 +215,7 @@ func (r *Replies) UnmarshalJSON(data []byte) error { if root.Data != nil { r.Comments = root.Data.Things.Comments - if len(root.Data.Things.MoreComments) > 0 { - r.MoreComments = root.Data.Things.MoreComments[0] - } + r.MoreComments = root.Data.Things.MoreComments } return nil @@ -329,6 +328,13 @@ func (rl *rootListing) getComments() *Comments { return v } +func (rl *rootListing) getMoreComments() *More { + if rl == nil || rl.Data == nil { + return nil + } + return rl.Data.Things.MoreComments +} + func (rl *rootListing) getUsers() *Users { v := new(Users) if rl != nil && rl.Data != nil { @@ -404,17 +410,18 @@ type ModActions struct { Before string `json:"before"` } -// postAndComments is a post and its comments -type postAndComments struct { - Post *Post - Comments []*Comment +// PostAndComments is a post and its comments. +type PostAndComments struct { + Post *Post `json:"post"` + Comments []*Comment `json:"comments"` + moreComments *More } // UnmarshalJSON implements the json.Unmarshaler interface. // When getting a sticky post, you get an array of 2 Listings // The 1st one contains the single post in its children array // 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 err := json.Unmarshal(data, &l) @@ -428,9 +435,17 @@ func (pc *postAndComments) UnmarshalJSON(data []byte) error { post := l[0].getPosts().Posts[0] comments := l[1].getComments().Comments + moreComments := l[1].getMoreComments() pc.Post = post pc.Comments = comments + pc.moreComments = moreComments return nil } + +func (pc *PostAndComments) hasMore() bool { + return pc.moreComments != nil && len(pc.moreComments.Children) > 0 +} + +func (pc *PostAndComments) M() *More { return pc.moreComments }