Add attachments support
This commit is contained in:
parent
ea66bd539d
commit
e129ea922e
|
@ -83,6 +83,32 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params in
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
ct = mw.FormDataContentType()
|
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)
|
||||||
|
part, err := mw.CreateFormFile("file", filepath.Base(file.Filename))
|
||||||
|
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 {
|
} else if reader, ok := params.(io.Reader); ok {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
mw := multipart.NewWriter(&buf)
|
mw := multipart.NewWriter(&buf)
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
|
@ -295,3 +296,13 @@ func (c *Client) UploadMediaFromReader(ctx context.Context, reader io.Reader) (*
|
||||||
}
|
}
|
||||||
return &attachment, nil
|
return &attachment, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// UploadMediaFromReader uploads a media attachment from a io.Reader.
|
||||||
|
func (c *Client) UploadMediaFromMultipartFileHeader(ctx context.Context, fh *multipart.FileHeader) (*Attachment, error) {
|
||||||
|
var attachment Attachment
|
||||||
|
err := c.doAPI(ctx, http.MethodPost, "/api/v1/media", fh, &attachment, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &attachment, nil
|
||||||
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"io"
|
"io"
|
||||||
"mastodon"
|
"mastodon"
|
||||||
|
"mime/multipart"
|
||||||
"web/model"
|
"web/model"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -142,10 +143,10 @@ func (s *authService) UnRetweet(ctx context.Context, client io.Writer, c *mastod
|
||||||
return s.Service.UnRetweet(ctx, client, c, id)
|
return s.Service.UnRetweet(ctx, client, c, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (id string, err error) {
|
func (s *authService) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string, files []*multipart.FileHeader) (id string, err error) {
|
||||||
c, err = s.getClient(ctx)
|
c, err = s.getClient(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return s.Service.PostTweet(ctx, client, c, content, replyToID)
|
return s.Service.PostTweet(ctx, client, c, content, replyToID, files)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"io"
|
"io"
|
||||||
"log"
|
"log"
|
||||||
"mastodon"
|
"mastodon"
|
||||||
|
"mime/multipart"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -108,10 +109,10 @@ func (s *loggingService) UnRetweet(ctx context.Context, client io.Writer, c *mas
|
||||||
return s.Service.UnRetweet(ctx, client, c, id)
|
return s.Service.UnRetweet(ctx, client, c, id)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *loggingService) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (id string, err error) {
|
func (s *loggingService) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string, files []*multipart.FileHeader) (id string, err error) {
|
||||||
defer func(begin time.Time) {
|
defer func(begin time.Time) {
|
||||||
s.logger.Printf("method=%v, content=%v, reply_to_id=%v, took=%v, err=%v\n",
|
s.logger.Printf("method=%v, content=%v, reply_to_id=%v, took=%v, err=%v\n",
|
||||||
"PostTweet", content, replyToID, time.Since(begin), err)
|
"PostTweet", content, replyToID, time.Since(begin), err)
|
||||||
}(time.Now())
|
}(time.Now())
|
||||||
return s.Service.PostTweet(ctx, client, c, content, replyToID)
|
return s.Service.PostTweet(ctx, client, c, content, replyToID, files)
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,6 +7,7 @@ import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/url"
|
"net/url"
|
||||||
"path"
|
"path"
|
||||||
|
@ -36,7 +37,7 @@ type Service interface {
|
||||||
UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
|
UnLike(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
|
||||||
Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
|
Retweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
|
||||||
UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
|
UnRetweet(ctx context.Context, client io.Writer, c *mastodon.Client, id string) (err error)
|
||||||
PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (id string, err error)
|
PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string, files []*multipart.FileHeader) (id string, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
type service struct {
|
type service struct {
|
||||||
|
@ -292,10 +293,20 @@ func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *mastodon
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string) (id string, err error) {
|
func (svc *service) PostTweet(ctx context.Context, client io.Writer, c *mastodon.Client, content string, replyToID string, files []*multipart.FileHeader) (id string, err error) {
|
||||||
|
var mediaIds []string
|
||||||
|
for _, f := range files {
|
||||||
|
a, err := c.UploadMediaFromMultipartFileHeader(ctx, f)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
mediaIds = append(mediaIds, a.ID)
|
||||||
|
}
|
||||||
|
|
||||||
tweet := &mastodon.Toot{
|
tweet := &mastodon.Toot{
|
||||||
Status: content,
|
Status: content,
|
||||||
InReplyToID: replyToID,
|
InReplyToID: replyToID,
|
||||||
|
MediaIDs: mediaIds,
|
||||||
}
|
}
|
||||||
|
|
||||||
s, err := c.PostStatus(ctx, tweet)
|
s, err := c.PostStatus(ctx, tweet)
|
||||||
|
|
|
@ -3,6 +3,7 @@ package service
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"path"
|
"path"
|
||||||
|
|
||||||
|
@ -153,9 +154,18 @@ func NewHandler(s Service, staticDir string) http.Handler {
|
||||||
|
|
||||||
r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
|
r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
|
||||||
ctx := getContextWithSession(context.Background(), req)
|
ctx := getContextWithSession(context.Background(), req)
|
||||||
content := req.FormValue("content")
|
|
||||||
replyToID := req.FormValue("reply_to_id")
|
err := req.ParseMultipartForm(4 << 20)
|
||||||
id, err := s.PostTweet(ctx, w, nil, content, replyToID)
|
if err != nil {
|
||||||
|
s.ServeErrorPage(ctx, w, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
content := getMultipartFormValue(req.MultipartForm, "content")
|
||||||
|
replyToID := getMultipartFormValue(req.MultipartForm, "reply_to_id")
|
||||||
|
files := req.MultipartForm.File["attachments"]
|
||||||
|
|
||||||
|
id, err := s.PostTweet(ctx, w, nil, content, replyToID, files)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
s.ServeErrorPage(ctx, w, err)
|
s.ServeErrorPage(ctx, w, err)
|
||||||
return
|
return
|
||||||
|
@ -178,3 +188,14 @@ func NewHandler(s Service, staticDir string) http.Handler {
|
||||||
|
|
||||||
return r
|
return r
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func getMultipartFormValue(mf *multipart.Form, key string) (val string) {
|
||||||
|
vals, ok := mf.Value[key]
|
||||||
|
if !ok {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
if len(vals) < 1 {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
return vals[0]
|
||||||
|
}
|
||||||
|
|
|
@ -8,12 +8,14 @@
|
||||||
|
|
||||||
{{template "status.tmpl" .Status}}
|
{{template "status.tmpl" .Status}}
|
||||||
{{if .PostReply}}
|
{{if .PostReply}}
|
||||||
<form class="timeline-post-form" action="/post" method="POST">
|
<form class="timeline-post-form" action="/post" method="POST" enctype="multipart/form-data">
|
||||||
<input type="hidden" name="reply_to_id" value="{{.ReplyToID}}" />
|
<input type="hidden" name="reply_to_id" value="{{.ReplyToID}}" />
|
||||||
<label for="post-content"> Reply to {{.Status.Account.DisplayName}} </label>
|
<label for="post-content"> Reply to {{.Status.Account.DisplayName}} </label>
|
||||||
<br/>
|
<br/>
|
||||||
<textarea id="post-content" name="content" class="post-content" cols="50" rows="5">{{.ReplyContent}}</textarea>
|
<textarea id="post-content" name="content" class="post-content" cols="50" rows="5">{{.ReplyContent}}</textarea>
|
||||||
<br/>
|
<br/>
|
||||||
|
Attachments <input id="post-file-picker" type="file" name="attachments" multiple>
|
||||||
|
<br/>
|
||||||
<button type="submit"> Post </button>
|
<button type="submit"> Post </button>
|
||||||
</form>
|
</form>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
|
@ -2,11 +2,13 @@
|
||||||
<div class="page-title"> Timeline </div>
|
<div class="page-title"> Timeline </div>
|
||||||
{{template "navigation.tmpl"}}
|
{{template "navigation.tmpl"}}
|
||||||
|
|
||||||
<form class="timeline-post-form" action="/post" method="POST">
|
<form class="timeline-post-form" action="/post" method="POST" enctype="multipart/form-data">
|
||||||
<label for="post-content"> New Post </label>
|
<label for="post-content"> New Post </label>
|
||||||
<br/>
|
<br/>
|
||||||
<textarea id="post-content" name="content" class="post-content" cols="50" rows="5"></textarea>
|
<textarea id="post-content" name="content" class="post-content" cols="50" rows="5"></textarea>
|
||||||
<br/>
|
<br/>
|
||||||
|
Attachments <input id="post-file-picker" type="file" name="attachments" multiple>
|
||||||
|
<br/>
|
||||||
<button type="submit"> Post </button>
|
<button type="submit"> Post </button>
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue