From 5f6955d36ff33e25d8b1dbef69e3d403ad3f9f2d Mon Sep 17 00:00:00 2001 From: Tulir Asokan Date: Fri, 24 Aug 2018 22:31:18 +0300 Subject: [PATCH] Add Matrix->WhatsApp media bridging --- ROADMAP.md | 4 +-- portal.go | 90 ++++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 82 insertions(+), 12 deletions(-) diff --git a/ROADMAP.md b/ROADMAP.md index d08bfda..9995536 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,9 +1,9 @@ # Features & roadmap * Matrix → WhatsApp - * [ ] Message content + * [x] Message content * [x] Plain text * [x] Formatted messages - * [ ] Media/files + * [x] Media/files * [ ] Message redactions * [ ] Presence * [ ] Typing notifications diff --git a/portal.go b/portal.go index 97c109d..55ad18b 100644 --- a/portal.go +++ b/portal.go @@ -22,6 +22,7 @@ import ( "fmt" "html" "image" + "io" "math/rand" "mime" "net/http" @@ -309,9 +310,9 @@ var boldRegex = regexp.MustCompile("([\\s>_~]|^)\\*(.+?)\\*([^a-zA-Z\\d]|$)") var strikethroughRegex = regexp.MustCompile("([\\s>_*]|^)~(.+?)~([^a-zA-Z\\d]|$)") var whatsAppFormat = map[*regexp.Regexp]string{ - codeBlockRegex: "
$1
", - italicRegex: "$1$2$3", - boldRegex: "$1$2$3", + codeBlockRegex: "
$1
", + italicRegex: "$1$2$3", + boldRegex: "$1$2$3", strikethroughRegex: "$1$2$3", } @@ -475,27 +476,96 @@ func makeMessageID() string { return strings.ToUpper(hex.EncodeToString(b)) } +func (portal *Portal) PreprocessMatrixMedia(evt *gomatrix.Event) (string, io.ReadCloser, []byte) { + if evt.Content.Info == nil { + evt.Content.Info = &gomatrix.FileInfo{} + } + caption := evt.Content.Body + exts, err := mime.ExtensionsByType(evt.Content.Info.MimeType) + for _, ext := range exts { + if strings.HasSuffix(caption, ext) { + caption = "" + break + } + } + content, err := portal.MainIntent().Download(evt.Content.URL) + if err != nil { + portal.log.Errorln("Failed to download media in %s: %v", evt.ID, err) + return "", nil, nil + } + thumbnail, err := portal.MainIntent().DownloadBytes(evt.Content.Info.ThumbnailURL) + return caption, content, thumbnail +} + func (portal *Portal) HandleMatrixMessage(evt *gomatrix.Event) { + info := whatsapp.MessageInfo{ + Id: makeMessageID(), + RemoteJid: portal.JID, + } var err error switch evt.Content.MsgType { - case gomatrix.MsgText: + case gomatrix.MsgText, gomatrix.MsgEmote: text := evt.Content.Body if evt.Content.Format == gomatrix.FormatHTML { text = htmlParser.Parse(evt.Content.FormattedBody) } - id := makeMessageID() + if evt.Content.MsgType == gomatrix.MsgEmote { + text = "/me " + text + } err = portal.user.Conn.Send(whatsapp.TextMessage{ Text: text, - Info: whatsapp.MessageInfo{ - Id: id, - RemoteJid: portal.JID, - }, + Info: info, + }) + case gomatrix.MsgImage: + caption, content, thumbnail := portal.PreprocessMatrixMedia(evt) + if content == nil { + return + } + err = portal.user.Conn.Send(whatsapp.ImageMessage{ + Caption: caption, + Content: content, + Thumbnail: thumbnail, + Type: evt.Content.Info.MimeType, + Info: info, + }) + case gomatrix.MsgVideo: + caption, content, thumbnail := portal.PreprocessMatrixMedia(evt) + if content == nil { + return + } + err = portal.user.Conn.Send(whatsapp.VideoMessage{ + Caption: caption, + Content: content, + Thumbnail: thumbnail, + Type: evt.Content.Info.MimeType, + Info: info, + }) + case gomatrix.MsgAudio: + _, content, _ := portal.PreprocessMatrixMedia(evt) + if content == nil { + return + } + err = portal.user.Conn.Send(whatsapp.AudioMessage{ + Content: content, + Type: evt.Content.Info.MimeType, + Info: info, + }) + case gomatrix.MsgFile: + _, content, thumbnail := portal.PreprocessMatrixMedia(evt) + if content == nil { + return + } + err = portal.user.Conn.Send(whatsapp.DocumentMessage{ + Content: content, + Thumbnail: thumbnail, + Type: evt.Content.Info.MimeType, + Info: info, }) - portal.MarkHandled(id, evt.ID) default: portal.log.Debugln("Unhandled Matrix event:", evt) return } + portal.MarkHandled(info.Id, evt.ID) if err != nil { portal.log.Errorfln("Error handling Matrix event %s: %v", evt.ID, err) } else {