From 2746f57d100e9341e400a8dcca4239391266ad35 Mon Sep 17 00:00:00 2001 From: Gerardo Rodriguez Date: Wed, 21 Oct 2026 19:29:41 +0000 Subject: [PATCH] stickers: bridge LINE stickers as m.sticker at LINE's display size Stickers were sent as m.image with no dimensions, so clients fell back to their default image size (Desktop renders them 400 px tall). Send them as m.sticker with width/height set to LINE's PC/@1x size (2/3 of the Android asset we download). Advertise sticker support and send Matrix stickers to LINE as images so forwarding a bridged sticker keeps working. --- pkg/connector/handlers/sticker.go | 28 ++++++++++++++++++---------- pkg/connector/send_message.go | 4 +++- pkg/connector/userinfo.go | 12 +++++++++++- 3 files changed, 32 insertions(+), 12 deletions(-) diff --git a/pkg/connector/handlers/sticker.go b/pkg/connector/handlers/sticker.go index 80582011..89ffe4b9 100644 --- a/pkg/connector/handlers/sticker.go +++ b/pkg/connector/handlers/sticker.go @@ -1,10 +1,13 @@ package handlers import ( + "bytes" "context" "encoding/binary" "fmt" "hash/crc32" + "image" + _ "image/png" "io" "strings" @@ -14,7 +17,7 @@ import ( "github.com/beeper/line/pkg/line" ) -// ConvertSticker converts a LINE sticker message to a Matrix image or text fallback. +// ConvertSticker converts a LINE sticker message to a Matrix sticker or text fallback. func (h *Handler) ConvertSticker(ctx context.Context, portal *bridgev2.Portal, intent bridgev2.MatrixAPI, data line.Message, relatesTo *event.RelatesTo) (*bridgev2.ConvertedMessage, error) { stkID := data.ContentMetadata["STKID"] stkTxt := data.ContentMetadata["STKTXT"] @@ -77,6 +80,15 @@ func (h *Handler) ConvertSticker(ctx context.Context, portal *bridgev2.Portal, i bodyName := "sticker." + ext + info := &event.FileInfo{ + MimeType: mimeType, + Size: len(stkData), + } + if cfg, _, err := image.DecodeConfig(bytes.NewReader(stkData)); err == nil { + info.Width = cfg.Width * 2 / 3 + info.Height = cfg.Height * 2 / 3 + } + mxc, file, err := intent.UploadMedia(ctx, portal.MXID, stkData, bodyName, mimeType) if err != nil { h.Log.Warn().Err(err).Msg("Failed to upload sticker to Matrix") @@ -84,16 +96,12 @@ func (h *Handler) ConvertSticker(ctx context.Context, portal *bridgev2.Portal, i return &bridgev2.ConvertedMessage{ Parts: []*bridgev2.ConvertedMessagePart{ { - Type: event.EventMessage, + Type: event.EventSticker, Content: &event.MessageEventContent{ - MsgType: event.MsgImage, - Body: bodyName, - URL: mxc, - File: file, - Info: &event.FileInfo{ - MimeType: mimeType, - Size: len(stkData), - }, + Body: bodyName, + URL: mxc, + File: file, + Info: info, RelatesTo: relatesTo, }, }, diff --git a/pkg/connector/send_message.go b/pkg/connector/send_message.go index 9632ad22..5fffa717 100644 --- a/pkg/connector/send_message.go +++ b/pkg/connector/send_message.go @@ -131,7 +131,9 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat // 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 { + if effectiveMsgType == event.CapMsgSticker { + effectiveMsgType = event.MsgImage + } else if effectiveMsgType == event.MsgFile && msg.Content.Info != nil { mime := msg.Content.Info.MimeType if strings.HasPrefix(mime, "audio/") { effectiveMsgType = event.MsgAudio diff --git a/pkg/connector/userinfo.go b/pkg/connector/userinfo.go index ecda636e..15d8fdbf 100644 --- a/pkg/connector/userinfo.go +++ b/pkg/connector/userinfo.go @@ -46,7 +46,7 @@ func (lc *LineClient) HandleMatrixReadReceipt(ctx context.Context, read *bridgev func (lc *LineClient) GetCapabilities(ctx context.Context, portal *bridgev2.Portal) *event.RoomFeatures { caps := &event.RoomFeatures{ - ID: "dev.highest.matrix.line.capabilities.2026_10_14", + ID: "dev.highest.matrix.line.capabilities.2026_10_21", MaxTextLength: 5000, Reply: event.CapLevelFullySupported, Reaction: event.CapLevelPartialSupport, @@ -81,6 +81,16 @@ func (lc *LineClient) GetCapabilities(ctx context.Context, portal *bridgev2.Port "image/jxl": event.CapLevelFullySupported, }, }, + event.CapMsgSticker: { + Caption: event.CapLevelRejected, + MaxSize: handlers.BeeperMaxFileSize, + MimeTypes: map[string]event.CapabilitySupportLevel{ + "image/png": event.CapLevelFullySupported, + "image/gif": event.CapLevelFullySupported, + "image/webp": event.CapLevelFullySupported, + "image/jpeg": event.CapLevelFullySupported, + }, + }, event.MsgFile: { Caption: event.CapLevelRejected, MaxSize: handlers.BeeperMaxFileSize,