116 lines
3.2 KiB
Go
116 lines
3.2 KiB
Go
// mautrix-signal - A Matrix-Signal puppeting bridge.
|
|
// Copyright (C) 2023 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 signalfmt
|
|
|
|
import (
|
|
"context"
|
|
"html"
|
|
"strings"
|
|
|
|
"github.com/google/uuid"
|
|
"golang.org/x/exp/maps"
|
|
"golang.org/x/exp/slices"
|
|
"maunium.net/go/mautrix/event"
|
|
"maunium.net/go/mautrix/id"
|
|
|
|
signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
|
|
)
|
|
|
|
type UserInfo struct {
|
|
MXID id.UserID
|
|
Name string
|
|
}
|
|
|
|
type FormatParams struct {
|
|
GetUserInfo func(ctx context.Context, uuid uuid.UUID) UserInfo
|
|
}
|
|
|
|
type formatContext struct {
|
|
IsInCodeblock bool
|
|
}
|
|
|
|
func (ctx formatContext) TextToHTML(text string) string {
|
|
if ctx.IsInCodeblock {
|
|
return html.EscapeString(text)
|
|
}
|
|
return event.TextToHTML(text)
|
|
}
|
|
|
|
func Parse(ctx context.Context, message string, ranges []*signalpb.BodyRange, params *FormatParams) *event.MessageEventContent {
|
|
content := &event.MessageEventContent{
|
|
MsgType: event.MsgText,
|
|
Body: message,
|
|
Mentions: &event.Mentions{},
|
|
}
|
|
if len(ranges) == 0 {
|
|
return content
|
|
}
|
|
// LinkedRangeTree.Add depends on the ranges being sorted by increasing start index and then decreasing length.
|
|
slices.SortFunc(ranges, func(a, b *signalpb.BodyRange) int {
|
|
if *a.Start == *b.Start {
|
|
if *a.Length == *b.Length {
|
|
return 0
|
|
} else if *a.Length < *b.Length {
|
|
return 1
|
|
} else {
|
|
return -1
|
|
}
|
|
} else if *a.Start < *b.Start {
|
|
return -1
|
|
} else {
|
|
return 1
|
|
}
|
|
})
|
|
|
|
lrt := &LinkedRangeTree{}
|
|
mentions := map[id.UserID]struct{}{}
|
|
utf16Message := NewUTF16String(message)
|
|
maxLength := len(utf16Message)
|
|
for _, r := range ranges {
|
|
br := BodyRange{
|
|
Start: int(*r.Start),
|
|
Length: int(*r.Length),
|
|
}.TruncateEnd(maxLength)
|
|
switch rv := r.GetAssociatedValue().(type) {
|
|
case *signalpb.BodyRange_Style_:
|
|
br.Value = Style(rv.Style)
|
|
case *signalpb.BodyRange_MentionAci:
|
|
parsed, err := uuid.Parse(rv.MentionAci)
|
|
if err != nil {
|
|
continue
|
|
}
|
|
userInfo := params.GetUserInfo(ctx, parsed)
|
|
if userInfo.MXID == "" {
|
|
continue
|
|
}
|
|
mentions[userInfo.MXID] = struct{}{}
|
|
// This could replace the wrong thing if there's a mention without fffc.
|
|
// Maybe use NewUTF16String and do index replacements for the plaintext body too,
|
|
// or just replace the plaintext body by parsing the generated HTML.
|
|
content.Body = strings.Replace(content.Body, "\uFFFC", userInfo.Name, 1)
|
|
br.Value = Mention{UserInfo: userInfo, UUID: parsed}
|
|
}
|
|
lrt.Add(br)
|
|
}
|
|
|
|
content.Mentions.UserIDs = maps.Keys(mentions)
|
|
content.FormattedBody = lrt.Format(utf16Message, formatContext{})
|
|
content.Format = event.FormatHTML
|
|
//content.Body = format.HTMLToText(content.FormattedBody)
|
|
return content
|
|
}
|