Skip to content

Commit 624b6c1

Browse files
authored
Merge pull request #218 from beeper/highest/plat-37984
fix: preserve multibyte text around LINE sticons
2 parents 31c3995 + a430861 commit 624b6c1

4 files changed

Lines changed: 470 additions & 79 deletions

File tree

pkg/connector/handle_message.go

Lines changed: 29 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -402,32 +402,38 @@ func (lc *LineClient) convertLineMessage(ctx context.Context, portal *bridgev2.P
402402
return h.ConvertDeviceContact(ctx, portal, intent, data, unwrappedText, replyRelatesTo)
403403
}
404404

405+
var converted *bridgev2.ConvertedMessage
406+
var err error
407+
405408
// Handle inline emoji/stamp embedded in text messages
406409
if data.ContentMetadata["STKID"] != "" || data.ContentMetadata["STKPKGID"] != "" ||
407-
data.ContentMetadata["STICON_OWNERSHIP"] != "" {
410+
data.ContentMetadata["STICON_OWNERSHIP"] != "" ||
411+
handlers.HasSticonBody(bodyText) ||
412+
handlers.ContainsLineSticonPlaceholder(unwrappedText) {
408413
if data.ContentMetadata["STICON_OWNERSHIP"] != "" {
409414
h.Log.Debug().
410-
Str("body_text", bodyText).
411-
Str("unwrapped_text", unwrappedText).
412-
Interface("content_metadata", data.ContentMetadata).
415+
Int("body_length", len(bodyText)).
416+
Int("unwrapped_length", len(unwrappedText)).
417+
Int("metadata_count", len(data.ContentMetadata)).
413418
Msg("STICON_OWNERSHIP: full message body")
414419
}
415-
return h.ConvertInlineEmoji(ctx, portal, intent, data, unwrappedText, bodyText, replyRelatesTo)
416-
}
420+
converted, err = h.ConvertInlineEmoji(ctx, portal, intent, data, unwrappedText, bodyText, replyRelatesTo)
421+
} else {
422+
// Skip empty/whitespace-only text messages (system messages that fell through)
423+
if strings.TrimSpace(unwrappedText) == "" {
424+
return nil, nil
425+
}
417426

418-
// Skip empty/whitespace-only text messages (system messages that fell through)
419-
if strings.TrimSpace(unwrappedText) == "" {
420-
return nil, nil
427+
// Default to text
428+
converted, err = h.ConvertText(unwrappedText, replyRelatesTo)
421429
}
422-
423-
// Default to text
424-
converted, err := h.ConvertText(unwrappedText, replyRelatesTo)
425430
if err != nil {
426431
return nil, err
427432
}
428433

429-
if mentionStr := data.ContentMetadata["MENTION"]; mentionStr != "" && len(converted.Parts) > 0 {
434+
if mentionStr := data.ContentMetadata["MENTION"]; mentionStr != "" && converted != nil && len(converted.Parts) > 0 && converted.Parts[0].Content != nil {
430435
lc.UserLogin.Bridge.Log.Debug().Str("raw_mention", mentionStr).Msg("Processing inbound LINE MENTION metadata")
436+
canFormatMentions := converted.Parts[0].Content.Body == unwrappedText && converted.Parts[0].Content.FormattedBody == ""
431437
var mentionData struct {
432438
MENTIONEES []struct {
433439
M string `json:"M,omitempty"`
@@ -472,17 +478,21 @@ func (lc *LineClient) convertLineMessage(ctx context.Context, portal *bridgev2.P
472478
}
473479
lc.UserLogin.Bridge.Log.Debug().Str("mxid", string(mxid)).Msg("Formatted MXID from LINE MID")
474480
mentions.UserIDs = append(mentions.UserIDs, mxid)
475-
if s, errS := strconv.Atoi(ment.S); errS == nil && s >= 0 {
476-
if e, errE := strconv.Atoi(ment.E); errE == nil && e <= len(unwrappedText) && e > s {
477-
entries = append(entries, mentionEntry{start: s, end: e, mxid: string(mxid)})
481+
if canFormatMentions {
482+
if s, errS := strconv.Atoi(ment.S); errS == nil && s >= 0 {
483+
if e, errE := strconv.Atoi(ment.E); errE == nil && e <= len(unwrappedText) && e > s {
484+
entries = append(entries, mentionEntry{start: s, end: e, mxid: string(mxid)})
485+
}
478486
}
479487
}
480488
}
481489
if ment.A == "1" {
482490
mentions.Room = true
483-
if s, errS := strconv.Atoi(ment.S); errS == nil && s >= 0 {
484-
if e, errE := strconv.Atoi(ment.E); errE == nil && e <= len(unwrappedText) && e > s {
485-
entries = append(entries, mentionEntry{start: s, end: e, mxid: "@room"})
491+
if canFormatMentions {
492+
if s, errS := strconv.Atoi(ment.S); errS == nil && s >= 0 {
493+
if e, errE := strconv.Atoi(ment.E); errE == nil && e <= len(unwrappedText) && e > s {
494+
entries = append(entries, mentionEntry{start: s, end: e, mxid: "@room"})
495+
}
486496
}
487497
}
488498
}

pkg/connector/handle_message_test.go

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,56 @@
11
package connector
22

33
import (
4+
"io"
5+
"strings"
46
"testing"
57

8+
"github.com/rs/zerolog"
9+
"maunium.net/go/mautrix/bridgev2"
10+
"maunium.net/go/mautrix/bridgev2/database"
611
"maunium.net/go/mautrix/event"
12+
"maunium.net/go/mautrix/id"
713

814
"github.com/highesttt/matrix-line-messenger/pkg/line"
915
)
1016

17+
func TestConvertLineMessagePreservesMentionsForSticonFallback(t *testing.T) {
18+
const placeholder = "\U00100084"
19+
text := "hello " + placeholder
20+
userMXID := id.UserID("@user:example.com")
21+
lc := &LineClient{
22+
Mid: "self-mid",
23+
UserLogin: &bridgev2.UserLogin{
24+
UserLogin: &database.UserLogin{UserMXID: userMXID},
25+
Bridge: &bridgev2.Bridge{Log: zerolog.New(io.Discard)},
26+
},
27+
}
28+
data := line.Message{
29+
ContentType: int(ContentText),
30+
ContentMetadata: map[string]string{
31+
"MENTION": `{"MENTIONEES":[{"M":"self-mid","S":"0","E":"5"}]}`,
32+
},
33+
}
34+
35+
converted, err := lc.convertLineMessage(t.Context(), nil, nil, data, text, text, false)
36+
if err != nil {
37+
t.Fatalf("convertLineMessage returned error: %v", err)
38+
}
39+
if converted == nil || len(converted.Parts) != 1 || converted.Parts[0].Content == nil {
40+
t.Fatalf("convertLineMessage returned %#v, want one message part", converted)
41+
}
42+
content := converted.Parts[0].Content
43+
if content.Body != "hello [Emoji]" {
44+
t.Fatalf("Body = %q, want cleaned sticon fallback", content.Body)
45+
}
46+
if content.Mentions == nil || len(content.Mentions.UserIDs) != 1 || content.Mentions.UserIDs[0] != userMXID {
47+
t.Fatalf("Mentions = %#v, want user %s", content.Mentions, userMXID)
48+
}
49+
if strings.Contains(content.FormattedBody, placeholder) {
50+
t.Fatalf("FormattedBody still contains LINE placeholder: %q", content.FormattedBody)
51+
}
52+
}
53+
1154
func TestDecryptMessageBodySkipsGeneratedFallbackWhenDecryptUnavailable(t *testing.T) {
1255
msg := &line.Message{
1356
Text: "",

0 commit comments

Comments
 (0)