diff --git a/pkg/connector/client.go b/pkg/connector/client.go index ef98495..dc785e9 100644 --- a/pkg/connector/client.go +++ b/pkg/connector/client.go @@ -2,6 +2,7 @@ package connector import ( "context" + "errors" "fmt" "strconv" "time" @@ -14,6 +15,8 @@ import ( "go.mau.fi/mautrix-meta/config" "go.mau.fi/mautrix-meta/messagix" "go.mau.fi/mautrix-meta/messagix/cookies" + + //"go.mau.fi/mautrix-meta/messagix/socket" "go.mau.fi/mautrix-meta/messagix/table" "go.mau.fi/mautrix-meta/messagix/types" @@ -21,8 +24,12 @@ import ( "maunium.net/go/mautrix/bridge/status" "maunium.net/go/mautrix/bridgev2" + "maunium.net/go/mautrix/bridgev2/database" "maunium.net/go/mautrix/bridgev2/networkid" + "maunium.net/go/mautrix/event" + //"maunium.net/go/mautrix/event" + metaTypes "go.mau.fi/mautrix-meta/messagix/types" ) type metaEvent struct { @@ -285,9 +292,132 @@ func (m *MetaClient) GetUserInfo(ctx context.Context, ghost *bridgev2.Ghost) (*b panic("unimplemented") } +type msgconvContextKey int + +const ( + msgconvContextKeyIntent msgconvContextKey = iota + msgconvContextKeyClient + msgconvContextKeyE2EEClient + msgconvContextKeyBackfill +) + // HandleMatrixMessage implements bridgev2.NetworkAPI. -func (m *MetaClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (message *bridgev2.MatrixMessageResponse, err error) { - panic("unimplemented") +func (m *MetaClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) { + log := zerolog.Ctx(ctx) + + content, ok := msg.Event.Content.Parsed.(*event.MessageEventContent) + if !ok { + log.Error().Type("content_type", content).Msg("Unexpected parsed content type") + return nil, fmt.Errorf("unexpected parsed content type: %T", content) + } + if content.MsgType == event.MsgNotice /*&& !portal.bridge.Config.Bridge.BridgeNotices*/ { + log.Warn().Msg("Ignoring notice message") + return nil, nil + } + + ctx = context.WithValue(ctx, msgconvContextKeyClient, m.client) + + thread, err := strconv.Atoi(string(msg.Portal.ID)) + if err != nil { + log.Err(err).Str("thread_id", string(msg.Portal.ID)).Msg("Failed to parse thread ID") + return nil, fmt.Errorf("failed to parse thread ID: %w", err) + } + + tasks, otid, err := m.messageConverter.ToMeta(ctx, msg.Event, content, false, int64(thread)) + if errors.Is(err, metaTypes.ErrPleaseReloadPage) { + log.Err(err).Msg("Got please reload page error while converting message, reloading page in background") + // go m.client.Disconnect() + // err = errReloading + panic("unimplemented") + } else if errors.Is(err, messagix.ErrTokenInvalidated) { + panic("unimplemented") + // go sender.DisconnectFromError(status.BridgeState{ + // StateEvent: status.StateBadCredentials, + // Error: MetaCookieRemoved, + // }) + // err = errLoggedOut + } + + if err != nil { + log.Err(err).Msg("Failed to convert message") + //go ms.sendMessageMetrics(evt, err, "Error converting", true) + return nil, err + } + + log.UpdateContext(func(c zerolog.Context) zerolog.Context { + return c.Int64("otid", otid) + }) + log.Debug().Msg("Sending Matrix message to Meta") + + //otidStr := strconv.FormatInt(otid, 10) + //portal.pendingMessages[otid] = evt.ID + //messageTS := time.Now() + var resp *table.LSTable + + retries := 0 + for retries < 5 { + if err = m.client.WaitUntilCanSendMessages(15 * time.Second); err != nil { + log.Err(err).Msg("Error waiting to be able to send messages, retrying") + } else { + resp, err = m.client.ExecuteTasks(tasks...) + if err == nil { + break + } + log.Err(err).Msg("Failed to send message to Meta, retrying") + } + retries++ + } + + log.Trace().Any("response", resp).Msg("Meta send response") + // var msgID string + // if resp != nil && err == nil { + // for _, replace := range resp.LSReplaceOptimsiticMessage { + // if replace.OfflineThreadingId == otidStr { + // msgID = replace.MessageId + // } + // } + // if len(msgID) == 0 { + // for _, failed := range resp.LSMarkOptimisticMessageFailed { + // if failed.OTID == otidStr { + // log.Warn().Str("message", failed.Message).Msg("Sending message failed") + // //go ms.sendMessageMetrics(evt, fmt.Errorf("%w: %s", errServerRejected, failed.Message), "Error sending", true) + // return + // } + // } + // for _, failed := range resp.LSHandleFailedTask { + // if failed.OTID == otidStr { + // log.Warn().Str("message", failed.Message).Msg("Sending message failed") + // go ms.sendMessageMetrics(evt, fmt.Errorf("%w: %s", errServerRejected, failed.Message), "Error sending", true) + // return + // } + // } + // log.Warn().Msg("Message send response didn't include message ID") + // } + // } + // if msgID != "" { + // portal.pendingMessagesLock.Lock() + // _, ok = portal.pendingMessages[otid] + // if ok { + // portal.storeMessageInDB(ctx, evt.ID, msgID, otid, sender.MetaID, messageTS, 0) + // delete(portal.pendingMessages, otid) + // } else { + // log.Debug().Msg("Not storing message send response: pending message was already removed from map") + // } + // portal.pendingMessagesLock.Unlock() + // } + + return &bridgev2.MatrixMessageResponse{ + DB: &database.Message{ + //ID: nil, + MXID: msg.Event.ID, + Room: networkid.PortalKey{ID: msg.Portal.ID}, + //SenderID: nil, + Timestamp: time.Time{}, + }, + }, nil + + // timings.totalSend = time.Since(start) + // go ms.sendMessageMetrics(evt, err, "Error sending", true) } // IsLoggedIn implements bridgev2.NetworkAPI. diff --git a/pkg/connector/msgconv/from-matrix.go b/pkg/connector/msgconv/from-matrix.go index fe070fb..b1073cf 100644 --- a/pkg/connector/msgconv/from-matrix.go +++ b/pkg/connector/msgconv/from-matrix.go @@ -20,20 +20,21 @@ import ( "context" "errors" "fmt" - "net/http" + + //"net/http" "time" - "github.com/rs/zerolog" - "go.mau.fi/util/exerrors" - "go.mau.fi/util/exmime" - "go.mau.fi/util/ffmpeg" + //"github.com/rs/zerolog" + //"go.mau.fi/util/exerrors" + //"go.mau.fi/util/exmime" + //"go.mau.fi/util/ffmpeg" "maunium.net/go/mautrix/event" - "go.mau.fi/mautrix-meta/messagix" + //"go.mau.fi/mautrix-meta/messagix" "go.mau.fi/mautrix-meta/messagix/methods" "go.mau.fi/mautrix-meta/messagix/socket" "go.mau.fi/mautrix-meta/messagix/table" - "go.mau.fi/mautrix-meta/messagix/types" + //"go.mau.fi/mautrix-meta/messagix/types" ) var ( @@ -46,20 +47,21 @@ var ( ErrURLNotFound = errors.New("url not found") ) -func (mc *MessageConverter) ToMeta(ctx context.Context, evt *event.Event, content *event.MessageEventContent, relaybotFormatted bool) ([]socket.Task, int64, error) { +func (mc *MessageConverter) ToMeta(ctx context.Context, evt *event.Event, content *event.MessageEventContent, relaybotFormatted bool, threadID int64) ([]socket.Task, int64, error) { if evt.Type == event.EventSticker { content.MsgType = event.MsgImage } task := &socket.SendMessageTask{ - ThreadId: mc.GetData(ctx).ThreadID, + //ThreadId: mc.GetData(ctx).ThreadID, + ThreadId: threadID, Otid: methods.GenerateEpochId(), Source: table.MESSENGER_INBOX_IN_THREAD, InitiatingSource: table.FACEBOOK_INBOX, SendType: table.TEXT, SyncGroup: 1, - ReplyMetaData: mc.GetMetaReply(ctx, content), + //ReplyMetaData: mc.GetMetaReply(ctx, content), } if content.MsgType == event.MsgEmote && !relaybotFormatted { content.Body = "/me " + content.Body @@ -70,22 +72,22 @@ func (mc *MessageConverter) ToMeta(ctx context.Context, evt *event.Event, conten switch content.MsgType { case event.MsgText, event.MsgNotice, event.MsgEmote: task.Text = content.Body - case event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile: - resp, err := mc.reuploadFileToMeta(ctx, evt, content) - if err != nil { - return nil, 0, err - } - attachmentID := resp.Payload.RealMetadata.GetFbId() - if attachmentID == 0 { - zerolog.Ctx(ctx).Warn().RawJSON("response", resp.Raw).Msg("No fbid received for upload") - return nil, 0, fmt.Errorf("failed to upload attachment: fbid not received") - } - task.SendType = table.MEDIA - task.AttachmentFBIds = []int64{attachmentID} - if content.FileName != "" && content.Body != content.FileName { - // This might not actually be allowed - task.Text = content.Body - } + // case event.MsgImage, event.MsgVideo, event.MsgAudio, event.MsgFile: + // resp, err := mc.reuploadFileToMeta(ctx, evt, content) + // if err != nil { + // return nil, 0, err + // } + // attachmentID := resp.Payload.RealMetadata.GetFbId() + // if attachmentID == 0 { + // zerolog.Ctx(ctx).Warn().RawJSON("response", resp.Raw).Msg("No fbid received for upload") + // return nil, 0, fmt.Errorf("failed to upload attachment: fbid not received") + // } + // task.SendType = table.MEDIA + // task.AttachmentFBIds = []int64{attachmentID} + // if content.FileName != "" && content.Body != content.FileName { + // // This might not actually be allowed + // task.Text = content.Body + // } case event.MsgLocation: // TODO implement fallthrough @@ -101,65 +103,65 @@ func (mc *MessageConverter) ToMeta(ctx context.Context, evt *event.Event, conten return []socket.Task{task, readTask}, task.Otid, nil } -func (mc *MessageConverter) downloadMatrixMedia(ctx context.Context, content *event.MessageEventContent) (data []byte, mimeType, fileName string, err error) { - mxc := content.URL - if content.File != nil { - mxc = content.File.URL - } - data, err = mc.DownloadMatrixMedia(ctx, mxc) - if err != nil { - err = exerrors.NewDualError(ErrMediaDownloadFailed, err) - return - } - if content.File != nil { - err = content.File.DecryptInPlace(data) - if err != nil { - err = exerrors.NewDualError(ErrMediaDecryptFailed, err) - return - } - } - mimeType = content.GetInfo().MimeType - if mimeType == "" { - mimeType = http.DetectContentType(data) - } - fileName = content.FileName - if fileName == "" { - fileName = content.Body - if fileName == "" { - fileName = string(content.MsgType)[2:] + exmime.ExtensionFromMimetype(mimeType) - } - } - return -} +// func (mc *MessageConverter) downloadMatrixMedia(ctx context.Context, content *event.MessageEventContent) (data []byte, mimeType, fileName string, err error) { +// mxc := content.URL +// if content.File != nil { +// mxc = content.File.URL +// } +// data, err = mc.DownloadMatrixMedia(ctx, mxc) +// if err != nil { +// err = exerrors.NewDualError(ErrMediaDownloadFailed, err) +// return +// } +// if content.File != nil { +// err = content.File.DecryptInPlace(data) +// if err != nil { +// err = exerrors.NewDualError(ErrMediaDecryptFailed, err) +// return +// } +// } +// mimeType = content.GetInfo().MimeType +// if mimeType == "" { +// mimeType = http.DetectContentType(data) +// } +// fileName = content.FileName +// if fileName == "" { +// fileName = content.Body +// if fileName == "" { +// fileName = string(content.MsgType)[2:] + exmime.ExtensionFromMimetype(mimeType) +// } +// } +// return +// } -func (mc *MessageConverter) reuploadFileToMeta(ctx context.Context, evt *event.Event, content *event.MessageEventContent) (*types.MercuryUploadResponse, error) { - threadID := mc.GetData(ctx).ThreadID - data, mimeType, fileName, err := mc.downloadMatrixMedia(ctx, content) - if err != nil { - return nil, err - } - _, isVoice := evt.Content.Raw["org.matrix.msc3245.voice"] - if isVoice { - data, err = ffmpeg.ConvertBytes(ctx, data, ".m4a", []string{}, []string{"-c:a", "aac"}, mimeType) - if err != nil { - return nil, err - } - mimeType = "audio/mp4" - fileName += ".m4a" - } - resp, err := mc.GetClient(ctx).SendMercuryUploadRequest(ctx, threadID, &messagix.MercuryUploadMedia{ - Filename: fileName, - MimeType: mimeType, - MediaData: data, - IsVoiceClip: isVoice, - }) - if err != nil { - zerolog.Ctx(ctx).Debug(). - Str("file_name", fileName). - Str("mime_type", mimeType). - Bool("is_voice_clip", isVoice). - Msg("Failed upload metadata") - return nil, fmt.Errorf("%w: %w", ErrMediaUploadFailed, err) - } - return resp, nil -} +// func (mc *MessageConverter) reuploadFileToMeta(ctx context.Context, evt *event.Event, content *event.MessageEventContent) (*types.MercuryUploadResponse, error) { +// threadID := mc.GetData(ctx).ThreadID +// data, mimeType, fileName, err := mc.downloadMatrixMedia(ctx, content) +// if err != nil { +// return nil, err +// } +// _, isVoice := evt.Content.Raw["org.matrix.msc3245.voice"] +// if isVoice { +// data, err = ffmpeg.ConvertBytes(ctx, data, ".m4a", []string{}, []string{"-c:a", "aac"}, mimeType) +// if err != nil { +// return nil, err +// } +// mimeType = "audio/mp4" +// fileName += ".m4a" +// } +// resp, err := mc.GetClient(ctx).SendMercuryUploadRequest(ctx, threadID, &messagix.MercuryUploadMedia{ +// Filename: fileName, +// MimeType: mimeType, +// MediaData: data, +// IsVoiceClip: isVoice, +// }) +// if err != nil { +// zerolog.Ctx(ctx).Debug(). +// Str("file_name", fileName). +// Str("mime_type", mimeType). +// Bool("is_voice_clip", isVoice). +// Msg("Failed upload metadata") +// return nil, fmt.Errorf("%w: %w", ErrMediaUploadFailed, err) +// } +// return resp, nil +// } diff --git a/pkg/connector/msgconv/from-meta.go b/pkg/connector/msgconv/from-meta.go index 7112672..264e075 100644 --- a/pkg/connector/msgconv/from-meta.go +++ b/pkg/connector/msgconv/from-meta.go @@ -17,30 +17,34 @@ package msgconv import ( - "bytes" + //"bytes" "context" "errors" "fmt" "html" - "image" + + //"image" _ "image/gif" _ "image/jpeg" _ "image/png" - "net/http" + + //"net/http" "net/url" "regexp" "slices" - "strconv" + + //"strconv" "strings" "github.com/rs/zerolog" - "go.mau.fi/util/exmime" - "go.mau.fi/util/ffmpeg" + //"go.mau.fi/util/exmime" + //"go.mau.fi/util/ffmpeg" //"golang.org/x/exp/maps" _ "golang.org/x/image/webp" "maunium.net/go/mautrix/bridgev2" - "maunium.net/go/mautrix/crypto/attachment" + + //"maunium.net/go/mautrix/crypto/attachment" "maunium.net/go/mautrix/event" "maunium.net/go/mautrix/id" @@ -187,24 +191,25 @@ func (mc *MessageConverter) ToMatrix(ctx context.Context, msg *table.WrappedMess _, hasExternalURL := part.Extra["external_url"] unsupported, _ := part.Extra["fi.mau.unsupported"].(bool) if unsupported && !hasExternalURL { - _, threadURL := mc.GetThreadURL(ctx) - if threadURL != "" { - part.Extra["external_url"] = threadURL - part.Content.EnsureHasHTML() - var protocolName string - switch { - case strings.HasPrefix(threadURL, "https://www.instagram.com"): - protocolName = "Instagram" - case strings.HasPrefix(threadURL, "https://www.facebook.com"): - protocolName = "Facebook" - case strings.HasPrefix(threadURL, "https://www.messenger.com"): - protocolName = "Messenger" - default: - protocolName = "native app" - } - part.Content.Body = fmt.Sprintf("%s\n\nOpen in %s: %s", part.Content.Body, protocolName, threadURL) - part.Content.FormattedBody = fmt.Sprintf("%s

Click here to open in %s", part.Content.FormattedBody, threadURL, protocolName) - } + //_, threadURL := mc.GetThreadURL(ctx) + panic("GetThreadURL not implemented") + // if threadURL != "" { + // part.Extra["external_url"] = threadURL + // part.Content.EnsureHasHTML() + // var protocolName string + // switch { + // case strings.HasPrefix(threadURL, "https://www.instagram.com"): + // protocolName = "Instagram" + // case strings.HasPrefix(threadURL, "https://www.facebook.com"): + // protocolName = "Facebook" + // case strings.HasPrefix(threadURL, "https://www.messenger.com"): + // protocolName = "Messenger" + // default: + // protocolName = "native app" + // } + // part.Content.Body = fmt.Sprintf("%s\n\nOpen in %s: %s", part.Content.Body, protocolName, threadURL) + // part.Content.FormattedBody = fmt.Sprintf("%s

Click here to open in %s", part.Content.FormattedBody, threadURL, protocolName) + // } } if part.Content.Mentions == nil { part.Content.Mentions = &event.Mentions{} @@ -388,223 +393,223 @@ func addExternalURLCaption(content *event.MessageEventContent, externalURL strin } } -func (mc *MessageConverter) fetchFullXMA(ctx context.Context, att *table.WrappedXMA, minimalConverted *bridgev2.ConvertedMessagePart) *bridgev2.ConvertedMessagePart { - ig := mc.GetClient(ctx).Instagram - if att.CTA == nil || ig == nil { - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "unsupported" - return minimalConverted - } - log := zerolog.Ctx(ctx) - switch { - case strings.HasPrefix(att.CTA.NativeUrl, "instagram://media/?shortcode="), strings.HasPrefix(att.CTA.NativeUrl, "instagram://reels_share/?shortcode="): - actionURL, _ := url.Parse(removeLPHP(att.CTA.ActionUrl)) - var carouselChildMediaID string - if actionURL != nil { - carouselChildMediaID = actionURL.Query().Get("carousel_share_child_media_id") - } +// func (mc *MessageConverter) fetchFullXMA(ctx context.Context, att *table.WrappedXMA, minimalConverted *bridgev2.ConvertedMessagePart) *bridgev2.ConvertedMessagePart { +// ig := mc.GetClient(ctx).Instagram +// if att.CTA == nil || ig == nil { +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "unsupported" +// return minimalConverted +// } +// log := zerolog.Ctx(ctx) +// switch { +// case strings.HasPrefix(att.CTA.NativeUrl, "instagram://media/?shortcode="), strings.HasPrefix(att.CTA.NativeUrl, "instagram://reels_share/?shortcode="): +// actionURL, _ := url.Parse(removeLPHP(att.CTA.ActionUrl)) +// var carouselChildMediaID string +// if actionURL != nil { +// carouselChildMediaID = actionURL.Query().Get("carousel_share_child_media_id") +// } - mediaShortcode := strings.TrimPrefix(att.CTA.NativeUrl, "instagram://media/?shortcode=") - mediaShortcode = strings.TrimPrefix(mediaShortcode, "instagram://reels_share/?shortcode=") - externalURL := fmt.Sprintf("https://www.instagram.com/p/%s/", mediaShortcode) - minimalConverted.Extra["external_url"] = externalURL - addExternalURLCaption(minimalConverted.Content, externalURL) - if !mc.ShouldFetchXMA(ctx) { - log.Debug().Msg("Not fetching XMA media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "skip" - return minimalConverted - } +// mediaShortcode := strings.TrimPrefix(att.CTA.NativeUrl, "instagram://media/?shortcode=") +// mediaShortcode = strings.TrimPrefix(mediaShortcode, "instagram://reels_share/?shortcode=") +// externalURL := fmt.Sprintf("https://www.instagram.com/p/%s/", mediaShortcode) +// minimalConverted.Extra["external_url"] = externalURL +// addExternalURLCaption(minimalConverted.Content, externalURL) +// if !mc.ShouldFetchXMA(ctx) { +// log.Debug().Msg("Not fetching XMA media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "skip" +// return minimalConverted +// } - log.Trace().Any("cta_data", att.CTA).Msg("Fetching XMA media from CTA data") - resp, err := ig.FetchMedia(strconv.FormatInt(att.CTA.TargetId, 10), mediaShortcode) - if err != nil { - log.Err(err).Int64("target_id", att.CTA.TargetId).Msg("Failed to fetch XMA media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "fetch fail" - return minimalConverted - } else if len(resp.Items) == 0 { - log.Warn().Int64("target_id", att.CTA.TargetId).Msg("Got empty XMA media response") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "empty response" - return minimalConverted - } else { - log.Trace().Int64("target_id", att.CTA.TargetId).Any("response", resp).Msg("Fetched XMA media") - log.Debug().Msg("Fetched XMA media") - targetItem := resp.Items[0] - if targetItem.CarouselMedia != nil && carouselChildMediaID != "" { - for _, subitem := range targetItem.CarouselMedia { - if subitem.ID == carouselChildMediaID { - targetItem = subitem - break - } - } - } - secondConverted, err := mc.instagramFetchedMediaToMatrix(ctx, att, targetItem) - if err != nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer fetched media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "reupload fail" - return minimalConverted - } - secondConverted.Content.Info.ThumbnailInfo = minimalConverted.Content.Info - secondConverted.Content.Info.ThumbnailURL = minimalConverted.Content.URL - secondConverted.Content.Info.ThumbnailFile = minimalConverted.Content.File - secondConverted.Extra["com.beeper.instagram_item_username"] = targetItem.User.Username - if externalURL != "" { - secondConverted.Extra["external_url"] = externalURL - } - secondConverted.Extra["fi.mau.meta.xma_fetch_status"] = "success" - return secondConverted - } - case strings.HasPrefix(att.CTA.ActionUrl, "/stories/direct/"): - log.Trace().Any("cta_data", att.CTA).Msg("Fetching XMA story from CTA data") - externalURL := fmt.Sprintf("https://www.instagram.com%s", att.CTA.ActionUrl) - match := reelActionURLRegex.FindStringSubmatch(att.CTA.ActionUrl) - if usernameRegex.MatchString(att.HeaderTitle) && len(match) == 3 { - // Very hacky way to hopefully fix the URL to work on mobile. - // When fetching the XMA data, this is done again later in a safer way. - externalURL = fmt.Sprintf("https://www.instagram.com/stories/%s/%s/", att.HeaderTitle, match[1]) - } - minimalConverted.Extra["external_url"] = externalURL - addExternalURLCaption(minimalConverted.Content, externalURL) - if !mc.ShouldFetchXMA(ctx) { - log.Debug().Msg("Not fetching XMA media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "skip" - return minimalConverted - } +// log.Trace().Any("cta_data", att.CTA).Msg("Fetching XMA media from CTA data") +// resp, err := ig.FetchMedia(strconv.FormatInt(att.CTA.TargetId, 10), mediaShortcode) +// if err != nil { +// log.Err(err).Int64("target_id", att.CTA.TargetId).Msg("Failed to fetch XMA media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "fetch fail" +// return minimalConverted +// } else if len(resp.Items) == 0 { +// log.Warn().Int64("target_id", att.CTA.TargetId).Msg("Got empty XMA media response") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "empty response" +// return minimalConverted +// } else { +// log.Trace().Int64("target_id", att.CTA.TargetId).Any("response", resp).Msg("Fetched XMA media") +// log.Debug().Msg("Fetched XMA media") +// targetItem := resp.Items[0] +// if targetItem.CarouselMedia != nil && carouselChildMediaID != "" { +// for _, subitem := range targetItem.CarouselMedia { +// if subitem.ID == carouselChildMediaID { +// targetItem = subitem +// break +// } +// } +// } +// secondConverted, err := mc.instagramFetchedMediaToMatrix(ctx, att, targetItem) +// if err != nil { +// zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer fetched media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "reupload fail" +// return minimalConverted +// } +// secondConverted.Content.Info.ThumbnailInfo = minimalConverted.Content.Info +// secondConverted.Content.Info.ThumbnailURL = minimalConverted.Content.URL +// secondConverted.Content.Info.ThumbnailFile = minimalConverted.Content.File +// secondConverted.Extra["com.beeper.instagram_item_username"] = targetItem.User.Username +// if externalURL != "" { +// secondConverted.Extra["external_url"] = externalURL +// } +// secondConverted.Extra["fi.mau.meta.xma_fetch_status"] = "success" +// return secondConverted +// } +// case strings.HasPrefix(att.CTA.ActionUrl, "/stories/direct/"): +// log.Trace().Any("cta_data", att.CTA).Msg("Fetching XMA story from CTA data") +// externalURL := fmt.Sprintf("https://www.instagram.com%s", att.CTA.ActionUrl) +// match := reelActionURLRegex.FindStringSubmatch(att.CTA.ActionUrl) +// if usernameRegex.MatchString(att.HeaderTitle) && len(match) == 3 { +// // Very hacky way to hopefully fix the URL to work on mobile. +// // When fetching the XMA data, this is done again later in a safer way. +// externalURL = fmt.Sprintf("https://www.instagram.com/stories/%s/%s/", att.HeaderTitle, match[1]) +// } +// minimalConverted.Extra["external_url"] = externalURL +// addExternalURLCaption(minimalConverted.Content, externalURL) +// if !mc.ShouldFetchXMA(ctx) { +// log.Debug().Msg("Not fetching XMA media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "skip" +// return minimalConverted +// } - if len(match) != 3 { - log.Warn().Str("action_url", att.CTA.ActionUrl).Msg("Failed to parse story action URL") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "parse fail" - return minimalConverted - } else if resp, err := ig.FetchReel([]string{match[2]}, match[1]); err != nil { - log.Err(err).Str("action_url", att.CTA.ActionUrl).Msg("Failed to fetch XMA story") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "fetch fail" - return minimalConverted - } else if reel, ok := resp.Reels[match[2]]; !ok { - log.Trace(). - Str("action_url", att.CTA.ActionUrl). - Any("response", resp). - Msg("XMA story fetch data") - log.Warn(). - Str("action_url", att.CTA.ActionUrl). - Str("reel_id", match[2]). - Str("media_id", match[1]). - Str("response_status", resp.Status). - Msg("Got empty XMA story response") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "empty response" - return minimalConverted - } else { - log.Trace(). - Str("action_url", att.CTA.ActionUrl). - Str("reel_id", match[2]). - Str("media_id", match[1]). - Any("response", resp). - Msg("Fetched XMA story") - minimalConverted.Extra["com.beeper.instagram_item_username"] = reel.User.Username - // Update external URL to use username so it works on mobile - externalURL = fmt.Sprintf("https://www.instagram.com/stories/%s/%s/", reel.User.Username, match[1]) - minimalConverted.Extra["external_url"] = externalURL - var relevantItem *responses.Items - foundIDs := make([]string, len(reel.Items)) - for i, item := range reel.Items { - foundIDs[i] = item.Pk - if item.Pk == match[1] { - relevantItem = &item.Items - } - } - if relevantItem == nil { - log.Warn(). - Str("action_url", att.CTA.ActionUrl). - Str("reel_id", match[2]). - Str("media_id", match[1]). - Strs("found_ids", foundIDs). - Msg("Failed to find exact item in fetched XMA story") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "item not found in response" - return minimalConverted - } - log.Debug().Msg("Fetched XMA story and found exact item") - secondConverted, err := mc.instagramFetchedMediaToMatrix(ctx, att, relevantItem) - if err != nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer fetched media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "reupload fail" - return minimalConverted - } - secondConverted.Content.Info.ThumbnailInfo = minimalConverted.Content.Info - secondConverted.Content.Info.ThumbnailURL = minimalConverted.Content.URL - secondConverted.Content.Info.ThumbnailFile = minimalConverted.Content.File - secondConverted.Extra["com.beeper.instagram_item_username"] = reel.User.Username - if externalURL != "" { - secondConverted.Extra["external_url"] = externalURL - } - secondConverted.Extra["fi.mau.meta.xma_fetch_status"] = "success" - return secondConverted - } - //case strings.HasPrefix(att.CTA.ActionUrl, "/stories/archive/"): - // TODO can these be handled? - case strings.HasPrefix(att.CTA.ActionUrl, "https://instagram.com/stories/"): - log.Trace().Any("cta_data", att.CTA).Msg("Fetching second type of XMA story from CTA data") - externalURL := att.CTA.ActionUrl - minimalConverted.Extra["external_url"] = externalURL - addExternalURLCaption(minimalConverted.Content, externalURL) - if !mc.ShouldFetchXMA(ctx) { - log.Debug().Msg("Not fetching XMA media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "skip" - return minimalConverted - } +// if len(match) != 3 { +// log.Warn().Str("action_url", att.CTA.ActionUrl).Msg("Failed to parse story action URL") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "parse fail" +// return minimalConverted +// } else if resp, err := ig.FetchReel([]string{match[2]}, match[1]); err != nil { +// log.Err(err).Str("action_url", att.CTA.ActionUrl).Msg("Failed to fetch XMA story") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "fetch fail" +// return minimalConverted +// } else if reel, ok := resp.Reels[match[2]]; !ok { +// log.Trace(). +// Str("action_url", att.CTA.ActionUrl). +// Any("response", resp). +// Msg("XMA story fetch data") +// log.Warn(). +// Str("action_url", att.CTA.ActionUrl). +// Str("reel_id", match[2]). +// Str("media_id", match[1]). +// Str("response_status", resp.Status). +// Msg("Got empty XMA story response") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "empty response" +// return minimalConverted +// } else { +// log.Trace(). +// Str("action_url", att.CTA.ActionUrl). +// Str("reel_id", match[2]). +// Str("media_id", match[1]). +// Any("response", resp). +// Msg("Fetched XMA story") +// minimalConverted.Extra["com.beeper.instagram_item_username"] = reel.User.Username +// // Update external URL to use username so it works on mobile +// externalURL = fmt.Sprintf("https://www.instagram.com/stories/%s/%s/", reel.User.Username, match[1]) +// minimalConverted.Extra["external_url"] = externalURL +// var relevantItem *responses.Items +// foundIDs := make([]string, len(reel.Items)) +// for i, item := range reel.Items { +// foundIDs[i] = item.Pk +// if item.Pk == match[1] { +// relevantItem = &item.Items +// } +// } +// if relevantItem == nil { +// log.Warn(). +// Str("action_url", att.CTA.ActionUrl). +// Str("reel_id", match[2]). +// Str("media_id", match[1]). +// Strs("found_ids", foundIDs). +// Msg("Failed to find exact item in fetched XMA story") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "item not found in response" +// return minimalConverted +// } +// log.Debug().Msg("Fetched XMA story and found exact item") +// secondConverted, err := mc.instagramFetchedMediaToMatrix(ctx, att, relevantItem) +// if err != nil { +// zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer fetched media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "reupload fail" +// return minimalConverted +// } +// secondConverted.Content.Info.ThumbnailInfo = minimalConverted.Content.Info +// secondConverted.Content.Info.ThumbnailURL = minimalConverted.Content.URL +// secondConverted.Content.Info.ThumbnailFile = minimalConverted.Content.File +// secondConverted.Extra["com.beeper.instagram_item_username"] = reel.User.Username +// if externalURL != "" { +// secondConverted.Extra["external_url"] = externalURL +// } +// secondConverted.Extra["fi.mau.meta.xma_fetch_status"] = "success" +// return secondConverted +// } +// //case strings.HasPrefix(att.CTA.ActionUrl, "/stories/archive/"): +// // TODO can these be handled? +// case strings.HasPrefix(att.CTA.ActionUrl, "https://instagram.com/stories/"): +// log.Trace().Any("cta_data", att.CTA).Msg("Fetching second type of XMA story from CTA data") +// externalURL := att.CTA.ActionUrl +// minimalConverted.Extra["external_url"] = externalURL +// addExternalURLCaption(minimalConverted.Content, externalURL) +// if !mc.ShouldFetchXMA(ctx) { +// log.Debug().Msg("Not fetching XMA media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "skip" +// return minimalConverted +// } - if match := reelActionURLRegex2.FindStringSubmatch(att.CTA.ActionUrl); len(match) != 3 { - log.Warn().Str("action_url", att.CTA.ActionUrl).Msg("Failed to parse story action URL (type 2)") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "parse fail" - return minimalConverted - } else if resp, err := ig.FetchMedia(match[2], ""); err != nil { - log.Err(err).Str("action_url", att.CTA.ActionUrl).Msg("Failed to fetch XMA story (type 2)") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "fetch fail" - return minimalConverted - } else if len(resp.Items) == 0 { - log.Trace(). - Str("action_url", att.CTA.ActionUrl). - Any("response", resp). - Msg("XMA story fetch data") - log.Warn(). - Str("action_url", att.CTA.ActionUrl). - Str("reel_id", match[2]). - Str("media_id", match[1]). - Str("response_status", resp.Status). - Msg("Got empty XMA story response (type 2)") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "empty response" - return minimalConverted - } else { - relevantItem := resp.Items[0] - log.Trace(). - Str("action_url", att.CTA.ActionUrl). - Str("reel_id", match[2]). - Str("media_id", match[1]). - Any("response", resp). - Msg("Fetched XMA story (type 2)") - minimalConverted.Extra["com.beeper.instagram_item_username"] = relevantItem.User.Username - log.Debug().Int("item_count", len(resp.Items)).Msg("Fetched XMA story (type 2)") - secondConverted, err := mc.instagramFetchedMediaToMatrix(ctx, att, relevantItem) - if err != nil { - zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer fetched media") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "reupload fail" - return minimalConverted - } - secondConverted.Content.Info.ThumbnailInfo = minimalConverted.Content.Info - secondConverted.Content.Info.ThumbnailURL = minimalConverted.Content.URL - secondConverted.Content.Info.ThumbnailFile = minimalConverted.Content.File - secondConverted.Extra["com.beeper.instagram_item_username"] = relevantItem.User.Username - if externalURL != "" { - secondConverted.Extra["external_url"] = externalURL - } - secondConverted.Extra["fi.mau.meta.xma_fetch_status"] = "success" - return secondConverted - } - default: - log.Debug(). - Any("cta_data", att.CTA). - Any("xma_data", att.LSInsertXmaAttachment). - Msg("Unrecognized CTA data") - minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "unrecognized" - return minimalConverted - } -} +// if match := reelActionURLRegex2.FindStringSubmatch(att.CTA.ActionUrl); len(match) != 3 { +// log.Warn().Str("action_url", att.CTA.ActionUrl).Msg("Failed to parse story action URL (type 2)") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "parse fail" +// return minimalConverted +// } else if resp, err := ig.FetchMedia(match[2], ""); err != nil { +// log.Err(err).Str("action_url", att.CTA.ActionUrl).Msg("Failed to fetch XMA story (type 2)") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "fetch fail" +// return minimalConverted +// } else if len(resp.Items) == 0 { +// log.Trace(). +// Str("action_url", att.CTA.ActionUrl). +// Any("response", resp). +// Msg("XMA story fetch data") +// log.Warn(). +// Str("action_url", att.CTA.ActionUrl). +// Str("reel_id", match[2]). +// Str("media_id", match[1]). +// Str("response_status", resp.Status). +// Msg("Got empty XMA story response (type 2)") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "empty response" +// return minimalConverted +// } else { +// relevantItem := resp.Items[0] +// log.Trace(). +// Str("action_url", att.CTA.ActionUrl). +// Str("reel_id", match[2]). +// Str("media_id", match[1]). +// Any("response", resp). +// Msg("Fetched XMA story (type 2)") +// minimalConverted.Extra["com.beeper.instagram_item_username"] = relevantItem.User.Username +// log.Debug().Int("item_count", len(resp.Items)).Msg("Fetched XMA story (type 2)") +// secondConverted, err := mc.instagramFetchedMediaToMatrix(ctx, att, relevantItem) +// if err != nil { +// zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer fetched media") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "reupload fail" +// return minimalConverted +// } +// secondConverted.Content.Info.ThumbnailInfo = minimalConverted.Content.Info +// secondConverted.Content.Info.ThumbnailURL = minimalConverted.Content.URL +// secondConverted.Content.Info.ThumbnailFile = minimalConverted.Content.File +// secondConverted.Extra["com.beeper.instagram_item_username"] = relevantItem.User.Username +// if externalURL != "" { +// secondConverted.Extra["external_url"] = externalURL +// } +// secondConverted.Extra["fi.mau.meta.xma_fetch_status"] = "success" +// return secondConverted +// } +// default: +// log.Debug(). +// Any("cta_data", att.CTA). +// Any("xma_data", att.LSInsertXmaAttachment). +// Msg("Unrecognized CTA data") +// minimalConverted.Extra["fi.mau.meta.xma_fetch_status"] = "unrecognized" +// return minimalConverted +// } +// } var instagramProfileURLRegex = regexp.MustCompile(`^https://www.instagram.com/([a-z0-9._]{1,30})$`) @@ -677,7 +682,7 @@ func (mc *MessageConverter) xmaAttachmentToMatrix(ctx context.Context, att *tabl zerolog.Ctx(ctx).Err(err).Msg("Failed to transfer XMA media") converted = errorToNotice(err, "XMA") } else { - converted = mc.fetchFullXMA(ctx, att, converted) + //converted = mc.fetchFullXMA(ctx, att, converted) } _, hasExternalURL := converted.Extra["external_url"] if !hasExternalURL && att.CTA != nil && att.CTA.ActionUrl != "" { @@ -710,120 +715,123 @@ func (mc *MessageConverter) xmaAttachmentToMatrix(ctx context.Context, att *tabl return parts } -func (mc *MessageConverter) uploadAttachment(ctx context.Context, data []byte, fileName, mimeType string) (*event.MessageEventContent, error) { - var file *event.EncryptedFileInfo - uploadMime := mimeType - uploadFileName := fileName - if mc.GetData(ctx).Encrypted { - file = &event.EncryptedFileInfo{ - EncryptedFile: *attachment.NewEncryptedFile(), - URL: "", - } - file.EncryptInPlace(data) - uploadMime = "application/octet-stream" - uploadFileName = "" - } - mxc, err := mc.UploadMatrixMedia(ctx, data, uploadFileName, uploadMime) - if err != nil { - return nil, err - } - content := &event.MessageEventContent{ - Body: fileName, - Info: &event.FileInfo{ - MimeType: mimeType, - Size: len(data), - }, - } - if file != nil { - file.URL = mxc - content.File = file - } else { - content.URL = mxc - } - return content, nil -} +// func (mc *MessageConverter) uploadAttachment(ctx context.Context, data []byte, fileName, mimeType string) (*event.MessageEventContent, error) { +// var file *event.EncryptedFileInfo +// uploadMime := mimeType +// uploadFileName := fileName +// if mc.GetData(ctx).Encrypted { +// file = &event.EncryptedFileInfo{ +// EncryptedFile: *attachment.NewEncryptedFile(), +// URL: "", +// } +// file.EncryptInPlace(data) +// uploadMime = "application/octet-stream" +// uploadFileName = "" +// } +// mxc, err := mc.UploadMatrixMedia(ctx, data, uploadFileName, uploadMime) +// if err != nil { +// return nil, err +// } +// content := &event.MessageEventContent{ +// Body: fileName, +// Info: &event.FileInfo{ +// MimeType: mimeType, +// Size: len(data), +// }, +// } +// if file != nil { +// file.URL = mxc +// content.File = file +// } else { +// content.URL = mxc +// } +// return content, nil +// } func (mc *MessageConverter) reuploadAttachment( ctx context.Context, attachmentType table.AttachmentType, url, fileName, mimeType string, width, height, duration int, ) (*bridgev2.ConvertedMessagePart, error) { - if url == "" { - return nil, ErrURLNotFound - } - data, err := DownloadMedia(ctx, mimeType, url, mc.MaxFileSize) - if err != nil { - return nil, fmt.Errorf("failed to download attachment: %w", err) - } - if mimeType == "" { - mimeType = http.DetectContentType(data) - } - extra := map[string]any{} - if attachmentType == table.AttachmentTypeAudio && mc.ConvertVoiceMessages && ffmpeg.Supported() { - data, err = ffmpeg.ConvertBytes(ctx, data, ".ogg", []string{}, []string{"-c:a", "libopus"}, mimeType) - if err != nil { - return nil, fmt.Errorf("failed to convert audio to ogg/opus: %w", err) - } - fileName += ".ogg" - mimeType = "audio/ogg" - extra["org.matrix.msc3245.voice"] = map[string]any{} - extra["org.matrix.msc1767.audio"] = map[string]any{ - "duration": duration, - } - } - if (attachmentType == table.AttachmentTypeImage || attachmentType == table.AttachmentTypeEphemeralImage) && (width == 0 || height == 0) { - config, _, err := image.DecodeConfig(bytes.NewReader(data)) - if err == nil { - width, height = config.Width, config.Height - } - } - content, err := mc.uploadAttachment(ctx, data, fileName, mimeType) - if err != nil { - return nil, err - } - content.Info.Duration = duration - content.Info.Width = width - content.Info.Height = height - - if attachmentType == table.AttachmentTypeAnimatedImage && mimeType == "video/mp4" { - extra["info"] = map[string]any{ - "fi.mau.gif": true, - "fi.mau.loop": true, - "fi.mau.autoplay": true, - "fi.mau.hide_controls": true, - "fi.mau.no_audio": true, - } - } - eventType := event.EventMessage - switch attachmentType { - case table.AttachmentTypeSticker: - eventType = event.EventSticker - case table.AttachmentTypeImage, table.AttachmentTypeEphemeralImage: - content.MsgType = event.MsgImage - case table.AttachmentTypeVideo, table.AttachmentTypeEphemeralVideo: - content.MsgType = event.MsgVideo - case table.AttachmentTypeFile: - content.MsgType = event.MsgFile - case table.AttachmentTypeAudio: - content.MsgType = event.MsgAudio - default: - switch strings.Split(mimeType, "/")[0] { - case "image": - content.MsgType = event.MsgImage - case "video": - content.MsgType = event.MsgVideo - case "audio": - content.MsgType = event.MsgAudio - default: - content.MsgType = event.MsgFile - } - } - if content.Body == "" { - content.Body = strings.TrimPrefix(string(content.MsgType), "m.") + exmime.ExtensionFromMimetype(mimeType) - } - return &bridgev2.ConvertedMessagePart{ - Type: eventType, - Content: content, - Extra: extra, - }, nil + panic("not implemented") } + +// if url == "" { +// return nil, ErrURLNotFound +// } +// data, err := DownloadMedia(ctx, mimeType, url, mc.MaxFileSize) +// if err != nil { +// return nil, fmt.Errorf("failed to download attachment: %w", err) +// } +// if mimeType == "" { +// mimeType = http.DetectContentType(data) +// } +// extra := map[string]any{} +// if attachmentType == table.AttachmentTypeAudio && mc.ConvertVoiceMessages && ffmpeg.Supported() { +// data, err = ffmpeg.ConvertBytes(ctx, data, ".ogg", []string{}, []string{"-c:a", "libopus"}, mimeType) +// if err != nil { +// return nil, fmt.Errorf("failed to convert audio to ogg/opus: %w", err) +// } +// fileName += ".ogg" +// mimeType = "audio/ogg" +// extra["org.matrix.msc3245.voice"] = map[string]any{} +// extra["org.matrix.msc1767.audio"] = map[string]any{ +// "duration": duration, +// } +// } +// if (attachmentType == table.AttachmentTypeImage || attachmentType == table.AttachmentTypeEphemeralImage) && (width == 0 || height == 0) { +// config, _, err := image.DecodeConfig(bytes.NewReader(data)) +// if err == nil { +// width, height = config.Width, config.Height +// } +// } +// //content, err := mc.uploadAttachment(ctx, data, fileName, mimeType) +// if err != nil { +// return nil, err +// } +// //content.Info.Duration = duration +// //content.Info.Width = width +// //content.Info.Height = height + +// if attachmentType == table.AttachmentTypeAnimatedImage && mimeType == "video/mp4" { +// extra["info"] = map[string]any{ +// "fi.mau.gif": true, +// "fi.mau.loop": true, +// "fi.mau.autoplay": true, +// "fi.mau.hide_controls": true, +// "fi.mau.no_audio": true, +// } +// } +// eventType := event.EventMessage +// switch attachmentType { +// case table.AttachmentTypeSticker: +// eventType = event.EventSticker +// case table.AttachmentTypeImage, table.AttachmentTypeEphemeralImage: +// content.MsgType = event.MsgImage +// case table.AttachmentTypeVideo, table.AttachmentTypeEphemeralVideo: +// content.MsgType = event.MsgVideo +// case table.AttachmentTypeFile: +// content.MsgType = event.MsgFile +// case table.AttachmentTypeAudio: +// content.MsgType = event.MsgAudio +// default: +// switch strings.Split(mimeType, "/")[0] { +// case "image": +// content.MsgType = event.MsgImage +// case "video": +// content.MsgType = event.MsgVideo +// case "audio": +// content.MsgType = event.MsgAudio +// default: +// content.MsgType = event.MsgFile +// } +// } +// if content.Body == "" { +// content.Body = strings.TrimPrefix(string(content.MsgType), "m.") + exmime.ExtensionFromMimetype(mimeType) +// } +// return &bridgev2.ConvertedMessagePart{ +// Type: eventType, +// Content: content, +// Extra: extra, +// }, nil +// } diff --git a/pkg/connector/msgconv/mentions.go b/pkg/connector/msgconv/mentions.go index bf1bc93..225c92e 100644 --- a/pkg/connector/msgconv/mentions.go +++ b/pkg/connector/msgconv/mentions.go @@ -18,7 +18,7 @@ package msgconv import ( "context" - "slices" + //"slices" "strings" "unicode/utf16" @@ -68,12 +68,12 @@ func (mc *MessageConverter) metaToMatrixText(ctx context.Context, text string, r } var mentionLink string switch mention.Type { - case socket.MentionTypePerson: - userID := mc.GetUserMXID(ctx, mention.ID) - if !slices.Contains(content.Mentions.UserIDs, userID) { - content.Mentions.UserIDs = append(content.Mentions.UserIDs, userID) - } - mentionLink = userID.URI().MatrixToURL() + // case socket.MentionTypePerson: + // userID := mc.GetUserMXID(ctx, mention.ID) + // if !slices.Contains(content.Mentions.UserIDs, userID) { + // content.Mentions.UserIDs = append(content.Mentions.UserIDs, userID) + // } + // mentionLink = userID.URI().MatrixToURL() case socket.MentionTypeThread: // TODO: how does one send thread mentions? } diff --git a/pkg/connector/msgconv/msgconv.go b/pkg/connector/msgconv/msgconv.go index acaa29e..c645deb 100644 --- a/pkg/connector/msgconv/msgconv.go +++ b/pkg/connector/msgconv/msgconv.go @@ -44,7 +44,7 @@ type PortalMethods interface { } type MessageConverter struct { - PortalMethods + //PortalMethods ConvertVoiceMessages bool ConvertGIFToAPNG bool @@ -53,6 +53,6 @@ type MessageConverter struct { BridgeMode config.BridgeMode } -func (mc *MessageConverter) IsPrivateChat(ctx context.Context) bool { - return mc.GetData(ctx).IsPrivateChat() -} +// func (mc *MessageConverter) IsPrivateChat(ctx context.Context) bool { +// return mc.GetData(ctx).IsPrivateChat() +// } diff --git a/pkg/connector/msgconv/to-whatsapp.go b/pkg/connector/msgconv/to-whatsapp.go index 85aaae1..3859380 100644 --- a/pkg/connector/msgconv/to-whatsapp.go +++ b/pkg/connector/msgconv/to-whatsapp.go @@ -17,21 +17,24 @@ package msgconv import ( - "bytes" + //"bytes" "context" "fmt" - "image" + + //"image" "strconv" "strings" - "time" - "go.mau.fi/util/ffmpeg" + //"time" + + //"go.mau.fi/util/ffmpeg" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/proto/waCommon" "go.mau.fi/whatsmeow/proto/waConsumerApplication" "go.mau.fi/whatsmeow/proto/waMediaTransport" "go.mau.fi/whatsmeow/proto/waMsgApplication" - "go.mau.fi/whatsmeow/types" + + //"go.mau.fi/whatsmeow/types" "google.golang.org/protobuf/proto" "maunium.net/go/mautrix/event" ) @@ -99,15 +102,16 @@ func (mc *MessageConverter) ToWhatsApp( return nil, nil, fmt.Errorf("%w %s", ErrUnsupportedMsgType, content.MsgType) } var meta waMsgApplication.MessageApplication_Metadata - if replyTo := mc.GetMetaReply(ctx, content); replyTo != nil { - meta.QuotedMessage = &waMsgApplication.MessageApplication_Metadata_QuotedMessage{ - StanzaID: proto.String(replyTo.ReplyMessageId), - // TODO: this is hacky since it hardcodes the server - // TODO 2: should this be included for DMs? - Participant: proto.String(types.JID{User: strconv.FormatInt(replyTo.ReplySender, 10), Server: types.MessengerServer}.String()), - Payload: nil, - } - } + //if replyTo := mc.GetMetaReply(ctx, content); replyTo != nil { + // if false { + // meta.QuotedMessage = &waMsgApplication.MessageApplication_Metadata_QuotedMessage{ + // StanzaID: proto.String(replyTo.ReplyMessageId), + // // TODO: this is hacky since it hardcodes the server + // // TODO 2: should this be included for DMs? + // Participant: proto.String(types.JID{User: strconv.FormatInt(replyTo.ReplySender, 10), Server: types.MessengerServer}.String()), + // Payload: nil, + // } + // } return &waConsumerApplication.ConsumerApplication{ Payload: &waConsumerApplication.ConsumerApplication_Payload{ Payload: &waConsumerApplication.ConsumerApplication_Payload_Content{ @@ -149,72 +153,73 @@ func clampTo400(w, h int) (int, int) { } func (mc *MessageConverter) reuploadMediaToWhatsApp(ctx context.Context, evt *event.Event, content *event.MessageEventContent) (*waMediaTransport.WAMediaTransport, string, error) { - data, mimeType, fileName, err := mc.downloadMatrixMedia(ctx, content) - if err != nil { - return nil, "", err - } - _, isVoice := evt.Content.Raw["org.matrix.msc3245.voice"] - if isVoice { - data, err = ffmpeg.ConvertBytes(ctx, data, ".m4a", []string{}, []string{"-c:a", "aac"}, mimeType) - if err != nil { - return nil, "", fmt.Errorf("%w voice message to m4a: %w", ErrMediaConvertFailed, err) - } - mimeType = "audio/mp4" - fileName += ".m4a" - } else if mimeType == "image/gif" && content.MsgType == event.MsgImage { - data, err = ffmpeg.ConvertBytes(ctx, data, ".mp4", []string{"-f", "gif"}, []string{ - "-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart", - "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", - }, mimeType) - if err != nil { - return nil, "", fmt.Errorf("%w gif to mp4: %w", ErrMediaConvertFailed, err) - } - mimeType = "video/mp4" - fileName += ".mp4" - content.MsgType = event.MsgVideo - customInfo, ok := evt.Content.Raw["info"].(map[string]any) - if !ok { - customInfo = make(map[string]any) - evt.Content.Raw["info"] = customInfo - } - customInfo["fi.mau.gif"] = true - } - if content.MsgType == event.MsgImage && content.Info.Width == 0 { - cfg, _, _ := image.DecodeConfig(bytes.NewReader(data)) - content.Info.Width, content.Info.Height = cfg.Width, cfg.Height - } - mediaType := msgToMediaType(content.MsgType) - uploaded, err := mc.GetE2EEClient(ctx).Upload(ctx, data, mediaType) - if err != nil { - return nil, "", fmt.Errorf("%w: %w", ErrMediaUploadFailed, err) - } - w, h := clampTo400(content.Info.Width, content.Info.Height) - if w == 0 && content.MsgType == event.MsgImage { - w, h = 400, 400 - } - mediaTransport := &waMediaTransport.WAMediaTransport{ - Integral: &waMediaTransport.WAMediaTransport_Integral{ - FileSHA256: uploaded.FileSHA256, - MediaKey: uploaded.MediaKey, - FileEncSHA256: uploaded.FileEncSHA256, - DirectPath: &uploaded.DirectPath, - MediaKeyTimestamp: proto.Int64(time.Now().Unix()), - }, - Ancillary: &waMediaTransport.WAMediaTransport_Ancillary{ - FileLength: proto.Uint64(uint64(len(data))), - Mimetype: &mimeType, - // This field is extremely required for some reason. - // Messenger iOS & Android will refuse to display the media if it's not present. - // iOS also requires that width and height are non-empty. - Thumbnail: &waMediaTransport.WAMediaTransport_Ancillary_Thumbnail{ - ThumbnailWidth: proto.Uint32(uint32(w)), - ThumbnailHeight: proto.Uint32(uint32(h)), - }, - ObjectID: &uploaded.ObjectID, - }, - } - fmt.Printf("Uploaded media transport: %+v\n", mediaTransport) - return mediaTransport, fileName, nil + panic("not implemented") + // data, mimeType, fileName, err := mc.downloadMatrixMedia(ctx, content) + // if err != nil { + // return nil, "", err + // } + // _, isVoice := evt.Content.Raw["org.matrix.msc3245.voice"] + // if isVoice { + // data, err = ffmpeg.ConvertBytes(ctx, data, ".m4a", []string{}, []string{"-c:a", "aac"}, mimeType) + // if err != nil { + // return nil, "", fmt.Errorf("%w voice message to m4a: %w", ErrMediaConvertFailed, err) + // } + // mimeType = "audio/mp4" + // fileName += ".m4a" + // } else if mimeType == "image/gif" && content.MsgType == event.MsgImage { + // data, err = ffmpeg.ConvertBytes(ctx, data, ".mp4", []string{"-f", "gif"}, []string{ + // "-pix_fmt", "yuv420p", "-c:v", "libx264", "-movflags", "+faststart", + // "-filter:v", "crop='floor(in_w/2)*2:floor(in_h/2)*2'", + // }, mimeType) + // if err != nil { + // return nil, "", fmt.Errorf("%w gif to mp4: %w", ErrMediaConvertFailed, err) + // } + // mimeType = "video/mp4" + // fileName += ".mp4" + // content.MsgType = event.MsgVideo + // customInfo, ok := evt.Content.Raw["info"].(map[string]any) + // if !ok { + // customInfo = make(map[string]any) + // evt.Content.Raw["info"] = customInfo + // } + // customInfo["fi.mau.gif"] = true + // } + // if content.MsgType == event.MsgImage && content.Info.Width == 0 { + // cfg, _, _ := image.DecodeConfig(bytes.NewReader(data)) + // content.Info.Width, content.Info.Height = cfg.Width, cfg.Height + // } + // mediaType := msgToMediaType(content.MsgType) + // uploaded, err := mc.GetE2EEClient(ctx).Upload(ctx, data, mediaType) + // if err != nil { + // return nil, "", fmt.Errorf("%w: %w", ErrMediaUploadFailed, err) + // } + // w, h := clampTo400(content.Info.Width, content.Info.Height) + // if w == 0 && content.MsgType == event.MsgImage { + // w, h = 400, 400 + // } + // mediaTransport := &waMediaTransport.WAMediaTransport{ + // Integral: &waMediaTransport.WAMediaTransport_Integral{ + // FileSHA256: uploaded.FileSHA256, + // MediaKey: uploaded.MediaKey, + // FileEncSHA256: uploaded.FileEncSHA256, + // DirectPath: &uploaded.DirectPath, + // MediaKeyTimestamp: proto.Int64(time.Now().Unix()), + // }, + // Ancillary: &waMediaTransport.WAMediaTransport_Ancillary{ + // FileLength: proto.Uint64(uint64(len(data))), + // Mimetype: &mimeType, + // // This field is extremely required for some reason. + // // Messenger iOS & Android will refuse to display the media if it's not present. + // // iOS also requires that width and height are non-empty. + // Thumbnail: &waMediaTransport.WAMediaTransport_Ancillary_Thumbnail{ + // ThumbnailWidth: proto.Uint32(uint32(w)), + // ThumbnailHeight: proto.Uint32(uint32(h)), + // }, + // ObjectID: &uploaded.ObjectID, + // }, + // } + // fmt.Printf("Uploaded media transport: %+v\n", mediaTransport) + // return mediaTransport, fileName, nil } func (mc *MessageConverter) wrapWhatsAppMedia(