add source, history for statuses and option to update a status
parent
b597f437a9
commit
51f9d7f999
|
@ -130,9 +130,12 @@ func main() {
|
||||||
* [x] GET /api/v1/statuses/:id
|
* [x] GET /api/v1/statuses/:id
|
||||||
* [x] GET /api/v1/statuses/:id/context
|
* [x] GET /api/v1/statuses/:id/context
|
||||||
* [x] GET /api/v1/statuses/:id/card
|
* [x] GET /api/v1/statuses/:id/card
|
||||||
|
* [x] GET /api/v1/statuses/:id/history
|
||||||
* [x] GET /api/v1/statuses/:id/reblogged_by
|
* [x] GET /api/v1/statuses/:id/reblogged_by
|
||||||
|
* [x] GET /api/v1/statuses/:id/source
|
||||||
* [x] GET /api/v1/statuses/:id/favourited_by
|
* [x] GET /api/v1/statuses/:id/favourited_by
|
||||||
* [x] POST /api/v1/statuses
|
* [x] POST /api/v1/statuses
|
||||||
|
* [x] PUT /api/v1/statuses/:id
|
||||||
* [x] DELETE /api/v1/statuses/:id
|
* [x] DELETE /api/v1/statuses/:id
|
||||||
* [x] POST /api/v1/statuses/:id/reblog
|
* [x] POST /api/v1/statuses/:id/reblog
|
||||||
* [x] POST /api/v1/statuses/:id/unreblog
|
* [x] POST /api/v1/statuses/:id/unreblog
|
||||||
|
|
180
mastodon_test.go
180
mastodon_test.go
|
@ -324,12 +324,190 @@ func TestPostStatusParams(t *testing.T) {
|
||||||
t.Fatalf("want %q but %q", "<p>bar</p>", s.SpoilerText)
|
t.Fatalf("want %q but %q", "<p>bar</p>", s.SpoilerText)
|
||||||
}
|
}
|
||||||
s, err = client.PostStatus(context.Background(), &Toot{
|
s, err = client.PostStatus(context.Background(), &Toot{
|
||||||
|
Status: "foobar",
|
||||||
|
Poll: &TootPoll{
|
||||||
|
Multiple: true,
|
||||||
|
Options: []string{"A", "B"},
|
||||||
|
HideTotals: true,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not be fail: %v", err)
|
||||||
|
}
|
||||||
|
if s.Poll == nil {
|
||||||
|
t.Fatalf("poll should not be %v", s.Poll)
|
||||||
|
}
|
||||||
|
if len(s.Poll.Options) != 2 {
|
||||||
|
t.Fatalf("want %q but %q", 2, len(s.Poll.Options))
|
||||||
|
}
|
||||||
|
if s.Poll.Options[0].Title != "A" {
|
||||||
|
t.Fatalf("want %q but %q", "A", s.Poll.Options[0].Title)
|
||||||
|
}
|
||||||
|
if s.Poll.Options[1].Title != "B" {
|
||||||
|
t.Fatalf("want %q but %q", "B", s.Poll.Options[1].Title)
|
||||||
|
}
|
||||||
|
if s.Poll.Multiple != true {
|
||||||
|
t.Fatalf("want %t but %t", true, s.Poll.Multiple)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateStatus(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.Header.Get("Authorization") != "Bearer zoo" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, `{"access_token": "zoo"}`)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
})
|
||||||
|
_, err := client.UpdateStatus(context.Background(), &Toot{
|
||||||
|
Status: "foobar",
|
||||||
|
}, ID("1"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client = NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
AccessToken: "zoo",
|
||||||
|
})
|
||||||
|
_, err = client.UpdateStatus(context.Background(), &Toot{
|
||||||
|
Status: "foobar",
|
||||||
|
}, ID("1"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not be fail: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdateStatusWithCancel(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
time.Sleep(3 * time.Second)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
})
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
cancel()
|
||||||
|
_, err := client.UpdateStatus(ctx, &Toot{
|
||||||
|
Status: "foobar",
|
||||||
|
}, ID("1"))
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
if want := fmt.Sprintf("Put %q: context canceled", ts.URL+"/api/v1/statuses/1"); want != err.Error() {
|
||||||
|
t.Fatalf("want %q but %q", want, err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestUpdateStatusParams(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/api/v1/statuses/1" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
r.ParseForm()
|
||||||
|
if r.FormValue("media_ids[]") != "" && r.FormValue("poll[options][]") != "" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest)
|
||||||
|
}
|
||||||
|
s := Status{
|
||||||
|
ID: ID("1"),
|
||||||
|
Content: fmt.Sprintf("<p>%s</p>", r.FormValue("status")),
|
||||||
|
}
|
||||||
|
if r.FormValue("in_reply_to_id") != "" {
|
||||||
|
s.InReplyToID = ID(r.FormValue("in_reply_to_id"))
|
||||||
|
}
|
||||||
|
if r.FormValue("visibility") != "" {
|
||||||
|
s.Visibility = (r.FormValue("visibility"))
|
||||||
|
}
|
||||||
|
if r.FormValue("language") != "" {
|
||||||
|
s.Language = (r.FormValue("language"))
|
||||||
|
}
|
||||||
|
if r.FormValue("sensitive") == "true" {
|
||||||
|
s.Sensitive = true
|
||||||
|
s.SpoilerText = fmt.Sprintf("<p>%s</p>", r.FormValue("spoiler_text"))
|
||||||
|
}
|
||||||
|
if r.FormValue("media_ids[]") != "" {
|
||||||
|
for _, id := range r.Form["media_ids[]"] {
|
||||||
|
s.MediaAttachments = append(s.MediaAttachments,
|
||||||
|
Attachment{ID: ID(id)})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if r.FormValue("poll[options][]") != "" {
|
||||||
|
p := Poll{}
|
||||||
|
for _, opt := range r.Form["poll[options][]"] {
|
||||||
|
p.Options = append(p.Options, PollOption{
|
||||||
|
Title: opt,
|
||||||
|
VotesCount: 0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if r.FormValue("poll[multiple]") == "true" {
|
||||||
|
p.Multiple = true
|
||||||
|
}
|
||||||
|
s.Poll = &p
|
||||||
|
}
|
||||||
|
json.NewEncoder(w).Encode(s)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
client := NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
AccessToken: "zoo",
|
||||||
|
})
|
||||||
|
s, err := client.UpdateStatus(context.Background(), &Toot{
|
||||||
|
Status: "foobar",
|
||||||
|
InReplyToID: ID("2"),
|
||||||
|
Visibility: "unlisted",
|
||||||
|
Language: "sv",
|
||||||
|
Sensitive: true,
|
||||||
|
SpoilerText: "bar",
|
||||||
|
MediaIDs: []ID{"1", "2"},
|
||||||
|
Poll: &TootPoll{
|
||||||
|
Options: []string{"A", "B"},
|
||||||
|
},
|
||||||
|
}, ID("1"))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not be fail: %v", err)
|
||||||
|
}
|
||||||
|
if len(s.MediaAttachments) > 0 && s.Poll != nil {
|
||||||
|
t.Fatal("should not fail, can't have both Media and Poll")
|
||||||
|
}
|
||||||
|
if s.Content != "<p>foobar</p>" {
|
||||||
|
t.Fatalf("want %q but %q", "<p>foobar</p>", s.Content)
|
||||||
|
}
|
||||||
|
if s.InReplyToID != "2" {
|
||||||
|
t.Fatalf("want %q but %q", "2", s.InReplyToID)
|
||||||
|
}
|
||||||
|
if s.Visibility != "unlisted" {
|
||||||
|
t.Fatalf("want %q but %q", "unlisted", s.Visibility)
|
||||||
|
}
|
||||||
|
if s.Language != "sv" {
|
||||||
|
t.Fatalf("want %q but %q", "sv", s.Language)
|
||||||
|
}
|
||||||
|
if s.Sensitive != true {
|
||||||
|
t.Fatalf("want %t but %t", true, s.Sensitive)
|
||||||
|
}
|
||||||
|
if s.SpoilerText != "<p>bar</p>" {
|
||||||
|
t.Fatalf("want %q but %q", "<p>bar</p>", s.SpoilerText)
|
||||||
|
}
|
||||||
|
s, err = client.UpdateStatus(context.Background(), &Toot{
|
||||||
Status: "foobar",
|
Status: "foobar",
|
||||||
Poll: &TootPoll{
|
Poll: &TootPoll{
|
||||||
Multiple: true,
|
Multiple: true,
|
||||||
Options: []string{"A", "B"},
|
Options: []string{"A", "B"},
|
||||||
},
|
},
|
||||||
})
|
}, ID("1"))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
|
54
status.go
54
status.go
|
@ -24,6 +24,7 @@ type Status struct {
|
||||||
Reblog *Status `json:"reblog"`
|
Reblog *Status `json:"reblog"`
|
||||||
Content string `json:"content"`
|
Content string `json:"content"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
EditedAt time.Time `json:"edited_at"`
|
||||||
Emojis []Emoji `json:"emojis"`
|
Emojis []Emoji `json:"emojis"`
|
||||||
RepliesCount int64 `json:"replies_count"`
|
RepliesCount int64 `json:"replies_count"`
|
||||||
ReblogsCount int64 `json:"reblogs_count"`
|
ReblogsCount int64 `json:"reblogs_count"`
|
||||||
|
@ -45,6 +46,17 @@ type Status struct {
|
||||||
Pinned interface{} `json:"pinned"`
|
Pinned interface{} `json:"pinned"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// StatusHistory is a struct to hold status history data.
|
||||||
|
type StatusHistory struct {
|
||||||
|
Content string `json:"content"`
|
||||||
|
SpoilerText string `json:"spoiler_text"`
|
||||||
|
Account Account `json:"account"`
|
||||||
|
Sensitive bool `json:"sensitive"`
|
||||||
|
CreatedAt time.Time `json:"created_at"`
|
||||||
|
Emojis []Emoji `json:"emojis"`
|
||||||
|
MediaAttachments []Attachment `json:"media_attachments"`
|
||||||
|
}
|
||||||
|
|
||||||
// Context holds information for a mastodon context.
|
// Context holds information for a mastodon context.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Ancestors []*Status `json:"ancestors"`
|
Ancestors []*Status `json:"ancestors"`
|
||||||
|
@ -67,6 +79,13 @@ type Card struct {
|
||||||
Height int64 `json:"height"`
|
Height int64 `json:"height"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Source holds source properties so a status can be edited.
|
||||||
|
type Source struct {
|
||||||
|
ID ID `json:"id"`
|
||||||
|
Text string `json:"text"`
|
||||||
|
SpoilerText string `json:"spoiler_text"`
|
||||||
|
}
|
||||||
|
|
||||||
// Conversation holds information for a mastodon conversation.
|
// Conversation holds information for a mastodon conversation.
|
||||||
type Conversation struct {
|
type Conversation struct {
|
||||||
ID ID `json:"id"`
|
ID ID `json:"id"`
|
||||||
|
@ -190,6 +209,25 @@ func (c *Client) GetStatusCard(ctx context.Context, id ID) (*Card, error) {
|
||||||
return &card, nil
|
return &card, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Client) GetStatusSource(ctx context.Context, id ID) (*Source, error) {
|
||||||
|
var source Source
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s/source", id), nil, &source, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &source, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStatusHistory returns the status history specified by id.
|
||||||
|
func (c *Client) GetStatusHistory(ctx context.Context, id ID) ([]*StatusHistory, error) {
|
||||||
|
var statuses []*StatusHistory
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%s/history", id), nil, &statuses, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return statuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetRebloggedBy returns the account list of the user who reblogged the toot of id.
|
// GetRebloggedBy returns the account list of the user who reblogged the toot of id.
|
||||||
func (c *Client) GetRebloggedBy(ctx context.Context, id ID, pg *Pagination) ([]*Account, error) {
|
func (c *Client) GetRebloggedBy(ctx context.Context, id ID, pg *Pagination) ([]*Account, error) {
|
||||||
var accounts []*Account
|
var accounts []*Account
|
||||||
|
@ -339,6 +377,15 @@ func (c *Client) GetTimelineMedia(ctx context.Context, isLocal bool, pg *Paginat
|
||||||
|
|
||||||
// PostStatus post the toot.
|
// PostStatus post the toot.
|
||||||
func (c *Client) PostStatus(ctx context.Context, toot *Toot) (*Status, error) {
|
func (c *Client) PostStatus(ctx context.Context, toot *Toot) (*Status, error) {
|
||||||
|
return c.postStatus(ctx, toot, false, ID("none"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateStatus updates the toot.
|
||||||
|
func (c *Client) UpdateStatus(ctx context.Context, toot *Toot, id ID) (*Status, error) {
|
||||||
|
return c.postStatus(ctx, toot, true, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Client) postStatus(ctx context.Context, toot *Toot, update bool, updateID ID) (*Status, error) {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("status", toot.Status)
|
params.Set("status", toot.Status)
|
||||||
if toot.InReplyToID != "" {
|
if toot.InReplyToID != "" {
|
||||||
|
@ -376,7 +423,12 @@ func (c *Client) PostStatus(ctx context.Context, toot *Toot) (*Status, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
var status Status
|
var status Status
|
||||||
err := c.doAPI(ctx, http.MethodPost, "/api/v1/statuses", params, &status, nil)
|
var err error
|
||||||
|
if !update {
|
||||||
|
err = c.doAPI(ctx, http.MethodPost, "/api/v1/statuses", params, &status, nil)
|
||||||
|
} else {
|
||||||
|
err = c.doAPI(ctx, http.MethodPut, fmt.Sprintf("/api/v1/statuses/%s", updateID), params, &status, nil)
|
||||||
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -172,6 +172,88 @@ func TestGetStatusContext(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetStatusSource(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/api/v1/statuses/1234567/source" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, `{"id":"1234567","text":"Foo","spoiler_text":"Bar"}%`)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
AccessToken: "zoo",
|
||||||
|
})
|
||||||
|
_, err := client.GetStatusSource(context.Background(), "123")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
source, err := client.GetStatusSource(context.Background(), "1234567")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not be fail: %v", err)
|
||||||
|
}
|
||||||
|
if source.ID != ID("1234567") {
|
||||||
|
t.Fatalf("want %q but %q", "1234567", source.ID)
|
||||||
|
}
|
||||||
|
if source.Text != "Foo" {
|
||||||
|
t.Fatalf("want %q but %q", "Foo", source.Text)
|
||||||
|
}
|
||||||
|
if source.SpoilerText != "Bar" {
|
||||||
|
t.Fatalf("want %q but %q", "Bar", source.SpoilerText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetStatusHistory(t *testing.T) {
|
||||||
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != "/api/v1/statuses/1234567/history" {
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
fmt.Fprintln(w, `[{"content": "foo", "emojis":[{"shortcode":"💩", "url":"http://example.com", "static_url": "http://example.com/static"}]}, {"content": "bar", "emojis":[{"shortcode":"💩", "url":"http://example.com", "static_url": "http://example.com/static"}]}]`)
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
AccessToken: "zoo",
|
||||||
|
})
|
||||||
|
_, err := client.GetStatusHistory(context.Background(), "123")
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
statuses, err := client.GetStatusHistory(context.Background(), "1234567")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not be fail: %v", err)
|
||||||
|
}
|
||||||
|
if len(statuses) != 2 {
|
||||||
|
t.Fatalf("want len %q but got %q", "2", len(statuses))
|
||||||
|
}
|
||||||
|
if statuses[0].Content != "foo" {
|
||||||
|
t.Fatalf("want %q but %q", "bar", statuses[0].Content)
|
||||||
|
}
|
||||||
|
if statuses[1].Content != "bar" {
|
||||||
|
t.Fatalf("want %q but %q", "bar", statuses[1].Content)
|
||||||
|
}
|
||||||
|
if len(statuses[0].Emojis) != 1 {
|
||||||
|
t.Fatal("should have emojis")
|
||||||
|
}
|
||||||
|
if statuses[0].Emojis[0].ShortCode != "💩" {
|
||||||
|
t.Fatalf("want %q but %q", "💩", statuses[0].Emojis[0].ShortCode)
|
||||||
|
}
|
||||||
|
if statuses[0].Emojis[0].URL != "http://example.com" {
|
||||||
|
t.Fatalf("want %q but %q", "https://example.com", statuses[0].Emojis[0].URL)
|
||||||
|
}
|
||||||
|
if statuses[0].Emojis[0].StaticURL != "http://example.com/static" {
|
||||||
|
t.Fatalf("want %q but %q", "https://example.com/static", statuses[0].Emojis[0].StaticURL)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestGetRebloggedBy(t *testing.T) {
|
func TestGetRebloggedBy(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api/v1/statuses/1234567/reblogged_by" {
|
if r.URL.Path != "/api/v1/statuses/1234567/reblogged_by" {
|
||||||
|
|
Loading…
Reference in New Issue