This commit is contained in:
Rasmus Lindroth 2022-05-01 17:47:45 +02:00
parent be11f5b013
commit b1b367ca33
45 changed files with 24 additions and 1976 deletions

View file

@ -118,7 +118,6 @@ func TestGetAccountStatuses(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`) fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -151,7 +150,6 @@ func TestGetAccountFollowers(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -187,7 +185,6 @@ func TestGetAccountFollowing(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -225,7 +222,6 @@ func TestGetBlocks(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -261,7 +257,6 @@ func TestAccountFollow(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id":1234567,"following":true}`) fmt.Fprintln(w, `{"id":1234567,"following":true}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -271,11 +266,11 @@ func TestAccountFollow(t *testing.T) {
ClientSecret: "bar", ClientSecret: "bar",
AccessToken: "zoo", AccessToken: "zoo",
}) })
rel, err := client.AccountFollow(context.Background(), "123") _, 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(context.Background(), "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)
} }
@ -294,7 +289,6 @@ func TestAccountUnfollow(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id":1234567,"following":false}`) fmt.Fprintln(w, `{"id":1234567,"following":false}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -304,11 +298,11 @@ func TestAccountUnfollow(t *testing.T) {
ClientSecret: "bar", ClientSecret: "bar",
AccessToken: "zoo", AccessToken: "zoo",
}) })
rel, err := client.AccountUnfollow(context.Background(), "123") _, 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(context.Background(), "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)
} }
@ -327,7 +321,6 @@ func TestAccountBlock(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id":1234567,"blocking":true}`) fmt.Fprintln(w, `{"id":1234567,"blocking":true}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -360,7 +353,6 @@ func TestAccountUnblock(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id":1234567,"blocking":false}`) fmt.Fprintln(w, `{"id":1234567,"blocking":false}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -393,7 +385,6 @@ func TestAccountMute(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id":1234567,"muting":true}`) fmt.Fprintln(w, `{"id":1234567,"muting":true}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -426,7 +417,6 @@ func TestAccountUnmute(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id":1234567,"muting":false}`) fmt.Fprintln(w, `{"id":1234567,"muting":false}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -492,7 +482,6 @@ func TestAccountsSearch(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foobar"}, {"username": "barfoo"}]`) fmt.Fprintln(w, `[{"username": "foobar"}, {"username": "barfoo"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -528,7 +517,6 @@ func TestFollowRemoteUser(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"username": "zzz"}`) fmt.Fprintln(w, `{"username": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -560,7 +548,6 @@ func TestGetFollowRequests(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -646,7 +633,6 @@ func TestGetMutes(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()

View file

@ -27,7 +27,6 @@ func TestRegisterApp(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id": 123, "client_id": "foo", "client_secret": "bar"}`) fmt.Fprintln(w, `{"id": 123, "client_id": "foo", "client_secret": "bar"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -79,7 +78,6 @@ func TestRegisterAppWithCancel(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) {
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
fmt.Fprintln(w, `{"client_id": "foo", "client_secret": "bar"}`) fmt.Fprintln(w, `{"client_id": "foo", "client_secret": "bar"}`)
return
})) }))
defer ts.Close() defer ts.Close()

View file

@ -1,52 +0,0 @@
# mstdn
command line tool for mstdn.jp
## Usage
```
NAME:
mstdn - mastodon client
USAGE:
mstdn [global options] command [command options] [arguments...]
VERSION:
0.0.1
COMMANDS:
toot post toot
stream stream statuses
timeline show timeline
notification show notification
instance show instance information
account show account information
search search content
follow follow account
followers show followers
upload upload file
delete delete status
init initialize profile
mikami search mikami
xsearch cross search
help, h Shows a list of commands or help for one command
GLOBAL OPTIONS:
--profile value profile name
--help, -h show help
--version, -v print the version
```
## Installation
```
$ go get github.com/mattn/go-mastodon/cmd/mstdn
```
## License
MIT
## Author
Yasuhiro Matsumoto (a.k.a. mattn)

View file

@ -1,30 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/RasmusLindroth/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", textContent(account.Note))
fmt.Fprintf(c.App.Writer, "URL : %v\n", account.URL)
return nil
}

View file

@ -1,30 +0,0 @@
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)
}
}

View file

@ -1,23 +0,0 @@
package main
import (
"context"
"errors"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdDelete(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
if !c.Args().Present() {
return errors.New("arguments required")
}
for i := 0; i < c.NArg(); i++ {
err := client.DeleteStatus(context.Background(), mastodon.ID(c.Args().Get(i)))
if err != nil {
return err
}
}
return nil
}

View file

@ -1,41 +0,0 @@
package main
import (
"fmt"
"net/http"
"testing"
"github.com/urfave/cli"
)
func TestCmdDelete(t *testing.T) {
ok := false
f := func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/statuses/123":
fmt.Fprintln(w, `{}`)
ok = true
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
testWithServer(
f, func(app *cli.App) {
app.Run([]string{"mstdn", "delete", "122"})
},
)
if ok {
t.Fatal("something wrong to sequence to follow account")
}
ok = false
testWithServer(
f, func(app *cli.App) {
app.Run([]string{"mstdn", "delete", "123"})
},
)
if !ok {
t.Fatal("something wrong to sequence to follow account")
}
}

View file

@ -1,30 +0,0 @@
package main
import (
"context"
"errors"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdFollow(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
if !c.Args().Present() {
return errors.New("arguments required")
}
for i := 0; i < c.NArg(); i++ {
account, err := client.AccountsSearch(context.Background(), c.Args().Get(i), 1)
if err != nil {
return err
}
if len(account) == 0 {
continue
}
_, err = client.AccountFollow(context.Background(), account[0].ID)
if err != nil {
return err
}
}
return nil
}

View file

@ -1,70 +0,0 @@
package main
import (
"fmt"
"net/http"
"testing"
"github.com/urfave/cli"
)
func TestCmdFollow(t *testing.T) {
ok := false
testWithServer(
func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/accounts/search":
q := r.URL.Query().Get("q")
if q == "mattn" {
fmt.Fprintln(w, `[{"id": 123}]`)
return
} else if q == "different_id" {
fmt.Fprintln(w, `[{"id": 1234567}]`)
return
} else if q == "empty" {
fmt.Fprintln(w, `[]`)
return
}
case "/api/v1/accounts/123/follow":
fmt.Fprintln(w, `{"id": 123}`)
ok = true
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
},
func(app *cli.App) {
err := app.Run([]string{"mstdn", "follow", "mattn"})
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
},
func(app *cli.App) {
err := app.Run([]string{"mstdn", "follow"})
if err == nil {
t.Fatalf("should be fail: %v", err)
}
},
func(app *cli.App) {
err := app.Run([]string{"mstdn", "follow", "fail"})
if err == nil {
t.Fatalf("should be fail: %v", err)
}
},
func(app *cli.App) {
err := app.Run([]string{"mstdn", "follow", "empty"})
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
},
func(app *cli.App) {
err := app.Run([]string{"mstdn", "follow", "different_id"})
if err == nil {
t.Fatalf("should be fail: %v", err)
}
},
)
if !ok {
t.Fatal("something wrong to sequence to follow account")
}
}

View file

@ -1,40 +0,0 @@
package main
import (
"context"
"fmt"
"time"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdFollowers(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
config := c.App.Metadata["config"].(*mastodon.Config)
account, err := client.GetAccountCurrentUser(context.Background())
if err != nil {
return err
}
var followers []*mastodon.Account
var pg mastodon.Pagination
for {
fs, err := client.GetAccountFollowers(context.Background(), account.ID, &pg)
if err != nil {
return err
}
followers = append(followers, fs...)
if pg.MaxID == "" {
break
}
pg.SinceID = ""
pg.MinID = ""
time.Sleep(10 * time.Second)
}
s := newScreen(config)
for _, follower := range followers {
fmt.Fprintf(c.App.Writer, "%v,%v\n", follower.ID, s.acct(follower.Acct))
}
return nil
}

View file

@ -1,34 +0,0 @@
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":
w.Header().Set("Link", `<http://example.com?since_id=890>; rel="prev"`)
fmt.Fprintln(w, `[{"id": 234, "username": "ZZZ", "acct": "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)
}
}

View file

@ -1,44 +0,0 @@
package main
import (
"context"
"fmt"
"sort"
"github.com/RasmusLindroth/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)
if instance.Version != "" {
fmt.Fprintf(c.App.Writer, "Version : %s\n", instance.Version)
}
if instance.Thumbnail != "" {
fmt.Fprintf(c.App.Writer, "Thumbnail : %s\n", instance.Thumbnail)
}
if instance.URLs != nil {
var keys []string
for _, k := range instance.URLs {
keys = append(keys, k)
}
sort.Strings(keys)
for _, k := range keys {
fmt.Fprintf(c.App.Writer, "%s: %s\n", k, instance.URLs[k])
}
}
if instance.Stats != nil {
fmt.Fprintf(c.App.Writer, "User Count : %v\n", instance.Stats.UserCount)
fmt.Fprintf(c.App.Writer, "Status Count : %v\n", instance.Stats.StatusCount)
fmt.Fprintf(c.App.Writer, "Domain Count : %v\n", instance.Stats.DomainCount)
}
return nil
}

View file

@ -1,24 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdInstanceActivity(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
activities, err := client.GetInstanceActivity(context.Background())
if err != nil {
return err
}
for _, activity := range activities {
fmt.Fprintf(c.App.Writer, "Logins : %v\n", activity.Logins)
fmt.Fprintf(c.App.Writer, "Registrations : %v\n", activity.Registrations)
fmt.Fprintf(c.App.Writer, "Statuses : %v\n", activity.Statuses)
fmt.Fprintf(c.App.Writer, "Week : %v\n", activity.Week)
}
return nil
}

View file

@ -1,21 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdInstancePeers(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
peers, err := client.GetInstancePeers(context.Background())
if err != nil {
return err
}
for _, peer := range peers {
fmt.Fprintln(c.App.Writer, peer)
}
return nil
}

View file

@ -1,30 +0,0 @@
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)
}
}

View file

@ -1,9 +0,0 @@
package main
import (
"github.com/urfave/cli"
)
func cmdMikami(c *cli.Context) error {
return xSearch(c.App.Metadata["xsearch_url"].(string), "三上", c.App.Writer)
}

View file

@ -1,41 +0,0 @@
package main
import (
"bytes"
"fmt"
"net/http"
"strings"
"testing"
"github.com/urfave/cli"
)
func TestCmdMikami(t *testing.T) {
ok := false
buf := bytes.NewBuffer(nil)
testWithServer(
func(w http.ResponseWriter, r *http.Request) {
if r.URL.Query().Get("q") == "三上" {
ok = true
fmt.Fprintln(w, `<div class="post"><div class="mst_content"><a href="http://example.com/@test/1"><p>三上</p></a></div></div>`)
}
},
func(app *cli.App) {
app.Writer = buf
err := app.Run([]string{"mstdn", "mikami"})
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
},
)
if !ok {
t.Fatal("should be search Mikami")
}
result := buf.String()
if !strings.Contains(result, "http://example.com/@test/1") {
t.Fatalf("%q should be contained in output of search: %s", "http://example.com/@test/1", result)
}
if !strings.Contains(result, "三上") {
t.Fatalf("%q should be contained in output of search: %s", "三上", result)
}
}

View file

@ -1,29 +0,0 @@
package main
import (
"context"
"fmt"
"github.com/RasmusLindroth/go-mastodon"
"github.com/fatih/color"
"github.com/urfave/cli"
)
func cmdNotification(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
notifications, err := client.GetNotifications(context.Background(), nil)
if err != nil {
return err
}
for _, n := range notifications {
if n.Status != nil {
color.Set(color.FgHiRed)
fmt.Fprint(c.App.Writer, n.Account.Acct)
color.Set(color.Reset)
fmt.Fprintln(c.App.Writer, " "+n.Type)
s := n.Status
fmt.Fprintln(c.App.Writer, textContent(s.Content))
}
}
return nil
}

View file

@ -1,30 +0,0 @@
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)
}
}

View file

@ -1,47 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/RasmusLindroth/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)
config := c.App.Metadata["config"].(*mastodon.Config)
results, err := client.Search(context.Background(), argstr(c), false)
if err != nil {
return err
}
s := newScreen(config)
if len(results.Accounts) > 0 {
fmt.Fprintln(c.App.Writer, "===ACCOUNT===")
for _, result := range results.Accounts {
fmt.Fprintf(c.App.Writer, "%v,%v\n", result.ID, s.acct(result.Acct))
}
fmt.Fprintln(c.App.Writer)
}
if len(results.Statuses) > 0 {
fmt.Fprintln(c.App.Writer, "===STATUS===")
for _, result := range results.Statuses {
s.displayStatus(c.App.Writer, result)
}
fmt.Fprintln(c.App.Writer)
}
if len(results.Hashtags) > 0 {
fmt.Fprintln(c.App.Writer, "===HASHTAG===")
for _, result := range results.Hashtags {
fmt.Fprintf(c.App.Writer, "#%v\n", result)
}
fmt.Fprintln(c.App.Writer)
}
return nil
}

View file

@ -1,32 +0,0 @@
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/v2/search":
fmt.Fprintln(w, `{"accounts": [{"id": 234, "acct": "zzz"}], "statuses":[{"id": 345, "content": "yyy"}], "hashtags": [{"name": "www"}, {"name": "わろす"}]}`)
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
},
func(app *cli.App) {
app.Run([]string{"mstdn", "search", "zzz"})
},
)
for _, s := range []string{"zzz", "yyy", "www", "わろす"} {
if !strings.Contains(out, s) {
t.Fatalf("%q should be contained in output of command: %v", s, out)
}
}
}

View file

@ -1,113 +0,0 @@
package main
import (
"context"
"encoding/json"
"errors"
"os"
"os/signal"
"strings"
"text/template"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
// SimpleJSON is a struct for output JSON for data to be simple used
type SimpleJSON struct {
ID mastodon.ID `json:"id"`
Username string `json:"username"`
Acct string `json:"acct"`
Avatar string `json:"avatar"`
Content string `json:"content"`
}
func checkFlag(f ...bool) bool {
n := 0
for _, on := range f {
if on {
n++
}
}
return n > 1
}
func cmdStream(c *cli.Context) error {
asJSON := c.Bool("json")
asSimpleJSON := c.Bool("simplejson")
asFormat := c.String("template")
if checkFlag(asJSON, asSimpleJSON, asFormat != "") {
return errors.New("cannot speicify two or three options in --json/--simplejson/--template")
}
tx, err := template.New("mstdn").Funcs(template.FuncMap{
"nl": func(s string) string {
return s + "\n"
},
"text": textContent,
}).Parse(asFormat)
if err != nil {
return err
}
client := c.App.Metadata["client"].(*mastodon.Client)
config := c.App.Metadata["config"].(*mastodon.Config)
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
sc := make(chan os.Signal, 1)
signal.Notify(sc, os.Interrupt)
var q chan mastodon.Event
t := c.String("type")
if t == "public" {
q, err = client.StreamingPublic(ctx, false)
} else if t == "" || t == "public/local" {
q, err = client.StreamingPublic(ctx, true)
} else if strings.HasPrefix(t, "user:") {
q, err = client.StreamingUser(ctx)
} else if strings.HasPrefix(t, "hashtag:") {
q, err = client.StreamingHashtag(ctx, t[8:], false)
} else {
return errors.New("invalid type")
}
if err != nil {
return err
}
go func() {
<-sc
cancel()
}()
c.App.Metadata["signal"] = sc
s := newScreen(config)
for e := range q {
if asJSON {
json.NewEncoder(c.App.Writer).Encode(e)
} else if asSimpleJSON {
if t, ok := e.(*mastodon.UpdateEvent); ok {
json.NewEncoder(c.App.Writer).Encode(&SimpleJSON{
ID: t.Status.ID,
Username: t.Status.Account.Username,
Acct: t.Status.Account.Acct,
Avatar: t.Status.Account.AvatarStatic,
Content: textContent(t.Status.Content),
})
}
} else if asFormat != "" {
tx.ExecuteTemplate(c.App.Writer, "mstdn", e)
} else {
switch t := e.(type) {
case *mastodon.UpdateEvent:
s.displayStatus(c.App.Writer, t.Status)
case *mastodon.NotificationEvent:
// TODO s.displayStatus(c.App.Writer, t.Notification.Status)
case *mastodon.ErrorEvent:
s.displayError(c.App.Writer, t)
}
}
}
return nil
}

View file

@ -1,84 +0,0 @@
package main
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"os"
"strings"
"testing"
"time"
"github.com/RasmusLindroth/go-mastodon"
)
func TestCmdStream(t *testing.T) {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/api/v1/streaming/public/local" {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
}
f, _ := w.(http.Flusher)
fmt.Fprintln(w, `
event: update
data: {"content": "foo", "account":{"acct":"FOO"}}
`)
f.Flush()
fmt.Fprintln(w, `
event: update
data: {"content": "bar", "account":{"acct":"BAR"}}
`)
f.Flush()
return
}))
defer ts.Close()
config := &mastodon.Config{
Server: ts.URL,
ClientID: "foo",
ClientSecret: "bar",
AccessToken: "zoo",
}
client := mastodon.NewClient(config)
var buf bytes.Buffer
app := makeApp()
app.Writer = &buf
app.Metadata = map[string]interface{}{
"client": client,
"config": config,
}
stop := func() {
time.Sleep(5 * time.Second)
if sig, ok := app.Metadata["signal"]; ok {
sig.(chan os.Signal) <- os.Interrupt
return
}
panic("timeout")
}
var out string
go stop()
app.Run([]string{"mstdn", "stream"})
out = buf.String()
if !strings.Contains(out, "FOO@") {
t.Fatalf("%q should be contained in output of command: %v", "FOO@", out)
}
if !strings.Contains(out, "foo") {
t.Fatalf("%q should be contained in output of command: %v", "foo", out)
}
go stop()
app.Run([]string{"mstdn", "stream", "--simplejson"})
out = buf.String()
if !strings.Contains(out, "FOO@") {
t.Fatalf("%q should be contained in output of command: %v", "FOO@", out)
}
if !strings.Contains(out, "foo") {
t.Fatalf("%q should be contained in output of command: %v", "foo", out)
}
}

View file

@ -1,41 +0,0 @@
package main
import (
"bytes"
"net/http"
"net/http/httptest"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func testWithServer(h http.HandlerFunc, testFuncs ...func(*cli.App)) string {
ts := httptest.NewServer(h)
defer ts.Close()
cli.OsExiter = func(n int) {}
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,
"config": &mastodon.Config{
Server: "https://example.com",
},
"xsearch_url": ts.URL,
}
for _, f := range testFuncs {
f(app)
}
return buf.String()
}

View file

@ -1,68 +0,0 @@
package main
import (
"context"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdTimeline(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
config := c.App.Metadata["config"].(*mastodon.Config)
timeline, err := client.GetTimelineHome(context.Background(), nil)
if err != nil {
return err
}
s := newScreen(config)
for i := len(timeline) - 1; i >= 0; i-- {
s.displayStatus(c.App.Writer, timeline[i])
}
return nil
}
func cmdTimelineHome(c *cli.Context) error {
return cmdTimeline(c)
}
func cmdTimelinePublic(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
config := c.App.Metadata["config"].(*mastodon.Config)
timeline, err := client.GetTimelinePublic(context.Background(), false, nil)
if err != nil {
return err
}
s := newScreen(config)
for i := len(timeline) - 1; i >= 0; i-- {
s.displayStatus(c.App.Writer, timeline[i])
}
return nil
}
func cmdTimelineLocal(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
config := c.App.Metadata["config"].(*mastodon.Config)
timeline, err := client.GetTimelinePublic(context.Background(), true, nil)
if err != nil {
return err
}
s := newScreen(config)
for i := len(timeline) - 1; i >= 0; i-- {
s.displayStatus(c.App.Writer, timeline[i])
}
return nil
}
func cmdTimelineDirect(c *cli.Context) error {
client := c.App.Metadata["client"].(*mastodon.Client)
config := c.App.Metadata["config"].(*mastodon.Config)
timeline, err := client.GetTimelineDirect(context.Background(), nil)
if err != nil {
return err
}
s := newScreen(config)
for i := len(timeline) - 1; i >= 0; i-- {
s.displayStatus(c.App.Writer, timeline[i])
}
return nil
}

View file

@ -1,52 +0,0 @@
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": "home"}]`)
return
case "/api/v1/timelines/public":
fmt.Fprintln(w, `[{"content": "public"}]`)
return
case "/api/v1/conversations":
fmt.Fprintln(w, `[{"id": "4", "unread":false, "last_status" : {"content": "direct"}}]`)
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
},
func(app *cli.App) {
app.Run([]string{"mstdn", "timeline"})
app.Run([]string{"mstdn", "timeline-home"})
app.Run([]string{"mstdn", "timeline-public"})
app.Run([]string{"mstdn", "timeline-local"})
app.Run([]string{"mstdn", "timeline-direct"})
},
)
want := strings.Join([]string{
"@example.com",
"home",
"@example.com",
"home",
"@example.com",
"public",
"@example.com",
"public",
"@example.com",
"direct",
}, "\n") + "\n"
if !strings.Contains(out, want) {
t.Fatalf("%q should be contained in output of command: %v", want, out)
}
}

