More attachment robust support
This commit is contained in:
parent
10ca97310e
commit
dba9b05c88
15
ROADMAP.md
15
ROADMAP.md
@ -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
|
||||||
|
@ -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
201
portal.go
@ -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)
|
||||||
|
Loading…
Reference in New Issue
Block a user