Skip to content

Commit

Permalink
msgconv: add support for m.mentions
Browse files Browse the repository at this point in the history
  • Loading branch information
tulir committed Jul 11, 2024
1 parent 3c78512 commit b8a1ba6
Show file tree
Hide file tree
Showing 8 changed files with 66 additions and 36 deletions.
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ require (
github.com/yuin/goldmark v1.7.4
go.mau.fi/util v0.5.1-0.20240708204011-043c35cda49c
gopkg.in/yaml.v3 v3.0.1
maunium.net/go/mautrix v0.19.0-beta.1.0.20240711083823-7f18d6b7358e
maunium.net/go/mautrix v0.19.0-beta.1.0.20240711110145-32e6f25c34b9
)

require (
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -73,5 +73,5 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
maunium.net/go/mauflag v1.0.0 h1:YiaRc0tEI3toYtJMRIfjP+jklH45uDHtT80nUamyD4M=
maunium.net/go/mauflag v1.0.0/go.mod h1:nLivPOpTpHnpzEh8jEdSL9UqO9+/KBJFmNRlwKfkPeA=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240711083823-7f18d6b7358e h1:DdWudfvcrUGs8BFre8ER00pfN1ROSWUi4z+JchNY+3s=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240711083823-7f18d6b7358e/go.mod h1:bNQrvIftiwJ+7OjSh+Gza5xcncq1ooHk6oyDWq4B4sg=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240711110145-32e6f25c34b9 h1:hlFgJUt9w1Z3lP7aTuY14gRQsFdJXFEa6Nv4NI8tMfk=
maunium.net/go/mautrix v0.19.0-beta.1.0.20240711110145-32e6f25c34b9/go.mod h1:bNQrvIftiwJ+7OjSh+Gza5xcncq1ooHk6oyDWq4B4sg=
58 changes: 31 additions & 27 deletions pkg/msgconv/blocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -74,16 +74,16 @@ func (mc *MessageConverter) renderImageBlock(ctx context.Context, portal *bridge
}, nil
}