View file

@ -1,33 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/RasmusLindroth/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 {
return 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,
InReplyToID: mastodon.ID(fmt.Sprint(c.String("i"))),
})
return err
}

View file

@ -1,52 +0,0 @@
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)
}
}
func TestCmdTootFileNotFound(t *testing.T) {
var err error
testWithServer(
func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/statuses":
fmt.Fprintln(w, `{"id": 2345}`)
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
},
func(app *cli.App) {
err = app.Run([]string{"mstdn", "toot", "-ff", "not-found"})
},
)
if err == nil {
t.Fatal("should be fail")
}
}

View file

@ -1,33 +0,0 @@
package main
import (
"context"
"errors"
"fmt"
"github.com/RasmusLindroth/go-mastodon"
"github.com/urfave/cli"
)
func cmdUpload(c *cli.Context) error {
if !c.Args().Present() {
return errors.New("arguments required")
}
client := c.App.Metadata["client"].(*mastodon.Client)
for i := 0; i < c.NArg(); i++ {
attachment, err := client.UploadMedia(context.Background(), c.Args().Get(i))
if err != nil {
return err
}
if i > 0 {
fmt.Fprintln(c.App.Writer)
}
fmt.Fprintf(c.App.Writer, "ID : %v\n", attachment.ID)
fmt.Fprintf(c.App.Writer, "Type : %v\n", attachment.Type)
fmt.Fprintf(c.App.Writer, "URL : %v\n", attachment.URL)
fmt.Fprintf(c.App.Writer, "RemoteURL : %v\n", attachment.RemoteURL)
fmt.Fprintf(c.App.Writer, "PreviewURL: %v\n", attachment.PreviewURL)
fmt.Fprintf(c.App.Writer, "TextURL : %v\n", attachment.TextURL)
}
return nil
}

