From c85bb5485fa4d1cb065b9a7066e38deb46c35ea2 Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Fri, 17 Jul 2020 17:04:28 -0400 Subject: [PATCH] WIP: Create More struct, to be used to load more comments Signed-off-by: Vartan Benohanian --- comment_test.go | 2 + listings.go | 10 ++--- post.go | 30 +++++++++++++++ subreddit.go | 16 ++++---- subreddit_test.go | 38 +++++++++---------- things.go | 95 +++++++++++++++++++++++++++++++---------------- user_test.go | 2 + 7 files changed, 130 insertions(+), 63 deletions(-) diff --git a/comment_test.go b/comment_test.go index 70393fd..083d021 100644 --- a/comment_test.go +++ b/comment_test.go @@ -36,6 +36,8 @@ var expectedCommentSubmitOrEdit = &Comment{ Edited: &Timestamp{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)}, PostID: "t3_link1", + + Replies: &Replies{}, } func TestCommentService_Submit(t *testing.T) { diff --git a/listings.go b/listings.go index e34f6b2..d4f1319 100644 --- a/listings.go +++ b/listings.go @@ -68,18 +68,18 @@ func (s *ListingsService) GetPosts(ctx context.Context, ids ...string) ([]Post, // GetPost returns a post with its comments. // The id here is the ID36 of the post, not its full id. // 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) req, err := s.client.NewRequest(http.MethodGet, path, 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) if err != nil { - return nil, resp, err + return nil, nil, resp, err } - return root, resp, nil + return root.Post, root.Comments, resp, nil } diff --git a/post.go b/post.go index 26c66ef..437fd52 100644 --- a/post.go +++ b/post.go @@ -409,3 +409,33 @@ 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, 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 +} diff --git a/subreddit.go b/subreddit.go index a80036f..af6ce84 100644 --- a/subreddit.go +++ b/subreddit.go @@ -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). -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) } // 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) } @@ -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). // 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 { 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, err := addOptions(path, query{num}) if err != nil { - return nil, nil, err + return nil, nil, nil, err } req, err := s.client.NewRequest(http.MethodGet, path, 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) 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. diff --git a/subreddit_test.go b/subreddit_test.go index da50f3d..ede84f8 100644 --- a/subreddit_test.go +++ b/subreddit_test.go @@ -84,8 +84,8 @@ var expectedSubreddits = &Subreddits{ }, } -var expectedSticky = &PostAndComments{ - Post: Post{ +var expectedSticky = &postAndComments{ + Post: &Post{ ID: "hcl9gq", FullID: "t3_hcl9gq", 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 -func TestSubredditService_GetSticky1(t *testing.T) { - setup() - defer teardown() +// func TestSubredditService_GetSticky1(t *testing.T) { +// setup() +// 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) { - assert.Equal(t, http.MethodGet, r.Method) +// mux.HandleFunc("/r/nba/about/sticky", func(w http.ResponseWriter, r *http.Request) { +// assert.Equal(t, http.MethodGet, r.Method) - err := r.ParseForm() - assert.NoError(t, err) - assert.Equal(t, "1", r.Form.Get("num")) +// err := r.ParseForm() +// assert.NoError(t, err) +// assert.Equal(t, "1", r.Form.Get("num")) - fmt.Fprint(w, blob) - }) +// fmt.Fprint(w, blob) +// }) - sticky, _, err := client.Subreddit.GetSticky1(ctx, "nba") - assert.NoError(t, err) - assert.Equal(t, expectedSticky.Post, sticky.Post) - // b, _ := json.MarshalIndent(sticky.Comments, "", " ") - // fmt.Println(string(b)) -} +// sticky, _, err := client.Subreddit.GetSticky1(ctx, "nba") +// assert.NoError(t, err) +// assert.Equal(t, expectedSticky.Post, sticky.Post) +// // b, _ := json.MarshalIndent(sticky.Comments, "", " ") +// // fmt.Println(string(b)) +// } func TestSubredditService_Moderators(t *testing.T) { setup() diff --git a/things.go b/things.go index e1be2f8..115b7d7 100644 --- a/things.go +++ b/things.go @@ -120,11 +120,13 @@ type Listing struct { // Things are objects/entities coming from the Reddit API. type Things struct { - Comments []Comment `json:"comments,omitempty"` - Users []User `json:"users,omitempty"` - Posts []Post `json:"posts,omitempty"` - Subreddits []Subreddit `json:"subreddits,omitempty"` - ModActions []ModAction `json:"moderationActions,omitempty"` + Comments []Comment + MoreComments []More + + Users []User + Posts []Post + Subreddits []Subreddit + ModActions []ModAction // todo: add the other kinds of things } @@ -132,6 +134,9 @@ 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) } @@ -160,33 +165,37 @@ func (t *Things) UnmarshalJSON(b []byte) error { byteValue, _ := json.Marshal(data) switch child["kind"] { - // todo: kindMore case kindComment: - v := new(Comment) - if err := json.Unmarshal(byteValue, v); err == nil && v != nil { - t.Comments = append(t.Comments, *v) + var v Comment + if err := json.Unmarshal(byteValue, &v); err == nil { + 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: - v := new(User) - if err := json.Unmarshal(byteValue, v); err == nil && v != nil { - t.Users = append(t.Users, *v) + var v User + if err := json.Unmarshal(byteValue, &v); err == nil { + t.Users = append(t.Users, v) } case kindLink: - v := new(Post) - if err := json.Unmarshal(byteValue, v); err == nil && v != nil { - t.Posts = append(t.Posts, *v) + var v Post + if err := json.Unmarshal(byteValue, &v); err == nil { + t.Posts = append(t.Posts, v) } case kindMessage: case kindSubreddit: - v := new(Subreddit) - if err := json.Unmarshal(byteValue, v); err == nil && v != nil { - t.Subreddits = append(t.Subreddits, *v) + var v Subreddit + if err := json.Unmarshal(byteValue, &v); err == nil { + t.Subreddits = append(t.Subreddits, v) } case kindAward: case kindModAction: - v := new(ModAction) - if err := json.Unmarshal(byteValue, v); err == nil && v != nil { - t.ModActions = append(t.ModActions, *v) + var v ModAction + if err := json.Unmarshal(byteValue, &v); err == nil { + t.ModActions = append(t.ModActions, v) } } } @@ -240,16 +249,22 @@ type Comment struct { CanGild bool `json:"can_gild"` NSFW bool `json:"over_18"` - Replies Replies `json:"replies"` + Replies *Replies `json:"replies"` } -// Replies are replies to a comment. -type Replies []Comment +// Replies holds replies to a 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. func (r *Replies) UnmarshalJSON(data []byte) error { // if a comment has no replies, its "replies" field is set to "" if string(data) == `""` { + r = nil return nil } @@ -259,10 +274,28 @@ func (r *Replies) UnmarshalJSON(data []byte) error { return err } - *r = root.getComments().Comments + if root.Data != nil { + r.Comments = root.Data.Things.Comments + r.MoreComments = root.Data.Things.MoreComments + } + 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. type Post struct { ID string `json:"id,omitempty"` @@ -431,17 +464,17 @@ type ModActions struct { Before string `json:"before"` } -// PostAndComments is a post and its comments -type PostAndComments struct { - Post Post `json:"post"` - Comments []Comment `json:"comments"` +// postAndComments is a post and its comments +type postAndComments struct { + Post *Post + Comments []Comment } // 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) @@ -456,7 +489,7 @@ func (pc *PostAndComments) UnmarshalJSON(data []byte) error { post := l[0].getPosts().Posts[0] comments := l[1].getComments().Comments - pc.Post = post + pc.Post = &post pc.Comments = comments return nil diff --git a/user_test.go b/user_test.go index c01a51b..15c8e22 100644 --- a/user_test.go +++ b/user_test.go @@ -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/", PostAuthor: "iamthatis", PostNumComments: 89751, + + Replies: &Replies{}, } var expectedRelationship = &Relationship{