Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 15 additions & 0 deletions pkg/connector/capabilities_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ func TestCapabilitiesAdvertiseFileSizeLimit(t *testing.T) {
event.MsgVideo,
event.MsgAudio,
event.CapMsgVoice,
event.CapMsgGIF,
} {
features := caps.File[messageType]
if features == nil {
Expand All @@ -26,3 +27,17 @@ func TestCapabilitiesAdvertiseFileSizeLimit(t *testing.T) {
}
}
}

func TestCapabilitiesAdvertiseRawGIFSupport(t *testing.T) {
caps := (&LineClient{}).GetCapabilities(context.Background(), nil)
features := caps.File[event.CapMsgGIF]
if features == nil {
t.Fatal("GIF file features are missing")
}
if got := features.MimeTypes["image/gif"]; got != event.CapLevelFullySupported {
t.Fatalf("image/gif support = %v, want %v", got, event.CapLevelFullySupported)
}
if len(features.MimeTypes) != 1 {
t.Fatalf("GIF MIME type count = %d, want 1", len(features.MimeTypes))
}
}
38 changes: 24 additions & 14 deletions pkg/connector/send_message.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,26 @@ func lineGroupE2EEFetchFailureError(err error) error {
return err
}

func effectiveLineMessageType(content *event.MessageEventContent) event.MessageType {
effectiveMsgType := content.MsgType
// Beeper Desktop represents GIFs as m.video with fi.mau.gif. Raw GIF data can
// use LINE's existing image path without media conversion.
if content.GetCapMsgType() == event.CapMsgGIF && content.Info != nil && content.Info.MimeType == "image/gif" {
return event.MsgImage
}
if effectiveMsgType == event.MsgFile && content.Info != nil {
mime := content.Info.MimeType
if strings.HasPrefix(mime, "audio/") {
return event.MsgAudio
} else if strings.HasPrefix(mime, "video/") {
return event.MsgVideo
} else if strings.HasPrefix(mime, "image/") {
return event.MsgImage
}
}
return effectiveMsgType
}

func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.MatrixMessage) (*bridgev2.MatrixMessageResponse, error) {
client := lc.newClient()
callLineErr := func(call func(*line.Client) error) error {
Expand Down Expand Up @@ -119,20 +139,10 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat
contentMetadata["e2eeVersion"] = "2"
}

// Detect media files sent as MsgFile and handle them with the correct LINE content type.
// Matrix clients often send media as MsgFile (e.g. drag-and-drop), which would otherwise
// be sent as ContentFile (14) instead of the appropriate media type on LINE.
effectiveMsgType := msg.Content.MsgType
if effectiveMsgType == event.MsgFile && msg.Content.Info != nil {
mime := msg.Content.Info.MimeType
if strings.HasPrefix(mime, "audio/") {
effectiveMsgType = event.MsgAudio
} else if strings.HasPrefix(mime, "video/") {
effectiveMsgType = event.MsgVideo
} else if strings.HasPrefix(mime, "image/") {
effectiveMsgType = event.MsgImage
}
}
// Normalize special GIF events and media files sent as MsgFile to the correct LINE content
// type. Matrix clients often use MsgFile for drag-and-drop media, which would otherwise be
// sent as ContentFile (14).
effectiveMsgType := effectiveLineMessageType(msg.Content)

// For non-text messages, check with the server whether to use E2EE or plain media upload.
// This must happen before media processing since it affects the upload path.
Expand Down
55 changes: 55 additions & 0 deletions pkg/connector/send_message_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,61 @@ type mentionTestMatrix struct {
ghosts map[id.UserID]networkid.UserID
}

func TestEffectiveLineMessageType(t *testing.T) {
tests := []struct {
name string
content *event.MessageEventContent
want event.MessageType
}{
{
name: "desktop raw GIF",
content: &event.MessageEventContent{
MsgType: event.MsgVideo,
Info: &event.FileInfo{
MimeType: "image/gif",
MauGIF: true,
},
},
want: event.MsgImage,
},
{
name: "video-backed GIF remains video",
content: &event.MessageEventContent{
MsgType: event.MsgVideo,
Info: &event.FileInfo{
MimeType: "video/mp4",
MauGIF: true,
},
},
want: event.MsgVideo,
},
{
name: "GIF file upload",
content: &event.MessageEventContent{
MsgType: event.MsgFile,
Info: &event.FileInfo{MimeType: "image/gif"},
},
want: event.MsgImage,
},
{
name: "ordinary video",
content: &event.MessageEventContent{
MsgType: event.MsgVideo,
Info: &event.FileInfo{MimeType: "video/mp4"},
},
want: event.MsgVideo,
},
}

for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if got := effectiveLineMessageType(test.content); got != test.want {
t.Fatalf("effectiveLineMessageType() = %s, want %s", got, test.want)
}
})
}
}

func (matrix *mentionTestMatrix) ParseGhostMXID(userID id.UserID) (networkid.UserID, bool) {
ghostID, ok := matrix.ghosts[userID]
return ghostID, ok
Expand Down
7 changes: 7 additions & 0 deletions pkg/connector/userinfo.go
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,13 @@ func (lc *LineClient) GetCapabilities(ctx context.Context, portal *bridgev2.Port
"audio/3gpp": event.CapLevelFullySupported,
},
},
event.CapMsgGIF: {
Caption: event.CapLevelRejected,
MaxSize: handlers.BeeperMaxFileSize,
MimeTypes: map[string]event.CapabilitySupportLevel{
"image/gif": event.CapLevelFullySupported,
},
},
},
}
}
Expand Down
Loading