View file

@ -1,30 +0,0 @@
package main
import (
"fmt"
"net/http"
"strings"
"testing"
"github.com/urfave/cli"
)
func TestCmdUpload(t *testing.T) {
out := testWithServer(
func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/media":
fmt.Fprintln(w, `{"id": 123}`)
return
}
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
},
func(app *cli.App) {
app.Run([]string{"mstdn", "upload", "../../testdata/logo.png"})
},
)
if !strings.Contains(out, "123") {
t.Fatalf("%q should be contained in output of command: %v", "123", out)
}
}

View file

@ -1,38 +0,0 @@
package main
import (
"fmt"
"io"
"net/url"
"github.com/PuerkitoBio/goquery"
"github.com/urfave/cli"
)
func cmdXSearch(c *cli.Context) error {
return xSearch(c.App.Metadata["xsearch_url"].(string), c.Args().First(), c.App.Writer)
}
func xSearch(xsearchRawurl, query string, w io.Writer) error {
u, err := url.Parse(xsearchRawurl)
if err != nil {
return err
}
params := url.Values{}
params.Set("q", query)
u.RawQuery = params.Encode()
doc, err := goquery.NewDocument(u.String())
if err != nil {
return err
}
doc.Find(".post").Each(func(n int, elem *goquery.Selection) {
href, ok := elem.Find(".mst_content a").Attr("href")
if !ok {
return
}
text := elem.Find(".mst_content p").Text()
fmt.Fprintf(w, "%s\n", href)
fmt.Fprintf(w, "%s\n\n", text)
})
return nil
}

