diff --git a/pkg/connector/send_message.go b/pkg/connector/send_message.go index 0b87701..b663602 100644 --- a/pkg/connector/send_message.go +++ b/pkg/connector/send_message.go @@ -661,6 +661,20 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat contentMetadata[k] = v } + fallBackToPlainText := func() { + plainText = true + delete(contentMetadata, "e2eeVersion") + if contentType == int(ContentText) { + plainTextBody = textBody + } else { + delete(contentMetadata, "OID") + delete(contentMetadata, "SID") + delete(contentMetadata, "ENC_KM") + plainMediaData = originalMediaData + plainThumbData = originalThumbData + } + } + // Encryption phase — skip entirely for plain text if !plainText { if isGroup { @@ -693,19 +707,9 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat lc.markGroupNoE2EE(portalMid) lc.UserLogin.Bridge.Log.Warn().Err(err).Str("chat_mid", portalMid). Msg("Failed to set up E2EE for group, falling back to plain text") - plainText = true + fallBackToPlainText() chunks = nil err = nil - delete(contentMetadata, "e2eeVersion") - if contentType == int(ContentText) { - plainTextBody = textBody - } else { - delete(contentMetadata, "OID") - delete(contentMetadata, "SID") - delete(contentMetadata, "ENC_KM") - plainMediaData = originalMediaData - plainThumbData = originalThumbData - } } } } else { @@ -731,6 +735,7 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat lc.UserLogin.Bridge.Log.Info().Str("portal", portalMid).Int("content_type", contentType).Msg("Sending plain text message (no E2EE)") } + sticonReplace := contentMetadata["REPLACE"] if !plainText { delete(contentMetadata, "REPLACE") } @@ -793,6 +798,7 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat contentMetadata["FILE_NAME"] = zipFileName contentMetadata["FILE_SIZE"] = fmt.Sprintf("%d", len(zipData)) lineMsg.ContentMetadata = contentMetadata + originalMediaData = zipData if plainText { plainMediaData = zipData @@ -881,6 +887,21 @@ func (lc *LineClient) HandleMatrixMessage(ctx context.Context, msg *bridgev2.Mat } } + if err != nil && isGroup && !plainText && line.IsMemberSettingsOff(err) { + lc.markGroupNoE2EE(portalMid) + lc.UserLogin.Bridge.Log.Warn().Err(err).Str("chat_mid", portalMid). + Msg("LINE rejected encrypted group message because a member has Letter Sealing off, resending as plain text") + fallBackToPlainText() + lineMsg.Chunks = nil + lineMsg.Text = plainTextBody + if sticonReplace != "" { + contentMetadata["REPLACE"] = sticonReplace + } + retryReqSeq := int(time.Now().UnixMilli() % 1_000_000_000) + lc.trackReqSeq(retryReqSeq) + sentMsg, err = sendLineMessage(retryReqSeq, lineMsg) + } + if shouldRetrySendWithoutReplyRelation(lineMsg, err) { relatedMessageID := lineMsg.RelatedMessageID clearReplyRelation(lineMsg) diff --git a/pkg/line/errors.go b/pkg/line/errors.go index fcc46d4..eeacfa1 100644 --- a/pkg/line/errors.go +++ b/pkg/line/errors.go @@ -364,6 +364,11 @@ func IsE2EEGroupMemberMismatch(err error) bool { return ok && talk.Code == 99 && strings.EqualFold(strings.TrimSpace(talk.Reason), "member count mismatch") } +func IsMemberSettingsOff(err error) bool { + talk, ok := parseTalkException(err) + return ok && talk.Code == 98 && strings.Contains(strings.ToLower(talk.Reason), "member settings off") +} + // IsTalkExceptionRateLimited returns true when LINE rejected a Talk API call // for excessive access. func IsTalkExceptionRateLimited(err error) bool {