mirror of
https://github.com/tulir/mautrix-whatsapp
synced 2024-12-15 01:43:49 +01:00
Add basic support for business messages
This commit is contained in:
parent
d6db16a4e2
commit
443ff97e7a
6 changed files with 93 additions and 8 deletions
|
@ -94,7 +94,7 @@ bridge:
|
||||||
# The following variables are also available, but will cause problems on multi-user instances:
|
# The following variables are also available, but will cause problems on multi-user instances:
|
||||||
# {{.FullName}} - full name from contact list
|
# {{.FullName}} - full name from contact list
|
||||||
# {{.FirstName}} - first name from contact list
|
# {{.FirstName}} - first name from contact list
|
||||||
displayname_template: "{{if .PushName}}{{.PushName}}{{else if .BusinessName}}{{.BusinessName}}{{else}}{{.JID}}{{end}} (WA)"
|
displayname_template: "{{if .BusinessName}}{{.BusinessName}}{{else if .PushName}}{{.PushName}}{{else}}{{.JID}}{{end}} (WA)"
|
||||||
# Should the bridge create a space for each logged-in user and add bridged rooms to it?
|
# Should the bridge create a space for each logged-in user and add bridged rooms to it?
|
||||||
# Users who logged in before turning this on should run `!wa sync space` to create and fill the space for the first time.
|
# Users who logged in before turning this on should run `!wa sync space` to create and fill the space for the first time.
|
||||||
personal_filtering_spaces: false
|
personal_filtering_spaces: false
|
||||||
|
|
|
@ -33,6 +33,7 @@ var italicRegex = regexp.MustCompile("([\\s>~*]|^)_(.+?)_([^a-zA-Z\\d]|$)")
|
||||||
var boldRegex = regexp.MustCompile("([\\s>_~]|^)\\*(.+?)\\*([^a-zA-Z\\d]|$)")
|
var boldRegex = regexp.MustCompile("([\\s>_~]|^)\\*(.+?)\\*([^a-zA-Z\\d]|$)")
|
||||||
var strikethroughRegex = regexp.MustCompile("([\\s>_*]|^)~(.+?)~([^a-zA-Z\\d]|$)")
|
var strikethroughRegex = regexp.MustCompile("([\\s>_*]|^)~(.+?)~([^a-zA-Z\\d]|$)")
|
||||||
var codeBlockRegex = regexp.MustCompile("```(?:.|\n)+?```")
|
var codeBlockRegex = regexp.MustCompile("```(?:.|\n)+?```")
|
||||||
|
var inlineURLRegex = regexp.MustCompile(`\[(.+?)]\((.+?)\)`)
|
||||||
|
|
||||||
const mentionedJIDsContextKey = "net.maunium.whatsapp.mentioned_jids"
|
const mentionedJIDsContextKey = "net.maunium.whatsapp.mentioned_jids"
|
||||||
|
|
||||||
|
@ -108,7 +109,7 @@ func (formatter *Formatter) getMatrixInfoByJID(roomID id.RoomID, jid types.JID)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (formatter *Formatter) ParseWhatsApp(roomID id.RoomID, content *event.MessageEventContent, mentionedJIDs []string) {
|
func (formatter *Formatter) ParseWhatsApp(roomID id.RoomID, content *event.MessageEventContent, mentionedJIDs []string, allowInlineURL bool) {
|
||||||
output := html.EscapeString(content.Body)
|
output := html.EscapeString(content.Body)
|
||||||
for regex, replacement := range formatter.waReplString {
|
for regex, replacement := range formatter.waReplString {
|
||||||
output = regex.ReplaceAllString(output, replacement)
|
output = regex.ReplaceAllString(output, replacement)
|
||||||
|
@ -116,6 +117,12 @@ func (formatter *Formatter) ParseWhatsApp(roomID id.RoomID, content *event.Messa
|
||||||
for regex, replacer := range formatter.waReplFunc {
|
for regex, replacer := range formatter.waReplFunc {
|
||||||
output = regex.ReplaceAllStringFunc(output, replacer)
|
output = regex.ReplaceAllStringFunc(output, replacer)
|
||||||
}
|
}
|
||||||
|
if allowInlineURL {
|
||||||
|
output = inlineURLRegex.ReplaceAllStringFunc(output, func(s string) string {
|
||||||
|
groups := inlineURLRegex.FindStringSubmatch(s)
|
||||||
|
return fmt.Sprintf(`<a href="%s">%s</a>`, groups[2], groups[1])
|
||||||
|
})
|
||||||
|
}
|
||||||
for _, rawJID := range mentionedJIDs {
|
for _, rawJID := range mentionedJIDs {
|
||||||
jid, err := types.ParseJID(rawJID)
|
jid, err := types.ParseJID(rawJID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
2
go.mod
2
go.mod
|
@ -10,7 +10,7 @@ require (
|
||||||
github.com/prometheus/client_golang v1.12.2-0.20220613221938-ebd77f036066
|
github.com/prometheus/client_golang v1.12.2-0.20220613221938-ebd77f036066
|
||||||
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e
|
||||||
github.com/tidwall/gjson v1.14.1
|
github.com/tidwall/gjson v1.14.1
|
||||||
go.mau.fi/whatsmeow v0.0.0-20220622155743-bfb10aff31b9
|
go.mau.fi/whatsmeow v0.0.0-20220624184947-57a69a641154
|
||||||
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
golang.org/x/image v0.0.0-20220413100746-70e8d0d3baa9
|
||||||
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
golang.org/x/net v0.0.0-20220513224357-95641704303c
|
||||||
google.golang.org/protobuf v1.28.0
|
google.golang.org/protobuf v1.28.0
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -62,8 +62,8 @@ github.com/yuin/goldmark v1.4.12 h1:6hffw6vALvEDqJ19dOJvJKOoAOKe4NDaTqvd2sktGN0=
|
||||||
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
github.com/yuin/goldmark v1.4.12/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
|
||||||
go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0 h1:3IQF2bgAyibdo77hTejwuJe4jlypj9QaE4xCQuxrThM=
|
go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0 h1:3IQF2bgAyibdo77hTejwuJe4jlypj9QaE4xCQuxrThM=
|
||||||
go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0/go.mod h1:kBOXTvYyDG/q1Ihgvd4J6WenGPh7wtEGvPKF6vmf5ak=
|
go.mau.fi/libsignal v0.0.0-20220425070825-c40c839ee6a0/go.mod h1:kBOXTvYyDG/q1Ihgvd4J6WenGPh7wtEGvPKF6vmf5ak=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20220622155743-bfb10aff31b9 h1:XJhWnHI8dB2oSNwpfbpbg1jpeLWKl0aht8fy/vo50ew=
|
go.mau.fi/whatsmeow v0.0.0-20220624184947-57a69a641154 h1:jUe0Re+w8/YHfxYryxjVkG3PEQDujCzGhbqsk6Qadtg=
|
||||||
go.mau.fi/whatsmeow v0.0.0-20220622155743-bfb10aff31b9/go.mod h1:iUBgOLNaqShLrR17u0kIiRptIGFH+nbT1tRhaWBEX/c=
|
go.mau.fi/whatsmeow v0.0.0-20220624184947-57a69a641154/go.mod h1:iUBgOLNaqShLrR17u0kIiRptIGFH+nbT1tRhaWBEX/c=
|
||||||
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220411220226-7b82a4e95df4/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9 h1:NUzdAbFtCJSXU20AOXgeqaUwg8Ypg4MPYmL+d+rsB5c=
|
||||||
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
golang.org/x/crypto v0.0.0-20220513210258-46612604a0f9/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
|
||||||
|
|
82
portal.go
82
portal.go
|
@ -351,7 +351,8 @@ func containsSupportedMessage(waMsg *waProto.Message) bool {
|
||||||
return waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil || waMsg.ImageMessage != nil ||
|
return waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil || waMsg.ImageMessage != nil ||
|
||||||
waMsg.StickerMessage != nil || waMsg.AudioMessage != nil || waMsg.VideoMessage != nil ||
|
waMsg.StickerMessage != nil || waMsg.AudioMessage != nil || waMsg.VideoMessage != nil ||
|
||||||
waMsg.DocumentMessage != nil || waMsg.ContactMessage != nil || waMsg.LocationMessage != nil ||
|
waMsg.DocumentMessage != nil || waMsg.ContactMessage != nil || waMsg.LocationMessage != nil ||
|
||||||
waMsg.LiveLocationMessage != nil || waMsg.GroupInviteMessage != nil || waMsg.ContactsArrayMessage != nil
|
waMsg.LiveLocationMessage != nil || waMsg.GroupInviteMessage != nil || waMsg.ContactsArrayMessage != nil ||
|
||||||
|
waMsg.HighlyStructuredMessage != nil || waMsg.TemplateMessage != nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMessageType(waMsg *waProto.Message) string {
|
func getMessageType(waMsg *waProto.Message) string {
|
||||||
|
@ -483,6 +484,10 @@ func (portal *Portal) convertMessage(intent *appservice.IntentAPI, source *User,
|
||||||
switch {
|
switch {
|
||||||
case waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil:
|
case waMsg.Conversation != nil || waMsg.ExtendedTextMessage != nil:
|
||||||
return portal.convertTextMessage(intent, source, waMsg)
|
return portal.convertTextMessage(intent, source, waMsg)
|
||||||
|
case waMsg.TemplateMessage != nil:
|
||||||
|
return portal.convertTemplateMessage(intent, source, info, waMsg.GetTemplateMessage())
|
||||||
|
case waMsg.HighlyStructuredMessage != nil:
|
||||||
|
return portal.convertTemplateMessage(intent, source, info, waMsg.GetHighlyStructuredMessage().GetHydratedHsm())
|
||||||
case waMsg.ImageMessage != nil:
|
case waMsg.ImageMessage != nil:
|
||||||
return portal.convertMediaMessage(intent, source, info, waMsg.GetImageMessage(), "photo", isBackfill)
|
return portal.convertMediaMessage(intent, source, info, waMsg.GetImageMessage(), "photo", isBackfill)
|
||||||
case waMsg.StickerMessage != nil:
|
case waMsg.StickerMessage != nil:
|
||||||
|
@ -1710,7 +1715,7 @@ func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, source *U
|
||||||
}
|
}
|
||||||
|
|
||||||
contextInfo := msg.GetExtendedTextMessage().GetContextInfo()
|
contextInfo := msg.GetExtendedTextMessage().GetContextInfo()
|
||||||
portal.bridge.Formatter.ParseWhatsApp(portal.MXID, content, contextInfo.GetMentionedJid())
|
portal.bridge.Formatter.ParseWhatsApp(portal.MXID, content, contextInfo.GetMentionedJid(), false)
|
||||||
replyTo := contextInfo.GetStanzaId()
|
replyTo := contextInfo.GetStanzaId()
|
||||||
expiresIn := contextInfo.GetExpiration()
|
expiresIn := contextInfo.GetExpiration()
|
||||||
extraAttrs := map[string]interface{}{}
|
extraAttrs := map[string]interface{}{}
|
||||||
|
@ -1726,6 +1731,77 @@ func (portal *Portal) convertTextMessage(intent *appservice.IntentAPI, source *U
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (portal *Portal) convertTemplateMessage(intent *appservice.IntentAPI, source *User, info *types.MessageInfo, tplMsg *waProto.TemplateMessage) *ConvertedMessage {
|
||||||
|
converted := &ConvertedMessage{
|
||||||
|
Intent: intent,
|
||||||
|
Type: event.EventMessage,
|
||||||
|
Content: &event.MessageEventContent{
|
||||||
|
Body: "Unsupported business message",
|
||||||
|
MsgType: event.MsgText,
|
||||||
|
},
|
||||||
|
ReplyTo: tplMsg.GetContextInfo().GetStanzaId(),
|
||||||
|
ExpiresIn: tplMsg.GetContextInfo().GetExpiration(),
|
||||||
|
}
|
||||||
|
|
||||||
|
tpl := tplMsg.GetHydratedTemplate()
|
||||||
|
if tpl == nil {
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
content := tpl.GetHydratedContentText()
|
||||||
|
if buttons := tpl.GetHydratedButtons(); len(buttons) > 0 {
|
||||||
|
addButtonText := false
|
||||||
|
descriptions := make([]string, len(buttons))
|
||||||
|
for i, rawButton := range buttons {
|
||||||
|
switch button := rawButton.GetHydratedButton().(type) {
|
||||||
|
case *waProto.HydratedTemplateButton_QuickReplyButton:
|
||||||
|
descriptions[i] = fmt.Sprintf("<%s>", button.QuickReplyButton.GetDisplayText())
|
||||||
|
addButtonText = true
|
||||||
|
case *waProto.HydratedTemplateButton_UrlButton:
|
||||||
|
descriptions[i] = fmt.Sprintf("[%s](%s)", button.UrlButton.GetDisplayText(), button.UrlButton.GetUrl())
|
||||||
|
case *waProto.HydratedTemplateButton_CallButton:
|
||||||
|
descriptions[i] = fmt.Sprintf("[%s](tel:%s)", button.CallButton.GetDisplayText(), button.CallButton.GetPhoneNumber())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
description := strings.Join(descriptions, " - ")
|
||||||
|
if addButtonText {
|
||||||
|
description += "\nUse the WhatsApp app to click buttons"
|
||||||
|
}
|
||||||
|
content = fmt.Sprintf("%s\n\n%s", content, description)
|
||||||
|
}
|
||||||
|
if footer := tpl.GetHydratedFooterText(); footer != "" {
|
||||||
|
content = fmt.Sprintf("%s\n\n%s", content, footer)
|
||||||
|
}
|
||||||
|
|
||||||
|
var convertedTitle *ConvertedMessage
|
||||||
|
switch title := tpl.GetTitle().(type) {
|
||||||
|
case *waProto.HydratedFourRowTemplate_DocumentMessage:
|
||||||
|
convertedTitle = portal.convertMediaMessage(intent, source, info, title.DocumentMessage, "file attachment", false)
|
||||||
|
case *waProto.HydratedFourRowTemplate_ImageMessage:
|
||||||
|
convertedTitle = portal.convertMediaMessage(intent, source, info, title.ImageMessage, "photo", false)
|
||||||
|
case *waProto.HydratedFourRowTemplate_VideoMessage:
|
||||||
|
convertedTitle = portal.convertMediaMessage(intent, source, info, title.VideoMessage, "video attachment", false)
|
||||||
|
case *waProto.HydratedFourRowTemplate_LocationMessage:
|
||||||
|
content = fmt.Sprintf("Unsupported location message\n\n%s", content)
|
||||||
|
case *waProto.HydratedFourRowTemplate_HydratedTitleText:
|
||||||
|
content = fmt.Sprintf("%s\n\n%s", title.HydratedTitleText, content)
|
||||||
|
}
|
||||||
|
|
||||||
|
converted.Content.Body = content
|
||||||
|
portal.bridge.Formatter.ParseWhatsApp(portal.MXID, converted.Content, nil, true)
|
||||||
|
if convertedTitle != nil {
|
||||||
|
converted.MediaKey = convertedTitle.MediaKey
|
||||||
|
converted.Extra = convertedTitle.Extra
|
||||||
|
converted.Caption = converted.Content
|
||||||
|
converted.Content = convertedTitle.Content
|
||||||
|
converted.Error = convertedTitle.Error
|
||||||
|
}
|
||||||
|
if converted.Extra == nil {
|
||||||
|
converted.Extra = make(map[string]interface{})
|
||||||
|
}
|
||||||
|
converted.Extra["fi.mau.whatsapp.hydrated_template_id"] = tpl.GetTemplateId()
|
||||||
|
return converted
|
||||||
|
}
|
||||||
|
|
||||||
func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, msg *waProto.LiveLocationMessage) *ConvertedMessage {
|
func (portal *Portal) convertLiveLocationMessage(intent *appservice.IntentAPI, msg *waProto.LiveLocationMessage) *ConvertedMessage {
|
||||||
content := &event.MessageEventContent{
|
content := &event.MessageEventContent{
|
||||||
Body: "Started sharing live location",
|
Body: "Started sharing live location",
|
||||||
|
@ -2232,7 +2308,7 @@ func (portal *Portal) convertMediaMessageContent(intent *appservice.IntentAPI, m
|
||||||
MsgType: event.MsgNotice,
|
MsgType: event.MsgNotice,
|
||||||
}
|
}
|
||||||
|
|
||||||
portal.bridge.Formatter.ParseWhatsApp(portal.MXID, captionContent, msg.GetContextInfo().GetMentionedJid())
|
portal.bridge.Formatter.ParseWhatsApp(portal.MXID, captionContent, msg.GetContextInfo().GetMentionedJid(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
return &ConvertedMessage{
|
return &ConvertedMessage{
|
||||||
|
|
2
user.go
2
user.go
|
@ -721,6 +721,8 @@ func (user *User) HandleEvent(event interface{}) {
|
||||||
go user.syncPuppet(v.JID, "contact event")
|
go user.syncPuppet(v.JID, "contact event")
|
||||||
case *events.PushName:
|
case *events.PushName:
|
||||||
go user.syncPuppet(v.JID, "push name event")
|
go user.syncPuppet(v.JID, "push name event")
|
||||||
|
case *events.BusinessName:
|
||||||
|
go user.syncPuppet(v.JID, "business name event")
|
||||||
case *events.GroupInfo:
|
case *events.GroupInfo:
|
||||||
user.groupListCache = nil
|
user.groupListCache = nil
|
||||||
go user.handleGroupUpdate(v)
|
go user.handleGroupUpdate(v)
|
||||||
|
|
Loading…
Reference in a new issue