From 34c2559707935f34685da45f5128566b674e43ab Mon Sep 17 00:00:00 2001 From: Vartan Benohanian Date: Tue, 8 Sep 2020 22:36:12 -0400 Subject: [PATCH] Create, update, and reorder flair templates Signed-off-by: Vartan Benohanian --- reddit/flair.go | 130 ++++++++++++++++++++++++- reddit/flair_test.go | 147 +++++++++++++++++++++++++++++ testdata/flair/flair-template.json | 20 ++++ 3 files changed, 296 insertions(+), 1 deletion(-) create mode 100644 testdata/flair/flair-template.json diff --git a/reddit/flair.go b/reddit/flair.go index 16e3611..86c35db 100644 --- a/reddit/flair.go +++ b/reddit/flair.go @@ -40,7 +40,7 @@ type FlairSummary struct { } // ConfigureFlairRequest represents a request to configure a subreddit's flair settings. -// Not setting an attribute can have unexpected side effects. +// Not setting an attribute can have unexpected side effects, so assign every one just in case. type ConfigureFlairRequest struct { // Enable user flair in the subreddit. UserFlairEnabled *bool `url:"flair_enabled,omitempty"` @@ -48,12 +48,60 @@ type ConfigureFlairRequest struct { UserFlairPosition string `url:"flair_position,omitempty"` // Allow users to assign their own flair. UserFlairSelfAssignEnabled *bool `url:"flair_self_assign_enabled,omitempty"` + // One of: none, left, right. PostFlairPosition string `url:"link_flair_position,omitempty"` // Allow submitters to assign their own post flair. PostFlairSelfAssignEnabled *bool `url:"link_flair_self_assign_enabled,omitempty"` } +// FlairTemplateCreateOrUpdateRequest represents a request to create/update a flair template. +// Not setting an attribute can have unexpected side effects, so assign every one just in case. +type FlairTemplateCreateOrUpdateRequest struct { + // The id of the template. Only provide this if it's an update request. + // If provided and it's not a valid id, the template will be created. + ID string `url:"flair_template_id,omitempty"` + + // One of: all, emoji, text. + AllowableContent string `url:"allowable_content,omitempty"` + // No longer than 64 characters. + Text string `url:"text,omitempty"` + // One of: light, dark. + TextColor string `url:"text_color,omitempty"` + // Allow user to edit the text of the flair. + TextEditable *bool `url:"text_editable,omitempty"` + + ModOnly *bool `url:"mod_only,omitempty"` + + // Between 1 and 10 (inclusive). Default: 10. + MaxEmojis *int `url:"max_emojis,omitempty"` + + // One of: none, transparent, 6-digit rgb hex color, e.g. #AABBCC. + BackgroundColor string `url:"background_color,omitempty"` + CSSClass string `url:"css_class,omitempty"` +} + +// FlairTemplate is a generic flair structure that can users can use next to their username +// or posts in a subreddit. +type FlairTemplate struct { + ID string `json:"id"` + // USER_FLAIR (for users) or LINK_FLAIR (for posts). + Type string `json:"flairType"` + ModOnly bool `json:"modOnly"` + + AllowableContent string `json:"allowableContent"` + Text string `json:"text"` + TextType string `json:"type"` + TextColor string `json:"textColor"` + TextEditable bool `json:"textEditable"` + RichText []map[string]string `json:"richtext"` + + OverrideCSS bool `json:"overrideCss"` + MaxEmojis int `json:"maxEmojis"` + BackgroundColor string `json:"backgroundColor"` + CSSClass string `json:"cssClass"` +} + // GetUserFlairs returns the user flairs from the subreddit. func (s *FlairService) GetUserFlairs(ctx context.Context, subreddit string) ([]*Flair, *Response, error) { path := fmt.Sprintf("r/%s/api/user_flair_v2", subreddit) @@ -164,6 +212,66 @@ func (s *FlairService) Disable(ctx context.Context, subreddit string) (*Response return s.client.Do(ctx, req, nil) } +// UpsertUserTemplate creates a user flair template, or updates it if the request.ID is valid. +// It returns the created/updated flair template. +func (s *FlairService) UpsertUserTemplate(ctx context.Context, subreddit string, request *FlairTemplateCreateOrUpdateRequest) (*FlairTemplate, *Response, error) { + if request == nil { + return nil, nil, errors.New("request: cannot be nil") + } + + path := fmt.Sprintf("r/%s/api/flairtemplate_v2", subreddit) + + form, err := query.Values(request) + if err != nil { + return nil, nil, err + } + form.Set("api_type", "json") + form.Set("flair_type", "USER_FLAIR") + + req, err := s.client.NewRequestWithForm(http.MethodPost, path, form) + if err != nil { + return nil, nil, err + } + + root := new(FlairTemplate) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, nil +} + +// UpsertPostTemplate creates a post flair template, or updates it if the request.ID is valid. +// It returns the created/updated flair template. +func (s *FlairService) UpsertPostTemplate(ctx context.Context, subreddit string, request *FlairTemplateCreateOrUpdateRequest) (*FlairTemplate, *Response, error) { + if request == nil { + return nil, nil, errors.New("request: cannot be nil") + } + + path := fmt.Sprintf("r/%s/api/flairtemplate_v2", subreddit) + + form, err := query.Values(request) + if err != nil { + return nil, nil, err + } + form.Set("api_type", "json") + form.Set("flair_type", "LINK_FLAIR") + + req, err := s.client.NewRequestWithForm(http.MethodPost, path, form) + if err != nil { + return nil, nil, err + } + + root := new(FlairTemplate) + resp, err := s.client.Do(ctx, req, root) + if err != nil { + return nil, resp, err + } + + return root, resp, nil +} + // Delete the flair of the user. func (s *FlairService) Delete(ctx context.Context, subreddit, username string) (*Response, error) { path := fmt.Sprintf("r/%s/api/deleteflair", subreddit) @@ -227,3 +335,23 @@ func (s *FlairService) DeleteAllPostTemplates(ctx context.Context, subreddit str return s.client.Do(ctx, req, nil) } + +// ReorderUserTemplates reorders the user flair templates in the order provided in the slice. +func (s *FlairService) ReorderUserTemplates(ctx context.Context, subreddit string, ids []string) (*Response, error) { + path := fmt.Sprintf("api/v1/%s/flair_template_order/USER_FLAIR", subreddit) + req, err := s.client.NewRequest(http.MethodPatch, path, ids) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ReorderPostTemplates reorders the post flair templates in the order provided in the slice. +func (s *FlairService) ReorderPostTemplates(ctx context.Context, subreddit string, ids []string) (*Response, error) { + path := fmt.Sprintf("api/v1/%s/flair_template_order/LINK_FLAIR", subreddit) + req, err := s.client.NewRequest(http.MethodPatch, path, ids) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/reddit/flair_test.go b/reddit/flair_test.go index 2c7de17..cdb96c9 100644 --- a/reddit/flair_test.go +++ b/reddit/flair_test.go @@ -1,6 +1,7 @@ package reddit import ( + "encoding/json" "fmt" "net/http" "net/url" @@ -62,6 +63,26 @@ var expectedListUserFlairs = []*FlairSummary{ }, } +var expectedFlairTemplate = &FlairTemplate{ + ID: "be0a6110-f23c-11ea-862f-0e08890d7323", + Type: "LINK_FLAIR", + ModOnly: false, + + AllowableContent: "all", + Text: "lol", + TextType: "richtext", + TextColor: "dark", + TextEditable: false, + RichText: []map[string]string{ + {"e": "text", "t": "lol"}, + }, + + OverrideCSS: false, + MaxEmojis: 1, + BackgroundColor: "#fafafa", + CSSClass: "", +} + func TestFlairService_GetUserFlairs(t *testing.T) { client, mux, teardown := setup() defer teardown() @@ -186,6 +207,98 @@ func TestFlairService_Disable(t *testing.T) { require.NoError(t, err) } +func TestFlairService_UpsertUserTemplate(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + blob, err := readFileContents("../testdata/flair/flair-template.json") + require.NoError(t, err) + + mux.HandleFunc("/r/testsubreddit/api/flairtemplate_v2", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("flair_type", "USER_FLAIR") + form.Set("allowable_content", "all") + form.Set("text", "testtext") + form.Set("text_color", "dark") + form.Set("text_editable", "false") + form.Set("mod_only", "true") + form.Set("max_emojis", "5") + form.Set("background_color", "transparent") + form.Set("css_class", "testclass") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + + fmt.Fprint(w, blob) + }) + + _, _, err = client.Flair.UpsertUserTemplate(ctx, "testsubreddit", nil) + require.EqualError(t, err, "request: cannot be nil") + + flairTemplate, _, err := client.Flair.UpsertUserTemplate(ctx, "testsubreddit", &FlairTemplateCreateOrUpdateRequest{ + AllowableContent: "all", + ModOnly: Bool(true), + Text: "testtext", + TextColor: "dark", + TextEditable: Bool(false), + MaxEmojis: Int(5), + BackgroundColor: "transparent", + CSSClass: "testclass", + }) + require.NoError(t, err) + require.Equal(t, expectedFlairTemplate, flairTemplate) +} + +func TestFlairService_UpsertPostTemplate(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + blob, err := readFileContents("../testdata/flair/flair-template.json") + require.NoError(t, err) + + mux.HandleFunc("/r/testsubreddit/api/flairtemplate_v2", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPost, r.Method) + + form := url.Values{} + form.Set("api_type", "json") + form.Set("flair_type", "LINK_FLAIR") + form.Set("flair_template_id", "testid") + form.Set("allowable_content", "text") + form.Set("text", "testtext") + form.Set("text_color", "light") + form.Set("text_editable", "true") + form.Set("mod_only", "false") + form.Set("background_color", "#fafafa") + form.Set("css_class", "testclass") + + err := r.ParseForm() + require.NoError(t, err) + require.Equal(t, form, r.PostForm) + + fmt.Fprint(w, blob) + }) + + _, _, err = client.Flair.UpsertPostTemplate(ctx, "testsubreddit", nil) + require.EqualError(t, err, "request: cannot be nil") + + flairTemplate, _, err := client.Flair.UpsertPostTemplate(ctx, "testsubreddit", &FlairTemplateCreateOrUpdateRequest{ + ID: "testid", + AllowableContent: "text", + ModOnly: Bool(false), + Text: "testtext", + TextColor: "light", + TextEditable: Bool(true), + BackgroundColor: "#fafafa", + CSSClass: "testclass", + }) + require.NoError(t, err) + require.Equal(t, expectedFlairTemplate, flairTemplate) +} + func TestFlairService_Delete(t *testing.T) { client, mux, teardown := setup() defer teardown() @@ -265,3 +378,37 @@ func TestFlairService_DeleteAllPostTemplates(t *testing.T) { _, err := client.Flair.DeleteAllPostTemplates(ctx, "testsubreddit") require.NoError(t, err) } + +func TestFlairService_ReorderUserTemplates(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/v1/testsubreddit/flair_template_order/USER_FLAIR", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPatch, r.Method) + + var ids []string + err := json.NewDecoder(r.Body).Decode(&ids) + require.NoError(t, err) + require.Equal(t, []string{"test1", "test2", "test3", "test4"}, ids) + }) + + _, err := client.Flair.ReorderUserTemplates(ctx, "testsubreddit", []string{"test1", "test2", "test3", "test4"}) + require.NoError(t, err) +} + +func TestFlairService_ReorderPostTemplates(t *testing.T) { + client, mux, teardown := setup() + defer teardown() + + mux.HandleFunc("/api/v1/testsubreddit/flair_template_order/LINK_FLAIR", func(w http.ResponseWriter, r *http.Request) { + require.Equal(t, http.MethodPatch, r.Method) + + var ids []string + err := json.NewDecoder(r.Body).Decode(&ids) + require.NoError(t, err) + require.Equal(t, []string{"test1", "test2", "test3", "test4"}, ids) + }) + + _, err := client.Flair.ReorderPostTemplates(ctx, "testsubreddit", []string{"test1", "test2", "test3", "test4"}) + require.NoError(t, err) +} diff --git a/testdata/flair/flair-template.json b/testdata/flair/flair-template.json new file mode 100644 index 0000000..88e1850 --- /dev/null +++ b/testdata/flair/flair-template.json @@ -0,0 +1,20 @@ +{ + "text": "lol", + "allowableContent": "all", + "modOnly": false, + "cssClass": "", + "id": "be0a6110-f23c-11ea-862f-0e08890d7323", + "textEditable": false, + "overrideCss": false, + "richtext": [ + { + "e": "text", + "t": "lol" + } + ], + "maxEmojis": 1, + "flairType": "LINK_FLAIR", + "backgroundColor": "#fafafa", + "textColor": "dark", + "type": "richtext" +}