Skip to content

Commit 31c3995

Browse files
authored
Merge pull request #217 from beeper/highest/plat-37946
fix: reaction, image, join with letter sealing off groups/dms
2 parents 8adc093 + 6bc5ca2 commit 31c3995

12 files changed

Lines changed: 704 additions & 134 deletions

File tree

pkg/connector/handlers/audio.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -45,11 +45,12 @@ func (h *Handler) ConvertAudio(ctx context.Context, portal *bridgev2.Portal, int
4545
sid = "m"
4646
}
4747
downloadOptions := lineOBSDownloadOptions(data.ContentMetadata, isPlainMedia)
48-
audioData, err := client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, sid, downloadOptions)
48+
talkMetaMessageID := obsTalkMetaMessageID(data.ID, isPlainMedia)
49+
audioData, err := client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, sid, downloadOptions)
4950

5051
if newClient, ok := h.tryRecoverClient(ctx, err); ok {
5152
client = newClient
52-
audioData, err = client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, sid, downloadOptions)
53+
audioData, err = client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, sid, downloadOptions)
5354
}
5455

5556
if err != nil {
@@ -58,19 +59,8 @@ func (h *Handler) ConvertAudio(ctx context.Context, portal *bridgev2.Portal, int
5859
Str("oid", oid).
5960
Str("msg_id", data.ID).
6061
Bool("plain_media", isPlainMedia).
61-
Msg("Failed to download audio from OBS, sending placeholder")
62-
return &bridgev2.ConvertedMessage{
63-
Parts: []*bridgev2.ConvertedMessagePart{
64-
{
65-
Type: event.EventMessage,
66-
Content: &event.MessageEventContent{
67-
MsgType: event.MsgNotice,
68-
Body: "[Audio unavailable — LINE media expired before it could be bridged]",
69-
RelatesTo: relatesTo,
70-
},
71-
},
72-
},
73-
}, nil
62+
Msg("Failed to download audio from OBS")
63+
return mediaDownloadFailure("Audio", err, relatesTo)
7464
}
7565

7666
// Decrypt audio if it has keyMaterial (E2EE)

pkg/connector/handlers/file.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -36,31 +36,21 @@ func (h *Handler) ConvertFile(ctx context.Context, portal *bridgev2.Portal, inte
3636
sid = "m"
3737
}
3838
downloadOptions := lineOBSDownloadOptions(data.ContentMetadata, isPlainMedia)
39-
fileData, err := client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, sid, downloadOptions)
39+
talkMetaMessageID := obsTalkMetaMessageID(data.ID, isPlainMedia)
40+
fileData, err := client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, sid, downloadOptions)
4041

4142
if newClient, ok := h.tryRecoverClient(ctx, err); ok {
4243
client = newClient
43-
fileData, err = client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, sid, downloadOptions)
44+
fileData, err = client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, sid, downloadOptions)
4445
}
4546

4647
if err != nil {
4748
h.Log.Warn().
4849
Err(err).
4950
Str("oid", oid).
5051
Bool("plain_media", isPlainMedia).
51-
Msg("Failed to download file from OBS, sending placeholder")
52-
return &bridgev2.ConvertedMessage{
53-
Parts: []*bridgev2.ConvertedMessagePart{
54-
{
55-
Type: event.EventMessage,
56-
Content: &event.MessageEventContent{
57-
MsgType: event.MsgNotice,
58-
Body: "[File unavailable — LINE media expired before it could be bridged]",
59-
RelatesTo: relatesTo,
60-
},
61-
},
62-
},
63-
}, nil
52+
Msg("Failed to download file from OBS")
53+
return mediaDownloadFailure("File", err, relatesTo)
6454
}
6555

6656
// Try to decrypt using keyMaterial from encrypted payload

pkg/connector/handlers/handler.go

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,14 @@ package handlers
22

33
import (
44
"context"
5+
"errors"
6+
"fmt"
57
"net/http"
8+
"strings"
69

710
"github.com/rs/zerolog"
11+
"maunium.net/go/mautrix/bridgev2"
12+
"maunium.net/go/mautrix/event"
813

914
"github.com/highesttt/matrix-line-messenger/pkg/line"
1015
)
@@ -28,6 +33,35 @@ type Handler struct {
2833
DecryptMedia func(data []byte, keyMaterial string) ([]byte, error)
2934
}
3035

