Watch
1
0
Fork
You've already forked mautrix-signal
0
mirror of https://github.com/mautrix/signal.git synced 2026-11-11 00:29:16 -05:00
mautrix-signal/pkg/msgconv/stories.go

123 lines
3.8 KiB
Go

// mautrix-signal - A Matrix-Signal puppeting bridge.
// Copyright (C) 2026 Tulir Asokan
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.
package msgconv
import (
"context"
"fmt"
"time"
"unicode/utf16"
"github.com/google/uuid"
"github.com/rs/zerolog"
"google.golang.org/protobuf/proto"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/bridgev2/database"
"maunium.net/go/mautrix/bridgev2/networkid"
"maunium.net/go/mautrix/event"
"go.mau.fi/mautrix-signal/pkg/signalid"
"go.mau.fi/mautrix-signal/pkg/signalmeow"
"go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf/signalpb"
)
const StoryLifetime = 24 * time.Hour
func (mc *MessageConverter) StoryToMatrix(
ctx context.Context,
client *signalmeow.Client,
portal *bridgev2.Portal,
sender uuid.UUID,
intent bridgev2.MatrixAPI,
story *signalpb.StoryMessage,
timestamp uint64,
caption string,
) *bridgev2.ConvertedMessage {
story = proto.Clone(story).(*signalpb.StoryMessage)
dm := &signalpb.DataMessage{
Timestamp: proto.Uint64(timestamp),
BodyRanges: story.GetBodyRanges(),
}
if att := story.GetFileAttachment(); att != nil {
dm.Attachments = []*signalpb.AttachmentPointer{att}
if att.GetCaption() != "" {
dm.Body = att.Caption
}
} else if text := story.GetTextAttachment(); text != nil {
dm.Body = proto.String(text.GetText())
if text.GetPreview() != nil {
dm.Preview = []*signalpb.Preview{text.GetPreview()}
if text.GetText() == "" {
dm.Body = proto.String(text.GetPreview().GetUrl())
}
}
}
if caption != "" {
prefixBody(dm, caption)
}
cm := mc.ToMatrix(ctx, client, portal, sender, intent, dm, nil)
cm.Disappear = database.DisappearingSetting{
Type: event.DisappearingTypeAfterSend,
Timer: StoryLifetime,
DisappearAt: time.UnixMilli(int64(timestamp)).Add(StoryLifetime),
}
return cm
}
func (mc *MessageConverter) convertStoryContextToMatrix(
ctx context.Context,
client *signalmeow.Client,
portal *bridgev2.Portal,
sender uuid.UUID,
intent bridgev2.MatrixAPI,
dm *signalpb.DataMessage,
attMap AttachmentMap,
) *bridgev2.ConvertedMessage {
storyAuthor, err := signalmeow.ParseStringOrBinaryUUID(dm.GetStoryContext().GetAuthorAci(), dm.GetStoryContext().GetAuthorAciBinary())
if err != nil {
zerolog.Ctx(ctx).Warn().Err(err).Msg("Failed to parse story context author")
}
storyID := signalid.MakeMessageID(storyAuthor, dm.GetStoryContext().GetSentTimestamp())
whose := "a story"
if storyAuthor == client.Store.ACI {
whose = "your story"
}
dm = proto.Clone(dm).(*signalpb.DataMessage)
dm.StoryContext = nil
if dm.Reaction != nil {
dm.Body = proto.String(fmt.Sprintf("Reacted %s to %s", dm.GetReaction().GetEmoji(), whose))
dm.Reaction = nil
} else if !mc.Bridge.Config.CrossRoomReplies {
prefixBody(dm, "Replied to "+whose)
}
cm := mc.ToMatrix(ctx, client, portal, sender, intent, dm, attMap)
cm.ReplyTo = &networkid.MessageOptionalPartID{MessageID: storyID}
return cm
}
func prefixBody(dm *signalpb.DataMessage, prefix string) {
if dm.GetBody() == "" {
dm.Body = proto.String(prefix)
return
}
prefix += ": "
shift := uint32(len(utf16.Encode([]rune(prefix))))
for _, br := range dm.BodyRanges {
br.Start = proto.Uint32(br.GetStart() + shift)
}
dm.Body = proto.String(prefix + dm.GetBody())
}