aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--mastodon/mastodon.go26
-rw-r--r--mastodon/status.go11
-rw-r--r--service/auth.go5
-rw-r--r--service/logging.go5
-rw-r--r--service/service.go15
-rw-r--r--service/transport.go27
-rw-r--r--templates/thread.tmpl4
-rw-r--r--templates/timeline.tmpl4
8 files changed, 86 insertions, 11 deletions
diff --git a/mastodon/mastodon.go b/mastodon/mastodon.go
index ff86d2b..22f5a53 100644
--- a/mastodon/mastodon.go
+++ b/mastodon/mastodon.go
@@ -83,6 +83,32 @@ func (c *Client) doAPI(ctx context.Context, method string, uri string, params in
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)
+ 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 {
var buf bytes.Buffer
mw := multipart.NewWriter(&buf)
diff --git a/mastodon/status.go b/mastodon/status.go
index fd69914..b6110d5 100644
--- a/mastodon/status.go
+++ b/mastodon/status.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"io"
+ "mime/multipart"
"net/http"
"net/url"
"time"
@@ -295,3 +296,13 @@ func (c *Client) UploadMediaFromReader(ctx context.Context, reader io.Reader) (*
}
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
+}
diff --git a/service/auth.go b/service/auth.go
index 5ee2001..98012af 100644
--- a/service/auth.go
+++ b/service/auth.go
@@ -5,6 +5,7 @@ import (
"errors"
"io"
"mastodon"
+ "mime/multipart"
"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)
}
-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)
if err != nil {
return
}
- return s.Service.PostTweet(ctx, client, c, content, replyToID)
+ return s.Service.PostTweet(ctx, client, c, content, replyToID, files)
}
diff --git a/service/logging.go b/service/logging.go
index 0ed40bd..3a95a94 100644
--- a/service/logging.go
+++ b/service/logging.go
@@ -5,6 +5,7 @@ import (
"io"
"log"
"mastodon"
+ "mime/multipart"
"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)
}
-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) {
s.logger.Printf("method=%v, content=%v, reply_to_id=%v, took=%v, err=%v\n",
"PostTweet", content, replyToID, time.Since(begin), err)
}(time.Now())
- return s.Service.PostTweet(ctx, client, c, content, replyToID)
+ return s.Service.PostTweet(ctx, client, c, content, replyToID, files)
}
diff --git a/service/service.go b/service/service.go
index 3bfb163..15dab5d 100644
--- a/service/service.go
+++ b/service/service.go
@@ -7,6 +7,7 @@ import (
"errors"
"fmt"
"io"
+ "mime/multipart"
"net/http"
"net/url"
"path"
@@ -36,7 +37,7 @@ type Service interface {
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)
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 {
@@ -292,10 +293,20 @@ func (svc *service) UnRetweet(ctx context.Context, client io.Writer, c *mastodon
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{
Status: content,
InReplyToID: replyToID,
+ MediaIDs: mediaIds,
}
s, err := c.PostStatus(ctx, tweet)
diff --git a/service/transport.go b/service/transport.go
index 00f7430..d5a6ee8 100644
--- a/service/transport.go
+++ b/service/transport.go
@@ -3,6 +3,7 @@ package service
import (
"context"
"fmt"
+ "mime/multipart"
"net/http"
"path"
@@ -153,9 +154,18 @@ func NewHandler(s Service, staticDir string) http.Handler {
r.HandleFunc("/post", func(w http.ResponseWriter, req *http.Request) {
ctx := getContextWithSession(context.Background(), req)
- content := req.FormValue("content")
- replyToID := req.FormValue("reply_to_id")
- id, err := s.PostTweet(ctx, w, nil, content, replyToID)
+
+ err := req.ParseMultipartForm(4 << 20)
+ 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 {
s.ServeErrorPage(ctx, w, err)
return
@@ -178,3 +188,14 @@ func NewHandler(s Service, staticDir string) http.Handler {
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]
+}
diff --git a/templates/thread.tmpl b/templates/thread.tmpl
index 4bdc2f0..a3f7916 100644
--- a/templates/thread.tmpl
+++ b/templates/thread.tmpl
@@ -8,12 +8,14 @@
{{template "status.tmpl" .Status}}
{{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}}" />
<label for="post-content"> Reply to {{.Status.Account.DisplayName}} </label>
<br/>
<textarea id="post-content" name="content" class="post-content" cols="50" rows="5">{{.ReplyContent}}</textarea>
<br/>
+ Attachments <input id="post-file-picker" type="file" name="attachments" multiple>
+ <br/>
<button type="submit"> Post </button>
</form>
{{end}}
diff --git a/templates/timeline.tmpl b/templates/timeline.tmpl
index 7f3234c..51bf12e 100644
--- a/templates/timeline.tmpl
+++ b/templates/timeline.tmpl
@@ -2,11 +2,13 @@
<div class="page-title"> Timeline </div>
{{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>
<br/>
<textarea id="post-content" name="content" class="post-content" cols="50" rows="5"></textarea>
<br/>
+ Attachments <input id="post-file-picker" type="file" name="attachments" multiple>
+ <br/>
<button type="submit"> Post </button>
</form>