More attachment robust support

This commit is contained in:
Karmanyaah Malhotra 2021-03-15 14:29:06 -04:00
parent 10ca97310e
commit dba9b05c88
3 changed files with 118 additions and 100 deletions

View File

@ -2,7 +2,7 @@
* Matrix → GroupMe * Matrix → GroupMe
* [ ] Message content * [ ] Message content
* [x] Plain text * [x] Plain text
* [ ] Formatted messages * [ ] Formatted messages<sup>3</sup>
* [ ] Media/files * [ ] Media/files
* [ ] Replies * [ ] Replies
* [ ] Message redactions * [ ] Message redactions
@ -23,13 +23,12 @@
* GroupMe → Matrix * GroupMe → Matrix
* [ ] Message content * [ ] Message content
* [x] Plain text * [x] Plain text
* [ ] Formatted messages * [x] Media/files
* [ ] Media/files
* [x] Images * [x] Images
* [x] Videos * [x] Videos
* [x] Random Files * [x] Random Files
* [ ] Location messages * [x] Location messages<sup>1</sup>
* [ ] Contact messages * [ ] Polls<sup>3</sup>
* [ ] Replies * [ ] Replies
* [ ] Chat types * [ ] Chat types
* [ ] Private chat * [ ] Private chat
@ -38,6 +37,10 @@
* [ ] Presence * [ ] Presence
* [ ] Typing notifications * [ ] Typing notifications
* [ ] Read receipts * [ ] Read receipts
* [ ] Calendar things
* [ ] Events created
* [ ] Events modified
* [ ] Going/Not
* [ ] Admin/superadmin status * [ ] Admin/superadmin status
* [ ] Membership actions * [ ] Membership actions
* [ ] Invite * [ ] Invite
@ -64,6 +67,6 @@
* [ ] Option to use own Matrix account for messages sent from WhatsApp mobile/other web clients * [ ] Option to use own Matrix account for messages sent from WhatsApp mobile/other web clients
* [ ] Shared group chat portals * [ ] Shared group chat portals
<sup>[1]</sup> May involve reverse-engineering the WhatsApp Web API and/or editing go-whatsapp <sup>[1]</sup> Basic feature works. Improvements are TODO.
<sup>[2]</sup> May already work <sup>[2]</sup> May already work
<sup>[3]</sup> May not be possible <sup>[3]</sup> May not be possible

View File