View file

@ -1,76 +0,0 @@
package main
import (
"bytes"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"testing"
"github.com/urfave/cli"
)
func TestCmdXSearch(t *testing.T) {
testWithServer(
func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, `<div class="post"><div class="mst_content"><a href="http://example.com/@test/1"><p>test status</p></a></div></div>`)
},
func(app *cli.App) {
err := app.Run([]string{"mstdn", "xsearch", "test"})
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
},
)
}
func TestXSearch(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), 9999)
return
} else if r.URL.Query().Get("q") == "empty" {
fmt.Fprintln(w, `<div class="post"><div class="mst_content"><a><p>test status</p></a></div></div>`)
return
}
fmt.Fprintln(w, `<div class="post"><div class="mst_content"><a href="http://example.com/@test/1"><p>test status</p></a></div></div>`)
}))
defer ts.Close()
err := xSearch(":", "", nil)
if err == nil {
t.Fatalf("should be fail: %v", err)
}
err = xSearch(ts.URL, "", nil)
if err == nil {
t.Fatalf("should be fail: %v", err)
}
buf := bytes.NewBuffer(nil)
err = xSearch(ts.URL, "empty", buf)
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
result := buf.String()
if result != "" {
t.Fatalf("the search result should be empty: %s", result)
}
buf = bytes.NewBuffer(nil)
err = xSearch(ts.URL, "test", buf)
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
result = buf.String()
if !strings.Contains(result, "http://example.com/@test/1") {
t.Fatalf("%q should be contained in output of search: %s", "http://example.com/@test/1", result)
}
if !strings.Contains(result, "test status") {
t.Fatalf("%q should be contained in output of search: %s", "test status", result)
}
}

View file

