merge with upstream

This commit is contained in:
localhost_frssoft 2023-10-12 15:32:55 +03:00
commit 4379eab5bf
25 changed files with 465 additions and 186 deletions

View file

@ -1,11 +1,15 @@
package mastodon
import (
"bytes"
"context"
"fmt"
"encoding/json"
"io"
"mime/multipart"
"net/http"
"net/url"
"path/filepath"
"strconv"
"time"
"strings"
@ -42,6 +46,7 @@ type Account struct {
Moved *Account `json:"moved"`
Fields []Field `json:"fields"`
Bot bool `json:"bot"`
Source *AccountSource `json:"source"`
Pleroma *AccountPleroma `json:"pleroma"`
MastodonAccount bool
}
@ -186,7 +191,6 @@ type Profile struct {
//Other settings
Bot *bool
Pleroma *ProfilePleroma
}
type ProfilePleroma struct {
@ -199,44 +203,78 @@ type ProfilePleroma struct {
HideFollows *bool
HideFollowersCount *bool
HideFollowsCount *bool
Avatar *multipart.FileHeader
Header *multipart.FileHeader
}
// AccountUpdate updates the information of the current user.
func (c *Client) AccountUpdate(ctx context.Context, profile *Profile) (*Account, error) {
params := url.Values{}
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
if profile.DisplayName != nil {
params.Set("display_name", *profile.DisplayName)
err := mw.WriteField("display_name", *profile.DisplayName)
if err != nil {
return nil, err
}
}
if profile.Note != nil {
params.Set("note", *profile.Note)
err := mw.WriteField("note", *profile.Note)
if err != nil {
return nil, err
}
}
if profile.Locked != nil {
params.Set("locked", strconv.FormatBool(*profile.Locked))
err := mw.WriteField("locked", strconv.FormatBool(*profile.Locked))
if err != nil {
return nil, err
}
}
if profile.Fields != nil {
for idx, field := range *profile.Fields {
params.Set(fmt.Sprintf("fields_attributes[%d][name]", idx), field.Name)
params.Set(fmt.Sprintf("fields_attributes[%d][value]", idx), field.Value)
err := mw.WriteField(fmt.Sprintf("fields_attributes[%d][name]", idx), field.Name)
if err != nil {
return nil, err
}
err = mw.WriteField(fmt.Sprintf("fields_attributes[%d][value]", idx), field.Value)
if err != nil {
return nil, err
}
}
}
if profile.Source != nil {
if profile.Source.Privacy != nil {
params.Set("source[privacy]", *profile.Source.Privacy)
if profile.Avatar != nil {
f, err := profile.Avatar.Open()
if err != nil {
return nil, err
}
if profile.Source.Sensitive != nil {
params.Set("source[sensitive]", strconv.FormatBool(*profile.Source.Sensitive))
fname := filepath.Base(profile.Avatar.Filename)
part, err := mw.CreateFormFile("avatar", fname)
if err != nil {
return nil, err
}
if profile.Source.Language != nil {
params.Set("source[language]", *profile.Source.Language)
_, err = io.Copy(part, f)
if err != nil {
return nil, err
}
}
if profile.Avatar != "" {
params.Set("avatar", profile.Avatar)
if profile.Header != nil {
f, err := profile.Header.Open()
if err != nil {
return nil, err
}
fname := filepath.Base(profile.Header.Filename)
part, err := mw.CreateFormFile("header", fname)
if err != nil {
return nil, err
}
_, err = io.Copy(part, f)
if err != nil {
return nil, err
}
}
if profile.Header != "" {
params.Set("header", profile.Header)
err := mw.Close()
if err != nil {
return nil, err
}
if profile.Bot != nil {
params.Set("bot", strconv.FormatBool(*profile.Bot))
}
@ -246,14 +284,43 @@ func (c *Client) AccountUpdate(ctx context.Context, profile *Profile) (*Account,
}
}
params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
var account Account
err := c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
err = c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
if err != nil {
return nil, err
}
return &account, nil
}
func (c *Client) accountDeleteField(ctx context.Context, field string) (*Account, error) {
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
_, err := mw.CreateFormField(field)
if err != nil {
return nil, err
}
err = mw.Close()
if err != nil {
return nil, err
}
params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
var account Account
err = c.doAPI(ctx, http.MethodPatch, "/api/v1/accounts/update_credentials", params, &account, nil)
if err != nil {
return nil, err
}
return &account, nil
}
func (c *Client) AccountDeleteAvatar(ctx context.Context) (*Account, error) {
return c.accountDeleteField(ctx, "avatar")
}
func (c *Client) AccountDeleteHeader(ctx context.Context) (*Account, error) {
return c.accountDeleteField(ctx, "header")
}
// GetAccountStatuses return statuses by specified accuont.
func (c *Client) GetAccountStatuses(ctx context.Context, id string, onlyMedia bool, onlyPinned bool, pg *Pagination) ([]*Status, error) {
var statuses []*Status

View file

@ -11,7 +11,6 @@ import (
// AppConfig is a setting for registering applications.
type AppConfig struct {
http.Client
Server string
ClientName string
@ -61,7 +60,7 @@ func RegisterApp(ctx context.Context, appConfig *AppConfig) (*Application, error
}
req = req.WithContext(ctx)
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
resp, err := appConfig.Do(req)
resp, err := httpClient.Do(req)
if err != nil {
return nil, err
}

45
mastodon/http.go Normal file
View file

@ -0,0 +1,45 @@
package mastodon
import (
"fmt"
"io"
"net/http"
"time"
)
type lr struct {
io.ReadCloser
n int64
r *http.Request
}
func (r *lr) Read(p []byte) (n int, err error) {
if r.n <= 0 {
return 0, fmt.Errorf("%s \"%s\": response body too large", r.r.Method, r.r.URL)
}
if int64(len(p)) > r.n {
p = p[0:r.n]
}
n, err = r.ReadCloser.Read(p)
r.n -= int64(n)
return
}
type transport struct {
t http.RoundTripper
}
func (t *transport) RoundTrip(r *http.Request) (*http.Response, error) {
resp, err := t.t.RoundTrip(r)
if resp != nil && resp.Body != nil {
resp.Body = &lr{resp.Body, 8 << 20, r}
}
return resp, err
}
var httpClient = &http.Client{
Transport: &transport{
t: http.DefaultTransport,
},
Timeout: 30 * time.Second,
}

View file

@ -2,19 +2,15 @@
package mastodon
import (
"bytes"
"context"
"encoding/json"
"compress/gzip"
"errors"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"os"
"path"
"path/filepath"
"strings"
"github.com/tomnomnom/linkheader"
@ -34,6 +30,11 @@ type Client struct {
config *Config
}
type multipartRequest struct {
Data io.Reader
ContentType string
}
func (c *Client) doAPI(ctx context.Context, method string, uri string, params interface{}, res interface{}, pg *Pagination) error {
u, err := url.Parse(c.config.Server)
if err != nil {
@ -57,83 +58,12 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params in
if err != nil {
return err
}
} else if file, ok := params.(string); ok {
f, err := os.Open(file)
} else if mr, ok := params.(*multipartRequest); ok {
req, err = http.NewRequest(method, u.String(), mr.Data)
if err != nil {
return err
}
defer f.Close()
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
part, err := mw.CreateFormFile("file", filepath.Base(file))
if err != nil {
return err
}
_, err = io.Copy(part, f)
if err != nil {
return err
}
err = mw.Close()
if err != nil {
return err
}
req, err = http.NewRequest(method, u.String(), &buf)
if err != nil {
return err
}
ct = mw.FormDataContentType()
} else if file, ok := params.(*multipart.FileHeader); ok {
f, err := file.Open()
if err != nil {
return err
}
defer f.Close()
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
fname := filepath.Base(file.Filename)
err = mw.WriteField("description", fname)
if err != nil {
return err
}
part, err := mw.CreateFormFile("file", fname)
if err != nil {
return err
}
_, err = io.Copy(part, f)
if err != nil {
return err
}
err = mw.Close()
if err != nil {
return err
}
req, err = http.NewRequest(method, u.String(), &buf)
if err != nil {
return err
}
ct = mw.FormDataContentType()
} else if reader, ok := params.(io.Reader); ok {
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
part, err := mw.CreateFormFile("file", "upload")
if err != nil {
return err
}
_, err = io.Copy(part, reader)
if err != nil {
return err
}
err = mw.Close()
if err != nil {
return err
}
req, err = http.NewRequest(method, u.String(), &buf)
if err != nil {
return err
}
ct = mw.FormDataContentType()
ct = mr.ContentType
} else {
if method == http.MethodGet && pg != nil {
u.RawQuery = pg.toValues().Encode()
@ -183,7 +113,7 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params in
// NewClient return new mastodon API client.
func NewClient(config *Config) *Client {
return &Client{
Client: http.DefaultClient,
Client: httpClient,
config: config,
}
}
@ -219,6 +149,16 @@ func (c *Client) AuthenticateToken(ctx context.Context, authCode, redirectURI st
return c.authenticate(ctx, params)
}
func (c *Client) RevokeToken(ctx context.Context) error {
params := url.Values{
"client_id": {c.config.ClientID},
"client_secret": {c.config.ClientSecret},
"token": {c.GetAccessToken(ctx)},
}
return c.doAPI(ctx, http.MethodPost, "/oauth/revoke", params, nil, nil)
}
func (c *Client) authenticate(ctx context.Context, params url.Values) error {
u, err := url.Parse(c.config.Server)
if err != nil {

View file

@ -1,12 +1,14 @@
package mastodon
import (
"bytes"
"context"
"fmt"
"io"
"mime/multipart"
"net/http"
"net/url"
"path/filepath"
"time"
"encoding/json"
"path"
@ -405,30 +407,35 @@ func (c *Client) Search(ctx context.Context, q string, qType string, limit int,
return &results, nil
}
// UploadMedia upload a media attachment from a file.
func (c *Client) UploadMedia(ctx context.Context, file string) (*Attachment, error) {
var attachment Attachment
err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", file, &attachment, nil)
if err != nil {
return nil, err
}
return &attachment, nil
}
// UploadMediaFromReader uploads a media attachment from a io.Reader.
func (c *Client) UploadMediaFromReader(ctx context.Context, reader io.Reader) (*Attachment, error) {
var attachment Attachment
err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", reader, &attachment, nil)
if err != nil {
return nil, err
}
return &attachment, nil
}
// UploadMediaFromReader uploads a media attachment from a io.Reader.
func (c *Client) UploadMediaFromMultipartFileHeader(ctx context.Context, fh *multipart.FileHeader) (*Attachment, error) {
f, err := fh.Open()
if err != nil {
return nil, err
}
defer f.Close()
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
fname := filepath.Base(fh.Filename)
err = mw.WriteField("description", fname)
if err != nil {
return nil, err
}
part, err := mw.CreateFormFile("file", fname)
if err != nil {
return nil, err
}
_, err = io.Copy(part, f)
if err != nil {
return nil, err
}
err = mw.Close()
if err != nil {
return nil, err
}
params := &multipartRequest{Data: &buf, ContentType: mw.FormDataContentType()}
var attachment Attachment
err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", fh, &attachment, nil)
err = c.doAPI(ctx, http.MethodPost, "/api/v1/media", params, &attachment, nil)
if err != nil {
return nil, err
}