From e73b89f0b84a20e43603cd7f84786acc41c536de Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Fri, 18 Sep 2020 01:20:18 -0400 Subject: [PATCH] Finish LiveThreadService Signed-off-by: Vartan Benohanian --- reddit/live-thread.go | 178 +++++++++++++++- reddit/live-thread_test.go | 289 +++++++++++++++++++++++++- reddit/reddit.go | 4 +- reddit/things.go | 34 ++- testdata/live-thread/discussions.json | 271 ++++++++++++++++++++++++ testdata/live-thread/update.json | 26 +++ testdata/live-thread/updates.json | 41 ++++ 7 files changed, 831 insertions(+), 12 deletions(-) create mode 100644 testdata/live-thread/discussions.json create mode 100644 testdata/live-thread/update.json create mode 100644 testdata/live-thread/updates.json diff --git a/reddit/live-thread.go b/reddit/live-thread.go index 9d0f18a..f3ef078 100644 --- a/reddit/live-thread.go +++ b/reddit/live-thread.go @@ -5,6 +5,7 @@ import ( "encoding/json" "errors" "fmt" + "io" "net/http" "net/url" "reflect" @@ -42,6 +43,19 @@ type LiveThread struct { NSFW bool `json:"nsfw"` } +// LiveThreadUpdate is an update in a live thread. +type LiveThreadUpdate struct { + ID string `json:"id,omitempty"` + FullID string `json:"name,omitempty"` + Author string `json:"author,omitempty"` + Created *Timestamp `json:"created_utc,omitempty"` + + Body string `json:"body,omitempty"` + // todo: add "embeds" field? + + Stricken bool `json:"stricken"` +} + // LiveThreadCreateOrUpdateRequest represents a request to create/update a live thread. type LiveThreadCreateOrUpdateRequest struct { // No longer than 120 characters. @@ -133,7 +147,8 @@ type LiveThreadPermissions struct { Edit bool `permission:"edit"` Manage bool `permission:"manage"` Settings bool `permission:"settings"` - Update bool `permission:"update"` + // Posting updates to the thread. + Update bool `permission:"update"` } func (p *LiveThreadPermissions) String() (s string) { @@ -168,6 +183,28 @@ func (p *LiveThreadPermissions) String() (s string) { return } +// Now gets information about the currently featured live thread. +// This returns an empty 204 response if no thread is currently featured. +func (s *LiveThreadService) Now(ctx context.Context) (*LiveThread, *Response, error) { + path := "api/live/happening_now" + req, err := s.client.NewRequest(http.MethodGet, path, nil) + if err != nil { + return nil, nil, err + } + + root := new(thing) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + if err == io.EOF && resp != nil && resp.StatusCode == http.StatusNoContent { + return nil, resp, nil + } + return nil, resp, err + } + + t, _ := root.LiveThread() + return t, resp, nil +} + // Get information about a live thread. func (s *LiveThreadService) Get(ctx context.Context, id string) (*LiveThread, *Response, error) { path := fmt.Sprintf("live/%s/about", id) @@ -199,6 +236,94 @@ func (s *LiveThreadService) GetMultiple(ctx context.Context, ids ...string) ([]* return l.LiveThreads(), resp, nil } +// Update the live thread by posting an update to it. +// Requires the "update" permission. +func (s *LiveThreadService) Update(ctx context.Context, id, text string) (*Response, error) { + form := url.Values{} + form.Set("api_type", "json") + form.Set("body", text) + + path := fmt.Sprintf("/api/live/%s/update", id) + req, err := s.client.NewRequest(http.MethodPost, path, form) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Updates gets a list of updates posted in the live thread. +func (s *LiveThreadService) Updates(ctx context.Context, id string, opts *ListOptions) ([]*LiveThreadUpdate, *Response, error) { + path := fmt.Sprintf("live/%s", id) + l, resp, err := s.client.getListing(ctx, path, opts) + if err != nil { + return nil, resp, err + } + return l.LiveThreadUpdates(), resp, nil +} + +// UpdateByID gets a specific update in the live thread by its id. +// The ID of the update is the "short" one, i.e. the one that doesn't start with "LiveUpdate_". +func (s *LiveThreadService) UpdateByID(ctx context.Context, threadID, updateID string) (*LiveThreadUpdate, *Response, error) { + path := fmt.Sprintf("live/%s/updates/%s", threadID, updateID) + + // this endpoint returns a listing + l, resp, err := s.client.getListing(ctx, path, nil) + if err != nil { + return nil, resp, err + } + + var update *LiveThreadUpdate + updates := l.LiveThreadUpdates() + if len(updates) > 0 { + update = updates[0] + } + + return update, resp, nil +} + +// Discussions gets a list of discussions (posts) about the live thread. +func (s *LiveThreadService) Discussions(ctx context.Context, id string, opts *ListOptions) ([]*Post, *Response, error) { + path := fmt.Sprintf("live/%s/discussions", id) + l, resp, err := s.client.getListing(ctx, path, opts) + if err != nil { + return nil, resp, err + } + return l.Posts(), resp, nil +} + +// Strike (mark incorrect and cross out) the content of an update. +// You must either be the author of the update or have the "edit" permission. +func (s *LiveThreadService) Strike(ctx context.Context, threadID, updateID string) (*Response, error) { + form := url.Values{} + form.Set("api_type", "json") + form.Set("id", updateID) + + path := fmt.Sprintf("api/live/%s/strike_update", threadID) + req, err := s.client.NewRequest(http.MethodPost, path, form) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// Delete an update from the live thread. +// You must either be the author of the update or have the "edit" permission. +func (s *LiveThreadService) Delete(ctx context.Context, threadID, updateID string) (*Response, error) { + form := url.Values{} + form.Set("api_type", "json") + form.Set("id", updateID) + + path := fmt.Sprintf("api/live/%s/delete_update", threadID) + req, err := s.client.NewRequest(http.MethodPost, path, form) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + // Create a live thread and get its id. func (s *LiveThreadService) Create(ctx context.Context, request *LiveThreadCreateOrUpdateRequest) (string, *Response, error) { if request == nil { @@ -360,6 +485,25 @@ func (s *LiveThreadService) Uninvite(ctx context.Context, threadID, userID strin // If permissions is nil, all permissions will be granted. // Requires the "manage" permission. func (s *LiveThreadService) SetPermissions(ctx context.Context, id, username string, permissions *LiveThreadPermissions) (*Response, error) { + form := url.Values{} + form.Set("api_type", "json") + form.Set("name", username) + form.Set("type", "liveupdate_contributor") + form.Set("permissions", permissions.String()) + + path := fmt.Sprintf("/api/live/%s/set_contributor_permissions", id) + req, err := s.client.NewRequest(http.MethodPost, path, form) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// SetPermissionsForInvite sets the permissions for a contributor who's yet to accept/refuse their invite. +// If permissions is nil, all permissions will be granted. +// Requires the "manage" permission. +func (s *LiveThreadService) SetPermissionsForInvite(ctx context.Context, id, username string, permissions *LiveThreadPermissions) (*Response, error) { form := url.Values{} form.Set("api_type", "json") form.Set("name", username) @@ -391,6 +535,38 @@ func (s *LiveThreadService) Revoke(ctx context.Context, threadID, userID string) return s.client.Do(ctx, req, nil) } +// HideDiscussion hides a linked post from the live thread's discussion sidebar. +// The postID should be the base36 ID of the post, i.e. not its full id. +func (s *LiveThreadService) HideDiscussion(ctx context.Context, threadID, postID string) (*Response, error) { + form := url.Values{} + form.Set("api_type", "json") + form.Set("link", postID) + + path := fmt.Sprintf("/api/live/%s/hide_discussion", threadID) + req, err := s.client.NewRequest(http.MethodPost, path, form) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + +// UnhideDiscussion unhides a linked post from the live thread's discussion sidebar. +// The postID should be the base36 ID of the post, i.e. not its full id. +func (s *LiveThreadService) UnhideDiscussion(ctx context.Context, threadID, postID string) (*Response, error) { + form := url.Values{} + form.Set("api_type", "json") + form.Set("link", postID) + + path := fmt.Sprintf("/api/live/%s/unhide_discussion", threadID) + req, err := s.client.NewRequest(http.MethodPost, path, form) + if err != nil { + return nil, err + } + + return s.client.Do(ctx, req, nil) +} + // Report the live thread. // The reason should be one of: // spam, vote-manipulation, personal-information, sexualizing-minors, site-breaking diff --git a/reddit/live-thread_test.go b/reddit/live-thread_test.go index 3bfa108..c68d433 100644 --- a/reddit/live-thread_test.go +++ b/reddit/live-thread_test.go @@ -68,6 +68,89 @@ var expectedLiveThreads = []*LiveThread{ }, } +var expectedLiveThreadUpdates = []*LiveThreadUpdate{ + { + ID: "5e46cd94-f968-11ea-9a6a-0e1933241e7d", + FullID: "LiveUpdate_5e46cd94-f968-11ea-9a6a-0e1933241e7d", + Author: "testuser1", + Created: &Timestamp{time.Date(2020, 9, 18, 4, 35, 24, 0, time.UTC)}, + + Body: "test 2", + + Stricken: true, + }, + { + ID: "fc44f204-f964-11ea-b148-0e2e56a0425f", + FullID: "LiveUpdate_fc44f204-f964-11ea-b148-0e2e56a0425f", + Author: "testuser1", + Created: &Timestamp{time.Date(2020, 9, 18, 4, 11, 11, 0, time.UTC)}, + + Body: "test 1", + + Stricken: true, + }, +} + +var expectedLiveThreadUpdate = &LiveThreadUpdate{ + ID: "fc44f204-f964-11ea-b148-0e2e56a0425f", + FullID: "LiveUpdate_fc44f204-f964-11ea-b148-0e2e56a0425f", + Author: "testuser1", + Created: &Timestamp{time.Date(2020, 9, 18, 4, 11, 11, 0, time.UTC)}, + + Body: "test 1", + + Stricken: true, +} + +var expectedLiveThreadDiscussions = []*Post{ + { + ID: "test1", + FullID: "t3_test1", + Created: &Timestamp{time.Date(2020, 9, 16, 12, 37, 31, 0, time.UTC)}, + Edited: &Timestamp{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)}, + + Permalink: "/r/live/comments/test1/test_title/", + URL: "https://www.reddit.com/live/15nfp4mtfbo14/", + + Title: "test title", + + Score: 22, + UpvoteRatio: 0.9, + NumberOfComments: 1, + + SubredditName: "live", + SubredditNamePrefixed: "r/live", + SubredditID: "t5_32o7w", + SubredditSubscribers: 24501, + + Author: "TestUser", + AuthorID: "t2_test1", + }, + { + ID: "test2", + FullID: "t3_test2", + Created: &Timestamp{time.Date(2020, 9, 16, 12, 37, 1, 0, time.UTC)}, + Edited: &Timestamp{time.Date(1, 1, 1, 0, 0, 0, 0, time.UTC)}, + + Permalink: "/r/live/comments/test2/test_title/", + URL: "https://www.reddit.com/live/15nfp4mtfbo14/", + + Title: "test title", + + Score: 71, + UpvoteRatio: 0.97, + NumberOfComments: 34, + + SubredditName: "live", + SubredditNamePrefixed: "r/live", + SubredditID: "t5_32o7w", + SubredditSubscribers: 24501, + + Author: "TestUser", + AuthorID: "t2_test1", + }, +} + var expectedLiveThreadContributors = &LiveThreadContributors{ Current: []*LiveThreadContributor{ {ID: "t2_test1", Name: "test1", Permissions: []string{"all"}}, @@ -103,6 +186,37 @@ func TestLiveThreadService_Get(t *testing.T) { require.Equal(t, expectedLiveThread, liveThread) } +func TestLiveThreadService_Now(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + blob, err := readFileContents("../testdata/live-thread/live-thread.json") + require.NoError(t, err) + + mux.HandleFunc("/api/live/happening_now", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + fmt.Fprint(w, blob) + }) + + liveThread, _, err := client.LiveThread.Now(ctx) + require.NoError(t, err) + require.Equal(t, expectedLiveThread, liveThread) +} + +func TestLiveThreadService_Now_NoContent(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/happening_now", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + w.WriteHeader(204) + }) + + liveThread, _, err := client.LiveThread.Now(ctx) + require.NoError(t, err) + require.Nil(t, liveThread) +} + func TestLiveThreadService_GetMultiple(t *testing.T) { client, mux, teardown := setup() defer teardown() @@ -123,6 +237,117 @@ func TestLiveThreadService_GetMultiple(t *testing.T) { require.Equal(t, expectedLiveThreads, liveThreads) } +func TestLiveThreadService_Update(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/id123/update", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("body", "test") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + }) + + _, err := client.LiveThread.Update(ctx, "id123", "test") + require.NoError(t, err) +} + +func TestLiveThreadService_Updates(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + blob, err := readFileContents("../testdata/live-thread/updates.json") + require.NoError(t, err) + + mux.HandleFunc("/live/id123", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + fmt.Fprint(w, blob) + }) + + liveThreadUpdates, _, err := client.LiveThread.Updates(ctx, "id123", nil) + require.NoError(t, err) + require.Equal(t, expectedLiveThreadUpdates, liveThreadUpdates) +} + +func TestLiveThreadService_UpdateByID(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + blob, err := readFileContents("../testdata/live-thread/update.json") + require.NoError(t, err) + + mux.HandleFunc("/live/id123/updates/update123", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + fmt.Fprint(w, blob) + }) + + liveThreadUpdate, _, err := client.LiveThread.UpdateByID(ctx, "id123", "update123") + require.NoError(t, err) + require.Equal(t, expectedLiveThreadUpdate, liveThreadUpdate) +} + +func TestLiveThreadService_Discussions(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + blob, err := readFileContents("../testdata/live-thread/discussions.json") + require.NoError(t, err) + + mux.HandleFunc("/live/id123/discussions", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodGet, r.Method) + fmt.Fprint(w, blob) + }) + + liveThreadDiscussions, _, err := client.LiveThread.Discussions(ctx, "id123", nil) + require.NoError(t, err) + require.Equal(t, expectedLiveThreadDiscussions, liveThreadDiscussions) +} + +func TestLiveThreadService_Strike(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/id123/strike_update", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("id", "update123") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + }) + + _, err := client.LiveThread.Strike(ctx, "id123", "update123") + require.NoError(t, err) +} + +func TestLiveThreadService_Delete(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/id123/delete_update", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("id", "update123") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + }) + + _, err := client.LiveThread.Delete(ctx, "id123", "update123") + require.NoError(t, err) +} + func TestLiveThreadService_Create(t *testing.T) { client, mux, teardown := setup() defer teardown() @@ -369,7 +594,7 @@ func TestLiveThreadService_SetPermissions(t *testing.T) { form := url.Values{} form.Set("api_type", "json") form.Set("name", "testuser") - form.Set("type", "liveupdate_contributor_invite") + form.Set("type", "liveupdate_contributor") form.Set("permissions", "-all,-close,+discussions,+edit,-manage,+settings,-update") err := r.ParseForm() @@ -381,6 +606,28 @@ func TestLiveThreadService_SetPermissions(t *testing.T) { require.NoError(t, err) } +func TestLiveThreadService_SetPermissionsForInvite(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/id123/set_contributor_permissions", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("name", "testuser") + form.Set("type", "liveupdate_contributor_invite") + form.Set("permissions", "-all,-close,+discussions,+edit,-manage,+settings,-update") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + }) + + _, err := client.LiveThread.SetPermissionsForInvite(ctx, "id123", "testuser", &LiveThreadPermissions{Discussions: true, Edit: true, Settings: true}) + require.NoError(t, err) +} + func TestLiveThreadService_Revoke(t *testing.T) { client, mux, teardown := setup() defer teardown() @@ -401,6 +648,46 @@ func TestLiveThreadService_Revoke(t *testing.T) { require.NoError(t, err) } +func TestLiveThreadService_HideDiscussion(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/id123/hide_discussion", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("link", "t3_test") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + }) + + _, err := client.LiveThread.HideDiscussion(ctx, "id123", "t3_test") + require.NoError(t, err) +} + +func TestLiveThreadService_UnhideDiscussion(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/live/id123/unhide_discussion", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("link", "t3_test") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + }) + + _, err := client.LiveThread.UnhideDiscussion(ctx, "id123", "t3_test") + require.NoError(t, err) +} + func TestLiveThreadService_Report(t *testing.T) { client, mux, teardown := setup() defer teardown() diff --git a/reddit/reddit.go b/reddit/reddit.go index 19ac2c6..63f98b1 100644 --- a/reddit/reddit.go +++ b/reddit/reddit.go @@ -322,12 +322,12 @@ func (c *Client) Do(ctx context.Context, req *http.Request, v interface{}) (*Res if w, ok := v.(io.Writer); ok { _, err = io.Copy(w, response.Body) if err != nil { - return nil, err + return response, err } } else { err = json.NewDecoder(response.Body).Decode(v) if err != nil { - return nil, err + return response, err } } diff --git a/reddit/things.go b/reddit/things.go index 1f93a33..4e6ae65 100644 --- a/reddit/things.go +++ b/reddit/things.go @@ -18,6 +18,7 @@ const ( kindUserList = "UserList" kindMore = "more" kindLiveThread = "LiveUpdateEvent" + kindLiveThreadUpdate = "LiveUpdate" kindModAction = "modaction" kindMulti = "LabeledMulti" kindMultiDescription = "LabeledMultiDescription" @@ -92,6 +93,8 @@ func (t *thing) UnmarshalJSON(b []byte) error { v = new(Subreddit) case kindLiveThread: v = new(LiveThread) + case kindLiveThreadUpdate: + v = new(LiveThreadUpdate) case kindModAction: v = new(ModAction) case kindMulti: @@ -160,6 +163,11 @@ func (t *thing) LiveThread() (v *LiveThread, ok bool) { return } +func (t *thing) LiveThreadUpdate() (v *LiveThreadUpdate, ok bool) { + v, ok = t.Data.(*LiveThreadUpdate) + return +} + func (t *thing) ModAction() (v *ModAction, ok bool) { v, ok = t.Data.(*ModAction) return @@ -314,15 +322,23 @@ func (l *listing) LiveThreads() []*LiveThread { return l.things.LiveThreads } +func (l *listing) LiveThreadUpdates() []*LiveThreadUpdate { + if l == nil { + return nil + } + return l.things.LiveThreadUpdates +} + type things struct { - Comments []*Comment - Mores []*More - Users []*User - Posts []*Post - Subreddits []*Subreddit - ModActions []*ModAction - Multis []*Multi - LiveThreads []*LiveThread + Comments []*Comment + Mores []*More + Users []*User + Posts []*Post + Subreddits []*Subreddit + ModActions []*ModAction + Multis []*Multi + LiveThreads []*LiveThread + LiveThreadUpdates []*LiveThreadUpdate } // UnmarshalJSON implements the json.Unmarshaler interface. @@ -355,6 +371,8 @@ func (t *things) add(things ...thing) { t.Multis = append(t.Multis, v) case *LiveThread: t.LiveThreads = append(t.LiveThreads, v) + case *LiveThreadUpdate: + t.LiveThreadUpdates = append(t.LiveThreadUpdates, v) } } } diff --git a/testdata/live-thread/discussions.json b/testdata/live-thread/discussions.json new file mode 100644 index 0000000..70a475a --- /dev/null +++ b/testdata/live-thread/discussions.json @@ -0,0 +1,271 @@ +{ + "kind": "Listing", + "data": { + "modhash": null, + "dist": 2, + "children": [ + { + "kind": "t3", + "data": { + "approved_at_utc": null, + "subreddit": "live", + "selftext": "", + "author_fullname": "t2_test1", + "saved": false, + "mod_reason_title": null, + "gilded": 0, + "clicked": false, + "title": "test title", + "link_flair_richtext": [], + "subreddit_name_prefixed": "r/live", + "hidden": false, + "pwls": 6, + "link_flair_css_class": null, + "downs": 0, + "thumbnail_height": 140, + "top_awarded_type": null, + "hide_score": false, + "name": "t3_test1", + "quarantine": false, + "link_flair_text_color": "dark", + "upvote_ratio": 0.9, + "author_flair_background_color": null, + "subreddit_type": "public", + "ups": 22, + "total_awards_received": 0, + "media_embed": { + "content": "\n<div class=\"psuedo-selftext\">\n <iframe src=\"//www.redditmedia.com/live/15nfp4mtfbo14/embed\" height=\"500\"></iframe>\n</div>\n", + "width": 710, + "scrolling": false, + "height": 500 + }, + "thumbnail_width": 140, + "author_flair_template_id": null, + "is_original_content": false, + "user_reports": [], + "secure_media": { + "event_id": "15nfp4mtfbo14", + "type": "liveupdate" + }, + "is_reddit_media_domain": false, + "is_meta": false, + "category": null, + "secure_media_embed": { + "content": "\n<div class=\"psuedo-selftext\">\n <iframe src=\"//www.redditmedia.com/live/15nfp4mtfbo14/embed\" height=\"500\"></iframe>\n</div>\n", + "width": 710, + "scrolling": false, + "media_domain_url": "https://www.redditmedia.com/mediaembed/test1", + "height": 500 + }, + "link_flair_text": "LIVE THREAD", + "can_mod_post": false, + "score": 22, + "approved_by": null, + "author_premium": true, + "thumbnail": "default", + "edited": false, + "author_flair_css_class": null, + "author_flair_richtext": [], + "gildings": {}, + "content_categories": null, + "is_self": false, + "mod_note": null, + "created": 1600288651.0, + "link_flair_type": "text", + "wls": 6, + "removed_by_category": null, + "banned_by": null, + "author_flair_type": "text", + "domain": "reddit.com", + "allow_live_comments": false, + "selftext_html": null, + "likes": null, + "suggested_sort": null, + "banned_at_utc": null, + "url_overridden_by_dest": "https://www.reddit.com/live/15nfp4mtfbo14/", + "view_count": null, + "archived": false, + "no_follow": false, + "is_crosspostable": true, + "pinned": false, + "over_18": false, + "all_awardings": [], + "awarders": [], + "media_only": false, + "can_gild": true, + "spoiler": false, + "locked": false, + "author_flair_text": null, + "treatment_tags": [], + "visited": false, + "removed_by": null, + "num_reports": null, + "distinguished": null, + "subreddit_id": "t5_32o7w", + "mod_reason_by": null, + "removal_reason": null, + "link_flair_background_color": "", + "id": "test1", + "is_robot_indexable": true, + "report_reasons": null, + "author": "TestUser", + "discussion_type": null, + "num_comments": 1, + "send_replies": true, + "whitelist_status": "all_ads", + "contest_mode": false, + "mod_reports": [], + "author_patreon_flair": false, + "author_flair_text_color": null, + "permalink": "/r/live/comments/test1/test_title/", + "parent_whitelist_status": "all_ads", + "stickied": false, + "url": "https://www.reddit.com/live/15nfp4mtfbo14/", + "subreddit_subscribers": 24501, + "created_utc": 1600259851.0, + "num_crossposts": 0, + "media": { + "event_id": "15nfp4mtfbo14", + "type": "liveupdate" + }, + "is_video": false + } + }, + { + "kind": "t3", + "data": { + "approved_at_utc": null, + "subreddit": "live", + "selftext": "", + "author_fullname": "t2_test1", + "saved": false, + "mod_reason_title": null, + "gilded": 0, + "clicked": false, + "title": "test title", + "link_flair_richtext": [ + { + "e": "text", + "t": "LIVE THREAD CLOSED | No further updates." + } + ], + "subreddit_name_prefixed": "r/live", + "hidden": false, + "pwls": 6, + "link_flair_css_class": "diss", + "downs": 0, + "thumbnail_height": 140, + "top_awarded_type": null, + "hide_score": false, + "name": "t3_test2", + "quarantine": false, + "link_flair_text_color": "dark", + "upvote_ratio": 0.97, + "author_flair_background_color": null, + "subreddit_type": "public", + "ups": 71, + "total_awards_received": 0, + "media_embed": { + "content": "\n<div class=\"psuedo-selftext\">\n <iframe src=\"//www.redditmedia.com/live/15nfp4mtfbo14/embed\" height=\"500\"></iframe>\n</div>\n", + "width": 710, + "scrolling": false, + "height": 500 + }, + "thumbnail_width": 140, + "author_flair_template_id": null, + "is_original_content": false, + "user_reports": [], + "secure_media": { + "event_id": "15nfp4mtfbo14", + "type": "liveupdate" + }, + "is_reddit_media_domain": false, + "is_meta": false, + "category": null, + "secure_media_embed": { + "content": "\n<div class=\"psuedo-selftext\">\n <iframe src=\"//www.redditmedia.com/live/15nfp4mtfbo14/embed\" height=\"500\"></iframe>\n</div>\n", + "width": 710, + "scrolling": false, + "media_domain_url": "https://www.redditmedia.com/mediaembed/itulo6", + "height": 500 + }, + "link_flair_text": "LIVE THREAD CLOSED | No further updates.", + "can_mod_post": false, + "score": 71, + "approved_by": null, + "author_premium": true, + "thumbnail": "https://b.thumbs.redditmedia.com/rZKNaYfha47BqSqVTn2S7WGm5-ydloMOqz3Oqli87aU.jpg", + "edited": false, + "author_flair_css_class": null, + "author_flair_richtext": [], + "gildings": {}, + "content_categories": null, + "is_self": false, + "mod_note": null, + "created": 1600288621.0, + "link_flair_type": "richtext", + "wls": 6, + "removed_by_category": null, + "banned_by": null, + "author_flair_type": "text", + "domain": "reddit.com", + "allow_live_comments": true, + "selftext_html": null, + "likes": null, + "suggested_sort": "new", + "banned_at_utc": null, + "url_overridden_by_dest": "https://www.reddit.com/live/15nfp4mtfbo14/", + "view_count": null, + "archived": false, + "no_follow": false, + "is_crosspostable": true, + "pinned": false, + "over_18": false, + "all_awardings": [], + "awarders": [], + "media_only": false, + "link_flair_template_id": "9b12fc60-ff01-11e3-b179-12313b0a9e38", + "can_gild": true, + "spoiler": false, + "locked": false, + "author_flair_text": null, + "treatment_tags": [], + "visited": false, + "removed_by": null, + "num_reports": null, + "distinguished": null, + "subreddit_id": "t5_32o7w", + "mod_reason_by": null, + "removal_reason": null, + "link_flair_background_color": "#f5f5f5", + "id": "test2", + "is_robot_indexable": true, + "report_reasons": null, + "author": "TestUser", + "discussion_type": null, + "num_comments": 34, + "send_replies": true, + "whitelist_status": "all_ads", + "contest_mode": false, + "mod_reports": [], + "author_patreon_flair": false, + "author_flair_text_color": null, + "permalink": "/r/live/comments/test2/test_title/", + "parent_whitelist_status": "all_ads", + "stickied": false, + "url": "https://www.reddit.com/live/15nfp4mtfbo14/", + "subreddit_subscribers": 24501, + "created_utc": 1600259821.0, + "num_crossposts": 0, + "media": { + "event_id": "15nfp4mtfbo14", + "type": "liveupdate" + }, + "is_video": false + } + } + ], + "after": null, + "before": null + } +} diff --git a/testdata/live-thread/update.json b/testdata/live-thread/update.json new file mode 100644 index 0000000..fa0780e --- /dev/null +++ b/testdata/live-thread/update.json @@ -0,0 +1,26 @@ +{ + "kind": "Listing", + "data": { + "modhash": null, + "dist": null, + "children": [ + { + "kind": "LiveUpdate", + "data": { + "body": "test 1", + "name": "LiveUpdate_fc44f204-f964-11ea-b148-0e2e56a0425f", + "mobile_embeds": [], + "author": "testuser1", + "embeds": [], + "created": 1600431071.0, + "created_utc": 1600402271.0, + "body_html": "<div class=\"md\"><p>test 1</p>\n</div>", + "stricken": true, + "id": "fc44f204-f964-11ea-b148-0e2e56a0425f" + } + } + ], + "after": null, + "before": null + } +} diff --git a/testdata/live-thread/updates.json b/testdata/live-thread/updates.json new file mode 100644 index 0000000..64f82e2 --- /dev/null +++ b/testdata/live-thread/updates.json @@ -0,0 +1,41 @@ +{ + "kind": "Listing", + "data": { + "modhash": null, + "dist": null, + "children": [ + { + "kind": "LiveUpdate", + "data": { + "body": "test 2", + "name": "LiveUpdate_5e46cd94-f968-11ea-9a6a-0e1933241e7d", + "mobile_embeds": [], + "author": "testuser1", + "embeds": [], + "created": 1600432524.0, + "created_utc": 1600403724.0, + "body_html": "<div class=\"md\"><p>test 2</p>\n</div>", + "stricken": true, + "id": "5e46cd94-f968-11ea-9a6a-0e1933241e7d" + } + }, + { + "kind": "LiveUpdate", + "data": { + "body": "test 1", + "name": "LiveUpdate_fc44f204-f964-11ea-b148-0e2e56a0425f", + "mobile_embeds": [], + "author": "testuser1", + "embeds": [], + "created": 1600431071.0, + "created_utc": 1600402271.0, + "body_html": "<div class=\"md\"><p>test 1</p>\n</div>", + "stricken": true, + "id": "fc44f204-f964-11ea-b148-0e2e56a0425f" + } + } + ], + "after": null, + "before": null + } +}