@ -78,7 +78,7 @@ type Message struct {
Chat PortalKey `gorm:"primaryKey;embedded;embeddedPrefix:chat_"` Chat PortalKey `gorm:"primaryKey;embedded;embeddedPrefix:chat_"`
JID types.GroupMeID `gorm:"primaryKey"` JID types.GroupMeID `gorm:"primaryKey"`
MXID id.EventID `gorm:"unique;notNull"` MXID id.EventID `gorm:"primaryKey;unique;notNull"`
Sender types.GroupMeID `gorm:"notNull"` Sender types.GroupMeID `gorm:"notNull"`
Timestamp uint64 `gorm:"notNull;default:0"` Timestamp uint64 `gorm:"notNull;default:0"`
Content *groupmeExt.Message `gorm:"type:TEXT;notNull"` Content *groupmeExt.Message `gorm:"type:TEXT;notNull"`

201
portal.go
View File

@ -28,12 +28,14 @@ import (
"image/jpeg" "image/jpeg"
"image/png" "image/png"
"io/ioutil" "io/ioutil"
"math"
"math/rand" "math/rand"
"net/http" "net/http"
"net/url" "net/url"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"strconv"
"strings" "strings"
"sync" "sync"
"time" "time"
@ -1260,13 +1262,13 @@ func (portal *Portal) sendMessageDirect(intent *appservice.IntentAPI, eventType
} }
} }
func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment *groupme.Attachment, source *User, message *groupme.Message) (err error, sendText bool) { func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment *groupme.Attachment, source *User, message *groupme.Message) (msg *event.MessageEventContent, sendText bool, err error) {
sendText = true sendText = true
switch attachment.Type { switch attachment.Type {
case "image": case "image":
imgData, mime, err := groupmeExt.DownloadImage(attachment.URL) imgData, mime, err := groupmeExt.DownloadImage(attachment.URL)
if err != nil { if err != nil {
return fmt.Errorf("failed to load media info: %w", err), true return nil, true, fmt.Errorf("failed to load media info: %w", err)
} }
var width, height int var width, height int
@ -1285,7 +1287,7 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment
} else { } else {
err = fmt.Errorf("failed to upload media: %w", err) err = fmt.Errorf("failed to upload media: %w", err)
} }
return err, true return nil, true, err
} }
attachmentUrl, _ := url.Parse(attachment.URL) attachmentUrl, _ := url.Parse(attachment.URL)
urlParts := strings.Split(attachmentUrl.Path, ".") urlParts := strings.Split(attachmentUrl.Path, ".")
@ -1315,15 +1317,8 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment
} }
//TODO thumbnail since groupme supports it anyway //TODO thumbnail since groupme supports it anyway
content.MsgType = event.MsgImage content.MsgType = event.MsgImage
_, _ = intent.UserTyping(portal.MXID, false, 0)
eventType := event.EventMessage return content, true, nil
_, err = portal.sendMessage(intent, eventType, content, message.CreatedAt.ToTime().Unix())
if err != nil {
portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err)
return nil, true
}
case "video": case "video":
vidContents, mime := groupmeExt.DownloadVideo(attachment.VideoPreviewURL, attachment.URL, source.Token) vidContents, mime := groupmeExt.DownloadVideo(attachment.VideoPreviewURL, attachment.URL, source.Token)
if mime == "" { if mime == "" {
@ -1340,7 +1335,7 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment
} else { } else {
err = fmt.Errorf("failed to upload media: %w", err) err = fmt.Errorf("failed to upload media: %w", err)
} }
return err, true return nil, true, err
} }
text := strings.Split(attachment.URL, "/") text := strings.Split(attachment.URL, "/")
@ -1361,18 +1356,9 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment
content.URL = uploaded.ContentURI.CUString() content.URL = uploaded.ContentURI.CUString()
} }
content.MsgType = event.MsgVideo content.MsgType = event.MsgVideo
_, _ = intent.UserTyping(portal.MXID, false, 0)
eventType := event.EventMessage
_, err = portal.sendMessage(intent, eventType, content, message.CreatedAt.ToTime().Unix())
if err != nil {
portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err)
return nil, true
}
message.Text = strings.Replace(message.Text, attachment.URL, "", 1) message.Text = strings.Replace(message.Text, attachment.URL, "", 1)
return nil, true return content, true, nil
case "file": case "file":
fileData, fname, fmime := groupmeExt.DownloadFile(portal.Key.JID, attachment.FileID, source.Token) fileData, fname, fmime := groupmeExt.DownloadFile(portal.Key.JID, attachment.FileID, source.Token)
if fmime == "" { if fmime == "" {
@ -1389,7 +1375,7 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment
} else { } else {
err = fmt.Errorf("failed to upload media: %w", err) err = fmt.Errorf("failed to upload media: %w", err)
} }
return err, true return nil, true, err
} }
content := &event.MessageEventContent{ content := &event.MessageEventContent{
@ -1416,21 +1402,34 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment
} else { } else {
content.MsgType = event.MsgFile content.MsgType = event.MsgFile
} }
_, _ = intent.UserTyping(portal.MXID, false, 0)
eventType := event.EventMessage return content, false, nil
case "location":
_, err = portal.sendMessage(intent, eventType, content, message.CreatedAt.ToTime().Unix()) name := attachment.Name
if err != nil { lat, _ := strconv.ParseFloat(attachment.Latitude, 64)
portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err) lng, _ := strconv.ParseFloat(attachment.Longitude, 64)
return nil, true latChar := 'N'
if lat < 0 {
latChar = 'S'
} }
return nil, false longChar := 'E'
if lng < 0 {
longChar = 'W'
}
formattedLoc := fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(lat), latChar, math.Abs(lng), longChar)
content := &event.MessageEventContent{
MsgType: event.MsgLocation,
Body: fmt.Sprintf("Location: %s\n%s", name, formattedLoc), //TODO link and stuff
GeoURI: fmt.Sprintf("geo:%.5f,%.5f", lat, lng),
}
return content, false, nil
default: default:
portal.log.Warnln("Unable to handle groupme attachment type", attachment.Type) portal.log.Warnln("Unable to handle groupme attachment type", attachment.Type)
return fmt.Errorf("Unable to handle groupme attachment type %s", attachment.Type), true return nil, true, fmt.Errorf("Unable to handle groupme attachment type %s", attachment.Type)
} }
return nil, true return nil, true, errors.New("Unknown type")
} }
func (portal *Portal) HandleMediaMessage(source *User, msg mediaMessage) { func (portal *Portal) HandleMediaMessage(source *User, msg mediaMessage) {
// intent := portal.startHandling(source, msg.info) // intent := portal.startHandling(source, msg.info)
@ -1583,11 +1582,26 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message)
} }
sendText := true sendText := true
var sentID id.EventID
for _, a := range message.Attachments { for _, a := range message.Attachments {
err, text := portal.handleAttachment(intent, a, source, message) msg, text, err := portal.handleAttachment(intent, a, source, message)
if err != nil { if err != nil {
portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err)
portal.sendMediaBridgeFailure(source, intent, *message, err) portal.sendMediaBridgeFailure(source, intent, *message, err)
continue
} }
if msg == nil {
continue
}
resp, err := portal.sendMessage(intent, event.EventMessage, msg, message.CreatedAt.ToTime().Unix())
if err != nil {
portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err)
portal.sendMediaBridgeFailure(source, intent, *message, err)
continue
}
sentID = resp.EventID
sendText = sendText && text sendText = sendText && text
} }
@ -1606,69 +1620,70 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message)
portal.log.Errorfln("Failed to handle message %s: %v", message.ID, err) portal.log.Errorfln("Failed to handle message %s: %v", message.ID, err)
return return
} }
sentID = resp.EventID
portal.finishHandling(source, message, resp.EventID)
} }
portal.finishHandling(source, message, sentID)
} }
//func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) { func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) {
// intent := portal.startHandling(source, message.Info) // intent := portal.startHandling(source, message.Info)
// if intent == nil { // if intent == nil {
// return // return
// } // }
// //
// url := message.Url // url := message.Url
// if len(url) == 0 { // if len(url) == 0 {
// url = fmt.Sprintf("https://maps.google.com/?q=%.5f,%.5f", message.DegreesLatitude, message.DegreesLongitude) // url = fmt.Sprintf("https://maps.google.com/?q=%.5f,%.5f", message.DegreesLatitude, message.DegreesLongitude)
// } // }
// name := message.Name // name := message.Name
// if len(name) == 0 { // if len(name) == 0 {
// latChar := 'N' // latChar := 'N'
// if message.DegreesLatitude < 0 { // if message.DegreesLatitude < 0 {
// latChar = 'S' // latChar = 'S'
// } // }
// longChar := 'E' // longChar := 'E'
// if message.DegreesLongitude < 0 { // if message.DegreesLongitude < 0 {
// longChar = 'W' // longChar = 'W'
// } // }
// name = fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(message.DegreesLatitude), latChar, math.Abs(message.DegreesLongitude), longChar) // name = fmt.Sprintf("%.4f° %c %.4f° %c", math.Abs(message.DegreesLatitude), latChar, math.Abs(message.DegreesLongitude), longChar)
// } // }
// //
// content := &event.MessageEventContent{ // content := &event.MessageEventContent{
// MsgType: event.MsgLocation, // MsgType: event.MsgLocation,
// Body: fmt.Sprintf("Location: %s\n%s\n%s", name, message.Address, url), // Body: fmt.Sprintf("Location: %s\n%s\n%s", name, message.Address, url),
// Format: event.FormatHTML, // Format: event.FormatHTML,
// FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a><br>%s", url, name, message.Address), // FormattedBody: fmt.Sprintf("Location: <a href='%s'>%s</a><br>%s", url, name, message.Address),
// GeoURI: fmt.Sprintf("geo:%.5f,%.5f", message.DegreesLatitude, message.DegreesLongitude), // GeoURI: fmt.Sprintf("geo:%.5f,%.5f", message.DegreesLatitude, message.DegreesLongitude),
// } // }
// //
// if len(message.JpegThumbnail) > 0 { // if len(message.JpegThumbnail) > 0 {
// thumbnailMime := http.DetectContentType(message.JpegThumbnail) // thumbnailMime := http.DetectContentType(message.JpegThumbnail)
// uploadedThumbnail, _ := intent.UploadBytes(message.JpegThumbnail, thumbnailMime) // uploadedThumbnail, _ := intent.UploadBytes(message.JpegThumbnail, thumbnailMime)
// if uploadedThumbnail != nil { // if uploadedThumbnail != nil {
// cfg, _, _ := image.DecodeConfig(bytes.NewReader(message.JpegThumbnail)) // cfg, _, _ := image.DecodeConfig(bytes.NewReader(message.JpegThumbnail))
// content.Info = &event.FileInfo{ // content.Info = &event.FileInfo{
// ThumbnailInfo: &event.FileInfo{ // ThumbnailInfo: &event.FileInfo{
// Size: len(message.JpegThumbnail), // Size: len(message.JpegThumbnail),
// Width: cfg.Width, // Width: cfg.Width,
// Height: cfg.Height, // Height: cfg.Height,
// MimeType: thumbnailMime, // MimeType: thumbnailMime,
// }, // },
// ThumbnailURL: uploadedThumbnail.ContentURI.CUString(), // ThumbnailURL: uploadedThumbnail.ContentURI.CUString(),
// } // }
// } // }
// } // }
// //
// portal.SetReply(content, message.ContextInfo) // portal.SetReply(content, message.ContextInfo)
// //
// _, _ = intent.UserTyping(portal.MXID, false, 0) // _, _ = intent.UserTyping(portal.MXID, false, 0)
// resp, err := portal.sendMessage(intent, event.EventMessage, content, int64(message.Info.Timestamp*1000)) // resp, err := portal.sendMessage(intent, event.EventMessage, content, int64(message.Info.Timestamp*1000))
// if err != nil { // if err != nil {
// portal.log.Errorfln("Failed to handle message %s: %v", message.Info.Id, err) // portal.log.Errorfln("Failed to handle message %s: %v", message.Info.Id, err)
// return // return
// } // }
// portal.finishHandling(source, message.Info.Source, resp.EventID) // portal.finishHandling(source, message.Info.Source, resp.EventID)
//} }
//func (portal *Portal) HandleContactMessage(source *User, message whatsapp.ContactMessage) { //func (portal *Portal) HandleContactMessage(source *User, message whatsapp.ContactMessage) {
// intent := portal.startHandling(source, message.Info) // intent := portal.startHandling(source, message.Info)