diff --git a/pkg/connector/client.go b/pkg/connector/client.go index c2d928c..b41f11a 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -143,7 +143,7 @@ func (c *GChatClient) onConnect(ctx context.Context) { }, ChatInfo: &bridgev2.ChatInfo{ Name: name, - Members: c.gcMembersToMxMembers(gcMembers), + Members: c.gcMembersToMatrix(gcMembers), }, }) @@ -200,9 +200,19 @@ func (c *GChatClient) convertToMatrix(ctx context.Context, portal *bridgev2.Port parts = append(parts, textPart) } + for _, annotation := range msg.Annotations { + attachmentPart, err := c.gcAnnotationToMatrix(ctx, portal, intent, annotation) + if err != nil { + fmt.Println(err) + continue + } + parts = append(parts, attachmentPart) + } + cm := &bridgev2.ConvertedMessage{ Parts: parts, } + cm.MergeCaption() return cm } diff --git a/pkg/connector/mapping.go b/pkg/connector/mapping.go index 4c57f8e..54819ab 100644 --- a/pkg/connector/mapping.go +++ b/pkg/connector/mapping.go @@ -1,13 +1,20 @@ package connector import ( + "context" + "io" + "mime" + "net/url" + "strings" + "maunium.net/go/mautrix/bridgev2" "maunium.net/go/mautrix/bridgev2/networkid" + bridgeEvt "maunium.net/go/mautrix/event" "go.mau.fi/mautrix-googlechat/pkg/gchatmeow/proto" ) -func (c *GChatClient) gcMembersToMxMembers(gcMembers []*proto.UserId) *bridgev2.ChatMemberList { +func (c *GChatClient) gcMembersToMatrix(gcMembers []*proto.UserId) *bridgev2.ChatMemberList { memberMap := map[networkid.UserID]bridgev2.ChatMember{} for _, gcMember := range gcMembers { userId := networkid.UserID(*gcMember.Id) @@ -23,3 +30,83 @@ func (c *GChatClient) gcMembersToMxMembers(gcMembers []*proto.UserId) *bridgev2. MemberMap: memberMap, } } + +func (c *GChatClient) gcAnnotationToMatrix(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, annotation *proto.Annotation) (*bridgev2.ConvertedMessagePart, error) { + var attUrl *url.URL + var mimeType string + var fileName string + uploadMeta := annotation.GetUploadMetadata() + urlMeta := annotation.GetUrlMetadata() + if uploadMeta != nil { + mimeType = *uploadMeta.ContentType + fileName = *uploadMeta.ContentName + params := url.Values{ + "url_type": []string{"DOWNLOAD_URL"}, + "attachment_token": []string{uploadMeta.GetAttachmentToken()}, + } + if strings.HasPrefix(*uploadMeta.ContentType, "image/") { + params.Set("url_type", "FIFE_URL") + params.Set("sz", "w10000-h10000") + params.Set("content_type", *uploadMeta.ContentType) + } + parsedUrl, err := url.Parse("https://chat.google.com/api/get_attachment_url") + if err != nil { + return nil, err + } + attUrl = parsedUrl + attUrl.RawQuery = params.Encode() + + } else if urlMeta != nil { + if urlMeta.MimeType != nil { + mimeType = *urlMeta.MimeType + } + parsedUrl, err := url.Parse(*urlMeta.Url.Url) + if err != nil { + return nil, err + } + attUrl = parsedUrl + } else { + return nil, nil + } + resp, err := c.client.DownloadAttachment(ctx, attUrl) + if err != nil { + return nil, err + } + + if fileName == "" { + _, params, _ := mime.ParseMediaType(resp.Header.Get("Content-Disposition")) + fileName = params["filename"] + } + if mimeType == "" { + mimeType = resp.Header.Get("Content-Type") + } + if fileName == "" && mimeType != "" { + fileName = strings.Replace(mimeType, "/", ".", 1) + } + + content := bridgeEvt.MessageEventContent{ + Body: fileName, + Info: &bridgeEvt.FileInfo{ + MimeType: mimeType, + }, + MsgType: bridgeEvt.MsgImage, + } + content.URL, content.File, err = intent.UploadMediaStream(ctx, portal.MXID, resp.ContentLength, true, func(file io.Writer) (*bridgev2.FileStreamResult, error) { + _, err := io.Copy(file, resp.Body) + if err != nil { + return nil, err + } + return &bridgev2.FileStreamResult{ + MimeType: content.Info.MimeType, + FileName: fileName, + }, nil + }) + if err != nil { + return nil, err + } + return &bridgev2.ConvertedMessagePart{ + ID: "", + Type: bridgeEvt.EventMessage, + Content: &content, + }, nil +} diff --git a/pkg/gchatmeow/channel.go b/pkg/gchatmeow/channel.go index a866151..cbe7baa 100644 --- a/pkg/gchatmeow/channel.go +++ b/pkg/gchatmeow/channel.go @@ -425,7 +425,7 @@ func (c *Channel) longPollRequest(ctx context.Context) error { func (c *Channel) onPushData(dataBytes []byte) error { // Log received chunk - log.Printf("Received chunk:\n%s", string(dataBytes)) + // log.Printf("Received chunk:\n%s", string(dataBytes)) // Process chunks chunks := c.chunkParser.GetChunks(dataBytes) diff --git a/pkg/gchatmeow/client.go b/pkg/gchatmeow/client.go index 7040883..79d6ffb 100644 --- a/pkg/gchatmeow/client.go +++ b/pkg/gchatmeow/client.go @@ -4,6 +4,10 @@ import ( "context" "encoding/json" "fmt" + "slices" + "strings" + + // "io" "log" "net/http" "net/url" @@ -200,7 +204,7 @@ func (c *Client) onReceiveArray(arg interface{}) { // Process each event body for _, evt := range c.splitEventBodies(resp.GetEvent()) { - log.Printf("Dispatching stream event: %v", evt) + log.Printf("Dispatching stream event: %v", evt.String()) c.OnStreamEvent.Fire(evt) } @@ -252,3 +256,23 @@ func (c *Client) GetSelf(ctx context.Context) (*proto.User, error) { func (c *Client) Sync(ctx context.Context) (*proto.PaginatedWorldResponse, error) { return c.paginatedWorld(ctx) } + +func (c *Client) DownloadAttachment(ctx context.Context, attUrl *url.URL) (*http.Response, error) { + urlStr := attUrl.String() + if strings.HasSuffix(attUrl.Host, ".google.com") { + resp, err := c.session.FetchRaw(ctx, http.MethodGet, urlStr, nil, nil, false, nil) + if err != nil { + return nil, err + } + if slices.Contains([]int{301, 302, 307, 308}, resp.StatusCode) { + redirected, err := url.Parse(resp.Header.Get("Location")) + if err != nil { + return nil, err + } + return c.DownloadAttachment(ctx, redirected) + } + } + + // External attachment + return http.Get(urlStr) +}