diff --git a/go.mod b/go.mod index 91adf9b..1b5961f 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.15 require ( github.com/Rhymen/go-whatsapp v0.1.1 + github.com/gabriel-vasile/mimetype v1.1.2 github.com/gorilla/websocket v1.4.2 github.com/jackc/pgproto3/v2 v2.0.7 // indirect github.com/karmanyaahm/groupme v0.2.0 diff --git a/go.sum b/go.sum index 850c980..580f5ec 100644 --- a/go.sum +++ b/go.sum @@ -82,6 +82,8 @@ github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5Kwzbycv github.com/franela/goblin v0.0.0-20200105215937-c9ffbefa60db/go.mod h1:7dvUGVsVBjqR7JHJk0brhHOZYGmfBYOrK0ZhYMEtBr4= github.com/franela/goreq v0.0.0-20171204163338-bcd34c9993f8/go.mod h1:ZhphrRTfi2rbfLwlschooIH4+wKKDR4Pdxhh+TRoA20= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/gabriel-vasile/mimetype v1.1.2 h1:gaPnPcNor5aZSVCJVSGipcpbgMWiAAj9z182ocSGbHU= +github.com/gabriel-vasile/mimetype v1.1.2/go.mod h1:6CDPel/o/3/s4+bp6kIbsWATq8pmgOisOPG40CJa6To= 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-kit/kit v0.9.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2as= diff --git a/groupmeExt/message.go b/groupmeExt/message.go index 02a2b0e..16fa2b7 100644 --- a/groupmeExt/message.go +++ b/groupmeExt/message.go @@ -1,6 +1,7 @@ package groupmeExt import ( + "bytes" "database/sql/driver" "encoding/json" "errors" @@ -9,6 +10,7 @@ import ( "net/http" "github.com/karmanyaahm/groupme" + "github.com/karmanyaahm/matrix-groupme-go/types" ) type Message struct{ groupme.Message } @@ -56,3 +58,43 @@ func DownloadImage(URL string) (bytes *[]byte, mime string, err error) { } return } + +func DownloadFile(RoomJID types.GroupMeID, FileID string, token string) (contents []byte, fname, mime string) { + client := &http.Client{} + b, _ := json.Marshal(struct { + FileIDS []string `json:"file_ids"` + }{ + FileIDS: []string{FileID}, + }) + + req, _ := http.NewRequest("POST", fmt.Sprintf("https://file.groupme.com/v1/%s/fileData", RoomJID), bytes.NewReader(b)) + req.Header.Add("X-Access-Token", token) + req.Header.Add("Content-Type", "application/json") + resp, _ := client.Do(req) + + defer resp.Body.Close() + data := []ImgData{} + json.NewDecoder(resp.Body).Decode(&data) + fmt.Println(data, RoomJID, FileID, token) + if len(data) < 1 { + return + } + + req, _ = http.NewRequest("POST", fmt.Sprintf("https://file.groupme.com/v1/%s/files/%s", RoomJID, FileID), nil) + req.URL.Query().Add("token", token) + req.Header.Add("X-Access-Token", token) + resp, _ = client.Do(req) + defer resp.Body.Close() + + bytes, _ := ioutil.ReadAll(resp.Body) + return bytes, data[0].FileData.FileName, data[0].FileData.Mime + +} + +type ImgData struct { + FileData struct { + FileName string `json:"file_name"` + FileSize int `json:"file_size"` + Mime string `json:"mime_type"` + } `json:"file_data"` +} diff --git a/portal.go b/portal.go index bdeb84d..8c41400 100644 --- a/portal.go +++ b/portal.go @@ -43,6 +43,7 @@ import ( "maunium.net/go/mautrix/crypto/attachment" "github.com/Rhymen/go-whatsapp" + "github.com/gabriel-vasile/mimetype" "github.com/karmanyaahm/groupme" "maunium.net/go/mautrix" @@ -1259,12 +1260,13 @@ func (portal *Portal) sendMessageDirect(intent *appservice.IntentAPI, eventType } } -func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment *groupme.Attachment, ts int64) error { +func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment *groupme.Attachment, ts int64, source *User) (err error, sendText bool) { + sendText = true switch attachment.Type { case "image": imgData, mime, err := groupmeExt.DownloadImage(attachment.URL) if err != nil { - return fmt.Errorf("failed to load media info: %w", err) + return fmt.Errorf("failed to load media info: %w", err), true } var width, height int @@ -1277,12 +1279,13 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment uploaded, err := portal.uploadWithRetry(intent, data, uploadMimeType, MediaUploadRetries) if err != nil { if errors.Is(err, mautrix.MTooLarge) { - return errors.New("homeserver rejected too large file") + err = errors.New("homeserver rejected too large file") } else if httpErr := err.(mautrix.HTTPError); httpErr.IsStatus(413) { - return errors.New("proxy rejected too large file") + err = errors.New("proxy rejected too large file") } else { - return fmt.Errorf("failed to upload media: %w", err) + err = fmt.Errorf("failed to upload media: %w", err) } + return err, true } attachmentUrl, _ := url.Parse(attachment.URL) urlParts := strings.Split(attachmentUrl.Path, ".") @@ -1319,14 +1322,66 @@ func (portal *Portal) handleAttachment(intent *appservice.IntentAPI, attachment _, err = portal.sendMessage(intent, eventType, content, ts) if err != nil { portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err) - return nil + return nil, true + } + case "file": + fileData, fname, fmime := groupmeExt.DownloadFile(portal.Key.JID, attachment.FileID, source.Token) + if fmime == "" { + fmime = mimetype.Detect(fileData).String() + } + data, uploadMimeType, file := portal.encryptFile(fileData, fmime) + + uploaded, err := portal.uploadWithRetry(intent, data, uploadMimeType, MediaUploadRetries) + if err != nil { + if errors.Is(err, mautrix.MTooLarge) { + err = errors.New("homeserver rejected too large file") + } else if httpErr := err.(mautrix.HTTPError); httpErr.IsStatus(413) { + err = errors.New("proxy rejected too large file") + } else { + err = fmt.Errorf("failed to upload media: %w", err) + } + return err, true } + content := &event.MessageEventContent{ + Body: fname, + File: file, + Info: &event.FileInfo{ + Size: len(data), + MimeType: fmime, + //Width: width, + //Height: height, + //Duration: int(msg.length), + }, + } + if content.File != nil { + content.File.URL = uploaded.ContentURI.CUString() + } else { + content.URL = uploaded.ContentURI.CUString() + } + //TODO thumbnail since groupme supports it anyway + if strings.HasPrefix(fmime, "image") { + content.MsgType = event.MsgImage + } else if strings.HasPrefix(fmime, "video") { + content.MsgType = event.MsgVideo + } else { + content.MsgType = event.MsgFile + } + _, _ = intent.UserTyping(portal.MXID, false, 0) + + eventType := event.EventMessage + + _, err = portal.sendMessage(intent, eventType, content, ts) + if err != nil { + portal.log.Errorfln("Failed to handle message %s: %v", "TODOID", err) + return nil, true + } + return nil, false default: portal.log.Warnln("Unable to handle groupme attachment type", attachment.Type) - return fmt.Errorf("Unable to handle groupme attachment type %s", attachment.Type) + return fmt.Errorf("Unable to handle groupme attachment type %s", attachment.Type), true } - return nil + return nil, true } func (portal *Portal) HandleMediaMessage(source *User, msg mediaMessage) { // intent := portal.startHandling(source, msg.info) @@ -1482,11 +1537,13 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message) Body: message.Text, MsgType: event.MsgText, } + sendText := true for _, a := range message.Attachments { - err := portal.handleAttachment(intent, a, message.CreatedAt.ToTime().Unix()) + err, text := portal.handleAttachment(intent, a, message.CreatedAt.ToTime().Unix(), source) if err != nil { portal.sendMediaBridgeFailure(source, intent, *message, err) } + sendText = sendText && text } // portal.bridge.Formatter.ParseWhatsApp(content, message.ContextInfo.MentionedJID) @@ -1494,12 +1551,15 @@ func (portal *Portal) HandleTextMessage(source *User, message *groupme.Message) //TODO: mentions _, _ = intent.UserTyping(portal.MXID, false, 0) - resp, err := portal.sendMessage(intent, event.EventMessage, content, message.CreatedAt.ToTime().Unix()) - if err != nil { - portal.log.Errorfln("Failed to handle message %s: %v", message.ID, err) - return + if sendText { + resp, err := portal.sendMessage(intent, event.EventMessage, content, message.CreatedAt.ToTime().Unix()) + if err != nil { + portal.log.Errorfln("Failed to handle message %s: %v", message.ID, err) + return + } + + portal.finishHandling(source, message, resp.EventID) } - portal.finishHandling(source, message, resp.EventID) } //func (portal *Portal) HandleLocationMessage(source *User, message whatsapp.LocationMessage) {