func (mc *MessageConverter) mrkdwnToMatrixHtml(ctx context.Context, inputMrkdwn string) string {
output, _ := mc.SlackMrkdwnParser.Parse(ctx, inputMrkdwn)
func (mc *MessageConverter) mrkdwnToMatrixHtml(ctx context.Context, inputMrkdwn string, mentions *event.Mentions) string {
output, _ := mc.SlackMrkdwnParser.Parse(ctx, inputMrkdwn, mentions)
return output
}

func (mc *MessageConverter) renderSlackTextBlock(ctx context.Context, block slack.TextBlockObject) string {
func (mc *MessageConverter) renderSlackTextBlock(ctx context.Context, block slack.TextBlockObject, mentions *event.Mentions) string {
if block.Type == slack.PlainTextType {
return event.TextToHTML(block.Text)
} else if block.Type == slack.MarkdownType {
return mc.mrkdwnToMatrixHtml(ctx, block.Text)
return mc.mrkdwnToMatrixHtml(ctx, block.Text, mentions)
} else {
return ""
}
Expand Down Expand Up @@ -125,7 +125,7 @@ func closingTags(out io.StringWriter, style *slack.RichTextSectionTextStyle) {
}
}

func (mc *MessageConverter) renderRichTextSectionElements(ctx context.Context, elements []slack.RichTextSectionElement) string {
func (mc *MessageConverter) renderRichTextSectionElements(ctx context.Context, elements []slack.RichTextSectionElement, mentions *event.Mentions) string {
var htmlText strings.Builder
for _, element := range elements {
switch e := element.(type) {
Expand All @@ -135,6 +135,7 @@ func (mc *MessageConverter) renderRichTextSectionElements(ctx context.Context, e
closingTags(&htmlText, e.Style)
case *slack.RichTextSectionUserElement:
mxid, name := mc.GetMentionedUserInfo(ctx, e.UserID)
mentions.Add(mxid)
openingTags(&htmlText, e.Style)
mrkdwn.UserMentionToHTML(&htmlText, e.UserID, mxid, name)
closingTags(&htmlText, e.Style)
Expand All @@ -154,6 +155,7 @@ func (mc *MessageConverter) renderRichTextSectionElements(ctx context.Context, e
_, _ = fmt.Fprintf(&htmlText, `<a href="%s">%s</a>`, html.EscapeString(e.URL), event.TextToHTML(linkText))
closingTags(&htmlText, e.Style)
case *slack.RichTextSectionBroadcastElement:
mentions.Room = true
htmlText.WriteString("@room")
case *slack.RichTextSectionEmojiElement:
openingTags(&htmlText, e.Style)
Expand Down Expand Up @@ -189,16 +191,16 @@ func (mc *MessageConverter) renderRichTextSectionElements(ctx context.Context, e
return htmlText.String()
}

func (mc *MessageConverter) renderSlackBlock(ctx context.Context, block slack.Block) (string, bool) {
func (mc *MessageConverter) renderSlackBlock(ctx context.Context, block slack.Block, mentions *event.Mentions) (string, bool) {
switch b := block.(type) {
case *slack.HeaderBlock:
return fmt.Sprintf("<h1>%s</h1>", mc.renderSlackTextBlock(ctx, *b.Text)), false
return fmt.Sprintf("<h1>%s</h1>", mc.renderSlackTextBlock(ctx, *b.Text, mentions)), false
case *slack.DividerBlock:
return "<hr>", false
case *slack.SectionBlock:
var htmlParts []string
if b.Text != nil {
htmlParts = append(htmlParts, mc.renderSlackTextBlock(ctx, *b.Text))
htmlParts = append(htmlParts, mc.renderSlackTextBlock(ctx, *b.Text, mentions))
}
if len(b.Fields) > 0 {
var fieldTable strings.Builder
Expand All @@ -207,7 +209,7 @@ func (mc *MessageConverter) renderSlackBlock(ctx context.Context, block slack.Bl
if i%2 == 0 {
fieldTable.WriteString("<tr>")
}
fieldTable.WriteString(fmt.Sprintf("<td>%s</td>", mc.mrkdwnToMatrixHtml(ctx, field.Text)))
fieldTable.WriteString(fmt.Sprintf("<td>%s</td>", mc.mrkdwnToMatrixHtml(ctx, field.Text, mentions)))
if i%2 != 0 || i == len(b.Fields)-1 {
fieldTable.WriteString("</tr>")
}
Expand All @@ -219,15 +221,15 @@ func (mc *MessageConverter) renderSlackBlock(ctx context.Context, block slack.Bl
case *slack.RichTextBlock:
var htmlText strings.Builder
for _, element := range b.Elements {
htmlText.WriteString(mc.renderSlackRichTextElement(ctx, len(b.Elements), element))
htmlText.WriteString(mc.renderSlackRichTextElement(ctx, len(b.Elements), element, mentions))
}
return format.UnwrapSingleParagraph(htmlText.String()), false
case *slack.ContextBlock:
var htmlText strings.Builder
var unsupported bool = false
for _, element := range b.ContextElements.Elements {
if mrkdwnElem, ok := element.(*slack.TextBlockObject); ok {
htmlText.WriteString(fmt.Sprintf("<sup>%s</sup>", mc.mrkdwnToMatrixHtml(ctx, mrkdwnElem.Text)))
htmlText.WriteString(fmt.Sprintf("<sup>%s</sup>", mc.mrkdwnToMatrixHtml(ctx, mrkdwnElem.Text, mentions)))
} else {
zerolog.Ctx(ctx).Debug().
Type("element_type", element).
Expand All @@ -247,7 +249,7 @@ func (mc *MessageConverter) renderSlackBlock(ctx context.Context, block slack.Bl
}
}

func (mc *MessageConverter) renderSlackRichTextElement(ctx context.Context, numElements int, element slack.RichTextElement) string {
func (mc *MessageConverter) renderSlackRichTextElement(ctx context.Context, numElements int, element slack.RichTextElement, mentions *event.Mentions) string {
switch e := element.(type) {
case *slack.RichTextSection:
var htmlTag string
Expand All @@ -262,7 +264,7 @@ func (mc *MessageConverter) renderSlackRichTextElement(ctx context.Context, numE
htmlTag = "<p>"
htmlCloseTag = "</p>"
}
return fmt.Sprintf("%s%s%s", htmlTag, mc.renderRichTextSectionElements(ctx, e.Elements), htmlCloseTag)
return fmt.Sprintf("%s%s%s", htmlTag, mc.renderRichTextSectionElements(ctx, e.Elements, mentions), htmlCloseTag)
case *slack.RichTextList:
var htmlText strings.Builder
var htmlTag string
Expand All @@ -276,7 +278,7 @@ func (mc *MessageConverter) renderSlackRichTextElement(ctx context.Context, numE
}
htmlText.WriteString(htmlTag)
for _, e := range e.Elements {
htmlText.WriteString(fmt.Sprintf("<li>%s</li>", mc.renderSlackRichTextElement(ctx, 1, &e)))
htmlText.WriteString(fmt.Sprintf("<li>%s</li>", mc.renderSlackRichTextElement(ctx, 1, &e, mentions)))
}
htmlText.WriteString(htmlCloseTag)
return htmlText.String()
Expand All @@ -286,17 +288,17 @@ func (mc *MessageConverter) renderSlackRichTextElement(ctx context.Context, numE
}
}

func (mc *MessageConverter) blocksToHTML(ctx context.Context, blocks slack.Blocks, alwaysWrap bool) string {
func (mc *MessageConverter) blocksToHTML(ctx context.Context, blocks slack.Blocks, alwaysWrap bool, mentions *event.Mentions) string {
var htmlText strings.Builder

if len(blocks.BlockSet) == 1 && !alwaysWrap {
// don't wrap in <p> tag if there's only one block
text, _ := mc.renderSlackBlock(ctx, blocks.BlockSet[0])
text, _ := mc.renderSlackBlock(ctx, blocks.BlockSet[0], mentions)
htmlText.WriteString(text)
} else {
var lastBlockWasUnsupported bool = false
for _, block := range blocks.BlockSet {
text, unsupported := mc.renderSlackBlock(ctx, block)
text, unsupported := mc.renderSlackBlock(ctx, block, mentions)
if !(unsupported && lastBlockWasUnsupported) {
htmlText.WriteString(fmt.Sprintf("<p>%s</p>", text))
}
Expand Down Expand Up @@ -331,9 +333,10 @@ func (mc *MessageConverter) slackBlocksToMatrix(ctx context.Context, portal *bri
return mc.renderImageBlock(ctx, portal, intent, *imageBlock)
}

mentions := &event.Mentions{}
var htmlText strings.Builder

htmlText.WriteString(mc.blocksToHTML(ctx, blocks, false))
htmlText.WriteString(mc.blocksToHTML(ctx, blocks, false, mentions))

if len(attachments) > 0 && htmlText.String() != "" {
htmlText.WriteString("<br>")
Expand All @@ -342,18 +345,18 @@ func (mc *MessageConverter) slackBlocksToMatrix(ctx context.Context, portal *bri
for _, attachment := range attachments {
if attachment.IsMsgUnfurl {
for _, message_block := range attachment.MessageBlocks {
renderedAttachment := mc.blocksToHTML(ctx, message_block.Message.Blocks, true)
renderedAttachment := mc.blocksToHTML(ctx, message_block.Message.Blocks, true, mentions)
htmlText.WriteString(fmt.Sprintf("<blockquote><b>%s</b><br>%s<a href=\"%s\"><i>%s</i></a><br></blockquote>",
attachment.AuthorName, renderedAttachment, attachment.FromURL, attachment.Footer))
}
} else if len(attachment.Blocks.BlockSet) > 0 {
for _, message_block := range attachment.Blocks.BlockSet {
renderedAttachment, _ := mc.renderSlackBlock(ctx, message_block)
renderedAttachment, _ := mc.renderSlackBlock(ctx, message_block, mentions)
htmlText.WriteString(fmt.Sprintf("<blockquote>%s</blockquote>", renderedAttachment))
}
} else {
if len(attachment.Pretext) > 0 {
htmlText.WriteString(fmt.Sprintf("<p>%s</p>", mc.mrkdwnToMatrixHtml(ctx, attachment.Pretext)))
htmlText.WriteString(fmt.Sprintf("<p>%s</p>", mc.mrkdwnToMatrixHtml(ctx, attachment.Pretext, mentions)))
}
var attachParts []string
if len(attachment.AuthorName) > 0 {
Expand All @@ -367,15 +370,15 @@ func (mc *MessageConverter) slackBlocksToMatrix(ctx context.Context, portal *bri
if len(attachment.Title) > 0 {
if len(attachment.TitleLink) > 0 {
attachParts = append(attachParts, fmt.Sprintf("<b><a href=\"%s\">%s</a></b>",
attachment.TitleLink, mc.mrkdwnToMatrixHtml(ctx, attachment.Title)))
attachment.TitleLink, mc.mrkdwnToMatrixHtml(ctx, attachment.Title, mentions)))
} else {
attachParts = append(attachParts, fmt.Sprintf("<b>%s</b>", mc.mrkdwnToMatrixHtml(ctx, attachment.Title)))
attachParts = append(attachParts, fmt.Sprintf("<b>%s</b>", mc.mrkdwnToMatrixHtml(ctx, attachment.Title, mentions)))
}
}
if len(attachment.Text) > 0 {
attachParts = append(attachParts, mc.mrkdwnToMatrixHtml(ctx, attachment.Text))
attachParts = append(attachParts, mc.mrkdwnToMatrixHtml(ctx, attachment.Text, mentions))
} else if len(attachment.Fallback) > 0 {
attachParts = append(attachParts, mc.mrkdwnToMatrixHtml(ctx, attachment.Fallback))
attachParts = append(attachParts, mc.mrkdwnToMatrixHtml(ctx, attachment.Fallback, mentions))
}
htmlText.WriteString(fmt.Sprintf("<blockquote>%s", strings.Join(attachParts, "<br>")))
if len(attachment.Fields) > 0 {
Expand All @@ -386,7 +389,7 @@ func (mc *MessageConverter) slackBlocksToMatrix(ctx context.Context, portal *bri
fieldBody += "<tr>"
}
fieldBody += fmt.Sprintf("<td><strong>%s</strong><br>%s</td>",
field.Title, mc.mrkdwnToMatrixHtml(ctx, field.Value))
field.Title, mc.mrkdwnToMatrixHtml(ctx, field.Value, mentions))
short = !short && field.Short
if !short {
fieldBody += "</tr>"
Expand All @@ -398,7 +401,7 @@ func (mc *MessageConverter) slackBlocksToMatrix(ctx context.Context, portal *bri
}
var footerParts []string
if len(attachment.Footer) > 0 {
footerParts = append(footerParts, mc.mrkdwnToMatrixHtml(ctx, attachment.Footer))
footerParts = append(footerParts, mc.mrkdwnToMatrixHtml(ctx, attachment.Footer, mentions))
}
if len(attachment.Ts) > 0 {
ts, _ := attachment.Ts.Int64()
Expand All @@ -413,6 +416,7 @@ func (mc *MessageConverter) slackBlocksToMatrix(ctx context.Context, portal *bri
}

content := format.HTMLToContent(htmlText.String())
content.Mentions = mentions
return &bridgev2.ConvertedMessagePart{
Type: event.EventMessage,
Content: &content,
Expand Down
2 changes: 1 addition & 1 deletion pkg/msgconv/from-matrix.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ func (mc *MessageConverter) ToSlack(
case event.MsgText, event.MsgEmote, event.MsgNotice:
options := make([]slack.MsgOption, 0, 4)
if content.Format == event.FormatHTML {
options = append(options, slack.MsgOptionText(mc.MatrixHTMLParser.Parse(ctx, content.FormattedBody, portal), false))
options = append(options, slack.MsgOptionText(mc.MatrixHTMLParser.Parse(ctx, content.FormattedBody, content.Mentions, portal), false))
} else {
options = append(options,
slack.MsgOptionText(content.Body, false),
Expand Down
4 changes: 3 additions & 1 deletion pkg/msgconv/from-slack.go
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,9 @@ func (mc *MessageConverter) makeTextPart(ctx context.Context, msg *slack.Msg, po
}

func (mc *MessageConverter) slackTextToMatrix(ctx context.Context, text string) *bridgev2.ConvertedMessagePart {
content := format.HTMLToContent(mc.mrkdwnToMatrixHtml(ctx, text))
mentions := &event.Mentions{}
content := format.HTMLToContent(mc.mrkdwnToMatrixHtml(ctx, text, mentions))
content.Mentions = mentions
return &bridgev2.ConvertedMessagePart{
Type: event.EventMessage,
Content: &content,
Expand Down
16 changes: 14 additions & 2 deletions pkg/msgconv/matrixfmt/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,11 @@ package matrixfmt
import (
"context"
"fmt"
"slices"

"github.com/rs/zerolog"
"maunium.net/go/mautrix/bridgev2"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"
"maunium.net/go/mautrix/id"

Expand All @@ -33,11 +35,20 @@ type MatrixHTMLParser struct {
parser *format.HTMLParser
}

const ctxPortalKey = "portal"
const (
ctxPortalKey = "portal"
ctxAllowedMentionsKey = "allowed_mentions"
)

func (mhp *MatrixHTMLParser) pillConverter(displayname, mxid, eventID string, ctx format.Context) string {
switch mxid[0] {
case '@':
allowedMentions := ctx.ReturnData[ctxAllowedMentionsKey].(*event.Mentions)
if allowedMentions != nil && !slices.Contains(allowedMentions.UserIDs, id.UserID(mxid)) {
// If `m.mentions` is set and doesn't contain this user, don't convert the mention
// TODO does slack have some way to do silent mentions?
return displayname
}
ghostID, ok := mhp.br.Matrix.ParseGhostMXID(id.UserID(mxid))
if ok {
_, userID := slackid.ParseUserID(ghostID)
Expand Down Expand Up @@ -90,8 +101,9 @@ func New(br *bridgev2.Bridge) *MatrixHTMLParser {
return mhp
}

func (mhp *MatrixHTMLParser) Parse(ctx context.Context, htmlData string, portal *bridgev2.Portal) string {
func (mhp *MatrixHTMLParser) Parse(ctx context.Context, htmlData string, mentions *event.Mentions, portal *bridgev2.Portal) string {
formatCtx := format.NewContext(ctx)
formatCtx.ReturnData[ctxPortalKey] = portal
formatCtx.ReturnData[ctxAllowedMentionsKey] = mentions
return mhp.parser.Parse(htmlData, formatCtx)
}
4 changes: 3 additions & 1 deletion pkg/msgconv/mrkdwn/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (

"github.com/yuin/goldmark"
"github.com/yuin/goldmark/parser"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/format"

"go.mau.fi/mautrix-slack/pkg/msgconv/emoji"
Expand All @@ -44,9 +45,10 @@ func New(options *Params) *SlackMrkdwnParser {

var escapeFixer = regexp.MustCompile(`\\(__[^_]|\*\*[^*])`)

func (smp *SlackMrkdwnParser) Parse(ctx context.Context, input string) (string, error) {
func (smp *SlackMrkdwnParser) Parse(ctx context.Context, input string, mentions *event.Mentions) (string, error) {
parserCtx := parser.NewContext()
parserCtx.Set(ContextKeyContext, ctx)
parserCtx.Set(ContextKeyMentions, mentions)

input = emoji.ReplaceShortcodesWithUnicode(input)
// TODO is this actually needed or was it just blindly copied from Discord?
Expand Down
12 changes: 11 additions & 1 deletion pkg/msgconv/mrkdwn/tag.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ import (
"github.com/yuin/goldmark/renderer"
"github.com/yuin/goldmark/text"
goldmarkUtil "github.com/yuin/goldmark/util"
"maunium.net/go/mautrix/event"
"maunium.net/go/mautrix/id"
)

Expand Down Expand Up @@ -131,7 +132,10 @@ func (s *slackTagParser) Trigger() []byte {
return []byte{'<'}
}

var ContextKeyContext = parser.NewContextKey()
var (
ContextKeyContext = parser.NewContextKey()
ContextKeyMentions = parser.NewContextKey()
)

func (s *slackTagParser) Parse(parent ast.Node, block text.Reader, pc parser.Context) ast.Node {
//before := block.PrecendingCharacter()
Expand All @@ -153,11 +157,17 @@ func (s *slackTagParser) Parse(parent ast.Node, block text.Reader, pc parser.Con
switch sigil {
case "@":
mxid, name := s.GetUserInfo(ctx, content)
pc.Get(ContextKeyMentions).(*event.Mentions).Add(mxid)
return &astSlackUserMention{astSlackTag: tag, userID: content, mxid: mxid, name: name}
case "#":
mxid, alias, name := s.GetChannelInfo(ctx, content)
return &astSlackChannelMention{astSlackTag: tag, channelID: content, serverName: s.ServerName, mxid: mxid, alias: alias, name: name}
case "!":
switch content {
case "channel", "everyone", "here":
pc.Get(ContextKeyMentions).(*event.Mentions).Room = true
default:
}
return &astSlackSpecialMention{astSlackTag: tag, content: content}
case "":
return &astSlackURL{astSlackTag: tag, url: content}
Expand Down

0 comments on commit b8a1ba6

Please sign in to comment.