@ -1,411 +0,0 @@
package main
import (
"bufio"
"bytes"
"context"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"net/url"
"os"
"path/filepath"
"runtime"
"strings"
"github.com/RasmusLindroth/go-mastodon"
"github.com/fatih/color"
"github.com/mattn/go-tty"
"github.com/urfave/cli"
"golang.org/x/net/html"
)
func readFile(filename string) ([]byte, error) {
if filename == "-" {
return ioutil.ReadAll(os.Stdin)
}
return ioutil.ReadFile(filename)
}
func textContent(s string) string {
doc, err := html.Parse(strings.NewReader(s))
if err != nil {
return s
}
var buf bytes.Buffer
var extractText func(node *html.Node, w *bytes.Buffer)
extractText = func(node *html.Node, w *bytes.Buffer) {
if node.Type == html.TextNode {
data := strings.Trim(node.Data, "\r\n")
if data != "" {
w.WriteString(data)
}
}
for c := node.FirstChild; c != nil; c = c.NextSibling {
extractText(c, w)
}
if node.Type == html.ElementNode {
name := strings.ToLower(node.Data)
if name == "br" {
w.WriteString("\n")
}
}
}
extractText(doc, &buf)
return buf.String()
}
var (
readUsername = func() (string, error) {
b, _, err := bufio.NewReader(os.Stdin).ReadLine()
if err != nil {
return "", err
}
return string(b), nil
}
readPassword func() (string, error)
)
func prompt() (string, string, error) {
fmt.Print("E-Mail: ")
email, err := readUsername()
if err != nil {
return "", "", err
}
fmt.Print("Password: ")
var password string
if readPassword == nil {
var t *tty.TTY
t, err = tty.Open()
if err != nil {
return "", "", err
}
defer t.Close()
password, err = t.ReadPassword()
} else {
password, err = readPassword()
}
if err != nil {
return "", "", err
}
return email, password, nil
}
func configFile(c *cli.Context) (string, error) {
dir := os.Getenv("HOME")
if runtime.GOOS == "windows" {
dir = os.Getenv("APPDATA")
if dir == "" {
dir = filepath.Join(os.Getenv("USERPROFILE"), "Application Data", "mstdn")
}
dir = filepath.Join(dir, "mstdn")
} else {
dir = filepath.Join(dir, ".config", "mstdn")
}
if err := os.MkdirAll(dir, 0700); err != nil {
return "", err
}
var file string
profile := c.String("profile")
if profile != "" {
file = filepath.Join(dir, "settings-"+profile+".json")
} else {
file = filepath.Join(dir, "settings.json")
}
return file, nil
}
func getConfig(c *cli.Context) (string, *mastodon.Config, error) {
file, err := configFile(c)
if err != nil {
return "", nil, err
}
b, err := ioutil.ReadFile(file)
if err != nil && !os.IsNotExist(err) {
return "", nil, err
}
config := &mastodon.Config{
Server: "https://mstdn.jp",
ClientID: "1e463436008428a60ed14ff1f7bc0b4d923e14fc4a6827fa99560b0c0222612f",
ClientSecret: "72b63de5bc11111a5aa1a7b690672d78ad6a207ce32e16ea26115048ec5d234d",
}
if err == nil {
err = json.Unmarshal(b, &config)
if err != nil {
return "", nil, fmt.Errorf("could not unmarshal %v: %v", file, err)
}
}
return file, config, nil
}
func authenticate(client *mastodon.Client, config *mastodon.Config, file string) error {
email, password, err := prompt()
if err != nil {
return err
}
err = client.Authenticate(context.Background(), email, password)
if err != nil {
return err
}
b, err := json.MarshalIndent(config, "", " ")
if err != nil {
return fmt.Errorf("failed to store file: %v", err)
}
err = ioutil.WriteFile(file, b, 0700)
if err != nil {
return fmt.Errorf("failed to store file: %v", err)
}
return nil
}
func argstr(c *cli.Context) string {
a := []string{}
for i := 0; i < c.NArg(); i++ {
a = append(a, c.Args().Get(i))
}
return strings.Join(a, " ")
}
func fatalIf(err error) {
if err == nil {
return
}
fmt.Fprintf(os.Stderr, "%s: %v\n", os.Args[0], err)
os.Exit(1)
}
func makeApp() *cli.App {
app := cli.NewApp()
app.Name = "mstdn"
app.Usage = "mastodon client"
app.Version = "0.0.1"
app.Flags = []cli.Flag{
cli.StringFlag{
Name: "profile",
Usage: "profile name",
Value: "",
},
}
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: "",
},
cli.StringFlag{
Name: "i",
Usage: "in-reply-to",
Value: "",
},
},
Action: cmdToot,
},
{
Name: "stream",
Usage: "stream statuses",
Flags: []cli.Flag{
cli.StringFlag{
Name: "type",
Usage: "stream type (public,public/local,user:NAME,hashtag:TAG)",
},
cli.BoolFlag{
Name: "json",
Usage: "output JSON",
},
cli.BoolFlag{
Name: "simplejson",
Usage: "output simple JSON",
},
cli.StringFlag{
Name: "template",
Usage: "output with tamplate format",
},
},
Action: cmdStream,
},
{
Name: "timeline",
Usage: "show timeline",
Action: cmdTimeline,
},
{
Name: "timeline-home",
Usage: "show timeline home",
Action: cmdTimelineHome,
},
{
Name: "timeline-local",
Usage: "show timeline local",
Action: cmdTimelineLocal,
},
{
Name: "timeline-public",
Usage: "show timeline public",
Action: cmdTimelinePublic,
},
{
Name: "timeline-direct",
Usage: "show timeline direct",
Action: cmdTimelineDirect,
},
{
Name: "notification",
Usage: "show notification",
Action: cmdNotification,
},
{
Name: "instance",
Usage: "show instance information",
Action: cmdInstance,
},
{
Name: "instance_activity",
Usage: "show instance activity information",
Action: cmdInstanceActivity,
},
{
Name: "instance_peers",
Usage: "show instance peers information",
Action: cmdInstancePeers,
},
{
Name: "account",
Usage: "show account information",
Action: cmdAccount,
},
{
Name: "search",
Usage: "search content",
Action: cmdSearch,
},
{
Name: "follow",
Usage: "follow account",
Action: cmdFollow,
},
{
Name: "followers",
Usage: "show followers",
Action: cmdFollowers,
},
{
Name: "upload",
Usage: "upload file",
Action: cmdUpload,
},
{
Name: "delete",
Usage: "delete status",
Action: cmdDelete,
},
{
Name: "init",
Usage: "initialize profile",
Action: func(c *cli.Context) error { return nil },
},
{
Name: "mikami",
Usage: "search mikami",
Action: cmdMikami,
},
{
Name: "xsearch",
Usage: "cross search",
Action: cmdXSearch,
},
}
app.Setup()
return app
}
type screen struct {
host string
}
func newScreen(config *mastodon.Config) *screen {
var host string
u, err := url.Parse(config.Server)
if err == nil {
host = u.Host
}
return &screen{host}
}
func (s *screen) acct(a string) string {
if !strings.Contains(a, "@") {
a += "@" + s.host
}
return a
}
func (s *screen) displayError(w io.Writer, e error) {
color.Set(color.FgYellow)
fmt.Fprintln(w, e.Error())
color.Set(color.Reset)
}
func (s *screen) displayStatus(w io.Writer, t *mastodon.Status) {
if t == nil {
return
}
if t.Reblog != nil {
color.Set(color.FgHiRed)
fmt.Fprint(w, s.acct(t.Account.Acct))
color.Set(color.Reset)
fmt.Fprint(w, " reblogged ")
color.Set(color.FgHiBlue)
fmt.Fprintln(w, s.acct(t.Reblog.Account.Acct))
fmt.Fprintln(w, textContent(t.Reblog.Content))
color.Set(color.Reset)
} else {
color.Set(color.FgHiRed)
fmt.Fprintln(w, s.acct(t.Account.Acct))
color.Set(color.Reset)
fmt.Fprintln(w, textContent(t.Content))
}
}
func run() int {
app := makeApp()
app.Before = func(c *cli.Context) error {
if c.Args().Get(0) == "init" {
file, err := configFile(c)
if err != nil {
return err
}
os.Remove(file)
}
file, config, err := getConfig(c)
if err != nil {
return err
}
client := mastodon.NewClient(config)
client.UserAgent = "mstdn"
app.Metadata = map[string]interface{}{
"client": client,
"config": config,
"xsearch_url": "http://mastodonsearch.jp/cross/",
}
if config.AccessToken == "" {
return authenticate(client, config, file)
}
return nil
}
fatalIf(app.Run(os.Args))
return 0
}
func main() {
os.Exit(run())
}

View file