36+
func obsTalkMetaMessageID(messageID string, isPlainMedia bool) string {
37+
if isPlainMedia {
38+
return ""
39+
}
40+
return messageID
41+
}
42+
43+
func mediaDownloadFailure(kind string, err error, relatesTo *event.RelatesTo) (*bridgev2.ConvertedMessage, error) {
44+
if !errors.Is(err, line.ErrOBSObjectNotFound) {
45+
// Keep ambiguous OBS failures retryable. Returning ErrIgnoringRemoteEvent
46+
// prevents bridgev2 from posting a generic error notice, while omitting a
47+
// converted message means the remote event isn't stored as successfully
48+
// bridged and can be retried by a later backfill.
49+
return nil, fmt.Errorf("%w: failed to download %s from LINE OBS: %w", bridgev2.ErrIgnoringRemoteEvent, strings.ToLower(kind), err)
50+
}
51+
return &bridgev2.ConvertedMessage{
52+
Parts: []*bridgev2.ConvertedMessagePart{
53+
{
54+
Type: event.EventMessage,
55+
Content: &event.MessageEventContent{
56+
MsgType: event.MsgNotice,
57+
Body: fmt.Sprintf("[%s unavailable — LINE media expired before it could be bridged]", kind),
58+
RelatesTo: relatesTo,
59+
},
60+
},
61+
},
62+
}, nil
63+
}
64+
3165
// tryRecoverClient attempts token recovery on auth errors and returns a fresh client.
3266
// Returns (newClient, true) on success, (nil, false) if recovery was not needed or failed.
3367
func (h *Handler) tryRecoverClient(ctx context.Context, err error) (*line.Client, bool) {

pkg/connector/handlers/handler_test.go

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,11 @@ import (
44
"context"
55
"errors"
66
"testing"
7+
8+
"maunium.net/go/mautrix/bridgev2"
9+
"maunium.net/go/mautrix/event"
10+
11+
"github.com/highesttt/matrix-line-messenger/pkg/line"
712
)
813

914
func TestTryRecoverClientUsesShouldRecover(t *testing.T) {
@@ -34,3 +39,64 @@ func TestTryRecoverClientUsesShouldRecover(t *testing.T) {
3439
t.Fatal("RecoverToken was called despite ShouldRecover returning false")
3540
}
3641
}
42+
43+
func TestTryRecoverClientRecoversOBSObjectInfoUnauthorized(t *testing.T) {
44+
recoveredClient := line.NewClient("refreshed-token")
45+
var recoverCalled bool
46+
h := &Handler{
47+
ShouldRecover: func(_ context.Context, err error) bool {
48+
return line.IsUnauthorizedStatus(err)
49+
},
50+
IsLoggedOut: func(error) bool {
51+
return false
52+
},
53+
RecoverToken: func(context.Context) error {
54+
recoverCalled = true
55+
return nil
56+
},
57+
NewClient: func() *line.Client {
58+
return recoveredClient
59+
},
60+
}
61+
62+
client, ok := h.tryRecoverClient(context.Background(), errors.New("OBS object info failed (401): unauthorized"))
63+
if !ok || client != recoveredClient {
64+
t.Fatalf("tryRecoverClient returned client=%v ok=%v, want refreshed client", client, ok)
65+
}
66+
if !recoverCalled {
67+
t.Fatal("RecoverToken was not called for OBS object-info 401")
68+
}
69+
}
70+
71+
func TestMediaDownloadFailureOnlyMaterializesKnownExpiry(t *testing.T) {
72+
converted, err := mediaDownloadFailure("Image", line.ErrOBSObjectNotFound, nil)
73+
if err != nil {
74+
t.Fatal(err)
75+
}
76+
if converted == nil || len(converted.Parts) != 1 {
77+
t.Fatalf("converted = %#v, want one placeholder part", converted)
78+
}
79+
if converted.Parts[0].Content.MsgType != event.MsgNotice || converted.Parts[0].Content.Body != "[Image unavailable — LINE media expired before it could be bridged]" {
80+
t.Fatalf("placeholder content = %#v", converted.Parts[0].Content)
81+
}
82+
83+
converted, err = mediaDownloadFailure("Image", line.ErrOBSEncodingIncomplete, nil)
84+
if converted != nil {
85+
t.Fatalf("converted transient failure = %#v, want nil", converted)
86+
}
87+
if !errors.Is(err, line.ErrOBSEncodingIncomplete) {
88+
t.Fatalf("err = %v, want ErrOBSEncodingIncomplete", err)
89+
}
90+
if !errors.Is(err, bridgev2.ErrIgnoringRemoteEvent) {
91+
t.Fatalf("err = %v, want ErrIgnoringRemoteEvent", err)
92+
}
93+
}
94+
95+
func TestOBSTalkMetaMessageID(t *testing.T) {
96+
if got := obsTalkMetaMessageID("message-id", true); got != "" {
97+
t.Fatalf("plain media talk-meta ID = %q, want empty", got)
98+
}
99+
if got := obsTalkMetaMessageID("message-id", false); got != "message-id" {
100+
t.Fatalf("encrypted media talk-meta ID = %q", got)
101+
}
102+
}

pkg/connector/handlers/image.go

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ func (h *Handler) ConvertImage(ctx context.Context, portal *bridgev2.Portal, int
3030

3131
mediaCategory := lineMediaCategory(data.ContentMetadata)
3232
downloadOptions := lineOBSDownloadOptions(data.ContentMetadata, isPlainMedia)
33+
talkMetaMessageID := obsTalkMetaMessageID(data.ID, isPlainMedia)
3334

3435
var imgData []byte
3536
var err error
@@ -43,18 +44,18 @@ func (h *Handler) ConvertImage(ctx context.Context, portal *bridgev2.Portal, int
4344
Bool("plain_media", isPlainMedia).
4445
Msg("Downloading image from LINE OBS")
4546
if isPlainMedia {
46-
imgData, err = client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, "m", downloadOptions)
47+
imgData, err = client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, "m", downloadOptions)
4748
} else {
48-
imgData, err = client.DownloadOBSWithOptions(ctx, oid, data.ID, downloadOptions)
49+
imgData, err = client.DownloadOBSWithOptions(ctx, oid, talkMetaMessageID, downloadOptions)
4950
}
5051

5152
// Refresh token if we get a 401
5253
if newClient, ok := h.tryRecoverClient(ctx, err); ok {
5354
client = newClient
5455
if isPlainMedia {
55-
imgData, err = client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, "m", downloadOptions)
56+
imgData, err = client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, "m", downloadOptions)
5657
} else {
57-
imgData, err = client.DownloadOBSWithOptions(ctx, oid, data.ID, downloadOptions)
58+
imgData, err = client.DownloadOBSWithOptions(ctx, oid, talkMetaMessageID, downloadOptions)
5859
}
5960
}
6061
downloadDuration := time.Since(dlStart)
@@ -66,19 +67,8 @@ func (h *Handler) ConvertImage(ctx context.Context, portal *bridgev2.Portal, int
6667
Str("msg_id", data.ID).
6768
Bool("plain_media", isPlainMedia).
6869
Dur("download_duration", downloadDuration).
69-
Msg("Failed to download image from OBS, sending placeholder")
70-
return &bridgev2.ConvertedMessage{
71-
Parts: []*bridgev2.ConvertedMessagePart{
72-
{
73-
Type: event.EventMessage,
74-
Content: &event.MessageEventContent{
75-
MsgType: event.MsgNotice,
76-
Body: "[Image unavailable — LINE media expired before it could be bridged]",
77-
RelatesTo: relatesTo,
78-
},
79-
},
80-
},
81-
}, nil
70+
Msg("Failed to download image from OBS")
71+
return mediaDownloadFailure("Image", err, relatesTo)
8272
}
8373

