forked from MirrorHub/mautrix-whatsapp
Add support for intentional mentions in outgoing messages
This commit is contained in:
parent
7aa0cc1b7a
commit
10aa66a128
3 changed files with 73 additions and 19 deletions
|
@ -1,3 +1,7 @@
|
||||||
|
# v0.8.6 (unreleased)
|
||||||
|
|
||||||
|
* Implemented intentional mentions for outgoing messages.
|
||||||
|
|
||||||
# v0.8.5 (2023-05-16)
|
# v0.8.5 (2023-05-16)
|
||||||
|
|
||||||
* Added option to disable reply fallbacks entirely.
|
* Added option to disable reply fallbacks entirely.
|
||||||
|
|
|
@ -20,10 +20,11 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"html"
|
"html"
|
||||||
"regexp"
|
"regexp"
|
||||||
|
"sort"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"go.mau.fi/whatsmeow/types"
|
"go.mau.fi/whatsmeow/types"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"maunium.net/go/mautrix/event"
|
"maunium.net/go/mautrix/event"
|
||||||
"maunium.net/go/mautrix/format"
|
"maunium.net/go/mautrix/format"
|
||||||
"maunium.net/go/mautrix/id"
|
"maunium.net/go/mautrix/id"
|
||||||
|
@ -36,7 +37,7 @@ var codeBlockRegex = regexp.MustCompile("```(?:.|\n)+?```")
|
||||||
var inlineURLRegex = regexp.MustCompile(`\[(.+?)]\((.+?)\)`)
|
var inlineURLRegex = regexp.MustCompile(`\[(.+?)]\((.+?)\)`)
|
||||||
|
|
||||||
const mentionedJIDsContextKey = "fi.mau.whatsapp.mentioned_jids"
|
const mentionedJIDsContextKey = "fi.mau.whatsapp.mentioned_jids"
|
||||||
const disableMentionsContextKey = "fi.mau.whatsapp.no_mentions"
|
const allowedMentionsContextKey = "fi.mau.whatsapp.allowed_mentions"
|
||||||
|
|
||||||
type Formatter struct {
|
type Formatter struct {
|
||||||
bridge *WABridge
|
bridge *WABridge
|
||||||
|
@ -56,17 +57,24 @@ func NewFormatter(bridge *WABridge) *Formatter {
|
||||||
Newline: "\n",
|
Newline: "\n",
|
||||||
|
|
||||||
PillConverter: func(displayname, mxid, eventID string, ctx format.Context) string {
|
PillConverter: func(displayname, mxid, eventID string, ctx format.Context) string {
|
||||||
_, disableMentions := ctx.ReturnData[disableMentionsContextKey]
|
allowedMentions, _ := ctx.ReturnData[allowedMentionsContextKey].(map[types.JID]bool)
|
||||||
if mxid[0] == '@' && !disableMentions {
|
if mxid[0] == '@' {
|
||||||
puppet := bridge.GetPuppetByMXID(id.UserID(mxid))
|
var jid types.JID
|
||||||
if puppet != nil {
|
if puppet := bridge.GetPuppetByMXID(id.UserID(mxid)); puppet != nil {
|
||||||
jids, ok := ctx.ReturnData[mentionedJIDsContextKey].([]string)
|
jid = puppet.JID
|
||||||
if !ok {
|
} else if user := bridge.GetUserByMXIDIfExists(id.UserID(mxid)); user != nil {
|
||||||
ctx.ReturnData[mentionedJIDsContextKey] = []string{puppet.JID.String()}
|
jid = user.JID.ToNonAD()
|
||||||
} else {
|
}
|
||||||
ctx.ReturnData[mentionedJIDsContextKey] = append(jids, puppet.JID.String())
|
if !jid.IsEmpty() && (allowedMentions == nil || allowedMentions[jid]) {
|
||||||
|
if allowedMentions == nil {
|
||||||
|
jids, ok := ctx.ReturnData[mentionedJIDsContextKey].([]string)
|
||||||
|
if !ok {
|
||||||
|
ctx.ReturnData[mentionedJIDsContextKey] = []string{jid.String()}
|
||||||
|
} else {
|
||||||
|
ctx.ReturnData[mentionedJIDsContextKey] = append(jids, jid.String())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return "@" + puppet.JID.User
|
return "@" + jid.User
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return displayname
|
return displayname
|
||||||
|
@ -143,7 +151,6 @@ func (formatter *Formatter) ParseWhatsApp(roomID id.RoomID, content *event.Messa
|
||||||
content.Mentions.UserIDs = append(content.Mentions.UserIDs, mxid)
|
content.Mentions.UserIDs = append(content.Mentions.UserIDs, mxid)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
content.UnstableMentions = content.Mentions
|
|
||||||
if output != content.Body || forceHTML {
|
if output != content.Body || forceHTML {
|
||||||
output = strings.ReplaceAll(output, "\n", "<br/>")
|
output = strings.ReplaceAll(output, "\n", "<br/>")
|
||||||
content.FormattedBody = output
|
content.FormattedBody = output
|
||||||
|
@ -154,15 +161,38 @@ func (formatter *Formatter) ParseWhatsApp(roomID id.RoomID, content *event.Messa
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formatter *Formatter) ParseMatrix(html string) (string, []string) {
|
func (formatter *Formatter) ParseMatrix(html string, mentions *event.Mentions) (string, []string) {
|
||||||
ctx := format.NewContext()
|
ctx := format.NewContext()
|
||||||
|
var mentionedJIDs []string
|
||||||
|
if mentions != nil {
|
||||||
|
var allowedMentions = make(map[types.JID]bool)
|
||||||
|
mentionedJIDs = make([]string, 0, len(mentions.UserIDs))
|
||||||
|
for _, userID := range mentions.UserIDs {
|
||||||
|
var jid types.JID
|
||||||
|
if puppet := formatter.bridge.GetPuppetByMXID(userID); puppet != nil {
|
||||||
|
jid = puppet.JID
|
||||||
|
mentionedJIDs = append(mentionedJIDs, puppet.JID.String())
|
||||||
|
} else if user := formatter.bridge.GetUserByMXIDIfExists(userID); user != nil {
|
||||||
|
jid = user.JID.ToNonAD()
|
||||||
|
}
|
||||||
|
if !jid.IsEmpty() && !allowedMentions[jid] {
|
||||||
|
allowedMentions[jid] = true
|
||||||
|
mentionedJIDs = append(mentionedJIDs, jid.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.ReturnData[allowedMentionsContextKey] = allowedMentions
|
||||||
|
}
|
||||||
result := formatter.matrixHTMLParser.Parse(html, ctx)
|
result := formatter.matrixHTMLParser.Parse(html, ctx)
|
||||||
mentionedJIDs, _ := ctx.ReturnData[mentionedJIDsContextKey].([]string)
|
if mentions == nil {
|
||||||
|
mentionedJIDs, _ = ctx.ReturnData[mentionedJIDsContextKey].([]string)
|
||||||
|
sort.Strings(mentionedJIDs)
|
||||||
|
mentionedJIDs = slices.Compact(mentionedJIDs)
|
||||||
|
}
|
||||||
return result, mentionedJIDs
|
return result, mentionedJIDs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formatter *Formatter) ParseMatrixWithoutMentions(html string) string {
|
func (formatter *Formatter) ParseMatrixWithoutMentions(html string) string {
|
||||||
ctx := format.NewContext()
|
ctx := format.NewContext()
|
||||||
ctx.ReturnData[disableMentionsContextKey] = true
|
ctx.ReturnData[allowedMentionsContextKey] = map[types.JID]struct{}{}
|
||||||
return formatter.matrixHTMLParser.Parse(html, ctx)
|
return formatter.matrixHTMLParser.Parse(html, ctx)
|
||||||
}
|
}
|
||||||
|
|
26
portal.go
26
portal.go
|
@ -43,6 +43,7 @@ import (
|
||||||
|
|
||||||
"github.com/chai2010/webp"
|
"github.com/chai2010/webp"
|
||||||
"github.com/tidwall/gjson"
|
"github.com/tidwall/gjson"
|
||||||
|
"golang.org/x/exp/slices"
|
||||||
"golang.org/x/image/draw"
|
"golang.org/x/image/draw"
|
||||||
"google.golang.org/protobuf/proto"
|
"google.golang.org/protobuf/proto"
|
||||||
|
|
||||||
|
@ -1880,6 +1881,22 @@ func (portal *Portal) MainIntent() *appservice.IntentAPI {
|
||||||
return portal.bridge.Bot
|
return portal.bridge.Bot
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) addReplyMention(content *event.MessageEventContent, sender types.JID) {
|
||||||
|
if content.Mentions == nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var mxid id.UserID
|
||||||
|
if user := portal.bridge.GetUserByJID(sender); user != nil {
|
||||||
|
mxid = user.MXID
|
||||||
|
} else {
|
||||||
|
puppet := portal.bridge.GetPuppetByJID(sender)
|
||||||
|
mxid = puppet.MXID
|
||||||
|
}
|
||||||
|
if slices.Contains(content.Mentions.UserIDs, mxid) {
|
||||||
|
content.Mentions.UserIDs = append(content.Mentions.UserIDs, mxid)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) SetReply(content *event.MessageEventContent, replyTo *ReplyInfo, isBackfill bool) bool {
|
func (portal *Portal) SetReply(content *event.MessageEventContent, replyTo *ReplyInfo, isBackfill bool) bool {
|
||||||
if replyTo == nil {
|
if replyTo == nil {
|
||||||
return false
|
return false
|
||||||
|
@ -1908,10 +1925,13 @@ func (portal *Portal) SetReply(content *event.MessageEventContent, replyTo *Repl
|
||||||
if message == nil || message.IsFakeMXID() {
|
if message == nil || message.IsFakeMXID() {
|
||||||
if isBackfill && portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry {
|
if isBackfill && portal.bridge.Config.Homeserver.Software == bridgeconfig.SoftwareHungry {
|
||||||
content.RelatesTo = (&event.RelatesTo{}).SetReplyTo(targetPortal.deterministicEventID(replyTo.Sender, replyTo.MessageID, ""))
|
content.RelatesTo = (&event.RelatesTo{}).SetReplyTo(targetPortal.deterministicEventID(replyTo.Sender, replyTo.MessageID, ""))
|
||||||
|
portal.addReplyMention(content, replyTo.Sender)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
// TODO store sender mxid in db message
|
||||||
|
portal.addReplyMention(content, message.Sender)
|
||||||
content.RelatesTo = (&event.RelatesTo{}).SetReplyTo(message.MXID)
|
content.RelatesTo = (&event.RelatesTo{}).SetReplyTo(message.MXID)
|
||||||
if portal.bridge.Config.Bridge.DisableReplyFallbacks {
|
if portal.bridge.Config.Bridge.DisableReplyFallbacks {
|
||||||
return true
|
return true
|
||||||
|
@ -3395,7 +3415,7 @@ func (portal *Portal) preprocessMatrixMedia(ctx context.Context, sender *User, r
|
||||||
hasHTMLCaption = content.Format == event.FormatHTML
|
hasHTMLCaption = content.Format == event.FormatHTML
|
||||||
}
|
}
|
||||||
if relaybotFormatted || hasHTMLCaption {
|
if relaybotFormatted || hasHTMLCaption {
|
||||||
caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
|
caption, mentionedJIDs = portal.bridge.Formatter.ParseMatrix(content.FormattedBody, content.Mentions)
|
||||||
}
|
}
|
||||||
|
|
||||||
var file *event.EncryptedFileInfo
|
var file *event.EncryptedFileInfo
|
||||||
|
@ -3621,7 +3641,7 @@ func (portal *Portal) msc1767ToWhatsApp(msg MSC1767Message, mentions bool) (stri
|
||||||
}
|
}
|
||||||
if msg.HTML != "" {
|
if msg.HTML != "" {
|
||||||
if mentions {
|
if mentions {
|
||||||
return portal.bridge.Formatter.ParseMatrix(msg.HTML)
|
return portal.bridge.Formatter.ParseMatrix(msg.HTML, nil)
|
||||||
} else {
|
} else {
|
||||||
return portal.bridge.Formatter.ParseMatrixWithoutMentions(msg.HTML), nil
|
return portal.bridge.Formatter.ParseMatrixWithoutMentions(msg.HTML), nil
|
||||||
}
|
}
|
||||||
|
@ -3858,7 +3878,7 @@ func (portal *Portal) convertMatrixMessage(ctx context.Context, sender *User, ev
|
||||||
return nil, sender, extraMeta, errMNoticeDisabled
|
return nil, sender, extraMeta, errMNoticeDisabled
|
||||||
}
|
}
|
||||||
if content.Format == event.FormatHTML {
|
if content.Format == event.FormatHTML {
|
||||||
text, ctxInfo.MentionedJid = portal.bridge.Formatter.ParseMatrix(content.FormattedBody)
|
text, ctxInfo.MentionedJid = portal.bridge.Formatter.ParseMatrix(content.FormattedBody, content.Mentions)
|
||||||
}
|
}
|
||||||
if content.MsgType == event.MsgEmote && !relaybotFormatted {
|
if content.MsgType == event.MsgEmote && !relaybotFormatted {
|
||||||
text = "/me " + text
|
text = "/me " + text
|
||||||
|
|
Loading…
Reference in a new issue