@ -1,135 +0,0 @@
package main
import (
"encoding/json"
"flag"
"io/ioutil"
"os"
"testing"
"github.com/urfave/cli"
)
func TestReadFileFile(t *testing.T) {
b, err := readFile("main.go")
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Fatalf("should read something: %v", err)
}
}
func TestReadFileStdin(t *testing.T) {
f, err := os.Open("main.go")
if err != nil {
t.Fatal(err)
}
defer f.Close()
stdin := os.Stdin
os.Stdin = f
defer func() {
os.Stdin = stdin
}()
b, err := readFile("-")
if err != nil {
t.Fatal(err)
}
if len(b) == 0 {
t.Fatalf("should read something: %v", err)
}
}
func TestTextContent(t *testing.T) {
tests := []struct {
input string
want string
}{
{input: "", want: ""},
{input: "<p>foo</p>", want: "foo"},
{input: "<p>foo<span>\nbar\n</span>baz</p>", want: "foobarbaz"},
{input: "<p>foo<span>\nbar<br></span>baz</p>", want: "foobar\nbaz"},
}
for _, test := range tests {
got := textContent(test.input)
if got != test.want {
t.Fatalf("want %q but %q", test.want, got)
}
}
}
func TestGetConfig(t *testing.T) {
tmpdir, err := ioutil.TempDir("", "mstdn")
if err != nil {
t.Fatal(err)
}
home := os.Getenv("HOME")
appdata := os.Getenv("APPDATA")
os.Setenv("HOME", tmpdir)
os.Setenv("APPDATA", tmpdir)
defer func() {
os.RemoveAll(tmpdir)
os.Setenv("HOME", home)
os.Setenv("APPDATA", appdata)
}()
app := makeApp()
set := flag.NewFlagSet("test", 0)
set.Parse([]string{"mstdn", "-profile", ""})
c := cli.NewContext(app, set, nil)
file, config, err := getConfig(c)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(file); err == nil {
t.Fatal("should not exists")
}
if config.AccessToken != "" {
t.Fatalf("should be empty: %v", config.AccessToken)
}
if config.ClientID == "" {
t.Fatalf("should not be empty")
}
if config.ClientSecret == "" {
t.Fatalf("should not be empty")
}
config.AccessToken = "foo"
b, err := json.MarshalIndent(config, "", " ")
if err != nil {
t.Fatal(err)
}
err = ioutil.WriteFile(file, b, 0700)
if err != nil {
t.Fatal(err)
}
file, config, err = getConfig(c)
if err != nil {
t.Fatal(err)
}
if _, err := os.Stat(file); err != nil {
t.Fatalf("should exists: %v", err)
}
if got := config.AccessToken; got != "foo" {
t.Fatalf("want %q but %q", "foo", got)
}
}
func TestPrompt(t *testing.T) {
readUsername = func() (string, error) {
return "foo", nil
}
readPassword = func() (string, error) {
return "bar", nil
}
username, password, err := prompt()
if err != nil {
t.Fatal(err)
}
if username != "foo" {
t.Fatalf("want %q but %q", "foo", username)
}
if password != "bar" {
t.Fatalf("want %q but %q", "bar", password)
}
}

View file

