WIP: Create widgets
Signed-off-by: Vartan Benohanian <vartanbeno@gmail.com>
This commit is contained in:
+128
-70
@@ -3,6 +3,7 @@ package reddit
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
@@ -22,6 +23,8 @@ type Widget interface {
|
|||||||
// kind returns the widget kind.
|
// kind returns the widget kind.
|
||||||
// having un unexported method on an exported interface means it cannot be implemented by a client.
|
// having un unexported method on an exported interface means it cannot be implemented by a client.
|
||||||
kind() string
|
kind() string
|
||||||
|
// GetID returns the widget's id.
|
||||||
|
GetID() string
|
||||||
}
|
}
|
||||||
|
|
||||||
const (
|
const (
|
||||||
@@ -36,6 +39,47 @@ const (
|
|||||||
widgetKindCustom = "custom"
|
widgetKindCustom = "custom"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type rootWidget struct {
|
||||||
|
Data Widget
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
|
func (w *rootWidget) UnmarshalJSON(data []byte) error {
|
||||||
|
root := new(struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
})
|
||||||
|
|
||||||
|
err := json.Unmarshal(data, root)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch root.Kind {
|
||||||
|
case widgetKindTextArea:
|
||||||
|
w.Data = new(TextAreaWidget)
|
||||||
|
case widgetKindButton:
|
||||||
|
w.Data = new(ButtonWidget)
|
||||||
|
case widgetKindImage:
|
||||||
|
w.Data = new(ImageWidget)
|
||||||
|
case widgetKindCommunityList:
|
||||||
|
w.Data = new(CommunityListWidget)
|
||||||
|
case widgetKindMenu:
|
||||||
|
w.Data = new(MenuWidget)
|
||||||
|
case widgetKindCommunityDetails:
|
||||||
|
w.Data = new(CommunityDetailsWidget)
|
||||||
|
case widgetKindModerators:
|
||||||
|
w.Data = new(ModeratorsWidget)
|
||||||
|
case widgetKindSubredditRules:
|
||||||
|
w.Data = new(SubredditRulesWidget)
|
||||||
|
case widgetKindCustom:
|
||||||
|
w.Data = new(CustomWidget)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("unrecognized widget kind: %q", root.Kind)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json.Unmarshal(data, w.Data)
|
||||||
|
}
|
||||||
|
|
||||||
// WidgetList is a list of widgets.
|
// WidgetList is a list of widgets.
|
||||||
type WidgetList []Widget
|
type WidgetList []Widget
|
||||||
|
|
||||||
@@ -47,46 +91,14 @@ func (l *WidgetList) UnmarshalJSON(data []byte) error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
type widgetKind struct {
|
|
||||||
Kind string `json:"kind"`
|
|
||||||
}
|
|
||||||
for _, w := range widgetMap {
|
for _, w := range widgetMap {
|
||||||
root := new(widgetKind)
|
root := new(rootWidget)
|
||||||
err = json.Unmarshal(w, root)
|
err = json.Unmarshal(w, root)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
var widget Widget
|
*l = append(*l, root.Data)
|
||||||
switch root.Kind {
|
|
||||||
case widgetKindTextArea:
|
|
||||||
widget = new(TextAreaWidget)
|
|
||||||
case widgetKindButton:
|
|
||||||
widget = new(ButtonWidget)
|
|
||||||
case widgetKindImage:
|
|
||||||
widget = new(ImageWidget)
|
|
||||||
case widgetKindCommunityList:
|
|
||||||
widget = new(CommunityListWidget)
|
|
||||||
case widgetKindMenu:
|
|
||||||
widget = new(MenuWidget)
|
|
||||||
case widgetKindCommunityDetails:
|
|
||||||
widget = new(CommunityDetailsWidget)
|
|
||||||
case widgetKindModerators:
|
|
||||||
widget = new(ModeratorsWidget)
|
|
||||||
case widgetKindSubredditRules:
|
|
||||||
widget = new(SubredditRulesWidget)
|
|
||||||
case widgetKindCustom:
|
|
||||||
widget = new(CustomWidget)
|
|
||||||
default:
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
err = json.Unmarshal(w, widget)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
*l = append(*l, widget)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
@@ -99,6 +111,9 @@ type widget struct {
|
|||||||
Style *WidgetStyle `json:"styles,omitempty"`
|
Style *WidgetStyle `json:"styles,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (w *widget) kind() string { return w.Kind }
|
||||||
|
func (w *widget) GetID() string { return w.ID }
|
||||||
|
|
||||||
// TextAreaWidget displays a box of text in the subreddit.
|
// TextAreaWidget displays a box of text in the subreddit.
|
||||||
type TextAreaWidget struct {
|
type TextAreaWidget struct {
|
||||||
widget
|
widget
|
||||||
@@ -107,10 +122,6 @@ type TextAreaWidget struct {
|
|||||||
Text string `json:"text,omitempty"`
|
Text string `json:"text,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *TextAreaWidget) kind() string {
|
|
||||||
return widgetKindTextArea
|
|
||||||
}
|
|
||||||
|
|
||||||
// ButtonWidget displays up to 10 button style links with customizable font colors for each button.
|
// ButtonWidget displays up to 10 button style links with customizable font colors for each button.
|
||||||
type ButtonWidget struct {
|
type ButtonWidget struct {
|
||||||
widget
|
widget
|
||||||
@@ -120,10 +131,6 @@ type ButtonWidget struct {
|
|||||||
Buttons []*WidgetButton `json:"buttons,omitempty"`
|
Buttons []*WidgetButton `json:"buttons,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *ButtonWidget) kind() string {
|
|
||||||
return widgetKindButton
|
|
||||||
}
|
|
||||||
|
|
||||||
// ImageWidget display a random image from up to 10 selected images.
|
// ImageWidget display a random image from up to 10 selected images.
|
||||||
// The image can be clickable links.
|
// The image can be clickable links.
|
||||||
type ImageWidget struct {
|
type ImageWidget struct {
|
||||||
@@ -133,10 +140,6 @@ type ImageWidget struct {
|
|||||||
Images []*WidgetImageLink `json:"data,omitempty"`
|
Images []*WidgetImageLink `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *ImageWidget) kind() string {
|
|
||||||
return widgetKindImage
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommunityListWidget display a list of up to 10 other communities (subreddits).
|
// CommunityListWidget display a list of up to 10 other communities (subreddits).
|
||||||
type CommunityListWidget struct {
|
type CommunityListWidget struct {
|
||||||
widget
|
widget
|
||||||
@@ -145,10 +148,6 @@ type CommunityListWidget struct {
|
|||||||
Communities []*WidgetCommunity `json:"data,omitempty"`
|
Communities []*WidgetCommunity `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *CommunityListWidget) kind() string {
|
|
||||||
return widgetKindCommunityList
|
|
||||||
}
|
|
||||||
|
|
||||||
// MenuWidget displays tabs for your community's menu. These can be direct links or submenus that
|
// MenuWidget displays tabs for your community's menu. These can be direct links or submenus that
|
||||||
// create a drop-down menu to multiple links.
|
// create a drop-down menu to multiple links.
|
||||||
type MenuWidget struct {
|
type MenuWidget struct {
|
||||||
@@ -158,10 +157,6 @@ type MenuWidget struct {
|
|||||||
Links WidgetLinkList `json:"data,omitempty"`
|
Links WidgetLinkList `json:"data,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (w *MenuWidget) kind() string {
|
|
||||||
return widgetKindMenu
|
|
||||||
}
|
|
||||||
|
|
||||||
// CommunityDetailsWidget displays your subscriber count, users online, and community description,
|
// CommunityDetailsWidget displays your subscriber count, users online, and community description,
|
||||||
// as defined in your subreddit settings. You can customize the displayed text for subscribers and
|
// as defined in your subreddit settings. You can customize the displayed text for subscribers and
|
||||||
// users currently viewing the community.
|
// users currently viewing the community.
|
||||||
@@ -178,10 +173,6 @@ type CommunityDetailsWidget struct {
|
|||||||
CurrentlyViewingText string `json:"currentlyViewingText,omitempty"`
|
CurrentlyViewingText string `json:"currentlyViewingText,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*CommunityDetailsWidget) kind() string {
|
|
||||||
return widgetKindCommunityDetails
|
|
||||||
}
|
|
||||||
|
|
||||||
// ModeratorsWidget displays the list of moderators of the subreddit.
|
// ModeratorsWidget displays the list of moderators of the subreddit.
|
||||||
type ModeratorsWidget struct {
|
type ModeratorsWidget struct {
|
||||||
widget
|
widget
|
||||||
@@ -190,10 +181,6 @@ type ModeratorsWidget struct {
|
|||||||
Total int `json:"totalMods"`
|
Total int `json:"totalMods"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*ModeratorsWidget) kind() string {
|
|
||||||
return widgetKindModerators
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
func (w *ModeratorsWidget) UnmarshalJSON(data []byte) error {
|
func (w *ModeratorsWidget) UnmarshalJSON(data []byte) error {
|
||||||
root := new(struct {
|
root := new(struct {
|
||||||
@@ -229,10 +216,6 @@ type SubredditRulesWidget struct {
|
|||||||
Rules []string `json:"rules,omitempty"`
|
Rules []string `json:"rules,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*SubredditRulesWidget) kind() string {
|
|
||||||
return widgetKindSubredditRules
|
|
||||||
}
|
|
||||||
|
|
||||||
// UnmarshalJSON implements the json.Unmarshaler interface.
|
// UnmarshalJSON implements the json.Unmarshaler interface.
|
||||||
func (w *SubredditRulesWidget) UnmarshalJSON(data []byte) error {
|
func (w *SubredditRulesWidget) UnmarshalJSON(data []byte) error {
|
||||||
root := new(struct {
|
root := new(struct {
|
||||||
@@ -272,10 +255,6 @@ type CustomWidget struct {
|
|||||||
Images []*WidgetImage `json:"imageData,omitempty"`
|
Images []*WidgetImage `json:"imageData,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (*CustomWidget) kind() string {
|
|
||||||
return widgetKindCustom
|
|
||||||
}
|
|
||||||
|
|
||||||
// WidgetStyle contains style information for the widget.
|
// WidgetStyle contains style information for the widget.
|
||||||
type WidgetStyle struct {
|
type WidgetStyle struct {
|
||||||
HeaderColor string `json:"headerColor,omitempty"`
|
HeaderColor string `json:"headerColor,omitempty"`
|
||||||
@@ -381,6 +360,52 @@ type WidgetButtonHoverState struct {
|
|||||||
StrokeColor string `json:"color,omitempty"`
|
StrokeColor string `json:"color,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WidgetCreateRequest represents a request to create a widget.
|
||||||
|
type WidgetCreateRequest interface {
|
||||||
|
requestKind() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// TextAreaWidgetCreateRequest represents a requets to create a text area widget.
|
||||||
|
type TextAreaWidgetCreateRequest struct {
|
||||||
|
Style *WidgetStyle `json:"styles,omitempty"`
|
||||||
|
// No longer than 30 characters.
|
||||||
|
Name string `json:"shortName,omitempty"`
|
||||||
|
// Raw markdown text.
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*TextAreaWidgetCreateRequest) requestKind() string { return widgetKindTextArea }
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (r *TextAreaWidgetCreateRequest) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Style *WidgetStyle `json:"styles,omitempty"`
|
||||||
|
Name string `json:"shortName,omitempty"`
|
||||||
|
Text string `json:"text,omitempty"`
|
||||||
|
}{r.requestKind(), r.Style, r.Name, r.Text})
|
||||||
|
}
|
||||||
|
|
||||||
|
// CommunityListWidgetCreateRequest represents a requets to create a community list widget.
|
||||||
|
type CommunityListWidgetCreateRequest struct {
|
||||||
|
Style *WidgetStyle `json:"styles,omitempty"`
|
||||||
|
// No longer than 30 characters.
|
||||||
|
Name string `json:"shortName,omitempty"`
|
||||||
|
Communities []string `json:"data,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*CommunityListWidgetCreateRequest) requestKind() string { return widgetKindCommunityList }
|
||||||
|
|
||||||
|
// MarshalJSON implements the json.Marshaler interface.
|
||||||
|
func (r *CommunityListWidgetCreateRequest) MarshalJSON() ([]byte, error) {
|
||||||
|
return json.Marshal(struct {
|
||||||
|
Kind string `json:"kind"`
|
||||||
|
Style *WidgetStyle `json:"styles,omitempty"`
|
||||||
|
Name string `json:"shortName,omitempty"`
|
||||||
|
Communities []string `json:"data,omitempty"`
|
||||||
|
}{r.requestKind(), r.Style, r.Name, r.Communities})
|
||||||
|
}
|
||||||
|
|
||||||
// Get the subreddit's widgets.
|
// Get the subreddit's widgets.
|
||||||
func (s *WidgetService) Get(ctx context.Context, subreddit string) ([]Widget, *Response, error) {
|
func (s *WidgetService) Get(ctx context.Context, subreddit string) ([]Widget, *Response, error) {
|
||||||
path := fmt.Sprintf("r/%s/api/widgets?progressive_images=true", subreddit)
|
path := fmt.Sprintf("r/%s/api/widgets?progressive_images=true", subreddit)
|
||||||
@@ -400,6 +425,27 @@ func (s *WidgetService) Get(ctx context.Context, subreddit string) ([]Widget, *R
|
|||||||
return root.Widgets, resp, nil
|
return root.Widgets, resp, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create a widget for the subreddit.
|
||||||
|
func (s *WidgetService) Create(ctx context.Context, subreddit string, request WidgetCreateRequest) (Widget, *Response, error) {
|
||||||
|
if request == nil {
|
||||||
|
return nil, nil, errors.New("WidgetCreateRequest: cannot be nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
path := fmt.Sprintf("r/%s/api/widget", subreddit)
|
||||||
|
req, err := s.client.NewJSONRequest(http.MethodPost, path, request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
root := new(rootWidget)
|
||||||
|
resp, err := s.client.Do(ctx, req, root)
|
||||||
|
if err != nil {
|
||||||
|
return nil, resp, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return root.Data, resp, nil
|
||||||
|
}
|
||||||
|
|
||||||
// Delete a widget via its id.
|
// Delete a widget via its id.
|
||||||
func (s *WidgetService) Delete(ctx context.Context, subreddit, id string) (*Response, error) {
|
func (s *WidgetService) Delete(ctx context.Context, subreddit, id string) (*Response, error) {
|
||||||
path := fmt.Sprintf("r/%s/api/widget/%s", subreddit, id)
|
path := fmt.Sprintf("r/%s/api/widget/%s", subreddit, id)
|
||||||
@@ -409,3 +455,15 @@ func (s *WidgetService) Delete(ctx context.Context, subreddit, id string) (*Resp
|
|||||||
}
|
}
|
||||||
return s.client.Do(ctx, req, nil)
|
return s.client.Do(ctx, req, nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Reorder the widgets in the subreddit.
|
||||||
|
// The order should contain every single widget id in the subreddit; omitting any id will result in an error.
|
||||||
|
// The id list should only contain sidebar widgets. It should exclude the community details and moderators widgets.
|
||||||
|
func (s *WidgetService) Reorder(ctx context.Context, subreddit string, ids []string) (*Response, error) {
|
||||||
|
path := fmt.Sprintf("r/%s/api/widget_order/sidebar", subreddit)
|
||||||
|
req, err := s.client.NewJSONRequest(http.MethodPatch, path, ids)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return s.client.Do(ctx, req, nil)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package reddit
|
package reddit
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
@@ -195,6 +196,49 @@ func TestWidgetService_Get(t *testing.T) {
|
|||||||
require.ElementsMatch(t, expectedWidgets, widgets)
|
require.ElementsMatch(t, expectedWidgets, widgets)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWidgetService_Create(t *testing.T) {
|
||||||
|
client, mux, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/r/testsubreddit/api/widget", func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
require.Equal(t, http.MethodPost, r.Method)
|
||||||
|
|
||||||
|
body := new(struct {
|
||||||
|
Name string `json:"shortName"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
})
|
||||||
|
|
||||||
|
err := json.NewDecoder(r.Body).Decode(body)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "test name", body.Name)
|
||||||
|
require.Equal(t, "test text", body.Text)
|
||||||
|
|
||||||
|
fmt.Fprint(w, `{
|
||||||
|
"text": "test text",
|
||||||
|
"kind": "textarea",
|
||||||
|
"shortName": "test name",
|
||||||
|
"id": "id123"
|
||||||
|
}`)
|
||||||
|
})
|
||||||
|
|
||||||
|
_, _, err := client.Widget.Create(ctx, "testsubreddit", nil)
|
||||||
|
require.EqualError(t, err, "WidgetCreateRequest: cannot be nil")
|
||||||
|
|
||||||
|
createdWidget, _, err := client.Widget.Create(ctx, "testsubreddit", &TextAreaWidgetCreateRequest{
|
||||||
|
Name: "test name",
|
||||||
|
Text: "test text",
|
||||||
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, &TextAreaWidget{
|
||||||
|
widget: widget{
|
||||||
|
ID: "id123",
|
||||||
|
Kind: "textarea",
|
||||||
|
},
|
||||||
|
Name: "test name",
|
||||||
|
Text: "test text",
|
||||||
|
}, createdWidget)
|
||||||
|
}
|
||||||
|
|
||||||
func TestWidgetService_Delete(t *testing.T) {
|
func TestWidgetService_Delete(t *testing.T) {
|
||||||
client, mux, teardown := setup()
|
client, mux, teardown := setup()
|
||||||
defer teardown()
|
defer teardown()
|
||||||
@@ -206,3 +250,20 @@ func TestWidgetService_Delete(t *testing.T) {
|
|||||||
_, err := client.Widget.Delete(ctx, "testsubreddit", "abc123")
|
_, err := client.Widget.Delete(ctx, "testsubreddit", "abc123")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestWidgetService_Reorder(t *testing.T) {
|
||||||
|
client, mux, teardown := setup()
|
||||||
|
defer teardown()
|
||||||
|
|
||||||
|
mux.HandleFunc("/r/testsubreddit/api/widget_order/sidebar", 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.Widget.Reorder(ctx, "testsubreddit", []string{"test1", "test2", "test3", "test4"})
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user