8474
// Decrypt image if it has keyMaterial (E2EE)

pkg/connector/handlers/video.go

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -46,12 +46,13 @@ func (h *Handler) ConvertVideo(ctx context.Context, portal *bridgev2.Portal, int
4646
sid = "m"
4747
}
4848
downloadOptions := lineOBSDownloadOptions(data.ContentMetadata, isPlainMedia)
49+
talkMetaMessageID := obsTalkMetaMessageID(data.ID, isPlainMedia)
4950
dlStart := time.Now()
50-
videoData, err := client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, sid, downloadOptions)
51+
videoData, err := client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, sid, downloadOptions)
5152

5253
if newClient, ok := h.tryRecoverClient(ctx, err); ok {
5354
client = newClient
54-
videoData, err = client.DownloadOBSWithSIDOptions(ctx, oid, data.ID, sid, downloadOptions)
55+
videoData, err = client.DownloadOBSWithSIDOptions(ctx, oid, talkMetaMessageID, sid, downloadOptions)
5556
}
5657

5758
if err != nil {
@@ -61,19 +62,8 @@ func (h *Handler) ConvertVideo(ctx context.Context, portal *bridgev2.Portal, int
6162
Str("msg_id", data.ID).
6263
Bool("plain_media", isPlainMedia).
6364
Dur("download_duration", time.Since(dlStart)).
64-
Msg("Failed to download video from OBS, sending placeholder")
65-
return &bridgev2.ConvertedMessage{
66-
Parts: []*bridgev2.ConvertedMessagePart{
67-
{
68-
Type: event.EventMessage,
69-
Content: &event.MessageEventContent{
70-
MsgType: event.MsgNotice,
71-
Body: "[Video unavailable — LINE media expired before it could be bridged]",
72-
RelatesTo: relatesTo,
73-
},
74-
},
75-
},
76-
}, nil
65+
Msg("Failed to download video from OBS")
66+
return mediaDownloadFailure("Video", err, relatesTo)
7767
}
7868

7969
decrypted := false

0 commit comments

Comments
 (0)