@ -6,7 +6,7 @@ import (
"log" "log"
"time" "time"
"github.com/RasmusLindroth/go-mastodon" "github.com/RasmusLindroth/tut3/mastodon"
) )
func ExampleRegisterApp() { func ExampleRegisterApp() {

View file

@ -14,7 +14,6 @@ import (
func TestGetFilters(t *testing.T) { func TestGetFilters(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) {
fmt.Fprintln(w, `[{"id": "6191", "phrase": "rust", "context": ["home"], "whole_word": true, "expires_at": "2019-05-21T13:47:31.333Z", "irreversible": false}, {"id": "5580", "phrase": "@twitter.com", "context": ["home", "notifications", "public", "thread"], "whole_word": false, "expires_at": null, "irreversible": true}]`) fmt.Fprintln(w, `[{"id": "6191", "phrase": "rust", "context": ["home"], "whole_word": true, "expires_at": "2019-05-21T13:47:31.333Z", "irreversible": false}, {"id": "5580", "phrase": "@twitter.com", "context": ["home", "notifications", "public", "thread"], "whole_word": false, "expires_at": null, "irreversible": true}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -85,7 +84,6 @@ func TestGetFilter(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id": "1", "phrase": "rust", "context": ["home"], "whole_word": true, "expires_at": "2019-05-21T13:47:31.333Z", "irreversible": false}`) fmt.Fprintln(w, `{"id": "1", "phrase": "rust", "context": ["home"], "whole_word": true, "expires_at": "2019-05-21T13:47:31.333Z", "irreversible": false}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -328,7 +326,6 @@ func TestDeleteFilter(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()

16
go.mod
View file

@ -1,16 +0,0 @@
module github.com/RasmusLindroth/go-mastodon
go 1.12
require (
github.com/PuerkitoBio/goquery v1.8.0
github.com/cpuguy83/go-md2man/v2 v2.0.1 // indirect
github.com/fatih/color v1.13.0
github.com/gorilla/websocket v1.5.0
github.com/mattn/go-colorable v0.1.11 // indirect
github.com/mattn/go-tty v0.0.3
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80
github.com/urfave/cli v1.22.5
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b // indirect
)

53
go.sum
View file

@ -1,53 +0,0 @@
github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU=
github.com/PuerkitoBio/goquery v1.8.0 h1:PJTF7AmFCFKk1N6V6jmKfrNH9tV5pNE6lZMkG0gta/U=
github.com/PuerkitoBio/goquery v1.8.0/go.mod h1:ypIiRMtY7COPGk+I/YbZLbxsxn9g5ejnI2HSMtkjZvI=
github.com/andybalholm/cascadia v1.3.1 h1:nhxRkql1kdYCc8Snf7D5/D3spOX+dBgjA6u8x004T2c=
github.com/andybalholm/cascadia v1.3.1/go.mod h1:R4bJ1UQfqADjvDa4P6HZHLh/3OxWWEqc0Sk8XGwHqvA=
github.com/cpuguy83/go-md2man/v2 v2.0.0-20190314233015-f79a8a8ca69d/go.mod h1:maD7wRr/U5Z6m/iR4s+kqSMx2CaBsrgA7czyZG/E6dU=
github.com/cpuguy83/go-md2man/v2 v2.0.1 h1:r/myEWzV9lfsM1tFLgDyu0atFtJ1fXn261LKYj/3DxU=
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
github.com/fatih/color v1.13.0 h1:8LOYc1KYPPmyKMuN8QV2DNRWNbLo6LZ0iLs8+mlH53w=
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
github.com/gorilla/websocket v1.5.0 h1:PPwGk2jz7EePpoHN/+ClbZu8SPxiqlu12wZP/3sWmnc=
github.com/gorilla/websocket v1.5.0/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s=
github.com/mattn/go-isatty v0.0.10/go.mod h1:qgIWMr58cqv1PHHyhnkY9lrL7etaEgOFcMEpPG5Rm84=
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/mattn/go-runewidth v0.0.6/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI=
github.com/mattn/go-tty v0.0.3 h1:5OfyWorkyO7xP52Mq7tB36ajHDG5OHrmBGIS/DtakQI=
github.com/mattn/go-tty v0.0.3/go.mod h1:ihxohKRERHTVzN+aSVRwACLCeqIoZAWpoICkkvrWyR0=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk=
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80 h1:nrZ3ySNYwJbSpD6ce9duiP+QkD3JuLCcWkdaehUS/3Y=
github.com/tomnomnom/linkheader v0.0.0-20180905144013-02ca5825eb80/go.mod h1:iFyPdL66DjUD96XmzVL3ZntbzcflLnznH0fr99w5VqE=
github.com/urfave/cli v1.22.5 h1:lNq9sAHXK2qfdI8W+GRItjCEkI+2oR4d+MEHy1CKXoU=
github.com/urfave/cli v1.22.5/go.mod h1:Gos4lmkARVdJ6EkW0WaNv/tZAAMe9V7XWyB60NtXRu0=
golang.org/x/net v0.0.0-20210916014120-12bc252f5db8/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3 h1:VrJZAjbekhoRn7n5FBujY31gboH+iB3pdLxn3gE9FjU=
golang.org/x/net v0.0.0-20211101193420-4a448f8816b3/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20191008105621-543471e840be/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=

View file

@ -15,7 +15,6 @@ func TestGetLists(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"id": "1", "title": "foo"}, {"id": "2", "title": "bar"}]`) fmt.Fprintln(w, `[{"id": "1", "title": "foo"}, {"id": "2", "title": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -47,7 +46,6 @@ func TestGetAccountLists(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"id": "1", "title": "foo"}, {"id": "2", "title": "bar"}]`) fmt.Fprintln(w, `[{"id": "1", "title": "foo"}, {"id": "2", "title": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -83,7 +81,6 @@ func TestGetListAccounts(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -119,7 +116,6 @@ func TestGetList(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id": "1", "title": "foo"}`) fmt.Fprintln(w, `{"id": "1", "title": "foo"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -129,11 +125,11 @@ func TestGetList(t *testing.T) {
ClientSecret: "bar", ClientSecret: "bar",
AccessToken: "zoo", AccessToken: "zoo",
}) })
list, err := client.GetList(context.Background(), "2") _, err := client.GetList(context.Background(), "2")
if err == nil { if err == nil {
t.Fatalf("should be fail: %v", err) t.Fatalf("should be fail: %v", err)
} }
list, err = client.GetList(context.Background(), "1") list, err := client.GetList(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)
} }
@ -149,7 +145,6 @@ func TestCreateList(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id": "1", "title": "foo"}`) fmt.Fprintln(w, `{"id": "1", "title": "foo"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -159,11 +154,11 @@ func TestCreateList(t *testing.T) {
ClientSecret: "bar", ClientSecret: "bar",
AccessToken: "zoo", AccessToken: "zoo",
}) })
list, err := client.CreateList(context.Background(), "") _, err := client.CreateList(context.Background(), "")
if err == nil { if err == nil {
t.Fatalf("should be fail: %v", err) t.Fatalf("should be fail: %v", err)
} }
list, err = client.CreateList(context.Background(), "foo") list, err := client.CreateList(context.Background(), "foo")
if err != nil { if err != nil {
t.Fatalf("should not be fail: %v", err) t.Fatalf("should not be fail: %v", err)
} }
@ -183,7 +178,6 @@ func TestRenameList(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id": "1", "title": "bar"}`) fmt.Fprintln(w, `{"id": "1", "title": "bar"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -193,11 +187,11 @@ func TestRenameList(t *testing.T) {
ClientSecret: "bar", ClientSecret: "bar",
AccessToken: "zoo", AccessToken: "zoo",
}) })
list, err := client.RenameList(context.Background(), "2", "bar") _, err := client.RenameList(context.Background(), "2", "bar")
if err == nil { if err == nil {
t.Fatalf("should be fail: %v", err) t.Fatalf("should be fail: %v", err)
} }
list, err = client.RenameList(context.Background(), "1", "bar") list, err := client.RenameList(context.Background(), "1", "bar")
if err != nil { if err != nil {
t.Fatalf("should not be fail: %v", err) t.Fatalf("should not be fail: %v", err)
} }
@ -216,7 +210,6 @@ func TestDeleteList(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()
@ -246,7 +239,6 @@ func TestAddToList(t *testing.T) {
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()
@ -272,7 +264,6 @@ func TestRemoveFromList(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()

View file

@ -96,7 +96,6 @@ func TestAuthenticate(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"access_token": "zoo"}`) fmt.Fprintln(w, `{"access_token": "zoo"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -124,7 +123,6 @@ func TestAuthenticate(t *testing.T) {
func TestAuthenticateWithCancel(t *testing.T) { func TestAuthenticateWithCancel(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) {
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
return
})) }))
defer ts.Close() defer ts.Close()
@ -151,7 +149,6 @@ func TestPostStatus(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"access_token": "zoo"}`) fmt.Fprintln(w, `{"access_token": "zoo"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -184,7 +181,6 @@ func TestPostStatus(t *testing.T) {
func TestPostStatusWithCancel(t *testing.T) { func TestPostStatusWithCancel(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) {
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
return
})) }))
defer ts.Close() defer ts.Close()
@ -319,7 +315,6 @@ func TestPostStatusParams(t *testing.T) {
func TestGetTimelineHome(t *testing.T) { func TestGetTimelineHome(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) {
fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`) fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -359,7 +354,6 @@ func TestGetTimelineHome(t *testing.T) {
func TestGetTimelineHomeWithCancel(t *testing.T) { func TestGetTimelineHomeWithCancel(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) {
time.Sleep(3 * time.Second) time.Sleep(3 * time.Second)
return
})) }))
defer ts.Close() defer ts.Close()

View file

@ -28,7 +28,6 @@ func TestGetNotifications(t *testing.T) {
return return
} }
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
})) }))
defer ts.Close() defer ts.Close()
@ -76,7 +75,6 @@ func TestPushSubscription(t *testing.T) {
return return
} }
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return
})) }))
defer ts.Close() defer ts.Close()

View file

@ -15,7 +15,6 @@ func TestGetReports(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"id": 122, "action_taken": false}, {"id": 123, "action_taken": true}]`) fmt.Fprintln(w, `[{"id": 122, "action_taken": false}, {"id": 123, "action_taken": true}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -55,7 +54,6 @@ func TestReport(t *testing.T) {
} else { } else {
fmt.Fprintln(w, `{"id": 1234, "action_taken": true}`) fmt.Fprintln(w, `{"id": 1234, "action_taken": true}`)
} }
return
})) }))
defer ts.Close() defer ts.Close()
@ -65,11 +63,11 @@ func TestReport(t *testing.T) {
ClientSecret: "bar", ClientSecret: "bar",
AccessToken: "zoo", AccessToken: "zoo",
}) })
rp, err := client.Report(context.Background(), "121", nil, "") _, err := client.Report(context.Background(), "121", nil, "")
if err == nil { if err == nil {
t.Fatalf("should be fail: %v", err) t.Fatalf("should be fail: %v", err)
} }
rp, err = client.Report(context.Background(), "122", nil, "") rp, err := client.Report(context.Background(), "122", nil, "")
if err != nil { if err != nil {
t.Fatalf("should not be fail: %v", err) t.Fatalf("should not be fail: %v", err)
} }

