From 70efe09ff42f4f69cc72c017e24a94ae6ee37dcb Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 02:23:17 +0900 Subject: [PATCH 01/68] Add GetBlocks --- accounts.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/accounts.go b/accounts.go index 751e3d3..4504758 100644 --- a/accounts.go +++ b/accounts.go @@ -66,6 +66,16 @@ func (c *Client) GetAccountFollowing(id int64) ([]*Account, error) { return accounts, nil } +// GetBlocks return block list. +func (c *Client) GetBlocks() ([]*Account, error) { + var accounts []*Account + err := c.doAPI(http.MethodGet, "/api/v1/blocks", nil, &accounts) + if err != nil { + return nil, err + } + return accounts, nil +} + // Relationship hold information for relation-ship to the account. type Relationship struct { ID int64 `json:"id"` From dde3686355d4e2f1f6e189fc8bba3e7019b9b669 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 02:30:47 +0900 Subject: [PATCH 02/68] Add TestGetBlocks --- accounts_test.go | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/accounts_test.go b/accounts_test.go index 4ee715b..7c2dbe2 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -7,6 +7,38 @@ import ( "testing" ) +func TestGetBlocks(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/blocks" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + bl, err := client.GetBlocks() + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if len(bl) != 2 { + t.Fatalf("result should be two: %d", len(bl)) + } + if bl[0].Username != "foo" { + t.Fatalf("want %q but %q", "foo", bl[0].Username) + } + if bl[1].Username != "bar" { + t.Fatalf("want %q but %q", "bar", bl[0].Username) + } +} + func TestAccountFollow(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/accounts/1234567/follow" { From 2338b8e97e6b25507173cdcb826dad76513c9102 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 02:31:22 +0900 Subject: [PATCH 03/68] Minor fix --- accounts_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/accounts_test.go b/accounts_test.go index 7c2dbe2..3dec6b0 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -58,7 +58,7 @@ func TestAccountFollow(t *testing.T) { }) rel, err := client.AccountFollow(123) if err == nil { - t.Fatalf("should be fail: %v", err) + t.Fatalf("should be fail: %v", err) } rel, err = client.AccountFollow(1234567) if err != nil { From 4eb784dd8159fe5ca1133ceed1ed3490ce8f090c Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 03:28:50 +0900 Subject: [PATCH 04/68] Add GetFavourites --- status.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/status.go b/status.go index b597fe5..56f5fe1 100644 --- a/status.go +++ b/status.go @@ -45,6 +45,16 @@ type Card struct { Image string `json:"image"` } +// GetFavourites return the favorite list of the current user. +func (c *Client) GetFavourites() ([]*Status, error) { + var statuses []*Status + err := c.doAPI(http.MethodGet, "/api/v1/favourites", nil, &statuses) + if err != nil { + return nil, err + } + return statuses, nil +} + // GetStatus return status specified by id. func (c *Client) GetStatus(id string) (*Status, error) { var status Status From af2ce2c3b177e9f7e502cea07ce45f2206f463d4 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 03:40:06 +0900 Subject: [PATCH 05/68] Add TestGetFavourites --- status_test.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 status_test.go diff --git a/status_test.go b/status_test.go new file mode 100644 index 0000000..4add3cd --- /dev/null +++ b/status_test.go @@ -0,0 +1,36 @@ +package mastodon + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +func TestGetFavourites(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, `[{"Content": "foo"}, {"Content": "bar"}]`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + favs, err := client.GetFavourites() + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if len(favs) != 2 { + t.Fatalf("result should be two: %d", len(favs)) + } + if favs[0].Content != "foo" { + t.Fatalf("want %q but %q", "foo", favs[0].Content) + } + if favs[1].Content != "bar" { + t.Fatalf("want %q but %q", "bar", favs[1].Content) + } +} From 280982f0342a735f91ebe30c38d5223389a865b6 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 04:04:34 +0900 Subject: [PATCH 06/68] Remove uri to GetFollowRequests --- accounts.go | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/accounts.go b/accounts.go index 4504758..3de8000 100644 --- a/accounts.go +++ b/accounts.go @@ -187,12 +187,9 @@ func (c *Client) FollowRemoteUser(uri string) (*Account, error) { } // GetFollowRequests return follow-requests. -func (c *Client) GetFollowRequests(uri string) ([]*Account, error) { - params := url.Values{} - params.Set("uri", uri) - +func (c *Client) GetFollowRequests() ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, "/api/v1/follow_requests", params, &accounts) + err := c.doAPI(http.MethodGet, "/api/v1/follow_requests", nil, &accounts) if err != nil { return nil, err } From 920eb1ad5ad7c2de24b203ccb1d223c5c90cf860 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 04:08:43 +0900 Subject: [PATCH 07/68] Add TestGetFollowRequests --- accounts_test.go | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/accounts_test.go b/accounts_test.go index 3dec6b0..f68b31a 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -104,3 +104,31 @@ func TestAccountUnfollow(t *testing.T) { t.Fatalf("want %t but %t", false, rel.Following) } } + +func TestGetFollowRequests(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + fReqs, err := client.GetFollowRequests() + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if len(fReqs) != 2 { + t.Fatalf("result should be two: %d", len(fReqs)) + } + if fReqs[0].Username != "foo" { + t.Fatalf("want %q but %q", "foo", fReqs[0].Username) + } + if fReqs[1].Username != "bar" { + t.Fatalf("want %q but %q", "bar", fReqs[0].Username) + } +} From d61ce1c2415dd23bb77814b4e50273d823179d53 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 04:09:33 +0900 Subject: [PATCH 08/68] Fix test server to TestGetBlocks --- accounts_test.go | 4 ---- 1 file changed, 4 deletions(-) diff --git a/accounts_test.go b/accounts_test.go index f68b31a..0dc8a82 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -9,10 +9,6 @@ import ( func TestGetBlocks(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/api/v1/blocks" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return - } fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) return })) From 07ec40e5627816f380ffd68bbb5f3fd15c88c6dc Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 04:15:25 +0900 Subject: [PATCH 09/68] Fix travis badge to svg --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index b959ce3..dc1a855 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # go-mastodon -[![Build Status](https://travis-ci.org/mattn/go-mastodon.png?branch=master)](https://travis-ci.org/mattn/go-mastodon) +[![Build Status](https://travis-ci.org/mattn/go-mastodon.svg?branch=master)](https://travis-ci.org/mattn/go-mastodon) [![Coverage Status](https://coveralls.io/repos/github/mattn/go-mastodon/badge.svg?branch=master)](https://coveralls.io/github/mattn/go-mastodon?branch=master) [![GoDoc](https://godoc.org/github.com/mattn/go-mastodon?status.svg)](http://godoc.org/github.com/mattn/go-mastodon) [![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-mastodon)](https://goreportcard.com/report/github.com/mattn/go-mastodon) From cfb55e7b461cc8dd58d7f64651e496fb6dfa7914 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 04:23:36 +0900 Subject: [PATCH 10/68] Fix method string to const --- instance.go | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/instance.go b/instance.go index 60e82dc..20be51f 100644 --- a/instance.go +++ b/instance.go @@ -1,5 +1,7 @@ package mastodon +import "net/http" + // Instance hold information for mastodon instance. type Instance struct { URI string `json:"uri"` @@ -11,7 +13,7 @@ type Instance struct { // GetInstance return Instance. func (c *Client) GetInstance() (*Instance, error) { var instance Instance - err := c.doAPI("GET", "/api/v1/instance", nil, &instance) + err := c.doAPI(http.MethodGet, "/api/v1/instance", nil, &instance) if err != nil { return nil, err } From c63f290928f223271283b92c6fc564f325e2313c Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sat, 15 Apr 2017 04:36:27 +0900 Subject: [PATCH 11/68] Fix var name url to u --- apps.go | 6 +++--- mastodon.go | 12 ++++++------ streaming.go | 6 +++--- 3 files changed, 12 insertions(+), 12 deletions(-) diff --git a/apps.go b/apps.go index b5a66d2..4f2e4a4 100644 --- a/apps.go +++ b/apps.go @@ -45,13 +45,13 @@ func RegisterApp(appConfig *AppConfig) (*Application, error) { params.Set("scopes", appConfig.Scopes) params.Set("website", appConfig.Website) - url, err := url.Parse(appConfig.Server) + u, err := url.Parse(appConfig.Server) if err != nil { return nil, err } - url.Path = path.Join(url.Path, "/api/v1/apps") + u.Path = path.Join(u.Path, "/api/v1/apps") - req, err := http.NewRequest(http.MethodPost, url.String(), strings.NewReader(params.Encode())) + req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode())) if err != nil { return nil, err } diff --git a/mastodon.go b/mastodon.go index b49da71..2919e66 100644 --- a/mastodon.go +++ b/mastodon.go @@ -24,14 +24,14 @@ type Client struct { } func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error { - url, err := url.Parse(c.config.Server) + u, err := url.Parse(c.config.Server) if err != nil { return err } - url.Path = path.Join(url.Path, uri) + u.Path = path.Join(u.Path, uri) var resp *http.Response - req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode())) + req, err := http.NewRequest(method, u.String(), strings.NewReader(params.Encode())) if err != nil { return err } @@ -70,13 +70,13 @@ func (c *Client) Authenticate(username, password string) error { params.Set("password", password) params.Set("scope", "read write follow") - url, err := url.Parse(c.config.Server) + u, err := url.Parse(c.config.Server) if err != nil { return err } - url.Path = path.Join(url.Path, "/oauth/token") + u.Path = path.Join(u.Path, "/oauth/token") - req, err := http.NewRequest(http.MethodPost, url.String(), strings.NewReader(params.Encode())) + req, err := http.NewRequest(http.MethodPost, u.String(), strings.NewReader(params.Encode())) if err != nil { return err } diff --git a/streaming.go b/streaming.go index 6ccf82d..68a2c9e 100644 --- a/streaming.go +++ b/streaming.go @@ -69,11 +69,11 @@ func handleReader(ctx context.Context, q chan Event, r io.Reader) error { // StreamingPublic return channel to read events. func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { - url, err := url.Parse(c.config.Server) + u, err := url.Parse(c.config.Server) if err != nil { return nil, err } - url.Path = path.Join(url.Path, "/api/v1/streaming/public") + u.Path = path.Join(u.Path, "/api/v1/streaming/public") var resp *http.Response @@ -82,7 +82,7 @@ func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { defer ctx.Done() for { - req, err := http.NewRequest(http.MethodGet, url.String(), nil) + req, err := http.NewRequest(http.MethodGet, u.String(), nil) if err == nil { req.Header.Set("Authorization", "Bearer "+c.config.AccessToken) resp, err = c.Do(req) From 825a0db60c817b347b960867bfe3811e39d93a2d Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 16:28:18 +0900 Subject: [PATCH 12/68] update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index dc1a855..84e419f 100644 --- a/README.md +++ b/README.md @@ -38,9 +38,9 @@ if err != nil { * [x] GET /api/v1/accounts/relationships * [x] GET /api/v1/accounts/search * [x] POST /api/v1/apps -* [ ] GET /api/v1/blocks -* [ ] GET /api/v1/favourites -* [ ] GET /api/v1/follow_requests +* [x] GET /api/v1/blocks +* [x] GET /api/v1/favourites +* [x] GET /api/v1/follow_requests * [ ] POST /api/v1/follow_requests/authorize * [ ] POST /api/v1/follow_requests/reject * [x] POST /api/v1/follows @@ -65,7 +65,7 @@ if err != nil { * [ ] POST /api/v1/statuses/:id/favourite * [ ] POST /api/v1/statuses/:id/unfavourite * [x] GET /api/v1/timelines/home -* [ ] GET /api/v1/timelines/public +* [x] GET /api/v1/timelines/public * [ ] GET /api/v1/timelines/tag/:hashtag ## Installation From bc9dfd57068f58825d0dc9508b65fcd7977621d4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 20:20:22 +0900 Subject: [PATCH 13/68] fix error handling --- streaming.go | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/streaming.go b/streaming.go index 68a2c9e..d965b34 100644 --- a/streaming.go +++ b/streaming.go @@ -4,6 +4,7 @@ import ( "bufio" "context" "encoding/json" + "fmt" "io" "net/http" "net/url" @@ -86,16 +87,19 @@ func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { if err == nil { req.Header.Set("Authorization", "Bearer "+c.config.AccessToken) resp, err = c.Do(req) + if resp.StatusCode != 200 { + err = fmt.Errorf("bad request: %v", resp.Status) + } } if err == nil { err = handleReader(ctx, q, resp.Body) - resp.Body.Close() if err == nil { break } } else { q <- &ErrorEvent{err} } + resp.Body.Close() time.Sleep(3 * time.Second) } }() From fdcc64223482ffc07f32205e5324f93b724b43ad Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 20:20:30 +0900 Subject: [PATCH 14/68] update ClientID/ClientSecret --- cmd/mstdn/main.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index ab0cd4f..d4d5cbc 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -121,8 +121,8 @@ func getConfig() (string, *mastodon.Config, error) { } config := &mastodon.Config{ Server: "https://mstdn.jp", - ClientID: "7d1873f3940af3e9128c81d5a2ddb3f235ccfa1cd11761efd3b8426f40898fe8", - ClientSecret: "3c8ea997c580f196453e97c1c58f6f5c131f668456bbe1ed37aaccac719397db", + ClientID: "171d45f22068a5dddbd927b9d966f5b97971ed1d3256b03d489f5b3a83cdba59", + ClientSecret: "574a2cf4b3f28a5fa0cfd285fc80cfe9daa419945163ef18f5f3d0022f4add28", } if err == nil { err = json.Unmarshal(b, &config) From 564554c1c98a315e38117bf6fb4951df76b6d45a Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 21:02:55 +0900 Subject: [PATCH 15/68] add GetTimelineHashtag --- README.md | 2 +- status.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 84e419f..21dca9a 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,7 @@ if err != nil { * [ ] POST /api/v1/statuses/:id/unfavourite * [x] GET /api/v1/timelines/home * [x] GET /api/v1/timelines/public -* [ ] GET /api/v1/timelines/tag/:hashtag +* [x] GET /api/v1/timelines/tag/:hashtag ## Installation diff --git a/status.go b/status.go index 56f5fe1..a4235c6 100644 --- a/status.go +++ b/status.go @@ -95,6 +95,16 @@ func (c *Client) GetTimelineHome() ([]*Status, error) { return statuses, nil } +// GetTimelineHashtag return statuses from tagged timeline. +func (c *Client) GetTimelineHashtag(tag string) ([]*Status, error) { + var statuses []*Status + err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/timelines/tag/%s", (&url.URL{Path: tag}).EscapedPath()), nil, &statuses) + if err != nil { + return nil, err + } + return statuses, nil +} + // PostStatus post the toot. func (c *Client) PostStatus(toot *Toot) (*Status, error) { params := url.Values{} From 7ced22d42e0fe79db914215f984b711f54de6026 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 21:03:02 +0900 Subject: [PATCH 16/68] add StreamingHome, StreamingHashtag --- streaming.go | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/streaming.go b/streaming.go index d965b34..ded8aad 100644 --- a/streaming.go +++ b/streaming.go @@ -68,14 +68,15 @@ func handleReader(ctx context.Context, q chan Event, r io.Reader) error { return ctx.Err() } -// StreamingPublic return channel to read events. -func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { +func (c *Client) streaming(ctx context.Context, p string, tag string) (chan Event, error) { u, err := url.Parse(c.config.Server) if err != nil { return nil, err } - u.Path = path.Join(u.Path, "/api/v1/streaming/public") + u.Path = path.Join(u.Path, "/api/v1/streaming/"+p) + params := url.Values{} + params.Set("tag", tag) var resp *http.Response q := make(chan Event, 10) @@ -83,7 +84,11 @@ func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { defer ctx.Done() for { - req, err := http.NewRequest(http.MethodGet, u.String(), nil) + var in io.Reader + if tag != "" { + in = strings.NewReader(params.Encode()) + } + req, err := http.NewRequest(http.MethodGet, u.String(), in) if err == nil { req.Header.Set("Authorization", "Bearer "+c.config.AccessToken) resp, err = c.Do(req) @@ -110,4 +115,20 @@ func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { } }() return q, nil + +} + +// StreamingPublic return channel to read events on public. +func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) { + return c.streaming(ctx, "public", "") +} + +// StreamingHome return channel to read events on home. +func (c *Client) StreamingHome(ctx context.Context) (chan Event, error) { + return c.streaming(ctx, "home", "") +} + +// StreamingHashtag return channel to read events on tagged timeline. +func (c *Client) StreamingHashtag(ctx context.Context, tag string) (chan Event, error) { + return c.streaming(ctx, "hashtag", tag) } From 82f7536210932d6f61e655eeaccba620f27952ba Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 21:07:11 +0900 Subject: [PATCH 17/68] add DeleteToot --- README.md | 2 +- status.go | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 21dca9a..70a2d86 100644 --- a/README.md +++ b/README.md @@ -59,7 +59,7 @@ if err != nil { * [ ] GET /api/v1/statuses/:id/reblogged_by * [ ] GET /api/v1/statuses/:id/favourited_by * [ ] POST /api/v1/statuses -* [ ] DELETE /api/v1/statuses/:id +* [x] DELETE /api/v1/statuses/:id * [ ] POST /api/v1/statuses/:id/reblog * [ ] POST /api/v1/statuses/:id/unreblog * [ ] POST /api/v1/statuses/:id/favourite diff --git a/status.go b/status.go index a4235c6..53cbd14 100644 --- a/status.go +++ b/status.go @@ -122,3 +122,8 @@ func (c *Client) PostStatus(toot *Toot) (*Status, error) { } return &status, nil } + +// DeleteStatus delete the toot. +func (c *Client) DeleteStatus(id int64) error { + return c.doAPI(http.MethodDelete, fmt.Sprintf("/api/v1/statuses/%d", id), nil, nil) +} From 094a888b1d9d30a626c25bebc073ccfabde19221 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 21:48:20 +0900 Subject: [PATCH 18/68] change command-line style --- cmd/mstdn/main.go | 155 ++++++++++++++++++++++++++++------------------ 1 file changed, 96 insertions(+), 59 deletions(-) diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index d4d5cbc..61dc27f 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -5,7 +5,7 @@ import ( "bytes" "context" "encoding/json" - "flag" + "errors" "fmt" "io/ioutil" "log" @@ -18,14 +18,11 @@ import ( "github.com/fatih/color" "github.com/mattn/go-mastodon" "github.com/mattn/go-tty" + "github.com/urfave/cli" "golang.org/x/net/html" ) -var ( - toot = flag.String("t", "", "toot text") - stream = flag.Bool("S", false, "streaming public") - fromfile = flag.String("ff", "", "post utf-8 string from a file(\"-\" means STDIN)") -) +var () func readFile(filename string) ([]byte, error) { if filename == "-" { @@ -37,7 +34,7 @@ func readFile(filename string) ([]byte, error) { func textContent(s string) string { doc, err := html.Parse(strings.NewReader(s)) if err != nil { - log.Fatal(err) + return s } var buf bytes.Buffer @@ -133,32 +130,111 @@ func getConfig() (string, *mastodon.Config, error) { return file, config, nil } -func authenticate(client *mastodon.Client, config *mastodon.Config, file string) { +func authenticate(client *mastodon.Client, config *mastodon.Config, file string) error { email, password, err := prompt() if err != nil { - log.Fatal(err) + return err } err = client.Authenticate(email, password) if err != nil { - log.Fatal(err) + return err } b, err := json.MarshalIndent(config, "", " ") if err != nil { - log.Fatal("failed to store file:", err) + return fmt.Errorf("failed to store file:", err) } err = ioutil.WriteFile(file, b, 0700) if err != nil { - log.Fatal("failed to store file:", err) + return fmt.Errorf("failed to store file:", err) } + return nil } -func streaming(client *mastodon.Client) { +func fatalIf(err error) { + if err == nil { + return + } + fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err) + os.Exit(1) +} + +func run() int { + file, config, err := getConfig() + fatalIf(err) + + client := mastodon.NewClient(config) + if config.AccessToken == "" { + err = authenticate(client, config, file) + fatalIf(err) + } + + app := cli.NewApp() + app.Metadata = map[string]interface{}{ + "client": client, + "config": config, + } + app.Name = "mstdn" + app.Usage = "mastodon client" + app.Version = "0.0.1" + app.Commands = []cli.Command{ + { + Name: "toot", + Usage: "post toot", + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "ff", + Usage: "post utf-8 string from a file(\"-\" means STDIN)", + Value: "", + }, + }, + Action: cmdToot, + }, + { + Name: "stream", + Usage: "stream statuses", + Action: cmdStream, + }, + { + Name: "timeline", + Usage: "show timeline", + Action: cmdTimeline, + }, + } + app.Run(os.Args) + return 0 +} + +func cmdToot(c *cli.Context) error { + if !c.Args().Present() { + return errors.New("arguments required") + } + + var toot string + ff := c.String("ff") + if ff != "" { + text, err := readFile(ff) + if err != nil { + log.Fatal(err) + } + toot = string(text) + } else { + toot = strings.Join(c.Args().Tail(), " ") + } + client := c.App.Metadata["client"].(*mastodon.Client) + _, err := client.PostStatus(&mastodon.Toot{ + Status: toot, + }) + return err +} + +func cmdStream(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) ctx, cancel := context.WithCancel(context.Background()) sc := make(chan os.Signal, 1) signal.Notify(sc, os.Interrupt) q, err := client.StreamingPublic(ctx) if err != nil { - log.Fatal(err) + return err } go func() { <-sc @@ -178,32 +254,14 @@ func streaming(client *mastodon.Client) { color.Set(color.Reset) } } + return nil } -func init() { - flag.Parse() - if *fromfile != "" { - text, err := readFile(*fromfile) - if err != nil { - log.Fatal(err) - } - *toot = string(text) - } -} - -func post(client *mastodon.Client, text string) { - _, err := client.PostStatus(&mastodon.Toot{ - Status: text, - }) - if err != nil { - log.Fatal(err) - } -} - -func timeline(client *mastodon.Client) { +func cmdTimeline(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) timeline, err := client.GetTimelineHome() if err != nil { - log.Fatal(err) + return err } for i := len(timeline) - 1; i >= 0; i-- { t := timeline[i] @@ -212,30 +270,9 @@ func timeline(client *mastodon.Client) { color.Set(color.Reset) fmt.Println(textContent(t.Content)) } + return nil } func main() { - file, config, err := getConfig() - if err != nil { - log.Fatal(err) - } - - client := mastodon.NewClient(config) - - if config.AccessToken == "" { - authenticate(client, config, file) - return - } - - if *toot != "" { - post(client, *toot) - return - } - - if *stream { - streaming(client) - return - } - - timeline(client) + os.Exit(run()) } From 209a15a4d7151b71fb898030f7d4f78ab37efe94 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 21:52:11 +0900 Subject: [PATCH 19/68] separate command --- cmd/mstdn/cmd_stream.go | 42 ++++++++++++++++++++++ cmd/mstdn/cmd_timeline.go | 25 +++++++++++++ cmd/mstdn/cmd_toot.go | 33 +++++++++++++++++ cmd/mstdn/main.go | 76 --------------------------------------- 4 files changed, 100 insertions(+), 76 deletions(-) create mode 100644 cmd/mstdn/cmd_stream.go create mode 100644 cmd/mstdn/cmd_timeline.go create mode 100644 cmd/mstdn/cmd_toot.go diff --git a/cmd/mstdn/cmd_stream.go b/cmd/mstdn/cmd_stream.go new file mode 100644 index 0000000..0779010 --- /dev/null +++ b/cmd/mstdn/cmd_stream.go @@ -0,0 +1,42 @@ +package main + +import ( + "context" + "fmt" + "os" + "os/signal" + + "github.com/fatih/color" + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdStream(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) + ctx, cancel := context.WithCancel(context.Background()) + sc := make(chan os.Signal, 1) + signal.Notify(sc, os.Interrupt) + q, err := client.StreamingPublic(ctx) + if err != nil { + return err + } + go func() { + <-sc + cancel() + close(q) + }() + for e := range q { + switch t := e.(type) { + case *mastodon.UpdateEvent: + color.Set(color.FgHiRed) + fmt.Println(t.Status.Account.Username) + color.Set(color.Reset) + fmt.Println(textContent(t.Status.Content)) + case *mastodon.ErrorEvent: + color.Set(color.FgYellow) + fmt.Println(t.Error()) + color.Set(color.Reset) + } + } + return nil +} diff --git a/cmd/mstdn/cmd_timeline.go b/cmd/mstdn/cmd_timeline.go new file mode 100644 index 0000000..be3a10e --- /dev/null +++ b/cmd/mstdn/cmd_timeline.go @@ -0,0 +1,25 @@ +package main + +import ( + "fmt" + + "github.com/fatih/color" + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdTimeline(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) + timeline, err := client.GetTimelineHome() + if err != nil { + return err + } + for i := len(timeline) - 1; i >= 0; i-- { + t := timeline[i] + color.Set(color.FgHiRed) + fmt.Println(t.Account.Username) + color.Set(color.Reset) + fmt.Println(textContent(t.Content)) + } + return nil +} diff --git a/cmd/mstdn/cmd_toot.go b/cmd/mstdn/cmd_toot.go new file mode 100644 index 0000000..7b7f08a --- /dev/null +++ b/cmd/mstdn/cmd_toot.go @@ -0,0 +1,33 @@ +package main + +import ( + "errors" + "log" + "strings" + + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdToot(c *cli.Context) error { + if !c.Args().Present() { + return errors.New("arguments required") + } + + var toot string + ff := c.String("ff") + if ff != "" { + text, err := readFile(ff) + if err != nil { + log.Fatal(err) + } + toot = string(text) + } else { + toot = strings.Join(c.Args().Tail(), " ") + } + client := c.App.Metadata["client"].(*mastodon.Client) + _, err := client.PostStatus(&mastodon.Toot{ + Status: toot, + }) + return err +} diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index 61dc27f..562a541 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -3,27 +3,20 @@ package main import ( "bufio" "bytes" - "context" "encoding/json" - "errors" "fmt" "io/ioutil" - "log" "os" - "os/signal" "path/filepath" "runtime" "strings" - "github.com/fatih/color" "github.com/mattn/go-mastodon" "github.com/mattn/go-tty" "github.com/urfave/cli" "golang.org/x/net/html" ) -var () - func readFile(filename string) ([]byte, error) { if filename == "-" { return ioutil.ReadAll(os.Stdin) @@ -204,75 +197,6 @@ func run() int { return 0 } -func cmdToot(c *cli.Context) error { - if !c.Args().Present() { - return errors.New("arguments required") - } - - var toot string - ff := c.String("ff") - if ff != "" { - text, err := readFile(ff) - if err != nil { - log.Fatal(err) - } - toot = string(text) - } else { - toot = strings.Join(c.Args().Tail(), " ") - } - client := c.App.Metadata["client"].(*mastodon.Client) - _, err := client.PostStatus(&mastodon.Toot{ - Status: toot, - }) - return err -} - -func cmdStream(c *cli.Context) error { - client := c.App.Metadata["client"].(*mastodon.Client) - ctx, cancel := context.WithCancel(context.Background()) - sc := make(chan os.Signal, 1) - signal.Notify(sc, os.Interrupt) - q, err := client.StreamingPublic(ctx) - if err != nil { - return err - } - go func() { - <-sc - cancel() - close(q) - }() - for e := range q { - switch t := e.(type) { - case *mastodon.UpdateEvent: - color.Set(color.FgHiRed) - fmt.Println(t.Status.Account.Username) - color.Set(color.Reset) - fmt.Println(textContent(t.Status.Content)) - case *mastodon.ErrorEvent: - color.Set(color.FgYellow) - fmt.Println(t.Error()) - color.Set(color.Reset) - } - } - return nil -} - -func cmdTimeline(c *cli.Context) error { - client := c.App.Metadata["client"].(*mastodon.Client) - timeline, err := client.GetTimelineHome() - if err != nil { - return err - } - for i := len(timeline) - 1; i >= 0; i-- { - t := timeline[i] - color.Set(color.FgHiRed) - fmt.Println(t.Account.Username) - color.Set(color.Reset) - fmt.Println(textContent(t.Content)) - } - return nil -} - func main() { os.Exit(run()) } From 84038cfd798baa1e5c4edf76a4c774efdaf4cc09 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 21:52:52 +0900 Subject: [PATCH 20/68] update README.md --- cmd/mstdn/README.md | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-) diff --git a/cmd/mstdn/README.md b/cmd/mstdn/README.md index 753aab2..5e3b917 100644 --- a/cmd/mstdn/README.md +++ b/cmd/mstdn/README.md @@ -5,12 +5,24 @@ command line tool for mstdn.jp ## Usage ``` -Usage of mstdn: - -S streaming public - -ff string - post utf-8 string from a file("-" means STDIN) - -t string - toot text +NAME: + mstdn - mastodon client + +USAGE: + mstdn [global options] command [command options] [arguments...] + +VERSION: + 0.0.1 + +COMMANDS: + toot post toot + stream stream statuses + timeline show timeline + help, h Shows a list of commands or help for one command + +GLOBAL OPTIONS: + --help, -h show help + --version, -v print the version ``` ## Installation From f4bf175883d92c7d0cfb0e32a5e8ed1c9106a1c7 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 22:12:07 +0900 Subject: [PATCH 21/68] add notification command --- cmd/mstdn/cmd_notification.go | 28 ++++++++++++++++++++++++++++ cmd/mstdn/main.go | 5 +++++ 2 files changed, 33 insertions(+) create mode 100644 cmd/mstdn/cmd_notification.go diff --git a/cmd/mstdn/cmd_notification.go b/cmd/mstdn/cmd_notification.go new file mode 100644 index 0000000..55fc1e5 --- /dev/null +++ b/cmd/mstdn/cmd_notification.go @@ -0,0 +1,28 @@ +package main + +import ( + "fmt" + + "github.com/fatih/color" + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdNotification(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) + notifications, err := client.GetNotifications() + if err != nil { + return err + } + for _, n := range notifications { + if n.Status != nil { + color.Set(color.FgHiRed) + fmt.Print(n.Account.Username) + color.Set(color.Reset) + fmt.Println(" " + n.Type) + s := n.Status + fmt.Println(textContent(s.Content)) + } + } + return nil +} diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index 562a541..2c0b178 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -192,6 +192,11 @@ func run() int { Usage: "show timeline", Action: cmdTimeline, }, + { + Name: "notification", + Usage: "show notification", + Action: cmdNotification, + }, } app.Run(os.Args) return 0 From 76d34e4b0405bc3c244baa3d21ee4957f6c11084 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 22:25:20 +0900 Subject: [PATCH 22/68] add instance command --- cmd/mstdn/cmd_instance.go | 21 +++++++++++++++++++++ cmd/mstdn/main.go | 5 +++++ 2 files changed, 26 insertions(+) create mode 100644 cmd/mstdn/cmd_instance.go diff --git a/cmd/mstdn/cmd_instance.go b/cmd/mstdn/cmd_instance.go new file mode 100644 index 0000000..2059099 --- /dev/null +++ b/cmd/mstdn/cmd_instance.go @@ -0,0 +1,21 @@ +package main + +import ( + "fmt" + + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdInstance(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) + instance, err := client.GetInstance() + if err != nil { + return err + } + fmt.Printf("URI : %s\n", instance.URI) + fmt.Printf("Title : %s\n", instance.Title) + fmt.Printf("Description: %s\n", instance.Description) + fmt.Printf("EMail : %s\n", instance.EMail) + return nil +} diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index 2c0b178..0fb7ee1 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -197,6 +197,11 @@ func run() int { Usage: "show notification", Action: cmdNotification, }, + { + Name: "instance", + Usage: "show instance information", + Action: cmdInstance, + }, } app.Run(os.Args) return 0 From 07710230d92d9e32c3e8da7664c72037de44cb16 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 22:58:46 +0900 Subject: [PATCH 23/68] add account command --- cmd/mstdn/cmd_account.go | 29 +++++++++++++++++++++++++++++ cmd/mstdn/main.go | 5 +++++ 2 files changed, 34 insertions(+) create mode 100644 cmd/mstdn/cmd_account.go diff --git a/cmd/mstdn/cmd_account.go b/cmd/mstdn/cmd_account.go new file mode 100644 index 0000000..4ca9354 --- /dev/null +++ b/cmd/mstdn/cmd_account.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdAccount(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) + account, err := client.GetAccountCurrentUser() + if err != nil { + return err + } + fmt.Printf("URI : %v\n", account.Acct) + fmt.Printf("ID : %v\n", account.ID) + fmt.Printf("Username : %v\n", account.Username) + fmt.Printf("Acct : %v\n", account.Acct) + fmt.Printf("DisplayName : %v\n", account.DisplayName) + fmt.Printf("Locked : %v\n", account.Locked) + fmt.Printf("CreatedAt : %v\n", account.CreatedAt.Local()) + fmt.Printf("FollowersCount: %v\n", account.FollowersCount) + fmt.Printf("FollowingCount: %v\n", account.FollowingCount) + fmt.Printf("StatusesCount : %v\n", account.StatusesCount) + fmt.Printf("Note : %v\n", account.Note) + fmt.Printf("URL : %v\n", account.URL) + return nil +} diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index 0fb7ee1..b50977b 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -202,6 +202,11 @@ func run() int { Usage: "show instance information", Action: cmdInstance, }, + { + Name: "account", + Usage: "show account information", + Action: cmdAccount, + }, } app.Run(os.Args) return 0 From 6e7ed9d9d918acbb8f4221b17f80c36a5c0bb61b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 23:21:16 +0900 Subject: [PATCH 24/68] add Search --- status.go | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/status.go b/status.go index 53cbd14..e9d817d 100644 --- a/status.go +++ b/status.go @@ -127,3 +127,16 @@ func (c *Client) PostStatus(toot *Toot) (*Status, error) { func (c *Client) DeleteStatus(id int64) error { return c.doAPI(http.MethodDelete, fmt.Sprintf("/api/v1/statuses/%d", id), nil, nil) } + +// Search search content with query. +func (c *Client) Search(q string, resolve bool) (*Results, error) { + params := url.Values{} + params.Set("q", q) + params.Set("resolve", fmt.Sprint(resolve)) + var results Results + err := c.doAPI(http.MethodGet, "/api/v1/search", params, &results) + if err != nil { + return nil, err + } + return &results, nil +} From 05c270286ae3f763a26783774337aabb39c00836 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 23:21:37 +0900 Subject: [PATCH 25/68] add argstr --- mastodon.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/mastodon.go b/mastodon.go index 2919e66..a8660ab 100644 --- a/mastodon.go +++ b/mastodon.go @@ -135,3 +135,9 @@ type Attachment struct { PreviewURL string `json:"preview_url"` TextURL string `json:"text_url"` } + +type Results struct { + Accounts []*Account `json:"accounts"` + Statuses []*Status `json:"statuses"` + Hashtags []string `json:"hashtags"` +} From eb5f3fd239e685f21f8799d13bea4958a347b013 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sat, 15 Apr 2017 23:21:45 +0900 Subject: [PATCH 26/68] add search command --- cmd/mstdn/cmd_toot.go | 3 +-- cmd/mstdn/main.go | 13 +++++++++++++ 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/cmd/mstdn/cmd_toot.go b/cmd/mstdn/cmd_toot.go index 7b7f08a..ff3db38 100644 --- a/cmd/mstdn/cmd_toot.go +++ b/cmd/mstdn/cmd_toot.go @@ -3,7 +3,6 @@ package main import ( "errors" "log" - "strings" "github.com/mattn/go-mastodon" "github.com/urfave/cli" @@ -23,7 +22,7 @@ func cmdToot(c *cli.Context) error { } toot = string(text) } else { - toot = strings.Join(c.Args().Tail(), " ") + toot = argstr(c) } client := c.App.Metadata["client"].(*mastodon.Client) _, err := client.PostStatus(&mastodon.Toot{ diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index b50977b..f920d82 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -143,6 +143,14 @@ func authenticate(client *mastodon.Client, config *mastodon.Config, file string) return nil } +func argstr(c *cli.Context) string { + a := []string{} + for i := 0; i < c.NArg(); i++ { + a = append(a, c.Args().Get(i)) + } + return strings.Join(a, " ") +} + func fatalIf(err error) { if err == nil { return @@ -207,6 +215,11 @@ func run() int { Usage: "show account information", Action: cmdAccount, }, + { + Name: "search", + Usage: "search content", + Action: cmdSearch, + }, } app.Run(os.Args) return 0 From a25056d2ccc3b7252ce26252bec7c3b51bbeb95c Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 00:47:23 +0900 Subject: [PATCH 27/68] Add Content-Type header to doAPI --- mastodon.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/mastodon.go b/mastodon.go index 2919e66..06f460b 100644 --- a/mastodon.go +++ b/mastodon.go @@ -36,6 +36,9 @@ func (c *Client) doAPI(method string, uri string, params url.Values, res interfa return err } req.Header.Set("Authorization", "Bearer "+c.config.AccessToken) + if params != nil { + req.Header.Set("Content-Type", "application/x-www-form-urlencoded") + } resp, err = c.Do(req) if err != nil { return err From 77ce92929093b31109737a31c894a4ca8fffb532 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 00:47:44 +0900 Subject: [PATCH 28/68] Add AccountUpdate --- accounts.go | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/accounts.go b/accounts.go index 3de8000..fa94a1f 100644 --- a/accounts.go +++ b/accounts.go @@ -46,6 +46,42 @@ func (c *Client) GetAccountCurrentUser() (*Account, error) { return &account, nil } +// Profile is a struct for updating profiles. +type Profile struct { + // If it is nil it will not be updated. + // If it is empty, update it with empty. + DisplayName *string + Note *string + + // Set the base64 encoded character string of the image. + Avatar string + Header string +} + +// AccountUpdate updates the information of the current user. +func (c *Client) AccountUpdate(profile *Profile) (*Account, error) { + params := url.Values{} + if profile.DisplayName != nil { + params.Set("display_name", *profile.DisplayName) + } + if profile.Note != nil { + params.Set("note", *profile.Note) + } + if profile.Avatar != "" { + params.Set("avatar", profile.Avatar) + } + if profile.Header != "" { + params.Set("header", profile.Header) + } + + var account Account + err := c.doAPI(http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account) + if err != nil { + return nil, err + } + return &account, nil +} + // GetAccountFollowers return followers list. func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) { var accounts []*Account From 62318331a3e6f6aaf4c007bafd85610536858ac6 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 01:01:15 +0900 Subject: [PATCH 29/68] Add TestAccountUpdate --- accounts_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/accounts_test.go b/accounts_test.go index 0dc8a82..a369c3b 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -7,6 +7,35 @@ import ( "testing" ) +func TestAccountUpdate(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + fmt.Fprintln(w, `{"Username": "zzz"}`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + a, err := client.AccountUpdate(&Profile{ + DisplayName: String("display_name"), + Note: String("note"), + Avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...", + Header: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...", + }) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if a.Username != "zzz" { + t.Fatalf("want %q but %q", "zzz", a.Username) + } +} + +func String(v string) *string { return &v } + func TestGetBlocks(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) From e62f1adaae0719e54571dbb72d70357575a30547 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 02:00:30 +0900 Subject: [PATCH 30/68] Add helper --- accounts_test.go | 2 -- helper.go | 36 ++++++++++++++++++++++++++++++++++++ helper_test.go | 13 +++++++++++++ 3 files changed, 49 insertions(+), 2 deletions(-) create mode 100644 helper.go create mode 100644 helper_test.go diff --git a/accounts_test.go b/accounts_test.go index a369c3b..e7bc167 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -34,8 +34,6 @@ func TestAccountUpdate(t *testing.T) { } } -func String(v string) *string { return &v } - func TestGetBlocks(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) diff --git a/helper.go b/helper.go new file mode 100644 index 0000000..b606695 --- /dev/null +++ b/helper.go @@ -0,0 +1,36 @@ +package mastodon + +import ( + "encoding/base64" + "net/http" + "os" +) + +func Base64EncodeFileName(filename string) (string, error) { + file, err := os.Open(filename) + if err != nil { + return "", err + } + defer file.Close() + + return Base64Encode(file) +} + +func Base64Encode(file *os.File) (string, error) { + fi, err := file.Stat() + if err != nil { + return "", err + } + + d := make([]byte, fi.Size()) + _, err = file.Read(d) + if err != nil { + return "", err + } + + return "data:" + http.DetectContentType(d) + + ";base64," + base64.StdEncoding.EncodeToString(d), nil +} + +// String is a helper function to get the pointer value of a string. +func String(v string) *string { return &v } diff --git a/helper_test.go b/helper_test.go new file mode 100644 index 0000000..a4a6429 --- /dev/null +++ b/helper_test.go @@ -0,0 +1,13 @@ +package mastodon + +import ( + "testing" +) + +func TestString(t *testing.T) { + s := "test" + sp := String(s) + if *sp != s { + t.Fatalf("want %q but %q", s, *sp) + } +} From 727e6d8c543307c823af702d16b38fc91fc77cd1 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 02:24:46 +0900 Subject: [PATCH 31/68] Add godoc to Base64EncodeFileName and Base64Encode --- helper.go | 2 ++ 1 file changed, 2 insertions(+) diff --git a/helper.go b/helper.go index b606695..c9fefe9 100644 --- a/helper.go +++ b/helper.go @@ -6,6 +6,7 @@ import ( "os" ) +// Base64EncodeFileName returns the base64 data URI format string of the file with the file name. func Base64EncodeFileName(filename string) (string, error) { file, err := os.Open(filename) if err != nil { @@ -16,6 +17,7 @@ func Base64EncodeFileName(filename string) (string, error) { return Base64Encode(file) } +// Base64Encode returns the base64 data URI format string of the file. func Base64Encode(file *os.File) (string, error) { fi, err := file.Stat() if err != nil { From b92f648ec3ccdffbc3bd16c6dd4a6d9fcbf3f86d Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 02:55:32 +0900 Subject: [PATCH 32/68] add missing file --- cmd/mstdn/cmd_search.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 cmd/mstdn/cmd_search.go diff --git a/cmd/mstdn/cmd_search.go b/cmd/mstdn/cmd_search.go new file mode 100644 index 0000000..edb9428 --- /dev/null +++ b/cmd/mstdn/cmd_search.go @@ -0,0 +1,31 @@ +package main + +import ( + "errors" + "fmt" + + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdSearch(c *cli.Context) error { + if !c.Args().Present() { + return errors.New("arguments required") + } + + client := c.App.Metadata["client"].(*mastodon.Client) + results, err := client.Search(argstr(c), false) + if err != nil { + return err + } + for _, result := range results.Accounts { + fmt.Println(result) + } + for _, result := range results.Statuses { + fmt.Println(result) + } + for _, result := range results.Hashtags { + fmt.Println(result) + } + return nil +} From 2f4edf000c51f96fb0d2c7d2ec666776e26bbc06 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 03:04:33 +0900 Subject: [PATCH 33/68] Add update_credentials to status of implementations --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 70a2d86..ab158a4 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ if err != nil { * [x] GET /api/v1/accounts/:id * [x] GET /api/v1/accounts/verify_credentials +* [ ] PATCH /api/v1/accounts/update_credentials * [x] GET /api/v1/accounts/:id/followers * [x] GET /api/v1/accounts/:id/following * [ ] GET /api/v1/accounts/:id/statuses From effa489b8a1bf052bc897458d3e7e2528353076c Mon Sep 17 00:00:00 2001 From: Kaoru HAYAMA Date: Sun, 16 Apr 2017 10:41:21 +0900 Subject: [PATCH 34/68] Fix `mstdn.exe toot -ff -` errors `arguments required` --- cmd/mstdn/cmd_toot.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cmd/mstdn/cmd_toot.go b/cmd/mstdn/cmd_toot.go index ff3db38..747c35b 100644 --- a/cmd/mstdn/cmd_toot.go +++ b/cmd/mstdn/cmd_toot.go @@ -9,10 +9,6 @@ import ( ) func cmdToot(c *cli.Context) error { - if !c.Args().Present() { - return errors.New("arguments required") - } - var toot string ff := c.String("ff") if ff != "" { @@ -22,6 +18,9 @@ func cmdToot(c *cli.Context) error { } toot = string(text) } else { + if !c.Args().Present() { + return errors.New("arguments required") + } toot = argstr(c) } client := c.App.Metadata["client"].(*mastodon.Client) From 3cb20b5925e2f690190085f479d28d3ab2d169ff Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 13:11:56 +0900 Subject: [PATCH 35/68] Add GetRebloggedBy --- status.go | 10 ++++++++++ status_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/status.go b/status.go index e9d817d..155a764 100644 --- a/status.go +++ b/status.go @@ -85,6 +85,16 @@ func (c *Client) GetStatusCard(id string) (*Card, error) { return &card, nil } +// GetRebloggedBy returns the account of the user who re-blogged. +func (c *Client) GetRebloggedBy(id int64) ([]*Account, error) { + var accounts []*Account + err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/reblogged_by", id), nil, &accounts) + if err != nil { + return nil, err + } + return accounts, nil +} + // GetTimelineHome return statuses from home timeline. func (c *Client) GetTimelineHome() ([]*Status, error) { var statuses []*Status diff --git a/status_test.go b/status_test.go index 4add3cd..9c42e35 100644 --- a/status_test.go +++ b/status_test.go @@ -34,3 +34,39 @@ func TestGetFavourites(t *testing.T) { t.Fatalf("want %q but %q", "bar", favs[1].Content) } } + +func TestGetRebloggedBy(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567/reblogged_by" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.GetRebloggedBy(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + rbs, err := client.GetRebloggedBy(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if len(rbs) != 2 { + t.Fatalf("result should be two: %d", len(rbs)) + } + if rbs[0].Username != "foo" { + t.Fatalf("want %q but %q", "foo", rbs[0].Username) + } + if rbs[1].Username != "bar" { + t.Fatalf("want %q but %q", "bar", rbs[0].Username) + } +} From 74901c994cc6a68f786e92524c64b84d7483e8a3 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 13:45:59 +0900 Subject: [PATCH 36/68] Add GetFavouritedBy --- status.go | 12 +++++++++++- status_test.go | 36 ++++++++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/status.go b/status.go index 155a764..1cf9fd3 100644 --- a/status.go +++ b/status.go @@ -85,7 +85,7 @@ func (c *Client) GetStatusCard(id string) (*Card, error) { return &card, nil } -// GetRebloggedBy returns the account of the user who re-blogged. +// GetRebloggedBy returns the account list of the user who reblogged the toot of id. func (c *Client) GetRebloggedBy(id int64) ([]*Account, error) { var accounts []*Account err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/reblogged_by", id), nil, &accounts) @@ -95,6 +95,16 @@ func (c *Client) GetRebloggedBy(id int64) ([]*Account, error) { return accounts, nil } +// GetFavouritedBy returns the account list of the user who liked the toot of id. +func (c *Client) GetFavouritedBy(id int64) ([]*Account, error) { + var accounts []*Account + err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/favourited_by", id), nil, &accounts) + if err != nil { + return nil, err + } + return accounts, nil +} + // GetTimelineHome return statuses from home timeline. func (c *Client) GetTimelineHome() ([]*Status, error) { var statuses []*Status diff --git a/status_test.go b/status_test.go index 9c42e35..0e44bbc 100644 --- a/status_test.go +++ b/status_test.go @@ -70,3 +70,39 @@ func TestGetRebloggedBy(t *testing.T) { t.Fatalf("want %q but %q", "bar", rbs[0].Username) } } + +func TestGetFavouritedBy(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567/favourited_by" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.GetFavouritedBy(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + fbs, err := client.GetFavouritedBy(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if len(fbs) != 2 { + t.Fatalf("result should be two: %d", len(fbs)) + } + if fbs[0].Username != "foo" { + t.Fatalf("want %q but %q", "foo", fbs[0].Username) + } + if fbs[1].Username != "bar" { + t.Fatalf("want %q but %q", "bar", fbs[0].Username) + } +} From 6891f221bcf1570fac62f7d6ff4736888228ef82 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 14:53:15 +0900 Subject: [PATCH 37/68] Add Reblog and Unreblog --- status.go | 20 +++++++++++++++++ status_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/status.go b/status.go index 1cf9fd3..67884c6 100644 --- a/status.go +++ b/status.go @@ -105,6 +105,26 @@ func (c *Client) GetFavouritedBy(id int64) ([]*Account, error) { return accounts, nil } +// Reblog is reblog the toot of id. +func (c *Client) Reblog(id int64) (*Status, error) { + var status Status + err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/reblog", id), nil, &status) + if err != nil { + return nil, err + } + return &status, nil +} + +// Unreblog is unreblog the toot of id. +func (c *Client) Unreblog(id int64) (*Status, error) { + var status Status + err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unreblog", id), nil, &status) + if err != nil { + return nil, err + } + return &status, nil +} + // GetTimelineHome return statuses from home timeline. func (c *Client) GetTimelineHome() ([]*Status, error) { var statuses []*Status diff --git a/status_test.go b/status_test.go index 0e44bbc..3c0b8a2 100644 --- a/status_test.go +++ b/status_test.go @@ -106,3 +106,63 @@ func TestGetFavouritedBy(t *testing.T) { t.Fatalf("want %q but %q", "bar", fbs[0].Username) } } + +func TestReblog(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567/reblog" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `{"Content": "zzz"}`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.Reblog(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + status, err := client.Reblog(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if status.Content != "zzz" { + t.Fatalf("want %q but %q", "zzz", status.Content) + } +} + +func TestUnreblog(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567/unreblog" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `{"Content": "zzz"}`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.Unreblog(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + status, err := client.Unreblog(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if status.Content != "zzz" { + t.Fatalf("want %q but %q", "zzz", status.Content) + } +} From 7719f511aa7da98cd214493137c4a7fcf1fe509b Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 15:04:59 +0900 Subject: [PATCH 38/68] Fix godoc to Reblog and Unreblog --- status.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/status.go b/status.go index 67884c6..2b180b2 100644 --- a/status.go +++ b/status.go @@ -105,7 +105,7 @@ func (c *Client) GetFavouritedBy(id int64) ([]*Account, error) { return accounts, nil } -// Reblog is reblog the toot of id. +// Reblog is reblog the toot of id and return status of reblog. func (c *Client) Reblog(id int64) (*Status, error) { var status Status err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/reblog", id), nil, &status) @@ -115,7 +115,7 @@ func (c *Client) Reblog(id int64) (*Status, error) { return &status, nil } -// Unreblog is unreblog the toot of id. +// Unreblog is unreblog the toot of id and return status of the original toot. func (c *Client) Unreblog(id int64) (*Status, error) { var status Status err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unreblog", id), nil, &status) From bbb4df76cabed842ae899ed142a127b65c6379b3 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 15:32:48 +0900 Subject: [PATCH 39/68] Add Favourite and Unfavourite --- status.go | 20 +++++++++++++++++ status_test.go | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+) diff --git a/status.go b/status.go index 2b180b2..115232a 100644 --- a/status.go +++ b/status.go @@ -125,6 +125,26 @@ func (c *Client) Unreblog(id int64) (*Status, error) { return &status, nil } +// Favourite is favourite the toot of id and return status of the favourite toot. +func (c *Client) Favourite(id int64) (*Status, error) { + var status Status + err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/favourite", id), nil, &status) + if err != nil { + return nil, err + } + return &status, nil +} + +// Unfavourite is unfavourite the toot of id and return status of the unfavourite toot. +func (c *Client) Unfavourite(id int64) (*Status, error) { + var status Status + err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unfavourite", id), nil, &status) + if err != nil { + return nil, err + } + return &status, nil +} + // GetTimelineHome return statuses from home timeline. func (c *Client) GetTimelineHome() ([]*Status, error) { var statuses []*Status diff --git a/status_test.go b/status_test.go index 3c0b8a2..c88bcaf 100644 --- a/status_test.go +++ b/status_test.go @@ -166,3 +166,63 @@ func TestUnreblog(t *testing.T) { t.Fatalf("want %q but %q", "zzz", status.Content) } } + +func TestFavourite(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567/favourite" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `{"Content": "zzz"}`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.Favourite(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + status, err := client.Favourite(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if status.Content != "zzz" { + t.Fatalf("want %q but %q", "zzz", status.Content) + } +} + +func TestUnfavourite(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567/unfavourite" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `{"Content": "zzz"}`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.Unfavourite(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + status, err := client.Unfavourite(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if status.Content != "zzz" { + t.Fatalf("want %q but %q", "zzz", status.Content) + } +} From 39fd91e59f4ca7efd171dc6e879c3638ec2089fe Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 17:11:23 +0900 Subject: [PATCH 40/68] Fix status id type from string to int64 --- status.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/status.go b/status.go index 115232a..4536aae 100644 --- a/status.go +++ b/status.go @@ -56,7 +56,7 @@ func (c *Client) GetFavourites() ([]*Status, error) { } // GetStatus return status specified by id. -func (c *Client) GetStatus(id string) (*Status, error) { +func (c *Client) GetStatus(id int64) (*Status, error) { var status Status err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d", id), nil, &status) if err != nil { @@ -66,7 +66,7 @@ func (c *Client) GetStatus(id string) (*Status, error) { } // GetStatusContext return status specified by id. -func (c *Client) GetStatusContext(id string) (*Context, error) { +func (c *Client) GetStatusContext(id int64) (*Context, error) { var context Context err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/context", id), nil, &context) if err != nil { @@ -76,7 +76,7 @@ func (c *Client) GetStatusContext(id string) (*Context, error) { } // GetStatusCard return status specified by id. -func (c *Client) GetStatusCard(id string) (*Card, error) { +func (c *Client) GetStatusCard(id int64) (*Card, error) { var card Card err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/card", id), nil, &card) if err != nil { From 1495b1fb025cb2f9160b750ee43998fc6754abb9 Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Sun, 16 Apr 2017 17:11:47 +0900 Subject: [PATCH 41/68] Add TestGetStatus --- status_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/status_test.go b/status_test.go index c88bcaf..93d03e2 100644 --- a/status_test.go +++ b/status_test.go @@ -35,6 +35,36 @@ func TestGetFavourites(t *testing.T) { } } +func TestGetStatus(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses/1234567" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `{"Content": "zzz"}`) + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + _, err := client.GetStatus(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + status, err := client.GetStatus(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if status.Content != "zzz" { + t.Fatalf("want %q but %q", "zzz", status.Content) + } +} + func TestGetRebloggedBy(t *testing.T) { ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.URL.Path != "/api/v1/statuses/1234567/reblogged_by" { From 9f84e175f15c714d59a109e6332ae3d92580b793 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 20:05:42 +0900 Subject: [PATCH 42/68] separate test file --- mastodon_test.go | 54 ----------------------------------------- streaming_test.go | 62 +++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 62 insertions(+), 54 deletions(-) create mode 100644 streaming_test.go diff --git a/mastodon_test.go b/mastodon_test.go index bc020f2..c5bbf58 100644 --- a/mastodon_test.go +++ b/mastodon_test.go @@ -1,13 +1,11 @@ package mastodon import ( - "context" "fmt" "io" "net/http" "net/http/httptest" "testing" - "time" ) func TestAuthenticate(t *testing.T) { @@ -226,55 +224,3 @@ func TestRegisterApp(t *testing.T) { t.Fatalf("want %q but %q", "bar", app.ClientSecret) } } - -func TestStreamingPublic(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/api/v1/streaming/public" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return - } - f, _ := w.(http.Flusher) - fmt.Fprintln(w, ` -event: update -data: {"Content": "foo"} - `) - f.Flush() - - fmt.Fprintln(w, ` -event: update -data: {"Content": "bar"} - `) - f.Flush() - return - })) - defer ts.Close() - - client := NewClient(&Config{ - Server: ts.URL, - ClientID: "foo", - ClientSecret: "bar", - AccessToken: "zoo", - }) - ctx, cancel := context.WithCancel(context.Background()) - q, err := client.StreamingPublic(ctx) - if err != nil { - t.Fatalf("should not be fail: %v", err) - } - time.AfterFunc(3*time.Second, func() { - cancel() - close(q) - }) - events := []Event{} - for e := range q { - events = append(events, e) - } - if len(events) != 2 { - t.Fatalf("result should be two: %d", len(events)) - } - if events[0].(*UpdateEvent).Status.Content != "foo" { - t.Fatalf("want %q but %q", "foo", events[0].(*UpdateEvent).Status.Content) - } - if events[1].(*UpdateEvent).Status.Content != "bar" { - t.Fatalf("want %q but %q", "bar", events[1].(*UpdateEvent).Status.Content) - } -} diff --git a/streaming_test.go b/streaming_test.go new file mode 100644 index 0000000..7227c12 --- /dev/null +++ b/streaming_test.go @@ -0,0 +1,62 @@ +package mastodon + +import ( + "context" + "fmt" + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestStreamingPublic(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/streaming/public" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + f, _ := w.(http.Flusher) + fmt.Fprintln(w, ` +event: update +data: {"Content": "foo"} + `) + f.Flush() + + fmt.Fprintln(w, ` +event: update +data: {"Content": "bar"} + `) + f.Flush() + return + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + ctx, cancel := context.WithCancel(context.Background()) + q, err := client.StreamingPublic(ctx) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + time.AfterFunc(3*time.Second, func() { + cancel() + close(q) + }) + events := []Event{} + for e := range q { + events = append(events, e) + } + if len(events) != 2 { + t.Fatalf("result should be two: %d", len(events)) + } + if events[0].(*UpdateEvent).Status.Content != "foo" { + t.Fatalf("want %q but %q", "foo", events[0].(*UpdateEvent).Status.Content) + } + if events[1].(*UpdateEvent).Status.Content != "bar" { + t.Fatalf("want %q but %q", "bar", events[1].(*UpdateEvent).Status.Content) + } +} From 3b4b2ce7949770e3355235059f6f882361e3638b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 20:06:54 +0900 Subject: [PATCH 43/68] separate test files --- apps_test.go | 42 ++++++++++++++++++++++++++++++++++++++++++ mastodon_test.go | 34 ---------------------------------- 2 files changed, 42 insertions(+), 34 deletions(-) create mode 100644 apps_test.go diff --git a/apps_test.go b/apps_test.go new file mode 100644 index 0000000..64be57b --- /dev/null +++ b/apps_test.go @@ -0,0 +1,42 @@ +package mastodon + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" +) + +func TestRegisterApp(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + if r.URL.Path != "/api/v1/apps" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + if r.FormValue("redirect_uris") != "urn:ietf:wg:oauth:2.0:oob" { + http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) + return + } + fmt.Fprintln(w, `{"client_id": "foo", "client_secret": "bar"}`) + return + })) + defer ts.Close() + + app, err := RegisterApp(&AppConfig{ + Server: ts.URL, + Scopes: "read write follow", + }) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } + if app.ClientID != "foo" { + t.Fatalf("want %q but %q", "foo", app.ClientID) + } + if app.ClientSecret != "bar" { + t.Fatalf("want %q but %q", "bar", app.ClientSecret) + } +} diff --git a/mastodon_test.go b/mastodon_test.go index c5bbf58..1fbcf64 100644 --- a/mastodon_test.go +++ b/mastodon_test.go @@ -190,37 +190,3 @@ func TestGetAccountFollowing(t *testing.T) { t.Fatalf("want %q but %q", "bar", fl[0].Username) } } - -func TestRegisterApp(t *testing.T) { - ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - if r.Method != http.MethodPost { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - if r.URL.Path != "/api/v1/apps" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) - return - } - if r.FormValue("redirect_uris") != "urn:ietf:wg:oauth:2.0:oob" { - http.Error(w, http.StatusText(http.StatusBadRequest), http.StatusBadRequest) - return - } - fmt.Fprintln(w, `{"client_id": "foo", "client_secret": "bar"}`) - return - })) - defer ts.Close() - - app, err := RegisterApp(&AppConfig{ - Server: ts.URL, - Scopes: "read write follow", - }) - if err != nil { - t.Fatalf("should not be fail: %v", err) - } - if app.ClientID != "foo" { - t.Fatalf("want %q but %q", "foo", app.ClientID) - } - if app.ClientSecret != "bar" { - t.Fatalf("want %q but %q", "bar", app.ClientSecret) - } -} From 6651ec33635830d764945adbb669facfbf6969bb Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 21:01:46 +0900 Subject: [PATCH 44/68] update README.md --- README.md | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index ab158a4..d31b2a5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ if err != nil { * [x] GET /api/v1/accounts/:id * [x] GET /api/v1/accounts/verify_credentials -* [ ] PATCH /api/v1/accounts/update_credentials +* [x] PATCH /api/v1/accounts/update_credentials * [x] GET /api/v1/accounts/:id/followers * [x] GET /api/v1/accounts/:id/following * [ ] GET /api/v1/accounts/:id/statuses @@ -57,14 +57,14 @@ if err != nil { * [x] GET /api/v1/statuses/:id * [x] GET /api/v1/statuses/:id/context * [x] GET /api/v1/statuses/:id/card -* [ ] GET /api/v1/statuses/:id/reblogged_by -* [ ] GET /api/v1/statuses/:id/favourited_by -* [ ] POST /api/v1/statuses +* [x] GET /api/v1/statuses/:id/reblogged_by +* [x] GET /api/v1/statuses/:id/favourited_by +* [x] POST /api/v1/statuses * [x] DELETE /api/v1/statuses/:id -* [ ] POST /api/v1/statuses/:id/reblog -* [ ] POST /api/v1/statuses/:id/unreblog -* [ ] POST /api/v1/statuses/:id/favourite -* [ ] POST /api/v1/statuses/:id/unfavourite +* [x] POST /api/v1/statuses/:id/reblog +* [x] POST /api/v1/statuses/:id/unreblog +* [x] POST /api/v1/statuses/:id/favourite +* [x] POST /api/v1/statuses/:id/unfavourite * [x] GET /api/v1/timelines/home * [x] GET /api/v1/timelines/public * [x] GET /api/v1/timelines/tag/:hashtag From 8a1347fad6f30a97eea7e2ed59947eea6ca05d0b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 21:42:54 +0900 Subject: [PATCH 45/68] add GetAccountStatuses --- README.md | 2 +- accounts.go | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index d31b2a5..a28adca 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ if err != nil { * [x] PATCH /api/v1/accounts/update_credentials * [x] GET /api/v1/accounts/:id/followers * [x] GET /api/v1/accounts/:id/following -* [ ] GET /api/v1/accounts/:id/statuses +* [x] GET /api/v1/accounts/:id/statuses * [x] POST /api/v1/accounts/:id/follow * [x] POST /api/v1/accounts/:id/unfollow * [x] GET /api/v1/accounts/:id/block diff --git a/accounts.go b/accounts.go index fa94a1f..0dd7fa3 100644 --- a/accounts.go +++ b/accounts.go @@ -82,6 +82,16 @@ func (c *Client) AccountUpdate(profile *Profile) (*Account, error) { return &account, nil } +// GetAccountStatuses return statuses by specified accuont. +func (c *Client) GetAccountStatuses(id int64) ([]*Status, error) { + var statuses []*Status + err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/statuses", id), nil, &statuses) + if err != nil { + return nil, err + } + return statuses, nil +} + // GetAccountFollowers return followers list. func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) { var accounts []*Account From 70798b28390003c705c3b6ddc09749e12491bfe7 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 21:47:15 +0900 Subject: [PATCH 46/68] update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a28adca..78aee62 100644 --- a/README.md +++ b/README.md @@ -53,7 +53,7 @@ if err != nil { * [x] POST /api/v1/notifications/clear * [ ] GET /api/v1/reports * [ ] POST /api/v1/reports -* [ ] GET /api/v1/search +* [x] GET /api/v1/search * [x] GET /api/v1/statuses/:id * [x] GET /api/v1/statuses/:id/context * [x] GET /api/v1/statuses/:id/card From 04baed6e1a4e9aa35b16c4a08250adc2d5dbdf9b Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:26:05 +0900 Subject: [PATCH 47/68] small refactoring for tests --- cmd/mstdn/main.go | 39 +++++++++++++++++++++++++-------------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index f920d82..a8a6349 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -159,21 +159,8 @@ func fatalIf(err error) { os.Exit(1) } -func run() int { - file, config, err := getConfig() - fatalIf(err) - - client := mastodon.NewClient(config) - if config.AccessToken == "" { - err = authenticate(client, config, file) - fatalIf(err) - } - +func makeApp() *cli.App { app := cli.NewApp() - app.Metadata = map[string]interface{}{ - "client": client, - "config": config, - } app.Name = "mstdn" app.Usage = "mastodon client" app.Version = "0.0.1" @@ -220,7 +207,31 @@ func run() int { Usage: "search content", Action: cmdSearch, }, + { + Name: "followers", + Usage: "show followers", + Action: cmdFollowers, + }, } + return app +} + +func run() int { + app := makeApp() + + file, config, err := getConfig() + fatalIf(err) + + client := mastodon.NewClient(config) + if config.AccessToken == "" { + err = authenticate(client, config, file) + fatalIf(err) + } + app.Metadata = map[string]interface{}{ + "client": client, + "config": config, + } + app.Run(os.Args) return 0 } From 594708b656fe61d5e54b381f3e37e4bf67513d45 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:27:13 +0900 Subject: [PATCH 48/68] write to app writer --- cmd/mstdn/cmd_instance.go | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/cmd/mstdn/cmd_instance.go b/cmd/mstdn/cmd_instance.go index 2059099..752eef4 100644 --- a/cmd/mstdn/cmd_instance.go +++ b/cmd/mstdn/cmd_instance.go @@ -13,9 +13,9 @@ func cmdInstance(c *cli.Context) error { if err != nil { return err } - fmt.Printf("URI : %s\n", instance.URI) - fmt.Printf("Title : %s\n", instance.Title) - fmt.Printf("Description: %s\n", instance.Description) - fmt.Printf("EMail : %s\n", instance.EMail) + fmt.Fprintf(c.App.Writer, "URI : %s\n", instance.URI) + fmt.Fprintf(c.App.Writer, "Title : %s\n", instance.Title) + fmt.Fprintf(c.App.Writer, "Description: %s\n", instance.Description) + fmt.Fprintf(c.App.Writer, "EMail : %s\n", instance.EMail) return nil } From 11e25014556f821dd91808e7662540584998a8d5 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:27:43 +0900 Subject: [PATCH 49/68] test server --- cmd/mstdn/cmd_test.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 cmd/mstdn/cmd_test.go diff --git a/cmd/mstdn/cmd_test.go b/cmd/mstdn/cmd_test.go new file mode 100644 index 0000000..1283922 --- /dev/null +++ b/cmd/mstdn/cmd_test.go @@ -0,0 +1,31 @@ +package main + +import ( + "bytes" + "net/http" + "net/http/httptest" + + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func testWithServer(h http.HandlerFunc, testFunc func(*cli.App)) string { + ts := httptest.NewServer(h) + defer ts.Close() + + client := mastodon.NewClient(&mastodon.Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + + var buf bytes.Buffer + app := makeApp() + app.Writer = &buf + app.Metadata = map[string]interface{}{ + "client": client, + } + testFunc(app) + return buf.String() +} From dfbfecd32641e0c38814c09223e5494dc3ea4bea Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:27:54 +0900 Subject: [PATCH 50/68] add followers command --- cmd/mstdn/cmd_followers.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 cmd/mstdn/cmd_followers.go diff --git a/cmd/mstdn/cmd_followers.go b/cmd/mstdn/cmd_followers.go new file mode 100644 index 0000000..b1ed1ac --- /dev/null +++ b/cmd/mstdn/cmd_followers.go @@ -0,0 +1,24 @@ +package main + +import ( + "fmt" + + "github.com/mattn/go-mastodon" + "github.com/urfave/cli" +) + +func cmdFollowers(c *cli.Context) error { + client := c.App.Metadata["client"].(*mastodon.Client) + account, err := client.GetAccountCurrentUser() + if err != nil { + return err + } + followers, err := client.GetAccountFollowers(account.ID) + if err != nil { + return err + } + for _, follower := range followers { + fmt.Printf("%v,%v\n", follower.ID, follower.Username) + } + return nil +} From cb7f029e7c3ac3895d971c4f0798ed6dba992180 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:28:07 +0900 Subject: [PATCH 51/68] add test for instance command --- cmd/mstdn/cmd_instance_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 cmd/mstdn/cmd_instance_test.go diff --git a/cmd/mstdn/cmd_instance_test.go b/cmd/mstdn/cmd_instance_test.go new file mode 100644 index 0000000..ee9674f --- /dev/null +++ b/cmd/mstdn/cmd_instance_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdInstance(t *testing.T) { + out := testWithServer( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/instance" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `{"Title": "zzz"}`) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "instance"}) + }, + ) + if !strings.Contains(out, "zzz") { + t.Fatalf("%q should be contained in output of instance command: %v", "zzz", out) + } +} From 97552fbe30cdd4549d73ae31c976e524160f5ce5 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:37:46 +0900 Subject: [PATCH 52/68] add test for toot --- cmd/mstdn/cmd_toot_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 cmd/mstdn/cmd_toot_test.go diff --git a/cmd/mstdn/cmd_toot_test.go b/cmd/mstdn/cmd_toot_test.go new file mode 100644 index 0000000..2f2f0d3 --- /dev/null +++ b/cmd/mstdn/cmd_toot_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "net/http" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdToot(t *testing.T) { + toot := "" + testWithServer( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/statuses" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + toot = r.FormValue("status") + fmt.Fprintln(w, `{"ID": 2345}`) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "toot", "foo"}) + }, + ) + if toot != "foo" { + t.Fatalf("want %q, got %q", "foo", toot) + } +} From ea84240cceddd3ea245b4edc9ef4d5131a70caaf Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:50:00 +0900 Subject: [PATCH 53/68] fix test --- cmd/mstdn/cmd_instance_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cmd/mstdn/cmd_instance_test.go b/cmd/mstdn/cmd_instance_test.go index ee9674f..2a48f95 100644 --- a/cmd/mstdn/cmd_instance_test.go +++ b/cmd/mstdn/cmd_instance_test.go @@ -16,7 +16,7 @@ func TestCmdInstance(t *testing.T) { http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return } - fmt.Fprintln(w, `{"Title": "zzz"}`) + fmt.Fprintln(w, `{"title": "zzz"}`) return }, func(app *cli.App) { From 86066b95619f366e9630208b4b6ba52af2263813 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 22:50:09 +0900 Subject: [PATCH 54/68] add test for notification command --- cmd/mstdn/cmd_notification.go | 6 +++--- cmd/mstdn/cmd_notification_test.go | 29 +++++++++++++++++++++++++++++ 2 files changed, 32 insertions(+), 3 deletions(-) create mode 100644 cmd/mstdn/cmd_notification_test.go diff --git a/cmd/mstdn/cmd_notification.go b/cmd/mstdn/cmd_notification.go index 55fc1e5..3b6e5a7 100644 --- a/cmd/mstdn/cmd_notification.go +++ b/cmd/mstdn/cmd_notification.go @@ -17,11 +17,11 @@ func cmdNotification(c *cli.Context) error { for _, n := range notifications { if n.Status != nil { color.Set(color.FgHiRed) - fmt.Print(n.Account.Username) + fmt.Fprint(c.App.Writer, n.Account.Username) color.Set(color.Reset) - fmt.Println(" " + n.Type) + fmt.Fprintln(c.App.Writer, " "+n.Type) s := n.Status - fmt.Println(textContent(s.Content)) + fmt.Fprintln(c.App.Writer, textContent(s.Content)) } } return nil diff --git a/cmd/mstdn/cmd_notification_test.go b/cmd/mstdn/cmd_notification_test.go new file mode 100644 index 0000000..76d8d57 --- /dev/null +++ b/cmd/mstdn/cmd_notification_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdNotification(t *testing.T) { + out := testWithServer( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/notifications" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `[{"type": "rebloged", "status": {"content": "foo"}}]`) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "notification"}) + }, + ) + if !strings.Contains(out, "rebloged") { + t.Fatalf("%q should be contained in output of instance command: %v", "rebloged", out) + } +} From e1b7bac4e6bece2464ed383a4d331d2bd2e7a3de Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 23:04:31 +0900 Subject: [PATCH 55/68] use writer --- cmd/mstdn/cmd_account.go | 24 ++++++++++++------------ cmd/mstdn/cmd_followers.go | 2 +- cmd/mstdn/cmd_search.go | 6 +++--- cmd/mstdn/cmd_stream.go | 6 +++--- cmd/mstdn/cmd_timeline.go | 4 ++-- 5 files changed, 21 insertions(+), 21 deletions(-) diff --git a/cmd/mstdn/cmd_account.go b/cmd/mstdn/cmd_account.go index 4ca9354..e7caa2e 100644 --- a/cmd/mstdn/cmd_account.go +++ b/cmd/mstdn/cmd_account.go @@ -13,17 +13,17 @@ func cmdAccount(c *cli.Context) error { if err != nil { return err } - fmt.Printf("URI : %v\n", account.Acct) - fmt.Printf("ID : %v\n", account.ID) - fmt.Printf("Username : %v\n", account.Username) - fmt.Printf("Acct : %v\n", account.Acct) - fmt.Printf("DisplayName : %v\n", account.DisplayName) - fmt.Printf("Locked : %v\n", account.Locked) - fmt.Printf("CreatedAt : %v\n", account.CreatedAt.Local()) - fmt.Printf("FollowersCount: %v\n", account.FollowersCount) - fmt.Printf("FollowingCount: %v\n", account.FollowingCount) - fmt.Printf("StatusesCount : %v\n", account.StatusesCount) - fmt.Printf("Note : %v\n", account.Note) - fmt.Printf("URL : %v\n", account.URL) + fmt.Fprintf(c.App.Writer, "URI : %v\n", account.Acct) + fmt.Fprintf(c.App.Writer, "ID : %v\n", account.ID) + fmt.Fprintf(c.App.Writer, "Username : %v\n", account.Username) + fmt.Fprintf(c.App.Writer, "Acct : %v\n", account.Acct) + fmt.Fprintf(c.App.Writer, "DisplayName : %v\n", account.DisplayName) + fmt.Fprintf(c.App.Writer, "Locked : %v\n", account.Locked) + fmt.Fprintf(c.App.Writer, "CreatedAt : %v\n", account.CreatedAt.Local()) + fmt.Fprintf(c.App.Writer, "FollowersCount: %v\n", account.FollowersCount) + fmt.Fprintf(c.App.Writer, "FollowingCount: %v\n", account.FollowingCount) + fmt.Fprintf(c.App.Writer, "StatusesCount : %v\n", account.StatusesCount) + fmt.Fprintf(c.App.Writer, "Note : %v\n", account.Note) + fmt.Fprintf(c.App.Writer, "URL : %v\n", account.URL) return nil } diff --git a/cmd/mstdn/cmd_followers.go b/cmd/mstdn/cmd_followers.go index b1ed1ac..aab07ef 100644 --- a/cmd/mstdn/cmd_followers.go +++ b/cmd/mstdn/cmd_followers.go @@ -18,7 +18,7 @@ func cmdFollowers(c *cli.Context) error { return err } for _, follower := range followers { - fmt.Printf("%v,%v\n", follower.ID, follower.Username) + fmt.Fprintf(c.App.Writer, "%v,%v\n", follower.ID, follower.Username) } return nil } diff --git a/cmd/mstdn/cmd_search.go b/cmd/mstdn/cmd_search.go index edb9428..344bbdd 100644 --- a/cmd/mstdn/cmd_search.go +++ b/cmd/mstdn/cmd_search.go @@ -19,13 +19,13 @@ func cmdSearch(c *cli.Context) error { return err } for _, result := range results.Accounts { - fmt.Println(result) + fmt.Fprintln(c.App.Writer, result) } for _, result := range results.Statuses { - fmt.Println(result) + fmt.Fprintln(c.App.Writer, result) } for _, result := range results.Hashtags { - fmt.Println(result) + fmt.Fprintln(c.App.Writer, result) } return nil } diff --git a/cmd/mstdn/cmd_stream.go b/cmd/mstdn/cmd_stream.go index 0779010..6105896 100644 --- a/cmd/mstdn/cmd_stream.go +++ b/cmd/mstdn/cmd_stream.go @@ -29,12 +29,12 @@ func cmdStream(c *cli.Context) error { switch t := e.(type) { case *mastodon.UpdateEvent: color.Set(color.FgHiRed) - fmt.Println(t.Status.Account.Username) + fmt.Fprintln(c.App.Writer, t.Status.Account.Username) color.Set(color.Reset) - fmt.Println(textContent(t.Status.Content)) + fmt.Fprintln(c.App.Writer, textContent(t.Status.Content)) case *mastodon.ErrorEvent: color.Set(color.FgYellow) - fmt.Println(t.Error()) + fmt.Fprintln(c.App.Writer, t.Error()) color.Set(color.Reset) } } diff --git a/cmd/mstdn/cmd_timeline.go b/cmd/mstdn/cmd_timeline.go index be3a10e..6bb2999 100644 --- a/cmd/mstdn/cmd_timeline.go +++ b/cmd/mstdn/cmd_timeline.go @@ -17,9 +17,9 @@ func cmdTimeline(c *cli.Context) error { for i := len(timeline) - 1; i >= 0; i-- { t := timeline[i] color.Set(color.FgHiRed) - fmt.Println(t.Account.Username) + fmt.Fprintln(c.App.Writer, t.Account.Username) color.Set(color.Reset) - fmt.Println(textContent(t.Content)) + fmt.Fprintln(c.App.Writer, textContent(t.Content)) } return nil } From 70b6261e3403b9769f81b939851df0143ea8d3d4 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 23:38:53 +0900 Subject: [PATCH 56/68] golint & go vet --- accounts.go | 2 +- cmd/mstdn/cmd_stream.go | 1 + cmd/mstdn/main.go | 6 +++--- mastodon.go | 1 + notification.go | 2 +- status.go | 4 ++-- 6 files changed, 9 insertions(+), 7 deletions(-) diff --git a/accounts.go b/accounts.go index 0dd7fa3..ee9cec3 100644 --- a/accounts.go +++ b/accounts.go @@ -219,7 +219,7 @@ func (c *Client) AccountsSearch(q string, limit int64) ([]*Account, error) { return accounts, nil } -// Follow send follow-request. +// FollowRemoteUser send follow-request. func (c *Client) FollowRemoteUser(uri string) (*Account, error) { params := url.Values{} params.Set("uri", uri) diff --git a/cmd/mstdn/cmd_stream.go b/cmd/mstdn/cmd_stream.go index 6105896..50aba7d 100644 --- a/cmd/mstdn/cmd_stream.go +++ b/cmd/mstdn/cmd_stream.go @@ -14,6 +14,7 @@ import ( func cmdStream(c *cli.Context) error { client := c.App.Metadata["client"].(*mastodon.Client) ctx, cancel := context.WithCancel(context.Background()) + defer cancel() sc := make(chan os.Signal, 1) signal.Notify(sc, os.Interrupt) q, err := client.StreamingPublic(ctx) diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index a8a6349..1903c34 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -54,7 +54,7 @@ func textContent(s string) string { } var ( - readUsername func() (string, error) = func() (string, error) { + readUsername = func() (string, error) { b, _, err := bufio.NewReader(os.Stdin).ReadLine() if err != nil { return "", err @@ -134,11 +134,11 @@ func authenticate(client *mastodon.Client, config *mastodon.Config, file string) } b, err := json.MarshalIndent(config, "", " ") if err != nil { - return fmt.Errorf("failed to store file:", err) + return fmt.Errorf("failed to store file: %v", err) } err = ioutil.WriteFile(file, b, 0700) if err != nil { - return fmt.Errorf("failed to store file:", err) + return fmt.Errorf("failed to store file: %v", err) } return nil } diff --git a/mastodon.go b/mastodon.go index e3fbb88..3cdf902 100644 --- a/mastodon.go +++ b/mastodon.go @@ -139,6 +139,7 @@ type Attachment struct { TextURL string `json:"text_url"` } +// Results hold information for search result. type Results struct { Accounts []*Account `json:"accounts"` Statuses []*Status `json:"statuses"` diff --git a/notification.go b/notification.go index 6fa2436..b8762c0 100644 --- a/notification.go +++ b/notification.go @@ -25,7 +25,7 @@ func (c *Client) GetNotifications() ([]*Notification, error) { return notifications, nil } -// GetNotifications return notifications. +// GetNotification return notifications. func (c *Client) GetNotification(id int64) (*Notification, error) { var notification Notification err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/notifications/%d", id), nil, ¬ification) diff --git a/status.go b/status.go index 4536aae..ae20794 100644 --- a/status.go +++ b/status.go @@ -33,8 +33,8 @@ type Status struct { // Context hold information for mastodon context. type Context struct { - Ancestors []*Status `ancestors` - Descendants []*Status `descendants` + Ancestors []*Status `json:"ancestors"` + Descendants []*Status `json:"descendants"` } // Card hold information for mastodon card. From f2ab559a027b1aa74fd8037976892cee0db3de38 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 23:44:07 +0900 Subject: [PATCH 57/68] add test for timeline command --- cmd/mstdn/cmd_timeline_test.go | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 cmd/mstdn/cmd_timeline_test.go diff --git a/cmd/mstdn/cmd_timeline_test.go b/cmd/mstdn/cmd_timeline_test.go new file mode 100644 index 0000000..040e015 --- /dev/null +++ b/cmd/mstdn/cmd_timeline_test.go @@ -0,0 +1,29 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdTimeline(t *testing.T) { + out := testWithServer( + func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/timelines/home" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + } + fmt.Fprintln(w, `[{"content": "zzz"}]`) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "timeline"}) + }, + ) + if !strings.Contains(out, "zzz") { + t.Fatalf("%q should be contained in output of instance command: %v", "zzz", out) + } +} From 88c2fecca21ee8686134e72b0b1258b02f4ddc7a Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 23:49:04 +0900 Subject: [PATCH 58/68] use switch/case --- cmd/mstdn/cmd_instance_test.go | 7 ++++--- cmd/mstdn/cmd_notification_test.go | 7 ++++--- cmd/mstdn/cmd_timeline_test.go | 7 ++++--- cmd/mstdn/cmd_toot_test.go | 9 +++++---- 4 files changed, 17 insertions(+), 13 deletions(-) diff --git a/cmd/mstdn/cmd_instance_test.go b/cmd/mstdn/cmd_instance_test.go index 2a48f95..ed04e07 100644 --- a/cmd/mstdn/cmd_instance_test.go +++ b/cmd/mstdn/cmd_instance_test.go @@ -12,11 +12,12 @@ import ( func TestCmdInstance(t *testing.T) { out := testWithServer( func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/api/v1/instance" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + switch r.URL.Path { + case "/api/v1/instance": + fmt.Fprintln(w, `{"title": "zzz"}`) return } - fmt.Fprintln(w, `{"title": "zzz"}`) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return }, func(app *cli.App) { diff --git a/cmd/mstdn/cmd_notification_test.go b/cmd/mstdn/cmd_notification_test.go index 76d8d57..3921ff3 100644 --- a/cmd/mstdn/cmd_notification_test.go +++ b/cmd/mstdn/cmd_notification_test.go @@ -12,11 +12,12 @@ import ( func TestCmdNotification(t *testing.T) { out := testWithServer( func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/api/v1/notifications" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + switch r.URL.Path { + case "/api/v1/notifications": + fmt.Fprintln(w, `[{"type": "rebloged", "status": {"content": "foo"}}]`) return } - fmt.Fprintln(w, `[{"type": "rebloged", "status": {"content": "foo"}}]`) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return }, func(app *cli.App) { diff --git a/cmd/mstdn/cmd_timeline_test.go b/cmd/mstdn/cmd_timeline_test.go index 040e015..105d136 100644 --- a/cmd/mstdn/cmd_timeline_test.go +++ b/cmd/mstdn/cmd_timeline_test.go @@ -12,11 +12,12 @@ import ( func TestCmdTimeline(t *testing.T) { out := testWithServer( func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/api/v1/timelines/home" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + switch r.URL.Path { + case "/api/v1/timelines/home": + fmt.Fprintln(w, `[{"content": "zzz"}]`) return } - fmt.Fprintln(w, `[{"content": "zzz"}]`) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return }, func(app *cli.App) { diff --git a/cmd/mstdn/cmd_toot_test.go b/cmd/mstdn/cmd_toot_test.go index 2f2f0d3..8821536 100644 --- a/cmd/mstdn/cmd_toot_test.go +++ b/cmd/mstdn/cmd_toot_test.go @@ -12,12 +12,13 @@ func TestCmdToot(t *testing.T) { toot := "" testWithServer( func(w http.ResponseWriter, r *http.Request) { - if r.URL.Path != "/api/v1/statuses" { - http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + switch r.URL.Path { + case "/api/v1/statuses": + toot = r.FormValue("status") + fmt.Fprintln(w, `{"ID": 2345}`) return } - toot = r.FormValue("status") - fmt.Fprintln(w, `{"ID": 2345}`) + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) return }, func(app *cli.App) { From db7fcb38bbed5a50877c0f1396115c4c7ac832de Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Sun, 16 Apr 2017 23:52:32 +0900 Subject: [PATCH 59/68] add test for followers command --- cmd/mstdn/cmd_followers_test.go | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 cmd/mstdn/cmd_followers_test.go diff --git a/cmd/mstdn/cmd_followers_test.go b/cmd/mstdn/cmd_followers_test.go new file mode 100644 index 0000000..0a53213 --- /dev/null +++ b/cmd/mstdn/cmd_followers_test.go @@ -0,0 +1,33 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdFollowers(t *testing.T) { + out := testWithServer( + func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/accounts/verify_credentials": + fmt.Fprintln(w, `{"id": 123}`) + return + case "/api/v1/accounts/123/followers": + fmt.Fprintln(w, `[{"id": 234, "username": "zzz"}]`) + return + } + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "followers"}) + }, + ) + if !strings.Contains(out, "zzz") { + t.Fatalf("%q should be contained in output of instance command: %v", "zzz", out) + } +} From 5ee5295be5cc7a82d2193d19e20d3d8c0840042e Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 17 Apr 2017 00:00:12 +0900 Subject: [PATCH 60/68] add test for account command --- cmd/mstdn/cmd_account_test.go | 30 ++++++++++++++++++++++++++++++ cmd/mstdn/cmd_followers_test.go | 2 +- cmd/mstdn/cmd_instance_test.go | 2 +- cmd/mstdn/cmd_notification_test.go | 2 +- cmd/mstdn/cmd_timeline_test.go | 2 +- 5 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 cmd/mstdn/cmd_account_test.go diff --git a/cmd/mstdn/cmd_account_test.go b/cmd/mstdn/cmd_account_test.go new file mode 100644 index 0000000..8894451 --- /dev/null +++ b/cmd/mstdn/cmd_account_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdAccount(t *testing.T) { + out := testWithServer( + func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/accounts/verify_credentials": + fmt.Fprintln(w, `{"username": "zzz"}`) + return + } + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "account"}) + }, + ) + if !strings.Contains(out, "zzz") { + t.Fatalf("%q should be contained in output of command: %v", "zzz", out) + } +} diff --git a/cmd/mstdn/cmd_followers_test.go b/cmd/mstdn/cmd_followers_test.go index 0a53213..5518ccf 100644 --- a/cmd/mstdn/cmd_followers_test.go +++ b/cmd/mstdn/cmd_followers_test.go @@ -28,6 +28,6 @@ func TestCmdFollowers(t *testing.T) { }, ) if !strings.Contains(out, "zzz") { - t.Fatalf("%q should be contained in output of instance command: %v", "zzz", out) + t.Fatalf("%q should be contained in output of command: %v", "zzz", out) } } diff --git a/cmd/mstdn/cmd_instance_test.go b/cmd/mstdn/cmd_instance_test.go index ed04e07..f5a6279 100644 --- a/cmd/mstdn/cmd_instance_test.go +++ b/cmd/mstdn/cmd_instance_test.go @@ -25,6 +25,6 @@ func TestCmdInstance(t *testing.T) { }, ) if !strings.Contains(out, "zzz") { - t.Fatalf("%q should be contained in output of instance command: %v", "zzz", out) + t.Fatalf("%q should be contained in output of command: %v", "zzz", out) } } diff --git a/cmd/mstdn/cmd_notification_test.go b/cmd/mstdn/cmd_notification_test.go index 3921ff3..25a9429 100644 --- a/cmd/mstdn/cmd_notification_test.go +++ b/cmd/mstdn/cmd_notification_test.go @@ -25,6 +25,6 @@ func TestCmdNotification(t *testing.T) { }, ) if !strings.Contains(out, "rebloged") { - t.Fatalf("%q should be contained in output of instance command: %v", "rebloged", out) + t.Fatalf("%q should be contained in output of command: %v", "rebloged", out) } } diff --git a/cmd/mstdn/cmd_timeline_test.go b/cmd/mstdn/cmd_timeline_test.go index 105d136..b0801a8 100644 --- a/cmd/mstdn/cmd_timeline_test.go +++ b/cmd/mstdn/cmd_timeline_test.go @@ -25,6 +25,6 @@ func TestCmdTimeline(t *testing.T) { }, ) if !strings.Contains(out, "zzz") { - t.Fatalf("%q should be contained in output of instance command: %v", "zzz", out) + t.Fatalf("%q should be contained in output of command: %v", "zzz", out) } } From 104663d1e6216b34c49f845a1111ac00dd167e3b Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Mon, 17 Apr 2017 00:03:05 +0900 Subject: [PATCH 61/68] Fix doAPI error handling --- mastodon.go | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mastodon.go b/mastodon.go index e3fbb88..b3d59de 100644 --- a/mastodon.go +++ b/mastodon.go @@ -44,12 +44,11 @@ func (c *Client) doAPI(method string, uri string, params url.Values, res interfa return err } defer resp.Body.Close() - if res == nil { - return nil - } - if method == http.MethodGet && resp.StatusCode != http.StatusOK { + if resp.StatusCode != http.StatusOK { return fmt.Errorf("bad request: %v", resp.Status) + } else if res == nil { + return nil } return json.NewDecoder(resp.Body).Decode(&res) From f5194b9ebb24366078b46301b53c00d0a73ef20e Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Mon, 17 Apr 2017 00:04:25 +0900 Subject: [PATCH 62/68] Add FollowRequestAuthorize and FollowRequestReject --- accounts.go | 10 ++++++++++ accounts_test.go | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+) diff --git a/accounts.go b/accounts.go index 0dd7fa3..c7967a0 100644 --- a/accounts.go +++ b/accounts.go @@ -241,3 +241,13 @@ func (c *Client) GetFollowRequests() ([]*Account, error) { } return accounts, nil } + +// FollowRequestAuthorize is authorize the follow request of user with id. +func (c *Client) FollowRequestAuthorize(id int64) error { + return c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/authorize", id), nil, nil) +} + +// FollowRequestReject is rejects the follow request of user with id. +func (c *Client) FollowRequestReject(id int64) error { + return c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/reject", id), nil, nil) +} diff --git a/accounts_test.go b/accounts_test.go index e7bc167..3bc55b2 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -155,3 +155,51 @@ func TestGetFollowRequests(t *testing.T) { t.Fatalf("want %q but %q", "bar", fReqs[0].Username) } } + +func TestFollowRequestAuthorize(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/follow_requests/1234567/authorize" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + } + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + err := client.FollowRequestAuthorize(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + err = client.FollowRequestAuthorize(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } +} + +func TestFollowRequestReject(t *testing.T) { + ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if r.URL.Path != "/api/v1/follow_requests/1234567/reject" { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + } + })) + defer ts.Close() + + client := NewClient(&Config{ + Server: ts.URL, + ClientID: "foo", + ClientSecret: "bar", + AccessToken: "zoo", + }) + err := client.FollowRequestReject(123) + if err == nil { + t.Fatalf("should be fail: %v", err) + } + err = client.FollowRequestReject(1234567) + if err != nil { + t.Fatalf("should not be fail: %v", err) + } +} From c39d5765d43ddee6454163c417a6d4851ae84d5f Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Mon, 17 Apr 2017 00:07:31 +0900 Subject: [PATCH 63/68] Fix cover error to TestGetFollowRequests --- accounts_test.go | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/accounts_test.go b/accounts_test.go index 3bc55b2..b891225 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -129,7 +129,13 @@ func TestAccountUnfollow(t *testing.T) { } func TestGetFollowRequests(t *testing.T) { + canErr := true ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if canErr { + canErr = false + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + return + } fmt.Fprintln(w, `[{"Username": "foo"}, {"Username": "bar"}]`) return })) @@ -141,6 +147,10 @@ func TestGetFollowRequests(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) + _, err := client.GetFollowRequests() + if err == nil { + t.Fatalf("should be fail: %v", err) + } fReqs, err := client.GetFollowRequests() if err != nil { t.Fatalf("should not be fail: %v", err) From 183e1ecadf34ad667e20dfc6229e7b1550ad1d7d Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 17 Apr 2017 00:09:35 +0900 Subject: [PATCH 64/68] add test for search command --- cmd/mstdn/cmd_search_test.go | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 cmd/mstdn/cmd_search_test.go diff --git a/cmd/mstdn/cmd_search_test.go b/cmd/mstdn/cmd_search_test.go new file mode 100644 index 0000000..f498592 --- /dev/null +++ b/cmd/mstdn/cmd_search_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "net/http" + "strings" + "testing" + + "github.com/urfave/cli" +) + +func TestCmdSearch(t *testing.T) { + out := testWithServer( + func(w http.ResponseWriter, r *http.Request) { + switch r.URL.Path { + case "/api/v1/search": + fmt.Fprintln(w, `{"accounts": [{"id": 234, "username": "zzz"}], "contents":[], "hashtags": []}`) + return + } + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + return + }, + func(app *cli.App) { + app.Run([]string{"mstdn", "search", "zzz"}) + }, + ) + if !strings.Contains(out, "zzz") { + t.Fatalf("%q should be contained in output of command: %v", "zzz", out) + } +} From 679457b6ddad4f41bcf4a4f31bc89fbe6d9ae52b Mon Sep 17 00:00:00 2001 From: 178inaba <178inaba@users.noreply.github.com> Date: Mon, 17 Apr 2017 00:12:11 +0900 Subject: [PATCH 65/68] Fix README --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 78aee62..d2e618a 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ if err != nil { * [x] GET /api/v1/blocks * [x] GET /api/v1/favourites * [x] GET /api/v1/follow_requests -* [ ] POST /api/v1/follow_requests/authorize -* [ ] POST /api/v1/follow_requests/reject +* [ ] POST /api/v1/follow_requests/:id/authorize +* [ ] POST /api/v1/follow_requests/:id/reject * [x] POST /api/v1/follows * [x] GET /api/v1/instance * [ ] POST /api/v1/media From 32e66a3d726a65861273a5e6047a99d5cae8f886 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 17 Apr 2017 01:37:29 +0900 Subject: [PATCH 66/68] update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d2e618a..488ee2b 100644 --- a/README.md +++ b/README.md @@ -42,8 +42,8 @@ if err != nil { * [x] GET /api/v1/blocks * [x] GET /api/v1/favourites * [x] GET /api/v1/follow_requests -* [ ] POST /api/v1/follow_requests/:id/authorize -* [ ] POST /api/v1/follow_requests/:id/reject +* [x] POST /api/v1/follow_requests/:id/authorize +* [x] POST /api/v1/follow_requests/:id/reject * [x] POST /api/v1/follows * [x] GET /api/v1/instance * [ ] POST /api/v1/media From 5e84b57bf33e328a8c93f635333d4a90ddc77076 Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 17 Apr 2017 11:10:29 +0900 Subject: [PATCH 67/68] breaking compatibility changes. take context for first arguments. --- accounts.go | 77 ++++++++++++++++++----------------- accounts_test.go | 25 ++++++------ apps.go | 3 +- apps_test.go | 3 +- cmd/mstdn/cmd_account.go | 3 +- cmd/mstdn/cmd_followers.go | 5 ++- cmd/mstdn/cmd_instance.go | 3 +- cmd/mstdn/cmd_notification.go | 3 +- cmd/mstdn/cmd_search.go | 3 +- cmd/mstdn/cmd_timeline.go | 3 +- cmd/mstdn/cmd_toot.go | 3 +- cmd/mstdn/main.go | 3 +- instance.go | 9 ++-- mastodon.go | 46 ++++++++++++++------- mastodon_test.go | 21 +++++----- notification.go | 13 +++--- status.go | 61 +++++++++++++-------------- status_test.go | 31 +++++++------- 18 files changed, 176 insertions(+), 139 deletions(-) diff --git a/accounts.go b/accounts.go index 8238791..f5d933b 100644 --- a/accounts.go +++ b/accounts.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "net/http" "net/url" @@ -27,9 +28,9 @@ type Account struct { } // GetAccount return Account. -func (c *Client) GetAccount(id int) (*Account, error) { +func (c *Client) GetAccount(ctx context.Context, id int) (*Account, error) { var account Account - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d", id), nil, &account) if err != nil { return nil, err } @@ -37,9 +38,9 @@ func (c *Client) GetAccount(id int) (*Account, error) { } // GetAccountCurrentUser return Account of current user. -func (c *Client) GetAccountCurrentUser() (*Account, error) { +func (c *Client) GetAccountCurrentUser(ctx context.Context) (*Account, error) { var account Account - err := c.doAPI(http.MethodGet, "/api/v1/accounts/verify_credentials", nil, &account) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/accounts/verify_credentials", nil, &account) if err != nil { return nil, err } @@ -59,7 +60,7 @@ type Profile struct { } // AccountUpdate updates the information of the current user. -func (c *Client) AccountUpdate(profile *Profile) (*Account, error) { +func (c *Client) AccountUpdate(ctx context.Context, profile *Profile) (*Account, error) { params := url.Values{} if profile.DisplayName != nil { params.Set("display_name", *profile.DisplayName) @@ -75,7 +76,7 @@ func (c *Client) AccountUpdate(profile *Profile) (*Account, error) { } var account Account - err := c.doAPI(http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account) + err := c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account) if err != nil { return nil, err } @@ -83,9 +84,9 @@ func (c *Client) AccountUpdate(profile *Profile) (*Account, error) { } // GetAccountStatuses return statuses by specified accuont. -func (c *Client) GetAccountStatuses(id int64) ([]*Status, error) { +func (c *Client) GetAccountStatuses(ctx context.Context, id int64) ([]*Status, error) { var statuses []*Status - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/statuses", id), nil, &statuses) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/statuses", id), nil, &statuses) if err != nil { return nil, err } @@ -93,9 +94,9 @@ func (c *Client) GetAccountStatuses(id int64) ([]*Status, error) { } // GetAccountFollowers return followers list. -func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) { +func (c *Client) GetAccountFollowers(ctx context.Context, id int64) ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/followers", id), nil, &accounts) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/followers", id), nil, &accounts) if err != nil { return nil, err } @@ -103,9 +104,9 @@ func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) { } // GetAccountFollowing return following list. -func (c *Client) GetAccountFollowing(id int64) ([]*Account, error) { +func (c *Client) GetAccountFollowing(ctx context.Context, id int64) ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/following", id), nil, &accounts) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/accounts/%d/following", id), nil, &accounts) if err != nil { return nil, err } @@ -113,9 +114,9 @@ func (c *Client) GetAccountFollowing(id int64) ([]*Account, error) { } // GetBlocks return block list. -func (c *Client) GetBlocks() ([]*Account, error) { +func (c *Client) GetBlocks(ctx context.Context) ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, "/api/v1/blocks", nil, &accounts) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/blocks", nil, &accounts) if err != nil { return nil, err } @@ -133,9 +134,9 @@ type Relationship struct { } // AccountFollow follow the account. -func (c *Client) AccountFollow(id int64) (*Relationship, error) { +func (c *Client) AccountFollow(ctx context.Context, id int64) (*Relationship, error) { var relationship Relationship - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/follow", id), nil, &relationship) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/follow", id), nil, &relationship) if err != nil { return nil, err } @@ -143,9 +144,9 @@ func (c *Client) AccountFollow(id int64) (*Relationship, error) { } // AccountUnfollow unfollow the account. -func (c *Client) AccountUnfollow(id int64) (*Relationship, error) { +func (c *Client) AccountUnfollow(ctx context.Context, id int64) (*Relationship, error) { var relationship Relationship - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/unfollow", id), nil, &relationship) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/unfollow", id), nil, &relationship) if err != nil { return nil, err } @@ -153,9 +154,9 @@ func (c *Client) AccountUnfollow(id int64) (*Relationship, error) { } // AccountBlock block the account. -func (c *Client) AccountBlock(id int64) (*Relationship, error) { +func (c *Client) AccountBlock(ctx context.Context, id int64) (*Relationship, error) { var relationship Relationship - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/block", id), nil, &relationship) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/block", id), nil, &relationship) if err != nil { return nil, err } @@ -163,9 +164,9 @@ func (c *Client) AccountBlock(id int64) (*Relationship, error) { } // AccountUnblock unblock the account. -func (c *Client) AccountUnblock(id int64) (*Relationship, error) { +func (c *Client) AccountUnblock(ctx context.Context, id int64) (*Relationship, error) { var relationship Relationship - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/unblock", id), nil, &relationship) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/unblock", id), nil, &relationship) if err != nil { return nil, err } @@ -173,9 +174,9 @@ func (c *Client) AccountUnblock(id int64) (*Relationship, error) { } // AccountMute mute the account. -func (c *Client) AccountMute(id int64) (*Relationship, error) { +func (c *Client) AccountMute(ctx context.Context, id int64) (*Relationship, error) { var relationship Relationship - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/mute", id), nil, &relationship) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/mute", id), nil, &relationship) if err != nil { return nil, err } @@ -183,9 +184,9 @@ func (c *Client) AccountMute(id int64) (*Relationship, error) { } // AccountUnmute unmute the account. -func (c *Client) AccountUnmute(id int64) (*Relationship, error) { +func (c *Client) AccountUnmute(ctx context.Context, id int64) (*Relationship, error) { var relationship Relationship - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/unmute", id), nil, &relationship) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/accounts/%d/unmute", id), nil, &relationship) if err != nil { return nil, err } @@ -193,12 +194,12 @@ func (c *Client) AccountUnmute(id int64) (*Relationship, error) { } // GetAccountRelationship return relationship for the account. -func (c *Client) GetAccountRelationship(id int64) ([]*Relationship, error) { +func (c *Client) GetAccountRelationship(ctx context.Context, id int64) ([]*Relationship, error) { params := url.Values{} params.Set("id", fmt.Sprint(id)) var relationships []*Relationship - err := c.doAPI(http.MethodGet, "/api/v1/accounts/relationship", params, &relationships) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/accounts/relationship", params, &relationships) if err != nil { return nil, err } @@ -206,13 +207,13 @@ func (c *Client) GetAccountRelationship(id int64) ([]*Relationship, error) { } // AccountsSearch search accounts by query. -func (c *Client) AccountsSearch(q string, limit int64) ([]*Account, error) { +func (c *Client) AccountsSearch(ctx context.Context, q string, limit int64) ([]*Account, error) { params := url.Values{} params.Set("q", q) params.Set("limit", fmt.Sprint(limit)) var accounts []*Account - err := c.doAPI(http.MethodGet, "/api/v1/accounts/search", params, &accounts) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/accounts/search", params, &accounts) if err != nil { return nil, err } @@ -220,12 +221,12 @@ func (c *Client) AccountsSearch(q string, limit int64) ([]*Account, error) { } // FollowRemoteUser send follow-request. -func (c *Client) FollowRemoteUser(uri string) (*Account, error) { +func (c *Client) FollowRemoteUser(ctx context.Context, uri string) (*Account, error) { params := url.Values{} params.Set("uri", uri) var account Account - err := c.doAPI(http.MethodPost, "/api/v1/follows", params, &account) + err := c.doAPI(ctx, http.MethodPost, "/api/v1/follows", params, &account) if err != nil { return nil, err } @@ -233,9 +234,9 @@ func (c *Client) FollowRemoteUser(uri string) (*Account, error) { } // GetFollowRequests return follow-requests. -func (c *Client) GetFollowRequests() ([]*Account, error) { +func (c *Client) GetFollowRequests(ctx context.Context) ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, "/api/v1/follow_requests", nil, &accounts) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/follow_requests", nil, &accounts) if err != nil { return nil, err } @@ -243,11 +244,11 @@ func (c *Client) GetFollowRequests() ([]*Account, error) { } // FollowRequestAuthorize is authorize the follow request of user with id. -func (c *Client) FollowRequestAuthorize(id int64) error { - return c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/authorize", id), nil, nil) +func (c *Client) FollowRequestAuthorize(ctx context.Context, id int64) error { + return c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/authorize", id), nil, nil) } // FollowRequestReject is rejects the follow request of user with id. -func (c *Client) FollowRequestReject(id int64) error { - return c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/reject", id), nil, nil) +func (c *Client) FollowRequestReject(ctx context.Context, id int64) error { + return c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/reject", id), nil, nil) } diff --git a/accounts_test.go b/accounts_test.go index b891225..e5d3497 100644 --- a/accounts_test.go +++ b/accounts_test.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "net/http" "net/http/httptest" @@ -20,7 +21,7 @@ func TestAccountUpdate(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - a, err := client.AccountUpdate(&Profile{ + a, err := client.AccountUpdate(context.Background(), &Profile{ DisplayName: String("display_name"), Note: String("note"), Avatar: "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAUoAAADrCAYAAAA...", @@ -47,7 +48,7 @@ func TestGetBlocks(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - bl, err := client.GetBlocks() + bl, err := client.GetBlocks(context.Background()) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -79,11 +80,11 @@ func TestAccountFollow(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - rel, err := client.AccountFollow(123) + rel, err := client.AccountFollow(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - rel, err = client.AccountFollow(1234567) + rel, err = client.AccountFollow(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -112,11 +113,11 @@ func TestAccountUnfollow(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - rel, err := client.AccountUnfollow(123) + rel, err := client.AccountUnfollow(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - rel, err = client.AccountUnfollow(1234567) + rel, err = client.AccountUnfollow(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -147,11 +148,11 @@ func TestGetFollowRequests(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.GetFollowRequests() + _, err := client.GetFollowRequests(context.Background()) if err == nil { t.Fatalf("should be fail: %v", err) } - fReqs, err := client.GetFollowRequests() + fReqs, err := client.GetFollowRequests(context.Background()) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -180,11 +181,11 @@ func TestFollowRequestAuthorize(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - err := client.FollowRequestAuthorize(123) + err := client.FollowRequestAuthorize(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - err = client.FollowRequestAuthorize(1234567) + err = client.FollowRequestAuthorize(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -204,11 +205,11 @@ func TestFollowRequestReject(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - err := client.FollowRequestReject(123) + err := client.FollowRequestReject(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - err = client.FollowRequestReject(1234567) + err = client.FollowRequestReject(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } diff --git a/apps.go b/apps.go index 4f2e4a4..1dd50a5 100644 --- a/apps.go +++ b/apps.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "encoding/json" "fmt" "net/http" @@ -34,7 +35,7 @@ type Application struct { } // RegisterApp returns the mastodon application. -func RegisterApp(appConfig *AppConfig) (*Application, error) { +func RegisterApp(ctx context.Context, appConfig *AppConfig) (*Application, error) { params := url.Values{} params.Set("client_name", appConfig.ClientName) if appConfig.RedirectURIs == "" { diff --git a/apps_test.go b/apps_test.go index 64be57b..177440e 100644 --- a/apps_test.go +++ b/apps_test.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "net/http" "net/http/httptest" @@ -26,7 +27,7 @@ func TestRegisterApp(t *testing.T) { })) defer ts.Close() - app, err := RegisterApp(&AppConfig{ + app, err := RegisterApp(context.Background(), &AppConfig{ Server: ts.URL, Scopes: "read write follow", }) diff --git a/cmd/mstdn/cmd_account.go b/cmd/mstdn/cmd_account.go index e7caa2e..ceff781 100644 --- a/cmd/mstdn/cmd_account.go +++ b/cmd/mstdn/cmd_account.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/mattn/go-mastodon" @@ -9,7 +10,7 @@ import ( func cmdAccount(c *cli.Context) error { client := c.App.Metadata["client"].(*mastodon.Client) - account, err := client.GetAccountCurrentUser() + account, err := client.GetAccountCurrentUser(context.Background()) if err != nil { return err } diff --git a/cmd/mstdn/cmd_followers.go b/cmd/mstdn/cmd_followers.go index aab07ef..20fc7f2 100644 --- a/cmd/mstdn/cmd_followers.go +++ b/cmd/mstdn/cmd_followers.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/mattn/go-mastodon" @@ -9,11 +10,11 @@ import ( func cmdFollowers(c *cli.Context) error { client := c.App.Metadata["client"].(*mastodon.Client) - account, err := client.GetAccountCurrentUser() + account, err := client.GetAccountCurrentUser(context.Background()) if err != nil { return err } - followers, err := client.GetAccountFollowers(account.ID) + followers, err := client.GetAccountFollowers(context.Background(), account.ID) if err != nil { return err } diff --git a/cmd/mstdn/cmd_instance.go b/cmd/mstdn/cmd_instance.go index 752eef4..b287efc 100644 --- a/cmd/mstdn/cmd_instance.go +++ b/cmd/mstdn/cmd_instance.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/mattn/go-mastodon" @@ -9,7 +10,7 @@ import ( func cmdInstance(c *cli.Context) error { client := c.App.Metadata["client"].(*mastodon.Client) - instance, err := client.GetInstance() + instance, err := client.GetInstance(context.Background()) if err != nil { return err } diff --git a/cmd/mstdn/cmd_notification.go b/cmd/mstdn/cmd_notification.go index 3b6e5a7..6936751 100644 --- a/cmd/mstdn/cmd_notification.go +++ b/cmd/mstdn/cmd_notification.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/fatih/color" @@ -10,7 +11,7 @@ import ( func cmdNotification(c *cli.Context) error { client := c.App.Metadata["client"].(*mastodon.Client) - notifications, err := client.GetNotifications() + notifications, err := client.GetNotifications(context.Background()) if err != nil { return err } diff --git a/cmd/mstdn/cmd_search.go b/cmd/mstdn/cmd_search.go index 344bbdd..5cd754b 100644 --- a/cmd/mstdn/cmd_search.go +++ b/cmd/mstdn/cmd_search.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "fmt" @@ -14,7 +15,7 @@ func cmdSearch(c *cli.Context) error { } client := c.App.Metadata["client"].(*mastodon.Client) - results, err := client.Search(argstr(c), false) + results, err := client.Search(context.Background(), argstr(c), false) if err != nil { return err } diff --git a/cmd/mstdn/cmd_timeline.go b/cmd/mstdn/cmd_timeline.go index 6bb2999..26f2142 100644 --- a/cmd/mstdn/cmd_timeline.go +++ b/cmd/mstdn/cmd_timeline.go @@ -1,6 +1,7 @@ package main import ( + "context" "fmt" "github.com/fatih/color" @@ -10,7 +11,7 @@ import ( func cmdTimeline(c *cli.Context) error { client := c.App.Metadata["client"].(*mastodon.Client) - timeline, err := client.GetTimelineHome() + timeline, err := client.GetTimelineHome(context.Background()) if err != nil { return err } diff --git a/cmd/mstdn/cmd_toot.go b/cmd/mstdn/cmd_toot.go index 747c35b..9a82534 100644 --- a/cmd/mstdn/cmd_toot.go +++ b/cmd/mstdn/cmd_toot.go @@ -1,6 +1,7 @@ package main import ( + "context" "errors" "log" @@ -24,7 +25,7 @@ func cmdToot(c *cli.Context) error { toot = argstr(c) } client := c.App.Metadata["client"].(*mastodon.Client) - _, err := client.PostStatus(&mastodon.Toot{ + _, err := client.PostStatus(context.Background(), &mastodon.Toot{ Status: toot, }) return err diff --git a/cmd/mstdn/main.go b/cmd/mstdn/main.go index 1903c34..a3a3d15 100644 --- a/cmd/mstdn/main.go +++ b/cmd/mstdn/main.go @@ -3,6 +3,7 @@ package main import ( "bufio" "bytes" + "context" "encoding/json" "fmt" "io/ioutil" @@ -128,7 +129,7 @@ func authenticate(client *mastodon.Client, config *mastodon.Config, file string) if err != nil { return err } - err = client.Authenticate(email, password) + err = client.Authenticate(context.Background(), email, password) if err != nil { return err } diff --git a/instance.go b/instance.go index 20be51f..b84b77b 100644 --- a/instance.go +++ b/instance.go @@ -1,6 +1,9 @@ package mastodon -import "net/http" +import ( + "context" + "net/http" +) // Instance hold information for mastodon instance. type Instance struct { @@ -11,9 +14,9 @@ type Instance struct { } // GetInstance return Instance. -func (c *Client) GetInstance() (*Instance, error) { +func (c *Client) GetInstance(ctx context.Context) (*Instance, error) { var instance Instance - err := c.doAPI(http.MethodGet, "/api/v1/instance", nil, &instance) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/instance", nil, &instance) if err != nil { return nil, err } diff --git a/mastodon.go b/mastodon.go index 19b452e..b9da000 100644 --- a/mastodon.go +++ b/mastodon.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "encoding/json" "fmt" "net/http" @@ -23,14 +24,30 @@ type Client struct { config *Config } -func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error { +func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { + tr := &http.Transport{} + client := &http.Client{Transport: tr} + c := make(chan error, 1) + go func() { + c <- f(client.Do(req)) + }() + select { + case <-ctx.Done(): + tr.CancelRequest(req) + <-c + return ctx.Err() + case err := <-c: + return err + } +} + +func (c *Client) doAPI(ctx context.Context, method string, uri string, params url.Values, res interface{}) error { u, err := url.Parse(c.config.Server) if err != nil { return err } u.Path = path.Join(u.Path, uri) - var resp *http.Response req, err := http.NewRequest(method, u.String(), strings.NewReader(params.Encode())) if err != nil { return err @@ -39,19 +56,20 @@ func (c *Client) doAPI(method string, uri string, params url.Values, res interfa if params != nil { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } - resp, err = c.Do(req) - if err != nil { - return err - } - defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("bad request: %v", resp.Status) - } else if res == nil { - return nil - } + return httpDo(ctx, req, func(resp *http.Response, err error) error { + if err != nil { + return err + } + defer resp.Body.Close() - return json.NewDecoder(resp.Body).Decode(&res) + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad request: %v", resp.Status) + } else if res == nil { + return nil + } + return json.NewDecoder(resp.Body).Decode(&res) + }) } // NewClient return new mastodon API client. @@ -63,7 +81,7 @@ func NewClient(config *Config) *Client { } // Authenticate get access-token to the API. -func (c *Client) Authenticate(username, password string) error { +func (c *Client) Authenticate(ctx context.Context, username, password string) error { params := url.Values{} params.Set("client_id", c.config.ClientID) params.Set("client_secret", c.config.ClientSecret) diff --git a/mastodon_test.go b/mastodon_test.go index 1fbcf64..843df4c 100644 --- a/mastodon_test.go +++ b/mastodon_test.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "io" "net/http" @@ -24,7 +25,7 @@ func TestAuthenticate(t *testing.T) { ClientID: "foo", ClientSecret: "bar", }) - err := client.Authenticate("invalid", "user") + err := client.Authenticate(context.Background(), "invalid", "user") if err == nil { t.Fatalf("should be fail: %v", err) } @@ -34,7 +35,7 @@ func TestAuthenticate(t *testing.T) { ClientID: "foo", ClientSecret: "bar", }) - err = client.Authenticate("valid", "user") + err = client.Authenticate(context.Background(), "valid", "user") if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -56,7 +57,7 @@ func TestPostStatus(t *testing.T) { ClientID: "foo", ClientSecret: "bar", }) - _, err := client.PostStatus(&Toot{ + _, err := client.PostStatus(context.Background(), &Toot{ Status: "foobar", }) if err == nil { @@ -69,7 +70,7 @@ func TestPostStatus(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err = client.PostStatus(&Toot{ + _, err = client.PostStatus(context.Background(), &Toot{ Status: "foobar", }) if err != nil { @@ -89,7 +90,7 @@ func TestGetTimelineHome(t *testing.T) { ClientID: "foo", ClientSecret: "bar", }) - _, err := client.PostStatus(&Toot{ + _, err := client.PostStatus(context.Background(), &Toot{ Status: "foobar", }) if err == nil { @@ -102,7 +103,7 @@ func TestGetTimelineHome(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - tl, err := client.GetTimelineHome() + tl, err := client.GetTimelineHome(context.Background()) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -142,11 +143,11 @@ func TestGetAccount(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - a, err := client.GetAccount(1) + a, err := client.GetAccount(context.Background(), 1) if err == nil { t.Fatalf("should not be fail: %v", err) } - a, err = client.GetAccount(1234567) + a, err = client.GetAccount(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -172,11 +173,11 @@ func TestGetAccountFollowing(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - fl, err := client.GetAccountFollowing(123) + fl, err := client.GetAccountFollowing(context.Background(), 123) if err == nil { t.Fatalf("should not be fail: %v", err) } - fl, err = client.GetAccountFollowing(1234567) + fl, err = client.GetAccountFollowing(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } diff --git a/notification.go b/notification.go index b8762c0..8fd098d 100644 --- a/notification.go +++ b/notification.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "net/http" "time" @@ -16,9 +17,9 @@ type Notification struct { } // GetNotifications return notifications. -func (c *Client) GetNotifications() ([]*Notification, error) { +func (c *Client) GetNotifications(ctx context.Context) ([]*Notification, error) { var notifications []*Notification - err := c.doAPI(http.MethodGet, "/api/v1/notifications", nil, ¬ifications) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/notifications", nil, ¬ifications) if err != nil { return nil, err } @@ -26,9 +27,9 @@ func (c *Client) GetNotifications() ([]*Notification, error) { } // GetNotification return notifications. -func (c *Client) GetNotification(id int64) (*Notification, error) { +func (c *Client) GetNotification(ctx context.Context, id int64) (*Notification, error) { var notification Notification - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/notifications/%d", id), nil, ¬ification) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/notifications/%d", id), nil, ¬ification) if err != nil { return nil, err } @@ -36,6 +37,6 @@ func (c *Client) GetNotification(id int64) (*Notification, error) { } // ClearNotifications clear notifications. -func (c *Client) ClearNotifications() error { - return c.doAPI(http.MethodPost, "/api/v1/notifications/clear", nil, nil) +func (c *Client) ClearNotifications(ctx context.Context) error { + return c.doAPI(ctx, http.MethodPost, "/api/v1/notifications/clear", nil, nil) } diff --git a/status.go b/status.go index ae20794..104de1f 100644 --- a/status.go +++ b/status.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "net/http" "net/url" @@ -46,9 +47,9 @@ type Card struct { } // GetFavourites return the favorite list of the current user. -func (c *Client) GetFavourites() ([]*Status, error) { +func (c *Client) GetFavourites(ctx context.Context) ([]*Status, error) { var statuses []*Status - err := c.doAPI(http.MethodGet, "/api/v1/favourites", nil, &statuses) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/favourites", nil, &statuses) if err != nil { return nil, err } @@ -56,9 +57,9 @@ func (c *Client) GetFavourites() ([]*Status, error) { } // GetStatus return status specified by id. -func (c *Client) GetStatus(id int64) (*Status, error) { +func (c *Client) GetStatus(ctx context.Context, id int64) (*Status, error) { var status Status - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d", id), nil, &status) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d", id), nil, &status) if err != nil { return nil, err } @@ -66,9 +67,9 @@ func (c *Client) GetStatus(id int64) (*Status, error) { } // GetStatusContext return status specified by id. -func (c *Client) GetStatusContext(id int64) (*Context, error) { +func (c *Client) GetStatusContext(ctx context.Context, id int64) (*Context, error) { var context Context - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/context", id), nil, &context) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/context", id), nil, &context) if err != nil { return nil, err } @@ -76,9 +77,9 @@ func (c *Client) GetStatusContext(id int64) (*Context, error) { } // GetStatusCard return status specified by id. -func (c *Client) GetStatusCard(id int64) (*Card, error) { +func (c *Client) GetStatusCard(ctx context.Context, id int64) (*Card, error) { var card Card - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/card", id), nil, &card) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/card", id), nil, &card) if err != nil { return nil, err } @@ -86,9 +87,9 @@ func (c *Client) GetStatusCard(id int64) (*Card, error) { } // GetRebloggedBy returns the account list of the user who reblogged the toot of id. -func (c *Client) GetRebloggedBy(id int64) ([]*Account, error) { +func (c *Client) GetRebloggedBy(ctx context.Context, id int64) ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/reblogged_by", id), nil, &accounts) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/reblogged_by", id), nil, &accounts) if err != nil { return nil, err } @@ -96,9 +97,9 @@ func (c *Client) GetRebloggedBy(id int64) ([]*Account, error) { } // GetFavouritedBy returns the account list of the user who liked the toot of id. -func (c *Client) GetFavouritedBy(id int64) ([]*Account, error) { +func (c *Client) GetFavouritedBy(ctx context.Context, id int64) ([]*Account, error) { var accounts []*Account - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/favourited_by", id), nil, &accounts) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/favourited_by", id), nil, &accounts) if err != nil { return nil, err } @@ -106,9 +107,9 @@ func (c *Client) GetFavouritedBy(id int64) ([]*Account, error) { } // Reblog is reblog the toot of id and return status of reblog. -func (c *Client) Reblog(id int64) (*Status, error) { +func (c *Client) Reblog(ctx context.Context, id int64) (*Status, error) { var status Status - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/reblog", id), nil, &status) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/reblog", id), nil, &status) if err != nil { return nil, err } @@ -116,9 +117,9 @@ func (c *Client) Reblog(id int64) (*Status, error) { } // Unreblog is unreblog the toot of id and return status of the original toot. -func (c *Client) Unreblog(id int64) (*Status, error) { +func (c *Client) Unreblog(ctx context.Context, id int64) (*Status, error) { var status Status - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unreblog", id), nil, &status) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unreblog", id), nil, &status) if err != nil { return nil, err } @@ -126,9 +127,9 @@ func (c *Client) Unreblog(id int64) (*Status, error) { } // Favourite is favourite the toot of id and return status of the favourite toot. -func (c *Client) Favourite(id int64) (*Status, error) { +func (c *Client) Favourite(ctx context.Context, id int64) (*Status, error) { var status Status - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/favourite", id), nil, &status) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/favourite", id), nil, &status) if err != nil { return nil, err } @@ -136,9 +137,9 @@ func (c *Client) Favourite(id int64) (*Status, error) { } // Unfavourite is unfavourite the toot of id and return status of the unfavourite toot. -func (c *Client) Unfavourite(id int64) (*Status, error) { +func (c *Client) Unfavourite(ctx context.Context, id int64) (*Status, error) { var status Status - err := c.doAPI(http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unfavourite", id), nil, &status) + err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unfavourite", id), nil, &status) if err != nil { return nil, err } @@ -146,9 +147,9 @@ func (c *Client) Unfavourite(id int64) (*Status, error) { } // GetTimelineHome return statuses from home timeline. -func (c *Client) GetTimelineHome() ([]*Status, error) { +func (c *Client) GetTimelineHome(ctx context.Context) ([]*Status, error) { var statuses []*Status - err := c.doAPI(http.MethodGet, "/api/v1/timelines/home", nil, &statuses) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/timelines/home", nil, &statuses) if err != nil { return nil, err } @@ -156,9 +157,9 @@ func (c *Client) GetTimelineHome() ([]*Status, error) { } // GetTimelineHashtag return statuses from tagged timeline. -func (c *Client) GetTimelineHashtag(tag string) ([]*Status, error) { +func (c *Client) GetTimelineHashtag(ctx context.Context, tag string) ([]*Status, error) { var statuses []*Status - err := c.doAPI(http.MethodGet, fmt.Sprintf("/api/v1/timelines/tag/%s", (&url.URL{Path: tag}).EscapedPath()), nil, &statuses) + err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/timelines/tag/%s", (&url.URL{Path: tag}).EscapedPath()), nil, &statuses) if err != nil { return nil, err } @@ -166,7 +167,7 @@ func (c *Client) GetTimelineHashtag(tag string) ([]*Status, error) { } // PostStatus post the toot. -func (c *Client) PostStatus(toot *Toot) (*Status, error) { +func (c *Client) PostStatus(ctx context.Context, toot *Toot) (*Status, error) { params := url.Values{} params.Set("status", toot.Status) if toot.InReplyToID > 0 { @@ -176,7 +177,7 @@ func (c *Client) PostStatus(toot *Toot) (*Status, error) { //params.Set("visibility", "public") var status Status - err := c.doAPI(http.MethodPost, "/api/v1/statuses", params, &status) + err := c.doAPI(ctx, http.MethodPost, "/api/v1/statuses", params, &status) if err != nil { return nil, err } @@ -184,17 +185,17 @@ func (c *Client) PostStatus(toot *Toot) (*Status, error) { } // DeleteStatus delete the toot. -func (c *Client) DeleteStatus(id int64) error { - return c.doAPI(http.MethodDelete, fmt.Sprintf("/api/v1/statuses/%d", id), nil, nil) +func (c *Client) DeleteStatus(ctx context.Context, id int64) error { + return c.doAPI(ctx, http.MethodDelete, fmt.Sprintf("/api/v1/statuses/%d", id), nil, nil) } // Search search content with query. -func (c *Client) Search(q string, resolve bool) (*Results, error) { +func (c *Client) Search(ctx context.Context, q string, resolve bool) (*Results, error) { params := url.Values{} params.Set("q", q) params.Set("resolve", fmt.Sprint(resolve)) var results Results - err := c.doAPI(http.MethodGet, "/api/v1/search", params, &results) + err := c.doAPI(ctx, http.MethodGet, "/api/v1/search", params, &results) if err != nil { return nil, err } diff --git a/status_test.go b/status_test.go index 93d03e2..115082c 100644 --- a/status_test.go +++ b/status_test.go @@ -1,6 +1,7 @@ package mastodon import ( + "context" "fmt" "net/http" "net/http/httptest" @@ -20,7 +21,7 @@ func TestGetFavourites(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - favs, err := client.GetFavourites() + favs, err := client.GetFavourites(context.Background()) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -52,11 +53,11 @@ func TestGetStatus(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.GetStatus(123) + _, err := client.GetStatus(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - status, err := client.GetStatus(1234567) + status, err := client.GetStatus(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -82,11 +83,11 @@ func TestGetRebloggedBy(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.GetRebloggedBy(123) + _, err := client.GetRebloggedBy(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - rbs, err := client.GetRebloggedBy(1234567) + rbs, err := client.GetRebloggedBy(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -118,11 +119,11 @@ func TestGetFavouritedBy(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.GetFavouritedBy(123) + _, err := client.GetFavouritedBy(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - fbs, err := client.GetFavouritedBy(1234567) + fbs, err := client.GetFavouritedBy(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -154,11 +155,11 @@ func TestReblog(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.Reblog(123) + _, err := client.Reblog(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - status, err := client.Reblog(1234567) + status, err := client.Reblog(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -184,11 +185,11 @@ func TestUnreblog(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.Unreblog(123) + _, err := client.Unreblog(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - status, err := client.Unreblog(1234567) + status, err := client.Unreblog(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -214,11 +215,11 @@ func TestFavourite(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.Favourite(123) + _, err := client.Favourite(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - status, err := client.Favourite(1234567) + status, err := client.Favourite(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } @@ -244,11 +245,11 @@ func TestUnfavourite(t *testing.T) { ClientSecret: "bar", AccessToken: "zoo", }) - _, err := client.Unfavourite(123) + _, err := client.Unfavourite(context.Background(), 123) if err == nil { t.Fatalf("should be fail: %v", err) } - status, err := client.Unfavourite(1234567) + status, err := client.Unfavourite(context.Background(), 1234567) if err != nil { t.Fatalf("should not be fail: %v", err) } From 6fe43e545a2ff89169a1e21fc3887e449719279e Mon Sep 17 00:00:00 2001 From: Yasuhiro Matsumoto Date: Mon, 17 Apr 2017 12:25:20 +0900 Subject: [PATCH 68/68] Use Request.WithContext --- mastodon.go | 41 ++++++++++++----------------------------- 1 file changed, 12 insertions(+), 29 deletions(-) diff --git a/mastodon.go b/mastodon.go index b9da000..277e269 100644 --- a/mastodon.go +++ b/mastodon.go @@ -24,23 +24,6 @@ type Client struct { config *Config } -func httpDo(ctx context.Context, req *http.Request, f func(*http.Response, error) error) error { - tr := &http.Transport{} - client := &http.Client{Transport: tr} - c := make(chan error, 1) - go func() { - c <- f(client.Do(req)) - }() - select { - case <-ctx.Done(): - tr.CancelRequest(req) - <-c - return ctx.Err() - case err := <-c: - return err - } -} - func (c *Client) doAPI(ctx context.Context, method string, uri string, params url.Values, res interface{}) error { u, err := url.Parse(c.config.Server) if err != nil { @@ -52,24 +35,24 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params ur if err != nil { return err } + req.WithContext(ctx) req.Header.Set("Authorization", "Bearer "+c.config.AccessToken) if params != nil { req.Header.Set("Content-Type", "application/x-www-form-urlencoded") } - return httpDo(ctx, req, func(resp *http.Response, err error) error { - if err != nil { - return err - } - defer resp.Body.Close() + resp, err := c.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() - if resp.StatusCode != http.StatusOK { - return fmt.Errorf("bad request: %v", resp.Status) - } else if res == nil { - return nil - } - return json.NewDecoder(resp.Body).Decode(&res) - }) + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("bad request: %v", resp.Status) + } else if res == nil { + return nil + } + return json.NewDecoder(resp.Body).Decode(&res) } // NewClient return new mastodon API client.