From ddd5936fb1f92432123a9a30d1d3a1fa644a4f8e Mon Sep 17 00:00:00 2001 From: Alex Auvolat Date: Fri, 21 Feb 2020 15:57:53 +0100 Subject: [PATCH] Files&images Mattermost->Matrix works --- appservice/account.go | 39 +++++++++++++- appservice/server.go | 21 ++++---- connector/connector.go | 10 +++- connector/mattermost/mattermost.go | 24 +++++++++ connector/mediaobject.go | 85 ++++++++++++++++++++++++++++++ go.mod | 6 ++- go.sum | 12 +++++ mxlib/client.go | 6 +++ mxlib/mediaobject.go | 7 +++ 9 files changed, 198 insertions(+), 12 deletions(-) diff --git a/appservice/account.go b/appservice/account.go index 311f266..a2b95d7 100644 --- a/appservice/account.go +++ b/appservice/account.go @@ -249,6 +249,43 @@ func (a *Account) eventInternal(event *Event) error { typ = "m.emote" } - return mx.SendMessageAs(mx_room_id, typ, event.Text, mx_user_id) + err = mx.SendMessageAs(mx_room_id, typ, event.Text, mx_user_id) + if err != nil { + return err + } + + if event.Attachements != nil { + for _, file := range event.Attachements { + mxfile, err := mx.UploadMedia(file) + if err != nil { + return err + } + content := map[string]interface{} { + "body": mxfile.Filename(), + "filename": mxfile.Filename(), + "url": fmt.Sprintf("mxc://%s/%s", mxfile.MxcServer, mxfile.MxcMediaId), + } + if sz := mxfile.ImageSize(); sz != nil { + content["msgtype"] = "m.image" + content["info"] = map[string]interface{} { + "mimetype": mxfile.Mimetype(), + "size": mxfile.Size(), + "width": sz.Width, + "height": sz.Height, + } + } else { + content["msgtype"] = "m.file" + content["info"] = map[string]interface{} { + "mimetype": mxfile.Mimetype(), + "size": mxfile.Size(), + } + } + err = mx.SendAs(mx_room_id, "m.room.message", content, mx_user_id) + if err != nil { + return err + } + } + } + return nil } } diff --git a/appservice/server.go b/appservice/server.go index 48e26a6..5a5cebe 100644 --- a/appservice/server.go +++ b/appservice/server.go @@ -41,15 +41,18 @@ func Start(r *mxlib.Registration, c *Config) (chan error, error) { if mxe, ok := err.(*mxlib.MxError); !ok || mxe.ErrCode != "M_USER_IN_USE" { return nil, err } - err = mx.ProfileDisplayname(ezbrMxId(), fmt.Sprintf("Easybridge (%s)", EASYBRIDGE_SYSTEM_PROTOCOL)) - if err != nil { - return nil, err - } - err = mx.ProfileAvatar(ezbrMxId(), &connector.FileMediaObject{ - Path: "easybridge.jpg", - }) - if err != nil { - return nil, err + if err == nil { + // If Easybridge account was created, update avatar and display name + err = mx.ProfileAvatar(ezbrMxId(), &connector.FileMediaObject{ + Path: "easybridge.jpg", + }) + if err != nil { + return nil, err + } + err = mx.ProfileDisplayname(ezbrMxId(), fmt.Sprintf("Easybridge (%s)", EASYBRIDGE_SYSTEM_PROTOCOL)) + if err != nil { + return nil, err + } } router := mux.NewRouter() diff --git a/connector/connector.go b/connector/connector.go index d05f4a2..2bf1704 100644 --- a/connector/connector.go +++ b/connector/connector.go @@ -115,7 +115,7 @@ type Event struct { Text string // Attached files such as images - Attachements map[string]MediaObject + Attachements []MediaObject } type UserInfo struct { @@ -134,6 +134,9 @@ type MediaObject interface { Size() int64 Mimetype() string + // Returns the size of an image if it is an image, otherwise nil + ImageSize() *ImageSize + // Read: must always be implemented Read() (io.ReadCloser, error) @@ -141,3 +144,8 @@ type MediaObject interface { // If so, Read() is the only way to retrieve the object URL() string } + +type ImageSize struct { + Width int + Height int +} diff --git a/connector/mattermost/mattermost.go b/connector/mattermost/mattermost.go index 81adbde..330026a 100644 --- a/connector/mattermost/mattermost.go +++ b/connector/mattermost/mattermost.go @@ -345,6 +345,30 @@ func (mm *Mattermost) handlePosted(msg *model.WebSocketEvent) error { msg_ev.Type = EVENT_ACTION } + // Handle files + if post.FileIds != nil && len(post.FileIds) > 0 { + msg_ev.Attachements = []MediaObject{} + for _, file := range post.Metadata.Files { + blob, resp := mm.conn.Client.GetFile(file.Id) + if resp.Error != nil { + return resp.Error + } + media_object := &BlobMediaObject{ + ObjectFilename: file.Name, + ObjectSize: file.Size, + ObjectMimetype: file.MimeType, + ObjectData: blob, + } + if file.Width > 0 { + media_object.ObjectImageSize = &ImageSize{ + Width: file.Width, + Height: file.Height, + } + } + msg_ev.Attachements = append(msg_ev.Attachements, media_object) + } + } + // Dispatch as PM or as room message if len(strings.Split(channel_name, "__")) == 2 { // Private message, no need to find room id diff --git a/connector/mediaobject.go b/connector/mediaobject.go index 75635ee..c6634b7 100644 --- a/connector/mediaobject.go +++ b/connector/mediaobject.go @@ -1,6 +1,7 @@ package connector import ( + "bytes" "io" "net/http" "os" @@ -41,6 +42,11 @@ func (m *FileMediaObject) Mimetype() string { return http.DetectContentType(buffer) } +func (m *FileMediaObject) ImageSize() *ImageSize { + // TODO but not really usefull + return nil +} + func (m *FileMediaObject) Read() (io.ReadCloser, error) { return os.Open(m.Path) } @@ -48,3 +54,82 @@ func (m *FileMediaObject) Read() (io.ReadCloser, error) { func (m *FileMediaObject) URL() string { return "" } + +// ---- + +type UrlMediaObject struct { + ObjectFilename string + ObjectSize int64 + ObjectMimetype string + ObjectURL string + ObjectImageSize *ImageSize +} + +func (m *UrlMediaObject) Filename() string { + return m.ObjectFilename +} + +func (m *UrlMediaObject) Size() int64 { + return m.ObjectSize +} + +func (m *UrlMediaObject) Mimetype() string { + return m.ObjectMimetype +} + +func (m *UrlMediaObject) ImageSize() *ImageSize { + return m.ObjectImageSize +} + +func (m *UrlMediaObject) Read() (io.ReadCloser, error) { + resp, err := http.Get(m.ObjectURL) + if err != nil { + return nil, err + } + return resp.Body, nil +} + +func (m *UrlMediaObject) URL() string { + return m.ObjectURL +} + +// ---- + +type BlobMediaObject struct { + ObjectFilename string + ObjectSize int64 + ObjectMimetype string + ObjectImageSize *ImageSize + ObjectData []byte +} + +func (m *BlobMediaObject) Filename() string { + return m.ObjectFilename +} + +func (m *BlobMediaObject) Size() int64 { + return m.ObjectSize +} + +func (m *BlobMediaObject) Mimetype() string { + return m.ObjectMimetype +} + +func (m *BlobMediaObject) ImageSize() *ImageSize { + return m.ObjectImageSize +} + +func (m *BlobMediaObject) Read() (io.ReadCloser, error) { + return nullCloseReader{bytes.NewBuffer(m.ObjectData)}, nil +} + +func (m *BlobMediaObject) URL() string { + return "" +} + +type nullCloseReader struct { + io.Reader +} +func (ncr nullCloseReader) Close() error { + return nil +} diff --git a/go.mod b/go.mod index 1e54bf0..d7fdba8 100644 --- a/go.mod +++ b/go.mod @@ -4,12 +4,16 @@ go 1.13 require ( github.com/42wim/matterbridge v1.16.5 + github.com/blang/semver v3.5.1+incompatible // indirect + github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 // indirect + github.com/go-ldap/ldap v3.0.3+incompatible // indirect github.com/gorilla/mux v1.7.4 github.com/jinzhu/gorm v1.9.12 github.com/lrstanley/girc v0.0.0-20190801035559-4fc93959e1a7 github.com/matterbridge/go-xmpp v0.0.0-20180529212104-cd19799fba91 - github.com/mattermost/mattermost-server v5.5.0+incompatible + github.com/mattermost/mattermost-server v5.11.1+incompatible github.com/mattn/go-xmpp v0.0.0-20200128155807-a86b6abcb3ad github.com/sirupsen/logrus v1.4.2 + gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d // indirect gopkg.in/yaml.v2 v2.2.8 ) diff --git a/go.sum b/go.sum index f157a34..22f0e27 100644 --- a/go.sum +++ b/go.sum @@ -20,6 +20,8 @@ github.com/alexcesaro/log v0.0.0-20150915221235-61e686294e58/go.mod h1:YNfsMyWSs github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8= +github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdnnjpJbkM4JQ= +github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/bwmarrin/discordgo v0.20.2/go.mod h1:O9S4p+ofTFwB02em7jkpkV8M3R0/PUVOwN61zSZ0r4Q= github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= @@ -35,10 +37,14 @@ github.com/denisenkom/go-mssqldb v0.0.0-20191124224453-732737034ffd/go.mod h1:xb github.com/dfordsoft/golib v0.0.0-20180902042739-76ee6ab99bec/go.mod h1:UGa5M2Sz/Uh13AMse4+RELKCDw7kqgqlTjeGae+7vUY= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= github.com/dgryski/go-sip13 v0.0.0-20181026042036-e10d5fee7954/go.mod h1:vAd38F8PWV+bWy6jNmig1y/TA+kYO4g3RSRF0IAv0no= +github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8 h1:6muCmMJat6z7qptVrIf/+OWPxsjAfvhw5/6t+FwEkgg= +github.com/dyatlov/go-opengraph v0.0.0-20180429202543-816b6608b3c8/go.mod h1:nYia/MIs9OyvXXYboPmNOj0gVWo97Wx0sde+ZuKkoM4= github.com/erikstmartin/go-testdb v0.0.0-20160219214506-8d10e4a1bae5/go.mod h1:a2zkGnVExMxdzMo3M0Hi/3sEU+cWnZpSni0O6/Yb/P0= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/ghodss/yaml v1.0.0/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= +github.com/go-ldap/ldap v3.0.3+incompatible h1:HTeSZO8hWMS1Rgb2Ziku6b8a7qRIZZMHjsvuZyatzwk= +github.com/go-ldap/ldap v3.0.3+incompatible/go.mod h1:qfd9rJvER9Q0/D/Sqn1DfHRoBp40uXYvFoEVrNEPqRc= github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE= github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk= github.com/go-ole/go-ole v1.2.1/go.mod h1:7FAglXiTm7HKlQRDeOQ6ZNUHidzCWXuZWq/1dTyBNF8= @@ -116,6 +122,8 @@ github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749 github.com/matterbridge/logrus-prefixed-formatter v0.0.0-20180806162718-01618749af61/go.mod h1:iXGEotOvwI1R1SjLxRc+BF5rUORTMtE0iMZBT2lxqAU= github.com/mattermost/mattermost-server v5.5.0+incompatible h1:0wcLGgYtd+YImtLDPf2AOfpBHxbU4suATx+6XKw1XbU= github.com/mattermost/mattermost-server v5.5.0+incompatible/go.mod h1:5L6MjAec+XXQwMIt791Ganu45GKsSiM+I0tLR9wUj8Y= +github.com/mattermost/mattermost-server v5.11.1+incompatible h1:LPzKY0+2Tic/ik67qIg6VrydRCgxNXZQXOeaiJ2rMBY= +github.com/mattermost/mattermost-server v5.11.1+incompatible/go.mod h1:5L6MjAec+XXQwMIt791Ganu45GKsSiM+I0tLR9wUj8Y= github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= github.com/mattn/go-colorable v0.1.2/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= @@ -225,6 +233,7 @@ golang.org/x/net v0.0.0-20181220203305-927f97764cc3/go.mod h1:mL1N/T3taQHkDXs73r golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190522155817-f3200d17e092/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553 h1:efeOvDhwQ29Dj3SdAV/MJf8oukgn+8D8WgaCaRMchF8= golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -246,6 +255,7 @@ golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8 h1:JA8d3MPx/IToSyXZG/RhwYEtfrKO1Fxrqe8KrkiLXKM= golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= @@ -260,6 +270,8 @@ google.golang.org/genproto v0.0.0-20180831171423-11092d34479b/go.mod h1:JiN7NxoA google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.21.0/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d h1:TxyelI5cVkbREznMhfzycHdkp5cLA7DpE+GKjSslYhM= +gopkg.in/asn1-ber.v1 v1.0.0-20181015200546-f715ec2f112d/go.mod h1:cuepJuh7vyXfUyUwEgHQXw849cJrilpS5NeIjOWESAw= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= diff --git a/mxlib/client.go b/mxlib/client.go index 6d87663..d8237d1 100644 --- a/mxlib/client.go +++ b/mxlib/client.go @@ -280,6 +280,11 @@ func (mx *Client) RoomTopicAs(room string, topic string, as_user string) error { } func (mx *Client) UploadMedia(m connector.MediaObject) (*MediaObject, error) { + // Return early if this is already a Matrix media object + if mxm, ok := m.(*MediaObject); ok { + return mxm, nil + } + reader, err := m.Read() if err != nil { return nil, err @@ -308,6 +313,7 @@ func (mx *Client) UploadMedia(m connector.MediaObject) (*MediaObject, error) { filename: m.Filename(), size: m.Size(), mimetype: m.Mimetype(), + imageSize: m.ImageSize(), MxcServer: mxc[0], MxcMediaId: mxc[1], } diff --git a/mxlib/mediaobject.go b/mxlib/mediaobject.go index 8a730d2..1c35187 100644 --- a/mxlib/mediaobject.go +++ b/mxlib/mediaobject.go @@ -5,6 +5,8 @@ import ( "fmt" "net/url" "net/http" + + "git.deuxfleurs.fr/Deuxfleurs/easybridge/connector" ) type MediaObject struct { @@ -12,6 +14,7 @@ type MediaObject struct { filename string size int64 mimetype string + imageSize *connector.ImageSize MxcServer string MxcMediaId string } @@ -28,6 +31,10 @@ func (m *MediaObject) Mimetype() string { return m.mimetype } +func (m *MediaObject) ImageSize() *connector.ImageSize { + return m.imageSize +} + func (m *MediaObject) Read() (io.ReadCloser, error) { req, err := http.NewRequest("GET", m.URL(), nil) if err != nil {