View file

@ -28,10 +28,10 @@ type Status struct {
RepliesCount int64 `json:"replies_count"` RepliesCount int64 `json:"replies_count"`
ReblogsCount int64 `json:"reblogs_count"` ReblogsCount int64 `json:"reblogs_count"`
FavouritesCount int64 `json:"favourites_count"` FavouritesCount int64 `json:"favourites_count"`
Reblogged interface{} `json:"reblogged"` Reblogged bool `json:"reblogged"`
Favourited interface{} `json:"favourited"` Favourited bool `json:"favourited"`
Bookmarked interface{} `json:"bookmarked"` Bookmarked bool `json:"bookmarked"`
Muted interface{} `json:"muted"` Muted bool `json:"muted"`
Sensitive bool `json:"sensitive"` Sensitive bool `json:"sensitive"`
SpoilerText string `json:"spoiler_text"` SpoilerText string `json:"spoiler_text"`
Visibility string `json:"visibility"` Visibility string `json:"visibility"`
@ -42,7 +42,7 @@ type Status struct {
Poll *Poll `json:"poll"` Poll *Poll `json:"poll"`
Application Application `json:"application"` Application Application `json:"application"`
Language string `json:"language"` Language string `json:"language"`
Pinned interface{} `json:"pinned"` Pinned bool `json:"pinned"`
} }
// Context hold information for mastodon context. // Context hold information for mastodon context.

View file

@ -12,7 +12,6 @@ import (
func TestGetFavourites(t *testing.T) { func TestGetFavourites(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) {
fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`) fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -40,7 +39,6 @@ func TestGetFavourites(t *testing.T) {
func TestGetBookmarks(t *testing.T) { func TestGetBookmarks(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) {
fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`) fmt.Fprintln(w, `[{"content": "foo"}, {"content": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -72,7 +70,6 @@ func TestGetStatus(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz", "emojis":[{"shortcode":"💩", "url":"http://example.com", "static_url": "http://example.com/static"}]}`) fmt.Fprintln(w, `{"content": "zzz", "emojis":[{"shortcode":"💩", "url":"http://example.com", "static_url": "http://example.com/static"}]}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -114,7 +111,6 @@ func TestGetStatusCard(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"title": "zzz"}`) fmt.Fprintln(w, `{"title": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -144,7 +140,6 @@ func TestGetStatusContext(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"ancestors": [{"content": "zzz"},{"content": "bbb"}]}`) fmt.Fprintln(w, `{"ancestors": [{"content": "zzz"},{"content": "bbb"}]}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -183,7 +178,6 @@ func TestGetRebloggedBy(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -219,7 +213,6 @@ func TestGetFavouritedBy(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`) fmt.Fprintln(w, `[{"username": "foo"}, {"username": "bar"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -255,7 +248,6 @@ func TestReblog(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz"}`) fmt.Fprintln(w, `{"content": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -285,7 +277,6 @@ func TestUnreblog(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz"}`) fmt.Fprintln(w, `{"content": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -315,7 +306,6 @@ func TestFavourite(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz"}`) fmt.Fprintln(w, `{"content": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -345,7 +335,6 @@ func TestUnfavourite(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz"}`) fmt.Fprintln(w, `{"content": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -375,7 +364,6 @@ func TestBookmark(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz"}`) fmt.Fprintln(w, `{"content": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -405,7 +393,6 @@ func TestUnbookmark(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"content": "zzz"}`) fmt.Fprintln(w, `{"content": "zzz"}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -487,7 +474,6 @@ func TestGetTimelineHashtag(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"content": "zzz"},{"content": "yyy"}]`) fmt.Fprintln(w, `[{"content": "zzz"},{"content": "yyy"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -523,7 +509,6 @@ func TestGetTimelineList(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"content": "zzz"},{"content": "yyy"}]`) fmt.Fprintln(w, `[{"content": "zzz"},{"content": "yyy"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -559,7 +544,6 @@ func TestGetTimelineMedia(t *testing.T) {
return return
} }
fmt.Fprintln(w, `[{"content": "zzz"},{"content": "yyy"}]`) fmt.Fprintln(w, `[{"content": "zzz"},{"content": "yyy"}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -598,7 +582,6 @@ func TestDeleteStatus(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()
@ -634,7 +617,6 @@ func TestSearch(t *testing.T) {
"statuses":[{"content": "aaa"}], "statuses":[{"content": "aaa"}],
"hashtags":[{"name": "tag"},{"name": "tag2"},{"name": "tag3"}] "hashtags":[{"name": "tag"},{"name": "tag2"},{"name": "tag3"}]
}`) }`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -679,7 +661,6 @@ func TestUploadMedia(t *testing.T) {
return return
} }
fmt.Fprintln(w, `{"id": 123}`) fmt.Fprintln(w, `{"id": 123}`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -716,7 +697,6 @@ func TestGetConversations(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
} }
fmt.Fprintln(w, `[{"id": "4", "unread":false, "last_status" : {"content": "zzz"}}, {"id": "3", "unread":true, "last_status" : {"content": "bar"}}]`) fmt.Fprintln(w, `[{"id": "4", "unread":false, "last_status" : {"content": "zzz"}}, {"id": "3", "unread":true, "last_status" : {"content": "bar"}}]`)
return
})) }))
defer ts.Close() defer ts.Close()
@ -754,7 +734,6 @@ func TestDeleteConversation(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusMethodNotAllowed)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()
@ -780,7 +759,6 @@ func TestMarkConversationsAsRead(t *testing.T) {
http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound)
return return
} }
return
})) }))
defer ts.Close() defer ts.Close()

View file

@ -23,12 +23,11 @@ event: delete
data: 1234567 data: 1234567
:thump :thump
`) `)
errs := make(chan error, 1)
go func() { go func() {
defer close(q) defer close(q)
err := handleReader(q, r) err := handleReader(q, r)
if err != nil { errs <- err
t.Fatalf("should not be fail: %v", err)
}
}() }()
var passUpdate, passNotification, passDelete, passError bool var passUpdate, passNotification, passDelete, passError bool
for e := range q { for e := range q {
@ -60,6 +59,10 @@ data: 1234567
"update %t, notification %t, delete %t, error %t", "update %t, notification %t, delete %t, error %t",
passUpdate, passNotification, passDelete, passError) passUpdate, passNotification, passDelete, passError)
} }
err := <-errs
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
} }
func TestStreaming(t *testing.T) { func TestStreaming(t *testing.T) {
@ -142,9 +145,6 @@ func TestDoStreaming(t *testing.T) {
go func() { go func() {
defer close(q) defer close(q)
c.doStreaming(req, q) c.doStreaming(req, q)
if err != nil {
t.Fatalf("should not be fail: %v", err)
}
}() }()
var passError bool var passError bool
for e := range q { for e := range q {