Merge 3b27689851
into 9faaa4f0dc
This commit is contained in:
commit
9bb271a14a
11 changed files with 322 additions and 6 deletions
25
.vscode/launch.json
vendored
Normal file
25
.vscode/launch.json
vendored
Normal file
|
@ -0,0 +1,25 @@
|
|||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"name": "Launch Package",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
"program": "${fileDirname}",
|
||||
"console": "integratedTerminal"
|
||||
},
|
||||
{
|
||||
"name": "mstdn",
|
||||
"type": "go",
|
||||
"request": "launch",
|
||||
"mode": "auto",
|
||||
// "cwd": "${workspaceFolder}/cmd/mstdn",
|
||||
"program": "${workspaceFolder}/cmd/mstdn",
|
||||
"console": "integratedTerminal"
|
||||
}
|
||||
]
|
||||
}
|
60
README.md
60
README.md
|
@ -69,6 +69,65 @@ func main() {
|
|||
}
|
||||
```
|
||||
|
||||
### Client with Token
|
||||
This option lets the user avoid storing login credentials in the application. Instead, the user's Mastodon server
|
||||
provides an access token which is used to authenticate. This token can be stored in the application, but should be guarded.
|
||||
|
||||
```
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"log"
|
||||
"net/url"
|
||||
|
||||
"github.com/mattn/go-mastodon"
|
||||
)
|
||||
|
||||
func main() {
|
||||
appConfig := &mastodon.AppConfig{
|
||||
Server: "https://stranger.social",
|
||||
ClientName: "client-name",
|
||||
Scopes: "read write follow",
|
||||
Website: "https://github.com/mattn/go-mastodon",
|
||||
RedirectURIs: "urn:ietf:wg:oauth:2.0:oob",
|
||||
}
|
||||
app, err := mastodon.RegisterApp(context.Background(), appConfig)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
|
||||
// Have the user manually get the token and send it back to us
|
||||
u, err := url.Parse(app.AuthURI)
|
||||
if err != nil {
|
||||
log.Fatal(err)
|
||||
}
|
||||
fmt.Printf("Open your browser to \n%s\n and copy/paste the given token\n", u)
|
||||
var token string
|
||||
fmt.Print("Paste the token here:")
|
||||
fmt.Scanln(&token)
|
||||
config := &mastodon.Config{
|
||||
Server: "https://stranger.social",
|
||||
ClientID: app.ClientID,
|
||||
ClientSecret: app.ClientSecret,
|
||||
AccessToken: token,
|
||||
}
|
||||
|
||||
c := mastodon.NewClient(config)
|
||||
err = c.AuthenticateToken(context.Background(), token, "urn:ietf:wg:oauth:2.0:oob")
|
||||
if err != nil {
|
||||
log.Fatal((err)
|
||||
}
|
||||
|
||||
acct, err := c.GetAccountCurrentUser(context.Background())
|
||||
if err != nil {
|
||||
log.Fatal((err)
|
||||
}
|
||||
fmt.Printf("Account is %v\n", acct)
|
||||
}
|
||||
```
|
||||
|
||||
## Status of implementations
|
||||
|
||||
* [x] GET /api/v1/accounts/:id
|
||||
|
@ -102,6 +161,7 @@ func main() {
|
|||
* [x] GET /api/v1/follow_requests
|
||||
* [x] POST /api/v1/follow_requests/:id/authorize
|
||||
* [x] POST /api/v1/follow_requests/:id/reject
|
||||
* [x] GET /api/v1/followed_tags
|
||||
* [x] POST /api/v1/follows
|
||||
* [x] GET /api/v1/instance
|
||||
* [x] GET /api/v1/instance/activity
|
||||
|
|
46
accounts.go
46
accounts.go
|
@ -2,6 +2,7 @@ package mastodon
|
|||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
@ -32,6 +33,7 @@ type Account struct {
|
|||
Bot bool `json:"bot"`
|
||||
Discoverable bool `json:"discoverable"`
|
||||
Source *AccountSource `json:"source"`
|
||||
FollowedTag []FollowedTag `json:"followed_tags"`
|
||||
}
|
||||
|
||||
// Field is a Mastodon account profile field.
|
||||
|
@ -50,6 +52,40 @@ type AccountSource struct {
|
|||
Fields *[]Field `json:"fields"`
|
||||
}
|
||||
|
||||
// UnixTimeString represents a time in a Unix Epoch string
|
||||
type UnixTimeString struct {
|
||||
time.Time
|
||||
}
|
||||
|
||||
func (u *UnixTimeString) UnmarshalJSON(b []byte) error {
|
||||
var timestampSring string
|
||||
err := json.Unmarshal(b, ×tampSring)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
timestamp, err := strconv.ParseInt(timestampSring, 0, 0)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
u.Time = time.Unix(timestamp, 0)
|
||||
return nil
|
||||
}
|
||||
|
||||
// History is the history of a followed tag
|
||||
type FollowedTagHistory struct {
|
||||
Day UnixTimeString `json:"day,omitempty"`
|
||||
Accounts int `json:"accounts,string,omitempty"`
|
||||
Uses int `json:"uses,string,omitempty"`
|
||||
}
|
||||
|
||||
// FollowedTag is a Hash Tag followed by the user
|
||||
type FollowedTag struct {
|
||||
Name string `json:"name,omitempty"`
|
||||
URL string `json:"url,omitempty"`
|
||||
History []FollowedTagHistory `json:"history,omitempty"`
|
||||
Following bool `json:"following,omitempty"`
|
||||
}
|
||||
|
||||
// GetAccount return Account.
|
||||
func (c *Client) GetAccount(ctx context.Context, id ID) (*Account, error) {
|
||||
var account Account
|
||||
|
@ -326,3 +362,13 @@ func (c *Client) GetMutes(ctx context.Context, pg *Pagination) ([]*Account, erro
|
|||
}
|
||||
return accounts, nil
|
||||
}
|
||||
|
||||
// GetFollowedTags returns the list of Hashtags followed by the user.
|
||||
func (c *Client) GetFollowedTags(ctx context.Context, pg *Pagination) ([]*FollowedTag, error) {
|
||||
var followedTags []*FollowedTag
|
||||
err := c.doAPI(ctx, http.MethodGet, "/api/v1/followed_tags", nil, &followedTags, pg)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return followedTags, nil
|
||||
}
|
||||
|
|
|
@ -697,3 +697,93 @@ func TestGetMutes(t *testing.T) {
|
|||
t.Fatalf("want %q but %q", "bar", mutes[1].Username)
|
||||
}
|
||||
}
|
||||
func TestGetFollowedTags(t *testing.T) {
|
||||
t.Parallel()
|
||||
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, `[
|
||||
{
|
||||
"name": "Test1",
|
||||
"url": "http://mastodon.example/tags/test1",
|
||||
"history": [
|
||||
{
|
||||
"day": "1668211200",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668124800",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668038400",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
}
|
||||
],
|
||||
"following": true
|
||||
},
|
||||
{
|
||||
"name": "Test2",
|
||||
"url": "http://mastodon.example/tags/test2",
|
||||
"history": [
|
||||
{
|
||||
"day": "1668211200",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
}
|
||||
],
|
||||
"following": true
|
||||
}
|
||||
]`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(&Config{
|
||||
Server: ts.URL,
|
||||
ClientID: "foo",
|
||||
ClientSecret: "bar",
|
||||
AccessToken: "zoo",
|
||||
})
|
||||
_, err := client.GetFollowedTags(context.Background(), nil)
|
||||
if err == nil {
|
||||
t.Fatalf("should be fail: %v", err)
|
||||
}
|
||||
followedTags, err := client.GetFollowedTags(context.Background(), nil)
|
||||
if err != nil {
|
||||
t.Fatalf("should not be fail: %v", err)
|
||||
}
|
||||
if len(followedTags) != 2 {
|
||||
t.Fatalf("result should be two: %d", len(followedTags))
|
||||
}
|
||||
if followedTags[0].Name != "Test1" {
|
||||
t.Fatalf("want %q but %q", "Test1", followedTags[0].Name)
|
||||
}
|
||||
if followedTags[0].URL != "http://mastodon.example/tags/test1" {
|
||||
t.Fatalf("want %q but got %q", "http://mastodon.example/tags/test1", followedTags[0].URL)
|
||||
}
|
||||
if !followedTags[0].Following {
|
||||
t.Fatalf("want following, but got false")
|
||||
}
|
||||
if 3 != len(followedTags[0].History) {
|
||||
t.Fatalf("expecting first tag history length to be %d but got %d", 3, len(followedTags[0].History))
|
||||
}
|
||||
if followedTags[1].Name != "Test2" {
|
||||
t.Fatalf("want %q but %q", "Test2", followedTags[1].Name)
|
||||
}
|
||||
if followedTags[1].URL != "http://mastodon.example/tags/test2" {
|
||||
t.Fatalf("want %q but got %q", "http://mastodon.example/tags/test2", followedTags[1].URL)
|
||||
}
|
||||
if !followedTags[1].Following {
|
||||
t.Fatalf("want following, but got false")
|
||||
}
|
||||
if 1 != len(followedTags[1].History) {
|
||||
t.Fatalf("expecting first tag history length to be %d but got %d", 1, len(followedTags[1].History))
|
||||
}
|
||||
}
|
||||
|
|
|
@ -9,7 +9,6 @@ require (
|
|||
github.com/fatih/color v1.13.0
|
||||
github.com/mattn/go-mastodon v0.0.4
|
||||
github.com/mattn/go-tty v0.0.4
|
||||
github.com/urfave/cli v1.13.0
|
||||
github.com/urfave/cli/v2 v2.23.5 // indirect
|
||||
github.com/urfave/cli/v2 v2.23.5
|
||||
golang.org/x/net v0.0.0-20220531201128-c960675eff93
|
||||
)
|
||||
|
|
|
@ -24,8 +24,6 @@ github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf
|
|||
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||
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.13.0 h1:kkpCmfxnnnWIie2rCljcvaVrNYmsFq1ynTJH5kn1Ip4=
|
||||
github.com/urfave/cli v1.13.0/go.mod h1:70zkFmudgCuE/ngEzBv17Jvp/497gISqfk5gWijbERA=
|
||||
github.com/urfave/cli/v2 v2.23.5 h1:xbrU7tAYviSpqeR3X4nEFWUdB/uDZ6DE+HxmRU7Xtyw=
|
||||
github.com/urfave/cli/v2 v2.23.5/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc=
|
||||
github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU=
|
||||
|
|
|
@ -258,7 +258,7 @@ func makeApp() *cli.App {
|
|||
{
|
||||
Name: "timeline-tag",
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "local",
|
||||
Usage: "local tags only",
|
||||
},
|
||||
|
|
6
go.work
Normal file
6
go.work
Normal file
|
@ -0,0 +1,6 @@
|
|||
go 1.19
|
||||
|
||||
use (
|
||||
.
|
||||
./cmd/mstdn
|
||||
)
|
|
@ -729,7 +729,7 @@ func TestSearch(t *testing.T) {
|
|||
t.Fatalf("Hashtags have %q entries, but %q", "3", len(ret.Hashtags))
|
||||
}
|
||||
if ret.Hashtags[2].Name != "tag3" {
|
||||
t.Fatalf("Hashtags[2] should %q , but %q", "tag3", ret.Hashtags[2])
|
||||
t.Fatalf("Hashtags[2] should %v , but %v", "tag3", ret.Hashtags[2])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
17
tags.go
Normal file
17
tags.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package mastodon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
// TagUnfollow unfollows a hashtag.
|
||||
func (c *Client) TagUnfollow(ctx context.Context, ID string) (*FollowedTag, error) {
|
||||
var tag FollowedTag
|
||||
err := c.doAPI(ctx, http.MethodPost, fmt.Sprintf("/api/v1/tags/%s/unfollow", ID), nil, &tag, nil)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &tag, nil
|
||||
}
|
75
tags_test.go
Normal file
75
tags_test.go
Normal file
|
@ -0,0 +1,75 @@
|
|||
package mastodon
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestTagUnfollow(t *testing.T) {
|
||||
t.Parallel()
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
fmt.Fprintln(w, `{
|
||||
"name": "Test",
|
||||
"url": "http://mastodon.example/tags/test",
|
||||
"history": [
|
||||
{
|
||||
"day": "1668556800",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668470400",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668384000",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668297600",
|
||||
"accounts": "1",
|
||||
"uses": "1"
|
||||
},
|
||||
{
|
||||
"day": "1668211200",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668124800",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
},
|
||||
{
|
||||
"day": "1668038400",
|
||||
"accounts": "0",
|
||||
"uses": "0"
|
||||
}
|
||||
],
|
||||
"following": false
|
||||
}`)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
client := NewClient(&Config{
|
||||
Server: ts.URL,
|
||||
ClientID: "foo",
|
||||
ClientSecret: "bar",
|
||||
AccessToken: "zoo",
|
||||
})
|
||||
tag, err := client.TagUnfollow(context.Background(), "Test")
|
||||
if err != nil {
|
||||
t.Fatalf("should not be fail: %v", err)
|
||||
}
|
||||
if tag.Name != "Test" {
|
||||
t.Fatalf("want %q but %q", "Test", tag.Name)
|
||||
}
|
||||
if tag.Following {
|
||||
t.Fatalf("want %t but %t", false, tag.Following)
|
||||
}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue