aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorChristine Dodrill <me@christine.website>2018-10-10 05:16:15 +0000
committerChristine Dodrill <me@christine.website>2018-10-10 05:16:15 +0000
commit78c1bf58fe30f0660de32c05edc2d60d74214636 (patch)
tree9f2545568bcc0d99e8e0c86a8a527be198bb3c77
parent6f56286a19da821f8b5ebbe60086e935a171b7bb (diff)
downloadx-78c1bf58fe30f0660de32c05edc2d60d74214636.tar.xz
x-78c1bf58fe30f0660de32c05edc2d60d74214636.zip
sona-pi-toki-pona: only trigger if the user has a magic field
-rw-r--r--go.mod4
-rw-r--r--go.sum6
-rw-r--r--mastodon/sona-pi-toki-pona/main.go26
-rw-r--r--vendor/github.com/McKael/madon/v2/.gitignore20
-rw-r--r--vendor/github.com/McKael/madon/v2/.travis.yml19
-rw-r--r--vendor/github.com/McKael/madon/v2/LICENSE22
-rw-r--r--vendor/github.com/McKael/madon/v2/README.md49
-rw-r--r--vendor/github.com/McKael/madon/v2/account.go562
-rw-r--r--vendor/github.com/McKael/madon/v2/api.go261
-rw-r--r--vendor/github.com/McKael/madon/v2/app.go99
-rw-r--r--vendor/github.com/McKael/madon/v2/domain.go52
-rw-r--r--vendor/github.com/McKael/madon/v2/emoji.go20
-rw-r--r--vendor/github.com/McKael/madon/v2/endorsements.go46
-rw-r--r--vendor/github.com/McKael/madon/v2/go.mod14
-rw-r--r--vendor/github.com/McKael/madon/v2/go.sum24
-rw-r--r--vendor/github.com/McKael/madon/v2/instance.go42
-rw-r--r--vendor/github.com/McKael/madon/v2/lists.go136
-rw-r--r--vendor/github.com/McKael/madon/v2/login.go129
-rw-r--r--vendor/github.com/McKael/madon/v2/madon.go41
-rw-r--r--vendor/github.com/McKael/madon/v2/media.go108
-rw-r--r--vendor/github.com/McKael/madon/v2/notifications.go90
-rw-r--r--vendor/github.com/McKael/madon/v2/report.go48
-rw-r--r--vendor/github.com/McKael/madon/v2/search.go68
-rw-r--r--vendor/github.com/McKael/madon/v2/status.go309
-rw-r--r--vendor/github.com/McKael/madon/v2/streams.go177
-rw-r--r--vendor/github.com/McKael/madon/v2/suggestions.go31
-rw-r--r--vendor/github.com/McKael/madon/v2/timelines.go58
-rw-r--r--vendor/github.com/McKael/madon/v2/types.go265
-rw-r--r--vendor/golang.org/x/net/html/const.go10
-rw-r--r--vendor/golang.org/x/net/html/parse.go110
-rw-r--r--vendor/modules.txt4
31 files changed, 2788 insertions, 62 deletions
diff --git a/go.mod b/go.mod
index c962a37..65daf00 100644
--- a/go.mod
+++ b/go.mod
@@ -3,6 +3,7 @@ module github.com/Xe/x
require (
github.com/GeertJohan/yubigo v0.0.0-20140521141543-b1764f04aa9b // indirect
github.com/McKael/madon v2.3.0+incompatible
+ github.com/McKael/madon/v2 v2.0.0-20180929094633-c679abc985d6
github.com/Xe/ln v0.1.2
github.com/aclements/go-moremath v0.0.0-20180329182055-b1aff36309c7 // indirect
github.com/bearbin/mcgorcon v0.0.0-20141104170123-f611ad04551a
@@ -24,7 +25,6 @@ require (
github.com/google/go-github v17.0.0+incompatible
github.com/google/go-querystring v1.0.0 // indirect
github.com/google/gops v0.3.5
- github.com/gorilla/websocket v1.4.0 // indirect
github.com/hullerob/go.farbfeld v0.0.0-20160317142651-b572f0728b69
github.com/jaytaylor/html2text v0.0.0-20180606194806-57d518f124b0
github.com/joeshaw/envdecode v0.0.0-20180312135643-c9e015854467
@@ -39,7 +39,6 @@ require (
github.com/pborman/uuid v1.2.0
github.com/peterh/liner v1.1.0
github.com/pkg/errors v0.8.0
- github.com/sendgrid/rest v2.4.1+incompatible // indirect
github.com/ssor/bom v0.0.0-20170718123548-6386211fdfcf // indirect
github.com/streamrail/concurrent-map v0.0.0-20160823150647-8bf1e9bacbf6
github.com/technoweenie/multipartstreamer v1.0.1 // indirect
@@ -52,7 +51,6 @@ require (
golang.org/x/image v0.0.0-20180926015637-991ec62608f3 // indirect
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced
golang.org/x/sys v0.0.0-20181004145325-8469e314837c // indirect
- google.golang.org/appengine v1.2.0 // indirect
gopkg.in/irc.v1 v1.3.0
gopkg.in/yaml.v2 v2.2.1 // indirect
)
diff --git a/go.sum b/go.sum
index 96d2c21..ad4622b 100644
--- a/go.sum
+++ b/go.sum
@@ -6,6 +6,8 @@ github.com/GeertJohan/yubigo v0.0.0-20140521141543-b1764f04aa9b h1:SDBD2Avdba3sX
github.com/GeertJohan/yubigo v0.0.0-20140521141543-b1764f04aa9b/go.mod h1:njRCDrl+1RQ/A/+KVU8Ho2EWAxUSkohOWczdW3dzDG0=
github.com/McKael/madon v2.3.0+incompatible h1:xMUA+Fy4saDV+8tN3MMnwJUoYWC//5Fy8LeOqJsRNIM=
github.com/McKael/madon v2.3.0+incompatible/go.mod h1:+issnvJjN1rpjAHZwXRB/x30uHh/NoQR7QaojJK/lSI=
+github.com/McKael/madon/v2 v2.0.0-20180929094633-c679abc985d6 h1:9cJcTOeILzInNo+DCYmXKME1QfAP07FYdo3M9/9jyc4=
+github.com/McKael/madon/v2 v2.0.0-20180929094633-c679abc985d6/go.mod h1:mvlJhxZCchfiasx3XvN3hBu5RekGwTDm09dKlSM/dQQ=
github.com/Xe/ln v0.1.2 h1:VTF6Z95Kdd6S1RSjpnn0DFJV5Do9cfmHTHsE9OGI1dw=
github.com/Xe/ln v0.1.2/go.mod h1:GWZDvjL0ZC/9SSjY8DJ2IOrID6Lejs7D8pgQyjqMYSk=
github.com/aclements/go-moremath v0.0.0-20180329182055-b1aff36309c7 h1:/NWziSfuX4cu00adiPldD8sdCpKxu6BZtLoALSyElZQ=
@@ -25,6 +27,7 @@ github.com/codahale/hdrhistogram v0.0.0-20161010025455-3a0bb77429bd/go.mod h1:sE
github.com/coreos/go-systemd v0.0.0-20180705093442-88bfeed483d3/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4=
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
+github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-failure v0.0.0-20151001134759-4963dbd58fd0 h1:SJzP6kZNFsdxFN4fRtVXu2yrf8QrKnofjQouzaCn36w=
github.com/dgryski/go-failure v0.0.0-20151001134759-4963dbd58fd0/go.mod h1:vVcpVd0tzL5XdRrkHax1asNJsHVpgA0cd9fHPHU5a/w=
github.com/dgryski/go-onlinestats v0.0.0-20170612111826-1c7d19468768 h1:Xzl7CSuSnGsyU+9xmSU2h8w3d7Tnis66xeoNN207tLo=
@@ -171,7 +174,10 @@ golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73r
golang.org/x/net v0.0.0-20180808004115-f9ce57c11b24/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/net v0.0.0-20180811021610-c39426892332 h1:efGso+ep0DjyCBJPjvoz0HI6UldX4Md2F1rZFe1ir0E=
golang.org/x/net v0.0.0-20180811021610-c39426892332/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
+golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3 h1:dgd4x4kJt7G4k4m93AYLzM8Ni6h2qLTfh9n9vXJT3/0=
+golang.org/x/net v0.0.0-20180926154720-4dfa2610cdf3/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
golang.org/x/oauth2 v0.0.0-20180724155351-3d292e4d0cdc/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
+golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced h1:4oqSq7eft7MdPKBGQK11X9WYUxmj6ZLgGTqYIbY1kyw=
golang.org/x/oauth2 v0.0.0-20181003184128-c57b0facaced/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
golang.org/x/perf v0.0.0-20180704124530-6e6d33e29852/go.mod h1:JLpeXjPJfIyPr5TlbXLkXWLhP8nz10XfvxElABhCtcw=
diff --git a/mastodon/sona-pi-toki-pona/main.go b/mastodon/sona-pi-toki-pona/main.go
index ddd2d4f..99d3f56 100644
--- a/mastodon/sona-pi-toki-pona/main.go
+++ b/mastodon/sona-pi-toki-pona/main.go
@@ -6,7 +6,7 @@ import (
"io/ioutil"
"net/http"
- madon "github.com/McKael/madon"
+ madon "github.com/McKael/madon/v2"
"github.com/Xe/ln"
"github.com/Xe/ln/opname"
"github.com/Xe/x/web/tokipana"
@@ -35,6 +35,8 @@ func main() {
ln.FatalErr(opname.With(ctx, "restore-app"), err)
}
+ ln.Log(ctx, ln.Info("waiting for messages on #topipona..."))
+
for {
evChan := make(chan madon.StreamEvent, 10)
stop := make(chan bool)
@@ -62,6 +64,17 @@ func main() {
"originating_status_url": s.URL,
})
+ found := false
+ for _, f := range *s.Account.Fields {
+ if f.Name == "enable-bot" && f.Value == "sona-pi-toki-pona" {
+ found = true
+ }
+ }
+ if !found {
+ ln.Log(ctx, ln.Info("ignoring message"))
+ continue
+ }
+
text, err := html2text.FromString(s.Content, html2text.Options{PrettyTables: true})
if err != nil {
ln.Error(ctx, err)
@@ -87,12 +100,11 @@ func main() {
}
st, err := c.PostStatus(
- fmt.Sprintf(translationTemplate, string(data)),
- s.ID,
- nil,
- false,
- "",
- "public",
+ madon.PostStatusParams{
+ Text: fmt.Sprintf(translationTemplate, string(data)),
+ InReplyTo: s.ID,
+ Visibility: "public",
+ },
)
if err != nil {
ln.Error(ctx, err)
diff --git a/vendor/github.com/McKael/madon/v2/.gitignore b/vendor/github.com/McKael/madon/v2/.gitignore
new file mode 100644
index 0000000..836e7a0
--- /dev/null
+++ b/vendor/github.com/McKael/madon/v2/.gitignore
@@ -0,0 +1,20 @@
+# Created by .ignore support plugin (hsz.mobi)
+### Go template
+# Binaries for programs and plugins
+*.exe
+*.dll
+*.so
+*.dylib
+
+# Test binary, build with `go test -c`
+*.test
+
+# Output of the go coverage tool, specifically when used with LiteIDE
+*.out
+
+# Project-local glide cache, RE: https://github.com/Masterminds/glide/issues/736
+.glide/
+*.swp
+
+.idea/
+.local/
diff --git a/vendor/github.com/McKael/madon/v2/.travis.yml b/vendor/github.com/McKael/madon/v2/.travis.yml
new file mode 100644
index 0000000..5e14f30
--- /dev/null
+++ b/vendor/github.com/McKael/madon/v2/.travis.yml
@@ -0,0 +1,19 @@
+language: go
+go:
+- "1.9"
+- "1.10"
+- "1.11"
+- master
+matrix:
+ allow_failures:
+ - go: master
+ fast_finish: true
+branches:
+ only:
+ - master
+install:
+- go get golang.org/x/oauth2
+- go get github.com/pkg/errors
+- go get github.com/stretchr/testify/assert
+- go get github.com/sendgrid/rest
+- go get github.com/gorilla/websocket
diff --git a/vendor/github.com/McKael/madon/v2/LICENSE b/vendor/github.com/McKael/madon/v2/LICENSE
new file mode 100644
index 0000000..f0f42d2
--- /dev/null
+++ b/vendor/github.com/McKael/madon/v2/LICENSE
@@ -0,0 +1,22 @@
+MIT License
+
+Copyright (c) 2017 Ollivier Robert
+Copyright (c) 2017 Mikael Berthe <mikael@lilotux.net>
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/vendor/github.com/McKael/madon/v2/README.md b/vendor/github.com/McKael/madon/v2/README.md
new file mode 100644
index 0000000..5449329
--- /dev/null
+++ b/vendor/github.com/McKael/madon/v2/README.md
@@ -0,0 +1,49 @@
+# madon
+
+Golang library for the Mastodon API
+
+[![godoc](https://img.shields.io/badge/godoc-reference-blue.svg?style=flat)](https://godoc.org/github.com/McKael/madon)
+[![license](https://img.shields.io/badge/license-MIT-red.svg?style=flat)](https://raw.githubusercontent.com/McKael/madon/master/LICENSE)
+[![build](https://travis-ci.org/McKael/madon.svg?branch=master)](https://travis-ci.org/McKael/madon)
+[![Go Report Card](https://goreportcard.com/badge/github.com/McKael/madon)](https://goreportcard.com/report/github.com/McKael/madon)
+
+`madon` is a [Go](https://golang.org/) library to access the Mastondon REST API.
+
+This implementation covers 100% of the current API, including the streaming API.
+
+The [madonctl](https://github.com/McKael/madonctl) console client uses this library exhaustively.
+
+## Installation
+
+To install the library (Go >= v1.5 required):
+
+ go get github.com/McKael/madon
+
+For minimal compatibility with Go modules support (in Go v1.11), it is
+recommended to use Go version 1.9+.
+
+You can test it with my CLI tool:
+
+ go get github.com/McKael/madonctl
+
+## Usage
+
+This section has not been written yet (PR welcome).
+
+For now please check [godoc](https://godoc.org/github.com/McKael/madon) and
+check the [madonctl](https://github.com/McKael/madonctl) project
+implementation.
+
+## History
+
+This API implementation was initially submitted as a PR for gondole.
+
+The repository is actually a fork of my gondole branch so that
+history and credits are preserved.
+
+## References
+
+- [madonctl](https://github.com/McKael/madonctl) (console client based on madon)
+- [Mastodon API documentation](https://github.com/tootsuite/documentation/blob/master/Using-the-API/API.md)
+- [Mastodon Streaming API documentation](https://github.com/tootsuite/documentation/blob/master/Using-the-API/Streaming-API.md)
+- [Mastodon repository](https://github.com/tootsuite/mastodon)
diff --git a/vendor/github.com/McKael/madon/v2/account.go b/vendor/github.com/McKael/madon/v2/account.go
new file mode 100644
index 0000000..0947393
--- /dev/null
+++ b/vendor/github.com/McKael/madon/v2/account.go
@@ -0,0 +1,562 @@
+/*
+Copyright 2017-2018 Mikael Berthe
+
+Licensed under the MIT license. Please see the LICENSE file is this directory.
+*/
+
+package madon
+
+import (
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "mime/multipart"
+ "os"
+ "path/filepath"
+ "strconv"
+
+ "github.com/pkg/errors"
+ "github.com/sendgrid/rest"
+)
+
+// getAccountsOptions contains option fields for POST and DELETE API calls
+type getAccountsOptions struct {
+ // The ID is used for most commands
+ ID int64
+
+ // Following can be set to true to limit a search to "following" accounts
+ Following bool
+
+ // The Q field (query) is used when searching for accounts
+ Q string
+
+ Limit *LimitParams
+}
+
+// UpdateAccountParams contains option fields for the UpdateAccount command
+type UpdateAccountParams struct {
+ DisplayName *string
+ Note *string
+ AvatarImagePath *string
+ HeaderImagePath *string
+ Locked *bool
+ Bot *bool
+ FieldsAttributes *[]Field
+ Source *SourceParams
+}
+
+// updateRelationship returns a Relationship entity
+// The operation 'op' can be "follow", "unfollow", "block", "unblock",
+// "mute", "unmute".
+// The id is optional and depends on the operation.
+func (mc *Client) updateRelationship(op string, id int64, params apiCallParams) (*Relationship, error) {
+ var endPoint string
+ method := rest.Post
+ strID := strconv.FormatInt(id, 10)
+
+ switch op {
+ case "follow", "unfollow", "block", "unblock", "mute", "unmute", "pin", "unpin":
+ endPoint = "accounts/" + strID + "/" + op
+ default:
+ return nil, ErrInvalidParameter
+ }
+
+ var rel Relationship
+ if err := mc.apiCall("v1/"+endPoint, method, params, nil, nil, &rel); err != nil {
+ return nil, err
+ }
+ return &rel, nil
+}
+
+// getSingleAccount returns an account entity
+// The operation 'op' can be "account", "verify_credentials",
+// "follow_requests/authorize" or // "follow_requests/reject".
+// The id is optional and depends on the operation.
+func (mc *Client) getSingleAccount(op string, id int64) (*Account, error) {
+ var endPoint string
+ method := rest.Get
+ strID := strconv.FormatInt(id, 10)
+
+ switch op {
+ case "account":
+ endPoint = "accounts/" + strID
+ case "verify_credentials":
+ endPoint = "accounts/verify_credentials"
+ case "follow_requests/authorize", "follow_requests/reject":
+ // The documentation is incorrect, the endpoint actually
+ // is "follow_requests/:id/{authorize|reject}"
+ endPoint = op[:16] + strID + "/" + op[16:]
+ method = rest.Post
+ default:
+ return nil, ErrInvalidParameter
+ }
+
+ var account Account
+ if err := mc.apiCall("v1/"+endPoint, method, nil, nil, nil, &account); err != nil {
+ return nil, err
+ }
+ return &account, nil
+}
+
+// getMultipleAccounts returns a list of account entities
+// If lopt.All is true, several requests will be made until the API server
+// has nothing to return.
+func (mc *Client) getMultipleAccounts(endPoint string, params apiCallParams, lopt *LimitParams) ([]Account, error) {
+ var accounts []Account
+ var links apiLinks
+ if err := mc.apiCall("v1/"+endPoint, rest.Get, params, lopt, &links, &accounts); err != nil {
+ return nil, err
+ }
+ if lopt != nil { // Fetch more pages to reach our limit
+ var accountSlice []Account
+ for (lopt.All || lopt.Limit > len(accounts)) && links.next != nil {
+ newlopt := links.next
+ links = apiLinks{}
+ if err := mc.apiCall("v1/"+endPoint, rest.Get, params, newlopt, &links, &accountSlice); err != nil {
+ return nil, err
+ }
+ accounts = append(accounts, accountSlice...)
+ accountSlice = accountSlice[:0] // Clear struct
+ }
+ }
+ return accounts, nil
+}
+
+// getMultipleAccountsHelper returns a list of account entities
+// The operation 'op' can be "followers", "following", "search", "blocks",
+// "mutes", "follow_requests".
+// The id is optional and depends on the operation.
+// If opts.All is true, several requests will be made until the API server
+// has nothing to return.
+func (mc *Client) getMultipleAccountsHelper(op string, opts *getAccountsOptions) ([]Account, error) {
+ var endPoint string
+ var lopt *LimitParams
+
+ if opts != nil {
+ lopt = opts.Limit
+ }
+
+ switch op {
+ case "followers", "following":
+ if opts == nil || opts.ID < 1 {
+ return []Account{}, ErrInvalidID
+ }
+ endPoint = "accounts/" + strconv.FormatInt(opts.ID, 10) + "/" + op
+ case "follow_requests", "blocks", "mutes":
+ endPoint = op
+ case "search":
+ if opts == nil || opts.Q == "" {
+ return []Account{}, ErrInvalidParameter
+ }
+ endPoint = "accounts/" + op
+ case "reblogged_by", "favourited_by":
+ if opts == nil || opts.ID < 1 {
+ return []Account{}, ErrInvalidID
+ }
+ endPoint = "statuses/" + strconv.FormatInt(opts.ID, 10) + "/" + op
+ default:
+ return nil, ErrInvalidParameter
+ }
+
+ // Handle target-specific query parameters
+ params := make(apiCallParams)
+ if op == "search" {
+ params["q"] = opts.Q
+ if opts.Following {
+ params["following"] = "true"
+ }
+ }
+
+ return mc.getMultipleAccounts(endPoint, params, lopt)
+}
+
+// GetAccount returns an account entity
+// The returned value can be nil if there is an error or if the
+// requested ID does not exist.
+func (mc *Client) GetAccount(accountID int64) (*Account, error) {
+ account, err := mc.getSingleAccount("account", accountID)
+ if err != nil {
+ return nil, err
+ }
+ if account != nil && account.ID == 0 {
+ return nil, ErrEntityNotFound
+ }
+ return account, nil
+}
+
+// GetCurrentAccount returns the current user account
+func (mc *Client) GetCurrentAccount() (*Account, error) {
+ account, err := mc.getSingleAccount("verify_credentials", 0)
+ if err != nil {
+ return nil, err
+ }
+ if account != nil && account.ID == 0 {
+ return nil, ErrEntityNotFound
+ }
+ return account, nil
+}
+
+// GetAccountFollowers returns the list of accounts following a given account
+func (mc *Client) GetAccountFollowers(accountID int64, lopt *LimitParams) ([]Account, error) {
+ o := &getAccountsOptions{ID: accountID, Limit: lopt}
+ return mc.getMultipleAccountsHelper("followers", o)
+}
+
+// GetAccountFollowing returns the list of accounts a given account is following
+func (mc *Client) GetAccountFollowing(accountID int64, lopt *LimitParams) ([]Account, error) {
+ o := &getAccountsOptions{ID: accountID, Limit: lopt}
+ return mc.getMultipleAccountsHelper("following", o)
+}
+
+// FollowAccount follows an account
+// 'reblogs' can be used to specify if boots should be displayed or hidden.
+func (mc *Client) FollowAccount(accountID int64, reblogs *bool) (*Relationship, error) {
+ var params apiCallParams
+ if reblogs != nil {
+ params = make(apiCallParams)
+ if *reblogs {
+ params["reblogs"] = "true"
+ } else {
+ params["reblogs"] = "false"
+ }
+ }
+ rel, err := mc.updateRelationship("follow", accountID, params)
+ if err != nil {
+ return nil, err
+ }
+ if rel == nil {
+ return nil, ErrEntityNotFound
+ }
+ return rel, nil
+}
+
+// UnfollowAccount unfollows an account
+func (mc *Client) UnfollowAccount(accountID int64) (*Relationship, error) {
+ rel, err := mc.updateRelationship("unfollow", accountID, nil)
+ if err != nil {
+ return nil, err
+ }
+ if rel == nil {
+ return nil, ErrEntityNotFound
+ }
+ return rel, nil
+}
+
+// FollowRemoteAccount follows a remote account
+// The parameter 'uri' is a URI (e.g. "username@domain").
+func (mc *Client) FollowRemoteAccount(uri string) (*Account, error) {
+ if uri == "" {
+ return nil, ErrInvalidID
+ }
+
+ params := make(apiCallParams)
+ params["uri"] = uri
+
+ var account Account
+ if err := mc.apiCall("v1/follows", rest.Post, params, nil, nil, &account); err != nil {
+ return nil, err
+ }
+ if account.ID == 0 {
+ return nil, ErrEntityNotFound
+ }
+ return &account, nil
+}
+
+// BlockAccount blocks an account
+func (mc *Client) BlockAccount(accountID int64) (*Relationship, error) {
+ rel, err := mc.updateRelationship("block", accountID, nil)
+ if err != nil {
+ return nil, err
+ }
+ if rel == nil {
+ return nil, ErrEntityNotFound
+ }
+ return rel, nil
+}
+
+// UnblockAccount unblocks an account
+func (mc *Client) UnblockAccount(accountID int64) (*Relationship, error) {
+ rel, err := mc.updateRelationship("unblock", accountID, nil)
+ if err != nil {
+ return nil, err
+ }
+ if rel == nil {
+ return nil, ErrEntityNotFound
+ }
+ return rel, nil
+}
+
+// MuteAccount mutes an account
+// Note that with current Mastodon API, muteNotifications defaults to true
+// when it is not provided.
+func (mc *Client) MuteAccount(accountID int64, muteNotifications *bool) (*Relationship, error) {
+ var params apiCallParams
+
+ if muteNotifications != nil {
+ params = make(apiCallParams)
+ if *muteNotifications {
+ params["notifications"] = "true"
+ } else {
+ params["notifications"] = "false"
+ }
+ }
+
+ rel, err := mc.updateRelationship("mute", accountID, params)
+ if err != nil {
+ return nil, err
+ }
+ if rel == nil {
+ return nil, ErrEntityNotFound
+ }
+ return rel, nil
+}
+
+// UnmuteAccount unmutes an account
+func (mc *Client) UnmuteAccount(accountID int64) (*Relationship, error) {
+ rel, err := mc.updateRelationship("unmute", accountID, nil)
+ if err != nil {
+ return nil, err
+ }
+ if rel == nil {
+ return nil, ErrEntityNotFound
+ }
+ return rel, nil
+}
+
+// SearchAccounts returns a list of accounts matching the query string
+// The lopt parameter is optional (can be nil) or can be used to set a limit.
+func (mc *Client) SearchAccounts(query string, following bool, lopt *LimitParams) ([]Account, error) {
+ o := &getAccountsOptions{Q: query, Limit: lopt, Following: following}
+ return mc.getMultipleAccountsHelper("search", o)
+}
+
+// GetBlockedAccounts returns the list of blocked accounts
+// The lopt parameter is optional (can be nil).
+func (mc *Client) GetBlockedAccounts(lopt *LimitParams) ([]Account, error) {
+ o := &getAccountsOptions{Limit: lopt}
+ return mc.getMultipleAccountsHelper("blocks", o)
+}
+
+// GetMutedAccounts returns the list of muted accounts
+// The lopt parameter is optional (can be nil).
+func (mc *Client) GetMutedAccounts(lopt *LimitParams) ([]Account, error) {
+ o := &getAccountsOptions{Limit: lopt}
+ return mc.getMultipleAccountsHelper("mutes", o)
+}
+
+// GetAccountFollowRequests returns the list of follow requests accounts
+// The lopt parameter is optional (can be nil).
+func (mc *Client) GetAccountFollowRequests(lopt *LimitParams) ([]Account, error) {
+ o := &getAccountsOptions{Limit: lopt}
+ return mc.getMultipleAccountsHelper("follow_requests", o)
+}
+
+// GetAccountRelationships returns a list of relationship entities for the given accounts
+func (mc *Client) GetAccountRelationships(accountIDs []int64) ([]Relationship, error) {
+ if len(accountIDs) < 1 {
+ return nil, ErrInvalidID
+ }
+
+ params := make(apiCallParams)
+ for i, id := range accountIDs {
+ if id < 1 {
+ return nil, ErrInvalidID
+ }
+ qID := fmt.Sprintf("[%d]id", i)
+ params[qID] = strconv.FormatInt(id, 10)
+ }
+
+ var rl []Relationship
+ if err := mc.apiCall("v1/accounts/relationships", rest.Get, params, nil, nil, &rl); err != nil {
+ return nil, err
+ }
+ return rl, nil
+}
+
+// GetAccountStatuses returns a list of status entities for the given account
+// If onlyMedia is true, returns only statuses that have media attachments.
+// If onlyPinned is true, returns only statuses that have been pinned.
+// If excludeReplies is true, skip statuses that reply to other statuses.
+// If lopt.All is true, several requests will be made until the API server
+// has nothing to return.
+// If lopt.Limit is set (and not All), several queries can be made until the
+// limit is reached.
+func (mc *Client) GetAccountStatuses(accountID int64, onlyPinned, onlyMedia, excludeReplies bool, lopt *LimitParams) ([]Status, error) {
+ if accountID < 1 {
+ return nil, ErrInvalidID
+ }
+
+ endPoint := "accounts/" + strconv.FormatInt(accountID, 10) + "/" + "statuses"
+ params := make(apiCallParams)
+ if onlyMedia {
+ params["only_media"] = "true"
+ }
+ if onlyPinned {
+ params["pinned"] = "true"
+ }
+ if excludeReplies {
+ params["exclude_replies"] = "true"
+ }
+
+ return mc.getMultipleStatuses(endPoint, params, lopt)
+}
+
+// FollowRequestAuthorize authorizes or rejects an account follow-request
+func (mc *Client) FollowRequestAuthorize(accountID int64, authorize bool) error {
+ endPoint := "follow_requests/reject"
+ if authorize {
+ endPoint = "follow_requests/authorize"
+ }
+ _, err := mc.getSingleAccount(endPoint, accountID)
+ return err
+}
+
+// UpdateAccount updates the connected user's account data
+//
+// The fields avatar & headerImage are considered as file paths
+// and their content will be uploaded.
+// Please note that currently Mastodon leaks the avatar file name:
+// https://github.com/tootsuite/mastodon/issues/5776
+//
+// All fields can be nil, in which case they are not updated.
+// 'DisplayName' and 'Note' can be set to "" to delete previous values.
+// Setting 'Locked' to true means all followers should be approved.
+// You can set 'Bot' to true to indicate this is a service (automated) account.
+// I'm not sure images can be deleted -- only replaced AFAICS.
+func (mc *Client) UpdateAccount(cmdParams UpdateAccountParams) (*Account, error) {
+ const endPoint = "accounts/update_credentials"
+ params := make(apiCallParams)
+
+ if cmdParams.DisplayName != nil {
+ params["display_name"] = *cmdParams.DisplayName
+ }
+ if cmdParams.Note != nil {
+ params["note"] = *cmdParams.Note
+ }
+ if cmdParams.Locked != nil {
+ if *cmdParams.Locked {
+ params["locked"] = "true"
+ } else {
+ params["locked"] = "false"
+ }
+ }
+ if cmdParams.Bot != nil {
+ if *cmdParams.Bot {
+ params["bot"] = "true"
+ } else {
+ params["bot"] = "false"
+ }
+ }
+ if cmdParams.FieldsAttributes != nil {
+ if len(*cmdParams.FieldsAttributes) > 4 {
+ return nil, errors.New("too many fields (max=4)")
+ }
+ for i, attr := range *cmdParams.FieldsAttributes {
+ qName := fmt.Sprintf("fields_attributes[%d][name]", i)
+ qValue := fmt.Sprintf("fields_attributes[%d][value]", i)
+ params[qName] = attr.Name
+ params[qValue] = attr.Value
+ }
+ }
+ if cmdParams.Source != nil {
+ s := cmdParams.Source
+
+ if s.Privacy != nil {
+ params["source[privacy]"] = *s.Privacy
+ }
+ if s.Language != nil {
+ params["source[language]"] = *s.Language
+ }
+ if s.Sensitive != nil {
+ params["source[sensitive]"] = fmt.Sprintf("%v", *s.Sensitive)
+ }
+ }
+
+ var err error
+ var avatar, headerImage []byte
+
+ avatar, err = readFile(cmdParams.AvatarImagePath)
+ if err != nil {
+ return nil, err
+ }
+
+ headerImage, err = readFile(cmdParams.HeaderImagePath)
+ if err != nil {
+ return nil, err
+ }
+
+ var formBuf bytes.Buffer
+ w := multipart.NewWriter(&formBuf)
+
+ if avatar != nil {
+ formWriter, err := w.CreateFormFile("avatar", filepath.Base(*cmdParams.AvatarImagePath))
+ if err != nil {
+ return nil, errors.Wrap(err, "avatar upload")
+ }
+ formWriter.Write(avatar)
+ }
+ if headerImage != nil {
+ formWriter, err := w.CreateFormFile("header", filepath.Base(*cmdParams.HeaderImagePath))
+ if err != nil {
+ return nil, errors.Wrap(err, "header upload")
+ }
+ formWriter.Write(headerImage)
+ }
+ w.Close()
+
+ // Prepare the request
+ req, err := mc.prepareRequest("v1/"+endPoint, rest.Patch, params)
+ if err != nil {
+ return nil, errors.Wrap(err, "prepareRequest failed")
+ }
+ req.Headers["Content-Type"] = w.FormDataContentType()
+ req.Body = formBuf.Bytes()
+
+ // Make API call
+ r, err := restAPI(req)
+ if err != nil {
+ return nil, errors.Wrap(err, "account update failed")
+ }
+
+ // Check for error reply
+ var errorResult Error
+ if err := json.Unmarshal([]byte(r.Body), &errorResult); err == nil {
+ // The empty object is not an error
+ if errorResult.Text != "" {
+ return nil, errors.New(errorResult.Text)
+ }
+ }
+
+ // Not an error reply; let's unmarshal the data
+ var account Account
+ if err := json.Unmarshal([]byte(r.Body), &account); err != nil {
+ return nil, errors.Wrap(err, "cannot decode API response")
+ }
+ return &account, nil
+}
+
+// readFile is a helper function to read a file's contents.
+func readFile(filename *string) ([]byte, error) {
+ if filename == nil || *filename == "" {
+ return nil, nil
+ }
+
+ file, err := os.Open(*filename)
+ if err != nil {
+ return nil, err
+ }
+ defer file.Close()
+
+ fStat, err := file.Stat()
+ if err != nil {