diff -r fc338e606d37 -r e2a16e19eb8b status.go --- a/status.go Thu Apr 13 12:38:59 2017 +0200 +++ b/status.go Thu Apr 13 12:54:05 2017 +0200 @@ -8,9 +8,24 @@ "github.com/sendgrid/rest" ) +// updateStatusOptions contains option field for POST and DELETE API calls +type updateStatusOptions struct { + // The ID is used for most commands + ID int + + // The following fields are used for posting a new status + Status string + InReplyToID int + MediaIDs []int + Sensitive bool + SpoilerText string + Visibility string // "direct", "private", "unlisted" or "public" +} + // queryStatusData queries the statuses API -// The subquery can be empty (the status itself), "context", "card", -// "reblogged_by", "favourited_by". +// The subquery can be empty or "status" (the status itself), "context", +// "card", "reblogged_by", "favourited_by". +// The data argument will receive the object(s) returned by the API server. func (g *Client) queryStatusData(statusID int, subquery string, data interface{}) error { endPoint := "statuses/" + strconv.Itoa(statusID) @@ -18,8 +33,13 @@ return ErrInvalidID } - if subquery != "" { - // TODO: check subquery values? + if subquery != "" && subquery != "status" { + switch subquery { + case "context", "card", "reblogged_by", "favourited_by": + default: + return ErrInvalidParameter + } + endPoint += "/" + subquery } req := g.prepareRequest(endPoint) @@ -41,13 +61,94 @@ return nil } +// updateStatusData updates the statuses +// The subquery can be empty or "status" (to post a status), "delete" (for +// deleting a status), "reblog", "unreblog", "favourite", "unfavourite". +// The data argument will receive the object(s) returned by the API server. +func (g *Client) updateStatusData(subquery string, opts updateStatusOptions, data interface{}) error { + method := rest.Post + endPoint := "statuses" + + switch subquery { + case "", "status": + subquery = "status" + if opts.Status == "" { + return ErrInvalidParameter + } + switch opts.Visibility { + case "", "direct", "private", "unlisted", "public": + // Okay + default: + return ErrInvalidParameter + } + if len(opts.MediaIDs) > 4 { + return fmt.Errorf("too many (>4) media IDs") + } + case "delete": + method = rest.Delete + if opts.ID < 1 { + return ErrInvalidID + } + endPoint += "/" + strconv.Itoa(opts.ID) + case "reblog", "unreblog", "favourite", "unfavourite": + if opts.ID < 1 { + return ErrInvalidID + } + endPoint += "/" + strconv.Itoa(opts.ID) + "/" + subquery + default: + return ErrInvalidParameter + } + + req := g.prepareRequest(endPoint) + req.Method = method + + // Form items for a new toot + if subquery == "status" { + req.QueryParams["status"] = opts.Status + if opts.InReplyToID > 0 { + req.QueryParams["in_reply_to_id"] = strconv.Itoa(opts.InReplyToID) + } + for i, id := range opts.MediaIDs { + qID := fmt.Sprintf("media_ids[%d]", i+1) + req.QueryParams[qID] = strconv.Itoa(id) + } + if opts.Sensitive { + req.QueryParams["sensitive"] = "true" + } + if opts.SpoilerText != "" { + req.QueryParams["spoiler_text"] = opts.SpoilerText + } + if opts.Visibility != "" { + req.QueryParams["visibility"] = opts.Visibility + } + } + + r, err := rest.API(req) + if err != nil { + return fmt.Errorf("status/%s API query: %s", subquery, err.Error()) + } + + // Check for error reply + var errorResult Error + if err := json.Unmarshal([]byte(r.Body), &errorResult); err == nil { + return fmt.Errorf("%s", errorResult.Text) + } + + // Not an error reply; let's unmarshall the data + err = json.Unmarshal([]byte(r.Body), &data) + if err != nil { + return fmt.Errorf("status/%s API: %s", subquery, err.Error()) + } + return nil +} + // GetStatus returns a status // The returned status can be nil if there is an error or if the // requested ID does not exist. func (g *Client) GetStatus(id int) (*Status, error) { var status Status - if err := g.queryStatusData(id, "", &status); err != nil { + if err := g.queryStatusData(id, "status", &status); err != nil { return nil, err } @@ -93,3 +194,67 @@ err := g.queryStatusData(id, "favourited_by", &accounts) return accounts, err } + +// PostStatus posts a new "toot" +// All parameters but "text" can be empty. +// Visibility must be empty, or one of "direct", "private", "unlisted" and "public". +func (g *Client) PostStatus(text string, inReplyTo int, mediaIDs []int, sensitive bool, spoilerText string, visibility string) (*Status, error) { + var status Status + o := updateStatusOptions{ + Status: text, + InReplyToID: inReplyTo, + MediaIDs: mediaIDs, + Sensitive: sensitive, + SpoilerText: spoilerText, + Visibility: visibility, + } + + err := g.updateStatusData("status", o, &status) + if err != nil { + return nil, err + } + if status.ID == 0 { + return nil, ErrEntityNotFound // TODO Change error message + } + return &status, err +} + +// DeleteStatus deletes a status +func (g *Client) DeleteStatus(id int) error { + var status Status + o := updateStatusOptions{ID: id} + err := g.updateStatusData("delete", o, &status) + return err +} + +// ReblogStatus reblogs a status +func (g *Client) ReblogStatus(id int) error { + var status Status + o := updateStatusOptions{ID: id} + err := g.updateStatusData("reblog", o, &status) + return err +} + +// UnreblogStatus unreblogs a status +func (g *Client) UnreblogStatus(id int) error { + var status Status + o := updateStatusOptions{ID: id} + err := g.updateStatusData("unreblog", o, &status) + return err +} + +// FavouriteStatus favourites a status +func (g *Client) FavouriteStatus(id int) error { + var status Status + o := updateStatusOptions{ID: id} + err := g.updateStatusData("favourite", o, &status) + return err +} + +// UnfavouriteStatus unfavourites a status +func (g *Client) UnfavouriteStatus(id int) error { + var status Status + o := updateStatusOptions{ID: id} + err := g.updateStatusData("unfavourite", o, &status) + return err +}