Skip to content

Commit

Permalink
Improve the way to send files (#25)
Browse files Browse the repository at this point in the history
  • Loading branch information
skwair committed Feb 8, 2019
1 parent c6458d5 commit a77e5ad
Show file tree
Hide file tree
Showing 5 changed files with 107 additions and 28 deletions.
14 changes: 4 additions & 10 deletions channel_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import (
"context"
"encoding/json"
"errors"
"io"
"net/http"
"net/url"
"strconv"
Expand Down Expand Up @@ -219,9 +218,11 @@ func WithEmbed(e *embed.Embed) MessageOption {
}

// WithFiles attach files to a message.
func WithFiles(files ...File) MessageOption {
func WithFiles(files ...*File) MessageOption {
return MessageOption(func(m *createMessage) {
m.files = files
for _, f := range files {
m.files = append(m.files, *f)
}
})
}

Expand Down Expand Up @@ -281,13 +282,6 @@ func (cm *createMessage) json() ([]byte, error) {
return json.Marshal(cm)
}

// File is a file along with its name. It is used to send files
// to channels with SendFiles.
type File struct {
Name string
Reader io.Reader
}

func (c *Client) sendMessage(ctx context.Context, channelID string, msg *createMessage) (*Message, error) {
if msg.Embed != nil && msg.Embed.Type == "" {
msg.Embed.Type = "rich"
Expand Down
1 change: 1 addition & 0 deletions examples/03.files/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
03.files
17 changes: 6 additions & 11 deletions examples/03.files/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,22 +49,17 @@ func main() {

func (b *bot) onNewMessage(m *harmony.Message) {
if m.Content == "!file" {
f, err := os.Open("discord-gopher.png")
// Here, we demonstrate the FileFromDisk function to send a file present
// on the local filesystem. If the resource you want to send is a URL,
// use FileFromURL instead.
// If you already have your own reader, then FileFromReadCloser is the
// function you want to use.
file, err := harmony.FileFromDisk("discord-gopher.png", "zob")
if err != nil {
log.Println(err)
return
}

file := harmony.File{
// Setting a valid extension type here,
// such as "png", will allow Discord
// applications to display the files
// inline, instead of asking users
// to download them.
Name: "discord-gopher.png",
Reader: f,
}

if _, err = b.client.Channel(m.ChannelID).Send(context.Background(), harmony.WithFiles(file)); err != nil {
log.Println(err)
}
Expand Down
85 changes: 85 additions & 0 deletions file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
package harmony

import (
"fmt"
"io"
"net/http"
"os"
"path"
)

// File represents a file that can be sent with Send and the WithFiles option.
type File struct {
name string
reader io.ReadCloser
}

// FileFromReadCloser returns a File given a ReadCloser and a name.
// If the name ends with a valid extension recognized by Discord's
// client applications, the file will be displayed inline in the channel
// instead of asking users to manually download it.
func FileFromReadCloser(r io.ReadCloser, name string) *File {
return &File{
name: name,
reader: r,
}
}

// FileFromDisk returns a File from a local, on disk file.
// If name is left empty, it will default to the name of the
// file on disk.
// Note that since Send is responsible for closing files opened
// by FileFromDisk, calling this function and *not* calling Send
// after can lead to resource leaks.
func FileFromDisk(filepath, name string) (*File, error) {
f, err := os.Open(filepath)
if err != nil {
return nil, err
}

if name == "" {
name = path.Base(filepath)
}

return FileFromReadCloser(f, name), nil
}

// FileFromURL returns a File from a remote HTTP resource.
// If the name is left empty, it will default to the name of
// the file specified in the URL.
// Note that since Send is responsible for closing files opened
// by FileFromURL, calling this function and *not* calling Send
// after can lead to resource leaks.
func FileFromURL(url, name string) (*File, error) {
resp, err := http.Get(url)
if err != nil {
return nil, err
}

ct := resp.Header.Get("Content-Type")
if !stringsContains(supportedContentTypes, ct) {
return nil, fmt.Errorf("unsupported Content-Type: %q", ct)
}

if name == "" {
name = path.Base(url)
}

return FileFromReadCloser(resp.Body, name), nil
}

var supportedContentTypes = []string{
"image/png",
"image/gif",
"image/jpeg",
"video/mp4",
}

func stringsContains(stack []string, needle string) bool {
for _, hay := range stack {
if needle == hay {
return true
}
}
return false
}
18 changes: 11 additions & 7 deletions multipart_upload.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ type multipartPayload interface {
// It returns the raw generated body along a header with the proper Content-Type value set.
func multipartFromFiles(payload multipartPayload, files ...File) ([]byte, http.Header, error) {
// Underlying buffer the multipart body will be written to.
var b bytes.Buffer
w := multipart.NewWriter(&b)
var buf bytes.Buffer
w := multipart.NewWriter(&buf)

// Send the endpoint parameters as JSON in a the "payload_json" part.
h := textproto.MIMEHeader{}
Expand All @@ -29,17 +29,17 @@ func multipartFromFiles(payload multipartPayload, files ...File) ([]byte, http.H
return nil, nil, err
}

bytes, err := payload.json()
b, err := payload.json()
if err != nil {
return nil, nil, err
}
if _, err = pw.Write(bytes); err != nil {
if _, err = pw.Write(b); err != nil {
return nil, nil, err
}

// Create a new part for each file.
for i, f := range files {
cd := fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, f.Name)
cd := fmt.Sprintf(`form-data; name="file%d"; filename="%s"`, i, f.name)

h = textproto.MIMEHeader{}
h.Set("Content-Disposition", cd)
Expand All @@ -50,7 +50,11 @@ func multipartFromFiles(payload multipartPayload, files ...File) ([]byte, http.H
return nil, nil, err
}

if _, err = io.Copy(pw, f.Reader); err != nil {
if _, err = io.Copy(pw, f.reader); err != nil {
return nil, nil, err
}

if err = f.reader.Close(); err != nil {
return nil, nil, err
}
}
Expand All @@ -61,5 +65,5 @@ func multipartFromFiles(payload multipartPayload, files ...File) ([]byte, http.H

header := http.Header{}
header.Set("Content-Type", w.FormDataContentType())
return b.Bytes(), header, nil
return buf.Bytes(), header, nil
}

0 comments on commit a77e5ad

Please sign in to comment.