Skip to content

Commit

Permalink
Add support for Matrix -> Signal formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Dec 21, 2023
1 parent 3c0482e commit 8260ba1
Show file tree
Hide file tree
Showing 11 changed files with 792 additions and 112 deletions.
2 changes: 1 addition & 1 deletion ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
* Matrix → Signal
* [ ] Message content
* [x] Text
* [ ] Formatting
* [x] Formatting
* [x] Mentions
* [ ] Media
* [x] Images
Expand Down
17 changes: 16 additions & 1 deletion main.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import (

"go.mau.fi/mautrix-signal/config"
"go.mau.fi/mautrix-signal/database"
"go.mau.fi/mautrix-signal/msgconv/matrixfmt"
"go.mau.fi/mautrix-signal/msgconv/signalfmt"
"go.mau.fi/mautrix-signal/pkg/signalmeow"
)
Expand Down Expand Up @@ -116,7 +117,7 @@ func (br *SignalBridge) Init() {
br.Metrics = NewMetricsHandler(br.Config.Metrics.Listen, br.Log.Sub("Metrics"), br.DB)
br.MatrixHandler.TrackEventDuration = br.Metrics.TrackMatrixEvent

formatParams = &signalfmt.FormatParams{
signalFormatParams = &signalfmt.FormatParams{
GetUserInfo: func(uuid string) signalfmt.UserInfo {
puppet := br.GetPuppetBySignalID(uuid)
if puppet == nil {
Expand All @@ -135,6 +136,20 @@ func (br *SignalBridge) Init() {
}
},
}
matrixFormatParams = &matrixfmt.HTMLParser{
GetUUIDFromMXID: func(userID id.UserID) string {
parsed, ok := br.ParsePuppetMXID(userID)
if ok {
return parsed
}
// TODO only get if exists
user := br.GetUserByMXID(userID)
if user != nil && user.SignalID != "" {
return user.SignalID
}
return ""
},
}

signalmeow.HackyCaptionToggle = br.Config.Bridge.CaptionInMessage
}
Expand Down
43 changes: 43 additions & 0 deletions msgconv/matrixfmt/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// 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 matrixfmt

import (
"maunium.net/go/mautrix/event"

signalpb "go.mau.fi/mautrix-signal/pkg/signalmeow/protobuf"
)

func Parse(parser *HTMLParser, content *event.MessageEventContent) (string, []*signalpb.BodyRange) {
if content.Format != event.FormatHTML {
return content.Body, nil
}
ctx := NewContext()
ctx.AllowedMentions = content.Mentions
parsed := parser.Parse(content.FormattedBody, ctx)
if parsed == nil {
return "", nil
}
var bodyRanges []*signalpb.BodyRange
if len(parsed.Entities) > 0 {
bodyRanges = make([]*signalpb.BodyRange, len(parsed.Entities))
for i, ent := range parsed.Entities {
bodyRanges[i] = ent.Proto()
}
}
return parsed.String.String(), bodyRanges
}
159 changes: 159 additions & 0 deletions msgconv/matrixfmt/convert_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package matrixfmt_test

import (
"fmt"
"testing"

"github.com/stretchr/testify/assert"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"

"go.mau.fi/mautrix-signal/msgconv/matrixfmt"
"go.mau.fi/mautrix-signal/msgconv/signalfmt"
)

var formatParams = &matrixfmt.HTMLParser{
GetUUIDFromMXID: func(id id.UserID) string {
if id.Homeserver() == "signal" {
return id.Localpart()
}
return ""
},
}

func TestParse_Empty(t *testing.T) {
text, entities := matrixfmt.Parse(formatParams, &event.MessageEventContent{
MsgType: event.MsgText,
Body: "",
})
assert.Equal(t, "", text)
assert.Empty(t, entities)
}

func TestParse_EmptyHTML(t *testing.T) {
text, entities := matrixfmt.Parse(formatParams, &event.MessageEventContent{
MsgType: event.MsgText,
Body: "",
Format: event.FormatHTML,
FormattedBody: "",
})
assert.Equal(t, "", text)
assert.Empty(t, entities)
}

func TestParse_Plaintext(t *testing.T) {
text, entities := matrixfmt.Parse(formatParams, &event.MessageEventContent{
MsgType: event.MsgText,
Body: "Hello world!",
})
assert.Equal(t, "Hello world!", text)
assert.Empty(t, entities)
}

func TestParse_HTML(t *testing.T) {
tests := []struct {
name string
in string
out string
ent signalfmt.BodyRangeList
}{
{name: "Plain", in: "Hello, World!", out: "Hello, World!"},
{name: "Basic", in: "<strong>Hello</strong>, World!", out: "Hello, World!", ent: signalfmt.BodyRangeList{{
Start: 0,
Length: 5,
Value: signalfmt.StyleBold,
}}},
{
name: "MultiBasic",
in: "<strong><em>Hell</em>o</strong>, <del>Wo<span data-mx-spoiler>rld</span></del><code>!</code>",
out: "Hello, World!",
ent: signalfmt.BodyRangeList{{
Start: 0,
Length: 5,
Value: signalfmt.StyleBold,
}, {
Start: 0,
Length: 4,
Value: signalfmt.StyleItalic,
}, {
Start: 7,
Length: 5,
Value: signalfmt.StyleStrikethrough,
}, {
Start: 9,
Length: 3,
Value: signalfmt.StyleSpoiler,
}, {
Start: 12,
Length: 1,
Value: signalfmt.StyleMonospace,
}},
},
{
name: "TrimSpace",
in: "<strong> Hello </strong>",
out: "Hello",
ent: signalfmt.BodyRangeList{{
Start: 0,
Length: 5,
Value: signalfmt.StyleBold,
}},
},
{
name: "List",
in: "<ul><li>woof</li><li><strong>meow</strong></li><li><pre><code>hmm\nmeow</code></pre></li><li><blockquote>meow<br><h1>meow</h1></blockquote></li></ul>",
out: "* woof\n* meow\n* hmm\n meow\n* > meow\n > \n > # meow",
ent: signalfmt.BodyRangeList{{
Start: 9,
Length: 4,
Value: signalfmt.StyleBold,
}, {
Start: 16,
Length: 3,
Value: signalfmt.StyleMonospace,
}, {
// FIXME optimally this would be a single range with the previous one so the indent is also monospace
Start: 22,
Length: 4,
Value: signalfmt.StyleMonospace,
}, {
Start: 45,
Length: 6,
Value: signalfmt.StyleBold,
}},
},
{
name: "OrderedList",
in: "<ol start=9><li>woof</li><li><strong>meow</strong></li><li><pre><code>hmm\nmeow</code></pre></li><li><blockquote>meow<br><h1>meow</h1></blockquote></li></ol>",
out: "9. woof\n10. meow\n11. hmm\n meow\n12. > meow\n > \n > # meow",
ent: signalfmt.BodyRangeList{{
Start: 13,
Length: 4,
Value: signalfmt.StyleBold,
}, {
Start: 22,
Length: 3,
Value: signalfmt.StyleMonospace,
}, {
Start: 30,
Length: 4,
Value: signalfmt.StyleMonospace,
}, {
Start: 59,
Length: 6,
Value: signalfmt.StyleBold,
}},
},
}
matrixfmt.DebugLog = func(format string, args ...any) {
fmt.Printf(format, args...)
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
fmt.Println("--------------------------------------------------------------------------------")
parsed := formatParams.Parse(test.in, matrixfmt.NewContext())
assert.Equal(t, test.out, parsed.String.String())
assert.Equal(t, test.ent, parsed.Entities)
})
}
}
Loading

0 comments on commit 8260ba1

Please sign in to comment.