Add support for sending attachments from Matrix

This commit is contained in:
Tulir Asokan 2023-07-15 20:08:11 +03:00
parent 63d2aab736
commit aa89c98353
7 changed files with 305 additions and 279 deletions

View file

@ -1,15 +1,15 @@
# Features & roadmap # Features & roadmap
* Matrix → Google Messages * Matrix → Google Messages
* [ ] Message content * [x] Message content
* [x] Plain text * [x] Plain text
* [ ] Media/files * [x] Media/files
* [x] Replies (RCS) * [x] Replies (RCS)
* [x] Reactions (RCS) * [x] Reactions (RCS)
* [ ] Typing notifications (RCS) * [ ] Typing notifications (RCS)
* [x] Read receipts (RCS) * [x] Read receipts (RCS)
* [x] Message deletions (own device only) * [x] Message deletions (own device only)
* Google Messages → Matrix * Google Messages → Matrix
* [ ] Message content * [x] Message content
* [x] Plain text * [x] Plain text
* [x] Media/files * [x] Media/files
* [x] Replies (RCS) * [x] Replies (RCS)

View file

@ -582,11 +582,25 @@ const (
MediaFormats_AUDIO_OGG MediaFormats = 22 MediaFormats_AUDIO_OGG MediaFormats = 22
MediaFormats_AUDIO_UNSPECIFIED MediaFormats = 23 MediaFormats_AUDIO_UNSPECIFIED MediaFormats = 23
MediaFormats_TEXT_VCARD MediaFormats = 24 MediaFormats_TEXT_VCARD MediaFormats = 24
MediaFormats_APP_PDF MediaFormats = 28 MediaFormats_APP_PDF MediaFormats = 25
MediaFormats_APP_TXT MediaFormats = 29 MediaFormats_APP_TXT MediaFormats = 26
MediaFormats_APP_HTML MediaFormats = 30 MediaFormats_APP_HTML MediaFormats = 27
MediaFormats_AUDIO_OGG2 MediaFormats = 31 MediaFormats_APP_DOC MediaFormats = 28
MediaFormats_APP_SMIL MediaFormats = 32 MediaFormats_APP_DOCX MediaFormats = 29
MediaFormats_APP_PPTX MediaFormats = 30
MediaFormats_APP_PPT MediaFormats = 31
MediaFormats_APP_XLSX MediaFormats = 32
MediaFormats_APP_XLS MediaFormats = 33
MediaFormats_APP_APK MediaFormats = 34
MediaFormats_APP_ZIP MediaFormats = 35
MediaFormats_APP_JAR MediaFormats = 36
MediaFormats_APP_UNSPECIFIED MediaFormats = 37
MediaFormats_CAL_TEXT_VCALENDAR MediaFormats = 38
MediaFormats_CAL_TEXT_XVCALENDAR MediaFormats = 39
MediaFormats_CAL_TEXT_CALENDAR MediaFormats = 40
MediaFormats_CAL_APPLICATION_VCS MediaFormats = 41
MediaFormats_CAL_APPLICATION_ICS MediaFormats = 42
MediaFormats_CAL_APPLICATION_HBSVCS MediaFormats = 43
) )
// Enum value maps for MediaFormats. // Enum value maps for MediaFormats.
@ -617,11 +631,25 @@ var (
22: "AUDIO_OGG", 22: "AUDIO_OGG",
23: "AUDIO_UNSPECIFIED", 23: "AUDIO_UNSPECIFIED",
24: "TEXT_VCARD", 24: "TEXT_VCARD",
28: "APP_PDF", 25: "APP_PDF",
29: "APP_TXT", 26: "APP_TXT",
30: "APP_HTML", 27: "APP_HTML",
31: "AUDIO_OGG2", 28: "APP_DOC",
32: "APP_SMIL", 29: "APP_DOCX",
30: "APP_PPTX",
31: "APP_PPT",
32: "APP_XLSX",
33: "APP_XLS",
34: "APP_APK",
35: "APP_ZIP",
36: "APP_JAR",
37: "APP_UNSPECIFIED",
38: "CAL_TEXT_VCALENDAR",
39: "CAL_TEXT_XVCALENDAR",
40: "CAL_TEXT_CALENDAR",
41: "CAL_APPLICATION_VCS",
42: "CAL_APPLICATION_ICS",
43: "CAL_APPLICATION_HBSVCS",
} }
MediaFormats_value = map[string]int32{ MediaFormats_value = map[string]int32{
"UNSPECIFIED_TYPE": 0, "UNSPECIFIED_TYPE": 0,
@ -649,11 +677,25 @@ var (
"AUDIO_OGG": 22, "AUDIO_OGG": 22,
"AUDIO_UNSPECIFIED": 23, "AUDIO_UNSPECIFIED": 23,
"TEXT_VCARD": 24, "TEXT_VCARD": 24,
"APP_PDF": 28, "APP_PDF": 25,
"APP_TXT": 29, "APP_TXT": 26,
"APP_HTML": 30, "APP_HTML": 27,
"AUDIO_OGG2": 31, "APP_DOC": 28,
"APP_SMIL": 32, "APP_DOCX": 29,
"APP_PPTX": 30,
"APP_PPT": 31,
"APP_XLSX": 32,
"APP_XLS": 33,
"APP_APK": 34,
"APP_ZIP": 35,
"APP_JAR": 36,
"APP_UNSPECIFIED": 37,
"CAL_TEXT_VCALENDAR": 38,
"CAL_TEXT_XVCALENDAR": 39,
"CAL_TEXT_CALENDAR": 40,
"CAL_APPLICATION_VCS": 41,
"CAL_APPLICATION_ICS": 42,
"CAL_APPLICATION_HBSVCS": 43,
} }
) )
@ -3529,8 +3571,8 @@ var file_conversations_proto_rawDesc = []byte{
0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45, 0x41, 0x52, 0x43, 0x48, 0x49, 0x56, 0x45, 0x44, 0x10, 0x02, 0x12, 0x0b, 0x0a, 0x07, 0x44, 0x45,
0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x4c, 0x45, 0x54, 0x45, 0x44, 0x10, 0x03, 0x12, 0x18, 0x0a, 0x14, 0x42, 0x4c, 0x4f, 0x43, 0x4b,
0x45, 0x44, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10, 0x45, 0x44, 0x5f, 0x41, 0x4e, 0x44, 0x5f, 0x52, 0x45, 0x50, 0x4f, 0x52, 0x54, 0x45, 0x44, 0x10,
0x05, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x06, 0x2a, 0xfb, 0x05, 0x12, 0x0b, 0x0a, 0x07, 0x42, 0x4c, 0x4f, 0x43, 0x4b, 0x45, 0x44, 0x10, 0x06, 0x2a, 0x80,
0x03, 0x0a, 0x0c, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x12, 0x06, 0x0a, 0x0c, 0x4d, 0x65, 0x64, 0x69, 0x61, 0x46, 0x6f, 0x72, 0x6d, 0x61, 0x74, 0x73, 0x12,
0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54, 0x14, 0x0a, 0x10, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x5f, 0x54,
0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x59, 0x50, 0x45, 0x10, 0x00, 0x12, 0x0e, 0x0a, 0x0a, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x4a,
0x50, 0x45, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x4a, 0x50, 0x45, 0x47, 0x10, 0x01, 0x12, 0x0d, 0x0a, 0x09, 0x49, 0x4d, 0x41, 0x47, 0x45, 0x5f, 0x4a,
@ -3558,12 +3600,28 @@ var file_conversations_proto_rawDesc = []byte{
0x15, 0x0a, 0x11, 0x41, 0x55, 0x44, 0x49, 0x4f, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49, 0x15, 0x0a, 0x11, 0x41, 0x55, 0x44, 0x49, 0x4f, 0x5f, 0x55, 0x4e, 0x53, 0x50, 0x45, 0x43, 0x49,
0x46, 0x49, 0x45, 0x44, 0x10, 0x17, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x56, 0x46, 0x49, 0x45, 0x44, 0x10, 0x17, 0x12, 0x0e, 0x0a, 0x0a, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x56,
0x43, 0x41, 0x52, 0x44, 0x10, 0x18, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x50, 0x44, 0x43, 0x41, 0x52, 0x44, 0x10, 0x18, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x50, 0x44,
0x46, 0x10, 0x1c, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x54, 0x58, 0x54, 0x10, 0x1d, 0x46, 0x10, 0x19, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x54, 0x58, 0x54, 0x10, 0x1a,
0x12, 0x0c, 0x0a, 0x08, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x54, 0x4d, 0x4c, 0x10, 0x1e, 0x12, 0x0e, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x50, 0x50, 0x5f, 0x48, 0x54, 0x4d, 0x4c, 0x10, 0x1b, 0x12, 0x0b,
0x0a, 0x0a, 0x41, 0x55, 0x44, 0x49, 0x4f, 0x5f, 0x4f, 0x47, 0x47, 0x32, 0x10, 0x1f, 0x12, 0x0c, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x44, 0x4f, 0x43, 0x10, 0x1c, 0x12, 0x0c, 0x0a, 0x08, 0x41,
0x0a, 0x08, 0x41, 0x50, 0x50, 0x5f, 0x53, 0x4d, 0x49, 0x4c, 0x10, 0x20, 0x42, 0x0e, 0x5a, 0x0c, 0x50, 0x50, 0x5f, 0x44, 0x4f, 0x43, 0x58, 0x10, 0x1d, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x50, 0x50,
0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x72, 0x79, 0x62, 0x06, 0x70, 0x72, 0x5f, 0x50, 0x50, 0x54, 0x58, 0x10, 0x1e, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x50,
0x6f, 0x74, 0x6f, 0x33, 0x50, 0x54, 0x10, 0x1f, 0x12, 0x0c, 0x0a, 0x08, 0x41, 0x50, 0x50, 0x5f, 0x58, 0x4c, 0x53, 0x58,
0x10, 0x20, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x58, 0x4c, 0x53, 0x10, 0x21, 0x12,
0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50, 0x5f, 0x41, 0x50, 0x4b, 0x10, 0x22, 0x12, 0x0b, 0x0a, 0x07,
0x41, 0x50, 0x50, 0x5f, 0x5a, 0x49, 0x50, 0x10, 0x23, 0x12, 0x0b, 0x0a, 0x07, 0x41, 0x50, 0x50,
0x5f, 0x4a, 0x41, 0x52, 0x10, 0x24, 0x12, 0x13, 0x0a, 0x0f, 0x41, 0x50, 0x50, 0x5f, 0x55, 0x4e,
0x53, 0x50, 0x45, 0x43, 0x49, 0x46, 0x49, 0x45, 0x44, 0x10, 0x25, 0x12, 0x16, 0x0a, 0x12, 0x43,
0x41, 0x4c, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x56, 0x43, 0x41, 0x4c, 0x45, 0x4e, 0x44, 0x41,
0x52, 0x10, 0x26, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x41, 0x4c, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x5f,
0x58, 0x56, 0x43, 0x41, 0x4c, 0x45, 0x4e, 0x44, 0x41, 0x52, 0x10, 0x27, 0x12, 0x15, 0x0a, 0x11,
0x43, 0x41, 0x4c, 0x5f, 0x54, 0x45, 0x58, 0x54, 0x5f, 0x43, 0x41, 0x4c, 0x45, 0x4e, 0x44, 0x41,
0x52, 0x10, 0x28, 0x12, 0x17, 0x0a, 0x13, 0x43, 0x41, 0x4c, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x49,
0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x56, 0x43, 0x53, 0x10, 0x29, 0x12, 0x17, 0x0a, 0x13,
0x43, 0x41, 0x4c, 0x5f, 0x41, 0x50, 0x50, 0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f,
0x49, 0x43, 0x53, 0x10, 0x2a, 0x12, 0x1a, 0x0a, 0x16, 0x43, 0x41, 0x4c, 0x5f, 0x41, 0x50, 0x50,
0x4c, 0x49, 0x43, 0x41, 0x54, 0x49, 0x4f, 0x4e, 0x5f, 0x48, 0x42, 0x53, 0x56, 0x43, 0x53, 0x10,
0x2b, 0x42, 0x0e, 0x5a, 0x0c, 0x2e, 0x2e, 0x2f, 0x2e, 0x2e, 0x2f, 0x62, 0x69, 0x6e, 0x61, 0x72,
0x79, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
} }
var ( var (

View file

@ -359,12 +359,14 @@ enum MediaFormats {
IMAGE_WBMP = 5; IMAGE_WBMP = 5;
IMAGE_X_MS_BMP = 6; IMAGE_X_MS_BMP = 6;
IMAGE_UNSPECIFIED = 7; IMAGE_UNSPECIFIED = 7;
VIDEO_MP4 = 8; VIDEO_MP4 = 8;
VIDEO_3G2 = 9; VIDEO_3G2 = 9;
VIDEO_3GPP = 10; VIDEO_3GPP = 10;
VIDEO_WEBM = 11; VIDEO_WEBM = 11;
VIDEO_MKV = 12; VIDEO_MKV = 12;
VIDEO_UNSPECIFIED = 13; VIDEO_UNSPECIFIED = 13;
AUDIO_AAC = 14; AUDIO_AAC = 14;
AUDIO_AMR = 15; AUDIO_AMR = 15;
AUDIO_MP3 = 16; AUDIO_MP3 = 16;
@ -375,10 +377,27 @@ enum MediaFormats {
AUDIO_3GPP = 21; AUDIO_3GPP = 21;
AUDIO_OGG = 22; AUDIO_OGG = 22;
AUDIO_UNSPECIFIED = 23; AUDIO_UNSPECIFIED = 23;
TEXT_VCARD = 24; TEXT_VCARD = 24;
APP_PDF = 28;
APP_TXT = 29; APP_PDF = 25;
APP_HTML = 30; APP_TXT = 26;
AUDIO_OGG2 = 31; APP_HTML = 27;
APP_SMIL = 32; APP_DOC = 28;
APP_DOCX = 29;
APP_PPTX = 30;
APP_PPT = 31;
APP_XLSX = 32;
APP_XLS = 33;
APP_APK = 34;
APP_ZIP = 35;
APP_JAR = 36;
APP_UNSPECIFIED = 37;
CAL_TEXT_VCALENDAR = 38;
CAL_TEXT_XVCALENDAR = 39;
CAL_TEXT_CALENDAR = 40;
CAL_APPLICATION_VCS = 41;
CAL_APPLICATION_ICS = 42;
CAL_APPLICATION_HBSVCS = 43;
} }

View file

@ -1,116 +1,87 @@
package libgm package libgm
import ( import (
"strings"
"go.mau.fi/mautrix-gmessages/libgm/binary" "go.mau.fi/mautrix-gmessages/libgm/binary"
"go.mau.fi/mautrix-gmessages/libgm/crypto" "go.mau.fi/mautrix-gmessages/libgm/crypto"
"go.mau.fi/mautrix-gmessages/libgm/util"
) )
type MediaType struct { type MediaType struct {
Extension string Extension string
Format string Format string
Type int64 Type binary.MediaFormats
} }
var MediaTypes = map[string]MediaType{ var MimeToMediaType = map[string]MediaType{
"image/jpeg": {Extension: "jpeg", Format: "image/jpeg", Type: 1}, "image/jpeg": {Extension: "jpeg", Type: binary.MediaFormats_IMAGE_JPEG},
"image/jpg": {Extension: "jpg", Format: "image/jpg", Type: 2}, "image/jpg": {Extension: "jpg", Type: binary.MediaFormats_IMAGE_JPG},
"image/png": {Extension: "png", Format: "image/png", Type: 3}, "image/png": {Extension: "png", Type: binary.MediaFormats_IMAGE_PNG},
"image/gif": {Extension: "gif", Format: "image/gif", Type: 4}, "image/gif": {Extension: "gif", Type: binary.MediaFormats_IMAGE_GIF},
"image/wbmp": {Extension: "wbmp", Format: "image/wbmp", Type: 5}, "image/wbmp": {Extension: "wbmp", Type: binary.MediaFormats_IMAGE_WBMP},
"image/bmp": {Extension: "bmp", Format: "image/bmp", Type: 6}, "image/bmp": {Extension: "bmp", Type: binary.MediaFormats_IMAGE_X_MS_BMP},
"image/x-ms-bmp": {Extension: "bmp", Format: "image/x-ms-bmp", Type: 6}, "image/x-ms-bmp": {Extension: "bmp", Type: binary.MediaFormats_IMAGE_X_MS_BMP},
"image": {Type: 7},
"video/mp4": {Extension: "mp4", Format: "video/mp4", Type: 8}, "video/mp4": {Extension: "mp4", Type: binary.MediaFormats_VIDEO_MP4},
"video/3gpp2": {Extension: "3gpp2", Format: "video/3gpp2", Type: 9}, "video/3gpp2": {Extension: "3gpp2", Type: binary.MediaFormats_VIDEO_3G2},
"video/3gpp": {Extension: "3gpp", Format: "video/3gpp", Type: 10}, "video/3gpp": {Extension: "3gpp", Type: binary.MediaFormats_VIDEO_3GPP},
"video/webm": {Extension: "webm", Format: "video/webm", Type: 11}, "video/webm": {Extension: "webm", Type: binary.MediaFormats_VIDEO_WEBM},
"video/x-matroska": {Extension: "mkv", Format: "video/x-matroska", Type: 12}, "video/x-matroska": {Extension: "mkv", Type: binary.MediaFormats_VIDEO_MKV},
"video": {Type: 13},
"audio/aac": {Extension: "aac", Format: "audio/aac", Type: 14}, "audio/aac": {Extension: "aac", Type: binary.MediaFormats_AUDIO_AAC},
"audio/amr": {Extension: "amr", Format: "audio/amr", Type: 15}, "audio/amr": {Extension: "amr", Type: binary.MediaFormats_AUDIO_AMR},
"audio/mp3": {Extension: "mp3", Format: "audio/mp3", Type: 16}, "audio/mp3": {Extension: "mp3", Type: binary.MediaFormats_AUDIO_MP3},
"audio/mpeg": {Extension: "mpeg", Format: "audio/mpeg", Type: 17}, "audio/mpeg": {Extension: "mpeg", Type: binary.MediaFormats_AUDIO_MPEG},
"audio/mpg": {Extension: "mpg", Format: "audio/mpg", Type: 18}, "audio/mpg": {Extension: "mpg", Type: binary.MediaFormats_AUDIO_MPG},
"audio/mp4": {Extension: "mp4", Format: "audio/mp4", Type: 19}, "audio/mp4": {Extension: "mp4", Type: binary.MediaFormats_AUDIO_MP4},
"audio/mp4-latm": {Extension: "latm", Format: "audio/mp4-latm", Type: 20}, "audio/mp4-latm": {Extension: "latm", Type: binary.MediaFormats_AUDIO_MP4_LATM},
"audio/3gpp": {Extension: "3gpp", Format: "audio/3gpp", Type: 21}, "audio/3gpp": {Extension: "3gpp", Type: binary.MediaFormats_AUDIO_3GPP},
"audio/ogg": {Extension: "ogg", Format: "audio/ogg", Type: 22}, "audio/ogg": {Extension: "ogg", Type: binary.MediaFormats_AUDIO_OGG},
"auidio": {Type: 23},
"text/vcard": {Extension: "vcard", Format: "text/vcard", Type: 24}, "text/vcard": {Extension: "vcard", Type: binary.MediaFormats_TEXT_VCARD},
"text/x-vcard": {Extension: "vcard", Format: "text/x-vcard", Type: 24}, "application/pdf": {Extension: "pdf", Type: binary.MediaFormats_APP_PDF},
"application/pdf": {Extension: "pdf", Format: "application/pdf", Type: 25}, "text/plain": {Extension: "txt", Type: binary.MediaFormats_APP_TXT},
"text/plain": {Extension: "txt", Format: "text/plain", Type: 26}, "text/html": {Extension: "html", Type: binary.MediaFormats_APP_HTML},
"text/html": {Extension: "html", Format: "text/html", Type: 27}, "application/msword": {Extension: "doc", Type: binary.MediaFormats_APP_DOC},
"application/msword": {Extension: "doc", Format: "application/msword", Type: 28}, "application/vnd.openxmlformats-officedocument.wordprocessingml.document": {Extension: "docx", Type: binary.MediaFormats_APP_DOCX},
"application/vnd.openxmlformats-officedocument.wordprocessingml.document": {Extension: "docx", Format: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", Type: 29}, "application/vnd.openxmlformats-officedocument.presentationml.presentation": {Extension: "pptx", Type: binary.MediaFormats_APP_PPTX},
"application/vnd.openxmlformats-officedocument.presentationml.presentation": {Extension: "pptx", Format: "application/vnd.openxmlformats-officedocument.presentationml.presentation", Type: 30}, "application/vnd.ms-powerpoint": {Extension: "ppt", Type: binary.MediaFormats_APP_PPT},
"application/vnd.ms-powerpoint": {Extension: "ppt", Format: "application/vnd.ms-powerpoint", Type: 31}, "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {Extension: "xlsx", Type: binary.MediaFormats_APP_XLSX},
"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {Extension: "xlsx", Format: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", Type: 32}, "application/vnd.ms-excel": {Extension: "xls", Type: binary.MediaFormats_APP_XLS},
"application/vnd.ms-excel": {Extension: "xls", Format: "application/vnd.ms-excel", Type: 33}, "application/vnd.android.package-archive": {Extension: "apk", Type: binary.MediaFormats_APP_APK},
"application/vnd.android.package-archive": {Extension: "apk", Format: "application/vnd.android.package-archive", Type: 34}, "application/zip": {Extension: "zip", Type: binary.MediaFormats_APP_ZIP},
"application/zip": {Extension: "zip", Format: "application/zip", Type: 35}, "application/java-archive": {Extension: "jar", Type: binary.MediaFormats_APP_JAR},
"application/java-archive": {Extension: "jar", Format: "application/java-archive", Type: 36}, "text/x-calendar": {Extension: "vcs", Type: binary.MediaFormats_CAL_TEXT_VCALENDAR},
"text/x-vCalendar": {Extension: "vcs", Format: "text/x-vCalendar", Type: 38}, "text/calendar": {Extension: "ics", Type: binary.MediaFormats_CAL_TEXT_CALENDAR},
"text/x-vcalendar": {Extension: "ics", Format: "text/x-vcalendar", Type: 39},
"text/calendar": {Extension: "ics", Format: "text/calendar", Type: 40}, "image": {Type: binary.MediaFormats_IMAGE_UNSPECIFIED},
"application/vcs": {Extension: "vcs", Format: "application/vcs", Type: 41}, "video": {Type: binary.MediaFormats_VIDEO_UNSPECIFIED},
"application/ics": {Extension: "ics", Format: "application/ics", Type: 42}, "audio": {Type: binary.MediaFormats_AUDIO_UNSPECIFIED},
"application/hbs-vcs": {Extension: "vcs", Format: "application/hbs-vcs", Type: 43}, "application": {Type: binary.MediaFormats_APP_UNSPECIFIED},
"text": {Type: binary.MediaFormats_APP_TXT},
} }
type Image struct { var FormatToMediaType = map[binary.MediaFormats]MediaType{
imageCryptor *crypto.ImageCryptor binary.MediaFormats_CAL_TEXT_XVCALENDAR: MimeToMediaType["text/x-calendar"],
binary.MediaFormats_CAL_APPLICATION_VCS: MimeToMediaType["text/x-calendar"],
imageName string binary.MediaFormats_CAL_APPLICATION_ICS: MimeToMediaType["text/calendar"],
imageID string //binary.MediaFormats_CAL_APPLICATION_HBSVCS: ???
imageType MediaType
imageBytes []byte
imageSize int64
} }
func (i *Image) GetEncryptedBytes() ([]byte, error) { func init() {
encryptedBytes, encryptErr := i.imageCryptor.EncryptData(i.imageBytes) for key, mediaType := range MimeToMediaType {
if encryptErr != nil { if strings.ContainsRune(key, '/') {
return nil, encryptErr mediaType.Format = key
}
FormatToMediaType[mediaType.Type] = mediaType
} }
return encryptedBytes, nil
} }
func (i *Image) GetImageCryptor() *crypto.ImageCryptor { func (c *Client) UploadMedia(data []byte, fileName, mime string) (*binary.MediaContent, error) {
return i.imageCryptor mediaType := MimeToMediaType[mime]
if mediaType.Type == 0 {
mediaType = MimeToMediaType[strings.Split(mime, "/")[0]]
} }
func (i *Image) GetImageName() string {
return i.imageName
}
func (i *Image) GetImageBytes() []byte {
return i.imageBytes
}
func (i *Image) GetImageSize() int64 {
return i.imageSize
}
func (i *Image) GetImageType() MediaType {
return i.imageType
}
func (i *Image) GetImageID() string {
return i.imageID
}
// This is the equivalent of dragging an image into the window on messages web
//
// Keep in mind that adding an image to a MessageBuilder will also upload the image to googles server
func (c *Client) UploadMedia(data []byte, mime string) (*binary.MediaContent, error) {
mediaType := MediaTypes[mime]
mediaID := util.GenerateImageID()
fileName := util.RandStr(8) + "." + mediaType.Extension
decryptionKey, err := crypto.GenerateKey(32) decryptionKey, err := crypto.GenerateKey(32)
if err != nil { if err != nil {
return nil, err return nil, err
@ -119,30 +90,23 @@ func (c *Client) UploadMedia(data []byte, mime string) (*binary.MediaContent, er
if err != nil { if err != nil {
return nil, err return nil, err
} }
image := &Image{ encryptedBytes, err := cryptor.EncryptData(data)
imageCryptor: cryptor, if err != nil {
imageID: mediaID, return nil, err
imageBytes: data, }
imageType: mediaType, startUploadImage, err := c.StartUploadMedia(encryptedBytes, mime)
imageSize: int64(len(data)),
imageName: fileName,
}
startUploadImage, err := c.StartUploadMedia(image)
if err != nil { if err != nil {
return nil, err return nil, err
} }
upload, err := c.FinalizeUploadMedia(startUploadImage) upload, err := c.FinalizeUploadMedia(startUploadImage)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return &binary.MediaContent{ return &binary.MediaContent{
Format: binary.MediaFormats(image.GetImageType().Type), Format: mediaType.Type,
MediaID: upload.MediaID, MediaID: upload.MediaID,
MediaName: image.GetImageName(), MediaName: fileName,
Size: image.GetImageSize(), Size: int64(len(data)),
DecryptionKey: image.GetImageCryptor().GetKey(), DecryptionKey: decryptionKey,
}, nil }, nil
} }

View file

@ -19,15 +19,14 @@ type StartGoogleUpload struct {
UploadStatus string UploadStatus string
ChunkGranularity int64 ChunkGranularity int64
ControlURL string ControlURL string
MimeType string
Image *Image
EncryptedMediaBytes []byte EncryptedMediaBytes []byte
} }
type MediaUpload struct { type MediaUpload struct {
MediaID string MediaID string
MediaNumber int64 MediaNumber int64
Image *Image
} }
var ( var (
@ -36,10 +35,9 @@ var (
) )
func (c *Client) FinalizeUploadMedia(upload *StartGoogleUpload) (*MediaUpload, error) { func (c *Client) FinalizeUploadMedia(upload *StartGoogleUpload) (*MediaUpload, error) {
imageType := upload.Image.GetImageType()
encryptedImageSize := strconv.Itoa(len(upload.EncryptedMediaBytes)) encryptedImageSize := strconv.Itoa(len(upload.EncryptedMediaBytes))
finalizeUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "upload, finalize", "0", imageType.Format, "") finalizeUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "upload, finalize", "0", upload.MimeType, "")
req, reqErr := http.NewRequest("POST", upload.UploadURL, bytes.NewBuffer(upload.EncryptedMediaBytes)) req, reqErr := http.NewRequest("POST", upload.UploadURL, bytes.NewBuffer(upload.EncryptedMediaBytes))
if reqErr != nil { if reqErr != nil {
return nil, reqErr return nil, reqErr
@ -76,20 +74,13 @@ func (c *Client) FinalizeUploadMedia(upload *StartGoogleUpload) (*MediaUpload, e
return &MediaUpload{ return &MediaUpload{
MediaID: mediaIDs.Media.MediaID, MediaID: mediaIDs.Media.MediaID,
MediaNumber: mediaIDs.Media.MediaNumber, MediaNumber: mediaIDs.Media.MediaNumber,
Image: upload.Image,
}, nil }, nil
} }
func (c *Client) StartUploadMedia(image *Image) (*StartGoogleUpload, error) { func (c *Client) StartUploadMedia(encryptedImageBytes []byte, mime string) (*StartGoogleUpload, error) {
imageType := image.GetImageType()
encryptedImageBytes, encryptErr := image.GetEncryptedBytes()
if encryptErr != nil {
return nil, encryptErr
}
encryptedImageSize := strconv.Itoa(len(encryptedImageBytes)) encryptedImageSize := strconv.Itoa(len(encryptedImageBytes))
startUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "start", "", imageType.Format, "resumable") startUploadHeaders := util.NewMediaUploadHeaders(encryptedImageSize, "start", "", mime, "resumable")
startUploadPayload, buildPayloadErr := c.buildStartUploadPayload() startUploadPayload, buildPayloadErr := c.buildStartUploadPayload()
if buildPayloadErr != nil { if buildPayloadErr != nil {
return nil, buildPayloadErr return nil, buildPayloadErr
@ -125,8 +116,8 @@ func (c *Client) StartUploadMedia(image *Image) (*StartGoogleUpload, error) {
UploadStatus: rHeaders.Get("x-goog-upload-status"), UploadStatus: rHeaders.Get("x-goog-upload-status"),
ChunkGranularity: int64(chunkGranularity), ChunkGranularity: int64(chunkGranularity),
ControlURL: rHeaders.Get("x-goog-upload-control-url"), ControlURL: rHeaders.Get("x-goog-upload-control-url"),
MimeType: mime,
Image: image,
EncryptedMediaBytes: encryptedImageBytes, EncryptedMediaBytes: encryptedImageBytes,
} }
return uploadResponse, nil return uploadResponse, nil

View file

@ -40,6 +40,10 @@ var (
errUnknownMsgType = errors.New("unknown msgtype") errUnknownMsgType = errors.New("unknown msgtype")
errMediaUnsupportedType = errors.New("unsupported media type") errMediaUnsupportedType = errors.New("unsupported media type")
errTargetNotFound = errors.New("target event not found") errTargetNotFound = errors.New("target event not found")
errMissingMediaURL = errors.New("missing media URL")
errMediaDownloadFailed = errors.New("failed to download media")
errMediaDecryptFailed = errors.New("failed to decrypt media")
errMediaReuploadFailed = errors.New("failed to upload media to google")
errMessageTakingLong = errors.New("bridging the message is taking longer than usual") errMessageTakingLong = errors.New("bridging the message is taking longer than usual")
) )

144
portal.go
View file

@ -30,6 +30,7 @@ import (
"github.com/gabriel-vasile/mimetype" "github.com/gabriel-vasile/mimetype"
"github.com/rs/zerolog" "github.com/rs/zerolog"
"maunium.net/go/maulogger/v2" "maunium.net/go/maulogger/v2"
mutil "maunium.net/go/mautrix/util"
"maunium.net/go/mautrix/util/variationselector" "maunium.net/go/mautrix/util/variationselector"
"maunium.net/go/mautrix" "maunium.net/go/mautrix"
@ -40,6 +41,7 @@ import (
"maunium.net/go/mautrix/id" "maunium.net/go/mautrix/id"
"go.mau.fi/mautrix-gmessages/database" "go.mau.fi/mautrix-gmessages/database"
"go.mau.fi/mautrix-gmessages/libgm"
"go.mau.fi/mautrix-gmessages/libgm/binary" "go.mau.fi/mautrix-gmessages/libgm/binary"
"go.mau.fi/mautrix-gmessages/libgm/util" "go.mau.fi/mautrix-gmessages/libgm/util"
) )
@ -640,43 +642,6 @@ func (msg *ConvertedMessage) MergeCaption() {
msg.Parts = []ConvertedMessagePart{filePart} msg.Parts = []ConvertedMessagePart{filePart}
} }
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) { func (portal *Portal) convertGoogleMedia(source *User, intent *appservice.IntentAPI, msg *binary.MediaContent) (*event.MessageEventContent, error) {
var data []byte var data []byte
var err error var err error
@ -684,7 +649,7 @@ func (portal *Portal) convertGoogleMedia(source *User, intent *appservice.Intent
if err != nil { if err != nil {
return nil, err return nil, err
} }
mime := mediaFormatToMime[msg.GetFormat()] mime := libgm.FormatToMediaType[msg.GetFormat()].Format
if mime == "" { if mime == "" {
mime = mimetype.Detect(data).String() mime = mimetype.Detect(data).String()
} }
@ -1207,28 +1172,8 @@ func (portal *Portal) uploadMedia(intent *appservice.IntentAPI, data []byte, con
return nil return nil
} }
func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timings messageTimings) { func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, content *event.MessageEventContent, txnID string) (*binary.SendMessagePayload, error) {
ms := metricSender{portal: portal, timings: &timings} log := zerolog.Ctx(ctx)
log := portal.zlog.With().
Str("event_id", evt.ID.String()).
Str("action", "handle matrix message").
Logger()
ctx := log.WithContext(context.TODO())
log.Debug().Dur("age", timings.totalReceive).Msg("Handling Matrix message")
content, ok := evt.Content.Parsed.(*event.MessageEventContent)
if !ok {
return
}
txnID := util.GenerateTmpID()
portal.outgoingMessagesLock.Lock()
portal.outgoingMessages[txnID] = &outgoingMessage{Event: evt}
portal.outgoingMessagesLock.Unlock()
if evt.Type == event.EventSticker {
content.MsgType = event.MsgImage
}
req := &binary.SendMessagePayload{ req := &binary.SendMessagePayload{
ConversationID: portal.ID, ConversationID: portal.ID,
TmpID: txnID, TmpID: txnID,
@ -1265,26 +1210,71 @@ func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timing
}}, }},
}} }}
case event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile: case event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile:
//fileName := content.Body var url id.ContentURI
//if content.FileName != "" { if content.File != nil {
// fileName = content.FileName url = content.File.URL.ParseOrIgnore()
//} } else {
//sender.Client.StartUploadMedia() url = content.URL.ParseOrIgnore()
//req.MessagePayload.MessageInfo = []*binary.MessageInfo{{ }
// Data: &binary.MessageInfo_MediaContent{MediaContent: &binary.MediaContent{ if url.IsEmpty() {
// Format: 0, return nil, errMissingMediaURL
// MediaID: mediaID, }
// MediaName: fileName, data, err := portal.MainIntent().DownloadBytesContext(ctx, url)
// Size: int64(len(data)), if err != nil {
// DecryptionKey: decryptionKey, return nil, mutil.NewDualError(errMediaDownloadFailed, err)
// }}, }
//}} if content.File != nil {
err = content.File.DecryptInPlace(data)
if err != nil {
return nil, mutil.NewDualError(errMediaDecryptFailed, err)
}
}
if content.Info.MimeType == "" {
content.Info.MimeType = mimetype.Detect(data).String()
}
fileName := content.Body
if content.FileName != "" {
fileName = content.FileName
}
resp, err := sender.Client.UploadMedia(data, fileName, content.Info.MimeType)
if err != nil {
return nil, mutil.NewDualError(errMediaReuploadFailed, err)
}
req.MessagePayload.MessageInfo = []*binary.MessageInfo{{
Data: &binary.MessageInfo_MediaContent{MediaContent: resp},
}}
default: default:
go ms.sendMessageMetrics(evt, fmt.Errorf("unsupported msgtype"), "Ignoring", true) return nil, fmt.Errorf("%w %s", errUnknownMsgType, content.MsgType)
}
return req, nil
}
func (portal *Portal) HandleMatrixMessage(sender *User, evt *event.Event, timings messageTimings) {
ms := metricSender{portal: portal, timings: &timings}
log := portal.zlog.With().
Str("event_id", evt.ID.String()).
Str("action", "handle matrix message").
Logger()
ctx := log.WithContext(context.TODO())
log.Debug().Dur("age", timings.totalReceive).Msg("Handling Matrix message")
content, ok := evt.Content.Parsed.(*event.MessageEventContent)
if !ok {
return return
} }
_, err := sender.Client.Conversations.SendMessage(req)
if err != nil { txnID := util.GenerateTmpID()
portal.outgoingMessagesLock.Lock()
portal.outgoingMessages[txnID] = &outgoingMessage{Event: evt}
portal.outgoingMessagesLock.Unlock()
if evt.Type == event.EventSticker {
content.MsgType = event.MsgImage
}
if req, err := portal.convertMatrixMessage(ctx, sender, content, txnID); err != nil {
go ms.sendMessageMetrics(evt, err, "Error converting", true)
} else if _, err = sender.Client.Conversations.SendMessage(req); err != nil {
go ms.sendMessageMetrics(evt, err, "Error sending", true) go ms.sendMessageMetrics(evt, err, "Error sending", true)
} else { } else {
go ms.sendMessageMetrics(evt, nil, "", true) go ms.sendMessageMetrics(evt, nil, "", true)