Merge branch 'master' into reports
commit
52ac299ec8
37
README.md
37
README.md
|
@ -1,6 +1,6 @@
|
||||||
# go-mastodon
|
# 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)
|
[![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)
|
[![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)
|
[![Go Report Card](https://goreportcard.com/badge/github.com/mattn/go-mastodon)](https://goreportcard.com/report/github.com/mattn/go-mastodon)
|
||||||
|
@ -26,9 +26,10 @@ if err != nil {
|
||||||
|
|
||||||
* [x] GET /api/v1/accounts/:id
|
* [x] GET /api/v1/accounts/:id
|
||||||
* [x] GET /api/v1/accounts/verify_credentials
|
* [x] GET /api/v1/accounts/verify_credentials
|
||||||
|
* [x] PATCH /api/v1/accounts/update_credentials
|
||||||
* [x] GET /api/v1/accounts/:id/followers
|
* [x] GET /api/v1/accounts/:id/followers
|
||||||
* [x] GET /api/v1/accounts/:id/following
|
* [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/follow
|
||||||
* [x] POST /api/v1/accounts/:id/unfollow
|
* [x] POST /api/v1/accounts/:id/unfollow
|
||||||
* [x] GET /api/v1/accounts/:id/block
|
* [x] GET /api/v1/accounts/:id/block
|
||||||
|
@ -38,11 +39,11 @@ if err != nil {
|
||||||
* [x] GET /api/v1/accounts/relationships
|
* [x] GET /api/v1/accounts/relationships
|
||||||
* [x] GET /api/v1/accounts/search
|
* [x] GET /api/v1/accounts/search
|
||||||
* [x] POST /api/v1/apps
|
* [x] POST /api/v1/apps
|
||||||
* [ ] GET /api/v1/blocks
|
* [x] GET /api/v1/blocks
|
||||||
* [ ] GET /api/v1/favourites
|
* [x] GET /api/v1/favourites
|
||||||
* [ ] GET /api/v1/follow_requests
|
* [x] GET /api/v1/follow_requests
|
||||||
* [ ] POST /api/v1/follow_requests/authorize
|
* [x] POST /api/v1/follow_requests/:id/authorize
|
||||||
* [ ] POST /api/v1/follow_requests/reject
|
* [x] POST /api/v1/follow_requests/:id/reject
|
||||||
* [x] POST /api/v1/follows
|
* [x] POST /api/v1/follows
|
||||||
* [x] GET /api/v1/instance
|
* [x] GET /api/v1/instance
|
||||||
* [ ] POST /api/v1/media
|
* [ ] POST /api/v1/media
|
||||||
|
@ -52,21 +53,21 @@ if err != nil {
|
||||||
* [x] POST /api/v1/notifications/clear
|
* [x] POST /api/v1/notifications/clear
|
||||||
* [x] GET /api/v1/reports
|
* [x] GET /api/v1/reports
|
||||||
* [x] POST /api/v1/reports
|
* [x] 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
|
||||||
* [x] GET /api/v1/statuses/:id/context
|
* [x] GET /api/v1/statuses/:id/context
|
||||||
* [x] GET /api/v1/statuses/:id/card
|
* [x] GET /api/v1/statuses/:id/card
|
||||||
* [ ] GET /api/v1/statuses/:id/reblogged_by
|
* [x] GET /api/v1/statuses/:id/reblogged_by
|
||||||
* [ ] GET /api/v1/statuses/:id/favourited_by
|
* [x] GET /api/v1/statuses/:id/favourited_by
|
||||||
* [ ] POST /api/v1/statuses
|
* [x] POST /api/v1/statuses
|
||||||
* [ ] DELETE /api/v1/statuses/:id
|
* [x] DELETE /api/v1/statuses/:id
|
||||||
* [ ] POST /api/v1/statuses/:id/reblog
|
* [x] POST /api/v1/statuses/:id/reblog
|
||||||
* [ ] POST /api/v1/statuses/:id/unreblog
|
* [x] POST /api/v1/statuses/:id/unreblog
|
||||||
* [ ] POST /api/v1/statuses/:id/favourite
|
* [x] POST /api/v1/statuses/:id/favourite
|
||||||
* [ ] POST /api/v1/statuses/:id/unfavourite
|
* [x] POST /api/v1/statuses/:id/unfavourite
|
||||||
* [x] GET /api/v1/timelines/home
|
* [x] GET /api/v1/timelines/home
|
||||||
* [ ] GET /api/v1/timelines/public
|
* [x] GET /api/v1/timelines/public
|
||||||
* [ ] GET /api/v1/timelines/tag/:hashtag
|
* [x] GET /api/v1/timelines/tag/:hashtag
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
|
|
128
accounts.go
128
accounts.go
|
@ -1,6 +1,7 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -27,9 +28,9 @@ type Account struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccount return Account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -37,19 +38,65 @@ func (c *Client) GetAccount(id int) (*Account, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountCurrentUser return Account of current user.
|
// GetAccountCurrentUser return Account of current user.
|
||||||
func (c *Client) GetAccountCurrentUser() (*Account, error) {
|
func (c *Client) GetAccountCurrentUser(ctx context.Context) (*Account, error) {
|
||||||
var account Account
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &account, nil
|
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(ctx context.Context, 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(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &account, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccountStatuses return statuses by specified accuont.
|
||||||
|
func (c *Client) GetAccountStatuses(ctx context.Context, id int64) ([]*Status, error) {
|
||||||
|
var statuses []*Status
|
||||||
|
err := c.doAPI(ctx, 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.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -57,9 +104,19 @@ func (c *Client) GetAccountFollowers(id int64) ([]*Account, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountFollowing return following list.
|
// 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
|
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
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlocks return block list.
|
||||||
|
func (c *Client) GetBlocks(ctx context.Context) ([]*Account, error) {
|
||||||
|
var accounts []*Account
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, "/api/v1/blocks", nil, &accounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -77,9 +134,9 @@ type Relationship struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountFollow follow the account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -87,9 +144,9 @@ func (c *Client) AccountFollow(id int64) (*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUnfollow unfollow the account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -97,9 +154,9 @@ func (c *Client) AccountUnfollow(id int64) (*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountBlock block the account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -107,9 +164,9 @@ func (c *Client) AccountBlock(id int64) (*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUnblock unblock the account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -117,9 +174,9 @@ func (c *Client) AccountUnblock(id int64) (*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountMute mute the account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -127,9 +184,9 @@ func (c *Client) AccountMute(id int64) (*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountUnmute unmute the account.
|
// 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
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -137,12 +194,12 @@ func (c *Client) AccountUnmute(id int64) (*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAccountRelationship return relationship for the account.
|
// 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 := url.Values{}
|
||||||
params.Set("id", fmt.Sprint(id))
|
params.Set("id", fmt.Sprint(id))
|
||||||
|
|
||||||
var relationships []*Relationship
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -150,26 +207,26 @@ func (c *Client) GetAccountRelationship(id int64) ([]*Relationship, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// AccountsSearch search accounts by query.
|
// 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 := url.Values{}
|
||||||
params.Set("q", q)
|
params.Set("q", q)
|
||||||
params.Set("limit", fmt.Sprint(limit))
|
params.Set("limit", fmt.Sprint(limit))
|
||||||
|
|
||||||
var accounts []*Account
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return accounts, nil
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Follow send follow-request.
|
// 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 := url.Values{}
|
||||||
params.Set("uri", uri)
|
params.Set("uri", uri)
|
||||||
|
|
||||||
var account Account
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -177,14 +234,21 @@ func (c *Client) FollowRemoteUser(uri string) (*Account, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetFollowRequests return follow-requests.
|
// GetFollowRequests return follow-requests.
|
||||||
func (c *Client) GetFollowRequests(uri string) ([]*Account, error) {
|
func (c *Client) GetFollowRequests(ctx context.Context) ([]*Account, error) {
|
||||||
params := url.Values{}
|
|
||||||
params.Set("uri", uri)
|
|
||||||
|
|
||||||
var accounts []*Account
|
var accounts []*Account
|
||||||
err := c.doAPI(http.MethodGet, "/api/v1/follow_requests", params, &accounts)
|
err := c.doAPI(ctx, http.MethodGet, "/api/v1/follow_requests", nil, &accounts)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return accounts, nil
|
return accounts, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FollowRequestAuthorize is authorize the follow request of user with id.
|
||||||
|
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(ctx context.Context, id int64) error {
|
||||||
|
return c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/follow_requests/%d/reject", id), nil, nil)
|
||||||
|
}
|
||||||
|
|
152
accounts_test.go
152
accounts_test.go
|
@ -1,12 +1,68 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"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(context.Background(), &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 TestGetBlocks(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",
|
||||||
|
})
|
||||||
|
bl, err := client.GetBlocks(context.Background())
|
||||||
|
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) {
|
func TestAccountFollow(t *testing.T) {
|
||||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
if r.URL.Path != "/api/v1/accounts/1234567/follow" {
|
if r.URL.Path != "/api/v1/accounts/1234567/follow" {
|
||||||
|
@ -24,11 +80,11 @@ func TestAccountFollow(t *testing.T) {
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
AccessToken: "zoo",
|
AccessToken: "zoo",
|
||||||
})
|
})
|
||||||
rel, err := client.AccountFollow(123)
|
rel, err := client.AccountFollow(context.Background(), 123)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should be fail: %v", err)
|
t.Fatalf("should be fail: %v", err)
|
||||||
}
|
}
|
||||||
rel, err = client.AccountFollow(1234567)
|
rel, err = client.AccountFollow(context.Background(), 1234567)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -57,11 +113,11 @@ func TestAccountUnfollow(t *testing.T) {
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
AccessToken: "zoo",
|
AccessToken: "zoo",
|
||||||
})
|
})
|
||||||
rel, err := client.AccountUnfollow(123)
|
rel, err := client.AccountUnfollow(context.Background(), 123)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should be fail: %v", err)
|
t.Fatalf("should be fail: %v", err)
|
||||||
}
|
}
|
||||||
rel, err = client.AccountUnfollow(1234567)
|
rel, err = client.AccountUnfollow(context.Background(), 1234567)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -72,3 +128,89 @@ func TestAccountUnfollow(t *testing.T) {
|
||||||
t.Fatalf("want %t but %t", false, rel.Following)
|
t.Fatalf("want %t but %t", false, rel.Following)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}))
|
||||||
|
defer ts.Close()
|
||||||
|
|
||||||
|
client := NewClient(&Config{
|
||||||
|
Server: ts.URL,
|
||||||
|
ClientID: "foo",
|
||||||
|
ClientSecret: "bar",
|
||||||
|
AccessToken: "zoo",
|
||||||
|
})
|
||||||
|
_, err := client.GetFollowRequests(context.Background())
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
fReqs, err := client.GetFollowRequests(context.Background())
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
err = client.FollowRequestAuthorize(context.Background(), 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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
err = client.FollowRequestReject(context.Background(), 1234567)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("should not be fail: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
9
apps.go
9
apps.go
|
@ -1,6 +1,7 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -34,7 +35,7 @@ type Application struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// RegisterApp returns the mastodon application.
|
// RegisterApp returns the mastodon application.
|
||||||
func RegisterApp(appConfig *AppConfig) (*Application, error) {
|
func RegisterApp(ctx context.Context, appConfig *AppConfig) (*Application, error) {
|
||||||
params := url.Values{}
|
params := url.Values{}
|
||||||
params.Set("client_name", appConfig.ClientName)
|
params.Set("client_name", appConfig.ClientName)
|
||||||
if appConfig.RedirectURIs == "" {
|
if appConfig.RedirectURIs == "" {
|
||||||
|
@ -45,13 +46,13 @@ func RegisterApp(appConfig *AppConfig) (*Application, error) {
|
||||||
params.Set("scopes", appConfig.Scopes)
|
params.Set("scopes", appConfig.Scopes)
|
||||||
params.Set("website", appConfig.Website)
|
params.Set("website", appConfig.Website)
|
||||||
|
|
||||||
url, err := url.Parse(appConfig.Server)
|
u, err := url.Parse(appConfig.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,43 @@
|
||||||
|
package mastodon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background(), &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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,12 +5,24 @@ command line tool for mstdn.jp
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```
|
```
|
||||||
Usage of mstdn:
|
NAME:
|
||||||
-S streaming public
|
mstdn - mastodon client
|
||||||
-ff string
|
|
||||||
post utf-8 string from a file("-" means STDIN)
|
USAGE:
|
||||||
-t string
|
mstdn [global options] command [command options] [arguments...]
|
||||||
toot text
|
|
||||||
|
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
|
## Installation
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
followers, err := client.GetAccountFollowers(context.Background(), account.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, follower := range followers {
|
||||||
|
fmt.Fprintf(c.App.Writer, "%v,%v\n", follower.ID, follower.Username)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -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 command: %v", "zzz", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,22 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/instance":
|
||||||
|
fmt.Fprintln(w, `{"title": "zzz"}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
func(app *cli.App) {
|
||||||
|
app.Run([]string{"mstdn", "instance"})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !strings.Contains(out, "zzz") {
|
||||||
|
t.Fatalf("%q should be contained in output of command: %v", "zzz", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, n := range notifications {
|
||||||
|
if n.Status != nil {
|
||||||
|
color.Set(color.FgHiRed)
|
||||||
|
fmt.Fprint(c.App.Writer, n.Account.Username)
|
||||||
|
color.Set(color.Reset)
|
||||||
|
fmt.Fprintln(c.App.Writer, " "+n.Type)
|
||||||
|
s := n.Status
|
||||||
|
fmt.Fprintln(c.App.Writer, textContent(s.Content))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/notifications":
|
||||||
|
fmt.Fprintln(w, `[{"type": "rebloged", "status": {"content": "foo"}}]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
func(app *cli.App) {
|
||||||
|
app.Run([]string{"mstdn", "notification"})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !strings.Contains(out, "rebloged") {
|
||||||
|
t.Fatalf("%q should be contained in output of command: %v", "rebloged", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background(), argstr(c), false)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, result := range results.Accounts {
|
||||||
|
fmt.Fprintln(c.App.Writer, result)
|
||||||
|
}
|
||||||
|
for _, result := range results.Statuses {
|
||||||
|
fmt.Fprintln(c.App.Writer, result)
|
||||||
|
}
|
||||||
|
for _, result := range results.Hashtags {
|
||||||
|
fmt.Fprintln(c.App.Writer, result)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,43 @@
|
||||||
|
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())
|
||||||
|
defer cancel()
|
||||||
|
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.Fprintln(c.App.Writer, t.Status.Account.Username)
|
||||||
|
color.Set(color.Reset)
|
||||||
|
fmt.Fprintln(c.App.Writer, textContent(t.Status.Content))
|
||||||
|
case *mastodon.ErrorEvent:
|
||||||
|
color.Set(color.FgYellow)
|
||||||
|
fmt.Fprintln(c.App.Writer, t.Error())
|
||||||
|
color.Set(color.Reset)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -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()
|
||||||
|
}
|
|
@ -0,0 +1,26 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background())
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := len(timeline) - 1; i >= 0; i-- {
|
||||||
|
t := timeline[i]
|
||||||
|
color.Set(color.FgHiRed)
|
||||||
|
fmt.Fprintln(c.App.Writer, t.Account.Username)
|
||||||
|
color.Set(color.Reset)
|
||||||
|
fmt.Fprintln(c.App.Writer, textContent(t.Content))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,30 @@
|
||||||
|
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) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/timelines/home":
|
||||||
|
fmt.Fprintln(w, `[{"content": "zzz"}]`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
func(app *cli.App) {
|
||||||
|
app.Run([]string{"mstdn", "timeline"})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if !strings.Contains(out, "zzz") {
|
||||||
|
t.Fatalf("%q should be contained in output of command: %v", "zzz", out)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,32 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/mattn/go-mastodon"
|
||||||
|
"github.com/urfave/cli"
|
||||||
|
)
|
||||||
|
|
||||||
|
func cmdToot(c *cli.Context) error {
|
||||||
|
var toot string
|
||||||
|
ff := c.String("ff")
|
||||||
|
if ff != "" {
|
||||||
|
text, err := readFile(ff)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
toot = string(text)
|
||||||
|
} else {
|
||||||
|
if !c.Args().Present() {
|
||||||
|
return errors.New("arguments required")
|
||||||
|
}
|
||||||
|
toot = argstr(c)
|
||||||
|
}
|
||||||
|
client := c.App.Metadata["client"].(*mastodon.Client)
|
||||||
|
_, err := client.PostStatus(context.Background(), &mastodon.Toot{
|
||||||
|
Status: toot,
|
||||||
|
})
|
||||||
|
return err
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
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) {
|
||||||
|
switch r.URL.Path {
|
||||||
|
case "/api/v1/statuses":
|
||||||
|
toot = r.FormValue("status")
|
||||||
|
fmt.Fprintln(w, `{"ID": 2345}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
|
||||||
|
return
|
||||||
|
},
|
||||||
|
func(app *cli.App) {
|
||||||
|
app.Run([]string{"mstdn", "toot", "foo"})
|
||||||
|
},
|
||||||
|
)
|
||||||
|
if toot != "foo" {
|
||||||
|
t.Fatalf("want %q, got %q", "foo", toot)
|
||||||
|
}
|
||||||
|
}
|
|
@ -5,28 +5,19 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"flag"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"log"
|
|
||||||
"os"
|
"os"
|
||||||
"os/signal"
|
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/fatih/color"
|
|
||||||
"github.com/mattn/go-mastodon"
|
"github.com/mattn/go-mastodon"
|
||||||
"github.com/mattn/go-tty"
|
"github.com/mattn/go-tty"
|
||||||
|
"github.com/urfave/cli"
|
||||||
"golang.org/x/net/html"
|
"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)")
|
|
||||||
)
|
|
||||||
|
|
||||||
func readFile(filename string) ([]byte, error) {
|
func readFile(filename string) ([]byte, error) {
|
||||||
if filename == "-" {
|
if filename == "-" {
|
||||||
return ioutil.ReadAll(os.Stdin)
|
return ioutil.ReadAll(os.Stdin)
|
||||||
|
@ -37,7 +28,7 @@ func readFile(filename string) ([]byte, error) {
|
||||||
func textContent(s string) string {
|
func textContent(s string) string {
|
||||||
doc, err := html.Parse(strings.NewReader(s))
|
doc, err := html.Parse(strings.NewReader(s))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return s
|
||||||
}
|
}
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
|
|
||||||
|
@ -64,7 +55,7 @@ func textContent(s string) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
var (
|
var (
|
||||||
readUsername func() (string, error) = func() (string, error) {
|
readUsername = func() (string, error) {
|
||||||
b, _, err := bufio.NewReader(os.Stdin).ReadLine()
|
b, _, err := bufio.NewReader(os.Stdin).ReadLine()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
|
@ -121,8 +112,8 @@ func getConfig() (string, *mastodon.Config, error) {
|
||||||
}
|
}
|
||||||
config := &mastodon.Config{
|
config := &mastodon.Config{
|
||||||
Server: "https://mstdn.jp",
|
Server: "https://mstdn.jp",
|
||||||
ClientID: "7d1873f3940af3e9128c81d5a2ddb3f235ccfa1cd11761efd3b8426f40898fe8",
|
ClientID: "171d45f22068a5dddbd927b9d966f5b97971ed1d3256b03d489f5b3a83cdba59",
|
||||||
ClientSecret: "3c8ea997c580f196453e97c1c58f6f5c131f668456bbe1ed37aaccac719397db",
|
ClientSecret: "574a2cf4b3f28a5fa0cfd285fc80cfe9daa419945163ef18f5f3d0022f4add28",
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = json.Unmarshal(b, &config)
|
err = json.Unmarshal(b, &config)
|
||||||
|
@ -133,109 +124,119 @@ func getConfig() (string, *mastodon.Config, error) {
|
||||||
return file, config, nil
|
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()
|
email, password, err := prompt()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
err = client.Authenticate(email, password)
|
err = client.Authenticate(context.Background(), email, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
return err
|
||||||
}
|
}
|
||||||
b, err := json.MarshalIndent(config, "", " ")
|
b, err := json.MarshalIndent(config, "", " ")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("failed to store file:", err)
|
return fmt.Errorf("failed to store file: %v", err)
|
||||||
}
|
}
|
||||||
err = ioutil.WriteFile(file, b, 0700)
|
err = ioutil.WriteFile(file, b, 0700)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal("failed to store file:", err)
|
return fmt.Errorf("failed to store file: %v", err)
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func streaming(client *mastodon.Client) {
|
func argstr(c *cli.Context) string {
|
||||||
ctx, cancel := context.WithCancel(context.Background())
|
a := []string{}
|
||||||
sc := make(chan os.Signal, 1)
|
for i := 0; i < c.NArg(); i++ {
|
||||||
signal.Notify(sc, os.Interrupt)
|
a = append(a, c.Args().Get(i))
|
||||||
q, err := client.StreamingPublic(ctx)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(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 strings.Join(a, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func fatalIf(err error) {
|
||||||
flag.Parse()
|
if err == nil {
|
||||||
if *fromfile != "" {
|
return
|
||||||
text, err := readFile(*fromfile)
|
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
|
||||||
}
|
|
||||||
*toot = string(text)
|
|
||||||
}
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
|
||||||
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func post(client *mastodon.Client, text string) {
|
func makeApp() *cli.App {
|
||||||
_, err := client.PostStatus(&mastodon.Toot{
|
app := cli.NewApp()
|
||||||
Status: text,
|
app.Name = "mstdn"
|
||||||
})
|
app.Usage = "mastodon client"
|
||||||
if err != nil {
|
app.Version = "0.0.1"
|
||||||
log.Fatal(err)
|
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,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "notification",
|
||||||
|
Usage: "show notification",
|
||||||
|
Action: cmdNotification,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "instance",
|
||||||
|
Usage: "show instance information",
|
||||||
|
Action: cmdInstance,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "account",
|
||||||
|
Usage: "show account information",
|
||||||
|
Action: cmdAccount,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "search",
|
||||||
|
Usage: "search content",
|
||||||
|
Action: cmdSearch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "followers",
|
||||||
|
Usage: "show followers",
|
||||||
|
Action: cmdFollowers,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
return app
|
||||||
}
|
}
|
||||||
|
|
||||||
func timeline(client *mastodon.Client) {
|
func run() int {
|
||||||
timeline, err := client.GetTimelineHome()
|
app := makeApp()
|
||||||
if err != nil {
|
|
||||||
log.Fatal(err)
|
file, config, err := getConfig()
|
||||||
|
fatalIf(err)
|
||||||
|
|
||||||
|
client := mastodon.NewClient(config)
|
||||||
|
if config.AccessToken == "" {
|
||||||
|
err = authenticate(client, config, file)
|
||||||
|
fatalIf(err)
|
||||||
}
|
}
|
||||||
for i := len(timeline) - 1; i >= 0; i-- {
|
app.Metadata = map[string]interface{}{
|
||||||
t := timeline[i]
|
"client": client,
|
||||||
color.Set(color.FgHiRed)
|
"config": config,
|
||||||
fmt.Println(t.Account.Username)
|
|
||||||
color.Set(color.Reset)
|
|
||||||
fmt.Println(textContent(t.Content))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
app.Run(os.Args)
|
||||||
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
file, config, err := getConfig()
|
os.Exit(run())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
package mastodon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
"net/http"
|
||||||
|
"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 {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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 }
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,5 +1,10 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
// Instance hold information for mastodon instance.
|
// Instance hold information for mastodon instance.
|
||||||
type Instance struct {
|
type Instance struct {
|
||||||
URI string `json:"uri"`
|
URI string `json:"uri"`
|
||||||
|
@ -9,9 +14,9 @@ type Instance struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetInstance return Instance.
|
// GetInstance return Instance.
|
||||||
func (c *Client) GetInstance() (*Instance, error) {
|
func (c *Client) GetInstance(ctx context.Context) (*Instance, error) {
|
||||||
var instance Instance
|
var instance Instance
|
||||||
err := c.doAPI("GET", "/api/v1/instance", nil, &instance)
|
err := c.doAPI(ctx, http.MethodGet, "/api/v1/instance", nil, &instance)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
42
mastodon.go
42
mastodon.go
|
@ -1,6 +1,7 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
@ -23,32 +24,34 @@ type Client struct {
|
||||||
config *Config
|
config *Config
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) doAPI(method string, uri string, params url.Values, res interface{}) error {
|
func (c *Client) doAPI(ctx context.Context, 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 {
|
if err != nil {
|
||||||
return err
|
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, u.String(), strings.NewReader(params.Encode()))
|
||||||
req, err := http.NewRequest(method, url.String(), strings.NewReader(params.Encode()))
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
req.WithContext(ctx)
|
||||||
req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
|
||||||
resp, err = c.Do(req)
|
if params != nil {
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := c.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer resp.Body.Close()
|
defer resp.Body.Close()
|
||||||
if res == nil {
|
|
||||||
|
if resp.StatusCode != http.StatusOK {
|
||||||
|
return fmt.Errorf("bad request: %v", resp.Status)
|
||||||
|
} else if res == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if method == http.MethodGet && resp.StatusCode != http.StatusOK {
|
|
||||||
return fmt.Errorf("bad request: %v", resp.Status)
|
|
||||||
}
|
|
||||||
|
|
||||||
return json.NewDecoder(resp.Body).Decode(&res)
|
return json.NewDecoder(resp.Body).Decode(&res)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,7 +64,7 @@ func NewClient(config *Config) *Client {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Authenticate get access-token to the API.
|
// 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 := url.Values{}
|
||||||
params.Set("client_id", c.config.ClientID)
|
params.Set("client_id", c.config.ClientID)
|
||||||
params.Set("client_secret", c.config.ClientSecret)
|
params.Set("client_secret", c.config.ClientSecret)
|
||||||
|
@ -70,13 +73,13 @@ func (c *Client) Authenticate(username, password string) error {
|
||||||
params.Set("password", password)
|
params.Set("password", password)
|
||||||
params.Set("scope", "read write follow")
|
params.Set("scope", "read write follow")
|
||||||
|
|
||||||
url, err := url.Parse(c.config.Server)
|
u, err := url.Parse(c.config.Server)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -135,3 +138,10 @@ type Attachment struct {
|
||||||
PreviewURL string `json:"preview_url"`
|
PreviewURL string `json:"preview_url"`
|
||||||
TextURL string `json:"text_url"`
|
TextURL string `json:"text_url"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Results hold information for search result.
|
||||||
|
type Results struct {
|
||||||
|
Accounts []*Account `json:"accounts"`
|
||||||
|
Statuses []*Status `json:"statuses"`
|
||||||
|
Hashtags []string `json:"hashtags"`
|
||||||
|
}
|
||||||
|
|
107
mastodon_test.go
107
mastodon_test.go
|
@ -7,7 +7,6 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"testing"
|
"testing"
|
||||||
"time"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestAuthenticate(t *testing.T) {
|
func TestAuthenticate(t *testing.T) {
|
||||||
|
@ -26,7 +25,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
ClientID: "foo",
|
ClientID: "foo",
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
})
|
})
|
||||||
err := client.Authenticate("invalid", "user")
|
err := client.Authenticate(context.Background(), "invalid", "user")
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should be fail: %v", err)
|
t.Fatalf("should be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -36,7 +35,7 @@ func TestAuthenticate(t *testing.T) {
|
||||||
ClientID: "foo",
|
ClientID: "foo",
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
})
|
})
|
||||||
err = client.Authenticate("valid", "user")
|
err = client.Authenticate(context.Background(), "valid", "user")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -58,7 +57,7 @@ func TestPostStatus(t *testing.T) {
|
||||||
ClientID: "foo",
|
ClientID: "foo",
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
})
|
})
|
||||||
_, err := client.PostStatus(&Toot{
|
_, err := client.PostStatus(context.Background(), &Toot{
|
||||||
Status: "foobar",
|
Status: "foobar",
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -71,7 +70,7 @@ func TestPostStatus(t *testing.T) {
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
AccessToken: "zoo",
|
AccessToken: "zoo",
|
||||||
})
|
})
|
||||||
_, err = client.PostStatus(&Toot{
|
_, err = client.PostStatus(context.Background(), &Toot{
|
||||||
Status: "foobar",
|
Status: "foobar",
|
||||||
})
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -91,7 +90,7 @@ func TestGetTimelineHome(t *testing.T) {
|
||||||
ClientID: "foo",
|
ClientID: "foo",
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
})
|
})
|
||||||
_, err := client.PostStatus(&Toot{
|
_, err := client.PostStatus(context.Background(), &Toot{
|
||||||
Status: "foobar",
|
Status: "foobar",
|
||||||
})
|
})
|
||||||
if err == nil {
|
if err == nil {
|
||||||
|
@ -104,7 +103,7 @@ func TestGetTimelineHome(t *testing.T) {
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
AccessToken: "zoo",
|
AccessToken: "zoo",
|
||||||
})
|
})
|
||||||
tl, err := client.GetTimelineHome()
|
tl, err := client.GetTimelineHome(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -144,11 +143,11 @@ func TestGetAccount(t *testing.T) {
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
AccessToken: "zoo",
|
AccessToken: "zoo",
|
||||||
})
|
})
|
||||||
a, err := client.GetAccount(1)
|
a, err := client.GetAccount(context.Background(), 1)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
a, err = client.GetAccount(1234567)
|
a, err = client.GetAccount(context.Background(), 1234567)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -174,11 +173,11 @@ func TestGetAccountFollowing(t *testing.T) {
|
||||||
ClientSecret: "bar",
|
ClientSecret: "bar",
|
||||||
AccessToken: "zoo",
|
AccessToken: "zoo",
|
||||||
})
|
})
|
||||||
fl, err := client.GetAccountFollowing(123)
|
fl, err := client.GetAccountFollowing(context.Background(), 123)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
fl, err = client.GetAccountFollowing(1234567)
|
fl, err = client.GetAccountFollowing(context.Background(), 1234567)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("should not be fail: %v", err)
|
t.Fatalf("should not be fail: %v", err)
|
||||||
}
|
}
|
||||||
|
@ -192,89 +191,3 @@ func TestGetAccountFollowing(t *testing.T) {
|
||||||
t.Fatalf("want %q but %q", "bar", fl[0].Username)
|
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"time"
|
"time"
|
||||||
|
@ -16,19 +17,19 @@ type Notification struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNotifications return notifications.
|
// GetNotifications return notifications.
|
||||||
func (c *Client) GetNotifications() ([]*Notification, error) {
|
func (c *Client) GetNotifications(ctx context.Context) ([]*Notification, error) {
|
||||||
var notifications []*Notification
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return notifications, nil
|
return notifications, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetNotifications return notifications.
|
// GetNotifications return notification.
|
||||||
func (c *Client) GetNotification(id int64) (*Notification, error) {
|
func (c *Client) GetNotification(ctx context.Context, id int64) (*Notification, error) {
|
||||||
var notification Notification
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -36,6 +37,6 @@ func (c *Client) GetNotification(id int64) (*Notification, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// ClearNotifications clear notifications.
|
// ClearNotifications clear notifications.
|
||||||
func (c *Client) ClearNotifications() error {
|
func (c *Client) ClearNotifications(ctx context.Context) error {
|
||||||
return c.doAPI(http.MethodPost, "/api/v1/notifications/clear", nil, nil)
|
return c.doAPI(ctx, http.MethodPost, "/api/v1/notifications/clear", nil, nil)
|
||||||
}
|
}
|
||||||
|
|
123
status.go
123
status.go
|
@ -1,6 +1,7 @@
|
||||||
package mastodon
|
package mastodon
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -33,8 +34,8 @@ type Status struct {
|
||||||
|
|
||||||
// Context hold information for mastodon context.
|
// Context hold information for mastodon context.
|
||||||
type Context struct {
|
type Context struct {
|
||||||
Ancestors []*Status `ancestors`
|
Ancestors []*Status `json:"ancestors"`
|
||||||
Descendants []*Status `descendants`
|
Descendants []*Status `json:"descendants"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Card hold information for mastodon card.
|
// Card hold information for mastodon card.
|
||||||
|
@ -45,10 +46,20 @@ type Card struct {
|
||||||
Image string `json:"image"`
|
Image string `json:"image"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFavourites return the favorite list of the current user.
|
||||||
|
func (c *Client) GetFavourites(ctx context.Context) ([]*Status, error) {
|
||||||
|
var statuses []*Status
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, "/api/v1/favourites", nil, &statuses)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return statuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
// GetStatus return status specified by id.
|
// GetStatus return status specified by id.
|
||||||
func (c *Client) GetStatus(id string) (*Status, error) {
|
func (c *Client) GetStatus(ctx context.Context, id int64) (*Status, error) {
|
||||||
var status Status
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -56,9 +67,9 @@ func (c *Client) GetStatus(id string) (*Status, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusContext return status specified by id.
|
// GetStatusContext return status specified by id.
|
||||||
func (c *Client) GetStatusContext(id string) (*Context, error) {
|
func (c *Client) GetStatusContext(ctx context.Context, id int64) (*Context, error) {
|
||||||
var context Context
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -66,19 +77,89 @@ func (c *Client) GetStatusContext(id string) (*Context, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStatusCard return status specified by id.
|
// GetStatusCard return status specified by id.
|
||||||
func (c *Client) GetStatusCard(id string) (*Card, error) {
|
func (c *Client) GetStatusCard(ctx context.Context, id int64) (*Card, error) {
|
||||||
var card Card
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &card, nil
|
return &card, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetRebloggedBy returns the account list of the user who reblogged the toot of id.
|
||||||
|
func (c *Client) GetRebloggedBy(ctx context.Context, id int64) ([]*Account, error) {
|
||||||
|
var accounts []*Account
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/reblogged_by", id), nil, &accounts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFavouritedBy returns the account list of the user who liked the toot of id.
|
||||||
|
func (c *Client) GetFavouritedBy(ctx context.Context, id int64) ([]*Account, error) {
|
||||||
|
var accounts []*Account
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/statuses/%d/favourited_by", id), nil, &accounts)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return accounts, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reblog is reblog the toot of id and return status of reblog.
|
||||||
|
func (c *Client) Reblog(ctx context.Context, id int64) (*Status, error) {
|
||||||
|
var status Status
|
||||||
|
err := c.doAPI(ctx, 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 and return status of the original toot.
|
||||||
|
func (c *Client) Unreblog(ctx context.Context, id int64) (*Status, error) {
|
||||||
|
var status Status
|
||||||
|
err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/statuses/%d/unreblog", id), nil, &status)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &status, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Favourite is favourite the toot of id and return status of the favourite toot.
|
||||||
|
func (c *Client) Favourite(ctx context.Context, id int64) (*Status, error) {
|
||||||
|
var status Status
|
||||||
|
err := c.doAPI(ctx, 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(ctx context.Context, id int64) (*Status, error) {
|
||||||
|
var status Status
|
||||||
|
err := c.doAPI(ctx, 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.
|
// GetTimelineHome return statuses from home timeline.
|
||||||
func (c *Client) GetTimelineHome() ([]*Status, error) {
|
func (c *Client) GetTimelineHome(ctx context.Context) ([]*Status, error) {
|
||||||
var statuses []*Status
|
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
|
||||||
|
}
|
||||||
|
return statuses, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimelineHashtag return statuses from tagged timeline.
|
||||||
|
func (c *Client) GetTimelineHashtag(ctx context.Context, tag string) ([]*Status, error) {
|
||||||
|
var statuses []*Status
|
||||||
|
err := c.doAPI(ctx, http.MethodGet, fmt.Sprintf("/api/v1/timelines/tag/%s", (&url.URL{Path: tag}).EscapedPath()), nil, &statuses)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
@ -86,7 +167,7 @@ func (c *Client) GetTimelineHome() ([]*Status, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// PostStatus post the toot.
|
// 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 := url.Values{}
|
||||||
params.Set("status", toot.Status)
|
params.Set("status", toot.Status)
|
||||||
if toot.InReplyToID > 0 {
|
if toot.InReplyToID > 0 {
|
||||||
|
@ -96,9 +177,27 @@ func (c *Client) PostStatus(toot *Toot) (*Status, error) {
|
||||||
//params.Set("visibility", "public")
|
//params.Set("visibility", "public")
|
||||||
|
|
||||||
var status Status
|
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 {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return &status, nil
|
return &status, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeleteStatus delete the toot.
|
||||||
|
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(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(ctx, http.MethodGet, "/api/v1/search", params, &results)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &results, nil
|
||||||
|
}
|
||||||
|
|
|
@ -0,0 +1,259 @@
|
||||||
|
package mastodon
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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(context.Background())
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
status, err := client.GetStatus(context.Background(), 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" {
|
||||||
|
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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
rbs, err := client.GetRebloggedBy(context.Background(), 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
fbs, err := client.GetFavouritedBy(context.Background(), 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
status, err := client.Reblog(context.Background(), 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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
status, err := client.Unreblog(context.Background(), 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 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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
status, err := client.Favourite(context.Background(), 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(context.Background(), 123)
|
||||||
|
if err == nil {
|
||||||
|
t.Fatalf("should be fail: %v", err)
|
||||||
|
}
|
||||||
|
status, err := client.Unfavourite(context.Background(), 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)
|
||||||
|
}
|
||||||
|
}
|
37
streaming.go
37
streaming.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
@ -67,14 +68,15 @@ func handleReader(ctx context.Context, q chan Event, r io.Reader) error {
|
||||||
return ctx.Err()
|
return ctx.Err()
|
||||||
}
|
}
|
||||||
|
|
||||||
// StreamingPublic return channel to read events.
|
func (c *Client) streaming(ctx context.Context, p string, tag string) (chan Event, error) {
|
||||||
func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
|
u, err := url.Parse(c.config.Server)
|
||||||
url, err := url.Parse(c.config.Server)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
url.Path = path.Join(url.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
|
var resp *http.Response
|
||||||
|
|
||||||
q := make(chan Event, 10)
|
q := make(chan Event, 10)
|
||||||
|
@ -82,20 +84,27 @@ func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
|
||||||
defer ctx.Done()
|
defer ctx.Done()
|
||||||
|
|
||||||
for {
|
for {
|
||||||
req, err := http.NewRequest(http.MethodGet, url.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 {
|
if err == nil {
|
||||||
req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
|
req.Header.Set("Authorization", "Bearer "+c.config.AccessToken)
|
||||||
resp, err = c.Do(req)
|
resp, err = c.Do(req)
|
||||||
|
if resp.StatusCode != 200 {
|
||||||
|
err = fmt.Errorf("bad request: %v", resp.Status)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if err == nil {
|
if err == nil {
|
||||||
err = handleReader(ctx, q, resp.Body)
|
err = handleReader(ctx, q, resp.Body)
|
||||||
resp.Body.Close()
|
|
||||||
if err == nil {
|
if err == nil {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
q <- &ErrorEvent{err}
|
q <- &ErrorEvent{err}
|
||||||
}
|
}
|
||||||
|
resp.Body.Close()
|
||||||
time.Sleep(3 * time.Second)
|
time.Sleep(3 * time.Second)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
@ -106,4 +115,20 @@ func (c *Client) StreamingPublic(ctx context.Context) (chan Event, error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
return q, nil
|
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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue