Add basic support for incoming media messages
This commit is contained in:
parent
5542492a32
commit
7372bc4927
5 changed files with 106 additions and 7 deletions
|
@ -10,7 +10,7 @@
|
||||||
* Google Messages → Matrix
|
* Google Messages → Matrix
|
||||||
* [ ] Message content
|
* [ ] Message content
|
||||||
* [x] Plain text
|
* [x] Plain text
|
||||||
* [ ] Media/files
|
* [x] Media/files
|
||||||
* [ ] Replies (RCS)
|
* [ ] Replies (RCS)
|
||||||
* [ ] Reactions
|
* [ ] Reactions
|
||||||
* [ ] Typing notifications
|
* [ ] Typing notifications
|
||||||
|
|
1
go.mod
1
go.mod
|
@ -13,6 +13,7 @@ require (
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
github.com/coreos/go-systemd/v22 v22.5.0 // indirect
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
|
||||||
github.com/google/go-cmp v0.5.9 // indirect
|
github.com/google/go-cmp v0.5.9 // indirect
|
||||||
github.com/google/uuid v1.3.0 // indirect
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
github.com/gorilla/mux v1.8.0 // indirect
|
github.com/gorilla/mux v1.8.0 // indirect
|
||||||
|
|
2
go.sum
2
go.sum
|
@ -3,6 +3,8 @@ github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8
|
||||||
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
|
||||||
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
|
||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
|
||||||
|
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
|
||||||
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
|
||||||
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
|
||||||
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
|
||||||
|
|
|
@ -212,7 +212,7 @@ func (c *Client) decryptMedias(messages *binary.FetchMessagesResponse) error {
|
||||||
for _, details := range msg.GetMessageInfo() {
|
for _, details := range msg.GetMessageInfo() {
|
||||||
switch data := details.GetData().(type) {
|
switch data := details.GetData().(type) {
|
||||||
case *binary.MessageInfo_MediaContent:
|
case *binary.MessageInfo_MediaContent:
|
||||||
decryptedMediaData, err := c.decryptMediaData(data.MediaContent.MediaID, data.MediaContent.DecryptionKey)
|
decryptedMediaData, err := c.DownloadMedia(data.MediaContent.MediaID, data.MediaContent.DecryptionKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
return err
|
return err
|
||||||
|
@ -224,11 +224,11 @@ func (c *Client) decryptMedias(messages *binary.FetchMessagesResponse) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) decryptMediaData(mediaId string, key []byte) ([]byte, error) {
|
func (c *Client) DownloadMedia(mediaID string, key []byte) ([]byte, error) {
|
||||||
reqId := util.RandomUUIDv4()
|
reqId := util.RandomUUIDv4()
|
||||||
download_metadata := &binary.UploadImagePayload{
|
download_metadata := &binary.UploadImagePayload{
|
||||||
MetaData: &binary.ImageMetaData{
|
MetaData: &binary.ImageMetaData{
|
||||||
ImageID: mediaId,
|
ImageID: mediaID,
|
||||||
Encrypted: true,
|
Encrypted: true,
|
||||||
},
|
},
|
||||||
AuthData: &binary.AuthMessage{
|
AuthData: &binary.AuthMessage{
|
||||||
|
|
102
portal.go
102
portal.go
|
@ -27,6 +27,7 @@ import (
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/gabriel-vasile/mimetype"
|
||||||
"github.com/rs/zerolog"
|
"github.com/rs/zerolog"
|
||||||
log "maunium.net/go/maulogger/v2"
|
log "maunium.net/go/maulogger/v2"
|
||||||
|
|
||||||
|
@ -312,6 +313,16 @@ func (portal *Portal) isOutgoingMessage(evt *binary.Message) id.EventID {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasInProgressMedia(msg *binary.Message) bool {
|
||||||
|
for _, part := range msg.MessageInfo {
|
||||||
|
media, ok := part.GetData().(*binary.MessageInfo_MediaContent)
|
||||||
|
if ok && media.MediaContent.GetMediaID() == "" {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) handleMessage(source *User, evt *binary.Message) {
|
func (portal *Portal) handleMessage(source *User, evt *binary.Message) {
|
||||||
if len(portal.MXID) == 0 {
|
if len(portal.MXID) == 0 {
|
||||||
portal.zlog.Warn().Msg("handleMessage called even though portal.MXID is empty")
|
portal.zlog.Warn().Msg("handleMessage called even though portal.MXID is empty")
|
||||||
|
@ -322,6 +333,15 @@ func (portal *Portal) handleMessage(source *User, evt *binary.Message) {
|
||||||
Str("participant_id", evt.ParticipantID).
|
Str("participant_id", evt.ParticipantID).
|
||||||
Str("action", "handleMessage").
|
Str("action", "handleMessage").
|
||||||
Logger()
|
Logger()
|
||||||
|
switch evt.GetMessageStatus().GetStatus() {
|
||||||
|
case binary.MessageStatusType_INCOMING_AUTO_DOWNLOADING, binary.MessageStatusType_INCOMING_RETRYING_AUTO_DOWNLOAD:
|
||||||
|
log.Debug().Msg("Not handling incoming message that is auto downloading")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if hasInProgressMedia(evt) {
|
||||||
|
log.Debug().Msg("Not handling incoming message that doesn't have full media yet")
|
||||||
|
return
|
||||||
|
}
|
||||||
if evtID := portal.isOutgoingMessage(evt); evtID != "" {
|
if evtID := portal.isOutgoingMessage(evt); evtID != "" {
|
||||||
log.Debug().Str("event_id", evtID.String()).Msg("Got echo for outgoing message")
|
log.Debug().Str("event_id", evtID.String()).Msg("Got echo for outgoing message")
|
||||||
return
|
return
|
||||||
|
@ -366,9 +386,15 @@ func (portal *Portal) handleMessage(source *User, evt *binary.Message) {
|
||||||
Body: data.MessageContent.GetContent(),
|
Body: data.MessageContent.GetContent(),
|
||||||
}
|
}
|
||||||
case *binary.MessageInfo_MediaContent:
|
case *binary.MessageInfo_MediaContent:
|
||||||
content = event.MessageEventContent{
|
contentPtr, err := portal.convertGoogleMedia(source, intent, data.MediaContent)
|
||||||
MsgType: event.MsgNotice,
|
if err != nil {
|
||||||
Body: fmt.Sprintf("Attachment %s", data.MediaContent.GetMediaName()),
|
log.Err(err).Msg("Failed to copy attachment")
|
||||||
|
content = event.MessageEventContent{
|
||||||
|
MsgType: event.MsgNotice,
|
||||||
|
Body: fmt.Sprintf("Failed to transfer attachment %s", data.MediaContent.GetMediaName()),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
content = *contentPtr
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
resp, err := portal.sendMessage(intent, event.EventMessage, &content, nil, ts)
|
resp, err := portal.sendMessage(intent, event.EventMessage, &content, nil, ts)
|
||||||
|
@ -384,6 +410,76 @@ func (portal *Portal) handleMessage(source *User, evt *binary.Message) {
|
||||||
log.Debug().Interface("event_ids", eventIDs).Msg("Handled message")
|
log.Debug().Interface("event_ids", eventIDs).Msg("Handled message")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediaFormatToMime = map[binary.MediaFormats]string{
|
||||||
|
binary.MediaFormats_UNSPECIFIED_TYPE: "",
|
||||||
|
|
||||||
|
binary.MediaFormats_IMAGE_JPEG: "image/jpeg",
|
||||||
|
binary.MediaFormats_IMAGE_JPG: "image/jpeg",
|
||||||
|
binary.MediaFormats_IMAGE_PNG: "image/png",
|
||||||
|
binary.MediaFormats_IMAGE_GIF: "image/gif",
|
||||||
|
binary.MediaFormats_IMAGE_WBMP: "image/vnd.wap.vbmp",
|
||||||
|
binary.MediaFormats_IMAGE_X_MS_BMP: "image/bmp",
|
||||||
|
binary.MediaFormats_IMAGE_UNSPECIFIED: "",
|
||||||
|
|
||||||
|
binary.MediaFormats_VIDEO_MP4: "video/mp4",
|
||||||
|
binary.MediaFormats_VIDEO_3G2: "video/3gpp2",
|
||||||
|
binary.MediaFormats_VIDEO_3GPP: "video/3gpp",
|
||||||
|
binary.MediaFormats_VIDEO_WEBM: "video/webm",
|
||||||
|
binary.MediaFormats_VIDEO_MKV: "video/x-matroska",
|
||||||
|
binary.MediaFormats_VIDEO_UNSPECIFIED: "",
|
||||||
|
|
||||||
|
binary.MediaFormats_AUDIO_AAC: "audio/aac",
|
||||||
|
binary.MediaFormats_AUDIO_AMR: "audio/amr",
|
||||||
|
binary.MediaFormats_AUDIO_MP3: "audio/mp3",
|
||||||
|
binary.MediaFormats_AUDIO_MPEG: "audio/mpeg",
|
||||||
|
binary.MediaFormats_AUDIO_MPG: "audio/mpeg",
|
||||||
|
binary.MediaFormats_AUDIO_MP4: "audio/mp4",
|
||||||
|
binary.MediaFormats_AUDIO_MP4_LATM: "audio/mp4a-latm",
|
||||||
|
binary.MediaFormats_AUDIO_3GPP: "audio/3gpp",
|
||||||
|
binary.MediaFormats_AUDIO_OGG: "audio/ogg",
|
||||||
|
binary.MediaFormats_AUDIO_OGG2: "audio/ogg",
|
||||||
|
binary.MediaFormats_AUDIO_UNSPECIFIED: "",
|
||||||
|
|
||||||
|
binary.MediaFormats_TEXT_VCARD: "text/vcard",
|
||||||
|
binary.MediaFormats_APP_PDF: "application/pdf",
|
||||||
|
binary.MediaFormats_APP_TXT: "text/plain",
|
||||||
|
binary.MediaFormats_APP_HTML: "text/html",
|
||||||
|
binary.MediaFormats_APP_SMIL: "application/smil",
|
||||||
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) convertGoogleMedia(source *User, intent *appservice.IntentAPI, msg *binary.MediaContent) (*event.MessageEventContent, error) {
|
||||||
|
var data []byte
|
||||||
|
var err error
|
||||||
|
data, err = source.Client.DownloadMedia(msg.MediaID, msg.DecryptionKey)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
mime := mediaFormatToMime[msg.GetFormat()]
|
||||||
|
if mime == "" {
|
||||||
|
mime = mimetype.Detect(data).String()
|
||||||
|
}
|
||||||
|
msgtype := event.MsgFile
|
||||||
|
switch strings.Split(mime, "/")[0] {
|
||||||
|
case "image":
|
||||||
|
msgtype = event.MsgImage
|
||||||
|
case "video":
|
||||||
|
msgtype = event.MsgVideo
|
||||||
|
// TODO convert weird formats to mp4
|
||||||
|
case "audio":
|
||||||
|
msgtype = event.MsgAudio
|
||||||
|
// TODO convert everything to ogg and include voice message metadata
|
||||||
|
}
|
||||||
|
content := &event.MessageEventContent{
|
||||||
|
MsgType: msgtype,
|
||||||
|
Body: msg.MediaName,
|
||||||
|
Info: &event.FileInfo{
|
||||||
|
MimeType: mime,
|
||||||
|
Size: len(data),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return content, portal.uploadMedia(intent, data, content)
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) isRecentlyHandled(id string) bool {
|
func (portal *Portal) isRecentlyHandled(id string) bool {
|
||||||
start := portal.recentlyHandledIndex
|
start := portal.recentlyHandledIndex
|
||||||
for i := start; i != start; i = (i - 1) % recentlyHandledLength {
|
for i := start; i != start; i = (i - 1) % recentlyHandledLength {
|
||||||
|
|
Loading…
Reference in a new issue